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.
Files changed (60) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +50 -19
  4. package/src/backend/js/index.js +100 -55
  5. package/src/backend/js/runtime.js +102 -0
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +96 -16
  8. package/src/backend/js/stdlib/errors.js +2 -2
  9. package/src/backend/js/stdlib/fmt.js +26 -8
  10. package/src/backend/js/stdlib/index.js +3 -0
  11. package/src/backend/js/stdlib/maps.js +1 -1
  12. package/src/backend/js/stdlib/path.js +2 -2
  13. package/src/backend/js/stdlib/shared.js +17 -0
  14. package/src/backend/js/stdlib/slices.js +1 -1
  15. package/src/backend/js/stdlib/sort.js +1 -1
  16. package/src/backend/js/stdlib/strconv.js +34 -9
  17. package/src/backend/js/stdlib/strings.js +4 -1
  18. package/src/backend/js/stdlib/testing.js +2 -2
  19. package/src/backend/js/stdlib/time.js +3 -3
  20. package/src/backend/js/templ.js +1 -1
  21. package/src/backend/wasm/boundary.js +486 -33
  22. package/src/backend/wasm/emit-builtins.js +1103 -0
  23. package/src/backend/wasm/emit-exprs.js +1908 -0
  24. package/src/backend/wasm/emit-maps.js +1863 -0
  25. package/src/backend/wasm/emit-shared.js +519 -0
  26. package/src/backend/wasm/emit-stdlib.js +3006 -0
  27. package/src/backend/wasm/emit-stmts.js +2472 -0
  28. package/src/backend/wasm/emit.js +606 -5009
  29. package/src/backend/wasm/encode.js +212 -64
  30. package/src/backend/wasm/glue.js +19 -5
  31. package/src/backend/wasm/index.js +1426 -49
  32. package/src/backend/wasm/monomorph.js +711 -0
  33. package/src/backend/wasm/optimize.js +227 -0
  34. package/src/backend/wasm/types.js +159 -35
  35. package/src/backend/wasm/wat.js +49 -2
  36. package/src/cli-core.js +275 -72
  37. package/src/compiler.js +144 -21
  38. package/src/index.js +86 -115
  39. package/src/lower/boxing.js +3 -4
  40. package/src/lower/captures.js +10 -17
  41. package/src/lower/escape.js +4 -9
  42. package/src/lower/functions.js +277 -5
  43. package/src/lower/globals.js +7 -15
  44. package/src/lower/index.js +5 -2
  45. package/src/lower/ownership.js +5 -10
  46. package/src/lower/range.js +2 -5
  47. package/src/lower/walk.js +25 -0
  48. package/src/resolver.js +2 -1
  49. package/src/test-runner.js +20 -0
  50. package/src/typechecker/assignability.js +42 -1
  51. package/src/typechecker/expressions.js +142 -2
  52. package/src/typechecker/index.js +27 -1
  53. package/src/typechecker/resolve.js +35 -6
  54. package/src/typechecker/statements.js +0 -7
  55. package/src/typechecker/stdlib/core.js +17 -19
  56. package/src/typechecker/stdlib/extended.js +18 -3
  57. package/src/typechecker/stdlib/shared.js +53 -0
  58. package/src/typechecker/stdlib.js +2 -0
  59. package/src/typechecker/types.js +206 -7
  60. 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 Array AST nodes
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 isNarrowInt(goType) {
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
- return isNarrowInt({ kind: "basic", name: goType.name });
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" && goType.underlying) {
288
- return isNarrowInt(goType.underlying);
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
- if (goType.kind === "basic") {
291
- switch (goType.name) {
292
- case "int8":
293
- case "int16":
294
- case "uint8":
295
- case "uint16":
296
- case "byte":
297
- return true;
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") {
@@ -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
- return `${op} ${inst.typeIndex !== undefined ? `$t${inst.typeIndex}` : 0}`;
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++) {