/*
** vim: ts=4 sw=4 expandtab
*/
/*
INIT mercury_sys_init_trace
ENDINIT
*/
/*
** Copyright (C) 1997-2008, 2011 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.
*/

/*
** mercury_trace_base.c implements the interface between the main part
** of the runtime system (mainly mercury_wrapper.c) and the part of the
** tracing subsystem that has to be present even if no module in the program
** is compiled with execution tracing.
**
** Main author: Zoltan Somogyi.
*/

#include "mercury_imp.h"
#include "mercury_trace_base.h"
#include "mercury_engine.h"
#include "mercury_wrapper.h"
#include "mercury_misc.h"
#include "mercury_hash_table.h"
#include "mercury_layout_util.h"    /* for MR_generate_proc_name_from_layout */
#include "mercury_runtime_util.h"   /* for MR_strerror */
#include "mercury_signal.h"         /* for MR_setup_signal() */
#include "mercury_builtin_types.h"  /* for type_ctor_infos */
#include "mercury_array_macros.h"   /* for type_ctor_infos */
#include <signal.h>                 /* for SIGINT */
#include <stdio.h>
#include <errno.h>

#ifdef MR_HAVE_UNISTD_H
  #include <unistd.h>       /* for the write system call */
#endif

#ifdef MR_HAVE_SYS_WAIT_H
  #include <sys/wait.h>     /* for the wait system call */
#endif

#if defined(MR_HAVE__SNPRINTF) && ! defined(MR_HAVE_SNPRINTF)
  #define snprintf	_snprintf
#endif

#define MR_TRACE_COUNT_SUMMARY_MAX_DEFAULT  20

void                (*MR_trace_shutdown)(void) = NULL;

MR_bool             MR_trace_count_enabled = MR_FALSE;
MR_bool             MR_coverage_test_enabled = MR_FALSE;
const char          *MR_trace_count_summary_file = NULL;
const char          *MR_trace_count_summary_cmd = "mtc_union";
unsigned int        MR_trace_count_summary_max =
                        MR_TRACE_COUNT_SUMMARY_MAX_DEFAULT;
char                *MR_trace_counts_file = NULL;

MR_bool             MR_debug_ever_enabled = MR_FALSE;
MR_bool             MR_debug_enabled = MR_FALSE;
MR_bool             MR_trace_func_enabled = MR_FALSE;
MR_Code             *(*volatile MR_selected_trace_func_ptr)(
                        const MR_LabelLayout *);
MR_Unsigned         MR_trace_call_seqno = 0;
MR_Unsigned         MR_trace_call_depth = 0;
MR_Unsigned         MR_trace_event_number = 0;
MR_bool             MR_trace_from_full = MR_TRUE;
MR_bool             MR_standardize_event_details = MR_FALSE;
MR_Trace_Type       MR_trace_handler = MR_TRACE_INTERNAL;

MR_bool             MR_trace_unhide_events = MR_FALSE;
MR_bool             MR_trace_have_unhid_events = MR_FALSE;

MR_bool             MR_trace_tailrec_have_reused_frames = MR_FALSE;
MR_Unsigned         MR_trace_tailrec_num_reused_frames = 0;

/*
** I/O tabling is documented in library/table_builtin.m
*/

MR_IoTablingPhase   MR_io_tabling_phase = MR_IO_TABLING_UNINIT;
MR_bool             MR_io_tabling_enabled = MR_FALSE;
MR_TableNode        MR_io_tabling_pointer = { 0 };
MR_IoActionNum      MR_io_tabling_counter = 0;
MR_IoActionNum      MR_io_tabling_counter_hwm = 0;
MR_IoActionNum      MR_io_tabling_start = 0;
MR_IoActionNum      MR_io_tabling_end = 0;
MR_Unsigned         MR_io_tabling_start_event_num = 0;
MR_Unsigned         MR_io_tabling_stop_event_num = 0;
MR_bool             MR_io_tabling_debug = MR_FALSE;

#ifdef  MR_EXEC_TRACE
  MR_bool           MR_io_tabling_allowed = MR_TRUE;
#else
  MR_bool           MR_io_tabling_allowed = MR_FALSE;
#endif

#ifdef  MR_TRACE_HISTOGRAM

  int       *MR_trace_histogram_all = NULL;
  int       *MR_trace_histogram_exp = NULL;
  int       MR_trace_histogram_max  = 0;
  int       MR_trace_histogram_hwm  = 0;

  #define   MR_TRACE_HISTOGRAM_FILENAME ".mercury_histogram"

#endif

const char  *MR_actual_port_names[] = { MR_TRACE_PORT_ACTUAL_NAMES };
const char  *MR_simplified_port_names[] = { MR_TRACE_PORT_SIMPLIFIED_NAMES };

static  const void  *MR_get_orig_number(const void *record);
static  int         MR_hash_orig_number(const void *orig_number);
static  MR_bool     MR_equal_orig_number(const void *orig_number1,
                        const void *orig_number2);
static  MR_Unsigned MR_standardize_num(MR_Unsigned num,
                        MR_Hash_Table *table_ptr, MR_bool *init_ptr,
                        int *next_ptr);

/**************************************************************************/
/*
** This section of this file deals with the actions executed at trace events.
*/

MR_Code *
MR_trace(const MR_LabelLayout *layout)
{
    if (! MR_trace_func_enabled) {
        return NULL;
    }

    return (*MR_selected_trace_func_ptr)(layout);
}

MR_Code *
MR_user_trace(const MR_LabelLayout *layout)
{
    if (! MR_trace_func_enabled) {
        return NULL;
    }

    return (*MR_selected_trace_func_ptr)(layout);
}

void
MR_tracing_not_enabled(void)
{
    MR_fatal_error("This executable is not set up for debugging.\n"
        "Rebuild the <main>_init.c file, "
        "and give the `-t' (or `--trace')\n"
        "option to c2init when you do so.  "
        "If you are using mmake, you\n"
        "can do this by including "
        "`-t' (or `--trace') in C2INITFLAGS.\n"
        "For further details, please see the \"Debugging\" chapter "
        "of the\n"
        "Mercury User's Guide.\n");
}

MR_Code *
MR_trace_fake(const MR_LabelLayout *layout)
{
    MR_tracing_not_enabled();
    /*NOTREACHED*/
    return NULL;
}

