[mono][wasm] Add Truncate/CopySign/ScaleB intrinsics + narrow #103347 test exemption#129727
Merged
Conversation
…and jiterpreter
A few small WASM-relevant gaps in Mono's interpreter and jiterpreter
math intrinsic recognition.
**Truncate / CopySign**: `Math.Truncate(double)` / `MathF.Truncate(float)`
and `Math.CopySign(double, double)` / `MathF.CopySign(float, float)` were
not recognized at the IL -> MINT_* lowering step (transform.c), so the
interpreter walked through the BCL C# implementations on every call.
Adds:
* `MINT_TRUNC` / `MINT_TRUNCF` and `MINT_COPYSIGN` / `MINT_COPYSIGNF`
in `mintops.def`, in matched D/F-block positions so the existing
`(MINT_ASINF - MINT_ASIN)` shift in transform.c continues to map the
double opcodes to their float counterparts correctly.
* `MATH_UNOP(trunc) / MATH_UNOPF(truncf)` and
`MATH_BINOP(copysign) / MATH_BINOPF(copysignf)` dispatch in
`interp.c`.
* Recognition in the existing Math/MathF block in `transform.c`
(`Truncate` joins the `T...` unop dispatch alongside `Tan`/`Tanh`;
`CopySign` joins the binary block alongside `Min`/`Max`/`Pow`/`Atan2`).
In the jiterpreter all four new opcodes lower directly to native WASM
instructions via the `mathIntrinsicTable` -- `f64.trunc`/`f32.trunc`
and `f64.copysign`/`f32.copysign`. No libm import is needed.
**ScaleB**: `Math.ScaleB(double, int)` / `MathF.ScaleB(float, int)`
already had `MINT_SCALEB` / `MINT_SCALEBF` opcodes and interp dispatch
(via libm `scalbn` / `scalbnf`), but the jiterpreter had no handler --
encountering the opcode during tracing forced a trace bailout and fell
back to the interpreter for the rest of the trace. Because the
signature is `(float, int) -> float` (not uniform float-only), it
doesn't fit `mathIntrinsicTable`'s shape, so this mirrors the existing
`MINT_FMA` special case in `jiterpreter-trace-generator.ts` -- emit a
direct `callImport("scalbn"|"scalbnf")` and declare matching imports +
type signatures in `jiterpreter.ts`. No more bailout for `Math.ScaleB`
on hot WASM traces.
Stacked on top of:
* PR dotnet#129593 ([mono][llvm] Use llvm.minimum/maximum for scalar
Math.Min/Max float ops) -- this PR's interp/jiterpreter changes are
independent of the LLVM-AOT changes there, but stacking keeps the
Mono math-intrinsic story landing in one logical sequence.
* PR ?????? ([mono][llvm] Recognize MathF.Abs/Log and Single/Double.
MinNumber/MaxNumber as intrinsics) -- same reasoning.
Validation: builds clean on osx-arm64 (`./build.sh mono+libs -os osx
-arch arm64 -c Release`). Local browser-wasm verification was blocked
by an env-side `npm install` failure (Python 3.10+ requirement for
emscripten on this host); CI's `runtime-wasm` leg will exercise both
the interpreter and jiterpreter changes against the BCL Math /
MathF tests on browser-wasm.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PR dotnet#129593 added `[ActiveIssue("dotnet#103347", TestPlatforms.Browser)]` on `BFloat16Tests.ExplicitConversion_From{Single,Double}` to mirror the existing exemption on `HalfTests.ExplicitConversion_FromSingle`. Both were applied at the `[Theory]` level, disabling the entire parameterized test on Browser including the ~47 non-NaN rows covering ULP rounding, subnormals, sign handling, and overflow. Only the ~4 NaN rows per theory actually hit the WASM NaN-payload canonicalization issue tracked in dotnet#103347. Filter the NaN rows out of the `..._TestData` member-data sources when `PlatformDetection.IsWasm` is true (covers both Browser and WASI, which share the underlying `f32.min` / `f64.min` / `f32.add` / `f64.add` NaN-canonicalization behavior per the WebAssembly spec), and drop the `[ActiveIssue]` from the test methods. The non-NaN rows then run on every platform; on WASM the test summary simply shows fewer rows (no skip-noise from the ActiveIssue mechanism). Validation: built System.Runtime.Tests for net11.0-unix and ran `--filter "FullyQualifiedName~ExplicitConversion_FromSingle| ExplicitConversion_FromDouble"` on host osx-arm64. Result: `Passed: 204, Failed: 0, Skipped: 0` -- same row count as before on non-WASM platforms. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
|
Tagging subscribers to this area: @vitek-karas, @BrzVlad, @kotlarmilos |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends Mono’s WASM interpreter/jiterpreter handling of several Math/MathF intrinsics (Truncate, CopySign, ScaleB) and refines WASM-specific test behavior for Half/BFloat16 explicit conversion theories by skipping only NaN rows on WASM rather than skipping the full theories.
Changes:
- Add
MINT_TRUNC*andMINT_COPYSIGN*opcodes, wire up intrinsic recognition intransform.c, and implement interpreter dispatch ininterp.c. - Add jiterpreter support for
MINT_SCALEB*by importingscalbn/scalbnfand emitting the appropriate calls during trace generation. - Narrow WASM test exemptions by filtering NaN rows in
HalfTests/BFloat16Testsmember-data and removing[ActiveIssue]attributes.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/mono/mono/mini/interp/transform.c | Recognizes Truncate/CopySign as interpreter intrinsics (Math/MathF) and maps float ↔ double opcodes via the existing shift. |
| src/mono/mono/mini/interp/mintops.def | Introduces new MINT_TRUNC* and MINT_COPYSIGN* opcode definitions in the math intrinsic blocks. |
| src/mono/mono/mini/interp/interp.c | Adds interpreter implementations for MINT_TRUNC* and MINT_COPYSIGN*. |
| src/mono/browser/runtime/jiterpreter.ts | Adds scalbn/scalbnf imports and defines their wasm function types for trace modules. |
| src/mono/browser/runtime/jiterpreter-trace-generator.ts | Adds codegen support for MINT_SCALEB* in trace generation by calling imported scalbn/scalbnf. |
| src/mono/browser/runtime/jiterpreter-tables.ts | Adds MINT_TRUNC* and MINT_COPYSIGN* entries to mathIntrinsicTable to lower to WASM trunc/copysign. |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Numerics/BFloat16Tests.cs | Filters NaN rows on WASM for explicit conversion member-data and removes Browser-only [ActiveIssue]. |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/HalfTests.cs | Filters NaN rows on WASM for explicit conversion member-data and removes Browser-only [ActiveIssue]. |
This was referenced Jun 23, 2026
Three issues introduced by the new MINT_TRUNC*/MINT_COPYSIGN* opcodes and the SCALEB jiterpreter handler, caught by browser-wasm LibraryTests on dotnet#129727 (394 work-item failures, root cause `MONO_WASM: raw cwrap scalbn not found` at jiterpreter init): * `src/mono/browser/browser.proj`: add `_scalbn` and `_scalbnf` to the emscripten `EmccExportedFunction` list, alongside the existing `_fma`/`_fmaf`. Without these, the WASM runtime doesn't keep the libm symbols alive, so the jiterpreter `getRawCwrap("scalbn")` / `getRawCwrap("scalbnf")` calls at init throw and abort *every* WASM test work item -- not just ones that hit `Math.ScaleB`. * `src/mono/mono/mini/interp/jiterpreter-opcode-values.h`: extend the math-intrinsic OPRANGE upper bound from `MINT_MAXF` to `MINT_COPYSIGNF`. The new MINT_TRUNC/MINT_TRUNCF/MINT_COPYSIGN/ MINT_COPYSIGNF opcodes sit after MINT_MAXF in `mintops.def` and would otherwise be classified outside the math-intrinsic value bucket on the C side. * `src/mono/browser/runtime/jiterpreter-trace-generator.ts`: extend the matching TS range check (`opcode <= MINT_MAXF`) similarly to `MINT_COPYSIGNF`. Without this, the trace generator falls through past the math-intrinsic dispatch for the new opcodes and aborts the trace -- defeating the entire purpose of adding them to `mathIntrinsicTable` (which the existing handler would otherwise pick them up from cleanly via the native WASM `f64.trunc` / `f64.copysign` lowerings). Reviewer flag: Copilot review on dotnet#129727 identified the trace-generator range check; the OPRANGE update and the missing scalbn/scalbnf exports fell out of the same root cause when triaging the CI failures it predicted. Build verification: `./build.sh mono+libs -os osx -arch arm64 -c Release` clean (0 errors, 0 warnings). Local browser-wasm verification still blocked by the prior emscripten/npm install issue on this host; CI's browser-wasm LibraryTests legs (the ones that went red on the previous push) will validate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Addresses @jkotas's review concern on dotnet#129727: the previous filter gated on `PlatformDetection.IsWasm` alone, which would also apply to future CoreCLR-on-WASM configurations even though the underlying NaN-canonicalization issue is specific to Mono's lowering of `float.Min` / `float.Max` to WASM `f32.min` / `f32.max` (and `float.IsNaN` branches through `f32.add`). Other WASM runtimes won't share this lowering and shouldn't be pre-emptively excluded from the bit-strict NaN rows. Add a `PlatformDetection.IsMonoRuntime &&` guard to the filter predicate in all three sites (HalfTests.ExplicitConversion_FromSingle, BFloat16Tests.ExplicitConversion_FromSingle, BFloat16Tests. ExplicitConversion_FromDouble) and expand the inline comments to make the Mono-specific scope explicit. Validation: `dotnet test --filter "FullyQualifiedName~ ExplicitConversion_FromSingle|FullyQualifiedName~ ExplicitConversion_FromDouble"` on host osx-arm64 CoreCLR -- Passed: 204, Failed: 0, Skipped: 0 (IsMonoRuntime is false, so the filter doesn't activate and the row count matches the pre-PR baseline). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@tannergooding noted on dotnet#129727 that the previous comment was misleading: the WebAssembly spec doesn't *require* NaN-payload canonicalization on `f32.min` / `f64.min` / etc -- it merely permits it. Arch-native targets (Arm64, xArch) don't canonicalize; they at most strip the signaling bit per IEEE 754. What's actually canonicalizing in our CI environment is the V8 engine used by the Helix WASM queues. Rewrite the inline comments in all three filter sites to accurately describe the spec-permitted-but-not-required canonicalization and attribute the observed behavior to V8 rather than the spec. Also add a note that the filter can be removed if either (a) the software conversion path is rewritten to avoid the canonicalizing ops, or (b) host engines start preserving NaN payloads. Comment-only change; no behavioral impact. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pavelsavara
approved these changes
Jun 23, 2026
This was referenced Jun 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #129593 (
Math.Min/MaxLLVM lowering) and #129699 (MathF.Abs/Log+Single/Double.MinNumber/MaxNumberintrinsic gaps). Two related WASM-targeted improvements:1.
Math.Truncate/Math.CopySign/Math.ScaleBfor interpreter + jiterpreter (commit 1)A few BCL math entry points have direct WASM hardware support (
f64.trunc,f64.copysign) or already had interp opcodes (scalbn/scalbnf) but weren't wired all the way through:Math.Truncate(double)/MathF.Truncate(float): not recognized at the IL→MINT_* lowering step intransform.c, so the interpreter walked the BCL C# implementations on every call. AddsMINT_TRUNC/MINT_TRUNCFopcodes in matched D/F-block positions (preserving the existing(MINT_ASINF - MINT_ASIN)shift intransform.c),MATH_UNOP(trunc)/MATH_UNOPF(truncf)dispatch ininterp.c, and recognition intransform.c. In the jiterpreter these lower directly to native WASMf64.trunc/f32.truncviamathIntrinsicTable— no libm import needed.Math.CopySign(double, double)/MathF.CopySign(float, float): same shape. NewMINT_COPYSIGN/MINT_COPYSIGNFopcodes +MATH_BINOP(copysign)dispatch + recognition. Jiterpreter lowers to nativef64.copysign/f32.copysign.Math.ScaleB(double, int)/MathF.ScaleB(float, int): already hadMINT_SCALEB/MINT_SCALEBFopcodes and interp dispatch (via libmscalbn/scalbnf), but the jiterpreter had no handler — encountering the opcode during tracing forced a bailout to the interpreter for the rest of the trace. The(float, int) -> floatsignature doesn't fit the uniform-float-onlymathIntrinsicTableshape, so this mirrors the existingMINT_FMAspecial case injiterpreter-trace-generator.ts: directcallImport("scalbn"/"scalbnf")plus matching imports and type signatures injiterpreter.ts. No more trace-abort onMath.ScaleBin hot WASM code.2. Refine #129593's
[ActiveIssue]scope on Half/BFloat16 conversion theories (commit 2)PR #129593 added
[ActiveIssue("#103347", TestPlatforms.Browser)]onBFloat16Tests.ExplicitConversion_From{Single,Double}to mirror the existing exemption onHalfTests.ExplicitConversion_FromSingle. Both were applied at the[Theory]level, disabling the entire parameterized test on Browser — including the ~47 non-NaN rows covering ULP rounding, subnormals, sign handling, and overflow. Only the ~4 NaN rows per theory actually hit the WASMf32.min/f64.min/f32.addNaN-payload canonicalization issue tracked in #103347.Filter the NaN rows out of the
..._TestDatamember-data sources whenPlatformDetection.IsWasmis true (covers both Browser and WASI, which share the underlying WASM-spec NaN canonicalization behavior), and drop the[ActiveIssue]markers. Non-NaN rows then run on every platform; on WASM the test summary simply shows fewer rows (no[ActiveIssue]skip-noise).Validation
./build.sh mono+libs -os osx -arch arm64 -c Releaseclean (0 errors, 0 warnings).System.Runtime.Testsfornet11.0-unixand ran--filter "FullyQualifiedName~ExplicitConversion_FromSingle|FullyQualifiedName~ExplicitConversion_FromDouble"on host osx-arm64:Passed: 204, Failed: 0, Skipped: 0, matching the row count from before the test-data filter on non-WASM.npm installfailure on this host (Python 3.10+ requirement). CI'sruntime-wasmlegs will exercise both the new interp/jiterp opcodes (against BCLMath/MathFtests) and the test-data filter (against the affectedHalfTests/BFloat16Testsrows) on browser-wasm.Risk / scope
MATH_UNOP/MATH_BINOPmacros and the proven jiterpretermathIntrinsicTablelowering — no new infrastructure.MINT_FMAspecial case exactly, just with the int second arg.PlatformDetection.IsWasm. Filter happens atMemberDataenumeration time, which runs in the target test process (i.e. the WASM runtime on WASM, the host on every other platform), so the row-set behaves consistently with execution.HalfTests.ExplicitConversion_FromSingle[ActiveIssue](had been in place since [browser] HalfTests.ExplicitConversion_FromSingle failing due to NaN != NaN #103347 was filed). [browser] HalfTests.ExplicitConversion_FromSingle failing due to NaN != NaN #103347 stays open — the underlying WASM payload-canonicalization is unchanged; we're just narrowing the scope of the test exemption.cc @vargaz @kotlarmilos @lambdageek @tannergooding @pavelsavara
Note
This pull request was produced by GitHub Copilot during the AI-assisted investigation that started with #129593 and continued through #129699.