:
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 community 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.