MR_Code *
MR_trace_count(const MR_LabelLayout *label_layout)
{
    MR_Unsigned     *exec_count;

    exec_count = MR_trace_lookup_trace_count(label_layout);

#ifdef  MR_TRACE_COUNT_DEBUG
    {
        const MR_LabelLayout    *call_label_layout;
        MR_uint_least16_t       call_label_number;
        const MR_ModuleLayout   *module_layout;
        const MR_ProcLayout     *proc_layout;

        proc_layout = label_layout->MR_sll_entry;
        module_layout = proc_layout->MR_sle_module_layout;
        call_label_layout = proc_layout->MR_sle_call_label;
        if (label_layout != call_label_layout) {
            /*
            ** We should only get here if we have executed the call label,
            ** which means its count should be nonzero.
            */

            call_label_number = call_label_layout->MR_sll_label_num_in_module;
            if (call_label_number >=
                module_layout->MR_ml_num_label_exec_counts)
            {
                MR_fatal_error("MR_trace_count: invalid call label number");
            }

            if (module_layout->MR_ml_label_exec_count[call_label_number] == 0)
            {
                MR_fatal_error("MR_trace_count: call label count is zero");
            }
        }
    }
#endif

    *exec_count += 1;
    return NULL;
}

MR_Unsigned *
MR_trace_lookup_trace_count(const MR_LabelLayout *label_layout)
{
    const MR_ModuleLayout   *module_layout;
    const MR_ProcLayout     *proc_layout;
    MR_uint_least16_t       label_number;

    proc_layout = label_layout->MR_sll_entry;
    if (! MR_PROC_LAYOUT_HAS_EXEC_TRACE(proc_layout)) {
        MR_fatal_error("MR_trace_lookup_trace_count: no exec trace");
    }

    module_layout = proc_layout->MR_sle_module_layout;
    label_number = label_layout->MR_sll_label_num_in_module;
    if (label_number >= module_layout->MR_ml_num_label_exec_counts) {
        MR_fatal_error("MR_trace_lookup_trace_count: invalid label number");
    }

    return &(module_layout->MR_ml_label_exec_count[label_number]);
}

#define INIT_MODULE_TABLE_SIZE  10

const MR_ModuleLayout   **MR_module_infos;
unsigned                MR_module_info_next = 0;
unsigned                MR_module_info_max  = 0;

void
MR_insert_module_info_into_module_table(const MR_ModuleLayout *module)
{
    int     slot;

    MR_GC_ensure_room_for_next(MR_module_info, const MR_ModuleLayout *,
        INIT_MODULE_TABLE_SIZE, MR_ALLOC_SITE_NONE);
    MR_prepare_insert_into_sorted(MR_module_infos, MR_module_info_next, slot,
        strcmp(MR_module_infos[slot]->MR_ml_name, module->MR_ml_name));

    MR_module_infos[slot] = module;
}

static  void        MR_trace_write_quoted_atom(FILE *fp, const char *atom);
static  void        MR_trace_write_string(FILE *fp, const char *atom);
static  unsigned    MR_trace_write_label_exec_counts_for_file(FILE *fp,
                        const MR_ModuleLayout *module,
                        const MR_ModuleFileLayout *file,
                        const char *module_name,
                        MR_bool coverage_test);

MR_PathPort         MR_named_count_port[MR_PORT_NONE + 1];

#define MERCURY_TRACE_COUNTS_PREFIX     "mercury_trace_counts"
#define TEMP_SUFFIX                     ".tmp"

#if defined(F_OK)
    /* unistd.h version */
  #define MR_PERMISSIONS    F_OK
#else
    /* win32 version */
  #define MR_PERMISSIONS    0
#endif

#define MR_FILE_EXISTS(filename)        (access(filename, MR_PERMISSIONS) == 0)

