-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Fix assertions related to combining cmn (extended-register)
(#113337)
#113376
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
Conversation
…#113337) Fixes issues highlighted by Fuzzlyn, related to erroneously combining cast=>negate=>compare into `cmn (extended-register)` when the second operand is an integral constant.
using System.Runtime.CompilerServices; | ||
using Xunit; | ||
|
||
public class Program |
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.
public class Program | |
public class Runtime_113337 |
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.
Also, add the standard header (.NET Foundation)
public static int TestEntryPoint() | ||
{ | ||
// Checking for successful compilation | ||
issue1(); | ||
issue2(); | ||
return 100; | ||
} |
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.
public static int TestEntryPoint() | |
{ | |
// Checking for successful compilation | |
issue1(); | |
issue2(); | |
return 100; | |
} | |
public static void TestEntryPoint() | |
{ | |
// Checking for successful compilation | |
issue1(); | |
issue2(); | |
} |
src/coreclr/jit/lowerarmarch.cpp
Outdated
@@ -404,9 +404,9 @@ bool Lowering::IsContainableUnaryOrBinaryOp(GenTree* parentNode, GenTree* childN | |||
return false; | |||
} | |||
|
|||
if (childNode->gtGetOp1()->OperIs(GT_CAST)) | |||
if (childNode->gtGetOp1()->OperIs(GT_CAST) && !parentNode->gtGetOp2()->IsIntegralConst()) |
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.
if (childNode->gtGetOp1()->OperIs(GT_CAST) && !parentNode->gtGetOp2()->IsIntegralConst()) | |
if (childNode->gtGetOp1()->OperIs(GT_CAST) && !parentNode->gtGetOp2()->isContained()) |
Should this check rather be like this? Uncontained constant will still be in a register, and that should be fine.
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.
This line change doesn't seem to work but I'm not sure why just yet, I'll follow up shortly. It's already guaranteed that the constant is not contained here, because there is a check at the beginning of the function that will return if one of the nodes is contained already.
I've also noticed that gtGetOp2
isn't a reliable way of getting the other operand, as childNode
could be op1
or op2
, so I've fixed this.
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.
Indeed seems like something else must be wrong. An uncontained constant is, like any other node, going to be in a register, so this check should not make a difference if we know it is not contained.
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.
Looking again, I think it was down to the cast node needing more restrictions. I thought the assertion isGeneralRegister(reg2)
was firing on REG_NA
, but it was actually firing on a floating point register. The other assertion is down to containing a cast node that already contains a LCL_VAR
, this shouldn't be allowed.
…g casts that already contain some memory operation
src/coreclr/jit/lowerarmarch.cpp
Outdated
} | ||
|
||
// Cannot contain the cast if it may contain or is already containing a memory operation. | ||
if (IsContainableMemoryOp(castOp) || castOp->isContained()) |
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 the IsContainableMemoryOp
check necessary?
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.
Assuming castOp and cast have already been analyzed for containment before this, then it's not necessary. Is that always true?
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.
Yes, that is always true. castOp
is an operand, and operands always have to appear earlier in linear order.
/azp run Fuzzlyn |
Azure Pipelines successfully started running 1 pipeline(s). |
Are the Fuzzlyn found "unreached" assertion failures a different problem? E.g. // Generated by Fuzzlyn v2.5 on 2025-03-24 18:20:14
// Run on Arm64 Linux
// Seed: 8261718896722533474-vectort,vector64,vector128,armadvsimd,armadvsimdarm64,armaes,armarmbase,armarmbasearm64,armcrc32,armcrc32arm64,armdp,armrdm,armrdmarm64,armsha1,armsha256,armsve
// Reduced from 58.9 KiB to 0.5 KiB in 00:00:22
// Hits JIT assert in Release:
// Assertion failed 'unreached' in 'Program:Main(Fuzzlyn.ExecutionServer.IRuntime)' during 'Generate code' (IL size 72; hash 0xade6b36b; FullOpts)
//
// File: /__w/1/s/src/coreclr/jit/codegenarm64.cpp Line: 3530
//
using System;
using System.Numerics;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
public class Program
{
public static Vector<ulong>[] s_3;
public static void Main()
{
var vr3 = Vector.Create<short>(1);
var vr4 = (short)0;
var vr5 = Vector128.CreateScalar(vr4).AsVector();
if ((Sve.TestFirstTrue(vr3, vr5) | (3268100580U != (-(uint)Sve.SaturatingDecrementBy16BitElementCount(0, 1)))))
{
s_3[0] = s_3[0];
}
}
} |
I haven't seen this assertion fire before, but it looks like it's related to this change in containment patterns. I'll take a look today. |
This new assertion is fired when a It's a slightly different problem but still introduced by the same change as before. Would you prefer this fix as a separate pull request, or a continuation here? |
I wonder if we should rethink this transformation, to make it more robust. Would it make more sense to introduce a new |
Alternatively I would be ok with skipping the transformation in |
This could work. There's potentially some complications around the precedence of this transform over the
OK, I'll try this next. |
Yeah, but very possibly we would just skip the conversion into |
These latest build failures seem to be network related as far as I understand. I've added this case to the test class and it seems to be compiling now. |
/azp run Fuzzlyn |
Azure Pipelines successfully started running 1 pipeline(s). |
/ba-g Azurelinux timeouts |
@@ -3187,11 +3200,15 @@ bool Lowering::TryLowerAndOrToCCMP(GenTreeOp* tree, GenTree** next) | |||
// | |||
GenCondition cond1; | |||
if (op2->OperIsCmpCompare() && varTypeIsIntegralOrI(op2->gtGetOp1()) && IsInvariantInRange(op2, tree) && | |||
(op2->gtGetOp1()->IsIntegralConst() || !op2->gtGetOp1()->isContained()) && | |||
(op2->gtGetOp2() == nullptr || op2->gtGetOp2()->IsIntegralConst() || !op2->gtGetOp2()->isContained()) && |
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.
This nullptr check is unnecessary (we already checked for CmpCompare
above, all of those are binops). But let's not wait for another round of CI here.
Fixes issues highlighted by Fuzzlyn, related to erroneously combining cast=>negate=>compare into
cmn (extended-register)
when the second operand is an integral constant.Fixes: #113337