Skip to content

[Wasm R2R] Retype integer constant struct returns to the native return type#129102

Open
lewing wants to merge 4 commits into
dotnet:mainfrom
lewing:lewing/wasm-r2r-retstruct-const-i64
Open

[Wasm R2R] Retype integer constant struct returns to the native return type#129102
lewing wants to merge 4 commits into
dotnet:mainfrom
lewing:lewing/wasm-r2r-retstruct-const-i64

Conversation

@lewing
Copy link
Copy Markdown
Member

@lewing lewing commented Jun 7, 2026

Note

This PR was generated with the assistance of GitHub Copilot.

Problem

When Lowering::LowerRetStruct retypes a struct return to a primitive nativeReturnType and the return value is a GT_CNS_INT, the integer branch leaves the constant as TYP_INT even when the native return type is wider (e.g. TYP_LONG). On register targets this is harmless because integer registers are untyped, but wasm has a typed value stack, so codegen emits i32.const 0 for an i64-typed return.

Where it surfaced

Validating the R2R-compiled System.Private.CoreLib for wasm. System.Runtime.Intrinsics.Vector64<byte>.get_Zero reduces (after the unsupported-base-type check inlines away for byte) to return default; — a zero-initialized 8-byte struct, which the wasm ABI lowers to an i64 return. The emitted body ended with i32.const 0, and wasmtime rejected the module:

Invalid input WebAssembly code: type mismatch: expected i64, found i32

This is the first small-struct-constant return in corelib's code section; with it fixed the entire corelib validates.

Fix

Retype the integer constant to the native return type under TARGET_WASM so codegen emits an i64.const. The change is TARGET_WASM-guarded and does not affect other targets. It applies to both the browser and wasi wasm targets, which share the same JIT.

Validation

  • Regenerated the wasm R2R System.Private.CoreLib with the fixed JIT; the full module now passes wasmtime validation (previously failed at Vector64<byte>.get_Zero).
  • Confirmed the emitted body now ends with i64.const 0 instead of i32.const 0.

I held off on adding a dedicated regression test since this class of bug is caught implicitly once the wasm R2R corelib is built and validated/loaded (corelib itself contains the triggering method). Happy to add a targeted test if reviewers prefer an earlier, JIT-level signal.

When `LowerRetStruct` retypes a struct return to a primitive `nativeReturnType`
and the return value is a `GT_CNS_INT`, the integer branch left the constant as
`TYP_INT` even when the native return type is wider (e.g. `TYP_LONG`). On register
targets this is harmless because integer registers are untyped, but wasm has a
typed value stack, so codegen emitted `i32.const 0` for an `i64`-typed return.

This surfaced when validating the R2R-compiled `System.Private.CoreLib` for wasm:
`System.Runtime.Intrinsics.Vector64<byte>.get_Zero` returns a zero-initialized
8-byte struct, which the wasm ABI lowers to an `i64` return. The emitted body
ended with `i32.const 0`, and wasmtime rejected the module with
"type mismatch: expected i64, found i32".

Retype the integer constant to the native return type under `TARGET_WASM` so
codegen emits an `i64.const`. The fix applies to both the browser and wasi wasm
targets, which share the same JIT.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 7, 2026 22:05
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jun 7, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@lewing lewing changed the title [wasm] Retype integer constant struct returns to the native return type [Wasm R2R] Retype integer constant struct returns to the native return type Jun 7, 2026
@lewing lewing requested a review from kg June 7, 2026 22:09
@lewing
Copy link
Copy Markdown
Member Author

lewing commented Jun 7, 2026

cc @dotnet/wasm-contrib @dotnet/jit-contrib @AndyAyersMS

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts CoreCLR JIT lowering for Wasm so that when a struct return is lowered to a primitive integer return type, an integer constant return value is retyped to match the lowered native return type. This avoids generating Wasm code with a stack type mismatch (e.g., emitting an i32.const where an i64 return is required).

Changes:

  • In Lowering::LowerRetStruct, under TARGET_WASM, retype GT_CNS_INT return constants to genActualType(nativeReturnType) when the actual types differ.
  • Add a Wasm-specific comment explaining the typed value stack requirement and the intended effect on emitted constants.

