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
@@ -187,22 +187,26 @@ void flushAtlas(SDL_GPUDevice *device) {
187
187
  }
188
188
 
189
189
  SDL_GPUGraphicsPipeline *createTextPipeline(SDL_GPUDevice *device, SDL_Window *reference) {
190
+ const ShaderSource text_vs = selectShader(device, ShaderProgram::Text, false);
191
+ const ShaderSource text_fs = selectShader(device, ShaderProgram::Text, true);
192
+ if (!text_vs.valid() || !text_fs.valid()) return nullptr;
193
+
190
194
  SDL_GPUShaderCreateInfo vs_info;
191
195
  memset(&vs_info, 0, sizeof(vs_info));
192
- vs_info.code_size = strlen(kTextMSL);
193
- vs_info.code = (const Uint8 *)kTextMSL;
194
- vs_info.entrypoint = "vs_text";
195
- vs_info.format = SDL_GPU_SHADERFORMAT_MSL;
196
+ vs_info.code_size = text_vs.size;
197
+ vs_info.code = text_vs.code;
198
+ vs_info.entrypoint = text_vs.entry;
199
+ vs_info.format = text_vs.format;
196
200
  vs_info.stage = SDL_GPU_SHADERSTAGE_VERTEX;
197
201
  vs_info.num_uniform_buffers = 1;
198
202
  SDL_GPUShader *vs = SDL_CreateGPUShader(device, &vs_info);
199
203
 
200
204
  SDL_GPUShaderCreateInfo fs_info;
201
205
  memset(&fs_info, 0, sizeof(fs_info));
202
- fs_info.code_size = strlen(kTextMSL);
203
- fs_info.code = (const Uint8 *)kTextMSL;
204
- fs_info.entrypoint = "fs_text";
205
- fs_info.format = SDL_GPU_SHADERFORMAT_MSL;
206
+ fs_info.code_size = text_fs.size;
207
+ fs_info.code = text_fs.code;
208
+ fs_info.entrypoint = text_fs.entry;
209
+ fs_info.format = text_fs.format;
206
210
  fs_info.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
207
211
  fs_info.num_samplers = 1;
208
212
  SDL_GPUShader *fs = SDL_CreateGPUShader(device, &fs_info);
@@ -0,0 +1,98 @@
1
+ /*
2
+ * xbintsc GUI engine — shader sources compiled ahead of time into the embedded
3
+ * blobs in `renderer_blobs.h`.
4
+ *
5
+ * Three programs, two entry points each:
6
+ * - `vs_main` / `fs_main` rounded-rectangle fills
7
+ * - `vs_text` / `fs_text` glyph quads (single-channel atlas)
8
+ * - `vs_text` / `fs_image` image quads (RGBA texture)
9
+ *
10
+ * `vs_main` and `vs_text` are identical in effect; they are kept separate so the
11
+ * Metal (MSL) path keeps its distinct entry-point names, and this file is the
12
+ * HLSL half of the pair. Vertex attribute semantics map 1:1 onto
13
+ * `SDL_GPUVertexAttribute.location`, and the push constant becomes `b0`.
14
+ *
15
+ * Compile with (Windows SDK `dxc`):
16
+ * dxc -T vs_6_0 -E vs_main -Fo fill_vs.dxil shaders.hlsl
17
+ * dxc -T ps_6_0 -E fs_main -Fo fill_fs.dxil shaders.hlsl
18
+ * dxc -T vs_6_0 -E vs_text -Fo text_vs.dxil shaders.hlsl
19
+ * dxc -T ps_6_0 -E fs_text -Fo text_fs.dxil shaders.hlsl
20
+ * dxc -T ps_6_0 -E fs_image -Fo image_fs.dxil shaders.hlsl
21
+ */
22
+
23
+ cbuffer Viewport : register(b0) { float2 viewport; };
24
+
25
+ /* -- fills ---------------------------------------------------------------- */
26
+
27
+ struct FillIn {
28
+ float2 position : TEXCOORD0;
29
+ float2 local : TEXCOORD1;
30
+ float2 halfsize : TEXCOORD2;
31
+ float radius : TEXCOORD3;
32
+ float4 color : TEXCOORD4;
33
+ };
34
+
35
+ struct FillOut {
36
+ float4 position : SV_Position;
37
+ float2 local : TEXCOORD1;
38
+ float2 halfsize : TEXCOORD2;
39
+ float radius : TEXCOORD3;
40
+ float4 color : TEXCOORD4;
41
+ };
42
+
43
+ FillOut vs_main(FillIn input) {
44
+ FillOut output;
45
+ float2 ndc = float2(input.position.x / viewport.x * 2.0 - 1.0,
46
+ 1.0 - input.position.y / viewport.y * 2.0);
47
+ output.position = float4(ndc, 0.0, 1.0);
48
+ output.local = input.local;
49
+ output.halfsize = input.halfsize;
50
+ output.radius = input.radius;
51
+ output.color = input.color;
52
+ return output;
53
+ }
54
+
55
+ float4 fs_main(FillOut input) : SV_Target {
56
+ float2 d = abs(input.local) - input.halfsize + input.radius;
57
+ float dist = min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - input.radius;
58
+ float aa = max(fwidth(dist), 0.0001);
59
+ float alpha = clamp(0.5 - dist / aa, 0.0, 1.0);
60
+ return float4(input.color.rgb, input.color.a * alpha);
61
+ }
62
+
63
+ /* -- text and images ------------------------------------------------------ */
64
+
65
+ struct TextIn {
66
+ float2 position : TEXCOORD0;
67
+ float2 uv : TEXCOORD1;
68
+ float4 color : TEXCOORD2;
69
+ };
70
+
71
+ struct TextOut {
72
+ float4 position : SV_Position;
73
+ float2 uv : TEXCOORD1;
74
+ float4 color : TEXCOORD2;
75
+ };
76
+
77
+ Texture2D atlas : register(t0);
78
+ SamplerState samp : register(s0);
79
+
80
+ TextOut vs_text(TextIn input) {
81
+ TextOut output;
82
+ float2 ndc = float2(input.position.x / viewport.x * 2.0 - 1.0,
83
+ 1.0 - input.position.y / viewport.y * 2.0);
84
+ output.position = float4(ndc, 0.0, 1.0);
85
+ output.uv = input.uv;
86
+ output.color = input.color;
87
+ return output;
88
+ }
89
+
90
+ float4 fs_text(TextOut input) : SV_Target {
91
+ float coverage = atlas.Sample(samp, input.uv).r;
92
+ return float4(input.color.rgb, input.color.a * coverage);
93
+ }
94
+
95
+ float4 fs_image(TextOut input) : SV_Target {
96
+ float4 c = atlas.Sample(samp, input.uv);
97
+ return float4(c.rgb * input.color.rgb, c.a * input.color.a);
98
+ }
@@ -0,0 +1,19 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — rounded-rectangle fill fragment shader (Vulkan/SPIR-V).
4
+ * The distance field mirrors the MSL/DXIL fill shaders. */
5
+
6
+ layout(location = 0) in vec2 v_local;
7
+ layout(location = 1) in vec2 v_halfsize;
8
+ layout(location = 2) in float v_radius;
9
+ layout(location = 3) in vec4 v_color;
10
+
11
+ layout(location = 0) out vec4 out_color;
12
+
13
+ void main() {
14
+ vec2 d = abs(v_local) - v_halfsize + v_radius;
15
+ float dist = min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - v_radius;
16
+ float aa = max(fwidth(dist), 0.0001);
17
+ float alpha = clamp(0.5 - dist / aa, 0.0, 1.0);
18
+ out_color = vec4(v_color.rgb, v_color.a * alpha);
19
+ }
@@ -0,0 +1,42 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — rounded-rectangle fill, Vulkan/SPIR-V path.
4
+ *
5
+ * The descriptor layout matches what SDL_GPU's Vulkan backend builds
6
+ * (`SDL_gpu_vulkan.c`, `CreateDescriptorSetLayout`): uniforms at
7
+ * `set=1, binding=0`, and (in the text/image shaders) samplers at
8
+ * `set=2, binding=0` with textures at `set=2, binding=1`.
9
+ *
10
+ * Vertex attributes (matching SDL_GPUVertexAttribute.location):
11
+ * 0 -> position (pixels, viewport-relative)
12
+ * 1 -> local (pixels, relative to the rectangle centre)
13
+ * 2 -> halfsize (rectangle half extents, pixels)
14
+ * 3 -> radius (pixels)
15
+ * 4 -> colour
16
+ * The push constant is the viewport size in logical pixels.
17
+ */
18
+
19
+ layout(location = 0) in vec2 in_position;
20
+ layout(location = 1) in vec2 in_local;
21
+ layout(location = 2) in vec2 in_halfsize;
22
+ layout(location = 3) in float in_radius;
23
+ layout(location = 4) in vec4 in_color;
24
+
25
+ layout(location = 0) out vec2 v_local;
26
+ layout(location = 1) out vec2 v_halfsize;
27
+ layout(location = 2) out float v_radius;
28
+ layout(location = 3) out vec4 v_color;
29
+
30
+ layout(set = 1, binding = 0) uniform Viewport {
31
+ vec2 size;
32
+ } viewport;
33
+
34
+ void main() {
35
+ vec2 ndc = vec2(in_position.x / viewport.size.x * 2.0 - 1.0,
36
+ 1.0 - in_position.y / viewport.size.y * 2.0);
37
+ gl_Position = vec4(ndc, 0.0, 1.0);
38
+ v_local = in_local;
39
+ v_halfsize = in_halfsize;
40
+ v_radius = in_radius;
41
+ v_color = in_color;
42
+ }
@@ -0,0 +1,16 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — image quads (Vulkan/SPIR-V): sample an RGBA texture and
4
+ * modulate it by the vertex colour. */
5
+
6
+ layout(location = 0) in vec2 v_uv;
7
+ layout(location = 1) in vec4 v_color;
8
+
9
+ layout(location = 0) out vec4 out_color;
10
+
11
+ layout(set = 2, binding = 0) uniform sampler2D image;
12
+
13
+ void main() {
14
+ vec4 c = texture(image, v_uv);
15
+ out_color = vec4(c.rgb * v_color.rgb, c.a * v_color.a);
16
+ }
@@ -0,0 +1,30 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — glyph and image quads, Vulkan/SPIR-V path.
4
+ *
5
+ * The vertex stage is shared by both: the fragment stage differs only in how it
6
+ * uses the sampled texture (`fs_text` reads the atlas' red channel, `fs_image`
7
+ * modulates an RGBA texture). Attributes:
8
+ * 0 -> position (pixels, viewport-relative)
9
+ * 1 -> uv (atlas coordinates in [0,1])
10
+ * 2 -> colour
11
+ */
12
+
13
+ layout(location = 0) in vec2 in_position;
14
+ layout(location = 1) in vec2 in_uv;
15
+ layout(location = 2) in vec4 in_color;
16
+
17
+ layout(location = 0) out vec2 v_uv;
18
+ layout(location = 1) out vec4 v_color;
19
+
20
+ layout(set = 1, binding = 0) uniform Viewport {
21
+ vec2 size;
22
+ } viewport;
23
+
24
+ void main() {
25
+ vec2 ndc = vec2(in_position.x / viewport.size.x * 2.0 - 1.0,
26
+ 1.0 - in_position.y / viewport.size.y * 2.0);
27
+ gl_Position = vec4(ndc, 0.0, 1.0);
28
+ v_uv = in_uv;
29
+ v_color = in_color;
30
+ }
@@ -0,0 +1,16 @@
1
+ #version 450
2
+
3
+ /* xbintsc GUI engine — glyph quads (Vulkan/SPIR-V): sample the single-channel
4
+ * atlas and modulate alpha by its coverage. */
5
+
6
+ layout(location = 0) in vec2 v_uv;
7
+ layout(location = 1) in vec4 v_color;
8
+
9
+ layout(location = 0) out vec4 out_color;
10
+
11
+ layout(set = 2, binding = 0) uniform sampler2D atlas;
12
+
13
+ void main() {
14
+ float coverage = texture(atlas, v_uv).r;
15
+ out_color = vec4(v_color.rgb, v_color.a * coverage);
16
+ }
@@ -434,6 +434,7 @@ static void define_getter(xt_value proto, const char *name, void *fn) {
434
434
  }
435
435
 
436
436
  xt_value xt_gui_window_proto(void) {
437
+ xt_gc_add_root(&g_window_proto);
437
438
  if (XT_IS_OBJECT(g_window_proto)) return g_window_proto;
438
439
  xt_value proto = xt_object_new();
439
440
  define_method(proto, "setTitle", (void *)win_set_title);
@@ -2,6 +2,7 @@
2
2
 
3
3
  static xt_value xt_buffer_proto(void) {
4
4
  static xt_value proto = 0;
5
+ xt_gc_add_root(&proto);
5
6
  if (proto) return proto;
6
7
  proto = xt_object_new();
7
8
  xt_node_define_method(proto, "toString", (void *)xt_buffer_method_to_string);
@@ -316,6 +316,7 @@ xt_value xt_dgram_static(xt_value name, int32_t argc, xt_value *argv) {
316
316
 
317
317
  static xt_value xt_dgram_socket_proto(void) {
318
318
  static xt_value proto = 0;
319
+ xt_gc_add_root(&proto);
319
320
  if (proto) return proto;
320
321
  proto = xt_object_new();
321
322
  xt_node_install_emitter(proto);
@@ -267,6 +267,7 @@ xt_value xt_events_static(xt_value name, int32_t argc, xt_value *argv) {
267
267
 
268
268
  static xt_value xt_events_proto(void) {
269
269
  static xt_value proto = 0;
270
+ xt_gc_add_root(&proto);
270
271
  if (proto) return proto;
271
272
  proto = xt_object_new();
272
273
  xt_node_install_emitter(proto);
@@ -59,27 +59,18 @@ xt_value xt_node_dirent_result(const char *dir, const char *name) {
59
59
  return object;
60
60
  }
61
61
 
62
- typedef struct {
63
- xt_value *items;
64
- size_t count;
65
- size_t capacity;
66
- } xt_fs_list;
67
-
68
- static void xt_fs_list_push(xt_fs_list *list, xt_value value) {
69
- if (list->count == list->capacity) {
70
- list->capacity = list->capacity ? list->capacity * 2 : 16;
71
- xt_value *grown = (xt_value *)realloc(list->items, sizeof(xt_value) * list->capacity);
72
- if (!grown) return;
73
- list->items = grown;
74
- }
75
- list->items[list->count++] = value;
76
- }
62
+ /* Entries accumulate in an `xt_array`, not a raw `xt_value` buffer: the
63
+ * collector cannot see a plain malloc'd buffer, so a `Dirent` created earlier
64
+ * in the walk would be swept as soon as a later entry allocates (which is
65
+ * exactly the GC-stress failure "object has no callable method
66
+ * 'isDirectory'"). The array lives on the caller's stack, so the conservative
67
+ * stack scan keeps it, and every entry it holds, reachable. */
77
68
 
78
69
  /* Depth-first walk used by `readdirSync(path, { recursive: true })`. `prefix`
79
70
  is the directory path relative to `base` (empty for the root, otherwise
80
71
  ending in `/`). */
81
72
  static void xt_fs_readdir_into(const char *base, const char *prefix, int withFileTypes, int recursive,
82
- xt_fs_list *list) {
73
+ xt_value list) {
83
74
  size_t baseLength = strlen(base);
84
75
  size_t prefixLength = strlen(prefix);
85
76
  size_t dirSize = baseLength + prefixLength + 2;
@@ -104,7 +95,7 @@ static void xt_fs_readdir_into(const char *base, const char *prefix, int withFil
104
95
  char *relative = (char *)malloc(relSize);
105
96
  if (!relative) continue;
106
97
  snprintf(relative, relSize, "%s%s", prefix, name);
107
- xt_fs_list_push(list, withFileTypes ? xt_node_dirent_result(dirPath, name) : xt_string_from_cstr(relative));
98
+ xt_array_push(list, withFileTypes ? xt_node_dirent_result(dirPath, name) : xt_string_from_cstr(relative));
108
99
 
109
100
  if (recursive) {
110
101
  size_t fullSize = dirLength + nameLength + 2;
@@ -141,14 +132,8 @@ xt_value xt_node_read_dir(int32_t argc, xt_value *argv) {
141
132
  int withFileTypes = XT_IS_OBJECT(options) && xt_truthy(xt_object_get_cstr(options, "withFileTypes"));
142
133
  int recursive = XT_IS_OBJECT(options) && xt_truthy(xt_object_get_cstr(options, "recursive"));
143
134
 
144
- xt_fs_list list;
145
- list.items = NULL;
146
- list.count = 0;
147
- list.capacity = 0;
148
- xt_fs_readdir_into(path, "", withFileTypes, recursive, &list);
149
-
150
- xt_value result = xt_array_new((int32_t)list.count, list.items);
151
- free(list.items);
135
+ xt_value result = xt_array_new(0, NULL);
136
+ xt_fs_readdir_into(path, "", withFileTypes, recursive, result);
152
137
  return result;
153
138
  }
154
139
 
@@ -10,21 +10,9 @@
10
10
  #include "../node_common.h"
11
11
  #include "fs_common.h"
12
12
 
13
- typedef struct {
14
- xt_value *items;
15
- size_t count;
16
- size_t capacity;
17
- } xt_glob_list;
18
-
19
- static void xt_glob_push(xt_glob_list *list, xt_value value) {
20
- if (list->count == list->capacity) {
21
- list->capacity = list->capacity ? list->capacity * 2 : 16;
22
- xt_value *grown = (xt_value *)realloc(list->items, sizeof(xt_value) * list->capacity);
23
- if (!grown) return;
24
- list->items = grown;
25
- }
26
- list->items[list->count++] = value;
27
- }
13
+ /* Matches accumulate in an `xt_array` rather than a raw buffer so the garbage
14
+ * collector can see the `Dirent`/string entries created earlier in the walk;
15
+ * see the same note in `fs_ops.c`. */
28
16
 
29
17
  static char *xt_glob_join(const char *dir, const char *name) {
30
18
  size_t dirLength = strlen(dir);
@@ -96,11 +84,11 @@ static int xt_glob_is_dir(const char *path, int follow) {
96
84
  }
97
85
 
98
86
  static void xt_glob_walk(const char *dirPath, const char *pattern, const char *resultPrefix, int withFileTypes,
99
- xt_glob_list *list);
87
+ xt_value list);
100
88
 
101
89
  /* Recurse one directory level for the `**` segment. */
102
90
  static void xt_glob_descend(const char *dirPath, const char *pattern, const char *resultPrefix, int withFileTypes,
103
- xt_glob_list *list) {
91
+ xt_value list) {
104
92
  xt_fs_dir dir;
105
93
  if (!xt_fs_dir_open(&dir, dirPath)) return;
106
94
  while (xt_fs_dir_next(&dir)) {
@@ -123,7 +111,7 @@ static void xt_glob_descend(const char *dirPath, const char *pattern, const char
123
111
  }
124
112
 
125
113
  static void xt_glob_walk(const char *dirPath, const char *pattern, const char *resultPrefix, int withFileTypes,
126
- xt_glob_list *list) {
114
+ xt_value list) {
127
115
  if (pattern[0] == '\0') {
128
116
  if (resultPrefix[0] != '\0') {
129
117
  size_t length = strlen(resultPrefix);
@@ -131,7 +119,7 @@ static void xt_glob_walk(const char *dirPath, const char *pattern, const char *r
131
119
  if (!trimmed) return;
132
120
  memcpy(trimmed, resultPrefix, length + 1);
133
121
  while (length > 1 && trimmed[length - 1] == '/') trimmed[--length] = '\0';
134
- xt_glob_push(list, xt_string_from_cstr(trimmed));
122
+ xt_array_push(list, xt_string_from_cstr(trimmed));
135
123
  free(trimmed);
136
124
  }
137
125
  return;
@@ -165,13 +153,13 @@ static void xt_glob_walk(const char *dirPath, const char *pattern, const char *r
165
153
  char *child = xt_glob_join(dirPath, name);
166
154
  if (!child) continue;
167
155
  if (rest[0] == '\0') {
168
- if (withFileTypes) xt_glob_push(list, xt_node_dirent_result(dirPath, name));
156
+ if (withFileTypes) xt_array_push(list, xt_node_dirent_result(dirPath, name));
169
157
  else {
170
158
  size_t size = strlen(resultPrefix) + strlen(name) + 1;
171
159
  char *result = (char *)malloc(size);
172
160
  if (result) {
173
161
  snprintf(result, size, "%s%s", resultPrefix, name);
174
- xt_glob_push(list, xt_string_from_cstr(result));
162
+ xt_array_push(list, xt_string_from_cstr(result));
175
163
  free(result);
176
164
  }
177
165
  }
@@ -189,7 +177,7 @@ static void xt_glob_walk(const char *dirPath, const char *pattern, const char *r
189
177
  xt_fs_dir_close(&dir);
190
178
  }
191
179
 
192
- static void xt_glob_run(const char *pattern, const char *cwd, int withFileTypes, xt_glob_list *list) {
180
+ static void xt_glob_run(const char *pattern, const char *cwd, int withFileTypes, xt_value list) {
193
181
  if (!pattern) return;
194
182
  const char *base = cwd && cwd[0] ? cwd : ".";
195
183
  const char *prefix = "";
@@ -209,24 +197,19 @@ xt_value xt_node_glob(int32_t argc, xt_value *argv) {
209
197
  xt_value cwdValue = XT_IS_OBJECT(options) ? xt_object_get_cstr(options, "cwd") : XT_UNDEFINED;
210
198
  if (XT_IS_STRING(cwdValue)) cwd = xt_string_data(cwdValue);
211
199
 
212
- xt_glob_list list;
213
- list.items = NULL;
214
- list.count = 0;
215
- list.capacity = 0;
200
+ xt_value result = xt_array_new(0, NULL);
216
201
 
217
202
  xt_value patternValue = argv[0];
218
203
  if (XT_IS_ARRAY(patternValue)) {
219
204
  int32_t count = (int32_t)xt_to_number(xt_array_length(patternValue));
220
205
  for (int32_t i = 0; i < count; i++) {
221
206
  xt_value item = xt_array_get(patternValue, xt_number((double)i));
222
- if (XT_IS_STRING(item)) xt_glob_run(xt_string_data(item), cwd, withFileTypes, &list);
207
+ if (XT_IS_STRING(item)) xt_glob_run(xt_string_data(item), cwd, withFileTypes, result);
223
208
  }
224
209
  } else {
225
210
  const char *pattern = xt_node_cstr(patternValue);
226
- xt_glob_run(pattern, cwd, withFileTypes, &list);
211
+ xt_glob_run(pattern, cwd, withFileTypes, result);
227
212
  }
228
213
 
229
- xt_value result = xt_array_new((int32_t)list.count, list.items);
230
- free(list.items);
231
214
  return result;
232
215
  }
@@ -242,6 +242,7 @@ XT_FS_PROMISES_FACADE(xt_node_p_open, _)
242
242
 
243
243
  xt_value xt_fs_promises(void) {
244
244
  static xt_value cached = 0;
245
+ xt_gc_add_root(&cached);
245
246
  if (XT_IS_OBJECT(cached)) return cached;
246
247
  xt_value object = xt_object_new();
247
248
  #define XT_FS_PROMISE_METHOD(jsName, name) \
@@ -2,6 +2,7 @@
2
2
 
3
3
  static xt_value xt_http_server_proto(void) {
4
4
  static xt_value proto = 0;
5
+ xt_gc_add_root(&proto);
5
6
  if (proto) return proto;
6
7
  proto = xt_object_new();
7
8
  xt_node_install_emitter(proto);
@@ -13,6 +14,7 @@ static xt_value xt_http_server_proto(void) {
13
14
 
14
15
  static xt_value xt_http_response_proto(void) {
15
16
  static xt_value proto = 0;
17
+ xt_gc_add_root(&proto);
16
18
  if (proto) return proto;
17
19
  proto = xt_object_new();
18
20
  xt_node_install_emitter(proto);
@@ -28,6 +30,7 @@ static xt_value xt_http_response_proto(void) {
28
30
 
29
31
  static xt_value xt_http_request_proto(void) {
30
32
  static xt_value proto = 0;
33
+ xt_gc_add_root(&proto);
31
34
  if (proto) return proto;
32
35
  proto = xt_object_new();
33
36
  xt_node_install_emitter(proto);
@@ -37,6 +40,7 @@ static xt_value xt_http_request_proto(void) {
37
40
 
38
41
  static xt_value xt_http_client_proto(void) {
39
42
  static xt_value proto = 0;
43
+ xt_gc_add_root(&proto);
40
44
  if (proto) return proto;
41
45
  proto = xt_object_new();
42
46
  xt_node_install_emitter(proto);
@@ -49,6 +53,7 @@ static xt_value xt_http_client_proto(void) {
49
53
 
50
54
  static xt_value xt_http_client_response_proto(void) {
51
55
  static xt_value proto = 0;
56
+ xt_gc_add_root(&proto);
52
57
  if (proto) return proto;
53
58
  proto = xt_object_new();
54
59
  xt_node_install_emitter(proto);
@@ -2,6 +2,7 @@
2
2
 
3
3
  static xt_value xt_net_socket_proto(void) {
4
4
  static xt_value proto = 0;
5
+ xt_gc_add_root(&proto);
5
6
  if (proto) return proto;
6
7
  proto = xt_object_new();
7
8
  xt_node_install_emitter(proto);
@@ -22,6 +23,7 @@ static xt_value xt_net_socket_proto(void) {
22
23
 
23
24
  static xt_value xt_net_server_proto(void) {
24
25
  static xt_value proto = 0;
26
+ xt_gc_add_root(&proto);
25
27
  if (proto) return proto;
26
28
  proto = xt_object_new();
27
29
  xt_node_install_emitter(proto);
@@ -70,16 +70,18 @@ static xt_value xt_process_env(void) {
70
70
 
71
71
  static xt_value xt_process_argv(void) {
72
72
  int32_t count = xt_program_argc;
73
- if (count <= 0 || !xt_program_argv) return xt_array_new(0, NULL);
73
+ /* Fill an `xt_array` element by element rather than a raw `xt_value` buffer:
74
+ the collector cannot see a plain malloc'd buffer, so strings created here
75
+ would be swept as soon as a later element allocates (demonstrated by
76
+ `process.argv` returning recycled strings under GC stress). The array is a
77
+ stack local, so the conservative scan keeps it and its elements alive. */
78
+ xt_value result = xt_array_new(0, NULL);
79
+ if (count <= 0 || !xt_program_argv) return result;
74
80
  /* Node semantics: `[execPath, scriptPath, ...args]`. A standalone binary is
75
81
  both the executable and the script, so the executable path is repeated;
76
82
  user arguments therefore start at index 2, matching `node script.js a b`. */
77
- xt_value *items = (xt_value *)malloc(sizeof(xt_value) * (size_t)(count + 1));
78
- if (!items) return xt_array_new(0, NULL);
79
- items[0] = xt_string_from_cstr(xt_program_argv[0]);
80
- for (int32_t i = 0; i < count; i++) items[i + 1] = xt_string_from_cstr(xt_program_argv[i]);
81
- xt_value result = xt_array_new(count + 1, items);
82
- free(items);
83
+ xt_array_push(result, xt_string_from_cstr(xt_program_argv[0]));
84
+ for (int32_t i = 0; i < count; i++) xt_array_push(result, xt_string_from_cstr(xt_program_argv[i]));
83
85
  return result;
84
86
  }
85
87
 
@@ -363,6 +363,7 @@ xt_value xt_stream_static(xt_value name, int32_t argc, xt_value *argv) {
363
363
 
364
364
  static xt_value xt_stream_proto(void) {
365
365
  static xt_value proto = 0;
366
+ xt_gc_add_root(&proto);
366
367
  if (proto) return proto;
367
368
  proto = xt_object_new();
368
369
  xt_node_install_emitter(proto);
@@ -363,13 +363,13 @@ static xt_value util_promisify_executor(xt_value thisValue, xt_value env, int32_
363
363
  xt_value resolve = xt_arg(argc, argv, 0);
364
364
  xt_value reject = xt_arg(argc, argv, 1);
365
365
  int32_t count = (int32_t)xt_to_number(xt_array_length(args));
366
- xt_value *callArgs = (xt_value *)malloc(sizeof(xt_value) * (size_t)(count + 1));
367
- if (!callArgs) return xt_undefined();
368
- for (int32_t i = 0; i < count; i++) callArgs[i] = xt_array_get(args, xt_number((double)i));
366
+ /* An `xt_array` keeps the arguments, and the freshly created callback
367
+ closure, reachable if the call below triggers a collection. */
368
+ xt_value argArray = xt_array_new(0, NULL);
369
+ for (int32_t i = 0; i < count; i++) xt_array_push(argArray, xt_array_get(args, xt_number((double)i)));
369
370
  xt_value callbackEnv[2] = {resolve, reject};
370
- callArgs[count] = xt_closure_new((void *)util_promisify_callback, 2, callbackEnv);
371
- if (XT_IS_FUNCTION(fn)) xt_closure_call(fn, count + 1, callArgs);
372
- free(callArgs);
371
+ xt_array_push(argArray, xt_closure_new((void *)util_promisify_callback, 2, callbackEnv));
372
+ if (XT_IS_FUNCTION(fn)) xt_closure_call(fn, count + 1, xt_as_array(argArray)->items);
373
373
  return xt_undefined();
374
374
  }
375
375
 
package/runtime/rt.h CHANGED
@@ -362,6 +362,16 @@ void xt_println(xt_value v);
362
362
  size_t xt_heap_bytes(void);
363
363
  size_t xt_heap_allocations(void);
364
364
 
365
+ /* -- garbage collector ---------------------------------------------------- */
366
+ /**
367
+ * Register `slot` as a GC root: a static prototype cache or any value cell
368
+ * that lives outside the heap graph. The slot may be registered before it
369
+ * holds a value; it is marked on every collection and registration is
370
+ * idempotent. `xt_gc_add_root_range` registers `count` consecutive cells.
371
+ */
372
+ void xt_gc_add_root(xt_value *slot);
373
+ void xt_gc_add_root_range(xt_value *base, size_t count);
374
+
365
375
  #ifdef __cplusplus
366
376
  }
367
377
  #endif