xbintsc 0.5.20 → 0.5.21

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 (100) hide show
  1. package/AGENTS.md +5 -3
  2. package/dist/src/driver/compiler.js +1 -1
  3. package/dist/src/driver/compiler.js.map +1 -1
  4. package/dist/src/driver/runtime-objects.d.ts +1 -1
  5. package/dist/src/driver/runtime-objects.js +1 -0
  6. package/dist/src/driver/runtime-objects.js.map +1 -1
  7. package/dist/src/driver/version.d.ts +1 -1
  8. package/dist/src/driver/version.js +1 -1
  9. package/dist/src/extensions/node/fs/index.d.ts +4 -3
  10. package/dist/src/extensions/node/fs/index.js +12 -3
  11. package/dist/src/extensions/node/fs/index.js.map +1 -1
  12. package/dist/src/extensions/node/fs-promises/index.d.ts +5 -5
  13. package/dist/src/extensions/node/fs-promises/index.js +15 -6
  14. package/dist/src/extensions/node/fs-promises/index.js.map +1 -1
  15. package/dist/tests/e2e/async-await.test.js +1 -1
  16. package/dist/tests/e2e/async-fs.test.d.ts +9 -0
  17. package/dist/tests/e2e/async-fs.test.js +119 -0
  18. package/dist/tests/e2e/async-fs.test.js.map +1 -0
  19. package/dist/tests/e2e/fetch.test.js +24 -0
  20. package/dist/tests/e2e/fetch.test.js.map +1 -1
  21. package/dist/tests/e2e/gui-inline-svg.test.d.ts +11 -0
  22. package/dist/tests/e2e/gui-inline-svg.test.js +160 -0
  23. package/dist/tests/e2e/gui-inline-svg.test.js.map +1 -0
  24. package/doc/ai/contributing.md +2 -1
  25. package/doc/ai/extensions.md +4 -3
  26. package/doc/ai/language-support.md +2 -1
  27. package/doc/ai/zh-CN/contributing.md +1 -1
  28. package/doc/ai/zh-CN/extensions.md +4 -3
  29. package/doc/ai/zh-CN/language-support.md +2 -1
  30. package/doc/gui/api.md +8 -0
  31. package/doc/gui/known-issues.md +10 -9
  32. package/doc/gui/media.md +32 -3
  33. package/doc/gui.md +24 -2
  34. package/doc/implemented/async.md +16 -3
  35. package/doc/implemented/fetch.md +38 -6
  36. package/doc/implemented/node/fs-promises.md +19 -8
  37. package/doc/implemented/node.md +1 -1
  38. package/doc/implemented.md +1 -1
  39. package/doc/unimplemented/deviations.md +1 -1
  40. package/doc/unimplemented/node/async.md +6 -3
  41. package/doc/unimplemented/node/fs-promises.md +10 -6
  42. package/doc/unimplemented/node/fs.md +7 -8
  43. package/doc/unimplemented/node.md +2 -2
  44. package/doc/unimplemented/toolchain.md +1 -1
  45. package/doc/zh-CN/gui/api.md +7 -0
  46. package/doc/zh-CN/gui/known-issues.md +8 -7
  47. package/doc/zh-CN/gui/media.md +26 -2
  48. package/doc/zh-CN/gui.md +18 -1
  49. package/doc/zh-CN/implemented/async.md +13 -2
  50. package/doc/zh-CN/implemented/fetch.md +23 -4
  51. package/doc/zh-CN/implemented/node/fs-promises.md +6 -3
  52. package/doc/zh-CN/implemented/node.md +1 -1
  53. package/doc/zh-CN/implemented.md +1 -1
  54. package/doc/zh-CN/unimplemented/deviations.md +1 -1
  55. package/doc/zh-CN/unimplemented/node/async.md +6 -3
  56. package/doc/zh-CN/unimplemented/node/fs-promises.md +4 -4
  57. package/doc/zh-CN/unimplemented/node/fs.md +2 -2
  58. package/doc/zh-CN/unimplemented/node.md +2 -2
  59. package/doc/zh-CN/unimplemented/toolchain.md +1 -1
  60. package/package.json +1 -1
  61. package/runtime/ext_gui/inline_svg.cpp +219 -0
  62. package/runtime/ext_gui/inline_svg.h +41 -0
  63. package/runtime/ext_gui/layout.cpp +18 -0
  64. package/runtime/ext_gui/layout.h +7 -0
  65. package/runtime/ext_gui/layout_flow.cpp +4 -3
  66. package/runtime/ext_gui/paint.cpp +14 -0
  67. package/runtime/ext_gui/paint.h +5 -1
  68. package/runtime/ext_gui/renderer_image.cpp +53 -25
  69. package/runtime/ext_gui/style_ua.cpp +3 -0
  70. package/runtime/ext_gui/svg.cpp +122 -39
  71. package/runtime/ext_gui/svg.h +14 -3
  72. package/runtime/ext_gui/window.cpp +28 -0
  73. package/runtime/ext_node/fs/async_api.c +314 -0
  74. package/runtime/ext_node/fs/async_complete.c +99 -0
  75. package/runtime/ext_node/fs/async_fh.c +206 -0
  76. package/runtime/ext_node/fs/async_job.c +81 -0
  77. package/runtime/ext_node/fs/async_job.h +132 -0
  78. package/runtime/ext_node/fs/async_worker.c +534 -0
  79. package/runtime/ext_node/fs/async_worker_dir.c +439 -0
  80. package/runtime/ext_node/fs/async_worker_dir.h +33 -0
  81. package/runtime/ext_node/fs/async_worker_fd.c +74 -0
  82. package/runtime/ext_node/fs/dir.c +60 -15
  83. package/runtime/ext_node/fs/fs_common.h +9 -0
  84. package/runtime/ext_node/fs/fs_ops.c +18 -29
  85. package/runtime/ext_node/fs/parts/builtins.inc +34 -0
  86. package/runtime/ext_node/fs/parts/platform.inc +66 -0
  87. package/runtime/ext_node/fs/promises.c +44 -234
  88. package/runtime/rt_internal.h +2 -0
  89. package/runtime/xt_async_io.c +333 -0
  90. package/runtime/xt_async_io.h +47 -0
  91. package/runtime/xt_fetch/async.inc +295 -0
  92. package/runtime/xt_fetch/request.inc +6 -183
  93. package/runtime/xt_fetch/socket.inc +5 -4
  94. package/runtime/xt_fetch.c +6 -4
  95. package/runtime/xt_loop.c +44 -3
  96. package/src/driver/compiler.ts +1 -1
  97. package/src/driver/runtime-objects.ts +1 -0
  98. package/src/driver/version.ts +1 -1
  99. package/src/extensions/node/fs/index.ts +12 -3
  100. package/src/extensions/node/fs-promises/index.ts +15 -6
