mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-14 21:35:49 +00:00
Also fix some bugs in related code, and improve the related debugging
infrastructure.
-------------------
runtime/mercury_stacks.[ch]:
Fix bug 314 for temp frames created by nondet procedures. The fix will
probably also work for *det* procedures that create temp frames on the
nondet stack, but I can't think of a way to test that, because det
procedures create such frames only in very specific circumstances,
and I cannot think of a way to nest a recursive call inside those
circumstances.
The problem was that when we were creating new temp frames on
the nondet stack, we did not check whether the current nondet stack segment
had room for them. We now do.
The stack trace tracing code needs to know the size of each nondet stack
frame, since it uses the size to classify frames as temp or ordinary.
The size is given by the difference in address between the address of the
frame and the address of the previous frame. This difference would yield
an incorrect size and hence an incorrect frame classification if a temp
frame were allowed to have a frame on a different segment as its
immediate predecessor.
We prevent this by putting an ordinary (i.e. non-temp) frame at the bottom
of every new nondet stack segment as a sentinel. We hand-build this frame,
since it is not an "ordinary" ordinary frame. It is not created by a call,
so it has no meaningful success continuation, and since it does not make
any calls, no other frame's success continuation can point to it either.
If backtracking reaches this sentinel frame, we use this fact to free
all the segments beyond the one the sentinel frame is in, but keep the
frame the sentinel frame is in, since we are likely to need it again.
Document the reason why MR_incr_sp_leaf() does not have to check
whether a new stack segment is needed. (See the fix to llds_out_instr.m
below.)
runtime/mercury_stack_trace.[ch]:
When traversing the nondet stack, treat the sentinel frame specially.
We have to, since it is an ordinary frame (i.e. it is not a temp frame),
but it is not an "ordinary" ordinary frame: it does not make calls,
and hence calls cannot return to it, and it does not return to any
other frame either. It therefore does not have the layout structures
(label and proc) that the nondet stack traversal expects to find.
Fix an old bug: the nondet stack traversal used a simple directional
pointer comparison to check whether it has reached the bottom of the nondet
stack. This is NOT guaranteed to work in the presence of stack segments:
depending on exactly what addresses new stack segments get, a stack frame
can have an address BELOW the address of the initial stack frame
even if it is logically ABOVE that stack frame.
Another old bug was that a difference between two pointers, which could
be 64 bit, was stored in an int, which could be 32 bit.
The nondet stack traversal code used a similar directional comparison
to implement optionally stopping at an arbitrary point on the nondet stack.
Fixing this facility (the limit_addr parameter of MR_dump_nondet_stack)
while preserving reasonable efficiency would not be trivial, but it would
also be pointless, since the facility is not actually used. This diff
deletes the parameter instead.
Move some loop invariant code out of its loop.
trace/mercury_trace_cmd_developer.c:
trace/mercury_trace_external.c:
Don't pass the now-deleted parameter to mercury_stack_trace.c.
runtime/mercury_wrapper.c:
Record the zone of the initial nondet stack frame, since the fix
of mercury_stack_trace.c needs that info, and it is much more efficient
to set it up just once.
tests/hard_coded/bug314.{m,exp}:
The regression test for this bug.
tests/hard_coded/Mercury.options:
Compile the new test case with the options it needs.
tests/hard_coded/Mmakefile:
Enable the new test case.
-------------------
runtime/mercury_wrapper.c:
The compiler knows the number of words in a stack frame it is creating,
not necessarily the number of bytes (though it could put bounds on that
from the number of tag bits). Since this size must sync with the runtime,
change the runtime's variable holding this size to also be in words.
Note that similar changes would also be beneficial for other sizes.
compiler/llds_out_instr.m:
Conform to the change in mercury_wrapper.c, fixing an old bug
(mercury_wrapper.c reserved 128 BYTES for leaf procedures, but
llds_out_instr.m was using that space for procedures whose frames
were up to 128 WORDS in size.)
compiler/mercury_memory.c:
Conform to the change in mercury_wrapper.c.
-------------------
runtime/mercury_memory_zones.h:
Instead of starting to use EVERY zone at a different offset, do this
only for the INITIAL zones in each memory area, since only on these
is it useful. When the program first starts up, it WILL be using
the initial parts of the det stack, nondet stack and heap, so it is
useful to make sure that these do not collide in the cache. However,
when we allocate e.g. the second zone in e.g. the nondet stack, we are
no more likely to be beating on the initial part of any segment
of the det stack than on any other part of such segments.
If a new debug macro, MR_DEBUG_STACK_SEGMENTS_SET_SIZE is set (to an int),
use only that many words in each segment. This allows the segment switchover
code to be exercised and debugged with smaller test cases.
runtime/mercury_conf_param.h:
Document the MR_DEBUG_STACK_SEGMENTS_SET_SIZE macro.
Convert this file to four-space indentation with tabs expanded.
-------------------
runtime/mercury_overflow.h:
Make abort messages from overflows and underflows more useful by including
more information.
runtime/mercury_overflow.c:
Add a new function to help with the better abort messages.
Since this file did not exist before, create it.
runtime/Mmakefile:
Add the new source file to the list of source files.
-------------------
runtime/mercury_debug.[ch]:
Fix problems with the formatting of the debugging output from existing
functions.
Add new functions for dumping info about memory zones.
Factor out some common code.
Convert the header file to four-space indentation.
-------------------
runtime/mercury_grade.c:
Generate an error if stack segments are specified together with stack
extension
-------------------
trace/.gitignore:
util/.gitignore:
tests/debugger/.gitignore:
List some more files.
-------------------
runtime/mercury_context.c:
runtime/mercury_engine.[ch]:
runtime/mercury_misc.h:
compiler/notes/failure.html:
Fix white space.
771 lines
27 KiB
HTML
771 lines
27 KiB
HTML
<html>
|
|
<head>
|
|
<title>
|
|
Management of failure in the code generator
|
|
</title>
|
|
</head>
|
|
|
|
<body
|
|
bgcolor="#ffffff"
|
|
text="#000000"
|
|
>
|
|
|
|
<hr>
|
|
<!-------------------------->
|
|
|
|
This document describes the arrangement
|
|
for handling failure in the LLDS code generator.
|
|
It builds upon allocation.html,
|
|
which describes the mechanism we use
|
|
to preserve the values of variables that may be needed on backtracking
|
|
and the mechanism we use to set up resumption points.
|
|
|
|
<hr>
|
|
<!-------------------------->
|
|
|
|
<p>
|
|
|
|
<h2> RUNTIME DATA STRUCTURES </h2>
|
|
|
|
In the design of the nondet stack,
|
|
two registers point to nondet stack frames.
|
|
|
|
<p>
|
|
|
|
<ul>
|
|
<li>
|
|
The maxfr register always points to the topmost nondet stack frame.
|
|
<p>
|
|
<li>
|
|
While executing a model_non procedure,
|
|
the curfr register points to the ordinary nondet stack frame of that procedure.
|
|
While executing a model_det or model_semi procedure,
|
|
the curfr register points to the nondet stack frame
|
|
of the nearest model_non ancestor of that procedure.
|
|
(In the following, we will consider that any model_det or model_semi procedure
|
|
is effectively part of its nearest model_non caller.)
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
Each ordinary nondet stack frame contains five fixed slots:
|
|
|
|
<p>
|
|
|
|
<ul>
|
|
<li> prevfr, the address of the previous frame on the nondet stack
|
|
<li> redoip, the label to go to when backtracking reaches this frame
|
|
<li> redofr, the value to set curfr to when backtracking through redoip
|
|
<li> succip, the label to go to on success
|
|
<li> succfr, the value to reset curfr to on success (frame of our caller)
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
Ordinary nondet stack frames may also contain the values of variables
|
|
and/or temporary values such as saved trail pointers.
|
|
|
|
<p>
|
|
|
|
All temporary nondet stack frames
|
|
contain the three fixed slots, prevfr, redoip and redofr.
|
|
Temporary frames created by model_det and model_semi procedures
|
|
also have a fourth fixed slot, detfr, which points to the stack frame
|
|
(on the det stack) of the procedure that created them.
|
|
Temporary nondet stack frames
|
|
never contain any slots except these fixed slots.
|
|
Therefore for any given nondet frame its size determines its type.
|
|
If this is three words,
|
|
it is a temporary frame created by a model_non procedure;
|
|
if this is four words,
|
|
it is a temporary frame created by a model_det or model_semi procedure;
|
|
if this is five words or more,
|
|
it is an ordinary frame.
|
|
|
|
<p>
|
|
|
|
The fixed slots prevfr, redoip and redofr
|
|
are present in all nondet stack frames, ordinary and temporary,
|
|
at the same offset and with the same semantics.
|
|
Therefore code that accesses only these fields of a nondet stack frame
|
|
does not need to find out what kind of frame it is.
|
|
|
|
<p>
|
|
|
|
The detfr slot is for the accurate garbage collector;
|
|
it is never used by the backtracking mechanism.
|
|
Therefore in the following we can and will treat all temporary frames the same.
|
|
|
|
<p>
|
|
|
|
In grades that support stack segments,
|
|
the runtime system puts a sentinel frame
|
|
at the bottom of every segment after the first.
|
|
The sentinel frame appears as an ordinary frame,
|
|
with all five of the usual fixed slots,
|
|
but the two fields that ordinarily contain the success continuation
|
|
are not used for that purpose,
|
|
since sentinel frames do not correspond to calls, and thus cannot return.
|
|
Instead, the succfr slot contains
|
|
the value of the curfr abstract machine register
|
|
at the time the sentinel frame was created,
|
|
and the succip slot contains the address of the notreached label.
|
|
The address of the memory zone structure
|
|
that contains the segment the sentinel is at the bottom of
|
|
is stored in the sentinel frame's one framevar.
|
|
Sentinel frames can be distinguished from other ordinary frames
|
|
by the fact that their redoip slot contains
|
|
the address of the label in the runtime
|
|
that pops the segment off the nondet stack.
|
|
|
|
<h2> RUNTIME ACTIONS </h2>
|
|
|
|
The redo() macro is implemented as
|
|
|
|
<p>
|
|
|
|
<pre>
|
|
curfr = redofr_slot(maxfr);
|
|
goto *redoip_slot(maxfr);
|
|
</pre>
|
|
|
|
<p>
|
|
|
|
while the fail() macro discards the top nondet stack frame and then executes
|
|
a redo().
|
|
|
|
<p>
|
|
|
|
It is an invariant that
|
|
curfr will always point to an ordinary nondet stack frame;
|
|
it will never point to a temporary frame.
|
|
|
|
<p>
|
|
|
|
It is an invariant that for any nondet stack frame whose address is frame_addr,
|
|
redofr_slot(frame_addr) == frame_addr whenever curfr == frame_addr. However,
|
|
it is possible that redofr_slot(frame_addr) != frame_addr at other times.
|
|
|
|
<p>
|
|
|
|
One implication of this possibility is that
|
|
at a point in the code where execution may resume after a redo(),
|
|
the code of a procedure can assume that curfr == maxfr at that point
|
|
only if it did not hijack any frames anywhere to the left of that point.
|
|
|
|
<h2> CODE GENERATOR DATA STRUCTURES </h2>
|
|
|
|
The proposed code generator uses two data structures for managing failure:
|
|
the resume point stack and the left context.
|
|
It also consults the option --allow-hijacks
|
|
that says whether hijacks (see below) of nondet stack frames are allowed.
|
|
Grades that call for accurate garbage collection imply --no-allow-hijacks.
|
|
|
|
<h3> The resume point stack </h3>
|
|
|
|
Several kinds of goals want to get control on failure.
|
|
|
|
<p>
|
|
|
|
<ul>
|
|
<li> disjunctions want control when
|
|
a nonlast disjunct or something executed to its right fails
|
|
<li> if-then-elses want control when the condition fails
|
|
<li> negations are a special case of if-then-elses
|
|
<li> commits want control when the committed goal fails
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
These kinds of goals can be nested inside each other.
|
|
|
|
<p>
|
|
|
|
Whenever we are about to generate code for a goal after whose failure we
|
|
want to get back control, we push an entry onto the resume point stack.
|
|
This entry gives the arrangements for the resumption point;
|
|
the details of the arrangement are described in the "Resumption points"
|
|
section of allocation.html.
|
|
|
|
<p>
|
|
|
|
The depth of the resume point stack reflects the depth of nesting of
|
|
goals that want to get control on failure.
|
|
|
|
<p>
|
|
|
|
It is an invariant that the resume point stack is the same before and
|
|
after generating code for a goal.
|
|
|
|
<h3> The left context </h3>
|
|
|
|
When the code generator looks at a goal,
|
|
the left context field of code_info gives information about
|
|
what happened to the left of this goal
|
|
that may affect how we handle failures in this goal.
|
|
|
|
<p>
|
|
|
|
The left context has three subfields.
|
|
|
|
<p>
|
|
|
|
<ul>
|
|
<li>
|
|
The resume_point_known subfield can take on
|
|
one of the two values known and unknown,
|
|
saying whether the code generator knows what label to branch to on failure.
|
|
The answer is yes if there is no nondet code between
|
|
the establishment of the tightest enclosing resume point
|
|
and the start of the current goal.
|
|
|
|
If the resume point is known,
|
|
the compiler state also records whether
|
|
the redoip slot of the top nondet stack frame
|
|
is known to contain the address of this resume point.
|
|
XXX The rest of this document has not been updated
|
|
to reflect how we handle this extra component of the failure handling state.
|
|
|
|
<p>
|
|
|
|
<li>
|
|
The curfr_maxfr subfield can take on
|
|
one of the two values must_be_equal and may_be_different,
|
|
saying whether the code generator knows that
|
|
curfr is guaranteed to be equal to maxfr.
|
|
The answer is yes if all of the following conditions hold:
|
|
|
|
<ul>
|
|
<li>
|
|
we are in a procedure that lives on the nondet stack
|
|
(maxfr and curfr are both set to the address of the procedure's
|
|
stack frame when it is created);
|
|
<li>
|
|
there are no calls to nondet procedures on the path between
|
|
the start of the procedure and the start of the current goal; and
|
|
<li>
|
|
the code generator itself hasn't pushed any temporary nondet
|
|
stack frames (see below).
|
|
</ul>
|
|
|
|
<li>
|
|
The condition_environment subfield can take on
|
|
one of the two values inside_non_condition and not_inside_non_condition,
|
|
saying whether we are inside a model_non goal
|
|
that is the condition of an if-then-else.
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
The resume_point_known subfield is initialized to known in all procedures.
|
|
The curfr_maxfr subfield is initialized to
|
|
must_be_equal in model_non procedures,
|
|
and to may_be_different in other procedures.
|
|
The condition_environment subfield
|
|
is always initialized to not_inside_non_condition.
|
|
|
|
<p>
|
|
|
|
When the code generator processes any nondet goal,
|
|
it must set the resume_point_known subfield to unknown at the end of the goal.
|
|
|
|
<p>
|
|
|
|
When the code generator processes a model_non construct
|
|
that leaves this procedure (i.e. a call, higher order call or method call),
|
|
it must also set the curfr_maxfr subfield to may_be_different
|
|
at the end of the goal.
|
|
(Since a model_non procedure can remove its own stack frame
|
|
just before it succeeds for the last time,
|
|
we can't say that after a call to such a procedure
|
|
maxfr *will* be different from curfr.
|
|
|
|
<p>
|
|
|
|
When the code generator processes branched structures (if-then-elses,
|
|
switches or disjunctions), if resume_point_known is unknown at the end of
|
|
any arm, it must be unknown after the branched structure, and if curfr_maxfr
|
|
is may_be_different at the end of any arm, it must also be may_be_different
|
|
after the branched structure.
|
|
|
|
<p>
|
|
|
|
When the code generator establishes a new resumption point,
|
|
it will set the resume_point_known subfield to known,
|
|
but it must not set the curfr_maxfr subfield to must_be_equal.
|
|
|
|
<p>
|
|
|
|
Note that in the absence of model_non code, the resume_point_known subfield
|
|
will always have the value known.
|
|
|
|
<h3> Generating failure </h3>
|
|
|
|
How we generate code for failure depends on the top entry on the resume
|
|
point stack and the left context.
|
|
|
|
<p>
|
|
|
|
If resume_point_known is no, the code we generate for failure is redo().
|
|
If resume_point_known is yes, the code we generate for failure is a branch
|
|
to one of the labels in the resumption point; which one depends on the
|
|
locations of the variables needed at the resumption point
|
|
(see allocation.html).
|
|
|
|
<p>
|
|
|
|
To prepare for backtracking to a resumption point that takes place via
|
|
a redo(), not via a direct branch, we must select one of the labels
|
|
as the one whose address will be put in the redoip slot via which backtracking
|
|
will take place. This label will be the stack label, the label whose
|
|
resume map maps all the resume variables to their stack slots.
|
|
|
|
<p>
|
|
|
|
We have to make sure that this choice is always valid.
|
|
We do this by enforcing three invariants:
|
|
|
|
<ul>
|
|
<li>
|
|
We never generate redo() for failure if resume_point_known is known,
|
|
and we always generate redo() for failure if resume_point_known is unknown.
|
|
|
|
<p>
|
|
|
|
<li>
|
|
At all points when resume_point_known goes from known to unknown,
|
|
we make sure that the resume variables are flushed to their stack slots.
|
|
|
|
<p>
|
|
|
|
<li>
|
|
If, while a resumption point is active,
|
|
control can reach a point where resume_point_known goes from known to unknown,
|
|
liveness.m ensures that the resumption point includes a stack label.
|
|
</ul>
|
|
|
|
<h3> Classifying left contexts </h3>
|
|
|
|
The code generator classifies left contexts
|
|
into those in which hijacking is allowed,
|
|
and those in which hijacking is not allowed.
|
|
Hijacking is allowed only if the --allow-hijacks option is set,
|
|
and the condition_environment subfield is not_inside_non_condition.
|
|
|
|
<p>
|
|
|
|
The code generator further classifies left contexts
|
|
in which hijacking is allowed into the following three categories:
|
|
|
|
<p>
|
|
|
|
<ul>
|
|
<li> best case:
|
|
resume_point_known is known and curfr_maxfr is must_be_equal
|
|
<li> middle case:
|
|
resume_point_known is unknown and curfr_maxfr is must_be_equal
|
|
<li> worst case:
|
|
curfr_maxfr is may_be_different
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
The code generator can treat any situation as a no-hijack case,
|
|
it can treat best case as either middle case or worst case,
|
|
and it can treat middle case as worst case.
|
|
However, it should be able to generate better code
|
|
if it exploits the available information.
|
|
|
|
<p>
|
|
|
|
It may be that no-hijack case yields better code than worst case;
|
|
this should be investigated XXX.
|
|
|
|
<h2> HANDLING DISJUNCTIONS </h2>
|
|
|
|
<h3> Handling nondet disjunctions </h3>
|
|
|
|
<ul>
|
|
<li> No-hijack case.
|
|
Before entering the disjunction,
|
|
the code generator creates a temporary nondet stack frame.
|
|
When the code generator enters a nonlast disjunct,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the next disjunct,
|
|
and sets the redoip slot of the top frame (which is the temporary frame)
|
|
to the stack continuation in that resume point.
|
|
When the code generator enters the last disjunct,
|
|
it does not push any entry on the resume point stack,
|
|
and sets maxfr to the value
|
|
in the prevfr slot of the top frame (which is the temporary frame),
|
|
which pops the temporary frame off the stack.
|
|
|
|
<p>
|
|
|
|
<li> Best case.
|
|
When the code generator enters a nonlast disjunct,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the next disjunct,
|
|
and sets the redoip slot of the top frame (which is this frame)
|
|
to the stack continuation in that resume point.
|
|
When the code generator enters the last disjunct,
|
|
it does not push any entry on the resume point stack,
|
|
and sets the redoip slot of the top frame (which is this frame)
|
|
to the main continuation of the resume point
|
|
that was initially on top of the stack.
|
|
In this case, the disjunction hijacks its own frame
|
|
without having to save the value in the hijacked slot;
|
|
we call this a quarter hijack,
|
|
since it involves one restore.
|
|
|
|
<p>
|
|
|
|
<li> Middle case.
|
|
Before entering the disjunction,
|
|
the code generator saves in a stack slot
|
|
the contents of the redoip slot of the top frame
|
|
(which is this frame, and whose redofr slot must point to this frame).
|
|
When the code generator enters a nonlast disjunct,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the next disjunct,
|
|
and sets the redoip slot of the top frame (which is this frame)
|
|
to the stack continuation in that resume point.
|
|
When the code generator enters the last disjunct,
|
|
it does not push any entry on the resume point stack,
|
|
and sets the redoip slot of the top frame (which is this frame)
|
|
to its old, saved value.
|
|
In this case, the disjunction hijacks its own frame
|
|
while having to save the value in the hijacked slot;
|
|
we call this a half hijack,
|
|
since it involves one save and one restore.
|
|
|
|
<p>
|
|
|
|
<li> Worst case.
|
|
Before entering the disjunction,
|
|
the code generator saves in two stack slots
|
|
the contents of the redoip and redofr slots of the top frame,
|
|
(which may be different from this frame),
|
|
and sets the redofr slot of the top frame from curfr,
|
|
so that backtracking through the redoip slot of that frame
|
|
will set curfr to point to this frame.
|
|
When the code generator enters a nonlast disjunct,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the next disjunct,
|
|
and sets the redoip slot of the top frame
|
|
to the stack continuation in that resume point.
|
|
When the code generator enters the last disjunct,
|
|
it does not push any entry on the resume point stack,
|
|
and restores the redoip and redofr slots of the top frame
|
|
(which may be different from this frame),
|
|
to their old, saved values.
|
|
In this case, the disjunction hijacks a frame that may not be its own;
|
|
we call this a full hijack,
|
|
since it involves two saves and two restores.
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
For all the hijacking cases, the choices represented by the hijacked
|
|
redoip and maybe redofr slots cannot be backtracked to until the disjunction
|
|
that we are generating code for has failed. This cannot happen until the
|
|
last disjunct has failed, by which time the slots must have been restored.
|
|
This maintains the invariant that for any nondet stack frame whose address
|
|
is frame_addr, redofr_slot(frame_addr) == frame_addr whenever curfr ==
|
|
frame_addr.
|
|
|
|
<h3> Handling semi or det disjunctions </h3>
|
|
|
|
When the code generator enters a nonlast disjunct,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the next disjunct.
|
|
When the code generator enters the last disjunct,
|
|
it does not push any entry on the resume point stack.
|
|
|
|
<h2> HANDLING IF-THEN-ELSES </h2>
|
|
|
|
<h3> Handling nondet if-then-elses with nondet conditions </h3>
|
|
|
|
<ul>
|
|
<li> No-hijack case.
|
|
Before the code generator enters the condition,
|
|
the code generator creates a temporary nondet stack frame,
|
|
and saves the address of this temporary frame in a stack slot.
|
|
It also pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the else part,
|
|
and sets the redoip slot of the top frame (which must be the temporary frame)
|
|
to the stack continuation in that resume point.
|
|
After the code generator emerges from the condition,
|
|
it pops the new entry off the resume point stack.
|
|
Before entry to the then part or to the else part,
|
|
it assigns do_fail to the redoip slot of the temporary frame
|
|
(pointed to by the stack slot), effectively removing the temporary frame.
|
|
(We cannot generate code to pop the frame off the stack,
|
|
since it may not be on top.)
|
|
|
|
<p>
|
|
|
|
<li> Best case.
|
|
Before the code generator enters the condition,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the else part,
|
|
and sets the redoip slot of the top frame (which must be this frame)
|
|
to the stack continuation in that resume point.
|
|
After the code generator emerges from the condition,
|
|
it pops the new entry off the resume point stack.
|
|
Before entry to the then part or to the else part,
|
|
it resets the redoip slot of the frame it hijacked (pointed to by curfr)
|
|
to the stack label of the exposed top entry of the resume point stack.
|
|
If that entry has no stack label, then that resume point will never
|
|
be reached via redo() and thus we do not need to reset the redoip slot.
|
|
|
|
<p>
|
|
|
|
<li> Middle case.
|
|
Before entering the if-then-else,
|
|
the code generator saves in a stack slot
|
|
the contents of the redoip of the top frame (which must be this frame).
|
|
Before the code generator enters the condition,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the else part,
|
|
and sets the redoip slot of the top frame
|
|
to the stack continuation in that resume point.
|
|
After the code generator emerges from the condition,
|
|
it pops the new entry off the resume point stack.
|
|
Before entry to the then part or to the else part,
|
|
it resets the redoip slot of the frame it hijacked (pointed to by curfr)
|
|
to its saved value.
|
|
|
|
<p>
|
|
|
|
<li> Worst case.
|
|
Before entering the if-then-else,
|
|
the code generator saves in three stack slots
|
|
the address of the top frame (which is in maxfr),
|
|
and the contents of the redoip and redofr slots of the top frame
|
|
(which may be different from this frame).
|
|
It also sets the redofr slot of the top frame from curfr,
|
|
so that backtracking through the redoip slot of that frame
|
|
will set curfr to point to this frame.
|
|
Before the code generator enters the condition,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the else part,
|
|
and sets the redoip slot of the top frame
|
|
to the stack continuation in that resume point.
|
|
After the code generator emerges from the condition,
|
|
it pops the new entry off the resume point stack.
|
|
Before entry to the then part or to the else part,
|
|
it resets the redoip and redofr slots
|
|
of the frame it hijacked to their saved values.
|
|
Since maxfr may have been changed by the condition pushing new frames,
|
|
the address of the frame in which the restoration is to be performed
|
|
is given by the saved original value of maxfr
|
|
(although on entry to the else part maxfr will be equal to its value
|
|
on entry to the if-then-else, which may be different from curfr).
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
All these four manipulate the redoip slot of the nondet stack frame
|
|
that is on top of the nondet stack when execution enters the condition.
|
|
On entry to the condition, we set it to point the start of the else part;
|
|
on exit from the condition, we reset it to some other value to prevent
|
|
backtracking to the else part.
|
|
If the condition hijacks this slot, this scheme will not work.
|
|
(Although the code that hijacks the slot
|
|
will restore it to its original value before finally failing,
|
|
that is too late in this case.)
|
|
Therefore before entering the condition,
|
|
the code generator sets condition_environment to inside_non_condition;
|
|
on exit from the condition, it restores the original value.
|
|
(These are the only times the value of condition_environment is ever changed.)
|
|
|
|
<p>
|
|
|
|
Before entering the condition, the code generator will set resume_point_known
|
|
to known, since the resumption point to go to on failure of the condition is
|
|
known (it is the start of the else part). However, this resumption point
|
|
does not apply to failures in the then part. Therefore before entering the
|
|
then part, the code generator will reset resume_point_known to the value
|
|
it had before entering the if-then-else, except if resume_point_known
|
|
has become unknown during the generation of code for the condition, in which
|
|
case it will continue to be unknown.
|
|
|
|
<p>
|
|
|
|
A failure inside the condition should cause
|
|
a branch to the start of the else case
|
|
only if the condition has not already succeeded.
|
|
In general, the code inside the condition
|
|
may branch directly to the start of the else case
|
|
only up to the first goal that can succeed more than once;
|
|
any other goal following such a goal should branch through the redoip slot
|
|
on which the if-then-else performs the soft cut.
|
|
There are only two kinds of constructs that can succeed more than once:
|
|
calls (including higher order and method calls) and disjunctions.
|
|
When the code generator processes a call that can succeed more than once,
|
|
it sets resume_point_known to unknown,
|
|
which causes later failures to perform a redo.
|
|
To ensure that code in the last disjunct of a model_non disjunction
|
|
and code following a model_non disjunction also fail via a redo,
|
|
the code generator will,
|
|
on entering the last disjunct of a model_non disjunction,
|
|
check whether condition_environment is inside_non_condition,
|
|
and if yes, it will set resume_point_known to unknown.
|
|
(We do not do this in disjuncts other than the last
|
|
because non-last disjuncts fail to the start of the next disjunct).
|
|
Since at the end of branched control structures such as disjunctions
|
|
the code generator sets resume_point_known to unknown
|
|
if resume_point_known was unknown at the end of any branch,
|
|
this ensures that resume_point_known will stay unknown after the disjunction.
|
|
(The end of branched control structures
|
|
treat the curfr_vs_maxfr subfield similarly, i.e. set it to may_be_different
|
|
if it was may_be_different at the end of any branch.
|
|
The condition_environment subfield must, by construction,
|
|
have the same value at the end of every branch,
|
|
and this is the value it will have after the branched control structure.)
|
|
|
|
<h3> Handling nondet if-then-elses with semi or det conditions </h3>
|
|
|
|
We can handle these as we handle nondet if-then-elses with nondet conditions,
|
|
or we can handle them as we handle semi or det if-then-elses, with the
|
|
exception that we generate the then part and the else part as nondet goals.
|
|
The latter is more efficient, so that is what we do.
|
|
|
|
<h3> Handling semi or det if-then-elses </h3>
|
|
|
|
Before the code generator enters the condition,
|
|
it pushes a new entry on the resume point stack
|
|
indicating the resume point at the start of the else part,
|
|
to the stack continuation in that resume point.
|
|
After the code generator emerges from the condition,
|
|
it pops the new entry off the resume point stack.
|
|
|
|
<h2> HANDLING NEGATIONS </h2>
|
|
|
|
We handle not(G) as (G -> fail ; true).
|
|
|
|
<h2> HANDLING COMMITS </h2>
|
|
|
|
<h3> Handling commits across nondet goals </h3>
|
|
|
|
<ul>
|
|
<li> No-hijack case.
|
|
Before the code generator enters a goal that is being committed across,
|
|
it saves the value of maxfr in a stack slot,
|
|
and then it creates a temporary nondet stack frame
|
|
whose redoip slot points to the resume point
|
|
for getting control back if the goal has no solutions.
|
|
It also pushes a new entry on the resume point stack
|
|
indicating this resume point.
|
|
The code at this resume point will perform a failure in the manner
|
|
appropriate for whatever entry was originally on top of the resume point stack.
|
|
Both continuations will reset maxfr
|
|
to the value it had originally, which is in that stack slot.
|
|
|
|
<li> Best case.
|
|
Before the code generator enters a goal that is being committed across,
|
|
it pushes a new entry on the resume point stack indicating
|
|
the resume point for getting control back if the goal has no solutions
|
|
and sets the redoip slot of the top frame (which is this frame)
|
|
to the stack continuation in that resume point.
|
|
The code at this resume point will perform a failure in the manner
|
|
appropriate for whatever entry was originally on top of the resume point stack.
|
|
The code after the goal in the success continuation
|
|
will reset maxfr to the value it had originally, which is in curfr.
|
|
Both continuations will reset the redoip slot of the current frame
|
|
to its original value,
|
|
which is the address of the stack label
|
|
in the originally top failure continuation.
|
|
|
|
<p>
|
|
|
|
<li> Middle case.
|
|
Before the code generator enters a goal that is being committed across,
|
|
it pushes a new entry on the resume point stack indicating
|
|
the resume point for getting control back if the goal has no solutions,
|
|
saves the value of the redoip slot of the top frame (which is this frame)
|
|
and overrides this redoip slot to make it point
|
|
to the stack continuation in the resume point.
|
|
The success continuation will reset maxfr to curfr;
|
|
the failure continuation doesn't have to
|
|
(since the resume point can only be reached
|
|
if all other frames above the one hijacked have failed.)
|
|
Both continuations will reset the redoip slot of the top frame
|
|
to the saved value.
|
|
The failure continuation will then perform a failure in the manner
|
|
appropriate for the values of the resume point stack and left context
|
|
that were current before the commit.
|
|
The order of actions in the success continuation is important,
|
|
since resetting maxfr affects which frame is top.
|
|
|
|
<p>
|
|
|
|
<li> Worst case.
|
|
Before the code generator enters a goal that is being committed across,
|
|
it saves the value of maxfr,
|
|
pushes a new entry on the resume point stack indicating
|
|
the resume point for getting control back if the goal has no solutions,
|
|
saves the values of the redoip and redofr slots of the top frame
|
|
(which may be different from this frame),
|
|
overrides the redoip slot to make it point
|
|
to the stack continuation in the resume point
|
|
and the redofr slot to point to this frame.
|
|
The success continuation will reset maxfr;
|
|
the failure continuation doesn't have to
|
|
(since the resume point can only be reached
|
|
if all other frames above the one hijacked have failed.)
|
|
Both continuations will reset the redoip and redofr slots of the top frame
|
|
to their saved values.
|
|
The failure continuation will then perform a failure in the manner
|
|
appropriate for the values of the resume point stack and left context
|
|
that were current before the commit.
|
|
The order of actions in the success continuation is important,
|
|
since resetting maxfr affects which frame is top.
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
Since we are cutting away any frames created by the goal being committed
|
|
across, and since any resumption points established in that goal cannot
|
|
survive across the commit, at the end of processing the commit we can reset
|
|
both curfr_maxfr and resume_point_known to their values before the commit.
|
|
|
|
<p>
|
|
|
|
We do not need to push a junk frame
|
|
when entering the goal to be committed across
|
|
even if this goal may perform a hijacking.
|
|
If the goal fails, either it did not do any hijacking,
|
|
or it will have restored anything it hijacked before failing.
|
|
If the goal succeeds, it may have hijacked the top frame,
|
|
but we since we do not need to preserve any further solutions inside the goal,
|
|
we can simply restore that frame to its original contents anyway.
|
|
|
|
<h3> Handling commits across multi goals </h3>
|
|
|
|
<ul>
|
|
<li>
|
|
If curfr_maxfr is must_be_equal,
|
|
the code after the goal will reset maxfr to curfr.
|
|
<li>
|
|
If curfr_maxfr is may_be_different,
|
|
then before the code generator enters a goal that is being committed across,
|
|
it saves the value of maxfr in a stack slot.
|
|
The code after the goal will reset maxfr to the saved value.
|
|
</ul>
|
|
|
|
<p>
|
|
|
|
This is the way the code generator handles commits across nondet goals except
|
|
that we do not need to handle failure of the goal being committed across,
|
|
and thus do not need to set any redoips or hijack anything.
|
|
|
|
<p>
|
|
|
|
<hr>
|
|
<!-------------------------->
|
|
Last update was $Date: 2003-10-23 06:06:30 $ by $Author: fjh $@cs.mu.oz.au. <br>
|
|
</body>
|
|
</html>
|