From d45223c10e2304914335ecc0c9b5e415e6366324 Mon Sep 17 00:00:00 2001 From: Raul Estrada Date: Sun, 25 Oct 2026 19:16:42 -0600 Subject: [PATCH] feat: add staged libdevice math generation Signed-off-by: Raul Estrada --- .../crates/cuda-intrinsics-gen/README.md | 58 +- .../cuda-intrinsics-gen/src/generate.rs | 22 +- .../src/libdevice_math/evidence.rs | 159 ++++ .../src/libdevice_math/mod.rs | 408 ++++++++++ .../src/libdevice_math/render.rs | 47 ++ .../src/libdevice_math/tests.rs | 400 ++++++++++ .../crates/cuda-intrinsics-gen/src/main.rs | 18 + .../evidence/libdevice_math/README.md | 33 + .../evidence/libdevice_math/cuda-13.0.json | 71 ++ .../intrinsics/generated/libdevice_math.rs | 742 ++++++++++++++++++ cuda-oxide/intrinsics/libdevice-math.md | 87 ++ .../intrinsics/overlay/libdevice_math.toml | 194 +++++ 12 files changed, 2237 insertions(+), 2 deletions(-) create mode 100644 cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/evidence.rs create mode 100644 cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/mod.rs create mode 100644 cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/render.rs create mode 100644 cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/tests.rs create mode 100644 cuda-oxide/intrinsics/evidence/libdevice_math/README.md create mode 100644 cuda-oxide/intrinsics/evidence/libdevice_math/cuda-13.0.json create mode 100644 cuda-oxide/intrinsics/generated/libdevice_math.rs create mode 100644 cuda-oxide/intrinsics/libdevice-math.md create mode 100644 cuda-oxide/intrinsics/overlay/libdevice_math.toml diff --git a/cuda-oxide/crates/cuda-intrinsics-gen/README.md b/cuda-oxide/crates/cuda-intrinsics-gen/README.md index dc2901a853..02524a79df 100644 --- a/cuda-oxide/crates/cuda-intrinsics-gen/README.md +++ b/cuda-oxide/crates/cuda-intrinsics-gen/README.md @@ -4,7 +4,7 @@ Extractor and deterministic source generator for cuda-oxide's intrinsics. It reads `intrinsics/catalog.json` and writes every generated file in the intrinsic pipeline. `MANAGED_GENERATED_ROOTS` in `src/generate.rs` lists the -nine directories it owns, where `generate` writes outputs and removes stale +ten directories it owns, where `generate` writes outputs and removes stale marker-bearing files: - `cuda-oxide/crates/cuda-intrinsics/src/generated/`: the raw intrinsic declarations; @@ -17,6 +17,7 @@ marker-bearing files: requirements), `cuda-oxide/crates/mir-lower/src/convert` (conversion interfaces), and `cuda-oxide/crates/rustc-codegen-cuda/src` (collector predicates); - `intrinsics/probes/`: the per-intrinsic PTX probe evidence. +- `intrinsics/generated/`: staged libm-to-libdevice mapping metadata. Generated files carry a `// @generated by cuda-intrinsics-gen ... DO NOT EDIT.` banner; edit the @@ -27,6 +28,8 @@ cuda-intrinsics-gen extract --intrinsics-json FILE --nvptx-json FILE cuda-intrinsics-gen extract --llvm-src DIR --llvm-tblgen FILE cuda-intrinsics-gen generate cuda-intrinsics-gen check +cuda-intrinsics-gen extract-libdevice --libdevice FILE --llvm-dis FILE +cuda-intrinsics-gen verify-libdevice --libdevice FILE --llvm-dis FILE cuda-intrinsics-gen coverage [--family NAME] cuda-intrinsics-gen check-abi-history --base-ref REF cuda-intrinsics-gen probe [--all | --intrinsic ID] [--llc FILE] [--skip-terminal] [--per-target] @@ -40,3 +43,56 @@ by each route, and `check-abi-history` keeps the intrinsic ABI ledger append-only. Generation shells out to `rustfmt`, which the pinned toolchain provides. + +## Staged libdevice math family + +`intrinsics/overlay/libdevice_math.toml` records the 72 existing f32/f64 libm +exports, including aliases and handwritten `fmin`/`fmax` and `sincos` routes. +The renderer emits `intrinsics/generated/libdevice_math.rs` and +`intrinsics/libdevice-math.md`. The Rust metadata is compiled by generator tests; +compiler dispatch and its handwritten tables are migrated in a subsequent PR. + +This input is read explicitly by the libdevice family, separately from the +hardware shard index in `intrinsics/overlay.toml`. Its evidence comes from +`libdevice.10.bc`, so it does not receive hardware ABI IDs or change the hardware +catalog hash. The overlay reconciliation guard recognizes this separate reader; +unlisted hardware shards remain errors. Normal `generate` and `check` process +both domains, and stale metadata under `intrinsics/generated/` is reconciled. + +Rows name a Rust signature class, optional libm aliases and cmath spelling, +placeholder suffixes, libdevice symbol, optional native LLVM alternative, +link classification, and optional `handwritten:NAME` handler. `{width}` expands +to `f32`/`f64`; `{suffix}` expands to `f`/the empty string. Unknown fields, +duplicate names, invalid templates, ambiguous ABI signatures, and incompatible +link classifications fail before outputs are written. Tuple signatures require +an explicit handwritten handler. + +Generation checks every referenced C symbol and signature against the pinned +capture in `intrinsics/evidence/libdevice_math/cuda-13.0.json`. Capture provenance +and reproduction instructions are recorded beside it. The cache lets ordinary +`generate` and `check` run without a CUDA installation. + +To verify the same ABI against a locally installed toolkit, use the pinned +toolchain's `llvm-tools` component: + +```bash +libdevice_llvm_dis="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin/llvm-dis" +cargo run -p cuda-intrinsics-gen -- verify-libdevice \ + --libdevice "${CUDA_PATH:-/usr/local/cuda}/nvvm/libdevice/libdevice.10.bc" \ + --llvm-dis "$libdevice_llvm_dis" +``` + +Verification accepts compatible newer toolkit bitcode and reports its hash. +To refresh evidence, first review and update the bitcode hash pin in the shard, +then run `extract-libdevice` with the same arguments, followed by `generate` and +`check`. Extraction hashes and disassembles the same bytes, requires exported +definitions with exact return/argument types, and writes the capture only after +all referenced signatures pass. + +Validation from `cuda-oxide/`: + +```bash +cargo test -p cuda-intrinsics-gen --locked --all-targets +cargo clippy -p cuda-intrinsics-gen --locked --all-targets -- -D warnings +cargo run -p cuda-intrinsics-gen --locked -- check +``` diff --git a/cuda-oxide/crates/cuda-intrinsics-gen/src/generate.rs b/cuda-oxide/crates/cuda-intrinsics-gen/src/generate.rs index b9476f2bae..4ecfab910d 100644 --- a/cuda-oxide/crates/cuda-intrinsics-gen/src/generate.rs +++ b/cuda-oxide/crates/cuda-intrinsics-gen/src/generate.rs @@ -22,6 +22,7 @@ const MANAGED_GENERATED_ROOTS: &[&str] = &[ "crates/mir-lower/src/convert", "crates/rustc-codegen-cuda/src", "intrinsics/probes", + "intrinsics/generated", ]; const GENERATED_MARKER: &[u8] = b"@generated by cuda-intrinsics-gen"; @@ -72,7 +73,14 @@ fn unindexed_overlay_shards(repo_root: &Path) -> Result> { "{} lists no shards; refusing to report every overlay file as unindexed", manifest_path.display() ); - let listed: BTreeSet<&str> = index.shards.iter().map(String::as_str).collect(); + // Libdevice is a separate evidence domain, read unconditionally by + // libdevice_math::outputs rather than folded into the hardware ABI catalog. + let listed: BTreeSet<&str> = index + .shards + .iter() + .map(String::as_str) + .chain(std::iter::once(crate::libdevice_math::SHARD)) + .collect(); let intrinsics_root = repo_root.join("intrinsics"); let overlay_dir = intrinsics_root.join("overlay"); @@ -142,6 +150,7 @@ pub fn run(repo_root: &Path, check: bool) -> Result<()> { let catalog_json = pretty_json(&catalog)?; let catalog_hash = sha256_bytes(catalog_json.as_bytes()); let mut outputs = all_outputs(&catalog, catalog_json, &catalog_hash)?; + outputs.extend(crate::libdevice_math::outputs(repo_root)?); let expected: BTreeSet = outputs.keys().cloned().collect(); for (path, contents) in &mut outputs { if path.extension().and_then(|extension| extension.to_str()) == Some("rs") { @@ -300,6 +309,17 @@ mod tests { fs::remove_dir_all(root).unwrap(); } + #[test] + fn libdevice_shard_has_an_explicit_separate_reader() { + let root = overlay_fixture( + "libdevice-indexed", + &["listed.toml"], + &["listed.toml", "libdevice_math.toml"], + ); + assert!(unindexed_overlay_shards(&root).unwrap().is_empty()); + fs::remove_dir_all(root).unwrap(); + } + /// The manifest grammar accepts nested shard paths, so the walk must /// descend: a forgotten file is no less forgotten for sitting in a /// subdirectory. diff --git a/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/evidence.rs b/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/evidence.rs new file mode 100644 index 0000000000..afae09de91 --- /dev/null +++ b/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/evidence.rs @@ -0,0 +1,159 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use super::Evidence; +use crate::util::sha256_bytes; +use anyhow::{Context, Result, bail, ensure}; +use std::collections::BTreeMap; +use std::fs; +use std::io::Write; +use std::path::Path; +use std::process::{Command, Stdio}; + +pub(super) fn capture( + bitcode: &Path, + llvm_dis: &Path, + expected: &BTreeMap, +) -> Result { + let bytes = fs::read(bitcode).with_context(|| format!("read {}", bitcode.display()))?; + ensure!( + bytes.starts_with(b"BC\xc0\xde"), + "input is not LLVM bitcode" + ); + let version = Command::new(llvm_dis) + .arg("--version") + .output() + .with_context(|| format!("run {} --version", llvm_dis.display()))?; + ensure!(version.status.success(), "llvm-dis --version failed"); + let version = String::from_utf8(version.stdout).context("llvm-dis version is not UTF-8")?; + let mut child = Command::new(llvm_dis) + .args(["-o", "-", "-"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .with_context(|| format!("start {}", llvm_dis.display()))?; + // Hash and disassemble the same bytes, rather than reopening a mutable path. + child + .stdin + .take() + .context("llvm-dis stdin unavailable")? + .write_all(&bytes)?; + let output = child.wait_with_output().context("wait for llvm-dis")?; + ensure!( + output.status.success(), + "llvm-dis failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + let ir = String::from_utf8(output.stdout).context("llvm-dis output is not UTF-8")?; + Ok(Evidence { + schema: 1, + bitcode_sha256: sha256_bytes(&bytes), + llvm_dis_version: version.trim().to_string(), + symbols: signatures(&ir, expected)?, + }) +} + +fn llvm_type(text: &str) -> Result { + let mut words = text.split_whitespace(); + let first = words.next().context("missing LLVM parameter type")?; + if first == "ptr" || first.ends_with('*') { + if let Some(space) = words.next().filter(|word| word.starts_with("addrspace(")) { + return Ok(format!("ptr {}", space.trim_end_matches('*'))); + } + return Ok("ptr".into()); + } + ensure!( + matches!(first, "float" | "double" | "i32" | "i64" | "void"), + "unsupported libdevice ABI type {first}" + ); + Ok(first.into()) +} + +/// Parse only requested exported definitions. Unrelated intrinsics and helper +/// functions are controls; declarations alone are insufficient evidence. +pub(super) fn signatures( + ir: &str, + expected: &BTreeMap, +) -> Result> { + let mut found = BTreeMap::new(); + let mut header = String::new(); + for line in ir.lines() { + let line = line.trim(); + if line.starts_with("define ") { + ensure!(header.is_empty(), "unterminated LLVM function definition"); + header.push_str(line); + } else if !header.is_empty() { + header.push(' '); + header.push_str(line); + } else { + continue; + } + if !header.contains('{') { + continue; + } + let definition = std::mem::take(&mut header); + let Some((prefix, tail)) = definition.split_once('@') else { + continue; + }; + let Some((name, parameters)) = tail.split_once('(') else { + continue; + }; + let name = name.trim().trim_matches('"'); + let Some(expected_signature) = expected.get(name) else { + continue; + }; + ensure!( + !prefix + .split_whitespace() + .any(|word| matches!(word, "internal" | "private" | "available_externally")), + "libdevice symbol {name} is not an exported definition" + ); + let return_type = prefix + .split_whitespace() + .last() + .context("missing LLVM return type")?; + let return_type = llvm_type(return_type)?; + // Address-space parentheses can occur inside the parameter list. + let mut depth = 1; + let mut end = None; + for (index, byte) in parameters.bytes().enumerate() { + match byte { + b'(' => depth += 1, + b')' => depth -= 1, + _ => {} + } + if depth == 0 { + end = Some(index); + break; + } + } + let end = end.context("unterminated LLVM parameter list")?; + let parameters = ¶meters[..end]; + let arguments = if parameters.trim().is_empty() { + Vec::new() + } else { + parameters + .split(',') + .map(llvm_type) + .collect::>>()? + }; + let signature = format!("{return_type}({})", arguments.join(", ")); + ensure!( + &signature == expected_signature, + "libdevice signature mismatch for {name}: expected {expected_signature}, found {signature}" + ); + ensure!( + found.insert(name.into(), signature).is_none(), + "duplicate libdevice definition {name}" + ); + } + for symbol in expected.keys() { + if !found.contains_key(symbol) { + bail!("libdevice definition is missing: {symbol}"); + } + } + Ok(found) +} diff --git a/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/mod.rs b/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/mod.rs new file mode 100644 index 0000000000..c21399936b --- /dev/null +++ b/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/mod.rs @@ -0,0 +1,408 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Staged libm mappings. This family has bitcode evidence, not hardware ABI IDs. + +mod evidence; +mod render; +#[cfg(test)] +mod tests; + +use crate::util::{pretty_json, read_json, write_if_changed}; +use anyhow::{Context, Result, ensure}; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs; +use std::path::{Component, Path, PathBuf}; + +/// Read separately from the hardware shard index so hardware catalog hashes and +/// ABI IDs remain unchanged. `generate` reconciles this input explicitly. +pub const SHARD: &str = "overlay/libdevice_math.toml"; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct Shard { + schema: u32, + family: String, + libdevice_math: Math, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct Math { + evidence: String, + libdevice_sha256: String, + widths: Vec, + #[serde(rename = "function")] + functions: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct Function { + name: String, + signature: Signature, + #[serde(default)] + libm: Vec, + #[serde(default)] + cmath: Option, + #[serde(default)] + placeholders: Vec, + #[serde(default)] + libdevice: Option, + #[serde(default)] + llvm: Option, + #[serde(default)] + lowering: Option, + #[serde(default)] + link: Link, +} + +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)] +enum Signature { + #[serde(rename = "(F) -> F")] + Unary, + #[serde(rename = "(F, F) -> F")] + Binary, + #[serde(rename = "(F, F, F) -> F")] + Ternary, + #[serde(rename = "(F) -> i32")] + IntegerResult, + #[serde(rename = "(F, i32) -> F")] + IntegerArgument, + #[serde(rename = "(F) -> (F, i32)")] + FloatIntegerPair, + #[serde(rename = "(F) -> (F, F)")] + FloatPair, + #[serde(rename = "(F, F) -> (F, i32)")] + RemainderPair, +} + +impl Signature { + fn rust(self, width: &str) -> String { + let generic = match self { + Self::Unary => "(F) -> F", + Self::Binary => "(F, F) -> F", + Self::Ternary => "(F, F, F) -> F", + Self::IntegerResult => "(F) -> i32", + Self::IntegerArgument => "(F, i32) -> F", + Self::FloatIntegerPair => "(F) -> (F, i32)", + Self::FloatPair => "(F) -> (F, F)", + Self::RemainderPair => "(F, F) -> (F, i32)", + }; + generic.replace('F', width) + } + + fn c_abi(self, width: &str) -> String { + let float = if width == "f32" { "float" } else { "double" }; + match self { + Self::Unary => format!("{float}({float})"), + Self::Binary => format!("{float}({float}, {float})"), + Self::Ternary => format!("{float}({float}, {float}, {float})"), + Self::IntegerResult => format!("i32({float})"), + Self::IntegerArgument => format!("{float}({float}, i32)"), + Self::FloatIntegerPair | Self::FloatPair => format!("{float}({float}, ptr)"), + Self::RemainderPair => format!("{float}({float}, {float}, ptr)"), + } + } +} + +#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +enum Link { + #[default] + Always, + Libnvvm, + Never, +} + +impl Link { + fn as_str(self) -> &'static str { + match self { + Self::Always => "always", + Self::Libnvvm => "libnvvm", + Self::Never => "never", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Mapping { + libm: String, + signature: String, + placeholders: Vec, + cmath: Option, + libdevice: Option, + llvm: Option, + lowering: Option, + link: Link, + c_abi: String, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +struct Evidence { + schema: u32, + bitcode_sha256: String, + llvm_dis_version: String, + symbols: BTreeMap, +} + +fn identifier(value: &str) -> bool { + !value.is_empty() + && (value.as_bytes()[0].is_ascii_alphabetic() || value.starts_with('_')) + && value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_') +} + +fn expand(template: &str, width: &str) -> Result { + let value = template + .replace("{width}", width) + .replace("{suffix}", if width == "f32" { "f" } else { "" }); + ensure!( + identifier(&value), + "invalid math name/template {template:?}" + ); + Ok(value) +} + +fn parse(bytes: &[u8]) -> Result { + let shard: Shard = toml::from_slice(bytes).context("parse libdevice_math shard")?; + ensure!(shard.schema == 1, "unsupported libdevice_math schema"); + ensure!( + shard.family == "libdevice_math", + "wrong libdevice_math family" + ); + let math = &shard.libdevice_math; + ensure!( + math.widths == ["f32", "f64"], + "libdevice_math widths must be f32, f64" + ); + ensure!( + !math.functions.is_empty(), + "libdevice_math has no functions" + ); + ensure!( + math.libdevice_sha256.len() == 64 + && math + .libdevice_sha256 + .bytes() + .all(|byte| byte.is_ascii_hexdigit()), + "invalid libdevice bitcode SHA-256" + ); + let path = Path::new(&math.evidence); + ensure!( + path.starts_with("evidence") + && path.extension().and_then(|part| part.to_str()) == Some("json") + && path + .components() + .all(|part| matches!(part, Component::Normal(_))), + "libdevice evidence must be a relative JSON path under evidence/" + ); + Ok(shard) +} + +fn read(root: &Path) -> Result { + let path = root.join("intrinsics").join(SHARD); + parse(&fs::read(&path).with_context(|| format!("read {}", path.display()))?) +} + +fn mappings(shard: &Shard) -> Result> { + let mut names = BTreeSet::new(); + let mut exports = BTreeSet::new(); + let mut cmath_paths = BTreeSet::new(); + let mut scalar_placeholders = BTreeSet::new(); + let mut result = Vec::new(); + for function in &shard.libdevice_math.functions { + ensure!(identifier(&function.name), "invalid math function name"); + ensure!( + names.insert(&function.name), + "duplicate math function {}", + function.name + ); + if let Some(lowering) = &function.lowering { + ensure!( + lowering + .strip_prefix("handwritten:") + .is_some_and(identifier), + "invalid handwritten math lowering {lowering:?}" + ); + } else { + ensure!( + !matches!( + function.signature, + Signature::FloatPair | Signature::FloatIntegerPair | Signature::RemainderPair + ), + "tuple-returning math requires an explicit handwritten adapter" + ); + } + let aliases = if function.libm.is_empty() { + vec![function.name.clone()] + } else { + function.libm.clone() + }; + for width in &shard.libdevice_math.widths { + let symbol = if let Some(symbol) = &function.libdevice { + Some(expand(symbol, width)?) + } else if function.lowering.is_none() { + Some(format!( + "__nv_{}{}", + function.name, + if width == "f32" { "f" } else { "" } + )) + } else { + None + }; + if let Some(symbol) = &symbol { + ensure!( + symbol.starts_with("__nv_"), + "math symbol must start with __nv_" + ); + } + let llvm = function + .llvm + .as_deref() + .map(|name| expand(name, width)) + .transpose()?; + ensure!( + (function.link == Link::Libnvvm) == llvm.is_some(), + "backend-dependent math must name its LLVM intrinsic" + ); + ensure!( + function.link != Link::Libnvvm || symbol.is_some(), + "backend-dependent math must name its libdevice ABI" + ); + ensure!( + function.link != Link::Never || (symbol.is_none() && function.lowering.is_some()), + "never-linked math must use a handwritten lowering without a libdevice call" + ); + let templates = if function.placeholders.is_empty() { + vec![format!("{}{{width}}", function.name)] + } else { + function.placeholders.clone() + }; + let placeholders = templates + .iter() + .map(|name| expand(name, width)) + .collect::>>()?; + if function.lowering.is_none() { + ensure!( + placeholders.len() == 1, + "plain math requires one placeholder" + ); + ensure!( + scalar_placeholders.insert((width, placeholders[0].clone())), + "duplicate scalar math placeholder {} for {width}", + placeholders[0] + ); + } + let cmath = function + .cmath + .as_deref() + .map(|name| expand(name, width).map(|name| format!("std::sys::cmath::{name}"))) + .transpose()?; + if let Some(path) = &cmath { + ensure!( + cmath_paths.insert(path.clone()), + "duplicate cmath spelling {path}" + ); + } + for alias in &aliases { + ensure!(identifier(alias), "invalid libm alias {alias:?}"); + let libm = format!("{alias}{}", if width == "f32" { "f" } else { "" }); + ensure!( + exports.insert(libm.clone()), + "duplicate libm spelling {libm}" + ); + result.push(Mapping { + libm, + signature: function.signature.rust(width), + placeholders: placeholders.clone(), + cmath: cmath.clone(), + libdevice: symbol.clone(), + llvm: llvm.clone(), + lowering: function.lowering.clone(), + link: function.link, + c_abi: function.signature.c_abi(width), + }); + } + } + } + result.sort_by(|left, right| left.libm.cmp(&right.libm)); + Ok(result) +} + +fn expected_symbols(rows: &[Mapping]) -> Result> { + let mut result = BTreeMap::new(); + for row in rows { + if let Some(symbol) = &row.libdevice + && let Some(previous) = result.insert(symbol.clone(), row.c_abi.clone()) + { + ensure!( + previous == row.c_abi, + "conflicting libdevice signatures for {symbol}" + ); + } + } + Ok(result) +} + +fn validate_evidence(shard: &Shard, rows: &[Mapping], evidence: &Evidence) -> Result<()> { + ensure!( + evidence.schema == 1, + "unsupported libdevice evidence schema" + ); + ensure!( + evidence.bitcode_sha256 == shard.libdevice_math.libdevice_sha256, + "libdevice bitcode hash does not match the shard pin" + ); + ensure!( + !evidence.llvm_dis_version.trim().is_empty(), + "missing llvm-dis provenance" + ); + for (symbol, signature) in expected_symbols(rows)? { + ensure!( + evidence.symbols.get(&symbol) == Some(&signature), + "libdevice evidence missing or signature mismatch for {symbol}: expected {signature}" + ); + } + Ok(()) +} + +pub fn outputs(root: &Path) -> Result> { + let shard = read(root)?; + let rows = mappings(&shard)?; + let evidence: Evidence = + read_json(&root.join("intrinsics").join(&shard.libdevice_math.evidence))?; + validate_evidence(&shard, &rows, &evidence)?; + Ok(render::outputs(&rows, &evidence.bitcode_sha256)) +} + +/// Capture reviewed signatures, or compare a local toolkit against the same +/// rows. Verification accepts a compatible newer toolkit without changing pins. +pub fn inspect(root: &Path, bitcode: &Path, llvm_dis: &Path, capture: bool) -> Result<()> { + let shard = read(root)?; + let rows = mappings(&shard)?; + let expected = expected_symbols(&rows)?; + let evidence = evidence::capture(bitcode, llvm_dis, &expected)?; + if capture { + validate_evidence(&shard, &rows, &evidence)?; + let path = root.join("intrinsics").join(&shard.libdevice_math.evidence); + write_if_changed(&path, &pretty_json(&evidence)?)?; + println!( + "captured {} libdevice signatures in {}", + expected.len(), + path.display() + ); + } else { + println!( + "verified {} libdevice signatures; bitcode SHA-256 {}", + expected.len(), + evidence.bitcode_sha256 + ); + } + Ok(()) +} diff --git a/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/render.rs b/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/render.rs new file mode 100644 index 0000000000..612dc00999 --- /dev/null +++ b/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/render.rs @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use super::Mapping; +use std::collections::BTreeMap; +use std::fmt::Write; +use std::path::PathBuf; + +pub(super) fn outputs(rows: &[Mapping], bitcode_hash: &str) -> BTreeMap { + let mut rust = String::from( + "/*\n * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\n * SPDX-License-Identifier: Apache-2.0\n */\n\n// @generated by cuda-intrinsics-gen from overlay/libdevice_math.toml. DO NOT EDIT.\n// Staged metadata; compiler dispatch is still handwritten.\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct MathMapping {\n pub libm: &'static str,\n pub signature: &'static str,\n pub placeholders: &'static [&'static str],\n pub cmath: Option<&'static str>,\n pub libdevice: Option<&'static str>,\n pub llvm: Option<&'static str>,\n pub lowering: Option<&'static str>,\n pub link: &'static str,\n}\n\npub const MAPPINGS: &[MathMapping] = &[\n", + ); + let mut reference = format!( + "\n\n\n\n# Staged libm mappings\n\n{} mapped f32/f64 libm exports. Compiler dispatch remains handwritten.\n\nLibdevice bitcode SHA-256: `{bitcode_hash}`.\n\n`libnvvm` links only on that backend; `always` links on both; `never` uses\na handwritten expansion. Aliases are separate exports sharing a mapping.\n\n| libm export | Rust signature | libdevice symbol | LLVM NVPTX alternative | Handwritten lowering | Linking |\n| --- | --- | --- | --- | --- | --- |\n", + rows.len() + ); + for row in rows { + writeln!(rust, "MathMapping {{ libm: {:?}, signature: {:?}, placeholders: &{:?}, cmath: {:?}, libdevice: {:?}, llvm: {:?}, lowering: {:?}, link: {:?} }},", row.libm, row.signature, row.placeholders, row.cmath, row.libdevice, row.llvm, row.lowering, row.link.as_str()).unwrap(); + writeln!( + reference, + "| `{}` | `{}` | {} | {} | {} | `{}` |", + row.libm, + row.signature, + code(row.libdevice.as_deref()), + code(row.llvm.as_deref()), + code(row.lowering.as_deref()), + row.link.as_str() + ) + .unwrap(); + } + rust.push_str("];\n"); + BTreeMap::from([ + ( + PathBuf::from("intrinsics/generated/libdevice_math.rs"), + rust, + ), + (PathBuf::from("intrinsics/libdevice-math.md"), reference), + ]) +} + +fn code(value: Option<&str>) -> String { + value + .map(|text| format!("`{text}`")) + .unwrap_or_else(|| " ".into()) +} diff --git a/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/tests.rs b/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/tests.rs new file mode 100644 index 0000000000..cf51aa8ae7 --- /dev/null +++ b/cuda-oxide/crates/cuda-intrinsics-gen/src/libdevice_math/tests.rs @@ -0,0 +1,400 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use super::*; + +mod generated { + include!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../intrinsics/generated/libdevice_math.rs" + )); +} + +fn root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../..") +} + +fn fixture(functions: &str) -> Shard { + parse(format!("schema = 1\nfamily = \"libdevice_math\"\n[libdevice_math]\nevidence = \"evidence/test.json\"\nlibdevice_sha256 = \"{}\"\nwidths = [\"f32\", \"f64\"]\n{functions}", "a".repeat(64)).as_bytes()).unwrap() +} + +#[test] +fn staged_snapshot_has_exactly_the_original_72_exports() { + let shard = read(&root()).unwrap(); + let rows = mappings(&shard).unwrap(); + assert_eq!(rows.len(), 72); + assert_eq!(expected_symbols(&rows).unwrap().len(), 64); + assert_eq!(generated::MAPPINGS.len(), rows.len()); + for (actual, row) in generated::MAPPINGS.iter().zip(&rows) { + assert_eq!(actual.libm, row.libm); + assert_eq!(actual.signature, row.signature); + assert_eq!( + actual.placeholders, + row.placeholders + .iter() + .map(String::as_str) + .collect::>() + ); + assert_eq!(actual.cmath, row.cmath.as_deref()); + assert_eq!(actual.libdevice, row.libdevice.as_deref()); + assert_eq!(actual.llvm, row.llvm.as_deref()); + assert_eq!(actual.lowering, row.lowering.as_deref()); + assert_eq!(actual.link, row.link.as_str()); + } +} + +fn quoted(text: &str) -> impl Iterator { + text.split('"').skip(1).step_by(2) +} + +fn function_body<'a>(source: &'a str, name: &str) -> &'a str { + let start = source.find(&format!("fn {name}(")).unwrap(); + let rest = &source[start..]; + let end = [rest.find("\n fn "), rest.find("\n}\n")] + .into_iter() + .flatten() + .min() + .unwrap(); + &rest[..end] +} + +fn variants(source: &str, name: &str, target: &str) -> BTreeMap { + let mut result = BTreeMap::new(); + for line in function_body(source, name).lines() { + let Some((left, right)) = line.split_once(target) else { + continue; + }; + for variant in left.split('|') { + if let Some(variant) = variant.trim().strip_prefix("Self::") { + result.insert( + variant.into(), + right.split([')', ',']).next().unwrap().trim().into(), + ); + } + } + } + result +} + +/// Compare staged rows against the live handwritten routes, not another copy +/// of an expected mapping list. Additional hand-landed functions may coexist +/// until the compiler-table migration, without making this PR depend on them. +#[test] +fn staged_rows_preserve_existing_import_placeholder_symbol_and_link_routes() { + let importer = + include_str!("../../../mir-importer/src/translator/terminator/intrinsics/float_math.rs"); + let dialect = include_str!("../../../dialect-mir/src/rust_intrinsics.rs"); + let lower = include_str!("../../../mir-lower/src/convert/ops/call.rs"); + let start = importer.find("fn from_libm_path(").unwrap(); + let end = start + + importer[start..] + .find("fn from_fast_intrinsic_path(") + .unwrap(); + let mut imports = BTreeMap::new(); + for line in importer[start..end].lines() { + if let Some((left, right)) = line.split_once("=> Some(Self::") { + for name in quoted(left) { + imports.insert(name, right.split(')').next().unwrap()); + } + } + } + let placeholders = variants(importer, "placeholder_callee", "=> rust_intrinsics::"); + let constants: BTreeMap<_, _> = dialect + .lines() + .filter_map(|line| { + let line = line.strip_prefix("pub const ")?; + let (name, _) = line.split_once(':')?; + Some((name, quoted(line).next()?)) + }) + .collect(); + let symbols = variants(lower, "libdevice_name", "=> Ok("); + let native = variants(lower, "llvm_intrinsic_name", "=> Ok(Some("); + let always = function_body(dialect, "is_libdevice_backed_placeholder"); + let conditional = function_body(dialect, "is_backend_dependent_libdevice_placeholder"); + let contains_constant = |body: &str, name: &str| { + body.split(|ch: char| !ch.is_ascii_alphanumeric() && ch != '_') + .any(|token| token == name) + }; + for row in mappings(&read(&root()).unwrap()).unwrap() { + if row.libm == "sincos" || row.libm == "sincosf" { + assert!(importer.contains(&format!("\"{}\" => Some(", row.libm))); + assert_eq!(row.placeholders.len(), 2); + assert_eq!(row.lowering.as_deref(), Some("handwritten:emit_sincos")); + continue; + } + let variant = imports[row.libm.as_str()]; + let constant = &placeholders[variant]; + assert_eq!( + row.placeholders, + [constants[constant.as_str()]], + "{}", + row.libm + ); + assert_eq!( + contains_constant(always, constant), + row.link == Link::Always, + "{}", + row.libm + ); + assert_eq!( + contains_constant(conditional, constant), + row.link == Link::Libnvvm, + "{}", + row.libm + ); + if variant == "Fabs" { + for symbol in [&row.libdevice, &row.llvm].into_iter().flatten() { + assert!(lower.contains(&format!("\"{symbol}\""))); + } + } else { + assert_eq!( + symbols.get(variant).map(|name| name.trim_matches('"')), + row.libdevice.as_deref(), + "{}", + row.libm + ); + assert_eq!( + native.get(variant).map(|name| name.trim_matches('"')), + row.llvm.as_deref(), + "{}", + row.libm + ); + } + if let Some(path) = &row.cmath { + assert!(importer.contains(&format!("\"{path}\" => Some(Self::{variant})"))); + } + } +} + +#[test] +fn aliases_and_handwritten_controls_keep_their_semantics() { + let rows = mappings(&read(&root()).unwrap()).unwrap(); + let find = |name: &str| rows.iter().find(|row| row.libm == name).unwrap(); + for (alias, original) in [("roundeven", "rint"), ("roundevenf", "rintf")] { + assert_eq!(find(alias).placeholders, find(original).placeholders); + assert_eq!(find(alias).libdevice, find(original).libdevice); + assert_eq!(find(alias).llvm, find(original).llvm); + } + for name in ["fmax", "fmaxf", "fmin", "fminf"] { + assert_eq!(find(name).link, Link::Never); + assert!(find(name).libdevice.is_none()); + assert!(find(name).llvm.is_none()); + } + assert_eq!(find("fabs").placeholders, find("fabsf").placeholders); + assert_eq!(find("floor").link, Link::Libnvvm); + assert_eq!(find("sqrt").link, Link::Always); +} + +#[test] +fn render_is_independent_of_row_order_and_check_detects_drift() { + let mut shard = read(&root()).unwrap(); + let before = render::outputs( + &mappings(&shard).unwrap(), + &shard.libdevice_math.libdevice_sha256, + ); + shard.libdevice_math.functions.reverse(); + let after = render::outputs( + &mappings(&shard).unwrap(), + &shard.libdevice_math.libdevice_sha256, + ); + assert_eq!(before, after); + for (path, source) in before { + let source = if path.extension().is_some_and(|extension| extension == "rs") { + crate::util::rustfmt_source(&source).unwrap() + } else { + source + }; + crate::util::check_contents(&root().join(&path), &source).unwrap(); + let mut stale = source.clone(); + stale.push_str("// drift\n"); + assert!(crate::util::check_contents(&root().join(&path), &stale).is_err()); + } +} + +#[test] +fn signature_classes_distinguish_integer_results_arguments_and_out_parameters() { + for (signature, c32, c64) in [ + (Signature::Unary, "float(float)", "double(double)"), + ( + Signature::Binary, + "float(float, float)", + "double(double, double)", + ), + ( + Signature::Ternary, + "float(float, float, float)", + "double(double, double, double)", + ), + (Signature::IntegerResult, "i32(float)", "i32(double)"), + ( + Signature::IntegerArgument, + "float(float, i32)", + "double(double, i32)", + ), + ( + Signature::FloatIntegerPair, + "float(float, ptr)", + "double(double, ptr)", + ), + ( + Signature::FloatPair, + "float(float, ptr)", + "double(double, ptr)", + ), + ( + Signature::RemainderPair, + "float(float, float, ptr)", + "double(double, double, ptr)", + ), + ] { + assert_eq!(signature.c_abi("f32"), c32); + assert_eq!(signature.c_abi("f64"), c64); + } + let adapter = fixture( + "[[libdevice_math.function]]\nname = \"frexp\"\nsignature = \"(F) -> (F, i32)\"\nlowering = \"handwritten:emit_frexp\"\nlibdevice = \"__nv_frexp{suffix}\"\n", + ); + let rows = mappings(&adapter).unwrap(); + assert_eq!( + expected_symbols(&rows).unwrap()["__nv_frexpf"], + "float(float, ptr)" + ); + assert_eq!(rows[0].signature, "(f64) -> (f64, i32)"); +} + +#[test] +fn malformed_rows_fail_closed() { + let base = "[[libdevice_math.function]]\nname = \"demo\"\nsignature = \"(F) -> F\"\n"; + for (suffix, expected) in [ + ( + "[[libdevice_math.function]]\nname = \"demo\"\nsignature = \"(F) -> F\"\n", + "duplicate math function", + ), + ("libm = [\"demo\", \"demo\"]\n", "duplicate libm spelling"), + ( + "placeholders = [\"bad{unknown}\"]\n", + "invalid math name/template", + ), + ("link = \"never\"\n", "never-linked math"), + ("link = \"libnvvm\"\n", "backend-dependent math"), + ("llvm = \"llvm_demo_{width}\"\n", "backend-dependent math"), + ( + "placeholders = [\"left{width}\", \"right{width}\"]\n", + "one placeholder", + ), + ( + "lowering = \"handwritten:emit_demo\"\nllvm = \"llvm_demo_{width}\"\nlink = \"libnvvm\"\n", + "libdevice ABI", + ), + ( + "[[libdevice_math.function]]\nname = \"other\"\nsignature = \"(F) -> F\"\nplaceholders = [\"demo{width}\"]\n", + "duplicate scalar math placeholder", + ), + ( + "libdevice = \"user_demo{suffix}\"\n", + "must start with __nv_", + ), + ( + "lowering = \"arbitrary_rust()\"\n", + "invalid handwritten math lowering", + ), + ] { + let shard = fixture(&format!("{base}{suffix}")); + let error = mappings(&shard).unwrap_err().to_string(); + assert!(error.contains(expected), "{error}"); + } + let tuple = fixture(&base.replace("(F) -> F", "(F) -> (F, i32)")); + assert!( + mappings(&tuple) + .unwrap_err() + .to_string() + .contains("handwritten adapter") + ); + let cmath = fixture(&format!( + "{base}cmath = \"demo{{suffix}}\"\n[[libdevice_math.function]]\nname = \"other\"\nsignature = \"(F) -> F\"\ncmath = \"demo{{suffix}}\"\n" + )); + assert!( + mappings(&cmath) + .unwrap_err() + .to_string() + .contains("duplicate cmath") + ); + let conflict = fixture(&format!( + "{base}libdevice = \"__nv_shared{{suffix}}\"\n[[libdevice_math.function]]\nname = \"other\"\nsignature = \"(F, F) -> F\"\nlibdevice = \"__nv_shared{{suffix}}\"\n" + )); + assert!( + expected_symbols(&mappings(&conflict).unwrap()) + .unwrap_err() + .to_string() + .contains("conflicting libdevice signatures") + ); +} + +#[test] +fn schema_and_evidence_pins_are_strict() { + let source = fs::read_to_string(root().join("intrinsics").join(SHARD)).unwrap(); + for changed in [ + source.replacen("schema = 1", "schema = 2", 1), + source.replacen("family = \"libdevice_math\"", "family = \"scalar_math\"", 1), + source.replacen("[\"f32\", \"f64\"]", "[\"f16\"]", 1), + source.replacen( + "evidence/libdevice_math/cuda-13.0.json", + "evidence/../outside.json", + 1, + ), + source.replacen("schema = 1", "schema = 1\nunknown = true", 1), + source.replacen("(F) -> F", "(F) -> bool", 1), + ] { + assert!(parse(changed.as_bytes()).is_err()); + } + let shard = read(&root()).unwrap(); + let rows = mappings(&shard).unwrap(); + let mut evidence: Evidence = read_json( + &root() + .join("intrinsics") + .join(&shard.libdevice_math.evidence), + ) + .unwrap(); + validate_evidence(&shard, &rows, &evidence).unwrap(); + evidence + .symbols + .insert("__nv_sinf".into(), "double(double)".into()); + assert!( + validate_evidence(&shard, &rows, &evidence) + .unwrap_err() + .to_string() + .contains("signature mismatch") + ); + evidence.bitcode_sha256 = "0".repeat(64); + assert!( + validate_evidence(&shard, &rows, &evidence) + .unwrap_err() + .to_string() + .contains("hash") + ); +} + +#[test] +fn llvm_evidence_requires_exported_definitions_and_exact_signatures() { + let expected = BTreeMap::from([ + ("__nv_demo".into(), "double(double, i32)".into()), + ("__nv_pairf".into(), "float(float, ptr)".into()), + ]); + let ir = "declare float @__nv_unrelated(float)\ndefine void @control() { ret void }\ndefine double @__nv_demo(\n double %x, i32 noundef %n) { ret double %x }\ndefine float @__nv_pairf(float %x, ptr nocapture %out) { ret float %x }\n"; + assert_eq!(evidence::signatures(ir, &expected).unwrap(), expected); + for changed in [ + ir.replace("define double @__nv_demo", "declare double @__nv_demo"), + ir.replace("i32 noundef %n", "i64 %n"), + ir.replace( + "define double @__nv_demo", + "define internal double @__nv_demo", + ), + ir.replace("ptr nocapture", "ptr addrspace(1) nocapture"), + format!("{ir}\ndefine double @__nv_demo(double %x, i32 %n) {{ ret double %x }}"), + ] { + assert!(evidence::signatures(&changed, &expected).is_err()); + } + let typed = ir.replace("ptr nocapture", "i32* nocapture"); + assert_eq!(evidence::signatures(&typed, &expected).unwrap(), expected); +} diff --git a/cuda-oxide/crates/cuda-intrinsics-gen/src/main.rs b/cuda-oxide/crates/cuda-intrinsics-gen/src/main.rs index 92b3c4c640..13361762a2 100644 --- a/cuda-oxide/crates/cuda-intrinsics-gen/src/main.rs +++ b/cuda-oxide/crates/cuda-intrinsics-gen/src/main.rs @@ -7,6 +7,7 @@ mod abi_history; mod coverage; mod extract; mod generate; +mod libdevice_math; mod model; mod probe; mod ptx; @@ -74,6 +75,21 @@ fn try_main() -> Result<()> { reject_extra(arguments)?; generate::run(&repo_root, true) } + "extract-libdevice" | "verify-libdevice" => { + let bitcode = take_option(&mut arguments, "--libdevice")? + .map(PathBuf::from) + .context("libdevice command requires --libdevice FILE")?; + let llvm_dis = take_option(&mut arguments, "--llvm-dis")? + .map(PathBuf::from) + .context("libdevice command requires --llvm-dis FILE")?; + reject_extra(arguments)?; + libdevice_math::inspect( + &repo_root, + &bitcode, + &llvm_dis, + command == "extract-libdevice", + ) + } "coverage" => { let family = take_option(&mut arguments, "--family")?; reject_extra(arguments)?; @@ -218,6 +234,8 @@ fn print_usage() { cuda-intrinsics-gen extract --llvm-src DIR --llvm-tblgen FILE [--repo-root DIR]\n \ cuda-intrinsics-gen generate [--repo-root DIR]\n \ cuda-intrinsics-gen check [--repo-root DIR]\n \ + cuda-intrinsics-gen extract-libdevice --libdevice FILE --llvm-dis FILE [--repo-root DIR]\n \ + cuda-intrinsics-gen verify-libdevice --libdevice FILE --llvm-dis FILE [--repo-root DIR]\n \ cuda-intrinsics-gen coverage [--family NAME] [--repo-root DIR]\n \ cuda-intrinsics-gen check-abi-history --base-ref REF [--repo-root DIR]\n \ cuda-intrinsics-gen probe [--all | --intrinsic ID] [--llc FILE] [--skip-terminal] [--per-target] [--repo-root DIR]\n \ diff --git a/cuda-oxide/intrinsics/evidence/libdevice_math/README.md b/cuda-oxide/intrinsics/evidence/libdevice_math/README.md new file mode 100644 index 0000000000..4ca16f6c8e --- /dev/null +++ b/cuda-oxide/intrinsics/evidence/libdevice_math/README.md @@ -0,0 +1,33 @@ + + + +# Libdevice math ABI capture + +`cuda-13.0.json` contains 64 distinct exported function signatures referenced by +the initial libdevice math shard. They were extracted from NVIDIA's CUDA +13.0.88 `libdevice.10.bc` with LLVM 23.1.1 from the pinned Rust 1.99.1 toolchain. + +Source: [NVIDIA nvidia-nvvm 13.0.88](https://pypi.org/project/nvidia-nvvm/13.0.88/), +Linux x86-64 wheel +`nvidia_nvvm-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl`. + +- Wheel SHA-256: `c5f41ffeb6466944a026dfa5317d7d85355c119bbec279205d22f1869d1054e0`. +- Bitcode member: `nvidia/cu13/nvvm/libdevice/libdevice.10.bc`. +- Bitcode SHA-256: `91334d6e12748f6cb5bbf0a1cd965a56bcd93dc4f496d2e5c5f8c6e523094356`. + +To reproduce the capture from matching local bitcode, run from `cuda-oxide/`: + +```bash +libdevice_llvm_dis="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin/llvm-dis" +cargo run -p cuda-intrinsics-gen --locked -- extract-libdevice \ + --libdevice /path/to/libdevice.10.bc --llvm-dis "$libdevice_llvm_dis" +cargo run -p cuda-intrinsics-gen --locked -- generate +cargo run -p cuda-intrinsics-gen --locked -- check +``` + +The extractor checks definitions, return types, operand types, and duplicate +symbols before writing. Only signatures required by the shard are retained. +Opaque LLVM pointers retain their address space, but do not encode a pointee +type; out-parameter semantics remain the handwritten adapter's responsibility. +Ordinary generation validates the capture's bitcode hash against the shard pin +and every referenced ABI against the captured signatures. diff --git a/cuda-oxide/intrinsics/evidence/libdevice_math/cuda-13.0.json b/cuda-oxide/intrinsics/evidence/libdevice_math/cuda-13.0.json new file mode 100644 index 0000000000..6c9e8b70f7 --- /dev/null +++ b/cuda-oxide/intrinsics/evidence/libdevice_math/cuda-13.0.json @@ -0,0 +1,71 @@ +{ + "schema": 1, + "bitcode_sha256": "91334d6e12748f6cb5bbf0a1cd965a56bcd93dc4f496d2e5c5f8c6e523094356", + "llvm_dis_version": "LLVM (http://llvm.org/):\n LLVM version 23.1.1-rust-1.99.1-stable\n Optimized build.", + "symbols": { + "__nv_acos": "double(double)", + "__nv_acosf": "float(float)", + "__nv_acosh": "double(double)", + "__nv_acoshf": "float(float)", + "__nv_asin": "double(double)", + "__nv_asinf": "float(float)", + "__nv_asinh": "double(double)", + "__nv_asinhf": "float(float)", + "__nv_atan": "double(double)", + "__nv_atan2": "double(double, double)", + "__nv_atan2f": "float(float, float)", + "__nv_atanf": "float(float)", + "__nv_atanh": "double(double)", + "__nv_atanhf": "float(float)", + "__nv_cbrt": "double(double)", + "__nv_cbrtf": "float(float)", + "__nv_ceil": "double(double)", + "__nv_ceilf": "float(float)", + "__nv_copysign": "double(double, double)", + "__nv_copysignf": "float(float, float)", + "__nv_cos": "double(double)", + "__nv_cosf": "float(float)", + "__nv_cosh": "double(double)", + "__nv_coshf": "float(float)", + "__nv_exp": "double(double)", + "__nv_exp2": "double(double)", + "__nv_exp2f": "float(float)", + "__nv_expf": "float(float)", + "__nv_expm1": "double(double)", + "__nv_expm1f": "float(float)", + "__nv_fabs": "double(double)", + "__nv_fabsf": "float(float)", + "__nv_floor": "double(double)", + "__nv_floorf": "float(float)", + "__nv_fma": "double(double, double, double)", + "__nv_fmaf": "float(float, float, float)", + "__nv_hypot": "double(double, double)", + "__nv_hypotf": "float(float, float)", + "__nv_log": "double(double)", + "__nv_log10": "double(double)", + "__nv_log10f": "float(float)", + "__nv_log1p": "double(double)", + "__nv_log1pf": "float(float)", + "__nv_log2": "double(double)", + "__nv_log2f": "float(float)", + "__nv_logf": "float(float)", + "__nv_pow": "double(double, double)", + "__nv_powf": "float(float, float)", + "__nv_rint": "double(double)", + "__nv_rintf": "float(float)", + "__nv_round": "double(double)", + "__nv_roundf": "float(float)", + "__nv_sin": "double(double)", + "__nv_sinf": "float(float)", + "__nv_sinh": "double(double)", + "__nv_sinhf": "float(float)", + "__nv_sqrt": "double(double)", + "__nv_sqrtf": "float(float)", + "__nv_tan": "double(double)", + "__nv_tanf": "float(float)", + "__nv_tanh": "double(double)", + "__nv_tanhf": "float(float)", + "__nv_trunc": "double(double)", + "__nv_truncf": "float(float)" + } +} diff --git a/cuda-oxide/intrinsics/generated/libdevice_math.rs b/cuda-oxide/intrinsics/generated/libdevice_math.rs new file mode 100644 index 0000000000..aedf6eebed --- /dev/null +++ b/cuda-oxide/intrinsics/generated/libdevice_math.rs @@ -0,0 +1,742 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// @generated by cuda-intrinsics-gen from overlay/libdevice_math.toml. DO NOT EDIT. +// Staged metadata; compiler dispatch is still handwritten. + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct MathMapping { + pub libm: &'static str, + pub signature: &'static str, + pub placeholders: &'static [&'static str], + pub cmath: Option<&'static str>, + pub libdevice: Option<&'static str>, + pub llvm: Option<&'static str>, + pub lowering: Option<&'static str>, + pub link: &'static str, +} + +pub const MAPPINGS: &[MathMapping] = &[ + MathMapping { + libm: "acos", + signature: "(f64) -> f64", + placeholders: &["acosf64"], + cmath: Some("std::sys::cmath::acos"), + libdevice: Some("__nv_acos"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "acosf", + signature: "(f32) -> f32", + placeholders: &["acosf32"], + cmath: Some("std::sys::cmath::acosf"), + libdevice: Some("__nv_acosf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "acosh", + signature: "(f64) -> f64", + placeholders: &["acoshf64"], + cmath: Some("std::sys::cmath::acosh"), + libdevice: Some("__nv_acosh"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "acoshf", + signature: "(f32) -> f32", + placeholders: &["acoshf32"], + cmath: Some("std::sys::cmath::acoshf"), + libdevice: Some("__nv_acoshf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "asin", + signature: "(f64) -> f64", + placeholders: &["asinf64"], + cmath: Some("std::sys::cmath::asin"), + libdevice: Some("__nv_asin"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "asinf", + signature: "(f32) -> f32", + placeholders: &["asinf32"], + cmath: Some("std::sys::cmath::asinf"), + libdevice: Some("__nv_asinf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "asinh", + signature: "(f64) -> f64", + placeholders: &["asinhf64"], + cmath: Some("std::sys::cmath::asinh"), + libdevice: Some("__nv_asinh"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "asinhf", + signature: "(f32) -> f32", + placeholders: &["asinhf32"], + cmath: Some("std::sys::cmath::asinhf"), + libdevice: Some("__nv_asinhf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "atan", + signature: "(f64) -> f64", + placeholders: &["atanf64"], + cmath: Some("std::sys::cmath::atan"), + libdevice: Some("__nv_atan"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "atan2", + signature: "(f64, f64) -> f64", + placeholders: &["atan2f64"], + cmath: Some("std::sys::cmath::atan2"), + libdevice: Some("__nv_atan2"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "atan2f", + signature: "(f32, f32) -> f32", + placeholders: &["atan2f32"], + cmath: Some("std::sys::cmath::atan2f"), + libdevice: Some("__nv_atan2f"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "atanf", + signature: "(f32) -> f32", + placeholders: &["atanf32"], + cmath: Some("std::sys::cmath::atanf"), + libdevice: Some("__nv_atanf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "atanh", + signature: "(f64) -> f64", + placeholders: &["atanhf64"], + cmath: Some("std::sys::cmath::atanh"), + libdevice: Some("__nv_atanh"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "atanhf", + signature: "(f32) -> f32", + placeholders: &["atanhf32"], + cmath: Some("std::sys::cmath::atanhf"), + libdevice: Some("__nv_atanhf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "cbrt", + signature: "(f64) -> f64", + placeholders: &["cbrtf64"], + cmath: Some("std::sys::cmath::cbrt"), + libdevice: Some("__nv_cbrt"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "cbrtf", + signature: "(f32) -> f32", + placeholders: &["cbrtf32"], + cmath: Some("std::sys::cmath::cbrtf"), + libdevice: Some("__nv_cbrtf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "ceil", + signature: "(f64) -> f64", + placeholders: &["ceilf64"], + cmath: None, + libdevice: Some("__nv_ceil"), + llvm: Some("llvm_ceil_f64"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "ceilf", + signature: "(f32) -> f32", + placeholders: &["ceilf32"], + cmath: None, + libdevice: Some("__nv_ceilf"), + llvm: Some("llvm_ceil_f32"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "copysign", + signature: "(f64, f64) -> f64", + placeholders: &["copysignf64"], + cmath: None, + libdevice: Some("__nv_copysign"), + llvm: Some("llvm_copysign_f64"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "copysignf", + signature: "(f32, f32) -> f32", + placeholders: &["copysignf32"], + cmath: None, + libdevice: Some("__nv_copysignf"), + llvm: Some("llvm_copysign_f32"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "cos", + signature: "(f64) -> f64", + placeholders: &["cosf64"], + cmath: Some("std::sys::cmath::cos"), + libdevice: Some("__nv_cos"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "cosf", + signature: "(f32) -> f32", + placeholders: &["cosf32"], + cmath: Some("std::sys::cmath::cosf"), + libdevice: Some("__nv_cosf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "cosh", + signature: "(f64) -> f64", + placeholders: &["coshf64"], + cmath: Some("std::sys::cmath::cosh"), + libdevice: Some("__nv_cosh"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "coshf", + signature: "(f32) -> f32", + placeholders: &["coshf32"], + cmath: Some("std::sys::cmath::coshf"), + libdevice: Some("__nv_coshf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "exp", + signature: "(f64) -> f64", + placeholders: &["expf64"], + cmath: None, + libdevice: Some("__nv_exp"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "exp2", + signature: "(f64) -> f64", + placeholders: &["exp2f64"], + cmath: None, + libdevice: Some("__nv_exp2"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "exp2f", + signature: "(f32) -> f32", + placeholders: &["exp2f32"], + cmath: None, + libdevice: Some("__nv_exp2f"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "expf", + signature: "(f32) -> f32", + placeholders: &["expf32"], + cmath: None, + libdevice: Some("__nv_expf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "expm1", + signature: "(f64) -> f64", + placeholders: &["expm1f64"], + cmath: Some("std::sys::cmath::expm1"), + libdevice: Some("__nv_expm1"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "expm1f", + signature: "(f32) -> f32", + placeholders: &["expm1f32"], + cmath: Some("std::sys::cmath::expm1f"), + libdevice: Some("__nv_expm1f"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "fabs", + signature: "(f64) -> f64", + placeholders: &["fabs"], + cmath: None, + libdevice: Some("__nv_fabs"), + llvm: Some("llvm_fabs_f64"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "fabsf", + signature: "(f32) -> f32", + placeholders: &["fabs"], + cmath: None, + libdevice: Some("__nv_fabsf"), + llvm: Some("llvm_fabs_f32"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "floor", + signature: "(f64) -> f64", + placeholders: &["floorf64"], + cmath: None, + libdevice: Some("__nv_floor"), + llvm: Some("llvm_floor_f64"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "floorf", + signature: "(f32) -> f32", + placeholders: &["floorf32"], + cmath: None, + libdevice: Some("__nv_floorf"), + llvm: Some("llvm_floor_f32"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "fma", + signature: "(f64, f64, f64) -> f64", + placeholders: &["fmaf64"], + cmath: None, + libdevice: Some("__nv_fma"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "fmaf", + signature: "(f32, f32, f32) -> f32", + placeholders: &["fmaf32"], + cmath: None, + libdevice: Some("__nv_fmaf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "fmax", + signature: "(f64, f64) -> f64", + placeholders: &["maximum_number_nsz_f64"], + cmath: None, + libdevice: None, + llvm: None, + lowering: Some("handwritten:lower_float_minmax_nsz"), + link: "never", + }, + MathMapping { + libm: "fmaxf", + signature: "(f32, f32) -> f32", + placeholders: &["maximum_number_nsz_f32"], + cmath: None, + libdevice: None, + llvm: None, + lowering: Some("handwritten:lower_float_minmax_nsz"), + link: "never", + }, + MathMapping { + libm: "fmin", + signature: "(f64, f64) -> f64", + placeholders: &["minimum_number_nsz_f64"], + cmath: None, + libdevice: None, + llvm: None, + lowering: Some("handwritten:lower_float_minmax_nsz"), + link: "never", + }, + MathMapping { + libm: "fminf", + signature: "(f32, f32) -> f32", + placeholders: &["minimum_number_nsz_f32"], + cmath: None, + libdevice: None, + llvm: None, + lowering: Some("handwritten:lower_float_minmax_nsz"), + link: "never", + }, + MathMapping { + libm: "hypot", + signature: "(f64, f64) -> f64", + placeholders: &["hypotf64"], + cmath: Some("std::sys::cmath::hypot"), + libdevice: Some("__nv_hypot"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "hypotf", + signature: "(f32, f32) -> f32", + placeholders: &["hypotf32"], + cmath: Some("std::sys::cmath::hypotf"), + libdevice: Some("__nv_hypotf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "log", + signature: "(f64) -> f64", + placeholders: &["logf64"], + cmath: None, + libdevice: Some("__nv_log"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "log10", + signature: "(f64) -> f64", + placeholders: &["log10f64"], + cmath: None, + libdevice: Some("__nv_log10"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "log10f", + signature: "(f32) -> f32", + placeholders: &["log10f32"], + cmath: None, + libdevice: Some("__nv_log10f"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "log1p", + signature: "(f64) -> f64", + placeholders: &["log1pf64"], + cmath: Some("std::sys::cmath::log1p"), + libdevice: Some("__nv_log1p"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "log1pf", + signature: "(f32) -> f32", + placeholders: &["log1pf32"], + cmath: Some("std::sys::cmath::log1pf"), + libdevice: Some("__nv_log1pf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "log2", + signature: "(f64) -> f64", + placeholders: &["log2f64"], + cmath: None, + libdevice: Some("__nv_log2"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "log2f", + signature: "(f32) -> f32", + placeholders: &["log2f32"], + cmath: None, + libdevice: Some("__nv_log2f"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "logf", + signature: "(f32) -> f32", + placeholders: &["logf32"], + cmath: None, + libdevice: Some("__nv_logf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "pow", + signature: "(f64, f64) -> f64", + placeholders: &["powf64"], + cmath: None, + libdevice: Some("__nv_pow"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "powf", + signature: "(f32, f32) -> f32", + placeholders: &["powf32"], + cmath: None, + libdevice: Some("__nv_powf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "rint", + signature: "(f64) -> f64", + placeholders: &["round_ties_even_f64"], + cmath: None, + libdevice: Some("__nv_rint"), + llvm: Some("llvm_roundeven_f64"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "rintf", + signature: "(f32) -> f32", + placeholders: &["round_ties_even_f32"], + cmath: None, + libdevice: Some("__nv_rintf"), + llvm: Some("llvm_roundeven_f32"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "round", + signature: "(f64) -> f64", + placeholders: &["roundf64"], + cmath: None, + libdevice: Some("__nv_round"), + llvm: Some("llvm_round_f64"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "roundeven", + signature: "(f64) -> f64", + placeholders: &["round_ties_even_f64"], + cmath: None, + libdevice: Some("__nv_rint"), + llvm: Some("llvm_roundeven_f64"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "roundevenf", + signature: "(f32) -> f32", + placeholders: &["round_ties_even_f32"], + cmath: None, + libdevice: Some("__nv_rintf"), + llvm: Some("llvm_roundeven_f32"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "roundf", + signature: "(f32) -> f32", + placeholders: &["roundf32"], + cmath: None, + libdevice: Some("__nv_roundf"), + llvm: Some("llvm_round_f32"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "sin", + signature: "(f64) -> f64", + placeholders: &["sinf64"], + cmath: Some("std::sys::cmath::sin"), + libdevice: Some("__nv_sin"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "sincos", + signature: "(f64) -> (f64, f64)", + placeholders: &["sinf64", "cosf64"], + cmath: None, + libdevice: None, + llvm: None, + lowering: Some("handwritten:emit_sincos"), + link: "always", + }, + MathMapping { + libm: "sincosf", + signature: "(f32) -> (f32, f32)", + placeholders: &["sinf32", "cosf32"], + cmath: None, + libdevice: None, + llvm: None, + lowering: Some("handwritten:emit_sincos"), + link: "always", + }, + MathMapping { + libm: "sinf", + signature: "(f32) -> f32", + placeholders: &["sinf32"], + cmath: Some("std::sys::cmath::sinf"), + libdevice: Some("__nv_sinf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "sinh", + signature: "(f64) -> f64", + placeholders: &["sinhf64"], + cmath: Some("std::sys::cmath::sinh"), + libdevice: Some("__nv_sinh"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "sinhf", + signature: "(f32) -> f32", + placeholders: &["sinhf32"], + cmath: Some("std::sys::cmath::sinhf"), + libdevice: Some("__nv_sinhf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "sqrt", + signature: "(f64) -> f64", + placeholders: &["sqrtf64"], + cmath: None, + libdevice: Some("__nv_sqrt"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "sqrtf", + signature: "(f32) -> f32", + placeholders: &["sqrtf32"], + cmath: None, + libdevice: Some("__nv_sqrtf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "tan", + signature: "(f64) -> f64", + placeholders: &["tanf64"], + cmath: Some("std::sys::cmath::tan"), + libdevice: Some("__nv_tan"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "tanf", + signature: "(f32) -> f32", + placeholders: &["tanf32"], + cmath: Some("std::sys::cmath::tanf"), + libdevice: Some("__nv_tanf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "tanh", + signature: "(f64) -> f64", + placeholders: &["tanhf64"], + cmath: Some("std::sys::cmath::tanh"), + libdevice: Some("__nv_tanh"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "tanhf", + signature: "(f32) -> f32", + placeholders: &["tanhf32"], + cmath: Some("std::sys::cmath::tanhf"), + libdevice: Some("__nv_tanhf"), + llvm: None, + lowering: None, + link: "always", + }, + MathMapping { + libm: "trunc", + signature: "(f64) -> f64", + placeholders: &["truncf64"], + cmath: None, + libdevice: Some("__nv_trunc"), + llvm: Some("llvm_trunc_f64"), + lowering: None, + link: "libnvvm", + }, + MathMapping { + libm: "truncf", + signature: "(f32) -> f32", + placeholders: &["truncf32"], + cmath: None, + libdevice: Some("__nv_truncf"), + llvm: Some("llvm_trunc_f32"), + lowering: None, + link: "libnvvm", + }, +]; diff --git a/cuda-oxide/intrinsics/libdevice-math.md b/cuda-oxide/intrinsics/libdevice-math.md new file mode 100644 index 0000000000..c6ddf489c9 --- /dev/null +++ b/cuda-oxide/intrinsics/libdevice-math.md @@ -0,0 +1,87 @@ + + + + +# Staged libm mappings + +72 mapped f32/f64 libm exports. Compiler dispatch remains handwritten. + +Libdevice bitcode SHA-256: `91334d6e12748f6cb5bbf0a1cd965a56bcd93dc4f496d2e5c5f8c6e523094356`. + +`libnvvm` links only on that backend; `always` links on both; `never` uses +a handwritten expansion. Aliases are separate exports sharing a mapping. + +| libm export | Rust signature | libdevice symbol | LLVM NVPTX alternative | Handwritten lowering | Linking | +| --- | --- | --- | --- | --- | --- | +| `acos` | `(f64) -> f64` | `__nv_acos` | | | `always` | +| `acosf` | `(f32) -> f32` | `__nv_acosf` | | | `always` | +| `acosh` | `(f64) -> f64` | `__nv_acosh` | | | `always` | +| `acoshf` | `(f32) -> f32` | `__nv_acoshf` | | | `always` | +| `asin` | `(f64) -> f64` | `__nv_asin` | | | `always` | +| `asinf` | `(f32) -> f32` | `__nv_asinf` | | | `always` | +| `asinh` | `(f64) -> f64` | `__nv_asinh` | | | `always` | +| `asinhf` | `(f32) -> f32` | `__nv_asinhf` | | | `always` | +| `atan` | `(f64) -> f64` | `__nv_atan` | | | `always` | +| `atan2` | `(f64, f64) -> f64` | `__nv_atan2` | | | `always` | +| `atan2f` | `(f32, f32) -> f32` | `__nv_atan2f` | | | `always` | +| `atanf` | `(f32) -> f32` | `__nv_atanf` | | | `always` | +| `atanh` | `(f64) -> f64` | `__nv_atanh` | | | `always` | +| `atanhf` | `(f32) -> f32` | `__nv_atanhf` | | | `always` | +| `cbrt` | `(f64) -> f64` | `__nv_cbrt` | | | `always` | +| `cbrtf` | `(f32) -> f32` | `__nv_cbrtf` | | | `always` | +| `ceil` | `(f64) -> f64` | `__nv_ceil` | `llvm_ceil_f64` | | `libnvvm` | +| `ceilf` | `(f32) -> f32` | `__nv_ceilf` | `llvm_ceil_f32` | | `libnvvm` | +| `copysign` | `(f64, f64) -> f64` | `__nv_copysign` | `llvm_copysign_f64` | | `libnvvm` | +| `copysignf` | `(f32, f32) -> f32` | `__nv_copysignf` | `llvm_copysign_f32` | | `libnvvm` | +| `cos` | `(f64) -> f64` | `__nv_cos` | | | `always` | +| `cosf` | `(f32) -> f32` | `__nv_cosf` | | | `always` | +| `cosh` | `(f64) -> f64` | `__nv_cosh` | | | `always` | +| `coshf` | `(f32) -> f32` | `__nv_coshf` | | | `always` | +| `exp` | `(f64) -> f64` | `__nv_exp` | | | `always` | +| `exp2` | `(f64) -> f64` | `__nv_exp2` | | | `always` | +| `exp2f` | `(f32) -> f32` | `__nv_exp2f` | | | `always` | +| `expf` | `(f32) -> f32` | `__nv_expf` | | | `always` | +| `expm1` | `(f64) -> f64` | `__nv_expm1` | | | `always` | +| `expm1f` | `(f32) -> f32` | `__nv_expm1f` | | | `always` | +| `fabs` | `(f64) -> f64` | `__nv_fabs` | `llvm_fabs_f64` | | `libnvvm` | +| `fabsf` | `(f32) -> f32` | `__nv_fabsf` | `llvm_fabs_f32` | | `libnvvm` | +| `floor` | `(f64) -> f64` | `__nv_floor` | `llvm_floor_f64` | | `libnvvm` | +| `floorf` | `(f32) -> f32` | `__nv_floorf` | `llvm_floor_f32` | | `libnvvm` | +| `fma` | `(f64, f64, f64) -> f64` | `__nv_fma` | | | `always` | +| `fmaf` | `(f32, f32, f32) -> f32` | `__nv_fmaf` | | | `always` | +| `fmax` | `(f64, f64) -> f64` | | | `handwritten:lower_float_minmax_nsz` | `never` | +| `fmaxf` | `(f32, f32) -> f32` | | | `handwritten:lower_float_minmax_nsz` | `never` | +| `fmin` | `(f64, f64) -> f64` | | | `handwritten:lower_float_minmax_nsz` | `never` | +| `fminf` | `(f32, f32) -> f32` | | | `handwritten:lower_float_minmax_nsz` | `never` | +| `hypot` | `(f64, f64) -> f64` | `__nv_hypot` | | | `always` | +| `hypotf` | `(f32, f32) -> f32` | `__nv_hypotf` | | | `always` | +| `log` | `(f64) -> f64` | `__nv_log` | | | `always` | +| `log10` | `(f64) -> f64` | `__nv_log10` | | | `always` | +| `log10f` | `(f32) -> f32` | `__nv_log10f` | | | `always` | +| `log1p` | `(f64) -> f64` | `__nv_log1p` | | | `always` | +| `log1pf` | `(f32) -> f32` | `__nv_log1pf` | | | `always` | +| `log2` | `(f64) -> f64` | `__nv_log2` | | | `always` | +| `log2f` | `(f32) -> f32` | `__nv_log2f` | | | `always` | +| `logf` | `(f32) -> f32` | `__nv_logf` | | | `always` | +| `pow` | `(f64, f64) -> f64` | `__nv_pow` | | | `always` | +| `powf` | `(f32, f32) -> f32` | `__nv_powf` | | | `always` | +| `rint` | `(f64) -> f64` | `__nv_rint` | `llvm_roundeven_f64` | | `libnvvm` | +| `rintf` | `(f32) -> f32` | `__nv_rintf` | `llvm_roundeven_f32` | | `libnvvm` | +| `round` | `(f64) -> f64` | `__nv_round` | `llvm_round_f64` | | `libnvvm` | +| `roundeven` | `(f64) -> f64` | `__nv_rint` | `llvm_roundeven_f64` | | `libnvvm` | +| `roundevenf` | `(f32) -> f32` | `__nv_rintf` | `llvm_roundeven_f32` | | `libnvvm` | +| `roundf` | `(f32) -> f32` | `__nv_roundf` | `llvm_round_f32` | | `libnvvm` | +| `sin` | `(f64) -> f64` | `__nv_sin` | | | `always` | +| `sincos` | `(f64) -> (f64, f64)` | | | `handwritten:emit_sincos` | `always` | +| `sincosf` | `(f32) -> (f32, f32)` | | | `handwritten:emit_sincos` | `always` | +| `sinf` | `(f32) -> f32` | `__nv_sinf` | | | `always` | +| `sinh` | `(f64) -> f64` | `__nv_sinh` | | | `always` | +| `sinhf` | `(f32) -> f32` | `__nv_sinhf` | | | `always` | +| `sqrt` | `(f64) -> f64` | `__nv_sqrt` | | | `always` | +| `sqrtf` | `(f32) -> f32` | `__nv_sqrtf` | | | `always` | +| `tan` | `(f64) -> f64` | `__nv_tan` | | | `always` | +| `tanf` | `(f32) -> f32` | `__nv_tanf` | | | `always` | +| `tanh` | `(f64) -> f64` | `__nv_tanh` | | | `always` | +| `tanhf` | `(f32) -> f32` | `__nv_tanhf` | | | `always` | +| `trunc` | `(f64) -> f64` | `__nv_trunc` | `llvm_trunc_f64` | | `libnvvm` | +| `truncf` | `(f32) -> f32` | `__nv_truncf` | `llvm_trunc_f32` | | `libnvvm` | diff --git a/cuda-oxide/intrinsics/overlay/libdevice_math.toml b/cuda-oxide/intrinsics/overlay/libdevice_math.toml new file mode 100644 index 0000000000..ceb976e8d4 --- /dev/null +++ b/cuda-oxide/intrinsics/overlay/libdevice_math.toml @@ -0,0 +1,194 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Separate libdevice evidence domain; not part of the hardware ABI shard index. +# PR3 stages the existing 72 libm exports. Compiler tables are migrated later. +schema = 1 +family = "libdevice_math" + +[libdevice_math] +evidence = "evidence/libdevice_math/cuda-13.0.json" +libdevice_sha256 = "91334d6e12748f6cb5bbf0a1cd965a56bcd93dc4f496d2e5c5f8c6e523094356" +widths = ["f32", "f64"] + +[[libdevice_math.function]] +name = "acos" +signature = "(F) -> F" +cmath = "acos{suffix}" + +[[libdevice_math.function]] +name = "acosh" +signature = "(F) -> F" +cmath = "acosh{suffix}" + +[[libdevice_math.function]] +name = "asin" +signature = "(F) -> F" +cmath = "asin{suffix}" + +[[libdevice_math.function]] +name = "asinh" +signature = "(F) -> F" +cmath = "asinh{suffix}" + +[[libdevice_math.function]] +name = "atan" +signature = "(F) -> F" +cmath = "atan{suffix}" + +[[libdevice_math.function]] +name = "atan2" +signature = "(F, F) -> F" +cmath = "atan2{suffix}" + +[[libdevice_math.function]] +name = "atanh" +signature = "(F) -> F" +cmath = "atanh{suffix}" + +[[libdevice_math.function]] +name = "cbrt" +signature = "(F) -> F" +cmath = "cbrt{suffix}" + +[[libdevice_math.function]] +name = "ceil" +signature = "(F) -> F" +llvm = "llvm_ceil_{width}" +link = "libnvvm" + +[[libdevice_math.function]] +name = "copysign" +signature = "(F, F) -> F" +llvm = "llvm_copysign_{width}" +link = "libnvvm" + +[[libdevice_math.function]] +name = "cos" +signature = "(F) -> F" +cmath = "cos{suffix}" + +[[libdevice_math.function]] +name = "cosh" +signature = "(F) -> F" +cmath = "cosh{suffix}" + +[[libdevice_math.function]] +name = "exp" +signature = "(F) -> F" + +[[libdevice_math.function]] +name = "exp2" +signature = "(F) -> F" + +[[libdevice_math.function]] +name = "expm1" +signature = "(F) -> F" +cmath = "expm1{suffix}" + +[[libdevice_math.function]] +name = "fabs" +signature = "(F) -> F" +placeholders = ["fabs"] +llvm = "llvm_fabs_{width}" +link = "libnvvm" + +[[libdevice_math.function]] +name = "floor" +signature = "(F) -> F" +llvm = "llvm_floor_{width}" +link = "libnvvm" + +[[libdevice_math.function]] +name = "fma" +signature = "(F, F, F) -> F" + +[[libdevice_math.function]] +name = "fmax" +signature = "(F, F) -> F" +placeholders = ["maximum_number_nsz_{width}"] +lowering = "handwritten:lower_float_minmax_nsz" +link = "never" + +[[libdevice_math.function]] +name = "fmin" +signature = "(F, F) -> F" +placeholders = ["minimum_number_nsz_{width}"] +lowering = "handwritten:lower_float_minmax_nsz" +link = "never" + +[[libdevice_math.function]] +name = "hypot" +signature = "(F, F) -> F" +cmath = "hypot{suffix}" + +[[libdevice_math.function]] +name = "log" +signature = "(F) -> F" + +[[libdevice_math.function]] +name = "log10" +signature = "(F) -> F" + +[[libdevice_math.function]] +name = "log1p" +signature = "(F) -> F" +cmath = "log1p{suffix}" + +[[libdevice_math.function]] +name = "log2" +signature = "(F) -> F" + +[[libdevice_math.function]] +name = "pow" +signature = "(F, F) -> F" + +[[libdevice_math.function]] +name = "rint" +signature = "(F) -> F" +libm = ["rint", "roundeven"] +placeholders = ["round_ties_even_{width}"] +llvm = "llvm_roundeven_{width}" +link = "libnvvm" + +[[libdevice_math.function]] +name = "round" +signature = "(F) -> F" +llvm = "llvm_round_{width}" +link = "libnvvm" + +[[libdevice_math.function]] +name = "sin" +signature = "(F) -> F" +cmath = "sin{suffix}" + +[[libdevice_math.function]] +name = "sincos" +signature = "(F) -> (F, F)" +placeholders = ["sin{width}", "cos{width}"] +lowering = "handwritten:emit_sincos" + +[[libdevice_math.function]] +name = "sinh" +signature = "(F) -> F" +cmath = "sinh{suffix}" + +[[libdevice_math.function]] +name = "sqrt" +signature = "(F) -> F" + +[[libdevice_math.function]] +name = "tan" +signature = "(F) -> F" +cmath = "tan{suffix}" + +[[libdevice_math.function]] +name = "tanh" +signature = "(F) -> F" +cmath = "tanh{suffix}" + +[[libdevice_math.function]] +name = "trunc" +signature = "(F) -> F" +llvm = "llvm_trunc_{width}" +link = "libnvvm"