Formalizing the syntactic approach to type soundness in Twelf

The following Twelf code formalizes small-step semantics for System F with constants and additional typing rules, closely following the `Syntactic Approach to Type Soundness' with contexts, focusing and redexing. The type preservation and progress proofs indeed factor into the type preservation for redexes, and the progress for typed pre-values. Plus the context typing rules, which are shared across the preservation and progress proofs. Indeed, the type preservation theorem, the evaluation of an expression preserves its type,

Monadic (meta)System F

We show an example of formalizing System F extended with a monad, and mechanically proving its type soundness. We literally follow the approach of Moggi and Fagorzi and treat monadic language as a meta-language, with two mutually recursive evaluators. Given our particular choice of monads, the proof of type soundness becomes the proof of safety of a critical section guarded by a recursive lock (so-called `yield property').

Our broad goal is designing a trusted kernel to statically assure safety of a (recursive, multi-level) critical section: the program may re-enter the critical section but must not execute any blocking operation until it fully exited the critical section. To prove that the trusted kernel enforces the yield property we extend our previous syntactic proof for empty-list and array-bound checking: We define a language Strict with a sound type system, then show that any well-typed program in Lax (an idealization of Haskell) that uses only the exported names from the trusted kernel is a translation of a well-typed Strict program.

Our language Strict extends System F with a family of monads mn , one for each natural number n (an interrupt level). To the standard expressions of System F we add do and ret and special operations disable and yield . The latter corresponds to a blocking operation; disable e executes e in a critical section with the interrupts disabled by one level.

The dynamic semantics of Strict is formulated in terms of two mutually recursive reduction functions. The simplification function reduces terms; it is the standard call-by-name reduction of System F terms. The execution function operates on configurations<n,e> where n is the interrupt level and e is a term (of a monadic type). This distinction between two reduction relations is characteristic of monadic metalanguages.

The execution of disable and yield changes and checks the interrupt level. Accordingly, these constructs call for the only typing rules not standard for System F and monads. Not coincidentally, it is these typing rules that assure the yield property. Our mechanized proof of type preservation and progress essentially verifies that the typing rules match the dynamic semantics for disable and yield .

Using Twelf, we demonstrate the fully worked-out formal semantics of Strict and the complete formal proofs of type preservation and progress. Since we have two evaluation relations -- simplification and execution, corresponding to the two levels of the language -- we have to prove progress and preservation for both.

Our semantics is essentially small step, but with an implicit context: we keep focusing until we hit a redex. Our formalized semantics is distinguished from many other formalizations in Twelf by the following features. First, the rules of our reduction relations are free from overlapping (which makes the semantics deterministic). Second, the small-step semantics (albeit more involved than the big-step semantics shown for mini-ML and other examples in the Twelf distribution) gives meaningful progress theorems. Many Twelf formalizations do not distinguish well-typed divergent terms from ill-typed ones: both lead to non-terminating reductions. In contrast, in our dynamic semantics non-sensical terms do get stuck rather than diverge; we prove that well-typed terms do not get stuck.

