//! GPU Blake2b-256 throughput benchmark via honeycrisp/aruminium.
//!
//! Buffers are backed by `unimem::Block` (IOSurface-pinned pages) and
//! wrapped as Metal `MTLBuffer` via `aruminium::Gpu::wrap()`. The same
//! physical pages are addressable by:
//!   - CPU via `block.as_bytes()` / `as_bytes_mut()` (no syscalls, no copies)
//!   - GPU via the wrapped MTLBuffer (Metal's zero-copy access)
//!   - AMX/NEON via raw pointer (`block.address()`)
//!   - ANE via `block.handle()` (IOSurfaceRef)
//!
//! Phase 1 only exercises the CPU+GPU axes, but the same allocation
//! becomes the R-table in Phase 2 โ€” at which point AMX builds it on
//! the CPU side while GPU mines, with zero copies between the two.

use aruminium::{Buffer, Dispatch, Gpu, GpuError, Pipeline, Queue};
use unimem::{Block, MemError};

pub mod reference;

const SHADER_V1: &str = include_str!("../shaders/blake2b_v1.metal");
const SHADER_V2: &str = include_str!("../shaders/blake2b_v2.metal");
const SHADER_V3: &str = include_str!("../shaders/blake2b_v3.metal");
const SHADER_V4: &str = include_str!("../shaders/blake2b_v4.metal");

#[derive(Copy, Clone, Debug)]
pub enum Variant {
    V1Baseline,
    V2Unrolled,
    V3DualHash,
    V4DualHashFastRot,
}

impl Variant {
    pub fn shader_src(self) -> &'static str {
        match self {
            Variant::V1Baseline => SHADER_V1,
            Variant::V2Unrolled => SHADER_V2,
            Variant::V3DualHash => SHADER_V3,
            Variant::V4DualHashFastRot => SHADER_V4,
        }
    }
    pub fn function_name(self) -> &'static str {
        match self {
            Variant::V1Baseline => "blake2b256_v1",
            Variant::V2Unrolled => "blake2b256_v2",
            Variant::V3DualHash => "blake2b256_v3",
            Variant::V4DualHashFastRot => "blake2b256_v4",
        }
    }
    pub fn label(self) -> &'static str {
        match self {
            Variant::V1Baseline => "V1 baseline (loop, SIGMA table)",
            Variant::V2Unrolled => "V2 unrolled (no loop, inlined SIGMA)",
            Variant::V3DualHash => "V3 dual hash per thread (ILP)",
            Variant::V4DualHashFastRot => "V4 dual hash + per-amount rotates",
        }
    }
    pub fn hashes_per_thread(self) -> u32 {
        match self {
            Variant::V1Baseline | Variant::V2Unrolled => 1,
            Variant::V3DualHash | Variant::V4DualHashFastRot => 2,
        }
    }
}

/// Errors specific to blake-bench setup.
#[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)
    }
}

/// IOSurface-backed pinned buffer accessible from CPU directly AND from
/// GPU as an MTLBuffer wrapping the same physical pages. No copies on
/// either side.
///
/// Field order matters: `buffer` is declared first so it drops first,
/// releasing the MTLBuffer reference before the underlying IOSurface
/// `Block` is unmapped.
pub struct ZeroCopyBuf {
    pub buffer: Buffer,
    pub block: Block,
}

impl ZeroCopyBuf {
    pub fn open(gpu: &Gpu, size: usize) -> Result<Self, BenchError> {
        let block = Block::open(size)?;
        let buffer = gpu.wrap(&block)?;
        Ok(Self { block, buffer })
    }

    /// Direct mutable CPU access to the pinned memory. No syscalls,
    /// no Metal map/unmap, no copies.
    #[inline]
    pub fn as_bytes_mut(&self) -> &mut [u8] {
        self.block.as_bytes_mut()
    }

    /// Direct immutable CPU access. After a GPU dispatch+wait, this
    /// returns the GPU's writes โ€” same pages, no readback copy.
    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        self.block.as_bytes()
    }

    /// Underlying IOSurface global ID โ€” usable for cross-process or
    /// ANE/AMX sharing of the same allocation.
    #[inline]
    pub fn iosurface_id(&self) -> u32 {
        self.block.id()
    }
}

