1use core::fmt;
9
10use crate::region::{tag_program, wrap_anonymous, wrap_child};
11use vyre::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
12use vyre_foundation::ir::model::expr::GeneratorRef;
13
14const RANK_SUPERBLOCKS_OP_ID: &str = "vyre-libs::math::succinct::rank1_superblocks";
15const RANK_QUERY_OP_ID: &str = "vyre-libs::math::succinct::rank1_query";
16const SELECT_QUERY_OP_ID: &str = "vyre-libs::math::succinct::select1_query";
17
18#[derive(Debug, Clone, PartialEq, Eq)]
20pub enum SuccinctBuildError {
21 ZeroBlockWords,
23 SuperblockCountOverflow,
25}
26
27impl fmt::Display for SuccinctBuildError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::ZeroBlockWords => {
31 write!(f, "Fix: rank superblock size must be at least one u32 word")
32 }
33 Self::SuperblockCountOverflow => write!(
34 f,
35 "Fix: rank superblock count overflowed u32; shard the bitvector"
36 ),
37 }
38 }
39}
40
41impl std::error::Error for SuccinctBuildError {}
42
43fn superblock_count(word_count: u32, block_words: u32) -> Result<u32, SuccinctBuildError> {
44 if block_words == 0 {
45 return Err(SuccinctBuildError::ZeroBlockWords);
46 }
47 let full_blocks = word_count / block_words;
48 let has_partial = u32::from(word_count % block_words != 0);
49 full_blocks
50 .checked_add(has_partial)
51 .and_then(|blocks| blocks.checked_add(1))
52 .ok_or(SuccinctBuildError::SuperblockCountOverflow)
53}
54
55#[must_use]
61pub fn rank1_superblocks(
62 bits: &str,
63 superblocks: &str,
64 word_count: u32,
65 block_words: u32,
66) -> Program {
67 try_rank1_superblocks(bits, superblocks, word_count, block_words).unwrap_or_else(|err| {
68 crate::builder::invalid_output_program(
69 RANK_SUPERBLOCKS_OP_ID,
70 superblocks,
71 DataType::U32,
72 format!("{err}"),
73 )
74 })
75}
76
77pub fn try_rank1_superblocks(
84 bits: &str,
85 superblocks: &str,
86 word_count: u32,
87 block_words: u32,
88) -> Result<Program, SuccinctBuildError> {
89 let out_count = superblock_count(word_count, block_words)?;
90 let body = vec![Node::if_then(
91 Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
92 vec![
93 Node::store(superblocks, Expr::u32(0), Expr::u32(0)),
94 Node::let_bind("rank_acc", Expr::u32(0)),
95 Node::loop_for(
96 "rank_word",
97 Expr::u32(0),
98 Expr::u32(word_count),
99 vec![
100 Node::if_then(
101 Expr::and(
102 Expr::gt(Expr::var("rank_word"), Expr::u32(0)),
103 Expr::eq(
104 Expr::rem(Expr::var("rank_word"), Expr::u32(block_words)),
105 Expr::u32(0),
106 ),
107 ),
108 vec![Node::store(
109 superblocks,
110 Expr::div(Expr::var("rank_word"), Expr::u32(block_words)),
111 Expr::var("rank_acc"),
112 )],
113 ),
114 Node::assign(
115 "rank_acc",
116 Expr::add(
117 Expr::var("rank_acc"),
118 Expr::popcount(Expr::load(bits, Expr::var("rank_word"))),
119 ),
120 ),
121 ],
122 ),
123 Node::store(superblocks, Expr::u32(out_count - 1), Expr::var("rank_acc")),
124 ],
125 )];
126 Ok(Program::wrapped(
127 vec![
128 BufferDecl::storage(bits, 0, BufferAccess::ReadOnly, DataType::U32)
129 .with_count(word_count.max(1)),
130 BufferDecl::output(superblocks, 1, DataType::U32).with_count(out_count),
131 ],
132 [1, 1, 1],
133 vec![wrap_anonymous(
134 RANK_SUPERBLOCKS_OP_ID,
135 vec![wrap_child(
136 vyre_primitives::graph::path_reconstruct::OP_ID,
137 GeneratorRef {
138 name: RANK_SUPERBLOCKS_OP_ID.to_string(),
139 },
140 body,
141 )],
142 )],
143 ))
144}
145
146#[must_use]
152pub fn rank1_query(
153 bits: &str,
154 superblocks: &str,
155 bit_indices: &str,
156 out: &str,
157 word_count: u32,
158 query_count: u32,
159 block_words: u32,
160) -> Program {
161 try_rank1_query(
162 bits,
163 superblocks,
164 bit_indices,
165 out,
166 word_count,
167 query_count,
168 block_words,
169 )
170 .unwrap_or_else(|err| {
171 crate::builder::invalid_output_program(
172 RANK_QUERY_OP_ID,
173 out,
174 DataType::U32,
175 format!("{err}"),
176 )
177 })
178}
179
180pub fn try_rank1_query(
187 bits: &str,
188 superblocks: &str,
189 bit_indices: &str,
190 out: &str,
191 word_count: u32,
192 query_count: u32,
193 block_words: u32,
194) -> Result<Program, SuccinctBuildError> {
195 let sb_count = superblock_count(word_count, block_words)?;
196 let q = Expr::InvocationId { axis: 0 };
197 let body = vec![Node::if_then(
198 Expr::lt(q.clone(), Expr::u32(query_count)),
199 vec![
200 Node::let_bind("bit_index", Expr::load(bit_indices, q.clone())),
201 Node::let_bind(
202 "word_index",
203 Expr::div(Expr::var("bit_index"), Expr::u32(32)),
204 ),
205 Node::if_then(
206 Expr::ge(Expr::var("word_index"), Expr::u32(word_count)),
207 vec![Node::trap(
208 Expr::var("bit_index"),
209 "rank-query-out-of-bounds",
210 )],
211 ),
212 Node::let_bind(
213 "block_index",
214 Expr::div(Expr::var("word_index"), Expr::u32(block_words)),
215 ),
216 Node::let_bind(
217 "rank_acc",
218 Expr::load(superblocks, Expr::var("block_index")),
219 ),
220 Node::let_bind(
221 "block_start_word",
222 Expr::mul(Expr::var("block_index"), Expr::u32(block_words)),
223 ),
224 Node::loop_for(
225 "rank_word",
226 Expr::var("block_start_word"),
227 Expr::var("word_index"),
228 vec![Node::assign(
229 "rank_acc",
230 Expr::add(
231 Expr::var("rank_acc"),
232 Expr::popcount(Expr::load(bits, Expr::var("rank_word"))),
233 ),
234 )],
235 ),
236 Node::let_bind(
237 "bit_offset",
238 Expr::rem(Expr::var("bit_index"), Expr::u32(32)),
239 ),
240 Node::let_bind(
241 "partial_mask",
242 Expr::select(
243 Expr::eq(Expr::var("bit_offset"), Expr::u32(0)),
244 Expr::u32(0),
245 Expr::sub(
246 Expr::shl(Expr::u32(1), Expr::var("bit_offset")),
247 Expr::u32(1),
248 ),
249 ),
250 ),
251 Node::assign(
252 "rank_acc",
253 Expr::add(
254 Expr::var("rank_acc"),
255 Expr::popcount(Expr::bitand(
256 Expr::load(bits, Expr::var("word_index")),
257 Expr::var("partial_mask"),
258 )),
259 ),
260 ),
261 Node::store(out, q, Expr::var("rank_acc")),
262 ],
263 )];
264 Ok(Program::wrapped(
265 vec![
266 BufferDecl::storage(bits, 0, BufferAccess::ReadOnly, DataType::U32)
267 .with_count(word_count.max(1)),
268 BufferDecl::storage(superblocks, 1, BufferAccess::ReadOnly, DataType::U32)
269 .with_count(sb_count),
270 BufferDecl::storage(bit_indices, 2, BufferAccess::ReadOnly, DataType::U32)
271 .with_count(query_count.max(1)),
272 BufferDecl::output(out, 3, DataType::U32).with_count(query_count.max(1)),
273 ],
274 [64, 1, 1],
275 vec![wrap_anonymous(RANK_QUERY_OP_ID, body)],
276 ))
277}
278
279#[must_use]
285pub fn select1_query(
286 bits: &str,
287 k_indices: &str,
288 out: &str,
289 word_count: u32,
290 query_count: u32,
291) -> Program {
292 try_select1_query(bits, k_indices, out, word_count, query_count).unwrap_or_else(|err| {
293 crate::builder::invalid_output_program(
294 SELECT_QUERY_OP_ID,
295 out,
296 DataType::U32,
297 format!("{err}"),
298 )
299 })
300}
301
302pub fn try_select1_query(
309 bits: &str,
310 k_indices: &str,
311 out: &str,
312 word_count: u32,
313 query_count: u32,
314) -> Result<Program, SuccinctBuildError> {
315 Ok(tag_program(
316 SELECT_QUERY_OP_ID,
317 vyre_primitives::bitset::select::select1_query(
318 bits,
319 k_indices,
320 out,
321 word_count,
322 query_count,
323 ),
324 ))
325}
326
327inventory::submit! {
328 crate::harness::OpEntry {
329 id: RANK_SUPERBLOCKS_OP_ID,
330 build: || rank1_superblocks("bits", "superblocks", 4, 2),
331 test_inputs: Some(|| {
332 let bits = [0b1011u32, 0x8000_0000, 0xFFFF_0000, 0u32];
333 let to_bytes = vyre_primitives::wire::pack_u32_slice;
334 vec![vec![to_bytes(&bits)]]
335 }),
336 expected_output: Some(|| {
337 let expected = [0u32, 4, 20];
338 let bytes = vyre_primitives::wire::pack_u32_slice(&expected);
339 vec![vec![bytes]]
340 }),
341 category: Some("math"),
342 }
343}
344
345inventory::submit! {
346 crate::harness::OpEntry {
347 id: SELECT_QUERY_OP_ID,
348 build: || select1_query("bits", "queries", "out", 4, 5),
349 test_inputs: Some(|| {
350 let bits = [0b1011u32, 0x8000_0000, 0xFFFF_0000, 0u32];
351 let queries = [1u32, 2, 3, 4, 5];
352 let to_bytes = vyre_primitives::wire::pack_u32_slice;
353 vec![vec![to_bytes(&bits), to_bytes(&queries)]]
354 }),
355 expected_output: Some(|| {
356 let expected = [0u32, 1, 3, 63, 80];
357 let bytes = vyre_primitives::wire::pack_u32_slice(&expected);
358 vec![vec![bytes]]
359 }),
360 category: Some("math"),
361 }
362}
363
364inventory::submit! {
365 crate::harness::OpEntry {
366 id: RANK_QUERY_OP_ID,
367 build: || rank1_query("bits", "superblocks", "queries", "out", 4, 5, 2),
368 test_inputs: Some(|| {
369 let bits = [0b1011u32, 0x8000_0000, 0xFFFF_0000, 0u32];
370 let superblocks = [0u32, 4, 20];
371 let queries = [0u32, 1, 4, 63, 80];
372 let to_bytes = vyre_primitives::wire::pack_u32_slice;
373 vec![vec![to_bytes(&bits), to_bytes(&superblocks), to_bytes(&queries)]]
374 }),
375 expected_output: Some(|| {
376 let expected = [0u32, 1, 3, 3, 4];
377 let bytes = vyre_primitives::wire::pack_u32_slice(&expected);
378 vec![vec![bytes]]
379 }),
380 category: Some("math"),
381 }
382}