1use xlog_core::{Result, XlogError};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
6pub struct PirNodeId(u32);
8
9impl PirNodeId {
10 pub fn from_u32(id: u32) -> Self {
12 Self(id)
13 }
14
15 pub fn as_u32(self) -> u32 {
17 self.0
18 }
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct LeafId(u32);
24
25impl LeafId {
26 pub fn new(id: u32) -> Self {
28 Self(id)
29 }
30
31 pub fn as_u32(self) -> u32 {
33 self.0
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
38pub struct ChoiceVarId(u32);
40
41impl ChoiceVarId {
42 pub fn new(id: u32) -> Self {
44 Self(id)
45 }
46
47 pub fn as_u32(self) -> u32 {
49 self.0
50 }
51}
52
53#[derive(Debug, Clone, PartialEq)]
54pub enum PirNode {
56 Const(bool),
58 Lit {
60 leaf: LeafId,
62 },
63 NegLit {
65 leaf: LeafId,
67 },
68 And {
70 children: Vec<PirNodeId>,
72 },
73 Or {
75 children: Vec<PirNodeId>,
77 },
78 Decision {
80 var: ChoiceVarId,
82 child_false: PirNodeId,
84 child_true: PirNodeId,
86 },
87}
88
89#[derive(Debug, Default, Clone)]
90pub struct PirGraph {
92 nodes: Vec<PirNode>,
93}
94
95impl PirGraph {
96 pub fn new() -> Self {
98 Self { nodes: Vec::new() }
99 }
100
101 pub fn len(&self) -> usize {
103 self.nodes.len()
104 }
105
106 pub fn is_empty(&self) -> bool {
108 self.nodes.is_empty()
109 }
110
111 pub fn nodes(&self) -> &[PirNode] {
113 &self.nodes
114 }
115
116 pub(crate) fn nodes_mut(&mut self) -> &mut [PirNode] {
117 &mut self.nodes
118 }
119
120 pub fn node(&self, id: PirNodeId) -> Option<&PirNode> {
122 self.nodes.get(id.0 as usize)
123 }
124
125 fn push_node(&mut self, node: PirNode) -> PirNodeId {
126 let id = PirNodeId(u32::try_from(self.nodes.len()).expect("PIR node id overflow"));
127 self.nodes.push(node);
128 id
129 }
130
131 pub fn const_true(&mut self) -> PirNodeId {
133 self.push_node(PirNode::Const(true))
134 }
135
136 pub fn const_false(&mut self) -> PirNodeId {
138 self.push_node(PirNode::Const(false))
139 }
140
141 pub fn lit(&mut self, leaf: LeafId) -> PirNodeId {
143 self.push_node(PirNode::Lit { leaf })
144 }
145
146 pub fn neg_lit(&mut self, leaf: LeafId) -> PirNodeId {
148 self.push_node(PirNode::NegLit { leaf })
149 }
150
151 pub fn and(&mut self, children: Vec<PirNodeId>) -> PirNodeId {
153 self.push_node(PirNode::And { children })
154 }
155
156 pub fn or(&mut self, children: Vec<PirNodeId>) -> PirNodeId {
158 self.push_node(PirNode::Or { children })
159 }
160
161 pub fn decision(
163 &mut self,
164 var: ChoiceVarId,
165 child_false: PirNodeId,
166 child_true: PirNodeId,
167 ) -> PirNodeId {
168 self.push_node(PirNode::Decision {
169 var,
170 child_false,
171 child_true,
172 })
173 }
174
175 pub fn levelize(&self, roots: &[PirNodeId]) -> Result<Vec<Vec<PirNodeId>>> {
177 use std::collections::{HashMap, HashSet};
178
179 let mut visiting: HashSet<PirNodeId> = HashSet::new();
180 let mut levels: HashMap<PirNodeId, u32> = HashMap::new();
181
182 fn compute_level(
183 graph: &PirGraph,
184 id: PirNodeId,
185 visiting: &mut HashSet<PirNodeId>,
186 levels: &mut HashMap<PirNodeId, u32>,
187 ) -> Result<u32> {
188 if let Some(&lvl) = levels.get(&id) {
189 return Ok(lvl);
190 }
191 if !visiting.insert(id) {
192 return Err(XlogError::Compilation(format!(
193 "Cycle detected while levelizing PIR at node {:?}",
194 id
195 )));
196 }
197
198 let node = graph.node(id).ok_or_else(|| {
199 XlogError::Compilation(format!("Invalid PIR node id while levelizing: {:?}", id))
200 })?;
201
202 let lvl = match node {
203 PirNode::Const(_) | PirNode::Lit { .. } | PirNode::NegLit { .. } => 0,
204 PirNode::And { children } | PirNode::Or { children } => {
205 let mut max_child = 0u32;
206 for &child in children {
207 let child_lvl = compute_level(graph, child, visiting, levels)?;
208 max_child = max_child.max(child_lvl);
209 }
210 max_child + 1
211 }
212 PirNode::Decision {
213 child_false,
214 child_true,
215 ..
216 } => {
217 let lf = compute_level(graph, *child_false, visiting, levels)?;
218 let lt = compute_level(graph, *child_true, visiting, levels)?;
219 lf.max(lt) + 1
220 }
221 };
222
223 visiting.remove(&id);
224 levels.insert(id, lvl);
225 Ok(lvl)
226 }
227
228 for &root in roots {
229 compute_level(self, root, &mut visiting, &mut levels)?;
230 }
231
232 let max_level = levels.values().copied().max().unwrap_or(0);
233 let mut buckets: Vec<Vec<PirNodeId>> = vec![Vec::new(); (max_level as usize) + 1];
234
235 let mut ids: Vec<PirNodeId> = levels.keys().copied().collect();
236 ids.sort_by_key(|id| id.0);
237 for id in ids {
238 let lvl = levels[&id] as usize;
239 buckets[lvl].push(id);
240 }
241
242 Ok(buckets)
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_levelize_simple_dag() {
252 let mut pir = PirGraph::new();
253 let a = pir.lit(LeafId(0));
254 let b = pir.lit(LeafId(1));
255 let and_ab = pir.and(vec![a, b]);
256 let root = pir.or(vec![and_ab, a]);
257
258 let levels = pir.levelize(&[root]).unwrap();
259
260 assert!(!levels.is_empty());
261 assert!(levels.iter().any(|lvl| lvl.contains(&root)));
262 assert!(levels.iter().any(|lvl| lvl.contains(&a)));
263 assert!(levels.iter().any(|lvl| lvl.contains(&b)));
264 }
265
266 #[test]
267 fn test_levelize_decision_node() {
268 let mut pir = PirGraph::new();
269 let t = pir.const_true();
270 let f = pir.const_false();
271 let d = pir.decision(ChoiceVarId(0), f, t);
272
273 let levels = pir.levelize(&[d]).unwrap();
274 assert!(levels.iter().any(|lvl| lvl.contains(&d)));
275 assert!(levels.iter().any(|lvl| lvl.contains(&t)));
276 assert!(levels.iter().any(|lvl| lvl.contains(&f)));
277 }
278}