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
@@ -21,8 +21,15 @@
21
21
  * code free of write barriers and makes the first implementation small and
22
22
  * predictable. Replacing it with a precise/conservative collector is an
23
23
  * isolated change behind `xt_alloc` plus root registration.
24
- * * Strings are UTF-8. JavaScript measures string length in UTF-16 code
25
- * units; `xt_string_length` currently reports code points (see README).
24
+ * * Strings are stored as UTF-8 (with lone surrogate halves kept as WTF-8) but
25
+ * expose JavaScript's UTF-16 view: `length`, indexing, slicing, searching
26
+ * and splitting all count UTF-16 code units. `xt_string_length` is the raw
27
+ * byte length (what extensions and I/O need), while
28
+ * `xt_string_utf16_length` is the JavaScript `.length`. Concatenation is
29
+ * lazy: a `+` between two non-empty strings builds an immutable cons node
30
+ * (`flat == NULL`) and the bytes are materialised once, on first
31
+ * observation, by `xt_string_bytes`. This keeps `s += x` in a loop linear
32
+ * instead of copying the accumulated prefix every iteration.
26
33
  * * Objects use a small linear property list. Fine for the language subset
27
34
  * xbintsc targets today; swap for a hash map when the profile demands it.
28
35
  */
@@ -51,6 +58,7 @@
51
58
  #define XT_OBJECT_KIND_ERROR 12
52
59
  #define XT_OBJECT_KIND_GENERATOR 13
53
60
  #define XT_OBJECT_KIND_ITERATOR 14
61
+ #define XT_OBJECT_KIND_ASYNC 15
54
62
 
55
63
  /* Common header for every heap object. `flags` bit 0 is the collector's mark
56
64
  * bit; it is cleared on every surviving object at the end of a sweep. */
