xbintsc 0.3.81 → 0.5.6

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 (188) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +16 -5
  3. package/README.zh-CN.md +14 -6
  4. package/dist/src/ast/common.d.ts +23 -0
  5. package/dist/src/cli/main.js +68 -24
  6. package/dist/src/cli/main.js.map +1 -1
  7. package/dist/src/codegen/generator/calls/access.js +6 -2
  8. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  9. package/dist/src/codegen/generator/calls/closures.js +4 -0
  10. package/dist/src/codegen/generator/calls/closures.js.map +1 -1
  11. package/dist/src/codegen/generator/calls/invocation.js +9 -4
  12. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  13. package/dist/src/codegen/generator/context.d.ts +50 -0
  14. package/dist/src/codegen/generator/context.js +124 -13
  15. package/dist/src/codegen/generator/context.js.map +1 -1
  16. package/dist/src/codegen/generator/debug.d.ts +33 -0
  17. package/dist/src/codegen/generator/debug.js +97 -0
  18. package/dist/src/codegen/generator/debug.js.map +1 -0
  19. package/dist/src/codegen/generator/expressions/assignment.js +9 -5
  20. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  21. package/dist/src/codegen/generator/expressions/operators.js +35 -9
  22. package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
  23. package/dist/src/codegen/generator/expressions/primary.js +11 -3
  24. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  25. package/dist/src/codegen/generator/generator.d.ts +3 -1
  26. package/dist/src/codegen/generator/generator.js +3 -1
  27. package/dist/src/codegen/generator/generator.js.map +1 -1
  28. package/dist/src/codegen/generator/module.d.ts +1 -1
  29. package/dist/src/codegen/generator/module.js +82 -12
  30. package/dist/src/codegen/generator/module.js.map +1 -1
  31. package/dist/src/codegen/generator/numeric.d.ts +26 -0
  32. package/dist/src/codegen/generator/numeric.js +127 -0
  33. package/dist/src/codegen/generator/numeric.js.map +1 -0
  34. package/dist/src/codegen/generator/state.d.ts +17 -0
  35. package/dist/src/codegen/generator/state.js +9 -0
  36. package/dist/src/codegen/generator/state.js.map +1 -1
  37. package/dist/src/codegen/generator/statements/control-flow.js +2 -4
  38. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  39. package/dist/src/codegen/generator/statements/dispatch.js +3 -0
  40. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  41. package/dist/src/codegen/generator/statements/variables.js +8 -2
  42. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  43. package/dist/src/codegen/generator/tables.d.ts +16 -0
  44. package/dist/src/codegen/generator/tables.js +99 -2
  45. package/dist/src/codegen/generator/tables.js.map +1 -1
  46. package/dist/src/driver/bundler/merge.js +41 -5
  47. package/dist/src/driver/bundler/merge.js.map +1 -1
  48. package/dist/src/driver/compiler.d.ts +23 -4
  49. package/dist/src/driver/compiler.js +46 -57
  50. package/dist/src/driver/compiler.js.map +1 -1
  51. package/dist/src/driver/fingerprint.d.ts +27 -0
  52. package/dist/src/driver/fingerprint.js +88 -0
  53. package/dist/src/driver/fingerprint.js.map +1 -0
  54. package/dist/src/driver/toolchain-provider.d.ts +9 -2
  55. package/dist/src/driver/toolchain-provider.js +15 -15
  56. package/dist/src/driver/toolchain-provider.js.map +1 -1
  57. package/dist/src/driver/toolchain.d.ts +58 -1
  58. package/dist/src/driver/toolchain.js +169 -22
  59. package/dist/src/driver/toolchain.js.map +1 -1
  60. package/dist/src/parser/expressions.js +20 -1
  61. package/dist/src/parser/expressions.js.map +1 -1
  62. package/dist/tests/cli/main.test.js +41 -1
  63. package/dist/tests/cli/main.test.js.map +1 -1
  64. package/dist/tests/codegen/closures.test.js +8 -0
  65. package/dist/tests/codegen/closures.test.js.map +1 -1
  66. package/dist/tests/codegen/expressions.test.js +14 -3
  67. package/dist/tests/codegen/expressions.test.js.map +1 -1
  68. package/dist/tests/codegen/llvm.test.js +23 -0
  69. package/dist/tests/codegen/llvm.test.js.map +1 -1
  70. package/dist/tests/driver/toolchain-provider.test.js +49 -2
  71. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  72. package/dist/tests/driver/toolchain.test.js +62 -3
  73. package/dist/tests/driver/toolchain.test.js.map +1 -1
  74. package/dist/tests/e2e/async-await.test.d.ts +10 -0
  75. package/dist/tests/e2e/async-await.test.js +247 -0
  76. package/dist/tests/e2e/async-await.test.js.map +1 -0
  77. package/dist/tests/e2e/child-process.test.d.ts +10 -0
  78. package/dist/tests/e2e/child-process.test.js +59 -0
  79. package/dist/tests/e2e/child-process.test.js.map +1 -0
  80. package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
  81. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  82. package/dist/tests/e2e/error-stack.test.d.ts +9 -0
  83. package/dist/tests/e2e/error-stack.test.js +94 -0
  84. package/dist/tests/e2e/error-stack.test.js.map +1 -0
  85. package/dist/tests/e2e/runtime-coverage-globals.test.js +46 -0
  86. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
  87. package/dist/tests/e2e/string-concat.test.d.ts +9 -0
  88. package/dist/tests/e2e/string-concat.test.js +102 -0
  89. package/dist/tests/e2e/string-concat.test.js.map +1 -0
  90. package/dist/tests/e2e/string-utf16.test.d.ts +6 -0
  91. package/dist/tests/e2e/string-utf16.test.js +133 -0
  92. package/dist/tests/e2e/string-utf16.test.js.map +1 -0
  93. package/dist/tests/helpers.d.ts +9 -0
  94. package/dist/tests/helpers.js +26 -3
  95. package/dist/tests/helpers.js.map +1 -1
  96. package/dist/tests/parser/expressions.test.js +7 -0
  97. package/dist/tests/parser/expressions.test.js.map +1 -1
  98. package/dist/tests/version.test.d.ts +1 -0
  99. package/dist/tests/version.test.js +24 -0
  100. package/dist/tests/version.test.js.map +1 -0
  101. package/doc/DESIGN.md +20 -12
  102. package/doc/ai/cli.md +2 -0
  103. package/doc/ai/contributing.md +16 -8
  104. package/doc/ai/extensions.md +3 -2
  105. package/doc/ai/language-support.md +15 -12
  106. package/doc/ai/troubleshooting.md +43 -5
  107. package/doc/ai/zh-CN/cli.md +2 -0
  108. package/doc/ai/zh-CN/contributing.md +11 -7
  109. package/doc/ai/zh-CN/extensions.md +2 -1
  110. package/doc/ai/zh-CN/language-support.md +15 -12
  111. package/doc/ai/zh-CN/troubleshooting.md +38 -4
  112. package/doc/implemented.md +16 -13
  113. package/doc/requirements.md +13 -4
  114. package/doc/unimplemented.md +20 -18
  115. package/doc/zh-CN/DESIGN.md +15 -9
  116. package/doc/zh-CN/implemented.md +15 -12
  117. package/doc/zh-CN/requirements.md +12 -3
  118. package/doc/zh-CN/unimplemented.md +19 -17
  119. package/package.json +3 -1
  120. package/runtime/ext_node/buffer/parts/construction.inc +2 -2
  121. package/runtime/ext_node/buffer/parts/methods.inc +1 -1
  122. package/runtime/ext_node/buffer/parts/search.inc +1 -1
  123. package/runtime/ext_node/child_process/child_process.c +25 -0
  124. package/runtime/ext_node/fs/fd_ops.c +1 -1
  125. package/runtime/rt.h +49 -1
  126. package/runtime/rt_internal.h +136 -4
  127. package/runtime/xt_alloc.c +20 -2
  128. package/runtime/xt_async.c +197 -0
  129. package/runtime/xt_bigint/conversions.inc +1 -1
  130. package/runtime/xt_builtins.c +169 -43
  131. package/runtime/xt_constructors.c +389 -0
  132. package/runtime/xt_containers/access.inc +61 -24
  133. package/runtime/xt_containers/arrays.inc +26 -2
  134. package/runtime/xt_containers/objects.inc +206 -8
  135. package/runtime/xt_coroutine.c +212 -0
  136. package/runtime/xt_generator.c +31 -162
  137. package/runtime/xt_io/exception.inc +171 -2
  138. package/runtime/xt_io/inspect.inc +48 -13
  139. package/runtime/xt_io.c +5 -0
  140. package/runtime/xt_promise.c +62 -17
  141. package/runtime/xt_stdlib/arrays.inc +2 -2
  142. package/runtime/xt_stdlib/dispatch.inc +20 -4
  143. package/runtime/xt_stdlib/strings.inc +98 -29
  144. package/runtime/xt_stdlib2/array-statics.inc +3 -2
  145. package/runtime/xt_stdlib2/arrays.inc +1 -1
  146. package/runtime/xt_stdlib2/error.inc +33 -6
  147. package/runtime/xt_stdlib2/json.inc +46 -16
  148. package/runtime/xt_stdlib2/number-string-statics.inc +24 -13
  149. package/runtime/xt_stdlib2/objects.inc +1 -1
  150. package/runtime/xt_stdlib2/regexp-match.inc +50 -35
  151. package/runtime/xt_stdlib2/set.inc +6 -0
  152. package/runtime/xt_stdlib2/strings.inc +81 -42
  153. package/runtime/xt_symbol.c +1 -1
  154. package/runtime/xt_typed_array/construction.inc +43 -2
  155. package/runtime/xt_typed_array.c +4 -0
  156. package/runtime/xt_values/comparison.inc +2 -2
  157. package/runtime/xt_values/constructors.inc +18 -5
  158. package/runtime/xt_values/conversions.inc +4 -3
  159. package/runtime/xt_values/strings.inc +94 -4
  160. package/runtime/xt_values/utf16.inc +340 -0
  161. package/runtime/xt_values.c +1 -0
  162. package/scripts/bench.ts +172 -0
  163. package/scripts/build-runtime.ts +2 -1
  164. package/scripts/check-version.ts +53 -0
  165. package/src/ast/common.ts +23 -0
  166. package/src/cli/main.ts +68 -26
  167. package/src/codegen/generator/calls/access.ts +7 -2
  168. package/src/codegen/generator/calls/closures.ts +3 -0
  169. package/src/codegen/generator/calls/invocation.ts +9 -3
  170. package/src/codegen/generator/context.ts +129 -18
  171. package/src/codegen/generator/debug.ts +132 -0
  172. package/src/codegen/generator/expressions/assignment.ts +9 -5
  173. package/src/codegen/generator/expressions/operators.ts +47 -8
  174. package/src/codegen/generator/expressions/primary.ts +11 -3
  175. package/src/codegen/generator/generator.ts +18 -2
  176. package/src/codegen/generator/module.ts +83 -13
  177. package/src/codegen/generator/numeric.ts +158 -0
  178. package/src/codegen/generator/state.ts +26 -0
  179. package/src/codegen/generator/statements/control-flow.ts +2 -3
  180. package/src/codegen/generator/statements/dispatch.ts +3 -0
  181. package/src/codegen/generator/statements/variables.ts +8 -2
  182. package/src/codegen/generator/tables.ts +110 -2
  183. package/src/driver/bundler/merge.ts +39 -5
  184. package/src/driver/compiler.ts +65 -55
  185. package/src/driver/fingerprint.ts +85 -0
  186. package/src/driver/toolchain-provider.ts +17 -16
  187. package/src/driver/toolchain.ts +210 -21
  188. package/src/parser/expressions.ts +20 -1
