Files
mercury/runtime/mercury_stack_trace.h
Zoltan Somogyi c733b0359b Give the Mercury debugger the ability to detect cliques of mutually recursive
Estimated hours taken: 30
Branches: main

Give the Mercury debugger the ability to detect cliques of mutually recursive
predicates on the stack. Exploit this ability to enhance the debugger's
level, retry, finish and stack commands.

runtime/mercury_stack_trace.[ch]:
	Add a function, MR_find_clique_entry, that detects the clique
	that contains the top stack frame. This is used to implement the new
	arguments "clentry" and "clparent" (short for clique entry and parent)
	options of the level, retry and finish commands. "clique" is a synonym
	for "clentry" in these commands.

	Add a function, MR_dump_stack_layout_clique, that implements the
	new capabilities of the stack command. It can detect more than one
	clique, anywhere on the stack.

	To make this possible, modify the existing functions for printing
	the lines of stack traces. These used to keep some information around
	between calls in global variables. Now that information is stored in
	two structures that the caller passes them. One contains the parameters
	that govern what is to be printed, the other contains information about
	what has been buffered up to be printed, but has not been flushed yet.
	(The old code was confused in its handling of parameters. Some parts
	of it looked up the global variables storing them, while other parts
	were given the parameter values by their callers, values that could
	have been -but weren't- inconsistent.)

	Change the buffer flushing code to be idempotent, since in the new
	code, sometimes it is hard to avoid flushing the buffer more than once,
	and we want only the first to print its contents.

	Make some type names conform to our standard style.

runtime/mercury_stack_layout.h:
	Add a new flag in MR_ProcLayouts: a flag that indicates that the
	procedure has one or more higher order arguments. The new code in
	mercury_stack_trace.c handles procedures with this flag specially:
	it does not consider two non-consecutive occurrences of such procedures
	on the stack to be necessarily part of the same clique. This is to
	avoid having two calls to e.g. list.map in different part of the
	program pulling all the procedures between those parts on the stack
	into a single clique. (The deep profiler has a very similar tweak.)

	Add a pointer to the corresponding part of the compiler.

compiler/hlds_pred.m:
	Add a predicate to test whether a predicate has any higher order args.

compiler/stack_layout.m:
	When computing the flag in proc layouts, call the new procedure in
	hlds_pred.m to help figure it out.

trace/mercury_trace_cmd_backward.c:
	Implement the new options of the "retry" command.

trace/mercury_trace_cmd_forward.c:
	Implement the new options of the "finish" command.

trace/mercury_trace_cmd_browsing.c:
	Implement the "new options of the "level" command.

	Implement the new functionality of the "stack" command.

trace/mercury_trace_util.[ch]:
	Add some code common to the implementations of the level, retry and
	finish commands.

trace/mercury_trace_external.c:
	Conform to the changes to the runtime.

doc/user_guide.texi:
	Document the debugger's new capabilities.

NEWS:
	Announce the debugger's new capabilities.

tests/debugger/mutrec.{m,inp,exp}:
	A new test case to test the handling of the stack command
	in the presence of cliques.

tests/debugger/mutrec_higher_order.{m,inp,exp}:
	A new test case to test the handling of the stack command
	in the presence of cliques and higher order predicates.

tests/debugger/Mmakefile:
	Enable both new test cases.
2012-06-05 18:19:33 +00:00

424 lines
17 KiB
C

/*
** vim: ts=4 sw=4 expandtab
*/
/*
** Copyright (C) 1998-2001,2003-2006,2008,2011-2012 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 "mercury_regs.h"
#include "mercury_stack_layout.h"
#include <stdio.h>
/*
** mercury_stack_trace.h:
**
** Definitions for use by the stack tracing.
*/
typedef MR_Unsigned MR_FrameLimit;
typedef MR_Unsigned MR_SpecLineLimit;
typedef MR_Unsigned MR_Level;
/*---------------------------------------------------------------------------*/
/*
** 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 foreign_proc,
** 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 a Mercury procedure defined by a foreign_proc
** (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 this from Mercury code,
** it would probably be best to do it using an impure predicate defined
** using `:- external'.
*/
extern void MR_dump_stack(MR_Code *success_pointer,
MR_Word *det_stack_pointer, MR_Word *current_frame,
MR_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 limit is nonzero, dumps at most limit frames.
**
** If the entire wanted part of the stack was printed successfully,
** the return value is NULL; otherwise, it is a string indicating
** why the dump was cut short.
*/
typedef struct {
/*
** The min_level and max_level fields give the range of call levels
** covered by this dump record. The frame_count field gives the number
** of stack frames covered by this dump record.
**
** Normally, each call has its own frame, which translates into
** max_level+1-min_level being equal to frame_count. However,
** frame_count can be less than this if the procedure has tail
** recursion events. However, frame_count does not have to be one
** for such procedures, since not all recursive calls are tail
** recursive calls.
**
** If include_trace_data is TRUE, frame_count should be 1.
*/
const MR_ProcLayout *MR_sdi_proc_layout;
MR_Level MR_sdi_min_level;
MR_Level MR_sdi_max_level;
MR_Unsigned MR_sdi_num_frames;
const char *MR_sdi_filename;
int MR_sdi_linenumber;
MR_bool MR_sdi_context_mismatch;
/* These fields are meaningful only if include_trace_data is TRUE. */
MR_Word *MR_sdi_base_sp;
MR_Word *MR_sdi_base_curfr;
const char *MR_sdi_goal_path;
} MR_StackFrameDumpInfo;
typedef void (*MR_PrintStackRecord)(FILE *fp,
MR_bool include_trace_data,
const MR_StackFrameDumpInfo *frame_dump_info);
extern const char *MR_dump_stack_from_layout(FILE *fp,
const MR_LabelLayout *label_layout,
MR_Word *det_stack_pointer,
MR_Word *current_frame,
MR_bool include_trace_data,
MR_bool include_contexts,
MR_FrameLimit frame_limit,
MR_SpecLineLimit line_limit,
MR_PrintStackRecord print_stack_record);
extern const char *MR_dump_stack_from_layout_clique(FILE *fp,
const MR_LabelLayout *label_layout,
MR_Word *det_stack_pointer,
MR_Word *current_frame,
MR_bool include_trace_data,
MR_bool include_contexts,
MR_bool detect_cliques,
MR_SpecLineLimit clique_line_limit,
MR_FrameLimit frame_limit,
MR_SpecLineLimit line_limit,
MR_PrintStackRecord print_stack_record);
/*
** MR_dump_nondet_stack
**
** This function dumps the control slots of the nondet stack.
** If limit_addr is nonnull, dumps only frames above limit_addr.
** If limit is nonzero, dumps at most limit frames.
** The output format is not meant to be intelligible to non-implementors.
*/
extern void MR_dump_nondet_stack(FILE *fp, MR_Word *limit_addr,
MR_FrameLimit frame_limit, MR_SpecLineLimit line_limit,
MR_Word *maxfr);
/*
** MR_dump_nondet_stack_from_layout
**
** This function dumps the nondet stack.
** If limit_addr is nonnull, dumps only frames above limit_addr.
** If limit is nonzero, dumps at most limit frames.
** The output format is not meant to be intelligible to non-implementors.
*/
extern void MR_dump_nondet_stack_from_layout(FILE *fp,
MR_Word *limit_addr, MR_FrameLimit frame_limit,
MR_SpecLineLimit line_limit, MR_Word *maxfr,
const MR_LabelLayout *label_layout,
MR_Word *base_sp, MR_Word *base_curfr);
/*
** MR_traverse_nondet_stack_from_layout
**
** This function traverses the nondet stack, calling the specified
** function for each frame.
*/
typedef void MR_TraverseNondetFrameFunc(void *user_data,
const MR_LabelLayout *layout, MR_Word *base_sp,
MR_Word *base_curfr);
extern void MR_traverse_nondet_stack_from_layout(
MR_Word *maxfr, const MR_LabelLayout *label_layout,
MR_Word *base_sp, MR_Word *base_curfr,
MR_TraverseNondetFrameFunc *traverse_frame_func,
void *traverse_frame_func_data);
/*
** MR_find_clique_entry
**
** Walk the stack from the current event to the stack frame of main.
** The initial part of this walk visits the stack frames of procedures
** that are mutually recursive with the current event's procedure;
** the rest of the walk visits the frames of other procedures.
** This function find the boundary between these two parts.
**
** If we cannot walk all the way to main (e.g. because some stack frames
** have no layout information, or because the stack does not have the required
** depth), we return a pointer to an error message, and neither
** *clique_entry_level nor *first_outside_ancestor_level will be meaningful.
**
** If we can walk all the way to main, then we will set *clique_entry_level
** to be the level on the stack (in the sense of a number you can give to
** MR_find_nth_ancestor) of the stack frame that is in the initial mutually
** recursive group, but whose caller is not, and it will set
** *first_outside_ancestor_level to the level of the caller, unless there
** is no such caller, in which case we set *first_outside_ancestor_level
** to a negative number.
**
** Either clique_entry_level or first_outside_ancestor_level may be NULL,
** if the caller does not need one or other of these numbers.
*/
extern const char *MR_find_clique_entry(
const MR_LabelLayout *label_layout,
MR_Word *det_stack_pointer, MR_Word *current_frame,
int *clique_entry_level,
int *first_outside_ancestor_level);
/*
** 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_LabelLayout *MR_find_nth_ancestor(
const MR_LabelLayout *label_layout,
MR_Level ancestor_level, MR_Word **stack_trace_sp,
MR_Word **stack_trace_curfr,
MR_Level *actual_level_ptr, 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, i.e. to the
** caller's frame, setting the stack pointers to their new levels.
** The number of times that the topmost stack has been reused
** is returned in *reused_frames_ptr.
**
** *return_label_layout_ptr will be set to the stack_layout of the
** continuation label, or NULL if the bottom of the stack has
** been reached.
**
** The meanings of the possible return values from MR_stack_walk_step
** are as follows:
**
** MR_STEP_OK: everything is fine.
** MR_STEP_ERROR_BEFORE: entry_layout has no valid stack trace info.
** MR_STEP_ERROR_AFTER: entry_layout has valid stack trace info,
** but its caller does not.
**
** If a MR_stack_walk_step encounters a problem, it will set problem_ptr
** to point to a string representation of the error.
**
** Note that for nondetermistic code, this function will only
** traverse the success continuations (via MR_succfr),
** not the frames which represent failure continuations
** (which would be accessible via MR_redofr).
*/
typedef enum {
MR_STEP_ERROR_BEFORE,
MR_STEP_ERROR_AFTER,
MR_STEP_OK
} MR_StackWalkStepResult;
extern MR_StackWalkStepResult
MR_stack_walk_step(const MR_ProcLayout *entry_layout,
const MR_LabelLayout **return_label_layout,
MR_Word **stack_trace_sp_ptr,
MR_Word **stack_trace_curfr_ptr,
MR_Unsigned *reused_frames_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 MR_TRUE;
** otherwise, it returns MR_FALSE.
*/
extern MR_bool MR_find_context(const MR_LabelLayout *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 MR_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_ProcLayout *proc_layout,
MR_Word *base_sp, MR_Word *base_curfr);
extern void MR_maybe_print_call_trace_info(FILE *fp,
MR_bool include_trace_data,
const MR_ProcLayout *proc_layout,
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_ProcLayout *entry);
/*
** MR_print_pred_id prints everything that MR_print_proc_id does, except
** the mode number and determinism.
*/
extern void MR_print_pred_id(FILE *fp, const MR_ProcLayout *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_ProcLayout *entry);
/*
** MR_print_proc_separate prints a string that uniquely specifies the given
** procedure to the debugger, with each component of a name in a separate field
** to allow the output to be processed by tools (e.g. awk scripts).
*/
extern void MR_print_proc_separate(FILE *fp,
const MR_ProcLayout *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 pos argument says where (if anywhere) the contexts should appear;
** the user_event_context argument says what parts of the context (if any)
** to print for user defined events.
*/
typedef enum {
MR_CONTEXT_NOWHERE,
MR_CONTEXT_BEFORE,
MR_CONTEXT_AFTER,
MR_CONTEXT_PREVLINE,
MR_CONTEXT_NEXTLINE
} MR_ContextPosition;
typedef enum {
MR_USER_EVENT_CONTEXT_NONE,
MR_USER_EVENT_CONTEXT_FILE,
MR_USER_EVENT_CONTEXT_PROC,
MR_USER_EVENT_CONTEXT_FULL
} MR_UserEventContext;
extern void MR_print_proc_id_trace_and_context(FILE *fp,
MR_bool include_trace_data, MR_ContextPosition pos,
MR_UserEventContext user_event_context,
const MR_ProcLayout *proc_layout,
const char *maybe_user_event_name,
MR_Word *base_sp, MR_Word *base_curfr,
const char *path, const char *filename, int lineno,
MR_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,
MR_bool include_trace_data,
const MR_StackFrameDumpInfo *frame_dump_info);
/*
** Find the first call event on the stack whose event number or sequence number
** is less than or equal to the given event number or sequence number. The
** level of the call in the stack is returned. This can then be passed to
** MR_trace_retry as the ancestor_level. If no such call is found then -1 is
** returned and problem is set to the reason why the call could not be
** found.
*/
typedef enum {
MR_FIND_FIRST_CALL_BEFORE_SEQ,
MR_FIND_FIRST_CALL_BEFORE_EVENT
} MR_FindFirstCallSeqOrEvent;
extern int MR_find_first_call_less_eq_seq_or_event(
MR_FindFirstCallSeqOrEvent seq_or_event,
MR_Unsigned seq_no_or_event_no,
const MR_LabelLayout *label_layout,
MR_Word *det_stack_pointer, MR_Word *current_frame,
const char **problem);
#endif /* MERCURY_STACK_TRACE_H */