gofront 1.7.0 → 1.8.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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.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,
@@ -34,6 +34,7 @@
34
34
  // - `*testing.T`: the harness's JS test object, passed as an opaque
35
35
  // externref (test methods are routed back to JS via `env.testing_*`).
36
36
 
37
+ import { BROWSER_GLOBALS } from "../../typechecker/types.js";
37
38
  import {
38
39
  HELPER_BASE64,
39
40
  HELPER_DURATION,
@@ -41,6 +42,7 @@ import {
41
42
  HELPER_HEX,
42
43
  HELPER_PATH_CLEAN,
43
44
  HELPER_SHA256,
45
+ HELPER_SPRINT,
44
46
  HELPER_SPRINTF,
45
47
  HELPER_STRCONV,
46
48
  } from "../js/runtime.js";
@@ -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
 
@@ -148,10 +154,7 @@ function classifyType(goType, mod) {
148
154
  case "interface":
149
155
  return classifyInterface(t, mod);
150
156
  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
- };
157
+ return classifySimd(t, mod);
155
158
  default:
156
159
  return {
157
160
  k: "unsupported",
@@ -219,12 +222,11 @@ function classifyNamed(t, mod) {
219
222
  if (
220
223
  t.name === "simd.F32x4" ||
221
224
  t.name === "F32x4" ||
225
+ t.name === "simd.Mask32x4" ||
226
+ t.name === "Mask32x4" ||
222
227
  t.underlying?.kind === "simd"
223
228
  ) {
224
- return {
225
- k: "unsupported",
226
- what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
227
- };
229
+ return classifySimd(t.underlying ?? t, mod, t.name);
228
230
  }
229
231
  if (t.name === "error") return { k: "error", wType: "anyref" };
230
232
  if (t.name === "time.Duration")
@@ -239,6 +241,18 @@ function classifyNamed(t, mod) {
239
241
  return { k: "unsupported", what: `named type '${t.name}'` };
240
242
  }
241
243
 
244
+ // F32x4 crosses as a copied 4-element Float32Array, Mask32x4 as a 4-element
245
+ // boolean array. Both travel through the wasm boundary as a GC array of the
246
+ // four i32 lane words (`arr`), converted by __v128_from/__v128_to.
247
+ function classifySimd(t, mod, name = "") {
248
+ const isMask = t.simd === "Mask32x4" || name.endsWith("Mask32x4");
249
+ const arr = classifyArray(
250
+ { kind: "array", elem: { kind: "basic", name: "int32" }, size: 4 },
251
+ mod,
252
+ );
253
+ return { k: isMask ? "mask4" : "f32x4", wType: "v128", arr };
254
+ }
255
+
242
256
  function classifyAnonStruct(t, mod) {
243
257
  const rawFields = [];
244
258
  if (t.fields instanceof Map) {
@@ -282,13 +296,22 @@ function classifyPointer(t, mod) {
282
296
  if (base.k === "anon_struct" && !base.ptr) return { ...base, ptr: true };
283
297
  if (base.k === "time" && !base.ptr) return { ...base, ptr: true };
284
298
  const boxInfo = mod.getBoxType(t.base);
299
+ const ptrInfo = mod.getPtrType ? mod.getPtrType(t.base) : null;
285
300
  const key = `${base.k}${base.unsigned ? "_u" : ""}_${boxInfo.typeIndex}`;
286
301
  return {
287
302
  k: "ptr_box",
288
303
  base,
289
304
  boxInfo,
305
+ ptrInfo,
290
306
  key,
291
- wType: { kind: "ref", nullable: true, typeIndex: boxInfo.typeIndex },
307
+ wType: ptrInfo
308
+ ? {
309
+ kind: "ref",
310
+ nullable: true,
311
+ typeIndex: ptrInfo.typeIndex,
312
+ typeName: ptrInfo.name,
313
+ }
314
+ : { kind: "ref", nullable: true, typeIndex: boxInfo.typeIndex },
292
315
  };
293
316
  }
294
317
 
@@ -352,7 +375,11 @@ function classifySlice(t, mod) {
352
375
  k: "slice",
353
376
  elem,
354
377
  sliceInfo,
355
- key: wTypeKey(sliceInfo.elemWType),
378
+ // []F32x4 and []Mask32x4 share a wasm slice type but cross differently,
379
+ // so they get separate helper keys.
380
+ key:
381
+ wTypeKey(sliceInfo.elemWType) +
382
+ (elem.k === "f32x4" || elem.k === "mask4" ? `_${elem.k}` : ""),
356
383
  wType: { kind: "ref", nullable: true, typeIndex: sliceInfo.typeIndex },
357
384
  };
358
385
  }
@@ -387,6 +414,9 @@ function classifySig(t, mod) {
387
414
  }
388
415
 
389
416
  function classifyFunc(t, mod) {
417
+ // A variadic func value is a slice-taking closure in wasm but a rest-param
418
+ // function in JS; no adapter yet, so it may not cross.
419
+ if (t.variadic) return { k: "unsupported", what: "variadic func value" };
390
420
  const sig = classifySig(t, mod);
391
421
  if (sig.k === "unsupported") return sig;
392
422
  const closureInfo = mod.getClosureType(t);
@@ -451,7 +481,7 @@ function classifyNamedInterface(t, mod) {
451
481
  const desc = {
452
482
  k: "iface",
453
483
  name: t.name,
454
- key: t.name,
484
+ key: t.name.replaceAll(".", "_"),
455
485
  wType: "anyref",
456
486
  methods: [],
457
487
  concrete: [],
@@ -550,7 +580,7 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
550
580
  } else if (d.kind === "ConstDecl" && pkgTarget === "wasm") {
551
581
  collectConstDecl(d, pkgName, meta, errors);
552
582
  } else if (d.kind === "VarDecl" && pkgTarget === "wasm") {
553
- collectSharedVarDecl(d, meta, mod);
583
+ collectVarDecl(d, meta, mod);
554
584
  }
555
585
  }
556
586
  }
@@ -632,17 +662,24 @@ function checkDesc(desc, where, errors) {
632
662
  }
633
663
 
634
664
  // 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) {
665
+ // TypedArray views (read once after instantiation; sizing is fixed). Other
666
+ // exported vars of a supported boundary type are read once when the module
667
+ // loads, after package initialization (`var Limit = 40` is `Limit` in the
668
+ // facade); a var of an unsupported type is left out.
669
+ function collectVarDecl(d, meta, mod) {
637
670
  for (const spec of d.decls ?? d.specs ?? [d]) {
638
671
  const names = spec.names ?? (spec.name ? [spec.name] : []);
639
672
  const values = spec.value ?? (spec.init ? [spec.init] : []);
640
673
  for (let i = 0; i < names.length; i++) {
641
674
  const name = names[i];
642
675
  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) });
676
+ const goType =
677
+ spec.type ?? mod.resolveGlobal(name)?.goType ?? values[i]?._type;
678
+ const shared = Boolean(getSharedInfo(goType, mod.checker));
679
+ const desc = classifyType(goType, mod);
680
+ if (!shared && (!desc || desc.k === "unsupported" || desc.k === "void"))
681
+ continue;
682
+ meta.vars.push({ name, desc, shared });
646
683
  }
647
684
  }
648
685
  }
@@ -657,6 +694,7 @@ function collectMethodMeta(fn, meta, errors, mod) {
657
694
  exportName: fn._exportName,
658
695
  fnName: fn.name,
659
696
  ptrRecv: fn.params[0].type?.kind === "pointer",
697
+ variadic: Boolean(fn.params.at(-1)?._variadic),
660
698
  params: fn.params
661
699
  .slice(1)
662
700
  .map((p) => checkDesc(classifyType(p.type, mod), where, errors)),
@@ -673,6 +711,7 @@ function collectFuncMeta(fn, meta, errors, mod) {
673
711
  name: fn.name,
674
712
  exportName: fn.name,
675
713
  pkgName: fn._pkgName,
714
+ variadic: Boolean(fn.params?.at(-1)?._variadic),
676
715
  params: (fn.params ?? []).map((p) =>
677
716
  checkDesc(classifyType(p.type, mod), where, errors),
678
717
  ),
@@ -711,6 +750,11 @@ function visitNeed(desc, needs, errors, mod) {
711
750
  case "error":
712
751
  needs.error = true;
713
752
  return;
753
+ case "f32x4":
754
+ case "mask4":
755
+ needs.simd = desc.arr;
756
+ visitNeed(desc.arr, needs, errors, mod);
757
+ return;
714
758
  case "time":
715
759
  needs.time = true;
716
760
  return;
@@ -791,6 +835,7 @@ function collectNeeds(meta) {
791
835
  ifaces: new Map(), // interface name -> desc
792
836
  error: false,
793
837
  time: false,
838
+ simd: null, // array desc carrying vectors across the boundary
794
839
  shared: false, // any shared buffer on the surface
795
840
  };
796
841
  const errors = [];
@@ -835,16 +880,22 @@ export function scanBoundaryImports(mod, meta) {
835
880
  function isGcRef(wType) {
836
881
  return wType === "anyref" || (typeof wType === "object" && wType !== null);
837
882
  }
883
+ // A v128 crosses as an externref holding a GC array of its four i32 lanes;
884
+ // the converting helpers are registered first thing in addBoundaryHelpers.
885
+ const v128Conv = { from: null, to: null };
886
+
838
887
  function extType(wType) {
888
+ if (wType === "v128") return "externref";
839
889
  if (wType === "i64") return "f64";
840
890
  if (isGcRef(wType)) return "externref";
841
891
  return wType;
842
892
  }
843
893
  function needsConv(wTypes) {
844
- return wTypes.some((w) => w === "i64" || isGcRef(w));
894
+ return wTypes.some((w) => w === "i64" || w === "v128" || isGcRef(w));
845
895
  }
846
896
  // Instructions converting the value on the stack from its boundary form.
847
897
  function fromExt(wType) {
898
+ if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.from }];
848
899
  if (wType === "i64") return [{ op: "i64.trunc_sat_f64_s" }];
849
900
  if (wType === "anyref") return [{ op: "any.convert_extern" }];
850
901
  if (isGcRef(wType)) {
@@ -857,6 +908,7 @@ function fromExt(wType) {
857
908
  }
858
909
  // Instructions converting the value on the stack to its boundary form.
859
910
  function toExt(wType) {
911
+ if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.to }];
860
912
  if (wType === "i64") return [{ op: "f64.convert_i64_s" }];
861
913
  if (isGcRef(wType)) return [{ op: "extern.convert_any" }];
862
914
  return [];
@@ -875,6 +927,8 @@ function convertResults(wTypes, conv, localBase) {
875
927
  }
876
928
 
877
929
  function zeroInstr(wType) {
930
+ if (wType === "v128")
931
+ return { op: "v128.const", bytes: new Array(16).fill(0) };
878
932
  if (wType === "i64") return { op: "i64.const", value: 0n };
879
933
  if (wType === "f32" || wType === "f64")
880
934
  return { op: `${wType}.const`, value: 0 };
@@ -926,6 +980,47 @@ export function addBoundaryHelpers(mod, meta) {
926
980
  return idx;
927
981
  };
928
982
  const get = (i) => ({ op: "local.get", index: i });
983
+ if (needs.simd) {
984
+ const { arrInfo } = needs.simd;
985
+ const arrRef = needs.simd.wType;
986
+ v128Conv.to = addFunc(
987
+ "__v128_to",
988
+ ["v128"],
989
+ ["externref"],
990
+ [
991
+ ...[0, 1, 2, 3].flatMap((lane) => [
992
+ get(0),
993
+ { op: "i32x4.extract_lane", lane },
994
+ ]),
995
+ { op: "array.new_fixed", typeIndex: arrInfo.typeIndex, size: 4 },
996
+ { op: "extern.convert_any" },
997
+ ],
998
+ false,
999
+ );
1000
+ v128Conv.from = addFunc(
1001
+ "__v128_from",
1002
+ ["externref"],
1003
+ ["v128"],
1004
+ [
1005
+ get(0),
1006
+ { op: "any.convert_extern" },
1007
+ { op: "ref.cast_null", typeIndex: arrInfo.typeIndex },
1008
+ { op: "local.set", index: 1 },
1009
+ get(1),
1010
+ { op: "i32.const", value: 0 },
1011
+ { op: "array.get", typeIndex: arrInfo.typeIndex },
1012
+ { op: "i32x4.splat" },
1013
+ ...[1, 2, 3].flatMap((lane) => [
1014
+ get(1),
1015
+ { op: "i32.const", value: lane },
1016
+ { op: "array.get", typeIndex: arrInfo.typeIndex },
1017
+ { op: "i32x4.replace_lane", lane },
1018
+ ]),
1019
+ ],
1020
+ false,
1021
+ [arrRef],
1022
+ );
1023
+ }
929
1024
  // Parameter `i` converted from its boundary form.
930
1025
  const getIn = (i, wType) => [get(i), ...fromExt(wType)];
931
1026
  const EXT = "externref";
@@ -1169,7 +1264,8 @@ export function addBoundaryHelpers(mod, meta) {
1169
1264
  const paramW = sig.params;
1170
1265
  const resultW = sig.results;
1171
1266
  if (!needsConv(paramW) && !needsConv(resultW)) return;
1172
- entry.callName = `__x_${entry.exportName}`;
1267
+ // Exported names become JS identifiers: no dot of a qualified type.
1268
+ entry.callName = `__x_${entry.exportName}`.replaceAll(".", "_");
1173
1269
  addFunc(
1174
1270
  entry.callName,
1175
1271
  paramW.map(extType),
@@ -1210,7 +1306,7 @@ export function addBoundaryHelpers(mod, meta) {
1210
1306
  [get(0), get(1), { op: "struct.new", typeIndex }, ...toExt(wType)],
1211
1307
  );
1212
1308
  for (const v of meta.vars ?? []) {
1213
- const g = mod.resolveGlobal(v.name);
1309
+ const g = v.shared ? mod.resolveGlobal(v.name) : null;
1214
1310
  if (!g) continue;
1215
1311
  addFunc(
1216
1312
  `__var_${v.name}`,
@@ -1282,12 +1378,18 @@ export function addBoundaryHelpers(mod, meta) {
1282
1378
  [EXT],
1283
1379
  [...getIn(0, w), { op: "struct.new", typeIndex: ti }, ...toExt(ref)],
1284
1380
  );
1381
+ const cast = {
1382
+ op: "ref.cast",
1383
+ typeIndex: boxInfo.typeIndex,
1384
+ typeName: boxInfo.name,
1385
+ };
1285
1386
  addFunc(
1286
1387
  `__box_get_${key}`,
1287
1388
  [EXT],
1288
1389
  [extType(w)],
1289
1390
  [
1290
1391
  ...getIn(0, ref),
1392
+ cast,
1291
1393
  { op: "struct.get", typeIndex: ti, fieldIndex: 0 },
1292
1394
  ...toExt(w),
1293
1395
  ],
@@ -1298,6 +1400,7 @@ export function addBoundaryHelpers(mod, meta) {
1298
1400
  [],
1299
1401
  [
1300
1402
  ...getIn(0, ref),
1403
+ cast,
1301
1404
  ...getIn(1, w),
1302
1405
  { op: "struct.set", typeIndex: ti, fieldIndex: 0 },
1303
1406
  ],
@@ -1390,10 +1493,11 @@ export function addBoundaryHelpers(mod, meta) {
1390
1493
  [EXT],
1391
1494
  [
1392
1495
  get(0),
1393
- { op: "f64.const", value: 1000000 },
1394
- { op: "f64.mul" },
1395
1496
  { op: "i64.trunc_sat_f64_s" },
1497
+ { op: "i64.const", value: 1000000n },
1498
+ { op: "i64.mul" },
1396
1499
  get(1),
1500
+ { op: "ref.null", heapType: "any" },
1397
1501
  { op: "struct.new", typeIndex: timeInfo.typeIndex },
1398
1502
  ...toExt(timeWType),
1399
1503
  ],
@@ -1445,6 +1549,16 @@ export function addBoundaryHelpers(mod, meta) {
1445
1549
  [{ op: "global.get", index: g.index }, ...toExt(c.desc.wType)],
1446
1550
  );
1447
1551
  }
1552
+ for (const v of meta.vars ?? []) {
1553
+ const g = v.shared ? null : mod.resolveGlobal(v.name);
1554
+ if (!g) continue;
1555
+ addFunc(
1556
+ `__var_${v.name}`,
1557
+ [],
1558
+ [extType(v.desc.wType)],
1559
+ [{ op: "global.get", index: g.index }, ...toExt(v.desc.wType)],
1560
+ );
1561
+ }
1448
1562
  }
1449
1563
 
1450
1564
  // `(externref) -> i32`: `ref.test`s param 0 against each type index and
@@ -1529,6 +1643,10 @@ function inExpr(desc, v) {
1529
1643
  return `(${v} === undefined ? null : ${v})`;
1530
1644
  case "error":
1531
1645
  return `${P}errin(${v})`;
1646
+ case "f32x4":
1647
+ return `${P}f32x4in(${v})`;
1648
+ case "mask4":
1649
+ return `${P}mask4in(${v})`;
1532
1650
  case "time":
1533
1651
  return desc.ptr ? `${P}timein_ptr(${v})` : `${P}timein(${v})`;
1534
1652
  case "map":
@@ -1573,6 +1691,10 @@ function outExpr(desc, v) {
1573
1691
  return `(${v} ?? "")`;
1574
1692
  case "error":
1575
1693
  return `${P}errout(${v})`;
1694
+ case "f32x4":
1695
+ return `${P}f32x4out(${v})`;
1696
+ case "mask4":
1697
+ return `${P}mask4out(${v})`;
1576
1698
  case "time":
1577
1699
  return desc.ptr ? `${P}timeout_ptr(${v})` : `${P}timeout(${v})`;
1578
1700
  case "map":
@@ -1613,6 +1735,10 @@ function zeroExpr(desc) {
1613
1735
  return "0";
1614
1736
  case "string":
1615
1737
  return '""';
1738
+ case "f32x4":
1739
+ return "new Float32Array(4)";
1740
+ case "mask4":
1741
+ return "[false, false, false, false]";
1616
1742
  case "error":
1617
1743
  case "time":
1618
1744
  case "map":
@@ -1733,21 +1859,23 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1733
1859
  let lineBuf = "";
1734
1860
  let targs = [];
1735
1861
  let fmtStructMap = new WeakMap();
1862
+ // A value whose type has String()/Error(): the method already ran in WASM.
1863
+ // __gfUnbox hands fmt the raw value for the other verbs.
1864
+ class FmtStr { constructor(r, s) { this.r = r; this.s = s; } __gfUnbox() { return this.r; } String() { return this.s; } }
1865
+ class FmtErr { constructor(r, s) { this.r = r; this.s = s; } __gfUnbox() { return this.r; } Error() { return this.s; } }
1736
1866
  const late = { exports: null };
1737
- const flush = () => { write(lineBuf); lineBuf = ""; };
1867
+ // Go separates operands with a space unconditionally, so track whether the
1868
+ // current line already holds an operand (an empty string leaves lineBuf empty).
1869
+ let started = false;
1870
+ const flush = () => { write(lineBuf); lineBuf = ""; started = false; };
1738
1871
  const print = (v) => {
1739
- const s = String(v);
1740
- if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
1741
- lineBuf += " ";
1742
- }
1872
+ const s = Object.is(v, -0) ? "-0" : String(v);
1873
+ if (started) lineBuf += " ";
1874
+ started = true;
1743
1875
  lineBuf += s;
1744
1876
  };
1745
1877
  const println = (v) => {
1746
- const s = String(v);
1747
- if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
1748
- lineBuf += " ";
1749
- }
1750
- lineBuf += s;
1878
+ print(v);
1751
1879
  flush();
1752
1880
  };
1753
1881
  const targ = (v) => { targs.push(v); };
@@ -1787,6 +1915,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1787
1915
  },
1788
1916
  "str_to_upper": (s) => (s ? s.toUpperCase() : ""),
1789
1917
  "str_to_lower": (s) => (s ? s.toLowerCase() : ""),
1918
+ "str_to_valid_utf8": (s, repl) => (s ? s.replace(/(?:[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF])+/g, repl ?? "") : (s ?? "")),
1790
1919
  "str_trim_space": (s) => (s ? s.trim() : ""),
1791
1920
  "str_contains": (s, sub) => (s && sub !== undefined ? (s.includes(sub) ? 1 : 0) : 0),
1792
1921
  "str_has_prefix": (s, pre) => (s && pre !== undefined ? (s.startsWith(pre) ? 1 : 0) : 0),
@@ -1803,7 +1932,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1803
1932
  },
1804
1933
  "str_from_i64": (n) => String(n),
1805
1934
  "str_from_i32": (n) => String(n),
1806
- "str_from_f64": (f) => String(f),
1935
+ "str_from_f64": (f) => (Object.is(f, -0) ? "-0" : String(f)),
1807
1936
  "is_string": (v) => (typeof v === "string" ? 1 : 0),
1808
1937
  "str_to_bytes": (s) => {
1809
1938
  utf8Encoded = new TextEncoder().encode(s ?? "");
@@ -1836,28 +1965,55 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1836
1965
  "fmt_new_map": () => new Map(),
1837
1966
  "fmt_map_set": (m, k, v) => { m.set(k, v); },
1838
1967
  "fmt_new_struct": (ref, isPtr) => {
1839
- if (ref !== null && ref !== undefined && typeof ref === "object" && fmtStructMap.has(ref)) {
1840
- return fmtStructMap.get(ref);
1968
+ // One object per struct and per kind: a value and a pointer to it
1969
+ // in the same call (fmt.Println(&s, s)) must stay distinct.
1970
+ const slot = isPtr;
1971
+ if (isPtr === 2) {
1972
+ const seenBox = ref !== null && ref !== undefined && typeof ref === "object" ? fmtStructMap.get(ref) : undefined;
1973
+ if (seenBox?.[2]) return seenBox[2];
1974
+ const b = { value: null, __p: true, __box: true };
1975
+ if (ref !== null && ref !== undefined && typeof ref === "object") {
1976
+ if (seenBox) seenBox[2] = b;
1977
+ else fmtStructMap.set(ref, [undefined, undefined, b]);
1978
+ }
1979
+ return b;
1841
1980
  }
1981
+ const isRef = ref !== null && ref !== undefined && typeof ref === "object";
1982
+ const seen = isRef ? fmtStructMap.get(ref) : undefined;
1983
+ if (seen?.[slot]) return seen[slot];
1842
1984
  const o = {};
1843
1985
  Object.defineProperty(o, "__struct", { value: true, configurable: true });
1844
1986
  if (isPtr !== 0) Object.defineProperty(o, "__p", { value: true, configurable: true });
1845
- if (ref !== null && ref !== undefined && typeof ref === "object") fmtStructMap.set(ref, o);
1987
+ if (isRef) {
1988
+ if (seen) seen[slot] = o;
1989
+ else fmtStructMap.set(ref, slot ? [undefined, o] : [o, undefined]);
1990
+ }
1846
1991
  return o;
1847
1992
  },
1993
+ "fmt_struct_type": (o, i) => { Object.defineProperty(o, "__tn", { value: stringTable[i], configurable: true }); },
1848
1994
  "fmt_struct_set": (o, k, v) => { o[typeof k === "number" ? stringTable[k] : k] = v; },
1849
1995
  "fmt_box_i32": (v) => v,
1850
1996
  "fmt_box_i64": (v) => (v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1997
+ "fmt_box_u64": (v) => { v = BigInt.asUintN(64, v); return v > ${P}MAX ? v : Number(v); },
1851
1998
  "fmt_box_f64": (v) => v,
1852
1999
  "fmt_box_bool": (v) => v !== 0,
2000
+ "fmt_new_stringer": (r, s, isErr) => (isErr !== 0 ? new FmtErr(r, s) : new FmtStr(r, s)),
1853
2001
  "fmt_new_box": () => ({ value: null, __p: true, __box: true }),
1854
2002
  "testing_arg_i32": targ, "testing_arg_f32": targ, "testing_arg_f64": targ,
1855
2003
  "testing_arg_str": targ, "testing_arg_any": targ,
1856
2004
  "testing_arg_i64": (v) => targ(v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1857
2005
  "testing_arg_bool": (v) => targ(v !== 0),
2006
+ "testing_arg_u64": (v) => { v = BigInt.asUintN(64, v); targ(v > ${P}MAX ? v : Number(v)); },
2007
+ "fmt_arg_type": (i) => { if (targs.length > 0) targs[targs.length - 1] = __ft(targs[targs.length - 1], JSON.parse(stringTable[i])); },
2008
+ "testing_arg_spread": (a) => { if (a) for (const v of a) targs.push(v); },
2009
+ "testing_arg_err": (s) => targ({ Error() { return s; } }),
1858
2010
  "testing_call": (t, i) => { const a = targs; targs = []; fmtStructMap = new WeakMap(); t[stringTable[i]](...a); },
1859
2011
  "testing_name": (t) => t.Name(),
1860
- "testing_flag": (t, i) => (t[stringTable[i]]() ? 1 : 0),
2012
+ "testing_flag": (t, i) => Number(t[stringTable[i]]()),
2013
+ "testing_n": (t) => BigInt(t.N ?? 0),
2014
+ "testing_elapsed": (t) => BigInt(t.Elapsed()),
2015
+ "testing_context": (t) => t.Context(),
2016
+ "testing_output": (t) => t.Output(),
1861
2017
  };
1862
2018
  // Stdlib imports (strings/strconv/utf8/fmt/t.Run) are a separate block so
1863
2019
  // modules that never call them do not ship it (see WASM_STDLIB_JS).
@@ -1874,7 +2030,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1874
2030
  }
1875
2031
  Object.assign(env, extraEnv);
1876
2032
  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];
2033
+ 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
2034
  m.mod = (x, y) => x % y;
1879
2035
  // Go rounds half away from zero; Math.round rounds half toward +Infinity.
1880
2036
  m.round = (x) => Math.sign(x) * Math.round(Math.abs(x));
@@ -1886,30 +2042,16 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1886
2042
  // module imports one of the packages that use it.
1887
2043
  export const WASM_STDLIB_JS = `${HELPER_ERROR}
1888
2044
  ${HELPER_SPRINTF}
2045
+ ${HELPER_SPRINT}
1889
2046
  ${HELPER_STRCONV}
1890
2047
  ${HELPER_PATH_CLEAN}
1891
2048
  ${HELPER_DURATION}
1892
2049
  ${HELPER_SHA256}
1893
2050
  ${HELPER_HEX}
1894
2051
  ${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;
2052
+ function ${P}stdlib_fmt(stringTable, io) {
1905
2053
  let sscanTokens = [];
1906
2054
  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
2055
  return {
1914
2056
  "fmt_sscan_init": (s, mode) => {
1915
2057
  sscanHasErr = 0;
@@ -2020,9 +2162,26 @@ function ${P}stdlib(stringTable, io) {
2020
2162
  return 0;
2021
2163
  },
2022
2164
  "testing_run": (t, name, fn) => (t.Run(name, (sub) => { io.late.exports.__testing_run_cb(fn, sub); }) ? 1 : 0),
2165
+ "testing_cleanup": (t, fn) => { t.Cleanup(() => { io.late.exports.__testing_cleanup_cb(fn); }); },
2166
+ "testing_n": (t) => BigInt(t.N ?? 0),
2167
+ "testing_elapsed": (t) => BigInt(t.Elapsed()),
2168
+ "testing_context": (t) => t.Context(),
2169
+ "testing_output": (t) => t.Output(),
2023
2170
  "fmt_sprintf": (f) => __sprintf(f ?? "", ...io.take()),
2171
+ "fmt_sprint": (ln) => __sprint(ln !== 0, ...io.take()),
2172
+ "fmt_print": (ln) => { io.printf(__sprint(ln !== 0, ...io.take())); },
2173
+ "fmt_print_raw": (s) => { io.printf(s ?? ""); },
2024
2174
  "fmt_printf": (f) => { io.printf(__sprintf(f ?? "", ...io.take())); },
2025
2175
  "fmt_format_v": (v) => { const res = __sprintf("%v", v); io.take(); return res; },
2176
+ };
2177
+ }
2178
+ function ${P}stdlib_str(stringTable, io) {
2179
+ let parts = [];
2180
+ let cutBefore = "", cutAfter = "";
2181
+ let replacerTarget = "", replacerPairs = [];
2182
+ let strconvErr = null, strconvErrKind = 0;
2183
+ let decodeSize = 0;
2184
+ return {
2026
2185
  "str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
2027
2186
  "str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
2028
2187
  "str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
@@ -2139,7 +2298,7 @@ function ${P}stdlib(stringTable, io) {
2139
2298
  parts = r;
2140
2299
  return parts.length;
2141
2300
  },
2142
- "str_fields": (s) => { parts = (s ?? "").split(/\\s+/).filter((p) => p !== ""); return parts.length; },
2301
+ "str_fields": (s) => { parts = (s ?? "").split(/[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+/).filter((p) => p !== ""); return parts.length; },
2143
2302
  "str_part": (i) => parts[i] ?? "",
2144
2303
  "str_replacer_init": (s) => {
2145
2304
  replacerTarget = s ?? "";
@@ -2254,7 +2413,7 @@ function ${P}stdlib(stringTable, io) {
2254
2413
  // unicode
2255
2414
  "unicode_is_letter": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{L}/u.test(c)) ? 1 : 0; },
2256
2415
  "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; },
2416
+ "unicode_is_space": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /[ \\t\\n\\v\\f\\r\\u0085\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]/.test(c)) ? 1 : 0; },
2258
2417
  "unicode_is_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Lu}/u.test(c)) ? 1 : 0; },
2259
2418
  "unicode_is_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Ll}/u.test(c)) ? 1 : 0; },
2260
2419
  "unicode_is_punct": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{P}/u.test(c)) ? 1 : 0; },
@@ -2263,6 +2422,22 @@ function ${P}stdlib(stringTable, io) {
2263
2422
  "unicode_is_graphic": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
2264
2423
  "unicode_to_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toUpperCase().codePointAt(0) ?? r) : r; },
2265
2424
  "unicode_to_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toLowerCase().codePointAt(0) ?? r) : r; },
2425
+ };
2426
+ }
2427
+ function ${P}stdlib_sys(stringTable, io) {
2428
+ let splitDir = "", splitFile = "";
2429
+ let joinParts = [];
2430
+ let matchHasErr = 0;
2431
+ let trimStart = 0, trimEnd = 0;
2432
+ let timeParseErr = null, timeParseOffset = 0, timeParseDurationErr = null, timeParseSubNs = 0;
2433
+ let rawBytes = new Uint8Array(0);
2434
+ let rawPos = 0;
2435
+ let hexErr = null;
2436
+ let base64Err = null;
2437
+ let customBase64Encodings = [];
2438
+ let bitsRem32 = 0;
2439
+ let bitsRem64 = 0n;
2440
+ return {
2266
2441
  // html
2267
2442
  "html_escape": (s) => (s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&#34;").replace(/'/g, "&#39;"),
2268
2443
  "html_unescape": (s) => (s ?? "").replace(/&(?:quot|apos|#39|#34|gt|lt|amp|#(?:[0-9]+|[xX][0-9a-fA-F]+));/g, (m) => {
@@ -2327,32 +2502,66 @@ function ${P}stdlib(stringTable, io) {
2327
2502
  "time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
2328
2503
  "time_timezone_offset": (ms) => -new Date(ms).getTimezoneOffset() * 60,
2329
2504
  "time_duration_string": (d) => __durationString(d),
2505
+ "time_parse_duration": (s) => {
2506
+ const res = __timeParseDuration(s);
2507
+ if (res[1]) {
2508
+ timeParseDurationErr = res[1].message || String(res[1]);
2509
+ return 0n;
2510
+ }
2511
+ timeParseDurationErr = null;
2512
+ return BigInt(res[0]);
2513
+ },
2514
+ "time_parse_duration_err": () => timeParseDurationErr,
2515
+ "time_month_string": (m) => __monthString(m),
2516
+ "time_weekday_string": (w) => __weekdayString(w),
2330
2517
  "time_parse": (layout, val) => {
2331
2518
  try {
2332
- const d = new Date(val ?? "");
2519
+ let s = String(val ?? "");
2520
+ if (s.length >= 2 && s.startsWith('"') && s.endsWith('"')) {
2521
+ s = s.slice(1, -1);
2522
+ }
2523
+ const d = new Date(s);
2333
2524
  if (isNaN(d.getTime())) throw 0;
2525
+ let subNs = 0;
2526
+ const m = /\\.([0-9]{1,9})/.exec(s);
2527
+ if (m) {
2528
+ subNs = Number(m[1].padEnd(9, "0")) % 1000000;
2529
+ }
2334
2530
  timeParseErr = null;
2335
- timeParseOffset = -d.getTimezoneOffset() * 60;
2531
+ if (s.endsWith("Z") || s.endsWith("z")) {
2532
+ timeParseOffset = 0;
2533
+ } else {
2534
+ const tzMatch = /([+-])(\\d{2}):?(\\d{2})$/.exec(s);
2535
+ if (tzMatch) {
2536
+ const sign = tzMatch[1] === "+" ? 1 : -1;
2537
+ timeParseOffset = sign * (Number(tzMatch[2]) * 3600 + Number(tzMatch[3]) * 60);
2538
+ } else {
2539
+ timeParseOffset = -d.getTimezoneOffset() * 60;
2540
+ }
2541
+ }
2542
+ timeParseSubNs = subNs;
2336
2543
  return d.getTime();
2337
2544
  } catch {
2338
2545
  timeParseErr = "parsing time: cannot parse";
2339
2546
  timeParseOffset = 0;
2547
+ timeParseSubNs = 0;
2340
2548
  return 0;
2341
2549
  }
2342
2550
  },
2343
2551
  "time_parse_err": () => timeParseErr,
2344
2552
  "time_parse_offset": () => timeParseOffset,
2553
+ "time_parse_sub_ns": () => timeParseSubNs,
2345
2554
  "time_format": (y, m, d, hr, min, sec, wd, ms, offsetSec, layout) => {
2346
2555
  const DAYS = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
2347
2556
  const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
2348
2557
  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 => {
2558
+ return (layout ?? "").replace(/2006|06|Monday|Mon|January|Jan|01|02|15|PM|pm|03|04|05|Z07:00|-07:00|-0700|MST|\\.(?:0+|9+)|1|2|3|4|5/g, t => {
2350
2559
  if (t.startsWith(".")) {
2351
2560
  const isZeroPad = t.includes("0");
2352
2561
  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);
2562
+ let frac = pad(ms % 1000000000, 9);
2563
+ if (maxLen <= 9) frac = frac.slice(0, maxLen);
2564
+ else frac = frac.padEnd(maxLen, "0");
2356
2565
  if (!isZeroPad) {
2357
2566
  frac = frac.replace(/0+$/, "");
2358
2567
  return frac.length > 0 ? "." + frac : "";
@@ -2383,6 +2592,15 @@ function ${P}stdlib(stringTable, io) {
2383
2592
  const o = Math.floor(offsetSec / 60);
2384
2593
  return o === 0 ? "Z" : (o > 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
2385
2594
  }
2595
+ case "-07:00": {
2596
+ const o = Math.floor(offsetSec / 60);
2597
+ return (o >= 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
2598
+ }
2599
+ case "-0700": {
2600
+ const o = Math.floor(offsetSec / 60);
2601
+ return (o >= 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + pad(Math.abs(o) % 60);
2602
+ }
2603
+ case "MST": return offsetSec === 0 ? "UTC" : "LOCAL";
2386
2604
  default: return t;
2387
2605
  }
2388
2606
  });
@@ -2442,8 +2660,50 @@ function ${P}stdlib(stringTable, io) {
2442
2660
  return rawBytes.length;
2443
2661
  },
2444
2662
  "base64_get_err": () => base64Err,
2663
+ "bits_div32": (hi, lo, y) => {
2664
+ const yu = y >>> 0;
2665
+ if (yu === 0) throw new Error("runtime error: integer divide by zero");
2666
+ const hiu = hi >>> 0;
2667
+ if (hiu >= yu) throw new Error("runtime error: integer overflow");
2668
+ const z = (BigInt(hiu) << 32n) | BigInt(lo >>> 0);
2669
+ const yB = BigInt(yu);
2670
+ bitsRem32 = Number(z % yB);
2671
+ return Number(z / yB);
2672
+ },
2673
+ "bits_div32_rem": () => bitsRem32,
2674
+ "bits_rem32": (hi, lo, y) => {
2675
+ const yu = y >>> 0;
2676
+ if (yu === 0) throw new Error("runtime error: integer divide by zero");
2677
+ const z = (BigInt(hi >>> 0) << 32n) | BigInt(lo >>> 0);
2678
+ return Number(z % BigInt(yu));
2679
+ },
2680
+ "bits_div64": (hi, lo, y) => {
2681
+ const yB = BigInt.asUintN(64, BigInt(y));
2682
+ if (yB === 0n) throw new Error("runtime error: integer divide by zero");
2683
+ const hiB = BigInt.asUintN(64, BigInt(hi));
2684
+ if (hiB >= yB) throw new Error("runtime error: integer overflow");
2685
+ const loB = BigInt.asUintN(64, BigInt(lo));
2686
+ const z = (hiB << 64n) | loB;
2687
+ bitsRem64 = z % yB;
2688
+ return z / yB;
2689
+ },
2690
+ "bits_div64_rem": () => bitsRem64,
2691
+ "bits_rem64": (hi, lo, y) => {
2692
+ const yB = BigInt.asUintN(64, BigInt(y));
2693
+ if (yB === 0n) throw new Error("runtime error: integer divide by zero");
2694
+ const z = ((BigInt.asUintN(64, BigInt(hi)) % yB) << 64n) | BigInt.asUintN(64, BigInt(lo));
2695
+ return z % yB;
2696
+ },
2445
2697
  };
2446
2698
  }
2699
+ function ${P}stdlib(stringTable, io) {
2700
+ return Object.assign(
2701
+ {},
2702
+ ${P}stdlib_fmt(stringTable, io),
2703
+ ${P}stdlib_str(stringTable, io),
2704
+ ${P}stdlib_sys(stringTable, io),
2705
+ );
2706
+ }
2447
2707
  function ${P}trim(s, cutset, left, right) {
2448
2708
  const cs = new Set(Array.from(cutset));
2449
2709
  const chars = Array.from(s);
@@ -2536,6 +2796,13 @@ function ${P}idxview(n, getAt, setAt) {
2536
2796
  }`;
2537
2797
 
2538
2798
  // Generates the JS facade for the linked wasm module.
2799
+ // A facade binding for a Go name that shadows a browser global carries the
2800
+ // same `$` suffix as JS output uses (see jsBindingName in backend/js), so the
2801
+ // flat bundle keeps reaching the real global.
2802
+ function facadeName(name) {
2803
+ return BROWSER_GLOBALS.has(name) ? `${name}$` : name;
2804
+ }
2805
+
2539
2806
  export function generateFacade(
2540
2807
  meta,
2541
2808
  { stringTable = [], callMain = false, stdlibEnv = true } = {},
@@ -2547,18 +2814,42 @@ export function generateFacade(
2547
2814
  out.push(`const ${P}env = {};`);
2548
2815
  for (const [key, desc] of needs.funcs) {
2549
2816
  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(", ")})`;
2817
+ const hasPtrBox = desc.params.some((d) => d.k === "ptr_box");
2554
2818
  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(", ")}];`;
2819
+ if (hasPtrBox) {
2820
+ const argVars = desc.params.map((_, i) => `__arg${i}`);
2821
+ const decls = desc.params.map(
2822
+ (d, i) =>
2823
+ `const __arg${i} = ${isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i])};`,
2824
+ );
2825
+ const call = `fn(${argVars.join(", ")})`;
2826
+ const writebacks = desc.params
2827
+ .map((d, i) =>
2828
+ d.k === "ptr_box"
2829
+ ? `if (__arg${i} && __arg${i}.value !== undefined) __w.__box_set_${d.key}(${names[i]}, ${inExpr(d.base, `__arg${i}.value`)});`
2830
+ : "",
2831
+ )
2832
+ .filter(Boolean);
2833
+ if (desc.returns.length === 0) {
2834
+ body = `${decls.join(" ")} ${call}; ${writebacks.join(" ")}`;
2835
+ } else if (desc.returns.length === 1) {
2836
+ body = `${decls.join(" ")} const __r = ${call}; ${writebacks.join(" ")} return ${inExpr(desc.returns[0], "__r")};`;
2837
+ } else {
2838
+ body = `${decls.join(" ")} const __r = ${call}; ${writebacks.join(" ")} return [${desc.returns.map((d, i) => inExpr(d, `__r[${i}]`)).join(", ")}];`;
2839
+ }
2840
+ } else {
2841
+ const args = desc.params.map((d, i) =>
2842
+ isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i]),
2843
+ );
2844
+ const call = `fn(${args.join(", ")})`;
2845
+ if (desc.returns.length === 0) body = `${call};`;
2846
+ else if (desc.returns.length === 1)
2847
+ body = `return ${inExpr(desc.returns[0], call)};`;
2848
+ else
2849
+ body = `const __r = ${call}; return [${desc.returns
2850
+ .map((d, i) => inExpr(d, `__r[${i}]`))
2851
+ .join(", ")}];`;
2852
+ }
2562
2853
  const sig = ["fn", ...names].join(", ");
2563
2854
  out.push(
2564
2855
  `${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic) __w.__push_panic(__e.message ?? String(__e)); throw __e; } };`,
@@ -2596,12 +2887,17 @@ ${desc.fields.map((f) => `\to.${f.name} = ${outExpr(f.desc, `__w.__get_${name}_$
2596
2887
  for (const [key, desc] of needs.boxes) {
2597
2888
  out.push(`function ${P}boxin_${key}(b) {
2598
2889
  if (b == null) return null;
2890
+ if (b.__ref) return b.__ref;
2599
2891
  const v = b.value !== undefined ? b.value : ${zeroExpr(desc.base)};
2600
2892
  return __w.__box_new_${key}(${inExpr(desc.base, "v")});
2601
2893
  }
2602
2894
  function ${P}boxout_${key}(r) {
2603
2895
  if (r == null) return null;
2604
- return { "value": ${outExpr(desc.base, `__w.__box_get_${key}(r)`)} };
2896
+ return {
2897
+ __ref: r,
2898
+ get value() { return ${outExpr(desc.base, `__w.__box_get_${key}(r)`)}; },
2899
+ set value(v) { __w.__box_set_${key}(r, ${inExpr(desc.base, "v")}); }
2900
+ };
2605
2901
  }
2606
2902
  function ${P}boxback_${key}(r, b) {
2607
2903
  if (r == null || b == null) return;
@@ -2610,6 +2906,27 @@ function ${P}boxback_${key}(r, b) {
2610
2906
  }
2611
2907
 
2612
2908
  for (const [key, desc] of needs.slices) {
2909
+ if (desc.elem.k === "f32x4") {
2910
+ // []F32x4 travels as one Float32Array of 4n lanes.
2911
+ out.push(`function ${P}slin_${key}(arr) {
2912
+ if (arr == null) return __w.__slice_nil_${key}();
2913
+ const n = arr.length >> 2;
2914
+ const s = __w.__slice_new_${key}(n);
2915
+ 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)));
2916
+ return s;
2917
+ }
2918
+ function ${P}slout_${key}(s) {
2919
+ if (s == null || __w.__slice_is_nil_${key}(s)) return null;
2920
+ const n = __w.__slice_len_${key}(s);
2921
+ const out = new Float32Array(4 * n);
2922
+ for (let i = 0; i < n; i++) out.set(${P}f32x4out(__w.__slice_get_${key}(s, i)), 4 * i);
2923
+ return out;
2924
+ }
2925
+ function ${P}slview_${key}(s) {
2926
+ return ${P}slout_${key}(s);
2927
+ }`);
2928
+ continue;
2929
+ }
2613
2930
  out.push(`function ${P}slin_${key}(arr) {
2614
2931
  if (arr == null) return __w.__slice_nil_${key}();
2615
2932
  const n = arr.length;
@@ -2655,6 +2972,34 @@ function ${P}mapout_${key}(m) {
2655
2972
  }`);
2656
2973
  }
2657
2974
 
2975
+ if (needs.simd) {
2976
+ const k = needs.simd.key;
2977
+ out.push(`function ${P}v4in(words) {
2978
+ const a = __w.__array_new_${k}(4);
2979
+ for (let i = 0; i < 4; i++) __w.__array_set_${k}(a, i, words[i]);
2980
+ return a;
2981
+ }
2982
+ function ${P}v4out(a) {
2983
+ const words = new Int32Array(4);
2984
+ for (let i = 0; i < 4; i++) words[i] = __w.__array_get_${k}(a, i);
2985
+ return words;
2986
+ }
2987
+ function ${P}f32x4in(v) {
2988
+ return ${P}v4in(new Int32Array(new Float32Array(v ?? 4).buffer));
2989
+ }
2990
+ function ${P}f32x4out(a) {
2991
+ return new Float32Array(${P}v4out(a).buffer);
2992
+ }
2993
+ function ${P}mask4in(v) {
2994
+ const w = new Int32Array(4);
2995
+ if (v) for (let i = 0; i < 4; i++) w[i] = v[i] ? -1 : 0;
2996
+ return ${P}v4in(w);
2997
+ }
2998
+ function ${P}mask4out(a) {
2999
+ return Array.from(${P}v4out(a), (w) => w !== 0);
3000
+ }`);
3001
+ }
3002
+
2658
3003
  for (const [key, desc] of needs.arrays) {
2659
3004
  out.push(`function ${P}arrzero_${key}(n) {
2660
3005
  const out = new Array(n);
@@ -2794,15 +3139,17 @@ function ${P}shin(v, Ctor) {
2794
3139
  for (const c of meta.consts) {
2795
3140
  if (c.dynamic) {
2796
3141
  out.push(
2797
- `const ${c.name} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
3142
+ `const ${facadeName(c.name)} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
2798
3143
  );
2799
3144
  } else {
2800
- out.push(`const ${c.name} = ${c.value};`);
3145
+ out.push(`const ${facadeName(c.name)} = ${c.value};`);
2801
3146
  }
2802
3147
  }
2803
3148
  for (const v of meta.vars ?? [])
2804
3149
  out.push(
2805
- `const ${v.name} = ${P}shout(__w.__var_${v.name}(), ${v.desc.ctor});`,
3150
+ v.shared
3151
+ ? `const ${facadeName(v.name)} = ${P}shout(__w.__var_${v.name}(), ${v.desc.ctor});`
3152
+ : `const ${facadeName(v.name)} = ${outExpr(v.desc, `__w.__var_${v.name}()`)};`,
2806
3153
  );
2807
3154
 
2808
3155
  for (const f of meta.funcs) {
@@ -2811,13 +3158,22 @@ function ${P}shin(v, Ctor) {
2811
3158
  f.params,
2812
3159
  f.returns,
2813
3160
  );
2814
- out.push(`function ${f.name}(${names.join(", ")}) {\n${body}\n}`);
3161
+ out.push(
3162
+ `function ${facadeName(f.name)}(${restParams(names, f.variadic)}) {\n${body}\n}`,
3163
+ );
2815
3164
  }
2816
3165
 
2817
3166
  if (callMain) out.push("__w.main();");
2818
3167
  return bindExports(out.join("\n"));
2819
3168
  }
2820
3169
 
3170
+ // A variadic Go function is a slice parameter in wasm; JS callers pass the
3171
+ // trailing arguments spread, so the facade collects them with a rest param.
3172
+ function restParams(names, variadic) {
3173
+ if (!variadic || names.length === 0) return names.join(", ");
3174
+ return [...names.slice(0, -1), `...${names.at(-1)}`].join(", ");
3175
+ }
3176
+
2821
3177
  // Rewrites `__w.name(...)` into calls through module-level `const` bindings.
2822
3178
  // V8 only inlines a JS→wasm call (and so passes f32/f64 arguments unboxed)
2823
3179
  // when the callee is a compile-time constant; a property load off the exports
@@ -2974,7 +3330,7 @@ class ${name} {
2974
3330
  [recv],
2975
3331
  );
2976
3332
  lines.push(
2977
- `\t${m.name}(${names.join(", ")}) {\n${indent(body, "\t")}\n\t}`,
3333
+ `\t${m.name}(${restParams(names, m.variadic)}) {\n${indent(body, "\t")}\n\t}`,
2978
3334
  );
2979
3335
  }
2980
3336
  lines.push("}");