WORMHOLE

ld without state -- a short-cut to a parallel universe
Major Section:  MISCELLANEOUS

Example Form:
(wormhole t 'interactive-break nil '(value 'hi!))
                             ; Enters a recursive read-eval-print loop
                             ; on a copy of the ``current state'' and
                             ; returns nil!

General Form: (wormhole pseudo-flg name input form :current-package ... ; known package name :ld-skip-proofsp ... ; t, nil or 'include-book :ld-redefinition-action ; nil or '(:a . :b) :ld-prompt ... ; nil, t, or some prompt printer fn :ld-keyword-aliases ... ; an alist pairing keywords to parse info :ld-pre-eval-filter ... ; :all, :query, or some new name :ld-pre-eval-print ... ; nil, t, or :never :ld-post-eval-print ... ; nil, t, or :command-conventions :ld-evisc-tuple ... ; nil or '(alist level length hiding-cars) :ld-error-triples ... ; nil or t :ld-error-action ... ; :continue, :return, or :error :ld-query-control-alist ; alist supplying default responses :ld-verbose ...) ; nil or t

The keyword arguments above are exactly those of ld (see ld) except that three of ld's keyword arguments are missing: the three that specify the channels standard-oi, standard-co and proofs-co. Essentially wormhole is just a call of ld on the current state with the given keyword arguments. Wormhole always returns nil. The amazing thing about wormhole is that it calls ld and interacts with the user even though state is not available as an argument!

Wormhole does this by manufacturing a ``wormhole state,'' a copy of the ``current state'' (whatever that is) modified so as to contain some of the wormhole arguments. Ld is called on that wormhole state with the three ld channels directed to ACL2's ``comment window.'' At the moment, the comment window is overlaid on the terminal and you cannot tell when output is going to *standard-co* and when it is going to the comment window. But we imagine that eventually a different window will pop up on your screen. In any case, the interaction provided by this call of ld does not modify the state ``from which'' wormhole was called, it modifies the copied state. When ld exits (e.g., in response to :q being typed in the comment window) the wormhole state evaporates and wormhole returns nil. Logically and actually (from the perspective of the ongoing computation) nothing has happened except that a ``no-op'' function was called and returned nil.

The name wormhole is meant to suggest the idea that the function provides easy access to state in situations where it is apparently impossible to get state. Thus, for example, if you define the factorial function, say, except that you sprinkled into its body appropriate calls of wormhole, then the execution of (factorial 6) would cause interactive breaks in the comment window. During those breaks you would apparently be able to inspect the ``current state'' even though factorial does not take state as an argument. The whole notion of there being a ``current state'' during the evaluation of (factorial 6) is logically ill-defined. And yet, we know from practical experience with the sequential computing machines upon which ACL2 is implemented that there is a ``current state'' (to which the factorial function is entirely insensitive) and that is the state to which wormhole ``tunnels.'' A call of wormhole from within factorial can pass factorial-specific information that is embedded in the wormhole state and made available for inspection by the user in an interactive setting. But no information ever flows out of a wormhole state: wormhole always returns nil.

There are some restrictions about what can be done inside a wormhole. As you may imagine, we really do not ``copy the current state'' but rather just keep track of how we modified it and undo those modifications upon exit. An error is signalled if you try to modify state in an unsupported way. For this same reason, wormholes do not allow updating of any user-defined single-threaded objects. See stobj.

There are four arguments to wormhole that need further explanation: pseudo-flg, name, input, and form. Roughly speaking, the value of pseudo-flg should be t or nil and indicates whether we are actually to enter a wormhole or just return nil immediately. The actual handling of pseudo-flg is more sophisticated and is explained in detail at the end of this documentation.

Name and input are used as follows. Recall that wormhole copies the ``current state'' and then modifies it slightly to obtain the state upon which ld is called. We now describe the modifications. First, the state global variable 'wormhole-name is set to name, which may be any non-nil ACL2 object but is usually a symbol. Then, 'wormhole-input is set to input, which may be any ACL2 object. Finally, and inexplicably, 'wormhole-output is set to the value of 'wormhole-output the last time a wormhole named name was exited (or nil if this is the first time a wormhole named name was entered). This last aspect of wormholes, namely the preservation of 'wormhole-output, allows all the wormholes of a given name to communicate with each other.

We can now explain how form is used. The modified state described above is the state on which ld is called. However, standard-oi -- the input channel from which ld reads commands -- is set so that the first command that ld reads and evaluates is form. If form returns an error triple with value :q, i.e., form returns via (value :q), then no further commands are read, ld exits, and the wormhole exits and returns nil. But if form returns any other value (or is not an error triple), then subsequent commands are read from the comment window.

As usual, the ld-specials affect whether a herald is printed upon entry, whether form is printed before evaluation, whether a prompt is printed, how errors are handled, etc. The ld-specials can be specified with the corresponding arguments to wormhole. It is standard practice to call wormhole so that the entry to ld and the evaluation of form are totally silent. Then, tests in form can inspect the state and decide whether user interaction is desired. If so, form can appropriately set ld-prompt, ld-error-action, etc., print a herald, and then return (value :invisible). Recall (see ld) that (value :invisible) causes ld not to print a value for the just executed form. The result of this arrangement is that whether interaction occurs can be based on tests that are performed on the wormhole state after (@ wormhole-input) and the last (@ wormhole-output) are available for inspection. This is important because outside the wormhole you can access wormhole-input (you are passing it into the wormhole) but you may not be able to access the current state (because you might be in factorial) and you definitely cannot access the wormhole-output of the last wormhole because it is not part of the ACL2 state. Thus, if the condition under which you wish to interact depends upon the state or that part of it preserved from the last wormhole interaction, that condition can only be tested from within the wormhole, via form.

It is via this mechanism that break-rewrite (see break-rewrite) is implemented. To be more precise, the list of monitored runes is maintained as part of the preserved wormhole-output of the break-rewrite wormhole. Because it is not part of the normal state, it may be changed by the user during proofs. That is what allows you to install new monitors while debugging proofs. But that means that the list of monitored runes cannot be inspected from outside the wormhole. Therefore, to decide whether a break is to occur when a given rule is applied, the rewriter must enter the break-rewrite wormhole, supplying a form that causes interaction if the given rule's break condition is satisfied. The user perceives this as though the wormhole was conditionally entered -- a perception that is happily at odds with the informed user's knowledge that the list of monitored runes is not part of the state. In fact, the wormhole was unconditionally entered and the condition was checked from within the wormhole, that being the only state in which the condition is known.

Another illustrative example is available in the implemention of the monitor command. How can we add a new rune to the list of monitored runes while in the normal ACL2 state (i.e., while not in a wormhole)? The answer is: by getting into a wormhole. In particular, when you type (monitor rune expr) at the top-level of ACL2, monitor enters the break-rewrite wormhole with a cleverly designed first form. That form adds rune and expr to the list of monitored runes -- said list only being available in break-rewrite wormhole states. Then the first form returns (value :q), which causes us to exit the wormhole. By using ld-specials that completely suppress all output during the process, it does not appear to the user that a wormhole was entered. The moral here is rather subtle: the first form supplied to wormhole may be the entire computation you want to perform in the wormhole; it need not just be a predicate that decides if interaction is to occur. Using wormholes of different names you can maintain a variety of ``hidden'' data structures that are always accessible (whether passed in or not). This appears to violate completely the applicative semantics of ACL2, but it does not: because these data structures are only accessible via wormholes, it is impossible for them to affect any ACL2 computation (except in the comment window).

As one might imagine, there is some overhead associated with entering a wormhole because of the need to copy the current state. This brings us back to pseudo-flg. Ostensibly, wormhole is a function and hence all of its argument expressions are evaluated outside the function (and hence, outside the wormhole it creates) and then their values are passed into the function where an appropriate wormhole is created. In fact, wormhole is a macro that permits the pseudo-flg expression to peer dimly into the wormhole that will be created before it is created. In particular, pseudo-flg allows the user to access the wormhole-output that will be used to create the wormhole state.

This is done by allowing the user to mention the (apparently unbound) variable wormhole-output in the first argument to wormhole. Logically, wormhole is a macro that wraps

(let ((wormhole-output nil)) ...)
around the expression supplied as its first argument. So logically, wormhole-output is always nil when the expression is evaluated. However, actually, wormhole-output is bound to the value of (@ wormhole-output) on the last exit from a wormhole of the given name (or nil if this is the first entrance). Thus, the pseudo-flg expression, while having to handle the possibility that wormhole-output is nil, will sometimes see non-nil values. The next question is, of course, ``But how can you get away with saying that logically wormhole-output is always nil but actually it is not? That doesn't appear to be sound.'' But it is sound because whether pseudo-flg evaluates to nil or non-nil doesn't matter, since in either case wormhole returns nil. To make that point slightly more formal, imagine that wormhole did not take pseudo-flg as an argument. Then it could be implemented by writing
(if pseudo-flg (wormhole name input form ...) nil).
Now since wormhole always returns nil, this expression is equivalent to (if pseudo-flg nil nil) and we see that the value of pseudo-flg is irrelevant. So we could in fact allow the user to access arbitrary information to decide which branch of this if to take. We allow access to wormhole-output because it is often all that is needed. We don't allow access to state (unless state is available at the level of the wormhole call) for technical reasons having to do with the difficulty of overcoming translate's prohibition of the sudden appearance of the variable state.

We conclude with an example of the use of pseudo-flg. This example is a simplification of our implementation of break-rewrite. To enter break-rewrite at the beginning of the attempted application of a rule, rule, we use

(wormhole
 (and (f-get-global 'brr-mode state)
      (member-equal (access rewrite-rule rule :rune)
                    (cdr (assoc-eq 'monitored-runes wormhole-output))))
 'break-rewrite
 ...)
The function in which this call of wormhole occurs has state as a formal. The pseudo-flg expression can therefore refer to state to determine whether 'brr-mode is set. But the pseudo-flg expression above mentions the variable wormhole-output; this variable is not bound in the context of the call of wormhole; if wormhole were a simple function symbol, this expression would be illegal because it mentions a free variable.

However, it is useful to think of wormhole as a simple function that evaluates all of its arguments but to also imagine that somehow wormhole-output is magically bound around the first argument so that wormhole-output is the output of the last break-rewrite wormhole. If we so imagine, then the pseudo-flg expression above evaluates either to nil or non-nil and we will enter the wormhole named break-rewrite in the latter case.

Now what does the pseudo-flg expression above actually test? We know the format of our own wormhole-output because we are responsible for maintaining it. In particular, we know that the list of monitored runes can be obtained via

(cdr (assoc-eq 'monitored-runes wormhole-output)).
Using that knowledge we can design a pseudo-flg expression which tests whether (a) we are in brr-mode and (b) the rune of the current rule is a member of the monitored runes. Question (a) is answered by looking into the current state. Question (b) is answered by looking into that part of the about-to-be-created wormhole state that will differ from the current state. To reiterate the reason we can make wormhole-output available here even though it is not in the current state: logically speaking the value of wormhole-output is irrelevant because it is only used to choose between two identical alternatives. This example also makes it clear that pseudo-flg provides no additional functionality. The test made in the pseudo-flg expression could be moved into the first form evaluated by the wormhole -- changing the free variable wormhole-output to (@ wormhole-output) and arranging for the first form to return (value :q) when the pseudo-flg expression returns nil. The only reason we provide the pseudo-flg feature is because it allows the test to be carried out without the overhead of entering the wormhole.

Wormholes can be used not only in :program mode definitions but also in :logic mode definitions. Thus, it is possible (though somewhat cumbersome without investing in macro support) to annotate logical functions with output facilities that do not require state. These facilities do not complicate proof obligations. Suppose then that one doctored a simple function, e.g., APP, so as to do some printing and then proved that APP is associative. The proof may generate extraneous output due to the doctoring. Furthermore, contrary to the theorem proved, execution of the function appears to affect *standard-co*. To see what the function ``really'' does when evaluated, enter raw lisp and set the global variable *inhibit-wormhole-activityp* to t.