xbintsc 0.5.52 → 0.6.9
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/README.md +8 -2
- package/README.zh-CN.md +9 -3
- package/dist/scripts/fetch-tarball.d.ts +28 -0
- package/dist/scripts/fetch-tarball.js +57 -0
- package/dist/scripts/fetch-tarball.js.map +1 -0
- package/dist/scripts/gui-build-options.d.ts +77 -0
- package/dist/scripts/gui-build-options.js +134 -0
- package/dist/scripts/gui-build-options.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/hints.d.ts +6 -2
- package/dist/src/cli/hints.js +16 -2
- package/dist/src/cli/hints.js.map +1 -1
- package/dist/src/cli/main.js +9 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/driver/app-artifacts.d.ts +2 -0
- package/dist/src/driver/app-artifacts.js +3 -3
- package/dist/src/driver/app-artifacts.js.map +1 -1
- package/dist/src/driver/compiler.js +147 -52
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/icon.d.ts +1 -1
- package/dist/src/driver/icon.js +3 -3
- package/dist/src/driver/icon.js.map +1 -1
- package/dist/src/driver/macos-sdk.d.ts +32 -0
- package/dist/src/driver/macos-sdk.js +50 -0
- package/dist/src/driver/macos-sdk.js.map +1 -0
- package/dist/src/driver/options.d.ts +6 -0
- package/dist/src/driver/platform-support.d.ts +45 -0
- package/dist/src/driver/platform-support.js +66 -0
- package/dist/src/driver/platform-support.js.map +1 -0
- package/dist/src/driver/prebuilt.d.ts +26 -0
- package/dist/src/driver/prebuilt.js +58 -0
- package/dist/src/driver/prebuilt.js.map +1 -0
- package/dist/src/driver/runtime-objects.d.ts +11 -0
- package/dist/src/driver/runtime-objects.js +14 -5
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/toolchain-provider.d.ts +8 -0
- package/dist/src/driver/toolchain-provider.js +8 -0
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +30 -1
- package/dist/src/driver/toolchain.js +88 -8
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/driver/version.js.map +1 -1
- package/dist/src/driver/win-entry.d.ts +1 -1
- package/dist/src/driver/win-entry.js +4 -4
- package/dist/src/driver/win-entry.js.map +1 -1
- package/dist/src/extensions/gui/html.d.ts +10 -7
- package/dist/src/extensions/gui/html.js +53 -29
- package/dist/src/extensions/gui/html.js.map +1 -1
- package/dist/src/extensions/node/child_process/index.d.ts +4 -3
- package/dist/src/extensions/node/child_process/index.js +4 -3
- package/dist/src/extensions/node/child_process/index.js.map +1 -1
- package/dist/tests/cli/hints.test.js +9 -1
- package/dist/tests/cli/hints.test.js.map +1 -1
- package/dist/tests/cli/main.test.js +9 -0
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/driver/compiler.test.js +107 -0
- package/dist/tests/driver/compiler.test.js.map +1 -1
- package/dist/tests/driver/cxx-compile-flags.test.d.ts +1 -0
- package/dist/tests/driver/cxx-compile-flags.test.js +24 -0
- package/dist/tests/driver/cxx-compile-flags.test.js.map +1 -0
- package/dist/tests/driver/macos-sdk.test.d.ts +1 -0
- package/dist/tests/driver/macos-sdk.test.js +83 -0
- package/dist/tests/driver/macos-sdk.test.js.map +1 -0
- package/dist/tests/driver/platform-support.test.d.ts +1 -0
- package/dist/tests/driver/platform-support.test.js +41 -0
- package/dist/tests/driver/platform-support.test.js.map +1 -0
- package/dist/tests/driver/prebuilt.test.d.ts +1 -0
- package/dist/tests/driver/prebuilt.test.js +44 -0
- package/dist/tests/driver/prebuilt.test.js.map +1 -0
- package/dist/tests/driver/toolchain-provider.test.js +24 -0
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +46 -2
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/async-await.test.js +4 -2
- package/dist/tests/e2e/async-await.test.js.map +1 -1
- package/dist/tests/e2e/compile-node.test.js +21 -0
- package/dist/tests/e2e/compile-node.test.js.map +1 -1
- package/dist/tests/e2e/fetch.test.js +34 -6
- package/dist/tests/e2e/fetch.test.js.map +1 -1
- package/dist/tests/e2e/gui-example-pelican3d.test.d.ts +10 -0
- package/dist/tests/e2e/gui-example-pelican3d.test.js +94 -0
- package/dist/tests/e2e/gui-example-pelican3d.test.js.map +1 -0
- package/dist/tests/e2e/gui-script.test.js +48 -0
- package/dist/tests/e2e/gui-script.test.js.map +1 -1
- package/dist/tests/e2e/unhandled-rejection.test.d.ts +14 -0
- package/dist/tests/e2e/unhandled-rejection.test.js +107 -0
- package/dist/tests/e2e/unhandled-rejection.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.js +44 -0
- package/dist/tests/extensions/gui.test.js.map +1 -1
- package/dist/tests/scripts/fetch-tarball.test.d.ts +1 -0
- package/dist/tests/scripts/fetch-tarball.test.js +70 -0
- package/dist/tests/scripts/fetch-tarball.test.js.map +1 -0
- package/doc/ai/cli.md +6 -0
- package/doc/ai/language-support.md +1 -0
- package/doc/ai/troubleshooting.md +32 -2
- package/doc/ai/zh-CN/cli.md +5 -0
- package/doc/ai/zh-CN/language-support.md +1 -0
- package/doc/ai/zh-CN/troubleshooting.md +27 -2
- package/doc/gui/build.md +7 -3
- package/doc/gui/dom.md +3 -1
- package/doc/gui/webgl.md +4 -0
- package/doc/gui-scripts.md +33 -17
- package/doc/gui.md +17 -1
- package/doc/implemented/async.md +1 -0
- package/doc/implemented/modules.md +9 -6
- package/doc/implemented/node/child-process.md +12 -4
- package/doc/implemented/runtime-core.md +1 -1
- package/doc/npm-package.md +23 -2
- package/doc/post/make-js-great-again.md +30 -0
- package/doc/requirements.md +36 -3
- package/doc/unimplemented/deviations.md +1 -0
- package/doc/zh-CN/gui/build.md +4 -2
- package/doc/zh-CN/gui/dom.md +3 -1
- package/doc/zh-CN/gui/webgl.md +1 -0
- package/doc/zh-CN/gui-scripts.md +23 -11
- package/doc/zh-CN/gui.md +11 -1
- package/doc/zh-CN/implemented/async.md +1 -0
- package/doc/zh-CN/implemented/modules.md +8 -4
- package/doc/zh-CN/implemented/node/child-process.md +10 -3
- package/doc/zh-CN/implemented/runtime-core.md +1 -1
- package/doc/zh-CN/npm-package.md +18 -1
- package/doc/zh-CN/post/make-js-great-again.md +32 -0
- package/doc/zh-CN/requirements.md +30 -2
- package/doc/zh-CN/unimplemented/deviations.md +1 -0
- package/package.json +7 -7
- package/runtime/ext_node/child_process/child_process.c +149 -7
- package/runtime/rt.h +4 -0
- package/runtime/xt_async.c +4 -0
- package/runtime/xt_generator.c +2 -0
- package/runtime/xt_loop.c +8 -4
- package/runtime/xt_promise.c +103 -0
- package/scripts/build-gui.ts +17 -5
- package/scripts/build-media.ts +89 -10
- package/scripts/build-runtime.ts +7 -2
- package/scripts/fetch-tarball.ts +71 -0
- package/scripts/gui-build-cache.ts +28 -0
- package/scripts/gui-build-options.ts +26 -2
- package/scripts/package-release.ts +8 -0
- package/src/cli/hints.ts +19 -2
- package/src/cli/main.ts +11 -0
- package/src/driver/app-artifacts.ts +5 -3
- package/src/driver/compiler.ts +167 -56
- package/src/driver/icon.ts +3 -2
- package/src/driver/macos-sdk.ts +52 -0
- package/src/driver/options.ts +6 -0
- package/src/driver/platform-support.ts +69 -0
- package/src/driver/prebuilt.ts +61 -0
- package/src/driver/runtime-objects.ts +26 -5
- package/src/driver/toolchain-provider.ts +11 -0
- package/src/driver/toolchain.ts +105 -9
- package/src/driver/version.ts +1 -1
- package/src/driver/win-entry.ts +4 -3
- package/src/extensions/gui/html.ts +61 -31
- package/src/extensions/node/child_process/index.ts +4 -3
|
@@ -21,10 +21,14 @@
|
|
|
21
21
|
|
|
22
22
|
## 延迟求值
|
|
23
23
|
|
|
24
|
-
- `gui` 的 `.html` 资产加载器用合成的 `__xt_deferred_module`
|
|
25
|
-
`<script src>` import。bundler
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
- `gui` 的 `.html` 资产加载器用合成的 `__xt_deferred_module` 绑定标记内联
|
|
25
|
+
`<script>` 主体与 `<script src>` 文件的相对 import。bundler 会把该模块及其
|
|
26
|
+
本地 import 图包装成返回各自导出的函数,生成的脚本在 `loadHTML` 之后调用它们
|
|
27
|
+
(`__xt_defer` 对每个模块做记忆化)。这与浏览器的模块脚本一致:文档就绪后才
|
|
28
|
+
求值 import 图。
|
|
29
|
+
- 两种脚本写法使用同一套拆分逻辑:顶层 `import`/`export … from` 语句被提升进
|
|
30
|
+
生成模块,`export` 修饰符被去掉,因此裸说明符或 `node:` 说明符与普通模块
|
|
31
|
+
import 一样链接。
|
|
28
32
|
- 同时被普通(非标记)import 引用的模块保持立即求值,并获得一个返回已初始化
|
|
29
33
|
导出的薄包装。
|
|
30
34
|
- 包装体是同步的:延迟脚本模块中的顶层 `await` 不受支持。
|
|
@@ -5,16 +5,23 @@
|
|
|
5
5
|
位置:`src/extensions/node/child_process/index.ts`、`runtime/ext_node/child_process/child_process.c`
|
|
6
6
|
|
|
7
7
|
`spawnSync(command, args[, options])` 运行一个程序直到结束,返回
|
|
8
|
-
`{ status, stdout, stderr }`。`options.cwd`
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
`{ status, stdout, stderr }`。`options.cwd` 设置子进程工作目录,`options.env`
|
|
9
|
+
替换子进程的环境变量,`options.stdio: "inherit"` 让子进程共用父进程的
|
|
10
|
+
stdout/stderr(`xbintsc run` 用它把被编译程序的输出实时透传),否则
|
|
11
|
+
stdout/stderr 会作为 UTF-8 字符串捕获。
|
|
11
12
|
|
|
12
13
|
| 选项 | 说明 |
|
|
13
14
|
| --- | --- |
|
|
14
15
|
| `cwd` | 子进程的工作目录 |
|
|
16
|
+
| `env` | 子进程的完整环境变量;不传则继承父进程,想扩展就展开 `process.env` |
|
|
15
17
|
| `stdio: "inherit"` | 共用父进程的 stdout/stderr,而不是捕获 |
|
|
16
18
|
| `encoding` | 接受但忽略(输出始终按 UTF-8 解码) |
|
|
17
19
|
|
|
20
|
+
`env` 遵循 Node 的替换语义:传了对象,它就是子进程的完整环境,调用方没列出的
|
|
21
|
+
变量在子进程里不存在。这正是调用方能把特定的 `SDKROOT`、`INCLUDE`、`LIB` 交给
|
|
22
|
+
工具链、又不泄漏自身 shell 环境的原因;自举出来的编译器跑的就是这段代码,macOS
|
|
23
|
+
的 SDK 修复也依赖它。
|
|
24
|
+
|
|
18
25
|
Windows 上子进程通过 `CreateProcess` 启动,stdout/stderr 由读线程经匿名管道持续排空。
|
|
19
26
|
不使用临时文件,因此受限的 `%TEMP%` 不会让 `spawnSync` 失败,输出超过管道缓冲区时
|
|
20
27
|
也不会死锁。
|
|
@@ -27,5 +27,5 @@
|
|
|
27
27
|
- 运算符辅助:`xt_in`(`in`)、`xt_delete`(`delete`)、`xt_rest_args`(剩余参数 / `arguments`)。
|
|
28
28
|
- Box:`box_new/get/set`(用于闭包捕获变量)。
|
|
29
29
|
- 函数与闭包:`arg`、`closure_new/call/env/arity`。
|
|
30
|
-
- 异常:`xt_try_enter` / `xt_try_exception` / `xt_try_leave` 维护 `_setjmp` 帧栈;`xt_throw` 在存在帧时长跳到最近 `try`,否则打印 `Uncaught ...`
|
|
30
|
+
- 异常:`xt_try_enter` / `xt_try_exception` / `xt_try_leave` 维护 `_setjmp` 帧栈;`xt_throw` 在存在帧时长跳到最近 `try`,否则打印 `Uncaught ...` 后退出。无人观察的 promise rejection 同样是致命的:事件循环排空后 `xt_promise_report_unhandled` 会打印 `Unhandled promise rejection` 与原因,并以状态码 1 退出(见[异步](./async.md))。codegen 还会维护一条影子调用栈(函数名、源文件、行号):`Error.stack` 用它生成堆栈(见[标准库](./stdlib.md)),原生崩溃时致命信号处理器也会打印同一组帧。`--debug` 还会生成 `!DICompileUnit` / `!DISubprogram` / `!DILocation` 元数据,并用 `-g -fno-omit-frame-pointer` 编译,因此 gdb/lldb/WinDbg 可以把指令映射回源码行。
|
|
31
31
|
- 对象 / 函数:`xt_object` 带原型链,`xt_function` 带属性包(静态成员与 `prototype`);`xt_new`(实例化)、`xt_instance_of`(原型链)、`xt_object_freeze/is_frozen/from_entries`;`xt_constructors.c` 保存一等公民构造器缓存、构造器原型与静态值分派。
|
package/doc/zh-CN/npm-package.md
CHANGED
|
@@ -201,8 +201,24 @@ Hello from xbintsc
|
|
|
201
201
|
(不一定是配置文件所在目录);可以用 `xbintsc_CACHE_DIR` 改到别处。这种回退构建
|
|
202
202
|
会明显比后续构建慢。
|
|
203
203
|
|
|
204
|
+
预编译归档由发布机的工具链生成,面向
|
|
205
|
+
[使用前置要求](./requirements.md#支持的平台)中的下限(macOS 11.0、Windows 11、
|
|
206
|
+
glibc 2.39)。如果它比本机的工具链新——例如更新的 Xcode Command Line
|
|
207
|
+
Tools/SDK 或 Visual Studio C++ 生成工具——链接器可能拒绝该归档,报未定义符号
|
|
208
|
+
(`LNK2038`、`LNK2019`、`Undefined symbols`、`undefined reference`)或
|
|
209
|
+
`was built for newer macOS version` 警告。此时驱动会**自动**用包内自带的源码重新
|
|
210
|
+
编译 `core`/`ext_node` 并重试链接,所以归档版本不匹配只会多花时间,不会让构建失败:
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
xbintsc: the prebuilt runtime archive does not match this toolchain (…); compiling the C runtime from source
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
这个回退不会被记住:升级工具链之后,后续构建仍会重新尝试预编译归档。随包的
|
|
217
|
+
`gui.a`/`media.a` 是例外——它们内含 SDL3/FFmpeg,npm 包里没有这些源码,无法这样
|
|
218
|
+
重编。如果冲突的是它们,请使用与本机工具链匹配的发布包,或在源码检出中构建。
|
|
219
|
+
|
|
204
220
|
如果确实想忽略平台包归档、改从主包自带的源码编译 C 运行时(例如修改了
|
|
205
|
-
`runtime
|
|
221
|
+
`runtime/`),可以设置:
|
|
206
222
|
|
|
207
223
|
```bash
|
|
208
224
|
xbintsc_PREFER_PREBUILT=0 npx xbintsc build src/main.ts --out build
|
|
@@ -408,6 +424,7 @@ npm 包通过可选平台包获得同一套预编译运行时,只有在跳过
|
|
|
408
424
|
| 第一次构建很慢 | 可选包 `@xbintsc/<os>-<arch>` 被跳过(或该宿主没有预编译包),正在编译并缓存运行时 C 源码。用 `npm ls @xbintsc/<os>-<arch>` 检查,并去掉 `--omit=optional` 重新安装;`--time` 可确认。 |
|
|
409
425
|
| 写 `.xbintsc/` 权限错误 | 用 `xbintsc_CACHE_DIR` 指向可写目录,或在可写目录中运行。 |
|
|
410
426
|
| `module 'fs' is provided by the 'node' extension` | 加 `--ext node`,或在配置里写 `"extensions": ["node"]`。 |
|
|
427
|
+
| 链接报 `LNK2038`、`LNK2019`、`Undefined symbols` 或 `was built for newer macOS version` | 预编译归档由更新的 Xcode/Visual Studio 工具链构建。驱动会自动改用运行时的 C 源码重试链接;若仍然失败,冲突的是 `gui.a`/`media.a`,无法从 npm 包重编——请使用匹配的发布包或源码检出。 |
|
|
411
428
|
| `GUI native library not found` / `media native library not found` | 携带 `gui.a`/`gui.lib` 或 `media.a`/`media.lib` 的可选平台包被跳过,或该宿主没有预编译的 GUI/media 归档(Windows arm64)。去掉 `--omit=optional` 重新安装,或使用 Linux/macOS/Windows x64 发布压缩包,或从源码检出构建。自动检测到的 `media` 特性缺归档只会 warning;`--feature media` 才会报错。 |
|
|
412
429
|
| `require("xbintsc")` 失败 | 包只提供 ESM;使用 `import`(或动态 `import()`)。 |
|
|
413
430
|
| Windows 链接时报缺少 MSVC 库 | 在 Native Tools 命令提示符中运行,安装 VS C++ 生成工具,并检查 `npx xbintsc doctor`。 |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 这可能是今年最让JS开发者激动的事情了
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
上面是我用JS+HTML+WebGL2做的鹈鹕3D骑自行车,FPS也非常不错。
|
|
6
|
+
|
|
7
|
+
你可能会说“这不是用Ai有手就行了么?”
|
|
8
|
+
|
|
9
|
+
但是,如果我说我把这上面用xbintsc编译成原生二进制,还不到10MB,还能在没有WebView环境上跑,还能支持Node.js和HTML混写,如下:
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<html>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="out">loading...</div>
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
import { readFileSync } from "fs";
|
|
17
|
+
|
|
18
|
+
const out = document.getElementById("out");
|
|
19
|
+
const world = readFileSync("data.txt", "utf8");
|
|
20
|
+
out.textContent = "hello " + world;
|
|
21
|
+
</script>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
要知道以前光chrome最少也得有100MB,用xbintsc编译产物10MB还不需要WebView环境,这可不仅仅代表桌面端开发体积极大减小和更加正确的实现路径的问题。
|
|
27
|
+
|
|
28
|
+
更加具有划时代意义的是,JS开发者在嵌入式设备跑你想要做的应用成为了可能。
|
|
29
|
+
|
|
30
|
+
[xbintsc开源地址](https://github.com/zy445566/xbintsc)
|
|
31
|
+
|
|
32
|
+
|
|
@@ -14,6 +14,27 @@ xbintsc 不再自带编译器,因此每个平台都需要安装一个兼容 cl
|
|
|
14
14
|
如果通过 npm 安装编译器而不是使用发布压缩包,Node.js 22+ 会直接运行编译器本身;
|
|
15
15
|
见[通过 npm 包使用 xbintsc](./npm-package.md)。
|
|
16
16
|
|
|
17
|
+
## 支持的平台
|
|
18
|
+
|
|
19
|
+
发布二进制和预编译运行库归档面向 xbintsc 支持的最低平台版本。CI 使用**各宿主仍
|
|
20
|
+
能产出发布包的最老工具链**构建它们,因此你机器上更新的 Xcode 或 Visual Studio 可以链接
|
|
21
|
+
这些产物;反过来(旧工具链链接新产物)才会出现 `LNK2038`、`Undefined symbols` 或
|
|
22
|
+
`was built for newer macOS version`。
|
|
23
|
+
|
|
24
|
+
| 平台 | 生成的二进制可运行于 | 构建基线(CI) |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| macOS x64 / arm64 | **macOS 11.0**(Big Sur)及以上 | 镜像中最老的 clang 16+ Xcode,`MACOSX_DEPLOYMENT_TARGET=11.0` |
|
|
27
|
+
| Windows x64 | **Windows 11** 及以上 | `windows-2025` 镜像自带的工具链(当前为 VS 2026) |
|
|
28
|
+
| Windows arm64 | **Windows 11** | `windows-11-arm` 镜像自带的工具链(当前为 VS 2026;没有更老的 arm64 镜像) |
|
|
29
|
+
| Linux x64 / arm64 | **glibc 2.39**(Ubuntu 24.04)及以上 | `ubuntu-24.04` 上的 clang 16 + lld 16 |
|
|
30
|
+
|
|
31
|
+
编译器工具链下限为:macOS 上的 Xcode 16(Apple clang 16)、Windows 上 `windows-2025`
|
|
32
|
+
镜像自带的 Visual Studio(当前为 VS 2026)、Linux 上的 clang 16。因此构建机的要求比生成二进制的
|
|
33
|
+
运行要求更高:Xcode 16 本身需要 macOS 14.5+,macOS 11–14 的构建机需要另外安装
|
|
34
|
+
LLVM 16+(例如 Homebrew 的 `llvm`)。`xbintsc doctor` 会打印当前宿主的支持下限。如果归档是用比本机更新的工具链构建的,`build` 会自动改用随包 C 源码
|
|
35
|
+
重试(见 [npm 包指南](./npm-package.md#首次构建与缓存));随包的 `gui.a`/`media.a`
|
|
36
|
+
无法这样重编,因为 SDL3/FFmpeg 的源码不在包里。
|
|
37
|
+
|
|
17
38
|
clang 15 及更旧版本会在任何程序的第一个字符串字面量处失败:
|
|
18
39
|
|
|
19
40
|
```
|
|
@@ -104,6 +125,10 @@ xcode-select -p # 打印当前开发者目录
|
|
|
104
125
|
xcrun --show-sdk-path # 打印 SDK 路径
|
|
105
126
|
```
|
|
106
127
|
|
|
128
|
+
xbintsc 会自己执行第二条命令,并把结果以 `-isysroot` 传给 clang,因此即使 clang
|
|
129
|
+
是用绝对路径选中的(CI 的基线 Xcode,或 `xbintsc_CLANG`),也仍能找到 SDK 里的
|
|
130
|
+
C 头文件与链接桩。自己设置 `SDKROOT` 优先,可用它固定某个 SDK。
|
|
131
|
+
|
|
107
132
|
## Linux —— 安装 clang 与 lld
|
|
108
133
|
|
|
109
134
|
常规的 glibc 发行版加上 `clang`、`lld` 即可:
|
|
@@ -129,8 +154,11 @@ winget install -e --id Microsoft.VisualStudio.2022.BuildTools `
|
|
|
129
154
|
--override "--quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --includeRecommended"
|
|
130
155
|
```
|
|
131
156
|
|
|
132
|
-
Visual Studio
|
|
133
|
-
19.1.5)就是 `scripts/setup-vs-toolchain.ps1`
|
|
157
|
+
Visual Studio 自带的 LLVM(`VC\Tools\Llvm\x64\bin\clang.exe`,当前 2022 安装中
|
|
158
|
+
为 19.1.5)就是 `scripts/setup-vs-toolchain.ps1` 选择的工具链。`xbintsc` 会探测
|
|
159
|
+
两种安装布局——按年份命名的旧布局(`2019`、`2022`)以及 Visual Studio 2026 及
|
|
160
|
+
之后按主版本号命名的布局(`18\Community`、`18\Insiders` 等)——因此更新版本的
|
|
161
|
+
安装无需设置 `xbintsc_CLANG` 也能被发现。独立安装的 LLVM
|
|
134
162
|
(`winget install -e --id LLVM.LLVM`)对 `xbintsc` 本身也可用,但 GUI/媒体
|
|
135
163
|
归档构建与 CI 都使用 Visual Studio 这一套,从而让运行时、预编译归档与最终
|
|
136
164
|
二进制出自同一条工具链。无论哪种,编译器必须以 MSVC ABI 为目标
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
| 内存管理 | 非移动标记-清扫回收器(在 `xt_alloc` 之后):显式根、子系统根提供者与保守 C 栈扫描。单线程、stop-the-world;无移动/压缩与分代 |
|
|
17
17
|
| 函数 `arity` / 调用参数个数 | 对本地可见函数的直接调用由默认[语义检查](../implemented/checker.md)校验(`TS4004`);通过属性、导入、展开实参或重新赋值后的绑定发起的调用不校验。`fn.length` 报告声明的 arity |
|
|
18
18
|
| `async` / `await` | 已在栈式协程上真实挂起;`await` 总会排入一次微任务(非 Promise 与已 settle 的 Promise 也一样),因此与 pending `.then` 回调的交错顺序符合规范。顶层 `await` 会阻塞事件循环等待,而不是让出模块主体。`fs/promises` 与 `fetch` 把阻塞工作 offload 到 4 线程池并在 reactor 上 settle。异步生成器可在 `await` 与 `yield` 处挂起,`for await...of` 每次迭代等待一步 |
|
|
19
|
+
| 未处理的 rejection | 在程序结束时才报告,而不是在当轮 tick 结束时:无人观察的 rejection 会在事件循环排空后打印并使进程以状态码 1 退出,因此长期运行的进程(如服务端)会一直运行到退出,稍后补挂的处理函数也仍会抑制该报告。Node 在微任务队列排空后立即抛出致命错误 |
|
|
19
20
|
| 异步生成器清理 | `for await...of` 提前退出时不会调用迭代器的 `return()`。异步生成器中的 `yield*` 只委托同步可迭代对象。在挂起的 `yield` 处调用 `.return()` 会直接完成而不执行 `finally`(与同步生成器相同的限制) |
|
|
20
21
|
| `super` | `super.x` / `super[x]` 从当前执行类的父级持有者读取(实例方法中是父原型,`static` 方法或静态块中是父构造函数),因此任意继承深度都正确 |
|
|
21
22
|
| `Error.stack` | 仅函数级帧:每帧为 `at 名称 (文件:行)`,行号取自函数声明(最内层帧会更新为错误构造处);没有列号,生成器 / `async` 函数体不产生帧(它们会切换栈) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xbintsc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -72,12 +72,12 @@
|
|
|
72
72
|
"vitest": "^2.1.8"
|
|
73
73
|
},
|
|
74
74
|
"optionalDependencies": {
|
|
75
|
-
"@xbintsc/linux-x64": "0.
|
|
76
|
-
"@xbintsc/linux-arm64": "0.
|
|
77
|
-
"@xbintsc/darwin-x64": "0.
|
|
78
|
-
"@xbintsc/darwin-arm64": "0.
|
|
79
|
-
"@xbintsc/win32-x64": "0.
|
|
80
|
-
"@xbintsc/win32-arm64": "0.
|
|
75
|
+
"@xbintsc/linux-x64": "0.6.9",
|
|
76
|
+
"@xbintsc/linux-arm64": "0.6.9",
|
|
77
|
+
"@xbintsc/darwin-x64": "0.6.9",
|
|
78
|
+
"@xbintsc/darwin-arm64": "0.6.9",
|
|
79
|
+
"@xbintsc/win32-x64": "0.6.9",
|
|
80
|
+
"@xbintsc/win32-arm64": "0.6.9"
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=22"
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* const result = spawnSync("clang", ["--version"], { cwd, encoding: "utf8" });
|
|
8
8
|
* result.status / result.stdout / result.stderr
|
|
9
9
|
*
|
|
10
|
-
* `options.cwd`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* `options.cwd` and `options.env` are honoured (the latter with Node's replace
|
|
11
|
+
* semantics); stdout/stderr are always captured as UTF-8 strings. On Windows
|
|
12
|
+
* the child runs under `CreateProcess` with anonymous pipes drained by reader
|
|
13
|
+
* threads (which also avoids pipe-buffer deadlocks).
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
#include "rt.h"
|
|
@@ -60,6 +61,58 @@ static int option_stdio_inherit(xt_value options) {
|
|
|
60
61
|
return stdio && XT_IS_STRING(stdio) && strcmp(xt_string_data(stdio), "inherit") == 0;
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/*
|
|
65
|
+
* `options.env` replaces the child's environment, exactly like Node: an absent
|
|
66
|
+
* option inherits the parent's environment, while a present one is the complete
|
|
67
|
+
* environment (spread `process.env` into it to extend rather than replace).
|
|
68
|
+
*
|
|
69
|
+
* The runtime used to ignore this option, which made every `spawnSync` inherit:
|
|
70
|
+
* a caller passing `SDKROOT` or `INCLUDE` silently got nothing, and a
|
|
71
|
+
* self-hosted compiler could not hand the macOS SDK to clang.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
/** The `env` object, or `XT_UNDEFINED` when the child should inherit. */
|
|
75
|
+
static xt_value option_env(xt_value options) {
|
|
76
|
+
if (!XT_IS_OBJECT(options)) return XT_UNDEFINED;
|
|
77
|
+
xt_value env = xt_object_get(options, xt_string_from_cstr("env"));
|
|
78
|
+
return XT_IS_OBJECT(env) ? env : XT_UNDEFINED;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The number of `KEY=VALUE` entries the environment object provides. */
|
|
82
|
+
static int32_t env_size(xt_value env) {
|
|
83
|
+
xt_value keys = xt_object_keys(env);
|
|
84
|
+
return XT_IS_ARRAY(keys) ? xt_array_size(keys) : 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
static void free_env_entries(char **entries, int32_t count) {
|
|
88
|
+
if (entries == NULL) return;
|
|
89
|
+
for (int32_t i = 0; i < count; i++) free(entries[i]);
|
|
90
|
+
free(entries);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `KEY=VALUE` entries for the environment object, in object key order. */
|
|
94
|
+
static char **env_entries(xt_value env, int32_t count) {
|
|
95
|
+
char **entries = (char **)calloc((size_t)(count > 0 ? count : 1), sizeof(char *));
|
|
96
|
+
if (entries == NULL) return NULL;
|
|
97
|
+
xt_value keys = xt_object_keys(env);
|
|
98
|
+
xt_value *items = XT_IS_ARRAY(keys) ? xt_array_items(keys) : NULL;
|
|
99
|
+
for (int32_t i = 0; i < count; i++) {
|
|
100
|
+
xt_value key = items[i];
|
|
101
|
+
xt_value raw = xt_object_get(env, key);
|
|
102
|
+
/* Node ignores `undefined` values instead of passing "undefined". */
|
|
103
|
+
if (XT_IS_UNDEFINED(raw)) continue;
|
|
104
|
+
const char *name = xt_string_data(xt_to_string(key));
|
|
105
|
+
const char *value = xt_string_data(xt_to_string(raw));
|
|
106
|
+
if (name == NULL || value == NULL) continue;
|
|
107
|
+
size_t length = strlen(name) + strlen(value) + 2;
|
|
108
|
+
char *entry = (char *)malloc(length);
|
|
109
|
+
if (entry == NULL) continue;
|
|
110
|
+
snprintf(entry, length, "%s=%s", name, value);
|
|
111
|
+
entries[i] = entry;
|
|
112
|
+
}
|
|
113
|
+
return entries;
|
|
114
|
+
}
|
|
115
|
+
|
|
63
116
|
static xt_value make_result(double status, const char *out, const char *err) {
|
|
64
117
|
xt_value result = xt_object_new();
|
|
65
118
|
xt_set(result, xt_string_from_cstr("status"), xt_number(status));
|
|
@@ -181,18 +234,60 @@ static char *build_command_line(char **child_argv, int count) {
|
|
|
181
234
|
return line;
|
|
182
235
|
}
|
|
183
236
|
|
|
237
|
+
/* `CreateProcessA` wants one ANSI block, entries sorted case-insensitively. */
|
|
238
|
+
static int compare_env_entries(const void *left, const void *right) {
|
|
239
|
+
const char *a = *(const char *const *)left;
|
|
240
|
+
const char *b = *(const char *const *)right;
|
|
241
|
+
if (a == NULL) return b == NULL ? 0 : 1;
|
|
242
|
+
if (b == NULL) return -1;
|
|
243
|
+
return _stricmp(a, b);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** The environment block for `CreateProcessA`, or NULL to inherit. */
|
|
247
|
+
static char *build_env_block(xt_value env) {
|
|
248
|
+
if (!XT_IS_OBJECT(env)) return NULL;
|
|
249
|
+
int32_t count = env_size(env);
|
|
250
|
+
char **entries = env_entries(env, count);
|
|
251
|
+
if (entries == NULL) return NULL;
|
|
252
|
+
qsort(entries, (size_t)count, sizeof(char *), compare_env_entries);
|
|
253
|
+
size_t total = 1;
|
|
254
|
+
for (int32_t i = 0; i < count; i++) total += entries[i] != NULL ? strlen(entries[i]) + 1 : 0;
|
|
255
|
+
char *block = (char *)malloc(total + 1);
|
|
256
|
+
if (block == NULL) {
|
|
257
|
+
free_env_entries(entries, count);
|
|
258
|
+
return NULL;
|
|
259
|
+
}
|
|
260
|
+
char *cursor = block;
|
|
261
|
+
for (int32_t i = 0; i < count; i++) {
|
|
262
|
+
if (entries[i] == NULL) continue;
|
|
263
|
+
size_t length = strlen(entries[i]) + 1;
|
|
264
|
+
memcpy(cursor, entries[i], length);
|
|
265
|
+
cursor += length;
|
|
266
|
+
}
|
|
267
|
+
/* The block ends with an extra terminator; an empty environment is "\0\0". */
|
|
268
|
+
*cursor++ = 0;
|
|
269
|
+
*cursor = 0;
|
|
270
|
+
free_env_entries(entries, count);
|
|
271
|
+
return block;
|
|
272
|
+
}
|
|
273
|
+
|
|
184
274
|
xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
185
275
|
const char *command = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : NULL;
|
|
186
276
|
if (!command) command = "";
|
|
187
277
|
xt_value args = argc > 1 ? argv[1] : XT_UNDEFINED;
|
|
188
278
|
const char *cwd = argc > 2 ? option_cwd(argv[2]) : NULL;
|
|
279
|
+
char *env_block = build_env_block(option_env(argc > 2 ? argv[2] : XT_UNDEFINED));
|
|
189
280
|
|
|
190
281
|
int count = 0;
|
|
191
282
|
char **child_argv = (char **)build_argv(command, args, &count);
|
|
192
|
-
if (!child_argv)
|
|
283
|
+
if (!child_argv) {
|
|
284
|
+
free(env_block);
|
|
285
|
+
return make_result(-1, "", "out of memory");
|
|
286
|
+
}
|
|
193
287
|
char *command_line = build_command_line(child_argv, count);
|
|
194
288
|
if (!command_line) {
|
|
195
289
|
free(child_argv);
|
|
290
|
+
free(env_block);
|
|
196
291
|
return make_result(-1, "", "out of memory");
|
|
197
292
|
}
|
|
198
293
|
|
|
@@ -204,9 +299,10 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
204
299
|
|
|
205
300
|
if (option_stdio_inherit(argc > 2 ? argv[2] : XT_UNDEFINED)) {
|
|
206
301
|
BOOL created = CreateProcessA(
|
|
207
|
-
NULL, command_line, NULL, NULL, TRUE, 0,
|
|
302
|
+
NULL, command_line, NULL, NULL, TRUE, 0, env_block, cwd, &startup, &process);
|
|
208
303
|
free(command_line);
|
|
209
304
|
free(child_argv);
|
|
305
|
+
free(env_block);
|
|
210
306
|
if (!created) return make_result(-1, "", "failed to start process");
|
|
211
307
|
WaitForSingleObject(process.hProcess, INFINITE);
|
|
212
308
|
DWORD exit_code = 0;
|
|
@@ -229,6 +325,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
229
325
|
close_capture_pipe(&err_pipe);
|
|
230
326
|
free(command_line);
|
|
231
327
|
free(child_argv);
|
|
328
|
+
free(env_block);
|
|
232
329
|
return make_result(-1, "", "could not create pipes");
|
|
233
330
|
}
|
|
234
331
|
startup.dwFlags = STARTF_USESTDHANDLES;
|
|
@@ -237,9 +334,10 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
237
334
|
startup.hStdError = err_write;
|
|
238
335
|
|
|
239
336
|
BOOL created = CreateProcessA(
|
|
240
|
-
NULL, command_line, NULL, NULL, TRUE, 0,
|
|
337
|
+
NULL, command_line, NULL, NULL, TRUE, 0, env_block, cwd, &startup, &process);
|
|
241
338
|
free(command_line);
|
|
242
339
|
free(child_argv);
|
|
340
|
+
free(env_block);
|
|
243
341
|
CloseHandle(out_write);
|
|
244
342
|
CloseHandle(err_write);
|
|
245
343
|
if (!created) {
|
|
@@ -285,30 +383,68 @@ static void append_chunk(char **buffer, size_t *length, size_t *capacity, const
|
|
|
285
383
|
(*buffer)[*length] = 0;
|
|
286
384
|
}
|
|
287
385
|
|
|
386
|
+
/** A NULL-terminated `envp` for `exec`, or NULL when the child inherits. */
|
|
387
|
+
static char **build_envp(xt_value env) {
|
|
388
|
+
if (!XT_IS_OBJECT(env)) return NULL;
|
|
389
|
+
int32_t count = env_size(env);
|
|
390
|
+
char **entries = env_entries(env, count);
|
|
391
|
+
if (entries == NULL) return NULL;
|
|
392
|
+
char **envp = (char **)calloc((size_t)count + 1, sizeof(char *));
|
|
393
|
+
if (envp == NULL) {
|
|
394
|
+
free_env_entries(entries, count);
|
|
395
|
+
return NULL;
|
|
396
|
+
}
|
|
397
|
+
int32_t kept = 0;
|
|
398
|
+
for (int32_t i = 0; i < count; i++) {
|
|
399
|
+
if (entries[i] != NULL) envp[kept++] = entries[i];
|
|
400
|
+
}
|
|
401
|
+
envp[kept] = NULL;
|
|
402
|
+
/* The strings now belong to `envp`; only the index array is released here. */
|
|
403
|
+
free(entries);
|
|
404
|
+
return envp;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Release an `envp` built by {@link build_envp}. */
|
|
408
|
+
static void free_envp(char **envp) {
|
|
409
|
+
if (envp == NULL) return;
|
|
410
|
+
for (char **entry = envp; *entry != NULL; entry++) free(*entry);
|
|
411
|
+
free(envp);
|
|
412
|
+
}
|
|
413
|
+
|
|
288
414
|
xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
289
415
|
const char *command = argc > 0 ? xt_string_data(xt_to_string(argv[0])) : NULL;
|
|
290
416
|
if (!command) command = "";
|
|
291
417
|
xt_value args = argc > 1 ? argv[1] : XT_UNDEFINED;
|
|
292
418
|
const char *cwd = argc > 2 ? option_cwd(argv[2]) : NULL;
|
|
419
|
+
char **envp = build_envp(option_env(argc > 2 ? argv[2] : XT_UNDEFINED));
|
|
293
420
|
|
|
294
421
|
int count = 0;
|
|
295
422
|
char **child_argv = (char **)build_argv(command, args, &count);
|
|
296
|
-
if (!child_argv)
|
|
423
|
+
if (!child_argv) {
|
|
424
|
+
free_envp(envp);
|
|
425
|
+
return make_result(-1, "", "out of memory");
|
|
426
|
+
}
|
|
297
427
|
|
|
298
428
|
if (option_stdio_inherit(argc > 2 ? argv[2] : XT_UNDEFINED)) {
|
|
299
429
|
pid_t inherit_pid = fork();
|
|
300
430
|
if (inherit_pid < 0) {
|
|
301
431
|
free(child_argv);
|
|
432
|
+
free_envp(envp);
|
|
302
433
|
return make_result(-1, "", "failed to fork");
|
|
303
434
|
}
|
|
304
435
|
if (inherit_pid == 0) {
|
|
305
436
|
if (cwd) chdir(cwd);
|
|
437
|
+
if (envp != NULL) {
|
|
438
|
+
extern char **environ;
|
|
439
|
+
environ = envp;
|
|
440
|
+
}
|
|
306
441
|
execvp(command, child_argv);
|
|
307
442
|
_exit(127);
|
|
308
443
|
}
|
|
309
444
|
int inherit_status = 0;
|
|
310
445
|
waitpid(inherit_pid, &inherit_status, 0);
|
|
311
446
|
free(child_argv);
|
|
447
|
+
free_envp(envp);
|
|
312
448
|
return make_result(
|
|
313
449
|
WIFEXITED(inherit_status) ? (double)WEXITSTATUS(inherit_status) : -1.0, "", "");
|
|
314
450
|
}
|
|
@@ -317,6 +453,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
317
453
|
int err_pipe[2];
|
|
318
454
|
if (pipe(out_pipe) != 0 || pipe(err_pipe) != 0) {
|
|
319
455
|
free(child_argv);
|
|
456
|
+
free_envp(envp);
|
|
320
457
|
return make_result(-1, "", "could not create pipes");
|
|
321
458
|
}
|
|
322
459
|
|
|
@@ -329,6 +466,10 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
329
466
|
close(err_pipe[0]);
|
|
330
467
|
close(err_pipe[1]);
|
|
331
468
|
if (cwd) chdir(cwd);
|
|
469
|
+
if (envp != NULL) {
|
|
470
|
+
extern char **environ;
|
|
471
|
+
environ = envp;
|
|
472
|
+
}
|
|
332
473
|
execvp(command, child_argv);
|
|
333
474
|
_exit(127);
|
|
334
475
|
}
|
|
@@ -371,6 +512,7 @@ xt_value xt_child_process_spawn_sync(int32_t argc, xt_value *argv) {
|
|
|
371
512
|
free(buffers[0]);
|
|
372
513
|
free(buffers[1]);
|
|
373
514
|
free(child_argv);
|
|
515
|
+
free_envp(envp);
|
|
374
516
|
return result;
|
|
375
517
|
}
|
|
376
518
|
|
package/runtime/rt.h
CHANGED
|
@@ -334,6 +334,10 @@ xt_value xt_promise_static(xt_value name, int32_t argc, xt_value *argv);
|
|
|
334
334
|
xt_value xt_await(xt_value value);
|
|
335
335
|
/** Run queued promise reactions; called once the program body finishes. */
|
|
336
336
|
void xt_drain_microtasks(void);
|
|
337
|
+
/** Mark `value` (a promise) as observed, so its rejection is not reported. */
|
|
338
|
+
void xt_promise_observe_value(xt_value value);
|
|
339
|
+
/** Print rejected promises that no handler ever observed; returns how many. */
|
|
340
|
+
int xt_promise_report_unhandled(void);
|
|
337
341
|
|
|
338
342
|
/* -- host event loop ------------------------------------------------------ */
|
|
339
343
|
/* A small select(2) reactor for host extensions (node's net/dgram/http). The
|
package/runtime/xt_async.c
CHANGED
|
@@ -122,6 +122,10 @@ void xt_async_wake(void *asyncPointer, xt_value value, int rejected) {
|
|
|
122
122
|
* queues a microtask before resuming, so an await always interleaves with
|
|
123
123
|
* pending `.then` callbacks exactly like the specification. */
|
|
124
124
|
xt_value xt_await(xt_value value) {
|
|
125
|
+
/* The awaiting code observes the outcome, so a rejection here is handled by
|
|
126
|
+
* definition — it is rethrown inside the coroutine, and the promise of the
|
|
127
|
+
* enclosing `async` function is what gets reported if nobody catches it. */
|
|
128
|
+
xt_promise_observe_value(value);
|
|
125
129
|
/* An `await` inside an async generator suspends that generator's coroutine
|
|
126
130
|
* while leaving the current `.next()` promise pending. */
|
|
127
131
|
xt_value generatorResult;
|
package/runtime/xt_generator.c
CHANGED
|
@@ -395,6 +395,8 @@ int xt_generator_await(xt_value value, xt_value *result) {
|
|
|
395
395
|
/* A nested async function inside the generator runs on its own coroutine;
|
|
396
396
|
* its awaits must not suspend the generator. */
|
|
397
397
|
if (!xt_coroutine_is_current(gen->coroutine)) return 0;
|
|
398
|
+
/* The generator's body observes the outcome, so this rejection is handled. */
|
|
399
|
+
xt_promise_observe_value(value);
|
|
398
400
|
int state = xt_promise_state_of(value);
|
|
399
401
|
if (state == XT_PROMISE_PENDING) {
|
|
400
402
|
gen->await_threw = 0;
|
package/runtime/xt_loop.c
CHANGED
|
@@ -449,11 +449,15 @@ void xt_run_event_loop(void) {
|
|
|
449
449
|
responsible for calling `xt_loop_poll` while it runs. */
|
|
450
450
|
if (xt_loop_main != NULL) {
|
|
451
451
|
xt_loop_main();
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
452
|
+
} else {
|
|
453
|
+
while (xt_loop_has_work()) {
|
|
454
|
+
if (xt_loop_poll(-1) < 0) break;
|
|
455
|
+
}
|
|
456
456
|
}
|
|
457
|
+
/* A rejection that no handler ever observed is fatal, like Node's default
|
|
458
|
+
* `--unhandled-rejections=throw`. Without this an `async main()` that throws
|
|
459
|
+
* exits 0 with no output at all, which hides the failure completely. */
|
|
460
|
+
if (xt_promise_report_unhandled() > 0) exit(1);
|
|
457
461
|
}
|
|
458
462
|
|
|
459
463
|
int xt_loop_has_work(void) {
|