gofront 1.4.0 → 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 (61) hide show
  1. package/README.md +72 -9
  2. package/package.json +2 -1
  3. package/src/{codegen → backend/js}/expressions.js +217 -60
  4. package/src/{codegen → backend/js}/index.js +21 -35
  5. package/src/{codegen → backend/js}/statements.js +43 -48
  6. package/src/backend/js/stdlib/bits.js +32 -0
  7. package/src/{codegen → backend/js}/stdlib/index.js +3 -0
  8. package/src/{codegen → backend/js}/templ.js +2 -2
  9. package/src/backend/wasm/boundary.js +1345 -0
  10. package/src/backend/wasm/emit.js +5779 -0
  11. package/src/backend/wasm/encode.js +789 -0
  12. package/src/backend/wasm/glue.js +130 -0
  13. package/src/backend/wasm/index.js +1413 -0
  14. package/src/backend/wasm/types.js +420 -0
  15. package/src/backend/wasm/wat.js +255 -0
  16. package/src/cli-core.js +52 -7
  17. package/src/compiler.js +322 -12
  18. package/src/dev-server.js +56 -15
  19. package/src/index.js +17 -9
  20. package/src/lexer.js +18 -0
  21. package/src/lower/boxing.js +36 -0
  22. package/src/lower/captures.js +179 -0
  23. package/src/lower/embedding.js +30 -0
  24. package/src/lower/escape.js +169 -0
  25. package/src/lower/functions.js +30 -0
  26. package/src/lower/globals.js +324 -0
  27. package/src/lower/index.js +115 -0
  28. package/src/lower/ownership.js +142 -0
  29. package/src/lower/range.js +70 -0
  30. package/src/minifier.js +2 -0
  31. package/src/parser/index.js +8 -3
  32. package/src/resolver.js +1 -0
  33. package/src/test-runner.js +74 -20
  34. package/src/typechecker/expressions.js +40 -1
  35. package/src/typechecker/index.js +234 -4
  36. package/src/typechecker/statements.js +7 -0
  37. package/src/typechecker/stdlib/core.js +17 -0
  38. package/src/typechecker/types.js +56 -2
  39. /package/src/{codegen → backend/js}/runtime.js +0 -0
  40. /package/src/{codegen → backend/js}/source-map.js +0 -0
  41. /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
  42. /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
  43. /package/src/{codegen → backend/js}/stdlib/errors.js +0 -0
  44. /package/src/{codegen → backend/js}/stdlib/fmt.js +0 -0
  45. /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
  46. /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
  47. /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
  48. /package/src/{codegen → backend/js}/stdlib/maps.js +0 -0
  49. /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
  50. /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
  51. /package/src/{codegen → backend/js}/stdlib/path.js +0 -0
  52. /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
  53. /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
  54. /package/src/{codegen → backend/js}/stdlib/slices.js +0 -0
  55. /package/src/{codegen → backend/js}/stdlib/sort.js +0 -0
  56. /package/src/{codegen → backend/js}/stdlib/strconv.js +0 -0
  57. /package/src/{codegen → backend/js}/stdlib/strings.js +0 -0
  58. /package/src/{codegen → backend/js}/stdlib/testing.js +0 -0
  59. /package/src/{codegen → backend/js}/stdlib/time.js +0 -0
  60. /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
  61. /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
