-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
Copy pathgc-stock.c
4083 lines (3842 loc) · 159 KB
/
gc-stock.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This file is a part of Julia. License is MIT: https://julialang.org/license
#include "gc-common.h"
#include "gc-stock.h"
#include "gc-alloc-profiler.h"
#include "gc-heap-snapshot.h"
#include "gc-page-profiler.h"
#include "julia.h"
#include "julia_atomics.h"
#include "julia_gcext.h"
#include "julia_assert.h"
#ifdef __cplusplus
extern "C" {
#endif
// Number of GC threads that may run parallel marking
int jl_n_markthreads;
// Number of GC threads that may run concurrent sweeping (0 or 1)
int jl_n_sweepthreads;
// Number of threads currently running the GC mark-loop
_Atomic(int) gc_n_threads_marking;
// Number of threads sweeping
_Atomic(int) gc_n_threads_sweeping_pools;
// Number of threads sweeping stacks
_Atomic(int) gc_n_threads_sweeping_stacks;
// Temporary for the `ptls->gc_tls.page_metadata_allocd` used during parallel sweeping (padded to avoid false sharing)
_Atomic(jl_gc_padded_page_stack_t *) gc_allocd_scratch;
// `tid` of mutator thread that triggered GC
_Atomic(int) gc_master_tid;
// counter for sharing work when sweeping stacks
_Atomic(int) gc_ptls_sweep_idx;
// counter for round robin of giving back stack pages to the OS
_Atomic(int) gc_stack_free_idx = 0;
// `tid` of first GC thread
int gc_first_tid;
// Mutex/cond used to synchronize wakeup of GC threads on parallel marking
uv_mutex_t gc_threads_lock;
uv_cond_t gc_threads_cond;
// To indicate whether concurrent sweeping should run
uv_sem_t gc_sweep_assists_needed;
// Mutex used to coordinate entry of GC threads in the mark loop
uv_mutex_t gc_queue_observer_lock;
// Tag for sentinel nodes in bigval list
uintptr_t gc_bigval_sentinel_tag;
// Table recording number of full GCs due to each reason
JL_DLLEXPORT uint64_t jl_full_sweep_reasons[FULL_SWEEP_NUM_REASONS];
// Flag that tells us whether we need to support conservative marking
// of objects.
static _Atomic(int) support_conservative_marking = 0;
/**
* Note about GC synchronization:
*
* When entering `jl_gc_collect()`, `jl_gc_running` is atomically changed from
* `0` to `1` to make sure that only one thread can be running `_jl_gc_collect`. Other
* mutator threads that enters `jl_gc_collect()` at the same time (or later calling
* from unmanaged code) will wait in `jl_gc_collect()` until the GC is finished.
*
* Before starting the mark phase the GC thread calls `jl_safepoint_start_gc()`
* and `jl_gc_wait_for_the_world()`
* to make sure all the thread are in a safe state for the GC. The function
* activates the safepoint and wait for all the threads to get ready for the
* GC (`gc_state != 0`). It also acquires the `finalizers` lock so that no
* other thread will access them when the GC is running.
*
* During the mark and sweep phase of the GC, the mutator threads that are not running
* the GC should either be running unmanaged code (or code section that does
* not have a GC critical region mainly including storing to the stack or
* another object) or paused at a safepoint and wait for the GC to finish.
* If a thread want to switch from running unmanaged code to running managed
* code, it has to perform a GC safepoint check after setting the `gc_state`
* flag (see `jl_gc_state_save_and_set()`. it is possible that the thread might
* have `gc_state == 0` in the middle of the GC transition back before entering
* the safepoint. This is fine since the thread won't be executing any GC
* critical region during that time).
*
* The finalizers are run after the GC finishes in normal mode (the `gc_state`
* when `jl_gc_collect` is called) with `jl_in_finalizer = 1`. (TODO:) When we
* have proper support of GC transition in codegen, we should execute the
* finalizers in unmanaged (GC safe) mode.
*/
gc_heapstatus_t gc_heap_stats = {0};
// List of big objects in oldest generation (`GC_OLD_MARKED`). Not per-thread. Accessed only by master thread.
bigval_t *oldest_generation_of_bigvals = NULL;
// explicitly scheduled objects for the sweepfunc callback
static void gc_sweep_foreign_objs_in_list(arraylist_t *objs) JL_NOTSAFEPOINT
{
size_t p = 0;
for (size_t i = 0; i < objs->len; i++) {
jl_value_t *v = (jl_value_t*)(objs->items[i]);
jl_datatype_t *t = (jl_datatype_t*)(jl_typeof(v));
const jl_datatype_layout_t *layout = t->layout;
jl_fielddescdyn_t *desc = (jl_fielddescdyn_t*)jl_dt_layout_fields(layout);
int bits = jl_astaggedvalue(v)->bits.gc;
if (!gc_marked(bits))
desc->sweepfunc(v);
else
objs->items[p++] = v;
}
objs->len = p;
}
static void gc_sweep_foreign_objs(void) JL_NOTSAFEPOINT
{
assert(gc_n_threads);
for (int i = 0; i < gc_n_threads; i++) {
jl_ptls_t ptls2 = gc_all_tls_states[i];
if (ptls2 != NULL)
gc_sweep_foreign_objs_in_list(&ptls2->gc_tls.sweep_objs);
}
}
// GC knobs and self-measurement variables
static int64_t last_gc_total_bytes = 0;
// max_total_memory is a suggestion. We try very hard to stay
// under this limit, but we will go above it rather than halting.
#ifdef _P64
typedef uint64_t memsize_t;
static const size_t default_collect_interval = 5600 * 1024 * sizeof(void*);
static size_t total_mem;
// We expose this to the user/ci as jl_gc_set_max_memory
static memsize_t max_total_memory = (memsize_t) 2 * 1024 * 1024 * 1024 * 1024 * 1024;
#else
typedef uint32_t memsize_t;
static const size_t default_collect_interval = 3200 * 1024 * sizeof(void*);
// Work really hard to stay within 2GB
// Alternative is to risk running out of address space
// on 32 bit architectures.
#define MAX32HEAP 1536 * 1024 * 1024
static memsize_t max_total_memory = (memsize_t) MAX32HEAP;
#endif
// heuristic stuff for https://dl.acm.org/doi/10.1145/3563323
// start with values that are in the target ranges to reduce transient hiccups at startup
static uint64_t old_pause_time = 1e7; // 10 ms
static uint64_t old_mut_time = 1e9; // 1 second
static uint64_t old_heap_size = 0;
static uint64_t old_alloc_diff = default_collect_interval;
static uint64_t old_freed_diff = default_collect_interval;
static uint64_t gc_end_time = 0;
static int thrash_counter = 0;
static int thrashing = 0;
// global variables for GC stats
static uint64_t freed_in_runtime = 0;
// Resetting the object to a young object, this is used when marking the
// finalizer list to collect them the next time because the object is very
// likely dead. This also won't break the GC invariance since these objects
// are not reachable from anywhere else.
static int mark_reset_age = 0;
/*
* The state transition looks like :
*
* ([(quick)sweep] means either a sweep or a quicksweep)
*
* <-[(quick)sweep]-
* |
* ----> GC_OLD <--[(quick)sweep]-------------------
* | | |
* | | GC_MARKED (in remset) |
* | | ^ | |
* | [mark] | [mark] |
* | | | | |
* | | | | |
* [sweep] | [write barrier] | |
* | v | v |
* ----- GC_OLD_MARKED <---- |
* | ^ |
* | | |
* --[quicksweep]--- |
* |
* ========= above this line objects are old ========= |
* |
* ----[new]------> GC_CLEAN ------[mark]-----------> GC_MARKED
* |
* <-[(quick)sweep]---
*
*/
// A quick sweep is a sweep where `!sweep_full`
// It means we won't touch GC_OLD_MARKED objects (old gen).
// When a reachable object has survived more than PROMOTE_AGE+1 collections
// it is tagged with GC_OLD during sweep and will be promoted on next mark
// because at that point we can know easily if it references young objects.
// Marked old objects that reference young ones are kept in the remset.
// When a write barrier triggers, the offending marked object is both queued,
// so as not to trigger the barrier again, and put in the remset.
static int64_t scanned_bytes; // young bytes scanned while marking
static int64_t perm_scanned_bytes; // old bytes scanned while marking
int prev_sweep_full = 1;
int current_sweep_full = 0;
int next_sweep_full = 0;
int under_pressure = 0;
// Full collection heuristics
static int64_t live_bytes = 0;
static int64_t promoted_bytes = 0;
static int64_t last_live_bytes = 0; // live_bytes at last collection
#ifdef __GLIBC__
// maxrss at last malloc_trim
static int64_t last_trim_maxrss = 0;
#endif
static void gc_sync_cache(jl_ptls_t ptls, jl_gc_mark_cache_t *gc_cache) JL_NOTSAFEPOINT
{
perm_scanned_bytes += gc_cache->perm_scanned_bytes;
scanned_bytes += gc_cache->scanned_bytes;
gc_cache->perm_scanned_bytes = 0;
gc_cache->scanned_bytes = 0;
}
// No other threads can be running marking at the same time
static void gc_sync_all_caches(jl_ptls_t ptls)
{
assert(gc_n_threads);
for (int t_i = 0; t_i < gc_n_threads; t_i++) {
jl_ptls_t ptls2 = gc_all_tls_states[t_i];
if (ptls2 != NULL)
gc_sync_cache(ptls, &ptls2->gc_tls.gc_cache);
}
}
// Atomically set the mark bit for object and return whether it was previously unmarked
FORCE_INLINE int gc_try_setmark_tag(jl_taggedvalue_t *o, uint8_t mark_mode) JL_NOTSAFEPOINT
{
assert(gc_marked(mark_mode));
uintptr_t tag = o->header;
if (gc_marked(tag))
return 0;
if (mark_reset_age) {
// Reset the object as if it was just allocated
mark_mode = GC_MARKED;
tag = gc_set_bits(tag, mark_mode);
}
else {
if (gc_old(tag))
mark_mode = GC_OLD_MARKED;
tag = tag | mark_mode;
assert((tag & 0x3) == mark_mode);
}
// XXX: note that marking not only sets the GC bits but also updates the
// page metadata for pool allocated objects.
// The second step is **not** idempotent, so we need a compare exchange here
// (instead of a pair of load&store) to avoid marking an object twice
tag = jl_atomic_exchange_relaxed((_Atomic(uintptr_t)*)&o->header, tag);
verify_val(jl_valueof(o));
return !gc_marked(tag);
}
// This function should be called exactly once during marking for each big
// object being marked to update the big objects metadata.
STATIC_INLINE void gc_setmark_big(jl_ptls_t ptls, jl_taggedvalue_t *o,
uint8_t mark_mode) JL_NOTSAFEPOINT
{
assert(!gc_alloc_map_is_set((char*)o));
bigval_t *hdr = bigval_header(o);
if (mark_mode == GC_OLD_MARKED) {
ptls->gc_tls.gc_cache.perm_scanned_bytes += hdr->sz;
}
else {
ptls->gc_tls.gc_cache.scanned_bytes += hdr->sz;
if (mark_reset_age) {
assert(jl_atomic_load(&gc_n_threads_marking) == 0); // `mark_reset_age` is only used during single-threaded marking
// Reset the object as if it was just allocated
gc_big_object_unlink(hdr);
gc_big_object_link(ptls->gc_tls.heap.young_generation_of_bigvals, hdr);
}
}
}
// This function should be called exactly once during marking for each pool
// object being marked to update the page metadata.
STATIC_INLINE void gc_setmark_pool_(jl_ptls_t ptls, jl_taggedvalue_t *o,
uint8_t mark_mode, jl_gc_pagemeta_t *page) JL_NOTSAFEPOINT
{
#ifdef MEMDEBUG
gc_setmark_big(ptls, o, mark_mode);
#else
if (mark_mode == GC_OLD_MARKED) {
ptls->gc_tls.gc_cache.perm_scanned_bytes += page->osize;
static_assert(sizeof(_Atomic(uint16_t)) == sizeof(page->nold), "");
jl_atomic_fetch_add_relaxed((_Atomic(uint16_t)*)&page->nold, 1);
}
else {
ptls->gc_tls.gc_cache.scanned_bytes += page->osize;
if (mark_reset_age) {
page->has_young = 1;
}
}
page->has_marked = 1;
#endif
}
STATIC_INLINE void gc_setmark_pool(jl_ptls_t ptls, jl_taggedvalue_t *o,
uint8_t mark_mode) JL_NOTSAFEPOINT
{
gc_setmark_pool_(ptls, o, mark_mode, page_metadata((char*)o));
}
STATIC_INLINE void gc_setmark(jl_ptls_t ptls, jl_taggedvalue_t *o,
uint8_t mark_mode, size_t sz) JL_NOTSAFEPOINT
{
if (sz <= GC_MAX_SZCLASS) {
gc_setmark_pool(ptls, o, mark_mode);
}
else {
gc_setmark_big(ptls, o, mark_mode);
}
}
STATIC_INLINE void gc_setmark_buf_(jl_ptls_t ptls, void *o, uint8_t mark_mode, size_t minsz) JL_NOTSAFEPOINT
{
jl_taggedvalue_t *buf = jl_astaggedvalue(o);
uint8_t bits = (gc_old(buf->header) && !mark_reset_age) ? GC_OLD_MARKED : GC_MARKED;;
// If the object is larger than the max pool size it can't be a pool object.
// This should be accurate most of the time but there might be corner cases
// where the size estimate is a little off so we do a pool lookup to make
// sure.
if (__likely(gc_try_setmark_tag(buf, mark_mode)) && !gc_verifying) {
if (minsz <= GC_MAX_SZCLASS) {
jl_gc_pagemeta_t *meta = page_metadata(buf);
if (meta != NULL) {
gc_setmark_pool_(ptls, buf, bits, meta);
return;
}
}
gc_setmark_big(ptls, buf, bits);
}
}
void gc_setmark_buf(jl_ptls_t ptls, void *o, uint8_t mark_mode, size_t minsz) JL_NOTSAFEPOINT
{
gc_setmark_buf_(ptls, o, mark_mode, minsz);
}
STATIC_INLINE void maybe_collect(jl_ptls_t ptls)
{
if (jl_atomic_load_relaxed(&gc_heap_stats.heap_size) >= jl_atomic_load_relaxed(&gc_heap_stats.heap_target) || jl_gc_debug_check_other()) {
jl_gc_collect(JL_GC_AUTO);
}
else {
jl_gc_safepoint_(ptls);
}
}
// weak references
JL_DLLEXPORT jl_weakref_t *jl_gc_new_weakref_th(jl_ptls_t ptls, jl_value_t *value)
{
jl_weakref_t *wr = (jl_weakref_t*)jl_gc_alloc(ptls, sizeof(void*),
jl_weakref_type);
wr->value = value; // NOTE: wb not needed here
small_arraylist_push(&ptls->gc_tls_common.heap.weak_refs, wr);
return wr;
}
static void clear_weak_refs(void)
{
assert(gc_n_threads);
for (int i = 0; i < gc_n_threads; i++) {
jl_ptls_t ptls2 = gc_all_tls_states[i];
if (ptls2 != NULL) {
size_t n, l = ptls2->gc_tls_common.heap.weak_refs.len;
void **lst = ptls2->gc_tls_common.heap.weak_refs.items;
for (n = 0; n < l; n++) {
jl_weakref_t *wr = (jl_weakref_t*)lst[n];
if (!gc_marked(jl_astaggedvalue(wr->value)->bits.gc))
wr->value = (jl_value_t*)jl_nothing;
}
}
}
}
static void sweep_weak_refs(void)
{
assert(gc_n_threads);
for (int i = 0; i < gc_n_threads; i++) {
jl_ptls_t ptls2 = gc_all_tls_states[i];
if (ptls2 != NULL) {
size_t n = 0;
size_t i = 0;
size_t l = ptls2->gc_tls_common.heap.weak_refs.len;
void **lst = ptls2->gc_tls_common.heap.weak_refs.items;
// filter with preserving order
for (i = 0; i < l; i++) {
jl_weakref_t *wr = (jl_weakref_t*)lst[i];
if (gc_marked(jl_astaggedvalue(wr)->bits.gc)) {
lst[n] = wr;
n++;
}
}
ptls2->gc_tls_common.heap.weak_refs.len = n;
}
}
}
STATIC_INLINE void jl_batch_accum_heap_size(jl_ptls_t ptls, uint64_t sz) JL_NOTSAFEPOINT
{
uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.alloc_acc) + sz;
if (alloc_acc < 16*1024)
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.alloc_acc, alloc_acc);
else {
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, alloc_acc);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.alloc_acc, 0);
}
}
STATIC_INLINE void jl_batch_accum_free_size(jl_ptls_t ptls, uint64_t sz) JL_NOTSAFEPOINT
{
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.free_acc, jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.free_acc) + sz);
}
// big value list
// Size includes the tag and the tag field is undefined on return (must be set before the next GC safepoint)
STATIC_INLINE jl_value_t *jl_gc_big_alloc_inner(jl_ptls_t ptls, size_t sz)
{
maybe_collect(ptls);
size_t offs = offsetof(bigval_t, header);
assert(sz >= sizeof(jl_taggedvalue_t) && "sz must include tag");
static_assert(offsetof(bigval_t, header) >= sizeof(void*), "Empty bigval header?");
static_assert(sizeof(bigval_t) % JL_HEAP_ALIGNMENT == 0, "");
size_t allocsz = LLT_ALIGN(sz + offs, JL_CACHE_BYTE_ALIGNMENT);
if (allocsz < sz) // overflow in adding offs, size was "negative"
jl_throw(jl_memory_exception);
bigval_t *v = (bigval_t*)malloc_cache_align(allocsz);
if (v == NULL)
jl_throw(jl_memory_exception);
gc_invoke_callbacks(jl_gc_cb_notify_external_alloc_t,
gc_cblist_notify_external_alloc, (v, allocsz));
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.allocd,
jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.allocd) + allocsz);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.bigalloc,
jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.bigalloc) + 1);
jl_batch_accum_heap_size(ptls, allocsz);
#ifdef MEMDEBUG
memset(v, 0xee, allocsz);
#endif
v->sz = allocsz;
#ifndef NDEBUG
v->header = 0; // must be initialized (and not gc_bigval_sentinel_tag) or gc_big_object_link assertions will get confused
#endif
gc_big_object_link(ptls->gc_tls.heap.young_generation_of_bigvals, v);
return jl_valueof(&v->header);
}
// Instrumented version of jl_gc_big_alloc_inner, called into by LLVM-generated code.
JL_DLLEXPORT jl_value_t *jl_gc_big_alloc(jl_ptls_t ptls, size_t sz, jl_value_t *type)
{
jl_value_t *val = jl_gc_big_alloc_inner(ptls, sz);
maybe_record_alloc_to_profile(val, sz, (jl_datatype_t*)type);
return val;
}
// This wrapper exists only to prevent `jl_gc_big_alloc_inner` from being inlined into
// its callers. We provide an external-facing interface for callers, and inline `jl_gc_big_alloc_inner`
// into this. (See https://github.com/JuliaLang/julia/pull/43868 for more details.)
jl_value_t *jl_gc_big_alloc_noinline(jl_ptls_t ptls, size_t sz) {
return jl_gc_big_alloc_inner(ptls, sz);
}
FORCE_INLINE void sweep_unlink_and_free(bigval_t *v) JL_NOTSAFEPOINT
{
gc_big_object_unlink(v);
gc_num.freed += v->sz;
jl_atomic_store_relaxed(&gc_heap_stats.heap_size, jl_atomic_load_relaxed(&gc_heap_stats.heap_size) - v->sz);
#ifdef MEMDEBUG
memset(v, 0xbb, v->sz);
#endif
gc_invoke_callbacks(jl_gc_cb_notify_external_free_t, gc_cblist_notify_external_free, (v));
jl_free_aligned(v);
}
static bigval_t *sweep_list_of_young_bigvals(bigval_t *young) JL_NOTSAFEPOINT
{
bigval_t *last_node = young;
bigval_t *v = young->next; // skip the sentinel
bigval_t *old = oldest_generation_of_bigvals;
int sweep_full = current_sweep_full; // don't load the global in the hot loop
while (v != NULL) {
bigval_t *nxt = v->next;
int bits = v->bits.gc;
int old_bits = bits;
if (gc_marked(bits)) {
if (sweep_full || bits == GC_MARKED) {
bits = GC_OLD;
last_node = v;
}
else { // `bits == GC_OLD_MARKED`
assert(bits == GC_OLD_MARKED);
// reached oldest generation, move from young list to old list
gc_big_object_unlink(v);
gc_big_object_link(old, v);
}
v->bits.gc = bits;
}
else {
sweep_unlink_and_free(v);
}
gc_time_count_big(old_bits, bits);
v = nxt;
}
return last_node;
}
static void sweep_list_of_oldest_bigvals(bigval_t *young) JL_NOTSAFEPOINT
{
bigval_t *v = oldest_generation_of_bigvals->next; // skip the sentinel
while (v != NULL) {
bigval_t *nxt = v->next;
assert(v->bits.gc == GC_OLD_MARKED);
v->bits.gc = GC_OLD;
gc_time_count_big(GC_OLD_MARKED, GC_OLD);
v = nxt;
}
}
static void sweep_big(jl_ptls_t ptls) JL_NOTSAFEPOINT
{
gc_time_big_start();
assert(gc_n_threads);
bigval_t *last_node_in_my_list = NULL;
for (int i = 0; i < gc_n_threads; i++) {
jl_ptls_t ptls2 = gc_all_tls_states[i];
if (ptls2 != NULL) {
bigval_t *last_node = sweep_list_of_young_bigvals(ptls2->gc_tls.heap.young_generation_of_bigvals);
if (ptls == ptls2) {
last_node_in_my_list = last_node;
}
}
}
if (current_sweep_full) {
sweep_list_of_oldest_bigvals(ptls->gc_tls.heap.young_generation_of_bigvals);
// move all nodes in `oldest_generation_of_bigvals` to my list of bigvals
assert(last_node_in_my_list != NULL);
assert(last_node_in_my_list->next == NULL);
last_node_in_my_list->next = oldest_generation_of_bigvals->next; // skip the sentinel
if (oldest_generation_of_bigvals->next != NULL) {
oldest_generation_of_bigvals->next->prev = last_node_in_my_list;
}
oldest_generation_of_bigvals->next = NULL;
}
gc_time_big_end();
}
void jl_gc_count_allocd(size_t sz) JL_NOTSAFEPOINT
{
jl_ptls_t ptls = jl_current_task->ptls;
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.allocd,
jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.allocd) + sz);
jl_batch_accum_heap_size(ptls, sz);
}
// Only safe to update the heap inside the GC
static void combine_thread_gc_counts(jl_gc_num_t *dest, int update_heap) JL_NOTSAFEPOINT
{
int gc_n_threads;
jl_ptls_t* gc_all_tls_states;
gc_n_threads = jl_atomic_load_acquire(&jl_n_threads);
gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states);
for (int i = 0; i < gc_n_threads; i++) {
jl_ptls_t ptls = gc_all_tls_states[i];
if (ptls) {
dest->allocd += (jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.allocd) + gc_num.interval);
dest->malloc += jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.malloc);
dest->realloc += jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.realloc);
dest->poolalloc += jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.poolalloc);
dest->bigalloc += jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.bigalloc);
dest->freed += jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.free_acc);
if (update_heap) {
uint64_t alloc_acc = jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.alloc_acc);
freed_in_runtime += jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.free_acc);
jl_atomic_store_relaxed(&gc_heap_stats.heap_size, alloc_acc + jl_atomic_load_relaxed(&gc_heap_stats.heap_size));
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.alloc_acc, 0);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.free_acc, 0);
}
}
}
}
static void reset_thread_gc_counts(void) JL_NOTSAFEPOINT
{
int gc_n_threads;
jl_ptls_t* gc_all_tls_states;
gc_n_threads = jl_atomic_load_acquire(&jl_n_threads);
gc_all_tls_states = jl_atomic_load_relaxed(&jl_all_tls_states);
for (int i = 0; i < gc_n_threads; i++) {
jl_ptls_t ptls = gc_all_tls_states[i];
if (ptls != NULL) {
// don't reset `pool_live_bytes` here
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.allocd, -(int64_t)gc_num.interval);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.malloc, 0);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.realloc, 0);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.poolalloc, 0);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.bigalloc, 0);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.alloc_acc, 0);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.free_acc, 0);
}
}
}
static int64_t inc_live_bytes(int64_t inc) JL_NOTSAFEPOINT
{
jl_timing_counter_inc(JL_TIMING_COUNTER_HeapSize, inc);
return live_bytes += inc;
}
void jl_gc_reset_alloc_count(void) JL_NOTSAFEPOINT
{
combine_thread_gc_counts(&gc_num, 0);
inc_live_bytes(gc_num.deferred_alloc + gc_num.allocd);
gc_num.allocd = 0;
gc_num.deferred_alloc = 0;
reset_thread_gc_counts();
}
static void jl_gc_free_memory(jl_genericmemory_t *m, int isaligned) JL_NOTSAFEPOINT
{
assert(jl_is_genericmemory(m));
assert(jl_genericmemory_how(m) == 1 || jl_genericmemory_how(m) == 2);
char *d = (char*)m->ptr;
size_t freed_bytes = memory_block_usable_size(d, isaligned);
assert(freed_bytes != 0);
if (isaligned)
jl_free_aligned(d);
else
free(d);
jl_atomic_store_relaxed(&gc_heap_stats.heap_size,
jl_atomic_load_relaxed(&gc_heap_stats.heap_size) - freed_bytes);
gc_num.freed += freed_bytes;
gc_num.freecall++;
}
static void sweep_malloced_memory(void) JL_NOTSAFEPOINT
{
gc_time_mallocd_memory_start();
assert(gc_n_threads);
for (int t_i = 0; t_i < gc_n_threads; t_i++) {
jl_ptls_t ptls2 = gc_all_tls_states[t_i];
if (ptls2 != NULL) {
size_t n = 0;
size_t l = ptls2->gc_tls_common.heap.mallocarrays.len;
void **lst = ptls2->gc_tls_common.heap.mallocarrays.items;
// filter without preserving order
while (n < l) {
jl_genericmemory_t *m = (jl_genericmemory_t*)((uintptr_t)lst[n] & ~1);
if (gc_marked(jl_astaggedvalue(m)->bits.gc)) {
n++;
}
else {
int isaligned = (uintptr_t)lst[n] & 1;
jl_gc_free_memory(m, isaligned);
l--;
lst[n] = lst[l];
}
}
ptls2->gc_tls_common.heap.mallocarrays.len = l;
}
}
gc_time_mallocd_memory_end();
}
// pool allocation
STATIC_INLINE jl_taggedvalue_t *gc_reset_page(jl_ptls_t ptls2, const jl_gc_pool_t *p, jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT
{
assert(GC_PAGE_OFFSET >= sizeof(void*));
pg->nfree = (GC_PAGE_SZ - GC_PAGE_OFFSET) / p->osize;
pg->pool_n = p - ptls2->gc_tls.heap.norm_pools;
jl_taggedvalue_t *beg = (jl_taggedvalue_t*)(pg->data + GC_PAGE_OFFSET);
pg->has_young = 0;
pg->has_marked = 0;
pg->prev_nold = 0;
pg->nold = 0;
pg->fl_begin_offset = UINT16_MAX;
pg->fl_end_offset = UINT16_MAX;
return beg;
}
jl_gc_page_stack_t global_page_pool_lazily_freed;
jl_gc_page_stack_t global_page_pool_clean;
jl_gc_page_stack_t global_page_pool_freed;
pagetable_t alloc_map;
// Add a new page to the pool. Discards any pages in `p->newpages` before.
static NOINLINE jl_taggedvalue_t *gc_add_page(jl_gc_pool_t *p) JL_NOTSAFEPOINT
{
// Do not pass in `ptls` as argument. This slows down the fast path
// in small_alloc significantly
jl_ptls_t ptls = jl_current_task->ptls;
jl_gc_pagemeta_t *pg = jl_gc_alloc_page();
pg->osize = p->osize;
pg->thread_n = ptls->tid;
set_page_metadata(pg);
push_lf_back(&ptls->gc_tls.page_metadata_allocd, pg);
jl_taggedvalue_t *fl = gc_reset_page(ptls, p, pg);
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, GC_PAGE_SZ);
p->newpages = fl;
return fl;
}
// Size includes the tag and the tag is not cleared!!
STATIC_INLINE jl_value_t *jl_gc_small_alloc_inner(jl_ptls_t ptls, int offset,
int osize)
{
// Use the pool offset instead of the pool address as the argument
// to workaround a llvm bug.
// Ref https://llvm.org/bugs/show_bug.cgi?id=27190
jl_gc_pool_t *p = (jl_gc_pool_t*)((char*)ptls + offset);
assert(jl_atomic_load_relaxed(&ptls->gc_state) == 0);
#ifdef MEMDEBUG
return jl_gc_big_alloc(ptls, osize, NULL);
#endif
maybe_collect(ptls);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.allocd,
jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.allocd) + osize);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.pool_live_bytes,
jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.pool_live_bytes) + osize);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.poolalloc,
jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.poolalloc) + 1);
// first try to use the freelist
jl_taggedvalue_t *v = p->freelist;
if (v != NULL) {
jl_taggedvalue_t *next = v->next;
p->freelist = next;
if (__unlikely(gc_page_data(v) != gc_page_data(next))) {
// we only update pg's fields when the freelist changes page
// since pg's metadata is likely not in cache
jl_gc_pagemeta_t *pg = jl_assume(page_metadata_unsafe(v));
assert(pg->osize == p->osize);
pg->nfree = 0;
pg->has_young = 1;
}
msan_allocated_memory(v, osize);
return jl_valueof(v);
}
// if the freelist is empty we reuse empty but not freed pages
v = p->newpages;
jl_taggedvalue_t *next = (jl_taggedvalue_t*)((char*)v + osize);
// If there's no pages left or the current page is used up,
// we need to use the slow path.
char *cur_page = gc_page_data((char*)v - 1);
if (__unlikely(v == NULL || cur_page + GC_PAGE_SZ < (char*)next)) {
if (v != NULL) {
// like the freelist case,
// but only update the page metadata when it is full
jl_gc_pagemeta_t *pg = jl_assume(page_metadata_unsafe((char*)v - 1));
assert(pg->osize == p->osize);
pg->nfree = 0;
pg->has_young = 1;
}
v = gc_add_page(p);
next = (jl_taggedvalue_t*)((char*)v + osize);
}
p->newpages = next;
msan_allocated_memory(v, osize);
return jl_valueof(v);
}
// Instrumented version of jl_gc_small_alloc_inner, called into by LLVM-generated code.
JL_DLLEXPORT jl_value_t *jl_gc_small_alloc(jl_ptls_t ptls, int offset, int osize, jl_value_t* type)
{
jl_value_t *val = jl_gc_small_alloc_inner(ptls, offset, osize);
maybe_record_alloc_to_profile(val, osize, (jl_datatype_t*)type);
return val;
}
// This wrapper exists only to prevent `jl_gc_small_alloc_inner` from being inlined into
// its callers. We provide an external-facing interface for callers, and inline `jl_gc_small_alloc_inner`
// into this. (See https://github.com/JuliaLang/julia/pull/43868 for more details.)
jl_value_t *jl_gc_small_alloc_noinline(jl_ptls_t ptls, int offset, int osize) {
return jl_gc_small_alloc_inner(ptls, offset, osize);
}
// Size does NOT include the type tag!!
inline jl_value_t *jl_gc_alloc_(jl_ptls_t ptls, size_t sz, void *ty)
{
jl_value_t *v;
const size_t allocsz = sz + sizeof(jl_taggedvalue_t);
if (sz <= GC_MAX_SZCLASS) {
int pool_id = jl_gc_szclass(allocsz);
jl_gc_pool_t *p = &ptls->gc_tls.heap.norm_pools[pool_id];
int osize = jl_gc_sizeclasses[pool_id];
// We call `jl_gc_small_alloc_noinline` instead of `jl_gc_small_alloc` to avoid double-counting in
// the Allocations Profiler. (See https://github.com/JuliaLang/julia/pull/43868 for more details.)
v = jl_gc_small_alloc_noinline(ptls, (char*)p - (char*)ptls, osize);
}
else {
if (allocsz < sz) // overflow in adding offs, size was "negative"
jl_throw(jl_memory_exception);
v = jl_gc_big_alloc_noinline(ptls, allocsz);
}
jl_set_typeof(v, ty);
maybe_record_alloc_to_profile(v, sz, (jl_datatype_t*)ty);
return v;
}
int jl_gc_classify_pools(size_t sz, int *osize)
{
if (sz > GC_MAX_SZCLASS)
return -1;
size_t allocsz = sz + sizeof(jl_taggedvalue_t);
int klass = jl_gc_szclass(allocsz);
*osize = jl_gc_sizeclasses[klass];
return (int)(intptr_t)(&((jl_ptls_t)0)->gc_tls.heap.norm_pools[klass]);
}
// sweep phase
gc_fragmentation_stat_t gc_page_fragmentation_stats[JL_GC_N_POOLS];
JL_DLLEXPORT double jl_gc_page_utilization_stats[JL_GC_N_MAX_POOLS];
STATIC_INLINE void gc_update_page_fragmentation_data(jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT
{
gc_fragmentation_stat_t *stats = &gc_page_fragmentation_stats[pg->pool_n];
jl_atomic_fetch_add_relaxed(&stats->n_freed_objs, pg->nfree);
jl_atomic_fetch_add_relaxed(&stats->n_pages_allocd, 1);
}
STATIC_INLINE void gc_dump_page_utilization_data(void) JL_NOTSAFEPOINT
{
for (int i = 0; i < JL_GC_N_POOLS; i++) {
gc_fragmentation_stat_t *stats = &gc_page_fragmentation_stats[i];
double utilization = 1.0;
size_t n_freed_objs = jl_atomic_load_relaxed(&stats->n_freed_objs);
size_t n_pages_allocd = jl_atomic_load_relaxed(&stats->n_pages_allocd);
if (n_pages_allocd != 0) {
utilization -= ((double)n_freed_objs * (double)jl_gc_sizeclasses[i]) / (double)n_pages_allocd / (double)GC_PAGE_SZ;
}
jl_gc_page_utilization_stats[i] = utilization;
jl_atomic_store_relaxed(&stats->n_freed_objs, 0);
jl_atomic_store_relaxed(&stats->n_pages_allocd, 0);
}
}
// Walks over a page, reconstruting the free lists if the page contains at least one live object. If not,
// queues up the page for later decommit (i.e. through `madvise` on Unix).
static void gc_sweep_page(gc_page_profiler_serializer_t *s, jl_gc_pool_t *p, jl_gc_page_stack_t *allocd, jl_gc_pagemeta_t *pg, int osize) JL_NOTSAFEPOINT
{
char *data = pg->data;
jl_taggedvalue_t *v0 = (jl_taggedvalue_t*)(data + GC_PAGE_OFFSET);
char *lim = data + GC_PAGE_SZ - osize;
char *lim_newpages = data + GC_PAGE_SZ;
if (gc_page_data((char*)p->newpages - 1) == data) {
lim_newpages = (char*)p->newpages;
}
size_t old_nfree = pg->nfree;
size_t nfree;
// avoid loading a global variable in the hot path
int page_profile_enabled = gc_page_profile_is_enabled();
gc_page_serializer_init(s, pg);
int re_use_page = 1;
int freedall = 1;
int pg_skpd = 1;
if (!pg->has_marked) {
re_use_page = 0;
nfree = (GC_PAGE_SZ - GC_PAGE_OFFSET) / osize;
gc_page_profile_write_empty_page(s, page_profile_enabled);
goto done;
}
// For quick sweep, we might be able to skip the page if the page doesn't
// have any young live cell before marking.
if (!current_sweep_full && !pg->has_young) {
assert(!prev_sweep_full || pg->prev_nold >= pg->nold);
if (!prev_sweep_full || pg->prev_nold == pg->nold) {
freedall = 0;
nfree = pg->nfree;
gc_page_profile_write_empty_page(s, page_profile_enabled);
goto done;
}
}
pg_skpd = 0;
{ // scope to avoid clang goto errors
int has_marked = 0;
int has_young = 0;
int16_t prev_nold = 0;
int pg_nfree = 0;
jl_taggedvalue_t *fl = NULL;
jl_taggedvalue_t **pfl = &fl;
jl_taggedvalue_t **pfl_begin = NULL;
// collect page profile
jl_taggedvalue_t *v = v0;
if (page_profile_enabled) {
while ((char*)v <= lim) {
int bits = v->bits.gc;
if (!gc_marked(bits) || (char*)v >= lim_newpages) {
gc_page_profile_write_garbage(s, page_profile_enabled);
}
else {
gc_page_profile_write_live_obj(s, v, page_profile_enabled);
}
v = (jl_taggedvalue_t*)((char*)v + osize);
}
v = v0;
}
// sweep the page
while ((char*)v <= lim) {
int bits = v->bits.gc;
// if an object is past `lim_newpages` then we can guarantee it's garbage
if (!gc_marked(bits) || (char*)v >= lim_newpages) {
*pfl = v;
pfl = &v->next;
pfl_begin = (pfl_begin != NULL) ? pfl_begin : pfl;
pg_nfree++;
}
else { // marked young or old
if (current_sweep_full || bits == GC_MARKED) { // old enough
bits = v->bits.gc = GC_OLD; // promote
}
prev_nold++;
has_marked |= gc_marked(bits);
freedall = 0;
}
v = (jl_taggedvalue_t*)((char*)v + osize);
}
assert(!freedall);
pg->has_marked = has_marked;
pg->has_young = has_young;
if (pfl_begin) {
pg->fl_begin_offset = (char*)pfl_begin - data;
pg->fl_end_offset = (char*)pfl - data;
}
else {
pg->fl_begin_offset = UINT16_MAX;
pg->fl_end_offset = UINT16_MAX;
}
pg->nfree = pg_nfree;
if (current_sweep_full) {
pg->nold = 0;
pg->prev_nold = prev_nold;
}
}
nfree = pg->nfree;
done:
if (re_use_page) {
gc_update_page_fragmentation_data(pg);
push_lf_back(allocd, pg);
}
else {
jl_atomic_fetch_add_relaxed(&gc_heap_stats.heap_size, -GC_PAGE_SZ);
gc_alloc_map_set(pg->data, GC_PAGE_LAZILY_FREED);
push_lf_back(&global_page_pool_lazily_freed, pg);
}
gc_page_profile_write_to_file(s);
gc_time_count_page(freedall, pg_skpd);
jl_ptls_t ptls = jl_current_task->ptls;
// Note that we aggregate the `pool_live_bytes` over all threads before returning this
// value to the user. It doesn't matter how the `pool_live_bytes` are partitioned among
// the threads as long as the sum is correct. Let's add the `pool_live_bytes` to the current thread
// instead of adding it to the thread that originally allocated the page, so we can avoid
// an atomic-fetch-add here.
size_t delta = (GC_PAGE_SZ - GC_PAGE_OFFSET - nfree * osize);
jl_atomic_store_relaxed(&ptls->gc_tls_common.gc_num.pool_live_bytes,
jl_atomic_load_relaxed(&ptls->gc_tls_common.gc_num.pool_live_bytes) + delta);
jl_atomic_fetch_add_relaxed((_Atomic(int64_t) *)&gc_num.freed, (nfree - old_nfree) * osize);
}
// the actual sweeping over all allocated pages in a memory pool
STATIC_INLINE void gc_sweep_pool_page(gc_page_profiler_serializer_t *s, jl_gc_page_stack_t *allocd, jl_gc_pagemeta_t *pg) JL_NOTSAFEPOINT
{
int p_n = pg->pool_n;
int t_n = pg->thread_n;
jl_ptls_t ptls2 = gc_all_tls_states[t_n];
jl_gc_pool_t *p = &ptls2->gc_tls.heap.norm_pools[p_n];
int osize = pg->osize;
gc_sweep_page(s, p, allocd, pg, osize);
}
// sweep over all memory that is being used and not in a pool
static void gc_sweep_other(jl_ptls_t ptls, int sweep_full) JL_NOTSAFEPOINT
{
uint64_t t_free_mallocd_memory_start = jl_hrtime();
gc_sweep_foreign_objs();
sweep_malloced_memory();
sweep_big(ptls);
uint64_t t_free_mallocd_memory_end = jl_hrtime();
gc_num.total_sweep_free_mallocd_memory_time += t_free_mallocd_memory_end - t_free_mallocd_memory_start;
jl_engine_sweep(gc_all_tls_states);
}
// wake up all threads to sweep the stacks
void gc_sweep_wake_all_stacks(jl_ptls_t ptls) JL_NOTSAFEPOINT
{
uv_mutex_lock(&gc_threads_lock);
int first = gc_first_parallel_collector_thread_id();