use super::{BufferDecl, MemoryKind, Program};
pub const NORMALIZED_PROGRAM_CACHE_DIGEST_VERSION: &str = "vyre-pipeline-cache-norm-v3";
impl Program {
pub fn try_normalized_cache_digest(&self) -> Result<[u8; 32], String> {
if let Some(digest) = self.normalized_cache_digest.get() {
return Ok(*digest);
}
let digest = self.compute_normalized_cache_digest()?;
let _ = self.normalized_cache_digest.set(digest);
Ok(digest)
}
pub(super) fn compute_normalized_cache_digest(&self) -> Result<[u8; 32], String> {
super::record_digest_computation();
thread_local! {
static SCRATCH: std::cell::RefCell<Vec<u8>> =
std::cell::RefCell::new(Vec::with_capacity(1024));
}
SCRATCH.with(|cell| {
let mut scratch = cell.borrow_mut();
scratch.clear();
scratch.extend_from_slice(NORMALIZED_PROGRAM_CACHE_DIGEST_VERSION.as_bytes());
scratch.extend_from_slice(b"\0wg\0");
for axis in self.workgroup_size {
scratch.extend_from_slice(&axis.to_le_bytes());
}
scratch.extend_from_slice(b"\0op\0");
match self.entry_op_id.as_deref() {
Some(op) => {
scratch.extend_from_slice(&op_len_bytes(op.len())?);
scratch.extend_from_slice(op.as_bytes());
}
None => scratch.extend_from_slice(&[0u8; 4]),
}
scratch.extend_from_slice(b"\0bufs\0");
for buffer in self.buffers.iter() {
append_buffer_cache_key(&mut scratch, buffer)?;
}
scratch.extend_from_slice(b"\0body\0");
crate::serial::wire::append_node_list_fingerprint(&mut scratch, self.entry()).map_err(
|message| {
format!(
"failed to fingerprint pipeline-cache Program body: {message}. Fix: validate and normalize the Program before computing a compiled-pipeline cache key; invalid IR must not enter cache identity."
)
},
)?;
Ok(*blake3::hash(&scratch).as_bytes())
})
}
}
fn op_len_bytes(len: usize) -> Result<[u8; 4], String> {
u32::try_from(len)
.map(u32::to_le_bytes)
.map_err(|_| {
format!(
"pipeline-cache Program entry op id length {len} exceeds u32. Fix: shorten the certified operation id before computing a compiled-pipeline cache key."
)
})
}
fn append_buffer_cache_key(scratch: &mut Vec<u8>, buffer: &BufferDecl) -> Result<(), String> {
let name = buffer.name();
let name_len = u32::try_from(name.len()).map_err(|_| {
format!(
"pipeline-cache buffer name length {} exceeds u32. Fix: shorten the buffer name before computing a compiled-pipeline cache key.",
name.len()
)
})?;
scratch.extend_from_slice(&name_len.to_le_bytes());
scratch.extend_from_slice(name.as_bytes());
scratch.push(memory_kind_cache_tag(buffer.kind()));
let access_tag = crate::serial::wire::tags::access_tag::access_tag(&buffer.access).map_err(
|message| {
format!(
"failed to tag pipeline-cache buffer access for `{name}`: {message}. Fix: validate and normalize the Program before computing a compiled-pipeline cache key; invalid IR must not enter cache identity."
)
},
)?;
scratch.push(access_tag);
scratch.extend_from_slice(&buffer.binding().to_le_bytes());
crate::serial::wire::append_data_type_fingerprint(scratch, &buffer.element()).map_err(
|message| {
format!(
"failed to fingerprint pipeline-cache buffer data type `{name}`: {message}. Fix: validate and normalize the Program before computing a compiled-pipeline cache key; invalid IR must not enter cache identity."
)
},
)?;
let static_count = if buffer.has_static_element_count() {
buffer.count()
} else {
0
};
scratch.extend_from_slice(&static_count.to_le_bytes());
Ok(())
}
pub(super) const fn memory_kind_cache_tag(kind: MemoryKind) -> u8 {
match kind {
MemoryKind::Global => 0,
MemoryKind::Shared => 1,
MemoryKind::Uniform => 2,
MemoryKind::Local => 3,
MemoryKind::Readonly => 4,
MemoryKind::Persistent => 5,
MemoryKind::Push => 6,
}
}