Commit 317dc057 authored by georgia.kouveli's avatar georgia.kouveli Committed by Commit bot

[arm64] Generate adds/ands.

Perform the following transformation:

    | Before           | After               |
    |------------------+---------------------|
    | add w2, w0, w1   | adds w2, w0, w1     |
    | cmp w2, #0x0     | b.<cond'> <addr>    |
    | b.<cond> <addr>  |                     |
    |------------------+---------------------|
    | add w2, w0, w1   | adds w2, w0, w1     |
    | cmp #0x0, w2     | b.<cond'> <addr>    |
    | b.<cond> <addr>  |                     |

and the same for and instructions instead of add.  When the result of the
add/and is not used, generate cmn/tst instead. We need to take care with which
conditions we can handle and what new condition we map them to.

BUG=

Review-Url: https://codereview.chromium.org/2065243005
Cr-Commit-Position: refs/heads/master@{#37400}
parent f58dd088
...@@ -296,6 +296,10 @@ Condition FlagsConditionToCondition(FlagsCondition condition) { ...@@ -296,6 +296,10 @@ Condition FlagsConditionToCondition(FlagsCondition condition) {
return vs; return vs;
case kNotOverflow: case kNotOverflow:
return vc; return vc;
case kPositiveOrZero:
return pl;
case kNegative:
return mi;
default: default:
break; break;
} }
......
...@@ -394,6 +394,10 @@ Condition FlagsConditionToCondition(FlagsCondition condition) { ...@@ -394,6 +394,10 @@ Condition FlagsConditionToCondition(FlagsCondition condition) {
case kUnorderedEqual: case kUnorderedEqual:
case kUnorderedNotEqual: case kUnorderedNotEqual:
break; break;
case kPositiveOrZero:
return pl;
case kNegative:
return mi;
} }
UNREACHABLE(); UNREACHABLE();
return nv; return nv;
...@@ -897,12 +901,34 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction( ...@@ -897,12 +901,34 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
} }
break; break;
case kArm64And: case kArm64And:
__ And(i.OutputRegister(), i.InputOrZeroRegister64(0), if (FlagsModeField::decode(opcode) != kFlags_none) {
i.InputOperand2_64(1)); // The ands instruction only sets N and Z, so only the following
// conditions make sense.
DCHECK(FlagsConditionField::decode(opcode) == kEqual ||
FlagsConditionField::decode(opcode) == kNotEqual ||
FlagsConditionField::decode(opcode) == kPositiveOrZero ||
FlagsConditionField::decode(opcode) == kNegative);
__ Ands(i.OutputRegister(), i.InputOrZeroRegister64(0),
i.InputOperand2_64(1));
} else {
__ And(i.OutputRegister(), i.InputOrZeroRegister64(0),
i.InputOperand2_64(1));
}
break; break;
case kArm64And32: case kArm64And32:
__ And(i.OutputRegister32(), i.InputOrZeroRegister32(0), if (FlagsModeField::decode(opcode) != kFlags_none) {
i.InputOperand2_32(1)); // The ands instruction only sets N and Z, so only the following
// conditions make sense.
DCHECK(FlagsConditionField::decode(opcode) == kEqual ||
FlagsConditionField::decode(opcode) == kNotEqual ||
FlagsConditionField::decode(opcode) == kPositiveOrZero ||
FlagsConditionField::decode(opcode) == kNegative);
__ Ands(i.OutputRegister32(), i.InputOrZeroRegister32(0),
i.InputOperand2_32(1));
} else {
__ And(i.OutputRegister32(), i.InputOrZeroRegister32(0),
i.InputOperand2_32(1));
}
break; break;
case kArm64Bic: case kArm64Bic:
__ Bic(i.OutputRegister(), i.InputOrZeroRegister64(0), __ Bic(i.OutputRegister(), i.InputOrZeroRegister64(0),
...@@ -1205,10 +1231,10 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction( ...@@ -1205,10 +1231,10 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
__ Cmn(i.InputOrZeroRegister32(0), i.InputOperand2_32(1)); __ Cmn(i.InputOrZeroRegister32(0), i.InputOperand2_32(1));
break; break;
case kArm64Tst: case kArm64Tst:
__ Tst(i.InputRegister(0), i.InputOperand(1)); __ Tst(i.InputOrZeroRegister64(0), i.InputOperand(1));
break; break;
case kArm64Tst32: case kArm64Tst32:
__ Tst(i.InputRegister32(0), i.InputOperand32(1)); __ Tst(i.InputOrZeroRegister32(0), i.InputOperand32(1));
break; break;
case kArm64Float32Cmp: case kArm64Float32Cmp:
if (instr->InputAt(1)->IsFPRegister()) { if (instr->InputAt(1)->IsFPRegister()) {
......
...@@ -1921,14 +1921,126 @@ void VisitWordCompare(InstructionSelector* selector, Node* node, ...@@ -1921,14 +1921,126 @@ void VisitWordCompare(InstructionSelector* selector, Node* node,
} }
} }
// This function checks whether we can convert:
// ((a <op> b) cmp 0), b.<cond>
// to:
// (a <ops> b), b.<cond'>
// where <ops> is the flag setting version of <op>.
// We only generate conditions <cond'> that are a combination of the N
// and Z flags. This avoids the need to make this function dependent on
// the flag-setting operation.
bool CanUseFlagSettingBinop(FlagsCondition cond) {
switch (cond) {
case kEqual:
case kNotEqual:
case kSignedLessThan:
case kSignedGreaterThanOrEqual:
case kUnsignedLessThanOrEqual: // x <= 0 -> x == 0
case kUnsignedGreaterThan: // x > 0 -> x != 0
return true;
default:
return false;
}
}
// Map <cond> to <cond'> so that the following transformation is possible:
// ((a <op> b) cmp 0), b.<cond>
// to:
// (a <ops> b), b.<cond'>
// where <ops> is the flag setting version of <op>.
FlagsCondition MapForFlagSettingBinop(FlagsCondition cond) {
DCHECK(CanUseFlagSettingBinop(cond));
switch (cond) {
case kEqual:
case kNotEqual:
return cond;
case kSignedLessThan:
return kNegative;
case kSignedGreaterThanOrEqual:
return kPositiveOrZero;
case kUnsignedLessThanOrEqual: // x <= 0 -> x == 0
return kEqual;
case kUnsignedGreaterThan: // x > 0 -> x != 0
return kNotEqual;
default:
UNREACHABLE();
return cond;
}
}
// This function checks if we can perform the transformation:
// ((a <op> b) cmp 0), b.<cond>
// to:
// (a <ops> b), b.<cond'>
// where <ops> is the flag setting version of <op>, and if so,
// updates {node}, {opcode} and {cont} accordingly.
void MaybeReplaceCmpZeroWithFlagSettingBinop(InstructionSelector* selector,
Node** node, Node* binop,
ArchOpcode* opcode,
FlagsCondition cond,
FlagsContinuation* cont,
ImmediateMode* immediate_mode) {
ArchOpcode binop_opcode;
ArchOpcode no_output_opcode;
ImmediateMode binop_immediate_mode;
switch (binop->opcode()) {
case IrOpcode::kInt32Add:
binop_opcode = kArm64Add32;
no_output_opcode = kArm64Cmn32;
binop_immediate_mode = kArithmeticImm;
break;
case IrOpcode::kWord32And:
binop_opcode = kArm64And32;
no_output_opcode = kArm64Tst32;
binop_immediate_mode = kLogical32Imm;
break;
default:
UNREACHABLE();
return;
}
if (selector->CanCover(*node, binop)) {
// The comparison is the only user of the add or and, so we can generate
// a cmn or tst instead.
cont->Overwrite(MapForFlagSettingBinop(cond));
*opcode = no_output_opcode;
*node = binop;
*immediate_mode = binop_immediate_mode;
} else if (selector->IsOnlyUserOfNodeInSameBlock(*node, binop)) {
// We can also handle the case where the add and the compare are in the
// same basic block, and the compare is the only use of add in this basic
// block (the add has users in other basic blocks).
cont->Overwrite(MapForFlagSettingBinop(cond));
*opcode = binop_opcode;
*node = binop;
*immediate_mode = binop_immediate_mode;
}
}
void VisitWord32Compare(InstructionSelector* selector, Node* node, void VisitWord32Compare(InstructionSelector* selector, Node* node,
FlagsContinuation* cont) { FlagsContinuation* cont) {
Int32BinopMatcher m(node); Int32BinopMatcher m(node);
ArchOpcode opcode = kArm64Cmp32; ArchOpcode opcode = kArm64Cmp32;
FlagsCondition cond = cont->condition();
// Select negated compare for comparisons with negated right input. ImmediateMode immediate_mode = kArithmeticImm;
if (m.right().IsInt32Sub()) { if (m.right().Is(0) && (m.left().IsInt32Add() || m.left().IsWord32And())) {
// Emit flag setting add/and instructions for comparisons against zero.
if (CanUseFlagSettingBinop(cond)) {
Node* binop = m.left().node();
MaybeReplaceCmpZeroWithFlagSettingBinop(selector, &node, binop, &opcode,
cond, cont, &immediate_mode);
}
} else if (m.left().Is(0) &&
(m.right().IsInt32Add() || m.right().IsWord32And())) {
// Same as above, but we need to commute the condition before we
// continue with the rest of the checks.
cond = CommuteFlagsCondition(cond);
if (CanUseFlagSettingBinop(cond)) {
Node* binop = m.right().node();
MaybeReplaceCmpZeroWithFlagSettingBinop(selector, &node, binop, &opcode,
cond, cont, &immediate_mode);
}
} else if (m.right().IsInt32Sub()) {
// Select negated compare for comparisons with negated right input.
Node* sub = m.right().node(); Node* sub = m.right().node();
Int32BinopMatcher msub(sub); Int32BinopMatcher msub(sub);
if (msub.left().Is(0)) { if (msub.left().Is(0)) {
...@@ -1946,7 +2058,7 @@ void VisitWord32Compare(InstructionSelector* selector, Node* node, ...@@ -1946,7 +2058,7 @@ void VisitWord32Compare(InstructionSelector* selector, Node* node,
opcode = kArm64Cmn32; opcode = kArm64Cmn32;
} }
} }
VisitBinop<Int32BinopMatcher>(selector, node, opcode, kArithmeticImm, cont); VisitBinop<Int32BinopMatcher>(selector, node, opcode, immediate_mode, cont);
} }
...@@ -2245,20 +2357,24 @@ void InstructionSelector::VisitWord32Equal(Node* const node) { ...@@ -2245,20 +2357,24 @@ void InstructionSelector::VisitWord32Equal(Node* const node) {
if (CanCover(user, value)) { if (CanCover(user, value)) {
switch (value->opcode()) { switch (value->opcode()) {
case IrOpcode::kInt32Add: case IrOpcode::kInt32Add:
return VisitWordCompare(this, value, kArm64Cmn32, &cont, true, case IrOpcode::kWord32And:
kArithmeticImm); return VisitWord32Compare(this, node, &cont);
case IrOpcode::kInt32Sub: case IrOpcode::kInt32Sub:
return VisitWordCompare(this, value, kArm64Cmp32, &cont, false, return VisitWordCompare(this, value, kArm64Cmp32, &cont, false,
kArithmeticImm); kArithmeticImm);
case IrOpcode::kWord32And:
return VisitWordCompare(this, value, kArm64Tst32, &cont, true,
kLogical32Imm);
case IrOpcode::kWord32Equal: { case IrOpcode::kWord32Equal: {
// Word32Equal(Word32Equal(x, y), 0) => Word32Compare(x, y, ne). // Word32Equal(Word32Equal(x, y), 0) => Word32Compare(x, y, ne).
Int32BinopMatcher mequal(value); Int32BinopMatcher mequal(value);
node->ReplaceInput(0, mequal.left().node()); node->ReplaceInput(0, mequal.left().node());
node->ReplaceInput(1, mequal.right().node()); node->ReplaceInput(1, mequal.right().node());
cont.Negate(); cont.Negate();
// {node} still does not cover its new operands, because {mequal} is
// still using them.
// Since we won't generate any more code for {mequal}, set its
// operands to zero to make sure {node} can cover them.
// This improves pattern matching in VisitWord32Compare.
mequal.node()->ReplaceInput(0, m.right().node());
mequal.node()->ReplaceInput(1, m.right().node());
return VisitWord32Compare(this, node, &cont); return VisitWord32Compare(this, node, &cont);
} }
default: default:
......
...@@ -171,7 +171,9 @@ enum FlagsCondition { ...@@ -171,7 +171,9 @@ enum FlagsCondition {
kUnorderedEqual, kUnorderedEqual,
kUnorderedNotEqual, kUnorderedNotEqual,
kOverflow, kOverflow,
kNotOverflow kNotOverflow,
kPositiveOrZero,
kNegative
}; };
inline FlagsCondition NegateFlagsCondition(FlagsCondition condition) { inline FlagsCondition NegateFlagsCondition(FlagsCondition condition) {
......
...@@ -354,6 +354,8 @@ class FlagsContinuation final { ...@@ -354,6 +354,8 @@ class FlagsContinuation final {
condition_ = CommuteFlagsCondition(condition_); condition_ = CommuteFlagsCondition(condition_);
} }
void Overwrite(FlagsCondition condition) { condition_ = condition; }
void OverwriteAndNegateIfEqual(FlagsCondition condition) { void OverwriteAndNegateIfEqual(FlagsCondition condition) {
bool negate = condition_ == kEqual; bool negate = condition_ == kEqual;
condition_ = condition; condition_ = condition;
......
...@@ -241,6 +241,20 @@ bool InstructionSelector::CanCover(Node* user, Node* node) const { ...@@ -241,6 +241,20 @@ bool InstructionSelector::CanCover(Node* user, Node* node) const {
return true; return true;
} }
bool InstructionSelector::IsOnlyUserOfNodeInSameBlock(Node* user,
Node* node) const {
BasicBlock* bb_user = schedule()->block(user);
BasicBlock* bb_node = schedule()->block(node);
if (bb_user != bb_node) return false;
for (Edge const edge : node->use_edges()) {
Node* from = edge.from();
if ((from != user) && (schedule()->block(from) == bb_user)) {
return false;
}
}
return true;
}
int InstructionSelector::GetVirtualRegister(const Node* node) { int InstructionSelector::GetVirtualRegister(const Node* node) {
DCHECK_NOT_NULL(node); DCHECK_NOT_NULL(node);
size_t const id = node->id(); size_t const id = node->id();
......
...@@ -151,6 +151,31 @@ class InstructionSelector final { ...@@ -151,6 +151,31 @@ class InstructionSelector final {
// edge and the two are in the same basic block. // edge and the two are in the same basic block.
bool CanCover(Node* user, Node* node) const; bool CanCover(Node* user, Node* node) const;
// Used in pattern matching during code generation.
// This function checks that {node} and {user} are in the same basic block,
// and that {user} is the only user of {node} in this basic block. This
// check guarantees that there are no users of {node} scheduled between
// {node} and {user}, and thus we can select a single instruction for both
// nodes, if such an instruction exists. This check can be used for example
// when selecting instructions for:
// n = Int32Add(a, b)
// c = Word32Compare(n, 0, cond)
// Branch(c, true_label, false_label)
// Here we can generate a flag-setting add instruction, even if the add has
// uses in other basic blocks, since the flag-setting add instruction will
// still generate the result of the addition and not just set the flags.
// However, if we had uses of the add in the same basic block, we could have:
// n = Int32Add(a, b)
// o = OtherOp(n, ...)
// c = Word32Compare(n, 0, cond)
// Branch(c, true_label, false_label)
// where we cannot select the add and the compare together. If we were to
// select a flag-setting add instruction for Word32Compare and Int32Add while
// visiting Word32Compare, we would then have to select an instruction for
// OtherOp *afterwards*, which means we would attempt to use the result of
// the add before we have defined it.
bool IsOnlyUserOfNodeInSameBlock(Node* user, Node* node) const;
// Checks if {node} was already defined, and therefore code was already // Checks if {node} was already defined, and therefore code was already
// generated for it. // generated for it.
bool IsDefined(Node* node) const; bool IsDefined(Node* node) const;
......
...@@ -48,6 +48,10 @@ FlagsCondition CommuteFlagsCondition(FlagsCondition condition) { ...@@ -48,6 +48,10 @@ FlagsCondition CommuteFlagsCondition(FlagsCondition condition) {
return kFloatGreaterThanOrEqualOrUnordered; return kFloatGreaterThanOrEqualOrUnordered;
case kFloatGreaterThan: case kFloatGreaterThan:
return kFloatLessThan; return kFloatLessThan;
case kPositiveOrZero:
case kNegative:
UNREACHABLE();
break;
case kEqual: case kEqual:
case kNotEqual: case kNotEqual:
case kOverflow: case kOverflow:
...@@ -448,6 +452,10 @@ std::ostream& operator<<(std::ostream& os, const FlagsCondition& fc) { ...@@ -448,6 +452,10 @@ std::ostream& operator<<(std::ostream& os, const FlagsCondition& fc) {
return os << "overflow"; return os << "overflow";
case kNotOverflow: case kNotOverflow:
return os << "not overflow"; return os << "not overflow";
case kPositiveOrZero:
return os << "positive or zero";
case kNegative:
return os << "negative";
} }
UNREACHABLE(); UNREACHABLE();
return os; return os;
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment