xbintsc 0.3.35 → 0.3.49

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 (130) hide show
  1. package/AGENTS.md +95 -0
  2. package/README.md +31 -3
  3. package/README.zh-CN.md +23 -0
  4. package/dist/src/cli/hints.d.ts +54 -0
  5. package/dist/src/cli/hints.js +165 -0
  6. package/dist/src/cli/hints.js.map +1 -0
  7. package/dist/src/cli/main.js +73 -9
  8. package/dist/src/cli/main.js.map +1 -1
  9. package/dist/src/codegen/generator/module.js +10 -0
  10. package/dist/src/codegen/generator/module.js.map +1 -1
  11. package/dist/src/codegen/generator/state.js +4 -0
  12. package/dist/src/codegen/generator/state.js.map +1 -1
  13. package/dist/src/codegen/generator/tables.d.ts +26 -0
  14. package/dist/src/codegen/generator/tables.js +64 -12
  15. package/dist/src/codegen/generator/tables.js.map +1 -1
  16. package/dist/src/diagnostics/source-text.d.ts +22 -0
  17. package/dist/src/diagnostics/source-text.js +76 -0
  18. package/dist/src/diagnostics/source-text.js.map +1 -0
  19. package/dist/src/driver/bundler/graph.js +2 -1
  20. package/dist/src/driver/bundler/graph.js.map +1 -1
  21. package/dist/src/driver/compiler.js +3 -2
  22. package/dist/src/driver/compiler.js.map +1 -1
  23. package/dist/src/lexer/scanner/strings.js +16 -3
  24. package/dist/src/lexer/scanner/strings.js.map +1 -1
  25. package/dist/tests/cli/hints.test.d.ts +9 -0
  26. package/dist/tests/cli/hints.test.js +143 -0
  27. package/dist/tests/cli/hints.test.js.map +1 -0
  28. package/dist/tests/cli/main.test.js +6 -4
  29. package/dist/tests/cli/main.test.js.map +1 -1
  30. package/dist/tests/codegen/llvm.test.js +17 -2
  31. package/dist/tests/codegen/llvm.test.js.map +1 -1
  32. package/dist/tests/e2e/gc.test.d.ts +1 -0
  33. package/dist/tests/e2e/gc.test.js +168 -0
  34. package/dist/tests/e2e/gc.test.js.map +1 -0
  35. package/dist/tests/e2e/harness.d.ts +2 -0
  36. package/dist/tests/e2e/harness.js +1 -0
  37. package/dist/tests/e2e/harness.js.map +1 -1
  38. package/dist/tests/helpers.js +3 -2
  39. package/dist/tests/helpers.js.map +1 -1
  40. package/dist/tests/lexer/strings.test.js +14 -2
  41. package/dist/tests/lexer/strings.test.js.map +1 -1
  42. package/doc/DESIGN.md +117 -0
  43. package/doc/ai/README.md +63 -0
  44. package/doc/ai/build-recipe.md +137 -0
  45. package/doc/ai/cli.md +142 -0
  46. package/doc/ai/contributing.md +196 -0
  47. package/doc/ai/extensions.md +148 -0
  48. package/doc/ai/language-support.md +152 -0
  49. package/doc/ai/troubleshooting.md +163 -0
  50. package/doc/ai/zh-CN/README.md +56 -0
  51. package/doc/ai/zh-CN/build-recipe.md +132 -0
  52. package/doc/ai/zh-CN/cli.md +127 -0
  53. package/doc/ai/zh-CN/contributing.md +173 -0
  54. package/doc/ai/zh-CN/extensions.md +139 -0
  55. package/doc/ai/zh-CN/language-support.md +147 -0
  56. package/doc/ai/zh-CN/troubleshooting.md +150 -0
  57. package/doc/gui-scripts.md +350 -0
  58. package/doc/gui.md +646 -0
  59. package/doc/icon.md +265 -0
  60. package/doc/implemented.md +373 -0
  61. package/doc/node-implemented.md +588 -0
  62. package/doc/node-unimplemented.md +167 -0
  63. package/doc/post/announce.md +43 -0
  64. package/doc/requirements.md +145 -0
  65. package/doc/unimplemented.md +286 -0
  66. package/doc/xbintsc.config.schema.json +67 -0
  67. package/doc/zh-CN/DESIGN.md +104 -0
  68. package/doc/zh-CN/gui-scripts.md +329 -0
  69. package/doc/zh-CN/gui.md +588 -0
  70. package/doc/zh-CN/icon.md +241 -0
  71. package/doc/zh-CN/implemented.md +365 -0
  72. package/doc/zh-CN/node-implemented.md +533 -0
  73. package/doc/zh-CN/node-unimplemented.md +141 -0
  74. package/doc/zh-CN/plan-require-node-modules.md +284 -0
  75. package/doc/zh-CN/post/announce.md +47 -0
  76. package/doc/zh-CN/requirements.md +134 -0
  77. package/doc/zh-CN/unimplemented.md +247 -0
  78. package/llms.txt +45 -0
  79. package/package.json +6 -2
  80. package/runtime/ext_gui/dom_api_proto.cpp +5 -0
  81. package/runtime/ext_gui/gui.cpp +3 -1
  82. package/runtime/ext_gui/renderer.cpp +13 -11
  83. package/runtime/ext_gui/renderer_image.cpp +12 -8
  84. package/runtime/ext_gui/renderer_shaders.h +131 -4
  85. package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
  86. package/runtime/ext_gui/renderer_text.cpp +12 -8
  87. package/runtime/ext_gui/shaders.hlsl +98 -0
  88. package/runtime/ext_gui/spirv/fill.frag +19 -0
  89. package/runtime/ext_gui/spirv/fill.vert +42 -0
  90. package/runtime/ext_gui/spirv/image.frag +16 -0
  91. package/runtime/ext_gui/spirv/quad.vert +30 -0
  92. package/runtime/ext_gui/spirv/text.frag +16 -0
  93. package/runtime/ext_gui/window.cpp +1 -0
  94. package/runtime/ext_node/buffer/parts/prototype.inc +1 -0
  95. package/runtime/ext_node/dgram/dgram.c +1 -0
  96. package/runtime/ext_node/events/events.c +1 -0
  97. package/runtime/ext_node/fs/fs_ops.c +10 -25
  98. package/runtime/ext_node/fs/glob.c +13 -30
  99. package/runtime/ext_node/fs/promises.c +1 -0
  100. package/runtime/ext_node/http/parts/prototypes.inc +5 -0
  101. package/runtime/ext_node/net/parts/prototypes.inc +2 -0
  102. package/runtime/ext_node/process/process.c +9 -7
  103. package/runtime/ext_node/stream/stream.c +1 -0
  104. package/runtime/ext_node/util/util.c +6 -6
  105. package/runtime/rt.h +10 -0
  106. package/runtime/rt_internal.h +63 -2
  107. package/runtime/xt_alloc.c +442 -5
  108. package/runtime/xt_generator.c +95 -1
  109. package/runtime/xt_loop.c +35 -1
  110. package/runtime/xt_promise.c +46 -0
  111. package/runtime/xt_stdlib2/error.inc +1 -0
  112. package/runtime/xt_stdlib2/regexp-match.inc +8 -7
  113. package/runtime/xt_symbol.c +2 -0
  114. package/runtime/xt_typed_array/construction.inc +142 -0
  115. package/runtime/xt_typed_array/elements.inc +92 -0
  116. package/runtime/xt_typed_array/methods.inc +329 -0
  117. package/runtime/xt_typed_array.c +6 -548
  118. package/runtime/xt_values/number-format.inc +26 -0
  119. package/scripts/build-gui-shaders.mjs +204 -0
  120. package/scripts/build-gui.ts +35 -0
  121. package/scripts/check-file-length.ts +89 -0
  122. package/src/cli/hints.ts +194 -0
  123. package/src/cli/main.ts +82 -9
  124. package/src/codegen/generator/module.ts +10 -0
  125. package/src/codegen/generator/state.ts +4 -0
  126. package/src/codegen/generator/tables.ts +60 -14
  127. package/src/diagnostics/source-text.ts +78 -0
  128. package/src/driver/bundler/graph.ts +2 -1
  129. package/src/driver/compiler.ts +3 -2
  130. package/src/lexer/scanner/strings.ts +16 -3