@@ -64,11 +72,24 @@ typedef struct xt_header {
64
72
 
65
73
  #define XT_GC_MARK 0x01u
66
74
 
67
- typedef struct {
75
+ /* A string is either *flat* (`flat` points at `capacity` bytes, the last one
76
+ * NUL) or a *cons* node (`flat == NULL`, `left`/`right` name the operands).
77
+ * `length` is the raw UTF-8 byte length; `utf16_length` (cached, 0 means
78
+ * "not computed yet" and is also the length of the empty string) is the same
79
+ * text measured in UTF-16 code units, which is what JavaScript observes.
80
+ * `code_point_count` is the number of Unicode scalar values (used by
81
+ * `for...of` and `Array.from`). `xt_string_bytes` turns a cons node into a flat
82
+ * one in place; treat the result as immutable and never write through a pointer
83
+ * obtained from it. */
84
+ typedef struct xt_string {
68
85
  xt_header header;
69
86
  uint32_t length;
87
+ uint32_t utf16_length;
88
+ uint32_t code_point_count;
70
89
  uint32_t capacity;
71
- char *data;
90
+ char *flat;
91
+ struct xt_string *left;
92
+ struct xt_string *right;
72
93
  } xt_string;
73
94
 
74
95
  /* Sign-magnitude arbitrary-precision integer; see xt_bigint.c. */
@@ -99,6 +120,10 @@ typedef struct {
99
120
  uint8_t frozen;
100
121
  uint8_t is_array_base;
101
122
  uint16_t reserved2;
123
+ /* Captured `Error.stack` for XT_OBJECT_KIND_ERROR, XT_UNDEFINED otherwise.
124
+ * Deliberately not a property: `Object.keys`, `for...in` and JSON.stringify
125
+ * must not see it, while `error.stack` returns it through `xt_get`. */
126
+ xt_value error_stack;
102
127
  } xt_object;
103
128
 
104
129
  typedef struct {
@@ -109,6 +134,10 @@ typedef struct {
109
134
  /* Named (non-index) properties such as `raw` on template strings or
110
135
  * `index`/`input` on RegExp match results, stored in an `xt_object`. */
111
136
  xt_value extra;
137
+ /* The array's [[Prototype]] (`Array.prototype` for ordinary arrays). Arrays
138
+ * are a distinct allocation kind, so this field is what lets
139
+ * `Object.getPrototypeOf([])` and `x instanceof <Array alias>` work. */
140
+ xt_value prototype;
112
141
  } xt_array;
113
142
 
114
143
  typedef struct {
@@ -123,6 +152,13 @@ typedef struct {
123
152
  /* Set on generator function objects: calling the closure does not run the
124
153
  * body, it creates a suspended generator (see xt_generator.c). */
125
154
  uint8_t is_generator;
155
+ /* Set on `async` function objects: calling the closure starts a coroutine
156
+ * (see xt_async.c) and immediately returns the promise it resolves. */
157
+ uint8_t is_async;
158
+ /* Non-NULL for the first-class value of a built-in constructor ("Array",
159
+ * "Map", ...). It lets property access resolve the constructor's statics
160
+ * (`const A = Array; A.isArray(x)`). Points at a static string. */
161
+ const char *builtin_name;
126
162
  } xt_function;
127
163
 
128
164
  /* Allocation and garbage collection (xt_alloc.c).
@@ -185,12 +221,67 @@ void xt_promise_gc_trace(xt_header *header);
185
221
  void xt_promise_gc_release(xt_header *header);
186
222
  void xt_generator_gc_trace(xt_header *header);
187
223
  void xt_generator_gc_release(xt_header *header);
224
+ void xt_async_gc_trace(xt_header *header);
225
+ void xt_async_gc_release(xt_header *header);
226
+
227
+ /* Stackful coroutines (xt_coroutine.c). A coroutine owns a private C stack and
228
+ * switches to it with `ucontext`/fibers; `enter` runs until the body calls
229
+ * `leave`. Exception frames and GC stack regions are swapped alongside the C
230
+ * stack. Generators and async functions own one each. */
231
+ typedef struct xt_coroutine xt_coroutine;
232
+ xt_coroutine *xt_coroutine_create(void (*entry)(void *), void *arg);
233
+ void xt_coroutine_enter(xt_coroutine *co);
234
+ void xt_coroutine_leave(xt_coroutine *co);
235
+ void xt_coroutine_gc_trace(xt_coroutine *co);
236
+ void xt_coroutine_release(xt_coroutine *co);
188
237
 
189
238
  /* Value / string helpers shared across translation units (xt_values.c). */
190
239
  xt_string *xt_string_alloc(size_t length);
191
240
  xt_string *xt_as_string(xt_value value);
192
241
  int32_t xt_string_length(xt_string *s);
193
242
  int xt_string_equals(xt_string *a, xt_string *b);
243
+ /* Contiguous UTF-8 bytes of a string, flattening a cons node in place on first
244
+ * use. The result is NUL-terminated at `s->length`; never write through it. */
245
+ const char *xt_string_bytes(xt_string *s);
246
+
247
+ /* UTF-8 <-> UTF-16 helpers (xt_values/utf16.inc). Decoding accepts WTF-8, so
248
+ * lone surrogate halves produced by indexing survive a round trip. */
249
+ uint32_t xt_utf8_decode(const char *data, size_t length, size_t *width);
250
+ size_t xt_utf8_encode(uint32_t code, char *out);
251
+ uint32_t xt_utf8_utf16_length(const char *data, size_t length);
252
+ void xt_utf8_measure(const char *data, size_t length, uint32_t *units, uint32_t *points);
253
+ /* Combine an adjacent WTF-8 high/low surrogate pair into one four-byte
254
+ * sequence, in place. Returns the resulting byte length. */
255
+ size_t xt_utf8_normalize_surrogates(char *data, size_t length);
256
+
257
+ /* Publish a manually filled buffer (`xt_string_alloc` + `xt_string_mutable`):
258
+ * normalises surrogate pairs, records the byte length and caches the UTF-16 and
259
+ * code-point counts. */
260
+ void xt_string_finish(xt_string *s);
261
+ uint32_t xt_string_utf16_length(xt_string *s);
262
+ uint32_t xt_string_code_point_count(xt_string *s);
263
+ /* UTF-16 code unit at `index`, or -1 when out of range. */
264
+ int32_t xt_string_code_unit(xt_string *s, int32_t index);
265
+ /* Unicode code point beginning at UTF-16 index `index` (combining a surrogate
266
+ * pair), or -1 when out of range. */
267
+ int32_t xt_string_code_point_at(xt_string *s, int32_t index);
268
+ /* A one-code-unit string (a lone surrogate when `index` points into a pair). */
269
+ xt_value xt_string_unit_value(xt_string *s, int32_t index);
270
+ xt_value xt_string_from_code_point(uint32_t code);
271
+ xt_value xt_string_from_units(const uint16_t *units, size_t count);
272
+ /* Copy UTF-16 range [start, end), splitting a surrogate pair when needed. */
273
+ xt_value xt_string_slice_units(xt_string *s, int32_t start, int32_t end);
274
+ /* Byte offset / UTF-16 index conversions; both clamp to the string bounds. */
275
+ int32_t xt_string_byte_offset(xt_string *s, int32_t unit_index);
276
+ int32_t xt_string_unit_offset(xt_string *s, int32_t byte_offset);
277
+ /* True when `unit_index` sits on a code-point boundary (not between the two
278
+ * halves of a surrogate pair). */
279
+ int xt_string_is_boundary(xt_string *s, int32_t unit_index);
280
+ /* The `index`-th Unicode code point (for `for...of` and `Array.from`). */
281
+ xt_value xt_string_code_point_value(xt_string *s, int32_t index);
282
+ /* Mutable bytes of a string just created with `xt_string_alloc` (never a cons
283
+ * node). Used by the constructors that fill a buffer before publishing it. */
284
+ char *xt_string_mutable(xt_string *s);
194
285
  int32_t xt_to_int32(xt_value v);
195
286
 
196
287
  /* ECMAScript number formatting (xt_values.c). */
@@ -277,6 +368,26 @@ xt_value xt_yield_star(xt_value delegate);
277
368
  int xt_generator_has_next(xt_value generator);
278
369
  xt_value xt_generator_iter_value(xt_value generator);
279
370
 
371
+ /* Async coroutines (xt_async.c). Calling an `async` function runs its body on
372
+ * a coroutine that suspends at a pending `await` and resumes when the awaited
373
+ * promise settles, so the event loop can make progress in between. */
374
+ #define XT_PROMISE_PENDING 0
375
+ #define XT_PROMISE_FULFILLED 1
376
+ #define XT_PROMISE_REJECTED 2
377
+ xt_value xt_async_call(xt_value function, xt_value thisValue, int32_t argc, xt_value *argv);
378
+ /* Resume `async` with the settlement of the promise it awaited. Called by the
379
+ * microtask runner when the promise settles. */
380
+ void xt_async_wake(void *async, xt_value value, int rejected);
381
+ /* Create a pending promise / settle an existing one (used by xt_async.c). */
382
+ xt_value xt_promise_create(void);
383
+ void xt_promise_fulfill(xt_value promise, xt_value value);
384
+ void xt_promise_reject_value(xt_value promise, xt_value value);
385
+ /* `-1` when `value` is not a promise, otherwise PENDING / FULFILLED / REJECTED. */
386
+ int xt_promise_state_of(xt_value value);
387
+ xt_value xt_promise_value_of(xt_value value);
388
+ /* Register a coroutine to resume when `promise` settles (it must be pending). */
389
+ void xt_promise_add_async_waiter(xt_value promise, void *async);
390
+
280
391
  /* Exception-frame plumbing shared with the generator runtime. The struct is
281
392
  * public so a coroutine can `_setjmp` its own boundary directly (a `setjmp`
282
393
  * must run in the frame it returns to, never in a helper). */
@@ -284,6 +395,10 @@ typedef struct xt_try_frame {
284
395
  jmp_buf buf;
285
396
  struct xt_try_frame *prev;
286
397
  xt_value exception;
398
+ /* Shadow-stack depth when the protected region was entered; restored when
399
+ * the region is left (normally or through a throw) so frames from unwound
400
+ * calls do not leak into later stack traces. */
401
+ int32_t stack_depth;
287
402
  } xt_try_frame;
288
403
  /* Save a try frame with clang's `_setjmp` built-in.
289
404
  *
@@ -300,6 +415,23 @@ typedef struct xt_try_frame {
300
415
  void *xt_try_mark(void);
301
416
  void xt_try_restore(void *mark);
302
417
 
418
+ /* -- shadow call stack and diagnostics (xt_io/exception.inc) --------------- */
419
+ /* Generated code pushes a frame on entry and pops it before returning, so an
420
+ * Error can capture a JavaScript-level stack without any debug metadata. Frame
421
+ * strings live in the module's static data and are never freed. */
422
+ void xt_stack_push(const char *name, const char *file, int32_t line);
423
+ void xt_stack_pop(void);
424
+ int32_t xt_stack_depth(void);
425
+ void xt_stack_set_line(int32_t line);
426
+ /* Restore the depth saved by a try frame after unwinding inner calls. */
427
+ void xt_stack_trim(int32_t depth);
428
+ /* Format `Name: message` plus the current frames as an `Error.stack` string. */
429
+ xt_value xt_stack_capture(const char *name, const char *message);
430
+ /* The captured stack of an error object, or XT_UNDEFINED. */
431
+ xt_value xt_error_stack(xt_value error);
432
+ /* Best-effort stack dump on a fatal native signal / unhandled exception. */
433
+ void xt_install_crash_handler(void);
434
+
303
435
  /* -- collection / date / regexp representations --------------------------- */
304
436
  typedef struct {
305
437
  xt_header header;
@@ -210,6 +210,9 @@ void xt_gc_init(void) {
210
210
  g_gc_threshold_pinned = 1;
211
211
  }
212
212
  }
213
+ /* A crash is the one case where the JavaScript-level shadow stack is the
214
+ * most useful thing to print; install the handler once per process. */
215
+ xt_install_crash_handler();
213
216
  }
214
217
 
215
218
  void xt_gc_arm(void) { g_gc_armed = 1; }
@@ -328,7 +331,16 @@ static void xt_gc_process(xt_header *header) {
328
331
  const void *base = (const char *)header + sizeof(xt_header);
329
332
 
330
333
  switch (header->kind) {
331
- case XT_OBJECT_KIND_STRING:
334
+ case XT_OBJECT_KIND_STRING: {
335
+ /* A flat string holds bytes only. A cons node is an immutable rope whose
336
+ * children must stay alive until `xt_string_bytes` materialises it. */
337
+ xt_string *string = (xt_string *)header;
338
+ if (string->flat == NULL) {
339
+ if (string->left) xt_gc_gray_push(&string->left->header);
340
+ if (string->right) xt_gc_gray_push(&string->right->header);
341
+ }
342
+ return;
343
+ }
332
344
  case XT_OBJECT_KIND_BIGINT:
333
345
  case XT_OBJECT_KIND_DATE:
334
346
  /* Numbers/bytes only: no references to follow. */
@@ -377,6 +389,9 @@ static void xt_gc_process(xt_header *header) {
377
389
  case XT_OBJECT_KIND_GENERATOR:
378
390
  xt_generator_gc_trace(header);
379
391
  return;
392
+ case XT_OBJECT_KIND_ASYNC:
393
+ xt_async_gc_trace(header);
394
+ return;
380
395
  default:
381
396
  /* Symbols, regexps, iterators and any future kind whose references live
382
397
  * inline: the payload scan covers them. */
@@ -392,7 +407,7 @@ static void xt_gc_process(xt_header *header) {
392
407
  static void xt_gc_release(xt_header *header) {
393
408
  switch (header->kind) {
394
409
  case XT_OBJECT_KIND_STRING:
395
- free(((xt_string *)header)->data);
410
+ free(((xt_string *)header)->flat);
396
411
  break;
397
412
  case XT_OBJECT_KIND_BIGINT:
398
413
  free(((xt_bigint *)header)->limbs);
@@ -420,6 +435,9 @@ static void xt_gc_release(xt_header *header) {
420
435
  case XT_OBJECT_KIND_GENERATOR:
421
436
  xt_generator_gc_release(header);
422
437
  break;
438
+ case XT_OBJECT_KIND_ASYNC:
439
+ xt_async_gc_release(header);
440
+ break;
423
441
  default:
424
442
  break;
425
443
  }
@@ -0,0 +1,197 @@
1
+ /*
2
+ * xbintsc runtime — real `async` / `await`.
3
+ *
4
+ * An `async` function runs its body on a stackful coroutine (the same
5
+ * machinery generators use, see `xt_coroutine.c`) and immediately returns a
6
+ * promise. The body runs synchronously until it reaches an `await` whose
7
+ * operand is still pending; at that point the coroutine suspends and control
8
+ * returns to the caller, so the program body or the event loop can keep going.
9
+ * When the awaited promise settles, the promise runtime queues a microtask that
10
+ * resumes the coroutine exactly where it stopped — because the coroutine keeps
11
+ * a real C stack, `try`/`finally`, loops and locals all survive the suspension
12
+ * without a state-machine transform.
13
+ *
14
+ * `await` on an already-settled value keeps the historical fast path: it
15
+ * returns (or throws) synchronously instead of forcing a microtask turn. This
16
+ * makes the common "await a synchronous builtin" case cheap and preserves the
17
+ * existing ordering guarantees.
18
+ */
19
+
20
+ #include "rt_internal.h"
21
+
22
+ #include <stdlib.h>
23
+ #include <string.h>
24
+
25
+ typedef struct xt_async {
26
+ xt_header header;
27
+ xt_value function;
28
+ xt_value this_value;
29
+ xt_value *args;
30
+ int32_t argc;
31
+ /* Promise returned to the caller and settled when the body finishes. */
32
+ xt_value promise;
33
+ /* Completion value, or the exception that escaped the body. */
34
+ xt_value value;
35
+ /* Values handed over by the waiter and consumed by `xt_await`. */
36
+ xt_value resume_value;
37
+ int await_threw;
38
+ int done;
39
+ int threw;
40
+ xt_coroutine *coroutine;
41
+ } xt_async;
42
+
43
+ /* The async coroutine currently running on its private stack, if any. It is
44
+ * only reachable through this pointer while it runs, so it is a GC root. */
45
+ static xt_async *g_active_async = NULL;
46
+
47
+ static void xt_async_gc_register(void);
48
+
49
+ /* Runs on the coroutine's private stack and never returns normally: it
50
+ * suspends back to the resumer once the body has completed. */
51
+ static void xt_async_run(void *arg) {
52
+ xt_async *async = (xt_async *)arg;
53
+ void *boundary = xt_try_enter();
54
+ if (xt_try_setjmp(boundary) == 0) {
55
+ xt_function *function = (xt_function *)XT_GET_PTR(async->function);
56
+ async->value = function->code(async->this_value, async->function, async->argc, async->args);
57
+ async->threw = 0;
58
+ } else {
59
+ /* `throw` inside the body (not caught by a `try`) rejects the promise. */
60
+ async->value = xt_try_exception(boundary);
61
+ async->threw = 1;
62
+ }
63
+ async->done = 1;
64
+ xt_coroutine_leave(async->coroutine);
65
+ }
66
+
67
+ /* Switch onto the coroutine. It either suspends at an `await` or completes. */
68
+ static void xt_async_resume(xt_async *async) {
69
+ xt_async *previous = g_active_async;
70
+ g_active_async = async;
71
+ xt_coroutine_enter(async->coroutine);
72
+ g_active_async = previous;
73
+ if (!async->done) return;
74
+ xt_coroutine_release(async->coroutine);
75
+ async->coroutine = NULL;
76
+ if (async->threw) xt_promise_reject_value(async->promise, async->value);
77
+ else xt_promise_fulfill(async->promise, async->value);
78
+ }
79
+
80
+ xt_value xt_async_call(xt_value function, xt_value thisValue, int32_t argc, xt_value *argv) {
81
+ xt_async *async = (xt_async *)xt_alloc(sizeof(xt_async), XT_OBJECT_KIND_ASYNC);
82
+ async->function = function;
83
+ async->this_value = thisValue;
84
+ async->argc = argc;
85
+ async->args = NULL;
86
+ if (argc > 0 && argv) {
87
+ async->args = (xt_value *)malloc(sizeof(xt_value) * (size_t)argc);
88
+ if (!async->args) abort();
89
+ memcpy(async->args, argv, sizeof(xt_value) * (size_t)argc);
90
+ }
91
+ async->promise = xt_promise_create();
92
+ async->value = XT_UNDEFINED;
93
+ async->resume_value = XT_UNDEFINED;
94
+ async->await_threw = 0;
95
+ async->done = 0;
96
+ async->threw = 0;
97
+ async->coroutine = xt_coroutine_create(xt_async_run, async);
98
+ xt_async_gc_register();
99
+ /* Start synchronously, matching JavaScript: the body runs up to its first
100
+ * pending `await` before the call returns. */
101
+ xt_async_resume(async);
102
+ return async->promise;
103
+ }
104
+
105
+ void xt_async_wake(void *asyncPointer, xt_value value, int rejected) {
106
+ xt_async *async = (xt_async *)asyncPointer;
107
+ if (!async || async->done || !async->coroutine) return;
108
+ async->resume_value = value;
109
+ async->await_threw = rejected;
110
+ xt_async_resume(async);
111
+ }
112
+
113
+ /* `await value`: unwrap promises, suspending the running async coroutine when
114
+ * the promise is still pending. */
115
+ xt_value xt_await(xt_value value) {
116
+ int state = xt_promise_state_of(value);
117
+ if (state < 0) return value;
118
+ if (state == XT_PROMISE_REJECTED) {
119
+ xt_throw(xt_promise_value_of(value));
120
+ return XT_UNDEFINED;
121
+ }
122
+ if (state == XT_PROMISE_FULFILLED) return xt_promise_value_of(value);
123
+
124
+ xt_async *async = g_active_async;
125
+ if (async && async->coroutine) {
126
+ async->await_threw = 0;
127
+ async->resume_value = XT_UNDEFINED;
128
+ xt_promise_add_async_waiter(value, async);
129
+ xt_coroutine_leave(async->coroutine);
130
+ /* The waiter resumed us. A rejection is rethrown inside the coroutine so
131
+ * its own `try`/`catch` and `finally` see it. */
132
+ if (async->await_threw) {
133
+ async->await_threw = 0;
134
+ xt_throw(async->resume_value);
135
+ return XT_UNDEFINED;
136
+ }
137
+ return async->resume_value;
138
+ }
139
+
140
+ /* No coroutine to suspend (top-level `await`, or `await` reached outside an
141
+ * async function): block on the reactor until the promise settles, exactly
142
+ * like a synchronous embedder waiting for the event loop. The loop returns
143
+ * immediately when no work remains, so a promise nothing can settle does not
144
+ * hang the process here. */
145
+ for (int guard = 0; guard < 100000; guard++) {
146
+ xt_drain_microtasks();
147
+ state = xt_promise_state_of(value);
148
+ if (state != XT_PROMISE_PENDING) break;
149
+ if (!xt_loop_has_work()) break;
150
+ if (xt_loop_poll(-1) < 0) break;
151
+ }
152
+ state = xt_promise_state_of(value);
153
+ if (state == XT_PROMISE_REJECTED) {
154
+ xt_throw(xt_promise_value_of(value));
155
+ return XT_UNDEFINED;
156
+ }
157
+ return state == XT_PROMISE_FULFILLED ? xt_promise_value_of(value) : XT_UNDEFINED;
158
+ }
159
+
160
+ /* While a coroutine runs, the async object is only reachable through this
161
+ * static pointer. */
162
+ static void xt_async_gc_scan_roots(void) {
163
+ if (!g_active_async) return;
164
+ xt_gc_mark_header((xt_header *)g_active_async);
165
+ }
166
+
167
+ static void xt_async_gc_register(void) {
168
+ static int registered = 0;
169
+ if (registered) return;
170
+ registered = 1;
171
+ xt_gc_register_root_provider(xt_async_gc_scan_roots);
172
+ }
173
+
174
+ void xt_async_gc_trace(xt_header *header) {
175
+ xt_async *async = (xt_async *)header;
176
+ xt_gc_mark_value(async->function);
177
+ xt_gc_mark_value(async->this_value);
178
+ xt_gc_mark_value(async->promise);
179
+ xt_gc_mark_value(async->value);
180
+ xt_gc_mark_value(async->resume_value);
181
+ if (async->args && async->argc > 0) {
182
+ xt_gc_scan_region(async->args, sizeof(xt_value) * (size_t)async->argc);
183
+ }
184
+ xt_coroutine_gc_trace(async->coroutine);
185
+ }
186
+
187
+ void xt_async_gc_release(xt_header *header) {
188
+ xt_async *async = (xt_async *)header;
189
+ free(async->args);
190
+ xt_coroutine_release(async->coroutine);
191
+ async->coroutine = NULL;
192
+ }
193
+
194
+ xt_value xt_function_set_async(xt_value value) {
195
+ if (XT_IS_FUNCTION(value)) ((xt_function *)XT_GET_PTR(value))->is_async = 1;
196
+ return value;
197
+ }
@@ -41,7 +41,7 @@ static xt_value bi_from_xt_value(xt_value value) {
41
41
  if (XT_IS_NUMBER(value)) return xt_bigint_from_double(xt_to_double(value));
42
42
  if (XT_IS_STRING(value)) {
43
43
  xt_string *s = xt_as_string(value);
44
- return xt_bigint_from_string(s->data, s->length);
44
+ return xt_bigint_from_string(xt_string_bytes(s), s->length);
45
45
  }
46
46
  if (value == XT_TRUE) return xt_bigint_from_i64(1);
47
47
  if (value == XT_FALSE) return xt_bigint_from_i64(0);