gofront 1.3.12 → 1.5.1

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 (63) hide show
  1. package/README.md +90 -20
  2. package/package.json +2 -1
  3. package/src/{codegen → backend/js}/expressions.js +217 -60
  4. package/src/{codegen → backend/js}/index.js +47 -36
  5. package/src/{codegen → backend/js}/runtime.js +11 -0
  6. package/src/{codegen → backend/js}/statements.js +43 -48
  7. package/src/backend/js/stdlib/bits.js +32 -0
  8. package/src/{codegen → backend/js}/stdlib/index.js +3 -0
  9. package/src/{codegen → backend/js}/templ.js +31 -2
  10. package/src/backend/wasm/boundary.js +1345 -0
  11. package/src/backend/wasm/emit.js +5779 -0
  12. package/src/backend/wasm/encode.js +789 -0
  13. package/src/backend/wasm/glue.js +130 -0
  14. package/src/backend/wasm/index.js +1413 -0
  15. package/src/backend/wasm/types.js +420 -0
  16. package/src/backend/wasm/wat.js +255 -0
  17. package/src/cli-core.js +52 -7
  18. package/src/compiler.js +328 -12
  19. package/src/dev-server.js +56 -15
  20. package/src/index.js +17 -9
  21. package/src/lexer.js +18 -0
  22. package/src/lower/boxing.js +36 -0
  23. package/src/lower/captures.js +179 -0
  24. package/src/lower/embedding.js +30 -0
  25. package/src/lower/escape.js +169 -0
  26. package/src/lower/functions.js +30 -0
  27. package/src/lower/globals.js +324 -0
  28. package/src/lower/index.js +115 -0
  29. package/src/lower/ownership.js +142 -0
  30. package/src/lower/range.js +70 -0
  31. package/src/minifier.js +2 -0
  32. package/src/parser/index.js +8 -3
  33. package/src/resolver.js +4 -1
  34. package/src/templ-lexer.js +107 -3
  35. package/src/templ-parser.js +29 -0
  36. package/src/test-runner.js +74 -20
  37. package/src/typechecker/expressions.js +40 -1
  38. package/src/typechecker/index.js +301 -6
  39. package/src/typechecker/statements.js +7 -0
  40. package/src/typechecker/stdlib/core.js +17 -0
  41. package/src/typechecker/types.js +56 -2
  42. /package/src/{codegen → backend/js}/source-map.js +0 -0
  43. /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
  44. /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
  45. /package/src/{codegen → backend/js}/stdlib/errors.js +0 -0
  46. /package/src/{codegen → backend/js}/stdlib/fmt.js +0 -0
  47. /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
  48. /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
  49. /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
  50. /package/src/{codegen → backend/js}/stdlib/maps.js +0 -0
  51. /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
  52. /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
  53. /package/src/{codegen → backend/js}/stdlib/path.js +0 -0
  54. /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
  55. /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
  56. /package/src/{codegen → backend/js}/stdlib/slices.js +0 -0
  57. /package/src/{codegen → backend/js}/stdlib/sort.js +0 -0
  58. /package/src/{codegen → backend/js}/stdlib/strconv.js +0 -0
  59. /package/src/{codegen → backend/js}/stdlib/strings.js +0 -0
  60. /package/src/{codegen → backend/js}/stdlib/testing.js +0 -0
  61. /package/src/{codegen → backend/js}/stdlib/time.js +0 -0
  62. /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
  63. /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
