mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-25 22:34:26 +00:00
Estimated hours taken: 6
A general cleanup of the code in the runtime directory, aimed at
formulating a more coherent header file inclusion policy. This change
fixes a couple of bugs that prevented the runtime from compiling in
certain configurations (e.g. on muse) due to missing #includes, and
also fixes a few minor unrelated things such as the use of `size_t'
instead of `unsigned'.
Our header file inclusion policy is that every header file should
#include any other header files needed by the declarations or by the
macros it defines. Cyclic interface dependencies, where two header
files each #include the other, must be avoided (by splitting up header
files into smaller indepdent units, if necessary).
At some stage in the future we should rename all the header files to
`mercury_*.h', to avoid any possible name clashes with system or user
header files.
runtime/Mmake:
Add a new target `check_headers' to check that each
header file is syntactically valid in isolation.
runtime/imp.h:
runtime/mercury_float.h:
runtime/mercury_string.h:
runtime/mercury_types.h:
runtime/calls.h:
Move the code in "imp.h" into new header files.
"imp.h" now contains nothing but #includes.
runtime/conf.h.in:
runtime/*.h:
runtime/{label,prof,prof_mem}.c:
runtime/*.mod:
Update the #includes to reflect the new header file structure.
Add some missing header guards. Add some comments.
Put the correct years in most of the copyright notices.
runtime/heap.h:
Fix a bug: add #include "context.h", needed for
min_heap_reclamation_point.
runtime/context.h:
Fix a bug: add #include "memory.h", needed for MemoryZone.
Move the general description comment to the top of the file.
Fix the indentation of some comments. Add a couple of new comments.
runtime/context.mod:
Delete a couple of unnecessary declarations.
runtime/wrapper.mod:
Change the type used for memory sizes from `unsigned' to `size_t'.
Change the `-p' (primary cache size) option so that it is always
a size in kilobytes, rather than being schitzophrenic about
whether it is bytes or kilobytes.
runtime/regorder_base.h:
Removed, since it not used (and constitutes a
double-maintenance problem).
380 lines
13 KiB
C
380 lines
13 KiB
C
/*
|
|
** Copyright (C) 1997 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.
|
|
*/
|
|
|
|
/*
|
|
** context.h - defines Mercury multithreading stuff.
|
|
**
|
|
** A Context is like a thread. It contains a detstack, a nondetstack,
|
|
** the various pointers that refer to them, a succip, and a thread-
|
|
** resumption continuation. Contexts are initally stored in a free-list.
|
|
** When one is running, the Unix process that is executing it has a pointer
|
|
** to its context structure `this_context'. When a context suspends, it
|
|
** calls `save_context(context_ptr)' which copies the context from the
|
|
** various registers and global variables into the structure refered to
|
|
** by `context_ptr'. The context contains no rN or fN registers - all
|
|
** registers are "context save" (by analogy to caller-save).
|
|
**
|
|
** When a new context is created information is passed to the new context
|
|
** on the stack. The top stackframe of the current context is copied to
|
|
** become the first det stackframe in the new process. (XXX this will need
|
|
** fixing eventually to include the nondet frame as well.)
|
|
**
|
|
** Threads can migrate transparently between multiple Unix processes.
|
|
** This is implicit since all the state of the thread is allocated in
|
|
** shared memory, and all the heaps are also in shared memory.
|
|
**
|
|
** Each Unix process has its own heap and solutions heap (both allocated
|
|
** in shared memory). This makes GC harder, but enables heap allocation
|
|
** to be done without locking.
|
|
** Each context has a copy of the heap pointer that is taken when it is
|
|
** switched out. If the Unix process' heap pointer is the same as the
|
|
** copied one when the context is switched back in, then it is safe for
|
|
** the context to do heap reclamation on failure.
|
|
**
|
|
** If PARALLEL is not defined, then everything gets executed within a
|
|
** single Unix process. No locking is required. No shared memory is
|
|
** required. Since there is only one process, no signalling is needed
|
|
** to wake suspended processes.
|
|
*/
|
|
|
|
#ifndef CONTEXT_H
|
|
#define CONTEXT_H
|
|
|
|
#include "regs.h" /* for hp */
|
|
|
|
#include <sys/types.h> /* for pid_t */
|
|
|
|
#include "mercury_types.h" /* for Word */
|
|
#include "memory.h" /* for MemoryZone */
|
|
#include "spinlock.h" /* for SpinLock */
|
|
#include "goto.h" /* for GOTO() */
|
|
|
|
/*
|
|
** If we have parallelism switched on (PARALLEL is defined),
|
|
** then we define how many processes should be used.
|
|
** Ultimately this should be configurable through the
|
|
** MERCURY_OPTIONS environment variable.
|
|
*/
|
|
#ifdef PARALLEL
|
|
extern int numprocs;
|
|
#endif
|
|
|
|
/*
|
|
** The number of context structures initially allocated.
|
|
** This allocation does not include the stacks that they
|
|
** refer to - only the actual context structures.
|
|
** At the moment if we need more than this number of contexts,
|
|
** we just die. In the longer term, we need to allocate more.
|
|
*/
|
|
#define INITIAL_NUM_CONTEXTS 20
|
|
|
|
/*
|
|
** The field names that correspond to virtual machine registers:
|
|
** sp, maxfr & curfr
|
|
** are prefixed with `context_' so that they don't get replaced
|
|
** during macro expansion.
|
|
*/
|
|
typedef struct CONTEXT Context;
|
|
struct CONTEXT {
|
|
struct CONTEXT *next;
|
|
/*
|
|
** if this context is in the free-list `next' will point
|
|
** to the next free context. If this context is suspended
|
|
** waiting for a variable to become bound, `next' will point to
|
|
** the next waiting context. If this context is runnable but not
|
|
** currently running then `next' points to the next runnable
|
|
** context in the runqueue.
|
|
*/
|
|
Code *resume;
|
|
/*
|
|
** a pointer to the code at which execution should resume when
|
|
** this context is next scheduled.
|
|
*/
|
|
Code *context_succip;
|
|
/* succip for this context */
|
|
MemoryZone *detstack_zone;
|
|
/* pointer to the detstack_zone for this context */
|
|
Word *context_sp;
|
|
/* saved stack pointer for this context */
|
|
MemoryZone *nondetstack_zone;
|
|
/* pointer to the nondetstack_zone for this context */
|
|
Word *context_maxfr;
|
|
/* saved maxfr pointer for this context */
|
|
Word *context_curfr;
|
|
/* saved curfr pointer for this context */
|
|
Word *context_hp;
|
|
/* saved hp for this context */
|
|
Word *min_heap_reclamation_point;
|
|
/*
|
|
** this pointer marks the minimum value of hp to which we can
|
|
** truncate the heap on backtracking. See comments before the
|
|
** set_min_heap_reclamation_point macro (below).
|
|
*/
|
|
};
|
|
|
|
/*
|
|
** free_context_list is a global linked list of unused context
|
|
** structures. If the MemoryZone pointers are not NULL,
|
|
** then they point to allocated MemoryZones, which will
|
|
** need to be reinitialized, but have space allocated to
|
|
** them. (see comments in memory.h about reset_zone())
|
|
*/
|
|
extern Context **free_context_list_ptr;
|
|
|
|
/*
|
|
** the runqueue is a linked list of contexts that are
|
|
** runnable.
|
|
*/
|
|
extern Context **runqueue_ptr;
|
|
|
|
/*
|
|
** this_context is a pointer to the currently executing
|
|
** context. the fields of this_context are not necessarily
|
|
** in sync with the real values, since *this_context only
|
|
** gets updated when save_context() gets called.
|
|
*/
|
|
extern Context *this_context;
|
|
|
|
/* a pointer to a word used for the spinlock on the runqueue */
|
|
extern SpinLock *runqueue_lock;
|
|
|
|
/*
|
|
** a pointer to a word used for the spinlock on the free
|
|
** context list
|
|
*/
|
|
extern SpinLock *free_context_list_lock;
|
|
|
|
/*
|
|
** init_processes() forks new process (if necessary), and
|
|
** initializes the data-structures for managing the interactions
|
|
** between them.
|
|
*/
|
|
void init_processes(void);
|
|
|
|
/*
|
|
** init_process_context() creates a top-level context for
|
|
** the original process, and allocates a heap and a solutions-
|
|
** heap for each process.
|
|
*/
|
|
void init_process_context(void);
|
|
|
|
/*
|
|
** new_context() allocates and initializes a new context
|
|
** structure.
|
|
*/
|
|
Context *new_context(void);
|
|
|
|
/*
|
|
** delete_context(ptr) returns the context structure pointed
|
|
** to by ptr to the free list, and releases resources as
|
|
** necessary.
|
|
*/
|
|
void delete_context(Context *context);
|
|
|
|
/*
|
|
** flounder() aborts with a runtime error message. It is called if
|
|
** the runqueue becomes empty and none of the running processes are
|
|
** working - ie the computation has floundered.
|
|
*/
|
|
void flounder(void);
|
|
|
|
/*
|
|
** procid[N] is the process id of the Nth process.
|
|
** procid[my_procnum] == getpid() == my_procid.
|
|
*/
|
|
extern pid_t *procid;
|
|
|
|
/*
|
|
** procwaiting[N] is true if the process procid[N] is
|
|
** suspended because the runqueue was empty when it
|
|
** called runnext().
|
|
** Although we semantically want bools here, we use
|
|
** words to ensure coherency. Since a bool may be
|
|
** smaller than a word, storing a bool may be implemented
|
|
** in a coherency-breaking manner.
|
|
** (Assuming that Words can be read and written in a
|
|
** coherent manner is sufficiently important in terms of
|
|
** simplifying the synchronization mechanisms, that
|
|
** we really need to do so -- or so says Tom, at least.
|
|
** I remain unconvinced. -Fergus.)
|
|
*/
|
|
typedef Word AtomicBool;
|
|
extern AtomicBool *procwaiting;
|
|
|
|
/*
|
|
** my_procnum is the number of the current process.
|
|
** my_procnum == 0 is the original parent process.
|
|
*/
|
|
extern int my_procnum;
|
|
extern pid_t my_procid;
|
|
|
|
/*
|
|
** The minimum value of hp to which the current context
|
|
** may truncate the heap on backtracking. see the comments
|
|
** below next to the set_min_heap_reclamation_point macro.
|
|
*/
|
|
extern Word *min_heap_reclamation_point;
|
|
|
|
/* do a context switch */
|
|
Declare_entry(do_runnext);
|
|
#define runnext() GOTO(ENTRY(do_runnext));
|
|
|
|
/*
|
|
** schedule(Context *cptr, Code *resume):
|
|
** setup a call to do_schedule which
|
|
** adds the context pointed to by `cptr' to the runqueue then
|
|
** branches to `resume'.
|
|
*/
|
|
Declare_entry(do_schedule);
|
|
extern Context *do_schedule_cptr;
|
|
extern Code *do_schedule_resume;
|
|
#define schedule(cptr, resume) do { \
|
|
do_schedule_cptr = (Context *)(cptr); \
|
|
do_schedule_resume = (Code *)(resume); \
|
|
GOTO(ENTRY(do_schedule)); \
|
|
} while(0)
|
|
|
|
/*
|
|
** fork_new_context(Code *child, Code *parent, int numslots):
|
|
** create a new context to execute the code at `child', and
|
|
** copy the topmost `numslots' from the current stackframe.
|
|
** The new context gets put on the runqueue, and the current
|
|
** context resumes at `parent'.
|
|
*/
|
|
#define fork_new_context(child, parent, numslots) do { \
|
|
Context *fork_new_context_context; \
|
|
int fork_new_context_i; \
|
|
fork_new_context_context = new_context(); \
|
|
for (fork_new_context_i = (numslots) ; \
|
|
fork_new_context_i > 0 ; \
|
|
fork_new_context_i--) { \
|
|
*(fork_new_context_context->sp) = \
|
|
detstackvar(fork_new_context_i); \
|
|
fork_new_context_context->sp++; \
|
|
} \
|
|
c->resume = (child); \
|
|
schedule(c, (parent)); \
|
|
} while (0)
|
|
|
|
#ifndef CONSERVATIVE_GC
|
|
|
|
/*
|
|
** To figure out the maximum amount of heap we can reclaim on backtracking,
|
|
** we compare hp with the context_hp.
|
|
**
|
|
** If context_hp == NULL then this is the first time this context has been
|
|
** scheduled, so the furthest back down the heap we can reclaim is to the
|
|
** current value of hp.
|
|
**
|
|
** If hp > context_hp, another context has allocated data on the heap since
|
|
** we were last scheduled, so the furthest back that we can reclaim is to
|
|
** the current value of hp, so we set min_heap_reclamation_point and the
|
|
** field of the same name in our context structure.
|
|
**
|
|
** If hp < context_hp, then another context has truncated the heap on failure.
|
|
** For this to happen, it must be the case that last time we were scheduled,
|
|
** that other context was the last one to allocate data on the heap, and we
|
|
** did not allocate any heap during that period of execution. That being the
|
|
** case, the furthest back to which we can reset the heap is to the current
|
|
** value of hp. This is a conservative approximation - it is possible that
|
|
** the current value of hp is the same as some previous value that we held,
|
|
** and we are now contiguous with our older data, so this algorithm will lead
|
|
** to holes in the heap, though GC will reclaim these.
|
|
**
|
|
** If hp == context_hp then no other process has allocated any heap since we
|
|
** were last scheduled, so we can proceed as if we had not stopped, and the
|
|
** furthest back that we can backtrack is the same as it was last time we
|
|
** were executing.
|
|
*/
|
|
#define set_min_heap_reclamation_point(ctxt) do { \
|
|
if (hp != (ctxt)->context_hp \
|
|
|| (ctxt)->context_hp == NULL) \
|
|
{ \
|
|
min_heap_reclamation_point = hp; \
|
|
(ctxt)->min_heap_reclamation_point = hp;\
|
|
} \
|
|
else \
|
|
{ \
|
|
min_heap_reclamation_point = \
|
|
(ctxt)->min_heap_reclamation_point;\
|
|
} \
|
|
} while (0)
|
|
|
|
#define save_hp_in_context(ctxt) do { \
|
|
(ctxt)->context_hp = hp; \
|
|
(ctxt)->min_heap_reclamation_point = \
|
|
min_heap_reclamation_point; \
|
|
} while (0)
|
|
|
|
#else
|
|
|
|
#define set_min_heap_reclamation_point(ctxt) do { } while (0)
|
|
|
|
#define save_hp_in_context(ctxt) do { } while (0)
|
|
|
|
#endif
|
|
|
|
#define load_context(cptr) do { \
|
|
Context *load_context_c; \
|
|
load_context_c = (cptr); \
|
|
succip = load_context_c->context_succip; \
|
|
detstack_zone = load_context_c->detstack_zone; \
|
|
sp = load_context_c->context_sp; \
|
|
nondetstack_zone = load_context_c->nondetstack_zone; \
|
|
maxfr = load_context_c->context_maxfr; \
|
|
curfr = load_context_c->context_curfr; \
|
|
set_min_heap_reclamation_point(load_context_c); \
|
|
} while (0)
|
|
|
|
#define save_context(cptr) do { \
|
|
Context *save_context_c; \
|
|
save_context_c = (cptr); \
|
|
save_context_c->context_succip = succip; \
|
|
save_context_c->detstack_zone = detstack_zone; \
|
|
save_context_c->context_sp = sp; \
|
|
save_context_c->nondetstack_zone = nondetstack_zone; \
|
|
save_context_c->context_maxfr = maxfr; \
|
|
save_context_c->context_curfr = curfr; \
|
|
save_hp_in_context(save_context_c); \
|
|
} while (0)
|
|
|
|
/*
|
|
** The following two macros join_and_terminate and join_and_continue
|
|
** both take a `sync_term' which has the following structure:
|
|
** sync_term[SYNC_TERM_LOCK] is a SpinLock
|
|
** sync_term[SYNC_TERM_COUNTER] is a counter for the number of
|
|
** processes that need to synchronize before the
|
|
** parent can proceed.
|
|
** sync_term[SYNC_TERM_PARENT] is either NULL or a pointer to the
|
|
** context of the parent process. If it is non-null
|
|
** then the last process to arrive (the one that
|
|
** decrements sync_term[SYNC_TERM_COUNTER] to 0)
|
|
** must wake the parent.
|
|
** These terms are allocated and manipulated as normal Mercury terms by
|
|
** generated Mercury code.
|
|
*/
|
|
|
|
#define SYNC_TERM_LOCK 0
|
|
#define SYNC_TERM_COUNTER 1
|
|
#define SYNC_TERM_PARENT 2
|
|
|
|
extern Word *do_join_and_terminate_sync_term;
|
|
#define join_and_terminate(sync_term) do { \
|
|
do_join_and_terminate_sync_term = (Word *)(sync_term); \
|
|
GOTO(ENTRY(do_join_and_terminate)); \
|
|
} while (0)
|
|
|
|
extern Word *do_join_and_continue_sync_term;
|
|
extern Code *do_join_and_continue_where_to;
|
|
#define join_and_continue(sync_term, where_to) do { \
|
|
do_join_and_continue_sync_term = (Word *)(sync_term); \
|
|
do_join_and_continue_where_to = (Code *)(where_to); \
|
|
GOTO(ENTRY(do_join_and_continue)); \
|
|
} while (0)
|
|
|
|
#endif
|
|
|