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.
- package/AGENTS.md +8 -4
- package/README.md +16 -5
- package/README.zh-CN.md +14 -6
- package/dist/src/ast/common.d.ts +23 -0
- package/dist/src/cli/main.js +68 -24
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/access.js +6 -2
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/calls/closures.js +4 -0
- package/dist/src/codegen/generator/calls/closures.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +9 -4
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +50 -0
- package/dist/src/codegen/generator/context.js +124 -13
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/debug.d.ts +33 -0
- package/dist/src/codegen/generator/debug.js +97 -0
- package/dist/src/codegen/generator/debug.js.map +1 -0
- package/dist/src/codegen/generator/expressions/assignment.js +9 -5
- package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
- package/dist/src/codegen/generator/expressions/operators.js +35 -9
- package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +11 -3
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/generator.d.ts +3 -1
- package/dist/src/codegen/generator/generator.js +3 -1
- package/dist/src/codegen/generator/generator.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +1 -1
- package/dist/src/codegen/generator/module.js +82 -12
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/numeric.d.ts +26 -0
- package/dist/src/codegen/generator/numeric.js +127 -0
- package/dist/src/codegen/generator/numeric.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +17 -0
- package/dist/src/codegen/generator/state.js +9 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +2 -4
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/statements/dispatch.js +3 -0
- package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
- package/dist/src/codegen/generator/statements/variables.js +8 -2
- package/dist/src/codegen/generator/statements/variables.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +16 -0
- package/dist/src/codegen/generator/tables.js +99 -2
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/merge.js +41 -5
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +23 -4
- package/dist/src/driver/compiler.js +46 -57
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/fingerprint.d.ts +27 -0
- package/dist/src/driver/fingerprint.js +88 -0
- package/dist/src/driver/fingerprint.js.map +1 -0
- package/dist/src/driver/toolchain-provider.d.ts +9 -2
- package/dist/src/driver/toolchain-provider.js +15 -15
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +58 -1
- package/dist/src/driver/toolchain.js +169 -22
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/parser/expressions.js +20 -1
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/tests/cli/main.test.js +41 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/closures.test.js +8 -0
- package/dist/tests/codegen/closures.test.js.map +1 -1
- package/dist/tests/codegen/expressions.test.js +14 -3
- package/dist/tests/codegen/expressions.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +23 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/toolchain-provider.test.js +49 -2
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +62 -3
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/async-await.test.d.ts +10 -0
- package/dist/tests/e2e/async-await.test.js +247 -0
- package/dist/tests/e2e/async-await.test.js.map +1 -0
- package/dist/tests/e2e/child-process.test.d.ts +10 -0
- package/dist/tests/e2e/child-process.test.js +59 -0
- package/dist/tests/e2e/child-process.test.js.map +1 -0
- package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/error-stack.test.d.ts +9 -0
- package/dist/tests/e2e/error-stack.test.js +94 -0
- package/dist/tests/e2e/error-stack.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js +46 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
- package/dist/tests/e2e/string-concat.test.d.ts +9 -0
- package/dist/tests/e2e/string-concat.test.js +102 -0
- package/dist/tests/e2e/string-concat.test.js.map +1 -0
- package/dist/tests/e2e/string-utf16.test.d.ts +6 -0
- package/dist/tests/e2e/string-utf16.test.js +133 -0
- package/dist/tests/e2e/string-utf16.test.js.map +1 -0
- package/dist/tests/helpers.d.ts +9 -0
- package/dist/tests/helpers.js +26 -3
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +7 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/version.test.d.ts +1 -0
- package/dist/tests/version.test.js +24 -0
- package/dist/tests/version.test.js.map +1 -0
- package/doc/DESIGN.md +20 -12
- package/doc/ai/cli.md +2 -0
- package/doc/ai/contributing.md +16 -8
- package/doc/ai/extensions.md +3 -2
- package/doc/ai/language-support.md +15 -12
- package/doc/ai/troubleshooting.md +43 -5
- package/doc/ai/zh-CN/cli.md +2 -0
- package/doc/ai/zh-CN/contributing.md +11 -7
- package/doc/ai/zh-CN/extensions.md +2 -1
- package/doc/ai/zh-CN/language-support.md +15 -12
- package/doc/ai/zh-CN/troubleshooting.md +38 -4
- package/doc/implemented.md +16 -13
- package/doc/requirements.md +13 -4
- package/doc/unimplemented.md +20 -18
- package/doc/zh-CN/DESIGN.md +15 -9
- package/doc/zh-CN/implemented.md +15 -12
- package/doc/zh-CN/requirements.md +12 -3
- package/doc/zh-CN/unimplemented.md +19 -17
- package/package.json +3 -1
- package/runtime/ext_node/buffer/parts/construction.inc +2 -2
- package/runtime/ext_node/buffer/parts/methods.inc +1 -1
- package/runtime/ext_node/buffer/parts/search.inc +1 -1
- package/runtime/ext_node/child_process/child_process.c +25 -0
- package/runtime/ext_node/fs/fd_ops.c +1 -1
- package/runtime/rt.h +49 -1
- package/runtime/rt_internal.h +136 -4
- package/runtime/xt_alloc.c +20 -2
- package/runtime/xt_async.c +197 -0
- package/runtime/xt_bigint/conversions.inc +1 -1
- package/runtime/xt_builtins.c +169 -43
- package/runtime/xt_constructors.c +389 -0
- package/runtime/xt_containers/access.inc +61 -24
- package/runtime/xt_containers/arrays.inc +26 -2
- package/runtime/xt_containers/objects.inc +206 -8
- package/runtime/xt_coroutine.c +212 -0
- package/runtime/xt_generator.c +31 -162
- package/runtime/xt_io/exception.inc +171 -2
- package/runtime/xt_io/inspect.inc +48 -13
- package/runtime/xt_io.c +5 -0
- package/runtime/xt_promise.c +62 -17
- package/runtime/xt_stdlib/arrays.inc +2 -2
- package/runtime/xt_stdlib/dispatch.inc +20 -4
- package/runtime/xt_stdlib/strings.inc +98 -29
- package/runtime/xt_stdlib2/array-statics.inc +3 -2
- package/runtime/xt_stdlib2/arrays.inc +1 -1
- package/runtime/xt_stdlib2/error.inc +33 -6
- package/runtime/xt_stdlib2/json.inc +46 -16
- package/runtime/xt_stdlib2/number-string-statics.inc +24 -13
- package/runtime/xt_stdlib2/objects.inc +1 -1
- package/runtime/xt_stdlib2/regexp-match.inc +50 -35
- package/runtime/xt_stdlib2/set.inc +6 -0
- package/runtime/xt_stdlib2/strings.inc +81 -42
- package/runtime/xt_symbol.c +1 -1
- package/runtime/xt_typed_array/construction.inc +43 -2
- package/runtime/xt_typed_array.c +4 -0
- package/runtime/xt_values/comparison.inc +2 -2
- package/runtime/xt_values/constructors.inc +18 -5
- package/runtime/xt_values/conversions.inc +4 -3
- package/runtime/xt_values/strings.inc +94 -4
- package/runtime/xt_values/utf16.inc +340 -0
- package/runtime/xt_values.c +1 -0
- package/scripts/bench.ts +172 -0
- package/scripts/build-runtime.ts +2 -1
- package/scripts/check-version.ts +53 -0
- package/src/ast/common.ts +23 -0
- package/src/cli/main.ts +68 -26
- package/src/codegen/generator/calls/access.ts +7 -2
- package/src/codegen/generator/calls/closures.ts +3 -0
- package/src/codegen/generator/calls/invocation.ts +9 -3
- package/src/codegen/generator/context.ts +129 -18
- package/src/codegen/generator/debug.ts +132 -0
- package/src/codegen/generator/expressions/assignment.ts +9 -5
- package/src/codegen/generator/expressions/operators.ts +47 -8
- package/src/codegen/generator/expressions/primary.ts +11 -3
- package/src/codegen/generator/generator.ts +18 -2
- package/src/codegen/generator/module.ts +83 -13
- package/src/codegen/generator/numeric.ts +158 -0
- package/src/codegen/generator/state.ts +26 -0
- package/src/codegen/generator/statements/control-flow.ts +2 -3
- package/src/codegen/generator/statements/dispatch.ts +3 -0
- package/src/codegen/generator/statements/variables.ts +8 -2
- package/src/codegen/generator/tables.ts +110 -2
- package/src/driver/bundler/merge.ts +39 -5
- package/src/driver/compiler.ts +65 -55
- package/src/driver/fingerprint.ts +85 -0
- package/src/driver/toolchain-provider.ts +17 -16
- package/src/driver/toolchain.ts +210 -21
- package/src/parser/expressions.ts +20 -1
package/runtime/rt_internal.h
CHANGED
|
@@ -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
|
|
25
|
-
*
|
|
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
|
-
|
|
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 *
|
|
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;
|
package/runtime/xt_alloc.c
CHANGED
|
@@ -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)->
|
|
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
|
|
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);
|