void
MR_trace_record_label_exec_counts(void *dummy)
{
    FILE        *fp;
    char        *name;
    unsigned    name_len;
    MR_bool     summarize;
    MR_bool     keep;
    char        *slash;
    const char  *program_name;
    char        errbuf[MR_STRERROR_BUF_SIZE];

    program_name = MR_copy_string(MR_progname);
    slash = strrchr(program_name, '/');
    if (slash != NULL) {
        program_name = slash + 1;
    }

    summarize = MR_FALSE;
    keep = MR_FALSE;
    if (MR_trace_count_summary_file != NULL) {
        if (MR_FILE_EXISTS(MR_trace_count_summary_file)) {
            int     i;

            /* 30 bytes must be enough for the dot, the value of i, and '\0' */
            name_len = strlen(MR_trace_count_summary_file) + 30;
            name = MR_malloc(name_len);

            fp = NULL;
            /* search for a suffix that doesn't exist yet */
            for (i = 1; i <= MR_trace_count_summary_max; i++) {
                snprintf(name, name_len, "%s.%d",
                    MR_trace_count_summary_file, i);
                if (! MR_FILE_EXISTS(name)) {
                    /* file doesn't exist, commit to this one */
                    if (i == MR_trace_count_summary_max) {
                        summarize = MR_TRUE;
                    }

                    break;
                }
            }
        } else {
            /*
            ** The summary file doesn't yet exist, create it.
            */
            name = MR_copy_string(MR_trace_count_summary_file);
        }
    } else if (MR_trace_counts_file) {
        name = MR_copy_string(MR_trace_counts_file);
        keep = MR_TRUE;
    } else {
        char    *s;

        /*
        ** If no trace counts file name is provided, then we generate
        ** a file name.
        */

        /* 100 bytes must be enough for the process id, dots and '\0' */
        name_len = strlen(MERCURY_TRACE_COUNTS_PREFIX) + strlen(program_name)
            + 100;
        name = MR_malloc(name_len);
        snprintf(name, name_len, ".%s.%s.%d", MERCURY_TRACE_COUNTS_PREFIX,
            program_name, getpid());

        /* make sure name is an acceptable filename */
        for (s = name; *s != '\0'; s++) {
            if (*s == '/') {
                *s = '_';
            }
        }
    }

    fp = fopen(name, "w");
    if (fp != NULL) {
        unsigned    num_written;

        num_written = MR_trace_write_label_exec_counts(fp,
            program_name, MR_coverage_test_enabled);
        (void) fclose(fp);

        if (num_written == 0 && !keep) {
            /*
            ** We did not write out any trace counts, so there is nothing
            ** to gather.
            */

            (void) unlink(name);
            summarize = MR_FALSE;
        }
    } else {
        fprintf(stderr, "%s: %s\n", name,
            MR_strerror(errno, errbuf, sizeof(errbuf)));
        /*
        ** You can't summarize a file list if you can't create
        ** one of its files.
        */
        summarize = MR_FALSE;
    }

    free(name);

    if (summarize) {
        char        *cmd;
        unsigned    cmd_len;
        int         summary_status;
        int         mv_status;
        int         unlink_status;
        int         i;
        const char  *old_options;

        /* 30 bytes must be enough for the dot, the value of i, and space */
        name_len = strlen(MR_trace_count_summary_file) + 30;
        name = MR_malloc(name_len);

        cmd_len = strlen(MR_trace_count_summary_cmd) + 4;
        cmd_len += strlen(MR_trace_count_summary_file)
            + strlen(TEMP_SUFFIX) + 1;
        cmd_len += (MR_trace_count_summary_max + 1) * name_len;
        cmd_len += 100;

        cmd = MR_malloc(cmd_len);

        strcpy(cmd, MR_trace_count_summary_cmd);
        strcat(cmd, " -o ");
        strcat(cmd, MR_trace_count_summary_file);
        strcat(cmd, TEMP_SUFFIX);
        strcat(cmd, " ");
        strcat(cmd, MR_trace_count_summary_file);

        for (i = 1; i <= MR_trace_count_summary_max; i++) {
            snprintf(name, name_len, "%s.%d", MR_trace_count_summary_file, i);
            strcat(cmd, " ");
            strcat(cmd, name);
        }

        strcat(cmd, " > /dev/null 2>&1");

        old_options = getenv("MERCURY_OPTIONS");
        if (old_options != NULL) {
            (void) MR_setenv("MERCURY_OPTIONS", "", MR_TRUE);
            summary_status = system(cmd);
            (void) MR_setenv("MERCURY_OPTIONS", old_options, MR_TRUE);
        } else {
            summary_status = system(cmd);
        }

        if (summary_status == 0) {
            strcpy(cmd, "mv ");
            strcat(cmd, MR_trace_count_summary_file);
            strcat(cmd, TEMP_SUFFIX);
            strcat(cmd, " ");
            strcat(cmd, MR_trace_count_summary_file);
            mv_status = system(cmd);

            if (mv_status == 0) {
                /* delete all files whose data is now in the summary file */
                for (i = 1; i <= MR_trace_count_summary_max; i++) {
                    snprintf(name, name_len, "%s.%d",
                        MR_trace_count_summary_file, i);
                    unlink_status = unlink(name);
                    if (unlink_status != 0) {
                        MR_fatal_error(
                            "couldn't create summary of trace data");
                    }
                }
            } else {
                MR_fatal_error("couldn't create summary of trace data");
            }
        } else {
            MR_fatal_error("couldn't create summary of trace data");
        }

        free(name);
        free(cmd);
    }
}

unsigned
MR_trace_write_label_exec_counts(FILE *fp, const char *progname,
    MR_bool coverage_test)
{
    const MR_ModuleLayout       *module;
    const MR_ModuleFileLayout   *file;
    int                         num_modules;
    int                         num_files;
    int                         module_num;
    int                         file_num;
    unsigned                    num_written;

    MR_trace_name_count_port_ensure_init();

    fprintf(fp, "%s", MR_TRACE_COUNT_FILE_ID);
    if (coverage_test) {
        fputs("single_file(base_count_file_type(user_all, ", fp);
    } else {
        fputs("single_file(base_count_file_type(user_nonzero, ", fp);
    }

    MR_trace_write_string(fp, progname);
    fputs(")).\n", fp);

    num_modules = MR_module_info_next;
    num_written = 0;
    for (module_num = 0; module_num < num_modules; module_num++) {
        module = MR_module_infos[module_num];
        num_files = module->MR_ml_filename_count;

        fputs("module ", fp);
        MR_trace_write_quoted_atom(fp, module->MR_ml_name);
        fputc('\n', fp);

        for (file_num = 0; file_num < num_files; file_num++) {
            file = module->MR_ml_module_file_layout[file_num];
            num_written += MR_trace_write_label_exec_counts_for_file(fp,
                module, file, module->MR_ml_name, coverage_test);
        }
    }

    return num_written;
}

static unsigned
MR_trace_write_label_exec_counts_for_file(FILE *fp,
    const MR_ModuleLayout *module, const MR_ModuleFileLayout *file,
    const char *module_name, MR_bool coverage_test)
{
    const MR_LabelLayout        *label;
    const MR_ProcLayout         *prev_proc;
    const MR_ProcLayout         *proc;
    const MR_UserProcId         *id;
    MR_TracePort                port;
    int                         num_labels;
    int                         label_num;
    int                         label_index;
    unsigned                    num_written;
    MR_Unsigned                 exec_count;
    MR_PathPort                 path_port;

    fputs("file ", fp);
    MR_trace_write_quoted_atom(fp, file->MR_mfl_filename);
    fputc('\n', fp);

    prev_proc = NULL;
    num_labels = file->MR_mfl_label_count;
    num_written = 0;
    for (label_num = 0; label_num < num_labels; label_num++) {
        label = file->MR_mfl_label_layout[label_num];
        proc = label->MR_sll_entry;
        label_index = label->MR_sll_label_num_in_module;
        exec_count = module->MR_ml_label_exec_count[label_index];
        if (! MR_PROC_LAYOUT_IS_UCI(proc) && label_index > 0 &&
            (exec_count > 0 || coverage_test))
        {
            num_written++;

            id = &proc->MR_sle_user;
            if (proc != prev_proc) {
                if (MR_strdiff(module_name, id->MR_user_def_module)) {
                    MR_fatal_error(
                        "MR_trace_write_label_exec_counts_for_file: "
                        "module name mismatch");
                }

                if (id->MR_user_pred_or_func == MR_PREDICATE) {
                    fputs("pproc", fp);
                } else {
                    fputs("fproc", fp);
                }

                if (MR_strdiff(module_name, id->MR_user_decl_module)) {
                    /* turn pproc/fproc into pprocdecl/fprocdecl */
                    fputs("decl ", fp);
                    MR_trace_write_quoted_atom(fp, id->MR_user_decl_module);
                }

                fputc(' ', fp);
                MR_trace_write_quoted_atom(fp, id->MR_user_name);
                fprintf(fp, " %d %d\n", id->MR_user_arity, id->MR_user_mode);
            }

            port = label->MR_sll_port;
            path_port = MR_named_count_port[port];

            switch (path_port) {

                case PORT_ONLY:
                    fputs(MR_actual_port_names[port], fp);
                    break;

                case PATH_ONLY:
                    putc('<', fp);
                    fputs(MR_label_goal_path(label), fp);
                    putc('>', fp);
                    break;

                case PORT_AND_PATH:
                    fputs(MR_actual_port_names[port], fp);
                    putc(' ', fp);
                    putc('<', fp);
                    fputs(MR_label_goal_path(label), fp);
                    putc('>', fp);
                    break;

                default:
                    MR_fatal_error(
                        "MR_trace_write_label_exec_counts_for_file: "
                        "bad path_port");
                    break;
            }

            putc(' ', fp);
            fprintf(fp, "%d", file->MR_mfl_label_lineno[label_num]);

            if (exec_count > 0) {
                putc(' ', fp);
                fprintf(fp, "%" MR_INTEGER_LENGTH_MODIFIER "u", exec_count);
            }

            putc('\n', fp);

            prev_proc = proc;
        }
    }

    return num_written;
}

