mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-09 17:44:48 +09:00
Use cinv and cneg instead of csel when possible (#84926)
* Use cinv instead of csel when possible * Fix when the operands of conditional select should be reversed * Scope down the cinv optimisation to local vars * Use more generic CSINV instead of CINV instruction * Add support for CSNEG and refactor conditional selects * Fix initialisation of srcReg2 * Add RequiresProcessIsolation property to fix test failures * Address review comments - Part 1 * Remove output type from conditional negate tests * Ensure operands of cneg/cinv are invariants while optimising * Update tests to the new format * Use annotations to run tests through the framework * Fix conditional negate tests * Handle conditional negate/invert of shifted operands
This commit is contained in:
parent
66644e3c5e
commit
86ce145893
16 changed files with 633 additions and 318 deletions
|
@ -901,7 +901,6 @@ protected:
|
|||
void genCodeForCompare(GenTreeOp* tree);
|
||||
#ifdef TARGET_ARM64
|
||||
void genCodeForCCMP(GenTreeCCMP* ccmp);
|
||||
void genCodeForCinc(GenTreeOp* cinc);
|
||||
#endif
|
||||
void genCodeForSelect(GenTreeOp* select);
|
||||
void genIntrinsic(GenTreeIntrinsic* treeNode);
|
||||
|
@ -1250,7 +1249,6 @@ protected:
|
|||
#if defined(TARGET_ARM64)
|
||||
void genCodeForJumpCompare(GenTreeOpCC* tree);
|
||||
void genCodeForBfiz(GenTreeOp* tree);
|
||||
void genCodeForCond(GenTreeOp* tree);
|
||||
#endif // TARGET_ARM64
|
||||
|
||||
#if defined(FEATURE_EH_FUNCLETS)
|
||||
|
|
|
@ -4683,32 +4683,49 @@ void CodeGen::genCodeForCCMP(GenTreeCCMP* ccmp)
|
|||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// genCodeForSelect: Produce code for a GT_SELECT node.
|
||||
// genCodeForSelect: Produce code for a GT_SELECT/GT_SELECT_INV/GT_SELECT_NEG node.
|
||||
//
|
||||
// Arguments:
|
||||
// tree - the node
|
||||
//
|
||||
void CodeGen::genCodeForSelect(GenTreeOp* tree)
|
||||
{
|
||||
assert(tree->OperIs(GT_SELECT, GT_SELECTCC));
|
||||
GenTree* opcond = nullptr;
|
||||
if (tree->OperIs(GT_SELECT))
|
||||
assert(tree->OperIs(GT_SELECT, GT_SELECTCC, GT_SELECT_INC, GT_SELECT_INCCC, GT_SELECT_INV, GT_SELECT_INVCC,
|
||||
GT_SELECT_NEG, GT_SELECT_NEGCC));
|
||||
GenTree* opcond = nullptr;
|
||||
instruction ins = INS_csel;
|
||||
GenTree* op1 = tree->gtOp1;
|
||||
GenTree* op2 = tree->gtOp2;
|
||||
|
||||
if (tree->OperIs(GT_SELECT_INV, GT_SELECT_INVCC))
|
||||
{
|
||||
ins = (op2 == nullptr) ? INS_cinv : INS_csinv;
|
||||
}
|
||||
else if (tree->OperIs(GT_SELECT_NEG, GT_SELECT_NEGCC))
|
||||
{
|
||||
ins = (op2 == nullptr) ? INS_cneg : INS_csneg;
|
||||
}
|
||||
else if (tree->OperIs(GT_SELECT_INC, GT_SELECT_INCCC))
|
||||
{
|
||||
ins = (op2 == nullptr) ? INS_cinc : INS_csinc;
|
||||
}
|
||||
|
||||
if (tree->OperIs(GT_SELECT, GT_SELECT_INV, GT_SELECT_NEG))
|
||||
{
|
||||
opcond = tree->AsConditional()->gtCond;
|
||||
genConsumeRegs(opcond);
|
||||
}
|
||||
|
||||
emitter* emit = GetEmitter();
|
||||
|
||||
GenTree* op1 = tree->gtOp1;
|
||||
GenTree* op2 = tree->gtOp2;
|
||||
var_types op1Type = genActualType(op1);
|
||||
var_types op2Type = genActualType(op2);
|
||||
emitAttr attr = emitActualTypeSize(tree);
|
||||
if (op2 != nullptr)
|
||||
{
|
||||
var_types op1Type = genActualType(op1);
|
||||
var_types op2Type = genActualType(op2);
|
||||
assert(genTypeSize(op1Type) == genTypeSize(op2Type));
|
||||
}
|
||||
|
||||
assert(!op1->isUsedFromMemory());
|
||||
assert(genTypeSize(op1Type) == genTypeSize(op2Type));
|
||||
|
||||
emitter* emit = GetEmitter();
|
||||
GenCondition cond;
|
||||
|
||||
if (opcond != nullptr)
|
||||
|
@ -4719,94 +4736,64 @@ void CodeGen::genCodeForSelect(GenTreeOp* tree)
|
|||
}
|
||||
else
|
||||
{
|
||||
assert(tree->OperIs(GT_SELECTCC));
|
||||
assert(tree->OperIs(GT_SELECTCC, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC));
|
||||
cond = tree->AsOpCC()->gtCondition;
|
||||
}
|
||||
|
||||
assert(!op1->isContained() || op1->IsIntegralConst(0));
|
||||
assert(!op2->isContained() || op2->IsIntegralConst(0));
|
||||
assert(op2 == nullptr || !op2->isContained() || op2->IsIntegralConst(0));
|
||||
|
||||
regNumber targetReg = tree->GetRegNum();
|
||||
regNumber srcReg1 = op1->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op1);
|
||||
regNumber srcReg2 = op2->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op2);
|
||||
const GenConditionDesc& prevDesc = GenConditionDesc::Get(cond);
|
||||
emitAttr attr = emitActualTypeSize(tree);
|
||||
regNumber srcReg2;
|
||||
|
||||
emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1));
|
||||
|
||||
// Some conditions require an additional condition check.
|
||||
if (prevDesc.oper == GT_OR)
|
||||
if (op2 == nullptr)
|
||||
{
|
||||
emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, srcReg1, targetReg, JumpKindToInsCond(prevDesc.jumpKind2));
|
||||
srcReg2 = srcReg1;
|
||||
emit->emitIns_R_R_COND(ins, attr, targetReg, srcReg1, JumpKindToInsCond(prevDesc.jumpKind1));
|
||||
}
|
||||
else if (prevDesc.oper == GT_AND)
|
||||
else
|
||||
{
|
||||
emit->emitIns_R_R_R_COND(INS_csel, attr, targetReg, targetReg, srcReg2, JumpKindToInsCond(prevDesc.jumpKind2));
|
||||
srcReg2 = (op2->IsIntegralConst(0) ? REG_ZR : genConsumeReg(op2));
|
||||
emit->emitIns_R_R_R_COND(ins, attr, targetReg, srcReg1, srcReg2, JumpKindToInsCond(prevDesc.jumpKind1));
|
||||
}
|
||||
|
||||
// Some floating point comparision conditions require an additional condition check.
|
||||
// These checks are emitted as a subsequent check using GT_AND or GT_OR nodes.
|
||||
// e.g., using GT_OR => `dest = (cond1 || cond2) ? src1 : src2`
|
||||
// GT_AND => `dest = (cond1 && cond2) ? src1 : src2`
|
||||
// The GT_OR case results in emitting the following sequence of two csel instructions.
|
||||
// csel dest, src1, src2, cond1 # emitted previously
|
||||
// csel dest, src1, dest, cond2
|
||||
//
|
||||
if (prevDesc.oper == GT_AND)
|
||||
{
|
||||
// To ensure correctness with invert and negate variants of conditional select, the second instruction needs to
|
||||
// be csinv or csneg respectively.
|
||||
// dest = (cond1 && cond2) ? src1 : ~src2
|
||||
// csinv dest, src1, src2, cond1
|
||||
// csinv dest, dest, src2, cond2
|
||||
//
|
||||
// However, the other variants - increment and select, the second instruction needs to be csel.
|
||||
// dest = (cond1 && cond2) ? src1 : src2++
|
||||
// csinc dest, src1, src2, cond1
|
||||
// csel dest, dest, src1 cond2
|
||||
ins = ((ins == INS_csinv) || (ins == INS_csneg)) ? ins : INS_csel;
|
||||
emit->emitIns_R_R_R_COND(ins, attr, targetReg, targetReg, srcReg2, JumpKindToInsCond(prevDesc.jumpKind2));
|
||||
}
|
||||
else if (prevDesc.oper == GT_OR)
|
||||
{
|
||||
// Similarly, the second instruction needs to be csinc while emitting conditional increment.
|
||||
ins = (ins == INS_csinc) ? ins : INS_csel;
|
||||
emit->emitIns_R_R_R_COND(ins, attr, targetReg, srcReg1, targetReg, JumpKindToInsCond(prevDesc.jumpKind2));
|
||||
}
|
||||
|
||||
regSet.verifyRegUsed(targetReg);
|
||||
genProduceReg(tree);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// genCodeForCinc: Produce code for a GT_CINC/GT_CINCCC node.
|
||||
//
|
||||
// Arguments:
|
||||
// tree - the node
|
||||
//
|
||||
void CodeGen::genCodeForCinc(GenTreeOp* cinc)
|
||||
{
|
||||
assert(cinc->OperIs(GT_CINC, GT_CINCCC));
|
||||
|
||||
GenTree* opcond = nullptr;
|
||||
GenTree* op = cinc->gtOp1;
|
||||
if (cinc->OperIs(GT_CINC))
|
||||
{
|
||||
opcond = cinc->gtOp1;
|
||||
op = cinc->gtOp2;
|
||||
genConsumeRegs(opcond);
|
||||
}
|
||||
|
||||
emitter* emit = GetEmitter();
|
||||
var_types opType = genActualType(op->TypeGet());
|
||||
emitAttr attr = emitActualTypeSize(cinc->TypeGet());
|
||||
|
||||
assert(!op->isUsedFromMemory());
|
||||
genConsumeRegs(op);
|
||||
|
||||
GenCondition cond;
|
||||
|
||||
if (cinc->OperIs(GT_CINC))
|
||||
{
|
||||
assert(!opcond->isContained());
|
||||
// Condition has been generated into a register - move it into flags.
|
||||
emit->emitIns_R_I(INS_cmp, emitActualTypeSize(opcond), opcond->GetRegNum(), 0);
|
||||
cond = GenCondition::NE;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(cinc->OperIs(GT_CINCCC));
|
||||
cond = cinc->AsOpCC()->gtCondition;
|
||||
}
|
||||
const GenConditionDesc& prevDesc = GenConditionDesc::Get(cond);
|
||||
regNumber targetReg = cinc->GetRegNum();
|
||||
regNumber srcReg;
|
||||
|
||||
if (op->isContained())
|
||||
{
|
||||
assert(op->IsIntegralConst(0));
|
||||
srcReg = REG_ZR;
|
||||
}
|
||||
else
|
||||
{
|
||||
srcReg = op->GetRegNum();
|
||||
}
|
||||
|
||||
assert(prevDesc.oper != GT_OR && prevDesc.oper != GT_AND);
|
||||
emit->emitIns_R_R_COND(INS_cinc, attr, targetReg, srcReg, JumpKindToInsCond(prevDesc.jumpKind1));
|
||||
regSet.verifyRegUsed(targetReg);
|
||||
genProduceReg(cinc);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// genCodeForJumpCompare: Generates code for a GT_JCMP or GT_JTEST statement.
|
||||
//
|
||||
|
@ -10365,53 +10352,6 @@ void CodeGen::genCodeForBfiz(GenTreeOp* tree)
|
|||
genProduceReg(tree);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// genCodeForCond: Generates the code sequence for a GenTree node that
|
||||
// represents a conditional instruction.
|
||||
//
|
||||
// Arguments:
|
||||
// tree - conditional op
|
||||
//
|
||||
void CodeGen::genCodeForCond(GenTreeOp* tree)
|
||||
{
|
||||
assert(tree->OperIs(GT_CSNEG_MI, GT_CNEG_LT));
|
||||
assert(!(tree->gtFlags & GTF_SET_FLAGS));
|
||||
genConsumeOperands(tree);
|
||||
|
||||
switch (tree->OperGet())
|
||||
{
|
||||
case GT_CSNEG_MI:
|
||||
{
|
||||
instruction ins = INS_csneg;
|
||||
insCond cond = INS_COND_MI;
|
||||
|
||||
regNumber dstReg = tree->GetRegNum();
|
||||
regNumber op1Reg = tree->gtGetOp1()->GetRegNum();
|
||||
regNumber op2Reg = tree->gtGetOp2()->GetRegNum();
|
||||
|
||||
GetEmitter()->emitIns_R_R_R_COND(ins, emitActualTypeSize(tree), dstReg, op1Reg, op2Reg, cond);
|
||||
break;
|
||||
}
|
||||
|
||||
case GT_CNEG_LT:
|
||||
{
|
||||
instruction ins = INS_cneg;
|
||||
insCond cond = INS_COND_LT;
|
||||
|
||||
regNumber dstReg = tree->GetRegNum();
|
||||
regNumber op1Reg = tree->gtGetOp1()->GetRegNum();
|
||||
|
||||
GetEmitter()->emitIns_R_R_COND(ins, emitActualTypeSize(tree), dstReg, op1Reg, cond);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
unreached();
|
||||
}
|
||||
|
||||
genProduceReg(tree);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// JumpKindToInsCond: Convert a Jump Kind to a condition.
|
||||
//
|
||||
|
|
|
@ -315,11 +315,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
|
|||
case GT_BFIZ:
|
||||
genCodeForBfiz(treeNode->AsOp());
|
||||
break;
|
||||
|
||||
case GT_CSNEG_MI:
|
||||
case GT_CNEG_LT:
|
||||
genCodeForCond(treeNode->AsOp());
|
||||
break;
|
||||
#endif // TARGET_ARM64
|
||||
|
||||
case GT_JMP:
|
||||
|
@ -355,15 +350,16 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
|
|||
break;
|
||||
|
||||
#ifdef TARGET_ARM64
|
||||
case GT_SELECT_NEG:
|
||||
case GT_SELECT_INV:
|
||||
case GT_SELECT_INC:
|
||||
case GT_SELECT:
|
||||
genCodeForSelect(treeNode->AsConditional());
|
||||
break;
|
||||
|
||||
case GT_CINC:
|
||||
case GT_CINCCC:
|
||||
genCodeForCinc(treeNode->AsOp());
|
||||
break;
|
||||
|
||||
case GT_SELECT_NEGCC:
|
||||
case GT_SELECT_INVCC:
|
||||
case GT_SELECT_INCCC:
|
||||
case GT_SELECTCC:
|
||||
genCodeForSelect(treeNode->AsOp());
|
||||
break;
|
||||
|
|
|
@ -4290,11 +4290,7 @@ void GenTree::VisitOperands(TVisitor visitor)
|
|||
}
|
||||
FALLTHROUGH;
|
||||
|
||||
// Standard unary operators
|
||||
#ifdef TARGET_ARM64
|
||||
case GT_CNEG_LT:
|
||||
case GT_CINCCC:
|
||||
#endif // TARGET_ARM64
|
||||
// Standard unary operators
|
||||
case GT_STORE_LCL_VAR:
|
||||
case GT_STORE_LCL_FLD:
|
||||
case GT_NOT:
|
||||
|
|
|
@ -6361,11 +6361,7 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse)
|
|||
case GT_IL_OFFSET:
|
||||
return false;
|
||||
|
||||
// Standard unary operators
|
||||
#ifdef TARGET_ARM64
|
||||
case GT_CNEG_LT:
|
||||
case GT_CINCCC:
|
||||
#endif // TARGET_ARM64
|
||||
// Standard unary operators
|
||||
case GT_STORE_LCL_VAR:
|
||||
case GT_STORE_LCL_FLD:
|
||||
case GT_NOT:
|
||||
|
@ -6554,7 +6550,11 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse)
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef TARGET_ARM64
|
||||
case GT_SELECT_NEG:
|
||||
case GT_SELECT_INV:
|
||||
case GT_SELECT_INC:
|
||||
#endif
|
||||
case GT_SELECT:
|
||||
{
|
||||
GenTreeConditional* const conditional = this->AsConditional();
|
||||
|
@ -9713,11 +9713,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node)
|
|||
m_state = -1;
|
||||
return;
|
||||
|
||||
// Standard unary operators
|
||||
#ifdef TARGET_ARM64
|
||||
case GT_CNEG_LT:
|
||||
case GT_CINCCC:
|
||||
#endif // TARGET_ARM64
|
||||
// Standard unary operators
|
||||
case GT_STORE_LCL_VAR:
|
||||
case GT_STORE_LCL_FLD:
|
||||
case GT_NOT:
|
||||
|
@ -9829,7 +9825,11 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node)
|
|||
m_advance = &GenTreeUseEdgeIterator::AdvanceCall<CALL_ARGS>;
|
||||
AdvanceCall<CALL_ARGS>();
|
||||
return;
|
||||
|
||||
#ifdef TARGET_ARM64
|
||||
case GT_SELECT_NEG:
|
||||
case GT_SELECT_INV:
|
||||
case GT_SELECT_INC:
|
||||
#endif
|
||||
case GT_SELECT:
|
||||
m_edge = &m_node->AsConditional()->gtCond;
|
||||
assert(*m_edge != nullptr);
|
||||
|
@ -9955,8 +9955,15 @@ void GenTreeUseEdgeIterator::AdvanceConditional()
|
|||
switch (m_state)
|
||||
{
|
||||
case 0:
|
||||
m_edge = &conditional->gtOp1;
|
||||
m_state = 1;
|
||||
m_edge = &conditional->gtOp1;
|
||||
if (conditional->gtOp2 == nullptr)
|
||||
{
|
||||
m_advance = &GenTreeUseEdgeIterator::Terminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_state = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
m_edge = &conditional->gtOp2;
|
||||
|
@ -12229,7 +12236,7 @@ void Compiler::gtDispTree(GenTree* tree,
|
|||
printf(" cond=%s", tree->AsOpCC()->gtCondition.Name());
|
||||
}
|
||||
#ifdef TARGET_ARM64
|
||||
else if (tree->OperIs(GT_CINCCC))
|
||||
else if (tree->OperIs(GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC))
|
||||
{
|
||||
printf(" cond=%s", tree->AsOpCC()->gtCondition.Name());
|
||||
}
|
||||
|
|
|
@ -1679,7 +1679,7 @@ public:
|
|||
}
|
||||
#endif
|
||||
#if defined(TARGET_ARM64)
|
||||
if (OperIs(GT_CCMP, GT_CINCCC))
|
||||
if (OperIs(GT_CCMP, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -1733,6 +1733,10 @@ public:
|
|||
#if defined(TARGET_ARM)
|
||||
case GT_PUTARG_REG:
|
||||
#endif // defined(TARGET_ARM)
|
||||
#if defined(TARGET_ARM64)
|
||||
case GT_SELECT_NEGCC:
|
||||
case GT_SELECT_INCCC:
|
||||
#endif // defined(TARGET_ARM64)
|
||||
|
||||
return true;
|
||||
default:
|
||||
|
@ -8637,7 +8641,11 @@ struct GenTreeOpCC : public GenTreeOp
|
|||
GenTreeOpCC(genTreeOps oper, var_types type, GenCondition condition, GenTree* op1 = nullptr, GenTree* op2 = nullptr)
|
||||
: GenTreeOp(oper, type, op1, op2 DEBUGARG(/*largeNode*/ FALSE)), gtCondition(condition)
|
||||
{
|
||||
#ifdef TARGET_ARM64
|
||||
assert(OperIs(GT_SELECTCC, GT_SELECT_INCCC, GT_SELECT_INVCC, GT_SELECT_NEGCC));
|
||||
#else
|
||||
assert(OperIs(GT_SELECTCC));
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUGGABLE_GENTREE
|
||||
|
|
|
@ -215,8 +215,6 @@ GTNODE(AND_NOT , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
|
|||
|
||||
#ifdef TARGET_ARM64
|
||||
GTNODE(BFIZ , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) // Bitfield Insert in Zero.
|
||||
GTNODE(CSNEG_MI , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR) // Conditional select, negate, minus result
|
||||
GTNODE(CNEG_LT , GenTreeOp ,0,GTK_UNOP|DBK_NOTHIR) // Conditional, negate, signed less than result
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -246,11 +244,21 @@ GTNODE(SELECTCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR)
|
|||
// operands and sets the condition flags according to the result. Otherwise
|
||||
// sets the condition flags to the specified immediate value.
|
||||
GTNODE(CCMP , GenTreeCCMP ,0,GTK_BINOP|GTK_NOVALUE|DBK_NOTHIR)
|
||||
// Maps to arm64 cinc instruction. It returns the operand incremented by one when the condition is true.
|
||||
// Otherwise returns the unchanged operand. Optimises for patterns such as, result = condition ? op1 + 1 : op1
|
||||
GTNODE(CINC , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
|
||||
// Variant of CINC that reuses flags computed by a previous node with the specified condition.
|
||||
GTNODE(CINCCC , GenTreeOpCC ,0,GTK_UNOP|DBK_NOTHIR)
|
||||
// Maps to arm64 csinc/cinc instruction. Computes result = condition ? op1 : op2 + 1.
|
||||
// If op2 is null, computes result = condition ? op1 + 1 : op1.
|
||||
GTNODE(SELECT_INC , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
|
||||
// Variant of SELECT_INC that reuses flags computed by a previous node with the specified condition.
|
||||
GTNODE(SELECT_INCCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR)
|
||||
// Maps to arm64 csinv/cinv instruction. Computes result = condition ? op1 : ~op2.
|
||||
// If op2 is null, computes result = condition ? ~op1 : op1.
|
||||
GTNODE(SELECT_INV , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
|
||||
// Variant of SELECT_INV that reuses flags computed by a previous node with the specified condition.
|
||||
GTNODE(SELECT_INVCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR)
|
||||
// Maps to arm64 csneg/cneg instruction.. Computes result = condition ? op1 : -op2.
|
||||
// If op2 is null, computes result = condition ? -op1 : op1.
|
||||
GTNODE(SELECT_NEG , GenTreeOp ,0,GTK_BINOP|DBK_NOTHIR)
|
||||
// Variant of SELECT_NEG that reuses flags computed by a previous node with the specified condition.
|
||||
GTNODE(SELECT_NEGCC , GenTreeOpCC ,0,GTK_BINOP|DBK_NOTHIR)
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
@ -94,7 +94,11 @@ GTSTRUCT_1(PhiArg , GT_PHI_ARG)
|
|||
GTSTRUCT_1(Phi , GT_PHI)
|
||||
GTSTRUCT_1(StoreInd , GT_STOREIND)
|
||||
GTSTRUCT_N(Indir , GT_STOREIND, GT_IND, GT_NULLCHECK, GT_BLK, GT_STORE_BLK, GT_STORE_DYN_BLK)
|
||||
#ifdef TARGET_ARM64
|
||||
GTSTRUCT_N(Conditional , GT_SELECT, GT_SELECT_INC, GT_SELECT_INV, GT_SELECT_NEG)
|
||||
#else
|
||||
GTSTRUCT_N(Conditional , GT_SELECT)
|
||||
#endif //TARGET_ARM64
|
||||
#if FEATURE_ARG_SPLIT
|
||||
GTSTRUCT_2_SPECIAL(PutArgStk, GT_PUTARG_STK, GT_PUTARG_SPLIT)
|
||||
GTSTRUCT_1(PutArgSplit , GT_PUTARG_SPLIT)
|
||||
|
@ -111,7 +115,7 @@ GTSTRUCT_1(ArrAddr , GT_ARR_ADDR)
|
|||
GTSTRUCT_2(CC , GT_JCC, GT_SETCC)
|
||||
#ifdef TARGET_ARM64
|
||||
GTSTRUCT_1(CCMP , GT_CCMP)
|
||||
GTSTRUCT_4(OpCC , GT_SELECTCC, GT_CINCCC, GT_JCMP, GT_JTEST)
|
||||
GTSTRUCT_N(OpCC , GT_SELECTCC, GT_SELECT_INCCC, GT_JCMP, GT_JTEST, GT_SELECT_INVCC, GT_SELECT_NEGCC)
|
||||
#else
|
||||
GTSTRUCT_3(OpCC , GT_SELECTCC, GT_JCMP, GT_JTEST)
|
||||
#endif
|
||||
|
|
|
@ -3851,11 +3851,16 @@ GenTree* Lowering::LowerSelect(GenTreeConditional* select)
|
|||
}
|
||||
|
||||
#ifdef TARGET_ARM64
|
||||
if (trueVal->IsCnsIntOrI() && falseVal->IsCnsIntOrI())
|
||||
if (trueVal->OperIs(GT_NOT, GT_NEG) || falseVal->OperIs(GT_NOT, GT_NEG))
|
||||
{
|
||||
TryLowerCselToCinvOrCneg(select, cond);
|
||||
}
|
||||
else if (trueVal->IsCnsIntOrI() && falseVal->IsCnsIntOrI())
|
||||
{
|
||||
TryLowerCselToCinc(select, cond);
|
||||
}
|
||||
#endif
|
||||
|
||||
return newSelect != nullptr ? newSelect->gtNext : select->gtNext;
|
||||
}
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ private:
|
|||
void ContainCheckConditionalCompare(GenTreeCCMP* ccmp);
|
||||
void ContainCheckNeg(GenTreeOp* neg);
|
||||
void TryLowerCselToCinc(GenTreeOp* select, GenTree* cond);
|
||||
void TryLowerCselToCinvOrCneg(GenTreeOp* select, GenTree* cond);
|
||||
#endif
|
||||
void ContainCheckSelect(GenTreeOp* select);
|
||||
void ContainCheckBitCast(GenTree* node);
|
||||
|
|
|
@ -874,8 +874,11 @@ void Lowering::LowerModPow2(GenTree* node)
|
|||
BlockRange().InsertAfter(cnsZero, cmp);
|
||||
LowerNode(cmp);
|
||||
|
||||
mod->ChangeOper(GT_CNEG_LT);
|
||||
mod->gtOp1 = trueExpr;
|
||||
mod->ChangeOper(GT_SELECT_NEGCC);
|
||||
GenTreeOpCC* node = mod->AsOpCC();
|
||||
node->gtOp1 = trueExpr;
|
||||
node->gtOp2 = nullptr;
|
||||
node->gtCondition = GenCondition::SLT;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -900,9 +903,11 @@ void Lowering::LowerModPow2(GenTree* node)
|
|||
BlockRange().InsertAfter(cns2, falseExpr);
|
||||
LowerNode(falseExpr);
|
||||
|
||||
mod->ChangeOper(GT_CSNEG_MI);
|
||||
mod->gtOp1 = trueExpr;
|
||||
mod->gtOp2 = falseExpr;
|
||||
mod->SetOper(GT_SELECT_NEGCC);
|
||||
GenTreeOpCC* node = mod->AsOpCC();
|
||||
node->gtOp1 = trueExpr;
|
||||
node->gtOp2 = falseExpr;
|
||||
node->gtCondition = GenCondition::S;
|
||||
}
|
||||
|
||||
ContainCheckNode(mod);
|
||||
|
@ -2552,8 +2557,93 @@ void Lowering::ContainCheckNeg(GenTreeOp* neg)
|
|||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
// Try converting SELECT/SELECTCC to CINC/CINCCC. Conversion is possible only if
|
||||
// both the trueVal and falseVal are integral constants and abs(trueVal - falseVal) = 1.
|
||||
// TryLowerCselToCinvOrCneg: Try converting SELECT/SELECTCC to SELECT_INV/SELECT_INVCC. Conversion is possible only if
|
||||
// one of the operands of the select node is inverted.
|
||||
//
|
||||
// Arguments:
|
||||
// select - The select node that is now SELECT or SELECTCC
|
||||
// cond - The condition node that SELECT or SELECTCC uses
|
||||
//
|
||||
void Lowering::TryLowerCselToCinvOrCneg(GenTreeOp* select, GenTree* cond)
|
||||
{
|
||||
assert(select->OperIs(GT_SELECT, GT_SELECTCC));
|
||||
|
||||
bool shouldReverseCondition;
|
||||
GenTree* invertedOrNegatedVal;
|
||||
GenTree* nonInvertedOrNegatedVal;
|
||||
GenTree* nodeToRemove;
|
||||
|
||||
GenTree* trueVal = select->gtOp1;
|
||||
GenTree* falseVal = select->gtOp2;
|
||||
const bool isCneg = trueVal->OperIs(GT_NEG) || falseVal->OperIs(GT_NEG);
|
||||
|
||||
assert(trueVal->OperIs(GT_NOT, GT_NEG) || falseVal->OperIs(GT_NOT, GT_NEG));
|
||||
|
||||
if (trueVal->OperIs(GT_NOT) || trueVal->OperIs(GT_NEG))
|
||||
{
|
||||
shouldReverseCondition = true;
|
||||
invertedOrNegatedVal = trueVal->gtGetOp1();
|
||||
nonInvertedOrNegatedVal = falseVal;
|
||||
nodeToRemove = trueVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldReverseCondition = false;
|
||||
invertedOrNegatedVal = falseVal->gtGetOp1();
|
||||
nonInvertedOrNegatedVal = trueVal;
|
||||
nodeToRemove = falseVal;
|
||||
}
|
||||
|
||||
if (shouldReverseCondition && !cond->OperIsCompare() && select->OperIs(GT_SELECT))
|
||||
{
|
||||
// Non-compare nodes add additional GT_NOT node after reversing.
|
||||
// This would remove gains from this optimisation so don't proceed.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(IsInvariantInRange(invertedOrNegatedVal, select) && IsInvariantInRange(nonInvertedOrNegatedVal, select)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// As the select node would handle the negation/inversion, the op is not required.
|
||||
// If a value is contained in the negate/invert op, it cannot be contained anymore.
|
||||
BlockRange().Remove(nodeToRemove);
|
||||
invertedOrNegatedVal->ClearContained();
|
||||
select->gtOp1 = nonInvertedOrNegatedVal;
|
||||
select->gtOp2 = invertedOrNegatedVal;
|
||||
|
||||
if (select->OperIs(GT_SELECT))
|
||||
{
|
||||
if (shouldReverseCondition)
|
||||
{
|
||||
GenTree* revCond = comp->gtReverseCond(cond);
|
||||
assert(cond == revCond); // Ensure `gtReverseCond` did not create a new node.
|
||||
}
|
||||
select->SetOper(isCneg ? GT_SELECT_NEG : GT_SELECT_INV);
|
||||
JITDUMP("Converted to: %s\n", isCneg ? "SELECT_NEG" : "SELECT_INV");
|
||||
DISPTREERANGE(BlockRange(), select);
|
||||
JITDUMP("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
GenTreeOpCC* selectcc = select->AsOpCC();
|
||||
GenCondition selectCond = selectcc->gtCondition;
|
||||
if (shouldReverseCondition)
|
||||
{
|
||||
// Reverse the condition so that op2 will be selected
|
||||
selectcc->gtCondition = GenCondition::Reverse(selectCond);
|
||||
}
|
||||
selectcc->SetOper(isCneg ? GT_SELECT_NEGCC : GT_SELECT_INVCC);
|
||||
JITDUMP("Converted to: %s\n", isCneg ? "SELECT_NEGCC" : "SELECT_INVCC");
|
||||
DISPTREERANGE(BlockRange(), selectcc);
|
||||
JITDUMP("\n");
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
// TryLowerCselToCinc: Try converting SELECT/SELECTCC to SELECT_INC/SELECT_INCCC. Conversion is possible only if both
|
||||
// the trueVal and falseVal are integral constants and abs(trueVal - falseVal) = 1.
|
||||
//
|
||||
// Arguments:
|
||||
// select - The select node that is now SELECT or SELECTCC
|
||||
|
@ -2568,11 +2658,10 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond)
|
|||
size_t op1Val = (size_t)trueVal->AsIntCon()->IconValue();
|
||||
size_t op2Val = (size_t)falseVal->AsIntCon()->IconValue();
|
||||
|
||||
if (op1Val + 1 == op2Val || op2Val + 1 == op1Val)
|
||||
if ((op1Val + 1 == op2Val) || (op2Val + 1 == op1Val))
|
||||
{
|
||||
const bool shouldReverseCondition = op1Val + 1 == op2Val;
|
||||
const bool shouldReverseCondition = (op1Val + 1 == op2Val);
|
||||
|
||||
// Create a cinc node, insert it and update the use.
|
||||
if (select->OperIs(GT_SELECT))
|
||||
{
|
||||
if (shouldReverseCondition)
|
||||
|
@ -2584,18 +2673,21 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond)
|
|||
// This would remove gains from this optimisation so don't proceed.
|
||||
return;
|
||||
}
|
||||
select->gtOp2 = select->gtOp1;
|
||||
GenTree* revCond = comp->gtReverseCond(cond);
|
||||
assert(cond == revCond); // Ensure `gtReverseCond` did not create a new node.
|
||||
}
|
||||
select->gtOp1 = cond->AsOp();
|
||||
select->SetOper(GT_CINC);
|
||||
BlockRange().Remove(select->gtOp2, true);
|
||||
select->gtOp2 = nullptr;
|
||||
select->SetOper(GT_SELECT_INC);
|
||||
JITDUMP("Converted to: GT_SELECT_INC\n");
|
||||
DISPTREERANGE(BlockRange(), select);
|
||||
JITDUMP("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
GenTreeOpCC* selectcc = select->AsOpCC();
|
||||
GenCondition selectCond = selectcc->gtCondition;
|
||||
|
||||
if (shouldReverseCondition)
|
||||
{
|
||||
// Reverse the condition so that op2 will be selected
|
||||
|
@ -2605,9 +2697,13 @@ void Lowering::TryLowerCselToCinc(GenTreeOp* select, GenTree* cond)
|
|||
{
|
||||
std::swap(selectcc->gtOp1, selectcc->gtOp2);
|
||||
}
|
||||
BlockRange().Remove(selectcc->gtOp2);
|
||||
selectcc->SetOper(GT_CINCCC);
|
||||
|
||||
BlockRange().Remove(selectcc->gtOp2, true);
|
||||
selectcc->gtOp2 = nullptr;
|
||||
selectcc->SetOper(GT_SELECT_INCCC);
|
||||
JITDUMP("Converted to: GT_SELECT_INCCC\n");
|
||||
DISPTREERANGE(BlockRange(), selectcc);
|
||||
JITDUMP("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,193 +10,148 @@ using Xunit;
|
|||
public class ConditionalIncrementTest
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[InlineData(72, 6)]
|
||||
[InlineData(32, 5)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static int cinc_byte(byte op1)
|
||||
public static void cinc_byte(byte op1, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #42
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
return op1 > 42 ? 6: 5;
|
||||
int result = op1 > 42 ? 6: 5;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(72, byte.MinValue)]
|
||||
[InlineData(32, byte.MaxValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static byte cinc_byte_min_max(byte op1)
|
||||
public static void cinc_byte_min_max(byte op1, byte expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #43
|
||||
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, wzr, {{w[0-9]+}}, {{ge|lt}}
|
||||
return op1 >= 43 ? byte.MinValue : byte.MaxValue;
|
||||
byte result = op1 >= 43 ? byte.MinValue : byte.MaxValue;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(72, byte.MinValue)]
|
||||
[InlineData(32, byte.MaxValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static int cinc_short(short op1)
|
||||
public static void cinv_byte_min_max(byte op1, byte expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #43
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}}
|
||||
byte result = (byte) (op1 >= 43 ? byte.MinValue : ~byte.MinValue);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(74, 5)]
|
||||
[InlineData(34, 6)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinc_short(short op1, short expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #44
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
return op1 <= 44 ? 6 : 5;
|
||||
short result = (short) (op1 <= 44 ? 6 : 5);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(76, short.MinValue)]
|
||||
[InlineData(-35, short.MaxValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static short cinc_short_min_max(short op1)
|
||||
public static void cinc_short_min_max(short op1, short expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #45
|
||||
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
return op1 > 45 ? short.MinValue : short.MaxValue;
|
||||
short result = op1 > 45 ? short.MinValue : short.MaxValue;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(76, 6)]
|
||||
[InlineData(36, 5)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static int cinc_int(int op1)
|
||||
public static void cinc_int(int op1, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #46
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
return op1 > 46 ? 6 : 5;
|
||||
int result = op1 > 46 ? 6 : 5;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(77, int.MinValue)]
|
||||
[InlineData(37, int.MaxValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static int cinc_int_min_max(int op1)
|
||||
public static void cinc_int_min_max(int op1, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #47
|
||||
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}}
|
||||
return op1 >= 47 ? int.MinValue : int.MaxValue;
|
||||
int result = op1 >= 47 ? int.MinValue : int.MaxValue;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(78, 5)]
|
||||
[InlineData(38, 6)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static long cinc_long(long op1)
|
||||
public static void cinc_long(long op1, long expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{x[0-9]+}}, #48
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}}
|
||||
//ARM64-FULL-LINE-NEXT: sxtw {{x[0-9]+}}, {{w[0-9]+}}
|
||||
return op1 < 48 ? 6 : 5;
|
||||
long result = op1 < 48 ? 6 : 5;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(79, long.MaxValue)]
|
||||
[InlineData(39, long.MinValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static long cinc_long_min_max(long op1)
|
||||
public static void cinc_long_min_max(long op1, long expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{x[0-9]+}}, #49
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{x[0-9]+}}, {{x[0-9]+}}, {{ge|lt}}
|
||||
return op1 < 49 ? long.MinValue : long.MaxValue;
|
||||
long result = op1 < 49 ? long.MinValue : long.MaxValue;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(79, long.MaxValue)]
|
||||
[InlineData(39, long.MinValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static int cinc_float(float op1)
|
||||
public static void cinv_long_min_max(long op1, long expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{x[0-9]+}}, #49
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{x[0-9]+}}, {{x[0-9]+}}, {{ge|lt}}
|
||||
long result = op1 < 49 ? long.MinValue : ~long.MinValue;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(80.0f, 6)]
|
||||
[InlineData(30.0f, 5)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinc_float(float op1, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: fcmp {{s[0-9]+}}, {{s[0-9]+}}
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
return op1 > 50.0f ? 6 : 5;
|
||||
int result = op1 > 50.0f ? 6 : 5;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public static int TestEntryPoint()
|
||||
[Theory]
|
||||
[InlineData(80.0, 5)]
|
||||
[InlineData(30.0, 6)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinc_double(double op1, int expected)
|
||||
{
|
||||
if (cinc_byte(72) != 6)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_byte() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_byte(32) != 5)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_byte() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_byte_min_max(72) != byte.MinValue)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_byte_min_max() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_byte_min_max(32) != byte.MaxValue)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_byte_min_max() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_short(34) != 6)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_short() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_short(74) != 5)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_short() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_short_min_max(75) != short.MinValue)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_short_min_max() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_short_min_max(-35) != short.MaxValue)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_short_min_max() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_int(76) != 6)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_int() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_int(36) != 5)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_int() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_int_min_max(77) != int.MinValue)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_int_min_max() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_int_min_max(37) != int.MaxValue)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_int_min_max() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_long(78) != 5)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_long() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_long(38) != 6)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_long() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_long_min_max(79) != long.MaxValue)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_long_min_max() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_long_min_max(39) != long.MinValue)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_long_min_max() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_float(80.0f) != 6)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_float() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
if (cinc_float(30.0f) != 5)
|
||||
{
|
||||
Console.WriteLine("ConditionalIncrementTest:cinc_float() failed");
|
||||
return 101;
|
||||
}
|
||||
|
||||
Console.WriteLine("PASSED");
|
||||
return 100;
|
||||
//ARM64-FULL-LINE: fcmp {{d[0-9]+}}, {{d[0-9]+}}
|
||||
//ARM64-FULL-LINE-NEXT: cinc {{w[0-9]+}}, {{w[0-9]+}}, {{hs|lo}}
|
||||
int result = op1 < 51.0 ? 6 : 5;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
}
|
||||
|
|
133
src/tests/JIT/opt/Compares/conditionalInverts.cs
Normal file
133
src/tests/JIT/opt/Compares/conditionalInverts.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
// unit test for the full range comparison optimization
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xunit;
|
||||
|
||||
public class ConditionalInvertTest
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[InlineData(72, 13, 13)]
|
||||
[InlineData(32, 13, 223)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_byte(byte op1, byte op2, byte expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #42
|
||||
//ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
byte result = (byte) (op1 > 42 ? op2: ~op1);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(34, 13, ~13)]
|
||||
[InlineData(74, 13, 74)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_short(short op1, short op2, short expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #43
|
||||
//ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
short result = (short) (op1 <= 43 ? ~op2 : op1);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(75, ~short.MaxValue)]
|
||||
[InlineData(-35, short.MaxValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_short_min_max(short op1, short expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #44
|
||||
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
short result = (short) (op1 > 44 ? ~short.MaxValue : short.MaxValue);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(76, 17, 17)]
|
||||
[InlineData(36, 17, ~36)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_int(int op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #45
|
||||
//ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 45 ? op2 : ~op1;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(77, int.MaxValue)]
|
||||
[InlineData(37, ~int.MaxValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_int_min_max(int op1, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #46
|
||||
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}}
|
||||
int result = op1 >= 46 ? int.MaxValue : ~int.MaxValue;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(78, 23, 78)]
|
||||
[InlineData(38, 23, ~23)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_long(long op1, long op2, long expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{x[0-9]+}}, #47
|
||||
//ARM64-FULL-LINE-NEXT: csinv {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, {{ge|lt}}
|
||||
long result = op1 < 47 ? ~op2 : op1;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(80.0f, 29, 29)]
|
||||
[InlineData(30.0f, 29, ~29)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_float(float op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: fcmp {{s[0-9]+}}, {{s[0-9]+}}
|
||||
//ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 48.0f ? op2 : ~op2;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(60.0, 31, ~31)]
|
||||
[InlineData(30.0, 31, 31)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_double(double op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: fcmp {{d[0-9]+}}, {{d[0-9]+}}
|
||||
//ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 49.0 ? ~op2 : op2;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(81, 21, 21)]
|
||||
[InlineData(31, 17, -8)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_int_shifted_false_opr(int op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #50
|
||||
//ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 50 ? op2 : ~(op1 >> 2);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(81, 21, -21)]
|
||||
[InlineData(31, 17, 17)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cinv_int_shifted_true_opr(int op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #51
|
||||
//ARM64-FULL-LINE-NEXT: csinv {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 51 ? ~(op1 >> 2) : op2;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
}
|
18
src/tests/JIT/opt/Compares/conditionalInverts.csproj
Normal file
18
src/tests/JIT/opt/Compares/conditionalInverts.csproj
Normal file
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- Needed for CLRTestEnvironmentVariable -->
|
||||
<RequiresProcessIsolation>true</RequiresProcessIsolation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DebugType>None</DebugType>
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildProjectName).cs">
|
||||
<HasDisasmCheck>true</HasDisasmCheck>
|
||||
</Compile>
|
||||
|
||||
<CLRTestEnvironmentVariable Include="DOTNET_TieredCompilation" Value="0" />
|
||||
<CLRTestEnvironmentVariable Include="DOTNET_JITMinOpts" Value="0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
132
src/tests/JIT/opt/Compares/conditionalNegates.cs
Normal file
132
src/tests/JIT/opt/Compares/conditionalNegates.cs
Normal file
|
@ -0,0 +1,132 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
// unit test for the full range comparison optimization
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xunit;
|
||||
|
||||
public class ConditionalNegateTest
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[InlineData(72, 13, 13)]
|
||||
[InlineData(32, 13, 224)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_byte(byte op1, byte op2, byte expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #42
|
||||
//ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
byte result = (byte) (op1 > 42 ? op2: -op1);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(74, 13, 74)]
|
||||
[InlineData(34, 13, -13)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_short(short op1, short op2, short expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #43
|
||||
//ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
short result = (short) (op1 <= 43 ? -op2 : op1);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(75, -short.MaxValue)]
|
||||
[InlineData(-35, short.MaxValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_short_min_max(short op1, short expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #44
|
||||
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
short result = (short) (op1 > 44 ? -short.MaxValue : short.MaxValue);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(76, 17, 17)]
|
||||
[InlineData(36, 17, -36)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_int(int op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #45
|
||||
//ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 45 ? op2 : -op1;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(77, int.MaxValue)]
|
||||
[InlineData(37, -int.MaxValue)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_int_min_max(int op1, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #46
|
||||
//ARM64-FULL-LINE-NEXT: csel {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}}
|
||||
int result = op1 >= 46 ? int.MaxValue : -int.MaxValue;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(78, 23, 78)]
|
||||
[InlineData(38, 23, -23)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_long(long op1, long op2, long expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{x[0-9]+}}, #47
|
||||
//ARM64-FULL-LINE-NEXT: csneg {{x[0-9]+}}, {{x[0-9]+}}, {{x[0-9]+}}, {{ge|lt}}
|
||||
long result = op1 < 47 ? -op2 : op1;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(80.0f, 29, 29)]
|
||||
[InlineData(30.0f, 29, -29)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_float(float op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: fcmp {{s[0-9]+}}, {{s[0-9]+}}
|
||||
//ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 48.0f ? op2 : -op2;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(60.0, 31, -31)]
|
||||
[InlineData(30.0, 31, 31)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_double(double op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: fcmp {{d[0-9]+}}, {{d[0-9]+}}
|
||||
//ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 49.0 ? -op2 : op2;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(81, 21, 21)]
|
||||
[InlineData(31, 21, -62)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_shifted_false_oper(int op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #50
|
||||
//ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{gt|le}}
|
||||
int result = op1 > 50 ? op2 : -(op1 << 1);
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(82, 22, 22)]
|
||||
[InlineData(32, 22, -4)]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void cneg_shifted_true_oper(int op1, int op2, int expected)
|
||||
{
|
||||
//ARM64-FULL-LINE: cmp {{w[0-9]+}}, #51
|
||||
//ARM64-FULL-LINE-NEXT: csneg {{w[0-9]+}}, {{w[0-9]+}}, {{w[0-9]+}}, {{ge|lt}}
|
||||
int result = op1 < 51 ? -(op1 >> 3) : op2;
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
}
|
18
src/tests/JIT/opt/Compares/conditionalNegates.csproj
Normal file
18
src/tests/JIT/opt/Compares/conditionalNegates.csproj
Normal file
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<!-- Needed for CLRTestEnvironmentVariable -->
|
||||
<RequiresProcessIsolation>true</RequiresProcessIsolation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DebugType>None</DebugType>
|
||||
<Optimize>True</Optimize>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildProjectName).cs">
|
||||
<HasDisasmCheck>true</HasDisasmCheck>
|
||||
</Compile>
|
||||
|
||||
<CLRTestEnvironmentVariable Include="DOTNET_TieredCompilation" Value="0" />
|
||||
<CLRTestEnvironmentVariable Include="DOTNET_JITMinOpts" Value="0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
Loading…
Add table
Add a link
Reference in a new issue