Skip to content

Commit d011874

Browse files
committed
1 parent 9c66789 commit d011874

File tree

6 files changed

+235
-40
lines changed

6 files changed

+235
-40
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
pthread_cond_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: 158 additions & 32 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)
@@ -112,24 +128,51 @@ st_tlskey caml_thread_key;
112128
/* overall table for threads across domains */
113129
struct caml_thread_table {
114130
caml_thread_t active_thread;
115-
st_masterlock thread_lock;
131+
struct caml_locking_scheme * _Atomic locking_scheme;
132+
st_masterlock default_lock;
133+
struct caml_locking_scheme default_locking_scheme;
116134
int tick_thread_running;
117135
st_thread_id tick_thread_id;
118136
};
119137

120138
/* thread_table instance, up to Max_domains */
121139
static struct caml_thread_table thread_table[Max_domains];
122140

123-
#define Thread_lock(dom_id) &thread_table[dom_id].thread_lock
141+
#define Locking_scheme(dom_id) (thread_table[dom_id].locking_scheme)
142+
#define Default_lock(dom_id) (&thread_table[dom_id].default_lock)
143+
#define Default_locking_scheme(dom_id) (&thread_table[dom_id].default_locking_scheme)
124144

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

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

135178
/* The remaining fields are accessed while holding the domain lock */
@@ -154,6 +197,17 @@ static value caml_threadstatus_new (void);
154197
static void caml_threadstatus_terminate (value);
155198
static st_retcode caml_threadstatus_wait (value);
156199

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

159213
static scan_roots_hook prev_scan_roots_hook;
@@ -192,25 +246,33 @@ static void save_runtime_state(void)
192246
{
193247
if (Caml_state->in_minor_collection)
194248
caml_fatal_error("Thread switch from inside minor GC");
195-
CAMLassert(This_thread != NULL);
196-
caml_thread_t this_thread = This_thread;
197-
this_thread->current_stack = Caml_state->current_stack;
198-
this_thread->c_stack = Caml_state->c_stack;
199-
this_thread->gc_regs = Caml_state->gc_regs;
200-
this_thread->gc_regs_buckets = Caml_state->gc_regs_buckets;
201-
this_thread->exn_handler = Caml_state->exn_handler;
202-
this_thread->local_roots = Caml_state->local_roots;
203-
this_thread->local_arenas = caml_get_local_arenas(Caml_state);
204-
this_thread->backtrace_pos = Caml_state->backtrace_pos;
205-
this_thread->backtrace_buffer = Caml_state->backtrace_buffer;
206-
this_thread->backtrace_last_exn = Caml_state->backtrace_last_exn;
249+
/* CR zqian: we save to [active_thread] instead of [this_thread]. I believe
250+
they are equivalent here, but I think use [active_thread] is easier to
251+
understand, also follows the systhreads4 behavior. */
252+
caml_thread_t th = Active_thread;
253+
CAMLassert(th != NULL);
254+
th->current_stack = Caml_state->current_stack;
255+
th->c_stack = Caml_state->c_stack;
256+
th->gc_regs = Caml_state->gc_regs;
257+
th->gc_regs_buckets = Caml_state->gc_regs_buckets;
258+
th->exn_handler = Caml_state->exn_handler;
259+
th->local_roots = Caml_state->local_roots;
260+
th->local_arenas = caml_get_local_arenas(Caml_state);
261+
th->backtrace_pos = Caml_state->backtrace_pos;
262+
th->backtrace_buffer = Caml_state->backtrace_buffer;
263+
th->backtrace_last_exn = Caml_state->backtrace_last_exn;
207264
#ifndef NATIVE_CODE
208-
this_thread->trap_sp_off = Caml_state->trap_sp_off;
209-
this_thread->trap_barrier_off = Caml_state->trap_barrier_off;
210-
this_thread->external_raise = Caml_state->external_raise;
265+
th->trap_sp_off = Caml_state->trap_sp_off;
266+
th->trap_barrier_off = Caml_state->trap_barrier_off;
267+
th->external_raise = Caml_state->external_raise;
211268
#endif
212269
}
213270

271+
CAMLexport void caml_thread_save_runtime_state(void)
272+
{
273+
save_runtime_state();
274+
}
275+
214276
static void restore_runtime_state(caml_thread_t th)
215277
{
216278
CAMLassert(th != NULL);
@@ -232,6 +294,29 @@ static void restore_runtime_state(caml_thread_t th)
232294
#endif
233295
}
234296

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

237322
static void reset_active(void)
@@ -384,15 +469,16 @@ static void caml_thread_reinitialize(void)
384469
Active_thread->next = Active_thread;
385470
Active_thread->prev = Active_thread;
386471

472+
// Single-domain hack: systhreads doesn't maintain domain lock
387473
/* Within the child, the domain_lock needs to be reset and acquired. */
388-
caml_reset_domain_lock();
389-
caml_acquire_domain_lock();
390-
/* The master lock needs to be initialized again. This process will also be
474+
// caml_reset_domain_lock();
475+
// caml_acquire_domain_lock();
476+
477+
/* The lock needs to be initialized again. This process will also be
391478
the effective owner of the lock. So there is no need to run
392-
st_masterlock_acquire (busy = 1) */
393-
st_masterlock *m = Thread_lock(Caml_state->id);
394-
m->init = 0; /* force initialization */
395-
st_masterlock_init(m);
479+
s->lock (busy = 1) */
480+
struct caml_locking_scheme *s = atomic_load(&Locking_scheme(Caml_state->id));
481+
s->reinitialize_after_fork(s->context);
396482
}
397483

