DEFPROXY

define a non-executable :program-mode function for attachment
Major Section:  EVENTS

This event is provided for those who want to experiment with defattach using :program mode functions, and without proof obligations or constraints on cycles in the extended ancestors graph; see defattach. If you merely want to define a stub or a non-executable function, see defstub or see defun-nx, respectively.

See distributed book books/misc/defproxy-test.lisp for an extended (but simple) example.

Example Forms:
(defproxy subr1 (* *) => *)
(defproxy add-hash (* * hashtable) => (mv * hashtable))

General Form:
(defproxy name args-sig => output-sig)
where name is a new function symbol and (name . args-sig) => output-sig) is a signature; see signature.

The macro defproxy provides a convenient way to introduce a ``proxy'': a :program mode function that can be given attachments for execution (see defattach), assuming that there is an active trust tag (see defttag). Thus, a defproxy calls expands to a defun form with the following xargs declare form: :non-executable :program. Note that verify-termination is not permitted for such a function. However, it is permitted to put the proxy function into :logic mode by use of an encapsulate event; indeed, this is the way to ``upgrade'' an attachment so that the normal checks are performed and no trust tag is necessary.

In order to take advantage of a defproxy form, one provides a subsequent defattach form to attach an executable function to the defproxy-introduced function. When :skip-checks t is provided in a defattach form, the usual checks for defattach events are skipped, including proof obligations and the check that the extended ancestor relation has no cycles (see defattach). There must be an active trust tag (see defttag) in order to use :skip-checks t. In that case the use of :skip-checks t is permitted; but note that its use is in fact required if a :program mode function is involved, and even if a :logic mode function is involved that has not been guard-verified.

The following log shows a simple use of defproxy.

ACL2 !>(defproxy foo-stub (*) => *)

Summary
Form:  ( DEFUN FOO-STUB ...)
Rules: NIL
Time:  0.01 seconds (prove: 0.00, print: 0.00, other: 0.01)
 FOO-STUB