void
MR_trace_name_count_port_ensure_init()
{
    static MR_bool  done = MR_FALSE;

    MR_do_init_modules_debugger();

    if (! done) {
        MR_TracePort    port;

        for (port = MR_PORT_CALL; port <= MR_PORT_NONE; port++) {
            MR_named_count_port[port] = PATH_ONLY;
        }

        MR_named_count_port[MR_PORT_CALL] = PORT_ONLY;
        MR_named_count_port[MR_PORT_EXIT] = PORT_ONLY;
        MR_named_count_port[MR_PORT_REDO] = PORT_ONLY;
        MR_named_count_port[MR_PORT_FAIL] = PORT_ONLY;

        MR_named_count_port[MR_PORT_NEG_ENTER] = PORT_AND_PATH;
        MR_named_count_port[MR_PORT_NEG_SUCCESS] = PORT_AND_PATH;
        MR_named_count_port[MR_PORT_NEG_FAILURE] = PORT_AND_PATH;

        done = MR_TRUE;
    }
}

/*
** The output of this is supposed to be equivalent to term_io__quote_atom
** except that it always uses quotes, even if not strictly necessary.
*/
static void
MR_trace_write_quoted_atom(FILE *fp, const char *atom)
{
    const char *c;

    fputc('\'', fp);
    for (c = atom; *c != '\0'; c++) {
        switch (*c) {
            case '\'':
                fputs("\\'", fp);
                break;
            case '"':
                fputs("\\\"", fp);
                break;
            case '\\':
                fputs("\\\\", fp);
                break;
            case '\n':
                fputs("\\n", fp);
                break;
            case '\t':
                fputs("\\t", fp);
                break;
            case '\b':
                fputs("\\b", fp);
                break;
            default:
                /* This assumes isalnum is the same as char__isalnum.
                ** The line noise is the equivalent of
                ** is_mercury_punctuation_char in library/term_io.m
                ** and compiler/mercury_to_mercury.m; any changes here
                ** may require similar changes there.
                */
                if (MR_isalnum(*c) ||
                    strchr(" !@#$%^&*()-_+=`~{}[];:'\"<>.,/?\\|", *c))
                {
                    fputc(*c, fp);
                } else {
                    fprintf(fp, "\\%03o\\", *c);
                }
                break;
        }
    }

    fputc('\'', fp);
}

/*
** The output of this is supposed to be equivalent to writing out a string.
*/

static void
MR_trace_write_string(FILE *fp, const char *atom)
{
    const char *c;

    fputc('\"', fp);
    for (c = atom; *c != '\0'; c++) {
        switch (*c) {
            case '"':
                fputs("\\\"", fp);
                break;
            case '\\':
                fputs("\\\\", fp);
                break;
            case '\n':
                fputs("\\n", fp);
                break;
            case '\t':
                fputs("\\t", fp);
                break;
            case '\b':
                fputs("\\b", fp);
                break;
            default:
                fputc(*c, fp);
                break;
        }
    }

    fputc('\"', fp);
}

/**************************************************************************/
/*
** This section of this file deals with the actions executed at the start
** and end of execution.
*/

#ifdef  MR_TABLE_DEBUG
MR_bool MR_saved_tabledebug;
#endif

void
MR_trace_init(void)
{
#ifdef  MR_TABLE_DEBUG
    /*
    ** We don't want to see any tabling debugging messages from
    ** initialization code about entering and leaving commit goals.
    */

    MR_saved_tabledebug = MR_tabledebug;
    MR_tabledebug = MR_FALSE;
#endif

#ifdef MR_USE_EXTERNAL_DEBUGGER
    if (MR_trace_handler == MR_TRACE_EXTERNAL) {
        if (MR_address_of_trace_init_external != NULL) {
            MR_address_of_trace_init_external();
        } else {
            MR_tracing_not_enabled();
        }
    }
#endif
}

void
MR_trace_final(void)
{
#ifdef MR_USE_EXTERNAL_DEBUGGER
    if (MR_trace_handler == MR_TRACE_EXTERNAL) {
        if (MR_address_of_trace_final_external != NULL) {
            MR_address_of_trace_final_external();
        } else {
            MR_tracing_not_enabled();
        }
    }
#endif

    /*
    ** If mdb started a window, make sure it dies now.
    */
    if (MR_trace_shutdown != NULL) {
        (*MR_trace_shutdown)();
    }
}

void
MR_trace_start(MR_bool enabled)
{
    MR_trace_event_number = 0;
    MR_trace_call_seqno = 0;
    MR_trace_call_depth = 0;
    MR_trace_from_full = MR_TRUE;
    MR_debug_enabled = enabled;
    MR_update_trace_func_enabled();

#ifdef  MR_TABLE_DEBUG
    /*
    ** Restore the value saved by MR_trace_init.
    */

    MR_tabledebug = MR_saved_tabledebug;
#endif

    /*
    ** Install the SIGINT signal handler.
    ** We only do this if tracing is enabled, and only
    ** for the internal debugger.  (This is a bit conservative:
    ** it might work fine for the external debugger too,
    ** but I'm just not certain of that.)
    */
    if (enabled &&
        MR_address_of_trace_interrupt_handler != NULL &&
        MR_trace_handler == MR_TRACE_INTERNAL)
    {
        MR_setup_signal(SIGINT,
            (MR_Code *) MR_address_of_trace_interrupt_handler,
            MR_FALSE, "mdb: cannot install SIGINT signal handler");
    }
}

