Skip to content

Commit 09456d4

Browse files
committed
1 parent 6902092 commit 09456d4

File tree

6 files changed

+237
-42
lines changed

6 files changed

+237
-42
lines changed

ocaml/otherlibs/systhreads/st_pthreads.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ static void st_masterlock_acquire(st_masterlock *m)
160160
atomic_fetch_add(&m->waiters, -1);
161161
}
162162
m->busy = 1;
163-
st_bt_lock_acquire(m);
163+
// single-domain hack: we assume no backup thread
164+
// st_bt_lock_acquire(m);
164165
pthread_mutex_unlock(&m->lock);
165166

166167
return;
@@ -170,7 +171,8 @@ static void st_masterlock_release(st_masterlock * m)
170171
{
171172
pthread_mutex_lock(&m->lock);
172173
m->busy = 0;
173-
st_bt_lock_release(m);
174+
// single-domain hack: we assume no backup thread
175+
// st_bt_lock_release(m);
174176
custom_condvar_signal(&m->is_free);
175177
pthread_mutex_unlock(&m->lock);
176178

@@ -208,7 +210,8 @@ Caml_inline void st_thread_yield(st_masterlock * m)
208210
messaging the bt should not be required because yield assumes
209211
that a thread will resume execution (be it the yielding thread
210212
or a waiting thread */
211-
caml_release_domain_lock();
213+
// single-domain hack
214+
// caml_release_domain_lock();
212215

213216
do {
214217
/* Note: the POSIX spec prevents the above signal from pairing with this
@@ -221,7 +224,8 @@ Caml_inline void st_thread_yield(st_masterlock * m)
221224
m->busy = 1;
222225
atomic_fetch_add(&m->waiters, -1);
223226

224-
caml_acquire_domain_lock();
227+
// single-domain hack
228+
// caml_acquire_domain_lock();
225229

226230
pthread_mutex_unlock(&m->lock);
227231

ocaml/otherlibs/systhreads/st_stubs.c

Lines changed: 160 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848

4949
#include "../../runtime/sync_posix.h"
5050

51-
/* threads.h is *not* included since it contains the _external_ declarations for
52-
the caml_c_thread_register and caml_c_thread_unregister functions. */
51+
#define CAMLextern_libthreads
52+
#include "threads.h"
5353

5454
/* Max computation time before rescheduling, in milliseconds */
5555
#define Thread_timeout 50
@@ -61,6 +61,22 @@
6161
#include "st_posix.h"
6262
#endif
6363

64+
/* Atomics */
65+
#if defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8
66+
/* GCC 4.8 shipped with a working implementation of atomics, but no
67+
stdatomic.h header, so we need to use GCC-specific intrinsics. */
68+
69+
#define _Atomic /* GCC intrinsics work on normal variables */
70+
#define atomic_store(v, x) \
71+
__atomic_store_n((v), (x), __ATOMIC_SEQ_CST)
72+
#define atomic_load(v) \
73+
__atomic_load_n((v), __ATOMIC_SEQ_CST)
74+
#define atomic_exchange(v, x) \
75+
__atomic_exchange_n((v), (x), __ATOMIC_SEQ_CST)
76+
#else
77+
#include <stdatomic.h>
78+
#endif
79+
6480
/* The ML value describing a thread (heap-allocated) */
6581

6682
#define Ident(v) Field(v, 0)
@@ -114,24 +130,51 @@ st_tlskey caml_thread_key;
114130
/* overall table for threads across domains */
115131
struct caml_thread_table {
116132
caml_thread_t active_thread;
117-
st_masterlock thread_lock;
133+
struct caml_locking_scheme * _Atomic locking_scheme;
134+
st_masterlock default_lock;
135+
struct caml_locking_scheme default_locking_scheme;
118136
int tick_thread_running;
119137
st_thread_id tick_thread_id;
120138
};
121139

122140
/* thread_table instance, up to Max_domains */
123141
static struct caml_thread_table thread_table[Max_domains];
124142

125-
#define Thread_lock(dom_id) &thread_table[dom_id].thread_lock
143+
#define Locking_scheme(dom_id) (thread_table[dom_id].locking_scheme)
144+
#define Default_lock(dom_id) (&thread_table[dom_id].default_lock)
145+
#define Default_locking_scheme(dom_id) (&thread_table[dom_id].default_locking_scheme)
126146

127147
static void thread_lock_acquire(int dom_id)
128148
{
129-
st_masterlock_acquire(Thread_lock(dom_id));
149+
struct caml_locking_scheme* s;
150+
151+
/* The locking scheme may be changed by the thread that currently
152+
holds it. This means that it may change while we're waiting to
153+
acquire it, so by the time we acquire it it may no longer be the
154+
right scheme. */
155+
156+
retry:
157+
s = atomic_load(&Locking_scheme(dom_id));
158+
s->lock(s->context);
159+
if (atomic_load(&Locking_scheme(dom_id)) != s) {
160+
/* This is no longer the right scheme. Unlock and try again */
161+
s->unlock(s->context);
162+
goto retry;
163+
}
164+
/* single-domain hack: systhreads doesn't maintain domain_lock, but still uses
165+
[caml_state] which was protected by domain_lock. Here we imitate that
166+
behaviour. */
167+
caml_state = caml_get_domain_state_unsafe();
130168
}
131169

132170
static void thread_lock_release(int dom_id)
133171
{
134-
st_masterlock_release(Thread_lock(dom_id));
172+
/* There is no tricky case here like in acquire, as only the holder
173+
of the lock can change it. (Here, that's us) */
174+
struct caml_locking_scheme *s;
175+
s = atomic_load(&Locking_scheme(dom_id));
176+
s->unlock(s->context);
177+
caml_state = NULL;
135178
}
136179

137180
/* The remaining fields are accessed while holding the domain lock */
@@ -156,6 +199,17 @@ static value caml_threadstatus_new (void);
156199
static void caml_threadstatus_terminate (value);
157200
static st_retcode caml_threadstatus_wait (value);
158201

202+
static int default_can_skip_yield(st_masterlock *m)
203+
{
204+
return st_masterlock_waiters(m) == 0;
205+
}
206+
207+
static void default_reinitialize_after_fork(st_masterlock *m)
208+
{
209+
m->init = 0; /* force initialization */
210+
st_masterlock_init(m);
211+
}
212+
159213
/* Hook for scanning the stacks of the other threads */
160214

161215
static scan_roots_hook prev_scan_roots_hook;
@@ -194,27 +248,35 @@ static void save_runtime_state(void)
194248
{
195249
if (Caml_state->in_minor_collection)
196250
caml_fatal_error("Thread switch from inside minor GC");
197-
CAMLassert(This_thread != NULL);
198-
caml_thread_t this_thread = This_thread;
199-
this_thread->current_stack = Caml_state->current_stack;
200-
this_thread->c_stack = Caml_state->c_stack;
201-
this_thread->gc_regs = Caml_state->gc_regs;
202-
this_thread->gc_regs_buckets = Caml_state->gc_regs_buckets;
203-
this_thread->exn_handler = Caml_state->exn_handler;
204-
this_thread->async_exn_handler = Caml_state->async_exn_handler;
205-
this_thread->local_roots = Caml_state->local_roots;
206-
this_thread->local_arenas = caml_get_local_arenas(Caml_state);
207-
this_thread->backtrace_pos = Caml_state->backtrace_pos;
208-
this_thread->backtrace_buffer = Caml_state->backtrace_buffer;
209-
this_thread->backtrace_last_exn = Caml_state->backtrace_last_exn;
251+
/* CR zqian: we save to [active_thread] instead of [this_thread]. I believe
252+
they are equivalent here, but I think use [active_thread] is easier to
253+
understand, also follows the systhreads4 behavior. */
254+
caml_thread_t th = Active_thread;
255+
CAMLassert(th != NULL);
256+
th->current_stack = Caml_state->current_stack;
257+
th->c_stack = Caml_state->c_stack;
258+
th->gc_regs = Caml_state->gc_regs;
259+
th->gc_regs_buckets = Caml_state->gc_regs_buckets;
260+
th->exn_handler = Caml_state->exn_handler;
261+
th->async_exn_handler = Caml_state->async_exn_handler;
262+
th->local_roots = Caml_state->local_roots;
263+
th->local_arenas = caml_get_local_arenas(Caml_state);
264+
th->backtrace_pos = Caml_state->backtrace_pos;
265+
th->backtrace_buffer = Caml_state->backtrace_buffer;
266+
th->backtrace_last_exn = Caml_state->backtrace_last_exn;
210267
#ifndef NATIVE_CODE
211-
this_thread->trap_sp_off = Caml_state->trap_sp_off;
212-
this_thread->trap_barrier_off = Caml_state->trap_barrier_off;
213-
this_thread->external_raise = Caml_state->external_raise;
214-
this_thread->external_raise_async = Caml_state->external_raise_async;
268+
th->trap_sp_off = Caml_state->trap_sp_off;
269+
th->trap_barrier_off = Caml_state->trap_barrier_off;
270+
th->external_raise = Caml_state->external_raise;
271+
th->external_raise_async = Caml_state->external_raise_async;
215272
#endif
216273
}
217274

275+
CAMLexport void caml_thread_save_runtime_state(void)
276+
{
277+
save_runtime_state();
278+
}
279+
218280
static void restore_runtime_state(caml_thread_t th)
219281
{
220282
CAMLassert(th != NULL);
@@ -238,6 +300,29 @@ static void restore_runtime_state(caml_thread_t th)
238300
#endif
239301
}
240302

303+
CAMLexport void caml_thread_restore_runtime_state(void)
304+
{
305+
restore_runtime_state(This_thread);
306+
}
307+
308+
309+
CAMLexport void caml_switch_runtime_locking_scheme(struct caml_locking_scheme* new)
310+
{
311+
struct caml_locking_scheme* old;
312+
int dom_id = Caml_state->id;
313+
save_runtime_state();
314+
old = atomic_exchange(&Locking_scheme(dom_id), new);
315+
/* We hold 'old', but it is no longer the runtime lock */
316+
old->unlock(old->context);
317+
thread_lock_acquire(dom_id);
318+
restore_runtime_state(This_thread);
319+
}
320+
321+
CAMLexport struct caml_locking_scheme* caml_get_runtime_locking_scheme(void)
322+
{
323+
return Locking_scheme(Caml_state->id);
324+
}
325+
241326
CAMLprim value caml_thread_cleanup(value unit);
242327

243328
static void reset_active(void)
@@ -394,15 +479,16 @@ static void caml_thread_reinitialize(void)
394479
Active_thread->next = Active_thread;
395480
Active_thread->prev = Active_thread;
396481

482+
// Single-domain hack: systhreads doesn't maintain domain lock
397483
/* Within the child, the domain_lock needs to be reset and acquired. */
398-
caml_reset_domain_lock();
399-
caml_acquire_domain_lock();
400-
/* The master lock needs to be initialized again. This process will also be
484+
// caml_reset_domain_lock();
485+
// caml_acquire_domain_lock();
486+
487+
/* The lock needs to be initialized again. This process will also be
401488
the effective owner of the lock. So there is no need to run
402-
st_masterlock_acquire (busy = 1) */
403-
st_masterlock *m = Thread_lock(Caml_state->id);
404-
m->init = 0; /* force initialization */
405-
st_masterlock_init(m);
489+
s->lock (busy = 1) */
490+
struct caml_locking_scheme *s = atomic_load(&Locking_scheme(Caml_state->id));
491+
s->reinitialize_after_fork(s->context);
406492
}
407493

408494
CAMLprim value caml_thread_join(value th);
@@ -442,7 +528,19 @@ static void caml_thread_domain_initialize_hook(void)
442528
/* OS-specific initialization */
443529
st_initialize();
444530

445-
st_masterlock_init(Thread_lock(Caml_state->id));
531+
st_masterlock *default_lock = Default_lock(Caml_state->id);
532+
st_masterlock_init(default_lock);
533+
struct caml_locking_scheme *ls = Default_locking_scheme(Caml_state->id);
534+
ls->context = default_lock;
535+
ls->lock = (void (*)(void*))&st_masterlock_acquire;
536+
ls->unlock = (void (*)(void*))&st_masterlock_release;
537+
ls->thread_start = NULL;
538+
ls->thread_stop = NULL;
539+
ls->reinitialize_after_fork = (void (*)(void*))&default_reinitialize_after_fork;
540+
ls->can_skip_yield = (int (*)(void*))&default_can_skip_yield;
541+
ls->yield = (void (*)(void*))&st_thread_yield;
542+
543+
Locking_scheme(Caml_state->id) = ls;
446544

447545
new_thread =
448546
(caml_thread_t) caml_stat_alloc(sizeof(struct caml_thread_struct));
@@ -555,6 +653,9 @@ static void * caml_thread_start(void * v)
555653
caml_init_domain_self(dom_id);
556654

557655
st_tls_set(caml_thread_key, th);
656+
struct caml_locking_scheme *s = atomic_load(&Locking_scheme(dom_id));
657+
if (s -> thread_start != NULL)
658+
s->thread_start(s->context, Thread_type_caml);
558659

559660
thread_lock_acquire(dom_id);
560661
restore_runtime_state(th);
@@ -570,6 +671,9 @@ static void * caml_thread_start(void * v)
570671
caml_modify(&(Start_closure(Active_thread->descr)), Val_unit);
571672
caml_callback_exn(clos, Val_unit);
572673
caml_thread_stop();
674+
s = atomic_load(&Locking_scheme(dom_id));
675+
if (s->thread_stop != NULL)
676+
s->thread_stop(s->context, Thread_type_caml);
573677
caml_free_signal_stack(signal_stack);
574678
return 0;
575679
}
@@ -658,6 +762,12 @@ CAMLprim value caml_thread_new(value clos)
658762
/* the thread lock is not held when entering */
659763
CAMLexport int caml_c_thread_register(void)
660764
{
765+
/* CR zqian: I would personally delay this after the "already registered"
766+
check, but this is to follow the original PR.*/
767+
struct caml_locking_scheme *s = atomic_load(&Locking_scheme(Dom_c_threads));
768+
if (s->thread_start != NULL)
769+
s->thread_start(s->context, Thread_type_c_registered);
770+
661771
/* Already registered? */
662772
if (This_thread != NULL) return 0;
663773

@@ -718,6 +828,13 @@ CAMLexport int caml_c_thread_unregister(void)
718828
caml_thread_remove_and_free(th);
719829
/* Release the runtime */
720830
thread_lock_release(Dom_c_threads);
831+
struct caml_locking_scheme *s = atomic_load(&Locking_scheme(Dom_c_threads));
832+
if (s->thread_stop != NULL)
833+
s->thread_stop(s->context, Thread_type_c_registered);
834+
/* CR zqian: This follows the original PR. But some asymetry here: if a thread
835+
is already registered, registering again gives callback. If a thread is
836+
already unregistered, unregistering again does not give callback. Is that
837+
fine? */
721838
return 1;
722839
}
723840

@@ -752,8 +869,9 @@ CAMLprim value caml_thread_uncaught_exception(value exn)
752869

753870
CAMLprim value caml_thread_yield(value unit)
754871
{
755-
st_masterlock *m = Thread_lock(Caml_state->id);
756-
if (st_masterlock_waiters(m) == 0)
872+
struct caml_locking_scheme *s;
873+
s = atomic_load(&Locking_scheme(Caml_state->id));
874+
if (s->can_skip_yield != NULL && s -> can_skip_yield(s->context))
757875
return Val_unit;
758876

759877
/* Do all the parts of a blocking section enter/leave except lock
@@ -763,8 +881,16 @@ CAMLprim value caml_thread_yield(value unit)
763881
*/
764882

765883
(void) caml_raise_async_if_exception(caml_process_pending_signals_exn (), "");
884+
885+
// s may have changed in caml_process_pending_signals_exn
886+
s = atomic_load(&Locking_scheme(Caml_state->id));
766887
save_runtime_state();
767-
st_thread_yield(m);
888+
s->yield(s->context);
889+
if (atomic_load(&Locking_scheme(Caml_state->id)) != s) {
890+
// The lock we own is no longer the runtime lock
891+
s->unlock(s->context);
892+
thread_lock_acquire(Caml_state->id);
893+
}
768894
restore_runtime_state(This_thread);
769895
(void) caml_raise_async_if_exception(caml_process_pending_signals_exn (), "");
770896

0 commit comments

Comments
 (0)