-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Fix a 32-bit specific bug in fgMorphCast
#52828
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
Fix a 32-bit specific bug in fgMorphCast
#52828
Conversation
f9e7b3e to
5a245e5
Compare
|
So, the runtime/src/coreclr/jit/morph.cpp Lines 12618 to 12662 in 5a0edac
It appears that the intention was to check whether the operands of Before:
[000028] ------------ +--* UMOD long
[000025] ---------U-- | +--* CAST long <- ulong <- double
[000024] ------------ | | \--* NEG double
[000023] ------------ | | \--* LCL_VAR double V11 loc7
[000027] ------------ | \--* CNS_LNG long 0x000000002487c9b5
After:
[000028] --CX-+------ +--* CALL help long HELPER.CORINFO_HELP_ULMOD
[000025] --C--+---U-- arg0 | +--* CALL help long HELPER.CORINFO_HELP_DBL2ULNG
[000024] -----+------ arg0 | | \--* NEG double
[000023] -----+------ | | \--* LCL_VAR double V11 loc7
[000027] -----+------ arg1 | \--* CNS_LNG long 0x000000002487c9b5Diff: Before:
[000028] ------------ +--* UMOD long
[000025] ------------ | +--* CAST long <- ulong <- double
[000024] ------------ | | \--* NEG double
[000023] ------------ | | \--* LCL_VAR double V11 loc7
[000027] ------------ | \--* CNS_LNG long 0x000000002487c9b5
After:
[000028] --C--+------ +--* UMOD long
[000025] --C--+------ | +--* CALL help long HELPER.CORINFO_HELP_DBL2ULNG
[000024] -----+------ arg0 | | \--* NEG double
[000023] -----+------ | | \--* LCL_VAR double V11 loc7
[000027] ------------ | \--* CNS_LNG long 0x000000002487c9b5While it is weird, it should be correct. Another note on the weird Edit: sure enough, commenting out the "is |
5a245e5 to
676fd81
Compare
|
Ready for review. cc @sandreenko for another morph change... |
|
Thanks, @SingleAccretion, how did you find this issue? It is great that you have added a new test for such cases. I will try to take a look this week, but such change would require a second approval, I think, PTAL @dotnet/jit-contrib |
From experimenting with |
AndyAyersMS
left a comment
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.
LGTM
src/coreclr/jit/compiler.hpp
Outdated
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.
we are calling GenTreeCast constructor:
runtime/src/coreclr/jit/gentree.h
Lines 3531 to 3535 in 4197653
| GenTreeCast(var_types type, GenTree* op, bool fromUnsigned, var_types castType DEBUGARG(bool largeNode = false)) | |
| : GenTreeOp(GT_CAST, type, op, nullptr DEBUGARG(largeNode)), gtCastType(castType) | |
| { | |
| gtFlags |= fromUnsigned ? GTF_UNSIGNED : GTF_EMPTY; | |
| } |
and after that we call
CanonicalizeCastNode to clear the flag in some cases, is it worth it?One option is to do
inline GenTreeCast* Compiler::gtNewCastNode(var_types typ, GenTree* op1, bool fromUnsigned, var_types castType)
{
if (varTypeIsFloating(cast->CastOp())) { fromUnsigned = false; }
GenTreeCast* cast = new (this, GT_CAST) GenTreeCast(typ, op1, fromUnsigned, castType);
or add this logic to GenTreeCast constructor (note it will affect more cases then).
Often I set conditional breakpoints on flag values so I would prefer not to change it back and forth without a reason.
Have you tried to add assert(!varTypeIsFloating(cast->CastOp() || !fromUnsigned) and fix the callers who call it with wrong value of bool fromUnsigned?
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.
Have you tried to add
assert(!varTypeIsFloating(cast->CastOp() || !fromUnsigned)and fix the callers who call it with wrong value of boolfromUnsigned?
Yes, in fact that was the original version of the fix, and the only place I had to fix was the importer. I went back and forth on this a few times, and ultimately decided to have the change initially be with this more conservative implementation, because it is "guaranteed" (not really because I did not add it to the constructor, but nothing calls the constructor directly) to always return "correct" result to the caller. I suppose I should revisit it and run the assertty version through the CI. Maybe it turns out more callers are getting it wrong and it would be better to normalize centrally (regardless of that, the flag flipping will be fixed).
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 suppose I should revisit it and run the assertty version through the CI. Maybe it turns out more callers are getting it wrong and it would be better to normalize centrally (regardless of that, the flag flipping will be fixed).
I would prefer this if you have time, if not please open a follow-up issue to investigate/clean it and we will merge this PR as is.
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 would prefer this
I would as well, so we'll investigate it as part of this PR.
Verifying that checked '.un' casts from floats to small types are not treated as casts from unsigned types.
It used to be that in the importer, whether the cast was to be marked as GTF_UNSIGNED was decided exclusively based on the incoming opcode. However, the flag only makes sense for casts from integral sources, and it turns out morph had a bug where it failed to clear this flag which resulted in bad codegen. The bug went as follows: "gtMorphCast" turns casts from an FP type to a small integer into a chain of casts: CAST(small integer <- FP) => CAST(small integer <- CAST(TYP_INT <- FP)). On 32 bit platforms, the code failed to clear the GTF_UNSIGNED flag from the original tree, which meant that the outer cast thought it had TYP_UINT as the source. This matters for checked casts: conv.ovf.i2.un(-2.0d), which is a legitimate conversion, was interpreted wrongly as an overflowing one as the resulting codegen only checked the upper bound via an unsigned compare. The fix is two-fold: clear GTF_UNSIGNED for GT_CAST nodes with FP sources on creation and unify the 64 bit and 32 bit paths in "gtMorphCast", which, after the removal of GTF_UNSIGNED handling, are identical. This is a zero-diff change across all SPMI collections for Windows x64, Linux x64, Linux ARM64. This **is not** a zero-diff change for Windows x86. Instances of bad codegen have been corrected in some tests.
a0ff6e3 to
395d829
Compare
Instead of normalizing GTF_UNSIGNED for FP sources in "gtNewCastNode", assert that it is not set in GenTreeCast's constructor and fix the importer to respect that constraint.
395d829 to
a1a0850
Compare
sandreenko
left a comment
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.
LGTM, thank you!
It used to be that in the importer, whether the cast was to be marked as GTF_UNSIGNED was decided exclusively based on the incoming opcode. However, the flag only makes sense for casts from integral sources, and it turns out morph had a bug where it failed to clear this flag which resulted in
bad codegen.
The bug went as follows:
gtMorphCastturns casts from an FP type into a small integer into a chain of casts:CAST(small integer <- FP)=>CAST(small integer <- CAST(TYP_INT <- FP)). On 32 bit platforms, the code failed to clear theGTF_UNSIGNEDflag from the original tree, which meant that the outer cast thought it hadTYP_UINTas the source. This matters for checked casts:conv.ovf.i2.un(-2.0d), which is a legitimate conversion, was interpretedwrongly as an overflowing one as the resulting codegen only checked the upper bound via an unsigned compare.
The fix is two-fold: clear
GTF_UNSIGNEDforGT_CASTnodes with FP sources on creation and unify the 64 bit and 32 bit paths ingtMorphCast, which, after the removal ofGTF_UNSIGNED, are identical.This is a zero-diff change across all SPMI collections for Windows x64, Linux x64, Linux ARM64.
This is not a zero-diff change for Windows x86. Instances of bad codegen have been corrected in some tests.
Diffs for the aforementioned tests:
ConvTest(JIT\Methodical\NaN\r8nanconv_il_r\r8nanconv),FullProof(JIT\Regression\VS-ia64-JIT\V2.0-Beta2\b309576\bug2) andDevDiv_578217diffs are as expected,still investigating what happened in the- weird code, see below.ILGENmethod - it looks likeUMODwas not transfomed intoCORINFO_HELP_ULMOD