pub struct GpuBlake2b {
    pub gpu: Gpu,
    _queue: Queue,
    dispatch: Dispatch,
    pipeline: Pipeline,
    pub variant: Variant,
    pub max_threads_per_group: usize,
    pub thread_execution_width: usize,
}

impl GpuBlake2b {
    pub fn open(variant: Variant) -> Result<Self, GpuError> {
        let gpu = Gpu::open()?;
        let queue = gpu.new_command_queue()?;
        let dispatch = Dispatch::new(&queue);

        let lib = gpu.compile(variant.shader_src())?;
        let func = lib.function(variant.function_name())?;
        let pipeline = gpu.pipeline(&func)?;

        let max_threads_per_group = pipeline.max_total_threads_per_threadgroup();
        let thread_execution_width = pipeline.thread_execution_width();

        Ok(Self {
            gpu,
            _queue: queue,
            dispatch,
            pipeline,
            variant,
            max_threads_per_group,
            thread_execution_width,
        })
    }

    pub fn open_v1() -> Result<Self, GpuError> {
        Self::open(Variant::V1Baseline)
    }

    /// Dispatch + wait against IOSurface-backed buffers. CPU writes to
    /// `input` are visible to the GPU and GPU writes to `output` are
    /// visible back on the CPU when this returns โ€” no copies either way.
    pub fn dispatch(
        &self,
        input: &ZeroCopyBuf,
        output: &ZeroCopyBuf,
        count: u32,
        threadgroup_width: usize,
    ) {
        let count_bytes = count.to_le_bytes();
        let hpt = self.variant.hashes_per_thread() as usize;
        let threads = (count as usize).div_ceil(hpt);
        let grid = threads.div_ceil(threadgroup_width) * threadgroup_width;
        unsafe {
            self.dispatch.dispatch_with_bytes(
                &self.pipeline,
                &[(&input.buffer, 0, 0), (&output.buffer, 0, 1)],
                &count_bytes,
                2,
                (grid, 1, 1),
                (threadgroup_width, 1, 1),
            );
        }
    }

    /// Allocate two IOSurface-backed buffers sized for `count` 32-byte
    /// hashes.
    pub fn alloc_buffers(&self, count: u32) -> Result<(ZeroCopyBuf, ZeroCopyBuf), BenchError> {
        let bytes = (count as usize) * 32;
        let in_buf = ZeroCopyBuf::open(&self.gpu, bytes)?;
        let out_buf = ZeroCopyBuf::open(&self.gpu, bytes)?;
        Ok((in_buf, out_buf))
    }
}

/// Fill input buffer with deterministic pseudo-random 32-byte messages.
/// Writes directly into the IOSurface-pinned pages โ€” no Metal map/unmap.
pub fn fill_inputs(buf: &ZeroCopyBuf, count: u32, seed: u64) {
    let bytes = buf.as_bytes_mut();
    let mut s = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
    for i in 0..(count as usize) {
        let off = i * 32;
        for k in 0..4 {
            s ^= s << 13;
            s ^= s >> 7;
            s ^= s << 17;
            bytes[off + k * 8..off + (k + 1) * 8].copy_from_slice(&s.to_le_bytes());
        }
    }
}

/// Verify GPU output against CPU reference with no heap allocation.
/// Reads inputs and outputs directly from IOSurface-pinned pages; the
/// CPU reference hash is produced into a stack-allocated [u8; 32]
/// (Blake2b state is internal to the `blake2` crate; this function
/// adds no heap memory of its own).
pub fn verify_zero_copy(
    in_buf: &ZeroCopyBuf,
    out_buf: &ZeroCopyBuf,
    count: u32,
) -> usize {
    let inputs = in_buf.as_bytes();
    let outputs = out_buf.as_bytes();
    let mut mismatches = 0usize;
    for i in 0..(count as usize) {
        let off = i * 32;
        let cpu: [u8; 32] = reference::blake2b256(&inputs[off..off + 32]);
        let gpu = &outputs[off..off + 32];
        if cpu != *gpu {
            mismatches += 1;
            if mismatches <= 3 {
                eprintln!(
                    "  mismatch at i={}: gpu[..8]={:02x?} cpu[..8]={:02x?}",
                    i,
                    &gpu[..8],
                    &cpu[..8]
                );
            }
        }
    }
    mismatches
}

Homonyms