lewing added a commit to lewing/runtime that referenced this pull request Jun 7, 2026
When LowerRetStruct retypes a struct return to a primitive nativeReturnType and
the return value is a GT_CNS_INT, the integer branch left the constant as TYP_INT
even when the native return type is wider (e.g. TYP_LONG). On register targets
this is harmless because integer registers are untyped, but wasm has a typed
value stack, so codegen emitted i32.const 0 for an i64-typed return.

This surfaced validating the R2R-compiled CoreLib for wasm:
Vector64<byte>.get_Zero returns a zero-initialized 8-byte struct, which the wasm
ABI lowers to an i64 return. The emitted body ended with i32.const 0 and wasmtime
rejected the module with "type mismatch: expected i64, found i32".

Retype the integer constant to the native return type under TARGET_WASM so
codegen emits an i64.const. Applies to both browser and wasi, which share the JIT.

Also submitted standalone as dotnet#129102; will reconcile if that lands
first.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@AndyAyersMS
Copy link
Copy Markdown
Member

I would have sworn we fixed this already. Let me see... we found it but I can't tell without a bit more work if we ever fixed it.

#125756 (comment)

@lewing
Copy link
Copy Markdown
Member Author

lewing commented Jun 7, 2026

I would have sworn we fixed this already. Let me see... we found it but I can't tell without a bit more work if we ever fixed it.

#125756 (comment)

Definitely looks like the same issue. It doesn't look like a real fix ever landed, only aworkaround. As you noted, the bad int constant originates in assertion prop (ZeroObj → CNS_INT int 0); I fixed it in LowerRetStruct — your "GT_RETURN inlowering" option — kept minimal and TARGET_WASM-scoped so it covers browser too. Happy to move it to the assertionprop source instead if you'd prefer atarget-independent fix.

@lewing
Copy link
Copy Markdown
Member Author

lewing commented Jun 7, 2026

It looks intended there at the time?

// Only propagate zeroes that lowering can deal with.
if (!ret->TypeIs(TYP_VOID) && varTypeIsStruct(retValue) && !varTypeIsStruct(info.compRetNativeType))
{
if (optZeroObjAssertionProp(retValue, assertions))
{
return optAssertionProp_Update(ret, ret, stmt);
}
}

Comment thread src/coreclr/jit/lower.cpp Outdated
{
assert(varTypeUsesIntReg(nativeReturnType));
#ifdef TARGET_WASM
// Wasm has a typed value stack, so the constant's type must match the
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the sizes don't match, should we do this only for zero (like float cases just above)?

Per review feedback, scope the integer-constant retype to the zero
constant, mirroring the float/double case just above. The INT-under-wider
struct return only arises from ZeroObj assertion propagation (always
zero); a nonzero size-mismatched constant could be widened incorrectly,
so gate on IsIntegralConst(0).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the manual ChangeType with BashToZeroConst(nativeReturnType), the
purpose-built helper used to create the zero in the first place
(assertionprop), and drop the TARGET_WASM guard so the integer-constant
case mirrors the unconditional float/double coercion just above. The
retype only triggers for the ZeroObj-produced zero (asserted) and is a
no-op on register targets, but produces correctly typed IR everywhere.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 8, 2026 14:44
@lewing
Copy link
Copy Markdown
Member Author

lewing commented Jun 8, 2026

I removed the WASM guard and used BashToZeroConst(nativeReturnType); which appears purpose made for this. Happy to add the ifdef back if needed.

@lewing lewing requested a review from AndyAyersMS June 8, 2026 14:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/jit/lower.cpp
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 8, 2026 17:26
@lewing lewing enabled auto-merge (squash) June 8, 2026 17:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/jit/lower.cpp
Comment on lines +5867 to +5871
if (genActualType(retVal) != genActualType(nativeReturnType))
{
assert(retVal->IsIntegralConst(0));
retVal->BashToZeroConst(nativeReturnType);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of an assertion this should just be part of the if condition

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants