gofront 1.6.1 → 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 (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  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 +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  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 +34 -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 +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  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 +518 -74
  34. package/src/backend/wasm/encode.js +128 -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/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -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 +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /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
 
@@ -147,6 +153,8 @@ function classifyType(goType, mod) {
147
153
  return classifyFunc(t, mod);
148
154
  case "interface":
149
155
  return classifyInterface(t, mod);
156
+ case "simd":
157
+ return classifySimd(t, mod);
150
158
  default:
151
159
  return {
152
160
  k: "unsupported",
@@ -211,6 +219,15 @@ function classifyBasic(n) {
211
219
  }
212
220
 
213
221
  function classifyNamed(t, mod) {
222
+ if (
223
+ t.name === "simd.F32x4" ||
224
+ t.name === "F32x4" ||
225
+ t.name === "simd.Mask32x4" ||
226
+ t.name === "Mask32x4" ||
227
+ t.underlying?.kind === "simd"
228
+ ) {
229
+ return classifySimd(t.underlying ?? t, mod, t.name);
230
+ }
214
231
  if (t.name === "error") return { k: "error", wType: "anyref" };
215
232
  if (t.name === "time.Duration")
216
233
  return { k: "i64", unsigned: false, wType: "i64" };
@@ -224,6 +241,18 @@ function classifyNamed(t, mod) {
224
241
  return { k: "unsupported", what: `named type '${t.name}'` };
225
242
  }
226
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
+
227
256
  function classifyAnonStruct(t, mod) {
228
257
  const rawFields = [];
229
258
  if (t.fields instanceof Map) {
@@ -267,13 +296,22 @@ function classifyPointer(t, mod) {
267
296
  if (base.k === "anon_struct" && !base.ptr) return { ...base, ptr: true };
268
297
  if (base.k === "time" && !base.ptr) return { ...base, ptr: true };
269
298
  const boxInfo = mod.getBoxType(t.base);
299
+ const ptrInfo = mod.getPtrType ? mod.getPtrType(t.base) : null;
270
300
  const key = `${base.k}${base.unsigned ? "_u" : ""}_${boxInfo.typeIndex}`;
271
301
  return {
272
302
  k: "ptr_box",
273
303
  base,
274
304
  boxInfo,
305
+ ptrInfo,
275
306
  key,
276
- 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 },
277
315
  };
278
316
  }
279
317
 
@@ -337,7 +375,11 @@ function classifySlice(t, mod) {
337
375
  k: "slice",
338
376
  elem,
339
377
  sliceInfo,
340
- 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}` : ""),
341
383
  wType: { kind: "ref", nullable: true, typeIndex: sliceInfo.typeIndex },
342
384
  };
343
385
  }
@@ -372,6 +414,9 @@ function classifySig(t, mod) {
372
414
  }
373
415
 
374
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" };
375
420
  const sig = classifySig(t, mod);
376
421
  if (sig.k === "unsupported") return sig;
377
422
  const closureInfo = mod.getClosureType(t);
@@ -436,7 +481,7 @@ function classifyNamedInterface(t, mod) {
436
481
  const desc = {
437
482
  k: "iface",
438
483
  name: t.name,
439
- key: t.name,
484
+ key: t.name.replaceAll(".", "_"),
440
485
  wType: "anyref",
441
486
  methods: [],
442
487
  concrete: [],
@@ -506,6 +551,9 @@ function literalConst(spec, i) {
506
551
  }
507
552
 
508
553
  function unsupportedMsg(where, what) {
554
+ if (typeof what === "string" && what.includes("boundary")) {
555
+ return `${where}: ${what}`;
556
+ }
509
557
  return `${where}: ${what} is not yet supported across the wasm boundary (planned)`;
510
558
  }
511
559
 
@@ -532,7 +580,7 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
532
580
  } else if (d.kind === "ConstDecl" && pkgTarget === "wasm") {
533
581
  collectConstDecl(d, pkgName, meta, errors);
534
582
  } else if (d.kind === "VarDecl" && pkgTarget === "wasm") {
535
- collectSharedVarDecl(d, meta, mod);
583
+ collectVarDecl(d, meta, mod);
536
584
  }
537
585
  }
538
586
  }
@@ -614,17 +662,24 @@ function checkDesc(desc, where, errors) {
614
662
  }
615
663
 
616
664
  // Exported `var Buf = shared.NewFloat32(n)` globals are exposed to JS as
617
- // TypedArray views (read once after instantiation; sizing is fixed).
618
- 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) {
619
670
  for (const spec of d.decls ?? d.specs ?? [d]) {
620
671
  const names = spec.names ?? (spec.name ? [spec.name] : []);
621
672
  const values = spec.value ?? (spec.init ? [spec.init] : []);
622
673
  for (let i = 0; i < names.length; i++) {
623
674
  const name = names[i];
624
675
  if (!isExported(name)) continue;
625
- const goType = spec.type ?? values[i]?._type;
626
- if (!getSharedInfo(goType, mod.checker)) continue;
627
- 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 });
628
683
  }
629
684
  }
630
685
  }
@@ -639,6 +694,7 @@ function collectMethodMeta(fn, meta, errors, mod) {
639
694
  exportName: fn._exportName,
640
695
  fnName: fn.name,
641
696
  ptrRecv: fn.params[0].type?.kind === "pointer",
697
+ variadic: Boolean(fn.params.at(-1)?._variadic),
642
698
  params: fn.params
643
699
  .slice(1)
644
700
  .map((p) => checkDesc(classifyType(p.type, mod), where, errors)),
@@ -655,6 +711,7 @@ function collectFuncMeta(fn, meta, errors, mod) {
655
711
  name: fn.name,
656
712
  exportName: fn.name,
657
713
  pkgName: fn._pkgName,
714
+ variadic: Boolean(fn.params?.at(-1)?._variadic),
658
715
  params: (fn.params ?? []).map((p) =>
659
716
  checkDesc(classifyType(p.type, mod), where, errors),
660
717
  ),
@@ -693,6 +750,11 @@ function visitNeed(desc, needs, errors, mod) {
693
750
  case "error":
694
751
  needs.error = true;
695
752
  return;
753
+ case "f32x4":
754
+ case "mask4":
755
+ needs.simd = desc.arr;
756
+ visitNeed(desc.arr, needs, errors, mod);
757
+ return;
696
758
  case "time":
697
759
  needs.time = true;
698
760
  return;
@@ -773,6 +835,7 @@ function collectNeeds(meta) {
773
835
  ifaces: new Map(), // interface name -> desc
774
836
  error: false,
775
837
  time: false,
838
+ simd: null, // array desc carrying vectors across the boundary
776
839
  shared: false, // any shared buffer on the surface
777
840
  };
778
841
  const errors = [];
@@ -817,16 +880,22 @@ export function scanBoundaryImports(mod, meta) {
817
880
  function isGcRef(wType) {
818
881
  return wType === "anyref" || (typeof wType === "object" && wType !== null);
819
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
+
820
887
  function extType(wType) {
888
+ if (wType === "v128") return "externref";
821
889
  if (wType === "i64") return "f64";
822
890
  if (isGcRef(wType)) return "externref";
823
891
  return wType;
824
892
  }
825
893
  function needsConv(wTypes) {
826
- return wTypes.some((w) => w === "i64" || isGcRef(w));
894
+ return wTypes.some((w) => w === "i64" || w === "v128" || isGcRef(w));
827
895
  }
828
896
  // Instructions converting the value on the stack from its boundary form.
829
897
  function fromExt(wType) {
898
+ if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.from }];
830
899
  if (wType === "i64") return [{ op: "i64.trunc_sat_f64_s" }];
831
900
  if (wType === "anyref") return [{ op: "any.convert_extern" }];
832
901
  if (isGcRef(wType)) {
@@ -839,6 +908,7 @@ function fromExt(wType) {
839
908
  }
840
909
  // Instructions converting the value on the stack to its boundary form.
841
910
  function toExt(wType) {
911
+ if (wType === "v128") return [{ op: "call", funcIndex: v128Conv.to }];
842
912
  if (wType === "i64") return [{ op: "f64.convert_i64_s" }];
843
913
  if (isGcRef(wType)) return [{ op: "extern.convert_any" }];
844
914
  return [];
@@ -857,6 +927,8 @@ function convertResults(wTypes, conv, localBase) {
857
927
  }
858
928
 
859
929
  function zeroInstr(wType) {
930
+ if (wType === "v128")
931
+ return { op: "v128.const", bytes: new Array(16).fill(0) };
860
932
  if (wType === "i64") return { op: "i64.const", value: 0n };
861
933
  if (wType === "f32" || wType === "f64")
862
934
  return { op: `${wType}.const`, value: 0 };
@@ -908,6 +980,47 @@ export function addBoundaryHelpers(mod, meta) {
908
980
  return idx;
909
981
  };
910
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
+ }
911
1024
  // Parameter `i` converted from its boundary form.
912
1025
  const getIn = (i, wType) => [get(i), ...fromExt(wType)];
913
1026
  const EXT = "externref";
@@ -1151,7 +1264,8 @@ export function addBoundaryHelpers(mod, meta) {
1151
1264
  const paramW = sig.params;
1152
1265
  const resultW = sig.results;
1153
1266
  if (!needsConv(paramW) && !needsConv(resultW)) return;
1154
- entry.callName = `__x_${entry.exportName}`;
1267
+ // Exported names become JS identifiers: no dot of a qualified type.
1268
+ entry.callName = `__x_${entry.exportName}`.replaceAll(".", "_");
1155
1269
  addFunc(
1156
1270
  entry.callName,
1157
1271
  paramW.map(extType),
@@ -1192,7 +1306,7 @@ export function addBoundaryHelpers(mod, meta) {
1192
1306
  [get(0), get(1), { op: "struct.new", typeIndex }, ...toExt(wType)],
1193
1307
  );
1194
1308
  for (const v of meta.vars ?? []) {
1195
- const g = mod.resolveGlobal(v.name);
1309
+ const g = v.shared ? mod.resolveGlobal(v.name) : null;
1196
1310
  if (!g) continue;
1197
1311
  addFunc(
1198
1312
  `__var_${v.name}`,
@@ -1264,12 +1378,18 @@ export function addBoundaryHelpers(mod, meta) {
1264
1378
  [EXT],
1265
1379
  [...getIn(0, w), { op: "struct.new", typeIndex: ti }, ...toExt(ref)],
1266
1380
  );
1381
+ const cast = {
1382
+ op: "ref.cast",
1383
+ typeIndex: boxInfo.typeIndex,
1384
+ typeName: boxInfo.name,
1385
+ };
1267
1386
  addFunc(
1268
1387
  `__box_get_${key}`,
1269
1388
  [EXT],
1270
1389
  [extType(w)],
1271
1390
  [
1272
1391
  ...getIn(0, ref),
1392
+ cast,
1273
1393
  { op: "struct.get", typeIndex: ti, fieldIndex: 0 },
1274
1394
  ...toExt(w),
1275
1395
  ],
@@ -1280,6 +1400,7 @@ export function addBoundaryHelpers(mod, meta) {
1280
1400
  [],
1281
1401
  [
1282
1402
  ...getIn(0, ref),
1403
+ cast,
1283
1404
  ...getIn(1, w),
1284
1405
  { op: "struct.set", typeIndex: ti, fieldIndex: 0 },
1285
1406
  ],
@@ -1372,10 +1493,11 @@ export function addBoundaryHelpers(mod, meta) {
1372
1493
  [EXT],
1373
1494
  [
1374
1495
  get(0),
1375
- { op: "f64.const", value: 1000000 },
1376
- { op: "f64.mul" },
1377
1496
  { op: "i64.trunc_sat_f64_s" },
1497
+ { op: "i64.const", value: 1000000n },
1498
+ { op: "i64.mul" },
1378
1499
  get(1),
1500
+ { op: "ref.null", heapType: "any" },
1379
1501
  { op: "struct.new", typeIndex: timeInfo.typeIndex },
1380
1502
  ...toExt(timeWType),
1381
1503
  ],
@@ -1427,6 +1549,16 @@ export function addBoundaryHelpers(mod, meta) {
1427
1549
  [{ op: "global.get", index: g.index }, ...toExt(c.desc.wType)],
1428
1550
  );
1429
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
+ }
1430
1562
  }
1431
1563
 
1432
1564
  // `(externref) -> i32`: `ref.test`s param 0 against each type index and
@@ -1511,6 +1643,10 @@ function inExpr(desc, v) {
1511
1643
  return `(${v} === undefined ? null : ${v})`;
1512
1644
  case "error":
1513
1645
  return `${P}errin(${v})`;
1646
+ case "f32x4":
1647
+ return `${P}f32x4in(${v})`;
1648
+ case "mask4":
1649
+ return `${P}mask4in(${v})`;
1514
1650
  case "time":
1515
1651
  return desc.ptr ? `${P}timein_ptr(${v})` : `${P}timein(${v})`;
1516
1652
  case "map":
@@ -1555,6 +1691,10 @@ function outExpr(desc, v) {
1555
1691
  return `(${v} ?? "")`;
1556
1692
  case "error":
1557
1693
  return `${P}errout(${v})`;
1694
+ case "f32x4":
1695
+ return `${P}f32x4out(${v})`;
1696
+ case "mask4":
1697
+ return `${P}mask4out(${v})`;
1558
1698
  case "time":
1559
1699
  return desc.ptr ? `${P}timeout_ptr(${v})` : `${P}timeout(${v})`;
1560
1700
  case "map":
@@ -1595,6 +1735,10 @@ function zeroExpr(desc) {
1595
1735
  return "0";
1596
1736
  case "string":
1597
1737
  return '""';
1738
+ case "f32x4":
1739
+ return "new Float32Array(4)";
1740
+ case "mask4":
1741
+ return "[false, false, false, false]";
1598
1742
  case "error":
1599
1743
  case "time":
1600
1744
  case "map":
@@ -1715,21 +1859,23 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1715
1859
  let lineBuf = "";
1716
1860
  let targs = [];
1717
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; } }
1718
1866
  const late = { exports: null };
1719
- 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; };
1720
1871
  const print = (v) => {
1721
- const s = String(v);
1722
- if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
1723
- lineBuf += " ";
1724
- }
1872
+ const s = Object.is(v, -0) ? "-0" : String(v);
1873
+ if (started) lineBuf += " ";
1874
+ started = true;
1725
1875
  lineBuf += s;
1726
1876
  };
1727
1877
  const println = (v) => {
1728
- const s = String(v);
1729
- if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
1730
- lineBuf += " ";
1731
- }
1732
- lineBuf += s;
1878
+ print(v);
1733
1879
  flush();
1734
1880
  };
1735
1881
  const targ = (v) => { targs.push(v); };
@@ -1769,6 +1915,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1769
1915
  },
1770
1916
  "str_to_upper": (s) => (s ? s.toUpperCase() : ""),
1771
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 ?? "")),
1772
1919
  "str_trim_space": (s) => (s ? s.trim() : ""),
1773
1920
  "str_contains": (s, sub) => (s && sub !== undefined ? (s.includes(sub) ? 1 : 0) : 0),
1774
1921
  "str_has_prefix": (s, pre) => (s && pre !== undefined ? (s.startsWith(pre) ? 1 : 0) : 0),
@@ -1785,7 +1932,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1785
1932
  },
1786
1933
  "str_from_i64": (n) => String(n),
1787
1934
  "str_from_i32": (n) => String(n),
1788
- "str_from_f64": (f) => String(f),
1935
+ "str_from_f64": (f) => (Object.is(f, -0) ? "-0" : String(f)),
1789
1936
  "is_string": (v) => (typeof v === "string" ? 1 : 0),
1790
1937
  "str_to_bytes": (s) => {
1791
1938
  utf8Encoded = new TextEncoder().encode(s ?? "");
@@ -1818,28 +1965,55 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1818
1965
  "fmt_new_map": () => new Map(),
1819
1966
  "fmt_map_set": (m, k, v) => { m.set(k, v); },
1820
1967
  "fmt_new_struct": (ref, isPtr) => {
1821
- if (ref !== null && ref !== undefined && typeof ref === "object" && fmtStructMap.has(ref)) {
1822
- 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;
1823
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];
1824
1984
  const o = {};
1825
1985
  Object.defineProperty(o, "__struct", { value: true, configurable: true });
1826
1986
  if (isPtr !== 0) Object.defineProperty(o, "__p", { value: true, configurable: true });
1827
- 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
+ }
1828
1991
  return o;
1829
1992
  },
1993
+ "fmt_struct_type": (o, i) => { Object.defineProperty(o, "__tn", { value: stringTable[i], configurable: true }); },
1830
1994
  "fmt_struct_set": (o, k, v) => { o[typeof k === "number" ? stringTable[k] : k] = v; },
1831
1995
  "fmt_box_i32": (v) => v,
1832
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); },
1833
1998
  "fmt_box_f64": (v) => v,
1834
1999
  "fmt_box_bool": (v) => v !== 0,
2000
+ "fmt_new_stringer": (r, s, isErr) => (isErr !== 0 ? new FmtErr(r, s) : new FmtStr(r, s)),
1835
2001
  "fmt_new_box": () => ({ value: null, __p: true, __box: true }),
1836
2002
  "testing_arg_i32": targ, "testing_arg_f32": targ, "testing_arg_f64": targ,
1837
2003
  "testing_arg_str": targ, "testing_arg_any": targ,
1838
2004
  "testing_arg_i64": (v) => targ(v > ${P}MAX || v < -${P}MAX ? v : Number(v)),
1839
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; } }),
1840
2010
  "testing_call": (t, i) => { const a = targs; targs = []; fmtStructMap = new WeakMap(); t[stringTable[i]](...a); },
1841
2011
  "testing_name": (t) => t.Name(),
1842
- "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(),
1843
2017
  };
1844
2018
  // Stdlib imports (strings/strconv/utf8/fmt/t.Run) are a separate block so
1845
2019
  // modules that never call them do not ship it (see WASM_STDLIB_JS).
@@ -1856,7 +2030,7 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1856
2030
  }
1857
2031
  Object.assign(env, extraEnv);
1858
2032
  const m = {};
1859
- 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];
1860
2034
  m.mod = (x, y) => x % y;
1861
2035
  // Go rounds half away from zero; Math.round rounds half toward +Infinity.
1862
2036
  m.round = (x) => Math.sign(x) * Math.round(Math.abs(x));
@@ -1868,30 +2042,16 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
1868
2042
  // module imports one of the packages that use it.
1869
2043
  export const WASM_STDLIB_JS = `${HELPER_ERROR}
1870
2044
  ${HELPER_SPRINTF}
2045
+ ${HELPER_SPRINT}
1871
2046
  ${HELPER_STRCONV}
1872
2047
  ${HELPER_PATH_CLEAN}
1873
2048
  ${HELPER_DURATION}
1874
2049
  ${HELPER_SHA256}
1875
2050
  ${HELPER_HEX}
1876
2051
  ${HELPER_BASE64}
1877
- function ${P}stdlib(stringTable, io) {
1878
- let parts = [];
1879
- let cutBefore = "", cutAfter = "";
1880
- let strconvErr = null, strconvErrKind = 0;
1881
- let decodeSize = 0;
1882
- let replacerTarget = "", replacerPairs = [];
1883
- let splitDir = "", splitFile = "";
1884
- let joinParts = [];
1885
- let matchHasErr = 0;
1886
- let trimStart = 0, trimEnd = 0;
2052
+ function ${P}stdlib_fmt(stringTable, io) {
1887
2053
  let sscanTokens = [];
1888
2054
  let sscanHasErr = 0, sscanErrMsg = "";
1889
- let timeParseErr = null, timeParseOffset = 0;
1890
- let rawBytes = new Uint8Array(0);
1891
- let rawPos = 0;
1892
- let hexErr = null;
1893
- let base64Err = null;
1894
- let customBase64Encodings = [];
1895
2055
  return {
1896
2056
  "fmt_sscan_init": (s, mode) => {
1897
2057
  sscanHasErr = 0;
@@ -2002,9 +2162,26 @@ function ${P}stdlib(stringTable, io) {
2002
2162
  return 0;
2003
2163
  },
2004
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(),
2005
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 ?? ""); },
2006
2174
  "fmt_printf": (f) => { io.printf(__sprintf(f ?? "", ...io.take())); },
2007
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 {
2008
2185
  "str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
2009
2186
  "str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
2010
2187
  "str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
@@ -2121,7 +2298,7 @@ function ${P}stdlib(stringTable, io) {
2121
2298
  parts = r;
2122
2299
  return parts.length;
2123
2300
  },
2124
- "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; },
2125
2302
  "str_part": (i) => parts[i] ?? "",
2126
2303
  "str_replacer_init": (s) => {
2127
2304
  replacerTarget = s ?? "";
@@ -2236,7 +2413,7 @@ function ${P}stdlib(stringTable, io) {
2236
2413
  // unicode
2237
2414
  "unicode_is_letter": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{L}/u.test(c)) ? 1 : 0; },
2238
2415
  "unicode_is_digit": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Nd}/u.test(c)) ? 1 : 0; },
2239
- "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; },
2240
2417
  "unicode_is_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Lu}/u.test(c)) ? 1 : 0; },
2241
2418
  "unicode_is_lower": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{Ll}/u.test(c)) ? 1 : 0; },
2242
2419
  "unicode_is_punct": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && /\\p{P}/u.test(c)) ? 1 : 0; },
@@ -2245,6 +2422,22 @@ function ${P}stdlib(stringTable, io) {
2245
2422
  "unicode_is_graphic": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return (c && !/\\p{Cc}/u.test(c)) ? 1 : 0; },
2246
2423
  "unicode_to_upper": (r) => { const c = (r >= 0 && r <= 0x10ffff) ? String.fromCodePoint(r) : null; return c ? (c.toUpperCase().codePointAt(0) ?? r) : r; },
2247
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 {
2248
2441
  // html
2249
2442
  "html_escape": (s) => (s ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&#34;").replace(/'/g, "&#39;"),
2250
2443
  "html_unescape": (s) => (s ?? "").replace(/&(?:quot|apos|#39|#34|gt|lt|amp|#(?:[0-9]+|[xX][0-9a-fA-F]+));/g, (m) => {
@@ -2309,32 +2502,66 @@ function ${P}stdlib(stringTable, io) {
2309
2502
  "time_now_ms": () => (typeof globalThis.__GOFRONT_CLOCK === "function" ? globalThis.__GOFRONT_CLOCK() : Date.now()),
2310
2503
  "time_timezone_offset": (ms) => -new Date(ms).getTimezoneOffset() * 60,
2311
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),
2312
2517
  "time_parse": (layout, val) => {
2313
2518
  try {
2314
- 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);
2315
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
+ }
2316
2530
  timeParseErr = null;
2317
- 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;
2318
2543
  return d.getTime();
2319
2544
  } catch {
2320
2545
  timeParseErr = "parsing time: cannot parse";
2321
2546
  timeParseOffset = 0;
2547
+ timeParseSubNs = 0;
2322
2548
  return 0;
2323
2549
  }
2324
2550
  },
2325
2551
  "time_parse_err": () => timeParseErr,
2326
2552
  "time_parse_offset": () => timeParseOffset,
2553
+ "time_parse_sub_ns": () => timeParseSubNs,
2327
2554
  "time_format": (y, m, d, hr, min, sec, wd, ms, offsetSec, layout) => {
2328
2555
  const DAYS = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
2329
2556
  const MONTHS = ["January","February","March","April","May","June","July","August","September","October","November","December"];
2330
2557
  const pad = (n, w = 2) => String(n).padStart(w, "0");
2331
- 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 => {
2332
2559
  if (t.startsWith(".")) {
2333
2560
  const isZeroPad = t.includes("0");
2334
2561
  const maxLen = t.length - 1;
2335
- let frac = pad(ms % 1000, 3);
2336
- if (maxLen > 3) frac = frac.padEnd(maxLen, "0");
2337
- 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");
2338
2565
  if (!isZeroPad) {
2339
2566
  frac = frac.replace(/0+$/, "");
2340
2567
  return frac.length > 0 ? "." + frac : "";
@@ -2365,6 +2592,15 @@ function ${P}stdlib(stringTable, io) {
2365
2592
  const o = Math.floor(offsetSec / 60);
2366
2593
  return o === 0 ? "Z" : (o > 0 ? "+" : "-") + pad(Math.floor(Math.abs(o) / 60)) + ":" + pad(Math.abs(o) % 60);
2367
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";
2368
2604
  default: return t;
2369
2605
  }
2370
2606
  });
@@ -2424,8 +2660,50 @@ function ${P}stdlib(stringTable, io) {
2424
2660
  return rawBytes.length;
2425
2661
  },
2426
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
+ },
2427
2697
  };
2428
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
+ }
2429
2707
  function ${P}trim(s, cutset, left, right) {
2430
2708
  const cs = new Set(Array.from(cutset));
2431
2709
  const chars = Array.from(s);
@@ -2518,6 +2796,13 @@ function ${P}idxview(n, getAt, setAt) {
2518
2796
  }`;
2519
2797
 
2520
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
+
2521
2806
  export function generateFacade(
2522
2807
  meta,
2523
2808
  { stringTable = [], callMain = false, stdlibEnv = true } = {},
@@ -2529,18 +2814,42 @@ export function generateFacade(
2529
2814
  out.push(`const ${P}env = {};`);
2530
2815
  for (const [key, desc] of needs.funcs) {
2531
2816
  const names = desc.params.map((_, i) => `a${i}`);
2532
- const args = desc.params.map((d, i) =>
2533
- isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i]),
2534
- );
2535
- const call = `fn(${args.join(", ")})`;
2817
+ const hasPtrBox = desc.params.some((d) => d.k === "ptr_box");
2536
2818
  let body;
2537
- if (desc.returns.length === 0) body = `${call};`;
2538
- else if (desc.returns.length === 1)
2539
- body = `return ${inExpr(desc.returns[0], call)};`;
2540
- else
2541
- body = `const __r = ${call}; return [${desc.returns
2542
- .map((d, i) => inExpr(d, `__r[${i}]`))
2543
- .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
+ }
2544
2853
  const sig = ["fn", ...names].join(", ");
2545
2854
  out.push(
2546
2855
  `${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic) __w.__push_panic(__e.message ?? String(__e)); throw __e; } };`,
@@ -2578,12 +2887,17 @@ ${desc.fields.map((f) => `\to.${f.name} = ${outExpr(f.desc, `__w.__get_${name}_$
2578
2887
  for (const [key, desc] of needs.boxes) {
2579
2888
  out.push(`function ${P}boxin_${key}(b) {
2580
2889
  if (b == null) return null;
2890
+ if (b.__ref) return b.__ref;
2581
2891
  const v = b.value !== undefined ? b.value : ${zeroExpr(desc.base)};
2582
2892
  return __w.__box_new_${key}(${inExpr(desc.base, "v")});
2583
2893
  }
2584
2894
  function ${P}boxout_${key}(r) {
2585
2895
  if (r == null) return null;
2586
- 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
+ };
2587
2901
  }
2588
2902
  function ${P}boxback_${key}(r, b) {
2589
2903
  if (r == null || b == null) return;
@@ -2592,6 +2906,27 @@ function ${P}boxback_${key}(r, b) {
2592
2906
  }
2593
2907
 
2594
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
+ }
2595
2930
  out.push(`function ${P}slin_${key}(arr) {
2596
2931
  if (arr == null) return __w.__slice_nil_${key}();
2597
2932
  const n = arr.length;
@@ -2637,6 +2972,34 @@ function ${P}mapout_${key}(m) {
2637
2972
  }`);
2638
2973
  }
2639
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
+
2640
3003
  for (const [key, desc] of needs.arrays) {
2641
3004
  out.push(`function ${P}arrzero_${key}(n) {
2642
3005
  const out = new Array(n);
@@ -2776,15 +3139,17 @@ function ${P}shin(v, Ctor) {
2776
3139
  for (const c of meta.consts) {
2777
3140
  if (c.dynamic) {
2778
3141
  out.push(
2779
- `const ${c.name} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
3142
+ `const ${facadeName(c.name)} = ${outExpr(c.desc, `__w.__const_${c.name}()`)};`,
2780
3143
  );
2781
3144
  } else {
2782
- out.push(`const ${c.name} = ${c.value};`);
3145
+ out.push(`const ${facadeName(c.name)} = ${c.value};`);
2783
3146
  }
2784
3147
  }
2785
3148
  for (const v of meta.vars ?? [])
2786
3149
  out.push(
2787
- `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}()`)};`,
2788
3153
  );
2789
3154
 
2790
3155
  for (const f of meta.funcs) {
@@ -2793,13 +3158,22 @@ function ${P}shin(v, Ctor) {
2793
3158
  f.params,
2794
3159
  f.returns,
2795
3160
  );
2796
- 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
+ );
2797
3164
  }
2798
3165
 
2799
3166
  if (callMain) out.push("__w.main();");
2800
3167
  return bindExports(out.join("\n"));
2801
3168
  }
2802
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
+
2803
3177
  // Rewrites `__w.name(...)` into calls through module-level `const` bindings.
2804
3178
  // V8 only inlines a JS→wasm call (and so passes f32/f64 arguments unboxed)
2805
3179
  // when the callee is a compile-time constant; a property load off the exports
@@ -2956,7 +3330,7 @@ class ${name} {
2956
3330
  [recv],
2957
3331
  );
2958
3332
  lines.push(
2959
- `\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}`,
2960
3334
  );
2961
3335
  }
2962
3336
  lines.push("}");