vyre_runtime/pipeline_cache/
fingerprint.rs1use vyre_foundation::ir::Program;
6
7const PIPELINE_FINGERPRINT_ALLOWED_FIELDS: &[&str] = &[
25 "canonical_ir_graph",
26 "buffer_layout",
27 "declared_workgroup_size",
28 "canonical_wire_framing",
29];
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub struct PipelineFingerprint(pub [u8; 32]);
35
36const _: fn(&Program) -> PipelineFingerprint = PipelineFingerprint::of;
37
38impl PipelineFingerprint {
39 #[must_use]
60 pub fn of(program: &Program) -> Self {
61 Self(hash_pipeline_fingerprint(program))
62 }
63
64 #[must_use]
67 pub fn hex(&self) -> String {
68 let mut out = String::with_capacity(64);
69 self.push_hex(&mut out);
70 out
71 }
72
73 pub(super) fn push_hex(&self, out: &mut String) {
74 const HEX: &[u8; 16] = b"0123456789abcdef";
75 for &byte in &self.0 {
76 out.push(HEX[(byte >> 4) as usize] as char);
77 out.push(HEX[(byte & 0x0f) as usize] as char);
78 }
79 }
80}
81
82fn hash_pipeline_fingerprint(program: &Program) -> [u8; 32] {
83 debug_assert_eq!(
84 PIPELINE_FINGERPRINT_ALLOWED_FIELDS.len(),
85 4,
86 "Fix: update PIPELINE_FINGERPRINT_ALLOWED_FIELDS whenever the fingerprint contract changes."
87 );
88 vyre_foundation::optimizer::pipeline_fingerprint_bytes(program)
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::pipeline_cache::test_helpers::tiny_program;
98 use vyre_foundation::ir::{BufferDecl, DataType, Expr, Node};
99
100 #[test]
101 fn fingerprint_is_deterministic() {
102 let a = PipelineFingerprint::of(&tiny_program());
103 let b = PipelineFingerprint::of(&tiny_program());
104 assert_eq!(a, b);
105 }
106
107 #[test]
108 fn fingerprint_hex_is_64_chars() {
109 let fp = PipelineFingerprint::of(&tiny_program());
110 assert_eq!(fp.hex().len(), 64);
111 }
112
113 #[test]
114 fn canonically_equal_programs_share_fingerprint() {
115 let p1 = Program::wrapped(
117 vec![BufferDecl::read_write("out", 0, DataType::U32).with_count(1)],
118 [1, 1, 1],
119 vec![Node::store(
120 "out",
121 Expr::u32(0),
122 Expr::add(Expr::var("a"), Expr::u32(1)),
123 )],
124 );
125 let p2 = Program::wrapped(
126 vec![BufferDecl::read_write("out", 0, DataType::U32).with_count(1)],
127 [1, 1, 1],
128 vec![Node::store(
129 "out",
130 Expr::u32(0),
131 Expr::add(Expr::u32(1), Expr::var("a")),
132 )],
133 );
134 let fp1 = PipelineFingerprint::of(&p1);
135 let fp2 = PipelineFingerprint::of(&p2);
136 assert_eq!(
137 fp1, fp2,
138 "canonicalize makes `a+1` and `1+a` share a fingerprint"
139 );
140 }
141
142 #[test]
143 fn fingerprint_changes_when_declared_program_shape_changes() {
144 let base = tiny_program();
145 let widened = Program::wrapped(
146 vec![BufferDecl::read_write("out", 0, DataType::U32).with_count(1)],
147 [64, 1, 1],
148 vec![Node::store("out", Expr::u32(0), Expr::u32(42))],
149 );
150
151 assert_ne!(
152 PipelineFingerprint::of(&base),
153 PipelineFingerprint::of(&widened),
154 "declared workgroup size is program-intrinsic and must change the fingerprint"
155 );
156 }
157}