ACL2 !>(foo-stub '(3 4 5))


ACL2 Error in TOP-LEVEL:  ACL2 cannot ev the call of undefined function
FOO-STUB on argument list:

((3 4 5))

To debug see :DOC print-gv, see :DOC trace, and see :DOC wet.

ACL2 !>(defun foo-impl (x)
         (declare (xargs :mode :program
                         :guard (or (consp x) (eq x nil))))
         (car x))

Summary
Form:  ( DEFUN FOO-IMPL ...)
Rules: NIL
Time:  0.00 seconds (prove: 0.00, print: 0.00, other: 0.00)
 FOO-IMPL
ACL2 !>(defttag t)

TTAG NOTE: Adding ttag T from the top level loop.
 T
ACL2 !>(defattach (foo-stub foo-impl) :skip-checks t)

Summary
Form:  ( DEFATTACH (FOO-STUB FOO-IMPL) ...)
Rules: NIL
Time:  0.00 seconds (prove: 0.00, print: 0.00, other: 0.00)
 :ATTACHMENTS-RECORDED
ACL2 !>(foo-stub '(3 4 5))
3
ACL2 !>

One can replace this attachment with one that uses :logic mode functions and does not skip checks. The idea is to reintroduce the proxy function using an encapsulate form, which does not require redefinition (see ld-redefinition-action) to be enabled, and either to put the attachment into :logic mode with the guard verified, as we do in the example below, or else to attach to a different guard-verified :logic mode function.

ACL2 !>(defattach (foo-stub nil) :skip-checks t) ; remove attachment

Summary
Form:  ( DEFATTACH (FOO-STUB NIL) ...)
Rules: NIL
Time:  0.00 seconds (prove: 0.00, print: 0.00, other: 0.00)
 :ATTACHMENTS-RECORDED
ACL2 !>(encapsulate
        ((foo-stub (x) t :guard (true-listp x)))
        (local (defun foo-stub (x) (cdr x)))
        (defthm foo-stub-reduces-acl2-count
          (implies (consp x)
                   (< (acl2-count (foo-stub x))
                      (acl2-count x)))))

[[ ... output omitted here ... ]]

The following constraint is associated with the function FOO-STUB:

(IMPLIES (CONSP X) (< (ACL2-COUNT (FOO-STUB X)) (ACL2-COUNT X)))

Summary
Form:  ( ENCAPSULATE ((FOO-STUB ...) ...) ...)
Rules: NIL
Warnings:  Non-rec
Time:  0.02 seconds (prove: 0.01, print: 0.00, other: 0.01)
 T
ACL2 !>(verify-termination foo-impl)

Since FOO-IMPL is non-recursive, its admission is trivial.  We could
deduce no constraints on the type of FOO-IMPL.

Computing the guard conjecture for FOO-IMPL....

The guard conjecture for FOO-IMPL is trivial to prove.  FOO-IMPL is
compliant with Common Lisp.

Summary
Form:  ( DEFUN FOO-IMPL ...)
Rules: NIL
Time:  0.00 seconds (prove: 0.00, print: 0.00, other: 0.00)

Summary
Form:  ( MAKE-EVENT (VERIFY-TERMINATION-FN ...))
Rules: NIL
Time:  0.01 seconds (prove: 0.00, print: 0.00, other: 0.01)
 FOO-IMPL
ACL2 !>(defttag nil) ; optional
 NIL
ACL2 !>(defattach (foo-stub foo-impl))

The guard proof obligation is

(IMPLIES (TRUE-LISTP X)
         (OR (CONSP X) (EQ X NIL))).

But we reduce the conjecture to T, by primitive type reasoning.

Q.E.D.

This concludes the guard proof.


We now prove that the attachment satisfies the required constraint.
The goal to prove is

(IMPLIES (CONSP X)
         (< (ACL2-COUNT (FOO-IMPL X))
            (ACL2-COUNT X))).

[[ ... output omitted here ... ]]

Q.E.D.

Summary
Form:  ( DEFATTACH (FOO-STUB FOO-IMPL))
Rules: ((:DEFINITION ACL2-COUNT)
        (:DEFINITION FOO-IMPL)
        (:ELIM CAR-CDR-ELIM)
        (:FAKE-RUNE-FOR-LINEAR NIL)
        (:FAKE-RUNE-FOR-TYPE-SET NIL)
        (:REWRITE CAR-CONS)
        (:REWRITE CDR-CONS)
        (:TYPE-PRESCRIPTION ACL2-COUNT))
Time:  0.02 seconds (prove: 0.01, print: 0.01, other: 0.00)
 :ATTACHMENTS-RECORDED
ACL2 !>

We close with some remarks on the checking of guards in the case that defattach has been called with keyword argument :skip-checks t. We illustrate with examples, where we assume an attachment pair (f . g) created by an event (defattach ... (f g) ... :skip-checks t ...). A good model for the treatment of :skip-checks t is dependent on whether f was introduced with defproxy or with encapsulate: for defproxy, the normal guard-related checks are treated as skipped, while for encapsulate, they are assumed to hold.

First suppose that f was introduced using defproxy, and consider the following example.

(defproxy f (*) => *)
(defun g (x) (car x)) ; not guard-verified; implicit guard of t is too weak
(defttag t) ; trust tag needed for :skip-checks t
(defattach (f g) :skip-checks t)
If we try to evaluate the form (f 3) in ACL2, then the top-level so-called ``executable counterpart'' (i.e., the logically-defined funcction, also known as the ``*1*'' function) of f is invoked. It calls the executable counterpart of g, which calls the executable counterpart of car, which in turn checks the guard of car and causes a guard violation error (unless we first turn off guard-checking; see set-guard-checking).
ACL2 !>(trace$ f g)
 ((F) (G))
ACL2 !>(f 3)
1> (ACL2_*1*_ACL2::F 3)
  2> (ACL2_*1*_ACL2::G 3)


ACL2 Error in TOP-LEVEL:  The guard for the function call (CAR X),
which is (OR (CONSP X) (EQUAL X NIL)), is violated by the arguments
in the call (CAR 3).  To debug see :DOC print-gv, see :DOC trace, and
see :DOC wet.  See :DOC set-guard-checking for information about suppressing
this check with (set-guard-checking :none), as recommended for new
users.

ACL2 !>

Little changes if we modify the example above by strengtheing the guard of g.

(defproxy f (*) => *)
(defun g (x)
  (declare (xargs :guard (consp x)))
  (car x))
(defttag t) ; trust tag needed for :skip-checks t
(defattach (f g) :skip-checks t)
The result of evaluating (f 3) is as before, except that this time the guard violation occurs at the time that g is called.
ACL2 !>(trace$ f g)
 ((F) (G))
ACL2 !>(f 3)
1> (ACL2_*1*_ACL2::F 3)
  2> (ACL2_*1*_ACL2::G 3)


ACL2 Error in TOP-LEVEL:  The guard for the function call (G X), which
is (CONSP X), is violated by the arguments in the call (G 3).  To debug
see :DOC print-gv, see :DOC trace, and see :DOC wet.  See :DOC set-
guard-checking for information about suppressing this check with (set-
guard-checking :none), as recommended for new users.

ACL2 !>

Now consider a slight variation of the example just above, in which f is introduced using encapsulate instead of using defproxy.

(encapsulate ( ((f *) => *) )
             (local (defun f (x) x)))
(defun g (x)
  (declare (xargs :guard (consp x)))
  (car x))
(defttag t) ; trust tag needed for :skip-checks t
(defattach (f g) :skip-checks t)
Since f was introduced by encapsulate instead of by defproxy, ACL2 assumes that the usual guard properties hold. In particular, it assumes that (informally speaking) the guard of f implies the guard of g; see defattach for details. So in this case, ACL2 proceeds under that assumption even though it's actually false, and the result is a raw Lisp error.

ACL2 !>(trace$ f g)
 ((F) (G))
ACL2 !>(f 3)
1> (ACL2_*1*_ACL2::F 3)
  2> (G 3)

***********************************************
************ ABORTING from raw Lisp ***********
Error:  Attempt to take the car of 3 which is not listp.
***********************************************

If you didn't cause an explicit interrupt (Control-C),
then the root cause may be call of a :program mode
function that has the wrong guard specified, or even no
guard specified (i.e., an implicit guard of t).
See :DOC guards.

To enable breaks into the debugger (also see :DOC acl2-customization):
(SET-DEBUGGER-ENABLE T)
ACL2 !>

If you replace g by its definition in the first example of this series, i.e. with a guard (implicitly) of t, you will see the same error, this time because the defattach event assumed that g was guard-verified.