Materialize Vector128<T> as a wasm v128 in the codegen ABI#130866
Materialize Vector128<T> as a wasm v128 in the codegen ABI#130866tannergooding merged 15 commits into
Conversation
Vector128<T> is the one SIMD type the JIT recognizes as TYP_SIMD16 on wasm (the others are TARGET_ARM64/TARGET_XARCH-only or multi-field structs passed by reference), so treat it as the wasm v128 ABI primitive. The lowering now maps Vector128<T> to CORINFO_WASM_TYPE_V128 -> TYP_SIMD16, and the codegen paths that previously bailed to the interpreter (SIMD parameter, SIMD16 local field load, store-indirect, call argument, and local load) emit real v128 values instead of producing an invalid module. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 5 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Updates the wasm ABI lowering and wasm JIT codegen so Vector128<T> is treated as a real wasm v128 value (instead of arriving as an i32 byref), enabling SIMD values to flow through key codegen paths without bailing to interpreter.
Changes:
- Teach wasm lowering/signature logic to classify
Vector128<T>asv128and round-trip'V'signature encodings. - Update wasm JIT ABI/type mapping and remove several
NYI_WASM_SIMDbailouts now thatv128is materialized by-value. - Add an R2R wasm test module and a new xUnit test suite entry to exercise the v128 calling convention plumbing.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/tools/Common/JitInterface/WasmLowering.cs | Recognize Vector128<T> as v128, emit 'V' in signature strings, and enable signature round-tripping. |
| src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | Plumbs WasmValueType.V128 into the JIT-facing CorInfoWasmType lowering. |
| src/coreclr/tools/Common/Compiler/CompilerTypeSystemContext.Wasm.cs | Adds caching for a representative v128 type to support 'V' signature raising. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs | Adds argument materialization support for v128 in interpreter-to-R2R thunks. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs | New test-case source to exercise v128 param/return/local/store/call-arg flows. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs | Adds a new test that compiles the wasm SIMD test case into a .wasm R2R artifact. |
| src/coreclr/jit/targetwasm.cpp | Maps CORINFO_WASM_TYPE_V128 to TYP_SIMD16 for wasm ABI classification. |
| src/coreclr/jit/codegenwasm.cpp | Removes several SIMD-related NYI bailouts in prolog/loads/stores/calls now that v128 is supported. |
Copilot's findings
- Files reviewed: 8/8 changed files
- Comments generated: 2
Strengthen the WasmSimdModule test to assert that all four methods (Echo, ThroughLocal, Store, CallEcho) are present in the compiled output rather than just one, so a regression in any single ABI/materialization path is caught. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
GetSignature already computes loweredParamType via LowerToAbiType, so pass it to LowerType directly instead of re-lowering paramType, avoiding a redundant field walk for single-field structs and matching the return-type lowering pattern. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The V128 branch already has loweredParamType (guaranteed to be the Vector128<T> that lowered to 'V'), so cache it directly instead of the outer paramType. This keeps the cached representative consistent with the type that produced the 'V' encoding for single-field struct wrappers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Decode each method's wasm function signature and verify the Vector128<int> parameter (and return, where applicable) materializes as v128 (0x7B) rather than a by-ref i32, so a regression to the generic struct ABI is caught. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
`Vector2` (`TYP_SIMD8`) and `Vector3` (`TYP_SIMD12`) have no native wasm valtype, so they live as a `v128` with the low 8/12 bytes populated. Previously the wasm JIT bailed out via the `ins_Load`/`ins_Store` NYIs for these types; this implements the split lane load/store sequences instead. The emitted sequences are: - **simd8 load**: `v128.load64_zero 0` - **simd8 store**: `v128.store64_lane 0, lane 0` - **simd12 load**: `local.get addr; v128.load64_zero 0; v128.load32_lane 8, lane 2` - **simd12 store**: tee the value into a `v128` temporary, `v128.store64_lane 0, lane 0` for the low 8 bytes, then re-materialize the address and `v128.store32_lane 8, lane 2` for the upper 4 bytes `v128.load64_zero` fills lanes 0-1 (zeroing the rest); the trailing lane store/load handles bytes 8-11 for the `Vector3` case. ---------- The `TYP_SIMD12` address is forced multiply-used (loads and heap stores re-materialize it for the trailing lane op). The local-to-stack store rewrite (`RewriteLocalStackStore`) produces a `STOREIND(LCL_ADDR, value)` whose address is a re-materializable `GT_LCL_ADDR`, so that case is excluded from multiply-use and codegen re-emits the frame pointer directly. Because that synthesized `STOREIND` is not revisited by the main collection walk, its internal `v128` tee register is requested in `RewriteLocalStackStore`. ---------- Measured on the corelib crossgen2 browser SuperPMI collection (27,540 contexts): hard asserts drop from **403 to 30**, eliminating **373** `SIMD8`/`SIMD12` load/store asserts with no regressions. The residual 30 are the pre-existing `NYIRAW` oper catch-all, unrelated to this change. Full effectiveness requires #130866 (which removes the shadowing SIMD-ABI bailouts for SIMD params/locals/stores/call-args); standalone, this change already clears the 373 asserts above on that collection. > [!NOTE] > This PR description was drafted with the assistance of GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
# Conflicts: # src/coreclr/jit/codegenwasm.cpp
|
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "7ffe7916ffb20d3db9d718d372e6bd0fffd2e0af",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "46ec2c1bce94b5cc262049195c5b5b50a64708e9",
"last_reviewed_commit": "7ffe7916ffb20d3db9d718d372e6bd0fffd2e0af",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "46ec2c1bce94b5cc262049195c5b5b50a64708e9",
"last_recorded_worker_run_id": "29688140671",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "7ffe7916ffb20d3db9d718d372e6bd0fffd2e0af",
"review_id": 4730810927
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: On wasm, several codegen paths currently bail to the interpreter (NYI_WASM_SIMD) because a 128-bit SIMD value arrives as an i32 by-ref pointer rather than a real v128 on the wasm value stack. Emitting a v128.* op against an i32 operand yields an invalid module, so the JIT defensively bails. This blocks SIMD from actually executing on wasm. The motivation is clear, correct, and well-justified.
Approach: The PR treats Vector128<T> as the wasm v128 ABI primitive and materializes it by value. getWasmLowering/LowerType map Vector128<T> (primitive-numeric T only) to CORINFO_WASM_TYPE_V128; WasmClassifier::ToJitType maps that to TYP_SIMD16; and the five codegen sites that previously bailed (SIMD parameter homing, SIMD16 local field load, store-indirect, call argument, and local load) are removed so real v128 values flow through. RaiseSignature round-trips the 'V' signature char via a newly cached representative v128 type. The IsPrimitiveNumeric gate in the three ARM64 ComputeValueTypeShapeCharacteristics sites is refactored into a shared VectorFieldLayoutAlgorithm.IsSupportedVectorBaseType helper, reused by the new wasm check. The scoping rationale for restricting to Vector128<T> (other SIMD types remain multi-field/by-ref or are target-gated to non-wasm) is sound and preserves existing ABI for those types.
Summary: This is a focused, correct step that removes the interpreter bails and wires Vector128<T> through the wasm v128 ABI by value. The IsSupportedVectorBaseType refactor is behavior-preserving for the existing ARM64 sites (it currently just wraps IsPrimitiveNumeric) while centralizing the definition. The 'V' round-trip via CachedV128Type is safe because every lowered 'V' is exactly a 16-byte v128, so the JIT's element-type-agnostic TYP_SIMD16 mapping makes the specific cached instantiation immaterial; the caching uses the same non-atomic ??= pattern as the existing empty-struct/struct-by-size caches. The added R2R test (WasmSimdModule) validates the ABI paths (parameter, return, through-local, store-indirect, call-argument) by asserting the emitted wasm valtype is 0x7B (v128), which is a meaningful regression guard against reverting to the by-ref i32 ABI. No arithmetic intrinsics are exercised, matching the stated scope. I found no correctness, security, or convention issues in the changed lines. Verdict: LGTM. As the author notes, this is a draft pending full CI (wasm SIMD only executes in CI), which is the appropriate gate before merge.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 65.4 AIC · ⌖ 10.6 AIC · ⊞ 10K
Restore the main SIMD-enabled ISA config (compiler.cpp compSetProcessor adds
PackedSimd + Vector128; InstructionSetHelpers adds simd128) that the prototype
had previously disabled, and drop the prototype-stale NYI_WASM_SIMD stubs for
NI_PackedSimd_{ExtractScalar,ReplaceScalar,shifts,StoreSelectedScalar} that were
shadowing the real implementations from dotnet#130850. Keeps the WasmBase
LeadingZeroCount/TrailingZeroCount cases. SIMD-enabled crossgen relies on the
JitWasmSimdNyiToR2RUnsupported punt flag for the remaining v128-materialization
gap (dotnet#130866) until that lands.
# Conflicts: # src/coreclr/jit/codegenwasm.cpp
davidwrighton
left a comment
There was a problem hiding this comment.
I'd like to understand the rationale behind some choices, and push back on the not handling the wrapper struct scenario.
A 128-bit Vector<T> uses the same v128 ABI as Vector128<T>, and a single-field struct wrapping either lowers to the v128 primitive, matching emscripten's isSingleElementStruct handling. Multi-field aggregates keep the generic by-ref ABI since the wasm C ABI has no HFA/HVA concept. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (1)
src/coreclr/tools/Common/JitInterface/WasmLowering.cs:154
LowerTypechecksIsWasmV128Type(type)before lowering, butLowerToAbiTypecan now lower a non-intrinsic wrapper struct (single-field wrapper around v128) to a v128 ABI type. In that caseabiTypebecomesVector128<T>/Vector<T>and the subsequentswitch (abiType.UnderlyingType.Category)will hit the default path and throw, even though the ABI lowering succeeded. Adding a post-lowering v128 check keepsLowerTypeconsistent withLowerToAbiTypeand makes it safe to call with wrapper structs.
TypeDesc abiType = LowerToAbiType(type);
if (abiType == null)
{
return pointerType;
}
- Files reviewed: 11/11 changed files
- Comments generated: 0 new
…Shuffle (Adam's shuffle-mask fix) Fixes the invalid i8x16.shuffle immediate (out-of-range lane) that made the SIMD composite fail wasm validation. Resolved 2 conflicts vs dotnet#130866/dotnet#131000: - codegenwasm.cpp: dropped redundant ins local (superseded by dotnet#131000 store restructure) - lowerwasm.cpp: took shuffle case + IsCnsVec() immediate handling from dotnet#130991
Today several wasm codegen paths bail to the interpreter (
NYI_WASM_SIMD) because a 128-bit SIMD value arrives as ani32(a by-ref pointer) rather than a realv128on the wasm value stack. Emitting av128.*op against ani32operand produces an invalid module, so the JIT defensively bails. This is the end-to-end blocker for SIMD actually executing on wasm.This change treats
Vector128<T>as the wasmv128ABI primitive and materializes it by value:getWasmLoweringmapsVector128<T>toCORINFO_WASM_TYPE_V128, andToJitTypemaps that toTYP_SIMD16.v128values: SIMD parameter homing, SIMD16 local field load, store-indirect, call argument, and local load.RaiseSignaturecan round-trip the'V'signature char back to a concrete v128 type.Why only
Vector128<T>. It is the single SIMD type the JIT recognizes asTYP_SIMD16on wasm. IngetBaseTypeAndSizeOfSIMDType,Vector128<T>(16 bytes) is not target-gated,where-asVector64<T>isTARGET_ARM64-only andVector256<T>/Vector512<T>areTARGET_XARCH-only, so on wasm those fall through toTYP_UNDEFand are handled as regular structs. TheSystem.Numericsvectors (Vector2/Vector3/Vector4,Vector<T>,Plane,Quaternion) are multi-field, sogetWasmLoweringclassifies them as passed by reference (they arrive asTYP_BYREF, notvarTypeIsSIMD). The net effect is that the only by-value SIMD value reaching these codegen paths is aVector128<T>in a realv128register, so the previous bails are no longer needed and no new gate is required. The other SIMD types keep their pre-change ABI and are deferred to follow-ups.Because every
'V'in a lowered signature is now exactly 16 bytes, there is no mixed-width offset ambiguity in the signature round-trip.Testing. Adds an R2R test (
WasmSimdModule) exercising the v128 calling convention — parameter, return, through-local, store-indirect, and call-argument — usingVector128<int>without any SIMD arithmetic intrinsics, so only the ABI/materialization paths are covered. Locally,ILCompiler.ReadyToRun.Tests(filterWasm) passes 2/2; wasm SIMD only executes in CI, which is the point of this draft.Opening as draft for full CI validation.
Note
This PR description was drafted with GitHub Copilot.