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,1507 @@
1
+ /* sp_slab.c -- size-class slab allocation for GC objects and heap strings,
2
+ * with the generations and the mark kept in bitmaps beside the chunks.
3
+ *
4
+ * Every object and every heap string used to be one malloc and, when it
5
+ * died, one free. On a server that allocates nine thousand of them per
6
+ * request from thirty workers, that was 40% of the CPU: glibc's arena locks
7
+ * and consolidation on the way in, and a sweep that handed every dead block
8
+ * back through free() on the way out, cold, from whichever thread ran the
9
+ * collection. Nothing a faster general allocator fixes -- tcmalloc measured
10
+ * the same -- because the cost is the per-block call and the cross-thread
11
+ * free, not the arithmetic.
12
+ *
13
+ * Here a block is a slot in a 16 KB chunk of one size class, and what the
14
+ * collector knows about a slot lives in seven bitmaps kept in the arena's
15
+ * metadata chunks, one bit per slot each, never in the slot itself:
16
+ *
17
+ * young[0], young[1] allocated in the epoch of that parity (see below)
18
+ * old promoted
19
+ * mark reached by this cycle's mark
20
+ * fin an object whose death runs a finalizer or recycler
21
+ * str a heap string (its bytes count on the string side)
22
+ * pin never freed by a sweep: a frozen heap string, a
23
+ * container payload, a dead header parked on its
24
+ * pool (the last two are in no generation at all and
25
+ * die by an explicit free, or come back by a relive)
26
+ *
27
+ * A slot is free when it is in no generation and not pinned (young[0],
28
+ * young[1], old, pin all clear), and allocation is a find-first-zero over
29
+ * those four words and one or into the current epoch's young word (atomic
30
+ * when threaded; single-threaded the words are plain). That replaced the
31
+ * per-chunk free list linked through the blocks, and the reason is the
32
+ * sweep: with the list, a sweep touched the header of every dead object to
33
+ * unlink it and to thread the free list through it, and on a server that
34
+ * churns a hundred megabytes of small blocks between two collections that
35
+ * was a stream of cache misses worth a fifth of the process. A sweep here
36
+ * reads the bitmaps of a chunk -- a few cache lines for sixteen kilobytes of
37
+ * slots -- and never the slots, except the dead ones whose `fin` bit says a
38
+ * finalizer has to run.
39
+ *
40
+ * The EPOCH is what tells the generation a concurrent sweep is reclaiming
41
+ * from what the running program has allocated since the barrier: the
42
+ * collector flips the parity under the barrier, so everything allocated
43
+ * from then on goes into the other young bitmap, and the sweep frees from
44
+ * the one that stopped growing. The mark promotes what it reaches -- it
45
+ * sets `old` on a young survivor, as the header's `old` bit is set there
46
+ * too -- so after the mark the swept young word is exactly the dead plus
47
+ * the promoted, and clearing it whole is the sweep's write.
48
+ *
49
+ * Every chunk lives inside one address range reserved at startup (16 GB of
50
+ * untouched, uncommitted pages; smaller where the system refuses), so a free
51
+ * tells a slab block from a malloc'd one by a range check, and a slot finds
52
+ * its chunk by masking its address. The range is handed out in 4 MB arenas
53
+ * whose first chunk holds the arena's chunk headers and whose next seven
54
+ * hold the bitmaps, so a released chunk keeps no resident page at all. At
55
+ * the end of a full cycle every chunk that is entirely free, beyond a small
56
+ * reserve per worker, is given back to the OS with madvise and re-carved
57
+ * from scratch when next used; that is what malloc_trim did for these
58
+ * blocks, at a syscall per idle chunk instead of a walk of thirty arenas.
59
+ *
60
+ * A chunk belongs to one worker for allocation and no other worker
61
+ * allocates from it, and every chunk a worker owns is on its owned list,
62
+ * which is what its sweep walks. Frees come from anywhere -- a sweeper
63
+ * thread, a finalizer on another worker, the explicit free of a payload --
64
+ * and are atomic bit clears; the one structure they share with the owner
65
+ * beyond the bitmaps is the owner's available list, pushed with the same
66
+ * compare-and-swap the owner pops with.
67
+ *
68
+ * SPINEL_GC_SLAB=0 turns this off and every block is a malloc again, which
69
+ * is the configuration ASAN wants: a slab hides a use-after-free from it.
70
+ * It is on by default whatever the process's malloc (it used to default off
71
+ * under jemalloc; see sp_slab_jemalloc_present for why that changed). */
72
+ #include <stdlib.h>
73
+ #include <stdio.h>
74
+ #include <string.h>
75
+ #include <stdint.h>
76
+ #include <sys/mman.h>
77
+ #include <time.h>
78
+ #include "sp_gc.h"
79
+ #include "sp_alloc.h" /* sp_gc_alloc is here: the collector's bookkeeping around the claim */
80
+ #ifndef MAP_ANONYMOUS
81
+ #define MAP_ANONYMOUS MAP_ANON
82
+ #endif
83
+ #ifndef MAP_NORESERVE
84
+ #define MAP_NORESERVE 0
85
+ #endif
86
+
87
+ #define SP_SLAB_ARENA ((size_t)4 << 20)
88
+ #define SP_SLAB_CHUNK ((size_t)16 << 10)
89
+ #define SP_SLAB_NCHUNK (SP_SLAB_ARENA / SP_SLAB_CHUNK) /* 256 */
90
+ #define SP_SLAB_NCLS 27
91
+ #define SP_SLAB_MAX 2048
92
+ /* chunk 0 is the header table (256 x 64 B); chunks 1..SP_SLAB_META hold the
93
+ bitmaps (256 x 448 B = 112 KB = 7 chunks); the rest are carved */
94
+ #define SP_SLAB_META 7
95
+ #define SP_SLAB_FIRST (1 + SP_SLAB_META)
96
+ /* fully free chunks a worker keeps resident across a full cycle per class, at least (64 KB) */
97
+ #define SP_SLAB_RESERVE 4
98
+ /* the most slots a chunk holds (16 KB / 32 B), in 64-bit words */
99
+ #define SP_SLAB_NW 8
100
+
101
+ #ifdef SP_THREADS
102
+ #define SP_SLAB_NWK SP_MAX_WORKERS
103
+ #define SP_SLAB_WID() (sp_worker_id)
104
+ static inline int sp_worker_id_shadow(void) { return sp_worker_id; }
105
+ #else
106
+ #define SP_SLAB_NWK 1
107
+ #define SP_SLAB_WID() 0
108
+ static inline int sp_worker_id_shadow(void) { return 0; }
109
+ #endif
110
+ void sp_slab_note(const void *p, int what);
111
+ extern int sp_slab_verify_on;
112
+
113
+ /* 16-byte steps to 256, then coarser: an object header is 48 bytes and a
114
+ string header 24, and most blocks are a header plus a few words, so the
115
+ fine steps are where the population is. Past SP_SLAB_MAX a block is
116
+ malloc'd as before. */
117
+ static const uint16_t sp_slab_csize[SP_SLAB_NCLS] = {
118
+ 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256,
119
+ 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048 };
120
+ static uint8_t sp_slab_cls_of[SP_SLAB_MAX / 16 + 1]; /* (need+15)/16 -> class */
121
+ /* offset-in-chunk / csize as a multiply: (off * recip) >> 32 is exact for
122
+ every off < 16 KB, checked at init */
123
+ static uint32_t sp_slab_recip[SP_SLAB_NCLS];
124
+ static uint16_t sp_slab_nslots_of[SP_SLAB_NCLS];
125
+
126
+ typedef struct sp_slab_chunk {
127
+ struct sp_slab_chunk *next_avail; /* the owner's available list, per class */
128
+ struct sp_slab_chunk *own_next, *own_prev; /* the owner's owned list, every chunk it carved */
129
+ uint32_t nslots;
130
+ uint16_t hint; /* the word the last allocation came from */
131
+ uint16_t cls, wid;
132
+ uint8_t on_avail; /* listed on the owner's available list */
133
+ uint8_t in_use; /* holds a class; 0 = empty, on the global pool */
134
+ uint8_t touched; /* has resident pages since the last release */
135
+ uint8_t _pad[64 - sizeof(void *) * 3 - 4 - 2 * 3 - 3]; /* a 64-byte header on any pointer width */
136
+ } sp_slab_chunk;
137
+ typedef char sp_slab_chunk_is_one_line[sizeof(sp_slab_chunk) == 64 ? 1 : -1];
138
+
139
+ /* one slot's worth of state per chunk: seven bitmaps of SP_SLAB_NW words */
140
+ typedef struct sp_slab_bm {
141
+ uint64_t young[2][SP_SLAB_NW];
142
+ uint64_t old[SP_SLAB_NW];
143
+ uint64_t mark[SP_SLAB_NW];
144
+ uint64_t fin[SP_SLAB_NW];
145
+ uint64_t str[SP_SLAB_NW];
146
+ uint64_t pin[SP_SLAB_NW];
147
+ } sp_slab_bm;
148
+ typedef char sp_slab_bm_is_448[sizeof(sp_slab_bm) == 448 ? 1 : -1];
149
+ typedef char sp_slab_meta_fits[SP_SLAB_NCHUNK * sizeof(sp_slab_bm) <= SP_SLAB_META * SP_SLAB_CHUNK ? 1 : -1];
150
+
151
+ typedef struct sp_slab_arena {
152
+ sp_slab_chunk ch[SP_SLAB_NCHUNK]; /* ch[0] is this table itself, never carved */
153
+ } sp_slab_arena;
154
+
155
+ typedef struct {
156
+ sp_slab_chunk *cur[SP_SLAB_NCLS];
157
+ sp_slab_chunk *avail[SP_SLAB_NCLS];
158
+ /* The allocation cache, per class: a word of the current chunk with its
159
+ free bits (wmask, not claimed yet), and per KIND (0: objects and
160
+ strings, claimed in the epoch's young word; 1: payloads, claimed in
161
+ pin) a RUN of consecutive slots cut from that word, claimed in the
162
+ bitmap all at once and handed out by a bump of rnext up to rend. The
163
+ two kinds keep their runs through each other's refills: dropping a
164
+ run on a kind switch orphaned its claimed rest until the next
165
+ collection, and a server alternating hash tables with objects of the
166
+ same class orphaned gigabytes that way. A slot claimed and not yet
167
+ handed out is in the bitmap and garbage in memory; nothing reads it,
168
+ and the collector unclaims every worker's remainders under the barrier
169
+ (sp_slab_runs_release) before it walks or sweeps anything. */
170
+ uint64_t wmask[SP_SLAB_NCLS]; /* free bits of the cached word not claimed yet */
171
+ uint64_t *wyoung[SP_SLAB_NCLS]; /* the cached word in this epoch's young bitmap */
172
+ uint64_t *wpin[SP_SLAB_NCLS]; /* the same word of the pin bitmap */
173
+ uint64_t *wfin[SP_SLAB_NCLS]; /* the same word of the fin bitmap */
174
+ uint64_t *wstr[SP_SLAB_NCLS]; /* the same word of the str bitmap */
175
+ char *wbase[SP_SLAB_NCLS]; /* slot 0 of that word */
176
+ char *rnext[2][SP_SLAB_NCLS]; /* the run's next slot, per kind */
177
+ char *rend[2][SP_SLAB_NCLS]; /* one past the run's last slot */
178
+ char *rbase[2][SP_SLAB_NCLS]; /* slot 0 of the run's word (the fin bit is computed from it) */
179
+ uint64_t *rclaim[2][SP_SLAB_NCLS]; /* the word the run was claimed in (unclaimed from there at a release) */
180
+ uint64_t *rfin[2][SP_SLAB_NCLS]; /* the run's word in the fin bitmap */
181
+ uint64_t *rstr[2][SP_SLAB_NCLS]; /* the run's word in the str bitmap */
182
+ sp_slab_chunk *owned; /* every chunk this worker carved, doubly linked */
183
+ long taken; /* chunks this worker started allocating into since the last release */
184
+ int taken_cls[SP_SLAB_NCLS]; /* the same, per class: what each class will need again next cycle */
185
+ int sweeping; /* a sweep of this worker's chunks is running (the release waits it out) */
186
+ char _pad[64 - ((19 * SP_SLAB_NCLS * sizeof(void *) + SP_SLAB_NCLS * (sizeof(uint64_t) + sizeof(int)) + sizeof(void *) + sizeof(long) + sizeof(int)) % 64)];
187
+ } sp_slab_worker;
188
+
189
+ static sp_slab_worker sp_slab_wk[SP_SLAB_NWK];
190
+ uintptr_t sp_slab_base = 0; /* the reservation (read inline by sp_slab_owns) */
191
+ static uintptr_t sp_slab_brk = 0; /* how much of it is in use */
192
+ size_t sp_slab_cap = 0;
193
+ static sp_slab_chunk *sp_slab_empty = NULL; /* chunks holding no class */
194
+ int sp_slab_on = -1; /* decided once from the environment */
195
+ /* The parity of the epoch new allocations join. The collector flips it under
196
+ the barrier (sp_slab_epoch_flip), where no mutator runs. */
197
+ unsigned sp_slab_epoch = 0;
198
+ #ifdef SP_THREADS
199
+ #include <pthread.h>
200
+ static pthread_mutex_t sp_slab_lock = PTHREAD_MUTEX_INITIALIZER;
201
+ #define SP_SLAB_LOCK() pthread_mutex_lock(&sp_slab_lock)
202
+ #define SP_SLAB_UNLOCK() pthread_mutex_unlock(&sp_slab_lock)
203
+ #else
204
+ #define SP_SLAB_LOCK() ((void)0)
205
+ #define SP_SLAB_UNLOCK() ((void)0)
206
+ #endif
207
+ /* The bitmap operations. Threaded, every one is atomic: a sweeper clears
208
+ bits of a chunk its owner allocates from, a pool hands a header back on
209
+ another thread, a finalizer frees a payload from anywhere. Single-
210
+ threaded nothing runs beside the program, and a locked instruction per
211
+ allocation and per death was a fifth of an allocation-bound benchmark. */
212
+ #ifdef SP_THREADS
213
+ /* A bitmap word as the atomics see it: 8-aligned. On 32-bit x86 the ABI
214
+ aligns a uint64_t in a struct to 4, and clang then turns a 64-bit atomic
215
+ into a libatomic call (__atomic_load_8), which nothing links; gcc inlines
216
+ it either way. The words are 8-aligned in fact: the bitmaps sit in a page-
217
+ aligned arena, in 448-byte records of nothing but uint64_t. */
218
+ #if SP_ATOMICS_BUILTIN
219
+ typedef uint64_t sp_bm_aword __attribute__((aligned(8)));
220
+ #else
221
+ typedef uint64_t sp_bm_aword;
222
+ #endif
223
+ static inline uint64_t bm_or(uint64_t *p, uint64_t v) { return SP_ATOMIC_FETCH_OR((sp_bm_aword *)p, v, __ATOMIC_ACQ_REL); }
224
+ static inline uint64_t bm_and(uint64_t *p, uint64_t v) { return SP_ATOMIC_FETCH_AND((sp_bm_aword *)p, v, __ATOMIC_ACQ_REL); }
225
+ static inline uint64_t bm_load(const uint64_t *p) { return SP_ATOMIC_LOAD((const sp_bm_aword *)p, __ATOMIC_ACQUIRE); }
226
+ static inline void bm_store(uint64_t *p, uint64_t v) { SP_ATOMIC_STORE((sp_bm_aword *)p, v, __ATOMIC_RELEASE); }
227
+ #endif
228
+ /* The claim of an object slot writes the current epoch's young word, which
229
+ has one writer: the chunk's owner, on its own thread. Nothing else sets or
230
+ clears a bit there -- the sweep works the epoch that closed, the mark
231
+ writes the old and mark words, the pools that used to hand a slot back
232
+ (sp_slab_relive) are bypassed with the slab on -- so the claim is a plain
233
+ read-modify-write on both builds. A payload's claim word is pin, which a
234
+ freeze or a sweep on another thread writes too, and stays atomic. */
235
+ #ifdef SP_THREADS
236
+ /* atomic load and store, not a locked instruction: the sweeper reads this
237
+ word (a parked header is one in no generation), and the model wants the
238
+ two sides atomic; with one writer the read-modify-write loses nothing.
239
+ Aging's carry into the current epoch would be a second writer, which is
240
+ one more reason it stays off with the slab on. */
241
+ static inline uint64_t bm_or_owned(uint64_t *p, uint64_t v) { uint64_t o = SP_ATOMIC_LOAD((sp_bm_aword *)p, __ATOMIC_RELAXED); SP_ATOMIC_STORE((sp_bm_aword *)p, o | v, __ATOMIC_RELEASE); return o; }
242
+ #else
243
+ static inline uint64_t bm_or_owned(uint64_t *p, uint64_t v) { uint64_t o = *p; *p = o | v; return o; }
244
+ #endif
245
+ #ifndef SP_THREADS
246
+ static inline uint64_t bm_or(uint64_t *p, uint64_t v) { uint64_t o = *p; *p = o | v; return o; }
247
+ static inline uint64_t bm_and(uint64_t *p, uint64_t v) { uint64_t o = *p; *p = o & v; return o; }
248
+ static inline uint64_t bm_load(const uint64_t *p) { return *p; }
249
+ static inline void bm_store(uint64_t *p, uint64_t v) { *p = v; }
250
+ #endif
251
+
252
+ /* One reservation, aligned to the arena size so a slot's arena and chunk are
253
+ address arithmetic. Mapped twice the size and trimmed to the aligned
254
+ middle; the pages are untouched until a chunk is carved, so the size costs
255
+ nothing but address space. */
256
+ static void sp_slab_reserve(void) {
257
+ #ifdef __wasi__
258
+ /* wasm has one linear memory that only grows: nothing to reserve, trim or
259
+ give back. 64 MB is taken as one aligned block (an engine commits its
260
+ pages as they are touched); past it every block is a malloc. */
261
+ { size_t want = (size_t)64 << 20; void *m = NULL;
262
+ if (posix_memalign(&m, SP_SLAB_ARENA, want) == 0) { sp_slab_base = sp_slab_brk = (uintptr_t)m; sp_slab_cap = want; }
263
+ else sp_slab_on = 0;
264
+ return; }
265
+ #endif
266
+ /* 16 GB of address space on a 64-bit host; a 32-bit one has 2 to 3 GB
267
+ for everything, so it asks for 512 MB and settles for what it gets
268
+ (16 << 30 is 0 in its size_t, which asked for nothing and turned the
269
+ slab off) */
270
+ size_t want = sizeof(void *) >= 8 ? (size_t)16 << 30 : (size_t)512 << 20;
271
+ while (want >= ((size_t)64 << 20)) {
272
+ size_t len = want + SP_SLAB_ARENA;
273
+ void *m = mmap(NULL, len, PROT_READ | PROT_WRITE,
274
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
275
+ if (m != MAP_FAILED) {
276
+ uintptr_t lo = (uintptr_t)m, base = (lo + SP_SLAB_ARENA - 1) & ~(SP_SLAB_ARENA - 1);
277
+ if (base > lo) munmap((void *)lo, base - lo);
278
+ uintptr_t hi = lo + len, top = base + want;
279
+ if (hi > top) munmap((void *)top, hi - top);
280
+ sp_slab_base = sp_slab_brk = base;
281
+ sp_slab_cap = want;
282
+ /* Keep the untouched reservation out of a core dump: the kernel writes a
283
+ mapping's every page, zero or not, and 16 GB of them took a crashing
284
+ program 20 s to die (a minute and more on CI). The arenas actually
285
+ carved are put back in, one at a time, as they are handed out. */
286
+ #ifdef MADV_DONTDUMP
287
+ madvise((void *)base, want, MADV_DONTDUMP);
288
+ #endif
289
+ return;
290
+ }
291
+ want >>= 2;
292
+ }
293
+ sp_slab_on = 0; /* no range at all: every block is a malloc */
294
+ }
295
+
296
+ /* Is jemalloc the process's malloc? Its thread caches did what the free
297
+ list did, and measured beside them the free-list slab was a loss
298
+ (campfire's room page 2,680 req/s with jemalloc alone against 2,470 with
299
+ the slab on top: the per-free atomics landed on caches that were already
300
+ free-list pops), so the slab used to default off when jemalloc was
301
+ present. The bitmap sweep changed the comparison: with it the slab under
302
+ jemalloc answers the same requests a second as jemalloc alone (5,570
303
+ against 5,560) on 17% less CPU and 13% less memory, the part jemalloc's
304
+ caches cannot do being the sweep that never touches the dead. So the slab
305
+ is on regardless now; the detection stays for the report and for
306
+ SPINEL_GC_SLAB=0. Detection is jemalloc's own mallctl symbol, resolved by
307
+ the dynamic linker from a linked or preloaded jemalloc; nothing else
308
+ defines it. */
309
+ #if defined(__APPLE__)
310
+ #include <dlfcn.h>
311
+ static int sp_slab_jemalloc_present(void) { return dlsym(RTLD_DEFAULT, "mallctl") != NULL; }
312
+ #elif SP_HAVE_WEAK
313
+ extern int mallctl(const char *, void *, size_t *, void *, size_t) SP_WEAK;
314
+ static int sp_slab_jemalloc_present(void) { return mallctl != NULL; }
315
+ #else
316
+ /* no weak symbols (sp_compat.h): jemalloc goes unreported */
317
+ static int sp_slab_jemalloc_present(void) { return 0; }
318
+ #endif
319
+
320
+ #if defined(__GLIBC__)
321
+ #include <malloc.h>
322
+ #endif
323
+ static void sp_slab_init(void) {
324
+ const char *e = getenv("SPINEL_GC_SLAB");
325
+ #if defined(__GLIBC__)
326
+ /* The blocks past the slab's largest class are malloc's, and glibc trims
327
+ the top of its heap whenever a free leaves 128 KB there: a program
328
+ churning arrays of a few tens of kilobytes had the heap's top shrink
329
+ and grow again on every one (900 brk calls and 6,000 page faults on a
330
+ 20 ms benchmark). The collector returns memory itself, with a
331
+ malloc_trim after every full cycle, so the automatic trim can wait
332
+ for a larger top. Setting any of these switches off glibc's dynamic
333
+ mmap threshold (which climbs as large blocks are freed), so it is set
334
+ here too: left at its 128 KB start, every page a server rendered
335
+ (400 KB) was an mmap and a munmap, 140 page faults a request, and a
336
+ test building a megabyte string 4,800 times faulted 760,000 pages.
337
+ A megabyte of top and a 4 MB mmap threshold: a server's arenas keep
338
+ at most a megabyte of free top each (32 MB kept 100 MB more resident
339
+ across 33 workers, and 4 MB 60 MB more), and its 400 KB pages stay
340
+ on the heap. */
341
+ mallopt(M_TRIM_THRESHOLD, 1 << 20);
342
+ mallopt(M_TOP_PAD, 1 << 20);
343
+ mallopt(M_MMAP_THRESHOLD, 4 << 20);
344
+ #endif
345
+ int c = 0;
346
+ for (unsigned i = 0; i <= SP_SLAB_MAX / 16; i++) {
347
+ while (c < SP_SLAB_NCLS - 1 && sp_slab_csize[c] < i * 16) c++;
348
+ sp_slab_cls_of[i] = (uint8_t)c;
349
+ }
350
+ /* sp_gc_alloc_sized (sp_alloc.h) and the fronts it picks take a block of
351
+ 256 bytes or less to be in the class of its size rounded up to 16, the
352
+ first of them 32: the table has to say the same */
353
+ for (unsigned i = 0; i <= 16; i++) {
354
+ unsigned k = i <= 2 ? 0 : i - 2;
355
+ if (sp_slab_cls_of[i] != k || sp_slab_csize[k] != 32 + 16 * k) { fputs("spinel: the slab's size classes moved; sp_gc_alloc_sized follows them by arithmetic\n", stderr); abort(); }
356
+ }
357
+ for (int k = 0; k < SP_SLAB_NCLS; k++) {
358
+ unsigned cs = sp_slab_csize[k];
359
+ sp_slab_nslots_of[k] = (uint16_t)(SP_SLAB_CHUNK / cs);
360
+ /* the smallest reciprocal that divides every in-chunk offset exactly */
361
+ uint32_t r = (uint32_t)((((uint64_t)1 << 32) + cs - 1) / cs);
362
+ for (unsigned off = 0; off < SP_SLAB_CHUNK; off += 16)
363
+ if ((unsigned)(((uint64_t)off * r) >> 32) != off / cs) { r = 0; break; }
364
+ sp_slab_recip[k] = r; /* 0: divide (never, checked here) */
365
+ }
366
+ if (e && *e) sp_slab_on = (*e != '0');
367
+ else sp_slab_on = 1;
368
+ (void)sp_slab_jemalloc_present;
369
+ if (sp_slab_on) sp_slab_reserve();
370
+ }
371
+
372
+ static inline sp_slab_chunk *sp_slab_chunk_of(const void *p) {
373
+ uintptr_t a = (uintptr_t)p;
374
+ sp_slab_arena *ar = (sp_slab_arena *)(a & ~(SP_SLAB_ARENA - 1));
375
+ return &ar->ch[(a & (SP_SLAB_ARENA - 1)) / SP_SLAB_CHUNK];
376
+ }
377
+ static inline char *sp_slab_chunk_base(sp_slab_chunk *ch) {
378
+ sp_slab_arena *ar = (sp_slab_arena *)((uintptr_t)ch & ~(SP_SLAB_ARENA - 1));
379
+ return (char *)ar + (size_t)(ch - ar->ch) * SP_SLAB_CHUNK;
380
+ }
381
+ static inline sp_slab_bm *sp_slab_bm_of(sp_slab_chunk *ch) {
382
+ sp_slab_arena *ar = (sp_slab_arena *)((uintptr_t)ch & ~(SP_SLAB_ARENA - 1));
383
+ return (sp_slab_bm *)((char *)ar + SP_SLAB_CHUNK) + (ch - ar->ch);
384
+ }
385
+ /* a pointer anywhere inside a slot names it: the chunk, its bitmaps, and the
386
+ slot's word and bit */
387
+ typedef struct { sp_slab_chunk *ch; sp_slab_bm *bm; unsigned w; uint64_t bit; unsigned idx; } sp_slab_loc;
388
+ static inline void sp_slab_locate(const void *p, sp_slab_loc *l) {
389
+ uintptr_t a = (uintptr_t)p;
390
+ l->ch = sp_slab_chunk_of(p);
391
+ l->bm = sp_slab_bm_of(l->ch);
392
+ unsigned off = (unsigned)(a & (SP_SLAB_CHUNK - 1));
393
+ unsigned cls = l->ch->cls;
394
+ unsigned idx = sp_slab_recip[cls] ? (unsigned)(((uint64_t)off * sp_slab_recip[cls]) >> 32) : off / sp_slab_csize[cls];
395
+ l->idx = idx;
396
+ l->w = idx >> 6;
397
+ l->bit = (uint64_t)1 << (idx & 63);
398
+ }
399
+
400
+ /* The next arena of the reservation: its header table and bitmaps are zero
401
+ already (an untouched page reads as zero), so only the empty list needs
402
+ writing. */
403
+ static int sp_slab_next_arena(void) {
404
+ if (sp_slab_brk + SP_SLAB_ARENA > sp_slab_base + sp_slab_cap) return 0;
405
+ sp_slab_arena *ar = (sp_slab_arena *)sp_slab_brk;
406
+ sp_slab_brk += SP_SLAB_ARENA;
407
+ #ifdef MADV_DODUMP
408
+ madvise((void *)ar, SP_SLAB_ARENA, MADV_DODUMP); /* this arena holds objects: dump it */
409
+ #endif
410
+ #ifdef MADV_HUGEPAGE
411
+ /* An arena is 4 MB, aligned: two huge pages where the kernel offers them
412
+ (transparent_hugepage=madvise, the common setting). The program then
413
+ faults an arena in twice instead of a thousand times, and walks it on
414
+ two TLB entries; a list benchmark spent a third of its time in those
415
+ faults. */
416
+ { static int huge = -1;
417
+ if (huge < 0) { const char *e = getenv("SPINEL_SLAB_HUGE"); huge = e ? atoi(e) : 1; } /* SPINEL_SLAB_HUGE=0 turns it off */
418
+ if (huge) madvise((void *)ar, SP_SLAB_ARENA, MADV_HUGEPAGE); }
419
+ #endif
420
+ for (int i = (int)SP_SLAB_NCHUNK - 1; i >= SP_SLAB_FIRST; i--) { /* pops then ascend in address */
421
+ ar->ch[i].next_avail = sp_slab_empty;
422
+ sp_slab_empty = &ar->ch[i];
423
+ }
424
+ return 1;
425
+ }
426
+
427
+ /* A free slot of the chunk, claimed into the current epoch's young word
428
+ (or, for a payload, into old and pin). The owner is the only claimer, so
429
+ the search reads the three generation words plainly: a bit a sweeper
430
+ clears meanwhile is a slot it may find next time, and no bit it reads as
431
+ clear can be set by anyone but itself. The claim is an atomic or all the
432
+ same, because a sweep carrying an aged survivor into the current epoch
433
+ writes the same word. NULL when the chunk has no free slot. */
434
+ /* The lowest run of consecutive free bits of the cached word, claimed
435
+ whole for one kind and installed as its bump range. 0 when the word
436
+ has none left. */
437
+ static int sp_slab_run(sp_slab_worker *wk, int cls, int payload) {
438
+ uint64_t m = wk->wmask[cls];
439
+ unsigned csize = sp_slab_csize[cls];
440
+ uint64_t *claim = payload ? wk->wpin[cls] : wk->wyoung[cls];
441
+ while (m) {
442
+ uint64_t low = m & -m;
443
+ uint64_t run = m & ~(m + low); /* the carry from the lowest bit stops at the run's end */
444
+ wk->wmask[cls] = m & ~run;
445
+ uint64_t was = payload ? bm_or(claim, run) : bm_or_owned(claim, run);
446
+ if (SP_EXPECT(was & run, 0)) {
447
+ /* a bit of it was taken meanwhile (a pin on a slot the sweep is
448
+ finalizing: only the pin word has other writers); keep what came
449
+ before the first such bit, unclaim the rest */
450
+ uint64_t bad = was & run;
451
+ uint64_t keep = run & ((bad & -bad) - 1);
452
+ bm_and(claim, ~(run & ~keep));
453
+ run = keep;
454
+ if (!run) { m = wk->wmask[cls]; continue; }
455
+ }
456
+ unsigned i = (unsigned)SP_CTZ64(run), n = (unsigned)SP_POPCOUNT64(run);
457
+ wk->rnext[payload][cls] = wk->wbase[cls] + (size_t)i * csize;
458
+ wk->rend[payload][cls] = wk->rnext[payload][cls] + (size_t)n * csize;
459
+ wk->rbase[payload][cls] = wk->wbase[cls];
460
+ wk->rclaim[payload][cls] = claim;
461
+ wk->rfin[payload][cls] = wk->wfin[cls];
462
+ wk->rstr[payload][cls] = wk->wstr[cls];
463
+ return 1;
464
+ }
465
+ return 0;
466
+ }
467
+ static inline void *sp_slab_take(sp_slab_worker *wk, int cls, sp_slab_chunk *ch, unsigned csize, int payload) {
468
+ sp_slab_bm *bm = sp_slab_bm_of(ch);
469
+ unsigned nw = (ch->nslots + 63) >> 6;
470
+ unsigned w = ch->hint;
471
+ unsigned e = sp_slab_epoch & 1;
472
+ wk->wmask[cls] = 0;
473
+ for (unsigned n = 0; n < nw; n++, w = (w + 1 == nw) ? 0 : w + 1) {
474
+ uint64_t tail = (w == nw - 1 && (ch->nslots & 63)) ? ~(uint64_t)0 << (ch->nslots & 63) : 0;
475
+ /* The four words are not one snapshot. The one writer that moves a
476
+ slot from used to used on another thread is a pool handing a parked
477
+ header back (sp_slab_relive): it sets the young bit and THEN clears
478
+ the pin, so reading pin first and young after cannot see the slot
479
+ in neither -- the pin is either still set, or young already is. A
480
+ free (sp_slab_free) only ever turns used into free. The claim's own
481
+ result is still checked (sp_slab_run). */
482
+ uint64_t used = bm_load(&bm->old[w]) | bm_load(&bm->pin[w]);
483
+ used |= bm_load(&bm->young[0][w]) | bm_load(&bm->young[1][w]) | tail;
484
+ if (used == ~(uint64_t)0) continue;
485
+ ch->hint = (uint16_t)w;
486
+ /* the word, for the fast path: its claim word (this epoch's parity,
487
+ or pin for a payload), its free bits, its first slot */
488
+ wk->wmask[cls] = ~used;
489
+ wk->wyoung[cls] = &bm->young[e][w];
490
+ wk->wpin[cls] = &bm->pin[w];
491
+ wk->wfin[cls] = &bm->fin[w];
492
+ wk->wstr[cls] = &bm->str[w];
493
+ wk->wbase[cls] = sp_slab_chunk_base(ch) + (size_t)(w << 6) * csize;
494
+ if (sp_slab_run(wk, cls, payload)) {
495
+ char *p = wk->rnext[payload][cls];
496
+ wk->rnext[payload][cls] = p + csize;
497
+ if (sp_slab_verify_on) sp_slab_note(p, 40 + (int)e + (payload ? 2 : 0));
498
+ return p;
499
+ }
500
+ }
501
+ return NULL;
502
+ }
503
+
504
+ /* The slow path: the worker's current chunk for this class has no slot. NULL
505
+ when the reservation is exhausted, and the caller mallocs. */
506
+ static SP_NOINLINE void *sp_slab_refill(sp_slab_worker *wk, int cls, unsigned csize, int payload) {
507
+ sp_slab_chunk *ch;
508
+ void *p = NULL;
509
+ for (;;) {
510
+ #ifdef SP_THREADS
511
+ /* pushed by a sweeper beside the running program (a compare-and-swap),
512
+ popped only by the owner: the same exchange, with no ABA to lose to */
513
+ ch = SP_ATOMIC_LOAD(&wk->avail[cls], __ATOMIC_ACQUIRE);
514
+ while (ch) {
515
+ sp_slab_chunk *nx = ch->next_avail;
516
+ if (SP_ATOMIC_CAS(&wk->avail[cls], &ch, nx, 1, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) break;
517
+ }
518
+ #else
519
+ ch = wk->avail[cls];
520
+ if (ch) wk->avail[cls] = ch->next_avail;
521
+ #endif
522
+ if (!ch) break;
523
+ ch->next_avail = NULL;
524
+ SP_ATOMIC_STORE(&ch->on_avail, 0, __ATOMIC_RELEASE);
525
+ /* A sweeper decides "not the current chunk" from a read that races with
526
+ this function installing exactly that chunk, so the chunk we are
527
+ allocating from can land on the list too. It has already been used up
528
+ from the front by then: take a slot if one is left, else drop it and
529
+ move on. */
530
+ ch->hint = 0;
531
+ p = sp_slab_take(wk, cls, ch, csize, payload);
532
+ if (p) break;
533
+ }
534
+ if (!ch) {
535
+ SP_SLAB_LOCK();
536
+ if (!sp_slab_empty && !sp_slab_next_arena()) { SP_SLAB_UNLOCK(); return NULL; }
537
+ ch = sp_slab_empty;
538
+ sp_slab_empty = ch->next_avail;
539
+ SP_SLAB_UNLOCK();
540
+ ch->next_avail = NULL;
541
+ ch->nslots = sp_slab_nslots_of[cls];
542
+ ch->hint = 0;
543
+ ch->cls = (uint16_t)cls;
544
+ ch->wid = (uint16_t)(wk - sp_slab_wk);
545
+ ch->on_avail = 0;
546
+ ch->in_use = 1;
547
+ /* onto the owner's owned list: the sweep of this worker walks it */
548
+ ch->own_prev = NULL;
549
+ ch->own_next = wk->owned;
550
+ if (wk->owned) wk->owned->own_prev = ch;
551
+ SP_ATOMIC_STORE(&wk->owned, ch, __ATOMIC_RELEASE);
552
+ p = sp_slab_take(wk, cls, ch, csize, payload);
553
+ }
554
+ SP_ATOMIC_STORE(&wk->cur[cls], ch, __ATOMIC_RELEASE);
555
+ wk->taken++; wk->taken_cls[cls]++;
556
+ ch->touched = 1;
557
+ return p;
558
+ }
559
+
560
+ /* Zero a block of a class's size: 16 bytes a store, the count from the
561
+ size, entered at the store that leaves exactly the block. Sizes above
562
+ 256 go to memset, which earns its call there. */
563
+ typedef struct { uint64_t a, b; } sp_slab_u16;
564
+ static SP_INLINE void sp_slab_zero(void *p, unsigned csize) {
565
+ sp_slab_u16 *q = (sp_slab_u16 *)p;
566
+ const sp_slab_u16 z = { 0, 0 };
567
+ switch (csize >> 4) {
568
+ case 16: q[15] = z; /* fallthrough */
569
+ case 15: q[14] = z; /* fallthrough */
570
+ case 14: q[13] = z; /* fallthrough */
571
+ case 13: q[12] = z; /* fallthrough */
572
+ case 12: q[11] = z; /* fallthrough */
573
+ case 11: q[10] = z; /* fallthrough */
574
+ case 10: q[9] = z; /* fallthrough */
575
+ case 9: q[8] = z; /* fallthrough */
576
+ case 8: q[7] = z; /* fallthrough */
577
+ case 7: q[6] = z; /* fallthrough */
578
+ case 6: q[5] = z; /* fallthrough */
579
+ case 5: q[4] = z; /* fallthrough */
580
+ case 4: q[3] = z; /* fallthrough */
581
+ case 3: q[2] = z; /* fallthrough */
582
+ case 2: q[1] = z; /* fallthrough */
583
+ case 1: q[0] = z; return;
584
+ default: memset(p, 0, csize); return;
585
+ }
586
+ }
587
+ /* the same for a block known to be 256 bytes or less: no call on any path,
588
+ which is what lets sp_gc_alloc's front run without a frame */
589
+ static SP_INLINE void sp_slab_zero_small(void *p, unsigned csize) {
590
+ sp_slab_u16 *q = (sp_slab_u16 *)p;
591
+ const sp_slab_u16 z = { 0, 0 };
592
+ switch (csize >> 4) {
593
+ case 16: q[15] = z; /* fallthrough */
594
+ case 15: q[14] = z; /* fallthrough */
595
+ case 14: q[13] = z; /* fallthrough */
596
+ case 13: q[12] = z; /* fallthrough */
597
+ case 12: q[11] = z; /* fallthrough */
598
+ case 11: q[10] = z; /* fallthrough */
599
+ case 10: q[9] = z; /* fallthrough */
600
+ case 9: q[8] = z; /* fallthrough */
601
+ case 8: q[7] = z; /* fallthrough */
602
+ case 7: q[6] = z; /* fallthrough */
603
+ case 6: q[5] = z; /* fallthrough */
604
+ case 5: q[4] = z; /* fallthrough */
605
+ case 4: q[3] = z; /* fallthrough */
606
+ case 3: q[2] = z; /* fallthrough */
607
+ case 2: q[1] = z; /* fallthrough */
608
+ case 1: q[0] = z; return;
609
+ default: SP_UNREACHABLE();
610
+ }
611
+ }
612
+ /* The slow half of an allocation: the cached word is empty, so search the
613
+ current chunk, else refill; past the largest class, or with the slab
614
+ off, malloc. */
615
+ static SP_NOINLINE void *sp_slab_alloc_slow(size_t need, int payload, int zero) {
616
+ if (SP_EXPECT(sp_slab_on < 0, 0)) sp_slab_init();
617
+ if (sp_slab_on && need <= SP_SLAB_MAX) {
618
+ int cls = sp_slab_cls_of[(need + 15) >> 4];
619
+ unsigned csize = sp_slab_csize[cls];
620
+ sp_slab_worker *wk = &sp_slab_wk[SP_SLAB_WID()];
621
+ sp_slab_chunk *ch = wk->cur[cls];
622
+ void *p = NULL;
623
+ /* the next run of the cached word, if the word is of this kind */
624
+ if (sp_slab_run(wk, cls, payload)) {
625
+ p = wk->rnext[payload][cls]; wk->rnext[payload][cls] = (char *)p + csize;
626
+ if (sp_slab_verify_on) sp_slab_note(p, 40 + (int)(sp_slab_epoch & 1) + (payload ? 2 : 0));
627
+ }
628
+ if (!p && ch) p = sp_slab_take(wk, cls, ch, csize, payload);
629
+ if (!p) p = sp_slab_refill(wk, cls, csize, payload);
630
+ if (p) {
631
+ if (zero) memset(p, 0, need);
632
+ return p;
633
+ }
634
+ }
635
+ void *p = zero ? calloc(1, need) : malloc(need);
636
+ if (!p) sp_oom_die();
637
+ return p;
638
+ }
639
+ /* The fast half, inlined into the entry points: the next slot of the
640
+ claimed run, a bump. The claim was made for the whole run when it was
641
+ cut (sp_slab_run), so the bitmap is not touched here; an object with a
642
+ finalizer sets its fin bit, in the word cached beside the claim word. */
643
+ static SP_INLINE void *sp_slab_alloc_in(size_t need, int payload, int fin, int zero) {
644
+ if (SP_EXPECT(sp_slab_on > 0 && need <= SP_SLAB_MAX, 1)) {
645
+ int cls = sp_slab_cls_of[(need + 15) >> 4];
646
+ sp_slab_worker *wk = &sp_slab_wk[SP_SLAB_WID()];
647
+ char *p = wk->rnext[payload][cls];
648
+ if (SP_EXPECT(p != wk->rend[payload][cls], 1)) {
649
+ unsigned csize = sp_slab_csize[cls];
650
+ wk->rnext[payload][cls] = p + csize;
651
+ if (fin) {
652
+ unsigned i = (unsigned)(((uint64_t)(size_t)(p - wk->rbase[payload][cls]) * sp_slab_recip[cls]) >> 32);
653
+ bm_or(wk->rfin[payload][cls], (uint64_t)1 << i);
654
+ }
655
+ /* zeroed slot by slot, the class size in 16-byte stores entered
656
+ mid-run (a memset call, or a counted loop, costs more than the
657
+ bytes at these sizes). Zeroing a whole free word in one memset at
658
+ the refill measured worse: the string stores miss the cache on
659
+ purpose, and the demand fills quintupled. */
660
+ if (zero) sp_slab_zero(p, csize);
661
+ if (SP_EXPECT(sp_slab_verify_on, 0)) sp_slab_note(p, 40 + (int)(sp_slab_epoch & 1) + (payload ? 2 : 0));
662
+ return p;
663
+ }
664
+ }
665
+ void *p = sp_slab_alloc_slow(need, payload, zero);
666
+ if (fin && sp_slab_on > 0 && sp_slab_owns(p)) {
667
+ sp_slab_loc l; sp_slab_locate(p, &l);
668
+ bm_or(&l.bm->fin[l.w], l.bit);
669
+ }
670
+ return p;
671
+ }
672
+ /* An object with its header written, in one call: the fast path above, the
673
+ zeroing, and the three header fields the collector reads (the flags are
674
+ zero already). What sp_gc_alloc used to do in two calls and a memset. */
675
+ void *sp_slab_alloc_obj(size_t need, void (*fin)(void *), void (*scn)(void *)) {
676
+ void *p = sp_slab_alloc_in(need, 0, fin != NULL, 1);
677
+ sp_gc_hdr *h = (sp_gc_hdr *)p;
678
+ h->finalize = fin; h->scan = scn; h->size = need;
679
+ if (SP_EXPECT(sp_slab_verify_on, 0)) sp_slab_note(p, 1);
680
+ return p;
681
+ }
682
+ /* The object allocation of the program, moved here from lib/sp_alloc.c so
683
+ that the claim above, the header, and the collector's bookkeeping (the
684
+ trigger, the byte count, the report) are one frame: what was two calls
685
+ deep is inlined into one. The semantics are sp_alloc.c's. */
686
+ /* The lean front of sp_gc_alloc: no call on its path, so the compiler
687
+ keeps it in caller-saved registers with no frame to build (every other
688
+ case tail-calls the full form below). It runs when the slab is on and
689
+ nothing wants a look at each allocation (the verifier, the report, the
690
+ stress switch not yet read): sp_gc_alloc_fast_ok, recomputed by the full
691
+ form and dropped by whoever turns one of those on. */
692
+ int sp_gc_alloc_fast_ok = 0;
693
+ static void *sp_gc_alloc_full(size_t sz, void (*fin)(void *), void (*scn)(void *));
694
+ void *sp_gc_alloc(size_t sz, void (*fin)(void *), void (*scn)(void *)) {
695
+ size_t need = sizeof(sp_gc_hdr) + sz;
696
+ if (SP_EXPECT(!sp_gc_alloc_fast_ok || need > 256, 0)) return sp_gc_alloc_full(sz, fin, scn);
697
+ if (SP_EXPECT(SP_GC_CTR_GET(sp_gc_bytes) > SP_GC_CTR_GET(sp_gc_threshold), 0)) return sp_gc_alloc_full(sz, fin, scn);
698
+ int cls = sp_slab_cls_of[(need + 15) >> 4];
699
+ sp_slab_worker *wk = &sp_slab_wk[SP_SLAB_WID()];
700
+ char *p = wk->rnext[0][cls];
701
+ if (SP_EXPECT(p == wk->rend[0][cls], 0)) return sp_gc_alloc_full(sz, fin, scn);
702
+ unsigned csize = sp_slab_csize[cls];
703
+ wk->rnext[0][cls] = p + csize;
704
+ if (fin) {
705
+ unsigned i = (unsigned)(((uint64_t)(size_t)(p - wk->rbase[0][cls]) * sp_slab_recip[cls]) >> 32);
706
+ bm_or(wk->rfin[0][cls], (uint64_t)1 << i);
707
+ }
708
+ sp_slab_zero_small(p, csize);
709
+ sp_gc_hdr *h = (sp_gc_hdr *)p;
710
+ h->finalize = fin; h->scan = scn; h->size = need;
711
+ sp_gc_bytes_add(need);
712
+ return p + sizeof(sp_gc_hdr);
713
+ }
714
+ /* The lean front once more, for a caller that knows the size at compile time
715
+ and has no finalizer, which a constructor does: sizeof its class. One
716
+ function per size class (sp_gc_alloc_sized, sp_alloc.h, picks it), so the
717
+ class and the slot size are constants where sp_gc_alloc reads two tables,
718
+ the finalizer test goes, and the zeroing is that many stores with no jump
719
+ into them. Whatever sp_gc_alloc hands to the full form, this does too. */
720
+ static SP_INLINE void *sp_gc_alloc_lean(size_t need, void (*scn)(void *), unsigned csize) {
721
+ int cls = (int)(csize - 32) >> 4;
722
+ if (SP_EXPECT(!sp_gc_alloc_fast_ok, 0)) return sp_gc_alloc_full(need - sizeof(sp_gc_hdr), NULL, scn);
723
+ if (SP_EXPECT(SP_GC_CTR_GET(sp_gc_bytes) > SP_GC_CTR_GET(sp_gc_threshold), 0)) return sp_gc_alloc_full(need - sizeof(sp_gc_hdr), NULL, scn);
724
+ sp_slab_worker *wk = &sp_slab_wk[SP_SLAB_WID()];
725
+ char *p = wk->rnext[0][cls];
726
+ if (SP_EXPECT(p == wk->rend[0][cls], 0)) return sp_gc_alloc_full(need - sizeof(sp_gc_hdr), NULL, scn);
727
+ wk->rnext[0][cls] = p + csize;
728
+ sp_slab_zero_small(p, csize);
729
+ sp_gc_hdr *h = (sp_gc_hdr *)p;
730
+ h->scan = scn; h->size = need;
731
+ sp_gc_bytes_add(need);
732
+ return p + sizeof(sp_gc_hdr);
733
+ }
734
+ void *sp_gc_alloc_32(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 32); }
735
+ void *sp_gc_alloc_48(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 48); }
736
+ void *sp_gc_alloc_64(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 64); }
737
+ void *sp_gc_alloc_80(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 80); }
738
+ void *sp_gc_alloc_96(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 96); }
739
+ void *sp_gc_alloc_112(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 112); }
740
+ void *sp_gc_alloc_128(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 128); }
741
+ void *sp_gc_alloc_144(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 144); }
742
+ void *sp_gc_alloc_160(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 160); }
743
+ void *sp_gc_alloc_176(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 176); }
744
+ void *sp_gc_alloc_192(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 192); }
745
+ void *sp_gc_alloc_208(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 208); }
746
+ void *sp_gc_alloc_224(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 224); }
747
+ void *sp_gc_alloc_240(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 240); }
748
+ void *sp_gc_alloc_256(size_t need, void (*scn)(void *)) { return sp_gc_alloc_lean(need, scn, 256); }
749
+ static SP_NOINLINE void *sp_gc_alloc_full(size_t sz, void (*fin)(void *), void (*scn)(void *)) {
750
+ #ifdef SP_THREADS
751
+ /* Lock-free fast path: the list push is a CAS (SP_GC_HEAP_PUSH) and the live-
752
+ byte counter is atomic, so concurrent allocations need no mutex -- the old
753
+ sp_heap_lock only serialized them and the string sweep, and both string
754
+ allocation (per-worker heap) and every collection (stop-the-world) have
755
+ moved off it. Removals happen only under stop-the-world with every mutator
756
+ parked, so a push never races the sweep. The stress-threshold one-shot is
757
+ idempotent under a race. */
758
+ if (!sp_gc_stress_checked) { sp_gc_stress_checked = 1; const char *e = getenv("SPINEL_GC_STRESS"); if (e && *e && *e != '0') { SP_GC_CTR_SET(sp_gc_threshold, 2048); sp_gc_threshold_init = 2048; sp_gc_stress_pin = 1; } }
759
+ if (SP_GC_CTR_GET(sp_gc_bytes) > SP_GC_CTR_GET(sp_gc_threshold)) sp_stw_collect();
760
+ size_t need = sizeof(sp_gc_hdr) + sz;
761
+ sp_gc_hdr *h = (sp_gc_hdr *)sp_slab_alloc_in(need, 0, fin != NULL, 1);
762
+ h->finalize = fin; h->scan = scn; h->size = need;
763
+ if (SP_EXPECT(sp_alloc_report_on, 0)) sp_alloc_report_count((void *)scn, sz);
764
+ SP_GC_HEAP_PUSH(h); sp_gc_bytes_add(need);
765
+ if (SP_EXPECT(sp_slab_verify_on, 0)) sp_slab_note(h, 1);
766
+ sp_gc_alloc_fast_ok = sp_gc_stress_checked && sp_slab_on > 0 && !sp_slab_verify_on && !sp_alloc_report_on;
767
+ return (char *)h + sizeof(sp_gc_hdr);
768
+ #else
769
+ SP_HEAP_LOCK();
770
+ /* The threshold store is atomic: sp_gc_collection_wanted reads it without
771
+ the heap lock. threshold_init stays plain -- only retune reads it, under
772
+ stop-the-world, ordered after this by the writer's park. */
773
+ if (!sp_gc_stress_checked) { sp_gc_stress_checked = 1; const char *e = getenv("SPINEL_GC_STRESS"); if (e && *e && *e != '0') { SP_GC_CTR_SET(sp_gc_threshold, 2048); sp_gc_threshold_init = 2048; sp_gc_stress_pin = 1; } }
774
+ if (SP_GC_CTR_GET(sp_gc_bytes) > sp_gc_threshold) {
775
+ sp_gc_collect_retune();
776
+ }
777
+ size_t need = sizeof(sp_gc_hdr) + sz;
778
+ sp_gc_hdr *h = (sp_gc_hdr *)sp_slab_alloc_in(need, 0, fin != NULL, 1);
779
+ h->finalize = fin; h->scan = scn; h->size = need;
780
+ if (SP_EXPECT(sp_alloc_report_on, 0)) sp_alloc_report_count((void *)scn, sz);
781
+ SP_GC_HEAP_PUSH(h); sp_gc_bytes_add(need);
782
+ if (SP_EXPECT(sp_slab_verify_on, 0)) sp_slab_note(h, 1);
783
+ sp_gc_alloc_fast_ok = sp_gc_stress_checked && sp_slab_on > 0 && !sp_slab_verify_on && !sp_alloc_report_on;
784
+ SP_HEAP_UNLOCK();
785
+ return (char *)h + sizeof(sp_gc_hdr);
786
+ #endif
787
+ }
788
+ /* A block the collector will sweep: an object (zeroed) or a heap string */
789
+ void *sp_slab_alloc(size_t need) {
790
+ void *p = sp_slab_alloc_in(need, 0, 0, 1);
791
+ if (SP_EXPECT(sp_slab_verify_on, 0)) sp_slab_note(p, 1);
792
+ return p;
793
+ }
794
+ /* A heap string: the bump of sp_slab_alloc_in with the str bit set in the
795
+ word cached beside the run, the way an object's fin bit is. Locating the
796
+ slot from its address instead (chunk, bitmap, word, bit) was two thirds of
797
+ the cost of allocating a short string. */
798
+ void *sp_slab_alloc_str(size_t need) {
799
+ if (SP_EXPECT(sp_slab_on > 0 && need <= SP_SLAB_MAX && !sp_slab_verify_on, 1)) {
800
+ int cls = sp_slab_cls_of[(need + 15) >> 4];
801
+ sp_slab_worker *wk = &sp_slab_wk[SP_SLAB_WID()];
802
+ char *q = wk->rnext[0][cls];
803
+ if (SP_EXPECT(q != wk->rend[0][cls], 1)) {
804
+ wk->rnext[0][cls] = q + sp_slab_csize[cls];
805
+ unsigned i = (unsigned)(((uint64_t)(size_t)(q - wk->rbase[0][cls]) * sp_slab_recip[cls]) >> 32);
806
+ bm_or(wk->rstr[0][cls], (uint64_t)1 << i);
807
+ return q;
808
+ }
809
+ }
810
+ void *p = sp_slab_alloc_in(need, 0, 0, 0);
811
+ if (sp_slab_on > 0 && sp_slab_owns(p)) {
812
+ sp_slab_loc l; sp_slab_locate(p, &l);
813
+ bm_or(&l.bm->str[l.w], l.bit);
814
+ if (sp_slab_verify_on) sp_slab_note(p, 2);
815
+ }
816
+ return p;
817
+ }
818
+ /* A block no sweep frees: a container's payload, dying by an explicit free */
819
+ void *sp_slab_alloc_raw(size_t need) {
820
+ void *p = sp_slab_alloc_in(need, 1, 0, 0);
821
+ if (sp_slab_verify_on) sp_slab_note(p, 3);
822
+ return p;
823
+ }
824
+ /* the header gained a finalizer or a recycler after its allocation */
825
+ void sp_slab_set_fin(void *h) {
826
+ if (sp_slab_on <= 0 || !sp_slab_owns(h)) return;
827
+ sp_slab_loc l; sp_slab_locate(h, &l);
828
+ bm_or(&l.bm->fin[l.w], l.bit);
829
+ if (sp_slab_verify_on) sp_slab_note(h, 4);
830
+ }
831
+ /* a frozen heap string: kept until the program ends, as a static literal is */
832
+ void sp_slab_pin(const void *p) {
833
+ if (sp_slab_on <= 0 || !sp_slab_owns(p)) return;
834
+ sp_slab_loc l; sp_slab_locate(p, &l);
835
+ bm_or(&l.bm->pin[l.w], l.bit);
836
+ if (sp_slab_verify_on) sp_slab_note(p, 9);
837
+ }
838
+ /* A dead header about to be handed to its pool: pinned, out of every
839
+ sweep's reach, until the pool hands it out again or frees it. Its
840
+ finalizer bit stays: a pinned slot is never dead, and the bit is what
841
+ makes its next death run the recycler again. */
842
+ void sp_slab_park(void *h) {
843
+ if (sp_slab_on <= 0 || !sp_slab_owns(h)) return;
844
+ sp_slab_loc l; sp_slab_locate(h, &l);
845
+ bm_or(&l.bm->pin[l.w], l.bit);
846
+ }
847
+ /* A pooled header handed out again: into the current epoch, and unpinned
848
+ -- in that order, since the owner's search reads pin before young
849
+ (sp_slab_take) and must never find the slot in neither. */
850
+ void sp_slab_relive(void *h) {
851
+ if (sp_slab_on <= 0 || !sp_slab_owns(h)) return;
852
+ sp_slab_loc l; sp_slab_locate(h, &l);
853
+ bm_or(&l.bm->young[sp_slab_epoch & 1][l.w], l.bit);
854
+ bm_and(&l.bm->pin[l.w], ~l.bit);
855
+ if (sp_slab_verify_on) sp_slab_note(h, 5 + 10 * (int)(sp_slab_epoch & 1));
856
+ }
857
+ /* ---- SPINEL_GC_STRESS=2: the quarantine ----
858
+ A slot a sweep frees is the next slot its class hands out, and until then
859
+ it keeps the dead object's bytes: the sweep reads the bitmaps and never
860
+ the slot. So a reference the collector was never told about reads the
861
+ object it lost, intact, or a live object of the same size in its place,
862
+ and the program answers right by luck. Here a freed slot is filled with
863
+ 0xdb and kept out of reuse: pinned, so no sweep and no allocation touches
864
+ it, and named in a bitmap of its own, which is what tells it from a
865
+ payload. An object keeps its header, and a string its header, marker byte
866
+ and length, so a report can say what the slot was; the bytes after are
867
+ poison. sp_slab_is_live answers no for such a slot, so the verifier's
868
+ membership test (sp_gc_mark) stops a mark that reaches one and names the
869
+ root or the holder, and a read by the program gets 0xdb: an Integer field
870
+ is -2604246222170760229, a pointer field faults, a string is its own
871
+ length of \xDB.
872
+ The quarantine holds SP_SLAB_QUAR_MAX bytes and is then let go whole, at
873
+ the next barrier: every chunk with a slot in it is one more for each
874
+ sweep to walk, and with a collection at every allocation that walk is the
875
+ run's cost. A slot let go keeps its poison until a claim zeroes it.
876
+ Blocks past the largest class are malloc's, as before. */
877
+ int sp_slab_quar_on = 0; /* asked for, before main (lib/sp_gc.c) */
878
+ static uint64_t *sp_slab_quar = NULL; /* SP_SLAB_NW words a chunk, over the reservation; mapped at the first barrier */
879
+ static size_t sp_slab_quar_bytes = 0;
880
+ #ifndef SP_SLAB_QUAR_MAX
881
+ #define SP_SLAB_QUAR_MAX ((size_t)1 << 20)
882
+ #endif
883
+ #define SP_SLAB_POISON 0xdb
884
+ static inline uint64_t *sp_slab_quar_of(sp_slab_chunk *ch) {
885
+ return sp_slab_quar + ((uintptr_t)sp_slab_chunk_base(ch) - sp_slab_base) / SP_SLAB_CHUNK * SP_SLAB_NW;
886
+ }
887
+ /* the slots of one bitmap word a sweep found dead (`strs`: which of them are
888
+ strings), or the one slot of an explicit free (`whole`: no header to keep) */
889
+ static SP_NOINLINE void sp_slab_quarantine(sp_slab_chunk *ch, sp_slab_bm *bm, unsigned w, uint64_t dead, uint64_t strs, int whole) {
890
+ unsigned csize = sp_slab_csize[ch->cls];
891
+ bm_or(&bm->pin[w], dead);
892
+ bm_or(&sp_slab_quar_of(ch)[w], dead);
893
+ for (uint64_t v = dead; v; v &= v - 1) {
894
+ unsigned b = (unsigned)SP_CTZ64(v);
895
+ char *slot = sp_slab_chunk_base(ch) + (size_t)((w << 6) + b) * csize;
896
+ if (whole) memset(slot, SP_SLAB_POISON, csize);
897
+ else if ((strs >> b) & 1) {
898
+ size_t room = csize - sizeof(sp_str_hdr) - 2, len = ((sp_str_hdr *)slot)->len;
899
+ memset(slot + sizeof(sp_str_hdr) + 1, SP_SLAB_POISON, len < room ? len : room);
900
+ }
901
+ else memset(slot + sizeof(sp_gc_hdr), SP_SLAB_POISON, csize - sizeof(sp_gc_hdr));
902
+ }
903
+ #ifdef SP_THREADS
904
+ SP_ATOMIC_FETCH_ADD(&sp_slab_quar_bytes, (size_t)SP_POPCOUNT64(dead) * csize, __ATOMIC_RELAXED);
905
+ #else
906
+ sp_slab_quar_bytes += (size_t)SP_POPCOUNT64(dead) * csize;
907
+ #endif
908
+ }
909
+ int sp_slab_is_quarantined(const void *p) {
910
+ if (!sp_slab_quar || !sp_slab_owns(p)) return 0;
911
+ sp_slab_loc l; sp_slab_locate(p, &l);
912
+ return (bm_load(&sp_slab_quar_of(l.ch)[l.w]) & l.bit) != 0;
913
+ }
914
+
915
+ int sp_slab_is_str(const void *p) {
916
+ sp_slab_loc l; sp_slab_locate(p, &l);
917
+ return (l.bm->str[l.w] & l.bit) != 0;
918
+ }
919
+ /* the verifiers' report: what the bitmaps say about a slot */
920
+ void sp_slab_describe(const void *p) {
921
+ if (sp_slab_on <= 0 || !sp_slab_owns(p)) { fprintf(stderr, " slab: not a slab block\n"); return; }
922
+ sp_slab_loc l; sp_slab_locate(p, &l);
923
+ fprintf(stderr, " slab: chunk wid=%u cls=%u (%u B) in_use=%d slot=%u epoch=%u young0=%d young1=%d old=%d mark=%d fin=%d str=%d pin=%d\n",
924
+ l.ch->wid, l.ch->cls, sp_slab_csize[l.ch->cls], l.ch->in_use, l.idx, sp_slab_epoch,
925
+ !!(l.bm->young[0][l.w] & l.bit), !!(l.bm->young[1][l.w] & l.bit), !!(l.bm->old[l.w] & l.bit),
926
+ !!(l.bm->mark[l.w] & l.bit), !!(l.bm->fin[l.w] & l.bit), !!(l.bm->str[l.w] & l.bit), !!(l.bm->pin[l.w] & l.bit));
927
+ }
928
+ /* SPINEL_GC_VERIFY: every chunk's bitmaps against their invariants -- a
929
+ finalizer, string or pin bit on a slot in no generation is a slot that was
930
+ freed without being cleared, or claimed without being free. */
931
+ int sp_slab_verify_on = 0; /* declared above */
932
+ /* under the verifier: who last set a slot's bits (a byte per 32-byte slot
933
+ over the whole reservation, touched only where slots are) */
934
+ typedef struct { unsigned char what, wid; unsigned short cycle; } sp_slab_shadow_ev;
935
+ typedef struct { unsigned char n; sp_slab_shadow_ev ev[8]; } sp_slab_shadow_rec;
936
+ static sp_slab_shadow_rec *sp_slab_shadow = NULL;
937
+ extern int sp_gc_cycle;
938
+ #define SP_SHADOW(p) (sp_slab_shadow ? &sp_slab_shadow[((uintptr_t)(p) - sp_slab_base) >> 5] : NULL)
939
+ void sp_slab_note(const void *p, int what) {
940
+ if (!sp_slab_verify_on || !sp_slab_owns(p)) return;
941
+ if (!sp_slab_shadow) {
942
+ void *m = mmap(NULL, (sp_slab_cap >> 5) * sizeof(sp_slab_shadow_rec), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
943
+ if (m == MAP_FAILED) return;
944
+ sp_slab_shadow = (sp_slab_shadow_rec *)m;
945
+ }
946
+ sp_slab_shadow_rec *sh = SP_SHADOW(p);
947
+ if (!sh) return;
948
+ sp_slab_shadow_ev *e = &sh->ev[sh->n++ & 7];
949
+ e->what = (unsigned char)what; e->wid = (unsigned char)sp_worker_id_shadow(); e->cycle = (unsigned short)sp_gc_cycle;
950
+ }
951
+ static void sp_slab_verify_chunk(sp_slab_chunk *ch, const char *when) {
952
+ sp_slab_bm *bm = sp_slab_bm_of(ch);
953
+ unsigned nw = (ch->nslots + 63) >> 6, csize = sp_slab_csize[ch->cls];
954
+ for (unsigned w = 0; w < nw; w++) {
955
+ uint64_t gen = bm->young[0][w] | bm->young[1][w] | bm->old[w] | bm->pin[w];
956
+ uint64_t stray = (bm->fin[w] | bm->str[w] | bm->mark[w]) & ~gen;
957
+ uint64_t both = bm->young[0][w] & bm->young[1][w];
958
+ uint64_t finstr = bm->fin[w] & bm->str[w];
959
+ uint64_t ymark = (bm->young[0][w] | bm->young[1][w]) & bm->mark[w] & (when[0] == 'a' && when[1] == 't' ? ~(uint64_t)0 : 0); /* at the barrier: no young slot carries a mark */
960
+ uint64_t bad = stray | both | finstr | ymark;
961
+ while (bad) {
962
+ unsigned b = (unsigned)SP_CTZ64(bad); bad &= bad - 1;
963
+ void *slot = sp_slab_chunk_base(ch) + (size_t)((w << 6) + b) * csize;
964
+ fprintf(stderr, "*** SPINEL_GC_VERIFY: slab invariant broken at %p (%s), %s:\n", slot,
965
+ (stray >> b) & 1 ? "bits on a free slot" : (both >> b) & 1 ? "in both epochs" : (finstr >> b) & 1 ? "finalizer bit on a string" : "a mark on a young slot", when);
966
+ sp_slab_describe(slot);
967
+ { const uint64_t *q = (const uint64_t *)slot;
968
+ fprintf(stderr, " slot words: %016llx %016llx %016llx %016llx %016llx %016llx\n",
969
+ (unsigned long long)q[0], (unsigned long long)q[1], (unsigned long long)q[2],
970
+ (unsigned long long)q[3], (unsigned long long)q[4], (unsigned long long)q[5]);
971
+ sp_slab_shadow_rec *sh = SP_SHADOW(slot);
972
+ fprintf(stderr, " history (1 obj alloc, 2 str alloc, 3 raw alloc, 4 set_fin, 5/15 relive into y0/y1, 6 free, 7 swept dead, 8/18 parked sweeping y0/y1, 9 pin, 30/31 survived sweep of y0/y1, 40-43 take into y0/y1/(+2 payload)), oldest first; now cycle %d:\n", sp_gc_cycle);
973
+ if (sh) for (unsigned k = 0; k < 8; k++) { sp_slab_shadow_ev *e = &sh->ev[(sh->n + k) & 7]; if (e->what) fprintf(stderr, " what=%d worker=%d cycle=%d\n", e->what, e->wid, e->cycle); } }
974
+ abort();
975
+ }
976
+ }
977
+ }
978
+ /* the verifiers' report of a slot's recorded history (SPINEL_GC_VERIFY) */
979
+ void sp_slab_history(const void *p) {
980
+ if (sp_slab_on <= 0 || !sp_slab_owns(p)) return;
981
+ sp_slab_shadow_rec *sh = SP_SHADOW(p);
982
+ fprintf(stderr, " history (1 obj alloc, 2 str alloc, 3 raw alloc, 4 set_fin, 5/15 relive, 6 free, 7 swept dead, 8/18 parked, 9 pin, 30/31 survived sweep, 40-43 take), oldest first; now cycle %d:\n", sp_gc_cycle);
983
+ if (sh) for (unsigned k = 0; k < 8; k++) { sp_slab_shadow_ev *e = &sh->ev[(sh->n + k) & 7]; if (e->what) fprintf(stderr, " what=%d worker=%d cycle=%d\n", e->what, e->wid, e->cycle); }
984
+ }
985
+ void sp_slab_verify_all(void) {
986
+ if (sp_slab_on <= 0) return;
987
+ sp_slab_verify_on = 1; sp_gc_alloc_fast_ok = 0;
988
+ for (uintptr_t a = sp_slab_base; a < sp_slab_brk; a += SP_SLAB_ARENA) {
989
+ sp_slab_arena *ar = (sp_slab_arena *)a;
990
+ for (int i = SP_SLAB_FIRST; i < (int)SP_SLAB_NCHUNK; i++) {
991
+ sp_slab_chunk *ch = &ar->ch[i];
992
+ if (!ch->in_use) continue;
993
+ sp_slab_verify_chunk(ch, "at the barrier");
994
+ }
995
+ }
996
+ }
997
+ /* allocated at all: in some generation (a verifier's membership test) */
998
+ int sp_slab_is_live(const void *p) {
999
+ if (sp_slab_on <= 0 || !sp_slab_owns(p)) return 0;
1000
+ sp_slab_loc l; sp_slab_locate(p, &l);
1001
+ if (SP_EXPECT(sp_slab_quar != NULL, 0) && (sp_slab_quar_of(l.ch)[l.w] & l.bit)) return 0; /* pinned, and freed */
1002
+ return ((l.bm->young[0][l.w] | l.bm->young[1][l.w] | l.bm->old[l.w] | l.bm->pin[l.w]) & l.bit) != 0;
1003
+ }
1004
+ int sp_slab_is_old(const void *p) {
1005
+ sp_slab_loc l; sp_slab_locate(p, &l);
1006
+ return (l.bm->old[l.w] & l.bit) != 0;
1007
+ }
1008
+
1009
+ /* A chunk a free put a slot back into rejoins its owner's available list,
1010
+ unless it is there already or is the chunk the owner allocates from. The
1011
+ owner's current chunk is read racily: when it is mid-refill the chunk it
1012
+ is installing can be pushed here too, and refill drops it again when it
1013
+ finds it empty. */
1014
+ static void sp_slab_avail_push(sp_slab_chunk *ch) {
1015
+ #ifdef SP_THREADS
1016
+ if (!SP_ATOMIC_LOAD(&ch->on_avail, __ATOMIC_RELAXED) &&
1017
+ SP_ATOMIC_LOAD(&sp_slab_wk[ch->wid].cur[ch->cls], __ATOMIC_RELAXED) != ch) {
1018
+ unsigned char was = SP_ATOMIC_EXCHANGE(&ch->on_avail, 1, __ATOMIC_ACQ_REL);
1019
+ if (!was) {
1020
+ sp_slab_chunk **head = &sp_slab_wk[ch->wid].avail[ch->cls];
1021
+ sp_slab_chunk *oh;
1022
+ do { oh = SP_ATOMIC_LOAD(head, __ATOMIC_ACQUIRE); ch->next_avail = oh;
1023
+ } while (!SP_ATOMIC_CAS(head, &oh, ch, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE));
1024
+ }
1025
+ }
1026
+ #else
1027
+ if (!ch->on_avail && sp_slab_wk[0].cur[ch->cls] != ch) {
1028
+ ch->on_avail = 1;
1029
+ ch->next_avail = sp_slab_wk[0].avail[ch->cls];
1030
+ sp_slab_wk[0].avail[ch->cls] = ch;
1031
+ }
1032
+ #endif
1033
+ }
1034
+
1035
+ /* An explicit free, from anywhere: a payload its container dropped, a pooled
1036
+ header over the pool's cap, a malloc'd block. The slot leaves every
1037
+ generation and loses its marks; the chunk goes back on the owner's list. */
1038
+ SP_TLS unsigned long sp_slab_frees = 0; /* this thread's explicit frees, counted (sp_gc_die_cb reads it around a recycler) */
1039
+ void sp_slab_free(void *p) {
1040
+ if (!sp_slab_owns(p)) { free(p); return; }
1041
+ sp_slab_loc l; sp_slab_locate(p, &l);
1042
+ uint64_t nb = ~l.bit;
1043
+ /* only the words that hold the bit: a payload is pinned and nothing else,
1044
+ and (threaded) each clear is a locked instruction */
1045
+ #define SP_SLAB_CLEAR(word) do { if (bm_load(&(word)) & l.bit) bm_and(&(word), nb); } while (0)
1046
+ SP_SLAB_CLEAR(l.bm->young[0][l.w]);
1047
+ SP_SLAB_CLEAR(l.bm->young[1][l.w]);
1048
+ SP_SLAB_CLEAR(l.bm->fin[l.w]);
1049
+ SP_SLAB_CLEAR(l.bm->str[l.w]);
1050
+ SP_SLAB_CLEAR(l.bm->mark[l.w]);
1051
+ SP_SLAB_CLEAR(l.bm->old[l.w]);
1052
+ if (SP_EXPECT(sp_slab_quar != NULL, 0)) { sp_slab_frees++; if (sp_slab_verify_on) sp_slab_note(p, 6); sp_slab_quarantine(l.ch, l.bm, l.w, l.bit, 0, 1); return; }
1053
+ SP_SLAB_CLEAR(l.bm->pin[l.w]);
1054
+ #undef SP_SLAB_CLEAR
1055
+ sp_slab_frees++;
1056
+ if (sp_slab_verify_on) sp_slab_note(p, 6);
1057
+ sp_slab_avail_push(l.ch);
1058
+ }
1059
+
1060
+ /* A container payload resized: a slab block's capacity is its size class,
1061
+ so a smaller or equal request keeps the block, a larger one moves to a
1062
+ fresh block (slab or malloc, by size) and frees the old; a malloc block
1063
+ is realloc'd, which for the largest is an mremap with no copy. */
1064
+ void *sp_pl_realloc(void *p, size_t newn) {
1065
+ if (!p) return sp_slab_alloc_raw(newn);
1066
+ if (!sp_slab_owns(p)) {
1067
+ void *q = realloc(p, newn);
1068
+ if (!q) sp_oom_die();
1069
+ return q;
1070
+ }
1071
+ sp_slab_chunk *ch = sp_slab_chunk_of(p);
1072
+ size_t have = sp_slab_csize[ch->cls];
1073
+ if (newn <= have) return p;
1074
+ void *q = sp_slab_alloc_raw(newn);
1075
+ memcpy(q, p, have);
1076
+ sp_slab_free(p);
1077
+ return q;
1078
+ }
1079
+
1080
+ /* ---- the mark ----
1081
+ The mark bit of a slot, set by whichever marker reaches it first, and
1082
+ promotion: a young slot the mark reaches becomes old here, which is what
1083
+ the concurrent sweep needs (it will not write the bit, and it frees what
1084
+ is young and unmarked). `aging` keeps a first-time survivor young: the
1085
+ sweep carries it into the next epoch instead. Returns 0 when the slot
1086
+ was already marked this cycle, else 1, with *was_young telling a promotion
1087
+ (or an aged survivor) from an old object reached again. */
1088
+ int sp_slab_mark(const void *p, int aging, int *was_young) {
1089
+ sp_slab_loc l; sp_slab_locate(p, &l);
1090
+ uint64_t o = bm_or(&l.bm->mark[l.w], l.bit);
1091
+ if (o & l.bit) { *was_young = 0; return 0; }
1092
+ uint64_t ow = bm_load(&l.bm->old[l.w]);
1093
+ if (ow & l.bit) { *was_young = 0; return 1; }
1094
+ *was_young = 1;
1095
+ if (!aging) bm_or(&l.bm->old[l.w], l.bit);
1096
+ return 1;
1097
+ }
1098
+ /* is the slot marked this cycle? (the string side's "already marked" test) */
1099
+ int sp_slab_is_marked(const void *p) {
1100
+ sp_slab_loc l; sp_slab_locate(p, &l);
1101
+ return (bm_load(&l.bm->mark[l.w]) & l.bit) != 0;
1102
+ }
1103
+ /* the verifiers' probe unmarks a slot to see who marks it again */
1104
+ void sp_slab_unmark(const void *p) {
1105
+ sp_slab_loc l; sp_slab_locate(p, &l);
1106
+ bm_and(&l.bm->mark[l.w], ~l.bit);
1107
+ }
1108
+
1109
+ /* ---- the sweep ----
1110
+ Under the barrier, before the sweep of the epoch that just closed starts:
1111
+ new allocations go to the other parity from here. */
1112
+ /* Under the barrier: every worker's claimed-and-not-handed-out run is
1113
+ unclaimed, and its cache dropped. Before the collector walks the young
1114
+ bits (they would name slots holding garbage) and before the epoch flips
1115
+ (the claim words name the parity that is about to close). */
1116
+ void sp_slab_runs_release(void) {
1117
+ if (sp_slab_on <= 0) return;
1118
+ for (int w = 0; w < SP_SLAB_NWK; w++) {
1119
+ sp_slab_worker *wk = &sp_slab_wk[w];
1120
+ for (int c = 0; c < SP_SLAB_NCLS; c++) {
1121
+ for (int k = 0; k < 2; k++) {
1122
+ char *p = wk->rnext[k][c], *end = wk->rend[k][c];
1123
+ if (p && p < end) {
1124
+ unsigned i = (unsigned)(((uint64_t)(size_t)(p - wk->rbase[k][c]) * sp_slab_recip[c]) >> 32);
1125
+ unsigned n = (unsigned)(((uint64_t)(size_t)(end - p) * sp_slab_recip[c]) >> 32);
1126
+ uint64_t mask = (n >= 64 ? ~(uint64_t)0 : (((uint64_t)1 << n) - 1)) << i;
1127
+ bm_and(wk->rclaim[k][c], ~mask);
1128
+ }
1129
+ wk->rnext[k][c] = wk->rend[k][c] = NULL;
1130
+ }
1131
+ wk->wmask[c] = 0;
1132
+ }
1133
+ }
1134
+ }
1135
+ /* Under the barrier, at the start of a collection, before any sweep or free
1136
+ can race it: the map is made at the first one, and a quarantine past its
1137
+ size is let go whole. */
1138
+ void sp_slab_quarantine_trim(void) {
1139
+ if (!sp_slab_quar) {
1140
+ if (!sp_slab_quar_on || sp_slab_on <= 0) return;
1141
+ void *m = mmap(NULL, sp_slab_cap / SP_SLAB_CHUNK * SP_SLAB_NW * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
1142
+ if (m == MAP_FAILED) { sp_slab_quar_on = 0; return; }
1143
+ sp_slab_quar = (uint64_t *)m;
1144
+ }
1145
+ if (sp_slab_quar_bytes <= SP_SLAB_QUAR_MAX) return;
1146
+ for (uintptr_t a = sp_slab_base; a < sp_slab_brk; a += SP_SLAB_ARENA) {
1147
+ sp_slab_arena *ar = (sp_slab_arena *)a;
1148
+ for (int i = SP_SLAB_FIRST; i < (int)SP_SLAB_NCHUNK; i++) {
1149
+ sp_slab_chunk *ch = &ar->ch[i];
1150
+ if (!ch->in_use) continue;
1151
+ sp_slab_bm *bm = sp_slab_bm_of(ch);
1152
+ uint64_t *q = sp_slab_quar_of(ch), any = 0;
1153
+ for (unsigned w = 0; w < (ch->nslots + 63u) >> 6; w++) {
1154
+ if (!q[w]) continue;
1155
+ bm_and(&bm->pin[w], ~q[w]); any |= q[w]; q[w] = 0;
1156
+ }
1157
+ if (any) sp_slab_avail_push(ch);
1158
+ }
1159
+ }
1160
+ sp_slab_quar_bytes = 0;
1161
+ }
1162
+ void sp_slab_epoch_flip(void) {
1163
+ sp_slab_runs_release();
1164
+ sp_slab_epoch = sp_slab_epoch + 1; /* under the barrier: nobody allocates or sweeps meanwhile */
1165
+ }
1166
+ /* Every chunk one worker owns, in one pass over the bitmaps. The epoch to
1167
+ reclaim from is the one before the current; `full` frees the old
1168
+ generation too; `aging` carries young survivors the mark did not promote
1169
+ into the current epoch. What dies with a finalizer bit set has its
1170
+ finalizer (or recycler) run, from this thread. The caller says which
1171
+ thread is sweeping: an owner beside the program, a sweeper thread, or the
1172
+ collector under the barrier -- the bit operations are the same. */
1173
+ void sp_slab_sweep_worker(int wid, int full, int aging, int (*die)(void *hdr), sp_slab_sweep_stats *st) {
1174
+ sp_slab_sweep_stats acc; memset(&acc, 0, sizeof acc);
1175
+ if (sp_slab_on <= 0 || wid < 0 || wid >= SP_SLAB_NWK) { if (st) *st = acc; return; }
1176
+ sp_slab_worker *wk = &sp_slab_wk[wid];
1177
+ unsigned e = sp_slab_epoch & 1, pe = e ^ 1;
1178
+ SP_ATOMIC_STORE(&wk->sweeping, 1, __ATOMIC_RELEASE);
1179
+ for (sp_slab_chunk *ch = SP_ATOMIC_LOAD(&wk->owned, __ATOMIC_ACQUIRE); ch; ch = ch->own_next) {
1180
+ if (!ch->in_use) continue;
1181
+ sp_slab_bm *bm = sp_slab_bm_of(ch);
1182
+ unsigned nw = (ch->nslots + 63) >> 6;
1183
+ unsigned csize = sp_slab_csize[ch->cls];
1184
+ size_t freed = 0;
1185
+ for (unsigned w = 0; w < nw; w++) {
1186
+ uint64_t yv = bm_load(&bm->young[pe][w]);
1187
+ uint64_t ov = bm_load(&bm->old[w]);
1188
+ uint64_t mv = bm_load(&bm->mark[w]);
1189
+ uint64_t pv = bm_load(&bm->pin[w]);
1190
+ /* the headers a pool holds (pinned, in no generation, with their
1191
+ finalizer bit) are resident like any live object: the object budget
1192
+ is sized from them too, as it was when the lists counted them */
1193
+ if (pv) {
1194
+ uint64_t parked_now = pv & bm_load(&bm->fin[w]) & ~(yv | ov | bm_load(&bm->young[e][w]));
1195
+ if (parked_now) acc.parked += (size_t)SP_POPCOUNT64(parked_now) * csize;
1196
+ }
1197
+ /* a word with nothing to reclaim is skipped, unless it carries marks:
1198
+ a minor's mark reaches old strings too, and a mark left behind would
1199
+ read as "already marked" next cycle and keep a dead string */
1200
+ if (!yv && !mv && !(full && ov)) continue;
1201
+ uint64_t dead = yv & ~mv & ~ov & ~pv;
1202
+ if (full) dead |= ov & ~mv & ~pv;
1203
+ if (aging) {
1204
+ uint64_t carry = yv & mv & ~ov;
1205
+ if (carry) { bm_or(&bm->young[e][w], carry); acc.kept_young += (size_t)SP_POPCOUNT64(carry) * csize; }
1206
+ }
1207
+ /* a dead object with a finalizer or a recycler: the callback runs it
1208
+ and says whether the slot is freed or kept. A pooled header stays
1209
+ allocated: the callback pins it (sp_slab_park) BEFORE handing it to
1210
+ its pool, since the pool may hand it out again on another thread at
1211
+ once, and that thread's relive undoes the pin. So nothing here
1212
+ writes a kept slot's bits after the callback: it is either still
1213
+ pinned or already alive again, and either way it is not dead. */
1214
+ uint64_t fdead = dead & bm_load(&bm->fin[w]);
1215
+ uint64_t parked = 0;
1216
+ if (fdead) {
1217
+ /* what the pass costs per slot is the finalizers it runs: the rest is
1218
+ a few words per chunk (the object budget's mark-share gate reads
1219
+ `swept` as the sweep's per-slot work, sp_gc_retune_object) */
1220
+ acc.swept += (size_t)SP_POPCOUNT64(fdead);
1221
+ /* pinned as a word before any callback runs, unpinned as a word for
1222
+ those the callbacks freed: a slot the recycler kept is pinned by
1223
+ then, whatever its pool did with the header meanwhile */
1224
+ bm_or(&bm->pin[w], fdead);
1225
+ uint64_t left = fdead, freed_now = 0;
1226
+ while (left) {
1227
+ unsigned i = (unsigned)SP_CTZ64(left);
1228
+ left &= left - 1;
1229
+ if (die(sp_slab_chunk_base(ch) + (size_t)((w << 6) + i) * csize)) freed_now |= (uint64_t)1 << i;
1230
+ }
1231
+ if (freed_now) bm_and(&bm->pin[w], ~freed_now);
1232
+ parked = fdead & ~freed_now;
1233
+ }
1234
+ if (parked) {
1235
+ dead &= ~parked;
1236
+ acc.parked += (size_t)SP_POPCOUNT64(parked) * csize; /* this cycle's, beside the ones counted above */
1237
+ if (sp_slab_verify_on) { uint64_t v = parked; while (v) { unsigned b = (unsigned)SP_CTZ64(v); v &= v - 1; sp_slab_note(sp_slab_chunk_base(ch) + (size_t)((w << 6) + b) * csize, 8 + 10 * (int)pe); } }
1238
+ }
1239
+ if (sp_slab_verify_on) { uint64_t v = yv & ~dead; while (v) { unsigned b = (unsigned)SP_CTZ64(v); v &= v - 1; sp_slab_note(sp_slab_chunk_base(ch) + (size_t)((w << 6) + b) * csize, 30 + (int)pe); } }
1240
+ if (dead) {
1241
+ uint64_t sd = dead & bm_load(&bm->str[w]);
1242
+ acc.freed_str += (size_t)SP_POPCOUNT64(sd) * csize;
1243
+ acc.freed_obj += (size_t)SP_POPCOUNT64(dead & ~sd) * csize;
1244
+ if (fdead | (dead & bm_load(&bm->fin[w]))) bm_and(&bm->fin[w], ~dead);
1245
+ if (sd) bm_and(&bm->str[w], ~dead);
1246
+ if (full && (ov & dead)) bm_and(&bm->old[w], ~dead);
1247
+ freed += (size_t)SP_POPCOUNT64(dead);
1248
+ if (sp_slab_verify_on) { uint64_t v = dead; while (v) { unsigned b = (unsigned)SP_CTZ64(v); v &= v - 1; sp_slab_note(sp_slab_chunk_base(ch) + (size_t)((w << 6) + b) * csize, 7); } }
1249
+ if (SP_EXPECT(sp_slab_quar != NULL, 0)) sp_slab_quarantine(ch, bm, w, dead, sd, 0);
1250
+ }
1251
+ /* the epoch's word is spent: dead, promoted or carried, every bit is
1252
+ accounted for. So are the marks: the next cycle starts clean. */
1253
+ bm_store(&bm->young[pe][w], 0);
1254
+ bm_store(&bm->mark[w], 0);
1255
+ }
1256
+ acc.slots += ch->nslots;
1257
+ if (freed) { acc.freed_slots += freed; sp_slab_avail_push(ch); }
1258
+ if (sp_slab_verify_on) sp_slab_verify_chunk(ch, "after its sweep");
1259
+ }
1260
+ SP_ATOMIC_STORE(&wk->sweeping, 0, __ATOMIC_RELEASE);
1261
+ if (st) *st = acc;
1262
+ }
1263
+ /* The verifiers walk every allocated slot of every chunk: objects (not
1264
+ strings) whose generation is young, old, or either. */
1265
+ void sp_slab_each_object(int young, int old, void (*fn)(void *hdr, void *arg), void *arg) {
1266
+ if (sp_slab_on <= 0) return;
1267
+ for (uintptr_t a = sp_slab_base; a < sp_slab_brk; a += SP_SLAB_ARENA) {
1268
+ sp_slab_arena *ar = (sp_slab_arena *)a;
1269
+ for (int i = SP_SLAB_FIRST; i < (int)SP_SLAB_NCHUNK; i++) {
1270
+ sp_slab_chunk *ch = &ar->ch[i];
1271
+ if (!ch->in_use) continue;
1272
+ sp_slab_bm *bm = sp_slab_bm_of(ch);
1273
+ unsigned nw = (ch->nslots + 63) >> 6, csize = sp_slab_csize[ch->cls];
1274
+ for (unsigned w = 0; w < nw; w++) {
1275
+ uint64_t v = 0;
1276
+ if (young) v |= bm->young[0][w] | bm->young[1][w];
1277
+ if (old) v |= bm->old[w];
1278
+ v &= ~bm->str[w]; /* not strings; a payload or a parked header is pinned and in no generation */
1279
+ while (v) {
1280
+ unsigned b = (unsigned)SP_CTZ64(v); v &= v - 1;
1281
+ fn(sp_slab_chunk_base(ch) + (size_t)((w << 6) + b) * csize, arg);
1282
+ }
1283
+ }
1284
+ }
1285
+ }
1286
+ }
1287
+ /* the same over heap strings (the slot's start; the body follows the header) */
1288
+ void sp_slab_each_string(int young, int old, void (*fn)(void *hdr, void *arg), void *arg) {
1289
+ if (sp_slab_on <= 0) return;
1290
+ for (uintptr_t a = sp_slab_base; a < sp_slab_brk; a += SP_SLAB_ARENA) {
1291
+ sp_slab_arena *ar = (sp_slab_arena *)a;
1292
+ for (int i = SP_SLAB_FIRST; i < (int)SP_SLAB_NCHUNK; i++) {
1293
+ sp_slab_chunk *ch = &ar->ch[i];
1294
+ if (!ch->in_use) continue;
1295
+ sp_slab_bm *bm = sp_slab_bm_of(ch);
1296
+ unsigned nw = (ch->nslots + 63) >> 6, csize = sp_slab_csize[ch->cls];
1297
+ for (unsigned w = 0; w < nw; w++) {
1298
+ uint64_t v = 0;
1299
+ if (young) v |= bm->young[0][w] | bm->young[1][w];
1300
+ if (old) v |= bm->old[w];
1301
+ v &= bm->str[w];
1302
+ while (v) {
1303
+ unsigned b = (unsigned)SP_CTZ64(v); v &= v - 1;
1304
+ fn(sp_slab_chunk_base(ch) + (size_t)((w << 6) + b) * csize, arg);
1305
+ }
1306
+ }
1307
+ }
1308
+ }
1309
+ }
1310
+
1311
+ /* ---- release: fully free chunks back to the OS ---- */
1312
+ /* The available lists are rebuilt in ADDRESS order. A chunk joins the list
1313
+ whenever a sweep frees its first slot, in whatever order the sweep meets
1314
+ them, and after a few cycles consecutive refills of one class hopped
1315
+ across the heap: a 72 MB tree benchmark (gcbench) ran 30% slower on
1316
+ 16 KB chunks than on glibc, whose coalescing hands back one run of
1317
+ addresses, and 15% faster once the refills walked the chunks in order. */
1318
+ static int sp_slab_chunk_cmp(const void *a, const void *b) {
1319
+ uintptr_t x = (uintptr_t)*(sp_slab_chunk *const *)a, y = (uintptr_t)*(sp_slab_chunk *const *)b;
1320
+ return x < y ? -1 : x > y;
1321
+ }
1322
+ /* The sort buffer is per thread: the owners release their own lists beside
1323
+ the program, at the same time, and a shared buffer would need a lock every
1324
+ one of them queued on. */
1325
+ static SP_TLS sp_slab_chunk **sp_slab_sortbuf = NULL;
1326
+ static SP_TLS size_t sp_slab_sortcap = 0;
1327
+ static sp_slab_chunk *sp_slab_sort_avail(sp_slab_chunk *head) {
1328
+ size_t n = 0;
1329
+ for (sp_slab_chunk *ch = head; ch; ch = ch->next_avail) {
1330
+ if (n == sp_slab_sortcap) {
1331
+ size_t c = sp_slab_sortcap ? sp_slab_sortcap * 2 : 256;
1332
+ sp_slab_chunk **nb = (sp_slab_chunk **)realloc(sp_slab_sortbuf, c * sizeof *nb);
1333
+ if (!nb) return head; /* unsorted is still correct */
1334
+ sp_slab_sortbuf = nb; sp_slab_sortcap = c;
1335
+ }
1336
+ sp_slab_sortbuf[n++] = ch;
1337
+ }
1338
+ if (n < 2) return head;
1339
+ qsort(sp_slab_sortbuf, n, sizeof *sp_slab_sortbuf, sp_slab_chunk_cmp);
1340
+ for (size_t i = 0; i + 1 < n; i++) sp_slab_sortbuf[i]->next_avail = sp_slab_sortbuf[i + 1];
1341
+ sp_slab_sortbuf[n - 1]->next_avail = NULL;
1342
+ return sp_slab_sortbuf[0];
1343
+ }
1344
+ static inline int sp_slab_chunk_empty(sp_slab_chunk *ch) {
1345
+ sp_slab_bm *bm = sp_slab_bm_of(ch);
1346
+ unsigned nw = (ch->nslots + 63) >> 6;
1347
+ for (unsigned w = 0; w < nw; w++)
1348
+ if (bm_load(&bm->young[0][w]) | bm_load(&bm->young[1][w]) | bm_load(&bm->old[w]) | bm_load(&bm->pin[w])) return 0;
1349
+ return 1;
1350
+ }
1351
+
1352
+ /* SPINEL_GC_PHASES: what a release spends its time on -- chunks walked,
1353
+ chunks handed back (each one an madvise), and the madvise time itself. */
1354
+ unsigned long long sp_slab_rel_calls = 0, sp_slab_rel_walked = 0, sp_slab_rel_madv = 0;
1355
+ double sp_slab_rel_madv_t = 0, sp_slab_rel_sort_t = 0;
1356
+ static double sp_slab_now(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec * 1e-9; }
1357
+ /* One worker's lists, released by their OWNER (or, for a slot no worker
1358
+ runs, by the collector under the barrier), right after its own sweep: the
1359
+ walk, the sort and the madvise calls that the collector used to do for
1360
+ every worker in turn under the barrier are spread over the workers
1361
+ instead, each on its own lists, while the others run. Only the owner pops
1362
+ its available lists, so detaching a list whole (an exchange with NULL)
1363
+ makes it private for the walk; a sweeper thread pushing a chunk meanwhile
1364
+ lands it on the fresh head, and the kept chunks are merged back under it
1365
+ with the same compare-and-swap the sweepers push with. A fully free chunk
1366
+ is stable once seen: every slot is out of every generation, so nothing can
1367
+ free into it, and only its owner could allocate from it. Never the chunk
1368
+ the class allocates from, and never while a sweep of this worker's chunks
1369
+ is still running on another thread: that sweep clears bits of the very
1370
+ chunks the pool would carve for someone else. The empty pool is shared,
1371
+ so it takes the slab lock. */
1372
+ void sp_slab_release_worker(int wid) {
1373
+ if (sp_slab_on <= 0 || wid < 0 || wid >= SP_SLAB_NWK) return;
1374
+ sp_slab_worker *wk = &sp_slab_wk[wid];
1375
+ if (SP_ATOMIC_LOAD(&wk->sweeping, __ATOMIC_ACQUIRE)) return;
1376
+ wk->taken = 0;
1377
+ if (sp_gc_ph_on) sp_slab_rel_calls++;
1378
+ for (int cls = 0; cls < SP_SLAB_NCLS; cls++) {
1379
+ /* The reserve is per class: what the class took this cycle it will
1380
+ take again next cycle, and a reserve shared across the classes was
1381
+ spent by the first few, so the rest handed their empties back and
1382
+ carved fresh ones every cycle -- 37,000 chunks a second on a server,
1383
+ each handed to the kernel and faulted in again. */
1384
+ long reserve = wk->taken_cls[cls];
1385
+ wk->taken_cls[cls] = 0;
1386
+ if (reserve < SP_SLAB_RESERVE) reserve = SP_SLAB_RESERVE;
1387
+ sp_slab_chunk *ch = (sp_slab_chunk *)(uintptr_t)SP_ATOMIC_EXCHANGE(&wk->avail[cls], NULL, __ATOMIC_ACQ_REL);
1388
+ if (!ch) continue;
1389
+ sp_slab_chunk *keep = NULL, *give = NULL;
1390
+ while (ch) {
1391
+ sp_slab_chunk *nx = ch->next_avail;
1392
+ if (sp_gc_ph_on) sp_slab_rel_walked++;
1393
+ int empty = sp_slab_chunk_empty(ch);
1394
+ if (empty && reserve <= 0 && ch != SP_ATOMIC_LOAD(&wk->cur[cls], __ATOMIC_RELAXED)) {
1395
+ ch->next_avail = give; give = ch; /* handed back below, in address order */
1396
+ }
1397
+ else {
1398
+ if (empty) reserve--;
1399
+ ch->next_avail = keep; keep = ch;
1400
+ }
1401
+ ch = nx;
1402
+ }
1403
+ if (give) {
1404
+ /* the chunks handed back, in address order, so neighbours go to the
1405
+ kernel as one madvise: a call per 16 KB chunk was most of the
1406
+ release's time, and half of them sit next to each other */
1407
+ give = sp_slab_sort_avail(give);
1408
+ sp_slab_chunk *gt = give;
1409
+ while (gt) {
1410
+ char *lo = sp_slab_chunk_base(gt), *hi = lo + SP_SLAB_CHUNK;
1411
+ int touched = gt->touched;
1412
+ sp_slab_chunk *run_end = gt;
1413
+ while (run_end->next_avail && sp_slab_chunk_base(run_end->next_avail) == hi &&
1414
+ run_end->next_avail->touched == touched) {
1415
+ run_end = run_end->next_avail; hi += SP_SLAB_CHUNK;
1416
+ }
1417
+ if (touched) {
1418
+ double mt0 = sp_gc_ph_on ? sp_slab_now() : 0;
1419
+ madvise(lo, (size_t)(hi - lo), MADV_DONTNEED);
1420
+ if (sp_gc_ph_on) { sp_slab_rel_madv++; sp_slab_rel_madv_t += sp_slab_now() - mt0; }
1421
+ }
1422
+ sp_slab_chunk *nx = run_end->next_avail;
1423
+ /* on_avail stays SET on a chunk handed back: a sweeper whose free
1424
+ landed before the walk read the chunk as empty can still be short
1425
+ of its own "put it on the list" step, and that step's exchange
1426
+ must find the chunk spoken for, or the owner would pop a chunk the
1427
+ pool has since carved for someone else. The carve (sp_slab_refill)
1428
+ is what clears it. Off the owned list here, before the pool can
1429
+ hand it to a worker that links it into its own. */
1430
+ for (sp_slab_chunk *c2 = gt;; c2 = c2->next_avail) {
1431
+ if (c2->own_prev) c2->own_prev->own_next = c2->own_next;
1432
+ else wk->owned = c2->own_next;
1433
+ if (c2->own_next) c2->own_next->own_prev = c2->own_prev;
1434
+ c2->own_next = c2->own_prev = NULL;
1435
+ memset(sp_slab_bm_of(c2), 0, sizeof(sp_slab_bm));
1436
+ c2->in_use = 0; c2->on_avail = 1; c2->touched = 0;
1437
+ c2->nslots = 0; c2->hint = 0;
1438
+ if (c2 == run_end) break;
1439
+ }
1440
+ gt = nx;
1441
+ }
1442
+ gt = give; while (gt->next_avail) gt = gt->next_avail;
1443
+ SP_SLAB_LOCK();
1444
+ gt->next_avail = sp_slab_empty; sp_slab_empty = give;
1445
+ SP_SLAB_UNLOCK();
1446
+ }
1447
+ if (keep) {
1448
+ double st0 = sp_gc_ph_on ? sp_slab_now() : 0;
1449
+ keep = sp_slab_sort_avail(keep);
1450
+ if (sp_gc_ph_on) sp_slab_rel_sort_t += sp_slab_now() - st0;
1451
+ sp_slab_chunk *kt = keep; while (kt->next_avail) kt = kt->next_avail;
1452
+ #ifdef SP_THREADS
1453
+ sp_slab_chunk *oh;
1454
+ do { oh = SP_ATOMIC_LOAD(&wk->avail[cls], __ATOMIC_ACQUIRE); kt->next_avail = oh;
1455
+ } while (!SP_ATOMIC_CAS(&wk->avail[cls], &oh, keep, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE));
1456
+ #else
1457
+ kt->next_avail = wk->avail[cls]; wk->avail[cls] = keep;
1458
+ #endif
1459
+ }
1460
+ }
1461
+ }
1462
+ /* The collector's part once the owners release their own: the slots no
1463
+ active worker owns (a worker that has exited, the sweeper slot), which
1464
+ nobody else will walk. Under the barrier. */
1465
+ void sp_slab_release_from(int first) {
1466
+ if (sp_slab_on <= 0) return;
1467
+ for (int w = first; w < SP_SLAB_NWK; w++) {
1468
+ sp_slab_worker *wk = &sp_slab_wk[w];
1469
+ for (int cls = 0; cls < SP_SLAB_NCLS; cls++) {
1470
+ if (!wk->avail[cls]) continue;
1471
+ sp_slab_release_worker(w);
1472
+ break;
1473
+ }
1474
+ }
1475
+ }
1476
+ /* every worker's, from one thread with nothing else running (the
1477
+ single-threaded build, or a stop-the-world sweep) */
1478
+ void sp_slab_release(void) {
1479
+ if (sp_slab_on <= 0) return;
1480
+ /* SPINEL_GC_PHASES: the slab's footprint every 64th release -- chunks in
1481
+ use, of which fully free (the reserve), and the bytes their live slots
1482
+ hold -- so the resident set can be read against what is live. */
1483
+ if (sp_gc_ph_on) {
1484
+ static int tick = 0;
1485
+ if ((++tick & 63) == 0) {
1486
+ size_t nuse = 0, nempty = 0, live = 0, untouched = 0;
1487
+ for (uintptr_t a = sp_slab_base; a < sp_slab_brk; a += SP_SLAB_ARENA) {
1488
+ sp_slab_arena *ar = (sp_slab_arena *)a;
1489
+ for (int i = SP_SLAB_FIRST; i < (int)SP_SLAB_NCHUNK; i++) {
1490
+ sp_slab_chunk *ch = &ar->ch[i];
1491
+ if (!ch->in_use) { if (!ch->touched) untouched++; continue; }
1492
+ nuse++;
1493
+ sp_slab_bm *bm = sp_slab_bm_of(ch);
1494
+ size_t used = 0;
1495
+ for (unsigned w = 0; w < (ch->nslots + 63u) / 64u; w++)
1496
+ used += (size_t)SP_POPCOUNT64(bm->young[0][w] | bm->young[1][w] | bm->old[w] | bm->pin[w]);
1497
+ if (!used) nempty++;
1498
+ live += used * sp_slab_csize[ch->cls];
1499
+ }
1500
+ }
1501
+ fprintf(stderr, "[slab] arenas %zu chunks in use %zu (fully free %zu) live in slots %.1f MB resident chunks %.1f MB\n",
1502
+ (size_t)((sp_slab_brk - sp_slab_base) / SP_SLAB_ARENA), nuse, nempty,
1503
+ live / 1048576.0, nuse * (SP_SLAB_CHUNK / 1048576.0));
1504
+ }
1505
+ }
1506
+ for (int w = 0; w < SP_SLAB_NWK; w++) sp_slab_release_worker(w);
1507
+ }