48
48
49
49
#include "../../runtime/sync_posix.h"
50
50
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"
53
53
54
54
/* Max computation time before rescheduling, in milliseconds */
55
55
#define Thread_timeout 50
61
61
#include "st_posix.h"
62
62
#endif
63
63
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
+
64
80
/* The ML value describing a thread (heap-allocated) */
65
81
66
82
#define Ident (v ) Field(v, 0)
@@ -112,24 +128,51 @@ st_tlskey caml_thread_key;
112
128
/* overall table for threads across domains */
113
129
struct caml_thread_table {
114
130
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 ;
116
134
int tick_thread_running ;
117
135
st_thread_id tick_thread_id ;
118
136
};
119
137
120
138
/* thread_table instance, up to Max_domains */
121
139
static struct caml_thread_table thread_table [Max_domains ];
122
140
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)
124
144
125
145
static void thread_lock_acquire (int dom_id )
126
146
{
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 ();
128
166
}
129
167
130
168
static void thread_lock_release (int dom_id )
131
169
{
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 ;
133
176
}
134
177
135
178
/* The remaining fields are accessed while holding the domain lock */
@@ -154,6 +197,17 @@ static value caml_threadstatus_new (void);
154
197
static void caml_threadstatus_terminate (value );
155
198
static st_retcode caml_threadstatus_wait (value );
156
199
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
+
157
211
/* Hook for scanning the stacks of the other threads */
158
212
159
213
static scan_roots_hook prev_scan_roots_hook ;
@@ -192,25 +246,33 @@ static void save_runtime_state(void)
192
246
{
193
247
if (Caml_state -> in_minor_collection )
194
248
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 ;
207
264
#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 ;
211
268
#endif
212
269
}
213
270
271
+ CAMLexport void caml_thread_save_runtime_state (void )
272
+ {
273
+ save_runtime_state ();
274
+ }
275
+
214
276
static void restore_runtime_state (caml_thread_t th )
215
277
{
216
278
CAMLassert (th != NULL );
@@ -232,6 +294,29 @@ static void restore_runtime_state(caml_thread_t th)
232
294
#endif
233
295
}
234
296
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
+
235
320
CAMLprim value caml_thread_cleanup (value unit );
236
321
237
322
static void reset_active (void )
@@ -384,15 +469,16 @@ static void caml_thread_reinitialize(void)
384
469
Active_thread -> next = Active_thread ;
385
470
Active_thread -> prev = Active_thread ;
386
471
472
+ // Single-domain hack: systhreads doesn't maintain domain lock
387
473
/* 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
391
478
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 );
396
482
}
397
483
398
484
CAMLprim value caml_thread_join (value th );
@@ -432,7 +518,19 @@ static void caml_thread_domain_initialize_hook(void)
432
518
/* OS-specific initialization */
433
519
st_initialize ();
434
520
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 ;
436
534
437
535
new_thread =
438
536
(caml_thread_t ) caml_stat_alloc (sizeof (struct caml_thread_struct ));
@@ -545,6 +643,9 @@ static void * caml_thread_start(void * v)
545
643
caml_init_domain_self (dom_id );
546
644
547
645
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 );
548
649
549
650
thread_lock_acquire (dom_id );
550
651
restore_runtime_state (th );
@@ -560,6 +661,9 @@ static void * caml_thread_start(void * v)
560
661
caml_modify (& (Start_closure (Active_thread -> descr )), Val_unit );
561
662
caml_callback_exn (clos , Val_unit );
562
663
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 );
563
667
caml_free_signal_stack (signal_stack );
564
668
return 0 ;
565
669
}
@@ -648,6 +752,12 @@ CAMLprim value caml_thread_new(value clos)
648
752
/* the thread lock is not held when entering */
649
753
CAMLexport int caml_c_thread_register (void )
650
754
{
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
+
651
761
/* Already registered? */
652
762
if (This_thread != NULL ) return 0 ;
653
763
@@ -708,6 +818,13 @@ CAMLexport int caml_c_thread_unregister(void)
708
818
caml_thread_remove_and_free (th );
709
819
/* Release the runtime */
710
820
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? */
711
828
return 1 ;
712
829
}
713
830
@@ -742,8 +859,9 @@ CAMLprim value caml_thread_uncaught_exception(value exn)
742
859
743
860
CAMLprim value caml_thread_yield (value unit )
744
861
{
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 ))
747
865
return Val_unit ;
748
866
749
867
/* Do all the parts of a blocking section enter/leave except lock
@@ -753,8 +871,16 @@ CAMLprim value caml_thread_yield(value unit)
753
871
*/
754
872
755
873
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 ));
756
877
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
+ }
758
884
restore_runtime_state (This_thread );
759
885
caml_raise_if_exception (caml_process_pending_signals_exn ());
760
886
0 commit comments