xbintsc 0.3.81 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +16 -5
- package/README.zh-CN.md +14 -6
- package/dist/src/ast/common.d.ts +23 -0
- package/dist/src/cli/main.js +68 -24
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/access.js +6 -2
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/calls/closures.js +4 -0
- package/dist/src/codegen/generator/calls/closures.js.map +1 -1
- package/dist/src/codegen/generator/calls/invocation.js +9 -4
- package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
- package/dist/src/codegen/generator/context.d.ts +50 -0
- package/dist/src/codegen/generator/context.js +124 -13
- package/dist/src/codegen/generator/context.js.map +1 -1
- package/dist/src/codegen/generator/debug.d.ts +33 -0
- package/dist/src/codegen/generator/debug.js +97 -0
- package/dist/src/codegen/generator/debug.js.map +1 -0
- package/dist/src/codegen/generator/expressions/assignment.js +9 -5
- package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
- package/dist/src/codegen/generator/expressions/operators.js +35 -9
- package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
- package/dist/src/codegen/generator/expressions/primary.js +11 -3
- package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
- package/dist/src/codegen/generator/generator.d.ts +3 -1
- package/dist/src/codegen/generator/generator.js +3 -1
- package/dist/src/codegen/generator/generator.js.map +1 -1
- package/dist/src/codegen/generator/module.d.ts +1 -1
- package/dist/src/codegen/generator/module.js +82 -12
- package/dist/src/codegen/generator/module.js.map +1 -1
- package/dist/src/codegen/generator/numeric.d.ts +26 -0
- package/dist/src/codegen/generator/numeric.js +127 -0
- package/dist/src/codegen/generator/numeric.js.map +1 -0
- package/dist/src/codegen/generator/state.d.ts +17 -0
- package/dist/src/codegen/generator/state.js +9 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/statements/control-flow.js +2 -4
- package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
- package/dist/src/codegen/generator/statements/dispatch.js +3 -0
- package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
- package/dist/src/codegen/generator/statements/variables.js +8 -2
- package/dist/src/codegen/generator/statements/variables.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +16 -0
- package/dist/src/codegen/generator/tables.js +99 -2
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/merge.js +41 -5
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/compiler.d.ts +23 -4
- package/dist/src/driver/compiler.js +46 -57
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/fingerprint.d.ts +27 -0
- package/dist/src/driver/fingerprint.js +88 -0
- package/dist/src/driver/fingerprint.js.map +1 -0
- package/dist/src/driver/toolchain-provider.d.ts +9 -2
- package/dist/src/driver/toolchain-provider.js +15 -15
- package/dist/src/driver/toolchain-provider.js.map +1 -1
- package/dist/src/driver/toolchain.d.ts +58 -1
- package/dist/src/driver/toolchain.js +169 -22
- package/dist/src/driver/toolchain.js.map +1 -1
- package/dist/src/parser/expressions.js +20 -1
- package/dist/src/parser/expressions.js.map +1 -1
- package/dist/tests/cli/main.test.js +41 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/codegen/closures.test.js +8 -0
- package/dist/tests/codegen/closures.test.js.map +1 -1
- package/dist/tests/codegen/expressions.test.js +14 -3
- package/dist/tests/codegen/expressions.test.js.map +1 -1
- package/dist/tests/codegen/llvm.test.js +23 -0
- package/dist/tests/codegen/llvm.test.js.map +1 -1
- package/dist/tests/driver/toolchain-provider.test.js +49 -2
- package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
- package/dist/tests/driver/toolchain.test.js +62 -3
- package/dist/tests/driver/toolchain.test.js.map +1 -1
- package/dist/tests/e2e/async-await.test.d.ts +10 -0
- package/dist/tests/e2e/async-await.test.js +247 -0
- package/dist/tests/e2e/async-await.test.js.map +1 -0
- package/dist/tests/e2e/child-process.test.d.ts +10 -0
- package/dist/tests/e2e/child-process.test.js +59 -0
- package/dist/tests/e2e/child-process.test.js.map +1 -0
- package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
- package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
- package/dist/tests/e2e/error-stack.test.d.ts +9 -0
- package/dist/tests/e2e/error-stack.test.js +94 -0
- package/dist/tests/e2e/error-stack.test.js.map +1 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js +46 -0
- package/dist/tests/e2e/runtime-coverage-globals.test.js.map +1 -1
- package/dist/tests/e2e/string-concat.test.d.ts +9 -0
- package/dist/tests/e2e/string-concat.test.js +102 -0
- package/dist/tests/e2e/string-concat.test.js.map +1 -0
- package/dist/tests/e2e/string-utf16.test.d.ts +6 -0
- package/dist/tests/e2e/string-utf16.test.js +133 -0
- package/dist/tests/e2e/string-utf16.test.js.map +1 -0
- package/dist/tests/helpers.d.ts +9 -0
- package/dist/tests/helpers.js +26 -3
- package/dist/tests/helpers.js.map +1 -1
- package/dist/tests/parser/expressions.test.js +7 -0
- package/dist/tests/parser/expressions.test.js.map +1 -1
- package/dist/tests/version.test.d.ts +1 -0
- package/dist/tests/version.test.js +24 -0
- package/dist/tests/version.test.js.map +1 -0
- package/doc/DESIGN.md +20 -12
- package/doc/ai/cli.md +2 -0
- package/doc/ai/contributing.md +16 -8
- package/doc/ai/extensions.md +3 -2
- package/doc/ai/language-support.md +15 -12
- package/doc/ai/troubleshooting.md +43 -5
- package/doc/ai/zh-CN/cli.md +2 -0
- package/doc/ai/zh-CN/contributing.md +11 -7
- package/doc/ai/zh-CN/extensions.md +2 -1
- package/doc/ai/zh-CN/language-support.md +15 -12
- package/doc/ai/zh-CN/troubleshooting.md +38 -4
- package/doc/implemented.md +16 -13
- package/doc/requirements.md +13 -4
- package/doc/unimplemented.md +20 -18
- package/doc/zh-CN/DESIGN.md +15 -9
- package/doc/zh-CN/implemented.md +15 -12
- package/doc/zh-CN/requirements.md +12 -3
- package/doc/zh-CN/unimplemented.md +19 -17
- package/package.json +3 -1
- package/runtime/ext_node/buffer/parts/construction.inc +2 -2
- package/runtime/ext_node/buffer/parts/methods.inc +1 -1
- package/runtime/ext_node/buffer/parts/search.inc +1 -1
- package/runtime/ext_node/child_process/child_process.c +25 -0
- package/runtime/ext_node/fs/fd_ops.c +1 -1
- package/runtime/rt.h +49 -1
- package/runtime/rt_internal.h +136 -4
- package/runtime/xt_alloc.c +20 -2
- package/runtime/xt_async.c +197 -0
- package/runtime/xt_bigint/conversions.inc +1 -1
- package/runtime/xt_builtins.c +169 -43
- package/runtime/xt_constructors.c +389 -0
- package/runtime/xt_containers/access.inc +61 -24
- package/runtime/xt_containers/arrays.inc +26 -2
- package/runtime/xt_containers/objects.inc +206 -8
- package/runtime/xt_coroutine.c +212 -0
- package/runtime/xt_generator.c +31 -162
- package/runtime/xt_io/exception.inc +171 -2
- package/runtime/xt_io/inspect.inc +48 -13
- package/runtime/xt_io.c +5 -0
- package/runtime/xt_promise.c +62 -17
- package/runtime/xt_stdlib/arrays.inc +2 -2
- package/runtime/xt_stdlib/dispatch.inc +20 -4
- package/runtime/xt_stdlib/strings.inc +98 -29
- package/runtime/xt_stdlib2/array-statics.inc +3 -2
- package/runtime/xt_stdlib2/arrays.inc +1 -1
- package/runtime/xt_stdlib2/error.inc +33 -6
- package/runtime/xt_stdlib2/json.inc +46 -16
- package/runtime/xt_stdlib2/number-string-statics.inc +24 -13
- package/runtime/xt_stdlib2/objects.inc +1 -1
- package/runtime/xt_stdlib2/regexp-match.inc +50 -35
- package/runtime/xt_stdlib2/set.inc +6 -0
- package/runtime/xt_stdlib2/strings.inc +81 -42
- package/runtime/xt_symbol.c +1 -1
- package/runtime/xt_typed_array/construction.inc +43 -2
- package/runtime/xt_typed_array.c +4 -0
- package/runtime/xt_values/comparison.inc +2 -2
- package/runtime/xt_values/constructors.inc +18 -5
- package/runtime/xt_values/conversions.inc +4 -3
- package/runtime/xt_values/strings.inc +94 -4
- package/runtime/xt_values/utf16.inc +340 -0
- package/runtime/xt_values.c +1 -0
- package/scripts/bench.ts +172 -0
- package/scripts/build-runtime.ts +2 -1
- package/scripts/check-version.ts +53 -0
- package/src/ast/common.ts +23 -0
- package/src/cli/main.ts +68 -26
- package/src/codegen/generator/calls/access.ts +7 -2
- package/src/codegen/generator/calls/closures.ts +3 -0
- package/src/codegen/generator/calls/invocation.ts +9 -3
- package/src/codegen/generator/context.ts +129 -18
- package/src/codegen/generator/debug.ts +132 -0
- package/src/codegen/generator/expressions/assignment.ts +9 -5
- package/src/codegen/generator/expressions/operators.ts +47 -8
- package/src/codegen/generator/expressions/primary.ts +11 -3
- package/src/codegen/generator/generator.ts +18 -2
- package/src/codegen/generator/module.ts +83 -13
- package/src/codegen/generator/numeric.ts +158 -0
- package/src/codegen/generator/state.ts +26 -0
- package/src/codegen/generator/statements/control-flow.ts +2 -3
- package/src/codegen/generator/statements/dispatch.ts +3 -0
- package/src/codegen/generator/statements/variables.ts +8 -2
- package/src/codegen/generator/tables.ts +110 -2
- package/src/driver/bundler/merge.ts +39 -5
- package/src/driver/compiler.ts +65 -55
- package/src/driver/fingerprint.ts +85 -0
- package/src/driver/toolchain-provider.ts +17 -16
- package/src/driver/toolchain.ts +210 -21
- package/src/parser/expressions.ts +20 -1
|
@@ -29,12 +29,18 @@ function isParameterProperty(parameter: Parameter): boolean {
|
|
|
29
29
|
);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** A readable, IR-symbol-safe suffix for a function name. */
|
|
33
|
+
function symbolSuffix(name: string): string {
|
|
34
|
+
if (name.length === 0 || name.startsWith("<")) return "";
|
|
35
|
+
return name.replace(/[^A-Za-z0-9_]/g, "_").replace(/^_+/, "").slice(0, 32);
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
export interface ModuleMethods {
|
|
33
39
|
run(this: Generator): string;
|
|
34
40
|
emitFunction(this: Generator, fn: FunctionInfo): void;
|
|
41
|
+
emitFunctionReturn(this: Generator, value: string): void;
|
|
35
42
|
emitMain(this: Generator): void;
|
|
36
43
|
functionName(this: Generator, fn: FunctionInfo): string;
|
|
37
|
-
wrapAsync(this: Generator, value: string): string;
|
|
38
44
|
classGlobal(this: Generator, classInfo: ClassInfo): string;
|
|
39
45
|
emitClassReference(this: Generator, classInfo: ClassInfo): string;
|
|
40
46
|
emitClassSetup(this: Generator, classInfo: ClassInfo): void;
|
|
@@ -77,13 +83,32 @@ export const moduleMethods: ModuleMethods = {
|
|
|
77
83
|
];
|
|
78
84
|
const runtimeDeclarations = new Set(RUNTIME_DECLARATIONS);
|
|
79
85
|
const extraDeclarations = [...this.extraDeclarations].filter((line) => !runtimeDeclarations.has(line));
|
|
80
|
-
return [
|
|
86
|
+
return [
|
|
87
|
+
...header,
|
|
88
|
+
...RUNTIME_DECLARATIONS,
|
|
89
|
+
...extraDeclarations,
|
|
90
|
+
"",
|
|
91
|
+
...this.globals,
|
|
92
|
+
"",
|
|
93
|
+
...this.functions,
|
|
94
|
+
"",
|
|
95
|
+
...this.debugMetadataLines(),
|
|
96
|
+
].join("\n");
|
|
81
97
|
},
|
|
82
98
|
|
|
83
99
|
// -- module level --------------------------------------------------------
|
|
84
100
|
|
|
85
101
|
emitFunction(fn: FunctionInfo): void {
|
|
102
|
+
this.unsupportedCount = 0;
|
|
103
|
+
// `async function*` would need an async iterator and a promise-returning
|
|
104
|
+
// `.next()`; the generator/async lowering below would silently produce a
|
|
105
|
+
// synchronous generator instead, so reject it rather than miscompile.
|
|
106
|
+
if (fn.isAsync && fn.isGenerator) {
|
|
107
|
+
this.unsupported(fn.node, "async generator function");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
86
110
|
const name = this.functionName(fn);
|
|
111
|
+
const debugSubprogram = this.debugSubprogramFor(fn);
|
|
87
112
|
const state: FunctionState = {
|
|
88
113
|
fn,
|
|
89
114
|
buffer: [],
|
|
@@ -98,10 +123,18 @@ export const moduleMethods: ModuleMethods = {
|
|
|
98
123
|
finallyStack: [],
|
|
99
124
|
escapePointers: [],
|
|
100
125
|
usesTry: false,
|
|
126
|
+
/* Generator and async bodies run on a private coroutine stack that can be
|
|
127
|
+
* suspended in the middle of a call. Their shadow frame would then sit on
|
|
128
|
+
* the shadow stack while the caller unwinds its own, so leave them out of
|
|
129
|
+
* `Error.stack` frames (nesting calls inside them are still recorded). */
|
|
130
|
+
trackFrames: !fn.isGenerator && !fn.isAsync,
|
|
131
|
+
...(debugSubprogram ? { debugSubprogram } : {}),
|
|
101
132
|
};
|
|
102
133
|
this.current = state;
|
|
134
|
+
if (debugSubprogram) this.setDebugLocation(fn.node);
|
|
103
135
|
|
|
104
|
-
const
|
|
136
|
+
const debugSuffix = debugSubprogram ? ` !dbg ${debugSubprogram}` : "";
|
|
137
|
+
const header = `define i64 @${name}(i64 %this, i64 %env, i32 %argc, i64* %argv)${debugSuffix} {`;
|
|
105
138
|
this.emit(`%saved.env = alloca i64`);
|
|
106
139
|
this.emit(`store i64 %env, i64* %saved.env`);
|
|
107
140
|
this.emit(`%saved.this = alloca i64`);
|
|
@@ -112,6 +145,23 @@ export const moduleMethods: ModuleMethods = {
|
|
|
112
145
|
this.emit(`%saved.argv = alloca i64*`);
|
|
113
146
|
this.emit(`store i64* %argv, i64** %saved.argv`);
|
|
114
147
|
|
|
148
|
+
// Shadow-stack frame: `Error.stack` and fatal-signal dumps walk this.
|
|
149
|
+
if (state.trackFrames) {
|
|
150
|
+
const displayName = fn.isModule
|
|
151
|
+
? "module"
|
|
152
|
+
: fn.isConstructor && fn.classInfo
|
|
153
|
+
? `new ${this.displayNameOf(fn.classInfo.name)}`
|
|
154
|
+
: this.displayNameOf(fn.name.length > 0 ? fn.name : "anonymous");
|
|
155
|
+
const namePointer = this.internStatic(displayName);
|
|
156
|
+
/* Normalise separators like `source_filename`: the compiler may run under
|
|
157
|
+
* Node (`\`) or as a self-hosted binary (`/`), and the IR must stay
|
|
158
|
+
* byte-identical across the two. */
|
|
159
|
+
const filePointer = this.internStatic(this.fileNameOf(fn.node).split("\\").join("/"));
|
|
160
|
+
this.emit(
|
|
161
|
+
` call void @xt_stack_push(i8* ${namePointer}, i8* ${filePointer}, i32 ${this.lineOf(fn.node)})`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
115
165
|
// Parameters.
|
|
116
166
|
const parameterNodes = (fn.node as { parameters?: Parameter[] }).parameters ?? [];
|
|
117
167
|
for (let index = 0; index < fn.params.length; index++) {
|
|
@@ -207,15 +257,17 @@ export const moduleMethods: ModuleMethods = {
|
|
|
207
257
|
this.emitStatements(this.sourceFile.statements);
|
|
208
258
|
} else if (fn.node.kind === SyntaxKind.ArrowFunction && (fn.node as ArrowFunction).body.kind !== SyntaxKind.Block) {
|
|
209
259
|
const value = this.emitExpression((fn.node as ArrowFunction).body as Expression);
|
|
210
|
-
this.
|
|
260
|
+
this.emitFunctionReturn(value);
|
|
211
261
|
} else {
|
|
212
262
|
const body = (fn.node as { body: Block }).body;
|
|
213
263
|
if (body) this.emitStatements(body.statements);
|
|
214
264
|
}
|
|
215
265
|
|
|
216
266
|
if (!this.current.terminated) {
|
|
217
|
-
|
|
218
|
-
|
|
267
|
+
/* An `async` body returns its value plainly: the runtime's async
|
|
268
|
+
* coroutine settles the returned promise (and adopts a returned
|
|
269
|
+
* promise), so wrapping here would add a redundant promise. */
|
|
270
|
+
this.emitFunctionReturn(i64(XT_UNDEFINED));
|
|
219
271
|
}
|
|
220
272
|
|
|
221
273
|
const escapes = state.usesTry
|
|
@@ -229,8 +281,22 @@ export const moduleMethods: ModuleMethods = {
|
|
|
229
281
|
const moduleName = this.functionName(this.binding.moduleFunction);
|
|
230
282
|
// Module bindings and class constructors live in globals; register them as
|
|
231
283
|
// GC roots before the module body runs so a collection cannot free them.
|
|
232
|
-
const
|
|
284
|
+
const stringEntries = [...this.strings.values()];
|
|
285
|
+
const roots = [
|
|
286
|
+
...this.classGlobals.values(),
|
|
287
|
+
...this.moduleGlobals.values(),
|
|
288
|
+
...stringEntries.map((entry) => entry.slot),
|
|
289
|
+
];
|
|
233
290
|
const rootLines = roots.map((name) => ` call void @xt_gc_add_root(i64* ${name})`);
|
|
291
|
+
// Every interned string literal is materialised once here and shared by all
|
|
292
|
+
// uses, so property names and other literals stop allocating per access.
|
|
293
|
+
const stringInit = stringEntries.flatMap((entry) => {
|
|
294
|
+
const register = `%strv.${entry.slot.replace("@.strv.", "")}`;
|
|
295
|
+
return [
|
|
296
|
+
` ${register} = call i64 @xt_string_new(i8* ${entry.label}, i64 ${entry.length})`,
|
|
297
|
+
` store i64 ${register}, i64* ${entry.slot}`,
|
|
298
|
+
];
|
|
299
|
+
});
|
|
234
300
|
this.functions.push(
|
|
235
301
|
[
|
|
236
302
|
"define i32 @main(i32 %argc, i8** %argv) {",
|
|
@@ -239,6 +305,7 @@ export const moduleMethods: ModuleMethods = {
|
|
|
239
305
|
" call void @xt_gc_set_stack_base(i8* %stackbase.ptr)",
|
|
240
306
|
" call void @xt_gc_init()",
|
|
241
307
|
...rootLines,
|
|
308
|
+
...stringInit,
|
|
242
309
|
" call void @xt_gc_arm()",
|
|
243
310
|
" call void @xt_set_program_args(i32 %argc, i8** %argv)",
|
|
244
311
|
` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
|
|
@@ -252,14 +319,17 @@ export const moduleMethods: ModuleMethods = {
|
|
|
252
319
|
},
|
|
253
320
|
|
|
254
321
|
functionName(fn: FunctionInfo): string {
|
|
255
|
-
|
|
322
|
+
if (fn.isModule) return "xt_module";
|
|
323
|
+
/* Keep the original name in the symbol so native debuggers, crash
|
|
324
|
+
* backtraces and `--emit ir` are readable (`xt_fn_3_sum`). */
|
|
325
|
+
const suffix = symbolSuffix(this.displayNameOf(fn.name));
|
|
326
|
+
return suffix.length > 0 ? `xt_fn_${fn.id}_${suffix}` : `xt_fn_${fn.id}`;
|
|
256
327
|
},
|
|
257
328
|
|
|
258
|
-
/**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
this.
|
|
262
|
-
return result;
|
|
329
|
+
/** Emit the function's return, dropping its shadow-stack frame first. */
|
|
330
|
+
emitFunctionReturn(value: string): void {
|
|
331
|
+
if (this.current.trackFrames) this.emit(" call void @xt_stack_pop()");
|
|
332
|
+
this.terminate(`ret i64 ${value}`);
|
|
263
333
|
},
|
|
264
334
|
|
|
265
335
|
classGlobal(classInfo: ClassInfo): string {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline fast paths for numeric operators.
|
|
3
|
+
*
|
|
4
|
+
* Every JavaScript operator used to be a runtime call (`xt_add`, `xt_lt`, …).
|
|
5
|
+
* That is correct but expensive in numeric code: a `for` loop paid three calls
|
|
6
|
+
* per iteration, and clang cannot inline across the generated IR / C runtime
|
|
7
|
+
* boundary. For two unboxed doubles the operation is just a hardware
|
|
8
|
+
* instruction, so emit that inline and keep the runtime call as the cold path
|
|
9
|
+
* for every other combination (strings, BigInt, coercion, …).
|
|
10
|
+
*
|
|
11
|
+
* Doubles are stored bit-for-bit in `xt_value`, so the tag check is an integer
|
|
12
|
+
* mask and the operation is `bitcast` → `fadd`/`fcmp` → `bitcast`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { i64, XT_FALSE, XT_NUMBER_MASK, XT_TRUE } from "../values.js";
|
|
16
|
+
import type { Generator } from "./generator.js";
|
|
17
|
+
|
|
18
|
+
export interface NumericMethods {
|
|
19
|
+
/**
|
|
20
|
+
* Emit `left <operator> right`, using an inline double fast path when both
|
|
21
|
+
* operands turn out to be numbers and {@link allowFastPath} is set.
|
|
22
|
+
*/
|
|
23
|
+
emitBinaryOperation(
|
|
24
|
+
this: Generator,
|
|
25
|
+
left: string,
|
|
26
|
+
right: string,
|
|
27
|
+
operator: string,
|
|
28
|
+
runtime: string,
|
|
29
|
+
allowFastPath?: boolean,
|
|
30
|
+
): string;
|
|
31
|
+
emitNumericBinary(this: Generator, left: string, right: string, instruction: string, runtime: string): string;
|
|
32
|
+
emitNumericComparison(this: Generator, left: string, right: string, predicate: string, runtime: string): string;
|
|
33
|
+
/** Emit an i1 SSA value that is true when `value` is an unboxed double. */
|
|
34
|
+
isNumberValue(this: Generator, value: string): string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Canonical quiet NaN; the runtime uses the same pattern in `xt_from_double`. */
|
|
38
|
+
const CANONICAL_NAN = 0x7ff8000000000000n;
|
|
39
|
+
|
|
40
|
+
const NUMERIC_ARITHMETIC: Record<string, string | undefined> = {
|
|
41
|
+
"+": "fadd",
|
|
42
|
+
"-": "fsub",
|
|
43
|
+
"*": "fmul",
|
|
44
|
+
"/": "fdiv",
|
|
45
|
+
"%": "frem",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const NUMERIC_COMPARISON: Record<string, string | undefined> = {
|
|
49
|
+
"<": "olt",
|
|
50
|
+
"<=": "ole",
|
|
51
|
+
">": "ogt",
|
|
52
|
+
">=": "oge",
|
|
53
|
+
"==": "oeq",
|
|
54
|
+
"!=": "une",
|
|
55
|
+
"===": "oeq",
|
|
56
|
+
"!==": "une",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const numericMethods: NumericMethods = {
|
|
60
|
+
emitBinaryOperation(left, right, operator, runtime, allowFastPath = true) {
|
|
61
|
+
if (allowFastPath) {
|
|
62
|
+
const instruction = NUMERIC_ARITHMETIC[operator];
|
|
63
|
+
if (instruction) return this.emitNumericBinary(left, right, instruction, runtime);
|
|
64
|
+
const predicate = NUMERIC_COMPARISON[operator];
|
|
65
|
+
if (predicate) return this.emitNumericComparison(left, right, predicate, runtime);
|
|
66
|
+
}
|
|
67
|
+
const result = this.reg();
|
|
68
|
+
this.emit(` ${result} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
|
|
69
|
+
return result;
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/** Emit an i1 SSA value that is true when `value` is an unboxed double. */
|
|
73
|
+
isNumberValue(value: string): string {
|
|
74
|
+
const masked = this.reg();
|
|
75
|
+
this.emit(` ${masked} = and i64 ${value}, ${i64(XT_NUMBER_MASK)}`);
|
|
76
|
+
const result = this.reg();
|
|
77
|
+
this.emit(` ${result} = icmp ne i64 ${masked}, ${i64(XT_NUMBER_MASK)}`);
|
|
78
|
+
return result;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
emitNumericBinary(left, right, instruction, runtime) {
|
|
82
|
+
const result = this.alloca();
|
|
83
|
+
const leftIsNumber = this.isNumberValue(left);
|
|
84
|
+
const rightIsNumber = this.isNumberValue(right);
|
|
85
|
+
const bothNumbers = this.reg();
|
|
86
|
+
this.emit(` ${bothNumbers} = and i1 ${leftIsNumber}, ${rightIsNumber}`);
|
|
87
|
+
const fast = this.label("num.fast");
|
|
88
|
+
const slow = this.label("num.slow");
|
|
89
|
+
const end = this.label("num.end");
|
|
90
|
+
this.terminate(`br i1 ${bothNumbers}, label %${fast}, label %${slow}`);
|
|
91
|
+
|
|
92
|
+
this.startBlock(fast);
|
|
93
|
+
const leftDouble = this.reg();
|
|
94
|
+
this.emit(` ${leftDouble} = bitcast i64 ${left} to double`);
|
|
95
|
+
const rightDouble = this.reg();
|
|
96
|
+
this.emit(` ${rightDouble} = bitcast i64 ${right} to double`);
|
|
97
|
+
const computed = this.reg();
|
|
98
|
+
this.emit(` ${computed} = ${instruction} double ${leftDouble}, ${rightDouble}`);
|
|
99
|
+
// Hardware NaNs carry many payloads; the value model only reserves the
|
|
100
|
+
// canonical quiet NaN, and `-NaN` would otherwise collide with XT_UNDEFINED.
|
|
101
|
+
const bits = this.reg();
|
|
102
|
+
this.emit(` ${bits} = bitcast double ${computed} to i64`);
|
|
103
|
+
const unordered = this.reg();
|
|
104
|
+
this.emit(` ${unordered} = fcmp uno double ${computed}, ${computed}`);
|
|
105
|
+
const canonical = this.reg();
|
|
106
|
+
this.emit(` ${canonical} = select i1 ${unordered}, i64 ${i64(CANONICAL_NAN)}, i64 ${bits}`);
|
|
107
|
+
this.emit(` store i64 ${canonical}, i64* ${result}`);
|
|
108
|
+
this.terminate(`br label %${end}`);
|
|
109
|
+
|
|
110
|
+
this.startBlock(slow);
|
|
111
|
+
const called = this.reg();
|
|
112
|
+
this.emit(` ${called} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
|
|
113
|
+
this.emit(` store i64 ${called}, i64* ${result}`);
|
|
114
|
+
this.terminate(`br label %${end}`);
|
|
115
|
+
|
|
116
|
+
this.startBlock(end);
|
|
117
|
+
const value = this.reg();
|
|
118
|
+
this.emit(` ${value} = load i64, i64* ${result}`);
|
|
119
|
+
return value;
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
emitNumericComparison(left, right, predicate, runtime) {
|
|
123
|
+
const result = this.alloca();
|
|
124
|
+
const leftIsNumber = this.isNumberValue(left);
|
|
125
|
+
const rightIsNumber = this.isNumberValue(right);
|
|
126
|
+
const bothNumbers = this.reg();
|
|
127
|
+
this.emit(` ${bothNumbers} = and i1 ${leftIsNumber}, ${rightIsNumber}`);
|
|
128
|
+
const fast = this.label("cmp.fast");
|
|
129
|
+
const slow = this.label("cmp.slow");
|
|
130
|
+
const end = this.label("cmp.end");
|
|
131
|
+
this.terminate(`br i1 ${bothNumbers}, label %${fast}, label %${slow}`);
|
|
132
|
+
|
|
133
|
+
this.startBlock(fast);
|
|
134
|
+
const leftDouble = this.reg();
|
|
135
|
+
this.emit(` ${leftDouble} = bitcast i64 ${left} to double`);
|
|
136
|
+
const rightDouble = this.reg();
|
|
137
|
+
this.emit(` ${rightDouble} = bitcast i64 ${right} to double`);
|
|
138
|
+
// Ordered comparisons are false for NaN and unordered `!=` is true, which
|
|
139
|
+
// is exactly ECMAScript's behaviour for two numbers.
|
|
140
|
+
const compared = this.reg();
|
|
141
|
+
this.emit(` ${compared} = fcmp ${predicate} double ${leftDouble}, ${rightDouble}`);
|
|
142
|
+
const value = this.reg();
|
|
143
|
+
this.emit(` ${value} = select i1 ${compared}, i64 ${i64(XT_TRUE)}, i64 ${i64(XT_FALSE)}`);
|
|
144
|
+
this.emit(` store i64 ${value}, i64* ${result}`);
|
|
145
|
+
this.terminate(`br label %${end}`);
|
|
146
|
+
|
|
147
|
+
this.startBlock(slow);
|
|
148
|
+
const called = this.reg();
|
|
149
|
+
this.emit(` ${called} = call i64 @${runtime}(i64 ${left}, i64 ${right})`);
|
|
150
|
+
this.emit(` store i64 ${called}, i64* ${result}`);
|
|
151
|
+
this.terminate(`br label %${end}`);
|
|
152
|
+
|
|
153
|
+
this.startBlock(end);
|
|
154
|
+
const merged = this.reg();
|
|
155
|
+
this.emit(` ${merged} = load i64, i64* ${result}`);
|
|
156
|
+
return merged;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
@@ -50,6 +50,16 @@ export interface FunctionState {
|
|
|
50
50
|
thisPtr?: string;
|
|
51
51
|
/** Set when the function contains a `try`, forcing locals to live in memory. */
|
|
52
52
|
usesTry: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* True when the function pushes/pops a shadow-stack frame. Generator bodies
|
|
55
|
+
* are excluded: they switch stacks and can be suspended, so a global shadow
|
|
56
|
+
* stack would attribute their frames to the wrong caller.
|
|
57
|
+
*/
|
|
58
|
+
trackFrames: boolean;
|
|
59
|
+
/** `!DISubprogram` for this function when DWARF emission is enabled. */
|
|
60
|
+
debugSubprogram?: string;
|
|
61
|
+
/** Current `!DILocation` attached to every emitted instruction. */
|
|
62
|
+
debugLocation?: string;
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
export interface CodegenResult {
|
|
@@ -74,6 +84,13 @@ export interface CodegenOptions {
|
|
|
74
84
|
* defaults to the running process; it is overridable for tests.
|
|
75
85
|
*/
|
|
76
86
|
readonly target?: { readonly platform: string; readonly arch: string };
|
|
87
|
+
/**
|
|
88
|
+
* Emit LLVM debug metadata (`!DICompileUnit`, `!DISubprogram`,
|
|
89
|
+
* `!DILocation`) so `clang -g` produces DWARF/CodeView for the generated
|
|
90
|
+
* program. Off by default: the metadata is large and only useful when
|
|
91
|
+
* debugging; `xbintsc build --debug` turns it on.
|
|
92
|
+
*/
|
|
93
|
+
readonly debug?: boolean;
|
|
77
94
|
}
|
|
78
95
|
|
|
79
96
|
export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
@@ -87,12 +104,17 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
87
104
|
"declare i64 @xt_closure_new(i8*, i32, i64*)",
|
|
88
105
|
"declare i64 @xt_function_set_metadata(i64, i64, i32)",
|
|
89
106
|
"declare i64 @xt_function_set_generator(i64)",
|
|
107
|
+
"declare i64 @xt_function_set_async(i64)",
|
|
90
108
|
"declare i64 @xt_closure_call(i64, i32, i64*)",
|
|
91
109
|
"declare i64 @xt_closure_env(i64, i32)",
|
|
92
110
|
"declare i64 @xt_call_with_this(i64, i64, i32, i64*)",
|
|
93
111
|
"declare i64 @xt_this()",
|
|
94
112
|
"declare i64 @xt_new(i64, i32, i64*)",
|
|
95
113
|
"declare i64 @xt_instance_of(i64, i64)",
|
|
114
|
+
"declare i64 @xt_instance_of_builtin(i64, i32)",
|
|
115
|
+
"declare i64 @xt_builtin_constructor(i64)",
|
|
116
|
+
"declare i64 @xt_array_ctor(i32, i64*)",
|
|
117
|
+
"declare i64 @xt_object_ctor(i32, i64*)",
|
|
96
118
|
"declare i64 @xt_function_set_prototype(i64, i64)",
|
|
97
119
|
"declare i64 @xt_function_get_prototype(i64)",
|
|
98
120
|
"declare i64 @xt_object_new_with_proto(i64)",
|
|
@@ -225,6 +247,7 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
225
247
|
"declare i64 @xt_object_entries(i64)",
|
|
226
248
|
"declare i64 @xt_object_assign(i32, i64*)",
|
|
227
249
|
"declare i64 @xt_object_spread(i64, i64)",
|
|
250
|
+
"declare i64 @xt_object_rest(i64, i32, i64*)",
|
|
228
251
|
"declare i64 @xt_call_method(i64, i64, i32, i64*)",
|
|
229
252
|
"declare i64 @xt_math_call(i64, i32, i64*)",
|
|
230
253
|
"declare i64 @xt_parse_int(i32, i64*)",
|
|
@@ -298,6 +321,9 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
|
|
|
298
321
|
"declare i8* @xt_try_enter()",
|
|
299
322
|
"declare i64 @xt_try_exception(i8*)",
|
|
300
323
|
"declare void @xt_try_leave(i8*)",
|
|
324
|
+
"declare void @xt_stack_push(i8*, i8*, i32)",
|
|
325
|
+
"declare void @xt_stack_pop()",
|
|
326
|
+
"declare void @xt_stack_set_line(i32)",
|
|
301
327
|
"declare void @xt_console_log(i32, i64*)",
|
|
302
328
|
"declare void @xt_console_info(i32, i64*)",
|
|
303
329
|
"declare void @xt_console_warn(i32, i64*)",
|
|
@@ -250,12 +250,11 @@ export const controlFlowStatementMethods: ControlFlowStatementMethods = {
|
|
|
250
250
|
},
|
|
251
251
|
|
|
252
252
|
emitReturn(statement: ReturnStatement): void {
|
|
253
|
-
|
|
254
|
-
if (this.current.fn.isAsync) value = this.wrapAsync(value);
|
|
253
|
+
const value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
|
|
255
254
|
this.runFinallysBeforeExit(0);
|
|
256
255
|
if (this.current.terminated) return;
|
|
257
256
|
this.popTryFramesTo(0);
|
|
258
|
-
this.
|
|
257
|
+
this.emitFunctionReturn(value);
|
|
259
258
|
},
|
|
260
259
|
|
|
261
260
|
/** Emit `xt_try_leave` for every active frame above `depth` (innermost first). */
|
|
@@ -41,6 +41,9 @@ export const statementDispatchMethods: StatementDispatchMethods = {
|
|
|
41
41
|
},
|
|
42
42
|
|
|
43
43
|
emitStatement(statement: Statement): void {
|
|
44
|
+
/* Every instruction emitted while lowering this statement (including
|
|
45
|
+
* nested expressions) points at its source position when DWARF is on. */
|
|
46
|
+
this.setDebugLocation(statement);
|
|
44
47
|
switch (statement.kind) {
|
|
45
48
|
case SyntaxKind.ExpressionStatement:
|
|
46
49
|
this.emitExpression((statement as { expression: Expression }).expression);
|
|
@@ -87,13 +87,19 @@ export const variableStatementMethods: VariableStatementMethods = {
|
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
const pattern = name as ObjectBindingPattern;
|
|
90
|
+
/* Keys named by the pattern are excluded from `...rest`. */
|
|
91
|
+
const excludedKeys: string[] = [];
|
|
90
92
|
for (const element of pattern.elements) {
|
|
91
|
-
const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
|
|
92
93
|
if (element.dotDotDotToken) {
|
|
93
|
-
this.
|
|
94
|
+
const rest = this.reg();
|
|
95
|
+
const keys = this.emitValueArguments(excludedKeys);
|
|
96
|
+
this.emit(` ${rest} = call i64 @xt_object_rest(i64 ${value}, i32 ${keys.argc}, i64* ${keys.ptr})`);
|
|
97
|
+
this.emitBindingPattern(element.name, rest);
|
|
94
98
|
continue;
|
|
95
99
|
}
|
|
100
|
+
const key = element.propertyName ?? (element.name.kind === SyntaxKind.Identifier ? element.name : undefined);
|
|
96
101
|
const keyValue = this.stringValue(key ? propertyNameText(key) : "undefined");
|
|
102
|
+
excludedKeys.push(keyValue);
|
|
97
103
|
let elementValue = this.reg();
|
|
98
104
|
this.emit(` ${elementValue} = call i64 @xt_get(i64 ${value}, i64 ${keyValue})`);
|
|
99
105
|
if (element.initializer) elementValue = this.emitBindingDefault(elementValue, element.initializer);
|
|
@@ -22,6 +22,32 @@ export function requiresSetjmpex(platform: string, arch: string): boolean {
|
|
|
22
22
|
return platform === "win32" && arch === "arm64";
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Built-in constructors usable on the right of `instanceof`, mapped to the id
|
|
27
|
+
* `xt_instance_of_builtin` expects. Keep in sync with the `XT_INSTANCE_*` enum
|
|
28
|
+
* in `runtime/xt_builtins.c`; the typed-array ids follow the runtime's
|
|
29
|
+
* `XT_TA_*` order (Uint8Array first).
|
|
30
|
+
*/
|
|
31
|
+
export const BUILTIN_INSTANCEOF: Readonly<Record<string, number>> = {
|
|
32
|
+
Array: 0,
|
|
33
|
+
Object: 1,
|
|
34
|
+
Function: 2,
|
|
35
|
+
Map: 3,
|
|
36
|
+
Set: 4,
|
|
37
|
+
Date: 5,
|
|
38
|
+
RegExp: 6,
|
|
39
|
+
Promise: 7,
|
|
40
|
+
Uint8Array: 16,
|
|
41
|
+
Int8Array: 17,
|
|
42
|
+
Uint8ClampedArray: 18,
|
|
43
|
+
Uint16Array: 19,
|
|
44
|
+
Int16Array: 20,
|
|
45
|
+
Uint32Array: 21,
|
|
46
|
+
Int32Array: 22,
|
|
47
|
+
Float32Array: 23,
|
|
48
|
+
Float64Array: 24,
|
|
49
|
+
};
|
|
50
|
+
|
|
25
51
|
export const BINARY_RUNTIME: Record<string, string | undefined> = {
|
|
26
52
|
[BinaryOperator.Add]: "xt_add",
|
|
27
53
|
[BinaryOperator.Subtract]: "xt_sub",
|
|
@@ -227,6 +253,23 @@ export const NAMESPACE_STATICS: Record<string, string> = {
|
|
|
227
253
|
...TYPED_ARRAY_STATICS,
|
|
228
254
|
};
|
|
229
255
|
|
|
256
|
+
/** The well-known symbols exposed as properties of the `Symbol` namespace. */
|
|
257
|
+
export const SYMBOL_WELL_KNOWN = new Set<string>([
|
|
258
|
+
"asyncIterator",
|
|
259
|
+
"hasInstance",
|
|
260
|
+
"isConcatSpreadable",
|
|
261
|
+
"iterator",
|
|
262
|
+
"match",
|
|
263
|
+
"matchAll",
|
|
264
|
+
"replace",
|
|
265
|
+
"search",
|
|
266
|
+
"species",
|
|
267
|
+
"split",
|
|
268
|
+
"toPrimitive",
|
|
269
|
+
"toStringTag",
|
|
270
|
+
"unscopables",
|
|
271
|
+
]);
|
|
272
|
+
|
|
230
273
|
/** Namespace identifiers whose property access maps to a runtime getter. */
|
|
231
274
|
export const NAMESPACE_PROPERTIES: Record<string, string> = {
|
|
232
275
|
process: "xt_process_get",
|
|
@@ -236,7 +279,7 @@ export const NAMESPACE_PROPERTIES: Record<string, string> = {
|
|
|
236
279
|
|
|
237
280
|
/** Global constructors called as `new X(...)` (all have signature `(i32, i64*)`). */
|
|
238
281
|
export const CTOR_FUNCTIONS: Record<string, string> = {
|
|
239
|
-
Array: "
|
|
282
|
+
Array: "xt_array_ctor",
|
|
240
283
|
Map: "xt_map_ctor",
|
|
241
284
|
Set: "xt_set_ctor",
|
|
242
285
|
Date: "xt_date_ctor",
|
|
@@ -255,6 +298,30 @@ export const CTOR_FUNCTIONS: Record<string, string> = {
|
|
|
255
298
|
...TYPED_ARRAY_CTORS,
|
|
256
299
|
};
|
|
257
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Built-in constructors that are also ordinary first-class values. The codegen
|
|
303
|
+
* lowers a bare reference (`const A = Array`) to `xt_builtin_constructor`, which
|
|
304
|
+
* returns a cached function with `name`/`length`, a `prototype` and the
|
|
305
|
+
* constructor's statics (`A.isArray`, `A.from`, ...).
|
|
306
|
+
*/
|
|
307
|
+
export const FIRST_CLASS_CONSTRUCTORS = new Set<string>([
|
|
308
|
+
"Array",
|
|
309
|
+
"Object",
|
|
310
|
+
"Function",
|
|
311
|
+
"Map",
|
|
312
|
+
"Set",
|
|
313
|
+
"Date",
|
|
314
|
+
"RegExp",
|
|
315
|
+
"Promise",
|
|
316
|
+
...Object.keys(TYPED_ARRAYS),
|
|
317
|
+
...Object.keys(ERROR_CONSTRUCTORS),
|
|
318
|
+
"Number",
|
|
319
|
+
"String",
|
|
320
|
+
"Boolean",
|
|
321
|
+
"BigInt",
|
|
322
|
+
"Symbol",
|
|
323
|
+
]);
|
|
324
|
+
|
|
258
325
|
export const BUILTIN_METHODS = new Set<string>([
|
|
259
326
|
"push", "pop", "shift", "unshift", "join", "slice", "indexOf", "includes", "map", "forEach", "filter",
|
|
260
327
|
"reduce", "concat", "reverse", "charAt", "charCodeAt", "substring", "substr", "split", "toUpperCase",
|
|
@@ -368,7 +435,7 @@ export function utf8Bytes(text: string): number[] {
|
|
|
368
435
|
if (!hasMultiByteUnit(text)) {
|
|
369
436
|
const bytes: number[] = [];
|
|
370
437
|
for (let index = 0; index < text.length; index++) bytes.push(text.charCodeAt(index) & 0xff);
|
|
371
|
-
return bytes;
|
|
438
|
+
return combineSurrogateBytes(bytes);
|
|
372
439
|
}
|
|
373
440
|
const bytes: number[] = [];
|
|
374
441
|
const length = text.length;
|
|
@@ -394,6 +461,47 @@ function hasMultiByteUnit(text: string): boolean {
|
|
|
394
461
|
return false;
|
|
395
462
|
}
|
|
396
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Join adjacent WTF-8 surrogate halves into one four-byte sequence.
|
|
466
|
+
*
|
|
467
|
+
* A literal such as `"\uD83D\uDE00"` scans to two separate three-byte
|
|
468
|
+
* escapes. JavaScript's string value is one code point, so the emitted bytes
|
|
469
|
+
* must be the four-byte UTF-8 form (and a lone escape must stay three bytes,
|
|
470
|
+
* which is why this runs after scanning rather than per escape).
|
|
471
|
+
*/
|
|
472
|
+
function combineSurrogateBytes(bytes: number[]): number[] {
|
|
473
|
+
const out: number[] = [];
|
|
474
|
+
for (let index = 0; index < bytes.length; index++) {
|
|
475
|
+
if (
|
|
476
|
+
index + 5 < bytes.length &&
|
|
477
|
+
bytes[index] === 0xed &&
|
|
478
|
+
bytes[index + 1]! >= 0xa0 &&
|
|
479
|
+
bytes[index + 1]! <= 0xaf &&
|
|
480
|
+
bytes[index + 2]! >= 0x80 &&
|
|
481
|
+
bytes[index + 2]! <= 0xbf &&
|
|
482
|
+
bytes[index + 3] === 0xed &&
|
|
483
|
+
bytes[index + 4]! >= 0xb0 &&
|
|
484
|
+
bytes[index + 4]! <= 0xbf &&
|
|
485
|
+
bytes[index + 5]! >= 0x80 &&
|
|
486
|
+
bytes[index + 5]! <= 0xbf
|
|
487
|
+
) {
|
|
488
|
+
const high = ((bytes[index]! & 0x0f) << 12) | ((bytes[index + 1]! & 0x3f) << 6) | (bytes[index + 2]! & 0x3f);
|
|
489
|
+
const low = ((bytes[index + 3]! & 0x0f) << 12) | ((bytes[index + 4]! & 0x3f) << 6) | (bytes[index + 5]! & 0x3f);
|
|
490
|
+
const code = 0x10000 + ((high - 0xd800) << 10) + (low - 0xdc00);
|
|
491
|
+
out.push(
|
|
492
|
+
0xf0 | (code >> 18),
|
|
493
|
+
0x80 | ((code >> 12) & 0x3f),
|
|
494
|
+
0x80 | ((code >> 6) & 0x3f),
|
|
495
|
+
0x80 | (code & 0x3f),
|
|
496
|
+
);
|
|
497
|
+
index += 5;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
out.push(bytes[index]!);
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
|
|
397
505
|
export function escapeBytes(bytes: readonly number[]): string {
|
|
398
506
|
return bytes
|
|
399
507
|
.map((byte) => {
|