@scriptc/compiler 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +409 -42
- 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 +71 -7
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +120 -2
- package/dist/backend/llvm/emitter.js +820 -142
- 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-dispatch.js +1 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +25 -1
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +20 -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 +32 -1
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +61 -1
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-network.js +21 -3
- package/dist/backend/llvm/lib-network.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +34 -0
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +77 -2
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-test.js +16 -3
- package/dist/backend/llvm/lib-test.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/nullable-unions.d.ts +6 -3
- package/dist/backend/llvm/nullable-unions.js +2 -0
- package/dist/backend/llvm/nullable-unions.js.map +1 -1
- 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.d.ts +1 -1
- package/dist/backend/llvm/record-results.js +78 -4
- 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/reference-writes.js +1 -0
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +12 -3
- package/dist/backend/llvm/shapes.js +23 -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/speculative-integers.d.ts +74 -0
- package/dist/backend/llvm/speculative-integers.js +571 -0
- package/dist/backend/llvm/speculative-integers.js.map +1 -0
- package/dist/backend/llvm/speculative-integers.test.d.ts +1 -0
- package/dist/backend/llvm/speculative-integers.test.js +148 -0
- package/dist/backend/llvm/speculative-integers.test.js.map +1 -0
- 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/may-throw.js +2 -0
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +2 -1
- package/dist/backend/native/executable.js +6 -1
- package/dist/backend/native/executable.js.map +1 -1
- package/dist/backend/native/runtime-inputs.d.ts +1 -1
- package/dist/backend/native/runtime-inputs.js +1 -0
- package/dist/backend/native/runtime-inputs.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.d.ts +7 -3
- package/dist/backend/runtime-pack.js +12 -6
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +16 -7
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/contracts/check.js +1 -0
- package/dist/contracts/check.js.map +1 -1
- package/dist/coverage/surface-manifest.js +3 -3
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +1 -1
- package/dist/diagnostics/diagnostic.js +6 -2
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/builtin-calls.js +13 -0
- package/dist/frontend/lowering/builtin-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/callable-aliases.js +82 -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/compression.d.ts +9 -0
- package/dist/frontend/lowering/builtins/compression.js +145 -30
- package/dist/frontend/lowering/builtins/compression.js.map +1 -1
- package/dist/frontend/lowering/builtins/crypto.js +12 -1
- package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
- package/dist/frontend/lowering/builtins/errors.js +5 -3
- package/dist/frontend/lowering/builtins/errors.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +6 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js +16 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem.d.ts +8 -0
- package/dist/frontend/lowering/builtins/filesystem.js +199 -2
- 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/hash-stream.d.ts +12 -0
- package/dist/frontend/lowering/builtins/hash-stream.js +80 -0
- package/dist/frontend/lowering/builtins/hash-stream.js.map +1 -0
- package/dist/frontend/lowering/builtins/module-calls.js +17 -3
- package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/process.js +9 -2
- package/dist/frontend/lowering/builtins/process.js.map +1 -1
- package/dist/frontend/lowering/builtins/readline.d.ts +45 -13
- package/dist/frontend/lowering/builtins/readline.js +812 -47
- package/dist/frontend/lowering/builtins/readline.js.map +1 -1
- package/dist/frontend/lowering/builtins/workers.d.ts +4 -0
- package/dist/frontend/lowering/builtins/workers.js +58 -0
- package/dist/frontend/lowering/builtins/workers.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/class-protocols.d.ts +55 -0
- package/dist/frontend/lowering/class-protocols.js +735 -0
- package/dist/frontend/lowering/class-protocols.js.map +1 -1
- package/dist/frontend/lowering/containers/array-from-async.d.ts +18 -0
- package/dist/frontend/lowering/containers/array-from-async.js +282 -0
- package/dist/frontend/lowering/containers/array-from-async.js.map +1 -0
- package/dist/frontend/lowering/containers/bytes.js +33 -12
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-grouping.js +2 -1
- package/dist/frontend/lowering/containers/collection-grouping.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-methods.js +27 -23
- package/dist/frontend/lowering/containers/collection-methods.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/divergences.js +188 -15
- package/dist/frontend/lowering/divergences.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +129 -25
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +46 -1
- 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 +88 -1
- 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 +239 -71
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +49 -3
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-mock.d.ts +11 -1
- package/dist/frontend/lowering/lower-mock.js +377 -61
- package/dist/frontend/lowering/lower-mock.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +1 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +2 -1
- package/dist/frontend/lowering/lower-server.js +234 -21
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +60 -13
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.d.ts +1 -3
- package/dist/frontend/lowering/lower-stream.js +325 -20
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-test.js +147 -48
- package/dist/frontend/lowering/lower-test.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +13 -1
- package/dist/frontend/lowering/lowerer.js +10 -2
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/null-prototype.d.ts +13 -0
- package/dist/frontend/lowering/null-prototype.js +33 -0
- package/dist/frontend/lowering/null-prototype.js.map +1 -0
- 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.d.ts +3 -3
- package/dist/frontend/lowering/surfaces.js +59 -5
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/mock-shapes.d.ts +5 -0
- package/dist/frontend/mock-shapes.js +27 -2
- package/dist/frontend/mock-shapes.js.map +1 -1
- package/dist/frontend/program.js +5 -4
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +3 -0
- package/dist/frontend/type-mapper.js +148 -4
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +18 -8
- package/dist/index.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 +72 -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 +258 -13
- 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 +2 -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 +398 -13
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +119 -8
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-loop-facts.d.ts +27 -1
- package/dist/ir/integer-loop-facts.js +239 -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 -59
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +285 -7
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +22 -1
- package/dist/ir/ir.js +4 -0
- 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.d.ts +3 -1
- package/dist/ir/runtime-features.js +102 -7
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/runtime-features.test.js +62 -0
- package/dist/ir/runtime-features.test.js.map +1 -1
- package/dist/ir/traverse.js +4 -0
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +71 -3
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +59 -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/dist/library/ir-policy.js +5 -0
- package/dist/library/ir-policy.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +57 -19
package/dist/ir/int32-slots.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { analyzeIntegerRanges, INT32_RANGE, withinInt32, } from "./integer-ranges.js";
|
|
1
|
+
import { analyzeIntegerRanges, INT32_RANGE, NON_NEGATIVE_INT32_RANGE, withinInt32, } from "./integer-ranges.js";
|
|
2
2
|
import { exprFunctionReference, moduleFunctionReferences } from "./function-references.js";
|
|
3
3
|
import { findInitializerBindings, withInitializerBindings } from "./initializer-bindings.js";
|
|
4
4
|
import { everyExprChild, everyStmtChild, everyStmtList, everyTypeChild } from "./traverse.js";
|
|
@@ -40,6 +40,8 @@ export class Int32Slots {
|
|
|
40
40
|
familyOf = new Map();
|
|
41
41
|
paramFacts = new Map();
|
|
42
42
|
returns = new Set();
|
|
43
|
+
/** Results narrowed below their level (narrowSlotRanges). */
|
|
44
|
+
returnRanges = new Map();
|
|
43
45
|
/** Captured-binding groups (one shared box) that hold an int32, and the
|
|
44
46
|
* group of each `function\0local` member. */
|
|
45
47
|
boxes = new Set();
|
|
@@ -58,6 +60,12 @@ export class Int32Slots {
|
|
|
58
60
|
/** Drops the analysis attempted on pinned slots. The contract check and
|
|
59
61
|
* this analysis use the same range proofs, so this stays empty. */
|
|
60
62
|
pinViolations = [];
|
|
63
|
+
/** Parameter, return and box candidates (`param:`, `ret:`, `box:` keys)
|
|
64
|
+
* still assumed to hold only non-negative int32s. The optimistic fixpoint
|
|
65
|
+
* starts every unpinned candidate here and demotes it to int32 when a
|
|
66
|
+
* write is an int32 that may be negative: `(x * 31 + n) % m` only proves
|
|
67
|
+
* a result when x and n are known not to be negative. */
|
|
68
|
+
nonNegative = new Set();
|
|
61
69
|
constructor(classes = new Map()) {
|
|
62
70
|
this.classes = classes;
|
|
63
71
|
}
|
|
@@ -96,10 +104,14 @@ export class Int32Slots {
|
|
|
96
104
|
this.boxes.size === 0 &&
|
|
97
105
|
this.int32Locals.size === 0)
|
|
98
106
|
return {};
|
|
107
|
+
const level = (key) => this.nonNegative.has(key) ? NON_NEGATIVE_INT32_RANGE : INT32_RANGE;
|
|
99
108
|
const result = {
|
|
100
109
|
field: (className, field) => (this.isField(className, field) ? INT32_RANGE : null),
|
|
101
|
-
call: (callee) =>
|
|
102
|
-
boxed: (localId) =>
|
|
110
|
+
call: (callee) => this.returns.has(callee) ? (this.returnRanges.get(callee) ?? level(`ret:${callee}`)) : null,
|
|
111
|
+
boxed: (localId) => {
|
|
112
|
+
const group = this.boxGroup(fnName, localId);
|
|
113
|
+
return group !== null && this.boxes.has(group) ? level(`box:${group}`) : null;
|
|
114
|
+
},
|
|
103
115
|
};
|
|
104
116
|
if (this.int32Locals.size > 0)
|
|
105
117
|
result.locals = (localId) => this.int32Locals.has(`${fnName}\0${localId}`) || this.int32Locals.has(`\0${localId}`)
|
|
@@ -107,9 +119,47 @@ export class Int32Slots {
|
|
|
107
119
|
: null;
|
|
108
120
|
const params = this.paramFacts.get(fnName);
|
|
109
121
|
if (params)
|
|
110
|
-
result.params =
|
|
122
|
+
result.params =
|
|
123
|
+
this.nonNegative.size === 0
|
|
124
|
+
? params
|
|
125
|
+
: new Map([...params].map(([id, range]) => {
|
|
126
|
+
const key = `param:${fnName}\0${id}`;
|
|
127
|
+
return [id, this.nonNegative.has(key) ? NON_NEGATIVE_INT32_RANGE : range];
|
|
128
|
+
}));
|
|
111
129
|
return result;
|
|
112
130
|
}
|
|
131
|
+
/** Assume an unpinned candidate holds only non-negative int32s. */
|
|
132
|
+
assumeNonNegative(key) {
|
|
133
|
+
if (!this.pinned.has(key))
|
|
134
|
+
this.nonNegative.add(key);
|
|
135
|
+
}
|
|
136
|
+
isNonNegative(key) {
|
|
137
|
+
return this.nonNegative.has(key);
|
|
138
|
+
}
|
|
139
|
+
/** Widen a non-negative candidate to int32; whether it was one. */
|
|
140
|
+
demote(key) {
|
|
141
|
+
return this.nonNegative.delete(key);
|
|
142
|
+
}
|
|
143
|
+
isPinned(key) {
|
|
144
|
+
return this.pinned.has(key);
|
|
145
|
+
}
|
|
146
|
+
/** The range a parameter fact currently asserts, if it has one. */
|
|
147
|
+
paramRange(fnName, localId) {
|
|
148
|
+
return this.facts(fnName).params?.get(localId) ?? null;
|
|
149
|
+
}
|
|
150
|
+
/** The range a result fact currently asserts, if it has one. */
|
|
151
|
+
returnRange(fnName) {
|
|
152
|
+
return this.facts(fnName).call?.(fnName) ?? null;
|
|
153
|
+
}
|
|
154
|
+
/** Replace a parameter fact with a narrower proven range. */
|
|
155
|
+
narrowParam(fnName, localId, range) {
|
|
156
|
+
this.nonNegative.delete(`param:${fnName}\0${localId}`);
|
|
157
|
+
this.addParam(fnName, localId, range);
|
|
158
|
+
}
|
|
159
|
+
/** Replace a result fact with a narrower proven range. */
|
|
160
|
+
narrowReturn(fnName, range) {
|
|
161
|
+
this.returnRanges.set(fnName, range);
|
|
162
|
+
}
|
|
113
163
|
boxGroup(fnName, localId) {
|
|
114
164
|
return this.boxGroupOf.get(`${fnName}\0${localId}`) ?? null;
|
|
115
165
|
}
|
|
@@ -137,6 +187,7 @@ export class Int32Slots {
|
|
|
137
187
|
dropBox(group) {
|
|
138
188
|
if (this.refusePinned(`box:${group}`))
|
|
139
189
|
return false;
|
|
190
|
+
this.nonNegative.delete(`box:${group}`);
|
|
140
191
|
return this.boxes.delete(group);
|
|
141
192
|
}
|
|
142
193
|
hasBox(group) {
|
|
@@ -162,6 +213,7 @@ export class Int32Slots {
|
|
|
162
213
|
dropParam(fnName, localId) {
|
|
163
214
|
if (this.refusePinned(`param:${fnName}\0${localId}`))
|
|
164
215
|
return false;
|
|
216
|
+
this.nonNegative.delete(`param:${fnName}\0${localId}`);
|
|
165
217
|
const params = this.paramFacts.get(fnName);
|
|
166
218
|
if (!params?.delete(localId))
|
|
167
219
|
return false;
|
|
@@ -178,6 +230,8 @@ export class Int32Slots {
|
|
|
178
230
|
dropReturn(fnName) {
|
|
179
231
|
if (this.refusePinned(`ret:${fnName}`))
|
|
180
232
|
return false;
|
|
233
|
+
this.nonNegative.delete(`ret:${fnName}`);
|
|
234
|
+
this.returnRanges.delete(fnName);
|
|
181
235
|
return this.returns.delete(fnName);
|
|
182
236
|
}
|
|
183
237
|
hasReturn(fnName) {
|
|
@@ -231,12 +285,25 @@ const DYN_READ_ONLY = new Set([
|
|
|
231
285
|
"arrayBuffer.viewU8",
|
|
232
286
|
"arrayBuffer.viewU8C",
|
|
233
287
|
"date.nativeNew",
|
|
288
|
+
// The dynamic operators run the ToPrimitive and coercion helpers of
|
|
289
|
+
// dyn.compare, dyn.numberConstructor and dyn.toStringCoerce, then build a
|
|
290
|
+
// fresh number, string or bigint. dyn.increment steps dyn.toNumeric's result.
|
|
291
|
+
"dyn.add",
|
|
292
|
+
"dyn.arithmetic",
|
|
234
293
|
"dyn.arrAt",
|
|
235
294
|
"dyn.arrLen",
|
|
236
295
|
"dyn.bagGet",
|
|
296
|
+
"dyn.bitwise",
|
|
237
297
|
"dyn.compare",
|
|
298
|
+
"dyn.forInKeyStrings",
|
|
299
|
+
"dyn.forInKeys",
|
|
300
|
+
"dyn.forInStable",
|
|
301
|
+
"dyn.getOwnPropertyNames",
|
|
302
|
+
"dyn.getPrototype",
|
|
238
303
|
"dyn.hasKey",
|
|
304
|
+
"dyn.hasOwn",
|
|
239
305
|
"dyn.in",
|
|
306
|
+
"dyn.increment",
|
|
240
307
|
"dyn.iterator",
|
|
241
308
|
"dyn.iteratorCanStep",
|
|
242
309
|
"dyn.iteratorResult",
|
|
@@ -245,13 +312,25 @@ const DYN_READ_ONLY = new Set([
|
|
|
245
312
|
"dyn.mapSeedEntries",
|
|
246
313
|
"dyn.mapSeedEntry",
|
|
247
314
|
"dyn.numberConstructor",
|
|
315
|
+
"dyn.objCreate",
|
|
316
|
+
"dyn.objEntries",
|
|
317
|
+
"dyn.objEntryPairs",
|
|
318
|
+
"dyn.objKeyStrings",
|
|
319
|
+
"dyn.objKeys",
|
|
320
|
+
"dyn.objValues",
|
|
321
|
+
"dyn.objectTag",
|
|
322
|
+
"dyn.ownKeys",
|
|
248
323
|
"dyn.packPush",
|
|
249
324
|
"dyn.packPushSpread",
|
|
250
325
|
"dyn.stringConstructor",
|
|
251
326
|
"dyn.this",
|
|
327
|
+
"dyn.toNumberCoerce",
|
|
328
|
+
"dyn.toNumeric",
|
|
329
|
+
"dyn.toString",
|
|
252
330
|
"dyn.toStringCoerce",
|
|
253
331
|
"dyn.typedRefIs",
|
|
254
332
|
"dyn.typedRefKeyIs",
|
|
333
|
+
"dyn.typeof",
|
|
255
334
|
// error.ctorOptions runs scr_error_init_options, the initializer
|
|
256
335
|
// error.newOptions also runs: it stamps the receiver's runtime-owned
|
|
257
336
|
// error prefix and reads `options.cause`.
|
|
@@ -259,9 +338,24 @@ const DYN_READ_ONLY = new Set([
|
|
|
259
338
|
"error.new",
|
|
260
339
|
"error.newOptions",
|
|
261
340
|
"fs.utimesSync",
|
|
341
|
+
"insp.dyn",
|
|
342
|
+
"insp.dynS",
|
|
262
343
|
"json.stringifyReplacer",
|
|
263
344
|
"json.stringifyValue",
|
|
264
345
|
"num.toStringRadix",
|
|
346
|
+
// Process event listeners (signals, uncaughtException, unhandledRejection,
|
|
347
|
+
// rejectionHandled, warning): the runtime keeps the callback and calls it
|
|
348
|
+
// with fresh event values; off compares callbacks by identity.
|
|
349
|
+
"process.offRejectionHandled",
|
|
350
|
+
"process.offSignal",
|
|
351
|
+
"process.offUncaughtException",
|
|
352
|
+
"process.offUnhandledRejection",
|
|
353
|
+
"process.offWarning",
|
|
354
|
+
"process.onRejectionHandled",
|
|
355
|
+
"process.onSignal",
|
|
356
|
+
"process.onUncaughtException",
|
|
357
|
+
"process.onUnhandledRejection",
|
|
358
|
+
"process.onWarning",
|
|
265
359
|
"sharedArrayBuffer.new",
|
|
266
360
|
"text.decodeStream",
|
|
267
361
|
"worker.new",
|
|
@@ -654,8 +748,10 @@ export function analyzeInt32Slots(mod) {
|
|
|
654
748
|
},
|
|
655
749
|
});
|
|
656
750
|
for (const fn of mod.functions) {
|
|
657
|
-
if (fn.returnType.kind === "f64" && !fn.async && !fn.generator)
|
|
751
|
+
if (fn.returnType.kind === "f64" && !fn.async && !fn.generator) {
|
|
658
752
|
slots.addReturn(fn.name);
|
|
753
|
+
slots.assumeNonNegative(`ret:${fn.name}`);
|
|
754
|
+
}
|
|
659
755
|
if (fn.async ||
|
|
660
756
|
fn.generator ||
|
|
661
757
|
!called.has(fn.name) ||
|
|
@@ -663,8 +759,10 @@ export function analyzeInt32Slots(mod) {
|
|
|
663
759
|
virtual(fn.name))
|
|
664
760
|
continue;
|
|
665
761
|
for (const param of fn.params)
|
|
666
|
-
if (param.type.kind === "f64")
|
|
762
|
+
if (param.type.kind === "f64" && !slots.isPinned(paramKey(fn.name, param.localId))) {
|
|
667
763
|
slots.addParam(fn.name, param.localId);
|
|
764
|
+
slots.assumeNonNegative(paramKey(fn.name, param.localId));
|
|
765
|
+
}
|
|
668
766
|
}
|
|
669
767
|
const arityChecked = (fn, args, offset) => {
|
|
670
768
|
if (fn && fn.params.length !== args + offset)
|
|
@@ -784,6 +882,11 @@ export function analyzeInt32Slots(mod) {
|
|
|
784
882
|
}
|
|
785
883
|
boxOrigin.set(`param:${fnName}\0${localId}`, group);
|
|
786
884
|
slots.addBox(group, members);
|
|
885
|
+
// A box starts from its origin parameter's value: it can only be
|
|
886
|
+
// non-negative while that parameter is.
|
|
887
|
+
if (!origin.params.some((p) => p.localId === localId) ||
|
|
888
|
+
slots.isNonNegative(paramKey(fnName, localId)))
|
|
889
|
+
slots.assumeNonNegative(`box:${group}`);
|
|
787
890
|
}
|
|
788
891
|
const dropBox = (group, why) => {
|
|
789
892
|
if (group === null)
|
|
@@ -876,6 +979,9 @@ export function analyzeInt32Slots(mod) {
|
|
|
876
979
|
});
|
|
877
980
|
};
|
|
878
981
|
const provenByFunction = new Map();
|
|
982
|
+
// Each function's parameter and result writes under its latest analysis:
|
|
983
|
+
// at the fixpoint, the ranges of every write under the final facts.
|
|
984
|
+
const lastWrites = new Map();
|
|
879
985
|
while (queue.length) {
|
|
880
986
|
const fn = functions.get(queue.pop());
|
|
881
987
|
queued.delete(fn.name);
|
|
@@ -888,16 +994,35 @@ export function analyzeInt32Slots(mod) {
|
|
|
888
994
|
const ranges = hasCandidateWrite(fn)
|
|
889
995
|
? analyzeIntegerRanges(numeric(fn), slots.facts(fn.name))
|
|
890
996
|
: new Map();
|
|
997
|
+
if (ranges.size > 0)
|
|
998
|
+
lastWrites.set(fn.name, slotWrites(fn, ranges, functions));
|
|
999
|
+
else
|
|
1000
|
+
lastWrites.delete(fn.name);
|
|
891
1001
|
const proven = [];
|
|
892
1002
|
const int32 = (e) => withinInt32(ranges.get(e));
|
|
1003
|
+
// An int32 written into a slot still assumed non-negative: widen the
|
|
1004
|
+
// assumption when the value may be negative, and reanalyze its readers.
|
|
1005
|
+
const settle = (slot, e) => {
|
|
1006
|
+
if (!slots.isNonNegative(slot) || (ranges.get(e)?.min ?? -1) >= 0)
|
|
1007
|
+
return;
|
|
1008
|
+
slots.demote(slot);
|
|
1009
|
+
invalidate(slot);
|
|
1010
|
+
const group = boxOrigin.get(slot);
|
|
1011
|
+
if (group !== undefined && slots.demote(`box:${group}`))
|
|
1012
|
+
invalidate(`box:${group}`);
|
|
1013
|
+
};
|
|
893
1014
|
const args = (callee, list, offset) => {
|
|
894
1015
|
if (!callee)
|
|
895
1016
|
return;
|
|
896
1017
|
list.forEach((arg, i) => {
|
|
897
1018
|
const param = callee.params[i + offset];
|
|
898
|
-
if (!param || !slots.hasParam(callee.name, param.localId)
|
|
1019
|
+
if (!param || !slots.hasParam(callee.name, param.localId))
|
|
899
1020
|
return;
|
|
900
1021
|
const slot = `param:${callee.name}\0${param.localId}`;
|
|
1022
|
+
if (int32(arg)) {
|
|
1023
|
+
settle(slot, arg);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
901
1026
|
if (dropParam(callee.name, param.localId, `argument in ${fn.name} is not int32`)) {
|
|
902
1027
|
invalidate(slot);
|
|
903
1028
|
// An argument computed where the caller's own parameters were
|
|
@@ -913,9 +1038,11 @@ export function analyzeInt32Slots(mod) {
|
|
|
913
1038
|
};
|
|
914
1039
|
const boxWrite = (localId, value) => {
|
|
915
1040
|
const group = slots.boxGroup(fn.name, localId);
|
|
916
|
-
if (!slots.hasBox(group)
|
|
1041
|
+
if (!slots.hasBox(group))
|
|
917
1042
|
return;
|
|
918
|
-
if (
|
|
1043
|
+
if (int32(value))
|
|
1044
|
+
settle(`box:${group}`, value);
|
|
1045
|
+
else if (dropBox(group, `write in ${fn.name} is not int32`))
|
|
919
1046
|
invalidate(`box:${group}`);
|
|
920
1047
|
};
|
|
921
1048
|
walkFunction(fn, {
|
|
@@ -936,8 +1063,10 @@ export function analyzeInt32Slots(mod) {
|
|
|
936
1063
|
}
|
|
937
1064
|
}
|
|
938
1065
|
else if (s.kind === "return" && slots.hasReturn(fn.name)) {
|
|
939
|
-
if (s.value && int32(s.value))
|
|
1066
|
+
if (s.value && int32(s.value)) {
|
|
1067
|
+
settle(`ret:${fn.name}`, s.value);
|
|
940
1068
|
return;
|
|
1069
|
+
}
|
|
941
1070
|
reason(`ret:${fn.name}`, "return is not int32");
|
|
942
1071
|
slots.dropReturn(fn.name);
|
|
943
1072
|
invalidate(`ret:${fn.name}`);
|
|
@@ -957,7 +1086,8 @@ export function analyzeInt32Slots(mod) {
|
|
|
957
1086
|
for (const proven of provenByFunction.values())
|
|
958
1087
|
for (const value of proven)
|
|
959
1088
|
slots.provenWrites.add(value);
|
|
960
|
-
rangedParameters(slots, functions, retry, numeric, (callee) => readers.get(`ret:${callee}`));
|
|
1089
|
+
const ranged = rangedParameters(slots, functions, retry, numeric, (callee) => readers.get(`ret:${callee}`));
|
|
1090
|
+
narrowSlotRanges(slots, functions, numeric, lastWrites, ranged, (slot) => readers.get(slot));
|
|
961
1091
|
return slots;
|
|
962
1092
|
}
|
|
963
1093
|
/** Bounds a widened parameter range jumps to: the int32 limits, and the
|
|
@@ -985,8 +1115,9 @@ const widenMax = (max) => WIDEN_MAX.find((bound) => bound >= max) ?? Infinity;
|
|
|
985
1115
|
* every call site is checked under the final ranges. The facts the main
|
|
986
1116
|
* fixpoint proved are fixed here: these ranges only add precision. */
|
|
987
1117
|
function rangedParameters(slots, functions, retry, numeric, callersOf) {
|
|
1118
|
+
const changed = new Set();
|
|
988
1119
|
if (retry.size === 0)
|
|
989
|
-
return;
|
|
1120
|
+
return changed;
|
|
990
1121
|
// Per function: retried parameter → its range so far (absent: no call
|
|
991
1122
|
// site analyzed yet; null: failed).
|
|
992
1123
|
const retried = new Map();
|
|
@@ -1097,7 +1228,261 @@ function rangedParameters(slots, functions, retry, numeric, callersOf) {
|
|
|
1097
1228
|
}
|
|
1098
1229
|
for (const [fnName, ranges] of retried)
|
|
1099
1230
|
for (const [id, range] of ranges)
|
|
1100
|
-
if (range)
|
|
1231
|
+
if (range) {
|
|
1101
1232
|
slots.addParam(fnName, id, range);
|
|
1233
|
+
changed.add(fnName);
|
|
1234
|
+
}
|
|
1235
|
+
return changed;
|
|
1236
|
+
}
|
|
1237
|
+
const joinRanges = (a, b) => a && b ? { min: Math.min(a.min, b.min), max: Math.max(a.max, b.max) } : null;
|
|
1238
|
+
const rangeWithin = (inner, outer) => inner !== null && inner.min >= outer.min && inner.max <= outer.max;
|
|
1239
|
+
const sameRange = (a, b) => a.min === b.min && a.max === b.max;
|
|
1240
|
+
/** The values one analysis of a function writes into parameter slots of
|
|
1241
|
+
* its direct callees and into its own result slot (`param:` and `ret:`
|
|
1242
|
+
* keys), joined per slot; null when some write has no proven range. */
|
|
1243
|
+
function slotWrites(fn, ranges, functions) {
|
|
1244
|
+
const writes = new Map();
|
|
1245
|
+
const write = (key, value) => {
|
|
1246
|
+
const range = value ? (ranges.get(value) ?? null) : null;
|
|
1247
|
+
const previous = writes.get(key);
|
|
1248
|
+
writes.set(key, previous === undefined ? range : joinRanges(previous, range));
|
|
1249
|
+
};
|
|
1250
|
+
const args = (callee, list, offset) => {
|
|
1251
|
+
if (!callee)
|
|
1252
|
+
return;
|
|
1253
|
+
list.forEach((arg, i) => {
|
|
1254
|
+
const param = callee.params[i + offset];
|
|
1255
|
+
if (param)
|
|
1256
|
+
write(`param:${callee.name}\0${param.localId}`, arg);
|
|
1257
|
+
});
|
|
1258
|
+
};
|
|
1259
|
+
walkFunction(fn, {
|
|
1260
|
+
stmt: (s) => {
|
|
1261
|
+
if (s.kind === "return")
|
|
1262
|
+
write(`ret:${fn.name}`, s.value);
|
|
1263
|
+
},
|
|
1264
|
+
expr: (e) => {
|
|
1265
|
+
if (e.kind === "call")
|
|
1266
|
+
args(functions.get(e.callee), e.args, 0);
|
|
1267
|
+
else if (e.kind === "new")
|
|
1268
|
+
args(functions.get(`%${e.className}.constructor`), e.args, 1);
|
|
1269
|
+
},
|
|
1270
|
+
});
|
|
1271
|
+
return writes;
|
|
1272
|
+
}
|
|
1273
|
+
/** Rounds after which narrowSlotRanges keeps its last verified ranges. */
|
|
1274
|
+
const NARROW_ROUNDS = 6;
|
|
1275
|
+
/** Narrows parameter and result facts from their proven level (int32 or
|
|
1276
|
+
* non-negative int32, or a call-site range) to the values actually written
|
|
1277
|
+
* into them, and revives results the level could not prove. `(x * 31 + n)
|
|
1278
|
+
* % m` proves its result only under a positive divisor, and keeps i32
|
|
1279
|
+
* arithmetic only when `x` is known to be small; the levels know neither,
|
|
1280
|
+
* the values passed and returned do.
|
|
1281
|
+
*
|
|
1282
|
+
* A descending iteration from the fixpoint's facts: each round assumes a
|
|
1283
|
+
* range for every slot, reanalyzes the functions that read a slot whose
|
|
1284
|
+
* assumption changed, and joins the ranges of every write into each slot.
|
|
1285
|
+
* The assumptions are verified (inductive) when every slot's writes land
|
|
1286
|
+
* inside its assumed range: then, by induction over the execution, every
|
|
1287
|
+
* slot holds only values in its range. A verified round adopts the joined
|
|
1288
|
+
* writes as the next assumptions (each bound moves at most once, so the
|
|
1289
|
+
* iteration ends), and a reanalyzed function whose every return is now an
|
|
1290
|
+
* int32 joins with that range as a result candidate. A slot whose writes
|
|
1291
|
+
* leave its assumption falls back to its last verified range (a revived
|
|
1292
|
+
* result to none). Only verified assumptions are kept, and a reanalyzed
|
|
1293
|
+
* function must still prove every write into a field or box slot (whose
|
|
1294
|
+
* facts do not change here), or the narrowing is abandoned. Pinned
|
|
1295
|
+
* (contract) slots keep their contract range. */
|
|
1296
|
+
function narrowSlotRanges(slots, functions, numeric, lastWrites, changed, readersOf) {
|
|
1297
|
+
// Every parameter and result slot with a fact, and that fact.
|
|
1298
|
+
const proven = new Map();
|
|
1299
|
+
const fixed = new Set();
|
|
1300
|
+
// Results the fixpoint dropped (a return was not an int32 at the level).
|
|
1301
|
+
const revivable = new Set();
|
|
1302
|
+
for (const fn of functions.values()) {
|
|
1303
|
+
const result = slots.hasReturn(fn.name) ? slots.returnRange(fn.name) : null;
|
|
1304
|
+
if (result)
|
|
1305
|
+
proven.set(`ret:${fn.name}`, result);
|
|
1306
|
+
else if (fn.returnType.kind === "f64" && !fn.async && !fn.generator)
|
|
1307
|
+
revivable.add(`ret:${fn.name}`);
|
|
1308
|
+
for (const param of fn.params) {
|
|
1309
|
+
const range = slots.hasParam(fn.name, param.localId)
|
|
1310
|
+
? slots.paramRange(fn.name, param.localId)
|
|
1311
|
+
: null;
|
|
1312
|
+
if (range)
|
|
1313
|
+
proven.set(`param:${fn.name}\0${param.localId}`, range);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
for (const key of proven.keys())
|
|
1317
|
+
if (slots.isPinned(key))
|
|
1318
|
+
fixed.add(key);
|
|
1319
|
+
if (proven.size === fixed.size)
|
|
1320
|
+
return;
|
|
1321
|
+
// The readers of a slot: a parameter's function, a result's callers.
|
|
1322
|
+
const readersOfKey = (key) => {
|
|
1323
|
+
if (key.startsWith("param:"))
|
|
1324
|
+
return [key.slice(6, key.indexOf("\0"))];
|
|
1325
|
+
const readers = readersOf(key);
|
|
1326
|
+
return readers ? [...readers] : [];
|
|
1327
|
+
};
|
|
1328
|
+
const factsUnder = (fn, assumed) => {
|
|
1329
|
+
const base = slots.facts(fn.name);
|
|
1330
|
+
const params = new Map(base.params ?? []);
|
|
1331
|
+
for (const param of fn.params) {
|
|
1332
|
+
const range = assumed.get(`param:${fn.name}\0${param.localId}`);
|
|
1333
|
+
if (range)
|
|
1334
|
+
params.set(param.localId, range);
|
|
1335
|
+
}
|
|
1336
|
+
const call = base.call;
|
|
1337
|
+
return {
|
|
1338
|
+
...base,
|
|
1339
|
+
params,
|
|
1340
|
+
call: (callee) => assumed.get(`ret:${callee}`) ?? (call ? call(callee) : null),
|
|
1341
|
+
};
|
|
1342
|
+
};
|
|
1343
|
+
// Reanalyze one function: its slot writes, and whether it still proves
|
|
1344
|
+
// every write into field and box slots.
|
|
1345
|
+
const observe = (fn, assumed) => {
|
|
1346
|
+
const ranges = analyzeIntegerRanges(numeric(fn), factsUnder(fn, assumed));
|
|
1347
|
+
let sound = true;
|
|
1348
|
+
const box = (localId, value) => {
|
|
1349
|
+
const group = slots.boxGroup(fn.name, localId);
|
|
1350
|
+
if (!slots.hasBox(group))
|
|
1351
|
+
return;
|
|
1352
|
+
const range = ranges.get(value) ?? null;
|
|
1353
|
+
const level = slots.isNonNegative(`box:${group}`) ? NON_NEGATIVE_INT32_RANGE : INT32_RANGE;
|
|
1354
|
+
if (!rangeWithin(range, level))
|
|
1355
|
+
sound = false;
|
|
1356
|
+
};
|
|
1357
|
+
walkFunction(fn, {
|
|
1358
|
+
stmt: (s) => {
|
|
1359
|
+
if (s.kind === "assign")
|
|
1360
|
+
box(s.localId, s.value);
|
|
1361
|
+
else if (s.kind === "varDecl" && s.init)
|
|
1362
|
+
box(s.localId, s.init);
|
|
1363
|
+
else if (s.kind === "fieldSet" && slots.isField(s.className, s.field))
|
|
1364
|
+
if (!withinInt32(ranges.get(s.value)))
|
|
1365
|
+
sound = false;
|
|
1366
|
+
},
|
|
1367
|
+
expr: (e) => {
|
|
1368
|
+
if (e.kind === "assignExpr")
|
|
1369
|
+
box(e.localId, e.value);
|
|
1370
|
+
},
|
|
1371
|
+
});
|
|
1372
|
+
return { writes: slotWrites(fn, ranges, functions), sound };
|
|
1373
|
+
};
|
|
1374
|
+
const writesByFunction = new Map(lastWrites);
|
|
1375
|
+
const join = () => {
|
|
1376
|
+
const joined = new Map();
|
|
1377
|
+
for (const writes of writesByFunction.values())
|
|
1378
|
+
for (const [key, range] of writes) {
|
|
1379
|
+
if (!proven.has(key) && !revivable.has(key))
|
|
1380
|
+
continue;
|
|
1381
|
+
const previous = joined.get(key);
|
|
1382
|
+
joined.set(key, previous === undefined ? range : joinRanges(previous, range));
|
|
1383
|
+
}
|
|
1384
|
+
return joined;
|
|
1385
|
+
};
|
|
1386
|
+
// The ranges every function was last analyzed under.
|
|
1387
|
+
let analyzed = new Map(proven);
|
|
1388
|
+
let verified = new Map(proven);
|
|
1389
|
+
// Functions whose recorded writes predate their current facts.
|
|
1390
|
+
const dirty = new Set(changed);
|
|
1391
|
+
// The narrowing operator: a bound moves at most once from its proven
|
|
1392
|
+
// value (a revived result's from int32), so recursion that shifts its
|
|
1393
|
+
// argument (`f(d - 1)`) cannot walk a bound down one step per round.
|
|
1394
|
+
const narrowed = (joined) => {
|
|
1395
|
+
const next = new Map(verified);
|
|
1396
|
+
for (const [key, range] of joined) {
|
|
1397
|
+
const current = verified.get(key);
|
|
1398
|
+
if (!current || fixed.has(key) || !range || !rangeWithin(range, current))
|
|
1399
|
+
continue;
|
|
1400
|
+
const original = proven.get(key) ?? INT32_RANGE;
|
|
1401
|
+
next.set(key, {
|
|
1402
|
+
min: current.min === original.min ? range.min : current.min,
|
|
1403
|
+
max: current.max === original.max ? range.max : current.max,
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
return next;
|
|
1407
|
+
};
|
|
1408
|
+
let assumed = dirty.size > 0 ? new Map(proven) : narrowed(join());
|
|
1409
|
+
for (let round = 0; round < NARROW_ROUNDS; round++) {
|
|
1410
|
+
for (const [key, range] of assumed) {
|
|
1411
|
+
const before = analyzed.get(key);
|
|
1412
|
+
if (!before || !sameRange(range, before))
|
|
1413
|
+
for (const name of readersOfKey(key))
|
|
1414
|
+
dirty.add(name);
|
|
1415
|
+
}
|
|
1416
|
+
for (const key of analyzed.keys())
|
|
1417
|
+
if (!assumed.has(key))
|
|
1418
|
+
for (const name of readersOfKey(key))
|
|
1419
|
+
dirty.add(name);
|
|
1420
|
+
const observedNow = new Set();
|
|
1421
|
+
for (const name of dirty) {
|
|
1422
|
+
const fn = functions.get(name);
|
|
1423
|
+
if (!fn)
|
|
1424
|
+
continue;
|
|
1425
|
+
const observed = observe(fn, assumed);
|
|
1426
|
+
if (!observed.sound)
|
|
1427
|
+
return;
|
|
1428
|
+
writesByFunction.set(name, observed.writes);
|
|
1429
|
+
observedNow.add(name);
|
|
1430
|
+
}
|
|
1431
|
+
dirty.clear();
|
|
1432
|
+
analyzed = assumed;
|
|
1433
|
+
const joined = join();
|
|
1434
|
+
let inductive = true;
|
|
1435
|
+
const next = new Map(assumed);
|
|
1436
|
+
for (const [key, range] of assumed) {
|
|
1437
|
+
const written = joined.get(key);
|
|
1438
|
+
if (written === undefined || rangeWithin(written, range))
|
|
1439
|
+
continue;
|
|
1440
|
+
inductive = false;
|
|
1441
|
+
const fallback = verified.get(key);
|
|
1442
|
+
if (fallback)
|
|
1443
|
+
next.set(key, fallback);
|
|
1444
|
+
else
|
|
1445
|
+
next.delete(key);
|
|
1446
|
+
}
|
|
1447
|
+
if (inductive) {
|
|
1448
|
+
verified = assumed;
|
|
1449
|
+
const tighter = narrowed(joined);
|
|
1450
|
+
// Results that reanalysis under the narrowed facts proves now.
|
|
1451
|
+
for (const key of revivable) {
|
|
1452
|
+
const range = joined.get(key);
|
|
1453
|
+
if (!tighter.has(key) && observedNow.has(key.slice(4)) && withinInt32(range))
|
|
1454
|
+
tighter.set(key, range);
|
|
1455
|
+
}
|
|
1456
|
+
if (tighter.size === assumed.size &&
|
|
1457
|
+
[...tighter].every(([key, range]) => sameRange(range, assumed.get(key))))
|
|
1458
|
+
break;
|
|
1459
|
+
assumed = tighter;
|
|
1460
|
+
}
|
|
1461
|
+
else if (next.size !== assumed.size ||
|
|
1462
|
+
[...next].some(([key, range]) => !sameRange(range, assumed.get(key)))) {
|
|
1463
|
+
assumed = next;
|
|
1464
|
+
}
|
|
1465
|
+
else
|
|
1466
|
+
break; // narrowing other slots broke a verified one: keep `verified`
|
|
1467
|
+
}
|
|
1468
|
+
for (const [key, range] of verified) {
|
|
1469
|
+
if (fixed.has(key))
|
|
1470
|
+
continue;
|
|
1471
|
+
const original = proven.get(key);
|
|
1472
|
+
if (original && sameRange(range, original))
|
|
1473
|
+
continue;
|
|
1474
|
+
if (key.startsWith("ret:")) {
|
|
1475
|
+
const fnName = key.slice(4);
|
|
1476
|
+
if (!original) {
|
|
1477
|
+
slots.addReturn(fnName);
|
|
1478
|
+
slots.reasons.delete(key);
|
|
1479
|
+
}
|
|
1480
|
+
slots.narrowReturn(fnName, range);
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
const cut = key.indexOf("\0");
|
|
1484
|
+
slots.narrowParam(key.slice(6, cut), key.slice(cut + 1), range);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1102
1487
|
}
|
|
1103
1488
|
//# sourceMappingURL=int32-slots.js.map
|