Storing User Information in the Backtrace

The debugger allows the user to store some private information in the backtrace. It allocates a Prolog variable in each break level and in each invocation. The breakpoint test private(Priv) unifies Priv with the private information associated with the break level, while the test goal_private(GPriv) unifies GPriv with the Prolog variable stored in the invocation.

Both variables are initially unbound, and behave as if they were passed around the program being debugged in additional arguments. This implies that any variable assignments done within these variables are undone on backtracking.

The private condition practically gives you access to a Prolog variable shared by all invocations of a break level. This makes it possible to remember a term and look at it later, in a possibly more instantiated form, as shown by the following example.

     memory(Term) :-
             execution_state(private(P)),
             memberchk(myterm(Term), P).
     
     | ?- trace, append([1,2,3,4], [5,6], L).
             1      1 Call: append([1,2,3,4],[5,6],_514) ? @
     | :- append(_,_,L)^memory(L).
             1      1 Call: append([1,2,3,4],[5,6],_514) ? c
             2      2 Call: append([2,3,4],[5,6],_2064) ? c
             3      3 Call: append([3,4],[5,6],_2422) ? c
             4      4 Call: append([4],[5,6],_2780) ? @
     | :- memory(L), write(L), nl.
     [1,2,3|_2780]
             4      4 Call: append([4],[5,6],_2780) ?
     

The predicate memory/1 receives the term to be remembered in its argument. It gets hold of the private field associated with the break level in variable P, and calls memberchk/2 (see Lists), with the term to be remembered, wrapped in myterm, as the list element, and the private field, as the list. Thus the latter, initially unbound variable, is used as an open-ended list. For example, when memory/1 is called for the first time, the private field gets instantiated to [myterm(Term)|_]. If later you call memory/1 with an uninstantiated argument, it will retrieve the term remembered earlier and unify it with the argument.

The above trace excerpt shows how this utility predicate can be used to remember an interesting Prolog term. Within invocation number 1 we call memory/1 with the third, output argument of append/3, using the @ command (see Debug Commands). A few tracing steps later, we retrieve the term remembered and print it, showing its current instantiation. Being able to access the instantiation status of some terms of interest can be very useful in debugging. In library(debugger_examples) we describe new debugger commands for naming Prolog variables and providing name-based access to these variables, based on the above technique.

We could have avoided the use of memberchk/2 in the example by simply storing the term to be remembered in the private field itself (memory(Term) :- execution_state(private(Term)).). But this would have made the private field unusable for other purposes. For example, the finite domain constraint debugger (see FDBG) would stop working, as it relies on the private fields.

There is only a single private variable of both kinds within the given scope. Therefore the convention of using an open ended list for storing information in private fields, as shown in the above example, is very much recommended. The different users of the private field are distinguished by the wrapper they use (e.g. myterm/1 above, fdbg/1 for the constraint debugger, etc.). Future SICStus Prolog releases may enforce this convention by providing appropriate breakpoint tests.

We now present an example of using the goal private field. Earlier we have shown a spypoint definition that made a predicate invisible in the sense that its ports are silently passed through and it is automatically skipped over. However, with that earlier solution, execution always continues in trace mode after skipping. We now improve the spypoint definition: the mode in which the Call port was reached is remembered in the goal private field, and the mode action variable is reset to this value at the Exit port.

     mode_memory(Mode) :-
             execution_state(goal_private(GP)),
             memberchk(mymode(Mode), GP).
     
     | ?- spy(foo/2, -[silent,proceed,
                      true(mode_memory(MM)),
                     (   call -> get(mode(MM)), inv(Inv), skip(Inv)
                     ;   exit -> mode(MM)
                     ;   true
                     )]).
     

Here, we first define an auxiliary predicate mode_memory/1, which uses the open list convention for storing information in the goal private field, applying the mymode/1 wrapper. We then create a spypoint for foo/2, whose action part first sets the print and command action variables. Next, the mode_memory/1 predicate is called, unifying the mode memory with the MM variable. We then branch in the action part: at Call ports the uninstantiated MM is unified with the current mode, and a skip command is issued. At Exit ports MM holds the mode saved at the Call port, so the mode(MM) action re-activates this mode. At all other ports we just silently proceed without changing the debugger mode.