@scriptc/compiler 0.3.4 → 0.3.5
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 +131 -10
- package/dist/backend/llvm/array-search.js +1 -1
- package/dist/backend/llvm/array-search.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.d.ts +6 -0
- package/dist/backend/llvm/call-lifetimes.js +19 -0
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.d.ts +2 -21
- package/dist/backend/llvm/constant-callbacks.js +36 -219
- package/dist/backend/llvm/constant-callbacks.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.test.js +22 -0
- package/dist/backend/llvm/constant-callbacks.test.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.d.ts +9 -0
- package/dist/backend/llvm/dense-array-access.js +27 -1
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +6 -0
- package/dist/backend/llvm/dyn.js +40 -6
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +75 -1
- package/dist/backend/llvm/emitter.js +408 -38
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +17 -7
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js +1 -1
- package/dist/backend/llvm/expr-callbacks.d.ts +11 -0
- package/dist/backend/llvm/expr-callbacks.js +53 -0
- package/dist/backend/llvm/expr-callbacks.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js +88 -2
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +50 -27
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.js +9 -1
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +4 -0
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/integer-clones.d.ts +103 -0
- package/dist/backend/llvm/integer-clones.js +1336 -0
- package/dist/backend/llvm/integer-clones.js.map +1 -0
- package/dist/backend/llvm/integer-clones.test.d.ts +1 -0
- package/dist/backend/llvm/integer-clones.test.js +219 -0
- package/dist/backend/llvm/integer-clones.test.js.map +1 -0
- package/dist/backend/llvm/lib-datagram.js +14 -0
- package/dist/backend/llvm/lib-datagram.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +4 -0
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +24 -0
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +34 -2
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-tls.js +6 -0
- package/dist/backend/llvm/lib-tls.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.js +1 -1
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/map-entry-fusion.d.ts +28 -0
- package/dist/backend/llvm/map-entry-fusion.js +233 -0
- package/dist/backend/llvm/map-entry-fusion.js.map +1 -0
- package/dist/backend/llvm/map-entry-fusion.test.d.ts +1 -0
- package/dist/backend/llvm/map-entry-fusion.test.js +183 -0
- package/dist/backend/llvm/map-entry-fusion.test.js.map +1 -0
- package/dist/backend/llvm/map-number-lookup.d.ts +2 -0
- package/dist/backend/llvm/map-number-lookup.js +1 -1
- package/dist/backend/llvm/map-number-lookup.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.test.js +10 -11
- package/dist/backend/llvm/map-number-lookup.test.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.d.ts +2 -0
- package/dist/backend/llvm/map-read-lifetimes.js +3 -0
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +2 -2
- package/dist/backend/llvm/publish.d.ts +16 -0
- package/dist/backend/llvm/publish.js +81 -10
- package/dist/backend/llvm/publish.js.map +1 -1
- package/dist/backend/llvm/publish.test.d.ts +1 -0
- package/dist/backend/llvm/publish.test.js +94 -0
- package/dist/backend/llvm/publish.test.js.map +1 -0
- package/dist/backend/llvm/record-results.js +76 -2
- package/dist/backend/llvm/record-results.js.map +1 -1
- package/dist/backend/llvm/record-sources.d.ts +7 -0
- package/dist/backend/llvm/record-sources.js +14 -0
- package/dist/backend/llvm/record-sources.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +9 -3
- package/dist/backend/llvm/shapes.js +18 -14
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +5 -3
- package/dist/backend/llvm/shapes.test.js.map +1 -1
- package/dist/backend/llvm/string-lifetimes.test.js +75 -1
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/walkers.d.ts +17 -0
- package/dist/backend/llvm/walkers.js +123 -0
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +2 -1
- package/dist/backend/native/executable.js +3 -1
- package/dist/backend/native/executable.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/frontend/lowering/builtin-calls.js +3 -0
- package/dist/frontend/lowering/builtin-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/callable-aliases.js +54 -8
- package/dist/frontend/lowering/builtins/callable-aliases.js.map +1 -1
- package/dist/frontend/lowering/builtins/child-process-async.d.ts +8 -6
- package/dist/frontend/lowering/builtins/child-process-async.js +45 -29
- package/dist/frontend/lowering/builtins/child-process-async.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +2 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js +5 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem.js +60 -0
- package/dist/frontend/lowering/builtins/filesystem.js.map +1 -1
- package/dist/frontend/lowering/builtins/fs-streams.d.ts +26 -0
- package/dist/frontend/lowering/builtins/fs-streams.js +131 -0
- package/dist/frontend/lowering/builtins/fs-streams.js.map +1 -0
- package/dist/frontend/lowering/builtins/module-calls.js +5 -1
- package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.js +150 -8
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.js +33 -12
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/devirtualize.js +20 -7
- package/dist/frontend/lowering/devirtualize.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +58 -3
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +7 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +55 -15
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.d.ts +9 -1
- package/dist/frontend/lowering/lower-event-emitter.js +78 -0
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
- package/dist/frontend/lowering/lower-exprs.js +132 -58
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +26 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +2 -1
- package/dist/frontend/lowering/lower-server.js +73 -5
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +12 -6
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +8 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +3 -1
- package/dist/frontend/lowering/lowerer.js +2 -2
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/record-sources.d.ts +15 -0
- package/dist/frontend/lowering/record-sources.js +46 -0
- package/dist/frontend/lowering/record-sources.js.map +1 -0
- package/dist/frontend/lowering/surfaces.js +33 -1
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +3 -0
- package/dist/frontend/type-mapper.js +53 -0
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/ir/analysis.d.ts +10 -0
- package/dist/ir/analysis.js +35 -0
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/analysis.test.js +69 -1
- package/dist/ir/analysis.test.js.map +1 -1
- package/dist/ir/builtin-effects.js +36 -0
- package/dist/ir/builtin-effects.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +91 -7
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/capture-values.d.ts +107 -0
- package/dist/ir/capture-values.js +850 -0
- package/dist/ir/capture-values.js.map +1 -0
- package/dist/ir/capture-values.test.d.ts +1 -0
- package/dist/ir/capture-values.test.js +268 -0
- package/dist/ir/capture-values.test.js.map +1 -0
- package/dist/ir/field-values.d.ts +16 -0
- package/dist/ir/field-values.js +128 -0
- package/dist/ir/field-values.js.map +1 -0
- package/dist/ir/field-values.test.d.ts +1 -0
- package/dist/ir/field-values.test.js +160 -0
- package/dist/ir/field-values.test.js.map +1 -0
- package/dist/ir/final-fields.d.ts +4 -1
- package/dist/ir/final-fields.js +11 -3
- package/dist/ir/final-fields.js.map +1 -1
- package/dist/ir/function-references.test.js +1 -1
- package/dist/ir/function-references.test.js.map +1 -1
- package/dist/ir/init-only-globals.d.ts +16 -0
- package/dist/ir/init-only-globals.js +121 -0
- package/dist/ir/init-only-globals.js.map +1 -0
- package/dist/ir/init-only-globals.test.d.ts +1 -0
- package/dist/ir/init-only-globals.test.js +115 -0
- package/dist/ir/init-only-globals.test.js.map +1 -0
- package/dist/ir/initializer-bindings.d.ts +18 -1
- package/dist/ir/initializer-bindings.js +91 -1
- package/dist/ir/initializer-bindings.js.map +1 -1
- package/dist/ir/initializer-bindings.test.js +57 -2
- package/dist/ir/initializer-bindings.test.js.map +1 -1
- package/dist/ir/int32-slots.d.ts +22 -0
- package/dist/ir/int32-slots.js +376 -13
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +91 -8
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-loop-facts.d.ts +19 -1
- package/dist/ir/integer-loop-facts.js +155 -7
- package/dist/ir/integer-loop-facts.js.map +1 -1
- package/dist/ir/integer-ranges.d.ts +8 -5
- package/dist/ir/integer-ranges.js +147 -55
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +225 -7
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +9 -1
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/property-bags.d.ts +14 -0
- package/dist/ir/property-bags.js +121 -0
- package/dist/ir/property-bags.js.map +1 -0
- package/dist/ir/receiver-readers.d.ts +21 -0
- package/dist/ir/receiver-readers.js +217 -0
- package/dist/ir/receiver-readers.js.map +1 -0
- package/dist/ir/runtime-features.js +6 -1
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/validate.js +26 -0
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +21 -0
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/int-infer.js +3 -0
- package/dist/library/int-infer.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +28 -2
|
@@ -6,7 +6,7 @@ import { CYC_BUF, CYC_HDR, FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "
|
|
|
6
6
|
import { hasLoneSurrogate, llvmBytes as llBytes, wtf8Bytes } from "../literals.js";
|
|
7
7
|
import { InternalCompilerError } from "../../errors.js";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
-
import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
9
|
+
import { endsWithJump, matchFieldSelfConcat, matchStringSelfConcat, readsArraySlot, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
10
10
|
import { emitLibraryIdentityLines } from "../library-identity-markers.js";
|
|
11
11
|
import { loopTestsEveryIteration } from "../loop-polls.js";
|
|
12
12
|
import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_EVENT_CLASS, RUNTIME_EVENT_TARGET_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeEquals, typeKey, STRING, VOID, } from "../../ir/ir.js";
|
|
@@ -21,16 +21,21 @@ import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
|
21
21
|
import { RESULT_ENTRY_PREFIX, addRecordResultEntries } from "./record-results.js";
|
|
22
22
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
23
23
|
import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
24
|
-
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
24
|
+
import { analyzeIntegerRanges, INT32_RANGE, } from "../../ir/integer-ranges.js";
|
|
25
25
|
import { unshareNodes } from "../../ir/unshare.js";
|
|
26
|
-
import {
|
|
26
|
+
import { copyStableCaptures } from "../../ir/capture-values.js";
|
|
27
|
+
import { analyzeInt32Slots, dynamicFieldStores } from "../../ir/int32-slots.js";
|
|
27
28
|
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
28
29
|
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
30
|
+
import { dropUnwrittenPropertyBags } from "../../ir/property-bags.js";
|
|
31
|
+
import { freezeInitOnlyGlobals } from "../../ir/init-only-globals.js";
|
|
32
|
+
import { hoistFinalFieldReads } from "../../ir/field-values.js";
|
|
29
33
|
import { findIntegerViews } from "./integer-views.js";
|
|
30
34
|
import { findInt32Abis, INT32_ABI_SUFFIX, SCALAR_UNION_DUMMY, SCALAR_UNION_RESULT, } from "./int32-abi.js";
|
|
31
35
|
import { findInt32Locals } from "./int32-locals.js";
|
|
36
|
+
import { emitIntegerClone, emitIntegerDispatcher, findIntegerClones, NO_INTEGER_CLONES, } from "./integer-clones.js";
|
|
32
37
|
import { exactInteger, narrowInteger } from "./integer-values.js";
|
|
33
|
-
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
38
|
+
import { conditionalLoopWrites, findInitializerBindings, initializerLocalCandidates, withInitializerBindings, withInitializerLocals, } from "../../ir/initializer-bindings.js";
|
|
34
39
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
35
40
|
import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback, } from "../ffi-callbacks.js";
|
|
36
41
|
import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
|
|
@@ -45,8 +50,9 @@ import { NullableRefFields } from "./nullable-fields.js";
|
|
|
45
50
|
import { InlineUnionFields, loadInlineUnion, presentBits, stackUnionBox, storeInlineUnion, unionBoxBits, } from "./inline-unions.js";
|
|
46
51
|
import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
|
|
47
52
|
import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js";
|
|
48
|
-
import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
|
|
53
|
+
import { emitDenseArrayGet, emitDenseArraySet, emitDenseSlotUnlink } from "./dense-array-access.js";
|
|
49
54
|
import { emitStackMapRead, findMapReadLifetimes, findReadNumberMapWrites, matchMapRead, } from "./map-read-lifetimes.js";
|
|
55
|
+
import { findMapEntryFusions } from "./map-entry-fusion.js";
|
|
50
56
|
import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
|
|
51
57
|
import { borrowableInputs, emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js";
|
|
52
58
|
import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
|
|
@@ -72,7 +78,7 @@ import { PublishWalkers } from "./publish.js";
|
|
|
72
78
|
import { computeImmutableFields, stableGlobals } from "../immutability.js";
|
|
73
79
|
import { computePublishedTypes } from "../publication.js";
|
|
74
80
|
import { emitExpr } from "./expr-dispatch.js";
|
|
75
|
-
import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
|
|
81
|
+
import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, emitterOnceAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
|
|
76
82
|
import { streamDataAdapter, streamDoneFnFor, cryptoBytesThunkFor, cryptoIntThunkFor, fsRenameThunkFor, streamCbThunkFor, zlibBytesThunkFor, } from "./expr-stream-callbacks.js";
|
|
77
83
|
import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
|
|
78
84
|
import { emitIntegerLoopIndex, emitBytesIndex, emitBytesData, emitBytesLength, emitBytesGet, emitToUint32, emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js";
|
|
@@ -129,14 +135,14 @@ export function emitLlvmModule(input, options = {}) {
|
|
|
129
135
|
const mod = unshareNodes(input);
|
|
130
136
|
// Keep source storage intact for debugger inspection in dev builds.
|
|
131
137
|
return new LlEmitter(options.debugSources === undefined
|
|
132
|
-
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
|
|
138
|
+
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
|
|
133
139
|
: mod, options).emit();
|
|
134
140
|
}
|
|
135
141
|
/** Keep large translation units below the host's single-string limit. */
|
|
136
142
|
export function emitLlvmModuleSource(input, options = {}) {
|
|
137
143
|
const mod = unshareNodes(input);
|
|
138
144
|
const parts = new LlEmitter(options.debugSources === undefined
|
|
139
|
-
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
|
|
145
|
+
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
|
|
140
146
|
: mod, options).emitParts();
|
|
141
147
|
const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
|
|
142
148
|
if (length <= 256 * 1024 * 1024)
|
|
@@ -147,6 +153,9 @@ function llStrBytes(text) {
|
|
|
147
153
|
return llBytes(wtf8Bytes(text));
|
|
148
154
|
}
|
|
149
155
|
const NO_BORROWED = new Set();
|
|
156
|
+
/** ScrExcKind tags of a generator slot holding a number or a boolean. */
|
|
157
|
+
const SCR_EXC_F64 = 1;
|
|
158
|
+
const SCR_EXC_BOOL = 2;
|
|
150
159
|
/** Whether any node of `e` names the local (reads, writes, declarations,
|
|
151
160
|
* captures). */
|
|
152
161
|
function mentionsLocal(e, localId) {
|
|
@@ -226,6 +235,12 @@ export class LlEmitter {
|
|
|
226
235
|
}
|
|
227
236
|
return `, !tbaa !${tag}`;
|
|
228
237
|
}
|
|
238
|
+
/** The `!range` attachment of an array length load: a length is at most
|
|
239
|
+
* 2^32 - 1 (the runtime's SCR_ARR_MAX_LENGTH), so conversions to double
|
|
240
|
+
* and back fold. 32-bit targets load the full range anyway. */
|
|
241
|
+
get arrayLengthRange() {
|
|
242
|
+
return this.sizeType === "i64" ? ", !range !{i64 0, i64 4294967296}" : "";
|
|
243
|
+
}
|
|
229
244
|
fieldAliasAttachment(ptr) {
|
|
230
245
|
const tag = this.fieldPointerTags.get(ptr);
|
|
231
246
|
return tag === undefined ? "" : `, !tbaa !${tag}`;
|
|
@@ -312,6 +327,8 @@ export class LlEmitter {
|
|
|
312
327
|
/** Interned NUL-terminated C-string constants (scr_jb_puts labels, the
|
|
313
328
|
* stringify indent text): UTF-8 text → { symbol, byte length }. */
|
|
314
329
|
cstrs = new Map();
|
|
330
|
+
/** Interned ScrMapOps descriptors (scr_map.h): initializer → symbol. */
|
|
331
|
+
mapOpsConsts = new Map();
|
|
315
332
|
/** Type-directed walker functions (JSON serializers, the indent
|
|
316
333
|
* rewriter, union ToString/join) — interned per typeKey/unionId, defs
|
|
317
334
|
* flushed with the shape helpers. */
|
|
@@ -355,6 +372,8 @@ export class LlEmitter {
|
|
|
355
372
|
callArrayReads = new Map();
|
|
356
373
|
mapReadLifetimes = { locals: new Map(), arguments: new Map() };
|
|
357
374
|
readNumberMapWrites = new Set();
|
|
375
|
+
/** The current function's fused string-key Map get-then-set pairs (map-entry-fusion.ts). */
|
|
376
|
+
mapEntryFusion = null;
|
|
358
377
|
callLifetimes;
|
|
359
378
|
constantCallbacks;
|
|
360
379
|
currentConstantCallbacks = new Map();
|
|
@@ -518,8 +537,20 @@ export class LlEmitter {
|
|
|
518
537
|
initializerBindings;
|
|
519
538
|
/** Whole-program int32 storage and parameter/result facts. */
|
|
520
539
|
int32Slots;
|
|
540
|
+
/** The program has no runtime store into a checked-dynamic value
|
|
541
|
+
* (dynamicFieldStores), so live views of classes and records never
|
|
542
|
+
* commit back into their objects. Library builds keep their commits:
|
|
543
|
+
* their values cross a native boundary. */
|
|
544
|
+
liveViewsReadOnly;
|
|
521
545
|
/** Functions whose direct calls use i32 parameters or results. */
|
|
522
546
|
int32Abis;
|
|
547
|
+
/** Pure number functions with speculative integer clones. */
|
|
548
|
+
integerClones;
|
|
549
|
+
/** The IR function being emitted. */
|
|
550
|
+
currentFunctionName = "";
|
|
551
|
+
integerParamRange(fnName, localId) {
|
|
552
|
+
return this.int32Slots.facts(fnName).params?.get(localId) ?? null;
|
|
553
|
+
}
|
|
523
554
|
/** Immutable module constants (contract types) whose graphs become
|
|
524
555
|
* immortal once initialized: their reads need no reference counting. */
|
|
525
556
|
immortalGlobals;
|
|
@@ -543,6 +574,9 @@ export class LlEmitter {
|
|
|
543
574
|
byteWindowEntries = new Map();
|
|
544
575
|
bytesBounds = new Set();
|
|
545
576
|
integerViews = new Map();
|
|
577
|
+
/** Module globals the current function owns outright (initializer
|
|
578
|
+
* bindings): their function-local slot, which stands in for the global. */
|
|
579
|
+
privateGlobalSlots = new Map();
|
|
546
580
|
/** Number locals of the current function stored only as an i32
|
|
547
581
|
* (int32-locals.ts); their slots are `alloca i32`. */
|
|
548
582
|
int32Locals = new Map();
|
|
@@ -576,6 +610,9 @@ export class LlEmitter {
|
|
|
576
610
|
* switched-coroutine intrinsics (wasm32 async bodies and generators,
|
|
577
611
|
* native synchronous generators; see coroLowered). */
|
|
578
612
|
currentCoro = null;
|
|
613
|
+
/** Inline generator slot accesses need the %ScrGenHead/%ScrGenSlot
|
|
614
|
+
* mirrors in the module header. */
|
|
615
|
+
usesGenSlots = false;
|
|
579
616
|
/** The generator channels of the function being emitted (null outside
|
|
580
617
|
* generator bodies): yieldExpr emission reads them, and emitTryCatch's
|
|
581
618
|
* catch prologue emits the GENRET sentinel re-unwind exactly here. */
|
|
@@ -600,6 +637,9 @@ export class LlEmitter {
|
|
|
600
637
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
601
638
|
this.initializerBindings = findInitializerBindings(mod);
|
|
602
639
|
this.int32Slots = analyzeInt32Slots(mod);
|
|
640
|
+
const dynamicStores = dynamicFieldStores(mod);
|
|
641
|
+
this.liveViewsReadOnly =
|
|
642
|
+
mod.lib === undefined && !dynamicStores.all && dynamicStores.names.size === 0;
|
|
603
643
|
this.int32Abis = this.debug === null ? findInt32Abis(mod, this.int32Slots) : new Map();
|
|
604
644
|
this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
|
|
605
645
|
this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
|
|
@@ -743,6 +783,20 @@ export class LlEmitter {
|
|
|
743
783
|
return shape !== undefined && !recordDynKeepsIdentity(shape);
|
|
744
784
|
});
|
|
745
785
|
this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
|
|
786
|
+
// Integer clones signal a stack overflow through their result, which
|
|
787
|
+
// needs the single-threaded guard; worker programs also poll for
|
|
788
|
+
// termination, and debug builds and stack traces keep every frame.
|
|
789
|
+
// Clones read number arrays their function borrows (64-bit layouts).
|
|
790
|
+
this.integerClones =
|
|
791
|
+
this.debug === null &&
|
|
792
|
+
this.stackChecks !== null &&
|
|
793
|
+
mod.workers !== true &&
|
|
794
|
+
mod.lib === undefined &&
|
|
795
|
+
!this.stackTraces
|
|
796
|
+
? findIntegerClones(mod, this.sizeType === "i64"
|
|
797
|
+
? (fn, index) => this.callLifetimes.borrowed.get(fn)?.has(index) === true
|
|
798
|
+
: undefined)
|
|
799
|
+
: NO_INTEGER_CLONES;
|
|
746
800
|
this.stackCallbacks = new StackCallbacks(this.fnByName);
|
|
747
801
|
this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
|
|
748
802
|
this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks);
|
|
@@ -780,6 +834,7 @@ export class LlEmitter {
|
|
|
780
834
|
needBadTag: () => this.needBadTag(),
|
|
781
835
|
internLiteral: (text) => this.internLiteral(text),
|
|
782
836
|
cstr: (text) => this.cstr(text),
|
|
837
|
+
mapOps: (operands) => this.mapOps(operands),
|
|
783
838
|
unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
|
|
784
839
|
retypeGuardRef: (unionId) => this.retypeGuardRef(unionId),
|
|
785
840
|
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
|
@@ -880,6 +935,10 @@ export class LlEmitter {
|
|
|
880
935
|
virtualSlotBorrowed(rootName, slotIndex) {
|
|
881
936
|
return this.virtualBorrows.slot(rootName, slotIndex);
|
|
882
937
|
}
|
|
938
|
+
/** Optimized emission: no debug-info (dev) posture. */
|
|
939
|
+
get optimizing() {
|
|
940
|
+
return this.debug === null;
|
|
941
|
+
}
|
|
883
942
|
/** The vtable entry for one implementation (VirtualBorrows.entry). */
|
|
884
943
|
virtualEntry(implFn) {
|
|
885
944
|
return this.virtualBorrows.entry(implFn);
|
|
@@ -1376,7 +1435,25 @@ export class LlEmitter {
|
|
|
1376
1435
|
` call void @scr_run_exit_listeners(double %${prefix}hd)`,
|
|
1377
1436
|
];
|
|
1378
1437
|
};
|
|
1379
|
-
|
|
1438
|
+
// The entry's exits release the module globals only where the thread
|
|
1439
|
+
// frees its heap when its script ends (scr_exit_frees: workers, and
|
|
1440
|
+
// builds that check for leaks); the main thread of other builds leaves
|
|
1441
|
+
// it to the operating system.
|
|
1442
|
+
const exitReleaseLines = (prefix) => {
|
|
1443
|
+
const releases = globalReleaseLines(prefix);
|
|
1444
|
+
if (releases.length === 0)
|
|
1445
|
+
return releases;
|
|
1446
|
+
this.declare(`declare zeroext i1 @scr_exit_frees()`);
|
|
1447
|
+
return [
|
|
1448
|
+
` %${prefix}.frees = call zeroext i1 @scr_exit_frees()`,
|
|
1449
|
+
` br i1 %${prefix}.frees, label %${prefix}.release, label %${prefix}.kept`,
|
|
1450
|
+
`${prefix}.release:`,
|
|
1451
|
+
...releases,
|
|
1452
|
+
` br label %${prefix}.kept`,
|
|
1453
|
+
`${prefix}.kept:`,
|
|
1454
|
+
];
|
|
1455
|
+
};
|
|
1456
|
+
const globalReleases = exitReleaseLines("g");
|
|
1380
1457
|
const asyncEntry = this.fnByName.get(this.mod.entry)?.async === true;
|
|
1381
1458
|
const entryMayThrow = this.mayThrow.has(this.mod.entry);
|
|
1382
1459
|
// The event loop runs when timers appeared OR any async/generator
|
|
@@ -1388,12 +1465,12 @@ export class LlEmitter {
|
|
|
1388
1465
|
const runsLoop = this.usesTimers ||
|
|
1389
1466
|
this.ffiHasForeignCallback ||
|
|
1390
1467
|
this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
|
|
1391
|
-
const uncaughtReleases = entryMayThrow && !asyncEntry ?
|
|
1392
|
-
const loopReleasesU = runsLoop ?
|
|
1393
|
-
const loopReleasesR = runsLoop ?
|
|
1394
|
-
const topRejectReleases = asyncEntry ?
|
|
1395
|
-
const topPendingReleases = asyncEntry ?
|
|
1396
|
-
const loopReportedReleases = runsLoop ?
|
|
1468
|
+
const uncaughtReleases = entryMayThrow && !asyncEntry ? exitReleaseLines("gu") : [];
|
|
1469
|
+
const loopReleasesU = runsLoop ? exitReleaseLines("gl") : [];
|
|
1470
|
+
const loopReleasesR = runsLoop ? exitReleaseLines("gr") : [];
|
|
1471
|
+
const topRejectReleases = asyncEntry ? exitReleaseLines("gt") : [];
|
|
1472
|
+
const topPendingReleases = asyncEntry ? exitReleaseLines("gp") : [];
|
|
1473
|
+
const loopReportedReleases = runsLoop ? exitReleaseLines("gq") : [];
|
|
1397
1474
|
// main's epilogues read the flag / the loop entry points — declared
|
|
1398
1475
|
// HERE, before the extern block flushes. They run once, so they keep
|
|
1399
1476
|
// the out-of-line scr_exc_pending call; per-call checks inline the
|
|
@@ -1519,22 +1596,20 @@ export class LlEmitter {
|
|
|
1519
1596
|
// preserving the target object's identity.
|
|
1520
1597
|
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`,
|
|
1521
1598
|
`%ScrArrOps = type { ptr, ptr, ptr }`,
|
|
1522
|
-
// The ScrMap prefix { rc, key_kind, val_kind,
|
|
1523
|
-
//
|
|
1524
|
-
|
|
1525
|
-
...(this.rcHelpers === null
|
|
1526
|
-
? []
|
|
1527
|
-
: [`%ScrMapRc = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr }`]),
|
|
1599
|
+
// The ScrMap prefix { rc, key_kind, val_kind, traced } — the inline
|
|
1600
|
+
// RC fast paths test the traced byte (shapes.ts).
|
|
1601
|
+
...(this.rcHelpers === null ? [] : [`%ScrMapRc = type { ${this.sizeType}, i8, i8, i8 }`]),
|
|
1528
1602
|
// The complete ScrMap and its { key, val, hash } entry, read by the
|
|
1529
1603
|
// inline number-key lookup (sc_map_entry_f64) and the inline
|
|
1530
|
-
// iteration steps (sc_map_iter_entry): field
|
|
1531
|
-
// field
|
|
1532
|
-
//
|
|
1604
|
+
// iteration steps (sc_map_iter_entry): field 8 is the i32 entry
|
|
1605
|
+
// count, field 10 the i64 absent-key filter, entries is field 11,
|
|
1606
|
+
// nbuckets and buckets fields 13 and 14, the direct index and its i32
|
|
1607
|
+
// length fields 15 and 16.
|
|
1533
1608
|
...(this.decls.has(NUMBER_MAP_ENTRY_DECL) ||
|
|
1534
1609
|
this.decls.has(STRING_MAP_ENTRY_DECL) ||
|
|
1535
1610
|
this.decls.has(MAP_ITER_DECL)
|
|
1536
1611
|
? [
|
|
1537
|
-
`%ScrMapIx = type { ${this.sizeType},
|
|
1612
|
+
`%ScrMapIx = type { ${this.sizeType}, i8, i8, i8, i8, i32, ptr, ptr, i32, i32, i64, ptr, i32, i32, ptr, ptr, i32, i32 }`,
|
|
1538
1613
|
]
|
|
1539
1614
|
: []),
|
|
1540
1615
|
// String-keyed reference reads load the value slot of the entry
|
|
@@ -1572,7 +1647,7 @@ export class LlEmitter {
|
|
|
1572
1647
|
// typed-array access GEPs through this directly: the IR type already
|
|
1573
1648
|
// fixes elem, so the hot path needs neither a runtime kind load nor
|
|
1574
1649
|
// the generic scr_bytes_get/set call.
|
|
1575
|
-
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`,
|
|
1650
|
+
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, i8, ptr }`,
|
|
1576
1651
|
// The capture box { rc, kind, obj_retain, obj_release, obj_trace,
|
|
1577
1652
|
// slot } — TDZ reads peek the payload slot (offset 40) directly.
|
|
1578
1653
|
`%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
|
@@ -1583,6 +1658,15 @@ export class LlEmitter {
|
|
|
1583
1658
|
`%ScrDynPath = type { ptr, ptr, ${this.sizeType} }`,
|
|
1584
1659
|
`%ScrIslandModule = type { ptr, ptr, ${this.sizeType}, ${this.sizeType}, i32, ptr, ${this.sizeType}, ${this.sizeType} }`,
|
|
1585
1660
|
`%ScrIslandEdge = type { ptr, ptr, ptr, i32 }`,
|
|
1661
|
+
// A generator's prefix { rc, receiver, fiber, state, out } and its
|
|
1662
|
+
// value slot { kind, f64, b, payload, release_fn } (scr_async.c pins
|
|
1663
|
+
// both): number and boolean yields and drives address OUT directly.
|
|
1664
|
+
...(this.usesGenSlots
|
|
1665
|
+
? [
|
|
1666
|
+
`%ScrGenSlot = type { i32, double, i8, ptr, ptr }`,
|
|
1667
|
+
`%ScrGenHead = type { ${this.sizeType}, ptr, ptr, i32, %ScrGenSlot }`,
|
|
1668
|
+
]
|
|
1669
|
+
: []),
|
|
1586
1670
|
];
|
|
1587
1671
|
for (const line of shapes.typeDefs)
|
|
1588
1672
|
out.push(line);
|
|
@@ -1720,6 +1804,11 @@ export class LlEmitter {
|
|
|
1720
1804
|
}
|
|
1721
1805
|
if (this.cstrs.size > 0)
|
|
1722
1806
|
out.push(``);
|
|
1807
|
+
for (const [body, sym] of this.mapOpsConsts) {
|
|
1808
|
+
out.push(`@${sym} = internal constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr } { ${body} }`);
|
|
1809
|
+
}
|
|
1810
|
+
if (this.mapOpsConsts.size > 0)
|
|
1811
|
+
out.push(``);
|
|
1723
1812
|
for (const line of ffiCallbacks.globals)
|
|
1724
1813
|
out.push(line);
|
|
1725
1814
|
if (ffiCallbacks.globals.length > 0)
|
|
@@ -2196,19 +2285,25 @@ export class LlEmitter {
|
|
|
2196
2285
|
const sz = this.sizeType;
|
|
2197
2286
|
let mapTmp = 0;
|
|
2198
2287
|
const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`);
|
|
2199
|
-
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
|
|
2288
|
+
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 16`, ...(sz === "i64"
|
|
2289
|
+
? [` %nd32 = load i32, ptr %ndp`, ` %nd = zext i32 %nd32 to i64`]
|
|
2290
|
+
: [` %nd = load i32, ptr %ndp`]),
|
|
2200
2291
|
// A non-integral key inside the table is as absent as one outside it.
|
|
2201
2292
|
...mapIndex.lines.map((line) => ` ${line}`), ` br i1 ${mapIndex.ok}, label %slot, label %outside`, `slot:`, sz === "i64"
|
|
2202
2293
|
? ` %i = add i64 ${mapIndex.wide}, 0`
|
|
2203
|
-
: ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2294
|
+
: ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 15`, ` %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 11`, ` %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:`, ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, ` ret ptr %hashed`, `}`, ``,
|
|
2204
2295
|
// Hashing (scr_map_find_f64): the SameValueZero key word, its
|
|
2205
2296
|
// scr_key_mix hash (zero remapped to one) and the absent-key filter.
|
|
2206
|
-
`define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, `entry:`, ` %nan = fcmp uno double %k, %k`, ` %zero = fcmp oeq double %k, 0.0`, ` %kz = select i1 %zero, double 0.0, double %k`, ` %kzb = bitcast double %kz to i64`, ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, ` %x1s = lshr i64 %kb, 30`, ` %x1 = xor i64 %kb, %x1s`, ` %x2 = mul i64 %x1, -4658895280553007687`, ` %x3s = lshr i64 %x2, 27`, ` %x3 = xor i64 %x2, %x3s`, ` %x4 = mul i64 %x3, -7723592293110705685`, ` %x5s = lshr i64 %x4, 31`, ` %x5 = xor i64 %x4, %x5s`, ` %hz = icmp eq i64 %x5, 0`, ` %h = select i1 %hz, i64 1, i64 %x5`, ` %f1s = lshr i64 %h, 44`, ` %f1a = and i64 %f1s, 63`, ` %f1 = shl i64 1, %f1a`, ` %f2s = lshr i64 %h, 50`, ` %f2a = and i64 %f2s, 63`, ` %f2 = shl i64 1, %f2a`, ` %fb = or i64 %f1, %f2`, ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2297
|
+
`define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, `entry:`, ` %nan = fcmp uno double %k, %k`, ` %zero = fcmp oeq double %k, 0.0`, ` %kz = select i1 %zero, double 0.0, double %k`, ` %kzb = bitcast double %kz to i64`, ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, ` %x1s = lshr i64 %kb, 30`, ` %x1 = xor i64 %kb, %x1s`, ` %x2 = mul i64 %x1, -4658895280553007687`, ` %x3s = lshr i64 %x2, 27`, ` %x3 = xor i64 %x2, %x3s`, ` %x4 = mul i64 %x3, -7723592293110705685`, ` %x5s = lshr i64 %x4, 31`, ` %x5 = xor i64 %x4, %x5s`, ` %hz = icmp eq i64 %x5, 0`, ` %h = select i1 %hz, i64 1, i64 %x5`, ` %f1s = lshr i64 %h, 44`, ` %f1a = and i64 %f1s, 63`, ` %f1 = shl i64 1, %f1a`, ` %f2s = lshr i64 %h, 50`, ` %f2a = and i64 %f2s, 63`, ` %f2 = shl i64 1, %f2a`, ` %fb = or i64 %f1, %f2`, ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, ` %filter = load i64, ptr %fp`, ` %fm = and i64 %filter, %fb`, ` %fin = icmp eq i64 %fm, %fb`, ` br i1 %fin, label %table, label %miss`, `table:`, ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, ...(sz === "i64"
|
|
2298
|
+
? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`]
|
|
2299
|
+
: [` %nb = load i32, ptr %nbp`]), ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, ` %ents = load ptr, ptr %eps`, ` %linear = icmp eq ${sz} %nb, 0`, ` br i1 %linear, label %scan, label %buckets`,
|
|
2207
2300
|
// Small maps keep no bucket table: scan the cached entry hashes.
|
|
2208
|
-
`scan:`, ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2301
|
+
`scan:`, ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ...(sz === "i64"
|
|
2302
|
+
? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`]
|
|
2303
|
+
: [` %ne = load i32, ptr %nep`]), ` br label %scan.loop`, `scan.loop:`, ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, ` %sdone = icmp eq ${sz} %si, %ne`, ` br i1 %sdone, label %miss, label %scan.entry`, `scan.entry:`, ` %se = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %si`, ` %seh = getelementptr inbounds %ScrMapEntry, ptr %se, i32 0, i32 2`, ` %sh = load i64, ptr %seh`, ` %sk = load i64, ptr %se`, ` %shq = icmp eq i64 %sh, %h`, ` %skq = icmp eq i64 %sk, %kb`, ` %sq = and i1 %shq, %skq`, ` br i1 %sq, label %scan.found, label %scan.next`, `scan.found:`, ` ret ptr %se`, `scan.next:`, ` %si.next = add ${sz} %si, 1`, ` br label %scan.loop`,
|
|
2209
2304
|
// Compact tables tag each 24-bit entry index (+1) with the hash's
|
|
2210
2305
|
// top byte; wider tables take the runtime lookup.
|
|
2211
|
-
`buckets:`, ` %wide = icmp ugt ${sz} %nb, 16777216`, ` br i1 %wide, label %runtime, label %probe`, `probe:`, ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2306
|
+
`buckets:`, ` %wide = icmp ugt ${sz} %nb, 16777216`, ` br i1 %wide, label %runtime, label %probe`, `probe:`, ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, ` %bk = load ptr, ptr %bp`, ` %mask = sub ${sz} %nb, 1`, ` %ht = lshr i64 %h, 32`, ` %ht32 = trunc i64 %ht to i32`, ` %tag = and i32 %ht32, -16777216`, sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, ` %i0 = and ${sz} %hi, %mask`, ` br label %probe.loop`, `probe.loop:`, ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, ` %b = load i32, ptr %bep`, ` %bempty = icmp eq i32 %b, 0`, ` br i1 %bempty, label %miss, label %probe.tag`, `probe.tag:`, ` %bt = and i32 %b, -16777216`, ` %btq = icmp eq i32 %bt, %tag`, ` br i1 %btq, label %probe.entry, label %probe.next`, `probe.entry:`, ` %bix = and i32 %b, 16777215`, ` %bix1 = sub i32 %bix, 1`, sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, ` %pe = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %bi`, ` %peh = getelementptr inbounds %ScrMapEntry, ptr %pe, i32 0, i32 2`, ` %ph = load i64, ptr %peh`, ` %pk = load i64, ptr %pe`, ` %phq = icmp eq i64 %ph, %h`, ` %pkq = icmp eq i64 %pk, %kb`, ` %pq = and i1 %phq, %pkq`, ` br i1 %pq, label %probe.found, label %probe.next`, `probe.found:`, ` ret ptr %pe`, `probe.next:`, ` %pi.inc = add ${sz} %pi, 1`, ` %pi.next = and ${sz} %pi.inc, %mask`, ` br label %probe.loop`, `miss:`, ` ret ptr null`, `runtime:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k) memory(read) nounwind willreturn`, ` ret ptr %r`, `}`, ``);
|
|
2212
2307
|
}
|
|
2213
2308
|
// Map/Set iteration steps read the entries array inline: an index is
|
|
2214
2309
|
// live when it is below the entry count and its entry's cached hash is
|
|
@@ -2217,7 +2312,7 @@ export class LlEmitter {
|
|
|
2217
2312
|
// the iteration are visited.
|
|
2218
2313
|
if (this.decls.has(MAP_ITER_DECL)) {
|
|
2219
2314
|
const sz = this.sizeType;
|
|
2220
|
-
defs.push(`define internal ptr @sc_map_iter_entry(ptr %m, double %i) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2315
|
+
defs.push(`define internal ptr @sc_map_iter_entry(ptr %m, double %i) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ` %n = load i32, ptr %np`, ` %nf = uitofp i32 %n to double`, ` %lo = fcmp oge double %i, 0.0`, ` %hi = fcmp olt double %i, %nf`, ` %in = and i1 %lo, %hi`, ` br i1 %in, label %load, label %none`, `load:`, ` %ix = fptoui double %i to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, ` %entries = load ptr, ptr %ep`, ` %e = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %ix`, ` %hp = getelementptr inbounds %ScrMapEntry, ptr %e, i32 0, i32 2`, ` %h = load i64, ptr %hp`, ` %live = icmp ne i64 %h, 0`, ` br i1 %live, label %yes, label %none`, `yes:`, ` ret ptr %e`, `none:`, ` ret ptr null`, `}`, ``, `define internal double @sc_map_iter_count(ptr %m) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ` %n = load i32, ptr %np`, ` %nf = uitofp i32 %n to double`, ` ret double %nf`, `}`, ``);
|
|
2221
2316
|
}
|
|
2222
2317
|
// String-key Map/Set lookups test the map's absent-key filter inline
|
|
2223
2318
|
// (scr_map.c scr_map_filter_bits): a key whose two filter bits are not
|
|
@@ -2230,7 +2325,7 @@ export class LlEmitter {
|
|
|
2230
2325
|
if (this.decls.has(STRING_MAP_ENTRY_DECL)) {
|
|
2231
2326
|
defs.push(`define internal ptr @sc_map_entry_str(ptr %m, ptr %k) ${FN_ATTRS} {`, `entry:`, ...(this.sizeType === "i64"
|
|
2232
2327
|
? [
|
|
2233
|
-
` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2328
|
+
` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`,
|
|
2234
2329
|
` %filter = load i64, ptr %fp`,
|
|
2235
2330
|
` %capp = getelementptr inbounds %ScrStr, ptr %k, i32 0, i32 2`,
|
|
2236
2331
|
` %cap = load i64, ptr %capp`,
|
|
@@ -3036,6 +3131,18 @@ export class LlEmitter {
|
|
|
3036
3131
|
needBadTag() {
|
|
3037
3132
|
this.needsBadTag = true;
|
|
3038
3133
|
}
|
|
3134
|
+
/** The ScrMapOps descriptor holding these operands (ShapeHost.mapOps). */
|
|
3135
|
+
mapOps(operands) {
|
|
3136
|
+
if (operands.every((op) => op === "null"))
|
|
3137
|
+
return "null";
|
|
3138
|
+
const body = operands.map((op) => `ptr ${op}`).join(", ");
|
|
3139
|
+
let sym = this.mapOpsConsts.get(body);
|
|
3140
|
+
if (!sym) {
|
|
3141
|
+
sym = this.contentSymbol("sc_map_ops_", body);
|
|
3142
|
+
this.mapOpsConsts.set(body, sym);
|
|
3143
|
+
}
|
|
3144
|
+
return `@${sym}`;
|
|
3145
|
+
}
|
|
3039
3146
|
/** The interned immortal instance for a UNIT arm of a union — asserts
|
|
3040
3147
|
* the arm really is payload-less (undefined/null). Public: class
|
|
3041
3148
|
* emission initializes undefined-admitting fields through it
|
|
@@ -4172,7 +4279,11 @@ export class LlEmitter {
|
|
|
4172
4279
|
const g = this.globalTypes.get(id);
|
|
4173
4280
|
if (!g)
|
|
4174
4281
|
throw new InternalCompilerError(`llvm emitter bug: unknown binding ${id}`);
|
|
4175
|
-
return {
|
|
4282
|
+
return {
|
|
4283
|
+
kind: "global",
|
|
4284
|
+
slot: this.privateGlobalSlots.get(id) ?? `@${mangleGlobal(id)}`,
|
|
4285
|
+
type: g,
|
|
4286
|
+
};
|
|
4176
4287
|
}
|
|
4177
4288
|
emitDebugLocal(local, slot, arg = 0, captured = false) {
|
|
4178
4289
|
const debug = this.debug?.local(local, this.debugScope, arg, captured);
|
|
@@ -4435,6 +4546,55 @@ export class LlEmitter {
|
|
|
4435
4546
|
}
|
|
4436
4547
|
this.emitCoroSuspendPrepared();
|
|
4437
4548
|
}
|
|
4549
|
+
/** A native generator's OUT slot (scr_async.c's ScrGen.out, mirrored by
|
|
4550
|
+
* %ScrGenHead and %ScrGenSlot { kind, f64, b, payload, release_fn }). */
|
|
4551
|
+
genOutSlot(gen) {
|
|
4552
|
+
this.usesGenSlots = true;
|
|
4553
|
+
const slot = this.B.tmp();
|
|
4554
|
+
this.B.line(`${slot} = getelementptr inbounds %ScrGenHead, ptr ${gen}, i32 0, i32 4`);
|
|
4555
|
+
return slot;
|
|
4556
|
+
}
|
|
4557
|
+
/** A yield on a number or boolean channel: scr_gen_slot_f64/bool inline.
|
|
4558
|
+
* Such a body's OUT never holds a reference at a yield (only its final
|
|
4559
|
+
* completion can park one), so the slot needs no release first. */
|
|
4560
|
+
emitGenSlotStore(slot, kind, value) {
|
|
4561
|
+
const B = this.B;
|
|
4562
|
+
const field = B.tmp();
|
|
4563
|
+
if (kind === "f64") {
|
|
4564
|
+
B.line(`store i32 ${SCR_EXC_F64}, ptr ${slot}`);
|
|
4565
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
|
|
4566
|
+
B.line(`store double ${value}, ptr ${field}`);
|
|
4567
|
+
}
|
|
4568
|
+
else {
|
|
4569
|
+
const byte = B.tmp();
|
|
4570
|
+
B.line(`store i32 ${SCR_EXC_BOOL}, ptr ${slot}`);
|
|
4571
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
|
|
4572
|
+
B.line(`${byte} = zext i1 ${value} to i8`);
|
|
4573
|
+
B.line(`store i8 ${byte}, ptr ${field}`);
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
/** scr_gen_take_out_f64/bool inline: the slot's value when it holds that
|
|
4577
|
+
* kind (otherwise 0 or false), leaving the slot empty. */
|
|
4578
|
+
emitGenSlotTake(slot, kind) {
|
|
4579
|
+
const B = this.B;
|
|
4580
|
+
const tag = B.tmp(), matches = B.tmp(), field = B.tmp(), raw = B.tmp(), out = B.tmp();
|
|
4581
|
+
B.line(`${tag} = load i32, ptr ${slot}`);
|
|
4582
|
+
B.line(`${matches} = icmp eq i32 ${tag}, ${kind === "f64" ? SCR_EXC_F64 : SCR_EXC_BOOL}`);
|
|
4583
|
+
if (kind === "f64") {
|
|
4584
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
|
|
4585
|
+
B.line(`${raw} = load double, ptr ${field}`);
|
|
4586
|
+
B.line(`${out} = select i1 ${matches}, double ${raw}, double 0.0`);
|
|
4587
|
+
}
|
|
4588
|
+
else {
|
|
4589
|
+
const bit = B.tmp();
|
|
4590
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
|
|
4591
|
+
B.line(`${raw} = load i8, ptr ${field}`);
|
|
4592
|
+
B.line(`${bit} = trunc i8 ${raw} to i1`);
|
|
4593
|
+
B.line(`${out} = select i1 ${matches}, i1 ${bit}, i1 false`);
|
|
4594
|
+
}
|
|
4595
|
+
B.line(`store i32 0, ptr ${slot}`);
|
|
4596
|
+
return out;
|
|
4597
|
+
}
|
|
4438
4598
|
/** Suspend after a target-specific runtime helper already queued the
|
|
4439
4599
|
* continuation (used by module awaits, which skip the hop when settled). */
|
|
4440
4600
|
emitCoroSuspendPrepared() {
|
|
@@ -4509,9 +4669,25 @@ export class LlEmitter {
|
|
|
4509
4669
|
}
|
|
4510
4670
|
}
|
|
4511
4671
|
}
|
|
4672
|
+
/** Number locals declared as a copy of a capture or of a lazily boxed
|
|
4673
|
+
* parameter, at the top of the body. */
|
|
4674
|
+
doubleCopies(fn) {
|
|
4675
|
+
const copies = [];
|
|
4676
|
+
for (const s of fn.body) {
|
|
4677
|
+
if (s.kind !== "varDecl")
|
|
4678
|
+
break;
|
|
4679
|
+
if (s.init?.kind !== "varRef" || s.init.type.kind !== "f64")
|
|
4680
|
+
continue;
|
|
4681
|
+
const source = s.init.localId;
|
|
4682
|
+
if (this.captureIds.has(source) || this.lazyCaptureBoxes.has(source))
|
|
4683
|
+
copies.push(s.localId);
|
|
4684
|
+
}
|
|
4685
|
+
return copies;
|
|
4686
|
+
}
|
|
4512
4687
|
emitFunction(fn) {
|
|
4513
4688
|
const B = new BlockBuilder();
|
|
4514
4689
|
this.B = B;
|
|
4690
|
+
this.currentFunctionName = fn.name;
|
|
4515
4691
|
// Fiber switches restore the active exception cell before control
|
|
4516
4692
|
// returns to a synchronous frame (emitPendingCheck).
|
|
4517
4693
|
B.excCellInvariant = fn.async !== true && fn.generator === undefined;
|
|
@@ -4564,6 +4740,11 @@ export class LlEmitter {
|
|
|
4564
4740
|
!this.reboundByFunction.get(fn.name)?.has(fn.params[index].localId))
|
|
4565
4741
|
this.borrowedParameters.add(fn.params[index].localId);
|
|
4566
4742
|
}
|
|
4743
|
+
// A generator frame owns its unchanged parameters across suspensions.
|
|
4744
|
+
const generatorParameters = this.callLifetimes.generatorParameters.get(fn.name);
|
|
4745
|
+
if (generatorParameters !== undefined)
|
|
4746
|
+
for (const id of generatorParameters)
|
|
4747
|
+
this.borrowedParameters.add(id);
|
|
4567
4748
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
4568
4749
|
this.stableCaptureIds =
|
|
4569
4750
|
fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set();
|
|
@@ -4589,6 +4770,7 @@ export class LlEmitter {
|
|
|
4589
4770
|
this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
|
|
4590
4771
|
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
4591
4772
|
this.readNumberMapWrites = findReadNumberMapWrites(fn);
|
|
4773
|
+
this.mapEntryFusion = this.debug === null ? findMapEntryFusions(fn) : null;
|
|
4592
4774
|
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
|
|
4593
4775
|
this.localStackObjects = this.stackObjects
|
|
4594
4776
|
? findLocalStackObjects(this, fn, this.callLifetimes)
|
|
@@ -4604,12 +4786,30 @@ export class LlEmitter {
|
|
|
4604
4786
|
this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
|
|
4605
4787
|
// Debug builds keep every number local a double for the debugger's
|
|
4606
4788
|
// variable view. Lazily boxed captured parameters keep their double,
|
|
4607
|
-
// which the box is created from
|
|
4789
|
+
// which the box is created from, and so do plain copies of a capture or
|
|
4790
|
+
// such a parameter (copyStableCaptures): the source already holds the
|
|
4791
|
+
// double, which an i32 copy would convert and widen again on every call.
|
|
4792
|
+
// Initializer bindings proven int32 move into an i32 local of their
|
|
4793
|
+
// initializer: no other code observes the global, so the loop-carried
|
|
4794
|
+
// value never round-trips through the double slot.
|
|
4795
|
+
const initializerLocals = initializerLocalCandidates(initializerBindings);
|
|
4608
4796
|
const int32Locals = this.debug === null
|
|
4609
|
-
? findInt32Locals(fn, this.integerRanges, this.int32Slots.facts(fn.name), new Set(this.lazyCaptureBoxes.keys()))
|
|
4797
|
+
? findInt32Locals(withInitializerLocals(fn, initializerLocals), this.integerRanges, this.int32Slots.facts(fn.name), new Set([...this.lazyCaptureBoxes.keys(), ...this.doubleCopies(fn)]))
|
|
4610
4798
|
: null;
|
|
4611
4799
|
this.int32Locals = int32Locals?.locals ?? new Map();
|
|
4612
4800
|
this.int32LocalWrites = int32Locals?.values ?? new Set();
|
|
4801
|
+
const localizedGlobals = new Set();
|
|
4802
|
+
for (const global of initializerLocals) {
|
|
4803
|
+
if (!this.int32Locals.has(global.id))
|
|
4804
|
+
continue;
|
|
4805
|
+
localizedGlobals.add(global.id);
|
|
4806
|
+
this.currentLocals.set(global.id, {
|
|
4807
|
+
id: global.id,
|
|
4808
|
+
name: global.name,
|
|
4809
|
+
type: global.type,
|
|
4810
|
+
mutable: global.mutable,
|
|
4811
|
+
});
|
|
4812
|
+
}
|
|
4613
4813
|
this.chainSlots.clear();
|
|
4614
4814
|
this.finallyStack = [];
|
|
4615
4815
|
this.tryStack = [];
|
|
@@ -4651,6 +4851,18 @@ export class LlEmitter {
|
|
|
4651
4851
|
B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`);
|
|
4652
4852
|
B.line(`call void @${started}(ptr ${handle})`);
|
|
4653
4853
|
B.line(`${self} = call ptr @scr_fiber_self()`);
|
|
4854
|
+
// A native synchronous body owns its generator for the whole run (the
|
|
4855
|
+
// ScrGen owns the frame), so its OUT slot address is fixed.
|
|
4856
|
+
let out = null;
|
|
4857
|
+
const yieldKind = fn.generator?.yieldT.kind;
|
|
4858
|
+
if (!this.wasi &&
|
|
4859
|
+
fn.async !== true &&
|
|
4860
|
+
(yieldKind === "f64" || yieldKind === "date" || yieldKind === "bool")) {
|
|
4861
|
+
this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
|
|
4862
|
+
const gen = B.tmp();
|
|
4863
|
+
B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${self})`);
|
|
4864
|
+
out = this.genOutSlot(gen);
|
|
4865
|
+
}
|
|
4654
4866
|
this.currentCoro = {
|
|
4655
4867
|
kind: fn.generator !== undefined ? "generator" : "async",
|
|
4656
4868
|
id,
|
|
@@ -4659,6 +4871,7 @@ export class LlEmitter {
|
|
|
4659
4871
|
finalLabel: B.newLabel("coro.final"),
|
|
4660
4872
|
cleanupLabel: B.newLabel("coro.cleanup"),
|
|
4661
4873
|
suspendLabel: B.newLabel("coro.suspend"),
|
|
4874
|
+
out,
|
|
4662
4875
|
};
|
|
4663
4876
|
}
|
|
4664
4877
|
const paramIds = new Set(fn.params.map((p) => p.localId));
|
|
@@ -4679,6 +4892,32 @@ export class LlEmitter {
|
|
|
4679
4892
|
B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
|
|
4680
4893
|
this.storeIntegerView(global.id, { name: value, type: global.type });
|
|
4681
4894
|
}
|
|
4895
|
+
// A number global only its guarded, synchronous initializer reads or
|
|
4896
|
+
// writes (findInitializerBindings) is invisible to every other function,
|
|
4897
|
+
// and reentry returns before reaching it, so a local slot can stand in
|
|
4898
|
+
// for it: LLVM keeps it in a register across calls and loops that would
|
|
4899
|
+
// otherwise force a store and reload per iteration. The global's
|
|
4900
|
+
// storage is never read again, so nothing writes the slot back. Bindings
|
|
4901
|
+
// proven int32 already live in an i32 local (localizedGlobals above);
|
|
4902
|
+
// this covers the remaining doubles.
|
|
4903
|
+
this.privateGlobalSlots.clear();
|
|
4904
|
+
if (this.debug === null && this.currentCoro === null && !fn.async && !fn.generator) {
|
|
4905
|
+
const conditional = initializerLocals.length ? conditionalLoopWrites(fn.body) : null;
|
|
4906
|
+
for (const global of initializerLocals) {
|
|
4907
|
+
if (localizedGlobals.has(global.id))
|
|
4908
|
+
continue;
|
|
4909
|
+
// A conditional update in a loop stays a memory update (see
|
|
4910
|
+
// conditionalLoopWrites).
|
|
4911
|
+
if (conditional?.has(global.id))
|
|
4912
|
+
continue;
|
|
4913
|
+
const slot = B.slot();
|
|
4914
|
+
const value = B.tmp();
|
|
4915
|
+
B.entryAllocas.push(`${slot} = alloca double ; module binding ${global.name}`);
|
|
4916
|
+
B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
|
|
4917
|
+
B.line(`store double ${value}, ptr ${slot}`);
|
|
4918
|
+
this.privateGlobalSlots.set(global.id, slot);
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4682
4921
|
for (const local of this.currentLocals.values()) {
|
|
4683
4922
|
// Boxed locals' slots hold their capture BOX (a ptr); captured
|
|
4684
4923
|
// (env-borrowed) locals bind the incoming box below. A caught-typed
|
|
@@ -4691,6 +4930,9 @@ export class LlEmitter {
|
|
|
4691
4930
|
: this.llType(local.type);
|
|
4692
4931
|
B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`);
|
|
4693
4932
|
this.emitDebugLocal(local, `%${mangleLocal(local.id)}`, fn.params.findIndex((p) => p.localId === local.id) + 1, this.captureIds.has(local.id));
|
|
4933
|
+
// A localized initializer binding starts at its global's zero value.
|
|
4934
|
+
if (localizedGlobals.has(local.id))
|
|
4935
|
+
B.line(`store i32 0, ptr %${mangleLocal(local.id)}`);
|
|
4694
4936
|
// Refcounted/boxed locals start NULL (the C prologue's `= NULL`):
|
|
4695
4937
|
// scope-exit releases run whether or not an assign ever did.
|
|
4696
4938
|
if (paramIds.has(local.id) || this.captureIds.has(local.id))
|
|
@@ -4910,9 +5152,17 @@ export class LlEmitter {
|
|
|
4910
5152
|
attrs = `inlinehint ${attrs}`;
|
|
4911
5153
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
4912
5154
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
5155
|
+
this.privateGlobalSlots.clear();
|
|
4913
5156
|
let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
4914
5157
|
if (int32Abi)
|
|
4915
5158
|
body += "\n" + this.emitInt32AbiWrapper(fn, symbol, int32Abi);
|
|
5159
|
+
if (this.integerClones.members.has(fn.name)) {
|
|
5160
|
+
body += "\n" + emitIntegerClone(fn, this, this.stackChecks?.has(fn.name) === true);
|
|
5161
|
+
if (this.integerClones.dispatched.has(fn.name))
|
|
5162
|
+
body +=
|
|
5163
|
+
"\n" +
|
|
5164
|
+
emitIntegerDispatcher(fn, this, `${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}`, int32Abi?.params ?? [], int32Abi?.result === true);
|
|
5165
|
+
}
|
|
4916
5166
|
if (!borrowed)
|
|
4917
5167
|
return body;
|
|
4918
5168
|
const slot = this.implSlotBorrowed(fn.name);
|
|
@@ -5779,6 +6029,8 @@ export class LlEmitter {
|
|
|
5779
6029
|
// the replaced element itself). A local receiver that the index and
|
|
5780
6030
|
// value cannot rebind is read in place, as for field stores; the
|
|
5781
6031
|
// released element cannot reach that binding.
|
|
6032
|
+
if (this.emitArraySelfConcat(s))
|
|
6033
|
+
break;
|
|
5782
6034
|
const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
|
|
5783
6035
|
const idx = this.emitExpr(s.index);
|
|
5784
6036
|
const v = this.emitExpr(s.value);
|
|
@@ -5813,7 +6065,7 @@ export class LlEmitter {
|
|
|
5813
6065
|
const resize = B.newLabel("arr.len.set"), done = B.newLabel("arr.len.done");
|
|
5814
6066
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
5815
6067
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
5816
|
-
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
6068
|
+
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
|
|
5817
6069
|
B.line(`${lenNumber} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${len} to double`);
|
|
5818
6070
|
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
|
5819
6071
|
B.condBr(same, done, resize);
|
|
@@ -5877,6 +6129,8 @@ export class LlEmitter {
|
|
|
5877
6129
|
this.storeInt32Field(ptr, v, s.value);
|
|
5878
6130
|
break;
|
|
5879
6131
|
}
|
|
6132
|
+
if (this.emitFieldSelfConcat(s))
|
|
6133
|
+
break;
|
|
5880
6134
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
5881
6135
|
const v = this.emitFieldValue(s.value, s.kind === "recordSet"
|
|
5882
6136
|
? this.recordFieldInline(s.shapeId, s.field)
|
|
@@ -6439,7 +6693,7 @@ export class LlEmitter {
|
|
|
6439
6693
|
// The inline form of scr_arr_len, like the `length` intrinsic.
|
|
6440
6694
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
6441
6695
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
6442
|
-
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
6696
|
+
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
|
|
6443
6697
|
B.line(`${len} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${rawLen} to double`);
|
|
6444
6698
|
B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
|
|
6445
6699
|
cur = i;
|
|
@@ -7146,6 +7400,118 @@ export class LlEmitter {
|
|
|
7146
7400
|
return null;
|
|
7147
7401
|
return this.emitExpr(packed.elems[0]);
|
|
7148
7402
|
}
|
|
7403
|
+
/**
|
|
7404
|
+
* Lower `recv.f = recv.f + suffix` on a plain string field like
|
|
7405
|
+
* emitStringSelfConcatAssign does for locals. The field's old value is
|
|
7406
|
+
* snapshotted (+1) and the suffix evaluated in JavaScript order; then the
|
|
7407
|
+
* field unlinks and releases its CURRENT value, so the snapshot is unique
|
|
7408
|
+
* unless a real alias exists and the concat appends in place instead of
|
|
7409
|
+
* copying the whole string on every append. Returns false (emitting
|
|
7410
|
+
* nothing) for every other store.
|
|
7411
|
+
*/
|
|
7412
|
+
emitFieldSelfConcat(s) {
|
|
7413
|
+
const suffix = matchFieldSelfConcat(s);
|
|
7414
|
+
if (!suffix || s.value.kind !== "strConcat")
|
|
7415
|
+
return false;
|
|
7416
|
+
let fieldType;
|
|
7417
|
+
if (s.kind === "fieldSet") {
|
|
7418
|
+
if (this.inlineUnions.classField(s.className, s.field) ||
|
|
7419
|
+
this.classMeta.get(s.className)?.root.def.name === "%Error")
|
|
7420
|
+
return false;
|
|
7421
|
+
fieldType = classFieldIndex(this.classMetaOf(s.className), s.field).type;
|
|
7422
|
+
}
|
|
7423
|
+
else {
|
|
7424
|
+
fieldType = this.recordShape(s.shapeId).fields.find((f) => f.name === s.field)?.type;
|
|
7425
|
+
}
|
|
7426
|
+
if (fieldType?.kind !== "string")
|
|
7427
|
+
return false;
|
|
7428
|
+
const B = this.B;
|
|
7429
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
7430
|
+
const snapshot = this.emitExpr(s.value.left);
|
|
7431
|
+
const parts = stringParts(suffix);
|
|
7432
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
7433
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
7434
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
7435
|
+
if (s.kind === "fieldSet")
|
|
7436
|
+
this.emitStoreGuard(obj.name, s);
|
|
7437
|
+
else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
7438
|
+
this.emitPublishedFieldGuard(obj.name, null, s.field);
|
|
7439
|
+
const { ptr } = s.kind === "fieldSet"
|
|
7440
|
+
? this.classFieldPtr(obj.name, s.className, s.field)
|
|
7441
|
+
: this.recordFieldPtr(obj.name, s.shapeId, s.field);
|
|
7442
|
+
// Unlink, then release: nothing between here and the store below can
|
|
7443
|
+
// run user code or observe the field (the concat only traps on OOM).
|
|
7444
|
+
// Only an owned snapshot may survive the field's release.
|
|
7445
|
+
const owned = this.ownsSnapshot(snapshot.name);
|
|
7446
|
+
if (owned) {
|
|
7447
|
+
const old = this.loadField(ptr, fieldType, false);
|
|
7448
|
+
this.storeField(ptr, fieldType, "null", false);
|
|
7449
|
+
this.releaseValue(old, fieldType);
|
|
7450
|
+
}
|
|
7451
|
+
let raw;
|
|
7452
|
+
if (right === null) {
|
|
7453
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
7454
|
+
}
|
|
7455
|
+
else {
|
|
7456
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
7457
|
+
raw = B.tmp();
|
|
7458
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
7459
|
+
}
|
|
7460
|
+
// The +1 result moves straight into the (now empty) field.
|
|
7461
|
+
if (owned)
|
|
7462
|
+
this.storeField(ptr, fieldType, raw, false);
|
|
7463
|
+
else
|
|
7464
|
+
this.replaceField(ptr, fieldType, false, this.own({ name: raw, type: fieldType }));
|
|
7465
|
+
return true;
|
|
7466
|
+
}
|
|
7467
|
+
/** A snapshot the current statement frames own (+1), or an immortal
|
|
7468
|
+
* value: it stays valid after its container releases the same string. */
|
|
7469
|
+
ownsSnapshot(name) {
|
|
7470
|
+
return (this.isImmortal(name) || this.frames.some((f) => f.some((v) => v.name === name && !v.slot)));
|
|
7471
|
+
}
|
|
7472
|
+
/**
|
|
7473
|
+
* `a[i] = a[i] + suffix` on a string array (the canonical statement form
|
|
7474
|
+
* of `a[i] += suffix`): the old element is snapshotted (+1) and the
|
|
7475
|
+
* suffix evaluated in JavaScript order; then, when the slot still holds
|
|
7476
|
+
* the snapshot, the array lets go of it (emitDenseSlotUnlink) so the
|
|
7477
|
+
* concat can append in place, and the result is stored as usual.
|
|
7478
|
+
*/
|
|
7479
|
+
emitArraySelfConcat(s) {
|
|
7480
|
+
const value = s.value;
|
|
7481
|
+
if (value.kind !== "strConcat" ||
|
|
7482
|
+
value.type.kind !== "string" ||
|
|
7483
|
+
value.left.type.kind !== "string" ||
|
|
7484
|
+
value.right.type.kind !== "string" ||
|
|
7485
|
+
s.arr.type.kind !== "array" ||
|
|
7486
|
+
s.arr.type.elem.kind !== "string" ||
|
|
7487
|
+
s.arr.kind !== "varRef" ||
|
|
7488
|
+
s.index.kind !== "varRef" ||
|
|
7489
|
+
!readsArraySlot(value.left, s.arr.localId, s.index.localId))
|
|
7490
|
+
return false;
|
|
7491
|
+
const elem = s.arr.type.elem;
|
|
7492
|
+
const B = this.B;
|
|
7493
|
+
const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
|
|
7494
|
+
const idx = this.emitExpr(s.index);
|
|
7495
|
+
const snapshot = this.emitExpr(value.left);
|
|
7496
|
+
const parts = stringParts(value.right);
|
|
7497
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
7498
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
7499
|
+
const right = mixed ? null : this.emitExpr(value.right);
|
|
7500
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
|
|
7501
|
+
if (this.ownsSnapshot(snapshot.name))
|
|
7502
|
+
emitDenseSlotUnlink(this, arr.name, idx, s.index, snapshot.name, this.internLiteral(""), elem);
|
|
7503
|
+
let raw;
|
|
7504
|
+
if (right === null) {
|
|
7505
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
7506
|
+
}
|
|
7507
|
+
else {
|
|
7508
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
7509
|
+
raw = B.tmp();
|
|
7510
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
7511
|
+
}
|
|
7512
|
+
emitDenseArraySet(this, arr.name, idx, s.index, "ref", elem, raw);
|
|
7513
|
+
return true;
|
|
7514
|
+
}
|
|
7149
7515
|
/** `local = local + suffix` when the suffix cannot read or write the
|
|
7150
7516
|
* local (an unboxed binding is only reachable through this function's own
|
|
7151
7517
|
* expressions): reading the binding after the suffix gives the value the
|
|
@@ -7373,6 +7739,7 @@ export class LlEmitter {
|
|
|
7373
7739
|
declare: (decl) => this.declare(decl),
|
|
7374
7740
|
cstr: (text) => this.cstr(text),
|
|
7375
7741
|
sizeType: this.sizeType,
|
|
7742
|
+
rcAudit: this.shapeHost.objectAudit !== false,
|
|
7376
7743
|
tracedShapes: this.tracedShapes,
|
|
7377
7744
|
unionsById: this.unionsById,
|
|
7378
7745
|
recordsById: this.recordsById,
|
|
@@ -8087,6 +8454,9 @@ export class LlEmitter {
|
|
|
8087
8454
|
emitterFixedAdapter(cbT) {
|
|
8088
8455
|
return emitterFixedAdapter(this, cbT);
|
|
8089
8456
|
}
|
|
8457
|
+
emitterOnceAdapter(boxerT) {
|
|
8458
|
+
return emitterOnceAdapter(this, boxerT);
|
|
8459
|
+
}
|
|
8090
8460
|
wrapEmitterListener(target, adapterFn) {
|
|
8091
8461
|
return wrapEmitterListener(this, target, adapterFn);
|
|
8092
8462
|
}
|