void
MR_trace_end(void)
{
    MR_debug_enabled = MR_FALSE;
    MR_update_trace_func_enabled();
}

/**************************************************************************/
/*
** This section of this file deals with the standardization of event and
** call sequence numbers. We use standardized event and call numbers to
** reduce the number of .exp files we need to create for debugger test cases.
*/

#define MR_STANDARD_HASH_TABLE_SIZE 1024

typedef struct {
    MR_Unsigned   MR_std_orig_number;
    MR_Unsigned   MR_std_std_number;
} MR_Standard_Hash_Record;

static const void *
MR_get_orig_number(const void *record)
{
    return (const void *)
        ((MR_Standard_Hash_Record *) record)->MR_std_orig_number;
}

static int
MR_hash_orig_number(const void *orig_number)
{
    return (int) (((MR_Unsigned) orig_number) % MR_STANDARD_HASH_TABLE_SIZE);
}

static MR_bool
MR_equal_orig_number(const void *orig_number1, const void *orig_number2)
{
    return (MR_Unsigned) orig_number1 == (MR_Unsigned) orig_number2;
}

static MR_Hash_Table MR_standard_event_num_table = {
    MR_STANDARD_HASH_TABLE_SIZE, NULL,
    MR_get_orig_number, MR_hash_orig_number, MR_equal_orig_number
};

static MR_Hash_Table MR_standard_call_num_table = {
    MR_STANDARD_HASH_TABLE_SIZE, NULL,
    MR_get_orig_number, MR_hash_orig_number, MR_equal_orig_number
};

static MR_bool  MR_init_event_num_hash = MR_FALSE;
static MR_bool  MR_init_call_num_hash = MR_FALSE;

static int      MR_next_std_event_num = 1;
static int      MR_next_std_call_num = 1;

static MR_Unsigned
MR_standardize_num(MR_Unsigned num, MR_Hash_Table *table_ptr,
    MR_bool *init_ptr, int *next_ptr)
{
    const MR_Standard_Hash_Record   *record;
    MR_Standard_Hash_Record         *new_record;
    int                             std_num;

    if (! *init_ptr) {
        *init_ptr = MR_TRUE;
        MR_init_hash_table(*table_ptr);
    }

    record = MR_lookup_hash_table(*table_ptr, num);
    if (record != NULL) {
        return record->MR_std_std_number;
    }

    std_num = *next_ptr;
    (*next_ptr)++;

    new_record = MR_GC_NEW(MR_Standard_Hash_Record);
    new_record->MR_std_orig_number = num;
    new_record->MR_std_std_number = std_num;
    (void) MR_insert_hash_table(*table_ptr, new_record);
    return std_num;
}

MR_Unsigned
MR_standardize_event_num(MR_Unsigned event_num)
{
    return MR_standardize_num(event_num, &MR_standard_event_num_table,
        &MR_init_event_num_hash, &MR_next_std_event_num);
}

MR_Unsigned
MR_standardize_call_num(MR_Unsigned call_num)
{
    return MR_standardize_num(call_num, &MR_standard_call_num_table,
        &MR_init_call_num_hash, &MR_next_std_call_num);
}

/**************************************************************************/
/*
** This section of this file reports on trace events so far, for use
** in messages about abnormal program termination.
*/

char    *MR_trace_report_msg = NULL;

void
MR_trace_report(FILE *fp)
{
    if (MR_trace_event_number > 0) {
        /*
        ** This means that the executable was compiled with tracing,
        ** which implies that the user wants trace info on abort.
        */

        if (MR_trace_report_msg != NULL) {
            fprintf(fp, "%s\n", MR_trace_report_msg);
        }

        if (MR_standardize_event_details) {
            fprintf(fp, "Last trace event was event #E%ld.\n",
                (long) MR_standardize_event_num(MR_trace_event_number));
        } else {
            fprintf(fp, "Last trace event was event #%ld.\n",
                (long) MR_trace_event_number);
        }

#ifdef  MR_TRACE_HISTOGRAM
        {
            FILE    *hfp;
            char    errbuf[MR_STRERROR_BUF_SIZE];

            hfp = fopen(MR_TRACE_HISTOGRAM_FILENAME, "w");
            if (hfp != NULL) {
                MR_trace_print_histogram(hfp, "All-inclusive",
                    MR_trace_histogram_all, MR_trace_histogram_hwm);
                if (fclose(hfp) == 0) {
                    fprintf(fp, "Event histogram put into file `%s'.\n",
                        MR_TRACE_HISTOGRAM_FILENAME);
                } else {
                    fprintf(fp, "Cannot put event histogram into `%s': %s."
                        MR_TRACE_HISTOGRAM_FILENAME,
                        MR_strerror(errno, errbuf, sizeof(errbuf)));
                }
            } else {
                fprintf(fp, "Cannot open `%s': %s.\n"
                    MR_TRACE_HISTOGRAM_FILENAME,
                    MR_strerror(errno, errbuf, sizeof(errbuf)));
            }
        }
#endif  /* MR_TRACE_HISTOGRAM */
    }
}

void
MR_trace_report_raw(int fd)
{
    char    buf[80];    /* that ought to be more than long enough */
    int     ret;

    if (MR_trace_event_number > 0) {
        /*
        ** This means that the executable was compiled with tracing,
        ** which implies that the user wants trace info on abort.
        */

        if (MR_trace_report_msg != NULL) {
            do {
                /* XXX we don't handle successful but partial writes */
                ret = write(fd, MR_trace_report_msg,
                    strlen(MR_trace_report_msg));
            } while (ret == -1 && MR_is_eintr(errno));
        }

        if (MR_standardize_event_details) {
            sprintf(buf, "Last trace event was event #E%ld.\n",
                (long) MR_standardize_event_num(MR_trace_event_number));
        } else {
            sprintf(buf, "Last trace event was event #%ld.\n",
                (long) MR_trace_event_number);
        }
        do {
            /* XXX we don't handle successful but partial writes */
            ret = write(fd, buf, strlen(buf));
        } while (ret == -1 && MR_is_eintr(errno));
    }
}

