-
Notifications
You must be signed in to change notification settings - Fork 5.3k
JIT: Improve constant folding for commutative operators #656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4773349
e4160d8
960dab6
a6a1afc
8ddbe2c
16754e3
e16225a
3e57d3d
409ddf6
be96dd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10830,6 +10830,173 @@ GenTree* Compiler::fgMorphFieldAssignToSIMDIntrinsicSet(GenTree* tree) | |
|
|
||
| #endif // FEATURE_SIMD | ||
|
|
||
| //------------------------------------------------------------------------ | ||
| // fgMorphCommutative: Fold constants in a tree with commutative operators (|, ^, &, +) | ||
| // | ||
| // Arguments: | ||
| // tree - This node will be checked for patterns where constants can be folded. | ||
| // | ||
| // Return Value: | ||
| // A GenTree* which points to the new tree with folded constants. | ||
| // | ||
|
|
||
| GenTree* Compiler::fgMorphCommutative(GenTreeOp* tree) | ||
| { | ||
| assert(tree->OperIs(GT_OR, GT_XOR, GT_AND, GT_ADD)); | ||
|
|
||
| GenTree* op1 = tree->gtGetOp1(); | ||
| GenTree* op2 = tree->gtGetOp2(); | ||
| genTreeOps oper = tree->OperGet(); | ||
|
|
||
| // Fold (X <op> C1) <op> (Y <op> C2) | ||
| // to (X <op> Y) <op> (C1 <op> C2) | ||
| if (op1->OperIs(oper) && op2->OperIs(oper) && | ||
| op1->AsOp()->gtGetOp2()->IsCnsIntOrI() && op2->AsOp()->gtGetOp2()->IsCnsIntOrI() && | ||
| !gtIsActiveCSE_Candidate(op1) && !gtIsActiveCSE_Candidate(op2)) | ||
| { | ||
| // Don't create a byref pointer that may point outside of the ref object. | ||
| // If a GC happens, the byref won't get updated. This can happen if one | ||
| // of the int components is negative. It also requires the address generation | ||
| // be in a fully-interruptible code region. | ||
| if (!varTypeIsGC(op1->AsOp()->gtGetOp1()->TypeGet()) && !varTypeIsGC(op2->AsOp()->gtGetOp1()->TypeGet())) | ||
| { | ||
| GenTreeIntCon* cns1 = op1->AsOp()->gtGetOp2()->AsIntCon(); | ||
| GenTreeIntCon* cns2 = op2->AsOp()->gtGetOp2()->AsIntCon(); | ||
|
|
||
| if (oper == GT_ADD) | ||
| { | ||
| if (tree->gtOverflow() || op1->gtOverflow() || op2->gtOverflow()) | ||
| { | ||
| return tree; | ||
| } | ||
| cns1->SetIconValue(cns1->IconValue() + cns2->IconValue()); | ||
| } | ||
| else if (oper == GT_OR) | ||
| { | ||
| cns1->SetIconValue(cns1->IconValue() | cns2->IconValue()); | ||
| } | ||
| else if (oper == GT_XOR) | ||
| { | ||
| cns1->SetIconValue(cns1->IconValue() ^ cns2->IconValue()); | ||
| } | ||
| else if (oper == GT_AND) | ||
| { | ||
| cns1->SetIconValue(cns1->IconValue() & cns2->IconValue()); | ||
| } | ||
| else | ||
| { | ||
| noway_assert(!"unexpected operator"); | ||
| } | ||
| #ifdef _TARGET_64BIT_ | ||
| if (cns1->TypeGet() == TYP_INT) | ||
| { | ||
| // we need to properly re-sign-extend or truncate after adding two int constants above | ||
| cns1->TruncateOrSignExtend32(); | ||
| } | ||
| #endif //_TARGET_64BIT_ | ||
|
|
||
| tree->gtOp2 = cns1; | ||
| DEBUG_DESTROY_NODE(cns2); | ||
|
|
||
| op1->AsOp()->gtOp2 = op2->AsOp()->gtOp1; | ||
| op1->gtFlags |= (op1->AsOp()->gtOp2->gtFlags & GTF_ALL_EFFECT); | ||
| DEBUG_DESTROY_NODE(op2); | ||
| op2 = tree->gtOp2; | ||
| } | ||
| } | ||
|
|
||
| if (op2->IsCnsIntOrI() && varTypeIsIntegralOrI(tree->TypeGet())) | ||
| { | ||
| // Fold (X <op> C1) <op> C2 | ||
| // to (X <op> (C1 <op> C2) | ||
| if (op1->OperIs(oper) && !gtIsActiveCSE_Candidate(op1) && op1->AsOp()->gtGetOp2()->IsCnsIntOrI() && | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here you check
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed |
||
| (op1->AsOp()->gtGetOp2()->OperGet() == op2->OperGet()) && | ||
| !varTypeIsGC(op1->AsOp()->gtGetOp2()->TypeGet())) | ||
| { | ||
| GenTreeIntConCommon* cns1 = op1->AsOp()->gtGetOp2()->AsIntConCommon(); | ||
| GenTreeIntConCommon* cns2 = op2->AsIntConCommon(); | ||
|
|
||
| if (oper == GT_ADD) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that code duplication doesn't look cool, but a function with 6 arguments
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neither am I, tried to simplify it a bit. Let me know if I should extract it to a separate method. |
||
| { | ||
| if (tree->gtOverflow() || op1->gtOverflow()) | ||
| { | ||
| return tree; | ||
| } | ||
| cns2->SetIconValue(cns1->IconValue() + cns2->IconValue()); | ||
| } | ||
| else if (oper == GT_OR) | ||
| { | ||
| cns2->SetIconValue(cns1->IconValue() | cns2->IconValue()); | ||
| } | ||
| else if (oper == GT_XOR) | ||
| { | ||
| cns2->SetIconValue(cns1->IconValue() ^ cns2->IconValue()); | ||
| } | ||
| else if (oper == GT_AND) | ||
| { | ||
| cns2->SetIconValue(cns1->IconValue() & cns2->IconValue()); | ||
| } | ||
| else | ||
| { | ||
| noway_assert(!"unexpected operator"); | ||
| } | ||
| #ifdef _TARGET_64BIT_ | ||
| if (op2->TypeGet() == TYP_INT) | ||
| { | ||
| // we need to properly re-sign-extend or truncate after folding two int constants above | ||
| op2->AsIntCon()->TruncateOrSignExtend32(); | ||
| } | ||
| #endif //_TARGET_64BIT_ | ||
|
|
||
| if (cns1->OperGet() == GT_CNS_INT) | ||
| { | ||
| op2->AsIntCon()->gtFieldSeq = | ||
| GetFieldSeqStore()->Append(cns1->AsIntCon()->gtFieldSeq, op2->AsIntCon()->gtFieldSeq); | ||
| } | ||
|
|
||
| DEBUG_DESTROY_NODE(cns1); | ||
| tree->gtOp1 = op1->AsOp()->gtOp1; | ||
| DEBUG_DESTROY_NODE(op1); | ||
| op1 = tree->gtOp1; | ||
| } | ||
|
|
||
| // Fold X <op> 0 | ||
| // to X | ||
| if (tree->OperIs(GT_ADD, GT_OR, GT_XOR) && (op2->AsIntConCommon()->IconValue() == 0) && | ||
| !gtIsActiveCSE_Candidate(tree)) | ||
| { | ||
| // If this addition is adding an offset to a null pointer, | ||
| // avoid the work and yield the null pointer immediately. | ||
| // Dereferencing the pointer in either case will have the | ||
| // same effect. | ||
| if (!optValnumCSE_phase && varTypeIsGC(op2->TypeGet()) && ((op1->gtFlags & GTF_ALL_EFFECT) == 0)) | ||
| { | ||
| op2->gtType = tree->gtType; | ||
| DEBUG_DESTROY_NODE(op1); | ||
| DEBUG_DESTROY_NODE(tree); | ||
| return op2; | ||
| } | ||
|
|
||
| // Remove the addition if it won't change the tree type | ||
| // to TYP_REF. | ||
| if (!gtIsActiveCSE_Candidate(op2) && ((op1->TypeGet() == tree->TypeGet()) || (op1->TypeGet() != TYP_REF))) | ||
| { | ||
| if (fgGlobalMorph && (op2->OperGet() == GT_CNS_INT) && (op2->AsIntCon()->gtFieldSeq != nullptr) && | ||
| (op2->AsIntCon()->gtFieldSeq != FieldSeqStore::NotAField())) | ||
| { | ||
| fgAddFieldSeqForZeroOffset(op1, op2->AsIntCon()->gtFieldSeq); | ||
| } | ||
|
|
||
| DEBUG_DESTROY_NODE(op2); | ||
| DEBUG_DESTROY_NODE(tree); | ||
| return op1; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return tree; | ||
| } | ||
|
|
||
| /***************************************************************************** | ||
| * | ||
| * Transform the given GTK_SMPOP tree for code generation. | ||
|
|
@@ -11871,7 +12038,6 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) | |
| */ | ||
|
|
||
| GenTree* temp; | ||
| GenTree* cns1; | ||
| GenTree* cns2; | ||
| size_t ival1, ival2; | ||
| GenTree* lclVarTree; | ||
|
|
@@ -12524,119 +12690,20 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) | |
| op2 = tree->AsOp()->gtOp2; | ||
| } | ||
|
|
||
| /* See if we can fold GT_ADD nodes. */ | ||
|
|
||
| if (oper == GT_ADD) | ||
| // See if we can fold constants for commutative operators. | ||
| if (tree->OperIs(GT_OR, GT_XOR, GT_AND, GT_ADD)) | ||
EgorBo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| /* Fold "((x+icon1)+(y+icon2)) to ((x+y)+(icon1+icon2))" */ | ||
|
|
||
| if (op1->gtOper == GT_ADD && op2->gtOper == GT_ADD && !gtIsActiveCSE_Candidate(op2) && | ||
| op1->AsOp()->gtOp2->gtOper == GT_CNS_INT && op2->AsOp()->gtOp2->gtOper == GT_CNS_INT && | ||
| !op1->gtOverflow() && !op2->gtOverflow()) | ||
| { | ||
| // Don't create a byref pointer that may point outside of the ref object. | ||
| // If a GC happens, the byref won't get updated. This can happen if one | ||
| // of the int components is negative. It also requires the address generation | ||
| // be in a fully-interruptible code region. | ||
| if (!varTypeIsGC(op1->AsOp()->gtOp1->TypeGet()) && !varTypeIsGC(op2->AsOp()->gtOp1->TypeGet())) | ||
| { | ||
| cns1 = op1->AsOp()->gtOp2; | ||
| cns2 = op2->AsOp()->gtOp2; | ||
| cns1->AsIntCon()->gtIconVal += cns2->AsIntCon()->gtIconVal; | ||
| #ifdef _TARGET_64BIT_ | ||
| if (cns1->TypeGet() == TYP_INT) | ||
| { | ||
| // we need to properly re-sign-extend or truncate after adding two int constants above | ||
| cns1->AsIntCon()->TruncateOrSignExtend32(); | ||
| } | ||
| #endif //_TARGET_64BIT_ | ||
|
|
||
| tree->AsOp()->gtOp2 = cns1; | ||
| DEBUG_DESTROY_NODE(cns2); | ||
|
|
||
| op1->AsOp()->gtOp2 = op2->AsOp()->gtOp1; | ||
| op1->gtFlags |= (op1->AsOp()->gtOp2->gtFlags & GTF_ALL_EFFECT); | ||
| DEBUG_DESTROY_NODE(op2); | ||
| op2 = tree->AsOp()->gtOp2; | ||
| } | ||
| } | ||
|
|
||
| if (op2->IsCnsIntOrI() && varTypeIsIntegralOrI(typ)) | ||
| tree = fgMorphCommutative(tree->AsOp()); | ||
| if (!tree->OperIsBinary()) // e.g. fgMorphCommutative optimized `x + 0` into `x` | ||
| { | ||
| /* Fold "((x+icon1)+icon2) to (x+(icon1+icon2))" */ | ||
| CLANG_FORMAT_COMMENT_ANCHOR; | ||
|
|
||
| if (op1->gtOper == GT_ADD && // | ||
| !gtIsActiveCSE_Candidate(op1) && // | ||
| !op1->gtOverflow() && // | ||
| op1->AsOp()->gtOp2->IsCnsIntOrI() && // | ||
| (op1->AsOp()->gtOp2->OperGet() == op2->OperGet()) && // | ||
| (op1->AsOp()->gtOp2->TypeGet() != TYP_REF) && // Don't fold REFs | ||
| (op2->TypeGet() != TYP_REF)) // Don't fold REFs | ||
| { | ||
| cns1 = op1->AsOp()->gtOp2; | ||
| op2->AsIntConCommon()->SetIconValue(cns1->AsIntConCommon()->IconValue() + | ||
| op2->AsIntConCommon()->IconValue()); | ||
| #ifdef _TARGET_64BIT_ | ||
| if (op2->TypeGet() == TYP_INT) | ||
| { | ||
| // we need to properly re-sign-extend or truncate after adding two int constants above | ||
| op2->AsIntCon()->TruncateOrSignExtend32(); | ||
| } | ||
| #endif //_TARGET_64BIT_ | ||
|
|
||
| if (cns1->OperGet() == GT_CNS_INT) | ||
| { | ||
| op2->AsIntCon()->gtFieldSeq = | ||
| GetFieldSeqStore()->Append(cns1->AsIntCon()->gtFieldSeq, op2->AsIntCon()->gtFieldSeq); | ||
| } | ||
| DEBUG_DESTROY_NODE(cns1); | ||
|
|
||
| tree->AsOp()->gtOp1 = op1->AsOp()->gtOp1; | ||
| DEBUG_DESTROY_NODE(op1); | ||
| op1 = tree->AsOp()->gtOp1; | ||
| } | ||
|
|
||
| // Fold (x + 0). | ||
|
|
||
| if ((op2->AsIntConCommon()->IconValue() == 0) && !gtIsActiveCSE_Candidate(tree)) | ||
| { | ||
|
|
||
| // If this addition is adding an offset to a null pointer, | ||
| // avoid the work and yield the null pointer immediately. | ||
| // Dereferencing the pointer in either case will have the | ||
| // same effect. | ||
|
|
||
| if (!optValnumCSE_phase && varTypeIsGC(op2->TypeGet()) && | ||
| ((op1->gtFlags & GTF_ALL_EFFECT) == 0)) | ||
| { | ||
| op2->gtType = tree->gtType; | ||
| DEBUG_DESTROY_NODE(op1); | ||
| DEBUG_DESTROY_NODE(tree); | ||
| return op2; | ||
| } | ||
|
|
||
| // Remove the addition iff it won't change the tree type | ||
| // to TYP_REF. | ||
|
|
||
| if (!gtIsActiveCSE_Candidate(op2) && | ||
| ((op1->TypeGet() == tree->TypeGet()) || (op1->TypeGet() != TYP_REF))) | ||
| { | ||
| if (fgGlobalMorph && (op2->OperGet() == GT_CNS_INT) && | ||
| (op2->AsIntCon()->gtFieldSeq != nullptr) && | ||
| (op2->AsIntCon()->gtFieldSeq != FieldSeqStore::NotAField())) | ||
| { | ||
| fgAddFieldSeqForZeroOffset(op1, op2->AsIntCon()->gtFieldSeq); | ||
| } | ||
|
|
||
| DEBUG_DESTROY_NODE(op2); | ||
| DEBUG_DESTROY_NODE(tree); | ||
|
|
||
| return op1; | ||
| } | ||
| } | ||
| return tree; | ||
| } | ||
| op1 = tree->gtGetOp1(); | ||
| op2 = tree->AsOp()->gtGetOp2(); | ||
| oper = tree->OperGet(); | ||
| typ = tree->TypeGet(); | ||
| } | ||
|
|
||
| /* See if we can fold GT_MUL by const nodes */ | ||
| else if (oper == GT_MUL && op2->IsCnsIntOrI() && !optValnumCSE_phase) | ||
| { | ||
|
|
@@ -12761,7 +12828,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) | |
| goto DONE_MORPHING_CHILDREN; | ||
| } | ||
| } | ||
| else if (fgOperIsBitwiseRotationRoot(oper)) | ||
| if (fgOperIsBitwiseRotationRoot(oper)) | ||
| { | ||
| tree = fgRecognizeAndMorphBitwiseRotation(tree); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here you check
varTypeIsGC(x),varTypeIsGC(y),gtIsActiveCSE_Candidate(op2).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added also
gtIsActiveCSE_Candidate(op1)here