POLISHING-PROOFS-TUTORIAL

A Beginner's Guide to Developing and Polishing ACL2 Proofs

This tutorial has been written by Shilpi Goel and Sandip Ray.

This documentation is about "styles" and user-level experience and mainly intended to provide advice to the new user to become successful at approaching a formalization and proof problem in ACL2. In this note we will consider the following two basic questions:

What kind of factors should the user look for and consider to successfully guide the theorem prover to a successful proof?

Once the proof is completed, how should it be cleaned up in order to facilitate maintenance and extension of the book?

The suggestions and advice provided herein are of course guided by our own styles of approaching an ACL2 proof. Eventually every user develops her unique proof style and there is no "right" or "wrong" approach. One should just use the style that one feels most comfortable with. Thus, the advice in this documentation should be taken as a template for guidance to eventually develop your own style --- not as an immutable ground truth on an ACL2 proof
approach. Furthermore, many of the suggestions, at least in the answer to the first question above, are influenced by common ACL2 guidance and also discussed elsewhere in the ACL2 documentation (see, for example, THE-METHOD). Here we show how some of these guidances translate into concrete directives in the interaction with the theorem prover.

To make the ideas concrete, we will take the following conjecture:

Conjecture:
"If the lower 32 bits of a number are all zero, then any
bit at a position less than 32 will have the value
zero."

This is an arithmetic conjecture, and it actually arose in the
context of developing correctness proofs for X86 machine code.

Corresponding to (1) and (2) above, we will consider the proof of this conjecture twice. First we will develop the proof, and then we will clean it up.

PART A: Proof Development:

Here is a formal version of the conjecture above.

(defthm lower-32-all-zero-no-

In this document, we will not consider the (interesting but orthogonal) question of whether this is the "right" approach to formalize the English statement of the conjecture in ACL2 or not. The focus of this document is to determine how to get ACL2 to prove it, once such a formalization is given.

The conjecture above is an arithmetic conjecture. So the first thing that a user should do is use an arithmetic library. This is specifically relevant for arithmetic, and in particular, reasoning about functions like floor and mod. This is because (1) such functions are difficult to reason about, and (2) ACL2 now has powerful support of libraries of lemmas for reasoning about them.

So we first include the library arithmetic-5 (which, as of this writing, is one of the most elaborate arithmetic libraries in ACL2).

(include-book "arithmetic-5/top" :dir :system)

We then submit the conjecture.

(defthm lower-32-all-zero-no-

The proof fails. So we will apply "The Method" to debug this. In particular, the following checkpoint shows up:

The key to success with ACL2 is the ability to read such checkpoints and determine what to do. So what information can we glean from the above?

First, we notice that 4294967296 is (expt 2 32) . What this checkpoint tells us is that:

Given the following three facts,

(mod res (expt 2 32)) == 0,

j is a nonnegative integer whose value is less than 32, and

res is a nonnegative integer,

ACL2 can not prove that 1/2 of (floor res (expt 2 j)) is an integer. In other words, ACL2 can not prove that (floor res (expt 2 j)) returns an even number under the given conditions.

We ask ourselves - is the above actually true? So, first we figure out what the function floor does. From the ACL2 documentation, we find that (floor x y) divides x by y and truncates the quotient. (Actually, the quotient is truncated towards negative infinity but we can ignore this fact for the purpose of this exercise).

If (mod res (expt 2 32)) is zero, then res is a multiple of (expt 2 32). So, if res is divided by (expt 2 j) where j is a value less than 32, the quotient will always be even (to be precise, it will be a multiple of 2^(32 - j). Hence, informally, we have reasoned that the above is a theorem and hence, should be provable.

Notice that if i is 32 in the above checkpoint, this is equivalent to the checkpoint (1). Indeed, the sufficiency of
the lemma lemma-floor-inference above for proving our original goal is evident from the following:

Thus we know that our final theorem will be proven if
lemma-floor-inference goes through. So we shift our focus now to proving
lemma-floor-inference.

We guess that may be ACL2 can not infer from (equal (mod res (expt
2 i)) 0) that res is a multiple of (expt 2 i) because had it been
able to infer that, it would have been able to figure out the
conclusion of lemma-floor-inference.

We include the hints so that ACL2 does not simplify away
/eliminate the definition of floor. However,
the proof does not go through. Inspecting the failed
proof, we notice that the following is where the proof
attempt stalls:

Note: The following are not hard-and-fast rules but are
essentially tips to have a cleaner proof.

First, it is typically not a good idea to non-locally include a
book like arithmetic-5. This is because later on in
the project you might want to use a different arithmetic book,
which might be incompatible with arithmetic-5.

Second, it is worthwhile to try to make the lemmas that are
auxiliary in the way of proving the main result local. This helps
to have a lot more control in the subsequent use of the book --- in
particular, this way, you do not need to worry about whether a
weird auxiliary rule that you had used as a hack to prove your main
theorem could subsequently cause a problem later.

Third, it is a good practice always to disable the rule that you
are using as a :use hint.

Fourth, if at all possible, put all the subgoal hints to the top
goal (or to the top subgoals after the induction). This is not
always going to work, but it is typically ok if the only subgoal
hints are :use hints. Putting :use hints at subgoal is absolutely
ok when you were developing the proof since you really want to put
it at the subgoal in which you found the problem, so that you do
not have to think about the extraneous stuff. But when everything
works, it is often better to put all the subgoal :use hints
together and put it at the top level. One key reason for this is
that as ACL2 goes from one version to another, the subgoal numbers
where the hint is applicable may change (due to slight change in
the simplification heuristic or some such). Putting it at the top
level is more robust to such changes.

Fifth, try to remove the :do-not hints. This is because they are
not usually needed for proof replay (only for debugging failed
proofs when you are developing them) and hence do not need to be
around in the cleaned-up version. This makes the script look less
messy. There are exceptions to this of course, --- there are
situations when a :do-not hint is actually necessary (the typical
case is :do-not '(generalize)), but more often than not they are
not. The simplest way to remove the hints from the cleaned-up
version is to remove the :do-not hint and if the proof does not
work just put it back on.