/**************************************************************************/
/*
** This section of this file deals with I/O actions.
*/

MR_bool
MR_trace_get_action(MR_IoActionNum action_number, MR_ConstString *proc_name_ptr,
    MR_Word *is_func_ptr, MR_Word *arg_list_ptr)
{
    const MR_TableIoDecl    *table_io_decl;
    const MR_ProcLayout     *proc_layout;
    MR_ConstString          proc_name;
    MR_Word                 is_func;
    MR_Word                 arg_list;
    MR_Word                 arg;
    int                     filtered_arity;
    int                     arity;
    int                     hv;
    MR_TrieNode             answer_block_trie;
    MR_Word                 *answer_block;
    MR_TypeInfo             *type_params;
    MR_TypeInfo             type_info;

    if (! (MR_io_tabling_start <= action_number
        && action_number < MR_io_tabling_counter_hwm))
    {
        return MR_FALSE;
    }

    MR_TABLE_START_INT(NULL, MR_tabledebug, MR_FALSE,
        answer_block_trie, (MR_TrieNode) &MR_io_tabling_pointer,
        MR_io_tabling_start, action_number);
    answer_block = answer_block_trie->MR_answerblock;

    if (answer_block == NULL) {
        return MR_FALSE;
    }

    table_io_decl = (const MR_TableIoDecl *) answer_block[0];
    proc_layout = table_io_decl->MR_table_io_decl_proc;
    filtered_arity = table_io_decl->MR_table_io_decl_filtered_arity;

    MR_generate_proc_name_from_layout(proc_layout, &proc_name, &arity,
        &is_func);

    type_params = MR_materialize_answer_block_type_params(
        table_io_decl->MR_table_io_decl_type_params, answer_block,
        filtered_arity);

    MR_restore_transient_hp();
    arg_list = MR_list_empty();
    MR_save_transient_hp();
    for (hv = filtered_arity; hv >= 1; hv--) {
        type_info = MR_create_type_info(type_params,
            table_io_decl->MR_table_io_decl_ptis[hv - 1]);
        MR_restore_transient_hp();
        MR_new_univ_on_hp(arg, type_info, answer_block[hv]);
        arg_list = MR_univ_list_cons(arg, arg_list);
        MR_save_transient_hp();
    }

    MR_free(type_params);

    *proc_name_ptr = proc_name;
    *is_func_ptr = is_func;
    *arg_list_ptr = arg_list;
    return MR_TRUE;
}

/**************************************************************************/
/*
** This section of this file deals with switching debugging on and off.
**
** XXX The code here is very similar to MR_TRACE_CALL_MERCURY in the header
** file. Look into merging the two pieces of code.
*/

void
MR_turn_off_debug(MR_SavedDebugState *saved_state,
    MR_bool include_counter_vars)
{
    int i;

    saved_state->MR_sds_debug_enabled = MR_debug_enabled;
    saved_state->MR_sds_io_tabling_enabled = MR_io_tabling_enabled;
    MR_debug_enabled = MR_FALSE;
    MR_update_trace_func_enabled();
    MR_io_tabling_enabled = MR_FALSE;

    for (i = 0; i < MR_MAXFLAG ; i++) {
        saved_state->MR_sds_debugflags[i] = MR_debugflag[i];
        MR_debugflag[i] = MR_FALSE;
    }

    saved_state->MR_sds_include_counter_vars = include_counter_vars;
    saved_state->MR_sds_trace_call_seqno = MR_trace_call_seqno;
    saved_state->MR_sds_trace_call_depth = MR_trace_call_depth;
    saved_state->MR_sds_trace_event_number = MR_trace_event_number;

#if defined(MR_DEEP_PROFILING) && defined(MR_EXEC_TRACE)
    MR_disable_deep_profiling_in_debugger = MR_TRUE;
#endif
}

void
MR_turn_debug_back_on(const MR_SavedDebugState *saved_state)
{
    int i;

    MR_debug_enabled = saved_state->MR_sds_debug_enabled;
    MR_update_trace_func_enabled();
    MR_io_tabling_enabled = saved_state->MR_sds_io_tabling_enabled;

    for (i = 0; i < MR_MAXFLAG; i++) {
        MR_debugflag[i] = saved_state->MR_sds_debugflags[i];
    }

    if (saved_state->MR_sds_include_counter_vars) {
        MR_trace_call_seqno = saved_state->MR_sds_trace_call_seqno;
        MR_trace_call_depth = saved_state->MR_sds_trace_call_depth;
        MR_trace_event_number = saved_state->MR_sds_trace_event_number;
    }

#if defined(MR_DEEP_PROFILING) && defined(MR_EXEC_TRACE)
    MR_disable_deep_profiling_in_debugger = MR_FALSE;
#endif
}

/**************************************************************************/
/*
** This section of this file deals with recording the value of an exception.
*/

static  MR_Word     MR_trace_exception_value = (MR_Word) NULL;

void
MR_trace_set_exception_value(MR_Word exception)
{
    MR_trace_exception_value = exception;
}

MR_Word
MR_trace_get_exception_value(void)
{
    return MR_trace_exception_value;
}

/**************************************************************************/
/*
** This section of this file deals with measuring the distribution of events
** across depths.
*/

#ifdef  MR_TRACE_HISTOGRAM

void
MR_trace_print_histogram(FILE *fp, const char *which, int *histogram, int max)
{
    int i;

    fprintf(fp, "%s histogram\n", which);
    for (i = 1; i <= max; i++) {
        fprintf(fp, "depth %4d: %10d", i, histogram[i]);
        if (i + 1 <= max && histogram[i] != 0) {
            fprintf(fp, ", branching factor %7.2f\n",
                (float) histogram[i+1] / (float) histogram[i]);
        } else {
            fprintf(fp, "\n");
        }
    }
}

#endif  /* MR_TRACE_HISTOGRAM */

/**************************************************************************/
/*
** This section of this file deals with statistics about which procedures
** are respondible for what fraction of I/O table entries.
*/

#define MR_IO_TABLE_STATS_HASH_TABLE_SIZE 1024

typedef struct {
    const MR_ProcLayout     *MR_io_tabling_stats_proc;
    MR_Unsigned             MR_io_tabling_stats_count;
} MR_IO_Table_Stats_Hash_Record;