@@ -1,9 +1,10 @@
1
1
  /* -- request / response exchange ------------------------------------------ */
2
2
 
3
3
  /*
4
- * The actual `fetch` implementation. Because the runtime has no event loop,
5
- * this is a synchronous, blocking HTTP/1.1 exchange; the promise it returns is
6
- * already settled, which is all the current `await` machinery needs.
4
+ * Request serialisation, response framing and header parsing for `fetch`. The
5
+ * blocking exchange itself runs on an async I/O worker (see `async.inc`); these
6
+ * helpers are shared by the main thread (building the request, parsing the
7
+ * completed response) and the worker (deciding when the response is complete).
7
8
  */
8
9
 
9
10
  /** Index just past the blank line that terminates the response headers. */
@@ -204,183 +205,5 @@ static void xt_fetch_build_request(const char *method, const xt_fetch_url *url,
204
205
  if (body && body->length > 0) xt_fetch_buffer_append(out, body->data, body->length);
205
206
  }
206
207
 
207
- static xt_value xt_fetch_reject(const char *message) {
208
- xt_value argument = xt_string_from_cstr(message);
209
- return xt_promise_reject(xt_type_error_ctor(1, &argument));
210
- }
211
-
212
- xt_value xt_fetch(int32_t argc, xt_value *argv) {
213
- xt_value input = xt_arg_at(argc, argv, 0);
214
- xt_value init = xt_arg_at(argc, argv, 1);
215
- if (input == XT_UNDEFINED || input == XT_NULL) return xt_fetch_reject("Failed to parse URL from undefined");
216
-
217
- xt_value urlValue = input;
218
- if (XT_IS_OBJECT(input)) {
219
- xt_value candidate = xt_object_get_cstr(input, "url");
220
- if (XT_IS_STRING(candidate)) urlValue = candidate;
221
- }
222
- const char *urlText = xt_string_data(xt_to_string(urlValue));
223
- if (!urlText) return xt_fetch_reject("Failed to parse URL");
224
-
225
- char currentUrl[4096];
226
- snprintf(currentUrl, sizeof(currentUrl), "%s", urlText);
227
-
228
- char method[16];
229
- snprintf(method, sizeof(method), "GET");
230
- xt_value headersSource = XT_UNDEFINED;
231
- xt_value bodySource = XT_UNDEFINED;
232
- const char *redirectMode = "follow";
233
- if (XT_IS_OBJECT(init)) {
234
- xt_value methodValue = xt_object_get_cstr(init, "method");
235
- if (XT_IS_STRING(methodValue)) {
236
- const char *text = xt_string_data(methodValue);
237
- size_t i = 0;
238
- for (; text && text[i] && i + 1 < sizeof(method); i++) {
239
- char c = text[i];
240
- method[i] = (c >= 'a' && c <= 'z') ? (char)(c - 32) : c;
241
- }
242
- method[i] = '\0';
243
- }
244
- headersSource = xt_object_get_cstr(init, "headers");
245
- bodySource = xt_object_get_cstr(init, "body");
246
- xt_value redirectValue = xt_object_get_cstr(init, "redirect");
247
- if (XT_IS_STRING(redirectValue)) redirectMode = xt_string_data(redirectValue);
248
- }
249
-
250
- xt_fetch_buffer body;
251
- xt_fetch_buffer_init(&body);
252
- if (bodySource != XT_UNDEFINED && bodySource != XT_NULL) {
253
- xt_value text = xt_to_string(bodySource);
254
- const char *data = xt_string_data(text);
255
- if (data) xt_fetch_buffer_append(&body, data, (size_t)xt_string_length_value(text));
256
- }
257
-
258
- for (int redirects = 0; redirects <= 20; redirects++) {
259
- xt_fetch_url parsed;
260
- if (xt_fetch_parse_url(currentUrl, &parsed) != 0) {
261
- xt_fetch_buffer_free(&body);
262
- return xt_fetch_reject("Failed to parse URL");
263
- }
264
- if (parsed.secure) {
265
- xt_fetch_buffer_free(&body);
266
- return xt_fetch_reject("fetch failed: https is not supported");
267
- }
268
-
269
- int fd = xt_fetch_connect(&parsed);
270
- if (fd < 0) {
271
- xt_fetch_buffer_free(&body);
272
- return xt_fetch_reject("fetch failed");
273
- }
274
-
275
- xt_value requestHeaders = xt_headers_from_source(headersSource);
276
- xt_fetch_buffer request;
277
- xt_fetch_buffer_init(&request);
278
- xt_fetch_build_request(method, &parsed, requestHeaders, body.length > 0 ? &body : NULL, &request);
279
- if (xt_fetch_send_all(fd, request.data, request.length) != 0) {
280
- xt_fetch_close_socket(fd);
281
- xt_fetch_buffer_free(&request);
282
- xt_fetch_buffer_free(&body);
283
- return xt_fetch_reject("fetch failed");
284
- }
285
- xt_fetch_buffer_free(&request);
286
-
287
- xt_fetch_buffer raw;
288
- xt_fetch_buffer_init(&raw);
289
- char chunk[16384];
290
- for (;;) {
291
- int headEnd = xt_fetch_head_end(&raw);
292
- if (headEnd > 0) {
293
- int status = 0;
294
- char statusText[256];
295
- size_t headerStart = 0;
296
- if (xt_fetch_parse_status_line(raw.data, raw.length, &status, statusText, sizeof(statusText), &headerStart) == 0) {
297
- int stop = 0;
298
- if (status == 204 || status == 304 || (status >= 100 && status < 200) || strcmp(method, "HEAD") == 0) {
299
- stop = 1;
300
- } else {
301
- size_t contentLength = 0;
302
- int hasContentLength = 0;
303
- int chunked = 0;
304
- xt_fetch_scan_headers(raw.data, headerStart, (size_t)headEnd - 2, &contentLength, &hasContentLength, &chunked);
305
- if (chunked) {
306
- size_t decoded = 0;
307
- if (xt_fetch_chunked_complete(raw.data, raw.length, (size_t)headEnd, &decoded) == 1) stop = 1;
308
- } else if (hasContentLength && raw.length - (size_t)headEnd >= contentLength) {
309
- stop = 1;
310
- }
311
- }
312
- if (stop) break;
313
- }
314
- }
315
- int count = xt_fetch_recv(fd, chunk, sizeof(chunk));
316
- if (count > 0) {
317
- xt_fetch_buffer_append(&raw, chunk, (size_t)count);
318
- continue;
319
- }
320
- break;
321
- }
322
- xt_fetch_close_socket(fd);
323
-
324
- int headEnd = xt_fetch_head_end(&raw);
325
- int status = 0;
326
- char statusText[256];
327
- size_t headerStart = 0;
328
- if (headEnd <= 0 ||
329
- xt_fetch_parse_status_line(raw.data, raw.length, &status, statusText, sizeof(statusText), &headerStart) != 0) {
330
- xt_fetch_buffer_free(&raw);
331
- xt_fetch_buffer_free(&body);
332
- return xt_fetch_reject("fetch failed: invalid response");
333
- }
334
-
335
- xt_value responseHeaders = xt_headers_new();
336
- xt_headers_parse_block(responseHeaders, raw.data + headerStart, (size_t)headEnd - 2 - headerStart);
337
-
338
- int isRedirect = status == 301 || status == 302 || status == 303 || status == 307 || status == 308;
339
- if (isRedirect && strcmp(redirectMode, "error") == 0) {
340
- xt_fetch_buffer_free(&raw);
341
- xt_fetch_buffer_free(&body);
342
- return xt_fetch_reject("fetch failed: redirect not allowed");
343
- }
344
- if (isRedirect && strcmp(redirectMode, "manual") != 0 && redirects < 20) {
345
- const char *location = xt_headers_lookup(responseHeaders, "location");
346
- char resolved[4096];
347
- if (location && xt_fetch_resolve_location(&parsed, location, resolved, sizeof(resolved)) == 0) {
348
- if (status == 303 || ((status == 301 || status == 302) && strcmp(method, "POST") == 0)) {
349
- snprintf(method, sizeof(method), "GET");
350
- body.length = 0;
351
- }
352
- snprintf(currentUrl, sizeof(currentUrl), "%s", resolved);
353
- xt_fetch_buffer_free(&raw);
354
- continue;
355
- }
356
- }
357
-
358
- size_t contentLength = 0;
359
- int hasContentLength = 0;
360
- int chunked = 0;
361
- xt_fetch_scan_headers(raw.data, headerStart, (size_t)headEnd - 2, &contentLength, &hasContentLength, &chunked);
362
- xt_fetch_buffer responseBody;
363
- xt_fetch_buffer_init(&responseBody);
364
- if (status == 204 || status == 304 || (status >= 100 && status < 200) || strcmp(method, "HEAD") == 0) {
365
- /* No body by definition. */
366
- } else if (chunked) {
367
- xt_fetch_decode_chunked(raw.data, raw.length, (size_t)headEnd, &responseBody);
368
- } else if (hasContentLength) {
369
- size_t available = raw.length - (size_t)headEnd;
370
- size_t take = contentLength < available ? contentLength : available;
371
- xt_fetch_buffer_append(&responseBody, raw.data + headEnd, take);
372
- } else {
373
- xt_fetch_buffer_append(&responseBody, raw.data + headEnd, raw.length - (size_t)headEnd);
374
- }
375
-
376
- xt_value response = xt_response_new(responseHeaders, status, statusText, currentUrl,
377
- responseBody.data ? responseBody.data : "", responseBody.length);
378
- xt_fetch_buffer_free(&responseBody);
379
- xt_fetch_buffer_free(&raw);
380
- xt_fetch_buffer_free(&body);
381
- return xt_promise_resolve(response);
382
- }
383
-
384
- xt_fetch_buffer_free(&body);
385
- return xt_fetch_reject("fetch failed: too many redirects");
386
- }
208
+ /* The async exchange itself lives in `async.inc`; it reuses the parsers and
209
+ * request builder above. */
@@ -1,10 +1,11 @@
1
1
  /* -- TCP socket helpers --------------------------------------------------- */
