From c46495be896c0f94f087a11a4ded4f5a387d987b Mon Sep 17 00:00:00 2001 From: lewing Date: Thu, 25 Jun 2026 10:18:38 -0500 Subject: [PATCH 01/31] Add RelaxedSimd intrinsic API surface for WebAssembly Relaxed SIMD 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 by Vector128 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> --- .../System.Private.CoreLib.Shared.projitems | 2 + .../Wasm/RelaxedSimd.PlatformNotSupported.cs | 49 ++++++ .../Runtime/Intrinsics/Wasm/RelaxedSimd.cs | 141 ++++++++++++++++++ .../ref/System.Runtime.Intrinsics.cs | 30 ++++ .../System.Runtime.Intrinsics.Tests.csproj | 1 + .../tests/Wasm/RelaxedSimdTests.cs | 108 ++++++++++++++ 6 files changed, 331 insertions(+) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs create mode 100644 src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 94bc31d6a10a45..b6829663c3a98a 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2890,10 +2890,12 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs new file mode 100644 index 00000000000000..3266fcdc2f228f --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.Intrinsics.Wasm +{ + [CLSCompliant(false)] + public abstract class RelaxedSimd + { + /// Gets a value that indicates whether the APIs in this class are supported. + /// if the APIs are supported; otherwise, . + /// A value of indicates that the APIs will throw . + public static bool IsSupported { [Intrinsic] get { return false; } } + + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) { throw new PlatformNotSupportedException(); } + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) { throw new PlatformNotSupportedException(); } + + public static Vector128 ConvertToInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector128 ConvertToUInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector128 ConvertToInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector128 ConvertToUInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + + public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + + public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + + public static Vector128 Min(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 Max(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 Min(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 Max(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + + public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + + public static Vector128 DotProduct(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) { throw new PlatformNotSupportedException(); } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs new file mode 100644 index 00000000000000..603e215032fe11 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace System.Runtime.Intrinsics.Wasm +{ + /// Provides access to the WebAssembly relaxed SIMD instructions via intrinsics. + /// + /// + /// Operations exposed on this class behave "relaxedly": for inputs outside a well-defined range + /// the result is implementation-defined and may differ between WebAssembly engines and host + /// architectures. Callers that require deterministic semantics across engines should use the + /// corresponding operation (where available) instead. + /// + /// + /// All members of this class require the runtime to support the + /// relaxed SIMD WebAssembly proposal. + /// + /// + [Intrinsic] + [CLSCompliant(false)] + public abstract class RelaxedSimd + { + /// Gets a value that indicates whether the APIs in this class are supported. + /// if the APIs are supported; otherwise, . + /// A value of indicates that the APIs will throw . + public static bool IsSupported { [Intrinsic] get { return IsSupported; } } + + // Relaxed swizzle: like PackedSimd.Swizzle, but for index lanes outside [0, 16) the + // result is implementation-defined (often the index modulo 16 on x86, zero on ARM). + + /// i8x16.relaxed_swizzle + [Intrinsic] + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) => Swizzle(vector, indices); + /// i8x16.relaxed_swizzle + [Intrinsic] + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) => Swizzle(vector, indices); + + // Relaxed truncating float-to-int conversions. For NaN or out-of-range inputs the result is + // implementation-defined; the saturating PackedSimd.ConvertToInt32Saturate / ConvertToUInt32Saturate + // overloads provide deterministic semantics. + + /// i32x4.relaxed_trunc_f32x4_s + [Intrinsic] + public static Vector128 ConvertToInt32(Vector128 value) => ConvertToInt32(value); + /// i32x4.relaxed_trunc_f32x4_u + [Intrinsic] + public static Vector128 ConvertToUInt32(Vector128 value) => ConvertToUInt32(value); + /// i32x4.relaxed_trunc_f64x2_s_zero + [Intrinsic] + public static Vector128 ConvertToInt32(Vector128 value) => ConvertToInt32(value); + /// i32x4.relaxed_trunc_f64x2_u_zero + [Intrinsic] + public static Vector128 ConvertToUInt32(Vector128 value) => ConvertToUInt32(value); + + // Relaxed fused multiply-add. Whether the intermediate product is rounded before the add + // (and whether the underlying instruction is a true fused FMA) is implementation-defined. + + /// f32x4.relaxed_madd + [Intrinsic] + public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) => MultiplyAdd(a, b, c); + /// f64x2.relaxed_madd + [Intrinsic] + public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) => MultiplyAdd(a, b, c); + + /// f32x4.relaxed_nmadd + [Intrinsic] + public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddNegated(a, b, c); + /// f64x2.relaxed_nmadd + [Intrinsic] + public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddNegated(a, b, c); + + // Relaxed lane select. The mask is interpreted per-byte/word; lanes where the mask bit is + // neither all-ones nor all-zeros produce implementation-defined results. For deterministic + // selection use Vector128.ConditionalSelect. + + /// i8x16.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i8x16.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i16x8.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i16x8.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i32x4.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i32x4.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i64x2.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i64x2.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + + // Relaxed min/max. NaN handling and sign-of-zero handling are implementation-defined. + // For IEEE-compliant min/max use PackedSimd.Min/Max; for pseudo-min/max (one-sided NaN + // propagation) use PackedSimd.PseudoMin/PseudoMax. + + /// f32x4.relaxed_min + [Intrinsic] + public static Vector128 Min(Vector128 left, Vector128 right) => Min(left, right); + /// f32x4.relaxed_max + [Intrinsic] + public static Vector128 Max(Vector128 left, Vector128 right) => Max(left, right); + /// f64x2.relaxed_min + [Intrinsic] + public static Vector128 Min(Vector128 left, Vector128 right) => Min(left, right); + /// f64x2.relaxed_max + [Intrinsic] + public static Vector128 Max(Vector128 left, Vector128 right) => Max(left, right); + + // Relaxed Q15 multiply with rounding. Differs from PackedSimd.MultiplyRoundedSaturateQ15 + // (i16x8.q15mulr_sat_s) in that the multiplication of INT16_MIN by INT16_MIN produces an + // implementation-defined value (typically INT16_MIN unsaturated on x86). + + /// i16x8.relaxed_q15mulr_s + [Intrinsic] + public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) => MultiplyRoundedQ15(left, right); + + // Relaxed dot products for mixed unsigned-by-signed bytes. The second operand is interpreted + // as signed but its lanes must be in the range [-64, 127] (the "i7" constraint); lanes whose + // value falls outside this range produce implementation-defined results. + + /// i16x8.relaxed_dot_i8x16_i7x16_s — multiplies adjacent (byte, sbyte) pairs and sums each pair into a signed 16-bit lane. + [Intrinsic] + public static Vector128 DotProduct(Vector128 left, Vector128 right) => DotProduct(left, right); + + /// i32x4.relaxed_dot_i8x16_i7x16_add_s — multiplies four adjacent (byte, sbyte) pairs, sums them with a signed 32-bit accumulator, and returns the result. + [Intrinsic] + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) => DotProductAdd(left, right, accumulator); + } +} diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index 81a28cd0bddcc5..088be622da4510 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -11904,4 +11904,34 @@ public abstract partial class PackedSimd public static Vector128 ZeroExtendWideningUpper(Vector128 value) { throw null; } public static Vector128 ZeroExtendWideningUpper(Vector128 value) { throw null; } } + [CLSCompliant(false)] + public abstract partial class RelaxedSimd + { + public static bool IsSupported { get { throw null; } } + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) { throw null; } + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) { throw null; } + public static Vector128 ConvertToInt32(Vector128 value) { throw null; } + public static Vector128 ConvertToUInt32(Vector128 value) { throw null; } + public static Vector128 ConvertToInt32(Vector128 value) { throw null; } + public static Vector128 ConvertToUInt32(Vector128 value) { throw null; } + public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 Min(Vector128 left, Vector128 right) { throw null; } + public static Vector128 Max(Vector128 left, Vector128 right) { throw null; } + public static Vector128 Min(Vector128 left, Vector128 right) { throw null; } + public static Vector128 Max(Vector128 left, Vector128 right) { throw null; } + public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) { throw null; } + public static Vector128 DotProduct(Vector128 left, Vector128 right) { throw null; } + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) { throw null; } + } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj index 5d98f157e174a3..2a552224dfd797 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj +++ b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs new file mode 100644 index 00000000000000..4b3e48a356613f --- /dev/null +++ b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Wasm; +using Xunit; + +namespace System.Runtime.Intrinsics.Wasm.Tests +{ + [PlatformSpecific(TestPlatforms.Browser)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/123011", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))] + public sealed class RelaxedSimdTests + { + [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(RelaxedSimd))] + public unsafe void RelaxedSimdIsSupportedReflects() + { + MethodInfo methodInfo = typeof(RelaxedSimd).GetMethod("get_IsSupported"); + Assert.Equal(RelaxedSimd.IsSupported, methodInfo.Invoke(null, null)); + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void DotProductByteSByteMatchesScalar() + { + // For inputs where each (byte, sbyte) product fits in int16 (which the i7 constraint + // on the second operand guarantees) the relaxed dot product equals the scalar pairwise + // multiply-add. We use small positive sbytes so the i7 contract holds. + var u = Vector128.Create((byte)1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + var s = Vector128.Create((sbyte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); + + Vector128 actual = RelaxedSimd.DotProduct(u, s); + + for (int i = 0; i < 8; i++) + { + short expected = (short)(u[2 * i] * s[2 * i] + u[2 * i + 1] * s[2 * i + 1]); + Assert.Equal(expected, actual[i]); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void DotProductAddByteSByteMatchesScalar() + { + var u = Vector128.Create((byte)1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + var s = Vector128.Create((sbyte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); + var acc = Vector128.Create(100, 200, 300, 400); + + Vector128 actual = RelaxedSimd.DotProductAdd(u, s, acc); + + for (int i = 0; i < 4; i++) + { + int expected = acc[i]; + for (int j = 0; j < 4; j++) + expected += u[4 * i + j] * s[4 * i + j]; + Assert.Equal(expected, actual[i]); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void MultiplyAddFloatMatchesScalarApproximately() + { + // Relaxed FMA may or may not round the intermediate product; verify the result is at + // most one ULP from the unfused result. + var a = Vector128.Create(1.5f, 2.25f, -3.125f, 4.0f); + var b = Vector128.Create(2.0f, -1.5f, 0.5f, 6.25f); + var c = Vector128.Create(0.5f, 1.0f, -0.25f, -2.0f); + + Vector128 actual = RelaxedSimd.MultiplyAdd(a, b, c); + Vector128 unfused = (a * b) + c; + + for (int i = 0; i < 4; i++) + { + Assert.True(Math.Abs(actual[i] - unfused[i]) <= Math.Max(Math.Abs(unfused[i]), 1.0f) * float.Epsilon * 8, + $"lane {i}: relaxed FMA {actual[i]} differs from unfused {unfused[i]} by more than 8 ULP"); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void LaneSelectAllOnesAllZerosBehavesLikeConditionalSelect() + { + // For mask lanes that are all-ones or all-zeros the relaxed lane select must match + // the deterministic semantics. + var left = Vector128.Create((byte)1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + var right = Vector128.Create((byte)17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32); + var mask = Vector128.Create((byte)0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00); + + Vector128 actual = RelaxedSimd.LaneSelect(left, right, mask); + Vector128 expected = Vector128.ConditionalSelect(mask, left, right); + + Assert.Equal(expected, actual); + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void SwizzleInRangeMatchesVector128Shuffle() + { + // For index lanes in [0, 16) the relaxed swizzle must agree with Vector128.Shuffle. + var v = Vector128.Create((byte)10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160); + var idx = Vector128.Create((byte)15, 0, 14, 1, 13, 2, 12, 3, 11, 4, 10, 5, 9, 6, 8, 7); + + Vector128 actual = RelaxedSimd.Swizzle(v, idx); + Vector128 expected = Vector128.Shuffle(v, idx); + + Assert.Equal(expected, actual); + } + } +} From 237fe25571d6c1d13914b183c76405f3375c1a20 Mon Sep 17 00:00:00 2001 From: lewing Date: Thu, 25 Jun 2026 10:40:30 -0500 Subject: [PATCH 02/31] Rename RelaxedSimd FMA methods to MultiplyAdd{,Negated}Estimate 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> --- .../Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs | 8 ++++---- .../src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs | 8 ++++---- .../ref/System.Runtime.Intrinsics.cs | 8 ++++---- .../tests/Wasm/RelaxedSimdTests.cs | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs index 3266fcdc2f228f..9b00843d5ccbe4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs @@ -21,11 +21,11 @@ public abstract class RelaxedSimd public static Vector128 ConvertToInt32(Vector128 value) { throw new PlatformNotSupportedException(); } public static Vector128 ConvertToUInt32(Vector128 value) { throw new PlatformNotSupportedException(); } - public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } - public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } - public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } - public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs index 603e215032fe11..caaded6ff3712f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs @@ -60,17 +60,17 @@ public abstract class RelaxedSimd /// f32x4.relaxed_madd [Intrinsic] - public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) => MultiplyAdd(a, b, c); + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddEstimate(a, b, c); /// f64x2.relaxed_madd [Intrinsic] - public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) => MultiplyAdd(a, b, c); + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddEstimate(a, b, c); /// f32x4.relaxed_nmadd [Intrinsic] - public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddNegated(a, b, c); + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddNegatedEstimate(a, b, c); /// f64x2.relaxed_nmadd [Intrinsic] - public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddNegated(a, b, c); + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddNegatedEstimate(a, b, c); // Relaxed lane select. The mask is interpreted per-byte/word; lanes where the mask bit is // neither all-ones nor all-zeros produce implementation-defined results. For deterministic diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index 088be622da4510..ebb54d748834e5 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -11914,10 +11914,10 @@ public abstract partial class RelaxedSimd public static Vector128 ConvertToUInt32(Vector128 value) { throw null; } public static Vector128 ConvertToInt32(Vector128 value) { throw null; } public static Vector128 ConvertToUInt32(Vector128 value) { throw null; } - public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) { throw null; } - public static Vector128 MultiplyAdd(Vector128 a, Vector128 b, Vector128 c) { throw null; } - public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) { throw null; } - public static Vector128 MultiplyAddNegated(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) { throw null; } public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs index 4b3e48a356613f..c497f1535bf6d9 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs @@ -66,7 +66,7 @@ public unsafe void MultiplyAddFloatMatchesScalarApproximately() var b = Vector128.Create(2.0f, -1.5f, 0.5f, 6.25f); var c = Vector128.Create(0.5f, 1.0f, -0.25f, -2.0f); - Vector128 actual = RelaxedSimd.MultiplyAdd(a, b, c); + Vector128 actual = RelaxedSimd.MultiplyAddEstimate(a, b, c); Vector128 unfused = (a * b) + c; for (int i = 0; i < 4; i++) From ced1d8c4e0d36c194593bae387261a17242501db Mon Sep 17 00:00:00 2001 From: lewing Date: Thu, 25 Jun 2026 10:40:41 -0500 Subject: [PATCH 03/31] Add WasmEnableRelaxedSimd MSBuild property and ILLink stubs 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> --- eng/testing/tests.wasm.targets | 2 ++ src/mono/browser/build/BrowserWasmApp.targets | 5 +++++ .../build/ILLink.Substitutions.NoWasmRelaxedSimd.xml | 7 +++++++ .../browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml | 7 +++++++ src/mono/wasm/build/WasmApp.Common.targets | 6 ++++++ 5 files changed, 27 insertions(+) create mode 100644 src/mono/browser/build/ILLink.Substitutions.NoWasmRelaxedSimd.xml create mode 100644 src/mono/browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index 24b79d0334c443..e4dd0c007a2ded 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -69,6 +69,8 @@ --> <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false'">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.WasmIntrinsics.xml" <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' == 'false'">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.NoWasmIntrinsics.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.WasmRelaxedSimd.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' != 'true'">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.NoWasmRelaxedSimd.xml" diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index 3937ca4624c841..b8896a43ff35ee 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -32,6 +32,8 @@ <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.WasmIntrinsics.xml" <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' == 'false'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.NoWasmIntrinsics.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.WasmRelaxedSimd.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' != 'true'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.NoWasmRelaxedSimd.xml" true emcc @@ -39,6 +41,7 @@ <_WasmDefaultFlags Condition="'$(WasmEnableExceptionHandling)' == 'false'">-fexceptions <_WasmDefaultFlags Condition="'$(WasmEnableExceptionHandling)' != 'false'">-fwasm-exceptions -s WASM_LEGACY_EXCEPTIONS=1 <_WasmDefaultFlags Condition="'$(WasmEnableSIMD)' == 'true'">$(_WasmDefaultFlags) -msimd128 + <_WasmDefaultFlags Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_WasmDefaultFlags) -mrelaxed-simd <_WasmOutputFileName Condition="'$(WasmSingleFileBundle)' != 'true'">dotnet.native.wasm @@ -250,6 +253,8 @@ + + diff --git a/src/mono/browser/build/ILLink.Substitutions.NoWasmRelaxedSimd.xml b/src/mono/browser/build/ILLink.Substitutions.NoWasmRelaxedSimd.xml new file mode 100644 index 00000000000000..3c512185aa297e --- /dev/null +++ b/src/mono/browser/build/ILLink.Substitutions.NoWasmRelaxedSimd.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/mono/browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml b/src/mono/browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml new file mode 100644 index 00000000000000..5e392a051c943f --- /dev/null +++ b/src/mono/browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/mono/wasm/build/WasmApp.Common.targets b/src/mono/wasm/build/WasmApp.Common.targets index d154d54a5b5104..b6d0a751b07d1e 100644 --- a/src/mono/wasm/build/WasmApp.Common.targets +++ b/src/mono/wasm/build/WasmApp.Common.targets @@ -79,6 +79,7 @@ - $(WasmAotProfilePath) - Path to an AOT profile file. - $(WasmEnableExceptionHandling) - Enable support for the WASM post MVP Exception Handling runtime extension. - $(WasmEnableSIMD) - Enable support for the WASM post MVP SIMD runtime extension. + - $(WasmEnableRelaxedSimd) - Enable support for the WASM Relaxed SIMD extension. Requires $(WasmEnableSIMD) == true. - $(WasmEnableWebcil) - Enable conversion of assembly .dlls to Webcil wrapped in .wasm (default: true) - $(WasmIncludeFullIcuData) - Loads full ICU data (icudt.dat). Defaults to false. Only applicable when InvariantGlobalization=false. - $(WasmIcuDataFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization=false and WasmIncludeFullIcuData=false. @@ -187,6 +188,7 @@ false false + false false false @@ -465,8 +467,11 @@ + + @@ -632,6 +637,7 @@ + From 407db7168669484a53836e48cfabe01155632cc9 Mon Sep 17 00:00:00 2001 From: lewing Date: Thu, 25 Jun 2026 11:04:04 -0500 Subject: [PATCH 04/31] Declare LLVM relaxed-SIMD intrinsics in Mono's intrinsic table 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> --- src/mono/mono/mini/llvm-intrinsics.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/mono/mono/mini/llvm-intrinsics.h b/src/mono/mono/mini/llvm-intrinsics.h index 0b00d40b1e2485..aa1c3836037c7b 100644 --- a/src/mono/mono/mini/llvm-intrinsics.h +++ b/src/mono/mono/mini/llvm-intrinsics.h @@ -325,6 +325,19 @@ INTRINS_OVR(WASM_PMAX_V2, fabs, Generic, sse_r8_t) INTRINS(WASM_Q15MULR_SAT_SIGNED, wasm_q15mulr_sat_signed, Wasm) INTRINS(WASM_SHUFFLE, wasm_shuffle, Wasm) INTRINS(WASM_SWIZZLE, wasm_swizzle, Wasm) +INTRINS(WASM_RELAXED_SWIZZLE, wasm_relaxed_swizzle, Wasm) +INTRINS(WASM_RELAXED_TRUNC_SIGNED, wasm_relaxed_trunc_signed, Wasm) +INTRINS(WASM_RELAXED_TRUNC_UNSIGNED, wasm_relaxed_trunc_unsigned, Wasm) +INTRINS(WASM_RELAXED_TRUNC_SIGNED_ZERO, wasm_relaxed_trunc_signed_zero, Wasm) +INTRINS(WASM_RELAXED_TRUNC_UNSIGNED_ZERO, wasm_relaxed_trunc_unsigned_zero, Wasm) +INTRINS_OVR_TAG(WASM_RELAXED_MADD, wasm_relaxed_madd, Wasm, V128 | R4 | R8) +INTRINS_OVR_TAG(WASM_RELAXED_NMADD, wasm_relaxed_nmadd, Wasm, V128 | R4 | R8) +INTRINS_OVR_TAG(WASM_RELAXED_LANESELECT, wasm_relaxed_laneselect, Wasm, V128 | I1 | I2 | I4 | I8) +INTRINS_OVR_TAG(WASM_RELAXED_MIN, wasm_relaxed_min, Wasm, V128 | R4 | R8) +INTRINS_OVR_TAG(WASM_RELAXED_MAX, wasm_relaxed_max, Wasm, V128 | R4 | R8) +INTRINS(WASM_RELAXED_Q15MULR_SIGNED, wasm_relaxed_q15mulr_signed, Wasm) +INTRINS(WASM_RELAXED_DOT_I8X16_I7X16_SIGNED, wasm_relaxed_dot_i8x16_i7x16_signed, Wasm) +INTRINS(WASM_RELAXED_DOT_I8X16_I7X16_ADD_SIGNED, wasm_relaxed_dot_i8x16_i7x16_add_signed, Wasm) INTRINS(WASM_GET_EXCEPTION, wasm_get_exception, Wasm) INTRINS(WASM_GET_EHSELECTOR, wasm_get_ehselector, Wasm) INTRINS(WASM_RETHROW, wasm_rethrow, Wasm) From 7a05324383b7af2125aa9662eb3739918e63195b Mon Sep 17 00:00:00 2001 From: lewing Date: Thu, 25 Jun 2026 11:25:09 -0500 Subject: [PATCH 05/31] Wire RelaxedSimd into Mono's SIMD intrinsic recognizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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> --- src/mono/mono/mini/aot-compiler.c | 2 ++ src/mono/mono/mini/mini-llvm.c | 47 +++++++++++++++------------- src/mono/mono/mini/mini.h | 5 +-- src/mono/mono/mini/simd-intrinsics.c | 40 +++++++++++++++++++++-- src/mono/mono/mini/simd-methods.h | 4 +++ 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index 465062d04c7f9d..d98a7730aef02d 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -8910,6 +8910,8 @@ parse_cpu_features (const gchar *attr) // MONO_CPU_WASM_BASE is unconditionally set in mini_get_cpu_features. if (!strcmp (attr + prefix, "simd")) feature = MONO_CPU_WASM_SIMD; + else if (!strcmp (attr + prefix, "relaxed-simd")) + feature = MONO_CPU_WASM_RELAXED_SIMD; #else (void)prefix; // unused #endif diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index 324aca3ed4eda2..88f0c83e80a7bc 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -8276,6 +8276,30 @@ MONO_RESTORE_WARNING values [ins->dreg] = call_overloaded_intrins (ctx, iid, ovr_tag, args, ""); break; } + case OP_XOP_X_X_X_X: { + IntrinsicId id = (IntrinsicId)ins->inst_c0; +#if defined(TARGET_ARM64) + gboolean getLowerElement = FALSE; + int arg_idx = -1; + switch (id) { + case INTRINS_AARCH64_SHA1C: + case INTRINS_AARCH64_SHA1M: + case INTRINS_AARCH64_SHA1P: + getLowerElement = TRUE; + arg_idx = 1; + break; + default: + break; + } +#endif + LLVMValueRef args [] = { lhs, rhs, arg3 }; +#if defined(TARGET_ARM64) + if (getLowerElement) + args [arg_idx] = LLVMBuildExtractElement (ctx->builder, args [arg_idx], const_int32 (0), ""); +#endif + values [ins->dreg] = call_intrins (ctx, id, args, ""); + break; + } case OP_XOP_OVR_BYSCALAR_X_X_X: { IntrinsicId iid = (IntrinsicId) ins->inst_c0; llvm_ovr_tag_t ovr_tag = ovr_tag_from_mono_vector_class (ins->klass); @@ -10923,26 +10947,6 @@ MONO_RESTORE_WARNING values [ins->dreg] = convert (ctx, values [ins->dreg], LLVMVectorType (LLVMInt64Type (), 2)); break; } - case OP_XOP_X_X_X_X: { - IntrinsicId id = (IntrinsicId)ins->inst_c0; - gboolean getLowerElement = FALSE; - int arg_idx = -1; - switch (id) { - case INTRINS_AARCH64_SHA1C: - case INTRINS_AARCH64_SHA1M: - case INTRINS_AARCH64_SHA1P: - getLowerElement = TRUE; - arg_idx = 1; - break; - default: - break; - } - LLVMValueRef args [] = { lhs, rhs, arg3 }; - if (getLowerElement) - args [arg_idx] = LLVMBuildExtractElement (ctx->builder, args [arg_idx], const_int32 (0), ""); - values [ins->dreg] = call_intrins (ctx, id, args, ""); - break; - } case OP_XOP_X_X: { IntrinsicId id = (IntrinsicId)ins->inst_c0; LLVMTypeRef ret_t = simd_class_to_llvm_type (ctx, ins->klass); @@ -15509,7 +15513,8 @@ MonoCPUFeatures mono_llvm_get_cpu_features (void) { "dotprod", MONO_CPU_ARM64_DP }, #endif #if defined(TARGET_WASM) - { "simd", MONO_CPU_WASM_SIMD }, + { "simd", MONO_CPU_WASM_SIMD }, + { "relaxed-simd", MONO_CPU_WASM_RELAXED_SIMD }, #endif // flags_map cannot be zero length in MSVC, so add useless dummy entry for arm32 #if defined(TARGET_ARM) && defined(HOST_WIN32) diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index 65b38b751e8c45..e5d17c93429d55 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -2947,8 +2947,9 @@ typedef enum { | MONO_CPU_X86_AES | MONO_CPU_X86_POPCNT | MONO_CPU_X86_FMA, #endif #ifdef TARGET_WASM - MONO_CPU_WASM_BASE = 1 << 1, - MONO_CPU_WASM_SIMD = 1 << 2, + MONO_CPU_WASM_BASE = 1 << 1, + MONO_CPU_WASM_SIMD = 1 << 2, + MONO_CPU_WASM_RELAXED_SIMD = 1 << 3, #endif #ifdef TARGET_ARM64 MONO_CPU_ARM64_BASE = 1 << 1, diff --git a/src/mono/mono/mini/simd-intrinsics.c b/src/mono/mono/mini/simd-intrinsics.c index 21295764fad806..306741ea1e9f2d 100644 --- a/src/mono/mono/mini/simd-intrinsics.c +++ b/src/mono/mono/mini/simd-intrinsics.c @@ -6495,9 +6495,25 @@ static SimdIntrinsic packedsimd_methods [] = { {SN_get_IsSupported}, }; +static SimdIntrinsic relaxedsimd_methods [] = { + {SN_ConvertToInt32}, + {SN_ConvertToUInt32}, + {SN_DotProduct, OP_XOP_X_X_X, INTRINS_WASM_RELAXED_DOT_I8X16_I7X16_SIGNED}, + {SN_DotProductAdd, OP_XOP_X_X_X_X, INTRINS_WASM_RELAXED_DOT_I8X16_I7X16_ADD_SIGNED}, + {SN_LaneSelect, OP_XOP_OVR_X_X_X_X, INTRINS_WASM_RELAXED_LANESELECT}, + {SN_Max, OP_XOP_OVR_X_X_X, INTRINS_WASM_RELAXED_MAX}, + {SN_Min, OP_XOP_OVR_X_X_X, INTRINS_WASM_RELAXED_MIN}, + {SN_MultiplyAddEstimate, OP_XOP_OVR_X_X_X_X, INTRINS_WASM_RELAXED_MADD}, + {SN_MultiplyAddNegatedEstimate, OP_XOP_OVR_X_X_X_X, INTRINS_WASM_RELAXED_NMADD}, + {SN_MultiplyRoundedQ15, OP_XOP_X_X_X, INTRINS_WASM_RELAXED_Q15MULR_SIGNED}, + {SN_Swizzle, OP_XOP_X_X_X, INTRINS_WASM_RELAXED_SWIZZLE}, + {SN_get_IsSupported}, +}; + static const IntrinGroup supported_wasm_intrinsics [] = { - { "PackedSimd", MONO_CPU_WASM_SIMD, packedsimd_methods, sizeof (packedsimd_methods) }, - { "WasmBase", MONO_CPU_WASM_BASE, wasmbase_methods, sizeof (wasmbase_methods) }, + { "PackedSimd", MONO_CPU_WASM_SIMD, packedsimd_methods, sizeof (packedsimd_methods) }, + { "RelaxedSimd", MONO_CPU_WASM_RELAXED_SIMD, relaxedsimd_methods, sizeof (relaxedsimd_methods) }, + { "WasmBase", MONO_CPU_WASM_BASE, wasmbase_methods, sizeof (wasmbase_methods) }, }; static const IntrinGroup supported_wasm_common_intrinsics [] = { @@ -6868,6 +6884,26 @@ emit_wasm_supported_intrinsics ( return emit_simd_ins_for_sig (cfg, klass, op, c0, arg0_type, fsig, args); } + if (feature == MONO_CPU_WASM_RELAXED_SIMD) { + uint16_t op = info->default_op; + uint16_t c0 = info->default_instc0; + + switch (id) { + case SN_ConvertToInt32: + op = OP_XOP_X_X; + c0 = arg0_type == MONO_TYPE_R8 ? INTRINS_WASM_RELAXED_TRUNC_SIGNED_ZERO : INTRINS_WASM_RELAXED_TRUNC_SIGNED; + break; + case SN_ConvertToUInt32: + op = OP_XOP_X_X; + c0 = arg0_type == MONO_TYPE_R8 ? INTRINS_WASM_RELAXED_TRUNC_UNSIGNED_ZERO : INTRINS_WASM_RELAXED_TRUNC_UNSIGNED; + break; + } + + // default emit path for cases with op set + if (op != 0) + return emit_simd_ins_for_sig (cfg, klass, op, c0, arg0_type, fsig, args); + } + g_assert_not_reached (); return NULL; diff --git a/src/mono/mono/mini/simd-methods.h b/src/mono/mono/mini/simd-methods.h index b696931ce51e92..e94e231a7ed308 100644 --- a/src/mono/mono/mini/simd-methods.h +++ b/src/mono/mono/mini/simd-methods.h @@ -297,6 +297,7 @@ METHOD(TestC) METHOD(TestNotZAndNotC) METHOD(TestZ) METHOD(DotProduct) +METHOD(DotProductAdd) METHOD(MultipleSumAbsoluteDifferences) // Sse42 METHOD(Crc32) @@ -690,6 +691,9 @@ METHOD(ExtractScalar) METHOD(LoadScalarAndInsert) METHOD(LoadScalarAndSplatVector128) METHOD(LoadWideningVector128) +METHOD(LaneSelect) +METHOD(MultiplyAddNegatedEstimate) +METHOD(MultiplyRoundedQ15) METHOD(MultiplyRoundedSaturateQ15) METHOD(PseudoMax) METHOD(PseudoMin) From 51113614adbe4fbcd97d5e85ec55d74cac11bf99 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Thu, 25 Jun 2026 12:40:41 -0500 Subject: [PATCH 06/31] [wasm] Wire RelaxedSimd through the Mono interpreter as a third SIMD 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.) --- .../Directory.Build.props | 1 + src/mono/browser/browser.proj | 1 + src/mono/browser/build/BrowserWasmApp.targets | 8 +- src/mono/browser/runtime/genmintops.py | 3 + src/mono/mono.proj | 3 + src/mono/mono/mini/CMakeLists.txt | 10 +++ .../mono/mini/interp/interp-simd-intrins.def | 67 ++++++++++++++ src/mono/mono/mini/interp/interp-simd.c | 47 ++++++++++ src/mono/mono/mini/interp/transform-simd.c | 87 +++++++++++++++++++ 9 files changed, 224 insertions(+), 3 deletions(-) diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index bd717001f5e476..6044a422dd49f0 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -229,6 +229,7 @@ + diff --git a/src/mono/browser/browser.proj b/src/mono/browser/browser.proj index ea0216294c7247..161fec3d3c4868 100644 --- a/src/mono/browser/browser.proj +++ b/src/mono/browser/browser.proj @@ -351,6 +351,7 @@ $(CMakeBuildRuntimeConfigureCmd) -DNATIVE_BIN_DIR="$(NativeBinDir.TrimEnd('\/').Replace('\','/'))" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128" -DCONFIGURATION_INTERPSIMDTABLES_LIB="simd" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_INTERPSIMDTABLES_LIB="nosimd" + $(CMakeBuildRuntimeConfigureCmd) -DENABLE_WASM_RELAXED_SIMD=0 $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_EH_LIB="eh-wasm" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_EH_LIB="eh-js" diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index b8896a43ff35ee..296cdb7abf202d 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -324,10 +324,12 @@ <_WasmEHLib Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-js.a <_WasmEHLibToExclude Condition="'$(WasmEnableExceptionHandling)' == 'true'">libmono-wasm-eh-js.a <_WasmEHLibToExclude Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-wasm.a - <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true'">libmono-wasm-simd.a + <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-relaxed-simd.a + <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-simd.a <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-nosimd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-simd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true'">libmono-wasm-nosimd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-simd.a;libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-nosimd.a;libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-nosimd.a;libmono-wasm-simd.a <_EmccExportedLibraryFunction>"[@(EmccExportedLibraryFunction -> '%27%(Identity)%27', ',')]" <_EmccExportedRuntimeMethods>"[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]" <_EmccExportedFunctions>@(EmccExportedFunction -> '%(Identity)',',') diff --git a/src/mono/browser/runtime/genmintops.py b/src/mono/browser/runtime/genmintops.py index 33c370690f8c0d..af777d928344cf 100755 --- a/src/mono/browser/runtime/genmintops.py +++ b/src/mono/browser/runtime/genmintops.py @@ -49,6 +49,9 @@ "INTERP_WASM_SIMD_INTRINSIC_V_VI": simd_values_2, "INTERP_WASM_SIMD_INTRINSIC_V_VVV": simd_values_3, "INTERP_WASM_SIMD_INTRINSIC_V_C3": simd_values_3, + "INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V": simd_values_1, + "INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV": simd_values_2, + "INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV": simd_values_3, } for line in simd_header_lines: diff --git a/src/mono/mono.proj b/src/mono/mono.proj index e936acfb2ce67f..bc3733d8209bf1 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -1002,6 +1002,9 @@ <_MonoRuntimeArtifacts Condition="('$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true') and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-wasm-simd.a"> $(RuntimeBinDir)libmono-wasm-simd.a + <_MonoRuntimeArtifacts Condition="('$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true') and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-wasm-relaxed-simd.a"> + $(RuntimeBinDir)libmono-wasm-relaxed-simd.a + <_MonoRuntimeArtifacts Condition="('$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true') and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-wasm-nosimd.a"> $(RuntimeBinDir)libmono-wasm-nosimd.a diff --git a/src/mono/mono/mini/CMakeLists.txt b/src/mono/mono/mini/CMakeLists.txt index 3b95c60c1f45da..2d8582884e60c2 100644 --- a/src/mono/mono/mini/CMakeLists.txt +++ b/src/mono/mono/mini/CMakeLists.txt @@ -486,6 +486,16 @@ if(HOST_BROWSER OR HOST_WASI) target_link_libraries (mono-wasm-simd PRIVATE monoapi eglib_api) set_target_properties(mono-wasm-simd PROPERTIES COMPILE_FLAGS "-msimd128") install(TARGETS mono-wasm-simd LIBRARY) + + # Relaxed-SIMD variant: same translation unit as mono-wasm-simd but compiled with + # -mrelaxed-simd and HOST_WASM_RELAXED_SIMD=1, so the relaxed-simd intrinsic + # wrappers in interp-simd.c expand to real emscripten builtins instead of stubs. + # The SDK ships both libs; the app build picks one based on WasmEnableRelaxedSimd. + add_library(mono-wasm-relaxed-simd STATIC interp/interp-simd.c) + target_link_libraries (mono-wasm-relaxed-simd PRIVATE monoapi eglib_api) + set_target_properties(mono-wasm-relaxed-simd PROPERTIES COMPILE_FLAGS "-msimd128 -mrelaxed-simd") + target_compile_definitions(mono-wasm-relaxed-simd PRIVATE HOST_WASM_RELAXED_SIMD=1) + install(TARGETS mono-wasm-relaxed-simd LIBRARY) endif() if(HOST_BROWSER OR HOST_WASI OR TARGET_WASM) diff --git a/src/mono/mono/mini/interp/interp-simd-intrins.def b/src/mono/mono/mini/interp/interp-simd-intrins.def index 4fefb8e395445b..5ddc9173306f92 100644 --- a/src/mono/mono/mini/interp/interp-simd-intrins.def +++ b/src/mono/mono/mini/interp/interp-simd-intrins.def @@ -38,6 +38,47 @@ #define INTERP_WASM_SIMD_INTRINSIC_V_C3(name, arg1, c_function, wasm_opcode) #endif // defined(HOST_BROWSER) || defined(HOST_WASI) +// Relaxed-SIMD macro family. The .def file uses INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_{V,VV,VVV} +// for the relaxed-simd entries below. There are two flavors of the runtime SIMD library: +// * libmono-wasm-simd.a is built without -mrelaxed-simd / without HOST_WASM_RELAXED_SIMD, +// so the relaxed entries are routed to a per-arity stub function (asserts at runtime) +// and their wasm-opcode table slots are 0 (the jiterpreter uses the fallback path, which +// in turn calls the stub — guarded by RelaxedSimd.IsSupported reporting false). +// * libmono-wasm-relaxed-simd.a is built with -mrelaxed-simd and HOST_WASM_RELAXED_SIMD=1, +// so the relaxed entries expand to the regular INTERP_WASM_SIMD_INTRINSIC_V_* path +// producing real emscripten intrinsic wrappers and real wasm opcodes. +// mono-ee-interp is always built with HOST_WASM_RELAXED_SIMD=1 so the MintSIMDOpsPP/PPP/PPPP +// enums in mintops.h and the unsorted_packedsimd_intrinsic_infos[] lookup table in +// transform-simd.c always include the relaxed entries at stable positions across both +// library flavors. +#if defined(HOST_BROWSER) || defined(HOST_WASI) +#ifdef HOST_WASM_RELAXED_SIMD +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V INTERP_WASM_SIMD_INTRINSIC_V_V +#endif +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV INTERP_WASM_SIMD_INTRINSIC_V_VV +#endif +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV INTERP_WASM_SIMD_INTRINSIC_V_VVV +#endif +#else +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V(name, arg1, c_intrinsic, wasm_opcode) INTERP_SIMD_INTRINSIC_P_P(INTERP_SIMD_INTRINSIC_ ## name ## arg1, _mono_interp_simd_relaxed_unsupported_1, 0) +#endif +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV(name, arg1, c_intrinsic, wasm_opcode) INTERP_SIMD_INTRINSIC_P_PP(INTERP_SIMD_INTRINSIC_ ## name ## arg1, _mono_interp_simd_relaxed_unsupported_2, 0) +#endif +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV(name, arg1, c_intrinsic, wasm_opcode) INTERP_SIMD_INTRINSIC_P_PPP(INTERP_SIMD_INTRINSIC_ ## name ## arg1, _mono_interp_simd_relaxed_unsupported_3, 0) +#endif +#endif // HOST_WASM_RELAXED_SIMD +#else +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V(name, arg1, c_intrinsic, wasm_opcode) +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV(name, arg1, c_intrinsic, wasm_opcode) +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV(name, arg1, c_intrinsic, wasm_opcode) +#endif // defined(HOST_BROWSER) || defined(HOST_WASI) + // The third argument is the wasm opcode that corresponds to this simd intrinsic, if any. // Specify -1 if there is no exact 1:1 mapping (the opcode can still be implemented manually in the jiterpreter.) @@ -391,3 +432,29 @@ INTERP_WASM_SIMD_INTRINSIC_V_C3 (StoreSelectedScalar, X1, interp_packedsimd_stor INTERP_WASM_SIMD_INTRINSIC_V_C3 (StoreSelectedScalar, X2, interp_packedsimd_store16_lane, 0x59) INTERP_WASM_SIMD_INTRINSIC_V_C3 (StoreSelectedScalar, X4, interp_packedsimd_store32_lane, 0x5a) INTERP_WASM_SIMD_INTRINSIC_V_C3 (StoreSelectedScalar, X8, interp_packedsimd_store64_lane, 0x5b) + +// Relaxed-SIMD (https://github.com/WebAssembly/relaxed-simd). The method names below are +// prefixed with "Relaxed" to avoid name collisions with PackedSimd entries in the shared +// PackedSimdIntrinsicInfo lookup table; emit_sri_relaxedsimd in transform-simd.c prepends +// "Relaxed" before looking them up. See the macro definitions at the top of this file for +// how the regular vs relaxed-simd library variants route these entries differently. +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedSwizzle, D1, wasm_i8x16_relaxed_swizzle, 0x100) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V (RelaxedConvertToInt32, R4, wasm_i32x4_relaxed_trunc_f32x4, 0x101) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V (RelaxedConvertToUInt32, R4, wasm_u32x4_relaxed_trunc_f32x4, 0x102) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V (RelaxedConvertToInt32, R8, wasm_i32x4_relaxed_trunc_f64x2_zero, 0x103) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V (RelaxedConvertToUInt32, R8, wasm_u32x4_relaxed_trunc_f64x2_zero, 0x104) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedMultiplyAddEstimate, R4, wasm_f32x4_relaxed_madd, 0x105) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedMultiplyAddNegatedEstimate, R4, wasm_f32x4_relaxed_nmadd, 0x106) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedMultiplyAddEstimate, R8, wasm_f64x2_relaxed_madd, 0x107) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedMultiplyAddNegatedEstimate, R8, wasm_f64x2_relaxed_nmadd, 0x108) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedLaneSelect, D1, wasm_i8x16_relaxed_laneselect, 0x109) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedLaneSelect, D2, wasm_i16x8_relaxed_laneselect, 0x10a) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedLaneSelect, D4, wasm_i32x4_relaxed_laneselect, 0x10b) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedLaneSelect, D8, wasm_i64x2_relaxed_laneselect, 0x10c) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMin, R4, wasm_f32x4_relaxed_min, 0x10d) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMax, R4, wasm_f32x4_relaxed_max, 0x10e) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMin, R8, wasm_f64x2_relaxed_min, 0x10f) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMax, R8, wasm_f64x2_relaxed_max, 0x110) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMultiplyRoundedQ15, I2, wasm_i16x8_relaxed_q15mulr, 0x111) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedDotProduct, U1, wasm_i16x8_relaxed_dot_i8x16_i7x16, 0x112) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedDotProductAdd, U1, wasm_i32x4_relaxed_dot_i8x16_i7x16_add, 0x113) diff --git a/src/mono/mono/mini/interp/interp-simd.c b/src/mono/mono/mini/interp/interp-simd.c index b779813fa745d3..e77e14626530e2 100644 --- a/src/mono/mono/mini/interp/interp-simd.c +++ b/src/mono/mono/mini/interp/interp-simd.c @@ -623,6 +623,36 @@ _interp_wasm_simd_assert_not_reached (v128_t lhs, v128_t rhs) { g_assert_not_reached (); } +// Per-arity stubs used by the regular (non-relaxed) libmono-wasm-simd.a as the +// function-pointer-table targets for the RelaxedSimd intrinsic entries. The relaxed +// variant of the library (libmono-wasm-relaxed-simd.a) is compiled with +// HOST_WASM_RELAXED_SIMD=1 and uses real intrinsic wrappers instead. +// +// These are never called when RelaxedSimd.IsSupported reports false: the recognizer in +// transform-simd.c keys the IsSupported answer off mono_interp_relaxed_simd_supported. +#ifndef HOST_WASM_RELAXED_SIMD +static void +_mono_interp_simd_relaxed_unsupported_1 (gpointer res, gpointer v1) { + g_assert_not_reached (); +} +static void +_mono_interp_simd_relaxed_unsupported_2 (gpointer res, gpointer v1, gpointer v2) { + g_assert_not_reached (); +} +static void +_mono_interp_simd_relaxed_unsupported_3 (gpointer res, gpointer v1, gpointer v2, gpointer v3) { + g_assert_not_reached (); +} +#endif + +// Runtime feature flag queried by transform-simd.c so a single mono-ee-interp.a can be +// linked against either libmono-wasm-simd.a (=0) or libmono-wasm-relaxed-simd.a (=1). +#ifdef HOST_WASM_RELAXED_SIMD +const int mono_interp_relaxed_simd_supported = 1; +#else +const int mono_interp_relaxed_simd_supported = 0; +#endif + #define LANE_COUNT(lane_type) (sizeof(v128_t) / sizeof(lane_type)) // ensure the lane is valid by wrapping it (in AOT it would fail to compile) @@ -893,6 +923,20 @@ _mono_interp_simd_ ## c_intrinsic (gpointer res, gpointer v1, gpointer v2, gpoin #define INTERP_WASM_SIMD_INTRINSIC_V_C2(name, arg1, c_function, wasm_opcode) #define INTERP_WASM_SIMD_INTRINSIC_V_C3(name, arg1, c_function, wasm_opcode) +// Relaxed-SIMD function-body phase: +// * With HOST_WASM_RELAXED_SIMD: route to the regular wrappers (real intrinsic bodies). +// * Without: skip per-entry function generation; the table entries below resolve to +// the per-arity stubs defined above (_mono_interp_simd_relaxed_unsupported_{1,2,3}). +#ifdef HOST_WASM_RELAXED_SIMD +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V INTERP_WASM_SIMD_INTRINSIC_V_V +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV INTERP_WASM_SIMD_INTRINSIC_V_VV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV INTERP_WASM_SIMD_INTRINSIC_V_VVV +#else +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V(name, arg1, c_intrinsic, wasm_opcode) +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV(name, arg1, c_intrinsic, wasm_opcode) +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV(name, arg1, c_intrinsic, wasm_opcode) +#endif + #include "interp-simd-intrins.def" #undef INTERP_WASM_SIMD_INTRINSIC_V_P @@ -904,6 +948,9 @@ _mono_interp_simd_ ## c_intrinsic (gpointer res, gpointer v1, gpointer v2, gpoin #undef INTERP_WASM_SIMD_INTRINSIC_V_C1 #undef INTERP_WASM_SIMD_INTRINSIC_V_C2 #undef INTERP_WASM_SIMD_INTRINSIC_V_C3 +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV // Now generate the wasm opcode tables for the intrinsics diff --git a/src/mono/mono/mini/interp/transform-simd.c b/src/mono/mono/mini/interp/transform-simd.c index b41bfc796d20fd..7c37e2c599abb3 100644 --- a/src/mono/mono/mini/interp/transform-simd.c +++ b/src/mono/mono/mini/interp/transform-simd.c @@ -36,6 +36,14 @@ enum { #define method_name(idx) ((const char*)&method_names + (idx)) static gboolean emit_sri_packedsimd (TransformData *, MonoMethod *, MonoMethodSignature *); +static gboolean emit_sri_relaxedsimd (TransformData *, MonoMethod *, MonoMethodSignature *); + +#if defined(HOST_BROWSER) || defined(HOST_WASI) +// Set by interp-simd.c to 1 in libmono-wasm-relaxed-simd.a and to 0 in libmono-wasm-simd.a / +// libmono-wasm-nosimd.a. The app build picks one library variant via WasmEnableRelaxedSimd, +// so this lets a single mono-ee-interp.a decide RelaxedSimd.IsSupported at runtime. +extern const int mono_interp_relaxed_simd_supported; +#endif static int simd_intrinsic_compare_by_name (const void *key, const void *value) @@ -972,6 +980,17 @@ typedef struct { #define INTERP_WASM_SIMD_INTRINSIC_V_C3(name, arg1, c_intrinsic, wasm_opcode) \ INTRINS_COMMON(name, arg1, c_intrinsic, MINT_SIMD_INTRINS_P_PPP, INTERP_SIMD_INTRINSIC_ ## name ## arg1) +// Relaxed-SIMD: route through the regular WASM_SIMD macros so the entries land in the +// shared unsorted_packedsimd_intrinsic_infos[] lookup table with proper enum ids and +// MINT_SIMD_INTRINS_P_{PP,PPP} dispatch opcodes. emit_sri_relaxedsimd prepends "Relaxed" +// to the method name before looking up the table entry. +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V INTERP_WASM_SIMD_INTRINSIC_V_V +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV INTERP_WASM_SIMD_INTRINSIC_V_VV +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV INTERP_WASM_SIMD_INTRINSIC_V_VVV + static PackedSimdIntrinsicInfo unsorted_packedsimd_intrinsic_infos[] = { #include "interp-simd-intrins.def" }; @@ -984,6 +1003,9 @@ static PackedSimdIntrinsicInfo unsorted_packedsimd_intrinsic_infos[] = { #undef INTERP_WASM_SIMD_INTRINSIC_V_C2 #undef INTERP_WASM_SIMD_INTRINSIC_V_VVV #undef INTERP_WASM_SIMD_INTRINSIC_V_C3 +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV static PackedSimdIntrinsicInfo *sorted_packedsimd_intrinsic_infos; @@ -1320,6 +1342,69 @@ emit_sri_packedsimd (TransformData *td, MonoMethod *cmethod, MonoMethodSignature return TRUE; } +static gboolean +emit_sri_relaxedsimd (TransformData *td, MonoMethod *cmethod, MonoMethodSignature *csignature) +{ + if (csignature->hasthis) + return FALSE; + + MonoClass *vector_klass = NULL; + if (csignature->ret->type == MONO_TYPE_GENERICINST) + vector_klass = mono_class_from_mono_type_internal (csignature->ret); + else if (csignature->param_count && csignature->params [0]->type == MONO_TYPE_GENERICINST) + vector_klass = mono_class_from_mono_type_internal (csignature->params [0]); + + int vector_size = -1; + + // IsSupported is normally constant-folded by the linker substitution + the JIT/AOT + // recognizer in simd-intrinsics.c. The interpreter only reaches it when those layers + // were bypassed (e.g. reflection); answer based on which mono-wasm-simd library + // variant the app build linked against. + if (!strcmp (cmethod->name, "get_IsSupported")) { +#if defined(HOST_BROWSER) || defined(HOST_WASI) + interp_add_ins (td, mono_interp_relaxed_simd_supported ? MINT_LDC_I4_1 : MINT_LDC_I4_0); +#else + interp_add_ins (td, MINT_LDC_I4_0); +#endif + vector_klass = mono_class_from_mono_type_internal (csignature->ret); + goto opcode_added; + } + +#if defined(HOST_BROWSER) || defined(HOST_WASI) + if (!mono_interp_relaxed_simd_supported) + return FALSE; + + if (!vector_klass) + return FALSE; + + MonoTypeEnum atype; + int arg_size, scalar_arg; + if (!get_common_simd_info (vector_klass, csignature, &atype, &vector_size, &arg_size, &scalar_arg)) + return FALSE; + + // The RelaxedSimd.* method names overlap with PackedSimd.* (Swizzle, Min, Max, + // MultiplyAddEstimate, ConvertToInt32, ConvertToUInt32). The interpreter intrinsic + // table is keyed on a flat method name, so the .def entries for relaxed-simd are + // prefixed with "Relaxed". Prepend it before the lookup. + char relaxed_name [128]; + if ((size_t)g_snprintf (relaxed_name, sizeof (relaxed_name), "Relaxed%s", cmethod->name) >= sizeof (relaxed_name)) + return FALSE; + + PackedSimdIntrinsicInfo *info = lookup_packedsimd_intrinsic (relaxed_name, csignature->params [0]); + if (!info || !info->interp_opcode || !info->simd_intrins) + return FALSE; + + interp_add_ins (td, info->interp_opcode); + td->last_ins->data [0] = info->simd_intrins; +#else + return FALSE; +#endif + +opcode_added: + emit_common_simd_epilogue (td, vector_klass, csignature, vector_size, TRUE); + return TRUE; +} + static gboolean interp_emit_simd_intrinsics (TransformData *td, MonoMethod *cmethod, MonoMethodSignature *csignature, gboolean newobj) { @@ -1351,6 +1436,8 @@ interp_emit_simd_intrinsics (TransformData *td, MonoMethod *cmethod, MonoMethodS } else if (!strcmp (class_ns, "System.Runtime.Intrinsics.Wasm")) { if (!strcmp (class_name, "PackedSimd")) return emit_sri_packedsimd (td, cmethod, csignature); + else if (!strcmp (class_name, "RelaxedSimd")) + return emit_sri_relaxedsimd (td, cmethod, csignature); } return FALSE; } From ff5d1cff65cbf6f167fe422db8ce13b2766348e7 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Tue, 30 Jun 2026 14:55:03 -0500 Subject: [PATCH 07/31] [wasm] Fix CI: drop stale -DENABLE_WASM_RELAXED_SIMD; define symbol in nosimd lib; wire WasiApp.targets Two CI regressions exposed on PR #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> --- src/mono/browser/browser.proj | 1 - src/mono/mono/mini/interp/interp-nosimd.c | 5 +++++ src/mono/wasi/build/WasiApp.targets | 8 +++++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/mono/browser/browser.proj b/src/mono/browser/browser.proj index 161fec3d3c4868..ea0216294c7247 100644 --- a/src/mono/browser/browser.proj +++ b/src/mono/browser/browser.proj @@ -351,7 +351,6 @@ $(CMakeBuildRuntimeConfigureCmd) -DNATIVE_BIN_DIR="$(NativeBinDir.TrimEnd('\/').Replace('\','/'))" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128" -DCONFIGURATION_INTERPSIMDTABLES_LIB="simd" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_INTERPSIMDTABLES_LIB="nosimd" - $(CMakeBuildRuntimeConfigureCmd) -DENABLE_WASM_RELAXED_SIMD=0 $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_EH_LIB="eh-wasm" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_EH_LIB="eh-js" diff --git a/src/mono/mono/mini/interp/interp-nosimd.c b/src/mono/mono/mini/interp/interp-nosimd.c index 215864eaac4976..eec32815d30217 100644 --- a/src/mono/mono/mini/interp/interp-nosimd.c +++ b/src/mono/mono/mini/interp/interp-nosimd.c @@ -17,6 +17,11 @@ int interp_simd_p_pp_wasm_opcode_table [] = { int interp_simd_p_ppp_wasm_opcode_table [] = { }; +// Always 0 in the nosimd library: when WasmEnableSIMD is false the app links this +// variant instead of either libmono-wasm-simd.a or libmono-wasm-relaxed-simd.a, and +// emit_sri_relaxedsimd in transform-simd.c reads this to gate RelaxedSimd.IsSupported. +const int mono_interp_relaxed_simd_supported = 0; + #endif // HOST_BROWSER || HOST_WASI PP_SIMD_Method interp_simd_p_p_table [] = { diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets index c2578e822eb08f..9bf402036a98b4 100644 --- a/src/mono/wasi/build/WasiApp.targets +++ b/src/mono/wasi/build/WasiApp.targets @@ -368,10 +368,12 @@ <_WasmEHLibToExclude Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-wasm.a - <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true'">libmono-wasm-simd.a + <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-relaxed-simd.a + <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-simd.a <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-nosimd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-simd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true'">libmono-wasm-nosimd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-simd.a;libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-nosimd.a;libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-nosimd.a;libmono-wasm-simd.a From bfebb64b7042e0f1095a4d87ff42a3504f62e122 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Tue, 30 Jun 2026 16:01:29 -0500 Subject: [PATCH 08/31] [wasm] Fix CI: stop linking libmono-wasm-relaxed-simd.a unconditionally 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> --- src/mono/browser/build/BrowserWasmApp.targets | 8 ++++---- src/mono/wasi/build/WasiApp.targets | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index 296cdb7abf202d..c6b0eded792cfa 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -327,9 +327,9 @@ <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-relaxed-simd.a <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-simd.a <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-nosimd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-simd.a;libmono-wasm-relaxed-simd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-nosimd.a;libmono-wasm-relaxed-simd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-nosimd.a;libmono-wasm-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-simd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-nosimd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-nosimd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-simd.a <_EmccExportedLibraryFunction>"[@(EmccExportedLibraryFunction -> '%27%(Identity)%27', ',')]" <_EmccExportedRuntimeMethods>"[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]" <_EmccExportedFunctions>@(EmccExportedFunction -> '%(Identity)',',') @@ -361,7 +361,7 @@ <_WasmNativeFileForLinking Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmEHLib)" /> <_WasmNativeFileForLinking Condition="'$(_WasmSIMDLib)' != ''" Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLib)" /> <_WasmNativeFileForLinking Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmEHLibToExclude)" /> - <_WasmNativeFileForLinking Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLibToExclude)" /> + <_WasmNativeFileForLinking Remove="$(_WasmSIMDLibToExclude)" /> <_WasmExtraJSFile Include="@(Content)" Condition="'%(Content.Extension)' == '.js'" /> diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets index 9bf402036a98b4..bd4247ec9a7809 100644 --- a/src/mono/wasi/build/WasiApp.targets +++ b/src/mono/wasi/build/WasiApp.targets @@ -371,9 +371,9 @@ <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-relaxed-simd.a <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-simd.a <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-nosimd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-simd.a;libmono-wasm-relaxed-simd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-nosimd.a;libmono-wasm-relaxed-simd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-nosimd.a;libmono-wasm-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-simd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-nosimd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-nosimd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-simd.a @@ -404,7 +404,7 @@ <_WasmNativeFileForLinking Condition="'$(_WasmEHLib)' != ''" Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmEHLib)" /> <_WasmNativeFileForLinking Condition="'$(_WasmSIMDLib)' != ''" Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLib)" /> <_WasmNativeFileForLinking Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmEHLibToExclude)" /> - <_WasmNativeFileForLinking Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLibToExclude)" /> + <_WasmNativeFileForLinking Remove="$(_WasmSIMDLibToExclude)" /> <_WasmNativeFileForLinking Include="-lstdc++" /> <_WasmNativeFileForLinking Include="@(NativeFileReference)" /> From 7f99067dc5ec245e93ab5dfc55742f28557fe279 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sun, 5 Jul 2026 12:27:52 -0500 Subject: [PATCH 09/31] [wasm] Address review feedback on RelaxedSimd surface - 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/runtime#130050 and tracks the API-review corrections captured in dotnet/runtime#130223. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Wasm/RelaxedSimd.PlatformNotSupported.cs | 4 +-- .../Runtime/Intrinsics/Wasm/RelaxedSimd.cs | 16 +++++---- .../ref/System.Runtime.Intrinsics.cs | 4 +-- .../tests/Wasm/RelaxedSimdTests.cs | 35 +++++++++++-------- src/mono/mono/mini/interp/transform-simd.c | 12 ++++++- 5 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs index 9b00843d5ccbe4..3ce3b5b9f78206 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs @@ -43,7 +43,7 @@ public abstract class RelaxedSimd public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } - public static Vector128 DotProduct(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } - public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) { throw new PlatformNotSupportedException(); } + public static Vector128 DotProduct(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) { throw new PlatformNotSupportedException(); } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs index caaded6ff3712f..6b470e5dab1a21 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs @@ -126,16 +126,18 @@ public abstract class RelaxedSimd [Intrinsic] public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) => MultiplyRoundedQ15(left, right); - // Relaxed dot products for mixed unsigned-by-signed bytes. The second operand is interpreted - // as signed but its lanes must be in the range [-64, 127] (the "i7" constraint); lanes whose - // value falls outside this range produce implementation-defined results. + // Relaxed dot products for signed-by-unsigned bytes. Per the finished spec pseudocode, + // operand `a` is signed and operand `b` is unsigned 7-bit; when any lane of `b` has the + // high bit set that lane's product is implementation-defined (may be interpreted as + // signed or unsigned). The pairwise/adjacent summation is also implementation-defined + // saturating (may or may not saturate on overflow). - /// i16x8.relaxed_dot_i8x16_i7x16_s — multiplies adjacent (byte, sbyte) pairs and sums each pair into a signed 16-bit lane. + /// i16x8.relaxed_dot_i8x16_i7x16_s — multiplies adjacent (sbyte, byte) pairs and sums each pair into a signed 16-bit lane. [Intrinsic] - public static Vector128 DotProduct(Vector128 left, Vector128 right) => DotProduct(left, right); + public static Vector128 DotProduct(Vector128 left, Vector128 right) => DotProduct(left, right); - /// i32x4.relaxed_dot_i8x16_i7x16_add_s — multiplies four adjacent (byte, sbyte) pairs, sums them with a signed 32-bit accumulator, and returns the result. + /// i32x4.relaxed_dot_i8x16_i7x16_add_s — multiplies four adjacent (sbyte, byte) pairs, sums them with a signed 32-bit accumulator, and returns the result. [Intrinsic] - public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) => DotProductAdd(left, right, accumulator); + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) => DotProductAdd(left, right, accumulator); } } diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index ebb54d748834e5..c7940a59f8e68b 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -11931,7 +11931,7 @@ public abstract partial class RelaxedSimd public static Vector128 Min(Vector128 left, Vector128 right) { throw null; } public static Vector128 Max(Vector128 left, Vector128 right) { throw null; } public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) { throw null; } - public static Vector128 DotProduct(Vector128 left, Vector128 right) { throw null; } - public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) { throw null; } + public static Vector128 DotProduct(Vector128 left, Vector128 right) { throw null; } + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) { throw null; } } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs index c497f1535bf6d9..9e22d10e614cfc 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs @@ -18,23 +18,24 @@ public sealed class RelaxedSimdTests public unsafe void RelaxedSimdIsSupportedReflects() { MethodInfo methodInfo = typeof(RelaxedSimd).GetMethod("get_IsSupported"); + Assert.NotNull(methodInfo); Assert.Equal(RelaxedSimd.IsSupported, methodInfo.Invoke(null, null)); } [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] public unsafe void DotProductByteSByteMatchesScalar() { - // For inputs where each (byte, sbyte) product fits in int16 (which the i7 constraint - // on the second operand guarantees) the relaxed dot product equals the scalar pairwise - // multiply-add. We use small positive sbytes so the i7 contract holds. - var u = Vector128.Create((byte)1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); - var s = Vector128.Create((sbyte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); + // Per the finished spec, `a` is signed and `b` is unsigned-7-bit. When every lane + // of `b` is in [0, 127] every implementation must match a straightforward + // pairwise (sbyte, byte) -> int16 multiply-add. + var s = Vector128.Create((sbyte)-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16); + var u = Vector128.Create((byte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); - Vector128 actual = RelaxedSimd.DotProduct(u, s); + Vector128 actual = RelaxedSimd.DotProduct(s, u); for (int i = 0; i < 8; i++) { - short expected = (short)(u[2 * i] * s[2 * i] + u[2 * i + 1] * s[2 * i + 1]); + short expected = (short)(s[2 * i] * u[2 * i] + s[2 * i + 1] * u[2 * i + 1]); Assert.Equal(expected, actual[i]); } } @@ -42,17 +43,17 @@ public unsafe void DotProductByteSByteMatchesScalar() [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] public unsafe void DotProductAddByteSByteMatchesScalar() { - var u = Vector128.Create((byte)1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); - var s = Vector128.Create((sbyte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); + var s = Vector128.Create((sbyte)-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16); + var u = Vector128.Create((byte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); var acc = Vector128.Create(100, 200, 300, 400); - Vector128 actual = RelaxedSimd.DotProductAdd(u, s, acc); + Vector128 actual = RelaxedSimd.DotProductAdd(s, u, acc); for (int i = 0; i < 4; i++) { int expected = acc[i]; for (int j = 0; j < 4; j++) - expected += u[4 * i + j] * s[4 * i + j]; + expected += s[4 * i + j] * u[4 * i + j]; Assert.Equal(expected, actual[i]); } } @@ -60,8 +61,10 @@ public unsafe void DotProductAddByteSByteMatchesScalar() [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] public unsafe void MultiplyAddFloatMatchesScalarApproximately() { - // Relaxed FMA may or may not round the intermediate product; verify the result is at - // most one ULP from the unfused result. + // Relaxed FMA may or may not round the intermediate product; verify the result is + // within a small relative tolerance of the unfused result. float.Epsilon is a + // subnormal (~1.4e-45) and is not a meaningful ULP scale for this comparison, so we + // use a plain relative epsilon calibrated for single precision. var a = Vector128.Create(1.5f, 2.25f, -3.125f, 4.0f); var b = Vector128.Create(2.0f, -1.5f, 0.5f, 6.25f); var c = Vector128.Create(0.5f, 1.0f, -0.25f, -2.0f); @@ -69,10 +72,12 @@ public unsafe void MultiplyAddFloatMatchesScalarApproximately() Vector128 actual = RelaxedSimd.MultiplyAddEstimate(a, b, c); Vector128 unfused = (a * b) + c; + const float RelativeTolerance = 1e-5f; for (int i = 0; i < 4; i++) { - Assert.True(Math.Abs(actual[i] - unfused[i]) <= Math.Max(Math.Abs(unfused[i]), 1.0f) * float.Epsilon * 8, - $"lane {i}: relaxed FMA {actual[i]} differs from unfused {unfused[i]} by more than 8 ULP"); + float tolerance = Math.Max(Math.Abs(unfused[i]), 1.0f) * RelativeTolerance; + Assert.True(Math.Abs(actual[i] - unfused[i]) <= tolerance, + $"lane {i}: relaxed FMA {actual[i]} differs from unfused {unfused[i]} by more than {tolerance}"); } } diff --git a/src/mono/mono/mini/interp/transform-simd.c b/src/mono/mono/mini/interp/transform-simd.c index 7c37e2c599abb3..01c6f2d8f73a39 100644 --- a/src/mono/mono/mini/interp/transform-simd.c +++ b/src/mono/mono/mini/interp/transform-simd.c @@ -1371,8 +1371,18 @@ emit_sri_relaxedsimd (TransformData *td, MonoMethod *cmethod, MonoMethodSignatur } #if defined(HOST_BROWSER) || defined(HOST_WASI) - if (!mono_interp_relaxed_simd_supported) + if (!mono_interp_relaxed_simd_supported) { + // IsSupported has already been handled above and returns false when the interp + // build did not link the relaxed-simd variant. Well-behaved callers gate on + // RelaxedSimd.IsSupported and never reach this point. Reflection or explicit + // bypass could still land here; because the managed body of every RelaxedSimd + // intrinsic is a self-recursive stub, returning FALSE here would cause the + // 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. return FALSE; + } if (!vector_klass) return FALSE; From 24face4ba9b951aa74403a02a39d85f386419a04 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Tue, 18 Aug 2026 20:29:05 -0500 Subject: [PATCH 10/31] Add Wasm RelaxedSimd intrinsics experiment Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e1f1453d-4249-46de-8828-f229e69adedb --- eng/testing/tests.wasm.targets | 2 + .../Directory.Build.props | 1 + .../System.Private.CoreLib.Shared.projitems | 2 + .../Wasm/RelaxedSimd.PlatformNotSupported.cs | 49 ++++++ .../Runtime/Intrinsics/Wasm/RelaxedSimd.cs | 143 ++++++++++++++++++ .../ref/System.Runtime.Intrinsics.cs | 30 ++++ .../System.Runtime.Intrinsics.Tests.csproj | 1 + .../tests/Wasm/RelaxedSimdTests.cs | 113 ++++++++++++++ src/mono/browser/build/BrowserWasmApp.targets | 15 +- ...ILLink.Substitutions.NoWasmRelaxedSimd.xml | 7 + .../ILLink.Substitutions.WasmRelaxedSimd.xml | 7 + src/mono/browser/runtime/genmintops.py | 3 + src/mono/mono.proj | 3 + src/mono/mono/mini/CMakeLists.txt | 10 ++ src/mono/mono/mini/aot-compiler.c | 2 + src/mono/mono/mini/interp/interp-nosimd.c | 5 + .../mono/mini/interp/interp-simd-intrins.def | 67 ++++++++ src/mono/mono/mini/interp/interp-simd.c | 47 ++++++ src/mono/mono/mini/interp/transform-simd.c | 97 ++++++++++++ src/mono/mono/mini/llvm-intrinsics.h | 13 ++ src/mono/mono/mini/mini-llvm.c | 47 +++--- src/mono/mono/mini/mini.h | 5 +- src/mono/mono/mini/simd-intrinsics.c | 40 ++++- src/mono/mono/mini/simd-methods.h | 4 + src/mono/wasi/build/WasiApp.targets | 10 +- src/mono/wasm/build/WasmApp.Common.targets | 6 + 26 files changed, 696 insertions(+), 33 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs create mode 100644 src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs create mode 100644 src/mono/browser/build/ILLink.Substitutions.NoWasmRelaxedSimd.xml create mode 100644 src/mono/browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index 37498d8f9da0b2..058a44471ce6e2 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -75,6 +75,8 @@ --> <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false'">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.WasmIntrinsics.xml" <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' == 'false'">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.NoWasmIntrinsics.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.WasmRelaxedSimd.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' != 'true'">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.NoWasmRelaxedSimd.xml" diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props index 5c36a463c5e5a6..54aa41f3adfed2 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Directory.Build.props @@ -229,6 +229,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index bd846a6aed09b1..5a3c169a3a2870 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2909,10 +2909,12 @@ + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs new file mode 100644 index 00000000000000..3ce3b5b9f78206 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.PlatformNotSupported.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.Intrinsics.Wasm +{ + [CLSCompliant(false)] + public abstract class RelaxedSimd + { + /// Gets a value that indicates whether the APIs in this class are supported. + /// if the APIs are supported; otherwise, . + /// A value of indicates that the APIs will throw . + public static bool IsSupported { [Intrinsic] get { return false; } } + + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) { throw new PlatformNotSupportedException(); } + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) { throw new PlatformNotSupportedException(); } + + public static Vector128 ConvertToInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector128 ConvertToUInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector128 ConvertToInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector128 ConvertToUInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) { throw new PlatformNotSupportedException(); } + + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw new PlatformNotSupportedException(); } + + public static Vector128 Min(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 Max(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 Min(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 Max(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + + public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + + public static Vector128 DotProduct(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) { throw new PlatformNotSupportedException(); } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs new file mode 100644 index 00000000000000..6b470e5dab1a21 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace System.Runtime.Intrinsics.Wasm +{ + /// Provides access to the WebAssembly relaxed SIMD instructions via intrinsics. + /// + /// + /// Operations exposed on this class behave "relaxedly": for inputs outside a well-defined range + /// the result is implementation-defined and may differ between WebAssembly engines and host + /// architectures. Callers that require deterministic semantics across engines should use the + /// corresponding operation (where available) instead. + /// + /// + /// All members of this class require the runtime to support the + /// relaxed SIMD WebAssembly proposal. + /// + /// + [Intrinsic] + [CLSCompliant(false)] + public abstract class RelaxedSimd + { + /// Gets a value that indicates whether the APIs in this class are supported. + /// if the APIs are supported; otherwise, . + /// A value of indicates that the APIs will throw . + public static bool IsSupported { [Intrinsic] get { return IsSupported; } } + + // Relaxed swizzle: like PackedSimd.Swizzle, but for index lanes outside [0, 16) the + // result is implementation-defined (often the index modulo 16 on x86, zero on ARM). + + /// i8x16.relaxed_swizzle + [Intrinsic] + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) => Swizzle(vector, indices); + /// i8x16.relaxed_swizzle + [Intrinsic] + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) => Swizzle(vector, indices); + + // Relaxed truncating float-to-int conversions. For NaN or out-of-range inputs the result is + // implementation-defined; the saturating PackedSimd.ConvertToInt32Saturate / ConvertToUInt32Saturate + // overloads provide deterministic semantics. + + /// i32x4.relaxed_trunc_f32x4_s + [Intrinsic] + public static Vector128 ConvertToInt32(Vector128 value) => ConvertToInt32(value); + /// i32x4.relaxed_trunc_f32x4_u + [Intrinsic] + public static Vector128 ConvertToUInt32(Vector128 value) => ConvertToUInt32(value); + /// i32x4.relaxed_trunc_f64x2_s_zero + [Intrinsic] + public static Vector128 ConvertToInt32(Vector128 value) => ConvertToInt32(value); + /// i32x4.relaxed_trunc_f64x2_u_zero + [Intrinsic] + public static Vector128 ConvertToUInt32(Vector128 value) => ConvertToUInt32(value); + + // Relaxed fused multiply-add. Whether the intermediate product is rounded before the add + // (and whether the underlying instruction is a true fused FMA) is implementation-defined. + + /// f32x4.relaxed_madd + [Intrinsic] + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddEstimate(a, b, c); + /// f64x2.relaxed_madd + [Intrinsic] + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddEstimate(a, b, c); + + /// f32x4.relaxed_nmadd + [Intrinsic] + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddNegatedEstimate(a, b, c); + /// f64x2.relaxed_nmadd + [Intrinsic] + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) => MultiplyAddNegatedEstimate(a, b, c); + + // Relaxed lane select. The mask is interpreted per-byte/word; lanes where the mask bit is + // neither all-ones nor all-zeros produce implementation-defined results. For deterministic + // selection use Vector128.ConditionalSelect. + + /// i8x16.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i8x16.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i16x8.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i16x8.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i32x4.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i32x4.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i64x2.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + /// i64x2.relaxed_laneselect + [Intrinsic] + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) => LaneSelect(left, right, mask); + + // Relaxed min/max. NaN handling and sign-of-zero handling are implementation-defined. + // For IEEE-compliant min/max use PackedSimd.Min/Max; for pseudo-min/max (one-sided NaN + // propagation) use PackedSimd.PseudoMin/PseudoMax. + + /// f32x4.relaxed_min + [Intrinsic] + public static Vector128 Min(Vector128 left, Vector128 right) => Min(left, right); + /// f32x4.relaxed_max + [Intrinsic] + public static Vector128 Max(Vector128 left, Vector128 right) => Max(left, right); + /// f64x2.relaxed_min + [Intrinsic] + public static Vector128 Min(Vector128 left, Vector128 right) => Min(left, right); + /// f64x2.relaxed_max + [Intrinsic] + public static Vector128 Max(Vector128 left, Vector128 right) => Max(left, right); + + // Relaxed Q15 multiply with rounding. Differs from PackedSimd.MultiplyRoundedSaturateQ15 + // (i16x8.q15mulr_sat_s) in that the multiplication of INT16_MIN by INT16_MIN produces an + // implementation-defined value (typically INT16_MIN unsaturated on x86). + + /// i16x8.relaxed_q15mulr_s + [Intrinsic] + public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) => MultiplyRoundedQ15(left, right); + + // Relaxed dot products for signed-by-unsigned bytes. Per the finished spec pseudocode, + // operand `a` is signed and operand `b` is unsigned 7-bit; when any lane of `b` has the + // high bit set that lane's product is implementation-defined (may be interpreted as + // signed or unsigned). The pairwise/adjacent summation is also implementation-defined + // saturating (may or may not saturate on overflow). + + /// i16x8.relaxed_dot_i8x16_i7x16_s — multiplies adjacent (sbyte, byte) pairs and sums each pair into a signed 16-bit lane. + [Intrinsic] + public static Vector128 DotProduct(Vector128 left, Vector128 right) => DotProduct(left, right); + + /// i32x4.relaxed_dot_i8x16_i7x16_add_s — multiplies four adjacent (sbyte, byte) pairs, sums them with a signed 32-bit accumulator, and returns the result. + [Intrinsic] + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) => DotProductAdd(left, right, accumulator); + } +} diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index 11653a06b37825..b21a845bcb6bf1 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -11984,4 +11984,34 @@ public abstract partial class PackedSimd public static Vector128 ZeroExtendWideningUpper(Vector128 value) { throw null; } public static Vector128 ZeroExtendWideningUpper(Vector128 value) { throw null; } } + [CLSCompliant(false)] + public abstract partial class RelaxedSimd + { + public static bool IsSupported { get { throw null; } } + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) { throw null; } + public static Vector128 Swizzle(Vector128 vector, Vector128 indices) { throw null; } + public static Vector128 ConvertToInt32(Vector128 value) { throw null; } + public static Vector128 ConvertToUInt32(Vector128 value) { throw null; } + public static Vector128 ConvertToInt32(Vector128 value) { throw null; } + public static Vector128 ConvertToUInt32(Vector128 value) { throw null; } + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddEstimate(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 MultiplyAddNegatedEstimate(Vector128 a, Vector128 b, Vector128 c) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 LaneSelect(Vector128 left, Vector128 right, Vector128 mask) { throw null; } + public static Vector128 Min(Vector128 left, Vector128 right) { throw null; } + public static Vector128 Max(Vector128 left, Vector128 right) { throw null; } + public static Vector128 Min(Vector128 left, Vector128 right) { throw null; } + public static Vector128 Max(Vector128 left, Vector128 right) { throw null; } + public static Vector128 MultiplyRoundedQ15(Vector128 left, Vector128 right) { throw null; } + public static Vector128 DotProduct(Vector128 left, Vector128 right) { throw null; } + public static Vector128 DotProductAdd(Vector128 left, Vector128 right, Vector128 accumulator) { throw null; } + } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj index 5d98f157e174a3..2a552224dfd797 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj +++ b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs new file mode 100644 index 00000000000000..9e22d10e614cfc --- /dev/null +++ b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Wasm; +using Xunit; + +namespace System.Runtime.Intrinsics.Wasm.Tests +{ + [PlatformSpecific(TestPlatforms.Browser)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/123011", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))] + public sealed class RelaxedSimdTests + { + [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(RelaxedSimd))] + public unsafe void RelaxedSimdIsSupportedReflects() + { + MethodInfo methodInfo = typeof(RelaxedSimd).GetMethod("get_IsSupported"); + Assert.NotNull(methodInfo); + Assert.Equal(RelaxedSimd.IsSupported, methodInfo.Invoke(null, null)); + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void DotProductByteSByteMatchesScalar() + { + // Per the finished spec, `a` is signed and `b` is unsigned-7-bit. When every lane + // of `b` is in [0, 127] every implementation must match a straightforward + // pairwise (sbyte, byte) -> int16 multiply-add. + var s = Vector128.Create((sbyte)-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16); + var u = Vector128.Create((byte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); + + Vector128 actual = RelaxedSimd.DotProduct(s, u); + + for (int i = 0; i < 8; i++) + { + short expected = (short)(s[2 * i] * u[2 * i] + s[2 * i + 1] * u[2 * i + 1]); + Assert.Equal(expected, actual[i]); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void DotProductAddByteSByteMatchesScalar() + { + var s = Vector128.Create((sbyte)-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16); + var u = Vector128.Create((byte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); + var acc = Vector128.Create(100, 200, 300, 400); + + Vector128 actual = RelaxedSimd.DotProductAdd(s, u, acc); + + for (int i = 0; i < 4; i++) + { + int expected = acc[i]; + for (int j = 0; j < 4; j++) + expected += s[4 * i + j] * u[4 * i + j]; + Assert.Equal(expected, actual[i]); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void MultiplyAddFloatMatchesScalarApproximately() + { + // Relaxed FMA may or may not round the intermediate product; verify the result is + // within a small relative tolerance of the unfused result. float.Epsilon is a + // subnormal (~1.4e-45) and is not a meaningful ULP scale for this comparison, so we + // use a plain relative epsilon calibrated for single precision. + var a = Vector128.Create(1.5f, 2.25f, -3.125f, 4.0f); + var b = Vector128.Create(2.0f, -1.5f, 0.5f, 6.25f); + var c = Vector128.Create(0.5f, 1.0f, -0.25f, -2.0f); + + Vector128 actual = RelaxedSimd.MultiplyAddEstimate(a, b, c); + Vector128 unfused = (a * b) + c; + + const float RelativeTolerance = 1e-5f; + for (int i = 0; i < 4; i++) + { + float tolerance = Math.Max(Math.Abs(unfused[i]), 1.0f) * RelativeTolerance; + Assert.True(Math.Abs(actual[i] - unfused[i]) <= tolerance, + $"lane {i}: relaxed FMA {actual[i]} differs from unfused {unfused[i]} by more than {tolerance}"); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void LaneSelectAllOnesAllZerosBehavesLikeConditionalSelect() + { + // For mask lanes that are all-ones or all-zeros the relaxed lane select must match + // the deterministic semantics. + var left = Vector128.Create((byte)1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + var right = Vector128.Create((byte)17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32); + var mask = Vector128.Create((byte)0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00); + + Vector128 actual = RelaxedSimd.LaneSelect(left, right, mask); + Vector128 expected = Vector128.ConditionalSelect(mask, left, right); + + Assert.Equal(expected, actual); + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public unsafe void SwizzleInRangeMatchesVector128Shuffle() + { + // For index lanes in [0, 16) the relaxed swizzle must agree with Vector128.Shuffle. + var v = Vector128.Create((byte)10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160); + var idx = Vector128.Create((byte)15, 0, 14, 1, 13, 2, 12, 3, 11, 4, 10, 5, 9, 6, 8, 7); + + Vector128 actual = RelaxedSimd.Swizzle(v, idx); + Vector128 expected = Vector128.Shuffle(v, idx); + + Assert.Equal(expected, actual); + } + } +} diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index 4afa05842060c2..2a602dc8ad3c89 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -32,6 +32,8 @@ <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.WasmIntrinsics.xml" <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' == 'false'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.NoWasmIntrinsics.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.WasmRelaxedSimd.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' != 'true'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.NoWasmRelaxedSimd.xml" true emcc @@ -39,6 +41,7 @@ <_WasmDefaultFlags Condition="'$(WasmEnableExceptionHandling)' == 'false'">-fexceptions <_WasmDefaultFlags Condition="'$(WasmEnableExceptionHandling)' != 'false'">-fwasm-exceptions -s WASM_LEGACY_EXCEPTIONS=0 <_WasmDefaultFlags Condition="'$(WasmEnableSIMD)' == 'true'">$(_WasmDefaultFlags) -msimd128 + <_WasmDefaultFlags Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_WasmDefaultFlags) -mrelaxed-simd <_WasmOutputFileName Condition="'$(WasmSingleFileBundle)' != 'true'">dotnet.native.wasm @@ -250,6 +253,8 @@ + + @@ -319,10 +324,12 @@ <_WasmEHLib Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-js.a <_WasmEHLibToExclude Condition="'$(WasmEnableExceptionHandling)' == 'true'">libmono-wasm-eh-js.a <_WasmEHLibToExclude Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-wasm.a - <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true'">libmono-wasm-simd.a + <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-relaxed-simd.a + <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-simd.a <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-nosimd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-simd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true'">libmono-wasm-nosimd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-simd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-nosimd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-nosimd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-simd.a <_EmccExportedLibraryFunction>"[@(EmccExportedLibraryFunction -> '%27%(Identity)%27', ',')]" <_EmccExportedRuntimeMethods>"[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]" <_EmccExportedFunctions>@(EmccExportedFunction -> '%(Identity)',',') @@ -354,7 +361,7 @@ <_WasmNativeFileForLinking Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmEHLib)" /> <_WasmNativeFileForLinking Condition="'$(_WasmSIMDLib)' != ''" Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLib)" /> <_WasmNativeFileForLinking Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmEHLibToExclude)" /> - <_WasmNativeFileForLinking Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLibToExclude)" /> + <_WasmNativeFileForLinking Remove="$(_WasmSIMDLibToExclude)" /> <_WasmExtraJSFile Include="@(Content)" Condition="'%(Content.Extension)' == '.js'" /> diff --git a/src/mono/browser/build/ILLink.Substitutions.NoWasmRelaxedSimd.xml b/src/mono/browser/build/ILLink.Substitutions.NoWasmRelaxedSimd.xml new file mode 100644 index 00000000000000..3c512185aa297e --- /dev/null +++ b/src/mono/browser/build/ILLink.Substitutions.NoWasmRelaxedSimd.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/mono/browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml b/src/mono/browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml new file mode 100644 index 00000000000000..5e392a051c943f --- /dev/null +++ b/src/mono/browser/build/ILLink.Substitutions.WasmRelaxedSimd.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/mono/browser/runtime/genmintops.py b/src/mono/browser/runtime/genmintops.py index 33c370690f8c0d..af777d928344cf 100755 --- a/src/mono/browser/runtime/genmintops.py +++ b/src/mono/browser/runtime/genmintops.py @@ -49,6 +49,9 @@ "INTERP_WASM_SIMD_INTRINSIC_V_VI": simd_values_2, "INTERP_WASM_SIMD_INTRINSIC_V_VVV": simd_values_3, "INTERP_WASM_SIMD_INTRINSIC_V_C3": simd_values_3, + "INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V": simd_values_1, + "INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV": simd_values_2, + "INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV": simd_values_3, } for line in simd_header_lines: diff --git a/src/mono/mono.proj b/src/mono/mono.proj index f36bf48c9369e2..da8a28b55d4e17 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -1002,6 +1002,9 @@ <_MonoRuntimeArtifacts Condition="('$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true') and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-wasm-simd.a"> $(RuntimeBinDir)libmono-wasm-simd.a + <_MonoRuntimeArtifacts Condition="('$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true') and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-wasm-relaxed-simd.a"> + $(RuntimeBinDir)libmono-wasm-relaxed-simd.a + <_MonoRuntimeArtifacts Condition="('$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true') and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-wasm-nosimd.a"> $(RuntimeBinDir)libmono-wasm-nosimd.a diff --git a/src/mono/mono/mini/CMakeLists.txt b/src/mono/mono/mini/CMakeLists.txt index 33623aae258757..dc3ebdadd49d90 100644 --- a/src/mono/mono/mini/CMakeLists.txt +++ b/src/mono/mono/mini/CMakeLists.txt @@ -486,6 +486,16 @@ if(HOST_BROWSER OR HOST_WASI) target_link_libraries (mono-wasm-simd PRIVATE monoapi eglib_api) set_target_properties(mono-wasm-simd PROPERTIES COMPILE_FLAGS "-msimd128") install(TARGETS mono-wasm-simd LIBRARY) + + # Relaxed-SIMD variant: same translation unit as mono-wasm-simd but compiled with + # -mrelaxed-simd and HOST_WASM_RELAXED_SIMD=1, so the relaxed-simd intrinsic + # wrappers in interp-simd.c expand to real emscripten builtins instead of stubs. + # The SDK ships both libs; the app build picks one based on WasmEnableRelaxedSimd. + add_library(mono-wasm-relaxed-simd STATIC interp/interp-simd.c) + target_link_libraries (mono-wasm-relaxed-simd PRIVATE monoapi eglib_api) + set_target_properties(mono-wasm-relaxed-simd PROPERTIES COMPILE_FLAGS "-msimd128 -mrelaxed-simd") + target_compile_definitions(mono-wasm-relaxed-simd PRIVATE HOST_WASM_RELAXED_SIMD=1) + install(TARGETS mono-wasm-relaxed-simd LIBRARY) endif() if(HOST_BROWSER OR HOST_WASI OR TARGET_WASM) diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index 3b9462c8e17e4c..4227eb95a55b1e 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -8940,6 +8940,8 @@ parse_cpu_features (const gchar *attr) // MONO_CPU_WASM_BASE is unconditionally set in mini_get_cpu_features. if (!strcmp (attr + prefix, "simd")) feature = MONO_CPU_WASM_SIMD; + else if (!strcmp (attr + prefix, "relaxed-simd")) + feature = MONO_CPU_WASM_RELAXED_SIMD; #else (void)prefix; // unused #endif diff --git a/src/mono/mono/mini/interp/interp-nosimd.c b/src/mono/mono/mini/interp/interp-nosimd.c index 215864eaac4976..eec32815d30217 100644 --- a/src/mono/mono/mini/interp/interp-nosimd.c +++ b/src/mono/mono/mini/interp/interp-nosimd.c @@ -17,6 +17,11 @@ int interp_simd_p_pp_wasm_opcode_table [] = { int interp_simd_p_ppp_wasm_opcode_table [] = { }; +// Always 0 in the nosimd library: when WasmEnableSIMD is false the app links this +// variant instead of either libmono-wasm-simd.a or libmono-wasm-relaxed-simd.a, and +// emit_sri_relaxedsimd in transform-simd.c reads this to gate RelaxedSimd.IsSupported. +const int mono_interp_relaxed_simd_supported = 0; + #endif // HOST_BROWSER || HOST_WASI PP_SIMD_Method interp_simd_p_p_table [] = { diff --git a/src/mono/mono/mini/interp/interp-simd-intrins.def b/src/mono/mono/mini/interp/interp-simd-intrins.def index 4fefb8e395445b..5ddc9173306f92 100644 --- a/src/mono/mono/mini/interp/interp-simd-intrins.def +++ b/src/mono/mono/mini/interp/interp-simd-intrins.def @@ -38,6 +38,47 @@ #define INTERP_WASM_SIMD_INTRINSIC_V_C3(name, arg1, c_function, wasm_opcode) #endif // defined(HOST_BROWSER) || defined(HOST_WASI) +// Relaxed-SIMD macro family. The .def file uses INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_{V,VV,VVV} +// for the relaxed-simd entries below. There are two flavors of the runtime SIMD library: +// * libmono-wasm-simd.a is built without -mrelaxed-simd / without HOST_WASM_RELAXED_SIMD, +// so the relaxed entries are routed to a per-arity stub function (asserts at runtime) +// and their wasm-opcode table slots are 0 (the jiterpreter uses the fallback path, which +// in turn calls the stub — guarded by RelaxedSimd.IsSupported reporting false). +// * libmono-wasm-relaxed-simd.a is built with -mrelaxed-simd and HOST_WASM_RELAXED_SIMD=1, +// so the relaxed entries expand to the regular INTERP_WASM_SIMD_INTRINSIC_V_* path +// producing real emscripten intrinsic wrappers and real wasm opcodes. +// mono-ee-interp is always built with HOST_WASM_RELAXED_SIMD=1 so the MintSIMDOpsPP/PPP/PPPP +// enums in mintops.h and the unsorted_packedsimd_intrinsic_infos[] lookup table in +// transform-simd.c always include the relaxed entries at stable positions across both +// library flavors. +#if defined(HOST_BROWSER) || defined(HOST_WASI) +#ifdef HOST_WASM_RELAXED_SIMD +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V INTERP_WASM_SIMD_INTRINSIC_V_V +#endif +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV INTERP_WASM_SIMD_INTRINSIC_V_VV +#endif +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV INTERP_WASM_SIMD_INTRINSIC_V_VVV +#endif +#else +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V(name, arg1, c_intrinsic, wasm_opcode) INTERP_SIMD_INTRINSIC_P_P(INTERP_SIMD_INTRINSIC_ ## name ## arg1, _mono_interp_simd_relaxed_unsupported_1, 0) +#endif +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV(name, arg1, c_intrinsic, wasm_opcode) INTERP_SIMD_INTRINSIC_P_PP(INTERP_SIMD_INTRINSIC_ ## name ## arg1, _mono_interp_simd_relaxed_unsupported_2, 0) +#endif +#ifndef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV(name, arg1, c_intrinsic, wasm_opcode) INTERP_SIMD_INTRINSIC_P_PPP(INTERP_SIMD_INTRINSIC_ ## name ## arg1, _mono_interp_simd_relaxed_unsupported_3, 0) +#endif +#endif // HOST_WASM_RELAXED_SIMD +#else +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V(name, arg1, c_intrinsic, wasm_opcode) +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV(name, arg1, c_intrinsic, wasm_opcode) +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV(name, arg1, c_intrinsic, wasm_opcode) +#endif // defined(HOST_BROWSER) || defined(HOST_WASI) + // The third argument is the wasm opcode that corresponds to this simd intrinsic, if any. // Specify -1 if there is no exact 1:1 mapping (the opcode can still be implemented manually in the jiterpreter.) @@ -391,3 +432,29 @@ INTERP_WASM_SIMD_INTRINSIC_V_C3 (StoreSelectedScalar, X1, interp_packedsimd_stor INTERP_WASM_SIMD_INTRINSIC_V_C3 (StoreSelectedScalar, X2, interp_packedsimd_store16_lane, 0x59) INTERP_WASM_SIMD_INTRINSIC_V_C3 (StoreSelectedScalar, X4, interp_packedsimd_store32_lane, 0x5a) INTERP_WASM_SIMD_INTRINSIC_V_C3 (StoreSelectedScalar, X8, interp_packedsimd_store64_lane, 0x5b) + +// Relaxed-SIMD (https://github.com/WebAssembly/relaxed-simd). The method names below are +// prefixed with "Relaxed" to avoid name collisions with PackedSimd entries in the shared +// PackedSimdIntrinsicInfo lookup table; emit_sri_relaxedsimd in transform-simd.c prepends +// "Relaxed" before looking them up. See the macro definitions at the top of this file for +// how the regular vs relaxed-simd library variants route these entries differently. +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedSwizzle, D1, wasm_i8x16_relaxed_swizzle, 0x100) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V (RelaxedConvertToInt32, R4, wasm_i32x4_relaxed_trunc_f32x4, 0x101) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V (RelaxedConvertToUInt32, R4, wasm_u32x4_relaxed_trunc_f32x4, 0x102) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V (RelaxedConvertToInt32, R8, wasm_i32x4_relaxed_trunc_f64x2_zero, 0x103) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V (RelaxedConvertToUInt32, R8, wasm_u32x4_relaxed_trunc_f64x2_zero, 0x104) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedMultiplyAddEstimate, R4, wasm_f32x4_relaxed_madd, 0x105) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedMultiplyAddNegatedEstimate, R4, wasm_f32x4_relaxed_nmadd, 0x106) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedMultiplyAddEstimate, R8, wasm_f64x2_relaxed_madd, 0x107) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedMultiplyAddNegatedEstimate, R8, wasm_f64x2_relaxed_nmadd, 0x108) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedLaneSelect, D1, wasm_i8x16_relaxed_laneselect, 0x109) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedLaneSelect, D2, wasm_i16x8_relaxed_laneselect, 0x10a) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedLaneSelect, D4, wasm_i32x4_relaxed_laneselect, 0x10b) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedLaneSelect, D8, wasm_i64x2_relaxed_laneselect, 0x10c) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMin, R4, wasm_f32x4_relaxed_min, 0x10d) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMax, R4, wasm_f32x4_relaxed_max, 0x10e) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMin, R8, wasm_f64x2_relaxed_min, 0x10f) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMax, R8, wasm_f64x2_relaxed_max, 0x110) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedMultiplyRoundedQ15, I2, wasm_i16x8_relaxed_q15mulr, 0x111) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV (RelaxedDotProduct, U1, wasm_i16x8_relaxed_dot_i8x16_i7x16, 0x112) +INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV (RelaxedDotProductAdd, U1, wasm_i32x4_relaxed_dot_i8x16_i7x16_add, 0x113) diff --git a/src/mono/mono/mini/interp/interp-simd.c b/src/mono/mono/mini/interp/interp-simd.c index b779813fa745d3..e77e14626530e2 100644 --- a/src/mono/mono/mini/interp/interp-simd.c +++ b/src/mono/mono/mini/interp/interp-simd.c @@ -623,6 +623,36 @@ _interp_wasm_simd_assert_not_reached (v128_t lhs, v128_t rhs) { g_assert_not_reached (); } +// Per-arity stubs used by the regular (non-relaxed) libmono-wasm-simd.a as the +// function-pointer-table targets for the RelaxedSimd intrinsic entries. The relaxed +// variant of the library (libmono-wasm-relaxed-simd.a) is compiled with +// HOST_WASM_RELAXED_SIMD=1 and uses real intrinsic wrappers instead. +// +// These are never called when RelaxedSimd.IsSupported reports false: the recognizer in +// transform-simd.c keys the IsSupported answer off mono_interp_relaxed_simd_supported. +#ifndef HOST_WASM_RELAXED_SIMD +static void +_mono_interp_simd_relaxed_unsupported_1 (gpointer res, gpointer v1) { + g_assert_not_reached (); +} +static void +_mono_interp_simd_relaxed_unsupported_2 (gpointer res, gpointer v1, gpointer v2) { + g_assert_not_reached (); +} +static void +_mono_interp_simd_relaxed_unsupported_3 (gpointer res, gpointer v1, gpointer v2, gpointer v3) { + g_assert_not_reached (); +} +#endif + +// Runtime feature flag queried by transform-simd.c so a single mono-ee-interp.a can be +// linked against either libmono-wasm-simd.a (=0) or libmono-wasm-relaxed-simd.a (=1). +#ifdef HOST_WASM_RELAXED_SIMD +const int mono_interp_relaxed_simd_supported = 1; +#else +const int mono_interp_relaxed_simd_supported = 0; +#endif + #define LANE_COUNT(lane_type) (sizeof(v128_t) / sizeof(lane_type)) // ensure the lane is valid by wrapping it (in AOT it would fail to compile) @@ -893,6 +923,20 @@ _mono_interp_simd_ ## c_intrinsic (gpointer res, gpointer v1, gpointer v2, gpoin #define INTERP_WASM_SIMD_INTRINSIC_V_C2(name, arg1, c_function, wasm_opcode) #define INTERP_WASM_SIMD_INTRINSIC_V_C3(name, arg1, c_function, wasm_opcode) +// Relaxed-SIMD function-body phase: +// * With HOST_WASM_RELAXED_SIMD: route to the regular wrappers (real intrinsic bodies). +// * Without: skip per-entry function generation; the table entries below resolve to +// the per-arity stubs defined above (_mono_interp_simd_relaxed_unsupported_{1,2,3}). +#ifdef HOST_WASM_RELAXED_SIMD +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V INTERP_WASM_SIMD_INTRINSIC_V_V +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV INTERP_WASM_SIMD_INTRINSIC_V_VV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV INTERP_WASM_SIMD_INTRINSIC_V_VVV +#else +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V(name, arg1, c_intrinsic, wasm_opcode) +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV(name, arg1, c_intrinsic, wasm_opcode) +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV(name, arg1, c_intrinsic, wasm_opcode) +#endif + #include "interp-simd-intrins.def" #undef INTERP_WASM_SIMD_INTRINSIC_V_P @@ -904,6 +948,9 @@ _mono_interp_simd_ ## c_intrinsic (gpointer res, gpointer v1, gpointer v2, gpoin #undef INTERP_WASM_SIMD_INTRINSIC_V_C1 #undef INTERP_WASM_SIMD_INTRINSIC_V_C2 #undef INTERP_WASM_SIMD_INTRINSIC_V_C3 +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV // Now generate the wasm opcode tables for the intrinsics diff --git a/src/mono/mono/mini/interp/transform-simd.c b/src/mono/mono/mini/interp/transform-simd.c index b41bfc796d20fd..01c6f2d8f73a39 100644 --- a/src/mono/mono/mini/interp/transform-simd.c +++ b/src/mono/mono/mini/interp/transform-simd.c @@ -36,6 +36,14 @@ enum { #define method_name(idx) ((const char*)&method_names + (idx)) static gboolean emit_sri_packedsimd (TransformData *, MonoMethod *, MonoMethodSignature *); +static gboolean emit_sri_relaxedsimd (TransformData *, MonoMethod *, MonoMethodSignature *); + +#if defined(HOST_BROWSER) || defined(HOST_WASI) +// Set by interp-simd.c to 1 in libmono-wasm-relaxed-simd.a and to 0 in libmono-wasm-simd.a / +// libmono-wasm-nosimd.a. The app build picks one library variant via WasmEnableRelaxedSimd, +// so this lets a single mono-ee-interp.a decide RelaxedSimd.IsSupported at runtime. +extern const int mono_interp_relaxed_simd_supported; +#endif static int simd_intrinsic_compare_by_name (const void *key, const void *value) @@ -972,6 +980,17 @@ typedef struct { #define INTERP_WASM_SIMD_INTRINSIC_V_C3(name, arg1, c_intrinsic, wasm_opcode) \ INTRINS_COMMON(name, arg1, c_intrinsic, MINT_SIMD_INTRINS_P_PPP, INTERP_SIMD_INTRINSIC_ ## name ## arg1) +// Relaxed-SIMD: route through the regular WASM_SIMD macros so the entries land in the +// shared unsorted_packedsimd_intrinsic_infos[] lookup table with proper enum ids and +// MINT_SIMD_INTRINS_P_{PP,PPP} dispatch opcodes. emit_sri_relaxedsimd prepends "Relaxed" +// to the method name before looking up the table entry. +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V INTERP_WASM_SIMD_INTRINSIC_V_V +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV INTERP_WASM_SIMD_INTRINSIC_V_VV +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV +#define INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV INTERP_WASM_SIMD_INTRINSIC_V_VVV + static PackedSimdIntrinsicInfo unsorted_packedsimd_intrinsic_infos[] = { #include "interp-simd-intrins.def" }; @@ -984,6 +1003,9 @@ static PackedSimdIntrinsicInfo unsorted_packedsimd_intrinsic_infos[] = { #undef INTERP_WASM_SIMD_INTRINSIC_V_C2 #undef INTERP_WASM_SIMD_INTRINSIC_V_VVV #undef INTERP_WASM_SIMD_INTRINSIC_V_C3 +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_V +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VV +#undef INTERP_WASM_RELAXED_SIMD_INTRINSIC_V_VVV static PackedSimdIntrinsicInfo *sorted_packedsimd_intrinsic_infos; @@ -1320,6 +1342,79 @@ emit_sri_packedsimd (TransformData *td, MonoMethod *cmethod, MonoMethodSignature return TRUE; } +static gboolean +emit_sri_relaxedsimd (TransformData *td, MonoMethod *cmethod, MonoMethodSignature *csignature) +{ + if (csignature->hasthis) + return FALSE; + + MonoClass *vector_klass = NULL; + if (csignature->ret->type == MONO_TYPE_GENERICINST) + vector_klass = mono_class_from_mono_type_internal (csignature->ret); + else if (csignature->param_count && csignature->params [0]->type == MONO_TYPE_GENERICINST) + vector_klass = mono_class_from_mono_type_internal (csignature->params [0]); + + int vector_size = -1; + + // IsSupported is normally constant-folded by the linker substitution + the JIT/AOT + // recognizer in simd-intrinsics.c. The interpreter only reaches it when those layers + // were bypassed (e.g. reflection); answer based on which mono-wasm-simd library + // variant the app build linked against. + if (!strcmp (cmethod->name, "get_IsSupported")) { +#if defined(HOST_BROWSER) || defined(HOST_WASI) + interp_add_ins (td, mono_interp_relaxed_simd_supported ? MINT_LDC_I4_1 : MINT_LDC_I4_0); +#else + interp_add_ins (td, MINT_LDC_I4_0); +#endif + vector_klass = mono_class_from_mono_type_internal (csignature->ret); + goto opcode_added; + } + +#if defined(HOST_BROWSER) || defined(HOST_WASI) + if (!mono_interp_relaxed_simd_supported) { + // IsSupported has already been handled above and returns false when the interp + // build did not link the relaxed-simd variant. Well-behaved callers gate on + // RelaxedSimd.IsSupported and never reach this point. Reflection or explicit + // bypass could still land here; because the managed body of every RelaxedSimd + // intrinsic is a self-recursive stub, returning FALSE here would cause the + // 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. + return FALSE; + } + + if (!vector_klass) + return FALSE; + + MonoTypeEnum atype; + int arg_size, scalar_arg; + if (!get_common_simd_info (vector_klass, csignature, &atype, &vector_size, &arg_size, &scalar_arg)) + return FALSE; + + // The RelaxedSimd.* method names overlap with PackedSimd.* (Swizzle, Min, Max, + // MultiplyAddEstimate, ConvertToInt32, ConvertToUInt32). The interpreter intrinsic + // table is keyed on a flat method name, so the .def entries for relaxed-simd are + // prefixed with "Relaxed". Prepend it before the lookup. + char relaxed_name [128]; + if ((size_t)g_snprintf (relaxed_name, sizeof (relaxed_name), "Relaxed%s", cmethod->name) >= sizeof (relaxed_name)) + return FALSE; + + PackedSimdIntrinsicInfo *info = lookup_packedsimd_intrinsic (relaxed_name, csignature->params [0]); + if (!info || !info->interp_opcode || !info->simd_intrins) + return FALSE; + + interp_add_ins (td, info->interp_opcode); + td->last_ins->data [0] = info->simd_intrins; +#else + return FALSE; +#endif + +opcode_added: + emit_common_simd_epilogue (td, vector_klass, csignature, vector_size, TRUE); + return TRUE; +} + static gboolean interp_emit_simd_intrinsics (TransformData *td, MonoMethod *cmethod, MonoMethodSignature *csignature, gboolean newobj) { @@ -1351,6 +1446,8 @@ interp_emit_simd_intrinsics (TransformData *td, MonoMethod *cmethod, MonoMethodS } else if (!strcmp (class_ns, "System.Runtime.Intrinsics.Wasm")) { if (!strcmp (class_name, "PackedSimd")) return emit_sri_packedsimd (td, cmethod, csignature); + else if (!strcmp (class_name, "RelaxedSimd")) + return emit_sri_relaxedsimd (td, cmethod, csignature); } return FALSE; } diff --git a/src/mono/mono/mini/llvm-intrinsics.h b/src/mono/mono/mini/llvm-intrinsics.h index 848ef32a64bf82..dca0e32bd27586 100644 --- a/src/mono/mono/mini/llvm-intrinsics.h +++ b/src/mono/mono/mini/llvm-intrinsics.h @@ -318,6 +318,19 @@ INTRINS_OVR(WASM_PMAX_V2, fabs, Generic, sse_r8_t) INTRINS(WASM_Q15MULR_SAT_SIGNED, wasm_q15mulr_sat_signed, Wasm) INTRINS(WASM_SHUFFLE, wasm_shuffle, Wasm) INTRINS(WASM_SWIZZLE, wasm_swizzle, Wasm) +INTRINS(WASM_RELAXED_SWIZZLE, wasm_relaxed_swizzle, Wasm) +INTRINS(WASM_RELAXED_TRUNC_SIGNED, wasm_relaxed_trunc_signed, Wasm) +INTRINS(WASM_RELAXED_TRUNC_UNSIGNED, wasm_relaxed_trunc_unsigned, Wasm) +INTRINS(WASM_RELAXED_TRUNC_SIGNED_ZERO, wasm_relaxed_trunc_signed_zero, Wasm) +INTRINS(WASM_RELAXED_TRUNC_UNSIGNED_ZERO, wasm_relaxed_trunc_unsigned_zero, Wasm) +INTRINS_OVR_TAG(WASM_RELAXED_MADD, wasm_relaxed_madd, Wasm, V128 | R4 | R8) +INTRINS_OVR_TAG(WASM_RELAXED_NMADD, wasm_relaxed_nmadd, Wasm, V128 | R4 | R8) +INTRINS_OVR_TAG(WASM_RELAXED_LANESELECT, wasm_relaxed_laneselect, Wasm, V128 | I1 | I2 | I4 | I8) +INTRINS_OVR_TAG(WASM_RELAXED_MIN, wasm_relaxed_min, Wasm, V128 | R4 | R8) +INTRINS_OVR_TAG(WASM_RELAXED_MAX, wasm_relaxed_max, Wasm, V128 | R4 | R8) +INTRINS(WASM_RELAXED_Q15MULR_SIGNED, wasm_relaxed_q15mulr_signed, Wasm) +INTRINS(WASM_RELAXED_DOT_I8X16_I7X16_SIGNED, wasm_relaxed_dot_i8x16_i7x16_signed, Wasm) +INTRINS(WASM_RELAXED_DOT_I8X16_I7X16_ADD_SIGNED, wasm_relaxed_dot_i8x16_i7x16_add_signed, Wasm) INTRINS(WASM_GET_EXCEPTION, wasm_get_exception, Wasm) INTRINS(WASM_GET_EHSELECTOR, wasm_get_ehselector, Wasm) INTRINS(WASM_RETHROW, wasm_rethrow, Wasm) diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index f3166c3d2881f1..1347407c693460 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -8285,6 +8285,30 @@ MONO_RESTORE_WARNING values [ins->dreg] = call_overloaded_intrins (ctx, iid, ovr_tag, args, ""); break; } + case OP_XOP_X_X_X_X: { + IntrinsicId id = (IntrinsicId)ins->inst_c0; +#if defined(TARGET_ARM64) + gboolean getLowerElement = FALSE; + int arg_idx = -1; + switch (id) { + case INTRINS_AARCH64_SHA1C: + case INTRINS_AARCH64_SHA1M: + case INTRINS_AARCH64_SHA1P: + getLowerElement = TRUE; + arg_idx = 1; + break; + default: + break; + } +#endif + LLVMValueRef args [] = { lhs, rhs, arg3 }; +#if defined(TARGET_ARM64) + if (getLowerElement) + args [arg_idx] = LLVMBuildExtractElement (ctx->builder, args [arg_idx], const_int32 (0), ""); +#endif + values [ins->dreg] = call_intrins (ctx, id, args, ""); + break; + } case OP_XOP_OVR_BYSCALAR_X_X_X: { IntrinsicId iid = (IntrinsicId) ins->inst_c0; llvm_ovr_tag_t ovr_tag = ovr_tag_from_mono_vector_class (ins->klass); @@ -10932,26 +10956,6 @@ MONO_RESTORE_WARNING values [ins->dreg] = convert (ctx, values [ins->dreg], LLVMVectorType (LLVMInt64Type (), 2)); break; } - case OP_XOP_X_X_X_X: { - IntrinsicId id = (IntrinsicId)ins->inst_c0; - gboolean getLowerElement = FALSE; - int arg_idx = -1; - switch (id) { - case INTRINS_AARCH64_SHA1C: - case INTRINS_AARCH64_SHA1M: - case INTRINS_AARCH64_SHA1P: - getLowerElement = TRUE; - arg_idx = 1; - break; - default: - break; - } - LLVMValueRef args [] = { lhs, rhs, arg3 }; - if (getLowerElement) - args [arg_idx] = LLVMBuildExtractElement (ctx->builder, args [arg_idx], const_int32 (0), ""); - values [ins->dreg] = call_intrins (ctx, id, args, ""); - break; - } case OP_XOP_X_X: { IntrinsicId id = (IntrinsicId)ins->inst_c0; LLVMTypeRef ret_t = simd_class_to_llvm_type (ctx, ins->klass); @@ -15518,7 +15522,8 @@ MonoCPUFeatures mono_llvm_get_cpu_features (void) { "dotprod", MONO_CPU_ARM64_DP }, #endif #if defined(TARGET_WASM) - { "simd", MONO_CPU_WASM_SIMD }, + { "simd", MONO_CPU_WASM_SIMD }, + { "relaxed-simd", MONO_CPU_WASM_RELAXED_SIMD }, #endif // flags_map cannot be zero length in MSVC, so add useless dummy entry for arm32 #if defined(TARGET_ARM) && defined(HOST_WIN32) diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index 65b38b751e8c45..e5d17c93429d55 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -2947,8 +2947,9 @@ typedef enum { | MONO_CPU_X86_AES | MONO_CPU_X86_POPCNT | MONO_CPU_X86_FMA, #endif #ifdef TARGET_WASM - MONO_CPU_WASM_BASE = 1 << 1, - MONO_CPU_WASM_SIMD = 1 << 2, + MONO_CPU_WASM_BASE = 1 << 1, + MONO_CPU_WASM_SIMD = 1 << 2, + MONO_CPU_WASM_RELAXED_SIMD = 1 << 3, #endif #ifdef TARGET_ARM64 MONO_CPU_ARM64_BASE = 1 << 1, diff --git a/src/mono/mono/mini/simd-intrinsics.c b/src/mono/mono/mini/simd-intrinsics.c index 21295764fad806..306741ea1e9f2d 100644 --- a/src/mono/mono/mini/simd-intrinsics.c +++ b/src/mono/mono/mini/simd-intrinsics.c @@ -6495,9 +6495,25 @@ static SimdIntrinsic packedsimd_methods [] = { {SN_get_IsSupported}, }; +static SimdIntrinsic relaxedsimd_methods [] = { + {SN_ConvertToInt32}, + {SN_ConvertToUInt32}, + {SN_DotProduct, OP_XOP_X_X_X, INTRINS_WASM_RELAXED_DOT_I8X16_I7X16_SIGNED}, + {SN_DotProductAdd, OP_XOP_X_X_X_X, INTRINS_WASM_RELAXED_DOT_I8X16_I7X16_ADD_SIGNED}, + {SN_LaneSelect, OP_XOP_OVR_X_X_X_X, INTRINS_WASM_RELAXED_LANESELECT}, + {SN_Max, OP_XOP_OVR_X_X_X, INTRINS_WASM_RELAXED_MAX}, + {SN_Min, OP_XOP_OVR_X_X_X, INTRINS_WASM_RELAXED_MIN}, + {SN_MultiplyAddEstimate, OP_XOP_OVR_X_X_X_X, INTRINS_WASM_RELAXED_MADD}, + {SN_MultiplyAddNegatedEstimate, OP_XOP_OVR_X_X_X_X, INTRINS_WASM_RELAXED_NMADD}, + {SN_MultiplyRoundedQ15, OP_XOP_X_X_X, INTRINS_WASM_RELAXED_Q15MULR_SIGNED}, + {SN_Swizzle, OP_XOP_X_X_X, INTRINS_WASM_RELAXED_SWIZZLE}, + {SN_get_IsSupported}, +}; + static const IntrinGroup supported_wasm_intrinsics [] = { - { "PackedSimd", MONO_CPU_WASM_SIMD, packedsimd_methods, sizeof (packedsimd_methods) }, - { "WasmBase", MONO_CPU_WASM_BASE, wasmbase_methods, sizeof (wasmbase_methods) }, + { "PackedSimd", MONO_CPU_WASM_SIMD, packedsimd_methods, sizeof (packedsimd_methods) }, + { "RelaxedSimd", MONO_CPU_WASM_RELAXED_SIMD, relaxedsimd_methods, sizeof (relaxedsimd_methods) }, + { "WasmBase", MONO_CPU_WASM_BASE, wasmbase_methods, sizeof (wasmbase_methods) }, }; static const IntrinGroup supported_wasm_common_intrinsics [] = { @@ -6868,6 +6884,26 @@ emit_wasm_supported_intrinsics ( return emit_simd_ins_for_sig (cfg, klass, op, c0, arg0_type, fsig, args); } + if (feature == MONO_CPU_WASM_RELAXED_SIMD) { + uint16_t op = info->default_op; + uint16_t c0 = info->default_instc0; + + switch (id) { + case SN_ConvertToInt32: + op = OP_XOP_X_X; + c0 = arg0_type == MONO_TYPE_R8 ? INTRINS_WASM_RELAXED_TRUNC_SIGNED_ZERO : INTRINS_WASM_RELAXED_TRUNC_SIGNED; + break; + case SN_ConvertToUInt32: + op = OP_XOP_X_X; + c0 = arg0_type == MONO_TYPE_R8 ? INTRINS_WASM_RELAXED_TRUNC_UNSIGNED_ZERO : INTRINS_WASM_RELAXED_TRUNC_UNSIGNED; + break; + } + + // default emit path for cases with op set + if (op != 0) + return emit_simd_ins_for_sig (cfg, klass, op, c0, arg0_type, fsig, args); + } + g_assert_not_reached (); return NULL; diff --git a/src/mono/mono/mini/simd-methods.h b/src/mono/mono/mini/simd-methods.h index b696931ce51e92..e94e231a7ed308 100644 --- a/src/mono/mono/mini/simd-methods.h +++ b/src/mono/mono/mini/simd-methods.h @@ -297,6 +297,7 @@ METHOD(TestC) METHOD(TestNotZAndNotC) METHOD(TestZ) METHOD(DotProduct) +METHOD(DotProductAdd) METHOD(MultipleSumAbsoluteDifferences) // Sse42 METHOD(Crc32) @@ -690,6 +691,9 @@ METHOD(ExtractScalar) METHOD(LoadScalarAndInsert) METHOD(LoadScalarAndSplatVector128) METHOD(LoadWideningVector128) +METHOD(LaneSelect) +METHOD(MultiplyAddNegatedEstimate) +METHOD(MultiplyRoundedQ15) METHOD(MultiplyRoundedSaturateQ15) METHOD(PseudoMax) METHOD(PseudoMin) diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets index da14aeaaf6b5db..a4d2d3dd7338ec 100644 --- a/src/mono/wasi/build/WasiApp.targets +++ b/src/mono/wasi/build/WasiApp.targets @@ -369,10 +369,12 @@ <_WasmEHLibToExclude Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-wasm.a - <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true'">libmono-wasm-simd.a + <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">libmono-wasm-relaxed-simd.a + <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">libmono-wasm-simd.a <_WasmSIMDLib Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-nosimd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">libmono-wasm-simd.a - <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true'">libmono-wasm-nosimd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-simd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' != 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-nosimd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-relaxed-simd.a + <_WasmSIMDLibToExclude Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'">$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-nosimd.a;$(MicrosoftNetCoreAppRuntimePackRidNativeDir)libmono-wasm-simd.a @@ -403,7 +405,7 @@ <_WasmNativeFileForLinking Condition="'$(_WasmEHLib)' != ''" Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmEHLib)" /> <_WasmNativeFileForLinking Condition="'$(_WasmSIMDLib)' != ''" Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLib)" /> <_WasmNativeFileForLinking Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmEHLibToExclude)" /> - <_WasmNativeFileForLinking Remove="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)$(_WasmSIMDLibToExclude)" /> + <_WasmNativeFileForLinking Remove="$(_WasmSIMDLibToExclude)" /> <_WasmNativeFileForLinking Include="-lstdc++" /> <_WasmNativeFileForLinking Include="@(NativeFileReference)" /> diff --git a/src/mono/wasm/build/WasmApp.Common.targets b/src/mono/wasm/build/WasmApp.Common.targets index 9cff34e8753a3b..fb1c2774a84fba 100644 --- a/src/mono/wasm/build/WasmApp.Common.targets +++ b/src/mono/wasm/build/WasmApp.Common.targets @@ -79,6 +79,7 @@ - $(WasmAotProfilePath) - Path to an AOT profile file. - $(WasmEnableExceptionHandling) - Enable support for the WASM post MVP Exception Handling runtime extension. - $(WasmEnableSIMD) - Enable support for the WASM post MVP SIMD runtime extension. + - $(WasmEnableRelaxedSimd) - Enable support for the WASM Relaxed SIMD extension. Requires $(WasmEnableSIMD) == true. - $(WasmEnableWebcil) - Enable conversion of assembly .dlls to Webcil wrapped in .wasm (default: true) - $(WasmIncludeFullIcuData) - Loads full ICU data (icudt.dat). Defaults to false. Only applicable when InvariantGlobalization=false. - $(WasmIcuDataFileName) - Name/path of ICU globalization file loaded to app. Only when InvariantGloblization=false and WasmIncludeFullIcuData=false. @@ -187,6 +188,7 @@ false false + false false false @@ -465,8 +467,11 @@ + + @@ -632,6 +637,7 @@ + From 59f799ad090aaa5b20594b16e2d73985c04a179f Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Tue, 18 Aug 2026 20:44:54 -0500 Subject: [PATCH 11/31] Add CoreCLR Wasm R2R RelaxedSimd support Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e1f1453d-4249-46de-8828-f229e69adedb --- src/coreclr/crossgen-corelib.proj | 1 + src/coreclr/inc/corinfoinstructionset.h | 6 + src/coreclr/inc/jiteeversionguid.h | 10 +- src/coreclr/inc/readytoruninstructionset.h | 1 + src/coreclr/jit/hwintrinsic.cpp | 1 + src/coreclr/jit/hwintrinsiclistwasm.h | 15 ++ src/coreclr/jit/hwintrinsicwasm.cpp | 4 + src/coreclr/jit/instrswasm.h | 22 +++ .../Runtime/ReadyToRunInstructionSet.cs | 1 + .../Runtime/ReadyToRunInstructionSetHelper.cs | 1 + .../JitInterface/CorInfoInstructionSet.cs | 20 +++ .../ThunkGenerator/InstructionSetDesc.txt | 4 +- .../TestCases/R2RTestSuites.cs | 127 ++++++++++++++-- .../TestCases/Webcil/WasmSimdModule.cs | 136 ++++++++++++++++++ .../TestCasesRunner/R2RTestRunner.cs | 30 ++-- .../TestCasesRunner/TestPaths.cs | 43 ++++++ .../TestCasesRunner/WasmR2RAssert.cs | 22 +++ src/coreclr/tools/r2rdump/WasmDisassembler.cs | 22 +++ src/coreclr/vm/codeman.cpp | 13 ++ .../build/BrowserWasmApp.CoreCLR.targets | 14 ++ .../browser/runtime/es6/dotnet.es6.lib.js | 2 + src/mono/browser/runtime/startup.ts | 4 + src/mono/browser/runtime/types/internal.ts | 1 + .../corehost/browserhost/browserhost.cpp | 12 ++ 24 files changed, 488 insertions(+), 24 deletions(-) diff --git a/src/coreclr/crossgen-corelib.proj b/src/coreclr/crossgen-corelib.proj index 07ea4086b26a36..1c5bbbe7e9e26d 100644 --- a/src/coreclr/crossgen-corelib.proj +++ b/src/coreclr/crossgen-corelib.proj @@ -157,6 +157,7 @@ $(CrossGenDllCmd) --targetarch:$(TargetArchitecture) $(CrossGenDllCmd) --obj-format:$(PublishReadyToRunContainerFormat) $(CrossGenDllCmd) --codegenopt:JitWasmNyiToR2RUnsupported=1 + $(CrossGenDllCmd) --instruction-set:relaxed-simd $(CrossGenDllCmd) --composite $(CrossGenDllCmd) --targetos:$(TargetOS) $(CrossGenDllCmd) -m:$(MergedMibcPath) --embed-pgo-data diff --git a/src/coreclr/inc/corinfoinstructionset.h b/src/coreclr/inc/corinfoinstructionset.h index 2f347572ee34d4..2d43c6cb2d925f 100644 --- a/src/coreclr/inc/corinfoinstructionset.h +++ b/src/coreclr/inc/corinfoinstructionset.h @@ -70,6 +70,7 @@ enum CORINFO_InstructionSet InstructionSet_WasmBase=1, InstructionSet_PackedSimd=2, InstructionSet_Vector128=3, + InstructionSet_RelaxedSimd=4, #endif // TARGET_WASM #ifdef TARGET_AMD64 InstructionSet_X86Base=1, @@ -473,6 +474,8 @@ inline CORINFO_InstructionSetFlags EnsureInstructionSetFlagsAreValid(CORINFO_Ins #ifdef TARGET_WASM if (resultflags.HasInstructionSet(InstructionSet_Vector128) && !resultflags.HasInstructionSet(InstructionSet_PackedSimd)) resultflags.RemoveInstructionSet(InstructionSet_Vector128); + if (resultflags.HasInstructionSet(InstructionSet_RelaxedSimd) && !resultflags.HasInstructionSet(InstructionSet_PackedSimd)) + resultflags.RemoveInstructionSet(InstructionSet_RelaxedSimd); if (resultflags.HasInstructionSet(InstructionSet_PackedSimd) && !resultflags.HasInstructionSet(InstructionSet_WasmBase)) resultflags.RemoveInstructionSet(InstructionSet_PackedSimd); #endif // TARGET_WASM @@ -785,6 +788,8 @@ inline const char *InstructionSetToString(CORINFO_InstructionSet instructionSet) return "PackedSimd"; case InstructionSet_Vector128 : return "Vector128"; + case InstructionSet_RelaxedSimd : + return "RelaxedSimd"; #endif // TARGET_WASM #ifdef TARGET_AMD64 case InstructionSet_X86Base : @@ -993,6 +998,7 @@ inline CORINFO_InstructionSet InstructionSetFromR2RInstructionSet(ReadyToRunInst #ifdef TARGET_WASM case READYTORUN_INSTRUCTION_WasmBase: return InstructionSet_WasmBase; case READYTORUN_INSTRUCTION_PackedSimd: return InstructionSet_PackedSimd; + case READYTORUN_INSTRUCTION_RelaxedSimd: return InstructionSet_RelaxedSimd; #endif // TARGET_WASM #ifdef TARGET_AMD64 case READYTORUN_INSTRUCTION_X86Base: return InstructionSet_X86Base; diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 7256e79b2c6b29..86720b44131dac 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -37,11 +37,11 @@ #include -constexpr GUID JITEEVersionIdentifier = { /* 0d18a7df-af1f-4481-a72f-aa6cf8aa0a65 */ - 0x0d18a7df, - 0xaf1f, - 0x4481, - {0xa7, 0x2f, 0xaa, 0x6c, 0xf8, 0xaa, 0x0a, 0x65} +constexpr GUID JITEEVersionIdentifier = { /* 060c911b-5051-454b-bc66-3839c1a307da */ + 0x060c911b, + 0x5051, + 0x454b, + {0xbc, 0x66, 0x38, 0x39, 0xc1, 0xa3, 0x07, 0xda} }; #endif // JIT_EE_VERSIONING_GUID_H diff --git a/src/coreclr/inc/readytoruninstructionset.h b/src/coreclr/inc/readytoruninstructionset.h index d2851e91577f1e..4e1c644737d4ff 100644 --- a/src/coreclr/inc/readytoruninstructionset.h +++ b/src/coreclr/inc/readytoruninstructionset.h @@ -103,6 +103,7 @@ enum ReadyToRunInstructionSet READYTORUN_INSTRUCTION_Cssc=93, READYTORUN_INSTRUCTION_Zicond=94, READYTORUN_INSTRUCTION_Fp16=95, + READYTORUN_INSTRUCTION_RelaxedSimd=96, }; diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index a1058fbffb0ccf..e4e5a2a4e53fa4 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -1019,6 +1019,7 @@ static const HWIntrinsicIsaRange hwintrinsicIsaRangeArray[] = { { FIRST_NI_WasmBase, LAST_NI_WasmBase }, // WasmBase { FIRST_NI_PackedSimd, LAST_NI_PackedSimd }, // PackedSimd { FIRST_NI_Vector, LAST_NI_Vector }, // Vector128 + { FIRST_NI_RelaxedSimd, LAST_NI_RelaxedSimd }, // RelaxedSimd #else #error Unsupported platform #endif diff --git a/src/coreclr/jit/hwintrinsiclistwasm.h b/src/coreclr/jit/hwintrinsiclistwasm.h index c639f343656889..af27913d886d90 100644 --- a/src/coreclr/jit/hwintrinsiclistwasm.h +++ b/src/coreclr/jit/hwintrinsiclistwasm.h @@ -82,6 +82,21 @@ HARDWARE_INTRINSIC(PackedSimd, ZeroExtendWideningLower, HARDWARE_INTRINSIC(PackedSimd, ZeroExtendWideningUpper, 16, 1, INS_i16x8_extend_high_u_i8x16, INS_i16x8_extend_high_u_i8x16, INS_i32x4_extend_high_u_i16x8, INS_i32x4_extend_high_u_i16x8, INS_i64x2_extend_high_u_i32x4, INS_i64x2_extend_high_u_i32x4, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) #define LAST_NI_PackedSimd NI_PackedSimd_ZeroExtendWideningUpper +// RelaxedSimd Intrinsics +#define FIRST_NI_RelaxedSimd NI_RelaxedSimd_ConvertToInt32 +HARDWARE_INTRINSIC(RelaxedSimd, ConvertToInt32, 16, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_i32x4_relaxed_trunc_f32x4_s, INS_i32x4_relaxed_trunc_f64x2_s_zero, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, ConvertToUInt32, 16, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_i32x4_relaxed_trunc_f32x4_u, INS_i32x4_relaxed_trunc_f64x2_u_zero, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, DotProduct, 16, 2, INS_i16x8_relaxed_dot_i8x16_i7x16_s, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, DotProductAdd, 16, 3, INS_i32x4_relaxed_dot_i8x16_i7x16_add_s, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, LaneSelect, 16, 3, INS_i8x16_relaxed_laneselect, INS_i8x16_relaxed_laneselect, INS_i16x8_relaxed_laneselect, INS_i16x8_relaxed_laneselect, INS_i32x4_relaxed_laneselect, INS_i32x4_relaxed_laneselect, INS_i64x2_relaxed_laneselect, INS_i64x2_relaxed_laneselect, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, Max, 16, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_f32x4_relaxed_max, INS_f64x2_relaxed_max, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg|HW_Flag_Commutative) +HARDWARE_INTRINSIC(RelaxedSimd, Min, 16, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_f32x4_relaxed_min, INS_f64x2_relaxed_min, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg|HW_Flag_Commutative) +HARDWARE_INTRINSIC(RelaxedSimd, MultiplyAddEstimate, 16, 3, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_f32x4_relaxed_madd, INS_f64x2_relaxed_madd, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, MultiplyAddNegatedEstimate, 16, 3, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_f32x4_relaxed_nmadd, INS_f64x2_relaxed_nmadd, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, MultiplyRoundedQ15, 16, 2, INS_invalid, INS_invalid, INS_i16x8_relaxed_q15mulr_s, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg|HW_Flag_Commutative) +HARDWARE_INTRINSIC(RelaxedSimd, Swizzle, 16, 2, INS_i8x16_relaxed_swizzle, INS_i8x16_relaxed_swizzle, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +#define LAST_NI_RelaxedSimd NI_RelaxedSimd_Swizzle + // WasmBase Intrinsics // These scalar intrinsics are special-imported (see hwintrinsicwasm.cpp) and always rewritten into the // existing NI_PRIMITIVE_*ZeroCount GT_INTRINSIC nodes (which codegen already emits as wasm clz/ctz), so diff --git a/src/coreclr/jit/hwintrinsicwasm.cpp b/src/coreclr/jit/hwintrinsicwasm.cpp index 859f0390554eaa..13c2bd6c2f2d75 100644 --- a/src/coreclr/jit/hwintrinsicwasm.cpp +++ b/src/coreclr/jit/hwintrinsicwasm.cpp @@ -25,6 +25,10 @@ CORINFO_InstructionSet Compiler::lookupInstructionSet(const char* className) { return InstructionSet_PackedSimd; } + else if (strcmp(className, "RelaxedSimd") == 0) + { + return InstructionSet_RelaxedSimd; + } else if ((strcmp(className, "Vector128") == 0) || (strcmp(className, "Vector128`1") == 0)) { return InstructionSet_Vector128; diff --git a/src/coreclr/jit/instrswasm.h b/src/coreclr/jit/instrswasm.h index 095a1d997b497a..fc6cdd0be83b73 100644 --- a/src/coreclr/jit/instrswasm.h +++ b/src/coreclr/jit/instrswasm.h @@ -527,6 +527,28 @@ INST2(i32x4_trunc_sat_u_f64x2_zero, "i32x4.trunc_sat_u_f64x2_zero", 0, IF_OP INST2(f64x2_convert_low_s_i32x4, "f64x2.convert_low_s_i32x4", 0, IF_OPCODE, 0xFD, 254) INST2(f64x2_convert_low_u_i32x4, "f64x2.convert_low_u_i32x4", 0, IF_OPCODE, 0xFD, 255) +// Relaxed SIMD operations +INST2(i8x16_relaxed_swizzle, "i8x16.relaxed_swizzle", 0, IF_OPCODE, 0xFD, 0x100) +INST2(i32x4_relaxed_trunc_f32x4_s, "i32x4.relaxed_trunc_f32x4_s", 0, IF_OPCODE, 0xFD, 0x101) +INST2(i32x4_relaxed_trunc_f32x4_u, "i32x4.relaxed_trunc_f32x4_u", 0, IF_OPCODE, 0xFD, 0x102) +INST2(i32x4_relaxed_trunc_f64x2_s_zero, "i32x4.relaxed_trunc_f64x2_s_zero", 0, IF_OPCODE, 0xFD, 0x103) +INST2(i32x4_relaxed_trunc_f64x2_u_zero, "i32x4.relaxed_trunc_f64x2_u_zero", 0, IF_OPCODE, 0xFD, 0x104) +INST2(f32x4_relaxed_madd, "f32x4.relaxed_madd", 0, IF_OPCODE, 0xFD, 0x105) +INST2(f32x4_relaxed_nmadd, "f32x4.relaxed_nmadd", 0, IF_OPCODE, 0xFD, 0x106) +INST2(f64x2_relaxed_madd, "f64x2.relaxed_madd", 0, IF_OPCODE, 0xFD, 0x107) +INST2(f64x2_relaxed_nmadd, "f64x2.relaxed_nmadd", 0, IF_OPCODE, 0xFD, 0x108) +INST2(i8x16_relaxed_laneselect, "i8x16.relaxed_laneselect", 0, IF_OPCODE, 0xFD, 0x109) +INST2(i16x8_relaxed_laneselect, "i16x8.relaxed_laneselect", 0, IF_OPCODE, 0xFD, 0x10A) +INST2(i32x4_relaxed_laneselect, "i32x4.relaxed_laneselect", 0, IF_OPCODE, 0xFD, 0x10B) +INST2(i64x2_relaxed_laneselect, "i64x2.relaxed_laneselect", 0, IF_OPCODE, 0xFD, 0x10C) +INST2(f32x4_relaxed_min, "f32x4.relaxed_min", 0, IF_OPCODE, 0xFD, 0x10D) +INST2(f32x4_relaxed_max, "f32x4.relaxed_max", 0, IF_OPCODE, 0xFD, 0x10E) +INST2(f64x2_relaxed_min, "f64x2.relaxed_min", 0, IF_OPCODE, 0xFD, 0x10F) +INST2(f64x2_relaxed_max, "f64x2.relaxed_max", 0, IF_OPCODE, 0xFD, 0x110) +INST2(i16x8_relaxed_q15mulr_s, "i16x8.relaxed_q15mulr_s", 0, IF_OPCODE, 0xFD, 0x111) +INST2(i16x8_relaxed_dot_i8x16_i7x16_s, "i16x8.relaxed_dot_i8x16_i7x16_s", 0, IF_OPCODE, 0xFD, 0x112) +INST2(i32x4_relaxed_dot_i8x16_i7x16_add_s, "i32x4.relaxed_dot_i8x16_i7x16_add_s", 0, IF_OPCODE, 0xFD, 0x113) + // clang-format on #undef INST diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSet.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSet.cs index 4c5574d6870c32..075243f63cf82e 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSet.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSet.cs @@ -106,5 +106,6 @@ public enum ReadyToRunInstructionSet Cssc = 93, Zicond = 94, Fp16 = 95, + RelaxedSimd = 96, } } diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSetHelper.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSetHelper.cs index 632e3053189a72..0d9f42b7b29d4c 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSetHelper.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunInstructionSetHelper.cs @@ -89,6 +89,7 @@ public static class ReadyToRunInstructionSetHelper case InstructionSet.Wasm32_WasmBase: return ReadyToRunInstructionSet.WasmBase; case InstructionSet.Wasm32_PackedSimd: return ReadyToRunInstructionSet.PackedSimd; case InstructionSet.Wasm32_Vector128: return null; + case InstructionSet.Wasm32_RelaxedSimd: return ReadyToRunInstructionSet.RelaxedSimd; default: throw new Exception("Unknown instruction set"); } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs b/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs index 870544e2156425..851b2844cbf929 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs @@ -66,6 +66,7 @@ public enum InstructionSet Wasm32_WasmBase = InstructionSet_Wasm32.WasmBase, Wasm32_PackedSimd = InstructionSet_Wasm32.PackedSimd, Wasm32_Vector128 = InstructionSet_Wasm32.Vector128, + Wasm32_RelaxedSimd = InstructionSet_Wasm32.RelaxedSimd, X64_X86Base = InstructionSet_X64.X86Base, X64_AVX = InstructionSet_X64.AVX, X64_AVX2 = InstructionSet_X64.AVX2, @@ -224,6 +225,7 @@ public enum InstructionSet_Wasm32 WasmBase = 1, PackedSimd = 2, Vector128 = 3, + RelaxedSimd = 4, } public enum InstructionSet_X64 @@ -620,6 +622,8 @@ public static InstructionSetFlags ExpandInstructionSetByImplicationHelper(Target case TargetArchitecture.Wasm32: if (resultflags.HasInstructionSet(InstructionSet.Wasm32_Vector128)) resultflags.AddInstructionSet(InstructionSet.Wasm32_PackedSimd); + if (resultflags.HasInstructionSet(InstructionSet.Wasm32_RelaxedSimd)) + resultflags.AddInstructionSet(InstructionSet.Wasm32_PackedSimd); if (resultflags.HasInstructionSet(InstructionSet.Wasm32_PackedSimd)) resultflags.AddInstructionSet(InstructionSet.Wasm32_WasmBase); break; @@ -931,6 +935,8 @@ private static InstructionSetFlags ExpandInstructionSetByReverseImplicationHelpe case TargetArchitecture.Wasm32: if (resultflags.HasInstructionSet(InstructionSet.Wasm32_PackedSimd)) resultflags.AddInstructionSet(InstructionSet.Wasm32_Vector128); + if (resultflags.HasInstructionSet(InstructionSet.Wasm32_PackedSimd)) + resultflags.AddInstructionSet(InstructionSet.Wasm32_RelaxedSimd); if (resultflags.HasInstructionSet(InstructionSet.Wasm32_WasmBase)) resultflags.AddInstructionSet(InstructionSet.Wasm32_PackedSimd); break; @@ -1194,6 +1200,7 @@ public static IEnumerable ArchitectureToValidInstructionSets yield return new InstructionSetInfo("base", "WasmBase", InstructionSet.Wasm32_WasmBase, true); yield return new InstructionSetInfo("simd128", "PackedSimd", InstructionSet.Wasm32_PackedSimd, true); yield return new InstructionSetInfo("Vector128", "", InstructionSet.Wasm32_Vector128, false); + yield return new InstructionSetInfo("relaxed-simd", "RelaxedSimd", InstructionSet.Wasm32_RelaxedSimd, true); break; case TargetArchitecture.X64: @@ -1663,6 +1670,9 @@ public static InstructionSet LookupPlatformIntrinsicInstructionSet(TargetArchite case "PackedSimd": return InstructionSet.Wasm32_PackedSimd; + case "RelaxedSimd": + return InstructionSet.Wasm32_RelaxedSimd; + default: return InstructionSet.ILLEGAL; } @@ -2369,6 +2379,16 @@ public static IEnumerable LookupPlatformIntrinsicTypes(TypeSystemC } break; + case (InstructionSet.Wasm32_RelaxedSimd, TargetArchitecture.Wasm32): + { + var type = context.SystemModule.GetType("System.Runtime.Intrinsics.Wasm"u8, "RelaxedSimd"u8, false); + if (type != null) + { + yield return type; + } + } + break; + case (InstructionSet.X64_X86Base, TargetArchitecture.X64): case (InstructionSet.X64_X86Base_X64, TargetArchitecture.X64): { diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt index a70cd7d9991687..a8a04d44dec630 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetDesc.txt @@ -26,7 +26,7 @@ ; DO NOT CHANGE R2R NUMERIC VALUES OF THE EXISTING SETS. Changing R2R numeric values definitions would be R2R format breaking change. ; The ISA definitions should also be mapped to `hwintrinsicIsaRangeArray` in hwintrinsic.cpp. -; NEXT_AVAILABLE_R2R_BIT = 96 +; NEXT_AVAILABLE_R2R_BIT = 97 ; Definition of X86 instruction sets definearch ,X86 ,32Bit ,X64, X64, X86 @@ -323,7 +323,9 @@ definearch ,Wasm32 ,32Bit , , ,Wasm instructionset ,Wasm32 ,WasmBase , ,91 ,WasmBase ,base instructionset ,Wasm32 ,PackedSimd , ,92 ,PackedSimd ,simd128 instructionset ,Wasm32 , , , ,Vector128 , +instructionset ,Wasm32 ,RelaxedSimd , ,96 ,RelaxedSimd ,relaxed-simd vectorinstructionset ,Wasm32 ,Vector128 implication ,Wasm32 ,Vector128 ,PackedSimd +implication ,Wasm32 ,RelaxedSimd ,PackedSimd implication ,Wasm32 ,PackedSimd ,WasmBase \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index 9bde75896d70a5..3c5d3bf2f7a4d4 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -195,20 +195,131 @@ static void Validate(ReadyToRunReader reader) static string Format(IReadOnlyList valTypes) => $"[{string.Join(",", valTypes.Select(b => $"0x{b:X2}"))}]"; } + } + + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsBrowserWasmRuntimePackAvailable))] + public void WasmRelaxedSimdModule() + { + var wasmRelaxedSimdModule = new CompiledAssembly + { + AssemblyName = nameof(WasmRelaxedSimdModule), + SourceResourceNames = ["Webcil/WasmSimdModule.cs"], + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(WasmRelaxedSimdModule), + [ + new(nameof(WasmRelaxedSimdModule), [new CrossgenAssembly(wasmRelaxedSimdModule)]) + { + OutputFileExtension = ".wasm", + AdditionalArgs = + { + "--targetarch", + "wasm", + "--targetos", + "browser", + "--instruction-set", + "relaxed-simd", + }, + TargetRuntimeIdentifier = "browser-wasm", + Validate = Validate, + }, + ])); - static WebcilImageReader.WasmFunctionInfo ResolveWasmBody( - ReadyToRunReader reader, WebcilImageReader webcilReader, ReadyToRunMethod method) + static void Validate(ReadyToRunReader reader) { - uint tableIndex = checked(reader.WasmMinFunctionTableIndex + (uint)method.EntryPointRuntimeFunctionId); - int functionIndex = webcilReader.GetFunctionIndexFromTableIndex(tableIndex); - Assert.True(functionIndex >= 0, $"Could not resolve wasm table index {tableIndex} to a function body."); + var webcilReader = Assert.IsType(reader.CompositeReader); + List methods = R2RAssert.GetAllMethods(reader); - WebcilImageReader.WasmFunctionInfo? body = webcilReader.GetWasmFunctionBody(functionIndex); - Assert.True(body is not null, $"Wasm function body {functionIndex} was not found."); - return body.Value; + foreach ((string name, uint opcode) in + new[] + { + ("RelaxedSwizzle", 0x100u), + ("RelaxedConvertF32ToInt32", 0x101u), + ("RelaxedConvertF32ToUInt32", 0x102u), + ("RelaxedConvertF64ToInt32", 0x103u), + ("RelaxedConvertF64ToUInt32", 0x104u), + ("RelaxedMultiplyAddF32", 0x105u), + ("RelaxedMultiplyAddNegatedF32", 0x106u), + ("RelaxedMultiplyAddF64", 0x107u), + ("RelaxedMultiplyAddNegatedF64", 0x108u), + ("RelaxedLaneSelectI8", 0x109u), + ("RelaxedLaneSelectI16", 0x10Au), + ("RelaxedLaneSelectI32", 0x10Bu), + ("RelaxedLaneSelectI64", 0x10Cu), + ("RelaxedMinF32", 0x10Du), + ("RelaxedMaxF32", 0x10Eu), + ("RelaxedMinF64", 0x10Fu), + ("RelaxedMaxF64", 0x110u), + ("RelaxedMultiplyRoundedQ15", 0x111u), + ("RelaxedDotProduct", 0x112u), + ("RelaxedDotProductAdd", 0x113u), + }) + { + ReadyToRunMethod method = Assert.Single( + methods, m => m.SignatureString.Contains($".{name}(", StringComparison.Ordinal)); + WebcilImageReader.WasmFunctionInfo body = ResolveWasmBody(reader, webcilReader, method); + + Assert.True( + WasmR2RAssert.WasmFunctionContainsPrefixedOpcode(body, 0xFD, opcode), + $"'{name}' did not contain relaxed SIMD opcode 0xFD 0x{opcode:X}."); + } } } + [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsBrowserWasmRuntimePackAvailable))] + public void WasmRelaxedSimdDisabledModule() + { + var wasmRelaxedSimdDisabledModule = new CompiledAssembly + { + AssemblyName = nameof(WasmRelaxedSimdDisabledModule), + SourceResourceNames = ["Webcil/WasmSimdModule.cs"], + }; + + new R2RTestRunner(_output).Run(new R2RTestCase( + nameof(WasmRelaxedSimdDisabledModule), + [ + new(nameof(WasmRelaxedSimdDisabledModule), [new CrossgenAssembly(wasmRelaxedSimdDisabledModule)]) + { + OutputFileExtension = ".wasm", + AdditionalArgs = + { + "--targetarch", + "wasm", + "--targetos", + "browser", + }, + TargetRuntimeIdentifier = "browser-wasm", + Validate = Validate, + }, + ])); + + static void Validate(ReadyToRunReader reader) + { + var webcilReader = Assert.IsType(reader.CompositeReader); + List methods = R2RAssert.GetAllMethods(reader); + ReadyToRunMethod method = Assert.Single( + methods, m => m.SignatureString.Contains(".RelaxedSwizzleIfSupported(", StringComparison.Ordinal)); + WebcilImageReader.WasmFunctionInfo body = ResolveWasmBody(reader, webcilReader, method); + + Assert.False( + WasmR2RAssert.WasmFunctionContainsPrefixedOpcode(body, 0xFD, 0x100), + "The default Wasm instruction-set baseline emitted a relaxed SIMD opcode."); + } + } + + private static WebcilImageReader.WasmFunctionInfo ResolveWasmBody( + ReadyToRunReader reader, WebcilImageReader webcilReader, ReadyToRunMethod method) + { + uint tableIndex = checked(reader.WasmMinFunctionTableIndex + (uint)method.EntryPointRuntimeFunctionId); + int functionIndex = webcilReader.GetFunctionIndexFromTableIndex(tableIndex); + Assert.True(functionIndex >= 0, $"Could not resolve wasm table index {tableIndex} to a function body."); + + WebcilImageReader.WasmFunctionInfo? body = webcilReader.GetWasmFunctionBody(functionIndex); + Assert.True(body is not null, $"Wasm function body {functionIndex} was not found."); + return body.Value; + } + [Fact] public void RuntimeFunctionsSectionSizeExcludesSentinel() { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs index 77518d9ee5336d..b3353688d4ac93 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs @@ -4,6 +4,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Wasm; namespace Webcil; @@ -50,6 +51,141 @@ public static Vector CallEchoVectorT(Vector value) return EchoVectorT(value); } + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedSwizzle(Vector128 value, Vector128 indices) + { + return RelaxedSimd.Swizzle(value, indices); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedConvertF32ToInt32(Vector128 value) + { + return RelaxedSimd.ConvertToInt32(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedConvertF32ToUInt32(Vector128 value) + { + return RelaxedSimd.ConvertToUInt32(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedConvertF64ToInt32(Vector128 value) + { + return RelaxedSimd.ConvertToInt32(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedConvertF64ToUInt32(Vector128 value) + { + return RelaxedSimd.ConvertToUInt32(value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMultiplyAddF32( + Vector128 left, Vector128 right, Vector128 addend) + { + return RelaxedSimd.MultiplyAddEstimate(left, right, addend); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMultiplyAddNegatedF32( + Vector128 left, Vector128 right, Vector128 addend) + { + return RelaxedSimd.MultiplyAddNegatedEstimate(left, right, addend); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMultiplyAddF64( + Vector128 left, Vector128 right, Vector128 addend) + { + return RelaxedSimd.MultiplyAddEstimate(left, right, addend); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMultiplyAddNegatedF64( + Vector128 left, Vector128 right, Vector128 addend) + { + return RelaxedSimd.MultiplyAddNegatedEstimate(left, right, addend); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedLaneSelectI8( + Vector128 left, Vector128 right, Vector128 mask) + { + return RelaxedSimd.LaneSelect(left, right, mask); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedLaneSelectI16( + Vector128 left, Vector128 right, Vector128 mask) + { + return RelaxedSimd.LaneSelect(left, right, mask); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedLaneSelectI32( + Vector128 left, Vector128 right, Vector128 mask) + { + return RelaxedSimd.LaneSelect(left, right, mask); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedLaneSelectI64( + Vector128 left, Vector128 right, Vector128 mask) + { + return RelaxedSimd.LaneSelect(left, right, mask); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMinF32(Vector128 left, Vector128 right) + { + return RelaxedSimd.Min(left, right); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMaxF32(Vector128 left, Vector128 right) + { + return RelaxedSimd.Max(left, right); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMinF64(Vector128 left, Vector128 right) + { + return RelaxedSimd.Min(left, right); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMaxF64(Vector128 left, Vector128 right) + { + return RelaxedSimd.Max(left, right); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedMultiplyRoundedQ15(Vector128 left, Vector128 right) + { + return RelaxedSimd.MultiplyRoundedQ15(left, right); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedDotProduct(Vector128 left, Vector128 right) + { + return RelaxedSimd.DotProduct(left, right); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedDotProductAdd( + Vector128 left, Vector128 right, Vector128 accumulator) + { + return RelaxedSimd.DotProductAdd(left, right, accumulator); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static Vector128 RelaxedSwizzleIfSupported(Vector128 value, Vector128 indices) + { + return RelaxedSimd.IsSupported ? RelaxedSimd.Swizzle(value, indices) : value; + } + // A single-field struct wrapping a v128 is itself passed/returned as a v128, matching emscripten. public struct WrappedVector128 { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs index f466fd20d336e7..1117f3c6b7fb4b 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/R2RTestRunner.cs @@ -89,6 +89,11 @@ internal sealed class CrossgenCompilation(string name, List as /// public List AdditionalArgs { get; init; } = new(); + /// + /// Optional runtime pack RID whose implementation assemblies should be used as Crossgen2 references. + /// + public string? TargetRuntimeIdentifier { get; init; } + /// /// Optional validator for this compilation's R2R output image. /// @@ -209,10 +214,9 @@ public void Run(R2RTestCase testCase) // Step 2: Run each crossgen2 compilation and validate var driver = new R2RDriver(_output, _paths); - var refPaths = BuildReferencePaths(); - foreach(var compilation in testCase.Compilations) { + List refPaths = BuildReferencePaths(compilation.TargetRuntimeIdentifier); string outputPath = RunCrossgenCompilation( testCase.Name, compilation, driver, compilation.FilePath, refPaths, assemblyPaths); @@ -339,18 +343,24 @@ private static void AddRefArgs(List args, List refPaths) } } - private List BuildReferencePaths() + private List BuildReferencePaths(string? targetRuntimeIdentifier) { var paths = new List(); - paths.Add(Path.Combine(_paths.RuntimePackDir, "*.dll")); + string runtimePackDir = _paths.RuntimePackDir; + string systemPrivateCoreLibPath = _paths.SystemPrivateCoreLibPath; + + if (targetRuntimeIdentifier is not null) + { + (runtimePackDir, string runtimePackNativeDir) = + _paths.GetRuntimePackDirectories(targetRuntimeIdentifier); + systemPrivateCoreLibPath = Path.Combine(runtimePackNativeDir, "System.Private.CoreLib.dll"); + } - string spcl = _paths.SystemPrivateCoreLibPath; - Assert.True(File.Exists(spcl), - $"System.Private.CoreLib.dll not found at '{spcl}'. " + - $"Searched RuntimePackNativeDir='{_paths.RuntimePackNativeDir}' and " + - $"CoreCLRArtifactsDir='{_paths.CoreCLRArtifactsDir}'"); - paths.Add(spcl); + paths.Add(Path.Combine(runtimePackDir, "*.dll")); + Assert.True(File.Exists(systemPrivateCoreLibPath), + $"System.Private.CoreLib.dll not found at '{systemPrivateCoreLibPath}'."); + paths.Add(systemPrivateCoreLibPath); return paths; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs index f028b442cf8e86..bb94386fc8dfb2 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/TestPaths.cs @@ -23,6 +23,23 @@ public TestPaths(ITestOutputHelper output) _output = output; } + public static bool IsBrowserWasmRuntimePackAvailable + { + get + { + DirectoryInfo? directory = new(AppContext.BaseDirectory); + + while ((directory is not null) && + ((directory.Name != "bin") || (directory.Parent?.Name != "artifacts"))) + { + directory = directory.Parent; + } + + return (directory is not null) && + Directory.Exists(Path.Combine(directory.FullName, "microsoft.netcore.app.runtime.browser-wasm")); + } + } + private static string GetRequiredConfig(string key) { return AppContext.GetData(key) as string @@ -208,6 +225,32 @@ public string RefPackDir } } + public (string RuntimePackDir, string RuntimePackNativeDir) GetRuntimePackDirectories( + string runtimeIdentifier) + { + string artifactsBin = Path.GetFullPath(Path.Combine(CoreCLRArtifactsDir, "..", "..")); + string runtimePackRoot = Path.Combine( + artifactsBin, + $"microsoft.netcore.app.runtime.{runtimeIdentifier}", + CoreCLRConfiguration, + "runtimes", + runtimeIdentifier); + runtimePackRoot = ProbeConfigFallback(runtimePackRoot); + + string runtimePackLibRoot = Path.Combine(runtimePackRoot, "lib"); + if (!Directory.Exists(runtimePackLibRoot)) + throw new DirectoryNotFoundException($"Runtime pack library directory not found: {runtimePackLibRoot}"); + + string[] targetFrameworkDirectories = Directory.GetDirectories(runtimePackLibRoot); + if (targetFrameworkDirectories.Length != 1) + { + throw new InvalidOperationException( + $"Expected one target framework directory under '{runtimePackLibRoot}', found {targetFrameworkDirectories.Length}."); + } + + return (targetFrameworkDirectories[0], Path.Combine(runtimePackRoot, "native")); + } + /// /// Returns the target triple string for crossgen2 (e.g. "linux-x64"). /// diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/WasmR2RAssert.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/WasmR2RAssert.cs index acf4a493508750..8a2e4bb2355765 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/WasmR2RAssert.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/WasmR2RAssert.cs @@ -50,6 +50,28 @@ public static bool WasmImageContainsWellKnownGlobalGet(WebcilImageReader reader, return false; } + public static bool WasmFunctionContainsPrefixedOpcode( + WebcilImageReader.WasmFunctionInfo body, byte prefix, uint subOpcode) + { + Span pattern = stackalloc byte[6]; + pattern[0] = prefix; + int patternLength = 1; + + do + { + byte encodedByte = (byte)(subOpcode & 0x7F); + subOpcode >>= 7; + if (subOpcode != 0) + encodedByte |= 0x80; + + pattern[patternLength++] = encodedByte; + } + while (subOpcode != 0); + + ReadOnlySpan instructions = body.Image.AsSpan().Slice(body.InstructionOffset, body.InstructionLength); + return instructions.IndexOf(pattern.Slice(0, patternLength)) >= 0; + } + /// /// Returns true if the default Webcil imports and defined section entries occupy the expected /// indices in their respective WASM external-kind index spaces. diff --git a/src/coreclr/tools/r2rdump/WasmDisassembler.cs b/src/coreclr/tools/r2rdump/WasmDisassembler.cs index 268f6632ea43d8..950c867f45de38 100644 --- a/src/coreclr/tools/r2rdump/WasmDisassembler.cs +++ b/src/coreclr/tools/r2rdump/WasmDisassembler.cs @@ -915,6 +915,28 @@ private string DecodeFDPrefixed() case 254: return "f32x4.demote_f64x2_zero"; case 255: return "f64x2.promote_low_f32x4"; + // Relaxed SIMD instructions + case 0x100: return "i8x16.relaxed_swizzle"; + case 0x101: return "i32x4.relaxed_trunc_f32x4_s"; + case 0x102: return "i32x4.relaxed_trunc_f32x4_u"; + case 0x103: return "i32x4.relaxed_trunc_f64x2_s_zero"; + case 0x104: return "i32x4.relaxed_trunc_f64x2_u_zero"; + case 0x105: return "f32x4.relaxed_madd"; + case 0x106: return "f32x4.relaxed_nmadd"; + case 0x107: return "f64x2.relaxed_madd"; + case 0x108: return "f64x2.relaxed_nmadd"; + case 0x109: return "i8x16.relaxed_laneselect"; + case 0x10A: return "i16x8.relaxed_laneselect"; + case 0x10B: return "i32x4.relaxed_laneselect"; + case 0x10C: return "i64x2.relaxed_laneselect"; + case 0x10D: return "f32x4.relaxed_min"; + case 0x10E: return "f32x4.relaxed_max"; + case 0x10F: return "f64x2.relaxed_min"; + case 0x110: return "f64x2.relaxed_max"; + case 0x111: return "i16x8.relaxed_q15mulr_s"; + case 0x112: return "i16x8.relaxed_dot_i8x16_i7x16_s"; + case 0x113: return "i32x4.relaxed_dot_i8x16_i7x16_add_s"; + default: return $""; } diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index b9041ad1ed51de..cef229c92d8ee7 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1386,6 +1386,15 @@ extern "C" DWORD64 __stdcall GetDataCacheZeroIDReg(); extern "C" uint64_t GetSveLengthFromOS(); #endif +#ifdef TARGET_WASM +static bool s_wasmRelaxedSimdSupported; + +extern "C" void coreclr_wasm_set_relaxed_simd_supported() +{ + s_wasmRelaxedSimdSupported = true; +} +#endif + void EEJitManager::SetCpuInfo() { LIMITED_METHOD_CONTRACT; @@ -1430,6 +1439,10 @@ void EEJitManager::SetCpuInfo() #if defined(TARGET_WASM) CPUCompileFlags.Set(InstructionSet_WasmBase); CPUCompileFlags.Set(InstructionSet_PackedSimd); + if (s_wasmRelaxedSimdSupported) + { + CPUCompileFlags.Set(InstructionSet_RelaxedSimd); + } #endif #if defined(TARGET_X86) || defined(TARGET_AMD64) diff --git a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets index 0e8b915873fa2d..df64524422f4b3 100644 --- a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets +++ b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets @@ -25,9 +25,12 @@ <_CoreCLRInvalidSIMD Condition="'$(WasmEnableSIMD)' != '' and '$(WasmEnableSIMD)' != 'true'">true true true + false emcc <_WasmDefaultFlags>-fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0 -msimd128 + <_WasmDefaultFlags Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_WasmDefaultFlags) -mrelaxed-simd + $(PublishReadyToRunCrossgen2ExtraArgs);--instruction-set=relaxed-simd Build Publish @@ -58,6 +61,12 @@ <_ExeExt Condition="$([MSBuild]::IsOSPlatform('windows'))">.exe + + + + $(Features.Replace('nullablePublicOnly', '') diff --git a/src/mono/browser/browser.proj b/src/mono/browser/browser.proj index 710a36801a553b..919cb61895ed5e 100644 --- a/src/mono/browser/browser.proj +++ b/src/mono/browser/browser.proj @@ -21,6 +21,7 @@ true false true + false true 2147483648 true @@ -277,6 +278,7 @@ { "identity": "WasmNativeStrip", "defaultValueInRuntimePack": "$(WasmNativeStrip)" }, { "identity": "WasmSingleFileBundle", "defaultValueInRuntimePack": "$(WasmSingleFileBundle)" }, { "identity": "WasmEnableSIMD", "defaultValueInRuntimePack": "$(WasmEnableSIMD)" }, + { "identity": "WasmEnableRelaxedSimd", "defaultValueInRuntimePack": "$(WasmEnableRelaxedSimd)" }, { "identity": "WasmEnableExceptionHandling", "defaultValueInRuntimePack": "$(WasmEnableExceptionHandling)" }, { "identity": "EnableDiagnostics", "defaultValueInRuntimePack": "$(EnableDiagnostics)" }, { "identity": "WasmProfilers", "defaultValueInRuntimePack": "$(WasmProfilers)" }, diff --git a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.targets.in b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.targets.in index ad0e43d11e1f6b..dce5d8bbbc5482 100644 --- a/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.targets.in +++ b/src/mono/nuget/Microsoft.NET.Workload.Mono.Toolchain.Current.Manifest/WorkloadManifest.targets.in @@ -54,6 +54,7 @@ <_WasmNativeWorkloadNeeded Condition=" '$(WasmEnableSIMD)' == 'false' or + '$(WasmEnableRelaxedSimd)' == 'true' or '$(WasmEnableExceptionHandling)' == 'false' or '$(InvariantTimezone)' == 'true' or '$(WasmNativeStrip)' == 'false' or diff --git a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs index 9dbf10ac2a365e..bf19729961dc31 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Blazor/WorkloadRequiredTests.cs @@ -23,6 +23,7 @@ public static (string propertyName, bool triggerValue)[] PropertiesWithTriggerVa { ("RunAOTCompilation", true), ("WasmEnableSIMD", false), + ("WasmEnableRelaxedSimd", true), ("WasmEnableExceptionHandling", false), ("InvariantTimezone", true), //("InvariantGlobalization", true), - not applicable for blazor diff --git a/src/mono/wasm/Wasm.Build.Tests/CoreCLRWasmNativeDefaultsTests.cs b/src/mono/wasm/Wasm.Build.Tests/CoreCLRWasmNativeDefaultsTests.cs index cb5375c5883cfe..570cfc9e4ab0ae 100644 --- a/src/mono/wasm/Wasm.Build.Tests/CoreCLRWasmNativeDefaultsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/CoreCLRWasmNativeDefaultsTests.cs @@ -31,6 +31,7 @@ public CoreCLRWasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTe { "false", false }, { "false", false }, { "false", false }, + { "false", false }, // property left unset -> no relink { "", false }, // casing must not matter, MSBuild string comparison is case-insensitive @@ -39,6 +40,7 @@ public CoreCLRWasmNativeDefaultsTests(ITestOutputHelper output, SharedBuildPerTe { "true", true }, { "true", true }, { "true", true }, + { "true", true }, { "67108864", true }, // EmccInitialHeapSize is the documented browser property and EmccTotalMemory its legacy // alias; both must be normalized into WasmInitialHeapSize before the registry compares. diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs index 098662f455dafe..d5e6605189ea02 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmNativeDefaultsTests.cs @@ -25,6 +25,7 @@ public static TheoryData SettingDiffere List<(string propertyName, bool defaultValueInRuntimePack)> defaults = new() { ("WasmEnableSIMD", true), + ("WasmEnableRelaxedSimd", false), ("WasmEnableExceptionHandling", true), ("InvariantTimezone", false), ("InvariantGlobalization", false), diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs index ed7ae6a13bd1ba..f4521b5acb9e58 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs @@ -66,6 +66,99 @@ public async Task PublishSIMD_AOT(Configuration config, bool aot, bool simd) Assert.Contains(result.TestOutput, m => m.Contains("Hello, World!")); } + [Theory] + [InlineData(false)] + [InlineData(true)] + [TestCategory("mono")] + public Task PublishRelaxedSimdMono(bool relaxedSimd) => PublishRelaxedSimd(relaxedSimd); + + [Theory] + [InlineData(false)] + [InlineData(true)] + [TestCategory("coreclr")] + public Task PublishRelaxedSimdCoreClr(bool relaxedSimd) => PublishRelaxedSimd(relaxedSimd); + + private async Task PublishRelaxedSimd(bool relaxedSimd) + { + Configuration config = Configuration.Debug; + string relaxedSimdValue = relaxedSimd.ToString().ToLowerInvariant(); + string extraProperties = $"{relaxedSimdValue}"; + ProjectInfo info = CopyTestAsset( + config, + aot: false, + TestAsset.WasmBasicTestApp, + $"relaxed_simd_{relaxedSimdValue}", + extraProperties: extraProperties); + UpdateFile( + Path.Combine("Common", "Program.cs"), + IsCoreClrRuntime + ? GetCoreClrRelaxedSimdProgramText(relaxedSimdValue) + : GetMonoRelaxedSimdProgramText(relaxedSimdValue)); + ReplaceMainJsWithMinimalRunMain(); + + PublishProject(info, config, isNativeBuild: relaxedSimd); + + RunResult result = await RunForPublishWithWebServer(new BrowserRunOptions( + config, + TestScenario: "DotnetRun", + ExpectedExitCode: 42)); + string expectedOutput = IsCoreClrRuntime + ? $"RelaxedSimd config: {(relaxedSimd ? "true" : "")}" + : $"RelaxedSimd.IsSupported: {relaxedSimd}"; + Assert.Contains(result.TestOutput, message => message.Contains(expectedOutput)); + } + + private static string GetCoreClrRelaxedSimdProgramText(string expectedConfigValue) => $$""" + using System; + + public class TestClass + { + public static int Main() + { + string configuredValue = AppContext.GetData( + "System.Runtime.Intrinsics.Wasm.RelaxedSimd.IsSupported") as string; + Console.WriteLine($"TestOutput -> RelaxedSimd config: {configuredValue ?? ""}"); + + return configuredValue == {{(expectedConfigValue == "true" ? "\"true\"" : "null")}} + ? 42 + : 1; + } + } + """; + + private static string GetMonoRelaxedSimdProgramText(string expectedIsSupported) => $$""" + using System; + using System.Runtime.Intrinsics; + using System.Runtime.Intrinsics.Wasm; + + public class TestClass + { + public static int Main() + { + bool isSupported = RelaxedSimd.IsSupported; + Console.WriteLine($"TestOutput -> RelaxedSimd.IsSupported: {isSupported}"); + + if (isSupported != {{expectedIsSupported}}) + { + return 1; + } + + if (isSupported) + { + Vector128 result = RelaxedSimd.ConvertToInt32Native( + Vector128.Create(1.75f, -2.25f, 3.0f, -4.99f)); + + if (result != Vector128.Create(1, -2, 3, -4)) + { + return 2; + } + } + + return 42; + } + } + """; + private static string s_simdProgramText = @" using System; using System.Runtime.Intrinsics; From a71579eabc95f083aaeb18e03c9596e12a9fe656 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Wed, 2 Sep 2026 13:55:01 -0500 Subject: [PATCH 15/31] [wasm] Ignore unresolved P/Invokes in intrinsics tests Relaxed SIMD forces a CoreCLR browser native relink, which scans the untrimmed cross-platform test closure. Disable unresolved-module warnings for this browser test project, matching existing invariant test projects. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- .../tests/System.Runtime.Intrinsics.Tests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj index 84c95ad80529bf..e12121e625bb82 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj +++ b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj @@ -6,6 +6,7 @@ true $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-browser true + false $(Features.Replace('nullablePublicOnly', '') From d84ac7a83f91689265756d011e5409c649c49d31 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Thu, 3 Sep 2026 15:09:45 -0500 Subject: [PATCH 16/31] Keep Wasm RelaxedSimd opt-in Do not let PackedSimd reverse implication expansion add RelaxedSimd. Packed SIMD remains the wasm default baseline, while RelaxedSimd is only enabled explicitly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3de5ced8-be89-4f1b-bff4-5b3c577cedac --- .../tools/Common/JitInterface/CorInfoInstructionSet.cs | 2 -- .../JitInterface/ThunkGenerator/InstructionSetGenerator.cs | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs b/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs index 851b2844cbf929..5ae9a3c0dafcc2 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs @@ -935,8 +935,6 @@ private static InstructionSetFlags ExpandInstructionSetByReverseImplicationHelpe case TargetArchitecture.Wasm32: if (resultflags.HasInstructionSet(InstructionSet.Wasm32_PackedSimd)) resultflags.AddInstructionSet(InstructionSet.Wasm32_Vector128); - if (resultflags.HasInstructionSet(InstructionSet.Wasm32_PackedSimd)) - resultflags.AddInstructionSet(InstructionSet.Wasm32_RelaxedSimd); if (resultflags.HasInstructionSet(InstructionSet.Wasm32_WasmBase)) resultflags.AddInstructionSet(InstructionSet.Wasm32_PackedSimd); break; diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetGenerator.cs b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetGenerator.cs index d15da9ddbff34c..7a511404a947f1 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetGenerator.cs +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetGenerator.cs @@ -670,6 +670,12 @@ private static InstructionSetFlags ExpandInstructionSetByReverseImplicationHelpe foreach (var implication in _implications) { if (implication.Architecture != architecture) continue; + if (implication.Architecture == "Wasm32" && implication.JitName == "RelaxedSimd" && implication.ImpliedJitName == "PackedSimd") + { + // PackedSimd is enabled by default on wasm; RelaxedSimd remains opt-in. + continue; + } + AddReverseImplication(architecture, implication.JitName, implication.ImpliedJitName); } tr.WriteLine(" break;"); From e77c88c9b722b492b4c188302f1268d31cf2912b Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Thu, 3 Sep 2026 15:22:32 -0500 Subject: [PATCH 17/31] Restore Wasm instruction-set dependency propagation 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 --- .../tools/Common/JitInterface/CorInfoInstructionSet.cs | 3 +++ .../JitInterface/ThunkGenerator/InstructionSetGenerator.cs | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs b/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs index 5ae9a3c0dafcc2..e2bb9d87c7fe66 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoInstructionSet.cs @@ -827,6 +827,7 @@ public static InstructionSetFlags ExpandInstructionSetByImplicationHelper(Target return resultflags; } + // Expands unsupported instruction sets. If A implies B and B is unsupported, A is unsupported too. public void ExpandInstructionSetByReverseImplication(TargetArchitecture architecture) { this = ExpandInstructionSetByReverseImplicationHelper(architecture, this); @@ -935,6 +936,8 @@ private static InstructionSetFlags ExpandInstructionSetByReverseImplicationHelpe case TargetArchitecture.Wasm32: if (resultflags.HasInstructionSet(InstructionSet.Wasm32_PackedSimd)) resultflags.AddInstructionSet(InstructionSet.Wasm32_Vector128); + if (resultflags.HasInstructionSet(InstructionSet.Wasm32_PackedSimd)) + resultflags.AddInstructionSet(InstructionSet.Wasm32_RelaxedSimd); if (resultflags.HasInstructionSet(InstructionSet.Wasm32_WasmBase)) resultflags.AddInstructionSet(InstructionSet.Wasm32_PackedSimd); break; diff --git a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetGenerator.cs b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetGenerator.cs index 7a511404a947f1..d89ed8f17f39e6 100644 --- a/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetGenerator.cs +++ b/src/coreclr/tools/Common/JitInterface/ThunkGenerator/InstructionSetGenerator.cs @@ -636,6 +636,7 @@ public static InstructionSetFlags ExpandInstructionSetByImplicationHelper(Target return resultflags; } + // Expands unsupported instruction sets. If A implies B and B is unsupported, A is unsupported too. public void ExpandInstructionSetByReverseImplication(TargetArchitecture architecture) { this = ExpandInstructionSetByReverseImplicationHelper(architecture, this); @@ -670,12 +671,6 @@ private static InstructionSetFlags ExpandInstructionSetByReverseImplicationHelpe foreach (var implication in _implications) { if (implication.Architecture != architecture) continue; - if (implication.Architecture == "Wasm32" && implication.JitName == "RelaxedSimd" && implication.ImpliedJitName == "PackedSimd") - { - // PackedSimd is enabled by default on wasm; RelaxedSimd remains opt-in. - continue; - } - AddReverseImplication(architecture, implication.JitName, implication.ImpliedJitName); } tr.WriteLine(" break;"); From 1879a24b4ab059a901bc067ff8eadbee6c6d2b22 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Thu, 3 Sep 2026 16:59:56 -0500 Subject: [PATCH 18/31] Address RelaxedSimd review feedback 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 --- src/coreclr/jit/hwintrinsiclistwasm.h | 8 ++++---- src/mono/browser/build/BrowserWasmApp.CoreCLR.targets | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/hwintrinsiclistwasm.h b/src/coreclr/jit/hwintrinsiclistwasm.h index 9dfb434ac8e0b2..c07b42396f6105 100644 --- a/src/coreclr/jit/hwintrinsiclistwasm.h +++ b/src/coreclr/jit/hwintrinsiclistwasm.h @@ -84,10 +84,10 @@ HARDWARE_INTRINSIC(PackedSimd, ZeroExtendWideningUpper, // RelaxedSimd Intrinsics #define FIRST_NI_RelaxedSimd NI_RelaxedSimd_ConvertToInt32Native -HARDWARE_INTRINSIC(RelaxedSimd, ConvertToInt32Native, 16, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_i32x4_relaxed_trunc_f32x4_s, INS_i32x4_relaxed_trunc_f64x2_s_zero, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(RelaxedSimd, ConvertToUInt32Native, 16, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_i32x4_relaxed_trunc_f32x4_u, INS_i32x4_relaxed_trunc_f64x2_u_zero, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(RelaxedSimd, DotProductAddNative, 16, 3, INS_i32x4_relaxed_dot_i8x16_i7x16_add_s, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(RelaxedSimd, DotProductNative, 16, 2, INS_i16x8_relaxed_dot_i8x16_i7x16_s, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, ConvertToInt32Native, 16, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_i32x4_relaxed_trunc_f32x4_s, INS_i32x4_relaxed_trunc_f64x2_s_zero, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, ConvertToUInt32Native, 16, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_i32x4_relaxed_trunc_f32x4_u, INS_i32x4_relaxed_trunc_f64x2_u_zero, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, DotProductAddNative, 16, 3, INS_i32x4_relaxed_dot_i8x16_i7x16_add_s, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(RelaxedSimd, DotProductNative, 16, 2, INS_i16x8_relaxed_dot_i8x16_i7x16_s, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(RelaxedSimd, LaneSelectNative, 16, 3, INS_i8x16_relaxed_laneselect, INS_i8x16_relaxed_laneselect, INS_i16x8_relaxed_laneselect, INS_i16x8_relaxed_laneselect, INS_i32x4_relaxed_laneselect, INS_i32x4_relaxed_laneselect, INS_i64x2_relaxed_laneselect, INS_i64x2_relaxed_laneselect, INS_invalid, INS_invalid, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(RelaxedSimd, MaxNative, 16, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_f32x4_relaxed_max, INS_f64x2_relaxed_max, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg|HW_Flag_Commutative) HARDWARE_INTRINSIC(RelaxedSimd, MinNative, 16, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_f32x4_relaxed_min, INS_f64x2_relaxed_min, -1, -1, HW_Category_SIMD, HW_Flag_BaseTypeFromFirstArg|HW_Flag_Commutative) diff --git a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets index f137f239c43ffe..905d3bf33adaa6 100644 --- a/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets +++ b/src/mono/browser/build/BrowserWasmApp.CoreCLR.targets @@ -36,7 +36,7 @@ <_WasmDefaultFlags>-fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0 -msimd128 <_WasmDefaultFlags Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_WasmDefaultFlags) -mrelaxed-simd - $(PublishReadyToRunCrossgen2ExtraArgs);--instruction-set=relaxed-simd + $(PublishReadyToRunCrossgen2ExtraArgs);--instruction-set:relaxed-simd Build Publish From 5334f97db69ff1b26b7453ef2c59b091e1292875 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 5 Sep 2026 15:36:21 -0500 Subject: [PATCH 19/31] Update Firefox used for wasm testing 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 --- eng/testing/BrowserVersions.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/testing/BrowserVersions.props b/eng/testing/BrowserVersions.props index 4e91b7c21278d2..9b82ce21e37c79 100644 --- a/eng/testing/BrowserVersions.props +++ b/eng/testing/BrowserVersions.props @@ -12,9 +12,9 @@ 1669021 https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/1669035 15.2.124 - 140.11.0esr + 155.0.1 0.37.0 - 140.11.0esr + 155.0.1 0.37.0 \ No newline at end of file From 96522d15fa876525dd65f265520f14f7dc85d759 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 5 Sep 2026 18:14:04 -0500 Subject: [PATCH 20/31] Build macOS wasm cross tool for host architecture The SmokeMac job runs on an x64 build agent. Build mono-aot-cross for x64 so native relinking can execute it before sending browser artifacts to the Arm64 test machine. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3de5ced8-be89-4f1b-bff4-5b3c577cedac --- eng/pipelines/runtime.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 91d96817ac747a..0569fe4c938841 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -1251,7 +1251,7 @@ extends: runOnlyOnChromeVersionUpdate: true shouldRunSmokeOnly: 'true' nameSuffix: _SmokeMac - extraBuildArgs: /p:AotHostArchitecture=arm64 /p:AotHostOS=$(_hostedOS) + extraBuildArgs: /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS) useHelixMonitor: ${{ variables.enableHelixJobMonitor }} scenarios: - WasmTestOnChrome From 77282b2813840471fe09022eab6164358f49fc92 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Thu, 10 Sep 2026 13:52:43 -0500 Subject: [PATCH 21/31] [wasm] Address RelaxedSimd review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- eng/testing/tests.wasm.targets | 8 +-- .../Runtime/Intrinsics/Wasm/RelaxedSimd.cs | 50 ++++++++--------- .../ref/System.Runtime.Intrinsics.cs | 53 ++++++++++--------- .../tests/Wasm/RelaxedSimdTests.cs | 9 ++++ src/mono/browser/build/BrowserWasmApp.targets | 4 +- .../ILLink.Substitutions.NoWasmIntrinsics.xml | 3 ++ src/mono/mono/mini/interp/transform-simd.c | 9 ---- .../wasm/Wasm.Build.Tests/WasmSIMDTests.cs | 11 ++++ 8 files changed, 81 insertions(+), 66 deletions(-) diff --git a/eng/testing/tests.wasm.targets b/eng/testing/tests.wasm.targets index bba5fe28fa5861..cd5d65bcf48071 100644 --- a/eng/testing/tests.wasm.targets +++ b/eng/testing/tests.wasm.targets @@ -89,16 +89,16 @@ --> <_WasmIntrinsicsSubstitutions Condition="'$(WasmEnableSIMD)' != 'false'">WasmIntrinsics <_WasmIntrinsicsSubstitutions Condition="'$(WasmEnableSIMD)' == 'false'">NoWasmIntrinsics - <_WasmRelaxedSimdSubstitutions Condition="'$(WasmEnableRelaxedSimd)' == 'true'">WasmRelaxedSimd - <_WasmRelaxedSimdSubstitutions Condition="'$(WasmEnableRelaxedSimd)' != 'true'">NoWasmRelaxedSimd + <_WasmRelaxedSimdSubstitutions Condition="'$(WasmEnableSIMD)' != 'false' and '$(WasmEnableRelaxedSimd)' == 'true'">WasmRelaxedSimd + <_WasmRelaxedSimdSubstitutions Condition="'$(WasmEnableSIMD)' != 'false' and '$(WasmEnableRelaxedSimd)' != 'true'">NoWasmRelaxedSimd <_WasmIntrinsicsSubstitutions Condition="'$(RuntimeFlavor)' == 'CoreCLR' and '$(PublishReadyToRun)' != 'true'">NoWasmIntrinsics - <_WasmRelaxedSimdSubstitutions Condition="'$(RuntimeFlavor)' == 'CoreCLR' and '$(PublishReadyToRun)' != 'true'">NoWasmRelaxedSimd + <_WasmRelaxedSimdSubstitutions Condition="'$(RuntimeFlavor)' == 'CoreCLR' and '$(PublishReadyToRun)' != 'true'"> <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.$(_WasmIntrinsicsSubstitutions).xml" - <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.$(_WasmRelaxedSimdSubstitutions).xml" + <_ExtraTrimmerArgs Condition="'$(_WasmRelaxedSimdSubstitutions)' != ''">$(_ExtraTrimmerArgs) --substitutions "$(BrowserProjectRoot)build\ILLink.Substitutions.$(_WasmRelaxedSimdSubstitutions).xml" diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs index aa3cb522e30825..e925ab9849d198 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs @@ -33,10 +33,10 @@ public abstract class RelaxedSimd /// i8x16.relaxed_swizzle [Intrinsic] - public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => SwizzleNative(vector, indices); + public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => IsSupported ? SwizzleNative(vector, indices) : throw new PlatformNotSupportedException(); /// i8x16.relaxed_swizzle [Intrinsic] - public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => SwizzleNative(vector, indices); + public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => IsSupported ? SwizzleNative(vector, indices) : throw new PlatformNotSupportedException(); // Relaxed truncating float-to-int conversions. For NaN or out-of-range inputs the result is // implementation-defined; the saturating PackedSimd.ConvertToInt32Saturate / ConvertToUInt32Saturate @@ -44,33 +44,33 @@ public abstract class RelaxedSimd /// i32x4.relaxed_trunc_f32x4_s [Intrinsic] - public static Vector128 ConvertToInt32Native(Vector128 value) => ConvertToInt32Native(value); + public static Vector128 ConvertToInt32Native(Vector128 value) => IsSupported ? ConvertToInt32Native(value) : throw new PlatformNotSupportedException(); /// i32x4.relaxed_trunc_f32x4_u [Intrinsic] - public static Vector128 ConvertToUInt32Native(Vector128 value) => ConvertToUInt32Native(value); + public static Vector128 ConvertToUInt32Native(Vector128 value) => IsSupported ? ConvertToUInt32Native(value) : throw new PlatformNotSupportedException(); /// i32x4.relaxed_trunc_f64x2_s_zero [Intrinsic] - public static Vector128 ConvertToInt32Native(Vector128 value) => ConvertToInt32Native(value); + public static Vector128 ConvertToInt32Native(Vector128 value) => IsSupported ? ConvertToInt32Native(value) : throw new PlatformNotSupportedException(); /// i32x4.relaxed_trunc_f64x2_u_zero [Intrinsic] - public static Vector128 ConvertToUInt32Native(Vector128 value) => ConvertToUInt32Native(value); + public static Vector128 ConvertToUInt32Native(Vector128 value) => IsSupported ? ConvertToUInt32Native(value) : throw new PlatformNotSupportedException(); // Relaxed fused multiply-add. Whether the intermediate product is rounded before the add // (and whether the underlying instruction is a true fused FMA) is implementation-defined. /// f32x4.relaxed_madd [Intrinsic] - public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddEstimate(left, right, addend); + public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => IsSupported ? MultiplyAddEstimate(left, right, addend) : throw new PlatformNotSupportedException(); /// f64x2.relaxed_madd [Intrinsic] - public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddEstimate(left, right, addend); + public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => IsSupported ? MultiplyAddEstimate(left, right, addend) : throw new PlatformNotSupportedException(); /// f32x4.relaxed_nmadd [Intrinsic] - public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddNegatedEstimate(left, right, addend); + public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => IsSupported ? MultiplyAddNegatedEstimate(left, right, addend) : throw new PlatformNotSupportedException(); /// f64x2.relaxed_nmadd [Intrinsic] - public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddNegatedEstimate(left, right, addend); + public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => IsSupported ? MultiplyAddNegatedEstimate(left, right, addend) : throw new PlatformNotSupportedException(); // Relaxed lane select. The mask is interpreted per-byte/word; lanes where the mask bit is // neither all-ones nor all-zeros produce implementation-defined results. For deterministic @@ -78,28 +78,28 @@ public abstract class RelaxedSimd /// i8x16.relaxed_laneselect [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); /// i8x16.relaxed_laneselect [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); /// i16x8.relaxed_laneselect [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); /// i16x8.relaxed_laneselect [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); /// i32x4.relaxed_laneselect [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); /// i32x4.relaxed_laneselect [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); /// i64x2.relaxed_laneselect [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); /// i64x2.relaxed_laneselect [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); // Relaxed min/max. NaN handling and sign-of-zero handling are implementation-defined. // For IEEE-compliant min/max use PackedSimd.Min/Max; for pseudo-min/max (one-sided NaN @@ -107,16 +107,16 @@ public abstract class RelaxedSimd /// f32x4.relaxed_min [Intrinsic] - public static Vector128 MinNative(Vector128 left, Vector128 right) => MinNative(left, right); + public static Vector128 MinNative(Vector128 left, Vector128 right) => IsSupported ? MinNative(left, right) : throw new PlatformNotSupportedException(); /// f32x4.relaxed_max [Intrinsic] - public static Vector128 MaxNative(Vector128 left, Vector128 right) => MaxNative(left, right); + public static Vector128 MaxNative(Vector128 left, Vector128 right) => IsSupported ? MaxNative(left, right) : throw new PlatformNotSupportedException(); /// f64x2.relaxed_min [Intrinsic] - public static Vector128 MinNative(Vector128 left, Vector128 right) => MinNative(left, right); + public static Vector128 MinNative(Vector128 left, Vector128 right) => IsSupported ? MinNative(left, right) : throw new PlatformNotSupportedException(); /// f64x2.relaxed_max [Intrinsic] - public static Vector128 MaxNative(Vector128 left, Vector128 right) => MaxNative(left, right); + public static Vector128 MaxNative(Vector128 left, Vector128 right) => IsSupported ? MaxNative(left, right) : throw new PlatformNotSupportedException(); // Relaxed Q15 multiply with rounding. Differs from PackedSimd.MultiplyRoundedSaturateQ15 // (i16x8.q15mulr_sat_s) in that the multiplication of INT16_MIN by INT16_MIN produces an @@ -124,7 +124,7 @@ public abstract class RelaxedSimd /// i16x8.relaxed_q15mulr_s [Intrinsic] - public static Vector128 MultiplyRoundedQ15Native(Vector128 left, Vector128 right) => MultiplyRoundedQ15Native(left, right); + public static Vector128 MultiplyRoundedQ15Native(Vector128 left, Vector128 right) => IsSupported ? MultiplyRoundedQ15Native(left, right) : throw new PlatformNotSupportedException(); // Relaxed dot products for signed-by-unsigned bytes. Per the finished spec pseudocode, // operand `a` is signed and operand `b` is unsigned 7-bit; when any lane of `b` has the @@ -134,10 +134,10 @@ public abstract class RelaxedSimd /// i16x8.relaxed_dot_i8x16_i7x16_s — multiplies adjacent (sbyte, byte) pairs and sums each pair into a signed 16-bit lane. [Intrinsic] - public static Vector128 DotProductNative(Vector128 left, Vector128 right) => DotProductNative(left, right); + public static Vector128 DotProductNative(Vector128 left, Vector128 right) => IsSupported ? DotProductNative(left, right) : throw new PlatformNotSupportedException(); /// i32x4.relaxed_dot_i8x16_i7x16_add_s — multiplies four adjacent (sbyte, byte) pairs, sums them with a signed 32-bit accumulator, and returns the result. [Intrinsic] - public static Vector128 DotProductAddNative(Vector128 left, Vector128 right, Vector128 accumulator) => DotProductAddNative(left, right, accumulator); + public static Vector128 DotProductAddNative(Vector128 left, Vector128 right, Vector128 accumulator) => IsSupported ? DotProductAddNative(left, right, accumulator) : throw new PlatformNotSupportedException(); } } diff --git a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index 4fc0f9989913f3..50b9c20b3e971c 100644 --- a/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/libraries/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -11984,34 +11984,35 @@ public abstract partial class PackedSimd public static Vector128 ZeroExtendWideningUpper(Vector128 value) { throw null; } public static Vector128 ZeroExtendWideningUpper(Vector128 value) { throw null; } } - [CLSCompliant(false)] + [System.CLSCompliantAttribute(false)] public abstract partial class RelaxedSimd { + protected RelaxedSimd() { } public static bool IsSupported { get { throw null; } } - public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) { throw null; } - public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) { throw null; } - public static Vector128 ConvertToInt32Native(Vector128 value) { throw null; } - public static Vector128 ConvertToUInt32Native(Vector128 value) { throw null; } - public static Vector128 ConvertToInt32Native(Vector128 value) { throw null; } - public static Vector128 ConvertToUInt32Native(Vector128 value) { throw null; } - public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) { throw null; } - public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) { throw null; } - public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) { throw null; } - public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) { throw null; } - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) { throw null; } - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) { throw null; } - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) { throw null; } - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) { throw null; } - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) { throw null; } - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) { throw null; } - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) { throw null; } - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) { throw null; } - public static Vector128 MinNative(Vector128 left, Vector128 right) { throw null; } - public static Vector128 MaxNative(Vector128 left, Vector128 right) { throw null; } - public static Vector128 MinNative(Vector128 left, Vector128 right) { throw null; } - public static Vector128 MaxNative(Vector128 left, Vector128 right) { throw null; } - public static Vector128 MultiplyRoundedQ15Native(Vector128 left, Vector128 right) { throw null; } - public static Vector128 DotProductNative(Vector128 left, Vector128 right) { throw null; } - public static Vector128 DotProductAddNative(Vector128 left, Vector128 right, Vector128 accumulator) { throw null; } + public static System.Runtime.Intrinsics.Vector128 ConvertToInt32Native(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public static System.Runtime.Intrinsics.Vector128 ConvertToInt32Native(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public static System.Runtime.Intrinsics.Vector128 ConvertToUInt32Native(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public static System.Runtime.Intrinsics.Vector128 ConvertToUInt32Native(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public static System.Runtime.Intrinsics.Vector128 DotProductAddNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 accumulator) { throw null; } + public static System.Runtime.Intrinsics.Vector128 DotProductNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 LaneSelectNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 mask) { throw null; } + public static System.Runtime.Intrinsics.Vector128 LaneSelectNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 mask) { throw null; } + public static System.Runtime.Intrinsics.Vector128 LaneSelectNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 mask) { throw null; } + public static System.Runtime.Intrinsics.Vector128 LaneSelectNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 mask) { throw null; } + public static System.Runtime.Intrinsics.Vector128 LaneSelectNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 mask) { throw null; } + public static System.Runtime.Intrinsics.Vector128 LaneSelectNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 mask) { throw null; } + public static System.Runtime.Intrinsics.Vector128 LaneSelectNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 mask) { throw null; } + public static System.Runtime.Intrinsics.Vector128 LaneSelectNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 mask) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MaxNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MaxNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MinNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MinNative(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MultiplyAddEstimate(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 addend) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MultiplyAddEstimate(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 addend) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MultiplyAddNegatedEstimate(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 addend) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MultiplyAddNegatedEstimate(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, System.Runtime.Intrinsics.Vector128 addend) { throw null; } + public static System.Runtime.Intrinsics.Vector128 MultiplyRoundedQ15Native(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 SwizzleNative(System.Runtime.Intrinsics.Vector128 vector, System.Runtime.Intrinsics.Vector128 indices) { throw null; } + public static System.Runtime.Intrinsics.Vector128 SwizzleNative(System.Runtime.Intrinsics.Vector128 vector, System.Runtime.Intrinsics.Vector128 indices) { throw null; } } } diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs index 298fd10ab6a228..501bad2ed884ad 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs @@ -13,6 +13,8 @@ namespace System.Runtime.Intrinsics.Wasm.Tests [ActiveIssue("https://github.com/dotnet/runtime/issues/123011", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))] public sealed class RelaxedSimdTests { + public static bool IsNotSupported => !RelaxedSimd.IsSupported; + [Fact] [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(RelaxedSimd))] public unsafe void RelaxedSimdIsSupportedReflects() @@ -22,6 +24,13 @@ public unsafe void RelaxedSimdIsSupportedReflects() Assert.Equal(RelaxedSimd.IsSupported, methodInfo.Invoke(null, null)); } + [ConditionalFact(typeof(RelaxedSimdTests), nameof(IsNotSupported))] + public void UnsupportedMethodThrowsPlatformNotSupportedException() + { + Assert.Throws( + () => RelaxedSimd.ConvertToInt32Native(Vector128.Create(1.0f))); + } + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] public unsafe void ConvertToIntegerNativeInRangeMatchesExpected() { diff --git a/src/mono/browser/build/BrowserWasmApp.targets b/src/mono/browser/build/BrowserWasmApp.targets index 2a602dc8ad3c89..821d9bc80dd336 100644 --- a/src/mono/browser/build/BrowserWasmApp.targets +++ b/src/mono/browser/build/BrowserWasmApp.targets @@ -32,8 +32,8 @@ <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.WasmIntrinsics.xml" <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' == 'false'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.NoWasmIntrinsics.xml" - <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' == 'true'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.WasmRelaxedSimd.xml" - <_ExtraTrimmerArgs Condition="'$(WasmEnableRelaxedSimd)' != 'true'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.NoWasmRelaxedSimd.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false' and '$(WasmEnableRelaxedSimd)' == 'true'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.WasmRelaxedSimd.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false' and '$(WasmEnableRelaxedSimd)' != 'true'">$(_ExtraTrimmerArgs) --substitutions "$(MSBuildThisFileDirectory)ILLink.Substitutions.NoWasmRelaxedSimd.xml" true emcc diff --git a/src/mono/browser/build/ILLink.Substitutions.NoWasmIntrinsics.xml b/src/mono/browser/build/ILLink.Substitutions.NoWasmIntrinsics.xml index 5e2282cae353c2..b9901fa36e6d0e 100644 --- a/src/mono/browser/build/ILLink.Substitutions.NoWasmIntrinsics.xml +++ b/src/mono/browser/build/ILLink.Substitutions.NoWasmIntrinsics.xml @@ -9,5 +9,8 @@ + + + diff --git a/src/mono/mono/mini/interp/transform-simd.c b/src/mono/mono/mini/interp/transform-simd.c index 38e652af206560..979df451d0c6d1 100644 --- a/src/mono/mono/mini/interp/transform-simd.c +++ b/src/mono/mono/mini/interp/transform-simd.c @@ -1499,15 +1499,6 @@ emit_sri_relaxedsimd (TransformData *td, MonoMethod *cmethod, MonoMethodSignatur #if defined(HOST_BROWSER) || defined(HOST_WASI) if (!mono_interp_relaxed_simd_supported) { - // IsSupported has already been handled above and returns false when the interp - // build did not link the relaxed-simd variant. Well-behaved callers gate on - // RelaxedSimd.IsSupported and never reach this point. Reflection or explicit - // bypass could still land here; because the managed body of every RelaxedSimd - // intrinsic is a self-recursive stub, returning FALSE here would cause the - // 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. return FALSE; } diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs index f4521b5acb9e58..53e92e9554e386 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSIMDTests.cs @@ -153,6 +153,17 @@ public static int Main() return 2; } } + else + { + try + { + RelaxedSimd.ConvertToInt32Native(Vector128.Create(1.0f)); + return 3; + } + catch (PlatformNotSupportedException) + { + } + } return 42; } From 2e779154e33c55aef7c7c81c8ef7978f4c8786d1 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 00:06:55 -0500 Subject: [PATCH 22/31] [wasm] Move RelaxedSimd support handling to Mono Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- .../Runtime/Intrinsics/Wasm/RelaxedSimd.cs | 79 +++++++------------ src/mono/mono/mini/interp/transform-simd.c | 3 + 2 files changed, 30 insertions(+), 52 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs index e925ab9849d198..b798dba1da3210 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs @@ -15,7 +15,7 @@ namespace System.Runtime.Intrinsics.Wasm /// corresponding operation (where available) instead. /// /// - /// All members of this class require the runtime to support the + /// All operations exposed by this class require the WebAssembly engine to support the /// relaxed SIMD WebAssembly proposal. /// /// @@ -26,105 +26,82 @@ public abstract class RelaxedSimd /// Gets a value that indicates whether the APIs in this class are supported. /// if the APIs are supported; otherwise, . /// A value of indicates that the APIs will throw . - public static bool IsSupported { [Intrinsic] get { return IsSupported; } } + public static bool IsSupported { get { return IsSupported; } } // Relaxed swizzle: like PackedSimd.Swizzle, but for index lanes outside [0, 16) the // result is implementation-defined (often the index modulo 16 on x86, zero on ARM). /// i8x16.relaxed_swizzle - [Intrinsic] - public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => IsSupported ? SwizzleNative(vector, indices) : throw new PlatformNotSupportedException(); + public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => SwizzleNative(vector, indices); /// i8x16.relaxed_swizzle - [Intrinsic] - public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => IsSupported ? SwizzleNative(vector, indices) : throw new PlatformNotSupportedException(); + public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => SwizzleNative(vector, indices); // Relaxed truncating float-to-int conversions. For NaN or out-of-range inputs the result is // implementation-defined; the saturating PackedSimd.ConvertToInt32Saturate / ConvertToUInt32Saturate // overloads provide deterministic semantics. /// i32x4.relaxed_trunc_f32x4_s - [Intrinsic] - public static Vector128 ConvertToInt32Native(Vector128 value) => IsSupported ? ConvertToInt32Native(value) : throw new PlatformNotSupportedException(); + public static Vector128 ConvertToInt32Native(Vector128 value) => ConvertToInt32Native(value); /// i32x4.relaxed_trunc_f32x4_u - [Intrinsic] - public static Vector128 ConvertToUInt32Native(Vector128 value) => IsSupported ? ConvertToUInt32Native(value) : throw new PlatformNotSupportedException(); + public static Vector128 ConvertToUInt32Native(Vector128 value) => ConvertToUInt32Native(value); /// i32x4.relaxed_trunc_f64x2_s_zero - [Intrinsic] - public static Vector128 ConvertToInt32Native(Vector128 value) => IsSupported ? ConvertToInt32Native(value) : throw new PlatformNotSupportedException(); + public static Vector128 ConvertToInt32Native(Vector128 value) => ConvertToInt32Native(value); /// i32x4.relaxed_trunc_f64x2_u_zero - [Intrinsic] - public static Vector128 ConvertToUInt32Native(Vector128 value) => IsSupported ? ConvertToUInt32Native(value) : throw new PlatformNotSupportedException(); + public static Vector128 ConvertToUInt32Native(Vector128 value) => ConvertToUInt32Native(value); // Relaxed fused multiply-add. Whether the intermediate product is rounded before the add // (and whether the underlying instruction is a true fused FMA) is implementation-defined. /// f32x4.relaxed_madd - [Intrinsic] - public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => IsSupported ? MultiplyAddEstimate(left, right, addend) : throw new PlatformNotSupportedException(); + public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddEstimate(left, right, addend); /// f64x2.relaxed_madd - [Intrinsic] - public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => IsSupported ? MultiplyAddEstimate(left, right, addend) : throw new PlatformNotSupportedException(); + public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddEstimate(left, right, addend); /// f32x4.relaxed_nmadd - [Intrinsic] - public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => IsSupported ? MultiplyAddNegatedEstimate(left, right, addend) : throw new PlatformNotSupportedException(); + public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddNegatedEstimate(left, right, addend); /// f64x2.relaxed_nmadd - [Intrinsic] - public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => IsSupported ? MultiplyAddNegatedEstimate(left, right, addend) : throw new PlatformNotSupportedException(); + public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddNegatedEstimate(left, right, addend); // Relaxed lane select. The mask is interpreted per-byte/word; lanes where the mask bit is // neither all-ones nor all-zeros produce implementation-defined results. For deterministic // selection use Vector128.ConditionalSelect. /// i8x16.relaxed_laneselect - [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); /// i8x16.relaxed_laneselect - [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); /// i16x8.relaxed_laneselect - [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); /// i16x8.relaxed_laneselect - [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); /// i32x4.relaxed_laneselect - [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); /// i32x4.relaxed_laneselect - [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); /// i64x2.relaxed_laneselect - [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); /// i64x2.relaxed_laneselect - [Intrinsic] - public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => IsSupported ? LaneSelectNative(left, right, mask) : throw new PlatformNotSupportedException(); + public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); // Relaxed min/max. NaN handling and sign-of-zero handling are implementation-defined. // For IEEE-compliant min/max use PackedSimd.Min/Max; for pseudo-min/max (one-sided NaN // propagation) use PackedSimd.PseudoMin/PseudoMax. /// f32x4.relaxed_min - [Intrinsic] - public static Vector128 MinNative(Vector128 left, Vector128 right) => IsSupported ? MinNative(left, right) : throw new PlatformNotSupportedException(); + public static Vector128 MinNative(Vector128 left, Vector128 right) => MinNative(left, right); /// f32x4.relaxed_max - [Intrinsic] - public static Vector128 MaxNative(Vector128 left, Vector128 right) => IsSupported ? MaxNative(left, right) : throw new PlatformNotSupportedException(); + public static Vector128 MaxNative(Vector128 left, Vector128 right) => MaxNative(left, right); /// f64x2.relaxed_min - [Intrinsic] - public static Vector128 MinNative(Vector128 left, Vector128 right) => IsSupported ? MinNative(left, right) : throw new PlatformNotSupportedException(); + public static Vector128 MinNative(Vector128 left, Vector128 right) => MinNative(left, right); /// f64x2.relaxed_max - [Intrinsic] - public static Vector128 MaxNative(Vector128 left, Vector128 right) => IsSupported ? MaxNative(left, right) : throw new PlatformNotSupportedException(); + public static Vector128 MaxNative(Vector128 left, Vector128 right) => MaxNative(left, right); // Relaxed Q15 multiply with rounding. Differs from PackedSimd.MultiplyRoundedSaturateQ15 // (i16x8.q15mulr_sat_s) in that the multiplication of INT16_MIN by INT16_MIN produces an // implementation-defined value (typically INT16_MIN unsaturated on x86). /// i16x8.relaxed_q15mulr_s - [Intrinsic] - public static Vector128 MultiplyRoundedQ15Native(Vector128 left, Vector128 right) => IsSupported ? MultiplyRoundedQ15Native(left, right) : throw new PlatformNotSupportedException(); + public static Vector128 MultiplyRoundedQ15Native(Vector128 left, Vector128 right) => MultiplyRoundedQ15Native(left, right); // Relaxed dot products for signed-by-unsigned bytes. Per the finished spec pseudocode, // operand `a` is signed and operand `b` is unsigned 7-bit; when any lane of `b` has the @@ -133,11 +110,9 @@ public abstract class RelaxedSimd // saturating (may or may not saturate on overflow). /// i16x8.relaxed_dot_i8x16_i7x16_s — multiplies adjacent (sbyte, byte) pairs and sums each pair into a signed 16-bit lane. - [Intrinsic] - public static Vector128 DotProductNative(Vector128 left, Vector128 right) => IsSupported ? DotProductNative(left, right) : throw new PlatformNotSupportedException(); + public static Vector128 DotProductNative(Vector128 left, Vector128 right) => DotProductNative(left, right); /// i32x4.relaxed_dot_i8x16_i7x16_add_s — multiplies four adjacent (sbyte, byte) pairs, sums them with a signed 32-bit accumulator, and returns the result. - [Intrinsic] - public static Vector128 DotProductAddNative(Vector128 left, Vector128 right, Vector128 accumulator) => IsSupported ? DotProductAddNative(left, right, accumulator) : throw new PlatformNotSupportedException(); + public static Vector128 DotProductAddNative(Vector128 left, Vector128 right, Vector128 accumulator) => DotProductAddNative(left, right, accumulator); } } diff --git a/src/mono/mono/mini/interp/transform-simd.c b/src/mono/mono/mini/interp/transform-simd.c index 979df451d0c6d1..8d8f0c199f9ed3 100644 --- a/src/mono/mono/mini/interp/transform-simd.c +++ b/src/mono/mono/mini/interp/transform-simd.c @@ -1499,6 +1499,9 @@ emit_sri_relaxedsimd (TransformData *td, MonoMethod *cmethod, MonoMethodSignatur #if defined(HOST_BROWSER) || defined(HOST_WASI) if (!mono_interp_relaxed_simd_supported) { + // Keep the original call so normal call transformation consumes its arguments. + // The emitted helper throws before the recursive managed intrinsic body is reached. + interp_generate_void_throw (td, MONO_JIT_ICALL_mono_throw_platform_not_supported); return FALSE; } From 2d37d3de7d521df09ed909b11c5170d3b3a07c9b Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 00:55:37 -0500 Subject: [PATCH 23/31] [wasm] Clean up SIMD intrinsic metadata and docs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- .../Runtime/Intrinsics/Wasm/PackedSimd.cs | 445 +----------------- .../Runtime/Intrinsics/Wasm/RelaxedSimd.cs | 187 +++++--- 2 files changed, 135 insertions(+), 497 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/PackedSimd.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/PackedSimd.cs index 0ffe3d96f4fcc9..89e8687a746c77 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/PackedSimd.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/PackedSimd.cs @@ -15,7 +15,7 @@ public abstract class PackedSimd /// Gets a value that indicates whether the APIs in this class are supported. /// if the APIs are supported; otherwise, . /// A value of indicates that the APIs will throw . - public static bool IsSupported { [Intrinsic] get { return IsSupported; } } + public static bool IsSupported { get { return IsSupported; } } // Constructing SIMD Values @@ -26,73 +26,61 @@ public abstract class PackedSimd /// v128_t wasm_i8x16_splat (int8_t a) /// i8x16.splat : [i32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(sbyte value) => Splat(value); /// /// v128_t wasm_u8x16_splat (uint8_t a) /// i8x16.splat : [i32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(byte value) => Splat(value); /// /// v128_t wasm_i16x8_splat (int16_t a) /// i16x8.splat : [i32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(short value) => Splat(value); /// /// v128_t wasm_u16x8_splat (uint16_t a) /// i16x8.splat : [i32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(ushort value) => Splat(value); /// /// v128_t wasm_i32x4_splat (int32_t a) /// i32x4.splat : [i32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(int value) => Splat(value); /// /// v128_t wasm_u32x4_splat (uint32_t a) /// i32x4.splat : [i32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(uint value) => Splat(value); /// /// v128_t wasm_i64x2_splat (int64_t a) /// i64x2.splat : [i64] -> [v128] /// - [Intrinsic] public static Vector128 Splat(long value) => Splat(value); /// /// v128_t wasm_u64x2_splat (uint64_t a) /// i64x2.splat : [i64] -> [v128] /// - [Intrinsic] public static Vector128 Splat(ulong value) => Splat(value); /// /// v128_t wasm_f32x4_splat (float a) /// f32x4.splat : [f32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(float value) => Splat(value); /// /// v128_t wasm_f64x2_splat (double a) /// f64x2.splat : [f64] -> [v128] /// - [Intrinsic] public static Vector128 Splat(double value) => Splat(value); /// /// v128_t wasm_i32x4_splat (int32_t a) /// i32x4.splat : [i32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(nint value) => Splat(value); /// /// v128_t wasm_u32x4_splat (uint32_t a) /// i32x4.splat : [i32] -> [v128] /// - [Intrinsic] public static Vector128 Splat(nuint value) => Splat(value); // Accessing lanes @@ -101,172 +89,144 @@ public abstract class PackedSimd /// int8_t wasm_i8x16_extract_lane (v128_t a, int i) /// i8x16.extract_lane_s laneidx : [v128] -> [i32] /// - [Intrinsic] public static int ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(15))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx16 /// /// uint8_t wasm_u8x16_extract_lane (v128_t a, int i) /// i8x16.extract_lane_u laneidx : [v128] -> [i32] /// - [Intrinsic] public static uint ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(15))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx16 /// /// int16_t wasm_i16x8_extract_lane (v128_t a, int i) /// i16x8.extract_lane_s laneidx : [v128] -> [i32] /// - [Intrinsic] public static int ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(7))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx8 /// /// uint16_t wasm_u16x8_extract_lane (v128_t a, int i) /// i16x8.extract_lane_u laneidx : [v128] -> [i32] /// - [Intrinsic] public static uint ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(7))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx8 /// /// int32_t wasm_i32x4_extract_lane (v128_t a, int i) /// i32x4.extract_lane laneidx : [v128] -> [i32] /// - [Intrinsic] public static int ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(3))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx4 /// /// uint32_t wasm_u32x4_extract_lane (v128_t a, int i) /// i32x4.extract_lane laneidx : [v128] -> [i32] /// - [Intrinsic] public static uint ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(3))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx4 /// /// int64_t wasm_i64x2_extract_lane (v128_t a, int i) /// i64x2.extract_lane laneidx : [v128] -> [i64] /// - [Intrinsic] public static long ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(1))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx2 /// /// uint64_t wasm_u64x2_extract_lane (v128_t a, int i) /// i64x2.extract_lane laneidx : [v128] -> [i64] /// - [Intrinsic] public static ulong ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(1))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx2 /// /// float wasm_f32x4_extract_lane (v128_t a, int i) /// f32x4.extract_lane laneidx : [v128] -> [f32] /// - [Intrinsic] public static float ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(3))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx4 /// /// double wasm_f64x2_extract_lane (v128_t a, int i) /// f64x2.extract_lane laneidx : [v128] -> [f64] /// - [Intrinsic] public static double ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(1))] byte index) => ExtractScalar(value, index); // takes ImmLaneIdx2 /// /// int32_t wasm_i32x4_extract_lane (v128_t a, int i) /// i32x4.extract_lane laneidx : [v128] -> [i32] /// - [Intrinsic] public static nint ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(3))] byte index) => ExtractScalar(value, index); /// /// uint32_t wasm_u32x4_extract_lane (v128_t a, int i) /// i32x4.extract_lane laneidx : [v128] -> [i32] /// - [Intrinsic] public static nuint ExtractScalar(Vector128 value, [ConstantExpected(Max = (byte)(3))] byte index) => ExtractScalar(value, index); /// /// v128_t wasm_i8x16_replace_lane (v128_t a, int i, int8_t b) /// i8x16.replace_lane laneidx : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(15))] byte imm, int value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx16 /// /// v128_t wasm_u8x16_replace_lane (v128_t a, int i, uint8_t b) /// i8x16.replace_lane laneidx : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(15))] byte imm, uint value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx16 /// /// v128_t wasm_i16x8_replace_lane (v128_t a, int i, int16_t b) /// i16x8.replace_lane laneidx : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(7))] byte imm, int value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx8 /// /// v128_t wasm_u16x8_replace_lane (v128_t a, int i, uint16_t b) /// i16x8.replace_lane laneidx : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(7))] byte imm, uint value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx8 /// /// v128_t wasm_i32x4_replace_lane (v128_t a, int i, int32_t b) /// i32x4.replace_lane laneidx : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte imm, int value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx4 /// /// v128_t wasm_u32x4_replace_lane (v128_t a, int i, uint32_t b) /// i32x4.replace_lane laneidx : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte imm, uint value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx4 /// /// v128_t wasm_i64x2_replace_lane (v128_t a, int i, int64_t b) /// i64x2.replace_lane laneidx : [v128 i64] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(1))] byte imm, long value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx2 /// /// v128_t wasm_u64x2_replace_lane (v128_t a, int i, uint64_t b) /// i64x2.replace_lane laneidx : [v128 i64] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(1))] byte imm, ulong value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx2 /// /// v128_t wasm_f32x4_replace_lane (v128_t a, int i, float b) /// f32x4.replace_lane laneidx : [v128 f32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte imm, float value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx4 /// /// v128_t wasm_f64x2_replace_lane (v128_t a, int i, double b) /// f64x2.replace_lane laneidx : [v128 f64] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(1))] byte imm, double value) => ReplaceScalar(vector, imm, value); // takes ImmLaneIdx2 /// /// v128_t wasm_i32x4_replace_lane (v128_t a, int i, int32_t b) /// i32x4.replace_lane laneidx : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte imm, nint value) => ReplaceScalar(vector, imm, value); /// /// v128_t wasm_u32x4_replace_lane (v128_t a, int i, uint32_t b) /// i32x4.replace_lane laneidx : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ReplaceScalar(Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte imm, nuint value) => ReplaceScalar(vector, imm, value); /// /// v128_t wasm_i8x16_shuffle (v128_t a, v128_t b, int c0, int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8, int c9, int c10, int c11, int c12, int c13, int c14, int c15) /// i8x16.shuffle laneidx^16 : [v128 v128] -> [v128] /// - [Intrinsic] internal static Vector128 Shuffle(Vector128 lower, Vector128 upper, Vector128 indices) => Shuffle(lower, upper, indices); /// /// v128_t wasm_i8x16_shuffle (v128_t a, v128_t b, int c0, int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8, int c9, int c10, int c11, int c12, int c13, int c14, int c15) /// i8x16.shuffle laneidx^16 : [v128 v128] -> [v128] /// - [Intrinsic] internal static Vector128 Shuffle(Vector128 lower, Vector128 upper, Vector128 indices) => Shuffle(lower, upper, indices); /// /// v128_t wasm_i8x16_swizzle (v128_t a, v128_t b) /// i8x16.swizzle : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Swizzle(Vector128 vector, Vector128 indices) => Swizzle(vector, indices); /// /// v128_t wasm_i8x16_swizzle (v128_t a, v128_t b) /// i8x16.swizzle : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Swizzle(Vector128 vector, Vector128 indices) => Swizzle(vector, indices); // Integer arithmetic @@ -275,239 +235,200 @@ public abstract class PackedSimd /// v128_t wasm_i8x16_add (v128_t a, v128_t b) /// i8x16.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i8x16_add (v128_t a, v128_t b) /// i8x16.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i16x8_add (v128_t a, v128_t b) /// i16x8.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i16x8_add (v128_t a, v128_t b) /// i16x8.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i32x4_add (v128_t a, v128_t b) /// i32x4.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i32x4_add (v128_t a, v128_t b) /// i32x4.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i64x2_add (v128_t a, v128_t b) /// i64x2.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i64x2_add (v128_t a, v128_t b) /// i64x2.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i32x4_add (v128_t a, v128_t b) /// i32x4.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i32x4_add (v128_t a, v128_t b) /// i32x4.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_i8x16_sub (v128_t a, v128_t b) /// i8x16.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i8x16_sub (v128_t a, v128_t b) /// i8x16.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i16x8_sub (v128_t a, v128_t b) /// i16x8.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i16x8_sub (v128_t a, v128_t b) /// i16x8.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i32x4_sub (v128_t a, v128_t b) /// i32x4.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i32x4_sub (v128_t a, v128_t b) /// i32x4.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i64x2_sub (v128_t a, v128_t b) /// i64x2.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i64x2_sub (v128_t a, v128_t b) /// i64x2.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i32x4_sub (v128_t a, v128_t b) /// i32x4.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i32x4_sub (v128_t a, v128_t b) /// i32x4.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_i16x8_mul (v128_t a, v128_t b) /// i16x8.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_i16x8_mul (v128_t a, v128_t b) /// i16x8.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_i32x4_mul (v128_t a, v128_t b) /// i32x4.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_i32x4_mul (v128_t a, v128_t b) /// i32x4.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_i64x2_mul (v128_t a, v128_t b) /// i64x2.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_i64x2_mul (v128_t a, v128_t b) /// i64x2.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_i32x4_mul (v128_t a, v128_t b) /// i32x4.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_i32x4_mul (v128_t a, v128_t b) /// i32x4.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_i32x4_dot_i16x8 (v128_t a, v128_t b) /// i32x4.dot_i16x8_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Dot(Vector128 left, Vector128 right) => Dot(left, right); /// /// v128_t wasm_i8x16_neg (v128_t a) /// i8x16.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i8x16_neg (v128_t a) /// i8x16.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i16x8_neg (v128_t a) /// i16x8.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i16x8_neg (v128_t a) /// i16x8.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i32x4_neg (v128_t a) /// i32x4.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i32x4_neg (v128_t a) /// i32x4.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i64x2_neg (v128_t a) /// i64x2.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i64x2_neg (v128_t a) /// i64x2.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i32x4_neg (v128_t a) /// i32x4.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_i32x4_neg (v128_t a) /// i32x4.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); // Extended integer arithmetic @@ -516,99 +437,83 @@ public abstract class PackedSimd /// v128_t wasm_i16x8_extmul_low_i8x16 (v128_t a, v128_t b) /// i16x8.extmul_low_i8x16_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningLower(Vector128 left, Vector128 right) => MultiplyWideningLower(left, right); /// /// v128_t wasm_u16x8_extmul_low_u8x16 (v128_t a, v128_t b) /// i16x8.extmul_low_i8x16_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningLower(Vector128 left, Vector128 right) => MultiplyWideningLower(left, right); /// /// v128_t wasm_i32x4_extmul_low_i16x8 (v128_t a, v128_t b) /// i32x4.extmul_low_i16x8_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningLower(Vector128 left, Vector128 right) => MultiplyWideningLower(left, right); /// /// v128_t wasm_u32x4_extmul_low_u16x8 (v128_t a, v128_t b) /// i32x4.extmul_low_i16x8_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningLower(Vector128 left, Vector128 right) => MultiplyWideningLower(left, right); /// /// v128_t wasm_i64x2_extmul_low_i32x4 (v128_t a, v128_t b) /// i64x2.extmul_low_i32x4_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningLower(Vector128 left, Vector128 right) => MultiplyWideningLower(left, right); /// /// v128_t wasm_u64x2_extmul_low_u32x4 (v128_t a, v128_t b) /// i64x2.extmul_low_i32x4_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningLower(Vector128 left, Vector128 right) => MultiplyWideningLower(left, right); /// /// v128_t wasm_i16x8_extmul_high_i8x16 (v128_t a, v128_t b) /// i16x8.extmul_high_i8x16_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningUpper(Vector128 left, Vector128 right) => MultiplyWideningUpper(left, right); /// /// v128_t wasm_u16x8_extmul_high_u8x16 (v128_t a, v128_t b) /// i16x8.extmul_high_i8x16_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningUpper(Vector128 left, Vector128 right) => MultiplyWideningUpper(left, right); /// /// v128_t wasm_i32x4_extmul_high_i16x8 (v128_t a, v128_t b) /// i32x4.extmul_high_i16x8_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningUpper(Vector128 left, Vector128 right) => MultiplyWideningUpper(left, right); /// /// v128_t wasm_u32x4_extmul_high_u16x8 (v128_t a, v128_t b) /// i32x4.extmul_high_i16x8_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningUpper(Vector128 left, Vector128 right) => MultiplyWideningUpper(left, right); /// /// v128_t wasm_i64x2_extmul_high_i32x4 (v128_t a, v128_t b) /// i64x2.extmul_high_i32x4_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningUpper(Vector128 left, Vector128 right) => MultiplyWideningUpper(left, right); /// /// v128_t wasm_u64x2_extmul_high_u32x4 (v128_t a, v128_t b) /// i64x2.extmul_high_i32x4_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 MultiplyWideningUpper(Vector128 left, Vector128 right) => MultiplyWideningUpper(left, right); /// /// v128_t wasm_i16x8_extadd_pairwise_i8x16 (v128_t a) /// i16x8.extadd_pairwise_i8x16_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 AddPairwiseWidening(Vector128 value) => AddPairwiseWidening(value); /// /// v128_t wasm_u16x8_extadd_pairwise_u8x16 (v128_t a) /// i16x8.extadd_pairwise_i8x16_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 AddPairwiseWidening(Vector128 value) => AddPairwiseWidening(value); /// /// v128_t wasm_i32x4_extadd_pairwise_i16x8 (v128_t a) /// i32x4.extadd_pairwise_i16x8_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 AddPairwiseWidening(Vector128 value) => AddPairwiseWidening(value); /// /// v128_t wasm_u32x4_extadd_pairwise_u16x8 (v128_t a) /// i32x4.extadd_pairwise_i16x8_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 AddPairwiseWidening(Vector128 value) => AddPairwiseWidening(value); // Saturating integer arithmetic @@ -766,183 +671,153 @@ public abstract class PackedSimd /// v128_t wasm_i8x16_shl (v128_t a, uint32_t b) /// i8x16.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i8x16_shl (v128_t a, uint32_t b) /// i8x16.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i16x8_shl (v128_t a, uint32_t b) /// i16x8.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i16x8_shl (v128_t a, uint32_t b) /// i16x8.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i32x4_shl (v128_t a, uint32_t b) /// i32x4.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i32x4_shl (v128_t a, uint32_t b) /// i32x4.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i64x2_shl (v128_t a, uint32_t b) /// i64x2.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i64x2_shl (v128_t a, uint32_t b) /// i64x2.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i32x4_shl (v128_t a, uint32_t b) /// i32x4.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i32x4_shl (v128_t a, uint32_t b) /// i32x4.shl : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftLeft(Vector128 value, int count) => ShiftLeft(value, count); /// /// v128_t wasm_i8x16_shr (v128_t a, uint32_t b) /// i8x16.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i8x16_shr (v128_t a, uint32_t b) /// i8x16.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i16x8_shr (v128_t a, uint32_t b) /// i16x8.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i16x8_shr (v128_t a, uint32_t b) /// i16x8.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i32x4_shr (v128_t a, uint32_t b) /// i32x4.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i32x4_shr (v128_t a, uint32_t b) /// i32x4.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i64x2_shr (v128_t a, uint32_t b) /// i64x2.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i64x2_shr (v128_t a, uint32_t b) /// i64x2.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i32x4_shr (v128_t a, uint32_t b) /// i32x4.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_i32x4_shr (v128_t a, uint32_t b) /// i32x4.shr_s : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightArithmetic(Vector128 value, int count) => ShiftRightArithmetic(value, count); /// /// v128_t wasm_u8x16_shr (v128_t a, uint32_t b) /// i8x16.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u8x16_shr (v128_t a, uint32_t b) /// i8x16.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u16x8_shr (v128_t a, uint32_t b) /// i16x8.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u16x8_shr (v128_t a, uint32_t b) /// i16x8.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u32x4_shr (v128_t a, uint32_t b) /// i32x4.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u32x4_shr (v128_t a, uint32_t b) /// i32x4.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u64x2_shr (v128_t a, uint32_t b) /// i64x2.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u64x2_shr (v128_t a, uint32_t b) /// i64x2.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u32x4_shr (v128_t a, uint32_t b) /// i32x4.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); /// /// v128_t wasm_u32x4_shr (v128_t a, uint32_t b) /// i32x4.shr_u : [v128 i32] -> [v128] /// - [Intrinsic] public static Vector128 ShiftRightLogical(Vector128 value, int count) => ShiftRightLogical(value, count); // Bitwise operations @@ -951,445 +826,372 @@ public abstract class PackedSimd /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_and (v128_t a, v128_t b) /// v128.and : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 And(Vector128 left, Vector128 right) => And(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_or (v128_t a, v128_t b) /// v128.or : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Or(Vector128 left, Vector128 right) => Or(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_xor (v128_t a, v128_t b) /// v128.xor : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Xor(Vector128 left, Vector128 right) => Xor(left, right); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_not (v128_t a) /// v128.not : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Not(Vector128 value) => Not(value); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_andnot (v128_t a, v128_t b) /// v128.andnot : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 AndNot(Vector128 left, Vector128 right) => AndNot(left, right); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_v128_bitselect (v128_t a, v128_t b, v128_t mask) /// v128.bitselect : [v128 v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 BitwiseSelect(Vector128 left, Vector128 right, Vector128 select) => BitwiseSelect(left, right, select); /// /// v128_t wasm_i8x16_popcnt (v128_t a) /// i8x16.popcnt : [v128] -> [v128] /// - [Intrinsic] public static Vector128 PopCount(Vector128 value) => PopCount(value); // Boolean horizontal reductions @@ -1398,134 +1200,112 @@ public abstract class PackedSimd /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_v128_any_true (v128_t a) /// v128.any_true : [v128] -> [i32] /// - [Intrinsic] public static bool AnyTrue(Vector128 value) => AnyTrue(value); /// /// bool wasm_i8x16_all_true (v128_t a) /// i8x16.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i8x16_all_true (v128_t a) /// i8x16.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i16x8_all_true (v128_t a) /// i16x8.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i16x8_all_true (v128_t a) /// i16x8.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i32x4_all_true (v128_t a) /// i32x4.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i32x4_all_true (v128_t a) /// i32x4.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i64x2_all_true (v128_t a) /// i64x2.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i64x2_all_true (v128_t a) /// i64x2.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i32x4_all_true (v128_t a) /// i32x4.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); /// /// bool wasm_i32x4_all_true (v128_t a) /// i32x4.all_true : [v128] -> [i32] /// - [Intrinsic] public static bool AllTrue(Vector128 value) => AllTrue(value); // Bitmask extraction @@ -1534,61 +1314,51 @@ public abstract class PackedSimd /// uint32_t wasm_i8x16_bitmask (v128_t a) /// i8x16.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i8x16_bitmask (v128_t a) /// i8x16.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i16x8_bitmask (v128_t a) /// i16x8.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i16x8_bitmask (v128_t a) /// i16x8.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i32x4_bitmask (v128_t a) /// i32x4.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i32x4_bitmask (v128_t a) /// i32x4.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i64x2_bitmask (v128_t a) /// i64x2.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i64x2_bitmask (v128_t a) /// i64x2.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i32x4_bitmask (v128_t a) /// i32x4.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); /// /// uint32_t wasm_i32x4_bitmask (v128_t a) /// i32x4.bitmask : [v128] -> [i32] /// - [Intrinsic] public static int Bitmask(Vector128 value) => Bitmask(value); // Comparisons @@ -1597,420 +1367,351 @@ public abstract class PackedSimd /// v128_t wasm_i8x16_eq (v128_t a, v128_t b) /// i8x16.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i8x16_eq (v128_t a, v128_t b) /// i8x16.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i16x8_eq (v128_t a, v128_t b) /// i16x8.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i16x8_eq (v128_t a, v128_t b) /// i16x8.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i32x4_eq (v128_t a, v128_t b) /// i32x4.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i32x4_eq (v128_t a, v128_t b) /// i32x4.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i64x2_eq (v128_t a, v128_t b) /// i64x2.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i64x2_eq (v128_t a, v128_t b) /// i64x2.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_f32x4_eq (v128_t a, v128_t b) /// f32x4.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_f64x2_eq (v128_t a, v128_t b) /// f64x2.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i32x4_eq (v128_t a, v128_t b) /// i32x4.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i32x4_eq (v128_t a, v128_t b) /// i32x4.eq : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareEqual(Vector128 left, Vector128 right) => CompareEqual(left, right); /// /// v128_t wasm_i8x16_ne (v128_t a, v128_t b) /// i8x16.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i8x16_ne (v128_t a, v128_t b) /// i8x16.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i16x8_ne (v128_t a, v128_t b) /// i16x8.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i16x8_ne (v128_t a, v128_t b) /// i16x8.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i32x4_ne (v128_t a, v128_t b) /// i32x4.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i32x4_ne (v128_t a, v128_t b) /// i32x4.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i64x2_ne (v128_t a, v128_t b) /// i64x2.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i64x2_ne (v128_t a, v128_t b) /// i64x2.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_f32x4_ne (v128_t a, v128_t b) /// f32x4.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_f64x2_ne (v128_t a, v128_t b) /// f64x2.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i32x4_ne (v128_t a, v128_t b) /// i32x4.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i32x4_ne (v128_t a, v128_t b) /// i32x4.ne : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareNotEqual(Vector128 left, Vector128 right) => CompareNotEqual(left, right); /// /// v128_t wasm_i8x16_lt (v128_t a, v128_t b) /// i8x16.lt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_u8x16_lt (v128_t a, v128_t b) /// i8x16.lt_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_i16x8_lt (v128_t a, v128_t b) /// i16x8.lt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_u16x8_lt (v128_t a, v128_t b) /// i16x8.lt_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_i32x4_lt (v128_t a, v128_t b) /// i32x4.lt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_u32x4_lt (v128_t a, v128_t b) /// i32x4.lt_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_i64x2_lt (v128_t a, v128_t b) /// i64x2.lt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// i64x2.lt_s : [v128 v128] -> [v128] /// No unsigned i64x2 comparison instruction exists; the sign bit of each operand is flipped before the signed comparison. /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_f32x4_lt (v128_t a, v128_t b) /// f32x4.lt : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_f64x2_lt (v128_t a, v128_t b) /// f64x2.lt : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_i32x4_lt (v128_t a, v128_t b) /// i32x4.lt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_u32x4_lt (v128_t a, v128_t b) /// i32x4.lt_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThan(Vector128 left, Vector128 right) => CompareLessThan(left, right); /// /// v128_t wasm_i8x16_le (v128_t a, v128_t b) /// i8x16.le_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_u8x16_le (v128_t a, v128_t b) /// i8x16.le_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_i16x8_le (v128_t a, v128_t b) /// i16x8.le_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_u16x8_le (v128_t a, v128_t b) /// i16x8.le_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_i32x4_le (v128_t a, v128_t b) /// i32x4.le_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_u32x4_le (v128_t a, v128_t b) /// i32x4.le_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_i64x2_le (v128_t a, v128_t b) /// i64x2.le_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// i64x2.le_s : [v128 v128] -> [v128] /// No unsigned i64x2 comparison instruction exists; the sign bit of each operand is flipped before the signed comparison. /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_f32x4_le (v128_t a, v128_t b) /// f32x4.le : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_f64x2_le (v128_t a, v128_t b) /// f64x2.le : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_i32x4_le (v128_t a, v128_t b) /// i32x4.le_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_u32x4_le (v128_t a, v128_t b) /// i32x4.le_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareLessThanOrEqual(Vector128 left, Vector128 right) => CompareLessThanOrEqual(left, right); /// /// v128_t wasm_i8x16_gt (v128_t a, v128_t b) /// i8x16.gt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_u8x16_gt (v128_t a, v128_t b) /// i8x16.gt_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_i16x8_gt (v128_t a, v128_t b) /// i16x8.gt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_u16x8_gt (v128_t a, v128_t b) /// i16x8.gt_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_i32x4_gt (v128_t a, v128_t b) /// i32x4.gt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_u32x4_gt (v128_t a, v128_t b) /// i32x4.gt_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_i64x2_gt (v128_t a, v128_t b) /// i64x2.gt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// i64x2.gt_s : [v128 v128] -> [v128] /// No unsigned i64x2 comparison instruction exists; the sign bit of each operand is flipped before the signed comparison. /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_f32x4_gt (v128_t a, v128_t b) /// f32x4.gt : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_f64x2_gt (v128_t a, v128_t b) /// f64x2.gt : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_i32x4_gt (v128_t a, v128_t b) /// i32x4.gt_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_u32x4_gt (v128_t a, v128_t b) /// i32x4.gt_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThan(Vector128 left, Vector128 right) => CompareGreaterThan(left, right); /// /// v128_t wasm_i8x16_ge (v128_t a, v128_t b) /// i8x16.ge_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_u8x16_ge (v128_t a, v128_t b) /// i8x16.ge_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_i16x8_ge (v128_t a, v128_t b) /// i16x8.ge_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_u16x8_ge (v128_t a, v128_t b) /// i16x8.ge_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_i32x4_ge (v128_t a, v128_t b) /// i32x4.ge_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_u32x4_ge (v128_t a, v128_t b) /// i32x4.ge_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_i64x2_ge (v128_t a, v128_t b) /// i64x2.ge_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// i64x2.ge_s : [v128 v128] -> [v128] /// No unsigned i64x2 comparison instruction exists; the sign bit of each operand is flipped before the signed comparison. /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_f32x4_ge (v128_t a, v128_t b) /// f32x4.ge : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_f64x2_ge (v128_t a, v128_t b) @@ -2021,13 +1722,11 @@ public abstract class PackedSimd /// v128_t wasm_i32x4_ge (v128_t a, v128_t b) /// i32x4.ge_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); /// /// v128_t wasm_u32x4_ge (v128_t a, v128_t b) /// i32x4.ge_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 CompareGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareGreaterThanOrEqual(left, right); // Load @@ -2036,268 +1735,224 @@ public abstract class PackedSimd /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(sbyte* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(byte* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(short* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(ushort* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(int* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(uint* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(long* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(ulong* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(float* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(double* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(nint* address) => LoadVector128(address); /// /// v128_t wasm_v128_load (const void *mem) /// v128.load memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadVector128(nuint* address) => LoadVector128(address); /// /// v128_t wasm_v128_load32_zero (const void *mem) /// v128.load32_zero memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarVector128(int* address) => LoadScalarVector128(address); /// /// v128_t wasm_v128_load32_zero (const void *mem) /// v128.load32_zero memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarVector128(uint* address) => LoadScalarVector128(address); /// /// v128_t wasm_v128_load64_zero (const void *mem) /// v128.load64_zero memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarVector128(long* address) => LoadScalarVector128(address); /// /// v128_t wasm_v128_load64_zero (const void *mem) /// v128.load64_zero memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarVector128(ulong* address) => LoadScalarVector128(address); /// /// v128_t wasm_v128_load32_zero (const void *mem) /// v128.load32_zero memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarVector128(float* address) => LoadScalarVector128(address); /// /// v128_t wasm_v128_load64_zero (const void *mem) /// v128.load64_zero memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarVector128(double* address) => LoadScalarVector128(address); /// /// v128_t wasm_v128_load32_zero (const void *mem) /// v128.load32_zero memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarVector128(nint* address) => LoadScalarVector128(address); /// /// v128_t wasm_v128_load32_zero (const void *mem) /// v128.load32_zero memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarVector128(nuint* address) => LoadScalarVector128(address); /// /// v128_t wasm_v128_load8_splat (const void *mem) /// v128.load8_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(sbyte* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load8_splat (const void *mem) /// v128.load8_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(byte* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load16_splat (const void *mem) /// v128.load16_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(short* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load16_splat (const void *mem) /// v128.load16_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(ushort* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load32_splat (const void *mem) /// v128.load32_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(int* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load32_splat (const void *mem) /// v128.load32_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(uint* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load64_splat (const void *mem) /// v128.load64_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(long* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load64_splat (const void *mem) /// v128.load64_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(ulong* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load32_splat (const void *mem) /// v128.load32_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(float* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load64_splat (const void *mem) /// v128.load64_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(double* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load32_splat (const void *mem) /// v128.load32_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(nint* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load32_splat (const void *mem) /// v128.load32_splat memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndSplatVector128(nuint* address) => LoadScalarAndSplatVector128(address); /// /// v128_t wasm_v128_load8_lane (const void *mem, v128_t vec, int i) /// v128.load8_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(sbyte* address, Vector128 vector, [ConstantExpected(Max = (byte)(15))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx16 /// /// v128_t wasm_v128_load8_lane (const void *mem, v128_t vec, int i) /// v128.load8_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(byte* address, Vector128 vector, [ConstantExpected(Max = (byte)(15))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx16 /// /// v128_t wasm_v128_load16_lane (const void *mem, v128_t vec, int i) /// v128.load16_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(short* address, Vector128 vector, [ConstantExpected(Max = (byte)(7))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx8 /// /// v128_t wasm_v128_load16_lane (const void *mem, v128_t vec, int i) /// v128.load16_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(ushort* address, Vector128 vector, [ConstantExpected(Max = (byte)(7))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx8 /// /// v128_t wasm_v128_load32_lane (const void *mem, v128_t vec, int i) /// v128.load32_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(int* address, Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx4 /// /// v128_t wasm_v128_load32_lane (const void *mem, v128_t vec, int i) /// v128.load32_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(uint* address, Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx4 /// /// v128_t wasm_v128_load64_lane (const void *mem, v128_t vec, int i) /// v128.load64_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(long* address, Vector128 vector, [ConstantExpected(Max = (byte)(1))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx2 /// /// v128_t wasm_v128_load64_lane (const void *mem, v128_t vec, int i) /// v128.load64_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(ulong* address, Vector128 vector, [ConstantExpected(Max = (byte)(1))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx2 /// /// v128_t wasm_v128_load32_lane (const void *mem, v128_t vec, int i) /// v128.load32_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(float* address, Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx4 /// /// v128_t wasm_v128_load64_lane (const void *mem, v128_t vec, int i) /// v128.load64_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(double* address, Vector128 vector, [ConstantExpected(Max = (byte)(1))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx2 /// /// v128_t wasm_v128_load32_lane (const void *mem, v128_t vec, int i) /// v128.load32_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(nint* address, Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx4 /// /// v128_t wasm_v128_load32_lane (const void *mem, v128_t vec, int i) /// v128.load32_lane memarg laneidx : [i32 v128] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadScalarAndInsert(nuint* address, Vector128 vector, [ConstantExpected(Max = (byte)(3))] byte index) => LoadScalarAndInsert(address, vector, index); // takes ImmLaneIdx4 // Store @@ -2306,183 +1961,153 @@ public abstract class PackedSimd /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(sbyte* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(byte* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(short* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(ushort* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(int* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(uint* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(long* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(ulong* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(float* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(double* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(nint* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store (void *mem, v128_t a) /// v128.store memarg : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void Store(nuint* address, Vector128 source) => Store(address, source); /// /// void wasm_v128_store8_lane (void *mem, v128_t vec, int i) /// v128.store8_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(sbyte* address, Vector128 source, [ConstantExpected(Max = (byte)(15))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx16 /// /// void wasm_v128_store8_lane (void *mem, v128_t vec, int i) /// v128.store8_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(byte* address, Vector128 source, [ConstantExpected(Max = (byte)(15))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx16 /// /// void wasm_v128_store16_lane (void *mem, v128_t vec, int i) /// v128.store16_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(short* address, Vector128 source, [ConstantExpected(Max = (byte)(7))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx8 /// /// void wasm_v128_store16_lane (void *mem, v128_t vec, int i) /// v128.store16_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(ushort* address, Vector128 source, [ConstantExpected(Max = (byte)(7))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx8 /// /// void wasm_v128_store32_lane (void *mem, v128_t vec, int i) /// v128.store32_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(int* address, Vector128 source, [ConstantExpected(Max = (byte)(3))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx4 /// /// void wasm_v128_store32_lane (void *mem, v128_t vec, int i) /// v128.store32_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(uint* address, Vector128 source, [ConstantExpected(Max = (byte)(3))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx4 /// /// void wasm_v128_store64_lane (void *mem, v128_t vec, int i) /// v128.store64_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(long* address, Vector128 source, [ConstantExpected(Max = (byte)(1))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx2 /// /// void wasm_v128_store64_lane (void *mem, v128_t vec, int i) /// v128.store64_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(ulong* address, Vector128 source, [ConstantExpected(Max = (byte)(1))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx2 /// /// void wasm_v128_store32_lane (void *mem, v128_t vec, int i) /// v128.store32_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(float* address, Vector128 source, [ConstantExpected(Max = (byte)(3))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx4 /// /// void wasm_v128_store64_lane (void *mem, v128_t vec, int i) /// v128.store64_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(double* address, Vector128 source, [ConstantExpected(Max = (byte)(1))] byte index) => StoreSelectedScalar(address, source, index); // takes ImmLaneIdx2 /// /// void wasm_v128_store32_lane (void *mem, v128_t vec, int i) /// v128.store32_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(nint* address, Vector128 source, [ConstantExpected(Max = (byte)(3))] byte index) => StoreSelectedScalar(address, source, index); /// /// void wasm_v128_store32_lane (void *mem, v128_t vec, int i) /// v128.store32_lane memarg laneidx : [i32 v128] -> [] /// - [Intrinsic] public static unsafe void StoreSelectedScalar(nuint* address, Vector128 source, [ConstantExpected(Max = (byte)(3))] byte index) => StoreSelectedScalar(address, source, index); /// /// v128_t wasm_i16x8_load8x8 (const void *mem) /// v128.load8x8_s memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadWideningVector128(sbyte* address) => LoadWideningVector128(address); /// /// v128_t wasm_u16x8_load8x8 (const void *mem) /// v128.load8x8_u memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadWideningVector128(byte* address) => LoadWideningVector128(address); /// /// v128_t wasm_i32x4_load16x4 (const void *mem) /// v128.load16x4_s memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadWideningVector128(short* address) => LoadWideningVector128(address); /// /// v128_t wasm_u32x4_load16x4 (const void *mem) /// v128.load16x4_u memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadWideningVector128(ushort* address) => LoadWideningVector128(address); /// /// v128_t wasm_i64x2_load32x2 (const void *mem) /// v128.load32x2_s memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadWideningVector128(int* address) => LoadWideningVector128(address); /// /// v128_t wasm_u64x2_load32x2 (const void *mem) /// v128.load32x2_u memarg : [i32] -> [v128] /// - [Intrinsic] public static unsafe Vector128 LoadWideningVector128(uint* address) => LoadWideningVector128(address); // Floating-point sign bit operations @@ -2491,26 +2116,22 @@ public abstract class PackedSimd /// v128_t wasm_f32x4_neg (v128_t a) /// f32x4.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_f64x2_neg (v128_t a) /// f64x2.neg : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Negate(Vector128 value) => Negate(value); /// /// v128_t wasm_f32x4_abs (v128_t a) /// f32x4.abs : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Abs(Vector128 value) => Abs(value); /// /// v128_t wasm_f64x2_abs (v128_t a) /// f64x2.abs : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Abs(Vector128 value) => Abs(value); // Floating-point min and max @@ -2519,52 +2140,44 @@ public abstract class PackedSimd /// v128_t wasm_f32x4_min (v128_t a, v128_t b) /// f32x4.min : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Min(Vector128 left, Vector128 right) => Min(left, right); /// /// v128_t wasm_f64x2_min (v128_t a, v128_t b) /// f64x2.min : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Min(Vector128 left, Vector128 right) => Min(left, right); /// /// v128_t wasm_f32x4_max (v128_t a, v128_t b) /// f32x4.max : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Max(Vector128 left, Vector128 right) => Max(left, right); /// /// v128_t wasm_f64x2_max (v128_t a, v128_t b) /// f64x2.max : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Max(Vector128 left, Vector128 right) => Max(left, right); /// /// v128_t wasm_f32x4_pmin (v128_t a, v128_t b) /// f32x4.pmin : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 PseudoMin(Vector128 left, Vector128 right) => PseudoMin(left, right); /// /// v128_t wasm_f64x2_pmin (v128_t a, v128_t b) /// f64x2.pmin : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 PseudoMin(Vector128 left, Vector128 right) => PseudoMin(left, right); /// /// v128_t wasm_f32x4_pmax (v128_t a, v128_t b) /// f32x4.pmax : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 PseudoMax(Vector128 left, Vector128 right) => PseudoMax(left, right); /// /// v128_t wasm_f64x2_pmax (v128_t a, v128_t b) /// f64x2.pmax : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 PseudoMax(Vector128 left, Vector128 right) => PseudoMax(left, right); // Floating-point arithmetic @@ -2573,117 +2186,99 @@ public abstract class PackedSimd /// v128_t wasm_f32x4_add (v128_t a, v128_t b) /// f32x4.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_f64x2_add (v128_t a, v128_t b) /// f64x2.add : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Add(Vector128 left, Vector128 right) => Add(left, right); /// /// v128_t wasm_f32x4_sub (v128_t a, v128_t b) /// f32x4.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_f64x2_sub (v128_t a, v128_t b) /// f64x2.sub : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Subtract(Vector128 left, Vector128 right) => Subtract(left, right); /// /// v128_t wasm_f32x4_div (v128_t a, v128_t b) /// f32x4.div : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Divide(Vector128 left, Vector128 right) => Divide(left, right); /// /// v128_t wasm_f64x2_div (v128_t a, v128_t b) /// f64x2.div : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Divide(Vector128 left, Vector128 right) => Divide(left, right); /// /// v128_t wasm_f32x4_mul (v128_t a, v128_t b) /// f32x4.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_f64x2_mul (v128_t a, v128_t b) /// f64x2.mul : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 Multiply(Vector128 left, Vector128 right) => Multiply(left, right); /// /// v128_t wasm_f32x4_sqrt (v128_t a) /// f32x4.sqrt : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Sqrt(Vector128 value) => Sqrt(value); /// /// v128_t wasm_f64x2_sqrt (v128_t a) /// f64x2.sqrt : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Sqrt(Vector128 value) => Sqrt(value); /// /// v128_t wasm_f32x4_ceil (v128_t a) /// f32x4.ceil : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Ceiling(Vector128 value) => Ceiling(value); /// /// v128_t wasm_f64x2_ceil (v128_t a) /// f64x2.ceil : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Ceiling(Vector128 value) => Ceiling(value); /// /// v128_t wasm_f32x4_floor (v128_t a) /// f32x4.floor : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Floor(Vector128 value) => Floor(value); /// /// v128_t wasm_f64x2_floor (v128_t a) /// f64x2.floor : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Floor(Vector128 value) => Floor(value); /// /// v128_t wasm_f32x4_trunc (v128_t a) /// f32x4.trunc : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Truncate(Vector128 value) => Truncate(value); /// /// v128_t wasm_f64x2_trunc (v128_t a) /// f64x2.trunc : [v128] -> [v128] /// - [Intrinsic] public static Vector128 Truncate(Vector128 value) => Truncate(value); /// /// v128_t wasm_f32x4_nearest (v128_t a) /// f32x4.nearest : [v128] -> [v128] /// - [Intrinsic] public static Vector128 RoundToNearest(Vector128 value) => RoundToNearest(value); /// /// v128_t wasm_f64x2_nearest (v128_t a) /// f64x2.nearest : [v128] -> [v128] /// - [Intrinsic] public static Vector128 RoundToNearest(Vector128 value) => RoundToNearest(value); // Conversions @@ -2692,240 +2287,202 @@ public abstract class PackedSimd /// v128_t wasm_f32x4_convert_i32x4 (v128_t a) /// f32x4.convert_i32x4_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToSingle(Vector128 value) => ConvertToSingle(value); /// /// v128_t wasm_f32x4_convert_u32x4 (v128_t a) /// f32x4.convert_i32x4_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToSingle(Vector128 value) => ConvertToSingle(value); /// /// v128_t wasm_f32x4_demote_f64x2_zero (v128_t a) /// f32x4.demote_f64x2_zero : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToSingle(Vector128 value) => ConvertToSingle(value); /// /// v128_t wasm_f64x2_convert_low_i32x4 (v128_t a) /// f64x2.convert_low_i32x4_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToDoubleLower(Vector128 value) => ConvertToDoubleLower(value); /// /// v128_t wasm_f64x2_convert_low_u32x4 (v128_t a) /// f64x2.convert_low_i32x4_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToDoubleLower(Vector128 value) => ConvertToDoubleLower(value); /// /// v128_t wasm_f64x2_promote_low_f32x4 (v128_t a) /// f64x2.promote_low_f32x4 : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToDoubleLower(Vector128 value) => ConvertToDoubleLower(value); /// /// v128_t wasm_i32x4_trunc_sat_f32x4 (v128_t a) /// i32x4.trunc_sat_f32x4_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToInt32Saturate(Vector128 value) => ConvertToInt32Saturate(value); /// /// v128_t wasm_u32x4_trunc_sat_f32x4 (v128_t a) /// i32x4.trunc_sat_f32x4_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToUInt32Saturate(Vector128 value) => ConvertToUInt32Saturate(value); /// /// v128_t wasm_i32x4_trunc_sat_f64x2_zero (v128_t a) /// i32x4.trunc_sat_f64x2_s_zero : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToInt32Saturate(Vector128 value) => ConvertToInt32Saturate(value); /// /// v128_t wasm_u32x4_trunc_sat_f64x2_zero (v128_t a) /// i32x4.trunc_sat_f64x2_u_zero : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertToUInt32Saturate(Vector128 value) => ConvertToUInt32Saturate(value); /// /// v128_t wasm_i8x16_narrow_i16x8 (v128_t a, v128_t b) /// i8x16.narrow_i16x8_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertNarrowingSaturateSigned(Vector128 lower, Vector128 upper) => ConvertNarrowingSaturateSigned(lower, upper); /// /// v128_t wasm_i16x8_narrow_i32x4 (v128_t a, v128_t b) /// i16x8.narrow_i32x4_s : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertNarrowingSaturateSigned(Vector128 lower, Vector128 upper) => ConvertNarrowingSaturateSigned(lower, upper); /// /// v128_t wasm_u8x16_narrow_i16x8 (v128_t a, v128_t b) /// i8x16.narrow_i16x8_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertNarrowingSaturateUnsigned(Vector128 lower, Vector128 upper) => ConvertNarrowingSaturateUnsigned(lower, upper); /// /// v128_t wasm_u16x8_narrow_i32x4 (v128_t a, v128_t b) /// i16x8.narrow_i32x4_u : [v128 v128] -> [v128] /// - [Intrinsic] public static Vector128 ConvertNarrowingSaturateUnsigned(Vector128 lower, Vector128 upper) => ConvertNarrowingSaturateUnsigned(lower, upper); /// /// v128_t wasm_i16x8_extend_low_i8x16 (v128_t a) /// i16x8.extend_low_i8x16_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningLower(Vector128 value) => SignExtendWideningLower(value); /// /// v128_t wasm_i16x8_extend_low_i8x16 (v128_t a) /// i16x8.extend_low_i8x16_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningLower(Vector128 value) => SignExtendWideningLower(value); /// /// v128_t wasm_i32x4_extend_low_i16x8 (v128_t a) /// i32x4.extend_low_i16x8_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningLower(Vector128 value) => SignExtendWideningLower(value); /// /// v128_t wasm_i32x4_extend_low_i16x8 (v128_t a) /// i32x4.extend_low_i16x8_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningLower(Vector128 value) => SignExtendWideningLower(value); /// /// v128_t wasm_i64x2_extend_low_i32x4 (v128_t a) /// i64x2.extend_low_i32x4_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningLower(Vector128 value) => SignExtendWideningLower(value); /// /// v128_t wasm_i64x2_extend_low_i32x4 (v128_t a) /// i64x2.extend_low_i32x4_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningLower(Vector128 value) => SignExtendWideningLower(value); /// /// v128_t wasm_i16x8_extend_high_i8x16 (v128_t a) /// i16x8.extend_high_i8x16_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningUpper(Vector128 value) => SignExtendWideningUpper(value); /// /// v128_t wasm_i16x8_extend_high_i8x16 (v128_t a) /// i16x8.extend_high_i8x16_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningUpper(Vector128 value) => SignExtendWideningUpper(value); /// /// v128_t wasm_i32x4_extend_high_i16x8 (v128_t a) /// i32x4.extend_high_i16x8_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningUpper(Vector128 value) => SignExtendWideningUpper(value); /// /// v128_t wasm_i32x4_extend_high_i16x8 (v128_t a) /// i32x4.extend_high_i16x8_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningUpper(Vector128 value) => SignExtendWideningUpper(value); /// /// v128_t wasm_i64x2_extend_high_i32x4 (v128_t a) /// i64x2.extend_high_i32x4_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningUpper(Vector128 value) => SignExtendWideningUpper(value); /// /// v128_t wasm_i64x2_extend_high_i32x4 (v128_t a) /// i64x2.extend_high_i32x4_s : [v128] -> [v128] /// - [Intrinsic] public static Vector128 SignExtendWideningUpper(Vector128 value) => SignExtendWideningUpper(value); /// /// v128_t wasm_u16x8_extend_low_u8x16 (v128_t a) /// i16x8.extend_low_i8x16_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningLower(Vector128 value) => ZeroExtendWideningLower(value); /// /// v128_t wasm_u16x8_extend_low_u8x16 (v128_t a) /// i16x8.extend_low_i8x16_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningLower(Vector128 value) => ZeroExtendWideningLower(value); /// /// v128_t wasm_u32x4_extend_low_u16x8 (v128_t a) /// i32x4.extend_low_i16x8_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningLower(Vector128 value) => ZeroExtendWideningLower(value); /// /// v128_t wasm_u32x4_extend_low_u16x8 (v128_t a) /// i32x4.extend_low_i16x8_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningLower(Vector128 value) => ZeroExtendWideningLower(value); /// /// v128_t wasm_u64x2_extend_low_u32x4 (v128_t a) /// i64x2.extend_low_i32x4_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningLower(Vector128 value) => ZeroExtendWideningLower(value); /// /// v128_t wasm_u64x2_extend_low_u32x4 (v128_t a) /// i64x2.extend_low_i32x4_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningLower(Vector128 value) => ZeroExtendWideningLower(value); /// /// v128_t wasm_u16x8_extend_high_u8x16 (v128_t a) /// i16x8.extend_high_i8x16_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningUpper(Vector128 value) => ZeroExtendWideningUpper(value); /// /// v128_t wasm_u16x8_extend_high_u8x16 (v128_t a) /// i16x8.extend_high_i8x16_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningUpper(Vector128 value) => ZeroExtendWideningUpper(value); /// /// v128_t wasm_u32x4_extend_high_u16x8 (v128_t a) /// i32x4.extend_high_i16x8_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningUpper(Vector128 value) => ZeroExtendWideningUpper(value); /// /// v128_t wasm_u32x4_extend_high_u16x8 (v128_t a) /// i32x4.extend_high_i16x8_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningUpper(Vector128 value) => ZeroExtendWideningUpper(value); /// /// v128_t wasm_u64x2_extend_high_u32x4 (v128_t a) /// i64x2.extend_high_i32x4_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningUpper(Vector128 value) => ZeroExtendWideningUpper(value); /// /// v128_t wasm_u64x2_extend_high_u32x4 (v128_t a) /// i64x2.extend_high_i32x4_u : [v128] -> [v128] /// - [Intrinsic] public static Vector128 ZeroExtendWideningUpper(Vector128 value) => ZeroExtendWideningUpper(value); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs index b798dba1da3210..06e1598f895a92 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Wasm/RelaxedSimd.cs @@ -28,91 +28,172 @@ public abstract class RelaxedSimd /// A value of indicates that the APIs will throw . public static bool IsSupported { get { return IsSupported; } } - // Relaxed swizzle: like PackedSimd.Swizzle, but for index lanes outside [0, 16) the - // result is implementation-defined (often the index modulo 16 on x86, zero on ARM). - - /// i8x16.relaxed_swizzle + /// Swizzles the elements in a vector using the specified indices. + /// The vector to swizzle. + /// The indices used to select elements from . + /// The swizzled vector. + /// + /// This method maps to the i8x16.relaxed_swizzle instruction. For indices outside + /// the range [0, 16), the result is implementation-defined. Use the corresponding + /// PackedSimd.Swizzle overload when deterministic handling of out-of-range indices is required. + /// public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => SwizzleNative(vector, indices); - /// i8x16.relaxed_swizzle + /// public static Vector128 SwizzleNative(Vector128 vector, Vector128 indices) => SwizzleNative(vector, indices); - // Relaxed truncating float-to-int conversions. For NaN or out-of-range inputs the result is - // implementation-defined; the saturating PackedSimd.ConvertToInt32Saturate / ConvertToUInt32Saturate - // overloads provide deterministic semantics. - - /// i32x4.relaxed_trunc_f32x4_s + /// Converts a vector of single-precision floating-point values to a vector of signed integers using native truncation. + /// The vector to convert. + /// The converted vector. + /// + /// This method maps to the i32x4.relaxed_trunc_f32x4_s instruction. The result for + /// NaN or out-of-range inputs is implementation-defined. Use + /// for deterministic saturation. + /// public static Vector128 ConvertToInt32Native(Vector128 value) => ConvertToInt32Native(value); - /// i32x4.relaxed_trunc_f32x4_u + /// Converts a vector of single-precision floating-point values to a vector of unsigned integers using native truncation. + /// The vector to convert. + /// The converted vector. + /// + /// This method maps to the i32x4.relaxed_trunc_f32x4_u instruction. The result for + /// NaN or out-of-range inputs is implementation-defined. Use + /// for deterministic saturation. + /// public static Vector128 ConvertToUInt32Native(Vector128 value) => ConvertToUInt32Native(value); - /// i32x4.relaxed_trunc_f64x2_s_zero + /// Converts a vector of double-precision floating-point values to a vector of signed integers using native truncation. + /// The vector to convert. + /// The converted vector, with the upper two elements initialized to zero. + /// + /// This method maps to the i32x4.relaxed_trunc_f64x2_s_zero instruction. The result + /// for NaN or out-of-range inputs is implementation-defined. Use + /// for deterministic saturation. + /// public static Vector128 ConvertToInt32Native(Vector128 value) => ConvertToInt32Native(value); - /// i32x4.relaxed_trunc_f64x2_u_zero + /// Converts a vector of double-precision floating-point values to a vector of unsigned integers using native truncation. + /// The vector to convert. + /// The converted vector, with the upper two elements initialized to zero. + /// + /// This method maps to the i32x4.relaxed_trunc_f64x2_u_zero instruction. The result + /// for NaN or out-of-range inputs is implementation-defined. Use + /// for deterministic saturation. + /// public static Vector128 ConvertToUInt32Native(Vector128 value) => ConvertToUInt32Native(value); - // Relaxed fused multiply-add. Whether the intermediate product is rounded before the add - // (and whether the underlying instruction is a true fused FMA) is implementation-defined. - - /// f32x4.relaxed_madd + /// Computes an implementation-dependent estimate of the product of two vectors added to a third vector. + /// The first vector to multiply. + /// The second vector to multiply. + /// The vector to add to the product. + /// The estimated multiply-add result. + /// + /// This method maps to the corresponding relaxed_madd instruction. Whether the + /// product is rounded before the addition, and whether the operation is fused, is + /// implementation-defined. + /// public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddEstimate(left, right, addend); - /// f64x2.relaxed_madd + /// public static Vector128 MultiplyAddEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddEstimate(left, right, addend); - /// f32x4.relaxed_nmadd + /// Computes an implementation-dependent estimate of the negated product of two vectors added to a third vector. + /// The first vector to multiply. + /// The second vector to multiply. + /// The vector to add to the negated product. + /// The estimated negated multiply-add result. + /// + /// This method maps to the corresponding relaxed_nmadd instruction. Whether the + /// product is rounded before the addition, and whether the operation is fused, is + /// implementation-defined. + /// public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddNegatedEstimate(left, right, addend); - /// f64x2.relaxed_nmadd + /// public static Vector128 MultiplyAddNegatedEstimate(Vector128 left, Vector128 right, Vector128 addend) => MultiplyAddNegatedEstimate(left, right, addend); - // Relaxed lane select. The mask is interpreted per-byte/word; lanes where the mask bit is - // neither all-ones nor all-zeros produce implementation-defined results. For deterministic - // selection use Vector128.ConditionalSelect. - - /// i8x16.relaxed_laneselect + /// Selects elements from two vectors using the specified mask. + /// The vector selected when a mask element is all bits set. + /// The vector selected when a mask element is zero. + /// The mask used to select elements. + /// A vector containing the selected elements. + /// + /// This method maps to the corresponding relaxed_laneselect instruction. The result + /// is implementation-defined for mask elements that are neither all bits set nor zero. Use + /// + /// when deterministic bitwise selection is required. + /// public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); - /// i8x16.relaxed_laneselect + /// public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); - /// i16x8.relaxed_laneselect + /// public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); - /// i16x8.relaxed_laneselect + /// public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); - /// i32x4.relaxed_laneselect + /// public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); - /// i32x4.relaxed_laneselect + /// public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); - /// i64x2.relaxed_laneselect + /// public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); - /// i64x2.relaxed_laneselect + /// public static Vector128 LaneSelectNative(Vector128 left, Vector128 right, Vector128 mask) => LaneSelectNative(left, right, mask); - // Relaxed min/max. NaN handling and sign-of-zero handling are implementation-defined. - // For IEEE-compliant min/max use PackedSimd.Min/Max; for pseudo-min/max (one-sided NaN - // propagation) use PackedSimd.PseudoMin/PseudoMax. - - /// f32x4.relaxed_min + /// Computes the minimum of each pair of elements in two vectors. + /// The first vector. + /// The second vector. + /// A vector containing the minimum values. + /// + /// This method maps to the corresponding relaxed_min instruction. NaN handling and + /// the sign of a zero result are implementation-defined. Use PackedSimd.Min for + /// IEEE-compliant behavior or PackedSimd.PseudoMin for pseudo-minimum behavior. + /// public static Vector128 MinNative(Vector128 left, Vector128 right) => MinNative(left, right); - /// f32x4.relaxed_max + /// Computes the maximum of each pair of elements in two vectors. + /// The first vector. + /// The second vector. + /// A vector containing the maximum values. + /// + /// This method maps to the corresponding relaxed_max instruction. NaN handling and + /// the sign of a zero result are implementation-defined. Use PackedSimd.Max for + /// IEEE-compliant behavior or PackedSimd.PseudoMax for pseudo-maximum behavior. + /// public static Vector128 MaxNative(Vector128 left, Vector128 right) => MaxNative(left, right); - /// f64x2.relaxed_min + /// public static Vector128 MinNative(Vector128 left, Vector128 right) => MinNative(left, right); - /// f64x2.relaxed_max + /// public static Vector128 MaxNative(Vector128 left, Vector128 right) => MaxNative(left, right); - // Relaxed Q15 multiply with rounding. Differs from PackedSimd.MultiplyRoundedSaturateQ15 - // (i16x8.q15mulr_sat_s) in that the multiplication of INT16_MIN by INT16_MIN produces an - // implementation-defined value (typically INT16_MIN unsaturated on x86). - - /// i16x8.relaxed_q15mulr_s + /// Multiplies corresponding signed 16-bit fixed-point elements and returns their rounded high halves. + /// The first vector to multiply. + /// The second vector to multiply. + /// The rounded fixed-point products. + /// + /// This method maps to the i16x8.relaxed_q15mulr_s instruction. The result of + /// multiplying by is + /// implementation-defined. Use + /// + /// when deterministic saturation is required. + /// public static Vector128 MultiplyRoundedQ15Native(Vector128 left, Vector128 right) => MultiplyRoundedQ15Native(left, right); - // Relaxed dot products for signed-by-unsigned bytes. Per the finished spec pseudocode, - // operand `a` is signed and operand `b` is unsigned 7-bit; when any lane of `b` has the - // high bit set that lane's product is implementation-defined (may be interpreted as - // signed or unsigned). The pairwise/adjacent summation is also implementation-defined - // saturating (may or may not saturate on overflow). - - /// i16x8.relaxed_dot_i8x16_i7x16_s — multiplies adjacent (sbyte, byte) pairs and sums each pair into a signed 16-bit lane. + /// Multiplies adjacent signed and unsigned byte elements and sums each pair into a signed 16-bit element. + /// The vector containing signed byte elements. + /// The vector containing unsigned 7-bit elements. + /// The pairwise dot products. + /// + /// This method maps to the i16x8.relaxed_dot_i8x16_i7x16_s instruction. If an element + /// in has its high bit set, whether it is interpreted as signed or + /// unsigned is implementation-defined. Whether the pairwise sum saturates on overflow is + /// also implementation-defined. + /// public static Vector128 DotProductNative(Vector128 left, Vector128 right) => DotProductNative(left, right); - /// i32x4.relaxed_dot_i8x16_i7x16_add_s — multiplies four adjacent (sbyte, byte) pairs, sums them with a signed 32-bit accumulator, and returns the result. + /// Multiplies groups of four signed and unsigned byte elements and adds each sum to a signed 32-bit accumulator. + /// The vector containing signed byte elements. + /// The vector containing unsigned 7-bit elements. + /// The vector added to the dot products. + /// The accumulated dot products. + /// + /// This method maps to the i32x4.relaxed_dot_i8x16_i7x16_add_s instruction. If an + /// element in has its high bit set, whether it is interpreted as + /// signed or unsigned is implementation-defined. Whether the group sum saturates on + /// overflow is also implementation-defined. + /// public static Vector128 DotProductAddNative(Vector128 left, Vector128 right, Vector128 accumulator) => DotProductAddNative(left, right, accumulator); } } From 102b2d6744613341229a688866ae86435f747332 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 11:39:21 -0500 Subject: [PATCH 24/31] [wasm] Address RelaxedSimd test and WASI feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- .../coreclr/hardware-intrinsics-wasm.yml | 2 +- .../TestCases/Webcil/WasmSimdModule.cs | 26 +--- .../tests/Wasm/RelaxedSimdTests.cs | 1 - ...t.NET.Runtime.WebAssembly.Wasi.Sdk.pkgproj | 2 + src/mono/wasi/README.md | 1 + src/mono/wasi/build/WasiApp.targets | 3 + src/mono/wasi/wasi.proj | 5 +- .../Wasm/RelaxedSimd/RelaxedSimdTests.cs | 127 ++++++++++++++++++ .../RelaxedSimd/RelaxedSimdTests_r.csproj | 12 ++ .../RelaxedSimd/RelaxedSimdTests_ro.csproj | 12 ++ 10 files changed, 164 insertions(+), 27 deletions(-) create mode 100644 src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests.cs create mode 100644 src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests_r.csproj create mode 100644 src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests_ro.csproj diff --git a/eng/pipelines/coreclr/hardware-intrinsics-wasm.yml b/eng/pipelines/coreclr/hardware-intrinsics-wasm.yml index 4db8cafecc02a9..c21c1649ad19ba 100644 --- a/eng/pipelines/coreclr/hardware-intrinsics-wasm.yml +++ b/eng/pipelines/coreclr/hardware-intrinsics-wasm.yml @@ -28,4 +28,4 @@ extends: parameters: platforms: - browser_wasm - testBuildArgs: '/p:EnableWasmHWIntrinsicsTests=true -tree:JIT/HardwareIntrinsics/Wasm' + testBuildArgs: '/p:EnableWasmHWIntrinsicsTests=true /p:WasmEnableRelaxedSimd=true -tree:JIT/HardwareIntrinsics/Wasm' diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs index 42d4abdc1216b4..b12002a32db439 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs @@ -8,9 +8,8 @@ namespace Webcil; -// Exercises the wasm v128 calling convention (SIMD passed/returned/stored by value) -// without relying on any SIMD arithmetic intrinsics, so only the ABI/materialization -// paths are covered. +// The initial helpers exercise the wasm v128 calling convention without relying on SIMD +// arithmetic intrinsics. The RelaxedSimd helpers exercise exact instruction lowering. public static class WasmSimdModule { [MethodImpl(MethodImplOptions.NoInlining)] @@ -51,136 +50,115 @@ public static Vector CallEchoVectorT(Vector value) return EchoVectorT(value); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedSwizzleNative(Vector128 value, Vector128 indices) { return RelaxedSimd.SwizzleNative(value, indices); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedConvertF32ToInt32Native(Vector128 value) { return RelaxedSimd.ConvertToInt32Native(value); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedConvertF32ToUInt32Native(Vector128 value) { return RelaxedSimd.ConvertToUInt32Native(value); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedConvertF64ToInt32Native(Vector128 value) { return RelaxedSimd.ConvertToInt32Native(value); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedConvertF64ToUInt32Native(Vector128 value) { return RelaxedSimd.ConvertToUInt32Native(value); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMultiplyAddF32( Vector128 left, Vector128 right, Vector128 addend) { return RelaxedSimd.MultiplyAddEstimate(left, right, addend); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMultiplyAddNegatedF32( Vector128 left, Vector128 right, Vector128 addend) { return RelaxedSimd.MultiplyAddNegatedEstimate(left, right, addend); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMultiplyAddF64( Vector128 left, Vector128 right, Vector128 addend) { return RelaxedSimd.MultiplyAddEstimate(left, right, addend); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMultiplyAddNegatedF64( Vector128 left, Vector128 right, Vector128 addend) { return RelaxedSimd.MultiplyAddNegatedEstimate(left, right, addend); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedLaneSelectI8Native( Vector128 left, Vector128 right, Vector128 mask) { return RelaxedSimd.LaneSelectNative(left, right, mask); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedLaneSelectI16Native( Vector128 left, Vector128 right, Vector128 mask) { return RelaxedSimd.LaneSelectNative(left, right, mask); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedLaneSelectI32Native( Vector128 left, Vector128 right, Vector128 mask) { return RelaxedSimd.LaneSelectNative(left, right, mask); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedLaneSelectI64Native( Vector128 left, Vector128 right, Vector128 mask) { return RelaxedSimd.LaneSelectNative(left, right, mask); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMinF32Native(Vector128 left, Vector128 right) { return RelaxedSimd.MinNative(left, right); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMaxF32Native(Vector128 left, Vector128 right) { return RelaxedSimd.MaxNative(left, right); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMinF64Native(Vector128 left, Vector128 right) { return RelaxedSimd.MinNative(left, right); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMaxF64Native(Vector128 left, Vector128 right) { return RelaxedSimd.MaxNative(left, right); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedMultiplyRoundedQ15Native(Vector128 left, Vector128 right) { return RelaxedSimd.MultiplyRoundedQ15Native(left, right); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedDotProductNative(Vector128 left, Vector128 right) { return RelaxedSimd.DotProductNative(left, right); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedDotProductAddNative( Vector128 left, Vector128 right, Vector128 accumulator) { return RelaxedSimd.DotProductAddNative(left, right, accumulator); } - [MethodImpl(MethodImplOptions.NoInlining)] public static Vector128 RelaxedSwizzleNativeIfSupported(Vector128 value, Vector128 indices) { return RelaxedSimd.IsSupported ? RelaxedSimd.SwizzleNative(value, indices) : value; diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs index 501bad2ed884ad..18a34f60b77d28 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs @@ -10,7 +10,6 @@ namespace System.Runtime.Intrinsics.Wasm.Tests { [PlatformSpecific(TestPlatforms.Browser)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/123011", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsCoreCLR))] public sealed class RelaxedSimdTests { public static bool IsNotSupported => !RelaxedSimd.IsSupported; diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.pkgproj b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.pkgproj index 5affff8d935deb..ed574be10fe761 100644 --- a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.pkgproj +++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.pkgproj @@ -18,6 +18,8 @@ + + diff --git a/src/mono/wasi/README.md b/src/mono/wasi/README.md index 45f1d2fb5ee981..cd2c309e623992 100644 --- a/src/mono/wasi/README.md +++ b/src/mono/wasi/README.md @@ -55,6 +55,7 @@ The following MSBuild properties will trigger a relinking during the WASI build - **`EnableDiagnostics`** - `/p:EnableDiagnostics=true` - Enables or disables diagnostic features in the native runtime. - **`WasmProfilers`** - `/p:WasmProfilers=...` - Changes profiler configuration in the native runtime. - **`WasmEnableSIMD`** - `/p:WasmEnableSIMD=true` - Enables or disables SIMD instruction support. +- **`WasmEnableRelaxedSimd`** - `/p:WasmEnableRelaxedSimd=true` - Enables relaxed SIMD instruction support and requires `WasmEnableSIMD=true`. - **`WasiBuildArgs`** - Any change to `/p:WasiBuildArgs=...` (custom build flags or feature toggles) can trigger a relink. - **Configuration/Target Architecture** - Changing `/p:Configuration=Debug|Release` or `/p:RuntimeIdentifier=wasi-wasm`, etc., may require relinking. diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets index a4d2d3dd7338ec..7e8a2b61c71538 100644 --- a/src/mono/wasi/build/WasiApp.targets +++ b/src/mono/wasi/build/WasiApp.targets @@ -57,6 +57,8 @@ <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false' and '$(_WasiIntrinsicsSubstitutionsDir)' != ''">$(_ExtraTrimmerArgs) --substitutions "$(_WasiIntrinsicsSubstitutionsDir)ILLink.Substitutions.WasmIntrinsics.xml" <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' == 'false' and '$(_WasiIntrinsicsSubstitutionsDir)' != ''">$(_ExtraTrimmerArgs) --substitutions "$(_WasiIntrinsicsSubstitutionsDir)ILLink.Substitutions.NoWasmIntrinsics.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false' and '$(WasmEnableRelaxedSimd)' == 'true' and '$(_WasiIntrinsicsSubstitutionsDir)' != ''">$(_ExtraTrimmerArgs) --substitutions "$(_WasiIntrinsicsSubstitutionsDir)ILLink.Substitutions.WasmRelaxedSimd.xml" + <_ExtraTrimmerArgs Condition="'$(WasmEnableSIMD)' != 'false' and '$(WasmEnableRelaxedSimd)' != 'true' and '$(_WasiIntrinsicsSubstitutionsDir)' != ''">$(_ExtraTrimmerArgs) --substitutions "$(_WasiIntrinsicsSubstitutionsDir)ILLink.Substitutions.NoWasmRelaxedSimd.xml" false @@ -259,6 +261,7 @@ <_WasiClangCommonFlags Include="-msimd128" Condition="'$(WasmEnableSIMD)' == 'true'" /> + <_WasiClangCommonFlags Include="-mrelaxed-simd" Condition="'$(WasmEnableSIMD)' == 'true' and '$(WasmEnableRelaxedSimd)' == 'true'" /> <_WasmCommonCFlags Include="-DGEN_PINVOKE=1" /> <_WasmCommonCFlags Condition="'$(_WasmShouldAOT)' == 'true'" Include="-DENABLE_AOT=1" /> diff --git a/src/mono/wasi/wasi.proj b/src/mono/wasi/wasi.proj index 66fcd6e7fe4b71..95609245e1f96e 100644 --- a/src/mono/wasi/wasi.proj +++ b/src/mono/wasi/wasi.proj @@ -6,6 +6,7 @@ true false false + false $(ArtifactsObjDir)wasi @@ -90,6 +91,7 @@ { "identity": "EnableDiagnostics", "defaultValueInRuntimePack": "$(EnableDiagnostics)" }, { "identity": "WasmProfilers", "defaultValueInRuntimePack": "$(WasmProfilers)" }, { "identity": "WasmEnableSIMD", "defaultValueInRuntimePack": "$(WasmEnableSIMD)" }, + { "identity": "WasmEnableRelaxedSimd", "defaultValueInRuntimePack": "$(WasmEnableRelaxedSimd)" }, { "identity": "RunAOTCompilation", "defaultValueInRuntimePack": "$(RunAOTCompilation)" } ] } @@ -154,7 +156,8 @@ $(CMakeBuildRuntimeConfigureCmd) -DMONO_OBJ_INCLUDES="$(MonoObjDir.TrimEnd('\/'))" $(CMakeBuildRuntimeConfigureCmd) -DMONO_ARTIFACTS_DIR="$(MonoArtifactsPath.TrimEnd('\/'))" $(CMakeBuildRuntimeConfigureCmd) -DNATIVE_BIN_DIR="$(NativeBinDir.TrimEnd('\/'))" - $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128" -DCONFIGURATION_INTERPSIMDTABLES_LIB="simd" + $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128 -mrelaxed-simd" -DCONFIGURATION_INTERPSIMDTABLES_LIB="relaxed-simd" + $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128" -DCONFIGURATION_INTERPSIMDTABLES_LIB="simd" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_INTERPSIMDTABLES_LIB="nosimd" $(CMakeBuildRuntimeConfigureCmd) -DDISABLE_THREADS=0 call "$(RepositoryEngineeringDir)native\init-vs-env.cmd" wasm && $(CMakeBuildRuntimeConfigureCmd) diff --git a/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests.cs b/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests.cs new file mode 100644 index 00000000000000..c82650d49acc41 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.Wasm; +using Xunit; + +public sealed class RelaxedSimdTests +{ + public static bool IsNotSupported => !RelaxedSimd.IsSupported; + + [Fact] + [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(RelaxedSimd))] + public static void RelaxedSimdIsSupportedReflects() + { + MethodInfo? methodInfo = typeof(RelaxedSimd).GetProperty(nameof(RelaxedSimd.IsSupported))?.GetGetMethod(); + Assert.NotNull(methodInfo); + Assert.Equal(RelaxedSimd.IsSupported, methodInfo.Invoke(null, null)); + } + + [ConditionalFact(typeof(RelaxedSimdTests), nameof(IsNotSupported))] + public static void UnsupportedMethodThrowsPlatformNotSupportedException() + { + Assert.Throws( + () => RelaxedSimd.ConvertToInt32Native(Vector128.Create(1.0f))); + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public static void ConvertToIntegerNativeInRangeMatchesExpected() + { + Assert.Equal( + Vector128.Create(1, -2, 3, -4), + RelaxedSimd.ConvertToInt32Native(Vector128.Create(1.75f, -2.25f, 3.0f, -4.99f))); + Assert.Equal( + Vector128.Create(5, -6, 0, 0), + RelaxedSimd.ConvertToInt32Native(Vector128.Create(5.75, -6.25))); + Assert.Equal( + Vector128.Create(1u, 2u, 3u, 4u), + RelaxedSimd.ConvertToUInt32Native(Vector128.Create(1.75f, 2.25f, 3.0f, 4.99f))); + Assert.Equal( + Vector128.Create(5u, 6u, 0u, 0u), + RelaxedSimd.ConvertToUInt32Native(Vector128.Create(5.75, 6.25))); + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public static void DotProductNativeByteSByteMatchesScalar() + { + Vector128 signed = Vector128.Create((sbyte)-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16); + Vector128 unsigned = Vector128.Create((byte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); + + Vector128 actual = RelaxedSimd.DotProductNative(signed, unsigned); + + for (int i = 0; i < 8; i++) + { + short expected = (short)(signed[2 * i] * unsigned[2 * i] + signed[2 * i + 1] * unsigned[2 * i + 1]); + Assert.Equal(expected, actual[i]); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public static void DotProductAddNativeByteSByteMatchesScalar() + { + Vector128 signed = Vector128.Create((sbyte)-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16); + Vector128 unsigned = Vector128.Create((byte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); + Vector128 accumulator = Vector128.Create(100, 200, 300, 400); + + Vector128 actual = RelaxedSimd.DotProductAddNative(signed, unsigned, accumulator); + + for (int i = 0; i < 4; i++) + { + int expected = accumulator[i]; + for (int j = 0; j < 4; j++) + { + expected += signed[4 * i + j] * unsigned[4 * i + j]; + } + Assert.Equal(expected, actual[i]); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public static void MultiplyAddFloatMatchesScalarApproximately() + { + Vector128 left = Vector128.Create(1.5f, 2.25f, -3.125f, 4.0f); + Vector128 right = Vector128.Create(2.0f, -1.5f, 0.5f, 6.25f); + Vector128 addend = Vector128.Create(0.5f, 1.0f, -0.25f, -2.0f); + + Vector128 actual = RelaxedSimd.MultiplyAddEstimate(left, right, addend); + Vector128 unfused = (left * right) + addend; + + const float RelativeTolerance = 1e-5f; + for (int i = 0; i < 4; i++) + { + float tolerance = Math.Max(Math.Abs(unfused[i]), 1.0f) * RelativeTolerance; + Assert.True(Math.Abs(actual[i] - unfused[i]) <= tolerance, + $"lane {i}: relaxed FMA {actual[i]} differs from unfused {unfused[i]} by more than {tolerance}"); + } + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public static void LaneSelectNativeAllOnesAllZerosBehavesLikeConditionalSelect() + { + Vector128 left = Vector128.Create((byte)1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + Vector128 right = Vector128.Create((byte)17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32); + Vector128 mask = Vector128.Create((byte)0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00); + + Vector128 actual = RelaxedSimd.LaneSelectNative(left, right, mask); + Vector128 expected = Vector128.ConditionalSelect(mask, left, right); + + Assert.Equal(expected, actual); + } + + [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] + public static void SwizzleNativeInRangeMatchesVector128Shuffle() + { + Vector128 value = Vector128.Create((byte)10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160); + Vector128 indices = Vector128.Create((byte)15, 0, 14, 1, 13, 2, 12, 3, 11, 4, 10, 5, 9, 6, 8, 7); + + Vector128 actual = RelaxedSimd.SwizzleNative(value, indices); + Vector128 expected = Vector128.Shuffle(value, indices); + + Assert.Equal(expected, actual); + } +} diff --git a/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests_r.csproj b/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests_r.csproj new file mode 100644 index 00000000000000..07a855f245a73a --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests_r.csproj @@ -0,0 +1,12 @@ + + + true + Embedded + True + true + true + + + + + diff --git a/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests_ro.csproj b/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests_ro.csproj new file mode 100644 index 00000000000000..9035420319aa24 --- /dev/null +++ b/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests_ro.csproj @@ -0,0 +1,12 @@ + + + true + Embedded + + true + true + + + + + From 51589717fb321c9db9c783b9a876a177e46a639e Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 11:54:14 -0500 Subject: [PATCH 25/31] [wasm] Remove RelaxedSimd lowering test Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- .../TestCases/R2RTestSuites.cs | 98 -------------- .../TestCases/Webcil/WasmSimdModule.cs | 120 +----------------- .../TestCasesRunner/WasmR2RAssert.cs | 22 ---- 3 files changed, 3 insertions(+), 237 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs index ae61ea23cd1d06..468492c83c4dba 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/R2RTestSuites.cs @@ -218,104 +218,6 @@ static string Format(IReadOnlyList valTypes) => } } - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWasmTarget))] - public void WasmRelaxedSimdModule() - { - var wasmRelaxedSimdModule = new CompiledAssembly - { - AssemblyName = nameof(WasmRelaxedSimdModule), - SourceResourceNames = ["Webcil/WasmSimdModule.cs"], - }; - - new R2RTestRunner(_output).Run(new R2RTestCase( - nameof(WasmRelaxedSimdModule), - [ - new(nameof(WasmRelaxedSimdModule), [new CrossgenAssembly(wasmRelaxedSimdModule)]) - { - OutputFileExtension = ".wasm", - AdditionalArgs = - { - "--instruction-set", - "relaxed-simd", - }, - Validate = Validate, - }, - ])); - - static void Validate(ReadyToRunReader reader) - { - var webcilReader = Assert.IsType(reader.CompositeReader); - List methods = R2RAssert.GetAllMethods(reader); - - foreach ((string name, uint opcode) in - new[] - { - ("RelaxedSwizzleNative", 0x100u), - ("RelaxedConvertF32ToInt32Native", 0x101u), - ("RelaxedConvertF32ToUInt32Native", 0x102u), - ("RelaxedConvertF64ToInt32Native", 0x103u), - ("RelaxedConvertF64ToUInt32Native", 0x104u), - ("RelaxedMultiplyAddF32", 0x105u), - ("RelaxedMultiplyAddNegatedF32", 0x106u), - ("RelaxedMultiplyAddF64", 0x107u), - ("RelaxedMultiplyAddNegatedF64", 0x108u), - ("RelaxedLaneSelectI8Native", 0x109u), - ("RelaxedLaneSelectI16Native", 0x10Au), - ("RelaxedLaneSelectI32Native", 0x10Bu), - ("RelaxedLaneSelectI64Native", 0x10Cu), - ("RelaxedMinF32Native", 0x10Du), - ("RelaxedMaxF32Native", 0x10Eu), - ("RelaxedMinF64Native", 0x10Fu), - ("RelaxedMaxF64Native", 0x110u), - ("RelaxedMultiplyRoundedQ15Native", 0x111u), - ("RelaxedDotProductNative", 0x112u), - ("RelaxedDotProductAddNative", 0x113u), - }) - { - ReadyToRunMethod method = Assert.Single( - methods, m => m.SignatureString.Contains($".{name}(", StringComparison.Ordinal)); - WebcilImageReader.WasmFunctionInfo body = ResolveWasmBody(reader, webcilReader, method); - - Assert.True( - WasmR2RAssert.WasmFunctionContainsPrefixedOpcode(body, 0xFD, opcode), - $"'{name}' did not contain relaxed SIMD opcode 0xFD 0x{opcode:X}."); - } - } - } - - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWasmTarget))] - public void WasmRelaxedSimdDisabledModule() - { - var wasmRelaxedSimdDisabledModule = new CompiledAssembly - { - AssemblyName = nameof(WasmRelaxedSimdDisabledModule), - SourceResourceNames = ["Webcil/WasmSimdModule.cs"], - }; - - new R2RTestRunner(_output).Run(new R2RTestCase( - nameof(WasmRelaxedSimdDisabledModule), - [ - new(nameof(WasmRelaxedSimdDisabledModule), [new CrossgenAssembly(wasmRelaxedSimdDisabledModule)]) - { - OutputFileExtension = ".wasm", - Validate = Validate, - }, - ])); - - static void Validate(ReadyToRunReader reader) - { - var webcilReader = Assert.IsType(reader.CompositeReader); - List methods = R2RAssert.GetAllMethods(reader); - ReadyToRunMethod method = Assert.Single( - methods, m => m.SignatureString.Contains(".RelaxedSwizzleNativeIfSupported(", StringComparison.Ordinal)); - WebcilImageReader.WasmFunctionInfo body = ResolveWasmBody(reader, webcilReader, method); - - Assert.False( - WasmR2RAssert.WasmFunctionContainsPrefixedOpcode(body, 0xFD, 0x100), - "The default Wasm instruction-set baseline emitted a relaxed SIMD opcode."); - } - } - [ConditionalFact(typeof(TestPaths), nameof(TestPaths.IsWasmTarget))] public void WasmCompositeModule() { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs index b12002a32db439..77518d9ee5336d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCases/Webcil/WasmSimdModule.cs @@ -4,12 +4,12 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.Wasm; namespace Webcil; -// The initial helpers exercise the wasm v128 calling convention without relying on SIMD -// arithmetic intrinsics. The RelaxedSimd helpers exercise exact instruction lowering. +// Exercises the wasm v128 calling convention (SIMD passed/returned/stored by value) +// without relying on any SIMD arithmetic intrinsics, so only the ABI/materialization +// paths are covered. public static class WasmSimdModule { [MethodImpl(MethodImplOptions.NoInlining)] @@ -50,120 +50,6 @@ public static Vector CallEchoVectorT(Vector value) return EchoVectorT(value); } - public static Vector128 RelaxedSwizzleNative(Vector128 value, Vector128 indices) - { - return RelaxedSimd.SwizzleNative(value, indices); - } - - public static Vector128 RelaxedConvertF32ToInt32Native(Vector128 value) - { - return RelaxedSimd.ConvertToInt32Native(value); - } - - public static Vector128 RelaxedConvertF32ToUInt32Native(Vector128 value) - { - return RelaxedSimd.ConvertToUInt32Native(value); - } - - public static Vector128 RelaxedConvertF64ToInt32Native(Vector128 value) - { - return RelaxedSimd.ConvertToInt32Native(value); - } - - public static Vector128 RelaxedConvertF64ToUInt32Native(Vector128 value) - { - return RelaxedSimd.ConvertToUInt32Native(value); - } - - public static Vector128 RelaxedMultiplyAddF32( - Vector128 left, Vector128 right, Vector128 addend) - { - return RelaxedSimd.MultiplyAddEstimate(left, right, addend); - } - - public static Vector128 RelaxedMultiplyAddNegatedF32( - Vector128 left, Vector128 right, Vector128 addend) - { - return RelaxedSimd.MultiplyAddNegatedEstimate(left, right, addend); - } - - public static Vector128 RelaxedMultiplyAddF64( - Vector128 left, Vector128 right, Vector128 addend) - { - return RelaxedSimd.MultiplyAddEstimate(left, right, addend); - } - - public static Vector128 RelaxedMultiplyAddNegatedF64( - Vector128 left, Vector128 right, Vector128 addend) - { - return RelaxedSimd.MultiplyAddNegatedEstimate(left, right, addend); - } - - public static Vector128 RelaxedLaneSelectI8Native( - Vector128 left, Vector128 right, Vector128 mask) - { - return RelaxedSimd.LaneSelectNative(left, right, mask); - } - - public static Vector128 RelaxedLaneSelectI16Native( - Vector128 left, Vector128 right, Vector128 mask) - { - return RelaxedSimd.LaneSelectNative(left, right, mask); - } - - public static Vector128 RelaxedLaneSelectI32Native( - Vector128 left, Vector128 right, Vector128 mask) - { - return RelaxedSimd.LaneSelectNative(left, right, mask); - } - - public static Vector128 RelaxedLaneSelectI64Native( - Vector128 left, Vector128 right, Vector128 mask) - { - return RelaxedSimd.LaneSelectNative(left, right, mask); - } - - public static Vector128 RelaxedMinF32Native(Vector128 left, Vector128 right) - { - return RelaxedSimd.MinNative(left, right); - } - - public static Vector128 RelaxedMaxF32Native(Vector128 left, Vector128 right) - { - return RelaxedSimd.MaxNative(left, right); - } - - public static Vector128 RelaxedMinF64Native(Vector128 left, Vector128 right) - { - return RelaxedSimd.MinNative(left, right); - } - - public static Vector128 RelaxedMaxF64Native(Vector128 left, Vector128 right) - { - return RelaxedSimd.MaxNative(left, right); - } - - public static Vector128 RelaxedMultiplyRoundedQ15Native(Vector128 left, Vector128 right) - { - return RelaxedSimd.MultiplyRoundedQ15Native(left, right); - } - - public static Vector128 RelaxedDotProductNative(Vector128 left, Vector128 right) - { - return RelaxedSimd.DotProductNative(left, right); - } - - public static Vector128 RelaxedDotProductAddNative( - Vector128 left, Vector128 right, Vector128 accumulator) - { - return RelaxedSimd.DotProductAddNative(left, right, accumulator); - } - - public static Vector128 RelaxedSwizzleNativeIfSupported(Vector128 value, Vector128 indices) - { - return RelaxedSimd.IsSupported ? RelaxedSimd.SwizzleNative(value, indices) : value; - } - // A single-field struct wrapping a v128 is itself passed/returned as a v128, matching emscripten. public struct WrappedVector128 { diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/WasmR2RAssert.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/WasmR2RAssert.cs index dba66224938f75..5f913584862f72 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/WasmR2RAssert.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun.Tests/TestCasesRunner/WasmR2RAssert.cs @@ -50,28 +50,6 @@ public static bool WasmImageContainsWellKnownGlobalGet(WebcilImageReader reader, return false; } - public static bool WasmFunctionContainsPrefixedOpcode( - WebcilImageReader.WasmFunctionInfo body, byte prefix, uint subOpcode) - { - Span pattern = stackalloc byte[6]; - pattern[0] = prefix; - int patternLength = 1; - - do - { - byte encodedByte = (byte)(subOpcode & 0x7F); - subOpcode >>= 7; - if (subOpcode != 0) - encodedByte |= 0x80; - - pattern[patternLength++] = encodedByte; - } - while (subOpcode != 0); - - ReadOnlySpan instructions = body.Image.AsSpan().Slice(body.InstructionOffset, body.InstructionLength); - return instructions.IndexOf(pattern.Slice(0, patternLength)) >= 0; - } - /// /// Returns true if the default Webcil imports and defined section entries occupy the expected /// indices in their respective WASM external-kind index spaces. From 2f536e99b89cf0152711ea1ed8b6e106741b4252 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 12:13:38 -0500 Subject: [PATCH 26/31] [wasm] Select relaxed SIMD prelinked runtimes Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- src/mono/browser/browser.proj | 4 +++- src/mono/wasi/wasi.proj | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mono/browser/browser.proj b/src/mono/browser/browser.proj index 919cb61895ed5e..d738ba20d3e3dc 100644 --- a/src/mono/browser/browser.proj +++ b/src/mono/browser/browser.proj @@ -331,6 +331,7 @@ $(CMakeConfigurationLinkFlags) -s EXPORT_ES6=1 -lexports.js $(CMakeConfigurationLinkFlags) -msimd128 + $(CMakeConfigurationLinkFlags) -mrelaxed-simd $(CMakeConfigurationLinkFlags) -Wno-pthreads-mem-growth $(CMakeConfigurationLinkFlags) --emit-symbol-map $(CMakeConfigurationLinkFlags) -fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0 @@ -347,7 +348,8 @@ $(CMakeBuildRuntimeConfigureCmd) -DMONO_OBJ_INCLUDES="$(MonoObjDir.TrimEnd('\/').Replace('\','/'))" $(CMakeBuildRuntimeConfigureCmd) -DMONO_ARTIFACTS_DIR="$(MonoArtifactsPath.TrimEnd('\/').Replace('\','/'))" $(CMakeBuildRuntimeConfigureCmd) -DNATIVE_BIN_DIR="$(NativeBinDir.TrimEnd('\/').Replace('\','/'))" - $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128" -DCONFIGURATION_INTERPSIMDTABLES_LIB="simd" + $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128;-mrelaxed-simd" -DCONFIGURATION_INTERPSIMDTABLES_LIB="relaxed-simd" + $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128" -DCONFIGURATION_INTERPSIMDTABLES_LIB="simd" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_INTERPSIMDTABLES_LIB="nosimd" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_EH_LIB="eh-wasm" diff --git a/src/mono/wasi/wasi.proj b/src/mono/wasi/wasi.proj index 95609245e1f96e..e0485628a3ee8e 100644 --- a/src/mono/wasi/wasi.proj +++ b/src/mono/wasi/wasi.proj @@ -156,7 +156,7 @@ $(CMakeBuildRuntimeConfigureCmd) -DMONO_OBJ_INCLUDES="$(MonoObjDir.TrimEnd('\/'))" $(CMakeBuildRuntimeConfigureCmd) -DMONO_ARTIFACTS_DIR="$(MonoArtifactsPath.TrimEnd('\/'))" $(CMakeBuildRuntimeConfigureCmd) -DNATIVE_BIN_DIR="$(NativeBinDir.TrimEnd('\/'))" - $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128 -mrelaxed-simd" -DCONFIGURATION_INTERPSIMDTABLES_LIB="relaxed-simd" + $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128;-mrelaxed-simd" -DCONFIGURATION_INTERPSIMDTABLES_LIB="relaxed-simd" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_COMPILE_OPTIONS="-msimd128" -DCONFIGURATION_INTERPSIMDTABLES_LIB="simd" $(CMakeBuildRuntimeConfigureCmd) -DCONFIGURATION_INTERPSIMDTABLES_LIB="nosimd" $(CMakeBuildRuntimeConfigureCmd) -DDISABLE_THREADS=0 From 647195bcc086c53f31e5758bf86882aa8db0fcf9 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 12:32:18 -0500 Subject: [PATCH 27/31] [wasm] Align jiterpreter relaxed SIMD opcodes Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- .../browser/runtime/jiterpreter-opcodes.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/mono/browser/runtime/jiterpreter-opcodes.ts b/src/mono/browser/runtime/jiterpreter-opcodes.ts index 27303091de9d07..17a776bb1c7db3 100644 --- a/src/mono/browser/runtime/jiterpreter-opcodes.ts +++ b/src/mono/browser/runtime/jiterpreter-opcodes.ts @@ -483,7 +483,7 @@ export const enum WasmSimdOpcode { i64x2_extmul_low_i32x4_u = 0xde, i64x2_extmul_high_i32x4_u = 0xdf, i16x8_q15mulr_sat_s = 0x82, - i16x8_relaxed_q15mulr_sat_s = 0x111, + i16x8_relaxed_q15mulr_s = 0x111, v128_any_true = 0x53, v128_load8_lane = 0x54, v128_load16_lane = 0x55, @@ -504,8 +504,8 @@ export const enum WasmSimdOpcode { f64x2_convert_low_i32x4_u = 0xff, i32x4_trunc_sat_f64x2_s_zero = 0xfc, i32x4_trunc_sat_f64x2_u_zero = 0xfd, - i32x4_relaxed_trunc_f64_s_zero = 0x103, - i32x4_relaxed_trunc_f64_u_zero = 0x104, + i32x4_relaxed_trunc_f64x2_s_zero = 0x103, + i32x4_relaxed_trunc_f64x2_u_zero = 0x104, f32x4_demote_f64x2_zero = 0x5e, f64x2_promote_low_f32x4 = 0x5f, i8x16_popcnt = 0x62, @@ -514,20 +514,19 @@ export const enum WasmSimdOpcode { i32x4_extadd_pairwise_i16x8_s = 0x7e, i32x4_extadd_pairwise_i16x8_u = 0x7f, f32x4_relaxed_madd = 0x105, - f32x4_relaxed_mnadd = 0x106, + f32x4_relaxed_nmadd = 0x106, f64x2_relaxed_madd = 0x107, - f64x2_relaxed_mnadd = 0x108, - i8x16_relaxed_lane_select = 0x109, - i16x8_relaxed_lane_select = 0x10a, - i32x4_relaxed_lane_select = 0x10b, - i64x2_relaxed_lane_select = 0x10c, + f64x2_relaxed_nmadd = 0x108, + i8x16_relaxed_laneselect = 0x109, + i16x8_relaxed_laneselect = 0x10a, + i32x4_relaxed_laneselect = 0x10b, + i64x2_relaxed_laneselect = 0x10c, f32x4_relaxed_min = 0x10d, f32x4_relaxed_max = 0x10e, f64x2_relaxed_min = 0x10f, f64x2_relaxed_max = 0x110, i16x8_relaxed_dot_i8x16_i7x16_s = 0x112, - i16x8_relaxed_dot_i8x16_i7x16_u = 0x113, - i32x4_relaxed_dot_i8x16_i7x16_s = 0x114, + i32x4_relaxed_dot_i8x16_i7x16_add_s = 0x113, } export const enum WasmAtomicOpcode { From f4975f4b1c8d743a5866e687547c390fc92b1df8 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 22:01:07 -0500 Subject: [PATCH 28/31] [wasm] Address RelaxedSimd review cleanup Make the Wasm CoreLib Crossgen2 capability unconditional while keeping per-app RelaxedSimd compilation opt-in. Remove redundant trimming annotations and unsafe modifiers from the tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- src/coreclr/crossgen-corelib.proj | 2 +- .../tests/Wasm/RelaxedSimdTests.cs | 16 +++++++--------- .../Wasm/RelaxedSimd/RelaxedSimdTests.cs | 4 +--- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/coreclr/crossgen-corelib.proj b/src/coreclr/crossgen-corelib.proj index cfdaac8f5b2c7c..89b2db56f9e988 100644 --- a/src/coreclr/crossgen-corelib.proj +++ b/src/coreclr/crossgen-corelib.proj @@ -157,7 +157,7 @@ $(CrossGenDllCmd) --targetarch:$(TargetArchitecture) $(CrossGenDllCmd) --obj-format:$(PublishReadyToRunContainerFormat) $(CrossGenDllCmd) --codegenopt:JitWasmNyiToR2RUnsupported=1 - $(CrossGenDllCmd) --instruction-set:relaxed-simd + $(CrossGenDllCmd) --instruction-set:relaxed-simd $(CrossGenDllCmd) "--opt-cross-module:*" $(CrossGenDllCmd) --composite diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs index 18a34f60b77d28..ec39ca965fa52a 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/RelaxedSimdTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.Wasm; @@ -15,8 +14,7 @@ public sealed class RelaxedSimdTests public static bool IsNotSupported => !RelaxedSimd.IsSupported; [Fact] - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(RelaxedSimd))] - public unsafe void RelaxedSimdIsSupportedReflects() + public void RelaxedSimdIsSupportedReflects() { MethodInfo methodInfo = typeof(RelaxedSimd).GetMethod("get_IsSupported"); Assert.NotNull(methodInfo); @@ -31,7 +29,7 @@ public void UnsupportedMethodThrowsPlatformNotSupportedException() } [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] - public unsafe void ConvertToIntegerNativeInRangeMatchesExpected() + public void ConvertToIntegerNativeInRangeMatchesExpected() { Assert.Equal( Vector128.Create(1, -2, 3, -4), @@ -48,7 +46,7 @@ public unsafe void ConvertToIntegerNativeInRangeMatchesExpected() } [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] - public unsafe void DotProductNativeByteSByteMatchesScalar() + public void DotProductNativeByteSByteMatchesScalar() { // Per the finished spec, `a` is signed and `b` is unsigned-7-bit. When every lane // of `b` is in [0, 127] every implementation must match a straightforward @@ -66,7 +64,7 @@ public unsafe void DotProductNativeByteSByteMatchesScalar() } [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] - public unsafe void DotProductAddNativeByteSByteMatchesScalar() + public void DotProductAddNativeByteSByteMatchesScalar() { var s = Vector128.Create((sbyte)-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16); var u = Vector128.Create((byte)2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3); @@ -84,7 +82,7 @@ public unsafe void DotProductAddNativeByteSByteMatchesScalar() } [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] - public unsafe void MultiplyAddFloatMatchesScalarApproximately() + public void MultiplyAddFloatMatchesScalarApproximately() { // Relaxed FMA may or may not round the intermediate product; verify the result is // within a small relative tolerance of the unfused result. float.Epsilon is a @@ -107,7 +105,7 @@ public unsafe void MultiplyAddFloatMatchesScalarApproximately() } [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] - public unsafe void LaneSelectNativeAllOnesAllZerosBehavesLikeConditionalSelect() + public void LaneSelectNativeAllOnesAllZerosBehavesLikeConditionalSelect() { // For mask lanes that are all-ones or all-zeros the relaxed lane select must match // the deterministic semantics. @@ -123,7 +121,7 @@ public unsafe void LaneSelectNativeAllOnesAllZerosBehavesLikeConditionalSelect() } [ConditionalFact(typeof(RelaxedSimd), nameof(RelaxedSimd.IsSupported))] - public unsafe void SwizzleNativeInRangeMatchesVector128Shuffle() + public void SwizzleNativeInRangeMatchesVector128Shuffle() { // For index lanes in [0, 16) the relaxed swizzle must agree with Vector128.Shuffle. var v = Vector128.Create((byte)10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160); diff --git a/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests.cs b/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests.cs index c82650d49acc41..33089bf5596847 100644 --- a/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests.cs +++ b/src/tests/JIT/HardwareIntrinsics/Wasm/RelaxedSimd/RelaxedSimdTests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.Wasm; @@ -13,10 +12,9 @@ public sealed class RelaxedSimdTests public static bool IsNotSupported => !RelaxedSimd.IsSupported; [Fact] - [DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties, typeof(RelaxedSimd))] public static void RelaxedSimdIsSupportedReflects() { - MethodInfo? methodInfo = typeof(RelaxedSimd).GetProperty(nameof(RelaxedSimd.IsSupported))?.GetGetMethod(); + MethodInfo? methodInfo = typeof(RelaxedSimd).GetMethod("get_IsSupported"); Assert.NotNull(methodInfo); Assert.Equal(RelaxedSimd.IsSupported, methodInfo.Invoke(null, null)); } From 40621ad6df35c54e34dcf58a99fe1dd76e57ba99 Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 22:49:13 -0500 Subject: [PATCH 29/31] [wasm] Keep runtime-pack CoreLib at baseline SIMD Crossgen2 already understands RelaxedSimd. Only opt-in app publishing should target the relaxed SIMD instruction set; the shared runtime-pack CoreLib must remain baseline-compatible. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- src/coreclr/crossgen-corelib.proj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/crossgen-corelib.proj b/src/coreclr/crossgen-corelib.proj index 89b2db56f9e988..ebe09a78aa9dad 100644 --- a/src/coreclr/crossgen-corelib.proj +++ b/src/coreclr/crossgen-corelib.proj @@ -157,7 +157,6 @@ $(CrossGenDllCmd) --targetarch:$(TargetArchitecture) $(CrossGenDllCmd) --obj-format:$(PublishReadyToRunContainerFormat) $(CrossGenDllCmd) --codegenopt:JitWasmNyiToR2RUnsupported=1 - $(CrossGenDllCmd) --instruction-set:relaxed-simd $(CrossGenDllCmd) "--opt-cross-module:*" $(CrossGenDllCmd) --composite From 32d19ff54491802268e83e69aae201f8208aa7ff Mon Sep 17 00:00:00 2001 From: Larry Ewing Date: Sat, 12 Sep 2026 23:13:43 -0500 Subject: [PATCH 30/31] [wasm] Configure RelaxedSimd in the shipped SDK Mirror the in-tree CoreCLR browser app wiring in Microsoft.NET.Sdk.WebAssembly.Pack so opt-in publishes pass the RelaxedSimd instruction set to Crossgen2 and advertise support through runtime configuration. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0e443132-2985-4b7b-8c59-28498d381fa0 --- .../Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets index 70d3fc52add4e7..5eb622e986af4b 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.CoreCLR.targets @@ -28,8 +28,15 @@ Copyright (c) .NET Foundation. All rights reserved. $(PublishReadyToRunCrossgen2ExtraArgs);--opt-cross-module:* $(PublishReadyToRunCrossgen2ExtraArgs);--codegenopt:JitWasmNyiToR2RUnsupported=1;--codegenopt:JitWasmSimdNyiToR2RUnsupported=1 + $(PublishReadyToRunCrossgen2ExtraArgs);--instruction-set:relaxed-simd + + + +