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
|
@@ -14,7 +14,149 @@ static void xt_object_reserve(xt_object *obj, uint32_t needed) {
|
|
|
14
14
|
obj->capacity = capacity;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
xt_value xt_object_new(void) { return xt_object_new_with_proto(
|
|
17
|
+
xt_value xt_object_new(void) { return xt_object_new_with_proto(xt_object_prototype()); }
|
|
18
|
+
|
|
19
|
+
/* ------------------------------------------------------------------------- */
|
|
20
|
+
/* Object.prototype */
|
|
21
|
+
/* ------------------------------------------------------------------------- */
|
|
22
|
+
|
|
23
|
+
/* `Object.prototype` is the root of the ordinary object prototype chain. Every
|
|
24
|
+
* plain object created by `xt_object_new` inherits from it, which is what makes
|
|
25
|
+
*
|
|
26
|
+
* Object.getPrototypeOf({}) === Object.prototype
|
|
27
|
+
* Object.prototype.hasOwnProperty.call(obj, key)
|
|
28
|
+
*
|
|
29
|
+
* hold. It is created lazily (and exactly once) with a null prototype, so the
|
|
30
|
+
* root itself terminates the chain. */
|
|
31
|
+
static xt_value xt_object_proto_root = XT_UNDEFINED;
|
|
32
|
+
static int xt_object_proto_ready = 0;
|
|
33
|
+
static xt_value xt_function_proto_root = XT_UNDEFINED;
|
|
34
|
+
static int xt_function_proto_ready = 0;
|
|
35
|
+
|
|
36
|
+
static const char *xt_object_tag_of(xt_value value) {
|
|
37
|
+
if (value == XT_UNDEFINED) return "Undefined";
|
|
38
|
+
if (value == XT_NULL) return "Null";
|
|
39
|
+
if (XT_IS_ARRAY(value)) return "Array";
|
|
40
|
+
if (XT_IS_FUNCTION(value)) return "Function";
|
|
41
|
+
if (XT_IS_STRING(value)) return "String";
|
|
42
|
+
if (XT_IS_BIGINT(value)) return "BigInt";
|
|
43
|
+
if (XT_IS_NUMBER(value)) return "Number";
|
|
44
|
+
if (value == XT_TRUE || value == XT_FALSE) return "Boolean";
|
|
45
|
+
if (XT_IS_OBJECT(value)) {
|
|
46
|
+
switch (((xt_object *)XT_GET_PTR(value))->header.kind) {
|
|
47
|
+
case XT_OBJECT_KIND_ERROR: return "Error";
|
|
48
|
+
case XT_OBJECT_KIND_MAP: return "Map";
|
|
49
|
+
case XT_OBJECT_KIND_SET: return "Set";
|
|
50
|
+
case XT_OBJECT_KIND_DATE: return "Date";
|
|
51
|
+
case XT_OBJECT_KIND_REGEXP: return "RegExp";
|
|
52
|
+
case XT_OBJECT_KIND_PROMISE: return "Promise";
|
|
53
|
+
case XT_OBJECT_KIND_SYMBOL: return "Symbol";
|
|
54
|
+
case XT_OBJECT_KIND_GENERATOR: return "Generator";
|
|
55
|
+
default: return "Object";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return "Object";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Backing trampoline for the methods on `Object.prototype`. The method name
|
|
62
|
+
* travels in environment slot 0; `this` is the receiver, exactly like a
|
|
63
|
+
* JavaScript method. */
|
|
64
|
+
static xt_value xt_object_proto_method(xt_value thisValue, xt_value env, int32_t argc,
|
|
65
|
+
xt_value *argv) {
|
|
66
|
+
xt_value nameValue = xt_closure_env(env, 0);
|
|
67
|
+
const char *name = xt_string_data(nameValue);
|
|
68
|
+
if (!name) return XT_UNDEFINED;
|
|
69
|
+
if (strcmp(name, "hasOwnProperty") == 0) {
|
|
70
|
+
if (thisValue == XT_UNDEFINED || thisValue == XT_NULL) {
|
|
71
|
+
xt_throw(xt_string_from_cstr("TypeError: Cannot convert undefined or null to object"));
|
|
72
|
+
}
|
|
73
|
+
return xt_object_has_own(thisValue, xt_arg_at(argc, argv, 0));
|
|
74
|
+
}
|
|
75
|
+
if (strcmp(name, "propertyIsEnumerable") == 0) {
|
|
76
|
+
if (thisValue == XT_UNDEFINED || thisValue == XT_NULL) {
|
|
77
|
+
xt_throw(xt_string_from_cstr("TypeError: Cannot convert undefined or null to object"));
|
|
78
|
+
}
|
|
79
|
+
/* Enumerability is not tracked; every own property is enumerable. */
|
|
80
|
+
return xt_object_has_own(thisValue, xt_arg_at(argc, argv, 0));
|
|
81
|
+
}
|
|
82
|
+
if (strcmp(name, "toString") == 0) {
|
|
83
|
+
char buffer[64];
|
|
84
|
+
snprintf(buffer, sizeof(buffer), "[object %s]", xt_object_tag_of(thisValue));
|
|
85
|
+
return xt_string_from_cstr(buffer);
|
|
86
|
+
}
|
|
87
|
+
if (strcmp(name, "valueOf") == 0) return thisValue;
|
|
88
|
+
if (strcmp(name, "isPrototypeOf") == 0) {
|
|
89
|
+
xt_value cursor = xt_object_get_prototype(xt_arg_at(argc, argv, 0));
|
|
90
|
+
while (cursor != XT_UNDEFINED && cursor != XT_NULL) {
|
|
91
|
+
if (cursor == thisValue) return XT_TRUE;
|
|
92
|
+
cursor = xt_object_get_prototype(cursor);
|
|
93
|
+
}
|
|
94
|
+
return XT_FALSE;
|
|
95
|
+
}
|
|
96
|
+
if (strcmp(name, "__proto__") == 0) return xt_object_get_prototype(thisValue);
|
|
97
|
+
return XT_UNDEFINED;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* `__proto__ = value`: ordinary assignment through the inherited setter. */
|
|
101
|
+
static xt_value xt_object_proto_setter(xt_value thisValue, xt_value env, int32_t argc,
|
|
102
|
+
xt_value *argv) {
|
|
103
|
+
(void)env;
|
|
104
|
+
(void)argc;
|
|
105
|
+
xt_value proto = xt_arg_at(argc, argv, 0);
|
|
106
|
+
if (proto == XT_NULL || XT_IS_OBJECT(proto) || XT_IS_ARRAY(proto) || XT_IS_FUNCTION(proto)) {
|
|
107
|
+
xt_object_set_prototype(thisValue, proto);
|
|
108
|
+
}
|
|
109
|
+
return XT_UNDEFINED;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static xt_value xt_object_proto_define(xt_value proto, const char *name, int32_t arity) {
|
|
113
|
+
xt_value env[1] = {xt_string_from_cstr(name)};
|
|
114
|
+
xt_value closure = xt_closure_new((void *)xt_object_proto_method, 1, env);
|
|
115
|
+
xt_function_set_metadata(closure, env[0], arity);
|
|
116
|
+
xt_object_set(proto, env[0], closure);
|
|
117
|
+
return closure;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
xt_value xt_function_prototype(void) {
|
|
121
|
+
if (xt_function_proto_ready) return xt_function_proto_root;
|
|
122
|
+
xt_function_proto_ready = 1;
|
|
123
|
+
xt_function_proto_root = xt_object_new_with_proto(xt_object_prototype());
|
|
124
|
+
xt_gc_add_root(&xt_function_proto_root);
|
|
125
|
+
/* `Array.constructor === Function` (every function inherits from here). The
|
|
126
|
+
* factory sees the ready flag, so this does not recurse. */
|
|
127
|
+
xt_value constructor = xt_builtin_constructor(xt_string_from_cstr("Function"));
|
|
128
|
+
if (constructor != XT_UNDEFINED) {
|
|
129
|
+
xt_object_set(xt_function_proto_root, xt_string_from_cstr("constructor"), constructor);
|
|
130
|
+
}
|
|
131
|
+
return xt_function_proto_root;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
xt_value xt_object_prototype(void) {
|
|
135
|
+
if (xt_object_proto_ready) return xt_object_proto_root;
|
|
136
|
+
xt_object_proto_ready = 1;
|
|
137
|
+
xt_object_proto_root = xt_object_new_with_proto(XT_NULL);
|
|
138
|
+
xt_gc_add_root(&xt_object_proto_root);
|
|
139
|
+
xt_object_proto_define(xt_object_proto_root, "hasOwnProperty", 1);
|
|
140
|
+
xt_object_proto_define(xt_object_proto_root, "propertyIsEnumerable", 1);
|
|
141
|
+
xt_object_proto_define(xt_object_proto_root, "isPrototypeOf", 1);
|
|
142
|
+
xt_object_proto_define(xt_object_proto_root, "toString", 0);
|
|
143
|
+
xt_object_proto_define(xt_object_proto_root, "valueOf", 0);
|
|
144
|
+
xt_value protoEnv[1] = {xt_string_from_cstr("__proto__")};
|
|
145
|
+
xt_value getter = xt_closure_new((void *)xt_object_proto_method, 1, protoEnv);
|
|
146
|
+
xt_value setter = xt_closure_new((void *)xt_object_proto_setter, 0, NULL);
|
|
147
|
+
xt_function_set_metadata(getter, protoEnv[0], 0);
|
|
148
|
+
xt_function_set_metadata(setter, protoEnv[0], 1);
|
|
149
|
+
xt_object_define_getter(xt_object_proto_root, protoEnv[0], getter);
|
|
150
|
+
xt_object_define_setter(xt_object_proto_root, protoEnv[0], setter);
|
|
151
|
+
/* `({}).constructor === Object`. The constructor factory calls back into
|
|
152
|
+
* `xt_object_prototype` while building the Object function, but the ready
|
|
153
|
+
* flag is already set and the root exists. */
|
|
154
|
+
xt_value constructor = xt_builtin_constructor(xt_string_from_cstr("Object"));
|
|
155
|
+
if (constructor != XT_UNDEFINED) {
|
|
156
|
+
xt_object_set(xt_object_proto_root, xt_string_from_cstr("constructor"), constructor);
|
|
157
|
+
}
|
|
158
|
+
return xt_object_proto_root;
|
|
159
|
+
}
|
|
18
160
|
|
|
19
161
|
xt_value xt_object_new_with_proto(xt_value proto) {
|
|
20
162
|
xt_object *obj = (xt_object *)xt_alloc(sizeof(xt_object), XT_OBJECT_KIND_OBJECT);
|
|
@@ -32,7 +174,7 @@ xt_object *xt_as_object(xt_value value) {
|
|
|
32
174
|
if (XT_IS_FUNCTION(value)) {
|
|
33
175
|
/* Functions carry their own property bag (used for static members). */
|
|
34
176
|
xt_function *fn = (xt_function *)XT_GET_PTR(value);
|
|
35
|
-
if (!fn->properties) fn->properties = (xt_object *)XT_GET_PTR(xt_object_new_with_proto(
|
|
177
|
+
if (!fn->properties) fn->properties = (xt_object *)XT_GET_PTR(xt_object_new_with_proto(xt_function_prototype()));
|
|
36
178
|
return fn->properties;
|
|
37
179
|
}
|
|
38
180
|
return NULL;
|
|
@@ -224,12 +366,38 @@ xt_value xt_object_define_setter(xt_value value, xt_value key, xt_value setter)
|
|
|
224
366
|
}
|
|
225
367
|
|
|
226
368
|
xt_value xt_object_get_prototype(xt_value value) {
|
|
227
|
-
|
|
228
|
-
if (
|
|
369
|
+
if (XT_IS_ARRAY(value)) return ((xt_array *)XT_GET_PTR(value))->prototype;
|
|
370
|
+
if (XT_IS_FUNCTION(value)) {
|
|
371
|
+
/* A function's [[Prototype]] is the prototype of its property bag
|
|
372
|
+
* (`Function.prototype` unless `Object.setPrototypeOf` changed it). */
|
|
373
|
+
xt_object *bag = xt_as_object(value);
|
|
374
|
+
return bag ? bag->prototype : XT_UNDEFINED;
|
|
375
|
+
}
|
|
376
|
+
if (XT_IS_OBJECT(value)) {
|
|
377
|
+
xt_object *obj = (xt_object *)XT_GET_PTR(value);
|
|
378
|
+
switch (obj->header.kind) {
|
|
379
|
+
case XT_OBJECT_KIND_OBJECT:
|
|
380
|
+
case XT_OBJECT_KIND_ERROR:
|
|
381
|
+
return obj->prototype;
|
|
382
|
+
case XT_OBJECT_KIND_SYMBOL:
|
|
383
|
+
case XT_OBJECT_KIND_MAP:
|
|
384
|
+
case XT_OBJECT_KIND_SET:
|
|
385
|
+
case XT_OBJECT_KIND_DATE:
|
|
386
|
+
case XT_OBJECT_KIND_REGEXP:
|
|
387
|
+
case XT_OBJECT_KIND_PROMISE:
|
|
388
|
+
return xt_builtin_prototype_for_kind(obj->header.kind);
|
|
389
|
+
default:
|
|
390
|
+
return XT_UNDEFINED;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
229
393
|
return XT_UNDEFINED;
|
|
230
394
|
}
|
|
231
395
|
|
|
232
396
|
xt_value xt_object_set_prototype(xt_value value, xt_value proto) {
|
|
397
|
+
if (XT_IS_ARRAY(value)) {
|
|
398
|
+
((xt_array *)XT_GET_PTR(value))->prototype = proto;
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
233
401
|
xt_object *obj = xt_as_object(value);
|
|
234
402
|
if (obj && !obj->frozen) obj->prototype = proto;
|
|
235
403
|
return value;
|
|
@@ -247,8 +415,33 @@ int xt_object_is_frozen(xt_value value) {
|
|
|
247
415
|
}
|
|
248
416
|
|
|
249
417
|
xt_value xt_object_has_own(xt_value value, xt_value key) {
|
|
418
|
+
if (XT_IS_ARRAY(value)) {
|
|
419
|
+
xt_array *array = (xt_array *)XT_GET_PTR(value);
|
|
420
|
+
xt_value propertyKey = xt_to_property_key(key);
|
|
421
|
+
if (XT_IS_STRING(propertyKey)) {
|
|
422
|
+
xt_string *k = xt_as_string(propertyKey);
|
|
423
|
+
if (k->length == 6 && memcmp(xt_string_bytes(k), "length", 6) == 0) return XT_TRUE;
|
|
424
|
+
uint32_t index = 0;
|
|
425
|
+
if (xt_key_is_array_index(k, &index)) return xt_bool(index < array->length);
|
|
426
|
+
}
|
|
427
|
+
return xt_bool(XT_IS_OBJECT(array->extra) &&
|
|
428
|
+
xt_object_find((xt_object *)XT_GET_PTR(array->extra), propertyKey));
|
|
429
|
+
}
|
|
430
|
+
if (XT_IS_STRING(value)) {
|
|
431
|
+
xt_string *string = xt_as_string(value);
|
|
432
|
+
if (xt_is_symbol(key)) return XT_FALSE;
|
|
433
|
+
xt_string *k = xt_as_string(xt_to_string(key));
|
|
434
|
+
if (k->length == 6 && memcmp(xt_string_bytes(k), "length", 6) == 0) return XT_TRUE;
|
|
435
|
+
uint32_t index = 0;
|
|
436
|
+
return xt_bool(xt_key_is_array_index(k, &index) && index < xt_string_utf16_length(string));
|
|
437
|
+
}
|
|
250
438
|
xt_object *obj = xt_as_object(value);
|
|
251
439
|
if (!obj) return XT_FALSE;
|
|
440
|
+
/* Map/Set/Date/RegExp/Promise/Symbol use their own struct layout and have no
|
|
441
|
+
* property bag; interpreting them as `xt_object` would read past the end. */
|
|
442
|
+
if (obj->header.kind != XT_OBJECT_KIND_OBJECT && obj->header.kind != XT_OBJECT_KIND_ERROR) {
|
|
443
|
+
return XT_FALSE;
|
|
444
|
+
}
|
|
252
445
|
return xt_bool(xt_object_find(obj, xt_to_property_key(key)) != NULL);
|
|
253
446
|
}
|
|
254
447
|
|
|
@@ -267,6 +460,9 @@ xt_value xt_object_from_entries(xt_value entries) {
|
|
|
267
460
|
xt_value xt_object_has(xt_value value, xt_value key) {
|
|
268
461
|
if (!XT_IS_OBJECT(value)) return XT_FALSE;
|
|
269
462
|
xt_object *obj = (xt_object *)XT_GET_PTR(value);
|
|
463
|
+
if (obj->header.kind != XT_OBJECT_KIND_OBJECT && obj->header.kind != XT_OBJECT_KIND_ERROR) {
|
|
464
|
+
return XT_FALSE;
|
|
465
|
+
}
|
|
270
466
|
return xt_bool(xt_object_find(obj, xt_to_property_key(key)) != NULL);
|
|
271
467
|
}
|
|
272
468
|
|
|
@@ -310,7 +506,8 @@ xt_value xt_object_keys(xt_value value) {
|
|
|
310
506
|
}
|
|
311
507
|
if (XT_IS_STRING(value)) {
|
|
312
508
|
xt_string *s = xt_as_string(value);
|
|
313
|
-
|
|
509
|
+
uint32_t units = xt_string_utf16_length(s);
|
|
510
|
+
for (uint32_t i = 0; i < units; i++) {
|
|
314
511
|
char buffer[32];
|
|
315
512
|
snprintf(buffer, sizeof(buffer), "%u", i);
|
|
316
513
|
xt_array_push(result, xt_string_from_cstr(buffer));
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc runtime — stackful coroutines.
|
|
3
|
+
*
|
|
4
|
+
* A coroutine owns a private C stack and switches to it with `ucontext`
|
|
5
|
+
* (POSIX) or fibers (Windows). Because the body keeps a real call stack,
|
|
6
|
+
* `try`/`finally`, nested loops and ordinary locals work across suspension
|
|
7
|
+
* points without a state-machine transform. Generators (`xt_generator.c`) and
|
|
8
|
+
* `async` functions (`xt_async.c`) are both built on this module.
|
|
9
|
+
*
|
|
10
|
+
* The coroutine's exception stack is swapped alongside its C stack, so a
|
|
11
|
+
* `throw` inside the body still targets the body's own `try` frames. Its stack
|
|
12
|
+
* is registered as an active GC region while it runs and scanned while it is
|
|
13
|
+
* suspended (through the owner's trace hook).
|
|
14
|
+
*
|
|
15
|
+
* The owner object decides *when* to enter/leave; this module only performs
|
|
16
|
+
* the switch, the exception-stack hand-off and the GC region bookkeeping.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
#define _XOPEN_SOURCE 700
|
|
20
|
+
|
|
21
|
+
#include "rt_internal.h"
|
|
22
|
+
|
|
23
|
+
#include <stdlib.h>
|
|
24
|
+
#include <string.h>
|
|
25
|
+
|
|
26
|
+
#if defined(__APPLE__)
|
|
27
|
+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
#ifdef _WIN32
|
|
31
|
+
#include <windows.h>
|
|
32
|
+
#else
|
|
33
|
+
#include <ucontext.h>
|
|
34
|
+
#endif
|
|
35
|
+
|
|
36
|
+
/* Generous enough for deep JavaScript recursion on top of a coroutine. The
|
|
37
|
+
* pages are committed lazily by the OS, so a suspended coroutine is cheap. */
|
|
38
|
+
#define XT_COROUTINE_STACK_SIZE ((size_t)1 << 20)
|
|
39
|
+
|
|
40
|
+
struct xt_coroutine {
|
|
41
|
+
void (*entry)(void *);
|
|
42
|
+
void *arg;
|
|
43
|
+
/* Private stack bounds, registered as an active stack region while the
|
|
44
|
+
* coroutine runs and scanned conservatively while it is suspended. */
|
|
45
|
+
char *stack;
|
|
46
|
+
size_t stack_size;
|
|
47
|
+
int stack_pushed;
|
|
48
|
+
int booted;
|
|
49
|
+
int started;
|
|
50
|
+
void *saved_try_top;
|
|
51
|
+
#ifdef _WIN32
|
|
52
|
+
void *fiber;
|
|
53
|
+
void *caller_fiber;
|
|
54
|
+
/* `SwitchToFiber` keeps the resumer's callee-saved registers in an opaque
|
|
55
|
+
* fiber context (POSIX stores them in `caller`), so snapshot them for the
|
|
56
|
+
* collector before switching away. */
|
|
57
|
+
jmp_buf caller_regs;
|
|
58
|
+
int has_caller_regs;
|
|
59
|
+
#else
|
|
60
|
+
ucontext_t ctx;
|
|
61
|
+
ucontext_t caller;
|
|
62
|
+
#endif
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/* Filled in just before a coroutine is first entered (POSIX has no portable
|
|
66
|
+
* way to pass a pointer to `makecontext`). */
|
|
67
|
+
static xt_coroutine *g_boot_coroutine = NULL;
|
|
68
|
+
#ifdef _WIN32
|
|
69
|
+
static void *g_main_fiber = NULL;
|
|
70
|
+
#else
|
|
71
|
+
/* Async functions are frequently short-lived, so keep a few private stacks
|
|
72
|
+
* around instead of asking the OS for a fresh 1 MiB mapping every call. The
|
|
73
|
+
* cached stacks hold no live values and are not part of the GC scan. */
|
|
74
|
+
#define XT_COROUTINE_STACK_CACHE 8
|
|
75
|
+
static char *g_stack_cache[XT_COROUTINE_STACK_CACHE];
|
|
76
|
+
static int g_stack_cache_count = 0;
|
|
77
|
+
#endif
|
|
78
|
+
|
|
79
|
+
xt_coroutine *xt_coroutine_create(void (*entry)(void *), void *arg) {
|
|
80
|
+
xt_coroutine *co = (xt_coroutine *)calloc(1, sizeof(xt_coroutine));
|
|
81
|
+
if (!co) abort();
|
|
82
|
+
co->entry = entry;
|
|
83
|
+
co->arg = arg;
|
|
84
|
+
return co;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#ifndef _WIN32
|
|
88
|
+
static void xt_coroutine_entry(void) {
|
|
89
|
+
xt_coroutine *co = g_boot_coroutine;
|
|
90
|
+
g_boot_coroutine = NULL;
|
|
91
|
+
co->entry(co->arg);
|
|
92
|
+
}
|
|
93
|
+
#else
|
|
94
|
+
static void CALLBACK xt_coroutine_entry(void *param) {
|
|
95
|
+
xt_coroutine *co = (xt_coroutine *)param;
|
|
96
|
+
/* The fiber stack is not observable from the outside; capture its bounds
|
|
97
|
+
* here (before the body allocates anything) so a collection during the body
|
|
98
|
+
* has a valid scan region. */
|
|
99
|
+
MEMORY_BASIC_INFORMATION info;
|
|
100
|
+
uintptr_t here = (uintptr_t)&info;
|
|
101
|
+
if (VirtualQuery((LPCVOID)here, &info, sizeof(info))) {
|
|
102
|
+
/* Use the *committed* region containing the current frame: a Windows
|
|
103
|
+
* stack reserves more than it commits, and `AllocationBase + RegionSize`
|
|
104
|
+
* can fall in uncommitted memory. `BaseAddress + RegionSize` is the
|
|
105
|
+
* stack top. */
|
|
106
|
+
co->stack = (char *)info.BaseAddress;
|
|
107
|
+
co->stack_size = info.RegionSize;
|
|
108
|
+
}
|
|
109
|
+
if (co->stack && co->stack_size && !co->stack_pushed) {
|
|
110
|
+
xt_gc_push_stack((uintptr_t)co->stack, (uintptr_t)co->stack + co->stack_size);
|
|
111
|
+
co->stack_pushed = 1;
|
|
112
|
+
}
|
|
113
|
+
co->entry(co->arg);
|
|
114
|
+
}
|
|
115
|
+
#endif
|
|
116
|
+
|
|
117
|
+
static void xt_coroutine_boot(xt_coroutine *co) {
|
|
118
|
+
#ifdef _WIN32
|
|
119
|
+
if (!g_main_fiber) g_main_fiber = ConvertThreadToFiber(NULL);
|
|
120
|
+
co->fiber = CreateFiber(XT_COROUTINE_STACK_SIZE, xt_coroutine_entry, co);
|
|
121
|
+
if (!co->fiber) abort();
|
|
122
|
+
#else
|
|
123
|
+
size_t size = XT_COROUTINE_STACK_SIZE;
|
|
124
|
+
char *stack = NULL;
|
|
125
|
+
if (g_stack_cache_count > 0) stack = g_stack_cache[--g_stack_cache_count];
|
|
126
|
+
if (!stack) stack = (char *)malloc(size);
|
|
127
|
+
if (!stack) abort();
|
|
128
|
+
co->stack = stack;
|
|
129
|
+
co->stack_size = size;
|
|
130
|
+
if (getcontext(&co->ctx) != 0) abort();
|
|
131
|
+
co->ctx.uc_stack.ss_sp = co->stack;
|
|
132
|
+
co->ctx.uc_stack.ss_size = size;
|
|
133
|
+
co->ctx.uc_link = NULL;
|
|
134
|
+
g_boot_coroutine = co;
|
|
135
|
+
makecontext(&co->ctx, xt_coroutine_entry, 0);
|
|
136
|
+
#endif
|
|
137
|
+
co->booted = 1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
void xt_coroutine_enter(xt_coroutine *co) {
|
|
141
|
+
if (!co->booted) xt_coroutine_boot(co);
|
|
142
|
+
/* Record where the current (calling) stack suspends before switching away.
|
|
143
|
+
* On Windows the first push happens inside the fiber once its stack bounds
|
|
144
|
+
* are known, so the caller's region must be marked suspended here, on the
|
|
145
|
+
* caller's own stack. */
|
|
146
|
+
xt_gc_suspend((uintptr_t)&co);
|
|
147
|
+
if (co->stack && co->stack_size && !co->stack_pushed) {
|
|
148
|
+
xt_gc_push_stack((uintptr_t)co->stack, (uintptr_t)co->stack + co->stack_size);
|
|
149
|
+
co->stack_pushed = 1;
|
|
150
|
+
}
|
|
151
|
+
void *caller_try = xt_try_mark();
|
|
152
|
+
xt_try_restore(co->saved_try_top);
|
|
153
|
+
#ifdef _WIN32
|
|
154
|
+
_setjmp(co->caller_regs);
|
|
155
|
+
co->has_caller_regs = 1;
|
|
156
|
+
co->caller_fiber = GetCurrentFiber();
|
|
157
|
+
SwitchToFiber(co->fiber);
|
|
158
|
+
#else
|
|
159
|
+
swapcontext(&co->caller, &co->ctx);
|
|
160
|
+
#endif
|
|
161
|
+
co->saved_try_top = xt_try_mark();
|
|
162
|
+
xt_try_restore(caller_try);
|
|
163
|
+
if (co->stack_pushed) {
|
|
164
|
+
xt_gc_pop_stack();
|
|
165
|
+
co->stack_pushed = 0;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
void xt_coroutine_leave(xt_coroutine *co) {
|
|
170
|
+
co->saved_try_top = xt_try_mark();
|
|
171
|
+
#ifdef _WIN32
|
|
172
|
+
SwitchToFiber(co->caller_fiber);
|
|
173
|
+
#else
|
|
174
|
+
swapcontext(&co->ctx, &co->caller);
|
|
175
|
+
#endif
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
void xt_coroutine_gc_trace(xt_coroutine *co) {
|
|
179
|
+
if (!co) return;
|
|
180
|
+
if (co->stack && co->stack_size) xt_gc_scan_region(co->stack, co->stack_size);
|
|
181
|
+
#ifndef _WIN32
|
|
182
|
+
/* Registers saved by `swapcontext` at the last suspension are not on the
|
|
183
|
+
* stack. */
|
|
184
|
+
xt_gc_scan_region(&co->ctx, sizeof(co->ctx));
|
|
185
|
+
#else
|
|
186
|
+
if (co->has_caller_regs) xt_gc_scan_region(&co->caller_regs, sizeof(co->caller_regs));
|
|
187
|
+
#endif
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
void xt_coroutine_release(xt_coroutine *co) {
|
|
191
|
+
if (!co) return;
|
|
192
|
+
#ifdef _WIN32
|
|
193
|
+
if (co->fiber) {
|
|
194
|
+
DeleteFiber(co->fiber);
|
|
195
|
+
co->fiber = NULL;
|
|
196
|
+
}
|
|
197
|
+
co->caller_fiber = NULL;
|
|
198
|
+
/* `DeleteFiber` releases the stack: never scan it again. */
|
|
199
|
+
co->stack = NULL;
|
|
200
|
+
co->stack_size = 0;
|
|
201
|
+
#else
|
|
202
|
+
if (co->stack && co->stack_size == XT_COROUTINE_STACK_SIZE &&
|
|
203
|
+
g_stack_cache_count < XT_COROUTINE_STACK_CACHE) {
|
|
204
|
+
g_stack_cache[g_stack_cache_count++] = co->stack;
|
|
205
|
+
} else {
|
|
206
|
+
free(co->stack);
|
|
207
|
+
}
|
|
208
|
+
co->stack = NULL;
|
|
209
|
+
co->stack_size = 0;
|
|
210
|
+
#endif
|
|
211
|
+
free(co);
|
|
212
|
+
}
|