398484
CAMLprim value caml_thread_join(value th);
@@ -432,7 +518,19 @@ static void caml_thread_domain_initialize_hook(void)
432518
/* OS-specific initialization */
433519
st_initialize();
434520

435-
st_masterlock_init(Thread_lock(Caml_state->id));
521+
st_masterlock *default_lock = Default_lock(Caml_state->id);
522+
st_masterlock_init(default_lock);
523+
struct caml_locking_scheme *ls = Default_locking_scheme(Caml_state->id);
524+
ls->context = default_lock;
525+
ls->lock = (void (*)(void*))&st_masterlock_acquire;
526+
ls->unlock = (void (*)(void*))&st_masterlock_release;
527+
ls->thread_start = NULL;
528+
ls->thread_stop = NULL;
529+
ls->reinitialize_after_fork = (void (*)(void*))&default_reinitialize_after_fork;
530+
ls->can_skip_yield = (int (*)(void*))&default_can_skip_yield;
531+
ls->yield = (void (*)(void*))&st_thread_yield;
532+
533+
Locking_scheme(Caml_state->id) = ls;
436534

437535
new_thread =
438536
(caml_thread_t) caml_stat_alloc(sizeof(struct caml_thread_struct));
@@ -545,6 +643,9 @@ static void * caml_thread_start(void * v)
545643
caml_init_domain_self(dom_id);
546644

547645
st_tls_set(caml_thread_key, th);
646+
struct caml_locking_scheme *s = atomic_load(&Locking_scheme(dom_id));
647+
if (s -> thread_start != NULL)
648+
s->thread_start(s->context, Thread_type_caml);
548649

549650
thread_lock_acquire(dom_id);
550651
restore_runtime_state(th);
@@ -560,6 +661,9 @@ static void * caml_thread_start(void * v)
560661
caml_modify(&(Start_closure(Active_thread->descr)), Val_unit);
561662
caml_callback_exn(clos, Val_unit);
562663
caml_thread_stop();
664+
s = atomic_load(&Locking_scheme(dom_id));
665+
if (s->thread_stop != NULL)
666+
s->thread_stop(s->context, Thread_type_caml);
563667
caml_free_signal_stack(signal_stack);
564668
return 0;
565669
}
@@ -648,6 +752,12 @@ CAMLprim value caml_thread_new(value clos)
648752
/* the thread lock is not held when entering */
649753
CAMLexport int caml_c_thread_register(void)
650754
{
755+
/* CR zqian: I would personally delay this after the "already registered"
756+
check, but this is to follow the original PR.*/
757+
struct caml_locking_scheme *s = atomic_load(&Locking_scheme(Dom_c_threads));
758+
if (s->thread_start != NULL)
759+
s->thread_start(s->context, Thread_type_c_registered);
760+
651761
/* Already registered? */
652762
if (This_thread != NULL) return 0;
653763

@@ -708,6 +818,13 @@ CAMLexport int caml_c_thread_unregister(void)
708818
caml_thread_remove_and_free(th);
709819
/* Release the runtime */
710820
thread_lock_release(Dom_c_threads);
821+
struct caml_locking_scheme *s = atomic_load(&Locking_scheme(Dom_c_threads));
822+
if (s->thread_stop != NULL)
823+
s->thread_stop(s->context, Thread_type_c_registered);
824+
/* CR zqian: This follows the original PR. But some asymetry here: if a thread
825+
is already registered, registering again gives callback. If a thread is
826+
already unregistered, unregistering again does not give callback. Is that
827+
fine? */
711828
return 1;
712829
}
713830

@@ -742,8 +859,9 @@ CAMLprim value caml_thread_uncaught_exception(value exn)
742859

743860
CAMLprim value caml_thread_yield(value unit)
744861
{
745-
st_masterlock *m = Thread_lock(Caml_state->id);
746-
if (st_masterlock_waiters(m) == 0)
862+
struct caml_locking_scheme *s;
863+
s = atomic_load(&Locking_scheme(Caml_state->id));
864+
if (s->can_skip_yield != NULL && s -> can_skip_yield(s->context))
747865
return Val_unit;
748866

749867
/* Do all the parts of a blocking section enter/leave except lock
@@ -753,8 +871,16 @@ CAMLprim value caml_thread_yield(value unit)
753871
*/
754872

755873
caml_raise_if_exception(caml_process_pending_signals_exn());
874+
875+
// s may have changed in caml_process_pending_signals_exn
876+
s = atomic_load(&Locking_scheme(Caml_state->id));
756877
save_runtime_state();
757-
st_thread_yield(m);
878+
s->yield(s->context);
879+
if (atomic_load(&Locking_scheme(Caml_state->id)) != s) {
880+
// The lock we own is no longer the runtime lock
881+
s->unlock(s->context);
882+
thread_lock_acquire(Caml_state->id);
883+
}
758884
restore_runtime_state(This_thread);
759885
caml_raise_if_exception(caml_process_pending_signals_exn());
760886

0 commit comments

Comments
 (0)