mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-17 06:47:17 +00:00
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.
202 lines
6.3 KiB
C
202 lines
6.3 KiB
C
/*
|
|
** vim: ts=4 sw=4 expandtab
|
|
*/
|
|
/*
|
|
** Copyright (C) 1998-2008,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.
|
|
*/
|
|
|
|
/*
|
|
** This module implements the mdb commands in the "backward" category.
|
|
**
|
|
** The structure of these files is:
|
|
**
|
|
** - all the #includes
|
|
** - local macros and declarations of local static functions
|
|
** - one function for each command in the category
|
|
** - any auxiliary functions
|
|
** - any command argument strings
|
|
** - option processing functions.
|
|
*/
|
|
|
|
#include "mercury_std.h"
|
|
#include "mercury_getopt.h"
|
|
|
|
#include "mercury_trace.h"
|
|
#include "mercury_trace_internal.h"
|
|
#include "mercury_trace_cmds.h"
|
|
#include "mercury_trace_cmd_backward.h"
|
|
#include "mercury_trace_cmd_parameter.h"
|
|
#include "mercury_trace_command_queue.h"
|
|
#include "mercury_trace_util.h"
|
|
|
|
/****************************************************************************/
|
|
|
|
/*
|
|
** The message to print for retries through un-io-tabled areas, when
|
|
** the MR_RETRY_IO_INTERACTIVE option is given.
|
|
*/
|
|
|
|
#define MR_UNTABLED_IO_RETRY_MESSAGE \
|
|
"Retry across I/O operations is not always safe.\n" \
|
|
"Are you sure you want to do it? "
|
|
|
|
static MR_bool MR_trace_options_retry(MR_RetryAcrossIo *across_io,
|
|
MR_bool *assume_all_io_is_tabled,
|
|
char ***words, int *word_count);
|
|
|
|
/****************************************************************************/
|
|
|
|
MR_Next
|
|
MR_trace_cmd_retry(char **words, int word_count, MR_TraceCmdInfo *cmd,
|
|
MR_EventInfo *event_info, MR_Code **jumpaddr)
|
|
{
|
|
MR_Level n;
|
|
MR_Level ancestor_level;
|
|
MR_RetryAcrossIo across_io;
|
|
const char *problem;
|
|
MR_RetryResult result;
|
|
MR_bool assume_all_io_is_tabled;
|
|
MR_bool unsafe_retry;
|
|
|
|
ancestor_level = 0;
|
|
across_io = MR_RETRY_IO_INTERACTIVE;
|
|
assume_all_io_is_tabled = MR_FALSE;
|
|
if (! MR_trace_options_retry(&across_io, &assume_all_io_is_tabled,
|
|
&words, &word_count))
|
|
{
|
|
; /* the usage message has already been printed */
|
|
} else if (word_count == 2 &&
|
|
( MR_streq(words[1], "clique") || MR_streq(words[1], "clentry")))
|
|
{
|
|
if (MR_find_clique_entry_mdb(event_info, MR_CLIQUE_ENTRY_FRAME,
|
|
&ancestor_level))
|
|
{
|
|
/* the error message has already been printed */
|
|
return KEEP_INTERACTING;
|
|
}
|
|
} else if (word_count == 2 && MR_streq(words[1], "clparent")) {
|
|
if (MR_find_clique_entry_mdb(event_info, MR_CLIQUE_ENTRY_PARENT_FRAME,
|
|
&ancestor_level))
|
|
{
|
|
/* the error message has already been printed */
|
|
return KEEP_INTERACTING;
|
|
}
|
|
} else if (word_count == 2 && MR_trace_is_natural_number(words[1], &n)) {
|
|
ancestor_level = n;
|
|
} else if (word_count == 1) {
|
|
ancestor_level = 0;
|
|
} else {
|
|
MR_trace_usage_cur_cmd();
|
|
return KEEP_INTERACTING;
|
|
}
|
|
|
|
if (ancestor_level == 0 && MR_port_is_entry(event_info->MR_trace_port)) {
|
|
MR_trace_do_noop();
|
|
return KEEP_INTERACTING;
|
|
}
|
|
|
|
result = MR_trace_retry(event_info, ancestor_level,
|
|
across_io, assume_all_io_is_tabled, MR_UNTABLED_IO_RETRY_MESSAGE,
|
|
&unsafe_retry, &problem, MR_mdb_in, MR_mdb_out, jumpaddr);
|
|
switch (result) {
|
|
|
|
case MR_RETRY_OK_DIRECT:
|
|
cmd->MR_trace_cmd = MR_CMD_GOTO;
|
|
cmd->MR_trace_stop_event = MR_trace_event_number + 1;
|
|
cmd->MR_trace_strict = MR_FALSE;
|
|
cmd->MR_trace_print_level = MR_default_print_level;
|
|
return STOP_INTERACTING;
|
|
|
|
case MR_RETRY_OK_FINISH_FIRST:
|
|
cmd->MR_trace_cmd = MR_CMD_FINISH;
|
|
cmd->MR_trace_stop_depth = event_info->MR_call_depth - ancestor_level;
|
|
cmd->MR_trace_strict = MR_TRUE;
|
|
cmd->MR_trace_print_level = MR_PRINT_LEVEL_NONE;
|
|
|
|
/* Arrange to retry the call once it is finished. */
|
|
/* XXX we should use the same options as the original retry */
|
|
MR_insert_command_line_at_head("retry -o");
|
|
return STOP_INTERACTING;
|
|
|
|
case MR_RETRY_OK_FAIL_FIRST:
|
|
cmd->MR_trace_cmd = MR_CMD_FAIL;
|
|
cmd->MR_trace_stop_depth = event_info->MR_call_depth - ancestor_level;
|
|
cmd->MR_trace_strict = MR_TRUE;
|
|
cmd->MR_trace_print_level = MR_PRINT_LEVEL_NONE;
|
|
|
|
/* Arrange to retry the call once it is finished. */
|
|
/* XXX we should use the same options as the original retry */
|
|
MR_insert_command_line_at_head("retry -o");
|
|
return STOP_INTERACTING;
|
|
|
|
case MR_RETRY_ERROR:
|
|
fflush(MR_mdb_out);
|
|
fprintf(MR_mdb_err, "%s\n", problem);
|
|
return KEEP_INTERACTING;
|
|
}
|
|
|
|
MR_fatal_error("unrecognized retry result");
|
|
}
|
|
|
|
/****************************************************************************/
|
|
|
|
/*
|
|
** "retry --assume-all-io-is-tabled" is deliberately not documented as
|
|
** it is for developers only.
|
|
*/
|
|
|
|
const char *const MR_trace_retry_cmd_args[] =
|
|
{ "--force", "--interactive", "--only-if-safe", NULL };
|
|
|
|
/****************************************************************************/
|
|
|
|
static struct MR_option MR_trace_retry_opts[] =
|
|
{
|
|
{ "assume-all-io-is-tabled", MR_no_argument, NULL, 'a' },
|
|
{ "force", MR_no_argument, NULL, 'f' },
|
|
{ "interactive", MR_no_argument, NULL, 'i' },
|
|
{ "only-if-safe", MR_no_argument, NULL, 'o' },
|
|
{ NULL, MR_no_argument, NULL, 0 }
|
|
};
|
|
|
|
static MR_bool
|
|
MR_trace_options_retry(MR_RetryAcrossIo *across_io,
|
|
MR_bool *assume_all_io_is_tabled, char ***words, int *word_count)
|
|
{
|
|
int c;
|
|
|
|
MR_optind = 0;
|
|
while ((c = MR_getopt_long(*word_count, *words, "afio",
|
|
MR_trace_retry_opts, NULL)) != EOF)
|
|
{
|
|
switch (c) {
|
|
|
|
case 'a':
|
|
*assume_all_io_is_tabled = MR_TRUE;
|
|
break;
|
|
|
|
case 'f':
|
|
*across_io = MR_RETRY_IO_FORCE;
|
|
break;
|
|
|
|
case 'i':
|
|
*across_io = MR_RETRY_IO_INTERACTIVE;
|
|
break;
|
|
|
|
case 'o':
|
|
*across_io = MR_RETRY_IO_ONLY_IF_SAFE;
|
|
break;
|
|
|
|
default:
|
|
MR_trace_usage_cur_cmd();
|
|
return MR_FALSE;
|
|
}
|
|
}
|
|
|
|
*words = *words + MR_optind - 1;
|
|
*word_count = *word_count - MR_optind + 1;
|
|
return MR_TRUE;
|
|
}
|