A Polymorphic Intermediate Verification Language: Design and Logical Encoding
(authored by agents unless marked đ§) Tag: leino2010polymorphic, paper
purpose and architecture
The paper presents Boogie 2 as an intermediate verification language between source-language front ends and SMT solvers. Its abstract states: âIntermediate languages are a paradigm to separate concerns in software verification systems when bridging the gap between programming languages and the logics understood by theorem provers.â (Abstract, PDF p. 312) Boogie 2 adds impredicative, higher-rank polymorphism, polymorphic maps, type quantification, and a type system that rejects ill-typed intermediate programs. The paper gives two translations of typed expressions into simply typed logic suitable for SMT solvers.
The architecture is useful when several source verifiers need one common verification-condition language. The paper says: âIn fact, we give two different translations into simply typed logic, and we present performance figures from substantial benchmarks that compare these.â (Section 1, p. 313) Source front ends encode heaps, procedures, and specifications in Boogie; Boogie generates verification conditions; a translator encodes types and expressions for an SMT solver. The paperâs benchmarks include Spec#, VCC, HAVOC, Dafny, and Boogie regression suites (Sections 1 and 5, pp. 312â325).
technique 1: use a typed intermediate language with polymorphic maps
What it is. Boogie declares built-in bool, mathematical int, arbitrary bit-vectors, user type constructors, polymorphic functions, and first-class maps. Distinct type instantiations denote disjoint sets. A type-safe object store can be modeled as HeapType = [α][Ref, Field α] α, with Heap[r,age] an integer and Heap[r,isMarried] a boolean (Section 2, pp. 313â316).
How to apply it.
- Declare uninterpreted constructors such as
RefandField α. - Declare polymorphic map types for state; use map select
m[i]and map updatem[i := x]. - Declare polymorphic functions and axioms, quantifying over type variables and values. For example,
Left(Cons(a,b))=ais quantified over arbitrary element types. - Require each type parameter to occur in a function or map domain/result so type inference can determine its instantiation. Add an explicit coercion
e:twhen the context is otherwise ambiguous. - Represent source-language modifies clauses by relating
old(Heap)andHeap; polymorphic field quantification allows one clause to range over all field result types (Section 2.2, pp. 314â317).
Boogie maps are first-order individuals with select/store operations, so they can model higher-order-looking values while remaining suitable for first-order SMT theories. Extensionality is deliberately not assumed; users add extensionality axioms only when required (Section 2.2, p. 316).
Verus form. Inference: use Rust generic types and traits for executable typing, and ghost/specification maps for heap abstractions. Verusâs Map-like specifications, spec fn, requires, ensures, and old expressions can state pre/post relations, but Verus does not provide Boogieâs impredicative type quantifiers or polymorphic map encoding directly. Inference: keep type invariants in Rustâs type system where possible and state cross-type logical facts as lemmas.
Evidence and boundaries. The paperâs type system is a design and encoding result, not a proof that every source front endâs heap model is adequate. Map extensionality and user axioms remain assumptions.
technique 2: represent types as first-order terms
What it is. Encode all Boogie types as terms in a simply typed target with sorts bool, int, U (non-built-in individuals), and T (encoded types). Introduce type: UâT and, for each type constructor C, a function C# : T^arity(C)âT; constructor tags and inverse functions express injectivity and disjointness (Section 3, pp. 318â320).
How to apply it.
- Normalize polymorphic map types to type constructors, selecting most-general map-type skeletons to avoid overlapping encodings.
- Translate a source type expression to a term of sort
T. - Add axioms stating constructor disjointness, injectivity, and selector/inverse behavior.
- Encode polymorphic map select/store with separate symbols for each map skeleton and explicit type parameters.
- Embed
intandboolintoUwith cast functionsi2uandu2i, plus round-trip axioms.
The paper warns that a naive constructor per map type can encode a shared instance twice; its most-general-skeleton construction avoids this overlap (Section 3, pp. 319â321). The target remains first-order and can be translated further to untyped logic with domain predicates if a solver requires it.
Verus form. Inference: a Verus front end could encode Rust generic type identity with ghost type tags and injectivity lemmas, but ordinary Verus proofs rely on Rustâs static type checker rather than reifying types as SMT terms. This encoding is therefore a backend design option for a multi-language verifier, not a direct Rust annotation.
Evidence. The paper proves the encoding definitions and uses them in the two expression translations; it does not claim that arbitrary SMT models enforce every omitted semantic property. The target solver, type axioms, and front-end translation remain trusted.
technique 3: translate expressions with type guards
What it is. The first expression encoding carries type information through logical guards. A polymorphic function becomes a U-typed function with a post-translation axiom stating the type of its result; quantified variables are guarded by equalities such as type(x)= [[t]] (Section 4.1, pp. 321â323).
How to apply it.
- Translate source individuals to
Uand source types toTterms. - For each quantifier over values and types, add guards that each value has the translated type.
- Add a result-type axiom for every translated polymorphic function.
- Translate built-in
intandbooloperations through cast functions and corresponding type axioms. - Preserve user-supplied triggers by encoding each trigger expression with the same type machinery.
The paperâs Mojo example adds type(Mojo(x))=int#, then separate guarded axioms for integer and GuitarPlayer arguments (Section 4.1, pp. 321â322). Type guards are general and support arbitrary polymorphic functions, but they add axioms and can burden e-matching.
Verus form. Inference: this resembles adding explicit ghost predicates that guard a polymorphic lemma with the relevant Rust type or trait invariant. Verusâs type checker already rejects ill-typed terms, so duplicating all type guards in specifications is usually unnecessary unless a dynamic type/tag model is being verified.
technique 4: translate with explicit type arguments
What it is. The second encoding passes each type parameter as an ordinary extra function argument. A polymorphic function f<α>(x) becomes f#(type-argument,x); triggers then match on terms carrying the required type information, avoiding most type guards (Section 4.2, pp. 323â324).
How to apply it.
- Reify each inferred type parameter as a term of sort
T. - Add those terms as leading arguments to translated functions and map operations.
- Translate quantifiers so type variables are ordinary
Tvariables. - Ensure triggers mention the explicit type arguments, making e-matching type-specific.
- Use this encoding when solver performance matters more than complete generality; the paper says it is not applicable to every source feature but is faster on the tested workloads.
Verus form. Inference: model a generic lemma with an explicit ghost type parameter or trait witness when SMT triggers need help. Verus does not expose SMT trigger control in the same way, so the correspondence is conceptual. Inference: avoid inventing runtime type arguments solely for proof; keep them in proof-only state.
technique 5: preserve triggers and evaluate translations
What it is. Triggers guide SMT e-matching for quantified formulas. The translator must encode trigger terms without changing their matching behavior. The paper evaluates type guards, type arguments, and an intentionally unsound âNo Typesâ translation that erases type information (Sections 1 and 5, pp. 312, 324â325).
How to measure it. Boogie 2 generates verification conditions for more than 10,000 benchmark conditions. Z3 2.01 runs each condition three times, with a 120-second timeout (1,800 seconds for Boogie tests), on an Intel Core 2 Duo 3.16 GHz with 4 GB RAM (Section 5, p. 325). The type-argument encoding is close to âNo Typesâ; type guards are on average 55% slower on VCC and similar on HAVOC. Across related work, the paper reports its maximum slowdown as 95% for type guards and 45% for type arguments (Section 5, pp. 325â326). The denominators are the benchmark conditions in each category, and the percentages are averages under this solver and hardware setting.
Verus form. Inference: treat quantifier triggers and solver context as part of proof performance, while checking semantics independently. A Verus benchmark should record solver version, timeout, successful obligations, and whether quantifier facts are exposed. Inference: a trigger-preserving translation is relevant to VL only if VL introduces a polymorphic intermediate representation; ordinary Rust type checking may make the typed encoding unnecessary.
what is proved, trusted, and still missing
The paper gives a formal type system, two logical encodings, and performance evidence that advanced types are feasible. Its conclusion says: âOur experimental data support the idea that including such advanced features in an intermediate verification language is both desirable for verifier front ends and feasible for performance.â (Section 6, p. 326) It does not provide a machine-checked proof of the full Boogie implementation, Z3, the source-language front ends, or the adequacy of user axioms. The unsound âNo Typesâ baseline is explicitly a comparison point, not a usable correctness translation.
Inference: VL should preserve a typed intermediate layer if it combines multiple front ends or models polymorphic libraries. It should retain explicit type information in generated SMT formulas and maintain user triggers through translation. Reimplementing Boogieâs complete polymorphic map encoding is unnecessary for a Rust-only verifier unless a concrete generic-heap benchmark requires it.
Inference: for agent-written verified Rust, the direct threat is medium at the infrastructure boundary and low at ordinary function proofs. Agents can generate specifications whose generic types or map axioms are inconsistent, or trigger solver blowups by exposing too many quantified facts. Verusâs static typing blocks many malformed terms, but it does not validate the semantic adequacy of ghost type models or external-library contracts. The paper does not study agent-written Rust, so this threat estimate is an inference.
techniques for the catalog
- typed intermediate verification language â use a shared polymorphic, map-aware IR to separate source front ends from solver logic
- first-order type-term encoding â represent type constructors, maps, and built-in casts as simply typed terms with axioms
- type-guard translation â preserve typing by adding result-type axioms and guards to quantified formulas
- explicit type-argument translation â pass type terms as function arguments so SMT triggers remain type-specific
- trigger-preserving evaluation â compare typed translations and an unsound type-erasing baseline with fixed solver, timeout, and denominator Checked 2026-10-14 against the paper by a Codex agent: audit record leino2010polymorphic; compared the full paper and study, corrected metadata where needed, and did not rerun the experiments
Last edited: