Uh oh!
There was an error while loading. Please reload this page.
[wasm] Implement RelaxedSimd intrinsics api - #130050
Conversation
Adds System.Runtime.Intrinsics.Wasm.RelaxedSimd as a sibling class to PackedSimd, exposing the 19 opcodes from the WebAssembly Relaxed SIMD proposal (Phase 5; shipped in Chrome 114+, Firefox 120+, V8, and Node 22+). Class shape mirrors the AVX10 precedent (sibling, not subclass) for two reasons: 1. Method-name overlap with PackedSimd on Swizzle/Min/Max has intentionally different semantics (relaxed = implementation- defined for out-of-domain inputs, deterministic on PackedSimd). Inheritance would silently shadow the deterministic operation. 2. The relaxed-SIMD proposal is its own feature flag at the engine level, so a distinct IsSupported makes the gate explicit. Method naming follows the PackedSimd idiom (MultiplyAdd not Fma, ConvertToInt32 not TruncateToInt32, DotProduct to disambiguate from PackedSimd.Dot which is the standard signed-i16 path). The relaxed dot-product overloads take Vector128<byte> by Vector128<sbyte> directly, encoding the spec's i7 constraint at the type-system level. This commit only adds the managed API surface (RelaxedSimd.cs, RelaxedSimd.PlatformNotSupported.cs, ref source, tests). Runtime wiring (Mono SIMD intrinsic recognizer, LLVM/AOT codegen, WasmEnableRelaxedSimd MSBuild property, IsSupported runtime detection) follows in stacked commits. API proposal draft: docs/ at ~/.copilot/session-state/. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match Vector128.MultiplyAddEstimate's naming for the 'may or may not be true FMA' semantic. The Wasm relaxed_madd / relaxed_nmadd ops are exactly that: the runtime may or may not emit a true fused multiply add depending on the host. FusedMultiplyAdd remains reserved for guaranteed-fusion (Avx512F, AdvSimd.Fma, etc.). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduces the build-time switch for the Wasm Relaxed SIMD extension (API surface added in the previous commit). When the property is set to true, the build: - threads -mrelaxed-simd to emcc (BrowserWasmApp.targets) - threads --enable-relaxed-simd to wasm-opt (WasmApp.Common.targets) - threads mattr=+relaxed-simd to the AOT compiler (likewise) - sets WASM_ENABLE_RELAXED_SIMD=1 in the emscripten env - includes ILLink.Substitutions.WasmRelaxedSimd.xml so that RelaxedSimd.get_IsSupported is stubbed to true by the trimmer When the property is false (default), the parallel ILLink.Substitutions.NoWasmRelaxedSimd.xml stubs IsSupported to false. The test infra in eng/testing/tests.wasm.targets mirrors the substitution wiring for the BuildAOTTestsOnHelix=true path that sidesteps BrowserWasmApp.targets. Validation that WasmEnableRelaxedSimd=true requires WasmEnableSIMD=true is performed at build time (in the _WasmCommonPrepareForWasmBuildNative target) with a clear error message. Default is false everywhere. The browser-wasm flavor will likely want to flip to true once the runtime wiring (Mono SIMD intrinsic recognizer + LLVM intrinsic table + interpreter handlers) lands in subsequent commits. Validated locally with both ./build.sh mono+libs -os browser -c Release (default off) and the same with /p:WasmEnableSIMD=true /p:WasmEnableRelaxedSimd=true. Both succeed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds 13 INTRINS_* entries for the WebAssembly relaxed-SIMD opcodes, mapping to LLVM's Intrinsic::wasm_relaxed_* function declarations: - relaxed swizzle (i8x16) - relaxed truncating float-to-int (f32x4 and f64x2->i32x4) - relaxed multiply-add / negated multiply-add (f32x4, f64x2) - relaxed lane select (i8x16, i16x8, i32x4, i64x2) - relaxed min / max (f32x4, f64x2) - relaxed q15mulr signed - relaxed dot i8x16 i7x16 signed - relaxed dot i8x16 i7x16 add signed Non-overloaded ops use INTRINS(...) (single fixed signature in LLVM); overloaded ones (madd, nmadd, laneselect, min, max) use INTRINS_OVR_TAG(...) with element-width tag bitmasks so the existing overloaded-intrinsic registration path picks the correct concrete type at call time. This commit is no-op on its own: nothing references INTRINS_WASM_ RELAXED_* yet. The Mono SIMD intrinsic recognizer wiring in the next commit will dispatch RelaxedSimd.* methods to these IDs. Build verified: ./build.sh mono+libs -os browser -c Release passes with mono-aot-cross successfully linked. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pieces 2 + 3 of the relaxed-SIMD support: declares the
MONO_CPU_WASM_RELAXED_SIMD CPU feature, recognizes the
'+relaxed-simd' mattr passed by the AOT compiler, advertises the
feature to the LLVM CPU-features query, and dispatches the
RelaxedSimd.* managed methods to the LLVM intrinsics added in the
previous commit.
Specifics:
- mini.h: add MONO_CPU_WASM_RELAXED_SIMD = 1 << 3.
- aot-compiler.c: parse 'relaxed-simd' (with optional +/- prefix)
in mattr arguments.
- mini-llvm.c flags_map: advertise the feature so the runtime
reports it when the AOT'd code is loaded.
- simd-methods.h: register the four new method names not previously
used elsewhere — DotProductAdd, LaneSelect,
MultiplyAddNegatedEstimate, MultiplyRoundedQ15. (DotProduct,
MultiplyAddEstimate, Swizzle, Min, Max, ConvertToInt32,
ConvertToUInt32 reuse existing entries.)
- simd-intrinsics.c: new relaxedsimd_methods[] table mapping each
method to its (opcode, intrinsic-id) pair; register the group
under 'RelaxedSimd'/MONO_CPU_WASM_RELAXED_SIMD; add an
emit-wasm-supported-intrinsics block to dispatch the type-
discriminated ConvertToInt32/UInt32 overloads (float vs double).
- mini-llvm.c: hoist case OP_XOP_X_X_X_X out of the TARGET_ARM64
block (DotProductAdd is the first non-arm consumer); keep the
INTRINS_AARCH64_SHA1{C,M,P} bits under TARGET_ARM64.
End-to-end validation on browser-wasm AOT (Chromium 143,
WasmEnableSIMD=true, WasmEnableRelaxedSimd=true):
System.Runtime.Intrinsics.Tests 13023/13023 passing including the
six new RelaxedSimdTests (DotProduct, DotProductAdd,
MultiplyAddEstimate, LaneSelect, Swizzle, IsSupportedReflects).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>…library variant
Mirrors the existing PackedSimd library structure: the SDK ships three static
mono-wasm interp-simd library variants and the app build picks one based on its
SIMD selection (the existing WasmEnableSIMD switch plus the new WasmEnableRelaxedSimd):
libmono-wasm-nosimd.a (default) WasmEnableSIMD=false
libmono-wasm-simd.a -msimd128 WasmEnableSIMD=true (default when SIMD on)
libmono-wasm-relaxed-simd.a -msimd128 -mrelaxed-simd WasmEnableSIMD=true, WasmEnableRelaxedSimd=true
This avoids forcing a runtime-build-time choice (the previous
MonoWasmEnableRelaxedSimd flag, now removed) and keeps WasmEnableRelaxedSimd a
pure per-app property paralleling the rest of the relaxed-simd wiring (AOT mattr,
emcc -mrelaxed-simd, wasm-opt --enable-relaxed-simd, ILLink IsSupported stub).
How the .def routes the relaxed-simd entries:
* New INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_{V,VV,VVV} macro family is set up in
the prelude of interp-simd-intrins.def. With HOST_WASM_RELAXED_SIMD defined it
aliases the regular INTERP_WASM_SIMD_INTRINSIC_V_{V,VV,VVV} macros, so the
relaxed-simd library generates real emscripten intrinsic wrappers and tables
with the real Wasm opcode values (0x100-0x113). Without the define the macros
route the table entries to per-arity stub functions
(_mono_interp_simd_relaxed_unsupported_{1,2,3}) and clear the Wasm-opcode slot,
so libmono-wasm-simd.a still exports all the same symbols with stable indices
and the jiterpreter falls back to the C helper (which asserts but should be
unreachable because RelaxedSimd.IsSupported reports false).
* transform-simd.c (in mono-ee-interp) compiles the relaxed entries
unconditionally through the same INTRINS_COMMON path that PackedSimd uses, so
the lookup table and MintSIMDOpsPP/PPP/PPPP enums in mintops.h have identical
layouts across both library flavors. A single mono-ee-interp.a links cleanly
against either variant.
* genmintops.py learns the new RELAXED macro names so the TypeScript SimdIntrinsic
enums and SimdInfo[] stay in sync.
* emit_sri_relaxedsimd in transform-simd.c keys RelaxedSimd.IsSupported and the
intrinsic dispatch off mono_interp_relaxed_simd_supported, an extern int
exported by both library variants (1 in relaxed, 0 in regular).
* Method-name collisions with PackedSimd (Swizzle, Min, Max, MultiplyAddEstimate,
ConvertToInt32, ConvertToUInt32) are sidestepped by prefixing the .def entry
names with 'Relaxed'; emit_sri_relaxedsimd prepends 'Relaxed' before lookup.
The public API surface keeps the unprefixed names.
* installer manifest learns the new libmono-wasm-relaxed-simd.a entry.
Jiterpreter wiring is fully data-driven and needs no TypeScript changes: the
existing emit_simd_3/emit_simd_4 fast path calls mono_jiterp_get_simd_opcode
which returns the relaxed-simd Wasm opcode value, and appendSimd encodes it
through appendULeb so the 2-byte LEB128 form for opcodes >= 0x80 is handled
correctly.
Validated on browser-wasm Release with Chromium 143:
* AOT, WasmEnableRelaxedSimd off: System.Runtime.Intrinsics.Tests
RelaxedSimdTests = 1 passed + 5 ConditionalFact skipped.
* AOT, WasmEnableRelaxedSimd on: full System.Runtime.Intrinsics suite
13023/13023 pass.
* Interp-only, both off and on: 1 passed + 5 skipped (interp path uses the
SDK's pre-built dotnet.native.wasm, which currently always links
libmono-wasm-simd.a; AOT is required to exercise the relaxed-simd helpers
end-to-end. Shipping a second dotnet.native.wasm variant for interp-only
apps is a future enhancement.)Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…n nosimd lib; wire WasiApp.targets Two CI regressions exposed on PR dotnet#130050: 1. browser.proj was still passing -DENABLE_WASM_RELAXED_SIMD=0 to the dotnet.native cmake configure based on a removed MonoWasmEnableRelaxedSimd property. Nothing under src/mono/browser/runtime/CMakeLists.txt consumes that variable, so CMake's 'Manually-specified variables were not used' diagnostic was elevated to a build error across all browser-wasm Build legs. Drop the dead line. 2. WASI's prebuilt dotnet.wasm always links libmono-wasm-nosimd.a (the runtime cmake never receives WasmEnableSIMD=true), but the new mono_interp_relaxed_simd_supported extern is referenced unconditionally from libmono-ee-interp.a under HOST_BROWSER || HOST_WASI. Definitions only lived in libmono-wasm-{simd,relaxed-simd}.a, leaving the WASI link with an undefined symbol. Provide a default '= 0' definition in interp-nosimd.c so every SIMD-library variant exports the symbol. Also mirror the browser app-build library selection into WasiApp.targets so WASI apps can opt into the relaxed-simd library variant when WasmEnableRelaxedSimd=true, matching the BrowserWasmApp.targets behavior. Validated locally: * browser-wasm Release mono+libs build clean. * wasi-wasm Release mono+libs build clean. * AOT smoke: System.Runtime.Intrinsics.Tests with WasmEnableSIMD=true and WasmEnableRelaxedSimd=true: 13023/13023 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The PR's third SIMD library variant (libmono-wasm-relaxed-simd.a) ships in the
runtime pack but is picked up by the broad '*.a' glob in {Browser,Wasi}App.targets
that scoops every static lib into _WasmNativeFileForLinking. The selection logic
sets _WasmSIMDLibToExclude to a semicolon-separated list of bare filenames and
then does:
<_WasmNativeFileForLinking
Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLibToExclude)" />
MSBuild splits the semicolons into items but only prefixes the FIRST one with the
runtime-pack directory; subsequent items become bare filenames that don't match
the full-path entries in _WasmNativeFileForLinking, so the second exclusion is
silently dropped and libmono-wasm-relaxed-simd.a leaks into every test app's link
line. The post-link wasm-opt then fails because it isn't invoked with
--enable-relaxed-simd:
[wasm-validator error in function _mono_interp_simd_wasm_i32x4_relaxed_trunc_f32x4]
unexpected false: all used features should be allowed, on
(i32x4.relaxed_trunc_f32x4_s ...)
[--enable-relaxed-simd]
Fatal: error validating input
Fix: embed the runtime-pack directory prefix into every value of
_WasmSIMDLibToExclude so the Remove attribute receives fully-qualified paths
that actually match the items added by the glob. Applied symmetrically to
BrowserWasmApp.targets and WasiApp.targets.
Validated locally on browser-wasm:
* Default (WasmEnableRelaxedSimd unset): System.Runtime.Intrinsics.Tests
RelaxedSimdTests = 1 passed + 5 ConditionalFact-skipped (was failing
wasm-opt validation pre-fix), full suite 13018 passed + 5 skipped.
* WasmEnableRelaxedSimd=true: full suite 13023/13023 pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
lewing
commented
Jul 2, 2026
@tannergooding relaxedsimd is standardized now, there is no rush but it would be nice to figure out the api details |
tannergooding
commented
Jul 2, 2026
We should just need to get an API proposal up and it will be relatively straightforward to get in/approved. The surface in here looks generally correct at a glance, I'd just need to go through the full proposal against the finalized spec before marking it |
lewing
commented
Jul 5, 2026
API proposal opened at #130223 (tracks the surface added in this PR). Marking this PR as dependent on the API-review outcome there. Note This comment was created with the assistance of GitHub Copilot. |
- Correct DotProduct/DotProductAdd operand types per the finished WebAssembly relaxed-simd spec pseudocode: operand `a` is signed and operand `b` is unsigned-7-bit, not the reversed (byte, sbyte) shape the prototype had. Updated the impl, PlatformNotSupported, ref, and tests. - Fix RelaxedSimd IsSupported reflection test: assert MethodInfo is non-null before invoking, so a lookup miss yields a diagnostic instead of NRE. - Replace float.Epsilon-based ULP-style tolerance in the MultiplyAddEstimate test with an explicit 1e-5 relative tolerance. float.Epsilon is the smallest subnormal (~1.4e-45), not the unit roundoff, so the previous check was effectively exact-equality. - Document the reflection-reach stack-overflow invariant in the Mono interp emit_sri_relaxedsimd path so future editors preserve the IsSupported gate contract, and note the general follow-up covering all wasm intrinsic classes. Addresses copilot-pull-request-reviewer feedback on dotnet#130050 and tracks the API-review corrections captured in dotnet#130223. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
lewing
commented
Jul 5, 2026
Pushed 7f99067 addressing the Copilot review feedback plus a spec-derived correction to
API-review issue #130223 updated with the corrected surface and prototype pointer. Note This comment was created with the assistance of GitHub Copilot. |
RelaxedSimd implies PackedSimd when expanding supported instruction sets. Conversely, an unsupported PackedSimd must make RelaxedSimd unsupported as well. Document that reverse implication expands unsupported sets to avoid treating it as feature enablement. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3de5ced8-be89-4f1b-bff4-5b3c577cedac
lewing
commented
Sep 3, 2026
sigh auto
Turns out it was a misread of the reverse table by copilot. |
There was a problem hiding this comment.
🟡 Changes recommended
There are merge-blocking correctness issues (a missing CoreLib using causing build failure, and an interpreter path that can fall into self-recursive stubs when relaxed SIMD is disabled) plus a test that doesn’t actually force the intended native build configuration.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
src/mono/mono/mini/interp/transform-simd.c:1512
- When mono_interp_relaxed_simd_supported is false, emit_sri_relaxedsimd returns FALSE even though the managed RelaxedSimd bodies are self-recursive stubs. If a caller reaches these methods (e.g., via reflection), returning FALSE can lead to infinite recursion/stack overflow instead of a controlled failure. Consider emitting a NIY/throw path here (similar to the PackedSimd handling above) so the interpreter does not attempt to execute the self-recursive stub body.
src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs:86 - PublishRelaxedSimd currently passes isNativeBuild: relaxedSimd, but that parameter is only used for bundle assertions and does not enable native relinking. As a result, the relaxedSimd=true case may not actually build/link the relaxed-simd native library variant, and it may also miss the WasmEnableSIMD=true prerequisite enforced in WasmApp.Common.targets for native builds. Make the test explicitly opt into WasmBuildNative (and SIMD) when relaxedSimd is true so it exercises the intended build configuration.
src/native/corehost/browserhost/browserhost.cpp:119 - BrowserHost_InitializeDotnet only recognizes RelaxedSimd.IsSupported when the property value is exactly "true" (case-sensitive). Since the property value ultimately originates from runtimeconfig/configProperties, make this check case-insensitive to avoid a mismatch where the trimmer forces IsSupported=true but the VM never enables the RelaxedSimd instruction set.
- Files reviewed: 56/56 changed files
- Comments generated: 1
- Review effort level: Lite
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
🟡 Changes recommended
The CoreCLR browser targets pass --instruction-set=relaxed-simd via PublishReadyToRunCrossgen2ExtraArgs, which is inconsistent with existing Crossgen2 argument patterns and may break R2R publishing when relaxed-SIMD is enabled.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 56/56 changed files
- Comments generated: 1
- Review effort level: Lite
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
adamperlin
left a comment
There was a problem hiding this comment.
The JIT changes look good to me besides a formatting nit for the intrinsic definitions! I can't comment as much on mono + other changes.
Align the Wasm relaxed SIMD intrinsic definitions and use the established Crossgen2 instruction-set option syntax. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3de5ced8-be89-4f1b-bff4-5b3c577cedac
There was a problem hiding this comment.
🔵 Needs a closer look
It touches multiple runtime layers (public API, CoreCLR JIT/EE/R2R encoding, Mono interpreter, and build/startup pipelines), so it warrants final human review despite only minor localized issues found.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/mono/mono/mini/interp/transform-simd.c:1510
- The comment claims the stub-looping behavior is "documented in the class-level XML remarks", but neither RelaxedSimd, PackedSimd, nor WasmBase have XML remarks explaining this reflection/bypass behavior. This makes the comment misleading and harder to trust when debugging interpreter behavior; either document it on the managed types or adjust the native comment to just reference the existing behavior/pattern.
- Files reviewed: 56/56 changed files
- Comments generated: 0 new
- Review effort level: Lite
lewing
commented
Sep 5, 2026
/azp run runtime |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
🔵 Needs a closer look
It spans new public API plus coordinated CoreCLR/Mono/JIT/R2R/build-system changes, so it needs a final human pass for end-to-end correctness and compatibility.
Review details
- Files reviewed: 56/56 changed files
- Comments generated: 1
- Review effort level: Lite
| // interpreter to loop through the stub. That matches the general risk pattern | ||
| // for all wasm intrinsic classes (PackedSimd, WasmBase) and is documented in | ||
| // the class-level XML remarks; a dedicated PlatformNotSupportedException throw | ||
| // is tracked as a follow-up covering all wasm intrinsic classes uniformly. |
Move browser-wasm Firefox testing from ESR 140 to Firefox 155, which supports WebAssembly Relaxed SIMD. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3de5ced8-be89-4f1b-bff4-5b3c577cedac
There was a problem hiding this comment.
🔵 Needs a closer look
It spans new public API plus coordinated changes across CoreCLR JIT/R2R, Mono runtime/build, and browser startup/tooling, requiring maintainer-level end-to-end validation.
Review details
- Files reviewed: 57/57 changed files
- Comments generated: 0 new
- Review effort level: Lite
Implementation of API approved in #130223