Commit cab07e2
authored
Single-pass body analysis with AllocMap coherence checks (#121)
## What's this about?
So, #120 fixed the immediate alloc-id mismatch by carrying collected
items forward instead of re-fetching them. That was the right call. But
it left the underlying structure intact: three separate phases (mk_item,
collect_unevaluated_constant_items, collect_interned_values), each with
full access to `TyCtxt`, each free to call `inst.body()` or any other
side-effecting rustc query whenever it felt like it. Nothing in the
types prevented that, and the bug was a direct consequence: one phase
called `inst.body()` a second time, rustc minted fresh AllocIds, and
suddenly the alloc map had ids that didn't correspond to anything in the
stored bodies.
The question is: how do we make that class of bug structurally
impossible, rather than just fixed for the one case we caught?
The full decision record is in
[`ADR-002`](https://github.com/cds-rs/stable-mir-json/blob/dc/declarative-spike/docs/adr/002-declarative-pipeline-with-allocmap-coherence.md).
### The restructuring
The fix is to split the pipeline into phases with type signatures that
enforce the boundary:
```rust
collect_and_analyze_items(HashMap<String, Item>)
-> (CollectedCrate, DerivedInfo)
assemble_smir(CollectedCrate, DerivedInfo) -> SmirJson
```
`CollectedCrate` holds items and unevaluated consts (the output of
talking to rustc). `DerivedInfo` holds calls, allocs, types, and spans
(the output of walking bodies). `assemble_smir` takes both by value and
does pure data transformation; it structurally cannot call `inst.body()`
because it has no `MonoItem` or `Instance` to call it on. That's the
whole point: if you can't reach the query, you can't accidentally call
it.
The two body-walking visitors (`InternedValueCollector` and
`UnevaluatedConstCollector`) are merged into a single `BodyAnalyzer`
that walks each body exactly once. The fixpoint loop for transitive
unevaluated constant discovery is integrated: when `BodyAnalyzer` finds
an unevaluated const, it records it; the outer loop creates the new
`Item` (the one place `inst.body()` is called) and enqueues it.
### But what about catching regressions?
Turns out the existing integration tests normalize away `alloc_id`s (via
the jq filter), so they literally cannot catch this class of bug. The
golden files don't contain alloc ids at all; you could scramble every id
in the output and the tests would still pass.
`AllocMap` replaces the bare `HashMap<AllocId, ...>` with a newtype
that, under `#[cfg(debug_assertions)]`, tracks every insertion and flags
duplicates. After the collect/analyze phase completes,
`verify_coherence` walks every stored `Item` body with an
`AllocIdCollector` visitor and asserts that each referenced `AllocId`
exists in the map. This catches both "walked a stale body" (missing ids)
and "walked the same body twice" (duplicate insertions) at dev time;
zero cost in release builds.
### Other things that fell out of this
- Static items now store their body in `MonoItemKind::MonoItemStatic`
(collected once in `mk_item`), so the analysis phase never goes back to
rustc for static bodies
- `get_item_details` takes the pre-collected body as a parameter instead
of calling `inst.body()` independently
- The `items_clone` full HashMap clone is replaced by a `HashSet` of
original item names (which is all the static fixup actually needed)
- [we uncovered and fixed a very old
bug](#121 (comment))
### What's deleted
`InternedValueCollector`, `UnevaluatedConstCollector`,
`collect_interned_values`, `collect_unevaluated_constant_items`, the
`InternedValues` type alias, and `items_clone`. Good riddance.
### Downstream impact
The tighter allocs representation has already shown positive downstream
effects in KMIR: the proof engine can now decode allocations inline
(resolving to concrete values like `StringVal("123")`) instead of
deferring them as opaque thunks. @dkcumming 's `offset-u8` test went
from thunking through `#decodeConstant(constantKindAllo...)` to directly
producing `toAlloc(allocId(0)), StringVal("123")`. The test's expected
output needed updating, but the new failure mode is semantically
grounded in actual data rather than deferred interpretation.
### Test plan
- [x] `cargo build` compiles
- [x] `cargo clippy` clean
- [x] `cargo fmt` clean
- [x] `make integration-test` passes (all 28 tests, identical output)
- [x] KMIR downstream: `test_prove_rs[offset-u8-fail]` expected output
updated1 parent 62a7917 commit cab07e2
8 files changed
Lines changed: 535 additions & 219 deletions
File tree
- docs/adr
- src
- mk_graph/output
- tests/ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
Lines changed: 54 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
295 | 295 | | |
296 | 296 | | |
297 | 297 | | |
298 | | - | |
299 | | - | |
300 | | - | |
301 | | - | |
302 | | - | |
| 298 | + | |
303 | 299 | | |
304 | 300 | | |
305 | 301 | | |
| |||
0 commit comments