2
2
 
3
3
  /*
4
- * `fetch` performs a conventional blocking request/response exchange: connect,
5
- * write the request, read until the response is complete (or the peer closes).
6
- * The socket carries a receive timeout so a stalled server cannot hang the
7
- * program forever; the timeout surfaces as a rejected promise.
4
+ * `fetch` performs a conventional blocking request/response exchange on an
5
+ * async I/O worker: connect, write the request, read until the response is
6
+ * complete (or the peer closes). The socket carries a receive timeout so a
7
+ * stalled server cannot pin a worker forever; the timeout surfaces as a
8
+ * rejected promise.
8
9
  */
9
10
  #define XT_FETCH_TIMEOUT_SECONDS 30
10
11
 
@@ -2,10 +2,11 @@
2
2
  * xbintsc runtime — the global `fetch` API.
3
3
  *
4
4
  * This is a small, dependency-free HTTP/1.1 client exposed as the platform
5
- * `fetch` global. The runtime has no event loop (promises are drained
6
- * synchronously by `await`), so the request is performed with blocking sockets
7
- * and the returned promise is already settled. TLS is not implemented, so only
8
- * `http://` URLs are accepted; `https://` rejects with a `TypeError`.
5
+ * `fetch` global. The blocking socket exchange runs on the async I/O pool
6
+ * (`xt_async_io.c`), so a request in flight does not stall the reactor: timers,
7
+ * sockets and other `await`s keep running until the response is dispatched on
8
+ * the main thread. TLS is not implemented, so only `http://` URLs are accepted;
9
+ * `https://` rejects with a `TypeError`.
9
10
  *
