Overcoming Restraint: Modular Refinement using Cogent's Principled Foreign Function Interface
OOvercoming Restraint: Modular Refinement usingCogent’s Principled Foreign Function Interface
Louis Cheung ! UNSW Sydney, Australia
Liam O’Connor ! ˇ University of Edinburgh, United Kingdom
Christine Rizkallah ! ˇ UNSW Sydney, Australia
Abstract
Cogent is a restricted functional language designed to reduce the cost of developing verifiedsystems code. However,
Cogent does not support recursion nor iteration, and its type systemimposes restrictions that are sometimes too strong for low-level system programming. To overcomethese restrictions,
Cogent provides a foreign function interface (FFI) between
Cogent and Cwhich allows for implementing those parts of the system which cannot be expressed in
Cogent ,such as data structures and iterators over these data structures, to be implemented in C andcalled from
Cogent . The
Cogent framework automatically guarantees correctness of the overall
Cogent -C system when provided proofs that the C components are functionally correct and satisfy
Cogent ’s FFI constraints. We previously implemented file systems in
Cogent and verified key filesystem operations. However, the C components and the FFI constraints that define the
Cogent -Cinteroperability were axiomatized. In this paper, we verify the correctness and FFI constraints ofthe C implementation of word arrays used in the file systems. We demonstrate how these proofsmodularly compose with existing
Cogent theorems and result in a functional correctness theoremof the overall
Cogent -C system. This demonstrates that
Cogent ’s FFI constraints ensure correctand safe inter-language interoperability.
Keywords and phrases compilers, verification, type-systems, language interoperability, data-structures
Acknowledgements
We thank Abdallah Saffidine, Amos Robinson, Jashank Jeremy, and VincentJackson for their feedback on the paper. Special thanks to Jashank for the heavy editing. Wehighly appreciate Vincent’s proof engineering work, over the past couple of years, on maintaining thecompiler proof chain and the BilbyFs proofs on ever-changing versions
Cogent and Isabelle/HOL.
Cogent [14] is a restricted pure functional language and a certifying compiler [17, 14]designed to ease creating high-assurance systems [3]. It has a foreign function interface(FFI) that enables implementing parts of a system in C.
Cogent ’s main restrictions are thepurposeful lack of recursion and iteration, which ensures totality, and its uniqueness typesystem , which enforces a uniqueness invariant that guarantees memory safety.Even in the restricted target domains of
Cogent , real programs contain some amountof iteration, primarily over specific data structures. This is achieved through
Cogent ’sintegrated FFI: engineers provide data structures and functions to operate on these datastructures, including iterators, in a special dialect of C, and imports them into
Cogent ,including in formal reasoning. The special C code, called anti-quoted
C, is C code thatcan refer to
Cogent data types and functions, translated into standard C along with the
Cogent program by the
Cogent compiler. As long as the C components respect
Cogent ’sforeign function interface — i.e., are correct and respect the uniqueness invariant — the
Cogent framework guarantees soundness for overall
Cogent -C system. a r X i v : . [ c s . P L ] F e b Modular Refinement using Cogent’s Principled Foreign Function Interface
We previously implemented two real-world Linux file systems in
Cogent — ext2 andBilbyFs — and verified key operations of BilbyFs [3]. This demonstrated Cogent ’s suitabilityfor implementing systems code, and significantly reducing the cost of verification. Theimplementations of ext2 and BilbyFs rely on an external C library of data structures. Thislibrary includes fixed-length word arrays along with iterators for implementing loops, and
Cogent stubs for accessing a range of the Linux kernel’s internal APIs. This library wascarefully designed to ensure compatibility with
Cogent ’s FFI constraints. We had previouslyonly verified the
Cogent parts of these file system operations, and axiomatized statementsof the underlying C correctness and FFI constraints defining
Cogent -C interoperability.To fully verify a system written in
Cogent and C, one needs to provide manually-writtenabstractions of the C parts, and manually prove refinement through
Cogent ’s FFI. Theeffort required for this manual verification remains substantial, but the reusability of theselibraries allows for the amortisation this verification cost across different systems.In this paper, we verify the correctness and uniqueness invariants of the word array imple-mentation used in the BilbyFs file system. This demonstrates that is it possible for the Ccomponents of a real-world
Cogent -C system to satisfy
Cogent ’s FFI conditions. Wedemonstrate that the compiler-generated refinement proofs guaranteeing semantic preser-vation and memory safety compose with manual C proofs at each intermediate level up to
Cogent ’s generated shallow embedding.Our specification of word arrays as Isabelle/HOL lists and verification of array operationsagainst abstract Isabelle/HOL functions on lists, including an array iterator specified asa map-accumulate function over a fragment of a list, are reusable beyond the context of
Cogent . Moreover, our proofs are reusable in the verification of any future
Cogent -Csystem that uses word arrays.Our results show that
Cogent ’s two-language approach is not only suitable for developingreal-world systems but also for verification. We show how to modularly compose compilergenerated theorems with external theorems. This demonstrates how language interoperabilitycan help guarantee correctness of an overall system.More generally, this work provides our community with a case-study demonstratinghow a principled foreign function interface between a high-level functional language witha safe type-system and a low-level imperative language with an unsafe type system can bestructured in order to modularly achieve refinement guarantees of the overall system. Inparticular, our work supports, and is well-described by, Ahmed’s claim that: “compositionalcompiler correctness is, in essence, a language interoperability problem: for viable solutionsin the long term, high-level languages must be equipped with principled foreign-functioninterfaces that specify safe interoperability between high-level and low-level components, andbetween more precisely and less precisely typed code.” [1] Our code and proofs are availableonline [4]. The
Cogent programming language [14] is intended for the implementation of operatingsystem components such as file systems. It is a purely functional language, but it can becompiled into efficient C code suitable for systems programming.The
Cogent compiler produces three artefacts: C code, a shallow embedding of the
Cogent code in Isabelle/HOL [13], and a formal refinement theorem relating the two [14, 17](arrow (1) in Figure 1). The refinement theorem and proof rely on several intermediateembeddings also generated by the
Cogent compiler, some related through language level . Cheung, L. O’Connor, and C. Rizkallah 3
Verification Framework
CorrectnessCogent Shallow Embeddingrefines ⊑ C System ( imported to Isabelle ) Polymorphic Cogent ( value semantics ) Monomorphic Cogent ( value semantics ) refines ⊑ refines ⊑ Cogent Shallow Embeddingrefines ⊑ C Data - structure ( imported to Isabelle ) Polymorphic Cogent ( value semantics ) ξ v Monomorphic Cogent ( value semantics ) ξ v Monomorphic Cogent ( update semantics ) ξ u refines+ safe ⊑ refines ⊑ refines ⊑ refines ⊑ Monomorphic Cogent ( update semantics ) refines+ safe ⊑ Cogent Program (4)
Cogent compiler (5)(1)
Isabelle/HOL
Systems
C code
Memory Safety Data-structuresCorrectness manualgenerated legend interfacemanual proofsgenerated proofs
Template C Data-structures (2) (6)(3)
Data-structuresSystems
Overview
Figure 1
An overview of the
Cogent framework (left) and the verification phases (right). proofs, and others through translation validation phases (Section 2.3). The compiler certificateguarantees that correctness theorems proven on top of the shallow embedding also holdfor the generated C, which eases verification, and serves as the basis for further functionalcorrectness proofs (Figure 1, arrow (3)).A key part of the compiler certificate describes
Cogent ’s uniqueness type system , whichenforces that each mutable heap object has exactly one active pointer in scope at any point intime. This uniqueness invariant allows modelling imperative computations as pure functions:the allocations and repeated copying commonly found in functional programming can bereplaced with destructive updates, and the need for garbage collection is eliminated, resultingin predictable and efficient code.Well-typed Cogent programs have two interpretations: a pure-functional value semantics ,which has no notion of a heap and treats all objects as immutable values, and an imperative update semantics , describing the destructive mutation of heap objects. These two semanticinterpretations correspond (Section 2.3), meaning that any reasoning about the functionalsemantics also applies to the imperative semantics. This correspondence further guaranteesthat well-typed
Cogent programs are memory safe (Figure 1, arrow (2)).As mentioned, the underlying C data structures of our two file systems were carefullydesigned to ensure compatibility with
Cogent ’s FFI constraints (Section 2.2). But theverification of the file systems previously assumed this compatibility (Figure 1, arrows (4), (5))as well as the correctness of the C code (Figure 1, arrow (6)). Our verification dischargesthese assumptions, giving us greater confidence in both the file systems and
Cogent ’s FFI.
Cogent has unit, numeric, and boolean primitive types, as well as functions, sum types (i.e.,variants) and product types (i.e., tuples and records). Users can declare additional abstract
Modular Refinement using Cogent’s Principled Foreign Function Interface types τ, τ ′ ::= () | Bool | U8 | U16 | U32 | U64 ( primitive types ) | ( τ, τ ′ ) ( tuples ) | ⟨ K τ ⟩ ( variant types - constructor K ) | { f i : τ i } ( record types - field names f ) | A τ ( abstract types - constructor A )Overlines indicate a set (order is not important). Figure 2
Grammar for desugared types in core
Cogent . types in Cogent , and define them externally (e.g., in C).
Cogent does not support closures,so partial application via currying is not common. Thus, functions of multiple argumentstake a tuple or record of those arguments.
Cogent types are shown in Figure 2.Similar to ML,
Cogent supports parametric polymorphism on top-level functions. Froma polymorphic C template, the compiler generates multiple specialised C implementations,one for each concrete instantiation used in the
Cogent code. Variables of polymorphic typeare by default linear (must be used exactly once). This allows the polymorphic type variableto be instantiated to any type.Types and functions provided in external C code are called abstract in Cogent . Asmentioned, The
Cogent compiler has an infrastructure for linking the C implementationsand the compiled
Cogent code, and allows embedding
Cogent types and expressionsinside the C code using quasi-quotation. Abstract types may also be given type parameters.Polymorphic functions and parameterised types are translated into a family of automaticallygenerated C functions and types for each concrete type used in
Cogent . ▶ Example 1 (Array) . type Array a length : ( Array a )! → U32 get : (( Array a )!, U32 ) → a put : ( Array a , U32 , a ) → Array a Example 1 is a
Cogent program that declares an externally-defined array library interface,where array indices and the array length are unsigned 32-bit integers,
U32 .Though the
Array type interface may appear purely functional,
Cogent assumes that allabstract types are linear , ensuring that the uniqueness invariant applies to variables of type
Array . Therefore, any implementation of the abstract put function is free to destructivelyupdate the provided
Array , without contradicting the purely functional semantics of
Cogent .When functions only need to read from a data structure, uniqueness types can complicate aprogram unnecessarily by requiring a programmer to thread through all state, even unchangedstate. The !-operator helps to avoid this by converting linear , writable types to read-only types that can be freely shared or discarded. This is analogous to a borrow in Rust. The length and get functions can read from the given array, but may not write to it. Cogent ensures that in
Cogent code, read-only references are never simultaneouslylive with writable references. This restriction is necessary to be able to reason equationallyabout
Cogent programs, and to preserve the refinement theorem connecting
Cogent ’s twosemantics. This condition is assumed of abstract functions by the
Cogent semantics andmust be proven of the C implementations for pointers that are visible to
Cogent .As
Cogent does not support recursion, iteration is expressed using abstract higher-orderfunctions , providing basic traversal combinators such as map and fold for abstract types. ▶ Example 2 (Simple Array Iterators) . map : ( a → a , Array a ) → Array a fold : (( a !, b ) → b , b , ( Array a )!) → b . Cheung, L. O’Connor, and C. Rizkallah 5 In Example 2, the map is passed a function of type a → a . As such, it is able to destructivelyoverwrite the array with the result of the function applied to each element.While Cogent supports higher-order functions — functions that accept functions asarguments or return functions — it does not support nested lambda abstractions or closures,as these can require allocation if they capture variables. Thus, to invoke the map or fold functions, a separate top-level function must be defined, such as in this function to sum anarray of 32-bit unsigned integers: ▶ Example 3 (Sum) . add : ( U32 , U32 ) → U32 add ( x , y ) = x + y sum : ( Array
U32 ) ! → U32 sum arr = fold ( add , 0, arr )Instead of relying on closure captures, we provide alternative iterator functions which carryan additional observer read-only input (of type c !). Example 4 presents the types of the wordarray iterators mapAccum and fold that are used in BilbyFs and ext2 . They account forthis extra observer input and allow for iterating over an interval of the array. The function mapAccum is a generalised version of map , similar to that in Haskell. It allows threadingan accumulating argument through the map function. We present the verification of theseiterators in Section 3. ▶ Example 4 (Iterators) . mapAccum : { f: ( a , b c !) → ( a , b ), acc: b , arr: ( Array a )!, frm: U32 , to:
U32 , obsv: c ! } → ( Array a , b ) fold : { f: ( a , b c !) → b , acc: b , arr: ( Array a )!, frm: U32 , to:
U32 , obsv: c ! } → b We re-frame our sum example in terms of BilbyFs’s generalised version of fold and presentits verification in Section 4.1. The observable input is not needed, so we pass the unit type,which also allows us to omit the !. ▶ Example 5 (Sum) . add : ( U32 , U32 , ()) → U32 add ( x , y , z ) = x + y sum : ( Array
U32 ) ! → U32 sum arr = fold { f: add , acc:0, arr: arr , frm:0, to: length x , obsv:() } Cogent ’s big-step value semantics is defined through the judgement ξ v , V ⊢ e ⇓ v v . Givena function ξ v : f id → ( v → v ), that for an abstract function with identifier f id defines itsvalue semantics, this judgement evaluates an expression e under a context V to a value v .Its imperative update semantics, which additionally may manipulate a store µ , is definedthrough the judgement ξ u , U ⊢ e | µ ⇓ u u | µ ′ . Given a function ξ u : f id → ( u × µ → u × µ ) thatsimilarly defines the update semantics of abstract functions, this judgement maps a context U , an expression e , and a store µ to a value u and a new store µ ′ . The correspondencebetween Cogent ’s two semantics relies on a uniqueness invariant and on type preservation.To ensure memory safety, Cogent’s FFI places restrictions on how evaluation may affectthe heap. Given an input set of writable pointers w i , an input heap µ i , an output setof pointers w o and an output heap µ o , the relation, w i | µ i frame w o | µ o , ensures three Modular Refinement using Cogent’s Principled Foreign Function Interface properties for any pointer p :inertia: p / ∈ w i ∪ w o −→ µ i ( p ) = µ o ( p )leak freedom: p ∈ w i −→ p / ∈ w o −→ µ o ( p ) = ⊥ fresh allocation: p / ∈ w i −→ p ∈ w o −→ µ i ( p ) = ⊥ Inertia ensures that pointers not in the frame remain unchanged; leak freedom ensures thatpointers removed from the frame no longer point to anything; and fresh allocation ensuresthat pointers added to the frame were not initially pointing to anything. The framingrelation implies that the correspondence relation between the value and update semantics isunaffected by updates to separate parts of the heap.
Figure 1 gives an overview of the verification framework. The overall proof that the C coderefines the purely functional shallow embedding in Isabelle/HOL is broken into a numberof sub-proofs and translation validation phases. The compiler generates four embeddings:a top-level shallow embedding in terms of pure functions; a polymorphic deep embeddingof the
Cogent program, which is interpreted using the value semantics; a monomorphicdeep embedding of the
Cogent program, which can be interpreted using either the value or update semantics; and an Isabelle/HOL representations of the C code generated by thecompiler, imported into Isabelle/HOL by the C-parser used in the seL4 project [8]. The Cparser generates a deep embedding of C in Isabelle/HOL, and, using AutoCorres [5, 6], isthen abstracted to a corresponding monadic embedding.The compiler also generates proofs that the program is well-typed, that the pure interpret-ation of the polymorphic deep embedding is a refinement of the top-level shallow embedding,that the monomorphic embedding is a refinement of the polymorphic one, and that the Ccode is a refinement of the imperative interpretation of the monomorphic deep embedding.These refinement proofs, along with the typing proof and the refinement theorem betweenthe two semantic interpretations, are composed to produce a refinement theorem stretchingfrom the C code all the way up to the pure shallow embedding. The static semantics are defined through a standard typing judgement A ; Γ ⊢ e : τ , with anadditional context A that tracks assumptions about the linearity of type variables in τ .Dynamic values in the value semantics are typed by the simple judgement v : τ , whereasupdate semantics values must be typed with the store µ to type the parts of the value thatare on the heap. Update semantics values are typed by the judgement u | µ : τ [ r ∗ w ], whichadditionally includes the heap footprint , consisting of the sets of read-only ( r ) and writable( w ) pointers the value can access. Disjointness requirements are added to the definition ofthis judgement to ensure that the uniqueness invariant is maintained. We use the samenotation for value typing on contexts.Each of the key refinement and typing theorems for Cogent code must be manually provedabout the semantics of our abstract functions ξ v and ξ u .For our value semantics functions, functions, our type preservation lemma statements areof the form: ▶ Theorem 6 ( Value
Semantics Type Preservation) . A ; Γ ⊢ e : τ ∧ ξ v , V ⊢ e ⇓ v v −→ v : τ . Cheung, L. O’Connor, and C. Rizkallah 7 Or in other words, if the expression e is of type τ under the typing context Γ and typingassumptions A ; the evaluation environment V has types Γ; and e evaluates to v under V and abstract function environment ξ v , then v has type τ . To handle abstract higher orderfunctions, the abstract function environment ξ v contains all lower-order functions, and onlyabstract functions defined in ξ v can be evaluated. The typing theorems are progressivelybuilt up from first order to second order and so on. In our libraries, second-order functionsare the highest order needed.In the update semantics, we prove type preservation and the frame constraints simultan-eously, as the heap footprints in the value typing relation shows which pointers are accessiblefrom a value, and the frame relation ensures that the evaluation of update semantics functionsdoes indeed only access pointers in the heap footprint. The lemma statements for provingtype preservation and the frame constraint in update semantics for update semantics functionsare of the form: ▶ Theorem 7 ( Update
Semantics Type Preservation and Frame Constraint) . A ; Γ ⊢ e : τ ∧ U | µ : Γ[ r ∗ w ] ∧ ξ u , U ⊢ e | µ ⇓ u u | µ ′ −→∃ r ′ w ′ . r ′ ⊆ r ∧ w | µ frame w ′ | µ ′ ∧ u | µ : τ [ r ′ ∗ w ′ ]For functions written in Cogent, this theorem is proven once and for all, but for ourabstract functions, we proved these statements manually. The proof follows from thedefinitions of value typing and, in the update semantics, the frame relation.To show correspondence of the two semantics, we combine these two value typing relationsinto one combined judgement u | µ : v : τ [ r ∗ w ]. We show this relation is preserved acrossevaluation of both semantics and use it to show the refinement theorem connecting the twosemantics: ▶ Theorem 8 (Value ⇒ Update refinement) . For any program e where A ; Γ ⊢ e : τ , if U | µ : V : Γ [ r ∗ w ] and U ⊢ e | µ ⇓ u u | µ ′ , then there exists a value v and pointer sets r ′ ⊆ r and w ′ such that V ⊢ e ⇓ v v , and u | µ ′ : v : τ [ r ′ ∗ w ′ ] and w | µ frame w ′ | µ ′ . Just as with typing preservation, this relation is automatically preserved for Cogentfunctions, but must be proven manually for our abstract functions.Specifically, we show the type preservation theorems hold for these functions: for anyabstract function f of type τ → τ ′ , if the argument values u and v correspond and are welltyped i.e. if ( v : τ ), ( u | µ : τ [ r ∗ w ]), ξ v f v = v ′ , and ξ u f ( u, µ ) = ( u ′ , µ ′ ), then v ′ : τ and u ′ | µ ′ : τ ′ [ r ′ ∗ w ′ ] for some r ′ ⊆ r and w ′ such that w | µ frame w ′ | µ ′ . The well-typednessrequirement ensures that the returned value does not include internal aliasing, and the frame requirements ensure that the abstract function does not manipulate any state otherthan values passed into it. In order to show refinement, we must also assume that abstractfunctions evaluate in the value semantics whenever they evaluate in the update semantics. Word arrays are arrays stored on the heap with elements having any primitive numeric typesupported by
Cogent (Figure 2). We verify five array operations: the length , get , and put functions in Example 1, and the fold and mapAccum iterators in Example 4. Word arrays are abstracted as Isabelle/HOL lists. Operations on word arrays are specifiedas Isabelle functions on lists. The specification for the word array functions presented in
Modular Refinement using Cogent’s Principled Foreign Function Interface length : [ a ] → word32 length xs = of _ nat ( List . length xs ) get : [ a ] → word32 → a get xs i = if unat i < List . length then xs ! ( unat i ) else put : [ a ] → word32 → a → [ a ] put xs i v = xs [ unat i := v ] fold : ( b → a → c → b ) → b → [ a ] → word32 → word32 → c → b fold f acc xs frm to obs = List . fold ( λ acc el . f acc el obs ) acc ( slice frm to xs ) mapAccum : ( b → a → c → ( a , b )) → b → [ a ] → word32 → word32 → c → ([ a ], b ) mapAccum f acc xs frm to obs = let ( xs ′ , acc ′ ) = List . fold ( λ el ( xs , acc ). let ( el ′ , acc ′ ) = f acc el obs in ( xs @ [ el ′ ], acc ′ )) ( slice frm to xs ) ([], acc ) in ( take frm xs @ xs ′ @ drop ( max frm to ) xs , acc ′ ) Figure 3
Functional correctness specification of the word array operations in Isabelle/HOL.
Examples 1 and 4 is provided in Figure 3. The majority of the word array operations behavesimilar to Isabelle/HOL’s list operations. The Isabelle library function unat converts a 32-bitword to a natural number, of _ nat converts a natural number to a 32-bit word, take n xs returns the first n elements of the list xs , drop n xs removes the first n elements of xs thefirst n elements removed from xs , slice n m xs returns the sublist that starts at index n andends at index m of the list xs , List . length returns the length of a list, List . fold is the foldfunction for lists, and @ is list concatenate.One exception is get , which behaves differently to its corresponding list operation: ourimplementation of get does not cause an exception when the given index is out of bounds,but instead returns the value 0. Another exception is mapAccum , which is not part of thestandard list library in Isabelle/HOL, and must be implemented in terms of fold . The
Cogent compiler automatically proves refinement
Cogent functions in four mainphases, these are: C to update , update to monomorphic value , monomorphic to polymorphic value , and polymorphic value to shallow. This means that the compiler must automaticallygenerate for each semantic layer an embedding of the program, and a value relation for all Cogent types between the semantic layer in each phase. Since
Cogent ’s refinement proofrelies on the typing information to show that no aliasing occurs, a value typing relation foreach
Cogent type in the update and value semantics is also generated.In our verification, we prove refinement analogously to the automatic proofs of Cogentcode. Thus we also define embeddings for word arrays and their operations for each semanticlayer; value relations for word arrays between each semantic layer; and value typing for wordarray in both the update semantics and the value semantics.
Embeddings
Since we use AutoCorres in our verification, we do not need to define our own embedding ofword arrays and our functions for the C semantic layer as AutoCorres automatically does thisfor us. For the top-level shallow semantic layer, we have already defined our specification for . Cheung, L. O’Connor, and C. Rizkallah 9 datatype UArray = UWA “ type ” “ word32 ” “ ptrtyp ” upd _ length ( µ , x ) ( µ ’, y ) = ∃ t len arr . ( x = UWA t lenarr ) ∧ ( y = len ) ∧ ( µ = µ ’) (a) Deep embedding in the update semantics. datatype ( a , b ) VArray = VWA “ type ” “( a , b ) vval ” val _ length x y = ∃ t xs . ( x = VWA t xs ) ∧ ( List . length xs = unat y ) (b) Deep embedding in the value semantics.
Figure 4
Deep embedding of word arrays and length operation in Isabelle/HOL. word arrays and its operations in Isabelle/HOL in our axiomatisation, so this specificationforms our embedding for the shallow semantic layer.Our embedding of word arrays in the update semantics, shown in Figure 4a, contains thetype of its elements, the length of the array, and the pointer to the first element in the array.This is very similar to the word array struct in C. It differs slightly, because it containsthe type of its elements, so that we can reuse the embedding for word arrays with differentelement types. The similarity with the word array struct in C has the benefit of makingthe value relation very simple, i.e., if the lengths and pointers are equal then they are related.In addition, we can make all pointers associated to a word array visible from
Cogent , whichin turn forces us to prove memory safety of all pointers associated to a word array. The wordarray operations are defined as input-output relations, where Figure 4a shows the embeddingfor length .The embedding of word arrays in the value semantics, shown in Figure 4b, consists thetype of its elements and a list of values, which should be pairwise related to each of itselements. The value semantics embedding is very similar to the shallow embedding of wordarrays, which is simply a list containing all of its elements, so that the value relation betweenthe shallow and the value semantics is simple. The embeddings for the word array operationswere also defined as input-output relations, and is similar to the corresponding embeddingsin the update semantics, as shown in Figure 4b.
Value Typing
In our verification, word arrays may only have elements which are primitive numeric types.This makes the value typing relation fairly simple, as the value typing of primitive numerictypes is also simple.The value typing in the value semantics is defined as: ▶ Definition 9. ( VWA t xs ) : n τ s s =( n = “Array” ) ∧ ( τ s = [ t ]) ∧ ( ∀ i < length xs . xs ! i : t )An abstract type consists of its name, a list of type arguments and its sigil, i.e., if it is onthe heap and if it is read-only or not. Hence our value relation checks that the value is ofthe correct type by checking the name matches and the type in the embedding matches thetype list. It also checks each element value types to the type in the type list, i.e., the wordarray’s element type.In the update semantics, the value typing is defined as: ▶ Definition 10 (Update Semantics: Value Typing for Array) . ( UWA t len p ) | µ : ( n τ s s )[ r ∗ w ] =( n = “Array” ) ∧ ( τ s = [ t ]) ∧ ( unat ( len ) × size ( t ) ≤ max _ word ) ∧ ( ∀ i < len . ∃ v. µ ( p + size ( t ) × i ) = Some v ∧ v | µ : t [ ∅ ∗ ∅ ]) ∧ ( s = readonly −→ r = { p + i | ∀ i. i < len } ∧ w = ∅ ) ∧ ( s = writable −→ w = { p + i | ∀ i. i < len } ∧ r = ∅ ) where max _ word is n − where n is the number of bits in the word. This checks for the same things as the value typing in value semantics , and also checks forthe following: if the word array is read-only then all of its pointers are read-only, otherwisethey are writable; all pointers point valid elements; and the size of the array is less than themaximum size of the heap. The constraint on the length ensures that the array does wrapon itself.As mentioned earlier,
Cogent has constraints on the value typing relation for abstracttypes. In our case, these constraints were fairly easy to discharge as they followed from thedefinition our value relation or case analysis.
Value Relation
As mentioned earlier, the value relations between the C and update , and the value andshallow, are very simple. In the former, the two a related if the length values and the pointervalues are equal, and the element types are related. In the latter, the two are related if thelength of the lists are the same, the elements are pairwise related, and the element types arerelated. For example, the value relation between the update and C is ▶ Definition 11 (Value Relation between
Update and C for 32-bit Word Arrays) . V C ( UWA t len arr, x ) = ( t = U32 ) ∧ ( len = len C x ) ∧ ( arr = arr C x ) where len C and arr C extract the length of the array pointer to the first element of the arrayfrom the word array embedding in the C semantics. The value relation between the update and the value semantics was also fairly straightfor-ward, since a word array value in the update semantics is related to a word array value inthe value semantics if the lengths are the same, the elements are pairwise related, and theelement types are the same. The value relation also calls the value typing relations to ensurethat the word array values are valid.
Refinement
Once we have the embeddings, value typing and value relations, we have all the ingredientswe need to prove refinement in each phase for our abstract functions. Below we outline howto prove refinement for first order abstract functions. This is similar for second order abstractfunctions except that we also assume that we can prove refinement for all first order abstractfunctions.The refinement relation corres between C and the update semantics is defined as: . Cheung, L. O’Connor, and C. Rizkallah 11 ▶ Definition 12 (C ⇒ Update correspondence) . corres R e p m ξ u U Γ µ σ =( ∃ r w. U | µ : Γ[ r ∗ w ]) −→ ( µ, σ ) ∈ R −→ ( ¬ failed ( p m σ ) ∧ ( ∀ v m σ ′ . ( v m , σ ′ ) ∈ results ( p m σ ) −→ ( ∃ µ ′ u. ξ u , U ⊢ e | µ ⇓ u u | µ ′ ∧ ( µ ′ , σ ′ ) ∈ R ∧ V C ( u, v m ))))In essence, this means that if the arguments are of the correct type, the Cogent state µ andC state σ correspond, i.e., in the set of corresponding states R , and if the C computationsucceeds, i.e., has no undefined behaviour as indicated by ¬ failed , then the corresponding update semantics embedding evaluates, and the resulting values and states correspond, where results is a set of pairs that consist of return values and new C states. We can then provethat if the arguments to the functions are related then we have corres between the C and update semantics. More formally, we have: ▶ Theorem 13 (C ⇒ Update refinement) . ∀ u v m . V C ( u, v m ) −→ corres R ( f x ) p m ξ u ( x u ) ( x : τ ) µ σ To bridge the gap between the update semantics and value semantics, we prove arefinement theorem that depends on value typing preservation (Theorem 7, Theorem 6), theframe relation, and the semantic correspondence (Theorem 8), as described in Subsection 2.4.To make the shift from monomorphic to polymorphic, we prove that if evaluation occursafter monomorphising expressions ( M e ) with the mapping r pm and similarly with values( M v ), then evaluation occurs without monomorphising. More formally, we prove: ▶ Theorem 14 (Monomorphisation) . ∀ v ′ . ξ m , ( x
7→ M v ( r pm , v )) ⊢ M e ( r pm , ( f x )) ⇓ v M v ( r pm , v ′ ) −→ ξ p , ( x v ) ⊢ ( f x ) ⇓ v v ′ Note that ξ m is the monomorphised abstract function environment of ξ p .Finally, we bridge the gap between deep and shallow, i.e., our functional correctnessspecification by showing the following: ▶ Theorem 15 (Polymorphic
Value ⇒ Shallow refinement) . ∀ v s v. V S ( v s , v ) −→ (( f x ) sh ⊑ ( p s v s )) ( x v ) ξ v where the refinement relation ( s sh ⊑ e ) Vξ v is defined as ∀ r. ξ v , V ⊢ e ⇓ v r −→ V S ( ξ v s, r ) . Together, this means that if the arguments are value related, and if the deep embeddingexecutes, then so will the shallow and the results will be related.For abstract functions that do not call other functions, the proofs of Theorems 8 and 13–15followed from the evaluation rules and the relevant definitions.For abstract functions that call other functions, we prove theorems that are variantsof Theorems 8 and 13–15, which assumes that Theorems 6–8 and 13–15 holds for all theirarguments, which are functions, and that they are type correct. These then followed fromthe assumptions, evaluation rules, and the relevant definitions. Our proofs can be reused fordifferent word sizes, e.g., 32-bit and 64-bit, with only minor changes.
The
Cogent compiler automatically generates the type correctness proofs for all functionsand the refinement proofs for each phase for all
Cogent functions. For
Cogent functionsthat do not call any abstract functions, it will emit the theorem: ▶ Theorem 16 (Shallow ⇒ C refinement) . If all the abstract functions defined in ξ u and ξ v preserve typing and the frame constraints inthe update and monomorphic value semantics respectively, then: ∀ µ σ. V ( r pm , ξ p , µ, v c , v u , v p , v s ) µ ∧ ξ m ∼ A M ev r pm ξ p ∧ V : Γ ∧ ξ u ∼ A ξ m −→ correspondence r pm R ( p s v s ) ( f x ) p c ξ u ξ m ξ p U V Γ µ σ where U = ( x v u ) , V = ( x
7→ M v ( r pm , v p )) , and Γ = ( x τ ) . Essentially, this theorem states that the monadic shallow embedding, derived from the Ccode, refines our shallow embedding transitively, i.e., refines the update , then the value andthen the shallow, if the following is true: their arguments are all value related ( V ); all abstractfunctions in the update semantics correspond to their embeddings in the value semantics,and upward propagation occurs ( ξ u ∼ A ξ v ); the monomorphised abstract functions refinetheir polymorphic counterparts ( ξ m ∼ A M ev r pm ξ p ); and the evaluation environment V inthe monomorphic value semantics value types to the type environment Γ. The value relation V is defined as: ▶ Definition 17 (Shallow to C value relation) . V ( r pm , ξ p , v s , v p , v u , v c , µ ) = ∃ r w τ. V S ( ξ p v s , v p ) ∧ µ | v u : M v ( r pm , v p ) : τ [ r ∗ w ] ∧ V C ( v u , v c ))This is simply the composition of all the value relations for each phase. correspondence isdefined as: ▶ Definition 18 (Shallow ⇒ C refinement) . correspondence r pm R v s e p c ξ u ξ m ξ p U V Γ µ σ =( µ, σ ) ∈ R −→ ( ∃ r w. µ | U : V : Γ [ r ∗ w ]) −→ ( ¬ failed ( p c σ ) ∧ ( ∃ v c σ ′ . ( v c , σ ′ ) ∈ results ( p c σ ) −→ ( ∃ µ ′ v u v p . ξ u , U ⊢ M e ( r pm , e ) | µ ⇓ u v u | µ ′ ∧ ξ m , V ⊢ M e ( r pm , e ) ⇓ v M v ( r pm , v p ) ∧ ( µ ′ , σ ′ ) ∈ R ∧ V ( r pm , ξ p , µ ′ , v c , v u , v p , v s ))))In essence, this means that if the states are related, the arguments to the functions are of thecorrect type and are value related, the C code executes successfully, then so will the update and the value semantics embeddings, and the final results will all be related.For Cogent programs that call abstract functions, the refinement theorem the compilerproduces is similar to Theorem 16, however, it also contains extra assumptions about theabstract functions called from the
Cogent program. The assumptions state that theabstract functions used in the
Cogent program refine their specifications and execute onthe arguments passed by the
Cogent program.Once the generated assumptions about abstract functions are discharged, we obtain ashallow embedding to C refinement theorem about the linked
Cogent -C program. Furthercorrectness theorems can be proven on top of the shallow embedding. To show this in action,we use the sum program presented in Example 5. . Cheung, L. O’Connor, and C. Rizkallah 13
In Example 5, we define a
Cogent function that calculates the sum of a 32-bit word array.After compiling this program and linking it to the word array implementation described inSection 3, the compiler produces a refinement theorem similar to Theorem 16. However, italso adds in the additional assumptions: ▷ Claim 19 (Update ⇒ C refinement assumption for length). ∀ u v m . V C ( u, v m ) −→ corres R ( length u x ) length c ξ u ( x u ) ( x : Array U32 r ) µ σ ▷ Claim 20 (Update ⇒ C refinement assumption for fold). ∀ u v m . V C ( u, v m ) ∧ ( f C v m = FUN _ ENUM _ add ) −→ corres R ( fold u x ) fold c ξ u ( x u ) ( x : τ ) µ σ where f C extracts the field f from a struct , FUN _ ENUM _ add is the function ID for thefunction add , τ = { arr : Array U32 , frm : U32 , to : U32 , f : τ f , acc : U32 , obsv : () } and τ f = { elem : U32 , acc : U32 , obsv : () } → U32 . ▶ Note 21.
At the time of writing this paper,
Cogent ’s automatic proof generation wasnot working for functions that call abstract function in the polymorphic shallow to deeprefinement phase. It fails to produce additional assumptions similar to Claim 19 for firstorder abstract functions and Claim 20 for higher order functions. These assumptions wouldthen be proven manually using Theorem 15 and a variant of it for first order and higherorder abstract functions, respectively.However, we can discharge Claim 19 since we proved Theorem 13 for length . We can alsodischarge Claim 20 by using a similar theorem to Theorem 13, which only differs because itassumes that we have proven Theorem 13 for the function f that it can call, and that f istype correct. In this case, f is the add function, and Cogent automatically proves theseresults for that function. So our refinement theorem for the function sum is the same asTheorem 16. We additionally prove that all abstract functions preserve typing and satisfythe frame constraints, and that all abstract functions refine their specifications in each of therefinement phases. Thus, our final refinement theorem for the function sum becomes: ▶ Theorem 22 (Shallow ⇒ C refinement for sum) . ∀ µ σ. V ( r pm , ξ p , µ, v c , v u , v p , v s ) µ −→ correspondence r pm R ( sum s v s ) ( sum d x ) sum c ξ u ξ m ξ p U V Γ µ σ where U = ( x v u ) , V = ( x
7→ M v ( r pm , v p )) , and Γ = ( x τ ) . Functional correctness of the generated shallow embedding of sum is shown by equating it tothe built-in sum_list in Isabelle, assuming that the list is definable in C, and this is provedby inducting on the list: ▶ Theorem 23 (Correctness of sum) . List . length xs < −→ sum s xs = sum _ list xs ▶ Corollary 24 (Correctness of sum on natural numbers) . List . length xs ≤ −→ sum _ list ( map unat xs ) < −→ unat ( sum s xs ) = ( sum _ list P x ← xs unat xs ) where P x ← xs unat xs ) is Isabelle’s shorthand for sum _ list ( map unat xs ) . We previously verified the functional correctness of key operations in BilbyFs [3]. Thisproof relied on the correctness of abstract functions including an axiomatization of wordarray operations [2]. We removed the axiomatization for the five core word array operationsthat we verified, and were able to show that the functional correctness proofs compose forthe combined system. A few word array operations that remain in the axiomatization are: create , set , copy , cmp , which all depend on platform specific functions such as malloc ; and modify , which updates an element in an array with a function, but for word-arrays this canbe implemented in terms of our verified put . Although it is possible to directly prove that a program refines its functional correctnessspecification, intermediate embeddings ease this process. In
Cogent , there are clear, distinctrefinement phases designed to abstract away one particular implementation feature at a time.For instance, the refinement phase between the update and the value semantics abstracts awaymutation and pointers. This eases further refinement proofs on top of the value semantics.In our experience, even when verifying abstract functions by hand, we benefit from thisdirected approach to refinement.
Cogent automatically proves refinement for all
Cogent functions, assuming that all theabstract functions refine their specification, and thus we have a partial proof of compilationcorrectness for all
Cogent functions. We can reduce this partiality or even eliminate itentirely, proving whole program compilation correctness, by manually proving the functionalcorrectness of the necessary abstract functions. The value typing for abstract types isprovided by the user, who is allowed to define this relation as they like, so long as it stillsatisfies
Cogent ’s FFI constraints. These constraints ensure that the refinement theoremsgenerated by the
Cogent compiler are not invalidated. This modular framework greatlyreduces the burden of proof engineers as they only need to focus their attention on theimportant tasks leaving the grunt work to the
Cogent compiler.
Cogent ’s refinement theorem depends on memory safety. Therefore it proves, sim-ultaneously to the refinement theorem, memory safety properties for all memory visiblefrom
Cogent for all
Cogent functions automatically. To prevent abstract functions frominvalidating this guarantee, the value typing judgement identifies the available pointers andthe frame relation ensures that this heap footprint is accurate. At the bare minimum, weneed to show that all abstract functions do not mutate any parts of
Cogent memory unlessthey have been given explicit permission, i.e., the piece of memory is passed as argument toan abstract function with writable permission. However, to guarantee memory safety globally,all pointers must be visible from
Cogent . In our word array verification, we accomplishthis by defining the read-only and writable pointer sets in our value typing for word arrays(in the update semantics) as the set of all pointers to each element in the array. This is onlypossible because we define our embedding of word arrays (also in the update semantics) aspointer to the first element in the array and the length of the array. If we instead chose todefine our embedding in the update semantics to look similar to our embedding in the value semantics, we would be unable to make use of the frame constraints to show memory safety.Whilst verifying word arrays, we found that our Isabelle/HOL formalisation of
Cogent ’sFFI value typing constraints were missing some constraints that were present in our pen-and-paper formalisation. We also found that the automatically generated
Cogent refinementtheorems require a small amount of manual intervention for integration with the manual . Cheung, L. O’Connor, and C. Rizkallah 15 proofs about abstract functions (e.g., for passing in the ξ function defining the semantics ofabstract functions). Currently, Cogent’s refinement proof generator for the shallow to value refinement phase uses a cheat tactic to assume the correctness of abstract functions ratherthan listing them as explicit assumptions (see Note 21). Listing these assumptions explicitly,makes integrating their manual proofs easier than it is currently (similar to what is doneon the update-to-C refinement phase). These minor inconveniences did not appear earliersince this is the first verification effort that verifies a Cogent -C system using
Cogent ’sFFI interface. They provide an avenue for further improvements to
Cogent ’s proof FFI.
Cogent ’s automatic refinement framework assumes users will provide it embeddings,value relations and environments for abstract functions after the automatic proof has run.One problem we faced was that
Cogent ’s evaluation relation only terminates if the allabstract functions terminate and higher order abstract functions only terminate if theevaluation relation terminates, i.e., they mutually recursive. We cannot define them asmutually recursive, however, since abstraction functions are defined by users later in theprocess, so we cannot close the recursion before users have provided these definitions. Thislead to us imposing a constraint that a n order abstract function can only call a function ofan order less than n . This forces us to prove refinement for all functions of order n − n . This constraint isnot burdensome, and is already satisfied by all Cogent codebases.Although not a serious issue, we found that
Cogent ’s refinement framework uses bothIsabelle/HOL’s consts and type classes for its value relation between the shallow and value semantics, and the update and monadic shallow C semantics, respectively. In our verificationof word arrays, we found that we could prove more general lemmas and theorems when thevalue relation was defined as a type class rather than a consts , and the type class versionwas much neater. In hindsight, we should have used a type class for both of them, as wecould then take advantage of the fact that we can add constraints with type classes whilstwe cannot for consts . Modular Refinement
Patterson and Ahmed [15] have defined a spectrum of compiler verification theorems focusingon compositional compiler correctness, extensible through linking.
Cogent ’s certifyingcompiler does not neatly fall on this spectrum as the compiler itself does the linking of the
Cogent -C system, and we are linking with manually verified C rather than other compileroutputs. Nevertheless, the FFI constraints we prove would remain the same if it were to linkto other compiler outputs.Similar to
Cogent , the Cito language [19] supports cross-language linking and modularverification in an interactive theorem prover. Unlike
Cogent , Cito is a low-level C-likelanguage without a sophisticated type system, so the FFI requirements are simpler: it doesnot ensure memory safety, and any verification on top of Cito is done through a Hoare-styleaxiomatic semantics rather than through pure equational reasoning.An important part of verified partial compilation is how the semantics of linking is definedfor the source language. The simplest way to define linking on the source language is torequire that all programs that can be linked must refine a program definable in the sourcelanguage. SepCompCert [7] and Pilsner[12] take this approach. This approach is suitable forlanguages unlike
Cogent , that are expressive enough for defining all the programs that wewant to link with. As
Cogent is a restricted language, we require C expressions to refine more expressive Isabelle/HOL specifications, not
Cogent ones. Our
Cogent compilergenerates shallow embeddings of
Cogent programs in Isabelle/HOL, which enables linkingat the Isabelle specification level. Note that if we were to change the
Cogent language, solong as are still able to shallowly embed the new language in Isabelle/HOL, linking wouldremain unaffected. Hence, hence,
Cogent supports source independent linking.Verified compilation typically occurs in more than one pass. That is, we we show that thesource program is refined by some intermediate representation, which in turn is refined bysome other intermediate representation, and so on until it is refined by our target program.Then by transitivity, we know that the target program refines the source program. However,these passes may have subtle dependencies on each other [18, 16], which can necessitatereproving correctness for all existing passes when new passes are introduced. On the otherhand, if the verification passes can occur in isolation, then the compiler supports verticalcompositionality, and is thus quite modular. The
Cogent compiler is technically not averified compiler, but like a verified compiler it consists of many refinement phases which areindependent of each other, and thus we also benefit from this vertical compositionality.
Verified Data Structures
Word arrays are widely used data structures, particularly in low level systems programming.The
Cogent compiler uses the C-parser [20] to import C code into Isabelle/HOL andAutoCorres [5, 6] to simplify the imported C code to a monadic shallow embedding inIsabelle/HOL. Within AutoCorres, there are a number of example verified programs, includingBinary search and Quicksort, which use 32-bit word arrays. Thus, AutoCorres also includesverified get and put operations for word arrays. Just for these two operations, their verificationis much smaller in terms of lines of proof (roughly 250 lines, which is about half of ours forthese functions), which can be attributed to the fact that we have more refinement steps. Onthe other hand, our proofs from the update semantics upwards are reusable for any primitiveelement type, whereas AutoCorres only verifies 32-bit word arrays.If we were only concerned with proving functional correctness of a word array imple-mentation, we could have taken a top down approach, i.e., generate an implementation fromthe specification, using existing tools or frameworks [9, 10, 11], rather than a bottom upapproach, i.e., prove that an implementation refines its specification. The top down approachwould likely lessen the verification burden, however, the bottom up approach allows formore control over the exact implementation produced, and is suitable for cases where animplementations already exist. Our aim was to verify an existing implementation of wordarrays for BilbyFs, as well as to demonstrate the technique for more sophisticated datastructures where this top-down approach may not be viable.
We have presented a case-study demonstrating a cross-language approach to proving refine-ment between
Cogent , a safe functional language, and C, an unsafe imperative language.Namely, we verified the word array implementation provided in
Cogent ’s ADT library thatwas used the implementation of two real-world Linux file systems and showed that it respects
Cogent ’s FFI. We demonstrated proving correctness of Cogent’s FFI linking. We alsoproved that our specification of word arrays discharges the axiomatization of word arrays inthe BilbyFs functional correctness specification. These case studies demonstrate that ourverification can be modularly composed with
Cogent ’s refinement proofs, as well as withfunctional correctness proofs to ensure the correctness of an overall
Cogent -C system. . Cheung, L. O’Connor, and C. Rizkallah 17
References Amal Ahmed. Compositional Compiler Verification for a Multi-Language World. In DeliaKesner and Brigitte Pientka, editors, , volume 52 of
Leibniz International Proceedingsin Informatics (LIPIcs) , pages 1:1–1:1, Dagstuhl, Germany, 2016. Schloss Dagstuhl–Leibniz-Zentrum fuer Informatik. URL: http://drops.dagstuhl.de/opus/volltexte/2016/5968 , doi:10.4230/LIPIcs.FSCD.2016.1 . Sidney Amani.
A Methodology for Trustworthy File Systems . PhD thesis, CSE, UNSW, Sydney,Australia, August 2016. Sidney Amani, Alex Hixon, Zilin Chen, Christine Rizkallah, Peter Chubb, Liam O’Connor,Joel Beeren, Yutaka Nagashima, Japheth Lim, Thomas Sewell, Joseph Tuong, Gabriele Keller,Toby Murray, Gerwin Klein, and Gernot Heiser. Cogent: Verifying high-assurance file systemimplementations. In
International Conference on Architectural Support for ProgrammingLanguages and Operating Systems , pages 175–188, Atlanta, GA, USA, April 2016. doi:10.1145/2872362.2872404 . Cogent FFI examples (code and proofs). https://github.com/NICTA/cogent/tree/wordarray-example/itp2021-artefact , 2021. Accessed February 2021. David Greenaway, June Andronick, and Gerwin Klein. Bridging the gap: Automatic verifiedabstraction of C. In
International Conference on Interactive Theorem Proving , pages 99–115,Princeton, New Jersey, USA, August 2012. David Greenaway, Japheth Lim, June Andronick, and Gerwin Klein. Don’t sweat the smallstuff: Formal verification of C code without the pain. In
ACM SIGPLAN Conference onProgramming Language Design and Implementation , pages 429–439, Edinburgh, UK, June2014. ACM. doi:10.1145/2594291.2594296 . Jeehoon Kang, Yoonseung Kim, Chung-Kil Hur, Derek Dreyer, and Viktor Vafeiadis. Light-weight verification of separate compilation. In
Proceedings of the 43rd Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages , POPL ’16, page 178–190, NewYork, NY, USA, 2016. Association for Computing Machinery. doi:10.1145/2837614.2837642 . Gerwin Klein, Kevin Elphinstone, Gernot Heiser, June Andronick, David Cock, Philip Derrin,Dhammika Elkaduwe, Kai Engelhardt, Rafal Kolanski, Michael Norrish, Thomas Sewell,Harvey Tuch, and Simon Winwood. seL4: Formal verification of an OS kernel. In
ACMSymposium on Operating Systems Principles , pages 207–220, Big Sky, MT, USA, October2009. Peter Lammich. Generating verified LLVM from isabelle/hol. In John Harrison, John O’Leary,and Andrew Tolmach, editors, , volume 141 of
LIPIcs , pages 22:1–22:19.Schloss Dagstuhl - Leibniz-Zentrum für Informatik, 2019. doi:10.4230/LIPIcs.ITP.2019.22 . Peter Lammich. Refinement to imperative hol.
Journal of Automated Reasoning , 62(4):481–503,Apr 2019. doi:10.1007/s10817-017-9437-1 . Peter Lammich and Andreas Lochbihler. The Isabelle collections framework. In MattKaufmann and Lawrence C. Paulson, editors,
Interactive Theorem Proving , pages 339–354,Berlin, Heidelberg, 2010. Springer Berlin Heidelberg. Georg Neis, Chung-Kil Hur, Jan-Oliver Kaiser, Craig McLaughlin, Derek Dreyer, and ViktorVafeiadis. Pilsner: A compositionally verified compiler for a higher-order imperative language.In
Proceedings of the 20th ACM SIGPLAN International Conference on Functional Program-ming , ICFP 2015, page 166–178, New York, NY, USA, 2015. Association for ComputingMachinery. doi:10.1145/2784731.2784764 . Tobias Nipkow, Lawrence C. Paulson, and Markus Wenzel.
Isabelle/HOL - A Proof Assistantfor Higher-Order Logic , volume 2283 of
Lecture Notes in Computer Science . Springer, 2002. Liam O’Connor, Zilin Chen, Christine Rizkallah, Sidney Amani, Japheth Lim, Toby Murray,Yutaka Nagashima, Thomas Sewell, and Gerwin Klein. Refinement through restraint: Bringing down the cost of verification. In
International Conference on Functional Programming , Nara,Japan, September 2016. Daniel Patterson and Amal Ahmed. The next 700 compiler correctness theorems (functionalpearl).
Proc. ACM Program. Lang. , 3(ICFP):85:1–85:29, 2019. doi:10.1145/3341689 . James T. Perconti and Amal Ahmed. Verifying an open compiler using multi-languagesemantics. In Zhong Shao, editor,
Programming Languages and Systems , pages 128–148, Berlin,Heidelberg, 2014. Springer Berlin Heidelberg. Christine Rizkallah, Japheth Lim, Yutaka Nagashima, Thomas Sewell, Zilin Chen, LiamO’Connor, Toby Murray, Gabriele Keller, and Gerwin Klein. A framework for the automaticformal verification of refinement from Cogent to C. In
International Conference on InteractiveTheorem Proving , Nancy, France, August 2016. Gordon Stewart, Lennart Beringer, Santiago Cuellar, and Andrew W. Appel. Compositionalcompcert. In
Proceedings of the 42nd Annual ACM SIGPLAN-SIGACT Symposium onPrinciples of Programming Languages , POPL ’15, page 275–287, New York, NY, USA, 2015.Association for Computing Machinery. doi:10.1145/2676726.2676985 . Peng Wang, Santiago Cuellar, and Adam Chlipala. Compiler verification meets cross-languagelinking via data abstraction. In Andrew P. Black and Todd D. Millstein, editors,
Proceedings ofthe 2014 ACM International Conference on Object Oriented Programming Systems Languages& Applications, OOPSLA 2014, part of SPLASH 2014, Portland, OR, USA, October 20-24,2014 , pages 675–690. ACM, 2014. doi:10.1145/2660193.2660201 . Simon Winwood, Gerwin Klein, Thomas Sewell, June Andronick, David Cock, and MichaelNorrish. Mind the gap: A verification framework for low-level C. In S. Berghofer, T.Nipkow, C. Urban, M. Wenzel, editor,
International Conference on Theorem Proving inHigher Order Logics , pages 500–515, Munich, Germany, August 2009. Springer. doi:10.1007/978-3-642-03359-9_34doi:10.1007/978-3-642-03359-9_34