static const void *
MR_get_proc_layout(const void *record)
{
    return (const void *)
        ((MR_IO_Table_Stats_Hash_Record *) record)->MR_io_tabling_stats_proc;
}

static int
MR_hash_proc_layout(const void *proc)
{
    return (((MR_Unsigned) proc) >> 5) % MR_STANDARD_HASH_TABLE_SIZE;
}

static MR_bool
MR_equal_proc_layout(const void *proc1, const void *proc2)
{
    return (const MR_ProcLayout *) proc1 == (const MR_ProcLayout *) proc2;
}

static MR_Hash_Table MR_io_tabling_stats_table = {
    MR_IO_TABLE_STATS_HASH_TABLE_SIZE, NULL,
    MR_get_proc_layout, MR_hash_proc_layout, MR_equal_proc_layout
};

static  int                             MR_io_tabling_stats_sort_arena_next;
static  MR_IO_Table_Stats_Hash_Record   *MR_io_tabling_stats_sort_arena;

static  void    MR_add_to_sort_arena(const void *addr);
static  int     MR_compare_in_sort_arena(const void *addr1, const void *addr2);

static void
MR_add_to_sort_arena(const void *addr)
{
    const MR_IO_Table_Stats_Hash_Record *record;
    int                                 next;

    record = (const MR_IO_Table_Stats_Hash_Record *) addr;
    next = MR_io_tabling_stats_sort_arena_next;
    MR_io_tabling_stats_sort_arena[next].MR_io_tabling_stats_proc =
        record->MR_io_tabling_stats_proc;
    MR_io_tabling_stats_sort_arena[next].MR_io_tabling_stats_count =
        record->MR_io_tabling_stats_count;
    MR_io_tabling_stats_sort_arena_next++;
}

static int
MR_compare_in_sort_arena(const void *addr1, const void *addr2)
{
    const MR_IO_Table_Stats_Hash_Record *record1;
    const MR_IO_Table_Stats_Hash_Record *record2;

    record1 = (const MR_IO_Table_Stats_Hash_Record *) addr1;
    record2 = (const MR_IO_Table_Stats_Hash_Record *) addr2;
    return record2->MR_io_tabling_stats_count -
        record1->MR_io_tabling_stats_count;
}

void
MR_io_tabling_stats(FILE *fp)
{
    const MR_TableIoDecl            *table_io_decl;
    const MR_ProcLayout             *proc_layout;
    MR_ConstString                  proc_name;
    int                             arity;
    MR_Word                         is_func;
    MR_TrieNode                     answer_block_trie;
    MR_Word                         *answer_block;
    MR_Hash_Table                   hash_table;
    MR_IO_Table_Stats_Hash_Record   *hash_record;
    MR_IO_Table_Stats_Hash_Record   *record;
    int                             num_entries;
    int                             count;
    int                             i;

    /*
    ** Create a fresh new hash table, separate the table created by
    ** any previous call to this function. We can't use structure assignment,
    ** as that causes gcc 3.2 to throw a fit.
    */
    hash_table.MR_ht_size  = MR_io_tabling_stats_table.MR_ht_size;
    hash_table.MR_ht_store = NULL;
    hash_table.MR_ht_key   = MR_io_tabling_stats_table.MR_ht_key;
    hash_table.MR_ht_hash  = MR_io_tabling_stats_table.MR_ht_hash;
    hash_table.MR_ht_equal = MR_io_tabling_stats_table.MR_ht_equal;
    MR_init_hash_table(hash_table);
    num_entries = 0;

    for (i = MR_io_tabling_start; i < MR_io_tabling_counter_hwm; i++) {
        MR_TABLE_START_INT(NULL, MR_FALSE, MR_FALSE, answer_block_trie,
            (MR_TrieNode) &MR_io_tabling_pointer, MR_io_tabling_start, i);
        answer_block = answer_block_trie->MR_answerblock;

        if (answer_block == NULL) {
            continue;
        }

        table_io_decl = (const MR_TableIoDecl *) answer_block[0];
        proc_layout = table_io_decl->MR_table_io_decl_proc;

        hash_record = MR_lookup_hash_table(hash_table, proc_layout);
        if (hash_record == NULL) {
            hash_record = MR_GC_NEW(MR_IO_Table_Stats_Hash_Record);
            hash_record->MR_io_tabling_stats_proc = proc_layout;
            hash_record->MR_io_tabling_stats_count = 1;
            (void) MR_insert_hash_table(hash_table, hash_record);
            num_entries++;
        } else {
            hash_record->MR_io_tabling_stats_count++;
        }
    }

    MR_io_tabling_stats_sort_arena =
        MR_GC_NEW_ARRAY(MR_IO_Table_Stats_Hash_Record, num_entries);
    MR_io_tabling_stats_sort_arena_next = 0;
    MR_process_all_entries(hash_table, MR_add_to_sort_arena);

    if (MR_io_tabling_stats_sort_arena_next != num_entries) {
        MR_fatal_error("MR_io_tabling_stats: num_entries mismatch");
    }

    qsort(MR_io_tabling_stats_sort_arena, num_entries,
        sizeof(MR_IO_Table_Stats_Hash_Record), MR_compare_in_sort_arena);

    for (i = 0; i < num_entries; i++) {
        record = &MR_io_tabling_stats_sort_arena[i];
        proc_layout = record->MR_io_tabling_stats_proc;
        count = record->MR_io_tabling_stats_count;
        MR_generate_proc_name_from_layout(proc_layout, &proc_name, &arity,
            &is_func);

        fprintf(fp, "%8d %4s %s/%d\n", count, (is_func ? "func" : "pred"),
            proc_name, arity);
    }
}

/**************************************************************************/
/*
** This section of this file maps proc layouts to materialized procedure
** body representations.
**
** We record information about procedure representations in a hash table
** that is indexed by the proc layout address.
**
** This table is used by the declarative debugger. Since the declarative
** debugger can be required in any grade, we always include this table, but
** it is initialized (and the bulk of its memory allocated) only if the
** declarative debugger is in fact invoked.
*/

#define PROC_DEFN_REP_TABLE_SIZE (1 << 16)   /* 64k */

typedef struct {
    const MR_ProcLayout     *plr_layout;
    MR_Word                 plr_rep;
} MR_ProcLayoutRep;

