use std::sync::atomic::{AtomicU64, Ordering};
use aruminium::{Buffer, Dispatch, Gpu, GpuError, Pipeline, Queue};
use blake_bench::reference::blake2b256;
use unimem::{Block, MemError};
pub const ROW_BYTES: usize = 32;
const SHADER_PROBE: &str = include_str!("../shaders/probe.metal");
#[derive(Debug)]
pub enum BenchError {
Gpu(GpuError),
Mem(MemError),
}
impl From<GpuError> for BenchError {
fn from(e: GpuError) -> Self {
BenchError::Gpu(e)
}
}
impl From<MemError> for BenchError {
fn from(e: MemError) -> Self {
BenchError::Mem(e)
}
}
pub struct RTable {
pub buffer: Buffer,
pub block: Block,
pub n: u64,
}
impl RTable {
pub fn open(gpu: &Gpu, n: u64) -> Result<Self, BenchError> {
let bytes = (n as usize)
.checked_mul(ROW_BYTES)
.expect("n * 32 overflow");
let block = Block::open(bytes)?;
let buffer = gpu.wrap(&block)?;
Ok(Self { buffer, block, n })
}
pub fn iosurface_id(&self) -> u32 {
self.block.id()
}
pub fn build_parallel(&self, h: u32, threads: usize) {
let n = self.n as usize;
let chunk_rows = n.div_ceil(threads);
let chunk_bytes = chunk_rows * ROW_BYTES;
let bytes: &mut [u8] = self.block.as_bytes_mut();
std::thread::scope(|s| {
let mut row_base = 0usize;
for chunk in bytes.chunks_mut(chunk_bytes) {
let start = row_base;
let local_rows = chunk.len() / ROW_BYTES;
row_base += local_rows;
s.spawn(move || {
let mut input = [0u8; 8];
input[4..8].copy_from_slice(&h.to_le_bytes());
for local in 0..local_rows {
let row = start + local;
input[0..4].copy_from_slice(&(row as u32).to_le_bytes());
let hash = blake2b256(&input);
let off = local * ROW_BYTES;
chunk[off..off + ROW_BYTES].copy_from_slice(&hash);
}
});
}
});
}
pub fn cpu_checksum(&self, seed: u64, count: u32) -> u64 {
let bytes: &[u8] = self.block.as_bytes();
let n = self.n;
let mut acc: u64 = 0;
for i in 0..count as u64 {
let idx = lcg_index(seed, i, n);
let off = (idx as usize) * ROW_BYTES;
let row0 = u64::from_le_bytes(bytes[off..off + 8].try_into().unwrap());
acc ^= row0;
}
acc
}
}
#[inline]
pub fn lcg_index(seed: u64, i: u64, n: u64) -> u64 {
let mut x = seed
.wrapping_add(i.wrapping_mul(0x9E37_79B9_7F4A_7C15));
x = (x ^ (x >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
x = (x ^ (x >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
x ^= x >> 31;
x % n
}
pub struct Probe {
pub gpu: Gpu,
_queue: Queue,
dispatch: Dispatch,
pipeline: Pipeline,
}
impl Probe {
pub fn open(gpu: Gpu) -> Result<Self, GpuError> {
let queue = gpu.new_command_queue()?;
let dispatch = Dispatch::new(&queue);
let lib = gpu.compile(SHADER_PROBE)?;
let func = lib.function("rtable_probe")?;
let pipeline = gpu.pipeline(&func)?;
Ok(Self {
gpu,
_queue: queue,
dispatch,
pipeline,
})
}
pub fn run(&self, table: &RTable, acc_buf: &Buffer, seed: u64, count: u32) {
#[repr(C)]
struct Params {
n: u64,
seed: u64,
count: u32,
_pad: u32,
}
let p = Params {
n: table.n,
seed,
count,
_pad: 0,
};
let p_bytes: [u8; 24] = unsafe { std::mem::transmute(p) };
let tg_width = 64usize;
let threads = (count as usize).max(1);
let grid = threads.div_ceil(tg_width) * tg_width;
unsafe {
self.dispatch.dispatch_with_bytes(
&self.pipeline,
&[(&table.buffer, 0, 0), (acc_buf, 0, 1)],
&p_bytes,
2,
(grid, 1, 1),
(tg_width, 1, 1),
);
}
}
}
pub fn read_u64(buf: &Buffer) -> u64 {
buf.read(|b| u64::from_le_bytes(b[..8].try_into().unwrap()))
}
pub fn write_u64(buf: &Buffer, v: u64) {
buf.write(|b| b[..8].copy_from_slice(&v.to_le_bytes()));
}
#[allow(dead_code)]
fn _unused_atomic_marker(_: &AtomicU64) {
let _ = Ordering::Relaxed;
}