This document describes the proposed new arrangement for handling failure in the 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.

RUNTIME DATA STRUCTURES

In the current design of the nondet stack, two registers point to nondet stack frames.

Each nondet stack frame contains four fixed fields:

The redo() macro is implemented as

curfr = maxfr;
goto *redoip_slot(maxfr);

while the fail() macro discards the top nondet stack frame and then executes a redo().

The proposed design extends each nondet stack frame with a fifth fixed field, redofr (it gives the frame corresponding to the label in the redoip slot). When a frame is created, the redofr slot is initialized to point to the frame itself. The redo macro is redefined as

curfr = redofr_slot(maxfr);
goto *redoip_slot(maxfr);

The purpose of the new slot is to allow the code generator to hijack the redoip slots of frames without jumping through hoops to arrange for curfr to be set correctly whenever execution backtracks through hijacked redoip slots. With the new design, the code generator can simply hijack the redofr field as well.

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.

One implication of the change 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.

CODE GENERATOR DATA STRUCTURES

The proposed code generator uses two data structures for managing failure: the resume point stack and the left context.

The resume point stack

Several kinds of goals want to get control on failure.

These kinds of goals can be nested inside each other.

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.

The depth of the resume point stack reflects the depth of nesting of goals that want to get control on failure.

It is an invariant that the resume point stack is the same before and after generating code for a goal.

The left context

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.

The left context has two subfields.

The resume_point_known subfield is initialized to yes in all procedures. curfr_is_maxfr is initialized to yes in procedures whose prologues create a nondet stack frame, and to no in other procedures.

When the code generator processes any nondet goal, it must set the resume_point_known subfield to no at the end of the goal.

When the code generator processes a nondet construct that leaves this procedure (i.e. a call or higher order call), it must set both subfields to no at the end of the goal.

When the code generator processes branched structures (if-then-elses, switches or disjunctions), if resume_point_known is no at the end of any arm, it must be no after the branched structure, and if curfr_is_maxfr is no at the end of any arm, it must also be no after the branched structure.

When the code generator establishes a new resumption point, it may set the resume_point_known subfield to yes, but it may not set the curfr_is_maxfr subfield to yes.

Note that in the absence of nondet code, both subfields will always be yes.

Generating failure

How we generate code for failure depends on the top entry on the resume point stack and the left context.

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).

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.

We have to make sure that this choice is always valid. We do this by enforcing three invariants:

Classifying left contexts

In many cases, the code generator classifies left contexts into three cases:

The code generator can treat any situation as worst case, but it can generate better code if it exploits the available information.

HANDLING DISJUNCTIONS

Handling nondet disjunctions

In the case of all these hijackings, the choices represented by the hijacked redoip and maybe redofr slots cannot be backtracked to until the disjunct 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.

Handling semi or det disjunctions

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.

HANDLING IF-THEN-ELSES

Handling nondet if-then-elses with nondet conditions

If the condition may perform a hijacking (i.e. if it contains a nondet disjunction, nondet if-then-else or a commit across a nondet -as opposed to multi- goal), the hijacking we do for the if-then-else may overwrite the same slots used by the hijacking inside the condition. We avoid this by pushing a junk frame whose redoip slot is do_fail before generating code for the condition; any hijacks inside the condition will hijack the slots of this frame. The presence of this frame on top of ours then requires us to set curfr_is_maxfr to no before entering the condition.

Handling nondet if-then-elses with semi or det conditions

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 will be more efficient.

Handling semi or det if-then-elses

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.

HANDLING NEGATIONS

We handle not(G) as (G -> fail ; true).

HANDLING COMMITS

Handling commits across nondet goals

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_is_maxfr and resume_point_known to their values before the commit.

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.

Handling commits across multi goals

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.

This is the way the code generator handles commits across nondet goals except that we do not need to handle failure, and thus do not need to set any redoips or hijack anything.


Last update was $Date: 1997-11-05 08:22:26 $ by $Author: zs $@cs.mu.oz.au.