@scriptc/compiler 0.2.9 → 0.2.10
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/dist/backend/cycle-analysis.d.ts +56 -0
- package/dist/backend/cycle-analysis.js +175 -14
- package/dist/backend/cycle-analysis.js.map +1 -1
- package/dist/backend/cycle-analysis.test.js +94 -1
- package/dist/backend/cycle-analysis.test.js.map +1 -1
- package/dist/backend/cycle-mutability.d.ts +2 -6
- package/dist/backend/cycle-mutability.js +3 -12
- package/dist/backend/cycle-mutability.js.map +1 -1
- package/dist/backend/immutability.d.ts +57 -0
- package/dist/backend/immutability.js +178 -0
- package/dist/backend/immutability.js.map +1 -0
- package/dist/backend/immutability.test.d.ts +1 -0
- package/dist/backend/immutability.test.js +131 -0
- package/dist/backend/immutability.test.js.map +1 -0
- package/dist/backend/llvm/borrowed-inputs.d.ts +9 -2
- package/dist/backend/llvm/borrowed-inputs.js +31 -3
- package/dist/backend/llvm/borrowed-inputs.js.map +1 -1
- package/dist/backend/llvm/classes.d.ts +28 -0
- package/dist/backend/llvm/classes.js +45 -5
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/classes.test.js +129 -1
- package/dist/backend/llvm/classes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +13 -0
- package/dist/backend/llvm/common.js +13 -0
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.js +5 -4
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.test.js +9 -1
- package/dist/backend/llvm/dense-array-access.test.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +2 -0
- package/dist/backend/llvm/dyn.js +3 -3
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +64 -2
- package/dist/backend/llvm/emitter.js +390 -45
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +18 -0
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js +14 -6
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +30 -7
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.js +30 -3
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.test.js +53 -0
- package/dist/backend/llvm/expr-primitives.test.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +4 -4
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.js +3 -3
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/loop-array-borrows.d.ts +5 -2
- package/dist/backend/llvm/loop-array-borrows.js +27 -8
- package/dist/backend/llvm/loop-array-borrows.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.d.ts +7 -0
- package/dist/backend/llvm/map-number-lookup.js +18 -1
- package/dist/backend/llvm/map-number-lookup.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.d.ts +5 -0
- package/dist/backend/llvm/map-read-lifetimes.js +14 -4
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +5 -0
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/reference-writes.d.ts +124 -0
- package/dist/backend/llvm/reference-writes.js +1090 -0
- package/dist/backend/llvm/reference-writes.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +8 -5
- package/dist/backend/llvm/shapes.js +36 -11
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/string-lifetimes.test.js +39 -10
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/string-reads.d.ts +3 -0
- package/dist/backend/llvm/string-reads.js +31 -0
- package/dist/backend/llvm/string-reads.js.map +1 -1
- package/dist/backend/llvm/switch-dispatch.d.ts +2 -2
- package/dist/backend/llvm/switch-dispatch.js +35 -22
- package/dist/backend/llvm/switch-dispatch.js.map +1 -1
- package/dist/backend/llvm/switch-dispatch.test.js +28 -10
- package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
- package/dist/backend/llvm/walk-borrows.d.ts +8 -1
- package/dist/backend/llvm/walk-borrows.js +15 -4
- package/dist/backend/llvm/walk-borrows.js.map +1 -1
- package/dist/backend/may-throw.d.ts +36 -2
- package/dist/backend/may-throw.js +308 -12
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/may-throw.test.js +146 -1
- package/dist/backend/may-throw.test.js.map +1 -1
- package/dist/backend/publication.d.ts +5 -0
- package/dist/backend/publication.js +10 -0
- package/dist/backend/publication.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-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack.test.js +1 -1
- package/dist/contracts/check.d.ts +8 -0
- package/dist/contracts/check.js +1577 -0
- package/dist/contracts/check.js.map +1 -0
- package/dist/executable/prepare.js +9 -0
- package/dist/executable/prepare.js.map +1 -1
- package/dist/frontend/contract-types.d.ts +50 -0
- package/dist/frontend/contract-types.js +232 -0
- package/dist/frontend/contract-types.js.map +1 -0
- package/dist/frontend/lowering/coercions/unions.d.ts +12 -0
- package/dist/frontend/lowering/coercions/unions.js +63 -0
- package/dist/frontend/lowering/coercions/unions.js.map +1 -1
- package/dist/frontend/lowering/expressions/optional-chains.js +4 -1
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +9 -2
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +83 -1
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +9 -0
- package/dist/frontend/lowering/lowerer.js +47 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/narrowing-stability.d.ts +100 -0
- package/dist/frontend/lowering/narrowing-stability.js +1174 -0
- package/dist/frontend/lowering/narrowing-stability.js.map +1 -0
- package/dist/frontend/lowering/narrowing-stability.test.d.ts +1 -0
- package/dist/frontend/lowering/narrowing-stability.test.js +150 -0
- package/dist/frontend/lowering/narrowing-stability.test.js.map +1 -0
- package/dist/frontend/program.js +1 -1
- package/dist/frontend/type-mapper.js +43 -0
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/ir/int32-slots.d.ts +26 -0
- package/dist/ir/int32-slots.js +147 -47
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/integer-loops.js +3 -0
- package/dist/ir/integer-loops.js.map +1 -1
- package/dist/ir/integer-loops.test.js +26 -1
- package/dist/ir/integer-loops.test.js.map +1 -1
- package/dist/ir/integer-ranges.d.ts +20 -0
- package/dist/ir/integer-ranges.js +178 -21
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +148 -3
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +32 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.d.ts +5 -1
- package/dist/ir/serialize.js +8 -0
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/unshare.d.ts +14 -0
- package/dist/ir/unshare.js +60 -0
- package/dist/ir/unshare.js.map +1 -0
- package/dist/ir/unshare.test.d.ts +1 -0
- package/dist/ir/unshare.test.js +115 -0
- package/dist/ir/unshare.test.js.map +1 -0
- package/dist/library/prepare.js +9 -0
- package/dist/library/prepare.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +1 -1
|
@@ -36,6 +36,7 @@ import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
|
36
36
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
37
37
|
import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
38
38
|
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
39
|
+
import { unshareNodes } from "../../ir/unshare.js";
|
|
39
40
|
import { analyzeInt32Slots } from "../../ir/int32-slots.js";
|
|
40
41
|
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
41
42
|
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
@@ -56,10 +57,11 @@ import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js"
|
|
|
56
57
|
import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
|
|
57
58
|
import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
|
|
58
59
|
import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
|
|
59
|
-
import { emitBorrowedInput } from "./borrowed-inputs.js";
|
|
60
|
+
import { borrowableInputs, emitBorrowedInput } from "./borrowed-inputs.js";
|
|
60
61
|
import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
|
|
61
62
|
import { findReboundParameters } from "./rebound-parameters.js";
|
|
62
63
|
import { ReferenceEffects } from "./reference-effects.js";
|
|
64
|
+
import { ReferenceWrites } from "./reference-writes.js";
|
|
63
65
|
import { LlvmDebugInfo } from "./debug-info.js";
|
|
64
66
|
import { StackCallbacks } from "./stack-callbacks.js";
|
|
65
67
|
import { findLoopArrayBorrows } from "./loop-array-borrows.js";
|
|
@@ -75,6 +77,7 @@ import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js";
|
|
|
75
77
|
import { emitDynamicExpr } from "./expr-dynamic.js";
|
|
76
78
|
import { emitIntrinsicExpr, emitSerializationExpr, emitAsyncExpr } from "./expr-async.js";
|
|
77
79
|
import { PublishWalkers } from "./publish.js";
|
|
80
|
+
import { computeImmutableFields, stableGlobals } from "../immutability.js";
|
|
78
81
|
import { computePublishedTypes } from "../publication.js";
|
|
79
82
|
import { emitJsInteropExpr, emitExpr } from "./expr-dispatch.js";
|
|
80
83
|
import { emitJsMarshal, emitJsOp, emitJsExit, islandAdapter, islandTypedAdapter, } from "./expr-island.js";
|
|
@@ -97,7 +100,7 @@ import { LlWalkers } from "./walkers.js";
|
|
|
97
100
|
import { isObjectArm, NULLABLE_ABSENT, NULLABLE_NULL, NullableUnions } from "./nullable-unions.js";
|
|
98
101
|
import { emitNullablePresent, emitUnionPeek, emitUnionTag } from "./union-repr.js";
|
|
99
102
|
import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
|
|
100
|
-
import { NUMBER_MAP_ENTRY_DECL } from "./map-number-lookup.js";
|
|
103
|
+
import { NUMBER_MAP_ENTRY_DECL, STRING_MAP_ENTRY_DECL } from "./map-number-lookup.js";
|
|
101
104
|
import { STRING_READ_HELPERS } from "./string-reads.js";
|
|
102
105
|
import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
|
|
103
106
|
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
@@ -127,14 +130,18 @@ function ffiCallbackDummyLl(callback) {
|
|
|
127
130
|
return "i32 0";
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
|
-
export function emitLlvmModule(
|
|
133
|
+
export function emitLlvmModule(input, options = {}) {
|
|
134
|
+
// Facts are keyed by node identity: emit the same module whether it was
|
|
135
|
+
// lowered in memory or read back from its serialized form.
|
|
136
|
+
const mod = unshareNodes(input);
|
|
131
137
|
// Keep source storage intact for debugger inspection in dev builds.
|
|
132
138
|
return new LlEmitter(options.debugSources === undefined
|
|
133
139
|
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
134
140
|
: mod, options).emit();
|
|
135
141
|
}
|
|
136
142
|
/** Keep large translation units below the host's single-string limit. */
|
|
137
|
-
export function emitLlvmModuleSource(
|
|
143
|
+
export function emitLlvmModuleSource(input, options = {}) {
|
|
144
|
+
const mod = unshareNodes(input);
|
|
138
145
|
const parts = new LlEmitter(options.debugSources === undefined
|
|
139
146
|
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
140
147
|
: mod, options).emitParts();
|
|
@@ -302,6 +309,12 @@ export class LlEmitter {
|
|
|
302
309
|
* fiberless-async analysis scans it for suspension entry points. */
|
|
303
310
|
fnDefText = new Map();
|
|
304
311
|
referenceEffects;
|
|
312
|
+
/** Field-sensitive reference writes (null in debug builds). */
|
|
313
|
+
referenceWrites = null;
|
|
314
|
+
borrowedReturnKeyCache = new Map();
|
|
315
|
+
/** The statement list being emitted and the current index, for blocks
|
|
316
|
+
* whose bindings end with the list (emitBlock and function bodies). */
|
|
317
|
+
stmtCursor = null;
|
|
305
318
|
optionalArrayReads;
|
|
306
319
|
checkedNarrows;
|
|
307
320
|
callArrayReads = new Map();
|
|
@@ -345,6 +358,9 @@ export class LlEmitter {
|
|
|
345
358
|
ffiHasRetainedCallback;
|
|
346
359
|
ffiHasForeignCallback;
|
|
347
360
|
globalTypes = new Map();
|
|
361
|
+
/** Module constants that keep one value once initialized
|
|
362
|
+
* (immutability.ts stableGlobals). */
|
|
363
|
+
stableGlobals = new Set();
|
|
348
364
|
tdzGlobals = new Map();
|
|
349
365
|
constantNumericTables;
|
|
350
366
|
/** May-throw analysis (computeMayThrow): pending
|
|
@@ -360,6 +376,13 @@ export class LlEmitter {
|
|
|
360
376
|
/** Static types whose values can be published (@scriptc/threads): their
|
|
361
377
|
* stores carry the frozen-object guard. Null without publish calls. */
|
|
362
378
|
publishedTypes;
|
|
379
|
+
/** Construction-only class and record fields (immutability.ts); null
|
|
380
|
+
* where the facts are disabled. */
|
|
381
|
+
immutableFields;
|
|
382
|
+
/** Constructor function names: their bodies may still write their own
|
|
383
|
+
* `this`'s construction-only fields. */
|
|
384
|
+
constructorFunctions;
|
|
385
|
+
inConstructor = false;
|
|
363
386
|
/** Method names with at least one may-throw implementation — the
|
|
364
387
|
* virtualCall pending check's key. */
|
|
365
388
|
mayThrowMethods = new Set();
|
|
@@ -436,6 +459,12 @@ export class LlEmitter {
|
|
|
436
459
|
initializerBindings;
|
|
437
460
|
/** Whole-program int32 storage and parameter/result facts. */
|
|
438
461
|
int32Slots;
|
|
462
|
+
/** Immutable module constants (contract types) whose graphs become
|
|
463
|
+
* immortal once initialized: their reads need no reference counting. */
|
|
464
|
+
immortalGlobals;
|
|
465
|
+
/** ProgramLifetime hierarchy roots (contract types): their instances are
|
|
466
|
+
* immortal from construction (classes.ts programLifetimeClass). */
|
|
467
|
+
programLifetime;
|
|
439
468
|
numericLocals = new Map();
|
|
440
469
|
captureIds = new Set();
|
|
441
470
|
/** Active induction bindings retain their width independently of size_t. */
|
|
@@ -495,6 +524,8 @@ export class LlEmitter {
|
|
|
495
524
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
496
525
|
this.initializerBindings = findInitializerBindings(mod);
|
|
497
526
|
this.int32Slots = analyzeInt32Slots(mod);
|
|
527
|
+
this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
|
|
528
|
+
this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
|
|
498
529
|
this.finalFields = analyzeFinalFields(mod);
|
|
499
530
|
this.sizeType = options.pointerBits === 32 ? "i32" : "i64";
|
|
500
531
|
this.ffiExtendNarrowIntegers =
|
|
@@ -549,6 +580,8 @@ export class LlEmitter {
|
|
|
549
580
|
this.workerEntryPolls = mt.workerEntryPolls ?? null;
|
|
550
581
|
this.stackChecks = mt.stackChecks ?? null;
|
|
551
582
|
this.publishedTypes = computePublishedTypes(mod);
|
|
583
|
+
this.immutableFields = computeImmutableFields(mod);
|
|
584
|
+
this.constructorFunctions = new Set((mod.classes ?? []).map((c) => `%${c.name}.constructor`));
|
|
552
585
|
this.indirectMayThrow = mt.indirect || mod.workers === true;
|
|
553
586
|
for (const cls of mod.classes ?? []) {
|
|
554
587
|
for (const m of cls.methods ?? []) {
|
|
@@ -568,6 +601,8 @@ export class LlEmitter {
|
|
|
568
601
|
this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById, (id) => this.nullableUnions.has(id));
|
|
569
602
|
this.checkedNarrows = new CheckedNarrows(this.fnByName);
|
|
570
603
|
this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null, mod.classes ?? []);
|
|
604
|
+
if (this.debug === null)
|
|
605
|
+
this.referenceWrites = new ReferenceWrites(this.fnByName, mod.classes ?? [], (call) => this.optionalArrayReads.get(call) !== null, new Set((mod.globals ?? []).map((g) => g.id)), { unions: mod.unions, records: mod.records }, this.immutableFields);
|
|
571
606
|
const walkParameters = new Map();
|
|
572
607
|
if (this.debug === null) {
|
|
573
608
|
const host = {
|
|
@@ -575,9 +610,10 @@ export class LlEmitter {
|
|
|
575
610
|
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
576
611
|
};
|
|
577
612
|
for (const fn of this.fnByName.values()) {
|
|
578
|
-
|
|
613
|
+
const preserving = this.referenceEffects.functions.has(fn.name);
|
|
614
|
+
if (!preserving && this.referenceWrites === null)
|
|
579
615
|
continue;
|
|
580
|
-
const walks = findWalkBorrows(fn, host);
|
|
616
|
+
const walks = findWalkBorrows(fn, preserving ? host : this.unwrittenWalkHost(fn, host));
|
|
581
617
|
if (walks.size === 0)
|
|
582
618
|
continue;
|
|
583
619
|
this.walkBorrowsByFunction.set(fn.name, walks);
|
|
@@ -661,6 +697,7 @@ export class LlEmitter {
|
|
|
661
697
|
},
|
|
662
698
|
pendingTestLines: (dest) => this.pendingTestLines(dest),
|
|
663
699
|
virtualEntry: (implFn) => this.virtualEntry(implFn),
|
|
700
|
+
programLifetime: this.programLifetime,
|
|
664
701
|
};
|
|
665
702
|
this.walkers = new LlWalkers(this.shapeHost);
|
|
666
703
|
this.dyn = new LlDyn(this.shapeHost);
|
|
@@ -682,6 +719,7 @@ export class LlEmitter {
|
|
|
682
719
|
if (g.tdz)
|
|
683
720
|
this.tdzGlobals.set(g.id, g.name);
|
|
684
721
|
}
|
|
722
|
+
this.stableGlobals = stableGlobals(mod);
|
|
685
723
|
// Runtime error, EventEmitter, and stream classes have known layouts.
|
|
686
724
|
// Subclasses embed the ScrEmitter/ScrStream prefixes from classes.ts.
|
|
687
725
|
const classes = mod.classes ?? [];
|
|
@@ -1403,12 +1441,14 @@ export class LlEmitter {
|
|
|
1403
1441
|
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1404
1442
|
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
|
|
1405
1443
|
// ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem,
|
|
1406
|
-
//
|
|
1407
|
-
//
|
|
1408
|
-
// (
|
|
1409
|
-
//
|
|
1410
|
-
//
|
|
1411
|
-
|
|
1444
|
+
// cyc, ops, data, present, ext } (field numbers in SCR_ARR_FIELD),
|
|
1445
|
+
// where ops points at the element type's constant RC descriptor
|
|
1446
|
+
// (%ScrArrOps, SCR_ELEM_REF only) and ext at the out-of-line sparse,
|
|
1447
|
+
// property and metadata storage (NULL until first used). Live dynamic
|
|
1448
|
+
// stream commits swap its dense, presence and out-of-line storage while
|
|
1449
|
+
// preserving the target object's identity.
|
|
1450
|
+
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr }`,
|
|
1451
|
+
`%ScrArrOps = type { ptr, ptr, ptr }`,
|
|
1412
1452
|
// The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
|
|
1413
1453
|
// val_trace, key_retain, key_release, key_trace } — the inline RC
|
|
1414
1454
|
// fast paths test the two trace slots (shapes.ts).
|
|
@@ -1421,9 +1461,13 @@ export class LlEmitter {
|
|
|
1421
1461
|
...(this.decls.has(NUMBER_MAP_ENTRY_DECL)
|
|
1422
1462
|
? [
|
|
1423
1463
|
`%ScrMapIx = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ptr, ptr, ${this.sizeType} }`,
|
|
1424
|
-
`%ScrMapEntry = type { i64, i64, i64 }`,
|
|
1425
1464
|
]
|
|
1426
1465
|
: []),
|
|
1466
|
+
// String-keyed reference reads load the value slot of the entry
|
|
1467
|
+
// scr_map_entry_str returns (map-number-lookup.ts).
|
|
1468
|
+
...(this.decls.has(NUMBER_MAP_ENTRY_DECL) || this.decls.has(STRING_MAP_ENTRY_DECL)
|
|
1469
|
+
? [`%ScrMapEntry = type { i64, i64, i64 }`]
|
|
1470
|
+
: []),
|
|
1427
1471
|
// The runtime error prefix { rc, vt, name, message, code, cause } and the
|
|
1428
1472
|
// class-object shape { rc, pre, post, ctor, name } — field reads on
|
|
1429
1473
|
// builtin errors and classval loads GEP through these.
|
|
@@ -1551,7 +1595,7 @@ export class LlEmitter {
|
|
|
1551
1595
|
// strings — a no-op.
|
|
1552
1596
|
const n = inst.slots.length;
|
|
1553
1597
|
const present = n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`;
|
|
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,
|
|
1598
|
+
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, i32 0, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null }`);
|
|
1555
1599
|
}
|
|
1556
1600
|
if (this.templateStringsInstances.size > 0)
|
|
1557
1601
|
out.push(``);
|
|
@@ -4086,6 +4130,7 @@ export class LlEmitter {
|
|
|
4086
4130
|
const numericFn = withInitializerBindings(fn, initializerBindings);
|
|
4087
4131
|
this.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l]));
|
|
4088
4132
|
this.borrowedParameters.clear();
|
|
4133
|
+
this.referenceWrites?.enter(fn);
|
|
4089
4134
|
this.reboundOwnerSlots.clear();
|
|
4090
4135
|
this.projectedParameters.clear();
|
|
4091
4136
|
const projectedParameterIndexes = this.callLifetimes.parameters.get(fn.name);
|
|
@@ -4117,7 +4162,7 @@ export class LlEmitter {
|
|
|
4117
4162
|
this.integerArrayBindings.clear();
|
|
4118
4163
|
this.loopArrayBorrows =
|
|
4119
4164
|
this.debug === null
|
|
4120
|
-
? findLoopArrayBorrows(fn, this.callLifetimes, this.referenceEffects, this.optionalArrayReads)
|
|
4165
|
+
? findLoopArrayBorrows(fn, this.callLifetimes, this.referenceEffects, this.optionalArrayReads, this.referenceWrites)
|
|
4121
4166
|
: new Set();
|
|
4122
4167
|
this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads, this.loopArrayBorrows, this.debug === null ? this.referenceEffects : undefined);
|
|
4123
4168
|
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes, this.debug === null ? this.referenceEffects : undefined);
|
|
@@ -4137,6 +4182,7 @@ export class LlEmitter {
|
|
|
4137
4182
|
this.currentReturnType = fn.returnType;
|
|
4138
4183
|
this.currentGenerator = fn.generator ?? null;
|
|
4139
4184
|
this.currentBorrowedReturn = this.borrowedReturns.has(fn.name);
|
|
4185
|
+
this.inConstructor = this.constructorFunctions.has(fn.name);
|
|
4140
4186
|
this.currentWasiCoro = null;
|
|
4141
4187
|
this.logArgSlots = 0;
|
|
4142
4188
|
if (this.wasi && (fn.async === true || fn.generator !== undefined)) {
|
|
@@ -4297,7 +4343,7 @@ export class LlEmitter {
|
|
|
4297
4343
|
this.emitStackCheck();
|
|
4298
4344
|
if (this.workerEntryPolls?.has(fn.name))
|
|
4299
4345
|
this.emitPendingCheck(undefined, true);
|
|
4300
|
-
this.emitStmts(fn.body);
|
|
4346
|
+
this.emitStmts(fn.body, true);
|
|
4301
4347
|
// Implicit exit of a void function: release the function scope unless
|
|
4302
4348
|
// the body already terminated its final block (return, or a throw
|
|
4303
4349
|
// whose unwind released everything down to depth 0).
|
|
@@ -4363,7 +4409,14 @@ export class LlEmitter {
|
|
|
4363
4409
|
if (fn.captures !== undefined)
|
|
4364
4410
|
params.unshift("ptr %sc_env");
|
|
4365
4411
|
const ret = this.llType(fn.returnType);
|
|
4366
|
-
|
|
4412
|
+
let attrs = coro !== null ? "#1" : FN_ATTRS;
|
|
4413
|
+
// The shared indexed-read helper (lowerSafeIndexRead) is a dense bounds
|
|
4414
|
+
// test, a state test and a load behind cold runtime fallbacks, called
|
|
4415
|
+
// from every `a[i]` of one element type. Without the hint LLVM keeps the
|
|
4416
|
+
// hot instances out of line once they have many callers: every read pays
|
|
4417
|
+
// a call, and an integer index converts to a double and back across it.
|
|
4418
|
+
if (fn.name.startsWith("%arr.idxOr."))
|
|
4419
|
+
attrs = `inlinehint ${attrs}`;
|
|
4367
4420
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
4368
4421
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
4369
4422
|
const body = `define internal ${ret} @${symbol}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
@@ -4460,6 +4513,10 @@ export class LlEmitter {
|
|
|
4460
4513
|
this.canBorrowReceiver(value.else_) &&
|
|
4461
4514
|
this.canBorrowCallArgument(value.then) &&
|
|
4462
4515
|
this.canBorrowCallArgument(value.else_));
|
|
4516
|
+
// A construction-only field of a stable owner keeps its value until the
|
|
4517
|
+
// owner dies: no later operand or callee can replace and release it.
|
|
4518
|
+
if ((value.kind === "fieldGet" || value.kind === "recordGet") && this.immutableRead(value))
|
|
4519
|
+
return this.canBorrowCallArgument(value.obj);
|
|
4463
4520
|
// An optional chain's bound receiver stays unchanged until its body
|
|
4464
4521
|
// finishes (see emitReadReceiver).
|
|
4465
4522
|
if (value.kind === "chainRecv")
|
|
@@ -4467,6 +4524,9 @@ export class LlEmitter {
|
|
|
4467
4524
|
if (value.kind !== "varRef")
|
|
4468
4525
|
return false;
|
|
4469
4526
|
const binding = this.binding(value.localId);
|
|
4527
|
+
// A module constant owns its value for the rest of the program.
|
|
4528
|
+
if (binding.kind === "global")
|
|
4529
|
+
return this.stableGlobals.has(value.localId);
|
|
4470
4530
|
return (binding.kind === "local" &&
|
|
4471
4531
|
binding.local !== undefined &&
|
|
4472
4532
|
(!binding.local.mutable ||
|
|
@@ -4476,15 +4536,212 @@ export class LlEmitter {
|
|
|
4476
4536
|
!binding.local.boxed &&
|
|
4477
4537
|
!binding.local.tdz);
|
|
4478
4538
|
}
|
|
4539
|
+
/** A plain-pointer read of a construction-only field outside any
|
|
4540
|
+
* constructor (ImmutableFields). */
|
|
4541
|
+
immutableRead(e) {
|
|
4542
|
+
if (this.inConstructor || this.immutableFields === null)
|
|
4543
|
+
return false;
|
|
4544
|
+
const t = e.type.kind;
|
|
4545
|
+
if (t !== "object" && t !== "record" && t !== "array" && t !== "string")
|
|
4546
|
+
return false;
|
|
4547
|
+
return e.kind === "fieldGet"
|
|
4548
|
+
? this.immutableFields.classField(e.className, e.field)
|
|
4549
|
+
: this.immutableFields.recordField(e.shapeId, e.field);
|
|
4550
|
+
}
|
|
4551
|
+
/** A projection read whose locations nothing writes while a borrowed
|
|
4552
|
+
* read of it is in use (ReferenceWrites), given the writes of that
|
|
4553
|
+
* extent. The root binding itself must stay alive across it too, which
|
|
4554
|
+
* its own location (`l:`, `g:`, `bx`) covers. */
|
|
4555
|
+
unwrittenRead(e, writes) {
|
|
4556
|
+
const rw = this.referenceWrites;
|
|
4557
|
+
if (rw === null)
|
|
4558
|
+
return false;
|
|
4559
|
+
const keys = rw.readKeys(e, (call) => this.borrowedReturnKeys(call.callee));
|
|
4560
|
+
return keys !== null && !rw.conflicts(keys, writes);
|
|
4561
|
+
}
|
|
4562
|
+
/** A stable binding initialized by a projection can borrow it when the
|
|
4563
|
+
* rest of its block (its whole lifetime) writes none of the projection's
|
|
4564
|
+
* locations: the source edge then keeps the value alive, and uses that
|
|
4565
|
+
* escape still retain their own copies. */
|
|
4566
|
+
unwrittenAlias(s) {
|
|
4567
|
+
const cursor = this.stmtCursor;
|
|
4568
|
+
const rw = this.referenceWrites;
|
|
4569
|
+
if (rw === null || s.init === null || cursor === null || cursor.stmts[cursor.index] !== s)
|
|
4570
|
+
return false;
|
|
4571
|
+
if (!this.plainReference(s.init.type) ||
|
|
4572
|
+
!this.canBorrowReceiver(s.init) ||
|
|
4573
|
+
!this.unownedWalk(s.init))
|
|
4574
|
+
return false;
|
|
4575
|
+
const writes = rw.union([rw.expr(s.init), rw.stmts(cursor.stmts.slice(cursor.index + 1))]);
|
|
4576
|
+
return this.unwrittenRead(s.init, writes);
|
|
4577
|
+
}
|
|
4578
|
+
/** An optional element read into a projection-only local can borrow the
|
|
4579
|
+
* element when the rest of the block writes no array element and none of
|
|
4580
|
+
* the locations keeping the array alive (which must itself be read
|
|
4581
|
+
* without a frame owner). */
|
|
4582
|
+
unwrittenLocalArrayRead(s, read) {
|
|
4583
|
+
const cursor = this.stmtCursor;
|
|
4584
|
+
const rw = this.referenceWrites;
|
|
4585
|
+
if (rw === null || s.init === null || cursor === null || cursor.stmts[cursor.index] !== s)
|
|
4586
|
+
return false;
|
|
4587
|
+
if (!this.unownedWalk(read.array))
|
|
4588
|
+
return false;
|
|
4589
|
+
const keys = rw.readKeys(read.array);
|
|
4590
|
+
if (keys === null || rw.expr(read.index) !== null)
|
|
4591
|
+
return false;
|
|
4592
|
+
const writes = rw.union([rw.expr(s.init), rw.stmts(cursor.stmts.slice(cursor.index + 1))]);
|
|
4593
|
+
return !rw.conflicts(["a", ...keys], writes);
|
|
4594
|
+
}
|
|
4595
|
+
/** An optional element read passed to a projection-only parameter can
|
|
4596
|
+
* borrow the element when the callee and later arguments (`writes`)
|
|
4597
|
+
* write no array element and nothing keeping the array alive. */
|
|
4598
|
+
unwrittenCallArrayRead(read, writes) {
|
|
4599
|
+
const rw = this.referenceWrites;
|
|
4600
|
+
if (rw === null)
|
|
4601
|
+
return false;
|
|
4602
|
+
const keys = rw.readKeys(read.array);
|
|
4603
|
+
if (keys === null || rw.expr(read.index) !== null)
|
|
4604
|
+
return false;
|
|
4605
|
+
return !rw.conflicts(["a", ...keys], writes);
|
|
4606
|
+
}
|
|
4607
|
+
/** `return x` of a local whose slot is an owned scope entry of the same
|
|
4608
|
+
* type: the return can take over the local's reference. */
|
|
4609
|
+
movableReturn(value) {
|
|
4610
|
+
if (value === null || value.kind !== "varRef" || !isRefCounted(value.type))
|
|
4611
|
+
return null;
|
|
4612
|
+
if (this.currentBorrowedReturn || this.currentWasiCoro !== null)
|
|
4613
|
+
return null;
|
|
4614
|
+
const binding = this.binding(value.localId);
|
|
4615
|
+
if (binding.kind !== "local" || binding.local === undefined || binding.local.boxed)
|
|
4616
|
+
return null;
|
|
4617
|
+
if (!typeEquals(binding.type, value.type) || !typeEquals(this.currentReturnType, value.type))
|
|
4618
|
+
return null;
|
|
4619
|
+
for (const scope of this.scopes) {
|
|
4620
|
+
const index = scope.findIndex((entry) => entry.slot === binding.slot);
|
|
4621
|
+
if (index < 0)
|
|
4622
|
+
continue;
|
|
4623
|
+
const entry = scope[index];
|
|
4624
|
+
if (entry.boxed || entry.lazy || !typeEquals(entry.type, value.type))
|
|
4625
|
+
return null;
|
|
4626
|
+
return { scope, entry, index };
|
|
4627
|
+
}
|
|
4628
|
+
return null;
|
|
4629
|
+
}
|
|
4630
|
+
/** A walk host for a body that may write the heap: each walk must read
|
|
4631
|
+
* only locations the whole body (with its callees) never writes;
|
|
4632
|
+
* rebinding the walk locals themselves releases nothing. */
|
|
4633
|
+
unwrittenWalkHost(fn, host) {
|
|
4634
|
+
return {
|
|
4635
|
+
...host,
|
|
4636
|
+
unwritten: (value, candidates) => this.referenceWrites?.unwrittenWalk(fn, value, candidates, (call) => this.borrowedReturnKeys(call.callee)) ?? false,
|
|
4637
|
+
};
|
|
4638
|
+
}
|
|
4639
|
+
/** emitReadReceiver yields a plain pointer for `e` that no statement
|
|
4640
|
+
* frame owns, so it can outlive the statement (walk-borrows' walks, from
|
|
4641
|
+
* any unboxed plain-pointer binding, plus element reads). */
|
|
4642
|
+
unownedWalk(e) {
|
|
4643
|
+
switch (e.kind) {
|
|
4644
|
+
case "varRef": {
|
|
4645
|
+
const binding = this.binding(e.localId);
|
|
4646
|
+
if (binding.kind === "global")
|
|
4647
|
+
return this.plainReference(binding.type);
|
|
4648
|
+
return (binding.kind === "local" &&
|
|
4649
|
+
binding.local !== undefined &&
|
|
4650
|
+
!binding.local.boxed &&
|
|
4651
|
+
!binding.local.tdz &&
|
|
4652
|
+
this.plainReference(binding.local.type) &&
|
|
4653
|
+
!this.stringSlices.has(e.localId) &&
|
|
4654
|
+
!this.storedSplits.has(e.localId) &&
|
|
4655
|
+
!this.localUnionStorage.has(e.localId));
|
|
4656
|
+
}
|
|
4657
|
+
case "fieldGet":
|
|
4658
|
+
case "recordGet":
|
|
4659
|
+
return this.borrowsWithoutOwning(e) && this.unownedWalk(e.obj);
|
|
4660
|
+
case "unionNarrow":
|
|
4661
|
+
case "downcast":
|
|
4662
|
+
case "upcast":
|
|
4663
|
+
return this.borrowsWithoutOwning(e) && this.unownedWalk(e.value);
|
|
4664
|
+
case "unionWrap":
|
|
4665
|
+
if (!this.borrowsWithoutOwning(e))
|
|
4666
|
+
return false;
|
|
4667
|
+
return e.value.kind === "unitLit" || this.unownedWalk(e.value);
|
|
4668
|
+
case "ternary": {
|
|
4669
|
+
const arm = (v) => (v.kind === "libCall" && v.fn === "error.nodeThrow") || this.unownedWalk(v);
|
|
4670
|
+
return this.borrowsWithoutOwning(e) && arm(e.then) && arm(e.else_);
|
|
4671
|
+
}
|
|
4672
|
+
case "call":
|
|
4673
|
+
return (this.borrowsWithoutOwning(e) &&
|
|
4674
|
+
e.args.every((arg) => !isRefCounted(arg.type) || this.unownedWalk(arg)));
|
|
4675
|
+
case "arrayGet":
|
|
4676
|
+
return (!this.isStackUnionSource(e) &&
|
|
4677
|
+
this.optionalArrayReads.get(e) === null &&
|
|
4678
|
+
this.unownedWalk(e.arr));
|
|
4679
|
+
case "seqExpr": {
|
|
4680
|
+
const guarded = guardedValue(e);
|
|
4681
|
+
return guarded !== null && this.unownedWalk(guarded);
|
|
4682
|
+
}
|
|
4683
|
+
default:
|
|
4684
|
+
return false;
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
/** Every location a borrowed-return callee's walks can read (its body's
|
|
4688
|
+
* field, record and element reads, through nested borrowed returns). */
|
|
4689
|
+
borrowedReturnKeys(callee) {
|
|
4690
|
+
if (!this.borrowedReturns.has(callee))
|
|
4691
|
+
return null;
|
|
4692
|
+
const known = this.borrowedReturnKeyCache.get(callee);
|
|
4693
|
+
if (known)
|
|
4694
|
+
return known;
|
|
4695
|
+
const rw = this.referenceWrites;
|
|
4696
|
+
const keys = new Set();
|
|
4697
|
+
const seen = new Set([callee]);
|
|
4698
|
+
const pending = [callee];
|
|
4699
|
+
while (pending.length > 0) {
|
|
4700
|
+
const fn = this.fnByName.get(pending.pop());
|
|
4701
|
+
if (!fn)
|
|
4702
|
+
continue;
|
|
4703
|
+
everyStmtList(fn.body, {
|
|
4704
|
+
stmt: () => true,
|
|
4705
|
+
expr: (e) => {
|
|
4706
|
+
if (e.kind === "fieldGet")
|
|
4707
|
+
keys.add(rw.fieldKey(e.className, e.field));
|
|
4708
|
+
else if (e.kind === "recordGet") {
|
|
4709
|
+
keys.add(`r:${e.field}`);
|
|
4710
|
+
keys.add("r:*");
|
|
4711
|
+
}
|
|
4712
|
+
else if (e.kind === "arrayGet")
|
|
4713
|
+
keys.add("a");
|
|
4714
|
+
else if (e.kind === "call" && this.borrowedReturns.has(e.callee) && !seen.has(e.callee)) {
|
|
4715
|
+
seen.add(e.callee);
|
|
4716
|
+
pending.push(e.callee);
|
|
4717
|
+
}
|
|
4718
|
+
return true;
|
|
4719
|
+
},
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
const result = [...keys];
|
|
4723
|
+
this.borrowedReturnKeyCache.set(callee, result);
|
|
4724
|
+
return result;
|
|
4725
|
+
}
|
|
4479
4726
|
// ── statements ──────────────────────────────────────────────────────────
|
|
4480
|
-
emitStmts(stmts) {
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
return
|
|
4487
|
-
|
|
4727
|
+
emitStmts(stmts, scoped = false) {
|
|
4728
|
+
const saved = this.stmtCursor;
|
|
4729
|
+
const cursor = scoped ? { stmts, index: 0 } : null;
|
|
4730
|
+
try {
|
|
4731
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
4732
|
+
// Statements after a terminator are unreachable (dead code after
|
|
4733
|
+
// return/break/continue). Skip them so no dropped SSA definition
|
|
4734
|
+
// can leak forward.
|
|
4735
|
+
if (this.B.isTerminated())
|
|
4736
|
+
return;
|
|
4737
|
+
if (cursor)
|
|
4738
|
+
cursor.index = i;
|
|
4739
|
+
this.stmtCursor = cursor;
|
|
4740
|
+
this.emitStmt(stmts[i]);
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
finally {
|
|
4744
|
+
this.stmtCursor = saved;
|
|
4488
4745
|
}
|
|
4489
4746
|
}
|
|
4490
4747
|
/** Emits a block in its own lexical scope (refcounted locals released at
|
|
@@ -4496,7 +4753,7 @@ export class LlEmitter {
|
|
|
4496
4753
|
const scope = [];
|
|
4497
4754
|
this.scopes.push(scope);
|
|
4498
4755
|
setup?.(scope);
|
|
4499
|
-
this.emitStmts(stmts);
|
|
4756
|
+
this.emitStmts(stmts, true);
|
|
4500
4757
|
const ended = endsWithJump(stmts);
|
|
4501
4758
|
this.scopes.pop();
|
|
4502
4759
|
if (!ended)
|
|
@@ -4558,7 +4815,9 @@ export class LlEmitter {
|
|
|
4558
4815
|
}
|
|
4559
4816
|
const localRead = this.localArrayReads.get(s.localId);
|
|
4560
4817
|
if (localRead) {
|
|
4561
|
-
const owner = emitLocalArrayRead(this, localRead,
|
|
4818
|
+
const owner = emitLocalArrayRead(this, !localRead.borrow && this.unwrittenLocalArrayRead(s, localRead)
|
|
4819
|
+
? { ...localRead, borrow: true }
|
|
4820
|
+
: localRead, b.slot);
|
|
4562
4821
|
if (owner)
|
|
4563
4822
|
this.scopes[this.scopes.length - 1].push(owner);
|
|
4564
4823
|
break;
|
|
@@ -4652,7 +4911,7 @@ export class LlEmitter {
|
|
|
4652
4911
|
b.local &&
|
|
4653
4912
|
isRefCounted(b.type) &&
|
|
4654
4913
|
this.stableCallBindings.has(s.localId) &&
|
|
4655
|
-
this.canBorrowCallArgument(s.init)) {
|
|
4914
|
+
(this.canBorrowCallArgument(s.init) || this.unwrittenAlias(s))) {
|
|
4656
4915
|
const v = this.emitReadReceiver(s.init);
|
|
4657
4916
|
B.line(`store ptr ${v.name}, ptr ${b.slot}`);
|
|
4658
4917
|
break;
|
|
@@ -4719,6 +4978,32 @@ export class LlEmitter {
|
|
|
4719
4978
|
}
|
|
4720
4979
|
B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
|
|
4721
4980
|
this.storeIntegerView(s.localId, v, s.value);
|
|
4981
|
+
if (b.kind === "global" && this.immortalGlobals.has(s.localId)) {
|
|
4982
|
+
// An Immutable module constant (contracts/check.ts proved it is
|
|
4983
|
+
// fresh, publishable and never written): its graph becomes
|
|
4984
|
+
// immortal, so reads skip reference counting and the collector
|
|
4985
|
+
// never visits it.
|
|
4986
|
+
const lines = this.publishWalkers.emitPublish(v.name, v.type, s.loc);
|
|
4987
|
+
for (const line of lines)
|
|
4988
|
+
B.line(line.trimStart());
|
|
4989
|
+
// Reads of this global skip retains from here on, which is only
|
|
4990
|
+
// sound once the graph really is immortal. Node.js never fails
|
|
4991
|
+
// here, so a refusal means the contract proof was wrong: stop
|
|
4992
|
+
// rather than keep running with a mortal graph read as immortal.
|
|
4993
|
+
if (lines.length > 0) {
|
|
4994
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
4995
|
+
this.declare(`declare void @scr_trap(ptr)`);
|
|
4996
|
+
const refused = B.tmp();
|
|
4997
|
+
B.line(`${refused} = call zeroext i1 @scr_exc_pending()`);
|
|
4998
|
+
const stop = B.newLabel("immutable.refused");
|
|
4999
|
+
const done = B.newLabel("immutable.published");
|
|
5000
|
+
B.condBr(refused, stop, done);
|
|
5001
|
+
B.startBlock(stop);
|
|
5002
|
+
B.line(`call void @scr_trap(ptr ${this.cstr("scriptc: internal error: an Immutable value could not be published\n")})`);
|
|
5003
|
+
B.terminate("unreachable");
|
|
5004
|
+
B.startBlock(done);
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
4722
5007
|
break;
|
|
4723
5008
|
}
|
|
4724
5009
|
case "exprStmt":
|
|
@@ -4816,14 +5101,14 @@ export class LlEmitter {
|
|
|
4816
5101
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4817
5102
|
// (The guard precedes the value here: the nullable store
|
|
4818
5103
|
// evaluates it itself.)
|
|
4819
|
-
this.
|
|
5104
|
+
this.emitStoreGuard(obj.name, s);
|
|
4820
5105
|
this.emitNullableFieldStore(obj.name, s.className, s.field, nullable, s.value);
|
|
4821
5106
|
break;
|
|
4822
5107
|
}
|
|
4823
5108
|
if (s.kind === "fieldSet" && this.int32Slots.isField(s.className, s.field)) {
|
|
4824
5109
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4825
5110
|
const v = this.emitExpr(s.value);
|
|
4826
|
-
this.
|
|
5111
|
+
this.emitStoreGuard(obj.name, s);
|
|
4827
5112
|
const { ptr } = this.classFieldPtr(obj.name, s.className, s.field);
|
|
4828
5113
|
this.storeInt32Field(ptr, v, s.value);
|
|
4829
5114
|
break;
|
|
@@ -4831,7 +5116,7 @@ export class LlEmitter {
|
|
|
4831
5116
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4832
5117
|
const v = this.emitExpr(s.value);
|
|
4833
5118
|
if (s.kind === "fieldSet")
|
|
4834
|
-
this.
|
|
5119
|
+
this.emitStoreGuard(obj.name, s);
|
|
4835
5120
|
else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
4836
5121
|
this.emitPublishedFieldGuard(obj.name, null, s.field);
|
|
4837
5122
|
const { ptr, type } = s.kind === "fieldSet"
|
|
@@ -5194,7 +5479,11 @@ export class LlEmitter {
|
|
|
5194
5479
|
if (integerArrayLoop && s.init?.kind === "varDecl" && s.init.init) {
|
|
5195
5480
|
const value = this.emitExpr(s.init.init);
|
|
5196
5481
|
start = B.tmp();
|
|
5197
|
-
|
|
5482
|
+
// The start is 0 or an enclosing array index plus one (at most
|
|
5483
|
+
// 2^32 - 1): on 64-bit targets the signed conversion is exact
|
|
5484
|
+
// and avoids x86-64's unsigned 64-bit conversion sequence.
|
|
5485
|
+
const conversion = this.sizeType === "i64" ? "fptosi" : "fptoui";
|
|
5486
|
+
B.line(`${start} = ${conversion} double ${value.name} to ${this.sizeType}`);
|
|
5198
5487
|
this.integerArrayBindings.add(integerLoop.localId);
|
|
5199
5488
|
}
|
|
5200
5489
|
B.line(`store ${this.sizeType} ${start}, ptr ${integerSlot}`);
|
|
@@ -5500,10 +5789,32 @@ export class LlEmitter {
|
|
|
5500
5789
|
// the actual ret. The parked value owns a synthetic slot-backed
|
|
5501
5790
|
// scope entry during each copy so a throwing finally releases it.
|
|
5502
5791
|
let v = null;
|
|
5503
|
-
|
|
5792
|
+
const moved = this.finallyStack.length === 0 ? this.movableReturn(s.value) : null;
|
|
5793
|
+
if (moved !== null) {
|
|
5794
|
+
// Returning an owned local hands its reference to the caller: no
|
|
5795
|
+
// retain now and no release of the local below.
|
|
5796
|
+
const t = B.tmp();
|
|
5797
|
+
B.line(`${t} = load ptr, ptr ${moved.entry.slot}`);
|
|
5798
|
+
v = { name: t, type: s.value.type };
|
|
5799
|
+
moved.scope.splice(moved.scope.indexOf(moved.entry), 1);
|
|
5800
|
+
this.releaseForJump(0, 0);
|
|
5801
|
+
moved.scope.splice(moved.index, 0, moved.entry);
|
|
5802
|
+
}
|
|
5803
|
+
else if (s.value !== null && this.currentBorrowedReturn) {
|
|
5504
5804
|
// A borrowed-return body hands back its walk without a reference
|
|
5505
|
-
// (it has no try statements, so no finally can intervene).
|
|
5506
|
-
|
|
5805
|
+
// (it has no try statements, so no finally can intervene). An
|
|
5806
|
+
// optional element read lends the element its array keeps alive.
|
|
5807
|
+
const read = this.optionalArrayReads.get(s.value);
|
|
5808
|
+
if (read) {
|
|
5809
|
+
const slot = B.slot();
|
|
5810
|
+
B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
5811
|
+
emitLocalArrayRead(this, { ...read, borrow: true }, slot);
|
|
5812
|
+
const t = B.tmp();
|
|
5813
|
+
B.line(`${t} = load ptr, ptr ${slot}`);
|
|
5814
|
+
v = { name: t, type: s.value.type };
|
|
5815
|
+
}
|
|
5816
|
+
else
|
|
5817
|
+
v = this.emitReadReceiver(s.value);
|
|
5507
5818
|
}
|
|
5508
5819
|
else if (s.value !== null) {
|
|
5509
5820
|
v = this.emitExpr(s.value);
|
|
@@ -5540,7 +5851,7 @@ export class LlEmitter {
|
|
|
5540
5851
|
this.finallyStack = savedFinally;
|
|
5541
5852
|
this.tryStack = savedTry;
|
|
5542
5853
|
}
|
|
5543
|
-
else {
|
|
5854
|
+
else if (moved === null) {
|
|
5544
5855
|
this.releaseForJump(0, 0);
|
|
5545
5856
|
}
|
|
5546
5857
|
if (!B.isTerminated()) {
|
|
@@ -5798,7 +6109,7 @@ export class LlEmitter {
|
|
|
5798
6109
|
const caseLabels = s.cases.map(() => B.newLabel("sw.c"));
|
|
5799
6110
|
const defaultIdx = s.cases.findIndex((c) => c.test === null);
|
|
5800
6111
|
const fallback = defaultIdx >= 0 ? caseLabels[defaultIdx] : end;
|
|
5801
|
-
if (!emitLiteralSwitch(this, disc, s.cases, caseLabels, fallback)) {
|
|
6112
|
+
if (!emitLiteralSwitch(this, disc, s.disc, s.cases, caseLabels, fallback)) {
|
|
5802
6113
|
s.cases.forEach((c, i) => {
|
|
5803
6114
|
if (c.test === null)
|
|
5804
6115
|
return;
|
|
@@ -6134,6 +6445,13 @@ export class LlEmitter {
|
|
|
6134
6445
|
``,
|
|
6135
6446
|
];
|
|
6136
6447
|
}
|
|
6448
|
+
/** The frozen-object guard of a class field store, unless the store
|
|
6449
|
+
* provably targets a still-private `this` (PublishedTypes.privateStores). */
|
|
6450
|
+
emitStoreGuard(obj, s) {
|
|
6451
|
+
if (this.publishedTypes?.privateStores.has(s) === true)
|
|
6452
|
+
return;
|
|
6453
|
+
this.emitPublishedFieldGuard(obj, s.className, s.field);
|
|
6454
|
+
}
|
|
6137
6455
|
/** The frozen-object guard of a class field or record field store:
|
|
6138
6456
|
* Node's "Cannot assign to read only property" TypeError when the target
|
|
6139
6457
|
* is published. `className` null means a record (`#<Object>`). */
|
|
@@ -6273,6 +6591,10 @@ export class LlEmitter {
|
|
|
6273
6591
|
this.ownSlot(result.owner.slot, result.owner.type);
|
|
6274
6592
|
return result.value;
|
|
6275
6593
|
}
|
|
6594
|
+
// A borrowed-return helper (such as an optional element read) lends its
|
|
6595
|
+
// result like any borrowed-return call.
|
|
6596
|
+
if (e.kind === "call" && this.borrowedReturns.has(e.callee) && this.canBorrowReceiver(e))
|
|
6597
|
+
return emitBorrowedResultCall(this, e);
|
|
6276
6598
|
const arrayRead = this.optionalArrayReads.get(e);
|
|
6277
6599
|
if (arrayRead)
|
|
6278
6600
|
return emitProjectedArrayRead(this, arrayRead);
|
|
@@ -6518,12 +6840,24 @@ export class LlEmitter {
|
|
|
6518
6840
|
const wrap = this.borrowableNullableWrap(e);
|
|
6519
6841
|
return wrap === "unit" || (wrap === "ref" && this.canBorrowReceiver(e.value));
|
|
6520
6842
|
}
|
|
6521
|
-
// A borrowed-return
|
|
6522
|
-
//
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6843
|
+
// A borrowed-return result stays reachable from its borrowed
|
|
6844
|
+
// arguments until the consumer runs. Each argument is lent without a
|
|
6845
|
+
// snapshot, so neither the callee nor a later argument may replace
|
|
6846
|
+
// what it was read from: a callee that preserves every edge cannot,
|
|
6847
|
+
// and any other one must leave the argument's locations unwritten.
|
|
6848
|
+
case "call": {
|
|
6849
|
+
if (!this.borrowedReturns.has(e.callee))
|
|
6850
|
+
return false;
|
|
6851
|
+
if (!e.args.every((arg) => this.referenceEffects.preserves(arg)))
|
|
6852
|
+
return false;
|
|
6853
|
+
const preserving = this.referenceEffects.functions.has(e.callee);
|
|
6854
|
+
if (preserving)
|
|
6855
|
+
return e.args.every((arg) => !isRefCounted(arg.type) || this.canBorrowReceiver(arg));
|
|
6856
|
+
if (this.referenceWrites === null)
|
|
6857
|
+
return false;
|
|
6858
|
+
const lent = borrowableInputs(this, e.args, false, this.referenceWrites.summary(e.callee));
|
|
6859
|
+
return e.args.every((arg, i) => !isRefCounted(arg.type) || lent[i] === true);
|
|
6860
|
+
}
|
|
6527
6861
|
// A required element read: the (borrowed) array owns the element
|
|
6528
6862
|
// while an edge-preserving index computes.
|
|
6529
6863
|
case "arrayGet":
|
|
@@ -6569,14 +6903,17 @@ export class LlEmitter {
|
|
|
6569
6903
|
};
|
|
6570
6904
|
const hostFor = (fn) => {
|
|
6571
6905
|
const allowed = borrowedIds(fn);
|
|
6572
|
-
|
|
6906
|
+
const host = {
|
|
6573
6907
|
pointerLocal: (local) => pointer(local.type),
|
|
6574
6908
|
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
6575
6909
|
borrowedReturn: (callee) => this.borrowedReturns.has(callee),
|
|
6576
6910
|
parameterAllowed: (id) => allowed.has(id),
|
|
6577
6911
|
};
|
|
6912
|
+
return this.referenceEffects.functions.has(fn.name) ? host : this.unwrittenWalkHost(fn, host);
|
|
6578
6913
|
};
|
|
6579
|
-
|
|
6914
|
+
// With ReferenceWrites, a body that writes none of its walks' locations
|
|
6915
|
+
// qualifies like one that preserves every edge.
|
|
6916
|
+
const preserving = [...this.fnByName.values()].filter((fn) => this.referenceEffects.functions.has(fn.name) || this.referenceWrites !== null);
|
|
6580
6917
|
const candidates = preserving.filter((fn) => {
|
|
6581
6918
|
if (!pointer(fn.returnType) || fn.async || fn.generator || fn.captures)
|
|
6582
6919
|
return false;
|
|
@@ -6597,6 +6934,8 @@ export class LlEmitter {
|
|
|
6597
6934
|
if (!analyzeWalks(fn, hostFor(fn)).returnsWalk)
|
|
6598
6935
|
continue;
|
|
6599
6936
|
this.borrowedReturns.add(fn.name);
|
|
6937
|
+
// Nested borrowed returns extend the locations a caller depends on.
|
|
6938
|
+
this.borrowedReturnKeyCache.clear();
|
|
6600
6939
|
changed = true;
|
|
6601
6940
|
}
|
|
6602
6941
|
}
|
|
@@ -6624,6 +6963,12 @@ export class LlEmitter {
|
|
|
6624
6963
|
return this.nullableFieldGet(e) === null;
|
|
6625
6964
|
case "recordGet":
|
|
6626
6965
|
return true;
|
|
6966
|
+
// emitReadReceiver lends a required element of a borrowed array when
|
|
6967
|
+
// computing the index removes no edge.
|
|
6968
|
+
case "arrayGet":
|
|
6969
|
+
return (e.arr.type.kind === "array" &&
|
|
6970
|
+
this.optionalArrayReads.get(e) === null &&
|
|
6971
|
+
this.referenceEffects.preserves(e.index));
|
|
6627
6972
|
case "unionNarrow":
|
|
6628
6973
|
return this.nullableUnions.has(e.unionId);
|
|
6629
6974
|
case "downcast":
|