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,1196 @@
|
|
|
1
|
+
#ifndef SP_ALLOC_H
|
|
2
|
+
#define SP_ALLOC_H
|
|
3
|
+
/* Shared GC-string allocation.
|
|
4
|
+
|
|
5
|
+
The string-heap state below is `extern` -- defined once in sp_alloc.c and
|
|
6
|
+
linked from libspinel_rt.a -- so the generated program's single translation
|
|
7
|
+
unit and every standalone lib C file share ONE string heap. A cold runtime C file
|
|
8
|
+
(marshal, pack, json, ...) can therefore allocate GC-tracked strings directly
|
|
9
|
+
without #including spinel_rt.h and inheriting its per-TU `static` heap state
|
|
10
|
+
(which would otherwise create a second, never-swept string heap and leak).
|
|
11
|
+
|
|
12
|
+
The hot allocators stay `static inline` here so each including TU still
|
|
13
|
+
inlines them over the shared extern state -- the same shape sp_gc_alloc
|
|
14
|
+
already uses for the extern sp_gc_heap / sp_gc_bytes object heap. */
|
|
15
|
+
#include "sp_types.h" /* sp_str_hdr, sp_int, sp_float */
|
|
16
|
+
#include "sp_gc.h" /* sp_gc_collect, sp_oom_die, sp_gc_str_sweep_hook */
|
|
17
|
+
#include "sp_time.h" /* sp_Time for sp_box_time */
|
|
18
|
+
#include <stdlib.h>
|
|
19
|
+
#include <string.h>
|
|
20
|
+
#include <stdio.h> /* snprintf for the int/float formatters below */
|
|
21
|
+
#include <math.h> /* HUGE_VAL / signbit for sp_float_to_s */
|
|
22
|
+
|
|
23
|
+
const char *sp_sprintf(const char *fmt, ...) SP_PRINTF_FORMAT(1, 2); /* defined in the generated TU */
|
|
24
|
+
|
|
25
|
+
/* Global heap lock (Phase 1, design 6.1). Under SP_THREADS one mutex serializes
|
|
26
|
+
the object- and string-heap mutations -- the trigger+collect, the calloc/
|
|
27
|
+
malloc, and the list link -- so N>1 workers cannot corrupt the shared heap
|
|
28
|
+
lists or byte counters. It wraps the hot allocators here (sp_str_alloc, in
|
|
29
|
+
this header so the generated TU's inlined copy locks too) and in sp_alloc.c
|
|
30
|
+
(sp_gc_alloc). The internal collect/sweep run under the held lock and never
|
|
31
|
+
re-acquire it (no allocator re-enters from a scan/finalize callback). A single
|
|
32
|
+
non-recursive mutex covers both heaps since a collection from either path
|
|
33
|
+
sweeps the other under the same held lock. No-op (and no pthread dependency)
|
|
34
|
+
in the single-threaded build, so that path is byte-identical.
|
|
35
|
+
NOTE: at N>1 this alone is not sufficient -- a worker doing pure computation
|
|
36
|
+
never reaches the lock, so stop-the-world via safepoints (added with the
|
|
37
|
+
workers) is still required before the collector may run. */
|
|
38
|
+
#ifdef SP_THREADS
|
|
39
|
+
#include <pthread.h>
|
|
40
|
+
extern pthread_mutex_t sp_heap_lock;
|
|
41
|
+
#define SP_HEAP_LOCK() pthread_mutex_lock(&sp_heap_lock)
|
|
42
|
+
#define SP_HEAP_UNLOCK() pthread_mutex_unlock(&sp_heap_lock)
|
|
43
|
+
#else
|
|
44
|
+
#define SP_HEAP_LOCK() ((void)0)
|
|
45
|
+
#define SP_HEAP_UNLOCK() ((void)0)
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
/* ---- shared string-heap state (defined in sp_alloc.c) ----
|
|
49
|
+
Threaded build: the string heap is per-worker (one singly-linked list + live-
|
|
50
|
+
byte counter per OS worker), so sp_str_alloc pushes lock-free onto its own
|
|
51
|
+
worker's list -- the shared sp_heap_lock serialized every string allocation
|
|
52
|
+
and made allocation-heavy parallel workloads scale NEGATIVELY. A started green
|
|
53
|
+
thread is pinned to its worker (home_wid), so only that worker's M ever pushes
|
|
54
|
+
onto its list, and it pumps one green thread at a time: no concurrent push.
|
|
55
|
+
The collector reaches every list under stop-the-world (all workers parked).
|
|
56
|
+
The single-threaded build keeps one global list and stays byte-identical. */
|
|
57
|
+
/* Generations. Allocation always pushes onto the YOUNG list; a string still
|
|
58
|
+
live at a sweep moves to the OLD list, which ordinary (minor) sweeps then
|
|
59
|
+
skip. Without this every sweep walks the entire live set, so a program
|
|
60
|
+
holding a large one -- a Merkle tree of hex digests, a parsed document --
|
|
61
|
+
pays that walk again on each collection no matter how little of it changed.
|
|
62
|
+
Strings have no outgoing references, so there is nothing here of the write
|
|
63
|
+
barrier and forwarding a generational OBJECT heap would need: the split is
|
|
64
|
+
two list heads and a byte counter.
|
|
65
|
+
Reclamation of old strings is deferred to a major sweep, which runs when the
|
|
66
|
+
old generation has itself grown past a threshold. */
|
|
67
|
+
#ifdef SP_THREADS
|
|
68
|
+
/* sp_worker_id / sp_active_workers / SP_MAX_WORKERS: sp_types.h */
|
|
69
|
+
/* One cache-line-padded slot per worker. The list head is written on EVERY
|
|
70
|
+
string allocation and the byte counter read on every one, so as four bare
|
|
71
|
+
[SP_MAX_WORKERS] arrays the per-worker slots sat 8 bytes apart: eight
|
|
72
|
+
workers shared a single line per array, and every allocation invalidated it
|
|
73
|
+
for all of them. The object heap already learned this -- sp_gc_wslot_t
|
|
74
|
+
exists for exactly this reason and its comment calls the padding essential
|
|
75
|
+
-- and the string heap never got the same treatment, which left it the
|
|
76
|
+
dominant cost of allocation-heavy parallel work. */
|
|
77
|
+
/* The young list is SP_STR_YSUB lists, filled round-robin. A sweep task is
|
|
78
|
+
one list, so a worker that allocated most of a cycle's strings (a busy
|
|
79
|
+
request handler among idle workers) has its sweep split across the parked
|
|
80
|
+
workers instead of being the one slot everyone waits on: the longest slot
|
|
81
|
+
was the whole parallel phase, four milliseconds of thirty workers idle. */
|
|
82
|
+
#define SP_STR_YSUB 4
|
|
83
|
+
typedef struct {
|
|
84
|
+
sp_str_hdr *young[SP_STR_YSUB]; /* per-worker young list heads */
|
|
85
|
+
unsigned rr; /* which young list the next allocation joins */
|
|
86
|
+
size_t young_bytes; /* live bytes on those lists */
|
|
87
|
+
sp_str_hdr *old; /* per-worker old list head (swept on a major) */
|
|
88
|
+
size_t old_bytes;
|
|
89
|
+
/* The byte count at which this worker may next ASK for a collection. The
|
|
90
|
+
trigger below fires on this worker's own bytes against the threshold,
|
|
91
|
+
while sp_stw_collect's early-out wants the AGGREGATE over N * threshold --
|
|
92
|
+
so a worker that crosses first is refused, its bytes are not reset by any
|
|
93
|
+
sweep, and it asks again on the very next allocation. Each ask takes the
|
|
94
|
+
global scheduler lock: measured at 7.2 MILLION asks for 941 collections,
|
|
95
|
+
99.99% of them refused, which is uncontended and free at one worker and a
|
|
96
|
+
futex storm at eight (#4334). Raising the mark by one threshold on every
|
|
97
|
+
ask bounds it to one per threshold's worth of allocation. */
|
|
98
|
+
size_t ask_at;
|
|
99
|
+
char _pad[2 * SP_CACHELINE - (SP_STR_YSUB + 1) * sizeof(sp_str_hdr *) - sizeof(unsigned) - 3 * sizeof(size_t)];
|
|
100
|
+
} sp_str_wslot_t;
|
|
101
|
+
extern sp_str_wslot_t sp_str_wslot[SP_MAX_WORKERS];
|
|
102
|
+
#else
|
|
103
|
+
extern sp_str_hdr *sp_str_heap; /* young list head */
|
|
104
|
+
extern size_t sp_str_heap_bytes; /* young string-heap bytes */
|
|
105
|
+
extern sp_str_hdr *sp_str_old; /* old list head */
|
|
106
|
+
extern size_t sp_str_old_bytes; /* old string-heap bytes */
|
|
107
|
+
#endif
|
|
108
|
+
extern size_t sp_str_old_threshold; /* old bytes that trigger a major sweep */
|
|
109
|
+
extern size_t sp_str_old_threshold_init; /* recompute floor for the above */
|
|
110
|
+
extern size_t sp_str_threshold; /* string-GC trigger (own heuristic) */
|
|
111
|
+
extern size_t sp_str_threshold_init; /* recompute floor */
|
|
112
|
+
extern int sp_str_stress_checked; /* one-shot SPINEL_GC_STRESS check */
|
|
113
|
+
extern int sp_gc_stress_pin; /* stress caps the retunes at the 2048 base (#3513) */
|
|
114
|
+
#ifdef SP_THREADS
|
|
115
|
+
void sp_alloc_stress_init(void); /* race-free one-shot stress check (pre-helpers) */
|
|
116
|
+
void sp_alloc_floors_from_env(void);
|
|
117
|
+
void sp_alloc_worker_tune(int workers); /* size the collection budget for N workers (pre-helpers) */
|
|
118
|
+
#endif
|
|
119
|
+
|
|
120
|
+
extern const char sp_str_empty_data[];
|
|
121
|
+
#define sp_str_empty (sp_str_empty_data + 1)
|
|
122
|
+
/* nil.to_s / true.to_s / false.to_s: frozen, as in CRuby (lib/sp_alloc.c) */
|
|
123
|
+
extern const char *const sp_str_frozen_empty;
|
|
124
|
+
extern const char *const sp_str_frozen_true;
|
|
125
|
+
extern const char *const sp_str_frozen_false;
|
|
126
|
+
/* A chilled String is what CRuby's Symbol#to_s answers: not frozen (frozen?
|
|
127
|
+
is false, and a mutation goes through), but +@ copies it as it copies a
|
|
128
|
+
frozen one. Spinel keeps one per symbol (sp_sym_to_s_chilled): static
|
|
129
|
+
storage with a header, the 0xfb marker, whose `next` link names
|
|
130
|
+
sp_str_chilled_tag. A static string is on no sweep list, so the link is
|
|
131
|
+
free to carry the mark, and no marker test -- the collector's included --
|
|
132
|
+
has to learn a new byte. The symbol rides ahead of the header, so a String
|
|
133
|
+
handle made from one can name it (sp_String.chilled). */
|
|
134
|
+
extern const sp_str_hdr sp_str_chilled_tag;
|
|
135
|
+
typedef struct { sp_int sym; sp_str_hdr h; unsigned char m; char d[]; } sp_str_chilled_obj;
|
|
136
|
+
static inline int sp_str_is_chilled(const char *s) {
|
|
137
|
+
return s && ((const unsigned char *)s)[-1] == 0xfb &&
|
|
138
|
+
(((const sp_str_hdr *)(s - 1)) - 1)->next == &sp_str_chilled_tag;
|
|
139
|
+
}
|
|
140
|
+
static inline sp_int sp_str_chilled_sym(const char *s) {
|
|
141
|
+
return ((const sp_str_chilled_obj *)(s - offsetof(sp_str_chilled_obj, d)))->sym;
|
|
142
|
+
}
|
|
143
|
+
/* a nullable string (NULL) as the empty string, the standard C spelling of
|
|
144
|
+
GNU's `s ?: sp_str_empty` */
|
|
145
|
+
static inline const char *sp_str_or_empty(const char *s) { return s ? s : sp_str_empty; }
|
|
146
|
+
|
|
147
|
+
#if defined(_WIN32)
|
|
148
|
+
/* The length of a path's root on Windows, 0 for a relative path: "/", or a
|
|
149
|
+
drive's "C:/" (or "C:\\"), the root CRuby there answers paths under. The
|
|
150
|
+
lexical path helpers (File.expand_path, Dir.glob, File.absolute_path?,
|
|
151
|
+
File.dirname, the tmpdir package) keep a root as it is and work on what
|
|
152
|
+
follows; on POSIX they test for "/" as they always have. */
|
|
153
|
+
static inline size_t sp_file_path_root_len(const char *p) {
|
|
154
|
+
if (!p) return 0;
|
|
155
|
+
if (((p[0] >= 'A' && p[0] <= 'Z') || (p[0] >= 'a' && p[0] <= 'z')) && p[1] == ':' && (p[2] == '/' || p[2] == '\\'))
|
|
156
|
+
return 3;
|
|
157
|
+
return p[0] == '/' ? 1 : 0;
|
|
158
|
+
}
|
|
159
|
+
#endif
|
|
160
|
+
|
|
161
|
+
/* UTF-8 char-length cache. Shared (extern) so sp_str_sweep flushes the same
|
|
162
|
+
table the length helpers in spinel_rt.h populate: a per-TU split would leave
|
|
163
|
+
the generated TU's cache pointing at strings the archive-side sweep already
|
|
164
|
+
freed. */
|
|
165
|
+
/* Two ways per bucket: a scan loop reading one string while measuring another
|
|
166
|
+
(a CSV parser walking the input and its field buffer) collided on every
|
|
167
|
+
character with a direct-mapped table and rescanned both strings each time. */
|
|
168
|
+
#define SP_STR_LCACHE_BITS 6
|
|
169
|
+
#define SP_STR_LCACHE_WAYS 2
|
|
170
|
+
#define SP_STR_LCACHE_SIZE ((1u << SP_STR_LCACHE_BITS) * SP_STR_LCACHE_WAYS)
|
|
171
|
+
struct sp_str_lcache_entry {
|
|
172
|
+
const char *s;
|
|
173
|
+
size_t byte_len;
|
|
174
|
+
sp_int char_len;
|
|
175
|
+
};
|
|
176
|
+
/* Per-worker (SP_TLS) in the threaded build: this string-length cache is keyed
|
|
177
|
+
by string pointer and written without the heap lock (sp_str_byte_len is on the
|
|
178
|
+
hot path), so a shared copy would be a data race across workers -- a torn read
|
|
179
|
+
yields a wrong length and sp_str_concat's memcpy overruns. Each worker keeps
|
|
180
|
+
its own; it is cleared at every safepoint park (before a sweep can recycle a
|
|
181
|
+
cached string's address) and by the string sweep on the collector. */
|
|
182
|
+
extern SP_TLS struct sp_str_lcache_entry sp_str_lcache[SP_STR_LCACHE_SIZE];
|
|
183
|
+
static inline unsigned sp_str_lcache_slot(const char *s) {
|
|
184
|
+
uintptr_t k = (uintptr_t)s;
|
|
185
|
+
return (unsigned)((k ^ (k >> 4) ^ (k >> 12)) & ((1u << SP_STR_LCACHE_BITS) - 1))
|
|
186
|
+
* SP_STR_LCACHE_WAYS;
|
|
187
|
+
}
|
|
188
|
+
/* The markers that carry a real sp_str_hdr in front of the bytes; a raw C
|
|
189
|
+
string (a bare literal, a getenv result) has none and answers "not binary". */
|
|
190
|
+
/* 0xfb = STATIC storage that nonetheless carries a header. The 1-byte
|
|
191
|
+
substring tables live in a static array, so their bytes are never allocated
|
|
192
|
+
and never freed; 0xff already says that, but it says it by having no header
|
|
193
|
+
at all, which leaves nowhere to put the BINARY tag or a NUL-safe length.
|
|
194
|
+
0xfb is 0xff plus a header: it joins the "has a header" group below and
|
|
195
|
+
stays OUT of the "is a mutable heap string" group (sp_gc's sweep,
|
|
196
|
+
setbyte's in-place write, append_grow), so every static-safety property of
|
|
197
|
+
0xff is kept by not being added anywhere. */
|
|
198
|
+
static inline int sp_str_has_hdr(const char *s) {
|
|
199
|
+
unsigned char m = ((const unsigned char *)s)[-1];
|
|
200
|
+
return m == 0xfe || m == 0xfc || m == 0xfd || m == 0xf1 || m == 0xfb || m == 0xfa || m == 0xf8;
|
|
201
|
+
}
|
|
202
|
+
/* "Every byte of this string is below 0x80", verified once and remembered.
|
|
203
|
+
A 7-bit string indexes at fixed width, so #length is its byte length and
|
|
204
|
+
`s[i]` is the byte at i -- no walk, and no probe of the pointer-keyed length
|
|
205
|
+
cache either, which is what the walk had already been folded into. The bit
|
|
206
|
+
is a HINT: set only after a scan proved it, cleared whenever the bytes may
|
|
207
|
+
change, and every reader falls back to the cache when it is off. It says
|
|
208
|
+
nothing about the string's encoding -- US-ASCII and UTF-8 are compatible and
|
|
209
|
+
spinel does not distinguish them (docs/limitations.md). */
|
|
210
|
+
#define SP_STR_SIZE_ASCII7 0x40000000u
|
|
211
|
+
static inline void sp_str_ascii7_clear(const char *s) {
|
|
212
|
+
if (!s || !sp_str_has_hdr(s)) return;
|
|
213
|
+
(((sp_str_hdr *)(s - 1)) - 1)->size &= ~SP_STR_SIZE_ASCII7;
|
|
214
|
+
}
|
|
215
|
+
/* Forget what we know about `s`: its bytes are about to change, or its buffer
|
|
216
|
+
is about to be freed and the address handed to something else. */
|
|
217
|
+
static inline void sp_str_lcache_drop(const char *s) {
|
|
218
|
+
if (!s) return;
|
|
219
|
+
sp_str_ascii7_clear(s);
|
|
220
|
+
unsigned h = sp_str_lcache_slot(s);
|
|
221
|
+
for (unsigned w = 0; w < SP_STR_LCACHE_WAYS; w++)
|
|
222
|
+
if (sp_str_lcache[h + w].s == s) sp_str_lcache[h + w].s = NULL;
|
|
223
|
+
}
|
|
224
|
+
/* Deep-return side channel (#3227): a method whose every return path yields
|
|
225
|
+
a shared-mutable string publishes the sp_String* handle here as the copy
|
|
226
|
+
is read out; a demand-marked call site picks it up right after the call
|
|
227
|
+
(resetting it to NULL first). Never read unless the analysis marked the
|
|
228
|
+
site, so ordinary callers are untouched. */
|
|
229
|
+
extern SP_TLS void *_sp_ret_strbuf;
|
|
230
|
+
|
|
231
|
+
/* Cold; single definitions in sp_alloc.c. sp_str_sweep is wired to the GC via a
|
|
232
|
+
constructor so it runs from sp_gc_collect regardless of which TU triggered
|
|
233
|
+
the collection. */
|
|
234
|
+
void sp_str_sweep(void);
|
|
235
|
+
/* The string-sweep gate, split so the scheduler can run the middle on the
|
|
236
|
+
workers that own the slots (see sp_alloc.c). */
|
|
237
|
+
int sp_str_sweep_begin(int *major);
|
|
238
|
+
void sp_str_sweep_end(int major, size_t promoted);
|
|
239
|
+
void sp_str_sweep_end_excluding(int major, size_t promoted, size_t young_exclude); /* the concurrent sweep's deferred end */
|
|
240
|
+
#ifdef SP_THREADS
|
|
241
|
+
void sp_str_sweep_old_one(int wid);
|
|
242
|
+
void sp_str_sweep_young_list(sp_str_hdr **head, sp_str_hdr **keep, sp_str_hdr **tail, size_t *moved, size_t *held);
|
|
243
|
+
size_t sp_str_sweep_old_list(sp_str_hdr **head);
|
|
244
|
+
size_t sp_str_bytes_total(void); /* every worker's young string bytes */
|
|
245
|
+
void sp_str_sweep_young_one(int wid, int sub, sp_str_hdr **keep, sp_str_hdr **tail,
|
|
246
|
+
size_t *moved, size_t *held);
|
|
247
|
+
void sp_str_sweep_young_done(int wid, sp_str_hdr *keep, sp_str_hdr *tail, size_t moved, size_t held,
|
|
248
|
+
size_t *promoted);
|
|
249
|
+
extern int sp_str_par_done;
|
|
250
|
+
#endif
|
|
251
|
+
void sp_str_lcache_clear(void);
|
|
252
|
+
/* Collect + retune (see sp_alloc.c). The single-threaded allocators call the
|
|
253
|
+
per-heap variants directly; sp_stw_collect (threaded) runs _all under STW. */
|
|
254
|
+
void sp_str_collect_retune(void);
|
|
255
|
+
void sp_gc_collect_retune_all(void);
|
|
256
|
+
int sp_gc_collection_wanted(void);
|
|
257
|
+
void sp_stw_collect(void); /* lib/sp_sched.c: stop-the-world collect (threaded) */
|
|
258
|
+
void sp_gc_collect_request(void); /* explicit GC.start: same barrier, forced */
|
|
259
|
+
|
|
260
|
+
/* SPINEL_ALLOC_REPORT counters (#1336): defined in sp_alloc.c. The `on` flag
|
|
261
|
+
is set once by a constructor from the env var; the hot entry points guard
|
|
262
|
+
their count call on it (one predictable branch when off). */
|
|
263
|
+
extern int sp_alloc_report_on;
|
|
264
|
+
void sp_alloc_report_count(void *scan, size_t bytes);
|
|
265
|
+
void sp_alloc_report_str(size_t bytes);
|
|
266
|
+
void sp_alloc_report_tag(void *scan, const char *name);
|
|
267
|
+
|
|
268
|
+
/* Binary (ASCII-8BIT) strings. spinel keeps no per-string encoding tag:
|
|
269
|
+
String#length counts UTF-8 code points while the bytes stay valid UTF-8 and
|
|
270
|
+
falls back to the byte count once an invalid byte shows up, which lands on
|
|
271
|
+
Ruby's answer for text and for almost all binary data. "Almost" is the gap:
|
|
272
|
+
a short draw of random bytes is valid UTF-8 by chance about 1.5% of the
|
|
273
|
+
time, and Random.urandom(4).length then answered 3 (#3474). One bit says
|
|
274
|
+
"these bytes are data, count them as bytes" for the few producers that know
|
|
275
|
+
it. It rides in the top bit of the header's `size` (the allocation total,
|
|
276
|
+
far below 2GB), so no string grows by a byte. */
|
|
277
|
+
#define SP_STR_SIZE_BINARY 0x80000000u
|
|
278
|
+
/* bit 30 is SP_STR_SIZE_ASCII7 (declared above, with the length cache it
|
|
279
|
+
short-circuits); the allocation total keeps the low 30, so a single string
|
|
280
|
+
is bounded at 1GB rather than 2. Every reader of `size` masks. */
|
|
281
|
+
#define SP_STR_SIZE_MASK 0x3FFFFFFFu
|
|
282
|
+
|
|
283
|
+
static inline char *sp_str_alloc(size_t len) {
|
|
284
|
+
size_t total = sizeof(sp_str_hdr) + 1 + len + 1;
|
|
285
|
+
sp_str_hdr *h;
|
|
286
|
+
/* String-heap pressure drives its own collection (see sp_str_heap_bytes).
|
|
287
|
+
Collect BEFORE the new allocation, like sp_gc_alloc, so the string being
|
|
288
|
+
built isn't yet live during the sweep. Operands of the calling op (e.g. the
|
|
289
|
+
arguments to sp_str_concat) must be reachable across this point -- they are
|
|
290
|
+
for rooted locals; the codegen's SP_GC_ROOT discipline is what keeps them
|
|
291
|
+
so. Threshold recompute mirrors sp_gc_alloc's. */
|
|
292
|
+
#ifdef SP_THREADS
|
|
293
|
+
int wid = sp_worker_id;
|
|
294
|
+
if (!sp_str_stress_checked) { sp_str_stress_checked = 1; const char *e = getenv("SPINEL_GC_STRESS"); if (e && *e && *e != '0') { sp_str_threshold = 2048; sp_str_threshold_init = 2048; sp_gc_stress_pin = 1; } }
|
|
295
|
+
/* Per-worker trigger: the fast path reads only this worker's own byte count,
|
|
296
|
+
no shared state. Each worker fires at the full threshold, so the aggregate
|
|
297
|
+
bound scales with the worker count -- keeping the stop-the-world frequency
|
|
298
|
+
per worker independent of N. A shared aggregate check (threshold/N) instead
|
|
299
|
+
multiplied the collection count by N and left allocation-heavy parallel
|
|
300
|
+
workloads STW-bound; this keeps them flat. */
|
|
301
|
+
{ size_t _yb = SP_GC_CTR_GET(sp_str_wslot[wid].young_bytes);
|
|
302
|
+
/* over the threshold AND past this worker's ask mark; the threshold clause
|
|
303
|
+
stays first so a retune that LOWERS it still triggers at once. */
|
|
304
|
+
if (_yb > sp_str_threshold && _yb >= sp_str_wslot[wid].ask_at) {
|
|
305
|
+
sp_stw_collect();
|
|
306
|
+
/* A collection resets young_bytes, so this lands back at one threshold;
|
|
307
|
+
a refusal leaves it where it was, so the next ask is one threshold of
|
|
308
|
+
allocation later rather than the next allocation. */
|
|
309
|
+
sp_str_wslot[wid].ask_at = SP_GC_CTR_GET(sp_str_wslot[wid].young_bytes) + sp_str_threshold;
|
|
310
|
+
} }
|
|
311
|
+
h = (sp_str_hdr *)sp_slab_alloc_str(total);
|
|
312
|
+
h->size = (uint32_t)total;
|
|
313
|
+
h->len = (uint32_t)len;
|
|
314
|
+
h->hash = 0;
|
|
315
|
+
/* A slab block's generation is a bit in its chunk (lib/sp_slab.c); only
|
|
316
|
+
a string too large for the slab goes on the worker's young list.
|
|
317
|
+
Publish h->next before the head store so a concurrent GC.stat walk that
|
|
318
|
+
observes the new head reaches a fully-linked node (only pushes touch the
|
|
319
|
+
head; the sweep runs under stop-the-world). */
|
|
320
|
+
if (!sp_slab_owns(h)) {
|
|
321
|
+
unsigned sub = sp_str_wslot[wid].rr++ & (SP_STR_YSUB - 1);
|
|
322
|
+
h->next = sp_str_wslot[wid].young[sub];
|
|
323
|
+
sp_str_wslot[wid].young[sub] = h; }
|
|
324
|
+
SP_GC_CTR_ADD(sp_str_wslot[wid].young_bytes, total);
|
|
325
|
+
#else
|
|
326
|
+
SP_HEAP_LOCK();
|
|
327
|
+
if (!sp_str_stress_checked) { sp_str_stress_checked = 1; const char *e = getenv("SPINEL_GC_STRESS"); if (e && *e && *e != '0') { SP_GC_CTR_SET(sp_str_threshold, 2048); sp_str_threshold_init = 2048; sp_gc_stress_pin = 1; } }
|
|
328
|
+
if (SP_GC_CTR_GET(sp_str_heap_bytes) > sp_str_threshold) {
|
|
329
|
+
sp_str_collect_retune(); /* sp_gc_collect runs sp_str_sweep */
|
|
330
|
+
}
|
|
331
|
+
h = (sp_str_hdr *)sp_slab_alloc_str(total);
|
|
332
|
+
h->size = (uint32_t)total;
|
|
333
|
+
h->len = (uint32_t)len;
|
|
334
|
+
h->hash = 0;
|
|
335
|
+
if (!sp_slab_owns(h)) { h->next = sp_str_heap; sp_str_heap = h; }
|
|
336
|
+
SP_GC_CTR_ADD(sp_str_heap_bytes, total);
|
|
337
|
+
SP_HEAP_UNLOCK();
|
|
338
|
+
#endif
|
|
339
|
+
/* Don't fold string-heap pressure into sp_gc_bytes: the threshold heuristic
|
|
340
|
+
in sp_gc_alloc is keyed on object-heap survivors, and the str-heap sweep
|
|
341
|
+
that runs alongside (sp_str_sweep, from sp_gc_collect) doesn't add surviving
|
|
342
|
+
strings back into sp_gc_bytes. Folding them in over-fires the object
|
|
343
|
+
heuristic (the reason they're excluded). */
|
|
344
|
+
char *body = (char *)(h + 1);
|
|
345
|
+
body[0] = (char)0xfe;
|
|
346
|
+
body[1 + len] = 0;
|
|
347
|
+
if (sp_alloc_report_on) sp_alloc_report_str(len);
|
|
348
|
+
return body + 1;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* Allocate a string WITHOUT giving the collector a chance to run first.
|
|
352
|
+
sp_str_alloc collects before allocating, which is right for every ordinary
|
|
353
|
+
build-a-string operation: its operands are rooted. It is wrong for copying a
|
|
354
|
+
pointer that CANNOT be rooted -- a bare C literal has no marker byte for
|
|
355
|
+
sp_mark_string to read, and an unrooted heap string would be swept out from
|
|
356
|
+
under the copy. The raise path takes one or the other on every call, so it
|
|
357
|
+
copies through here. The heap simply runs a little past the threshold; the
|
|
358
|
+
next ordinary allocation collects. */
|
|
359
|
+
static inline char *sp_str_alloc_nogc(size_t len) {
|
|
360
|
+
size_t total = sizeof(sp_str_hdr) + 1 + len + 1;
|
|
361
|
+
sp_str_hdr *h = (sp_str_hdr *)sp_slab_alloc_str(total);
|
|
362
|
+
h->size = (uint32_t)total;
|
|
363
|
+
h->len = (uint32_t)len;
|
|
364
|
+
h->hash = 0;
|
|
365
|
+
#ifdef SP_THREADS
|
|
366
|
+
{ int wid = sp_worker_id;
|
|
367
|
+
if (!sp_slab_owns(h)) {
|
|
368
|
+
unsigned sub = sp_str_wslot[wid].rr++ & (SP_STR_YSUB - 1);
|
|
369
|
+
h->next = sp_str_wslot[wid].young[sub];
|
|
370
|
+
sp_str_wslot[wid].young[sub] = h; }
|
|
371
|
+
SP_GC_CTR_ADD(sp_str_wslot[wid].young_bytes, total); }
|
|
372
|
+
#else
|
|
373
|
+
SP_HEAP_LOCK();
|
|
374
|
+
if (!sp_slab_owns(h)) { h->next = sp_str_heap; sp_str_heap = h; }
|
|
375
|
+
SP_GC_CTR_ADD(sp_str_heap_bytes, total);
|
|
376
|
+
SP_HEAP_UNLOCK();
|
|
377
|
+
#endif
|
|
378
|
+
{ char *body = (char *)(h + 1);
|
|
379
|
+
body[0] = (char)0xfe;
|
|
380
|
+
body[1 + len] = 0;
|
|
381
|
+
if (sp_alloc_report_on) sp_alloc_report_str(len);
|
|
382
|
+
return body + 1; }
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* Copy a message onto the string heap so it can be held by a string root.
|
|
386
|
+
The source is a bare literal (every raise the runtime and the generated
|
|
387
|
+
code issue passes one) or an unrooted heap string; neither can be rooted
|
|
388
|
+
across an allocation, so the copy runs with no collection in between.
|
|
389
|
+
The length is strlen's, not sp_str_byte_len's: a bare literal or a static
|
|
390
|
+
buffer (Process.spawn's sp_err_buf) has no header, and sp_str_byte_len reads
|
|
391
|
+
the byte before it for one, which for some neighbouring byte looks like a
|
|
392
|
+
header's marker and answers a made-up length (#7556 did, and a copied
|
|
393
|
+
message gained NUL bytes in some builds). */
|
|
394
|
+
/* A "counted" message: a raise message that is a Spinel String with a NUL inside it
|
|
395
|
+
(sp_exc_msg_given builds it, #7556). The one place a message's length survives the
|
|
396
|
+
const char * the exception path carries it as, and only for a message the generated
|
|
397
|
+
code gave: it starts with six bytes no C string of ours starts with (a raw buffer's
|
|
398
|
+
neighbour bytes are never read; these are compared from the pointer on, stopping
|
|
399
|
+
at the first mismatch, and a NUL ends any shorter string), then the payload's
|
|
400
|
+
length, then the payload. A bare C string, whose length is strlen's, never matches.
|
|
401
|
+
The sixth byte is 0x02 for a frozen String's message (a literal's), so the
|
|
402
|
+
exception's copy is frozen too and `e.message << x` raises FrozenError, as
|
|
403
|
+
CRuby's message is the literal itself (sp_cmsg_frozen). */
|
|
404
|
+
#define SP_CMSG_HDR 10
|
|
405
|
+
static inline int sp_cmsg_p(const char *m) {
|
|
406
|
+
return m && (unsigned char)m[0] == 0xff && (unsigned char)m[1] == 0xfe && m[2] == 'C' &&
|
|
407
|
+
m[3] == 'M' && (unsigned char)m[4] == 0xfd && ((unsigned char)m[5] == 0x01 || (unsigned char)m[5] == 0x02);
|
|
408
|
+
}
|
|
409
|
+
static inline int sp_cmsg_frozen(const char *m) { return (unsigned char)m[5] == 0x02; }
|
|
410
|
+
static inline size_t sp_cmsg_len(const char *m) { uint32_t n; memcpy(&n, m + 6, sizeof n); return n; }
|
|
411
|
+
static inline const char *sp_msg_heapify(const char *m) {
|
|
412
|
+
if (!m) return NULL;
|
|
413
|
+
if (sp_cmsg_p(m)) { /* stays counted: a later stage decodes it */
|
|
414
|
+
size_t total = SP_CMSG_HDR + sp_cmsg_len(m);
|
|
415
|
+
char *c = sp_str_alloc_nogc(total);
|
|
416
|
+
memcpy(c, m, total);
|
|
417
|
+
return c;
|
|
418
|
+
}
|
|
419
|
+
size_t n = strlen(m);
|
|
420
|
+
char *r = sp_str_alloc_nogc(n);
|
|
421
|
+
memcpy(r, m, n);
|
|
422
|
+
return r;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* Raw variant: the caller writes a NUL-terminated payload whose final
|
|
426
|
+
length it doesn't know yet (worst-case sized transforms: dump, gsub,
|
|
427
|
+
tr, ...). Leave the header length unset so sp_str_byte_len answers
|
|
428
|
+
strlen until sp_str_set_len records the real (possibly NUL-embedded)
|
|
429
|
+
length; a capacity left in the header would over-read on concat and
|
|
430
|
+
break byte-exact equality. */
|
|
431
|
+
#define SP_STR_LEN_UNSET 0xFFFFFFFFu
|
|
432
|
+
static inline char *sp_str_alloc_raw(size_t total_with_null) {
|
|
433
|
+
char *s = sp_str_alloc(total_with_null > 0 ? total_with_null - 1 : 0);
|
|
434
|
+
(((sp_str_hdr *)(s - 1)) - 1)->len = SP_STR_LEN_UNSET;
|
|
435
|
+
return s;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
static inline int sp_str_is_binary(const char *s) {
|
|
439
|
+
if (!s || !sp_str_has_hdr(s)) return 0;
|
|
440
|
+
return (((const sp_str_hdr *)(s - 1)) - 1)->size & SP_STR_SIZE_BINARY ? 1 : 0;
|
|
441
|
+
}
|
|
442
|
+
static inline void sp_str_mark_binary(char *s) {
|
|
443
|
+
if (!s || !sp_str_has_hdr(s)) return;
|
|
444
|
+
(((sp_str_hdr *)(s - 1)) - 1)->size |= SP_STR_SIZE_BINARY;
|
|
445
|
+
}
|
|
446
|
+
/* Fixed-width in ONE header decode: answers the byte length through *out and
|
|
447
|
+
returns 1 when indexing this string is byte indexing (BINARY, or a scan has
|
|
448
|
+
proved it 7-bit). Asking is_binary, then is_ascii7, then byte_len re-read the
|
|
449
|
+
marker and the header three times over -- 66 instructions where the answer is
|
|
450
|
+
two loads. */
|
|
451
|
+
static inline int sp_str_fixed_width(const char *s, size_t *out) {
|
|
452
|
+
if (!s) { *out = 0; return 0; }
|
|
453
|
+
unsigned char m = ((const unsigned char *)s)[-1];
|
|
454
|
+
if (m == 0xfe || m == 0xfc || m == 0xfd || m == 0xf1 || m == 0xfb || m == 0xfa || m == 0xf8) {
|
|
455
|
+
const sp_str_hdr *h = ((const sp_str_hdr *)(s - 1)) - 1;
|
|
456
|
+
if (h->len != SP_STR_LEN_UNSET &&
|
|
457
|
+
(h->size & (SP_STR_SIZE_BINARY | SP_STR_SIZE_ASCII7))) { *out = h->len; return 1; }
|
|
458
|
+
}
|
|
459
|
+
*out = 0;
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
462
|
+
/* Mark and hand back, so an emitter can wrap an expression in it. */
|
|
463
|
+
static inline const char *sp_str_as_binary(const char *s) {
|
|
464
|
+
sp_str_mark_binary((char *)s);
|
|
465
|
+
return s;
|
|
466
|
+
}
|
|
467
|
+
/* The inverse, for force_encoding back to the text side. */
|
|
468
|
+
static inline const char *sp_str_as_text(const char *s) {
|
|
469
|
+
if (s && sp_str_has_hdr(s)) (((sp_str_hdr *)(s - 1)) - 1)->size &= ~SP_STR_SIZE_BINARY;
|
|
470
|
+
return s;
|
|
471
|
+
}
|
|
472
|
+
static inline int sp_str_is_ascii7(const char *s) {
|
|
473
|
+
if (!s || !sp_str_has_hdr(s)) return 0;
|
|
474
|
+
return (((const sp_str_hdr *)(s - 1)) - 1)->size & SP_STR_SIZE_ASCII7 ? 1 : 0;
|
|
475
|
+
}
|
|
476
|
+
static inline void sp_str_mark_ascii7(const char *s) {
|
|
477
|
+
if (!s || !sp_str_has_hdr(s)) return;
|
|
478
|
+
(((sp_str_hdr *)(s - 1)) - 1)->size |= SP_STR_SIZE_ASCII7;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
static inline size_t sp_str_byte_len(const char *s) {
|
|
482
|
+
if (!s) return 0;
|
|
483
|
+
unsigned char marker = ((const unsigned char *)s)[-1];
|
|
484
|
+
/* 0xf1 (frozen heap string / frozen literal) also carries a real sp_str_hdr
|
|
485
|
+
whose len is the true byte length, so an embedded NUL survives freezing
|
|
486
|
+
(#2462 dedup, .freeze). */
|
|
487
|
+
if (marker == 0xfe || marker == 0xfc || marker == 0xfd || marker == 0xf1 || marker == 0xfb || marker == 0xfa || marker == 0xf8) {
|
|
488
|
+
uint32_t l = (((const sp_str_hdr *)(s - 1)) - 1)->len;
|
|
489
|
+
if (l != SP_STR_LEN_UNSET) return l;
|
|
490
|
+
}
|
|
491
|
+
return strlen(s);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
static inline void sp_str_set_len(char *s, size_t len) {
|
|
495
|
+
if (!s) return;
|
|
496
|
+
unsigned char marker = ((unsigned char *)s)[-1];
|
|
497
|
+
if (marker == 0xfe || marker == 0xfc || marker == 0xfd || marker == 0xf1 || marker == 0xfb || marker == 0xfa || marker == 0xf8) {
|
|
498
|
+
sp_str_hdr *hd = ((sp_str_hdr *)(s - 1)) - 1;
|
|
499
|
+
hd->len = (uint32_t)len;
|
|
500
|
+
hd->hash = 0; /* length change implies content change: invalidate cached hash */
|
|
501
|
+
hd->size &= ~SP_STR_SIZE_ASCII7; /* ...and the verified-7-bit answer */
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/* A fresh BINARY-tagged empty string, for the zero-length results whose source
|
|
506
|
+
was binary: CRuby gives `bin[0, 0]`, `bin.byteslice(0, 0)`, `bin * 0` and
|
|
507
|
+
`io.read(0)` the binary encoding, and the shared empty string cannot carry
|
|
508
|
+
it -- marking that would tag it for every other holder of the same pointer.
|
|
509
|
+
So these allocate, and only on that path. */
|
|
510
|
+
static inline char *sp_str_empty_binary(void) {
|
|
511
|
+
char *r = sp_str_alloc_raw(1);
|
|
512
|
+
r[0] = 0;
|
|
513
|
+
sp_str_set_len(r, 0);
|
|
514
|
+
sp_str_mark_binary(r);
|
|
515
|
+
return r;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* Byte count for the binary-safe return modes (FFI `:binstr`, native binding
|
|
519
|
+
`:cbinstr`). A C function returning bytes rather than a NUL-terminated
|
|
520
|
+
string publishes the exact length here just before it returns; the call site
|
|
521
|
+
reads it instead of calling strlen, which would truncate at an embedded NUL.
|
|
522
|
+
Lives here rather than in any one provider because both sp_net (socket
|
|
523
|
+
reads) and sp_crypto (raw digests) write it.
|
|
524
|
+
|
|
525
|
+
Per-worker in the threaded build, for the same reason the buffers it
|
|
526
|
+
describes are: the value is written by the provider and read by the call
|
|
527
|
+
site an expression later, so two workers in the same provider would
|
|
528
|
+
otherwise hand each other the wrong length. The generated TU is compiled
|
|
529
|
+
with the matching -DSP_THREADS, so both sides agree on the storage class. */
|
|
530
|
+
extern SP_TLS int sp_ffi_bin_len;
|
|
531
|
+
|
|
532
|
+
static inline const char *sp_str_from_bytes(const char *data, size_t len) {
|
|
533
|
+
char *s = sp_str_alloc(len);
|
|
534
|
+
if (data) memcpy(s, data, len);
|
|
535
|
+
s[len] = 0;
|
|
536
|
+
return s;
|
|
537
|
+
}
|
|
538
|
+
static inline const char *sp_str_dup_external(const char *s) {
|
|
539
|
+
if (!s) return NULL;
|
|
540
|
+
size_t n = strlen(s);
|
|
541
|
+
char *r = sp_str_alloc(n);
|
|
542
|
+
memcpy(r, s, n);
|
|
543
|
+
return r;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/* Integer / Float -> decimal string. Shared here (over the string heap) so cold
|
|
547
|
+
readers such as lib/sp_json.c can format numbers without spinel_rt.h. */
|
|
548
|
+
/* Interpolation writers: append one part into a caller-provided buffer and
|
|
549
|
+
return the new tail. emit_interp sizes the buffer from static bounds
|
|
550
|
+
(SP_W_INT_MAX digits per int, literal lengths) plus strlen of the
|
|
551
|
+
pre-evaluated dynamic parts, so one sp_str_alloc_raw serves the whole
|
|
552
|
+
string (previously: one heap string per part + an sp_sprintf pass). */
|
|
553
|
+
#define SP_W_INT_MAX 21 /* -9223372036854775808 */
|
|
554
|
+
/* Decimal digits of u, counted up front so the digits can be written in place
|
|
555
|
+
at their final position -- no scratch buffer and no second copy. */
|
|
556
|
+
static inline int sp_u64_ndigits(uint64_t u) {
|
|
557
|
+
int d = 1;
|
|
558
|
+
for (;;) {
|
|
559
|
+
if (u < 10) return d;
|
|
560
|
+
if (u < 100) return d + 1;
|
|
561
|
+
if (u < 1000) return d + 2;
|
|
562
|
+
if (u < 10000) return d + 3;
|
|
563
|
+
u /= 10000; d += 4;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/* Write u's digits so the last one lands just before `end`, two at a time from
|
|
567
|
+
a pair table: half the divisions of the digit-at-a-time loop. */
|
|
568
|
+
static inline void sp_u64_write_back(char *end, uint64_t u) {
|
|
569
|
+
static const char d2[] =
|
|
570
|
+
"00010203040506070809101112131415161718192021222324252627282930313233343536373839"
|
|
571
|
+
"40414243444546474849505152535455565758596061626364656667686970717273747576777879"
|
|
572
|
+
"8081828384858687888990919293949596979899";
|
|
573
|
+
while (u >= 100) {
|
|
574
|
+
unsigned r = (unsigned)(u % 100); u /= 100;
|
|
575
|
+
end -= 2; end[0] = d2[2 * r]; end[1] = d2[2 * r + 1];
|
|
576
|
+
}
|
|
577
|
+
if (u >= 10) { end -= 2; end[0] = d2[2 * u]; end[1] = d2[2 * u + 1]; }
|
|
578
|
+
else *--end = (char)('0' + u);
|
|
579
|
+
}
|
|
580
|
+
/* An Integer the program knows is never the nil sentinel (the analysis says
|
|
581
|
+
no nil reaches it): INTPTR_MIN is that number here, not "" (#7612). */
|
|
582
|
+
static inline char *sp_w_int_plain(char *p, sp_int n) {
|
|
583
|
+
uint64_t u;
|
|
584
|
+
if (n < 0) { *p++ = '-'; u = (uint64_t)(-(n + 1)) + 1; }
|
|
585
|
+
else u = (uint64_t)n;
|
|
586
|
+
p += sp_u64_ndigits(u);
|
|
587
|
+
sp_u64_write_back(p, u);
|
|
588
|
+
return p;
|
|
589
|
+
}
|
|
590
|
+
static inline char *sp_w_int(char *p, sp_int n) {
|
|
591
|
+
if (n == SP_INT_NIL) return p; /* a nil int slot interpolates as "" */
|
|
592
|
+
uint64_t u;
|
|
593
|
+
if (n < 0) { *p++ = '-'; u = (uint64_t)(-(n + 1)) + 1; }
|
|
594
|
+
else u = (uint64_t)n;
|
|
595
|
+
p += sp_u64_ndigits(u);
|
|
596
|
+
sp_u64_write_back(p, u);
|
|
597
|
+
return p;
|
|
598
|
+
}
|
|
599
|
+
/* NOTE: s must be a marked spinel string (heap or codegen literal) --
|
|
600
|
+
sp_str_byte_len reads the marker byte at s[-1], which is out of bounds on
|
|
601
|
+
a foreign C literal. emit_interp therefore uses strlen + memcpy for
|
|
602
|
+
dynamic string parts instead of this helper. */
|
|
603
|
+
static inline char *sp_w_str(char *p, const char *s) {
|
|
604
|
+
if (!s) return p;
|
|
605
|
+
size_t l = sp_str_byte_len(s);
|
|
606
|
+
memcpy(p, s, l);
|
|
607
|
+
return p + l;
|
|
608
|
+
}
|
|
609
|
+
static inline char *sp_w_bool(char *p, sp_bool v) {
|
|
610
|
+
if (v) { memcpy(p, "true", 4); return p + 4; }
|
|
611
|
+
memcpy(p, "false", 5); return p + 5;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/* Integer#to_s. This is one of the hottest allocating calls in the runtime --
|
|
615
|
+
every interpolation of a number, every inspect of an integer array, every
|
|
616
|
+
`j.to_s` in a loop -- and it went through snprintf: __printf_buffer and its
|
|
617
|
+
friends were a fifth of an allocating benchmark's profile. Writing the digits
|
|
618
|
+
by hand also lets the string be allocated at its real length instead of a
|
|
619
|
+
fixed 32, which is less string-heap pressure per call and so fewer
|
|
620
|
+
collections. */
|
|
621
|
+
static inline const char *sp_int_to_s(sp_int n) {
|
|
622
|
+
uint64_t u;
|
|
623
|
+
/* negate in unsigned space: -INT64_MIN does not fit in sp_int */
|
|
624
|
+
if (n < 0) u = (uint64_t)(-(n + 1)) + 1; else u = (uint64_t)n;
|
|
625
|
+
size_t len = (size_t)sp_u64_ndigits(u) + (n < 0);
|
|
626
|
+
/* sp_str_alloc records the exact length and terminates it, so the digits go
|
|
627
|
+
straight into the result. */
|
|
628
|
+
char *b = sp_str_alloc(len);
|
|
629
|
+
sp_u64_write_back(b + len, u);
|
|
630
|
+
if (n < 0) b[0] = '-';
|
|
631
|
+
/* digits and the sign are 7-bit by construction: the length and index paths
|
|
632
|
+
then answer without scanning. The header is a fresh heap one. */
|
|
633
|
+
(((sp_str_hdr *)(b - 1)) - 1)->size |= SP_STR_SIZE_ASCII7;
|
|
634
|
+
return b;
|
|
635
|
+
}
|
|
636
|
+
/* Float#to_s (Ruby semantics): the shortest decimal that round-trips, fixed
|
|
637
|
+
point for a decimal exponent in [-4, 15], scientific otherwise; NaN, ±Infinity
|
|
638
|
+
and -0.0 match CRuby's spelling. */
|
|
639
|
+
/* Float#to_s / #inspect: shortest round-trip decimal. Cold (display only) and
|
|
640
|
+
large, so it lives out-of-line in sp_alloc.c rather than inlining into every
|
|
641
|
+
TU that formats a float. */
|
|
642
|
+
const char *sp_float_to_s(sp_float f);
|
|
643
|
+
|
|
644
|
+
/* ---- container payloads: a hash's tables, an array's data ----
|
|
645
|
+
The blocks a container keeps beside its object (bucket arrays, an
|
|
646
|
+
element buffer) came from malloc, and a request on campfire made 3,600 of
|
|
647
|
+
them: a keyword hash is three 128-byte tables, and glibc's arena lock and
|
|
648
|
+
coalescing were a sixth of the CPU. A block up to the slab's largest
|
|
649
|
+
class comes from the calling worker's slab instead, lock-free; anything
|
|
650
|
+
larger, or the slab off, is malloc as before, and sp_slab_free tells the
|
|
651
|
+
two apart by address. A payload freed by the program (a grow, a rehash)
|
|
652
|
+
goes into the thread's batched free list like a sweep's would. */
|
|
653
|
+
static inline void *sp_pl_alloc(size_t n) { return sp_slab_alloc_raw(n); }
|
|
654
|
+
static inline void *sp_pl_zalloc(size_t n) { void *p = sp_slab_alloc_raw(n); memset(p, 0, n); return p; }
|
|
655
|
+
static inline void sp_pl_free(void *p) { if (p) sp_slab_free(p); }
|
|
656
|
+
void *sp_pl_realloc(void *p, size_t newn); /* lib/sp_slab.c: a slab block knows its size class, a malloc block is realloc'd */
|
|
657
|
+
|
|
658
|
+
/* ---- object construction (shared so lib C files can build values) ----
|
|
659
|
+
The built-in cls_id sentinels, the core sp_box_* constructors, the object
|
|
660
|
+
allocator, and sp_PolyArray. Moved here from spinel_rt.h so a standalone TU
|
|
661
|
+
(sp_pack.c, sp_strscan.c, ...) can allocate and box without the per-TU heap
|
|
662
|
+
state. SP_BUILTIN_FOREIGN_PTR/COMPLEX/RATIONAL are in sp_gc.h. */
|
|
663
|
+
#define SP_BUILTIN_ARRAY_OF(tag) (-(tag) - 1)
|
|
664
|
+
#define SP_BUILTIN_INT_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_INT) /* -1 */
|
|
665
|
+
#define SP_BUILTIN_STR_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_STR) /* -2 */
|
|
666
|
+
#define SP_BUILTIN_FLT_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_FLT) /* -3 */
|
|
667
|
+
#define SP_BUILTIN_PTR_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_OBJ) /* -6 */
|
|
668
|
+
#define SP_BUILTIN_SYM_ARRAY SP_BUILTIN_ARRAY_OF(SP_TAG_SYM) /* -7 */
|
|
669
|
+
#define SP_BUILTIN_PROC (-9)
|
|
670
|
+
#define SP_BUILTIN_RANGE (-10)
|
|
671
|
+
#define SP_BUILTIN_TIME (-11)
|
|
672
|
+
#define SP_BUILTIN_POLY_ARRAY (-12)
|
|
673
|
+
#define SP_BUILTIN_STR_INT_HASH (-13)
|
|
674
|
+
#define SP_BUILTIN_STR_STR_HASH (-14)
|
|
675
|
+
#define SP_BUILTIN_INT_STR_HASH (-15)
|
|
676
|
+
#define SP_BUILTIN_SYM_INT_HASH (-16)
|
|
677
|
+
#define SP_BUILTIN_SYM_STR_HASH (-17)
|
|
678
|
+
#define SP_BUILTIN_STR_POLY_HASH (-18)
|
|
679
|
+
#define SP_BUILTIN_SYM_POLY_HASH (-19)
|
|
680
|
+
#define SP_BUILTIN_POLY_POLY_HASH (-20)
|
|
681
|
+
#define SP_BUILTIN_OBJECT (-21)
|
|
682
|
+
#define SP_BUILTIN_FIBER (-22)
|
|
683
|
+
#define SP_BUILTIN_IO (-23)
|
|
684
|
+
#define SP_BUILTIN_METHOD (-24)
|
|
685
|
+
/* -45, not the -25 this once held: sp_gc.h had already given -25 to
|
|
686
|
+
SP_BUILTIN_FOREIGN_PTR, and the two blocks cannot see each other. An
|
|
687
|
+
Enumerator in a poly slot therefore read as a foreign pointer, which the
|
|
688
|
+
collector deliberately does NOT trace (#4158). */
|
|
689
|
+
#define SP_BUILTIN_ENUMERATOR (-45)
|
|
690
|
+
/* Exception lived at -13, aliasing SP_BUILTIN_STR_INT_HASH, which both put
|
|
691
|
+
exceptions inside the hash cls_id block [-20,-13] (breaking is_a?(Hash) /
|
|
692
|
+
poly .class for exceptions) and made a str_int_hash arriving as a poly value
|
|
693
|
+
misdispatch through the Exception inspect path. Give it a distinct id below
|
|
694
|
+
the hash block so the two no longer collide. */
|
|
695
|
+
#define SP_BUILTIN_EXCEPTION (-28)
|
|
696
|
+
#define SP_BUILTIN_THREAD (-29)
|
|
697
|
+
#define SP_BUILTIN_QUEUE (-30)
|
|
698
|
+
#define SP_BUILTIN_MUTEX (-31)
|
|
699
|
+
#define SP_BUILTIN_CONDVAR (-32)
|
|
700
|
+
/* Integer-keyed Integer-valued hash. It sits BELOW the contiguous hash block
|
|
701
|
+
[-20,-13] (which was full), so the "is this a hash" range checks that test
|
|
702
|
+
that block must also test for this id explicitly. */
|
|
703
|
+
#define SP_BUILTIN_INT_INT_HASH (-34)
|
|
704
|
+
#define SP_BUILTIN_BASIC_OBJECT (-37) /* a bare BasicObject.new instance */
|
|
705
|
+
#define SP_BUILTIN_STRBUF (-40) /* boxed sp_String* handle: a shared-
|
|
706
|
+
mutable string stored in a container
|
|
707
|
+
(#3227 phase 3); reads deref the live
|
|
708
|
+
buffer, identity is the handle */
|
|
709
|
+
#define SP_BUILTIN_DIR (-38) /* an open directory handle (sp_Dir *) */
|
|
710
|
+
#define SP_BUILTIN_TMS (-39) /* Process.times -> Process::Tms */
|
|
711
|
+
#define SP_BUILTIN_ADDRINFO (-46) /* Addrinfo (sp_Addrinfo *). -46, not
|
|
712
|
+
the -40 this once held: STRBUF above
|
|
713
|
+
had it, so a boxed Addrinfo answered
|
|
714
|
+
String and lost every method (#4158) */
|
|
715
|
+
#define SP_BUILTIN_SOCKOPT (-47) /* Socket::Option (sp_SockOpt *). -47,
|
|
716
|
+
not the -41 this once held: sp_gc.h
|
|
717
|
+
had given -41 to OPENSTRUCT (#4158) */
|
|
718
|
+
#define SP_BUILTIN_CURRY (-44) /* Proc#curry accumulator (sp_Curry *):
|
|
719
|
+
boxed so a curried proc survives a
|
|
720
|
+
poly slot, where it read as nil (#3885) */
|
|
721
|
+
#define SP_BUILTIN_MATCHDATA (-43) /* MatchData (sp_MatchData *): boxed so
|
|
722
|
+
a match can live in a container */
|
|
723
|
+
#define SP_BUILTIN_PROCESS_STATUS (-48) /* Process::Status (sp_ProcessStatus *):
|
|
724
|
+
boxed so waitpid2's status carries the
|
|
725
|
+
cls_id for .signaled? dispatch. -48,
|
|
726
|
+
not the -45 first chosen: the upstream
|
|
727
|
+
cls_id-distinctness fix moved
|
|
728
|
+
SP_BUILTIN_ENUMERATOR to -45 (#4158),
|
|
729
|
+
so PROCESS_STATUS had to take the next
|
|
730
|
+
free slot. The switch in spinel_rt.h
|
|
731
|
+
that asserts every id is distinct will
|
|
732
|
+
flag any future collision at compile
|
|
733
|
+
time. */
|
|
734
|
+
#define SP_BUILTIN_RANDOM (-50) /* Random (sp_Random *): boxed so a
|
|
735
|
+
generator in an Array or a poly slot
|
|
736
|
+
keeps its identity; it read as nil */
|
|
737
|
+
/* SP_BUILTIN_ARGF (-51) is in sp_gc.h: the collector must not trace it */
|
|
738
|
+
#define SP_BUILTIN_YIELDER (-49) /* Enumerator::Yielder: the generator's
|
|
739
|
+
block parameter as a VALUE, for a
|
|
740
|
+
proc inside the body that captures
|
|
741
|
+
it. v.p is the generator's own sp_Fiber;
|
|
742
|
+
`y << v` on it is Fiber.yield (the
|
|
743
|
+
proc runs on that fiber). */
|
|
744
|
+
|
|
745
|
+
static inline sp_RbVal sp_box_int(sp_int v) { sp_RbVal r; r.tag = SP_TAG_INT; r.cls_id = 0; r.v.i = v; return r; }
|
|
746
|
+
/* A NULL char* IS Ruby nil throughout the string paths (the nullable-string
|
|
747
|
+
invariant); preserve that across the boxing boundary, or a boxed nil-string
|
|
748
|
+
carries SP_TAG_STR and fails tag-keyed comparisons -- `defined?(x).should
|
|
749
|
+
== nil` compared STR(NULL) against NIL and answered false. */
|
|
750
|
+
static inline sp_RbVal sp_box_str(const char *v){ sp_RbVal r; if (!v) { r.tag = SP_TAG_NIL; r.cls_id = 0; r.v.s = NULL; return r; } r.tag = SP_TAG_STR; r.cls_id = 0; r.v.s = v; return r; }
|
|
751
|
+
/* A String mutator's value from a poly arm, which answers the box or the
|
|
752
|
+
unboxed String by mutator: boxed either way (a NULL String is nil). */
|
|
753
|
+
static inline sp_RbVal sp_box_same(sp_RbVal v){ return v; }
|
|
754
|
+
#define SP_BOX_STR_OR_POLY(x) _Generic((x), sp_RbVal: sp_box_same, default: sp_box_str)(x)
|
|
755
|
+
static inline sp_RbVal sp_box_float(sp_float v){ sp_RbVal r; r.tag = SP_TAG_FLT; r.cls_id = 0; r.v.f = v; return r; }
|
|
756
|
+
/* Write the full union word, not just the narrow `b` member: hash keys and
|
|
757
|
+
poly equality compare bool values through `v.i`, so bytes left
|
|
758
|
+
uninitialized here make two `true`s unequal (a garbage-dependent hash). */
|
|
759
|
+
static inline sp_RbVal sp_box_bool(sp_bool v) { sp_RbVal r; r.tag = SP_TAG_BOOL; r.cls_id = 0; r.v.i = (v != 0); return r; }
|
|
760
|
+
static inline sp_RbVal sp_box_nil(void) { sp_RbVal r; r.tag = SP_TAG_NIL; r.cls_id = 0; r.v.i = 0; return r; }
|
|
761
|
+
static inline sp_RbVal sp_box_obj(void *p, int cls_id) { sp_RbVal r; r.tag = SP_TAG_OBJ; r.cls_id = cls_id; r.v.p = p; return r; }
|
|
762
|
+
static inline sp_RbVal sp_box_sym(sp_sym v) { if (v == (sp_sym)-1) { sp_RbVal n; n.tag = SP_TAG_NIL; n.cls_id = 0; n.v.i = 0; return n; } sp_RbVal r; r.tag = SP_TAG_SYM; r.cls_id = 0; r.v.i = (sp_int)v; return r; } /* (sp_sym)-1 is the nilable-symbol sentinel: box it as nil, never as :"" */
|
|
763
|
+
static inline sp_RbVal sp_box_poly_array(void *p) { return sp_box_obj(p, SP_BUILTIN_POLY_ARRAY); }
|
|
764
|
+
|
|
765
|
+
/* The inverse of sp_box_int_or_nil / the float sentinel: unbox a poly into a
|
|
766
|
+
flat int / float slot that may hold nil. Reading `.v.i` straight off a
|
|
767
|
+
nil-tagged value yields the payload underneath the tag -- 0, an ordinary
|
|
768
|
+
Integer -- so nil silently becomes 0 (or 0.0). These map the nil tag to the
|
|
769
|
+
slot's reserved sentinel instead, which is what every nil? / to_s / boxing
|
|
770
|
+
site on a nullable int or float already tests for. */
|
|
771
|
+
static inline sp_int sp_poly_as_int_or_nil(sp_RbVal v) {
|
|
772
|
+
return v.tag == SP_TAG_NIL ? SP_INT_NIL : v.v.i;
|
|
773
|
+
}
|
|
774
|
+
static inline sp_float sp_poly_as_float_or_nil(sp_RbVal v) {
|
|
775
|
+
return v.tag == SP_TAG_NIL ? sp_float_nil() : v.v.f;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/* GC object allocator. The threshold/stress state is extern (defined in
|
|
779
|
+
sp_alloc.c) so every TU shares it -- the same model as sp_gc_heap/bytes.
|
|
780
|
+
sp_gc_alloc itself is an external function (defined in sp_alloc.c) so the
|
|
781
|
+
cold lib C files that already link it (sp_fiber.c, sp_io.c, sp_bigint.c)
|
|
782
|
+
keep resolving the same symbol. */
|
|
783
|
+
/* SPINEL_GC_OBJ_BUDGET=walk: the object budget is priced off the whole set a
|
|
784
|
+
mark walks, not the object heap alone. Opt-in; see sp_gc_retune_object. */
|
|
785
|
+
extern int sp_gc_obj_budget_mode;
|
|
786
|
+
extern size_t sp_gc_obj_alpha1024;
|
|
787
|
+
extern int sp_gc_str_major_fixed;
|
|
788
|
+
extern int sp_gc_str_major_sched;
|
|
789
|
+
extern size_t sp_gc_str_majors;
|
|
790
|
+
extern int sp_gc_obj_budget_fixed;
|
|
791
|
+
extern int sp_gc_str_budget_fixed;
|
|
792
|
+
extern size_t sp_gc_threshold;
|
|
793
|
+
extern size_t sp_gc_threshold_init;
|
|
794
|
+
extern int sp_gc_stress_checked;
|
|
795
|
+
void *sp_gc_alloc(size_t sz, void (*fin)(void *), void (*scn)(void *));
|
|
796
|
+
/* sp_gc_alloc(sz, NULL, scn) for a size that is a constant where it is
|
|
797
|
+
called: the switch folds to one call, of the front lib/sp_slab.c keeps for
|
|
798
|
+
that size class (16 bytes apart from 32 to 256, the header included). */
|
|
799
|
+
void *sp_gc_alloc_32(size_t need, void (*scn)(void *));
|
|
800
|
+
void *sp_gc_alloc_48(size_t need, void (*scn)(void *));
|
|
801
|
+
void *sp_gc_alloc_64(size_t need, void (*scn)(void *));
|
|
802
|
+
void *sp_gc_alloc_80(size_t need, void (*scn)(void *));
|
|
803
|
+
void *sp_gc_alloc_96(size_t need, void (*scn)(void *));
|
|
804
|
+
void *sp_gc_alloc_112(size_t need, void (*scn)(void *));
|
|
805
|
+
void *sp_gc_alloc_128(size_t need, void (*scn)(void *));
|
|
806
|
+
void *sp_gc_alloc_144(size_t need, void (*scn)(void *));
|
|
807
|
+
void *sp_gc_alloc_160(size_t need, void (*scn)(void *));
|
|
808
|
+
void *sp_gc_alloc_176(size_t need, void (*scn)(void *));
|
|
809
|
+
void *sp_gc_alloc_192(size_t need, void (*scn)(void *));
|
|
810
|
+
void *sp_gc_alloc_208(size_t need, void (*scn)(void *));
|
|
811
|
+
void *sp_gc_alloc_224(size_t need, void (*scn)(void *));
|
|
812
|
+
void *sp_gc_alloc_240(size_t need, void (*scn)(void *));
|
|
813
|
+
void *sp_gc_alloc_256(size_t need, void (*scn)(void *));
|
|
814
|
+
static inline void *sp_gc_alloc_sized(size_t sz, void (*scn)(void *)) {
|
|
815
|
+
size_t need = sizeof(sp_gc_hdr) + sz;
|
|
816
|
+
switch (need <= 32 ? 0 : need > 256 ? -1 : (int)((need + 15) >> 4) - 2) {
|
|
817
|
+
case 0: return sp_gc_alloc_32(need, scn);
|
|
818
|
+
case 1: return sp_gc_alloc_48(need, scn);
|
|
819
|
+
case 2: return sp_gc_alloc_64(need, scn);
|
|
820
|
+
case 3: return sp_gc_alloc_80(need, scn);
|
|
821
|
+
case 4: return sp_gc_alloc_96(need, scn);
|
|
822
|
+
case 5: return sp_gc_alloc_112(need, scn);
|
|
823
|
+
case 6: return sp_gc_alloc_128(need, scn);
|
|
824
|
+
case 7: return sp_gc_alloc_144(need, scn);
|
|
825
|
+
case 8: return sp_gc_alloc_160(need, scn);
|
|
826
|
+
case 9: return sp_gc_alloc_176(need, scn);
|
|
827
|
+
case 10: return sp_gc_alloc_192(need, scn);
|
|
828
|
+
case 11: return sp_gc_alloc_208(need, scn);
|
|
829
|
+
case 12: return sp_gc_alloc_224(need, scn);
|
|
830
|
+
case 13: return sp_gc_alloc_240(need, scn);
|
|
831
|
+
case 14: return sp_gc_alloc_256(need, scn);
|
|
832
|
+
default: return sp_gc_alloc(sz, NULL, scn);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
void *sp_gc_alloc_nogc(size_t sz, void (*fin)(void *), void (*scn)(void *));
|
|
836
|
+
|
|
837
|
+
SP_NORETURN void sp_raise_cls(const char *cls, const char *msg); /* lib/sp_core.c */
|
|
838
|
+
SP_NORETURN void sp_raise_frozen_str(const char *s); /* lib/sp_str.c */
|
|
839
|
+
/* The message carries the rodata marker byte: an in-flight exception's msg is
|
|
840
|
+
marked by the GC (sp_mark_string reads s[-1]), so a bare literal -- whose
|
|
841
|
+
[-1] is out of bounds -- would be UB when it lands at a section edge. */
|
|
842
|
+
static SP_NOINLINE SP_COLD void sp_raise_frozen_array(void) { sp_raise_cls("FrozenError", (&("\xff" "can't modify frozen Array")[1])); }
|
|
843
|
+
/* Same, but stages the receiver so FrozenError#receiver answers the frozen
|
|
844
|
+
object itself (identity-preserving boxing of the mutation target) (#3002).
|
|
845
|
+
sp_exc_stage_recv lives in the generated TU; the ctor transfers the staged
|
|
846
|
+
value onto the raised exception's xrecv slot. */
|
|
847
|
+
void sp_exc_stage_recv(sp_RbVal v);
|
|
848
|
+
/* the raise itself, in lib/sp_cold.c: the message carries the receiver's
|
|
849
|
+
inspect, as CRuby's does ("can't modify frozen Array: [1, 2]") */
|
|
850
|
+
SP_NORETURN void sp_raise_frozen_array_rv(sp_RbVal v);
|
|
851
|
+
static SP_NOINLINE SP_COLD void sp_raise_frozen_array_at(void *a, int cls_id) {
|
|
852
|
+
sp_raise_frozen_array_rv(sp_box_obj(a, cls_id));
|
|
853
|
+
}
|
|
854
|
+
/* boxed-receiver variant (the mutator holds an sp_RbVal, not the raw ptr) */
|
|
855
|
+
static SP_NOINLINE SP_COLD void sp_raise_frozen_array_v(sp_RbVal v) {
|
|
856
|
+
sp_raise_frozen_array_rv(v);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/* Top-level self, the main object (#4926): a bare Object allocated on first
|
|
860
|
+
use, whose to_s / inspect answer "main". sp_main_obj is its pointer, a GC
|
|
861
|
+
root marked with the runtime globals; lib/sp_cold.c. */
|
|
862
|
+
extern void *sp_main_obj;
|
|
863
|
+
sp_RbVal sp_main_self(void);
|
|
864
|
+
|
|
865
|
+
/* sp_PolyArray: a growable array of boxed values. The first
|
|
866
|
+
SP_POLYARR_INLINE elements live in the object itself: an array that never
|
|
867
|
+
outgrows them has no payload, so no finalizer, so the sweep never touches
|
|
868
|
+
it dead (lib/sp_slab.c) -- and most arrays never do. Growing past them
|
|
869
|
+
moves the elements to a payload and installs the finalizer that frees it. */
|
|
870
|
+
#define SP_POLYARR_INLINE 8
|
|
871
|
+
typedef struct { sp_RbVal *data; sp_int len; sp_int cap; sp_int frozen; sp_RbVal inl[SP_POLYARR_INLINE]; } sp_PolyArray;
|
|
872
|
+
static inline void sp_PolyArray_scan(void *p) { sp_PolyArray *a = (sp_PolyArray *)p; for (sp_int i = 0; i < a->len; i++) sp_mark_rbval(a->data[i]); }
|
|
873
|
+
static inline void sp_PolyArray_fin(void *p) { sp_PolyArray *a = (sp_PolyArray *)p; if (a->data == a->inl) return; sp_gc_hdr *h = (sp_gc_hdr *)((char *)a - sizeof(sp_gc_hdr)); sp_gc_bytes_sub(sizeof(sp_RbVal) * a->cap); h->size -= sizeof(sp_RbVal) * a->cap; sp_pl_free(a->data); }
|
|
874
|
+
/* Free-list pool for PolyArray, header AND data buffer kept together. The
|
|
875
|
+
allocation-heaviest programs (per-point tuple building: BabyStark's
|
|
876
|
+
constraint evaluation) churn millions of short-lived PolyArrays; recycling
|
|
877
|
+
them turns the calloc+malloc pair and the sweep-side free into list ops.
|
|
878
|
+
Pool state lives in sp_alloc.c; the recycle hook runs inside the sweep.
|
|
879
|
+
With the slab on there is no pool: the slab's own allocation is as cheap
|
|
880
|
+
as a pop, and a pooled header is one the sweep has to touch dead (to run
|
|
881
|
+
its recycler) where an unpooled one dies in its chunk's bitmap. */
|
|
882
|
+
/* The pool is per thread (sp_alloc.c says why), so the pop is a plain
|
|
883
|
+
list operation on both builds. */
|
|
884
|
+
extern SP_TLS sp_gc_hdr *sp_polyarr_pool_head;
|
|
885
|
+
extern SP_TLS long sp_polyarr_pool_count;
|
|
886
|
+
void sp_PolyArray_pool_recycle(sp_gc_hdr *h);
|
|
887
|
+
/* An Array subclass instance's embedded Array (#7449, see
|
|
888
|
+
sp_IntArray_init_embedded): its elements start inline, and the first growth
|
|
889
|
+
installs the finalizer that frees the payload, as an unpooled one's does. */
|
|
890
|
+
static inline void sp_PolyArray_init_embedded(sp_PolyArray *a) {
|
|
891
|
+
a->data = a->inl; a->cap = SP_POLYARR_INLINE; a->len = 0;
|
|
892
|
+
}
|
|
893
|
+
static inline sp_PolyArray *sp_PolyArray_new(void) {
|
|
894
|
+
if (sp_slab_on > 0) {
|
|
895
|
+
sp_PolyArray *a = (sp_PolyArray *)sp_gc_alloc(sizeof(sp_PolyArray), NULL, sp_PolyArray_scan);
|
|
896
|
+
a->data = a->inl; a->cap = SP_POLYARR_INLINE; a->len = 0;
|
|
897
|
+
return a;
|
|
898
|
+
}
|
|
899
|
+
sp_gc_hdr *ph = sp_polyarr_pool_head;
|
|
900
|
+
if (ph) { sp_polyarr_pool_head = ph->next; sp_polyarr_pool_count--; }
|
|
901
|
+
if (ph) {
|
|
902
|
+
/* back into the live heap (the sweep parked it); size still counts
|
|
903
|
+
header + retained data buffer, so the byte accounting stays exact */
|
|
904
|
+
ph->marked = 0; ph->old = 0; ph->dirty = 0;
|
|
905
|
+
ph->recycle = sp_PolyArray_pool_recycle;
|
|
906
|
+
if (sp_slab_owns(ph)) sp_slab_relive(ph);
|
|
907
|
+
else SP_GC_HEAP_PUSH(ph);
|
|
908
|
+
sp_gc_bytes_add(ph->size);
|
|
909
|
+
sp_PolyArray *a = (sp_PolyArray *)((char *)ph + sizeof(sp_gc_hdr));
|
|
910
|
+
a->len = 0;
|
|
911
|
+
a->frozen = 0;
|
|
912
|
+
return a;
|
|
913
|
+
}
|
|
914
|
+
sp_PolyArray *a = (sp_PolyArray *)sp_gc_alloc(sizeof(sp_PolyArray), sp_PolyArray_fin, sp_PolyArray_scan);
|
|
915
|
+
a->data = a->inl; a->cap = SP_POLYARR_INLINE; a->len = 0;
|
|
916
|
+
{ sp_gc_hdr *h = (sp_gc_hdr *)((char *)a - sizeof(sp_gc_hdr)); h->recycle = sp_PolyArray_pool_recycle; sp_slab_set_fin(h); }
|
|
917
|
+
return a;
|
|
918
|
+
}
|
|
919
|
+
/* past the inline elements: the first growth moves them to a payload and
|
|
920
|
+
gives the object the finalizer that will free it; later growths resize */
|
|
921
|
+
static SP_NOINLINE void sp_PolyArray_grow(sp_PolyArray *a) {
|
|
922
|
+
sp_gc_hdr *h = (sp_gc_hdr *)((char *)a - sizeof(sp_gc_hdr));
|
|
923
|
+
sp_int nc = (a->cap * 2) + 1;
|
|
924
|
+
if (a->data == a->inl) {
|
|
925
|
+
sp_RbVal *nd = (sp_RbVal *)sp_pl_alloc(sizeof(sp_RbVal) * (size_t)nc);
|
|
926
|
+
memcpy(nd, a->inl, sizeof(sp_RbVal) * (size_t)a->len);
|
|
927
|
+
a->data = nd;
|
|
928
|
+
if (!h->finalize) { h->finalize = sp_PolyArray_fin; sp_slab_set_fin(h); }
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
sp_gc_bytes_sub(sizeof(sp_RbVal) * a->cap); h->size -= sizeof(sp_RbVal) * a->cap;
|
|
932
|
+
a->data = (sp_RbVal *)sp_pl_realloc(a->data, sizeof(sp_RbVal) * (size_t)nc);
|
|
933
|
+
}
|
|
934
|
+
a->cap = nc;
|
|
935
|
+
h->size += sizeof(sp_RbVal) * a->cap; sp_gc_bytes_add(sizeof(sp_RbVal) * a->cap);
|
|
936
|
+
}
|
|
937
|
+
static inline void sp_PolyArray_push(sp_PolyArray *a, sp_RbVal v) { if (!a) return; sp_gc_wb((void*)a); if (a->frozen) { sp_raise_frozen_array(); return; } if (a->len >= a->cap) sp_PolyArray_grow(a); a->data[a->len++] = v; }
|
|
938
|
+
static inline sp_RbVal sp_PolyArray_get(sp_PolyArray *a, sp_int i) { if (!a) return sp_box_nil(); if ((unsigned long long)i < (unsigned long long)a->len) return a->data[i]; if (i < 0) i += a->len; if (i < 0 || i >= a->len) return sp_box_nil(); return a->data[i]; }
|
|
939
|
+
/* ---- relocated from spinel_rt.h: frozen-string check primitives used
|
|
940
|
+
by lib/sp_cold.c's sp_str_setbyte_cow, and the SPL frozen-literal macro
|
|
941
|
+
used by lib/sp_cold.c's sp_gc_stat. Pure textual move (still static
|
|
942
|
+
inline / object-like macro), no codegen change. ---- */
|
|
943
|
+
#define SPL(s) (&("\xff" s)[1])
|
|
944
|
+
/* 0xf1 = heap string frozen by an explicit .freeze call.
|
|
945
|
+
Unlike 0xff (rodata literal) this marker lives in a malloc'd buffer
|
|
946
|
+
so sp_str_freeze_val can set it. The frozen? predicate and mutation
|
|
947
|
+
guards check for 0xf1; plain rodata 0xff literals are NOT reported
|
|
948
|
+
as frozen (they behave as immutable value-semantics objects).
|
|
949
|
+
0xfa is a frozen heap string that is collected like any other where 0xf1
|
|
950
|
+
is kept for good; 0xf8 is its marked state on a malloc'd string, as 0xfc
|
|
951
|
+
is 0xfe's (a slab string's mark is a bit in its chunk). */
|
|
952
|
+
static inline sp_bool sp_str_is_frozen_val(const char *s) {
|
|
953
|
+
if (!s) return TRUE;
|
|
954
|
+
unsigned char m = ((const unsigned char *)s)[-1];
|
|
955
|
+
return m == 0xf1 || m == 0xfa || m == 0xf8;
|
|
956
|
+
}
|
|
957
|
+
static inline void sp_str_check_mutable(const char *s) {
|
|
958
|
+
if (sp_str_is_frozen_val(s)) sp_raise_frozen_str(s);
|
|
959
|
+
}
|
|
960
|
+
/* A String key as a Hash stores it: a mutable string is copied into a frozen
|
|
961
|
+
0xfa, so a later `<<` or setbyte on the caller's string cannot move the key
|
|
962
|
+
(CRuby's rb_hash_key_str). A frozen or literal key is stored as is. The
|
|
963
|
+
copy can collect, which clears the write barrier's record of the hash:
|
|
964
|
+
the caller issues its barrier again before storing. */
|
|
965
|
+
static inline const char *sp_hash_key_str(const char *s) {
|
|
966
|
+
unsigned char m = ((const unsigned char *)s)[-1];
|
|
967
|
+
if (m != 0xfe && m != 0xfc && m != 0xfd) return s;
|
|
968
|
+
char *r = (char *)sp_str_from_bytes(s, sp_str_byte_len(s));
|
|
969
|
+
if (sp_str_is_binary(s)) sp_str_mark_binary(r);
|
|
970
|
+
((unsigned char *)r)[-1] = 0xfa;
|
|
971
|
+
return r;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/* ---- relocated from spinel_rt.h: integer add/sub/mul overflow-check
|
|
975
|
+
helpers (still static inline, pure textual move) used by lib/sp_cold.c's
|
|
976
|
+
sp_int_pow. ---- */
|
|
977
|
+
/* the builtin, C23 <stdckdint.h> or a range check, per sp_compat.h */
|
|
978
|
+
static inline sp_bool sp_int_add_overflow_p(sp_int a, sp_int b, sp_int *r) {
|
|
979
|
+
return sp_ckd_add_iptr(a, b, r);
|
|
980
|
+
}
|
|
981
|
+
static inline sp_bool sp_int_sub_overflow_p(sp_int a, sp_int b, sp_int *r) {
|
|
982
|
+
return sp_ckd_sub_iptr(a, b, r);
|
|
983
|
+
}
|
|
984
|
+
static inline sp_bool sp_int_mul_overflow_p(sp_int a, sp_int b, sp_int *r) {
|
|
985
|
+
return sp_ckd_mul_iptr(a, b, r);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/* ---- Integer leaf-op prototypes (bodies relocated to lib/sp_cold.c):
|
|
989
|
+
chr/digits/bit_length/bit_range/to_s_base/opt variants/pow. ---- */
|
|
990
|
+
const char *sp_int_chr(sp_int n);
|
|
991
|
+
const char *sp_int_chr_utf8(sp_int n);
|
|
992
|
+
const char *sp_int_codepoint_to_str(sp_int n);
|
|
993
|
+
const char *sp_int_codepoint_to_str_in(const char *recv, sp_int n);
|
|
994
|
+
sp_IntArray *sp_int_digits(sp_int n, sp_int base);
|
|
995
|
+
sp_int sp_int_bit_length(sp_int n);
|
|
996
|
+
sp_int sp_int_bit_range(sp_int n, sp_int start, sp_int len);
|
|
997
|
+
const char *sp_int_interp(sp_int n);
|
|
998
|
+
const char *sp_int_to_s_base(sp_int n, sp_int base);
|
|
999
|
+
const char *sp_int_opt_inspect(sp_int v);
|
|
1000
|
+
const char *sp_int_opt_to_s(sp_int v);
|
|
1001
|
+
sp_int sp_int_pow(sp_int base, sp_int exp);
|
|
1002
|
+
|
|
1003
|
+
/* ---- Float leaf-op prototypes (bodies relocated to lib/sp_cold.c). ---- */
|
|
1004
|
+
const char *sp_float_opt_inspect(sp_float v);
|
|
1005
|
+
const char *sp_float_opt_to_s(sp_float v);
|
|
1006
|
+
sp_int sp_float_denominator(sp_float f);
|
|
1007
|
+
sp_RbVal sp_float_numerator(sp_float f);
|
|
1008
|
+
/* Float#to_i: an in-range, non-NaN value is a C truncation, done inline;
|
|
1009
|
+
NaN (which fails both tests), Infinity and an out-of-range value go to the
|
|
1010
|
+
raising body in sp_cold.c. The range is exactly the one that body checks. */
|
|
1011
|
+
sp_int sp_float_to_i_checked_slow(sp_float f);
|
|
1012
|
+
static inline sp_int sp_float_to_i_checked(sp_float f) {
|
|
1013
|
+
if (SP_LIKELY(f >= (sp_float)INTPTR_MIN && f < -(sp_float)INTPTR_MIN)) return (sp_int)f;
|
|
1014
|
+
return sp_float_to_i_checked_slow(f);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/* ---- forward declarations for pointer-only box params (full types stay
|
|
1018
|
+
opaque to lib/sp_alloc.h -- these box functions only store the pointer). ---- */
|
|
1019
|
+
typedef struct sp_Bigint sp_Bigint; /* full def: spinel_rt.h bigint block */
|
|
1020
|
+
typedef struct sp_OpenStruct_s sp_OpenStruct; /* full def: spinel_rt.h (SymPolyHash-backed) */
|
|
1021
|
+
typedef struct { sp_float utime, stime, cutime, cstime; } sp_Tms;
|
|
1022
|
+
/* Addrinfo: one resolved endpoint. Immutable; the strings are GC-managed. */
|
|
1023
|
+
typedef struct {
|
|
1024
|
+
const char *ip; /* numeric address, or the path for AF_UNIX */
|
|
1025
|
+
const char *afname; /* "AF_INET" / "AF_INET6" / "AF_UNIX", for #inspect */
|
|
1026
|
+
sp_int afamily; /* the AF_* value, which is what #afamily answers */
|
|
1027
|
+
sp_int port;
|
|
1028
|
+
sp_int socktype; /* SOCK_STREAM / SOCK_DGRAM */
|
|
1029
|
+
sp_int protocol;
|
|
1030
|
+
} sp_Addrinfo;
|
|
1031
|
+
/* Socket::Option: what #getsockopt answers. Spinel carries the integer-valued
|
|
1032
|
+
options only, so the payload is the int itself rather than a byte string. */
|
|
1033
|
+
typedef struct { sp_int family, level, optname, value; } sp_SockOpt;
|
|
1034
|
+
|
|
1035
|
+
/* ---- Box/Encoding helpers relocated from spinel_rt.h: hot-ish ones
|
|
1036
|
+
(sp_box_class 7x / sp_box_nullable_obj 64x / sp_box_int_array 24x /
|
|
1037
|
+
sp_box_float_array 12x / sp_box_str_array 5x / sp_box_range 4x in
|
|
1038
|
+
optcarrot) stay static inline (pure textual move, no codegen change);
|
|
1039
|
+
sp_encoding_name/_inspect/_eq (0 uses) ride along since sp_box_encoding
|
|
1040
|
+
needs sp_encoding_name. ---- */
|
|
1041
|
+
static inline sp_RbVal sp_box_class_name(const char *name) { sp_RbVal r; r.tag = SP_TAG_CLASS; r.cls_id = SP_CLASS_BY_NAME; r.v.s = name; return r; }
|
|
1042
|
+
/* box a sp_Class into a poly slot (a name-backed class boxes by name). */
|
|
1043
|
+
static inline sp_RbVal sp_box_class(sp_Class c) { if (sp_class_nil_p(c)) return sp_box_nil(); if (c.name) return sp_box_class_name(c.name); sp_RbVal r; r.tag = SP_TAG_CLASS; r.cls_id = (int)c.cls_id; r.v.i = c.cls_id; return r; }
|
|
1044
|
+
/* Regexp.escape(s) / Regexp.quote(s) -- prefix every regex metachar
|
|
1045
|
+
and whitespace byte with a single backslash, returning a heap
|
|
1046
|
+
string that callers can feed into `Regexp.new(...)` to match the
|
|
1047
|
+
original bytes literally. Matches CRuby's rb_reg_quote for the
|
|
1048
|
+
ASCII range (the only range Spinel's regex engine indexes today,
|
|
1049
|
+
so multibyte passes through unchanged).
|
|
1050
|
+
|
|
1051
|
+
The metachars covered: \\ . ? * + ^ $ | ( ) [ ] { } # -
|
|
1052
|
+
The whitespace covered: ' ' \t \n \r \f \v
|
|
1053
|
+
Everything else copies byte-for-byte. */
|
|
1054
|
+
static inline sp_RbVal sp_box_nullable_obj(void *p, int cls_id) { return p ? sp_box_obj(p, cls_id) : sp_box_nil(); }
|
|
1055
|
+
/* Same, for a class that has subclasses: the id the object itself carries (its
|
|
1056
|
+
first field) beats the static one, so an inherited method boxing `self`
|
|
1057
|
+
publishes the actual class rather than the one that defined the method. */
|
|
1058
|
+
static inline sp_RbVal sp_box_nullable_obj_dyn(void *p, int cls_id) {
|
|
1059
|
+
(void)cls_id;
|
|
1060
|
+
return p ? sp_box_obj(p, (int)*(sp_int *)p) : sp_box_nil();
|
|
1061
|
+
}
|
|
1062
|
+
/* Built-in pointer boxes -- share SP_TAG_OBJ with a reserved negative
|
|
1063
|
+
cls_id so the dispatch path is uniform. */
|
|
1064
|
+
static inline sp_RbVal sp_box_int_array(void *p) { return sp_box_obj(p, SP_BUILTIN_INT_ARRAY); }
|
|
1065
|
+
static inline sp_RbVal sp_box_float_array(void *p) { return sp_box_obj(p, SP_BUILTIN_FLT_ARRAY); }
|
|
1066
|
+
static inline sp_RbVal sp_box_str_array(void *p) { return sp_box_obj(p, SP_BUILTIN_STR_ARRAY); }
|
|
1067
|
+
/* sp_Range is a 16-byte value type that doesn't fit in sp_RbVal's union
|
|
1068
|
+
(max 8 bytes). When a Range crosses into a poly slot (heterogeneous
|
|
1069
|
+
hash / array / param / ivar), copy it onto the GC heap and box the
|
|
1070
|
+
pointer via SP_BUILTIN_RANGE. The Range has no internal pointer fields
|
|
1071
|
+
so no scanner is needed. */
|
|
1072
|
+
static inline sp_RbVal sp_box_range(sp_Range v) {
|
|
1073
|
+
sp_Range *p = (sp_Range *)sp_gc_alloc(sizeof(sp_Range), NULL, NULL);
|
|
1074
|
+
*p = v;
|
|
1075
|
+
return sp_box_obj(p, SP_BUILTIN_RANGE);
|
|
1076
|
+
}
|
|
1077
|
+
static inline const char*sp_encoding_name(sp_Encoding e){return e.name?e.name:sp_str_empty;}
|
|
1078
|
+
/* Encoding#inspect: the binary encoding reads "BINARY (ASCII-8BIT)" since Ruby 3.4 */
|
|
1079
|
+
static inline const char*sp_encoding_inspect_name(const char*n){return !strcmp(n,"ASCII-8BIT")?sp_sprintf("#<Encoding:BINARY (ASCII-8BIT)>"):sp_sprintf("#<Encoding:%s>",n);}
|
|
1080
|
+
static inline const char*sp_encoding_inspect(sp_Encoding e){return sp_encoding_inspect_name(sp_encoding_name(e));}
|
|
1081
|
+
static inline sp_bool sp_encoding_eq(sp_Encoding a,sp_Encoding b){const char*an=sp_encoding_name(a);const char*bn=sp_encoding_name(b);return strcmp(an,bn)==0;}
|
|
1082
|
+
|
|
1083
|
+
/* ---- Box helper prototypes (0 optcarrot uses; bodies in lib/sp_cold.c). ---- */
|
|
1084
|
+
/* An int? / float? value crossing into a poly slot: the sentinel is nil, never
|
|
1085
|
+
a number. Inline because every boxed element read goes through here. */
|
|
1086
|
+
static inline sp_RbVal sp_box_int_or_nil(sp_int v) { return v == SP_INT_NIL ? sp_box_nil() : sp_box_int(v); }
|
|
1087
|
+
static inline sp_RbVal sp_box_float_or_nil(sp_float v) { return sp_float_is_nil(v) ? sp_box_nil() : sp_box_float(v); }
|
|
1088
|
+
/* An element of an Integer or Float array boxed for a poly container: `nf` is
|
|
1089
|
+
the array's may_nil, read once ahead of the loop, so an array that cannot
|
|
1090
|
+
hold nil boxes its elements as they are. */
|
|
1091
|
+
#define sp_box_int_nf(nf, v) ((nf) ? sp_box_int_or_nil(v) : sp_box_int(v))
|
|
1092
|
+
#define sp_box_float_nf(nf, v) ((nf) ? sp_box_float_or_nil(v) : sp_box_float(v))
|
|
1093
|
+
sp_RbVal sp_unsentinel(sp_RbVal v);
|
|
1094
|
+
sp_RbVal sp_box_bigint(sp_Bigint *b);
|
|
1095
|
+
/* A bigint slot's nil is NULL (the compiler's nil_value for TY_BIGINT), so a
|
|
1096
|
+
nilable bigint boxes as nil rather than as a truthy Integer printing 0. */
|
|
1097
|
+
static inline sp_RbVal sp_box_bigint_or_nil(sp_Bigint *b) { return b ? sp_box_bigint(b) : sp_box_nil(); }
|
|
1098
|
+
/* A 64-bit value as a Ruby Integer: the sp_int when it fits, a Bignum when
|
|
1099
|
+
it does not (a checksum, an unpacked quad, a parsed literal on a 32-bit
|
|
1100
|
+
sp_int). What a package answers as :any for a value that may be wide. */
|
|
1101
|
+
sp_RbVal sp_box_i64(int64_t v);
|
|
1102
|
+
/* the inverse: a boxed Integer (or Float) as a 64-bit value, a Bignum through
|
|
1103
|
+
its low 64 bits, for a package parameter that may be wider than sp_int */
|
|
1104
|
+
int64_t sp_unbox_i64(sp_RbVal v);
|
|
1105
|
+
sp_RbVal sp_box_encoding(sp_Encoding e);
|
|
1106
|
+
sp_RbVal sp_encoding_find(sp_RbVal v);
|
|
1107
|
+
/* force_encoding's tag for an Encoding or a name known at run time */
|
|
1108
|
+
int sp_force_encoding_mode(sp_RbVal enc);
|
|
1109
|
+
sp_RbVal sp_box_nullable_str(const char *v);
|
|
1110
|
+
sp_RbVal sp_box_foreign_ptr(void *p);
|
|
1111
|
+
sp_RbVal sp_box_regexp(void *p);
|
|
1112
|
+
sp_RbVal sp_box_sym_array(void *p);
|
|
1113
|
+
sp_RbVal sp_box_ptr_array(void *p);
|
|
1114
|
+
/* Box a pointer array by reference, stamping what its elements are so the poly
|
|
1115
|
+
paths can read, render and refuse stores faithfully (#4486). */
|
|
1116
|
+
static inline sp_RbVal sp_box_ptr_array_k(void *p, int kind, int cls) {
|
|
1117
|
+
if (!p) return sp_box_nil();
|
|
1118
|
+
sp_PtrArray *a = (sp_PtrArray *)p;
|
|
1119
|
+
if (a->elem_kind == SP_PTR_ELEM_UNKNOWN) { a->elem_kind = kind; a->elem_cls = cls; }
|
|
1120
|
+
return sp_box_obj(p, SP_BUILTIN_PTR_ARRAY);
|
|
1121
|
+
}
|
|
1122
|
+
sp_RbVal sp_box_method(void *p);
|
|
1123
|
+
sp_RbVal sp_box_complex(sp_Complex v);
|
|
1124
|
+
sp_RbVal sp_box_rational(sp_Rational v);
|
|
1125
|
+
sp_RbVal sp_box_time(sp_Time v);
|
|
1126
|
+
sp_RbVal sp_box_tms(sp_Tms v);
|
|
1127
|
+
struct sp_ProcessStatus_s; /* forward decl for the box helper */
|
|
1128
|
+
sp_RbVal sp_box_process_status(struct sp_ProcessStatus_s *v);
|
|
1129
|
+
sp_RbVal sp_box_addrinfo(sp_Addrinfo *v);
|
|
1130
|
+
sp_RbVal sp_box_sockopt(sp_SockOpt *v);
|
|
1131
|
+
void sp_addrinfo_scan(void *p);
|
|
1132
|
+
sp_RbVal sp_box_openstruct(sp_OpenStruct *o);
|
|
1133
|
+
|
|
1134
|
+
/* ---- class-frozen bitmap (Class#freeze / #frozen?): state stays
|
|
1135
|
+
per-process like sp_argv, extern instead of spinel_rt.h-static. ---- */
|
|
1136
|
+
extern unsigned char sp_class_frozen_map[4096]; /* one definition: lib/sp_cold.c */
|
|
1137
|
+
void sp_class_freeze_id(sp_int cls_id);
|
|
1138
|
+
sp_bool sp_class_frozen_id(sp_int cls_id);
|
|
1139
|
+
|
|
1140
|
+
/* ---- rounding helpers relocated from spinel_rt.h (0 optcarrot uses). ---- */
|
|
1141
|
+
double sp_round_half_even(double x);
|
|
1142
|
+
double sp_round_half_down(double x);
|
|
1143
|
+
|
|
1144
|
+
/* ---- BigRational (Bignum-denominator Rational, #2469): 0 optcarrot
|
|
1145
|
+
uses. sp_Bigint is forward-declared above (box_bigint); the specific
|
|
1146
|
+
sp_bigint_* ops below are resolved at the final link against the
|
|
1147
|
+
generated TU, same as sp_sprintf. ---- */
|
|
1148
|
+
typedef struct { sp_Bigint *num, *den; } sp_BigRational;
|
|
1149
|
+
int sp_bigint_sign(sp_Bigint *b);
|
|
1150
|
+
sp_Bigint *sp_bigint_sub(sp_Bigint *a, sp_Bigint *b);
|
|
1151
|
+
sp_Bigint *sp_bigint_new_int(int64_t v);
|
|
1152
|
+
int64_t sp_bigint_to_int(sp_Bigint *b);
|
|
1153
|
+
sp_Bigint *sp_bigint_gcd(sp_Bigint *a, sp_Bigint *b);
|
|
1154
|
+
sp_Bigint *sp_bigint_div(sp_Bigint *a, sp_Bigint *b);
|
|
1155
|
+
sp_Bigint *sp_bigint_add(sp_Bigint *a, sp_Bigint *b);
|
|
1156
|
+
sp_Bigint *sp_bigint_mul(sp_Bigint *a, sp_Bigint *b);
|
|
1157
|
+
int sp_bigint_cmp(sp_Bigint *a, sp_Bigint *b);
|
|
1158
|
+
char *sp_str_alloc_ext(size_t len); /* non-inline sp_str_alloc; see sp_alloc.c */
|
|
1159
|
+
const char *sp_bigint_to_s(sp_Bigint *b);
|
|
1160
|
+
double sp_bigint_to_double(sp_Bigint *b);
|
|
1161
|
+
void sp_brat_scan(void *p);
|
|
1162
|
+
sp_RbVal sp_box_brat(sp_Bigint *num, sp_Bigint *den);
|
|
1163
|
+
sp_RbVal sp_brat_from_bigint(sp_Bigint *n);
|
|
1164
|
+
const char *sp_brat_to_s(sp_BigRational *r);
|
|
1165
|
+
const char *sp_brat_inspect(sp_BigRational *r);
|
|
1166
|
+
sp_float sp_brat_to_f(sp_BigRational *r);
|
|
1167
|
+
/* the exact integer quotients, each rounding the way its method does */
|
|
1168
|
+
sp_Bigint *sp_brat_trunc_b(sp_BigRational *r);
|
|
1169
|
+
sp_Bigint *sp_brat_floor_b(sp_BigRational *r);
|
|
1170
|
+
sp_Bigint *sp_brat_ceil_b(sp_BigRational *r);
|
|
1171
|
+
sp_Bigint *sp_brat_round_b(sp_BigRational *r);
|
|
1172
|
+
|
|
1173
|
+
/* ---- Marshal.dump/load helpers (lib/sp_marshal.c calls these): 0
|
|
1174
|
+
optcarrot uses. sp_marv_hash_new/set stay in spinel_rt.h instead of
|
|
1175
|
+
moving here -- they need sp_PolyPolyHash_new/set, which are hot
|
|
1176
|
+
(called there dozens of times) and
|
|
1177
|
+
whose home is the struct's own definition deep in spinel_rt.h, not
|
|
1178
|
+
this early header; de-static'ing them in place to reach two
|
|
1179
|
+
one-line marv wrappers would grow spinel_rt.h's non-static-body
|
|
1180
|
+
count for no real gain, so those two stay put instead. */
|
|
1181
|
+
sp_RbVal sp_marv_arr_new(void);
|
|
1182
|
+
void sp_marv_arr_push(sp_RbVal a, sp_RbVal v);
|
|
1183
|
+
sp_RbVal sp_marv_box_complex(sp_float re, sp_float im);
|
|
1184
|
+
sp_RbVal sp_marv_box_rational(sp_int n, sp_int d);
|
|
1185
|
+
void sp_marv_raise(const char *cls, const char *msg);
|
|
1186
|
+
|
|
1187
|
+
/* ---- FFI array data pointers, array-kind length probe, sp_Class
|
|
1188
|
+
unboxing: relocated from spinel_rt.h (0 optcarrot uses). ---- */
|
|
1189
|
+
const int64_t *sp_ffi_int_array_data(sp_RbVal v);
|
|
1190
|
+
const double *sp_ffi_float_array_data(sp_RbVal v);
|
|
1191
|
+
const int64_t *sp_PolyArray_ffi_int_data(sp_PolyArray *a);
|
|
1192
|
+
const double *sp_PolyArray_ffi_float_data(sp_PolyArray *a);
|
|
1193
|
+
sp_int sp_array_kind_len(sp_RbVal el);
|
|
1194
|
+
sp_Class sp_unbox_class(sp_RbVal v);
|
|
1195
|
+
|
|
1196
|
+
#endif /* SP_ALLOC_H */
|