@@ -0,0 +1,1345 @@
1
+ // src/backend/wasm/boundary.js
2
+ // JS <-> WASM boundary: type classification, marshalling helpers (WASM IR)
3
+ // and the generated JS facade that exposes a wasm package to JS callers.
4
+ //
5
+ // Boundary v1 rules (docs/v1.5.0/wasm-mvp-plan.md, Phase 5):
6
+ // - primitives: bool <-> i32, narrow ints <-> i32, int/int64 <-> f64 at the
7
+ // boundary (i64 inside; exact for |v| <= 2^53, which the facade already
8
+ // enforces, and keeps BigInt boxing off hot paths), floats direct,
9
+ // string externref, any anyref (crossing as externref).
10
+ // - every GC reference (struct/array/closure/anyref) crosses as externref
11
+ // and exports are bound to module-level consts: V8 only inlines JS->wasm
12
+ // calls (floats unboxed) for const callees with i32/i64/f32/f64/externref
13
+ // signatures outside try/catch. Panics are raised through `env.panic`
14
+ // (a JS Error) rather than a wasm `throw`, so no guard wrapper is needed.
15
+ // - struct values from `both` packages: copied field-by-field into the JS
16
+ // copy class (`new T(...)`); `*T` params are copied in and written back.
17
+ // - struct values from `wasm` packages: opaque handles with stable identity
18
+ // (class `T` wrapping the WasmGC ref, WeakMap keyed registry).
19
+ // - slices / arrays: element-wise copy (plain JS arrays on the JS side,
20
+ // TypedArray inputs accepted). No copy-back for slices.
21
+ // - func values: JS callbacks wrapped into wasm closures via thunks; wasm
22
+ // closures exposed to JS as callable functions.
23
+ // - maps, non-empty interfaces, error, pointers to non-structs: rejected
24
+ // with a compile error (planned for a later boundary version).
25
+ // - `*testing.T`: the harness's JS test object, passed as an opaque
26
+ // externref (test methods are routed back to JS via `env.testing_*`).
27
+
28
+ import { isTestingT } from "./types.js";
29
+
30
+ // ── Type classification ──────────────────────────────────────
31
+
32
+ const I32_INTS = new Set([
33
+ "int8",
34
+ "int16",
35
+ "int32",
36
+ "uint8",
37
+ "uint16",
38
+ "uint32",
39
+ "byte",
40
+ "rune",
41
+ ]);
42
+ const I64_INTS = new Set(["int", "uint", "int64", "uint64", "uintptr"]);
43
+ const UNSIGNED = new Set([
44
+ "uint",
45
+ "uint8",
46
+ "uint16",
47
+ "uint32",
48
+ "uint64",
49
+ "uintptr",
50
+ "byte",
51
+ ]);
52
+
53
+ export function wTypeKey(wType) {
54
+ if (typeof wType === "string") return wType;
55
+ if (wType && typeof wType.typeIndex === "number")
56
+ return `r${wType.typeIndex}`;
57
+ return `h${wType?.heapType ?? "any"}`;
58
+ }
59
+
60
+ function toRef(info) {
61
+ return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
62
+ }
63
+
64
+ function structDesc(info, ptr) {
65
+ return { k: "struct", name: info.name, ptr, info, wType: toRef(info) };
66
+ }
67
+
68
+ function funcSigFromAst(t) {
69
+ return {
70
+ kind: "func",
71
+ params: (t.params ?? []).map((p) => p.type ?? p),
72
+ returns: t.returnType
73
+ ? t.returnType.kind === "TupleType" || t.returnType.kind === "tuple"
74
+ ? t.returnType.types
75
+ : [t.returnType]
76
+ : [],
77
+ };
78
+ }
79
+
80
+ // Classifies a Go type (AST node or checker type) for boundary marshalling.
81
+ // Returns a descriptor `{ k, ... }`; `k === "unsupported"` carries `what`.
82
+ export function classifyType(goType, mod) {
83
+ if (!goType) return { k: "void" };
84
+ if (isTestingT(goType)) return { k: "extern", wType: "externref" };
85
+
86
+ if (goType.kind === "TypeName" || goType.kind === "Ident") {
87
+ const info = mod.getStructType(goType.name);
88
+ if (info) return structDesc(info, false);
89
+ const resolved = mod.checker?.types?.get(goType.name);
90
+ if (resolved) return classifyType(resolved, mod);
91
+ return classifyBasic(goType.name);
92
+ }
93
+
94
+ const t = normalizeAstType(goType);
95
+ switch (t.kind) {
96
+ case "named":
97
+ return classifyNamed(t, mod);
98
+ case "untyped":
99
+ return classifyBasic(t.base);
100
+ case "basic":
101
+ return classifyBasic(t.name);
102
+ case "pointer":
103
+ return classifyPointer(t, mod);
104
+ case "slice":
105
+ return classifySlice(t, mod);
106
+ case "array":
107
+ return classifyArray(t, mod);
108
+ case "func":
109
+ return classifyFunc(t, mod);
110
+ case "interface":
111
+ return classifyInterface(t);
112
+ default:
113
+ return {
114
+ k: "unsupported",
115
+ what: UNSUPPORTED_KINDS[t.kind] ?? `type kind '${t.kind}'`,
116
+ };
117
+ }
118
+ }
119
+
120
+ const UNSUPPORTED_KINDS = {
121
+ map: "map",
122
+ MapType: "map",
123
+ struct: "anonymous struct",
124
+ StructType: "anonymous struct",
125
+ InterfaceType: "interface",
126
+ ChanType: "chan",
127
+ chan: "chan",
128
+ };
129
+
130
+ // Maps parser AST type nodes onto the checker's type shapes.
131
+ function normalizeAstType(t) {
132
+ switch (t.kind) {
133
+ case "PointerType":
134
+ case "StarExpr":
135
+ return { kind: "pointer", base: t.base ?? t.expr ?? t.operand };
136
+ case "SliceType":
137
+ return { kind: "slice", elem: t.elem };
138
+ case "ArrayType":
139
+ return { kind: "array", elem: t.elem, size: t.size };
140
+ case "FuncType":
141
+ return funcSigFromAst(t);
142
+ case "InterfaceType":
143
+ return {
144
+ kind: "interface",
145
+ name: t.name,
146
+ methods: new Set((t.methods ?? []).map((m) => m.name ?? m)),
147
+ };
148
+ default:
149
+ return t;
150
+ }
151
+ }
152
+
153
+ const BASIC_DESCS = {
154
+ void: { k: "void" },
155
+ bool: { k: "bool", wType: "i32" },
156
+ float32: { k: "f32", wType: "f32" },
157
+ float64: { k: "f64", wType: "f64" },
158
+ string: { k: "string", wType: "externref" },
159
+ any: { k: "any", wType: "anyref" },
160
+ error: { k: "unsupported", what: "error" },
161
+ };
162
+
163
+ function classifyBasic(n) {
164
+ if (I32_INTS.has(n))
165
+ return { k: "i32", unsigned: UNSIGNED.has(n), wType: "i32" };
166
+ if (I64_INTS.has(n))
167
+ return { k: "i64", unsigned: UNSIGNED.has(n), wType: "i64" };
168
+ const d = BASIC_DESCS[n];
169
+ return d ? { ...d } : { k: "unsupported", what: `type '${n}'` };
170
+ }
171
+
172
+ function classifyNamed(t, mod) {
173
+ const info = mod.getStructType(t.name);
174
+ if (info) return structDesc(info, false);
175
+ if (t.underlying) return classifyType(t.underlying, mod);
176
+ return { k: "unsupported", what: `named type '${t.name}'` };
177
+ }
178
+
179
+ function classifyPointer(t, mod) {
180
+ const base = classifyType(t.base, mod);
181
+ if (base.k === "struct" && !base.ptr) return { ...base, ptr: true };
182
+ const what = base.k === "unsupported" ? base.what : (t.base?.name ?? base.k);
183
+ return { k: "unsupported", what: `pointer to ${what}` };
184
+ }
185
+
186
+ function classifySlice(t, mod) {
187
+ const elem = classifyType(t.elem, mod);
188
+ if (elem.k === "unsupported") return elem;
189
+ const sliceInfo = mod.getSliceType(t.elem);
190
+ return {
191
+ k: "slice",
192
+ elem,
193
+ sliceInfo,
194
+ key: wTypeKey(sliceInfo.elemWType),
195
+ wType: { kind: "ref", nullable: true, typeIndex: sliceInfo.typeIndex },
196
+ };
197
+ }
198
+
199
+ function arraySize(t) {
200
+ if (typeof t.size === "number") return t.size;
201
+ if (t.size?.value !== undefined) return Number(t.size.value);
202
+ return t.len ?? 0;
203
+ }
204
+
205
+ function classifyArray(t, mod) {
206
+ const elem = classifyType(t.elem, mod);
207
+ if (elem.k === "unsupported") return elem;
208
+ const arrInfo = mod.getArrayType(t.elem);
209
+ return {
210
+ k: "array",
211
+ elem,
212
+ size: arraySize(t),
213
+ arrInfo,
214
+ key: wTypeKey(arrInfo.elemWType),
215
+ wType: { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex },
216
+ };
217
+ }
218
+
219
+ function classifyFunc(t, mod) {
220
+ const params = (t.params ?? []).map((p) => classifyType(p, mod));
221
+ const returns = (t.returns ?? [])
222
+ .map((r) => classifyType(r, mod))
223
+ .filter((d) => d.k !== "void");
224
+ const bad = [...params, ...returns].find((d) => d.k === "unsupported");
225
+ if (bad) return bad;
226
+ const closureInfo = mod.getClosureType(t);
227
+ return {
228
+ k: "func",
229
+ params,
230
+ returns,
231
+ closureInfo,
232
+ key: `c${closureInfo.typeIndex}`,
233
+ wType: { kind: "ref", nullable: true, typeIndex: closureInfo.typeIndex },
234
+ };
235
+ }
236
+
237
+ function classifyInterface(t) {
238
+ if (t.name === "error") return { k: "unsupported", what: "error" };
239
+ if (!t.methods || t.methods.size === 0) return { k: "any", wType: "anyref" };
240
+ return {
241
+ k: "unsupported",
242
+ what: `interface${t.name ? ` '${t.name}'` : ""}`,
243
+ };
244
+ }
245
+
246
+ // ── Export metadata ──────────────────────────────────────────
247
+
248
+ function isExported(name) {
249
+ return name[0] >= "A" && name[0] <= "Z";
250
+ }
251
+
252
+ function returnDescs(returnType, mod) {
253
+ if (!returnType) return [];
254
+ const list =
255
+ returnType.kind === "TupleType" || returnType.kind === "tuple"
256
+ ? returnType.types
257
+ : [returnType];
258
+ return list.map((t) => classifyType(t, mod)).filter((d) => d.k !== "void");
259
+ }
260
+
261
+ function literalConst(spec, i) {
262
+ let node = spec.value?.[i];
263
+ let sign = "";
264
+ if (node?.kind === "UnaryExpr" && node.op === "-" && node.operand) {
265
+ node = node.operand;
266
+ sign = "-";
267
+ }
268
+ if (node?.kind !== "BasicLit") return null;
269
+ switch (node.litKind) {
270
+ case "INT":
271
+ case "FLOAT":
272
+ return `${sign}${node.value}`;
273
+ case "STRING":
274
+ return JSON.stringify(String(node.value));
275
+ case "BOOL":
276
+ return node.value === "true" ? "true" : "false";
277
+ default:
278
+ return null;
279
+ }
280
+ }
281
+
282
+ function unsupportedMsg(where, what) {
283
+ return `${where}: ${what} is not yet supported across the wasm boundary (planned)`;
284
+ }
285
+
286
+ // Builds the boundary metadata for every exported symbol of `wasm`-target
287
+ // packages (plus struct descriptors of `both` packages they expose).
288
+ // Throws a compile error for unsupported boundary types.
289
+ export function collectBoundaryMeta(progs, funcDecls, mod) {
290
+ const meta = {
291
+ structs: [],
292
+ funcs: [],
293
+ consts: [],
294
+ structByName: new Map(),
295
+ _mod: mod,
296
+ };
297
+ const errors = [];
298
+
299
+ for (const p of progs) {
300
+ const pkgName = p.pkg?.name ?? "main";
301
+ const pkgTarget = p.target ?? "wasm";
302
+ for (const d of p.decls ?? []) {
303
+ if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
304
+ collectStructDecl(d, pkgName, pkgTarget, meta, mod);
305
+ } else if (d.kind === "ConstDecl" && pkgTarget === "wasm") {
306
+ collectConstDecl(d, pkgName, meta, errors);
307
+ }
308
+ }
309
+ }
310
+
311
+ for (const fn of funcDecls) {
312
+ if (fn._isClosure || fn._pkgTarget !== "wasm") continue;
313
+ if (fn._isMethod) collectMethodMeta(fn, meta, errors, mod);
314
+ else collectFuncMeta(fn, meta, errors, mod);
315
+ }
316
+
317
+ if (errors.length > 0) throw new Error([...new Set(errors)].join("\n"));
318
+ return meta;
319
+ }
320
+
321
+ function collectStructDecl(d, pkgName, pkgTarget, meta, mod) {
322
+ if (!isExported(d.name) || meta.structByName.has(d.name)) return;
323
+ const info = mod.getStructType(d.name);
324
+ if (!info) return;
325
+ const s = { name: d.name, pkgName, pkgTarget, info, methods: [] };
326
+ meta.structs.push(s);
327
+ meta.structByName.set(d.name, s);
328
+ }
329
+
330
+ function collectConstDecl(d, pkgName, meta, errors) {
331
+ for (const spec of d.decls ?? []) {
332
+ for (let i = 0; i < spec.names.length; i++) {
333
+ const name = spec.names[i];
334
+ if (!isExported(name)) continue;
335
+ const value = literalConst(spec, i);
336
+ if (value !== null) meta.consts.push({ name, value });
337
+ else
338
+ errors.push(
339
+ unsupportedMsg(
340
+ `${pkgName}.${name}`,
341
+ "a constant with a non-literal value",
342
+ ),
343
+ );
344
+ }
345
+ }
346
+ }
347
+
348
+ function checkDesc(desc, where, errors) {
349
+ if (desc.k === "unsupported") errors.push(unsupportedMsg(where, desc.what));
350
+ return desc;
351
+ }
352
+
353
+ function collectMethodMeta(fn, meta, errors, mod) {
354
+ if (!isExported(fn._methodName)) return;
355
+ const s = meta.structByName.get(fn._recvTypeName);
356
+ if (!s) return;
357
+ const where = `${fn._recvTypeName}.${fn._methodName}`;
358
+ s.methods.push({
359
+ name: fn._methodName,
360
+ exportName: fn._exportName,
361
+ ptrRecv: fn.params[0].type?.kind === "pointer",
362
+ params: fn.params
363
+ .slice(1)
364
+ .map((p) => checkDesc(classifyType(p.type, mod), where, errors)),
365
+ returns: returnDescs(fn.returnType, mod).map((d) =>
366
+ checkDesc(d, where, errors),
367
+ ),
368
+ });
369
+ }
370
+
371
+ function collectFuncMeta(fn, meta, errors, mod) {
372
+ if (!isExported(fn.name)) return;
373
+ const where = `${fn._pkgName}.${fn.name}`;
374
+ meta.funcs.push({
375
+ name: fn.name,
376
+ exportName: fn.name,
377
+ pkgName: fn._pkgName,
378
+ params: (fn.params ?? []).map((p) =>
379
+ checkDesc(classifyType(p.type, mod), where, errors),
380
+ ),
381
+ returns: returnDescs(fn.returnType, mod).map((d) =>
382
+ checkDesc(d, where, errors),
383
+ ),
384
+ });
385
+ }
386
+
387
+ // ── Descriptor walk ──────────────────────────────────────────
388
+
389
+ function visitStructNeed(desc, needs, errors, mod) {
390
+ if (needs.structs.has(desc.name)) return;
391
+ const entry = { info: desc.info, fields: [] };
392
+ needs.structs.set(desc.name, entry);
393
+ for (const f of desc.info.fields) {
394
+ const fd = classifyType(f.goType, mod);
395
+ if (fd.k === "unsupported")
396
+ errors.push(unsupportedMsg(`${desc.name}.${f.name}`, fd.what));
397
+ entry.fields.push({ name: f.name, desc: fd });
398
+ visitNeed(fd, needs, errors, mod);
399
+ }
400
+ }
401
+
402
+ function visitNeed(desc, needs, errors, mod) {
403
+ if (!desc || desc.k === "void") return;
404
+ switch (desc.k) {
405
+ case "struct":
406
+ visitStructNeed(desc, needs, errors, mod);
407
+ return;
408
+ case "slice":
409
+ case "array": {
410
+ const map = desc.k === "slice" ? needs.slices : needs.arrays;
411
+ if (map.has(desc.key)) return;
412
+ map.set(desc.key, desc);
413
+ visitNeed(desc.elem, needs, errors, mod);
414
+ return;
415
+ }
416
+ case "func":
417
+ if (needs.funcs.has(desc.key)) return;
418
+ needs.funcs.set(desc.key, desc);
419
+ visitSignatureNeeds(desc, needs, errors, mod);
420
+ return;
421
+ default:
422
+ return;
423
+ }
424
+ }
425
+
426
+ function visitSignatureNeeds(sig, needs, errors, mod) {
427
+ for (const p of sig.params) visitNeed(p, needs, errors, mod);
428
+ for (const r of sig.returns) visitNeed(r, needs, errors, mod);
429
+ }
430
+
431
+ // Collects every descriptor reachable from the exported surface so helpers are
432
+ // emitted once per struct / slice key / array key / closure key.
433
+ function collectNeeds(meta) {
434
+ const mod = meta._mod;
435
+ const needs = {
436
+ structs: new Map(), // name -> { info, fields: [{ name, desc }] }
437
+ slices: new Map(), // key -> desc
438
+ arrays: new Map(), // key -> desc
439
+ funcs: new Map(), // key -> desc
440
+ };
441
+ const errors = [];
442
+ for (const s of meta.structs) {
443
+ if (s.pkgTarget === "wasm")
444
+ visitNeed(structDesc(s.info, false), needs, errors, mod);
445
+ for (const m of s.methods) visitSignatureNeeds(m, needs, errors, mod);
446
+ }
447
+ for (const f of meta.funcs) visitSignatureNeeds(f, needs, errors, mod);
448
+ if (errors.length > 0) throw new Error([...new Set(errors)].join("\n"));
449
+ return needs;
450
+ }
451
+
452
+ // ── WASM-side helpers ────────────────────────────────────────
453
+
454
+ // Pre-lock: registers env imports for JS callbacks (`__invoke$<key>`).
455
+ export function scanBoundaryImports(mod, meta) {
456
+ const needs = collectNeeds(meta);
457
+ meta._needs = needs;
458
+ for (const [key, desc] of needs.funcs) {
459
+ mod.getOrAddFuncImport(
460
+ "env",
461
+ `__invoke$${key}`,
462
+ ["externref", ...desc.closureInfo.paramWTypes.map(extType)],
463
+ desc.closureInfo.returnWTypes.map(extType),
464
+ );
465
+ }
466
+ }
467
+
468
+ // Boundary representation of a wasm value type. i64 crosses as f64 so JS
469
+ // never sees (or allocates) a BigInt for Go `int`; every GC reference (struct,
470
+ // array, closure, anyref) crosses as externref. V8 only inlines the
471
+ // JS→wasm wrapper (passing f32/f64 unboxed) when the signature holds nothing
472
+ // but i32/i64/f32/f64/externref: a single anyref or `(ref $T)` in the
473
+ // signature costs a 16-byte HeapNumber per float argument and result.
474
+ function isGcRef(wType) {
475
+ return wType === "anyref" || (typeof wType === "object" && wType !== null);
476
+ }
477
+ function extType(wType) {
478
+ if (wType === "i64") return "f64";
479
+ if (isGcRef(wType)) return "externref";
480
+ return wType;
481
+ }
482
+ function needsConv(wTypes) {
483
+ return wTypes.some((w) => w === "i64" || isGcRef(w));
484
+ }
485
+ // Instructions converting the value on the stack from its boundary form.
486
+ function fromExt(wType) {
487
+ if (wType === "i64") return [{ op: "i64.trunc_sat_f64_s" }];
488
+ if (wType === "anyref") return [{ op: "any.convert_extern" }];
489
+ if (isGcRef(wType)) {
490
+ const out = [{ op: "any.convert_extern" }];
491
+ if (wType.typeIndex !== undefined)
492
+ out.push({ op: "ref.cast_null", typeIndex: wType.typeIndex });
493
+ return out;
494
+ }
495
+ return [];
496
+ }
497
+ // Instructions converting the value on the stack to its boundary form.
498
+ function toExt(wType) {
499
+ if (wType === "i64") return [{ op: "f64.convert_i64_s" }];
500
+ if (isGcRef(wType)) return [{ op: "extern.convert_any" }];
501
+ return [];
502
+ }
503
+ // Rewrites the values on top of the stack (types `wTypes`) with `conv`,
504
+ // spilling through locals starting at `localBase` when more than one.
505
+ function convertResults(wTypes, conv, localBase) {
506
+ if (wTypes.length === 0 || !needsConv(wTypes)) return [];
507
+ if (wTypes.length === 1) return conv(wTypes[0]);
508
+ const out = [];
509
+ for (let i = wTypes.length - 1; i >= 0; i--)
510
+ out.push({ op: "local.set", index: localBase + i });
511
+ for (let i = 0; i < wTypes.length; i++)
512
+ out.push({ op: "local.get", index: localBase + i }, ...conv(wTypes[i]));
513
+ return out;
514
+ }
515
+
516
+ function zeroInstr(wType) {
517
+ if (wType === "i64") return { op: "i64.const", value: 0n };
518
+ if (wType === "f32" || wType === "f64")
519
+ return { op: `${wType}.const`, value: 0 };
520
+ if (typeof wType === "object" && wType !== null) {
521
+ return {
522
+ op: "ref.null",
523
+ heapType: wType.typeIndex ?? wType.heapType ?? "any",
524
+ };
525
+ }
526
+ if (wType === "externref") return { op: "ref.null", heapType: "extern" };
527
+ if (wType === "anyref") return { op: "ref.null", heapType: "any" };
528
+ return { op: "i32.const", value: 0 };
529
+ }
530
+
531
+ // Type entry at a flat type index (rec groups hold several entries each).
532
+ function typeAt(mod, index) {
533
+ if (index === undefined) return null;
534
+ let i = 0;
535
+ for (const entry of mod.types) {
536
+ const group = entry.form === "rec" ? entry.types : [entry];
537
+ if (index < i + group.length) return group[index - i];
538
+ i += group.length;
539
+ }
540
+ return null;
541
+ }
542
+
543
+ // Post user functions: appends marshalling helper functions and exports them.
544
+ export function addBoundaryHelpers(mod, meta) {
545
+ const needs = meta._needs ?? collectNeeds(meta);
546
+ const importFuncCount = mod.imports.filter((i) => i.kind === "func").length;
547
+
548
+ const addFunc = (
549
+ name,
550
+ params,
551
+ results,
552
+ body,
553
+ exported = true,
554
+ locals = [],
555
+ ) => {
556
+ const idx = importFuncCount + mod.funcs.length;
557
+ const typeIndex = mod.getTypeIndex(params, results);
558
+ mod.funcs.push({
559
+ typeIndex,
560
+ locals,
561
+ body: [...body, { op: "return" }],
562
+ });
563
+ mod.funcMap.set(name, idx);
564
+ if (exported) mod.exports.push({ name, kind: "func", index: idx });
565
+ return idx;
566
+ };
567
+ const get = (i) => ({ op: "local.get", index: i });
568
+ // Parameter `i` converted from its boundary form.
569
+ const getIn = (i, wType) => [get(i), ...fromExt(wType)];
570
+ const EXT = "externref";
571
+
572
+ for (const [name, { info }] of needs.structs) {
573
+ const ref = toRef(info);
574
+ const ti = info.typeIndex;
575
+ const fieldWTypes = info.fields.map((f) => f.wType);
576
+ addFunc(
577
+ `__new_${name}`,
578
+ fieldWTypes.map(extType),
579
+ [EXT],
580
+ [
581
+ ...fieldWTypes.flatMap((w, i) => getIn(i, w)),
582
+ { op: "struct.new", typeIndex: ti },
583
+ ...toExt(ref),
584
+ ],
585
+ );
586
+ addFunc(
587
+ `__zero_${name}`,
588
+ [],
589
+ [EXT],
590
+ [
591
+ ...fieldWTypes.map((w) => zeroInstr(w)),
592
+ { op: "struct.new", typeIndex: ti },
593
+ ...toExt(ref),
594
+ ],
595
+ );
596
+ addFunc(
597
+ `__clone_${name}`,
598
+ [EXT],
599
+ [EXT],
600
+ [
601
+ ...fieldWTypes.flatMap((_, i) => [
602
+ ...getIn(0, ref),
603
+ { op: "struct.get", typeIndex: ti, fieldIndex: i },
604
+ ]),
605
+ { op: "struct.new", typeIndex: ti },
606
+ ...toExt(ref),
607
+ ],
608
+ );
609
+ for (let i = 0; i < info.fields.length; i++) {
610
+ const f = info.fields[i];
611
+ addFunc(
612
+ `__get_${name}_${f.name}`,
613
+ [EXT],
614
+ [extType(f.wType)],
615
+ [
616
+ ...getIn(0, ref),
617
+ { op: "struct.get", typeIndex: ti, fieldIndex: i },
618
+ ...toExt(f.wType),
619
+ ],
620
+ );
621
+ addFunc(
622
+ `__set_${name}_${f.name}`,
623
+ [EXT, extType(f.wType)],
624
+ [],
625
+ [
626
+ ...getIn(0, ref),
627
+ ...getIn(1, f.wType),
628
+ { op: "struct.set", typeIndex: ti, fieldIndex: i },
629
+ ],
630
+ );
631
+ }
632
+ }
633
+
634
+ for (const [key, desc] of needs.arrays) {
635
+ const { arrInfo } = desc;
636
+ const ref = desc.wType;
637
+ addFunc(
638
+ `__array_new_${key}`,
639
+ ["i32"],
640
+ [EXT],
641
+ [
642
+ get(0),
643
+ { op: "array.new_default", typeIndex: arrInfo.typeIndex },
644
+ ...toExt(ref),
645
+ ],
646
+ );
647
+ addFunc(
648
+ `__array_len_${key}`,
649
+ [EXT],
650
+ ["i32"],
651
+ [...getIn(0, ref), { op: "array.len" }],
652
+ );
653
+ addFunc(
654
+ `__array_get_${key}`,
655
+ [EXT, "i32"],
656
+ [extType(arrInfo.elemWType)],
657
+ [
658
+ ...getIn(0, ref),
659
+ get(1),
660
+ { op: "array.get", typeIndex: arrInfo.typeIndex },
661
+ ...toExt(arrInfo.elemWType),
662
+ ],
663
+ );
664
+ addFunc(
665
+ `__array_set_${key}`,
666
+ [EXT, "i32", extType(arrInfo.elemWType)],
667
+ [],
668
+ [
669
+ ...getIn(0, ref),
670
+ get(1),
671
+ ...getIn(2, arrInfo.elemWType),
672
+ { op: "array.set", typeIndex: arrInfo.typeIndex },
673
+ ],
674
+ );
675
+ }
676
+
677
+ for (const [key, desc] of needs.slices) {
678
+ const { sliceInfo } = desc;
679
+ const ref = desc.wType;
680
+ const arrTI = sliceInfo.arrInfo.typeIndex;
681
+ const sTI = sliceInfo.typeIndex;
682
+ const elemAddr = [
683
+ ...getIn(0, ref),
684
+ { op: "struct.get", typeIndex: sTI, fieldIndex: 0 },
685
+ ...getIn(0, ref),
686
+ { op: "struct.get", typeIndex: sTI, fieldIndex: 1 },
687
+ get(1),
688
+ { op: "i32.add" },
689
+ ];
690
+ addFunc(
691
+ `__slice_new_${key}`,
692
+ ["i32"],
693
+ [EXT],
694
+ [
695
+ get(0),
696
+ { op: "array.new_default", typeIndex: arrTI },
697
+ { op: "i32.const", value: 0 },
698
+ get(0),
699
+ get(0),
700
+ { op: "struct.new", typeIndex: sTI },
701
+ ...toExt(ref),
702
+ ],
703
+ );
704
+ addFunc(
705
+ `__slice_len_${key}`,
706
+ [EXT],
707
+ ["i32"],
708
+ [...getIn(0, ref), { op: "struct.get", typeIndex: sTI, fieldIndex: 2 }],
709
+ );
710
+ addFunc(
711
+ `__slice_get_${key}`,
712
+ [EXT, "i32"],
713
+ [extType(sliceInfo.elemWType)],
714
+ [
715
+ ...elemAddr,
716
+ { op: "array.get", typeIndex: arrTI },
717
+ ...toExt(sliceInfo.elemWType),
718
+ ],
719
+ );
720
+ addFunc(
721
+ `__slice_nil_${key}`,
722
+ [],
723
+ [EXT],
724
+ [{ op: "global.get", index: sliceInfo.emptyGlobalIndex }, ...toExt(ref)],
725
+ );
726
+ addFunc(
727
+ `__slice_is_nil_${key}`,
728
+ [EXT],
729
+ ["i32"],
730
+ [
731
+ ...getIn(0, ref),
732
+ { op: "struct.get", typeIndex: sTI, fieldIndex: 0 },
733
+ { op: "ref.is_null" },
734
+ ],
735
+ );
736
+ addFunc(
737
+ `__slice_set_${key}`,
738
+ [EXT, "i32", extType(sliceInfo.elemWType)],
739
+ [],
740
+ [
741
+ ...elemAddr,
742
+ ...getIn(2, sliceInfo.elemWType),
743
+ { op: "array.set", typeIndex: arrTI },
744
+ ],
745
+ );
746
+ }
747
+
748
+ for (const [key, desc] of needs.funcs) {
749
+ const ci = desc.closureInfo;
750
+ const invokeIdx = mod.importCache.get(`env.__invoke$${key}`);
751
+ const nParams = ci.paramWTypes.length;
752
+ // Thunk: wasm closure body calling the JS callback (boundary forms out, back in).
753
+ const thunkIdx = addFunc(
754
+ `__jsthunk$${key}`,
755
+ ["anyref", ...ci.paramWTypes],
756
+ ci.returnWTypes,
757
+ [
758
+ get(0),
759
+ { op: "extern.convert_any" },
760
+ ...ci.paramWTypes.flatMap((w, i) => [get(i + 1), ...toExt(w)]),
761
+ { op: "call", funcIndex: invokeIdx },
762
+ ...convertResults(ci.returnWTypes, fromExt, nParams + 1),
763
+ ],
764
+ false,
765
+ ci.returnWTypes.length > 1 ? ci.returnWTypes.map(extType) : [],
766
+ );
767
+ mod.addRefFuncElement(thunkIdx);
768
+ addFunc(
769
+ `__wrap_fn$${key}`,
770
+ [EXT],
771
+ [EXT],
772
+ [
773
+ { op: "ref.func", funcIndex: thunkIdx },
774
+ get(0),
775
+ { op: "any.convert_extern" },
776
+ { op: "struct.new", typeIndex: ci.typeIndex },
777
+ ...toExt(desc.wType),
778
+ ],
779
+ );
780
+ // JS calling a wasm closure (boundary forms in, results out).
781
+ addFunc(
782
+ `__call_fn$${key}`,
783
+ [EXT, ...ci.paramWTypes.map(extType)],
784
+ ci.returnWTypes.map(extType),
785
+ [
786
+ ...getIn(0, desc.wType),
787
+ { op: "struct.get", typeIndex: ci.typeIndex, fieldIndex: 1 },
788
+ ...ci.paramWTypes.flatMap((w, i) => getIn(i + 1, w)),
789
+ ...getIn(0, desc.wType),
790
+ { op: "struct.get", typeIndex: ci.typeIndex, fieldIndex: 0 },
791
+ { op: "call_ref", typeIndex: ci.funcTypeIndex },
792
+ ...convertResults(ci.returnWTypes, toExt, nParams + 1),
793
+ ],
794
+ true,
795
+ ci.returnWTypes.length > 1 ? ci.returnWTypes : [],
796
+ );
797
+ }
798
+
799
+ // Exported user functions / methods whose signature carries i64 or GC refs
800
+ // get a trampoline (`__x_<export>`) with the boundary signature; the facade
801
+ // calls that instead of the raw export.
802
+ const addTrampoline = (entry) => {
803
+ const fnIdx = mod.funcMap.get(entry.exportName);
804
+ if (fnIdx === undefined) return;
805
+ const sig = typeAt(mod, mod.funcs[fnIdx - importFuncCount]?.typeIndex);
806
+ if (sig?.form !== "func") return;
807
+ const paramW = sig.params;
808
+ const resultW = sig.results;
809
+ if (!needsConv(paramW) && !needsConv(resultW)) return;
810
+ entry.callName = `__x_${entry.exportName}`;
811
+ addFunc(
812
+ entry.callName,
813
+ paramW.map(extType),
814
+ resultW.map(extType),
815
+ [
816
+ ...paramW.flatMap((w, i) => getIn(i, w)),
817
+ { op: "call", funcIndex: fnIdx },
818
+ ...convertResults(resultW, toExt, paramW.length),
819
+ ],
820
+ true,
821
+ resultW.length > 1 ? resultW : [],
822
+ );
823
+ };
824
+ for (const s of meta.structs) for (const m of s.methods) addTrampoline(m);
825
+ for (const f of meta.funcs) addTrampoline(f);
826
+ }
827
+
828
+ // ── JS facade generation ─────────────────────────────────────
829
+
830
+ const P = "__gfw_"; // facade helper prefix
831
+
832
+ // JS expression converting JS value `v` into the wasm representation.
833
+ function inExpr(desc, v) {
834
+ switch (desc.k) {
835
+ case "bool":
836
+ return `(${v} ? 1 : 0)`;
837
+ case "i32":
838
+ return `(${v} | 0)`;
839
+ case "i64":
840
+ return `${P}i64in(${v})`;
841
+ case "f32":
842
+ case "f64":
843
+ return `(+${v})`;
844
+ case "string":
845
+ return `${P}strin(${v})`;
846
+ case "any":
847
+ return `(${v} === undefined ? null : ${v})`;
848
+ case "struct":
849
+ if (desc.info.pkgTarget === "both")
850
+ return `${P}to_${desc.name}(${v}, ${desc.ptr ? "false" : "true"})`;
851
+ return desc.ptr ? `${P}href(${v})` : `${P}hval_${desc.name}(${v})`;
852
+ case "slice":
853
+ return `${P}slin_${desc.key}(${v})`;
854
+ case "array":
855
+ return `${P}arrin_${desc.key}(${v})`;
856
+ case "func":
857
+ return `${P}fnin_${desc.key}(${v})`;
858
+ default:
859
+ return v;
860
+ }
861
+ }
862
+
863
+ // JS expression converting wasm value `v` into the JS representation.
864
+ function outExpr(desc, v) {
865
+ switch (desc.k) {
866
+ case "bool":
867
+ return `(${v} !== 0)`;
868
+ case "i32":
869
+ return desc.unsigned ? `(${v} >>> 0)` : v;
870
+ case "i64":
871
+ return desc.unsigned ? `${P}u64out(${v})` : `${P}i64out(${v})`;
872
+ case "string":
873
+ return `(${v} ?? "")`;
874
+ case "struct":
875
+ return desc.info.pkgTarget === "both"
876
+ ? `${P}from_${desc.name}(${v})`
877
+ : `${desc.name}.__wrap(${v})`;
878
+ case "slice":
879
+ return `${P}slout_${desc.key}(${v})`;
880
+ case "array":
881
+ return `${P}arrout_${desc.key}(${v})`;
882
+ case "func":
883
+ return `${P}fnout_${desc.key}(${v})`;
884
+ default:
885
+ return v;
886
+ }
887
+ }
888
+
889
+ function zeroExpr(desc) {
890
+ switch (desc.k) {
891
+ case "bool":
892
+ return "false";
893
+ case "i32":
894
+ case "i64":
895
+ case "f32":
896
+ case "f64":
897
+ return "0";
898
+ case "string":
899
+ return '""';
900
+ case "struct":
901
+ return desc.ptr ? "null" : `new ${desc.name}()`;
902
+ case "array":
903
+ return `${P}arrzero_${desc.key}(${desc.size})`;
904
+ default:
905
+ return "null";
906
+ }
907
+ }
908
+
909
+ // JS expression exposing wasm storage `v` reached through a handle field as a
910
+ // *live* JS value: aggregate fields alias the wasm object instead of copying
911
+ // it, so `h.Pos.X = 1` and `h.Items[0] = 2` reach wasm like they do in Go.
912
+ function viewExpr(desc, v) {
913
+ switch (desc.k) {
914
+ case "struct":
915
+ return desc.info.pkgTarget === "both"
916
+ ? `${P}view_${desc.name}(${v})`
917
+ : `${desc.name}.__wrap(${v})`;
918
+ case "slice":
919
+ return `${P}slview_${desc.key}(${v})`;
920
+ case "array":
921
+ return `${P}arrview_${desc.key}(${v})`;
922
+ default:
923
+ return outExpr(desc, v);
924
+ }
925
+ }
926
+
927
+ function isPtrBoth(d) {
928
+ return d.k === "struct" && d.ptr && d.info.pkgTarget === "both";
929
+ }
930
+
931
+ function isBothStruct(d) {
932
+ return d.k === "struct" && d.info.pkgTarget === "both";
933
+ }
934
+
935
+ function returnLines(returns, resultExpr) {
936
+ if (returns.length === 0) return [];
937
+ if (returns.length === 1)
938
+ return [`\treturn ${outExpr(returns[0], resultExpr)};`];
939
+ return [
940
+ `\treturn [${returns.map((d, i) => outExpr(d, `${resultExpr}[${i}]`)).join(", ")}];`,
941
+ ];
942
+ }
943
+
944
+ // Generates the body of a JS wrapper calling `__w.<exportName>(leadArgs..., params...)`.
945
+ function genFuncBody(exportName, params, returns, leadArgs = []) {
946
+ const names = params.map((_, i) => `a${i}`);
947
+ const lines = [];
948
+ const argExprs = [...leadArgs];
949
+ const writeBack = params.some(isPtrBoth);
950
+ if (writeBack) lines.push("\tconst __m = new Map();");
951
+ for (let i = 0; i < params.length; i++) {
952
+ const d = params[i];
953
+ if (isPtrBoth(d)) {
954
+ lines.push(`\tconst __p${i} = ${P}to_${d.name}(a${i}, false, __m);`);
955
+ argExprs.push(`__p${i}`);
956
+ } else {
957
+ argExprs.push(inExpr(d, names[i]));
958
+ }
959
+ }
960
+ const call = `__w.${exportName}(${argExprs.join(", ")})`;
961
+ lines.push("\ttry {");
962
+ if (!writeBack) {
963
+ if (returns.length === 0) lines.push(`\t\t${call};`);
964
+ else if (returns.length === 1)
965
+ lines.push(...returnLines(returns, call).map((l) => `\t${l}`));
966
+ else {
967
+ lines.push(`\t\tconst __r = ${call};`);
968
+ lines.push(...returnLines(returns, "__r").map((l) => `\t${l}`));
969
+ }
970
+ } else {
971
+ lines.push(`\t\tconst __r = ${call};`);
972
+ for (let i = 0; i < params.length; i++) {
973
+ if (isPtrBoth(params[i]))
974
+ lines.push(`\t\t${P}back_${params[i].name}(__p${i}, a${i});`);
975
+ }
976
+ lines.push(...returnLines(returns, "__r").map((l) => `\t${l}`));
977
+ }
978
+ lines.push(`\t} catch (e) { throw ${P}mapTrap(e); }`);
979
+ return { names, body: lines.join("\n") };
980
+ }
981
+
982
+ function indent(text, prefix) {
983
+ return text.replace(/^/gm, prefix);
984
+ }
985
+
986
+ // Runtime import table shared by the facade and glue.js (single source so
987
+ // the tests exercise the same env the shipped bundle uses). Written to
988
+ // survive the built-in mangler: no shorthand destructuring, import keys
989
+ // quoted, helpers prefixed with `__`.
990
+ export const WASM_IMPORTS_JS = `const ${P}MAX = 9007199254740991n;
991
+ function ${P}imports(stringTable, extraEnv, tag, write) {
992
+ let buf = [];
993
+ let targs = [];
994
+ const flush = () => { const s = buf.join(" "); buf = []; write(s); };
995
+ const print = (v) => { buf.push(String(v)); };
996
+ const println = (v) => { buf.push(String(v)); flush(); };
997
+ const targ = (v) => { targs.push(v); };
998
+ const env = {
999
+ "panicTag": tag,
1000
+ "panic": (msg) => { throw new Error(String(msg)); },
1001
+ "str": (i) => stringTable[i] ?? "",
1002
+ "str_len": (s) => (s ? s.length : 0),
1003
+ "str_concat": (a, b) => (a ?? "") + (b ?? ""),
1004
+ "str_eq": (a, b) => (a === b ? 1 : 0),
1005
+ "str_ne": (a, b) => (a !== b ? 1 : 0),
1006
+ "str_lt": (a, b) => ((a ?? "") < (b ?? "") ? 1 : 0),
1007
+ "str_le": (a, b) => ((a ?? "") <= (b ?? "") ? 1 : 0),
1008
+ "str_gt": (a, b) => ((a ?? "") > (b ?? "") ? 1 : 0),
1009
+ "str_ge": (a, b) => ((a ?? "") >= (b ?? "") ? 1 : 0),
1010
+ "str_get": (s, i) => (s ? s.charCodeAt(i) : 0),
1011
+ "str_slice": (s, a, b) => (s ? s.slice(a, b) : ""),
1012
+ "str_from_code_point": (c) => String.fromCodePoint(c),
1013
+ "str_code_point_at": (s, i) => (s ? s.codePointAt(i) : 0),
1014
+ "is_string": (v) => (typeof v === "string" ? 1 : 0),
1015
+ "print_i32": print, "print_i64": print, "print_f32": print, "print_f64": print,
1016
+ "print_str": print, "print_any": print,
1017
+ "print_bool": (b) => print(b !== 0 ? "true" : "false"),
1018
+ "println_i32": println, "println_i64": println, "println_f32": println, "println_f64": println,
1019
+ "println_str": println, "println_any": println,
1020
+ "println_bool": (b) => println(b !== 0 ? "true" : "false"),
1021
+ "println_empty": flush,
1022
+ "testing_arg_i32": targ, "testing_arg_f32": targ, "testing_arg_f64": targ,
1023
+ "testing_arg_str": targ, "testing_arg_any": targ,
1024
+ "testing_arg_i64": (v) => targ(v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1025
+ "testing_arg_bool": (v) => targ(v !== 0),
1026
+ "testing_call": (t, i) => { const a = targs; targs = []; t[stringTable[i]](...a); },
1027
+ "testing_name": (t) => t.Name(),
1028
+ "testing_flag": (t, i) => (t[stringTable[i]]() ? 1 : 0),
1029
+ };
1030
+ Object.assign(env, extraEnv);
1031
+ const m = {};
1032
+ for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "round"]) m[k] = Math[k];
1033
+ return { "env": env, "Math": m };
1034
+ }`;
1035
+
1036
+ // Runtime loader emitted at the top of the facade.
1037
+ export const WASM_LOADER_JS = `${WASM_IMPORTS_JS}
1038
+ async function ${P}fetch(url) {
1039
+ const res = await fetch(url);
1040
+ if (!res.ok) throw new Error("GoFront: failed to fetch " + url + " (" + res.status + ")");
1041
+ return res.arrayBuffer();
1042
+ }
1043
+ async function ${P}instantiate(imports) {
1044
+ const bytes = globalThis.__GOFRONT_WASM_BYTES;
1045
+ if (bytes) return WebAssembly.instantiate(bytes, imports);
1046
+ const url = globalThis.__GOFRONT_WASM_URL ?? "app.wasm";
1047
+ if (typeof WebAssembly.instantiateStreaming === "function") {
1048
+ try { return await WebAssembly.instantiateStreaming(fetch(url), imports); }
1049
+ catch { /* fall through: wrong MIME type or no streaming support */ }
1050
+ }
1051
+ return WebAssembly.instantiate(await ${P}fetch(url), imports);
1052
+ }
1053
+ async function ${P}load(stringTable, extraEnv) {
1054
+ if (typeof WebAssembly === "undefined" || typeof WebAssembly.Tag !== "function") {
1055
+ throw new Error("GoFront: this runtime lacks the WebAssembly GC / exception support required by app.wasm");
1056
+ }
1057
+ const tag = new WebAssembly.Tag({ "parameters": ["externref"] });
1058
+ const imports = ${P}imports(stringTable, extraEnv, tag, (s) => console.log(s));
1059
+ // Panics arrive as plain JS Errors thrown by env.panic (also from the
1060
+ // start function while package-level initializers run), so exports are
1061
+ // returned raw: no try/catch wrapper, which keeps JS→wasm calls inlinable.
1062
+ const result = await ${P}instantiate(imports);
1063
+ return result.instance.exports;
1064
+ }
1065
+ // Go int/int64 cross the boundary as f64 (exact within the safe-integer range).
1066
+ const ${P}i64in = (v) => (typeof v === "bigint" ? Number(v) : +v);
1067
+ const ${P}i64out = (v) => {
1068
+ if (v > ${P}MAX || v < -${P}MAX) throw new RangeError("GoFront: int64 value " + v + " exceeds the safe JS integer range");
1069
+ return v;
1070
+ };
1071
+ const ${P}u64out = (v) => {
1072
+ if (v < 0 || v > ${P}MAX) throw new RangeError("GoFront: uint64 value exceeds the safe JS integer range");
1073
+ return v;
1074
+ };
1075
+ const ${P}strin = (v) => (v == null ? "" : String(v));
1076
+ const ${P}href = (h) => (h == null ? null : h.__ref);
1077
+ const ${P}NIL_DEREF_PATTERNS = [
1078
+ "dereferencing a null pointer", // V8
1079
+ "dereferencing null pointer", // SpiderMonkey
1080
+ "null pointer dereference", // SpiderMonkey
1081
+ "null dereference", // JavaScriptCore
1082
+ ];
1083
+ function ${P}mapTrap(e) {
1084
+ if (typeof WebAssembly !== "undefined" && e instanceof WebAssembly.RuntimeError) {
1085
+ const msg = e.message || "";
1086
+ for (const p of ${P}NIL_DEREF_PATTERNS) {
1087
+ if (msg.includes(p)) {
1088
+ return new Error("runtime error: invalid memory address or nil pointer dereference");
1089
+ }
1090
+ }
1091
+ }
1092
+ return e;
1093
+ }
1094
+ // Live index view over a wasm array/slice (reads and writes go through to wasm).
1095
+ function ${P}idxview(n, getAt, setAt) {
1096
+ const inRange = (k) => { if (typeof k !== "string") return -1; const i = +k; return i === (i | 0) && i >= 0 && i < n ? i : -1; };
1097
+ return new Proxy(new Array(n), {
1098
+ "get": (t, k, r) => { const i = inRange(k); return i < 0 ? Reflect.get(t, k, r) : getAt(i); },
1099
+ "set": (t, k, v, r) => { const i = inRange(k); if (i < 0) return Reflect.set(t, k, v, r); setAt(i, v); return true; },
1100
+ "has": (t, k) => inRange(k) >= 0 || Reflect.has(t, k),
1101
+ "getOwnPropertyDescriptor": (t, k) => {
1102
+ const i = inRange(k);
1103
+ if (i < 0) return Reflect.getOwnPropertyDescriptor(t, k);
1104
+ return { "value": getAt(i), "writable": true, "enumerable": true, "configurable": true };
1105
+ },
1106
+ "ownKeys": (t) => { const keys = []; for (let i = 0; i < n; i++) keys.push(String(i)); keys.push("length"); return keys; },
1107
+ });
1108
+ }`;
1109
+
1110
+ // Generates the JS facade for the linked wasm module.
1111
+ export function generateFacade(
1112
+ meta,
1113
+ { stringTable = [], callMain = false } = {},
1114
+ ) {
1115
+ const needs = meta._needs ?? collectNeeds(meta);
1116
+ const out = [WASM_LOADER_JS];
1117
+
1118
+ // JS callback imports (registered before instantiation; bodies run after).
1119
+ out.push(`const ${P}env = {};`);
1120
+ for (const [key, desc] of needs.funcs) {
1121
+ const names = desc.params.map((_, i) => `a${i}`);
1122
+ const call = `fn(${desc.params.map((d, i) => outExpr(d, names[i])).join(", ")})`;
1123
+ let body;
1124
+ if (desc.returns.length === 0) body = `${call};`;
1125
+ else if (desc.returns.length === 1)
1126
+ body = `return ${inExpr(desc.returns[0], call)};`;
1127
+ else
1128
+ body = `const __r = ${call}; return [${desc.returns
1129
+ .map((d, i) => inExpr(d, `__r[${i}]`))
1130
+ .join(", ")}];`;
1131
+ const sig = ["fn", ...names].join(", ");
1132
+ out.push(`${P}env["__invoke$${key}"] = (${sig}) => { ${body} };`);
1133
+ }
1134
+ out.push(
1135
+ `const __w = await ${P}load(${JSON.stringify(stringTable)}, ${P}env);`,
1136
+ );
1137
+
1138
+ for (const [name, { info, fields }] of needs.structs) {
1139
+ if (info.pkgTarget === "both") genBothStruct(out, name, fields);
1140
+ else {
1141
+ const methods = meta.structByName.get(name)?.methods ?? [];
1142
+ genHandleStruct(out, name, fields, methods);
1143
+ }
1144
+ }
1145
+
1146
+ for (const [key, desc] of needs.slices) {
1147
+ out.push(`function ${P}slin_${key}(arr) {
1148
+ if (arr == null) return __w.__slice_nil_${key}();
1149
+ const n = arr.length;
1150
+ const s = __w.__slice_new_${key}(n);
1151
+ for (let i = 0; i < n; i++) __w.__slice_set_${key}(s, i, ${inExpr(desc.elem, "arr[i]")});
1152
+ return s;
1153
+ }
1154
+ function ${P}slout_${key}(s) {
1155
+ if (s == null || __w.__slice_is_nil_${key}(s)) return null;
1156
+ const n = __w.__slice_len_${key}(s);
1157
+ const out = new Array(n);
1158
+ for (let i = 0; i < n; i++) out[i] = ${outExpr(desc.elem, `__w.__slice_get_${key}(s, i)`)};
1159
+ return out;
1160
+ }
1161
+ function ${P}slview_${key}(s) {
1162
+ if (s == null || __w.__slice_is_nil_${key}(s)) return null;
1163
+ return ${P}idxview(__w.__slice_len_${key}(s), (i) => ${viewExpr(desc.elem, `__w.__slice_get_${key}(s, i)`)}, (i, v) => __w.__slice_set_${key}(s, i, ${inExpr(desc.elem, "v")}));
1164
+ }`);
1165
+ }
1166
+ for (const [key, desc] of needs.arrays) {
1167
+ out.push(`function ${P}arrzero_${key}(n) {
1168
+ const out = new Array(n);
1169
+ for (let i = 0; i < n; i++) out[i] = ${zeroExpr(desc.elem)};
1170
+ return out;
1171
+ }
1172
+ function ${P}arrin_${key}(arr) {
1173
+ if (arr == null) return null;
1174
+ const n = arr.length;
1175
+ const a = __w.__array_new_${key}(n);
1176
+ for (let i = 0; i < n; i++) __w.__array_set_${key}(a, i, ${inExpr(desc.elem, "arr[i]")});
1177
+ return a;
1178
+ }
1179
+ function ${P}arrout_${key}(a) {
1180
+ if (a == null) return null;
1181
+ const n = __w.__array_len_${key}(a);
1182
+ const out = new Array(n);
1183
+ for (let i = 0; i < n; i++) out[i] = ${outExpr(desc.elem, `__w.__array_get_${key}(a, i)`)};
1184
+ return out;
1185
+ }
1186
+ function ${P}arrview_${key}(a) {
1187
+ if (a == null) return null;
1188
+ return ${P}idxview(__w.__array_len_${key}(a), (i) => ${viewExpr(desc.elem, `__w.__array_get_${key}(a, i)`)}, (i, v) => __w.__array_set_${key}(a, i, ${inExpr(desc.elem, "v")}));
1189
+ }`);
1190
+ }
1191
+
1192
+ for (const [key, desc] of needs.funcs) {
1193
+ const { names, body } = genFuncBody(
1194
+ `__call_fn$${key}`,
1195
+ desc.params,
1196
+ desc.returns,
1197
+ ["c"],
1198
+ );
1199
+ out.push(`const ${P}fnmap_${key} = new WeakMap();
1200
+ function ${P}fnin_${key}(fn) {
1201
+ if (fn == null) return null;
1202
+ let c = ${P}fnmap_${key}.get(fn);
1203
+ if (!c) { c = __w.__wrap_fn$${key}(fn); ${P}fnmap_${key}.set(fn, c); }
1204
+ return c;
1205
+ }
1206
+ function ${P}fnout_${key}(c) {
1207
+ if (c == null) return null;
1208
+ return (${names.join(", ")}) => {
1209
+ ${indent(body, "\t")}
1210
+ };
1211
+ }`);
1212
+ }
1213
+
1214
+ for (const c of meta.consts) out.push(`const ${c.name} = ${c.value};`);
1215
+
1216
+ for (const f of meta.funcs) {
1217
+ const { names, body } = genFuncBody(
1218
+ f.callName ?? f.exportName,
1219
+ f.params,
1220
+ f.returns,
1221
+ );
1222
+ out.push(`function ${f.name}(${names.join(", ")}) {\n${body}\n}`);
1223
+ }
1224
+
1225
+ if (callMain) out.push("__w.main();");
1226
+ return bindExports(out.join("\n"));
1227
+ }
1228
+
1229
+ // Rewrites `__w.name(...)` into calls through module-level `const` bindings.
1230
+ // V8 only inlines a JS→wasm call (and so passes f32/f64 arguments unboxed)
1231
+ // when the callee is a compile-time constant; a property load off the exports
1232
+ // object is not, and every float argument then costs a 16-byte HeapNumber.
1233
+ function bindExports(src) {
1234
+ const loadLine = "const __w = await ";
1235
+ const at = src.indexOf(loadLine);
1236
+ if (at < 0) return src;
1237
+ const eol = src.indexOf("\n", at);
1238
+ const head = src.slice(0, eol + 1);
1239
+ let tail = src.slice(eol + 1);
1240
+ const names = new Set();
1241
+ tail = tail.replace(/__w\.([A-Za-z_$][\w$]*)/g, (_, n) => {
1242
+ names.add(n);
1243
+ return `__w$${n}`;
1244
+ });
1245
+ const binds = [...names].map((n) => `const __w$${n} = __w.${n};`).join("\n");
1246
+ return `${head}${binds}\n${tail}`;
1247
+ }
1248
+
1249
+ function genBothStruct(out, name, fields) {
1250
+ const toArgs = fields.map((f) => {
1251
+ const v = `o.${f.name}`;
1252
+ if (isBothStruct(f.desc))
1253
+ return `${P}to_${f.desc.name}(${v}, ${f.desc.ptr ? "false" : "true"}, m)`;
1254
+ return inExpr(f.desc, v);
1255
+ });
1256
+ const backLines = fields.map((f) => {
1257
+ const got = `__w.__get_${name}_${f.name}(r)`;
1258
+ if (isBothStruct(f.desc))
1259
+ return `\t${P}back_${f.desc.name}(${got}, o.${f.name});`;
1260
+ return `\to.${f.name} = ${outExpr(f.desc, got)};`;
1261
+ });
1262
+ const fromArgs = fields.map((f) =>
1263
+ outExpr(f.desc, `__w.__get_${name}_${f.name}(r)`),
1264
+ );
1265
+ const viewProps = fields.map((f) => {
1266
+ const got = `__w.__get_${name}_${f.name}(this.__ref)`;
1267
+ return `"${f.name}": { "enumerable": true, "get"() { return ${viewExpr(f.desc, got)}; }, "set"(v) { __w.__set_${name}_${f.name}(this.__ref, ${inExpr(f.desc, "v")}); } }`;
1268
+ });
1269
+ out.push(`function ${P}to_${name}(o, isValue, m) {
1270
+ if (o == null) return isValue ? __w.__zero_${name}() : null;
1271
+ if (m) { const hit = m.get(o); if (hit) return hit; }
1272
+ const r = __w.__new_${name}(${toArgs.join(", ")});
1273
+ if (m) m.set(o, r);
1274
+ return r;
1275
+ }
1276
+ function ${P}from_${name}(r) {
1277
+ if (r == null) return null;
1278
+ return new ${name}(${fromArgs.join(", ")});
1279
+ }
1280
+ // Live views share one prototype and are cached per wasm object so hot paths
1281
+ // (\`h.Pos.X = 1\` in a frame loop) do not allocate a wrapper per access. The
1282
+ // prototype is built on first use: the facade may precede the \`both\`
1283
+ // package's own JS (and so the \`${name}\` class) in the bundle.
1284
+ let ${P}vp_${name} = null;
1285
+ const ${P}vc_${name} = new WeakMap();
1286
+ function ${P}view_${name}(r) {
1287
+ if (r == null) return null;
1288
+ let o = ${P}vc_${name}.get(r);
1289
+ if (!o) {
1290
+ if (${P}vp_${name} === null) ${P}vp_${name} = Object.create(${name}.prototype, { ${viewProps.join(", ")} });
1291
+ o = Object.create(${P}vp_${name}); o.__ref = r; ${P}vc_${name}.set(r, o);
1292
+ }
1293
+ return o;
1294
+ }
1295
+ function ${P}back_${name}(r, o) {
1296
+ if (r == null || o == null) return;
1297
+ ${backLines.join("\n")}
1298
+ }`);
1299
+ }
1300
+
1301
+ function genHandleStruct(out, name, fields, methods) {
1302
+ // `$`-suffixed params keep the mangler from renaming the matching getters.
1303
+ const ctorParams = fields.map((f) => `${f.name}$ = ${zeroExpr(f.desc)}`);
1304
+ const ctorArgs = fields.map((f) => inExpr(f.desc, `${f.name}$`));
1305
+ const lines = [
1306
+ `const ${P}h_${name} = new WeakMap();
1307
+ function ${P}hval_${name}(h) {
1308
+ if (h == null) return __w.__zero_${name}();
1309
+ return __w.__clone_${name}(h.__ref);
1310
+ }
1311
+ class ${name} {
1312
+ constructor(${ctorParams.join(", ")}) {
1313
+ this.__ref = __w.__new_${name}(${ctorArgs.join(", ")});
1314
+ ${P}h_${name}.set(this.__ref, this);
1315
+ }
1316
+ static __wrap(ref) {
1317
+ if (ref == null) return null;
1318
+ let h = ${P}h_${name}.get(ref);
1319
+ if (!h) { h = Object.create(${name}.prototype); h.__ref = ref; ${P}h_${name}.set(ref, h); }
1320
+ return h;
1321
+ }
1322
+ __clone() { return ${name}.__wrap(__w.__clone_${name}(this.__ref)); }`,
1323
+ ];
1324
+ for (const f of fields) {
1325
+ const got = `__w.__get_${name}_${f.name}(this.__ref)`;
1326
+ lines.push(`\tget ${f.name}() { return ${viewExpr(f.desc, got)}; }`);
1327
+ lines.push(
1328
+ `\tset ${f.name}(v) { __w.__set_${name}_${f.name}(this.__ref, ${inExpr(f.desc, "v")}); }`,
1329
+ );
1330
+ }
1331
+ for (const m of methods) {
1332
+ const recv = m.ptrRecv ? "this.__ref" : `__w.__clone_${name}(this.__ref)`;
1333
+ const { names, body } = genFuncBody(
1334
+ m.callName ?? m.exportName,
1335
+ m.params,
1336
+ m.returns,
1337
+ [recv],
1338
+ );
1339
+ lines.push(
1340
+ `\t${m.name}(${names.join(", ")}) {\n${indent(body, "\t")}\n\t}`,
1341
+ );
1342
+ }
1343
+ lines.push("}");
1344
+ out.push(lines.join("\n"));
1345
+ }