gofront 1.4.0 → 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 (77) hide show
  1. package/README.md +174 -403
  2. package/package.json +16 -19
  3. package/src/{codegen → backend/js}/expressions.js +265 -77
  4. package/src/{codegen → backend/js}/index.js +120 -89
  5. package/src/{codegen → backend/js}/runtime.js +102 -0
  6. package/src/{codegen → backend/js}/source-map.js +32 -0
  7. package/src/{codegen → backend/js}/statements.js +139 -64
  8. package/src/backend/js/stdlib/bits.js +32 -0
  9. package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
  10. package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
  11. package/src/{codegen → backend/js}/stdlib/index.js +6 -0
  12. package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
  13. package/src/{codegen → backend/js}/stdlib/path.js +2 -2
  14. package/src/backend/js/stdlib/shared.js +17 -0
  15. package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
  16. package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
  17. package/src/backend/js/stdlib/strconv.js +58 -0
  18. package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
  19. package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
  20. package/src/{codegen → backend/js}/stdlib/time.js +3 -3
  21. package/src/{codegen → backend/js}/templ.js +3 -3
  22. package/src/backend/wasm/boundary.js +1798 -0
  23. package/src/backend/wasm/emit-builtins.js +1103 -0
  24. package/src/backend/wasm/emit-exprs.js +1908 -0
  25. package/src/backend/wasm/emit-maps.js +1863 -0
  26. package/src/backend/wasm/emit-shared.js +519 -0
  27. package/src/backend/wasm/emit-stdlib.js +3006 -0
  28. package/src/backend/wasm/emit-stmts.js +2472 -0
  29. package/src/backend/wasm/emit.js +1376 -0
  30. package/src/backend/wasm/encode.js +937 -0
  31. package/src/backend/wasm/glue.js +144 -0
  32. package/src/backend/wasm/index.js +2790 -0
  33. package/src/backend/wasm/monomorph.js +711 -0
  34. package/src/backend/wasm/optimize.js +227 -0
  35. package/src/backend/wasm/types.js +544 -0
  36. package/src/backend/wasm/wat.js +302 -0
  37. package/src/cli-core.js +323 -75
  38. package/src/compiler.js +447 -14
  39. package/src/dev-server.js +56 -15
  40. package/src/index.js +99 -120
  41. package/src/lexer.js +18 -0
  42. package/src/lower/boxing.js +35 -0
  43. package/src/lower/captures.js +172 -0
  44. package/src/lower/embedding.js +30 -0
  45. package/src/lower/escape.js +164 -0
  46. package/src/lower/functions.js +302 -0
  47. package/src/lower/globals.js +316 -0
  48. package/src/lower/index.js +118 -0
  49. package/src/lower/ownership.js +137 -0
  50. package/src/lower/range.js +67 -0
  51. package/src/lower/walk.js +25 -0
  52. package/src/minifier.js +2 -0
  53. package/src/parser/index.js +8 -3
  54. package/src/resolver.js +3 -1
  55. package/src/test-runner.js +94 -20
  56. package/src/typechecker/assignability.js +42 -1
  57. package/src/typechecker/expressions.js +182 -3
  58. package/src/typechecker/index.js +261 -5
  59. package/src/typechecker/resolve.js +35 -6
  60. package/src/typechecker/stdlib/core.js +34 -19
  61. package/src/typechecker/stdlib/extended.js +18 -3
  62. package/src/typechecker/stdlib/shared.js +53 -0
  63. package/src/typechecker/stdlib.js +2 -0
  64. package/src/typechecker/types.js +259 -6
  65. package/src/vendor.js +2 -2
  66. package/src/codegen/stdlib/strconv.js +0 -33
  67. /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
  68. /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
  69. /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
  70. /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
  71. /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
  72. /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
  73. /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
  74. /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
  75. /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
  76. /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
  77. /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
