Skip to content

[wasm][R2R] Fix null ReturnTypeDesc dereference in SpillStructCallResult - #134981

Merged
lewing merged 2 commits into
mainfrom
lewing-fix-wasm-lower-storeloc-recursion
Oct 1, 2026
Merged

lewing merged 2 commits into
mainfrom
lewing-fix-wasm-lower-storeloc-recursion

Conversation

@lewing

@lewing lewing commented Sep 30, 2026

Copy link
Copy Markdown
Member

Wasm defines FEATURE_MULTIREG_RET as 0, so GenTreeCall::GetReturnTypeDesc() returns nullptr. Lowering::SpillStructCallResult dereferenced it unconditionally to get the single return field offset.

This path is reached on Wasm when a struct call result is returned in a single value but the destination local can't be retyped to a primitive. For example, TestStruct wraps a Vector128<byte>, is returned as v128/simd16, and is stored into a TYP_STRUCT local. crossgen2 then crashed: an abort (exit 134) on Linux Helix, and a spin in the PAL fault dispatch on macOS.

The fix uses offset 0 when multi-reg returns aren't supported. GetSingleReturnFieldOffset() returns the same value on every target except RISC-V and LoongArch.

Validation

  • Reproduced locally with the Helix job's inputs. Before the fix, crossgen2 crashed in VectorImmBinaryOpTest__op_LeftShiftByte1:RunStructLclFldScenario. After the fix, all assemblies in HardwareIntrinsics_General_r and _ro compile.
  • ./build.sh -s clr+libs -os browser -c checked -lc release, then src/tests/build.sh -browser checked priority1 -test:JIT/HardwareIntrinsics/HardwareIntrinsics_General_r.csproj -test:JIT/HardwareIntrinsics/HardwareIntrinsics_General_ro.csproj /p:LibrariesConfiguration=Release, then src/tests/run.sh wasm checked --runcrossgen2tests --node --runner-filter=HardwareIntrinsics_General:
    • HardwareIntrinsics_General_r: 2584/2584 passed
    • HardwareIntrinsics_General_ro: 2551/2551 passed
  • src/coreclr/scripts/jitformat.py: no changes.

Resolves #134976

Note

This PR was drafted with GitHub Copilot assistance.

Wasm has FEATURE_MULTIREG_RET == 0, so GenTreeCall::GetReturnTypeDesc()
returns nullptr. Spilling a struct call result that is returned in a single
register but whose destination local has no primitive register type (e.g. a
struct wrapping Vector128<T>, returned as v128) dereferenced it and crashed
crossgen2.

Fixes #134976

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@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 Sep 30, 2026
@lewing lewing added the arch-wasm WebAssembly architecture label Sep 30, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

@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.

The function is no longer only reached for 3/5/6/7-byte returns; on Wasm it
also handles single-register struct returns (e.g. v128) whose layout has no
primitive register type.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing
lewing requested a review from jkotas September 30, 2026 21:57
lewing added a commit that referenced this pull request Oct 1, 2026
…n Wasm (#134984)

## Motivation

a17debe (#129494) excluded Wasm from the 16-byte `TYP_SIMD16` case
in `ClassLayout::GetRegisterType()` while Wasm SIMD16 local loads were
still NYI. Those SIMD NYIs are no longer present in
`src/coreclr/jit/codegenwasm.cpp`.

Because of the exclusion, on Wasm a struct wrapping `Vector128<T>` that
is returned as `v128` (call typed `simd16`) and stored to a `TYP_STRUCT`
local has `lclRegType == TYP_UNDEF`, so `Lowering::LowerStoreLocCommon`
routes it through `SpillStructCallResult`: a new do-not-enregister temp,
a `STORE_LCL_FLD simd16` into it, then a struct copy into the
destination. That path also crashed crossgen2 with a null
`ReturnTypeDesc` dereference (#134976, fixed separately in #134981).
This PR removes the reason Wasm takes that path for v128 returns.

## Change

In `ClassLayout::GetRegisterType()` (`src/coreclr/jit/layout.h`), change
`#if defined(FEATURE_SIMD) && !defined(TARGET_WASM)` back to `#ifdef
FEATURE_SIMD`, so 16-byte struct layouts get `TYP_SIMD16` as their
register type on Wasm as on other SIMD targets.

## Validation

Performed in a sibling worktree: browser-wasm Checked, osx-arm64 host
crossgen2, with the #134981 fix also applied.

- `./build.sh -s clr+libs -os browser -c checked -lc release`: succeeded
- `src/tests/build.sh -browser checked priority1
-test:JIT/HardwareIntrinsics/HardwareIntrinsics_General_r.csproj
-test:JIT/HardwareIntrinsics/HardwareIntrinsics_General_ro.csproj
/p:LibrariesConfiguration=Release`: succeeded
- `src/tests/run.sh wasm checked --runcrossgen2tests --node
--runner-filter=HardwareIntrinsics_General` (IL-CG2 dirs and .wasm
images were deleted first to force recompilation):
  - `HardwareIntrinsics_General_r`: 2584/2584 passed
  - `HardwareIntrinsics_General_ro`: 2551/2551 passed
- JitDump of
`VectorImmBinaryOpTest__op_LeftShiftByte1:RunStructLclFldScenario`
(Vector128_1_r): the JIT no longer creates the `Return value temp` spill
and stores the `simd16` call result directly.
- R2R image sizes:
  - `Vector128_1_r.wasm`: 4,957,655 → 4,951,047 bytes (-6.6 KB)
  - `Vector128_1_ro.wasm`: 4,926,510 → 4,919,838 bytes (-6.7 KB)

On this branch, `./build.sh clr.wasmjit -c checked` succeeded.

## Caveat

Validation covered only the HardwareIntrinsics General runners. This
change affects every 16-byte struct local on Wasm, so it needs broader
Wasm R2R coverage (e.g. the outerloop R2R_CG2 browser-wasm lane) and
sign-off from the Wasm JIT owners.

Related to #134976 and #134981

> [!NOTE]
> This PR description was drafted with GitHub Copilot assistance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@jakobbotsch jakobbotsch left a comment

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.

Seems reasonable.
I would like to clean things up so that the return ABI info is always available in GenTreeCall, at which point wasm should also put something in there, but that is a separate task.

@lewing
lewing merged commit 219fe9d into main Oct 1, 2026
136 of 138 checks passed
@lewing
lewing deleted the lewing-fix-wasm-lower-storeloc-recursion branch October 1, 2026 18:30
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture 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.

[wasm][R2R] crossgen2 aborts compiling HardwareIntrinsics General Vector128_1 tests for browser-wasm

2 participants