@scriptc/compiler 0.2.8 → 0.2.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/ambient/scriptc-node-fallback.d.ts +1 -0
- package/dist/backend/cycle-analysis.js +50 -17
- package/dist/backend/cycle-analysis.js.map +1 -1
- package/dist/backend/cycle-analysis.test.js +138 -14
- package/dist/backend/cycle-analysis.test.js.map +1 -1
- package/dist/backend/cycle-mutability.d.ts +31 -6
- package/dist/backend/cycle-mutability.js +120 -18
- package/dist/backend/cycle-mutability.js.map +1 -1
- package/dist/backend/llvm/alloc.d.ts +13 -3
- package/dist/backend/llvm/alloc.js +24 -10
- package/dist/backend/llvm/alloc.js.map +1 -1
- package/dist/backend/llvm/alloc.test.js +5 -5
- package/dist/backend/llvm/alloc.test.js.map +1 -1
- package/dist/backend/llvm/byte-numbers.js +2 -3
- package/dist/backend/llvm/byte-numbers.js.map +1 -1
- package/dist/backend/llvm/classes.js +7 -6
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +17 -0
- package/dist/backend/llvm/common.js +28 -0
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.js +1 -1
- package/dist/backend/llvm/dense-array-access.test.js +1 -1
- package/dist/backend/llvm/dyn.d.ts +4 -4
- package/dist/backend/llvm/dyn.js +27 -11
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +45 -3
- package/dist/backend/llvm/emitter.js +333 -74
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +36 -3
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js +35 -28
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +1 -1
- package/dist/backend/llvm/expr-context.d.ts +2 -0
- package/dist/backend/llvm/expr-control.js +46 -5
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +3 -0
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.d.ts +3 -0
- package/dist/backend/llvm/expr-stream-bridges.js +73 -11
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +18 -2
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +1 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.d.ts +17 -6
- package/dist/backend/llvm/local-array-reads.js +155 -43
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/reference-effects.js +34 -1
- package/dist/backend/llvm/reference-effects.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +14 -3
- package/dist/backend/llvm/shapes.js +43 -12
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +5 -5
- package/dist/backend/llvm/shapes.test.js.map +1 -1
- package/dist/backend/llvm/string-reads.js +29 -26
- package/dist/backend/llvm/string-reads.js.map +1 -1
- package/dist/backend/llvm/string-slices.js +1 -1
- package/dist/backend/llvm/trunc.d.ts +8 -0
- package/dist/backend/llvm/trunc.js +28 -0
- package/dist/backend/llvm/trunc.js.map +1 -0
- package/dist/backend/loop-polls.d.ts +14 -0
- package/dist/backend/loop-polls.js +100 -0
- package/dist/backend/loop-polls.js.map +1 -0
- package/dist/backend/loop-polls.test.d.ts +1 -0
- package/dist/backend/loop-polls.test.js +86 -0
- package/dist/backend/loop-polls.test.js.map +1 -0
- package/dist/backend/may-throw.js +20 -1
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/may-throw.test.js +23 -0
- package/dist/backend/may-throw.test.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +1 -3
- package/dist/backend/native-codegen.js +4 -32
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/runtime-abi.d.ts +2 -2
- package/dist/backend/runtime-abi.js +2 -2
- package/dist/backend/runtime-abi.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack-targets.test.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +1 -1
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/cli/command.js +8 -1
- package/dist/cli/command.js.map +1 -1
- package/dist/cli/output-options.d.ts +1 -0
- package/dist/cli/output-options.js +3 -0
- package/dist/cli/output-options.js.map +1 -1
- package/dist/cli/usage.d.ts +5 -1
- package/dist/cli/usage.js +6 -0
- package/dist/cli/usage.js.map +1 -1
- package/dist/compile-types.d.ts +5 -0
- package/dist/executable/executable-cache.d.ts +2 -0
- package/dist/executable/executable-cache.js +3 -0
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +1 -0
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/frontend/lowering/array-values.js +37 -1
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/builtins/strings.js +4 -1
- package/dist/frontend/lowering/builtins/strings.js.map +1 -1
- package/dist/frontend/lowering/call-arguments.js +96 -4
- package/dist/frontend/lowering/call-arguments.js.map +1 -1
- package/dist/frontend/lowering/class-method-mutations.js +88 -0
- package/dist/frontend/lowering/class-method-mutations.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.js +15 -4
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/expressions/union-fields.d.ts +19 -0
- package/dist/frontend/lowering/expressions/union-fields.js +236 -0
- package/dist/frontend/lowering/expressions/union-fields.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.js +12 -2
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +6 -0
- package/dist/frontend/lowering/lower-exprs.js +162 -3
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +5 -19
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +11 -1
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +5 -0
- package/dist/frontend/lowering/lowerer.js +174 -8
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/nan-coded-reads.d.ts +161 -0
- package/dist/frontend/lowering/nan-coded-reads.js +1342 -0
- package/dist/frontend/lowering/nan-coded-reads.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-bounds.d.ts +14 -2
- package/dist/frontend/lowering/runtime-optional-bounds.js +17 -11
- package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.test.js +28 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +8 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +20 -8
- package/dist/index.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +2 -0
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/constant-globals.d.ts +14 -0
- package/dist/ir/constant-globals.js +81 -0
- package/dist/ir/constant-globals.js.map +1 -0
- package/dist/ir/constant-globals.test.d.ts +1 -0
- package/dist/ir/constant-globals.test.js +97 -0
- package/dist/ir/constant-globals.test.js.map +1 -0
- package/dist/ir/final-fields.d.ts +35 -0
- package/dist/ir/final-fields.js +187 -0
- package/dist/ir/final-fields.js.map +1 -0
- package/dist/ir/final-fields.test.d.ts +1 -0
- package/dist/ir/final-fields.test.js +106 -0
- package/dist/ir/final-fields.test.js.map +1 -0
- package/dist/ir/int32-slots.d.ts +9 -0
- package/dist/ir/int32-slots.js +70 -57
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +27 -0
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-loops.d.ts +5 -0
- package/dist/ir/integer-loops.js +20 -4
- package/dist/ir/integer-loops.js.map +1 -1
- package/dist/ir/integer-loops.test.js +17 -0
- package/dist/ir/integer-loops.test.js.map +1 -1
- package/dist/ir/ir.d.ts +6 -1
- package/dist/ir/ir.js +4 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.d.ts +12 -2
- package/dist/ir/serialize.js +190 -39
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/serialize.test.js +65 -1
- package/dist/ir/serialize.test.js.map +1 -1
- package/dist/native/cache.d.ts +8 -1
- package/dist/native/cache.js +38 -1
- package/dist/native/cache.js.map +1 -1
- package/dist/native/cache.test.js +11 -0
- package/dist/native/cache.test.js.map +1 -1
- package/dist/native/compiler.js +4 -3
- package/dist/native/compiler.js.map +1 -1
- package/dist/native/prepare.d.ts +3 -1
- package/dist/native/prepare.js +63 -28
- package/dist/native/prepare.js.map +1 -1
- package/dist/native/prepare.test.js +23 -1
- package/dist/native/prepare.test.js.map +1 -1
- package/dist/native/text-file.d.ts +10 -0
- package/dist/native/text-file.js +41 -0
- package/dist/native/text-file.js.map +1 -0
- package/dist/text-parts.d.ts +5 -0
- package/dist/text-parts.js +36 -0
- package/dist/text-parts.js.map +1 -0
- package/package.json +20 -20
- package/surface-manifest.json +7 -1
- package/dist/frontend/lowering/expressions/union-field-write.d.ts +0 -8
- package/dist/frontend/lowering/expressions/union-field-write.js +0 -108
- package/dist/frontend/lowering/expressions/union-field-write.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { emitLlvmLayouts } from "./layouts.js";
|
|
2
|
-
import { inlineAllocSupported } from "./alloc.js";
|
|
2
|
+
import { FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js";
|
|
3
3
|
import { llvmBytes as llBytes } from "../literals.js";
|
|
4
4
|
import { InternalCompilerError } from "../../errors.js";
|
|
5
5
|
/** Lower typed IR to LLVM, using the runtime's C ABI.
|
|
@@ -23,6 +23,7 @@ import { createHash } from "node:crypto";
|
|
|
23
23
|
import { deflateRawSync } from "node:zlib";
|
|
24
24
|
import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
25
25
|
import { emitLibraryIdentityLines } from "../library-identity-markers.js";
|
|
26
|
+
import { loopTestsEveryIteration } from "../loop-polls.js";
|
|
26
27
|
import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeEquals, typeKey, STRING, VOID, } from "../../ir/ir.js";
|
|
27
28
|
import { moduleRuntimeFeatures, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, NPM_COMPRESS_MIN, } from "../../ir/runtime-features.js";
|
|
28
29
|
import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js";
|
|
@@ -36,6 +37,8 @@ import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js"
|
|
|
36
37
|
import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
37
38
|
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
38
39
|
import { analyzeInt32Slots } from "../../ir/int32-slots.js";
|
|
40
|
+
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
41
|
+
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
39
42
|
import { findIntegerViews } from "./integer-views.js";
|
|
40
43
|
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
41
44
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
@@ -64,7 +67,7 @@ import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callba
|
|
|
64
67
|
import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
|
|
65
68
|
import { LazyCaptures } from "./lazy-captures.js";
|
|
66
69
|
import { StackCaptures } from "./stack-captures.js";
|
|
67
|
-
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
70
|
+
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, exactIndexLines, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
68
71
|
import { ffiExtendsNarrowIntegers } from "../targets.js";
|
|
69
72
|
import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
|
|
70
73
|
import { emitControlExpr } from "./expr-control.js";
|
|
@@ -127,13 +130,13 @@ function ffiCallbackDummyLl(callback) {
|
|
|
127
130
|
export function emitLlvmModule(mod, options = {}) {
|
|
128
131
|
// Keep source storage intact for debugger inspection in dev builds.
|
|
129
132
|
return new LlEmitter(options.debugSources === undefined
|
|
130
|
-
? scalarizeNumericRecords(specializeNumericCalls(mod, mangleFunction))
|
|
133
|
+
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
131
134
|
: mod, options).emit();
|
|
132
135
|
}
|
|
133
136
|
/** Keep large translation units below the host's single-string limit. */
|
|
134
137
|
export function emitLlvmModuleSource(mod, options = {}) {
|
|
135
138
|
const parts = new LlEmitter(options.debugSources === undefined
|
|
136
|
-
? scalarizeNumericRecords(specializeNumericCalls(mod, mangleFunction))
|
|
139
|
+
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
137
140
|
: mod, options).emitParts();
|
|
138
141
|
const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
|
|
139
142
|
if (length <= 256 * 1024 * 1024)
|
|
@@ -224,12 +227,27 @@ export class LlEmitter {
|
|
|
224
227
|
debugScope = null;
|
|
225
228
|
sizeType;
|
|
226
229
|
ffiExtendNarrowIntegers;
|
|
227
|
-
|
|
230
|
+
cycleBufferedOffset;
|
|
231
|
+
/** Emitted ScrCycType descriptors so far (their collector statistics
|
|
232
|
+
* indices; see ShapeHost.nextCycKind). */
|
|
233
|
+
cycKinds = 0;
|
|
228
234
|
wasi;
|
|
235
|
+
/** The target lacks a rounding instruction at its baseline CPU (x86-64
|
|
236
|
+
* before SSE4.1), so `llvm.trunc` would lower to a libm call: emit
|
|
237
|
+
* truncation inline through an integer round trip instead. */
|
|
238
|
+
inlineTrunc;
|
|
229
239
|
/** ELF worker executables give thread-locals the executable TLS models. */
|
|
230
240
|
executableTls;
|
|
241
|
+
/** Source loops enclosing the statement being emitted in the current
|
|
242
|
+
* function body. */
|
|
243
|
+
loopDepth = 0;
|
|
244
|
+
/** Darwin worker executables read the stack guard from a pthread
|
|
245
|
+
* thread-specific-data slot off the thread pointer (scr_runtime.h's
|
|
246
|
+
* SCR_STACK_GUARD_TSD): the architecture names the access sequence. */
|
|
247
|
+
stackGuardTsd;
|
|
231
248
|
emitLibraryIdentity;
|
|
232
249
|
runtimeAbiMarker;
|
|
250
|
+
detachedExit;
|
|
233
251
|
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
|
234
252
|
* first-use order, the runtime ABI’s determinism discipline. */
|
|
235
253
|
literals = new Map();
|
|
@@ -423,6 +441,11 @@ export class LlEmitter {
|
|
|
423
441
|
/** Active induction bindings retain their width independently of size_t. */
|
|
424
442
|
integerLoopBindings = new Map();
|
|
425
443
|
countedLoopsEnabled = false;
|
|
444
|
+
/** Whole-program final reference fields (ir/final-fields.ts). */
|
|
445
|
+
finalFields;
|
|
446
|
+
/** The current function may borrow final fields of borrowable objects:
|
|
447
|
+
* not a constructor (where they change), not async or a generator. */
|
|
448
|
+
finalFieldBorrows = false;
|
|
426
449
|
integerRanges = new Map();
|
|
427
450
|
byteWindowsEnabled = true;
|
|
428
451
|
byteWindowEntries = new Map();
|
|
@@ -472,22 +495,41 @@ export class LlEmitter {
|
|
|
472
495
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
473
496
|
this.initializerBindings = findInitializerBindings(mod);
|
|
474
497
|
this.int32Slots = analyzeInt32Slots(mod);
|
|
498
|
+
this.finalFields = analyzeFinalFields(mod);
|
|
475
499
|
this.sizeType = options.pointerBits === 32 ? "i32" : "i64";
|
|
476
500
|
this.ffiExtendNarrowIntegers =
|
|
477
501
|
options.wasi === true || ffiExtendsNarrowIntegers(options.targetTriple);
|
|
478
502
|
this.wasi = options.wasi === true;
|
|
503
|
+
// An empty triple targets the host, as for executableTls below: the
|
|
504
|
+
// Node-hosted CLI passes none, the native CLI passes its toolchain's, and
|
|
505
|
+
// both must emit the same module for the same target.
|
|
506
|
+
this.inlineTrunc =
|
|
507
|
+
!this.wasi &&
|
|
508
|
+
(options.targetTriple
|
|
509
|
+
? /^(x86_64|amd64)\b/i.test(options.targetTriple)
|
|
510
|
+
: process.arch === "x64");
|
|
479
511
|
this.executableTls =
|
|
480
512
|
mod.workers === true &&
|
|
481
513
|
mod.lib === undefined &&
|
|
482
514
|
!this.wasi &&
|
|
483
515
|
!/apple|darwin|windows|mingw|cygwin|win32/.test(options.targetTriple || process.platform);
|
|
516
|
+
const host = options.targetTriple || `${process.arch}-${process.platform}`;
|
|
517
|
+
this.stackGuardTsd =
|
|
518
|
+
mod.workers === true && mod.lib === undefined && !this.wasi && /apple|darwin/.test(host)
|
|
519
|
+
? /^(arm64|aarch64)/.test(host)
|
|
520
|
+
? "aarch64"
|
|
521
|
+
: /^(x86_64|amd64|x64)/.test(host)
|
|
522
|
+
? "x86_64"
|
|
523
|
+
: null
|
|
524
|
+
: null;
|
|
484
525
|
this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
|
|
485
526
|
this.runtimeAbiMarker = options.runtimeAbiMarker === true;
|
|
527
|
+
this.detachedExit = options.detachedExit === true;
|
|
486
528
|
this.rcHelpers = options.inlineRc === true ? new Set() : null;
|
|
487
|
-
// ScrCycHdr.
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
this.
|
|
529
|
+
// ScrCycHdr.buffered sits 6 bytes behind every object. The wasm32
|
|
530
|
+
// header pads before its descriptor so its payload remains
|
|
531
|
+
// double-aligned without changing this ABI offset.
|
|
532
|
+
this.cycleBufferedOffset = 6;
|
|
491
533
|
this.ffiCallbackAdapters = allocateFfiCallbackAdapters(mod.ffiImports ?? []);
|
|
492
534
|
this.ffiHasRetainedCallback = hasRetainedFfiCallback(mod.ffiImports ?? []);
|
|
493
535
|
this.ffiHasForeignCallback = hasForeignFfiCallback(mod.ffiImports ?? []);
|
|
@@ -522,7 +564,8 @@ export class LlEmitter {
|
|
|
522
564
|
this.nullableFields = new NullableRefFields(mod.classes ?? [], this.boxedUnionsById);
|
|
523
565
|
// Optional array reads also cover nullable unions: emitLocalArrayRead
|
|
524
566
|
// then yields the (borrowed or owned) element pointer, not a stack box.
|
|
525
|
-
|
|
567
|
+
// Arrays of such a union are read the same way.
|
|
568
|
+
this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById, (id) => this.nullableUnions.has(id));
|
|
526
569
|
this.checkedNarrows = new CheckedNarrows(this.fnByName);
|
|
527
570
|
this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null, mod.classes ?? []);
|
|
528
571
|
const walkParameters = new Map();
|
|
@@ -578,7 +621,7 @@ export class LlEmitter {
|
|
|
578
621
|
this.tracedUnions = traced.unions;
|
|
579
622
|
this.shapeHost = {
|
|
580
623
|
sizeType: this.sizeType,
|
|
581
|
-
|
|
624
|
+
cycleBufferedOffset: this.cycleBufferedOffset,
|
|
582
625
|
tracedShapes: this.tracedShapes,
|
|
583
626
|
tracedUnions: this.tracedUnions,
|
|
584
627
|
nullableUnions: this.nullableUnions,
|
|
@@ -595,6 +638,7 @@ export class LlEmitter {
|
|
|
595
638
|
// paths address its thread-local state.
|
|
596
639
|
threadLocalAlloc: mod.workers === true,
|
|
597
640
|
rcHelpers: this.rcHelpers,
|
|
641
|
+
nextCycKind: () => FIRST_EMITTED_CYC_KIND + this.cycKinds++,
|
|
598
642
|
unionsById: this.unionsById,
|
|
599
643
|
declare: (decl) => this.declare(decl),
|
|
600
644
|
needOom: () => this.needOom(),
|
|
@@ -997,14 +1041,16 @@ export class LlEmitter {
|
|
|
997
1041
|
if (!this.executableTls)
|
|
998
1042
|
return parts;
|
|
999
1043
|
// A worker executable's thread-locals all live in the executable's own
|
|
1000
|
-
// TLS block
|
|
1001
|
-
//
|
|
1002
|
-
//
|
|
1003
|
-
//
|
|
1004
|
-
//
|
|
1044
|
+
// TLS block: the program's internal ones, and the runtime's external
|
|
1045
|
+
// ones, because the runtime is always linked statically into the
|
|
1046
|
+
// executable. So on ELF they can all use the local-exec model: LLVM's
|
|
1047
|
+
// default general-dynamic sequence (relaxed by the linker to `mov %fs:0`
|
|
1048
|
+
// plus an add), and even initial-exec (relaxed to `mov $offset` plus an
|
|
1049
|
+
// indexed access), become one %fs-relative operand LLVM can fold into
|
|
1050
|
+
// loads and compares. Darwin's thread-local variables have one model.
|
|
1005
1051
|
return parts.map((part) => part
|
|
1006
1052
|
.replace(/^(@[-$._A-Za-z0-9]+ = internal )thread_local global /gm, "$1thread_local(localexec) global ")
|
|
1007
|
-
.replace(/^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, "$1thread_local(
|
|
1053
|
+
.replace(/^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, "$1thread_local(localexec) global "));
|
|
1008
1054
|
}
|
|
1009
1055
|
emitModuleParts() {
|
|
1010
1056
|
// Function bodies first (the literal/unit/fn-value tables fill as they
|
|
@@ -1356,10 +1402,13 @@ export class LlEmitter {
|
|
|
1356
1402
|
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr }`,
|
|
1357
1403
|
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1358
1404
|
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
|
|
1359
|
-
// ScrArr mirrors scr_runtime.h field-for-field
|
|
1360
|
-
//
|
|
1361
|
-
//
|
|
1362
|
-
|
|
1405
|
+
// ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem,
|
|
1406
|
+
// elem_retain, elem_release, elem_trace, data, present, ext }, where
|
|
1407
|
+
// ext points at the out-of-line sparse, property and metadata storage
|
|
1408
|
+
// (NULL until first used). Live dynamic stream commits swap its dense,
|
|
1409
|
+
// presence and out-of-line storage while preserving the target
|
|
1410
|
+
// object's identity.
|
|
1411
|
+
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, ptr, ptr, ptr, ptr }`,
|
|
1363
1412
|
// The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
|
|
1364
1413
|
// val_trace, key_retain, key_release, key_trace } — the inline RC
|
|
1365
1414
|
// fast paths test the two trace slots (shapes.ts).
|
|
@@ -1422,6 +1471,10 @@ export class LlEmitter {
|
|
|
1422
1471
|
const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
|
|
1423
1472
|
out.push(``, `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`, ...(this.mod.workers ? [`declare void @scr_runtime_workers_v8()`] : []), ...(this.stackChecks?.size ? [`declare void @scr_stack_guard_init()`] : []), ...(this.runtimeAbiMarker && this.mod.lib === undefined
|
|
1424
1473
|
? [`declare void @${RUNTIME_ABI_MARKER}()`]
|
|
1474
|
+
: []),
|
|
1475
|
+
// Overrides the runtime's weak default (scr_lib.c).
|
|
1476
|
+
...(this.detachedExit && this.mod.lib === undefined
|
|
1477
|
+
? [`@scr_detached_exit = constant i8 1`]
|
|
1425
1478
|
: []));
|
|
1426
1479
|
for (const d of this.decls)
|
|
1427
1480
|
out.push(d);
|
|
@@ -1429,10 +1482,9 @@ export class LlEmitter {
|
|
|
1429
1482
|
const flushedDecls = new Set(this.decls);
|
|
1430
1483
|
for (const table of this.constantNumericTables.values()) {
|
|
1431
1484
|
const n = table.values.length;
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
` %index = fptoui double %i to ${this.sizeType}`, ` %roundtrip = uitofp ${this.sizeType} %index to double`, ` %integer = fcmp oeq double %roundtrip, %i`, ` br i1 %integer, label %read, label %fallback`, `read:`, ` %slot = getelementptr inbounds [${n} x double], ptr @${table.symbol}, ${this.sizeType} 0, ${this.sizeType} %index`, ` %value = load double, ptr %slot`, ` ret double %value`, `fallback:`, ` %generic = call double @scr_arr_get_number(ptr %a, double %i)`, ` ret double %generic`, `}`, ``);
|
|
1485
|
+
let tableTmp = 0;
|
|
1486
|
+
const tableIndex = exactIndexLines("%i", String(n), "i64", () => `%ix${tableTmp++}`);
|
|
1487
|
+
out.push(`@${table.symbol} = private constant [${n} x double] [${table.values.map((v) => `double ${f64Lit(v)}`).join(", ")}]`, `define internal double @${table.symbol}_get(ptr %a, double %i) alwaysinline #0 {`, `entry:`, ` %initialized = icmp ne ptr %a, null`, ...tableIndex.lines.map((line) => ` ${line}`), ` %safe = and i1 %initialized, ${tableIndex.ok}`, ` br i1 %safe, label %read, label %fallback`, `read:`, ` %slot = getelementptr inbounds [${n} x double], ptr @${table.symbol}, i64 0, i64 ${tableIndex.wide}`, ` %value = load double, ptr %slot`, ` ret double %value`, `fallback:`, ` %generic = call double @scr_arr_get_number(ptr %a, double %i)`, ` ret double %generic`, `}`, ``);
|
|
1436
1488
|
}
|
|
1437
1489
|
for (const [text, lit] of this.literals) {
|
|
1438
1490
|
// Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } —
|
|
@@ -1499,7 +1551,7 @@ export class LlEmitter {
|
|
|
1499
1551
|
// strings — a no-op.
|
|
1500
1552
|
const n = inst.slots.length;
|
|
1501
1553
|
const present = n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`;
|
|
1502
|
-
out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n}, i32 2, ptr null, ptr null, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null
|
|
1554
|
+
out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n}, i32 2, ptr null, ptr null, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null }`);
|
|
1503
1555
|
}
|
|
1504
1556
|
if (this.templateStringsInstances.size > 0)
|
|
1505
1557
|
out.push(``);
|
|
@@ -2024,7 +2076,13 @@ export class LlEmitter {
|
|
|
2024
2076
|
// keeps the complete SameValueZero semantics.
|
|
2025
2077
|
if (this.decls.has(NUMBER_MAP_ENTRY_DECL)) {
|
|
2026
2078
|
const sz = this.sizeType;
|
|
2027
|
-
|
|
2079
|
+
let mapTmp = 0;
|
|
2080
|
+
const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`);
|
|
2081
|
+
defs.push(`define internal ptr @sc_map_entry_f64(ptr %m, double %k) ${FN_ATTRS} {`, `entry:`, ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 19`, ` %nd = load ${sz}, ptr %ndp`,
|
|
2082
|
+
// A non-integral key inside the table is as absent as one outside it.
|
|
2083
|
+
...mapIndex.lines.map((line) => ` ${line}`), ` br i1 ${mapIndex.ok}, label %slot, label %outside`, `slot:`, sz === "i64"
|
|
2084
|
+
? ` %i = add i64 ${mapIndex.wide}, 0`
|
|
2085
|
+
: ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 18`, ` %dense = load ptr, ptr %dp`, ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, ` %s = load i32, ptr %sp`, ` %empty = icmp eq i32 %s, 0`, ` br i1 %empty, label %miss, label %hit`, `hit:`, ` %e32 = sub i32 %s, 1`, sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 12`, ` %entries = load ptr, ptr %ep`, ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, ` ret ptr %found`, `miss:`, ` ret ptr null`, `outside:`, ` %dense.any = icmp ne ${sz} %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k)`, ` ret ptr %r`, `}`, ``);
|
|
2028
2086
|
}
|
|
2029
2087
|
// String comparisons enter through small internal wrappers that LLVM
|
|
2030
2088
|
// inlines at each site: interned literals and shared values compare by
|
|
@@ -2833,13 +2891,28 @@ export class LlEmitter {
|
|
|
2833
2891
|
* return and stays eligible for tail-call and recursion elimination. */
|
|
2834
2892
|
emitUnwind(passThrough) {
|
|
2835
2893
|
if (this.mod.workers && this.tryStack.length > 0) {
|
|
2836
|
-
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
|
2837
|
-
const stopped = this.B.tmp();
|
|
2838
|
-
this.B.line(`${stopped} = call zeroext i1 @scr_context_stopping()`);
|
|
2839
2894
|
const stop = this.B.newLabel("context.stop");
|
|
2840
2895
|
const ordinary = this.B.newLabel("context.throw");
|
|
2841
|
-
this.
|
|
2842
|
-
|
|
2896
|
+
if (this.executableTls) {
|
|
2897
|
+
// A set stop bit unwinds while the active cell may still be clear:
|
|
2898
|
+
// install the termination sentinel before skipping the handlers.
|
|
2899
|
+
const word = this.loadExcFlag();
|
|
2900
|
+
const bit = this.B.tmp();
|
|
2901
|
+
const stopped = this.B.tmp();
|
|
2902
|
+
this.B.line(`${bit} = and i32 ${word}, 2`);
|
|
2903
|
+
this.B.line(`${stopped} = icmp ne i32 ${bit}, 0`);
|
|
2904
|
+
this.B.condBr(stopped, stop, ordinary);
|
|
2905
|
+
this.B.startBlock(stop);
|
|
2906
|
+
this.declare(`declare zeroext i1 @scr_context_checkpoint()`);
|
|
2907
|
+
this.B.line(`call zeroext i1 @scr_context_checkpoint()`);
|
|
2908
|
+
}
|
|
2909
|
+
else {
|
|
2910
|
+
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
|
2911
|
+
const stopped = this.B.tmp();
|
|
2912
|
+
this.B.line(`${stopped} = call zeroext i1 @scr_context_stopping()`);
|
|
2913
|
+
this.B.condBr(stopped, stop, ordinary);
|
|
2914
|
+
this.B.startBlock(stop);
|
|
2915
|
+
}
|
|
2843
2916
|
const handlers = this.tryStack;
|
|
2844
2917
|
this.tryStack = [];
|
|
2845
2918
|
this.emitUnwind();
|
|
@@ -2896,6 +2969,18 @@ export class LlEmitter {
|
|
|
2896
2969
|
}
|
|
2897
2970
|
this.B.br(cleanup.label);
|
|
2898
2971
|
}
|
|
2972
|
+
/** Loads the running thread's exact pending word (ELF worker
|
|
2973
|
+
* executables, scr_exception.c): bit 0 mirrors the active cell, bit 1
|
|
2974
|
+
* requests the context's stop. It lives in the executable's static TLS
|
|
2975
|
+
* block, so the access is one thread-pointer-relative load. Monotonic by
|
|
2976
|
+
* default: a loop must not hoist the load out and miss a cross-thread
|
|
2977
|
+
* stop (see emitPendingCheck for the unordered tests). */
|
|
2978
|
+
loadExcFlag(unordered = false) {
|
|
2979
|
+
this.declare(`@scr_exc_flag = external thread_local(localexec) global i32, align 4`);
|
|
2980
|
+
const word = this.B.tmp();
|
|
2981
|
+
this.B.line(`${word} = load atomic i32, ptr @scr_exc_flag ${unordered ? "unordered" : "monotonic"}, align 4`);
|
|
2982
|
+
return word;
|
|
2983
|
+
}
|
|
2899
2984
|
/** Instructions (two-space indented) that set the i1 `dest` to "an
|
|
2900
2985
|
* exception is pending in the active context" — scr_exc_pending()'s
|
|
2901
2986
|
* meaning — for the helper and adapter bodies that test it outside
|
|
@@ -2922,8 +3007,9 @@ export class LlEmitter {
|
|
|
2922
3007
|
* call's result temp must join its frame BEFORE this runs so the unwind
|
|
2923
3008
|
* releases the dummy (NULL for refcounted kinds) harmlessly. A scalar
|
|
2924
3009
|
* call result of the function's return type may be passed as the unwind's
|
|
2925
|
-
* dummy value (see emitUnwind).
|
|
2926
|
-
|
|
3010
|
+
* dummy value (see emitUnwind). `poll` marks a worker termination poll
|
|
3011
|
+
* (function entry or loop budget), whose test must stay ordered. */
|
|
3012
|
+
emitPendingCheck(passThrough, poll = false) {
|
|
2927
3013
|
const through = passThrough !== undefined &&
|
|
2928
3014
|
this.llType(passThrough.type) === this.llType(this.currentReturnType) &&
|
|
2929
3015
|
(passThrough.type.kind === "f64" || passThrough.type.kind === "bool")
|
|
@@ -2945,16 +3031,35 @@ export class LlEmitter {
|
|
|
2945
3031
|
return;
|
|
2946
3032
|
}
|
|
2947
3033
|
// Executables test the active exception cell's kind inline (its first
|
|
2948
|
-
// field).
|
|
2949
|
-
// is nonzero
|
|
2950
|
-
//
|
|
2951
|
-
//
|
|
2952
|
-
//
|
|
2953
|
-
//
|
|
3034
|
+
// field). ELF worker executables test the thread's exact pending word,
|
|
3035
|
+
// which is also nonzero once the context must stop, and unwind directly:
|
|
3036
|
+
// no site carries an out-of-line call, so LLVM's inliner sees the check
|
|
3037
|
+
// as two instructions. Other worker programs test the process-wide
|
|
3038
|
+
// alert word: it is nonzero while any script thread may have an
|
|
3039
|
+
// exception pending in its active cell or has been asked to stop, so
|
|
3040
|
+
// the common case is one ordinary global load (a thread-local access is
|
|
3041
|
+
// a call on Darwin) and the out-of-line scr_exc_pending answers for this
|
|
3042
|
+
// thread, observing cancellation and reinstalling the termination
|
|
3043
|
+
// sentinel.
|
|
2954
3044
|
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
2955
3045
|
let word;
|
|
2956
|
-
|
|
2957
|
-
|
|
3046
|
+
const exact = this.mod.workers !== true || this.executableTls;
|
|
3047
|
+
if (this.executableTls) {
|
|
3048
|
+
// Termination stays observable where the polls were dropped for it:
|
|
3049
|
+
// polls, tests inside source loops (exc's loop-poll rule relies on
|
|
3050
|
+
// them every iteration) and tail-position tests (a dead ordered load
|
|
3051
|
+
// keeps recursion from becoming a loop without stack growth) use an
|
|
3052
|
+
// ordered load, which LLVM never deletes, hoists or merges. Every
|
|
3053
|
+
// other test is an unordered load: it reads the same word, but LLVM's
|
|
3054
|
+
// memory analyses no longer treat it as a write, so loads around it
|
|
3055
|
+
// stay forwardable after inlining. Same-thread changes only happen in
|
|
3056
|
+
// runtime calls, which clobber memory.
|
|
3057
|
+
word = this.loadExcFlag(!poll && through === undefined && this.loopDepth === 0);
|
|
3058
|
+
}
|
|
3059
|
+
else if (this.mod.workers === true) {
|
|
3060
|
+
// dso_local: executables link the runtime statically, so the word is
|
|
3061
|
+
// addressed directly instead of through the GOT.
|
|
3062
|
+
this.declare(`@scr_exc_alert = external dso_local global i32, align 64`);
|
|
2958
3063
|
word = B.tmp();
|
|
2959
3064
|
B.line(`${word} = load atomic i32, ptr @scr_exc_alert monotonic, align 64`);
|
|
2960
3065
|
}
|
|
@@ -2984,7 +3089,7 @@ export class LlEmitter {
|
|
|
2984
3089
|
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${hit}, i1 false)`);
|
|
2985
3090
|
const lu = B.newLabel("exc.u");
|
|
2986
3091
|
const lk = B.newLabel("exc.k");
|
|
2987
|
-
if (
|
|
3092
|
+
if (!exact) {
|
|
2988
3093
|
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2989
3094
|
const slow = B.newLabel("exc.s");
|
|
2990
3095
|
B.condBr(cold, slow, lk);
|
|
@@ -3016,30 +3121,98 @@ export class LlEmitter {
|
|
|
3016
3121
|
* throw Node's RangeError and unwind like any other pending exception. */
|
|
3017
3122
|
emitStackCheck() {
|
|
3018
3123
|
const B = this.B;
|
|
3019
|
-
const tl = this.mod.workers === true ? "thread_local " : "";
|
|
3020
|
-
this.declare(`@scr_stack_guard = external ${tl}global ptr`);
|
|
3021
3124
|
this.declare(`declare ptr @llvm.stacksave.p0()`);
|
|
3022
3125
|
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
3023
3126
|
this.declare(`declare void @scr_stack_overflow()`);
|
|
3024
|
-
const sp = B.tmp();
|
|
3025
|
-
const guard = B.tmp();
|
|
3026
|
-
const low = B.tmp();
|
|
3027
|
-
const cold = B.tmp();
|
|
3028
|
-
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
3029
|
-
B.line(`${guard} = load ptr, ptr @scr_stack_guard`);
|
|
3030
|
-
B.line(`${low} = icmp ult ptr ${sp}, ${guard}`);
|
|
3031
|
-
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${low}, i1 false)`);
|
|
3032
3127
|
const over = B.newLabel("stack.over");
|
|
3033
3128
|
const ok = B.newLabel("stack.ok");
|
|
3034
|
-
B.
|
|
3129
|
+
const sp = B.tmp();
|
|
3130
|
+
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
3131
|
+
const test = (guard) => {
|
|
3132
|
+
const low = B.tmp();
|
|
3133
|
+
const cold = B.tmp();
|
|
3134
|
+
B.line(`${low} = icmp ult ptr ${sp}, ${guard}`);
|
|
3135
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${low}, i1 false)`);
|
|
3136
|
+
B.condBr(cold, over, ok);
|
|
3137
|
+
};
|
|
3138
|
+
if (this.stackGuardTsd !== null) {
|
|
3139
|
+
// A Darwin thread-local access is a call to its TLV thunk: read the
|
|
3140
|
+
// guard from its thread-specific-data slot at the runtime-validated
|
|
3141
|
+
// offset instead, and call out only when the runtime found no slot.
|
|
3142
|
+
this.declare(`@scr_stack_guard_tsd = external dso_local global i64`);
|
|
3143
|
+
this.declare(`declare i64 @scr_stack_guard_get()`);
|
|
3144
|
+
const offset = B.tmp();
|
|
3145
|
+
const direct = B.tmp();
|
|
3146
|
+
const likely = B.tmp();
|
|
3147
|
+
B.line(`${offset} = load i64, ptr @scr_stack_guard_tsd`);
|
|
3148
|
+
B.line(`${direct} = icmp ne i64 ${offset}, 0`);
|
|
3149
|
+
B.line(`${likely} = call i1 @llvm.expect.i1(i1 ${direct}, i1 true)`);
|
|
3150
|
+
const fast = B.newLabel("stack.tsd");
|
|
3151
|
+
const slow = B.newLabel("stack.tlv");
|
|
3152
|
+
B.condBr(likely, fast, slow);
|
|
3153
|
+
B.startBlock(fast);
|
|
3154
|
+
const guard = B.tmp();
|
|
3155
|
+
if (this.stackGuardTsd === "aarch64") {
|
|
3156
|
+
this.declare(`declare i64 @llvm.read_register.i64(metadata)`);
|
|
3157
|
+
const thread = B.tmp();
|
|
3158
|
+
const base = B.tmp();
|
|
3159
|
+
const slot = B.tmp();
|
|
3160
|
+
const address = B.tmp();
|
|
3161
|
+
B.line(`${thread} = call i64 @llvm.read_register.i64(metadata !{!"tpidrro_el0"})`);
|
|
3162
|
+
B.line(`${base} = and i64 ${thread}, -8`);
|
|
3163
|
+
B.line(`${slot} = add i64 ${base}, ${offset}`);
|
|
3164
|
+
B.line(`${address} = inttoptr i64 ${slot} to ptr`);
|
|
3165
|
+
B.line(`${guard} = load ptr, ptr ${address}`);
|
|
3166
|
+
}
|
|
3167
|
+
else {
|
|
3168
|
+
const address = B.tmp();
|
|
3169
|
+
B.line(`${address} = inttoptr i64 ${offset} to ptr addrspace(256)`);
|
|
3170
|
+
B.line(`${guard} = load ptr, ptr addrspace(256) ${address}`);
|
|
3171
|
+
}
|
|
3172
|
+
test(guard);
|
|
3173
|
+
B.startBlock(slow);
|
|
3174
|
+
// uintptr_t scr_stack_guard_get(void): an i64 in the C ABI.
|
|
3175
|
+
const raw = B.tmp();
|
|
3176
|
+
const fallback = B.tmp();
|
|
3177
|
+
B.line(`${raw} = call i64 @scr_stack_guard_get()`);
|
|
3178
|
+
B.line(`${fallback} = inttoptr i64 ${raw} to ptr`);
|
|
3179
|
+
test(fallback);
|
|
3180
|
+
}
|
|
3181
|
+
else {
|
|
3182
|
+
const tl = this.mod.workers === true ? "thread_local " : "";
|
|
3183
|
+
this.declare(`@scr_stack_guard = external ${tl}global ptr`);
|
|
3184
|
+
const guard = B.tmp();
|
|
3185
|
+
B.line(`${guard} = load ptr, ptr @scr_stack_guard`);
|
|
3186
|
+
test(guard);
|
|
3187
|
+
}
|
|
3035
3188
|
B.startBlock(over);
|
|
3036
3189
|
B.line(`call void @scr_stack_overflow()`);
|
|
3037
3190
|
this.emitUnwind();
|
|
3038
3191
|
B.startBlock(ok);
|
|
3039
3192
|
}
|
|
3040
|
-
|
|
3193
|
+
/** Whether the emitter follows this call with a pending test (the
|
|
3194
|
+
* conditions in expr-calls.ts). */
|
|
3195
|
+
callTestsPending(e) {
|
|
3196
|
+
switch (e.kind) {
|
|
3197
|
+
case "call":
|
|
3198
|
+
return this.mayThrow.has(e.callee);
|
|
3199
|
+
case "new":
|
|
3200
|
+
return this.mayThrow.has(`%${e.className}.constructor`);
|
|
3201
|
+
case "virtualCall":
|
|
3202
|
+
return this.mayThrowMethods.has(e.method);
|
|
3203
|
+
case "callValue":
|
|
3204
|
+
return this.indirectMayThrow;
|
|
3205
|
+
default:
|
|
3206
|
+
return false;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
workerLoopBudget(loop) {
|
|
3041
3210
|
if (!this.mod.workers)
|
|
3042
3211
|
return null;
|
|
3212
|
+
// A loop that runs a pending test on every iteration observes a
|
|
3213
|
+
// terminate request through it; a separate poll would only add work.
|
|
3214
|
+
if (loopTestsEveryIteration(loop, (e) => this.callTestsPending(e)))
|
|
3215
|
+
return null;
|
|
3043
3216
|
const slot = this.B.slot();
|
|
3044
3217
|
this.B.entryAllocas.push(`${slot} = alloca i32 ; worker cancellation budget`);
|
|
3045
3218
|
this.B.line(`store i32 0, ptr ${slot}`);
|
|
@@ -3061,7 +3234,7 @@ export class LlEmitter {
|
|
|
3061
3234
|
// Calls and throwing operations retain their own checks. Amortize the
|
|
3062
3235
|
// extra cancellation poll across bounded loop batches so scalar work
|
|
3063
3236
|
// does not cross the runtime ABI on every iteration.
|
|
3064
|
-
this.emitPendingCheck();
|
|
3237
|
+
this.emitPendingCheck(undefined, true);
|
|
3065
3238
|
B.br(next);
|
|
3066
3239
|
B.startBlock(next);
|
|
3067
3240
|
}
|
|
@@ -3665,18 +3838,38 @@ export class LlEmitter {
|
|
|
3665
3838
|
this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
|
|
3666
3839
|
B.line(`call void @scr_box_set_ref(ptr ${box}, ptr ${cell})`);
|
|
3667
3840
|
}
|
|
3841
|
+
/** The payload a TDZ box holds before its declaration runs, when that is
|
|
3842
|
+
* not NULL. A nullable union's value is its arm pointer with NULL as a
|
|
3843
|
+
* unit arm (nullable-unions.ts), so a stored `null`/`undefined` would
|
|
3844
|
+
* read back as the temporal dead zone; such boxes start at the module's
|
|
3845
|
+
* immortal NULLABLE_ABSENT sentinel instead, which no value of the
|
|
3846
|
+
* binding can hold and every RC entry point and the collector skip. */
|
|
3847
|
+
tdzEmptySentinel(t) {
|
|
3848
|
+
if (!this.nullableUnions.of(t))
|
|
3849
|
+
return null;
|
|
3850
|
+
this.needsNullableAbsent = true;
|
|
3851
|
+
this.immortalValues.add(NULLABLE_ABSENT);
|
|
3852
|
+
return NULLABLE_ABSENT;
|
|
3853
|
+
}
|
|
3668
3854
|
/** The TDZ-guarded read of a boxed binding: an empty payload slot is
|
|
3669
3855
|
* the temporal dead zone — throw Node's exact catchable ReferenceError
|
|
3670
3856
|
* (exprs.ts's varRef guard). Scalars then peek the one-element
|
|
3671
3857
|
* array cell; ref kinds read the box normally (+1). */
|
|
3672
|
-
checkTdz(box, name) {
|
|
3858
|
+
checkTdz(box, t, name) {
|
|
3673
3859
|
const B = this.B;
|
|
3674
3860
|
const slotp = B.tmp();
|
|
3675
3861
|
const slotv = B.tmp();
|
|
3676
3862
|
const empty = B.tmp();
|
|
3677
3863
|
B.line(`${slotp} = getelementptr inbounds %ScrBox, ptr ${box}, i64 0, i32 5`);
|
|
3678
|
-
|
|
3679
|
-
|
|
3864
|
+
const sentinel = this.tdzEmptySentinel(t);
|
|
3865
|
+
if (sentinel !== null) {
|
|
3866
|
+
B.line(`${slotv} = load ptr, ptr ${slotp}`);
|
|
3867
|
+
B.line(`${empty} = icmp eq ptr ${slotv}, ${sentinel}`);
|
|
3868
|
+
}
|
|
3869
|
+
else {
|
|
3870
|
+
B.line(`${slotv} = load i64, ptr ${slotp}`);
|
|
3871
|
+
B.line(`${empty} = icmp eq i64 ${slotv}, 0`);
|
|
3872
|
+
}
|
|
3680
3873
|
this.throwIfUninitialized(empty, name);
|
|
3681
3874
|
return slotv;
|
|
3682
3875
|
}
|
|
@@ -3713,7 +3906,7 @@ export class LlEmitter {
|
|
|
3713
3906
|
B.startBlock(lk);
|
|
3714
3907
|
}
|
|
3715
3908
|
tdzBoxRead(box, t, name) {
|
|
3716
|
-
const slotv = this.checkTdz(box, name);
|
|
3909
|
+
const slotv = this.checkTdz(box, t, name);
|
|
3717
3910
|
const B = this.B;
|
|
3718
3911
|
const acc = boxAccess(t);
|
|
3719
3912
|
if (acc === "ref")
|
|
@@ -3733,7 +3926,7 @@ export class LlEmitter {
|
|
|
3733
3926
|
writeBindingBox(box, local, value, initializes = false, borrowed = false) {
|
|
3734
3927
|
if (local.tdz) {
|
|
3735
3928
|
const first = initializes || !local.mutable;
|
|
3736
|
-
const slotv = first ? null : this.checkTdz(box, local.name);
|
|
3929
|
+
const slotv = first ? null : this.checkTdz(box, local.type, local.name);
|
|
3737
3930
|
const acc = boxAccess(local.type);
|
|
3738
3931
|
if (acc !== "ref") {
|
|
3739
3932
|
if (first)
|
|
@@ -3912,6 +4105,8 @@ export class LlEmitter {
|
|
|
3912
4105
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
3913
4106
|
this.integerLoopBindings.clear();
|
|
3914
4107
|
this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
|
|
4108
|
+
this.finalFieldBorrows =
|
|
4109
|
+
!fn.async && !fn.generator && constructorClass(fn.name) === null && this.finalFields.size > 0;
|
|
3915
4110
|
this.byteWindowsEnabled = this.countedLoopsEnabled;
|
|
3916
4111
|
this.byteWindowEntries =
|
|
3917
4112
|
this.byteWindowsEnabled && numericFn.locals.some((l) => l.type.kind === "bytes")
|
|
@@ -3924,8 +4119,8 @@ export class LlEmitter {
|
|
|
3924
4119
|
this.debug === null
|
|
3925
4120
|
? findLoopArrayBorrows(fn, this.callLifetimes, this.referenceEffects, this.optionalArrayReads)
|
|
3926
4121
|
: new Set();
|
|
3927
|
-
this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads, this.loopArrayBorrows);
|
|
3928
|
-
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes);
|
|
4122
|
+
this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads, this.loopArrayBorrows, this.debug === null ? this.referenceEffects : undefined);
|
|
4123
|
+
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes, this.debug === null ? this.referenceEffects : undefined);
|
|
3929
4124
|
this.scalarStringSlices =
|
|
3930
4125
|
this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
|
|
3931
4126
|
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
@@ -3938,6 +4133,7 @@ export class LlEmitter {
|
|
|
3938
4133
|
this.chainSlots.clear();
|
|
3939
4134
|
this.finallyStack = [];
|
|
3940
4135
|
this.tryStack = [];
|
|
4136
|
+
this.loopDepth = 0;
|
|
3941
4137
|
this.currentReturnType = fn.returnType;
|
|
3942
4138
|
this.currentGenerator = fn.generator ?? null;
|
|
3943
4139
|
this.currentBorrowedReturn = this.borrowedReturns.has(fn.name);
|
|
@@ -4100,7 +4296,7 @@ export class LlEmitter {
|
|
|
4100
4296
|
if (this.stackChecks?.has(fn.name))
|
|
4101
4297
|
this.emitStackCheck();
|
|
4102
4298
|
if (this.workerEntryPolls?.has(fn.name))
|
|
4103
|
-
this.emitPendingCheck();
|
|
4299
|
+
this.emitPendingCheck(undefined, true);
|
|
4104
4300
|
this.emitStmts(fn.body);
|
|
4105
4301
|
// Implicit exit of a void function: release the function scope unless
|
|
4106
4302
|
// the body already terminated its final block (return, or a throw
|
|
@@ -4250,11 +4446,24 @@ export class LlEmitter {
|
|
|
4250
4446
|
return this.canBorrowCallArgument(value.value);
|
|
4251
4447
|
if (value.kind === "libCall")
|
|
4252
4448
|
return value.fn === "error.nodeThrow";
|
|
4449
|
+
// A final field never changes outside constructors: while its object is
|
|
4450
|
+
// borrowable, so is the field's value. Nullable-pointer fields have no
|
|
4451
|
+
// box to lend and keep their owning path.
|
|
4452
|
+
if (value.kind === "fieldGet" &&
|
|
4453
|
+
this.finalFieldBorrows &&
|
|
4454
|
+
isRefCounted(value.type) &&
|
|
4455
|
+
this.finalFields.isFinal(value.className, value.field) &&
|
|
4456
|
+
!this.nullableFieldGet(value))
|
|
4457
|
+
return this.canBorrowReceiver(value.obj) && this.canBorrowCallArgument(value.obj);
|
|
4253
4458
|
if (value.kind === "ternary")
|
|
4254
4459
|
return (this.canBorrowReceiver(value.then) &&
|
|
4255
4460
|
this.canBorrowReceiver(value.else_) &&
|
|
4256
4461
|
this.canBorrowCallArgument(value.then) &&
|
|
4257
4462
|
this.canBorrowCallArgument(value.else_));
|
|
4463
|
+
// An optional chain's bound receiver stays unchanged until its body
|
|
4464
|
+
// finishes (see emitReadReceiver).
|
|
4465
|
+
if (value.kind === "chainRecv")
|
|
4466
|
+
return this.chainSlots.has(value.id);
|
|
4258
4467
|
if (value.kind !== "varRef")
|
|
4259
4468
|
return false;
|
|
4260
4469
|
const binding = this.binding(value.localId);
|
|
@@ -4296,11 +4505,16 @@ export class LlEmitter {
|
|
|
4296
4505
|
emitStmt(s) {
|
|
4297
4506
|
const previous = this.B.debugLocation;
|
|
4298
4507
|
this.B.debugLocation = this.debug?.location(s.loc, this.debugScope) ?? null;
|
|
4508
|
+
const loop = s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf";
|
|
4509
|
+
if (loop)
|
|
4510
|
+
this.loopDepth++;
|
|
4299
4511
|
try {
|
|
4300
4512
|
this.emitStmtBody(s);
|
|
4301
4513
|
}
|
|
4302
4514
|
finally {
|
|
4303
4515
|
this.B.debugLocation = previous;
|
|
4516
|
+
if (loop)
|
|
4517
|
+
this.loopDepth--;
|
|
4304
4518
|
}
|
|
4305
4519
|
}
|
|
4306
4520
|
emitStmtBody(s) {
|
|
@@ -4397,8 +4611,14 @@ export class LlEmitter {
|
|
|
4397
4611
|
B.line(`${box} = ${boxNew} ; let ${b.local.name} (boxed)`);
|
|
4398
4612
|
B.line(`store ptr ${box}, ptr ${b.slot}`);
|
|
4399
4613
|
this.scopes[this.scopes.length - 1].push({ slot: b.slot, type: b.type, boxed: true });
|
|
4400
|
-
if (s.init === null)
|
|
4614
|
+
if (s.init === null) {
|
|
4615
|
+
// An empty TDZ box whose payload can legitimately be NULL
|
|
4616
|
+
// starts at its distinct sentinel (tdzEmptySentinel).
|
|
4617
|
+
const sentinel = b.local.tdz === true ? this.tdzEmptySentinel(b.type) : null;
|
|
4618
|
+
if (sentinel !== null)
|
|
4619
|
+
B.line(`store ptr ${sentinel}, ptr ${this.boxSlot(box)}`);
|
|
4401
4620
|
break;
|
|
4621
|
+
}
|
|
4402
4622
|
const v = this.emitExpr(s.init);
|
|
4403
4623
|
if (isRefCounted(v.type))
|
|
4404
4624
|
this.moveTemp(v); // the box takes ownership
|
|
@@ -4545,7 +4765,7 @@ export class LlEmitter {
|
|
|
4545
4765
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
4546
4766
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
4547
4767
|
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
4548
|
-
B.line(`${lenNumber} =
|
|
4768
|
+
B.line(`${lenNumber} = sitofp ${this.sizeType} ${len} to double`);
|
|
4549
4769
|
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
|
4550
4770
|
B.condBr(same, done, resize);
|
|
4551
4771
|
B.startBlock(resize);
|
|
@@ -4833,7 +5053,7 @@ export class LlEmitter {
|
|
|
4833
5053
|
break;
|
|
4834
5054
|
}
|
|
4835
5055
|
case "while": {
|
|
4836
|
-
const workerBudget = this.workerLoopBudget();
|
|
5056
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
4837
5057
|
const lc = B.newLabel("loop.c");
|
|
4838
5058
|
const lb = B.newLabel("loop.b");
|
|
4839
5059
|
const le = B.newLabel("loop.e");
|
|
@@ -4858,7 +5078,7 @@ export class LlEmitter {
|
|
|
4858
5078
|
break;
|
|
4859
5079
|
}
|
|
4860
5080
|
case "doWhile": {
|
|
4861
|
-
const workerBudget = this.workerLoopBudget();
|
|
5081
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
4862
5082
|
// Body first (runs at least once); continue jumps to the CONDITION.
|
|
4863
5083
|
const lb = B.newLabel("loop.b");
|
|
4864
5084
|
const lc = B.newLabel("loop.c");
|
|
@@ -4923,12 +5143,39 @@ export class LlEmitter {
|
|
|
4923
5143
|
}
|
|
4924
5144
|
let countedJoin = null;
|
|
4925
5145
|
if (countedLoop?.guarded) {
|
|
4926
|
-
|
|
4927
|
-
|
|
5146
|
+
let safe = "true";
|
|
5147
|
+
if (countedLoop.limitGuarded) {
|
|
5148
|
+
const bound = this.emitExpr(countedLoop.limit);
|
|
5149
|
+
safe = B.tmp();
|
|
5150
|
+
B.line(`${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`);
|
|
5151
|
+
}
|
|
5152
|
+
if (countedLoop.startGuarded) {
|
|
5153
|
+
// An exact integer of at most 2^53 - 1 in magnitude, and not -0
|
|
5154
|
+
// (the bit patterns of the value and its round trip agree).
|
|
5155
|
+
// NaN and infinities fail the ordered range tests.
|
|
5156
|
+
const start = this.emitExpr(countedLoop.start);
|
|
5157
|
+
const low = B.tmp(), high = B.tmp(), raw = B.tmp(), wide = B.tmp(), back = B.tmp(), bits = B.tmp(), backBits = B.tmp(), exact = B.tmp(), inRange = B.tmp(), ok = B.tmp();
|
|
5158
|
+
B.line(`${low} = fcmp oge double ${start.name}, ${f64Lit(-Number.MAX_SAFE_INTEGER)}`);
|
|
5159
|
+
B.line(`${high} = fcmp ole double ${start.name}, ${f64Lit(Number.MAX_SAFE_INTEGER)}`);
|
|
5160
|
+
B.line(`${inRange} = and i1 ${low}, ${high}`);
|
|
5161
|
+
B.line(`${raw} = fptosi double ${start.name} to i64`);
|
|
5162
|
+
B.line(`${wide} = freeze i64 ${raw}`);
|
|
5163
|
+
B.line(`${back} = sitofp i64 ${wide} to double`);
|
|
5164
|
+
B.line(`${bits} = bitcast double ${start.name} to i64`);
|
|
5165
|
+
B.line(`${backBits} = bitcast double ${back} to i64`);
|
|
5166
|
+
B.line(`${exact} = icmp eq i64 ${bits}, ${backBits}`);
|
|
5167
|
+
B.line(`${ok} = and i1 ${inRange}, ${exact}`);
|
|
5168
|
+
if (safe === "true")
|
|
5169
|
+
safe = ok;
|
|
5170
|
+
else {
|
|
5171
|
+
const both = B.tmp();
|
|
5172
|
+
B.line(`${both} = and i1 ${safe}, ${ok}`);
|
|
5173
|
+
safe = both;
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
4928
5176
|
const fast = B.newLabel("counted.fast");
|
|
4929
5177
|
const slow = B.newLabel("counted.slow");
|
|
4930
5178
|
countedJoin = B.newLabel("counted.done");
|
|
4931
|
-
B.line(`${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`);
|
|
4932
5179
|
B.condBr(safe, fast, slow);
|
|
4933
5180
|
B.startBlock(slow);
|
|
4934
5181
|
this.countedLoopsEnabled = false;
|
|
@@ -5009,7 +5256,7 @@ export class LlEmitter {
|
|
|
5009
5256
|
};
|
|
5010
5257
|
const lu = s.update || capturedLets.length > 0 ? B.newLabel("loop.u") : lc;
|
|
5011
5258
|
freshenBindings();
|
|
5012
|
-
const workerBudget = this.workerLoopBudget();
|
|
5259
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
5013
5260
|
B.br(lc);
|
|
5014
5261
|
B.startBlock(lc);
|
|
5015
5262
|
this.emitWorkerLoopCheck(workerBudget);
|
|
@@ -5117,7 +5364,7 @@ export class LlEmitter {
|
|
|
5117
5364
|
const lb = B.newLabel("fof.b");
|
|
5118
5365
|
const lu = B.newLabel("fof.u");
|
|
5119
5366
|
const le = B.newLabel("fof.e");
|
|
5120
|
-
const workerBudget = this.workerLoopBudget();
|
|
5367
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
5121
5368
|
B.br(lc);
|
|
5122
5369
|
B.startBlock(lc);
|
|
5123
5370
|
this.emitWorkerLoopCheck(workerBudget);
|
|
@@ -5136,7 +5383,7 @@ export class LlEmitter {
|
|
|
5136
5383
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
5137
5384
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
5138
5385
|
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
5139
|
-
B.line(`${len} =
|
|
5386
|
+
B.line(`${len} = sitofp ${this.sizeType} ${rawLen} to double`);
|
|
5140
5387
|
B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
|
|
5141
5388
|
cur = i;
|
|
5142
5389
|
}
|
|
@@ -5994,6 +6241,16 @@ export class LlEmitter {
|
|
|
5994
6241
|
this.B.line(`${value} = load ptr, ptr ${binding.slot}`);
|
|
5995
6242
|
return { name: value, type: e.type };
|
|
5996
6243
|
}
|
|
6244
|
+
if (e.kind === "chainRecv") {
|
|
6245
|
+
// The chain's bind slot holds its receiver unchanged until the body
|
|
6246
|
+
// finishes, owned by the slot itself or by the chain's receiver.
|
|
6247
|
+
const bound = this.chainSlots.get(e.id);
|
|
6248
|
+
if (!bound)
|
|
6249
|
+
throw new InternalCompilerError(`llvm emitter bug: chainRecv "${e.id}" outside its chain`);
|
|
6250
|
+
const value = this.B.tmp();
|
|
6251
|
+
this.B.line(`${value} = load ${this.llType(bound.type)}, ptr ${bound.name}`);
|
|
6252
|
+
return { name: value, type: e.type };
|
|
6253
|
+
}
|
|
5997
6254
|
if (e.kind === "arrayGet" && this.canBorrowReceiver(e)) {
|
|
5998
6255
|
// The array keeps the element alive until the consumer; a missing
|
|
5999
6256
|
// element still traps exactly like the owned read.
|
|
@@ -6229,6 +6486,8 @@ export class LlEmitter {
|
|
|
6229
6486
|
}
|
|
6230
6487
|
canBorrowReceiver(e) {
|
|
6231
6488
|
switch (e.kind) {
|
|
6489
|
+
case "chainRecv":
|
|
6490
|
+
return this.chainSlots.has(e.id);
|
|
6232
6491
|
case "varRef": {
|
|
6233
6492
|
const binding = this.binding(e.localId);
|
|
6234
6493
|
return (binding.kind !== "boxed" ||
|