xbintsc 0.3.5 → 0.3.7
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 +81 -8
- package/README.zh-CN.md +78 -8
- package/dist/src/cli/main.d.ts +1 -0
- package/dist/src/cli/main.js +13 -1
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +12 -0
- package/dist/src/codegen/generator/context.js +28 -1
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/state.d.ts +8 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +1 -8
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +11 -0
- package/dist/src/codegen/generator/tables.js +13 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/compiler.js +22 -4
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/toolchain-download.js +26 -0
- package/dist/src/driver/toolchain-download.js.map +1 -1
- package/dist/src/extensions/native.d.ts +92 -0
- package/dist/src/extensions/native.js +217 -0
- package/dist/src/extensions/native.js.map +1 -0
- package/dist/src/extensions/registry.d.ts +13 -0
- package/dist/src/extensions/registry.js +10 -0
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/tests/cli/main.test.js +15 -0
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +24 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/toolchain-download.test.js +15 -0
- package/dist/tests/driver/toolchain-download.test.js.map +1 -1
- package/dist/tests/driver/toolchain-provider.test.js +21 -1
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/e2e/native-extensions.test.d.ts +13 -0
- package/dist/tests/e2e/native-extensions.test.js +175 -0
- package/dist/tests/e2e/native-extensions.test.js.map +1 -0
- package/dist/tests/extensions/native.test.d.ts +1 -0
- package/dist/tests/extensions/native.test.js +101 -0
- package/dist/tests/extensions/native.test.js.map +1 -0
- package/dist/tests/helpers.d.ts +4 -1
- package/dist/tests/helpers.js +2 -1
- package/dist/tests/helpers.js.map +1 -1
- package/package.json +1 -1
- package/runtime/rt_internal.h +5 -3
- package/runtime/xt_ext.h +109 -0
- package/runtime/xt_ext.rs +173 -0
- package/runtime/xt_io.c +15 -14
- package/src/cli/main.ts +13 -1
- package/src/codegen/generator/context.ts +29 -1
- package/src/codegen/generator/state.ts +5 -0
- package/src/codegen/generator/statements/control-flow.ts +1 -8
- package/src/codegen/generator/tables.ts +14 -0
- package/src/driver/compiler.ts +25 -4
- package/src/driver/toolchain-download.ts +27 -0
- package/src/extensions/native.ts +270 -0
- package/src/extensions/registry.ts +21 -0
- package/src/index.ts +10 -0
package/runtime/rt_internal.h
CHANGED
|
@@ -235,9 +235,11 @@ typedef struct xt_try_frame {
|
|
|
235
235
|
* `_setjmp(jmp_buf, void *frame)` itself, so the frame must be passed
|
|
236
236
|
* explicitly -- exactly what its own `setjmp` macro does.
|
|
237
237
|
*
|
|
238
|
-
* The generated IR
|
|
239
|
-
*
|
|
240
|
-
*
|
|
238
|
+
* The generated IR emits the two-argument form, because it is already IR and
|
|
239
|
+
* clang will not rewrite it. On Windows ARM64 it names `_setjmpex` (and the
|
|
240
|
+
* entry stack pointer from `llvm.sponentry`) instead, matching clang's own
|
|
241
|
+
* lowering there. `_setjmp`/`_setjmpex` (rather than the `setjmp` macro) is
|
|
242
|
+
* used so it pairs with the plain `longjmp` in `xt_throw`. */
|
|
241
243
|
#if defined(_WIN32) && !defined(__MINGW32__)
|
|
242
244
|
#define xt_try_setjmp(framePtr) _setjmp(((xt_try_frame *)(framePtr))->buf)
|
|
243
245
|
#elif defined(_WIN32) && defined(__aarch64__)
|
package/runtime/xt_ext.h
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc native-extension helper header.
|
|
3
|
+
*
|
|
4
|
+
* Include this from a C or C++ extension that xbintsc links into a generated
|
|
5
|
+
* binary. It pulls in the runtime ABI (`rt.h`) and adds small helpers for the
|
|
6
|
+
* common job of reading the `(argc, argv)` arguments and building result
|
|
7
|
+
* values. In C++ everything is wrapped in `extern "C"` by `rt.h`, so a
|
|
8
|
+
* definition like
|
|
9
|
+
*
|
|
10
|
+
* XT_EXT_FN(mathx_add) {
|
|
11
|
+
* double a = xt_ext_number(argc, argv, 0, 0);
|
|
12
|
+
* double b = xt_ext_number(argc, argv, 1, 0);
|
|
13
|
+
* return xt_number(a + b);
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* exports a symbol callable from TypeScript (via a native manifest) without any
|
|
17
|
+
* name mangling.
|
|
18
|
+
*
|
|
19
|
+
* Build the extension with the same clang that xbintsc uses, keeping the
|
|
20
|
+
* runtime directory on the include path:
|
|
21
|
+
*
|
|
22
|
+
* clang++ -O2 -fPIC -I<runtime> -c mathx.cpp -o mathx.o
|
|
23
|
+
* ar rcs libmathx.a mathx.o
|
|
24
|
+
*/
|
|
25
|
+
#ifndef XT_EXT_H
|
|
26
|
+
#define XT_EXT_H
|
|
27
|
+
|
|
28
|
+
#include <stddef.h>
|
|
29
|
+
#include <stdint.h>
|
|
30
|
+
|
|
31
|
+
#include "rt.h"
|
|
32
|
+
|
|
33
|
+
#ifdef __cplusplus
|
|
34
|
+
#define XT_EXT_FN(name) extern "C" xt_value name(int32_t argc, xt_value *argv)
|
|
35
|
+
#else
|
|
36
|
+
#define XT_EXT_FN(name) xt_value name(int32_t argc, xt_value *argv)
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
#ifdef __cplusplus
|
|
40
|
+
extern "C" {
|
|
41
|
+
#endif
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* XT_EXT_FN(name) declares/defines an exported entry point with the signature
|
|
45
|
+
* the code generator uses for builtins and module exports:
|
|
46
|
+
*
|
|
47
|
+
* xt_value name(int32_t argc, xt_value *argv);
|
|
48
|
+
*
|
|
49
|
+
* In C++ it also gives the definition C linkage, so the symbol is never
|
|
50
|
+
* name-mangled and the manifest can reference it directly.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/** The `index`-th argument, or `undefined` when it was not supplied. */
|
|
54
|
+
static inline xt_value xt_ext_arg(int32_t argc, xt_value *argv, int32_t index) {
|
|
55
|
+
if (argv == NULL || index < 0 || index >= argc) return XT_UNDEFINED;
|
|
56
|
+
return argv[index];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The `index`-th argument coerced to a number, or `fallback` when absent. */
|
|
60
|
+
static inline double xt_ext_number(int32_t argc, xt_value *argv, int32_t index, double fallback) {
|
|
61
|
+
xt_value value = xt_ext_arg(argc, argv, index);
|
|
62
|
+
if (XT_IS_UNDEFINED(value)) return fallback;
|
|
63
|
+
return xt_to_number(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The `index`-th argument coerced to a 32-bit integer. */
|
|
67
|
+
static inline int32_t xt_ext_int(int32_t argc, xt_value *argv, int32_t index, int32_t fallback) {
|
|
68
|
+
return (int32_t)xt_ext_number(argc, argv, index, (double)fallback);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The `index`-th argument coerced to a boolean. */
|
|
72
|
+
static inline int xt_ext_bool(int32_t argc, xt_value *argv, int32_t index, int fallback) {
|
|
73
|
+
xt_value value = xt_ext_arg(argc, argv, index);
|
|
74
|
+
if (XT_IS_UNDEFINED(value)) return fallback;
|
|
75
|
+
return xt_truthy(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Borrow the UTF-8 bytes of the `index`-th argument when it is a string.
|
|
80
|
+
* Returns `NULL` and stores 0 in `*length` otherwise. The pointer stays valid
|
|
81
|
+
* for the lifetime of the value (the runtime arena never frees).
|
|
82
|
+
*/
|
|
83
|
+
static inline const char *xt_ext_string(int32_t argc, xt_value *argv, int32_t index, size_t *length) {
|
|
84
|
+
xt_value value = xt_ext_arg(argc, argv, index);
|
|
85
|
+
if (!XT_IS_STRING(value)) {
|
|
86
|
+
if (length != NULL) *length = 0;
|
|
87
|
+
return NULL;
|
|
88
|
+
}
|
|
89
|
+
if (length != NULL) *length = (size_t)xt_string_length_value(value);
|
|
90
|
+
return xt_string_data(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Build a string value from bytes. */
|
|
94
|
+
static inline xt_value xt_ext_string_value(const char *data, size_t length) {
|
|
95
|
+
return xt_string_new(data, length);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#ifdef __cplusplus
|
|
99
|
+
} /* extern "C" */
|
|
100
|
+
|
|
101
|
+
#include <string>
|
|
102
|
+
|
|
103
|
+
/** Convenience overload for `std::string` / string literals in C++. */
|
|
104
|
+
static inline xt_value xt_ext_string_from(const std::string &text) {
|
|
105
|
+
return xt_string_new(text.data(), text.size());
|
|
106
|
+
}
|
|
107
|
+
#endif /* __cplusplus */
|
|
108
|
+
|
|
109
|
+
#endif /* XT_EXT_H */
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// xbintsc native-extension bindings for Rust.
|
|
2
|
+
//
|
|
3
|
+
// Include this file from a Rust `staticlib`/`cdylib` that xbintsc links into a
|
|
4
|
+
// generated binary:
|
|
5
|
+
//
|
|
6
|
+
// // src/lib.rs
|
|
7
|
+
// include!("../../runtime/xt_ext.rs");
|
|
8
|
+
//
|
|
9
|
+
// #[no_mangle]
|
|
10
|
+
// pub extern "C" fn mathx_add(argc: i32, argv: *const XtValue) -> XtValue {
|
|
11
|
+
// let a = arg_number(argc, argv, 0, 0.0);
|
|
12
|
+
// let b = arg_number(argc, argv, 1, 0.0);
|
|
13
|
+
// xt_number(a + b)
|
|
14
|
+
// }
|
|
15
|
+
//
|
|
16
|
+
// The exported functions use the runtime ABI, so a native manifest maps them
|
|
17
|
+
// exactly like C or C++ symbols: `xt_value name(int32_t argc, xt_value *argv)`.
|
|
18
|
+
//
|
|
19
|
+
// Build with the runtime directory reachable and produce a static library:
|
|
20
|
+
//
|
|
21
|
+
// cargo build --release # crate-type = ["staticlib"]
|
|
22
|
+
// # -> target/release/libmathx.a
|
|
23
|
+
|
|
24
|
+
/// A JavaScript value: a 64-bit NaN-boxed word (see `runtime/rt.h`).
|
|
25
|
+
pub type XtValue = u64;
|
|
26
|
+
|
|
27
|
+
pub const XT_TAG_MASK: XtValue = 0xFFFF_0000_0000_0000;
|
|
28
|
+
pub const XT_NUMBER_MASK: XtValue = 0xFFF8_0000_0000_0000;
|
|
29
|
+
|
|
30
|
+
pub const XT_UNDEFINED: XtValue = 0xFFF8_0000_0000_0000;
|
|
31
|
+
pub const XT_NULL: XtValue = 0xFFF9_0000_0000_0000;
|
|
32
|
+
pub const XT_FALSE: XtValue = 0xFFFA_0000_0000_0000;
|
|
33
|
+
pub const XT_TAG_STRING: XtValue = 0xFFFC_0000_0000_0000;
|
|
34
|
+
|
|
35
|
+
// Raw runtime entry points. They are wrapped in safe helpers below so that
|
|
36
|
+
// extension authors never have to spell `unsafe` just to build a value.
|
|
37
|
+
extern "C" {
|
|
38
|
+
#[link_name = "xt_number"]
|
|
39
|
+
fn raw_xt_number(d: f64) -> XtValue;
|
|
40
|
+
#[link_name = "xt_string_new"]
|
|
41
|
+
fn raw_xt_string_new(data: *const u8, len: usize) -> XtValue;
|
|
42
|
+
#[link_name = "xt_string_data"]
|
|
43
|
+
fn raw_xt_string_data(value: XtValue) -> *const u8;
|
|
44
|
+
#[link_name = "xt_string_length_value"]
|
|
45
|
+
fn raw_xt_string_length_value(value: XtValue) -> i32;
|
|
46
|
+
#[link_name = "xt_truthy"]
|
|
47
|
+
fn raw_xt_truthy(value: XtValue) -> i32;
|
|
48
|
+
#[link_name = "xt_to_number"]
|
|
49
|
+
fn raw_xt_to_number(value: XtValue) -> f64;
|
|
50
|
+
#[link_name = "xt_to_string"]
|
|
51
|
+
fn raw_xt_to_string(value: XtValue) -> XtValue;
|
|
52
|
+
#[link_name = "xt_array_new"]
|
|
53
|
+
fn raw_xt_array_new(count: i32, items: *mut XtValue) -> XtValue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Box an `f64` into a JavaScript number value.
|
|
57
|
+
#[inline]
|
|
58
|
+
pub fn xt_number(d: f64) -> XtValue {
|
|
59
|
+
unsafe { raw_xt_number(d) }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Build a string value from a byte slice (assumed UTF-8).
|
|
63
|
+
#[inline]
|
|
64
|
+
pub fn xt_string_new(data: *const u8, len: usize) -> XtValue {
|
|
65
|
+
unsafe { raw_xt_string_new(data, len) }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Pointer to the first byte of a string value's payload.
|
|
69
|
+
#[inline]
|
|
70
|
+
pub fn xt_string_data(value: XtValue) -> *const u8 {
|
|
71
|
+
unsafe { raw_xt_string_data(value) }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Byte length of a string value.
|
|
75
|
+
#[inline]
|
|
76
|
+
pub fn xt_string_length_value(value: XtValue) -> i32 {
|
|
77
|
+
unsafe { raw_xt_string_length_value(value) }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// JavaScript truthiness of a value (`0`/`1`).
|
|
81
|
+
#[inline]
|
|
82
|
+
pub fn xt_truthy(value: XtValue) -> i32 {
|
|
83
|
+
unsafe { raw_xt_truthy(value) }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Convert a value to a number, coercing non-numbers like the runtime does.
|
|
87
|
+
#[inline]
|
|
88
|
+
pub fn xt_to_number(value: XtValue) -> f64 {
|
|
89
|
+
unsafe { raw_xt_to_number(value) }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Convert a value to a string value.
|
|
93
|
+
#[inline]
|
|
94
|
+
pub fn xt_to_string(value: XtValue) -> XtValue {
|
|
95
|
+
unsafe { raw_xt_to_string(value) }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Build an array from `count` items.
|
|
99
|
+
#[inline]
|
|
100
|
+
pub fn xt_array_new(count: i32, items: *mut XtValue) -> XtValue {
|
|
101
|
+
unsafe { raw_xt_array_new(count, items) }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// Whether a value carries the number (unboxed double) representation.
|
|
105
|
+
#[inline]
|
|
106
|
+
pub fn is_number(value: XtValue) -> bool {
|
|
107
|
+
(value & XT_NUMBER_MASK) != XT_NUMBER_MASK
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Whether a value is a string.
|
|
111
|
+
#[inline]
|
|
112
|
+
pub fn is_string(value: XtValue) -> bool {
|
|
113
|
+
(value & XT_TAG_MASK) == XT_TAG_STRING
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Reinterpret an unboxed number value as `f64`.
|
|
117
|
+
#[inline]
|
|
118
|
+
pub fn as_f64(value: XtValue) -> f64 {
|
|
119
|
+
f64::from_bits(value)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Read argument `index`, returning `undefined` when it was not supplied.
|
|
123
|
+
///
|
|
124
|
+
/// `argv` may be null when a function is called with no arguments.
|
|
125
|
+
#[inline]
|
|
126
|
+
pub fn arg(argc: i32, argv: *const XtValue, index: usize) -> XtValue {
|
|
127
|
+
if argv.is_null() || index >= argc as usize {
|
|
128
|
+
return XT_UNDEFINED;
|
|
129
|
+
}
|
|
130
|
+
unsafe { *argv.add(index) }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Read argument `index` coerced to a number, or `fallback` when absent.
|
|
134
|
+
#[inline]
|
|
135
|
+
pub fn arg_number(argc: i32, argv: *const XtValue, index: usize, fallback: f64) -> f64 {
|
|
136
|
+
let value = arg(argc, argv, index);
|
|
137
|
+
if value == XT_UNDEFINED {
|
|
138
|
+
return fallback;
|
|
139
|
+
}
|
|
140
|
+
xt_to_number(value)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Read argument `index` coerced to a boolean.
|
|
144
|
+
#[inline]
|
|
145
|
+
pub fn arg_bool(argc: i32, argv: *const XtValue, index: usize, fallback: bool) -> bool {
|
|
146
|
+
let value = arg(argc, argv, index);
|
|
147
|
+
if value == XT_UNDEFINED {
|
|
148
|
+
return fallback;
|
|
149
|
+
}
|
|
150
|
+
xt_truthy(value) != 0
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Borrow argument `index` as a UTF-8 string slice, or `None` when it is not a
|
|
154
|
+
/// string. The bytes live in the runtime arena and never move.
|
|
155
|
+
#[inline]
|
|
156
|
+
pub fn arg_str<'a>(argc: i32, argv: *const XtValue, index: usize) -> Option<&'a str> {
|
|
157
|
+
let value = arg(argc, argv, index);
|
|
158
|
+
if !is_string(value) {
|
|
159
|
+
return None;
|
|
160
|
+
}
|
|
161
|
+
let length = xt_string_length_value(value) as usize;
|
|
162
|
+
let data = xt_string_data(value);
|
|
163
|
+
if data.is_null() || length == 0 {
|
|
164
|
+
return Some("");
|
|
165
|
+
}
|
|
166
|
+
unsafe { Some(core::str::from_utf8_unchecked(core::slice::from_raw_parts(data, length))) }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// Build a string value from a Rust string slice.
|
|
170
|
+
#[inline]
|
|
171
|
+
pub fn string_from(text: &str) -> XtValue {
|
|
172
|
+
xt_string_new(text.as_ptr(), text.len())
|
|
173
|
+
}
|
package/runtime/xt_io.c
CHANGED
|
@@ -5,20 +5,21 @@
|
|
|
5
5
|
* innermost frame when one is active and otherwise prints + exits. Also holds
|
|
6
6
|
* `print`/`console` output and the Node-like value inspector.
|
|
7
7
|
*
|
|
8
|
-
* The generated IR
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
* `
|
|
14
|
-
* 0xC00000FF).
|
|
15
|
-
*
|
|
16
|
-
* the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
8
|
+
* The generated IR saves each frame with a `setjmp`-family call that passes
|
|
9
|
+
* the frame explicitly — the jmp_buf plus a frame pointer — because the call
|
|
10
|
+
* is already IR and clang will not rewrite it. On Linux/macOS that is
|
|
11
|
+
* `_setjmp(buf, frame)`; the extra argument is ignored. On Windows the UCRT
|
|
12
|
+
* `_setjmp` stores the frame in `_JUMP_BUFFER.Frame` and `longjmp` passes it
|
|
13
|
+
* to `RtlUnwind` to run the unwind, so omitting it makes `longjmp` unwind to a
|
|
14
|
+
* bogus target (`STATUS_BAD_FUNCTION_TABLE`, 0xC00000FF). Windows ARM64 has
|
|
15
|
+
* no `_setjmp` at all: the generated IR uses `_setjmpex(buf, entry-sp)` with
|
|
16
|
+
* the stack pointer on entry (`@llvm.sponentry`), exactly as clang lowers a C
|
|
17
|
+
* `setjmp` there. The C runtime (see `xt_try_setjmp`) calls whatever the
|
|
18
|
+
* active `<setjmp.h>` declares — one argument on MSVC (clang injects the
|
|
19
|
+
* frame) and two on MinGW-w64. `_setjmp`/`_setjmpex` are used rather than the
|
|
20
|
+
* exported `setjmp` symbol, whose Windows ABI is an incompatible two-argument
|
|
21
|
+
* routine; on Linux/macOS `_setjmp` takes only the buffer and pairs with
|
|
22
|
+
* `longjmp` (the XSI `_longjmp` does not exist on Windows).
|
|
22
23
|
*/
|
|
23
24
|
|
|
24
25
|
#include "rt_internal.h"
|
package/src/cli/main.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* available programmatically through the driver API:
|
|
6
6
|
*
|
|
7
7
|
* xbintsc build <file> [-o out] [--emit ir|obj|exe] [-O0..3] [--ext node]
|
|
8
|
+
* [--ext-native manifest.json]
|
|
8
9
|
* xbintsc run <file> [-- args...]
|
|
9
10
|
* xbintsc emit <file> # print LLVM IR to stdout
|
|
10
11
|
* xbintsc doctor # report the resolved toolchain
|
|
@@ -23,6 +24,7 @@ import { resolveToolchain } from "../driver/toolchain-provider.js";
|
|
|
23
24
|
import { realRunner } from "../driver/toolchain.js";
|
|
24
25
|
import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
|
|
25
26
|
import { nodeExtension } from "../extensions/node/index.js";
|
|
27
|
+
import { nativeExtensionFromManifest } from "../extensions/native.js";
|
|
26
28
|
|
|
27
29
|
export interface CliIo {
|
|
28
30
|
readonly stdout: (text: string) => void;
|
|
@@ -41,7 +43,7 @@ interface ParsedArgs {
|
|
|
41
43
|
readonly passthrough: string[];
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
const VALUE_FLAGS = new Set(["output", "out", "emit", "optimize", "ext"]);
|
|
46
|
+
const VALUE_FLAGS = new Set(["output", "out", "emit", "optimize", "ext", "ext-native"]);
|
|
45
47
|
|
|
46
48
|
function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
47
49
|
const positionals: string[] = [];
|
|
@@ -102,6 +104,14 @@ function buildRegistry(flags: Map<string, string | boolean>): ExtensionRegistry
|
|
|
102
104
|
else throw new Error(`Unknown extension '${name}'`);
|
|
103
105
|
}
|
|
104
106
|
}
|
|
107
|
+
// Each `--ext-native` value is a manifest path describing a pre-built C++ or
|
|
108
|
+
// Rust library; several manifests may be comma separated.
|
|
109
|
+
const native = flags.get("ext-native");
|
|
110
|
+
if (typeof native === "string") {
|
|
111
|
+
for (const manifest of native.split(",").map((n) => n.trim()).filter(Boolean)) {
|
|
112
|
+
registry.register(nativeExtensionFromManifest(manifest));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
105
115
|
return registry;
|
|
106
116
|
}
|
|
107
117
|
|
|
@@ -133,6 +143,8 @@ Options:
|
|
|
133
143
|
--emit <kind> exe | obj | ir (default: exe)
|
|
134
144
|
-O0..-O3 Optimization level (default: -O2)
|
|
135
145
|
--ext <names> Comma separated extensions (e.g. node)
|
|
146
|
+
--ext-native <m> Register a C++/Rust extension from a JSON manifest
|
|
147
|
+
(comma separated for several)
|
|
136
148
|
--force Ignore the incremental cache
|
|
137
149
|
--verbose Print progress information
|
|
138
150
|
`;
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
ModuleExports,
|
|
19
19
|
} from "../../extensions/registry.js";
|
|
20
20
|
import type { CodegenOptions, FunctionState } from "./state.js";
|
|
21
|
-
import { escapeBytes, isErrorFamily, kindName, utf8Bytes } from "./tables.js";
|
|
21
|
+
import { escapeBytes, isErrorFamily, kindName, requiresSetjmpex, utf8Bytes } from "./tables.js";
|
|
22
22
|
|
|
23
23
|
export class GeneratorContext {
|
|
24
24
|
readonly binding: BindResult;
|
|
@@ -26,6 +26,8 @@ export class GeneratorContext {
|
|
|
26
26
|
readonly diagnostics: DiagnosticBag;
|
|
27
27
|
readonly builtins: Readonly<Record<string, BuiltinFunction>>;
|
|
28
28
|
readonly modules: Readonly<Record<string, ExtensionModule>>;
|
|
29
|
+
/** Host the emitted IR targets (the process by default; see `CodegenOptions`). */
|
|
30
|
+
readonly target: { readonly platform: string; readonly arch: string };
|
|
29
31
|
/** Imported symbol id -> the module binding it refers to. */
|
|
30
32
|
readonly importExports = new Map<number, ModuleExport>();
|
|
31
33
|
/** Imported symbol id -> the namespace it aliases (e.g. `path`). */
|
|
@@ -52,6 +54,7 @@ export class GeneratorContext {
|
|
|
52
54
|
this.binding = bind(sourceFile);
|
|
53
55
|
this.builtins = options.builtins ?? {};
|
|
54
56
|
this.modules = options.modules ?? {};
|
|
57
|
+
this.target = options.target ?? { platform: process.platform, arch: process.arch };
|
|
55
58
|
this.resolveImports();
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -127,6 +130,31 @@ export class GeneratorContext {
|
|
|
127
130
|
return `%r${this.current.reg++}`;
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Emit the `setjmp` that opens a `try` frame and return the register holding
|
|
135
|
+
* its result. The frame address is always passed explicitly because Windows
|
|
136
|
+
* `_setjmp` stores it in `_JUMP_BUFFER.Frame` for `longjmp` to unwind to; the
|
|
137
|
+
* extra argument is ignored by the one-argument `_setjmp` on Linux/macOS.
|
|
138
|
+
*/
|
|
139
|
+
emitSetjmp(frame: string): string {
|
|
140
|
+
if (requiresSetjmpex(this.target.platform, this.target.arch)) {
|
|
141
|
+
// Windows ARM64 has no `_setjmp`; use `_setjmpex`, which expects the
|
|
142
|
+
// caller's stack pointer on entry (`llvm.sponentry`).
|
|
143
|
+
this.extraDeclarations.add("declare i8* @llvm.sponentry()");
|
|
144
|
+
this.extraDeclarations.add("declare i32 @_setjmpex(i8*, i8*) returns_twice");
|
|
145
|
+
const entry = this.reg();
|
|
146
|
+
this.emit(` ${entry} = call i8* @llvm.sponentry()`);
|
|
147
|
+
const jump = this.reg();
|
|
148
|
+
this.emit(` ${jump} = call i32 @_setjmpex(i8* ${frame}, i8* ${entry})`);
|
|
149
|
+
return jump;
|
|
150
|
+
}
|
|
151
|
+
const frameAddress = this.reg();
|
|
152
|
+
this.emit(` ${frameAddress} = call i8* @llvm.frameaddress(i32 0)`);
|
|
153
|
+
const jump = this.reg();
|
|
154
|
+
this.emit(` ${jump} = call i32 @_setjmp(i8* ${frame}, i8* ${frameAddress})`);
|
|
155
|
+
return jump;
|
|
156
|
+
}
|
|
157
|
+
|
|
130
158
|
label(prefix: string): string {
|
|
131
159
|
return `${prefix}.${this.current.label++}`;
|
|
132
160
|
}
|
|
@@ -62,6 +62,11 @@ export interface CodegenOptions {
|
|
|
62
62
|
readonly builtins?: Readonly<Record<string, BuiltinFunction>>;
|
|
63
63
|
/** Importable modules supplied by registered extensions. */
|
|
64
64
|
readonly modules?: Readonly<Record<string, ExtensionModule>>;
|
|
65
|
+
/**
|
|
66
|
+
* Host the IR is generated for. xbintsc builds for its own host, so this
|
|
67
|
+
* defaults to the running process; it is overridable for tests.
|
|
68
|
+
*/
|
|
69
|
+
readonly target?: { readonly platform: string; readonly arch: string };
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
@@ -166,14 +166,7 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
|
|
|
166
166
|
this.emit(` ${frame} = call i8* @xt_try_enter()`);
|
|
167
167
|
this.emit(` store i8* ${frame}, i8** ${frameSlot}`);
|
|
168
168
|
this.emit(` store i64 0, i64* ${flagSlot}`);
|
|
169
|
-
|
|
170
|
-
// (`_JUMP_BUFFER.Frame`); `longjmp` uses it to run the unwind. clang lowers
|
|
171
|
-
// a C call to `_setjmp` exactly like this. The extra argument is ignored by
|
|
172
|
-
// the one-argument `_setjmp` on Linux/macOS.
|
|
173
|
-
const frameAddress = this.reg();
|
|
174
|
-
this.emit(` ${frameAddress} = call i8* @llvm.frameaddress(i32 0)`);
|
|
175
|
-
const jump = this.reg();
|
|
176
|
-
this.emit(` ${jump} = call i32 @_setjmp(i8* ${frame}, i8* ${frameAddress})`);
|
|
169
|
+
const jump = this.emitSetjmp(frame);
|
|
177
170
|
const isThrow = this.reg();
|
|
178
171
|
this.emit(` ${isThrow} = icmp ne i32 ${jump}, 0`);
|
|
179
172
|
const exceptionTarget = hasCatch ? catchLabel : exceptionLabel;
|
|
@@ -9,6 +9,20 @@ import {
|
|
|
9
9
|
type Node,
|
|
10
10
|
} from "../../ast/nodes.js";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* True when a `try` frame must be saved with `_setjmpex` rather than `_setjmp`.
|
|
14
|
+
*
|
|
15
|
+
* 64-bit Windows on ARM has no `_setjmp`: both the UCRT and MinGW-w64 declare
|
|
16
|
+
* `setjmp` as `_setjmpex(buf, sponentry)` there, so the linker expects
|
|
17
|
+
* `_setjmpex` (with the caller's stack pointer at entry, `llvm.sponentry`,
|
|
18
|
+
* instead of its frame pointer). clang lowers a C `setjmp` on this target to
|
|
19
|
+
* exactly that pair. Checked at code-generation time because xbintsc builds for
|
|
20
|
+
* its own host.
|
|
21
|
+
*/
|
|
22
|
+
export function requiresSetjmpex(platform: string, arch: string): boolean {
|
|
23
|
+
return platform === "win32" && arch === "arm64";
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
export const BINARY_RUNTIME: Record<string, string | undefined> = {
|
|
13
27
|
[BinaryOperator.Add]: "xt_add",
|
|
14
28
|
[BinaryOperator.Subtract]: "xt_sub",
|
package/src/driver/compiler.ts
CHANGED
|
@@ -160,7 +160,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
160
160
|
emit,
|
|
161
161
|
optimize,
|
|
162
162
|
process.platform,
|
|
163
|
-
registry
|
|
163
|
+
registryFingerprint(registry),
|
|
164
164
|
runtimeFingerprint(runtimeDir),
|
|
165
165
|
]);
|
|
166
166
|
|
|
@@ -209,7 +209,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
209
209
|
return { outputPath, irPath, cached: false, diagnostics: [], ir };
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
const { runtimeObjects, extensionObjects } = ensureRuntimeObjects(
|
|
212
|
+
const { runtimeObjects, extensionObjects, nativeObjects } = ensureRuntimeObjects(
|
|
213
213
|
runner,
|
|
214
214
|
clang,
|
|
215
215
|
runtimeDir,
|
|
@@ -221,7 +221,7 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
221
221
|
|
|
222
222
|
link(runner, {
|
|
223
223
|
clang,
|
|
224
|
-
objectPaths: [objectPath, ...runtimeObjects, ...extensionObjects],
|
|
224
|
+
objectPaths: [objectPath, ...runtimeObjects, ...extensionObjects, ...nativeObjects],
|
|
225
225
|
outputPath,
|
|
226
226
|
linkerFlags: [
|
|
227
227
|
...toolchain.linkerArgs,
|
|
@@ -240,6 +240,8 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
|
|
|
240
240
|
interface RuntimeObjects {
|
|
241
241
|
readonly runtimeObjects: readonly string[];
|
|
242
242
|
readonly extensionObjects: readonly string[];
|
|
243
|
+
/** Pre-built C++/Rust objects and archives contributed by native extensions. */
|
|
244
|
+
readonly nativeObjects: readonly string[];
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
/** Core runtime translation units (each compiled and cached independently). */
|
|
@@ -285,6 +287,24 @@ function runtimeFingerprint(runtimeDir: string): string {
|
|
|
285
287
|
return hashParts(parts);
|
|
286
288
|
}
|
|
287
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Fingerprint everything an extension contributes that can change the linked
|
|
292
|
+
* executable but does not live under `runtime/`: its name, linker flags and the
|
|
293
|
+
* contents of any pre-built C++/Rust objects. Keeps a cached binary fresh when
|
|
294
|
+
* a native extension is rebuilt, or when `--ext-native` flags change.
|
|
295
|
+
*/
|
|
296
|
+
function registryFingerprint(registry: ExtensionRegistry): string {
|
|
297
|
+
const parts: string[] = [];
|
|
298
|
+
for (const extension of registry.all()) {
|
|
299
|
+
parts.push(extension.name);
|
|
300
|
+
parts.push(...(extension.linkerFlags?.() ?? []));
|
|
301
|
+
for (const object of extension.nativeObjects?.() ?? []) {
|
|
302
|
+
parts.push(object, existsSync(object) ? readFileSync(object).toString("base64") : "");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return hashParts(parts);
|
|
306
|
+
}
|
|
307
|
+
|
|
288
308
|
/**
|
|
289
309
|
* Compile the core runtime and every extension source, reusing cached object
|
|
290
310
|
* files keyed on the C source hash. Returns the object paths to link.
|
|
@@ -348,5 +368,6 @@ function ensureRuntimeObjects(
|
|
|
348
368
|
extensionObjects.push(compileOne(sourcePath, `ext_${extensionObjects.length}_${basename(sourcePath, ".c")}`));
|
|
349
369
|
}
|
|
350
370
|
}
|
|
351
|
-
|
|
371
|
+
// A native archive may be shared by several manifests; link each once.
|
|
372
|
+
return { runtimeObjects, extensionObjects, nativeObjects: [...new Set(registry.nativeObjects())] };
|
|
352
373
|
}
|
|
@@ -65,6 +65,18 @@ export function toolchainDownload(
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
if (platform === "win32" && arch === "arm64") {
|
|
69
|
+
// The llvm-mingw `aarch64` package is the Windows-on-ARM host build; its
|
|
70
|
+
// clang targets aarch64 by default, so a native Windows ARM64 runner gets
|
|
71
|
+
// native MinGW-w64 binaries with no MSVC dependency.
|
|
72
|
+
return {
|
|
73
|
+
url: `https://github.com/mstorsjo/llvm-mingw/releases/download/${LLVM_MINGW_VERSION}/llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-aarch64.zip`,
|
|
74
|
+
archive: `llvm-mingw-${LLVM_MINGW_VERSION}-ucrt-aarch64.zip`,
|
|
75
|
+
kind: "zip",
|
|
76
|
+
stripComponents: 1,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
// macOS (and any other host) uses the system SDK; nothing to bundle.
|
|
69
81
|
return undefined;
|
|
70
82
|
}
|
|
@@ -104,5 +116,20 @@ export function toolchainSupportLibraries(
|
|
|
104
116
|
},
|
|
105
117
|
];
|
|
106
118
|
}
|
|
119
|
+
if (platform === "linux" && arch === "arm64") {
|
|
120
|
+
// The aarch64 release build links the same removed `libtinfo.so.5` soname as
|
|
121
|
+
// the x86_64 one; arm64 packages live in the Ubuntu ports archive.
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
urls: [
|
|
125
|
+
"https://ports.ubuntu.com/ubuntu-ports/pool/main/n/ncurses/libtinfo5_6.1-1ubuntu1.18.04.1_arm64.deb",
|
|
126
|
+
"http://ports.ubuntu.com/ubuntu-ports/pool/main/n/ncurses/libtinfo5_6.1-1ubuntu1.18.04.1_arm64.deb",
|
|
127
|
+
],
|
|
128
|
+
archive: "libtinfo5.deb",
|
|
129
|
+
member: "lib/aarch64-linux-gnu/libtinfo.so.5.9",
|
|
130
|
+
dest: "libtinfo.so.5",
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
}
|
|
107
134
|
return [];
|
|
108
135
|
}
|