gofront 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -2,7 +2,7 @@
2
2
  // JS <-> WASM boundary: type classification, marshalling helpers (WASM IR)
3
3
  // and the generated JS facade that exposes a wasm package to JS callers.
4
4
  //
5
- // Boundary v1 rules (docs/v1.5.0/wasm-mvp-plan.md, Phase 5):
5
+ // Boundary v1 rules (plans/v1.5.0/wasm-mvp-plan.md, Phase 5):
6
6
  // - primitives: bool <-> i32, narrow ints <-> i32, int/int64 <-> f64 at the
7
7
  // boundary (i64 inside; exact for |v| <= 2^53, which the facade already
8
8
  // enforces, and keeps BigInt boxing off hot paths), floats direct,
@@ -31,24 +31,18 @@
31
31
  // module's exported linear memory; JS sees a zero-copy TypedArray over
32
32
  // `memory.buffer`. Only TypedArrays that already alias that memory (ones
33
33
  // handed out by this facade) may be passed back in.
34
- // - `*testing.T`: the harness's JS test object, passed as an opaque
35
- // externref (test methods are routed back to JS via `env.testing_*`).
36
34
 
35
+ import { BROWSER_GLOBALS } from "../../typechecker/types.js";
36
+ import { FIXED_FLOAT_JS } from "../shared/fixed-float.js";
37
37
  import {
38
- HELPER_BASE64,
39
- HELPER_DURATION,
40
- HELPER_ERROR,
41
- HELPER_HEX,
42
- HELPER_PATH_CLEAN,
43
- HELPER_SHA256,
44
- HELPER_SPRINTF,
45
- HELPER_STRCONV,
46
- } from "../js/runtime.js";
38
+ unicodeIsJs,
39
+ unicodeToLowerJs,
40
+ unicodeToUpperJs,
41
+ } from "../shared/host-unicode.js";
47
42
  import {
48
43
  anonIfaceName,
49
44
  getInterfaceMethodList,
50
45
  getSharedInfo,
51
- isTestingT,
52
46
  } from "./types.js";
53
47
 
54
48
  // ── Type classification ──────────────────────────────────────
@@ -85,8 +79,16 @@ function toRef(info) {
85
79
  return { kind: "ref", nullable: true, typeIndex: info.typeIndex };
86
80
  }
87
81
 
82
+ // The facade and its wasm export names build identifiers from a struct's name,
83
+ // so the dot of a stdlib type (`strings.Builder`) becomes `$`.
88
84
  function structDesc(info, ptr) {
89
- return { k: "struct", name: info.name, ptr, info, wType: toRef(info) };
85
+ return {
86
+ k: "struct",
87
+ name: info.name.replaceAll(".", "$"),
88
+ ptr,
89
+ info,
90
+ wType: toRef(info),
91
+ };
90
92
  }
91
93
 
92
94
  function funcSigFromAst(t) {
@@ -98,6 +100,10 @@ function funcSigFromAst(t) {
98
100
  ? t.returnType.types
99
101
  : [t.returnType]
100
102
  : [],
103
+ // `_variadic`: already desugared to a slice by variadic.js
104
+ variadic: Boolean(
105
+ t.params?.at(-1)?.variadic || t.params?.at(-1)?._variadic,
106
+ ),
101
107
  };
102
108
  }
103
109
 
