Skip to content

fix(mir-importer): lower libm float ops to libdevice intrinsics - #142

Merged
nihalpasham merged 7 commits into
NVIDIA:mainfrom
haixuanTao:fix/libm-float-lowering
Jun 12, 2026
Merged

nihalpasham merged 7 commits into
NVIDIA:mainfrom
haixuanTao:fix/libm-float-lowering

Conversation

@haixuanTao

@haixuanTao haixuanTao commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

When auto-porting existing rust-gpu / SPIR-V shader crates to cuda-oxide, their glam dependency (with nostd-libm) lowers float ops to libm::math::* (sqrtf, sinf,cosf, expf, powf, floor, ...). Intercept these at the call site and map them to the libdevice intrinsics (hardware math), and tell the collector NOT to translate the libm bodies (otherwise -Zalways-encode-mir drags in their generic software implementations and trips importer gaps).

Adds from_libm_path (final-segment match -> intrinsic) and an emit_sincos handler for libm::sincos{f} (returns (sin, cos), so no single scalar intrinsic fits — emits a sinf/cosf pair packed into the destination tuple).

glam (with `nostd-libm`) lowers float ops to `libm::math::*` (`sqrtf`, `sinf`,
`cosf`, `expf`, `powf`, `floor`, ...). Intercept these at the call site and map
them to the libdevice intrinsics (hardware math), and tell the collector NOT to
translate the `libm` bodies (otherwise `-Zalways-encode-mir` drags in their
generic software implementations and trips importer gaps).

Adds `from_libm_path` (final-segment match -> intrinsic) and an `emit_sincos`
handler for `libm::sincos{f}` (returns `(sin, cos)`, so no single scalar intrinsic
fits — emits a `sinf`/`cosf` pair packed into the destination tuple).
@nihalpasham nihalpasham added bug Something isn't working intrinsics Device intrinsics and libdevice math mappings codegen Device code-generation pipeline (Rust MIR to IR to PTX) labels Jun 10, 2026
Resolve add-add conflict in mir-importer terminator dispatch: main
added the bigint intrinsic dispatch at the same spot where the PR
added the libm sincos dispatch. Keep both, bigint first to match
main's ordering.
The libm match guard accepted any path containing the substring
"libm", so a user function that shares a libm function name (for
example an `expf` helper in a `libm_compat` module, or anything in a
crate whose name mentions libm) was silently rerouted to the
`__nv_expf` libdevice call, replacing the user's body with hardware
math: a miscompile, proven on IR during review.

Anchor both `from_libm_path` and `libm_sincos_is_f64` on the first
path segment being exactly `libm`, and pin the adversarial cases in
unit tests.
These libm entry points already have lowering variants on the
core-intrinsic path, so the table extension is free: fmax{f}/fmin{f}
reuse the _nsz maxNum/minNum lowering that backs f32::max and
f32::min, and rint{f}/roundeven{f} reuse the round-ties-even lowering
(the device has no dynamic rounding mode, so rint and roundeven
coincide).
The collector skips the whole libm crate, so a libm call the
float-math dispatch does not recognize can never resolve: it used to
surface only at module verification as a bare "Symbol libm__cbrtf not
found", far from the offending kernel. Bail out at the call site
instead, naming the libm function and where to add the mapping,
following the per-site diagnostics direction of NVIDIA#168.
The workspace fmt check gates CI; the sincos helper's callee-pair
tuples needed wrapping.
Covers the f32 and f64 libm entry points, the tuple-returning
sincosf, and the new fmax/fmin/rint lanes, comparing device results
against host stdlib math with a SUCCESS marker. Also pins the
interception anchor end to end: a user expf in a libm-named module
must stay a regular device call; the +1000.0 offset turns any wrong
rerouting to __nv_expf into a host-comparison failure.

libm sqrt/sqrtf are excluded: libm routes sqrt through an
arch-specific inline-asm wrapper that MIR-inlines into the kernel
before call-site interception, a known follow-up.
@nihalpasham

Copy link
Copy Markdown
Collaborator

Thanks for the PR. LGTM

Just a couple of additions:

  • merged current main into the branch (one add-add conflict in the terminator dispatch; kept both arms)
  • anchored the libm match to the crate root in from_libm_path / libm_sincos_is_f64: contains("libm") rerouted any user function whose path mentions libm (IR-proven: a user expf in a libm-named module compiled to __nv_expf); adversarial unit tests pin it
  • mapped fmaxf/fmax, fminf/fmin, rintf/rint, roundevenf/roundeven onto the existing enum variants
  • unmapped libm::* calls now fail at the call site with "libm function X is not yet mapped to a libdevice intrinsic" instead of the late "Symbol libm__cbrtf not found" verifier error
  • promoted the repro to a libm_math feature example (f32/f64 lanes, sincosf, host comparison, plus an end-to-end guard that a user expf in a libm-named module stays a real call); libm::sqrt{,f} stays excluded for now, its inline-asm wrapper is MIR-inlined before interception
  • cbrt intentionally left out: feat: Add cbrt intrinsic support for f32 and f64 #148 adds the core-path cbrt, whichever lands second rebases trivially

@nihalpasham
nihalpasham merged commit 0a8404b into NVIDIA:main Jun 12, 2026
24 checks passed
nihalpasham added a commit to goog00/cuda-oxide that referenced this pull request Jun 12, 2026
Completes the coordination between the libm interception table (NVIDIA#142)
and the cbrt lowering (NVIDIA#148): the libm-crate spellings now reach the
same libdevice __nv_cbrt{,f} path as the core ones.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working codegen Device code-generation pipeline (Rust MIR to IR to PTX) intrinsics Device intrinsics and libdevice math mappings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants