[RyuJIT] Make the mis-sized struct return logic only apply to return values that are structs#126772
[RyuJIT] Make the mis-sized struct return logic only apply to return values that are structs#126772kg wants to merge 3 commits intodotnet:mainfrom
Conversation
…at are actually structs
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull request overview
Fixes a RyuJIT lowering assertion encountered in WASM codegen when a struct return’s value node has an erased (non-struct) type, which caused an invalid spill-to-struct-temp transformation.
Changes:
- Guard the mis-sized struct return “spill to temp local” workaround so it only runs when the return value node is actually
TYP_STRUCT. - Avoid invoking
ReplaceWithLclVarto store a non-struct-typedGT_INDinto a struct-typed temp local.
src/coreclr/jit/lower.cpp
Outdated
| // Ensure that the retval is actually a struct - otherwise the ReplaceWithLclVar below | ||
| // will fail due to assigning a non-struct to a struct local |
There was a problem hiding this comment.
The added comment says to ensure the retval is actually a struct, but in this code path the method return can be a struct even when retVal has an erased primitive type (which is the scenario being fixed). Consider rewording to clarify you mean the tree is TYP_STRUCT (i.e., struct-typed indir) to avoid confusion for future readers.
| // Ensure that the retval is actually a struct - otherwise the ReplaceWithLclVar below | |
| // will fail due to assigning a non-struct to a struct local | |
| // Ensure that the return-value tree is TYP_STRUCT; otherwise the ReplaceWithLclVar | |
| // below will fail due to assigning a non-struct tree to a struct local. |
|
I misread something and this is incorrect. Will keep working on it. |
| // Instead, wrap the indirection in a cast to zero extend it. | ||
| GenTreeCast* cast = | ||
| m_compiler->gtNewCastNode(nativeReturnType, retVal, true, varTypeToUnsigned(nativeReturnType)); |
There was a problem hiding this comment.
The new widening path builds a GT_CAST with castType = varTypeToUnsigned(nativeReturnType). That makes the cast’s semantic target type UINT/U_IMPL (same width as nativeReturnType), so it no longer encodes the required zero-extension of the mismatched load size (e.g., UBYTE/USHORT) and can become a no-op cast on some targets. For correct/portable zero-extension without widening the memory read, the cast should keep the destination node type as the actual native return type (e.g., genActualType(nativeReturnType) / ret->TypeGet()), but set CastToType to an unsigned small type based on the indirection’s type (e.g., varTypeToUnsigned(retVal->TypeGet())), so the cast represents a zero-extending small→int/native-int conversion.
| // Instead, wrap the indirection in a cast to zero extend it. | |
| GenTreeCast* cast = | |
| m_compiler->gtNewCastNode(nativeReturnType, retVal, true, varTypeToUnsigned(nativeReturnType)); | |
| // Instead, wrap the indirection in a cast to zero extend it from the actual load size. | |
| GenTreeCast* cast = | |
| m_compiler->gtNewCastNode(nativeReturnType, retVal, true, varTypeToUnsigned(retVal->TypeGet())); |
There was a problem hiding this comment.
Did I misunderstand the comments around cast nodes @AndyAyersMS ? What I'm trying to do here is 'cast to the unsigned version of the return type' and then have the cast result masquerade as the actual return type, in order to get a zero-extend.
There was a problem hiding this comment.
Cast semantics are a frequent source of confusion, because logically they accept and return stack types but they can operate on non-stack types.
The last type arg here should be a small type, the type of the indir.
You can assert cast->IsZeroExtending() after you create it to make sure it agrees with what you expect.
This addresses an assert in wasm codegen with a release SPC, i.e.:
Would produce:
The problem is that we are creating a temporary local of struct type using the
retTypeClass, but the type of the return value was erased, so we try to turn aGT_INDof typeTYP_UBYTEinto a temp ofTYP_STRUCTwhich can't possibly work the wayReplaceWithLclVaris constructed.EDIT:
In this scenario what we need to do is zero-extend the mis-sized return value (a byte or short, most likely) to match the native return type's size. So I wrap the return value in a CAST.