| 2309 | // the collected addresses are already the sorted key set. |
| 2310 | let const_addrs: Vec<Address> = |
| 2311 | index.consts.iter().map(|c| c.addr.clone()).collect(); |
| 2312 | let computed_root = |
| 2313 | merkle_root_canonical(&const_addrs).unwrap_or_else(zero_address); |
| 2314 | if computed_root != stored_root { |
| 2315 | return Err(format!( |
| 2316 | "parse_lazy_index: merkle root mismatch (stored={}, computed={})", |
| 2317 | stored_root.hex(), |
| 2318 | computed_root.hex(), |
| 2319 | )); |
| 2320 | } |
| 2321 | |
| 2322 | // `main` must reference a constant present in the file. |
| 2323 | if let Some(m) = &index.main |
| 2324 | && const_addrs.binary_search(m).is_err() |
| 2325 | { |
| 2326 | return Err(format!( |
| 2327 | "parse_lazy_index: main {} not present in consts", |
| 2328 | m.hex() |
| 2329 | )); |
| 2330 | } |
| 2331 | |
| 2332 | Ok((index, names_lookup)) |
| 2333 | } |
| 2334 | |
| 2335 | /// [`Env::prune_to_closure`] over a lazily-loaded env — the |
| 2336 | /// streaming-metadata pack path. `self` must be the env built by |
| 2337 | /// [`Env::from_lazy_index`]/[`Env::from_lazy_index_mmap`] from |
| 2338 | /// `index` over `data`, and `names` the §4 lookup from |
| 2339 | /// [`Env::parse_lazy_index_with_names`]. Each fixpoint round |
| 2340 | /// re-streams §5 with a [`NamedMetaCursor`], materializing `Named` |
| 2341 | /// entries only for carried constants — resident metadata is |
| 2342 | /// O(survivors) instead of O(env), the full reader's cost. The carry |
| 2343 | /// logic is [`Env::carry_named_entry`], shared with the in-memory |
| 2344 | /// prune, so the two paths produce identical bundles. |
| 2345 | #[cfg(not(target_arch = "riscv64"))] |
| 2346 | pub fn prune_to_closure_streaming( |
| 2347 | &self, |
| 2348 | index: &LazyIndex, |
| 2349 | data: &[u8], |
| 2350 | names: &FxHashMap<Address, Name>, |
| 2351 | main: &Address, |
| 2352 | assumed: &FxHashSet<Address>, |
| 2353 | ) -> Result<Env, String> { |
| 2354 | let (mut out, mut visited, mut pending) = Self::prune_init(main, assumed)?; |
| 2355 | let mut named_done: FxHashSet<Name> = FxHashSet::default(); |
| 2356 | loop { |
| 2357 | self.prune_value_pass(&mut out, &mut visited, &mut pending, assumed)?; |
| 2358 | |
| 2359 | // ── Named pass, streamed: every §5 entry is parsed (boundaries |
| 2360 | // require it — no length sidecar), but only entries whose |
| 2361 | // constant was carried this round materialize into `out`. |
| 2362 | let mut cursor = NamedMetaCursor::open(data, index)?; |
| 2363 | let mut i = 0usize; |
| 2364 | while let Some((_, named)) = cursor.next_entry()? { |
| 2365 | let name = &index.named[i].name; |
| 2366 | i += 1; |
| 2367 | if !out.consts.contains_key(&named.addr) || named_done.contains(name) { |
| 2368 | continue; |