@@ -0,0 +1,2790 @@
1
+ // src/backend/wasm/index.js
2
+ // WASM Backend compiler entry point: lowered AST + types -> Module IR -> binary / WAT.
3
+
4
+ import { Lexer } from "../../lexer.js";
5
+ import { lower } from "../../lower/index.js";
6
+ import { Parser } from "../../parser/index.js";
7
+ import { TypeChecker } from "../../typechecker/index.js";
8
+ import {
9
+ addBoundaryHelpers,
10
+ collectBoundaryMeta,
11
+ generateFacade,
12
+ scanBoundaryImports,
13
+ wTypeKey,
14
+ } from "./boundary.js";
15
+ import { FunctionEmitter } from "./emit.js";
16
+ import { BINARY_MATH_IMPORTS } from "./emit-stdlib.js";
17
+ import { encodeModule, isGoFrontWasm } from "./encode.js";
18
+ import { hasGenerics, monomorphise } from "./monomorph.js";
19
+ import { hasNativeWasmOpt, optimizeWasm } from "./optimize.js";
20
+ import {
21
+ ERROR_STRUCT,
22
+ getFuncSignature,
23
+ getMapKeyValTypes,
24
+ getReceiverTypeName,
25
+ isMapType,
26
+ isNonEmptyInterface,
27
+ isTestingT,
28
+ toWasmType,
29
+ } from "./types.js";
30
+ import { emitWat } from "./wat.js";
31
+
32
+ export { hasNativeWasmOpt, isGoFrontWasm, optimizeWasm };
33
+
34
+ const ERROR_PRODUCING_PKGS = new Set(["errors", "fmt", "strconv"]);
35
+ // Packages whose calls lower to the `env.*` imports in STDLIB_IMPORTS (the
36
+ // matching JS lives in WASM_STDLIB_JS). Only modules importing one of them
37
+ // declare the imports and ship that JS block.
38
+ const STDLIB_ENV_PKGS = new Set([
39
+ "strings",
40
+ "strconv",
41
+ "unicode/utf8",
42
+ "fmt",
43
+ "testing",
44
+ ]);
45
+
46
+ function importsAnyOf(programs, pkgs) {
47
+ return (programs ?? []).some((p) =>
48
+ (p.imports ?? []).some((decl) =>
49
+ (decl.imports ?? [decl]).some((imp) => pkgs.has(imp.path)),
50
+ ),
51
+ );
52
+ }
53
+
54
+ // [name, params, results] for `env.*` imports backing stdlib calls; the JS
55
+ // side lives in WASM_STDLIB_JS (boundary.js). All are registered eagerly
56
+ // (when any STDLIB_ENV_PKGS package is imported) because the import table is
57
+ // locked before function bodies are emitted.
58
+ const S = "externref";
59
+ const STDLIB_IMPORTS = [
60
+ // strings
61
+ ["str_trim", [S, S], [S]],
62
+ ["str_trim_left", [S, S], [S]],
63
+ ["str_trim_right", [S, S], [S]],
64
+ ["str_trim_prefix", [S, S], [S]],
65
+ ["str_trim_suffix", [S, S], [S]],
66
+ ["str_replace", [S, S, S, "i32"], [S]],
67
+ ["str_contains_rune", [S, "i32"], ["i32"]],
68
+ ["str_contains_any", [S, S], ["i32"]],
69
+ ["str_index_byte", [S, "i32"], ["i32"]],
70
+ ["str_index_rune", [S, "i32"], ["i32"]],
71
+ ["str_index_any", [S, S], ["i32"]],
72
+ ["str_last_index_byte", [S, "i32"], ["i32"]],
73
+ ["str_title", [S], [S]],
74
+ ["str_split", [S, S], ["i32"]],
75
+ ["str_fields", [S], ["i32"]],
76
+ ["str_part", ["i32"], [S]],
77
+ // strconv
78
+ ["strconv_atoi", [S], ["i64"]],
79
+ ["strconv_parse_int", [S, "i32", "i32"], ["i64"]],
80
+ ["strconv_parse_float", [S], ["f64"]],
81
+ ["strconv_parse_bool", [S], ["i32"]],
82
+ ["strconv_err", [], [S]],
83
+ ["strconv_format_float", ["f64", "i32", "i32", "i32"], [S]],
84
+ ["strconv_format_int", ["i64", "i32"], [S]],
85
+ ["strconv_quote", [S], [S]],
86
+ // unicode/utf8
87
+ ["utf8_rune_count", [S], ["i32"]],
88
+ ["utf8_rune_len", ["i32"], ["i32"]],
89
+ ["utf8_valid_string", [S], ["i32"]],
90
+ ["utf8_decode_rune", [S, "i32"], ["i32"]],
91
+ ["utf8_decode_size", [], ["i32"]],
92
+ ["utf8_full_rune", [S], ["i32"]],
93
+ // fmt (arguments travel through the testing_arg_* buffer)
94
+ ["fmt_sprintf", [S], [S]],
95
+ ["fmt_printf", [S], []],
96
+ // testing.T.Run: the harness calls back into the exported __testing_run_cb
97
+ ["testing_run", [S, S, "anyref"], ["i32"]],
98
+ ];
99
+
100
+ class ModuleEmitter {
101
+ constructor(checker, lowerResult = null, programs = [], options = {}) {
102
+ this.checker = checker;
103
+ this.lowerResult = lowerResult;
104
+ // Local package names whose code is linked into this module; selectors
105
+ // like `mathx.NewVec3` are de-qualified to `NewVec3` by the emitter.
106
+ this.bundledPackages = options.bundledPackages ?? new Set();
107
+ this.constCache = new Map(); // name -> BasicLit (package-level literal consts)
108
+ this.nonLiteralConsts = new Set(); // package-level consts the backend cannot evaluate yet
109
+
110
+ this.types = []; // type entries
111
+ this.typeCache = new Map(); // signature string -> index
112
+ this._openRec = null; // rec group receiving new types while structs are resolved
113
+
114
+ this.structTypes = new Map(); // name -> struct info
115
+ this.boxTypes = new Map(); // wType key -> box info
116
+ this.arrayTypes = new Map(); // wType key -> array info
117
+ this.sliceTypes = new Map(); // wType key -> slice info
118
+ this.sliceTypesByIndex = new Map(); // typeIndex -> slice info
119
+ this.mapTypes = new Map(); // wType key -> map info
120
+ this.mapTypesByIndex = new Map(); // typeIndex -> map info
121
+ this.mapBucketsType = null; // shared (array (mut i32))
122
+ this.closureTypes = new Map(); // key -> closure info
123
+ this.envTypes = new Map(); // key -> env info
124
+
125
+ this.elements = []; // declarative elements for ref.func
126
+ this.elementSet = new Set();
127
+
128
+ this.imports = []; // import entries
129
+ this.importCache = new Map(); // key -> func index
130
+
131
+ this.tags = []; // exception tags
132
+ this.globals = []; // global entries
133
+ this.globalCache = new Map(); // name -> index
134
+
135
+ this.funcs = []; // internal func definitions
136
+ this.funcMap = new Map(); // name -> global func index
137
+ this.funcParamTypes = new Map(); // name -> param types array
138
+ this.funcParamGoTypes = new Map(); // name -> param Go types array
139
+ this.funcReturnTypesByIdx = new Map(); // global func index -> result wasm types
140
+
141
+ this.exports = []; // export entries
142
+ this.stringTable = []; // string literals
143
+ this.stringCache = new Map();
144
+
145
+ // `errors.New` / `fmt.Errorf` / strconv parse failures allocate the
146
+ // runtime's own error struct; it joins the struct rec group so the
147
+ // `error.Error` dispatcher can `ref.test` it like any user type.
148
+ this.hasErrorType = importsAnyOf(programs, ERROR_PRODUCING_PKGS);
149
+ this.usesStdlibEnv = importsAnyOf(programs, STDLIB_ENV_PKGS);
150
+
151
+ if (programs && programs.length > 0) {
152
+ this.initTypes(programs);
153
+ }
154
+
155
+ this._importsLocked = false;
156
+ this._initRuntimeImports();
157
+ }
158
+
159
+ initTypes(programs) {
160
+ const progs = Array.isArray(programs) ? programs : [programs];
161
+ const rawStructs = [];
162
+ const seen = new Set();
163
+ const aliases = []; // qualified key (pkg.T) -> unqualified struct name
164
+
165
+ if (this.hasErrorType) {
166
+ seen.add(ERROR_STRUCT);
167
+ rawStructs.push({
168
+ name: ERROR_STRUCT,
169
+ structType: {
170
+ kind: "struct",
171
+ fields: new Map([
172
+ ["msg", { kind: "basic", name: "string" }],
173
+ ["cause", { kind: "basic", name: "any" }],
174
+ ]),
175
+ },
176
+ });
177
+ }
178
+
179
+ for (const p of progs) {
180
+ for (const d of p.decls ?? []) {
181
+ if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
182
+ if (!seen.has(d.name)) {
183
+ seen.add(d.name);
184
+ rawStructs.push({
185
+ name: d.name,
186
+ astFields: d.type.fields ?? [],
187
+ pkgName: p.pkg?.name ?? "main",
188
+ pkgTarget: p.target ?? "wasm",
189
+ });
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ if (this.checker?.types) {
196
+ for (const [key, t] of this.checker.types.entries()) {
197
+ if (t.kind === "named" && t.underlying?.kind === "struct") {
198
+ const name = t.name ?? key;
199
+ if (!seen.has(name)) {
200
+ seen.add(name);
201
+ rawStructs.push({ name, structType: t.underlying });
202
+ }
203
+ if (key !== name) aliases.push([key, name]);
204
+ }
205
+ }
206
+ }
207
+
208
+ if (rawStructs.length === 0) return;
209
+
210
+ // Field resolution may create array/slice/box types; they join this rec
211
+ // group so struct indices (base + i) stay valid and may be referenced
212
+ // from the auxiliary types in any order.
213
+ const base = this._getTotalTypeCount();
214
+ const structEntries = rawStructs.map(() => ({
215
+ form: "struct",
216
+ fields: [],
217
+ }));
218
+ this.types.push({ form: "rec", types: structEntries });
219
+ this._openRec = structEntries;
220
+
221
+ for (let i = 0; i < rawStructs.length; i++) {
222
+ const s = rawStructs[i];
223
+ this.structTypes.set(s.name, {
224
+ name: s.name,
225
+ typeIndex: base + i,
226
+ fields: [],
227
+ fieldIndexMap: new Map(),
228
+ embeds: [],
229
+ pkgName: s.pkgName ?? null,
230
+ pkgTarget: s.pkgTarget ?? "wasm",
231
+ });
232
+ }
233
+ // `mathx.Vec3` resolves to the same struct info as `Vec3` once linked.
234
+ for (const [key, name] of aliases) {
235
+ if (!this.structTypes.has(key))
236
+ this.structTypes.set(key, this.structTypes.get(name));
237
+ }
238
+
239
+ for (let i = 0; i < rawStructs.length; i++) {
240
+ const s = rawStructs[i];
241
+ const info = this.structTypes.get(s.name);
242
+ const resolvedStruct =
243
+ s.structType ?? this.checker?.types?.get(s.name)?.underlying;
244
+
245
+ const fields = [];
246
+ const fieldIndexMap = new Map();
247
+ const embeds = [];
248
+
249
+ if (s.astFields && s.astFields.length > 0) {
250
+ let fIdx = 0;
251
+ for (const f of s.astFields) {
252
+ const names = f.embedded
253
+ ? [f.type.name]
254
+ : f.names?.length
255
+ ? f.names
256
+ : f.name
257
+ ? [f.name]
258
+ : [];
259
+ for (const n of names) {
260
+ const wType = toWasmType(f.type, this.checker, this);
261
+ fields.push({
262
+ name: n,
263
+ goType: f.type,
264
+ wType,
265
+ embedded: Boolean(f.embedded),
266
+ });
267
+ fieldIndexMap.set(n, fIdx);
268
+ if (f.embedded) {
269
+ embeds.push({
270
+ name: n,
271
+ fieldIndex: fIdx,
272
+ });
273
+ }
274
+ fIdx++;
275
+ }
276
+ }
277
+ } else if (resolvedStruct?.fields) {
278
+ let fIdx = 0;
279
+ for (const [fName, fType] of resolvedStruct.fields.entries()) {
280
+ const wType = toWasmType(fType, this.checker, this);
281
+ fields.push({ name: fName, goType: fType, wType });
282
+ fieldIndexMap.set(fName, fIdx);
283
+ fIdx++;
284
+ }
285
+ if (resolvedStruct?._embeds) {
286
+ for (const embed of resolvedStruct._embeds) {
287
+ const embedName = embed.kind === "named" ? embed.name : null;
288
+ if (embedName && fieldIndexMap.has(embedName)) {
289
+ embeds.push({
290
+ name: embedName,
291
+ fieldIndex: fieldIndexMap.get(embedName),
292
+ });
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ info.fields = fields;
299
+ info.fieldIndexMap = fieldIndexMap;
300
+ info.embeds = embeds;
301
+
302
+ const typeEntry = structEntries[i];
303
+ typeEntry.fields = fields.map((f) => ({ type: f.wType, mutable: true }));
304
+ info.typeEntry = typeEntry;
305
+ }
306
+
307
+ this._openRec = null;
308
+ }
309
+
310
+ _pushType(typeEntry) {
311
+ (this._openRec ?? this.types).push(typeEntry);
312
+ }
313
+
314
+ getStructType(name) {
315
+ return this.structTypes.get(name) ?? null;
316
+ }
317
+
318
+ getBoxType(goType) {
319
+ const wType = toWasmType(goType, this.checker, this);
320
+ const key = typeof wType === "string" ? wType : JSON.stringify(wType);
321
+ if (this.boxTypes.has(key)) {
322
+ return this.boxTypes.get(key);
323
+ }
324
+ const typeIndex = this._getTotalTypeCount();
325
+ const typeEntry = {
326
+ form: "struct",
327
+ fields: [{ type: wType, mutable: true }],
328
+ };
329
+ this._pushType(typeEntry);
330
+ const boxInfo = { typeIndex, typeEntry, wType };
331
+ this.boxTypes.set(key, boxInfo);
332
+ return boxInfo;
333
+ }
334
+
335
+ getArrayType(elemGoType) {
336
+ const elemWType = toWasmType(elemGoType, this.checker, this);
337
+ const key =
338
+ typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
339
+ if (this.arrayTypes.has(key)) {
340
+ return this.arrayTypes.get(key);
341
+ }
342
+ const typeIndex = this._getTotalTypeCount();
343
+ const typeEntry = {
344
+ form: "array",
345
+ elemType: elemWType,
346
+ mutable: true,
347
+ };
348
+ this._pushType(typeEntry);
349
+ const arrInfo = { typeIndex, typeEntry, elemWType, elemGoType };
350
+ this.arrayTypes.set(key, arrInfo);
351
+ return arrInfo;
352
+ }
353
+
354
+ getSliceType(elemGoType) {
355
+ const elemWType = toWasmType(elemGoType, this.checker, this);
356
+ const key =
357
+ typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
358
+ if (this.sliceTypes.has(key)) {
359
+ return this.sliceTypes.get(key);
360
+ }
361
+ const arrInfo = this.getArrayType(elemGoType);
362
+ const typeIndex = this._getTotalTypeCount();
363
+ const typeEntry = {
364
+ form: "struct",
365
+ fields: [
366
+ {
367
+ type: {
368
+ kind: "ref",
369
+ nullable: true,
370
+ typeIndex: arrInfo.typeIndex,
371
+ },
372
+ mutable: false,
373
+ },
374
+ { type: "i32", mutable: false }, // offset
375
+ { type: "i32", mutable: false }, // len
376
+ { type: "i32", mutable: false }, // cap
377
+ ],
378
+ };
379
+ this._pushType(typeEntry);
380
+
381
+ const emptyGlobalIndex = this.globals.length;
382
+ this.globals.push({
383
+ type: {
384
+ kind: "ref",
385
+ nullable: true,
386
+ typeIndex,
387
+ },
388
+ mutable: false,
389
+ init: [
390
+ { op: "ref.null", heapType: arrInfo.typeIndex },
391
+ { op: "i32.const", value: 0 },
392
+ { op: "i32.const", value: 0 },
393
+ { op: "i32.const", value: 0 },
394
+ { op: "struct.new", typeIndex },
395
+ ],
396
+ });
397
+
398
+ const sliceInfo = {
399
+ typeIndex,
400
+ typeEntry,
401
+ arrInfo,
402
+ elemWType,
403
+ elemGoType,
404
+ emptyGlobalIndex,
405
+ };
406
+ this.sliceTypes.set(key, sliceInfo);
407
+ this.sliceTypesByIndex.set(typeIndex, sliceInfo);
408
+ return sliceInfo;
409
+ }
410
+
411
+ getSliceTypeByIndex(typeIndex) {
412
+ return this.sliceTypesByIndex?.get(typeIndex) ?? null;
413
+ }
414
+
415
+ getMapType(keyGoType, valGoType) {
416
+ const keyWType = toWasmType(keyGoType, this.checker, this);
417
+ const valWType = toWasmType(valGoType, this.checker, this);
418
+ const key = `${wTypeKey(keyWType)}:${wTypeKey(valWType)}`;
419
+ if (this.mapTypes.has(key)) {
420
+ return this.mapTypes.get(key);
421
+ }
422
+
423
+ if (!this.mapBucketsType) {
424
+ const typeIndex = this._getTotalTypeCount();
425
+ this._pushType({
426
+ form: "array",
427
+ elemType: "i32",
428
+ mutable: true,
429
+ });
430
+ this.mapBucketsType = { typeIndex };
431
+ }
432
+
433
+ const entryTypeIndex = this._getTotalTypeCount();
434
+ this._pushType({
435
+ form: "struct",
436
+ fields: [
437
+ { type: keyWType, mutable: true }, // 0: key
438
+ { type: valWType, mutable: true }, // 1: val
439
+ { type: "i32", mutable: true }, // 2: next (bucket chain)
440
+ { type: "i32", mutable: true }, // 3: order_prev (insertion order)
441
+ { type: "i32", mutable: true }, // 4: order_next (insertion order)
442
+ { type: "i32", mutable: true }, // 5: active (1 = active, 0 = deleted)
443
+ ],
444
+ });
445
+
446
+ const entriesTypeIndex = this._getTotalTypeCount();
447
+ this._pushType({
448
+ form: "array",
449
+ elemType: { kind: "ref", nullable: true, typeIndex: entryTypeIndex },
450
+ mutable: true,
451
+ });
452
+
453
+ const mapTypeIndex = this._getTotalTypeCount();
454
+ this._pushType({
455
+ form: "struct",
456
+ fields: [
457
+ {
458
+ type: {
459
+ kind: "ref",
460
+ nullable: true,
461
+ typeIndex: this.mapBucketsType.typeIndex,
462
+ },
463
+ mutable: true,
464
+ }, // 0: buckets
465
+ {
466
+ type: {
467
+ kind: "ref",
468
+ nullable: true,
469
+ typeIndex: entriesTypeIndex,
470
+ },
471
+ mutable: true,
472
+ }, // 1: entries
473
+ { type: "i32", mutable: true }, // 2: len
474
+ { type: "i32", mutable: true }, // 3: cap
475
+ { type: "i32", mutable: true }, // 4: count
476
+ { type: "i32", mutable: true }, // 5: head
477
+ { type: "i32", mutable: true }, // 6: tail
478
+ { type: "i32", mutable: true }, // 7: free_head
479
+ { type: "i32", mutable: true }, // 8: num_buckets
480
+ ],
481
+ });
482
+
483
+ const suffix = key.replace(/[^a-zA-Z0-9_]/g, "_");
484
+ const mapInfo = {
485
+ key,
486
+ suffix,
487
+ typeIndex: mapTypeIndex,
488
+ entryTypeIndex,
489
+ entriesTypeIndex,
490
+ bucketsTypeIndex: this.mapBucketsType.typeIndex,
491
+ keyWType,
492
+ valWType,
493
+ keyGoType,
494
+ valGoType,
495
+ wType: { kind: "ref", nullable: true, typeIndex: mapTypeIndex },
496
+ makeFuncName: `__map_make_${suffix}`,
497
+ getFuncName: `__map_get_${suffix}`,
498
+ getOkFuncName: `__map_get_ok_${suffix}`,
499
+ setFuncName: `__map_set_${suffix}`,
500
+ deleteFuncName: `__map_delete_${suffix}`,
501
+ lenFuncName: `__map_len_${suffix}`,
502
+ clearFuncName: `__map_clear_${suffix}`,
503
+ keysFuncName: `__map_keys_${suffix}`,
504
+ valuesFuncName: `__map_values_${suffix}`,
505
+ cloneFuncName: `__map_clone_${suffix}`,
506
+ };
507
+ this.mapTypes.set(key, mapInfo);
508
+ this.mapTypesByIndex.set(mapTypeIndex, mapInfo);
509
+ return mapInfo;
510
+ }
511
+
512
+ getMapTypeByIndex(typeIndex) {
513
+ return this.mapTypesByIndex?.get(typeIndex) ?? null;
514
+ }
515
+
516
+ getClosureType(goType) {
517
+ const sig = getFuncSignature(goType, this.checker);
518
+ const paramWTypes = sig.params
519
+ .map((p) => toWasmType(p, this.checker, this))
520
+ .filter(Boolean);
521
+ const returnWTypes = sig.returns
522
+ .map((r) => toWasmType(r, this.checker, this))
523
+ .filter(Boolean);
524
+
525
+ const key = `${paramWTypes.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${returnWTypes.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
526
+ if (this.closureTypes.has(key)) {
527
+ return this.closureTypes.get(key);
528
+ }
529
+
530
+ // 1. Function signature type index: (param anyref, ...paramWTypes) -> (...returnWTypes)
531
+ const funcTypeIndex = this.getTypeIndex(
532
+ ["anyref", ...paramWTypes],
533
+ returnWTypes,
534
+ );
535
+
536
+ // 2. Closure struct type index: struct { fn: (ref funcTypeIndex), env: anyref }
537
+ const typeIndex = this._getTotalTypeCount();
538
+ const typeEntry = {
539
+ form: "struct",
540
+ fields: [
541
+ {
542
+ type: {
543
+ kind: "ref",
544
+ nullable: false,
545
+ typeIndex: funcTypeIndex,
546
+ },
547
+ mutable: false,
548
+ },
549
+ {
550
+ type: "anyref",
551
+ mutable: true,
552
+ },
553
+ ],
554
+ };
555
+ this._pushType(typeEntry);
556
+
557
+ const closureInfo = {
558
+ typeIndex,
559
+ typeEntry,
560
+ funcTypeIndex,
561
+ paramWTypes,
562
+ returnWTypes,
563
+ sig,
564
+ };
565
+ this.closureTypes.set(key, closureInfo);
566
+ return closureInfo;
567
+ }
568
+
569
+ getEnvType(fieldTypes) {
570
+ const key = fieldTypes
571
+ .map((f) =>
572
+ typeof f.wType === "object" ? JSON.stringify(f.wType) : f.wType,
573
+ )
574
+ .join(",");
575
+ if (this.envTypes.has(key)) {
576
+ return this.envTypes.get(key);
577
+ }
578
+ const typeIndex = this._getTotalTypeCount();
579
+ const typeEntry = {
580
+ form: "struct",
581
+ fields: fieldTypes.map((f) => ({ type: f.wType, mutable: true })),
582
+ };
583
+ this._pushType(typeEntry);
584
+ const envInfo = { typeIndex, typeEntry, fieldTypes };
585
+ this.envTypes.set(key, envInfo);
586
+ return envInfo;
587
+ }
588
+
589
+ addRefFuncElement(funcIdx) {
590
+ if (!this.elementSet.has(funcIdx)) {
591
+ this.elementSet.add(funcIdx);
592
+ this.elements.push(funcIdx);
593
+ }
594
+ }
595
+
596
+ _getTotalTypeCount() {
597
+ let count = 0;
598
+ for (const entry of this.types) {
599
+ if (entry.form === "rec") count += entry.types.length;
600
+ else count += 1;
601
+ }
602
+ return count;
603
+ }
604
+
605
+ _initRuntimeImports() {
606
+ this._initPanicTag();
607
+ this._initPanicGlobal();
608
+ this.getPanicImportIndex(); // env.panic
609
+ this.internString("runtime error: index out of range");
610
+ this.internString("runtime error: slice bounds out of range");
611
+ this.internString("assignment to entry in nil map");
612
+
613
+ // String built-ins
614
+ this.getStringImportIndex(); // env.str
615
+ this.getStringLenImportIndex(); // env.str_len
616
+ this.getStringConcatImportIndex(); // env.str_concat
617
+ this.getStringCmpImportIndex("=="); // env.str_eq
618
+ this.getStringCmpImportIndex("!="); // env.str_ne
619
+ this.getStringCmpImportIndex("<"); // env.str_lt
620
+ this.getStringCmpImportIndex("<="); // env.str_le
621
+ this.getStringCmpImportIndex(">"); // env.str_gt
622
+ this.getStringCmpImportIndex(">="); // env.str_ge
623
+ this.getStringGetImportIndex(); // env.str_get
624
+ this.getStringSliceImportIndex(); // env.str_slice
625
+ this.getStringFromCodePointImportIndex(); // env.str_from_code_point
626
+ this.getStringCodePointAtImportIndex(); // env.str_code_point_at
627
+ this.getStringHashImportIndex(); // env.str_hash
628
+ this.getStringToUpperImportIndex();
629
+ this.getStringToLowerImportIndex();
630
+ this.getStringTrimSpaceImportIndex();
631
+ this.getStringContainsImportIndex();
632
+ this.getStringHasPrefixImportIndex();
633
+ this.getStringHasSuffixImportIndex();
634
+ this.getStringIndexImportIndex();
635
+ this.getStringLastIndexImportIndex();
636
+ this.getStringRepeatImportIndex();
637
+ this.getStringReplaceAllImportIndex();
638
+ this.getStringEqualFoldImportIndex();
639
+ this.getStringCountImportIndex();
640
+ this.getStrFromI64ImportIndex();
641
+ this.getStrFromI32ImportIndex();
642
+ this.getStrFromF64ImportIndex();
643
+ this.getIsStringImportIndex(); // env.is_string
644
+ this.getTestingCallImportIndex();
645
+ this.getTestingNameImportIndex();
646
+ this.getTestingFlagImportIndex();
647
+ for (const t of ["i32", "i64", "f32", "f64", "externref", "anyref"]) {
648
+ this.getTestingArgImportIndex(t, false);
649
+ }
650
+ this.getTestingArgImportIndex("i32", true);
651
+ if (this.usesStdlibEnv) {
652
+ for (const [name, params, results] of STDLIB_IMPORTS) {
653
+ this.getOrAddFuncImport("env", name, params, results);
654
+ }
655
+ }
656
+
657
+ // Logging built-ins
658
+ this.getPrintlnEmptyIndex(); // env.println_empty
659
+ const logTypes = ["i32", "i64", "f32", "f64", "externref", "anyref"];
660
+ for (const t of logTypes) {
661
+ this.getLogImportIndex(t, false, false); // env.print_*
662
+ this.getLogImportIndex(t, true, false); // env.println_*
663
+ }
664
+ this.getLogImportIndex("i32", false, true); // env.print_bool
665
+ this.getLogImportIndex("i32", true, true); // env.println_bool
666
+
667
+ // Math built-ins
668
+ const unaryMath = [
669
+ "sin",
670
+ "cos",
671
+ "tan",
672
+ "asin",
673
+ "acos",
674
+ "atan",
675
+ "exp",
676
+ "log",
677
+ "log2",
678
+ "log10",
679
+ "round",
680
+ "cbrt",
681
+ ];
682
+ for (const m of unaryMath) {
683
+ this.getMathImportIndex(m, false);
684
+ }
685
+ for (const m of BINARY_MATH_IMPORTS) {
686
+ this.getMathImportIndex(m, true);
687
+ }
688
+ }
689
+
690
+ _initPanicTag() {
691
+ // Type: (externref) -> ()
692
+ const typeIdx = this.getTypeIndex(["externref"], []);
693
+ // Tag 0
694
+ this.tags.push({ typeIndex: typeIdx });
695
+ // Export tag 0 as "panicTag"
696
+ this.exports.push({ name: "panicTag", kind: "tag", index: 0 });
697
+ }
698
+
699
+ getPanicNodeTypeIndex() {
700
+ if (this.panicNodeTypeIndex !== undefined) {
701
+ return this.panicNodeTypeIndex;
702
+ }
703
+ const typeIndex = this._getTotalTypeCount();
704
+ const typeEntry = {
705
+ form: "rec",
706
+ types: [
707
+ {
708
+ form: "struct",
709
+ fields: [
710
+ {
711
+ type: "anyref",
712
+ mutable: true,
713
+ },
714
+ {
715
+ type: "i32",
716
+ mutable: true,
717
+ },
718
+ {
719
+ type: {
720
+ kind: "ref",
721
+ nullable: true,
722
+ typeIndex,
723
+ },
724
+ mutable: true,
725
+ },
726
+ ],
727
+ },
728
+ ],
729
+ };
730
+ this._pushType(typeEntry);
731
+ this.panicNodeTypeIndex = typeIndex;
732
+ return typeIndex;
733
+ }
734
+
735
+ _initPanicGlobal() {
736
+ const pType = this.getPanicNodeTypeIndex();
737
+ const gIdx = this.globals.length;
738
+ this.globals.push({
739
+ type: { kind: "ref", nullable: true, typeIndex: pType },
740
+ mutable: true,
741
+ init: [{ op: "ref.null", typeIndex: pType }],
742
+ });
743
+ this.panicGlobalIndex = gIdx;
744
+ this.exports.push({ name: "__panic", kind: "global", index: gIdx });
745
+ }
746
+
747
+ getPanicGlobalIndex() {
748
+ return this.panicGlobalIndex;
749
+ }
750
+
751
+ // `__deferArmed` is set to 1 immediately before a deferred closure is
752
+ // invoked and cleared by that closure's prologue. recover() only takes
753
+ // effect when the current frame observed the flag, which gives Go's
754
+ // "recover must be called directly by the deferred function" rule.
755
+ getDeferArmedGlobalIndex() {
756
+ if (this.deferArmedGlobalIndex !== undefined) {
757
+ return this.deferArmedGlobalIndex;
758
+ }
759
+ const gIdx = this.globals.length;
760
+ this.globals.push({
761
+ type: "i32",
762
+ mutable: true,
763
+ init: [{ op: "i32.const", value: 0 }],
764
+ });
765
+ this.deferArmedGlobalIndex = gIdx;
766
+ return gIdx;
767
+ }
768
+
769
+ getDeferNodeTypeIndex() {
770
+ if (this.deferNodeTypeIndex !== undefined) {
771
+ return this.deferNodeTypeIndex;
772
+ }
773
+ const deferClosureSig = { kind: "Signature", params: [], results: [] };
774
+ const deferClosureInfo = this.getClosureType(deferClosureSig);
775
+ const typeIndex = this._getTotalTypeCount();
776
+ const typeEntry = {
777
+ form: "rec",
778
+ types: [
779
+ {
780
+ form: "struct",
781
+ fields: [
782
+ {
783
+ type: {
784
+ kind: "ref",
785
+ nullable: true,
786
+ typeIndex: deferClosureInfo.typeIndex,
787
+ },
788
+ mutable: false,
789
+ },
790
+ {
791
+ type: {
792
+ kind: "ref",
793
+ nullable: true,
794
+ typeIndex,
795
+ },
796
+ mutable: true,
797
+ },
798
+ ],
799
+ },
800
+ ],
801
+ };
802
+ this._pushType(typeEntry);
803
+ this.deferNodeTypeIndex = typeIndex;
804
+ return typeIndex;
805
+ }
806
+
807
+ getTypeIndex(params, results) {
808
+ const key = `${params.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${results.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
809
+ if (this.typeCache.has(key)) {
810
+ return this.typeCache.get(key);
811
+ }
812
+ const idx = this._getTotalTypeCount();
813
+ this._pushType({ form: "func", params, results });
814
+ this.typeCache.set(key, idx);
815
+ return idx;
816
+ }
817
+
818
+ internString(str) {
819
+ if (this.stringCache.has(str)) {
820
+ return this.stringCache.get(str);
821
+ }
822
+ const idx = this.stringTable.length;
823
+ this.stringTable.push(str);
824
+ this.stringCache.set(str, idx);
825
+ return idx;
826
+ }
827
+
828
+ getOrAddFuncImport(module, name, params, results) {
829
+ const key = `${module}.${name}`;
830
+ if (this.importCache.has(key)) {
831
+ return this.importCache.get(key);
832
+ }
833
+ if (this._importsLocked) {
834
+ throw new Error(`Unexpected import added after lock: ${key}`);
835
+ }
836
+ const typeIndex = this.getTypeIndex(params, results);
837
+ const globalFuncIdx = this.imports.filter((i) => i.kind === "func").length;
838
+ this.imports.push({
839
+ module,
840
+ name,
841
+ kind: "func",
842
+ typeIndex,
843
+ });
844
+ this.importCache.set(key, globalFuncIdx);
845
+ return globalFuncIdx;
846
+ }
847
+
848
+ getStringImportIndex() {
849
+ return this.getOrAddFuncImport("env", "str", ["i32"], ["externref"]);
850
+ }
851
+
852
+ // Panics are raised by a JS import that throws a plain `Error`, so wasm
853
+ // frames unwind without a wasm `throw` and the JS caller needs no
854
+ // try/catch guard around exports. (V8 cannot inline JS→wasm calls made
855
+ // inside a try block, and a non-inlined call boxes every float argument.)
856
+ getPanicImportIndex() {
857
+ return this.getOrAddFuncImport("env", "panic", ["externref"], []);
858
+ }
859
+
860
+ getBoundsPanicFuncIndex() {
861
+ return this.resolveFuncIndex("__bounds_panic");
862
+ }
863
+
864
+ // `gofront/shared` buffers are `(base i32, len i32)` views over the single
865
+ // exported linear memory, bump-allocated by `__shared_new` during start.
866
+ getSharedType() {
867
+ if (this.sharedType) return this.sharedType;
868
+ const typeIndex = this._getTotalTypeCount();
869
+ const typeEntry = {
870
+ form: "struct",
871
+ fields: [
872
+ { type: "i32", mutable: false }, // byte base
873
+ { type: "i32", mutable: false }, // element count
874
+ ],
875
+ };
876
+ this._pushType(typeEntry);
877
+ this.memory = { min: 1 };
878
+ this.exports.push({ name: "memory", kind: "memory", index: 0 });
879
+ const topGlobalIndex = this.globals.length;
880
+ this.globals.push({
881
+ type: "i32",
882
+ mutable: true,
883
+ init: [{ op: "i32.const", value: 0 }],
884
+ });
885
+ const wType = { kind: "ref", nullable: true, typeIndex };
886
+ this.sharedType = { typeIndex, typeEntry, wType, topGlobalIndex };
887
+ return this.sharedType;
888
+ }
889
+
890
+ getSliceBoundsPanicFuncIndex() {
891
+ return this.resolveFuncIndex("__slice_bounds_panic");
892
+ }
893
+
894
+ getPrintlnEmptyIndex() {
895
+ return this.getOrAddFuncImport("env", "println_empty", [], []);
896
+ }
897
+
898
+ // `*testing.T` method calls: args are pushed one by one to a JS-side
899
+ // buffer, then `testing_call(t, methodNameStrIdx)` invokes the method.
900
+ getTestingArgImportIndex(wType, isBool = false) {
901
+ if (isBool)
902
+ return this.getOrAddFuncImport("env", "testing_arg_bool", ["i32"], []);
903
+ const suffix =
904
+ wType === "externref" ? "str" : wType === "anyref" ? "any" : wType;
905
+ if (!["i32", "i64", "f32", "f64", "str", "any"].includes(suffix)) {
906
+ throw new Error(
907
+ "only primitive, string, error and any arguments are supported for fmt and testing.T calls in wasm packages (format slices/structs field by field)",
908
+ );
909
+ }
910
+ return this.getOrAddFuncImport(
911
+ "env",
912
+ `testing_arg_${suffix}`,
913
+ [suffix === "str" ? "externref" : suffix === "any" ? "anyref" : wType],
914
+ [],
915
+ );
916
+ }
917
+
918
+ getTestingCallImportIndex() {
919
+ return this.getOrAddFuncImport(
920
+ "env",
921
+ "testing_call",
922
+ ["externref", "i32"],
923
+ [],
924
+ );
925
+ }
926
+
927
+ getTestingNameImportIndex() {
928
+ return this.getOrAddFuncImport(
929
+ "env",
930
+ "testing_name",
931
+ ["externref"],
932
+ ["externref"],
933
+ );
934
+ }
935
+
936
+ getTestingFlagImportIndex() {
937
+ return this.getOrAddFuncImport(
938
+ "env",
939
+ "testing_flag",
940
+ ["externref", "i32"],
941
+ ["i32"],
942
+ );
943
+ }
944
+
945
+ getLogImportIndex(wType, isLast = false, isBool = false) {
946
+ const prefix = isLast ? "println" : "print";
947
+ if (isBool) {
948
+ return this.getOrAddFuncImport("env", `${prefix}_bool`, ["i32"], []);
949
+ }
950
+ switch (wType) {
951
+ case "i32":
952
+ return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
953
+ case "i64":
954
+ return this.getOrAddFuncImport("env", `${prefix}_i64`, ["i64"], []);
955
+ case "f32":
956
+ return this.getOrAddFuncImport("env", `${prefix}_f32`, ["f32"], []);
957
+ case "f64":
958
+ return this.getOrAddFuncImport("env", `${prefix}_f64`, ["f64"], []);
959
+ case "externref":
960
+ return this.getOrAddFuncImport(
961
+ "env",
962
+ `${prefix}_str`,
963
+ ["externref"],
964
+ [],
965
+ );
966
+ default:
967
+ if (typeof wType === "object") {
968
+ return this.getOrAddFuncImport(
969
+ "env",
970
+ `${prefix}_any`,
971
+ ["anyref"],
972
+ [],
973
+ );
974
+ }
975
+ return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
976
+ }
977
+ }
978
+
979
+ getStringLenImportIndex() {
980
+ return this.getOrAddFuncImport("env", "str_len", ["externref"], ["i32"]);
981
+ }
982
+
983
+ getStringConcatImportIndex() {
984
+ return this.getOrAddFuncImport(
985
+ "env",
986
+ "str_concat",
987
+ ["externref", "externref"],
988
+ ["externref"],
989
+ );
990
+ }
991
+
992
+ getStringCmpImportIndex(op) {
993
+ const opMap = {
994
+ "==": "str_eq",
995
+ "!=": "str_ne",
996
+ "<": "str_lt",
997
+ "<=": "str_le",
998
+ ">": "str_gt",
999
+ ">=": "str_ge",
1000
+ };
1001
+ const fn = opMap[op] ?? "str_eq";
1002
+ return this.getOrAddFuncImport(
1003
+ "env",
1004
+ fn,
1005
+ ["externref", "externref"],
1006
+ ["i32"],
1007
+ );
1008
+ }
1009
+
1010
+ getStringGetImportIndex() {
1011
+ return this.getOrAddFuncImport(
1012
+ "env",
1013
+ "str_get",
1014
+ ["externref", "i32"],
1015
+ ["i32"],
1016
+ );
1017
+ }
1018
+
1019
+ getStringSliceImportIndex() {
1020
+ return this.getOrAddFuncImport(
1021
+ "env",
1022
+ "str_slice",
1023
+ ["externref", "i32", "i32"],
1024
+ ["externref"],
1025
+ );
1026
+ }
1027
+
1028
+ getStringFromCodePointImportIndex() {
1029
+ return this.getOrAddFuncImport(
1030
+ "env",
1031
+ "str_from_code_point",
1032
+ ["i32"],
1033
+ ["externref"],
1034
+ );
1035
+ }
1036
+
1037
+ getStringCodePointAtImportIndex() {
1038
+ return this.getOrAddFuncImport(
1039
+ "env",
1040
+ "str_code_point_at",
1041
+ ["externref", "i32"],
1042
+ ["i32"],
1043
+ );
1044
+ }
1045
+
1046
+ getIsStringImportIndex() {
1047
+ return this.getOrAddFuncImport("env", "is_string", ["anyref"], ["i32"]);
1048
+ }
1049
+
1050
+ getStringHashImportIndex() {
1051
+ return this.getOrAddFuncImport("env", "str_hash", ["externref"], ["i32"]);
1052
+ }
1053
+
1054
+ getStringToUpperImportIndex() {
1055
+ return this.getOrAddFuncImport(
1056
+ "env",
1057
+ "str_to_upper",
1058
+ ["externref"],
1059
+ ["externref"],
1060
+ );
1061
+ }
1062
+
1063
+ getStringToLowerImportIndex() {
1064
+ return this.getOrAddFuncImport(
1065
+ "env",
1066
+ "str_to_lower",
1067
+ ["externref"],
1068
+ ["externref"],
1069
+ );
1070
+ }
1071
+
1072
+ getStringTrimSpaceImportIndex() {
1073
+ return this.getOrAddFuncImport(
1074
+ "env",
1075
+ "str_trim_space",
1076
+ ["externref"],
1077
+ ["externref"],
1078
+ );
1079
+ }
1080
+
1081
+ getStringContainsImportIndex() {
1082
+ return this.getOrAddFuncImport(
1083
+ "env",
1084
+ "str_contains",
1085
+ ["externref", "externref"],
1086
+ ["i32"],
1087
+ );
1088
+ }
1089
+
1090
+ getStringHasPrefixImportIndex() {
1091
+ return this.getOrAddFuncImport(
1092
+ "env",
1093
+ "str_has_prefix",
1094
+ ["externref", "externref"],
1095
+ ["i32"],
1096
+ );
1097
+ }
1098
+
1099
+ getStringHasSuffixImportIndex() {
1100
+ return this.getOrAddFuncImport(
1101
+ "env",
1102
+ "str_has_suffix",
1103
+ ["externref", "externref"],
1104
+ ["i32"],
1105
+ );
1106
+ }
1107
+
1108
+ getStringIndexImportIndex() {
1109
+ return this.getOrAddFuncImport(
1110
+ "env",
1111
+ "str_index",
1112
+ ["externref", "externref"],
1113
+ ["i32"],
1114
+ );
1115
+ }
1116
+
1117
+ getStringLastIndexImportIndex() {
1118
+ return this.getOrAddFuncImport(
1119
+ "env",
1120
+ "str_last_index",
1121
+ ["externref", "externref"],
1122
+ ["i32"],
1123
+ );
1124
+ }
1125
+
1126
+ getStringRepeatImportIndex() {
1127
+ return this.getOrAddFuncImport(
1128
+ "env",
1129
+ "str_repeat",
1130
+ ["externref", "i32"],
1131
+ ["externref"],
1132
+ );
1133
+ }
1134
+
1135
+ getStringReplaceAllImportIndex() {
1136
+ return this.getOrAddFuncImport(
1137
+ "env",
1138
+ "str_replace_all",
1139
+ ["externref", "externref", "externref"],
1140
+ ["externref"],
1141
+ );
1142
+ }
1143
+
1144
+ getStringEqualFoldImportIndex() {
1145
+ return this.getOrAddFuncImport(
1146
+ "env",
1147
+ "str_equal_fold",
1148
+ ["externref", "externref"],
1149
+ ["i32"],
1150
+ );
1151
+ }
1152
+
1153
+ getStringCountImportIndex() {
1154
+ return this.getOrAddFuncImport(
1155
+ "env",
1156
+ "str_count",
1157
+ ["externref", "externref"],
1158
+ ["i32"],
1159
+ );
1160
+ }
1161
+
1162
+ getStrFromI64ImportIndex() {
1163
+ return this.getOrAddFuncImport(
1164
+ "env",
1165
+ "str_from_i64",
1166
+ ["i64"],
1167
+ ["externref"],
1168
+ );
1169
+ }
1170
+
1171
+ getStrFromI32ImportIndex() {
1172
+ return this.getOrAddFuncImport(
1173
+ "env",
1174
+ "str_from_i32",
1175
+ ["i32"],
1176
+ ["externref"],
1177
+ );
1178
+ }
1179
+
1180
+ getStrFromF64ImportIndex() {
1181
+ return this.getOrAddFuncImport(
1182
+ "env",
1183
+ "str_from_f64",
1184
+ ["f64"],
1185
+ ["externref"],
1186
+ );
1187
+ }
1188
+
1189
+ getMathImportIndex(jsName, isBinary = false) {
1190
+ const params = isBinary ? ["f64", "f64"] : ["f64"];
1191
+ return this.getOrAddFuncImport("Math", jsName, params, ["f64"]);
1192
+ }
1193
+
1194
+ // Imports from the STDLIB_IMPORTS table (strings/strconv/utf8/fmt/testing.Run).
1195
+ getStdlibImportIndex(name) {
1196
+ const idx = this.importCache.get(`env.${name}`);
1197
+ if (idx === undefined) throw new Error(`Unknown runtime import: ${name}`);
1198
+ return idx;
1199
+ }
1200
+
1201
+ // Closure type of `func(*testing.T)` (the t.Run body).
1202
+ getTestingRunClosureType() {
1203
+ return this.getClosureType({
1204
+ kind: "func",
1205
+ params: [{ kind: "pointer", base: { kind: "named", name: "testing.T" } }],
1206
+ returns: [],
1207
+ });
1208
+ }
1209
+
1210
+ resolveFuncIndex(name) {
1211
+ return this.funcMap.get(name) ?? null;
1212
+ }
1213
+
1214
+ getFuncParamTypes(name) {
1215
+ return this.funcParamTypes.get(name) ?? [];
1216
+ }
1217
+
1218
+ getFuncParamGoTypes(name) {
1219
+ return this.funcParamGoTypes.get(name) ?? [];
1220
+ }
1221
+
1222
+ resolveGlobal(name) {
1223
+ return this.globalCache.get(name) ?? null;
1224
+ }
1225
+
1226
+ resolveConst(name) {
1227
+ return this.constCache.get(name) ?? null;
1228
+ }
1229
+
1230
+ isNonLiteralConst(name) {
1231
+ return this.nonLiteralConsts.has(name);
1232
+ }
1233
+
1234
+ // Interface method dispatch is a `ref.test` chain over every concrete type
1235
+ // that declares `methodName` (no itabs); the static interface type is not
1236
+ // needed to build the candidate list.
1237
+ findInterfaceCandidates(_ifaceType, methodName) {
1238
+ const candidates = [];
1239
+ const seenKeys = new Set();
1240
+
1241
+ // 1. Struct types
1242
+ for (const [sName, sInfo] of this.structTypes.entries()) {
1243
+ const directMethodName = `${sName}.${methodName}`;
1244
+ if (this.funcMap.has(directMethodName)) {
1245
+ const fIdx = this.resolveFuncIndex(directMethodName);
1246
+ const paramGoTypes = this.getFuncParamGoTypes(directMethodName);
1247
+ const paramWTypes = this.getFuncParamTypes(directMethodName);
1248
+ const recvGoType = paramGoTypes[0];
1249
+ const isPtrRecv = recvGoType?.kind === "pointer";
1250
+
1251
+ if (isPtrRecv) {
1252
+ const key = `ptr:${sName}`;
1253
+ if (!seenKeys.has(key)) {
1254
+ seenKeys.add(key);
1255
+ candidates.push({
1256
+ recvTypeName: sName,
1257
+ testTypeIndex: sInfo.typeIndex,
1258
+ isBoxedValue: false,
1259
+ needsValueDeref: false,
1260
+ embedPath: null,
1261
+ funcIndex: fIdx,
1262
+ targetParamTypes: paramWTypes,
1263
+ structInfo: sInfo,
1264
+ });
1265
+ }
1266
+ } else {
1267
+ // Value receiver: both *sName and sName match
1268
+ const ptrKey = `ptr:${sName}`;
1269
+ if (!seenKeys.has(ptrKey)) {
1270
+ seenKeys.add(ptrKey);
1271
+ candidates.push({
1272
+ recvTypeName: sName,
1273
+ testTypeIndex: sInfo.typeIndex,
1274
+ isBoxedValue: false,
1275
+ needsValueDeref: true,
1276
+ embedPath: null,
1277
+ funcIndex: fIdx,
1278
+ targetParamTypes: paramWTypes,
1279
+ targetRecvWType: paramWTypes[0],
1280
+ structInfo: sInfo,
1281
+ });
1282
+ }
1283
+ const box = this.getBoxType({ kind: "named", name: sName });
1284
+ const valKey = `val:${sName}`;
1285
+ if (box && !seenKeys.has(valKey)) {
1286
+ seenKeys.add(valKey);
1287
+ candidates.push({
1288
+ recvTypeName: sName,
1289
+ testTypeIndex: box.typeIndex,
1290
+ isBoxedValue: true,
1291
+ needsValueDeref: false,
1292
+ embedPath: null,
1293
+ funcIndex: fIdx,
1294
+ targetParamTypes: paramWTypes,
1295
+ targetRecvWType: paramWTypes[0],
1296
+ structInfo: sInfo,
1297
+ });
1298
+ }
1299
+ }
1300
+ }
1301
+
1302
+ // Promoted methods on sName
1303
+ if (sInfo.embeds && sInfo.embeds.length > 0) {
1304
+ for (const embed of sInfo.embeds) {
1305
+ const embedMethodName = `${embed.name}.${methodName}`;
1306
+ if (this.funcMap.has(embedMethodName)) {
1307
+ const fIdx = this.resolveFuncIndex(embedMethodName);
1308
+ const paramGoTypes = this.getFuncParamGoTypes(embedMethodName);
1309
+ const paramWTypes = this.getFuncParamTypes(embedMethodName);
1310
+ const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
1311
+ const embedSInfo = this.getStructType(embed.name);
1312
+
1313
+ const ptrKey = `ptr:${sName}`;
1314
+ if (!seenKeys.has(ptrKey)) {
1315
+ seenKeys.add(ptrKey);
1316
+ candidates.push({
1317
+ recvTypeName: sName,
1318
+ testTypeIndex: sInfo.typeIndex,
1319
+ isBoxedValue: false,
1320
+ needsValueDeref: !isPtrRecv,
1321
+ embedPath: [
1322
+ {
1323
+ parentTypeIndex: sInfo.typeIndex,
1324
+ fieldIndex: embed.fieldIndex,
1325
+ },
1326
+ ],
1327
+ funcIndex: fIdx,
1328
+ targetParamTypes: paramWTypes,
1329
+ targetRecvWType: paramWTypes[0],
1330
+ structInfo: embedSInfo,
1331
+ });
1332
+ }
1333
+
1334
+ if (!isPtrRecv) {
1335
+ const box = this.getBoxType({ kind: "named", name: sName });
1336
+ const valKey = `val:${sName}`;
1337
+ if (box && !seenKeys.has(valKey)) {
1338
+ seenKeys.add(valKey);
1339
+ candidates.push({
1340
+ recvTypeName: sName,
1341
+ testTypeIndex: box.typeIndex,
1342
+ isBoxedValue: true,
1343
+ needsValueDeref: false,
1344
+ embedPath: [
1345
+ {
1346
+ parentTypeIndex: sInfo.typeIndex,
1347
+ fieldIndex: embed.fieldIndex,
1348
+ },
1349
+ ],
1350
+ funcIndex: fIdx,
1351
+ targetParamTypes: paramWTypes,
1352
+ targetRecvWType: paramWTypes[0],
1353
+ structInfo: embedSInfo,
1354
+ });
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ }
1360
+ }
1361
+
1362
+ // 2. Named non-struct types
1363
+ for (const [fnName, fIdx] of this.funcMap.entries()) {
1364
+ if (fnName.endsWith(`.${methodName}`)) {
1365
+ const rName = fnName.slice(0, fnName.length - methodName.length - 1);
1366
+ if (!this.structTypes.has(rName)) {
1367
+ const box = this.getBoxType({ kind: "named", name: rName });
1368
+ const key = `named:${rName}`;
1369
+ if (box && !seenKeys.has(key)) {
1370
+ seenKeys.add(key);
1371
+ const paramWTypes = this.getFuncParamTypes(fnName);
1372
+ candidates.push({
1373
+ recvTypeName: rName,
1374
+ testTypeIndex: box.typeIndex,
1375
+ isBoxedValue: true,
1376
+ needsValueDeref: false,
1377
+ embedPath: null,
1378
+ funcIndex: fIdx,
1379
+ targetParamTypes: paramWTypes,
1380
+ });
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+
1386
+ return candidates;
1387
+ }
1388
+
1389
+ getTypesImplementingInterface(ifaceType) {
1390
+ const candidates = [];
1391
+ const seen = new Set();
1392
+ const ifaceResolved =
1393
+ this.checker?.resolveType?.(
1394
+ ifaceType?.kind === "named" ? ifaceType.underlying : ifaceType,
1395
+ ) ?? ifaceType;
1396
+
1397
+ // 1. Struct types
1398
+ for (const [sName, sInfo] of this.structTypes.entries()) {
1399
+ const namedType = this.checker?.types?.get(sName) ?? {
1400
+ kind: "named",
1401
+ name: sName,
1402
+ };
1403
+ const ptrType = { kind: "pointer", base: namedType };
1404
+
1405
+ // Check pointer to struct
1406
+ if (this.checker?.implements?.(ptrType, ifaceResolved)) {
1407
+ if (!seen.has(sInfo.typeIndex)) {
1408
+ seen.add(sInfo.typeIndex);
1409
+ candidates.push({ name: `*${sName}`, typeIndex: sInfo.typeIndex });
1410
+ }
1411
+ }
1412
+
1413
+ // Check struct value (boxed)
1414
+ if (this.checker?.implements?.(namedType, ifaceResolved)) {
1415
+ const box = this.getBoxType(namedType);
1416
+ if (box && !seen.has(box.typeIndex)) {
1417
+ seen.add(box.typeIndex);
1418
+ candidates.push({ name: sName, typeIndex: box.typeIndex });
1419
+ }
1420
+ }
1421
+ }
1422
+
1423
+ // 2. Named non-struct types that have methods
1424
+ if (this.checker?.types) {
1425
+ for (const [tName, tVal] of this.checker.types.entries()) {
1426
+ if (
1427
+ tVal?.kind === "named" &&
1428
+ tVal.underlying?.kind !== "struct" &&
1429
+ tVal.underlying?.kind !== "interface"
1430
+ ) {
1431
+ if (this.checker.implements(tVal, ifaceResolved)) {
1432
+ const box = this.getBoxType(tVal);
1433
+ if (box && !seen.has(box.typeIndex)) {
1434
+ seen.add(box.typeIndex);
1435
+ candidates.push({ name: tName, typeIndex: box.typeIndex });
1436
+ }
1437
+ }
1438
+ }
1439
+ }
1440
+ }
1441
+
1442
+ return candidates;
1443
+ }
1444
+ }
1445
+
1446
+ function peepholeOptimize(instructions) {
1447
+ let changed = true;
1448
+ let current = instructions;
1449
+ while (changed) {
1450
+ changed = false;
1451
+ const next = [];
1452
+ for (let i = 0; i < current.length; i++) {
1453
+ const c = current[i];
1454
+ const n = current[i + 1];
1455
+
1456
+ // Pattern 1: local.get X; local.set X -> nothing
1457
+ if (
1458
+ c?.op === "local.get" &&
1459
+ n?.op === "local.set" &&
1460
+ c.index === n.index
1461
+ ) {
1462
+ changed = true;
1463
+ i++; // skip both
1464
+ continue;
1465
+ }
1466
+
1467
+ // Pattern 2: local.set X; local.get X -> local.tee X
1468
+ if (
1469
+ c?.op === "local.set" &&
1470
+ n?.op === "local.get" &&
1471
+ c.index === n.index
1472
+ ) {
1473
+ changed = true;
1474
+ next.push({ op: "local.tee", index: c.index });
1475
+ i++; // skip n (local.get)
1476
+ continue;
1477
+ }
1478
+
1479
+ // Pattern 3: local.tee X; local.set X -> local.set X
1480
+ if (
1481
+ c?.op === "local.tee" &&
1482
+ n?.op === "local.set" &&
1483
+ c.index === n.index
1484
+ ) {
1485
+ changed = true;
1486
+ next.push({ op: "local.set", index: c.index });
1487
+ i++;
1488
+ continue;
1489
+ }
1490
+
1491
+ // Pattern 4: local.tee X; drop -> local.set X
1492
+ if (c?.op === "local.tee" && (n === "drop" || n?.op === "drop")) {
1493
+ changed = true;
1494
+ next.push({ op: "local.set", index: c.index });
1495
+ i++;
1496
+ continue;
1497
+ }
1498
+
1499
+ next.push(c);
1500
+ }
1501
+ current = next;
1502
+ }
1503
+ return current;
1504
+ }
1505
+
1506
+ export function compileWasmModule(
1507
+ programs,
1508
+ checker,
1509
+ lowerResult = null,
1510
+ options = {},
1511
+ ) {
1512
+ let progs = Array.isArray(programs) ? programs : [programs];
1513
+ let resolvedChecker = checker;
1514
+ let resolvedLowerResult = lowerResult;
1515
+
1516
+ if (hasGenerics(progs)) {
1517
+ const mono = monomorphise(progs, checker);
1518
+ progs = mono.programs;
1519
+ resolvedChecker = mono.checker;
1520
+ resolvedLowerResult = lower(progs, resolvedChecker);
1521
+ } else if (!resolvedLowerResult) {
1522
+ resolvedLowerResult = lower(progs, resolvedChecker);
1523
+ }
1524
+
1525
+ const mod = new ModuleEmitter(resolvedChecker, resolvedLowerResult, progs, {
1526
+ bundledPackages: options.bundledPackages,
1527
+ });
1528
+
1529
+ // 1. Collect all functions, methods, and package-level globals
1530
+ const funcDecls = [];
1531
+ let initCount = 0;
1532
+ const initNames = [];
1533
+ for (const p of progs) {
1534
+ const pkgName = p.pkg?.name ?? "main";
1535
+ const pkgTarget = p.target ?? "wasm";
1536
+ for (const d of p.decls ?? []) {
1537
+ if (d.kind === "FuncDecl") {
1538
+ d._pkgName = pkgName;
1539
+ d._pkgTarget = pkgTarget;
1540
+ d._srcFile = p._path ?? p._filename ?? null;
1541
+ if (d.name === "init" || d.name.startsWith("init$")) {
1542
+ const renamed = initCount === 0 ? "init" : `init$${initCount}`;
1543
+ d.name = renamed;
1544
+ initNames.push(renamed);
1545
+ initCount++;
1546
+ }
1547
+ funcDecls.push(d);
1548
+ } else if (d.kind === "MethodDecl") {
1549
+ const recvTypeName =
1550
+ d.recvType?.name ??
1551
+ (d.recvType?.kind === "TypeName" ? d.recvType.name : "Recv");
1552
+ const recvGoType = d.recvPointer
1553
+ ? { kind: "pointer", base: { kind: "named", name: recvTypeName } }
1554
+ : { kind: "named", name: recvTypeName };
1555
+ const normFn = {
1556
+ kind: "FuncDecl",
1557
+ name: `${recvTypeName}.${d.name}`,
1558
+ params: [{ name: d.recvName, type: recvGoType }, ...(d.params ?? [])],
1559
+ returnType: d.returnType,
1560
+ body: d.body,
1561
+ _isMethod: true,
1562
+ _recvTypeName: recvTypeName,
1563
+ _methodName: d.name,
1564
+ _exportName: `${recvTypeName}_${d.name}`,
1565
+ _sourceDecl: d,
1566
+ _pkgName: pkgName,
1567
+ _pkgTarget: pkgTarget,
1568
+ _srcFile: p._path ?? p._filename ?? null,
1569
+ _line: d._line,
1570
+ };
1571
+ funcDecls.push(normFn);
1572
+ }
1573
+ }
1574
+ }
1575
+
1576
+ // 1b. Scan and lift all closures (FuncLits)
1577
+ let closureCounter = 0;
1578
+ const closures = [];
1579
+
1580
+ function scanClosures(node, rootFn) {
1581
+ if (!node || typeof node !== "object") return;
1582
+ if (Array.isArray(node)) {
1583
+ for (const item of node) scanClosures(item, rootFn);
1584
+ return;
1585
+ }
1586
+ if (node.kind === "FuncLit") {
1587
+ closureCounter++;
1588
+ const closureName = `_closure$${closureCounter}`;
1589
+ node._liftedName = closureName;
1590
+ node._rootFuncDecl = rootFn;
1591
+
1592
+ const liftedFn = {
1593
+ kind: "FuncDecl",
1594
+ name: closureName,
1595
+ params: [
1596
+ { name: "__env", type: { kind: "basic", name: "any" } },
1597
+ ...(node.params ?? []),
1598
+ ],
1599
+ returnType: node.returnType,
1600
+ body: node.body,
1601
+ _isClosure: true,
1602
+ _funcLit: node,
1603
+ _rootFuncDecl: rootFn,
1604
+ _srcFile: rootFn?._srcFile ?? null,
1605
+ _line: typeof node._line === "number" ? node._line : rootFn?._line,
1606
+ };
1607
+ node._liftedFn = liftedFn;
1608
+ closures.push(liftedFn);
1609
+
1610
+ scanClosures(node.body, rootFn);
1611
+ return;
1612
+ }
1613
+ for (const key of Object.keys(node)) {
1614
+ if (key.startsWith("_")) continue;
1615
+ scanClosures(node[key], rootFn);
1616
+ }
1617
+ }
1618
+
1619
+ for (const fn of funcDecls) {
1620
+ fn._rootFuncDecl = fn;
1621
+ if (fn.body) {
1622
+ scanClosures(fn.body, fn);
1623
+ }
1624
+ }
1625
+ funcDecls.push(...closures);
1626
+
1627
+ // 1c. Add trampolines for named functions (for when they are passed as function values)
1628
+ const topLevelFuncs = new Map();
1629
+ for (const fn of funcDecls) {
1630
+ if (
1631
+ !fn._isClosure &&
1632
+ !fn._isMethod &&
1633
+ fn.name !== "main" &&
1634
+ !fn.name.startsWith("_")
1635
+ ) {
1636
+ topLevelFuncs.set(fn.name, fn);
1637
+ }
1638
+ }
1639
+ mod.topLevelFuncMap = topLevelFuncs;
1640
+
1641
+ const trampolines = [];
1642
+ for (const [name, targetFn] of topLevelFuncs.entries()) {
1643
+ const trampName = `_tramp$${name}`;
1644
+ const callExpr = {
1645
+ kind: "CallExpr",
1646
+ func: { kind: "Ident", name: targetFn.name },
1647
+ args: (targetFn.params ?? []).map((p) => ({
1648
+ kind: "Ident",
1649
+ name: p.name,
1650
+ _type: p.type,
1651
+ })),
1652
+ _type: targetFn.returnType ?? { kind: "basic", name: "void" },
1653
+ };
1654
+ const bodyStmts = targetFn.returnType
1655
+ ? [
1656
+ {
1657
+ kind: "ReturnStmt",
1658
+ values: [callExpr],
1659
+ },
1660
+ ]
1661
+ : [
1662
+ { kind: "ExprStmt", expr: callExpr },
1663
+ { kind: "ReturnStmt", values: [] },
1664
+ ];
1665
+
1666
+ const trampFn = {
1667
+ kind: "FuncDecl",
1668
+ name: trampName,
1669
+ params: [
1670
+ { name: "__env", type: { kind: "basic", name: "any" } },
1671
+ ...(targetFn.params ?? []),
1672
+ ],
1673
+ returnType: targetFn.returnType,
1674
+ body: {
1675
+ kind: "Block",
1676
+ stmts: bodyStmts,
1677
+ list: bodyStmts,
1678
+ },
1679
+ _isClosure: true,
1680
+ _isTrampoline: true,
1681
+ _targetFuncName: name,
1682
+ _rootFuncDecl: targetFn,
1683
+ };
1684
+ trampolines.push(trampFn);
1685
+ }
1686
+ funcDecls.push(...trampolines);
1687
+
1688
+ const pendingInits = _collectPackageGlobals(progs, mod, checker);
1689
+ _collectPackageConsts(progs, mod);
1690
+ const globalInitFn =
1691
+ pendingInits.length > 0 || initNames.length > 0
1692
+ ? _makeGlobalInitFunc(pendingInits, initNames)
1693
+ : null;
1694
+ if (globalInitFn) funcDecls.push(globalInitFn);
1695
+
1696
+ const boundsPanicFn = {
1697
+ kind: "FuncDecl",
1698
+ name: "__bounds_panic",
1699
+ params: [],
1700
+ returnType: null,
1701
+ _isRuntimePanic: true,
1702
+ _panicMsg: "runtime error: index out of range",
1703
+ };
1704
+ funcDecls.push(boundsPanicFn);
1705
+
1706
+ const sliceBoundsPanicFn = {
1707
+ kind: "FuncDecl",
1708
+ name: "__slice_bounds_panic",
1709
+ params: [],
1710
+ returnType: null,
1711
+ _isRuntimePanic: true,
1712
+ _panicMsg: "runtime error: slice bounds out of range",
1713
+ };
1714
+ funcDecls.push(sliceBoundsPanicFn);
1715
+
1716
+ const pushPanicFn = {
1717
+ kind: "FuncDecl",
1718
+ name: "__push_panic",
1719
+ params: [{ name: "msg", type: { kind: "basic", name: "string" } }],
1720
+ returnType: null,
1721
+ _isPushPanic: true,
1722
+ };
1723
+ funcDecls.push(pushPanicFn);
1724
+
1725
+ // 1d. Interface dispatchers
1726
+ _collectInterfaceDispatchers(progs, funcDecls, mod, checker);
1727
+
1728
+ // 1e. Map helpers
1729
+ function scanMapTypes(node) {
1730
+ if (!node || typeof node !== "object") return;
1731
+ if (Array.isArray(node)) {
1732
+ for (const item of node) scanMapTypes(item);
1733
+ return;
1734
+ }
1735
+ if (node._type && isMapType(node._type, checker)) {
1736
+ const { keyType, valType } = getMapKeyValTypes(node._type, checker);
1737
+ if (keyType && valType) {
1738
+ mod.getMapType(keyType, valType);
1739
+ }
1740
+ }
1741
+ if (node.kind === "MapType") {
1742
+ const keyGoType = node.key;
1743
+ const valGoType = node.value ?? node.elem;
1744
+ if (keyGoType && valGoType) {
1745
+ mod.getMapType(keyGoType, valGoType);
1746
+ }
1747
+ }
1748
+ for (const key of Object.keys(node)) {
1749
+ if (key.startsWith("_")) continue;
1750
+ scanMapTypes(node[key]);
1751
+ }
1752
+ }
1753
+
1754
+ for (const fn of funcDecls) {
1755
+ if (fn.params) scanMapTypes(fn.params);
1756
+ if (fn.returnType) scanMapTypes(fn.returnType);
1757
+ if (fn.body) scanMapTypes(fn.body);
1758
+ }
1759
+ for (const p of progs) {
1760
+ for (const d of p.decls ?? []) {
1761
+ scanMapTypes(d);
1762
+ }
1763
+ }
1764
+
1765
+ for (const mapInfo of mod.mapTypes.values()) {
1766
+ // make
1767
+ funcDecls.push({
1768
+ kind: "FuncDecl",
1769
+ name: mapInfo.makeFuncName,
1770
+ params: [{ name: "cap", type: { kind: "basic", name: "int32" } }],
1771
+ returnType: mapInfo.wType,
1772
+ body: { kind: "Block", stmts: [], list: [] },
1773
+ _isMapHelper: true,
1774
+ _mapHelperKind: "make",
1775
+ _mapInfo: mapInfo,
1776
+ _rootFuncDecl: null,
1777
+ });
1778
+ // get
1779
+ funcDecls.push({
1780
+ kind: "FuncDecl",
1781
+ name: mapInfo.getFuncName,
1782
+ params: [
1783
+ { name: "m", type: mapInfo.wType },
1784
+ { name: "k", type: mapInfo.keyGoType },
1785
+ ],
1786
+ returnType: mapInfo.valGoType,
1787
+ body: { kind: "Block", stmts: [], list: [] },
1788
+ _isMapHelper: true,
1789
+ _mapHelperKind: "get",
1790
+ _mapInfo: mapInfo,
1791
+ _rootFuncDecl: null,
1792
+ });
1793
+ // get_ok
1794
+ funcDecls.push({
1795
+ kind: "FuncDecl",
1796
+ name: mapInfo.getOkFuncName,
1797
+ params: [
1798
+ { name: "m", type: mapInfo.wType },
1799
+ { name: "k", type: mapInfo.keyGoType },
1800
+ ],
1801
+ returnType: {
1802
+ kind: "tuple",
1803
+ types: [mapInfo.valGoType, { kind: "basic", name: "bool" }],
1804
+ },
1805
+ body: { kind: "Block", stmts: [], list: [] },
1806
+ _isMapHelper: true,
1807
+ _mapHelperKind: "get_ok",
1808
+ _mapInfo: mapInfo,
1809
+ _rootFuncDecl: null,
1810
+ });
1811
+ // set
1812
+ funcDecls.push({
1813
+ kind: "FuncDecl",
1814
+ name: mapInfo.setFuncName,
1815
+ params: [
1816
+ { name: "m", type: mapInfo.wType },
1817
+ { name: "k", type: mapInfo.keyGoType },
1818
+ { name: "v", type: mapInfo.valGoType },
1819
+ ],
1820
+ returnType: null,
1821
+ body: { kind: "Block", stmts: [], list: [] },
1822
+ _isMapHelper: true,
1823
+ _mapHelperKind: "set",
1824
+ _mapInfo: mapInfo,
1825
+ _rootFuncDecl: null,
1826
+ });
1827
+ // delete
1828
+ funcDecls.push({
1829
+ kind: "FuncDecl",
1830
+ name: mapInfo.deleteFuncName,
1831
+ params: [
1832
+ { name: "m", type: mapInfo.wType },
1833
+ { name: "k", type: mapInfo.keyGoType },
1834
+ ],
1835
+ returnType: null,
1836
+ body: { kind: "Block", stmts: [], list: [] },
1837
+ _isMapHelper: true,
1838
+ _mapHelperKind: "delete",
1839
+ _mapInfo: mapInfo,
1840
+ _rootFuncDecl: null,
1841
+ });
1842
+ // len
1843
+ funcDecls.push({
1844
+ kind: "FuncDecl",
1845
+ name: mapInfo.lenFuncName,
1846
+ params: [{ name: "m", type: mapInfo.wType }],
1847
+ returnType: { kind: "basic", name: "int32" },
1848
+ body: { kind: "Block", stmts: [], list: [] },
1849
+ _isMapHelper: true,
1850
+ _mapHelperKind: "len",
1851
+ _mapInfo: mapInfo,
1852
+ _rootFuncDecl: null,
1853
+ });
1854
+ // clear
1855
+ funcDecls.push({
1856
+ kind: "FuncDecl",
1857
+ name: mapInfo.clearFuncName,
1858
+ params: [{ name: "m", type: mapInfo.wType }],
1859
+ returnType: null,
1860
+ body: { kind: "Block", stmts: [], list: [] },
1861
+ _isMapHelper: true,
1862
+ _mapHelperKind: "clear",
1863
+ _mapInfo: mapInfo,
1864
+ _rootFuncDecl: null,
1865
+ });
1866
+ // keys
1867
+ funcDecls.push({
1868
+ kind: "FuncDecl",
1869
+ name: mapInfo.keysFuncName,
1870
+ params: [{ name: "m", type: mapInfo.wType }],
1871
+ returnType: { kind: "slice", elem: mapInfo.keyGoType },
1872
+ body: { kind: "Block", stmts: [], list: [] },
1873
+ _isMapHelper: true,
1874
+ _mapHelperKind: "keys",
1875
+ _mapInfo: mapInfo,
1876
+ _rootFuncDecl: null,
1877
+ });
1878
+ // values
1879
+ funcDecls.push({
1880
+ kind: "FuncDecl",
1881
+ name: mapInfo.valuesFuncName,
1882
+ params: [{ name: "m", type: mapInfo.wType }],
1883
+ returnType: { kind: "slice", elem: mapInfo.valGoType },
1884
+ body: { kind: "Block", stmts: [], list: [] },
1885
+ _isMapHelper: true,
1886
+ _mapHelperKind: "values",
1887
+ _mapInfo: mapInfo,
1888
+ _rootFuncDecl: null,
1889
+ });
1890
+ // clone
1891
+ funcDecls.push({
1892
+ kind: "FuncDecl",
1893
+ name: mapInfo.cloneFuncName,
1894
+ params: [{ name: "m", type: mapInfo.wType }],
1895
+ returnType: mapInfo.wType,
1896
+ body: { kind: "Block", stmts: [], list: [] },
1897
+ _isMapHelper: true,
1898
+ _mapHelperKind: "clone",
1899
+ _mapInfo: mapInfo,
1900
+ _rootFuncDecl: null,
1901
+ });
1902
+ }
1903
+
1904
+ // 1e2. `gofront/shared` linear-memory helpers (only when imported).
1905
+ const usesShared = progs.some((p) =>
1906
+ (p.imports ?? []).some((decl) =>
1907
+ (decl.imports ?? [decl]).some((imp) => imp.path === "gofront/shared"),
1908
+ ),
1909
+ );
1910
+ if (usesShared) {
1911
+ const sharedWType = mod.getSharedType().wType;
1912
+ const i32 = { kind: "basic", name: "int32" };
1913
+ funcDecls.push({
1914
+ kind: "FuncDecl",
1915
+ name: "__shared_len_panic",
1916
+ params: [],
1917
+ returnType: null,
1918
+ _isRuntimePanic: true,
1919
+ _panicMsg: "runtime error: shared buffer length out of range",
1920
+ });
1921
+ funcDecls.push({
1922
+ kind: "FuncDecl",
1923
+ name: "__shared_new",
1924
+ params: [
1925
+ { name: "len", type: i32 },
1926
+ { name: "bytes", type: i32 },
1927
+ ],
1928
+ returnType: sharedWType,
1929
+ body: { kind: "Block", stmts: [], list: [] },
1930
+ _isSharedHelper: "new",
1931
+ _rootFuncDecl: null,
1932
+ });
1933
+ funcDecls.push({
1934
+ kind: "FuncDecl",
1935
+ name: "__shared_sub",
1936
+ params: [
1937
+ { name: "buf", type: sharedWType },
1938
+ { name: "lo", type: i32 },
1939
+ { name: "hi", type: i32 },
1940
+ { name: "bytes", type: i32 },
1941
+ ],
1942
+ returnType: sharedWType,
1943
+ body: { kind: "Block", stmts: [], list: [] },
1944
+ _isSharedHelper: "sub",
1945
+ _rootFuncDecl: null,
1946
+ });
1947
+ }
1948
+
1949
+ // 1e3. Runtime helpers: error values, interface equality, t.Run callback.
1950
+ mod.usesTestingRun = funcDecls.some((fn) => _usesTestingRun(fn.body));
1951
+ mod.usesAnyEq = _usesAnyEq(funcDecls, mod);
1952
+ const ANY_T = { kind: "basic", name: "any" };
1953
+ const STR_T = { kind: "basic", name: "string" };
1954
+ const BOOL_T = { kind: "basic", name: "bool" };
1955
+ const helper = (name, params, returnType, kind, extra = {}) => ({
1956
+ kind: "FuncDecl",
1957
+ name,
1958
+ params,
1959
+ returnType,
1960
+ body: { kind: "Block", stmts: [], list: [] },
1961
+ _isRuntimeHelper: kind,
1962
+ _rootFuncDecl: null,
1963
+ ...extra,
1964
+ });
1965
+ if (mod.usesAnyEq) {
1966
+ funcDecls.push(
1967
+ helper(
1968
+ "__any_eq",
1969
+ [
1970
+ { name: "a", type: ANY_T },
1971
+ { name: "b", type: ANY_T },
1972
+ ],
1973
+ BOOL_T,
1974
+ "any_eq",
1975
+ ),
1976
+ );
1977
+ }
1978
+ if (mod.hasErrorType) {
1979
+ const errPtr = {
1980
+ kind: "pointer",
1981
+ base: { kind: "named", name: ERROR_STRUCT },
1982
+ };
1983
+ funcDecls.push(
1984
+ helper(
1985
+ `${ERROR_STRUCT}.Error`,
1986
+ [{ name: "e", type: errPtr }],
1987
+ STR_T,
1988
+ "error_msg",
1989
+ {
1990
+ _isMethod: true,
1991
+ _recvTypeName: ERROR_STRUCT,
1992
+ _methodName: "Error",
1993
+ },
1994
+ ),
1995
+ helper(
1996
+ `${ERROR_STRUCT}.Unwrap`,
1997
+ [{ name: "e", type: errPtr }],
1998
+ ANY_T,
1999
+ "error_cause",
2000
+ {
2001
+ _isMethod: true,
2002
+ _recvTypeName: ERROR_STRUCT,
2003
+ _methodName: "Unwrap",
2004
+ },
2005
+ ),
2006
+ helper(
2007
+ "__mk_error",
2008
+ [
2009
+ { name: "msg", type: STR_T },
2010
+ { name: "cause", type: ANY_T },
2011
+ ],
2012
+ ANY_T,
2013
+ "mk_error",
2014
+ ),
2015
+ helper("__error_str", [{ name: "e", type: ANY_T }], STR_T, "error_str"),
2016
+ {
2017
+ kind: "FuncDecl",
2018
+ name: "__errors_unwrap",
2019
+ params: [{ name: "__recv", type: ANY_T }],
2020
+ returnType: ANY_T,
2021
+ body: { kind: "Block", stmts: [], list: [] },
2022
+ _isInterfaceDispatcher: true,
2023
+ _softDispatch: true,
2024
+ _ifaceName: "error",
2025
+ _ifaceType: null,
2026
+ _methodName: "Unwrap",
2027
+ _rootFuncDecl: null,
2028
+ },
2029
+ helper(
2030
+ "__errors_is",
2031
+ [
2032
+ { name: "err", type: ANY_T },
2033
+ { name: "target", type: ANY_T },
2034
+ ],
2035
+ BOOL_T,
2036
+ "errors_is",
2037
+ ),
2038
+ );
2039
+ }
2040
+ if (mod.usesTestingRun) {
2041
+ funcDecls.push(
2042
+ helper(
2043
+ "__testing_run_cb",
2044
+ [
2045
+ { name: "fn", type: ANY_T },
2046
+ {
2047
+ name: "t",
2048
+ type: {
2049
+ kind: "pointer",
2050
+ base: { kind: "named", name: "testing.T" },
2051
+ },
2052
+ },
2053
+ ],
2054
+ null,
2055
+ "testing_run_cb",
2056
+ { _forceExport: true },
2057
+ ),
2058
+ );
2059
+ }
2060
+
2061
+ // 1f. Boundary metadata (exported surface of wasm packages)
2062
+ const boundaryMeta = options.boundary
2063
+ ? collectBoundaryMeta(progs, funcDecls, mod)
2064
+ : null;
2065
+
2066
+ // 2. Pre-scan for needed imports so import func indices are fixed
2067
+ for (const fn of funcDecls) {
2068
+ _scanImportsInBody(fn.body, mod);
2069
+ }
2070
+ if (boundaryMeta) scanBoundaryImports(mod, boundaryMeta);
2071
+
2072
+ const importFuncCount = mod.imports.filter((i) => i.kind === "func").length;
2073
+ mod._importsLocked = true;
2074
+
2075
+ // 3. Register all internal functions and their type signatures
2076
+ for (let i = 0; i < funcDecls.length; i++) {
2077
+ const fn = funcDecls[i];
2078
+ const globalIdx = importFuncCount + i;
2079
+ mod.funcMap.set(fn.name, globalIdx);
2080
+ if (fn._isClosure) {
2081
+ if (fn._funcLit) {
2082
+ fn._funcLit._globalFuncIndex = globalIdx;
2083
+ }
2084
+ mod.addRefFuncElement(globalIdx);
2085
+ }
2086
+
2087
+ const paramTypes = (fn.params ?? []).map((p) =>
2088
+ toWasmType(p.type, checker, mod),
2089
+ );
2090
+ mod.funcParamTypes.set(fn.name, paramTypes);
2091
+ mod.funcParamGoTypes.set(
2092
+ fn.name,
2093
+ (fn.params ?? []).map((p) => p.type),
2094
+ );
2095
+ const returnTypes = [];
2096
+ if (fn.returnType) {
2097
+ if (
2098
+ fn.returnType.kind === "TupleType" ||
2099
+ fn.returnType.kind === "tuple"
2100
+ ) {
2101
+ for (const t of fn.returnType.types) {
2102
+ const wt = toWasmType(t, checker, mod);
2103
+ if (wt) returnTypes.push(wt);
2104
+ }
2105
+ } else {
2106
+ const wt = toWasmType(fn.returnType, checker, mod);
2107
+ if (wt) returnTypes.push(wt);
2108
+ }
2109
+ }
2110
+
2111
+ const typeIndex = mod.getTypeIndex(paramTypes, returnTypes);
2112
+ fn._typeIndex = typeIndex;
2113
+ fn._globalFuncIndex = globalIdx;
2114
+ mod.funcReturnTypesByIdx.set(globalIdx, returnTypes);
2115
+
2116
+ // Export if public (capitalized) or main
2117
+ if (
2118
+ fn._forceExport ||
2119
+ (!fn._isGlobalInit &&
2120
+ !fn._isRuntimeHelper &&
2121
+ (options.exportAll ||
2122
+ fn.name === "main" ||
2123
+ (fn.name[0] >= "A" && fn.name[0] <= "Z") ||
2124
+ (fn._isMethod &&
2125
+ fn._methodName[0] >= "A" &&
2126
+ fn._methodName[0] <= "Z")))
2127
+ ) {
2128
+ mod.exports.push({ name: fn.name, kind: "func", index: globalIdx });
2129
+ if (fn._isMethod && fn._exportName) {
2130
+ mod.exports.push({
2131
+ name: fn._exportName,
2132
+ kind: "func",
2133
+ index: globalIdx,
2134
+ });
2135
+ }
2136
+ }
2137
+ if (
2138
+ fn._isPushPanic &&
2139
+ !mod.exports.some((e) => e.name === "__push_panic")
2140
+ ) {
2141
+ mod.exports.push({
2142
+ name: "__push_panic",
2143
+ kind: "func",
2144
+ index: globalIdx,
2145
+ });
2146
+ }
2147
+ }
2148
+
2149
+ // 4. Emit function bodies
2150
+ for (const fn of funcDecls) {
2151
+ const emitter = new FunctionEmitter(mod, fn, fn._globalFuncIndex);
2152
+ if (fn._isRuntimePanic) {
2153
+ const strIdx = mod.internString(fn._panicMsg);
2154
+ const funcIdx = mod.getStringImportIndex();
2155
+ emitter.pushInstruction({ op: "i32.const", value: strIdx });
2156
+ emitter.pushInstruction({ op: "call", funcIndex: funcIdx });
2157
+ emitter.pushInstruction("any.convert_extern");
2158
+ emitter.emitPanicThrow();
2159
+ } else if (fn._isPushPanic) {
2160
+ const panicNodeTypeIndex = mod.getPanicNodeTypeIndex();
2161
+ const panicGlobal = mod.getPanicGlobalIndex();
2162
+ emitter.pushInstruction({ op: "local.get", index: 0 });
2163
+ emitter.pushInstruction("any.convert_extern");
2164
+ emitter.pushInstruction({ op: "i32.const", value: 0 });
2165
+ emitter.pushInstruction({ op: "global.get", index: panicGlobal });
2166
+ emitter.pushInstruction({
2167
+ op: "struct.new",
2168
+ typeIndex: panicNodeTypeIndex,
2169
+ });
2170
+ emitter.pushInstruction({ op: "global.set", index: panicGlobal });
2171
+ emitter.pushInstruction("return");
2172
+ } else if (fn._isInterfaceDispatcher) {
2173
+ emitter.emitInterfaceDispatcher(fn);
2174
+ } else if (fn._isMapHelper) {
2175
+ emitter.emitMapHelper(fn);
2176
+ } else if (fn._isSharedHelper) {
2177
+ emitter.emitSharedHelper(fn);
2178
+ } else if (fn._isRuntimeHelper) {
2179
+ emitter.emitRuntimeHelper(fn);
2180
+ } else if (fn.body) {
2181
+ emitter.emitFunctionBody(fn.body);
2182
+ }
2183
+ // If last instruction is not return or unreachable, auto-emit return or unreachable
2184
+ let body = emitter.body;
2185
+ if (
2186
+ body.length === 0 ||
2187
+ (body[body.length - 1].op !== "return" &&
2188
+ body[body.length - 1].op !== "unreachable")
2189
+ ) {
2190
+ if (emitter.returnTypes.length === 0) {
2191
+ body.push({ op: "return" });
2192
+ } else {
2193
+ body.push({ op: "unreachable" });
2194
+ }
2195
+ }
2196
+
2197
+ body = peepholeOptimize(body);
2198
+
2199
+ mod.funcs.push({
2200
+ typeIndex: fn._typeIndex,
2201
+ locals: emitter.localTypes,
2202
+ body,
2203
+ src:
2204
+ fn._srcFile && typeof fn._line === "number"
2205
+ ? { file: fn._srcFile, line: fn._line }
2206
+ : null,
2207
+ });
2208
+ }
2209
+
2210
+ // 5. Boundary marshalling helpers + JS facade
2211
+ let facade = null;
2212
+ if (boundaryMeta) {
2213
+ addBoundaryHelpers(mod, boundaryMeta);
2214
+ facade = generateFacade(boundaryMeta, {
2215
+ stringTable: mod.stringTable,
2216
+ callMain: Boolean(options.callMain) && mod.funcMap.has("main"),
2217
+ stdlibEnv: mod.usesStdlibEnv,
2218
+ });
2219
+ }
2220
+
2221
+ const moduleIR = {
2222
+ types: mod.types,
2223
+ imports: mod.imports,
2224
+ tags: mod.tags,
2225
+ globals: mod.globals,
2226
+ elements: mod.elements,
2227
+ funcs: mod.funcs,
2228
+ exports: mod.exports,
2229
+ memory: mod.memory ?? null,
2230
+ start: globalInitFn ? globalInitFn._globalFuncIndex : null,
2231
+ sourceMappingURL: options.sourceMappingURL ?? null,
2232
+ };
2233
+
2234
+ const wasmBytes = encodeModule(moduleIR);
2235
+ const watText = options.emitWat ? emitWat(moduleIR) : null;
2236
+
2237
+ // Function-level source positions: `sources` are the absolute Go files,
2238
+ // `mappings` pair each function body's byte offset with its 0-based line.
2239
+ const sources = [];
2240
+ const sourceIndex = new Map();
2241
+ const mappings = [];
2242
+ moduleIR.funcs.forEach((fn, i) => {
2243
+ if (!fn.src) return;
2244
+ let idx = sourceIndex.get(fn.src.file);
2245
+ if (idx === undefined) {
2246
+ idx = sources.length;
2247
+ sources.push(fn.src.file);
2248
+ sourceIndex.set(fn.src.file, idx);
2249
+ }
2250
+ mappings.push({
2251
+ offset: moduleIR.codeOffsets[i],
2252
+ srcFileIdx: idx,
2253
+ srcLine: Math.max(0, fn.src.line - 1),
2254
+ });
2255
+ });
2256
+
2257
+ return {
2258
+ wasm: wasmBytes,
2259
+ wat: watText,
2260
+ stringTable: mod.stringTable,
2261
+ moduleIR,
2262
+ facade,
2263
+ sourceMapInfo: { sources, mappings },
2264
+ };
2265
+ }
2266
+
2267
+ // Package-level `const` declarations with literal values (iota is already
2268
+ // substituted by the parser). Non-literal constant expressions are not
2269
+ // supported by the wasm backend yet.
2270
+ function _collectPackageConsts(progs, mod) {
2271
+ for (const p of progs) {
2272
+ for (const d of p.decls ?? []) {
2273
+ if (d.kind !== "ConstDecl") continue;
2274
+ for (const spec of d.decls ?? []) {
2275
+ for (let i = 0; i < spec.names.length; i++) {
2276
+ let lit = spec.value?.[i];
2277
+ if (
2278
+ lit?.kind === "UnaryExpr" &&
2279
+ lit.op === "-" &&
2280
+ lit.operand?.kind === "BasicLit"
2281
+ ) {
2282
+ lit = { ...lit.operand, value: `-${lit.operand.value}` };
2283
+ }
2284
+ if (lit?.kind === "BasicLit") mod.constCache.set(spec.names[i], lit);
2285
+ else if (lit) mod.nonLiteralConsts.add(spec.names[i]);
2286
+ }
2287
+ }
2288
+ }
2289
+ }
2290
+ }
2291
+
2292
+ // Depth-first search over AST nodes (skipping `_`-prefixed annotations).
2293
+ function _someNode(node, pred) {
2294
+ if (!node || typeof node !== "object") return false;
2295
+ if (Array.isArray(node)) return node.some((n) => _someNode(n, pred));
2296
+ if (pred(node)) return true;
2297
+ for (const key of Object.keys(node)) {
2298
+ if (key.startsWith("_")) continue;
2299
+ if (_someNode(node[key], pred)) return true;
2300
+ }
2301
+ return false;
2302
+ }
2303
+
2304
+ function _usesTestingRun(node) {
2305
+ return _someNode(
2306
+ node,
2307
+ (n) =>
2308
+ n.kind === "CallExpr" &&
2309
+ n.func?.kind === "SelectorExpr" &&
2310
+ n.func.field === "Run" &&
2311
+ isTestingT(n.func.expr?._type),
2312
+ );
2313
+ }
2314
+
2315
+ // `__any_eq` is only needed when some `==`/`!=` (or switch case) compares
2316
+ // anyref operands, a struct with an anyref field is compared, or the error
2317
+ // runtime (`errors.Is`) is present. `_emitAnyEq` throws if this misses a
2318
+ // site, so a gap surfaces as a compile error rather than a bad module.
2319
+ function _usesAnyEq(funcDecls, mod) {
2320
+ if (mod.hasErrorType) return true;
2321
+ for (const info of mod.structTypes.values()) {
2322
+ if (info.fields?.some((f) => f.wType === "anyref")) return true;
2323
+ }
2324
+ const isAnyRef = (n) =>
2325
+ Boolean(n?._type) && toWasmType(n._type, mod.checker, mod) === "anyref";
2326
+ return funcDecls.some((fn) =>
2327
+ _someNode(fn.body, (n) => {
2328
+ if (n.kind === "BinaryExpr" && (n.op === "==" || n.op === "!="))
2329
+ return isAnyRef(n.left) || isAnyRef(n.right);
2330
+ if (n.kind === "SwitchStmt" && n.tag) return isAnyRef(n.tag);
2331
+ return false;
2332
+ }),
2333
+ );
2334
+ }
2335
+
2336
+ function _scanImportsInBody(node, mod) {
2337
+ if (!node || typeof node !== "object") return;
2338
+
2339
+ if (node.kind === "CallExpr") {
2340
+ const { func, args } = node;
2341
+ if (
2342
+ func.kind === "Ident" &&
2343
+ (func.name === "print" || func.name === "println")
2344
+ ) {
2345
+ const isPrintln = func.name === "println";
2346
+ if (args.length === 0 && isPrintln) {
2347
+ mod.getPrintlnEmptyIndex();
2348
+ }
2349
+ for (let i = 0; i < args.length; i++) {
2350
+ const arg = args[i];
2351
+ const isLast = i === args.length - 1 && isPrintln;
2352
+ const isBool =
2353
+ arg._type?.name === "bool" ||
2354
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL");
2355
+ const wType = toWasmType(arg._type, mod.checker);
2356
+ mod.getLogImportIndex(wType, isLast, isBool);
2357
+ }
2358
+ } else if (
2359
+ func.kind === "SelectorExpr" &&
2360
+ func.expr?.name === "math" &&
2361
+ func.field
2362
+ ) {
2363
+ const jsMathMap = {
2364
+ Sin: "sin",
2365
+ Cos: "cos",
2366
+ Tan: "tan",
2367
+ Asin: "asin",
2368
+ Acos: "acos",
2369
+ Atan: "atan",
2370
+ Atan2: "atan2",
2371
+ Pow: "pow",
2372
+ Mod: "mod",
2373
+ Hypot: "hypot",
2374
+ Cbrt: "cbrt",
2375
+ Exp: "exp",
2376
+ Log: "log",
2377
+ Log2: "log2",
2378
+ Log10: "log10",
2379
+ Round: "round",
2380
+ };
2381
+ if (jsMathMap[func.field]) {
2382
+ const isBinary = BINARY_MATH_IMPORTS.has(jsMathMap[func.field]);
2383
+ mod.getMathImportIndex(jsMathMap[func.field], isBinary);
2384
+ }
2385
+ } else if (
2386
+ func.kind === "SelectorExpr" &&
2387
+ isTestingT(func.expr?._type) &&
2388
+ func.field
2389
+ ) {
2390
+ mod.internString(func.field);
2391
+ if (func.field === "Name") mod.getTestingNameImportIndex();
2392
+ else if (func.field === "Failed" || func.field === "Skipped")
2393
+ mod.getTestingFlagImportIndex();
2394
+ else if (func.field === "Run") {
2395
+ // closure type for func(*testing.T) must exist before bodies are emitted
2396
+ mod.getTestingRunClosureType();
2397
+ } else {
2398
+ mod.getTestingCallImportIndex();
2399
+ for (const arg of args) {
2400
+ mod.getTestingArgImportIndex(
2401
+ toWasmType(arg._type, mod.checker),
2402
+ arg._type?.name === "bool" ||
2403
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL"),
2404
+ );
2405
+ }
2406
+ }
2407
+ }
2408
+ }
2409
+
2410
+ if (node.kind === "BasicLit" && node.litKind === "STRING") {
2411
+ mod.getStringImportIndex();
2412
+ const strVal = String(node.value ?? "");
2413
+ mod.internString(strVal);
2414
+ }
2415
+
2416
+ // Recurse child properties
2417
+ for (const key of Object.keys(node)) {
2418
+ if (key.startsWith("_")) continue;
2419
+ const child = node[key];
2420
+ if (Array.isArray(child)) {
2421
+ for (const item of child) _scanImportsInBody(item, mod);
2422
+ } else if (child && typeof child === "object") {
2423
+ _scanImportsInBody(child, mod);
2424
+ }
2425
+ }
2426
+ }
2427
+
2428
+ export function compileWasm(source, options = {}) {
2429
+ const filename = options.filename ?? "main.go";
2430
+ const tokens = new Lexer(source, filename).tokenize();
2431
+ const ast = new Parser(tokens, filename, source).parse();
2432
+
2433
+ const checker = new TypeChecker();
2434
+ checker.target = "wasm";
2435
+ checker.pkgName = ast.pkg?.name ?? "main";
2436
+
2437
+ const errors = checker.check(ast);
2438
+ if (errors.length > 0) {
2439
+ return { wasm: null, errors };
2440
+ }
2441
+
2442
+ const lowerRes = lower([ast], checker);
2443
+ const res = compileWasmModule([ast], checker, lowerRes, {
2444
+ ...options,
2445
+ exportAll: options.exportAll ?? true,
2446
+ });
2447
+
2448
+ return { ...res, errors: [] };
2449
+ }
2450
+
2451
+ function _isPointerGoType(t, mod) {
2452
+ if (!t) return false;
2453
+ if (t.kind === "pointer" || t.kind === "PointerType" || t.kind === "StarExpr")
2454
+ return true;
2455
+ // `type P *T` used as a field type: look through the name.
2456
+ const named =
2457
+ t.kind === "named" || t.kind === "TypeName" || t.kind === "Ident";
2458
+ const under = named
2459
+ ? (t.underlying ?? mod.checker?.types?.get(t.name)?.underlying)
2460
+ : null;
2461
+ return under ? _isPointerGoType(under, mod) : false;
2462
+ }
2463
+
2464
+ // Constant-expression zero value of a struct global. Nested struct values are
2465
+ // built in place: a null ref would trap on the first field access.
2466
+ function _zeroStructConst(structInfo, mod) {
2467
+ const out = [];
2468
+ for (const f of structInfo.fields) out.push(..._zeroFieldConst(f, mod));
2469
+ out.push({ op: "struct.new", typeIndex: structInfo.typeIndex });
2470
+ return out;
2471
+ }
2472
+
2473
+ function _zeroFieldConst(f, mod) {
2474
+ const w = f.wType;
2475
+ if (w === "i64") return [{ op: "i64.const", value: 0n }];
2476
+ if (w === "f32" || w === "f64") return [{ op: `${w}.const`, value: 0 }];
2477
+ if (w === "externref") return [{ op: "ref.null", heapType: "extern" }];
2478
+ if (w === "anyref") return [{ op: "ref.null", heapType: "any" }];
2479
+ if (typeof w !== "object" || w === null)
2480
+ return [{ op: "i32.const", value: 0 }];
2481
+ const ti = w.typeIndex;
2482
+ const slice = typeof ti === "number" ? mod.getSliceTypeByIndex(ti) : null;
2483
+ if (slice) return [{ op: "global.get", index: slice.emptyGlobalIndex }];
2484
+ if (typeof ti === "number" && !_isPointerGoType(f.goType, mod)) {
2485
+ for (const s of mod.structTypes.values())
2486
+ if (s.typeIndex === ti) return _zeroStructConst(s, mod);
2487
+ }
2488
+ return [{ op: "ref.null", heapType: ti ?? "any" }];
2489
+ }
2490
+
2491
+ function _collectPackageGlobals(progs, mod, checker) {
2492
+ const pending = []; // non-constant initializers, run by the start function
2493
+ for (const p of progs) {
2494
+ for (const d of p.decls ?? []) {
2495
+ if (d.kind !== "VarDecl") continue;
2496
+ for (const spec of d.decls ?? d.specs ?? [d]) {
2497
+ const names = spec.names ?? (spec.name ? [spec.name] : []);
2498
+ const values = spec.value ?? (spec.init ? [spec.init] : []);
2499
+ for (let i = 0; i < names.length; i++) {
2500
+ const name = names[i];
2501
+ const rawType = spec.type ?? values[i]?._type;
2502
+ const wType = toWasmType(rawType, checker, mod);
2503
+ const gIdx = mod.globals.length;
2504
+ let initInsts = [{ op: `${wType}.const`, value: 0 }];
2505
+ if (wType === "i64") initInsts = [{ op: "i64.const", value: 0n }];
2506
+ else if (wType === "f32" || wType === "f64")
2507
+ initInsts = [{ op: `${wType}.const`, value: 0.0 }];
2508
+ else if (
2509
+ wType === "externref" ||
2510
+ wType === "anyref" ||
2511
+ typeof wType === "object"
2512
+ ) {
2513
+ const sliceInfo =
2514
+ typeof wType === "object" && typeof wType.typeIndex === "number"
2515
+ ? mod.getSliceTypeByIndex(wType.typeIndex)
2516
+ : null;
2517
+ if (sliceInfo) {
2518
+ initInsts = [
2519
+ { op: "global.get", index: sliceInfo.emptyGlobalIndex },
2520
+ ];
2521
+ } else if (
2522
+ typeof wType === "object" &&
2523
+ typeof wType.typeIndex === "number" &&
2524
+ rawType?.kind !== "pointer"
2525
+ ) {
2526
+ const structInfo = Array.from(mod.structTypes.values()).find(
2527
+ (s) => s.typeIndex === wType.typeIndex,
2528
+ );
2529
+ if (structInfo) {
2530
+ initInsts = _zeroStructConst(structInfo, mod);
2531
+ } else {
2532
+ initInsts = [
2533
+ { op: "ref.null", heapType: wType.typeIndex ?? "any" },
2534
+ ];
2535
+ }
2536
+ } else {
2537
+ initInsts = [
2538
+ {
2539
+ op: "ref.null",
2540
+ heapType:
2541
+ typeof wType === "object"
2542
+ ? (wType.heapType ?? wType.typeIndex ?? "any")
2543
+ : wType.replace("ref", ""),
2544
+ },
2545
+ ];
2546
+ }
2547
+ }
2548
+
2549
+ let litNode = values?.[i];
2550
+ let sign = 1;
2551
+ if (
2552
+ litNode &&
2553
+ litNode.kind === "UnaryExpr" &&
2554
+ litNode.op === "-" &&
2555
+ litNode.operand?.kind === "BasicLit"
2556
+ ) {
2557
+ litNode = litNode.operand;
2558
+ sign = -1;
2559
+ }
2560
+
2561
+ let isConstInit = false;
2562
+ if (litNode && litNode.kind === "BasicLit") {
2563
+ if (litNode.litKind === "INT") {
2564
+ isConstInit = true;
2565
+ initInsts = [
2566
+ wType === "i64"
2567
+ ? {
2568
+ op: "i64.const",
2569
+ value: BigInt(sign) * BigInt(litNode.value),
2570
+ }
2571
+ : {
2572
+ op: "i32.const",
2573
+ value: (sign * Number(litNode.value)) | 0,
2574
+ },
2575
+ ];
2576
+ } else if (litNode.litKind === "FLOAT") {
2577
+ isConstInit = true;
2578
+ initInsts = [
2579
+ {
2580
+ op: `${wType}.const`,
2581
+ value: sign * Number(litNode.value),
2582
+ },
2583
+ ];
2584
+ } else if (litNode.litKind === "BOOL") {
2585
+ isConstInit = true;
2586
+ initInsts = [
2587
+ {
2588
+ op: "i32.const",
2589
+ value: litNode.value === "true" ? 1 : 0,
2590
+ },
2591
+ ];
2592
+ }
2593
+ }
2594
+ if (values[i] && !isConstInit && names.length === values.length) {
2595
+ pending.push({ name, expr: values[i] });
2596
+ }
2597
+ mod.globals.push({
2598
+ type: wType,
2599
+ mutable: true,
2600
+ init: initInsts,
2601
+ });
2602
+ mod.globalCache.set(name, {
2603
+ index: gIdx,
2604
+ type: wType,
2605
+ goType: rawType,
2606
+ });
2607
+ }
2608
+ }
2609
+ }
2610
+ }
2611
+ return pending;
2612
+ }
2613
+
2614
+ // Synthesises `__init_globals()` assigning every non-constant package-level
2615
+ // initializer in declaration order and invoking package `init()` functions;
2616
+ // registered as the module start function.
2617
+ function _makeGlobalInitFunc(pending, initNames = []) {
2618
+ const stmts = pending.map(({ name, expr }) => ({
2619
+ kind: "AssignStmt",
2620
+ lhs: [{ kind: "Ident", name, _type: expr._type }],
2621
+ op: "=",
2622
+ rhs: [expr],
2623
+ }));
2624
+ for (const initName of initNames) {
2625
+ stmts.push({
2626
+ kind: "ExprStmt",
2627
+ expr: {
2628
+ kind: "CallExpr",
2629
+ func: { kind: "Ident", name: initName },
2630
+ args: [],
2631
+ },
2632
+ });
2633
+ }
2634
+ stmts.push({ kind: "ReturnStmt", values: [] });
2635
+ return {
2636
+ kind: "FuncDecl",
2637
+ name: "__init_globals",
2638
+ params: [],
2639
+ returnType: null,
2640
+ body: { kind: "Block", stmts, list: stmts },
2641
+ _isGlobalInit: true,
2642
+ };
2643
+ }
2644
+
2645
+ function _scanInterfaceCallNodes(node, interfaceCalls, checker) {
2646
+ if (!node || typeof node !== "object") return;
2647
+ if (Array.isArray(node)) {
2648
+ for (const item of node)
2649
+ _scanInterfaceCallNodes(item, interfaceCalls, checker);
2650
+ return;
2651
+ }
2652
+ if (node.kind === "CallExpr" && node.func?.kind === "SelectorExpr") {
2653
+ const recvType = node.func.expr?._type;
2654
+ if (isNonEmptyInterface(recvType, checker)) {
2655
+ const ifaceName = getReceiverTypeName(recvType, node.func.expr) || "anon";
2656
+ const methodName = node.func.field;
2657
+ const key = `${ifaceName}.${methodName}`;
2658
+ if (!interfaceCalls.has(key)) {
2659
+ interfaceCalls.set(key, {
2660
+ ifaceName,
2661
+ ifaceType: recvType,
2662
+ methodName,
2663
+ callNode: node,
2664
+ });
2665
+ }
2666
+ }
2667
+ }
2668
+ for (const key of Object.keys(node)) {
2669
+ if (!key.startsWith("_"))
2670
+ _scanInterfaceCallNodes(node[key], interfaceCalls, checker);
2671
+ }
2672
+ }
2673
+
2674
+ function _scanInterfaceTypeDecls(progs, interfaceCalls, checker) {
2675
+ for (const p of progs) {
2676
+ for (const d of p.decls ?? []) {
2677
+ if (d.kind !== "TypeDecl") continue;
2678
+ const resolved = checker?.types?.get(d.name) ?? d.type;
2679
+ const underlying =
2680
+ resolved?.kind === "named"
2681
+ ? resolved.underlying
2682
+ : d.type?.kind === "InterfaceType"
2683
+ ? d.type
2684
+ : resolved;
2685
+ if (!isNonEmptyInterface(underlying, checker)) continue;
2686
+ const methods =
2687
+ underlying.methods instanceof Map
2688
+ ? Array.from(underlying.methods.entries())
2689
+ : Array.isArray(underlying.methods)
2690
+ ? underlying.methods.map((m) => [m.name ?? m, m])
2691
+ : [];
2692
+ for (const [mName, mSig] of methods) {
2693
+ const key = `${d.name}.${mName}`;
2694
+ if (!interfaceCalls.has(key)) {
2695
+ interfaceCalls.set(key, {
2696
+ ifaceName: d.name,
2697
+ ifaceType: resolved,
2698
+ methodName: mName,
2699
+ methodSig: mSig,
2700
+ });
2701
+ }
2702
+ }
2703
+ }
2704
+ }
2705
+ }
2706
+
2707
+ function _makeInterfaceDispatcher(info, checker) {
2708
+ let methodSig = info.methodSig;
2709
+ if (!methodSig) {
2710
+ const ifaceResolved =
2711
+ checker?.resolveType?.(
2712
+ info.ifaceType?.kind === "named"
2713
+ ? info.ifaceType.underlying
2714
+ : info.ifaceType,
2715
+ ) ?? info.ifaceType;
2716
+ if (ifaceResolved?.methods instanceof Map) {
2717
+ methodSig = ifaceResolved.methods.get(info.methodName);
2718
+ }
2719
+ }
2720
+
2721
+ const params = [{ name: "__recv", type: { kind: "basic", name: "any" } }];
2722
+ if (methodSig?.params) {
2723
+ for (let p = 0; p < methodSig.params.length; p++) {
2724
+ params.push({ name: `__arg${p}`, type: methodSig.params[p] });
2725
+ }
2726
+ } else if (info.callNode?.args) {
2727
+ for (let p = 0; p < info.callNode.args.length; p++) {
2728
+ params.push({
2729
+ name: `__arg${p}`,
2730
+ type: info.callNode.args[p]._type ?? {
2731
+ kind: "basic",
2732
+ name: "any",
2733
+ },
2734
+ });
2735
+ }
2736
+ }
2737
+
2738
+ let returnType = info.callNode?._type ?? null;
2739
+ if (methodSig?.returns) {
2740
+ returnType =
2741
+ methodSig.returns.length === 1
2742
+ ? methodSig.returns[0]
2743
+ : methodSig.returns.length > 1
2744
+ ? { kind: "tuple", types: methodSig.returns }
2745
+ : null;
2746
+ }
2747
+
2748
+ return {
2749
+ kind: "FuncDecl",
2750
+ name: `__dispatch_${info.ifaceName}_${info.methodName}`,
2751
+ params,
2752
+ returnType,
2753
+ body: { kind: "Block", stmts: [], list: [] },
2754
+ _isInterfaceDispatcher: true,
2755
+ _ifaceName: info.ifaceName,
2756
+ _ifaceType: info.ifaceType,
2757
+ _methodName: info.methodName,
2758
+ _rootFuncDecl: null,
2759
+ };
2760
+ }
2761
+
2762
+ function _collectInterfaceDispatchers(progs, funcDecls, mod, checker) {
2763
+ const interfaceCalls = new Map();
2764
+ for (const fn of funcDecls) {
2765
+ if (fn.body) _scanInterfaceCallNodes(fn.body, interfaceCalls, checker);
2766
+ }
2767
+ _scanInterfaceTypeDecls(progs, interfaceCalls, checker);
2768
+
2769
+ // `error.Error()` is reached implicitly by fmt verbs and errors.Is, so
2770
+ // the dispatcher must exist even when user code never calls it.
2771
+ if (mod.hasErrorType && !interfaceCalls.has("error.Error")) {
2772
+ interfaceCalls.set("error.Error", {
2773
+ ifaceName: "error",
2774
+ ifaceType: checker?.types?.get("error") ?? {
2775
+ kind: "interface",
2776
+ name: "error",
2777
+ },
2778
+ methodName: "Error",
2779
+ methodSig: {
2780
+ kind: "func",
2781
+ params: [],
2782
+ returns: [{ kind: "basic", name: "string" }],
2783
+ },
2784
+ });
2785
+ }
2786
+
2787
+ for (const info of interfaceCalls.values()) {
2788
+ funcDecls.push(_makeInterfaceDispatcher(info, checker));
2789
+ }
2790
+ }