Skip to content

[mono][wasm] Add Truncate/CopySign/ScaleB intrinsics + narrow #103347 test exemption#129727

Merged
lewing merged 5 commits into
dotnet:mainfrom
lewing:mono-wasm-math-intrinsics
Jun 23, 2026
Merged

[mono][wasm] Add Truncate/CopySign/ScaleB intrinsics + narrow #103347 test exemption#129727
lewing merged 5 commits into
dotnet:mainfrom
lewing:mono-wasm-math-intrinsics

Conversation

@lewing

@lewing lewing commented Jun 23, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #129593 (Math.Min/Max LLVM lowering) and #129699 (MathF.Abs/Log + Single/Double.MinNumber/MaxNumber intrinsic gaps). Two related WASM-targeted improvements:

1. Math.Truncate / Math.CopySign / Math.ScaleB for 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 in transform.c, so the interpreter walked the BCL C# implementations on every call. Adds MINT_TRUNC/MINT_TRUNCF opcodes in matched D/F-block positions (preserving the existing (MINT_ASINF - MINT_ASIN) shift in transform.c), MATH_UNOP(trunc) / MATH_UNOPF(truncf) dispatch in interp.c, and recognition in transform.c. In the jiterpreter these lower directly to native WASM f64.trunc / f32.trunc via mathIntrinsicTable — no libm import needed.

  • Math.CopySign(double, double) / MathF.CopySign(float, float): same shape. New MINT_COPYSIGN/MINT_COPYSIGNF opcodes + MATH_BINOP(copysign) dispatch + recognition. Jiterpreter lowers to native f64.copysign / f32.copysign.

  • 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 bailout to the interpreter for the rest of the trace. The (float, int) -> float signature doesn't fit the uniform-float-only mathIntrinsicTable shape, so this mirrors the existing MINT_FMA special case in jiterpreter-trace-generator.ts: direct callImport("scalbn"/"scalbnf") plus matching imports and type signatures in jiterpreter.ts. No more trace-abort on Math.ScaleB in hot WASM code.

2. Refine #129593's [ActiveIssue] scope on Half/BFloat16 conversion theories (commit 2)

PR #129593 added [ActiveIssue("#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 f32.min/f64.min/f32.add NaN-payload canonicalization issue tracked in #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 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 Release clean (0 errors, 0 warnings).
  • Built System.Runtime.Tests for net11.0-unix and 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.
  • Browser-WASM verification was attempted locally but blocked by an emscripten npm install failure on this host (Python 3.10+ requirement). CI's runtime-wasm legs will exercise both the new interp/jiterp opcodes (against BCL Math / MathF tests) and the test-data filter (against the affected HalfTests/BFloat16Tests rows) on browser-wasm.

Risk / scope

  • Interpreter additions reuse existing MATH_UNOP/MATH_BINOP macros and the proven jiterpreter mathIntrinsicTable lowering — no new infrastructure.
  • SCALEB jiterpreter path mirrors the existing MINT_FMA special case exactly, just with the int second arg.
  • Test-data filter is pure C# in two test files; the only platform-conditional is PlatformDetection.IsWasm. Filter happens at MemberData enumeration 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.
  • Drops the existing 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.

lewing and others added 2 commits June 22, 2026 22:11
…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>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @BrzVlad, @kotlarmilos
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR 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* and MINT_COPYSIGN* opcodes, wire up intrinsic recognition in transform.c, and implement interpreter dispatch in interp.c.
  • Add jiterpreter support for MINT_SCALEB* by importing scalbn/scalbnf and emitting the appropriate calls during trace generation.
  • Narrow WASM test exemptions by filtering NaN rows in HalfTests/BFloat16Tests member-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].

Comment thread src/mono/browser/runtime/jiterpreter-tables.ts
lewing and others added 3 commits June 23, 2026 09:46
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>
Copilot AI review requested due to automatic review settings June 23, 2026 14:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants