use std::sync::{
Arc, Barrier, Mutex, MutexGuard,
atomic::{AtomicUsize, Ordering},
};
use mlxrs::{
Array,
ops::arithmetic::{add, multiply, square},
transforms::{
CompileMode, compile, compile_fn, disable_compile, enable_compile, set_compile_mode,
},
};
fn mode_guard() -> MutexGuard<'static, ()> {
static MODE: Mutex<()> = Mutex::new(());
MODE.lock().unwrap_or_else(|poison| poison.into_inner())
}
fn approx_eq(a: f32, b: f32, eps: f32) -> bool {
(a - b).abs() <= eps
}
#[test]
fn rust_side_effects_run_on_trace_not_on_cache_hit() {
let _mode = mode_guard();
enable_compile().unwrap();
let trace_count = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&trace_count);
let f = move |a: &[Array]| -> mlxrs::Result<Vec<Array>> {
counter.fetch_add(1, Ordering::SeqCst);
Ok(vec![square(&a[0])?])
};
let compiled = compile(f, false).unwrap();
let inputs = [Array::from_slice::<f32>(&[1.0f32, 2.0, 3.0], &[3]).unwrap()];
let mut o1 = compiled.call(&inputs).unwrap(); o1[0].to_vec::<f32>().unwrap();
let mut o2 = compiled.call(&inputs).unwrap(); o2[0].to_vec::<f32>().unwrap();
assert_eq!(
trace_count.load(Ordering::SeqCst),
1,
"f's Rust body runs once (the trace), never on a cache hit",
);
}
#[test]
fn compiled_matches_uncompiled_unary() {
let xs = [1.0f32, 2.0, 3.0, -4.0, 0.5];
let f = |a: &[Array]| -> mlxrs::Result<Vec<Array>> {
let sq = square(&a[0])?;
Ok(vec![add(&sq, &a[0])?])
};
let compiled = compile(f, false).unwrap();
let x = Array::from_slice::<f32>(&xs, &[5]).unwrap();
let mut out = compiled.call(&[x]).unwrap();
let got = out[0].to_vec::<f32>().unwrap();
let x_ref = Array::from_slice::<f32>(&xs, &[5]).unwrap();
let mut want = f(&[x_ref]).unwrap();
let want = want[0].to_vec::<f32>().unwrap();
assert_eq!(got.len(), xs.len());
for (g, w) in got.iter().zip(want.iter()) {
assert!(approx_eq(*g, *w, 1e-6), "compiled {g} != uncompiled {w}");
}
for (g, &v) in got.iter().zip(xs.iter()) {
assert!(
approx_eq(*g, v * v + v, 1e-6),
"{g} != closed-form {}",
v * v + v
);
}
}
#[test]
fn compiled_matches_uncompiled_binary() {
let av = [1.0f32, 2.0, 3.0];
let bv = [4.0f32, 5.0, 6.0];
let f = |a: &[Array]| -> mlxrs::Result<Vec<Array>> {
let prod = multiply(&a[0], &a[1])?;
Ok(vec![add(&prod, &a[1])?])
};
let compiled = compile(f, false).unwrap();
let a = Array::from_slice::<f32>(&av, &[3]).unwrap();
let b = Array::from_slice::<f32>(&bv, &[3]).unwrap();
let mut out = compiled.call(&[a, b]).unwrap();
let got = out[0].to_vec::<f32>().unwrap();
for (i, g) in got.iter().enumerate() {
let w = av[i] * bv[i] + bv[i];
assert!(approx_eq(*g, w, 1e-6), "compiled {g} != {w}");
}
}
#[test]
fn compiled_multiple_outputs() {
let xs = [2.0f32, 3.0];
let compiled = compile(
|a: &[Array]| -> mlxrs::Result<Vec<Array>> { Ok(vec![square(&a[0])?, add(&a[0], &a[0])?]) },
false,
)
.unwrap();
let x = Array::from_slice::<f32>(&xs, &[2]).unwrap();
let mut out = compiled.call(&[x]).unwrap();
assert_eq!(out.len(), 2);
let sq = out[0].to_vec::<f32>().unwrap();
let dbl = out[1].to_vec::<f32>().unwrap();
assert!(approx_eq(sq[0], 4.0, 1e-6) && approx_eq(sq[1], 9.0, 1e-6));
assert!(approx_eq(dbl[0], 4.0, 1e-6) && approx_eq(dbl[1], 6.0, 1e-6));
}
#[test]
fn compiled_reused_across_calls_is_stable() {
let compiled = compile(
|a: &[Array]| -> mlxrs::Result<Vec<Array>> { Ok(vec![square(&a[0])?]) },
false,
)
.unwrap();
for v in [1.0f32, 2.0, 5.0, 10.0] {
let x = Array::from_slice::<f32>(&[v], &[1]).unwrap();
let mut out = compiled.call(&[x]).unwrap();
let got = out[0].to_vec::<f32>().unwrap();
assert!(
approx_eq(got[0], v * v, 1e-6),
"reuse call {v} -> {}",
got[0]
);
}
}
#[test]
fn compile_fn_matches_uncompiled() {
let g = compile_fn(
|a: &[Array]| -> mlxrs::Result<Vec<Array>> { Ok(vec![add(&a[0], &a[0])?]) },
false,
)
.unwrap();
let x = Array::from_slice::<f32>(&[3.0f32, 4.0], &[2]).unwrap();
let mut out = g(&[x]).unwrap();
let got = out[0].to_vec::<f32>().unwrap();
assert!(approx_eq(got[0], 6.0, 1e-6) && approx_eq(got[1], 8.0, 1e-6));
}
#[test]
fn compiled_shapeless_handles_varying_shapes() {
let compiled = compile(
|a: &[Array]| -> mlxrs::Result<Vec<Array>> { Ok(vec![square(&a[0])?]) },
true,
)
.unwrap();
let x3 = Array::from_slice::<f32>(&[1.0f32, 2.0, 3.0], &[3]).unwrap();
let mut o3 = compiled.call(&[x3]).unwrap();
assert_eq!(o3[0].to_vec::<f32>().unwrap(), vec![1.0, 4.0, 9.0]);
let x4 = Array::from_slice::<f32>(&[1.0f32, 2.0, 3.0, 4.0], &[4]).unwrap();
let mut o4 = compiled.call(&[x4]).unwrap();
assert_eq!(o4[0].to_vec::<f32>().unwrap(), vec![1.0, 4.0, 9.0, 16.0]);
}
#[test]
fn compiled_propagates_closure_error() {
let compiled = compile(
|_a: &[Array]| -> mlxrs::Result<Vec<Array>> {
Err(mlxrs::error::Error::EmptyInput(
mlxrs::error::EmptyInputPayload::new("compile test: forced error"),
))
},
false,
)
.unwrap();
let x = Array::from_slice::<f32>(&[1.0f32], &[1]).unwrap();
let res = compiled.call(&[x]);
assert!(
res.is_err(),
"a closure Err must propagate through Compiled::call"
);
}
#[test]
fn failed_first_trace_poisons_and_never_returns_stale_success() {
let _mode = mode_guard();
enable_compile().unwrap();
set_compile_mode(CompileMode::Enabled).unwrap();
let make_err = |_a: &[Array]| -> mlxrs::Result<Vec<Array>> {
Err(mlxrs::error::Error::EmptyInput(
mlxrs::error::EmptyInputPayload::new("compile test: forced first-trace failure"),
))
};
{
let compiled = compile(make_err, false).unwrap();
let first = compiled.call(&[]);
assert!(
first.is_err(),
"first nullary call must error (trace failed)"
);
let second = compiled.call(&[]);
assert!(
second.is_err(),
"second nullary call must ALSO error, never a stale empty Ok",
);
match second {
Err(mlxrs::error::Error::InvariantViolation(_)) => {}
other => panic!("second call must be the poison InvariantViolation, got {other:?}"),
}
}
{
let compiled = compile(make_err, false).unwrap();
let x1 = Array::from_slice::<f32>(&[1.0f32, 2.0], &[2]).unwrap();
assert!(
compiled.call(&[x1]).is_err(),
"first fixed-shape call errors"
);
let x2 = Array::from_slice::<f32>(&[1.0f32, 2.0], &[2]).unwrap();
match compiled.call(&[x2]) {
Err(mlxrs::error::Error::InvariantViolation(_)) => {}
other => panic!("second fixed-shape call must be the poison error, got {other:?}"),
}
}
}
#[test]
fn disabled_passthrough_never_poisons_and_retries_after_error() {
let _mode = mode_guard();
disable_compile().unwrap();
let runs = Arc::new(AtomicUsize::new(0));
let body_runs = Arc::clone(&runs);
let f = move |a: &[Array]| -> mlxrs::Result<Vec<Array>> {
let nth = body_runs.fetch_add(1, Ordering::SeqCst);
if nth == 0 {
return Err(mlxrs::error::Error::EmptyInput(
mlxrs::error::EmptyInputPayload::new("compile test: forced passthrough error"),
));
}
Ok(vec![square(&a[0])?])
};
let compiled = compile(f, false).unwrap();
let x1 = Array::from_slice::<f32>(&[2.0f32, 3.0], &[2]).unwrap();
match compiled.call(&[x1]) {
Err(mlxrs::error::Error::InvariantViolation(_)) => {
panic!("a passthrough's first error must be f's own, never the poison error")
}
Err(_) => {}
Ok(_) => panic!("first call must surface f's forced error"),
}
let x2 = Array::from_slice::<f32>(&[2.0f32, 3.0], &[2]).unwrap();
let mut out = compiled
.call(&[x2])
.expect("second disabled call must succeed — a passthrough never poisons");
let got = out[0].to_vec::<f32>().unwrap();
assert!(approx_eq(got[0], 4.0, 1e-6) && approx_eq(got[1], 9.0, 1e-6));
assert_eq!(
runs.load(Ordering::SeqCst),
2,
"a passthrough runs the body on every call (no caching), so both calls ran it",
);
enable_compile().unwrap();
set_compile_mode(CompileMode::Enabled).unwrap();
}
#[test]
fn compile_mode_controls_roundtrip_and_preserve_correctness() {
let _guard = mode_guard();
let compiled = compile(
|a: &[Array]| -> mlxrs::Result<Vec<Array>> { Ok(vec![square(&a[0])?]) },
false,
)
.unwrap();
let check_correct = || {
let x = Array::from_slice::<f32>(&[2.0f32, 3.0], &[2]).unwrap();
let mut out = compiled.call(&[x]).unwrap();
let got = out[0].to_vec::<f32>().unwrap();
assert!(approx_eq(got[0], 4.0, 1e-6) && approx_eq(got[1], 9.0, 1e-6));
};
for mode in [
CompileMode::Disabled,
CompileMode::NoSimplify,
CompileMode::NoFuse,
CompileMode::Enabled,
] {
set_compile_mode(mode).unwrap();
check_correct();
}
disable_compile().unwrap();
check_correct();
enable_compile().unwrap();
set_compile_mode(CompileMode::Enabled).unwrap();
check_correct();
}
#[test]
fn compile_mode_as_str() {
assert_eq!(CompileMode::Disabled.as_str(), "disabled");
assert_eq!(CompileMode::NoSimplify.as_str(), "no_simplify");
assert_eq!(CompileMode::NoFuse.as_str(), "no_fuse");
assert_eq!(CompileMode::Enabled.as_str(), "enabled");
}
#[test]
fn concurrent_independent_first_traces_are_sound() {
const THREADS: usize = 8;
const ITERS: usize = 24;
let xs = [1.0f32, 2.0, 3.0, -4.0, 0.5, 7.0];
let _mode = mode_guard();
enable_compile().unwrap();
set_compile_mode(CompileMode::Enabled).unwrap();
for iter in 0..ITERS {
let barrier = Arc::new(Barrier::new(THREADS));
let handles: Vec<_> = (0..THREADS)
.map(|t| {
let barrier = Arc::clone(&barrier);
std::thread::spawn(move || {
let k = (iter * THREADS + t) as f32;
let body_runs = Arc::new(AtomicUsize::new(0));
let counter = Arc::clone(&body_runs);
let compiled = compile(
move |a: &[Array]| -> mlxrs::Result<Vec<Array>> {
counter.fetch_add(1, Ordering::SeqCst);
let sq = square(&a[0])?;
let kc = Array::from_slice::<f32>(&[k], &[1])?;
Ok(vec![add(&sq, &kc)?])
},
false,
)
.expect("compile must succeed");
let x = Array::from_slice::<f32>(&xs, &[6]).expect("input array");
barrier.wait();
let mut out = compiled.call(&[x]).expect("compiled call must succeed");
let got = out[0].to_vec::<f32>().expect("materialize output");
assert_eq!(got.len(), xs.len());
for (g, &v) in got.iter().zip(xs.iter()) {
assert!(
approx_eq(*g, v * v + k, 1e-4),
"thread {t} iter {iter}: {g} != closed-form {}",
v * v + k
);
}
let x2 = Array::from_slice::<f32>(&xs, &[6]).expect("input array");
let mut out2 = compiled
.call(&[x2])
.expect("second compiled call must succeed");
out2[0].to_vec::<f32>().expect("materialize output");
assert_eq!(
body_runs.load(Ordering::SeqCst),
1,
"thread {t} iter {iter}: compiled body must run once (the trace) and \
not on a same-shape cache hit — a count of 2 means compilation was \
disabled and this test was not exercising compile_trace",
);
})
})
.collect();
for h in handles {
h.join().expect("a concurrent-trace thread panicked");
}
}
set_compile_mode(CompileMode::Enabled).unwrap();
}
#[test]
fn nested_compile_does_not_self_deadlock() {
let outer = compile(
|a: &[Array]| -> mlxrs::Result<Vec<Array>> {
let inner = compile(
|b: &[Array]| -> mlxrs::Result<Vec<Array>> { Ok(vec![add(&b[0], &b[0])?]) },
false,
)?;
let doubled = inner.call(a)?;
Ok(vec![square(&doubled[0])?])
},
false,
)
.unwrap();
let x = Array::from_slice::<f32>(&[1.0f32, 2.0, 3.0], &[3]).unwrap();
let mut out = outer.call(&[x]).unwrap();
let got = out[0].to_vec::<f32>().unwrap();
for (g, v) in got.iter().zip([1.0f32, 2.0, 3.0]) {
assert!(approx_eq(*g, 4.0 * v * v, 1e-4), "{g} != {}", 4.0 * v * v);
}
}