From e1dcea75c620c937fca9315320013a25fe1da11c Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sun, 7 Jun 2026 17:05:37 -0500 Subject: [PATCH 1/4] [wasm] Retype integer constant struct returns to the native return type 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.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> --- src/coreclr/jit/lower.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index b122b8de775259..0549859b60e3f7 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -5859,6 +5859,16 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) else { assert(varTypeUsesIntReg(nativeReturnType)); +#ifdef TARGET_WASM + // Wasm has a typed value stack, so the constant's type must match the + // type the function returns. Retype an INT constant (e.g. a zero from a + // promoted/zero-initialized struct field) to the wider native return + // type so codegen emits an i64.const rather than an i32.const. + if (genActualType(retVal) != genActualType(nativeReturnType)) + { + retVal->ChangeType(genActualType(nativeReturnType)); + } +#endif // TARGET_WASM } break; } From bc85b6fbc07f4411f4c2cdd02a67f8539bcffe8f Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sun, 7 Jun 2026 18:53:24 -0500 Subject: [PATCH 2/4] Restrict struct-return constant retype to zero 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> --- src/coreclr/jit/lower.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 0549859b60e3f7..10e1808188a2ad 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -5861,10 +5861,11 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) assert(varTypeUsesIntReg(nativeReturnType)); #ifdef TARGET_WASM // Wasm has a typed value stack, so the constant's type must match the - // type the function returns. Retype an INT constant (e.g. a zero from a - // promoted/zero-initialized struct field) to the wider native return - // type so codegen emits an i64.const rather than an i32.const. - if (genActualType(retVal) != genActualType(nativeReturnType)) + // type the function returns. ZeroObj assertion propagation can create an + // INT zero for a wider (e.g. LONG) struct return; retype it so codegen + // emits an i64.const rather than an i32.const. Only the zero constant can + // be widened unambiguously here (matching the float/double case above). + if (retVal->IsIntegralConst(0) && (genActualType(retVal) != genActualType(nativeReturnType))) { retVal->ChangeType(genActualType(nativeReturnType)); } From 9f55470438647107ff460d7536ed3135b251b0fc Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Mon, 8 Jun 2026 09:44:43 -0500 Subject: [PATCH 3/4] Use BashToZeroConst and apply on all targets 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> --- src/coreclr/jit/lower.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 10e1808188a2ad..f32c24cb17e5db 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -5859,17 +5859,15 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) else { assert(varTypeUsesIntReg(nativeReturnType)); -#ifdef TARGET_WASM - // Wasm has a typed value stack, so the constant's type must match the - // type the function returns. ZeroObj assertion propagation can create an - // INT zero for a wider (e.g. LONG) struct return; retype it so codegen - // emits an i64.const rather than an i32.const. Only the zero constant can - // be widened unambiguously here (matching the float/double case above). - if (retVal->IsIntegralConst(0) && (genActualType(retVal) != genActualType(nativeReturnType))) + // ZeroObj assertion propagation can create an INT zero for a wider + // (e.g. LONG) integer struct return. Retype it to the native return type + // so that targets with a typed value stack (Wasm) emit e.g. an i64.const + // rather than an i32.const. Mirrors the float/double case above. + if (genActualType(retVal) != genActualType(nativeReturnType)) { - retVal->ChangeType(genActualType(nativeReturnType)); + assert(retVal->IsIntegralConst(0)); + retVal->BashToZeroConst(nativeReturnType); } -#endif // TARGET_WASM } break; } From c8065caa173c54b4704829f65fd9dd165fb7d19d Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Mon, 8 Jun 2026 12:26:16 -0500 Subject: [PATCH 4/4] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/coreclr/jit/lower.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index f32c24cb17e5db..2cd5507940790b 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -5863,11 +5863,13 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) // (e.g. LONG) integer struct return. Retype it to the native return type // so that targets with a typed value stack (Wasm) emit e.g. an i64.const // rather than an i32.const. Mirrors the float/double case above. +#if defined(TARGET_WASM) if (genActualType(retVal) != genActualType(nativeReturnType)) { assert(retVal->IsIntegralConst(0)); retVal->BashToZeroConst(nativeReturnType); } +#endif // defined(TARGET_WASM) } break; }