mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-12 12:26:29 +00:00
deep_profiler/display_report.m:
deep_profiler/message.m:
deep_profiler/recursion_patterns.m:
deep_profiler/var_use_analsis.m:
java/runtime/UnreachableDefault.java:
runtime/mercury_engine.c:
runtime/mercury_minimal_model.c:
runtime/mercury_signal.h:
runtime/mercury_stack_layout.h:
runtime/mercury_wrapper.c:
runtime/mercury_threadscope.c:
trace/mercury_trace_external.c:
HISTORY:
As above.
770 lines
25 KiB
C
770 lines
25 KiB
C
// vim: ts=4 sw=4 expandtab ft=c
|
|
|
|
// Copyright (C) 1993-2001, 2003-2007, 2009-2011 The University of Melbourne.
|
|
// Copyright (C) 2014, 2016, 2018 The Mercury team.
|
|
// This file is distributed under the terms specified in COPYING.LIB.
|
|
|
|
/*
|
|
INIT mercury_sys_init_engine
|
|
ENDINIT
|
|
*/
|
|
|
|
#include "mercury_imp.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <setjmp.h>
|
|
|
|
#include "mercury_engine.h"
|
|
#include "mercury_memory_zones.h" // for MR_create_zone()
|
|
#include "mercury_memory_handlers.h" // for MR_default_handler()
|
|
#include "mercury_threadscope.h" // for event posting
|
|
|
|
#include "mercury_dummy.h"
|
|
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
|
|
#ifdef MR_USE_GCC_NONLOCAL_GOTOS
|
|
|
|
// Space to reserve for local vars. If this parameter is modified
|
|
// then the reference manual will also need to be updated.
|
|
|
|
#define LOCALS_SIZE 10240
|
|
|
|
#define MAGIC_MARKER 187 // a random character
|
|
#define MAGIC_MARKER_2 142 // another random character
|
|
|
|
#endif // MR_USE_GCC_NONLOCAL_GOTOS
|
|
|
|
MR_NO_RETURN(static void call_engine_inner(MR_Code *entry_point));
|
|
|
|
#ifndef MR_USE_GCC_NONLOCAL_GOTOS
|
|
static MR_Code *engine_done(void);
|
|
static MR_Code *engine_done_2(void);
|
|
static MR_Code *engine_init_registers(void);
|
|
#endif
|
|
|
|
#endif // !MR_HIGHLEVEL_CODE
|
|
|
|
MR_bool MR_debugflag[MR_MAXFLAG];
|
|
|
|
MR_Debug_Flag_Info MR_debug_flag_info[MR_MAXFLAG] = {
|
|
{ "prog", MR_PROGFLAG },
|
|
{ "goto", MR_GOTOFLAG },
|
|
{ "call", MR_CALLFLAG },
|
|
{ "heap", MR_HEAPFLAG },
|
|
{ "detstack", MR_DETSTACKFLAG },
|
|
{ "nondetstack", MR_NONDETSTACKFLAG },
|
|
{ "final", MR_FINALFLAG },
|
|
{ "mem", MR_MEMFLAG },
|
|
{ "sreg", MR_SREGFLAG },
|
|
{ "trace", MR_TRACEFLAG },
|
|
{ "table", MR_TABLEFLAG },
|
|
{ "tablehash", MR_TABLEHASHFLAG },
|
|
{ "tablestack", MR_TABLESTACKFLAG },
|
|
{ "unbuf", MR_UNBUFFLAG },
|
|
{ "agc", MR_AGC_FLAG },
|
|
{ "ordreg", MR_ORDINARY_REG_FLAG },
|
|
{ "anyreg", MR_ANY_REG_FLAG },
|
|
{ "printlocn", MR_PRINT_LOCN_FLAG },
|
|
{ "enabled", MR_LLD_DEBUG_ENABLED_FLAG },
|
|
{ "notnearest", MR_NOT_NEAREST_FLAG },
|
|
{ "debugslots", MR_DEBUG_SLOTS_FLAG },
|
|
{ "deepdebugfile", MR_DEEP_PROF_DEBUG_FILE_FLAG },
|
|
{ "stackextend", MR_STACK_EXTEND_FLAG },
|
|
{ "detail", MR_DETAILFLAG }
|
|
};
|
|
|
|
#ifndef MR_THREAD_SAFE
|
|
MercuryEngine MR_engine_base;
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// MR_init_engine() calls MR_init_memory() which sets up all the necessary
|
|
// stuff for allocating memory-zones and other runtime areas (such as
|
|
// the zone structures and context structures).
|
|
|
|
void
|
|
MR_init_engine(MercuryEngine *eng)
|
|
{
|
|
// First, ensure that the truly global stuff has been initialized
|
|
// (if it was already initialized, this does nothing).
|
|
|
|
MR_init_memory();
|
|
|
|
#if !defined(MR_USE_GCC_NONLOCAL_GOTOS) && !defined(MR_HIGHLEVEL_CODE)
|
|
{
|
|
static MR_bool made_engine_done_label = MR_FALSE;
|
|
if (!made_engine_done_label) {
|
|
MR_make_label("engine_done", MR_LABEL(engine_done), engine_done);
|
|
made_engine_done_label = MR_TRUE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Second, initialize the per-engine (i.e. normally per Posix thread)
|
|
// stuff.
|
|
|
|
#ifndef MR_CONSERVATIVE_GC
|
|
eng->MR_eng_heap_zone = MR_create_or_reuse_zone("heap",
|
|
MR_heap_size, MR_next_offset(), MR_heap_zone_size, MR_default_handler);
|
|
eng->MR_eng_hp = eng->MR_eng_heap_zone->MR_zone_min;
|
|
|
|
#ifdef MR_NATIVE_GC
|
|
eng->MR_eng_heap_zone2 = MR_create_or_reuse_zone("heap2",
|
|
MR_heap_size, MR_next_offset(), MR_heap_zone_size, MR_default_handler);
|
|
|
|
#ifdef MR_DEBUG_AGC_PRINT_VARS
|
|
eng->MR_eng_debug_heap_zone = MR_create_or_reuse_zone("debug_heap",
|
|
MR_debug_heap_size, MR_next_offset(),
|
|
MR_debug_heap_zone_size, MR_default_handler);
|
|
#endif
|
|
#endif // MR_NATIVE_GC
|
|
|
|
#ifdef MR_MIGHT_RECLAIM_HP_ON_FAILURE
|
|
eng->MR_eng_solutions_heap_zone = MR_create_or_reuse_zone("solutions_heap",
|
|
MR_solutions_heap_size, MR_next_offset(),
|
|
MR_solutions_heap_zone_size, MR_default_handler);
|
|
eng->MR_eng_sol_hp = eng->MR_eng_solutions_heap_zone->MR_zone_min;
|
|
|
|
eng->MR_eng_global_heap_zone = MR_create_or_reuse_zone("global_heap",
|
|
MR_global_heap_size, MR_next_offset(),
|
|
MR_global_heap_zone_size, MR_default_handler);
|
|
eng->MR_eng_global_hp = eng->MR_eng_global_heap_zone->MR_zone_min;
|
|
#endif // MR_MIGHT_RECLAIM_HP_ON_FAILURE
|
|
#endif // !MR_CONSERVATIVE_GC
|
|
|
|
#ifdef MR_THREAD_SAFE
|
|
// The caller must initialise id and type.
|
|
eng->MR_eng_id = MR_ENGINE_ID_NONE;
|
|
eng->MR_eng_type = MR_ENGINE_TYPE_SHARED;
|
|
eng->MR_eng_c_depth = 0;
|
|
#endif
|
|
|
|
#ifdef MR_LL_PARALLEL_CONJ
|
|
eng->MR_eng_spark_deque = MR_GC_NEW(MR_SparkDeque);
|
|
MR_init_wsdeque(eng->MR_eng_spark_deque, MR_INITIAL_SPARK_DEQUE_SIZE);
|
|
#endif
|
|
|
|
// Don't allocate a context for this engine until it is actually needed.
|
|
eng->MR_eng_this_context = NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// The engine must be removed from MR_all_engine_bases BEFORE calling this
|
|
// function.
|
|
|
|
void MR_finalize_engine(MercuryEngine *eng)
|
|
{
|
|
// XXX There are lots of other resources in MercuryEngine that
|
|
// might need to be finalized.
|
|
|
|
if (eng->MR_eng_this_context) {
|
|
// Saving the context is very important before releasing it.
|
|
// See the documentation for MR_release_context
|
|
|
|
MR_save_context(eng->MR_eng_this_context);
|
|
MR_release_context(eng->MR_eng_this_context);
|
|
}
|
|
|
|
#if MR_THREADSCOPE
|
|
if (eng->MR_eng_ts_buffer) {
|
|
MR_threadscope_finalize_engine(eng);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
MercuryEngine *
|
|
MR_create_engine(void)
|
|
{
|
|
MercuryEngine *eng;
|
|
|
|
// We need to use MR_GC_NEW_UNCOLLECTABLE() here, rather than MR_GC_NEW(),
|
|
// since the engine pointer will normally be stored in thread-local
|
|
// storage, which is not traced by the conservative garbage collector.
|
|
|
|
eng = MR_GC_NEW_UNCOLLECTABLE_ATTRIB(MercuryEngine, MR_ALLOC_SITE_RUNTIME);
|
|
MR_init_engine(eng);
|
|
return eng;
|
|
}
|
|
|
|
void
|
|
MR_destroy_engine(MercuryEngine *eng)
|
|
{
|
|
MR_finalize_engine(eng);
|
|
MR_GC_free_attrib(eng);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifdef MR_HIGHLEVEL_CODE
|
|
|
|
// This debugging hook is empty in the high-level code case:
|
|
// we don't save the previous locations.
|
|
|
|
void
|
|
MR_dump_prev_locations(void)
|
|
{
|
|
}
|
|
|
|
#else // !MR_HIGHLEVEL_CODE
|
|
|
|
// MR_Word *
|
|
// MR_call_engine(MR_Code *entry_point, MR_bool catch_exceptions)
|
|
//
|
|
// This routine calls a Mercury routine from C.
|
|
//
|
|
// The called routine should be det/semidet/cc_multi/cc_nondet.
|
|
//
|
|
// If the called routine returns normally (this includes the case of a
|
|
// semidet/cc_nondet routine failing, i.e. returning with
|
|
// MR_r1 = MR_FALSE), then MR_call_engine() will return NULL.
|
|
//
|
|
// If the called routine exits by throwing an exception, then the
|
|
// behaviour depends on the `catch_exceptions' flag.
|
|
// if `catch_exceptions' is true, then MR_call_engine() will return the
|
|
// Mercury exception object thrown. If `catch_exceptions' is false,
|
|
// then MR_call_engine() will not return; instead, the code for `throw'
|
|
// will unwind the stacks (including the C stack) back to the nearest
|
|
// enclosing exception handler.
|
|
//
|
|
// The virtual machine registers must be set up correctly before the call
|
|
// to MR_call_engine(). Specifically, the non-transient real registers
|
|
// must have valid values, and the fake_reg copies of the transient
|
|
// (register window) registers must have valid values; call_engine()
|
|
// will call MR_restore_transient_registers() and will then assume that
|
|
// all the registers have been correctly set up.
|
|
//
|
|
// call_engine() will call MR_save_registers() before returning.
|
|
// That will copy the real registers we use to the fake_reg array.
|
|
//
|
|
// Beware, however, that if you are planning to return to C code that did
|
|
// not #include "mercury_regs.h" (directly or via e.g. "mercury_imp.h"),
|
|
// and you have fiddled with the Mercury registers or invoked
|
|
// call_engine() or anything like that, then you will need to
|
|
// save the real registers that C is using before modifying the
|
|
// Mercury registers and then restore them afterwards.
|
|
//
|
|
// The called routine may invoke C functions; currently this
|
|
// is done by just invoking them directly, although that will
|
|
// have to change if we start using the caller-save registers.
|
|
//
|
|
// The called routine may invoke C functions which in turn
|
|
// invoke call_engine() to invoke Mercury routines (which
|
|
// in turn invoke C functions which ... etc. ad infinitum.)
|
|
//
|
|
// MR_call_engine() calls setjmp() and then invokes call_engine_inner()
|
|
// which does the real work. call_engine_inner() exits by calling
|
|
// longjmp() to return to MR_call_engine(). There are two
|
|
// different implementations of call_engine_inner(), one for gcc,
|
|
// and another portable version that works on standard ANSI C compilers.
|
|
|
|
MR_Word *
|
|
MR_call_engine(MR_Code *entry_point, MR_bool catch_exceptions)
|
|
{
|
|
|
|
jmp_buf curr_jmp_buf;
|
|
jmp_buf * volatile prev_jmp_buf;
|
|
#if defined(MR_MPROF_PROFILE_TIME)
|
|
MR_Code * volatile prev_proc;
|
|
#endif
|
|
|
|
// Preserve the value of MR_ENGINE(MR_eng_jmp_buf) on the C stack.
|
|
// This is so "C calls Mercury which calls C which calls Mercury" etc.
|
|
// will work.
|
|
|
|
MR_restore_transient_registers();
|
|
|
|
prev_jmp_buf = MR_ENGINE(MR_eng_jmp_buf);
|
|
MR_ENGINE(MR_eng_jmp_buf) = &curr_jmp_buf;
|
|
|
|
// Create an exception handler frame on the nondet stack so that
|
|
// we can catch and return Mercury exceptions.
|
|
|
|
if (catch_exceptions) {
|
|
MR_create_exception_handler("call_engine", MR_C_LONGJMP_HANDLER, 0,
|
|
MR_ENTRY(MR_do_fail));
|
|
}
|
|
|
|
// Mark this as the spot to return to.
|
|
|
|
#ifdef MR_DEBUG_JMPBUFS
|
|
printf("engine setjmp %p\n", curr_jmp_buf);
|
|
#endif
|
|
|
|
if (setjmp(curr_jmp_buf)) {
|
|
MR_Word * this_frame;
|
|
MR_Word * exception;
|
|
|
|
#ifdef MR_DEBUG_JMPBUFS
|
|
printf("engine caught jmp %p %p\n",
|
|
prev_jmp_buf, MR_ENGINE(MR_eng_jmp_buf));
|
|
#endif
|
|
|
|
MR_debugmsg0("...caught longjmp\n");
|
|
// On return, set MR_prof_current_proc to be the caller proc again
|
|
// (if time profiling is enabled), restore the registers (since
|
|
// longjmp may clobber them), and restore the saved value of
|
|
// MR_ENGINE(MR_eng_jmp_buf).
|
|
|
|
MR_update_prof_current_proc(prev_proc);
|
|
MR_restore_registers();
|
|
MR_ENGINE(MR_eng_jmp_buf) = prev_jmp_buf;
|
|
if (catch_exceptions) {
|
|
// Figure out whether or not we got an exception. If we got an
|
|
// exception, then all of the necessary cleanup such as stack
|
|
// unwinding has already been done, so all we have to do here
|
|
// is to return the exception.
|
|
|
|
exception = MR_ENGINE(MR_eng_exception);
|
|
if (exception != NULL) {
|
|
return exception;
|
|
}
|
|
// If we added an exception hander, but we didn't get an exception,
|
|
// then we need to remove the exception handler frames from the
|
|
// nondet stack and prune the trail ticket allocated by
|
|
// MR_create_exception_handler().
|
|
|
|
this_frame = MR_curfr;
|
|
MR_maxfr_word = MR_prevfr_slot_word(this_frame);
|
|
MR_curfr_word = MR_succfr_slot_word(this_frame);
|
|
#ifdef MR_USE_TRAIL
|
|
MR_prune_ticket();
|
|
#endif
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
MR_ENGINE(MR_eng_jmp_buf) = &curr_jmp_buf;
|
|
|
|
// If call profiling is enabled, and this is a case of Mercury calling C
|
|
// code which then calls Mercury, then we record the (Mercury caller,
|
|
// Mercury callee) pair in the table of call counts, if possible.
|
|
|
|
#ifdef MR_MPROF_PROFILE_CALLS
|
|
#ifdef MR_MPROF_PROFILE_TIME
|
|
if (MR_prof_current_proc != NULL) {
|
|
MR_PROFILE(entry_point, MR_prof_current_proc);
|
|
}
|
|
#else
|
|
// XXX There is not much we can do in this case to keep the call counts
|
|
// accurate, since we don't know who the caller is.
|
|
|
|
#endif
|
|
#endif // MR_MPROF_PROFILE_CALLS
|
|
|
|
// If time profiling is enabled, then we need to save MR_prof_current_proc
|
|
// so that we can restore it when we return. We must then set
|
|
// MR_prof_current_proc to the procedure that we are about to call.
|
|
//
|
|
// We do this last thing before calling call_engine_inner(), since we want
|
|
// to credit as much as possible of the time in C code to the caller,
|
|
// not to the callee. Note that setting and restoring MR_prof_current_proc
|
|
// here in call_engine() means that time in call_engine_inner()
|
|
// unfortunately gets credited to the callee. That is not ideal, but we
|
|
// can't move this code into call_engine_inner() since call_engine_inner()
|
|
// can't have any local variables and this code needs the `prev_proc'
|
|
// local variable.
|
|
|
|
#ifdef MR_MPROF_PROFILE_TIME
|
|
prev_proc = MR_prof_current_proc;
|
|
MR_set_prof_current_proc(entry_point);
|
|
#endif
|
|
|
|
call_engine_inner(entry_point);
|
|
}
|
|
|
|
#ifdef MR_USE_GCC_NONLOCAL_GOTOS
|
|
|
|
// The gcc-specific version
|
|
|
|
static void
|
|
call_engine_inner(MR_Code *entry_point)
|
|
{
|
|
// Allocate some space for local variables in other procedures. This is
|
|
// done because we may jump into the middle of a C function, which may
|
|
// assume that space on the stack has already been allocated for its
|
|
// variables. Such space would generally be used for expression temporary
|
|
// variables. How did we arrive at the correct value of LOCALS_SIZE?
|
|
// Good question. I think it's more voodoo than science.
|
|
//
|
|
// This used to be done by just calling alloca(LOCALS_SIZE), but on MIPS
|
|
// that just decrements the stack pointer, whereas local variables are
|
|
// referenced via the frame pointer, so it didn't work. This technique
|
|
// should work and should be vaguely portable, just so long as local
|
|
// variables and temporaries are allocated in the same way in every
|
|
// function.
|
|
//
|
|
// WARNING!
|
|
// Do not add local variables to call_engine_inner that you expect
|
|
// to remain live across Mercury execution - Mercury execution will
|
|
// scribble on the stack frame for this function.
|
|
|
|
unsigned char locals[LOCALS_SIZE];
|
|
{
|
|
|
|
#ifdef MR_LOWLEVEL_DEBUG
|
|
{
|
|
// Ensure that we only make the label once.
|
|
static MR_bool initialized = MR_FALSE;
|
|
|
|
if (!initialized) {
|
|
MR_make_label("engine_done", MR_LABEL(engine_done), engine_done);
|
|
MR_make_label("engine_done_2", MR_LABEL(engine_done_2), engine_done_2);
|
|
initialized = MR_TRUE;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Restore any registers that get clobbered by the C function call
|
|
// mechanism.
|
|
|
|
MR_restore_transient_registers();
|
|
|
|
// We save the address of the locals in a global pointer to make sure that
|
|
// gcc can't optimize them away.
|
|
|
|
MR_global_pointer = locals;
|
|
|
|
#ifdef MR_LOWLEVEL_DEBUG
|
|
MR_memset((void *) locals, MAGIC_MARKER, LOCALS_SIZE);
|
|
#endif
|
|
MR_debugmsg1("in `call_engine_inner', locals at %p\n", (void *) locals);
|
|
|
|
// We need to ensure that there is at least one real function call
|
|
// in call_engine_inner(), because otherwise gcc thinks that it doesn't
|
|
// need to restore the caller-save registers (such as the return address!)
|
|
// because it thinks call_engine_inner() is a leaf routine which doesn't
|
|
// call anything else, and so it thinks that they won't have been
|
|
// clobbered.
|
|
//
|
|
// This probably isn't necessary now that we exit from this function
|
|
// using longjmp(), but it doesn't do much harm, so I'm leaving it in.
|
|
//
|
|
// Also for gcc versions >= egcs1.1, we need to ensure that there is
|
|
// at least one jump to an unknown label.
|
|
|
|
goto *MR_dummy_identify_function(&&dummy_label);
|
|
dummy_label:
|
|
|
|
// Increment the number of times we have entered this engine from C,
|
|
// and push the current engine onto the context's stack of saved owners.
|
|
|
|
#ifdef MR_THREAD_SAFE
|
|
MR_ENGINE(MR_eng_c_depth)++;
|
|
|
|
if (MR_ENGINE(MR_eng_this_context) != NULL) {
|
|
MR_ResumeStack *elem;
|
|
|
|
elem = MR_GC_NEW_ATTRIB(MR_ResumeStack, MR_ALLOC_SITE_RUNTIME);
|
|
elem->MR_resume_engine = MR_ENGINE(MR_eng_id);
|
|
elem->MR_resume_c_depth = MR_ENGINE(MR_eng_c_depth);
|
|
elem->MR_resume_stack_next =
|
|
MR_ENGINE(MR_eng_this_context)->MR_ctxt_resume_stack;
|
|
MR_ENGINE(MR_eng_this_context)->MR_ctxt_resume_stack = elem;
|
|
}
|
|
#endif
|
|
|
|
// Now just call the entry point.
|
|
|
|
MR_noprof_call(entry_point, MR_LABEL(engine_done));
|
|
|
|
MR_define_label(engine_done);
|
|
|
|
assert(MR_ENGINE(MR_eng_this_context));
|
|
|
|
// Check that we are reentering C in the correct engine.
|
|
// If not, reschedule the context so that it will be picked up by
|
|
// the correct engine when it is available.
|
|
|
|
#ifdef MR_THREAD_SAFE
|
|
{
|
|
MR_Context *this_ctxt;
|
|
MR_ResumeStack *elem;
|
|
|
|
this_ctxt = MR_ENGINE(MR_eng_this_context);
|
|
elem = this_ctxt->MR_ctxt_resume_stack;
|
|
this_ctxt->MR_ctxt_resume_stack = elem->MR_resume_stack_next;
|
|
|
|
if ((elem->MR_resume_engine == MR_ENGINE(MR_eng_id)) &&
|
|
elem->MR_resume_c_depth == MR_ENGINE(MR_eng_c_depth))
|
|
{
|
|
MR_GC_free_attrib(elem);
|
|
MR_GOTO_LABEL(engine_done_2);
|
|
}
|
|
|
|
#ifdef MR_THREADSCOPE
|
|
MR_threadscope_post_stop_context(MR_TS_STOP_REASON_YIELDING);
|
|
#endif
|
|
MR_save_context(this_ctxt);
|
|
this_ctxt->MR_ctxt_resume = MR_LABEL(engine_done_2);
|
|
this_ctxt->MR_ctxt_resume_engine_required = MR_TRUE;
|
|
this_ctxt->MR_ctxt_resume_engine = elem->MR_resume_engine;
|
|
this_ctxt->MR_ctxt_resume_c_depth = elem->MR_resume_c_depth;
|
|
MR_GC_free_attrib(elem);
|
|
MR_schedule_context(this_ctxt);
|
|
|
|
MR_ENGINE(MR_eng_this_context) = NULL;
|
|
MR_idle();
|
|
}
|
|
#endif
|
|
|
|
// engine_done can be entered while the context is running on the wrong
|
|
// engine (thread). If it turns out to be the case, then we suspend the
|
|
// context and reschedule it so that it will resume in engine_done_2 and be
|
|
// run on the correct engine. So engine_done_2 will always be run on the
|
|
// engine which started the C->Mercury call, and engine_done ensures that
|
|
// is the case.
|
|
|
|
MR_define_label(engine_done_2);
|
|
|
|
#ifdef MR_THREAD_SAFE
|
|
// Decrement the number of times we have entered this engine from C.
|
|
MR_ENGINE(MR_eng_c_depth)--;
|
|
#endif
|
|
|
|
MR_debugmsg1("in label `engine_done', locals at %p\n", locals);
|
|
|
|
#ifdef MR_LOWLEVEL_DEBUG
|
|
// Check how much of the space we reserved for local variables
|
|
// was actually used.
|
|
|
|
if (MR_check_space) {
|
|
int low = 0, high = LOCALS_SIZE;
|
|
int used_low, used_high;
|
|
|
|
while (low < high && locals[low] == MAGIC_MARKER) {
|
|
low++;
|
|
}
|
|
while (low < high && locals[high - 1] == MAGIC_MARKER) {
|
|
high--;
|
|
}
|
|
used_low = high;
|
|
used_high = LOCALS_SIZE - low;
|
|
printf("max locals used: %3d bytes (probably)\n",
|
|
MR_min(high, LOCALS_SIZE - low));
|
|
printf("(low mark = %d, high mark = %d)\n", low, high);
|
|
}
|
|
#endif // MR_LOWLEVEL_DEBUG
|
|
|
|
// Despite the above precautions with allocating a large chunk of unused
|
|
// stack space, the return address may still have been stored on the
|
|
// top of the stack, past our dummy locals, where it may have been
|
|
// clobbered. Hence the only safe way to exit is with longjmp().
|
|
//
|
|
// Since longjmp() may clobber the registers, we need to save them first.
|
|
|
|
MR_ENGINE(MR_eng_exception) = NULL;
|
|
MR_save_registers();
|
|
|
|
#ifdef MR_DEBUG_JMPBUFS
|
|
printf("engine longjmp %p\n", MR_ENGINE(MR_eng_jmp_buf));
|
|
#endif
|
|
|
|
MR_debugmsg0("longjmping out...\n");
|
|
longjmp(*(MR_ENGINE(MR_eng_jmp_buf)), 1);
|
|
}} // end call_engine_inner()
|
|
|
|
// with nonlocal gotos, we don't save the previous locations
|
|
void
|
|
MR_dump_prev_locations(void)
|
|
{
|
|
}
|
|
|
|
#else // not MR_USE_GCC_NONLOCAL_GOTOS
|
|
|
|
// The portable version.
|
|
//
|
|
// To keep the main dispatch loop tight, instead of returning a null pointer
|
|
// to indicate when we have finished executing, we just longjmp() out.
|
|
// We need to save the registers before calling longjmp(), since doing
|
|
// a longjmp() might clobber them.
|
|
//
|
|
// With register windows, we need to restore the registers to their initialized
|
|
// values from their saved copies. This must be done in a function
|
|
// engine_init_registers() rather than directly from call_engine_inner()
|
|
// because otherwise their value would get mucked up because of the function
|
|
// call from call_engine_inner().
|
|
//
|
|
// XXX The portable version does not yet prevent Mercury code returning back
|
|
// into C code on the wrong Mercury engine. Therefore low-level .par grades
|
|
// without gcc non-local gotos are unsafe.
|
|
|
|
static MR_Code *
|
|
engine_done(void)
|
|
{
|
|
MR_ENGINE(MR_eng_exception) = NULL;
|
|
MR_save_registers();
|
|
MR_debugmsg0("longjmping out...\n");
|
|
longjmp(*(MR_ENGINE(MR_eng_jmp_buf)), 1);
|
|
return NULL; // Not executed, but required to suppress warnings.
|
|
}
|
|
|
|
static MR_Code *
|
|
engine_init_registers(void)
|
|
{
|
|
MR_restore_transient_registers();
|
|
MR_succip_word = (MR_Word) (MR_Code *) engine_done;
|
|
return NULL;
|
|
}
|
|
|
|
// For debugging purposes, we keep a circular buffer of the last 40 locations
|
|
// that we jumped to. This is very useful for determining the cause of a
|
|
// crash, since it runs a lot faster than -dg.
|
|
|
|
#define NUM_PREV_FPS 40
|
|
|
|
typedef MR_Code *Func(void);
|
|
|
|
static MR_Code *prev_fps[NUM_PREV_FPS];
|
|
static int prev_fp_index = 0;
|
|
|
|
void
|
|
MR_dump_prev_locations(void)
|
|
{
|
|
int i;
|
|
int pos;
|
|
|
|
#if !defined(MR_DEBUG_GOTOS)
|
|
if (MR_tracedebug)
|
|
#endif
|
|
{
|
|
printf("previous %d locations:\n", NUM_PREV_FPS);
|
|
for (i = 0; i < NUM_PREV_FPS; i++) {
|
|
pos = (i + prev_fp_index) % NUM_PREV_FPS;
|
|
MR_printlabel(stdout, prev_fps[pos]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
call_engine_inner(MR_Code *entry_point)
|
|
{
|
|
register Func *fp;
|
|
|
|
// Start up the actual engine.
|
|
// The loop is unrolled a bit for efficiency.
|
|
|
|
fp = engine_init_registers;
|
|
fp = (Func *) (*fp)();
|
|
fp = (Func *) entry_point;
|
|
|
|
#if !defined(MR_DEBUG_GOTOS)
|
|
if (!MR_tracedebug) {
|
|
for (;;) {
|
|
fp = (Func *) (*fp)();
|
|
fp = (Func *) (*fp)();
|
|
fp = (Func *) (*fp)();
|
|
fp = (Func *) (*fp)();
|
|
fp = (Func *) (*fp)();
|
|
fp = (Func *) (*fp)();
|
|
fp = (Func *) (*fp)();
|
|
fp = (Func *) (*fp)();
|
|
}
|
|
} else
|
|
#endif
|
|
for (;;) {
|
|
prev_fps[prev_fp_index] = (MR_Code *) fp;
|
|
|
|
if (++prev_fp_index >= NUM_PREV_FPS) {
|
|
prev_fp_index = 0;
|
|
}
|
|
|
|
MR_debuggoto(fp);
|
|
MR_debugsreg();
|
|
fp = (Func *) (*fp)();
|
|
}
|
|
} // end call_engine_inner()
|
|
#endif // not MR_USE_GCC_NONLOCAL_GOTOS
|
|
|
|
#endif // !MR_HIGHLEVEL_CODE
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void
|
|
MR_terminate_engine(void)
|
|
{
|
|
// We don't bother to deallocate memory...
|
|
// that will happen automatically on process exit anyway.
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
|
|
MR_define_extern_entry(MR_do_redo);
|
|
MR_define_extern_entry(MR_do_fail);
|
|
MR_define_extern_entry(MR_do_succeed);
|
|
MR_define_extern_entry(MR_do_last_succeed);
|
|
MR_define_extern_entry(MR_do_not_reached);
|
|
MR_define_extern_entry(MR_exception_handler_do_fail);
|
|
|
|
MR_BEGIN_MODULE(special_labels_module)
|
|
MR_init_entry_an(MR_do_redo);
|
|
MR_init_entry_an(MR_do_fail);
|
|
MR_init_entry_an(MR_do_succeed);
|
|
MR_init_entry_an(MR_do_last_succeed);
|
|
MR_init_entry_an(MR_do_not_reached);
|
|
MR_init_entry_an(MR_exception_handler_do_fail);
|
|
MR_BEGIN_CODE
|
|
|
|
MR_define_entry(MR_do_redo);
|
|
MR_redo();
|
|
|
|
MR_define_entry(MR_do_fail);
|
|
MR_fail();
|
|
|
|
MR_define_entry(MR_do_succeed);
|
|
MR_succeed();
|
|
|
|
MR_define_entry(MR_do_last_succeed);
|
|
MR_succeed_discard();
|
|
|
|
MR_define_entry(MR_do_not_reached);
|
|
MR_fatal_error("reached not_reached\n");
|
|
|
|
MR_define_entry(MR_exception_handler_do_fail);
|
|
// `MR_exception_handler_do_fail' is the same as `MR_do_fail':
|
|
// it just invokes MR_fail(). The reason we don't just use `MR_do_fail'
|
|
// for this is that when unwinding the stack we check for a redoip
|
|
// of `MR_exception_handler_do_fail' and handle it specially.
|
|
|
|
MR_fail();
|
|
|
|
MR_END_MODULE
|
|
|
|
#endif // !MR_HIGHLEVEL_CODE
|
|
|
|
// Forward decls to suppress gcc warnings.
|
|
void mercury_sys_init_engine_init(void);
|
|
void mercury_sys_init_engine_init_type_tables(void);
|
|
#ifdef MR_DEEP_PROFILING
|
|
void mercury_sys_init_engine_write_out_proc_statics(FILE *fp);
|
|
#endif
|
|
|
|
void mercury_sys_init_engine_init(void)
|
|
{
|
|
#ifndef MR_HIGHLEVEL_CODE
|
|
special_labels_module();
|
|
#endif
|
|
}
|
|
|
|
void mercury_sys_init_engine_init_type_tables(void)
|
|
{
|
|
// No types to register.
|
|
}
|
|
|
|
#ifdef MR_DEEP_PROFILING
|
|
void mercury_sys_init_engine_write_out_proc_statics(FILE *fp)
|
|
{
|
|
// No proc_statics to write out.
|
|
}
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|