qr_reader-native 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE-mruby.txt +22 -0
- data/LICENSE-spinel.txt +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/ext/qr_reader_native/SPINEL_VERSION +1 -0
- data/ext/qr_reader_native/extconf.rb +8 -0
- data/ext/qr_reader_native/mruby/error.h +13 -0
- data/ext/qr_reader_native/mruby/internal.h +145 -0
- data/ext/qr_reader_native/mruby/string.h +22 -0
- data/ext/qr_reader_native/mruby.h +93 -0
- data/ext/qr_reader_native/mruby_shim.h +118 -0
- data/ext/qr_reader_native/qr_reader_native.c +13513 -0
- data/ext/qr_reader_native/qr_reader_native.h +29 -0
- data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
- data/ext/qr_reader_native/re_cased.h +71 -0
- data/ext/qr_reader_native/re_compile.c +5784 -0
- data/ext/qr_reader_native/re_ctype.h +3540 -0
- data/ext/qr_reader_native/re_exec.c +2190 -0
- data/ext/qr_reader_native/re_internal.h +671 -0
- data/ext/qr_reader_native/re_prop.h +4629 -0
- data/ext/qr_reader_native/re_spinel.c +363 -0
- data/ext/qr_reader_native/re_spinel.h +22 -0
- data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
- data/ext/qr_reader_native/re_utf8.c +156 -0
- data/ext/qr_reader_native/sp_alloc.c +1447 -0
- data/ext/qr_reader_native/sp_alloc.h +1196 -0
- data/ext/qr_reader_native/sp_argf.h +33 -0
- data/ext/qr_reader_native/sp_array.c +1052 -0
- data/ext/qr_reader_native/sp_array.h +456 -0
- data/ext/qr_reader_native/sp_bigint.c +5826 -0
- data/ext/qr_reader_native/sp_bigint.h +117 -0
- data/ext/qr_reader_native/sp_cold.c +5259 -0
- data/ext/qr_reader_native/sp_compat.h +402 -0
- data/ext/qr_reader_native/sp_core.c +818 -0
- data/ext/qr_reader_native/sp_core.h +56 -0
- data/ext/qr_reader_native/sp_crypto.c +877 -0
- data/ext/qr_reader_native/sp_crypto.h +169 -0
- data/ext/qr_reader_native/sp_dtoa.c +1533 -0
- data/ext/qr_reader_native/sp_dtoa.h +29 -0
- data/ext/qr_reader_native/sp_encoding_names.h +172 -0
- data/ext/qr_reader_native/sp_enum.h +86 -0
- data/ext/qr_reader_native/sp_exc.c +1185 -0
- data/ext/qr_reader_native/sp_exc.h +186 -0
- data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
- data/ext/qr_reader_native/sp_fiber.c +889 -0
- data/ext/qr_reader_native/sp_fiber.h +162 -0
- data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
- data/ext/qr_reader_native/sp_format.c +695 -0
- data/ext/qr_reader_native/sp_format.h +83 -0
- data/ext/qr_reader_native/sp_gc.c +1793 -0
- data/ext/qr_reader_native/sp_gc.h +762 -0
- data/ext/qr_reader_native/sp_hash.c +131 -0
- data/ext/qr_reader_native/sp_hash.h +131 -0
- data/ext/qr_reader_native/sp_inspect.c +215 -0
- data/ext/qr_reader_native/sp_inspect.h +128 -0
- data/ext/qr_reader_native/sp_io.c +1676 -0
- data/ext/qr_reader_native/sp_io.h +272 -0
- data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
- data/ext/qr_reader_native/sp_iobuffer.h +167 -0
- data/ext/qr_reader_native/sp_marshal.c +487 -0
- data/ext/qr_reader_native/sp_marshal.h +60 -0
- data/ext/qr_reader_native/sp_net.c +1029 -0
- data/ext/qr_reader_native/sp_net.h +205 -0
- data/ext/qr_reader_native/sp_pack.c +1254 -0
- data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
- data/ext/qr_reader_native/sp_proc.c +410 -0
- data/ext/qr_reader_native/sp_proc.h +327 -0
- data/ext/qr_reader_native/sp_process.c +547 -0
- data/ext/qr_reader_native/sp_process_status.c +211 -0
- data/ext/qr_reader_native/sp_process_status.h +66 -0
- data/ext/qr_reader_native/sp_random.c +287 -0
- data/ext/qr_reader_native/sp_random.h +52 -0
- data/ext/qr_reader_native/sp_range.h +144 -0
- data/ext/qr_reader_native/sp_re.c +1379 -0
- data/ext/qr_reader_native/sp_re.h +186 -0
- data/ext/qr_reader_native/sp_re_names.h +59 -0
- data/ext/qr_reader_native/sp_sched.c +4044 -0
- data/ext/qr_reader_native/sp_sched.h +295 -0
- data/ext/qr_reader_native/sp_slab.c +1507 -0
- data/ext/qr_reader_native/sp_str.c +1462 -0
- data/ext/qr_reader_native/sp_str.h +306 -0
- data/ext/qr_reader_native/sp_str_crypt.c +23 -0
- data/ext/qr_reader_native/sp_string.c +92 -0
- data/ext/qr_reader_native/sp_string.h +253 -0
- data/ext/qr_reader_native/sp_system.c +78 -0
- data/ext/qr_reader_native/sp_system.h +18 -0
- data/ext/qr_reader_native/sp_time.c +860 -0
- data/ext/qr_reader_native/sp_time.h +100 -0
- data/ext/qr_reader_native/sp_types.h +292 -0
- data/ext/qr_reader_native/spinel_rt.h +19017 -0
- data/ext/qr_reader_native/unicase.c +399 -0
- data/ext/qr_reader_native/unicase.h +901 -0
- data/lib/qr_reader/native.rb +6 -0
- metadata +153 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
/* sp_gc.h -- the mark/sweep collector's shared surface.
|
|
2
|
+
*
|
|
3
|
+
* Included by both the generated translation unit (via spinel_rt.h) and
|
|
4
|
+
* lib/sp_gc.c, which holds the collector's non-inline machinery (mark,
|
|
5
|
+
* sweep, collect, the memory-limit governor, and the SPINEL_GC_VERIFY
|
|
6
|
+
* support). The hot inline mark helpers stay here so both sides inline
|
|
7
|
+
* them -- moving the cold collector body to a single compiled unit must
|
|
8
|
+
* not de-inline the per-object mark path. The collector globals are
|
|
9
|
+
* declared extern here and defined once in lib/sp_gc.c.
|
|
10
|
+
*/
|
|
11
|
+
#ifndef SP_GC_H
|
|
12
|
+
#define SP_GC_H
|
|
13
|
+
|
|
14
|
+
#include <stddef.h>
|
|
15
|
+
#include "sp_types.h"
|
|
16
|
+
|
|
17
|
+
/* ---- Value tag constants + the boxed value (sp_RbVal) ----
|
|
18
|
+
* The mark helpers below dispatch on the tag, so the type lives here
|
|
19
|
+
* rather than in the generated TU. */
|
|
20
|
+
#define SP_TAG_INT 0
|
|
21
|
+
#define SP_TAG_STR 1
|
|
22
|
+
#define SP_TAG_FLT 2
|
|
23
|
+
#define SP_TAG_BOOL 3
|
|
24
|
+
#define SP_TAG_NIL 4
|
|
25
|
+
#define SP_TAG_OBJ 5
|
|
26
|
+
#define SP_TAG_SYM 6
|
|
27
|
+
#define SP_TAG_CLASS 7
|
|
28
|
+
#define SP_TAG_ENCODING 8
|
|
29
|
+
#define SP_TAG_BIGINT 9 /* v.p is a GC-allocated sp_Bigint* */
|
|
30
|
+
/* SP_TAG_OBJ cls_id sentinel for an opaque foreign/FFI pointer (e.g. a
|
|
31
|
+
ffi_read_ptr / ffi func ptr return). It is NOT a sp_gc_alloc allocation, so
|
|
32
|
+
the collector must not trace it -- sp_mark_rbval skips it. Kept here (not with
|
|
33
|
+
the other SP_BUILTIN_* in spinel_rt.h) so the inline mark helper can see it.
|
|
34
|
+
Value is the next free slot below SP_BUILTIN_METHOD (-24). */
|
|
35
|
+
#define SP_BUILTIN_FOREIGN_PTR (-25)
|
|
36
|
+
/* a compiled Regexp: malloc-owned (never GC heap), so like FOREIGN_PTR the
|
|
37
|
+
collector must not trace it */
|
|
38
|
+
#define SP_BUILTIN_REGEX (-33)
|
|
39
|
+
/* ARGF: the one static sp_argf_obj, never GC heap, so it is not traced */
|
|
40
|
+
#define SP_BUILTIN_ARGF (-51)
|
|
41
|
+
/* Wide value types (heap-copied crossing into a poly slot). Shared here so
|
|
42
|
+
lib/sp_marshal.c can recognize them by cls_id. */
|
|
43
|
+
#define SP_BUILTIN_COMPLEX (-26)
|
|
44
|
+
#define SP_BUILTIN_RATIONAL (-27)
|
|
45
|
+
/* A Rational whose numerator/denominator exceed sp_int: a boxed object with
|
|
46
|
+
two sp_Bigint* fields, distinct from the by-value int Rational (#2469). */
|
|
47
|
+
#define SP_BUILTIN_BIG_RATIONAL (-35)
|
|
48
|
+
/* A Float range (1.0..3.0): a boxed sp_FloatRange, distinct from the int-backed
|
|
49
|
+
by-value Range so its endpoints are not truncated. */
|
|
50
|
+
#define SP_BUILTIN_FLOAT_RANGE (-36)
|
|
51
|
+
#define SP_BUILTIN_STR_RANGE (-42) /* ("a".."e"): sp_StrRange. Distinct from
|
|
52
|
+
STRBUF/ADDRINFO, which -40 already
|
|
53
|
+
names: a boxed string range answered
|
|
54
|
+
String from every tag-keyed switch,
|
|
55
|
+
so it was neither a Range nor
|
|
56
|
+
enumerable through a poly slot (#3619) */
|
|
57
|
+
#define SP_BUILTIN_OPENSTRUCT (-41) /* OpenStruct: dynamic symbol->value members */
|
|
58
|
+
typedef struct { int tag; int cls_id; union { sp_int i; const char *s; sp_float f; sp_bool b; void *p; } v; } sp_RbVal;
|
|
59
|
+
|
|
60
|
+
/* ---- Collector globals shared with the generated TU ----
|
|
61
|
+
* Only the globals touched by both the kept hot path (sp_gc_alloc, the
|
|
62
|
+
* SP_GC_ROOT macros, GC.stat, the fiber root hook) and the moved cold
|
|
63
|
+
* body are extern; the rest stay static on whichever side owns them. */
|
|
64
|
+
/* Capacity of the stack-resident GC root array (sp_gc_roots). At 8 bytes/entry
|
|
65
|
+
the default is a 512 KB static buffer -- ample for desktop, but the dominant
|
|
66
|
+
static allocation in a minimal binary. Embedded targets can shrink it with
|
|
67
|
+
-DSP_GC_STACK_MAX=<n> (pass the SAME value when building lib/sp_gc.c and the
|
|
68
|
+
generated TU -- both consult this for the array and the SP_GC_ROOT bound).
|
|
69
|
+
A push past it goes to a heap-allocated overflow segment (sp_gc_root_push_slow),
|
|
70
|
+
so a recursion deeper than the array -- 27k Ruby frames of a growing
|
|
71
|
+
string reached it, and the dropped roots were freed under the frames that
|
|
72
|
+
still held them -- keeps every root; only the fast path's bound is this
|
|
73
|
+
constant. */
|
|
74
|
+
#ifndef SP_GC_STACK_MAX
|
|
75
|
+
#define SP_GC_STACK_MAX 65536
|
|
76
|
+
#endif
|
|
77
|
+
#define SP_GC_FULL_INTERVAL 8
|
|
78
|
+
/* Per-worker root stack (SP_TLS): each OS worker carries the active roots of
|
|
79
|
+
the green thread it runs, swapped with the fiber's saved_roots on a context
|
|
80
|
+
switch. Plain globals in the single-threaded build. */
|
|
81
|
+
extern SP_TLS void **sp_gc_roots[SP_GC_STACK_MAX];
|
|
82
|
+
extern SP_TLS int sp_gc_nroots;
|
|
83
|
+
extern SP_TLS int sp_gc_in_sweeper; /* set on a sweeper thread: finalizers skip the per-worker byte accounting */
|
|
84
|
+
|
|
85
|
+
/* GC root tracking. SP_GC_ROOT registers a stack-resident root with a
|
|
86
|
+
cleanup-attribute sentinel so it auto-pops when its declaring scope ends.
|
|
87
|
+
Shared here (was in spinel_rt.h) so standalone lib C files -- e.g. the
|
|
88
|
+
Marshal loader, which builds GC arrays/hashes across a recursive parse --
|
|
89
|
+
can root their in-flight objects too. Helpers touch only the extern root
|
|
90
|
+
stack above, so relocating them is layout-neutral. */
|
|
91
|
+
/* Root i, whichever segment holds it: the walkers and the fiber snapshot read
|
|
92
|
+
through this past SP_GC_STACK_MAX. */
|
|
93
|
+
extern SP_TLS void ***sp_gc_roots_ext;
|
|
94
|
+
int sp_gc_root_push_slow(void **p);
|
|
95
|
+
int sp_gc_roots_ext_reserve(int n); /* room for n roots in total; 0 on OOM */
|
|
96
|
+
static inline void **sp_gc_root_at(int i) {
|
|
97
|
+
return i < SP_GC_STACK_MAX ? sp_gc_roots[i] : sp_gc_roots_ext[i - SP_GC_STACK_MAX];
|
|
98
|
+
}
|
|
99
|
+
static inline int _sp_gc_root_push(void **p) {
|
|
100
|
+
if (sp_gc_nroots < SP_GC_STACK_MAX) { sp_gc_roots[sp_gc_nroots++] = p; return 1; }
|
|
101
|
+
return sp_gc_root_push_slow(p);
|
|
102
|
+
}
|
|
103
|
+
static inline void _sp_gc_root_pop(int *added) { if (*added) sp_gc_nroots--; }
|
|
104
|
+
static inline void sp_gc_cleanup(int *p) { sp_gc_nroots = *p; }
|
|
105
|
+
#define _SP_GC_CONCAT2(a,b) a##b
|
|
106
|
+
#define _SP_GC_CONCAT(a,b) _SP_GC_CONCAT2(a,b)
|
|
107
|
+
#define SP_GC_SAVE() int SP_CLEANUP(sp_gc_cleanup) _gc_saved = sp_gc_nroots
|
|
108
|
+
/* A `const char *` slot is a String, and takes the string tag (below) whichever
|
|
109
|
+
macro roots it: a mutable String's payload (marker 0xfd) is kept alive by
|
|
110
|
+
the handle in front of it, which only sp_mark_string reaches, and the
|
|
111
|
+
object walk skips the payload. The slot's C type is the one thing every
|
|
112
|
+
emitter agrees on, so the choice is made here rather than at each site.
|
|
113
|
+
A local a rescue can write is `const char * volatile`, the one other
|
|
114
|
+
spelling of a String slot the compiler emits, and is a String all the
|
|
115
|
+
same. */
|
|
116
|
+
#define _SP_GC_SLOT_TAG(v) _Generic(&(v), const char **: (uintptr_t)2, const char *volatile *: (uintptr_t)2, default: (uintptr_t)0)
|
|
117
|
+
#define SP_GC_ROOT(v) int SP_CLEANUP(_sp_gc_root_pop) _SP_GC_CONCAT(_sp_gcr_, __COUNTER__) = _sp_gc_root_push((void**)((uintptr_t)&(v) | _SP_GC_SLOT_TAG(v)))
|
|
118
|
+
/* Root a poly (sp_RbVal) local: tag the stored slot's low bit so the mark
|
|
119
|
+
walker routes it through sp_mark_rbval (the object pointer sits in a union at
|
|
120
|
+
a nonzero offset, only for STR/OBJ tags). */
|
|
121
|
+
#define SP_GC_ROOT_RBVAL(v) int SP_CLEANUP(_sp_gc_root_pop) _SP_GC_CONCAT(_sp_gcr_, __COUNTER__) = _sp_gc_root_push((void**)((uintptr_t)&(v) | (uintptr_t)1))
|
|
122
|
+
/* Root a string slot that may hold a NON-spinel pointer (a stack line
|
|
123
|
+
buffer from sp_File_gets_buf, an external char*): tag bit 2 routes the
|
|
124
|
+
mark through sp_mark_string, which touches nothing unless the marker
|
|
125
|
+
byte is exactly 0xfe -- safe on arbitrary memory, unlike sp_gc_mark's
|
|
126
|
+
header walk. Use this for string parameters in runtime helpers. */
|
|
127
|
+
#define SP_GC_ROOT_STR(v) int SP_CLEANUP(_sp_gc_root_pop) _SP_GC_CONCAT(_sp_gcr_, __COUNTER__) = _sp_gc_root_push((void**)((uintptr_t)&(v) | (uintptr_t)2))
|
|
128
|
+
#define SP_GC_RESTORE() sp_gc_nroots = _gc_saved
|
|
129
|
+
|
|
130
|
+
/* ---- root frames ----
|
|
131
|
+
A generated function registers ONE entry for all of its roots instead of
|
|
132
|
+
one per root: a stack struct headed by sp_gc_frame_hdr, followed by nv
|
|
133
|
+
sp_RbVal slots that are the homes of the function's boxed temporaries, then
|
|
134
|
+
np entries in the same tagged-pointer encoding as the root array itself
|
|
135
|
+
(SP_GC_ENTRY_*) for the locals rooted at function scope. Tag 3 -- the one
|
|
136
|
+
value the per-root tags 1 and 2 never combine to -- marks the entry, so the
|
|
137
|
+
walkers' existing `& 3` test already routes it to sp_gc_mark_root_entry.
|
|
138
|
+
|
|
139
|
+
The per-root form costs the C compiler an address-taken stack slot, a
|
|
140
|
+
bounds check and a cleanup-attribute pop duplicated on every scope exit;
|
|
141
|
+
on a large generated TU those were a third of clang's unoptimised IR and
|
|
142
|
+
most of its peak memory. The frame is one alloca, zeroed once at entry
|
|
143
|
+
(a zero slot is INT 0, which marks nothing), popped by SP_GC_SAVE's cleanup
|
|
144
|
+
with everything above it. A slot keeps its last value until the function
|
|
145
|
+
returns, so an object a dead temporary named is held that much longer --
|
|
146
|
+
an over-approximation of liveness, never an under-approximation. The
|
|
147
|
+
layout is decoded in sp_gc_mark_frame; the generated struct is
|
|
148
|
+
`{ sp_gc_frame_hdr h; sp_RbVal v[nv]; void **p[np]; }`, either array
|
|
149
|
+
omitted when its count is 0. */
|
|
150
|
+
typedef struct { int nv; int np; } sp_gc_frame_hdr;
|
|
151
|
+
#define SP_GC_ROOT_FRAME(f) ((void)_sp_gc_root_push((void**)((uintptr_t)&(f) | (uintptr_t)3)))
|
|
152
|
+
#define SP_GC_ENTRY_PTR(v) ((void**)((uintptr_t)&(v) | _SP_GC_SLOT_TAG(v)))
|
|
153
|
+
#define SP_GC_ENTRY_RBVAL(v) ((void**)((uintptr_t)&(v) | (uintptr_t)1))
|
|
154
|
+
#define SP_GC_ENTRY_STR(v) ((void**)((uintptr_t)&(v) | (uintptr_t)2))
|
|
155
|
+
|
|
156
|
+
/* ---- write barrier ----
|
|
157
|
+
A generational mark walks the young objects and whatever the roots reach; an
|
|
158
|
+
old object it does not walk can still be the only thing holding a young one.
|
|
159
|
+
The barrier records those: when a reference is stored into an object that has
|
|
160
|
+
already been promoted, that object joins the remembered set, which a minor
|
|
161
|
+
collection treats as an extra root.
|
|
162
|
+
|
|
163
|
+
Cost is one load of a header bit already on the store's own cache line and a
|
|
164
|
+
branch that steady state does not take -- measured at 0.5% on optcarrot when
|
|
165
|
+
it fires on reference stores only, and 14% when it fires on every ivar store
|
|
166
|
+
including the scalar ones, which is why the emitter discriminates.
|
|
167
|
+
|
|
168
|
+
The set is a plain array with a dirty bit for deduplication. Overflow is
|
|
169
|
+
safe rather than fatal: a full set means the next collection marks whole-heap
|
|
170
|
+
(sp_gc_rem_overflow), which is exactly what today's collector always does. */
|
|
171
|
+
/* Wrap the OBJECT of a reference store: runs the barrier and yields the object
|
|
172
|
+
itself, so it works wherever the store appears -- a statement, or an
|
|
173
|
+
assignment inside a larger expression. The statement expression evaluates the
|
|
174
|
+
object once, which a comma form would not. */
|
|
175
|
+
#define SP_WBO(x) ({ __typeof__(x) _sp_wbo = (x); sp_gc_wb((void *)_sp_wbo); _sp_wbo; })
|
|
176
|
+
#define SP_GC_REMEMBERED_MAX 65536
|
|
177
|
+
extern void *sp_gc_remembered[SP_GC_REMEMBERED_MAX];
|
|
178
|
+
extern int sp_gc_nremembered;
|
|
179
|
+
extern int sp_gc_rem_overflow;
|
|
180
|
+
extern int sp_gc_minor_on; /* read by sp_gc_wb below; set once before main */
|
|
181
|
+
/* SPINEL_GC_OBJ_BUDGET=walk: the object collection budget is priced off the
|
|
182
|
+
whole set a mark walks (objects + strings) rather than the object heap
|
|
183
|
+
alone. Set once before main, beside the modes above; read by
|
|
184
|
+
sp_gc_retune_object, which is where the reasoning lives. */
|
|
185
|
+
extern int sp_gc_obj_budget_mode; /* 0 obj, 1 walk, 2 gated (default) */
|
|
186
|
+
extern size_t sp_gc_obj_alpha1024; /* the last gate decision, in 1024ths */
|
|
187
|
+
extern int sp_gc_str_major_fixed;
|
|
188
|
+
extern int sp_gc_str_major_sched;
|
|
189
|
+
extern size_t sp_gc_str_majors;
|
|
190
|
+
extern int sp_gc_obj_budget_fixed;
|
|
191
|
+
extern int sp_gc_str_budget_fixed;
|
|
192
|
+
/* Set for the duration of the string sweep hook on a minor cycle: only the
|
|
193
|
+
young string list may be swept, because the mark that just ran did not
|
|
194
|
+
walk old objects and so did not reach the strings they hold. */
|
|
195
|
+
extern int sp_gc_str_minor_only;
|
|
196
|
+
/* The barrier proper. Out of line and behind the mode test: with the minor
|
|
197
|
+
mark off, which is the default, every store site pays one predictable
|
|
198
|
+
branch instead of carrying the tag protocol and the remembered-set push
|
|
199
|
+
inline. Inlining all of it cost ~5% on optcarrot, whose inner loops write
|
|
200
|
+
object references per scanline. */
|
|
201
|
+
void sp_gc_wb_slow(void *obj);
|
|
202
|
+
/* The STICKY half of the remembered set. `sp_gc_wb` records a store the
|
|
203
|
+
barrier saw; this records a HOLDER whose stores it will not see, so the
|
|
204
|
+
entry is kept for as long as the object lives rather than cleared each
|
|
205
|
+
cycle.
|
|
206
|
+
|
|
207
|
+
The one producer is a by-reference String parameter. The callee stores
|
|
208
|
+
through `const char **_cell_x` and cannot name what owns that slot -- the
|
|
209
|
+
caller may have lent a stack local, a heap cell, a proc's capture slot or
|
|
210
|
+
an ivar -- and reading a header off a stack address to find out is exactly
|
|
211
|
+
the fault that took #4391's first half down. The owner IS nameable at the
|
|
212
|
+
lending call site, but that site runs BEFORE the store, and a barrier
|
|
213
|
+
before a call that can collect does not cover the stores after it (#4378).
|
|
214
|
+
Sticky is what makes the placement stop mattering.
|
|
215
|
+
|
|
216
|
+
Only the two forms that need it are lent: a heap cell and an ivar's owner.
|
|
217
|
+
A stack local is lent as `&lv_x`, and the caller's frame roots it for the
|
|
218
|
+
whole nest below, so nothing is pinned for it -- which is also why
|
|
219
|
+
FORWARDING a by-reference parameter pins nothing: whatever the original
|
|
220
|
+
lending site was, it already decided. */
|
|
221
|
+
void sp_gc_pin_remembered_slow(void *obj);
|
|
222
|
+
static inline void sp_gc_pin_remembered(void *obj) {
|
|
223
|
+
if (SP_EXPECT(sp_gc_minor_on, 1)) sp_gc_pin_remembered_slow(obj);
|
|
224
|
+
}
|
|
225
|
+
#define SP_GC_PINNED_MAX 16384
|
|
226
|
+
extern void *sp_gc_pinned[SP_GC_PINNED_MAX];
|
|
227
|
+
extern int sp_gc_npinned;
|
|
228
|
+
extern int sp_gc_pin_overflow;
|
|
229
|
+
static inline void sp_gc_wb(void *obj) {
|
|
230
|
+
/* Nothing reads the remembered set unless a minor mark runs, and whether one
|
|
231
|
+
can is decided once, from the environment, before main. So with the
|
|
232
|
+
generational mark off -- the default -- the whole barrier is bookkeeping
|
|
233
|
+
for a reader that never comes. rubys observed the other half of this from
|
|
234
|
+
the source: `old` is set on every survivor regardless of the mode, so the
|
|
235
|
+
barrier was doing its full work in both. */
|
|
236
|
+
if (SP_EXPECT(sp_gc_minor_on, 1)) {
|
|
237
|
+
/* With the mark on, the common case -- a young holder, or an old one
|
|
238
|
+
already recorded -- is decided here from the header the store is about
|
|
239
|
+
to touch anyway, so the call is paid only by a store that actually
|
|
240
|
+
records something. The tag byte says whether there is a header at all
|
|
241
|
+
(the same protocol sp_gc_wb_slow and sp_gc_mark read). */
|
|
242
|
+
if (!obj) return;
|
|
243
|
+
unsigned char pm = ((const unsigned char *)obj)[-1];
|
|
244
|
+
if (pm == 0xfd || pm == 0xff || pm == 0xf1 || pm == 0xf0 ||
|
|
245
|
+
pm == 0xfe || pm == 0xfc || pm == 0xfb || pm == 0xfa || pm == 0xf8) return;
|
|
246
|
+
const sp_gc_hdr *h = (const sp_gc_hdr *)obj - 1;
|
|
247
|
+
if (!h->old || h->dirty) return;
|
|
248
|
+
sp_gc_wb_slow(obj);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/* Young object heap. Threaded build: per-worker lists (one pusher each, since a
|
|
252
|
+
started thread is pinned to its worker), so allocation pushes without the
|
|
253
|
+
CAS-on-shared-head that made object-heavy parallel workloads bounce a cache
|
|
254
|
+
line every alloc. Removals happen only under stop-the-world (every mutator
|
|
255
|
+
parked). Survivors promote into the single shared old heap during the sweep,
|
|
256
|
+
under stop-the-world, so the old list stays lock-free and shared. The live-
|
|
257
|
+
byte counter sp_gc_bytes stays a single (relaxed-atomic) total -- array data
|
|
258
|
+
buffers adjust it from whichever worker mutates them, which a per-worker split
|
|
259
|
+
could not attribute correctly. */
|
|
260
|
+
#ifdef SP_THREADS
|
|
261
|
+
/* One cache-line-padded slot per worker holding the two fields written on every
|
|
262
|
+
object allocation: the young list head and the unflushed live-byte delta.
|
|
263
|
+
Padding is essential -- without it adjacent workers' 8-byte slots share a
|
|
264
|
+
cache line, so a per-worker heap still bounced that line every alloc (false
|
|
265
|
+
sharing), which kept object-heavy parallel allocation from scaling despite the
|
|
266
|
+
lock/CAS removal. One line per worker isolates them completely. */
|
|
267
|
+
#define SP_CACHELINE 64
|
|
268
|
+
typedef struct {
|
|
269
|
+
sp_gc_hdr *young; /* per-worker young list head */
|
|
270
|
+
size_t flush_delta; /* per-worker unflushed live-byte delta (see below) */
|
|
271
|
+
char _pad[SP_CACHELINE - sizeof(sp_gc_hdr*) - sizeof(size_t)];
|
|
272
|
+
} sp_gc_wslot_t;
|
|
273
|
+
extern sp_gc_wslot_t sp_gc_wslot[SP_MAX_WORKERS];
|
|
274
|
+
#else
|
|
275
|
+
extern sp_gc_hdr *sp_gc_heap;
|
|
276
|
+
#endif
|
|
277
|
+
/* Current mark generation (see sp_gc_hdr.marked in sp_types.h). */
|
|
278
|
+
extern unsigned sp_gc_mark_gen;
|
|
279
|
+
extern void (*sp_gc_obj_retune_hook)(size_t before);
|
|
280
|
+
extern size_t sp_gc_bytes;
|
|
281
|
+
extern size_t sp_gc_old_bytes;
|
|
282
|
+
extern int sp_gc_cycle;
|
|
283
|
+
/* SPINEL_GC_STATS=1 (report in sp_alloc.c): how many collections ran and what
|
|
284
|
+
they cost. A program whose GC share of CPU climbs with concurrency looks
|
|
285
|
+
from outside the process exactly like one collecting more often, and there
|
|
286
|
+
was no counter to tell the two apart -- which is where the diagnosis in
|
|
287
|
+
#4352 stopped. One clock pair and two increments per COLLECTION, so they
|
|
288
|
+
are always kept; only the printing is gated. */
|
|
289
|
+
extern unsigned long long sp_gc_stat_collections;
|
|
290
|
+
extern unsigned long long sp_gc_stat_fulls;
|
|
291
|
+
extern double sp_gc_stat_seconds;
|
|
292
|
+
extern void (*sp_gc_mark_suspended_fibers_hook)(void);
|
|
293
|
+
|
|
294
|
+
/* Heap byte-counter accounting. The container growth paths (sp_array.h,
|
|
295
|
+
sp_alloc.h's PolyArray, the string builder) adjust sp_gc_bytes inline
|
|
296
|
+
WITHOUT the heap lock -- at N>1 workers that is a data race against the
|
|
297
|
+
locked allocators and against each other (torn counters skew the GC
|
|
298
|
+
trigger; TSan flags it). Under SP_THREADS every update and every
|
|
299
|
+
trigger-decision read goes through a relaxed atomic: the counter is a
|
|
300
|
+
heuristic (collection thresholds), so relaxed ordering is enough -- no
|
|
301
|
+
other data is published through it. Collector-side code that runs under
|
|
302
|
+
stop-the-world (sweep, retune) keeps plain accesses: every mutator is
|
|
303
|
+
parked at the barrier, which already gives the happens-before edge.
|
|
304
|
+
The single-threaded build expands to the exact plain +=/-= it had, so
|
|
305
|
+
that archive stays byte-identical. */
|
|
306
|
+
#ifdef SP_THREADS
|
|
307
|
+
#define SP_GC_CTR_ADD(ctr, n) SP_ATOMIC_FETCH_ADD(&(ctr), (size_t)(n), __ATOMIC_RELAXED)
|
|
308
|
+
#define SP_GC_CTR_SUB(ctr, n) SP_ATOMIC_FETCH_SUB(&(ctr), (size_t)(n), __ATOMIC_RELAXED)
|
|
309
|
+
#define SP_GC_CTR_GET(ctr) SP_ATOMIC_LOAD(&(ctr), __ATOMIC_RELAXED)
|
|
310
|
+
#define SP_GC_CTR_SET(ctr, n) SP_ATOMIC_STORE(&(ctr), (size_t)(n), __ATOMIC_RELAXED)
|
|
311
|
+
#else
|
|
312
|
+
#define SP_GC_CTR_ADD(ctr, n) ((ctr) += (size_t)(n))
|
|
313
|
+
#define SP_GC_CTR_SUB(ctr, n) ((ctr) -= (size_t)(n))
|
|
314
|
+
#define SP_GC_CTR_GET(ctr) (ctr)
|
|
315
|
+
#define SP_GC_CTR_SET(ctr, n) ((ctr) = (size_t)(n))
|
|
316
|
+
#endif
|
|
317
|
+
|
|
318
|
+
/* Live-byte accounting for the object heap. Every allocation and every array-
|
|
319
|
+
buffer resize adjusts sp_gc_bytes; under SP_THREADS a shared atomic RMW per
|
|
320
|
+
op bounces one cache line across workers and dominated object-heavy parallel
|
|
321
|
+
allocation (measured ~13x on the counter alone at 4 workers). Batch it: each
|
|
322
|
+
worker accumulates its delta in a private (non-atomic) slot and flushes to the
|
|
323
|
+
shared total only every SP_GC_FLUSH_QUANTUM, cutting the atomic frequency by
|
|
324
|
+
~quantum/alloc-size. sp_gc_bytes stays the authoritative shared total -- every
|
|
325
|
+
read (threshold trigger, GC.stat) and the collector's recompute are unchanged;
|
|
326
|
+
the trigger merely lags the true total by at most quantum*workers, bounded
|
|
327
|
+
overshoot for a heuristic. The collector resets the deltas after its recompute
|
|
328
|
+
(which already counts every live object's size, so the pending deltas are
|
|
329
|
+
subsumed). The single-threaded build is the plain +=/-= it always was. */
|
|
330
|
+
#ifdef SP_THREADS
|
|
331
|
+
#define SP_GC_FLUSH_QUANTUM (16u * 1024u)
|
|
332
|
+
static inline void sp_gc_bytes_add(size_t n) {
|
|
333
|
+
if (sp_gc_in_sweeper) return; /* a finalizer on a sweeper thread: the mark already counted the live total */
|
|
334
|
+
size_t *d = &sp_gc_wslot[sp_worker_id].flush_delta;
|
|
335
|
+
size_t v = *d + n;
|
|
336
|
+
if (v >= SP_GC_FLUSH_QUANTUM) { SP_GC_CTR_ADD(sp_gc_bytes, v); *d = 0; }
|
|
337
|
+
else *d = v;
|
|
338
|
+
}
|
|
339
|
+
static inline void sp_gc_bytes_sub(size_t n) {
|
|
340
|
+
if (sp_gc_in_sweeper) return;
|
|
341
|
+
size_t *d = &sp_gc_wslot[sp_worker_id].flush_delta;
|
|
342
|
+
if (*d >= n) { *d -= n; }
|
|
343
|
+
else { size_t rem = n - *d; *d = 0; SP_GC_CTR_SUB(sp_gc_bytes, rem); }
|
|
344
|
+
}
|
|
345
|
+
#else
|
|
346
|
+
static inline void sp_gc_bytes_add(size_t n) { sp_gc_bytes += n; }
|
|
347
|
+
/* Floored: the counter is a heuristic the retune divides by, and a wrapped
|
|
348
|
+
value there is not a large heap but a collector that never triggers again
|
|
349
|
+
(#4073). */
|
|
350
|
+
/* Floored. The counter is a heuristic the retune divides by and multiplies, and
|
|
351
|
+
an array-growth path can subtract a capacity larger than the counter holds
|
|
352
|
+
(measured: sp_array.h's StrArray grow, 20472 against 10344) -- wrapping it
|
|
353
|
+
makes the trigger fire on every allocation until the next collection resets
|
|
354
|
+
it, and used to make the retune set a threshold that never fires at all. */
|
|
355
|
+
static inline void sp_gc_bytes_sub(size_t n) {
|
|
356
|
+
sp_gc_bytes = sp_gc_bytes >= n ? sp_gc_bytes - n : 0;
|
|
357
|
+
}
|
|
358
|
+
#endif
|
|
359
|
+
|
|
360
|
+
/* Push a header onto the shared sp_gc_heap list. Under SP_THREADS this is a
|
|
361
|
+
lock-free CAS push so callers that hold no lock (the pool-hit relink) stay
|
|
362
|
+
off the heap mutex; the allocators, which hold the mutex anyway for the
|
|
363
|
+
collect trigger, use the same push so every writer to the list head agrees
|
|
364
|
+
on one protocol (a plain locked store racing an unlocked CAS would itself
|
|
365
|
+
be a race). The `next` store is atomic: a node being re-linked from a
|
|
366
|
+
shared pool free list may still have a stale pool popper reading its
|
|
367
|
+
`next` (that popper's CAS then fails and discards the value, but the read
|
|
368
|
+
itself must be defined). Removals happen only in the stop-the-world sweep
|
|
369
|
+
with every mutator parked, so the push never races a pop and needs no ABA
|
|
370
|
+
defense. Release order publishes the node's initialized header to the
|
|
371
|
+
collector. */
|
|
372
|
+
/* The slab's reservation (lib/sp_slab.c): a block is the slab's when its
|
|
373
|
+
address falls inside it, which the allocation fast paths test inline. */
|
|
374
|
+
extern uintptr_t sp_slab_base;
|
|
375
|
+
extern size_t sp_slab_cap;
|
|
376
|
+
static inline int sp_slab_owns(const void *p) { return (uintptr_t)p - sp_slab_base < sp_slab_cap; }
|
|
377
|
+
/* Only a block the slab does not hold goes on a list: a slab block's
|
|
378
|
+
generation is a bit in its chunk's bitmaps, set by the allocation itself
|
|
379
|
+
(lib/sp_slab.c), and the lists carry what fell back to malloc. */
|
|
380
|
+
#ifdef SP_THREADS
|
|
381
|
+
/* Per-worker young list: only this worker's M pushes here (started threads are
|
|
382
|
+
pinned and a worker pumps one green thread at a time), so a plain store is
|
|
383
|
+
race-free -- no CAS, no shared-head cache-line bounce. */
|
|
384
|
+
#define SP_GC_HEAP_PUSH(hdr) do { if (!sp_slab_owns(hdr)) { \
|
|
385
|
+
sp_gc_hdr **_sp_head = &sp_gc_wslot[sp_worker_id].young; \
|
|
386
|
+
(hdr)->next = *_sp_head; *_sp_head = (hdr); } \
|
|
387
|
+
} while (0)
|
|
388
|
+
#else
|
|
389
|
+
#define SP_GC_HEAP_PUSH(hdr) do { if (!sp_slab_owns(hdr)) { (hdr)->next = sp_gc_heap; sp_gc_heap = (hdr); } } while (0)
|
|
390
|
+
#endif
|
|
391
|
+
|
|
392
|
+
/* ---- Slab allocator for GC objects and heap strings (lib/sp_slab.c) ----
|
|
393
|
+
* sp_slab_alloc answers a zeroed block of `need` bytes (header included) from
|
|
394
|
+
* the calling worker's size-class chunks, in the current epoch's young
|
|
395
|
+
* generation; sp_slab_alloc_str the same for a heap string (unzeroed, its
|
|
396
|
+
* bytes counted on the string side); sp_slab_alloc_raw a payload no sweep
|
|
397
|
+
* frees. A block's generation, mark and finalizer bit live in its chunk's
|
|
398
|
+
* bitmaps, which is what the sweep reads (sp_slab_sweep_worker) and the
|
|
399
|
+
* mark sets (sp_slab_mark). A size past the largest class, or the allocator
|
|
400
|
+
* off (SPINEL_GC_SLAB=0), falls back to calloc/malloc, and such a block goes
|
|
401
|
+
* on the young/old LISTS instead; sp_slab_owns tells the two apart by
|
|
402
|
+
* address: every chunk lives inside one reserved range. sp_slab_release, at
|
|
403
|
+
* the end of a full cycle, returns fully free chunks to the OS. */
|
|
404
|
+
void *sp_slab_alloc(size_t need); /* an object: zeroed, in the current epoch's young generation */
|
|
405
|
+
void *sp_slab_alloc_obj(size_t need, void (*fin)(void *), void (*scn)(void *)); /* the same, with the header written */
|
|
406
|
+
void *sp_slab_alloc_str(size_t need); /* a heap string: young, its bytes counted on the string side */
|
|
407
|
+
void *sp_slab_alloc_raw(size_t need); /* a payload: no sweep frees it, only sp_slab_free */
|
|
408
|
+
void sp_slab_free(void *p);
|
|
409
|
+
void sp_slab_set_fin(void *hdr); /* the object gained a finalizer or recycler after allocation */
|
|
410
|
+
void sp_slab_park(void *hdr); /* a dead header about to be pooled: out of every generation's reach */
|
|
411
|
+
void sp_slab_relive(void *hdr); /* a pooled header handed out again */
|
|
412
|
+
void sp_slab_pin(const void *p); /* a frozen heap string: immortal, as a literal is */
|
|
413
|
+
int sp_slab_is_str(const void *p);
|
|
414
|
+
int sp_slab_is_live(const void *p); /* allocated, in some generation */
|
|
415
|
+
int sp_slab_is_old(const void *p);
|
|
416
|
+
int sp_slab_is_marked(const void *p);
|
|
417
|
+
void sp_slab_describe(const void *p); /* the verifiers: what the bitmaps say about a slot */
|
|
418
|
+
void sp_slab_verify_all(void); /* SPINEL_GC_VERIFY: the bitmaps against their invariants */
|
|
419
|
+
void sp_slab_unmark(const void *p);
|
|
420
|
+
/* the mark: sets the slot's mark bit, promotes a young slot (unless `aging`
|
|
421
|
+
keeps it young); 0 when the slot was already marked this cycle */
|
|
422
|
+
int sp_slab_mark(const void *p, int aging, int *was_young);
|
|
423
|
+
extern unsigned sp_slab_epoch;
|
|
424
|
+
extern SP_TLS unsigned long sp_slab_frees; /* this thread's explicit frees, counted */
|
|
425
|
+
void sp_slab_epoch_flip(void); /* under the barrier: new allocations go to the other parity */
|
|
426
|
+
void sp_slab_runs_release(void);
|
|
427
|
+
void sp_slab_history(const void *p); /* SPINEL_GC_VERIFY: print a slot's recorded events */
|
|
428
|
+
/* SPINEL_GC_STRESS=2 (lib/sp_slab.c): what dies is poisoned and kept out of reuse */
|
|
429
|
+
extern int sp_slab_quar_on;
|
|
430
|
+
int sp_slab_is_quarantined(const void *p);
|
|
431
|
+
void sp_slab_quarantine_trim(void); /* under the barrier: a quarantine past its size is let go */
|
|
432
|
+
extern int sp_gc_alloc_fast_ok; /* sp_gc_alloc's lean front may run: drop to 0 to route every allocation through the full form */ /* under the barrier: the workers' claimed-not-allocated runs are unclaimed */
|
|
433
|
+
typedef struct { size_t freed_obj, freed_str, freed_slots, slots, swept, kept_young, parked; } sp_slab_sweep_stats; /* swept: finalizers run; parked: bytes of headers held by their pools */
|
|
434
|
+
/* one worker's chunks: frees what the closed epoch holds unmarked (and,
|
|
435
|
+
at a full cycle, the old generation's unmarked); `die` runs a dead
|
|
436
|
+
object's finalizer or recycler and answers 1 when the slot is freed, 0
|
|
437
|
+
when the pool keeps the header */
|
|
438
|
+
void sp_slab_sweep_worker(int wid, int full, int aging, int (*die)(void *hdr), sp_slab_sweep_stats *st);
|
|
439
|
+
void sp_slab_each_object(int young, int old, void (*fn)(void *hdr, void *arg), void *arg);
|
|
440
|
+
void sp_slab_each_string(int young, int old, void (*fn)(void *hdr, void *arg), void *arg);
|
|
441
|
+
void sp_slab_release(void);
|
|
442
|
+
void sp_slab_release_worker(int wid); /* one worker's lists, by their owner, beside the program */
|
|
443
|
+
void sp_slab_release_from(int first); /* the slots from `first` on, under the barrier */
|
|
444
|
+
extern int sp_slab_on;
|
|
445
|
+
|
|
446
|
+
/* ---- Collector entry points (defined in lib/sp_gc.c) ---- */
|
|
447
|
+
int sp_gc_verify_on(void); /* SPINEL_GC_VERIFY is set (diagnostics only) */
|
|
448
|
+
extern const char *sp_gc_dbg_phase; /* which root group the mark walk is in */
|
|
449
|
+
extern void *sp_gc_dbg_ctx;
|
|
450
|
+
void sp_gc_mark(void *obj);
|
|
451
|
+
void sp_gc_mark_all(void);
|
|
452
|
+
void sp_gc_mark_drain(void);
|
|
453
|
+
extern int sp_gc_minor;
|
|
454
|
+
extern int sp_gc_young_probe_on, sp_gc_young_probe_hit;
|
|
455
|
+
extern int sp_gc_age_survivors, sp_gc_age_on, sp_gc_root_phase;
|
|
456
|
+
extern size_t sp_gc_old_live;
|
|
457
|
+
extern size_t sp_gc_young_kept_bytes, sp_gc_npromoted;
|
|
458
|
+
extern int sp_gc_minor_on;
|
|
459
|
+
extern int sp_gc_verify_gen;
|
|
460
|
+
extern int sp_gc_verify_gen_fail;
|
|
461
|
+
extern int sp_gc_verify_probe_on, sp_gc_verify_probe_hit;
|
|
462
|
+
extern unsigned sp_gc_verify_probe;
|
|
463
|
+
/* Per-phase collector time, in seconds, cumulative (SPINEL_GC_PHASES=1; all
|
|
464
|
+
zero when it is off). sp_gc_stat_seconds is their sum plus the bookkeeping
|
|
465
|
+
between them. Reported by sp_alloc.c, which is where the stats line lives. */
|
|
466
|
+
/* Objects marked and slots swept since the process started. Counts are what
|
|
467
|
+
the two phases' costs are actually per; see sp_gc_sweep_young. */
|
|
468
|
+
extern size_t sp_gc_ct_swept, sp_gc_ct_marked;
|
|
469
|
+
extern double sp_gc_ph_mark, sp_gc_ph_oldsweep, sp_gc_ph_slotsweep,
|
|
470
|
+
sp_gc_ph_rembclear, sp_gc_ph_strsweep, sp_gc_ph_trim;
|
|
471
|
+
/* The mark, split the way sp_gc_mark_all walks: this worker's own root stack,
|
|
472
|
+
every live fiber's saved roots, the globals hook, then the trace that drains
|
|
473
|
+
what those three found. Named as sp_gc_dbg_phase names them under verify, so
|
|
474
|
+
a number leads to the code. They sum to sp_gc_ph_mark.
|
|
475
|
+
|
|
476
|
+
The split exists because "mark grew" has two causes that need different
|
|
477
|
+
answers: more ROOTS to scan (the fibers row, which grows with in-flight
|
|
478
|
+
fibers) and more GRAPH to trace (the scan row, which grows because those
|
|
479
|
+
fibers hold live objects). Only the first is what slicing the fiber list to
|
|
480
|
+
the parked workers would address (#4384). */
|
|
481
|
+
extern double sp_gc_ph_slot_max; /* longest single sweep task, summed (sp_sched.c) */
|
|
482
|
+
extern double sp_gc_ph_task_sum, sp_gc_ph_task_obj, sp_gc_ph_task_sold, sp_gc_ph_task_syoung; /* all tasks' time, by kind */
|
|
483
|
+
extern unsigned long long sp_gc_ph_mk_helpers, sp_gc_ph_mk_drains;
|
|
484
|
+
extern double sp_gc_ph_mk_drain, sp_gc_ph_mk_join, sp_gc_ph_mk_idle;
|
|
485
|
+
extern double sp_gc_ph_conc_wait, sp_gc_ph_conc_wall, sp_gc_ph_barrier, sp_gc_ph_park, sp_gc_ph_apply_obj, sp_gc_ph_apply_str, sp_gc_ph_apply_release; extern unsigned long long sp_gc_ph_conc_waits;
|
|
486
|
+
extern double sp_gc_ph_mk_roots, sp_gc_ph_mk_fibers,
|
|
487
|
+
sp_gc_ph_mk_globals, sp_gc_ph_mk_scan;
|
|
488
|
+
extern int sp_gc_ph_on;
|
|
489
|
+
void sp_gc_collect(void);
|
|
490
|
+
#ifdef SP_THREADS
|
|
491
|
+
/* Sweep one worker's young list on that worker (see sp_gc.c). Survivors come
|
|
492
|
+
back as a local list for the collector to splice into the old heap. */
|
|
493
|
+
void sp_gc_sweep_slot(int wid, sp_gc_hdr **out_head, sp_gc_hdr **out_tail, size_t *out_bytes);
|
|
494
|
+
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);
|
|
495
|
+
void sp_gc_sweep_old_list(sp_gc_hdr **pp, size_t *out_live, sp_gc_hdr **out_tail);
|
|
496
|
+
extern void (*sp_gc_par_sweep_hook)(void);
|
|
497
|
+
/* The parallel mark: the scheduler lends parked workers to the drain. */
|
|
498
|
+
extern void (*sp_gc_par_mark_hook)(void);
|
|
499
|
+
void sp_gc_mark_par_run(void);
|
|
500
|
+
void sp_gc_mark_par_begin(void);
|
|
501
|
+
void sp_gc_mark_par_markers(int n);
|
|
502
|
+
extern int sp_gc_par_mark_on;
|
|
503
|
+
/* The header's flag word: `marked` (27 bits) and the five bits after it,
|
|
504
|
+
which the C bit-fields lay out from the low end of one 32-bit unit on
|
|
505
|
+
every target spinel runs on. The parallel mark claims a stamp with an
|
|
506
|
+
exchange on this word; sp_gc_hdr_flags_check aborts at start-up if the
|
|
507
|
+
compiler laid the fields out differently. */
|
|
508
|
+
#define SP_GC_FL_MARK_MASK 0x07ffffffu
|
|
509
|
+
#define SP_GC_FL_FROZEN (1u << 27)
|
|
510
|
+
#define SP_GC_FL_PINNED (1u << 28)
|
|
511
|
+
#define SP_GC_FL_OLD (1u << 29)
|
|
512
|
+
#define SP_GC_FL_DIRTY (1u << 30)
|
|
513
|
+
#define SP_GC_FL_AGED (1u << 31)
|
|
514
|
+
static inline unsigned *sp_gc_hdr_flags(sp_gc_hdr *h) { return (unsigned *)((char *)h + offsetof(sp_gc_hdr, size) + sizeof(size_t)); }
|
|
515
|
+
void sp_gc_hdr_flags_check(void);
|
|
516
|
+
/* The concurrent sweep (see sp_gc.c): the driver installs both or neither. */
|
|
517
|
+
extern void (*sp_gc_conc_sweep_hook)(int full, int str_sweep, int str_major);
|
|
518
|
+
extern void (*sp_gc_conc_wait_hook)(void);
|
|
519
|
+
extern int sp_gc_conc_on;
|
|
520
|
+
/* The old object list, for the driver: detach it whole for a full cycle's
|
|
521
|
+
sweep, attach a list (survivors, or the swept remainder) back. */
|
|
522
|
+
void sp_gc_sweep_chunks(int wid, int full); /* one slot's slab chunks, by their bitmaps (lib/sp_gc.c) */
|
|
523
|
+
sp_gc_hdr *sp_gc_old_detach(void);
|
|
524
|
+
void sp_gc_old_attach(sp_gc_hdr *head, sp_gc_hdr *tail);
|
|
525
|
+
/* Splice one worker's survivors onto the shared old heap. Collector-only. */
|
|
526
|
+
void sp_gc_promote_slot(sp_gc_hdr *head, sp_gc_hdr *tail, size_t bytes);
|
|
527
|
+
#endif
|
|
528
|
+
void sp_gc_enforce_mem_limit(void);
|
|
529
|
+
extern int sp_gc_trim_wanted; /* set by a full cycle; the trimmer thread clears it with a malloc_trim */
|
|
530
|
+
extern int sp_gc_trimmer_on;
|
|
531
|
+
/* Collect + re-tune the threshold, assuming exclusive heap access (see
|
|
532
|
+
sp_alloc.c). sp_stw_collect (sp_sched.c, threaded build) stops the world then
|
|
533
|
+
runs sp_gc_collect_retune; the single-threaded allocator calls it directly
|
|
534
|
+
under the heap lock. */
|
|
535
|
+
void sp_gc_collect_retune(void);
|
|
536
|
+
void sp_stw_collect(void);
|
|
537
|
+
void sp_gc_collect_request(void); /* explicit GC.start: same barrier, forced */
|
|
538
|
+
/* ObjectSpace finalizers left pending by a collection. They run at a safe
|
|
539
|
+
point of the program -- a method's entry, a loop's back-edge, GC.start,
|
|
540
|
+
exit -- never inside the allocation that collected, where the runtime may
|
|
541
|
+
be halfway through an operation on an object the finalizer can reach. The
|
|
542
|
+
generated code polls the flag only when the program defines finalizers. */
|
|
543
|
+
extern volatile int sp_fin_pending_flag;
|
|
544
|
+
void sp_fin_run_pending(void);
|
|
545
|
+
#define SP_FIN_POLL() \
|
|
546
|
+
do { if (SP_UNLIKELY(SP_ATOMIC_LOAD(&sp_fin_pending_flag, __ATOMIC_RELAXED))) sp_fin_run_pending(); } while (0)
|
|
547
|
+
void sp_oom_die(void);
|
|
548
|
+
/* The instance variables of a builtin value (an Array, a Hash, a Random):
|
|
549
|
+
a map from the object to its ivar table, a GC object the TU makes
|
|
550
|
+
(sp_bivar_set in spinel_rt.h). The map does not keep the object alive;
|
|
551
|
+
the collector marks a table only while its object lives, and drops the
|
|
552
|
+
entry of an object it frees (lib/sp_gc.c). */
|
|
553
|
+
void *sp_ivtbl_get(const void *obj);
|
|
554
|
+
void sp_ivtbl_put(const void *obj, void *tbl);
|
|
555
|
+
/* the TU's rendering of an ivar table, for Random#inspect (set with the first table) */
|
|
556
|
+
extern const char *(*sp_ivtbl_inspect_fn)(void *tbl);
|
|
557
|
+
|
|
558
|
+
/* ---- Embedder callbacks supplied by the generated TU ----
|
|
559
|
+
* The collector cannot own the program's roots or string heap (they are
|
|
560
|
+
* static state in the generated TU: the regexp match globals, ARGV, the
|
|
561
|
+
* in-flight exception stack, and the heap-string free list). The TU
|
|
562
|
+
* installs its mark-roots and string-sweep callbacks here at startup;
|
|
563
|
+
* sp_gc_mark_all / sp_gc_collect invoke them through these pointers, the
|
|
564
|
+
* same way fibers register sp_gc_mark_suspended_fibers_hook. */
|
|
565
|
+
extern void (*sp_gc_mark_globals_hook)(void);
|
|
566
|
+
extern void (*sp_gc_str_sweep_hook)(void);
|
|
567
|
+
/* Whether the string heap's own schedule (or its growth backstop) would take
|
|
568
|
+
a major this cycle. A string major needs a whole-heap mark, so under the
|
|
569
|
+
minor mark the object cycle it lands on has to be full; the collector asks
|
|
570
|
+
this before it decides. */
|
|
571
|
+
extern int (*sp_gc_str_major_due_hook)(void);
|
|
572
|
+
|
|
573
|
+
/* ---- value-introspection hooks (set by the generated TU at startup) ----
|
|
574
|
+
* lib/sp_json.c (and other cold readers) own no container types; they reach the
|
|
575
|
+
* generated TU's typed arrays/hashes only through these generic readers, the
|
|
576
|
+
* same idiom as the GC hooks above. sp_sym_name maps a symbol id to its name;
|
|
577
|
+
* sp_json_kind classifies a boxed value (1=array, 2=hash, 0=other); len/aref
|
|
578
|
+
* iterate any array; hpair yields a hash's (key,value) at insertion index i. */
|
|
579
|
+
extern const char *(*sp_sym_name_fn)(sp_sym);
|
|
580
|
+
extern int (*sp_json_kind_fn)(sp_RbVal);
|
|
581
|
+
extern sp_int (*sp_json_len_fn)(sp_RbVal);
|
|
582
|
+
extern sp_RbVal (*sp_json_aref_fn)(sp_RbVal, sp_int);
|
|
583
|
+
extern void (*sp_json_hpair_fn)(sp_RbVal, sp_int, sp_RbVal *, sp_RbVal *);
|
|
584
|
+
/* Container BUILDERS for JSON.parse (installed by the generated TU, which owns
|
|
585
|
+
the hash type): make an empty string-keyed hash, and set a (key, value) pair
|
|
586
|
+
-- CRuby's JSON.parse returns String keys. Arrays are built directly from the
|
|
587
|
+
package ABI (sp_PolyArray). */
|
|
588
|
+
extern sp_RbVal (*sp_json_mk_hash_fn)(void);
|
|
589
|
+
extern sp_sym (*sp_json_sym_intern_fn)(const char *); /* symbolize_names key interner (TU-installed) */
|
|
590
|
+
extern void (*sp_json_hash_set_fn)(sp_RbVal, const char *, sp_RbVal);
|
|
591
|
+
/* Recursive #inspect of a boxed value, for lib/sp_inspect.c's container walker
|
|
592
|
+
(set to sp_poly_inspect; same idiom as the JSON hooks). */
|
|
593
|
+
extern const char *(*sp_poly_inspect_fn)(sp_RbVal);
|
|
594
|
+
/* #to_s of any boxed value, for a package that writes one (StringIO#puts):
|
|
595
|
+
the generated TU installs sp_poly_to_s, which renders a user object
|
|
596
|
+
through its own #to_s or the #<Name:0xADDR> default. */
|
|
597
|
+
extern const char *(*sp_poly_to_s_fn)(sp_RbVal);
|
|
598
|
+
/* Convert a plain object (a Struct) to a boxed StrPoly hash of its members,
|
|
599
|
+
generic (no format knowledge). The generated program installs it (switch on
|
|
600
|
+
cls_id) when it has Structs and a package consumes it; a consumer such as
|
|
601
|
+
the json package reads it to serialize an object as a hash. NULL otherwise. */
|
|
602
|
+
extern sp_RbVal (*sp_obj_to_hash_fn)(sp_RbVal);
|
|
603
|
+
extern const char *(*sp_obj_to_json_fn)(sp_RbVal);
|
|
604
|
+
/* Symbol-keyed Struct/Data #to_h, for a poly receiver (#2906). */
|
|
605
|
+
extern sp_RbVal (*sp_obj_to_h_fn)(sp_RbVal);
|
|
606
|
+
extern sp_RbVal (*sp_obj_struct_values_fn)(sp_RbVal);
|
|
607
|
+
/* user-object #to_a for container-read poly receivers (#3234): installed by
|
|
608
|
+
the generated prologue when any instantiated class defines a no-arg to_a */
|
|
609
|
+
extern sp_RbVal (*sp_obj_to_a_fn)(sp_RbVal);
|
|
610
|
+
/* #to_ary, the CONVERSION protocol -- distinct from to_a, the enumeration
|
|
611
|
+
one: Kernel#Array asks to_ary first, and only a class that defines it
|
|
612
|
+
answers here (#4187). */
|
|
613
|
+
extern sp_RbVal (*sp_obj_to_ary_fn)(sp_RbVal);
|
|
614
|
+
/* The array a `case/in` array pattern matches a user object against. Separate
|
|
615
|
+
from #to_a: a Data answers #deconstruct but has no #to_a at all. */
|
|
616
|
+
extern sp_RbVal (*sp_obj_deconstruct_fn)(sp_RbVal);
|
|
617
|
+
/* 1 when cls_id is a Data class. Data defines no #dig, so a dig that lands on
|
|
618
|
+
one is the TypeError CRuby raises rather than a member read; Struct, which
|
|
619
|
+
does define #dig, is unaffected. */
|
|
620
|
+
extern int (*sp_obj_is_data_fn)(int cls_id);
|
|
621
|
+
/* Data#with copy-update for a poly receiver: (value, symbol-keyed overrides). (#2890) */
|
|
622
|
+
extern sp_RbVal (*sp_obj_with_fn)(sp_RbVal, sp_RbVal);
|
|
623
|
+
/* default Object#inspect for user objects: the generated TU installs a
|
|
624
|
+
per-class ivar walk (sp_obj_inspect_sw); sp_poly_inspect's OBJ default
|
|
625
|
+
consults it so nested/boxed objects render like CRuby */
|
|
626
|
+
extern const char *(*sp_obj_inspect_fn)(int cls_id, void *p);
|
|
627
|
+
/* Same shape for user #to_s: sp_poly_to_s's OBJ default consults it so a
|
|
628
|
+
boxed user object with a custom to_s renders through it. */
|
|
629
|
+
extern const char *(*sp_obj_to_s_fn)(int cls_id, void *p);
|
|
630
|
+
/* CRuby's implicit conversion protocol on BOXED user objects: the runtime's
|
|
631
|
+
Integer/String conversion sites (pack, typed-slot coercions) reach a
|
|
632
|
+
compiled #to_int / #to_str through these. A class without the method is
|
|
633
|
+
the default arm (*ok = 0 / NULL) and the caller raises CRuby's TypeError. */
|
|
634
|
+
extern sp_int (*sp_obj_to_int_fn)(int cls_id, void *p, int *ok);
|
|
635
|
+
extern const char *(*sp_obj_to_str_fn)(int cls_id, void *p);
|
|
636
|
+
/* #to_path, which File/Dir/IO's path slots ask before #to_str (CRuby's
|
|
637
|
+
rb_get_path); NULL when the class defines none. */
|
|
638
|
+
extern const char *(*sp_obj_to_path_fn)(int cls_id, void *p);
|
|
639
|
+
/* Kernel#Integer / Kernel#Float on a boxed user object: the class's #to_int,
|
|
640
|
+
#to_i, #to_f or #to_str (`which`, in that order; 4 is #to_f for a Math
|
|
641
|
+
argument, 5 #to_hash for a `**` operand), WHATEVER the method's
|
|
642
|
+
static type -- CRuby calls it and judges the answer -- boxed into *out.
|
|
643
|
+
Answers 1 when the class has the method, 0 when it does not; with a NULL
|
|
644
|
+
`out` it only answers that, calling nothing. */
|
|
645
|
+
extern int (*sp_obj_conv_fn)(int cls_id, void *p, int which, sp_RbVal *out);
|
|
646
|
+
/* Ruby class name for a user cls_id (the generated id->name table), so a
|
|
647
|
+
runtime TU can word a TypeError the way CRuby does. */
|
|
648
|
+
extern const char *(*sp_obj_cls_name_fn)(int cls_id);
|
|
649
|
+
/* The user class of a builtin subclass instance boxed as its builtin (an
|
|
650
|
+
Array subclass instance boxed as its Array, #7449): the program's own
|
|
651
|
+
scan-function table names it, -1 for a plain one. NULL in a program with
|
|
652
|
+
no such class. */
|
|
653
|
+
extern int (*sp_bsub_cls_fn)(sp_RbVal v);
|
|
654
|
+
/* Is user class `sub` the class `super` or a descendant of it? The generated
|
|
655
|
+
class bank installs it; NULL means only an exact id can be trusted. A
|
|
656
|
+
pointer array of one class checks a stored object against it (#4486). */
|
|
657
|
+
extern int (*sp_class_le_id_fn)(int sub, int super);
|
|
658
|
+
/* Module#<=> of two boxed classes (-1/0/1, nil when unrelated), by the class
|
|
659
|
+
graph the generated program installs. */
|
|
660
|
+
extern sp_RbVal (*sp_class_cmp_fn)(sp_RbVal a, sp_RbVal b);
|
|
661
|
+
/* the same walk from a user class id, matching the ancestor by the NAME the
|
|
662
|
+
runtime reader holds (sp_class_kind_of_name in the generated program) */
|
|
663
|
+
extern int (*sp_class_kind_of_name_fn)(int cls, const char *name);
|
|
664
|
+
/* is a class value a module? (sp_class_is_module_val in the generated
|
|
665
|
+
program; NULL leaves only the builtin modules known) */
|
|
666
|
+
extern int (*sp_class_is_module_fn)(sp_Class c);
|
|
667
|
+
|
|
668
|
+
/* ---- Hot inline mark helpers (inlined into both sides) ----
|
|
669
|
+
* String tag bytes: 0xfe heap-unmarked -> 0xfc marked (0xfa -> 0xf8 for a
|
|
670
|
+
* collectable frozen one); others skipped. */
|
|
671
|
+
void sp_gc_mark_str(const char *s); /* lib/sp_gc.c: the bitmap mark of a slab string, the byte of a malloc'd one */
|
|
672
|
+
static inline void sp_mark_string(const char *s) {
|
|
673
|
+
if (!s) return;
|
|
674
|
+
/* 0xfe or 0xfa in one compare: a second branch in this layout-sensitive
|
|
675
|
+
inline costs optcarrot's mark (#1449) */
|
|
676
|
+
if (((unsigned char)s[-1] | 0x04) == 0xfe) {
|
|
677
|
+
sp_gc_mark_str(s);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
/* 0xfd is a mutable String's payload, whose lifetime belongs to the handle
|
|
681
|
+
in front of it: marking the bytes alone leaves the handle unreferenced,
|
|
682
|
+
and its finalizer frees the bytes this container still points at. The
|
|
683
|
+
payload's `next` field carries that handle (sp_fd_own). */
|
|
684
|
+
if ((unsigned char)s[-1] == 0xfd) {
|
|
685
|
+
void *owner = (void *)(((const sp_str_hdr *)(s - 1)) - 1)->next;
|
|
686
|
+
if (owner) sp_gc_mark(owner);
|
|
687
|
+
}
|
|
688
|
+
/* No frozen (0xf1) branch here: this is inlined into optcarrot's GC mark and
|
|
689
|
+
is layout-sensitive. A live frozen heap string is kept immortal by
|
|
690
|
+
sp_str_sweep instead (#1449). */
|
|
691
|
+
}
|
|
692
|
+
static inline void sp_mark_rbval(sp_RbVal v) {
|
|
693
|
+
if (v.tag == SP_TAG_STR) sp_mark_string(v.v.s);
|
|
694
|
+
/* A class boxed by name holds its name: a Method's owner is named with a
|
|
695
|
+
fresh copy, and nothing else keeps that copy -- an Array holding the
|
|
696
|
+
class printed the next string allocated in its place. Literal names
|
|
697
|
+
(SPL), and the copy kept for an exception's class (sp_exc_class_name),
|
|
698
|
+
are not heap strings and mark as nothing. */
|
|
699
|
+
else if (v.tag == SP_TAG_CLASS && v.cls_id == SP_CLASS_BY_NAME) sp_mark_string(v.v.s);
|
|
700
|
+
else if (v.tag == SP_TAG_OBJ && v.cls_id != SP_BUILTIN_FOREIGN_PTR &&
|
|
701
|
+
v.cls_id != SP_BUILTIN_REGEX && v.cls_id != SP_BUILTIN_ARGF) sp_gc_mark(v.v.p);
|
|
702
|
+
else if (v.tag == SP_TAG_BIGINT) sp_gc_mark(v.v.p);
|
|
703
|
+
}
|
|
704
|
+
/* A scratch root: the proc calling convention's side channel keeps its last
|
|
705
|
+
value after the value's reader is done with it, so by the next collection
|
|
706
|
+
it may name a slot a sweep has freed since. Marking that would set
|
|
707
|
+
generation bits on a free slot and run whatever scan hook its stale bytes
|
|
708
|
+
hold. A slab slot that is free now is skipped; one that was reused is
|
|
709
|
+
some live object, marked a cycle longer than needed, which is harmless. */
|
|
710
|
+
static inline void sp_mark_rbval_scratch(sp_RbVal v) {
|
|
711
|
+
const void *h = NULL;
|
|
712
|
+
if (v.tag == SP_TAG_STR || (v.tag == SP_TAG_CLASS && v.cls_id == SP_CLASS_BY_NAME)) { if (v.v.s && ((unsigned char)v.v.s[-1] | 0x04) == 0xfe) h = ((const sp_str_hdr *)(v.v.s - 1)) - 1; }
|
|
713
|
+
else if ((v.tag == SP_TAG_OBJ && v.cls_id != SP_BUILTIN_FOREIGN_PTR && v.cls_id != SP_BUILTIN_REGEX && v.cls_id != SP_BUILTIN_ARGF) || v.tag == SP_TAG_BIGINT) { if (v.v.p) h = (const char *)v.v.p - sizeof(sp_gc_hdr); }
|
|
714
|
+
if (h && sp_slab_owns(h) && !sp_slab_is_live(h)) return;
|
|
715
|
+
sp_mark_rbval(v);
|
|
716
|
+
}
|
|
717
|
+
/* Closure-cell content markers. A captured non-int local is laundered into the
|
|
718
|
+
pointer-sized sp_int cell as (uintptr_t)<ptr>; the cell's GC scan marks the
|
|
719
|
+
referent so it survives as long as the capturing proc does. */
|
|
720
|
+
static inline void sp_cell_scan_str(void *p) { sp_mark_string(*(const char **)p); }
|
|
721
|
+
static inline void sp_cell_scan_ptr(void *p) { sp_gc_mark(*(void **)p); }
|
|
722
|
+
static inline void sp_cell_scan_rbval(void *p) { sp_mark_rbval(*(sp_RbVal *)p); }
|
|
723
|
+
/* A captured String range rides a cell of its own struct: both endpoints are
|
|
724
|
+
strings to mark (sp_box_srange's scan, for the cell). */
|
|
725
|
+
static inline void sp_cell_scan_srange(void *p) {
|
|
726
|
+
sp_StrRange *r = (sp_StrRange *)p;
|
|
727
|
+
if (r->first) sp_mark_string(r->first);
|
|
728
|
+
if (r->last) sp_mark_string(r->last);
|
|
729
|
+
}
|
|
730
|
+
/* A captured Proc rides in an sp_int cell as (sp_int)(uintptr_t)ptr -- the cell
|
|
731
|
+
is an integer slot, but what it holds is a collectable object, and without a
|
|
732
|
+
scan the capture kept the CELL alive and nothing kept the proc. A nested
|
|
733
|
+
`proc { |v| two.call(v, v) }` then called through freed memory (#4077). */
|
|
734
|
+
static inline void sp_cell_scan_procint(void *p) {
|
|
735
|
+
sp_int v = *(sp_int *)p;
|
|
736
|
+
if (v) sp_gc_mark((void *)(uintptr_t)v);
|
|
737
|
+
}
|
|
738
|
+
/* A low-bit-tagged root entry is an sp_RbVal* (see SP_GC_ROOT_RBVAL);
|
|
739
|
+
an untagged entry is a plain void** to a direct GC pointer. */
|
|
740
|
+
static inline void sp_gc_mark_root_entry_flat(void **e) {
|
|
741
|
+
uintptr_t u = (uintptr_t)e;
|
|
742
|
+
if (u & (uintptr_t)1) { sp_mark_rbval(*(sp_RbVal *)(u & ~(uintptr_t)1)); }
|
|
743
|
+
else if (u & (uintptr_t)2) { sp_mark_string(*(const char **)(u & ~(uintptr_t)2)); }
|
|
744
|
+
else { void *o = *e; if (o) sp_gc_mark(o); }
|
|
745
|
+
}
|
|
746
|
+
/* A root frame (tag 3, see SP_GC_ROOT_FRAME): the boxed slots first, then the
|
|
747
|
+
tagged entries, each of which is a per-root entry relocated into the frame
|
|
748
|
+
-- never another frame. An entry the function has not reached yet is still
|
|
749
|
+
the zero the frame started with, and is skipped. */
|
|
750
|
+
static inline void sp_gc_mark_frame(const sp_gc_frame_hdr *h) {
|
|
751
|
+
const sp_RbVal *v = (const sp_RbVal *)(h + 1);
|
|
752
|
+
for (int i = 0; i < h->nv; i++) sp_mark_rbval(v[i]);
|
|
753
|
+
void **const *p = (void **const *)(v + h->nv);
|
|
754
|
+
for (int i = 0; i < h->np; i++) if (p[i]) sp_gc_mark_root_entry_flat(p[i]);
|
|
755
|
+
}
|
|
756
|
+
static inline void sp_gc_mark_root_entry(void **e) {
|
|
757
|
+
uintptr_t u = (uintptr_t)e;
|
|
758
|
+
if ((u & (uintptr_t)3) == (uintptr_t)3) sp_gc_mark_frame((const sp_gc_frame_hdr *)(u & ~(uintptr_t)3));
|
|
759
|
+
else sp_gc_mark_root_entry_flat(e);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
#endif
|