@@ -0,0 +1,1413 @@
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
+ } from "./boundary.js";
14
+ import { FunctionEmitter } from "./emit.js";
15
+ import { encodeModule, isGoFrontWasm } from "./encode.js";
16
+ import { getFuncSignature, isTestingT, toWasmType } from "./types.js";
17
+ import { emitWat } from "./wat.js";
18
+
19
+ export { isGoFrontWasm };
20
+
21
+ export class ModuleEmitter {
22
+ constructor(checker, lowerResult = null, programs = [], options = {}) {
23
+ this.checker = checker;
24
+ this.lowerResult = lowerResult;
25
+ // Local package names whose code is linked into this module; selectors
26
+ // like `mathx.NewVec3` are de-qualified to `NewVec3` by the emitter.
27
+ this.bundledPackages = options.bundledPackages ?? new Set();
28
+ this.constCache = new Map(); // name -> BasicLit (package-level literal consts)
29
+ this.nonLiteralConsts = new Set(); // package-level consts the backend cannot evaluate yet
30
+
31
+ this.types = []; // type entries
32
+ this.typeCache = new Map(); // signature string -> index
33
+ this._openRec = null; // rec group receiving new types while structs are resolved
34
+
35
+ this.structTypes = new Map(); // name -> struct info
36
+ this.boxTypes = new Map(); // wType key -> box info
37
+ this.arrayTypes = new Map(); // wType key -> array info
38
+ this.sliceTypes = new Map(); // wType key -> slice info
39
+ this.sliceTypesByIndex = new Map(); // typeIndex -> slice info
40
+ this.closureTypes = new Map(); // key -> closure info
41
+ this.envTypes = new Map(); // key -> env info
42
+
43
+ this.elements = []; // declarative elements for ref.func
44
+ this.elementSet = new Set();
45
+
46
+ this.imports = []; // import entries
47
+ this.importCache = new Map(); // key -> func index
48
+
49
+ this.tags = []; // exception tags
50
+ this.globals = []; // global entries
51
+ this.globalCache = new Map(); // name -> index
52
+
53
+ this.funcs = []; // internal func definitions
54
+ this.funcMap = new Map(); // name -> global func index
55
+ this.funcParamTypes = new Map(); // name -> param types array
56
+ this.funcParamGoTypes = new Map(); // name -> param Go types array
57
+
58
+ this.exports = []; // export entries
59
+ this.stringTable = []; // string literals
60
+ this.stringCache = new Map();
61
+
62
+ if (programs && programs.length > 0) {
63
+ this.initTypes(programs);
64
+ }
65
+
66
+ this._importsLocked = false;
67
+ this._initRuntimeImports();
68
+ }
69
+
70
+ initTypes(programs) {
71
+ const progs = Array.isArray(programs) ? programs : [programs];
72
+ const rawStructs = [];
73
+ const seen = new Set();
74
+ const aliases = []; // qualified key (pkg.T) -> unqualified struct name
75
+
76
+ for (const p of progs) {
77
+ for (const d of p.decls ?? []) {
78
+ if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
79
+ if (!seen.has(d.name)) {
80
+ seen.add(d.name);
81
+ rawStructs.push({
82
+ name: d.name,
83
+ astFields: d.type.fields ?? [],
84
+ pkgName: p.pkg?.name ?? "main",
85
+ pkgTarget: p.target ?? "wasm",
86
+ });
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ if (this.checker?.types) {
93
+ for (const [key, t] of this.checker.types.entries()) {
94
+ if (t.kind === "named" && t.underlying?.kind === "struct") {
95
+ const name = t.name ?? key;
96
+ if (!seen.has(name)) {
97
+ seen.add(name);
98
+ rawStructs.push({ name, structType: t.underlying });
99
+ }
100
+ if (key !== name) aliases.push([key, name]);
101
+ }
102
+ }
103
+ }
104
+
105
+ if (rawStructs.length === 0) return;
106
+
107
+ // Field resolution may create array/slice/box types; they join this rec
108
+ // group so struct indices (base + i) stay valid and may be referenced
109
+ // from the auxiliary types in any order.
110
+ const base = this._getTotalTypeCount();
111
+ const structEntries = rawStructs.map(() => ({
112
+ form: "struct",
113
+ fields: [],
114
+ }));
115
+ this.types.push({ form: "rec", types: structEntries });
116
+ this._openRec = structEntries;
117
+
118
+ for (let i = 0; i < rawStructs.length; i++) {
119
+ const s = rawStructs[i];
120
+ this.structTypes.set(s.name, {
121
+ name: s.name,
122
+ typeIndex: base + i,
123
+ fields: [],
124
+ fieldIndexMap: new Map(),
125
+ embeds: [],
126
+ pkgName: s.pkgName ?? null,
127
+ pkgTarget: s.pkgTarget ?? "wasm",
128
+ });
129
+ }
130
+ // `mathx.Vec3` resolves to the same struct info as `Vec3` once linked.
131
+ for (const [key, name] of aliases) {
132
+ if (!this.structTypes.has(key))
133
+ this.structTypes.set(key, this.structTypes.get(name));
134
+ }
135
+
136
+ for (let i = 0; i < rawStructs.length; i++) {
137
+ const s = rawStructs[i];
138
+ const info = this.structTypes.get(s.name);
139
+ const resolvedStruct = this.checker?.types?.get(s.name)?.underlying;
140
+
141
+ const fields = [];
142
+ const fieldIndexMap = new Map();
143
+ const embeds = [];
144
+
145
+ if (s.astFields && s.astFields.length > 0) {
146
+ let fIdx = 0;
147
+ for (const f of s.astFields) {
148
+ const names = f.embedded
149
+ ? [f.type.name]
150
+ : f.names?.length
151
+ ? f.names
152
+ : f.name
153
+ ? [f.name]
154
+ : [];
155
+ for (const n of names) {
156
+ const wType = toWasmType(f.type, this.checker, this);
157
+ fields.push({
158
+ name: n,
159
+ goType: f.type,
160
+ wType,
161
+ embedded: Boolean(f.embedded),
162
+ });
163
+ fieldIndexMap.set(n, fIdx);
164
+ if (f.embedded) {
165
+ embeds.push({
166
+ name: n,
167
+ fieldIndex: fIdx,
168
+ });
169
+ }
170
+ fIdx++;
171
+ }
172
+ }
173
+ } else if (resolvedStruct?.fields) {
174
+ let fIdx = 0;
175
+ for (const [fName, fType] of resolvedStruct.fields.entries()) {
176
+ const wType = toWasmType(fType, this.checker, this);
177
+ fields.push({ name: fName, goType: fType, wType });
178
+ fieldIndexMap.set(fName, fIdx);
179
+ fIdx++;
180
+ }
181
+ if (resolvedStruct?._embeds) {
182
+ for (const embed of resolvedStruct._embeds) {
183
+ const embedName = embed.kind === "named" ? embed.name : null;
184
+ if (embedName && fieldIndexMap.has(embedName)) {
185
+ embeds.push({
186
+ name: embedName,
187
+ fieldIndex: fieldIndexMap.get(embedName),
188
+ });
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ info.fields = fields;
195
+ info.fieldIndexMap = fieldIndexMap;
196
+ info.embeds = embeds;
197
+
198
+ const typeEntry = structEntries[i];
199
+ typeEntry.fields = fields.map((f) => ({ type: f.wType, mutable: true }));
200
+ info.typeEntry = typeEntry;
201
+ }
202
+
203
+ this._openRec = null;
204
+ }
205
+
206
+ _pushType(typeEntry) {
207
+ (this._openRec ?? this.types).push(typeEntry);
208
+ }
209
+
210
+ getStructType(name) {
211
+ return this.structTypes.get(name) ?? null;
212
+ }
213
+
214
+ getBoxType(goType) {
215
+ const wType = toWasmType(goType, this.checker, this);
216
+ const key = typeof wType === "string" ? wType : JSON.stringify(wType);
217
+ if (this.boxTypes.has(key)) {
218
+ return this.boxTypes.get(key);
219
+ }
220
+ const typeIndex = this._getTotalTypeCount();
221
+ const typeEntry = {
222
+ form: "struct",
223
+ fields: [{ type: wType, mutable: true }],
224
+ };
225
+ this._pushType(typeEntry);
226
+ const boxInfo = { typeIndex, typeEntry, wType };
227
+ this.boxTypes.set(key, boxInfo);
228
+ return boxInfo;
229
+ }
230
+
231
+ getArrayType(elemGoType) {
232
+ const elemWType = toWasmType(elemGoType, this.checker, this);
233
+ const key =
234
+ typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
235
+ if (this.arrayTypes.has(key)) {
236
+ return this.arrayTypes.get(key);
237
+ }
238
+ const typeIndex = this._getTotalTypeCount();
239
+ const typeEntry = {
240
+ form: "array",
241
+ elemType: elemWType,
242
+ mutable: true,
243
+ };
244
+ this._pushType(typeEntry);
245
+ const arrInfo = { typeIndex, typeEntry, elemWType, elemGoType };
246
+ this.arrayTypes.set(key, arrInfo);
247
+ return arrInfo;
248
+ }
249
+
250
+ getSliceType(elemGoType) {
251
+ const elemWType = toWasmType(elemGoType, this.checker, this);
252
+ const key =
253
+ typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
254
+ if (this.sliceTypes.has(key)) {
255
+ return this.sliceTypes.get(key);
256
+ }
257
+ const arrInfo = this.getArrayType(elemGoType);
258
+ const typeIndex = this._getTotalTypeCount();
259
+ const typeEntry = {
260
+ form: "struct",
261
+ fields: [
262
+ {
263
+ type: {
264
+ kind: "ref",
265
+ nullable: true,
266
+ typeIndex: arrInfo.typeIndex,
267
+ },
268
+ mutable: false,
269
+ },
270
+ { type: "i32", mutable: false }, // offset
271
+ { type: "i32", mutable: false }, // len
272
+ { type: "i32", mutable: false }, // cap
273
+ ],
274
+ };
275
+ this._pushType(typeEntry);
276
+
277
+ const emptyGlobalIndex = this.globals.length;
278
+ this.globals.push({
279
+ type: {
280
+ kind: "ref",
281
+ nullable: true,
282
+ typeIndex,
283
+ },
284
+ mutable: false,
285
+ init: [
286
+ { op: "ref.null", heapType: arrInfo.typeIndex },
287
+ { op: "i32.const", value: 0 },
288
+ { op: "i32.const", value: 0 },
289
+ { op: "i32.const", value: 0 },
290
+ { op: "struct.new", typeIndex },
291
+ ],
292
+ });
293
+
294
+ const sliceInfo = {
295
+ typeIndex,
296
+ typeEntry,
297
+ arrInfo,
298
+ elemWType,
299
+ elemGoType,
300
+ emptyGlobalIndex,
301
+ };
302
+ this.sliceTypes.set(key, sliceInfo);
303
+ this.sliceTypesByIndex.set(typeIndex, sliceInfo);
304
+ return sliceInfo;
305
+ }
306
+
307
+ getSliceTypeByIndex(typeIndex) {
308
+ return this.sliceTypesByIndex?.get(typeIndex) ?? null;
309
+ }
310
+
311
+ getClosureType(goType) {
312
+ const sig = getFuncSignature(goType, this.checker);
313
+ const paramWTypes = sig.params
314
+ .map((p) => toWasmType(p, this.checker, this))
315
+ .filter(Boolean);
316
+ const returnWTypes = sig.returns
317
+ .map((r) => toWasmType(r, this.checker, this))
318
+ .filter(Boolean);
319
+
320
+ const key = `${paramWTypes.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${returnWTypes.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
321
+ if (this.closureTypes.has(key)) {
322
+ return this.closureTypes.get(key);
323
+ }
324
+
325
+ // 1. Function signature type index: (param anyref, ...paramWTypes) -> (...returnWTypes)
326
+ const funcTypeIndex = this.getTypeIndex(
327
+ ["anyref", ...paramWTypes],
328
+ returnWTypes,
329
+ );
330
+
331
+ // 2. Closure struct type index: struct { fn: (ref funcTypeIndex), env: anyref }
332
+ const typeIndex = this._getTotalTypeCount();
333
+ const typeEntry = {
334
+ form: "struct",
335
+ fields: [
336
+ {
337
+ type: {
338
+ kind: "ref",
339
+ nullable: false,
340
+ typeIndex: funcTypeIndex,
341
+ },
342
+ mutable: false,
343
+ },
344
+ {
345
+ type: "anyref",
346
+ mutable: true,
347
+ },
348
+ ],
349
+ };
350
+ this._pushType(typeEntry);
351
+
352
+ const closureInfo = {
353
+ typeIndex,
354
+ typeEntry,
355
+ funcTypeIndex,
356
+ paramWTypes,
357
+ returnWTypes,
358
+ sig,
359
+ };
360
+ this.closureTypes.set(key, closureInfo);
361
+ return closureInfo;
362
+ }
363
+
364
+ getEnvType(fieldTypes) {
365
+ const key = fieldTypes
366
+ .map((f) =>
367
+ typeof f.wType === "object" ? JSON.stringify(f.wType) : f.wType,
368
+ )
369
+ .join(",");
370
+ if (this.envTypes.has(key)) {
371
+ return this.envTypes.get(key);
372
+ }
373
+ const typeIndex = this._getTotalTypeCount();
374
+ const typeEntry = {
375
+ form: "struct",
376
+ fields: fieldTypes.map((f) => ({ type: f.wType, mutable: true })),
377
+ };
378
+ this._pushType(typeEntry);
379
+ const envInfo = { typeIndex, typeEntry, fieldTypes };
380
+ this.envTypes.set(key, envInfo);
381
+ return envInfo;
382
+ }
383
+
384
+ addRefFuncElement(funcIdx) {
385
+ if (!this.elementSet.has(funcIdx)) {
386
+ this.elementSet.add(funcIdx);
387
+ this.elements.push(funcIdx);
388
+ }
389
+ }
390
+
391
+ _getTotalTypeCount() {
392
+ let count = 0;
393
+ for (const entry of this.types) {
394
+ if (entry.form === "rec") count += entry.types.length;
395
+ else count += 1;
396
+ }
397
+ return count;
398
+ }
399
+
400
+ _initRuntimeImports() {
401
+ this._initPanicTag();
402
+ this.getPanicImportIndex(); // env.panic
403
+ this.internString("runtime error: index out of range");
404
+ this.internString("runtime error: slice bounds out of range");
405
+
406
+ // String built-ins
407
+ this.getStringImportIndex(); // env.str
408
+ this.getStringLenImportIndex(); // env.str_len
409
+ this.getStringConcatImportIndex(); // env.str_concat
410
+ this.getStringCmpImportIndex("=="); // env.str_eq
411
+ this.getStringCmpImportIndex("!="); // env.str_ne
412
+ this.getStringCmpImportIndex("<"); // env.str_lt
413
+ this.getStringCmpImportIndex("<="); // env.str_le
414
+ this.getStringCmpImportIndex(">"); // env.str_gt
415
+ this.getStringCmpImportIndex(">="); // env.str_ge
416
+ this.getStringGetImportIndex(); // env.str_get
417
+ this.getStringSliceImportIndex(); // env.str_slice
418
+ this.getStringFromCodePointImportIndex(); // env.str_from_code_point
419
+ this.getStringCodePointAtImportIndex(); // env.str_code_point_at
420
+ this.getIsStringImportIndex(); // env.is_string
421
+
422
+ // Logging built-ins
423
+ this.getPrintlnEmptyIndex(); // env.println_empty
424
+ const logTypes = ["i32", "i64", "f32", "f64", "externref", "anyref"];
425
+ for (const t of logTypes) {
426
+ this.getLogImportIndex(t, false, false); // env.print_*
427
+ this.getLogImportIndex(t, true, false); // env.println_*
428
+ }
429
+ this.getLogImportIndex("i32", false, true); // env.print_bool
430
+ this.getLogImportIndex("i32", true, true); // env.println_bool
431
+
432
+ // Math built-ins
433
+ const unaryMath = [
434
+ "sin",
435
+ "cos",
436
+ "tan",
437
+ "asin",
438
+ "acos",
439
+ "atan",
440
+ "exp",
441
+ "log",
442
+ "log2",
443
+ "log10",
444
+ "round",
445
+ ];
446
+ for (const m of unaryMath) {
447
+ this.getMathImportIndex(m, false);
448
+ }
449
+ const binaryMath = ["atan2", "pow"];
450
+ for (const m of binaryMath) {
451
+ this.getMathImportIndex(m, true);
452
+ }
453
+ }
454
+
455
+ _initPanicTag() {
456
+ // Type: (externref) -> ()
457
+ const typeIdx = this.getTypeIndex(["externref"], []);
458
+ // Tag 0
459
+ this.tags.push({ typeIndex: typeIdx });
460
+ // Export tag 0 as "panicTag"
461
+ this.exports.push({ name: "panicTag", kind: "tag", index: 0 });
462
+ }
463
+
464
+ getTypeIndex(params, results) {
465
+ const key = `${params.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${results.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
466
+ if (this.typeCache.has(key)) {
467
+ return this.typeCache.get(key);
468
+ }
469
+ const idx = this._getTotalTypeCount();
470
+ this._pushType({ form: "func", params, results });
471
+ this.typeCache.set(key, idx);
472
+ return idx;
473
+ }
474
+
475
+ internString(str) {
476
+ if (this.stringCache.has(str)) {
477
+ return this.stringCache.get(str);
478
+ }
479
+ const idx = this.stringTable.length;
480
+ this.stringTable.push(str);
481
+ this.stringCache.set(str, idx);
482
+ return idx;
483
+ }
484
+
485
+ getOrAddFuncImport(module, name, params, results) {
486
+ const key = `${module}.${name}`;
487
+ if (this.importCache.has(key)) {
488
+ return this.importCache.get(key);
489
+ }
490
+ if (this._importsLocked) {
491
+ throw new Error(`Unexpected import added after lock: ${key}`);
492
+ }
493
+ const typeIndex = this.getTypeIndex(params, results);
494
+ const globalFuncIdx = this.imports.filter((i) => i.kind === "func").length;
495
+ this.imports.push({
496
+ module,
497
+ name,
498
+ kind: "func",
499
+ typeIndex,
500
+ });
501
+ this.importCache.set(key, globalFuncIdx);
502
+ return globalFuncIdx;
503
+ }
504
+
505
+ getStringImportIndex() {
506
+ return this.getOrAddFuncImport("env", "str", ["i32"], ["externref"]);
507
+ }
508
+
509
+ // Panics are raised by a JS import that throws a plain `Error`, so wasm
510
+ // frames unwind without a wasm `throw` and the JS caller needs no
511
+ // try/catch guard around exports. (V8 cannot inline JS→wasm calls made
512
+ // inside a try block, and a non-inlined call boxes every float argument.)
513
+ getPanicImportIndex() {
514
+ return this.getOrAddFuncImport("env", "panic", ["externref"], []);
515
+ }
516
+
517
+ getBoundsPanicFuncIndex() {
518
+ return this.resolveFuncIndex("__bounds_panic");
519
+ }
520
+
521
+ getSliceBoundsPanicFuncIndex() {
522
+ return this.resolveFuncIndex("__slice_bounds_panic");
523
+ }
524
+
525
+ getPrintlnEmptyIndex() {
526
+ return this.getOrAddFuncImport("env", "println_empty", [], []);
527
+ }
528
+
529
+ // `*testing.T` method calls: args are pushed one by one to a JS-side
530
+ // buffer, then `testing_call(t, methodNameStrIdx)` invokes the method.
531
+ getTestingArgImportIndex(wType, isBool = false) {
532
+ if (isBool)
533
+ return this.getOrAddFuncImport("env", "testing_arg_bool", ["i32"], []);
534
+ const suffix =
535
+ wType === "externref" ? "str" : wType === "anyref" ? "any" : wType;
536
+ if (!["i32", "i64", "f32", "f64", "str", "any"].includes(suffix)) {
537
+ throw new Error(
538
+ "only primitive, string and any arguments are supported for testing.T methods in wasm packages",
539
+ );
540
+ }
541
+ return this.getOrAddFuncImport(
542
+ "env",
543
+ `testing_arg_${suffix}`,
544
+ [suffix === "str" ? "externref" : suffix === "any" ? "anyref" : wType],
545
+ [],
546
+ );
547
+ }
548
+
549
+ getTestingCallImportIndex() {
550
+ return this.getOrAddFuncImport(
551
+ "env",
552
+ "testing_call",
553
+ ["externref", "i32"],
554
+ [],
555
+ );
556
+ }
557
+
558
+ getTestingNameImportIndex() {
559
+ return this.getOrAddFuncImport(
560
+ "env",
561
+ "testing_name",
562
+ ["externref"],
563
+ ["externref"],
564
+ );
565
+ }
566
+
567
+ getTestingFlagImportIndex() {
568
+ return this.getOrAddFuncImport(
569
+ "env",
570
+ "testing_flag",
571
+ ["externref", "i32"],
572
+ ["i32"],
573
+ );
574
+ }
575
+
576
+ getLogImportIndex(wType, isLast = false, isBool = false) {
577
+ const prefix = isLast ? "println" : "print";
578
+ if (isBool) {
579
+ return this.getOrAddFuncImport("env", `${prefix}_bool`, ["i32"], []);
580
+ }
581
+ switch (wType) {
582
+ case "i32":
583
+ return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
584
+ case "i64":
585
+ return this.getOrAddFuncImport("env", `${prefix}_i64`, ["i64"], []);
586
+ case "f32":
587
+ return this.getOrAddFuncImport("env", `${prefix}_f32`, ["f32"], []);
588
+ case "f64":
589
+ return this.getOrAddFuncImport("env", `${prefix}_f64`, ["f64"], []);
590
+ case "externref":
591
+ return this.getOrAddFuncImport(
592
+ "env",
593
+ `${prefix}_str`,
594
+ ["externref"],
595
+ [],
596
+ );
597
+ case "anyref":
598
+ return this.getOrAddFuncImport("env", `${prefix}_any`, ["anyref"], []);
599
+ default:
600
+ return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
601
+ }
602
+ }
603
+
604
+ getStringLenImportIndex() {
605
+ return this.getOrAddFuncImport("env", "str_len", ["externref"], ["i32"]);
606
+ }
607
+
608
+ getStringConcatImportIndex() {
609
+ return this.getOrAddFuncImport(
610
+ "env",
611
+ "str_concat",
612
+ ["externref", "externref"],
613
+ ["externref"],
614
+ );
615
+ }
616
+
617
+ getStringCmpImportIndex(op) {
618
+ const opMap = {
619
+ "==": "str_eq",
620
+ "!=": "str_ne",
621
+ "<": "str_lt",
622
+ "<=": "str_le",
623
+ ">": "str_gt",
624
+ ">=": "str_ge",
625
+ };
626
+ const fn = opMap[op] ?? "str_eq";
627
+ return this.getOrAddFuncImport(
628
+ "env",
629
+ fn,
630
+ ["externref", "externref"],
631
+ ["i32"],
632
+ );
633
+ }
634
+
635
+ getStringGetImportIndex() {
636
+ return this.getOrAddFuncImport(
637
+ "env",
638
+ "str_get",
639
+ ["externref", "i32"],
640
+ ["i32"],
641
+ );
642
+ }
643
+
644
+ getStringSliceImportIndex() {
645
+ return this.getOrAddFuncImport(
646
+ "env",
647
+ "str_slice",
648
+ ["externref", "i32", "i32"],
649
+ ["externref"],
650
+ );
651
+ }
652
+
653
+ getStringFromCodePointImportIndex() {
654
+ return this.getOrAddFuncImport(
655
+ "env",
656
+ "str_from_code_point",
657
+ ["i32"],
658
+ ["externref"],
659
+ );
660
+ }
661
+
662
+ getStringCodePointAtImportIndex() {
663
+ return this.getOrAddFuncImport(
664
+ "env",
665
+ "str_code_point_at",
666
+ ["externref", "i32"],
667
+ ["i32"],
668
+ );
669
+ }
670
+
671
+ getIsStringImportIndex() {
672
+ return this.getOrAddFuncImport("env", "is_string", ["anyref"], ["i32"]);
673
+ }
674
+
675
+ getMathImportIndex(jsName, isBinary = false) {
676
+ const params = isBinary ? ["f64", "f64"] : ["f64"];
677
+ return this.getOrAddFuncImport("Math", jsName, params, ["f64"]);
678
+ }
679
+
680
+ resolveFuncIndex(name) {
681
+ return this.funcMap.get(name) ?? null;
682
+ }
683
+
684
+ getFuncParamTypes(name) {
685
+ return this.funcParamTypes.get(name) ?? [];
686
+ }
687
+
688
+ getFuncParamGoTypes(name) {
689
+ return this.funcParamGoTypes.get(name) ?? [];
690
+ }
691
+
692
+ resolveGlobal(name) {
693
+ return this.globalCache.get(name) ?? null;
694
+ }
695
+
696
+ resolveConst(name) {
697
+ return this.constCache.get(name) ?? null;
698
+ }
699
+
700
+ isNonLiteralConst(name) {
701
+ return this.nonLiteralConsts.has(name);
702
+ }
703
+ }
704
+
705
+ export function peepholeOptimize(instructions) {
706
+ let changed = true;
707
+ let current = instructions;
708
+ while (changed) {
709
+ changed = false;
710
+ const next = [];
711
+ for (let i = 0; i < current.length; i++) {
712
+ const c = current[i];
713
+ const n = current[i + 1];
714
+
715
+ // Pattern 1: local.get X; local.set X -> nothing
716
+ if (
717
+ c?.op === "local.get" &&
718
+ n?.op === "local.set" &&
719
+ c.index === n.index
720
+ ) {
721
+ changed = true;
722
+ i++; // skip both
723
+ continue;
724
+ }
725
+
726
+ // Pattern 2: local.set X; local.get X -> local.tee X
727
+ if (
728
+ c?.op === "local.set" &&
729
+ n?.op === "local.get" &&
730
+ c.index === n.index
731
+ ) {
732
+ changed = true;
733
+ next.push({ op: "local.tee", index: c.index });
734
+ i++; // skip n (local.get)
735
+ continue;
736
+ }
737
+
738
+ // Pattern 3: local.tee X; local.set X -> local.set X
739
+ if (
740
+ c?.op === "local.tee" &&
741
+ n?.op === "local.set" &&
742
+ c.index === n.index
743
+ ) {
744
+ changed = true;
745
+ next.push({ op: "local.set", index: c.index });
746
+ i++;
747
+ continue;
748
+ }
749
+
750
+ // Pattern 4: local.tee X; drop -> local.set X
751
+ if (c?.op === "local.tee" && (n === "drop" || n?.op === "drop")) {
752
+ changed = true;
753
+ next.push({ op: "local.set", index: c.index });
754
+ i++;
755
+ continue;
756
+ }
757
+
758
+ next.push(c);
759
+ }
760
+ current = next;
761
+ }
762
+ return current;
763
+ }
764
+
765
+ export function compileWasmModule(
766
+ programs,
767
+ checker,
768
+ lowerResult = null,
769
+ options = {},
770
+ ) {
771
+ const progs = Array.isArray(programs) ? programs : [programs];
772
+ const resolvedLowerResult = lowerResult ?? lower(progs, checker);
773
+ const mod = new ModuleEmitter(checker, resolvedLowerResult, progs, {
774
+ bundledPackages: options.bundledPackages,
775
+ });
776
+
777
+ // 1. Collect all functions, methods, and package-level globals
778
+ const funcDecls = [];
779
+ let initCount = 0;
780
+ const initNames = [];
781
+ for (const p of progs) {
782
+ const pkgName = p.pkg?.name ?? "main";
783
+ const pkgTarget = p.target ?? "wasm";
784
+ for (const d of p.decls ?? []) {
785
+ if (d.kind === "FuncDecl") {
786
+ d._pkgName = pkgName;
787
+ d._pkgTarget = pkgTarget;
788
+ if (d.name === "init" || d.name.startsWith("init$")) {
789
+ const renamed = initCount === 0 ? "init" : `init$${initCount}`;
790
+ d.name = renamed;
791
+ initNames.push(renamed);
792
+ initCount++;
793
+ }
794
+ funcDecls.push(d);
795
+ } else if (d.kind === "MethodDecl") {
796
+ const recvTypeName =
797
+ d.recvType?.name ??
798
+ (d.recvType?.kind === "TypeName" ? d.recvType.name : "Recv");
799
+ const recvGoType = d.recvPointer
800
+ ? { kind: "pointer", base: { kind: "named", name: recvTypeName } }
801
+ : { kind: "named", name: recvTypeName };
802
+ const normFn = {
803
+ kind: "FuncDecl",
804
+ name: `${recvTypeName}.${d.name}`,
805
+ params: [{ name: d.recvName, type: recvGoType }, ...(d.params ?? [])],
806
+ returnType: d.returnType,
807
+ body: d.body,
808
+ _isMethod: true,
809
+ _recvTypeName: recvTypeName,
810
+ _methodName: d.name,
811
+ _exportName: `${recvTypeName}_${d.name}`,
812
+ _sourceDecl: d,
813
+ _pkgName: pkgName,
814
+ _pkgTarget: pkgTarget,
815
+ };
816
+ funcDecls.push(normFn);
817
+ }
818
+ }
819
+ }
820
+
821
+ // 1b. Scan and lift all closures (FuncLits)
822
+ let closureCounter = 0;
823
+ const closures = [];
824
+
825
+ function scanClosures(node, rootFn) {
826
+ if (!node || typeof node !== "object") return;
827
+ if (Array.isArray(node)) {
828
+ for (const item of node) scanClosures(item, rootFn);
829
+ return;
830
+ }
831
+ if (node.kind === "FuncLit") {
832
+ closureCounter++;
833
+ const closureName = `_closure$${closureCounter}`;
834
+ node._liftedName = closureName;
835
+ node._rootFuncDecl = rootFn;
836
+
837
+ const liftedFn = {
838
+ kind: "FuncDecl",
839
+ name: closureName,
840
+ params: [
841
+ { name: "__env", type: { kind: "basic", name: "any" } },
842
+ ...(node.params ?? []),
843
+ ],
844
+ returnType: node.returnType,
845
+ body: node.body,
846
+ _isClosure: true,
847
+ _funcLit: node,
848
+ _rootFuncDecl: rootFn,
849
+ };
850
+ node._liftedFn = liftedFn;
851
+ closures.push(liftedFn);
852
+
853
+ scanClosures(node.body, rootFn);
854
+ return;
855
+ }
856
+ for (const key of Object.keys(node)) {
857
+ if (key.startsWith("_")) continue;
858
+ scanClosures(node[key], rootFn);
859
+ }
860
+ }
861
+
862
+ for (const fn of funcDecls) {
863
+ fn._rootFuncDecl = fn;
864
+ if (fn.body) {
865
+ scanClosures(fn.body, fn);
866
+ }
867
+ }
868
+ funcDecls.push(...closures);
869
+
870
+ // 1c. Add trampolines for named functions (for when they are passed as function values)
871
+ const topLevelFuncs = new Map();
872
+ for (const fn of funcDecls) {
873
+ if (
874
+ !fn._isClosure &&
875
+ !fn._isMethod &&
876
+ fn.name !== "main" &&
877
+ !fn.name.startsWith("_")
878
+ ) {
879
+ topLevelFuncs.set(fn.name, fn);
880
+ }
881
+ }
882
+ mod.topLevelFuncMap = topLevelFuncs;
883
+
884
+ const trampolines = [];
885
+ for (const [name, targetFn] of topLevelFuncs.entries()) {
886
+ const trampName = `_tramp$${name}`;
887
+ const callExpr = {
888
+ kind: "CallExpr",
889
+ func: { kind: "Ident", name: targetFn.name },
890
+ args: (targetFn.params ?? []).map((p) => ({
891
+ kind: "Ident",
892
+ name: p.name,
893
+ _type: p.type,
894
+ })),
895
+ _type: targetFn.returnType ?? { kind: "basic", name: "void" },
896
+ };
897
+ const bodyStmts = targetFn.returnType
898
+ ? [
899
+ {
900
+ kind: "ReturnStmt",
901
+ values: [callExpr],
902
+ },
903
+ ]
904
+ : [
905
+ { kind: "ExprStmt", expr: callExpr },
906
+ { kind: "ReturnStmt", values: [] },
907
+ ];
908
+
909
+ const trampFn = {
910
+ kind: "FuncDecl",
911
+ name: trampName,
912
+ params: [
913
+ { name: "__env", type: { kind: "basic", name: "any" } },
914
+ ...(targetFn.params ?? []),
915
+ ],
916
+ returnType: targetFn.returnType,
917
+ body: {
918
+ kind: "Block",
919
+ stmts: bodyStmts,
920
+ list: bodyStmts,
921
+ },
922
+ _isClosure: true,
923
+ _isTrampoline: true,
924
+ _targetFuncName: name,
925
+ _rootFuncDecl: targetFn,
926
+ };
927
+ trampolines.push(trampFn);
928
+ }
929
+ funcDecls.push(...trampolines);
930
+
931
+ const pendingInits = _collectPackageGlobals(progs, mod, checker);
932
+ _collectPackageConsts(progs, mod);
933
+ const globalInitFn =
934
+ pendingInits.length > 0 || initNames.length > 0
935
+ ? _makeGlobalInitFunc(pendingInits, initNames)
936
+ : null;
937
+ if (globalInitFn) funcDecls.push(globalInitFn);
938
+
939
+ const boundsPanicFn = {
940
+ kind: "FuncDecl",
941
+ name: "__bounds_panic",
942
+ params: [],
943
+ returnType: null,
944
+ _isRuntimePanic: true,
945
+ _panicMsg: "runtime error: index out of range",
946
+ };
947
+ funcDecls.push(boundsPanicFn);
948
+
949
+ const sliceBoundsPanicFn = {
950
+ kind: "FuncDecl",
951
+ name: "__slice_bounds_panic",
952
+ params: [],
953
+ returnType: null,
954
+ _isRuntimePanic: true,
955
+ _panicMsg: "runtime error: slice bounds out of range",
956
+ };
957
+ funcDecls.push(sliceBoundsPanicFn);
958
+
959
+ // 1d. Boundary metadata (exported surface of wasm packages)
960
+ const boundaryMeta = options.boundary
961
+ ? collectBoundaryMeta(progs, funcDecls, mod)
962
+ : null;
963
+
964
+ // 2. Pre-scan for needed imports so import func indices are fixed
965
+ for (const fn of funcDecls) {
966
+ _scanImportsInBody(fn.body, mod);
967
+ }
968
+ if (boundaryMeta) scanBoundaryImports(mod, boundaryMeta);
969
+
970
+ const importFuncCount = mod.imports.filter((i) => i.kind === "func").length;
971
+ mod._importsLocked = true;
972
+
973
+ // 3. Register all internal functions and their type signatures
974
+ for (let i = 0; i < funcDecls.length; i++) {
975
+ const fn = funcDecls[i];
976
+ const globalIdx = importFuncCount + i;
977
+ mod.funcMap.set(fn.name, globalIdx);
978
+ if (fn._isClosure) {
979
+ if (fn._funcLit) {
980
+ fn._funcLit._globalFuncIndex = globalIdx;
981
+ }
982
+ mod.addRefFuncElement(globalIdx);
983
+ }
984
+
985
+ const paramTypes = (fn.params ?? []).map((p) =>
986
+ toWasmType(p.type, checker, mod),
987
+ );
988
+ mod.funcParamTypes.set(fn.name, paramTypes);
989
+ mod.funcParamGoTypes.set(
990
+ fn.name,
991
+ (fn.params ?? []).map((p) => p.type),
992
+ );
993
+ const returnTypes = [];
994
+ if (fn.returnType) {
995
+ if (
996
+ fn.returnType.kind === "TupleType" ||
997
+ fn.returnType.kind === "tuple"
998
+ ) {
999
+ for (const t of fn.returnType.types) {
1000
+ const wt = toWasmType(t, checker, mod);
1001
+ if (wt) returnTypes.push(wt);
1002
+ }
1003
+ } else {
1004
+ const wt = toWasmType(fn.returnType, checker, mod);
1005
+ if (wt) returnTypes.push(wt);
1006
+ }
1007
+ }
1008
+
1009
+ const typeIndex = mod.getTypeIndex(paramTypes, returnTypes);
1010
+ fn._typeIndex = typeIndex;
1011
+ fn._globalFuncIndex = globalIdx;
1012
+
1013
+ // Export if public (capitalized) or main
1014
+ if (
1015
+ !fn._isGlobalInit &&
1016
+ (options.exportAll ||
1017
+ fn.name === "main" ||
1018
+ (fn.name[0] >= "A" && fn.name[0] <= "Z") ||
1019
+ (fn._isMethod && fn._methodName[0] >= "A" && fn._methodName[0] <= "Z"))
1020
+ ) {
1021
+ mod.exports.push({ name: fn.name, kind: "func", index: globalIdx });
1022
+ if (fn._isMethod && fn._exportName) {
1023
+ mod.exports.push({
1024
+ name: fn._exportName,
1025
+ kind: "func",
1026
+ index: globalIdx,
1027
+ });
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ // 4. Emit function bodies
1033
+ for (const fn of funcDecls) {
1034
+ const emitter = new FunctionEmitter(mod, fn, fn._globalFuncIndex);
1035
+ if (fn._isRuntimePanic) {
1036
+ const strIdx = mod.internString(fn._panicMsg);
1037
+ const funcIdx = mod.getStringImportIndex();
1038
+ emitter.pushInstruction({ op: "i32.const", value: strIdx });
1039
+ emitter.pushInstruction({ op: "call", funcIndex: funcIdx });
1040
+ emitter.emitPanicThrow();
1041
+ } else if (fn.body) {
1042
+ emitter.emitFunctionBody(fn.body);
1043
+ }
1044
+ // If last instruction is not return or unreachable, auto-emit return or unreachable
1045
+ let body = emitter.body;
1046
+ if (
1047
+ body.length === 0 ||
1048
+ (body[body.length - 1].op !== "return" &&
1049
+ body[body.length - 1].op !== "unreachable")
1050
+ ) {
1051
+ if (emitter.returnTypes.length === 0) {
1052
+ body.push({ op: "return" });
1053
+ } else {
1054
+ body.push({ op: "unreachable" });
1055
+ }
1056
+ }
1057
+
1058
+ body = peepholeOptimize(body);
1059
+
1060
+ mod.funcs.push({
1061
+ typeIndex: fn._typeIndex,
1062
+ locals: emitter.localTypes,
1063
+ body,
1064
+ });
1065
+ }
1066
+
1067
+ // 5. Boundary marshalling helpers + JS facade
1068
+ let facade = null;
1069
+ if (boundaryMeta) {
1070
+ addBoundaryHelpers(mod, boundaryMeta);
1071
+ facade = generateFacade(boundaryMeta, {
1072
+ stringTable: mod.stringTable,
1073
+ callMain: Boolean(options.callMain) && mod.funcMap.has("main"),
1074
+ });
1075
+ }
1076
+
1077
+ const moduleIR = {
1078
+ types: mod.types,
1079
+ imports: mod.imports,
1080
+ tags: mod.tags,
1081
+ globals: mod.globals,
1082
+ elements: mod.elements,
1083
+ funcs: mod.funcs,
1084
+ exports: mod.exports,
1085
+ start: globalInitFn ? globalInitFn._globalFuncIndex : null,
1086
+ };
1087
+
1088
+ const wasmBytes = encodeModule(moduleIR);
1089
+ const watText = options.emitWat ? emitWat(moduleIR) : null;
1090
+
1091
+ return {
1092
+ wasm: wasmBytes,
1093
+ wat: watText,
1094
+ stringTable: mod.stringTable,
1095
+ moduleIR,
1096
+ facade,
1097
+ };
1098
+ }
1099
+
1100
+ // Package-level `const` declarations with literal values (iota is already
1101
+ // substituted by the parser). Non-literal constant expressions are not
1102
+ // supported by the wasm backend yet.
1103
+ function _collectPackageConsts(progs, mod) {
1104
+ for (const p of progs) {
1105
+ for (const d of p.decls ?? []) {
1106
+ if (d.kind !== "ConstDecl") continue;
1107
+ for (const spec of d.decls ?? []) {
1108
+ for (let i = 0; i < spec.names.length; i++) {
1109
+ let lit = spec.value?.[i];
1110
+ if (
1111
+ lit?.kind === "UnaryExpr" &&
1112
+ lit.op === "-" &&
1113
+ lit.operand?.kind === "BasicLit"
1114
+ ) {
1115
+ lit = { ...lit.operand, value: `-${lit.operand.value}` };
1116
+ }
1117
+ if (lit?.kind === "BasicLit") mod.constCache.set(spec.names[i], lit);
1118
+ else if (lit) mod.nonLiteralConsts.add(spec.names[i]);
1119
+ }
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+
1125
+ function _scanImportsInBody(node, mod) {
1126
+ if (!node || typeof node !== "object") return;
1127
+
1128
+ if (node.kind === "CallExpr") {
1129
+ const { func, args } = node;
1130
+ if (
1131
+ func.kind === "Ident" &&
1132
+ (func.name === "print" || func.name === "println")
1133
+ ) {
1134
+ const isPrintln = func.name === "println";
1135
+ if (args.length === 0 && isPrintln) {
1136
+ mod.getPrintlnEmptyIndex();
1137
+ }
1138
+ for (let i = 0; i < args.length; i++) {
1139
+ const arg = args[i];
1140
+ const isLast = i === args.length - 1 && isPrintln;
1141
+ const isBool =
1142
+ arg._type?.name === "bool" ||
1143
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL");
1144
+ const wType = toWasmType(arg._type, mod.checker);
1145
+ mod.getLogImportIndex(wType, isLast, isBool);
1146
+ }
1147
+ } else if (
1148
+ func.kind === "SelectorExpr" &&
1149
+ func.expr?.name === "math" &&
1150
+ func.field
1151
+ ) {
1152
+ const jsMathMap = {
1153
+ Sin: "sin",
1154
+ Cos: "cos",
1155
+ Tan: "tan",
1156
+ Asin: "asin",
1157
+ Acos: "acos",
1158
+ Atan: "atan",
1159
+ Atan2: "atan2",
1160
+ Pow: "pow",
1161
+ Exp: "exp",
1162
+ Log: "log",
1163
+ Log2: "log2",
1164
+ Log10: "log10",
1165
+ Round: "round",
1166
+ };
1167
+ if (jsMathMap[func.field]) {
1168
+ const isBinary = func.field === "Atan2" || func.field === "Pow";
1169
+ mod.getMathImportIndex(jsMathMap[func.field], isBinary);
1170
+ }
1171
+ } else if (
1172
+ func.kind === "SelectorExpr" &&
1173
+ isTestingT(func.expr?._type) &&
1174
+ func.field
1175
+ ) {
1176
+ mod.internString(func.field);
1177
+ if (func.field === "Name") mod.getTestingNameImportIndex();
1178
+ else if (func.field === "Failed" || func.field === "Skipped")
1179
+ mod.getTestingFlagImportIndex();
1180
+ else if (func.field !== "Run") {
1181
+ mod.getTestingCallImportIndex();
1182
+ for (const arg of args) {
1183
+ mod.getTestingArgImportIndex(
1184
+ toWasmType(arg._type, mod.checker),
1185
+ arg._type?.name === "bool" ||
1186
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL"),
1187
+ );
1188
+ }
1189
+ }
1190
+ }
1191
+ }
1192
+
1193
+ if (node.kind === "BasicLit" && node.litKind === "STRING") {
1194
+ mod.getStringImportIndex();
1195
+ const strVal = String(node.value ?? "");
1196
+ mod.internString(strVal);
1197
+ }
1198
+
1199
+ // Recurse child properties
1200
+ for (const key of Object.keys(node)) {
1201
+ if (key.startsWith("_")) continue;
1202
+ const child = node[key];
1203
+ if (Array.isArray(child)) {
1204
+ for (const item of child) _scanImportsInBody(item, mod);
1205
+ } else if (child && typeof child === "object") {
1206
+ _scanImportsInBody(child, mod);
1207
+ }
1208
+ }
1209
+ }
1210
+
1211
+ export function compileWasm(source, options = {}) {
1212
+ const filename = options.filename ?? "main.go";
1213
+ const tokens = new Lexer(source, filename).tokenize();
1214
+ const ast = new Parser(tokens, filename, source).parse();
1215
+
1216
+ const checker = new TypeChecker();
1217
+ checker.target = "wasm";
1218
+ checker.pkgName = ast.pkg?.name ?? "main";
1219
+
1220
+ const errors = checker.check(ast);
1221
+ if (errors.length > 0) {
1222
+ return { wasm: null, errors };
1223
+ }
1224
+
1225
+ const lowerRes = lower([ast], checker);
1226
+ const res = compileWasmModule([ast], checker, lowerRes, {
1227
+ ...options,
1228
+ exportAll: options.exportAll ?? true,
1229
+ });
1230
+
1231
+ return { ...res, errors: [] };
1232
+ }
1233
+
1234
+ function _collectPackageGlobals(progs, mod, checker) {
1235
+ const pending = []; // non-constant initializers, run by the start function
1236
+ for (const p of progs) {
1237
+ for (const d of p.decls ?? []) {
1238
+ if (d.kind !== "VarDecl") continue;
1239
+ for (const spec of d.decls ?? d.specs ?? [d]) {
1240
+ const names = spec.names ?? (spec.name ? [spec.name] : []);
1241
+ const values = spec.value ?? (spec.init ? [spec.init] : []);
1242
+ for (let i = 0; i < names.length; i++) {
1243
+ const name = names[i];
1244
+ const rawType = spec.type ?? values[i]?._type;
1245
+ const wType = toWasmType(rawType, checker, mod);
1246
+ const gIdx = mod.globals.length;
1247
+ let initInsts = [{ op: `${wType}.const`, value: 0 }];
1248
+ if (wType === "i64") initInsts = [{ op: "i64.const", value: 0n }];
1249
+ else if (wType === "f32" || wType === "f64")
1250
+ initInsts = [{ op: `${wType}.const`, value: 0.0 }];
1251
+ else if (
1252
+ wType === "externref" ||
1253
+ wType === "anyref" ||
1254
+ typeof wType === "object"
1255
+ ) {
1256
+ const sliceInfo =
1257
+ typeof wType === "object" && typeof wType.typeIndex === "number"
1258
+ ? mod.getSliceTypeByIndex(wType.typeIndex)
1259
+ : null;
1260
+ if (sliceInfo) {
1261
+ initInsts = [
1262
+ { op: "global.get", index: sliceInfo.emptyGlobalIndex },
1263
+ ];
1264
+ } else if (
1265
+ typeof wType === "object" &&
1266
+ typeof wType.typeIndex === "number" &&
1267
+ rawType?.kind !== "pointer"
1268
+ ) {
1269
+ const structInfo = Array.from(mod.structTypes.values()).find(
1270
+ (s) => s.typeIndex === wType.typeIndex,
1271
+ );
1272
+ if (structInfo) {
1273
+ initInsts = [];
1274
+ for (const f of structInfo.fields) {
1275
+ if (f.wType === "i64")
1276
+ initInsts.push({ op: "i64.const", value: 0n });
1277
+ else if (f.wType === "f32" || f.wType === "f64")
1278
+ initInsts.push({ op: `${f.wType}.const`, value: 0.0 });
1279
+ else if (typeof f.wType === "object") {
1280
+ const fieldSlice =
1281
+ typeof f.wType.typeIndex === "number"
1282
+ ? mod.getSliceTypeByIndex(f.wType.typeIndex)
1283
+ : null;
1284
+ if (fieldSlice) {
1285
+ initInsts.push({
1286
+ op: "global.get",
1287
+ index: fieldSlice.emptyGlobalIndex,
1288
+ });
1289
+ } else {
1290
+ initInsts.push({
1291
+ op: "ref.null",
1292
+ heapType: f.wType.typeIndex ?? "any",
1293
+ });
1294
+ }
1295
+ } else initInsts.push({ op: "i32.const", value: 0 });
1296
+ }
1297
+ initInsts.push({
1298
+ op: "struct.new",
1299
+ typeIndex: structInfo.typeIndex,
1300
+ });
1301
+ } else {
1302
+ initInsts = [
1303
+ { op: "ref.null", heapType: wType.typeIndex ?? "any" },
1304
+ ];
1305
+ }
1306
+ } else {
1307
+ initInsts = [
1308
+ {
1309
+ op: "ref.null",
1310
+ heapType:
1311
+ typeof wType === "object"
1312
+ ? (wType.heapType ?? wType.typeIndex ?? "any")
1313
+ : wType.replace("ref", ""),
1314
+ },
1315
+ ];
1316
+ }
1317
+ }
1318
+
1319
+ let litNode = values?.[i];
1320
+ let sign = 1;
1321
+ if (
1322
+ litNode &&
1323
+ litNode.kind === "UnaryExpr" &&
1324
+ litNode.op === "-" &&
1325
+ litNode.operand?.kind === "BasicLit"
1326
+ ) {
1327
+ litNode = litNode.operand;
1328
+ sign = -1;
1329
+ }
1330
+
1331
+ let isConstInit = false;
1332
+ if (litNode && litNode.kind === "BasicLit") {
1333
+ if (litNode.litKind === "INT") {
1334
+ isConstInit = true;
1335
+ initInsts = [
1336
+ wType === "i64"
1337
+ ? {
1338
+ op: "i64.const",
1339
+ value: BigInt(sign) * BigInt(litNode.value),
1340
+ }
1341
+ : {
1342
+ op: "i32.const",
1343
+ value: (sign * Number(litNode.value)) | 0,
1344
+ },
1345
+ ];
1346
+ } else if (litNode.litKind === "FLOAT") {
1347
+ isConstInit = true;
1348
+ initInsts = [
1349
+ {
1350
+ op: `${wType}.const`,
1351
+ value: sign * Number(litNode.value),
1352
+ },
1353
+ ];
1354
+ } else if (litNode.litKind === "BOOL") {
1355
+ isConstInit = true;
1356
+ initInsts = [
1357
+ {
1358
+ op: "i32.const",
1359
+ value: litNode.value === "true" ? 1 : 0,
1360
+ },
1361
+ ];
1362
+ }
1363
+ }
1364
+ if (values[i] && !isConstInit && names.length === values.length) {
1365
+ pending.push({ name, expr: values[i] });
1366
+ }
1367
+ mod.globals.push({
1368
+ type: wType,
1369
+ mutable: true,
1370
+ init: initInsts,
1371
+ });
1372
+ mod.globalCache.set(name, {
1373
+ index: gIdx,
1374
+ type: wType,
1375
+ goType: rawType,
1376
+ });
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ return pending;
1382
+ }
1383
+
1384
+ // Synthesises `__init_globals()` assigning every non-constant package-level
1385
+ // initializer in declaration order and invoking package `init()` functions;
1386
+ // registered as the module start function.
1387
+ function _makeGlobalInitFunc(pending, initNames = []) {
1388
+ const stmts = pending.map(({ name, expr }) => ({
1389
+ kind: "AssignStmt",
1390
+ lhs: [{ kind: "Ident", name, _type: expr._type }],
1391
+ op: "=",
1392
+ rhs: [expr],
1393
+ }));
1394
+ for (const initName of initNames) {
1395
+ stmts.push({
1396
+ kind: "ExprStmt",
1397
+ expr: {
1398
+ kind: "CallExpr",
1399
+ func: { kind: "Ident", name: initName },
1400
+ args: [],
1401
+ },
1402
+ });
1403
+ }
1404
+ stmts.push({ kind: "ReturnStmt", values: [] });
1405
+ return {
1406
+ kind: "FuncDecl",
1407
+ name: "__init_globals",
1408
+ params: [],
1409
+ returnType: null,
1410
+ body: { kind: "Block", stmts, list: stmts },
1411
+ _isGlobalInit: true,
1412
+ };
1413
+ }