qr_reader-native 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE-mruby.txt +22 -0
- data/LICENSE-spinel.txt +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/ext/qr_reader_native/SPINEL_VERSION +1 -0
- data/ext/qr_reader_native/extconf.rb +8 -0
- data/ext/qr_reader_native/mruby/error.h +13 -0
- data/ext/qr_reader_native/mruby/internal.h +145 -0
- data/ext/qr_reader_native/mruby/string.h +22 -0
- data/ext/qr_reader_native/mruby.h +93 -0
- data/ext/qr_reader_native/mruby_shim.h +118 -0
- data/ext/qr_reader_native/qr_reader_native.c +13513 -0
- data/ext/qr_reader_native/qr_reader_native.h +29 -0
- data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
- data/ext/qr_reader_native/re_cased.h +71 -0
- data/ext/qr_reader_native/re_compile.c +5784 -0
- data/ext/qr_reader_native/re_ctype.h +3540 -0
- data/ext/qr_reader_native/re_exec.c +2190 -0
- data/ext/qr_reader_native/re_internal.h +671 -0
- data/ext/qr_reader_native/re_prop.h +4629 -0
- data/ext/qr_reader_native/re_spinel.c +363 -0
- data/ext/qr_reader_native/re_spinel.h +22 -0
- data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
- data/ext/qr_reader_native/re_utf8.c +156 -0
- data/ext/qr_reader_native/sp_alloc.c +1447 -0
- data/ext/qr_reader_native/sp_alloc.h +1196 -0
- data/ext/qr_reader_native/sp_argf.h +33 -0
- data/ext/qr_reader_native/sp_array.c +1052 -0
- data/ext/qr_reader_native/sp_array.h +456 -0
- data/ext/qr_reader_native/sp_bigint.c +5826 -0
- data/ext/qr_reader_native/sp_bigint.h +117 -0
- data/ext/qr_reader_native/sp_cold.c +5259 -0
- data/ext/qr_reader_native/sp_compat.h +402 -0
- data/ext/qr_reader_native/sp_core.c +818 -0
- data/ext/qr_reader_native/sp_core.h +56 -0
- data/ext/qr_reader_native/sp_crypto.c +877 -0
- data/ext/qr_reader_native/sp_crypto.h +169 -0
- data/ext/qr_reader_native/sp_dtoa.c +1533 -0
- data/ext/qr_reader_native/sp_dtoa.h +29 -0
- data/ext/qr_reader_native/sp_encoding_names.h +172 -0
- data/ext/qr_reader_native/sp_enum.h +86 -0
- data/ext/qr_reader_native/sp_exc.c +1185 -0
- data/ext/qr_reader_native/sp_exc.h +186 -0
- data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
- data/ext/qr_reader_native/sp_fiber.c +889 -0
- data/ext/qr_reader_native/sp_fiber.h +162 -0
- data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
- data/ext/qr_reader_native/sp_format.c +695 -0
- data/ext/qr_reader_native/sp_format.h +83 -0
- data/ext/qr_reader_native/sp_gc.c +1793 -0
- data/ext/qr_reader_native/sp_gc.h +762 -0
- data/ext/qr_reader_native/sp_hash.c +131 -0
- data/ext/qr_reader_native/sp_hash.h +131 -0
- data/ext/qr_reader_native/sp_inspect.c +215 -0
- data/ext/qr_reader_native/sp_inspect.h +128 -0
- data/ext/qr_reader_native/sp_io.c +1676 -0
- data/ext/qr_reader_native/sp_io.h +272 -0
- data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
- data/ext/qr_reader_native/sp_iobuffer.h +167 -0
- data/ext/qr_reader_native/sp_marshal.c +487 -0
- data/ext/qr_reader_native/sp_marshal.h +60 -0
- data/ext/qr_reader_native/sp_net.c +1029 -0
- data/ext/qr_reader_native/sp_net.h +205 -0
- data/ext/qr_reader_native/sp_pack.c +1254 -0
- data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
- data/ext/qr_reader_native/sp_proc.c +410 -0
- data/ext/qr_reader_native/sp_proc.h +327 -0
- data/ext/qr_reader_native/sp_process.c +547 -0
- data/ext/qr_reader_native/sp_process_status.c +211 -0
- data/ext/qr_reader_native/sp_process_status.h +66 -0
- data/ext/qr_reader_native/sp_random.c +287 -0
- data/ext/qr_reader_native/sp_random.h +52 -0
- data/ext/qr_reader_native/sp_range.h +144 -0
- data/ext/qr_reader_native/sp_re.c +1379 -0
- data/ext/qr_reader_native/sp_re.h +186 -0
- data/ext/qr_reader_native/sp_re_names.h +59 -0
- data/ext/qr_reader_native/sp_sched.c +4044 -0
- data/ext/qr_reader_native/sp_sched.h +295 -0
- data/ext/qr_reader_native/sp_slab.c +1507 -0
- data/ext/qr_reader_native/sp_str.c +1462 -0
- data/ext/qr_reader_native/sp_str.h +306 -0
- data/ext/qr_reader_native/sp_str_crypt.c +23 -0
- data/ext/qr_reader_native/sp_string.c +92 -0
- data/ext/qr_reader_native/sp_string.h +253 -0
- data/ext/qr_reader_native/sp_system.c +78 -0
- data/ext/qr_reader_native/sp_system.h +18 -0
- data/ext/qr_reader_native/sp_time.c +860 -0
- data/ext/qr_reader_native/sp_time.h +100 -0
- data/ext/qr_reader_native/sp_types.h +292 -0
- data/ext/qr_reader_native/spinel_rt.h +19017 -0
- data/ext/qr_reader_native/unicase.c +399 -0
- data/ext/qr_reader_native/unicase.h +901 -0
- data/lib/qr_reader/native.rb +6 -0
- metadata +153 -0
|
@@ -0,0 +1,1793 @@
|
|
|
1
|
+
/* sp_gc.c -- the mark/sweep collector's non-inline machinery.
|
|
2
|
+
* See sp_gc.h. The program root-marking and string-heap sweep are
|
|
3
|
+
* supplied by the generated TU via sp_gc_mark_globals_hook /
|
|
4
|
+
* sp_gc_str_sweep_hook. */
|
|
5
|
+
#include <stdlib.h>
|
|
6
|
+
#include <stdio.h>
|
|
7
|
+
#include <string.h>
|
|
8
|
+
#include <stdint.h>
|
|
9
|
+
/* malloc_trim is glibc's (returns freed arena pages to the OS); no other libc
|
|
10
|
+
has it, so elsewhere the trim below is a no-op and RSS is whatever the
|
|
11
|
+
allocator keeps. An interposed allocator (jemalloc) makes it a no-op on
|
|
12
|
+
glibc too: the symbol walks glibc's own, then empty, arena. */
|
|
13
|
+
#if defined(__GLIBC__)
|
|
14
|
+
#include <malloc.h>
|
|
15
|
+
#else
|
|
16
|
+
#define malloc_trim(x) ((void)0)
|
|
17
|
+
#endif
|
|
18
|
+
#include <unistd.h>
|
|
19
|
+
#include <time.h>
|
|
20
|
+
#include "sp_gc.h"
|
|
21
|
+
#include <signal.h>
|
|
22
|
+
#include <unistd.h>
|
|
23
|
+
#include "sp_marshal.h" /* sp_marshal_vt -- the instance lives here (always linked) */
|
|
24
|
+
void sp_str_verify_begin(void); /* lib/sp_alloc.c: string side of the generational check */
|
|
25
|
+
void sp_str_lcache_clear(void); /* lib/sp_alloc.c: this thread's string-length cache */
|
|
26
|
+
void sp_str_mark_settle(int full); /* lib/sp_alloc.c: the slab strings' old total, from this mark */
|
|
27
|
+
size_t sp_str_verify_end(void);
|
|
28
|
+
void sp_str_verify_probe_arm(void);
|
|
29
|
+
int sp_str_verify_probe_hit(void);
|
|
30
|
+
void sp_str_verify_probe_done(void);
|
|
31
|
+
int sp_str_sweep_begin(int *major); /* lib/sp_alloc.c: the string heap's gate, taken here before the concurrent sweep starts */
|
|
32
|
+
|
|
33
|
+
/* ---- Globals shared with the generated TU (declared extern in sp_gc.h) ---- */
|
|
34
|
+
SP_TLS void **sp_gc_roots[SP_GC_STACK_MAX]; /* per-worker (SP_TLS); see sp_gc.h */
|
|
35
|
+
/* The overflow segment: roots SP_GC_STACK_MAX and up, grown on demand and
|
|
36
|
+
kept for the worker's life. A push the array cannot take used to be dropped
|
|
37
|
+
on the floor, and a deep enough recursion then had the collector free
|
|
38
|
+
strings its live frames still named. */
|
|
39
|
+
SP_TLS void ***sp_gc_roots_ext = NULL;
|
|
40
|
+
static SP_TLS int sp_gc_roots_ext_cap = 0;
|
|
41
|
+
int sp_gc_roots_ext_reserve(int n) {
|
|
42
|
+
int need = n - SP_GC_STACK_MAX;
|
|
43
|
+
if (need <= sp_gc_roots_ext_cap) return 1;
|
|
44
|
+
int nc = sp_gc_roots_ext_cap ? sp_gc_roots_ext_cap : 4096;
|
|
45
|
+
while (nc < need) nc *= 2;
|
|
46
|
+
void ***nx = (void ***)realloc(sp_gc_roots_ext, sizeof(void **) * (size_t)nc);
|
|
47
|
+
if (!nx) return 0;
|
|
48
|
+
sp_gc_roots_ext = nx; sp_gc_roots_ext_cap = nc;
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
int sp_gc_root_push_slow(void **p) {
|
|
52
|
+
if (!sp_gc_roots_ext_reserve(sp_gc_nroots + 1)) return 0;
|
|
53
|
+
sp_gc_roots_ext[sp_gc_nroots - SP_GC_STACK_MAX] = p;
|
|
54
|
+
sp_gc_nroots++;
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
SP_TLS int sp_gc_nroots = 0;
|
|
58
|
+
#ifdef SP_THREADS
|
|
59
|
+
sp_gc_wslot_t sp_gc_wslot[SP_MAX_WORKERS]; /* per-worker young head + flush delta, cache-line padded */
|
|
60
|
+
#else
|
|
61
|
+
sp_gc_hdr *sp_gc_heap = NULL;
|
|
62
|
+
#endif
|
|
63
|
+
size_t sp_gc_bytes = 0;
|
|
64
|
+
size_t sp_gc_old_bytes = 0;
|
|
65
|
+
int sp_gc_cycle = 0;
|
|
66
|
+
void (*sp_gc_mark_suspended_fibers_hook)(void) = NULL;
|
|
67
|
+
void (*sp_gc_mark_globals_hook)(void) = NULL;
|
|
68
|
+
void (*sp_gc_str_sweep_hook)(void) = NULL;
|
|
69
|
+
int (*sp_gc_str_major_due_hook)(void) = NULL;
|
|
70
|
+
int sp_gc_root_phase = 0; /* the mark is walking the C roots (see sp_gc_mark_all) */
|
|
71
|
+
const char *(*sp_sym_name_fn)(sp_sym) = NULL;
|
|
72
|
+
int (*sp_json_kind_fn)(sp_RbVal) = NULL;
|
|
73
|
+
sp_int (*sp_json_len_fn)(sp_RbVal) = NULL;
|
|
74
|
+
sp_RbVal (*sp_json_aref_fn)(sp_RbVal, sp_int) = NULL;
|
|
75
|
+
void (*sp_json_hpair_fn)(sp_RbVal, sp_int, sp_RbVal *, sp_RbVal *) = NULL;
|
|
76
|
+
sp_RbVal (*sp_json_mk_hash_fn)(void) = NULL;
|
|
77
|
+
sp_sym (*sp_json_sym_intern_fn)(const char *) = NULL;
|
|
78
|
+
void (*sp_json_hash_set_fn)(sp_RbVal, const char *, sp_RbVal) = NULL;
|
|
79
|
+
const char *(*sp_poly_inspect_fn)(sp_RbVal) = NULL;
|
|
80
|
+
const char *(*sp_poly_to_s_fn)(sp_RbVal) = NULL;
|
|
81
|
+
sp_RbVal (*sp_obj_to_hash_fn)(sp_RbVal) = NULL;
|
|
82
|
+
/* A user class's own #to_json, keyed by cls_id: the json package asks for it
|
|
83
|
+
before falling back to the generic field reflection. */
|
|
84
|
+
const char *(*sp_obj_to_json_fn)(sp_RbVal) = NULL;
|
|
85
|
+
sp_RbVal (*sp_obj_to_h_fn)(sp_RbVal) = NULL;
|
|
86
|
+
sp_RbVal (*sp_obj_struct_values_fn)(sp_RbVal) = NULL;
|
|
87
|
+
sp_RbVal (*sp_obj_to_a_fn)(sp_RbVal) = NULL;
|
|
88
|
+
sp_RbVal (*sp_obj_to_ary_fn)(sp_RbVal) = NULL;
|
|
89
|
+
sp_RbVal (*sp_obj_deconstruct_fn)(sp_RbVal) = NULL;
|
|
90
|
+
int (*sp_obj_is_data_fn)(int) = NULL;
|
|
91
|
+
sp_RbVal (*sp_obj_with_fn)(sp_RbVal, sp_RbVal) = NULL;
|
|
92
|
+
const char *(*sp_obj_inspect_fn)(int cls_id, void *p) = NULL;
|
|
93
|
+
const char *(*sp_obj_to_s_fn)(int cls_id, void *p) = NULL;
|
|
94
|
+
sp_int (*sp_obj_to_int_fn)(int cls_id, void *p, int *ok) = NULL;
|
|
95
|
+
const char *(*sp_obj_to_str_fn)(int cls_id, void *p) = NULL;
|
|
96
|
+
const char *(*sp_obj_to_path_fn)(int cls_id, void *p) = NULL;
|
|
97
|
+
int (*sp_obj_conv_fn)(int cls_id, void *p, int which, sp_RbVal *out) = NULL;
|
|
98
|
+
const char *(*sp_obj_cls_name_fn)(int cls_id) = NULL;
|
|
99
|
+
int (*sp_bsub_cls_fn)(sp_RbVal v) = NULL;
|
|
100
|
+
int (*sp_class_le_id_fn)(int sub, int super) = NULL;
|
|
101
|
+
sp_RbVal (*sp_class_cmp_fn)(sp_RbVal a, sp_RbVal b) = NULL;
|
|
102
|
+
int (*sp_class_kind_of_name_fn)(int cls, const char *name) = NULL;
|
|
103
|
+
int (*sp_class_is_module_fn)(sp_Class c) = NULL;
|
|
104
|
+
sp_marshal_vt sp_marshal_v = {0}; /* filled by the generated TU (sp_tu_init) */
|
|
105
|
+
|
|
106
|
+
/* The concurrent sweep (sp_sched.c): start takes the lists the barrier
|
|
107
|
+
detached and sweeps them beside the mutators; wait, at the next barrier,
|
|
108
|
+
finishes the previous one -- joins the sweepers, splices the survivors,
|
|
109
|
+
recycles the pooled dead, retunes the string budget. NULL: sweep under the
|
|
110
|
+
barrier as before. */
|
|
111
|
+
void (*sp_gc_conc_sweep_hook)(int full, int str_sweep, int str_major) = NULL;
|
|
112
|
+
void (*sp_gc_conc_wait_hook)(void) = NULL;
|
|
113
|
+
int sp_gc_conc_on = -1; /* SPINEL_GC_CONC=0 turns the concurrent sweep off */
|
|
114
|
+
int sp_gc_conc_promote = 0; /* this cycle's mark promotes what it marks (the sweep is concurrent) */
|
|
115
|
+
size_t sp_gc_mk_bytes = 0, sp_gc_mk_young_bytes = 0; /* bytes the mark reached, and of those the young ones */
|
|
116
|
+
SP_TLS int sp_gc_in_sweeper = 0; /* a sweeper thread: finalizers skip the per-worker byte accounting */
|
|
117
|
+
/* ---- Collector-private globals ---- */
|
|
118
|
+
static int sp_gc_verify = 0; /* 1: SPINEL_GC_VERIFY; 2: only its membership test on the mark path (SPINEL_GC_STRESS=2) */
|
|
119
|
+
static sp_gc_hdr *sp_gc_old_heap = NULL;
|
|
120
|
+
/* The mark stack grows on demand: overflowing it used to drop the walk into
|
|
121
|
+
recursive scanning, and a live set of a few hundred thousand containers
|
|
122
|
+
(an A* frontier of [vertex, priority] pairs) then overflowed the C stack
|
|
123
|
+
and crashed the process mid-collection. SP_GC_MARK_STACK_MAX is its
|
|
124
|
+
initial capacity, allocated as one block on the first mark (512 KB on
|
|
125
|
+
LP64). A build can set it with -DSP_GC_MARK_STACK_MAX=<n>, as it can
|
|
126
|
+
SP_GC_STACK_MAX; the stack still grows from there on demand. */
|
|
127
|
+
#ifndef SP_GC_MARK_STACK_MAX
|
|
128
|
+
#define SP_GC_MARK_STACK_MAX (1024*64)
|
|
129
|
+
#endif
|
|
130
|
+
/* Per thread: the collector's, and under the parallel mark each helper's
|
|
131
|
+
own; a scan pushes onto the stack of the thread running it. */
|
|
132
|
+
static SP_TLS void **sp_gc_mark_stack = NULL;
|
|
133
|
+
static SP_TLS int sp_gc_mark_top = 0;
|
|
134
|
+
static SP_TLS int sp_gc_mark_cap = 0;
|
|
135
|
+
/* What this thread's marking counted, folded into the totals when its
|
|
136
|
+
drain ends (a shared counter per marked object would bounce a line
|
|
137
|
+
between the markers on every object). */
|
|
138
|
+
static SP_TLS size_t sp_gc_mkl_marked = 0, sp_gc_mkl_bytes = 0, sp_gc_mkl_young = 0;
|
|
139
|
+
static int sp_gc_par_mark = 0; /* the drain is running on several threads */
|
|
140
|
+
static SP_TLS int sp_gc_mk_is_collector=0;
|
|
141
|
+
unsigned long long sp_gc_ph_mk_by_helpers=0, sp_gc_ph_mk_spills=0, sp_gc_ph_mk_takes=0;
|
|
142
|
+
double sp_gc_ph_mk_drain=0, sp_gc_ph_mk_join=0, sp_gc_ph_mk_idle=0;
|
|
143
|
+
int sp_gc_par_mark_on = -1; /* SPINEL_GC_PAR_MARK=0 turns it off */
|
|
144
|
+
static void sp_gc_mkl_fold(void);
|
|
145
|
+
static sp_gc_hdr **sp_gc_vsnap = NULL;
|
|
146
|
+
static size_t sp_gc_vsnap_n = 0, sp_gc_vsnap_cap = 0;
|
|
147
|
+
static size_t sp_gc_max_bytes = 0;
|
|
148
|
+
static int sp_gc_max_bytes_init = 0;
|
|
149
|
+
#define SP_GC_FULL_INTERVAL 8
|
|
150
|
+
#define SP_GC_FULL_INTERVAL_MIN 1 /* the adaptive floor under the minor mark: every cycle full */
|
|
151
|
+
#define SP_GC_PROMOTED_DEAD_CUT (1.0/16.0) /* dead-at-full per minor, as a share of the live set */
|
|
152
|
+
static int sp_gc_minors_since_full = 0;
|
|
153
|
+
static double sp_gc_last_per_minor = 0;
|
|
154
|
+
static int sp_gc_fulls_at_min = 0;
|
|
155
|
+
|
|
156
|
+
/* Major-collection cadence. A compile-time constant until the measurements in
|
|
157
|
+
docs/internals/gc-string-minor-design.md showed it is where essentially all
|
|
158
|
+
of a minor mark's remaining cost lives (231 ms at 8 against 46 at 64 on a
|
|
159
|
+
retained-heavy array workload). Runtime state, in this file, so the header
|
|
160
|
+
every generated TU includes does not change. */
|
|
161
|
+
#define SP_GC_FULL_INTERVAL_MAX 128
|
|
162
|
+
static int sp_gc_full_interval = SP_GC_FULL_INTERVAL;
|
|
163
|
+
/* What the last full sweep found LIVE in the old generation, and the slack a
|
|
164
|
+
heap may grow past it before a full is forced. The adaptive interval bounds
|
|
165
|
+
the TIME between full sweeps and nothing bounded the SPACE: a phase that
|
|
166
|
+
promotes heavily piles garbage onto a list nothing walks, sp_gc_old_bytes
|
|
167
|
+
counts it as live, the object threshold retunes off that inflated total, and
|
|
168
|
+
the interval's own correction is gated behind the full cycle it is
|
|
169
|
+
postponing (#4076). A heap that really is live never trips this, because old
|
|
170
|
+
stays where the last sweep left it. */
|
|
171
|
+
size_t sp_gc_old_live = 0; /* what the last full found live in the old generation */
|
|
172
|
+
#define SP_GC_OLD_GROWTH_SLACK (4u * 1024u * 1024u)
|
|
173
|
+
static int sp_gc_full_interval_fixed = 0; /* SPINEL_GC_FULL_INTERVAL pins it */
|
|
174
|
+
int sp_gc_full_runs = 0; /* read by GC.stat (lib/sp_cold.c) */
|
|
175
|
+
int sp_gc_trim_wanted = 0; /* a full cycle asks the trimmer thread for a malloc_trim (sp_sched.c) */
|
|
176
|
+
int sp_gc_trimmer_on = 0; /* the trimmer thread is running */
|
|
177
|
+
/* High-water mark of the remembered set, for GC.stat. A minor collection
|
|
178
|
+
walks every entry and runs its scan, so a workload that stores into most
|
|
179
|
+
of its old heap makes the minor do the full mark's work plus the
|
|
180
|
+
bookkeeping -- which is the shape to check when a minor mark is SLOWER
|
|
181
|
+
than the whole-heap one on some route. */
|
|
182
|
+
int sp_gc_rem_peak = 0;
|
|
183
|
+
|
|
184
|
+
/* Issue #755: bail out cleanly on OOM rather than returning NULL into a
|
|
185
|
+
caller that would deref it next. */
|
|
186
|
+
void sp_oom_die(void){fputs("unhandled exception: out of memory\n",stderr);exit(1);}
|
|
187
|
+
|
|
188
|
+
/* ---- GC verify (SPINEL_GC_VERIFY=1): a sorted snapshot of every
|
|
189
|
+
* registered header, so the scan-time membership test is O(log n). ---- */
|
|
190
|
+
static int sp_gc_vsnap_cmp(const void *a, const void *b){ uintptr_t x=(uintptr_t)*(sp_gc_hdr*const*)a, y=(uintptr_t)*(sp_gc_hdr*const*)b; return x<y?-1:x>y?1:0; }
|
|
191
|
+
static void sp_gc_vsnap_push(sp_gc_hdr *h){ if(sp_gc_vsnap_n==sp_gc_vsnap_cap){ size_t c=sp_gc_vsnap_cap?sp_gc_vsnap_cap*2:1024; sp_gc_hdr**n=(sp_gc_hdr**)realloc(sp_gc_vsnap,c*sizeof(sp_gc_hdr*)); if(!n)sp_oom_die(); sp_gc_vsnap=n; sp_gc_vsnap_cap=c; } sp_gc_vsnap[sp_gc_vsnap_n++]=h; }
|
|
192
|
+
static void sp_gc_verify_snapshot(void){ sp_gc_vsnap_n=0;
|
|
193
|
+
#ifdef SP_THREADS
|
|
194
|
+
{ int n=sp_active_workers; if(n<1)n=1; if(n>SP_MAX_WORKERS)n=SP_MAX_WORKERS; for(int i=0;i<n;i++)for(sp_gc_hdr*p=sp_gc_wslot[i].young;p;p=p->next)sp_gc_vsnap_push(p); }
|
|
195
|
+
#else
|
|
196
|
+
for(sp_gc_hdr*p=sp_gc_heap;p;p=p->next)sp_gc_vsnap_push(p);
|
|
197
|
+
#endif
|
|
198
|
+
for(sp_gc_hdr*p=sp_gc_old_heap;p;p=p->next)sp_gc_vsnap_push(p); if(sp_gc_vsnap_n>1)qsort(sp_gc_vsnap,sp_gc_vsnap_n,sizeof(sp_gc_hdr*),sp_gc_vsnap_cmp); }
|
|
199
|
+
static int sp_gc_obj_registered(sp_gc_hdr *h){ if(sp_slab_owns(h))return sp_slab_is_live(h)&&!sp_slab_is_str(h); uintptr_t hv=(uintptr_t)h; size_t lo=0,hi=sp_gc_vsnap_n; while(lo<hi){ size_t m=lo+(hi-lo)/2; uintptr_t x=(uintptr_t)sp_gc_vsnap[m]; if(x==hv)return 1; if(x<hv)lo=m+1; else hi=m; } return 0; }
|
|
200
|
+
/* Verify diagnostics: which phase/slot the bad pointer came from. */
|
|
201
|
+
int sp_gc_verify_on(void) { return sp_gc_verify; }
|
|
202
|
+
const char *sp_gc_dbg_phase = "?";
|
|
203
|
+
void *sp_gc_dbg_ctx = NULL;
|
|
204
|
+
static void sp_gc_verify_fail(void *obj, sp_gc_hdr *h){
|
|
205
|
+
/* SPINEL_GC_STRESS=2: the slot was freed and the quarantine still holds
|
|
206
|
+
it, so the reference outlived a collection that could not see it. ctx is
|
|
207
|
+
the root-stack entry in the root phase, and the object whose scan found
|
|
208
|
+
the reference in the scan, remembered and pinned phases. */
|
|
209
|
+
if (sp_slab_is_quarantined(h)) {
|
|
210
|
+
const char *ph = sp_gc_dbg_phase;
|
|
211
|
+
fprintf(stderr, "\n*** SPINEL_GC_STRESS: the mark reached a freed slot ***\n"
|
|
212
|
+
" obj = %p phase = %s ctx = %p\n", obj, ph, sp_gc_dbg_ctx);
|
|
213
|
+
sp_slab_describe(h);
|
|
214
|
+
if (sp_gc_verify == 1) sp_slab_history(h);
|
|
215
|
+
if (!sp_slab_is_str(h)) fprintf(stderr, " freed: scan=%p size=%zu\n", (void *)(uintptr_t)h->scan, (size_t)h->size);
|
|
216
|
+
if (sp_gc_dbg_ctx && (ph[0] == 's' || ph[0] == 'p' || (ph[0] == 'r' && ph[1] == 'e'))) {
|
|
217
|
+
sp_gc_hdr *hc = (sp_gc_hdr *)((char *)sp_gc_dbg_ctx - sizeof(sp_gc_hdr));
|
|
218
|
+
fprintf(stderr, " holder: scan=%p size=%zu old=%d\n", (void *)(uintptr_t)hc->scan, (size_t)hc->size, (int)hc->old);
|
|
219
|
+
}
|
|
220
|
+
fflush(stderr);
|
|
221
|
+
abort();
|
|
222
|
+
}
|
|
223
|
+
fprintf(stderr, " [phase=%s ctx=%p]\n", sp_gc_dbg_phase, sp_gc_dbg_ctx);
|
|
224
|
+
sp_slab_describe(h);
|
|
225
|
+
if (sp_gc_dbg_ctx) sp_slab_describe((char*)sp_gc_dbg_ctx - sizeof(sp_gc_hdr));
|
|
226
|
+
if (sp_gc_dbg_ctx && sp_gc_dbg_phase && sp_gc_dbg_phase[0] == 's') {
|
|
227
|
+
sp_gc_hdr *hc = (sp_gc_hdr *)((char *)sp_gc_dbg_ctx - sizeof(sp_gc_hdr));
|
|
228
|
+
fprintf(stderr, " holder scan=%p size=%u old=%d\n", (void *)hc->scan, (unsigned)hc->size, (int)hc->old);
|
|
229
|
+
}
|
|
230
|
+
fprintf(stderr,
|
|
231
|
+
"\n*** SPINEL_GC_VERIFY: collector reached a non-heap/corrupt object ***\n"
|
|
232
|
+
" obj = %p\n header = %p\n"
|
|
233
|
+
" This pointer is on the GC mark path but is not a registered live GC\n"
|
|
234
|
+
" allocation -- most likely a raw/aliased pointer (e.g. into a string or\n"
|
|
235
|
+
" builder buffer) reachable from a root or a scanned field. Invoking its\n"
|
|
236
|
+
" scan hook would jump through a bogus function pointer.\n",
|
|
237
|
+
obj, (void*)h);
|
|
238
|
+
fflush(stderr);
|
|
239
|
+
fprintf(stderr, " ->scan = %p ->size = %zu\n\n",
|
|
240
|
+
(void*)(uintptr_t)h->scan, (size_t)h->size);
|
|
241
|
+
abort();
|
|
242
|
+
}
|
|
243
|
+
/* Under verify, a fault ON the mark path is the interesting case and the one
|
|
244
|
+
the existing check cannot reach: sp_gc_mark reads the marker byte BEFORE it
|
|
245
|
+
can ask whether the object is registered, so a pointer whose [-1] is
|
|
246
|
+
unreadable (a bare literal at the start of a rodata page, an interior
|
|
247
|
+
pointer into an unmapped neighbour) takes the process down with nothing said.
|
|
248
|
+
Name the root slot the collector was walking, which is what turns an
|
|
249
|
+
unreproducible crash into a variable. Re-raises so the core dump is still
|
|
250
|
+
produced. */
|
|
251
|
+
static void sp_gc_fault_report(int sig) {
|
|
252
|
+
static const char hex[] = "0123456789abcdef";
|
|
253
|
+
char buf[256]; size_t o = 0;
|
|
254
|
+
const char *m1 = "\n*** SPINEL_GC_VERIFY: fault on the GC mark path (signal ";
|
|
255
|
+
for (const char *p = m1; *p; p++) buf[o++] = *p;
|
|
256
|
+
buf[o++] = (char)('0' + (sig / 10) % 10); buf[o++] = (char)('0' + sig % 10);
|
|
257
|
+
const char *m2 = ")\n phase = ";
|
|
258
|
+
for (const char *p = m2; *p; p++) buf[o++] = *p;
|
|
259
|
+
for (const char *p = sp_gc_dbg_phase ? sp_gc_dbg_phase : "?"; *p && o < 200; p++) buf[o++] = *p;
|
|
260
|
+
const char *m3 = "\n root slot = 0x";
|
|
261
|
+
for (const char *p = m3; *p; p++) buf[o++] = *p;
|
|
262
|
+
uintptr_t v = (uintptr_t)sp_gc_dbg_ctx;
|
|
263
|
+
for (int i = (int)(sizeof(v) * 2) - 1; i >= 0; i--) buf[o++] = hex[(v >> (i * 4)) & 0xf];
|
|
264
|
+
const char *m4 = "\n The slot's value is the pointer the collector could not read.\n";
|
|
265
|
+
for (const char *p = m4; *p; p++) buf[o++] = *p;
|
|
266
|
+
ssize_t wr = write(2, buf, o); (void)wr;
|
|
267
|
+
if (sp_slab_quar_on) {
|
|
268
|
+
static const char q[] = " SPINEL_GC_STRESS=2: with phase = ? the fault is the program's own, and a\n"
|
|
269
|
+
" pointer read out of a freed slot is all 0xdb bytes.\n";
|
|
270
|
+
wr = write(2, q, sizeof q - 1); (void)wr;
|
|
271
|
+
}
|
|
272
|
+
signal(sig, SIG_DFL);
|
|
273
|
+
raise(sig);
|
|
274
|
+
}
|
|
275
|
+
SP_CONSTRUCTOR static void sp_gc_debug_env(void){
|
|
276
|
+
const char *v=getenv("SPINEL_GC_VERIFY"); sp_gc_verify=(v&&*v&&*v!='0');
|
|
277
|
+
/* SPINEL_GC_STRESS=2 keeps what a sweep frees poisoned and out of reuse
|
|
278
|
+
(lib/sp_slab.c), and what stops a mark that reaches such a slot is the
|
|
279
|
+
verifier's membership test. Only that: the verifier's walks of the whole
|
|
280
|
+
heap (the bitmaps, the remembered set) are per collection, and here a
|
|
281
|
+
collection is per allocation; SPINEL_GC_VERIFY=1 beside it adds them.
|
|
282
|
+
The thresholds are sp_alloc.c's to set. */
|
|
283
|
+
{ const char *st=getenv("SPINEL_GC_STRESS"); if(st&&atoi(st)>=2){ if(!sp_gc_verify)sp_gc_verify=2; sp_slab_quar_on=1; } }
|
|
284
|
+
{ const char *ph=getenv("SPINEL_GC_PHASES"); sp_gc_ph_on=(ph&&*ph&&*ph!='0'); }
|
|
285
|
+
{ const char *fi=getenv("SPINEL_GC_FULL_INTERVAL");
|
|
286
|
+
if(fi&&*fi){ int n=atoi(fi); if(n>0&&n<=4096){ sp_gc_full_interval=n; sp_gc_full_interval_fixed=1; } } }
|
|
287
|
+
{ const char *g=getenv("SPINEL_GC_VERIFY_GEN"); sp_gc_verify_gen=(g&&*g&&*g!='0');
|
|
288
|
+
/* The default since 2026-09-12. What made it one: every test program
|
|
289
|
+
clean under the generational verifier with stress on (every allocation
|
|
290
|
+
collects, so every survivor promotes and any holder the barrier missed
|
|
291
|
+
is named), the LangArena suite at 0.94x wall and 0.7x peak RSS end to
|
|
292
|
+
end, and the Rails-shaped application (#4311) green on its minor leg.
|
|
293
|
+
SPINEL_GC_MINOR=0 is the way back: the collector then marks the whole
|
|
294
|
+
heap on every cycle, as it did before the barrier landed. */
|
|
295
|
+
const char *mn=getenv("SPINEL_GC_MINOR"); sp_gc_minor_on=!(mn&&*mn&&*mn=='0');
|
|
296
|
+
const char *ag=getenv("SPINEL_GC_AGE"); if(ag&&*ag) sp_gc_age_on=(*ag!='0');
|
|
297
|
+
if(sp_gc_verify_gen) sp_gc_minor_on=1; }
|
|
298
|
+
/* Read here rather than in sp_alloc_worker_tune, which a single-threaded
|
|
299
|
+
program never calls: the budget policy is not a threads-only question. */
|
|
300
|
+
{ const char *ob = getenv("SPINEL_GC_OBJ_BUDGET");
|
|
301
|
+
/* The default GATES the widening on what the last collection actually
|
|
302
|
+
cost, rather than widening always (`walk`) or never (`obj`). Both of
|
|
303
|
+
those are kept, because a measurement wants to be able to pin the
|
|
304
|
+
policy at either end. */
|
|
305
|
+
sp_gc_obj_budget_mode = (ob && strcmp(ob, "obj") == 0) ? 0
|
|
306
|
+
: (ob && strcmp(ob, "walk") == 0) ? 1
|
|
307
|
+
: 2;
|
|
308
|
+
/* PINNED, not floored. SPINEL_GC_THRESHOLD_*_KB sets where the budget
|
|
309
|
+
STARTS and the retune moves it from there, which is right for running a
|
|
310
|
+
program and wrong for asking what the retune itself is responsible for.
|
|
311
|
+
rubys wanted exactly that question answered -- whether a change that
|
|
312
|
+
lowers the rate of string garbage grows the resident set because the
|
|
313
|
+
budget lets it, or for some reason that has nothing to do with the
|
|
314
|
+
budget -- and said they had not run it because they did not know the
|
|
315
|
+
knob. There was none (#4396). With `fixed`, the budget is whatever the
|
|
316
|
+
floor says and stays there, so the two emits differ by the change and
|
|
317
|
+
not by two different pacing histories. */
|
|
318
|
+
sp_gc_obj_budget_fixed = (ob && strcmp(ob, "fixed") == 0);
|
|
319
|
+
const char *sb = getenv("SPINEL_GC_STR_BUDGET");
|
|
320
|
+
sp_gc_str_budget_fixed = (sb && strcmp(sb, "fixed") == 0);
|
|
321
|
+
/* The schedule is the DEFAULT. `size` is the escape hatch back to the gate
|
|
322
|
+
that shipped before it -- a size test re-aimed from its own leftover --
|
|
323
|
+
for anyone the change costs more than it saves. `fixed` pins both the
|
|
324
|
+
cadence and the backstop where the floor put them, as it always did. */
|
|
325
|
+
const char *sm = getenv("SPINEL_GC_STR_MAJOR");
|
|
326
|
+
sp_gc_str_major_fixed = (sm && strcmp(sm, "fixed") == 0);
|
|
327
|
+
sp_gc_str_major_sched = !(sm && strcmp(sm, "size") == 0); }
|
|
328
|
+
if (sp_gc_verify) { signal(SIGSEGV, sp_gc_fault_report); signal(SIGBUS, sp_gc_fault_report); }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* Tag byte preceding `obj`: 0xfe heap-unmarked -> 0xfc, 0xfa -> 0xf8;
|
|
332
|
+
* 0xfc/0xf8/0xff/0xfd/0xf1/0xfb skipped; else a real GC object reached
|
|
333
|
+
* through its scan hook. 0xfb is the static header-bearing table (the
|
|
334
|
+
* 1-byte binary substrings): nothing before it is an sp_gc_hdr, so reaching
|
|
335
|
+
* for one and calling its scan hook jumps into the payload byte. */
|
|
336
|
+
|
|
337
|
+
static double sp_gc_stat_now(void);
|
|
338
|
+
#ifdef SP_THREADS
|
|
339
|
+
#include <pthread.h>
|
|
340
|
+
#define SP_GC_CPU_RELAX() SP_CPU_RELAX() /* sp_compat.h */
|
|
341
|
+
/* ---- The parallel mark ----
|
|
342
|
+
The roots are walked by the collector alone (they are few, and the root
|
|
343
|
+
phase has state of its own); the DRAIN, which is the mark's time, runs on
|
|
344
|
+
the collector plus the parked workers the scheduler lends it
|
|
345
|
+
(sp_gc_par_mark_hook). Work moves between markers in chunks: a marker whose
|
|
346
|
+
stack is full spills half of it onto a shared chunk list, and one whose
|
|
347
|
+
stack is empty takes a chunk from it. The drain is over when the list is
|
|
348
|
+
empty and no marker holds work. */
|
|
349
|
+
#define SP_GC_MK_CHUNK 16
|
|
350
|
+
typedef struct sp_gc_mk_chunk { struct sp_gc_mk_chunk *next; int n; void *obj[SP_GC_MK_CHUNK]; } sp_gc_mk_chunk;
|
|
351
|
+
static pthread_mutex_t sp_gc_mk_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
352
|
+
static sp_gc_mk_chunk *sp_gc_mk_work = NULL, *sp_gc_mk_free = NULL;
|
|
353
|
+
static int sp_gc_mk_nwork = 0; /* chunks on the work list (read without the lock to avoid taking it for nothing) */
|
|
354
|
+
static int sp_gc_mk_busy = 0; /* markers holding work */
|
|
355
|
+
static void sp_gc_mark_spill_n(int out) {
|
|
356
|
+
int i = 0;
|
|
357
|
+
pthread_mutex_lock(&sp_gc_mk_lock);
|
|
358
|
+
while (i < out) {
|
|
359
|
+
sp_gc_mk_chunk *c = sp_gc_mk_free;
|
|
360
|
+
if (c) sp_gc_mk_free = c->next;
|
|
361
|
+
else { pthread_mutex_unlock(&sp_gc_mk_lock); c = (sp_gc_mk_chunk *)malloc(sizeof *c); if (!c) sp_oom_die(); pthread_mutex_lock(&sp_gc_mk_lock); }
|
|
362
|
+
int n = out - i; if (n > SP_GC_MK_CHUNK) n = SP_GC_MK_CHUNK;
|
|
363
|
+
memcpy(c->obj, sp_gc_mark_stack + i, (size_t)n * sizeof(void *)); c->n = n; i += n;
|
|
364
|
+
c->next = sp_gc_mk_work; sp_gc_mk_work = c; SP_ATOMIC_FETCH_ADD(&sp_gc_mk_nwork, 1, __ATOMIC_RELAXED); sp_gc_ph_mk_spills++;
|
|
365
|
+
}
|
|
366
|
+
pthread_mutex_unlock(&sp_gc_mk_lock);
|
|
367
|
+
memmove(sp_gc_mark_stack, sp_gc_mark_stack + out, (size_t)(sp_gc_mark_top - out) * sizeof(void *));
|
|
368
|
+
sp_gc_mark_top -= out;
|
|
369
|
+
}
|
|
370
|
+
/* The OLDER half of the stack goes out: what is left on top is what the
|
|
371
|
+
scan that pushed it is about to need, and stays hot on this thread. */
|
|
372
|
+
static void sp_gc_mark_spill(void) { sp_gc_mark_spill_n(sp_gc_mark_top / 2); }
|
|
373
|
+
/* Before the helpers are woken: everything the root walk pushed goes out
|
|
374
|
+
as chunks, so there is work for them the moment they arrive. A stack that
|
|
375
|
+
only spilled when full (64K entries) never did on a live set of 30K
|
|
376
|
+
objects, and eight helpers spun while the collector marked alone. */
|
|
377
|
+
void sp_gc_mark_par_begin(void) { if (sp_gc_mark_top > 0) sp_gc_mark_spill_n(sp_gc_mark_top); }
|
|
378
|
+
static int sp_gc_mk_markers = 1; /* seats the scheduler hands out, plus the collector */
|
|
379
|
+
void sp_gc_mark_par_markers(int n) { sp_gc_mk_markers = n; }
|
|
380
|
+
static int sp_gc_mark_take(void) {
|
|
381
|
+
if (SP_ATOMIC_LOAD(&sp_gc_mk_nwork, __ATOMIC_RELAXED) == 0) return 0;
|
|
382
|
+
pthread_mutex_lock(&sp_gc_mk_lock);
|
|
383
|
+
sp_gc_mk_chunk *c = sp_gc_mk_work;
|
|
384
|
+
if (c) { sp_gc_mk_work = c->next; SP_ATOMIC_FETCH_SUB(&sp_gc_mk_nwork, 1, __ATOMIC_RELAXED); sp_gc_ph_mk_takes++; }
|
|
385
|
+
pthread_mutex_unlock(&sp_gc_mk_lock);
|
|
386
|
+
if (!c) return 0;
|
|
387
|
+
memcpy(sp_gc_mark_stack + sp_gc_mark_top, c->obj, (size_t)c->n * sizeof(void *));
|
|
388
|
+
sp_gc_mark_top += c->n;
|
|
389
|
+
pthread_mutex_lock(&sp_gc_mk_lock);
|
|
390
|
+
c->next = sp_gc_mk_free; sp_gc_mk_free = c;
|
|
391
|
+
pthread_mutex_unlock(&sp_gc_mk_lock);
|
|
392
|
+
return 1;
|
|
393
|
+
}
|
|
394
|
+
/* One marker's drain, run by the collector and by every helper. Returns when
|
|
395
|
+
the whole mark is done: no chunk to take and nobody holding work. */
|
|
396
|
+
void sp_gc_mark_par_run(void) {
|
|
397
|
+
if (!sp_gc_mark_stack) { sp_gc_mark_stack = (void **)malloc(sizeof(void *) * SP_GC_MARK_STACK_MAX); if (!sp_gc_mark_stack) sp_oom_die(); sp_gc_mark_cap = SP_GC_MARK_STACK_MAX; sp_gc_mark_top = 0; }
|
|
398
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_mk_busy, 1, __ATOMIC_ACQ_REL);
|
|
399
|
+
for (;;) {
|
|
400
|
+
while (sp_gc_mark_top > 0) {
|
|
401
|
+
/* share while there is little on the list and a lot here: a marker
|
|
402
|
+
that hoards a deep stack leaves the others spinning */
|
|
403
|
+
if (sp_gc_mark_top > 2 * SP_GC_MK_CHUNK && SP_ATOMIC_LOAD(&sp_gc_mk_nwork, __ATOMIC_RELAXED) < sp_gc_mk_markers) sp_gc_mark_spill();
|
|
404
|
+
void *obj = sp_gc_mark_stack[--sp_gc_mark_top];
|
|
405
|
+
sp_gc_hdr *h = (sp_gc_hdr *)((char *)obj - sizeof(sp_gc_hdr));
|
|
406
|
+
if (h->scan) h->scan(obj);
|
|
407
|
+
}
|
|
408
|
+
if (sp_gc_mark_take()) continue;
|
|
409
|
+
SP_ATOMIC_FETCH_SUB(&sp_gc_mk_busy, 1, __ATOMIC_ACQ_REL);
|
|
410
|
+
double i0 = (sp_gc_ph_on && sp_gc_mk_is_collector) ? sp_gc_stat_now() : 0;
|
|
411
|
+
for (;;) {
|
|
412
|
+
if (SP_ATOMIC_LOAD(&sp_gc_mk_nwork, __ATOMIC_ACQUIRE) > 0) break;
|
|
413
|
+
if (SP_ATOMIC_LOAD(&sp_gc_mk_busy, __ATOMIC_ACQUIRE) == 0) { if (i0) sp_gc_ph_mk_idle += sp_gc_stat_now() - i0; sp_gc_mkl_fold(); return; }
|
|
414
|
+
SP_GC_CPU_RELAX();
|
|
415
|
+
}
|
|
416
|
+
if (i0) sp_gc_ph_mk_idle += sp_gc_stat_now() - i0;
|
|
417
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_mk_busy, 1, __ATOMIC_ACQ_REL);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
void (*sp_gc_par_mark_hook)(void) = NULL;
|
|
421
|
+
void sp_gc_hdr_flags_check(void) {
|
|
422
|
+
sp_gc_hdr h; memset(&h, 0, sizeof h);
|
|
423
|
+
h.marked = 0x5a5a5a5; h.old = 1; h.aged = 1;
|
|
424
|
+
unsigned w = *sp_gc_hdr_flags(&h);
|
|
425
|
+
/* six words on either pointer width: four pointers, the size, the flags */
|
|
426
|
+
if (w != (0x5a5a5a5u | SP_GC_FL_OLD | SP_GC_FL_AGED) || sizeof(sp_gc_hdr) != 6 * sizeof(void *)) {
|
|
427
|
+
fprintf(stderr, "spinel: sp_gc_hdr flag layout is not the one the parallel mark assumes (word %08x)\n", w);
|
|
428
|
+
abort();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
#endif
|
|
432
|
+
/* The drain: on the collector alone, or on the collector and the helpers the
|
|
433
|
+
scheduler lends it. The verifiers and probes read state the helpers do not
|
|
434
|
+
keep, so they drain serially. */
|
|
435
|
+
static void sp_gc_mark_drain_all(void) {
|
|
436
|
+
#ifdef SP_THREADS
|
|
437
|
+
if (sp_gc_par_mark_hook && sp_gc_par_mark_on && !sp_gc_verify && !sp_gc_verify_gen && !sp_gc_verify_probe_on && !sp_gc_young_probe_on && !sp_gc_age_on) {
|
|
438
|
+
sp_gc_par_mark = 1; sp_gc_mk_is_collector = 1;
|
|
439
|
+
sp_gc_par_mark_hook(); /* lends helpers, runs sp_gc_mark_par_run itself, joins them */
|
|
440
|
+
sp_gc_mkl_fold(); /* the collector's share, while par_mark still says which it is */
|
|
441
|
+
sp_gc_par_mark = 0; sp_gc_mk_is_collector = 0;
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
#endif
|
|
445
|
+
sp_gc_mark_drain();
|
|
446
|
+
}
|
|
447
|
+
/* What this thread's marking counted on the string side: bytes reached, and
|
|
448
|
+
of those the young ones the mark promoted (the bitmap strings; a string
|
|
449
|
+
too large for the slab is counted by its list sweep). */
|
|
450
|
+
static SP_TLS size_t sp_gc_mkl_str = 0, sp_gc_mkl_str_young = 0;
|
|
451
|
+
size_t sp_gc_mk_str_bytes = 0, sp_gc_mk_str_young_bytes = 0;
|
|
452
|
+
/* and the slab objects the mark promoted (their bytes; the list sweep counts
|
|
453
|
+
its own promotions as it splices them) */
|
|
454
|
+
static SP_TLS size_t sp_gc_mkl_promo = 0;
|
|
455
|
+
size_t sp_gc_mk_promo_bytes = 0;
|
|
456
|
+
/* A heap string the mark reached. A slab string's mark is a bit in its
|
|
457
|
+
chunk, and the bit is also the "already marked" test: the marker byte
|
|
458
|
+
stays 0xfe, since nothing resets a byte the sweep does not touch. A
|
|
459
|
+
string that fell back to malloc keeps the byte protocol (0xfe -> 0xfc,
|
|
460
|
+
0xfa -> 0xf8, reset by the list sweep that frees or promotes it). */
|
|
461
|
+
void sp_gc_mark_str(const char *s) {
|
|
462
|
+
sp_str_hdr *h = ((sp_str_hdr *)(s - 1)) - 1;
|
|
463
|
+
if (sp_slab_owns(h)) {
|
|
464
|
+
int wy;
|
|
465
|
+
/* SPINEL_GC_VERIFY: a string slot the sweep has freed is nobody's to
|
|
466
|
+
mark; marking it would set generation bits on a free slot */
|
|
467
|
+
if (SP_EXPECT(sp_gc_verify, 0) && !sp_slab_is_live(h)) { fprintf(stderr, "*** SPINEL_GC_VERIFY: the mark reached a freed heap string %p\n", (const void *)s); sp_gc_verify_fail((void *)s, (sp_gc_hdr *)h); }
|
|
468
|
+
if (!sp_slab_mark(h, 0, &wy)) return;
|
|
469
|
+
size_t sz = h->size & 0x3FFFFFFFu;
|
|
470
|
+
sp_gc_mkl_str += sz;
|
|
471
|
+
if (wy) sp_gc_mkl_str_young += sz;
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
/* 0xf8 too: a parallel marker may have got here first */
|
|
475
|
+
unsigned char m = (unsigned char)s[-1], mk = (m == 0xfa || m == 0xf8) ? 0xf8 : 0xfc;
|
|
476
|
+
#ifdef SP_THREADS
|
|
477
|
+
SP_ATOMIC_STORE((unsigned char *)s - 1, mk, __ATOMIC_RELAXED); /* several markers may set it; a plain byte store, spelled so TSan reads it as intended */
|
|
478
|
+
#else
|
|
479
|
+
((unsigned char *)s)[-1] = mk;
|
|
480
|
+
#endif
|
|
481
|
+
}
|
|
482
|
+
void sp_gc_mark(void*obj){if(!obj)return;unsigned char pm=((unsigned char*)obj)[-1];if((pm|0x04)==0xfe){sp_gc_mark_str((const char*)obj);return;}
|
|
483
|
+
if(pm==0xfc||pm==0xff||pm==0xfd||pm==0xf1||pm==0xfb||pm==0xf8)return;sp_gc_hdr*h=(sp_gc_hdr*)((char*)obj-sizeof(sp_gc_hdr));if(sp_gc_verify&&!sp_gc_obj_registered(h))sp_gc_verify_fail(obj,h);if(sp_gc_verify_probe_on){if(!h->old&&h->marked==sp_gc_verify_probe)sp_gc_verify_probe_hit=1;return;}if(sp_gc_young_probe_on){if(!h->old)sp_gc_young_probe_hit=1;return;}if(sp_gc_root_phase&&!h->old)h->aged=1;
|
|
484
|
+
int was_young=0;
|
|
485
|
+
#ifdef SP_THREADS
|
|
486
|
+
if(sp_gc_par_mark){
|
|
487
|
+
/* Several threads mark at once: the stamp is claimed with an exchange
|
|
488
|
+
on the flag word, so an object is counted and scanned by exactly one
|
|
489
|
+
of them. Nothing else writes the word under the barrier. */
|
|
490
|
+
unsigned *w=sp_gc_hdr_flags(h);unsigned o=SP_ATOMIC_LOAD(w,__ATOMIC_RELAXED);
|
|
491
|
+
for(;;){
|
|
492
|
+
if((o&SP_GC_FL_MARK_MASK)==sp_gc_mark_gen)return;
|
|
493
|
+
if(sp_gc_minor&&(o&SP_GC_FL_OLD))return;
|
|
494
|
+
unsigned nw=(o&~SP_GC_FL_MARK_MASK)|sp_gc_mark_gen;
|
|
495
|
+
if(sp_gc_conc_promote&&!(o&SP_GC_FL_OLD))nw|=SP_GC_FL_OLD;
|
|
496
|
+
if(SP_ATOMIC_CAS(w,&o,nw,0,__ATOMIC_ACQ_REL,__ATOMIC_RELAXED))break;
|
|
497
|
+
}
|
|
498
|
+
sp_gc_mkl_marked++;sp_gc_mkl_bytes+=h->size;if(!(o&SP_GC_FL_OLD)){sp_gc_mkl_young+=h->size;was_young=1;}
|
|
499
|
+
}
|
|
500
|
+
else
|
|
501
|
+
#endif
|
|
502
|
+
{if(h->marked==sp_gc_mark_gen)return;if(sp_gc_minor&&h->old)return;h->marked=sp_gc_mark_gen;sp_gc_mkl_marked++;sp_gc_mkl_bytes+=h->size;if(!h->old){sp_gc_mkl_young+=h->size;was_young=1;if(sp_gc_conc_promote)h->old=1;/* promoted here, under the barrier: the concurrent sweep will not write the bit */}}
|
|
503
|
+
/* A slab object: its generation is a bit in its chunk, and the mark is what
|
|
504
|
+
promotes it -- the sweep reads the bitmaps and never this header. A
|
|
505
|
+
first-time survivor under aging stays young (the sweep carries it into
|
|
506
|
+
the next epoch) and is aged for the next time; the header's `old` bit
|
|
507
|
+
follows the promotion so the write barrier sees it. */
|
|
508
|
+
if(was_young&&sp_slab_owns(h)){
|
|
509
|
+
/* SPINEL_GC_AGE keeps a first-time survivor young on the lists; a slab
|
|
510
|
+
object promotes at once, since the remembered-set repair aging needs
|
|
511
|
+
(the young probe over what this cycle promoted) walks the old list */
|
|
512
|
+
int keep_young=0;
|
|
513
|
+
int wy;
|
|
514
|
+
sp_slab_mark(h,keep_young,&wy);
|
|
515
|
+
sp_gc_mkl_promo+=h->size;
|
|
516
|
+
{
|
|
517
|
+
#ifdef SP_THREADS
|
|
518
|
+
if(sp_gc_par_mark)SP_ATOMIC_FETCH_OR(sp_gc_hdr_flags(h),SP_GC_FL_OLD,__ATOMIC_RELAXED);
|
|
519
|
+
else
|
|
520
|
+
#endif
|
|
521
|
+
h->old=1;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
else if(sp_slab_owns(h)){int wy;sp_slab_mark(h,0,&wy);}
|
|
525
|
+
if(h->scan){if(sp_gc_mark_stack&&sp_gc_mark_top>=sp_gc_mark_cap){
|
|
526
|
+
#ifdef SP_THREADS
|
|
527
|
+
if(sp_gc_par_mark){sp_gc_mark_spill();}
|
|
528
|
+
else
|
|
529
|
+
#endif
|
|
530
|
+
if(sp_gc_mark_cap<(1<<28)){int nc=sp_gc_mark_cap*2;void**ns=(void**)realloc(sp_gc_mark_stack,sizeof(void*)*(size_t)nc);if(ns){sp_gc_mark_stack=ns;sp_gc_mark_cap=nc;}}}
|
|
531
|
+
if(sp_gc_mark_stack&&sp_gc_mark_top<sp_gc_mark_cap){sp_gc_mark_stack[sp_gc_mark_top++]=obj;}
|
|
532
|
+
else{h->scan(obj);}}}
|
|
533
|
+
/* The thread's counts, folded in. The collector folds its own at the end of
|
|
534
|
+
the mark; a helper folds when its drain ends. */
|
|
535
|
+
static void sp_gc_mkl_fold(void){
|
|
536
|
+
#ifdef SP_THREADS
|
|
537
|
+
if(sp_gc_par_mark&&!sp_gc_mk_is_collector)SP_ATOMIC_FETCH_ADD(&sp_gc_ph_mk_by_helpers,sp_gc_mkl_marked,__ATOMIC_RELAXED);
|
|
538
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_ct_marked,sp_gc_mkl_marked,__ATOMIC_RELAXED);
|
|
539
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_mk_bytes,sp_gc_mkl_bytes,__ATOMIC_RELAXED);
|
|
540
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_mk_young_bytes,sp_gc_mkl_young,__ATOMIC_RELAXED);
|
|
541
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_mk_str_bytes,sp_gc_mkl_str,__ATOMIC_RELAXED);
|
|
542
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_mk_str_young_bytes,sp_gc_mkl_str_young,__ATOMIC_RELAXED);
|
|
543
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_mk_promo_bytes,sp_gc_mkl_promo,__ATOMIC_RELAXED);
|
|
544
|
+
#else
|
|
545
|
+
sp_gc_ct_marked+=sp_gc_mkl_marked;sp_gc_mk_bytes+=sp_gc_mkl_bytes;sp_gc_mk_young_bytes+=sp_gc_mkl_young;
|
|
546
|
+
sp_gc_mk_str_bytes+=sp_gc_mkl_str;sp_gc_mk_str_young_bytes+=sp_gc_mkl_str_young;sp_gc_mk_promo_bytes+=sp_gc_mkl_promo;
|
|
547
|
+
#endif
|
|
548
|
+
sp_gc_mkl_marked=0;sp_gc_mkl_bytes=0;sp_gc_mkl_young=0;sp_gc_mkl_str=0;sp_gc_mkl_str_young=0;sp_gc_mkl_promo=0;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
void sp_gc_mark_drain(void){
|
|
552
|
+
while(sp_gc_mark_top>0){void*obj=sp_gc_mark_stack[--sp_gc_mark_top];
|
|
553
|
+
sp_gc_hdr*h=(sp_gc_hdr*)((char*)obj-sizeof(sp_gc_hdr));
|
|
554
|
+
/* Name the object being scanned, not the root group it came from: a bad
|
|
555
|
+
reference found here is a field of THIS object, and the group label on
|
|
556
|
+
its own says nothing about which. */
|
|
557
|
+
if(sp_gc_verify){sp_gc_dbg_phase="scan";sp_gc_dbg_ctx=obj;}
|
|
558
|
+
if(h->scan)h->scan(obj);}
|
|
559
|
+
}
|
|
560
|
+
/* sp_gc_stat_now is defined with the rest of the phase clock, below: the split
|
|
561
|
+
here is the same measurement, taken one level down. */
|
|
562
|
+
static double sp_gc_stat_now(void);
|
|
563
|
+
/* Close the mark sub-phase that ended here. Same shape and same cost as
|
|
564
|
+
SP_GC_PH in sp_gc_collect: off by default, one not-taken branch per
|
|
565
|
+
boundary, four of them per collection. */
|
|
566
|
+
#define SP_GC_MK_PH(bucket) do { if (sp_gc_ph_on) { double _t = sp_gc_stat_now(); (bucket) += _t - mk_t; mk_t = _t; } } while (0)
|
|
567
|
+
|
|
568
|
+
void sp_gc_mark_all(void){if(!sp_gc_mark_stack){sp_gc_mark_stack=(void**)malloc(sizeof(void*)*SP_GC_MARK_STACK_MAX);if(sp_gc_mark_stack)sp_gc_mark_cap=SP_GC_MARK_STACK_MAX;}sp_gc_mark_top=0;if(sp_gc_verify)sp_gc_verify_snapshot();int vd=sp_gc_verify;
|
|
569
|
+
double mk_t = sp_gc_ph_on ? sp_gc_stat_now() : 0.0;
|
|
570
|
+
/* An object a C root names directly is one some runtime function holds in
|
|
571
|
+
a local across this collection, and such a function may have recorded
|
|
572
|
+
its holder BEFORE the allocation that collected (the barrier-then-store
|
|
573
|
+
shape #4376 describes): the record is cleared by this cycle, and the
|
|
574
|
+
store that follows would go unrecorded. Promoting every survivor covered
|
|
575
|
+
that: an old value into an old holder needs no record. Aging survivors
|
|
576
|
+
reopens it, so what a root names directly is promoted on this survival
|
|
577
|
+
(aged is set before the mark); the rest of the graph ages. The same for
|
|
578
|
+
a suspended fiber's roots, which are runtime locals too. */
|
|
579
|
+
sp_gc_root_phase=1;
|
|
580
|
+
for(int i=0;i<sp_gc_nroots;i++){void**e=sp_gc_root_at(i);if(vd){sp_gc_dbg_phase="root";sp_gc_dbg_ctx=(void*)e;}if((uintptr_t)e&(uintptr_t)3){sp_gc_mark_root_entry(e);}
|
|
581
|
+
else{void*obj=*e;if(obj)sp_gc_mark(obj);}}
|
|
582
|
+
SP_GC_MK_PH(sp_gc_ph_mk_roots);
|
|
583
|
+
if(vd)sp_gc_dbg_phase="fibers";if(sp_gc_mark_suspended_fibers_hook)sp_gc_mark_suspended_fibers_hook();
|
|
584
|
+
sp_gc_root_phase=0;
|
|
585
|
+
SP_GC_MK_PH(sp_gc_ph_mk_fibers);
|
|
586
|
+
if(vd)sp_gc_dbg_phase="globals";if(sp_gc_mark_globals_hook)sp_gc_mark_globals_hook();
|
|
587
|
+
SP_GC_MK_PH(sp_gc_ph_mk_globals);
|
|
588
|
+
sp_gc_mark_drain_all();
|
|
589
|
+
sp_gc_mkl_fold();
|
|
590
|
+
SP_GC_MK_PH(sp_gc_ph_mk_scan);
|
|
591
|
+
if(vd){sp_gc_dbg_phase="?";sp_gc_dbg_ctx=NULL;}}
|
|
592
|
+
|
|
593
|
+
unsigned sp_gc_mark_gen = 0;
|
|
594
|
+
/* Set for the duration of a minor mark: an object already promoted is not
|
|
595
|
+
walked, because the sweep does not free the old list on a minor cycle and
|
|
596
|
+
the remembered set carries the old->young references the walk would miss. */
|
|
597
|
+
int sp_gc_minor = 0;
|
|
598
|
+
int sp_gc_minor_on = 1; /* the constructor above reads SPINEL_GC_MINOR=0 */
|
|
599
|
+
int sp_gc_verify_gen = 0;
|
|
600
|
+
int sp_gc_verify_gen_fail = 0;
|
|
601
|
+
int sp_gc_verify_probe_on = 0, sp_gc_verify_probe_hit = 0;
|
|
602
|
+
/* Armed after a minor's sweep to ask each remembered holder whether it still
|
|
603
|
+
reaches a young object (one the sweep kept young for a second look): with the
|
|
604
|
+
probe on, sp_gc_mark records the answer and marks nothing. */
|
|
605
|
+
int sp_gc_young_probe_on = 0, sp_gc_young_probe_hit = 0;
|
|
606
|
+
/* This cycle's sweeps age survivors instead of promoting every one: a minor
|
|
607
|
+
whose remembered set is intact. SPINEL_GC_AGE=0 turns aging off. */
|
|
608
|
+
int sp_gc_age_survivors = 0;
|
|
609
|
+
int sp_gc_age_on = 0;
|
|
610
|
+
size_t sp_gc_young_kept_bytes = 0;
|
|
611
|
+
size_t sp_gc_npromoted = 0;
|
|
612
|
+
unsigned sp_gc_verify_probe = 0;
|
|
613
|
+
void *sp_gc_remembered[SP_GC_REMEMBERED_MAX];
|
|
614
|
+
int sp_gc_nremembered = 0;
|
|
615
|
+
int sp_gc_rem_overflow = 0;
|
|
616
|
+
void *sp_gc_pinned[SP_GC_PINNED_MAX];
|
|
617
|
+
int sp_gc_npinned = 0;
|
|
618
|
+
int sp_gc_pin_overflow = 0;
|
|
619
|
+
|
|
620
|
+
/* Sticky remembered entry; see the comment on sp_gc_pin_remembered in sp_gc.h
|
|
621
|
+
for why the by-reference String ABI needs one. Same tag protocol as
|
|
622
|
+
sp_gc_wb_slow: the byte in front says whether there is a header at all, so
|
|
623
|
+
a literal, a frozen string or the static root fiber is refused rather than
|
|
624
|
+
dereferenced. The caller only ever passes a heap cell or an object it
|
|
625
|
+
named, so this is a guard and not a filter.
|
|
626
|
+
|
|
627
|
+
`pinned` makes it once per object rather than once per lending call: the
|
|
628
|
+
set is one entry per distinct holder, not per call, which is what keeps it
|
|
629
|
+
small enough to walk on every minor cycle. Overflow degrades the same way
|
|
630
|
+
the barrier's does -- to a full mark, which needs no remembered set at
|
|
631
|
+
all -- rather than silently dropping a holder. */
|
|
632
|
+
void sp_gc_pin_remembered_slow(void *obj) {
|
|
633
|
+
if (!obj) return;
|
|
634
|
+
{ unsigned char pm = ((unsigned char *)obj)[-1];
|
|
635
|
+
if (pm == 0xfd || pm == 0xff || pm == 0xf1 || pm == 0xf0 ||
|
|
636
|
+
pm == 0xfe || pm == 0xfc || pm == 0xfb || pm == 0xfa || pm == 0xf8) return; }
|
|
637
|
+
sp_gc_hdr *h = (sp_gc_hdr *)obj - 1;
|
|
638
|
+
if (h->pinned) return;
|
|
639
|
+
/* The slot is claimed BEFORE the bit goes up, which is the opposite order to
|
|
640
|
+
sp_gc_wb_slow's, and for the opposite reason. There the bit is the record
|
|
641
|
+
and the array is the index, so a bit with no entry is self-healing. Here
|
|
642
|
+
the ARRAY is the record and the bit is only a dedupe, so a bit with no
|
|
643
|
+
entry would refuse the object forever -- it would never be pinned and
|
|
644
|
+
every young string it later holds would be invisible to a minor mark. A
|
|
645
|
+
refused push therefore leaves the bit down and the object is offered
|
|
646
|
+
again on its next lending, by which time a compaction may have made
|
|
647
|
+
room. Two threads racing here can both win a slot; a duplicate entry is
|
|
648
|
+
scanned twice and costs nothing. */
|
|
649
|
+
#ifdef SP_THREADS
|
|
650
|
+
{ int idx = SP_ATOMIC_FETCH_ADD(&sp_gc_npinned, 1, __ATOMIC_RELAXED);
|
|
651
|
+
if (idx < SP_GC_PINNED_MAX) { sp_gc_pinned[idx] = obj; h->pinned = 1; }
|
|
652
|
+
else { SP_ATOMIC_STORE(&sp_gc_pin_overflow, 1, __ATOMIC_RELAXED);
|
|
653
|
+
SP_ATOMIC_STORE(&sp_gc_npinned, SP_GC_PINNED_MAX, __ATOMIC_RELAXED); } }
|
|
654
|
+
#else
|
|
655
|
+
if (sp_gc_npinned < SP_GC_PINNED_MAX) { sp_gc_pinned[sp_gc_npinned++] = obj; h->pinned = 1; }
|
|
656
|
+
else sp_gc_pin_overflow = 1;
|
|
657
|
+
#endif
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
void sp_gc_wb_slow(void *obj) {
|
|
661
|
+
if (!obj) return;
|
|
662
|
+
/* Same tag-byte protocol sp_gc_mark uses: the byte in front says whether
|
|
663
|
+
there is a header to read at all. The root fiber is a static whose guard
|
|
664
|
+
byte is 0xfd, so reaching past it for `old` walks off the end of a global
|
|
665
|
+
(ASAN: global-buffer-overflow, and a wandering segfault without it), and a
|
|
666
|
+
literal or a frozen string is not a GC allocation either. */
|
|
667
|
+
{ unsigned char pm = ((unsigned char *)obj)[-1];
|
|
668
|
+
if (pm == 0xfd || pm == 0xff || pm == 0xf1 || pm == 0xf0 ||
|
|
669
|
+
pm == 0xfe || pm == 0xfc || pm == 0xfb || pm == 0xfa || pm == 0xf8) return; }
|
|
670
|
+
sp_gc_hdr *h = (sp_gc_hdr *)obj - 1;
|
|
671
|
+
if (!h->old || h->dirty) return;
|
|
672
|
+
/* The bit goes up before the entry goes in, so between here and the push an
|
|
673
|
+
object carries it with nothing naming it, and the collector's clear reads
|
|
674
|
+
the array. Two things keep that safe, and the second is the one that
|
|
675
|
+
actually covers every case:
|
|
676
|
+
|
|
677
|
+
- No collection reaches this window from another thread. Nothing below
|
|
678
|
+
allocates or polls a safepoint, so a worker cannot park inside this
|
|
679
|
+
function, and a collection waits for every other worker to park.
|
|
680
|
+
- A collection that begins ON THIS THREAD, mid-window, is harmless because
|
|
681
|
+
the bit goes up FIRST. That collection leaves the bit set with no entry;
|
|
682
|
+
the push we are about to make lands in the next epoch's array, and the
|
|
683
|
+
clear after that finds it. It is self-healing, where pushing first would
|
|
684
|
+
not be. This is not hypothetical: an async `Signal.trap` handler runs
|
|
685
|
+
Ruby in signal context (sp_sig_c_handler -> sp_proc_call), on whatever
|
|
686
|
+
worker the OS picks, and the allocation it makes can collect right here
|
|
687
|
+
-- on a thread that does not park, because the collector never parks
|
|
688
|
+
itself. The single-threaded build has always cleared through the array
|
|
689
|
+
and has always had that same path.
|
|
690
|
+
|
|
691
|
+
Add an allocation or a safepoint poll below and the first argument goes;
|
|
692
|
+
reverse the two writes and the second one goes with it. */
|
|
693
|
+
h->dirty = 1;
|
|
694
|
+
#ifdef SP_THREADS
|
|
695
|
+
/* Mutators run this concurrently, so the slot has to be claimed atomically:
|
|
696
|
+
a plain `n++` lets two workers take the same index and one of the two
|
|
697
|
+
holders is silently dropped from the set -- a missing barrier with all the
|
|
698
|
+
barriers in place. The dirty bit needs no such care: only a mutator writes
|
|
699
|
+
it, only ever to 1, and the collector reads and clears it under
|
|
700
|
+
stop-the-world. */
|
|
701
|
+
{ int idx = SP_ATOMIC_FETCH_ADD(&sp_gc_nremembered, 1, __ATOMIC_RELAXED);
|
|
702
|
+
if (idx < SP_GC_REMEMBERED_MAX) sp_gc_remembered[idx] = obj;
|
|
703
|
+
else { SP_ATOMIC_STORE(&sp_gc_rem_overflow, 1, __ATOMIC_RELAXED);
|
|
704
|
+
SP_ATOMIC_STORE(&sp_gc_nremembered, SP_GC_REMEMBERED_MAX, __ATOMIC_RELAXED); } }
|
|
705
|
+
#else
|
|
706
|
+
if (sp_gc_nremembered < SP_GC_REMEMBERED_MAX) sp_gc_remembered[sp_gc_nremembered++] = obj;
|
|
707
|
+
else sp_gc_rem_overflow = 1;
|
|
708
|
+
#endif
|
|
709
|
+
}
|
|
710
|
+
int sp_gc_str_minor_only = 0;
|
|
711
|
+
/* Object-threshold retune, installed by sp_alloc.c. Running it INSIDE every
|
|
712
|
+
collection (not only on the object-triggered wrapper) keeps the trigger
|
|
713
|
+
tracking the live size whichever heap initiated the collect; the old
|
|
714
|
+
split retunes left one threshold stale and re-triggered immediately. */
|
|
715
|
+
void (*sp_gc_obj_retune_hook)(size_t before) = NULL;
|
|
716
|
+
/* Minor sweep of one young list (under stop-the-world): free/recycle the dead,
|
|
717
|
+
promote survivors into the shared old heap, accumulating survivor bytes into
|
|
718
|
+
sp_gc_bytes and sp_gc_old_bytes (both pre-seeded by the caller). */
|
|
719
|
+
/* Counts, not bytes. The sweep's cost is per SLOT -- a header touch and a
|
|
720
|
+
free -- and the mark's is per LIVE OBJECT. Sized in bytes the two are not
|
|
721
|
+
comparable across programs: a cache of large strings and a churn of small
|
|
722
|
+
arrays can hold the same megabytes with slot counts orders of magnitude
|
|
723
|
+
apart, which is why a per-byte cost ratio measured on one workload did not
|
|
724
|
+
carry to another (#4384). Counted, the coefficients are properties of this
|
|
725
|
+
code rather than of a program's allocation sizes. */
|
|
726
|
+
size_t sp_gc_ct_swept = 0, sp_gc_ct_marked = 0;
|
|
727
|
+
static void sp_gc_sweep_young(sp_gc_hdr **pp){
|
|
728
|
+
size_t kept=0, promoted=0;
|
|
729
|
+
while(*pp){sp_gc_hdr*h=*pp;SP_GC_CTR_ADD(sp_gc_ct_swept,1);if(h->marked!=sp_gc_mark_gen){*pp=h->next;if(h->recycle){h->recycle(h);}
|
|
730
|
+
else{if(h->finalize)h->finalize((char*)h+sizeof(sp_gc_hdr));sp_slab_free(h);}}
|
|
731
|
+
else if(sp_gc_age_survivors&&!h->aged){h->aged=1;kept+=h->size;pp=&h->next;} /* first survival: stays young */
|
|
732
|
+
else{*pp=h->next;h->next=sp_gc_old_heap;sp_gc_old_heap=h;h->old=1;sp_gc_old_bytes+=h->size;sp_gc_bytes+=h->size;promoted++;}}
|
|
733
|
+
sp_gc_young_kept_bytes+=kept;
|
|
734
|
+
sp_gc_npromoted+=promoted;
|
|
735
|
+
}
|
|
736
|
+
#ifdef SP_THREADS
|
|
737
|
+
/* One worker's young list, swept BY THAT WORKER while it is parked at the
|
|
738
|
+
stop-the-world barrier. Two things make this worth the barrier phase it
|
|
739
|
+
needs. It is parallel -- the sweep was 93% of the stopped time and every
|
|
740
|
+
other worker idled through it. And the frees go back to the arena the
|
|
741
|
+
allocation came from: a single collector thread freeing eight workers'
|
|
742
|
+
objects takes eight different arena locks and touches eight cold sets of
|
|
743
|
+
metadata, which is why the serial sweep got MORE expensive as workers were
|
|
744
|
+
added (51 ms at one worker, 188 ms at eight, for the same allocations).
|
|
745
|
+
|
|
746
|
+
Survivors are collected into a caller-owned local list rather than pushed
|
|
747
|
+
straight onto the shared old heap: that is the one part that cannot be
|
|
748
|
+
concurrent, so it becomes an O(workers) splice the collector does after. */
|
|
749
|
+
/* The list form, which the concurrent sweep (sp_sched.c) runs beside the
|
|
750
|
+
mutators on a young list detached from its worker at the barrier. What
|
|
751
|
+
differs from the stop-the-world form: the survivor's `old` bit is not
|
|
752
|
+
written, since the mark set it (sp_gc_mark, under the barrier), so a store
|
|
753
|
+
into the survivor after the world resumed was already recorded, and the
|
|
754
|
+
bit shares a word with the one the barrier writes. A pooled object IS
|
|
755
|
+
pushed onto its pool here, beside the mutators popping from it: the pools
|
|
756
|
+
are Treiber stacks, and the ABA a push could hand a popper needs the
|
|
757
|
+
popped object to die and come back between the popper's load and its
|
|
758
|
+
exchange -- a whole collection, which cannot complete while that popper
|
|
759
|
+
has not parked. Chaining the dead for the collector to push at the next
|
|
760
|
+
barrier instead cost the barrier more than the sweep it had shed (8.8s of
|
|
761
|
+
13.3s on a server run). */
|
|
762
|
+
void sp_gc_sweep_list(sp_gc_hdr **pp, int conc, sp_gc_hdr **out_head, sp_gc_hdr **out_tail, size_t *out_bytes) {
|
|
763
|
+
sp_gc_hdr *head = NULL, *tail = NULL;
|
|
764
|
+
size_t live = 0;
|
|
765
|
+
/* Counted into a LOCAL and published once. Every worker runs this at the
|
|
766
|
+
same time, so incrementing the global per slot was a data race (found by
|
|
767
|
+
scripts/tsan-run.sh) -- and the atomic that would fix it in place is a
|
|
768
|
+
locked add per slot on one shared cache line, which is the same
|
|
769
|
+
ping-pong the parallel sweep exists to avoid. */
|
|
770
|
+
size_t swept = 0, kept = 0, promoted = 0;
|
|
771
|
+
while (*pp) {
|
|
772
|
+
sp_gc_hdr *h = *pp;
|
|
773
|
+
SP_PREFETCH(h->next);
|
|
774
|
+
swept++;
|
|
775
|
+
if (h->marked != sp_gc_mark_gen) {
|
|
776
|
+
*pp = h->next;
|
|
777
|
+
if (h->recycle) h->recycle(h);
|
|
778
|
+
else { if (h->finalize) h->finalize((char *)h + sizeof(sp_gc_hdr)); sp_slab_free(h); }
|
|
779
|
+
}
|
|
780
|
+
else if (!conc && sp_gc_age_survivors && !h->aged) {
|
|
781
|
+
h->aged = 1; kept += h->size; pp = &h->next; /* first survival: stays young */
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
*pp = h->next;
|
|
785
|
+
h->next = head; head = h;
|
|
786
|
+
if (!tail) tail = h;
|
|
787
|
+
if (!conc) h->old = 1; /* survivor: joins the old list (see sp_gc_wb) */
|
|
788
|
+
live += h->size; promoted++;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
SP_GC_CTR_ADD(sp_gc_ct_swept, swept);
|
|
792
|
+
SP_GC_CTR_ADD(sp_gc_young_kept_bytes, kept);
|
|
793
|
+
SP_GC_CTR_ADD(sp_gc_npromoted, promoted);
|
|
794
|
+
*out_head = head; *out_tail = tail; *out_bytes = live;
|
|
795
|
+
}
|
|
796
|
+
void sp_gc_sweep_slot(int wid, sp_gc_hdr **out_head, sp_gc_hdr **out_tail, size_t *out_bytes) {
|
|
797
|
+
sp_gc_sweep_list(&sp_gc_wslot[wid].young, 0, out_head, out_tail, out_bytes);
|
|
798
|
+
}
|
|
799
|
+
/* The old list's full-cycle sweep in list form, for the concurrent driver:
|
|
800
|
+
frees the unmarked and answers the bytes that stayed. The dirty bits are
|
|
801
|
+
left alone (the barrier writes that word from the mutators; the collector
|
|
802
|
+
clears the recorded ones under the barrier instead). */
|
|
803
|
+
void sp_gc_sweep_old_list(sp_gc_hdr **pp, size_t *out_live, sp_gc_hdr **out_tail) {
|
|
804
|
+
size_t live = 0, swept = 0; sp_gc_hdr *tail = NULL;
|
|
805
|
+
while (*pp) {
|
|
806
|
+
sp_gc_hdr *h = *pp;
|
|
807
|
+
SP_PREFETCH(h->next);
|
|
808
|
+
swept++;
|
|
809
|
+
if (h->marked != sp_gc_mark_gen) {
|
|
810
|
+
*pp = h->next;
|
|
811
|
+
if (h->recycle) h->recycle(h);
|
|
812
|
+
else { if (h->finalize) h->finalize((char *)h + sizeof(sp_gc_hdr)); sp_slab_free(h); }
|
|
813
|
+
}
|
|
814
|
+
else { live += h->size; tail = h; pp = &h->next; }
|
|
815
|
+
}
|
|
816
|
+
SP_GC_CTR_ADD(sp_gc_ct_swept, swept);
|
|
817
|
+
*out_live = live; *out_tail = tail; /* the tail, so the reattach is a splice and not a walk */
|
|
818
|
+
}
|
|
819
|
+
/* Installed by the scheduler when it can drive the parked workers; NULL means
|
|
820
|
+
nobody is parked to help and the collector sweeps every slot itself. */
|
|
821
|
+
void (*sp_gc_par_sweep_hook)(void) = NULL;
|
|
822
|
+
|
|
823
|
+
void sp_gc_promote_slot(sp_gc_hdr *head, sp_gc_hdr *tail, size_t bytes) {
|
|
824
|
+
if (!head) return;
|
|
825
|
+
tail->next = sp_gc_old_heap;
|
|
826
|
+
sp_gc_old_heap = head;
|
|
827
|
+
sp_gc_old_bytes += bytes;
|
|
828
|
+
sp_gc_bytes += bytes;
|
|
829
|
+
}
|
|
830
|
+
sp_gc_hdr *sp_gc_old_detach(void) { sp_gc_hdr *h = sp_gc_old_heap; sp_gc_old_heap = NULL; return h; }
|
|
831
|
+
void sp_gc_old_attach(sp_gc_hdr *head, sp_gc_hdr *tail) {
|
|
832
|
+
if (!head) return;
|
|
833
|
+
if (!tail) { tail = head; while (tail->next) tail = tail->next; }
|
|
834
|
+
tail->next = sp_gc_old_heap;
|
|
835
|
+
sp_gc_old_heap = head;
|
|
836
|
+
}
|
|
837
|
+
#endif
|
|
838
|
+
/* ---- the slab's sweep: one worker's chunks, by their bitmaps ---- */
|
|
839
|
+
/* a dead object with a finalizer or a recycler (lib/sp_slab.c calls this for
|
|
840
|
+
each): 1 when the slot is freed, 0 when the recycler kept the header
|
|
841
|
+
(parked on its pool; the pool frees it itself when over the cap, and the
|
|
842
|
+
slot's bits say which happened) */
|
|
843
|
+
static int sp_gc_die_cb(void *hp){
|
|
844
|
+
sp_gc_hdr *h=(sp_gc_hdr*)hp;
|
|
845
|
+
if(sp_gc_verify&&(h->size<sizeof(sp_gc_hdr)||h->size>(1u<<30)||((uintptr_t)h->recycle&&(uintptr_t)h->recycle<4096)||((uintptr_t)h->finalize&&(uintptr_t)h->finalize<4096))){
|
|
846
|
+
fprintf(stderr,"*** SPINEL_GC_VERIFY: a dead slot with a finalizer bit is no object: hdr=%p size=%zu recycle=%p finalize=%p scan=%p\n",(void*)h,(size_t)h->size,(void*)h->recycle,(void*)h->finalize,(void*)h->scan);
|
|
847
|
+
sp_slab_describe(h); abort();
|
|
848
|
+
}
|
|
849
|
+
if(h->recycle){
|
|
850
|
+
/* parked before the pool sees it: another thread may pop and relive it
|
|
851
|
+
the moment it is pushed, and parking after that would undo the relive.
|
|
852
|
+
An over-cap header the recycler frees itself, which this thread's
|
|
853
|
+
free count shows. */
|
|
854
|
+
unsigned long f0=sp_slab_frees;
|
|
855
|
+
h->recycle(h); /* the sweep pinned the slot before calling (sp_slab_sweep_worker) */
|
|
856
|
+
return sp_slab_frees!=f0;
|
|
857
|
+
}
|
|
858
|
+
if(h->finalize)h->finalize((char*)h+sizeof(sp_gc_hdr));
|
|
859
|
+
return 1;
|
|
860
|
+
}
|
|
861
|
+
static void sp_gc_clear_dirty_cb(void *hp,void *arg){(void)arg;((sp_gc_hdr*)hp)->dirty=0;}
|
|
862
|
+
static void sp_gc_stamp_reset_cb(void *hp,void *arg){(void)arg;((sp_gc_hdr*)hp)->marked=0;}
|
|
863
|
+
static void sp_gc_rem_invariant_cb(void *hp,void *arg){
|
|
864
|
+
sp_gc_hdr *h=(sp_gc_hdr*)hp; void *o=(char*)h+sizeof(sp_gc_hdr); int listed=0;
|
|
865
|
+
for(int ri=0;ri<sp_gc_nremembered;ri++) if(sp_gc_remembered[ri]==o){listed=1;break;}
|
|
866
|
+
if(!!h->dirty!=listed){
|
|
867
|
+
fprintf(stderr,"spinel: GC remembered-set invariant broken: obj=%p scan=%p dirty=%d listed=%d full=%d age=%d cycle=%d\n",
|
|
868
|
+
o,(void*)h->scan,(int)h->dirty,listed,*(int*)arg,sp_gc_age_survivors,sp_gc_cycle);
|
|
869
|
+
fprintf(stderr," size=%zu old=%d marked=%u gen=%u fin=%p\n",h->size,(int)h->old,(unsigned)h->marked,(unsigned)sp_gc_mark_gen,(void*)h->finalize);
|
|
870
|
+
sp_slab_describe(hp); sp_slab_history(hp);
|
|
871
|
+
abort();
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
static int sp_gc_sweep_full_now=0; /* the cycle the stop-the-world sweep tasks are for */
|
|
875
|
+
size_t sp_gc_ph_slab_freed_obj=0, sp_gc_ph_slab_freed_str=0;
|
|
876
|
+
/* bytes of headers the pools hold, summed over the sweep of one cycle and
|
|
877
|
+
folded into the live total the budget is sized from (sp_gc_parked_take) */
|
|
878
|
+
static size_t sp_gc_parked_acc=0;
|
|
879
|
+
size_t sp_gc_parked_take(void){ size_t v;
|
|
880
|
+
#ifdef SP_THREADS
|
|
881
|
+
v=SP_ATOMIC_EXCHANGE(&sp_gc_parked_acc,0,__ATOMIC_RELAXED);
|
|
882
|
+
#else
|
|
883
|
+
v=sp_gc_parked_acc; sp_gc_parked_acc=0;
|
|
884
|
+
#endif
|
|
885
|
+
return v; }
|
|
886
|
+
void sp_gc_sweep_chunks(int wid,int full){
|
|
887
|
+
sp_slab_sweep_stats st;
|
|
888
|
+
sp_slab_sweep_worker(wid,full,0,sp_gc_die_cb,&st);
|
|
889
|
+
SP_GC_CTR_ADD(sp_gc_ct_swept,st.swept);
|
|
890
|
+
#ifdef SP_THREADS
|
|
891
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_ph_slab_freed_obj,st.freed_obj,__ATOMIC_RELAXED);
|
|
892
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_ph_slab_freed_str,st.freed_str,__ATOMIC_RELAXED);
|
|
893
|
+
SP_ATOMIC_FETCH_ADD(&sp_gc_parked_acc,st.parked,__ATOMIC_RELAXED);
|
|
894
|
+
#else
|
|
895
|
+
sp_gc_ph_slab_freed_obj+=st.freed_obj; sp_gc_ph_slab_freed_str+=st.freed_str; sp_gc_parked_acc+=st.parked;
|
|
896
|
+
#endif
|
|
897
|
+
}
|
|
898
|
+
/* the form the stop-the-world parallel sweep (sp_sched.c) runs per slot */
|
|
899
|
+
void sp_gc_sweep_chunks_slot(int wid){ sp_gc_sweep_chunks(wid,sp_gc_sweep_full_now); }
|
|
900
|
+
static int sp_ivt_n; /* the ivar tables of builtin values (sp_ivtbl_put, below) */
|
|
901
|
+
static void sp_ivt_mark(int full, void (*drain)(void));
|
|
902
|
+
static sp_gc_hdr **sp_gc_vg_cand; static size_t sp_gc_vg_n, sp_gc_vg_cap; static unsigned sp_gc_vg_gen;
|
|
903
|
+
static void sp_gc_vg_cand_cb(void *hp,void *arg){
|
|
904
|
+
(void)arg; sp_gc_hdr *h=(sp_gc_hdr*)hp;
|
|
905
|
+
if(!sp_gc_vg_cand||h->old||h->marked==sp_gc_vg_gen)return;
|
|
906
|
+
if(sp_gc_vg_n==sp_gc_vg_cap){sp_gc_vg_cap*=2;sp_gc_vg_cand=(sp_gc_hdr**)realloc(sp_gc_vg_cand,sizeof(sp_gc_hdr*)*sp_gc_vg_cap);if(!sp_gc_vg_cand)return;}
|
|
907
|
+
sp_gc_vg_cand[sp_gc_vg_n++]=h;
|
|
908
|
+
}
|
|
909
|
+
/* The generational check, out of line: it is off unless SPINEL_GC_VERIFY_GEN
|
|
910
|
+
is set, and inline it added 1.8KB to the middle of the collector -- code
|
|
911
|
+
the common cycle walks past on its way to the sweep. */
|
|
912
|
+
static SP_NOINLINE void sp_gc_verify_gen_run(void) {
|
|
913
|
+
/* Snapshot the young objects the minor did NOT reach, then mark whole-heap
|
|
914
|
+
and see which of them the full mark does: each one is held only through
|
|
915
|
+
an old object the barrier failed to record. Without the snapshot the
|
|
916
|
+
full mark's own stamps make the two indistinguishable. */
|
|
917
|
+
unsigned minor_gen = sp_gc_mark_gen;
|
|
918
|
+
sp_str_verify_begin();
|
|
919
|
+
size_t cap = 4096, n = 0;
|
|
920
|
+
sp_gc_hdr **cand = (sp_gc_hdr **)malloc(sizeof(sp_gc_hdr *) * cap);
|
|
921
|
+
if (cand) {
|
|
922
|
+
/* the slab's young objects first, then the lists' */
|
|
923
|
+
sp_gc_vg_cand=cand; sp_gc_vg_n=0; sp_gc_vg_cap=cap; sp_gc_vg_gen=minor_gen;
|
|
924
|
+
sp_slab_each_object(1,0,sp_gc_vg_cand_cb,NULL);
|
|
925
|
+
cand=sp_gc_vg_cand; n=sp_gc_vg_n; cap=sp_gc_vg_cap;
|
|
926
|
+
#ifdef SP_THREADS
|
|
927
|
+
{ int w=sp_active_workers; if(w<1)w=1; if(w>SP_MAX_WORKERS)w=SP_MAX_WORKERS;
|
|
928
|
+
for(int i=0;i<w;i++)
|
|
929
|
+
for(sp_gc_hdr*h=sp_gc_wslot[i].young;h;h=h->next)
|
|
930
|
+
if(!h->old && h->marked!=minor_gen){
|
|
931
|
+
if(n==cap){cap*=2;cand=(sp_gc_hdr**)realloc(cand,sizeof(sp_gc_hdr*)*cap);if(!cand)break;}
|
|
932
|
+
cand[n++]=h; } }
|
|
933
|
+
#else
|
|
934
|
+
for(sp_gc_hdr*h=sp_gc_heap;h;h=h->next)
|
|
935
|
+
if(!h->old && h->marked!=minor_gen){
|
|
936
|
+
if(n==cap){cap*=2;cand=(sp_gc_hdr**)realloc(cand,sizeof(sp_gc_hdr*)*cap);if(!cand)break;}
|
|
937
|
+
cand[n++]=h; }
|
|
938
|
+
#endif
|
|
939
|
+
}
|
|
940
|
+
sp_gc_mark_gen = (sp_gc_mark_gen + 1) & 0x7ffffffu;
|
|
941
|
+
if(!sp_gc_mark_gen) sp_gc_mark_gen = 1;
|
|
942
|
+
sp_gc_mark_all();
|
|
943
|
+
/* The sweep that follows uses THIS mark, so it has to keep everything the
|
|
944
|
+
minor's would have kept, or the check changes what the program sees. A
|
|
945
|
+
minor takes every recorded holder as a root -- a dead old object on the
|
|
946
|
+
set still keeps its young referents alive until a full cycle -- and the
|
|
947
|
+
whole-heap walk above does not. With survivors now aged rather than
|
|
948
|
+
promoted, a referent the precise walk dropped stayed reachable from its
|
|
949
|
+
(dead, still recorded) holder, which the next minor walked into freed
|
|
950
|
+
memory. Walking the set here keeps the two marks equivalent; it hides
|
|
951
|
+
no miss, since a candidate reached through a RECORDED holder was reached
|
|
952
|
+
by the minor and is not a candidate. */
|
|
953
|
+
for(int ri=0;ri<sp_gc_nremembered;ri++){
|
|
954
|
+
sp_gc_hdr *rh=(sp_gc_hdr*)sp_gc_remembered[ri]-1;
|
|
955
|
+
if(rh->scan) rh->scan(sp_gc_remembered[ri]);
|
|
956
|
+
}
|
|
957
|
+
for(int pi=0;pi<sp_gc_npinned;pi++){
|
|
958
|
+
sp_gc_hdr *ph=(sp_gc_hdr*)sp_gc_pinned[pi]-1;
|
|
959
|
+
if(ph->scan) ph->scan(sp_gc_pinned[pi]);
|
|
960
|
+
}
|
|
961
|
+
sp_gc_mark_drain();
|
|
962
|
+
/* the sweep keeps what this mark reached: the ivar tables too */
|
|
963
|
+
if (sp_ivt_n) sp_ivt_mark(0, sp_gc_mark_drain);
|
|
964
|
+
size_t str_leaked = sp_str_verify_end();
|
|
965
|
+
if(str_leaked){
|
|
966
|
+
fprintf(stderr,"spinel: GC generational check: %zu young STRING(s) reachable only "
|
|
967
|
+
"through an old object the barrier did not record\n", str_leaked);
|
|
968
|
+
for(sp_gc_hdr*h=sp_gc_old_heap;h;h=h->next){
|
|
969
|
+
if(h->dirty||!h->scan) continue;
|
|
970
|
+
sp_str_verify_probe_arm();
|
|
971
|
+
sp_gc_verify_probe=sp_gc_mark_gen; sp_gc_verify_probe_on=1;
|
|
972
|
+
h->scan((char*)h+sizeof(sp_gc_hdr));
|
|
973
|
+
sp_gc_verify_probe_on=0;
|
|
974
|
+
if(sp_str_verify_probe_hit()) fprintf(stderr,"spinel: string holder scan=%p\n",(void*)h->scan);
|
|
975
|
+
}
|
|
976
|
+
sp_str_verify_probe_done();
|
|
977
|
+
sp_gc_verify_gen_fail = 1;
|
|
978
|
+
}
|
|
979
|
+
size_t leaked = 0;
|
|
980
|
+
for(size_t i=0;cand&&i<n;i++) if(cand[i]->marked==sp_gc_mark_gen) leaked++;
|
|
981
|
+
if(leaked){
|
|
982
|
+
fprintf(stderr,"spinel: GC generational check: %zu young object(s) reachable only "
|
|
983
|
+
"through an old one the barrier did not record\n", leaked);
|
|
984
|
+
/* Name the holders: an old object reaching one of the LEAKED objects is
|
|
985
|
+
where the missing barrier is, and its scan function names the type.
|
|
986
|
+
|
|
987
|
+
The probe has to test membership of the leaked set, not "young and
|
|
988
|
+
marked". Every live young object carries the full mark's stamp by the
|
|
989
|
+
time this runs, so the looser test named every old object that holds
|
|
990
|
+
any young one at all -- which, in a program whose long-lived objects
|
|
991
|
+
point at fresh ones, is all of them, on every collection. The count
|
|
992
|
+
above was always right; the names were noise. */
|
|
993
|
+
/* Leave the full mark's stamp on the leaked objects ALONE and take it
|
|
994
|
+
off every other young one, so the probe's "young and stamped" test
|
|
995
|
+
means "leaked" for the duration. A spare bit cannot do this: marked is
|
|
996
|
+
a wrapping generation counter that takes every value in its range. */
|
|
997
|
+
unsigned other = sp_gc_mark_gen ^ 1u;
|
|
998
|
+
char *leaked_flag = (char *)calloc(n ? n : 1, 1);
|
|
999
|
+
for(size_t i=0;i<n;i++) leaked_flag[i] = (cand[i]->marked==sp_gc_mark_gen);
|
|
1000
|
+
#ifndef SP_THREADS
|
|
1001
|
+
for(sp_gc_hdr*h=sp_gc_heap;h;h=h->next) if(h->marked==sp_gc_mark_gen) h->marked=other;
|
|
1002
|
+
#endif
|
|
1003
|
+
for(size_t i=0;i<n;i++) if(leaked_flag[i]) cand[i]->marked = sp_gc_mark_gen;
|
|
1004
|
+
for(sp_gc_hdr*h=sp_gc_old_heap;h;h=h->next){
|
|
1005
|
+
if(h->dirty||!h->scan) continue;
|
|
1006
|
+
sp_gc_verify_probe_hit=0; sp_gc_verify_probe=sp_gc_mark_gen; sp_gc_verify_probe_on=1;
|
|
1007
|
+
h->scan((char*)h+sizeof(sp_gc_hdr));
|
|
1008
|
+
sp_gc_verify_probe_on=0;
|
|
1009
|
+
if(sp_gc_verify_probe_hit) fprintf(stderr,"spinel: holder scan=%p\n",(void*)h->scan);
|
|
1010
|
+
}
|
|
1011
|
+
#ifndef SP_THREADS
|
|
1012
|
+
for(sp_gc_hdr*h=sp_gc_heap;h;h=h->next) if(h->marked==other) h->marked=sp_gc_mark_gen;
|
|
1013
|
+
#endif
|
|
1014
|
+
free(leaked_flag);
|
|
1015
|
+
sp_gc_verify_gen_fail = 1;
|
|
1016
|
+
}
|
|
1017
|
+
free(cand);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/* Phase accounting (SPINEL_GC_PHASES=1). sp_gc_stat_seconds says how much time
|
|
1021
|
+
a collection cost; it never said which part of one, so "GC is half of wall"
|
|
1022
|
+
gave no way to choose between the mark and the sweep. Each figure below had
|
|
1023
|
+
been measured by patching this file by hand, more than once; this keeps the
|
|
1024
|
+
measurement instead of redoing it.
|
|
1025
|
+
|
|
1026
|
+
The remembered-set clear gets a bucket of its own rather than being folded
|
|
1027
|
+
into a sweep, and earned it: the threaded clear used to walk the WHOLE old
|
|
1028
|
+
heap on every non-full cycle, O(live) per collection, and nothing attributed
|
|
1029
|
+
it -- 5.7s of 13.1s of collector time on a server workload, invisible in the
|
|
1030
|
+
total. That is what this found, and #4380 then removed; it reads ~0 now. */
|
|
1031
|
+
double sp_gc_ph_wait_top = 0; /* joining the previous sweep at the top of the collection */
|
|
1032
|
+
double sp_gc_ph_park_sweeping = 0; unsigned long long sp_gc_ph_park_sweeping_n = 0; /* the park wait spent while an owner sweep of the previous cycle was still running (sp_sched.c) */
|
|
1033
|
+
double sp_gc_ph_mark = 0, sp_gc_ph_oldsweep = 0, sp_gc_ph_slotsweep = 0,
|
|
1034
|
+
sp_gc_ph_rembclear = 0, sp_gc_ph_strsweep = 0, sp_gc_ph_trim = 0;
|
|
1035
|
+
double sp_gc_ph_slot_max = 0, sp_gc_ph_task_sum = 0, sp_gc_ph_task_obj = 0, sp_gc_ph_task_sold = 0, sp_gc_ph_task_syoung = 0; /* filled by the threaded sweep driver */
|
|
1036
|
+
unsigned long long sp_gc_ph_mk_helpers = 0, sp_gc_ph_mk_drains = 0;
|
|
1037
|
+
double sp_gc_ph_conc_wait = 0, sp_gc_ph_conc_wall = 0, sp_gc_ph_barrier = 0, sp_gc_ph_park = 0, sp_gc_ph_apply_obj = 0, sp_gc_ph_apply_str = 0, sp_gc_ph_apply_release = 0; unsigned long long sp_gc_ph_conc_waits = 0; /* joining the previous sweep under the barrier */
|
|
1038
|
+
double sp_gc_ph_mk_roots = 0, sp_gc_ph_mk_fibers = 0,
|
|
1039
|
+
sp_gc_ph_mk_globals = 0, sp_gc_ph_mk_scan = 0;
|
|
1040
|
+
int sp_gc_ph_on = 0;
|
|
1041
|
+
unsigned long long sp_gc_stat_collections=0;
|
|
1042
|
+
unsigned long long sp_gc_stat_fulls=0;
|
|
1043
|
+
double sp_gc_stat_seconds=0;
|
|
1044
|
+
static double sp_gc_stat_now(void){
|
|
1045
|
+
#if defined(CLOCK_MONOTONIC)
|
|
1046
|
+
struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts);
|
|
1047
|
+
return (double)ts.tv_sec+(double)ts.tv_nsec*1e-9;
|
|
1048
|
+
#else
|
|
1049
|
+
return 0.0;
|
|
1050
|
+
#endif
|
|
1051
|
+
}
|
|
1052
|
+
/* Close the phase that ended here. Off by default, so a build that does not ask
|
|
1053
|
+
for the breakdown pays one not-taken branch per boundary, against a
|
|
1054
|
+
collection measured in microseconds. */
|
|
1055
|
+
#define SP_GC_PH(bucket) \
|
|
1056
|
+
do { if (sp_gc_ph_on) { double _t = sp_gc_stat_now(); (bucket) += _t - ph_t; ph_t = _t; } } while (0)
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
/* What malloc still holds is container buffers; the objects and strings
|
|
1060
|
+
are the slab's. A trim is still a 60 ms walk of every arena on a
|
|
1061
|
+
32-worker box, so once a second is the cadence (SPINEL_GC_TRIM_SEC). */
|
|
1062
|
+
unsigned long long sp_gc_ph_trim_req = 0, sp_gc_ph_trim_inline = 0; double sp_gc_ph_trim_inline_t = 0;
|
|
1063
|
+
static void sp_gc_trim_request(void){
|
|
1064
|
+
static double last_trim=0, trim_every=-1;
|
|
1065
|
+
if(trim_every<0){ const char*e=getenv("SPINEL_GC_TRIM_SEC"); trim_every=(e&&*e)?atof(e):1.0; }
|
|
1066
|
+
double now=sp_gc_stat_now();
|
|
1067
|
+
if(trim_every>0&&now-last_trim>=trim_every){
|
|
1068
|
+
last_trim=now;
|
|
1069
|
+
if (sp_gc_ph_on) sp_gc_ph_trim_req++;
|
|
1070
|
+
#ifdef SP_THREADS
|
|
1071
|
+
/* Not here: a trim walks every arena, 60 ms on a 32-worker box, and
|
|
1072
|
+
under stop-the-world that was the longest pause the server had (p99
|
|
1073
|
+
147 ms against 105 without it). The trimmer thread (sp_sched.c) does
|
|
1074
|
+
it beside the running program, where a mutator that lands on the
|
|
1075
|
+
arena being walked waits a few milliseconds for that arena alone. */
|
|
1076
|
+
if (sp_gc_trimmer_on) SP_ATOMIC_STORE(&sp_gc_trim_wanted, 1, __ATOMIC_RELEASE);
|
|
1077
|
+
else { double t0 = sp_gc_ph_on ? sp_gc_stat_now() : 0; malloc_trim(0); if (sp_gc_ph_on) { sp_gc_ph_trim_inline++; sp_gc_ph_trim_inline_t += sp_gc_stat_now() - t0; } } /* no worker pool yet, so no trimmer: a single-threaded program on the mt archive */
|
|
1078
|
+
#else
|
|
1079
|
+
malloc_trim(0);
|
|
1080
|
+
#endif
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
/* ---- ObjectSpace.define_finalizer ----
|
|
1084
|
+
* A finalizer is a Ruby callable run after its object dies. The callable is
|
|
1085
|
+
* kept (and marked) on the Ruby side, in ObjectSpace's table, under an index;
|
|
1086
|
+
* this registry holds only the object's identity and that index, and so does
|
|
1087
|
+
* not keep the object alive. After a cycle's mark, an entry whose object the
|
|
1088
|
+
* cycle will free moves to the pending list, and the pending finalizers run
|
|
1089
|
+
* at the program's next safe point (SP_FIN_POLL in sp_gc.h) -- never inside
|
|
1090
|
+
* the collection, nor inside the allocation that set it off -- through the
|
|
1091
|
+
* runner the Ruby side installed. What is still registered at exit runs then, as in
|
|
1092
|
+
* CRuby. A threaded build guards the lists with a lock (never held across a
|
|
1093
|
+
* callback); the mark-time scan runs with the world stopped, and no mutator
|
|
1094
|
+
* stops inside a locked region, which allocates nothing. */
|
|
1095
|
+
typedef struct { void *obj; int tag; uint64_t bits; sp_int idx; } sp_fin_ent;
|
|
1096
|
+
static sp_fin_ent *sp_fin_tab; static int sp_fin_n, sp_fin_cap;
|
|
1097
|
+
static sp_int *sp_fin_pend; static int sp_fin_pn, sp_fin_pcap;
|
|
1098
|
+
static void (*sp_fin_runner)(int64_t);
|
|
1099
|
+
volatile int sp_fin_pending_flag; /* sp_gc.h: SP_FIN_POLL */
|
|
1100
|
+
static int sp_fin_running, sp_fin_exit_armed;
|
|
1101
|
+
#ifdef SP_THREADS
|
|
1102
|
+
static pthread_mutex_t sp_fin_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
1103
|
+
#define SP_FIN_LOCK() pthread_mutex_lock(&sp_fin_lock)
|
|
1104
|
+
#define SP_FIN_UNLOCK() pthread_mutex_unlock(&sp_fin_lock)
|
|
1105
|
+
#else
|
|
1106
|
+
#define SP_FIN_LOCK() ((void)0)
|
|
1107
|
+
#define SP_FIN_UNLOCK() ((void)0)
|
|
1108
|
+
#endif
|
|
1109
|
+
|
|
1110
|
+
static void sp_fin_push_pending(sp_int idx) {
|
|
1111
|
+
if (sp_fin_pn == sp_fin_pcap) {
|
|
1112
|
+
sp_fin_pcap = sp_fin_pcap ? sp_fin_pcap * 2 : 16;
|
|
1113
|
+
sp_fin_pend = realloc(sp_fin_pend, sizeof(sp_int) * (size_t)sp_fin_pcap);
|
|
1114
|
+
if (!sp_fin_pend) { fprintf(stderr, "spinel: out of memory\n"); exit(1); }
|
|
1115
|
+
}
|
|
1116
|
+
sp_fin_pend[sp_fin_pn++] = idx;
|
|
1117
|
+
SP_ATOMIC_STORE(&sp_fin_pending_flag, 1, __ATOMIC_RELAXED);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
void sp_fin_run_pending(void) {
|
|
1121
|
+
SP_FIN_LOCK();
|
|
1122
|
+
if (sp_fin_running || !sp_fin_runner || sp_fin_pn == 0) { SP_FIN_UNLOCK(); return; }
|
|
1123
|
+
sp_fin_running = 1;
|
|
1124
|
+
/* in the order the objects were registered, as CRuby runs them; a
|
|
1125
|
+
finalizer that frees more (or registers more) lands in the next batch */
|
|
1126
|
+
while (sp_fin_pn > 0) {
|
|
1127
|
+
int n = sp_fin_pn;
|
|
1128
|
+
sp_int *batch = malloc(sizeof(sp_int) * (size_t)n);
|
|
1129
|
+
if (!batch) { fprintf(stderr, "spinel: out of memory\n"); exit(1); }
|
|
1130
|
+
memcpy(batch, sp_fin_pend, sizeof(sp_int) * (size_t)n);
|
|
1131
|
+
sp_fin_pn = 0;
|
|
1132
|
+
SP_ATOMIC_STORE(&sp_fin_pending_flag, 0, __ATOMIC_RELAXED);
|
|
1133
|
+
SP_FIN_UNLOCK();
|
|
1134
|
+
for (int i = 0; i < n; i++) sp_fin_runner((int64_t)batch[i]);
|
|
1135
|
+
free(batch);
|
|
1136
|
+
SP_FIN_LOCK();
|
|
1137
|
+
}
|
|
1138
|
+
sp_fin_running = 0;
|
|
1139
|
+
SP_FIN_UNLOCK();
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
static void sp_fin_at_exit(void) {
|
|
1143
|
+
SP_FIN_LOCK();
|
|
1144
|
+
for (int i = 0; i < sp_fin_n; i++) sp_fin_push_pending(sp_fin_tab[i].idx);
|
|
1145
|
+
sp_fin_n = 0;
|
|
1146
|
+
SP_FIN_UNLOCK();
|
|
1147
|
+
sp_fin_run_pending();
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/* The program is ending: run what is still registered. Called where the
|
|
1151
|
+
program ends in Ruby terms (main's return, exit, an uncaught raise) --
|
|
1152
|
+
before the C exit handlers run, which is where a library's own teardown
|
|
1153
|
+
happens. The atexit registration is only the fallback for a path that
|
|
1154
|
+
leaves some other way. */
|
|
1155
|
+
void sp_fin_run_exit(void) {
|
|
1156
|
+
if (sp_fin_n || sp_fin_pn) sp_fin_at_exit();
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
void sp_fin_set_runner(void (*f)(int64_t)) {
|
|
1160
|
+
sp_fin_runner = f;
|
|
1161
|
+
if (!sp_fin_exit_armed) { sp_fin_exit_armed = 1; atexit(sp_fin_at_exit); }
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/* An object with a collector header is watched by address; any other value
|
|
1165
|
+
(an immediate, a string on the string heap) is matched by its bits and
|
|
1166
|
+
keeps its finalizer until exit, which is when its storage goes too. */
|
|
1167
|
+
static void sp_fin_ident(sp_RbVal obj, void **p, int *tag, uint64_t *bits) {
|
|
1168
|
+
*p = (obj.tag == SP_TAG_OBJ) ? obj.v.p : NULL;
|
|
1169
|
+
*tag = (int)obj.tag;
|
|
1170
|
+
*bits = 0;
|
|
1171
|
+
memcpy(bits, &obj.v, sizeof obj.v < sizeof *bits ? sizeof obj.v : sizeof *bits);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
void sp_fin_register(sp_RbVal obj, sp_int idx) {
|
|
1175
|
+
void *p; int tag; uint64_t bits;
|
|
1176
|
+
sp_fin_ident(obj, &p, &tag, &bits);
|
|
1177
|
+
SP_FIN_LOCK();
|
|
1178
|
+
if (sp_fin_n == sp_fin_cap) {
|
|
1179
|
+
sp_fin_cap = sp_fin_cap ? sp_fin_cap * 2 : 16;
|
|
1180
|
+
sp_fin_tab = realloc(sp_fin_tab, sizeof(sp_fin_ent) * (size_t)sp_fin_cap);
|
|
1181
|
+
if (!sp_fin_tab) { fprintf(stderr, "spinel: out of memory\n"); exit(1); }
|
|
1182
|
+
}
|
|
1183
|
+
sp_fin_tab[sp_fin_n].obj = p;
|
|
1184
|
+
sp_fin_tab[sp_fin_n].tag = tag;
|
|
1185
|
+
sp_fin_tab[sp_fin_n].bits = bits;
|
|
1186
|
+
sp_fin_tab[sp_fin_n].idx = idx;
|
|
1187
|
+
sp_fin_n++;
|
|
1188
|
+
SP_FIN_UNLOCK();
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/* Drops one registration of `obj` and answers its index (the Ruby side then
|
|
1192
|
+
releases the callable), or -1 when none is left. */
|
|
1193
|
+
sp_int sp_fin_unregister_one(sp_RbVal obj) {
|
|
1194
|
+
void *p; int tag; uint64_t bits;
|
|
1195
|
+
sp_fin_ident(obj, &p, &tag, &bits);
|
|
1196
|
+
sp_int r = -1;
|
|
1197
|
+
SP_FIN_LOCK();
|
|
1198
|
+
for (int i = 0; i < sp_fin_n; i++) {
|
|
1199
|
+
sp_fin_ent *e = &sp_fin_tab[i];
|
|
1200
|
+
if (p ? e->obj == p : (!e->obj && e->tag == tag && e->bits == bits)) {
|
|
1201
|
+
r = e->idx;
|
|
1202
|
+
memmove(e, e + 1, sizeof(sp_fin_ent) * (size_t)(sp_fin_n - i - 1));
|
|
1203
|
+
sp_fin_n--;
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
SP_FIN_UNLOCK();
|
|
1208
|
+
return r;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/* After the mark, before any sweep: which watched objects does this cycle
|
|
1212
|
+
free? A full cycle frees anything unmarked; a minor one only the young. */
|
|
1213
|
+
static void sp_fin_after_mark(int full) {
|
|
1214
|
+
int keep = 0;
|
|
1215
|
+
for (int i = 0; i < sp_fin_n; i++) {
|
|
1216
|
+
void *p = sp_fin_tab[i].obj;
|
|
1217
|
+
int live = 1;
|
|
1218
|
+
if (p) {
|
|
1219
|
+
sp_gc_hdr *h = (sp_gc_hdr *)p - 1;
|
|
1220
|
+
live = full ? (h->marked == sp_gc_mark_gen) : (h->old || h->marked == sp_gc_mark_gen);
|
|
1221
|
+
}
|
|
1222
|
+
if (live) sp_fin_tab[keep++] = sp_fin_tab[i];
|
|
1223
|
+
else sp_fin_push_pending(sp_fin_tab[i].idx);
|
|
1224
|
+
}
|
|
1225
|
+
sp_fin_n = keep;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/* ---- the ivar tables of builtin values ----
|
|
1229
|
+
* CRuby keeps an ivar of an Array, a Hash or a Random in a table keyed by
|
|
1230
|
+
* the object, which does not keep the object alive. This is that table: a
|
|
1231
|
+
* dense array of (object, ivar table) entries and an open-addressing index
|
|
1232
|
+
* into it, both malloc'd. An ivar table is an ordinary GC object, made and
|
|
1233
|
+
* written by the TU (sp_bivar_set). After the mark, the tables of the
|
|
1234
|
+
* objects the cycle keeps are marked, again until nothing new is reached
|
|
1235
|
+
* (a table can hold another keyed object); then, with the marks final, the
|
|
1236
|
+
* entries of objects the cycle frees are dropped, where the finalizer
|
|
1237
|
+
* registry drops its own. The lock is the finalizer registry's rule: never
|
|
1238
|
+
* held across an allocation, so no collection stops a mutator inside it. */
|
|
1239
|
+
typedef struct { const void *obj; void *tbl; } sp_ivt_ent;
|
|
1240
|
+
static sp_ivt_ent *sp_ivt; static int sp_ivt_cap; /* and sp_ivt_n, declared above sp_gc_verify_gen_run */
|
|
1241
|
+
static int *sp_ivt_ix; static int sp_ivt_icap; /* entry index + 1; 0 empty; icap a power of two */
|
|
1242
|
+
#ifdef SP_THREADS
|
|
1243
|
+
static pthread_mutex_t sp_ivt_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
1244
|
+
#define SP_IVT_LOCK() pthread_mutex_lock(&sp_ivt_lock)
|
|
1245
|
+
#define SP_IVT_UNLOCK() pthread_mutex_unlock(&sp_ivt_lock)
|
|
1246
|
+
#else
|
|
1247
|
+
#define SP_IVT_LOCK() ((void)0)
|
|
1248
|
+
#define SP_IVT_UNLOCK() ((void)0)
|
|
1249
|
+
#endif
|
|
1250
|
+
|
|
1251
|
+
static size_t sp_ivt_hash(const void *p) {
|
|
1252
|
+
uint64_t x = (uint64_t)(uintptr_t)p;
|
|
1253
|
+
x ^= x >> 33; x *= 0xff51afd7ed558ccdULL; x ^= x >> 33;
|
|
1254
|
+
return (size_t)x;
|
|
1255
|
+
}
|
|
1256
|
+
/* the index slot holding obj's entry, or the empty slot where it would go */
|
|
1257
|
+
static int sp_ivt_slot(const void *obj) {
|
|
1258
|
+
size_t m = (size_t)sp_ivt_icap - 1, i = sp_ivt_hash(obj) & m;
|
|
1259
|
+
while (sp_ivt_ix[i] && sp_ivt[sp_ivt_ix[i] - 1].obj != obj) i = (i + 1) & m;
|
|
1260
|
+
return (int)i;
|
|
1261
|
+
}
|
|
1262
|
+
static void sp_ivt_reindex(int icap) {
|
|
1263
|
+
free(sp_ivt_ix);
|
|
1264
|
+
sp_ivt_ix = (int *)calloc((size_t)icap, sizeof(int));
|
|
1265
|
+
if (!sp_ivt_ix) { fprintf(stderr, "spinel: out of memory\n"); exit(1); }
|
|
1266
|
+
sp_ivt_icap = icap;
|
|
1267
|
+
for (int e = 0; e < sp_ivt_n; e++) sp_ivt_ix[sp_ivt_slot(sp_ivt[e].obj)] = e + 1;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const char *(*sp_ivtbl_inspect_fn)(void *tbl) = NULL;
|
|
1271
|
+
|
|
1272
|
+
void *sp_ivtbl_get(const void *obj) {
|
|
1273
|
+
void *t = NULL;
|
|
1274
|
+
SP_IVT_LOCK();
|
|
1275
|
+
if (sp_ivt_n && obj) { int s = sp_ivt_slot(obj); if (sp_ivt_ix[s]) t = sp_ivt[sp_ivt_ix[s] - 1].tbl; }
|
|
1276
|
+
SP_IVT_UNLOCK();
|
|
1277
|
+
return t;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
void sp_ivtbl_put(const void *obj, void *tbl) {
|
|
1281
|
+
if (!obj) return;
|
|
1282
|
+
SP_IVT_LOCK();
|
|
1283
|
+
if (sp_ivt_icap && sp_ivt_n) {
|
|
1284
|
+
int s = sp_ivt_slot(obj);
|
|
1285
|
+
if (sp_ivt_ix[s]) { sp_ivt[sp_ivt_ix[s] - 1].tbl = tbl; SP_IVT_UNLOCK(); return; }
|
|
1286
|
+
}
|
|
1287
|
+
if (sp_ivt_n == sp_ivt_cap) {
|
|
1288
|
+
sp_ivt_cap = sp_ivt_cap ? sp_ivt_cap * 2 : 16;
|
|
1289
|
+
sp_ivt = (sp_ivt_ent *)realloc(sp_ivt, sizeof(sp_ivt_ent) * (size_t)sp_ivt_cap);
|
|
1290
|
+
if (!sp_ivt) { fprintf(stderr, "spinel: out of memory\n"); exit(1); }
|
|
1291
|
+
}
|
|
1292
|
+
sp_ivt[sp_ivt_n].obj = obj; sp_ivt[sp_ivt_n].tbl = tbl; sp_ivt_n++;
|
|
1293
|
+
if ((size_t)sp_ivt_n * 2 > (size_t)sp_ivt_icap) sp_ivt_reindex(sp_ivt_icap ? sp_ivt_icap * 2 : 32);
|
|
1294
|
+
else sp_ivt_ix[sp_ivt_slot(obj)] = sp_ivt_n;
|
|
1295
|
+
SP_IVT_UNLOCK();
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/* Whether this cycle keeps obj: the finalizer registry's test. A value with
|
|
1299
|
+
one of the markers sp_gc_mark passes over (a static or a non-heap object)
|
|
1300
|
+
is never freed, so it always lives, and so does an unaligned key, which
|
|
1301
|
+
names a class rather than an object. */
|
|
1302
|
+
static int sp_ivt_key_live(const void *obj, int full) {
|
|
1303
|
+
if ((uintptr_t)obj & 7) return 1; /* a class's key (sp_bivar_key): no object behind it */
|
|
1304
|
+
unsigned char pm = ((const unsigned char *)obj)[-1];
|
|
1305
|
+
if (pm == 0xfc || pm == 0xff || pm == 0xfd || pm == 0xf1 || pm == 0xfb || pm == 0xf8) return 1;
|
|
1306
|
+
const sp_gc_hdr *h = (const sp_gc_hdr *)obj - 1;
|
|
1307
|
+
return full ? h->marked == sp_gc_mark_gen : (h->old || h->marked == sp_gc_mark_gen);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
/* After the mark's drain: the tables of the live objects, as ephemerons. */
|
|
1311
|
+
static void sp_ivt_mark(int full, void (*drain)(void)) {
|
|
1312
|
+
if (!sp_ivt_n) return;
|
|
1313
|
+
unsigned char *done = (unsigned char *)calloc((size_t)sp_ivt_n, 1);
|
|
1314
|
+
if (!done) { fprintf(stderr, "spinel: out of memory\n"); exit(1); }
|
|
1315
|
+
for (int more = 1; more; ) {
|
|
1316
|
+
more = 0;
|
|
1317
|
+
for (int e = 0; e < sp_ivt_n; e++)
|
|
1318
|
+
if (!done[e] && sp_ivt_key_live(sp_ivt[e].obj, full)) { done[e] = 1; more = 1; sp_gc_mark(sp_ivt[e].tbl); }
|
|
1319
|
+
if (more) drain();
|
|
1320
|
+
}
|
|
1321
|
+
free(done);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/* With the marks final, before any sweep: the entries of the objects this
|
|
1325
|
+
cycle frees go. */
|
|
1326
|
+
static void sp_ivt_after_mark(int full) {
|
|
1327
|
+
int keep = 0;
|
|
1328
|
+
for (int e = 0; e < sp_ivt_n; e++)
|
|
1329
|
+
if (sp_ivt_key_live(sp_ivt[e].obj, full)) sp_ivt[keep++] = sp_ivt[e];
|
|
1330
|
+
if (keep != sp_ivt_n) { sp_ivt_n = keep; sp_ivt_reindex(sp_ivt_icap); }
|
|
1331
|
+
}
|
|
1332
|
+
static void sp_ivt_drain(void) { sp_gc_mark_drain_all(); sp_gc_mkl_fold(); }
|
|
1333
|
+
|
|
1334
|
+
void sp_gc_collect(void){
|
|
1335
|
+
/* The previous cycle's sweep may still be running beside the mutators:
|
|
1336
|
+
finish it before anything here walks a list or reads a live total. */
|
|
1337
|
+
double ph_top0 = sp_gc_ph_on ? sp_gc_stat_now() : 0;
|
|
1338
|
+
if(sp_gc_conc_wait_hook) sp_gc_conc_wait_hook();
|
|
1339
|
+
/* the slab's workers hold slots claimed ahead of use: unclaimed before
|
|
1340
|
+
anything here reads the young bits (they would name garbage) */
|
|
1341
|
+
sp_slab_runs_release();
|
|
1342
|
+
sp_slab_quarantine_trim();
|
|
1343
|
+
size_t ob_before = sp_gc_bytes;
|
|
1344
|
+
double stat_t0 = sp_gc_stat_now();
|
|
1345
|
+
double ph_t = stat_t0;
|
|
1346
|
+
if (sp_gc_ph_on) sp_gc_ph_wait_top += stat_t0 - ph_top0;
|
|
1347
|
+
/* SPINEL_GC_AGE keeps a young survivor young for a second look and repairs
|
|
1348
|
+
the remembered set by probing what the cycle promoted, which it finds at
|
|
1349
|
+
the head of the old LIST. A slab object's promotion is a bit in its chunk
|
|
1350
|
+
and nothing walks those, so with the slab on an aged list object held by
|
|
1351
|
+
a slab object promoted this cycle would go unrecorded: aging is off then. */
|
|
1352
|
+
if(sp_gc_age_on&&sp_slab_on>0)sp_gc_age_on=0;
|
|
1353
|
+
int full=(sp_gc_cycle%sp_gc_full_interval==0);sp_gc_cycle++;
|
|
1354
|
+
/* Forced by growth rather than by the schedule: the old generation has
|
|
1355
|
+
outgrown what the last full found live in it. */
|
|
1356
|
+
int forced_full=0;
|
|
1357
|
+
if(!full&&!sp_gc_full_interval_fixed&&
|
|
1358
|
+
sp_gc_old_bytes>sp_gc_old_live*2+SP_GC_OLD_GROWTH_SLACK){ full=1; forced_full=1; }
|
|
1359
|
+
/* The string heap runs its own major cadence, and a string major needs a
|
|
1360
|
+
whole-heap mark: on a minor cycle the sweep has to stand it down, since
|
|
1361
|
+
the old strings only old objects hold were never marked. Standing it down
|
|
1362
|
+
silently let the string old generation grow to the object FULL cadence
|
|
1363
|
+
instead of its own -- 36 MB of old strings against a 0.6 MB live set on
|
|
1364
|
+
one benchmark, and the wall with it. So a due string major makes the
|
|
1365
|
+
cycle full. Off-schedule for the object heap, so its cadence does not
|
|
1366
|
+
adapt on the sample. */
|
|
1367
|
+
int str_full=0;
|
|
1368
|
+
if(!full&&sp_gc_minor_on&&!sp_gc_full_interval_fixed&&
|
|
1369
|
+
sp_gc_str_major_due_hook&&sp_gc_str_major_due_hook()){ full=1; str_full=1; }
|
|
1370
|
+
if(full)sp_gc_full_runs++;
|
|
1371
|
+
if(!full)sp_gc_minors_since_full++;
|
|
1372
|
+
/* new mark generation: every object becomes unmarked without touching it.
|
|
1373
|
+
On the (30-bit) wrap, clear the whole heap once so no stale stamp can
|
|
1374
|
+
alias the reused generation value. */
|
|
1375
|
+
sp_gc_mark_gen=(sp_gc_mark_gen+1)&0x7ffffffu; /* marked is 27 bits (see sp_gc_hdr) */
|
|
1376
|
+
if(!sp_gc_mark_gen){
|
|
1377
|
+
sp_gc_mark_gen=1;
|
|
1378
|
+
sp_slab_each_object(1,1,sp_gc_stamp_reset_cb,NULL);
|
|
1379
|
+
for(sp_gc_hdr*hh=sp_gc_old_heap;hh;hh=hh->next)hh->marked=0;
|
|
1380
|
+
#ifdef SP_THREADS
|
|
1381
|
+
{ int n=sp_active_workers; if(n<1)n=1; if(n>SP_MAX_WORKERS)n=SP_MAX_WORKERS; for(int i=0;i<n;i++)for(sp_gc_hdr*hh=sp_gc_wslot[i].young;hh;hh=hh->next)hh->marked=0; }
|
|
1382
|
+
#else
|
|
1383
|
+
for(sp_gc_hdr*hh=sp_gc_heap;hh;hh=hh->next)hh->marked=0;
|
|
1384
|
+
#endif
|
|
1385
|
+
}
|
|
1386
|
+
/* SPINEL_GC_MINOR=0 turns this off and the collector behaves exactly as it
|
|
1387
|
+
did before the barrier landed. SPINEL_GC_VERIFY_GEN is what proves the
|
|
1388
|
+
coverage: it re-marks whole-heap after every minor and names the holder of
|
|
1389
|
+
anything the minor missed. */
|
|
1390
|
+
if (sp_gc_nremembered > sp_gc_rem_peak) sp_gc_rem_peak = sp_gc_nremembered;
|
|
1391
|
+
sp_gc_minor = sp_gc_minor_on && !full && !sp_gc_rem_overflow && !sp_gc_pin_overflow;
|
|
1392
|
+
/* Sweep beside the mutators rather than under the barrier? Only with a
|
|
1393
|
+
driver, and not under the diagnostic modes that read the lists between
|
|
1394
|
+
mark and sweep (verify, the generational verifier, aging's young probe). */
|
|
1395
|
+
if(sp_gc_conc_on<0){ const char*e=getenv("SPINEL_GC_CONC"); sp_gc_conc_on=!(e&&*e=='0'); }
|
|
1396
|
+
if(sp_gc_par_mark_on<0){ const char*e=getenv("SPINEL_GC_PAR_MARK"); sp_gc_par_mark_on=!(e&&*e=='0'); }
|
|
1397
|
+
int conc = sp_gc_conc_on && sp_gc_conc_sweep_hook && !sp_gc_verify && !sp_gc_verify_gen &&
|
|
1398
|
+
!sp_gc_age_on;
|
|
1399
|
+
sp_gc_conc_promote = conc;
|
|
1400
|
+
sp_gc_mk_bytes = 0; sp_gc_mk_young_bytes = 0;
|
|
1401
|
+
sp_gc_mk_str_bytes = 0; sp_gc_mk_str_young_bytes = 0; sp_gc_mk_promo_bytes = 0;
|
|
1402
|
+
/* the closed epoch is what the sweep frees from; everything allocated from
|
|
1403
|
+
here on is in the other parity and untouched by it (lib/sp_slab.c) */
|
|
1404
|
+
if(sp_gc_verify==1)sp_slab_verify_all();
|
|
1405
|
+
sp_slab_epoch_flip();
|
|
1406
|
+
/* This thread's string-length cache: the list sweep dropped each freed
|
|
1407
|
+
string from it one by one, and the bitmap sweep touches no string. Every
|
|
1408
|
+
parked worker cleared its own on the way in; the collector's (and the
|
|
1409
|
+
single thread's) goes here, before anything is freed. */
|
|
1410
|
+
sp_str_lcache_clear();
|
|
1411
|
+
sp_gc_mark_all();
|
|
1412
|
+
if(sp_gc_minor){
|
|
1413
|
+
/* the remembered set is the rest of the root set for a minor: each entry is
|
|
1414
|
+
an old object holding a reference the walk above did not follow. The
|
|
1415
|
+
minor stays in force through this walk and the drain: every survivor of
|
|
1416
|
+
a minor is promoted by its sweep, so an old object never holds a young
|
|
1417
|
+
one except through a recorded store, and walking INTO old objects here
|
|
1418
|
+
only re-marks what the cycle cannot free. Clearing the flag first made
|
|
1419
|
+
the drain do exactly that: 27% (gcbench) to 48% (threaded render) of a
|
|
1420
|
+
minor's marked objects were old, and its time followed the count. */
|
|
1421
|
+
for(int ri=0;ri<sp_gc_nremembered;ri++){
|
|
1422
|
+
sp_gc_hdr *rh=(sp_gc_hdr*)sp_gc_remembered[ri]-1;
|
|
1423
|
+
if(sp_gc_verify){sp_gc_dbg_phase="remembered";sp_gc_dbg_ctx=sp_gc_remembered[ri];}
|
|
1424
|
+
if(rh->scan) rh->scan(sp_gc_remembered[ri]);
|
|
1425
|
+
}
|
|
1426
|
+
/* and the sticky half: holders whose stores the barrier never sees, so
|
|
1427
|
+
there is no cycle in which they are "already recorded" */
|
|
1428
|
+
for(int pi=0;pi<sp_gc_npinned;pi++){
|
|
1429
|
+
sp_gc_hdr *ph=(sp_gc_hdr*)sp_gc_pinned[pi]-1;
|
|
1430
|
+
if(sp_gc_verify){sp_gc_dbg_phase="pinned";sp_gc_dbg_ctx=sp_gc_pinned[pi];}
|
|
1431
|
+
if(ph->scan) ph->scan(sp_gc_pinned[pi]);
|
|
1432
|
+
}
|
|
1433
|
+
if(sp_gc_verify){sp_gc_dbg_phase="minor-drain";sp_gc_dbg_ctx=NULL;}
|
|
1434
|
+
sp_gc_mark_drain_all();
|
|
1435
|
+
sp_gc_mkl_fold();
|
|
1436
|
+
if(sp_gc_verify){sp_gc_dbg_phase="?";sp_gc_dbg_ctx=NULL;}
|
|
1437
|
+
}
|
|
1438
|
+
/* the ivar tables of the builtin values the cycle keeps (sp_ivt_mark) */
|
|
1439
|
+
if (sp_ivt_n) sp_ivt_mark(full, sp_ivt_drain);
|
|
1440
|
+
sp_gc_minor = 0;
|
|
1441
|
+
sp_gc_conc_promote = 0;
|
|
1442
|
+
/* Verification: re-run the mark whole-heap and compare. Anything the full
|
|
1443
|
+
mark reaches that the minor did not is a reference the barrier failed to
|
|
1444
|
+
record -- the one failure mode of this design, silent until it is a use
|
|
1445
|
+
after free. Off unless SPINEL_GC_VERIFY_GEN is set. */
|
|
1446
|
+
if(!full && sp_gc_verify_gen) sp_gc_verify_gen_run();
|
|
1447
|
+
/* Drop pinned holders that are about to be freed, while the marks are final
|
|
1448
|
+
and before any sweep runs -- an entry naming freed memory would be
|
|
1449
|
+
dereferenced on the next minor cycle. What a cycle can free decides the
|
|
1450
|
+
test: a full one frees anything unmarked, and a minor one frees only from
|
|
1451
|
+
the young lists, so an old holder survives it whatever its stamp says.
|
|
1452
|
+
Clearing the bit on the way out is what lets the object be pinned again
|
|
1453
|
+
if it turns out to be alive after all -- it cannot, since it is being
|
|
1454
|
+
freed, but the bit and the array must not disagree. */
|
|
1455
|
+
{ int keep = 0;
|
|
1456
|
+
for (int pi = 0; pi < sp_gc_npinned; pi++) {
|
|
1457
|
+
sp_gc_hdr *ph = (sp_gc_hdr *)sp_gc_pinned[pi] - 1;
|
|
1458
|
+
int live = full ? (ph->marked == sp_gc_mark_gen)
|
|
1459
|
+
: (ph->old || ph->marked == sp_gc_mark_gen);
|
|
1460
|
+
if (live) sp_gc_pinned[keep++] = sp_gc_pinned[pi];
|
|
1461
|
+
else ph->pinned = 0;
|
|
1462
|
+
}
|
|
1463
|
+
sp_gc_npinned = keep;
|
|
1464
|
+
/* The overflow degradation lasts exactly as long as the array is full.
|
|
1465
|
+
While it is set every mark is whole-heap, so nothing is missed; once
|
|
1466
|
+
compaction has made room the set is authoritative again. */
|
|
1467
|
+
if (sp_gc_pin_overflow && keep < SP_GC_PINNED_MAX) sp_gc_pin_overflow = 0; }
|
|
1468
|
+
if (sp_fin_n) sp_fin_after_mark(full);
|
|
1469
|
+
if (sp_ivt_n) sp_ivt_after_mark(full);
|
|
1470
|
+
SP_GC_PH(sp_gc_ph_mark);
|
|
1471
|
+
sp_str_mark_settle(full);
|
|
1472
|
+
if(full){
|
|
1473
|
+
size_t old_before=sp_gc_old_bytes;
|
|
1474
|
+
if(conc){
|
|
1475
|
+
/* the old list is swept beside the mutators; what stays is what the
|
|
1476
|
+
mark reached of it, and that is known now */
|
|
1477
|
+
sp_gc_old_bytes=sp_gc_mk_bytes-sp_gc_mk_young_bytes;
|
|
1478
|
+
}
|
|
1479
|
+
else{
|
|
1480
|
+
/* the old LIST: the objects too large for the slab. The slab's old
|
|
1481
|
+
generation is swept with each worker's chunks below, and what stays of
|
|
1482
|
+
both is what the mark reached, known now. */
|
|
1483
|
+
/* the remembered set's bits, cleared through the array while it still
|
|
1484
|
+
names live memory (the sweep below frees some of what it names), as
|
|
1485
|
+
the concurrent form does; only an overflowed set costs the walk of
|
|
1486
|
+
every old object, which on a list benchmark was a tenth of the run */
|
|
1487
|
+
if(sp_gc_rem_overflow){ for(sp_gc_hdr*h=sp_gc_old_heap;h;h=h->next)h->dirty=0; sp_slab_each_object(0,1,sp_gc_clear_dirty_cb,NULL); }
|
|
1488
|
+
else for(int ri=0;ri<sp_gc_nremembered;ri++)((sp_gc_hdr*)sp_gc_remembered[ri]-1)->dirty=0;
|
|
1489
|
+
sp_gc_hdr**pp=&sp_gc_old_heap;
|
|
1490
|
+
while(*pp){sp_gc_hdr*h=*pp;SP_PREFETCH(h->next);SP_GC_CTR_ADD(sp_gc_ct_swept,1);if(h->marked!=sp_gc_mark_gen){*pp=h->next;if(h->recycle){h->recycle(h);}
|
|
1491
|
+
else{if(h->finalize)h->finalize((char*)h+sizeof(sp_gc_hdr));sp_slab_free(h);}}
|
|
1492
|
+
else{pp=&h->next;}}
|
|
1493
|
+
sp_gc_old_bytes=sp_gc_mk_bytes-sp_gc_mk_young_bytes;
|
|
1494
|
+
}
|
|
1495
|
+
/* Retune the cadence on what this sweep actually reclaimed. A heap the
|
|
1496
|
+
full cycle barely touches is one the minor mark was re-walking for
|
|
1497
|
+
nothing, and the interval can grow; a heap it empties is one where
|
|
1498
|
+
promoted objects are dying, and every cycle the interval adds is memory
|
|
1499
|
+
held past its death. The second case is not hypothetical: at a fixed
|
|
1500
|
+
interval of 128 a workload that promotes and then drops 20k arrays per
|
|
1501
|
+
round peaked at 285 MB against 26 at 8, and ran slower for it. */
|
|
1502
|
+
sp_gc_old_live=sp_gc_old_bytes; /* re-baseline the space bound */
|
|
1503
|
+
|
|
1504
|
+
/* The survival ratio is a statement about a sample taken ON SCHEDULE. A
|
|
1505
|
+
full forced by growth is not that sample -- it is evidence the cadence
|
|
1506
|
+
was already too long for this phase, and reading survival there gets the
|
|
1507
|
+
answer backwards: growth that is still live reads as ~100% survival and
|
|
1508
|
+
DOUBLES the interval. So shorten on a forced full and do not adapt. */
|
|
1509
|
+
if(!sp_gc_full_interval_fixed&&forced_full){
|
|
1510
|
+
if(sp_gc_full_interval>SP_GC_FULL_INTERVAL) sp_gc_full_interval/=2;
|
|
1511
|
+
}
|
|
1512
|
+
else if(str_full){ /* the string heap's sample, not this heap's */ }
|
|
1513
|
+
else if(old_before>0&&!sp_gc_full_interval_fixed){
|
|
1514
|
+
size_t kept=sp_gc_old_bytes;
|
|
1515
|
+
if(kept>old_before-(old_before>>2)){ /* >75% survived */
|
|
1516
|
+
/* ...unless the minors between were promoting garbage (below): a
|
|
1517
|
+
high survival at a SHORT cadence is what the short cadence bought */
|
|
1518
|
+
if(sp_gc_full_interval<SP_GC_FULL_INTERVAL_MAX&&sp_gc_last_per_minor<SP_GC_PROMOTED_DEAD_CUT/2) sp_gc_full_interval*=2;
|
|
1519
|
+
}
|
|
1520
|
+
else if(kept<(old_before>>1)){ /* <50% survived */
|
|
1521
|
+
if(sp_gc_full_interval>SP_GC_FULL_INTERVAL) sp_gc_full_interval/=2;
|
|
1522
|
+
}
|
|
1523
|
+
/* Under the minor mark a second reading of the same sample: what the
|
|
1524
|
+
minors between two fulls PROMOTED and this full then freed, per minor,
|
|
1525
|
+
against the live set. A minor is worth its skipped old walk only if
|
|
1526
|
+
what it promotes mostly survives; an interpreter's evaluation frames
|
|
1527
|
+
promoted 13% of the live set per minor and died at the next full,
|
|
1528
|
+
and carrying them was a third of its wall (most of it outside the
|
|
1529
|
+
collector: the heap the mutator ran on had that much dead in it).
|
|
1530
|
+
Above the cut the cadence shortens toward every cycle full, where the
|
|
1531
|
+
minor never runs; below it the survival rule above governs. */
|
|
1532
|
+
if(sp_gc_minor_on&&kept>0&&sp_gc_minors_since_full>0){
|
|
1533
|
+
size_t dead=old_before>kept?old_before-kept:0;
|
|
1534
|
+
double per_minor=(double)dead/(double)kept/(double)sp_gc_minors_since_full;
|
|
1535
|
+
sp_gc_last_per_minor=per_minor;
|
|
1536
|
+
if(per_minor>SP_GC_PROMOTED_DEAD_CUT){
|
|
1537
|
+
if(sp_gc_full_interval>SP_GC_FULL_INTERVAL_MIN) sp_gc_full_interval/=2;
|
|
1538
|
+
}
|
|
1539
|
+
sp_gc_fulls_at_min=0;
|
|
1540
|
+
}
|
|
1541
|
+
else if(sp_gc_minor_on&&sp_gc_full_interval==SP_GC_FULL_INTERVAL_MIN){
|
|
1542
|
+
/* every cycle full measures nothing about minors; try one again now
|
|
1543
|
+
and then, and let the reading above decide */
|
|
1544
|
+
if(++sp_gc_fulls_at_min>=8){ sp_gc_fulls_at_min=0; sp_gc_last_per_minor=0; sp_gc_full_interval=2; }
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
sp_gc_minors_since_full=0;
|
|
1548
|
+
}
|
|
1549
|
+
/* minor: the old list is not walked at all -- an old object's stale stamp
|
|
1550
|
+
simply reads as unmarked next generation, which is what a fresh unmark
|
|
1551
|
+
pass used to produce. */
|
|
1552
|
+
/* Decided BEFORE the object sweep, because the threaded path sweeps the
|
|
1553
|
+
string heap from inside it (each worker takes its own slot) and would
|
|
1554
|
+
otherwise read this flag while it was still 0 -- and then sweep the old
|
|
1555
|
+
string list on a minor cycle, freeing strings only an old object holds. */
|
|
1556
|
+
SP_GC_PH(sp_gc_ph_oldsweep);
|
|
1557
|
+
sp_gc_str_minor_only = (sp_gc_str_sweep_hook && !full && sp_gc_minor_on);
|
|
1558
|
+
/* Age survivors only on a minor whose remembered set is intact: a full
|
|
1559
|
+
cycle's old sweep clears every dirty bit and discards the array, so a
|
|
1560
|
+
holder of an object kept young would be forgotten; an overflowed set is
|
|
1561
|
+
cleared by a whole-heap walk for the same reason. On those cycles every
|
|
1562
|
+
survivor promotes, as before. */
|
|
1563
|
+
sp_gc_age_survivors = sp_gc_age_on && sp_gc_minor_on && !full &&
|
|
1564
|
+
!sp_gc_rem_overflow && !sp_gc_pin_overflow;
|
|
1565
|
+
sp_gc_young_kept_bytes = 0; sp_gc_npromoted = 0;
|
|
1566
|
+
#ifdef SP_THREADS
|
|
1567
|
+
if(conc){
|
|
1568
|
+
/* Every survivor is promoted by the mark (sp_gc_conc_promote), so the
|
|
1569
|
+
old generation grows by the young bytes it reached, and the live total
|
|
1570
|
+
is the old total. The lists themselves go to the driver, which detaches
|
|
1571
|
+
them from their workers and sweeps them once the world is running. */
|
|
1572
|
+
int str_major=0;
|
|
1573
|
+
int str_sweep=sp_str_sweep_begin(&str_major);
|
|
1574
|
+
if(sp_gc_str_minor_only) str_major=0;
|
|
1575
|
+
sp_gc_old_bytes+=sp_gc_mk_young_bytes;
|
|
1576
|
+
sp_gc_bytes=sp_gc_old_bytes+sp_gc_parked_take(); /* the pooled headers the previous sweep counted */
|
|
1577
|
+
/* the remembered set: what it recorded led the mark to young objects
|
|
1578
|
+
that are old now, so the record is spent; a full cycle cannot lean on
|
|
1579
|
+
the old sweep to clear the bits the way the barrier form does. Cleared
|
|
1580
|
+
before the sweepers start: they read the flag word of every old object
|
|
1581
|
+
and nobody else may be writing it then */
|
|
1582
|
+
if(sp_gc_rem_overflow){ for(sp_gc_hdr*h=sp_gc_old_heap;h;h=h->next)h->dirty=0; sp_slab_each_object(0,1,sp_gc_clear_dirty_cb,NULL); }
|
|
1583
|
+
else for(int ri=0;ri<sp_gc_nremembered;ri++)((sp_gc_hdr*)sp_gc_remembered[ri]-1)->dirty=0;
|
|
1584
|
+
sp_gc_nremembered=0; sp_gc_rem_overflow=0;
|
|
1585
|
+
SP_GC_PH(sp_gc_ph_rembclear);
|
|
1586
|
+
sp_gc_conc_sweep_hook(full,str_sweep,str_major);
|
|
1587
|
+
{ int n=sp_active_workers; if(n<1)n=1; if(n>SP_MAX_WORKERS)n=SP_MAX_WORKERS;
|
|
1588
|
+
for(int i=0;i<n;i++)sp_gc_wslot[i].flush_delta=0; }
|
|
1589
|
+
SP_GC_PH(sp_gc_ph_slotsweep);
|
|
1590
|
+
SP_GC_PH(sp_gc_ph_strsweep);
|
|
1591
|
+
sp_gc_str_minor_only = 0;
|
|
1592
|
+
if(full) sp_gc_trim_request(); /* the slab's own release waits for the sweep (the driver runs it) */
|
|
1593
|
+
SP_GC_PH(sp_gc_ph_trim);
|
|
1594
|
+
sp_gc_stat_collections++;
|
|
1595
|
+
if(full)sp_gc_stat_fulls++;
|
|
1596
|
+
sp_gc_stat_seconds+=sp_gc_stat_now()-stat_t0;
|
|
1597
|
+
if(sp_gc_obj_retune_hook)sp_gc_obj_retune_hook(ob_before);
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
{ int n=sp_active_workers; if(n<1)n=1; if(n>SP_MAX_WORKERS)n=SP_MAX_WORKERS;
|
|
1601
|
+
/* Hand each parked worker its own slot. Only with a pool worth the barrier
|
|
1602
|
+
round trip: below that the serial walk the collector has always done is
|
|
1603
|
+
cheaper than waking everyone. */
|
|
1604
|
+
sp_gc_sweep_full_now=full;
|
|
1605
|
+
if (sp_gc_par_sweep_hook && n > 1) sp_gc_par_sweep_hook(); /* every slot's chunks too */
|
|
1606
|
+
else {
|
|
1607
|
+
for(int i=0;i<n;i++)sp_gc_sweep_young(&sp_gc_wslot[i].young);
|
|
1608
|
+
/* every slot's chunks, the ones no worker runs included (the sweeper
|
|
1609
|
+
threads', a worker that left); a chunk is swept exactly once a cycle */
|
|
1610
|
+
for(int i=0;i<SP_MAX_WORKERS;i++)sp_gc_sweep_chunks(i,full);
|
|
1611
|
+
}
|
|
1612
|
+
/* The recompute above set sp_gc_bytes from every live object's size, so the
|
|
1613
|
+
workers' unflushed per-worker deltas are now subsumed -- clear them (all
|
|
1614
|
+
mutators are parked, so this is race-free). */
|
|
1615
|
+
for(int i=0;i<n;i++)sp_gc_wslot[i].flush_delta=0; }
|
|
1616
|
+
#else
|
|
1617
|
+
sp_gc_sweep_young(&sp_gc_heap);
|
|
1618
|
+
sp_gc_sweep_chunks(0,full);
|
|
1619
|
+
#endif
|
|
1620
|
+
/* the slab's promotions were counted by the mark; the list sweeps above
|
|
1621
|
+
spliced theirs into the old total as they went */
|
|
1622
|
+
sp_gc_old_bytes+=sp_gc_mk_promo_bytes;
|
|
1623
|
+
/* Take the live total AFTER the sweep, not before it. The sweep accumulates
|
|
1624
|
+
each survivor's size into sp_gc_old_bytes as it promotes, so this reads the
|
|
1625
|
+
same number either way -- but a dead object's finalizer subtracts the
|
|
1626
|
+
buffer it accounted for on the way up, and against a total already reset to
|
|
1627
|
+
the live set those bytes were never there. Churning large arrays walked the
|
|
1628
|
+
counter below zero; the retune read the wrapped value and set a threshold
|
|
1629
|
+
near SIZE_MAX, which never fires again (#4073). */
|
|
1630
|
+
SP_GC_PH(sp_gc_ph_slotsweep);
|
|
1631
|
+
sp_gc_bytes=sp_gc_old_bytes+SP_GC_CTR_GET(sp_gc_young_kept_bytes)+sp_gc_parked_take(); /* the kept young and the pooled headers still occupy the heap */
|
|
1632
|
+
/* The remembered set has done its job and starts over after EVERY cycle, not
|
|
1633
|
+
only a full one. Every young object it led the mark to has just been
|
|
1634
|
+
promoted by the sweep above, so a holder that is not written to again has
|
|
1635
|
+
nothing left to record; one that is gets recorded afresh by sp_gc_wb.
|
|
1636
|
+
Keeping entries until the next full cycle instead made the array grow
|
|
1637
|
+
monotonically and overflow on any real workload.
|
|
1638
|
+
|
|
1639
|
+
The clear cannot go through the array alone. Once it has overflowed,
|
|
1640
|
+
objects carry dirty=1 with no entry in it, and clearing only what the
|
|
1641
|
+
array holds leaves them permanently dirty -- so sp_gc_wb's `!h->dirty`
|
|
1642
|
+
test rejects them forever and every young object they later point at is
|
|
1643
|
+
invisible to the minor mark. That is a silent use-after-free, and it is
|
|
1644
|
+
why the overflow path has to pay for the whole-heap walk. */
|
|
1645
|
+
if(full){
|
|
1646
|
+
/* the old sweep above cleared every survivor; the array may name objects it
|
|
1647
|
+
just freed, so it must not be walked here */
|
|
1648
|
+
sp_gc_nremembered=0; sp_gc_rem_overflow=0;
|
|
1649
|
+
}
|
|
1650
|
+
/* Clear the bits the barrier set, not every bit in the old heap. An object can
|
|
1651
|
+
carry the bit with no entry naming it in exactly one case -- sp_gc_wb was
|
|
1652
|
+
refused a slot -- and it sets sp_gc_rem_overflow when it is, so the overflow
|
|
1653
|
+
branch covers precisely the state the array cannot describe. Same reasoning
|
|
1654
|
+
in both builds.
|
|
1655
|
+
|
|
1656
|
+
This reads the array, so it needs the array consistent: see the note in
|
|
1657
|
+
sp_gc_wb_slow on why no collection can observe it mid-update.
|
|
1658
|
+
|
|
1659
|
+
The threaded path walked the whole old list here instead, on every non-full
|
|
1660
|
+
cycle -- O(live) per collection for work proportional to the stores. On a
|
|
1661
|
+
server it was the largest phase of the stopped window (5.7s of 13.1s of
|
|
1662
|
+
collector time, collector at 65% of wall; campfire from matz/spinel#4352). */
|
|
1663
|
+
else if(sp_gc_rem_overflow){
|
|
1664
|
+
for(sp_gc_hdr*h=sp_gc_old_heap;h;h=h->next)h->dirty=0;
|
|
1665
|
+
sp_slab_each_object(0,1,sp_gc_clear_dirty_cb,NULL);
|
|
1666
|
+
sp_gc_nremembered=0; sp_gc_rem_overflow=0;
|
|
1667
|
+
}
|
|
1668
|
+
else if(sp_gc_age_survivors){
|
|
1669
|
+
/* The premise of the clear -- every young object the set led the mark to
|
|
1670
|
+
has just been promoted -- no longer holds for the survivors the sweep
|
|
1671
|
+
kept young for a second look. Two kinds of holder reach those now, and
|
|
1672
|
+
both have to be on the set the next minor walks, or it never walks them
|
|
1673
|
+
and frees what they hold.
|
|
1674
|
+
|
|
1675
|
+
An object promoted THIS cycle holding one: the store was young-into-
|
|
1676
|
+
young when it happened, so no barrier saw it, and promotion made it an
|
|
1677
|
+
old-into-young edge nobody recorded. The promotions sit at the head of
|
|
1678
|
+
the old list (every sweep prepends), so those are walked with the young
|
|
1679
|
+
probe armed -- the scan marks nothing and answers only whether a young
|
|
1680
|
+
object is reached -- and recorded when it is. A refused slot sets the
|
|
1681
|
+
overflow flag, which makes the next mark whole-heap.
|
|
1682
|
+
|
|
1683
|
+
A holder already on the set: it stays recorded while it still reaches a
|
|
1684
|
+
young object, and is cleared when it does not. The holders are old and a
|
|
1685
|
+
minor frees no old object, so the array names nothing freed. */
|
|
1686
|
+
sp_gc_rem_overflow=0;
|
|
1687
|
+
sp_gc_young_probe_on=1;
|
|
1688
|
+
{ size_t np=sp_gc_npromoted; sp_gc_hdr *h=sp_gc_old_heap;
|
|
1689
|
+
for(size_t k=0;k<np&&h;k++,h=h->next){
|
|
1690
|
+
if(h->dirty||!h->scan) continue;
|
|
1691
|
+
sp_gc_young_probe_hit=0;
|
|
1692
|
+
h->scan((char*)h+sizeof(sp_gc_hdr));
|
|
1693
|
+
if(!sp_gc_young_probe_hit) continue;
|
|
1694
|
+
h->dirty=1;
|
|
1695
|
+
if(sp_gc_nremembered<SP_GC_REMEMBERED_MAX) sp_gc_remembered[sp_gc_nremembered++]=(char*)h+sizeof(sp_gc_hdr);
|
|
1696
|
+
else sp_gc_rem_overflow=1;
|
|
1697
|
+
} }
|
|
1698
|
+
int keep=0;
|
|
1699
|
+
for(int ri=0;ri<sp_gc_nremembered;ri++){
|
|
1700
|
+
void *obj=sp_gc_remembered[ri];
|
|
1701
|
+
sp_gc_hdr *rh=(sp_gc_hdr*)obj-1;
|
|
1702
|
+
sp_gc_young_probe_hit=0;
|
|
1703
|
+
if(rh->scan) rh->scan(obj);
|
|
1704
|
+
if(sp_gc_young_probe_hit) sp_gc_remembered[keep++]=obj;
|
|
1705
|
+
else rh->dirty=0;
|
|
1706
|
+
}
|
|
1707
|
+
sp_gc_young_probe_on=0;
|
|
1708
|
+
sp_gc_nremembered=keep;
|
|
1709
|
+
}
|
|
1710
|
+
else{
|
|
1711
|
+
for(int ri=0;ri<sp_gc_nremembered;ri++)((sp_gc_hdr*)sp_gc_remembered[ri]-1)->dirty=0;
|
|
1712
|
+
sp_gc_nremembered=0;
|
|
1713
|
+
sp_gc_rem_overflow=0;
|
|
1714
|
+
}
|
|
1715
|
+
/* Under SPINEL_GC_VERIFY: the remembered set's invariant, dirty <=> listed,
|
|
1716
|
+
holds for every old object once the array is not overflowed. */
|
|
1717
|
+
if(sp_gc_verify==1&&!sp_gc_rem_overflow){
|
|
1718
|
+
sp_slab_each_object(0,1,sp_gc_rem_invariant_cb,&full);
|
|
1719
|
+
for(sp_gc_hdr*h=sp_gc_old_heap;h;h=h->next){
|
|
1720
|
+
void *o=(char*)h+sizeof(sp_gc_hdr); int listed=0;
|
|
1721
|
+
for(int ri=0;ri<sp_gc_nremembered;ri++) if(sp_gc_remembered[ri]==o){listed=1;break;}
|
|
1722
|
+
if(!!h->dirty!=listed){
|
|
1723
|
+
fprintf(stderr,"spinel: GC remembered-set invariant broken: obj=%p scan=%p dirty=%d listed=%d full=%d age=%d cycle=%d\n",
|
|
1724
|
+
o,(void*)h->scan,(int)h->dirty,listed,full,sp_gc_age_survivors,sp_gc_cycle);
|
|
1725
|
+
abort();
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
SP_GC_PH(sp_gc_ph_rembclear);
|
|
1730
|
+
/* Sweep the string heap only when IT is over its trigger: the sweep is a
|
|
1731
|
+
full walk of the live string list, and running it on every OBJECT-heap
|
|
1732
|
+
collection made each collection O(live strings) -- the dominant cost of
|
|
1733
|
+
an allocation-heavy run. Skipping is safe: string marks accumulate, so a
|
|
1734
|
+
dead string at worst survives until the next string sweep (delayed
|
|
1735
|
+
reclamation, not a leak), and the sweep itself resets marks for the next
|
|
1736
|
+
cycle. The retune keeps the trigger tracking the live size. */
|
|
1737
|
+
/* Only on a full cycle now. The string heap has no generation of its own, so
|
|
1738
|
+
its sweep frees anything the mark did not reach -- and a minor mark does
|
|
1739
|
+
not reach a string held by an old object, because it does not walk old
|
|
1740
|
+
objects at all. Sweeping strings on a minor cycle therefore reaped live
|
|
1741
|
+
ones (test/file_basename_gc). Deferring to the full cycle is the same
|
|
1742
|
+
delayed reclamation the trigger already allowed. */
|
|
1743
|
+
/* The string heap is generational in its own right, so a minor cycle can
|
|
1744
|
+
sweep its young list -- the strings an old object holds live in the old
|
|
1745
|
+
list, which this leaves alone, and a young string stored into an old
|
|
1746
|
+
holder is what the barrier records. Deferring the whole sweep to the
|
|
1747
|
+
full cycle instead made every string-heavy workload SLOWER with the
|
|
1748
|
+
minor mark than without it (+17% on a string benchmark, and rubys'
|
|
1749
|
+
ballast inversion on lobsters); sweeping young only turns that into
|
|
1750
|
+
-52%. */
|
|
1751
|
+
if(sp_gc_str_sweep_hook){
|
|
1752
|
+
sp_gc_str_sweep_hook();
|
|
1753
|
+
}
|
|
1754
|
+
SP_GC_PH(sp_gc_ph_strsweep);
|
|
1755
|
+
sp_gc_str_minor_only = 0;
|
|
1756
|
+
/* malloc_trim walks the allocator arena; once per full cycle was ~10% of
|
|
1757
|
+
collection time on allocation-heavy runs, and "every 4th full" was a
|
|
1758
|
+
fraction of that only while a full was one cycle in eight. Under the
|
|
1759
|
+
generational mark a small-heap server runs nearly every cycle full (the
|
|
1760
|
+
cadence rule's floor), and every 4th of THOSE was 2.4 s of a 6.3 s
|
|
1761
|
+
collector budget on campfire, against 0.46 s with the mark off. The
|
|
1762
|
+
arena does not need walking more than about once a second: the RSS it
|
|
1763
|
+
returns is what the last second freed. */
|
|
1764
|
+
if(full){
|
|
1765
|
+
/* the object heap's own chunks first: what the slab hands back here is
|
|
1766
|
+
what malloc_trim used to find in the arena */
|
|
1767
|
+
sp_slab_release();
|
|
1768
|
+
sp_gc_trim_request();
|
|
1769
|
+
}
|
|
1770
|
+
SP_GC_PH(sp_gc_ph_trim);
|
|
1771
|
+
/* Bump BEFORE the retune hook: the hook is where the stats line is printed
|
|
1772
|
+
(sp_alloc.c sees both thresholds and the string heap), and it must read
|
|
1773
|
+
this collection, not the previous one. */
|
|
1774
|
+
sp_gc_stat_collections++;
|
|
1775
|
+
if(full)sp_gc_stat_fulls++;
|
|
1776
|
+
sp_gc_stat_seconds+=sp_gc_stat_now()-stat_t0;
|
|
1777
|
+
if(sp_gc_obj_retune_hook)sp_gc_obj_retune_hook(ob_before);
|
|
1778
|
+
/* the finalizers of what this collection freed wait for the program's
|
|
1779
|
+
next safe point (SP_FIN_POLL): the allocation that brought us here may
|
|
1780
|
+
be in the middle of an operation one of them can reach */
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
/* Issue #1302: optional RSS ceiling via SPINEL_MAX_HEAP_MB; checked only
|
|
1784
|
+
* at GC-trigger points against real /proc/self/statm RSS. Default off. */
|
|
1785
|
+
void sp_gc_enforce_mem_limit(void){
|
|
1786
|
+
if(!sp_gc_max_bytes_init){const char*e=getenv("SPINEL_MAX_HEAP_MB");long v=(e&&*e)?atol(e):0;sp_gc_max_bytes=(v>0)?(size_t)v*1024*1024:0;sp_gc_max_bytes_init=1;}
|
|
1787
|
+
if(!sp_gc_max_bytes)return;
|
|
1788
|
+
#if defined(__linux__)
|
|
1789
|
+
FILE*sf=fopen("/proc/self/statm","r");if(!sf)return;long tot=0,res=0;int n=fscanf(sf,"%ld %ld",&tot,&res);fclose(sf);if(n!=2||res<=0)return;
|
|
1790
|
+
size_t rss=(size_t)res*(size_t)sysconf(_SC_PAGESIZE);
|
|
1791
|
+
if(rss>sp_gc_max_bytes){fprintf(stderr,"unhandled exception: out of memory (RSS %zu MB exceeded SPINEL_MAX_HEAP_MB=%zu MB)\n",rss/(1024*1024),sp_gc_max_bytes/(1024*1024));exit(1);}
|
|
1792
|
+
#endif
|
|
1793
|
+
}
|