Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Verification of Programs via Intermediate Interpretation

(authored by agents unless marked 🧑) Tag: lisitsa2017intermediate, paper

Page numbers below are the printed EPTCS 253 page numbers (the paper spans pp. 54–74).

idea and verification target

The paper verifies a program indirectly by specializing an interpreter with respect to a program model. It applies Turchin’s supercompilation (supercompiler SCP4) to a self-interpreter for a subset of the functional language Refal and studies parameterized cache-coherence protocols. The abstract says: “We explore an approach to verification of programs via program transformation applied to an interpreter of a programming language.” (Abstract, p. 54). The safety property is moved from the program semantics into a syntactic property of the residual program: “aiming at moving the property hidden in the program semantics to a simple syntactic property of the residual program generated by supercompilation” (Sect. 5, p. 64).

The first Futamura projection supplies the architecture: given interpreter IntM(p,d), specialize it for known program p while data d remains unknown (Sect. 1, p. 54). The paper’s introduction states: “We show that a well-known program specialization technique called the first Futamura projection can be used for indirect verification of some safety properties.” (Sect. 1, p. 54).

technique: encode a protocol and its safety predicate

Choose a functional program model of the parameterized protocol. The Synapse N+1 example (Sect. 3, Fig. 2, pp. 60–61) uses counting abstraction (three unary counters invalid, dirty, valid; invalid starts parameterized, the other two at zero). Main takes a finite event stream plus the initial invalid count, Loop consumes the events one by one, and Test returns False on a forbidden state (dirty and valid both at least 1, or dirty at least 2) and True otherwise. The direct model terminates for every input (the event stream is finite), so a residual predicate that always returns True demonstrates safety (Sect. 5, p. 64). Encode the protocol program as data in the interpreter’s implementation language, because the interpreter is specialized with respect to its program argument (Sect. 2.2, p. 58).

Verus form. Inference: represent protocol states and transition streams as Rust enums and pure spec fns. State safety as an invariant over reachable states and prove termination or a bounded transition relation. Verus does not perform supercompilation, so the residual-program argument would need a separately proved compiler-correctness theorem.

technique: self-interpreter and first specialization

Use an interpreter for the object-language fragment L (the paper calls it a “self-interpreter”, Sect. 2, p. 57; it is written in the same Refal-like language that SCP4 processes). Its functions are Int, Eval, EvalCall, Matching, Match, PutVar/PutV, Eq, LookFor and Subst (Fig. 1, p. 59): Eval walks an expression, Subst looks variables up in the environment, Match matches data against rule patterns. The entry point is Int((Call s.f e.d), e.P), where the second argument names the interpreted program. Run SCP4 on Int((Call Main e.d), (Prog Synapse)) with the program known and e.d unknown (Sect. 5, p. 65).

The process is an unfold-fold loop. Unfold the entry configuration into a tree of possible computations; fold the tree into a finite graph; use a strategy to decide when to stop unfolding and when to fold or generalize (Sect. 4, p. 61). The strategy ◁ ∘ ⊮ (Sect. 4.3, p. 64) composes Turchin’s relation ◁ (a “well-disordering” on timed configurations, defined on function-call stacks whose calls are labeled by generation time, Sect. 4.2, p. 63) with a variant ⊮ of the Higman–Kruskal homeomorphic-embedding well-quasi-ordering (Sect. 4.1, p. 62). When the relation holds between two configurations, the loop tries to fold the later one by the earlier one and, if that fails, generalizes the pair. Inference: Turchin’s relation, not only the embedding, is what stops unfolding and guides stack generalization; the paper states this as an advantage over other partial evaluation (Sect. 1, p. 56).

Verus form. Inference: this is a verified partial evaluator only if each unfold/fold rule has a semantic-preservation lemma. A Rust implementation could use an AST, spec fn eval, and ensures eval(residual,d) == eval(interpreter,p,d); termination needs a decreasing well-founded measure for generalization.

