xbintsc 0.3.87 → 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 +4 -3
- package/README.zh-CN.md +2 -2
- 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 +4 -3
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +6 -1
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +0 -1
- package/dist/src/codegen/generator/module.js +10 -10
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/state.js +4 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +1 -3
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +9 -0
- package/dist/src/codegen/generator/tables.js +74 -2
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +2 -2
- package/dist/src/driver/compiler.js +4 -1
- package/dist/src/driver/compiler.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/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/runtime-coverage-globals.test.js +46 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
- 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/doc/ai/contributing.md +9 -7
- package/doc/ai/extensions.md +3 -2
- package/doc/ai/language-support.md +11 -8
- package/doc/ai/troubleshooting.md +4 -3
- package/doc/ai/zh-CN/contributing.md +5 -5
- package/doc/ai/zh-CN/extensions.md +2 -1
- package/doc/ai/zh-CN/language-support.md +11 -8
- package/doc/ai/zh-CN/troubleshooting.md +3 -2
- package/doc/implemented.md +15 -12
- package/doc/unimplemented.md +11 -10
- package/doc/zh-CN/implemented.md +14 -11
- package/doc/zh-CN/unimplemented.md +11 -10
- package/package.json +1 -1
- package/runtime/rt.h +32 -0
- package/runtime/rt_internal.h +99 -10
- package/runtime/xt_alloc.c +6 -0
- package/runtime/xt_async.c +197 -0
- package/runtime/xt_builtins.c +40 -36
- package/runtime/xt_constructors.c +389 -0
- package/runtime/xt_containers/access.inc +47 -11
- package/runtime/xt_containers/arrays.inc +26 -2
- package/runtime/xt_containers/objects.inc +202 -5
- package/runtime/xt_coroutine.c +212 -0
- package/runtime/xt_generator.c +31 -162
- package/runtime/xt_io/inspect.inc +35 -2
- package/runtime/xt_promise.c +62 -17
- package/runtime/xt_stdlib/dispatch.inc +18 -2
- package/runtime/xt_stdlib/strings.inc +73 -16
- package/runtime/xt_stdlib2/array-statics.inc +3 -3
- package/runtime/xt_stdlib2/error.inc +11 -0
- package/runtime/xt_stdlib2/json.inc +44 -15
- package/runtime/xt_stdlib2/number-string-statics.inc +24 -15
- package/runtime/xt_stdlib2/objects.inc +1 -1
- package/runtime/xt_stdlib2/regexp-match.inc +2 -2
- package/runtime/xt_stdlib2/set.inc +6 -0
- package/runtime/xt_stdlib2/strings.inc +70 -40
- package/runtime/xt_typed_array/construction.inc +43 -2
- package/runtime/xt_typed_array.c +4 -0
- package/runtime/xt_values/constructors.inc +8 -0
- package/runtime/xt_values/strings.inc +19 -3
- package/runtime/xt_values/utf16.inc +340 -0
- package/runtime/xt_values.c +1 -0
- 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 +4 -3
- package/src/codegen/generator/expressions/primary.ts +6 -1
- package/src/codegen/generator/module.ts +10 -12
- package/src/codegen/generator/state.ts +4 -0
- package/src/codegen/generator/statements/control-flow.ts +1 -2
- package/src/codegen/generator/tables.ts +84 -2
- package/src/driver/compiler.ts +4 -1
package/runtime/xt_generator.c
CHANGED
|
@@ -8,28 +8,17 @@
|
|
|
8
8
|
* `finally` and nested loops work across suspension points without a state
|
|
9
9
|
* machine transform.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* targets the body's own
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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(
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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->
|
|
87
|
+
if (!gen->coroutine) xt_generator_boot(gen);
|
|
193
88
|
xt_generator *previous = g_active_generator;
|
|
194
89
|
g_active_generator = gen;
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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->
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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); }
|
|
@@ -83,6 +104,18 @@ static void xt_inspect_string(xt_string *s, FILE *out) {
|
|
|
83
104
|
default:
|
|
84
105
|
if (c == (unsigned char)quote) { fputc('\\', out); fputc(c, out); }
|
|
85
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
|
+
}
|
|
86
119
|
else fputc(c, out);
|
|
87
120
|
}
|
|
88
121
|
}
|
package/runtime/xt_promise.c
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* xbintsc runtime — promises and
|
|
2
|
+
* xbintsc runtime — promises and the microtask queue.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Promise reactions are queued as microtasks and run when the program body
|
|
5
|
+
* finishes, by the event loop between callbacks, and on demand. `await` lives
|
|
6
|
+
* in `xt_async.c`: an `async` function runs on a coroutine, so awaiting a
|
|
7
|
+
* promise that is still pending suspends it and registers a waiter here. When
|
|
8
|
+
* the promise settles, the waiter becomes a microtask that resumes the
|
|
9
|
+
* coroutine. Everything is single-threaded and non-blocking.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
#include "rt_internal.h"
|
|
@@ -12,10 +14,6 @@
|
|
|
12
14
|
#include <stdlib.h>
|
|
13
15
|
#include <string.h>
|
|
14
16
|
|
|
15
|
-
#define XT_PROMISE_PENDING 0
|
|
16
|
-
#define XT_PROMISE_FULFILLED 1
|
|
17
|
-
#define XT_PROMISE_REJECTED 2
|
|
18
|
-
|
|
19
17
|
typedef struct xt_promise xt_promise;
|
|
20
18
|
|
|
21
19
|
typedef struct {
|
|
@@ -23,6 +21,9 @@ typedef struct {
|
|
|
23
21
|
xt_value on_fulfilled;
|
|
24
22
|
xt_value on_rejected;
|
|
25
23
|
xt_promise *result;
|
|
24
|
+
/* Non-NULL for an `async` coroutine waiting on this promise. Such a reaction
|
|
25
|
+
* has no handlers: running it resumes the coroutine instead. */
|
|
26
|
+
void *async;
|
|
26
27
|
} xt_reaction;
|
|
27
28
|
|
|
28
29
|
struct xt_promise {
|
|
@@ -87,6 +88,7 @@ static void xt_promise_gc_scan_roots(void) {
|
|
|
87
88
|
xt_gc_mark_value(reaction->on_fulfilled);
|
|
88
89
|
xt_gc_mark_value(reaction->on_rejected);
|
|
89
90
|
xt_gc_mark_header((xt_header *)reaction->result);
|
|
91
|
+
if (reaction->async) xt_gc_mark_header((xt_header *)reaction->async);
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
|
|
@@ -107,6 +109,7 @@ void xt_promise_gc_trace(xt_header *header) {
|
|
|
107
109
|
xt_gc_mark_value(reaction->on_fulfilled);
|
|
108
110
|
xt_gc_mark_value(reaction->on_rejected);
|
|
109
111
|
xt_gc_mark_header((xt_header *)reaction->result);
|
|
112
|
+
if (reaction->async) xt_gc_mark_header((xt_header *)reaction->async);
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -133,6 +136,7 @@ static void xt_promise_resolve_into(xt_promise *result, xt_value value) {
|
|
|
133
136
|
reaction->on_fulfilled = XT_UNDEFINED;
|
|
134
137
|
reaction->on_rejected = XT_UNDEFINED;
|
|
135
138
|
reaction->result = result;
|
|
139
|
+
reaction->async = NULL;
|
|
136
140
|
if (other->reaction_count >= other->reaction_capacity) {
|
|
137
141
|
other->reaction_capacity = other->reaction_capacity == 0 ? 4 : other->reaction_capacity * 2;
|
|
138
142
|
other->reactions = (xt_reaction **)realloc(other->reactions, sizeof(xt_reaction *) * other->reaction_capacity);
|
|
@@ -156,6 +160,16 @@ static xt_value xt_promise_run_reactions(void) {
|
|
|
156
160
|
while (xt_microtask_head < xt_microtask_count) {
|
|
157
161
|
xt_reaction *reaction = xt_microtasks[xt_microtask_head++];
|
|
158
162
|
xt_promise *promise = reaction->promise;
|
|
163
|
+
if (reaction->async) {
|
|
164
|
+
/* A suspended `async` coroutine: resume it with the settled value. It
|
|
165
|
+
* runs until it completes or awaits another pending promise. */
|
|
166
|
+
void *async = reaction->async;
|
|
167
|
+
int rejected = promise->state == XT_PROMISE_REJECTED;
|
|
168
|
+
xt_value settled = promise->value;
|
|
169
|
+
free(reaction);
|
|
170
|
+
xt_async_wake(async, settled, rejected);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
159
173
|
xt_value handler = promise->state == XT_PROMISE_FULFILLED ? reaction->on_fulfilled : reaction->on_rejected;
|
|
160
174
|
if (XT_IS_FUNCTION(handler)) {
|
|
161
175
|
xt_value arg = promise->value;
|
|
@@ -181,16 +195,46 @@ void xt_drain_microtasks(void) {
|
|
|
181
195
|
}
|
|
182
196
|
}
|
|
183
197
|
|
|
184
|
-
|
|
198
|
+
/* -- helpers used by the async coroutine runtime -------------------------- */
|
|
199
|
+
|
|
200
|
+
xt_value xt_promise_create(void) { return xt_promise_value(xt_promise_new()); }
|
|
201
|
+
|
|
202
|
+
void xt_promise_fulfill(xt_value promise, xt_value value) {
|
|
203
|
+
xt_promise *target = xt_as_promise(promise);
|
|
204
|
+
if (target) xt_promise_resolve_into(target, value);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
void xt_promise_reject_value(xt_value promise, xt_value value) {
|
|
208
|
+
xt_promise *target = xt_as_promise(promise);
|
|
209
|
+
if (target) xt_promise_settle(target, XT_PROMISE_REJECTED, value);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
int xt_promise_state_of(xt_value value) {
|
|
185
213
|
xt_promise *promise = xt_as_promise(value);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
214
|
+
return promise ? promise->state : -1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
xt_value xt_promise_value_of(xt_value value) {
|
|
218
|
+
xt_promise *promise = xt_as_promise(value);
|
|
219
|
+
return promise ? promise->value : value;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
void xt_promise_add_async_waiter(xt_value promiseValue, void *async) {
|
|
223
|
+
xt_promise *promise = xt_as_promise(promiseValue);
|
|
224
|
+
if (!promise || promise->state != XT_PROMISE_PENDING) return;
|
|
225
|
+
xt_reaction *reaction = (xt_reaction *)malloc(sizeof(xt_reaction));
|
|
226
|
+
if (!reaction) abort();
|
|
227
|
+
reaction->promise = promise;
|
|
228
|
+
reaction->on_fulfilled = XT_UNDEFINED;
|
|
229
|
+
reaction->on_rejected = XT_UNDEFINED;
|
|
230
|
+
reaction->result = NULL;
|
|
231
|
+
reaction->async = async;
|
|
232
|
+
if (promise->reaction_count >= promise->reaction_capacity) {
|
|
233
|
+
promise->reaction_capacity = promise->reaction_capacity == 0 ? 4 : promise->reaction_capacity * 2;
|
|
234
|
+
promise->reactions = (xt_reaction **)realloc(promise->reactions, sizeof(xt_reaction *) * promise->reaction_capacity);
|
|
235
|
+
if (!promise->reactions) abort();
|
|
192
236
|
}
|
|
193
|
-
|
|
237
|
+
promise->reactions[promise->reaction_count++] = reaction;
|
|
194
238
|
}
|
|
195
239
|
|
|
196
240
|
/* -- C-callable resolve / reject ------------------------------------------ */
|
|
@@ -316,6 +360,7 @@ xt_value xt_promise_method(xt_value target, const char *method, int32_t argc, xt
|
|
|
316
360
|
reaction->on_fulfilled = xt_arg_at(argc, argv, 0);
|
|
317
361
|
reaction->on_rejected = xt_arg_at(argc, argv, 1);
|
|
318
362
|
reaction->result = result;
|
|
363
|
+
reaction->async = NULL;
|
|
319
364
|
if (promise->state == XT_PROMISE_PENDING) {
|
|
320
365
|
if (promise->reaction_count >= promise->reaction_capacity) {
|
|
321
366
|
promise->reaction_capacity = promise->reaction_capacity == 0 ? 4 : promise->reaction_capacity * 2;
|
|
@@ -40,6 +40,7 @@ static xt_value xt_string_method(xt_value target, const char *method, int32_t ar
|
|
|
40
40
|
*handled = 1;
|
|
41
41
|
if (strcmp(method, "charAt") == 0) return xt_string_char_at(target, xt_arg_at(argc, argv, 0));
|
|
42
42
|
if (strcmp(method, "charCodeAt") == 0) return xt_string_char_code_at(target, xt_arg_at(argc, argv, 0));
|
|
43
|
+
if (strcmp(method, "codePointAt") == 0) return xt_string_code_point_at_value(target, xt_arg_at(argc, argv, 0));
|
|
43
44
|
if (strcmp(method, "indexOf") == 0) return xt_string_index_of(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
|
|
44
45
|
if (strcmp(method, "includes") == 0) return xt_bool(xt_truthy(xt_ne(xt_string_index_of(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1)), xt_number(-1))));
|
|
45
46
|
if (strcmp(method, "slice") == 0) return xt_string_slice(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
|
|
@@ -51,8 +52,8 @@ static xt_value xt_string_method(xt_value target, const char *method, int32_t ar
|
|
|
51
52
|
if (strcmp(method, "trim") == 0) return xt_string_trim(target);
|
|
52
53
|
if (strcmp(method, "replace") == 0) return xt_string_replace(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
|
|
53
54
|
if (strcmp(method, "repeat") == 0) return xt_string_repeat(target, xt_arg_at(argc, argv, 0));
|
|
54
|
-
if (strcmp(method, "startsWith") == 0) return xt_string_starts_with(target, xt_arg_at(argc, argv, 0));
|
|
55
|
-
if (strcmp(method, "endsWith") == 0) return xt_string_ends_with(target, xt_arg_at(argc, argv, 0));
|
|
55
|
+
if (strcmp(method, "startsWith") == 0) return xt_string_starts_with(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
|
|
56
|
+
if (strcmp(method, "endsWith") == 0) return xt_string_ends_with(target, xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
|
|
56
57
|
if (strcmp(method, "concat") == 0) return xt_string_concat_args(target, argc, argv);
|
|
57
58
|
*handled = 0;
|
|
58
59
|
return XT_UNDEFINED;
|
|
@@ -286,11 +287,19 @@ xt_value xt_call_method(xt_value target, xt_value name, int32_t argc, xt_value *
|
|
|
286
287
|
if (kind != XT_OBJECT_KIND_OBJECT && kind != XT_OBJECT_KIND_FUNCTION) {
|
|
287
288
|
xt_value result = xt_ext_container_method(target, method, argc, argv, &handled);
|
|
288
289
|
if (handled) return result;
|
|
290
|
+
/* Containers keep their methods in dedicated C dispatch tables, but
|
|
291
|
+
* still inherit `Object.prototype` (`new Map().toString()`). */
|
|
292
|
+
xt_value inherited = xt_get(target, name);
|
|
293
|
+
if (XT_IS_FUNCTION(inherited)) return xt_call_with_this(inherited, target, argc, argv);
|
|
289
294
|
}
|
|
290
295
|
xt_value fn = xt_object_get(target, name);
|
|
291
296
|
if (XT_IS_FUNCTION(fn)) return xt_call_with_this(fn, target, argc, argv);
|
|
292
297
|
xt_value result = xt_ext_object_method(target, method, argc, argv, &handled);
|
|
293
298
|
if (handled) return result;
|
|
299
|
+
/* Inherited Object.prototype methods on containers without a property bag
|
|
300
|
+
* (`new Map().toString()`, ...). */
|
|
301
|
+
xt_value inherited = xt_get(target, name);
|
|
302
|
+
if (XT_IS_FUNCTION(inherited)) return xt_call_with_this(inherited, target, argc, argv);
|
|
294
303
|
{
|
|
295
304
|
char message[256];
|
|
296
305
|
snprintf(message, sizeof(message), "TypeError: object has no callable method '%s'", method);
|
|
@@ -333,6 +342,13 @@ xt_value xt_call_method(xt_value target, xt_value name, int32_t argc, xt_value *
|
|
|
333
342
|
}
|
|
334
343
|
xt_value fn = xt_object_get(target, name);
|
|
335
344
|
if (XT_IS_FUNCTION(fn)) return xt_call_with_this(fn, target, argc, argv);
|
|
345
|
+
/* Statics of a first-class built-in constructor (`const A = Array;
|
|
346
|
+
* A.isArray(x)`). */
|
|
347
|
+
xt_value builtinStatic = xt_builtin_static_value(target, name);
|
|
348
|
+
if (builtinStatic != XT_UNDEFINED) {
|
|
349
|
+
if (XT_IS_FUNCTION(builtinStatic)) return xt_call_with_this(builtinStatic, target, argc, argv);
|
|
350
|
+
return builtinStatic;
|
|
351
|
+
}
|
|
336
352
|
if (strcmp(method, "toString") == 0) {
|
|
337
353
|
/* Source text is not retained; report a native-style representation. */
|
|
338
354
|
xt_function *self = (xt_function *)XT_GET_PTR(target);
|