warriors/trisha/wgpu/lib.rs
soft3/glia/run/lib.rs
soft3/mir/src/lib.rs
soft3/foculus/src/lib.rs
cyb/optica/src/lib.rs
soft3/strata/src/lib.rs
warriors/trisha/rs/lib.rs
cyb/shell/src/lib.rs
cyb/core/src/lib.rs
soft3/glia/import/lib.rs
warriors/trisha/honeycrisp/lib.rs
neural/trident/src/lib.rs
soft3/crate/src/lib.rs
cyb/honeycrisp/src/lib.rs
cyb/prysm/rs/lib.rs
soft3/lens/src/lib.rs
soft3/tru/rs/lib.rs
soft3/cybergraph/src/lib.rs
soft3/nox/rs/lib.rs
soft3/mudra/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
neural/rs/dialect/src/lib.rs
soft3/lens/assayer/src/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/bbg/rs/src/lib.rs
neural/rs/sigil/src/lib.rs
soft3/radio/iroh-willow/src/lib.rs
cyb/crates/cyb/src/lib.rs
neural/rs/link/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
soft3/lens/porphyry/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
soft3/radio/iroh/src/lib.rs
cyb/crates/cyb-reserve/src/lib.rs
soft3/strata/ext/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/rs/codegen/src/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
soft3/zheng/rs/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/tok/rs/src/lib.rs
soft3/conformance/rs/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
neural/rs/macros/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
neural/rune/rs/prysm/lib.rs
soft3/lens/core/src/lib.rs
neural/rs/mir-format/src/lib.rs
neural/rs/core/src/lib.rs
soft3/strata/nebu/rs/lib.rs
neural/rune/rs/compile/lib.rs
neural/rune/rs/interp/lib.rs
soft3/strata/compute/src/lib.rs
soft3/lens/binius/src/lib.rs
soft3/strata/proof/src/lib.rs
neural/eidos/rs/src/lib.rs
neural/rune/rs/mold/lib.rs
soft3/strata/core/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
neural/rune/rs/subject/lib.rs
soft3/soma/kernel/src/lib.rs
neural/rune/rs/lower/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
neural/rune/rs/parse-pure/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
neural/rune/rs/lex/lib.rs
soft3/hemera/rs/src/lib.rs
neural/rune/rs/ast/lib.rs
soft3/lens/ikat/src/lib.rs
soft3/tape/impl/rust/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
neural/inf/rs/lex/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
neural/inf/rs/oracle/src/lib.rs
neural/trident/editor/zed/src/lib.rs
warriors/trisha/.vendor/twenty-first/src/lib.rs
warriors/erga/rs/pool/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
soft3/radio/quinn/quinn-udp/src/lib.rs
warriors/trisha/.vendor/triton-vm/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
soft3/radio/nettools/portmapper/src/lib.rs
warriors/erga/rs/wallet/src/lib.rs
soft3/lytics/rs/core/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
neural/inf/rs/plan/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
warriors/erga/rs/autolykos/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
warriors/trisha/.vendor/triton-constraint-circuit/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
warriors/trisha/.vendor/triton-air/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
neural/inf/rs/source/src/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
warriors/trisha/.vendor/triton-isa/src/lib.rs
soft3/lytics/rs/event/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
warriors/trisha/.vendor/triton-constraint-builder/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/ast/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
soft3/radio/quinn/bench/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
warriors/erga/rs/rtable-bench/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
neural/inf/rs/parse/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
soft3/radio/quinn/quinn/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
warriors/erga/rs/app/src/lib.rs
neural/inf/rs/lower/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
warriors/erga/rs/mine-bench/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
soft3/radio/nettools/netwatch/src/lib.rs
warriors/erga/rs/miner/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
soft3/radio/quinn/perf/src/lib.rs
soft3/radio/quinn/quinn-proto/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
neural/inf/rs/cozo/cozo-lib-python/src/lib.rs
neural/inf/rs/cozo/cozo-lib-swift/src/lib.rs
neural/inf/rs/cozo/cozorocks/src/lib.rs
neural/inf/rs/cozo/cozo-lib-java/src/lib.rs
neural/inf/rs/cozo/cozo-lib-c/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/src/lib.rs
neural/inf/rs/cozo/cozo-lib-nodejs/src/lib.rs
neural/inf/rs/cozo/cozo-core/src/lib.rs
neural/inf/rs/cozo/cozo-lib-wasm/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/rane/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/acpu/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/aruminium/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/aruminium/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/rane/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/acpu/src/lib.rs

Graph