mod common;
use common::u32_bytes;
use vyre_driver::{DispatchConfig, VyreBackend};
use vyre_driver_wgpu::WgpuBackend;
use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
fn sub_i32_into_u32_program(n: u32) -> Program {
let mut body = Vec::new();
for i in 0..n {
body.push(Node::store(
"out",
Expr::u32(i),
Expr::sub(Expr::load("a", Expr::u32(i)), Expr::load("b", Expr::u32(i))),
));
}
Program::wrapped(
vec![
BufferDecl::storage("out", 0, BufferAccess::ReadWrite, DataType::U32).with_count(n),
BufferDecl::storage("a", 1, BufferAccess::ReadOnly, DataType::I32).with_count(n),
BufferDecl::storage("b", 2, BufferAccess::ReadOnly, DataType::I32).with_count(n),
],
[1, 1, 1],
body,
)
}
fn and_u32_into_i32_program(n: u32) -> Program {
let mut body = Vec::new();
for i in 0..n {
body.push(Node::store(
"out",
Expr::u32(i),
Expr::bitand(Expr::load("a", Expr::u32(i)), Expr::load("b", Expr::u32(i))),
));
}
Program::wrapped(
vec![
BufferDecl::storage("out", 0, BufferAccess::ReadWrite, DataType::I32).with_count(n),
BufferDecl::storage("a", 1, BufferAccess::ReadOnly, DataType::U32).with_count(n),
BufferDecl::storage("b", 2, BufferAccess::ReadOnly, DataType::U32).with_count(n),
],
[1, 1, 1],
body,
)
}
fn dispatch_words(backend: &WgpuBackend, program: &Program, a: &[u32], b: &[u32]) -> Vec<u32> {
let out_init = u32_bytes(&vec![0u32; a.len()]);
let outputs = backend
.dispatch_borrowed(
program,
&[
out_init.as_slice(),
u32_bytes(a).as_slice(),
u32_bytes(b).as_slice(),
],
&DispatchConfig::default(),
)
.expect("Fix: WGPU must dispatch the same-width store contract.");
outputs[0]
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect()
}
#[test]
fn i32_value_stored_into_u32_buffer_preserves_bits_on_gpu() {
let backend =
WgpuBackend::acquire().expect("Fix: same-width store parity requires a live GPU backend.");
let a: Vec<i32> = vec![5, 10, 0, -100, i32::MIN];
let b: Vec<i32> = vec![8, 2, 1, -100, 1];
let a_u: Vec<u32> = a.iter().map(|&x| x as u32).collect();
let b_u: Vec<u32> = b.iter().map(|&x| x as u32).collect();
let n = a.len() as u32;
let gpu = dispatch_words(&backend, &sub_i32_into_u32_program(n), &a_u, &b_u);
let expected: Vec<u32> = a
.iter()
.zip(&b)
.map(|(&x, &y)| x.wrapping_sub(y) as u32)
.collect();
assert_eq!(
expected,
vec![
(-3i32) as u32,
8,
(-1i32) as u32,
0,
i32::MIN.wrapping_sub(1) as u32
],
"reference two's-complement subtraction drifted"
);
assert_eq!(
gpu, expected,
"GPU store of an i32-typed value into a u32 buffer dropped/changed bits.\n \
expected: {expected:?}\n gpu: {gpu:?}"
);
assert_eq!(
gpu[0] as i32, -3,
"5 - 8 must read back as -3 from the u32 buffer"
);
}
#[test]
fn u32_value_stored_into_i32_buffer_preserves_bits_on_gpu() {
let backend =
WgpuBackend::acquire().expect("Fix: same-width store parity requires a live GPU backend.");
let a: Vec<u32> = vec![0xFFFF_FFFF, 0x1234_5678, 0x8000_0000, 0x0000_FF00];
let b: Vec<u32> = vec![0x0000_FF00, 0xFFFF_0000, 0xFFFF_FFFF, 0x00FF_FF00];
let n = a.len() as u32;
let gpu = dispatch_words(&backend, &and_u32_into_i32_program(n), &a, &b);
let expected: Vec<u32> = a.iter().zip(&b).map(|(&x, &y)| x & y).collect();
assert_eq!(
expected,
vec![0x0000_FF00, 0x1234_0000, 0x8000_0000, 0x0000_FF00],
"reference bitwise-and drifted"
);
assert_eq!(
gpu, expected,
"GPU store of a u32-typed value into an i32 buffer dropped/changed bits.\n \
expected: {expected:?}\n gpu: {gpu:?}"
);
assert_eq!(
gpu[2] as i32,
i32::MIN,
"0x80000000 must survive as i32::MIN"
);
}