Hao Chen and Jonathan S. Shapiro. Using Build-Integrated Static Checking to Preserve Correctness Invariants. CCS'04: Proc. 11th ACM conf. on Computer and Communications Security, pp 288-297, 2004.
The paper describes the application of the finite-state model checker MOPS to assure safety properties in the operating system kernel EROS. One of such properties is the `yield' property: the kernel must not call yield() when interrupts are disabled. EROS permits disabling interrupts by multiple levels, that is, multiple times in succession; each disabling must be balanced by exactly one enabling. This corresponds to so-called recursive locks. One may acquire the same lock multiple times, but then one must release it that many times: lock acquisitions and releases must be properly nested. Safe control flows with recursive locks cannot be modeled by a finite-state machine. In order to use MOPS, the authors had to introduce an approximation and limit the recursion depth, to 5.

Yet another explanation of intrinsic encoding

When implementing a domain-specific language (calculus) in a host language such as Haskell, ML, Twelf, Agda, Coq, etc., we represent terms of the domain-specific language (DSL) as expressions or values in the host language. We are often interested in only those DSL terms that satisfy certain invariants: e.g., being well-typed. An extrinsic encoding permits ill-typed DSL terms. We therefore need separate, after-the-fact judgments to identify those DSL terms that satisfy the desired invariant. In contrast, an intrinsic encoding cannot even express the terms that fail the invariant. The invariant is part of the encoding itself. Intrinsic encodings often require dependent types, or at least type-indexed data types (GADT). Tagless-final intrinsic encodings often get by without dependent types.

When implementing a DSL in a host language, we need to encode not only DSL terms but also their semantics. The latter often takes form of a definitional interpreter for the DSL written in the host language. Operational semantics, a mapping from terms to terms, can be encoded as the meta-language function eval . An intrinsic encoding again lets us enforce invariants: A reduction rule that fails the invariant cannot even be defined. A useful invariant to encode is subject reduction: eval preserves DSL types of the evaluated expressions. The progress property -- every well-typed non-value can be reduced -- can also be represented in the encoding, a bit less directly. Intrinsic encodings thus greatly simplify meta-theoretic proofs such as type soundness.

For example, we define (in Twelf) one-step reduction as:

eval : {E:exp T} non-value E -> exp T -> type.

That definition itself is the statement of subject reduction: any expression E of type T that is not a value is reduced to an expression of the same type T. Furthermore, eval is total: every non-value can be further reduced. This is the statement of progress. We supply the totality declaration, and Twelf agrees.

Thanks to Robert Harper, Dan Licata, William Lovas and Tom Murphy for helpful discussions about the intrinsic encoding. Thanks to Chung-chieh Shan for pointing out the connection between tagless final DSL embeddings and intrinsic encodings in Twelf.

tagless-l.elf [8K]
Intrinsic, higher-order abstract syntax encoding of simply-typed call-by-value lambda-calculus with integers, arithmetic, and fix-point. The dynamic semantics is small step, with no explicit contexts: neither on the object level, nor on the meta-level (All world declarations are empty). This Twelf code defines the evaluator and the mechanized proofs of type soundness (subject reduction and progress). The style of proofs in this file is used to prove type soundness of the calculus with shift/reset and effect typing.

Mechanized proofs of type soundness of delimited control

We present two very different mechanized proofs of soundness of a type-and-effect system for delimited control operators shift/reset. Our calculus is call-by-value simply-typed lambda-calculus with constants and answer-type-modifying delimited control. The dynamic semantics is small-step. This is the calculus originally proposed by Danvy and Filinski, enhanced by Asai and Kameyama (APLAS07) with let-bound polymorphism. The constants of the calculus may include natural numbers, arithmetic, and the fixpoint. The type of a general expression specifies not only the type of the produced value but also the effect of the expression. In the simplest case, the effect may be raising of an exception; more complex effects include co-routine invocations or backtracking. The type of a functional value is not just a->b but rather a/t1->b/t2 , so to describe effects that may occur when the function is applied. Despite the more complex expression and arrow types, all types are inferred and no type annotations are required. This is joint work with Kenichi Asai, Noriko Hirota, Yukiyoshi Kameyama and Chung-chieh Shan.

One mechanized soundness proof is done in Coq with a locally nameless methodology. The calculus includes the let-bound polymorphism but omits, for simplicity, the fixpoint and constants. This development deals only with meta-theory: it proves subject reduction and progress properties, but does not infer types and does not evaluate expressions.

We have also mechanized type soundness in Twelf, using the intrinsic encoding of the calculus. We present not only progress and subject reduction proofs but also the type inferencer and the evaluator: we can run sample terms and see their types. Our code includes the standard shift/reset test suite. It is Twelf itself that infers and checks object language types. We did not specify the type-checking relation since terms not typeable in the object language cannot be built. More remarkably, the one-step evaluator of our calculus is also the proof term of type soundness. In other words, the very proof of type soundness can be used to evaluate sample expressions. Proofs literally have a practical use.

README.dr [2K]
The Isabelle/HOL development by Kenichi Asai, Ochanomizu University, 2005.
He has formalized Danvy/Filinski calculus with the type-and-effect system, without let-polymorphism. He has proved subject reduction.

Mechanizing multilevel metatheory with control effects

We have mechanized the type soundness proof for the first sound
multilevel calculus with control effects. The calculus (an extension
of Kameyama et al., PEPM09) lets us write direct-style generators
that memoize open code. Our mechanization overcomes two challenges:
first, to intrinsically encode an object calculus whose typing
judgements involve non-trivial type functions; second, to represent
open code and especially evaluation contexts containing variable
binders. These challenges and the necessary small-step operational
semantics recommend multilevel calculi with effects as a benchmark of
mechanized metatheory.

wmm-talk-notes.pdf [146K]
The annotated slides of the talk presented at the 5th ACM SIGPLAN Workshop on Mechanizing Metatheory. September 25, 2010. Baltimore, Maryland.

circle-shift-1.elf [50K]
Warm-up: two-stage lambda calculus with delimited control effects
This Twelf code defines the calculus, specifies its static and dynamic semantics, and proves the type soundness. The code contains many examples, including the staged Gibonacci example with memoization and let-insertion.

circle-shift-n.elf [48K]
Multi-staged calculus with delimited control
This is the main development, for arbitrarily many stages, described in the talk and the abstract.

Strengthening in Logical Frameworks

Many logic systems admit a weakening principle: a derived conclusion continues to hold when more assumptions are added. Strengthening is the opposite: proving that the established conclusion holds given fewer assumptions. Strengthening certainly is not generally valid and has to be proven case-by-case. It seems strengthening is often needed when proving meta-theoretical properties such as totality of non-surjective endomorphisms of higher-order terms.

The article on the Twelf Wiki shows an example requiring strengthening and describes one solution, which relies on defining term equality. In this article we demonstrate simpler examples of strengthening, and two surprisingly trivial solutions. Quite literally, one solution deals with the problem head-on, whereas the second one avoids the problem in a round-about way. The examples are inspired by staging calculi. At the end we describe the real development, which gives perhaps the first formal proof of type preservation of primitive reductions in an expressive staged calculus with escapes, run and cross-staged persistence. The type-preservation proof uses both of our solutions and requires two strengthening lemmas. Throughout we use Twelf and the higher-order abstract syntax (HOAS) for terms, without ever resorting to concrete syntax for `variables'.

