pub const MODULE: &str = "wasi:cuda/host@0.2.0";
pub const FN_LOAD_PTX: &str = "wasi_cuda_load_ptx";
pub const FN_LAUNCH: &str = "wasi_cuda_launch";
pub const FN_SYNC: &str = "wasi_cuda_sync";
pub const FN_LAST_ERROR_PTR: &str = "wasi_cuda_last_error_ptr";
pub const FN_LAST_ERROR_LEN: &str = "wasi_cuda_last_error_len";
pub const FN_LAST_ERROR_COPY: &str = "wasi_cuda_last_error_copy";
pub const FN_ALLOC: &str = "wasi_cuda_alloc";
pub const FN_FREE: &str = "wasi_cuda_free";
pub const FN_MEMCPY_H2D: &str = "wasi_cuda_memcpy_h2d";
pub const FN_MEMCPY_D2H: &str = "wasi_cuda_memcpy_d2h";
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AbiError {
NotAvailable = -1,
InvalidPointer = -2,
InvalidKernel = -3,
MalformedPtx = -4,
LaunchFailed = -5,
QuotaExceeded = -6,
Internal = -7,
InvalidDimensions = -8,
InvalidArgs = -9,
KernelArgsUnsupported = -10,
InvalidHandle = -11,
}
impl AbiError {
pub const fn code(self) -> i32 {
self as i32
}
pub fn name(self) -> &'static str {
match self {
AbiError::NotAvailable => "not_available",
AbiError::InvalidPointer => "invalid_pointer",
AbiError::InvalidKernel => "invalid_kernel",
AbiError::MalformedPtx => "malformed_ptx",
AbiError::LaunchFailed => "launch_failed",
AbiError::QuotaExceeded => "quota_exceeded",
AbiError::Internal => "internal",
AbiError::InvalidDimensions => "invalid_dimensions",
AbiError::InvalidArgs => "invalid_args",
AbiError::KernelArgsUnsupported => "kernel_args_unsupported",
AbiError::InvalidHandle => "invalid_handle",
}
}
}
pub const MAX_PTX_BYTES: usize = 8 * 1024 * 1024;
pub const MAX_KERNELS_PER_INSTANCE: usize = 256;
pub const MAX_THREADS_PER_BLOCK: u32 = 1024;
pub const MAX_BLOCK_DIM: u32 = 1024;
pub const MAX_GRID_DIM: u32 = i32::MAX as u32;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn abi_error_codes_stable() {
assert_eq!(AbiError::NotAvailable.code(), -1);
assert_eq!(AbiError::InvalidPointer.code(), -2);
assert_eq!(AbiError::InvalidKernel.code(), -3);
assert_eq!(AbiError::MalformedPtx.code(), -4);
assert_eq!(AbiError::LaunchFailed.code(), -5);
assert_eq!(AbiError::QuotaExceeded.code(), -6);
assert_eq!(AbiError::Internal.code(), -7);
assert_eq!(AbiError::InvalidDimensions.code(), -8);
assert_eq!(AbiError::InvalidArgs.code(), -9);
assert_eq!(AbiError::KernelArgsUnsupported.code(), -10);
assert_eq!(AbiError::InvalidHandle.code(), -11);
}
#[test]
fn abi_error_names_stable() {
assert_eq!(AbiError::NotAvailable.name(), "not_available");
assert_eq!(AbiError::MalformedPtx.name(), "malformed_ptx");
assert_eq!(AbiError::InvalidDimensions.name(), "invalid_dimensions");
assert_eq!(AbiError::InvalidArgs.name(), "invalid_args");
assert_eq!(
AbiError::KernelArgsUnsupported.name(),
"kernel_args_unsupported"
);
assert_eq!(AbiError::InvalidHandle.name(), "invalid_handle");
}
#[test]
fn function_names_carry_wasi_cuda_prefix() {
for name in [
FN_LOAD_PTX,
FN_LAUNCH,
FN_SYNC,
FN_LAST_ERROR_PTR,
FN_LAST_ERROR_LEN,
FN_LAST_ERROR_COPY,
FN_ALLOC,
FN_FREE,
FN_MEMCPY_H2D,
FN_MEMCPY_D2H,
] {
assert!(name.starts_with("wasi_cuda_"));
}
}
#[test]
fn module_string_is_versioned() {
assert!(MODULE.contains("wasi:cuda"));
assert!(MODULE.ends_with("@0.2.0"));
}
#[test]
fn module_version_matches_wit_package_decl() {
const WIT: &str = include_str!("../wit/wasi-cuda.wit");
let pkg_line = WIT
.lines()
.find(|l| l.trim_start().starts_with("package wasi:cuda@"))
.expect("wit/wasi-cuda.wit must declare `package wasi:cuda@x.y.z;`");
let version = pkg_line
.trim()
.trim_start_matches("package wasi:cuda@")
.trim_end_matches(';')
.trim();
assert!(
!version.is_empty(),
"could not parse a version out of: {pkg_line:?}"
);
let expected_suffix = format!("@{version}");
assert!(
MODULE.ends_with(&expected_suffix),
"MODULE ({MODULE:?}) drifted from wit/wasi-cuda.wit \
package version ({version:?}); update src/abi.rs::MODULE \
or the WIT file so they agree."
);
}
#[test]
fn wit_file_has_no_merge_conflict_markers() {
const WIT: &str = include_str!("../wit/wasi-cuda.wit");
let ours = "<".repeat(7);
let sep = "=".repeat(7);
let theirs = ">".repeat(7);
for (label, marker) in [
("ours (<<<<<<<)", ours.as_str()),
("separator (=======)", sep.as_str()),
("theirs (>>>>>>>)", theirs.as_str()),
] {
for (lineno, line) in WIT.lines().enumerate() {
assert!(
!line.contains(marker),
"wit/wasi-cuda.wit contains an unresolved git merge \
conflict marker {label} on line {} — resolve it before \
publishing or downstream `wit-bindgen` consumers will \
fail to parse the contract. Offending line: {line:?}",
lineno + 1,
);
}
}
}
#[test]
fn cuda_wit_mirror_interface_body_matches() {
use std::path::Path;
fn interface_host_body(src: &str) -> String {
let start = src
.find("interface host {")
.expect("WIT must declare `interface host {`");
let rest = &src[start..];
let mut depth = 0usize;
let mut end = None;
for (idx, ch) in rest.char_indices() {
match ch {
'{' => depth += 1,
'}' => {
depth -= 1;
if depth == 0 {
end = Some(idx + 1);
break;
}
}
_ => {}
}
}
let end = end.expect("interface host block must be brace-balanced");
rest[..end].trim().to_string()
}
let manifest = env!("CARGO_MANIFEST_DIR");
let in_crate_path = Path::new(manifest).join("wit").join("wasi-cuda.wit");
let root_path = Path::new(manifest)
.join("..")
.join("..")
.join("wit")
.join("wasi-cuda.wit");
let in_crate = std::fs::read_to_string(&in_crate_path)
.unwrap_or_else(|e| panic!("read {in_crate_path:?}: {e}"));
let root = std::fs::read_to_string(&root_path)
.unwrap_or_else(|e| panic!("read {root_path:?}: {e}"));
assert_eq!(
interface_host_body(&in_crate),
interface_host_body(&root),
"wasi-cuda.wit `interface host` body drifted between the in-crate \
authoritative copy ({in_crate_path:?}) and the workspace-root \
mirror ({root_path:?}); update both so guests bind one contract."
);
}
#[test]
fn dimension_caps_are_plausible() {
assert_eq!(MAX_THREADS_PER_BLOCK, 1024);
assert_eq!(MAX_BLOCK_DIM, 1024);
assert_eq!(MAX_GRID_DIM, i32::MAX as u32);
}
}