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.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. 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
+ }