10
11
  * The implementation is grouped by concern under `xt_fetch/` and `#include`d
11
12
  * here as a single translation unit, following the other runtime modules.
@@ -37,3 +38,4 @@ typedef socklen_t xt_fetch_socklen_t;
37
38
  #include "xt_fetch/headers.inc"
38
39
  #include "xt_fetch/response.inc"
39
40
  #include "xt_fetch/request.inc"
41
+ #include "xt_fetch/async.inc"
package/runtime/xt_loop.c CHANGED
@@ -33,6 +33,10 @@
33
33
 
34
34
  #define XT_LOOP_MAX_WATCHERS 1024
35
35
 
36
+ /* Fallback wait bound while offloaded I/O is outstanding; the completion
37
+ * channel normally wakes `select` immediately. */
38
+ #define XT_ASYNC_POLL_MS 10
39
+
36
40
  typedef struct {
37
41
  int fd;
38
42
  int events;
@@ -294,6 +298,11 @@ xt_value xt_clear_immediate(int32_t argc, xt_value *argv) {
294
298
  return xt_clear_timeout(argc, argv);
295
299
  }
296
300
 
301
+ /* Reset to 1 while the reactor is dispatching handlers. Reusing a removed slot
302
+ * mid-dispatch would make the new watcher observe the current `fd_set`, so a
303
+ * registration from inside a callback appends instead. */
304
+ static int xt_loop_dispatching = 0;
305
+
297
306
  int xt_loop_add(int fd, int events, xt_io_handler handler, void *userdata) {
298
307
  if (fd < 0 || !handler) return -1;
299
308
  xt_loop_gc_register();
@@ -304,6 +313,21 @@ int xt_loop_add(int fd, int events, xt_io_handler handler, void *userdata) {
304
313
  existing->userdata = userdata;
305
314
  return 0;
306
315
  }
316
+ /* Reuse a slot left by a removed watcher (the async pool toggles its wakeup
317
+ * descriptor on and off, and sockets are opened and closed repeatedly). */
318
+ if (!xt_loop_dispatching) {
319
+ for (int i = 0; i < xt_loop_watcher_count; i++) {
320
+ xt_loop_watcher *slot = &xt_loop_watchers[i];
321
+ if (slot->active) continue;
322
+ slot->fd = fd;
323
+ slot->events = events;
324
+ slot->handler = handler;
325
+ slot->userdata = userdata;
326
+ slot->active = 1;
327
+ xt_loop_active_count++;
328
+ return 0;
329
+ }
330
+ }
307
331
  if (xt_loop_watcher_count >= XT_LOOP_MAX_WATCHERS) return -1;
308
332
  xt_loop_watcher *watcher = &xt_loop_watchers[xt_loop_watcher_count++];
309
333
  watcher->fd = fd;
@@ -337,7 +361,8 @@ void xt_loop_set_main(xt_main_loop_fn fn) { xt_loop_main = fn; }
337
361
  int xt_loop_poll(int timeout_ms) {
338
362
  int has_fds = xt_loop_active_count > 0;
339
363
  int64_t next_due = xt_timer_next_due();
340
- if (!has_fds && next_due < 0) {
364
+ int async_pending = xt_async_pending() > 0;
365
+ if (!has_fds && next_due < 0 && !async_pending) {
341
366
  xt_drain_microtasks();
342
367
  return 0;
343
368
  }
@@ -350,11 +375,15 @@ int xt_loop_poll(int timeout_ms) {
350
375
  if (delta > 100000000) delta = 100000000;
351
376
  if (wait_ms < 0 || delta < (int64_t)wait_ms) wait_ms = (int)delta;
352
377
  }
378
+ /* The completion channel normally wakes `select` immediately; polling every
379
+ * few milliseconds keeps progress even if it could not be registered. */
380
+ if (async_pending && (wait_ms < 0 || wait_ms > XT_ASYNC_POLL_MS)) wait_ms = XT_ASYNC_POLL_MS;
353
381
 
354
382
  /* Timers only: sleep, then run whatever is due. */
355
383
  if (!has_fds) {
356
384
  xt_loop_sleep_ms(wait_ms);
357
385
  xt_timer_run_due();
386
+ xt_async_poll();
358
387
  xt_drain_microtasks();
359
388
  return (int)(xt_loop_active_count + xt_timer_active_count());
360
389
  }
@@ -372,7 +401,9 @@ int xt_loop_poll(int timeout_ms) {
372
401
  if (watcher->fd > maxFd) maxFd = watcher->fd;
373
402
  }
374
403
  if (maxFd < 0) {
404
+ xt_loop_sleep_ms(wait_ms);
375
405
  xt_timer_run_due();
406
+ xt_async_poll();
376
407
  xt_drain_microtasks();
377
408
  return (int)(xt_loop_active_count + xt_timer_active_count());
378
409
  }
@@ -390,7 +421,13 @@ int xt_loop_poll(int timeout_ms) {
390
421
 
391
422
  if (ready > 0) {
392
423
  /* Collect the ready watchers first: handlers may add/remove descriptors. */
393
- for (int i = 0; i < xt_loop_watcher_count; i++) {
424
+ int previousDispatching = xt_loop_dispatching;
425
+ xt_loop_dispatching = 1;
426
+ /* Snapshot the table: a handler may register a new descriptor, and it must
427
+ * wait for the next iteration rather than being tested against this
428
+ * iteration's `fd_set`. */
429
+ int dispatchCount = xt_loop_watcher_count;
430
+ for (int i = 0; i < dispatchCount; i++) {
394
431
  xt_loop_watcher *watcher = &xt_loop_watchers[i];
395
432
  if (!watcher->active) continue;
396
433
  int events = 0;
@@ -398,9 +435,11 @@ int xt_loop_poll(int timeout_ms) {
398
435
  if ((watcher->events & XT_IO_WRITE) && FD_ISSET(watcher->fd, &writeSet)) events |= XT_IO_WRITE;
399
436
  if (events) watcher->handler(watcher->userdata, events);
400
437
  }
438
+ xt_loop_dispatching = previousDispatching;
401
439
  }
402
440
 
403
441
  xt_timer_run_due();
442
+ xt_async_poll();
404
443
  xt_drain_microtasks();
405
444
  return (int)(xt_loop_active_count + xt_timer_active_count());
406
445
  }
@@ -417,4 +456,6 @@ void xt_run_event_loop(void) {
417
456
  }
418
457
  }
419
458
 
420
- int xt_loop_has_work(void) { return xt_loop_active_count > 0 || xt_timer_active_count() > 0; }
459
+ int xt_loop_has_work(void) {
460
+ return xt_loop_active_count > 0 || xt_timer_active_count() > 0 || xt_async_pending() > 0;
461
+ }
@@ -259,7 +259,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
259
259
  outputPath,
260
260
  linkerFlags: [
261
261
  ...toolchain.linkerArgs,
262
- ...(process.platform === "win32" ? ["-lws2_32"] : ["-lm"]),
262
+ ...(process.platform === "win32" ? ["-lws2_32"] : ["-lm", "-pthread"]),
263
263
  ...registry.linkerFlags(),
264
264
  /* A GUI program is a Windows-subsystem executable: no console window is
265
265
  * created when it is launched. Console output (stdout/stderr) is lost in
@@ -27,6 +27,7 @@ export const RUNTIME_SOURCES = [
27
27
  "xt_promise.c",
28
28
  "xt_async.c",
29
29
  "xt_loop.c",
30
+ "xt_async_io.c",
30
31
  "xt_generator.c",
31
32
  "xt_coroutine.c",
32
33
  "xt_fetch.c",
@@ -9,4 +9,4 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export const COMPILER_VERSION = "0.5.20";
12
+ export const COMPILER_VERSION = "0.5.21";
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Node's `fs` module: synchronous file and directory operations backed by
3
- * `runtime/ext_node/fs`. Asynchronous callbacks are not supported (xbintsc has
4
- * no event loop), so the API is the `*Sync` surface plus `watch`/`watchFile`
5
- * (which return API-shaped watchers that never emit).
3
+ * `runtime/ext_node/fs`, plus the `fs.promises` namespace (implemented in
4
+ * `async_*.c` on top of the asynchronous I/O pool). The callback-style
5
+ * `readFile`/`writeFile` forms are not supported; `*Sync` and `fs.promises` are.
6
+ * `watch`/`watchFile` return API-shaped watchers that never emit.
6
7
  *
7
8
  * `fs.constants` and `fs.promises` are exposed as value getters, so both
8
9
  * `import { constants } from "fs"` and the `fs.constants.F_OK` property form
@@ -40,6 +41,14 @@ export const fsModule: NodeModule = {
40
41
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/watch.c"),
41
42
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/constants.c"),
42
43
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/streams.c"),
44
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_job.c"),
45
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker.c"),
46
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker_dir.c"),
47
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker_fd.c"),
48
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_complete.c"),
49
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_api.c"),
50
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_fh.c"),
51
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/promises.c"),
43
52
  ],
44
53
  builtins: () => builtins,
45
54
  exports: () => exports,
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Node's `fs/promises` module.
3
3
  *
4
- * There is no asynchronous I/O scheduler, so each function wraps the matching
5
- * synchronous `fs` implementation in an already-settled promise (rejected on
6
- * failure). `readFile`, `writeFile`, `mkdir`, `readdir`, `stat`, `rm`,
7
- * `open` (a `FileHandle`), ... are therefore importable from `fs/promises`.
8
- * `constants` is re-exported as a value getter.
4
+ * Each function submits a job to the asynchronous I/O pool: the blocking
5
+ * syscall runs on a worker thread and the promise settles on the event loop, so
6
+ * awaiting `readFile`, `writeFile`, `stat`, `rm`, `open` (a `FileHandle`), ...
7
+ * yields to timers and sockets while the I/O is in flight. `constants` is
8
+ * re-exported as a value getter.
9
9
  */
10
10
 
11
11
  import type { NodeModule } from "../module.js";
@@ -54,7 +54,7 @@ const exports: ModuleExports = {
54
54
  export const fsPromisesModule: NodeModule = {
55
55
  name: "fs/promises",
56
56
  runtimeSources: () => [
57
- // Reuse the synchronous fs implementations the promise wrappers delegate to.
57
+ // Synchronous builtins (used by `watch` and the sync module) plus the async job split.
58
58
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/read_file.c"),
59
59
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/write_file.c"),
60
60
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/fs_ops.c"),
@@ -63,6 +63,15 @@ export const fsPromisesModule: NodeModule = {
63
63
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/link_ops.c"),
64
64
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/copy_ops.c"),
65
65
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/constants.c"),
66
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/dir.c"),
67
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/watch.c"),
68
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_job.c"),
69
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker.c"),
70
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker_dir.c"),
71
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_worker_fd.c"),
72
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_complete.c"),
73
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_api.c"),
74
+ resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/async_fh.c"),
66
75
  resolveFrom(import.meta.url, "../../../../runtime/ext_node/fs/promises.c"),
67
76
  ],
68
77
  builtins: () => builtins,