use std::fmt::Write;
use thiserror::Error;
use crate::ir::{ElemType, TensorWasmKernelBlueprint, TensorWasmOp};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RegClass {
F32,
S32,
}
impl RegClass {
fn for_elem(elem: ElemType) -> Self {
if elem.is_float() {
RegClass::F32
} else {
RegClass::S32
}
}
fn prefix(self) -> char {
match self {
RegClass::F32 => 'f',
RegClass::S32 => 'r',
}
}
}
fn add_mnemonic(elem: ElemType) -> Result<&'static str, EmitError> {
match elem {
ElemType::F32 => Ok("add.f32"),
ElemType::I32 => Ok("add.s32"),
_ => Err(EmitError::NotYetImplemented(
"PTX add for this SIMD element width is not yet emittable",
)),
}
}
fn mul_mnemonic(elem: ElemType) -> Result<&'static str, EmitError> {
match elem {
ElemType::F32 => Ok("mul.f32"),
ElemType::I32 => Ok("mul.lo.s32"),
_ => Err(EmitError::NotYetImplemented(
"PTX mul for this SIMD element width is not yet emittable",
)),
}
}
fn fma_mnemonic(elem: ElemType) -> Result<&'static str, EmitError> {
match elem {
ElemType::F32 => Ok("fma.rn.f32"),
_ => Err(EmitError::NotYetImplemented(
"PTX fma for this SIMD element width is not yet emittable",
)),
}
}
fn load_store_type(elem: ElemType) -> &'static str {
if elem.is_float() {
"f32"
} else {
"u32"
}
}
#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum EmitError {
#[error("PTX emission not yet implemented: {0}")]
NotYetImplemented(&'static str),
#[error("invalid PTX entry-name: {entry}")]
InvalidEntryName {
entry: String,
},
#[error("PTX register allocation overflowed the u32 index space")]
TooManyRegisters,
#[error("PTX emission budget exceeded: {what}")]
EmissionBudgetExceeded {
what: &'static str,
},
}
pub const MAX_PTX_IDENTIFIER_LEN: usize = 1024;
#[must_use]
pub fn is_valid_ptx_identifier(s: &str) -> bool {
if s.is_empty() || s.len() > MAX_PTX_IDENTIFIER_LEN {
return false;
}
let mut bytes = s.bytes();
let first = bytes.next().unwrap();
if !(first.is_ascii_alphabetic() || first == b'_') {
return false;
}
bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'$')
}
pub const WMMA_OPERAND_FRAG_REGS: u32 = 8;
pub const WMMA_ACC_FRAG_REGS: u32 = 8;
pub const WMMA_TILE_DIM: u32 = 16;
pub const MAX_LANES: u32 = 1024;
pub const MAX_EMITTED_OPS: u64 = 1 << 20;
pub const DEFAULT_TARGET: &str = "sm_80";
pub const DEFAULT_PTX_VERSION: &str = "8.0";
#[derive(Debug, Clone)]
pub struct EmitConfig {
pub target: String,
pub ptx_version: String,
pub launch_bounds: bool,
pub enable_experimental_matmul: bool,
}
impl Default for EmitConfig {
fn default() -> Self {
Self {
target: DEFAULT_TARGET.to_string(),
ptx_version: DEFAULT_PTX_VERSION.to_string(),
launch_bounds: true,
enable_experimental_matmul: false,
}
}
}
#[derive(Debug, Clone)]
pub struct EmittedPtx {
pub text: String,
pub launch_geometry: (u32, u32),
}
impl EmittedPtx {
pub fn len(&self) -> usize {
self.text.len()
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
}
pub fn emit(blueprint: &TensorWasmKernelBlueprint) -> Result<EmittedPtx, EmitError> {
emit_with(blueprint, &EmitConfig::default())
}
fn lower_body(
blueprint: &TensorWasmKernelBlueprint,
cfg: &EmitConfig,
) -> Result<(String, u32, u32, u32, u32, u32), EmitError> {
let mut body = String::with_capacity(blueprint.ops.len() * 80);
let mut value_stack: Vec<u32> = Vec::with_capacity(blueprint.ops.len());
let mut next_f = 0u32;
let mut next_r = 1u32;
let mut max_s64 = 2u32; let max_pred = 2u32;
let mut max_b32 = 0u32;
let mut in_off: u32 = 0;
let mut out_off: u32 = 0;
let alloc = |class: RegClass, next_f: &mut u32, next_r: &mut u32| -> Result<u32, EmitError> {
let counter = match class {
RegClass::F32 => next_f,
RegClass::S32 => next_r,
};
let r = *counter;
*counter = counter.checked_add(1).ok_or(EmitError::TooManyRegisters)?;
Ok(r)
};
let pop_or_alloc = |value_stack: &mut Vec<u32>,
class: RegClass,
next_f: &mut u32,
next_r: &mut u32|
-> Result<u32, EmitError> {
match value_stack.pop() {
Some(idx) => Ok(idx),
None => alloc(class, next_f, next_r),
}
};
for op in &blueprint.ops {
match op {
TensorWasmOp::VecAdd { elem, lanes } => {
let class = RegClass::for_elem(*elem);
let _ = writeln!(body, " // vec_add.{elem}[{lanes}] lanes");
let mnemonic = add_mnemonic(*elem)?;
let p = class.prefix();
for _ in 0..*lanes {
let b = pop_or_alloc(&mut value_stack, class, &mut next_f, &mut next_r)?;
let a = pop_or_alloc(&mut value_stack, class, &mut next_f, &mut next_r)?;
let dst = alloc(class, &mut next_f, &mut next_r)?;
let _ = writeln!(body, " {mnemonic} %{p}{dst}, %{p}{a}, %{p}{b};");
value_stack.push(dst);
}
}
TensorWasmOp::VecMul { elem, lanes } => {
let class = RegClass::for_elem(*elem);
let _ = writeln!(body, " // vec_mul.{elem}[{lanes}] lanes");
let mnemonic = mul_mnemonic(*elem)?;
let p = class.prefix();
for _ in 0..*lanes {
let b = pop_or_alloc(&mut value_stack, class, &mut next_f, &mut next_r)?;
let a = pop_or_alloc(&mut value_stack, class, &mut next_f, &mut next_r)?;
let dst = alloc(class, &mut next_f, &mut next_r)?;
let _ = writeln!(body, " {mnemonic} %{p}{dst}, %{p}{a}, %{p}{b};");
value_stack.push(dst);
}
}
TensorWasmOp::VecFma { elem, lanes } => {
if !elem.is_float() {
return Err(EmitError::NotYetImplemented(
"integer VecFma has no single-rounding PTX form",
));
}
let class = RegClass::for_elem(*elem);
let _ = writeln!(body, " // vec_fma.{elem}[{lanes}] lanes");
let mnemonic = fma_mnemonic(*elem)?;
let p = class.prefix();
for _ in 0..*lanes {
let c = pop_or_alloc(&mut value_stack, class, &mut next_f, &mut next_r)?;
let b = pop_or_alloc(&mut value_stack, class, &mut next_f, &mut next_r)?;
let a = pop_or_alloc(&mut value_stack, class, &mut next_f, &mut next_r)?;
let dst = alloc(class, &mut next_f, &mut next_r)?;
let _ = writeln!(body, " {mnemonic} %{p}{dst}, %{p}{a}, %{p}{b}, %{p}{c};");
value_stack.push(dst);
}
}
TensorWasmOp::MatMul { m, n, k } => {
if !cfg.enable_experimental_matmul {
return Err(EmitError::NotYetImplemented(
"MatMul lowering deferred to v0.4",
));
}
if (*m, *n, *k) != (WMMA_TILE_DIM, WMMA_TILE_DIM, WMMA_TILE_DIM) {
return Err(EmitError::NotYetImplemented(
"experimental wmma lowering only models m16n16k16",
));
}
lower_wmma_m16n16k16(&mut body, &mut next_f, &mut max_b32, &mut max_s64)?;
}
TensorWasmOp::LoadUnified { elem, lanes } => {
let class = RegClass::for_elem(*elem);
let p = class.prefix();
let ld_ty = load_store_type(*elem);
let width = elem.byte_width();
let _ = writeln!(
body,
" // load_unified.{elem}[{lanes}] lanes (.lu cache hint) from %rd0+{in_off}"
);
for _ in 0..*lanes {
let dst = alloc(class, &mut next_f, &mut next_r)?;
if in_off == 0 {
let _ = writeln!(body, " ld.global.lu.{ld_ty} %{p}{dst}, [%rd0];");
} else {
let _ =
writeln!(body, " ld.global.lu.{ld_ty} %{p}{dst}, [%rd0+{in_off}];");
}
in_off = in_off
.checked_add(width)
.ok_or(EmitError::TooManyRegisters)?;
value_stack.push(dst);
}
}
TensorWasmOp::StoreUnified { elem, lanes } => {
let class = RegClass::for_elem(*elem);
let p = class.prefix();
let ld_ty = load_store_type(*elem);
let width = elem.byte_width();
let _ = writeln!(
body,
" // store_unified.{elem}[{lanes}] lanes (.cs cache hint) to %rd1+{out_off}"
);
for _ in 0..*lanes {
let src = pop_or_alloc(&mut value_stack, class, &mut next_f, &mut next_r)?;
if out_off == 0 {
let _ = writeln!(body, " st.global.cs.{ld_ty} [%rd1], %{p}{src};");
} else {
let _ = writeln!(
body,
" st.global.cs.{ld_ty} [%rd1+{out_off}], %{p}{src};"
);
}
out_off = out_off
.checked_add(width)
.ok_or(EmitError::TooManyRegisters)?;
}
}
TensorWasmOp::Barrier => {
let _ = writeln!(body, " bar.sync 0;");
}
}
}
let max_f = next_f.max(1);
let max_s32 = next_r.max(1);
Ok((body, max_f, max_s32, max_s64, max_pred, max_b32))
}
fn lower_wmma_m16n16k16(
body: &mut String,
next_f: &mut u32,
max_b32: &mut u32,
max_s64: &mut u32,
) -> Result<(), EmitError> {
let a_frag_base = *max_b32;
*max_b32 = max_b32
.checked_add(WMMA_OPERAND_FRAG_REGS)
.ok_or(EmitError::TooManyRegisters)?;
let b_frag_base = *max_b32;
*max_b32 = max_b32
.checked_add(WMMA_OPERAND_FRAG_REGS)
.ok_or(EmitError::TooManyRegisters)?;
let acc_frag_base = *next_f;
*next_f = next_f
.checked_add(WMMA_ACC_FRAG_REGS)
.ok_or(EmitError::TooManyRegisters)?;
let b_base = *max_s64; let c_base = b_base + 1; *max_s64 = c_base.checked_add(1).ok_or(EmitError::TooManyRegisters)?;
let stride = WMMA_TILE_DIM;
const A_TILE_BYTES: u32 = WMMA_TILE_DIM * WMMA_TILE_DIM * 2; const B_TILE_BYTES: u32 = WMMA_TILE_DIM * WMMA_TILE_DIM * 2; let b_tile_off = A_TILE_BYTES;
let c_tile_off = A_TILE_BYTES + B_TILE_BYTES;
let _ = writeln!(
body,
" // EXPERIMENTAL / UNVERIFIED ON HARDWARE: wmma m16n16k16 \
(row.col, f16xf16->f32)"
);
let frag_list = |base: u32, count: u32, prefix: &str| -> String {
let regs: Vec<String> = (0..count)
.map(|i| format!("%{prefix}{}", base + i))
.collect();
format!("{{{}}}", regs.join(", "))
};
let a_list = frag_list(a_frag_base, WMMA_OPERAND_FRAG_REGS, "rb");
let b_list = frag_list(b_frag_base, WMMA_OPERAND_FRAG_REGS, "rb");
let acc_list = frag_list(acc_frag_base, WMMA_ACC_FRAG_REGS, "f");
let _ = writeln!(body, " add.s64 %rd{b_base}, %rd0, {b_tile_off};");
let _ = writeln!(body, " add.s64 %rd{c_base}, %rd0, {c_tile_off};");
let _ = writeln!(
body,
" wmma.load.a.sync.aligned.row.m16n16k16.global.f16 {a_list}, [%rd0], {stride};"
);
let _ = writeln!(
body,
" wmma.load.b.sync.aligned.col.m16n16k16.global.f16 {b_list}, [%rd{b_base}], {stride};"
);
let _ = writeln!(
body,
" wmma.load.c.sync.aligned.row.m16n16k16.global.f32 {acc_list}, [%rd{c_base}], {stride};"
);
let _ = writeln!(
body,
" wmma.mma.sync.aligned.row.col.m16n16k16.f32.f32 \
{acc_list}, {a_list}, {b_list}, {acc_list};"
);
let _ = writeln!(
body,
" wmma.store.d.sync.aligned.row.m16n16k16.global.f32 [%rd1], {acc_list}, {stride};"
);
Ok(())
}
pub fn emit_with(
blueprint: &TensorWasmKernelBlueprint,
cfg: &EmitConfig,
) -> Result<EmittedPtx, EmitError> {
if !is_valid_ptx_identifier(&blueprint.entry) {
return Err(EmitError::InvalidEntryName {
entry: blueprint.entry.clone(),
});
}
let mut emitted_budget: u64 = 0;
for op in &blueprint.ops {
let lanes = match op {
TensorWasmOp::VecAdd { lanes, .. }
| TensorWasmOp::VecMul { lanes, .. }
| TensorWasmOp::VecFma { lanes, .. }
| TensorWasmOp::LoadUnified { lanes, .. }
| TensorWasmOp::StoreUnified { lanes, .. } => *lanes,
TensorWasmOp::MatMul { .. } | TensorWasmOp::Barrier => 0,
};
if lanes > MAX_LANES {
return Err(EmitError::EmissionBudgetExceeded {
what: "single op `lanes` exceeds MAX_LANES",
});
}
emitted_budget = emitted_budget.saturating_add(u64::from(lanes));
if emitted_budget > MAX_EMITTED_OPS {
return Err(EmitError::EmissionBudgetExceeded {
what: "aggregate ops * lanes exceeds MAX_EMITTED_OPS",
});
}
}
let mut text = String::with_capacity(blueprint.ops.len().saturating_mul(64));
let _ = writeln!(text, "//");
let _ = writeln!(text, "// Auto-emitted by tensor-wasm-jit::ptx_emit");
let _ = writeln!(text, "// entry: {}", blueprint.entry);
let _ = writeln!(text, "// ops: {}", blueprint.ops.len());
let _ = writeln!(text, "//");
let _ = writeln!(text);
let _ = writeln!(text, ".version {}", cfg.ptx_version);
let _ = writeln!(text, ".target {}", cfg.target);
let _ = writeln!(text, ".address_size 64");
let _ = writeln!(text);
let (grid_size, block_size) = blueprint.grid_hint.launch_geometry();
let (body, max_f, max_s32, max_s64, max_pred, max_b32) = lower_body(blueprint, cfg)?;
let _ = writeln!(text, ".visible .entry {}(", blueprint.entry);
let _ = writeln!(text, " .param .u64 {}_param_in_ptr,", blueprint.entry);
let _ = writeln!(text, " .param .u64 {}_param_out_ptr,", blueprint.entry);
let _ = writeln!(text, " .param .u32 {}_param_n", blueprint.entry);
let _ = writeln!(text, ")");
if cfg.launch_bounds {
let _ = writeln!(text, ".maxntid {block_size}, 1, 1");
}
let _ = writeln!(text, "{{");
let _ = writeln!(text, " .reg .pred %p<{max_pred}>;");
let _ = writeln!(text, " .reg .s32 %r<{max_s32}>;");
let _ = writeln!(text, " .reg .s64 %rd<{max_s64}>;");
let _ = writeln!(text, " .reg .f32 %f<{max_f}>;");
if max_b32 > 0 {
let _ = writeln!(text, " .reg .b32 %rb<{max_b32}>;");
}
let _ = writeln!(text);
let _ = writeln!(
text,
" ld.param.u64 %rd0, [{entry}_param_in_ptr];",
entry = blueprint.entry,
);
let _ = writeln!(
text,
" ld.param.u64 %rd1, [{entry}_param_out_ptr];",
entry = blueprint.entry,
);
let _ = writeln!(
text,
" ld.param.u32 %r0, [{entry}_param_n];",
entry = blueprint.entry,
);
let _ = writeln!(text);
text.push_str(&body);
let _ = writeln!(text);
let _ = writeln!(text, " ret;");
let _ = writeln!(text, "}}");
Ok(EmittedPtx {
text,
launch_geometry: (grid_size, block_size),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{GridHint, TensorWasmKernelBlueprint, TensorWasmOp};
#[test]
fn vector_add_emits_add_f32() {
let bp = TensorWasmKernelBlueprint::new("vector_add")
.push(TensorWasmOp::LoadUnified {
elem: ElemType::F32,
lanes: 4,
})
.push(TensorWasmOp::LoadUnified {
elem: ElemType::F32,
lanes: 4,
})
.push(TensorWasmOp::VecAdd {
elem: ElemType::F32,
lanes: 4,
})
.push(TensorWasmOp::StoreUnified {
elem: ElemType::F32,
lanes: 4,
});
let out = emit(&bp).expect("emit");
assert!(out.text.contains(".target sm_80"));
assert!(out.text.contains(".visible .entry vector_add"));
assert!(out.text.contains("add.f32"));
assert!(out.text.contains("ld.global.lu.f32"));
assert!(out.text.contains("st.global.cs.f32"));
assert!(out.text.contains("ret;"));
}
#[test]
fn i32x4_add_emits_integer_ops_not_float() {
let bp = TensorWasmKernelBlueprint::new("int_add")
.push(TensorWasmOp::LoadUnified {
elem: ElemType::I32,
lanes: 4,
})
.push(TensorWasmOp::LoadUnified {
elem: ElemType::I32,
lanes: 4,
})
.push(TensorWasmOp::VecAdd {
elem: ElemType::I32,
lanes: 4,
})
.push(TensorWasmOp::StoreUnified {
elem: ElemType::I32,
lanes: 4,
});
let out = emit(&bp).expect("i32x4 must emit");
assert!(
out.text.contains("add.s32"),
"expected integer add, got:\n{}",
out.text
);
assert!(
!out.text.contains("add.f32"),
"i32x4.add must not lower to a float kernel:\n{}",
out.text
);
assert!(out.text.contains("ld.global.lu.u32"));
assert!(out.text.contains("st.global.cs.u32"));
assert!(out.text.contains("%r1"));
}
#[test]
fn i32x4_mul_emits_integer_mul() {
let bp = TensorWasmKernelBlueprint::new("int_mul").push(TensorWasmOp::VecMul {
elem: ElemType::I32,
lanes: 4,
});
let out = emit(&bp).expect("i32x4 mul must emit");
assert!(out.text.contains("mul.lo.s32"));
assert!(!out.text.contains("mul.f32"));
}
#[test]
fn unsupported_element_width_refused_not_miscompiled() {
let bp = TensorWasmKernelBlueprint::new("f64_add").push(TensorWasmOp::VecAdd {
elem: ElemType::F64,
lanes: 2,
});
assert!(matches!(emit(&bp), Err(EmitError::NotYetImplemented(_))));
}
#[test]
fn matmul_emission_is_not_yet_implemented() {
let bp = TensorWasmKernelBlueprint::new("matmul_16x16x16").push(TensorWasmOp::MatMul {
m: 16,
n: 16,
k: 16,
});
let err = emit(&bp).expect_err("MatMul emission must fail until v0.4");
assert!(matches!(err, EmitError::NotYetImplemented(_)));
}
#[test]
fn matmul_refused_under_default_config() {
let bp = TensorWasmKernelBlueprint::new("matmul_16x16x16").push(TensorWasmOp::MatMul {
m: 16,
n: 16,
k: 16,
});
assert!(!EmitConfig::default().enable_experimental_matmul);
let err = emit_with(&bp, &EmitConfig::default())
.expect_err("MatMul must be refused with the flag off");
assert!(matches!(err, EmitError::NotYetImplemented(_)));
assert!(matches!(emit(&bp), Err(EmitError::NotYetImplemented(_))));
}
#[test]
fn matmul_opt_in_emits_wmma_sequence() {
let bp = TensorWasmKernelBlueprint::new("matmul").push(TensorWasmOp::MatMul {
m: 16,
n: 16,
k: 16,
});
let cfg = EmitConfig {
enable_experimental_matmul: true,
..EmitConfig::default()
};
let out = emit_with(&bp, &cfg).expect("opt-in emit must succeed");
assert!(out.text.contains("wmma.load.a.sync.aligned.row.m16n16k16"));
assert!(out.text.contains("wmma.load.b.sync.aligned.col.m16n16k16"));
assert!(out.text.contains("wmma.load.c.sync.aligned.row.m16n16k16"));
assert_eq!(out.text.matches("wmma.mma.sync.aligned").count(), 1);
assert!(out.text.contains("wmma.store.d.sync.aligned.row.m16n16k16"));
assert!(out.text.contains(".reg .b32 %rb<"));
assert!(out.text.contains("EXPERIMENTAL / UNVERIFIED ON HARDWARE"));
}
#[test]
fn matmul_opt_in_rejects_non_16_shape() {
let bp = TensorWasmKernelBlueprint::new("matmul").push(TensorWasmOp::MatMul {
m: 32,
n: 32,
k: 32,
});
let cfg = EmitConfig {
enable_experimental_matmul: true,
..EmitConfig::default()
};
assert!(matches!(
emit_with(&bp, &cfg),
Err(EmitError::NotYetImplemented(_))
));
}
#[test]
fn no_wmma_reg_decl_without_matmul() {
let bp = TensorWasmKernelBlueprint::new("vector_add")
.push(TensorWasmOp::LoadUnified {
elem: ElemType::F32,
lanes: 4,
})
.push(TensorWasmOp::LoadUnified {
elem: ElemType::F32,
lanes: 4,
})
.push(TensorWasmOp::VecAdd {
elem: ElemType::F32,
lanes: 4,
})
.push(TensorWasmOp::StoreUnified {
elem: ElemType::F32,
lanes: 4,
});
let out = emit(&bp).expect("emit");
assert!(!out.text.contains("%rb<"));
assert!(!out.text.contains("wmma."));
}
#[test]
fn matmul_in_mixed_stream_also_refused() {
let bp = TensorWasmKernelBlueprint::new("mixed")
.push(TensorWasmOp::LoadUnified {
elem: ElemType::F32,
lanes: 4,
})
.push(TensorWasmOp::MatMul {
m: 16,
n: 16,
k: 16,
})
.push(TensorWasmOp::StoreUnified {
elem: ElemType::F32,
lanes: 4,
});
assert!(matches!(emit(&bp), Err(EmitError::NotYetImplemented(_))));
}
#[test]
fn ptx_header_includes_version_and_target() {
let bp = TensorWasmKernelBlueprint::new("noop");
let out = emit(&bp).expect("emit");
assert!(out.text.contains(".version 8.0"));
assert!(out.text.contains(".target sm_80"));
assert!(out.text.contains(".address_size 64"));
}
#[test]
fn launch_geometry_in_header() {
let bp = TensorWasmKernelBlueprint::new("k").with_grid(GridHint {
total_threads: 1024,
preferred_block_size: 128,
});
let out = emit(&bp).expect("emit");
assert_eq!(out.launch_geometry, (8, 128));
assert!(out.text.contains(".maxntid 128, 1, 1"));
assert!(!out.text.contains("__launch_bounds__"));
}
#[test]
fn barrier_emits_bar_sync() {
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::Barrier);
let out = emit(&bp).expect("emit");
assert!(out.text.contains("bar.sync 0;"));
}
#[test]
fn fma_emits_fma_rn() {
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::VecFma {
elem: ElemType::F32,
lanes: 4,
});
let out = emit(&bp).expect("emit");
assert!(out.text.contains("fma.rn.f32"));
}
#[test]
fn custom_target() {
let bp = TensorWasmKernelBlueprint::new("k");
let cfg = EmitConfig {
target: "sm_89".into(),
..EmitConfig::default()
};
let out = emit_with(&bp, &cfg).expect("emit");
assert!(out.text.contains(".target sm_89"));
}
#[test]
fn emitted_text_nonempty() {
let bp = TensorWasmKernelBlueprint::new("k");
let out = emit(&bp).expect("emit");
assert!(!out.is_empty());
}
#[test]
fn register_allocator_assigns_fresh_destination_per_op() {
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::VecAdd {
elem: ElemType::F32,
lanes: 8,
});
let out = emit(&bp).expect("emit");
let mut destinations = std::collections::BTreeSet::new();
for line in out.text.lines() {
if let Some(rest) = line.trim_start().strip_prefix("add.f32 %f") {
if let Some((reg, _)) = rest.split_once(',') {
if let Ok(n) = reg.trim().parse::<u32>() {
destinations.insert(n);
}
}
}
}
assert!(
destinations.len() >= 8,
"expected at least 8 distinct add destinations, got {} ({:?})",
destinations.len(),
destinations,
);
}
#[test]
fn register_declarations_match_usage() {
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::VecAdd {
elem: ElemType::F32,
lanes: 16,
});
let out = emit(&bp).expect("emit");
let count_line = out
.text
.lines()
.find(|l| l.contains(".reg .f32"))
.expect("missing .reg .f32 declaration");
let count: u32 = count_line
.split('<')
.nth(1)
.and_then(|s| s.split('>').next())
.and_then(|s| s.parse().ok())
.expect("parse reg count");
assert!(
count >= 16,
"register declaration must cover all uses (got {count})"
);
}
#[test]
fn prologue_loads_params_into_registers() {
let bp = TensorWasmKernelBlueprint::new("vec_op");
let out = emit(&bp).expect("emit");
assert!(out.text.contains("ld.param.u64 %rd0"));
assert!(out.text.contains("ld.param.u64 %rd1"));
assert!(out.text.contains("ld.param.u32 %r0"));
}
#[test]
fn loads_advance_input_offset() {
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::LoadUnified {
elem: ElemType::F32,
lanes: 3,
});
let out = emit(&bp).expect("emit");
assert!(out.text.contains("[%rd0];"), "first load uses no offset");
assert!(out.text.contains("[%rd0+4]"), "second lane at offset 4");
assert!(out.text.contains("[%rd0+8]"), "third lane at offset 8");
}
#[test]
fn oversized_lanes_is_rejected() {
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::VecAdd {
elem: ElemType::F32,
lanes: u32::MAX,
});
let err = emit(&bp).expect_err("oversized lanes must be refused");
assert!(matches!(err, EmitError::EmissionBudgetExceeded { .. }));
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::LoadUnified {
elem: ElemType::F32,
lanes: MAX_LANES + 1,
});
assert!(matches!(
emit(&bp),
Err(EmitError::EmissionBudgetExceeded { .. })
));
}
#[test]
fn aggregate_emission_budget_is_enforced() {
let n_ops = (MAX_EMITTED_OPS / u64::from(MAX_LANES)) as usize + 2;
let mut bp = TensorWasmKernelBlueprint::new("k");
for _ in 0..n_ops {
bp = bp.push(TensorWasmOp::VecMul {
elem: ElemType::F32,
lanes: MAX_LANES,
});
}
assert!(matches!(
emit(&bp),
Err(EmitError::EmissionBudgetExceeded { .. })
));
}
#[test]
fn lanes_at_max_still_emits() {
let bp = TensorWasmKernelBlueprint::new("k").push(TensorWasmOp::VecAdd {
elem: ElemType::F32,
lanes: MAX_LANES,
});
assert!(emit(&bp).is_ok());
}
#[test]
fn stores_advance_output_offset() {
let bp = TensorWasmKernelBlueprint::new("k")
.push(TensorWasmOp::LoadUnified {
elem: ElemType::F32,
lanes: 2,
})
.push(TensorWasmOp::StoreUnified {
elem: ElemType::F32,
lanes: 2,
});
let out = emit(&bp).expect("emit");
assert!(out.text.contains("[%rd1]"));
assert!(out.text.contains("[%rd1+4]"));
}
}