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,889 @@
|
|
|
1
|
+
/* sp_fiber.c -- Fiber runtime bodies (POSIX ucontext).
|
|
2
|
+
* See sp_fiber.h. The collector roots, sp_gc_alloc, and sp_raise_cls live
|
|
3
|
+
* in the generated TU and are reached by name; fiber-local storage is
|
|
4
|
+
* self-contained here. */
|
|
5
|
+
/* pthread_getattr_np (sp_thread_stack_bounds, below) is a GNU extension:
|
|
6
|
+
glibc declares it only under _GNU_SOURCE, and clang 16+ (and gcc 14+)
|
|
7
|
+
make the implicit declaration an error rather than a warning. Before
|
|
8
|
+
the first include, as sp_cold.c does for statx. */
|
|
9
|
+
#ifndef _GNU_SOURCE
|
|
10
|
+
#define _GNU_SOURCE
|
|
11
|
+
#endif
|
|
12
|
+
#include "sp_fiber.h"
|
|
13
|
+
#include <stdlib.h>
|
|
14
|
+
#include <string.h>
|
|
15
|
+
#include <sys/mman.h>
|
|
16
|
+
#include <unistd.h>
|
|
17
|
+
|
|
18
|
+
/* OpenBSD requires the stack a coroutine runs on to be MAP_STACK, else the
|
|
19
|
+
first push faults. Linux defines MAP_STACK as a (currently) no-op; keep it
|
|
20
|
+
portable. */
|
|
21
|
+
#ifndef MAP_NORESERVE
|
|
22
|
+
#define MAP_NORESERVE 0 /* macOS, the BSDs: no overcommit accounting to opt out of */
|
|
23
|
+
#endif
|
|
24
|
+
#ifndef MAP_STACK
|
|
25
|
+
#define MAP_STACK 0
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
/* A PROT_NONE guard page below the usable stack: the coroutine stack grows down
|
|
29
|
+
from base+size toward base (see sp_ctx_make), so an overflow runs into the
|
|
30
|
+
guard and faults cleanly instead of silently corrupting whatever mapping sits
|
|
31
|
+
below. f->stack points at the mmap base (guard + stack); the usable region the
|
|
32
|
+
context switch runs on starts sp_fiber_guard() bytes in. Sized to one page
|
|
33
|
+
(queried so 16K/64K-page arm64 hosts stay aligned). */
|
|
34
|
+
size_t sp_fiber_stack_size = 0; /* 0: not decided yet (see sp_fiber_stack_bytes) */
|
|
35
|
+
static int sp_fiber_stack_from_env = 0;
|
|
36
|
+
static size_t sp_fiber_stack_parse(const char *e) {
|
|
37
|
+
char *end = NULL;
|
|
38
|
+
unsigned long long v = strtoull(e, &end, 10);
|
|
39
|
+
if (end == e) return 0;
|
|
40
|
+
if (*end == 'k' || *end == 'K') v <<= 10;
|
|
41
|
+
else if (*end == 'm' || *end == 'M') v <<= 20;
|
|
42
|
+
return (size_t)v;
|
|
43
|
+
}
|
|
44
|
+
/* the size the next fiber's stack is mapped with, decided once */
|
|
45
|
+
static size_t sp_fiber_stack_bytes(void) {
|
|
46
|
+
if (!sp_fiber_stack_size) {
|
|
47
|
+
const char *e = getenv("SPINEL_FIBER_STACK");
|
|
48
|
+
size_t v = e && *e ? sp_fiber_stack_parse(e) : 0;
|
|
49
|
+
if (v) sp_fiber_stack_from_env = 1;
|
|
50
|
+
sp_fiber_stack_size = v ? v : (size_t)SP_FIBER_STACK_SIZE;
|
|
51
|
+
}
|
|
52
|
+
/* whole pages, at least one */
|
|
53
|
+
long p = sysconf(_SC_PAGESIZE); size_t page = p > 0 ? (size_t)p : 4096;
|
|
54
|
+
size_t sz = (sp_fiber_stack_size + page - 1) / page * page;
|
|
55
|
+
return sz < page ? page : sz;
|
|
56
|
+
}
|
|
57
|
+
void sp_fiber_stack_hint(size_t bytes) {
|
|
58
|
+
(void)sp_fiber_stack_bytes(); /* reads the environment first */
|
|
59
|
+
if (!sp_fiber_stack_from_env && bytes) sp_fiber_stack_size = bytes;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static size_t sp_fiber_guard(void) {
|
|
63
|
+
static size_t g = 0;
|
|
64
|
+
if (!g) {
|
|
65
|
+
long p = sysconf(_SC_PAGESIZE);
|
|
66
|
+
size_t page = p > 0 ? (size_t)p : 4096;
|
|
67
|
+
/* SP_FIBER_GUARD_SIZE rounded up to whole pages (see sp_fiber.h) */
|
|
68
|
+
g = ((size_t)SP_FIBER_GUARD_SIZE + page - 1) / page * page;
|
|
69
|
+
if (g < page) g = page;
|
|
70
|
+
}
|
|
71
|
+
return g;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
/* ---- Portable coroutine context switch (replaces swapcontext) ----
|
|
76
|
+
sp_ctx_swap saves the callee-saved registers onto the *current* stack, stows
|
|
77
|
+
the resulting stack pointer in *from, loads *to's stack pointer, and pops the
|
|
78
|
+
callee-saved registers (the very first switch into a coroutine pops the zeroed
|
|
79
|
+
slots sp_ctx_make laid out and `ret`s into the trampoline). It lives in .text
|
|
80
|
+
(W^X-safe) and issues no syscall. See sp_fiber_ctx.h for sp_ctx_make and the
|
|
81
|
+
stack layout it must mirror. */
|
|
82
|
+
#if SP_FIBER_ASM
|
|
83
|
+
#if defined(__APPLE__)
|
|
84
|
+
#define SP_CTX_SYM "_sp_ctx_swap"
|
|
85
|
+
#else
|
|
86
|
+
#define SP_CTX_SYM "sp_ctx_swap"
|
|
87
|
+
#endif
|
|
88
|
+
#if defined(__x86_64__)
|
|
89
|
+
__asm__(
|
|
90
|
+
".text\n"
|
|
91
|
+
".globl " SP_CTX_SYM "\n"
|
|
92
|
+
SP_CTX_SYM ":\n"
|
|
93
|
+
" pushq %rbp\n pushq %rbx\n pushq %r12\n pushq %r13\n pushq %r14\n pushq %r15\n"
|
|
94
|
+
" movq %rsp, (%rdi)\n" /* from->sp = rsp */
|
|
95
|
+
" movq (%rsi), %rsp\n" /* rsp = to->sp */
|
|
96
|
+
" popq %r15\n popq %r14\n popq %r13\n popq %r12\n popq %rbx\n popq %rbp\n"
|
|
97
|
+
" ret\n"
|
|
98
|
+
);
|
|
99
|
+
#elif defined(__aarch64__)
|
|
100
|
+
__asm__(
|
|
101
|
+
".text\n"
|
|
102
|
+
".globl " SP_CTX_SYM "\n"
|
|
103
|
+
SP_CTX_SYM ":\n"
|
|
104
|
+
/* save x19..x30 (12*8) + d8..d15 (8*8) = 160 bytes onto the current stack */
|
|
105
|
+
" stp x19, x20, [sp, #-160]!\n"
|
|
106
|
+
" stp x21, x22, [sp, #16]\n"
|
|
107
|
+
" stp x23, x24, [sp, #32]\n"
|
|
108
|
+
" stp x25, x26, [sp, #48]\n"
|
|
109
|
+
" stp x27, x28, [sp, #64]\n"
|
|
110
|
+
" stp x29, x30, [sp, #80]\n"
|
|
111
|
+
" stp d8, d9, [sp, #96]\n"
|
|
112
|
+
" stp d10, d11, [sp, #112]\n"
|
|
113
|
+
" stp d12, d13, [sp, #128]\n"
|
|
114
|
+
" stp d14, d15, [sp, #144]\n"
|
|
115
|
+
" mov x2, sp\n"
|
|
116
|
+
" str x2, [x0]\n" /* from->sp = sp */
|
|
117
|
+
" ldr x2, [x1]\n" /* sp = to->sp */
|
|
118
|
+
" mov sp, x2\n"
|
|
119
|
+
" ldp d14, d15, [sp, #144]\n"
|
|
120
|
+
" ldp d12, d13, [sp, #128]\n"
|
|
121
|
+
" ldp d10, d11, [sp, #112]\n"
|
|
122
|
+
" ldp d8, d9, [sp, #96]\n"
|
|
123
|
+
" ldp x29, x30, [sp, #80]\n"
|
|
124
|
+
" ldp x27, x28, [sp, #64]\n"
|
|
125
|
+
" ldp x25, x26, [sp, #48]\n"
|
|
126
|
+
" ldp x23, x24, [sp, #32]\n"
|
|
127
|
+
" ldp x21, x22, [sp, #16]\n"
|
|
128
|
+
" ldp x19, x20, [sp], #160\n"
|
|
129
|
+
" ret\n"
|
|
130
|
+
);
|
|
131
|
+
#endif
|
|
132
|
+
#else /* ucontext fallback */
|
|
133
|
+
void sp_ctx_swap(sp_fiber_ctx *from, sp_fiber_ctx *to) { swapcontext(&from->uc, &to->uc); }
|
|
134
|
+
#endif
|
|
135
|
+
|
|
136
|
+
/* ---- Reached by name in the generated TU ---- */
|
|
137
|
+
void *sp_gc_alloc(size_t sz, void (*fin)(void *), void (*scn)(void *));
|
|
138
|
+
SP_NORETURN void sp_raise_cls(const char *cls, const char *msg);
|
|
139
|
+
|
|
140
|
+
/* Per-fiber exception/catch handler context (#1474): defined in the generated
|
|
141
|
+
TU (spinel_rt.h), reached here by name. A fiber's begin/rescue handlers and
|
|
142
|
+
catch tags are saved/restored around each context switch so a raise/throw
|
|
143
|
+
can't longjmp into a suspended fiber's stack frame, and an unhandled fiber
|
|
144
|
+
raise is re-raised in the resumer's context rather than crossing stacks. */
|
|
145
|
+
#include <setjmp.h>
|
|
146
|
+
void *sp_exc_ctx_new(void);
|
|
147
|
+
void sp_exc_ctx_free(void *p);
|
|
148
|
+
void sp_exc_ctx_save(void *p);
|
|
149
|
+
void sp_exc_ctx_load(void *p);
|
|
150
|
+
void sp_exc_ctx_mark(void *p);
|
|
151
|
+
void sp_exc_arm(jmp_buf b);
|
|
152
|
+
void sp_exc_disarm(void);
|
|
153
|
+
const char *sp_exc_cur_cls(void);
|
|
154
|
+
const char *sp_exc_cur_msg(void);
|
|
155
|
+
void *sp_exc_cur_obj(void);
|
|
156
|
+
void sp_fiber_reraise(const char *cls, const char *msg, void *obj);
|
|
157
|
+
|
|
158
|
+
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; }
|
|
159
|
+
static void sp_raise(const char *msg) {SP_GC_ROOT_STR(msg); sp_raise_cls("RuntimeError", msg); }
|
|
160
|
+
|
|
161
|
+
/* Fiber-local variable storage (Fiber#[] / Fiber#[]=). A small sym->RbVal
|
|
162
|
+
map, GC-allocated so the collector marks its values via the scan hook;
|
|
163
|
+
self-contained here rather than borrowing the runtime's sym_poly_hash so
|
|
164
|
+
the runtime sym_poly_hash stays collector-private to the generated TU. */
|
|
165
|
+
typedef struct { sp_sym *keys; sp_RbVal *vals; sp_int len, cap; } sp_FiberStore;
|
|
166
|
+
static void sp_FiberStore_scan(void *p) { sp_FiberStore *s = (sp_FiberStore *)p; for (sp_int i = 0; i < s->len; i++) sp_mark_rbval(s->vals[i]); }
|
|
167
|
+
static void sp_FiberStore_fin(void *p) { sp_FiberStore *s = (sp_FiberStore *)p; free(s->keys); free(s->vals); }
|
|
168
|
+
static sp_FiberStore *sp_FiberStore_new(void) {
|
|
169
|
+
sp_FiberStore *s = (sp_FiberStore *)sp_gc_alloc(sizeof(sp_FiberStore), sp_FiberStore_fin, sp_FiberStore_scan);
|
|
170
|
+
s->cap = 8; s->len = 0;
|
|
171
|
+
s->keys = (sp_sym *)malloc(sizeof(sp_sym) * s->cap);
|
|
172
|
+
s->vals = (sp_RbVal *)malloc(sizeof(sp_RbVal) * s->cap);
|
|
173
|
+
if (!s->keys || !s->vals) {
|
|
174
|
+
/* If one side succeeded, release it now rather than waiting for the
|
|
175
|
+
finalizer -- and NULL both so that finalizer's free stays a no-op. */
|
|
176
|
+
free(s->keys); free(s->vals);
|
|
177
|
+
s->keys = NULL; s->vals = NULL;
|
|
178
|
+
sp_raise_cls("NoMemoryError", "failed to allocate fiber storage");
|
|
179
|
+
}
|
|
180
|
+
return s;
|
|
181
|
+
}
|
|
182
|
+
static sp_RbVal sp_FiberStore_get(sp_FiberStore *s, sp_sym k) {
|
|
183
|
+
for (sp_int i = 0; i < s->len; i++) if (s->keys[i] == k) return s->vals[i];
|
|
184
|
+
return sp_box_nil();
|
|
185
|
+
}
|
|
186
|
+
static void sp_FiberStore_set(sp_FiberStore *s, sp_sym k, sp_RbVal v) {SP_GC_ROOT_RBVAL(v);SP_GC_ROOT(s); sp_gc_wb((void*)s);
|
|
187
|
+
for (sp_int i = 0; i < s->len; i++) if (s->keys[i] == k) { s->vals[i] = v; return; }
|
|
188
|
+
if (s->len == s->cap) {
|
|
189
|
+
/* Grow each array into a temp and commit only on success, so a failed
|
|
190
|
+
realloc leaves the store intact (the old buffer survives) instead of
|
|
191
|
+
NULL-deref'ing on the write below or double-freeing a moved buffer. */
|
|
192
|
+
sp_int nc = s->cap * 2;
|
|
193
|
+
sp_sym *nk = (sp_sym *)realloc(s->keys, sizeof(sp_sym) * nc);
|
|
194
|
+
if (!nk) sp_raise_cls("NoMemoryError", "failed to grow fiber storage");
|
|
195
|
+
s->keys = nk;
|
|
196
|
+
sp_RbVal *nv = (sp_RbVal *)realloc(s->vals, sizeof(sp_RbVal) * nc);
|
|
197
|
+
if (!nv) sp_raise_cls("NoMemoryError", "failed to grow fiber storage");
|
|
198
|
+
s->vals = nv; s->cap = nc;
|
|
199
|
+
}
|
|
200
|
+
s->keys[s->len] = k; s->vals[s->len] = v; s->len++;
|
|
201
|
+
}
|
|
202
|
+
/* drop key k, keeping the order of the rest */
|
|
203
|
+
static void sp_FiberStore_delete(sp_FiberStore *s, sp_sym k) {
|
|
204
|
+
for (sp_int i = 0; i < s->len; i++) {
|
|
205
|
+
if (s->keys[i] != k) continue;
|
|
206
|
+
for (sp_int j = i + 1; j < s->len; j++) {
|
|
207
|
+
s->keys[j - 1] = s->keys[j];
|
|
208
|
+
s->vals[j - 1] = s->vals[j];
|
|
209
|
+
}
|
|
210
|
+
s->len--;
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
static sp_FiberStore *sp_FiberStore_dup(sp_FiberStore *o) {SP_GC_ROOT(o);
|
|
215
|
+
sp_FiberStore *s = sp_FiberStore_new();
|
|
216
|
+
for (sp_int i = 0; i < o->len; i++) sp_FiberStore_set(s, o->keys[i], o->vals[i]);
|
|
217
|
+
return s;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* ThreadSanitizer fiber instrumentation (see sp_fiber.h SP_TSAN). Each fiber
|
|
221
|
+
gets a __tsan fiber handle; before every context switch we tell TSan which
|
|
222
|
+
fiber will run next so its happens-before tracking follows the cooperative
|
|
223
|
+
switch instead of choking on the asm stack swap. No-ops when not building
|
|
224
|
+
under -fsanitize=thread. */
|
|
225
|
+
#ifdef SP_TSAN
|
|
226
|
+
extern void *__tsan_get_current_fiber(void);
|
|
227
|
+
extern void *__tsan_create_fiber(unsigned flags);
|
|
228
|
+
extern void __tsan_destroy_fiber(void *fiber);
|
|
229
|
+
extern void __tsan_switch_to_fiber(void *fiber, unsigned flags);
|
|
230
|
+
#define SP_TSAN_SET_CALLER(f, who) ((f)->caller_fiber = (who))
|
|
231
|
+
#define SP_TSAN_SWITCH(to) do { sp_Fiber *_st = (to); if (_st && _st->tsan_fiber) __tsan_switch_to_fiber(_st->tsan_fiber, 0); } while (0)
|
|
232
|
+
#else
|
|
233
|
+
#define SP_TSAN_SET_CALLER(f, who) ((void)0)
|
|
234
|
+
#define SP_TSAN_SWITCH(to) ((void)0)
|
|
235
|
+
#endif
|
|
236
|
+
|
|
237
|
+
/* The root fiber is a static, not a GC allocation -- but `Fiber.current`
|
|
238
|
+
hands it to user code, where a rooted local makes the collector mark
|
|
239
|
+
it. Lay a 0xfd skip byte directly before it so sp_gc_mark's tag-byte
|
|
240
|
+
protocol bails out instead of treating .bss as a GC header. The guard
|
|
241
|
+
array is exactly one alignment unit, so its last byte always directly
|
|
242
|
+
precedes `root` (no compiler padding can slip in between). */
|
|
243
|
+
/* In the threaded build the root fiber is per-worker (SP_TLS): each OS worker's
|
|
244
|
+
native scheduler stack is its own root coroutine, so the green thread running
|
|
245
|
+
on a worker transfers back to *that* worker's root. The 0xfd guard byte still
|
|
246
|
+
directly precedes `root` in each thread's TLS block, so sp_gc_mark's tag-byte
|
|
247
|
+
protocol skips it there too. sp_fiber_current cannot be statically initialized
|
|
248
|
+
to &sp_fiber_root once the root is thread-local (that address is not a
|
|
249
|
+
constant), so each worker sets it via sp_fiber_worker_init before running any
|
|
250
|
+
fiber; the single-threaded build keeps the constant static init unchanged
|
|
251
|
+
(byte-identical). */
|
|
252
|
+
static SP_TLS struct { char guard[_Alignof(sp_Fiber)]; sp_Fiber root; } sp_fiber_root_box
|
|
253
|
+
= { .guard = { [_Alignof(sp_Fiber) - 1] = (char)0xfd }, .root = {0} };
|
|
254
|
+
#define sp_fiber_root (sp_fiber_root_box.root)
|
|
255
|
+
#ifdef SP_THREADS
|
|
256
|
+
SP_TLS sp_Fiber *sp_fiber_current = NULL; /* set by sp_fiber_worker_init */
|
|
257
|
+
#else
|
|
258
|
+
SP_TLS sp_Fiber *sp_fiber_current = &sp_fiber_root; /* extern: read by the generated TU */
|
|
259
|
+
#endif
|
|
260
|
+
|
|
261
|
+
/* Adopt the calling OS thread's root fiber as its current coroutine. Called once
|
|
262
|
+
per worker before it runs any green thread (worker 0 from sp_sched_init; helper
|
|
263
|
+
workers from their startup). A no-op-shaped reset in the single-threaded build
|
|
264
|
+
(sp_fiber_current already equals &sp_fiber_root). */
|
|
265
|
+
/* A fault inside the current fiber's guard is a stack overflow, and it is
|
|
266
|
+
reported as one: without this the program died in ___chkstk_darwin on macOS
|
|
267
|
+
or in a corrupted neighbour mapping on Linux, and nothing named the fiber
|
|
268
|
+
stack (#4496). The handler runs on a per-thread alternate stack, since the
|
|
269
|
+
fiber's own is what just ran out, writes the line with write(2) only, then
|
|
270
|
+
restores the default disposition and returns so the faulting instruction
|
|
271
|
+
re-executes and the process dies the ordinary way (a core, the signal in
|
|
272
|
+
the exit status). Any other fault is left to the default just the same.
|
|
273
|
+
Installed once, only when nothing else has claimed the signal (the GC
|
|
274
|
+
verifier's fault reporter, for one). */
|
|
275
|
+
#include <signal.h>
|
|
276
|
+
#include <stdio.h>
|
|
277
|
+
#include <sys/resource.h>
|
|
278
|
+
#ifdef SP_THREADS
|
|
279
|
+
#include <pthread.h> /* only where the build has it: see sp_thread_stack_bounds */
|
|
280
|
+
#endif
|
|
281
|
+
/* Unblock signal a (and b, when nonzero) for the calling thread: a handler
|
|
282
|
+
that leaves by a jump instead of a return has to, or the signal stays
|
|
283
|
+
blocked (see sp_trap_call in spinel_rt.h). */
|
|
284
|
+
void sp_sig_unblock(int a, int b) {
|
|
285
|
+
sigset_t m; sigemptyset(&m); sigaddset(&m, a); if (b) sigaddset(&m, b);
|
|
286
|
+
#ifdef SP_THREADS
|
|
287
|
+
pthread_sigmask(SIG_UNBLOCK, &m, 0);
|
|
288
|
+
#else
|
|
289
|
+
sigprocmask(SIG_UNBLOCK, &m, 0); /* one thread: the same mask */
|
|
290
|
+
#endif
|
|
291
|
+
}
|
|
292
|
+
static void sp_fiber_fault_write(const char *s) { size_t n = strlen(s); while (n) { ssize_t w = write(2, s, n); if (w <= 0) break; s += w; n -= (size_t)w; } }
|
|
293
|
+
static void sp_fiber_fault_write_num(size_t v) { char b[24]; int i = (int)sizeof b; b[--i] = 0; do { b[--i] = (char)('0' + v % 10); v /= 10; } while (v); sp_fiber_fault_write(b + i); }
|
|
294
|
+
static void sp_fiber_fault_handler(int sig, siginfo_t *si, void *uctx) {
|
|
295
|
+
(void)uctx;
|
|
296
|
+
sp_Fiber *f = sp_fiber_current;
|
|
297
|
+
char *a = si ? (char *)si->si_addr : NULL;
|
|
298
|
+
/* The thread's own stack ran past its end: the fault lands in the OS guard
|
|
299
|
+
just below it. Read as an ordinary segfault this killed the process where
|
|
300
|
+
CRuby raises SystemStackError and lets a rescue continue. The raise is
|
|
301
|
+
the generated TU's (it owns the exception stack). */
|
|
302
|
+
if (sp_stack_overflow_raise_fn && a && sp_thread_stack_lo &&
|
|
303
|
+
a < sp_thread_stack_lo && a >= sp_thread_stack_lo - (ptrdiff_t)(1 << 20) &&
|
|
304
|
+
(!f || f == &sp_fiber_root || !f->stack)) {
|
|
305
|
+
/* unblock the signal first: this raise does not return, and the mask it
|
|
306
|
+
was entered with would otherwise stay blocked in the frame we jump to
|
|
307
|
+
(on Linux longjmp does not restore it) */
|
|
308
|
+
sp_sig_unblock(SIGSEGV, SIGBUS);
|
|
309
|
+
#ifdef __APPLE__
|
|
310
|
+
/* _longjmp (sp_types.h) also leaves the kernel's on-alt-stack flag set,
|
|
311
|
+
so the next overflow's signal has nowhere to go and the process dies
|
|
312
|
+
of SIGILL. Clear it the way Darwin's longjmp does. */
|
|
313
|
+
{ extern int __sigreturn(void *, int, uintptr_t);
|
|
314
|
+
__sigreturn(NULL, 0x80000000 /* UC_RESET_ALT_STACK */, 0); }
|
|
315
|
+
#endif
|
|
316
|
+
sp_stack_overflow_raise_fn(); /* returns only if no handler is armed */
|
|
317
|
+
/* Nothing to rescue it: report it as the uncaught exception it is and
|
|
318
|
+
leave, rather than letting the default disposition kill the process on
|
|
319
|
+
a signal with nothing said. Writing a fixed string and _exit are all
|
|
320
|
+
this handler may do -- the allocator is not safe to re-enter from a
|
|
321
|
+
signal, and the stack a formatter would want is the one that just ran
|
|
322
|
+
out. */
|
|
323
|
+
/* what the program already printed is still in stdout's buffer, and
|
|
324
|
+
_exit does not flush it: a run that printed a hundred lines and then
|
|
325
|
+
overflowed would otherwise show none of them. The at_exit hooks do NOT
|
|
326
|
+
run here -- they are Ruby code, and the stack they would run on is the
|
|
327
|
+
one that just ran out. */
|
|
328
|
+
fflush(stdout);
|
|
329
|
+
sp_fiber_fault_write("stack level too deep (SystemStackError)\n");
|
|
330
|
+
_exit(1);
|
|
331
|
+
}
|
|
332
|
+
if (f && f != &sp_fiber_root && f->stack && a >= f->stack && a < f->stack + sp_fiber_guard()) {
|
|
333
|
+
sp_fiber_fault_write("spinel: fiber stack overflow: a green thread, Fiber or Enumerator body ran past the ");
|
|
334
|
+
sp_fiber_fault_write_num(f->stack_size / 1024);
|
|
335
|
+
sp_fiber_fault_write(" KB C stack the fiber runs on (a deep call chain, a large local, or an unoptimised build "
|
|
336
|
+
"whose frames are far larger than -O2's); SPINEL_FIBER_STACK=<bytes> in the environment gives it more\n");
|
|
337
|
+
}
|
|
338
|
+
signal(sig, SIG_DFL);
|
|
339
|
+
}
|
|
340
|
+
void (*sp_stack_overflow_raise_fn)(void) = 0;
|
|
341
|
+
SP_TLS char *sp_thread_stack_lo = 0;
|
|
342
|
+
SP_TLS char *sp_thread_stack_hi = 0;
|
|
343
|
+
|
|
344
|
+
/* The running thread's stack bounds. A fault just below the low end is this
|
|
345
|
+
stack growing past it -- the OS puts its own guard page there, which is
|
|
346
|
+
what the fault is.
|
|
347
|
+
|
|
348
|
+
With threads, pthread answers for every stack including the main one (on
|
|
349
|
+
macOS the address it returns is the HIGH end; on Linux the attr pair gives
|
|
350
|
+
base and size). A program built without them has one stack, the process's
|
|
351
|
+
own: arming happens at startup, so a local here is within a frame or two of
|
|
352
|
+
its high end, and the soft RLIMIT_STACK is its size. That is asked of
|
|
353
|
+
<sys/resource.h> rather than of pthread, because a target may have no
|
|
354
|
+
pthread at all -- a pack of an unthreaded program is built with whatever
|
|
355
|
+
compiler its recipient has, and <pthread.h> there may not even compile. */
|
|
356
|
+
static void sp_thread_stack_bounds(char *here) {
|
|
357
|
+
#ifdef SP_THREADS
|
|
358
|
+
(void)here;
|
|
359
|
+
pthread_t self = pthread_self();
|
|
360
|
+
#if defined(__APPLE__)
|
|
361
|
+
char *hi = (char *)pthread_get_stackaddr_np(self);
|
|
362
|
+
size_t sz = pthread_get_stacksize_np(self);
|
|
363
|
+
sp_thread_stack_hi = hi;
|
|
364
|
+
sp_thread_stack_lo = hi - sz;
|
|
365
|
+
#else
|
|
366
|
+
pthread_attr_t at;
|
|
367
|
+
void *base = 0; size_t sz = 0;
|
|
368
|
+
if (pthread_getattr_np(self, &at) == 0) {
|
|
369
|
+
if (pthread_attr_getstack(&at, &base, &sz) != 0) { base = 0; sz = 0; }
|
|
370
|
+
pthread_attr_destroy(&at);
|
|
371
|
+
}
|
|
372
|
+
sp_thread_stack_lo = (char *)base;
|
|
373
|
+
sp_thread_stack_hi = (char *)base + sz;
|
|
374
|
+
#endif
|
|
375
|
+
#else
|
|
376
|
+
struct rlimit rl;
|
|
377
|
+
size_t sz = 8u << 20; /* the common default, when the limit says nothing */
|
|
378
|
+
if (getrlimit(RLIMIT_STACK, &rl) == 0 && rl.rlim_cur != RLIM_INFINITY &&
|
|
379
|
+
rl.rlim_cur > (rlim_t)(64u << 10))
|
|
380
|
+
sz = (size_t)rl.rlim_cur;
|
|
381
|
+
/* `here` is a frame or two below the true top, not at it, and the low end is
|
|
382
|
+
derived by subtracting the size -- so under-estimating the top would put
|
|
383
|
+
the computed low end BELOW the real one, and the real guard page would sit
|
|
384
|
+
above the band the handler tests rather than in it. Over-estimate instead:
|
|
385
|
+
64 KB of startup frames is far more than the few hundred bytes actually
|
|
386
|
+
used, and the computed low end then sits just inside the stack, which is
|
|
387
|
+
where the handler's window wants it. */
|
|
388
|
+
sp_thread_stack_hi = here + (64u << 10);
|
|
389
|
+
sp_thread_stack_lo = sp_thread_stack_hi - sz;
|
|
390
|
+
#endif
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
static SP_TLS int sp_fiber_fault_armed = 0;
|
|
394
|
+
/* this thread's sigaltstack buffer; external, so the store is not optimized away */
|
|
395
|
+
SP_TLS void *sp_fiber_altstack;
|
|
396
|
+
static void sp_fiber_fault_arm(void) {
|
|
397
|
+
if (sp_fiber_fault_armed) return;
|
|
398
|
+
sp_fiber_fault_armed = 1;
|
|
399
|
+
{ char here; sp_thread_stack_bounds(&here); }
|
|
400
|
+
/* the alternate stack is per OS thread; workers live as long as the process.
|
|
401
|
+
Its only other reference is the kernel's, so it is kept here too: a leak
|
|
402
|
+
checker (valgrind) reported the buffer as definitely lost. */
|
|
403
|
+
size_t asz = 64 * 1024;
|
|
404
|
+
void *as = malloc(asz);
|
|
405
|
+
sp_fiber_altstack = as;
|
|
406
|
+
if (as) { stack_t ss; ss.ss_sp = as; ss.ss_size = asz; ss.ss_flags = 0; sigaltstack(&ss, NULL); }
|
|
407
|
+
static int installed = 0; /* process-wide: read racily, a second install is harmless */
|
|
408
|
+
if (installed) return;
|
|
409
|
+
int sigs[2] = { SIGSEGV, SIGBUS };
|
|
410
|
+
for (int i = 0; i < 2; i++) {
|
|
411
|
+
struct sigaction cur;
|
|
412
|
+
if (sigaction(sigs[i], NULL, &cur) != 0) continue;
|
|
413
|
+
if (!(cur.sa_flags & SA_SIGINFO) && cur.sa_handler != SIG_DFL) continue;
|
|
414
|
+
if ((cur.sa_flags & SA_SIGINFO) && cur.sa_sigaction) continue;
|
|
415
|
+
struct sigaction sa; memset(&sa, 0, sizeof sa);
|
|
416
|
+
sa.sa_sigaction = sp_fiber_fault_handler;
|
|
417
|
+
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
|
418
|
+
sigemptyset(&sa.sa_mask);
|
|
419
|
+
sigaction(sigs[i], &sa, NULL);
|
|
420
|
+
}
|
|
421
|
+
installed = 1;
|
|
422
|
+
}
|
|
423
|
+
/* Arm the fault handler for a program that never creates a fiber: the C
|
|
424
|
+
stack it overflows is the thread's own, and the handler is what turns that
|
|
425
|
+
into SystemStackError. Fiber creation and worker startup arm it too, so
|
|
426
|
+
this is a no-op once either has run. */
|
|
427
|
+
void sp_stack_guard_init(void) { sp_fiber_fault_arm(); }
|
|
428
|
+
|
|
429
|
+
/* ---- the stack the program body runs on -------------------------------
|
|
430
|
+
The OS gives a process one stack and decides how big: 8 MB on macOS, and
|
|
431
|
+
a recursion that is merely deep -- generated code reaches depths written
|
|
432
|
+
code does not -- runs out of it long before it has done anything wrong.
|
|
433
|
+
The body runs on a stack mapped here instead, so its depth is this
|
|
434
|
+
compiler's choice rather than the loader's.
|
|
435
|
+
|
|
436
|
+
This deliberately does NOT go through sp_Fiber. A fiber would bring its
|
|
437
|
+
bookkeeping -- state, roots, exception context, a resumer to hand back to
|
|
438
|
+
-- and an overflow raised past all that leaves it mid-flight. The body
|
|
439
|
+
needs none of it: it is entered once, it returns once, and nothing
|
|
440
|
+
transfers to it. So it takes the context primitives on their own, and the
|
|
441
|
+
root fiber stays what it already is, the implicit running coroutine with
|
|
442
|
+
no mmap'd stack of its own.
|
|
443
|
+
|
|
444
|
+
The guard is the same PROT_NONE page a fiber's stack gets, and the raise
|
|
445
|
+
that reads it is the one already in this file: sp_thread_stack_lo/hi are
|
|
446
|
+
pointed at the new stack, so the handler's own test needs no change. */
|
|
447
|
+
static size_t sp_main_stack_size = 0;
|
|
448
|
+
static int sp_main_stack_from_env = 0;
|
|
449
|
+
void sp_main_stack_hint(size_t bytes) {
|
|
450
|
+
if (!sp_main_stack_from_env && bytes) sp_main_stack_size = bytes;
|
|
451
|
+
}
|
|
452
|
+
static size_t sp_main_stack_bytes(void) {
|
|
453
|
+
if (!sp_main_stack_size) {
|
|
454
|
+
const char *e = getenv("SPINEL_MAIN_STACK");
|
|
455
|
+
size_t v = e && *e ? sp_fiber_stack_parse(e) : 0;
|
|
456
|
+
if (v) sp_main_stack_from_env = 1;
|
|
457
|
+
/* A 64-bit address space is wide, but the reservation is not free
|
|
458
|
+
everywhere: with overcommit disabled the mapping is charged against
|
|
459
|
+
commit even though MAP_NORESERVE asks otherwise, so the default is a
|
|
460
|
+
size that is defensible to pay for outright rather than the largest
|
|
461
|
+
one the address space would hold. 32-bit space is scarcer still. */
|
|
462
|
+
sp_main_stack_size = v ? v
|
|
463
|
+
: (sizeof(void *) < 8 ? (size_t)24 << 20
|
|
464
|
+
: (size_t)64 << 20);
|
|
465
|
+
}
|
|
466
|
+
long p = sysconf(_SC_PAGESIZE); size_t page = p > 0 ? (size_t)p : 4096;
|
|
467
|
+
size_t sz = (sp_main_stack_size + page - 1) / page * page;
|
|
468
|
+
/* a stack too small to hold the startup frames is worse than none: floor it
|
|
469
|
+
at a size that certainly works, whatever the environment asked for */
|
|
470
|
+
if (sz < (size_t)(1u << 20)) sz = (size_t)1u << 20;
|
|
471
|
+
return sz;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
static char *sp_main_stack_base = 0; /* mmap base (guard + stack), or NULL */
|
|
475
|
+
static size_t sp_main_stack_map = 0;
|
|
476
|
+
static void (*sp_main_stack_body)(void) = 0;
|
|
477
|
+
static sp_fiber_ctx sp_main_stack_caller;
|
|
478
|
+
static sp_fiber_ctx sp_main_stack_ctx;
|
|
479
|
+
|
|
480
|
+
static void sp_main_stack_entry(void) {
|
|
481
|
+
sp_main_stack_body();
|
|
482
|
+
/* back to main, on the stack the loader gave it, to return from there */
|
|
483
|
+
sp_ctx_swap(&sp_main_stack_ctx, &sp_main_stack_caller);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/* Run `body` on a stack of this compiler's choosing. If one cannot be had --
|
|
487
|
+
any size, down to the floor -- run it where we are: a program must not fail
|
|
488
|
+
to start because it could not reserve a big stack. */
|
|
489
|
+
void sp_main_stack_run(void (*body)(void)) {
|
|
490
|
+
size_t guard = sp_fiber_guard();
|
|
491
|
+
size_t want = sp_main_stack_bytes();
|
|
492
|
+
char *base = MAP_FAILED;
|
|
493
|
+
for (;;) {
|
|
494
|
+
base = (char *)mmap(NULL, guard + want, PROT_READ | PROT_WRITE,
|
|
495
|
+
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK | MAP_NORESERVE, -1, 0);
|
|
496
|
+
if (base != MAP_FAILED) break;
|
|
497
|
+
if (want <= (size_t)(1u << 20)) break; /* the floor: give up on mapping */
|
|
498
|
+
want /= 2; /* halve and ask again */
|
|
499
|
+
}
|
|
500
|
+
if (base == MAP_FAILED) { body(); return; }
|
|
501
|
+
mprotect(base, guard, PROT_NONE);
|
|
502
|
+
sp_main_stack_base = base; sp_main_stack_map = guard + want;
|
|
503
|
+
sp_main_stack_body = body;
|
|
504
|
+
/* Arm FIRST: arming reads the running thread's own bounds, and would
|
|
505
|
+
overwrite the ones set here if it ran after them. Then say where the
|
|
506
|
+
stack really is -- the handler reads these to tell a stack that ran out
|
|
507
|
+
from a bad pointer, and the guard page below `lo` is the mprotect'd one
|
|
508
|
+
above. */
|
|
509
|
+
sp_stack_guard_init();
|
|
510
|
+
sp_thread_stack_lo = base + guard;
|
|
511
|
+
sp_thread_stack_hi = base + guard + want;
|
|
512
|
+
sp_ctx_make(&sp_main_stack_ctx, base + guard, want, sp_main_stack_entry);
|
|
513
|
+
sp_ctx_swap(&sp_main_stack_caller, &sp_main_stack_ctx);
|
|
514
|
+
/* the body returned; nothing runs on that stack any more */
|
|
515
|
+
sp_thread_stack_lo = 0; sp_thread_stack_hi = 0;
|
|
516
|
+
munmap(sp_main_stack_base, sp_main_stack_map);
|
|
517
|
+
sp_main_stack_base = 0; sp_main_stack_map = 0;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
void sp_fiber_worker_init(void) {
|
|
521
|
+
sp_fiber_current = &sp_fiber_root;
|
|
522
|
+
sp_fiber_fault_arm();
|
|
523
|
+
#ifdef SP_TSAN
|
|
524
|
+
/* Register this worker's root fiber on its own OS thread, so every worker --
|
|
525
|
+
including a helper spawned mid-run that only ever runs fibers created by
|
|
526
|
+
another thread -- has a valid tsan handle to switch back to. Without this,
|
|
527
|
+
switching to the root (SP_TSAN_SWITCH) is a silent no-op and TSan loses the
|
|
528
|
+
thread's current-fiber tracking, faulting on the next real switch. */
|
|
529
|
+
if (!sp_fiber_root.tsan_fiber) sp_fiber_root.tsan_fiber = __tsan_get_current_fiber();
|
|
530
|
+
#endif
|
|
531
|
+
}
|
|
532
|
+
sp_Fiber *sp_fiber_worker_root(void) { return &sp_fiber_root; }
|
|
533
|
+
static sp_Fiber *sp_fiber_list_head = NULL;
|
|
534
|
+
|
|
535
|
+
static void sp_fiber_save_roots(sp_Fiber*f){if(f->stack)sp_gc_wb((void*)f);/* the snapshot may name young objects, and only a scanned fiber follows it now: an old fiber has to be remembered (#4525) */if(f->saved_roots_cap<sp_gc_nroots){int nc=sp_gc_nroots>64?sp_gc_nroots*2:64;void***nx=(void***)realloc(f->saved_roots,sizeof(void**)*nc);if(!nx)return;f->saved_roots=nx;f->saved_roots_cap=nc;}/* both segments: the array, then whatever spilled past it (sp_gc.h) */
|
|
536
|
+
int n1=sp_gc_nroots<SP_GC_STACK_MAX?sp_gc_nroots:SP_GC_STACK_MAX;if(n1>0)memcpy(f->saved_roots,sp_gc_roots,sizeof(void**)*n1);if(sp_gc_nroots>n1)memcpy(f->saved_roots+n1,sp_gc_roots_ext,sizeof(void**)*(sp_gc_nroots-n1));f->saved_nroots=sp_gc_nroots;}
|
|
537
|
+
static void sp_fiber_restore_roots(sp_Fiber*f){int n=f->saved_nroots;int n1=n<SP_GC_STACK_MAX?n:SP_GC_STACK_MAX;if(n1>0)memcpy(sp_gc_roots,f->saved_roots,sizeof(void**)*n1);if(n>n1){if(!sp_gc_roots_ext_reserve(n))sp_oom_die();memcpy(sp_gc_roots_ext,f->saved_roots+n1,sizeof(void**)*(n-n1));}sp_gc_nroots=n;}
|
|
538
|
+
/* Snapshot the calling worker's live shadow-stack roots into the green thread it
|
|
539
|
+
is running, so a stop-the-world collector can mark them while this worker is
|
|
540
|
+
parked at a safepoint. The collector reaches them via the suspended-fibers GC
|
|
541
|
+
hook (every fiber but the collector's own current is marked from saved_roots). */
|
|
542
|
+
void sp_fiber_publish_current_roots(void){if(sp_fiber_current)sp_fiber_save_roots(sp_fiber_current);}
|
|
543
|
+
/* The global fiber list is mutated by Fiber.new from any worker (list_add) and
|
|
544
|
+
walked by the collector during stop-the-world (all mutators parked, so the
|
|
545
|
+
walk needs no lock). A small mutex serializes concurrent list_add/remove; the
|
|
546
|
+
critical section is a few pointer writes with no safepoint poll inside, so a
|
|
547
|
+
worker never parks holding it and the collector never waits on it. */
|
|
548
|
+
#ifdef SP_THREADS
|
|
549
|
+
#include <pthread.h>
|
|
550
|
+
static pthread_mutex_t sp_fiber_list_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
551
|
+
#define FIBER_LIST_LOCK() pthread_mutex_lock(&sp_fiber_list_lock)
|
|
552
|
+
#define FIBER_LIST_UNLOCK() pthread_mutex_unlock(&sp_fiber_list_lock)
|
|
553
|
+
#else
|
|
554
|
+
#define FIBER_LIST_LOCK() ((void)0)
|
|
555
|
+
#define FIBER_LIST_UNLOCK() ((void)0)
|
|
556
|
+
#endif
|
|
557
|
+
static void sp_fiber_list_add(sp_Fiber*f){FIBER_LIST_LOCK();f->fiber_prev=NULL;f->fiber_next=sp_fiber_list_head;if(sp_fiber_list_head)sp_fiber_list_head->fiber_prev=f;sp_fiber_list_head=f;FIBER_LIST_UNLOCK();}
|
|
558
|
+
static void sp_fiber_list_remove(sp_Fiber*f){FIBER_LIST_LOCK();if(f->fiber_prev)f->fiber_prev->fiber_next=f->fiber_next;else if(sp_fiber_list_head==f)sp_fiber_list_head=f->fiber_next;if(f->fiber_next)f->fiber_next->fiber_prev=f->fiber_prev;f->fiber_prev=NULL;f->fiber_next=NULL;FIBER_LIST_UNLOCK();}
|
|
559
|
+
/* Mark a fiber's published (saved) roots and pending-exception objects. Public
|
|
560
|
+
so the thread scheduler can mark a parked worker's per-worker root fiber, which
|
|
561
|
+
is not on sp_fiber_list_head (only Fiber.new fibers are) and so would otherwise
|
|
562
|
+
be missed when a *different* worker is the stop-the-world collector. */
|
|
563
|
+
void sp_fiber_mark_roots(sp_Fiber*f){SP_GC_ROOT(f);int i;for(i=0;i<f->saved_nroots;i++){void**e=f->saved_roots[i];if((uintptr_t)e&(uintptr_t)3){sp_gc_mark_root_entry(e);}
|
|
564
|
+
else{void*obj=*e;if(obj)sp_gc_mark(obj);}}if(f->exc_ctx)sp_exc_ctx_mark(f->exc_ctx);if(f->raised_obj)sp_gc_mark(f->raised_obj);if(f->inj_obj)sp_gc_mark(f->inj_obj);}
|
|
565
|
+
/* The fibers whose C stacks are live: the running one (its roots are the
|
|
566
|
+
collector's own) and the chain of resumers waiting for it, each suspended
|
|
567
|
+
inside #resume with a frame that continues when the resumed fiber yields.
|
|
568
|
+
Those are roots. Every other suspended fiber is an object like any other:
|
|
569
|
+
it lives while something refers to it, and its saved roots are marked when
|
|
570
|
+
it is (sp_Fiber_scan). Rooting the whole fiber list kept an object alive
|
|
571
|
+
through the fiber it owned (`@loop = Fiber.new { loop { tick } }`, whose
|
|
572
|
+
block captures self), so neither was ever collected (#4525). */
|
|
573
|
+
/* The fiber the collection runs on: its saved roots are stale (the live ones
|
|
574
|
+
are the root array) and no scanner may read them -- a parallel mark helper
|
|
575
|
+
on another worker has its own sp_fiber_current, so the test cannot be TLS.
|
|
576
|
+
Set by the fibers hook, which runs before any object is scanned. */
|
|
577
|
+
static sp_Fiber *sp_fiber_collecting = NULL;
|
|
578
|
+
void sp_fiber_mark_chain(sp_Fiber*f){for(sp_Fiber*g=f;g;g=g->resumer){if(g==sp_fiber_current||g==sp_fiber_collecting)continue;if(g->stack)sp_gc_mark(g);/* a root fiber is a worker's native stack, not a heap object */sp_fiber_mark_roots(g);}}
|
|
579
|
+
static void sp_mark_suspended_fibers(void){sp_fiber_collecting=sp_fiber_current;if(sp_fiber_current!=&sp_fiber_root)sp_fiber_mark_roots(&sp_fiber_root);if(sp_fiber_current)sp_fiber_mark_chain(sp_fiber_current->resumer);
|
|
580
|
+
/* and the fiber a transferred one returns to, suspended in its transfer */
|
|
581
|
+
if(sp_fiber_current&&sp_fiber_current->return_to)sp_fiber_mark_chain(sp_fiber_current->return_to);}
|
|
582
|
+
static void sp_fiber_install_gc_hook(void){if(!sp_gc_mark_suspended_fibers_hook)sp_gc_mark_suspended_fibers_hook=sp_mark_suspended_fibers;}
|
|
583
|
+
static void sp_Fiber_fin(void*p){sp_Fiber*f=(sp_Fiber*)p;if(f->stack)munmap(f->stack,sp_fiber_guard()+f->stack_size);if(f->saved_roots)free(f->saved_roots);if(f->exc_ctx)sp_exc_ctx_free(f->exc_ctx);
|
|
584
|
+
#ifdef SP_TSAN
|
|
585
|
+
if(f->tsan_fiber)__tsan_destroy_fiber(f->tsan_fiber);
|
|
586
|
+
#endif
|
|
587
|
+
sp_fiber_list_remove(f);}
|
|
588
|
+
static void sp_Fiber_scan(void*p){sp_Fiber*f=(sp_Fiber*)p;if(f->user_data)sp_gc_mark(f->user_data);if(f->storage)sp_gc_mark(f->storage);if(f->attrs)sp_gc_mark(f->attrs);if(f->return_to)sp_gc_mark(f->return_to);
|
|
589
|
+
/* a reachable suspended fiber keeps what its stack holds (its published
|
|
590
|
+
roots); the running one's roots are live, a dead one's are gone */
|
|
591
|
+
if(f!=sp_fiber_collecting&&f!=sp_fiber_current&&f->state!=3)sp_fiber_mark_roots(f);}
|
|
592
|
+
sp_Fiber*sp_Fiber_new(void(*body)(sp_Fiber*)){sp_fiber_fault_arm();sp_Fiber*f=(sp_Fiber*)sp_gc_alloc(sizeof(sp_Fiber),sp_Fiber_fin,sp_Fiber_scan);{size_t _g=sp_fiber_guard();f->stack_size=sp_fiber_stack_bytes();
|
|
593
|
+
/* MAP_NORESERVE: the stack is virtual space until touched, and a strict-overcommit host must not charge every fiber the whole of it (#4496) */
|
|
594
|
+
f->stack=(char*)mmap(NULL,_g+f->stack_size,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK|MAP_NORESERVE,-1,0);if(f->stack==MAP_FAILED){f->stack=NULL;sp_raise_cls("FiberError","failed to allocate fiber stack");}mprotect(f->stack,_g,PROT_NONE);}f->state=0;f->transferred=0;f->body=body;f->yielded_value=sp_box_nil();f->resumed_value=sp_box_nil();f->user_data=NULL;f->saved_exc_top=0;f->saved_catch_top=0;f->exc_ctx=sp_exc_ctx_new();f->raised=0;f->raised_cls=NULL;f->raised_msg=NULL;f->raised_obj=NULL;f->inject=0;f->inject_defer=0;f->inj_cls=NULL;f->inj_msg=NULL;f->inj_obj=NULL;f->storage=NULL;f->attrs=NULL;f->birth_file=NULL;f->birth_line=0;f->pass_argc=-1;f->owner=sp_thread_owner_id();f->return_to=NULL;f->thread_main=0;f->blocking=0;f->saved_roots=NULL;f->saved_nroots=0;f->saved_roots_cap=0;f->fiber_next=NULL;f->fiber_prev=NULL;sp_fiber_list_add(f);sp_fiber_install_gc_hook();if(sp_fiber_current&&sp_fiber_current->storage){sp_Fiber*volatile _froot=f;int _pushed=0;if(sp_gc_nroots<SP_GC_STACK_MAX){sp_gc_roots[sp_gc_nroots++]=(void**)&_froot;_pushed=1;}sp_FiberStore*_fst=sp_FiberStore_dup((sp_FiberStore*)sp_fiber_current->storage);sp_gc_wb((void*)f);f->storage=_fst;if(_pushed)sp_gc_nroots--;}
|
|
595
|
+
#ifdef SP_TSAN
|
|
596
|
+
if(!sp_fiber_root.tsan_fiber)sp_fiber_root.tsan_fiber=__tsan_get_current_fiber();
|
|
597
|
+
f->tsan_fiber=__tsan_create_fiber(0);f->caller_fiber=NULL;
|
|
598
|
+
#endif
|
|
599
|
+
return f;}
|
|
600
|
+
sp_RbVal sp_Fiber_storage_get(sp_Fiber*f,sp_sym k){if(!f->storage)return sp_box_nil();return sp_FiberStore_get((sp_FiberStore*)f->storage,k);}
|
|
601
|
+
/* store v under k as it is, nil included */
|
|
602
|
+
void sp_Fiber_storage_put(sp_Fiber*f,sp_sym k,sp_RbVal v){SP_GC_ROOT_RBVAL(v);SP_GC_ROOT(f); sp_gc_wb((void*)f);if(!f->storage){f->storage=sp_FiberStore_new();sp_gc_wb((void*)f);}sp_FiberStore_set((sp_FiberStore*)f->storage,k,v);}
|
|
603
|
+
/* Fiber[k] = v: nil removes the key (CRuby) */
|
|
604
|
+
void sp_Fiber_storage_set(sp_Fiber*f,sp_sym k,sp_RbVal v){
|
|
605
|
+
if(v.tag==SP_TAG_NIL){
|
|
606
|
+
if(f->storage)sp_FiberStore_delete((sp_FiberStore*)f->storage,k);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
sp_Fiber_storage_put(f,k,v);
|
|
610
|
+
}
|
|
611
|
+
/* the whole store, for Fiber#storage / #storage= (the Hash side is in
|
|
612
|
+
spinel_rt.h) */
|
|
613
|
+
sp_int sp_Fiber_storage_len(sp_Fiber*f){
|
|
614
|
+
return f->storage?((sp_FiberStore*)f->storage)->len:0;
|
|
615
|
+
}
|
|
616
|
+
/* entry i: keep i below sp_Fiber_storage_len, which is 0 when there is no store */
|
|
617
|
+
sp_sym sp_Fiber_storage_key(sp_Fiber*f,sp_int i){
|
|
618
|
+
return ((sp_FiberStore*)f->storage)->keys[i];
|
|
619
|
+
}
|
|
620
|
+
sp_RbVal sp_Fiber_storage_val(sp_Fiber*f,sp_int i){
|
|
621
|
+
return ((sp_FiberStore*)f->storage)->vals[i];
|
|
622
|
+
}
|
|
623
|
+
/* no store at all (#storage answers nil), or an empty one */
|
|
624
|
+
void sp_Fiber_storage_clear(sp_Fiber*f){
|
|
625
|
+
sp_gc_wb((void*)f);
|
|
626
|
+
f->storage=NULL;
|
|
627
|
+
}
|
|
628
|
+
void sp_Fiber_storage_empty(sp_Fiber*f){
|
|
629
|
+
SP_GC_ROOT(f);
|
|
630
|
+
sp_FiberStore*s=sp_FiberStore_new();
|
|
631
|
+
sp_gc_wb((void*)f);
|
|
632
|
+
f->storage=s;
|
|
633
|
+
}
|
|
634
|
+
/* A `Fiber.attr_accessor` attribute: the fiber's own table, which -- unlike
|
|
635
|
+
`storage` -- a new fiber does not inherit (an attribute on a fresh fiber is
|
|
636
|
+
nil, as an ivar on a fresh object is). */
|
|
637
|
+
sp_RbVal sp_Fiber_attr_get(sp_Fiber*f,sp_sym k){if(!f||!f->attrs)return sp_box_nil();return sp_FiberStore_get((sp_FiberStore*)f->attrs,k);}
|
|
638
|
+
void sp_Fiber_attr_set(sp_Fiber*f,sp_sym k,sp_RbVal v){if(!f)return;SP_GC_ROOT_RBVAL(v);SP_GC_ROOT(f); sp_gc_wb((void*)f);if(!f->attrs){f->attrs=sp_FiberStore_new();sp_gc_wb((void*)f);}sp_FiberStore_set((sp_FiberStore*)f->attrs,k,v);} /* the store, not the head, is what the barrier has to follow: sp_FiberStore_new can collect */
|
|
639
|
+
/* Internal class name of the Fiber#kill signal. It is raised to unwind the
|
|
640
|
+
fiber (running ensure blocks) but is excluded from every user rescue clause by
|
|
641
|
+
the codegen (emit_begin), so only ensures run; the trampoline below recognizes
|
|
642
|
+
it and terminates without propagating. Must stay in sync with the literal the
|
|
643
|
+
codegen emits in src/codegen_stmt.c. */
|
|
644
|
+
#define SP_FIBER_KILL_CLS "FiberKillSignal"
|
|
645
|
+
/* the kill signal's message when it is aimed at a main fiber: fibers pass it
|
|
646
|
+
on up instead of ending there (see sp_Fiber_kill) */
|
|
647
|
+
#define SP_FIBER_KILL_MAIN "main"
|
|
648
|
+
|
|
649
|
+
/* The inject slot is written by another OS thread (Thread#kill/#raise under the
|
|
650
|
+
scheduler lock) but read lock-free by the target's own paths (the trampoline,
|
|
651
|
+
Fiber.yield, sp_fiber_fire_inject_if_pending after a park). The flag alone is
|
|
652
|
+
not enough: a second inject can arrive while the target is mid-consume, so the
|
|
653
|
+
payload fields (inj_cls/inj_msg/inj_obj) would race with the consumer's reads
|
|
654
|
+
and clears. Guard the whole slot with a spinlock folded into the high bit of
|
|
655
|
+
the inject int: publishers and the consumer CAS the bit in, touch the payload,
|
|
656
|
+
and release-store the new kind (which also drops the bit). The critical
|
|
657
|
+
section is a handful of plain stores, so contention is a few spins at worst;
|
|
658
|
+
the lock holder never takes the scheduler lock, so there is no ordering cycle
|
|
659
|
+
with publishers that hold it. In the single-threaded build the atomics
|
|
660
|
+
compile to plain ops on a never-contended slot. */
|
|
661
|
+
#define SP_INJECT_LOCK_BIT 0x40000000
|
|
662
|
+
#define SP_INJECT_PEEK(f) (SP_ATOMIC_LOAD(&(f)->inject, __ATOMIC_ACQUIRE) & ~SP_INJECT_LOCK_BIT)
|
|
663
|
+
/* Acquire the slot; returns the kind bits observed at lock time. */
|
|
664
|
+
static int sp_fiber_inject_lock(sp_Fiber*f){
|
|
665
|
+
for(;;){
|
|
666
|
+
int cur=SP_ATOMIC_LOAD(&f->inject,__ATOMIC_RELAXED);
|
|
667
|
+
if(cur&SP_INJECT_LOCK_BIT)continue; /* holder is mid-publish/consume; spin */
|
|
668
|
+
if(SP_ATOMIC_CAS(&f->inject,&cur,cur|SP_INJECT_LOCK_BIT,0,
|
|
669
|
+
__ATOMIC_ACQUIRE,__ATOMIC_RELAXED))return cur;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
static void sp_fiber_inject_unlock(sp_Fiber*f,int kind){SP_ATOMIC_STORE(&f->inject,kind,__ATOMIC_RELEASE);}
|
|
673
|
+
/* Publish kind+payload atomically with respect to a concurrent consume. */
|
|
674
|
+
static void sp_fiber_inject_publish(sp_Fiber*f,int kind,const char*cls,const char*msg,void*obj){SP_GC_ROOT(f);SP_GC_ROOT_STR(msg);
|
|
675
|
+
sp_fiber_inject_lock(f);
|
|
676
|
+
f->inj_cls=cls;f->inj_msg=msg;f->inj_obj=obj;
|
|
677
|
+
sp_fiber_inject_unlock(f,kind);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/* Raise the exception queued by sp_Fiber_raise / sp_Fiber_kill in the fiber's
|
|
681
|
+
own context. Runs at the fiber's suspension point (sp_Fiber_yield) or, for an
|
|
682
|
+
unstarted fiber, at body entry (the trampoline). inject==2 is a kill signal;
|
|
683
|
+
inject==1 is an ordinary raise. Clears the slot (under the inject spinlock)
|
|
684
|
+
first so a rescue/retry does not re-fire it. */
|
|
685
|
+
static void sp_fiber_consume_inject(sp_Fiber*f){SP_GC_ROOT(f);int kind=sp_fiber_inject_lock(f);const char*cl=f->inj_cls;const char*ms=f->inj_msg;void*ob=f->inj_obj;f->inj_cls=NULL;f->inj_msg=NULL;f->inj_obj=NULL;sp_fiber_inject_unlock(f,0);if(kind==2)sp_raise_cls(SP_FIBER_KILL_CLS,(&("\xff")[1]));else sp_fiber_reraise(cl,ms,ob);/* kind 1 (Fiber#raise) and 3 (Thread#raise) both re-raise */}
|
|
686
|
+
|
|
687
|
+
/* Thread #kill / #raise support: the scheduler sets a pending inject on a target
|
|
688
|
+
thread's fiber, then fires it (sp_fiber_fire_inject_if_pending) when that
|
|
689
|
+
thread next runs -- in its own context, so its ensure blocks unwind on its own
|
|
690
|
+
stack. sp_fiber_raise_kill_self raises the kill signal in the running fiber
|
|
691
|
+
(a thread killing itself).
|
|
692
|
+
A thread raise is inject==3, NOT 1: the trampoline must not consume it at
|
|
693
|
+
body entry. A Thread#raise that lands before the body has run (the target
|
|
694
|
+
was picked up but not yet started) would otherwise raise ahead of the body's
|
|
695
|
+
own begin/rescue and escape as an unhandled thread exception; deferring it to
|
|
696
|
+
the thread's next suspension point (sp_sched_block / sleep / Thread.pass /
|
|
697
|
+
yield) delivers it inside the body, where its rescue/ensure can see it. */
|
|
698
|
+
void sp_fiber_set_raise_inject(sp_Fiber*f,const char*cls,const char*msg,void*obj){SP_GC_ROOT(f);SP_GC_ROOT_STR(msg);sp_fiber_inject_publish(f,3,cls,msg,obj);}
|
|
699
|
+
void sp_fiber_set_kill_inject(sp_Fiber*f){SP_GC_ROOT(f);sp_fiber_inject_publish(f,2,NULL,NULL,NULL);}
|
|
700
|
+
void sp_fiber_defer_inject(void){sp_Fiber*f=sp_fiber_current;if(f)f->inject_defer++;}
|
|
701
|
+
void sp_fiber_undefer_inject(void){sp_Fiber*f=sp_fiber_current;if(f&&f->inject_defer)f->inject_defer--;}
|
|
702
|
+
void sp_fiber_fire_inject_if_pending(void){sp_Fiber*f=sp_fiber_current;if(!f||f->inject_defer)return;if(SP_INJECT_PEEK(f)){sp_fiber_consume_inject(f);return;}
|
|
703
|
+
/* A #kill/#raise of a thread that is inside a Fiber it resumed: it is
|
|
704
|
+
delivered in that fiber, and a kill goes on up to the thread's own. */
|
|
705
|
+
if(f->thread_main||f==&sp_fiber_root)return;
|
|
706
|
+
sp_Fiber*m=sp_thread_main_fiber();if(!m||!SP_INJECT_PEEK(m))return;
|
|
707
|
+
SP_GC_ROOT(m);int kind=sp_fiber_inject_lock(m);const char*cl=m->inj_cls;const char*ms=m->inj_msg;void*ob=m->inj_obj;m->inj_cls=NULL;m->inj_msg=NULL;m->inj_obj=NULL;sp_fiber_inject_unlock(m,0);
|
|
708
|
+
if(kind==2)sp_raise_cls(SP_FIBER_KILL_CLS,(&("\xff" SP_FIBER_KILL_MAIN)[1]));else sp_fiber_reraise(cl,ms,ob);}
|
|
709
|
+
/* Lock-free peek for the scheduler's pre-park checks (sp_sched_block etc.). */
|
|
710
|
+
int sp_fiber_inject_pending(sp_Fiber*f){return SP_INJECT_PEEK(f)!=0;}
|
|
711
|
+
SP_NORETURN void sp_fiber_raise_kill_self(void){sp_raise_cls(SP_FIBER_KILL_CLS,(&("\xff")[1]));}
|
|
712
|
+
static void sp_fiber_trampoline(void){sp_Fiber*f=sp_fiber_current;jmp_buf base;if(setjmp(base)==0){sp_exc_arm(base);{int _inj=SP_INJECT_PEEK(f);if(_inj&&_inj!=3)sp_fiber_consume_inject(f);}/* a thread raise (3) defers to the body's first suspension point */f->body(f);sp_exc_disarm();}
|
|
713
|
+
else{const char*_cc=sp_exc_cur_cls();
|
|
714
|
+
int killed=_cc&&!strcmp(_cc,SP_FIBER_KILL_CLS);
|
|
715
|
+
/* a kill aimed at a main fiber goes on up to it, like an exception */
|
|
716
|
+
const char*_km=killed?sp_exc_cur_msg():NULL;
|
|
717
|
+
if(_km&&!strcmp(_km,SP_FIBER_KILL_MAIN)&&f!=sp_thread_main_fiber())killed=0;
|
|
718
|
+
if(killed){
|
|
719
|
+
/* killed: ensures already ran while unwinding; end without propagating,
|
|
720
|
+
and #resume answers nil rather than whatever it last yielded */
|
|
721
|
+
f->yielded_value=sp_box_nil();}
|
|
722
|
+
else{f->raised=1;f->raised_cls=_cc;f->raised_msg=sp_exc_cur_msg();f->raised_obj=sp_exc_cur_obj();}}f->state=3;f->saved_nroots=0;/* dead: the snapshot points into unwound frames; never mark it */if(f->transferred){
|
|
723
|
+
/* back to the fiber it returns to, whose transfer answers the block's result */
|
|
724
|
+
sp_Fiber*home=f->return_to&&f->return_to->state!=3?f->return_to:sp_thread_main_fiber();
|
|
725
|
+
if(!home||home==f)home=sp_sched_home_fiber();
|
|
726
|
+
home->resumed_value=f->yielded_value;sp_fiber_current=home;
|
|
727
|
+
SP_TSAN_SWITCH(home);sp_ctx_swap(&f->ctx,&home->ctx);}
|
|
728
|
+
else{SP_TSAN_SWITCH(f->caller_fiber);sp_ctx_swap(&f->ctx,&f->caller_ctx);}}
|
|
729
|
+
/* A fiber belongs to the thread that made it (CRuby's rule). */
|
|
730
|
+
static void sp_fiber_check_thread(sp_Fiber*f){
|
|
731
|
+
if(f->owner&&f->owner!=sp_thread_owner_id())
|
|
732
|
+
sp_raise_cls("FiberError","fiber called across threads");
|
|
733
|
+
}
|
|
734
|
+
/* f is waiting in its #resume of a fiber on the running chain. */
|
|
735
|
+
static int sp_fiber_resuming(sp_Fiber*f){
|
|
736
|
+
for(sp_Fiber*g=sp_fiber_current;g;){
|
|
737
|
+
if(g->resumer){if(g->resumer==f)return 1;g=g->resumer;}
|
|
738
|
+
else g=g->return_to;
|
|
739
|
+
}
|
|
740
|
+
return 0;
|
|
741
|
+
}
|
|
742
|
+
/* CRuby's refusals for a resume target, in its order. A root fiber has no
|
|
743
|
+
stack and is never resumable: it is current, resuming, or transferring.
|
|
744
|
+
That holds for the main thread's root asked from another thread too, which
|
|
745
|
+
is not this worker's root, so the test is the missing stack. */
|
|
746
|
+
static void sp_fiber_check_resume(sp_Fiber*f){
|
|
747
|
+
if(f->state==3)sp_raise_cls("FiberError","attempt to resume a terminated fiber");
|
|
748
|
+
if(f==sp_fiber_current)sp_raise_cls("FiberError","attempt to resume the current fiber");
|
|
749
|
+
if(f->resumer)sp_raise_cls("FiberError","attempt to resume a resumed fiber (double resume)");
|
|
750
|
+
if(sp_fiber_resuming(f))sp_raise_cls("FiberError","attempt to resume a resuming fiber");
|
|
751
|
+
if(f->transferred||f==&sp_fiber_root||!f->stack)sp_raise_cls("FiberError","attempt to resume a transferring fiber");
|
|
752
|
+
if(f->state==1)sp_raise_cls("FiberError","attempt to resume a resumed fiber (double resume)");
|
|
753
|
+
}
|
|
754
|
+
sp_RbVal sp_Fiber_resume(sp_Fiber*f,sp_RbVal val){SP_GC_ROOT_RBVAL(val);SP_GC_ROOT(f);sp_fiber_check_thread(f);sp_fiber_check_resume(f);f->resumed_value=val;sp_Fiber*prev=sp_fiber_current;sp_fiber_save_roots(prev);sp_fiber_restore_roots(f);if(!prev->exc_ctx)prev->exc_ctx=sp_exc_ctx_new();sp_exc_ctx_save(prev->exc_ctx);sp_exc_ctx_load(f->exc_ctx);f->resumer=prev;sp_fiber_current=f;SP_TSAN_SET_CALLER(f,prev);SP_TSAN_SWITCH(f);if(f->state==0){f->state=1;sp_ctx_make(&f->ctx,f->stack+sp_fiber_guard(),f->stack_size,sp_fiber_trampoline);sp_ctx_swap(&f->caller_ctx,&f->ctx);}
|
|
755
|
+
else{f->state=1;sp_ctx_swap(&f->caller_ctx,&f->ctx);}f->resumer=NULL;sp_exc_ctx_save(f->exc_ctx);sp_exc_ctx_load(prev->exc_ctx);if(f->state!=3)sp_fiber_save_roots(f);sp_fiber_restore_roots(prev);sp_fiber_current=prev;if(f->raised){f->raised=0;const char*rc=f->raised_cls;const char*rm=f->raised_msg;void*ro=f->raised_obj;f->raised_obj=NULL;sp_fiber_reraise(rc,rm,ro);}return f->yielded_value;}
|
|
756
|
+
/* Fiber.yield is only valid inside a fiber entered via #resume. The root fiber
|
|
757
|
+
was never resumed, and a fiber entered via #transfer has no resumer to return
|
|
758
|
+
to (its caller_ctx is unset) -- yielding from either would swap to a garbage
|
|
759
|
+
context, so raise instead (matching CRuby's FiberError). */
|
|
760
|
+
sp_RbVal sp_Fiber_yield(sp_RbVal val){SP_GC_ROOT_RBVAL(val);sp_Fiber*f=sp_fiber_current;if(f==&sp_fiber_root||f->transferred){sp_raise_cls("FiberError","attempt to yield on a not resumed fiber");}f->yielded_value=val;f->state=2;SP_TSAN_SWITCH(f->caller_fiber);sp_ctx_swap(&f->ctx,&f->caller_ctx);if(!f->inject_defer&&SP_INJECT_PEEK(f))sp_fiber_consume_inject(f);return f->resumed_value;}
|
|
761
|
+
sp_bool sp_Fiber_alive(sp_Fiber*f){return f->state!=3;}
|
|
762
|
+
/* A main fiber -- the root (no stack of its own) or a Thread's -- is
|
|
763
|
+
blocking; a Fiber.new one only when asked to be. */
|
|
764
|
+
sp_bool sp_Fiber_blocking_p(sp_Fiber*f){
|
|
765
|
+
return f->stack==NULL||f->thread_main||f->blocking;
|
|
766
|
+
}
|
|
767
|
+
sp_Fiber*sp_Fiber_at(sp_Fiber*f,const char*file,sp_int line){if(f){f->birth_file=file;f->birth_line=line;}return f;}
|
|
768
|
+
/* Fiber#inspect: CRuby's #<Fiber:0xADDR file:line (status)>. Only the
|
|
769
|
+
running fiber is "resumed"; one waiting in #resume of another is
|
|
770
|
+
"suspended by resuming". */
|
|
771
|
+
const char*sp_Fiber_inspect(sp_Fiber*f){
|
|
772
|
+
extern const char*sp_sprintf(const char*fmt,...);
|
|
773
|
+
if(!f)return "nil";
|
|
774
|
+
const char*st="suspended";
|
|
775
|
+
if(f->state==0&&f!=&sp_fiber_root)st="created";
|
|
776
|
+
else if(f->state==3)st="terminated";
|
|
777
|
+
else if(f==sp_fiber_current)st="resumed";
|
|
778
|
+
else for(sp_Fiber*g=sp_fiber_current->resumer;g;g=g->resumer)if(g==f){st="suspended by resuming";break;}
|
|
779
|
+
if(f->birth_file)
|
|
780
|
+
return sp_sprintf("#<Fiber:0x%016llx %s:%lld (%s)>",(unsigned long long)(uintptr_t)f,f->birth_file,(long long)f->birth_line,st);
|
|
781
|
+
return sp_sprintf("#<Fiber:0x%016llx (%s)>",(unsigned long long)(uintptr_t)f,st);
|
|
782
|
+
}
|
|
783
|
+
/* Fiber#raise: queue an exception, then resume the fiber so its suspension point
|
|
784
|
+
(or body entry) raises it. An unhandled raise propagates to this caller via
|
|
785
|
+
sp_Fiber_resume's re-raise, exactly like an exception raised by the body. */
|
|
786
|
+
sp_RbVal sp_Fiber_raise(sp_Fiber*f,const char*cls,const char*msg,void*obj){SP_GC_ROOT(f);SP_GC_ROOT_STR(msg);
|
|
787
|
+
if(f->state==3){sp_raise_cls("FiberError","dead fiber called");}
|
|
788
|
+
/* Never resumed: there is no fiber context to deliver into, so CRuby refuses
|
|
789
|
+
rather than raising the exception somewhere. Delivering it in the CALLER
|
|
790
|
+
instead -- which is what resuming an unstarted fiber with the injection
|
|
791
|
+
queued did -- made a `rescue FiberError` around fiber setup miss, and
|
|
792
|
+
handed the caller the wrong class (#3468). Note Fiber#kill is different
|
|
793
|
+
and stays a no-op here: an unborn fiber has nothing to unwind. */
|
|
794
|
+
if(f->state==0){sp_raise_cls("FiberError","cannot raise exception on unborn fiber");}
|
|
795
|
+
sp_fiber_inject_publish(f,1,cls,msg,obj); /* same-thread, but keep the slot discipline uniform */
|
|
796
|
+
return sp_Fiber_resume(f,sp_box_nil());
|
|
797
|
+
}
|
|
798
|
+
/* Fiber#kill: terminate the fiber, running its ensure blocks. A suspended fiber
|
|
799
|
+
is resumed with a kill signal that unwinds it (ensures run, user rescues are
|
|
800
|
+
bypassed) until the trampoline terminates it. An unstarted fiber never ran its
|
|
801
|
+
body, so it is just marked dead. Returns the fiber, matching CRuby. */
|
|
802
|
+
sp_Fiber*sp_Fiber_kill(sp_Fiber*f){SP_GC_ROOT(f);
|
|
803
|
+
/* A main fiber: the root (no stack of its own) or a Thread's own fiber. */
|
|
804
|
+
int main_fiber=f->stack==NULL||f->thread_main;
|
|
805
|
+
/* the running thread's: the root only counts on the main thread */
|
|
806
|
+
int ours=f->stack==NULL?sp_thread_main_fiber()==NULL:f==sp_thread_main_fiber();
|
|
807
|
+
/* Killing the main fiber of the program or of a Thread ends that program
|
|
808
|
+
or thread, as in CRuby: the signal unwinds every fiber on the way. */
|
|
809
|
+
if(main_fiber&&ours)
|
|
810
|
+
sp_raise_cls(SP_FIBER_KILL_CLS,(&("\xff" SP_FIBER_KILL_MAIN)[1]));
|
|
811
|
+
/* Another thread's fiber is refused before anything is queued on it,
|
|
812
|
+
with CRuby's messages. */
|
|
813
|
+
if(main_fiber)sp_raise_cls("FiberError","attempt to resume a transferring fiber");
|
|
814
|
+
sp_fiber_check_thread(f);
|
|
815
|
+
/* a fiber killing itself unwinds right here */
|
|
816
|
+
if(f==sp_fiber_current)sp_fiber_raise_kill_self();
|
|
817
|
+
if(f->state==3)return f; /* already dead: no-op */
|
|
818
|
+
if(f->state==0){f->state=3;return f;}/* unstarted: nothing to unwind */
|
|
819
|
+
sp_fiber_inject_publish(f,2,NULL,NULL,NULL);
|
|
820
|
+
sp_Fiber_resume(f,sp_box_nil()); /* runs ensures; the trampoline terminates it */
|
|
821
|
+
return f;
|
|
822
|
+
}
|
|
823
|
+
/* Core of #transfer: switch to f and do all the save/restore, but leave f's
|
|
824
|
+
unhandled termination exception PENDING in f->raised for the caller to
|
|
825
|
+
consume. sp_Fiber_transfer re-raises it (Fiber semantics); the thread
|
|
826
|
+
scheduler captures it instead (sp_Fiber_transfer_catch). */
|
|
827
|
+
/* CRuby's refusals for a transfer target. The root fiber passes, and so does
|
|
828
|
+
a live fiber already entered by transfer (what the scheduler switches to). */
|
|
829
|
+
static void sp_fiber_check_transfer(sp_Fiber*f){
|
|
830
|
+
/* the main thread's root, from another thread: it has no owner to check */
|
|
831
|
+
if(!f->stack&&sp_thread_main_fiber())sp_raise_cls("FiberError","fiber called across threads");
|
|
832
|
+
if(f==&sp_fiber_root||f==sp_fiber_current)return;
|
|
833
|
+
if(f->state==3)sp_raise_cls("FiberError","dead fiber called");
|
|
834
|
+
if(f->transferred)return;
|
|
835
|
+
if(f->state==2)sp_raise_cls("FiberError","attempt to transfer to a yielding fiber");
|
|
836
|
+
/* resuming: waiting in #resume of a fiber on the running chain. One that
|
|
837
|
+
is only suspended in its own transfer may be transferred back to. */
|
|
838
|
+
for(sp_Fiber*g=sp_fiber_current;g;){
|
|
839
|
+
if(g->resumer){if(g->resumer==f)sp_raise_cls("FiberError","attempt to transfer to a resuming fiber");g=g->resumer;}
|
|
840
|
+
else g=g->return_to;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
/* The switch itself, past CRuby's checks: save prev's (the current fiber's)
|
|
844
|
+
context and run f. Inlined, so a transfer costs what it did before the
|
|
845
|
+
scheduler's switch shared it. The caller roots f. */
|
|
846
|
+
static SP_INLINE sp_RbVal sp_fiber_switch(sp_Fiber*f,sp_Fiber*prev,sp_RbVal val){f->resumed_value=val;
|
|
847
|
+
sp_fiber_save_roots(prev);sp_fiber_restore_roots(f);if(!prev->exc_ctx)prev->exc_ctx=sp_exc_ctx_new();sp_exc_ctx_save(prev->exc_ctx);sp_exc_ctx_load(f->exc_ctx);sp_fiber_current=f;SP_TSAN_SWITCH(f);if(f->state==0&&f!=&sp_fiber_root){f->state=1;f->transferred=1;sp_ctx_make(&f->ctx,f->stack+sp_fiber_guard(),f->stack_size,sp_fiber_trampoline);sp_ctx_swap(&prev->ctx,&f->ctx);}
|
|
848
|
+
else{/* the root fiber is the implicit running coroutine: it has no mmap'd
|
|
849
|
+
stack/body, so it must never be ctx_make'd. Its context was already
|
|
850
|
+
saved into root.ctx by the first transfer away from it, so transferring
|
|
851
|
+
back just swaps to that saved context. */f->state=1;sp_ctx_swap(&prev->ctx,&f->ctx);}/* Do NOT re-save f's exception context or roots here. When f yielded back it
|
|
852
|
+
already saved its own (its transfer's pre-swap save); the live TLS now holds
|
|
853
|
+
prev's, which f restored before switching to us. Re-saving would clobber f's
|
|
854
|
+
saved state with prev's -- losing a blocked/sleeping thread's roots/handlers
|
|
855
|
+
and freeing its live locals on the next collection -- and, since f may already
|
|
856
|
+
be running on another worker (woken between its switch-out and here), that
|
|
857
|
+
write races that worker's load of f's context. We only restore prev's. */sp_exc_ctx_load(prev->exc_ctx);sp_fiber_restore_roots(prev);sp_fiber_current=prev;return prev->resumed_value;}
|
|
858
|
+
static sp_RbVal sp_Fiber_transfer_core(sp_Fiber*f,sp_RbVal val){SP_GC_ROOT(f);sp_fiber_check_thread(f);sp_fiber_check_transfer(f);sp_Fiber*prev=sp_fiber_current;
|
|
859
|
+
/* it returns where its transferrer would: to that fiber if it was resumed */
|
|
860
|
+
if(f!=&sp_fiber_root&&f!=prev)f->return_to=prev->resumer?prev:prev->return_to;
|
|
861
|
+
/* TSan's caller is set by the transfer, not by the scheduler's switch back:
|
|
862
|
+
a resumed fiber's caller is its resumer, which its Fiber.yield goes to */
|
|
863
|
+
SP_TSAN_SET_CALLER(f,prev);
|
|
864
|
+
return sp_fiber_switch(f,prev,val);}
|
|
865
|
+
/* A green thread's switch back to the fiber that ran it (sp_sched.c). That
|
|
866
|
+
may be a Fiber the main thread resumed, which a transfer would refuse (it
|
|
867
|
+
is the main thread's, and resumed), and it is not the green thread's to
|
|
868
|
+
return to when its body ends, so it leaves return_to alone, and under TSan
|
|
869
|
+
caller_fiber too. */
|
|
870
|
+
void sp_fiber_sched_switch(sp_Fiber*f){SP_GC_ROOT(f);sp_fiber_switch(f,sp_fiber_current,sp_box_nil());}
|
|
871
|
+
sp_RbVal sp_Fiber_transfer(sp_Fiber*f,sp_RbVal val){SP_GC_ROOT_RBVAL(val);SP_GC_ROOT(f);sp_RbVal r=sp_Fiber_transfer_core(f,val);if(f->raised){f->raised=0;const char*rc=f->raised_cls;const char*rm=f->raised_msg;void*ro=f->raised_obj;f->raised_obj=NULL;sp_fiber_reraise(rc,rm,ro);}return r;}
|
|
872
|
+
/* resume / transfer that also tell the body how many values were passed */
|
|
873
|
+
sp_RbVal sp_Fiber_resume_n(sp_Fiber*f,sp_RbVal val,int argc){SP_GC_ROOT(f);f->pass_argc=argc;sp_RbVal r=sp_Fiber_resume(f,val);f->pass_argc=-1;return r;}
|
|
874
|
+
sp_RbVal sp_Fiber_transfer_n(sp_Fiber*f,sp_RbVal val,int argc){SP_GC_ROOT(f);f->pass_argc=argc;sp_RbVal r=sp_Fiber_transfer(f,val);f->pass_argc=-1;return r;}
|
|
875
|
+
/* Thread scheduler transfer: on f's unhandled termination exception, hand the
|
|
876
|
+
(cls,msg,obj) back through *out_* and set *out_raised, rather than re-raising
|
|
877
|
+
in the caller (the scheduler stores it on the green thread for #join/#value).
|
|
878
|
+
A non-terminating transfer (f yielded back) leaves *out_raised 0.
|
|
879
|
+
The thread's fiber returns to no fiber: the fibers it transfers to end in
|
|
880
|
+
it, and its own body ends where its yields go, in the fiber that ran it
|
|
881
|
+
(sp_sched_home_fiber). The main thread may run it from inside a Fiber,
|
|
882
|
+
whose chain is not the thread's. When the thread stopped inside a Fiber
|
|
883
|
+
it resumed, `at`, the switch goes there, still with f's bookkeeping. */
|
|
884
|
+
sp_RbVal sp_Fiber_transfer_catch(sp_Fiber*f,sp_Fiber*at,sp_RbVal val,int*out_raised,const char**out_cls,const char**out_msg,void**out_obj){SP_GC_ROOT_RBVAL(val);SP_GC_ROOT(f);sp_Fiber*prev=sp_fiber_current;f->return_to=NULL;sp_RbVal r;
|
|
885
|
+
if(at){SP_GC_ROOT(at);r=sp_fiber_switch(at,prev,val);}
|
|
886
|
+
else{sp_fiber_check_thread(f);sp_fiber_check_transfer(f);SP_TSAN_SET_CALLER(f,prev);r=sp_fiber_switch(f,prev,val);}
|
|
887
|
+
*out_raised=f->raised;if(f->raised){f->raised=0;*out_cls=f->raised_cls;*out_msg=f->raised_msg;*out_obj=f->raised_obj;f->raised_obj=NULL;}return r;}
|
|
888
|
+
|
|
889
|
+
void sp_mark_fiber_root_storage(void){if(sp_fiber_root.storage)sp_gc_mark(sp_fiber_root.storage);if(sp_fiber_root.attrs)sp_gc_mark(sp_fiber_root.attrs);}
|