static  void                MR_do_init_proc_defn_rep_table(void);
static  const void          *MR_proc_layout_rep_key(const void *proc_layout);
static  int                 MR_hash_proc_layout_addr(const void *addr);
static  MR_bool             MR_equal_proc_layouts(const void *addr1,
                                const void *addr2);

static  MR_Hash_Table       proc_defn_rep_table = {
                                PROC_DEFN_REP_TABLE_SIZE,
                                NULL,
                                MR_proc_layout_rep_key,
                                MR_hash_proc_layout_addr,
                                MR_equal_proc_layouts
                            };

static void
MR_do_init_proc_defn_rep_table(void)
{
    static  MR_bool done = MR_FALSE;

    if (!done) {
        MR_init_hash_table(proc_defn_rep_table);
        done = MR_TRUE;
    }
}

void
MR_insert_proc_defn_rep(const MR_ProcLayout *proc_layout,
    MR_Word proc_defn_rep)
{
    MR_ProcLayoutRep    *layout_rep;

    MR_do_init_proc_defn_rep_table();

    layout_rep = MR_GC_NEW(MR_ProcLayoutRep);
    layout_rep->plr_layout = proc_layout;
    layout_rep->plr_rep = proc_defn_rep;

    (void) MR_insert_hash_table(proc_defn_rep_table, layout_rep);

#ifdef  MR_DEBUG_PROC_REP
    if (MR_progdebug) {
        printf("insert: layout %p, rep %x, pair %p\n",
            proc_layout, proc_defn_rep, layout_rep);
    }
#endif
}

MR_Word
MR_lookup_proc_defn_rep(const MR_ProcLayout *proc_layout)
{
    const MR_ProcLayoutRep  *layout_rep;

    MR_do_init_proc_defn_rep_table();

    layout_rep = MR_lookup_hash_table(proc_defn_rep_table, proc_layout);
    if (layout_rep == NULL) {
#ifdef  MR_DEBUG_PROC_REP
        if (MR_progdebug) {
            printf("search for layout %p: not found\n", proc_layout);
        }
#endif

        return 0;
    }

#ifdef  MR_DEBUG_PROC_REP
    if (MR_progdebug) {
        printf("search for layout %p: found pair %p, rep %x\n",
            proc_layout, layout_rep, layout_rep->plr_rep);
    }
#endif

    return layout_rep->plr_rep;
}

static const void *
MR_proc_layout_rep_key(const void *pair)
{
    MR_ProcLayoutRep    *proc_layout_rep;

    proc_layout_rep = (MR_ProcLayoutRep *) pair;
    if (proc_layout_rep == NULL) {
        return NULL;
    } else {
        return (const void *) proc_layout_rep->plr_layout;
    }
}

static int
MR_hash_proc_layout_addr(const void *addr)
{
    return (((MR_Unsigned) addr) >> 5) % PROC_DEFN_REP_TABLE_SIZE;
}

static MR_bool
MR_equal_proc_layouts(const void *addr1, const void *addr2)
{
    return ((const MR_ProcLayout *) addr1) == ((const MR_ProcLayout *) addr2);
}

/**************************************************************************/
/*
** This section of this file provides the code that generated redo events.
** Its labels are pointed to by the temp frames pushed onto the nondet stack
** by model_non procedures when they exit.
*/

#ifndef MR_HIGHLEVEL_CODE

MR_define_extern_entry(MR_do_trace_redo_fail_shallow);
MR_define_extern_entry(MR_do_trace_redo_fail_deep);

MR_BEGIN_MODULE(MR_trace_labels_module)
    MR_init_entry_an(MR_do_trace_redo_fail_shallow);
    MR_init_entry_an(MR_do_trace_redo_fail_deep);
MR_BEGIN_CODE

MR_define_entry(MR_do_trace_redo_fail_shallow);
    /*
    ** If this code ever needs changing, you may also need to change
    ** the code in extras/exceptions/exception.m similarly.
    */
    if (MR_redo_fromfull_framevar(MR_redofr_slot(MR_curfr))) {
        MR_Code *MR_jumpaddr;
        MR_save_transient_registers();
        MR_jumpaddr = MR_trace((const MR_LabelLayout *)
            MR_redo_layout_framevar(MR_redofr_slot(MR_curfr)));
        MR_restore_transient_registers();
        if (MR_jumpaddr != NULL) {
            MR_GOTO(MR_jumpaddr);
        }
    }
    MR_fail();

MR_define_entry(MR_do_trace_redo_fail_deep);
#if 0
    /* For use in case this ever needs to be debugged again. */
    printf("MR_curfr = %p\n", MR_curfr);
    printf("MR_redofr_slot(MR_curfr) = %p\n", MR_redofr_slot(MR_curfr));
    printf("&MR_redo_layout_framevar(MR_redofr_slot(MR_curfr) = %p\n",
        &MR_redo_layout_framevar(MR_redofr_slot(MR_curfr)));
    printf("MR_redo_layout_framevar(MR_redofr_slot(MR_curfr) = %p\n",
        MR_redo_layout_framevar(MR_redofr_slot(MR_curfr)));
#endif
    /*
    ** If this code ever needs changing, you may also need to change
    ** the code in library/exception.m similarly.
    */
    {
        MR_Code *MR_jumpaddr;

        MR_save_transient_registers();
        MR_jumpaddr = MR_trace((const MR_LabelLayout *)
            MR_redo_layout_framevar(MR_redofr_slot(MR_curfr)));
        MR_restore_transient_registers();
        if (MR_jumpaddr != NULL) {
            MR_GOTO(MR_jumpaddr);
        }
    }
    MR_fail();

MR_END_MODULE

#endif /* !MR_HIGHLEVEL_CODE */

/* forward decls to suppress gcc warnings */
void mercury_sys_init_trace_init(void);
void mercury_sys_init_trace_init_type_tables(void);
#ifdef  MR_DEEP_PROFILING
void mercury_sys_init_trace_write_out_proc_statics(FILE *fp);
#endif

void mercury_sys_init_trace_init(void)
{
#ifndef MR_HIGHLEVEL_CODE
    MR_trace_labels_module();
#endif
}

void mercury_sys_init_trace_init_type_tables(void)
{
    /* no types to register */
}

#ifdef  MR_DEEP_PROFILING
void mercury_sys_init_trace_write_out_proc_statics(FILE *fp)
{
    /* no proc_statics to write out */
}
#endif