@@ -52,7 +52,8 @@
52
52
  #define XT_OBJECT_KIND_GENERATOR 13
53
53
  #define XT_OBJECT_KIND_ITERATOR 14
54
54
 
55
- /* Common header for every heap object. */
55
+ /* Common header for every heap object. `flags` bit 0 is the collector's mark
56
+ * bit; it is cleared on every surviving object at the end of a sweep. */
56
57
  typedef struct xt_header {
57
58
  uint8_t kind;
58
59
  uint8_t flags;
@@ -61,6 +62,8 @@ typedef struct xt_header {
61
62
  struct xt_header *gc_next;
62
63
  } xt_header;
63
64
 
65
+ #define XT_GC_MARK 0x01u
66
+
64
67
  typedef struct {
65
68
  xt_header header;
66
69
  uint32_t length;
@@ -122,9 +125,67 @@ typedef struct {
122
125
  uint8_t is_generator;
123
126
  } xt_function;
124
127
 
125
- /* Allocation (xt_alloc.c). */
128
+ /* Allocation and garbage collection (xt_alloc.c).
129
+ *
130
+ * The collector is a non-moving mark-sweep over the linked list of every
131
+ * allocation. Roots are (a) explicitly registered value slots, (b) root
132
+ * providers registered by runtime subsystems and extensions, and (c) a
133
+ * conservative scan of the active C stack (plus a setjmp buffer for the
134
+ * callee-saved registers). Heap objects are traced precisely for their own
135
+ * value fields, with a conservative scan of their payload for the raw pointer
136
+ * fields that older code still stores directly. */
126
137
  void *xt_alloc(size_t size, int kind);
127
138
 
139
+ /* Record the current stack as the base of the root set. Call once, as early as
140
+ * possible, before any allocation (generated `main` does this). */
141
+ void xt_gc_init(void);
142
+ /* Record the high bound of the main stack. The generated `main` passes the
143
+ * address of one of its own locals, which lives above every frame its callees
144
+ * ever use, so the conservative scan reaches spilled roots. Without this the
145
+ * bound would be taken from inside the runtime and would cut off the top of
146
+ * the caller's frame. Safe to call before `xt_gc_init`. */
147
+ void xt_gc_set_stack_base(void *base);
148
+ /* Enable automatic collection once the heap crosses the threshold. Programs
149
+ * that never arm the collector still get `xt_gc_collect()` on demand. */
150
+ void xt_gc_arm(void);
151
+ /* Run a full collection now. Re-entrant calls are ignored. */
152
+ void xt_gc_collect(void);
153
+ /* Override the automatic trigger threshold (bytes of live heap). */
154
+ void xt_gc_set_threshold(size_t bytes);
155
+
156
+ /* Explicit roots: the address of a slot that always holds a live value (a
157
+ * module global, a runtime prototype cache, ...). */
158
+ void xt_gc_add_root(xt_value *slot);
159
+ void xt_gc_add_root_range(xt_value *base, size_t count);
160
+ /* Subsystems whose live set is not reachable through value slots (the event
161
+ * loop's timers, the promise microtask queue, ...) register a provider that
162
+ * marks its reachable objects. Providers run during every collection. */
163
+ void xt_gc_register_root_provider(void (*provider)(void));
164
+
165
+ /* Track the active C stack so the conservative scan has an upper bound. The
166
+ * initial region is installed by `xt_gc_init`; each generator pushes its own
167
+ * private stack while it runs. Before switching to a deeper region the caller
168
+ * records where its own stack was suspended, so every active region is scanned
169
+ * and suspended callers stay reachable. */
170
+ void xt_gc_push_stack(uintptr_t low, uintptr_t high);
171
+ /* Mark the currently-executing region as suspended at `sp` (its frames above
172
+ * `sp` stay live while a deeper generator region runs). */
173
+ void xt_gc_suspend(uintptr_t sp);
174
+ void xt_gc_pop_stack(void);
175
+
176
+ /* Marking primitives used by root providers and per-kind trace hooks. */
177
+ void xt_gc_mark_value(xt_value value);
178
+ void xt_gc_mark_header(xt_header *header);
179
+ /* Conservatively mark every pointer-sized word in `[base, base + bytes)`. */
180
+ void xt_gc_scan_region(const void *base, size_t bytes);
181
+
182
+ /* Kind-specific hooks for allocations with separately allocated sub-objects.
183
+ * Implemented by the translation unit that owns the representation. */
184
+ void xt_promise_gc_trace(xt_header *header);
185
+ void xt_promise_gc_release(xt_header *header);
186
+ void xt_generator_gc_trace(xt_header *header);
187
+ void xt_generator_gc_release(xt_header *header);
188
+
128
189
  /* Value / string helpers shared across translation units (xt_values.c). */
129
190
  xt_string *xt_string_alloc(size_t length);
130
191
  xt_string *xt_as_string(xt_value value);
@@ -1,31 +1,152 @@
1
1
  /*
2
- * xbintsc runtime — heap allocation.
2
+ * xbintsc runtime — heap allocation and garbage collection.
3
3
  *
4
- * A bump-style arena shared by every object kind. Allocations are never
5
- * reclaimed; the design keeps replacing this with a real collector isolated
6
- * behind `xt_alloc` (see rt_internal.h).
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
- /* Allocation */
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(&registers, sizeof(registers));
432
+ uintptr_t stack_pointer = (uintptr_t)&registers;
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
+ }