Toward a Practical Module System for ACL2

نویسندگان

  • Carl Eastlund
  • Matthias Felleisen
چکیده

Boyer and Moore’s ACL2 theorem prover combines firstorder applicative Common Lisp with a computational, first-order logic. While ACL2 has become popular and is being used for large programs, ACL2 forces programmers to rely on manually maintained protocols for managing modularity. In this paper, we present a prototype of Modular ACL2. The system extends ACL2 with a simple, but pragmatic functional module system. We provide an informal introduction, sketch a formal semantics, and report on our first experiences. 1 A logic for Common Lisp, Modules for ACL2 In the early 1980s, the Boyer and Moore team decided to re-build their Nqthm theorem prover [1] for a first-order, functional sub-language of a standardized, industrial programming language: Common Lisp [2]. It was an attempt to piggyback theorem proving on the expected success of Lisp and functional programming. Although Common Lisp didn’t succeed, the ACL2 system became the most widely used theorem prover in industry. Over the past 20 years, numerous hardware companies and some software companies turned to ACL2 to verify critical pieces of their products [3]; by 2006, their contributions to the ACL2 regression test suite amounted to over one million lines of code. The ACL2 team received the 2005 ACM Systems Award for their achievement. During the same 20 years, programming language theory and practice have evolved, too. In particular, programming language designers have designed, implemented, and experimented with numerous module systems for managing large functional programs [4]. One major goal of these design efforts has been to help programmers reason locally about their code. That is, a module should express its expectations about imports, and all verification efforts for definitions in a module should be conducted with respect to these expectations. Common Lisp and thus ACL2, however, lack a proper module system. Instead, ACL2 programmers emulate modular programming with Common Lisp’s namespace management mechanisms, or by hiding certain program fragments from the theorem prover. Naturally, the manual maintenance of abstraction boundaries is difficult and error prone. Worse, it forces the programmer to choose between local reasoning and end-to-end execution, as functions hidden from the theorem prover cannot be run. 1 campus.acm.org/public/pressroom/press releases/3 2006/software.cfm Over the past year, we have investigated the design of a module system for ACL2. Specifically, we have extended ACL2’s language with modules and produced two translations for modular programs: a compiler to ACL2 executables and a logic translator to ACL2 proof obligations. With the latter, programmers can now reason locally about individual modules. One goal is to empower ACL2 programmers with large code bases to gradually migrate their monolithic program into a modular world. Another goal is to expand Rex Page’s [5] use of this industrial-strength theorem prover in software engineering courses to teach theorem proving in a modular setting. Without modules, such a software engineering course simply isn’t convincing enough. This paper is our first report on bringing this module technology to ACL2. In section 2, we demonstrate our module system and its prototype implementation. In section 3, we present our formal model of the module system. We have also implemented several projects as modules; in section 4 we describe the positive and negative outcomes of these experiments. Section 5 presents related work, and the last section sketches our future challenges. 2 Reasoning with Modules ACL2. The ACL2 theorem prover is similar to a LISP read-eval-print loop; it accepts events such as function definitions or logical conjectures from the user, verifies each in turn, and updates the logical state for the next event. Its interface is purely text-based; the system comes with an Emacs mode as the preferred interface for professional ACL2 users. Four years ago, Rex Page (Oklahoma University) started the ambitious effort of teaching a senior-level course sequence on software engineering in ACL2 [5]. Students reported difficulty with the text-based interface to ACL2; in response, Felleisen and Vaillancourt produced Dracula [6] as a graphical user interface for ACL2. Dracula has since been used in courses on software engineering and symbolic logic [7]. Dracula. Dracula is a language level in the DrScheme integrated development environment. It provides a simulation of Applicative Common Lisp (ACL), the executable component of ACL2. Dracula incorporates DrScheme’s usual programming tools, including a syntax checker, stack traces, unit testing, and a functional, graphical toolkit geared toward novice programmers. It provides an interface to the ACL2 theorem prover for the logical component. Figure 1 shows a screenshot of Dracula in action. The left-hand side of the Dracula interface provides two windows for formulating and executing programs: the definitions window, where users edit their programs, and the interactions window, where users may try out their functions. The right-hand side of the display is Dracula’s interface to the ACL2 theorem prover. It provides buttons to invoke ACL2 and to send each term from the definitions window to the theorem prover. Dracula paints the terms green when ACL2 proves them sound and red when it fails. Green terms are locked from further editing to faithfully represent ACL2’s logical state; users may edit red Fig. 1. The Dracula graphical user interface. terms or undo the admission of green terms to edit those. Below the control buttons, Dracula shows the theorem prover’s output; above them, it shows a proof tree, naming key checkpoints for quick diagnosis of a failed attempt. Figure 1 shows a program with two functions and two theorems. The functions are insert , which adds a single element to a set, and join, which adds multiple elements. The theorems insert/no-duplicates and join/no-duplicates state that the functions preserve the the uniqueness of set elements. Dracula’s simulation of ACL ignores the theorems, as they are logical rather than executable, and compiles the rest. As we can see in the interactions window, join produces the expected output when given ’(1 2 3) and ’(4 5 6) as input. In contrast, the ACL2 theorem prover attempts to verify the logical soundness of each term successively. First it checks insert , which it must prove terminating for all inputs—a requirement of all functions in ACL2’s logic. Next ACL2 checks insert/no-duplicates, for which it must prove that the conjecture expression produces a true value (non-nil). Free variables in defthm conjectures (such as x and xs) are implicitly universally quantified over all ACL2 values. ACL2 repeats the verification process for join and join/no-duplicates. ACL2 successfully admits all these terms. The ACL2 output window displays a list of rules used in the proof of join/no-duplicates. The list includes the definitions of insert and add-to-set-eql , but not insert/no-duplicates. Rather than using the lemma proved above to reason about join, ACL2 re-examined the definition of insert to prove the uniqueness of its elements. The theorem prover’s search strategies often prefer to delve into a function definition rather than use an existing lemma, resulting in duplicated proofs that span several functions. Fig. 2. A modular program in Dracula. Modular ACL2. Figure 2 shows a version of the join program in our new language, Modular ACL2. The definitions window contains two interfaces, two modules, a link clause, and an invoke clause. Interfaces contain signatures and contracts. A signature declares a function, providing its name and argument list. A contract declares a logical property that may refer to the signatures. Interfaces may also include other interfaces. This allows them to refer to other signatures in their contracts, extending them with new properties or stating relationships between multiple interfaces. The IInsert interface contains a signature insert and a contract insert/no-duplicates. They have the same arity and state the same property as the previous insert and insert/no-duplicates, but the interface does not provide a definition for insert . The IJoin interface similarly contains a signature and the join/no-duplicates contract for join. Modules contain definitions, import clauses, and export clauses. The import and export clauses each name an interface. Definitions form the body of the module; they may refer to functions from imported interfaces, and rely on the properties declared by imported contracts. Conversely, the body of the module must define all functions declared in exported modules in a way that satisfies the associated contracts. A link clause constructs a new module from two existing modules. The exports of all the modules are combined, and the imports of each module are connected to the matching exports of any prior module. TheMInsert module contains the same definition of insert we saw before and exports IInsert . This obligates insert to satisfy insert/no-duplicates. The MJoin module imports IInsert . This allows it to call the binary function insert and assume insert/no-duplicates holds. It then defines join as before, and exports IJoin. Once again, join must satisfy join/no-duplicates. This time, however, its soundness is not with respect to a concrete definition of insert , but rather with respect to the imported signature and its associated contract. The MSet module in our example provides IInsert from MInsert and IJoin from MJoin; the reference to insert in MJoin is resolved to the definition in MInsert . Linking is applicative; the original MJoin is unchanged and may later be linked to a different implementation of IInsert . Finally, our example program invokes MSet , making its exported functions available outside the module. As with standard ACL, Dracula compiles the modular program to an executable form and disregards the logical aspects. It compiles insert and join, links them together, and provides them for use in the interactions window. Reasoning locally. The ACL2 GUI allows the user to verify each module separately using the theorem prover. Once the user selects a module, Dracula provides ACL2 with stubs (abstract functions) representing its imported signatures and axioms (unproven logical rules) asserting its imported contracts. Dracula then passes the body of the module to ACL2. Once that is admitted, it sends ACL2 a theorem corresponding to each exported contract. If ACL2 admits all three stages—stubs and axioms for imports, body definitions, and theorems for exports—the module is guaranteed to satisfy its export interface for any sound implementation of its import interface. The presence of stubs and axioms may seem troubling; these are unverified assumptions added to ACL2’s logical state. Using them is sound with respect to a fully linked program, however. The interface imported by one module must be linked to an export from another, so contracts assumed as axioms in one module must be proved as theorems in another before the whole program is verified. Dracula only admits primitive modules, such as MInsert and MJoin, via ACL2. It safely disregards linked modules, such as MSet ; once MInsert and MJoin have been verified separately, they can be linked to any module with a matching interface without need for re-verification. In figure 2, we see that ACL2 has admitted MJoin. This time the proof of join/no-duplicates does not refer to the definitions of insert or add-to-set-eql ; instead, it uses the imported contract insert/no-duplicates. Manual modularization in ACL2. ACL2 has mechanisms for abstract reasoning and proof reuse. Certain definitions in a book (separate file) or encapsulate block (lexical scope) may be declared local, which hides some or all of their definition from the remaining proof, but renders them unexecutable as well. These abstract proofs may later be applied to concrete functions, but the 2 As ACL2 does not allow forward references, neither do linked modules; this prevents cyclic definitions and preserves each module’s termination proofs. rules must be applied on a theorem-by-theorem basis, and no executable content is reused. Logical rules may be selectively disabled in the global theory, but they may be re-enabled later, defeating abstraction boundaries. Worse, these mechanisms require the programmer to maintain the invariants of an abstraction boundarymanually, setting up a “negative interface” by declaring which logical entities are not available for reasoning rather than which are. ACL2 can simulate a normal, “positive interface” by layering these mechanisms, but not a reusable, externally stated one. 3 The Dual Semantics of Modules The purpose of our module system is to enable programmers to develop units of code in isolation and to reason about them independently. This informal specification implies the need for two additions to core ACL2: modules and interfaces. For an untyped language such as ACL2, a module consists of definitions and manages the scope of names. An interface describes the functions that a module provides in terms of signatures and contracts, which play the role of both obligations on the exporting module and promises for the importing one. Naturally a module can use the services of another module, i.e., it can import functions and rely on the contracts that hold for them. Using just those contracts and the definitions in the module, a programmer must be able to verify the module’s export interface. That is, it is the task of the module system to reformulate the imported contracts and the module body so that the ACL2 theorem prover can verify the exported contracts from these premises. Another design choice concerns the connection between modules. One alternative is to used fixed links between modules, specified via interfaces. The other one is to think of modules as relations from interfaces to interfaces and to link modules separately. Based on our experience with Scheme units [8, 9] and ML functors [4], we have chosen the second alternative. Finally, we also decided to separate module invocation from module linking. The rest of the section presents a model of Modular ACL2, its syntax and two semantic mappings. Syntax. Figure 3 shows the core syntax of ACL2 and Modular ACL2. ACL2 has two variable namespaces: function parameters and local variables (v), and functions and theorems (n). Modular ACL2 introduces a third namespace for modules and interfaces (N ). An ACL2 program consists of of a sequence of def initions and expressions. Definitions give names to functions, stubs, theorems, or axioms, or may in turn be a sequence of other definitions. Expressions include variables, literal constants, function application, conditionals, and variable bindings. Modular programs consist of a sequence of top-level forms including interface definitions, primitive module definitions, linking specifications, module invocations, and expressions from the core language. An interface contains Specifications, including signatures, contracts, and other included interfaces, as described in section 2. A primitive module contains a sequence of Def initions, extended from ACL2 to allow imports and exports via interfaces. Exported prog = top . . . top = def | expr def = (defun n (v . . . ) expr) | (defstub n (v . . . ) t) | (defthm n expr) | (defaxiom n expr) | (progn def . . . ) expr = v | const | (n expr . . . ) | (cond (expr expr) . . . ) | (let ((v expr) . . . ) expr) const = t | nil | number | string Prog = Top . . . Top = Ifc | Mod | Link | Inv | expr Ifc = (interface N Spec . . . ) Mod = (module N Def . . . ) Link = (link N (N N )) Inv = (invoke N ) Spec = (sig n (v . . . )) | (con n expr) | (include N ) Def = Imp | Exp | def Imp = (import N ) Exp = (export N (n n) . . . ) Fig. 3. The core grammars of ACL2 (left) and Modular ACL2 (right). interfaces allow renaming, in case the internal and external names of a function differ. A compound module links together two other modules. Fully-linked modules—those whose imports have all been resolved—may be invoked, making their declared exports available to top level expressions. Dual Semantics. Modular ACL2 programs can be verified logically, and they can be executed. For this reason, modules in a program are either translated into ACL2 proof obligations, or linked together and run as an ACL2 program. The two semantics are closely related, so that verification has meaning with respect to execution. Specifically, once a module is verified, its exports are guaranteed to satisfy their contracts whenever the implementations of their imports satisfy theirs as well. Put another way, once every module in a program has been verified, every contract must hold true at run-time. We do not present the straightforward description of a static semantics for determining the syntactic well-formedness of programs. In order for a Modular ACL2 program to translate to well-formed ACL2, it must avoid forward references, name clashes within interfaces and modules, modules that import one interface without importing another that it includes, and a few other errors. Logical Semantics. A Modular ACL2 program is verified by tranforming each primitive module into an ACL2 proof obligation stating that its definitions satisfy its exported contracts, predicated on the correctness of its imports. We represent this transformation as the function L (for “Logical”) that consumes a Modular ACL2 program and produces a sequence of ACL2 programs, one for each module. Figure 4 shows the definition of L and its auxiliary functions. The L function transforms a program by invoking LT with two accumulators: a list of interfaces and a list of obligations. This function traverses the toplevel definitions of a modular program. Each interface LT encounters is added to Γ . Each primitive module is transformed into a proof obligation; the proof 3 In our full implementation, imported interfaces allow renaming as well, and compound modules may link any number of modules. L : Prog → prog . . . L(Prog) = LT ( ,Prog , ) LT : (Ifc . . . ,Top . . . , prog . . .)→ prog . . . LT (Γ, , Φ) = Φ LT (Γ, Ifc Top . . . , Φ) = LT (Γ Ifc,Top . . . , Φ) LT (Γ,Mod Top . . . , Φ) = LT (Γ,Top . . . , Φ LM(Γ,Mod)) LT (Γ,Top0 Top . . . , Φ) = LT (Γ,Top . . . , Φ) where Top0 = Link | Inv | expr LM : (Ifc . . . ,Mod)→ prog LM(Γ, (module N Def . . . )) = LD(Γ, ,Def . . . , ) LD : (Ifc . . . ,n → n,Def . . . , def . . .)→ prog LD(Γ, ρ, ,∆) = ∆ LD(Γ, ρ, def Def . . . , ∆) = LD(Γ, ρ,Def . . . , ∆ def ) LD(Γ, ρ, (import N ) Def . . . , ∆) = LD(Γ, ρ,Def . . . , ∆ LI(Spec . . . , )) where Γ (N ) = (interface N Spec . . . ) LD(Γ, ρ, (export N (n1 n2) . . . ) Def . . . , ∆) = LD(Γ, ρ[n2/n1 . . .],Def . . . , ∆ ∆ ′) where Γ (N ) = (interface N Spec . . . ) and LE(ρ′,Spec . . . , ) = ∆′ LI : (Spec . . . , def . . .)→ def . . .

