#![cfg(feature = "cuda")]
use cust::stream::{Stream, StreamFlags};
use myelin_accelerator::{GpuAccelerator, GpuBuffer, GpuError};
fn require_gpu() -> GpuAccelerator {
GpuAccelerator::require_gpu().unwrap_or_else(|err| {
panic!(
"GPU required for buffer-contract tests: reason={} detail={}",
err.fallback_reason().map(|r| r.code()).unwrap_or("unknown"),
err
)
})
}
const BOUNDARY_LENS: &[usize] = &[0, 1, 2, 15, 16, 17, 255, 256, 257, 4096];
#[test]
#[ignore] fn zero_prefix_on_uses_caller_stream_and_preserves_tail() {
let _gpu = require_gpu();
let stream = Stream::new(StreamFlags::DEFAULT, None).unwrap();
let mut buffer = GpuBuffer::from_slice(&[7.0f32, 8.0, 9.0, 10.0]).unwrap();
unsafe { buffer.zero_prefix_on(2, &stream) }.unwrap();
stream.synchronize().unwrap();
assert_eq!(buffer.to_vec().unwrap(), [0.0, 0.0, 9.0, 10.0]);
}
fn assert_length_mismatch(err: GpuError, buffer_len: usize, input_len: usize) {
match err {
GpuError::MemoryError(msg) => {
assert!(msg.contains("length mismatch"), "msg: {msg}");
assert!(
msg.contains(&format!("buffer has {buffer_len} elements")),
"msg: {msg}"
);
assert!(
msg.contains(&format!("input has {input_len}")),
"msg: {msg}"
);
}
other => panic!("expected MemoryError, got {other}"),
}
}
#[test]
#[ignore] fn alloc_reads_back_default_immediately_u8_u32_i32_f32() {
let _gpu = require_gpu();
for &len in BOUNDARY_LENS {
let b8 = GpuBuffer::<u8>::alloc(len).unwrap();
assert_eq!(b8.len(), len);
assert_eq!(b8.to_vec().unwrap(), vec![0u8; len], "u8 len {len}");
let b32 = GpuBuffer::<u32>::alloc(len).unwrap();
assert_eq!(b32.to_vec().unwrap(), vec![0u32; len], "u32 len {len}");
let bi = GpuBuffer::<i32>::alloc(len).unwrap();
assert_eq!(bi.to_vec().unwrap(), vec![0i32; len], "i32 len {len}");
let bf = GpuBuffer::<f32>::alloc(len).unwrap();
let got = bf.to_vec().unwrap();
assert_eq!(got.len(), len, "f32 len {len}");
assert!(
got.iter().all(|v| v.to_bits() == 0.0f32.to_bits()),
"f32 alloc not +0.0 at len {len}"
);
}
}
#[derive(Clone, Copy, Debug, PartialEq, cust::DeviceCopy)]
#[repr(C)]
struct Sentinel {
a: u32,
b: i32,
}
impl Default for Sentinel {
fn default() -> Self {
Sentinel {
a: 0xDEAD_BEEF,
b: -12_345,
}
}
}
#[test]
#[ignore] fn alloc_uses_default_not_zeroing_for_nonzero_default_type() {
let _gpu = require_gpu();
for &len in &[1usize, 4, 64, 257] {
let buf = GpuBuffer::<Sentinel>::alloc(len).unwrap();
assert_eq!(
buf.to_vec().unwrap(),
vec![Sentinel::default(); len],
"nonzero-default alloc at len {len}"
);
}
}
#[test]
#[ignore] fn from_slice_to_vec_roundtrip_boundaries() {
let _gpu = require_gpu();
for &len in BOUNDARY_LENS {
let input: Vec<u32> = (0..len as u32)
.map(|i| i.wrapping_mul(2_654_435_761))
.collect();
let buf = GpuBuffer::from_slice(&input).unwrap();
assert_eq!(buf.len(), len);
assert_eq!(buf.to_vec().unwrap(), input, "roundtrip len {len}");
}
}
#[test]
#[ignore] fn from_slice_preserves_f32_bit_patterns() {
let _gpu = require_gpu();
let input = [
0.0f32,
-0.0f32,
f32::from_bits(0x7FC0_0001), f32::from_bits(0xFFA0_1234), f32::INFINITY,
f32::NEG_INFINITY,
1.5,
-2.25,
];
let buf = GpuBuffer::from_slice(&input).unwrap();
let got = buf.to_vec().unwrap();
let got_bits: Vec<u32> = got.iter().map(|v| v.to_bits()).collect();
let want_bits: Vec<u32> = input.iter().map(|v| v.to_bits()).collect();
assert_eq!(got_bits, want_bits, "f32 bit patterns not preserved");
}
#[test]
#[ignore] fn alloc_overflow_returns_memory_error_without_allocating() {
let _gpu = require_gpu();
for len in [usize::MAX, isize::MAX as usize] {
assert_alloc_overflow::<u64>(len);
}
for len in [usize::MAX, isize::MAX as usize + 1] {
assert_alloc_overflow::<u8>(len);
}
assert_alloc_overflow::<u32>(usize::MAX);
assert!(GpuBuffer::<u64>::alloc(0).unwrap().is_empty());
assert_eq!(
GpuBuffer::<u64>::alloc(4).unwrap().to_vec().unwrap(),
vec![0u64; 4]
);
}
fn assert_alloc_overflow<T: cust::memory::DeviceCopy + Default + Clone>(len: usize) {
match GpuBuffer::<T>::alloc(len) {
Err(GpuError::MemoryError(msg)) => assert!(msg.contains("size overflow"), "msg: {msg}"),
Err(other) => panic!("expected MemoryError for len {len}, got {other}"),
Ok(_) => panic!("oversized alloc unexpectedly succeeded"),
}
}
#[test]
#[ignore] fn upload_equal_length_replaces_all() {
let _gpu = require_gpu();
let mut buf = GpuBuffer::from_slice(&[9i32; 8]).unwrap();
let replacement: Vec<i32> = (0..8).map(|i| i * 10 - 15).collect();
buf.upload(&replacement).unwrap();
assert_eq!(buf.to_vec().unwrap(), replacement);
}
#[test]
#[ignore] fn upload_empty_into_empty_ok() {
let _gpu = require_gpu();
let mut buf = GpuBuffer::<i32>::alloc(0).unwrap();
buf.upload(&[]).unwrap();
assert!(buf.to_vec().unwrap().is_empty());
}
#[test]
#[ignore] fn upload_too_short_rejected_and_non_mutating() {
let _gpu = require_gpu();
let original = vec![1i32, 2, 3, 4];
let mut buf = GpuBuffer::from_slice(&original).unwrap();
assert_length_mismatch(buf.upload(&[7, 8]).unwrap_err(), 4, 2);
assert_eq!(buf.to_vec().unwrap(), original, "device contents mutated");
}
#[test]
#[ignore] fn upload_too_long_rejected_and_non_mutating() {
let _gpu = require_gpu();
let original = vec![1i32, 2, 3, 4];
let mut buf = GpuBuffer::from_slice(&original).unwrap();
assert_length_mismatch(buf.upload(&[7, 8, 9, 10, 11]).unwrap_err(), 4, 5);
assert_eq!(buf.to_vec().unwrap(), original, "device contents mutated");
}
#[test]
#[ignore] fn upload_empty_nonempty_mismatches_fail() {
let _gpu = require_gpu();
let mut empty = GpuBuffer::<i32>::alloc(0).unwrap();
assert_length_mismatch(empty.upload(&[1]).unwrap_err(), 0, 1);
let mut nonempty = GpuBuffer::from_slice(&[5i32, 6, 7]).unwrap();
assert_length_mismatch(nonempty.upload(&[]).unwrap_err(), 3, 0);
assert_eq!(nonempty.to_vec().unwrap(), vec![5, 6, 7]);
}