Analyze expressions for sharing opportunities within a block. Returns a map from content hash to SubtermInfo, and a map from pointer to hash. Uses a two-phase algorithm: 1. Build DAG structure via post-order traversal with Merkle-tree hashing 2. Propagate usage counts structurally from roots to leaves (O(n) total) If `track_hash_consed_size` is true, computes the hash-consed size for each subter
( exprs: &[Arc<Expr>], track_hash_consed_size: bool, )
| 219 | /// subterm (32-byte key + value). This adds overhead and can be disabled when |
| 220 | /// only sharing analysis is needed. |
| 221 | pub fn analyze_block( |
| 222 | exprs: &[Arc<Expr>], |
| 223 | track_hash_consed_size: bool, |
| 224 | ) -> ( |
| 225 | HashMap<blake3::Hash, SubtermInfo>, |
| 226 | FxHashMap<*const Expr, blake3::Hash>, |
| 227 | Vec<blake3::Hash>, |
| 228 | ) { |
| 229 | let mut info_map: HashMap<blake3::Hash, SubtermInfo> = HashMap::new(); |
| 230 | let mut ptr_to_hash: FxHashMap<*const Expr, blake3::Hash> = |
| 231 | FxHashMap::default(); |
| 232 | let mut hash_buf: Vec<u8> = Vec::with_capacity(128); |
| 233 | |
| 234 | // Phase 1: Build DAG structure via post-order traversal |
| 235 | // Don't compute usage counts here - just build the hash→children mapping |
| 236 | enum Frame<'a> { |
| 237 | Visit(&'a Arc<Expr>), |
| 238 | Process(&'a Arc<Expr>), |
| 239 | } |
| 240 | |
| 241 | for root in exprs { |
| 242 | let mut stack: Vec<Frame<'_>> = vec![Frame::Visit(root)]; |
| 243 | |
| 244 | while let Some(frame) = stack.pop() { |
| 245 | match frame { |
| 246 | Frame::Visit(arc_expr) => { |
| 247 | let ptr = arc_expr.as_ref() as *const Expr; |
| 248 | |
| 249 | // Already processed this pointer - just skip |
| 250 | // Usage counts will be computed in phase 2 |
| 251 | if ptr_to_hash.contains_key(&ptr) { |
| 252 | continue; |
| 253 | } |
| 254 | |
| 255 | // Push process frame, then children (in reverse for correct order) |
| 256 | stack.push(Frame::Process(arc_expr)); |
| 257 | for child in get_children(arc_expr).into_iter().rev() { |
| 258 | stack.push(Frame::Visit(child)); |
| 259 | } |
| 260 | }, |
| 261 | Frame::Process(arc_expr) => { |
| 262 | let ptr = arc_expr.as_ref() as *const Expr; |
| 263 | if ptr_to_hash.contains_key(&ptr) { |
| 264 | continue; |
| 265 | } |
| 266 | |
| 267 | let (hash, children, value_size) = |
| 268 | hash_node(arc_expr.as_ref(), &ptr_to_hash, &mut hash_buf); |
| 269 | |
| 270 | // Add to ptr_to_hash cache |
| 271 | ptr_to_hash.insert(ptr, hash); |
| 272 | |
| 273 | // Add to info_map if not already present (same content hash from different pointer) |
| 274 | info_map.entry(hash).or_insert_with(|| { |
| 275 | let base_size = compute_base_size(arc_expr.as_ref()); |
| 276 | let hash_consed_size = |
| 277 | if track_hash_consed_size { 32 + value_size } else { 0 }; |
| 278 | SubtermInfo { |