• Top
    • Documentation
    • Books
    • Boolean-reasoning
    • Projects
    • Debugging
    • Std
    • Proof-automation
    • Macro-libraries
    • ACL2
      • Theories
      • Rule-classes
      • Proof-builder
      • Recursion-and-induction
      • Hons-and-memoization
      • Events
      • Parallelism
      • History
      • Programming
        • Defun
        • Declare
        • System-utilities
        • Stobj
        • State
        • Mutual-recursion
        • Memoize
        • Mbe
        • Io
        • Defpkg
        • Apply$
        • Loop$
        • Programming-with-state
        • Arrays
        • Characters
        • Time$
        • Defmacro
        • Loop$-primer
        • Fast-alists
        • Defconst
        • Evaluation
        • Guard
        • Equality-variants
        • Compilation
        • Hons
        • ACL2-built-ins
        • Developers-guide
          • Developers-guide-background
          • Developers-guide-maintenance
          • Developers-guide-build
          • Developers-guide-utilities
          • Developers-guide-logic
            • Developers-guide-evaluation
            • Developers-guide-programming
            • Developers-guide-introduction
            • Developers-guide-extending-knowledge
            • Developers-guide-examples
            • Developers-guide-contributing
            • Developers-guide-prioritizing
            • Developers-guide-other
            • Developers-guide-emacs
            • Developers-guide-style
            • Developers-guide-miscellany
            • Developers-guide-releases
            • Developers-guide-ACL2-devel
            • Developers-guide-pitfalls
          • System-attachments
          • Advanced-features
          • Set-check-invariant-risk
          • Numbers
          • Efficiency
          • Irrelevant-formals
          • Introduction-to-programming-in-ACL2-for-those-who-know-lisp
          • Redefining-programs
          • Lists
          • Invariant-risk
          • Errors
          • Defabbrev
          • Conses
          • Alists
          • Set-register-invariant-risk
          • Strings
          • Program-wrapper
          • Get-internal-time
          • Basics
          • Packages
          • Oracle-eval
          • Defmacro-untouchable
          • <<
          • Primitive
          • Revert-world
          • Unmemoize
          • Set-duplicate-keys-action
          • Symbols
          • Def-list-constructor
          • Easy-simplify-term
          • Defiteration
          • Fake-oracle-eval
          • Defopen
          • Sleep
        • Operational-semantics
        • Real
        • Start-here
        • Debugging
        • Miscellaneous
        • Output-controls
        • Macros
        • Interfacing-tools
      • Interfacing-tools
      • Hardware-verification
      • Software-verification
      • Math
      • Testing-utilities
    • Developers-guide

    Developers-guide-logic

    Logical Considerations

    This Developer's Guide may give the impression that ACL2 maintenance is primarily a programming exercise, and that is probably true. However, there are some subtle logical considerations that need to be considered when working with some parts of the implementation. This topic addresses an assortment of such considerations, often by pointing to source code comments. Additional reading for those intrigued by logic is the paper: ``Structured Theory Development for a Mechanized Logic,'' Matt Kaufmann and J Strother Moore, Journal of Automated Reasoning 26, no. 2 (2001), pp. 161--203.

    Note: The examples below are by no means comprehensive! You are encouraged to extend this documentation topic with additional logical considerations as they arise. Also see the Essay on Soundness Threats.

    Histories, chronologies, and theories

    An ACL2 session has at least three logical interpretations, as follows.

    • The corresponding history, which is the sequence of all axiomatic events from the session: this includes defun, defchoose, and defaxiom events, as well as encapsulate events with non-empty signatures. It does not include defthm events.
    • The corresponding chronology, which is the sequence of all events from the session with logical content. Thus the history is a subsequence of the chronology, but the chronology also includes defthm events.
    • The corresponding theory of the session, which is the first-order theory of its history, that is, axiomatized by the formulas in its history. (Careful: This theory is not sensitive to which runes are enabled, unlike the macro, current-theory.) A basic property of ACL2 is that all formulas in the session's chronology are provable in the session's theory. In particular, by restricting to the chronology of events up to and including a given defthm event, we see that the formula of that event is provable from the axiomatic events that precede it.

    When a session S1 is extended to a session S2 without using defaxiom events, then the theory T2 for S2 is a conservative extension of the theory T1 for S1: that is, every theorem of T2 whose function symbols all occur in T1 — that is, are all introduced in S1 — is a theorem of T1. An important corollary is that if a session has no defaxiom events, then its theory is consistent.

    Note that defattach events are ignored for all three notions listed above. There is also a notion of evaluation theory, which is the extension of the session's theory by the equations that equate each function with its attachment. A basic property is that every evaluation theory built from a session free of defaxiom events is the theory of some history that is free of defaxiom events, and thus (by the corollary stated in the preceding paragraph) is consistent. For a detailed development of theory supporting the use of defattach (though this can probably ignored unless you are doing deep work with defattach), see the source code comment, ``Essay on Defattach.''

    There is also a logical explanation for apply$, which is based on the notion of evaluation theory mentioned above for defattach. The upshot is that a certain construction, the ``doppelganger construction'', produces an evaluation theory in which every warrant is valid. For detailed theoretical justification (probably not necessary for most developers, unless perhaps they are doing deep modifications pertaining to defattach or apply$), see the source code comment, ``Essay on Admitting a Model for Apply$ and the Functions that Use It.''

    Local

    IN THIS SECTION WE CAN FOCUS MAINLY ON THE DISPLAYS.

    Many soundness bugs in past versions of ACL2 — indeed, perhaps the majority of them — can be attributed to a subtle mishandling of local events. Perhaps the only advice possible in this regard is the following: Think carefully about the effects of local when making event-level changes to ACL2!

    Consider for example the following, seemingly innocuous ``enhancement'': allow verify-guards to comprehend macro-aliases (see macro-aliases-table). Such an ``enhancement'' would be unsound! Instead, a similar but sound enhancement, verify-guards+, was introduced (into Version 6.3). See verify-guards+ for an example, using encapsulate and local, for why such an enhancement to verify-guards would be unsound.

    We turn now to discuss a key mechanism for avoiding potential soundness issues caused by local: the ACL2-defaults-table. Because of the logical information stored in this table, it is prohibited to modify this table locally, as we illustrate with a couple of examples. First consider the following introduction of a program-mode function, f, that clearly is inadmissible in logic-mode.

    (encapsulate
      ()
      (program)
      (defun f () (not (f))))

    If the (program) event were allowed to be local, then the second pass of the encapsulate event would introduce f in logic-mode, creating an inconsistent theory!

    A slightly more subtle example is the following.

    (encapsulate
      ()
      (set-ruler-extenders :all)
      (defun foo (x)
        (cons (if (consp x) (foo (cdr x)) x)
              3)))

    The induction scheme stored for foo is as follows, which is the same as would be stored for the simpler definition, (defun foo (x) (if (consp x) (foo (cdr x)) x)). (Don't worry about the form of the structure below. Further relevant discussion may be found below in the section, ``Induction, recursion, and termination.'')

    ACL2 !>(getpropc 'foo 'induction-machine)
    ((TESTS-AND-CALLS ((CONSP X))
                      (FOO (CDR X)))
     (TESTS-AND-CALLS ((NOT (CONSP X)))))
    ACL2 !>

    The 'induction-machine property is computed based on the ruler-extenders. So if the event (set-ruler-extenders :all) above could be made local, we would be storing the same induction machine as for the following definition.

    (skip-proofs
     (defun foo (x)
       (declare (xargs :measure (acl2-count x)))
       (cons (if (consp x) (foo (cdr x)) x)
             3)))

    But that induction-machine is unsound!

    (thm nil :hints (("Goal" :induct (foo x))))

    The two examples above illustrate the importance of thinking about whether an event can soundly be local. If not, then it may be best for that event to be one that sets the ACL2-defaults-table, like program and set-ruler-extenders, since table events that set the acl2-defaults-table are not permitted to be local.

    Skip-proofs vs. defaxiom

    IN THE WORKSHOP THE FOCUS WILL BE ONLY ON THE LOGICAL DIFFERENCE.

    There is a fundamental logical difference between wrapping skip-proofs around a defthm event and using defaxiom. When using skip-proofs, the user is promising that the ensuing proof obligations are indeed theorems of the theory of the current ACL2 session. However, the meaning of defaxiom is to extend that theory with a new axiom, one which is generally not provable from the session's theory. See skip-proofs and see defaxiom. This logical distinction has ramifications for the implementation, as we now describe.

    Since the use of skip-proofs carries an implicit promise of provability, the implementation can largely ignore the question of whether an event was, or was not, introduced using skip-proofs. The key reason to do any such tracking is to inform certify-book when the use of keyword argument :skip-proofs-okp t is required.

    On the other hand, it is critical for soundness to track the use of defaxiom. In particular, it is unsound to allow local defaxiom events. That is obvious from the following example, which would leave us in an ACL2 world in which nil is provable even though there is no defaxiom event in that world.

    (encapsulate
      ()
      (local (defaxiom bad-axiom nil :rule-classes nil))
      (defthm bad-theorem nil
        :hints (("Goal" :use bad-axiom))
        :rule-classes nil))

    That event is, of course, rejected by ACL2. The following, on the other hand, is accepted; but this does not demonstrate unsoundness of ACL2, because the user violated the implied promise that, quoting from above, ``the ensuing proof obligations are indeed theorems of the theory of the current ACL2 session.''

    (encapsulate
      ()
      (local (skip-proofs (defthm bad-axiom nil :rule-classes nil)))
      (defthm bad-theorem nil
        :hints (("Goal" :use bad-axiom))
        :rule-classes nil))

    We conclude this section by noting that there are different ``levels'' of skip-proofs used by the implementation that are not directly visible to the user. The basic skip-proofs you see around an event generates a binding of state global 'ld-skip-proofsp to t. However, when you include a book, state global 'ld-skip-proofsp is bound to 'include-book, which has two major effects: local events are skipped, and some checks are omitted. For example, the redundancy checks normally performed after (set-enforce-redundancy t) are, as one would hope, omitted when including a book. If you tags-search the sources for ``(enforce-redundancy'', you'll see that it is used to implement events (see for example the definition of defconst-fn); then if you look at the definition of enforce-redundancy, you'll see that its check is skipped when state global 'ld-skip-proofsp is bound to 'include-book, hence when a book is being included. Also see ld-skip-proofsp.

    Induction, recursion, and termination

    Every proposed definition with recursion generates two related artifacts: a proof obligation that is generally described as the termination proof obligation, and an induction scheme to be stored if the definition is admitted. A key soundness requirement is that the termination proof obligation justifies use of the induction scheme. This produces a tension, as can be seen by the analysis below of the following example.

    (defun f (x)
      (if (consp x)
          (cons (if (consp (car x)) (f (car x)) x)
                x)
        x))

    The generated induction is displayed symbolically just below: when proving a proposition (P x), the induction step is to assume (consp x) and (P (car x)) when proving (P x), and the base case is to assume (not (consp x)) when proving (P x).

    ACL2 !>(getpropc 'f 'induction-machine)
    ((TESTS-AND-CALLS ((CONSP X))
                      (F (CAR X)))
     (TESTS-AND-CALLS ((NOT (CONSP X)))))
    ACL2 !>

    There is a comment just above the defrec for tests-and-calls that explains that record structure. The corresponding termination scheme may be seen by evaluating (trace$ termination-machine) before submitting the defun form above. Here is the result.

    <1 (TERMINATION-MACHINE ((TESTS-AND-CALL ((CONSP X))
                                             (F (CAR X)))))

    What this means is that ACL2 must prove that under the hypothesis (consp x), then (acl2-count (car x)) is smaller than (acl2-count x). That proof obligation clearly justifies the induction scheme described above.

    But let us try an experiment in which ACL2 is instructed to consider, for termination and induction, any IF-tests that are not at the top level — in this case, within the call of cons. In a fresh session, we try this instead.

    (defun f (x)
      (declare (xargs :ruler-extenders (cons)))
      (if (consp x)
          (cons (if (consp (car x)) (f (car x)) x)
                x)
        x))

    The induction machine produced this time includes the extra if test or its negation. It says that when proving a proposition (P x), the induction step is to assume (consp x), (consp (car x)), and (P (car x)) when proving (P x); one base case is to assume (consp x) and (not (consp (car x))) when proving (P x); and the other base case is to assume (not (consp x)) when proving (P x).

    ACL2 !>(getpropc 'f 'induction-machine)
    ((TESTS-AND-CALLS ((CONSP X) (CONSP (CAR X)))
                      (F (CAR X)))
     (TESTS-AND-CALLS ((CONSP X) (NOT (CONSP (CAR X)))))
     (TESTS-AND-CALLS ((NOT (CONSP X)))))
    ACL2 !>

    This time there is a different termination proof obligation as well, stating that (car x) has a smaller acl2-count than that of x under the conjunction of the pair of assumptions (consp x) and (consp (car x)). As before, it justifies the induction scheme just above.

    <1 (TERMINATION-MACHINE ((TESTS-AND-CALL ((CONSP X) (CONSP (CAR X)))
                                             (F (CAR X)))))

    Now suppose that the implementation is careless, by making the ruler-extenders affect the termination proof obligations but not the induction scheme. Consider the following example.

    (defun g (x)
      (declare (xargs :ruler-extenders (cons)))
      (cons (if (consp x) (g (car x)) x)
            x))

    This produces the termination proof obligation represented as follows (again from a trace), which says that assuming (consp x), (acl2-count (car x)) is less than (acl2-count x). Note that this is indeed a theorem.

    <1 (TERMINATION-MACHINE ((TESTS-AND-CALL ((CONSP X))
                                             (G (CAR X)))))

    To see what the induction scheme would be if we ignored the ruler-extenders, we submit the following.

    (skip-proofs
     (defun g (x)
       (declare (xargs :measure (acl2-count x)))
       (cons (if (consp (car x)) (g (car x)) x)
             x)))

    The corresponding induction machine states that to prove (P x), we may assume (P (car x)).

    ACL2 !>(getpropc 'g 'induction-machine)
    ((TESTS-AND-CALLS NIL (G (CAR X))))
    ACL2 !>

    If we let (P x) be (consp x), then clearly this induction scheme allows us to prove (consp x) for all x!

    (thm (consp x)
         :hints (("Goal" :induct (g x))))

    This induction scheme is thus clearly unsound.

    The moral of the story is this: As the termination machine is modified to accommodate the ruler-extenders, the induction machine must be modified accordingly. More generally, and as already noted: The termination machine must justify the induction machine.

    Other tricky stuff

    The logical underpinnings of ACL2 can be a bit overwhelming when considered together, so the following paragraphs should be taken as a suggestion for what to explore only when you're in the mood for some logic, and also as an acknowledgment that many subtle logical issues exist. It is NOT meant as a prescription for what you need to explore! Moreover, it is far from complete.

    The foundations of metatheoretic reasoning can be challenging. If you tags-search for ``; Essay on Metafunction Support'', you'll see two relevant essays. But a much longer essay is the ``Essay on Correctness of Meta Reasoning''. That essay even ties into the ``Essay on Defattach'', at the mention of the Attachment Restriction Lemma. If you decide to change the handling of metafunctions or clause-processors or their corresponding rule-classes, :meta and :clause-processor, other than fixing coding bugs or making extra-logical changes such as output, you probably should read these Essays. Of course, before reading these or any essays, it is generally a good idea to read relevant user-level documentation, such as the documentation for meta or clause-processor.

    A few other features of ACL2 with interesting logical foundations are defchoose, defabsstobj, and the interaction of packages with local (see the ``Essay on Hidden Packages'').

    NEXT SECTION: developers-guide-programming