use super::*;
#[test]
fn subnormal_sqrt_sin_cos_produce_canonical_results() {
let pos_sub = f32::from_bits(0x0000_0001);
let neg_sub = f32::from_bits(0x8000_0001);
assert_eq!(
float_bits(eval_expr_value(&Expr::UnOp {
op: UnOp::Sqrt,
operand: Box::new(Expr::f32(pos_sub)),
})),
0x0000_0000
);
assert_eq!(
float_bits(eval_expr_value(&Expr::UnOp {
op: UnOp::Sqrt,
operand: Box::new(Expr::f32(neg_sub)),
})),
0x8000_0000
);
assert_eq!(
float_bits(eval_expr_value(&Expr::UnOp {
op: UnOp::Sin,
operand: Box::new(Expr::f32(pos_sub)),
})),
0x0000_0000
);
assert_eq!(
float_bits(eval_expr_value(&Expr::UnOp {
op: UnOp::Sin,
operand: Box::new(Expr::f32(neg_sub)),
})),
0x8000_0000
);
assert_eq!(
float_bits(eval_expr_value(&Expr::UnOp {
op: UnOp::Cos,
operand: Box::new(Expr::f32(pos_sub)),
})),
1.0f32.to_bits()
);
assert_eq!(
float_bits(eval_expr_value(&Expr::UnOp {
op: UnOp::Cos,
operand: Box::new(Expr::f32(neg_sub)),
})),
1.0f32.to_bits()
);
}
#[test]
fn atomic_oob_index_returns_zero() {
let program = Program::wrapped(
vec![BufferDecl::read_write("buf", 0, DataType::U32).with_count(1)],
[1, 1, 1],
Vec::new(),
);
let mut memory =
Memory::empty().with_storage("buf", Buffer::new(vec![0xAB; 4], DataType::U32));
let result = eval_expr::eval(
&Expr::atomic_add("buf", Expr::u32(999), Expr::u32(1)),
&mut zero_invocation(&program),
&mut memory,
&program,
)
.expect("Fix: OOB atomic must return zero, not panic");
assert_eq!(result, Value::U32(0), "OOB atomic must return old=0");
}
#[test]
fn atomic_on_u64_buffer_touches_lower_half_only() {
let program = Program::wrapped(
vec![BufferDecl::read_write("buf", 0, DataType::U64).with_count(1)],
[1, 1, 1],
Vec::new(),
);
let mut memory = Memory::empty().with_storage(
"buf",
Buffer::new(0x0000_0001_0000_0000u64.to_le_bytes().to_vec(), DataType::U64),
);
let old = eval_expr::eval(
&Expr::atomic_add("buf", Expr::u32(0), Expr::u32(1)),
&mut zero_invocation(&program),
&mut memory,
&program,
)
.expect("Fix: atomic on U64 buffer must evaluate");
assert_eq!(old, Value::U32(0));
let loaded = eval_expr::eval(
&Expr::load("buf", Expr::u32(0)),
&mut zero_invocation(&program),
&mut memory,
&program,
)
.expect("Fix: load after atomic must succeed");
assert_eq!(
loaded,
Value::U64(0x0000_0001_0000_0001),
"atomic add on U64 must only touch lower 32 bits"
);
}
#[test]
fn multiple_atomics_on_same_location_are_deterministic() {
let program = Program::wrapped(
vec![BufferDecl::read_write("buf", 0, DataType::U32).with_count(1)],
[1, 1, 1],
Vec::new(),
);
let mut memory =
Memory::empty().with_storage("buf", Buffer::new(vec![0; 4], DataType::U32));
let mut invocation = zero_invocation(&program);
let first = eval_expr::eval(
&Expr::atomic_add("buf", Expr::u32(0), Expr::u32(1)),
&mut invocation,
&mut memory,
&program,
)
.unwrap();
let second = eval_expr::eval(
&Expr::atomic_add("buf", Expr::u32(0), Expr::u32(1)),
&mut invocation,
&mut memory,
&program,
)
.unwrap();
assert_eq!(first, Value::U32(0), "first atomic must see old=0");
assert_eq!(second, Value::U32(1), "second atomic must see old=1");
let final_val = eval_expr::eval(
&Expr::load("buf", Expr::u32(0)),
&mut invocation,
&mut memory,
&program,
)
.unwrap();
assert_eq!(final_val, Value::U32(2));
}
#[test]
fn oob_load_returns_zero() {
let program = Program::wrapped(
vec![
BufferDecl::read("in", 0, DataType::U32).with_count(1),
BufferDecl::output("out", 1, DataType::U32).with_count(1),
],
[1, 1, 1],
vec![Node::store(
"out",
Expr::u32(0),
Expr::load("in", Expr::u32(999)),
)],
);
let outputs = reference_eval(
&program,
&[Value::from(vec![0xAB; 4]), Value::from(vec![0u8; 4])],
)
.expect("Fix: OOB load must not panic");
assert_eq!(
outputs[0].to_bytes(),
vec![0; 4],
"OOB load must return defined-type zero"
);
}
#[test]
fn oob_store_is_silent_noop() {
let program = Program::wrapped(
vec![
BufferDecl::read("idx", 0, DataType::U32).with_count(1),
BufferDecl::output("out", 1, DataType::U32).with_count(1),
],
[1, 1, 1],
vec![Node::store(
"out",
Expr::load("idx", Expr::u32(0)),
Expr::u32(0xDEAD_BEEF),
)],
);
let outputs = reference_eval(
&program,
&[
Value::from(999u32.to_le_bytes().to_vec()),
Value::from(vec![0u8; 4]),
],
)
.expect("Fix: OOB store must not panic");
assert_eq!(outputs[0].to_bytes(), vec![0; 4], "OOB store must be silent no-op");
}
#[test]
fn zero_sized_buffer_load_returns_zero() {
let program = Program::wrapped(
vec![
BufferDecl::read("in", 0, DataType::U32).with_count(0),
BufferDecl::output("out", 1, DataType::U32).with_count(1),
],
[1, 1, 1],
vec![Node::store(
"out",
Expr::u32(0),
Expr::load("in", Expr::u32(0)),
)],
);
let outputs = reference_eval(
&program,
&[Value::from(vec![]), Value::from(vec![0u8; 4])],
)
.expect("Fix: zero-sized buffer load must not panic");
assert_eq!(
outputs[0].to_bytes(),
vec![0; 4],
"load from zero-sized buffer must return zero"
);
}
#[test]
fn zero_sized_buffer_store_is_noop() {
let program = Program::wrapped(
vec![BufferDecl::output("out", 0, DataType::U32).with_count(0)],
[1, 1, 1],
vec![Node::store(
"out",
Expr::u32(0),
Expr::u32(0xDEAD_BEEF),
)],
);
let outputs = reference_eval(&program, &[Value::from(vec![])])
.expect("Fix: zero-sized buffer store must not panic");
assert_eq!(
outputs.len(),
1,
"zero-sized output buffer is still declared as an output"
);
assert_eq!(
outputs[0].to_bytes(),
Vec::<u8>::new(),
"zero-sized output must yield empty bytes"
);
}
#[test]
fn u32_max_index_load_returns_zero() {
let program = Program::wrapped(
vec![
BufferDecl::read("in", 0, DataType::U32).with_count(1),
BufferDecl::output("out", 1, DataType::U32).with_count(1),
],
[1, 1, 1],
vec![Node::store(
"out",
Expr::u32(0),
Expr::load("in", Expr::u32(u32::MAX)),
)],
);
let outputs = reference_eval(
&program,
&[Value::from(vec![0xAB; 4]), Value::from(vec![0u8; 4])],
)
.expect("Fix: u32::MAX index load must not panic");
assert_eq!(
outputs[0].to_bytes(),
vec![0; 4],
"u32::MAX index load must return zero"
);
}