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)
@@ -114,24 +130,51 @@ st_tlskey caml_thread_key;
114
130
/* overall table for threads across domains */
115
131
struct caml_thread_table {
116
132
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 ;
118
136
int tick_thread_running ;
119
137
st_thread_id tick_thread_id ;
120
138
};
121
139
122
140
/* thread_table instance, up to Max_domains */
123
141
static struct caml_thread_table thread_table [Max_domains ];
124
142
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)
126
146
127
147
static void thread_lock_acquire (int dom_id )
128
148
{
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 ();
130
168
}
131
169
132
170
static void thread_lock_release (int dom_id )
133
171
{
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 ;
135
178
}
136
179
137
180
/* The remaining fields are accessed while holding the domain lock */
@@ -156,6 +199,17 @@ static value caml_threadstatus_new (void);
156
199
static void caml_threadstatus_terminate (value );
157
200
static st_retcode caml_threadstatus_wait (value );
158
201
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
+
159
213
/* Hook for scanning the stacks of the other threads */
160
214
161
215
static scan_roots_hook prev_scan_roots_hook ;
@@ -194,27 +248,35 @@ static void save_runtime_state(void)
194
248
{
195
249
if (Caml_state -> in_minor_collection )
196
250
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 ;
210
267
#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 ;
215
272
#endif
216
273
}
217
274
275
+ CAMLexport void caml_thread_save_runtime_state (void )
276
+ {
277
+ save_runtime_state ();
278
+ }
279
+
218
280
static void restore_runtime_state (caml_thread_t th )
219
281
{
220
282
CAMLassert (th != NULL );
@@ -238,6 +300,29 @@ static void restore_runtime_state(caml_thread_t th)
238
300
#endif
239
301
}
240
302
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
+
241
326
CAMLprim value caml_thread_cleanup (value unit );
242
327
243
328
static void reset_active (void )
@@ -394,15 +479,16 @@ static void caml_thread_reinitialize(void)
394
479
Active_thread -> next = Active_thread ;
395
480
Active_thread -> prev = Active_thread ;
396
481
482
+ // Single-domain hack: systhreads doesn't maintain domain lock
397
483
/* 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
401
488
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 );
406
492
}
407
493
408
494
CAMLprim value caml_thread_join (value th );
@@ -442,7 +528,19 @@ static void caml_thread_domain_initialize_hook(void)
442
528
/* OS-specific initialization */
443
529
st_initialize ();
444
530
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 ;
446
544
447
545
new_thread =
448
546
(caml_thread_t ) caml_stat_alloc (sizeof (struct caml_thread_struct ));
@@ -555,6 +653,9 @@ static void * caml_thread_start(void * v)
555
653
caml_init_domain_self (dom_id );
556
654
557
655
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 );
558
659
559
660
thread_lock_acquire (dom_id );
560
661
restore_runtime_state (th );
@@ -570,6 +671,9 @@ static void * caml_thread_start(void * v)
570
671
caml_modify (& (Start_closure (Active_thread -> descr )), Val_unit );
571
672
caml_callback_exn (clos , Val_unit );
572
673
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 );
573
677
caml_free_signal_stack (signal_stack );
574
678
return 0 ;
575
679
}
@@ -658,6 +762,12 @@ CAMLprim value caml_thread_new(value clos)
658
762
/* the thread lock is not held when entering */
659
763
CAMLexport int caml_c_thread_register (void )
660
764
{
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
+
661
771
/* Already registered? */
662
772
if (This_thread != NULL ) return 0 ;
663
773
@@ -718,6 +828,13 @@ CAMLexport int caml_c_thread_unregister(void)
718
828
caml_thread_remove_and_free (th );
719
829
/* Release the runtime */
720
830
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? */
721
838
return 1 ;
722
839
}
723
840
@@ -752,8 +869,9 @@ CAMLprim value caml_thread_uncaught_exception(value exn)
752
869
753
870
CAMLprim value caml_thread_yield (value unit )
754
871
{
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 ))
757
875
return Val_unit ;
758
876
759
877
/* Do all the parts of a blocking section enter/leave except lock
@@ -763,8 +881,16 @@ CAMLprim value caml_thread_yield(value unit)
763
881
*/
764
882
765
883
(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 ));
766
887
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
+ }
768
894
restore_runtime_state (This_thread );
769
895
(void ) caml_raise_async_if_exception (caml_process_pending_signals_exn (), "" );
770
896
0 commit comments