@@ -8,28 +8,17 @@
8
8
  * `finally` and nested loops work across suspension points without a state
9
9
  * machine transform.
10
10
  *
11
- * POSIX uses `ucontext` (`swapcontext`); Windows uses fibers. The generator's
12
- * exception stack is swapped alongside it so a `throw` inside the body still
13
- * targets the body's own `try` frames.
11
+ * The private stack and the context switch come from `xt_coroutine.c`, which
12
+ * `async` functions use as well. The generator's exception stack is swapped
13
+ * alongside it so a `throw` inside the body still targets the body's own
14
+ * `try` frames.
14
15
  */
15
16
 
16
- #define _XOPEN_SOURCE 700
17
-
18
17
  #include "rt_internal.h"
19
18
 
20
19
  #include <stdlib.h>
21
20
  #include <string.h>
22
21
 
23
- #if defined(__APPLE__)
24
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
25
- #endif
26
-
27
- #ifdef _WIN32
28
- #include <windows.h>
29
- #else
30
- #include <ucontext.h>
31
- #endif
32
-
33
22
  typedef struct xt_generator {
34
23
  xt_header header;
35
24
  xt_value function;
@@ -44,35 +33,13 @@ typedef struct xt_generator {
44
33
  int started;
45
34
  int done;
46
35
  int threw;
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;
53
- void *saved_try_top;
54
- #ifdef _WIN32
55
- void *fiber;
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;
62
- #else
63
- ucontext_t ctx;
64
- ucontext_t caller;
65
- #endif
36
+ /* Private stack, context switch and GC region bookkeeping live in
37
+ * xt_coroutine.c; `NULL` once the body has finished. */
38
+ xt_coroutine *coroutine;
66
39
  } xt_generator;
67
40
 
68
41
  /* The generator whose coroutine is running, if any. */
69
42
  static xt_generator *g_active_generator = NULL;
70
- /* Filled in just before a coroutine is first entered (POSIX has no portable
71
- * way to pass a pointer to `makecontext`). */
72
- static xt_generator *g_boot_generator = NULL;
73
- #ifdef _WIN32
74
- static void *g_main_fiber = NULL;
75
- #endif
76
43
 
77
44
  int xt_is_generator(xt_value value) {
78
45
  if (!XT_IS_OBJECT(value)) return 0;
@@ -91,38 +58,9 @@ static xt_value xt_generator_result_object(xt_value value, int done) {
91
58
  return result;
92
59
  }
93
60
 
94
- static void xt_generator_suspend(xt_generator *gen) {
95
- gen->saved_try_top = xt_try_mark();
96
- #ifdef _WIN32
97
- SwitchToFiber(gen->caller_fiber);
98
- #else
99
- swapcontext(&gen->ctx, &gen->caller);
100
- #endif
101
- }
102
-
103
61
  /* Runs on the generator's private stack. */
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
- }
62
+ static void xt_generator_run(void *arg) {
63
+ xt_generator *gen = (xt_generator *)arg;
126
64
  void *boundary = xt_try_enter();
127
65
  if (xt_try_setjmp(boundary) == 0) {
128
66
  xt_function *function = (xt_function *)XT_GET_PTR(gen->function);
@@ -133,90 +71,23 @@ static void xt_generator_run(xt_generator *gen) {
133
71
  gen->threw = 1;
134
72
  }
135
73
  gen->done = 1;
136
- xt_generator_suspend(gen);
137
- }
138
-
139
- #ifndef _WIN32
140
- static void xt_generator_entry(void) {
141
- xt_generator *gen = g_boot_generator;
142
- g_boot_generator = NULL;
143
- xt_generator_run(gen);
144
- }
145
- #else
146
- static void CALLBACK xt_generator_entry(void *param) {
147
- xt_generator_run((xt_generator *)param);
148
- }
149
- #endif
150
-
151
- static void xt_generator_boot(xt_generator *gen) {
152
- #ifdef _WIN32
153
- if (!g_main_fiber) g_main_fiber = ConvertThreadToFiber(NULL);
154
- gen->fiber = CreateFiber(1 << 20, xt_generator_entry, gen);
155
- if (!gen->fiber) abort();
156
- #else
157
- size_t size = 1 << 20;
158
- gen->stack = (char *)malloc(size);
159
- if (!gen->stack) abort();
160
- gen->stack_size = size;
161
- if (getcontext(&gen->ctx) != 0) abort();
162
- gen->ctx.uc_stack.ss_sp = gen->stack;
163
- gen->ctx.uc_stack.ss_size = size;
164
- gen->ctx.uc_link = NULL;
165
- g_boot_generator = gen;
166
- makecontext(&gen->ctx, xt_generator_entry, 0);
167
- #endif
168
- gen->booted = 1;
74
+ xt_coroutine_leave(gen->coroutine);
169
75
  }
170
76
 
171
77
  /* Release the private stack once the coroutine has finished. The generator
172
78
  * object itself stays valid; later `.next()` calls just report `done`. */
173
79
  static void xt_generator_dispose(xt_generator *gen) {
174
- #ifdef _WIN32
175
- if (gen->fiber) {
176
- DeleteFiber(gen->fiber);
177
- gen->fiber = NULL;
178
- }
179
- gen->caller_fiber = NULL;
180
- /* `DeleteFiber` releases the stack: never scan it again. */
181
- gen->stack = NULL;
182
- gen->stack_size = 0;
183
- #else
184
- if (gen->stack) {
185
- free(gen->stack);
186
- gen->stack = NULL;
187
- }
188
- #endif
80
+ xt_coroutine_release(gen->coroutine);
81
+ gen->coroutine = NULL;
189
82
  }
190
83
 
84
+ static void xt_generator_boot(xt_generator *gen);
85
+
191
86
  static void xt_generator_resume(xt_generator *gen) {
192
- if (!gen->booted) xt_generator_boot(gen);
87
+ if (!gen->coroutine) xt_generator_boot(gen);
193
88
  xt_generator *previous = g_active_generator;
194
89
  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
- }
204
- void *caller_try = xt_try_mark();
205
- xt_try_restore(gen->saved_try_top);
206
- #ifdef _WIN32
207
- _setjmp(gen->caller_regs);
208
- gen->has_caller_regs = 1;
209
- gen->caller_fiber = GetCurrentFiber();
210
- SwitchToFiber(gen->fiber);
211
- #else
212
- swapcontext(&gen->caller, &gen->ctx);
213
- #endif
214
- gen->saved_try_top = xt_try_mark();
215
- xt_try_restore(caller_try);
216
- if (gen->stack_pushed) {
217
- xt_gc_pop_stack();
218
- gen->stack_pushed = 0;
219
- }
90
+ xt_coroutine_enter(gen->coroutine);
220
91
  if (gen->done) xt_generator_dispose(gen);
221
92
  g_active_generator = previous;
222
93
  }
@@ -242,23 +113,18 @@ void xt_generator_gc_trace(xt_header *header) {
242
113
  if (gen->args && gen->argc > 0) {
243
114
  xt_gc_scan_region(gen->args, sizeof(xt_value) * (size_t)gen->argc);
244
115
  }
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
116
+ xt_coroutine_gc_trace(gen->coroutine);
252
117
  }
253
118
 
254
119
  void xt_generator_gc_release(xt_header *header) {
255
120
  xt_generator *gen = (xt_generator *)header;
256
121
  free(gen->args);
257
- #ifdef _WIN32
258
- if (gen->fiber) DeleteFiber(gen->fiber);
259
- #else
260
- free(gen->stack);
261
- #endif
122
+ xt_coroutine_release(gen->coroutine);
123
+ gen->coroutine = NULL;
124
+ }
125
+
126
+ static void xt_generator_boot(xt_generator *gen) {
127
+ gen->coroutine = xt_coroutine_create(xt_generator_run, gen);
262
128
  }
263
129
 
264
130
  xt_value xt_generator_new(xt_value function, xt_value thisValue, int32_t argc, xt_value *argv) {
@@ -267,9 +133,7 @@ xt_value xt_generator_new(xt_value function, xt_value thisValue, int32_t argc, x
267
133
  gen->this_value = thisValue;
268
134
  gen->argc = argc;
269
135
  gen->args = NULL;
270
- gen->stack = NULL;
271
- gen->stack_size = 0;
272
- gen->stack_pushed = 0;
136
+ gen->coroutine = NULL;
273
137
  gen->result = XT_UNDEFINED;
274
138
  gen->sent = XT_UNDEFINED;
275
139
  gen->iter_value = XT_UNDEFINED;
@@ -339,7 +203,7 @@ xt_value xt_yield(xt_value value) {
339
203
  xt_generator *gen = g_active_generator;
340
204
  if (!gen || gen->done) return XT_UNDEFINED;
341
205
  gen->result = value;
342
- xt_generator_suspend(gen);
206
+ xt_coroutine_leave(gen->coroutine);
343
207
  if (gen->resume_kind == 1) {
344
208
  xt_throw(gen->sent);
345
209
  return XT_UNDEFINED;
@@ -348,7 +212,7 @@ xt_value xt_yield(xt_value value) {
348
212
  gen->done = 1;
349
213
  gen->threw = 0;
350
214
  gen->result = gen->sent;
351
- xt_generator_suspend(gen);
215
+ xt_coroutine_leave(gen->coroutine);
352
216
  return XT_UNDEFINED;
353
217
  }
354
218
  return gen->sent;
@@ -396,3 +260,8 @@ xt_value xt_generator_iter_value(xt_value generator) {
396
260
  xt_generator *gen = xt_as_generator(generator);
397
261
  return gen ? gen->iter_value : XT_UNDEFINED;
398
262
  }
263
+
264
+ xt_value xt_function_set_generator(xt_value value) {
265
+ if (XT_IS_FUNCTION(value)) ((xt_function *)XT_GET_PTR(value))->is_generator = 1;
266
+ return value;
267
+ }
@@ -14,6 +14,162 @@ static xt_try_frame *g_try_top = NULL;
14
14
  int32_t xt_program_argc = 0;
15
15
  char **xt_program_argv = NULL;
16
16
 
17
+ /* ------------------------------------------------------------------------- */
18
+ /* Shadow call stack */
19
+ /* ------------------------------------------------------------------------- */
20
+
21
+ /*
22
+ * Compiled functions push one frame on entry and pop it before returning, so
23
+ * `Error` can capture a JavaScript-level stack without any debug metadata.
24
+ * Generator bodies are not tracked (they switch stacks and can be suspended
25
+ * indefinitely); a normal function called from a generator is tracked normally.
26
+ *
27
+ * `name`/`file` point into the module's static string constants; the runtime
28
+ * never frees them. The depth is trimmed when a `try` frame is left, so frames
29
+ * from calls unwound by a throw cannot leak into a later stack.
30
+ */
31
+ #define XT_STACK_MAX_DEPTH 256
32
+
33
+ typedef struct {
34
+ const char *name;
35
+ const char *file;
36
+ int32_t line;
37
+ } xt_stack_entry;
38
+
39
+ static xt_stack_entry g_stack[XT_STACK_MAX_DEPTH];
40
+ static int32_t g_stack_depth = 0;
41
+
42
+ void xt_stack_push(const char *name, const char *file, int32_t line) {
43
+ if (g_stack_depth >= XT_STACK_MAX_DEPTH) return;
44
+ xt_stack_entry *entry = &g_stack[g_stack_depth++];
45
+ entry->name = name;
46
+ entry->file = file;
47
+ entry->line = line;
48
+ }
49
+
50
+ void xt_stack_pop(void) {
51
+ if (g_stack_depth > 0) g_stack_depth--;
52
+ }
53
+
54
+ int32_t xt_stack_depth(void) { return g_stack_depth; }
55
+
56
+ void xt_stack_set_line(int32_t line) {
57
+ /* Called just before an `Error` is constructed, so the innermost frame
58
+ * reports the construction line rather than the function's declaration. */
59
+ if (g_stack_depth > 0) g_stack[g_stack_depth - 1].line = line;
60
+ }
61
+
62
+ void xt_stack_trim(int32_t depth) {
63
+ if (depth < 0) depth = 0;
64
+ if (depth < g_stack_depth) g_stack_depth = depth;
65
+ }
66
+
67
+ static void xt_stack_append(char **buffer, size_t *length, size_t *capacity, const char *text) {
68
+ size_t count = strlen(text);
69
+ if (*length + count + 1 > *capacity) {
70
+ size_t next = *capacity ? *capacity : 256;
71
+ while (next < *length + count + 1) next *= 2;
72
+ char *grown = (char *)realloc(*buffer, next);
73
+ if (!grown) return;
74
+ *buffer = grown;
75
+ *capacity = next;
76
+ }
77
+ memcpy(*buffer + *length, text, count);
78
+ *length += count;
79
+ (*buffer)[*length] = 0;
80
+ }
81
+
82
+ xt_value xt_stack_capture(const char *name, const char *message) {
83
+ const char *header = name && name[0] ? name : "Error";
84
+ char *buffer = NULL;
85
+ size_t length = 0;
86
+ size_t capacity = 0;
87
+ xt_stack_append(&buffer, &length, &capacity, header);
88
+ if (message && message[0]) {
89
+ xt_stack_append(&buffer, &length, &capacity, ": ");
90
+ xt_stack_append(&buffer, &length, &capacity, message);
91
+ }
92
+ xt_stack_append(&buffer, &length, &capacity, "\n");
93
+ for (int32_t index = g_stack_depth - 1; index >= 0; index--) {
94
+ const xt_stack_entry *entry = &g_stack[index];
95
+ char line[24];
96
+ xt_stack_append(&buffer, &length, &capacity, " at ");
97
+ xt_stack_append(&buffer, &length, &capacity, entry->name && entry->name[0] ? entry->name : "anonymous");
98
+ xt_stack_append(&buffer, &length, &capacity, " (");
99
+ xt_stack_append(&buffer, &length, &capacity, entry->file && entry->file[0] ? entry->file : "<anonymous>");
100
+ if (entry->line > 0) {
101
+ snprintf(line, sizeof(line), ":%d", (int)entry->line);
102
+ xt_stack_append(&buffer, &length, &capacity, line);
103
+ }
104
+ xt_stack_append(&buffer, &length, &capacity, ")\n");
105
+ }
106
+ xt_value result = xt_string_new(buffer ? buffer : "", length);
107
+ free(buffer);
108
+ return result;
109
+ }
110
+
111
+ xt_value xt_error_stack(xt_value error) {
112
+ if (!XT_IS_OBJECT(error)) return XT_UNDEFINED;
113
+ xt_object *object = (xt_object *)XT_GET_PTR(error);
114
+ if (object->header.kind != XT_OBJECT_KIND_ERROR) return XT_UNDEFINED;
115
+ return object->error_stack;
116
+ }
117
+
118
+ /* ------------------------------------------------------------------------- */
119
+ /* Fatal-signal diagnostics */
120
+ /* ------------------------------------------------------------------------- */
121
+
122
+ static char g_crash_report[16384];
123
+
124
+ static int xt_format_stack(char *out, size_t size) {
125
+ int written = 0;
126
+ for (int32_t index = g_stack_depth - 1; index >= 0 && written < (int)size; index--) {
127
+ const xt_stack_entry *entry = &g_stack[index];
128
+ int count = snprintf(out + written, size - (size_t)written, " at %s (%s:%d)\n",
129
+ entry->name && entry->name[0] ? entry->name : "anonymous",
130
+ entry->file && entry->file[0] ? entry->file : "<anonymous>", (int)entry->line);
131
+ if (count < 0) break;
132
+ written += count;
133
+ }
134
+ return written < (int)size ? written : (int)size - 1;
135
+ }
136
+
137
+ #if defined(_WIN32)
138
+
139
+ static LONG WINAPI xt_fatal_exception(EXCEPTION_POINTERS *info) {
140
+ unsigned long code =
141
+ info && info->ExceptionRecord ? (unsigned long)info->ExceptionRecord->ExceptionCode : 0UL;
142
+ int written = snprintf(g_crash_report, sizeof(g_crash_report), "\nxbintsc: fatal exception 0x%08lx\n", code);
143
+ written += xt_format_stack(g_crash_report + written, sizeof(g_crash_report) - (size_t)written);
144
+ _write(2, g_crash_report, (unsigned int)written);
145
+ _exit(1);
146
+ return EXCEPTION_EXECUTE_HANDLER;
147
+ }
148
+
149
+ void xt_install_crash_handler(void) { SetUnhandledExceptionFilter(xt_fatal_exception); }
150
+
151
+ #else
152
+
153
+ static void xt_fatal_signal(int sig) {
154
+ int written = snprintf(g_crash_report, sizeof(g_crash_report), "\nxbintsc: fatal signal %d\n", sig);
155
+ written += xt_format_stack(g_crash_report + written, sizeof(g_crash_report) - (size_t)written);
156
+ ssize_t ignored = write(STDERR_FILENO, g_crash_report, (size_t)written);
157
+ (void)ignored;
158
+ _exit(128 + sig);
159
+ }
160
+
161
+ void xt_install_crash_handler(void) {
162
+ signal(SIGSEGV, xt_fatal_signal);
163
+ signal(SIGABRT, xt_fatal_signal);
164
+ signal(SIGFPE, xt_fatal_signal);
165
+ signal(SIGBUS, xt_fatal_signal);
166
+ #ifdef SIGILL
167
+ signal(SIGILL, xt_fatal_signal);
168
+ #endif
169
+ }
170
+
171
+ #endif
172
+
17
173
  #if defined(_WIN32)
18
174
  /*
19
175
  * The UCRT validates its low-level I/O arguments and, when one is out of
@@ -107,18 +263,23 @@ void *xt_try_enter(void) {
107
263
  if (!frame) abort();
108
264
  frame->prev = g_try_top;
109
265
  frame->exception = XT_UNDEFINED;
266
+ frame->stack_depth = g_stack_depth;
110
267
  g_try_top = frame;
111
268
  return (void *)frame;
112
269
  }
113
270
 
114
271
  xt_value xt_try_exception(void *framePtr) {
115
272
  xt_try_frame *frame = (xt_try_frame *)framePtr;
116
- return frame ? frame->exception : XT_UNDEFINED;
273
+ if (!frame) return XT_UNDEFINED;
274
+ /* A throw longjmps past the calls it unwinds; drop their shadow frames. */
275
+ xt_stack_trim(frame->stack_depth);
276
+ return frame->exception;
117
277
  }
118
278
 
119
279
  void xt_try_leave(void *framePtr) {
120
280
  xt_try_frame *frame = (xt_try_frame *)framePtr;
121
281
  if (frame && g_try_top == frame) {
282
+ xt_stack_trim(frame->stack_depth);
122
283
  g_try_top = frame->prev;
123
284
  free(frame);
124
285
  }
@@ -135,8 +296,16 @@ void xt_throw(xt_value v) {
135
296
  g_try_top->exception = v;
136
297
  longjmp(g_try_top->buf, 1);
137
298
  }
299
+ xt_value stack = xt_error_stack(v);
300
+ if (stack != XT_UNDEFINED) {
301
+ xt_string *text = xt_as_string(stack);
302
+ if (text) {
303
+ fprintf(stderr, "Uncaught %s\n", xt_string_bytes(text));
304
+ exit(1);
305
+ }
306
+ }
138
307
  xt_value message = xt_to_string(v);
139
308
  xt_string *s = xt_as_string(message);
140
- fprintf(stderr, "Uncaught %s\n", s->data);
309
+ fprintf(stderr, "Uncaught %s\n", xt_string_bytes(s));
141
310
  exit(1);
142
311
  }
@@ -2,15 +2,36 @@
2
2
  /* Output and inspection */
3
3
  /* ------------------------------------------------------------------------- */
4
4
 
5
+ /* Write UTF-8 bytes, turning a lone surrogate half (WTF-8) into U+FFFD. Node's
6
+ * terminal encoding does the same, so `console.log("😀"[0])` prints one
7
+ * replacement character instead of emitting invalid UTF-8. */
8
+ static void xt_print_utf8(FILE *out, const char *bytes, size_t length) {
9
+ size_t offset = 0;
10
+ while (offset < length) {
11
+ unsigned char c = (unsigned char)bytes[offset];
12
+ if (c == 0xED && offset + 2 < length) {
13
+ unsigned char second = (unsigned char)bytes[offset + 1];
14
+ unsigned char third = (unsigned char)bytes[offset + 2];
15
+ if (second >= 0xA0 && second <= 0xBF && third >= 0x80 && third <= 0xBF) {
16
+ fwrite("\xEF\xBF\xBD", 1, 3, out);
17
+ offset += 3;
18
+ continue;
19
+ }
20
+ }
21
+ fputc((int)c, out);
22
+ offset++;
23
+ }
24
+ }
25
+
5
26
  static void xt_print_value(xt_value v, FILE *out) {
6
27
  if (XT_IS_STRING(v)) {
7
28
  xt_string *s = xt_as_string(v);
8
- fwrite(s->data, 1, s->length, out);
29
+ xt_print_utf8(out, xt_string_bytes(s), s->length);
9
30
  return;
10
31
  }
11
32
  xt_value text = xt_to_string(v);
12
33
  xt_string *s = xt_as_string(text);
13
- fwrite(s->data, 1, s->length, out);
34
+ xt_print_utf8(out, xt_string_bytes(s), s->length);
14
35
  }
15
36
 
16
37
  void xt_print(xt_value v) { xt_print_value(v, stdout); }
@@ -48,10 +69,11 @@ static void xt_inspect_pop(void) {
48
69
 
49
70
  static int xt_inspect_identifier_key(xt_string *key) {
50
71
  if (key->length == 0) return 0;
51
- char c = key->data[0];
72
+ const char *bytes = xt_string_bytes(key);
73
+ char c = bytes[0];
52
74
  if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$')) return 0;
53
75
  for (uint32_t i = 1; i < key->length; i++) {
54
- c = key->data[i];
76
+ c = bytes[i];
55
77
  if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$')) return 0;
56
78
  }
57
79
  return 1;
@@ -60,16 +82,17 @@ static int xt_inspect_identifier_key(xt_string *key) {
60
82
  /* Render a string the way `util.inspect` does inside a container: quoted,
61
83
  * preferring whichever quote character needs fewer escapes. */
62
84
  static void xt_inspect_string(xt_string *s, FILE *out) {
85
+ const char *bytes = xt_string_bytes(s);
63
86
  int hasSingle = 0;
64
87
  int hasDouble = 0;
65
88
  for (uint32_t i = 0; i < s->length; i++) {
66
- if (s->data[i] == '\'') hasSingle = 1;
67
- else if (s->data[i] == '"') hasDouble = 1;
89
+ if (bytes[i] == '\'') hasSingle = 1;
90
+ else if (bytes[i] == '"') hasDouble = 1;
68
91
  }
69
92
  char quote = (hasSingle && !hasDouble) ? '"' : '\'';
70
93
  fputc(quote, out);
71
94
  for (uint32_t i = 0; i < s->length; i++) {
72
- unsigned char c = (unsigned char)s->data[i];
95
+ unsigned char c = (unsigned char)bytes[i];
73
96
  switch (c) {
74
97
  case '\\': fputs("\\\\", out); break;
75
98
  case '\n': fputs("\\n", out); break;
@@ -81,6 +104,18 @@ static void xt_inspect_string(xt_string *s, FILE *out) {
81
104
  default:
82
105
  if (c == (unsigned char)quote) { fputc('\\', out); fputc(c, out); }
83
106
  else if (c < 0x20) fprintf(out, "\\x%02x", c);
107
+ else if (c == 0xED && i + 2 < s->length) {
108
+ unsigned char second = (unsigned char)bytes[i + 1];
109
+ unsigned char third = (unsigned char)bytes[i + 2];
110
+ if (second >= 0xA0 && second <= 0xBF && third >= 0x80 && third <= 0xBF) {
111
+ unsigned code = ((unsigned)(c & 0x0F) << 12) | ((unsigned)(second & 0x3F) << 6) |
112
+ (unsigned)(third & 0x3F);
113
+ fprintf(out, "\\u%04x", code);
114
+ i += 2;
115
+ } else {
116
+ fputc(c, out);
117
+ }
118
+ }
84
119
  else fputc(c, out);
85
120
  }
86
121
  }
@@ -90,13 +125,13 @@ static void xt_inspect_string(xt_string *s, FILE *out) {
90
125
  static void xt_inspect(xt_value v, FILE *out, int nested) {
91
126
  if (xt_is_symbol(v)) {
92
127
  xt_string *text = xt_as_string(xt_symbol_to_string(v));
93
- fwrite(text->data, 1, text->length, out);
128
+ fwrite(xt_string_bytes(text), 1, text->length, out);
94
129
  return;
95
130
  }
96
131
  if (XT_IS_BIGINT(v)) {
97
132
  xt_value text = xt_bigint_to_decimal(v);
98
133
  xt_string *s = xt_as_string(text);
99
- fwrite(s->data, 1, s->length, out);
134
+ fwrite(xt_string_bytes(s), 1, s->length, out);
100
135
  fputc('n', out);
101
136
  return;
102
137
  }
@@ -130,10 +165,10 @@ static void xt_inspect(xt_value v, FILE *out, int nested) {
130
165
  xt_value key = extra->properties[i].key;
131
166
  if (xt_is_symbol(key)) {
132
167
  xt_string *text = xt_as_string(xt_symbol_to_string(key));
133
- fwrite(text->data, 1, text->length, out);
168
+ fwrite(xt_string_bytes(text), 1, text->length, out);
134
169
  } else {
135
170
  xt_string *keyString = xt_as_string(key);
136
- fwrite(keyString->data, 1, keyString->length, out);
171
+ fwrite(xt_string_bytes(keyString), 1, keyString->length, out);
137
172
  }
138
173
  fputs(": ", out);
139
174
  xt_inspect(extra->properties[i].value, out, 1);
@@ -170,10 +205,10 @@ static void xt_inspect(xt_value v, FILE *out, int nested) {
170
205
  written++;
171
206
  if (isSymbol) {
172
207
  xt_string *text = xt_as_string(xt_symbol_to_string(key));
173
- fwrite(text->data, 1, text->length, out);
208
+ fwrite(xt_string_bytes(text), 1, text->length, out);
174
209
  } else {
175
210
  xt_string *keyString = xt_as_string(key);
176
- if (xt_inspect_identifier_key(keyString)) fwrite(keyString->data, 1, keyString->length, out);
211
+ if (xt_inspect_identifier_key(keyString)) fwrite(xt_string_bytes(keyString), 1, keyString->length, out);
177
212
  else xt_inspect_string(keyString, out);
178
213
  }
179
214
  fputs(": ", out);
package/runtime/xt_io.c CHANGED
@@ -37,7 +37,12 @@
37
37
  #if defined(_WIN32)
38
38
  #include <fcntl.h>
39
39
  #include <io.h>
40
+ #include <process.h>
40
41
  #include <winsock2.h>
42
+ #include <windows.h>
43
+ #else
44
+ #include <signal.h>
45
+ #include <unistd.h>
41
46
  #endif
42
47
 
43
48
  #include "xt_io/exception.inc"