mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-16 09:53:36 +00:00
Estimated hours taken: 60
The debugger's retry command at the moment works only from a final port for
the call to be retried, the reason being that the RTTI does not have the
information required to make sure that the state of the stacks is reset
correctly. If you invoke retry from a non-final port, the current
implementation skips forward to a final port and then does the retry;
this does not work if you get a core dump or a infinite loop during the forward
skip. This change adds the required info to the RTTI and thus enables
direct retries from the middle of calls.
The information added has two components. First, if a procedure that lives on
the nondet stack allocates any temporary nondet stack frames, then it must
record the old value of maxfr in a stack slot so that retry can restore it.
Second, if a procedure is tabled, then it must record the call table tip node
corresponding to the actual input arguments, so we can reset this node to
uninitialized (if we don't, then the retried call will find the active call
marker and report an infinite loop error).
The support for retries across minimal model calls is not finished yet.
Finding out what the right thing to do in such cases is a research project,
one that cannot even be started until minimal model tabling works reliably
in the *absence* of retries. However, such retries do grossly wrong things
at the moment; this change is a definite improvement. It attempts to perform
the retry from the fail port, since that is the only time when the minimal
model tabling data structures are quiescent. The "fail" command I added to
the debugger command set to let this be done is not complete yet and is
therefore undocumented; the problem is that a call to a model_non predicate
in a committed choice context will not get to the fail port. I added goal paths
to return layouts so that we will eventually be able to tell when execution
leaves a committed choice context surrounding an ancestor of a model_non
predicate call, but this functionality is not yet implemented.
compiler/stack_layout.m:
Generate the three new fields: the evaluation method, (maybe) the id
of the stack slot containing the saved value of maxfr, and (maybe)
the id of the stack slot containing the call table tip.
compiler/continuation_info.m:
Record the information about the new fields for later use by
stack_layout.m.
Add a new field to record the goal path of calls for their return
layouts.
Fix a screwed comment for the continuation_info data structure.
compiler/llds.m:
Add a new field to call() instructions to hold the goal path of the
call.
Add a utility function for use by trace.m.
compiler/call_gen.m:
Fill in this new field.
compiler/trace.m:
compiler/live_vars.m:
Reserve the fixed stack slot for the saved maxfr if necessary,
and if the call table tip node is needed, make sure that the variable
holding its address is allocated a low-numbered stack slot (otherwise,
its number may not fit into the MR_int_least8_t field in the
proc_layout).
compiler/trace.m:
If necessary, fill in the saved maxfr slot.
If necessary, initialize the call table tip slot.
compiler/hlds_goal.m:
Add a goal feature which marks its goal as defining the variable
representing the call table tip node.
Add a field to the goal path step representing quantification;
the field says whether the quantification changes the determinism of
the goal (i.e. whether it cuts away solutions).
compiler/hlds_pred.m:
compiler/hlds_out.m:
Add two fields to proc_infos which (a) record which variable, if any,
holds the call table tip node, and (b) record whether the procedure's
layout structure needs to reserve a slot for the saved value of maxfr.
compiler/table_gen.m:
Put this feature on the appropriate goal.
Also, rename a predicate to make it reflect its purpose better.
compiler/code_gen.m:
Generate code to put the call table tip variable in its stack slot
immediately after it has been generated.
Add a sanity check to ensure that if a procedure that lives on the det
stack can create a temporary nondet frame, and debugging is enabled,
then it did have a stack slot reserved for the saved maxfr.
compiler/code_util.m:
Add a predicate to make a conservative prediction of whether a
procedure may allocate a temporary nondet stack frame. We cannot
just generate the code and see, because the code generator needs to
know which variables live in which stack slots, and we cannot decide
that until we know whether we need a stack slot for the saved value of
maxfr.
Make an unrelated predicate semidet procedure use a det helper, in
order to make it more robust in the face of changes to the HLDS
(e.g. it was missing code for handling bi_implications).
compiler/code_info.m:
Record whether a procedure has in fact created a temporary nondet stack
frame.
compiler/handle_options.m:
Disable hijacks if debugging is enabled. The code we now use to
restore the stacks for direct retries works only if the retry does not
"backtrack" over a hijacked nondet stack frame whose hijack has not
been undone. Note that code compiled without debugging may still hijack
nondet stack frames. Execution may reemerge from the nondebugged region
in one of two ways. If the nondebugged code returns, then it will have
undone hijack, and the retry code will work. If the nondebugged code
calls debugged code, there will be a region on the stacks containing
no debugging information, and the retry command will refuse to perform
retries that go into or beyond this region. Both cases preserve
correctness.
compiler/*.m:
Trivial changes to conform to changes in data structures.
runtime/mercury_stack_layout.h:
Add three new fields to proc layouts: the numbers of the stack slots
(if any) storing the saved maxfr and the call table tip, and a
representation of the procedure's evaluation method.
runtime/mercury_stack_trace.[ch]:
Now that return layouts contain goal paths, print them in stack dumps
only if the include_trace_data flag is set (in mdb, this requires the
-d flag of the "stack" command).
Pass this flag around directly, instead of encoding its value in
the NULL vs non-NULL values of sp and curfr.
runtime/mercury_regorder.h:
Provide a mechanism to access the values of the first few rN registers
from a save area, for use in debugging low-level C code in the runtime
and the trace directories.
trace/mercury_trace.[ch]:
Reimplement MR_trace_retry to allow retries from the middle.
If the stack segment being retried over contains minimal model
procedures, we must still arrange to skip to the end of the retried
call. If this call is a minimal model generator, skipping to just any
final port is not sufficient to guarantee correctness on retry; to
ensure that subgoal is complete, we must skip to a fail port.
trace/mercury_trace.[ch]:
trace/mercury_trace_internal.c:
Implement a debugger command, "fail", which skips to the fail port or
the exception port of the specified ancestor. Since procedures that are
not model_non are not guaranteed to get to such a port, this
command reports an error if the specified call is not model_non.
Actually, even calls to model_non procedures may not get to the fail
port, as explained above; this is why the command is not yet
documented.
trace/mercury_trace.c:
trace/mercury_trace_util.[ch]:
Move some functions to print parts of the Mercury abstract machine
state from mercury_trace to mercury_trace_util, so that they are
available for use in debugging e.g. mercury_trace_declarative.
trace/mercury_trace_internal.c:
trace/mercury_trace_external.c:
trace/mercury_trace_declarative.c:
Use the new implementation of retries. At the moment, only the
internal debugger implements the full functionality. The declarative
debugger issues retry commands only from situations where the missing
functionality is not (yet) needed. The external debugger should
continue to work correctly, but Erwan may wish to update it to
exploit the availability of the fail command.
trace/mercury_trace*.[ch]:
Fix MR_prefixes, and a signed/unsigned mismatch.
doc/user_guide.texi:
Document the new "fail" command, but comment it out for now.
tests/debugger/retry.{m,inp,exp,exp2}:
A new test case for exercising retry.
tests/debugger/Mmakefile:
Enable the new test case.
tests/debugger/*.exp:
Update the expected output, given the extra info now output e.g. for
exception events and detailed stack traces.
239 lines
8.7 KiB
C
239 lines
8.7 KiB
C
/*
|
|
** Copyright (C) 1998-2000 The University of Melbourne.
|
|
** This file may only be copied under the terms of the GNU Library General
|
|
** Public License - see the file COPYING.LIB in the Mercury distribution.
|
|
*/
|
|
|
|
#ifndef MERCURY_STACK_TRACE_H
|
|
#define MERCURY_STACK_TRACE_H
|
|
|
|
#include <stdio.h>
|
|
#include "mercury_stack_layout.h"
|
|
|
|
/*
|
|
** mercury_stack_trace.h -
|
|
** Definitions for use by the stack tracing.
|
|
*/
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
/*
|
|
** MR_dump_stack:
|
|
** Given the succip, det stack pointer and current frame, generate a
|
|
** stack dump showing the name of each active procedure on the
|
|
** stack. If include_trace_data data is set, also print the
|
|
** call event number, call sequence number and depth for every
|
|
** traced procedure.
|
|
** NOTE: MR_dump_stack will assume that the succip is for the
|
|
** topmost stack frame. If you call MR_dump_stack from some
|
|
** pragma c_code, that may not be the case.
|
|
** Due to some optimizations (or lack thereof) the MR_dump_stack call
|
|
** may end up inside code that has a stack frame allocated, but
|
|
** that has a succip for the previous stack frame.
|
|
** Don't call MR_dump_stack from Mercury pragma c_code (calling
|
|
** from other C code in the runtime is probably ok, provided the
|
|
** succip corresponds to the topmost stack frame).
|
|
** (See library/require.m for a technique for calling MR_dump_stack
|
|
** from Mercury).
|
|
** If you need a more convenient way of calling from Mercury code,
|
|
** it would probably be best to make an impure predicate defined
|
|
** using `:- external'.
|
|
*/
|
|
|
|
extern void MR_dump_stack(MR_Code *success_pointer, MR_Word *det_stack_pointer,
|
|
MR_Word *current_frame, bool include_trace_data);
|
|
|
|
/*
|
|
** MR_dump_stack_from_layout:
|
|
** This function does the same job and makes the same assumptions
|
|
** as MR_dump_stack, but instead of the succip, it takes the label
|
|
** layout of the current point in the current procedure as input.
|
|
** It also takes a parameter that tells it where to put the stack dump
|
|
** and flags that say whether to include execution trace data and/or
|
|
** line numbers.
|
|
**
|
|
** If the entire stack was printed successfully, the return value is NULL;
|
|
** otherwise, it is a string indicating why the dump was cut short.
|
|
*/
|
|
|
|
typedef void (*MR_Print_Stack_Record)(FILE *fp,
|
|
const MR_Stack_Layout_Entry * proc_layout,
|
|
int count, int level,
|
|
MR_Word *base_sp, MR_Word * base_curfr,
|
|
const char *filename, int linenumber,
|
|
const char *goal_path, bool context_mismatch);
|
|
|
|
extern const char *MR_dump_stack_from_layout(FILE *fp,
|
|
const MR_Stack_Layout_Label *label_layout,
|
|
MR_Word *det_stack_pointer, MR_Word *current_frame,
|
|
bool include_trace_data,
|
|
bool include_contexts,
|
|
MR_Print_Stack_Record print_stack_record);
|
|
|
|
/*
|
|
** MR_dump_nondet_stack_from_layout:
|
|
** This function dumps the control control slots of the nondet stack.
|
|
** The output format is not meant to be intelligible to non-implementors.
|
|
** The value of maxfr should be in *base_maxfr.
|
|
*/
|
|
|
|
extern void MR_dump_nondet_stack_from_layout(FILE *fp, MR_Word *base_maxfr);
|
|
|
|
/*
|
|
** MR_find_nth_ancestor:
|
|
** Return the layout structure of the return label of the call
|
|
** ancestor_level levels above the current call. Label_layout
|
|
** tells us how to decipher the stack of the current call, while
|
|
** *stack_trace_sp and *stack_trace_curfr tell us where it is.
|
|
** On return, *stack_trace_sp and *stack_trace_curfr will be
|
|
** set up to match the specified ancestor.
|
|
**
|
|
** If the required stack walk is not possible (e.g. because some
|
|
** stack frames have no layout information, or because the stack
|
|
** does not have the required depth), the return value will be NULL,
|
|
** and problem will point to an error message.
|
|
*/
|
|
|
|
extern const MR_Stack_Layout_Label *MR_find_nth_ancestor(
|
|
const MR_Stack_Layout_Label *label_layout,
|
|
int ancestor_level, MR_Word **stack_trace_sp,
|
|
MR_Word **stack_trace_curfr, const char **problem);
|
|
|
|
/*
|
|
** MR_stack_walk_step:
|
|
** This function takes the entry_layout for the current stack
|
|
** frame (which is the topmost stack frame from the two stack
|
|
** pointers given), and moves down one stack frame, setting the
|
|
** stack pointers to their new levels.
|
|
**
|
|
** return_label_layout will be set to the stack_layout of the
|
|
** continuation label, or NULL if the bottom of the stack has
|
|
** been reached.
|
|
**
|
|
** The meaning of the return value for MR_stack_walk_step is
|
|
** described in its type definition above. If an error is
|
|
** encountered, problem_ptr will be set to a string representation
|
|
** of the error.
|
|
*/
|
|
|
|
typedef enum {
|
|
STEP_ERROR_BEFORE, /* the current entry_layout has no valid info */
|
|
STEP_ERROR_AFTER, /* the current entry_layout has valid info,
|
|
but the next one does not */
|
|
STEP_OK /* both have valid info */
|
|
} MR_Stack_Walk_Step_Result;
|
|
|
|
extern MR_Stack_Walk_Step_Result
|
|
MR_stack_walk_step(const MR_Stack_Layout_Entry *entry_layout,
|
|
const MR_Stack_Layout_Label **return_label_layout,
|
|
MR_Word **stack_trace_sp_ptr, MR_Word **stack_trace_curfr_ptr,
|
|
const char **problem_ptr);
|
|
|
|
/*
|
|
** MR_stack_trace_bottom should be set to the address of global_success,
|
|
** the label main/2 goes to on success. Stack dumps terminate when they
|
|
** reach a stack frame whose saved succip slot contains this address.
|
|
*/
|
|
|
|
extern MR_Code *MR_stack_trace_bottom;
|
|
|
|
/*
|
|
** MR_nondet_stack_trace_bottom should be set to the address of the buffer
|
|
** nondet stack frame created before calling main. Nondet stack dumps terminate
|
|
** when they reach a stack frame whose redoip contains this address. Note that
|
|
** the redoip and redofr slots of this frame may be hijacked.
|
|
*/
|
|
|
|
extern MR_Word *MR_nondet_stack_trace_bottom;
|
|
|
|
/*
|
|
** The different Mercury determinisms are internally represented by integers.
|
|
** This array gives the correspondance with the internal representation and
|
|
** the names that are usually used to denote determinisms.
|
|
*/
|
|
|
|
extern const char *MR_detism_names[];
|
|
|
|
/*
|
|
** MR_find_context attempts to look up the file name and line number
|
|
** corresponding to a label identified by its layout structure. If successful,
|
|
** it fills in *fileptr and *lineptr accordingly, and returns TRUE; otherwise,
|
|
** it returns FALSE.
|
|
*/
|
|
|
|
extern bool MR_find_context(const MR_Stack_Layout_Label *label,
|
|
const char **fileptr, int *lineptr);
|
|
|
|
/*
|
|
** MR_print_call_trace_info prints the call event number, call sequence number
|
|
** and call depth of the call stored in the stack frame of the procedure
|
|
** identified by the given proc layout. It requires the procedure to have
|
|
** trace layout information, and the relevant one of base_sp and base_curfr
|
|
** to be non-NULL, since these numbers are stored in stack slots.
|
|
**
|
|
** MR_maybe_print_call_trace_info calls MR_print_call_trace_info if
|
|
** include_trace_data is TRUE and the other conditions required by
|
|
** MR_print_call_trace_info are satisfied.
|
|
*/
|
|
|
|
extern void MR_print_call_trace_info(FILE *fp,
|
|
const MR_Stack_Layout_Entry *entry,
|
|
MR_Word *base_sp, MR_Word *base_curfr);
|
|
|
|
extern void MR_maybe_print_call_trace_info(FILE *fp,
|
|
bool include_trace_data,
|
|
const MR_Stack_Layout_Entry *entry,
|
|
MR_Word *base_sp, MR_Word *base_curfr);
|
|
|
|
/*
|
|
** MR_print_proc_id prints an identification of the given procedure,
|
|
** consisting of "pred" or "func", module name, pred or func name, arity,
|
|
** mode number and determinism. It does not output a newline, so that
|
|
** the caller can put something else after the procedure id on the same line.
|
|
*/
|
|
|
|
extern void MR_print_proc_id(FILE *fp, const MR_Stack_Layout_Entry *entry);
|
|
|
|
/*
|
|
** MR_print_proc_spec prints a string that uniquely specifies the given
|
|
** procedure to the debugger.
|
|
*/
|
|
|
|
extern void MR_print_proc_spec(FILE *fp,
|
|
const MR_Stack_Layout_Entry *entry);
|
|
|
|
/*
|
|
** MR_print_proc_id_trace_and_context prints an identification of the given
|
|
** procedure, together with call trace information (if available), a context
|
|
** within the procedure, and possibly a context identifying the caller.
|
|
** The position argument says where (if anywhere) the contexts should appear.
|
|
*/
|
|
|
|
typedef enum {
|
|
MR_CONTEXT_NOWHERE,
|
|
MR_CONTEXT_BEFORE,
|
|
MR_CONTEXT_AFTER,
|
|
MR_CONTEXT_PREVLINE,
|
|
MR_CONTEXT_NEXTLINE
|
|
} MR_Context_Position;
|
|
|
|
extern void MR_print_proc_id_trace_and_context(FILE *fp,
|
|
bool include_trace_data, MR_Context_Position pos,
|
|
const MR_Stack_Layout_Entry *entry,
|
|
MR_Word *base_sp, MR_Word *base_curfr, const char *path,
|
|
const char *filename, int lineno, bool print_parent,
|
|
const char *parent_filename, int parent_lineno,
|
|
int indent);
|
|
|
|
/*
|
|
** MR_dump_stack_record_print() prints one line of a stack dump.
|
|
*/
|
|
|
|
extern void MR_dump_stack_record_print(FILE *fp,
|
|
const MR_Stack_Layout_Entry *entry_layout, int count,
|
|
int start_level, MR_Word *base_sp, MR_Word *base_curfr,
|
|
const char *filename, int linenumber,
|
|
const char *goal_path, bool context_mismatch);
|
|
|
|
#endif /* MERCURY_STACK_TRACE_H */
|