Skip to content

[release/11.0-rc1] [mono][interp] Intrinsify Vector128.Store/StoreUnsafe on wasm - #132575

Merged
lewing merged 1 commit into
release/11.0-rc1from
lewing-backport-pr-132502-to-release-11-0-rc1
Aug 20, 2026
Merged

[release/11.0-rc1] [mono][interp] Intrinsify Vector128.Store/StoreUnsafe on wasm#132575
lewing merged 1 commit into
release/11.0-rc1from
lewing-backport-pr-132502-to-release-11-0-rc1

Conversation

@lewing

Copy link
Copy Markdown
Member

Manual backport of #132502 to release/11.0-rc1.

Why this needed a manual backport

The automatic cherry-pick conflicted in two files:

Every added and removed line in this commit is byte-identical to upstream be1c680 — only hunk headers and line numbers differ.

Depends on #132525

This backport is only effective because #132525 already merged. On release/11.0-rc1 before that fix, packedsimd_alias_methods was mis-sorted, and since lookup_intrins() binary-searches the table, Store, StoreUnsafe, Subtract and SubtractSaturate were all unreachable. Simulating mono_binary_search against the pre-#132525 table confirms this — so #132502 on its own would have been a silent no-op.

With #132525 merged, the resulting table is strictly sorted and all 49 entries resolve; Store and StoreUnsafe land at indices 29 and 30.

Original change

PackedSimd.Store(T* address, Vector128<T> source) takes its operands in the opposite order from Vector128.Store(this Vector128<T> source, T* destination), and the alias path only renames the method — emit_common_simd_epilogue assigns sregs in signature order. This adds them to the alias table and swaps the two sregs after the epilogue, guarded on the shape actually being a store (two parameters, void return, raw address as the second parameter). The three-argument StoreUnsafe(source, destination, elementOffset) has no PackedSimd counterpart and still falls back to managed code.

interp_packedsimd_store also switches from assigning through a v128_t* to wasm_v128_store, which does not claim 16-byte alignment that neither API guarantees.

Upstream measured ~6.2x on Vector128.Store<int> and ~6.3x on Vector128.StoreUnsafe<int> under the interpreter.

Local validation

  • ./build.sh clr+libs -rc release — 0 errors, 0 warnings
  • System.Runtime.Intrinsics.Tests13011/13011 passing, 0 failed, including the three new tests: Vector128StoreUnalignedTest, Vector128StoreUnsafeUnalignedTest, Vector128StoreUnsafeElementOffsetUnalignedTest

The count is 13011 rather than upstream's 13024 because the #132499 and #132496 tests are not on this branch.

Not validated locally: the interp-simd.c change is wasm-only, so a native osx-arm64 build does not exercise it. It is byte-identical to the upstream commit, which passed full CI.

Note

This pull request was authored with the assistance of GitHub Copilot.

