-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Gtneg mul divoptimizations #45604
Gtneg mul divoptimizations #45604
Conversation
I think you need to perform this optimization only if checked
{
int x = -y * int.MinValue;
} Also, I mentioned #44266 in the issue - you need to make sure the constant on the right is not |
However, even class Program
{
static async Task Main()
{
int x1 = -GetValue() / 1; // OK
int x2 = GetValue() / -1; // throws OverflowException even in the unchecked context
}
[MethodImpl(MethodImplOptions.NoInlining)]
static int GetValue() => int.MinValue;
} So I'd give up if |
src/coreclr/src/jit/morph.cpp
Outdated
case GT_MUL: | ||
// -a * C => a * -C, where C is constant | ||
// MUL(NEG(a), C) => MUL(a, NEG(C)) | ||
if (op1->OperIs(GT_NEG) && !op1->IsCnsIntOrI() && op2->IsCnsIntOrI()) |
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.
!op1->IsCnsIntOrI()
- I think you meant op1->gtOp1()->IsCnsIntOrI()
here.
src/coreclr/src/jit/morph.cpp
Outdated
// op1: a | ||
// op2: NEG | ||
// op2Child: C | ||
op1 = op1->AsOp()->gtOp1; // a |
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.
I'd rewrite to be:
tree->AsOp()->gtOp1 = op1->gtGetOp1();
DEBUG_DESTROY_NODE(op1);
op2->AsIntCon()->SetIconValue(-op2->AsIntCon()->IconValue());
return tree;
// Auto-generated message 69e114c which was merged 12/7 removed the intermediate src/coreclr/src/ folder. This PR needs to be updated as it touches files in that directory which causes conflicts. To update your commits you can use this bash script: https://gist.github.com/ViktorHofer/6d24f62abdcddb518b4966ead5ef3783. Feel free to use the comment section of the gist to improve the script for others. |
@EgorBo Thanks for the feedback. I have these changes made on my dev machine, but the machine is having some issues at the moment. I expect to have these updated by tomorrow. Sorry for the delay! |
Hi @alexcovington, thanks for your PR and sorry for the long review. |
Pinging @dotnet/jit-contrib |
@alexcovington - Looks like there are multiple failures when crossgening
|
@kunalspathak Sorry, I started looking at this, but it fell of my radar for a little bit. I'm a little stuck on the |
src/coreclr/jit/morph.cpp
Outdated
tree->AsOp()->gtOp1 = op1->gtGetOp1(); | ||
DEBUG_DESTROY_NODE(op1); | ||
op2->AsIntCon()->SetIconValue(-op2->AsIntCon()->IconValue()); // -C | ||
return 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.
I think this is the cause of the problem. You return the tree immediately but if you see after the switch-case, we update the tree flags. In your case, when we hit the assert, because GTF_CALL
is not yet removed from the node because here, we return immediately. I believe same applies to the GT_MUL
case.
I am not too familiar with the morph and probably @sandreenko should confirm.
Changes LGTM. Is it possible to get the libraries pmi code size diff? |
src/coreclr/jit/morph.cpp
Outdated
* A GenTree* which points to the new tree. If the tree is not for simd intrinsic, | ||
* return nullptr. | ||
*/ | ||
* If a read operation tries to access simd struct field, then transform the |
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.
did jit-format cause these changes or were these unintentional edits? Could you please try to delete them and run jit-format again?
src/coreclr/jit/morph.cpp
Outdated
@@ -11909,6 +11909,22 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) | |||
return fgMorphCast(tree); | |||
|
|||
case GT_MUL: | |||
// -a * C => a * -C, where C is constant | |||
// MUL(NEG(a), C) => MUL(a, NEG(C)) | |||
if (fgGlobalMorph && op1->OperIs(GT_NEG) && !op1->gtGetOp1()->IsCnsIntOrI() && op2->IsCnsIntOrI() && |
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.
opts.OptimizationEnabled()
check is missed here and in the other cases, when this flag is set to false we don't want to do any CQ transformations, only what is needed for correctness, it is used, for example, in minopts.
src/coreclr/jit/morph.cpp
Outdated
// -a * C => a * -C, where C is constant | ||
// MUL(NEG(a), C) => MUL(a, NEG(C)) | ||
if (fgGlobalMorph && op1->OperIs(GT_NEG) && !op1->gtGetOp1()->IsCnsIntOrI() && op2->IsCnsIntOrI() && | ||
!op2->IsIconHandle() && op2->AsIntCon()->IconValue() != 1 && op2->AsIntCon()->IconValue() != 0 && |
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.
Why do you check fgGlobalMorph
here? Did you want to use !gtIsActiveCSE_Candidate
? gtIsActiveCSE_Candidate
means it is not CSE phase (it could be a global morph phase instead) or this tree is not the current candidate that could be dangerous to remove.
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.
I checked fgGlobalMorph
because it was checked in #43921, which is what I based most of this work on. I can use !gtIsActiveCSE_Candidate
if that would be better.
Still learning about the CoreCLR JIT, I greatly appreciate the advice :).
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.
On second thought I think fgGlobalMorph
check is fine here because you are changing node value without updating its VN.
An alternative could be to do the optimization with !gtIsActiveCSE_Candidate
but create a new tree for the constant:
tree->AsOp()->gtOp2 = gtNewIconNode(-op2->AsIntCon()->IconValue(), op2->TypeGet());
DEBUG_DESTROY_NODE(op2);
it would allow the optimization to happen in other phases, so I think it would be better.
src/coreclr/jit/morph.cpp
Outdated
// -a * C => a * -C, where C is constant | ||
// MUL(NEG(a), C) => MUL(a, NEG(C)) | ||
if (fgGlobalMorph && op1->OperIs(GT_NEG) && !op1->gtGetOp1()->IsCnsIntOrI() && op2->IsCnsIntOrI() && | ||
!op2->IsIconHandle() && op2->AsIntCon()->IconValue() != 1 && op2->AsIntCon()->IconValue() != 0 && |
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.
On second thought I think fgGlobalMorph
check is fine here because you are changing node value without updating its VN.
An alternative could be to do the optimization with !gtIsActiveCSE_Candidate
but create a new tree for the constant:
tree->AsOp()->gtOp2 = gtNewIconNode(-op2->AsIntCon()->IconValue(), op2->TypeGet());
DEBUG_DESTROY_NODE(op2);
it would allow the optimization to happen in other phases, so I think it would be better.
src/coreclr/jit/morph.cpp
Outdated
!op2->IsIconHandle() && op2->AsIntCon()->IconValue() != 1 && op2->AsIntCon()->IconValue() != 0 && | ||
op2->AsIntCon()->IconValue() != -1 && !tree->gtOverflow()) | ||
{ | ||
// tree: MUL |
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.
is this the comment for the tree before the transformation?
then it should be
// tree: MUL
// op1: Neg
// op1Child: a
// op2: Const
src/coreclr/jit/morph.cpp
Outdated
{ | ||
// tree: DIV | ||
// op1: a | ||
// op2: NEG |
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.
the same here.
Co-authored-by: Sergey Andreenko <seandree@microsoft.com>
Looks good, @alexcovington, do you mind if I push a small change to your branch for this PR? The checks for bad cases that @EgorBo pointed out should be a little bit different. Also, I think it is worth a new test (probably we should place it to https://github.com/dotnet/runtime/tree/master/src/tests/JIT/opt/InstructionCombining) that I expect will behave differently on arm/arm64 so I don't want to bother you with it (for your first PR 😄). |
@sandreenko Absolutely! Just let me know if you need anything else from me 😄. |
1 improvement in SPC:
expected improvements on the repro test:
the regression is from runtime/src/coreclr/jit/lower.cpp Lines 5323 to 5325 in 323e7a3
but it is not worth checking for it in my opinion. Also, I decided to use |
@sandreenko Thanks for the help, this is very informative as well! I'll keep it in mind for future PRs. |
static int CheckMulNeg() | ||
{ | ||
bool fail = false; | ||
if (Mul1_1(3) != -7 * 3) |
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.
minor nit: can we make the method names Mul_1
and other more meaningful like Mul_Minus7
, etc.?
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.
Thanks @sandreenko ! Should be good to go.
Thanks @alexcovington for the PR! |
Extends the JIT to optimize the cases outlined in #44375.
I used the following test case:
jit-diff
output: