diff options
author | Andy Wingo <wingo@pobox.com> | 2016-10-26 22:32:51 +0200 |
---|---|---|
committer | Andy Wingo <wingo@pobox.com> | 2016-10-26 22:50:26 +0200 |
commit | c957ec7ab0f0a028910dc737e12191f7bdc1ca93 (patch) | |
tree | 5949cfd1de4a7eecfacb35611430498a53b4c2fe | |
parent | f3bfe29235199e12b961c3fd1fa92666ad031d0d (diff) |
Use atomics for async interrupts
* libguile/__scm.h (SCM_TICK): Always define as scm_async_tick().
* libguile/error.c (scm_syserror, scm_syserror_msg):
* libguile/fports.c (fport_read, fport_write):
* libguile/_scm.h (SCM_SYSCALL): Replace SCM_ASYNC_TICK with
scm_async_tick ().
(SCM_ASYNC_TICK, SCM_ASYNC_TICK_WITH_CODE)
(SCM_ASYNC_TICK_WITH_GUARD_CODE): Remove internal definitions. We
inline into vm-engine.c, the only place where it matters.
* libguile/async.h:
* libguile/async.c (scm_async_tick, scm_i_setup_sleep):
(scm_i_reset_sleep, scm_system_async_mark_for_thread):
* libguile/threads.h (struct scm_thread_wake_data):
* libguile/threads.h (scm_i_thread):
* libguile/threads.c (block_self, guilify_self_1, scm_std_select):
Rewrite to use sequentially-consistent atomic references.
* libguile/atomics-internal.h (scm_atomic_set_pointer):
(scm_atomic_ref_pointer): New definitions.
* libguile/finalizers.c (queue_finalizer_async): We can allocate, so
just use scm_system_async_mark_for_thread instead of the set-cdr!
shenanigans.
* libguile/scmsigs.c (take_signal):
* libguile/gc.c (queue_after_gc_hook): Adapt to new asyncs mechanism.
Can't allocate but we're just manipulating the current thread when no
other threads are running so we should be good.
* libguile/vm-engine.c (VM_HANDLE_INTERRUPTS): Inline the async_tick
business.
-rw-r--r-- | libguile/__scm.h | 4 | ||||
-rw-r--r-- | libguile/_scm.h | 24 | ||||
-rw-r--r-- | libguile/async.c | 181 | ||||
-rw-r--r-- | libguile/async.h | 1 | ||||
-rw-r--r-- | libguile/atomics-internal.h | 27 | ||||
-rw-r--r-- | libguile/error.c | 4 | ||||
-rw-r--r-- | libguile/finalizers.c | 26 | ||||
-rw-r--r-- | libguile/fports.c | 4 | ||||
-rw-r--r-- | libguile/gc.c | 9 | ||||
-rw-r--r-- | libguile/init.c | 2 | ||||
-rw-r--r-- | libguile/scmsigs.c | 5 | ||||
-rw-r--r-- | libguile/threads.c | 20 | ||||
-rw-r--r-- | libguile/threads.h | 18 | ||||
-rw-r--r-- | libguile/vm-engine.c | 19 |
14 files changed, 152 insertions, 192 deletions
diff --git a/libguile/__scm.h b/libguile/__scm.h index 31e395285..1ea4822a6 100644 --- a/libguile/__scm.h +++ b/libguile/__scm.h @@ -474,11 +474,7 @@ typedef long SCM_STACKITEM; #define SCM_STACK_PTR(ptr) ((SCM_STACKITEM *) (void *) (ptr)) -#ifdef BUILDING_LIBGUILE -#define SCM_TICK SCM_ASYNC_TICK -#else #define SCM_TICK scm_async_tick () -#endif diff --git a/libguile/_scm.h b/libguile/_scm.h index 60ad08295..e482b7e31 100644 --- a/libguile/_scm.h +++ b/libguile/_scm.h @@ -100,7 +100,7 @@ errno = 0; \ line; \ if (EVMSERR == errno && (vaxc$errno>>3)==(SS$_CONTROLC>>3)) \ - SCM_ASYNC_TICK; \ + scm_async_tick (); \ else \ break; \ } \ @@ -119,7 +119,7 @@ line; \ if (errno == EINTR) \ { \ - SCM_ASYNC_TICK; \ + scm_async_tick (); \ errno = EINTR; \ } \ } \ @@ -225,26 +225,6 @@ void scm_ia64_longjmp (scm_i_jmp_buf *, int); -#define SCM_ASYNC_TICK_WITH_GUARD_CODE(thr, pre, post) \ - do \ - { \ - if (SCM_UNLIKELY (thr->pending_asyncs)) \ - { \ - pre; \ - scm_async_tick (); \ - post; \ - } \ - } \ - while (0) - -#define SCM_ASYNC_TICK_WITH_CODE(thr, stmt) \ - SCM_ASYNC_TICK_WITH_GUARD_CODE (thr, stmt, (void) 0) -#define SCM_ASYNC_TICK \ - SCM_ASYNC_TICK_WITH_CODE (SCM_I_CURRENT_THREAD, (void) 0) - - - - #if (defined __GNUC__) # define SCM_NOINLINE __attribute__ ((__noinline__)) #else diff --git a/libguile/async.c b/libguile/async.c index 1cf105881..aa4f508b7 100644 --- a/libguile/async.c +++ b/libguile/async.c @@ -24,6 +24,7 @@ #endif #include "libguile/_scm.h" +#include "libguile/atomics-internal.h" #include "libguile/eval.h" #include "libguile/throw.h" #include "libguile/root.h" @@ -50,142 +51,51 @@ * * Each thread has a list of 'activated asyncs', which is a normal * Scheme list of procedures with zero arguments. When a thread - * executes a SCM_ASYNC_TICK statement (which is included in SCM_TICK), - * it will call all procedures on this list. + * executes an scm_async_tick (), it will call all procedures on this + * list. */ - - -static scm_i_pthread_mutex_t async_mutex = SCM_I_PTHREAD_MUTEX_INITIALIZER; - -/* System asyncs. */ - void scm_async_tick (void) { scm_i_thread *t = SCM_I_CURRENT_THREAD; SCM asyncs; - /* Reset pending_asyncs even when asyncs are blocked and not really - executed since this will avoid future futile calls to this - function. When asyncs are unblocked again, this function is - invoked even when pending_asyncs is zero. - */ - - scm_i_scm_pthread_mutex_lock (&async_mutex); - t->pending_asyncs = 0; - if (t->block_asyncs == 0) - { - asyncs = t->active_asyncs; - t->active_asyncs = SCM_EOL; - } - else - asyncs = SCM_EOL; - scm_i_pthread_mutex_unlock (&async_mutex); + if (t->block_asyncs) + return; - while (scm_is_pair (asyncs)) + asyncs = scm_atomic_swap_scm (&t->pending_asyncs, SCM_EOL); + while (!scm_is_null (asyncs)) { - SCM next = SCM_CDR (asyncs); - SCM_SETCDR (asyncs, SCM_BOOL_F); - scm_call_0 (SCM_CAR (asyncs)); + SCM next = scm_cdr (asyncs); + scm_call_0 (scm_car (asyncs)); + scm_set_cdr_x (asyncs, SCM_BOOL_F); asyncs = next; } } -void -scm_i_queue_async_cell (SCM c, scm_i_thread *t) -{ - SCM sleep_object; - scm_i_pthread_mutex_t *sleep_mutex; - int sleep_fd; - SCM p; - - scm_i_scm_pthread_mutex_lock (&async_mutex); - p = t->active_asyncs; - SCM_SETCDR (c, SCM_EOL); - if (!scm_is_pair (p)) - t->active_asyncs = c; - else - { - SCM pp; - while (scm_is_pair (pp = SCM_CDR (p))) - { - if (scm_is_eq (SCM_CAR (p), SCM_CAR (c))) - { - scm_i_pthread_mutex_unlock (&async_mutex); - return; - } - p = pp; - } - SCM_SETCDR (p, c); - } - t->pending_asyncs = 1; - sleep_object = t->sleep_object; - sleep_mutex = t->sleep_mutex; - sleep_fd = t->sleep_fd; - scm_i_pthread_mutex_unlock (&async_mutex); - - if (sleep_mutex) - { - /* By now, the thread T might be out of its sleep already, or - might even be in the next, unrelated sleep. Interrupting it - anyway does no harm, however. - - The important thing to prevent here is to signal sleep_cond - before T waits on it. This can not happen since T has - sleep_mutex locked while setting t->sleep_mutex and will only - unlock it again while waiting on sleep_cond. - */ - scm_i_scm_pthread_mutex_lock (sleep_mutex); - scm_i_pthread_cond_signal (&t->sleep_cond); - scm_i_pthread_mutex_unlock (sleep_mutex); - } - - if (sleep_fd >= 0) - { - char dummy = 0; - - /* Likewise, T might already been done with sleeping here, but - interrupting it once too often does no harm. T might also - not yet have started sleeping, but this is no problem either - since the data written to a pipe will not be lost, unlike a - condition variable signal. */ - full_write (sleep_fd, &dummy, 1); - } - - /* This is needed to protect sleep_mutex. - */ - scm_remember_upto_here_1 (sleep_object); -} - int scm_i_setup_sleep (scm_i_thread *t, SCM sleep_object, scm_i_pthread_mutex_t *sleep_mutex, int sleep_fd) { - int pending; + struct scm_thread_wake_data *wake; - scm_i_scm_pthread_mutex_lock (&async_mutex); - pending = t->pending_asyncs; - if (!pending) - { - t->sleep_object = sleep_object; - t->sleep_mutex = sleep_mutex; - t->sleep_fd = sleep_fd; - } - scm_i_pthread_mutex_unlock (&async_mutex); - return pending; + wake = scm_gc_typed_calloc (struct scm_thread_wake_data); + wake->object = sleep_object; + wake->mutex = sleep_mutex; + wake->fd = sleep_fd; + + scm_atomic_set_pointer ((void **)&t->wake, wake); + + return !scm_is_null (scm_atomic_ref_scm (&t->pending_asyncs)); } void scm_i_reset_sleep (scm_i_thread *t) { - scm_i_scm_pthread_mutex_lock (&async_mutex); - t->sleep_object = SCM_BOOL_F; - t->sleep_mutex = NULL; - t->sleep_fd = -1; - scm_i_pthread_mutex_unlock (&async_mutex); + scm_atomic_set_pointer ((void **)&t->wake, NULL); } SCM_DEFINE (scm_system_async_mark_for_thread, "system-async-mark", 1, 1, 0, @@ -200,13 +110,9 @@ SCM_DEFINE (scm_system_async_mark_for_thread, "system-async-mark", 1, 1, 0, "signal handlers.") #define FUNC_NAME s_scm_system_async_mark_for_thread { - /* The current thread might not have a handle yet. This can happen - when the GC runs immediately before allocating the handle. At - the end of that GC, a system async might be marked. Thus, we can - not use scm_current_thread here. - */ - scm_i_thread *t; + SCM asyncs; + struct scm_thread_wake_data *wake; if (SCM_UNBNDP (thread)) t = SCM_I_CURRENT_THREAD; @@ -217,7 +123,48 @@ SCM_DEFINE (scm_system_async_mark_for_thread, "system-async-mark", 1, 1, 0, SCM_MISC_ERROR ("thread has already exited", SCM_EOL); t = SCM_I_THREAD_DATA (thread); } - scm_i_queue_async_cell (scm_cons (proc, SCM_BOOL_F), t); + + asyncs = scm_atomic_ref_scm (&t->pending_asyncs); + do + if (scm_is_true (scm_c_memq (proc, asyncs))) + return SCM_UNSPECIFIED; + while (!scm_atomic_compare_and_swap_scm (&t->pending_asyncs, &asyncs, + scm_cons (proc, asyncs))); + + /* At this point the async is enqueued. However if the thread is + sleeping, we have to wake it up. */ + if ((wake = scm_atomic_ref_pointer ((void **) &t->wake))) + { + /* By now, the thread T might be out of its sleep already, or + might even be in the next, unrelated sleep. Interrupting it + anyway does no harm, however. + + The important thing to prevent here is to signal sleep_cond + before T waits on it. This can not happen since T has + sleep_mutex locked while setting t->sleep_mutex and will only + unlock it again while waiting on sleep_cond. + */ + scm_i_scm_pthread_mutex_lock (wake->mutex); + scm_i_pthread_cond_signal (&t->sleep_cond); + scm_i_pthread_mutex_unlock (wake->mutex); + + /* This is needed to protect wake->mutex. + */ + scm_remember_upto_here_1 (wake->object); + + if (wake->fd >= 0) + { + char dummy = 0; + + /* Likewise, T might already been done with sleeping here, but + interrupting it once too often does no harm. T might also + not yet have started sleeping, but this is no problem + either since the data written to a pipe will not be lost, + unlike a condition variable signal. */ + full_write (wake->fd, &dummy, 1); + } + } + return SCM_UNSPECIFIED; } #undef FUNC_NAME diff --git a/libguile/async.h b/libguile/async.h index 1e9760a58..e8b6ee9c2 100644 --- a/libguile/async.h +++ b/libguile/async.h @@ -34,7 +34,6 @@ SCM_API void scm_async_tick (void); SCM_API void scm_switch (void); SCM_API SCM scm_system_async_mark (SCM a); SCM_API SCM scm_system_async_mark_for_thread (SCM a, SCM thread); -SCM_INTERNAL void scm_i_queue_async_cell (SCM cell, scm_i_thread *); SCM_INTERNAL int scm_i_setup_sleep (scm_i_thread *, SCM obj, scm_i_pthread_mutex_t *m, int fd); diff --git a/libguile/atomics-internal.h b/libguile/atomics-internal.h index 9d18cbc1a..9074d8cc3 100644 --- a/libguile/atomics-internal.h +++ b/libguile/atomics-internal.h @@ -45,6 +45,16 @@ scm_atomic_compare_and_swap_uint32 (uint32_t *loc, uint32_t *expected, return atomic_compare_exchange_weak (loc, expected, desired); } static inline void +scm_atomic_set_pointer (void **loc, void *val) +{ + atomic_store (loc, val); +} +static inline void * +scm_atomic_ref_pointer (void **loc) +{ + return atomic_load (loc); +} +static inline void scm_atomic_set_scm (SCM *loc, SCM val) { atomic_store (loc, val); @@ -100,6 +110,23 @@ scm_atomic_compare_and_swap_uint32 (uint32_t *loc, uint32_t *expected, } static inline void +scm_atomic_set_pointer (void **loc, void *val) +{ + scm_i_pthread_mutex_lock (&atomics_lock); + *loc = val; + scm_i_pthread_mutex_unlock (&atomics_lock); +} +static inline void * +scm_atomic_ref_pointer (void **loc) +{ + void *ret; + scm_i_pthread_mutex_lock (&atomics_lock); + ret = *loc; + scm_i_pthread_mutex_unlock (&atomics_lock); + return ret; +} + +static inline void scm_atomic_set_scm (SCM *loc, SCM val) { scm_i_pthread_mutex_lock (&atomics_lock); diff --git a/libguile/error.c b/libguile/error.c index 89345c2b7..ff84f41d8 100644 --- a/libguile/error.c +++ b/libguile/error.c @@ -163,7 +163,7 @@ scm_syserror (const char *subr) */ #ifdef EINTR if (scm_to_int (err) == EINTR) - SCM_ASYNC_TICK; + scm_async_tick (); #endif scm_error (scm_system_error_key, @@ -179,7 +179,7 @@ scm_syserror_msg (const char *subr, const char *message, SCM args, int eno) /* See above note about the EINTR signal handling race. */ #ifdef EINTR if (eno == EINTR) - SCM_ASYNC_TICK; + scm_async_tick (); #endif scm_error (scm_system_error_key, subr, diff --git a/libguile/finalizers.c b/libguile/finalizers.c index 82f292cd2..9b9075830 100644 --- a/libguile/finalizers.c +++ b/libguile/finalizers.c @@ -40,6 +40,8 @@ static int automatic_finalization_p = 1; static size_t finalization_count; +static SCM run_finalizers_subr; + @@ -132,8 +134,6 @@ scm_i_add_finalizer (void *obj, scm_t_finalizer_proc proc, void *data) -static SCM finalizer_async_cell; - static SCM run_finalizers_async_thunk (void) { @@ -150,19 +150,13 @@ static void queue_finalizer_async (void) { scm_i_thread *t = SCM_I_CURRENT_THREAD; - static scm_i_pthread_mutex_t lock = SCM_I_PTHREAD_MUTEX_INITIALIZER; - scm_i_pthread_mutex_lock (&lock); - /* If t is NULL, that could be because we're allocating in - threads.c:guilify_self_1. In that case, rely on the + /* Could be that the current thread is is NULL when we're allocating + in threads.c:guilify_self_1. In that case, rely on the GC_invoke_finalizers call there after the thread spins up. */ - if (t && scm_is_false (SCM_CDR (finalizer_async_cell))) - { - SCM_SETCDR (finalizer_async_cell, t->active_asyncs); - t->active_asyncs = finalizer_async_cell; - t->pending_asyncs = 1; - } - scm_i_pthread_mutex_unlock (&lock); + if (!t) return; + + scm_system_async_mark_for_thread (run_finalizers_subr, t->handle); } @@ -418,10 +412,8 @@ scm_init_finalizers (void) { /* When the async is to run, the cdr of the pair gets set to the asyncs queue of the current thread. */ - finalizer_async_cell = - scm_cons (scm_c_make_gsubr ("%run-finalizers", 0, 0, 0, - run_finalizers_async_thunk), - SCM_BOOL_F); + run_finalizers_subr = scm_c_make_gsubr ("%run-finalizers", 0, 0, 0, + run_finalizers_async_thunk); if (automatic_finalization_p) GC_set_finalizer_notifier (queue_finalizer_async); diff --git a/libguile/fports.c b/libguile/fports.c index 5886f628d..8fa69933d 100644 --- a/libguile/fports.c +++ b/libguile/fports.c @@ -595,7 +595,7 @@ fport_read (SCM port, SCM dst, size_t start, size_t count) { if (errno == EINTR) { - SCM_ASYNC_TICK; + scm_async_tick (); goto retry; } if (errno == EWOULDBLOCK || errno == EAGAIN) @@ -618,7 +618,7 @@ fport_write (SCM port, SCM src, size_t start, size_t count) { if (errno == EINTR) { - SCM_ASYNC_TICK; + scm_async_tick (); goto retry; } if (errno == EWOULDBLOCK || errno == EAGAIN) diff --git a/libguile/gc.c b/libguile/gc.c index b75a688aa..1e9f59683 100644 --- a/libguile/gc.c +++ b/libguile/gc.c @@ -685,8 +685,8 @@ after_gc_async_thunk (void) */ static void * queue_after_gc_hook (void * hook_data SCM_UNUSED, - void *fn_data SCM_UNUSED, - void *data SCM_UNUSED) + void *fn_data SCM_UNUSED, + void *data SCM_UNUSED) { /* If cell access debugging is enabled, the user may choose to perform * additional garbage collections after an arbitrary number of cell @@ -721,9 +721,8 @@ queue_after_gc_hook (void * hook_data SCM_UNUSED, if (scm_is_false (SCM_CDR (after_gc_async_cell))) { - SCM_SETCDR (after_gc_async_cell, t->active_asyncs); - t->active_asyncs = after_gc_async_cell; - t->pending_asyncs = 1; + SCM_SETCDR (after_gc_async_cell, t->pending_asyncs); + t->pending_asyncs = after_gc_async_cell; } } diff --git a/libguile/init.c b/libguile/init.c index 4b95f3612..8b0813a1b 100644 --- a/libguile/init.c +++ b/libguile/init.c @@ -344,7 +344,7 @@ invoke_main_func (void *body_data) * asyncs a chance to run. This must be done after * the call to scm_restore_signals. */ - SCM_ASYNC_TICK; + scm_async_tick (); /* Indicate success by returning non-NULL. */ diff --git a/libguile/scmsigs.c b/libguile/scmsigs.c index 441da3ec7..b030b0413 100644 --- a/libguile/scmsigs.c +++ b/libguile/scmsigs.c @@ -228,9 +228,8 @@ take_signal (int signum) if (scm_is_false (SCM_CDR (cell))) { - SCM_SETCDR (cell, t->active_asyncs); - t->active_asyncs = cell; - t->pending_asyncs = 1; + SCM_SETCDR (cell, t->pending_asyncs); + t->pending_asyncs = cell; } #ifndef HAVE_SIGACTION diff --git a/libguile/threads.c b/libguile/threads.c index 1dece5694..2a315e472 100644 --- a/libguile/threads.c +++ b/libguile/threads.c @@ -275,7 +275,7 @@ thread_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED) /*** Blocking on queues. */ -/* See also scm_i_queue_async_cell for how such a block is +/* See also scm_system_async_mark_for_thread for how such a block is interrputed. */ @@ -309,7 +309,10 @@ block_self (SCM queue, SCM sleep_object, scm_i_pthread_mutex_t *mutex, int err; if (scm_i_setup_sleep (t, sleep_object, mutex, -1)) - err = EINTR; + { + scm_i_reset_sleep (t); + err = EINTR; + } else { t->block_asyncs++; @@ -415,9 +418,8 @@ guilify_self_1 (struct GC_stack_base *base) t.dynstack.base = NULL; t.dynstack.top = NULL; t.dynstack.limit = NULL; - t.active_asyncs = SCM_EOL; + t.pending_asyncs = SCM_EOL; t.block_asyncs = 1; - t.pending_asyncs = 1; t.critical_section_level = 0; t.base = base->mem_base; #ifdef __ia64__ @@ -426,9 +428,7 @@ guilify_self_1 (struct GC_stack_base *base) t.continuation_root = SCM_EOL; t.continuation_base = t.base; scm_i_pthread_cond_init (&t.sleep_cond, NULL); - t.sleep_mutex = NULL; - t.sleep_object = SCM_BOOL_F; - t.sleep_fd = -1; + t.wake = NULL; t.vp = NULL; if (pipe2 (t.sleep_pipe, O_CLOEXEC) != 0) @@ -1776,7 +1776,10 @@ scm_std_select (int nfds, } while (scm_i_setup_sleep (t, SCM_BOOL_F, NULL, t->sleep_pipe[1])) - SCM_TICK; + { + scm_i_reset_sleep (t); + SCM_TICK; + } wakeup_fd = t->sleep_pipe[0]; FD_SET (wakeup_fd, readfds); @@ -1795,7 +1798,6 @@ scm_std_select (int nfds, res = args.result; eno = args.errno_value; - t->sleep_fd = -1; scm_i_reset_sleep (t); if (res > 0 && FD_ISSET (wakeup_fd, readfds)) diff --git a/libguile/threads.h b/libguile/threads.h index a8bb21a4a..241907db2 100644 --- a/libguile/threads.h +++ b/libguile/threads.h @@ -47,6 +47,13 @@ SCM_API scm_t_bits scm_tc16_thread; SCM_API scm_t_bits scm_tc16_mutex; SCM_API scm_t_bits scm_tc16_condvar; +struct scm_thread_wake_data +{ + SCM object; + scm_i_pthread_mutex_t *mutex; + int fd; +}; + typedef struct scm_i_thread { struct scm_i_thread *next_thread; @@ -67,10 +74,9 @@ typedef struct scm_i_thread { /* Boolean indicating whether the thread is in guile mode. */ int guile_mode; - SCM sleep_object; - scm_i_pthread_mutex_t *sleep_mutex; + struct scm_thread_wake_data *wake; scm_i_pthread_cond_t sleep_cond; - int sleep_fd, sleep_pipe[2]; + int sleep_pipe[2]; /* Thread-local freelists; see gc-inline.h. */ void **freelists; @@ -85,12 +91,10 @@ typedef struct scm_i_thread { /* For system asyncs. */ - SCM active_asyncs; /* The thunks to be run at the next - safe point */ + SCM pending_asyncs; /* The thunks to be run at the next + safe point. Accessed atomically. */ unsigned int block_asyncs; /* Non-zero means that asyncs should not be run. */ - unsigned int pending_asyncs; /* Non-zero means that asyncs might be pending. - */ /* The current continuation root and the stack base for it. diff --git a/libguile/vm-engine.c b/libguile/vm-engine.c index 852e10d06..4f66b9e7d 100644 --- a/libguile/vm-engine.c +++ b/libguile/vm-engine.c @@ -127,9 +127,24 @@ #define ABORT_CONTINUATION_HOOK() \ RUN_HOOK0 (abort) -#define VM_HANDLE_INTERRUPTS \ - SCM_ASYNC_TICK_WITH_GUARD_CODE (thread, SYNC_IP (), CACHE_SP ()) +/* TODO: Invoke asyncs without trampolining out to C. That will let us + preempt computations via an asynchronous interrupt. */ +#define VM_HANDLE_INTERRUPTS \ + do \ + if (SCM_LIKELY (thread->block_asyncs == 0)) \ + { \ + SCM asyncs = scm_atomic_ref_scm (&thread->pending_asyncs); \ + if (SCM_UNLIKELY (!scm_is_null (asyncs))) \ + { \ + SYNC_IP (); \ + scm_async_tick (); \ + CACHE_SP (); \ + } \ + } \ + while (0) + + /* Virtual Machine |