Files
mercury/runtime/mercury_engine.c
Paul Bone 79c5b951fb Modify how an idle engine with a dirty context gets new work.
These changes fix a couple of performance bugs and also modify the
algorithms in the RTS so that they match the ones in my thesis.

runtime/mercury_context.[ch]:
    An engine with a dirty context will no-longer check for a runnable
    context first.  It first checks for a local spark, if the spark is not
    compatible it puts the spark back on the local spark stack.  Then it
    saves the dirty context and jumps to MR_idle - the entry point for
    engines with no contexts.

    Remove the MR_MAYBE_TRAMPOLINE macro and expand out any case where it
    was previously used.

    We no longer execute a spark when an engine has a dirty incompatible
    context.  (previously we saved the old context then allocated a new
    one).  Therefore prepare_engine_for_spark() no-longer needs the
    join_label parameter (which was used when saving a dirty context).
    Consequently, the same parameter has been removed from
    MR_do_steal_spark.

    If a work stealing thief looses a race then it retries until it wins or
    there is no work.

    Use a mutex rather than a (binary) semaphore to protect the engine sleep
    sync structure.  This more directly reflects the intentions plus POSIX
    mutexes don't always make kernel calls but semaphores do.

    The MR_num_idle_engines global was not being updated correctly, in
    particular it showed that there were idle engines even when there
    weren't.  This caused an engine creating a spark to always attempt to
    notify other engines of the spark.  Fixing the use of
    MR_num_idle_engines improves performance by over a factor of 2x in the
    naive fibs micro benchmark.

    Refactor MR_join_and_continue to match the simplier structure in my
    thesis.

    Rename MR_destroy_context to MR_release_context, which gives a more
    accurate impression.

    Update some MR_assert calls that where incorrect.

runtime/mercury_engine.c:
runtime/mercury_par_builtin.c:
    Conform to MR_release_context.

library/thread.m:
    Conform to MR_release_context.

    Add a missing MR_save_context.
2012-08-06 02:11:24 +00:00

820 lines
25 KiB
C

/*
** vim: ts=4 sw=4 expandtab
*/
/*
INIT mercury_sys_init_engine
ENDINIT
*/
/*
** Copyright (C) 1993-2001, 2003-2007, 2009-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.
*/
#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
eng->MR_eng_owner_thread = pthread_self();
eng->MR_eng_c_depth = 0;
#endif
#ifdef MR_LL_PARALLEL_CONJ
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;
}
/*---------------------------------------------------------------------------*/
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 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's 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've 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_SavedOwner *owner;
owner = MR_GC_NEW_ATTRIB(MR_SavedOwner, MR_ALLOC_SITE_RUNTIME);
owner->MR_saved_owner_engine = MR_ENGINE(MR_eng_id);
owner->MR_saved_owner_c_depth = MR_ENGINE(MR_eng_c_depth);
owner->MR_saved_owner_next =
MR_ENGINE(MR_eng_this_context)->MR_ctxt_saved_owners;
MR_ENGINE(MR_eng_this_context)->MR_ctxt_saved_owners = owner;
}
#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're 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_SavedOwner *owner;
this_ctxt = MR_ENGINE(MR_eng_this_context);
owner = this_ctxt->MR_ctxt_saved_owners;
this_ctxt->MR_ctxt_saved_owners = owner->MR_saved_owner_next;
if ((owner->MR_saved_owner_engine == MR_ENGINE(MR_eng_id)) &&
owner->MR_saved_owner_c_depth == MR_ENGINE(MR_eng_c_depth))
{
MR_GC_free_attrib(owner);
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_owner_engine = owner->MR_saved_owner_engine;
this_ctxt->MR_ctxt_resume_c_depth = owner->MR_saved_owner_c_depth;
this_ctxt->MR_ctxt_resume_engine_required = MR_TRUE;
MR_GC_free_attrib(owner);
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've 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've 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 (see the code involving
** MR_eng_owner_thread and MR_eng_c_depth in the gcc version). 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
/*---------------------------------------------------------------------------*/