qr_reader-native 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE-mruby.txt +22 -0
- data/LICENSE-spinel.txt +19 -0
- data/LICENSE.txt +21 -0
- data/README.md +61 -0
- data/ext/qr_reader_native/SPINEL_VERSION +1 -0
- data/ext/qr_reader_native/extconf.rb +8 -0
- data/ext/qr_reader_native/mruby/error.h +13 -0
- data/ext/qr_reader_native/mruby/internal.h +145 -0
- data/ext/qr_reader_native/mruby/string.h +22 -0
- data/ext/qr_reader_native/mruby.h +93 -0
- data/ext/qr_reader_native/mruby_shim.h +118 -0
- data/ext/qr_reader_native/qr_reader_native.c +13513 -0
- data/ext/qr_reader_native/qr_reader_native.h +29 -0
- data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
- data/ext/qr_reader_native/re_cased.h +71 -0
- data/ext/qr_reader_native/re_compile.c +5784 -0
- data/ext/qr_reader_native/re_ctype.h +3540 -0
- data/ext/qr_reader_native/re_exec.c +2190 -0
- data/ext/qr_reader_native/re_internal.h +671 -0
- data/ext/qr_reader_native/re_prop.h +4629 -0
- data/ext/qr_reader_native/re_spinel.c +363 -0
- data/ext/qr_reader_native/re_spinel.h +22 -0
- data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
- data/ext/qr_reader_native/re_utf8.c +156 -0
- data/ext/qr_reader_native/sp_alloc.c +1447 -0
- data/ext/qr_reader_native/sp_alloc.h +1196 -0
- data/ext/qr_reader_native/sp_argf.h +33 -0
- data/ext/qr_reader_native/sp_array.c +1052 -0
- data/ext/qr_reader_native/sp_array.h +456 -0
- data/ext/qr_reader_native/sp_bigint.c +5826 -0
- data/ext/qr_reader_native/sp_bigint.h +117 -0
- data/ext/qr_reader_native/sp_cold.c +5259 -0
- data/ext/qr_reader_native/sp_compat.h +402 -0
- data/ext/qr_reader_native/sp_core.c +818 -0
- data/ext/qr_reader_native/sp_core.h +56 -0
- data/ext/qr_reader_native/sp_crypto.c +877 -0
- data/ext/qr_reader_native/sp_crypto.h +169 -0
- data/ext/qr_reader_native/sp_dtoa.c +1533 -0
- data/ext/qr_reader_native/sp_dtoa.h +29 -0
- data/ext/qr_reader_native/sp_encoding_names.h +172 -0
- data/ext/qr_reader_native/sp_enum.h +86 -0
- data/ext/qr_reader_native/sp_exc.c +1185 -0
- data/ext/qr_reader_native/sp_exc.h +186 -0
- data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
- data/ext/qr_reader_native/sp_fiber.c +889 -0
- data/ext/qr_reader_native/sp_fiber.h +162 -0
- data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
- data/ext/qr_reader_native/sp_format.c +695 -0
- data/ext/qr_reader_native/sp_format.h +83 -0
- data/ext/qr_reader_native/sp_gc.c +1793 -0
- data/ext/qr_reader_native/sp_gc.h +762 -0
- data/ext/qr_reader_native/sp_hash.c +131 -0
- data/ext/qr_reader_native/sp_hash.h +131 -0
- data/ext/qr_reader_native/sp_inspect.c +215 -0
- data/ext/qr_reader_native/sp_inspect.h +128 -0
- data/ext/qr_reader_native/sp_io.c +1676 -0
- data/ext/qr_reader_native/sp_io.h +272 -0
- data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
- data/ext/qr_reader_native/sp_iobuffer.h +167 -0
- data/ext/qr_reader_native/sp_marshal.c +487 -0
- data/ext/qr_reader_native/sp_marshal.h +60 -0
- data/ext/qr_reader_native/sp_net.c +1029 -0
- data/ext/qr_reader_native/sp_net.h +205 -0
- data/ext/qr_reader_native/sp_pack.c +1254 -0
- data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
- data/ext/qr_reader_native/sp_proc.c +410 -0
- data/ext/qr_reader_native/sp_proc.h +327 -0
- data/ext/qr_reader_native/sp_process.c +547 -0
- data/ext/qr_reader_native/sp_process_status.c +211 -0
- data/ext/qr_reader_native/sp_process_status.h +66 -0
- data/ext/qr_reader_native/sp_random.c +287 -0
- data/ext/qr_reader_native/sp_random.h +52 -0
- data/ext/qr_reader_native/sp_range.h +144 -0
- data/ext/qr_reader_native/sp_re.c +1379 -0
- data/ext/qr_reader_native/sp_re.h +186 -0
- data/ext/qr_reader_native/sp_re_names.h +59 -0
- data/ext/qr_reader_native/sp_sched.c +4044 -0
- data/ext/qr_reader_native/sp_sched.h +295 -0
- data/ext/qr_reader_native/sp_slab.c +1507 -0
- data/ext/qr_reader_native/sp_str.c +1462 -0
- data/ext/qr_reader_native/sp_str.h +306 -0
- data/ext/qr_reader_native/sp_str_crypt.c +23 -0
- data/ext/qr_reader_native/sp_string.c +92 -0
- data/ext/qr_reader_native/sp_string.h +253 -0
- data/ext/qr_reader_native/sp_system.c +78 -0
- data/ext/qr_reader_native/sp_system.h +18 -0
- data/ext/qr_reader_native/sp_time.c +860 -0
- data/ext/qr_reader_native/sp_time.h +100 -0
- data/ext/qr_reader_native/sp_types.h +292 -0
- data/ext/qr_reader_native/spinel_rt.h +19017 -0
- data/ext/qr_reader_native/unicase.c +399 -0
- data/ext/qr_reader_native/unicase.h +901 -0
- data/lib/qr_reader/native.rb +6 -0
- metadata +153 -0
|
@@ -0,0 +1,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
|
+
}
|