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