From: Matt Birkholz Date: Sat, 20 Dec 2014 15:32:10 +0000 (-0700) Subject: smp: Synchronize processors for GC. X-Git-Url: https://birchwood-abbey.net/git?a=commitdiff_plain;h=a15960e49cd22b5b400c6ced5afeb4689ad1b129;p=mit-scheme.git smp: Synchronize processors for GC. --- diff --git a/src/microcode/fasdump.c b/src/microcode/fasdump.c index e04c5b7fa..bbe4e9087 100644 --- a/src/microcode/fasdump.c +++ b/src/microcode/fasdump.c @@ -108,9 +108,13 @@ static bool write_fasl_file #ifndef ENABLE_SMP #define HEAP_START heap_start #define HEAP_END heap_end +#define SMP_GC_START() +#define SMP_GC_FINISHED() #else #define HEAP_START shared_heap_start #define HEAP_END shared_heap_end +#define SMP_GC_START smp_gc_start +#define SMP_GC_FINISHED smp_gc_finished #endif /* FASDUMP: @@ -149,6 +153,7 @@ at by compiled code are ignored (and discarded).") error_bad_range_arg (2); transaction_record_action (tat_always, close_fasl_file, (&ff_info)); + SMP_GC_START (); open_tospace (HEAP_START); /* This must be _before_ the call to initialize_fixups(): */ transaction_record_action (tat_abort, abort_fasdump, 0); @@ -192,6 +197,7 @@ at by compiled code are ignored (and discarded).") ok = ((write_fasl_header (fh, (ff_info . handle))) && (save_tospace (save_tospace_write, (&ff_info)))); + SMP_GC_FINISHED (); transaction_commit (); /* 1 */ COMPARE_GC_VARS (); @@ -576,6 +582,7 @@ When the file is reloaded, PROCEDURE is called with an argument of #F.") to = Free; Primitive_GC_If_Needed (6); #else + smp_gc_start (); ENTER_CRITICAL_SECTION ("band dump"); diff --git a/src/microcode/fasload.c b/src/microcode/fasload.c index 75d6b72fa..d9ede4e9c 100644 --- a/src/microcode/fasload.c +++ b/src/microcode/fasload.c @@ -202,6 +202,9 @@ can, however, be any file which can be loaded with BINARY-FASLOAD.") CHECK_ARG (1, STRING_P); canonicalize_primitive_context (); +#ifdef ENABLE_SMP + smp_gc_start (); +#endif result = (read_band_file (ARG_REF (1))); /* Reset implementation state parameters. */ diff --git a/src/microcode/memmag.c b/src/microcode/memmag.c index 50db38bd1..01a68fdf7 100644 --- a/src/microcode/memmag.c +++ b/src/microcode/memmag.c @@ -472,6 +472,13 @@ the primitive GC daemons before returning.") #else + while (true) { + if (smp_gc_started ()) + break; + if (Free == heap_start) + PRIMITIVE_RETURN (ULONG_TO_FIXNUM (HEAP_AVAILABLE)); + } + open_tospace (shared_heap_start); #endif /* ENABLE_SMP */ diff --git a/src/microcode/ossmp.h b/src/microcode/ossmp.h index 5adef326b..449687caf 100644 --- a/src/microcode/ossmp.h +++ b/src/microcode/ossmp.h @@ -35,6 +35,7 @@ typedef enum { PROCESSOR_NEW, PROCESSOR_RUNNING, PROCESSOR_PAUSED, + PROCESSOR_GC_WAIT, PROCESSOR_DEAD } processor_state_t; @@ -61,6 +62,13 @@ extern processor_t *processors; extern __thread processor_t *self; extern void smp_initialize (int processor_count); + +extern void smp_gc_start (void); +extern bool smp_gc_started (void); +extern void smp_gc_finished (void); + +extern void smp_kill_gc (pthread_t); + #endif #endif /* SCM_OSSMP_H */ diff --git a/src/microcode/prossmp.c b/src/microcode/prossmp.c index bfcbe5700..944224d46 100644 --- a/src/microcode/prossmp.c +++ b/src/microcode/prossmp.c @@ -38,6 +38,19 @@ USA. /* The chain of processors, starting with processor0 -- main()'s thread: */ processor_t *processors; +/* The mutex that serializes processor state changes, including the + gc_processor variable. */ +pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* The processor running the garbage collector. */ +processor_t *gc_processor = NULL; + +/* The condition variable on which GC-WAIT processors wait. */ +static pthread_cond_t finished = PTHREAD_COND_INITIALIZER; + +/* The condition variable on which the gc_processor waits. */ +static pthread_cond_t ready = PTHREAD_COND_INITIALIZER; + /* The mutex that serializes thread switches. */ static pthread_mutex_t thread_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -68,6 +81,61 @@ trace (const char * format, ...) #endif +static void +fatal (const char * format, ...) +{ + va_list args; + va_start (args, format); + voutf_fatal (format, args); + va_end (args); + outf_flush_fatal (); + self->free_pointer = Free; + self->stack_pointer = stack_pointer; + self->history_register = history_register; + self->state = PROCESSOR_DEAD; + pthread_exit ((void*)self); + /* NOTREACHED */ +} + +static void +mutex_lock (pthread_mutex_t *mutex) +{ + int err = pthread_mutex_lock (mutex); + if (err) + fatal ("\n;%d pthread_mutex_lock failed: %d", self->id, err); +} + +static void +mutex_unlock (pthread_mutex_t *mutex) +{ + int err = pthread_mutex_unlock (mutex); + if (err) + fatal ("\n;%d pthread_mutex_unlock failed: %d", self->id, err); +} + +static void +cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + int err = pthread_cond_wait (cond, mutex); + if (err) + fatal ("\n;%d pthread_cond_wait failed: %d", self->id, err); +} + +static void +cond_broadcast (pthread_cond_t *cond) +{ + int err = pthread_cond_broadcast (cond); + if (err) + fatal ("\n;%d pthread_cond_broadcast failed: %d", self->id, err); +} + +static void +create (int id, pthread_t *thread, void *(*start_routine) (void *), void *arg) +{ + int err = pthread_create (thread, NULL, start_routine, arg); + if (err) + fatal ("\n;%d pthread_create failed: %d", id, err); +} #endif DEFINE_PRIMITIVE ("SMP-ID", Prim_smp_id, 0, 0, "(SMP-ID)\n\ @@ -188,7 +256,7 @@ make_processors (int id) new = malloc (sizeof (processor_t)); if (new == NULL) { - outf_fatal ("\n;%d could not malloc processor_t\n", id); + outf_fatal ("\n;%d could not malloc processor_t", id); outf_flush_fatal (); Microcode_Termination (TERM_NO_SPACE); } @@ -205,15 +273,7 @@ make_processors (int id) processors = new; if (id != 0) - { - int err = pthread_create (&new->pthread, NULL, &work, new); - if (err) - { - outf_fatal ("pthread_create failed: %d\n", err); - outf_flush_fatal (); - exit (1); - } - } + create (id, &new->pthread, &work, new); trace (";%d heap: 0x%0lx-0x%0lx", id, (ulong)new->heap_start, (ulong)new->heap_end); @@ -223,7 +283,161 @@ make_processors (int id) if (id > 0) make_processors (id - 1); } + +static bool +all_in (processor_state_t s) +{ + processor_t *p; + bool all = true; + for (p = processors; p != NULL; p = p->next) + if (p->state != s) + { + all = false; + break; + } + return (all); +} + +static void +export_state (void) +{ + self->free_pointer = Free; + self->stack_pointer = stack_pointer; + self->history_register = history_register; +} + +static void +import_state (void) +{ + Free = self->free_pointer; + history_register = self->history_register; + heap_alloc_limit = heap_end - heap_reserved; +} + +static void +gc_wait (void) +{ + trace (";%d Waiting.", self->id); + self->state = PROCESSOR_GC_WAIT; + export_state (); + if (all_in (PROCESSOR_GC_WAIT)) + { + trace (";%d ALL waiting.", self->id); + cond_broadcast (&ready); + } + cond_wait (&finished, &state_mutex); + + assert (self->state == PROCESSOR_RUNNING); + trace (";%d Done waiting.", self->id); + import_state (); +} + +static void +interrupt_others (void) +{ + processor_t *p; + + for (p = processors; p != NULL; p = p->next) + if (p != self && p->state != PROCESSOR_GC_WAIT) + { + trace (";%d sending SIGUSR2 to pthread %#x", self->id, p->pthread); + smp_kill_gc (p->pthread); + } +} + +void +smp_gc_start (void) +{ + /* Wait to become gc_processor. If a GC has already started, wait + until it is finished, then try again (forever). Called by + primitives that need to be the gc_processor. gc_processor == + self (if :-) upon return. */ + + trace (";%d GC start.", self->id); + mutex_lock (&state_mutex); + while (gc_processor != NULL) + { +#ifdef ENABLE_DEBUGGING_TOOLS + int GCer = gc_processor->id; +#endif + trace (";%d GC started by %d.", self->id, GCer); + gc_wait (); + trace (";%d GC finished by %d.", self->id, GCer); + } + gc_processor = self; + trace (";%d GC initiated.", self->id); + self->state = PROCESSOR_GC_WAIT; + interrupt_others (); + export_state (); + if (! all_in (PROCESSOR_GC_WAIT)) + { + trace (";%d GC waiting.", self->id); + cond_wait (&ready, &state_mutex); + } + trace (";%d GC ready (all waiting).", self->id); + mutex_unlock (&state_mutex); +} + +bool +smp_gc_started (void) +{ + /* Wait to become gc_processor. If a GC has already started, wait + until it is finished, then return false. Else this thread is now + the gc_processor, so return true. */ + trace (";%d GCany start.", self->id); + mutex_lock (&state_mutex); + if (gc_processor != NULL) + { +#ifdef ENABLE_DEBUGGING_TOOLS + int GCer = gc_processor->id; +#endif + trace (";%d GCany started by %d.", self->id, GCer); + gc_wait (); + trace (";%d GCany finished by %d.", self->id, GCer); + mutex_unlock (&state_mutex); + return (false); + } + gc_processor = self; + trace (";%d GCany initiated.", self->id); + self->state = PROCESSOR_GC_WAIT; + interrupt_others (); + export_state (); + if (! all_in (PROCESSOR_GC_WAIT)) + { + trace (";%d GCany waiting.", self->id); + cond_wait (&ready, &state_mutex); + } + assert (all_in (PROCESSOR_GC_WAIT)); + trace (";%d GCany ready (all waiting).", self->id); + mutex_unlock (&state_mutex); + return (true); +} + +void +smp_gc_finished (void) +{ + /* Called by the primitive gc daemon and primitive-fasdump to + broadcast the finished condition. */ + trace (";%d smp_gc_finished", self->id); + mutex_lock (&state_mutex); + assert (gc_processor == self); + assert (all_in (PROCESSOR_GC_WAIT)); + gc_processor = NULL; + { + processor_t *P = processors; + while (P != NULL) + { + if (P != self) + P->free_pointer = P->heap_start; + P->state = PROCESSOR_RUNNING; + P = P->next; + } + } + cond_broadcast (&finished); + mutex_unlock (&state_mutex); +} #endif + DEFINE_PRIMITIVE ("SMP-PAUSE", Prim_smp_pause, 0, 0, "(SMP-PAUSE)\n\ Pause a new processor.") @@ -239,3 +453,33 @@ Pause a new processor.") #endif PRIMITIVE_RETURN (UNSPECIFIC); } + +DEFINE_PRIMITIVE ("SMP-GC-WAIT", Prim_smp_gc_wait, 0, 0, "(SMP-GC-WAIT)\n\ +Put the current processor in the GC-WAIT state.\n\ +Called by the global-gc interrupt handler.") +{ + PRIMITIVE_HEADER (0); +#ifdef ENABLE_SMP + trace (";%d SMP-GC-Wait.", self->id); + CLEAR_INTERRUPT (INT_Global_GC); + mutex_lock (&state_mutex); + gc_wait (); + mutex_unlock (&state_mutex); +#else + signal_error_from_primitive (ERR_UNIMPLEMENTED_PRIMITIVE); +#endif + PRIMITIVE_RETURN (UNSPECIFIC); +} + +DEFINE_PRIMITIVE ("SMP-GC-FINISHED", Prim_smp_gc_finished, 0, 0, + "(SMP-GC-FINISHED)\n\ +Broadcast that the gc_finished condition variable is true.\n\ +Called at the end of the primitive GC daemon.") +{ + PRIMITIVE_HEADER (0); +#ifdef ENABLE_SMP + trace (";%d SMP-GC-Finished.", self->id); + smp_gc_finished (); +#endif + PRIMITIVE_RETURN (UNSPECIFIC); +} diff --git a/src/microcode/purify.c b/src/microcode/purify.c index a1744deba..e4d6c11ae 100644 --- a/src/microcode/purify.c +++ b/src/microcode/purify.c @@ -34,9 +34,11 @@ USA. #ifndef ENABLE_SMP #define HEAP_START heap_start #define HEAP_END heap_end +#define SMP_GC_START() #else #define HEAP_START shared_heap_start #define HEAP_END shared_heap_end +#define SMP_GC_START smp_gc_start #endif static void purify (SCHEME_OBJECT); @@ -66,6 +68,7 @@ PURE? is ignored.") ENTER_CRITICAL_SECTION ("purify"); heap_reserved = safety_margin; + SMP_GC_START (); purify (object); Will_Push (CONTINUATION_SIZE); diff --git a/src/microcode/uxsig.c b/src/microcode/uxsig.c index 9722a91dd..5b6347c9d 100644 --- a/src/microcode/uxsig.c +++ b/src/microcode/uxsig.c @@ -31,6 +31,7 @@ USA. #include "osctty.h" #include "ostty.h" #include "ostop.h" +#include "ossmp.h" #include "uxtrap.h" #include "uxsig.h" #include "uxutil.h" @@ -505,6 +506,21 @@ DEFUN_STD_HANDLER (sighnd_console_resize, request_console_resize_interrupt (); }) +#ifdef ENABLE_SMP + +static +DEFUN_STD_HANDLER (sighnd_global_gc, +{ + REQUEST_INTERRUPT (INT_Global_GC); +}) + +void +smp_kill_gc (pthread_t thread) +{ + pthread_kill (thread, SIGUSR2); +} + +#endif /* ENABLE_SMP */ /* The following conditionalization would more naturally be expressed by conditionalizing the code inside the handler, but the Sun @@ -671,8 +687,12 @@ UX_initialize_signals (void) bind_handler (SIGALRM, sighnd_timer); bind_handler (SIGVTALRM, sighnd_timer); bind_handler (SIGUSR1, sighnd_save_then_terminate); +#ifndef ENABLE_SMP #ifdef HAVE_NICE bind_handler (SIGUSR2, sighnd_renice); +#endif +#else + bind_handler (SIGUSR2, sighnd_global_gc); #endif bind_handler (SIGCHLD, sighnd_dead_subprocess); /* If this signal is ignored, then the system call that would have diff --git a/src/runtime/gcdemn.scm b/src/runtime/gcdemn.scm index 12e0e95d5..bb70cfa0c 100644 --- a/src/runtime/gcdemn.scm +++ b/src/runtime/gcdemn.scm @@ -31,7 +31,7 @@ USA. (define (initialize-package!) (set! primitive-gc-daemons (make-queue)) - (set! trigger-primitive-gc-daemons! (make-trigger primitive-gc-daemons)) + (set! %trigger-primitive-gc-daemons! (make-trigger primitive-gc-daemons)) (set! add-primitive-gc-daemon! (make-adder primitive-gc-daemons)) (set! gc-daemons (make-queue)) (set! trigger-gc-daemons! (make-trigger gc-daemons)) @@ -47,9 +47,14 @@ USA. ;;; allocate any storage and they must be prepared to run at times ;;; when many data structures are not consistent. (define primitive-gc-daemons) -(define trigger-primitive-gc-daemons!) +(define %trigger-primitive-gc-daemons!) (define add-primitive-gc-daemon!) +(define (trigger-primitive-gc-daemons!) + (%trigger-primitive-gc-daemons!) + (if ((ucode-primitive get-primitive-address 2) 'SMP-GC-FINISHED #f) + ((ucode-primitive smp-gc-finished 0)))) + ;;; GC-DAEMONS are executed after each GC from an interrupt handler. ;;; This interrupt handler has lower priority than the GC interrupt, ;;; which guarantees that these daemons will not be run inside of diff --git a/src/runtime/intrpt.scm b/src/runtime/intrpt.scm index 866673bc0..f8eddfc66 100644 --- a/src/runtime/intrpt.scm +++ b/src/runtime/intrpt.scm @@ -123,6 +123,10 @@ USA. ;; prevent us from getting into a loop just running the daemons. (clear-interrupts! interrupt-bit/after-gc)) +(define (global-gc-interrupt-handler interrupt-code interrupt-enables) + interrupt-code interrupt-enables + ((ucode-primitive smp-gc-wait 0))) + (define event:console-resize) (define (console-resize-handler interrupt-code interrupt-enables) interrupt-code interrupt-enables @@ -230,8 +234,12 @@ USA. interrupt-mask/none) (vector-set! interrupt-mask-vector gc-slot - ;; interrupt-mask/none - (fix:lsh 1 global-gc-slot)) + interrupt-mask/none) + + (vector-set! system-interrupt-vector global-gc-slot + global-gc-interrupt-handler) + (vector-set! interrupt-mask-vector global-gc-slot + interrupt-mask/none) (vector-set! system-interrupt-vector timer-slot timer-interrupt-handler)