Rebased onto `main` now that #132500 has merged; this is a single commit
touching 3 files.
`Vector128.Store` and `Vector128.StoreUnsafe` were deliberately left out
of the PackedSimd alias table because their operands are reversed
relative to the method they would lower to:
```
PackedSimd.Store (T* address, Vector128<T> source)
Vector128.Store (this Vector128<T> source, T* destination)
```
The alias path only renames the method, and `emit_common_simd_epilogue`
assigns sregs in signature order, so aliasing them as-is would have
passed the vector where the destination address is expected. They
therefore fell back to managed code, which routes through
`Unsafe.WriteUnaligned` — an intrinsic the interpreter does not
implement — making a documented-as-fast API roughly six times slower
than the PackedSimd equivalent.
Add them to the alias table and swap the two sregs after the epilogue
has run. sregs are var indices consumed positionally by
`MINT_SIMD_INTRINS_P_PP` (`interp.c:6141`), so the swap is a
permutation: it changes their order, not the set of vars used, leaving
liveness and refcounting unaffected. It runs during IL→IR generation,
before every optimization pass.
It also matches what the jiterpreter already expects —
`SimdIntrinsic3.StoreANY` in `jiterpreter-trace-generator.ts` loads arg
2 as the address and arg 3 as the vector.
`Store` is registered for every element type (`ANY`), so the reorder is
guarded on the shape actually being a store: two parameters, void
return, and a raw address (`T*` or `ref T`) as the second parameter. The
three-argument `StoreUnsafe(source, destination, elementOffset)` has no
PackedSimd counterpart and is rejected by the parameter count, falling
back to managed code as before.
`System.Numerics.Vector.Store`/`StoreUnsafe` share the identical shape
and route through the same emit path, so they are lowered too.
`StoreAligned`/`StoreAlignedNonTemporal` are absent from the alias table
and continue to fall back to managed code — importantly, since
`StoreAligned` has a runtime alignment check that must throw.
`interp_packedsimd_store` assigned through a `v128_t*`, claiming 16-byte
alignment that neither `PackedSimd.Store` nor `Vector128.StoreUnsafe`
guarantee. It now uses `wasm_v128_store`, which stores through a
`__packed__ __may_alias__` struct. This mirrors
`interp_packedsimd_load128`'s existing use of `wasm_v128_load`, and
matches the jiterpreter's `v128_store` with an alignment hint of 1.
Misaligned access is well-defined at the wasm ISA level, so this was C
UB rather than a live bug.
browser-wasm under V8, interpreter, 1,000,000 iterations, 2 warmups,
best of 5, same harness before and after on the same machine:
| Operation | Before | After | Speedup |
|---|---|---|---|
| `Vector128.Store<int>` | 3.243 ns | 0.521 ns | **6.2x** |
| `Vector128.StoreUnsafe<int>` | 3.253 ns | 0.518 ns | **6.3x** |
| `PackedSimd.Store<int>` (control) | 0.485 ns | 0.527 ns | unchanged |
The control's ~8% drift bounds run-to-run noise; the 6x is far outside
it. No loop-overhead baseline is subtracted — an empty-loop control
measured *higher* than the store loops, since the jiterpreter doesn't
trace it.
- browser-wasm `mono+libs` Release: 0 errors, 0 warnings
- native osx-arm64 `mono` Release: 0 errors, 0 warnings
- `System.Runtime.Intrinsics`: **13024/13024** passing (13021 baseline +
3 new)
- `System.Numerics.Vectors`: **7449/7449** passing
- IR (`MONO_VERBOSE_METHOD`): `simd_intrins_p_pp [74 <- 73 72], 191` —
descending sregs confirm the swap; the three-argument overload still
emits a managed `call`
- `System.Numerics.Vector<T>.StoreUnsafe` likewise emits
`simd_intrins_p_pp [19 <- 18 14], 191`
Three tests covering `Store`, `StoreUnsafe`, and `StoreUnsafe` with an
element offset, across twelve element types, at every 16-byte alignment,
with guard bytes on both sides so a misplaced or oversized store is
caught. The element-offset arm uses `StoreUnsafe(ref *(destination - 1),
elementOffset: 1)` so a dropped offset argument shows up as a misplaced
store. They pass on an unmodified runtime as well, so they validate
behavior rather than implementation.
> [!NOTE]
> This pull request was authored with the assistance of GitHub Copilot.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 3ebb2f49-c9fc-4c28-9755-c51b9deac735
(cherry picked from commit be1c680)
CopilotAI lite review requested due to automatic review settings August 20, 2026 17:28
@lewing
lewing changed the base branch from main to release/11.0-rc1August 20, 2026 17:28
@lewinglewing added the arch-wasm WebAssembly architecture label Aug 20, 2026
@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.

@lewinglewing closed this Aug 20, 2026
@lewinglewing reopened this Aug 20, 2026
@azure-pipelines

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

@steveisoksteveisok added the Servicing-approved Approved for servicing release label Aug 20, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 backports the Mono interpreter/WASM optimization to intrinsify Vector128.Store / Vector128.StoreUnsafe by routing them through the PackedSimd store intrinsic, including an operand swap to account for the reversed parameter ordering between the APIs. It also updates the WASM store helper to avoid C UB on unaligned destinations and adds regression tests to validate correct behavior across alignments and element types.

Changes:

  • Add Store/StoreUnsafe to the PackedSimd alias table and detect store-shaped calls that require swapping operands.
  • Swap the two operand sregs after emit_common_simd_epilogue assigns them in signature order, so the interpreter passes (addr, vec) to the PackedSimd store helper.
  • Update interp_packedsimd_store to use wasm_v128_store for well-defined unaligned stores, and add alignment-sensitive store tests.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

FileDescription
src/mono/mono/mini/interp/transform-simd.cAdds Store/StoreUnsafe aliasing and swaps sregs for store-shaped signatures so the interpreter calls PackedSimd.Store with correct operand order.
src/mono/mono/mini/interp/interp-simd.cSwitches the PackedSimd store helper to wasm_v128_store to avoid assuming 16-byte alignment.
src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.csAdds tests validating correct store behavior for unaligned destinations (including the elementOffset overload).

@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-rc1 milestone Aug 20, 2026
@lewing

Copy link
Copy Markdown
MemberAuthor

/ba-g failures are known, this is a release branch

@lewing
lewing merged commit 6dd4cd9 into release/11.0-rc1Aug 20, 2026
129 of 193 checks passed
@lewing
lewing deleted the lewing-backport-pr-132502-to-release-11-0-rc1 branch August 20, 2026 20:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasmWebAssembly architecturearea-Codegen-Interpreter-monoServicing-approvedApproved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@lewing@steveisok