< http://twelf.plparty.org/wiki/Strengthening >
The Twelf Project: the tutorial on Strengthening
The running example is proving that evaluation in the language with locations preserves the well-formedness of terms with respect to the store (no dangling references or loops).

translation-problem.elf [7K]
More realistic example of strengthening, when proving totality or other meta-theoretical property of a translation from one higher-order abstract syntax expression to another

Proving termination of interesting functions

Termination checkers of Twelf, Agda and Coq relatively easily prove termination of structurally inductive functions and relations. Relying on lexicographic ordering, the termination checker of Twelf can handle even Ackermann-like functions. Alas, termination of many functions is much harder to see; often we end up proving not mere termination but detailed properties of function's behavior.

A good example is McCarthy's function 91:

f91(n,k) = if k <= n then n else f91(f91(n+1,k),k)

It is straightforward to implement in Twelf. Since the function is not obviously structurally recursive, proving its termination requires advanced reasoning, beyond the abilities of the termination checker. We demonstrate how to do this advanced reasoning and so convince Twelf the function is terminating -- without changing the definition of the function.

We end up proving a stronger result about the function: for all natural numbers i and n , f91(n,i+n) = i+n . We state this property and constructively prove it in Twelf. The proof relies on the lexicographic structural recursion and ex falso quodlibet.