gofront 1.5.1 → 1.6.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 +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +50 -19
- package/src/backend/js/index.js +100 -55
- package/src/backend/js/runtime.js +102 -0
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +96 -16
- package/src/backend/js/stdlib/errors.js +2 -2
- package/src/backend/js/stdlib/fmt.js +26 -8
- package/src/backend/js/stdlib/index.js +3 -0
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +1 -1
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +34 -9
- package/src/backend/js/stdlib/strings.js +4 -1
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +3 -3
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +486 -33
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +606 -5009
- package/src/backend/wasm/encode.js +212 -64
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +1426 -49
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +159 -35
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +275 -72
- package/src/compiler.js +144 -21
- package/src/index.js +86 -115
- package/src/lower/boxing.js +3 -4
- package/src/lower/captures.js +10 -17
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +5 -10
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/resolver.js +2 -1
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +142 -2
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +17 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +206 -7
- package/src/vendor.js +2 -2
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// src/backend/wasm/optimize.js
|
|
2
|
+
// Binaryen optimization pipeline for GoFront WebAssembly modules.
|
|
3
|
+
//
|
|
4
|
+
// Supports:
|
|
5
|
+
// - Bundled Binaryen optimizer (binaryen npm package)
|
|
6
|
+
// - Native wasm-opt binary auto-detection with graceful fallback
|
|
7
|
+
// - WasmGC feature flag configuration (GC, ReferenceTypes, BulkMemory, Multivalue, ExceptionHandling, etc.)
|
|
8
|
+
// - Optimization levels (-O3 by default) + GUFA (Grand Unified Flow Analysis)
|
|
9
|
+
// - Post-optimization module validation (WebAssembly.validate)
|
|
10
|
+
// - WASM source map passthrough (`-ism` the encoder's function-level map,
|
|
11
|
+
// `-osm` the remapped one) and GoFront custom section preservation
|
|
12
|
+
|
|
13
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
rmSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
} from "node:fs";
|
|
21
|
+
import { createRequire } from "node:module";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { GOFRONT_SECTION_BYTES, isGoFrontWasm } from "./encode.js";
|
|
25
|
+
|
|
26
|
+
// Binaryen feature bitmask: GC (1024) | ReferenceTypes (256) | BulkMemory (16) | Multivalue (512) |
|
|
27
|
+
// ExceptionHandling (64) | MutableGlobals (2) | NontrappingFPToInt (4) | TailCall (128) = 2006
|
|
28
|
+
export const DEFAULT_WASM_FEATURES = 2006;
|
|
29
|
+
|
|
30
|
+
let _nativeWasmOptAvailable = null;
|
|
31
|
+
let _npmWasmOptPath = null;
|
|
32
|
+
|
|
33
|
+
const _require = createRequire(import.meta.url);
|
|
34
|
+
|
|
35
|
+
function getNpmWasmOptPath() {
|
|
36
|
+
if (_npmWasmOptPath === null) {
|
|
37
|
+
try {
|
|
38
|
+
_npmWasmOptPath = _require.resolve("binaryen/bin/wasm-opt");
|
|
39
|
+
} catch {
|
|
40
|
+
_npmWasmOptPath = "";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return _npmWasmOptPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function hasNativeWasmOpt() {
|
|
47
|
+
if (_nativeWasmOptAvailable === null) {
|
|
48
|
+
try {
|
|
49
|
+
const res = spawnSync("wasm-opt", ["--version"], { stdio: "ignore" });
|
|
50
|
+
_nativeWasmOptAvailable = res.status === 0;
|
|
51
|
+
} catch {
|
|
52
|
+
_nativeWasmOptAvailable = false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return _nativeWasmOptAvailable;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Injects GoFront custom section right after the 8-byte WASM header so that
|
|
59
|
+
// isGoFrontWasm recognises the module for clean artifact cleanup.
|
|
60
|
+
export function injectGoFrontSection(bytes) {
|
|
61
|
+
if (isGoFrontWasm(bytes)) return bytes;
|
|
62
|
+
if (!bytes || bytes.length < 8) return bytes;
|
|
63
|
+
const header = bytes.subarray(0, 8);
|
|
64
|
+
const rest = bytes.subarray(8);
|
|
65
|
+
const tagged = new Uint8Array(
|
|
66
|
+
header.length + GOFRONT_SECTION_BYTES.length + rest.length,
|
|
67
|
+
);
|
|
68
|
+
tagged.set(header, 0);
|
|
69
|
+
tagged.set(GOFRONT_SECTION_BYTES, header.length);
|
|
70
|
+
tagged.set(rest, header.length + GOFRONT_SECTION_BYTES.length);
|
|
71
|
+
return tagged;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function runWasmOpt(
|
|
75
|
+
command,
|
|
76
|
+
cmdArgs,
|
|
77
|
+
wasmBytes,
|
|
78
|
+
options = {},
|
|
79
|
+
engine = "native",
|
|
80
|
+
) {
|
|
81
|
+
const dir = mkdtempSync(join(tmpdir(), "gofront-wasmopt-"));
|
|
82
|
+
const input = join(dir, "in.wasm");
|
|
83
|
+
const output = join(dir, "out.wasm");
|
|
84
|
+
const mapOutput = join(dir, "out.wasm.map");
|
|
85
|
+
const watOutput = join(dir, "out.wat");
|
|
86
|
+
try {
|
|
87
|
+
writeFileSync(input, wasmBytes);
|
|
88
|
+
const level = options.level ?? 3;
|
|
89
|
+
const shrinkLevel = options.shrinkLevel ?? 0;
|
|
90
|
+
const args = [
|
|
91
|
+
`-O${level}`,
|
|
92
|
+
`--shrink-level=${shrinkLevel}`,
|
|
93
|
+
"--enable-gc",
|
|
94
|
+
"--enable-reference-types",
|
|
95
|
+
"--enable-exception-handling",
|
|
96
|
+
"--enable-multivalue",
|
|
97
|
+
"--enable-bulk-memory",
|
|
98
|
+
"--enable-mutable-globals",
|
|
99
|
+
"--enable-nontrapping-float-to-int",
|
|
100
|
+
"--enable-tail-call",
|
|
101
|
+
];
|
|
102
|
+
if (options.gufa !== false) {
|
|
103
|
+
args.push("--gufa");
|
|
104
|
+
}
|
|
105
|
+
if (options.sourceMap) {
|
|
106
|
+
if (options.inputSourceMap) {
|
|
107
|
+
const mapInput = join(dir, "in.wasm.map");
|
|
108
|
+
writeFileSync(mapInput, options.inputSourceMap);
|
|
109
|
+
args.push("-ism", mapInput);
|
|
110
|
+
}
|
|
111
|
+
args.push("-osm", mapOutput);
|
|
112
|
+
if (options.sourceMapUrl) {
|
|
113
|
+
args.push("-osu", options.sourceMapUrl);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
args.push(input, "-o", output);
|
|
117
|
+
execFileSync(command, [...cmdArgs, ...args], { stdio: "pipe" });
|
|
118
|
+
const optimizedBytes = new Uint8Array(readFileSync(output));
|
|
119
|
+
let sourceMap = null;
|
|
120
|
+
if (options.sourceMap && existsSync(mapOutput)) {
|
|
121
|
+
sourceMap = readFileSync(mapOutput, "utf8");
|
|
122
|
+
}
|
|
123
|
+
let wat = null;
|
|
124
|
+
if (options.emitWat) {
|
|
125
|
+
try {
|
|
126
|
+
const watArgs = [
|
|
127
|
+
`-O${level}`,
|
|
128
|
+
`--shrink-level=${shrinkLevel}`,
|
|
129
|
+
"--enable-gc",
|
|
130
|
+
"--enable-reference-types",
|
|
131
|
+
"--enable-exception-handling",
|
|
132
|
+
"--enable-multivalue",
|
|
133
|
+
"--enable-bulk-memory",
|
|
134
|
+
"--enable-mutable-globals",
|
|
135
|
+
"--enable-nontrapping-float-to-int",
|
|
136
|
+
"--enable-tail-call",
|
|
137
|
+
];
|
|
138
|
+
if (options.gufa !== false) watArgs.push("--gufa");
|
|
139
|
+
watArgs.push(input, "-S", "-o", watOutput);
|
|
140
|
+
execFileSync(command, [...cmdArgs, ...watArgs], { stdio: "pipe" });
|
|
141
|
+
if (existsSync(watOutput)) {
|
|
142
|
+
wat = readFileSync(watOutput, "utf8");
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// Continue if WAT generation fails
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const tagged = injectGoFrontSection(optimizedBytes);
|
|
149
|
+
if (typeof WebAssembly !== "undefined" && !WebAssembly.validate(tagged)) {
|
|
150
|
+
throw new Error("WASM module validation failed after optimization");
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
wasm: tagged,
|
|
154
|
+
wat,
|
|
155
|
+
sourceMap,
|
|
156
|
+
engine,
|
|
157
|
+
};
|
|
158
|
+
} catch (e) {
|
|
159
|
+
const stderr = e.stderr ? e.stderr.toString() : e.message;
|
|
160
|
+
throw new Error(`WASM optimization failed: ${stderr}`);
|
|
161
|
+
} finally {
|
|
162
|
+
rmSync(dir, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function optimizeWithNative(wasmBytes, options = {}) {
|
|
167
|
+
return runWasmOpt("wasm-opt", [], wasmBytes, options, "native");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function optimizeWithBinaryen(wasmBytes, options = {}) {
|
|
171
|
+
const npmPath = getNpmWasmOptPath();
|
|
172
|
+
if (!npmPath) {
|
|
173
|
+
throw new Error("Binaryen npm package wasm-opt binary not found");
|
|
174
|
+
}
|
|
175
|
+
return runWasmOpt(
|
|
176
|
+
process.execPath,
|
|
177
|
+
[npmPath],
|
|
178
|
+
wasmBytes,
|
|
179
|
+
options,
|
|
180
|
+
"binaryen",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function optimizeWasm(wasmBytes, options = {}) {
|
|
185
|
+
if (!wasmBytes || wasmBytes.length === 0) {
|
|
186
|
+
return {
|
|
187
|
+
wasm: wasmBytes,
|
|
188
|
+
wat: null,
|
|
189
|
+
sourceMap: null,
|
|
190
|
+
originalSize: 0,
|
|
191
|
+
optimizedSize: 0,
|
|
192
|
+
savedBytes: 0,
|
|
193
|
+
percentSaved: 0,
|
|
194
|
+
engine: "none",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const originalSize = wasmBytes.length;
|
|
199
|
+
let res;
|
|
200
|
+
if (options.preferNative !== false && hasNativeWasmOpt()) {
|
|
201
|
+
try {
|
|
202
|
+
res = optimizeWithNative(wasmBytes, options);
|
|
203
|
+
} catch {
|
|
204
|
+
res = optimizeWithBinaryen(wasmBytes, options);
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
res = optimizeWithBinaryen(wasmBytes, options);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const optimizedSize = res.wasm.length;
|
|
211
|
+
const savedBytes = originalSize - optimizedSize;
|
|
212
|
+
const percentSaved =
|
|
213
|
+
originalSize > 0
|
|
214
|
+
? Number(((savedBytes / originalSize) * 100).toFixed(1))
|
|
215
|
+
: 0;
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
wasm: res.wasm,
|
|
219
|
+
wat: res.wat ?? null,
|
|
220
|
+
sourceMap: res.sourceMap ?? null,
|
|
221
|
+
originalSize,
|
|
222
|
+
optimizedSize,
|
|
223
|
+
savedBytes,
|
|
224
|
+
percentSaved,
|
|
225
|
+
engine: res.engine,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
// src/backend/wasm/types.js
|
|
2
2
|
// Maps GoFront types to WasmGC types and manages type registry.
|
|
3
3
|
|
|
4
|
+
import { SHARED_KINDS } from "../../typechecker/stdlib/shared.js";
|
|
5
|
+
|
|
6
|
+
// Name of the runtime's error struct (`errors.New`, `fmt.Errorf`, strconv).
|
|
7
|
+
export const ERROR_STRUCT = "__errorString";
|
|
8
|
+
|
|
4
9
|
// `*testing.T` is a JS-side object (the harness's __GoFront_T) passed through
|
|
5
10
|
// the boundary as an opaque externref.
|
|
6
11
|
export function isTestingT(goType) {
|
|
@@ -16,6 +21,22 @@ export function isTestingT(goType) {
|
|
|
16
21
|
|
|
17
22
|
export function toWasmType(goType, checker = null, mod = null) {
|
|
18
23
|
if (!goType) return "i32";
|
|
24
|
+
if (typeof goType === "string") {
|
|
25
|
+
if (
|
|
26
|
+
goType === "i32" ||
|
|
27
|
+
goType === "i64" ||
|
|
28
|
+
goType === "f32" ||
|
|
29
|
+
goType === "f64" ||
|
|
30
|
+
goType === "anyref" ||
|
|
31
|
+
goType === "externref" ||
|
|
32
|
+
goType === "funcref"
|
|
33
|
+
) {
|
|
34
|
+
return goType;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (typeof goType === "object" && goType.kind === "ref") {
|
|
38
|
+
return goType;
|
|
39
|
+
}
|
|
19
40
|
if (isTestingT(goType)) return "externref";
|
|
20
41
|
|
|
21
42
|
// Pointer AST nodes
|
|
@@ -30,13 +51,20 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
30
51
|
);
|
|
31
52
|
}
|
|
32
53
|
|
|
33
|
-
// Slice and
|
|
54
|
+
// Slice, Array, and Map AST nodes
|
|
34
55
|
if (goType.kind === "SliceType") {
|
|
35
56
|
return toWasmType({ kind: "slice", elem: goType.elem }, checker, mod);
|
|
36
57
|
}
|
|
37
58
|
if (goType.kind === "ArrayType") {
|
|
38
59
|
return toWasmType({ kind: "array", elem: goType.elem }, checker, mod);
|
|
39
60
|
}
|
|
61
|
+
if (goType.kind === "MapType") {
|
|
62
|
+
return toWasmType(
|
|
63
|
+
{ kind: "map", key: goType.key, value: goType.value ?? goType.elem },
|
|
64
|
+
checker,
|
|
65
|
+
mod,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
40
68
|
|
|
41
69
|
// TypeName AST node
|
|
42
70
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
@@ -144,6 +172,16 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
144
172
|
return { kind: "ref", nullable: true, heapType: "any" };
|
|
145
173
|
}
|
|
146
174
|
|
|
175
|
+
if (goType.kind === "shared") {
|
|
176
|
+
if (mod?.getSharedType)
|
|
177
|
+
return {
|
|
178
|
+
kind: "ref",
|
|
179
|
+
nullable: true,
|
|
180
|
+
typeIndex: mod.getSharedType().typeIndex,
|
|
181
|
+
};
|
|
182
|
+
return { kind: "ref", nullable: true, heapType: "struct" };
|
|
183
|
+
}
|
|
184
|
+
|
|
147
185
|
if (goType.kind === "slice") {
|
|
148
186
|
if (mod?.getSliceType && goType.elem) {
|
|
149
187
|
const sliceInfo = mod.getSliceType(goType.elem);
|
|
@@ -168,6 +206,20 @@ export function toWasmType(goType, checker = null, mod = null) {
|
|
|
168
206
|
return { kind: "ref", nullable: true, heapType: "array" };
|
|
169
207
|
}
|
|
170
208
|
|
|
209
|
+
if (goType.kind === "map") {
|
|
210
|
+
const key = goType.key;
|
|
211
|
+
const val = goType.value ?? goType.elem;
|
|
212
|
+
if (mod?.getMapType && key && val) {
|
|
213
|
+
const mapInfo = mod.getMapType(key, val);
|
|
214
|
+
return {
|
|
215
|
+
kind: "ref",
|
|
216
|
+
nullable: true,
|
|
217
|
+
typeIndex: mapInfo.typeIndex,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return { kind: "ref", nullable: true, heapType: "struct" };
|
|
221
|
+
}
|
|
222
|
+
|
|
171
223
|
if (goType.kind === "struct") {
|
|
172
224
|
if (goType.name && mod?.getStructType(goType.name)) {
|
|
173
225
|
return {
|
|
@@ -235,6 +287,20 @@ export function isSliceType(goType, checker = null) {
|
|
|
235
287
|
return goType.kind === "slice" || goType.kind === "SliceType";
|
|
236
288
|
}
|
|
237
289
|
|
|
290
|
+
// `gofront/shared` linear-memory buffer: returns the SHARED_KINDS entry
|
|
291
|
+
// (elem, ctor, bytes, key) or null.
|
|
292
|
+
export function getSharedInfo(goType, checker = null) {
|
|
293
|
+
if (!goType) return null;
|
|
294
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
295
|
+
const resolved = checker?.types?.get(goType.name);
|
|
296
|
+
return resolved ? getSharedInfo(resolved, checker) : null;
|
|
297
|
+
}
|
|
298
|
+
if (goType.kind === "named" && goType.underlying)
|
|
299
|
+
return getSharedInfo(goType.underlying, checker);
|
|
300
|
+
if (goType.kind !== "shared") return null;
|
|
301
|
+
return { name: goType.shared, ...SHARED_KINDS[goType.shared] };
|
|
302
|
+
}
|
|
303
|
+
|
|
238
304
|
export function isArrayType(goType, checker = null) {
|
|
239
305
|
if (!goType) return false;
|
|
240
306
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
@@ -247,6 +313,37 @@ export function isArrayType(goType, checker = null) {
|
|
|
247
313
|
return goType.kind === "array" || goType.kind === "ArrayType";
|
|
248
314
|
}
|
|
249
315
|
|
|
316
|
+
export function isMapType(goType, checker = null) {
|
|
317
|
+
if (!goType) return false;
|
|
318
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
319
|
+
const resolved = checker?.types?.get(goType.name);
|
|
320
|
+
if (resolved) return isMapType(resolved, checker);
|
|
321
|
+
}
|
|
322
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
323
|
+
return isMapType(goType.underlying, checker);
|
|
324
|
+
}
|
|
325
|
+
return goType.kind === "map" || goType.kind === "MapType";
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Returns `{ keyType, valType }` (Go types) for a map type, or nulls.
|
|
329
|
+
export function getMapKeyValTypes(goType, checker = null) {
|
|
330
|
+
if (!goType) return { keyType: null, valType: null };
|
|
331
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
332
|
+
const resolved = checker?.types?.get(goType.name);
|
|
333
|
+
if (resolved) return getMapKeyValTypes(resolved, checker);
|
|
334
|
+
}
|
|
335
|
+
if (goType.kind === "named" && goType.underlying) {
|
|
336
|
+
return getMapKeyValTypes(goType.underlying, checker);
|
|
337
|
+
}
|
|
338
|
+
if (goType.kind === "map" || goType.kind === "MapType") {
|
|
339
|
+
const key = goType.key ?? goType.keyType;
|
|
340
|
+
const val =
|
|
341
|
+
goType.value ?? goType.elem ?? goType.valueType ?? goType.valType;
|
|
342
|
+
return { keyType: key, valType: val };
|
|
343
|
+
}
|
|
344
|
+
return { keyType: null, valType: null };
|
|
345
|
+
}
|
|
346
|
+
|
|
250
347
|
export function isStringType(goType, checker = null) {
|
|
251
348
|
if (!goType) return false;
|
|
252
349
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
@@ -279,27 +376,75 @@ export function isAnyType(goType, checker = null) {
|
|
|
279
376
|
return false;
|
|
280
377
|
}
|
|
281
378
|
|
|
282
|
-
export function
|
|
379
|
+
export function isInterfaceType(goType, checker = null) {
|
|
283
380
|
if (!goType) return false;
|
|
381
|
+
if (goType.kind === "interface" || goType.kind === "InterfaceType")
|
|
382
|
+
return true;
|
|
284
383
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
285
|
-
|
|
384
|
+
if (goType.name === "any" || goType.name === "interface{}") return true;
|
|
385
|
+
const resolved = checker?.types?.get(goType.name);
|
|
386
|
+
if (resolved) return isInterfaceType(resolved, checker);
|
|
286
387
|
}
|
|
287
|
-
if (goType.kind === "named"
|
|
288
|
-
|
|
388
|
+
if (goType.kind === "named") {
|
|
389
|
+
if (goType.name === "any" || goType.name === "interface{}") return true;
|
|
390
|
+
if (goType.underlying) return isInterfaceType(goType.underlying, checker);
|
|
391
|
+
const resolved = checker?.types?.get(goType.name);
|
|
392
|
+
if (resolved) return isInterfaceType(resolved, checker);
|
|
289
393
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// The built-in `error` interface (checker ERROR type or its name).
|
|
398
|
+
export function isErrorType(goType) {
|
|
399
|
+
if (!goType) return false;
|
|
400
|
+
if (goType.kind === "interface") return goType.name === "error";
|
|
401
|
+
if (
|
|
402
|
+
(goType.kind === "named" ||
|
|
403
|
+
goType.kind === "TypeName" ||
|
|
404
|
+
goType.kind === "Ident") &&
|
|
405
|
+
goType.name === "error"
|
|
406
|
+
) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
if (goType.kind === "named" && goType.underlying)
|
|
410
|
+
return isErrorType(goType.underlying);
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function isNonEmptyInterface(goType, checker = null) {
|
|
415
|
+
if (!goType) return false;
|
|
416
|
+
if (goType.name === "any" || goType.name === "interface{}") return false;
|
|
417
|
+
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
418
|
+
const resolved = checker?.types?.get(goType.name);
|
|
419
|
+
if (resolved) return isNonEmptyInterface(resolved, checker);
|
|
420
|
+
}
|
|
421
|
+
if (goType.kind === "named") {
|
|
422
|
+
if (goType.name === "any" || goType.name === "interface{}") return false;
|
|
423
|
+
if (goType.underlying)
|
|
424
|
+
return isNonEmptyInterface(goType.underlying, checker);
|
|
425
|
+
const resolved = checker?.types?.get(goType.name);
|
|
426
|
+
if (resolved) return isNonEmptyInterface(resolved, checker);
|
|
427
|
+
}
|
|
428
|
+
if (goType.kind === "interface" || goType.kind === "InterfaceType") {
|
|
429
|
+
if (goType.methods instanceof Map) return goType.methods.size > 0;
|
|
430
|
+
if (Array.isArray(goType.methods)) return goType.methods.length > 0;
|
|
431
|
+
if (goType.methods instanceof Set) return goType.methods.size > 0;
|
|
432
|
+
return false;
|
|
299
433
|
}
|
|
300
434
|
return false;
|
|
301
435
|
}
|
|
302
436
|
|
|
437
|
+
export function getReceiverTypeName(recvType, _node = null) {
|
|
438
|
+
if (recvType) {
|
|
439
|
+
let t = recvType;
|
|
440
|
+
while (t.kind === "pointer") t = t.base;
|
|
441
|
+
if (t.name) return t.name;
|
|
442
|
+
if (t.kind === "named") return t.name;
|
|
443
|
+
if (t.kind === "TypeName" || t.kind === "Ident") return t.name;
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
|
|
303
448
|
export function isSigned(goType) {
|
|
304
449
|
if (!goType) return true;
|
|
305
450
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
@@ -329,27 +474,6 @@ export function isFloat(goType) {
|
|
|
329
474
|
);
|
|
330
475
|
}
|
|
331
476
|
|
|
332
|
-
export function isInt(goType) {
|
|
333
|
-
if (!goType) return false;
|
|
334
|
-
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
|
335
|
-
return isInt({ kind: "basic", name: goType.name });
|
|
336
|
-
}
|
|
337
|
-
if (goType.kind === "named" && goType.underlying) {
|
|
338
|
-
return isInt(goType.underlying);
|
|
339
|
-
}
|
|
340
|
-
if (goType.kind === "untyped") return goType.base === "int";
|
|
341
|
-
if (goType.kind === "basic") {
|
|
342
|
-
return (
|
|
343
|
-
goType.name.startsWith("int") ||
|
|
344
|
-
goType.name.startsWith("uint") ||
|
|
345
|
-
goType.name === "byte" ||
|
|
346
|
-
goType.name === "rune" ||
|
|
347
|
-
goType.name === "uintptr"
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
return false;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
477
|
export function isFuncType(goType, checker = null) {
|
|
354
478
|
if (!goType) return false;
|
|
355
479
|
if (goType.kind === "TypeName" || goType.kind === "Ident") {
|
package/src/backend/wasm/wat.js
CHANGED
|
@@ -83,6 +83,21 @@ function formatInstruction(inst) {
|
|
|
83
83
|
case "global.get":
|
|
84
84
|
case "global.set":
|
|
85
85
|
return `${op} ${inst.globalIndex ?? inst.index ?? 0}`;
|
|
86
|
+
case "i32.load":
|
|
87
|
+
case "i64.load":
|
|
88
|
+
case "f32.load":
|
|
89
|
+
case "f64.load":
|
|
90
|
+
case "i32.load8_s":
|
|
91
|
+
case "i32.load8_u":
|
|
92
|
+
case "i32.load16_s":
|
|
93
|
+
case "i32.load16_u":
|
|
94
|
+
case "i32.store":
|
|
95
|
+
case "i64.store":
|
|
96
|
+
case "f32.store":
|
|
97
|
+
case "f64.store":
|
|
98
|
+
case "i32.store8":
|
|
99
|
+
case "i32.store16":
|
|
100
|
+
return inst.offset ? `${op} offset=${inst.offset}` : op;
|
|
86
101
|
case "i32.const":
|
|
87
102
|
case "i64.const":
|
|
88
103
|
return `${op} ${inst.value ?? 0}`;
|
|
@@ -96,6 +111,26 @@ function formatInstruction(inst) {
|
|
|
96
111
|
}
|
|
97
112
|
case "throw":
|
|
98
113
|
return `${op} ${inst.tagIndex ?? inst.index ?? 0}`;
|
|
114
|
+
case "throw_ref":
|
|
115
|
+
return "throw_ref";
|
|
116
|
+
case "try_table": {
|
|
117
|
+
const bt = inst.blockType ?? inst.resultType;
|
|
118
|
+
const res = bt && bt !== "void" ? ` (result ${formatValType(bt)})` : "";
|
|
119
|
+
const catches = (inst.catches ?? [])
|
|
120
|
+
.map((c) => {
|
|
121
|
+
const k = c.kind ?? c.op;
|
|
122
|
+
if (k === "catch")
|
|
123
|
+
return `(catch ${c.tagIndex ?? 0} ${c.label ?? 0})`;
|
|
124
|
+
if (k === "catch_ref")
|
|
125
|
+
return `(catch_ref ${c.tagIndex ?? 0} ${c.label ?? 0})`;
|
|
126
|
+
if (k === "catch_all") return `(catch_all ${c.label ?? 0})`;
|
|
127
|
+
if (k === "catch_all_ref") return `(catch_all_ref ${c.label ?? 0})`;
|
|
128
|
+
return "";
|
|
129
|
+
})
|
|
130
|
+
.filter(Boolean)
|
|
131
|
+
.join(" ");
|
|
132
|
+
return `${op}${res}${catches ? ` ${catches}` : ""}`;
|
|
133
|
+
}
|
|
99
134
|
case "struct.new":
|
|
100
135
|
return `struct.new ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
|
|
101
136
|
case "struct.get":
|
|
@@ -119,8 +154,15 @@ function formatInstruction(inst) {
|
|
|
119
154
|
case "ref.test":
|
|
120
155
|
case "ref.test_null":
|
|
121
156
|
case "ref.cast":
|
|
122
|
-
case "ref.cast_null":
|
|
123
|
-
|
|
157
|
+
case "ref.cast_null": {
|
|
158
|
+
const ht =
|
|
159
|
+
inst.typeIndex !== undefined
|
|
160
|
+
? `$t${inst.typeIndex}`
|
|
161
|
+
: inst.heapType !== undefined
|
|
162
|
+
? String(inst.heapType)
|
|
163
|
+
: 0;
|
|
164
|
+
return `${op} ${ht}`;
|
|
165
|
+
}
|
|
124
166
|
case "any.convert_extern":
|
|
125
167
|
case "extern.convert_any":
|
|
126
168
|
return op;
|
|
@@ -166,6 +208,11 @@ export function emitWat(mod) {
|
|
|
166
208
|
}
|
|
167
209
|
}
|
|
168
210
|
|
|
211
|
+
if (mod.memory) {
|
|
212
|
+
const max = mod.memory.max != null ? ` ${mod.memory.max}` : "";
|
|
213
|
+
lines.push(` (memory $mem ${mod.memory.min}${max})`);
|
|
214
|
+
}
|
|
215
|
+
|
|
169
216
|
// 4. Globals
|
|
170
217
|
if (mod.globals && mod.globals.length > 0) {
|
|
171
218
|
for (let i = 0; i < mod.globals.length; i++) {
|