@@ -105,17 +111,9 @@ function funcSigFromAst(t) {
105
111
  // Returns a descriptor `{ k, ... }`; `k === "unsupported"` carries `what`.
106
112
  function classifyType(goType, mod) {
107
113
  if (!goType) return { k: "void" };
108
- if (isTestingT(goType)) return { k: "extern", wType: "externref" };
109
114
 
110
115
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
111
116
  if (goType.name === "error") return { k: "error", wType: "anyref" };
112
- if (goType.name === "time.Duration" || goType.name === "Duration")
113
- return { k: "i64", unsigned: false, wType: "i64" };
114
- if (goType.name === "time.Time" || goType.name === "Time") {
115
- const info = mod.getStructType("time.Time");
116
- if (info)
117
- return { k: "time", name: "time.Time", info, wType: toRef(info) };
118
- }
119
117
  const info = mod.getStructType(goType.name);
120
118
  if (info) return structDesc(info, false);
121
119
  const resolved = mod.checker?.types?.get(goType.name);
@@ -148,10 +146,7 @@ function classifyType(goType, mod) {
148
146
  case "interface":
149
147
  return classifyInterface(t, mod);
150
148
  case "simd":
151
- return {
152
- k: "unsupported",
153
- what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
154
- };
149
+ return classifySimd(t, mod);
155
150
  default:
156
151
  return {
157
152
  k: "unsupported",
@@ -210,7 +205,12 @@ function classifyBasic(n) {
210
205
  if (I32_INTS.has(n))
211
206
  return { k: "i32", unsigned: UNSIGNED.has(n), wType: "i32" };
212
207
  if (I64_INTS.has(n))
213
- return { k: "i64", unsigned: UNSIGNED.has(n), wType: "i64" };
208
+ return {
209
+ k: "i64",
210
+ unsigned: UNSIGNED.has(n),
211
+ is64: n === "int64" || n === "uint64",
212
+ wType: "i64",
213
+ };
214
214
  const d = BASIC_DESCS[n];
215
215
  return d ? { ...d } : { k: "unsupported", what: `type '${n}'` };
216
216
  }
@@ -219,26 +219,31 @@ function classifyNamed(t, mod) {
219
219
  if (
220
220
  t.name === "simd.F32x4" ||
221
221
  t.name === "F32x4" ||
222
+ t.name === "simd.Mask32x4" ||
223
+ t.name === "Mask32x4" ||
222
224
  t.underlying?.kind === "simd"
223
225
  ) {
224
- return {
225
- k: "unsupported",
226
- what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
227
- };
226
+ return classifySimd(t.underlying ?? t, mod, t.name);
228
227
  }
229
228
  if (t.name === "error") return { k: "error", wType: "anyref" };
230
- if (t.name === "time.Duration")
231
- return { k: "i64", unsigned: false, wType: "i64" };
232
- if (t.name === "time.Time") {
233
- const info = mod.getStructType("time.Time");
234
- if (info) return { k: "time", name: "time.Time", info, wType: toRef(info) };
235
- }
236
229
  const info = mod.getStructType(t.name);
237
230
  if (info) return structDesc(info, false);
238
231
  if (t.underlying) return classifyType(t.underlying, mod);
239
232
  return { k: "unsupported", what: `named type '${t.name}'` };
240
233
  }
241
234
 
235
+ // F32x4 crosses as a copied 4-element Float32Array, Mask32x4 as a 4-element
236
+ // boolean array. Both travel through the wasm boundary as a GC array of the
237
+ // four i32 lane words (`arr`), converted by __v128_from/__v128_to.
238
+ function classifySimd(t, mod, name = "") {
239
+ const isMask = t.simd === "Mask32x4" || name.endsWith("Mask32x4");
240
+ const arr = classifyArray(
241
+ { kind: "array", elem: { kind: "basic", name: "int32" }, size: 4 },
242
+ mod,
243
+ );
244
+ return { k: isMask ? "mask4" : "f32x4", wType: "v128", arr };
245
+ }
246
+
242
247
  function classifyAnonStruct(t, mod) {
243
248
  const rawFields = [];
244
249
  if (t.fields instanceof Map) {
@@ -280,15 +285,23 @@ function classifyPointer(t, mod) {
280
285
  if (base.k === "unsupported") return base;
281
286
  if (base.k === "struct" && !base.ptr) return { ...base, ptr: true };
282
287
  if (base.k === "anon_struct" && !base.ptr) return { ...base, ptr: true };
283
- if (base.k === "time" && !base.ptr) return { ...base, ptr: true };
284
288
  const boxInfo = mod.getBoxType(t.base);
285
- const key = `${base.k}${base.unsigned ? "_u" : ""}_${boxInfo.typeIndex}`;
289
+ const ptrInfo = mod.getPtrType ? mod.getPtrType(t.base) : null;
290
+ const key = `${base.k}${base.unsigned ? "_u" : ""}${base.is64 ? "_64" : ""}_${boxInfo.typeIndex}`;
286
291
  return {
287
292
  k: "ptr_box",
288
293
  base,
289
294
  boxInfo,
295
+ ptrInfo,
290
296
  key,
291
- wType: { kind: "ref", nullable: true, typeIndex: boxInfo.typeIndex },
297
+ wType: ptrInfo
298
+ ? {
299
+ kind: "ref",
300
+ nullable: true,
301
+ typeIndex: ptrInfo.typeIndex,
302
+ typeName: ptrInfo.name,
303
+ }
304
+ : { kind: "ref", nullable: true, typeIndex: boxInfo.typeIndex },
292
305
  };
293
306
  }
294
307
 
@@ -352,7 +365,15 @@ function classifySlice(t, mod) {
352
365
  k: "slice",
353
366
  elem,
354
367
  sliceInfo,
355
- key: wTypeKey(sliceInfo.elemWType),
368
+ // []F32x4 and []Mask32x4 share a wasm slice type but cross differently,
369
+ // so they get separate helper keys.
370
+ key:
371
+ wTypeKey(sliceInfo.elemWType) +
372
+ (elem.k === "f32x4" || elem.k === "mask4"
373
+ ? `_${elem.k}`
374
+ : elem.is64
375
+ ? "_i64"
376
+ : ""),
356
377
  wType: { kind: "ref", nullable: true, typeIndex: sliceInfo.typeIndex },
357
378
  };
358
379
  }
@@ -372,7 +393,7 @@ function classifyArray(t, mod) {
372
393
  elem,
373
394
  size: arraySize(t),
374
395
  arrInfo,
375
- key: wTypeKey(arrInfo.elemWType),
396
+ key: wTypeKey(arrInfo.elemWType) + (elem.is64 ? "_i64" : ""),
376
397
  wType: { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex },
377
398
  };
378
399
  }
@@ -387,6 +408,9 @@ function classifySig(t, mod) {
387
408
  }
388
409
 
389
410
  function classifyFunc(t, mod) {
411
+ // A variadic func value is a slice-taking closure in wasm but a rest-param
412
+ // function in JS; no adapter yet, so it may not cross.
413
+ if (t.variadic) return { k: "unsupported", what: "variadic func value" };
390
414
  const sig = classifySig(t, mod);
391
415
  if (sig.k === "unsupported") return sig;
392
416
  const closureInfo = mod.getClosureType(t);
@@ -451,7 +475,7 @@ function classifyNamedInterface(t, mod) {
451
475
  const desc = {
452
476
  k: "iface",
453
477
  name: t.name,
454
- key: t.name,
478
+ key: t.name.replaceAll(".", "_"),
455
479
  wType: "anyref",
456
480
  methods: [],
457
481
  concrete: [],
@@ -544,19 +568,23 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
544
568
  for (const p of progs) {
545
569
  const pkgName = p.pkg?.name ?? "main";
546
570
  const pkgTarget = p.target ?? "wasm";
571
+ // The tests of a package run inside the module: only the generated
572
+ // driver crosses the boundary.
573
+ if (isTestSource(p._path ?? p._filename)) continue;
547
574
  for (const d of p.decls ?? []) {
548
575
  if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
549
576
  collectStructDecl(d, pkgName, pkgTarget, meta, mod);
550
577
  } else if (d.kind === "ConstDecl" && pkgTarget === "wasm") {
551
578
  collectConstDecl(d, pkgName, meta, errors);
552
579
  } else if (d.kind === "VarDecl" && pkgTarget === "wasm") {
553
- collectSharedVarDecl(d, meta, mod);
580
+ collectVarDecl(d, meta, mod);
554
581
  }
555
582
  }
556
583
  }
557
584
 
558
585
  for (const fn of funcDecls) {
559
586
  if (fn._isClosure || fn._pkgTarget !== "wasm") continue;
587
+ if (isTestSource(fn._srcFile) && fn.name !== TEST_DRIVER) continue;
560
588
  if (fn._isMethod) collectMethodMeta(fn, meta, errors, mod);
561
589
  else collectFuncMeta(fn, meta, errors, mod);
562
590
  }
@@ -565,12 +593,19 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
565
593
  return meta;
566
594
  }
567
595
 
596
+ // The driver function the test runner compiles in (src/cli/test-runner.js).
597
+ const TEST_DRIVER = "GofrontRunTests";
598
+
599
+ const isTestSource = (file) => Boolean(file?.endsWith("_test.go"));
600
+
568
601
  function collectStructDecl(d, pkgName, pkgTarget, meta, mod) {
569
- if (!isExported(d.name) || meta.structByName.has(d.name)) return;
602
+ if (meta.structByName.has(d.name)) return;
570
603
  const info = mod.getStructType(d.name);
571
604
  if (!info) return;
572
605
  const s = { name: d.name, pkgName, pkgTarget, info, methods: [] };
573
- meta.structs.push(s);
606
+ // An unexported struct has no class of its own, but its exported methods
607
+ // reach JS through the exported structs that embed it.
608
+ if (isExported(d.name)) meta.structs.push(s);
574
609
  meta.structByName.set(d.name, s);
575
610
  }
576
611
 
@@ -632,17 +667,24 @@ function checkDesc(desc, where, errors) {
632
667
  }
633
668
 
634
669
  // Exported `var Buf = shared.NewFloat32(n)` globals are exposed to JS as
635
- // TypedArray views (read once after instantiation; sizing is fixed).
636
- function collectSharedVarDecl(d, meta, mod) {
670
+ // TypedArray views (read once after instantiation; sizing is fixed). Other
671
+ // exported vars of a supported boundary type are read once when the module
672
+ // loads, after package initialization (`var Limit = 40` is `Limit` in the
673
+ // facade); a var of an unsupported type is left out.
674
+ function collectVarDecl(d, meta, mod) {
637
675
  for (const spec of d.decls ?? d.specs ?? [d]) {
638
676
  const names = spec.names ?? (spec.name ? [spec.name] : []);
639
677
  const values = spec.value ?? (spec.init ? [spec.init] : []);
640
678
  for (let i = 0; i < names.length; i++) {
641
679
  const name = names[i];
642
680
  if (!isExported(name)) continue;
643
- const goType = spec.type ?? values[i]?._type;
644
- if (!getSharedInfo(goType, mod.checker)) continue;
645
- meta.vars.push({ name, desc: classifyType(goType, mod) });
681
+ const goType =
682
+ spec.type ?? mod.resolveGlobal(name)?.goType ?? values[i]?._type;
683
+ const shared = Boolean(getSharedInfo(goType, mod.checker));
684
+ const desc = classifyType(goType, mod);
685
+ if (!shared && (!desc || desc.k === "unsupported" || desc.k === "void"))
686
+ continue;
687
+ meta.vars.push({ name, desc, shared });
646
688
  }
647
689
  }
648
690
  }
@@ -657,6 +699,7 @@ function collectMethodMeta(fn, meta, errors, mod) {
657
699
  exportName: fn._exportName,
658
700
  fnName: fn.name,
659
701
  ptrRecv: fn.params[0].type?.kind === "pointer",
702
+ variadic: Boolean(fn.params.at(-1)?._variadic),
660
703
  params: fn.params
661
704
  .slice(1)
662
705
  .map((p) => checkDesc(classifyType(p.type, mod), where, errors)),
@@ -673,6 +716,7 @@ function collectFuncMeta(fn, meta, errors, mod) {
673
716
  name: fn.name,
674
717
  exportName: fn.name,
675
718
  pkgName: fn._pkgName,
719
+ variadic: Boolean(fn.params?.at(-1)?._variadic),
676
720
  params: (fn.params ?? []).map((p) =>
677
721
  checkDesc(classifyType(p.type, mod), where, errors),
678
722
  ),
@@ -711,8 +755,10 @@ function visitNeed(desc, needs, errors, mod) {
711
755
  case "error":
712
756
  needs.error = true;
713
757
  return;
714
- case "time":
715
- needs.time = true;
758
+ case "f32x4":
759
+ case "mask4":
760
+ needs.simd = desc.arr;
761
+ visitNeed(desc.arr, needs, errors, mod);
716
762
  return;
717
763
  case "slice":
718
764
  case "array": {
@@ -790,7 +836,7 @@ function collectNeeds(meta) {
790
836
  funcs: new Map(), // key -> desc
791
837
  ifaces: new Map(), // interface name -> desc
792
838
  error: false,
793
- time: false,
839
+ simd: null, // array desc carrying vectors across the boundary
794
840
  shared: false, // any shared buffer on the surface
795
841
  };
796
842
  const errors = [];
@@ -817,11 +863,20 @@ export function scanBoundaryImports(mod, meta) {
817
863
  const needs = collectNeeds(meta);
818
864
  meta._needs = needs;
819
865
  for (const [key, desc] of needs.funcs) {
866
+ const paramDescs = desc.params ?? [];
867
+ const returnDescs = desc.returns ?? [];
820
868
  mod.getOrAddFuncImport(
821
869
  "env",
822
870
  `__invoke$${key}`,
823
- ["externref", ...desc.closureInfo.paramWTypes.map(extType)],
824
- desc.closureInfo.returnWTypes.map(extType),
871
+ [
872
+ "externref",
873
+ ...desc.closureInfo.paramWTypes.map((w, i) =>
874
+ extType(w, paramDescs[i]?.is64),
875
+ ),
876
+ ],
877
+ desc.closureInfo.returnWTypes.map((w, i) =>
878
+ extType(w, returnDescs[i]?.is64),
879
+ ),
825
880
  );
826
881
  }
827
882
  }
@@ -835,17 +890,25 @@ export function scanBoundaryImports(mod, meta) {
835
890
  function isGcRef(wType) {
836
891
  return wType === "anyref" || (typeof wType === "object" && wType !== null);
837
892
  }
838
- function extType(wType) {
839
- if (wType === "i64") return "f64";
893
+ // A v128 crosses as an externref holding a GC array of its four i32 lanes;
894
+ // the converting helpers are registered first thing in addBoundaryHelpers.
895
+ const v128Conv = { from: null, to: null };
896
+
897
+ function extType(wType, is64 = false) {
898
+ if (wType === "v128") return "externref";
899
+ if (wType === "i64") return is64 ? "i64" : "f64";
840
900
  if (isGcRef(wType)) return "externref";
841
901
  return wType;
842
902
  }
843
- function needsConv(wTypes) {
844
- return wTypes.some((w) => w === "i64" || isGcRef(w));
903
+ function needsConv(wTypes, descs = []) {
904
+ return wTypes.some(
905
+ (w, i) => (w === "i64" && !descs[i]?.is64) || w === "v128" || isGcRef(w),
906
+ );
845
907
  }
846
908
  // Instructions converting the value on the stack from its boundary form.
847
- function fromExt(wType) {
848
- if (wType === "i64") return [{ op: "i64.trunc_sat_f64_s" }];
909
+ function fromExt(wType, is64 = false) {
910
+ if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.from }];
911
+ if (wType === "i64") return is64 ? [] : [{ op: "i64.trunc_sat_f64_s" }];
849
912
  if (wType === "anyref") return [{ op: "any.convert_extern" }];
850
913
  if (isGcRef(wType)) {
851
914
  const out = [{ op: "any.convert_extern" }];
@@ -856,25 +919,31 @@ function fromExt(wType) {
856
919
  return [];
857
920
  }
858
921
  // Instructions converting the value on the stack to its boundary form.
859
- function toExt(wType) {
860
- if (wType === "i64") return [{ op: "f64.convert_i64_s" }];
922
+ function toExt(wType, is64 = false) {
923
+ if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.to }];
924
+ if (wType === "i64") return is64 ? [] : [{ op: "f64.convert_i64_s" }];
861
925
  if (isGcRef(wType)) return [{ op: "extern.convert_any" }];
862
926
  return [];
863
927
  }
864
928
  // Rewrites the values on top of the stack (types `wTypes`) with `conv`,
865
929
  // spilling through locals starting at `localBase` when more than one.
866
- function convertResults(wTypes, conv, localBase) {
867
- if (wTypes.length === 0 || !needsConv(wTypes)) return [];
868
- if (wTypes.length === 1) return conv(wTypes[0]);
930
+ function convertResults(wTypes, conv, localBase, descs = []) {
931
+ if (wTypes.length === 0 || !needsConv(wTypes, descs)) return [];
932
+ if (wTypes.length === 1) return conv(wTypes[0], descs[0]?.is64);
869
933
  const out = [];
870
934
  for (let i = wTypes.length - 1; i >= 0; i--)
871
935
  out.push({ op: "local.set", index: localBase + i });
872
936
  for (let i = 0; i < wTypes.length; i++)
873
- out.push({ op: "local.get", index: localBase + i }, ...conv(wTypes[i]));
937
+ out.push(
938
+ { op: "local.get", index: localBase + i },
939
+ ...conv(wTypes[i], descs[i]?.is64),
940
+ );
874
941
  return out;
875
942
  }
876
943
 
877
944
  function zeroInstr(wType) {
945
+ if (wType === "v128")
946
+ return { op: "v128.const", bytes: new Array(16).fill(0) };
878
947
  if (wType === "i64") return { op: "i64.const", value: 0n };
879
948
  if (wType === "f32" || wType === "f64")
880
949
  return { op: `${wType}.const`, value: 0 };
@@ -926,20 +995,61 @@ export function addBoundaryHelpers(mod, meta) {
926
995
  return idx;
927
996
  };
928
997
  const get = (i) => ({ op: "local.get", index: i });
998
+ if (needs.simd) {
999
+ const { arrInfo } = needs.simd;
1000
+ const arrRef = needs.simd.wType;
1001
+ v128Conv.to = addFunc(
1002
+ "__v128_to",
1003
+ ["v128"],
1004
+ ["externref"],
1005
+ [
1006
+ ...[0, 1, 2, 3].flatMap((lane) => [
1007
+ get(0),
1008
+ { op: "i32x4.extract_lane", lane },
1009
+ ]),
1010
+ { op: "array.new_fixed", typeIndex: arrInfo.typeIndex, size: 4 },
1011
+ { op: "extern.convert_any" },
1012
+ ],
1013
+ false,
1014
+ );
1015
+ v128Conv.from = addFunc(
1016
+ "__v128_from",
1017
+ ["externref"],
1018
+ ["v128"],
1019
+ [
1020
+ get(0),
1021
+ { op: "any.convert_extern" },
1022
+ { op: "ref.cast_null", typeIndex: arrInfo.typeIndex },
1023
+ { op: "local.set", index: 1 },
1024
+ get(1),
1025
+ { op: "i32.const", value: 0 },
1026
+ { op: "array.get", typeIndex: arrInfo.typeIndex },
1027
+ { op: "i32x4.splat" },
1028
+ ...[1, 2, 3].flatMap((lane) => [
1029
+ get(1),
1030
+ { op: "i32.const", value: lane },
1031
+ { op: "array.get", typeIndex: arrInfo.typeIndex },
1032
+ { op: "i32x4.replace_lane", lane },
1033
+ ]),
1034
+ ],
1035
+ false,
1036
+ [arrRef],
1037
+ );
1038
+ }
929
1039
  // Parameter `i` converted from its boundary form.
930
- const getIn = (i, wType) => [get(i), ...fromExt(wType)];
1040
+ const getIn = (i, wType, is64 = false) => [get(i), ...fromExt(wType, is64)];
931
1041
  const EXT = "externref";
932
1042
 
933
- for (const [name, { info }] of needs.structs) {
1043
+ for (const [name, { info, fields }] of needs.structs) {
934
1044
  const ref = toRef(info);
935
1045
  const ti = info.typeIndex;
936
1046
  const fieldWTypes = info.fields.map((f) => f.wType);
937
1047
  addFunc(
938
1048
  `__new_${name}`,
939
- fieldWTypes.map(extType),
1049
+ fieldWTypes.map((w, i) => extType(w, fields[i]?.desc?.is64)),
940
1050
  [EXT],
941
1051
  [
942
- ...fieldWTypes.flatMap((w, i) => getIn(i, w)),
1052
+ ...fieldWTypes.flatMap((w, i) => getIn(i, w, fields[i]?.desc?.is64)),
943
1053
  { op: "struct.new", typeIndex: ti },
944
1054
  ...toExt(ref),
945
1055
  ],
@@ -969,23 +1079,24 @@ export function addBoundaryHelpers(mod, meta) {
969
1079
  );
970
1080
  for (let i = 0; i < info.fields.length; i++) {
971
1081
  const f = info.fields[i];
1082
+ const is64 = Boolean(fields[i]?.desc?.is64);
972
1083
  addFunc(
973
1084
  `__get_${name}_${f.name}`,
974
1085
  [EXT],
975
- [extType(f.wType)],
1086
+ [extType(f.wType, is64)],
976
1087
  [
977
1088
  ...getIn(0, ref),
978
1089
  { op: "struct.get", typeIndex: ti, fieldIndex: i },
979
- ...toExt(f.wType),
1090
+ ...toExt(f.wType, is64),
980
1091
  ],
981
1092
  );
982
1093
  addFunc(
983
1094
  `__set_${name}_${f.name}`,
984
- [EXT, extType(f.wType)],
1095
+ [EXT, extType(f.wType, is64)],
985
1096
  [],
986
1097
  [
987
1098
  ...getIn(0, ref),
988
- ...getIn(1, f.wType),
1099
+ ...getIn(1, f.wType, is64),
989
1100
  { op: "struct.set", typeIndex: ti, fieldIndex: i },
990
1101
  ],
991
1102
  );
@@ -1011,25 +1122,26 @@ export function addBoundaryHelpers(mod, meta) {
1011
1122
  ["i32"],
1012
1123
  [...getIn(0, ref), { op: "array.len" }],
1013
1124
  );
1125
+ const is64 = Boolean(desc.elem?.is64);
1014
1126
  addFunc(
1015
1127
  `__array_get_${key}`,
1016
1128
  [EXT, "i32"],
1017
- [extType(arrInfo.elemWType)],
1129
+ [extType(arrInfo.elemWType, is64)],
1018
1130
  [
1019
1131
  ...getIn(0, ref),
1020
1132
  get(1),
1021
1133
  { op: "array.get", typeIndex: arrInfo.typeIndex },
1022
- ...toExt(arrInfo.elemWType),
1134
+ ...toExt(arrInfo.elemWType, is64),
1023
1135
  ],
1024
1136
  );
1025
1137
  addFunc(
1026
1138
  `__array_set_${key}`,
1027
- [EXT, "i32", extType(arrInfo.elemWType)],
1139
+ [EXT, "i32", extType(arrInfo.elemWType, is64)],
1028
1140
  [],
1029
1141
  [
1030
1142
  ...getIn(0, ref),
1031
1143
  get(1),
1032
- ...getIn(2, arrInfo.elemWType),
1144
+ ...getIn(2, arrInfo.elemWType, is64),
1033
1145
  { op: "array.set", typeIndex: arrInfo.typeIndex },
1034
1146
  ],
1035
1147
  );
@@ -1040,6 +1152,7 @@ export function addBoundaryHelpers(mod, meta) {
1040
1152
  const ref = desc.wType;
1041
1153
  const arrTI = sliceInfo.arrInfo.typeIndex;
1042
1154
  const sTI = sliceInfo.typeIndex;
1155
+ const is64 = Boolean(desc.elem?.is64);
1043
1156
  const elemAddr = [
1044
1157
  ...getIn(0, ref),
1045
1158
  { op: "struct.get", typeIndex: sTI, fieldIndex: 0 },
@@ -1071,11 +1184,11 @@ export function addBoundaryHelpers(mod, meta) {
1071
1184
  addFunc(
1072
1185
  `__slice_get_${key}`,
1073
1186
  [EXT, "i32"],
1074
- [extType(sliceInfo.elemWType)],
1187
+ [extType(sliceInfo.elemWType, is64)],
1075
1188
  [
1076
1189
  ...elemAddr,
1077
1190
  { op: "array.get", typeIndex: arrTI },
1078
- ...toExt(sliceInfo.elemWType),
1191
+ ...toExt(sliceInfo.elemWType, is64),
1079
1192
  ],
1080
1193
  );
1081
1194
  addFunc(
@@ -1096,11 +1209,11 @@ export function addBoundaryHelpers(mod, meta) {
1096
1209
  );
1097
1210
  addFunc(
1098
1211
  `__slice_set_${key}`,
1099
- [EXT, "i32", extType(sliceInfo.elemWType)],
1212
+ [EXT, "i32", extType(sliceInfo.elemWType, is64)],
1100
1213
  [],
1101
1214
  [
1102
1215
  ...elemAddr,
1103
- ...getIn(2, sliceInfo.elemWType),
1216
+ ...getIn(2, sliceInfo.elemWType, is64),
1104
1217
  { op: "array.set", typeIndex: arrTI },
1105
1218
  ],
1106
1219
  );
@@ -1110,6 +1223,8 @@ export function addBoundaryHelpers(mod, meta) {
1110
1223
  const ci = desc.closureInfo;
1111
1224
  const invokeIdx = mod.importCache.get(`env.__invoke$${key}`);
1112
1225
  const nParams = ci.paramWTypes.length;
1226
+ const paramDescs = desc.params ?? [];
1227
+ const returnDescs = desc.returns ?? [];
1113
1228
  // Thunk: wasm closure body calling the JS callback (boundary forms out, back in).
1114
1229
  const thunkIdx = addFunc(
1115
1230
  `__jsthunk$${key}`,
@@ -1118,12 +1233,17 @@ export function addBoundaryHelpers(mod, meta) {
1118
1233
  [
1119
1234
  get(0),
1120
1235
  { op: "extern.convert_any" },
1121
- ...ci.paramWTypes.flatMap((w, i) => [get(i + 1), ...toExt(w)]),
1236
+ ...ci.paramWTypes.flatMap((w, i) => [
1237
+ get(i + 1),
1238
+ ...toExt(w, paramDescs[i]?.is64),
1239
+ ]),
1122
1240
  { op: "call", funcIndex: invokeIdx },
1123
- ...convertResults(ci.returnWTypes, fromExt, nParams + 1),
1241
+ ...convertResults(ci.returnWTypes, fromExt, nParams + 1, returnDescs),
1124
1242
  ],
1125
1243
  false,
1126
- ci.returnWTypes.length > 1 ? ci.returnWTypes.map(extType) : [],
1244
+ ci.returnWTypes.length > 1
1245
+ ? ci.returnWTypes.map((w, i) => extType(w, returnDescs[i]?.is64))
1246
+ : [],
1127
1247
  );
1128
1248
  mod.addRefFuncElement(thunkIdx);
1129
1249
  addFunc(
@@ -1141,16 +1261,18 @@ export function addBoundaryHelpers(mod, meta) {
1141
1261
  // JS calling a wasm closure (boundary forms in, results out).
1142
1262
  addFunc(
1143
1263
  `__call_fn$${key}`,
1144
- [EXT, ...ci.paramWTypes.map(extType)],
1145
- ci.returnWTypes.map(extType),
1264
+ [EXT, ...ci.paramWTypes.map((w, i) => extType(w, paramDescs[i]?.is64))],
1265
+ ci.returnWTypes.map((w, i) => extType(w, returnDescs[i]?.is64)),
1146
1266
  [
1147
1267
  ...getIn(0, desc.wType),
1148
1268
  { op: "struct.get", typeIndex: ci.typeIndex, fieldIndex: 1 },
1149
- ...ci.paramWTypes.flatMap((w, i) => getIn(i + 1, w)),
1269
+ ...ci.paramWTypes.flatMap((w, i) =>
1270
+ getIn(i + 1, w, paramDescs[i]?.is64),
1271
+ ),
1150
1272
  ...getIn(0, desc.wType),
1151
1273
  { op: "struct.get", typeIndex: ci.typeIndex, fieldIndex: 0 },
1152
1274
  { op: "call_ref", typeIndex: ci.funcTypeIndex },
1153
- ...convertResults(ci.returnWTypes, toExt, nParams + 1),
1275
+ ...convertResults(ci.returnWTypes, toExt, nParams + 1, returnDescs),
1154
1276
  ],
1155
1277
  true,
1156
1278
  ci.returnWTypes.length > 1 ? ci.returnWTypes : [],
@@ -1168,16 +1290,20 @@ export function addBoundaryHelpers(mod, meta) {
1168
1290
  if (sig?.form !== "func") return;
1169
1291
  const paramW = sig.params;
1170
1292
  const resultW = sig.results;
1171
- if (!needsConv(paramW) && !needsConv(resultW)) return;
1172
- entry.callName = `__x_${entry.exportName}`;
1293
+ const paramDescs = entry.params ?? [];
1294
+ const resultDescs = entry.returns ?? [];
1295
+ if (!needsConv(paramW, paramDescs) && !needsConv(resultW, resultDescs))
1296
+ return;
1297
+ // Exported names become JS identifiers: no dot of a qualified type.
1298
+ entry.callName = `__x_${entry.exportName}`.replaceAll(".", "_");
1173
1299
  addFunc(
1174
1300
  entry.callName,
1175
- paramW.map(extType),
1176
- resultW.map(extType),
1301
+ paramW.map((w, i) => extType(w, paramDescs[i]?.is64)),
1302
+ resultW.map((w, i) => extType(w, resultDescs[i]?.is64)),
1177
1303
  [
1178
- ...paramW.flatMap((w, i) => getIn(i, w)),
1304
+ ...paramW.flatMap((w, i) => getIn(i, w, paramDescs[i]?.is64)),
1179
1305
  { op: "call", funcIndex: fnIdx },
1180
- ...convertResults(resultW, toExt, paramW.length),
1306
+ ...convertResults(resultW, toExt, paramW.length, resultDescs),
1181
1307
  ],
1182
1308
  true,
1183
1309
  resultW.length > 1 ? resultW : [],
@@ -1210,7 +1336,7 @@ export function addBoundaryHelpers(mod, meta) {
1210
1336
  [get(0), get(1), { op: "struct.new", typeIndex }, ...toExt(wType)],
1211
1337
  );
1212
1338
  for (const v of meta.vars ?? []) {
1213
- const g = mod.resolveGlobal(v.name);
1339
+ const g = v.shared ? mod.resolveGlobal(v.name) : null;
1214
1340
  if (!g) continue;
1215
1341
  addFunc(
1216
1342
  `__var_${v.name}`,
@@ -1222,16 +1348,16 @@ export function addBoundaryHelpers(mod, meta) {
1222
1348
  }
1223
1349
 
1224
1350
  for (const [name, desc] of needs.anonStructs) {
1225
- const { info } = desc;
1351
+ const { info, fields } = desc;
1226
1352
  const ref = desc.wType;
1227
1353
  const ti = info.typeIndex;
1228
1354
  const fieldWTypes = info.fields.map((f) => f.wType);
1229
1355
  addFunc(
1230
1356
  `__new_${name}`,
1231
- fieldWTypes.map(extType),
1357
+ fieldWTypes.map((w, i) => extType(w, fields[i]?.desc?.is64)),
1232
1358
  [EXT],
1233
1359
  [
1234
- ...fieldWTypes.flatMap((w, i) => getIn(i, w)),
1360
+ ...fieldWTypes.flatMap((w, i) => getIn(i, w, fields[i]?.desc?.is64)),
1235
1361
  { op: "struct.new", typeIndex: ti },
1236
1362
  ...toExt(ref),
1237
1363
  ],
@@ -1248,23 +1374,24 @@ export function addBoundaryHelpers(mod, meta) {
1248
1374
  );
1249
1375
  for (let i = 0; i < info.fields.length; i++) {
1250
1376
  const f = info.fields[i];
1377
+ const is64 = Boolean(fields[i]?.desc?.is64);
1251
1378
  addFunc(
1252
1379
  `__get_${name}_${f.name}`,
1253
1380
  [EXT],
1254
- [extType(f.wType)],
1381
+ [extType(f.wType, is64)],
1255
1382
  [
1256
1383
  ...getIn(0, ref),
1257
1384
  { op: "struct.get", typeIndex: ti, fieldIndex: i },
1258
- ...toExt(f.wType),
1385
+ ...toExt(f.wType, is64),
1259
1386
  ],
1260
1387
  );
1261
1388
  addFunc(
1262
1389
  `__set_${name}_${f.name}`,
1263
- [EXT, extType(f.wType)],
1390
+ [EXT, extType(f.wType, is64)],
1264
1391
  [],
1265
1392
  [
1266
1393
  ...getIn(0, ref),
1267
- ...getIn(1, f.wType),
1394
+ ...getIn(1, f.wType, is64),
1268
1395
  { op: "struct.set", typeIndex: ti, fieldIndex: i },
1269
1396
  ],
1270
1397
  );
@@ -1272,40 +1399,52 @@ export function addBoundaryHelpers(mod, meta) {
1272
1399
  }
1273
1400
 
1274
1401
  for (const [key, desc] of needs.boxes) {
1275
- const { boxInfo } = desc;
1402
+ const { boxInfo, base } = desc;
1276
1403
  const ref = desc.wType;
1277
1404
  const ti = boxInfo.typeIndex;
1278
1405
  const w = boxInfo.wType;
1406
+ const is64 = Boolean(base?.is64);
1279
1407
  addFunc(
1280
1408
  `__box_new_${key}`,
1281
- [extType(w)],
1409
+ [extType(w, is64)],
1282
1410
  [EXT],
1283
- [...getIn(0, w), { op: "struct.new", typeIndex: ti }, ...toExt(ref)],
1411
+ [
1412
+ ...getIn(0, w, is64),
1413
+ { op: "struct.new", typeIndex: ti },
1414
+ ...toExt(ref),
1415
+ ],
1284
1416
  );
1417
+ const cast = {
1418
+ op: "ref.cast",
1419
+ typeIndex: boxInfo.typeIndex,
1420
+ typeName: boxInfo.name,
1421
+ };
1285
1422
  addFunc(
1286
1423
  `__box_get_${key}`,
1287
1424
  [EXT],
1288
- [extType(w)],
1425
+ [extType(w, is64)],
1289
1426
  [
1290
1427
  ...getIn(0, ref),
1428
+ cast,
1291
1429
  { op: "struct.get", typeIndex: ti, fieldIndex: 0 },
1292
- ...toExt(w),
1430
+ ...toExt(w, is64),
1293
1431
  ],
1294
1432
  );
1295
1433
  addFunc(
1296
1434
  `__box_set_${key}`,
1297
- [EXT, extType(w)],
1435
+ [EXT, extType(w, is64)],
1298
1436
  [],
1299
1437
  [
1300
1438
  ...getIn(0, ref),
1301
- ...getIn(1, w),
1439
+ cast,
1440
+ ...getIn(1, w, is64),
1302
1441
  { op: "struct.set", typeIndex: ti, fieldIndex: 0 },
1303
1442
  ],
1304
1443
  );
1305
1444
  }
1306
1445
 
1307
1446
  for (const [key, desc] of needs.maps) {
1308
- const { mapInfo, keySliceDesc, valSliceDesc } = desc;
1447
+ const { mapInfo, keySliceDesc, valSliceDesc, keyDesc, valDesc } = desc;
1309
1448
  const makeIdx = mod.resolveFuncIndex(mapInfo.makeFuncName);
1310
1449
  addFunc(
1311
1450
  `__map_new_${key}`,
@@ -1316,12 +1455,16 @@ export function addBoundaryHelpers(mod, meta) {
1316
1455
  const setIdx = mod.resolveFuncIndex(mapInfo.setFuncName);
1317
1456
  addFunc(
1318
1457
  `__map_set_${key}`,
1319
- [EXT, extType(mapInfo.keyWType), extType(mapInfo.valWType)],
1458
+ [
1459
+ EXT,
1460
+ extType(mapInfo.keyWType, keyDesc?.is64),
1461
+ extType(mapInfo.valWType, valDesc?.is64),
1462
+ ],
1320
1463
  [],
1321
1464
  [
1322
1465
  ...getIn(0, desc.wType),
1323
- ...getIn(1, mapInfo.keyWType),
1324
- ...getIn(2, mapInfo.valWType),
1466
+ ...getIn(1, mapInfo.keyWType, keyDesc?.is64),
1467
+ ...getIn(2, mapInfo.valWType, valDesc?.is64),
1325
1468
  { op: "call", funcIndex: setIdx },
1326
1469
  ],
1327
1470
  );
@@ -1380,69 +1523,27 @@ export function addBoundaryHelpers(mod, meta) {
1380
1523
  );
1381
1524
  }
1382
1525
 
1383
- if (needs.time) {
1384
- const timeInfo = mod.getStructType("time.Time");
1385
- if (timeInfo) {
1386
- const timeWType = toRef(timeInfo);
1387
- addFunc(
1388
- "__time_make",
1389
- ["f64", "i32"],
1390
- [EXT],
1391
- [
1392
- get(0),
1393
- { op: "f64.const", value: 1000000 },
1394
- { op: "f64.mul" },
1395
- { op: "i64.trunc_sat_f64_s" },
1396
- get(1),
1397
- { op: "struct.new", typeIndex: timeInfo.typeIndex },
1398
- ...toExt(timeWType),
1399
- ],
1400
- );
1401
- addFunc(
1402
- "__time_unix_milli",
1403
- [EXT],
1404
- ["f64"],
1405
- [
1406
- get(0),
1407
- { op: "ref.is_null" },
1408
- { op: "if", blockType: "f64" },
1409
- { op: "f64.const", value: 0 },
1410
- { op: "else" },
1411
- ...getIn(0, timeWType),
1412
- { op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 0 },
1413
- { op: "i64.const", value: 1000000n },
1414
- { op: "i64.div_s" },
1415
- { op: "f64.convert_i64_s" },
1416
- { op: "end" },
1417
- ],
1418
- );
1419
- addFunc(
1420
- "__time_offset",
1421
- [EXT],
1422
- ["i32"],
1423
- [
1424
- get(0),
1425
- { op: "ref.is_null" },
1426
- { op: "if", blockType: "i32" },
1427
- { op: "i32.const", value: 0 },
1428
- { op: "else" },
1429
- ...getIn(0, timeWType),
1430
- { op: "struct.get", typeIndex: timeInfo.typeIndex, fieldIndex: 1 },
1431
- { op: "end" },
1432
- ],
1433
- );
1434
- }
1435
- }
1436
-
1437
1526
  for (const c of meta.consts ?? []) {
1438
1527
  if (!c.dynamic) continue;
1439
1528
  const g = mod.resolveGlobal(c.name);
1440
1529
  if (!g) continue;
1530
+ const is64 = Boolean(c.desc?.is64);
1441
1531
  addFunc(
1442
1532
  `__const_${c.name}`,
1443
1533
  [],
1444
- [extType(c.desc.wType)],
1445
- [{ op: "global.get", index: g.index }, ...toExt(c.desc.wType)],
1534
+ [extType(c.desc.wType, is64)],
1535
+ [{ op: "global.get", index: g.index }, ...toExt(c.desc.wType, is64)],
1536
+ );
1537
+ }
1538
+ for (const v of meta.vars ?? []) {
1539
+ const g = v.shared ? null : mod.resolveGlobal(v.name);
1540
+ if (!g) continue;
1541
+ const is64 = Boolean(v.desc?.is64);
1542
+ addFunc(
1543
+ `__var_${v.name}`,
1544
+ [],
1545
+ [extType(v.desc.wType, is64)],
1546
+ [{ op: "global.get", index: g.index }, ...toExt(v.desc.wType, is64)],
1446
1547
  );
1447
1548
  }
1448
1549
  }
@@ -1511,6 +1612,13 @@ function addIfaceHelpers(desc, mod, addFunc, addTrampoline) {
1511
1612
 
1512
1613
  const P = "__gfw_"; // facade helper prefix
1513
1614
 
1615
+ // Pieces of the strings host imports (written for use inside the templates
1616
+ // below, where a backslash escape must be doubled).
1617
+ const GO_SPACE =
1618
+ "[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]";
1619
+ const BAD_RUNE = "r < 0 || r > 0x10FFFF || (r >= 0xD800 && r <= 0xDFFF)";
1620
+ const SURROGATE = "(v >= 0xD800 && v <= 0xDFFF)";
1621
+
1514
1622
  // JS expression converting JS value `v` into the wasm representation.
1515
1623
  function inExpr(desc, v) {
1516
1624
  switch (desc.k) {
@@ -1519,7 +1627,7 @@ function inExpr(desc, v) {
1519
1627
  case "i32":
1520
1628
  return `(${v} | 0)`;
1521
1629
  case "i64":
1522
- return `${P}i64in(${v})`;
1630
+ return desc.is64 ? `BigInt(${v})` : `${P}i64in(${v})`;
1523
1631
  case "f32":
1524
1632
  case "f64":
1525
1633
  return `(+${v})`;
@@ -1529,8 +1637,10 @@ function inExpr(desc, v) {
1529
1637
  return `(${v} === undefined ? null : ${v})`;
1530
1638
  case "error":
1531
1639
  return `${P}errin(${v})`;
1532
- case "time":
1533
- return desc.ptr ? `${P}timein_ptr(${v})` : `${P}timein(${v})`;
1640
+ case "f32x4":
1641
+ return `${P}f32x4in(${v})`;
1642
+ case "mask4":
1643
+ return `${P}mask4in(${v})`;
1534
1644
  case "map":
1535
1645
  return `${P}mapin_${desc.key}(${v})`;
1536
1646
  case "ptr_box":
@@ -1568,13 +1678,19 @@ function outExpr(desc, v) {
1568
1678
  case "i32":
1569
1679
  return desc.unsigned ? `(${v} >>> 0)` : v;
1570
1680
  case "i64":
1681
+ if (desc.is64)
1682
+ return desc.unsigned
1683
+ ? `BigInt.asUintN(64, ${v})`
1684
+ : `BigInt.asIntN(64, ${v})`;
1571
1685
  return desc.unsigned ? `${P}u64out(${v})` : `${P}i64out(${v})`;
1572
1686
  case "string":
1573
1687
  return `(${v} ?? "")`;
1574
1688
  case "error":
1575
1689
  return `${P}errout(${v})`;
1576
- case "time":
1577
- return desc.ptr ? `${P}timeout_ptr(${v})` : `${P}timeout(${v})`;
1690
+ case "f32x4":
1691
+ return `${P}f32x4out(${v})`;
1692
+ case "mask4":
1693
+ return `${P}mask4out(${v})`;
1578
1694
  case "map":
1579
1695
  return `${P}mapout_${desc.key}(${v})`;
1580
1696
  case "ptr_box":
@@ -1607,14 +1723,18 @@ function zeroExpr(desc) {
1607
1723
  case "bool":
1608
1724
  return "false";
1609
1725
  case "i32":
1610
- case "i64":
1611
1726
  case "f32":
1612
1727
  case "f64":
1613
1728
  return "0";
1729
+ case "i64":
1730
+ return desc.is64 ? "0n" : "0";
1614
1731
  case "string":
1615
1732
  return '""';
1733
+ case "f32x4":
1734
+ return "new Float32Array(4)";
1735
+ case "mask4":
1736
+ return "[false, false, false, false]";
1616
1737
  case "error":
1617
- case "time":
1618
1738
  case "map":
1619
1739
  case "ptr_box":
1620
1740
  return "null";
@@ -1729,28 +1849,26 @@ function indent(text, prefix) {
1729
1849
  // survive the built-in mangler: no shorthand destructuring, import keys
1730
1850
  // quoted, helpers prefixed with `__`.
1731
1851
  export const WASM_IMPORTS_JS = `const ${P}MAX = 9007199254740991n;
1732
- function ${P}imports(stringTable, extraEnv, tag, write) {
1852
+ function ${P}imports(stringTable, extraEnv, tag, write, toStderr) {
1853
+ // Each instance numbers the references fmt_ptr_id is asked about from 1
1854
+ // (the table the JS body of that intrinsic uses).
1855
+ globalThis.__fmtptr = { m: new WeakMap(), n: 1 };
1733
1856
  let lineBuf = "";
1734
- let targs = [];
1735
- let fmtStructMap = new WeakMap();
1736
1857
  const late = { exports: null };
1737
- const flush = () => { write(lineBuf); lineBuf = ""; };
1858
+ // Go separates operands with a space unconditionally, so track whether the
1859
+ // current line already holds an operand (an empty string leaves lineBuf empty).
1860
+ let started = false;
1861
+ const flush = () => { write(lineBuf); lineBuf = ""; started = false; };
1738
1862
  const print = (v) => {
1739
- const s = String(v);
1740
- if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
1741
- lineBuf += " ";
1742
- }
1863
+ const s = Object.is(v, -0) ? "-0" : String(v);
1864
+ if (started) lineBuf += " ";
1865
+ started = true;
1743
1866
  lineBuf += s;
1744
1867
  };
1745
1868
  const println = (v) => {
1746
- const s = String(v);
1747
- if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
1748
- lineBuf += " ";
1749
- }
1750
- lineBuf += s;
1869
+ print(v);
1751
1870
  flush();
1752
1871
  };
1753
- const targ = (v) => { targs.push(v); };
1754
1872
  let utf8Encoded = null;
1755
1873
  let convBytes = null, convPos = 0;
1756
1874
  const env = {
@@ -1762,7 +1880,9 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1762
1880
  } catch {
1763
1881
  str = "[panic object]";
1764
1882
  }
1765
- throw new Error(str);
1883
+ const err = new Error(str);
1884
+ err.__gfp = true;
1885
+ throw err;
1766
1886
  },
1767
1887
  "str": (i) => stringTable[i] ?? "",
1768
1888
  "str_len": (s) => (s ? s.length : 0),
@@ -1775,7 +1895,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1775
1895
  "str_ge": (a, b) => ((a ?? "") >= (b ?? "") ? 1 : 0),
1776
1896
  "str_get": (s, i) => (s ? s.charCodeAt(i) : 0),
1777
1897
  "str_slice": (s, a, b) => (s ? s.slice(a, b) : ""),
1778
- "str_from_code_point": (c) => String.fromCodePoint(c),
1898
+ "str_from_code_point": (c) => String.fromCodePoint(c >= 0 && c <= 0x10ffff && !(c >= 0xd800 && c <= 0xdfff) ? c : 0xfffd),
1779
1899
  "str_code_point_at": (s, i) => (s ? s.codePointAt(i) : 0),
1780
1900
  "str_hash": (s) => {
1781
1901
  if (!s) return 0;
@@ -1785,28 +1905,109 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1785
1905
  }
1786
1906
  return h;
1787
1907
  },
1788
- "str_to_upper": (s) => (s ? s.toUpperCase() : ""),
1789
- "str_to_lower": (s) => (s ? s.toLowerCase() : ""),
1790
- "str_trim_space": (s) => (s ? s.trim() : ""),
1791
- "str_contains": (s, sub) => (s && sub !== undefined ? (s.includes(sub) ? 1 : 0) : 0),
1792
- "str_has_prefix": (s, pre) => (s && pre !== undefined ? (s.startsWith(pre) ? 1 : 0) : 0),
1793
- "str_has_suffix": (s, suf) => (s && suf !== undefined ? (s.endsWith(suf) ? 1 : 0) : 0),
1794
- "str_index": (s, sub) => (s ? s.indexOf(sub) : -1),
1795
- "str_last_index": (s, sub) => (s ? s.lastIndexOf(sub) : -1),
1796
- "str_repeat": (s, n) => (s && n > 0 ? s.repeat(Number(n)) : ""),
1797
- "str_replace_all": (s, o, n) => (s ? s.replaceAll(o, n) : ""),
1798
- "str_equal_fold": (a, b) => ((a ?? "").toLowerCase() === (b ?? "").toLowerCase() ? 1 : 0),
1799
- "str_count": (s, sep) => {
1800
- if (!s) return sep === "" ? 1 : 0;
1801
- if (sep === "") return s.length + 1;
1802
- return s.split(sep).length - 1;
1908
+ "str_to_upper": (s) => ${P}mapCase(s ?? "", "toUpperCase"),
1909
+ "str_to_lower": (s) => ${P}mapCase(s ?? "", "toLowerCase"),
1910
+ "str_trim_space": (s) => {
1911
+ s = s ?? "";
1912
+ const sp = /${GO_SPACE}/;
1913
+ let i = 0, j = s.length;
1914
+ while (i < j && sp.test(s[i])) i++;
1915
+ while (j > i && sp.test(s[j - 1])) j--;
1916
+ return s.slice(i, j);
1917
+ },
1918
+ "str_contains": (s, sub) => ((s ?? "").includes(sub ?? "") ? 1 : 0),
1919
+ "str_has_prefix": (s, pre) => ((s ?? "").startsWith(pre ?? "") ? 1 : 0),
1920
+ "str_has_suffix": (s, suf) => ((s ?? "").endsWith(suf ?? "") ? 1 : 0),
1921
+ "os_write": (fd, s) => {
1922
+ if (fd === 2 && toStderr && typeof process !== "undefined" && process.stderr?.write) { process.stderr.write(s ?? ""); return; }
1923
+ lineBuf += s ?? "";
1924
+ const nl = lineBuf.lastIndexOf("\\n");
1925
+ if (nl >= 0) { write(lineBuf.slice(0, nl)); lineBuf = lineBuf.slice(nl + 1); }
1926
+ },
1927
+ "time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
1928
+ "time_timezone_offset": (ms) => (-new Date(ms).getTimezoneOffset() * 60) | 0,
1929
+ "os_exit": (code) => {
1930
+ if (lineBuf !== "") { write(lineBuf); lineBuf = ""; }
1931
+ if (typeof process !== "undefined" && process.exit) process.exit(code);
1932
+ throw new Error("os.Exit(" + code + ")");
1933
+ },
1934
+ "os_getenv": (k) => (typeof process !== "undefined" && process.env ? process.env : (globalThis.__os_env ??= {}))[k ?? ""] ?? "",
1935
+ "os_setenv": (k, v) => { (typeof process !== "undefined" && process.env ? process.env : (globalThis.__os_env ??= {}))[k ?? ""] = v ?? ""; },
1936
+ "unicode_is": (r, k) => { ${unicodeIsJs("r", "k")} },
1937
+ "unicode_to_upper": (r) => { ${unicodeToUpperJs("r")} },
1938
+ "unicode_to_lower": (r) => { ${unicodeToLowerJs("r")} },
1939
+ "testing_nanotime": () => performance.now() * 1e6,
1940
+ "testing_match": (p, s) => new RegExp(p ?? "").test(s ?? "") ? 1 : 0,
1941
+ "fmt_ptr_id": (o) => {
1942
+ if (o === null || o === undefined || (typeof o !== "object" && typeof o !== "function")) return 0;
1943
+ const t = globalThis.__fmtptr;
1944
+ let id = t.m.get(o);
1945
+ if (!id) { id = t.n++; t.m.set(o, id); }
1946
+ return id;
1947
+ },
1948
+ "crypto_rand_u32": () => {
1949
+ const c = typeof crypto !== "undefined" ? crypto : globalThis.crypto;
1950
+ return c?.getRandomValues ? c.getRandomValues(new Uint32Array(1))[0] | 0 : (Math.random() * 4294967296) | 0;
1951
+ },
1952
+ "strconv_shortest": (f) => f.toExponential(),
1953
+ "strconv_fixed": ${FIXED_FLOAT_JS},
1954
+ "strconv_parse_decimal": (s) => Number((s ?? "").replaceAll("_", "")),
1955
+ "str_index": (s, sub) => (s ?? "").indexOf(sub ?? ""),
1956
+ "str_last_index": (s, sub) => (s ?? "").lastIndexOf(sub ?? ""),
1957
+ "str_index_byte": (s, b) => (s ?? "").indexOf(String.fromCharCode(b)),
1958
+ "str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
1959
+ "str_index_rune": (s, r) => (${BAD_RUNE} ? -1 : (s ?? "").indexOf(String.fromCodePoint(r))),
1960
+ "str_contains_rune": (s, r) => (!(${BAD_RUNE}) && (s ?? "").includes(String.fromCodePoint(r)) ? 1 : 0),
1961
+ "str_index_any": (s, cs) => {
1962
+ s = s ?? "";
1963
+ let m = -1;
1964
+ for (const c of (cs ?? "")) {
1965
+ const v = c.codePointAt(0);
1966
+ if (${SURROGATE}) continue;
1967
+ const i = s.indexOf(c);
1968
+ if (i >= 0 && (m < 0 || i < m)) m = i;
1969
+ }
1970
+ return m;
1971
+ },
1972
+ "str_last_index_any": (s, cs) => {
1973
+ s = s ?? "";
1974
+ let m = -1;
1975
+ for (const c of (cs ?? "")) {
1976
+ const v = c.codePointAt(0);
1977
+ if (${SURROGATE}) continue;
1978
+ const i = s.lastIndexOf(c);
1979
+ if (i > m) m = i;
1980
+ }
1981
+ return m;
1982
+ },
1983
+ "str_contains_any": (s, cs) => {
1984
+ s = s ?? "";
1985
+ for (const c of (cs ?? "")) {
1986
+ const v = c.codePointAt(0);
1987
+ if (!${SURROGATE} && s.includes(c)) return 1;
1988
+ }
1989
+ return 0;
1803
1990
  },
1991
+ "str_count": (s, t) => {
1992
+ s = s ?? ""; t = t ?? "";
1993
+ if (t === "") return [...s].length + 1;
1994
+ let n = 0;
1995
+ for (let i = s.indexOf(t); i !== -1; i = s.indexOf(t, i + t.length)) n++;
1996
+ return n;
1997
+ },
1998
+ "str_replace": (s, o, w, n) => ${P}replace(s ?? "", o ?? "", w ?? "", n),
1999
+ "str_replace_all": (s, o, w) => ${P}replace(s ?? "", o ?? "", w ?? "", -1),
2000
+ "str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
2001
+ "str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
2002
+ "str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
2003
+ "str_trim_prefix": (s, p) => ((s ?? "").startsWith(p ?? "") ? (s ?? "").slice((p ?? "").length) : (s ?? "")),
2004
+ "str_trim_suffix": (s, p) => ((s ?? "").endsWith(p ?? "") ? (s ?? "").slice(0, (s ?? "").length - (p ?? "").length) : (s ?? "")),
1804
2005
  "str_from_i64": (n) => String(n),
1805
2006
  "str_from_i32": (n) => String(n),
1806
- "str_from_f64": (f) => String(f),
2007
+ "str_from_f64": (f) => (Object.is(f, -0) ? "-0" : String(f)),
1807
2008
  "is_string": (v) => (typeof v === "string" ? 1 : 0),
1808
2009
  "str_to_bytes": (s) => {
1809
- utf8Encoded = new TextEncoder().encode(s ?? "");
2010
+ utf8Encoded = ${P}u8enc(s ?? "");
1810
2011
  return utf8Encoded.length;
1811
2012
  },
1812
2013
  "str_byte_at": (i) => (utf8Encoded ? (utf8Encoded[i] ?? 0) : 0),
@@ -1826,636 +2027,127 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1826
2027
  },
1827
2028
  "print_i32": print, "print_i64": print, "print_f32": print, "print_f64": print,
1828
2029
  "print_str": print, "print_any": print,
2030
+ "print_u64": (v) => print(BigInt.asUintN(64, v)),
2031
+ "println_u64": (v) => println(BigInt.asUintN(64, v)),
2032
+ "print_reset": () => { started = false; },
1829
2033
  "print_bool": (b) => print(b !== 0 ? "true" : "false"),
1830
2034
  "println_i32": println, "println_i64": println, "println_f32": println, "println_f64": println,
1831
2035
  "println_str": println, "println_any": println,
1832
2036
  "println_bool": (b) => println(b !== 0 ? "true" : "false"),
1833
2037
  "println_empty": flush,
1834
- "fmt_new_array": () => [],
1835
- "fmt_array_push": (arr, v) => { arr.push(v); },
1836
- "fmt_new_map": () => new Map(),
1837
- "fmt_map_set": (m, k, v) => { m.set(k, v); },
1838
- "fmt_new_struct": (ref, isPtr) => {
1839
- if (ref !== null && ref !== undefined && typeof ref === "object" && fmtStructMap.has(ref)) {
1840
- return fmtStructMap.get(ref);
1841
- }
1842
- const o = {};
1843
- Object.defineProperty(o, "__struct", { value: true, configurable: true });
1844
- if (isPtr !== 0) Object.defineProperty(o, "__p", { value: true, configurable: true });
1845
- if (ref !== null && ref !== undefined && typeof ref === "object") fmtStructMap.set(ref, o);
1846
- return o;
1847
- },
1848
- "fmt_struct_set": (o, k, v) => { o[typeof k === "number" ? stringTable[k] : k] = v; },
1849
- "fmt_box_i32": (v) => v,
1850
- "fmt_box_i64": (v) => (v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1851
- "fmt_box_f64": (v) => v,
1852
- "fmt_box_bool": (v) => v !== 0,
1853
- "fmt_new_box": () => ({ value: null, __p: true, __box: true }),
1854
- "testing_arg_i32": targ, "testing_arg_f32": targ, "testing_arg_f64": targ,
1855
- "testing_arg_str": targ, "testing_arg_any": targ,
1856
- "testing_arg_i64": (v) => targ(v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1857
- "testing_arg_bool": (v) => targ(v !== 0),
1858
- "testing_call": (t, i) => { const a = targs; targs = []; fmtStructMap = new WeakMap(); t[stringTable[i]](...a); },
1859
- "testing_name": (t) => t.Name(),
1860
- "testing_flag": (t, i) => (t[stringTable[i]]() ? 1 : 0),
1861
2038
  };
1862
- // Stdlib imports (strings/strconv/utf8/fmt/t.Run) are a separate block so
1863
- // modules that never call them do not ship it (see WASM_STDLIB_JS).
1864
- if (typeof ${P}stdlib === "function") {
1865
- Object.assign(env, ${P}stdlib(stringTable, {
1866
- "take": () => { const a = targs; targs = []; fmtStructMap = new WeakMap(); return a; },
1867
- "printf": (s) => {
1868
- lineBuf += s;
1869
- const nl = lineBuf.lastIndexOf("\\n");
1870
- if (nl >= 0) { write(lineBuf.slice(0, nl)); lineBuf = lineBuf.slice(nl + 1); }
1871
- },
1872
- "late": late,
1873
- }));
1874
- }
1875
2039
  Object.assign(env, extraEnv);
2040
+ // A host import that throws raises a Go panic: the module's panic stack
2041
+ // gets the message (as env.panic does through __push_panic) before the
2042
+ // error unwinds, so recover() in a calling frame sees it. Errors already
2043
+ // pushed (env.panic, nested calls into the module) are marked and pass
2044
+ // through; traps and wasm exceptions are not host panics.
2045
+ const hostPanic = (e) => {
2046
+ if (e !== null && typeof e === "object") {
2047
+ if (e.__gfp === true) return;
2048
+ if (typeof WebAssembly !== "undefined" && (e instanceof WebAssembly.Exception || e instanceof WebAssembly.RuntimeError)) return;
2049
+ try { e.__gfp = true; } catch {}
2050
+ }
2051
+ const push = late.exports && late.exports.__push_panic;
2052
+ if (push) push(String(e !== null && typeof e === "object" && e.message !== undefined ? e.message : e));
2053
+ };
2054
+ for (const k of Object.keys(env)) {
2055
+ const fn = env[k];
2056
+ if (typeof fn !== "function" || k === "panic" || k === "panicTag") continue;
2057
+ env[k] = function () {
2058
+ try { return fn.apply(this, arguments); } catch (e) { hostPanic(e); throw e; }
2059
+ };
2060
+ }
1876
2061
  const m = {};
1877
- for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "cbrt", "hypot", "random", "sinh", "cosh", "tanh"]) m[k] = Math[k];
2062
+ for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "cbrt", "hypot", "random", "sinh", "cosh", "tanh", "log1p", "expm1"]) m[k] = Math[k];
1878
2063
  m.mod = (x, y) => x % y;
1879
2064
  // Go rounds half away from zero; Math.round rounds half toward +Infinity.
1880
2065
  m.round = (x) => Math.sign(x) * Math.round(Math.abs(x));
1881
2066
  // Exports are needed by imports that call back into the module (t.Run).
1882
- return { "env": env, "Math": m, "__bind": (ex) => { late.exports = ex; } };
1883
- }`;
1884
-
1885
- // JS side of the STDLIB_IMPORTS table (index.js); emitted only when the
1886
- // module imports one of the packages that use it.
1887
- export const WASM_STDLIB_JS = `${HELPER_ERROR}
1888
- ${HELPER_SPRINTF}
1889
- ${HELPER_STRCONV}
1890
- ${HELPER_PATH_CLEAN}
1891
- ${HELPER_DURATION}
1892
- ${HELPER_SHA256}
1893
- ${HELPER_HEX}
1894
- ${HELPER_BASE64}
1895
- function ${P}stdlib(stringTable, io) {
1896
- let parts = [];
1897
- let cutBefore = "", cutAfter = "";
1898
- let strconvErr = null, strconvErrKind = 0;
1899
- let decodeSize = 0;
1900
- let replacerTarget = "", replacerPairs = [];
1901
- let splitDir = "", splitFile = "";
1902
- let joinParts = [];
1903
- let matchHasErr = 0;
1904
- let trimStart = 0, trimEnd = 0;
1905
- let sscanTokens = [];
1906
- let sscanHasErr = 0, sscanErrMsg = "";
1907
- let timeParseErr = null, timeParseOffset = 0;
1908
- let rawBytes = new Uint8Array(0);
1909
- let rawPos = 0;
1910
- let hexErr = null;
1911
- let base64Err = null;
1912
- let customBase64Encodings = [];
1913
- return {
1914
- "fmt_sscan_init": (s, mode) => {
1915
- sscanHasErr = 0;
1916
- sscanErrMsg = "";
1917
- const str = s ?? "";
1918
- sscanTokens = (mode === 1 ? str.split("\\n")[0] : str).trim().split(/\\s+/).filter(Boolean);
1919
- return sscanTokens.length;
1920
- },
1921
- "fmt_sscanf_init": (s, fmt) => {
1922
- sscanHasErr = 0;
1923
- sscanErrMsg = "";
1924
- const str = s ?? "";
1925
- const fmtStr = fmt ?? "";
1926
- let si = 0, fi = 0;
1927
- sscanTokens = [];
1928
- while (fi < fmtStr.length) {
1929
- if (/\\s/.test(fmtStr[fi])) {
1930
- while (fi < fmtStr.length && /\\s/.test(fmtStr[fi])) fi++;
1931
- while (si < str.length && /\\s/.test(str[si])) si++;
1932
- continue;
1933
- }
1934
- if (fmtStr[fi] === "%") {
1935
- if (fi + 1 < fmtStr.length && fmtStr[fi + 1] === "%") {
1936
- if (str[si] !== "%") {
1937
- sscanHasErr = 1;
1938
- sscanErrMsg = "input does not match format";
1939
- break;
1940
- }
1941
- si++;
1942
- fi += 2;
1943
- continue;
1944
- }
1945
- const vm = fmtStr.slice(fi).match(/^%[vdsfqtxXbcoeEfFgG]/);
1946
- if (vm) {
1947
- const verb = vm[0];
1948
- fi += verb.length;
1949
- if (verb !== "%c") {
1950
- while (si < str.length && /\\s/.test(str[si])) si++;
1951
- }
1952
- if (si >= str.length) break;
1953
- let tokRe = /^\\S+/;
1954
- if (verb === "%d" || verb === "%i") tokRe = /^[+-]?\\d+/;
1955
- else if (verb === "%f" || verb === "%F" || verb === "%e" || verb === "%E" || verb === "%g" || verb === "%G") tokRe = /^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?/;
1956
- else if (verb === "%t") tokRe = /^(?:true|false|1|0|t|f|T|F|TRUE|FALSE)/;
1957
- else if (verb === "%x" || verb === "%X") tokRe = /^[+-]?(?:0x)?[0-9a-fA-F]+/;
1958
- else if (verb === "%c") tokRe = /^[\\s\\S]/;
1959
- const m = str.slice(si).match(tokRe);
1960
- if (!m) {
1961
- sscanHasErr = 1;
1962
- sscanErrMsg = "input does not match format";
1963
- break;
1964
- }
1965
- sscanTokens.push(m[0]);
1966
- si += m[0].length;
1967
- continue;
1968
- }
1969
- }
1970
- if (si >= str.length) break;
1971
- if (str[si] !== fmtStr[fi]) {
1972
- sscanHasErr = 1;
1973
- sscanErrMsg = "input does not match format";
1974
- break;
1975
- }
1976
- si++;
1977
- fi++;
1978
- }
1979
- return sscanTokens.length;
1980
- },
1981
- "fmt_sscan_has_err": () => sscanHasErr,
1982
- "fmt_sscan_err_msg": () => sscanErrMsg || "syntax error",
1983
- "fmt_sscan_token_str": (i) => sscanTokens[i] ?? "",
1984
- "fmt_sscan_token_i64": (i) => {
1985
- if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0n; }
1986
- const t = sscanTokens[i];
1987
- if (/^[+-]?(?:0[xX])?[0-9a-fA-F]+$/.test(t)) {
1988
- try {
1989
- if (/^[+-]?[0-9a-fA-F]+$/.test(t) && /[a-fA-F]/.test(t)) {
1990
- return BigInt("0x" + t);
1991
- }
1992
- return BigInt(t);
1993
- } catch {}
1994
- }
1995
- if (t.length === 1) {
1996
- return BigInt(t.charCodeAt(0));
1997
- }
1998
- sscanHasErr = 1;
1999
- sscanErrMsg = "expected integer";
2000
- return 0n;
2001
- },
2002
- "fmt_sscan_token_f64": (i) => {
2003
- if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
2004
- const t = sscanTokens[i];
2005
- const n = Number(t);
2006
- if (Number.isNaN(n) || !/^[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+-]?\\d+)?$/.test(t)) {
2007
- sscanHasErr = 1;
2008
- sscanErrMsg = "expected float";
2009
- return 0;
2010
- }
2011
- return n;
2012
- },
2013
- "fmt_sscan_token_bool": (i) => {
2014
- if (i >= sscanTokens.length) { sscanHasErr = 1; sscanErrMsg = "unexpected EOF"; return 0; }
2015
- const t = sscanTokens[i]?.toLowerCase();
2016
- if (t === "true" || t === "1" || t === "t") return 1;
2017
- if (t === "false" || t === "0" || t === "f") return 0;
2018
- sscanHasErr = 1;
2019
- sscanErrMsg = "expected boolean";
2020
- return 0;
2021
- },
2022
- "testing_run": (t, name, fn) => (t.Run(name, (sub) => { io.late.exports.__testing_run_cb(fn, sub); }) ? 1 : 0),
2023
- "fmt_sprintf": (f) => __sprintf(f ?? "", ...io.take()),
2024
- "fmt_printf": (f) => { io.printf(__sprintf(f ?? "", ...io.take())); },
2025
- "fmt_format_v": (v) => { const res = __sprintf("%v", v); io.take(); return res; },
2026
- "str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
2027
- "str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
2028
- "str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
2029
- "str_trim_prefix": (s, p) => (s && p && s.startsWith(p) ? s.slice(p.length) : (s ?? "")),
2030
- "str_trim_suffix": (s, p) => (s && p && s.endsWith(p) ? s.slice(0, s.length - p.length) : (s ?? "")),
2031
- "str_cut": (s, sep) => {
2032
- const i = (s ?? "").indexOf(sep ?? "");
2033
- if (i < 0) { cutBefore = s ?? ""; cutAfter = ""; return 0; }
2034
- cutBefore = (s ?? "").slice(0, i);
2035
- cutAfter = (s ?? "").slice(i + (sep ?? "").length);
2036
- return 1;
2037
- },
2038
- "str_cut_before": () => cutBefore,
2039
- "str_cut_after": () => cutAfter,
2040
- "str_cut_prefix": (s, p) => {
2041
- s = s ?? ""; p = p ?? "";
2042
- if (s.startsWith(p)) { cutAfter = s.slice(p.length); return 1; }
2043
- cutAfter = s; return 0;
2044
- },
2045
- "str_cut_suffix": (s, suf) => {
2046
- s = s ?? ""; suf = suf ?? "";
2047
- if (s.endsWith(suf)) { cutBefore = s.slice(0, s.length - suf.length); return 1; }
2048
- cutBefore = s; return 0;
2049
- },
2050
- "str_replace": (s, o, n, cnt) => {
2051
- s = s ?? ""; o = o ?? ""; n = n ?? "";
2052
- if (cnt < 0) return s.replaceAll(o, n);
2053
- let out = "", from = 0;
2054
- for (let k = 0; k < cnt; k++) {
2055
- const at = s.indexOf(o, from);
2056
- if (at < 0) break;
2057
- out += s.slice(from, at) + n;
2058
- from = at + o.length;
2059
- if (o === "") { if (from >= s.length) break; out += s[from]; from++; }
2060
- }
2061
- return out + s.slice(from);
2062
- },
2063
- "str_contains_rune": (s, r) => ((s ?? "").includes(String.fromCodePoint(r)) ? 1 : 0),
2064
- "str_contains_any": (s, cs) => { for (const c of (cs ?? "")) if ((s ?? "").includes(c)) return 1; return 0; },
2065
- "str_index_byte": (s, b) => (s ?? "").indexOf(String.fromCharCode(b)),
2066
- "str_index_rune": (s, r) => (s ?? "").indexOf(String.fromCodePoint(r)),
2067
- "str_index_any": (s, cs) => { s = s ?? ""; let best = -1; for (const c of (cs ?? "")) { const i = s.indexOf(c); if (i >= 0 && (best < 0 || i < best)) best = i; } return best; },
2068
- "str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
2069
- "str_last_index_any": (s, cs) => {
2070
- s = s ?? ""; let best = -1;
2071
- for (const c of (cs ?? "")) {
2072
- const i = s.lastIndexOf(c);
2073
- if (i > best) best = i;
2074
- }
2075
- return best;
2076
- },
2077
- "str_title": (s) => (s ?? "").replace(/(^|[^\\p{L}\\p{N}_'])(\\p{Ll})/gu, (m, a, b) => a + b.toUpperCase()),
2078
- "str_split": (s, sep) => { parts = (s ?? "").split(sep ?? ""); return parts.length; },
2079
- "str_split_n": (s, sep, n) => {
2080
- s = s ?? ""; sep = sep ?? "";
2081
- if (n === 0) return -1;
2082
- if (n < 0) { parts = sep === "" ? [...s] : s.split(sep); return parts.length; }
2083
- if (sep === "") {
2084
- const chars = [...s];
2085
- if (chars.length <= n) { parts = chars; return parts.length; }
2086
- const r = chars.slice(0, n - 1);
2087
- r.push(chars.slice(n - 1).join(""));
2088
- parts = r;
2089
- return parts.length;
2090
- }
2091
- const r = [];
2092
- let cur = s;
2093
- for (let i = 1; i < n && cur.length; i++) {
2094
- const j = cur.indexOf(sep);
2095
- if (j < 0) break;
2096
- r.push(cur.slice(0, j));
2097
- cur = cur.slice(j + sep.length);
2098
- }
2099
- r.push(cur);
2100
- parts = r;
2101
- return parts.length;
2102
- },
2103
- "str_split_after": (s, sep) => {
2104
- s = s ?? ""; sep = sep ?? "";
2105
- if (sep === "") { parts = [...s]; return parts.length; }
2106
- const r = [];
2107
- let cur = s;
2108
- while (true) {
2109
- const j = cur.indexOf(sep);
2110
- if (j < 0) { r.push(cur); break; }
2111
- r.push(cur.slice(0, j + sep.length));
2112
- cur = cur.slice(j + sep.length);
2113
- }
2114
- parts = r;
2115
- return parts.length;
2116
- },
2117
- "str_split_after_n": (s, sep, n) => {
2118
- s = s ?? ""; sep = sep ?? "";
2119
- if (n === 0) return -1;
2120
- if (sep === "") {
2121
- const chars = [...s];
2122
- if (n < 0 || chars.length <= n) parts = chars;
2123
- else {
2124
- const r = chars.slice(0, n - 1);
2125
- r.push(chars.slice(n - 1).join(""));
2126
- parts = r;
2127
- }
2128
- return parts.length;
2129
- }
2130
- const r = [];
2131
- let cur = s;
2132
- for (let i = 1; (n < 0 || i < n) && cur.length; i++) {
2133
- const j = cur.indexOf(sep);
2134
- if (j < 0) break;
2135
- r.push(cur.slice(0, j + sep.length));
2136
- cur = cur.slice(j + sep.length);
2137
- }
2138
- r.push(cur);
2139
- parts = r;
2140
- return parts.length;
2141
- },
2142
- "str_fields": (s) => { parts = (s ?? "").split(/\\s+/).filter((p) => p !== ""); return parts.length; },
2143
- "str_part": (i) => parts[i] ?? "",
2144
- "str_replacer_init": (s) => {
2145
- replacerTarget = s ?? "";
2146
- replacerPairs = [];
2147
- },
2148
- "str_replacer_add": (o, n) => {
2149
- replacerPairs.push([o ?? "", n ?? ""]);
2150
- },
2151
- "str_replacer_run": () => {
2152
- let s = replacerTarget;
2153
- if (!replacerPairs.length || s === "") return s;
2154
- let out = "";
2155
- let i = 0;
2156
- while (i < s.length) {
2157
- let matched = false;
2158
- for (let k = 0; k < replacerPairs.length; k++) {
2159
- const [oldStr, newStr] = replacerPairs[k];
2160
- if (oldStr.length > 0 && s.startsWith(oldStr, i)) {
2161
- out += newStr;
2162
- i += oldStr.length;
2163
- matched = true;
2164
- break;
2165
- }
2166
- }
2167
- if (!matched) {
2168
- out += s[i];
2169
- i++;
2170
- }
2171
- }
2172
- return out;
2173
- },
2174
- "strconv_atoi": (s) => {
2175
- const r = __strconv_atoi(s ?? "");
2176
- strconvErr = r[1];
2177
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2178
- return r[0];
2179
- },
2180
- "strconv_parse_int": (s, base, bits) => {
2181
- const r = __strconv_parse_int(s ?? "", base, bits);
2182
- strconvErr = r[1];
2183
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2184
- return r[0];
2185
- },
2186
- "strconv_parse_float": (s) => {
2187
- const r = __strconv_parse_float(s ?? "");
2188
- strconvErr = r[1];
2189
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2190
- return r[0];
2191
- },
2192
- "strconv_parse_bool": (s) => {
2193
- const r = __strconv_parse_bool(s ?? "");
2194
- strconvErr = r[1];
2195
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2196
- return r[0] ? 1 : 0;
2197
- },
2198
- "strconv_err": () => strconvErr,
2199
- "strconv_err_kind": () => strconvErrKind,
2200
- "strconv_format_float": (f, fmtc, prec, _bits) => __strconv_format_float(f, fmtc, prec),
2201
- "strconv_format_int": (n, base) => n.toString(base),
2202
- "strconv_format_uint": (n, base) => BigInt.asUintN(64, BigInt(n)).toString(base),
2203
- "strconv_parse_uint": (s, base, bits) => {
2204
- const r = __strconv_parse_uint(s ?? "", base, bits);
2205
- strconvErr = r[1];
2206
- strconvErrKind = r[1] ? (r[1].includes("invalid syntax") ? 1 : r[1].includes("value out of range") ? 2 : 0) : 0;
2207
- return r[0];
2208
- },
2209
- "strconv_quote": (s) => JSON.stringify(s ?? ""),
2210
- "strconv_unquote": (s) => {
2211
- s = s ?? "";
2212
- if (s.startsWith("\`") && s.endsWith("\`") && s.length >= 2) {
2213
- if (s.slice(1, -1).includes("\`")) {
2214
- strconvErr = "invalid syntax";
2215
- strconvErrKind = 1;
2216
- return "";
2217
- }
2218
- strconvErr = null;
2219
- strconvErrKind = 0;
2220
- return s.slice(1, -1);
2221
- }
2222
- try {
2223
- const v = JSON.parse(s);
2224
- if (typeof v !== "string") {
2225
- strconvErr = "invalid syntax";
2226
- strconvErrKind = 1;
2227
- return "";
2228
- }
2229
- strconvErr = null;
2230
- strconvErrKind = 0;
2231
- return v;
2232
- } catch (e) {
2233
- strconvErr = "invalid syntax";
2234
- strconvErrKind = 1;
2235
- return "";
2236
- }
2237
- },
2238
- "utf8_rune_count": (s) => { let n = 0; for (const _ of (s ?? "")) n++; return n; },
2239
- "utf8_rune_len": (r) => (r < 0 || r > 0x10ffff || (r >= 0xd800 && r <= 0xdfff) ? -1 : r < 0x80 ? 1 : r < 0x800 ? 2 : r < 0x10000 ? 3 : 4),
2240
- "utf8_valid_string": (s) => (s ?? "").isWellFormed ? ((s ?? "").isWellFormed() ? 1 : 0) : (/[\\uD800-\\uDFFF]/.test((s ?? "").replace(/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g, "")) ? 0 : 1),
2241
- "utf8_decode_rune": (s, last) => {
2242
- s = s ?? "";
2243
- if (s.length === 0) { decodeSize = 0; return 0xfffd; }
2244
- let cp;
2245
- if (last) { const tail = s.codePointAt(s.length - 1); cp = (tail >= 0xdc00 && tail <= 0xdfff && s.length >= 2) ? s.codePointAt(s.length - 2) : tail; }
2246
- else cp = s.codePointAt(0);
2247
- if (cp >= 0xd800 && cp <= 0xdfff) { decodeSize = 1; return 0xfffd; }
2248
- // Size is in UTF-16 code units, matching the JS backend (strings are JS strings).
2249
- decodeSize = cp > 0xffff ? 2 : 1;
2250
- return cp;
2251
- },
2252
- "utf8_decode_size": () => decodeSize,
2253
- "utf8_full_rune": (s) => { s = s ?? ""; if (s.length === 0) return 0; const c = s.charCodeAt(0); return (c >= 0xd800 && c <= 0xdbff && s.length < 2) ? 0 : 1; },
2254
- // unicode
2255
- "unicode_is_letter": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{L}/u.test(c)) ? 1 : 0; },
2256
- "unicode_is_digit": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Nd}/u.test(c)) ? 1 : 0; },
2257
- "unicode_is_space": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\s/u.test(c)) ? 1 : 0; },
2258
- "unicode_is_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Lu}/u.test(c)) ? 1 : 0; },
2259
- "unicode_is_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Ll}/u.test(c)) ? 1 : 0; },
2260
- "unicode_is_punct": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{P}/u.test(c)) ? 1 : 0; },
2261
- "unicode_is_control": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Cc}/u.test(c)) ? 1 : 0; },
2262
- "unicode_is_print": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
2263
- "unicode_is_graphic": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
2264
- "unicode_to_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toUpperCase().codePointAt(0) ?? r) : r; },
2265
- "unicode_to_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toLowerCase().codePointAt(0) ?? r) : r; },
2266
- // html
2267
- "html_escape": (s) => (s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&#34;").replace(/'/g, "&#39;"),
2268
- "html_unescape": (s) => (s ?? "").replace(/&(?:quot|apos|#39|#34|gt|lt|amp|#(?:[0-9]+|[xX][0-9a-fA-F]+));/g, (m) => {
2269
- switch (m) {
2270
- case "&quot;": case "&#34;": return '"';
2271
- case "&apos;": case "&#39;": return "'";
2272
- case "&gt;": return ">";
2273
- case "&lt;": return "<";
2274
- case "&amp;": return "&";
2275
- default: {
2276
- if (m.startsWith("&#")) {
2277
- const raw = m.slice(2, -1);
2278
- const cp = (raw.startsWith("x") || raw.startsWith("X")) ? parseInt(raw.slice(1), 16) : parseInt(raw, 10);
2279
- return (cp >= 0 && cp <= 0x10FFFF && !(cp >= 0xD800 && cp <= 0xDFFF)) ? String.fromCodePoint(cp) : "\\uFFFD";
2280
- }
2281
- return m;
2282
- }
2283
- }
2284
- }),
2285
- // path
2286
- "path_base": (p) => { if (!p) return "."; const stripped = p.replace(/\\/+$/, ""); if (!stripped) return "/"; const i = stripped.lastIndexOf("/"); return i < 0 ? stripped : stripped.slice(i + 1) || "/"; },
2287
- "path_dir": (p) => { const s = p ?? ""; const i = s.lastIndexOf("/"); if (i < 0) return "."; const res = s.slice(0, i); return res === "" ? "/" : __pathClean(res); },
2288
- "path_ext": (p) => { const s = p ?? ""; const b = s.slice(s.lastIndexOf("/") + 1); const i = b.lastIndexOf("."); return i <= 0 ? "" : b.slice(i); },
2289
- "path_is_abs": (p) => (p ?? "").startsWith("/") ? 1 : 0,
2290
- "path_clean": (p) => __pathClean(p ?? ""),
2291
- "path_split": (p) => {
2292
- const s = p ?? "";
2293
- const i = s.lastIndexOf("/");
2294
- if (i < 0) { splitDir = ""; splitFile = s; }
2295
- else { splitDir = s.slice(0, i + 1); splitFile = s.slice(i + 1); }
2296
- },
2297
- "path_split_dir": () => splitDir,
2298
- "path_split_file": () => splitFile,
2299
- "path_join_init": () => { joinParts = []; },
2300
- "path_join_push": (s) => { if (s) joinParts.push(s); },
2301
- "path_join_run": () => {
2302
- if (joinParts.length === 0) return "";
2303
- const res = __pathClean(joinParts.join("/"));
2304
- joinParts = [];
2305
- return res;
2306
- },
2307
- "path_match": (pat, name) => {
2308
- const r = __pathMatch(pat ?? "", name ?? "");
2309
- matchHasErr = r[1] ? 1 : 0;
2310
- return r[0] ? 1 : 0;
2311
- },
2312
- "path_match_has_err": () => matchHasErr,
2313
- // bytes trim
2314
- "bytes_trim": (s, cutset, left, right) => {
2315
- const cs = new Set(Array.from(cutset ?? ""));
2316
- const chars = Array.from(s ?? "");
2317
- let a = 0, b = chars.length;
2318
- if (left) while (a < b && cs.has(chars[a])) a++;
2319
- if (right) while (b > a && cs.has(chars[b - 1])) b--;
2320
- const enc = new TextEncoder();
2321
- trimStart = a === 0 ? 0 : enc.encode(chars.slice(0, a).join("")).length;
2322
- trimEnd = b === chars.length ? enc.encode(s ?? "").length : enc.encode(chars.slice(0, b).join("")).length;
2323
- },
2324
- "bytes_trim_start": () => trimStart,
2325
- "bytes_trim_end": () => trimEnd,
2326
- // time
2327
- "time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
2328
- "time_timezone_offset": (ms) => -new Date(ms).getTimezoneOffset() * 60,
2329
- "time_duration_string": (d) => __durationString(d),
2330
- "time_parse": (layout, val) => {
2331
- try {
2332
- const d = new Date(val ?? "");
2333
- if (isNaN(d.getTime())) throw 0;
2334
- timeParseErr = null;
2335
- timeParseOffset = -d.getTimezoneOffset() * 60;
2336
- return d.getTime();
2337
- } catch {
2338
- timeParseErr = "parsing time: cannot parse";
2339
- timeParseOffset = 0;
2340
- return 0;
2341
- }
2342
- },
2343
- "time_parse_err": () => timeParseErr,
2344
- "time_parse_offset": () => timeParseOffset,
2345
- "time_format": (y, m, d, hr, min, sec, wd, ms, offsetSec, layout) => {
2346
- const DAYS = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
2347
- const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
2348
- const pad = (n, w = 2) => String(n).padStart(w, "0");
2349
- return (layout ?? "").replace(/2006|06|Monday|Mon|January|Jan|01|02|15|PM|pm|03|04|05|Z07:00|\\.(?:0+|9+)|1|2|3|4|5/g, t => {
2350
- if (t.startsWith(".")) {
2351
- const isZeroPad = t.includes("0");
2352
- const maxLen = t.length - 1;
2353
- let frac = pad(ms % 1000, 3);
2354
- if (maxLen > 3) frac = frac.padEnd(maxLen, "0");
2355
- else frac = frac.slice(0, maxLen);
2356
- if (!isZeroPad) {
2357
- frac = frac.replace(/0+$/, "");
2358
- return frac.length > 0 ? "." + frac : "";
2359
- }
2360
- return "." + frac;
2361
- }
2362
- switch (t) {
2363
- case "2006": return String(y);
2364
- case "06": return String(y).slice(-2);
2365
- case "Monday": return DAYS[wd];
2366
- case "Mon": return DAYS[wd].slice(0, 3);
2367
- case "January": return MONTHS[m - 1];
2368
- case "Jan": return MONTHS[m - 1].slice(0, 3);
2369
- case "01": return pad(m);
2370
- case "1": return String(m);
2371
- case "02": return pad(d);
2372
- case "2": return String(d);
2373
- case "15": return pad(hr);
2374
- case "PM": return hr < 12 ? "AM" : "PM";
2375
- case "pm": return hr < 12 ? "am" : "pm";
2376
- case "03": return pad(hr % 12 || 12);
2377
- case "3": return String(hr % 12 || 12);
2378
- case "04": return pad(min);
2379
- case "4": return String(min);
2380
- case "05": return pad(sec);
2381
- case "5": return String(sec);
2382
- case "Z07:00": {
2383
- const o = Math.floor(offsetSec / 60);
2384
- return o === 0 ? "Z" : (o > 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
2385
- }
2386
- default: return t;
2387
- }
2388
- });
2389
- },
2390
- "raw_bytes_init": (len) => {
2391
- rawBytes = new Uint8Array(len);
2392
- rawPos = 0;
2393
- },
2394
- "raw_bytes_push": (b) => {
2395
- if (rawPos < rawBytes.length) {
2396
- rawBytes[rawPos++] = b & 0xff;
2397
- }
2398
- },
2399
- "raw_bytes_len": () => rawBytes.length,
2400
- "raw_bytes_get": (i) => rawBytes[i] ?? 0,
2401
- "crypto_rand_fill": (len) => {
2402
- rawBytes = new Uint8Array(len);
2403
- if (typeof crypto !== "undefined" && crypto.getRandomValues) {
2404
- const CHUNK = 65536;
2405
- for (let offset = 0; offset < len; offset += CHUNK) {
2406
- crypto.getRandomValues(rawBytes.subarray(offset, Math.min(offset + CHUNK, len)));
2407
- }
2408
- } else {
2409
- for (let i = 0; i < len; i++) rawBytes[i] = Math.floor(Math.random() * 256);
2410
- }
2411
- return len;
2412
- },
2413
- "crypto_sha256_sum": () => {
2414
- const d = __sha256.Sum256(rawBytes);
2415
- rawBytes = new Uint8Array(d);
2416
- return 32;
2417
- },
2418
- "hex_encode_to_string": () => {
2419
- return __hex_encode_to_string(rawBytes);
2420
- },
2421
- "hex_decode_string": (s) => {
2422
- const [res, err] = __hex_decode_string(s ?? "");
2423
- hexErr = err ? (err.message ?? String(err)) : null;
2424
- rawBytes = new Uint8Array(res);
2425
- return rawBytes.length;
2426
- },
2427
- "hex_get_err": () => hexErr,
2428
- "base64_new_encoding": (alphabet) => {
2429
- const id = customBase64Encodings.length + 4;
2430
- customBase64Encodings.push(__base64_make(alphabet ?? "", "="));
2431
- return id;
2432
- },
2433
- "base64_encode_to_string": (mode) => {
2434
- const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
2435
- return (enc ?? __base64_std).EncodeToString(rawBytes);
2436
- },
2437
- "base64_decode_string": (mode, s) => {
2438
- const enc = mode === 1 ? __base64_url : mode === 2 ? __base64_raw_std : mode === 3 ? __base64_raw_url : mode >= 4 ? customBase64Encodings[mode - 4] : __base64_std;
2439
- const [res, err] = (enc ?? __base64_std).DecodeString(s ?? "");
2440
- base64Err = err ? (err.message ?? String(err)) : null;
2441
- rawBytes = new Uint8Array(res);
2442
- return rawBytes.length;
2443
- },
2444
- "base64_get_err": () => base64Err,
2067
+ // A partial last line (print / fmt.Print without a newline) is written when
2068
+ // the program ends; Node gets it without the newline console.log adds.
2069
+ const flushPartial = () => {
2070
+ if (lineBuf === "") return;
2071
+ const s = lineBuf;
2072
+ lineBuf = "";
2073
+ started = false;
2074
+ if (typeof process !== "undefined" && process?.stdout?.write) process.stdout.write(s);
2075
+ else write(s);
2445
2076
  };
2077
+ return { "env": env, "Math": m, "__bind": (ex) => { late.exports = ex; }, "__flush": flushPartial };
2446
2078
  }
2079
+ // string -> bytes: UTF-8, except that a lone surrogate U+DC80-U+DCFF (a raw
2080
+ // byte from a \\xNN escape in the source) encodes as that single byte.
2081
+ function ${P}u8enc(s) {
2082
+ const e = new TextEncoder();
2083
+ if (s.isWellFormed()) return e.encode(s);
2084
+ const out = [];
2085
+ for (let i = 0; i < s.length; i++) {
2086
+ const c = s.charCodeAt(i);
2087
+ if (c >= 0xdc80 && c <= 0xdcff && !(i > 0 && (s.charCodeAt(i - 1) & 0xfc00) === 0xd800)) { out.push(c - 0xdc00); continue; }
2088
+ const cp = s.codePointAt(i);
2089
+ if (cp > 0xffff) i++;
2090
+ for (const b of e.encode(String.fromCodePoint(cp))) out.push(b);
2091
+ }
2092
+ return Uint8Array.from(out);
2093
+ }
2094
+ // strings.ToUpper/ToLower: letter by letter through the first code point of
2095
+ // the host's mapping, as unicode.ToUpper/ToLower do; ASCII text maps whole.
2096
+ function ${P}mapCase(s, method) {
2097
+ if (/^[\\x00-\\x7f]*$/.test(s)) return s[method]();
2098
+ return Array.from(s, (c) => {
2099
+ const m = c[method]();
2100
+ const r = String.fromCodePoint(m.codePointAt(0));
2101
+ // A letter whose upper case spans several code points (ß) stays as is.
2102
+ return method === "toUpperCase" && m.length > r.length ? c : r;
2103
+ }).join("");
2104
+ }
2105
+ // Code-point-aware Trim/TrimLeft/TrimRight of strings.
2447
2106
  function ${P}trim(s, cutset, left, right) {
2448
- const cs = new Set(Array.from(cutset));
2449
- const chars = Array.from(s);
2450
- let a = 0, b = chars.length;
2451
- if (left) while (a < b && cs.has(chars[a])) a++;
2452
- if (right) while (b > a && cs.has(chars[b - 1])) b--;
2453
- return chars.slice(a, b).join("");
2107
+ if (s === "" || cutset === "") return s;
2108
+ const set = new Set(cutset);
2109
+ let i = 0, j = s.length;
2110
+ if (left) while (i < j) {
2111
+ const c = s.codePointAt(i);
2112
+ if (!set.has(String.fromCodePoint(c))) break;
2113
+ i += c > 0xFFFF ? 2 : 1;
2114
+ }
2115
+ if (right) while (j > i) {
2116
+ let k = j - 1;
2117
+ const lo = s.charCodeAt(k);
2118
+ if (lo >= 0xDC00 && lo <= 0xDFFF && k > i) {
2119
+ const hi = s.charCodeAt(k - 1);
2120
+ if (hi >= 0xD800 && hi <= 0xDBFF) k--;
2121
+ }
2122
+ if (!set.has(s.slice(k, j))) break;
2123
+ j = k;
2124
+ }
2125
+ return s.slice(i, j);
2126
+ }
2127
+ // strings.Replace: an empty old string matches before every code point and at
2128
+ // the end; the replacement is literal.
2129
+ function ${P}replace(s, o, w, n) {
2130
+ if (o === w || n === 0) return s;
2131
+ if (o !== "") {
2132
+ if (n < 0) return s.replaceAll(o, () => w);
2133
+ let r = "", i = 0;
2134
+ for (let k = n; k > 0; k--) {
2135
+ const j = s.indexOf(o, i);
2136
+ if (j < 0) break;
2137
+ r += s.slice(i, j) + w;
2138
+ i = j + o.length;
2139
+ }
2140
+ return r + s.slice(i);
2141
+ }
2142
+ const cs = [...s];
2143
+ const m = n < 0 || n > cs.length + 1 ? cs.length + 1 : n;
2144
+ let r = "";
2145
+ for (let k = 0; k < m; k++) r += (k > 0 ? cs[k - 1] : "") + w;
2146
+ return r + cs.slice(m - 1).join("");
2454
2147
  }`;
2455
2148
 
2456
2149
  // Runtime loader emitted at the top of the facade.
2457
- const wasmLoaderJs = (stdlibEnv) => `${WASM_IMPORTS_JS}
2458
- ${stdlibEnv ? WASM_STDLIB_JS : ""}
2150
+ const wasmLoaderJs = `${WASM_IMPORTS_JS}
2459
2151
  async function ${P}fetch(url) {
2460
2152
  const res = await fetch(url);
2461
2153
  if (!res.ok) throw new Error("GoFront: failed to fetch " + url + " (" + res.status + ")");
@@ -2472,17 +2164,19 @@ async function ${P}instantiate(imports) {
2472
2164
  }
2473
2165
  return WebAssembly.instantiate(await ${P}fetch(url), imports);
2474
2166
  }
2167
+ let ${P}flushOut = () => {};
2475
2168
  async function ${P}load(stringTable, extraEnv) {
2476
2169
  if (typeof WebAssembly === "undefined" || typeof WebAssembly.Tag !== "function") {
2477
2170
  throw new Error("GoFront: this runtime lacks the WebAssembly GC / exception support required by app.wasm");
2478
2171
  }
2479
2172
  const tag = new WebAssembly.Tag({ "parameters": ["externref"] });
2480
- const imports = ${P}imports(stringTable, extraEnv, tag, (s) => console.log(s));
2173
+ const imports = ${P}imports(stringTable, extraEnv, tag, (s) => console.log(s), true);
2481
2174
  // Panics arrive as plain JS Errors thrown by env.panic (also from the
2482
2175
  // start function while package-level initializers run), so exports are
2483
2176
  // returned raw: no try/catch wrapper, which keeps JS→wasm calls inlinable.
2484
2177
  const result = await ${P}instantiate(imports);
2485
2178
  imports.__bind(result.instance.exports);
2179
+ ${P}flushOut = imports.__flush;
2486
2180
  return result.instance.exports;
2487
2181
  }
2488
2182
  // Go int/int64 cross the boundary as f64 (exact within the safe-integer range).
@@ -2536,32 +2230,63 @@ function ${P}idxview(n, getAt, setAt) {
2536
2230
  }`;
2537
2231
 
2538
2232
  // Generates the JS facade for the linked wasm module.
2233
+ // A facade binding for a Go name that shadows a browser global carries the
2234
+ // same `$` suffix as JS output uses (see jsBindingName in backend/js), so the
2235
+ // flat bundle keeps reaching the real global.
2236
+ function facadeName(name) {
2237
+ return BROWSER_GLOBALS.has(name) ? `${name}$` : name;
2238
+ }
2239
+
2539
2240
  export function generateFacade(
2540
2241
  meta,
2541
- { stringTable = [], callMain = false, stdlibEnv = true } = {},
2242
+ { stringTable = [], callMain = false } = {},
2542
2243
  ) {
2543
2244
  const needs = meta._needs ?? collectNeeds(meta);
2544
- const out = [wasmLoaderJs(stdlibEnv)];
2245
+ const out = [wasmLoaderJs];
2545
2246
 
2546
2247
  // JS callback imports (registered before instantiation; bodies run after).
2547
2248
  out.push(`const ${P}env = {};`);
2548
2249
  for (const [key, desc] of needs.funcs) {
2549
2250
  const names = desc.params.map((_, i) => `a${i}`);
2550
- const args = desc.params.map((d, i) =>
2551
- isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i]),
2552
- );
2553
- const call = `fn(${args.join(", ")})`;
2251
+ const hasPtrBox = desc.params.some((d) => d.k === "ptr_box");
2554
2252
  let body;
2555
- if (desc.returns.length === 0) body = `${call};`;
2556
- else if (desc.returns.length === 1)
2557
- body = `return ${inExpr(desc.returns[0], call)};`;
2558
- else
2559
- body = `const __r = ${call}; return [${desc.returns
2560
- .map((d, i) => inExpr(d, `__r[${i}]`))
2561
- .join(", ")}];`;
2253
+ if (hasPtrBox) {
2254
+ const argVars = desc.params.map((_, i) => `__arg${i}`);
2255
+ const decls = desc.params.map(
2256
+ (d, i) =>
2257
+ `const __arg${i} = ${isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i])};`,
2258
+ );
2259
+ const call = `fn(${argVars.join(", ")})`;
2260
+ const writebacks = desc.params
2261
+ .map((d, i) =>
2262
+ d.k === "ptr_box"
2263
+ ? `if (__arg${i} && __arg${i}.value !== undefined) __w.__box_set_${d.key}(${names[i]}, ${inExpr(d.base, `__arg${i}.value`)});`
2264
+ : "",
2265
+ )
2266
+ .filter(Boolean);
2267
+ if (desc.returns.length === 0) {
2268
+ body = `${decls.join(" ")} ${call}; ${writebacks.join(" ")}`;
2269
+ } else if (desc.returns.length === 1) {
2270
+ body = `${decls.join(" ")} const __r = ${call}; ${writebacks.join(" ")} return ${inExpr(desc.returns[0], "__r")};`;
2271
+ } else {
2272
+ body = `${decls.join(" ")} const __r = ${call}; ${writebacks.join(" ")} return [${desc.returns.map((d, i) => inExpr(d, `__r[${i}]`)).join(", ")}];`;
2273
+ }
2274
+ } else {
2275
+ const args = desc.params.map((d, i) =>
2276
+ isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i]),
2277
+ );
2278
+ const call = `fn(${args.join(", ")})`;
2279
+ if (desc.returns.length === 0) body = `${call};`;
2280
+ else if (desc.returns.length === 1)
2281
+ body = `return ${inExpr(desc.returns[0], call)};`;
2282
+ else
2283
+ body = `const __r = ${call}; return [${desc.returns
2284
+ .map((d, i) => inExpr(d, `__r[${i}]`))
2285
+ .join(", ")}];`;
2286
+ }
2562
2287
  const sig = ["fn", ...names].join(", ");
2563
2288
  out.push(
2564
- `${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic) __w.__push_panic(__e.message ?? String(__e)); throw __e; } };`,
2289
+ `${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic && !(__e && __e.__gfp === true)) { __w.__push_panic(__e.message ?? String(__e)); try { __e.__gfp = true; } catch {} } throw __e; } };`,
2565
2290
  );
2566
2291
  }
2567
2292
  out.push(
@@ -2572,9 +2297,11 @@ export function generateFacade(
2572
2297
  if (info.pkgTarget === "both") genBothStruct(out, name, fields);
2573
2298
  else {
2574
2299
  const methods = meta.structByName.get(name)?.methods ?? [];
2575
- genHandleStruct(out, name, fields, methods);
2300
+ const promoted = promotedMethods(info, fields, methods, meta);
2301
+ genHandleStruct(out, name, fields, methods, promoted);
2576
2302
  }
2577
2303
  }
2304
+ genGenericClasses(out, needs, meta._mod);
2578
2305
 
2579
2306
  for (const [name, desc] of needs.anonStructs) {
2580
2307
  out.push(`function ${P}to_${name}(o) {
@@ -2596,12 +2323,17 @@ ${desc.fields.map((f) => `\to.${f.name} = ${outExpr(f.desc, `__w.__get_${name}_$
2596
2323
  for (const [key, desc] of needs.boxes) {
2597
2324
  out.push(`function ${P}boxin_${key}(b) {
2598
2325
  if (b == null) return null;
2326
+ if (b.__ref) return b.__ref;
2599
2327
  const v = b.value !== undefined ? b.value : ${zeroExpr(desc.base)};
2600
2328
  return __w.__box_new_${key}(${inExpr(desc.base, "v")});
2601
2329
  }
2602
2330
  function ${P}boxout_${key}(r) {
2603
2331
  if (r == null) return null;
2604
- return { "value": ${outExpr(desc.base, `__w.__box_get_${key}(r)`)} };
2332
+ return {
2333
+ __ref: r,
2334
+ get value() { return ${outExpr(desc.base, `__w.__box_get_${key}(r)`)}; },
2335
+ set value(v) { __w.__box_set_${key}(r, ${inExpr(desc.base, "v")}); }
2336
+ };
2605
2337
  }
2606
2338
  function ${P}boxback_${key}(r, b) {
2607
2339
  if (r == null || b == null) return;
@@ -2610,6 +2342,27 @@ function ${P}boxback_${key}(r, b) {
2610
2342
  }
2611
2343
 
2612
2344
  for (const [key, desc] of needs.slices) {
2345
+ if (desc.elem.k === "f32x4") {
2346
+ // []F32x4 travels as one Float32Array of 4n lanes.
2347
+ out.push(`function ${P}slin_${key}(arr) {
2348
+ if (arr == null) return __w.__slice_nil_${key}();
2349
+ const n = arr.length >> 2;
2350
+ const s = __w.__slice_new_${key}(n);
2351
+ for (let i = 0; i < n; i++) __w.__slice_set_${key}(s, i, ${P}f32x4in(arr.subarray ? arr.subarray(4 * i, 4 * i + 4) : arr.slice(4 * i, 4 * i + 4)));
2352
+ return s;
2353
+ }
2354
+ function ${P}slout_${key}(s) {
2355
+ if (s == null || __w.__slice_is_nil_${key}(s)) return null;
2356
+ const n = __w.__slice_len_${key}(s);
2357
+ const out = new Float32Array(4 * n);
2358
+ for (let i = 0; i < n; i++) out.set(${P}f32x4out(__w.__slice_get_${key}(s, i)), 4 * i);
2359
+ return out;
2360
+ }
2361
+ function ${P}slview_${key}(s) {
2362
+ return ${P}slout_${key}(s);
2363
+ }`);
2364
+ continue;
2365
+ }
2613
2366
  out.push(`function ${P}slin_${key}(arr) {
2614
2367
  if (arr == null) return __w.__slice_nil_${key}();
2615
2368
  const n = arr.length;
@@ -2630,6 +2383,15 @@ function ${P}slview_${key}(s) {
2630
2383
  } `);
2631
2384
  }
2632
2385
 
2386
+ if (needs.maps.size > 0) {
2387
+ // GoFront JS code reads a map as a plain object (`m["a"]`, `len(m)`), so
2388
+ // each entry is also an own property of the returned Map unless that name
2389
+ // is one the Map already has.
2390
+ out.push(`function ${P}mapprop(m, k, v) {
2391
+ const p = String(k);
2392
+ if (!(p in m)) m[p] = v;
2393
+ }`);
2394
+ }
2633
2395
  for (const [key, desc] of needs.maps) {
2634
2396
  out.push(`function ${P}mapin_${key}(m) {
2635
2397
  if (m == null) return null;
@@ -2649,12 +2411,41 @@ function ${P}mapout_${key}(m) {
2649
2411
  if (ks) {
2650
2412
  for (let i = 0; i < ks.length; i++) {
2651
2413
  out.set(ks[i], vs[i]);
2414
+ ${P}mapprop(out, ks[i], vs[i]);
2652
2415
  }
2653
2416
  }
2654
2417
  return out;
2655
2418
  }`);
2656
2419
  }
2657
2420
 
2421
+ if (needs.simd) {
2422
+ const k = needs.simd.key;
2423
+ out.push(`function ${P}v4in(words) {
2424
+ const a = __w.__array_new_${k}(4);
2425
+ for (let i = 0; i < 4; i++) __w.__array_set_${k}(a, i, words[i]);
2426
+ return a;
2427
+ }
2428
+ function ${P}v4out(a) {
2429
+ const words = new Int32Array(4);
2430
+ for (let i = 0; i < 4; i++) words[i] = __w.__array_get_${k}(a, i);
2431
+ return words;
2432
+ }
2433
+ function ${P}f32x4in(v) {
2434
+ return ${P}v4in(new Int32Array(new Float32Array(v ?? 4).buffer));
2435
+ }
2436
+ function ${P}f32x4out(a) {
2437
+ return new Float32Array(${P}v4out(a).buffer);
2438
+ }
2439
+ function ${P}mask4in(v) {
2440
+ const w = new Int32Array(4);
2441
+ if (v) for (let i = 0; i < 4; i++) w[i] = v[i] ? -1 : 0;
2442
+ return ${P}v4in(w);
2443
+ }
2444
+ function ${P}mask4out(a) {
2445
+ return Array.from(${P}v4out(a), (w) => w !== 0);
2446
+ }`);
2447
+ }
2448
+
2658
2449
  for (const [key, desc] of needs.arrays) {
2659
2450
  out.push(`function ${P}arrzero_${key}(n) {
2660
2451
  const out = new Array(n);
@@ -2748,28 +2539,6 @@ function ${P}errin(e) {
2748
2539
  }`);
2749
2540
  }
2750
2541
 
2751
- if (needs.time) {
2752
- out.push(`function ${P}timeout(r) {
2753
- if (r == null) return null;
2754
- const ms = __w.__time_unix_milli ? __w.__time_unix_milli(r) : 0;
2755
- const off = __w.__time_offset ? __w.__time_offset(r) : 0;
2756
- return { _d: new Date(ms), _offset: off };
2757
- }
2758
- function ${P}timeout_ptr(r) {
2759
- return ${P}timeout(r);
2760
- }
2761
- function ${P}timein(v) {
2762
- if (v == null) return null;
2763
- const d = v._d ? v._d : (v instanceof Date ? v : new Date(v));
2764
- const ms = d.getTime();
2765
- const off = typeof v?._offset === "number" ? v._offset : -d.getTimezoneOffset() * 60;
2766
- return __w.__time_make ? __w.__time_make(ms, off) : null;
2767
- }
2768
- function ${P}timein_ptr(v) {
2769
- return ${P}timein(v);
2770
- }`);
2771
- }
2772
-
2773
2542
  if (needs.shared) {
2774
2543
  // Zero-copy TypedArray views over the exported memory. Memory never
2775
2544
  // grows after the start function (allocation is startup-only), but the
@@ -2794,15 +2563,17 @@ function ${P}shin(v, Ctor) {
2794
2563
  for (const c of meta.consts) {
2795
2564
  if (c.dynamic) {
2796
2565
  out.push(
2797
- `const ${c.name} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
2566
+ `const ${facadeName(c.name)} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
2798
2567
  );
2799
2568
  } else {
2800
- out.push(`const ${c.name} = ${c.value};`);
2569
+ out.push(`const ${facadeName(c.name)} = ${c.value};`);
2801
2570
  }
2802
2571
  }
2803
2572
  for (const v of meta.vars ?? [])
2804
2573
  out.push(
2805
- `const ${v.name} = ${P}shout(__w.__var_${v.name}(), ${v.desc.ctor});`,
2574
+ v.shared
2575
+ ? `const ${facadeName(v.name)} = ${P}shout(__w.__var_${v.name}(), ${v.desc.ctor});`
2576
+ : `const ${facadeName(v.name)} = ${outExpr(v.desc, `__w.__var_${v.name}()`)};`,
2806
2577
  );
2807
2578
 
2808
2579
  for (const f of meta.funcs) {
@@ -2811,13 +2582,22 @@ function ${P}shin(v, Ctor) {
2811
2582
  f.params,
2812
2583
  f.returns,
2813
2584
  );
2814
- out.push(`function ${f.name}(${names.join(", ")}) {\n${body}\n}`);
2585
+ out.push(
2586
+ `function ${facadeName(f.name)}(${restParams(names, f.variadic)}) {\n${body}\n}`,
2587
+ );
2815
2588
  }
2816
2589
 
2817
- if (callMain) out.push("__w.main();");
2590
+ if (callMain) out.push(`try { __w.main(); } finally { ${P}flushOut(); }`);
2818
2591
  return bindExports(out.join("\n"));
2819
2592
  }
2820
2593
 
2594
+ // A variadic Go function is a slice parameter in wasm; JS callers pass the
2595
+ // trailing arguments spread, so the facade collects them with a rest param.
2596
+ function restParams(names, variadic) {
2597
+ if (!variadic || names.length === 0) return names.join(", ");
2598
+ return [...names.slice(0, -1), `...${names.at(-1)}`].join(", ");
2599
+ }
2600
+
2821
2601
  // Rewrites `__w.name(...)` into calls through module-level `const` bindings.
2822
2602
  // V8 only inlines a JS→wasm call (and so passes f32/f64 arguments unboxed)
2823
2603
  // when the callee is a compile-time constant; a property load off the exports
@@ -2858,8 +2638,26 @@ function genBothStruct(out, name, fields) {
2858
2638
  const got = `__w.__get_${name}_${f.name}(this.__ref)`;
2859
2639
  return `"${f.name}": { "enumerable": true, "get"() { return ${viewExpr(f.desc, got)}; }, "set"(v) { __w.__set_${name}_${f.name}(this.__ref, ${inExpr(f.desc, "v")}); } }`;
2860
2640
  });
2641
+ // The one conversion `time.Time` has beyond the ordinary struct rules: JS
2642
+ // code may pass a Date (or an object holding one in `_d`), and a Time
2643
+ // handed to JS also reads as a Date through `_d`.
2644
+ const isTime = name === "Time$$time";
2645
+ if (isTime)
2646
+ out.push(`function ${P}time_in(o) {
2647
+ const d = o instanceof Date ? o : o?._d instanceof Date ? o._d : null;
2648
+ if (d === null) return o;
2649
+ const ms = d.getTime();
2650
+ const off = typeof o._offset === "number" ? o._offset : -d.getTimezoneOffset() * 60;
2651
+ const sec = Math.floor(ms / 1000);
2652
+ return new Time$$time(BigInt(sec) + 62135596800n, (ms - sec * 1000) * 1000000, new Location$$time("", off, 0));
2653
+ }
2654
+ function ${P}time_out(t) {
2655
+ Object.defineProperty(t, "_d", { configurable: true, get() { return new Date(Number(this.sec - 62135596800n) * 1000 + Math.floor(this.nsec / 1000000)); } });
2656
+ Object.defineProperty(t, "_offset", { configurable: true, get() { return this.loc ? this.loc.offset : 0; } });
2657
+ return t;
2658
+ }`);
2861
2659
  out.push(`function ${P}to_${name}(o, isValue, m) {
2862
- if (o == null) return isValue ? __w.__zero_${name}() : null;
2660
+ ${isTime ? `\to = ${P}time_in(o);\n` : ""} if (o == null) return isValue ? __w.__zero_${name}() : null;
2863
2661
  if (m) { const hit = m.get(o); if (hit) return hit; }
2864
2662
  const r = __w.__new_${name}(${toArgs.join(", ")});
2865
2663
  if (m) m.set(o, r);
@@ -2867,7 +2665,7 @@ function genBothStruct(out, name, fields) {
2867
2665
  }
2868
2666
  function ${P}from_${name}(r) {
2869
2667
  if (r == null) return null;
2870
- return new ${name}(${fromArgs.join(", ")});
2668
+ return ${isTime ? `${P}time_out(new ${name}(${fromArgs.join(", ")}))` : `new ${name}(${fromArgs.join(", ")})`};
2871
2669
  }
2872
2670
  // Live views share one prototype and are cached per wasm object so hot paths
2873
2671
  // (\`h.Pos.X = 1\` in a frame loop) do not allocate a wrapper per access. The
@@ -2926,7 +2724,84 @@ function ${P}ifin_${key}(v) {
2926
2724
  }`);
2927
2725
  }
2928
2726
 
2929
- function genHandleStruct(out, name, fields, methods) {
2727
+ // Methods a struct gets from its embedded structs, shallowest first (a name
2728
+ // found twice at one depth is ambiguous and left out). Each is forwarded
2729
+ // through the embedded field's getter: `{ name, via, params, variadic }`.
2730
+ function promotedMethods(info, fields, methods, meta) {
2731
+ const taken = new Set([
2732
+ ...methods.map((m) => m.name),
2733
+ ...fields.map((f) => f.name),
2734
+ ]);
2735
+ const promoted = [];
2736
+ const seen = new Set([info]);
2737
+ let level = [{ info, via: "" }];
2738
+ while (level.length > 0) {
2739
+ const next = [];
2740
+ const found = new Map();
2741
+ for (const { info: owner, via } of level) {
2742
+ for (const e of owner.embeds ?? []) {
2743
+ const typeName = e.typeName ?? e.name;
2744
+ const path = `${via}.${e.name}`;
2745
+ const inner = meta._mod.getStructType(typeName);
2746
+ const innerMethods =
2747
+ meta.structByName.get(typeName.replaceAll(".", "$"))?.methods ?? [];
2748
+ for (const m of innerMethods) {
2749
+ if (!found.has(m.name)) found.set(m.name, []);
2750
+ found.get(m.name).push({
2751
+ name: m.name,
2752
+ via: path,
2753
+ params: m.params,
2754
+ variadic: m.variadic,
2755
+ });
2756
+ }
2757
+ if (inner && !seen.has(inner)) {
2758
+ seen.add(inner);
2759
+ next.push({ info: inner, via: path });
2760
+ }
2761
+ }
2762
+ }
2763
+ for (const [n, c] of found) {
2764
+ if (!taken.has(n) && c.length === 1) promoted.push(c[0]);
2765
+ taken.add(n);
2766
+ }
2767
+ level = next;
2768
+ }
2769
+ return promoted;
2770
+ }
2771
+
2772
+ // A generic struct is built in a js main as `new Box(V, [...zeroFactories,
2773
+ // "int"])`; the facade only has the instances (`Box$int`). `Box` picks the
2774
+ // instance named by the last type-argument entry (the printed type arguments).
2775
+ function genGenericClasses(out, needs, mod) {
2776
+ const groups = new Map();
2777
+ for (const [name, { info }] of needs.structs) {
2778
+ if (info.pkgTarget === "both") continue;
2779
+ const goName = mod?.checker?.types?.get(info.name)?._goName;
2780
+ const m = goName ? /^([^[]+)\[(.*)\]$/s.exec(goName) : null;
2781
+ if (!m) continue;
2782
+ if (!groups.has(m[1])) groups.set(m[1], new Map());
2783
+ // The js caller keys instances by the unqualified printed arguments.
2784
+ groups
2785
+ .get(m[1])
2786
+ .set(m[2].replaceAll(" ", "").replace(/(?<![\w.])main\./g, ""), name);
2787
+ }
2788
+ for (const [base, instances] of groups) {
2789
+ const table = [...instances]
2790
+ .map(([id, cls]) => `${JSON.stringify(id)}: ${cls}`)
2791
+ .join(", ");
2792
+ out.push(`class ${facadeName(base)} {
2793
+ constructor(...a) {
2794
+ const tz = a.at(-1);
2795
+ const id = Array.isArray(tz) && typeof tz.at(-1) === "string" ? tz.at(-1).replaceAll(" ", "") : undefined;
2796
+ const C = id === undefined ? ${instances.size === 1 ? `${[...instances.values()][0]}` : "undefined"} : { ${table} }[id];
2797
+ if (!C) throw new TypeError("GoFront: no ${base} instance for the type arguments " + id + " in the wasm package");
2798
+ return new C(...(Array.isArray(tz) ? a.slice(0, -1) : a));
2799
+ }
2800
+ }`);
2801
+ }
2802
+ }
2803
+
2804
+ function genHandleStruct(out, name, fields, methods, promoted = []) {
2930
2805
  // `$`-suffixed params keep the mangler from renaming the matching getters.
2931
2806
  const ctorParams = fields.map((f) => `${f.name}$ = ${zeroExpr(f.desc)}`);
2932
2807
  const ctorArgs = fields.map((f) => inExpr(f.desc, `${f.name}$`));
@@ -2974,7 +2849,13 @@ class ${name} {
2974
2849
  [recv],
2975
2850
  );
2976
2851
  lines.push(
2977
- `\t${m.name}(${names.join(", ")}) {\n${indent(body, "\t")}\n\t}`,
2852
+ `\t${m.name}(${restParams(names, m.variadic)}) {\n${indent(body, "\t")}\n\t}`,
2853
+ );
2854
+ }
2855
+ for (const m of promoted) {
2856
+ const names = m.params.map((_, i) => `a${i}`);
2857
+ lines.push(
2858
+ `\t${m.name}(${restParams(names, m.variadic)}) { return this${m.via}.${m.name}(${restParams(names, m.variadic)}); }`,
2978
2859
  );
2979
2860
  }
2980
2861
  lines.push("}");