use std::sync::OnceLock;
use cranelift_codegen::ir::{self, types, AbiParam, InstBuilder};
use cranelift_codegen::ir::condcodes::IntCC;
use cranelift_codegen::settings::{self, Configurable};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{Linkage, Module};
pub struct SimdKernels {
pub dot_f32: unsafe extern "C" fn(*const f32, *const f32, u64) -> f32,
pub l2sq_f32: unsafe extern "C" fn(*const f32, *const f32, u64) -> f32,
pub add_f32: unsafe extern "C" fn(*const f32, *const f32, *mut f32, u64),
pub scale_f32: unsafe extern "C" fn(*const f32, f32, *mut f32, u64),
_module: JITModule,
}
unsafe impl Send for SimdKernels {}
unsafe impl Sync for SimdKernels {}
static KERNELS: OnceLock<Option<SimdKernels>> = OnceLock::new();
pub fn kernels() -> Option<&'static SimdKernels> {
KERNELS.get_or_init(|| compile_kernels().ok()).as_ref()
}
#[derive(Clone, Copy, PartialEq)]
enum KernelKind {
Dot,
L2Sq,
Add,
Scale,
}
fn compile_kernels() -> Result<SimdKernels, String> {
let mut flag_builder = settings::builder();
flag_builder.set("opt_level", "speed").unwrap();
let isa_builder = cranelift_codegen::isa::lookup(target_lexicon::Triple::host())
.map_err(|e| format!("ISA lookup failed: {e}"))?;
let isa = isa_builder
.finish(settings::Flags::new(flag_builder))
.map_err(|e| format!("ISA build failed: {e}"))?;
let jit_builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
let mut module = JITModule::new(jit_builder);
let mut fn_ctx = FunctionBuilderContext::new();
let dot_id = build_kernel(&mut module, &mut fn_ctx, "simd_dot_f32", KernelKind::Dot)?;
let l2_id = build_kernel(&mut module, &mut fn_ctx, "simd_l2sq_f32", KernelKind::L2Sq)?;
let add_id = build_kernel(&mut module, &mut fn_ctx, "simd_add_f32", KernelKind::Add)?;
let scale_id = build_kernel(&mut module, &mut fn_ctx, "simd_scale_f32", KernelKind::Scale)?;
module
.finalize_definitions()
.map_err(|e| format!("finalize: {e}"))?;
unsafe {
Ok(SimdKernels {
dot_f32: std::mem::transmute::<*const u8, unsafe extern "C" fn(*const f32, *const f32, u64) -> f32>(
module.get_finalized_function(dot_id),
),
l2sq_f32: std::mem::transmute::<*const u8, unsafe extern "C" fn(*const f32, *const f32, u64) -> f32>(
module.get_finalized_function(l2_id),
),
add_f32: std::mem::transmute::<*const u8, unsafe extern "C" fn(*const f32, *const f32, *mut f32, u64)>(
module.get_finalized_function(add_id),
),
scale_f32: std::mem::transmute::<*const u8, unsafe extern "C" fn(*const f32, f32, *mut f32, u64)>(
module.get_finalized_function(scale_id),
),
_module: module,
})
}
}
fn build_kernel(
module: &mut JITModule,
fn_ctx: &mut FunctionBuilderContext,
name: &str,
kind: KernelKind,
) -> Result<cranelift_module::FuncId, String> {
let mut sig = module.make_signature();
match kind {
KernelKind::Dot | KernelKind::L2Sq => {
sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); sig.returns.push(AbiParam::new(types::F32));
}
KernelKind::Add => {
sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); }
KernelKind::Scale => {
sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::F32)); sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); }
}
let func_id = module
.declare_function(name, Linkage::Local, &sig)
.map_err(|e| format!("declare {name}: {e}"))?;
let mut ctx = module.make_context();
ctx.func.signature = sig;
{
let mut b = FunctionBuilder::new(&mut ctx.func, fn_ctx);
let entry = b.create_block();
b.append_block_params_for_function_params(entry);
b.switch_to_block(entry);
b.seal_block(entry);
let params: Vec<ir::Value> = b.block_params(entry).to_vec();
let (a_ptr, b_or_k, out_ptr, len) = match kind {
KernelKind::Dot | KernelKind::L2Sq => (params[0], params[1], None, params[2]),
KernelKind::Add => (params[0], params[1], Some(params[2]), params[3]),
KernelKind::Scale => (params[0], params[1], Some(params[2]), params[3]),
};
let mf = ir::MemFlags::new();
let reducing = matches!(kind, KernelKind::Dot | KernelKind::L2Sq);
let n4 = b.ins().band_imm(len, !3i64);
let zero_i = b.ins().iconst(types::I64, 0);
let zero_f = b.ins().f32const(0.0);
let vzero = b.ins().splat(types::F32X4, zero_f);
let vk = if kind == KernelKind::Scale {
Some(b.ins().splat(types::F32X4, b_or_k))
} else {
None
};
let vhead = b.create_block();
b.append_block_param(vhead, types::I64);
b.append_block_param(vhead, types::F32X4);
let vbody = b.create_block();
let vexit = b.create_block();
b.append_block_param(vexit, types::I64);
b.append_block_param(vexit, types::F32X4);
b.ins().jump(vhead, &[zero_i, vzero]);
b.switch_to_block(vhead);
let vi = b.block_params(vhead)[0];
let vacc = b.block_params(vhead)[1];
let done4 = b.ins().icmp(IntCC::UnsignedGreaterThanOrEqual, vi, n4);
b.ins().brif(
done4,
vexit,
&[vi, vacc],
vbody,
&[],
);
b.switch_to_block(vbody);
let byte_off = b.ins().ishl_imm(vi, 2); let a_addr = b.ins().iadd(a_ptr, byte_off);
let va = b.ins().load(types::F32X4, mf, a_addr, 0);
let (next_acc, store_val) = match kind {
KernelKind::Dot => {
let b_addr = b.ins().iadd(b_or_k, byte_off);
let vb = b.ins().load(types::F32X4, mf, b_addr, 0);
let prod = b.ins().fmul(va, vb);
(b.ins().fadd(vacc, prod), None)
}
KernelKind::L2Sq => {
let b_addr = b.ins().iadd(b_or_k, byte_off);
let vb = b.ins().load(types::F32X4, mf, b_addr, 0);
let d = b.ins().fsub(va, vb);
let sq = b.ins().fmul(d, d);
(b.ins().fadd(vacc, sq), None)
}
KernelKind::Add => {
let b_addr = b.ins().iadd(b_or_k, byte_off);
let vb = b.ins().load(types::F32X4, mf, b_addr, 0);
(vacc, Some(b.ins().fadd(va, vb)))
}
KernelKind::Scale => (vacc, Some(b.ins().fmul(va, vk.unwrap()))),
};
if let Some(v) = store_val {
let out_addr = b.ins().iadd(out_ptr.unwrap(), byte_off);
b.ins().store(mf, v, out_addr, 0);
}
let vi_next = b.ins().iadd_imm(vi, 4);
b.ins().jump(vhead, &[vi_next, next_acc]);
b.seal_block(vhead);
b.seal_block(vbody);
b.switch_to_block(vexit);
b.seal_block(vexit);
let ti = b.block_params(vexit)[0];
let facc = b.block_params(vexit)[1];
let red = if reducing {
let l0 = b.ins().extractlane(facc, 0);
let l1 = b.ins().extractlane(facc, 1);
let l2 = b.ins().extractlane(facc, 2);
let l3 = b.ins().extractlane(facc, 3);
let s01 = b.ins().fadd(l0, l1);
let s23 = b.ins().fadd(l2, l3);
b.ins().fadd(s01, s23)
} else {
zero_f
};
let shead = b.create_block();
b.append_block_param(shead, types::I64);
b.append_block_param(shead, types::F32);
let sbody = b.create_block();
let sexit = b.create_block();
b.append_block_param(sexit, types::F32);
b.ins().jump(shead, &[ti, red]);
b.switch_to_block(shead);
let si = b.block_params(shead)[0];
let ss = b.block_params(shead)[1];
let done = b.ins().icmp(IntCC::UnsignedGreaterThanOrEqual, si, len);
b.ins().brif(done, sexit, &[ss], sbody, &[]);
b.switch_to_block(sbody);
let s_off = b.ins().ishl_imm(si, 2);
let sa_addr = b.ins().iadd(a_ptr, s_off);
let sa = b.ins().load(types::F32, mf, sa_addr, 0);
let s_next = match kind {
KernelKind::Dot => {
let sb_addr = b.ins().iadd(b_or_k, s_off);
let sb = b.ins().load(types::F32, mf, sb_addr, 0);
let p = b.ins().fmul(sa, sb);
b.ins().fadd(ss, p)
}
KernelKind::L2Sq => {
let sb_addr = b.ins().iadd(b_or_k, s_off);
let sb = b.ins().load(types::F32, mf, sb_addr, 0);
let d = b.ins().fsub(sa, sb);
let sq = b.ins().fmul(d, d);
b.ins().fadd(ss, sq)
}
KernelKind::Add => {
let sb_addr = b.ins().iadd(b_or_k, s_off);
let sb = b.ins().load(types::F32, mf, sb_addr, 0);
let v = b.ins().fadd(sa, sb);
let out_addr = b.ins().iadd(out_ptr.unwrap(), s_off);
b.ins().store(mf, v, out_addr, 0);
ss
}
KernelKind::Scale => {
let v = b.ins().fmul(sa, b_or_k);
let out_addr = b.ins().iadd(out_ptr.unwrap(), s_off);
b.ins().store(mf, v, out_addr, 0);
ss
}
};
let si_next = b.ins().iadd_imm(si, 1);
b.ins().jump(shead, &[si_next, s_next]);
b.seal_block(shead);
b.seal_block(sbody);
b.switch_to_block(sexit);
b.seal_block(sexit);
let result = b.block_params(sexit)[0];
if reducing {
b.ins().return_(&[result]);
} else {
b.ins().return_(&[]);
}
b.finalize();
}
module
.define_function(func_id, &mut ctx)
.map_err(|e| format!("define {name}: {e}"))?;
module.clear_context(&mut ctx);
Ok(func_id)
}
#[cfg(test)]
mod tests {
fn test_vec(n: usize, seed: u64) -> Vec<f32> {
(0..n)
.map(|i| {
let h = xxhash_rust::xxh3::xxh3_64(&(seed ^ i as u64).to_le_bytes());
(h as f64 / u64::MAX as f64 * 2.0 - 1.0) as f32
})
.collect()
}
fn assert_close(simd: f32, reference: f32, what: &str) {
let denom = reference.abs().max(1e-6);
let rel = ((simd - reference).abs()) / denom;
assert!(
rel < 1e-4,
"{what}: simd={simd} reference={reference} rel_err={rel}"
);
}
#[test]
fn simd_kernels_match_scalar_reference() {
let Some(k) = super::kernels() else {
panic!("SIMD kernels failed to compile on this host — \
the cranelift ISA should support enable_simd");
};
for &n in &[0usize, 3, 8, 1029] {
let a = test_vec(n, 0xA);
let b = test_vec(n, 0xB);
let dot_ref: f32 = a.iter().zip(&b).map(|(x, y)| x * y).sum();
let l2_ref: f32 = a.iter().zip(&b).map(|(x, y)| (x - y) * (x - y)).sum();
let (dot, l2) = unsafe {
(
(k.dot_f32)(a.as_ptr(), b.as_ptr(), n as u64),
(k.l2sq_f32)(a.as_ptr(), b.as_ptr(), n as u64),
)
};
assert_close(dot, dot_ref, &format!("dot n={n}"));
assert_close(l2, l2_ref, &format!("l2sq n={n}"));
let mut out = vec![0.0f32; n];
unsafe { (k.add_f32)(a.as_ptr(), b.as_ptr(), out.as_mut_ptr(), n as u64) };
for i in 0..n {
assert_eq!(out[i], a[i] + b[i], "add lane {i} n={n}");
}
unsafe { (k.scale_f32)(a.as_ptr(), 2.5, out.as_mut_ptr(), n as u64) };
for i in 0..n {
assert_eq!(out[i], a[i] * 2.5, "scale lane {i} n={n}");
}
}
}
}