برای دانلود رایگان متن کامل این مقاله و بیش از 32 میلیون مقاله دیگر ابتدا ثبت نام کنید

ثبت نام

اگر عضو سایت هستید لطفا وارد حساب کاربری خود شوید

منابع مشابه

Modular ACL2

In the early 1980s, Boyer and Moore decided to re-build their Nqthm theorem prover [1] for a first-order, functional subset of a standardized, industrial programming language: Common Lisp [8]. The resulting system, ACL2, was an attempt to piggy-back theorem proving on the expected success of Lisp and functional programming. Although Common Lisp didn’t succeed, ACL2 became the most widely used t...

متن کامل

Encapsulation for Practical Simplification Procedures

ACL2 was used to prove properties of two simplification procedures. The procedures differ in complexity but solve the same programming problem that arises in the context of a resolution/paramodulation theorem proving system. Term rewriting is at the core of the two procedures, but details of the rewriting procedure itself are irrelevant. The ACL2 encapsulate construct was used to assert the exi...

متن کامل

System Description: IVY

IVY is a veriied theorem prover for rst-order logic with equality. It is coded in ACL2, and it makes calls to the theorem prover Otter to search for proofs and to the program MACE to search for coun-termodels. Veriications of Otter and MACE are not practical because they are coded in C. Instead, Otter and MACE give detailed proofs and models that are checked by veriied ACL2 programs. In additio...

متن کامل

Verified AIG Algorithms in ACL2

And-Inverter Graphs (AIGs) are a popular way to represent Boolean functions (like circuits). AIG simplification algorithms can dramatically reduce an AIG, and play an important role in modern hardware verification tools like equivalence checkers. In practice, these tricky algorithms are implemented with optimized C or C++ routines with no guarantee of correctness. Meanwhile, many interactive th...

متن کامل

A Certified Module to Study Digital Images with the Kenzo System

Kenzo is a Computer Algebra system devoted to Algebraic Topology, written in the Common Lisp programming language. In this paper, programs which allow us to analyze monochromatic digital images with the Kenzo system are presented. Besides a complete automated proof of the correctness of our programs is provided. The proof is carried out using ACL2, a system for proving properties of programs wr...

متن کامل

ذخیره در منابع من


  با ذخیره ی این منبع در منابع من، دسترسی به آن را برای استفاده های بعدی آسان تر کنید

عنوان ژورنال:

دوره   شماره 

صفحات  -

تاریخ انتشار 2009