Trail Management
This document describes how the generated code and
the runtime system manage the trail.
It should be read in conjunction with the
"Trailing" section of the language reference manual.
Purpose of the trail
The trail is a data structure which in Mercury is used for three things.
- For code using backtrackable destructive update (i.e. code with modes
such as `mdi'/`muo' which use `mostly_unique' insts), the trail
is used to undo backtrackable destructive updates to arguments
when backtracking (or the like) occurs.
- For code using dynamic moding (`any' insts),
the trail is used to undo updates to the representation
of variable bindings or to the constraint store
when backtracking (or the like) occurs.
- For code using dynamic scheduling (where constraints
are not checked for satisfiability as soon as they are called,
but are instead delayed, with the variable bindings
or constraint store holding a representation of the delayed
constraints), the trail is also used to check for floundering
(attempting to commit to a solution which there are still
outstanding delayed constraints).
Nature of the trail
The trail is represented as a stack of trail entries.
There are two kinds of trail entries, value trail entries
and functions trail entries.
Value trail entries hold two words, an address and its corresponding
saved value. C code can create a value trail entry before modifying
some data, and then on backtracking the Mercury runtime will reset
the modified word with the saved value.
Function trail entries hold a function pointer and an opaque (`void *')
data pointer. C code can register a function trail entry, and then in certain
circumstances the Mercury runtime will call back the specified function with
its correspoding data, also passing an MR_trail_reason, which specifies why
the function was called.
These circumstances (and their corresponding reasons) are:
- On backtracking, which is further subdivided into
- ordinary backtracking on failure (MR_undo)
- when an exception is thrown (MR_exception)
- when executing a retry command in the debugger (MR_retry)
- At soft commits, e.g. for solutions/2 or if-then-elses with
nondet conditions (MR_solve)
- At hard commits (MR_commit)
In addition to the trail stack itself, which is implemented as an array of
trail entries (stored in the memory zone MR_trail_zone) and a pointer to the
first unused entry (MR_trail_ptr), the trail management code also maintains
the value of two additional variables, MR_ticket_counter and
MR_ticket_high_water, which are used to implement the "trail ticket"
abstraction (see below) and the MR_current_choicepoint_id() macro
(see the "avoiding redundant trailing" section of the language reference manual).
Trail operations
The trail is implemented as an ADT which is modified only via
certain operations, which are mostly defined as macros in
runtime/mercury_trail.h.
There are two kinds of trail operations: public operations, which are
available for use by the user in `pragma c_code', and private operations,
which are intended for use only by the Mercury implementation itself.
Public trail operations
The public operations include
- operations to add entries to the trail: MR_trail_value(),
MR_trail_current_value(), and MR_trail_function().
- operations to query the value of MR_ticket_counter:
MR_current_choicepoint_id().
They are documented in the "Trailing" section of the Mercury language
reference manual.
Private trail operations
The private operations are used (indirectly) by the code generator, which
inserts calls to them in the generated code.
The private operations are also directly used by a few other parts of the
Mercury implementation, e.g. the exception handling code (library/exception.m
and the MR_create_exception_handler() macro in runtime/mercury_stacks.h) and
the support for the debugger's retry command (MR_trace_retry() in
trace/mercury_trace_internal.c).
For each private operation that the code generator can generate,
there is a corresponding LLDS or MLDS instruction.
The private operations are documented in runtime/mercury_trail.h,
and also in compiler/llds.m, and are also documented below.
Trail tickets
At any point where we create a choice point, i.e. a point to which we may want
to backtrack (including exception handlers, and if debugging is enabled,
all procedure prologues [to handle the debugger retry command]), or where we
are about to start a goal for which we may want to do a soft or hard commit,
the code must allocate a trail ticket, using the MR_store_ticket() macro.
This saves the trail pointer, and allocates a new ticket id by incrementing
MR_ticket_high_water and assigning that to MR_ticket_counter. Note that the
trail pointer is typically saved on the stack, but the ticket id is only
stored in MR_ticket_counter. If the code needs to save the ticket id too,
then it must call MR_mark_ticket_stack().
Whenever we backtrack, or whenever we do a hard or soft commit,
the code must call MR_reset_ticket(), passing it the trail pointer
value that was saved by MR_store_ticket(), together with the appropriate
MR_trail_reason. This will walk the trail, applying all the trail
entries from the top of the trail down to the given pointer,
most recently added entries first.
In addition, if the ticket will no longer be used,
it should then be pruned or discarded (see below).
Whenever we do a hard or soft commit,
the code must "prune" the trail tickets that we have allocated,
using either MR_prune_ticket(), which prunes the most recently
allocated ticket, or MR_prune_tickets_to(), which prunes all the
tickets allocated since the corresponding call to MR_mark_ticket_stack().
Whenever we backtrack, the code must "discard" the trail tickets that have
been allocated since the choice point was created,
using either MR_discard_ticket(), which discards the most recently
allocated ticket, or MR_discard_tickets_to(), which discards all the
tickets allocated since the corresponding call to MR_mark_ticket_stack().
Invariants
- MR_trail_ptr:
- increases when trail entries are created
- gets reset to its previous value on backtracking
- does not decrease after commits
- is non-decreasing during forward execution
- MR_ticket_high_water:
- increases at each choice point
- gets reset to its previous value on backtracking
- does not decrease after commits
- is non-decreasing during forward execution
- MR_ticket_counter:
- increases at each choice point
- gets reset to its previous value on backtracking or after commits
- is non-decreasing during commit-free forward execution
- values are not reused during forward execution
- any call to a model_det goal will leave it unchanged
(MR_ticket_counter may change during the execution of the goal,
but its value when the goal exits will be the same
as the value it had when the goal was entered,
because the goal must prune away any tickets that
it allocates)
- any call to a model_semi goal which succeeds will likewise
leave it unchanged
- any call to a goal which fails will leave it unchanged
(any tickets allocated must be discarded or at least pruned
before failing)
Last update was $Date: 2000/03/22 14:45:04 $ by $Author: fjh $@cs.mu.oz.au.