1
0
Fork 0
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:
SwapnilGaikwad 2023-05-25 21:00:09 +01:00 committed by GitHub
parent 66644e3c5e
commit 86ce145893
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 633 additions and 318 deletions

View file

@ -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)

View file

@ -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.
//

View file

@ -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;

View file

@ -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:

View file

@ -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());
}

View file

@ -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

View file

@ -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
//-----------------------------------------------------------------------------

View file

@ -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

View file

@ -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;
}

View file

@ -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);

View file

@ -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");
}
}
}

View file

@ -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);
}
}

View 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);
}
}

View 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>

View 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);
}
}

View 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>