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,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