semantifold 0.3.0 → 0.4.0
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/README.md +58 -15
- package/build/index.d.ts +48 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +57 -8
- package/build/src/artifact-path.d.ts +23 -1
- package/build/src/artifact-path.d.ts.map +1 -1
- package/build/src/artifact-path.js +45 -3
- package/build/src/artifacts.d.ts.map +1 -1
- package/build/src/artifacts.js +66 -1
- package/build/src/backends/c-validation.d.ts.map +1 -1
- package/build/src/backends/c-validation.js +7 -3
- package/build/src/backends/cpp-validation.d.ts.map +1 -1
- package/build/src/backends/cpp-validation.js +7 -3
- package/build/src/backends/csharp.js +7 -1
- package/build/src/backends/dart-runtime.d.ts +2 -0
- package/build/src/backends/dart-runtime.d.ts.map +1 -0
- package/build/src/backends/dart-runtime.js +41 -0
- package/build/src/backends/dart.d.ts +20 -0
- package/build/src/backends/dart.d.ts.map +1 -0
- package/build/src/backends/dart.js +321 -0
- package/build/src/backends/effects.d.ts +60 -0
- package/build/src/backends/effects.d.ts.map +1 -0
- package/build/src/backends/effects.js +746 -0
- package/build/src/backends/go.js +18 -4
- package/build/src/backends/identifiers.d.ts +36 -4
- package/build/src/backends/identifiers.d.ts.map +1 -1
- package/build/src/backends/identifiers.js +154 -10
- package/build/src/backends/index.d.ts.map +1 -1
- package/build/src/backends/index.js +27 -4
- package/build/src/backends/ios.d.ts +68 -0
- package/build/src/backends/ios.d.ts.map +1 -0
- package/build/src/backends/ios.js +1503 -0
- package/build/src/backends/java.d.ts.map +1 -1
- package/build/src/backends/java.js +429 -36
- package/build/src/backends/javascript.d.ts +6 -0
- package/build/src/backends/javascript.d.ts.map +1 -1
- package/build/src/backends/javascript.js +380 -24
- package/build/src/backends/kotlin-runtime.d.ts +3 -0
- package/build/src/backends/kotlin-runtime.d.ts.map +1 -0
- package/build/src/backends/kotlin-runtime.js +29 -0
- package/build/src/backends/kotlin.d.ts +23 -0
- package/build/src/backends/kotlin.d.ts.map +1 -0
- package/build/src/backends/kotlin.js +136 -0
- package/build/src/backends/native-validation.d.ts +2 -2
- package/build/src/backends/native-validation.d.ts.map +1 -1
- package/build/src/backends/native-validation.js +2 -2
- package/build/src/backends/ordered-expressions.d.ts.map +1 -1
- package/build/src/backends/ordered-expressions.js +39 -7
- package/build/src/backends/php-stdlib-support.d.ts +19 -0
- package/build/src/backends/php-stdlib-support.d.ts.map +1 -0
- package/build/src/backends/php-stdlib-support.js +227 -0
- package/build/src/backends/php.d.ts.map +1 -1
- package/build/src/backends/php.js +448 -33
- package/build/src/backends/program.d.ts +81 -0
- package/build/src/backends/program.d.ts.map +1 -0
- package/build/src/backends/program.js +1308 -0
- package/build/src/backends/python.js +7 -1
- package/build/src/backends/ruby.d.ts.map +1 -1
- package/build/src/backends/ruby.js +310 -24
- package/build/src/backends/rust-runtime.d.ts +7 -0
- package/build/src/backends/rust-runtime.d.ts.map +1 -0
- package/build/src/backends/rust-runtime.js +32 -0
- package/build/src/backends/rust-validation.d.ts +9 -0
- package/build/src/backends/rust-validation.d.ts.map +1 -0
- package/build/src/backends/rust-validation.js +78 -0
- package/build/src/backends/rust.d.ts +24 -0
- package/build/src/backends/rust.d.ts.map +1 -0
- package/build/src/backends/rust.js +344 -0
- package/build/src/backends/scalars.d.ts +14 -6
- package/build/src/backends/scalars.d.ts.map +1 -1
- package/build/src/backends/scalars.js +167 -17
- package/build/src/backends/shared.d.ts +23 -4
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +2158 -62
- package/build/src/backends/stdlib-support.d.ts +14 -0
- package/build/src/backends/stdlib-support.d.ts.map +1 -0
- package/build/src/backends/stdlib-support.js +237 -0
- package/build/src/backends/swift-runtime.d.ts +3 -0
- package/build/src/backends/swift-runtime.d.ts.map +1 -0
- package/build/src/backends/swift-runtime.js +14 -0
- package/build/src/backends/swift.d.ts +8 -0
- package/build/src/backends/swift.d.ts.map +1 -0
- package/build/src/backends/swift.js +127 -0
- package/build/src/backends/typescript.d.ts.map +1 -1
- package/build/src/backends/typescript.js +344 -24
- package/build/src/backends/wasm.d.ts +43 -0
- package/build/src/backends/wasm.d.ts.map +1 -0
- package/build/src/backends/wasm.js +2164 -0
- package/build/src/backends/writer.d.ts +235 -4
- package/build/src/backends/writer.d.ts.map +1 -1
- package/build/src/backends/writer.js +468 -3
- package/build/src/diagnostic.d.ts +4 -3
- package/build/src/diagnostic.d.ts.map +1 -1
- package/build/src/diagnostic.js +5 -3
- package/build/src/frontends/c.d.ts.map +1 -1
- package/build/src/frontends/c.js +5 -3
- package/build/src/frontends/cpp.d.ts.map +1 -1
- package/build/src/frontends/cpp.js +5 -3
- package/build/src/frontends/csharp.d.ts.map +1 -1
- package/build/src/frontends/csharp.js +7 -3
- package/build/src/frontends/dart.d.ts +10 -0
- package/build/src/frontends/dart.d.ts.map +1 -0
- package/build/src/frontends/dart.js +913 -0
- package/build/src/frontends/index.d.ts +3 -1
- package/build/src/frontends/index.d.ts.map +1 -1
- package/build/src/frontends/index.js +15 -5
- package/build/src/frontends/java.d.ts +110 -1
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +1725 -70
- package/build/src/frontends/javascript-typescript.d.ts +69 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +1825 -93
- package/build/src/frontends/kotlin.d.ts +10 -0
- package/build/src/frontends/kotlin.d.ts.map +1 -0
- package/build/src/frontends/kotlin.js +685 -0
- package/build/src/frontends/php.d.ts +118 -1
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +1583 -76
- package/build/src/frontends/program.d.ts +74 -0
- package/build/src/frontends/program.d.ts.map +1 -0
- package/build/src/frontends/program.js +989 -0
- package/build/src/frontends/ruby.d.ts +68 -1
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +1446 -101
- package/build/src/frontends/rust-parser.d.ts +11 -0
- package/build/src/frontends/rust-parser.d.ts.map +1 -0
- package/build/src/frontends/rust-parser.js +15 -0
- package/build/src/frontends/rust.d.ts +14 -0
- package/build/src/frontends/rust.d.ts.map +1 -0
- package/build/src/frontends/rust.js +715 -0
- package/build/src/frontends/scalars.d.ts +10 -0
- package/build/src/frontends/scalars.d.ts.map +1 -1
- package/build/src/frontends/scalars.js +28 -2
- package/build/src/frontends/swift.d.ts +10 -0
- package/build/src/frontends/swift.d.ts.map +1 -0
- package/build/src/frontends/swift.js +860 -0
- package/build/src/frontends/types.d.ts +186 -0
- package/build/src/frontends/types.d.ts.map +1 -0
- package/build/src/frontends/types.js +634 -0
- package/build/src/language-registry.d.ts +46 -4
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +167 -13
- package/build/src/mapping.d.ts +2 -2
- package/build/src/mapping.d.ts.map +1 -1
- package/build/src/mapping.js +31 -10
- package/build/src/semantic/capabilities.d.ts +66 -0
- package/build/src/semantic/capabilities.d.ts.map +1 -0
- package/build/src/semantic/capabilities.js +649 -0
- package/build/src/semantic/effects.d.ts +26 -0
- package/build/src/semantic/effects.d.ts.map +1 -0
- package/build/src/semantic/effects.js +764 -0
- package/build/src/semantic/generics.d.ts +23 -0
- package/build/src/semantic/generics.d.ts.map +1 -0
- package/build/src/semantic/generics.js +61 -0
- package/build/src/semantic/lifetime.d.ts +37 -0
- package/build/src/semantic/lifetime.d.ts.map +1 -0
- package/build/src/semantic/lifetime.js +527 -0
- package/build/src/semantic/provenance.d.ts +9 -2
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +462 -20
- package/build/src/semantic/scalars.d.ts +11 -0
- package/build/src/semantic/scalars.d.ts.map +1 -1
- package/build/src/semantic/scalars.js +16 -1
- package/build/src/semantic/stdlib.d.ts +254 -0
- package/build/src/semantic/stdlib.d.ts.map +1 -0
- package/build/src/semantic/stdlib.js +880 -0
- package/build/src/semantic/types.d.ts +2952 -202
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +688 -15
- package/build/src/semantic/validate.d.ts +85 -5
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +2576 -98
- package/build/src/stdlib-facades.d.ts +304 -0
- package/build/src/stdlib-facades.d.ts.map +1 -0
- package/build/src/stdlib-facades.js +1167 -0
- package/build/src/stdlib-negotiation.d.ts +69 -0
- package/build/src/stdlib-negotiation.d.ts.map +1 -0
- package/build/src/stdlib-negotiation.js +403 -0
- package/build/src/stdlib-providers.d.ts +72 -0
- package/build/src/stdlib-providers.d.ts.map +1 -0
- package/build/src/stdlib-providers.js +339 -0
- package/build/src/toolchains.d.ts +55 -1
- package/build/src/toolchains.d.ts.map +1 -1
- package/build/src/toolchains.js +15 -4
- package/docs/architecture.md +45 -13
- package/docs/dart.md +60 -0
- package/docs/goals.md +3 -3
- package/docs/ios.md +121 -0
- package/docs/kotlin.md +42 -0
- package/docs/language-support.md +231 -26
- package/docs/multi-file-modules.md +52 -0
- package/docs/parser-qualification.md +121 -8
- package/docs/plans/2026-09-05-standard-library-portability.md +10 -10
- package/docs/rust.md +88 -0
- package/docs/source-maps.md +15 -7
- package/docs/standard-library-portability.md +39 -8
- package/docs/swift.md +44 -0
- package/docs/testing.md +366 -4
- package/docs/webassembly.md +52 -0
- package/index.js +63 -8
- package/node_modules/semantifold-tree-sitter-legacy-internal/README.md +2 -2
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/LICENSE +21 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/README.md +45 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/binding.gyp +30 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding.cc +20 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/binding_test.js +9 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.d.ts +28 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/bindings/node/index.js +11 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/grammar.js +1630 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/package.json +64 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/darwin-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/linux-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-arm64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/prebuilds/win32-x64/tree-sitter-rust.node +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/highlights.scm +159 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/injections.scm +9 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/queries/tags.scm +60 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/grammar.json +9405 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/node-types.json +5399 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/parser.c +192569 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/scanner.c +393 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/alloc.h +54 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/array.h +290 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/src/tree_sitter/parser.h +266 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter-rust.wasm +0 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/node_modules/tree-sitter-rust/tree-sitter.json +53 -0
- package/node_modules/semantifold-tree-sitter-legacy-internal/package.json +3 -2
- package/node_modules/semantifold-tree-sitter-legacy-internal/src/c.js +5 -4
- package/package.json +5 -2
- package/src/artifact-path.js +47 -2
- package/src/artifacts.js +67 -0
- package/src/backends/c-validation.js +6 -1
- package/src/backends/cpp-validation.js +6 -1
- package/src/backends/csharp.js +3 -0
- package/src/backends/dart-runtime.js +41 -0
- package/src/backends/dart.js +326 -0
- package/src/backends/effects.js +705 -0
- package/src/backends/go.js +14 -2
- package/src/backends/identifiers.js +160 -9
- package/src/backends/index.js +27 -2
- package/src/backends/ios.js +1612 -0
- package/src/backends/java.js +454 -36
- package/src/backends/javascript.js +389 -23
- package/src/backends/kotlin-runtime.js +29 -0
- package/src/backends/kotlin.js +141 -0
- package/src/backends/native-validation.js +1 -1
- package/src/backends/ordered-expressions.js +40 -6
- package/src/backends/php-stdlib-support.js +239 -0
- package/src/backends/php.js +469 -32
- package/src/backends/program.js +1407 -0
- package/src/backends/python.js +3 -0
- package/src/backends/ruby.js +326 -23
- package/src/backends/rust-runtime.js +34 -0
- package/src/backends/rust-validation.js +75 -0
- package/src/backends/rust.js +335 -0
- package/src/backends/scalars.js +141 -16
- package/src/backends/shared.js +2304 -124
- package/src/backends/stdlib-support.js +234 -0
- package/src/backends/swift-runtime.js +14 -0
- package/src/backends/swift.js +132 -0
- package/src/backends/typescript.js +349 -23
- package/src/backends/wasm.js +2208 -0
- package/src/backends/writer.js +520 -2
- package/src/diagnostic.js +4 -2
- package/src/frontends/c.js +4 -2
- package/src/frontends/cpp.js +4 -2
- package/src/frontends/csharp.js +4 -2
- package/src/frontends/dart.js +963 -0
- package/src/frontends/index.js +14 -4
- package/src/frontends/java.js +1977 -69
- package/src/frontends/javascript-typescript.js +2046 -102
- package/src/frontends/kotlin.js +713 -0
- package/src/frontends/php.js +1725 -79
- package/src/frontends/program.js +1062 -0
- package/src/frontends/ruby.js +1624 -98
- package/src/frontends/rust-parser.js +18 -0
- package/src/frontends/rust.js +714 -0
- package/src/frontends/scalars.js +31 -1
- package/src/frontends/swift.js +893 -0
- package/src/frontends/types.js +750 -0
- package/src/language-registry.js +172 -12
- package/src/mapping.js +33 -9
- package/src/semantic/capabilities.js +671 -0
- package/src/semantic/effects.js +737 -0
- package/src/semantic/generics.js +58 -0
- package/src/semantic/lifetime.js +517 -0
- package/src/semantic/provenance.js +412 -19
- package/src/semantic/scalars.js +17 -0
- package/src/semantic/stdlib.js +921 -0
- package/src/semantic/types.js +765 -14
- package/src/semantic/validate.js +2934 -209
- package/src/stdlib-facades.js +1211 -0
- package/src/stdlib-negotiation.js +446 -0
- package/src/stdlib-providers.js +377 -0
- package/src/toolchains.js +16 -3
|
@@ -0,0 +1,2208 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {addSegment, GenMapping, maybeAddSegment, setSourceContent, toEncodedMap} from "@jridgewell/gen-mapping"
|
|
4
|
+
import {createByteMapping} from "../binary-mapping.js"
|
|
5
|
+
import {unsupportedCapability} from "../diagnostic.js"
|
|
6
|
+
import {finalizeMapping, toSourceMapV3} from "../mapping.js"
|
|
7
|
+
import {createGenerationIndex, primaryLocation, semanticEntries} from "../semantic/provenance.js"
|
|
8
|
+
import {validateNativeGraph} from "./native-validation.js"
|
|
9
|
+
import {validateBackendModule} from "./shared.js"
|
|
10
|
+
import {SourceWriter} from "./writer.js"
|
|
11
|
+
|
|
12
|
+
/** @typedef {import("../semantic/types.js").SemanticModule} SemanticModule */
|
|
13
|
+
/** @typedef {import("../semantic/types.js").SemanticNode} SemanticNode */
|
|
14
|
+
/** @typedef {import("../semantic/types.js").Expression} Expression */
|
|
15
|
+
/** @typedef {import("../semantic/types.js").SemanticTypeName} Scalar */
|
|
16
|
+
/** @typedef {import("../semantic/types.js").GeneratedByteRange} GeneratedByteRange */
|
|
17
|
+
/** @typedef {import("../semantic/types.js").SemanticNodeProvenance} SemanticNodeProvenance */
|
|
18
|
+
/** @typedef {{type: Scalar, knownBoolean?: boolean, knownInteger?: bigint, knownString?: string, stringLength?: number}} AbstractValue */
|
|
19
|
+
/** @typedef {{allocated: number, env: Map<string, AbstractValue>, returned?: AbstractValue}} AnalysisState */
|
|
20
|
+
/** @typedef {{type: Scalar, indexes: number[]}} LocalBinding */
|
|
21
|
+
|
|
22
|
+
const encoder = new TextEncoder()
|
|
23
|
+
const scratchCapacity = 1_048_576
|
|
24
|
+
const maximumCallDepth = 64
|
|
25
|
+
const wasmPageSize = 65_536
|
|
26
|
+
const maximumAnalysisSteps = 100_000
|
|
27
|
+
const valueTypes = Object.freeze({i32: 0x7f, i64: 0x7e})
|
|
28
|
+
const helperNames = Object.freeze(["$concat", "$string_equal", "$checked_add", "$checked_subtract", "$checked_multiply", "$checked_negate"])
|
|
29
|
+
const helperCount = helperNames.length
|
|
30
|
+
const importCount = 3
|
|
31
|
+
const concatFunctionIndex = importCount
|
|
32
|
+
const stringEqualFunctionIndex = concatFunctionIndex + 1
|
|
33
|
+
const checkedAddFunctionIndex = stringEqualFunctionIndex + 1
|
|
34
|
+
const checkedSubtractFunctionIndex = checkedAddFunctionIndex + 1
|
|
35
|
+
const checkedMultiplyFunctionIndex = checkedSubtractFunctionIndex + 1
|
|
36
|
+
const checkedNegateFunctionIndex = checkedMultiplyFunctionIndex + 1
|
|
37
|
+
const cursorGlobalIndex = 0
|
|
38
|
+
const depthGlobalIndex = 1
|
|
39
|
+
const activeGlobalIndex = 2
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generates the complete deterministic browser Wasm artifact set.
|
|
43
|
+
* @param {{filename?: string, mapDirective?: unknown, module: SemanticModule, sourceMapFilename?: unknown, sources?: {filename: string, content: string, language?: import("../semantic/types.js").SemanticLanguage}[]}} input - Backend request.
|
|
44
|
+
* @returns {{artifacts: import("../semantic/types.js").GeneratedSetArtifact[], metadata: Readonly<Record<string, unknown>>, target: string}} Candidate artifact set.
|
|
45
|
+
*/
|
|
46
|
+
export function generateBrowserWasm({filename, mapDirective, module, sourceMapFilename, sources}) {
|
|
47
|
+
validateNativeGraph(module, "wasm")
|
|
48
|
+
validateBackendModule(module, "wasm")
|
|
49
|
+
if (filename !== undefined && filename != "program.wasm") {
|
|
50
|
+
unsupportedCapability("wasm", "artifact filename other than program.wasm", module.location)
|
|
51
|
+
}
|
|
52
|
+
if (mapDirective !== undefined) unsupportedCapability("wasm", "source-map directive option", module.location)
|
|
53
|
+
if (sourceMapFilename !== undefined) unsupportedCapability("wasm", "alternate source-map filename", module.location)
|
|
54
|
+
analyzeScratchUse(module)
|
|
55
|
+
|
|
56
|
+
const index = createGenerationIndex(module, sources)
|
|
57
|
+
const literalLayout = collectLiteralLayout(module, index)
|
|
58
|
+
const scratchBase = align(literalLayout.boundary, 16)
|
|
59
|
+
const memoryBytes = checkedSize(scratchBase + scratchCapacity, module, "linear memory size")
|
|
60
|
+
const memoryPages = Math.ceil(memoryBytes / wasmPageSize)
|
|
61
|
+
const encoded = encodeModule(module, index, literalLayout, scratchBase, memoryPages)
|
|
62
|
+
|
|
63
|
+
validateEncodedModule(encoded.bytes, module)
|
|
64
|
+
const byteMapping = createByteMapping({byteLength: encoded.bytes.byteLength, path: "program.wasm", ranges: encoded.ranges})
|
|
65
|
+
const wasmMap = createWasmSourceMap(byteMapping, index.provenance)
|
|
66
|
+
const loader = textArtifact(semantifoldLoader, "semantifold-loader.mjs", "javascript", "loader", module, sources,
|
|
67
|
+
"Versioned browser Wasm ABI loader and exact scalar bridge.")
|
|
68
|
+
const html = textArtifact(browserHarness, "index.html", "html", "entry", module, sources,
|
|
69
|
+
"Deterministic local browser acceptance harness.")
|
|
70
|
+
const metadata = /** @type {const} */ ({
|
|
71
|
+
abi: "semantifold.browser.v1",
|
|
72
|
+
exports: ["memory", "run"],
|
|
73
|
+
imports: [
|
|
74
|
+
{module: "semantifold", name: "print_i64", parameters: ["i64"], results: []},
|
|
75
|
+
{module: "semantifold", name: "print_bool", parameters: ["i32"], results: []},
|
|
76
|
+
{module: "semantifold", name: "print_string", parameters: ["i32", "i32"], results: []}
|
|
77
|
+
],
|
|
78
|
+
maximumCallDepth,
|
|
79
|
+
memory: {
|
|
80
|
+
initialPages: memoryPages,
|
|
81
|
+
literalBoundary: literalLayout.boundary,
|
|
82
|
+
maximumPages: memoryPages,
|
|
83
|
+
scratchBase,
|
|
84
|
+
scratchCapacity
|
|
85
|
+
},
|
|
86
|
+
schema: "SemantifoldBrowserWasmABI",
|
|
87
|
+
version: 1
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
artifacts: [{
|
|
92
|
+
content: encoded.bytes,
|
|
93
|
+
contentKind: "binary",
|
|
94
|
+
mediaType: "application/wasm",
|
|
95
|
+
ownership: "generated",
|
|
96
|
+
path: "program.wasm",
|
|
97
|
+
provenance: {kind: "bytes", mapping: byteMapping},
|
|
98
|
+
role: "resource"
|
|
99
|
+
}, {
|
|
100
|
+
content: `${JSON.stringify(wasmMap)}\n`,
|
|
101
|
+
contentKind: "text",
|
|
102
|
+
mediaType: "application/json",
|
|
103
|
+
ownership: "generated",
|
|
104
|
+
path: "program.wasm.map",
|
|
105
|
+
provenance: {
|
|
106
|
+
kind: "synthetic",
|
|
107
|
+
reason: "Source Map v3 projection of program.wasm byte provenance.",
|
|
108
|
+
relatedOrigins: relatedOrigins(index.recordFor(module, ""))
|
|
109
|
+
},
|
|
110
|
+
role: "mapping"
|
|
111
|
+
}, loader, html],
|
|
112
|
+
metadata,
|
|
113
|
+
target: "wasm"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Independently checks the completed module before any bytes enter an artifact.
|
|
119
|
+
* @param {Uint8Array} bytes - Completed module bytes.
|
|
120
|
+
* @param {SemanticModule} module - Related semantic module.
|
|
121
|
+
* @returns {void}
|
|
122
|
+
*/
|
|
123
|
+
function validateEncodedModule(bytes, module) {
|
|
124
|
+
const moduleBytes = new Uint8Array(bytes.byteLength)
|
|
125
|
+
|
|
126
|
+
moduleBytes.set(bytes)
|
|
127
|
+
if (!WebAssembly.validate(moduleBytes)) unsupportedCapability("wasm", "invalid encoded section, index, stack, or control shape", module.location)
|
|
128
|
+
|
|
129
|
+
/** @type {WebAssembly.Module} */
|
|
130
|
+
let compiled
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
compiled = new WebAssembly.Module(moduleBytes)
|
|
134
|
+
} catch (_error) {
|
|
135
|
+
unsupportedCapability("wasm", "invalid completed WebAssembly module", module.location)
|
|
136
|
+
}
|
|
137
|
+
const imports = WebAssembly.Module.imports(compiled).map(({kind, module: namespace, name}) => [namespace, name, kind])
|
|
138
|
+
const exports = WebAssembly.Module.exports(compiled).map(({kind, name}) => [name, kind])
|
|
139
|
+
|
|
140
|
+
if (JSON.stringify(imports) != JSON.stringify([
|
|
141
|
+
["semantifold", "print_i64", "function"],
|
|
142
|
+
["semantifold", "print_bool", "function"],
|
|
143
|
+
["semantifold", "print_string", "function"]
|
|
144
|
+
]) || JSON.stringify(exports) != JSON.stringify([["memory", "memory"], ["run", "function"]])) {
|
|
145
|
+
unsupportedCapability("wasm", "invalid encoded ABI import or export set", module.location)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Encodes one validated semantic module directly to portable core WebAssembly.
|
|
151
|
+
* @param {SemanticModule} module - Validated module.
|
|
152
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Canonical provenance index.
|
|
153
|
+
* @param {ReturnType<typeof collectLiteralLayout>} literals - Static literal layout.
|
|
154
|
+
* @param {number} scratchBase - Aligned scratch arena base.
|
|
155
|
+
* @param {number} memoryPages - Fixed memory page count.
|
|
156
|
+
* @returns {{bytes: Uint8Array, ranges: GeneratedByteRange[]}} Encoded module and rich byte ranges.
|
|
157
|
+
*/
|
|
158
|
+
function encodeModule(module, index, literals, scratchBase, memoryPages) {
|
|
159
|
+
const writer = new BinaryWriter(index)
|
|
160
|
+
const root = {node: module, path: ""}
|
|
161
|
+
|
|
162
|
+
writer.synthetic([0x00, 0x61, 0x73, 0x6d], "WebAssembly binary magic", root, "module header")
|
|
163
|
+
writer.synthetic([0x01, 0x00, 0x00, 0x00], "Pinned WebAssembly core binary version 1", root, "module header")
|
|
164
|
+
|
|
165
|
+
const types = new TypePool()
|
|
166
|
+
const importTypes = [
|
|
167
|
+
types.add(["i64"], []),
|
|
168
|
+
types.add(["i32"], []),
|
|
169
|
+
types.add(["i32", "i32"], [])
|
|
170
|
+
]
|
|
171
|
+
const helperTypes = [
|
|
172
|
+
types.add(["i32", "i32", "i32", "i32"], ["i32", "i32"]),
|
|
173
|
+
types.add(["i32", "i32", "i32", "i32"], ["i32"]),
|
|
174
|
+
types.add(["i64", "i64"], ["i64"]),
|
|
175
|
+
types.add(["i64", "i64"], ["i64"]),
|
|
176
|
+
types.add(["i64", "i64"], ["i64"]),
|
|
177
|
+
types.add(["i64"], ["i64"])
|
|
178
|
+
]
|
|
179
|
+
const semanticTypeIndexes = module.functions.map((declaration) => types.add(
|
|
180
|
+
declaration.parameters.flatMap(({type, location}) => wasmTypes(wasmScalarType(type, location))),
|
|
181
|
+
wasmTypes(wasmReturnType(declaration))
|
|
182
|
+
))
|
|
183
|
+
const runType = types.add([], [])
|
|
184
|
+
const functionIndexes = new Map(module.functions.map((declaration, offset) => [declaration.name, importCount + helperCount + offset]))
|
|
185
|
+
const runFunctionIndex = importCount + helperCount + module.functions.length
|
|
186
|
+
|
|
187
|
+
appendSection(writer, 1, "type", encodeTypeSection(types, index, root), root)
|
|
188
|
+
appendSection(writer, 2, "import", encodeImportSection(importTypes, index, root), root)
|
|
189
|
+
appendSection(writer, 3, "function", encodeFunctionSection(helperTypes, semanticTypeIndexes, runType, module, index), root)
|
|
190
|
+
appendSection(writer, 5, "memory", encodeMemorySection(memoryPages, index, root), root)
|
|
191
|
+
appendSection(writer, 6, "global", encodeGlobalSection(scratchBase, index, root), root)
|
|
192
|
+
appendSection(writer, 7, "export", encodeExportSection(runFunctionIndex, index, root), root)
|
|
193
|
+
|
|
194
|
+
const code = new BinaryWriter(index)
|
|
195
|
+
|
|
196
|
+
code.synthetic(u32(helperCount + module.functions.length + 1), "Defined function count", root, "function count")
|
|
197
|
+
appendFunctionBody(code, helperConcat(index, module, scratchBase), ["i32", "i32", "i32", "i32"], root, "$concat")
|
|
198
|
+
appendFunctionBody(code, helperStringEqual(index, module), ["i32"], root, "$string_equal")
|
|
199
|
+
appendFunctionBody(code, helperCheckedAdd(index, module), ["i64"], root, "$checked_add")
|
|
200
|
+
appendFunctionBody(code, helperCheckedSubtract(index, module), ["i64"], root, "$checked_subtract")
|
|
201
|
+
appendFunctionBody(code, helperCheckedMultiply(index, module), ["i64"], root, "$checked_multiply")
|
|
202
|
+
appendFunctionBody(code, helperCheckedNegate(index, module), [], root, "$checked_negate")
|
|
203
|
+
|
|
204
|
+
for (let functionOffset = 0; functionOffset < module.functions.length; functionOffset += 1) {
|
|
205
|
+
const declaration = module.functions[functionOffset]
|
|
206
|
+
const path = `/functions/${functionOffset}`
|
|
207
|
+
const emitter = new FunctionEmitter({functionIndexes, index, literals: literals.byValue, module, scratchBase})
|
|
208
|
+
|
|
209
|
+
emitter.bindParameters(declaration.parameters)
|
|
210
|
+
emitter.block(declaration.body, `${path}/body`)
|
|
211
|
+
emitter.syntheticUnreachable(declaration, path)
|
|
212
|
+
emitter.syntheticEnd(declaration, path)
|
|
213
|
+
appendFunctionBody(code, emitter.writer, emitter.locals, {node: declaration, path}, declaration.name)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const runEmitter = new FunctionEmitter({functionIndexes, index, literals: literals.byValue, module, scratchBase})
|
|
217
|
+
|
|
218
|
+
runEmitter.run(module.entryPoint, "/entryPoint")
|
|
219
|
+
appendFunctionBody(code, runEmitter.writer, runEmitter.locals, {node: module.entryPoint, path: "/entryPoint"}, "run")
|
|
220
|
+
appendSection(writer, 10, "code", code, root)
|
|
221
|
+
appendSection(writer, 11, "data", encodeDataSection(literals.entries, index, root), root)
|
|
222
|
+
appendCustomSection(writer, "name", encodeNamePayload(module, runFunctionIndex, index), root)
|
|
223
|
+
appendCustomSection(writer, "sourceMappingURL", namedPayload("program.wasm.map", index, root), root)
|
|
224
|
+
|
|
225
|
+
return {bytes: writer.finish(), ranges: writer.ranges}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Direct semantic function and entry-point instruction emitter. */
|
|
229
|
+
class FunctionEmitter {
|
|
230
|
+
/**
|
|
231
|
+
* Creates an instruction emitter.
|
|
232
|
+
* @param {object} options - Encoding context.
|
|
233
|
+
* @param {Map<string, number>} options.functionIndexes - Semantic function indexes.
|
|
234
|
+
* @param {ReturnType<typeof createGenerationIndex>} options.index - Provenance index.
|
|
235
|
+
* @param {Map<string, {address: number, bytes: Uint8Array}>} options.literals - Literal addresses.
|
|
236
|
+
* @param {SemanticModule} options.module - Complete module.
|
|
237
|
+
* @param {number} options.scratchBase - Scratch base.
|
|
238
|
+
*/
|
|
239
|
+
constructor({functionIndexes, index, literals, module, scratchBase}) {
|
|
240
|
+
this.functionIndexes = functionIndexes
|
|
241
|
+
this.functions = new Map(module.functions.map((declaration) => [declaration.name, declaration]))
|
|
242
|
+
this.index = index
|
|
243
|
+
this.literals = literals
|
|
244
|
+
this.module = module
|
|
245
|
+
this.scratchBase = scratchBase
|
|
246
|
+
this.writer = new BinaryWriter(index)
|
|
247
|
+
/** @type {("i32" | "i64")[]} */
|
|
248
|
+
this.locals = []
|
|
249
|
+
/** @type {Map<string, LocalBinding>} */
|
|
250
|
+
this.bindings = new Map()
|
|
251
|
+
this.parameterCount = 0
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Binds flattened semantic parameters to Wasm parameter indexes.
|
|
256
|
+
* @param {import("../semantic/types.js").Parameter[]} parameters - Semantic parameters.
|
|
257
|
+
* @returns {void}
|
|
258
|
+
*/
|
|
259
|
+
bindParameters(parameters) {
|
|
260
|
+
let index = 0
|
|
261
|
+
|
|
262
|
+
for (const parameter of parameters) {
|
|
263
|
+
const scalar = wasmScalarType(parameter.type, parameter.location)
|
|
264
|
+
const types = wasmTypes(scalar)
|
|
265
|
+
const indexes = types.map(() => index++)
|
|
266
|
+
|
|
267
|
+
this.bindings.set(parameter.name, {indexes, type: scalar})
|
|
268
|
+
}
|
|
269
|
+
this.parameterCount = index
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Emits the guarded exported run body.
|
|
274
|
+
* @param {import("../semantic/types.js").EntryPoint} entryPoint - Semantic entry point.
|
|
275
|
+
* @param {string} path - Entry-point path.
|
|
276
|
+
* @returns {void}
|
|
277
|
+
*/
|
|
278
|
+
run(entryPoint, path) {
|
|
279
|
+
this.syntheticOp(0x23, entryPoint, path, "active guard read")
|
|
280
|
+
this.syntheticImmediate(u32(activeGlobalIndex), entryPoint, path, "active guard global index")
|
|
281
|
+
this.syntheticOp(0x04, entryPoint, path, "active guard branch")
|
|
282
|
+
this.syntheticImmediate([0x40], entryPoint, path, "empty block type")
|
|
283
|
+
this.syntheticOp(0x00, entryPoint, path, "reentrant invocation trap")
|
|
284
|
+
this.syntheticOp(0x0b, entryPoint, path, "active guard branch end")
|
|
285
|
+
this.syntheticI32(1, entryPoint, path, "mark run active")
|
|
286
|
+
this.syntheticGlobalSet(activeGlobalIndex, entryPoint, path, "active guard")
|
|
287
|
+
this.syntheticI32(this.scratchBase, entryPoint, path, "reset scratch cursor")
|
|
288
|
+
this.syntheticGlobalSet(cursorGlobalIndex, entryPoint, path, "scratch cursor")
|
|
289
|
+
this.syntheticI32(0, entryPoint, path, "reset semantic call depth")
|
|
290
|
+
this.syntheticGlobalSet(depthGlobalIndex, entryPoint, path, "call depth")
|
|
291
|
+
this.block(entryPoint.body, `${path}/body`)
|
|
292
|
+
this.syntheticI32(0, entryPoint, path, "clear semantic call depth")
|
|
293
|
+
this.syntheticGlobalSet(depthGlobalIndex, entryPoint, path, "call depth")
|
|
294
|
+
this.syntheticI32(0, entryPoint, path, "mark run inactive")
|
|
295
|
+
this.syntheticGlobalSet(activeGlobalIndex, entryPoint, path, "active guard")
|
|
296
|
+
this.syntheticEnd(entryPoint, path)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Emits one semantic block.
|
|
301
|
+
* @param {import("../semantic/types.js").Block} block - Semantic block.
|
|
302
|
+
* @param {string} path - Occurrence path.
|
|
303
|
+
* @returns {void}
|
|
304
|
+
*/
|
|
305
|
+
block(block, path) {
|
|
306
|
+
for (let index = 0; index < block.statements.length; index += 1) {
|
|
307
|
+
const statement = block.statements[index]
|
|
308
|
+
const statementPath = `${path}/statements/${index}`
|
|
309
|
+
|
|
310
|
+
if (statement.kind == "LocalDeclaration") {
|
|
311
|
+
this.expression(statement.initializer, `${statementPath}/initializer`)
|
|
312
|
+
const binding = this.allocateBinding(wasmScalarType(statement.type, statement.location))
|
|
313
|
+
|
|
314
|
+
this.bindings.set(statement.name, binding)
|
|
315
|
+
this.setBinding(binding, statement, statementPath, "name")
|
|
316
|
+
} else if (statement.kind == "AssignmentStatement") {
|
|
317
|
+
this.expression(statement.expression, `${statementPath}/expression`)
|
|
318
|
+
const binding = this.bindings.get(statement.target.name)
|
|
319
|
+
|
|
320
|
+
if (!binding) throw new Error("Validated Wasm assignment omitted its binding.")
|
|
321
|
+
this.setBinding(binding, statement.target, `${statementPath}/target`, "name")
|
|
322
|
+
} else if (statement.kind == "PrintStatement") {
|
|
323
|
+
const type = expressionType(statement.expression, this.bindings, this.functions)
|
|
324
|
+
|
|
325
|
+
this.expression(statement.expression, `${statementPath}/expression`)
|
|
326
|
+
const importIndex = type == "integer" ? 0 : type == "boolean" ? 1 : 2
|
|
327
|
+
|
|
328
|
+
this.mappedOp(0x10, statement, statementPath, "print instruction")
|
|
329
|
+
this.mappedImmediate(u32(importIndex), statement, statementPath, "print import index")
|
|
330
|
+
} else if (statement.kind == "ReturnStatement") {
|
|
331
|
+
if (!statement.expression) throw new TypeError("Wasm bare return reached emission.")
|
|
332
|
+
this.expression(statement.expression, `${statementPath}/expression`)
|
|
333
|
+
this.mappedOp(0x0f, statement, statementPath, "return instruction")
|
|
334
|
+
} else if (statement.kind == "ExpressionStatement") {
|
|
335
|
+
throw new TypeError("Wasm Task 005 expression statement reached emission.")
|
|
336
|
+
} else if (statement.kind == "IfStatement") {
|
|
337
|
+
this.expression(statement.condition, `${statementPath}/condition`)
|
|
338
|
+
this.mappedOp(0x04, statement.condition, `${statementPath}/condition`, "conditional instruction")
|
|
339
|
+
this.syntheticImmediate([0x40], statement, statementPath, "empty conditional block type")
|
|
340
|
+
this.block(statement.consequent, `${statementPath}/consequent`)
|
|
341
|
+
if (statement.alternate) {
|
|
342
|
+
this.mappedOp(0x05, statement, statementPath, "alternate instruction")
|
|
343
|
+
this.block(statement.alternate, `${statementPath}/alternate`)
|
|
344
|
+
}
|
|
345
|
+
this.mappedOp(0x0b, statement, statementPath, "conditional end")
|
|
346
|
+
} else {
|
|
347
|
+
throw new TypeError("Unsupported Wasm statement reached emission.")
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Emits one expression and leaves its flattened result on the operand stack.
|
|
354
|
+
* @param {Expression} expression - Semantic expression.
|
|
355
|
+
* @param {string} path - Occurrence path.
|
|
356
|
+
* @returns {void}
|
|
357
|
+
*/
|
|
358
|
+
expression(expression, path) {
|
|
359
|
+
if (expression.kind == "IdentifierExpression") {
|
|
360
|
+
const binding = this.bindings.get(expression.name)
|
|
361
|
+
|
|
362
|
+
if (!binding) throw new Error("Validated Wasm identifier omitted its binding.")
|
|
363
|
+
for (const localIndex of binding.indexes) {
|
|
364
|
+
this.mappedOp(0x20, expression, path, "local read", "name")
|
|
365
|
+
this.mappedImmediate(u32(localIndex), expression, path, "local index", "name")
|
|
366
|
+
}
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
if (expression.kind == "IntegerLiteral") {
|
|
370
|
+
this.mappedOp(0x42, expression, path, "i64 constant", "literal")
|
|
371
|
+
this.mappedImmediate(s64(BigInt(expression.value)), expression, path, "i64 immediate", "literal")
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
if (expression.kind == "BooleanLiteral") {
|
|
375
|
+
this.mappedOp(0x41, expression, path, "canonical Boolean constant", "literal")
|
|
376
|
+
this.mappedImmediate(s32(expression.value ? 1 : 0), expression, path, "i32 immediate", "literal")
|
|
377
|
+
return
|
|
378
|
+
}
|
|
379
|
+
if (expression.kind == "StringLiteral") {
|
|
380
|
+
const literal = this.literals.get(expression.value)
|
|
381
|
+
|
|
382
|
+
if (!literal) throw new Error("Validated Wasm string literal omitted its static layout.")
|
|
383
|
+
this.mappedOp(0x41, expression, path, "string pointer constant", "literal")
|
|
384
|
+
this.mappedImmediate(s32(literal.address), expression, path, "string pointer immediate", "literal")
|
|
385
|
+
this.mappedOp(0x41, expression, path, "string length constant", "literal")
|
|
386
|
+
this.mappedImmediate(s32(literal.bytes.byteLength), expression, path, "string length immediate", "literal")
|
|
387
|
+
return
|
|
388
|
+
}
|
|
389
|
+
if (expression.kind == "CallExpression") {
|
|
390
|
+
for (let index = 0; index < expression.arguments.length; index += 1) {
|
|
391
|
+
this.expression(expression.arguments[index], `${path}/arguments/${index}`)
|
|
392
|
+
}
|
|
393
|
+
this.semanticCall(expression, path)
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
if (expression.kind == "UnaryExpression") {
|
|
397
|
+
this.expression(expression.operand, `${path}/operand`)
|
|
398
|
+
if (expression.operation == "BooleanNot") this.mappedOp(0x45, expression, path, "Boolean not instruction", "operator")
|
|
399
|
+
else {
|
|
400
|
+
this.mappedOp(0x10, expression, path, "checked integer negate instruction", "operator")
|
|
401
|
+
this.mappedImmediate(u32(checkedNegateFunctionIndex), expression, path, "checked helper index", "operator")
|
|
402
|
+
}
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
if (expression.kind != "BinaryExpression") {
|
|
406
|
+
return unsupportedCapability("wasm", "collection expression reached scalar emission", expression.location)
|
|
407
|
+
}
|
|
408
|
+
if (expression.operation == "BooleanAnd" || expression.operation == "BooleanOr") {
|
|
409
|
+
this.expression(expression.left, `${path}/left`)
|
|
410
|
+
this.mappedOp(0x04, expression, path, "short-circuit conditional", "operator")
|
|
411
|
+
this.syntheticImmediate([valueTypes.i32], expression, path, "Boolean conditional result type")
|
|
412
|
+
if (expression.operation == "BooleanAnd") {
|
|
413
|
+
this.expression(expression.right, `${path}/right`)
|
|
414
|
+
this.mappedOp(0x05, expression, path, "short-circuit alternate", "operator")
|
|
415
|
+
this.syntheticI32(0, expression, path, "canonical false result")
|
|
416
|
+
} else {
|
|
417
|
+
this.syntheticI32(1, expression, path, "canonical true result")
|
|
418
|
+
this.mappedOp(0x05, expression, path, "short-circuit alternate", "operator")
|
|
419
|
+
this.expression(expression.right, `${path}/right`)
|
|
420
|
+
}
|
|
421
|
+
this.mappedOp(0x0b, expression, path, "short-circuit end", "operator")
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.expression(expression.left, `${path}/left`)
|
|
426
|
+
this.expression(expression.right, `${path}/right`)
|
|
427
|
+
const directOpcode = new Map([
|
|
428
|
+
["IntegerEqual", 0x51], ["IntegerNotEqual", 0x52], ["IntegerLessThan", 0x53],
|
|
429
|
+
["IntegerGreaterThan", 0x55], ["IntegerLessThanOrEqual", 0x57], ["IntegerGreaterThanOrEqual", 0x59],
|
|
430
|
+
["BooleanEqual", 0x46], ["BooleanNotEqual", 0x47]
|
|
431
|
+
]).get(expression.operation)
|
|
432
|
+
|
|
433
|
+
if (directOpcode !== undefined) this.mappedOp(directOpcode, expression, path, `${expression.operation} instruction`, "operator")
|
|
434
|
+
else {
|
|
435
|
+
const helperIndex = expression.operation == "StringConcat" ? concatFunctionIndex :
|
|
436
|
+
expression.operation == "StringEqual" || expression.operation == "StringNotEqual" ? stringEqualFunctionIndex :
|
|
437
|
+
expression.operation == "IntegerAdd" ? checkedAddFunctionIndex :
|
|
438
|
+
expression.operation == "IntegerSubtract" ? checkedSubtractFunctionIndex : checkedMultiplyFunctionIndex
|
|
439
|
+
|
|
440
|
+
this.mappedOp(0x10, expression, path, `${expression.operation} helper call`, "operator")
|
|
441
|
+
this.mappedImmediate(u32(helperIndex), expression, path, "helper function index", "operator")
|
|
442
|
+
if (expression.operation == "StringNotEqual") this.mappedOp(0x45, expression, path, "String inequality inversion", "operator")
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Emits checked semantic-call depth accounting without changing argument order.
|
|
448
|
+
* @param {import("../semantic/types.js").CallExpression} expression - Call expression.
|
|
449
|
+
* @param {string} path - Call occurrence path.
|
|
450
|
+
* @returns {void}
|
|
451
|
+
*/
|
|
452
|
+
semanticCall(expression, path) {
|
|
453
|
+
const declaration = this.functions.get(expression.callee)
|
|
454
|
+
const functionIndex = this.functionIndexes.get(expression.callee)
|
|
455
|
+
|
|
456
|
+
if (!declaration || functionIndex === undefined) throw new Error("Validated Wasm call omitted its declaration.")
|
|
457
|
+
this.syntheticOp(0x23, expression, path, "read semantic call depth")
|
|
458
|
+
this.syntheticImmediate(u32(depthGlobalIndex), expression, path, "call-depth global index")
|
|
459
|
+
this.syntheticI32(maximumCallDepth, expression, path, "maximum semantic call depth")
|
|
460
|
+
this.syntheticOp(0x4f, expression, path, "semantic call-depth limit comparison")
|
|
461
|
+
this.syntheticOp(0x04, expression, path, "semantic call-depth guard")
|
|
462
|
+
this.syntheticImmediate([0x40], expression, path, "empty call-depth guard block type")
|
|
463
|
+
this.syntheticOp(0x00, expression, path, "semantic call-depth trap")
|
|
464
|
+
this.syntheticOp(0x0b, expression, path, "semantic call-depth guard end")
|
|
465
|
+
this.syntheticOp(0x23, expression, path, "read semantic call depth")
|
|
466
|
+
this.syntheticImmediate(u32(depthGlobalIndex), expression, path, "call-depth global index")
|
|
467
|
+
this.syntheticI32(1, expression, path, "semantic call-depth increment")
|
|
468
|
+
this.syntheticOp(0x6a, expression, path, "increment semantic call depth")
|
|
469
|
+
this.syntheticGlobalSet(depthGlobalIndex, expression, path, "call depth")
|
|
470
|
+
this.mappedOp(0x10, expression, path, "semantic call instruction", "callee")
|
|
471
|
+
this.mappedImmediate(u32(functionIndex), expression, path, "semantic callee index", "callee")
|
|
472
|
+
|
|
473
|
+
const result = this.allocateBinding(wasmReturnType(declaration))
|
|
474
|
+
|
|
475
|
+
this.setBinding(result, expression, path)
|
|
476
|
+
this.syntheticOp(0x23, expression, path, "read semantic call depth")
|
|
477
|
+
this.syntheticImmediate(u32(depthGlobalIndex), expression, path, "call-depth global index")
|
|
478
|
+
this.syntheticI32(1, expression, path, "semantic call-depth decrement")
|
|
479
|
+
this.syntheticOp(0x6b, expression, path, "decrement semantic call depth")
|
|
480
|
+
this.syntheticGlobalSet(depthGlobalIndex, expression, path, "call depth")
|
|
481
|
+
for (const localIndex of result.indexes) {
|
|
482
|
+
this.syntheticOp(0x20, expression, path, "restore semantic call result")
|
|
483
|
+
this.syntheticImmediate(u32(localIndex), expression, path, "result temporary local index")
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Allocates flattened Wasm locals for one semantic value.
|
|
489
|
+
* @param {Scalar} type - Semantic type.
|
|
490
|
+
* @returns {LocalBinding} Local binding.
|
|
491
|
+
*/
|
|
492
|
+
allocateBinding(type) {
|
|
493
|
+
const indexes = wasmTypes(type).map((wasmType) => {
|
|
494
|
+
const index = this.parameterCount + this.locals.length
|
|
495
|
+
|
|
496
|
+
this.locals.push(wasmType)
|
|
497
|
+
|
|
498
|
+
return index
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
return {indexes, type}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Pops a flattened value into its locals.
|
|
506
|
+
* @param {LocalBinding} binding - Destination.
|
|
507
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
508
|
+
* @param {string} path - Occurrence path.
|
|
509
|
+
* @param {string} [sourceRole] - Exact parser-owned token role, when this is a semantic binding.
|
|
510
|
+
* @returns {void}
|
|
511
|
+
*/
|
|
512
|
+
setBinding(binding, node, path, sourceRole) {
|
|
513
|
+
for (let index = binding.indexes.length - 1; index >= 0; index -= 1) {
|
|
514
|
+
if (sourceRole) {
|
|
515
|
+
this.mappedOp(0x21, node, path, "local write", sourceRole)
|
|
516
|
+
this.mappedImmediate(u32(binding.indexes[index]), node, path, "local index", sourceRole)
|
|
517
|
+
} else {
|
|
518
|
+
this.syntheticOp(0x21, node, path, "store semantic call result")
|
|
519
|
+
this.syntheticImmediate(u32(binding.indexes[index]), node, path, "result temporary local index")
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Writes a source-mapped instruction opcode.
|
|
526
|
+
* @param {number} opcode - Wasm opcode.
|
|
527
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
528
|
+
* @param {string} path - Node occurrence path.
|
|
529
|
+
* @param {string} role - Mapping role.
|
|
530
|
+
* @param {string} [sourceRole] - Exact parser-owned token role.
|
|
531
|
+
* @returns {void}
|
|
532
|
+
*/
|
|
533
|
+
mappedOp(opcode, node, path, role, sourceRole) { this.writer.mapped([opcode], node, path, role, sourceRole) }
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Writes a source-mapped instruction immediate.
|
|
537
|
+
* @param {number[]} bytes - Encoded immediate bytes.
|
|
538
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
539
|
+
* @param {string} path - Node occurrence path.
|
|
540
|
+
* @param {string} role - Mapping role.
|
|
541
|
+
* @param {string} [sourceRole] - Exact parser-owned token role.
|
|
542
|
+
* @returns {void}
|
|
543
|
+
*/
|
|
544
|
+
mappedImmediate(bytes, node, path, role, sourceRole) { this.writer.mapped(bytes, node, path, role, sourceRole) }
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Writes a synthetic instruction opcode with semantic context.
|
|
548
|
+
* @param {number} opcode - Wasm opcode.
|
|
549
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
550
|
+
* @param {string} path - Node occurrence path.
|
|
551
|
+
* @param {string} reason - Synthetic provenance reason.
|
|
552
|
+
* @returns {void}
|
|
553
|
+
*/
|
|
554
|
+
syntheticOp(opcode, node, path, reason) { this.writer.synthetic([opcode], reason, {node, path}, "instruction") }
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Writes a synthetic immediate with semantic context.
|
|
558
|
+
* @param {number[]} bytes - Encoded immediate bytes.
|
|
559
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
560
|
+
* @param {string} path - Node occurrence path.
|
|
561
|
+
* @param {string} reason - Synthetic provenance reason.
|
|
562
|
+
* @returns {void}
|
|
563
|
+
*/
|
|
564
|
+
syntheticImmediate(bytes, node, path, reason) { this.writer.synthetic(bytes, reason, {node, path}, "immediate") }
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Writes a synthetic i32 constant instruction.
|
|
568
|
+
* @param {number} value - Constant value.
|
|
569
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
570
|
+
* @param {string} path - Node occurrence path.
|
|
571
|
+
* @param {string} reason - Synthetic provenance reason.
|
|
572
|
+
* @returns {void}
|
|
573
|
+
*/
|
|
574
|
+
syntheticI32(value, node, path, reason) {
|
|
575
|
+
this.syntheticOp(0x41, node, path, reason)
|
|
576
|
+
this.syntheticImmediate(s32(value), node, path, `${reason} immediate`)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Writes a synthetic global assignment.
|
|
581
|
+
* @param {number} index - Global index.
|
|
582
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
583
|
+
* @param {string} path - Node occurrence path.
|
|
584
|
+
* @param {string} reason - Synthetic provenance reason.
|
|
585
|
+
* @returns {void}
|
|
586
|
+
*/
|
|
587
|
+
syntheticGlobalSet(index, node, path, reason) {
|
|
588
|
+
this.syntheticOp(0x24, node, path, `set ${reason}`)
|
|
589
|
+
this.syntheticImmediate(u32(index), node, path, `${reason} global index`)
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Writes a synthetic function-end instruction.
|
|
594
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
595
|
+
* @param {string} path - Node occurrence path.
|
|
596
|
+
* @returns {void}
|
|
597
|
+
*/
|
|
598
|
+
syntheticEnd(node, path) { this.syntheticOp(0x0b, node, path, "function end") }
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Writes a synthetic unreachable instruction.
|
|
602
|
+
* @param {SemanticNode} node - Related semantic node.
|
|
603
|
+
* @param {string} path - Node occurrence path.
|
|
604
|
+
* @returns {void}
|
|
605
|
+
*/
|
|
606
|
+
syntheticUnreachable(node, path) { this.syntheticOp(0x00, node, path, "validated non-void fallthrough trap") }
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/** Byte writer that preserves one non-overlapping provenance annotation per write. */
|
|
610
|
+
class BinaryWriter {
|
|
611
|
+
/**
|
|
612
|
+
* Creates an annotated binary writer.
|
|
613
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Canonical provenance index.
|
|
614
|
+
*/
|
|
615
|
+
constructor(index) {
|
|
616
|
+
this.index = index
|
|
617
|
+
/** @type {number[]} */
|
|
618
|
+
this.bytes = []
|
|
619
|
+
/** @type {GeneratedByteRange[]} */
|
|
620
|
+
this.ranges = []
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Writes source-backed bytes.
|
|
625
|
+
* @param {number[] | Uint8Array} bytes - Exact bytes.
|
|
626
|
+
* @param {SemanticNode} node - Semantic node.
|
|
627
|
+
* @param {string} path - Occurrence path.
|
|
628
|
+
* @param {string} role - Binary role.
|
|
629
|
+
* @param {string} [sourceRole] - Exact parser-owned token role.
|
|
630
|
+
* @returns {void}
|
|
631
|
+
*/
|
|
632
|
+
mapped(bytes, node, path, role, sourceRole) {
|
|
633
|
+
const record = this.index.recordFor(node, path)
|
|
634
|
+
const origin = sourceRole ? originForRole(this.index, record, sourceRole) : record.origin
|
|
635
|
+
|
|
636
|
+
this.write(bytes, {nodeId: record.id, origin, role, symbolId: record.symbolId})
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Writes explicit backend scaffolding bytes.
|
|
641
|
+
* @param {number[] | Uint8Array} bytes - Exact bytes.
|
|
642
|
+
* @param {string} reason - Synthetic reason.
|
|
643
|
+
* @param {{node: SemanticNode, path: string}} related - Related occurrence.
|
|
644
|
+
* @param {string} role - Binary role.
|
|
645
|
+
* @returns {void}
|
|
646
|
+
*/
|
|
647
|
+
synthetic(bytes, reason, related, role) {
|
|
648
|
+
const record = this.index.recordFor(related.node, related.path)
|
|
649
|
+
|
|
650
|
+
this.write(bytes, {
|
|
651
|
+
origin: {kind: "synthetic", reason, relatedOrigins: relatedOrigins(record)},
|
|
652
|
+
role
|
|
653
|
+
})
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Appends another annotated writer.
|
|
658
|
+
* @param {BinaryWriter} child - Child bytes.
|
|
659
|
+
* @returns {void}
|
|
660
|
+
*/
|
|
661
|
+
append(child) {
|
|
662
|
+
const offset = this.bytes.length
|
|
663
|
+
|
|
664
|
+
for (const byte of child.bytes) this.bytes.push(byte)
|
|
665
|
+
for (const range of child.ranges) {
|
|
666
|
+
this.ranges.push({...range, generated: {start: range.generated.start + offset, end: range.generated.end + offset}})
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Returns detached encoded bytes.
|
|
672
|
+
* @returns {Uint8Array} Detached bytes.
|
|
673
|
+
*/
|
|
674
|
+
finish() { return Uint8Array.from(this.bytes) }
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Writes one non-empty annotated range.
|
|
678
|
+
* @param {number[] | Uint8Array} bytes - Exact bytes.
|
|
679
|
+
* @param {Omit<GeneratedByteRange, "generated">} annotation - Provenance.
|
|
680
|
+
* @returns {void}
|
|
681
|
+
*/
|
|
682
|
+
write(bytes, annotation) {
|
|
683
|
+
if (bytes.length == 0) return
|
|
684
|
+
const start = this.bytes.length
|
|
685
|
+
|
|
686
|
+
for (const byte of bytes) {
|
|
687
|
+
if (!Number.isInteger(byte) || byte < 0 || byte > 255) throw new RangeError("Invalid WebAssembly byte.")
|
|
688
|
+
this.bytes.push(byte)
|
|
689
|
+
}
|
|
690
|
+
this.ranges.push({...annotation, generated: {start, end: this.bytes.length}})
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Deterministic function-type interner. */
|
|
695
|
+
class TypePool {
|
|
696
|
+
constructor() {
|
|
697
|
+
/** @type {{parameters: ("i32" | "i64")[], results: ("i32" | "i64")[]}[]} */
|
|
698
|
+
this.entries = []
|
|
699
|
+
/** @type {Map<string, number>} */
|
|
700
|
+
this.indexes = new Map()
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Interns one function type.
|
|
705
|
+
* @param {("i32" | "i64")[]} parameters - Parameters.
|
|
706
|
+
* @param {("i32" | "i64")[]} results - Results.
|
|
707
|
+
* @returns {number} Type index.
|
|
708
|
+
*/
|
|
709
|
+
add(parameters, results) {
|
|
710
|
+
const key = `${parameters.join(",")}->${results.join(",")}`
|
|
711
|
+
const prior = this.indexes.get(key)
|
|
712
|
+
|
|
713
|
+
if (prior !== undefined) return prior
|
|
714
|
+
const index = this.entries.length
|
|
715
|
+
|
|
716
|
+
this.entries.push({parameters, results})
|
|
717
|
+
this.indexes.set(key, index)
|
|
718
|
+
|
|
719
|
+
return index
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Encodes the function-type section payload.
|
|
725
|
+
* @param {TypePool} types - Interned function types.
|
|
726
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
727
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
728
|
+
* @returns {BinaryWriter} Encoded payload.
|
|
729
|
+
*/
|
|
730
|
+
function encodeTypeSection(types, index, root) {
|
|
731
|
+
const writer = new BinaryWriter(index)
|
|
732
|
+
|
|
733
|
+
writer.synthetic(u32(types.entries.length), "Function type count", root, "type count")
|
|
734
|
+
for (const type of types.entries) {
|
|
735
|
+
writer.synthetic([0x60], "Function type form", root, "type form")
|
|
736
|
+
writer.synthetic(u32(type.parameters.length), "Function parameter count", root, "type parameter count")
|
|
737
|
+
for (const parameter of type.parameters) writer.synthetic([valueTypes[parameter]], "Function parameter value type", root, "value type")
|
|
738
|
+
writer.synthetic(u32(type.results.length), "Function result count", root, "type result count")
|
|
739
|
+
for (const result of type.results) writer.synthetic([valueTypes[result]], "Function result value type", root, "value type")
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return writer
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Encodes the fixed ABI import section payload.
|
|
747
|
+
* @param {number[]} typeIndexes - Import function type indexes.
|
|
748
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
749
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
750
|
+
* @returns {BinaryWriter} Encoded payload.
|
|
751
|
+
*/
|
|
752
|
+
function encodeImportSection(typeIndexes, index, root) {
|
|
753
|
+
const writer = new BinaryWriter(index)
|
|
754
|
+
const names = ["print_i64", "print_bool", "print_string"]
|
|
755
|
+
|
|
756
|
+
writer.synthetic(u32(names.length), "ABI import count", root, "import count")
|
|
757
|
+
for (let offset = 0; offset < names.length; offset += 1) {
|
|
758
|
+
writer.synthetic(nameBytes("semantifold"), "ABI import module name", root, "import module")
|
|
759
|
+
writer.synthetic(nameBytes(names[offset]), "ABI import function name", root, "import name")
|
|
760
|
+
writer.synthetic([0x00], "Function import kind", root, "import kind")
|
|
761
|
+
writer.synthetic(u32(typeIndexes[offset]), "ABI import type index", root, "type index")
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return writer
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Encodes defined-function type indexes.
|
|
769
|
+
* @param {number[]} helperTypes - Private helper type indexes.
|
|
770
|
+
* @param {number[]} semanticTypes - Semantic function type indexes.
|
|
771
|
+
* @param {number} runType - Exported run function type index.
|
|
772
|
+
* @param {SemanticModule} module - Semantic module.
|
|
773
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
774
|
+
* @returns {BinaryWriter} Encoded payload.
|
|
775
|
+
*/
|
|
776
|
+
function encodeFunctionSection(helperTypes, semanticTypes, runType, module, index) {
|
|
777
|
+
const writer = new BinaryWriter(index)
|
|
778
|
+
const root = {node: module, path: ""}
|
|
779
|
+
|
|
780
|
+
writer.synthetic(u32(helperTypes.length + semanticTypes.length + 1), "Defined function count", root, "function count")
|
|
781
|
+
for (const typeIndex of helperTypes) writer.synthetic(u32(typeIndex), "Private ABI helper type index", root, "type index")
|
|
782
|
+
for (let offset = 0; offset < semanticTypes.length; offset += 1) {
|
|
783
|
+
const declaration = module.functions[offset]
|
|
784
|
+
|
|
785
|
+
writer.synthetic(u32(semanticTypes[offset]), "Semantic function type index", {node: declaration, path: `/functions/${offset}`}, "type index")
|
|
786
|
+
}
|
|
787
|
+
writer.synthetic(u32(runType), "Exported run type index", {node: module.entryPoint, path: "/entryPoint"}, "type index")
|
|
788
|
+
|
|
789
|
+
return writer
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Encodes one fixed-memory declaration.
|
|
794
|
+
* @param {number} pages - Equal initial and maximum page count.
|
|
795
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
796
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
797
|
+
* @returns {BinaryWriter} Encoded payload.
|
|
798
|
+
*/
|
|
799
|
+
function encodeMemorySection(pages, index, root) {
|
|
800
|
+
const writer = new BinaryWriter(index)
|
|
801
|
+
|
|
802
|
+
writer.synthetic(u32(1), "Single fixed linear memory", root, "memory count")
|
|
803
|
+
writer.synthetic([0x01], "Memory limits include equal maximum", root, "memory limits flags")
|
|
804
|
+
writer.synthetic(u32(pages), "Initial memory page count", root, "memory initial pages")
|
|
805
|
+
writer.synthetic(u32(pages), "Maximum memory page count", root, "memory maximum pages")
|
|
806
|
+
|
|
807
|
+
return writer
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Encodes private mutable ABI globals.
|
|
812
|
+
* @param {number} scratchBase - Initial scratch cursor.
|
|
813
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
814
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
815
|
+
* @returns {BinaryWriter} Encoded payload.
|
|
816
|
+
*/
|
|
817
|
+
function encodeGlobalSection(scratchBase, index, root) {
|
|
818
|
+
const writer = new BinaryWriter(index)
|
|
819
|
+
const globals = [
|
|
820
|
+
{initial: scratchBase, reason: "Private scratch cursor"},
|
|
821
|
+
{initial: 0, reason: "Private semantic call depth"},
|
|
822
|
+
{initial: 0, reason: "Private run active guard"}
|
|
823
|
+
]
|
|
824
|
+
|
|
825
|
+
writer.synthetic(u32(globals.length), "Private mutable global count", root, "global count")
|
|
826
|
+
for (const global of globals) {
|
|
827
|
+
writer.synthetic([valueTypes.i32, 0x01], `${global.reason} type and mutability`, root, "global type")
|
|
828
|
+
writer.synthetic([0x41], `${global.reason} initializer instruction`, root, "instruction")
|
|
829
|
+
writer.synthetic(s32(global.initial), `${global.reason} initializer immediate`, root, "immediate")
|
|
830
|
+
writer.synthetic([0x0b], `${global.reason} initializer end`, root, "instruction")
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return writer
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Encodes exactly the public memory and run exports.
|
|
838
|
+
* @param {number} runIndex - Exported run function index.
|
|
839
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
840
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
841
|
+
* @returns {BinaryWriter} Encoded payload.
|
|
842
|
+
*/
|
|
843
|
+
function encodeExportSection(runIndex, index, root) {
|
|
844
|
+
const writer = new BinaryWriter(index)
|
|
845
|
+
|
|
846
|
+
writer.synthetic(u32(2), "Exact ABI export count", root, "export count")
|
|
847
|
+
writer.synthetic(nameBytes("memory"), "Exported memory name", root, "export name")
|
|
848
|
+
writer.synthetic([0x02], "Memory export kind", root, "export kind")
|
|
849
|
+
writer.synthetic(u32(0), "Exported memory index", root, "memory index")
|
|
850
|
+
writer.synthetic(nameBytes("run"), "Exported run name", root, "export name")
|
|
851
|
+
writer.synthetic([0x00], "Function export kind", root, "export kind")
|
|
852
|
+
writer.synthetic(u32(runIndex), "Exported run function index", root, "function index")
|
|
853
|
+
|
|
854
|
+
return writer
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Encodes immutable UTF-8 literal data segments.
|
|
859
|
+
* @param {ReturnType<typeof collectLiteralLayout>["entries"]} entries - Ordered literal entries.
|
|
860
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
861
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
862
|
+
* @returns {BinaryWriter} Encoded payload.
|
|
863
|
+
*/
|
|
864
|
+
function encodeDataSection(entries, index, root) {
|
|
865
|
+
const writer = new BinaryWriter(index)
|
|
866
|
+
const nonempty = entries.filter(({bytes}) => bytes.byteLength > 0)
|
|
867
|
+
|
|
868
|
+
writer.synthetic(u32(nonempty.length), "Immutable literal data segment count", root, "data segment count")
|
|
869
|
+
for (const entry of nonempty) {
|
|
870
|
+
writer.synthetic([0x00, 0x41], "Active literal data segment and offset instruction", root, "data segment header")
|
|
871
|
+
writer.synthetic(s32(entry.address), "Literal data address", root, "data segment offset")
|
|
872
|
+
writer.synthetic([0x0b], "Literal offset expression end", root, "instruction")
|
|
873
|
+
writer.synthetic(u32(entry.bytes.byteLength), "Literal data byte length", root, "data segment length")
|
|
874
|
+
writer.write(entry.bytes, {
|
|
875
|
+
origin: entry.origin,
|
|
876
|
+
role: "immutable UTF-8 literal data"
|
|
877
|
+
})
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return writer
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Encodes deterministic internal and semantic function names.
|
|
885
|
+
* @param {SemanticModule} module - Semantic module.
|
|
886
|
+
* @param {number} runIndex - Exported run function index.
|
|
887
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
888
|
+
* @returns {BinaryWriter} Encoded custom-section payload.
|
|
889
|
+
*/
|
|
890
|
+
function encodeNamePayload(module, runIndex, index) {
|
|
891
|
+
const writer = new BinaryWriter(index)
|
|
892
|
+
const root = {node: module, path: ""}
|
|
893
|
+
const subsection = new BinaryWriter(index)
|
|
894
|
+
/** @type {{declaration?: import("../semantic/types.js").FunctionDeclaration, index: number, name: string, path?: string}[]} */
|
|
895
|
+
const names = [
|
|
896
|
+
...helperNames.map((name, offset) => ({index: importCount + offset, name})),
|
|
897
|
+
...module.functions.map((declaration, offset) => ({declaration, index: importCount + helperCount + offset, name: declaration.name, path: `/functions/${offset}`})),
|
|
898
|
+
{index: runIndex, name: "run"}
|
|
899
|
+
]
|
|
900
|
+
|
|
901
|
+
subsection.synthetic(u32(names.length), "Named function count", root, "name count")
|
|
902
|
+
for (const name of names) {
|
|
903
|
+
subsection.synthetic(u32(name.index), "Named function index", root, "function index")
|
|
904
|
+
if (name.declaration) {
|
|
905
|
+
const bytes = encoder.encode(name.name)
|
|
906
|
+
const related = {node: name.declaration, path: /** @type {string} */ (name.path)}
|
|
907
|
+
|
|
908
|
+
subsection.synthetic(u32(bytes.byteLength), "Semantic function name byte length", related, "function name length")
|
|
909
|
+
subsection.mapped(bytes, name.declaration, related.path, "semantic function name", "name")
|
|
910
|
+
} else subsection.synthetic(nameBytes(name.name), "Private ABI function name", root, "function name")
|
|
911
|
+
}
|
|
912
|
+
writer.synthetic([0x01], "Function names subsection ID", root, "custom subsection id")
|
|
913
|
+
writer.synthetic(u32(subsection.bytes.length), "Function names subsection size", root, "custom subsection size")
|
|
914
|
+
writer.append(subsection)
|
|
915
|
+
|
|
916
|
+
return writer
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Encodes one source-map URL custom-section payload.
|
|
921
|
+
* @param {string} content - Relative map URL.
|
|
922
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
923
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
924
|
+
* @returns {BinaryWriter} Encoded payload.
|
|
925
|
+
*/
|
|
926
|
+
function namedPayload(content, index, root) {
|
|
927
|
+
const writer = new BinaryWriter(index)
|
|
928
|
+
|
|
929
|
+
writer.synthetic(encoder.encode(content), "Relative external Wasm source-map URL", root, "custom section payload")
|
|
930
|
+
|
|
931
|
+
return writer
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* Appends one sized module section.
|
|
936
|
+
* @param {BinaryWriter} moduleWriter - Complete module writer.
|
|
937
|
+
* @param {number} id - Core section ID.
|
|
938
|
+
* @param {string} name - Diagnostic section name.
|
|
939
|
+
* @param {BinaryWriter} payload - Encoded section payload.
|
|
940
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
941
|
+
* @returns {void}
|
|
942
|
+
*/
|
|
943
|
+
function appendSection(moduleWriter, id, name, payload, root) {
|
|
944
|
+
moduleWriter.synthetic([id], `${name} section ID`, root, `${name} section`)
|
|
945
|
+
moduleWriter.synthetic(u32(payload.bytes.length), `${name} section byte length`, root, `${name} section length`)
|
|
946
|
+
moduleWriter.append(payload)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Appends one named custom section.
|
|
951
|
+
* @param {BinaryWriter} writer - Complete module writer.
|
|
952
|
+
* @param {string} name - Custom-section name.
|
|
953
|
+
* @param {BinaryWriter} payload - Custom-section payload.
|
|
954
|
+
* @param {{node: SemanticNode, path: string}} root - Related module occurrence.
|
|
955
|
+
* @returns {void}
|
|
956
|
+
*/
|
|
957
|
+
function appendCustomSection(writer, name, payload, root) {
|
|
958
|
+
const complete = new BinaryWriter(writer.index)
|
|
959
|
+
|
|
960
|
+
complete.synthetic(nameBytes(name), `Custom section name '${name}'`, root, "custom section name")
|
|
961
|
+
complete.append(payload)
|
|
962
|
+
appendSection(writer, 0, `custom ${name}`, complete, root)
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* Appends one sized function body and its flattened locals.
|
|
967
|
+
* @param {BinaryWriter} code - Code-section payload writer.
|
|
968
|
+
* @param {BinaryWriter} instructions - Function instructions.
|
|
969
|
+
* @param {("i32" | "i64")[]} locals - Flattened local types.
|
|
970
|
+
* @param {{node: SemanticNode, path: string}} related - Related semantic occurrence.
|
|
971
|
+
* @param {string} name - Function name for provenance.
|
|
972
|
+
* @returns {void}
|
|
973
|
+
*/
|
|
974
|
+
function appendFunctionBody(code, instructions, locals, related, name) {
|
|
975
|
+
const body = new BinaryWriter(code.index)
|
|
976
|
+
|
|
977
|
+
body.synthetic(u32(locals.length), `Local declaration group count for ${name}`, related, "function locals")
|
|
978
|
+
for (const local of locals) {
|
|
979
|
+
body.synthetic(u32(1), `Local count for ${name}`, related, "local count")
|
|
980
|
+
body.synthetic([valueTypes[local]], `Local value type for ${name}`, related, "local type")
|
|
981
|
+
}
|
|
982
|
+
body.append(instructions)
|
|
983
|
+
code.synthetic(u32(body.bytes.length), `Function body byte length for ${name}`, related, "function body length")
|
|
984
|
+
code.append(body)
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Encodes checked scratch allocation and string concatenation.
|
|
989
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
990
|
+
* @param {SemanticModule} module - Semantic module.
|
|
991
|
+
* @param {number} scratchBase - Arena start address.
|
|
992
|
+
* @returns {BinaryWriter} Encoded helper body.
|
|
993
|
+
*/
|
|
994
|
+
function helperConcat(index, module, scratchBase) {
|
|
995
|
+
const e = new SyntheticEmitter(index, module, "$concat")
|
|
996
|
+
const total = 4
|
|
997
|
+
const start = 5
|
|
998
|
+
const end = 6
|
|
999
|
+
const cursor = 7
|
|
1000
|
+
|
|
1001
|
+
e.localGet(1); e.localGet(3); e.op(0x6a, "add string lengths"); e.localTee(total)
|
|
1002
|
+
e.localGet(1); e.op(0x49, "detect unsigned string-length overflow"); e.trapIf("string-length overflow")
|
|
1003
|
+
e.globalGet(cursorGlobalIndex); e.localTee(start)
|
|
1004
|
+
e.localGet(total); e.op(0x6a, "add scratch cursor and allocation length"); e.localTee(end)
|
|
1005
|
+
e.localGet(start); e.op(0x49, "detect unsigned scratch-cursor overflow"); e.trapIf("scratch cursor overflow")
|
|
1006
|
+
e.localGet(end); e.i32(scratchBase + scratchCapacity); e.op(0x4b, "compare allocation end with arena end"); e.trapIf("scratch capacity exceeded")
|
|
1007
|
+
e.i32(0); e.localSet(cursor)
|
|
1008
|
+
e.copyLoop(cursor, 0, 1, start, undefined)
|
|
1009
|
+
e.i32(0); e.localSet(cursor)
|
|
1010
|
+
e.copyLoop(cursor, 2, 3, start, 1)
|
|
1011
|
+
e.localGet(end); e.globalSet(cursorGlobalIndex)
|
|
1012
|
+
e.localGet(start); e.localGet(total); e.end()
|
|
1013
|
+
|
|
1014
|
+
return e.writer
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Encodes byte-exact string equality.
|
|
1019
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
1020
|
+
* @param {SemanticModule} module - Semantic module.
|
|
1021
|
+
* @returns {BinaryWriter} Encoded helper body.
|
|
1022
|
+
*/
|
|
1023
|
+
function helperStringEqual(index, module) {
|
|
1024
|
+
const e = new SyntheticEmitter(index, module, "$string_equal")
|
|
1025
|
+
const cursor = 4
|
|
1026
|
+
|
|
1027
|
+
e.localGet(1); e.localGet(3); e.op(0x47, "compare string lengths")
|
|
1028
|
+
e.op(0x04, "branch for unequal string lengths"); e.immediate([0x40], "empty block type")
|
|
1029
|
+
e.i32(0); e.op(0x0f, "return false for unequal lengths"); e.op(0x0b, "end length branch")
|
|
1030
|
+
e.i32(0); e.localSet(cursor)
|
|
1031
|
+
e.op(0x02, "string comparison exit block"); e.immediate([0x40], "empty block type")
|
|
1032
|
+
e.op(0x03, "string comparison loop"); e.immediate([0x40], "empty block type")
|
|
1033
|
+
e.localGet(cursor); e.localGet(1); e.op(0x4f, "compare cursor with string length")
|
|
1034
|
+
e.op(0x0d, "exit completed comparison"); e.immediate(u32(1), "comparison block depth")
|
|
1035
|
+
e.localGet(0); e.localGet(cursor); e.op(0x6a, "left byte address"); e.load8()
|
|
1036
|
+
e.localGet(2); e.localGet(cursor); e.op(0x6a, "right byte address"); e.load8()
|
|
1037
|
+
e.op(0x47, "compare UTF-8 bytes")
|
|
1038
|
+
e.op(0x04, "branch for unequal UTF-8 bytes"); e.immediate([0x40], "empty block type")
|
|
1039
|
+
e.i32(0); e.op(0x0f, "return false for unequal bytes"); e.op(0x0b, "end byte branch")
|
|
1040
|
+
e.localGet(cursor); e.i32(1); e.op(0x6a, "increment comparison cursor"); e.localSet(cursor)
|
|
1041
|
+
e.op(0x0c, "continue string comparison"); e.immediate(u32(0), "comparison loop depth")
|
|
1042
|
+
e.op(0x0b, "end comparison loop"); e.op(0x0b, "end comparison block")
|
|
1043
|
+
e.i32(1); e.end()
|
|
1044
|
+
|
|
1045
|
+
return e.writer
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Encodes checked signed i64 addition.
|
|
1050
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
1051
|
+
* @param {SemanticModule} module - Semantic module.
|
|
1052
|
+
* @returns {BinaryWriter} Encoded helper body.
|
|
1053
|
+
*/
|
|
1054
|
+
function helperCheckedAdd(index, module) {
|
|
1055
|
+
const e = new SyntheticEmitter(index, module, "$checked_add")
|
|
1056
|
+
|
|
1057
|
+
e.localGet(0); e.localGet(1); e.op(0x7c, "signed i64 addition"); e.localSet(2)
|
|
1058
|
+
e.localGet(0); e.localGet(2); e.op(0x85, "addition overflow xor")
|
|
1059
|
+
e.localGet(1); e.localGet(2); e.op(0x85, "addition overflow xor")
|
|
1060
|
+
e.op(0x83, "addition overflow mask"); e.i64(0n); e.op(0x53, "addition overflow sign test"); e.trapIf("signed i64 addition overflow")
|
|
1061
|
+
e.localGet(2); e.end()
|
|
1062
|
+
|
|
1063
|
+
return e.writer
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Encodes checked signed i64 subtraction.
|
|
1068
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
1069
|
+
* @param {SemanticModule} module - Semantic module.
|
|
1070
|
+
* @returns {BinaryWriter} Encoded helper body.
|
|
1071
|
+
*/
|
|
1072
|
+
function helperCheckedSubtract(index, module) {
|
|
1073
|
+
const e = new SyntheticEmitter(index, module, "$checked_subtract")
|
|
1074
|
+
|
|
1075
|
+
e.localGet(0); e.localGet(1); e.op(0x7d, "signed i64 subtraction"); e.localSet(2)
|
|
1076
|
+
e.localGet(0); e.localGet(1); e.op(0x85, "subtraction overflow xor")
|
|
1077
|
+
e.localGet(0); e.localGet(2); e.op(0x85, "subtraction overflow xor")
|
|
1078
|
+
e.op(0x83, "subtraction overflow mask"); e.i64(0n); e.op(0x53, "subtraction overflow sign test"); e.trapIf("signed i64 subtraction overflow")
|
|
1079
|
+
e.localGet(2); e.end()
|
|
1080
|
+
|
|
1081
|
+
return e.writer
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Encodes checked signed i64 multiplication.
|
|
1086
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
1087
|
+
* @param {SemanticModule} module - Semantic module.
|
|
1088
|
+
* @returns {BinaryWriter} Encoded helper body.
|
|
1089
|
+
*/
|
|
1090
|
+
function helperCheckedMultiply(index, module) {
|
|
1091
|
+
const e = new SyntheticEmitter(index, module, "$checked_multiply")
|
|
1092
|
+
|
|
1093
|
+
e.localGet(0); e.op(0x50, "left operand zero test")
|
|
1094
|
+
e.localGet(1); e.op(0x50, "right operand zero test"); e.op(0x72, "zero operand combination")
|
|
1095
|
+
e.op(0x04, "zero multiplication branch"); e.immediate([valueTypes.i64], "i64 branch result type")
|
|
1096
|
+
e.i64(0n)
|
|
1097
|
+
e.op(0x05, "nonzero multiplication branch")
|
|
1098
|
+
e.localGet(0); e.i64(-1n); e.op(0x51, "left negative-one test")
|
|
1099
|
+
e.localGet(1); e.i64(-9223372036854775808n); e.op(0x51, "right minimum test"); e.op(0x71, "first division overflow combination")
|
|
1100
|
+
e.localGet(1); e.i64(-1n); e.op(0x51, "right negative-one test")
|
|
1101
|
+
e.localGet(0); e.i64(-9223372036854775808n); e.op(0x51, "left minimum test"); e.op(0x71, "second division overflow combination")
|
|
1102
|
+
e.op(0x72, "division overflow combination"); e.trapIf("signed i64 multiplication overflow")
|
|
1103
|
+
e.localGet(0); e.localGet(1); e.op(0x7e, "signed i64 multiplication"); e.localSet(2)
|
|
1104
|
+
e.localGet(2); e.localGet(1); e.op(0x7f, "signed i64 division overflow check"); e.localGet(0); e.op(0x52, "multiplication quotient mismatch")
|
|
1105
|
+
e.trapIf("signed i64 multiplication overflow")
|
|
1106
|
+
e.localGet(2); e.op(0x0b, "end checked multiplication branch"); e.end()
|
|
1107
|
+
|
|
1108
|
+
return e.writer
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Encodes checked signed i64 negation.
|
|
1113
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
1114
|
+
* @param {SemanticModule} module - Semantic module.
|
|
1115
|
+
* @returns {BinaryWriter} Encoded helper body.
|
|
1116
|
+
*/
|
|
1117
|
+
function helperCheckedNegate(index, module) {
|
|
1118
|
+
const e = new SyntheticEmitter(index, module, "$checked_negate")
|
|
1119
|
+
|
|
1120
|
+
e.localGet(0); e.i64(-9223372036854775808n); e.op(0x51, "minimum i64 negate test"); e.trapIf("signed i64 negation overflow")
|
|
1121
|
+
e.i64(0n); e.localGet(0); e.op(0x7d, "signed i64 negation"); e.end()
|
|
1122
|
+
|
|
1123
|
+
return e.writer
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/** Helper instruction emitter with explicit synthetic provenance. */
|
|
1127
|
+
class SyntheticEmitter {
|
|
1128
|
+
/**
|
|
1129
|
+
* Creates a synthetic helper emitter.
|
|
1130
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
1131
|
+
* @param {SemanticModule} module - Related semantic module.
|
|
1132
|
+
* @param {string} name - Helper function name.
|
|
1133
|
+
*/
|
|
1134
|
+
constructor(index, module, name) {
|
|
1135
|
+
this.writer = new BinaryWriter(index)
|
|
1136
|
+
this.related = {node: module, path: ""}
|
|
1137
|
+
this.name = name
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* Writes an instruction opcode.
|
|
1142
|
+
* @param {number} opcode - Wasm opcode.
|
|
1143
|
+
* @param {string} reason - Synthetic provenance reason.
|
|
1144
|
+
* @returns {void}
|
|
1145
|
+
*/
|
|
1146
|
+
op(opcode, reason) { this.writer.synthetic([opcode], `${this.name}: ${reason}`, this.related, "instruction") }
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Writes encoded instruction immediates.
|
|
1150
|
+
* @param {number[]} bytes - Immediate bytes.
|
|
1151
|
+
* @param {string} reason - Synthetic provenance reason.
|
|
1152
|
+
* @returns {void}
|
|
1153
|
+
*/
|
|
1154
|
+
immediate(bytes, reason) { this.writer.synthetic(bytes, `${this.name}: ${reason}`, this.related, "immediate") }
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Reads one local.
|
|
1158
|
+
* @param {number} index - Local index.
|
|
1159
|
+
* @returns {void}
|
|
1160
|
+
*/
|
|
1161
|
+
localGet(index) { this.op(0x20, "read local"); this.immediate(u32(index), "local index") }
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Writes one local.
|
|
1165
|
+
* @param {number} index - Local index.
|
|
1166
|
+
* @returns {void}
|
|
1167
|
+
*/
|
|
1168
|
+
localSet(index) { this.op(0x21, "write local"); this.immediate(u32(index), "local index") }
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Writes and retains one local value.
|
|
1172
|
+
* @param {number} index - Local index.
|
|
1173
|
+
* @returns {void}
|
|
1174
|
+
*/
|
|
1175
|
+
localTee(index) { this.op(0x22, "tee local"); this.immediate(u32(index), "local index") }
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Reads one global.
|
|
1179
|
+
* @param {number} index - Global index.
|
|
1180
|
+
* @returns {void}
|
|
1181
|
+
*/
|
|
1182
|
+
globalGet(index) { this.op(0x23, "read global"); this.immediate(u32(index), "global index") }
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Writes one global.
|
|
1186
|
+
* @param {number} index - Global index.
|
|
1187
|
+
* @returns {void}
|
|
1188
|
+
*/
|
|
1189
|
+
globalSet(index) { this.op(0x24, "write global"); this.immediate(u32(index), "global index") }
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Writes one i32 constant.
|
|
1193
|
+
* @param {number} value - Constant value.
|
|
1194
|
+
* @returns {void}
|
|
1195
|
+
*/
|
|
1196
|
+
i32(value) { this.op(0x41, "i32 constant"); this.immediate(s32(value), "i32 immediate") }
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Writes one i64 constant.
|
|
1200
|
+
* @param {bigint} value - Constant value.
|
|
1201
|
+
* @returns {void}
|
|
1202
|
+
*/
|
|
1203
|
+
i64(value) { this.op(0x42, "i64 constant"); this.immediate(s64(value), "i64 immediate") }
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Loads one unsigned byte.
|
|
1207
|
+
* @returns {void}
|
|
1208
|
+
*/
|
|
1209
|
+
load8() { this.op(0x2d, "load unsigned byte"); this.immediate([0x00, 0x00], "byte-load memory arguments") }
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Stores one byte.
|
|
1213
|
+
* @returns {void}
|
|
1214
|
+
*/
|
|
1215
|
+
store8() { this.op(0x3a, "store byte"); this.immediate([0x00, 0x00], "byte-store memory arguments") }
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Traps when the stack's condition is nonzero.
|
|
1219
|
+
* @param {string} reason - Trap provenance reason.
|
|
1220
|
+
* @returns {void}
|
|
1221
|
+
*/
|
|
1222
|
+
trapIf(reason) {
|
|
1223
|
+
this.op(0x04, `${reason} branch`); this.immediate([0x40], "empty block type")
|
|
1224
|
+
this.op(0x00, `${reason} trap`); this.op(0x0b, `${reason} branch end`)
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Copies one string operand byte by byte.
|
|
1228
|
+
* @param {number} cursor - Loop cursor local.
|
|
1229
|
+
* @param {number} sourcePointer - Source pointer local.
|
|
1230
|
+
* @param {number} sourceLength - Source length local.
|
|
1231
|
+
* @param {number} destination - Allocation start local.
|
|
1232
|
+
* @param {number | undefined} destinationOffset - Optional left-length local.
|
|
1233
|
+
* @returns {void}
|
|
1234
|
+
*/
|
|
1235
|
+
copyLoop(cursor, sourcePointer, sourceLength, destination, destinationOffset) {
|
|
1236
|
+
this.op(0x02, "copy exit block"); this.immediate([0x40], "empty block type")
|
|
1237
|
+
this.op(0x03, "copy loop"); this.immediate([0x40], "empty block type")
|
|
1238
|
+
this.localGet(cursor); this.localGet(sourceLength); this.op(0x4f, "compare copy cursor with length")
|
|
1239
|
+
this.op(0x0d, "exit completed copy"); this.immediate(u32(1), "copy block depth")
|
|
1240
|
+
this.localGet(destination)
|
|
1241
|
+
if (destinationOffset !== undefined) { this.localGet(destinationOffset); this.op(0x6a, "add destination offset") }
|
|
1242
|
+
this.localGet(cursor); this.op(0x6a, "add destination cursor")
|
|
1243
|
+
this.localGet(sourcePointer); this.localGet(cursor); this.op(0x6a, "add source cursor"); this.load8(); this.store8()
|
|
1244
|
+
this.localGet(cursor); this.i32(1); this.op(0x6a, "increment copy cursor"); this.localSet(cursor)
|
|
1245
|
+
this.op(0x0c, "continue copy loop"); this.immediate(u32(0), "copy loop depth")
|
|
1246
|
+
this.op(0x0b, "end copy loop"); this.op(0x0b, "end copy block")
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Ends one helper function.
|
|
1250
|
+
* @returns {void}
|
|
1251
|
+
*/
|
|
1252
|
+
end() { this.op(0x0b, "function end") }
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Collects deduplicated literal bytes and their combined semantic origins.
|
|
1257
|
+
* @param {SemanticModule} module - Semantic module.
|
|
1258
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Provenance index.
|
|
1259
|
+
* @returns {{boundary: number, byValue: Map<string, {address: number, bytes: Uint8Array}>, entries: {address: number, bytes: Uint8Array, origin: import("../semantic/types.js").SemanticOrigin}[]}} Layout.
|
|
1260
|
+
*/
|
|
1261
|
+
function collectLiteralLayout(module, index) {
|
|
1262
|
+
/** @type {Map<string, {bytes: Uint8Array, occurrences: ReturnType<typeof index.recordFor>[]}>} */
|
|
1263
|
+
const collected = new Map()
|
|
1264
|
+
|
|
1265
|
+
for (const {node, path} of semanticEntries(module)) {
|
|
1266
|
+
if (node.kind != "StringLiteral") continue
|
|
1267
|
+
const existing = collected.get(node.value)
|
|
1268
|
+
|
|
1269
|
+
if (existing) existing.occurrences.push(index.recordFor(node, path))
|
|
1270
|
+
else collected.set(node.value, {bytes: encoder.encode(node.value), occurrences: [index.recordFor(node, path)]})
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
let address = 1
|
|
1274
|
+
/** @type {Map<string, {address: number, bytes: Uint8Array}>} */
|
|
1275
|
+
const byValue = new Map()
|
|
1276
|
+
/** @type {{address: number, bytes: Uint8Array, origin: import("../semantic/types.js").SemanticOrigin}[]} */
|
|
1277
|
+
const entries = []
|
|
1278
|
+
|
|
1279
|
+
for (const [value, literal] of collected) {
|
|
1280
|
+
const next = checkedSize(address + literal.bytes.byteLength, module, "immutable literal memory layout")
|
|
1281
|
+
const exactOrigins = literal.occurrences.map((record) => originForRole(index, record, "literal"))
|
|
1282
|
+
const origins = literal.occurrences.flatMap((record, offset) => relatedOrigins(record, exactOrigins[offset]))
|
|
1283
|
+
const origin = literal.occurrences.length == 1 ? exactOrigins[0] : origins.length > 0
|
|
1284
|
+
? {kind: "derived", origins}
|
|
1285
|
+
: {kind: "synthetic", reason: "Deduplicated immutable UTF-8 literal data without source ranges.", relatedOrigins: []}
|
|
1286
|
+
|
|
1287
|
+
entries.push({address, bytes: literal.bytes, origin: /** @type {import("../semantic/types.js").SemanticOrigin} */ (origin)})
|
|
1288
|
+
byValue.set(value, {address, bytes: literal.bytes})
|
|
1289
|
+
address = next
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
return {boundary: address, byValue, entries}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Performs bounded path-sensitive allocation analysis from run.
|
|
1297
|
+
* @param {SemanticModule} module - Validated module.
|
|
1298
|
+
* @returns {number} Worst reachable allocation bytes.
|
|
1299
|
+
*/
|
|
1300
|
+
function analyzeScratchUse(module) {
|
|
1301
|
+
const functions = new Map(module.functions.map((declaration) => [declaration.name, declaration]))
|
|
1302
|
+
let steps = 0
|
|
1303
|
+
|
|
1304
|
+
const states = block(module.entryPoint.body, [{allocated: 0, env: new Map()}], 0)
|
|
1305
|
+
|
|
1306
|
+
return Math.max(0, ...states.map(({allocated}) => allocated))
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Analyzes one block from every incoming path state.
|
|
1310
|
+
* @param {import("../semantic/types.js").Block} semanticBlock - Block to analyze.
|
|
1311
|
+
* @param {AnalysisState[]} incoming - Incoming path states.
|
|
1312
|
+
* @param {number} depth - Active semantic call depth.
|
|
1313
|
+
* @returns {AnalysisState[]} Resulting path states.
|
|
1314
|
+
*/
|
|
1315
|
+
function block(semanticBlock, incoming, depth) {
|
|
1316
|
+
let states = incoming
|
|
1317
|
+
|
|
1318
|
+
for (const statement of semanticBlock.statements) {
|
|
1319
|
+
const next = []
|
|
1320
|
+
|
|
1321
|
+
for (const state of states) {
|
|
1322
|
+
if (state.returned) { next.push(state); continue }
|
|
1323
|
+
if (statement.kind == "LocalDeclaration") {
|
|
1324
|
+
for (const result of expression(statement.initializer, state, depth)) {
|
|
1325
|
+
const env = new Map(result.state.env)
|
|
1326
|
+
|
|
1327
|
+
env.set(statement.name, result.value)
|
|
1328
|
+
next.push({...result.state, env})
|
|
1329
|
+
}
|
|
1330
|
+
} else if (statement.kind == "AssignmentStatement") {
|
|
1331
|
+
for (const result of expression(statement.expression, state, depth)) {
|
|
1332
|
+
const env = new Map(result.state.env)
|
|
1333
|
+
|
|
1334
|
+
env.set(statement.target.name, result.value)
|
|
1335
|
+
next.push({...result.state, env})
|
|
1336
|
+
}
|
|
1337
|
+
} else if (statement.kind == "PrintStatement") {
|
|
1338
|
+
for (const result of expression(statement.expression, state, depth)) next.push(result.state)
|
|
1339
|
+
} else if (statement.kind == "ReturnStatement") {
|
|
1340
|
+
if (!statement.expression) throw new TypeError("Wasm bare return reached abstract execution.")
|
|
1341
|
+
for (const result of expression(statement.expression, state, depth)) next.push({...result.state, returned: result.value})
|
|
1342
|
+
} else if (statement.kind == "ExpressionStatement") {
|
|
1343
|
+
throw new TypeError("Wasm Task 005 expression statement reached abstract execution.")
|
|
1344
|
+
} else if (statement.kind == "IfStatement") {
|
|
1345
|
+
for (const condition of expression(statement.condition, state, depth)) {
|
|
1346
|
+
if (condition.value.knownBoolean !== false) {
|
|
1347
|
+
next.push(...withoutBranchLocals(block(statement.consequent, [cloneState(condition.state)], depth), condition.state.env))
|
|
1348
|
+
}
|
|
1349
|
+
if (condition.value.knownBoolean !== true) {
|
|
1350
|
+
const alternate = statement.alternate
|
|
1351
|
+
? block(statement.alternate, [cloneState(condition.state)], depth)
|
|
1352
|
+
: [cloneState(condition.state)]
|
|
1353
|
+
|
|
1354
|
+
next.push(...withoutBranchLocals(alternate, condition.state.env))
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
} else {
|
|
1358
|
+
throw new TypeError("Unsupported Wasm statement reached abstract execution.")
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
states = boundedStates(next, statement)
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
return states
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* Evaluates abstract values and allocation demand for one expression.
|
|
1369
|
+
* @param {Expression} value - Expression to analyze.
|
|
1370
|
+
* @param {AnalysisState} state - Incoming path state.
|
|
1371
|
+
* @param {number} depth - Active semantic call depth.
|
|
1372
|
+
* @returns {{state: AnalysisState, value: AbstractValue}[]} Possible outcomes.
|
|
1373
|
+
*/
|
|
1374
|
+
function expression(value, state, depth) {
|
|
1375
|
+
steps += 1
|
|
1376
|
+
if (steps > maximumAnalysisSteps) unsupportedCapability("wasm", "unprovable path-sensitive scratch allocation bound", value.location)
|
|
1377
|
+
|
|
1378
|
+
if (value.kind == "IdentifierExpression") {
|
|
1379
|
+
const found = state.env.get(value.name)
|
|
1380
|
+
|
|
1381
|
+
if (!found) throw new Error("Validated Wasm analysis omitted an identifier binding.")
|
|
1382
|
+
|
|
1383
|
+
return [{state, value: found}]
|
|
1384
|
+
}
|
|
1385
|
+
if (value.kind == "IntegerLiteral") return [{state, value: {knownInteger: BigInt(value.value), type: "integer"}}]
|
|
1386
|
+
if (value.kind == "BooleanLiteral") return [{state, value: {knownBoolean: value.value, type: "boolean"}}]
|
|
1387
|
+
if (value.kind == "StringLiteral") {
|
|
1388
|
+
return [{state, value: {knownString: value.value, stringLength: encoder.encode(value.value).byteLength, type: "string"}}]
|
|
1389
|
+
}
|
|
1390
|
+
if (value.kind == "CallExpression") {
|
|
1391
|
+
let argumentsList = [{state, values: /** @type {AbstractValue[]} */ ([])}]
|
|
1392
|
+
|
|
1393
|
+
for (const argument of value.arguments) {
|
|
1394
|
+
const next = []
|
|
1395
|
+
|
|
1396
|
+
for (const current of argumentsList) {
|
|
1397
|
+
for (const result of expression(argument, current.state, depth)) {
|
|
1398
|
+
next.push({state: result.state, values: [...current.values, result.value]})
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
argumentsList = next
|
|
1402
|
+
}
|
|
1403
|
+
const declaration = functions.get(value.callee)
|
|
1404
|
+
|
|
1405
|
+
if (!declaration) throw new Error("Validated Wasm analysis omitted a call declaration.")
|
|
1406
|
+
if (depth >= maximumCallDepth) unsupportedCapability("wasm", `semantic call depth above ${maximumCallDepth}`, value.location)
|
|
1407
|
+
/** @type {{state: AnalysisState, value: AbstractValue}[]} */
|
|
1408
|
+
const outcomes = []
|
|
1409
|
+
|
|
1410
|
+
for (const argumentSet of argumentsList) {
|
|
1411
|
+
const env = new Map(declaration.parameters.map((parameter, index) => [parameter.name, argumentSet.values[index]]))
|
|
1412
|
+
const called = block(declaration.body, [{allocated: 0, env}], depth + 1)
|
|
1413
|
+
|
|
1414
|
+
for (const returned of called) {
|
|
1415
|
+
if (!returned.returned) throw new Error("Validated Wasm function analysis omitted a return value.")
|
|
1416
|
+
outcomes.push({
|
|
1417
|
+
state: {...argumentSet.state, allocated: addAllocation(argumentSet.state.allocated, returned.allocated, value)},
|
|
1418
|
+
value: returned.returned
|
|
1419
|
+
})
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
return outcomes
|
|
1424
|
+
}
|
|
1425
|
+
if (value.kind == "UnaryExpression") {
|
|
1426
|
+
return expression(value.operand, state, depth).map((operand) => {
|
|
1427
|
+
if (value.operation == "BooleanNot") {
|
|
1428
|
+
return {state: operand.state, value: {type: "boolean", ...(operand.value.knownBoolean === undefined ? {} : {knownBoolean: !operand.value.knownBoolean})}}
|
|
1429
|
+
}
|
|
1430
|
+
const knownInteger = operand.value.knownInteger === undefined ? undefined : checkedI64(-operand.value.knownInteger, value)
|
|
1431
|
+
|
|
1432
|
+
return {state: operand.state, value: {type: "integer", ...(knownInteger === undefined ? {} : {knownInteger})}}
|
|
1433
|
+
})
|
|
1434
|
+
}
|
|
1435
|
+
if (value.kind != "BinaryExpression") {
|
|
1436
|
+
return unsupportedCapability("wasm", "collection expression reached scalar analysis", value.location)
|
|
1437
|
+
}
|
|
1438
|
+
if (value.operation == "BooleanAnd" || value.operation == "BooleanOr") {
|
|
1439
|
+
/** @type {{state: AnalysisState, value: AbstractValue}[]} */
|
|
1440
|
+
const outcomes = []
|
|
1441
|
+
|
|
1442
|
+
for (const left of expression(value.left, state, depth)) {
|
|
1443
|
+
const shortCircuits = value.operation == "BooleanAnd" ? left.value.knownBoolean === false : left.value.knownBoolean === true
|
|
1444
|
+
const mustEvaluate = value.operation == "BooleanAnd" ? left.value.knownBoolean === true : left.value.knownBoolean === false
|
|
1445
|
+
|
|
1446
|
+
if (shortCircuits || !mustEvaluate) outcomes.push({state: left.state, value: {knownBoolean: value.operation == "BooleanOr", type: "boolean"}})
|
|
1447
|
+
if (!shortCircuits) outcomes.push(...expression(value.right, cloneState(left.state), depth))
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
return outcomes
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const outcomes = []
|
|
1454
|
+
|
|
1455
|
+
for (const left of expression(value.left, state, depth)) {
|
|
1456
|
+
for (const right of expression(value.right, left.state, depth)) {
|
|
1457
|
+
outcomes.push(binary(value, left.value, right.value, right.state))
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
return outcomes
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/**
|
|
1465
|
+
* Combines abstract operands for one binary operation.
|
|
1466
|
+
* @param {import("../semantic/types.js").BinaryExpression} expressionNode - Binary expression.
|
|
1467
|
+
* @param {AbstractValue} left - Abstract left operand.
|
|
1468
|
+
* @param {AbstractValue} right - Abstract right operand.
|
|
1469
|
+
* @param {AnalysisState} state - State after evaluating both operands.
|
|
1470
|
+
* @returns {{state: AnalysisState, value: AbstractValue}} Combined outcome.
|
|
1471
|
+
*/
|
|
1472
|
+
function binary(expressionNode, left, right, state) {
|
|
1473
|
+
const operation = expressionNode.operation
|
|
1474
|
+
|
|
1475
|
+
if (operation == "StringConcat") {
|
|
1476
|
+
if (left.stringLength === undefined || right.stringLength === undefined) {
|
|
1477
|
+
unsupportedCapability("wasm", "unprovable StringConcat byte length", expressionNode.location)
|
|
1478
|
+
}
|
|
1479
|
+
const length = addAllocation(left.stringLength, right.stringLength, expressionNode)
|
|
1480
|
+
const allocated = addAllocation(state.allocated, length, expressionNode)
|
|
1481
|
+
const knownString = left.knownString !== undefined && right.knownString !== undefined ? left.knownString + right.knownString : undefined
|
|
1482
|
+
|
|
1483
|
+
return {state: {...state, allocated}, value: {type: "string", stringLength: length, ...(knownString === undefined ? {} : {knownString})}}
|
|
1484
|
+
}
|
|
1485
|
+
if (["IntegerAdd", "IntegerSubtract", "IntegerMultiply"].includes(operation)) {
|
|
1486
|
+
let knownInteger
|
|
1487
|
+
|
|
1488
|
+
if (left.knownInteger !== undefined && right.knownInteger !== undefined) {
|
|
1489
|
+
knownInteger = operation == "IntegerAdd" ? left.knownInteger + right.knownInteger :
|
|
1490
|
+
operation == "IntegerSubtract" ? left.knownInteger - right.knownInteger : left.knownInteger * right.knownInteger
|
|
1491
|
+
knownInteger = checkedI64(knownInteger, expressionNode)
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
return {state, value: {type: "integer", ...(knownInteger === undefined ? {} : {knownInteger})}}
|
|
1495
|
+
}
|
|
1496
|
+
if (operation.startsWith("Integer") && operation != "IntegerEqual" && operation != "IntegerNotEqual") {
|
|
1497
|
+
return {state, value: {knownBoolean: compareKnown(operation, left.knownInteger, right.knownInteger), type: "boolean"}}
|
|
1498
|
+
}
|
|
1499
|
+
if (operation == "IntegerEqual" || operation == "IntegerNotEqual") {
|
|
1500
|
+
return {state, value: {knownBoolean: equalKnown(left.knownInteger, right.knownInteger, operation.endsWith("NotEqual")), type: "boolean"}}
|
|
1501
|
+
}
|
|
1502
|
+
if (operation == "BooleanEqual" || operation == "BooleanNotEqual") {
|
|
1503
|
+
return {state, value: {knownBoolean: equalKnown(left.knownBoolean, right.knownBoolean, operation.endsWith("NotEqual")), type: "boolean"}}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
return {state, value: {knownBoolean: equalKnown(left.knownString, right.knownString, operation == "StringNotEqual"), type: "boolean"}}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* Rejects path-state growth beyond the deterministic analysis bound.
|
|
1511
|
+
* @param {AnalysisState[]} candidates - Candidate states.
|
|
1512
|
+
* @param {{location: import("../semantic/types.js").SourceLocation}} node - Related semantic node.
|
|
1513
|
+
* @returns {AnalysisState[]} Accepted states.
|
|
1514
|
+
*/
|
|
1515
|
+
function boundedStates(candidates, node) {
|
|
1516
|
+
if (candidates.length > maximumAnalysisSteps) unsupportedCapability("wasm", "unprovable path-sensitive scratch allocation state count", node.location)
|
|
1517
|
+
|
|
1518
|
+
return candidates
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
/**
|
|
1523
|
+
* Compares two known integer operands when available.
|
|
1524
|
+
* @param {string} operation - Semantic comparison operation.
|
|
1525
|
+
* @param {bigint | undefined} left - Known left operand.
|
|
1526
|
+
* @param {bigint | undefined} right - Known right operand.
|
|
1527
|
+
* @returns {boolean | undefined} Known comparison result.
|
|
1528
|
+
*/
|
|
1529
|
+
function compareKnown(operation, left, right) {
|
|
1530
|
+
if (left === undefined || right === undefined) return undefined
|
|
1531
|
+
if (operation == "IntegerLessThan") return left < right
|
|
1532
|
+
if (operation == "IntegerLessThanOrEqual") return left <= right
|
|
1533
|
+
if (operation == "IntegerGreaterThan") return left > right
|
|
1534
|
+
|
|
1535
|
+
return left >= right
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/**
|
|
1539
|
+
* Compares two known same-type operands when available.
|
|
1540
|
+
* @template T
|
|
1541
|
+
* @param {T | undefined} left - Known left operand.
|
|
1542
|
+
* @param {T | undefined} right - Known right operand.
|
|
1543
|
+
* @param {boolean} invert - Whether to invert equality.
|
|
1544
|
+
* @returns {boolean | undefined} Known equality result.
|
|
1545
|
+
*/
|
|
1546
|
+
function equalKnown(left, right, invert) {
|
|
1547
|
+
return left === undefined || right === undefined ? undefined : invert ? left !== right : left === right
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* Checks one compile-time integer against signed i64 bounds.
|
|
1552
|
+
* @param {bigint} value - Candidate integer.
|
|
1553
|
+
* @param {{location: import("../semantic/types.js").SourceLocation}} node - Related semantic node.
|
|
1554
|
+
* @returns {bigint} Checked integer.
|
|
1555
|
+
*/
|
|
1556
|
+
function checkedI64(value, node) {
|
|
1557
|
+
if (value < -9223372036854775808n || value > 9223372036854775807n) {
|
|
1558
|
+
unsupportedCapability("wasm", "compile-time-known signed i64 overflow", node.location)
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
return value
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Adds bounded allocation counts without approximation.
|
|
1566
|
+
* @param {number} left - Existing allocation count.
|
|
1567
|
+
* @param {number} right - Additional allocation count.
|
|
1568
|
+
* @param {{location: import("../semantic/types.js").SourceLocation}} node - Related semantic node.
|
|
1569
|
+
* @returns {number} Checked total.
|
|
1570
|
+
*/
|
|
1571
|
+
function addAllocation(left, right, node) {
|
|
1572
|
+
const total = left + right
|
|
1573
|
+
|
|
1574
|
+
if (!Number.isSafeInteger(total) || total < 0) unsupportedCapability("wasm", "scratch allocation arithmetic overflow", node.location)
|
|
1575
|
+
if (total > scratchCapacity) unsupportedCapability("wasm", `scratch allocation above ${scratchCapacity} bytes`, node.location)
|
|
1576
|
+
|
|
1577
|
+
return total
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* Clones one path state and its lexical environment.
|
|
1582
|
+
* @param {AnalysisState} state - State to clone.
|
|
1583
|
+
* @returns {AnalysisState} Detached state.
|
|
1584
|
+
*/
|
|
1585
|
+
function cloneState(state) { return {...state, env: new Map(state.env)} }
|
|
1586
|
+
|
|
1587
|
+
/**
|
|
1588
|
+
* Removes bindings declared only inside a branch.
|
|
1589
|
+
* @param {AnalysisState[]} states - Branch result states.
|
|
1590
|
+
* @param {Map<string, AbstractValue>} parent - Parent lexical environment.
|
|
1591
|
+
* @returns {AnalysisState[]} States restricted to parent bindings.
|
|
1592
|
+
*/
|
|
1593
|
+
function withoutBranchLocals(states, parent) {
|
|
1594
|
+
return states.map((state) => {
|
|
1595
|
+
const env = new Map([...state.env].filter(([name]) => parent.has(name)))
|
|
1596
|
+
|
|
1597
|
+
return {...state, env}
|
|
1598
|
+
})
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
/**
|
|
1602
|
+
* Resolves one expression's semantic scalar type.
|
|
1603
|
+
* @param {Expression} expression - Expression to inspect.
|
|
1604
|
+
* @param {Map<string, LocalBinding>} bindings - Lexical local bindings.
|
|
1605
|
+
* @param {Map<string, import("../semantic/types.js").FunctionDeclaration>} functions - Semantic declarations by name.
|
|
1606
|
+
* @returns {Scalar} Expression type.
|
|
1607
|
+
*/
|
|
1608
|
+
function expressionType(expression, bindings, functions) {
|
|
1609
|
+
if (expression.kind == "IntegerLiteral") return "integer"
|
|
1610
|
+
if (expression.kind == "BooleanLiteral") return "boolean"
|
|
1611
|
+
if (expression.kind == "StringLiteral") return "string"
|
|
1612
|
+
if (expression.kind == "UnaryExpression" || expression.kind == "BinaryExpression") return expression.type
|
|
1613
|
+
if (expression.kind == "CallExpression") {
|
|
1614
|
+
const declaration = functions.get(expression.callee)
|
|
1615
|
+
|
|
1616
|
+
if (!declaration) throw new Error("Validated Wasm expression omitted its call declaration.")
|
|
1617
|
+
|
|
1618
|
+
return wasmReturnType(declaration)
|
|
1619
|
+
}
|
|
1620
|
+
if (expression.kind != "IdentifierExpression") {
|
|
1621
|
+
return unsupportedCapability("wasm", "collection expression reached scalar type inference", expression.location)
|
|
1622
|
+
}
|
|
1623
|
+
const binding = bindings.get(expression.name)
|
|
1624
|
+
|
|
1625
|
+
if (!binding) throw new Error("Validated Wasm expression omitted its local binding.")
|
|
1626
|
+
|
|
1627
|
+
return binding.type
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/**
|
|
1631
|
+
* Narrows a declaration return after the Wasm Task 005 capability preflight.
|
|
1632
|
+
* @param {import("../semantic/types.js").FunctionDeclaration} declaration - Validated declaration.
|
|
1633
|
+
* @returns {Scalar} Supported Wasm scalar return.
|
|
1634
|
+
*/
|
|
1635
|
+
function wasmReturnType(declaration) {
|
|
1636
|
+
if (declaration.returnType.kind != "TypeReference" || declaration.returnType.name == "void") {
|
|
1637
|
+
return unsupportedCapability("wasm", "Task 005 void function return", declaration.location)
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
return declaration.returnType.name
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/**
|
|
1644
|
+
* Narrows one scalar after Wasm collection capability rejection.
|
|
1645
|
+
* @param {import("../semantic/types.js").SemanticValueType} type - Semantic value type.
|
|
1646
|
+
* @param {import("../semantic/types.js").SourceLocation} location - Owning location.
|
|
1647
|
+
* @returns {Scalar} Scalar type.
|
|
1648
|
+
*/
|
|
1649
|
+
function wasmScalarType(type, location) {
|
|
1650
|
+
if (type.kind != "TypeReference") return unsupportedCapability("wasm", "Task 006 immutable collection type", location)
|
|
1651
|
+
|
|
1652
|
+
return type.name
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/**
|
|
1656
|
+
* Flattens one semantic scalar into Wasm value types.
|
|
1657
|
+
* @param {Scalar} type - Semantic scalar type.
|
|
1658
|
+
* @returns {("i32" | "i64")[]} Wasm value types.
|
|
1659
|
+
*/
|
|
1660
|
+
function wasmTypes(type) { return type == "integer" ? ["i64"] : type == "boolean" ? ["i32"] : ["i32", "i32"] }
|
|
1661
|
+
|
|
1662
|
+
/**
|
|
1663
|
+
* Aligns an unsigned size upward.
|
|
1664
|
+
* @param {number} value - Unaligned value.
|
|
1665
|
+
* @param {number} alignment - Required alignment.
|
|
1666
|
+
* @returns {number} Aligned value.
|
|
1667
|
+
*/
|
|
1668
|
+
function align(value, alignment) { return Math.ceil(value / alignment) * alignment }
|
|
1669
|
+
|
|
1670
|
+
/**
|
|
1671
|
+
* Checks a computed module size against the portable u32 domain.
|
|
1672
|
+
* @param {number} value - Candidate size.
|
|
1673
|
+
* @param {{location: import("../semantic/types.js").SourceLocation}} node - Related semantic node.
|
|
1674
|
+
* @param {string} capability - Failure capability description.
|
|
1675
|
+
* @returns {number} Checked size.
|
|
1676
|
+
*/
|
|
1677
|
+
function checkedSize(value, node, capability) {
|
|
1678
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 0xffffffff) unsupportedCapability("wasm", capability, node.location)
|
|
1679
|
+
|
|
1680
|
+
return value
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/**
|
|
1684
|
+
* Encodes an unsigned u32 LEB128 value.
|
|
1685
|
+
* @param {number} value - Unsigned value.
|
|
1686
|
+
* @returns {number[]} Encoded bytes.
|
|
1687
|
+
*/
|
|
1688
|
+
function u32(value) {
|
|
1689
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 0xffffffff) throw new RangeError("Unsigned WebAssembly integer is outside u32.")
|
|
1690
|
+
const bytes = []
|
|
1691
|
+
let remaining = value
|
|
1692
|
+
|
|
1693
|
+
do {
|
|
1694
|
+
let byte = remaining % 128
|
|
1695
|
+
|
|
1696
|
+
remaining = Math.floor(remaining / 128)
|
|
1697
|
+
if (remaining != 0) byte |= 0x80
|
|
1698
|
+
bytes.push(byte)
|
|
1699
|
+
} while (remaining != 0)
|
|
1700
|
+
|
|
1701
|
+
return bytes
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* Encodes a signed i32 LEB128 value.
|
|
1706
|
+
* @param {number} value - Signed value.
|
|
1707
|
+
* @returns {number[]} Encoded bytes.
|
|
1708
|
+
*/
|
|
1709
|
+
function s32(value) { return s64(BigInt(value)) }
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* Encodes a signed i64 LEB128 value.
|
|
1713
|
+
* @param {bigint} value - Signed value.
|
|
1714
|
+
* @returns {number[]} Encoded bytes.
|
|
1715
|
+
*/
|
|
1716
|
+
function s64(value) {
|
|
1717
|
+
const bytes = []
|
|
1718
|
+
let remaining = value
|
|
1719
|
+
let done = false
|
|
1720
|
+
|
|
1721
|
+
while (!done) {
|
|
1722
|
+
let byte = Number(remaining & 0x7fn)
|
|
1723
|
+
|
|
1724
|
+
remaining >>= 7n
|
|
1725
|
+
const signSet = (byte & 0x40) != 0
|
|
1726
|
+
|
|
1727
|
+
done = remaining == 0n && !signSet || remaining == -1n && signSet
|
|
1728
|
+
if (!done) byte |= 0x80
|
|
1729
|
+
bytes.push(byte)
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
return bytes
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
/**
|
|
1736
|
+
* Encodes a length-prefixed UTF-8 Wasm name.
|
|
1737
|
+
* @param {string} value - Name value.
|
|
1738
|
+
* @returns {number[]} Encoded bytes.
|
|
1739
|
+
*/
|
|
1740
|
+
function nameBytes(value) {
|
|
1741
|
+
const bytes = encoder.encode(value)
|
|
1742
|
+
|
|
1743
|
+
return [...u32(bytes.byteLength), ...bytes]
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/**
|
|
1747
|
+
* Extracts contextual related origins from one semantic record.
|
|
1748
|
+
* @param {SemanticNodeProvenance} record - Semantic provenance record.
|
|
1749
|
+
* @param {import("../semantic/types.js").SemanticOrigin} [origin] - Optional exact semantic origin.
|
|
1750
|
+
* @returns {import("../semantic/types.js").RelatedOrigin[]} Related origins.
|
|
1751
|
+
*/
|
|
1752
|
+
function relatedOrigins(record, origin = record.origin) {
|
|
1753
|
+
if (origin.kind == "source") {
|
|
1754
|
+
return [{location: origin.location, nodeId: record.id, role: "related semantic occurrence", sourceId: origin.sourceId,
|
|
1755
|
+
...(record.symbolId ? {symbolId: record.symbolId} : {})}]
|
|
1756
|
+
}
|
|
1757
|
+
const origins = origin.kind == "derived" ? origin.origins : origin.relatedOrigins
|
|
1758
|
+
|
|
1759
|
+
return origins.map((origin) => ({...origin, nodeId: origin.nodeId ?? record.id, role: origin.role ?? "related semantic occurrence",
|
|
1760
|
+
...(record.symbolId && !origin.symbolId ? {symbolId: record.symbolId} : {})}))
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* Resolves a parser-owned token role to its canonical source identity.
|
|
1765
|
+
* @param {ReturnType<typeof createGenerationIndex>} index - Canonical provenance index.
|
|
1766
|
+
* @param {SemanticNodeProvenance} record - Semantic occurrence record.
|
|
1767
|
+
* @param {string} role - Parser-owned token role.
|
|
1768
|
+
* @returns {import("../semantic/types.js").SemanticOrigin} Exact role origin, or the occurrence origin when unavailable.
|
|
1769
|
+
*/
|
|
1770
|
+
function originForRole(index, record, role) {
|
|
1771
|
+
const location = record.ranges[role]
|
|
1772
|
+
|
|
1773
|
+
if (!location) return record.origin
|
|
1774
|
+
/** @type {string | undefined} */
|
|
1775
|
+
let fallbackSourceId
|
|
1776
|
+
|
|
1777
|
+
if (record.origin.kind == "source" && record.origin.location.filename == location.filename) fallbackSourceId = record.origin.sourceId
|
|
1778
|
+
else if (record.origin.kind == "derived") {
|
|
1779
|
+
fallbackSourceId = record.origin.origins.find((origin) => origin.location.filename == location.filename)?.sourceId
|
|
1780
|
+
} else if (record.origin.kind == "synthetic") {
|
|
1781
|
+
fallbackSourceId = record.origin.relatedOrigins.find((origin) => origin.location.filename == location.filename)?.sourceId
|
|
1782
|
+
}
|
|
1783
|
+
const source = index.provenance.sources.find((candidate) => candidate.id == fallbackSourceId) ??
|
|
1784
|
+
index.provenance.sources.find((candidate) => candidate.filename == location.filename)
|
|
1785
|
+
|
|
1786
|
+
return source ? {kind: "source", location, sourceId: source.id} : record.origin
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
/**
|
|
1790
|
+
* Projects byte provenance to the Wasm Source Map v3 convention.
|
|
1791
|
+
* @param {import("../semantic/types.js").SemantifoldByteMapping} mapping - Authoritative byte mapping.
|
|
1792
|
+
* @param {import("../semantic/types.js").SemanticProvenance} provenance - Source registry.
|
|
1793
|
+
* @returns {import("@jridgewell/gen-mapping").EncodedSourceMap} Source Map v3 projection.
|
|
1794
|
+
*/
|
|
1795
|
+
function createWasmSourceMap(mapping, provenance) {
|
|
1796
|
+
const generated = new GenMapping({file: mapping.generated.path})
|
|
1797
|
+
const sourcesById = new Map(provenance.sources.map((source) => [source.id, source]))
|
|
1798
|
+
const symbolsById = new Map(provenance.symbols.map((symbol) => [symbol.id, symbol]))
|
|
1799
|
+
|
|
1800
|
+
for (const source of provenance.sources) setSourceContent(generated, source.filename, source.content)
|
|
1801
|
+
for (const range of mapping.ranges) {
|
|
1802
|
+
const location = primaryLocation(range.origin)
|
|
1803
|
+
|
|
1804
|
+
if (!location || range.origin.kind == "synthetic") {
|
|
1805
|
+
maybeAddSegment(generated, 0, range.generated.start)
|
|
1806
|
+
continue
|
|
1807
|
+
}
|
|
1808
|
+
const sourceId = range.origin.kind == "source" ? range.origin.sourceId : range.origin.origins[0]?.sourceId
|
|
1809
|
+
const source = sourceId ? sourcesById.get(sourceId) : undefined
|
|
1810
|
+
|
|
1811
|
+
if (!source) throw new Error("Validated Wasm byte origin omitted its registered source.")
|
|
1812
|
+
if (range.symbolId === undefined) {
|
|
1813
|
+
addSegment(generated, 0, range.generated.start, source.filename, location.start.line - 1, location.start.column - 1,
|
|
1814
|
+
null, source.content)
|
|
1815
|
+
} else {
|
|
1816
|
+
const symbol = symbolsById.get(range.symbolId)
|
|
1817
|
+
|
|
1818
|
+
if (!symbol) throw new Error("Validated Wasm byte range omitted its semantic symbol.")
|
|
1819
|
+
addSegment(generated, 0, range.generated.start, source.filename, location.start.line - 1, location.start.column - 1,
|
|
1820
|
+
symbol.name, source.content)
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
return toEncodedMap(generated)
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
/**
|
|
1828
|
+
* Creates one mapped generated text artifact.
|
|
1829
|
+
* @param {string} content - Complete artifact content.
|
|
1830
|
+
* @param {string} path - Artifact path.
|
|
1831
|
+
* @param {import("../semantic/types.js").GeneratedTextLanguage} language - Generated text syntax.
|
|
1832
|
+
* @param {import("../semantic/types.js").GeneratedArtifactRole} role - Artifact role.
|
|
1833
|
+
* @param {SemanticModule} module - Source semantic module.
|
|
1834
|
+
* @param {{filename: string, content: string, language?: import("../semantic/types.js").SemanticLanguage}[] | undefined} sources - Source overrides.
|
|
1835
|
+
* @param {string} reason - Synthetic mapping reason.
|
|
1836
|
+
* @returns {import("../semantic/types.js").GeneratedSetArtifact} Generated artifact.
|
|
1837
|
+
*/
|
|
1838
|
+
function textArtifact(content, path, language, role, module, sources, reason) {
|
|
1839
|
+
const writer = new SourceWriter({filename: path, language, module, sources})
|
|
1840
|
+
|
|
1841
|
+
writer.synthetic(content, reason, [module], [""])
|
|
1842
|
+
const mapping = finalizeMapping(writer.finish())
|
|
1843
|
+
|
|
1844
|
+
return {
|
|
1845
|
+
content,
|
|
1846
|
+
contentKind: "text",
|
|
1847
|
+
mediaType: path.endsWith(".html") ? "text/html" : "text/javascript",
|
|
1848
|
+
ownership: "generated",
|
|
1849
|
+
path,
|
|
1850
|
+
provenance: {kind: "text", mapping, sourceMap: toSourceMapV3(mapping)},
|
|
1851
|
+
role
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
const semantifoldLoader = `const expectedImports = [
|
|
1856
|
+
["semantifold", "print_i64", ["i64"], []],
|
|
1857
|
+
["semantifold", "print_bool", ["i32"], []],
|
|
1858
|
+
["semantifold", "print_string", ["i32", "i32"], []]
|
|
1859
|
+
]
|
|
1860
|
+
|
|
1861
|
+
export class SemantifoldBrowserError extends Error {
|
|
1862
|
+
constructor(stage, message, cause) {
|
|
1863
|
+
super(message, cause instanceof Error ? {cause} : undefined)
|
|
1864
|
+
this.name = "SemantifoldBrowserError"
|
|
1865
|
+
this.stage = stage
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
export async function loadSemantifold(url, {fetch: fetchImplementation = globalThis.fetch, sink, timeoutMs = 10000} = {}) {
|
|
1870
|
+
if (typeof fetchImplementation != "function") throw new TypeError("A fetch implementation is required.")
|
|
1871
|
+
if (typeof sink != "function") throw new TypeError("A text sink function is required.")
|
|
1872
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) {
|
|
1873
|
+
throw new TypeError("timeoutMs must be an integer from 1 through 2147483647.")
|
|
1874
|
+
}
|
|
1875
|
+
const controller = new AbortController()
|
|
1876
|
+
let timedOut = false
|
|
1877
|
+
let timeout
|
|
1878
|
+
const deadline = new Promise((resolve, reject) => {
|
|
1879
|
+
const timeoutCause = new Error("Semantifold browser load timed out.")
|
|
1880
|
+
|
|
1881
|
+
timeout = setTimeout(() => {
|
|
1882
|
+
timedOut = true
|
|
1883
|
+
controller.abort(timeoutCause)
|
|
1884
|
+
reject(new SemantifoldBrowserError("timeout", "Wasm loading timed out.", timeoutCause))
|
|
1885
|
+
}, timeoutMs)
|
|
1886
|
+
})
|
|
1887
|
+
const beforeDeadline = (operation) => Promise.race([operation, deadline])
|
|
1888
|
+
const timeoutFailure = (cause) => new SemantifoldBrowserError("timeout", "Wasm loading timed out.",
|
|
1889
|
+
cause instanceof Error ? cause : controller.signal.reason)
|
|
1890
|
+
const bodyFailure = (cause) => {
|
|
1891
|
+
if (cause instanceof SemantifoldBrowserError) return cause
|
|
1892
|
+
if (timedOut) return timeoutFailure(cause)
|
|
1893
|
+
return new SemantifoldBrowserError("fetch/MIME", "Wasm response body could not be read.", cause)
|
|
1894
|
+
}
|
|
1895
|
+
const instantiationFailure = (cause) => {
|
|
1896
|
+
if (cause instanceof SemantifoldBrowserError) return cause
|
|
1897
|
+
if (timedOut) return timeoutFailure(cause)
|
|
1898
|
+
const stage = cause instanceof WebAssembly.CompileError ? "compile" : "instantiate/import"
|
|
1899
|
+
|
|
1900
|
+
return new SemantifoldBrowserError(stage, \`Wasm \${stage} failed.\`, cause)
|
|
1901
|
+
}
|
|
1902
|
+
const readBody = async (body) => {
|
|
1903
|
+
try {
|
|
1904
|
+
return new Uint8Array(await beforeDeadline(body.arrayBuffer()))
|
|
1905
|
+
} catch (cause) {
|
|
1906
|
+
throw bodyFailure(cause)
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
const instantiate = async (operation) => {
|
|
1910
|
+
try {
|
|
1911
|
+
return await beforeDeadline(operation())
|
|
1912
|
+
} catch (cause) {
|
|
1913
|
+
throw instantiationFailure(cause)
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
let response
|
|
1917
|
+
|
|
1918
|
+
try {
|
|
1919
|
+
response = await beforeDeadline(Promise.resolve().then(() =>
|
|
1920
|
+
fetchImplementation(url, {credentials: "same-origin", redirect: "error", signal: controller.signal})))
|
|
1921
|
+
} catch (cause) {
|
|
1922
|
+
clearTimeout(timeout)
|
|
1923
|
+
if (cause instanceof SemantifoldBrowserError) throw cause
|
|
1924
|
+
const stage = timedOut ? "timeout" : "fetch/MIME"
|
|
1925
|
+
throw new SemantifoldBrowserError(stage, stage == "timeout" ? "Wasm loading timed out." : "Wasm fetch failed.", cause)
|
|
1926
|
+
}
|
|
1927
|
+
if (!response.ok) {
|
|
1928
|
+
clearTimeout(timeout)
|
|
1929
|
+
throw new SemantifoldBrowserError("fetch/MIME", \`Wasm fetch returned HTTP \${response.status}.\`)
|
|
1930
|
+
}
|
|
1931
|
+
const mediaType = response.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase()
|
|
1932
|
+
|
|
1933
|
+
if (mediaType != "application/wasm") {
|
|
1934
|
+
clearTimeout(timeout)
|
|
1935
|
+
throw new SemantifoldBrowserError("fetch/MIME", "Wasm response requires application/wasm.")
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
let instance
|
|
1939
|
+
let module
|
|
1940
|
+
const decoder = new TextDecoder("utf-8", {fatal: true, ignoreBOM: true})
|
|
1941
|
+
let memory
|
|
1942
|
+
let abiApproved = false
|
|
1943
|
+
const requireApprovedAbi = () => {
|
|
1944
|
+
if (!abiApproved) throw new SemantifoldBrowserError("instantiate/import", "Wasm host imports are locked until ABI inspection succeeds.")
|
|
1945
|
+
}
|
|
1946
|
+
const append = (text) => sink(\`\${text}\\n\`)
|
|
1947
|
+
const imports = {semantifold: {
|
|
1948
|
+
print_i64(value) {
|
|
1949
|
+
requireApprovedAbi()
|
|
1950
|
+
if (typeof value != "bigint") throw new TypeError("print_i64 requires bigint.")
|
|
1951
|
+
append(value.toString(10))
|
|
1952
|
+
},
|
|
1953
|
+
print_bool(value) {
|
|
1954
|
+
requireApprovedAbi()
|
|
1955
|
+
if (value !== 0 && value !== 1) throw new TypeError("print_bool requires canonical 0 or 1.")
|
|
1956
|
+
append(value === 1 ? "true" : "false")
|
|
1957
|
+
},
|
|
1958
|
+
print_string(pointer, length) {
|
|
1959
|
+
requireApprovedAbi()
|
|
1960
|
+
if (!memory || !Number.isInteger(pointer) || pointer < 0 || !Number.isInteger(length) || length < 0) {
|
|
1961
|
+
const cause = new TypeError("print_string requires a non-negative integer pointer and length.")
|
|
1962
|
+
|
|
1963
|
+
throw new SemantifoldBrowserError("memory bounds/UTF-8", cause.message, cause)
|
|
1964
|
+
}
|
|
1965
|
+
const end = pointer + length
|
|
1966
|
+
|
|
1967
|
+
if (!Number.isSafeInteger(end) || end < pointer || end > memory.buffer.byteLength) {
|
|
1968
|
+
const cause = new RangeError("print_string range is outside exported memory.")
|
|
1969
|
+
|
|
1970
|
+
throw new SemantifoldBrowserError("memory bounds/UTF-8", cause.message, cause)
|
|
1971
|
+
}
|
|
1972
|
+
let text
|
|
1973
|
+
|
|
1974
|
+
try {
|
|
1975
|
+
text = decoder.decode(new Uint8Array(memory.buffer, pointer, length))
|
|
1976
|
+
} catch (cause) {
|
|
1977
|
+
throw new SemantifoldBrowserError("memory bounds/UTF-8", "print_string bytes are not valid UTF-8.", cause)
|
|
1978
|
+
}
|
|
1979
|
+
append(text)
|
|
1980
|
+
}
|
|
1981
|
+
}}
|
|
1982
|
+
|
|
1983
|
+
let result
|
|
1984
|
+
|
|
1985
|
+
try {
|
|
1986
|
+
if (typeof WebAssembly.instantiateStreaming == "function") {
|
|
1987
|
+
let body
|
|
1988
|
+
|
|
1989
|
+
try {
|
|
1990
|
+
body = response.clone()
|
|
1991
|
+
} catch (cause) {
|
|
1992
|
+
throw bodyFailure(cause)
|
|
1993
|
+
}
|
|
1994
|
+
const pending = instantiate(() => WebAssembly.instantiateStreaming(response, imports))
|
|
1995
|
+
.then((value) => ({value}), (error) => ({error}))
|
|
1996
|
+
const bytes = await readBody(body)
|
|
1997
|
+
|
|
1998
|
+
inspectAbi(bytes)
|
|
1999
|
+
abiApproved = true
|
|
2000
|
+
const settled = await pending
|
|
2001
|
+
|
|
2002
|
+
if ("error" in settled) throw settled.error
|
|
2003
|
+
result = settled.value
|
|
2004
|
+
} else {
|
|
2005
|
+
const bytes = await readBody(response)
|
|
2006
|
+
|
|
2007
|
+
inspectAbi(bytes)
|
|
2008
|
+
abiApproved = true
|
|
2009
|
+
result = await instantiate(() => WebAssembly.instantiate(bytes, imports))
|
|
2010
|
+
}
|
|
2011
|
+
} catch (error) {
|
|
2012
|
+
clearTimeout(timeout)
|
|
2013
|
+
throw error
|
|
2014
|
+
}
|
|
2015
|
+
clearTimeout(timeout)
|
|
2016
|
+
instance = result.instance
|
|
2017
|
+
module = result.module
|
|
2018
|
+
validateModuleShape(module, instance)
|
|
2019
|
+
memory = instance.exports.memory
|
|
2020
|
+
let active = false
|
|
2021
|
+
let poisoned = false
|
|
2022
|
+
|
|
2023
|
+
return Object.freeze({
|
|
2024
|
+
memory,
|
|
2025
|
+
run() {
|
|
2026
|
+
if (poisoned) throw new SemantifoldBrowserError("invocation/trap", "Wasm instance is poisoned after a trap.")
|
|
2027
|
+
if (active) throw new SemantifoldBrowserError("invocation/trap", "Reentrant run invocation is forbidden.")
|
|
2028
|
+
active = true
|
|
2029
|
+
try {
|
|
2030
|
+
instance.exports.run()
|
|
2031
|
+
} catch (cause) {
|
|
2032
|
+
poisoned = true
|
|
2033
|
+
if (cause instanceof SemantifoldBrowserError) throw cause
|
|
2034
|
+
throw new SemantifoldBrowserError("invocation/trap", "Wasm run trapped.", cause)
|
|
2035
|
+
} finally {
|
|
2036
|
+
active = false
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
})
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
function validateModuleShape(module, instance) {
|
|
2043
|
+
const imports = WebAssembly.Module.imports(module)
|
|
2044
|
+
const exports = WebAssembly.Module.exports(module)
|
|
2045
|
+
|
|
2046
|
+
if (JSON.stringify(imports.map(({kind, module, name}) => [module, name, kind])) !=
|
|
2047
|
+
JSON.stringify(expectedImports.map(([module, name]) => [module, name, "function"]))) {
|
|
2048
|
+
throw new SemantifoldBrowserError("instantiate/import", "Wasm imports do not match semantifold.browser.v1.")
|
|
2049
|
+
}
|
|
2050
|
+
if (JSON.stringify(exports.map(({kind, name}) => [name, kind])) != JSON.stringify([["memory", "memory"], ["run", "function"]]) ||
|
|
2051
|
+
!(instance.exports.memory instanceof WebAssembly.Memory) || typeof instance.exports.run != "function" || instance.exports.run.length != 0) {
|
|
2052
|
+
throw new SemantifoldBrowserError("instantiate/import", "Wasm exports do not match semantifold.browser.v1.")
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
function inspectAbi(bytes) {
|
|
2057
|
+
try {
|
|
2058
|
+
let offset = 8
|
|
2059
|
+
const types = []
|
|
2060
|
+
const importedTypes = []
|
|
2061
|
+
const definedTypes = []
|
|
2062
|
+
const exports = []
|
|
2063
|
+
const memories = []
|
|
2064
|
+
|
|
2065
|
+
if (bytes.length < 8 || bytes[0] != 0 || bytes[1] != 97 || bytes[2] != 115 || bytes[3] != 109 || bytes[4] != 1 ||
|
|
2066
|
+
bytes[5] != 0 || bytes[6] != 0 || bytes[7] != 0) throw new WebAssembly.CompileError("Invalid Wasm header.")
|
|
2067
|
+
while (offset < bytes.length) {
|
|
2068
|
+
const id = bytes[offset++]
|
|
2069
|
+
const sizeValue = readU32(bytes, offset)
|
|
2070
|
+
offset = sizeValue.next
|
|
2071
|
+
const end = offset + sizeValue.value
|
|
2072
|
+
|
|
2073
|
+
if (end > bytes.length) throw new WebAssembly.CompileError("Truncated Wasm section.")
|
|
2074
|
+
if (id == 1) {
|
|
2075
|
+
const count = readU32(bytes, offset); offset = count.next
|
|
2076
|
+
for (let index = 0; index < count.value; index += 1) {
|
|
2077
|
+
if (bytes[offset++] != 0x60) throw new WebAssembly.CompileError("Invalid function type.")
|
|
2078
|
+
const parameters = readTypes(bytes, offset); offset = parameters.next
|
|
2079
|
+
const results = readTypes(bytes, offset); offset = results.next
|
|
2080
|
+
types.push([parameters.values, results.values])
|
|
2081
|
+
}
|
|
2082
|
+
} else if (id == 2) {
|
|
2083
|
+
const count = readU32(bytes, offset); offset = count.next
|
|
2084
|
+
for (let index = 0; index < count.value; index += 1) {
|
|
2085
|
+
const moduleName = readName(bytes, offset); offset = moduleName.next
|
|
2086
|
+
const fieldName = readName(bytes, offset); offset = fieldName.next
|
|
2087
|
+
if (bytes[offset++] != 0) throw new SemantifoldBrowserError("instantiate/import", "Only function imports are allowed.")
|
|
2088
|
+
const type = readU32(bytes, offset); offset = type.next
|
|
2089
|
+
importedTypes.push([moduleName.value, fieldName.value, type.value])
|
|
2090
|
+
}
|
|
2091
|
+
} else if (id == 3) {
|
|
2092
|
+
const count = readU32(bytes, offset); offset = count.next
|
|
2093
|
+
for (let index = 0; index < count.value; index += 1) {
|
|
2094
|
+
const type = readU32(bytes, offset); offset = type.next; definedTypes.push(type.value)
|
|
2095
|
+
}
|
|
2096
|
+
} else if (id == 5) {
|
|
2097
|
+
const count = readU32(bytes, offset); offset = count.next
|
|
2098
|
+
for (let index = 0; index < count.value; index += 1) {
|
|
2099
|
+
const flags = bytes[offset++]
|
|
2100
|
+
const minimum = readU32(bytes, offset); offset = minimum.next
|
|
2101
|
+
let maximum
|
|
2102
|
+
if ((flags & 1) != 0) { maximum = readU32(bytes, offset); offset = maximum.next }
|
|
2103
|
+
memories.push([flags, minimum.value, maximum?.value])
|
|
2104
|
+
}
|
|
2105
|
+
} else if (id == 7) {
|
|
2106
|
+
const count = readU32(bytes, offset); offset = count.next
|
|
2107
|
+
for (let index = 0; index < count.value; index += 1) {
|
|
2108
|
+
const name = readName(bytes, offset); offset = name.next
|
|
2109
|
+
const kind = bytes[offset++]
|
|
2110
|
+
const target = readU32(bytes, offset); offset = target.next
|
|
2111
|
+
exports.push([name.value, kind, target.value])
|
|
2112
|
+
}
|
|
2113
|
+
} else if (id == 8) throw new SemantifoldBrowserError("instantiate/import", "A Wasm start section is forbidden.")
|
|
2114
|
+
offset = end
|
|
2115
|
+
}
|
|
2116
|
+
if (importedTypes.length != expectedImports.length) throw new SemantifoldBrowserError("instantiate/import", "Unexpected Wasm import count.")
|
|
2117
|
+
for (let index = 0; index < expectedImports.length; index += 1) {
|
|
2118
|
+
const expected = expectedImports[index]
|
|
2119
|
+
const actual = importedTypes[index]
|
|
2120
|
+
const type = types[actual?.[2]]
|
|
2121
|
+
|
|
2122
|
+
if (!actual || actual[0] != expected[0] || actual[1] != expected[1] || !type ||
|
|
2123
|
+
JSON.stringify(type) != JSON.stringify([expected[2], expected[3]])) {
|
|
2124
|
+
throw new SemantifoldBrowserError("instantiate/import", "Wasm import signature does not match semantifold.browser.v1.")
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
if (memories.length != 1 || memories[0][0] != 1 || memories[0][1] != memories[0][2]) {
|
|
2128
|
+
throw new SemantifoldBrowserError("instantiate/import", "Wasm memory must have equal fixed initial and maximum limits.")
|
|
2129
|
+
}
|
|
2130
|
+
if (exports.length != 2 || exports[0][0] != "memory" || exports[0][1] != 2 || exports[0][2] != 0 ||
|
|
2131
|
+
exports[1][0] != "run" || exports[1][1] != 0) {
|
|
2132
|
+
throw new SemantifoldBrowserError("instantiate/import", "Wasm exports do not match semantifold.browser.v1.")
|
|
2133
|
+
}
|
|
2134
|
+
const runType = types[definedTypes[exports[1][2] - importedTypes.length]]
|
|
2135
|
+
|
|
2136
|
+
if (!runType || runType[0].length != 0 || runType[1].length != 0) {
|
|
2137
|
+
throw new SemantifoldBrowserError("instantiate/import", "Wasm run signature does not match semantifold.browser.v1.")
|
|
2138
|
+
}
|
|
2139
|
+
} catch (cause) {
|
|
2140
|
+
if (cause instanceof SemantifoldBrowserError) throw cause
|
|
2141
|
+
throw new SemantifoldBrowserError("compile", "Wasm binary inspection failed.", cause)
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
function readTypes(bytes, offset) {
|
|
2146
|
+
const count = readU32(bytes, offset); offset = count.next; const values = []
|
|
2147
|
+
for (let index = 0; index < count.value; index += 1) {
|
|
2148
|
+
const type = bytes[offset++]
|
|
2149
|
+
if (type == 0x7f) values.push("i32")
|
|
2150
|
+
else if (type == 0x7e) values.push("i64")
|
|
2151
|
+
else if (type == 0x7d) values.push("f32")
|
|
2152
|
+
else if (type == 0x7c) values.push("f64")
|
|
2153
|
+
else if (type == 0x7b) values.push("v128")
|
|
2154
|
+
else if (type == 0x70) values.push("funcref")
|
|
2155
|
+
else if (type == 0x6f) values.push("externref")
|
|
2156
|
+
else throw new WebAssembly.CompileError("Unsupported ABI value type.")
|
|
2157
|
+
}
|
|
2158
|
+
return {next: offset, values}
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
function readName(bytes, offset) {
|
|
2162
|
+
const length = readU32(bytes, offset); const end = length.next + length.value
|
|
2163
|
+
if (end > bytes.length) throw new WebAssembly.CompileError("Truncated Wasm name.")
|
|
2164
|
+
return {next: end, value: new TextDecoder("utf-8", {fatal: true}).decode(bytes.subarray(length.next, end))}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
function readU32(bytes, offset) {
|
|
2168
|
+
let value = 0
|
|
2169
|
+
let shift = 0
|
|
2170
|
+
for (let index = 0; index < 5; index += 1) {
|
|
2171
|
+
if (offset >= bytes.length) throw new WebAssembly.CompileError("Truncated unsigned LEB128.")
|
|
2172
|
+
const byte = bytes[offset++]
|
|
2173
|
+
value += (byte & 0x7f) * 2 ** shift
|
|
2174
|
+
if ((byte & 0x80) == 0) return {next: offset, value}
|
|
2175
|
+
shift += 7
|
|
2176
|
+
}
|
|
2177
|
+
throw new WebAssembly.CompileError("Oversized unsigned LEB128.")
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
async function runBrowserHarness() {
|
|
2181
|
+
const output = document.querySelector("#output")
|
|
2182
|
+
|
|
2183
|
+
try {
|
|
2184
|
+
const program = await loadSemantifold("./program.wasm", {sink: (line) => { output.textContent += line }})
|
|
2185
|
+
program.run()
|
|
2186
|
+
document.documentElement.dataset.semantifoldOutput = [...new TextEncoder().encode(output.textContent)]
|
|
2187
|
+
.map((byte) => byte.toString(16).padStart(2, "0")).join("")
|
|
2188
|
+
document.documentElement.dataset.semantifold = "pass"
|
|
2189
|
+
console.log("SEMANTIFOLD_BROWSER_OK")
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
document.documentElement.dataset.semantifold = "fail"
|
|
2192
|
+
output.textContent += \`ERROR[\${error.stage ?? "unknown"}]: \${error.message}\\n\`
|
|
2193
|
+
console.error("SEMANTIFOLD_BROWSER_FAILED", error)
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
if (typeof document != "undefined" && document.documentElement.dataset.semantifold == "pending") void runBrowserHarness()
|
|
2198
|
+
`
|
|
2199
|
+
|
|
2200
|
+
const browserHarness = `<!doctype html>
|
|
2201
|
+
<html lang="en" data-semantifold="pending">
|
|
2202
|
+
<meta charset="utf-8">
|
|
2203
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self'; base-uri 'none'; form-action 'none'">
|
|
2204
|
+
<title>Semantifold Browser Wasm</title>
|
|
2205
|
+
<pre id="output"></pre>
|
|
2206
|
+
<script type="module" src="./semantifold-loader.mjs"></script>
|
|
2207
|
+
</html>
|
|
2208
|
+
`
|