use std::sync::{Arc, OnceLock};
use vyre_driver::{DispatchConfig, VyreBackend};
use vyre_driver_wgpu::WgpuBackend;
use vyre_foundation::ir::model::expr::{GeneratorRef, Ident};
use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
fn backend() -> &'static WgpuBackend {
static BACKEND: OnceLock<WgpuBackend> = OnceLock::new();
BACKEND.get_or_init(|| {
WgpuBackend::acquire().expect(
"Fix: GPU adapter required for buf_len_array_length tests. Run on a host with a working wgpu adapter.",
)
})
}
fn buf_len_writer_program() -> Program {
let body = vec![Node::if_then(
Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
vec![Node::store("out", Expr::u32(0), Expr::buf_len("input"))],
)];
Program::wrapped(
vec![
BufferDecl::storage("input", 0, BufferAccess::ReadOnly, DataType::U32),
BufferDecl::output("out", 1, DataType::U32).with_count(1),
],
[1, 1, 1],
body,
)
}
fn dispatch_and_read_first_word(program: &Program, input_bytes: Vec<u8>) -> u32 {
dispatch_and_read_first_word_with_lowering(program, input_bytes, false)
}
fn dispatch_and_read_first_word_lowered(program: &Program, input_bytes: Vec<u8>) -> u32 {
dispatch_and_read_first_word_with_lowering(program, input_bytes, true)
}
fn dispatch_and_read_first_word_with_lowering(
program: &Program,
input_bytes: Vec<u8>,
lower: bool,
) -> u32 {
let lowered;
let prog = if lower {
lowered = vyre_foundation::optimizer::pre_lowering::optimize(program.clone());
&lowered
} else {
program
};
let inputs = vec![input_bytes, vec![0u8; 4]];
let outputs = backend()
.dispatch(prog, &inputs, &DispatchConfig::default())
.expect("Fix: backend.dispatch must succeed for the buf_len writer program");
let raw = &outputs[0];
assert!(
raw.len() >= 4,
"Fix: output buffer too small to read a u32 result"
);
u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]])
}
#[test]
fn buf_len_returns_one_element_for_four_byte_input() {
let program = buf_len_writer_program();
let observed = dispatch_and_read_first_word(&program, vec![0xAB, 0, 0, 0]);
assert_eq!(
observed, 1,
"Q3: arrayLength on a 4-byte (1×u32) read-only storage buffer must return 1, got {observed}. \
If this is 0, the wgpu/Vulkan path is computing the binding range wrong for small storage buffers - \
see docs/optimization/ROADMAP.md Q3."
);
}
#[test]
fn buf_len_returns_three_elements_for_twelve_byte_input() {
let program = buf_len_writer_program();
let observed =
dispatch_and_read_first_word(&program, vec![0x01, 0, 0, 0, 0x02, 0, 0, 0, 0x03, 0, 0, 0]);
assert_eq!(
observed, 3,
"Q3: arrayLength on a 12-byte (3×u32) read-only storage buffer must return 3, got {observed}."
);
}
#[test]
fn buf_len_returns_eight_elements_for_thirty_two_byte_input() {
let program = buf_len_writer_program();
let bytes: Vec<u8> = (0..32).map(|i| i as u8).collect();
let observed = dispatch_and_read_first_word(&program, bytes);
assert_eq!(
observed, 8,
"Q3: arrayLength on a 32-byte (8×u32) read-only storage buffer must return 8, got {observed}."
);
}
fn deep_region_wrapped_buf_len_program() -> Program {
let inner = vec![Node::if_then(
Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
vec![Node::store("out", Expr::u32(0), Expr::buf_len("input"))],
)];
let mid = Node::Region {
generator: Ident::from("vyre-primitives::test::buf_len_inner"),
source_region: None,
body: Arc::new(inner),
};
let outer = Node::Region {
generator: Ident::from("vyre-primitives::test::buf_len_mid"),
source_region: Some(GeneratorRef {
name: "vyre-libs::catalog::test::buf_len_outer".to_string(),
}),
body: Arc::new(vec![mid]),
};
let body = Node::Region {
generator: Ident::from("vyre-libs::catalog::test::buf_len_outer"),
source_region: None,
body: Arc::new(vec![outer]),
};
Program::wrapped(
vec![
BufferDecl::storage("input", 0, BufferAccess::ReadOnly, DataType::U32),
BufferDecl::output("out", 1, DataType::U32).with_count(1),
],
[1, 1, 1],
vec![body],
)
}
fn loop_counting_buf_len_program() -> Program {
Program::wrapped(
vec![
BufferDecl::storage("input", 0, BufferAccess::ReadOnly, DataType::U32),
BufferDecl::output("out", 1, DataType::U32).with_count(1),
],
[1, 1, 1],
vec![Node::if_then(
Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
vec![
Node::let_bind("seen", Expr::u32(0)),
Node::loop_for(
"i",
Expr::u32(0),
Expr::buf_len("input"),
vec![Node::assign(
"seen",
Expr::add(Expr::var("seen"), Expr::u32(1)),
)],
),
Node::store("out", Expr::u32(0), Expr::var("seen")),
],
)],
)
}
#[test]
fn buf_len_through_three_region_wraps_for_one_element() {
let program = deep_region_wrapped_buf_len_program();
let observed = dispatch_and_read_first_word(&program, vec![0x99, 0, 0, 0]);
assert_eq!(
observed, 1,
"Q3: arrayLength on a triple-Region-wrapped Program must report 1 for a 4-byte input, got {observed}. \
If this fails while the flat-program tests pass, region inlining or pre-lowering is breaking the BufLen path \
in catalog wrappers - see ROADMAP.md Q3."
);
}
#[test]
fn buf_len_through_three_region_wraps_for_three_elements() {
let program = deep_region_wrapped_buf_len_program();
let observed = dispatch_and_read_first_word(&program, vec![1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]);
assert_eq!(
observed, 3,
"Q3: arrayLength on a triple-Region-wrapped Program must report 3 for a 12-byte input, got {observed}."
);
}
#[test]
fn buf_len_through_three_region_wraps_through_pre_lowering_for_one_element() {
let program = deep_region_wrapped_buf_len_program();
let observed = dispatch_and_read_first_word_lowered(&program, vec![0x99, 0, 0, 0]);
assert_eq!(
observed, 1,
"Q3: arrayLength after pre_lowering::optimize on a triple-Region-wrapped Program must report 1 for a 4-byte input, got {observed}. \
If this fails while the pre-lowering-skipping tests pass, an optimizer pass is folding `Expr::buf_len` to a constant - see ROADMAP.md Q3."
);
}
#[test]
fn buf_len_through_three_region_wraps_through_pre_lowering_for_three_elements() {
let program = deep_region_wrapped_buf_len_program();
let observed =
dispatch_and_read_first_word_lowered(&program, vec![1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]);
assert_eq!(
observed, 3,
"Q3: arrayLength after pre_lowering::optimize on a triple-Region-wrapped Program must report 3 for a 12-byte input, got {observed}."
);
}
#[test]
fn buf_len_loop_bound_survives_pre_lowering() {
let program = loop_counting_buf_len_program();
let observed =
dispatch_and_read_first_word_lowered(&program, vec![1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]);
assert_eq!(
observed, 3,
"Q3: a loop bounded by dynamic buf_len(input) must execute once per bound element after pre_lowering, got {observed}."
);
}
fn fnv1a64_shaped_count_program() -> Program {
let inner = vec![Node::if_then(
Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
vec![
Node::let_bind("n", Expr::u32(0)),
Node::loop_for(
"i",
Expr::u32(0),
Expr::buf_len("input"),
vec![
Node::let_bind(
"byte",
Expr::bitand(Expr::load("input", Expr::var("i")), Expr::u32(0xFF)),
),
Node::let_bind("next", Expr::add(Expr::var("n"), Expr::u32(1))),
Node::assign("n", Expr::var("next")),
Node::let_bind("_swallow", Expr::var("byte")),
],
),
Node::store("out", Expr::u32(0), Expr::var("n")),
],
)];
let mid = Node::Region {
generator: Ident::from("vyre-primitives::test::fnv_shape_inner"),
source_region: None,
body: Arc::new(inner),
};
let outer = Node::Region {
generator: Ident::from("vyre-primitives::test::fnv_shape_mid"),
source_region: Some(GeneratorRef {
name: "vyre-libs::catalog::test::fnv_shape_outer".to_string(),
}),
body: Arc::new(vec![mid]),
};
let body = Node::Region {
generator: Ident::from("vyre-libs::catalog::test::fnv_shape_outer"),
source_region: None,
body: Arc::new(vec![outer]),
};
Program::wrapped(
vec![
BufferDecl::storage("input", 0, BufferAccess::ReadOnly, DataType::U32),
BufferDecl::output("out", 1, DataType::U32).with_count(1),
],
[1, 1, 1],
vec![body],
)
}
#[test]
fn fnv1a64_shaped_loop_runs_once_for_one_byte_input() {
let program = fnv1a64_shaped_count_program();
let observed = dispatch_and_read_first_word_lowered(&program, vec![0xAB, 0, 0, 0]);
assert_eq!(
observed, 1,
"Q3: a fnv1a64-shaped loop (BufLen-bounded, with outer-state assign) must iterate once for a 4-byte input, got {observed}. \
If this fails while the simpler buf_len tests pass, the bug is in how the loop body's outer-scope assigns interact with BufLen lowering."
);
}
#[test]
fn fnv1a64_shaped_loop_runs_three_times_for_twelve_byte_input() {
let program = fnv1a64_shaped_count_program();
let observed =
dispatch_and_read_first_word_lowered(&program, vec![1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]);
assert_eq!(
observed, 3,
"Q3: a fnv1a64-shaped loop must iterate three times for a 12-byte input, got {observed}."
);
}