xbintsc 0.3.35 → 0.3.46
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.
- package/README.md +6 -3
- package/dist/src/codegen/generator/module.js +10 -0
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.js +4 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/tests/e2e/gc.test.d.ts +1 -0
- package/dist/tests/e2e/gc.test.js +168 -0
- package/dist/tests/e2e/gc.test.js.map +1 -0
- package/dist/tests/e2e/harness.d.ts +2 -0
- package/dist/tests/e2e/harness.js +1 -0
- package/dist/tests/e2e/harness.js.map +1 -1
- package/package.json +3 -2
- package/runtime/ext_gui/dom_api_proto.cpp +5 -0
- package/runtime/ext_gui/window.cpp +1 -0
- package/runtime/ext_node/buffer/parts/prototype.inc +1 -0
- package/runtime/ext_node/dgram/dgram.c +1 -0
- package/runtime/ext_node/events/events.c +1 -0
- package/runtime/ext_node/fs/fs_ops.c +10 -25
- package/runtime/ext_node/fs/glob.c +13 -30
- package/runtime/ext_node/fs/promises.c +1 -0
- package/runtime/ext_node/http/parts/prototypes.inc +5 -0
- package/runtime/ext_node/net/parts/prototypes.inc +2 -0
- package/runtime/ext_node/process/process.c +9 -7
- package/runtime/ext_node/stream/stream.c +1 -0
- package/runtime/ext_node/util/util.c +6 -6
- package/runtime/rt.h +10 -0
- package/runtime/rt_internal.h +63 -2
- package/runtime/xt_alloc.c +442 -5
- package/runtime/xt_generator.c +95 -1
- package/runtime/xt_loop.c +35 -1
- package/runtime/xt_promise.c +46 -0
- package/runtime/xt_stdlib2/error.inc +1 -0
- package/runtime/xt_stdlib2/regexp-match.inc +8 -7
- package/runtime/xt_symbol.c +2 -0
- package/runtime/xt_typed_array/construction.inc +142 -0
- package/runtime/xt_typed_array/elements.inc +92 -0
- package/runtime/xt_typed_array/methods.inc +329 -0
- package/runtime/xt_typed_array.c +6 -548
- package/runtime/xt_values/number-format.inc +26 -0
- package/scripts/check-file-length.ts +85 -0
- package/src/codegen/generator/module.ts +10 -0
- package/src/codegen/generator/state.ts +4 -0
package/runtime/xt_alloc.c
CHANGED
|
@@ -1,31 +1,152 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* xbintsc runtime — heap allocation.
|
|
2
|
+
* xbintsc runtime — heap allocation and garbage collection.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Every heap object is a `calloc`-backed block whose first bytes are an
|
|
5
|
+
* `xt_header`. Live blocks are threaded through `g_heap_head` (`gc_next`), and
|
|
6
|
+
* the collector is a non-moving mark-sweep over that list:
|
|
7
|
+
*
|
|
8
|
+
* 1. build an address -> header hash set of every live allocation;
|
|
9
|
+
* 2. mark from the roots — registered slots, root providers and a
|
|
10
|
+
* conservative scan of the active C stack (the whole `jmp_buf` plus the
|
|
11
|
+
* stack words up to the region's high bound);
|
|
12
|
+
* 3. trace each marked object. Value fields are visited precisely where the
|
|
13
|
+
* layout is known, but the object payload and its separately allocated
|
|
14
|
+
* backing buffers are also scanned conservatively, so a raw pointer held
|
|
15
|
+
* by older code is still followed;
|
|
16
|
+
* 4. sweep: unmarked blocks are released (including their backing buffers)
|
|
17
|
+
* and unlinked, survivors keep their block and have the mark bit cleared.
|
|
18
|
+
*
|
|
19
|
+
* A non-moving collector keeps the NaN-boxed `xt_value` representation
|
|
20
|
+
* untouched: a pointer never changes, so no write barriers or handle
|
|
21
|
+
* indirection are needed. `gc_next` doubles as the live list and, once a block
|
|
22
|
+
* is released, the memory simply returns to `malloc`.
|
|
23
|
+
*
|
|
24
|
+
* The collector is single-threaded, like the rest of the runtime (workers are
|
|
25
|
+
* child processes, not threads).
|
|
7
26
|
*/
|
|
8
27
|
|
|
9
28
|
#include "rt_internal.h"
|
|
10
29
|
|
|
30
|
+
#include <setjmp.h>
|
|
11
31
|
#include <stdio.h>
|
|
12
32
|
#include <stdlib.h>
|
|
33
|
+
#include <string.h>
|
|
13
34
|
|
|
14
35
|
/* ------------------------------------------------------------------------- */
|
|
15
|
-
/*
|
|
36
|
+
/* Heap state */
|
|
16
37
|
/* ------------------------------------------------------------------------- */
|
|
17
38
|
|
|
18
39
|
static size_t g_heap_bytes = 0;
|
|
19
40
|
static size_t g_heap_allocations = 0;
|
|
20
41
|
static xt_header *g_heap_head = NULL;
|
|
21
42
|
|
|
43
|
+
static int g_gc_initialised = 0;
|
|
44
|
+
static int g_gc_armed = 0;
|
|
45
|
+
static int g_gc_running = 0;
|
|
46
|
+
static int g_gc_threshold_pinned = 0;
|
|
47
|
+
static size_t g_gc_threshold = (size_t)16 << 20; /* 16 MiB */
|
|
48
|
+
#define XT_GC_MIN_THRESHOLD ((size_t)1 << 20)
|
|
49
|
+
|
|
50
|
+
/* ------------------------------------------------------------------------- */
|
|
51
|
+
/* Roots */
|
|
52
|
+
/* ------------------------------------------------------------------------- */
|
|
53
|
+
|
|
54
|
+
static xt_value **g_root_slots = NULL;
|
|
55
|
+
static size_t g_root_count = 0;
|
|
56
|
+
static size_t g_root_capacity = 0;
|
|
57
|
+
|
|
58
|
+
static void (**g_root_providers)(void) = NULL;
|
|
59
|
+
static size_t g_root_provider_count = 0;
|
|
60
|
+
static size_t g_root_provider_capacity = 0;
|
|
61
|
+
|
|
62
|
+
/* ------------------------------------------------------------------------- */
|
|
63
|
+
/* Active stack regions */
|
|
64
|
+
/* ------------------------------------------------------------------------- */
|
|
65
|
+
|
|
66
|
+
#define XT_GC_MAX_STACKS 64
|
|
67
|
+
typedef struct {
|
|
68
|
+
uintptr_t low;
|
|
69
|
+
uintptr_t high;
|
|
70
|
+
/* Lower bound captured from the stack below this region when a deeper region
|
|
71
|
+
* was entered on top of it. `UINTPTR_MAX` means "this is the top region"; the
|
|
72
|
+
* collector then scans from the current stack pointer. */
|
|
73
|
+
uintptr_t scan_low;
|
|
74
|
+
} xt_stack_region;
|
|
75
|
+
|
|
76
|
+
static xt_stack_region g_stack_regions[XT_GC_MAX_STACKS];
|
|
77
|
+
static int g_stack_depth = 0;
|
|
78
|
+
|
|
79
|
+
/* ------------------------------------------------------------------------- */
|
|
80
|
+
/* Mark stack and address set */
|
|
81
|
+
/* ------------------------------------------------------------------------- */
|
|
82
|
+
|
|
83
|
+
static xt_header **g_gray = NULL;
|
|
84
|
+
static size_t g_gray_count = 0;
|
|
85
|
+
static size_t g_gray_capacity = 0;
|
|
86
|
+
|
|
87
|
+
static xt_header **g_table = NULL;
|
|
88
|
+
static size_t g_table_capacity = 0;
|
|
89
|
+
static size_t g_table_mask = 0;
|
|
90
|
+
|
|
91
|
+
static size_t xt_gc_hash(uintptr_t pointer) {
|
|
92
|
+
uint64_t mixed = (uint64_t)(pointer >> 4) * 0x9E3779B97F4A7C15ULL;
|
|
93
|
+
return (size_t)(mixed >> 17);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static xt_header *xt_gc_table_lookup(uintptr_t pointer) {
|
|
97
|
+
if (g_table_capacity == 0) return NULL;
|
|
98
|
+
size_t slot = xt_gc_hash(pointer) & g_table_mask;
|
|
99
|
+
for (;;) {
|
|
100
|
+
xt_header *header = g_table[slot];
|
|
101
|
+
if (!header) return NULL;
|
|
102
|
+
if ((uintptr_t)header == pointer) return header;
|
|
103
|
+
slot = (slot + 1) & g_table_mask;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static void xt_gc_table_build(void) {
|
|
108
|
+
size_t capacity = 16;
|
|
109
|
+
while (capacity < (g_heap_allocations + 1) * 2) capacity <<= 1;
|
|
110
|
+
free(g_table);
|
|
111
|
+
g_table = (xt_header **)calloc(capacity, sizeof(xt_header *));
|
|
112
|
+
if (!g_table) {
|
|
113
|
+
fprintf(stderr, "xbintsc: out of memory building GC table\n");
|
|
114
|
+
abort();
|
|
115
|
+
}
|
|
116
|
+
g_table_capacity = capacity;
|
|
117
|
+
g_table_mask = capacity - 1;
|
|
118
|
+
for (xt_header *header = g_heap_head; header; header = header->gc_next) {
|
|
119
|
+
size_t slot = xt_gc_hash((uintptr_t)header) & g_table_mask;
|
|
120
|
+
while (g_table[slot]) slot = (slot + 1) & g_table_mask;
|
|
121
|
+
g_table[slot] = header;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static void xt_gc_table_free(void) {
|
|
126
|
+
free(g_table);
|
|
127
|
+
g_table = NULL;
|
|
128
|
+
g_table_capacity = 0;
|
|
129
|
+
g_table_mask = 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ------------------------------------------------------------------------- */
|
|
133
|
+
/* Allocation */
|
|
134
|
+
/* ------------------------------------------------------------------------- */
|
|
135
|
+
|
|
22
136
|
void *xt_alloc(size_t size, int kind) {
|
|
137
|
+
if (!g_gc_initialised) xt_gc_init();
|
|
138
|
+
/* Collect before handing out the new block: the block being allocated here
|
|
139
|
+
* is guaranteed not to be swept, and references held by the caller survive
|
|
140
|
+
* the conservative stack scan. */
|
|
141
|
+
if (g_gc_armed && !g_gc_running && g_heap_bytes >= g_gc_threshold) xt_gc_collect();
|
|
142
|
+
|
|
23
143
|
xt_header *header = (xt_header *)calloc(1, size);
|
|
24
144
|
if (!header) {
|
|
25
145
|
fprintf(stderr, "xbintsc: out of memory allocating %zu bytes\n", size);
|
|
26
146
|
abort();
|
|
27
147
|
}
|
|
28
148
|
header->kind = (uint8_t)kind;
|
|
149
|
+
header->flags = 0;
|
|
29
150
|
header->size = (uint32_t)size;
|
|
30
151
|
header->gc_next = g_heap_head;
|
|
31
152
|
g_heap_head = header;
|
|
@@ -36,3 +157,319 @@ void *xt_alloc(size_t size, int kind) {
|
|
|
36
157
|
|
|
37
158
|
size_t xt_heap_bytes(void) { return g_heap_bytes; }
|
|
38
159
|
size_t xt_heap_allocations(void) { return g_heap_allocations; }
|
|
160
|
+
|
|
161
|
+
/* ------------------------------------------------------------------------- */
|
|
162
|
+
/* Collector configuration */
|
|
163
|
+
/* ------------------------------------------------------------------------- */
|
|
164
|
+
|
|
165
|
+
void xt_gc_set_stack_base(void *base) {
|
|
166
|
+
uintptr_t high = (uintptr_t)base;
|
|
167
|
+
if (high == 0) return;
|
|
168
|
+
if (g_stack_depth == 0) {
|
|
169
|
+
g_stack_regions[0].low = 0;
|
|
170
|
+
g_stack_regions[0].high = high;
|
|
171
|
+
g_stack_regions[0].scan_low = UINTPTR_MAX;
|
|
172
|
+
g_stack_depth = 1;
|
|
173
|
+
} else if (high > g_stack_regions[0].high) {
|
|
174
|
+
g_stack_regions[0].high = high;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
void xt_gc_init(void) {
|
|
179
|
+
if (g_gc_initialised) return;
|
|
180
|
+
g_gc_initialised = 1;
|
|
181
|
+
uintptr_t high = (uintptr_t)&high;
|
|
182
|
+
if (g_stack_depth == 0) {
|
|
183
|
+
g_stack_regions[0].low = 0;
|
|
184
|
+
g_stack_regions[0].high = high;
|
|
185
|
+
g_stack_regions[0].scan_low = UINTPTR_MAX;
|
|
186
|
+
g_stack_depth = 1;
|
|
187
|
+
} else if (high > g_stack_regions[0].high) {
|
|
188
|
+
/* `xt_gc_set_stack_base` normally installed a bound above the caller's
|
|
189
|
+
* frame already; keep the higher of the two. */
|
|
190
|
+
g_stack_regions[0].high = high;
|
|
191
|
+
}
|
|
192
|
+
const char *env = getenv("XT_GC_THRESHOLD");
|
|
193
|
+
if (env && *env) {
|
|
194
|
+
long long value = atoll(env);
|
|
195
|
+
if (value > 0) {
|
|
196
|
+
g_gc_threshold = (size_t)value;
|
|
197
|
+
g_gc_threshold_pinned = 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
void xt_gc_arm(void) { g_gc_armed = 1; }
|
|
203
|
+
|
|
204
|
+
void xt_gc_set_threshold(size_t bytes) {
|
|
205
|
+
g_gc_threshold = bytes > XT_GC_MIN_THRESHOLD ? bytes : XT_GC_MIN_THRESHOLD;
|
|
206
|
+
g_gc_threshold_pinned = 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
void xt_gc_add_root(xt_value *slot) {
|
|
210
|
+
if (!slot) return;
|
|
211
|
+
for (size_t i = 0; i < g_root_count; i++) {
|
|
212
|
+
if (g_root_slots[i] == slot) return;
|
|
213
|
+
}
|
|
214
|
+
if (g_root_count == g_root_capacity) {
|
|
215
|
+
size_t capacity = g_root_capacity ? g_root_capacity * 2 : 32;
|
|
216
|
+
xt_value **grown = (xt_value **)realloc(g_root_slots, sizeof(xt_value *) * capacity);
|
|
217
|
+
if (!grown) abort();
|
|
218
|
+
g_root_slots = grown;
|
|
219
|
+
g_root_capacity = capacity;
|
|
220
|
+
}
|
|
221
|
+
g_root_slots[g_root_count++] = slot;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
void xt_gc_add_root_range(xt_value *base, size_t count) {
|
|
225
|
+
for (size_t i = 0; i < count; i++) xt_gc_add_root(base + i);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
void xt_gc_register_root_provider(void (*provider)(void)) {
|
|
229
|
+
if (!provider) return;
|
|
230
|
+
for (size_t i = 0; i < g_root_provider_count; i++) {
|
|
231
|
+
if (g_root_providers[i] == provider) return;
|
|
232
|
+
}
|
|
233
|
+
if (g_root_provider_count == g_root_provider_capacity) {
|
|
234
|
+
size_t capacity = g_root_provider_capacity ? g_root_provider_capacity * 2 : 8;
|
|
235
|
+
void (**grown)(void) = (void (**)(void))realloc(g_root_providers, sizeof(void (*)(void)) * capacity);
|
|
236
|
+
if (!grown) abort();
|
|
237
|
+
g_root_providers = grown;
|
|
238
|
+
g_root_provider_capacity = capacity;
|
|
239
|
+
}
|
|
240
|
+
g_root_providers[g_root_provider_count++] = provider;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
void xt_gc_push_stack(uintptr_t low, uintptr_t high) {
|
|
244
|
+
if (g_stack_depth >= XT_GC_MAX_STACKS) return;
|
|
245
|
+
g_stack_regions[g_stack_depth].low = low;
|
|
246
|
+
g_stack_regions[g_stack_depth].high = high;
|
|
247
|
+
g_stack_regions[g_stack_depth].scan_low = low;
|
|
248
|
+
g_stack_depth++;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
void xt_gc_suspend(uintptr_t sp) {
|
|
252
|
+
if (g_stack_depth <= 0) return;
|
|
253
|
+
uintptr_t *scan = &g_stack_regions[g_stack_depth - 1].scan_low;
|
|
254
|
+
if (*scan == UINTPTR_MAX || sp < *scan) *scan = sp;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
void xt_gc_pop_stack(void) {
|
|
258
|
+
if (g_stack_depth <= 1) return;
|
|
259
|
+
g_stack_depth--;
|
|
260
|
+
/* Back on the underlying stack: resume scanning it from the current frame. */
|
|
261
|
+
if (g_stack_depth > 0) g_stack_regions[g_stack_depth - 1].scan_low = UINTPTR_MAX;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* ------------------------------------------------------------------------- */
|
|
265
|
+
/* Marking */
|
|
266
|
+
/* ------------------------------------------------------------------------- */
|
|
267
|
+
|
|
268
|
+
static void xt_gc_gray_push(xt_header *header) {
|
|
269
|
+
if (!header || (header->flags & XT_GC_MARK)) return;
|
|
270
|
+
header->flags |= XT_GC_MARK;
|
|
271
|
+
if (g_gray_count == g_gray_capacity) {
|
|
272
|
+
size_t capacity = g_gray_capacity ? g_gray_capacity * 2 : 256;
|
|
273
|
+
xt_header **grown = (xt_header **)realloc(g_gray, sizeof(xt_header *) * capacity);
|
|
274
|
+
if (!grown) abort();
|
|
275
|
+
g_gray = grown;
|
|
276
|
+
g_gray_capacity = capacity;
|
|
277
|
+
}
|
|
278
|
+
g_gray[g_gray_count++] = header;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
void xt_gc_mark_header(xt_header *header) { xt_gc_gray_push(header); }
|
|
282
|
+
|
|
283
|
+
void xt_gc_mark_value(xt_value value) {
|
|
284
|
+
if (XT_IS_STRING(value) || XT_IS_OBJECT(value) || XT_IS_ARRAY(value) || XT_IS_FUNCTION(value) ||
|
|
285
|
+
XT_IS_BIGINT(value)) {
|
|
286
|
+
xt_gc_gray_push((xt_header *)XT_GET_PTR(value));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
void xt_gc_scan_region(const void *base, size_t bytes) {
|
|
291
|
+
if (!base) return;
|
|
292
|
+
const uint64_t *words = (const uint64_t *)base;
|
|
293
|
+
size_t count = bytes / sizeof(uint64_t);
|
|
294
|
+
for (size_t i = 0; i < count; i++) {
|
|
295
|
+
uint64_t word = words[i];
|
|
296
|
+
xt_header *header = NULL;
|
|
297
|
+
if (XT_IS_STRING(word) || XT_IS_OBJECT(word) || XT_IS_ARRAY(word) || XT_IS_FUNCTION(word) ||
|
|
298
|
+
XT_IS_BIGINT(word)) {
|
|
299
|
+
header = xt_gc_table_lookup((uintptr_t)XT_GET_PTR(word));
|
|
300
|
+
} else {
|
|
301
|
+
header = xt_gc_table_lookup((uintptr_t)word);
|
|
302
|
+
}
|
|
303
|
+
if (header) xt_gc_gray_push(header);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* ------------------------------------------------------------------------- */
|
|
308
|
+
/* Tracing */
|
|
309
|
+
/* ------------------------------------------------------------------------- */
|
|
310
|
+
|
|
311
|
+
/* Visit the payload (everything after the header) plus any separately
|
|
312
|
+
* allocated backing buffer owned by the object. */
|
|
313
|
+
static void xt_gc_process(xt_header *header) {
|
|
314
|
+
size_t payload = header->size > sizeof(xt_header) ? header->size - sizeof(xt_header) : 0;
|
|
315
|
+
const void *base = (const char *)header + sizeof(xt_header);
|
|
316
|
+
|
|
317
|
+
switch (header->kind) {
|
|
318
|
+
case XT_OBJECT_KIND_STRING:
|
|
319
|
+
case XT_OBJECT_KIND_BIGINT:
|
|
320
|
+
case XT_OBJECT_KIND_DATE:
|
|
321
|
+
/* Numbers/bytes only: no references to follow. */
|
|
322
|
+
return;
|
|
323
|
+
case XT_OBJECT_KIND_OBJECT:
|
|
324
|
+
case XT_OBJECT_KIND_ERROR: {
|
|
325
|
+
xt_object *object = (xt_object *)header;
|
|
326
|
+
xt_gc_scan_region(base, payload);
|
|
327
|
+
if (object->properties && object->count) {
|
|
328
|
+
xt_gc_scan_region(object->properties, sizeof(xt_property) * (size_t)object->count);
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
case XT_OBJECT_KIND_ARRAY: {
|
|
333
|
+
xt_array *array = (xt_array *)header;
|
|
334
|
+
xt_gc_scan_region(base, payload);
|
|
335
|
+
if (array->items && array->length) {
|
|
336
|
+
xt_gc_scan_region(array->items, sizeof(xt_value) * (size_t)array->length);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
case XT_OBJECT_KIND_FUNCTION: {
|
|
341
|
+
xt_function *function = (xt_function *)header;
|
|
342
|
+
xt_gc_scan_region(base, payload);
|
|
343
|
+
if (function->environment && function->environment_count > 0) {
|
|
344
|
+
xt_gc_scan_region(function->environment, sizeof(xt_value) * (size_t)function->environment_count);
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
case XT_OBJECT_KIND_MAP: {
|
|
349
|
+
xt_map *map = (xt_map *)header;
|
|
350
|
+
xt_gc_scan_region(base, payload);
|
|
351
|
+
if (map->keys && map->count) xt_gc_scan_region(map->keys, sizeof(xt_value) * (size_t)map->count);
|
|
352
|
+
if (map->values && map->count) xt_gc_scan_region(map->values, sizeof(xt_value) * (size_t)map->count);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
case XT_OBJECT_KIND_SET: {
|
|
356
|
+
xt_set_object *set = (xt_set_object *)header;
|
|
357
|
+
xt_gc_scan_region(base, payload);
|
|
358
|
+
if (set->values && set->count) xt_gc_scan_region(set->values, sizeof(xt_value) * (size_t)set->count);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
case XT_OBJECT_KIND_PROMISE:
|
|
362
|
+
xt_promise_gc_trace(header);
|
|
363
|
+
return;
|
|
364
|
+
case XT_OBJECT_KIND_GENERATOR:
|
|
365
|
+
xt_generator_gc_trace(header);
|
|
366
|
+
return;
|
|
367
|
+
default:
|
|
368
|
+
/* Symbols, regexps, iterators and any future kind whose references live
|
|
369
|
+
* inline: the payload scan covers them. */
|
|
370
|
+
xt_gc_scan_region(base, payload);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/* ------------------------------------------------------------------------- */
|
|
376
|
+
/* Release */
|
|
377
|
+
/* ------------------------------------------------------------------------- */
|
|
378
|
+
|
|
379
|
+
static void xt_gc_release(xt_header *header) {
|
|
380
|
+
switch (header->kind) {
|
|
381
|
+
case XT_OBJECT_KIND_STRING:
|
|
382
|
+
free(((xt_string *)header)->data);
|
|
383
|
+
break;
|
|
384
|
+
case XT_OBJECT_KIND_BIGINT:
|
|
385
|
+
free(((xt_bigint *)header)->limbs);
|
|
386
|
+
break;
|
|
387
|
+
case XT_OBJECT_KIND_OBJECT:
|
|
388
|
+
case XT_OBJECT_KIND_ERROR:
|
|
389
|
+
free(((xt_object *)header)->properties);
|
|
390
|
+
break;
|
|
391
|
+
case XT_OBJECT_KIND_ARRAY:
|
|
392
|
+
free(((xt_array *)header)->items);
|
|
393
|
+
break;
|
|
394
|
+
case XT_OBJECT_KIND_FUNCTION:
|
|
395
|
+
free(((xt_function *)header)->environment);
|
|
396
|
+
break;
|
|
397
|
+
case XT_OBJECT_KIND_MAP:
|
|
398
|
+
free(((xt_map *)header)->keys);
|
|
399
|
+
free(((xt_map *)header)->values);
|
|
400
|
+
break;
|
|
401
|
+
case XT_OBJECT_KIND_SET:
|
|
402
|
+
free(((xt_set_object *)header)->values);
|
|
403
|
+
break;
|
|
404
|
+
case XT_OBJECT_KIND_PROMISE:
|
|
405
|
+
xt_promise_gc_release(header);
|
|
406
|
+
break;
|
|
407
|
+
case XT_OBJECT_KIND_GENERATOR:
|
|
408
|
+
xt_generator_gc_release(header);
|
|
409
|
+
break;
|
|
410
|
+
default:
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
free(header);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/* ------------------------------------------------------------------------- */
|
|
417
|
+
/* Collection */
|
|
418
|
+
/* ------------------------------------------------------------------------- */
|
|
419
|
+
|
|
420
|
+
static void xt_gc_mark_roots(void) {
|
|
421
|
+
for (size_t i = 0; i < g_root_count; i++) {
|
|
422
|
+
if (g_root_slots[i]) xt_gc_mark_value(*g_root_slots[i]);
|
|
423
|
+
}
|
|
424
|
+
for (size_t i = 0; i < g_root_provider_count; i++) g_root_providers[i]();
|
|
425
|
+
|
|
426
|
+
/* Callee-saved registers captured by setjmp, plus every active stack region.
|
|
427
|
+
* Scanning more than the top region matters while a generator runs: the
|
|
428
|
+
* frames that resumed it are still live on the main stack. */
|
|
429
|
+
jmp_buf registers;
|
|
430
|
+
_setjmp(registers);
|
|
431
|
+
xt_gc_scan_region(®isters, sizeof(registers));
|
|
432
|
+
uintptr_t stack_pointer = (uintptr_t)®isters;
|
|
433
|
+
for (int i = 0; i < g_stack_depth; i++) {
|
|
434
|
+
uintptr_t high = g_stack_regions[i].high;
|
|
435
|
+
uintptr_t low = (i == g_stack_depth - 1) ? stack_pointer : g_stack_regions[i].scan_low;
|
|
436
|
+
if (high > low) xt_gc_scan_region((const void *)low, high - low);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
void xt_gc_collect(void) {
|
|
441
|
+
if (g_gc_running) return;
|
|
442
|
+
if (!g_gc_initialised) xt_gc_init();
|
|
443
|
+
g_gc_running = 1;
|
|
444
|
+
|
|
445
|
+
xt_gc_table_build();
|
|
446
|
+
xt_gc_mark_roots();
|
|
447
|
+
while (g_gray_count > 0) {
|
|
448
|
+
xt_header *header = g_gray[--g_gray_count];
|
|
449
|
+
xt_gc_process(header);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
xt_header **link = &g_heap_head;
|
|
453
|
+
xt_header *header = g_heap_head;
|
|
454
|
+
while (header) {
|
|
455
|
+
xt_header *next = header->gc_next;
|
|
456
|
+
if (header->flags & XT_GC_MARK) {
|
|
457
|
+
header->flags &= (uint8_t)~XT_GC_MARK;
|
|
458
|
+
link = &header->gc_next;
|
|
459
|
+
} else {
|
|
460
|
+
*link = next;
|
|
461
|
+
g_heap_bytes -= header->size;
|
|
462
|
+
g_heap_allocations--;
|
|
463
|
+
xt_gc_release(header);
|
|
464
|
+
}
|
|
465
|
+
header = next;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
xt_gc_table_free();
|
|
469
|
+
g_gc_running = 0;
|
|
470
|
+
|
|
471
|
+
if (!g_gc_threshold_pinned) {
|
|
472
|
+
if (g_gc_threshold < g_heap_bytes * 2) g_gc_threshold = g_heap_bytes * 2;
|
|
473
|
+
if (g_gc_threshold < XT_GC_MIN_THRESHOLD) g_gc_threshold = XT_GC_MIN_THRESHOLD;
|
|
474
|
+
}
|
|
475
|
+
}
|
package/runtime/xt_generator.c
CHANGED
|
@@ -45,14 +45,23 @@ typedef struct xt_generator {
|
|
|
45
45
|
int done;
|
|
46
46
|
int threw;
|
|
47
47
|
int booted;
|
|
48
|
+
/* Private stack bounds, registered as an active stack region while the
|
|
49
|
+
* generator runs and scanned conservatively while it is suspended. */
|
|
50
|
+
char *stack;
|
|
51
|
+
size_t stack_size;
|
|
52
|
+
int stack_pushed;
|
|
48
53
|
void *saved_try_top;
|
|
49
54
|
#ifdef _WIN32
|
|
50
55
|
void *fiber;
|
|
51
56
|
void *caller_fiber;
|
|
57
|
+
/* `SwitchToFiber` keeps the resumer's callee-saved registers in an opaque
|
|
58
|
+
* fiber context (POSIX stores them in `caller`), so snapshot them for the
|
|
59
|
+
* collector before switching away. */
|
|
60
|
+
jmp_buf caller_regs;
|
|
61
|
+
int has_caller_regs;
|
|
52
62
|
#else
|
|
53
63
|
ucontext_t ctx;
|
|
54
64
|
ucontext_t caller;
|
|
55
|
-
char *stack;
|
|
56
65
|
#endif
|
|
57
66
|
} xt_generator;
|
|
58
67
|
|
|
@@ -93,6 +102,27 @@ static void xt_generator_suspend(xt_generator *gen) {
|
|
|
93
102
|
|
|
94
103
|
/* Runs on the generator's private stack. */
|
|
95
104
|
static void xt_generator_run(xt_generator *gen) {
|
|
105
|
+
#ifdef _WIN32
|
|
106
|
+
/* The fiber stack is not observable from the outside; capture its bounds
|
|
107
|
+
* here (before the body allocates anything) so a collection during the body
|
|
108
|
+
* has a valid scan region. */
|
|
109
|
+
if (!gen->stack) {
|
|
110
|
+
MEMORY_BASIC_INFORMATION info;
|
|
111
|
+
uintptr_t here = (uintptr_t)&info;
|
|
112
|
+
if (VirtualQuery((LPCVOID)here, &info, sizeof(info))) {
|
|
113
|
+
/* Use the *committed* region containing the current frame: a Windows
|
|
114
|
+
* stack reserves more than it commits, and `AllocationBase + RegionSize`
|
|
115
|
+
* can fall in uncommitted memory. `BaseAddress + RegionSize` is the
|
|
116
|
+
* stack top. */
|
|
117
|
+
gen->stack = (char *)info.BaseAddress;
|
|
118
|
+
gen->stack_size = info.RegionSize;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
#endif
|
|
122
|
+
if (gen->stack && gen->stack_size && !gen->stack_pushed) {
|
|
123
|
+
xt_gc_push_stack((uintptr_t)gen->stack, (uintptr_t)gen->stack + gen->stack_size);
|
|
124
|
+
gen->stack_pushed = 1;
|
|
125
|
+
}
|
|
96
126
|
void *boundary = xt_try_enter();
|
|
97
127
|
if (xt_try_setjmp(boundary) == 0) {
|
|
98
128
|
xt_function *function = (xt_function *)XT_GET_PTR(gen->function);
|
|
@@ -127,6 +157,7 @@ static void xt_generator_boot(xt_generator *gen) {
|
|
|
127
157
|
size_t size = 1 << 20;
|
|
128
158
|
gen->stack = (char *)malloc(size);
|
|
129
159
|
if (!gen->stack) abort();
|
|
160
|
+
gen->stack_size = size;
|
|
130
161
|
if (getcontext(&gen->ctx) != 0) abort();
|
|
131
162
|
gen->ctx.uc_stack.ss_sp = gen->stack;
|
|
132
163
|
gen->ctx.uc_stack.ss_size = size;
|
|
@@ -146,6 +177,9 @@ static void xt_generator_dispose(xt_generator *gen) {
|
|
|
146
177
|
gen->fiber = NULL;
|
|
147
178
|
}
|
|
148
179
|
gen->caller_fiber = NULL;
|
|
180
|
+
/* `DeleteFiber` releases the stack: never scan it again. */
|
|
181
|
+
gen->stack = NULL;
|
|
182
|
+
gen->stack_size = 0;
|
|
149
183
|
#else
|
|
150
184
|
if (gen->stack) {
|
|
151
185
|
free(gen->stack);
|
|
@@ -158,9 +192,20 @@ static void xt_generator_resume(xt_generator *gen) {
|
|
|
158
192
|
if (!gen->booted) xt_generator_boot(gen);
|
|
159
193
|
xt_generator *previous = g_active_generator;
|
|
160
194
|
g_active_generator = gen;
|
|
195
|
+
/* Record where the current (calling) stack suspends before switching away.
|
|
196
|
+
* On Windows the first push happens inside the fiber once its stack bounds
|
|
197
|
+
* are known, so the caller's region must be marked suspended here, on the
|
|
198
|
+
* caller's own stack. */
|
|
199
|
+
xt_gc_suspend((uintptr_t)&gen);
|
|
200
|
+
if (gen->stack && gen->stack_size && !gen->stack_pushed) {
|
|
201
|
+
xt_gc_push_stack((uintptr_t)gen->stack, (uintptr_t)gen->stack + gen->stack_size);
|
|
202
|
+
gen->stack_pushed = 1;
|
|
203
|
+
}
|
|
161
204
|
void *caller_try = xt_try_mark();
|
|
162
205
|
xt_try_restore(gen->saved_try_top);
|
|
163
206
|
#ifdef _WIN32
|
|
207
|
+
_setjmp(gen->caller_regs);
|
|
208
|
+
gen->has_caller_regs = 1;
|
|
164
209
|
gen->caller_fiber = GetCurrentFiber();
|
|
165
210
|
SwitchToFiber(gen->fiber);
|
|
166
211
|
#else
|
|
@@ -168,15 +213,63 @@ static void xt_generator_resume(xt_generator *gen) {
|
|
|
168
213
|
#endif
|
|
169
214
|
gen->saved_try_top = xt_try_mark();
|
|
170
215
|
xt_try_restore(caller_try);
|
|
216
|
+
if (gen->stack_pushed) {
|
|
217
|
+
xt_gc_pop_stack();
|
|
218
|
+
gen->stack_pushed = 0;
|
|
219
|
+
}
|
|
171
220
|
if (gen->done) xt_generator_dispose(gen);
|
|
172
221
|
g_active_generator = previous;
|
|
173
222
|
}
|
|
174
223
|
|
|
224
|
+
/* The active generator is only reachable through this static pointer, and its
|
|
225
|
+
* private stack holds values that are invisible to a scan of the main stack. */
|
|
226
|
+
static void xt_generator_gc_scan_roots(void) {
|
|
227
|
+
if (!g_active_generator) return;
|
|
228
|
+
xt_gc_mark_header((xt_header *)g_active_generator);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
static void xt_generator_gc_register(void) {
|
|
232
|
+
static int registered = 0;
|
|
233
|
+
if (registered) return;
|
|
234
|
+
registered = 1;
|
|
235
|
+
xt_gc_register_root_provider(xt_generator_gc_scan_roots);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
void xt_generator_gc_trace(xt_header *header) {
|
|
239
|
+
xt_generator *gen = (xt_generator *)header;
|
|
240
|
+
size_t payload = header->size > sizeof(xt_header) ? header->size - sizeof(xt_header) : 0;
|
|
241
|
+
xt_gc_scan_region((const char *)header + sizeof(xt_header), payload);
|
|
242
|
+
if (gen->args && gen->argc > 0) {
|
|
243
|
+
xt_gc_scan_region(gen->args, sizeof(xt_value) * (size_t)gen->argc);
|
|
244
|
+
}
|
|
245
|
+
if (gen->stack && gen->stack_size) xt_gc_scan_region(gen->stack, gen->stack_size);
|
|
246
|
+
#ifndef _WIN32
|
|
247
|
+
/* Registers saved by `swapcontext` at the last yield are not on the stack. */
|
|
248
|
+
xt_gc_scan_region(&gen->ctx, sizeof(gen->ctx));
|
|
249
|
+
#else
|
|
250
|
+
if (gen->has_caller_regs) xt_gc_scan_region(&gen->caller_regs, sizeof(gen->caller_regs));
|
|
251
|
+
#endif
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
void xt_generator_gc_release(xt_header *header) {
|
|
255
|
+
xt_generator *gen = (xt_generator *)header;
|
|
256
|
+
free(gen->args);
|
|
257
|
+
#ifdef _WIN32
|
|
258
|
+
if (gen->fiber) DeleteFiber(gen->fiber);
|
|
259
|
+
#else
|
|
260
|
+
free(gen->stack);
|
|
261
|
+
#endif
|
|
262
|
+
}
|
|
263
|
+
|
|
175
264
|
xt_value xt_generator_new(xt_value function, xt_value thisValue, int32_t argc, xt_value *argv) {
|
|
176
265
|
xt_generator *gen = (xt_generator *)xt_alloc(sizeof(xt_generator), XT_OBJECT_KIND_GENERATOR);
|
|
177
266
|
gen->function = function;
|
|
178
267
|
gen->this_value = thisValue;
|
|
179
268
|
gen->argc = argc;
|
|
269
|
+
gen->args = NULL;
|
|
270
|
+
gen->stack = NULL;
|
|
271
|
+
gen->stack_size = 0;
|
|
272
|
+
gen->stack_pushed = 0;
|
|
180
273
|
gen->result = XT_UNDEFINED;
|
|
181
274
|
gen->sent = XT_UNDEFINED;
|
|
182
275
|
gen->iter_value = XT_UNDEFINED;
|
|
@@ -185,6 +278,7 @@ xt_value xt_generator_new(xt_value function, xt_value thisValue, int32_t argc, x
|
|
|
185
278
|
if (!gen->args) abort();
|
|
186
279
|
memcpy(gen->args, argv, sizeof(xt_value) * (size_t)argc);
|
|
187
280
|
}
|
|
281
|
+
xt_generator_gc_register();
|
|
188
282
|
return XT_FROM_PTR(XT_TAG_OBJECT, gen);
|
|
189
283
|
}
|
|
190
284
|
|
package/runtime/xt_loop.c
CHANGED
|
@@ -130,14 +130,22 @@ static int64_t xt_timer_next_due(void) {
|
|
|
130
130
|
static void xt_timer_compact(void) {
|
|
131
131
|
uint32_t write = 0;
|
|
132
132
|
for (uint32_t read = 0; read < xt_timer_count; read++) {
|
|
133
|
-
if (!xt_timers[read].active)
|
|
133
|
+
if (!xt_timers[read].active) {
|
|
134
|
+
free(xt_timers[read].args);
|
|
135
|
+
xt_timers[read].args = NULL;
|
|
136
|
+
xt_timers[read].argc = 0;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
134
139
|
if (write != read) xt_timers[write] = xt_timers[read];
|
|
135
140
|
write++;
|
|
136
141
|
}
|
|
137
142
|
xt_timer_count = write;
|
|
138
143
|
}
|
|
139
144
|
|
|
145
|
+
static void xt_loop_gc_register(void);
|
|
146
|
+
|
|
140
147
|
static uint64_t xt_timer_add(xt_value callback, int64_t delay_ms, int64_t interval_ms, int32_t argc, xt_value *argv) {
|
|
148
|
+
xt_loop_gc_register();
|
|
141
149
|
if (xt_timer_count >= xt_timer_capacity) {
|
|
142
150
|
xt_timer_capacity = xt_timer_capacity == 0 ? 16 : xt_timer_capacity * 2;
|
|
143
151
|
xt_timer *grown = (xt_timer *)realloc(xt_timers, sizeof(xt_timer) * xt_timer_capacity);
|
|
@@ -170,6 +178,31 @@ static int xt_timer_cancel(uint64_t id) {
|
|
|
170
178
|
return 0;
|
|
171
179
|
}
|
|
172
180
|
|
|
181
|
+
/* Watchers and pending timers hold values outside the value graph: the timer
|
|
182
|
+
* callbacks/arguments live in the flat array, and extension watchers keep an
|
|
183
|
+
* `xt_value *` box in their opaque userdata (the `xt_loop_add` convention). */
|
|
184
|
+
static void xt_loop_gc_scan_roots(void) {
|
|
185
|
+
for (int i = 0; i < xt_loop_watcher_count; i++) {
|
|
186
|
+
if (!xt_loop_watchers[i].active) continue;
|
|
187
|
+
xt_value *boxed = (xt_value *)xt_loop_watchers[i].userdata;
|
|
188
|
+
if (boxed) xt_gc_mark_value(*boxed);
|
|
189
|
+
}
|
|
190
|
+
for (uint32_t i = 0; i < xt_timer_count; i++) {
|
|
191
|
+
if (!xt_timers[i].active) continue;
|
|
192
|
+
xt_gc_mark_value(xt_timers[i].callback);
|
|
193
|
+
if (xt_timers[i].args && xt_timers[i].argc > 0) {
|
|
194
|
+
xt_gc_scan_region(xt_timers[i].args, sizeof(xt_value) * (size_t)xt_timers[i].argc);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static void xt_loop_gc_register(void) {
|
|
200
|
+
static int registered = 0;
|
|
201
|
+
if (registered) return;
|
|
202
|
+
registered = 1;
|
|
203
|
+
xt_gc_register_root_provider(xt_loop_gc_scan_roots);
|
|
204
|
+
}
|
|
205
|
+
|
|
173
206
|
/* Invoke every timer whose deadline has passed. Timers scheduled by a callback
|
|
174
207
|
* (or by a microtask) are left for the next reactor iteration, so a recursive
|
|
175
208
|
* `setTimeout(fn, 0)` cannot spin forever inside a single call. */
|
|
@@ -232,6 +265,7 @@ xt_value xt_clear_interval(int32_t argc, xt_value *argv) {
|
|
|
232
265
|
|
|
233
266
|
int xt_loop_add(int fd, int events, xt_io_handler handler, void *userdata) {
|
|
234
267
|
if (fd < 0 || !handler) return -1;
|
|
268
|
+
xt_loop_gc_register();
|
|
235
269
|
xt_loop_watcher *existing = xt_loop_find(fd);
|
|
236
270
|
if (existing) {
|
|
237
271
|
existing->events = events;
|