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) {
return vs;
case kNotOverflow:
return vc;
case kPositiveOrZero:
return pl;
case kNegative:
return mi;
default:
break;
}
......
......@@ -394,6 +394,10 @@ Condition FlagsConditionToCondition(FlagsCondition condition) {
case kUnorderedEqual:
case kUnorderedNotEqual:
break;
case kPositiveOrZero:
return pl;
case kNegative:
return mi;
}
UNREACHABLE();
return nv;
......@@ -897,12 +901,34 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
}
break;
case kArm64And:
__ And(i.OutputRegister(), i.InputOrZeroRegister64(0),
i.InputOperand2_64(1));
if (FlagsModeField::decode(opcode) != kFlags_none) {
// 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;
case kArm64And32:
__ And(i.OutputRegister32(), i.InputOrZeroRegister32(0),
i.InputOperand2_32(1));
if (FlagsModeField::decode(opcode) != kFlags_none) {
// 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;
case kArm64Bic:
__ Bic(i.OutputRegister(), i.InputOrZeroRegister64(0),
......@@ -1205,10 +1231,10 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
__ Cmn(i.InputOrZeroRegister32(0), i.InputOperand2_32(1));
break;
case kArm64Tst:
__ Tst(i.InputRegister(0), i.InputOperand(1));
__ Tst(i.InputOrZeroRegister64(0), i.InputOperand(1));
break;
case kArm64Tst32:
__ Tst(i.InputRegister32(0), i.InputOperand32(1));
__ Tst(i.InputOrZeroRegister32(0), i.InputOperand32(1));
break;
case kArm64Float32Cmp:
if (instr->InputAt(1)->IsFPRegister()) {
......
......@@ -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,
FlagsContinuation* cont) {
Int32BinopMatcher m(node);
ArchOpcode opcode = kArm64Cmp32;
// Select negated compare for comparisons with negated right input.
if (m.right().IsInt32Sub()) {
FlagsCondition cond = cont->condition();
ImmediateMode immediate_mode = kArithmeticImm;
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();
Int32BinopMatcher msub(sub);
if (msub.left().Is(0)) {
......@@ -1946,7 +2058,7 @@ void VisitWord32Compare(InstructionSelector* selector, Node* node,
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) {
if (CanCover(user, value)) {
switch (value->opcode()) {
case IrOpcode::kInt32Add:
return VisitWordCompare(this, value, kArm64Cmn32, &cont, true,
kArithmeticImm);
case IrOpcode::kWord32And:
return VisitWord32Compare(this, node, &cont);
case IrOpcode::kInt32Sub:
return VisitWordCompare(this, value, kArm64Cmp32, &cont, false,
kArithmeticImm);
case IrOpcode::kWord32And:
return VisitWordCompare(this, value, kArm64Tst32, &cont, true,
kLogical32Imm);
case IrOpcode::kWord32Equal: {
// Word32Equal(Word32Equal(x, y), 0) => Word32Compare(x, y, ne).
Int32BinopMatcher mequal(value);
node->ReplaceInput(0, mequal.left().node());
node->ReplaceInput(1, mequal.right().node());
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);
}
default:
......
......@@ -171,7 +171,9 @@ enum FlagsCondition {
kUnorderedEqual,
kUnorderedNotEqual,
kOverflow,
kNotOverflow
kNotOverflow,
kPositiveOrZero,
kNegative
};
inline FlagsCondition NegateFlagsCondition(FlagsCondition condition) {
......
......@@ -354,6 +354,8 @@ class FlagsContinuation final {
condition_ = CommuteFlagsCondition(condition_);
}
void Overwrite(FlagsCondition condition) { condition_ = condition; }
void OverwriteAndNegateIfEqual(FlagsCondition condition) {
bool negate = condition_ == kEqual;
condition_ = condition;
......
......@@ -241,6 +241,20 @@ bool InstructionSelector::CanCover(Node* user, Node* node) const {
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) {
DCHECK_NOT_NULL(node);
size_t const id = node->id();
......
......@@ -151,6 +151,31 @@ class InstructionSelector final {
// edge and the two are in the same basic block.
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
// generated for it.
bool IsDefined(Node* node) const;
......
......@@ -48,6 +48,10 @@ FlagsCondition CommuteFlagsCondition(FlagsCondition condition) {
return kFloatGreaterThanOrEqualOrUnordered;
case kFloatGreaterThan:
return kFloatLessThan;
case kPositiveOrZero:
case kNegative:
UNREACHABLE();
break;
case kEqual:
case kNotEqual:
case kOverflow:
......@@ -448,6 +452,10 @@ std::ostream& operator<<(std::ostream& os, const FlagsCondition& fc) {
return os << "overflow";
case kNotOverflow:
return os << "not overflow";
case kPositiveOrZero:
return os << "positive or zero";
case kNegative:
return os << "negative";
}
UNREACHABLE();
return os;
......
......@@ -478,6 +478,454 @@ TEST(BranchCombineEffectLevel) {
CHECK_EQ(42, m.Call());
}
TEST(BranchCombineInt32AddLessThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Int32Add(a, b);
Node* compare = m.Int32LessThan(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
int32_t a = *i;
int32_t b = *j;
int32_t expect = (a + b < 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32AddGreaterThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Int32Add(a, b);
Node* compare = m.Int32GreaterThanOrEqual(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
int32_t a = *i;
int32_t b = *j;
int32_t expect = (a + b >= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroGreaterThanAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Int32Add(a, b);
Node* compare = m.Int32GreaterThan(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
int32_t a = *i;
int32_t b = *j;
int32_t expect = (0 > a + b) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroLessThanOrEqualAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Int32Add(a, b);
Node* compare = m.Int32LessThanOrEqual(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
int32_t a = *i;
int32_t b = *j;
int32_t expect = (0 <= a + b) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AddLessThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Int32Add(a, b);
Node* compare = m.Uint32LessThanOrEqual(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
uint32_t a = *i;
uint32_t b = *j;
int32_t expect = (a + b <= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AddGreaterThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Int32Add(a, b);
Node* compare = m.Uint32GreaterThan(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
uint32_t a = *i;
uint32_t b = *j;
int32_t expect = (a + b > 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroGreaterThanOrEqualAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Int32Add(a, b);
Node* compare = m.Uint32GreaterThanOrEqual(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
uint32_t a = *i;
uint32_t b = *j;
int32_t expect = (0 >= a + b) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroLessThanAdd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Int32Add(a, b);
Node* compare = m.Uint32LessThan(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
uint32_t a = *i;
uint32_t b = *j;
int32_t expect = (0 < a + b) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32AndLessThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Word32And(a, b);
Node* compare = m.Int32LessThan(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
int32_t a = *i;
int32_t b = *j;
int32_t expect = ((a & b) < 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineWord32AndGreaterThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Word32And(a, b);
Node* compare = m.Int32GreaterThanOrEqual(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
int32_t a = *i;
int32_t b = *j;
int32_t expect = ((a & b) >= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroGreaterThanAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Word32And(a, b);
Node* compare = m.Int32GreaterThan(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
int32_t a = *i;
int32_t b = *j;
int32_t expect = (0 > (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineInt32ZeroLessThanOrEqualAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Int32(),
MachineType::Int32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Word32And(a, b);
Node* compare = m.Int32LessThanOrEqual(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
int32_t a = *i;
int32_t b = *j;
int32_t expect = (0 <= (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AndLessThanOrEqualZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Word32And(a, b);
Node* compare = m.Uint32LessThanOrEqual(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
uint32_t a = *i;
uint32_t b = *j;
int32_t expect = ((a & b) <= 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32AndGreaterThanZero) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Word32And(a, b);
Node* compare = m.Uint32GreaterThan(add, m.Int32Constant(0));
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
uint32_t a = *i;
uint32_t b = *j;
int32_t expect = ((a & b) > 0) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroGreaterThanOrEqualAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Word32And(a, b);
Node* compare = m.Uint32GreaterThanOrEqual(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
uint32_t a = *i;
uint32_t b = *j;
int32_t expect = (0 >= (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
TEST(BranchCombineUint32ZeroLessThanAnd) {
int32_t t_constant = -1033;
int32_t f_constant = 825118;
RawMachineAssemblerTester<int32_t> m(MachineType::Uint32(),
MachineType::Uint32());
Node* a = m.Parameter(0);
Node* b = m.Parameter(1);
Node* add = m.Word32And(a, b);
Node* compare = m.Uint32LessThan(m.Int32Constant(0), add);
RawMachineLabel blocka, blockb;
m.Branch(compare, &blocka, &blockb);
m.Bind(&blocka);
m.Return(m.Int32Constant(t_constant));
m.Bind(&blockb);
m.Return(m.Int32Constant(f_constant));
FOR_INT32_INPUTS(i) {
FOR_INT32_INPUTS(j) {
uint32_t a = *i;
uint32_t b = *j;
int32_t expect = (0 < (a & b)) ? t_constant : f_constant;
CHECK_EQ(expect, m.Call(a, b));
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -3242,6 +3242,323 @@ TEST_F(InstructionSelectorTest, CmnShiftByImmediateOnLeft) {
}
}
// -----------------------------------------------------------------------------
// Flag-setting add and and instructions.
const IntegerCmp kBinopCmpZeroRightInstructions[] = {
{{&RawMachineAssembler::Word32Equal, "Word32Equal", kArm64Cmp32,
MachineType::Int32()},
kEqual,
kEqual},
{{&RawMachineAssembler::Word32NotEqual, "Word32NotEqual", kArm64Cmp32,
MachineType::Int32()},
kNotEqual,
kNotEqual},
{{&RawMachineAssembler::Int32LessThan, "Int32LessThan", kArm64Cmp32,
MachineType::Int32()},
kNegative,
kNegative},
{{&RawMachineAssembler::Int32GreaterThanOrEqual, "Int32GreaterThanOrEqual",
kArm64Cmp32, MachineType::Int32()},
kPositiveOrZero,
kPositiveOrZero},
{{&RawMachineAssembler::Uint32LessThanOrEqual, "Uint32LessThanOrEqual",
kArm64Cmp32, MachineType::Int32()},
kEqual,
kEqual},
{{&RawMachineAssembler::Uint32GreaterThan, "Uint32GreaterThan", kArm64Cmp32,
MachineType::Int32()},
kNotEqual,
kNotEqual}};
const IntegerCmp kBinopCmpZeroLeftInstructions[] = {
{{&RawMachineAssembler::Word32Equal, "Word32Equal", kArm64Cmp32,
MachineType::Int32()},
kEqual,
kEqual},
{{&RawMachineAssembler::Word32NotEqual, "Word32NotEqual", kArm64Cmp32,
MachineType::Int32()},
kNotEqual,
kNotEqual},
{{&RawMachineAssembler::Int32GreaterThan, "Int32GreaterThan", kArm64Cmp32,
MachineType::Int32()},
kNegative,
kNegative},
{{&RawMachineAssembler::Int32LessThanOrEqual, "Int32LessThanOrEqual",
kArm64Cmp32, MachineType::Int32()},
kPositiveOrZero,
kPositiveOrZero},
{{&RawMachineAssembler::Uint32GreaterThanOrEqual,
"Uint32GreaterThanOrEqual", kArm64Cmp32, MachineType::Int32()},
kEqual,
kEqual},
{{&RawMachineAssembler::Uint32LessThan, "Uint32LessThan", kArm64Cmp32,
MachineType::Int32()},
kNotEqual,
kNotEqual}};
struct FlagSettingInst {
MachInst2 mi;
ArchOpcode no_output_opcode;
};
std::ostream& operator<<(std::ostream& os, const FlagSettingInst& inst) {
return os << inst.mi.constructor_name;
}
const FlagSettingInst kFlagSettingInstructions[] = {
{{&RawMachineAssembler::Int32Add, "Int32Add", kArm64Add32,
MachineType::Int32()},
kArm64Cmn32},
{{&RawMachineAssembler::Word32And, "Word32And", kArm64And32,
MachineType::Int32()},
kArm64Tst32}};
typedef InstructionSelectorTestWithParam<FlagSettingInst>
InstructionSelectorFlagSettingTest;
TEST_P(InstructionSelectorFlagSettingTest, CmpZeroRight) {
const FlagSettingInst inst = GetParam();
// Add with single user : a cmp instruction.
TRACED_FOREACH(IntegerCmp, cmp, kBinopCmpZeroRightInstructions) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32(),
MachineType::Int32());
RawMachineLabel a, b;
Node* binop = (m.*inst.mi.constructor)(m.Parameter(0), m.Parameter(1));
Node* comp = (m.*cmp.mi.constructor)(binop, m.Int32Constant(0));
m.Branch(comp, &a, &b);
m.Bind(&a);
m.Return(m.Int32Constant(1));
m.Bind(&b);
m.Return(m.Int32Constant(0));
Stream s = m.Build();
ASSERT_EQ(1U, s.size());
ASSERT_EQ(4U, s[0]->InputCount()); // The labels are also inputs.
EXPECT_EQ(inst.no_output_opcode, s[0]->arch_opcode());
EXPECT_EQ(s.ToVreg(m.Parameter(0)), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(s.ToVreg(m.Parameter(1)), s.ToVreg(s[0]->InputAt(1)));
EXPECT_EQ(kFlags_branch, s[0]->flags_mode());
EXPECT_EQ(cmp.cond, s[0]->flags_condition());
}
}
TEST_P(InstructionSelectorFlagSettingTest, CmpZeroLeft) {
const FlagSettingInst inst = GetParam();
// Test a cmp with zero on the left-hand side.
TRACED_FOREACH(IntegerCmp, cmp, kBinopCmpZeroLeftInstructions) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32(),
MachineType::Int32());
RawMachineLabel a, b;
Node* binop = (m.*inst.mi.constructor)(m.Parameter(0), m.Parameter(1));
Node* comp = (m.*cmp.mi.constructor)(m.Int32Constant(0), binop);
m.Branch(comp, &a, &b);
m.Bind(&a);
m.Return(m.Int32Constant(1));
m.Bind(&b);
m.Return(m.Int32Constant(0));
Stream s = m.Build();
ASSERT_EQ(1U, s.size());
ASSERT_EQ(4U, s[0]->InputCount()); // The labels are also inputs.
EXPECT_EQ(inst.no_output_opcode, s[0]->arch_opcode());
EXPECT_EQ(s.ToVreg(m.Parameter(0)), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(s.ToVreg(m.Parameter(1)), s.ToVreg(s[0]->InputAt(1)));
EXPECT_EQ(kFlags_branch, s[0]->flags_mode());
EXPECT_EQ(cmp.cond, s[0]->flags_condition());
}
}
TEST_P(InstructionSelectorFlagSettingTest, CmpZeroOnlyUserInBasicBlock) {
const FlagSettingInst inst = GetParam();
// Binop with additional users, but in a different basic block.
TRACED_FOREACH(IntegerCmp, cmp, kBinopCmpZeroRightInstructions) {
// For kEqual and kNotEqual, we generate a cbz or cbnz.
if (cmp.cond == kEqual || cmp.cond == kNotEqual) continue;
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32(),
MachineType::Int32());
RawMachineLabel a, b;
Node* binop = (m.*inst.mi.constructor)(m.Parameter(0), m.Parameter(1));
Node* comp = (m.*cmp.mi.constructor)(binop, m.Int32Constant(0));
m.Branch(comp, &a, &b);
m.Bind(&a);
m.Return(binop);
m.Bind(&b);
m.Return(m.Int32Constant(0));
Stream s = m.Build();
ASSERT_EQ(1U, s.size());
ASSERT_EQ(4U, s[0]->InputCount()); // The labels are also inputs.
EXPECT_EQ(inst.mi.arch_opcode, s[0]->arch_opcode());
EXPECT_EQ(s.ToVreg(m.Parameter(0)), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(s.ToVreg(m.Parameter(1)), s.ToVreg(s[0]->InputAt(1)));
EXPECT_EQ(kFlags_branch, s[0]->flags_mode());
EXPECT_EQ(cmp.cond, s[0]->flags_condition());
}
}
TEST_P(InstructionSelectorFlagSettingTest, ShiftedOperand) {
const FlagSettingInst inst = GetParam();
// Like the test above, but with a shifted input to the binary operator.
TRACED_FOREACH(IntegerCmp, cmp, kBinopCmpZeroRightInstructions) {
// For kEqual and kNotEqual, we generate a cbz or cbnz.
if (cmp.cond == kEqual || cmp.cond == kNotEqual) continue;
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32(),
MachineType::Int32());
RawMachineLabel a, b;
Node* imm = m.Int32Constant(5);
Node* shift = m.Word32Shl(m.Parameter(1), imm);
Node* binop = (m.*inst.mi.constructor)(m.Parameter(0), shift);
Node* comp = (m.*cmp.mi.constructor)(binop, m.Int32Constant(0));
m.Branch(comp, &a, &b);
m.Bind(&a);
m.Return(binop);
m.Bind(&b);
m.Return(m.Int32Constant(0));
Stream s = m.Build();
ASSERT_EQ(1U, s.size());
ASSERT_EQ(5U, s[0]->InputCount()); // The labels are also inputs.
EXPECT_EQ(inst.mi.arch_opcode, s[0]->arch_opcode());
EXPECT_EQ(s.ToVreg(m.Parameter(0)), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(s.ToVreg(m.Parameter(1)), s.ToVreg(s[0]->InputAt(1)));
EXPECT_EQ(5, s.ToInt32(s[0]->InputAt(2)));
EXPECT_EQ(kMode_Operand2_R_LSL_I, s[0]->addressing_mode());
EXPECT_EQ(kFlags_branch, s[0]->flags_mode());
EXPECT_EQ(cmp.cond, s[0]->flags_condition());
}
}
TEST_P(InstructionSelectorFlagSettingTest, UsersInSameBasicBlock) {
const FlagSettingInst inst = GetParam();
// Binop with additional users, in the same basic block. We need to make sure
// we don't try to optimise this case.
TRACED_FOREACH(IntegerCmp, cmp, kIntegerCmpInstructions) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32(),
MachineType::Int32());
RawMachineLabel a, b;
Node* binop = (m.*inst.mi.constructor)(m.Parameter(0), m.Parameter(1));
Node* mul = m.Int32Mul(m.Parameter(0), binop);
Node* comp = (m.*cmp.mi.constructor)(binop, m.Int32Constant(0));
m.Branch(comp, &a, &b);
m.Bind(&a);
m.Return(mul);
m.Bind(&b);
m.Return(m.Int32Constant(0));
Stream s = m.Build();
ASSERT_EQ(3U, s.size());
EXPECT_EQ(inst.mi.arch_opcode, s[0]->arch_opcode());
EXPECT_NE(kFlags_branch, s[0]->flags_mode());
EXPECT_EQ(kArm64Mul32, s[1]->arch_opcode());
EXPECT_EQ(cmp.cond == kEqual ? kArm64CompareAndBranch32 : kArm64Cmp32,
s[2]->arch_opcode());
EXPECT_EQ(kFlags_branch, s[2]->flags_mode());
EXPECT_EQ(cmp.cond, s[2]->flags_condition());
}
}
TEST_P(InstructionSelectorFlagSettingTest, CommuteImmediate) {
const FlagSettingInst inst = GetParam();
// Immediate on left hand side of the binary operator.
TRACED_FOREACH(IntegerCmp, cmp, kBinopCmpZeroRightInstructions) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32());
RawMachineLabel a, b;
// 3 can be an immediate on both arithmetic and logical instructions.
Node* imm = m.Int32Constant(3);
Node* binop = (m.*inst.mi.constructor)(imm, m.Parameter(0));
Node* comp = (m.*cmp.mi.constructor)(binop, m.Int32Constant(0));
m.Branch(comp, &a, &b);
m.Bind(&a);
m.Return(m.Int32Constant(1));
m.Bind(&b);
m.Return(m.Int32Constant(0));
Stream s = m.Build();
ASSERT_EQ(1U, s.size());
ASSERT_EQ(4U, s[0]->InputCount()); // The labels are also inputs.
EXPECT_EQ(inst.no_output_opcode, s[0]->arch_opcode());
EXPECT_EQ(s.ToVreg(m.Parameter(0)), s.ToVreg(s[0]->InputAt(0)));
EXPECT_EQ(3, s.ToInt32(s[0]->InputAt(1)));
EXPECT_EQ(kFlags_branch, s[0]->flags_mode());
EXPECT_EQ(cmp.cond, s[0]->flags_condition());
}
}
TEST_P(InstructionSelectorFlagSettingTest, CommuteShift) {
const FlagSettingInst inst = GetParam();
// Left-hand side operand shifted by immediate.
TRACED_FOREACH(IntegerCmp, cmp, kBinopCmpZeroRightInstructions) {
TRACED_FOREACH(Shift, shift, kShiftInstructions) {
// Only test relevant shifted operands.
if (shift.mi.machine_type != MachineType::Int32()) continue;
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32(),
MachineType::Int32());
Node* imm = m.Int32Constant(5);
Node* shifted_operand = (m.*shift.mi.constructor)(m.Parameter(0), imm);
Node* binop = (m.*inst.mi.constructor)(shifted_operand, m.Parameter(1));
Node* comp = (m.*cmp.mi.constructor)(binop, m.Int32Constant(0));
m.Return(comp);
Stream s = m.Build();
// Cmn does not support ROR shifts.
if (inst.no_output_opcode == kArm64Cmn32 &&
shift.mi.arch_opcode == kArm64Ror32) {
ASSERT_EQ(2U, s.size());
continue;
}
ASSERT_EQ(1U, s.size());
EXPECT_EQ(inst.no_output_opcode, s[0]->arch_opcode());
EXPECT_EQ(shift.mode, s[0]->addressing_mode());
EXPECT_EQ(3U, s[0]->InputCount());
EXPECT_EQ(5, s.ToInt64(s[0]->InputAt(2)));
EXPECT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(kFlags_set, s[0]->flags_mode());
EXPECT_EQ(cmp.cond, s[0]->flags_condition());
}
}
}
INSTANTIATE_TEST_CASE_P(InstructionSelectorTest,
InstructionSelectorFlagSettingTest,
::testing::ValuesIn(kFlagSettingInstructions));
TEST_F(InstructionSelectorTest, TstInvalidImmediate) {
// Make sure we do not generate an invalid immediate for TST.
TRACED_FOREACH(IntegerCmp, cmp, kBinopCmpZeroRightInstructions) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32());
RawMachineLabel a, b;
// 5 is not a valid constant for TST.
Node* imm = m.Int32Constant(5);
Node* binop = m.Word32And(imm, m.Parameter(0));
Node* comp = (m.*cmp.mi.constructor)(binop, m.Int32Constant(0));
m.Branch(comp, &a, &b);
m.Bind(&a);
m.Return(m.Int32Constant(1));
m.Bind(&b);
m.Return(m.Int32Constant(0));
Stream s = m.Build();
ASSERT_EQ(1U, s.size());
ASSERT_EQ(4U, s[0]->InputCount()); // The labels are also inputs.
EXPECT_EQ(kArm64Tst32, s[0]->arch_opcode());
EXPECT_NE(InstructionOperand::IMMEDIATE, s[0]->InputAt(0)->kind());
EXPECT_NE(InstructionOperand::IMMEDIATE, s[0]->InputAt(1)->kind());
EXPECT_EQ(kFlags_branch, s[0]->flags_mode());
EXPECT_EQ(cmp.cond, s[0]->flags_condition());
}
}
TEST_F(InstructionSelectorTest, CommuteAddsExtend) {
// Extended left-hand side operand.
TRACED_FOREACH(IntegerCmp, cmp, kBinopCmpZeroRightInstructions) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32(),
MachineType::Int32());
Node* extend = m.Word32Sar(m.Word32Shl(m.Parameter(0), m.Int32Constant(24)),
m.Int32Constant(24));
Node* binop = m.Int32Add(extend, m.Parameter(1));
m.Return((m.*cmp.mi.constructor)(binop, m.Int32Constant(0)));
Stream s = m.Build();
ASSERT_EQ(1U, s.size());
EXPECT_EQ(kArm64Cmn32, s[0]->arch_opcode());
EXPECT_EQ(kFlags_set, s[0]->flags_mode());
EXPECT_EQ(cmp.cond, s[0]->flags_condition());
EXPECT_EQ(kMode_Operand2_R_SXTB, s[0]->addressing_mode());
}
}
// -----------------------------------------------------------------------------
// Miscellaneous
......
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