qr_reader-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. metadata +153 -0
@@ -0,0 +1,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);}