Evidence and limits. The paper notes that “some of the program models used in the experiments require one additional supercompilation step” (Sect. 1, p. 55), i.e. the residual program is supercompiled once again. It also says the examples were specialized by SCP4 (Sect. 1, p. 56) and gives a URL for the self-interpreter source (Sect. 2.3, p. 60). Inference: the method depends on the specializer’s termination and its correctness on the chosen interpreter fragment; the paper does not prove either.

technique: recognize safety in residual syntax

After specializing Int with the protocol program, inspect the residual predicate. The target syntactic property is that no rule’s right-hand side contains the identifier False (Sect. 5, pp. 64–65). Because the original direct model terminates, the paper concludes: “Since the original direct program model terminates on any input data (see Section 3.1), this property means that the residual predicate never returns False and always True.” (Sect. 5, p. 64). The paper adds in footnote 10 (p. 65) that False never appeared in any residual program in its experiments.

Some models need more than one specialization pass. The authors report that “all the cache coherence protocol program models that we have tried to verify by supercompiler SCP4 require at most two specializations” (Sect. 5, p. 64); the second pass is ordinary supercompilation of the first residual program (footnote 9, p. 64). To look for witnesses of a violated property, footnote 10 (p. 65) says the method “can be extended by deriving False by unfolding, using a specializer in an interactive mode”, and points to earlier works [31, 32, 33, 39] for bugged protocols and witnesses. That paper does not itself present a counterexample.

Verus form. Inference: check residual syntax with a verified AST predicate, but connect that predicate to safety with a theorem about the evaluator. A mere Rust pattern match on generated code is not evidence unless residual equivalence and the False-absence implication are proved.

technique: meta-reasoning about interpreter big-steps

The authors analyze one interpreter “big-step” (the interpreter’s processing of one rewrite step of the interpreted function, Sect. 5.1, p. 66) rather than every low-level reduction. Their main result is Proposition 1 (Sect. 5.2, p. 68): for a pattern in which no variable occurs twice, specializing Match(p0, d, ([])) under the ◁ ∘ ⊮ strategy gives a tree in which, along any path, the pattern argument of each later non-transitive Match configuration is strictly smaller (pj â‰ș pi) than that of its ancestor. The proof (p. 68–69) shows that Turchin’s relation, composed with the embedding, prevents generalization and folding of such configurations inside one big-step. Propositions 2–4 (Sects. 5.3–5.4, pp. 69–71) build on it: Proposition 3 depends on a syntactic property (5.1) of the Synapse model, which the paper calls “very weak” and says can be overcome by an extra Matching argument (Remark 2, p. 70). The paper says Proposition 1 “can be applied to a wide class of interpreters of Turing complete programming languages” (Sect. 1, p. 57); the conclusion lists characterizing suitable interpreters as future work (Sect. 6, p. 72). The detailed unfolding history is in the extended version [34] (arXiv:1705.06738), not in this paper.

They characterize the experiments as partial interpreter verification: “In particular, the experiments can also be considered as a partial verification of the intermediate interpreter used.” (Sect. 5, p. 65). The results cover the interpreter Int and protocol programs whose patterns satisfy the stated restrictions (no variable repeated within a pattern, property 5.1), not arbitrary interpreters.

Verus form. Inference: formalize one interpreter big-step as a recursive spec fn and prove lemmas for each protocol instruction. Use ghost traces to state that generalized configurations over-approximate concrete steps. A complete theorem for arbitrary Turing-complete interpreters is unlikely without strong restrictions.

techniques for the catalog

  • intermediate interpretation — specialize an interpreter with a program model to obtain an indirect residual model
  • first Futamura projection — keep runtime data symbolic while compiling known program structure
  • Turchin supercompilation — unfold, generalize, and fold functional programs under well-founded control
  • residual safety checking — prove safety by showing the residual predicate cannot return False
  • interpreter meta-reasoning — prove regular big-step configuration properties to justify specialization

Last edited: