Aggregates, symbolic index and quantifier alternation in specification obligations (#355) - #419
Closed
0xGeorgii wants to merge 1 commit into
Closed
Aggregates, symbolic index and quantifier alternation in specification obligations (#355)#4190xGeorgii wants to merge 1 commit into
0xGeorgii wants to merge 1 commit into
Conversation
Closes #355
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #355.
Specification bodies can now state aggregate, element-wise and alternating-quantifier properties directly, instead of being rejected outright.
What changed
Aggregates as ordered scalar leaves. A compound
@, a compound parameter, an array or struct literal, and a copy of one translate to a shape-preserving tree of scalar leaves — one universal slot with its ownHA_has_typeguard per leaf (oneHA_exbinder per leaf existentially), arrays enumerated row-major and struct fields in layout order, allocated parameters-first-in-declaration-order then each@in binding order. Field and constant-index access resolve against that tree at translation time, and aggregate==/!=in assertion position is the leafwise conjunction or its De Morgan dual.The supported surface is deliberately equal to the executable aggregate
@surface, since proof mode lowers spec bodies through the same unrolling: arrays of scalars at any rank, and structs whose fields are scalars or one-dimensional scalar arrays. Arrays of structs (A028) and structs with struct or multidimensional-array fields (A027) stay rejected on every specification path.Symbolic index. An index the translation cannot fold binds a witness pinned by the unsigned range bound
i <u Nfirst, then one implication per element. Out of range that definition is unsatisfiable and the enclosing atom is refuted:a[i]denotes the element at indexi, which exists — a definedness rule, not a mirror of any runtime check, since proof mode emits no bounds check at all. The alternative, a guarded implication leaving an out-of-range read vacuously satisfied, is what P010 rejects elsewhere. Constant steps of a chain descend first, som[1][j]splits over the selected row; two non-constant steps in one chain are P002.Quantifier alternation. A
forallblock inside anexists/assumeblock of aforall/plain spec function emits a real universal binder — new IR nodeHAssert::All, printed as wasm-verifier's derivedHall, carried on newinference.hspecsassert tag0x0B, declared in the vendored stub. A free slot there would have been quantified byValidSpec's outer universal, turning∃k. ∀x. Pinto∀x. ∃k. Psilently — which is why P007 is lifted only for that nesting and kept insideexists/uniquebodies, where every@is a hidden choice parameter the judgment quantifies operationally.Two new fatal diagnostics. P013, a per-spec-function running budget of 64 quantified scalar leaves, checked from the declared type before any leaf is materialized (levels accumulate across introductions, so a per-introduction cap would not bound the shared assertion-depth budget). P014, a constant-folded out-of-bounds index (
const K: i32 = 5; a[K],a[1 + 4]), stating the same fact A037 states for a direct-literal index at the spellings A037's pattern cannot see.Diagnostics reworded now that the encoding makes several of them inaccurate — P004's blanket tail at all eight sites, P003's schedule-sounding wording for a permanent rule,
looplifted out of the shared no-encoding template to name the quantifier idiom, and the reach-mode P007/P008/P004 messages naming the quantifier that makes the construct impossible.Verification
Every obligation of all three new corpus fixtures was proved
Qedagainst real wasm-verifier before the goldens were committed — 19 obligations plus the emittedValidModule/ValidSpectheorems, zeroAdmitted, with neutralization runs confirming each green is live. The proofs are staged in a companion wasm-verifier PR.That is how the bounded-iteration fixture's first draft was caught: it emitted a false obligation — an index constrained only from above, refutable at
i = -1, since the emitted range bound is unsigned while a signed source comparison is not its complement — and was rewritten rather than shipped as the documented example of the encoding. Thecoqcgate cannot catch this class of defect on its own, because it rewritesQedtoAdmittedand so only elaborates statements.One usability trap is inherent to the definedness rule and is documented rather than diagnosed: a signed index needs both
0 <= iandi < Nbefore its element denotes at all, and those bounds are necessary rather than sufficient — a claim about the element's value still needs hypotheses about that value. Au32index needs no lower bound and yields a simpler obligation.Compatibility
Every committed
.vgolden is byte-identical, as is every proof-mode.wasm(the pass is read-only over the typed AST), and compile mode has no obligations. No previously emitted obligation shape changes, so no downstream proof needs restating.HSPECS_SECTION_VERSIONstays 2: the new tag is additive, an older decoder fails loudly withUnknownHassertTag(0x0B), and recompilation rather than migration is the compatibility story. Programs that compiled still compile; programs that were rejected may now translate.cargo test --no-fail-fast: 5838 passed, 0 failed, 51 suites.Confidence Score: 5/5
The PR appears safe to merge; no concrete changed-code failure remains after checking the new producer, codec, printer, verifier-stub, and test paths together.
The aggregate ordering and type guards remain aligned with executable layout, symbolic indexes are constrained to defined scalar elements, and nested universal guards remain scoped inside their corresponding binders.
Important Files Changed
Allwire tag and consistently includes it in encoding, decoding, validation, depth checks, and symbol traversal.Hallconstruct.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR A["Typed specification AST"] --> B["Aggregate shape and scalar-leaf translation"] B --> C["HAssert tree"] D["Symbolic index"] --> E["Range-bound existential witness"] E --> C F["Nested forall"] --> G["HAssert::All binder"] G --> C C --> H["inference.hspecs codec"] H --> I["wasm-to-v HAssert printer"] I --> J["Rocq obligation using Hall"]Reviews (1): Last reviewed commit: "HAssert aggregates" | Re-trigger Greptile
Context used (4)
hassert: The Verification-Obligation IR