1use std::sync::Arc;
4
5use vyre_foundation::ir::model::expr::{GeneratorRef, Ident};
6use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
7
8pub const BYTE_HISTOGRAM_256_OP_ID: &str = "vyre-primitives::text::byte_histogram_256";
10
11#[must_use]
13pub fn byte_histogram_256_body(input: &str, histogram: &str, count: u32) -> Vec<Node> {
14 let rounds = Expr::div(Expr::add(Expr::u32(count), Expr::u32(255)), Expr::u32(256));
15 let load_byte = |index: Expr| {
16 Expr::bitand(
17 Expr::cast(DataType::U32, Expr::load(input, index)),
18 Expr::u32(0xFF),
19 )
20 };
21
22 vec![
23 Node::let_bind("lane", Expr::InvocationId { axis: 0 }),
24 Node::if_then(
30 Expr::lt(Expr::var("lane"), Expr::buf_len(histogram)),
31 vec![Node::store(histogram, Expr::var("lane"), Expr::u32(0))],
32 ),
33 Node::Barrier {
34 ordering: vyre_foundation::MemoryOrdering::SeqCst,
35 },
36 Node::loop_for(
37 "round",
38 Expr::u32(0),
39 rounds,
40 vec![
41 Node::let_bind(
42 "idx",
43 Expr::add(
44 Expr::mul(Expr::var("round"), Expr::u32(256)),
45 Expr::var("lane"),
46 ),
47 ),
48 Node::if_then(
49 Expr::lt(Expr::var("idx"), Expr::u32(count)),
50 vec![
51 Node::let_bind("byte", load_byte(Expr::var("idx"))),
52 Node::let_bind(
53 "_prev_hist",
54 Expr::atomic_add(histogram, Expr::var("byte"), Expr::u32(1)),
55 ),
56 ],
57 ),
58 ],
59 ),
60 Node::Barrier {
61 ordering: vyre_foundation::MemoryOrdering::SeqCst,
62 },
63 ]
64}
65
66#[must_use]
68pub fn byte_histogram_256_child(
69 parent_op_id: &str,
70 input: &str,
71 histogram: &str,
72 count: u32,
73) -> Node {
74 byte_histogram_256_child_with_source_type(parent_op_id, input, histogram, count)
75}
76
77#[must_use]
79pub fn byte_histogram_256_u8_child(
80 parent_op_id: &str,
81 input: &str,
82 histogram: &str,
83 count: u32,
84) -> Node {
85 byte_histogram_256_child_with_source_type(parent_op_id, input, histogram, count)
86}
87
88fn byte_histogram_256_child_with_source_type(
89 parent_op_id: &str,
90 input: &str,
91 histogram: &str,
92 count: u32,
93) -> Node {
94 Node::Region {
95 generator: Ident::from(BYTE_HISTOGRAM_256_OP_ID),
96 source_region: Some(GeneratorRef {
97 name: parent_op_id.to_string(),
98 }),
99 body: Arc::new(byte_histogram_256_body(input, histogram, count)),
100 }
101}
102
103#[must_use]
110pub fn byte_histogram_256(input: &str, histogram: &str, count: u32) -> Program {
111 byte_histogram_256_with_source_type(input, histogram, count, DataType::U32)
112}
113
114#[must_use]
119pub fn byte_histogram_256_u8(input: &str, histogram: &str, count: u32) -> Program {
120 byte_histogram_256_with_source_type(input, histogram, count, DataType::U8)
121}
122
123fn byte_histogram_256_with_source_type(
124 input: &str,
125 histogram: &str,
126 count: u32,
127 source_type: DataType,
128) -> Program {
129 let input_decl = if source_type == DataType::U8 && count == 0 {
130 BufferDecl::storage(input, 0, BufferAccess::ReadOnly, source_type)
131 } else {
132 BufferDecl::storage(input, 0, BufferAccess::ReadOnly, source_type).with_count(count.max(1))
133 };
134
135 Program::wrapped(
136 vec![
137 input_decl,
138 BufferDecl::output(histogram, 1, DataType::U32)
139 .with_count(256)
140 .with_output_byte_range(0..256 * 4),
141 ],
142 [256, 1, 1],
143 vec![Node::Region {
144 generator: Ident::from(BYTE_HISTOGRAM_256_OP_ID),
145 source_region: None,
146 body: Arc::new(byte_histogram_256_body(input, histogram, count)),
147 }],
148 )
149}
150
151#[must_use]
153#[cfg(any(test, feature = "cpu-parity"))]
154pub fn reference_byte_histogram(bytes: &[u8]) -> [u32; 256] {
155 let mut histogram = [0u32; 256];
156 for &byte in bytes {
157 histogram[usize::from(byte)] += 1;
158 }
159 histogram
160}
161
162#[cfg(feature = "inventory-registry")]
163inventory::submit! {
164 vyre_foundation::operation::OperationRegistration::primitive(
165 BYTE_HISTOGRAM_256_OP_ID,
166 || byte_histogram_256("bytes", "histogram", 5),
167 Some(|| {
168 vec![vec![
169 crate::wire::pack_bytes_as_u32_slice(&[b'a', b'b', b'a', 0xC3, 0xA9]),
170 vec![0; 256 * 4],
171 ]]
172 }),
173 Some(|| {
174 let mut histogram = [0u32; 256];
175 histogram[usize::from(b'a')] = 2;
176 histogram[usize::from(b'b')] = 1;
177 histogram[0xC3] = 1;
178 histogram[0xA9] = 1;
179 vec![vec![crate::wire::pack_u32_slice(&histogram)]]
180 }),
181 )
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn reference_counts_each_byte() {
190 let histogram = reference_byte_histogram(&[b'a', b'b', b'a', 0xC3, 0xA9]);
191 assert_eq!(histogram[usize::from(b'a')], 2);
192 assert_eq!(histogram[usize::from(b'b')], 1);
193 assert_eq!(histogram[0xC3], 1);
194 assert_eq!(histogram[0xA9], 1);
195 }
196
197 #[test]
198 fn packed_u8_program_declares_one_source_byte_per_element() {
199 let program = byte_histogram_256_u8("bytes", "histogram", 513);
200 let source = program
201 .buffers()
202 .iter()
203 .find(|buffer| buffer.name() == "bytes")
204 .expect("Fix: packed-u8 byte histogram source buffer must be declared");
205 let histogram = program
206 .buffers()
207 .iter()
208 .find(|buffer| buffer.name() == "histogram")
209 .expect("Fix: byte histogram output buffer must be declared");
210
211 assert_eq!(source.element(), DataType::U8);
212 assert_eq!(source.count(), 513);
213 assert_eq!(histogram.element(), DataType::U32);
214 assert_eq!(histogram.count(), 256);
215 assert_eq!(program.workgroup_size(), [256, 1, 1]);
216 }
217
218 #[test]
219 fn masks_high_bit_source_and_records_no_interpreter_oob() {
220 use vyre_reference::value::Value;
221 let program = byte_histogram_256("bytes", "histogram", 1);
228 let (outputs, report) = vyre_reference::reference_eval_oob_report(
229 &program,
230 &[
231 Value::from(crate::wire::pack_u32_slice(&[0x0141])), Value::from(vec![0u8; 256 * 4]),
233 ],
234 )
235 .expect("Fix: byte_histogram_256 must reference-evaluate a high-bit source element");
236 assert_eq!(
237 report.total(),
238 0,
239 "Fix: masked bin index must stay in bounds without relying on interpreter OOB masking"
240 );
241 let histogram = crate::wire::decode_u32_le_bytes_all(&outputs[0].to_bytes());
242 assert_eq!(
243 histogram[0x41], 1,
244 "Fix: 0x0141 must count into bin 0x41 via the `& 0xFF` mask"
245 );
246 assert_eq!(
247 histogram.iter().sum::<u32>(),
248 1,
249 "exactly one byte counted, no stray OOB write elsewhere"
250 );
251 }
252}