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.
Files changed (85) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +4 -3
  3. package/README.zh-CN.md +2 -2
  4. package/dist/src/codegen/generator/calls/access.js +6 -2
  5. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/closures.js +4 -0
  7. package/dist/src/codegen/generator/calls/closures.js.map +1 -1
  8. package/dist/src/codegen/generator/calls/invocation.js +4 -3
  9. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  10. package/dist/src/codegen/generator/expressions/primary.js +6 -1
  11. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  12. package/dist/src/codegen/generator/module.d.ts +0 -1
  13. package/dist/src/codegen/generator/module.js +10 -10
  14. package/dist/src/codegen/generator/module.js.map +1 -1
  15. package/dist/src/codegen/generator/state.js +4 -0
  16. package/dist/src/codegen/generator/state.js.map +1 -1
  17. package/dist/src/codegen/generator/statements/control-flow.js +1 -3
  18. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  19. package/dist/src/codegen/generator/tables.d.ts +9 -0
  20. package/dist/src/codegen/generator/tables.js +74 -2
  21. package/dist/src/codegen/generator/tables.js.map +1 -1
  22. package/dist/src/driver/compiler.d.ts +2 -2
  23. package/dist/src/driver/compiler.js +4 -1
  24. package/dist/src/driver/compiler.js.map +1 -1
  25. package/dist/tests/codegen/closures.test.js +8 -0
  26. package/dist/tests/codegen/closures.test.js.map +1 -1
  27. package/dist/tests/e2e/async-await.test.d.ts +10 -0
  28. package/dist/tests/e2e/async-await.test.js +247 -0
  29. package/dist/tests/e2e/async-await.test.js.map +1 -0
  30. package/dist/tests/e2e/runtime-coverage-globals.test.js +46 -0
  31. package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
  32. package/dist/tests/e2e/string-utf16.test.d.ts +6 -0
  33. package/dist/tests/e2e/string-utf16.test.js +133 -0
  34. package/dist/tests/e2e/string-utf16.test.js.map +1 -0
  35. package/doc/ai/contributing.md +9 -7
  36. package/doc/ai/extensions.md +3 -2
  37. package/doc/ai/language-support.md +11 -8
  38. package/doc/ai/troubleshooting.md +4 -3
  39. package/doc/ai/zh-CN/contributing.md +5 -5
  40. package/doc/ai/zh-CN/extensions.md +2 -1
  41. package/doc/ai/zh-CN/language-support.md +11 -8
  42. package/doc/ai/zh-CN/troubleshooting.md +3 -2
  43. package/doc/implemented.md +15 -12
  44. package/doc/unimplemented.md +11 -10
  45. package/doc/zh-CN/implemented.md +14 -11
  46. package/doc/zh-CN/unimplemented.md +11 -10
  47. package/package.json +1 -1
  48. package/runtime/rt.h +32 -0
  49. package/runtime/rt_internal.h +99 -10
  50. package/runtime/xt_alloc.c +6 -0
  51. package/runtime/xt_async.c +197 -0
  52. package/runtime/xt_builtins.c +40 -36
  53. package/runtime/xt_constructors.c +389 -0
  54. package/runtime/xt_containers/access.inc +47 -11
  55. package/runtime/xt_containers/arrays.inc +26 -2
  56. package/runtime/xt_containers/objects.inc +202 -5
  57. package/runtime/xt_coroutine.c +212 -0
  58. package/runtime/xt_generator.c +31 -162
  59. package/runtime/xt_io/inspect.inc +35 -2
  60. package/runtime/xt_promise.c +62 -17
  61. package/runtime/xt_stdlib/dispatch.inc +18 -2
  62. package/runtime/xt_stdlib/strings.inc +73 -16
  63. package/runtime/xt_stdlib2/array-statics.inc +3 -3
  64. package/runtime/xt_stdlib2/error.inc +11 -0
  65. package/runtime/xt_stdlib2/json.inc +44 -15
  66. package/runtime/xt_stdlib2/number-string-statics.inc +24 -15
  67. package/runtime/xt_stdlib2/objects.inc +1 -1
  68. package/runtime/xt_stdlib2/regexp-match.inc +2 -2
  69. package/runtime/xt_stdlib2/set.inc +6 -0
  70. package/runtime/xt_stdlib2/strings.inc +70 -40
  71. package/runtime/xt_typed_array/construction.inc +43 -2
  72. package/runtime/xt_typed_array.c +4 -0
  73. package/runtime/xt_values/constructors.inc +8 -0
  74. package/runtime/xt_values/strings.inc +19 -3
  75. package/runtime/xt_values/utf16.inc +340 -0
  76. package/runtime/xt_values.c +1 -0
  77. package/src/codegen/generator/calls/access.ts +7 -2
  78. package/src/codegen/generator/calls/closures.ts +3 -0
  79. package/src/codegen/generator/calls/invocation.ts +4 -3
  80. package/src/codegen/generator/expressions/primary.ts +6 -1
  81. package/src/codegen/generator/module.ts +10 -12
  82. package/src/codegen/generator/state.ts +4 -0
  83. package/src/codegen/generator/statements/control-flow.ts +1 -2
  84. package/src/codegen/generator/tables.ts +84 -2
  85. 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(XT_UNDEFINED); }
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(XT_UNDEFINED));
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
- xt_object *obj = xt_as_object(value);
228
- if (obj) return obj->prototype;
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
- for (uint32_t i = 0; i < s->length; i++) {
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
+ }