gofront 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -55,6 +55,18 @@ function sameSignature(c, params, paramTypes, want, got) {
55
55
  return want === undefined || got === undefined || sameTypes(got, want);
56
56
  }
57
57
 
58
+ // True when a struct type embeds a pointer, directly or through an embedded
59
+ // struct: promoted method calls read through it.
60
+ function _embedsPointer(t, seen = new Set()) {
61
+ const base = t?.kind === "pointer" ? t.base : t;
62
+ const st = base?.underlying ?? base;
63
+ if (!st || seen.has(st)) return false;
64
+ seen.add(st);
65
+ return (st._embeds ?? []).some(
66
+ (e) => e.kind === "pointer" || _embedsPointer(e, seen),
67
+ );
68
+ }
69
+
58
70
  export class FunctionEmitter {
59
71
  constructor(moduleEmitter, funcDecl, funcIndex) {
60
72
  this.mod = moduleEmitter;
@@ -201,6 +213,35 @@ export class FunctionEmitter {
201
213
  return targets;
202
214
  }
203
215
 
216
+ // Outer stacked labels (`a: b: for`) alias the innermost one. Collected
217
+ // once per root function so the yield closures of a range-over-func loop
218
+ // (emitted by their own emitter) resolve them too.
219
+ _stackedLabelAlias(label) {
220
+ const root = this.rootFuncDecl;
221
+ if (root && !root._labelAliases) {
222
+ const aliases = new Map();
223
+ const scan = (node) => {
224
+ if (!node || typeof node !== "object") return;
225
+ if (Array.isArray(node)) {
226
+ for (const item of node) scan(item);
227
+ return;
228
+ }
229
+ if (node.kind === "LabeledStmt" && node.body?.kind === "LabeledStmt") {
230
+ let inner = node.body;
231
+ while (inner.body?.kind === "LabeledStmt") inner = inner.body;
232
+ aliases.set(node.label, inner.label);
233
+ }
234
+ for (const k of Object.keys(node)) {
235
+ if (k.startsWith("_")) continue;
236
+ scan(node[k]);
237
+ }
238
+ };
239
+ scan(root.body);
240
+ root._labelAliases = aliases;
241
+ }
242
+ return root?._labelAliases?.get(label) ?? this._labelAliases?.get(label);
243
+ }
244
+
204
245
  _findLabelTarget(label, role) {
205
246
  for (const target of this.rootFuncDecl?._labelTargets?.values() ?? []) {
206
247
  if (target.label === label && target.role === role) return target;
@@ -781,7 +822,22 @@ export class FunctionEmitter {
781
822
  // A pointer dereference or a call through a func value: sites whose nil
782
823
  // operand raises Go's panic through the `nil` block.
783
824
  _isNilTrapSite(node) {
784
- if (node.kind === "CallExpr") return this._isFuncValueCall(node);
825
+ if (node.kind === "CallExpr")
826
+ return (
827
+ this._isFuncValueCall(node) ||
828
+ (node.func?.kind === "SelectorExpr" &&
829
+ _embedsPointer(node.func.expr?._type))
830
+ );
831
+ // Binding a method value on an interface checks it for nil.
832
+ if (node.kind === "NilCheckStmt") return true;
833
+ // A pointer to an array: indexing, slicing and ranging with a value
834
+ // read the array.
835
+ if (
836
+ node.kind === "IndexExpr" ||
837
+ node.kind === "SliceExpr" ||
838
+ node.kind === "RangeExpr"
839
+ )
840
+ return node.expr?._type?.kind === "pointer";
785
841
  const star =
786
842
  (node.kind === "UnaryExpr" && node.op === "*") ||
787
843
  node.kind === "StarExpr";
@@ -893,11 +949,12 @@ export class FunctionEmitter {
893
949
  return null;
894
950
  }
895
951
 
896
- _buildEnvFields(capturedNames) {
952
+ _buildEnvFields(capturedNames, funcLit) {
897
953
  const envFields = [];
954
+ const types = this.captureAnalysis?.captureTypes?.get(funcLit);
898
955
  for (const name of capturedNames) {
899
956
  const isBoxed = this.mutatedCaptures.has(name);
900
- const goType = this._findCapturedVarGoType(name);
957
+ const goType = types?.get(name) ?? this._findCapturedVarGoType(name);
901
958
  const boxInfo = isBoxed ? this.mod.getBoxType(goType) : null;
902
959
  envFields.push({
903
960
  name,
@@ -913,7 +970,10 @@ export class FunctionEmitter {
913
970
  }
914
971
 
915
972
  _unpackClosureEnv() {
916
- const envFields = this._buildEnvFields(this.capturedNames);
973
+ const envFields = this._buildEnvFields(
974
+ this.capturedNames,
975
+ this.funcDecl._funcLit,
976
+ );
917
977
  const envInfo = this.mod.getEnvType(envFields);
918
978
  this.envInfo = envInfo;
919
979
  this.envFields = envFields;
@@ -1126,7 +1186,7 @@ export class FunctionEmitter {
1126
1186
  const instName = (ft?.kind === "PointerType" ? ft.base : ft)?.name;
1127
1187
  const embedInfo =
1128
1188
  (instName && this.mod.structTypes.get(instName)) ??
1129
- this.mod.structTypes.get(embed.name);
1189
+ this.mod.structTypes.get(embed.typeName ?? embed.name);
1130
1190
  if (embedInfo) {
1131
1191
  const sub = this._resolveFieldPath(embedInfo, fieldName);
1132
1192
  if (sub) {
@@ -1381,6 +1441,7 @@ export class FunctionEmitter {
1381
1441
 
1382
1442
  // 2. Iterate candidates
1383
1443
  for (const cand of candidates) {
1444
+ this._emitNilPtrCandidate(cand, fn, recvLocal);
1384
1445
  this.pushInstruction({ op: "block", blockType: "void" });
1385
1446
  this.pushInstruction({ op: "local.get", index: recvLocal });
1386
1447
  this.pushInstruction({ op: "ref.test", typeIndex: cand.testTypeIndex });
@@ -1402,31 +1463,9 @@ export class FunctionEmitter {
1402
1463
  typeIndex: cand.testTypeIndex,
1403
1464
  fieldIndex: 0,
1404
1465
  });
1405
- if (cand.embedPath) {
1406
- for (const step of cand.embedPath) {
1407
- if (step.castTypeIndex) {
1408
- this.pushInstruction({
1409
- op: "ref.cast",
1410
- typeIndex: step.castTypeIndex,
1411
- });
1412
- }
1413
- this.pushInstruction({
1414
- op: "struct.get",
1415
- typeIndex: step.parentTypeIndex,
1416
- fieldIndex: step.fieldIndex,
1417
- });
1418
- }
1419
- }
1466
+ if (cand.embedPath) this._emitEmbedPath(cand);
1420
1467
  } else {
1421
- if (cand.embedPath) {
1422
- for (const step of cand.embedPath) {
1423
- this.pushInstruction({
1424
- op: "struct.get",
1425
- typeIndex: step.parentTypeIndex,
1426
- fieldIndex: step.fieldIndex,
1427
- });
1428
- }
1429
- }
1468
+ if (cand.embedPath) this._emitEmbedPath(cand);
1430
1469
  if (cand.needsValueDeref && cand.structInfo) {
1431
1470
  this.emitCloneStruct(cand.structInfo, cand.targetRecvWType);
1432
1471
  }
@@ -1437,7 +1476,10 @@ export class FunctionEmitter {
1437
1476
  this.pushInstruction({ op: "local.get", index: p });
1438
1477
  }
1439
1478
 
1440
- this.pushInstruction({ op: "call", funcIndex: cand.funcIndex });
1479
+ this.pushInstruction({
1480
+ op: "call",
1481
+ funcIndex: cand.selfDispatch ? fn._globalFuncIndex : cand.funcIndex,
1482
+ });
1441
1483
  this.pushInstruction("return");
1442
1484
  this.pushInstruction("end");
1443
1485
  }
@@ -1445,6 +1487,73 @@ export class FunctionEmitter {
1445
1487
  emitMiss("interface conversion: nil or unmatched type for method call");
1446
1488
  }
1447
1489
 
1490
+ // Walks `cand.embedPath` from the embedder on the stack to the embedded
1491
+ // value. A step through a nil embedded pointer panics, except that the last
1492
+ // step may be nil for a pointer-receiver method (`nilCheckLast` is unset).
1493
+ _emitEmbedPath(cand) {
1494
+ const steps = cand.embedPath;
1495
+ for (const [i, step] of steps.entries()) {
1496
+ if (step.castTypeIndex) {
1497
+ this.pushInstruction({ op: "ref.cast", typeIndex: step.castTypeIndex });
1498
+ }
1499
+ this.pushInstruction({
1500
+ op: "struct.get",
1501
+ typeIndex: step.parentTypeIndex,
1502
+ fieldIndex: step.fieldIndex,
1503
+ });
1504
+ if (step.ptrWType && (i < steps.length - 1 || cand.nilCheckLast))
1505
+ this._emitNilPanicIfNull(step.ptrWType);
1506
+ }
1507
+ }
1508
+
1509
+ // Panics with a nil dereference when the reference on the stack is null,
1510
+ // leaving it on the stack otherwise.
1511
+ _emitNilPanicIfNull(wType) {
1512
+ const tmp = this.acquireTemp(wType);
1513
+ this.pushInstruction({ op: "local.tee", index: tmp });
1514
+ this.pushInstruction("ref.is_null");
1515
+ this.pushInstruction({ op: "if", blockType: "void" });
1516
+ this.emitPanic(
1517
+ "runtime error: invalid memory address or nil pointer dereference",
1518
+ );
1519
+ this.pushInstruction("end");
1520
+ this.pushInstruction({ op: "local.get", index: tmp });
1521
+ this.releaseTemp(tmp, wType);
1522
+ }
1523
+
1524
+ // A typed nil `*S` inside the interface is a marker object (see
1525
+ // getNilBoxType): a pointer-receiver method runs with a nil receiver, as in
1526
+ // Go; a value-receiver or promoted method must read through the nil pointer.
1527
+ // A promoted candidate tests the outer struct but carries the embedded
1528
+ // struct's info, so it is recognised by its embed path.
1529
+ _emitNilPtrCandidate(cand, fn, recvLocal) {
1530
+ if (cand.isBoxedValue || !cand.structInfo) return;
1531
+ if (!cand.embedPath && cand.testTypeIndex !== cand.structInfo.typeIndex)
1532
+ return;
1533
+ const nilBox = this.mod.getNilBoxType({
1534
+ kind: "pointer",
1535
+ base: { kind: "named", name: cand.recvTypeName },
1536
+ });
1537
+ this.pushInstruction({ op: "block", blockType: "void" });
1538
+ this.pushInstruction({ op: "local.get", index: recvLocal });
1539
+ this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
1540
+ this.pushInstruction("i32.eqz");
1541
+ this.pushInstruction({ op: "br_if", depth: 0 });
1542
+ if (cand.needsValueDeref || cand.embedPath) {
1543
+ this.emitPanic(
1544
+ "runtime error: invalid memory address or nil pointer dereference",
1545
+ );
1546
+ } else {
1547
+ this.pushInstruction({ op: "ref.null", heapType: cand.testTypeIndex });
1548
+ for (let p = 1; p < fn.params.length; p++) {
1549
+ this.pushInstruction({ op: "local.get", index: p });
1550
+ }
1551
+ this.pushInstruction({ op: "call", funcIndex: cand.funcIndex });
1552
+ this.pushInstruction("return");
1553
+ }
1554
+ this.pushInstruction("end");
1555
+ }
1556
+
1448
1557
  // A value-receiver method reached through a pointer box reads the pointee
1449
1558
  // first, so a nil pointer inside the interface is a catchable panic.
1450
1559
  _emitNilPtrBoxPanic(cand, recvLocal) {
@@ -1570,11 +1679,9 @@ export class FunctionEmitter {
1570
1679
  size,
1571
1680
  });
1572
1681
  } else {
1573
- this.pushInstruction({ op: "i32.const", value: size });
1574
- this.pushInstruction({
1575
- op: "array.new_default",
1576
- typeIndex: arrInfo.typeIndex,
1577
- });
1682
+ this._emitNewArray(elemType, arrInfo, () =>
1683
+ this.pushInstruction({ op: "i32.const", value: size }),
1684
+ );
1578
1685
  }
1579
1686
  return;
1580
1687
  }
@@ -1616,8 +1723,24 @@ export class FunctionEmitter {
1616
1723
  }
1617
1724
  }
1618
1725
 
1619
- allocTemp(wasmType) {
1620
- return this.acquireTemp(wasmType);
1726
+ // Pushes a new array of `arrInfo` with the length `emitLen` pushes. Elements
1727
+ // start at their zero value: strings are the empty string, not a null
1728
+ // reference, so the array reads, compares and prints like Go's.
1729
+ _emitNewArray(elemType, arrInfo, emitLen) {
1730
+ if (
1731
+ arrInfo.elemWType === "externref" &&
1732
+ isStringType(elemType, this.mod.checker)
1733
+ ) {
1734
+ this.emitZeroValue(elemType, "externref");
1735
+ emitLen();
1736
+ this.pushInstruction({ op: "array.new", typeIndex: arrInfo.typeIndex });
1737
+ return;
1738
+ }
1739
+ emitLen();
1740
+ this.pushInstruction({
1741
+ op: "array.new_default",
1742
+ typeIndex: arrInfo.typeIndex,
1743
+ });
1621
1744
  }
1622
1745
 
1623
1746
  // Temps are keyed by a canonical string so ref-typed temps (fresh object
@@ -1796,10 +1919,25 @@ export class FunctionEmitter {
1796
1919
  this.emitExprStmt(stmt);
1797
1920
  break;
1798
1921
 
1922
+ // Evaluates `stmt.expr` and panics (recoverably) when it is nil.
1923
+ case "NilCheckStmt":
1924
+ this.emitExpr(stmt.expr, "anyref");
1925
+ this._emitNilBranch();
1926
+ this.pushInstruction("drop");
1927
+ break;
1928
+
1799
1929
  case "LabeledStmt":
1800
1930
  if (stmt.body) {
1801
- if (stmt.body.kind === "ForStmt") {
1931
+ // Stacked labels (`a: b: for`) all target the loop: outer
1932
+ // labels alias the innermost one.
1933
+ if (stmt.body.kind !== "LabeledStmt") {
1802
1934
  stmt.body.label = stmt.label;
1935
+ this._labelAliases?.delete(stmt.label);
1936
+ } else {
1937
+ let inner = stmt.body;
1938
+ while (inner.body?.kind === "LabeledStmt") inner = inner.body;
1939
+ this._labelAliases ??= new Map();
1940
+ this._labelAliases.set(stmt.label, inner.label);
1803
1941
  }
1804
1942
  this.emitStmt(stmt.body);
1805
1943
  }
@@ -5,7 +5,12 @@
5
5
  import { foldConstExpr } from "../../typechecker/const-fold.js";
6
6
  import { orderPackageInits } from "../../typechecker/init-order.js";
7
7
  import { _someNode } from "./prescan.js";
8
- import { isStructType, toWasmType, variadicAsSlice } from "./types.js";
8
+ import {
9
+ isStringType,
10
+ isStructType,
11
+ toWasmType,
12
+ variadicAsSlice,
13
+ } from "./types.js";
9
14
 
10
15
  // Package-level `const` declarations (iota is already substituted by the
11
16
  // parser). Literals and expressions the folder can reduce become BasicLits in
@@ -99,6 +104,7 @@ function _zeroFieldConst(f, mod) {
99
104
  const w = f.wType;
100
105
  if (w === "i64") return [{ op: "i64.const", value: 0n }];
101
106
  if (w === "f32" || w === "f64") return [{ op: `${w}.const`, value: 0 }];
107
+ if (w === "v128") return [{ op: "v128.const", bytes: new Array(16).fill(0) }];
102
108
  if (w === "externref") return [{ op: "ref.null", heapType: "extern" }];
103
109
  if (w === "anyref") return [{ op: "ref.null", heapType: "any" }];
104
110
  if (typeof w !== "object" || w === null)
@@ -169,6 +175,36 @@ function _zeroArrayConst(goType, mod, depth = 0) {
169
175
  return out;
170
176
  }
171
177
 
178
+ // True when the zero value of `t` holds a string (a string, or a struct or
179
+ // array containing one). A constant expression cannot build the empty string,
180
+ // so such globals get their zero value from the start function.
181
+ function _zeroHasString(t, mod, depth = 0) {
182
+ if (!t || depth > 4 || _isPointerGoType(t, mod)) return false;
183
+ if (isStringType(t, mod.checker)) return true;
184
+ const shape = _arrayShape(t, mod.checker);
185
+ if (shape) return _zeroHasString(shape.elem, mod, depth + 1);
186
+ const w = toWasmType(t, mod.checker, mod);
187
+ if (typeof w === "object" && typeof w?.typeIndex === "number") {
188
+ const info = Array.from(mod.structTypes.values()).find(
189
+ (x) => x.typeIndex === w.typeIndex,
190
+ );
191
+ if (info)
192
+ return info.fields.some((f) => _zeroHasString(f.goType, mod, depth + 1));
193
+ }
194
+ return false;
195
+ }
196
+
197
+ // Types of the names in `var a, b = f()` / `var v, ok = m[k]`: one value fills
198
+ // every name.
199
+ function _spreadValueTypes(spec, value) {
200
+ if (spec._commaOk)
201
+ return [
202
+ value._mapValueType ?? value._type,
203
+ { kind: "basic", name: "bool" },
204
+ ];
205
+ return value?._type?.types ?? [];
206
+ }
207
+
172
208
  export function _collectPackageGlobals(progs, mod, checker) {
173
209
  const pending = []; // non-constant initializers, run by the start function
174
210
  const pendingConsts = []; // unfoldable consts; they run before any var
@@ -183,11 +219,12 @@ export function _collectPackageGlobals(progs, mod, checker) {
183
219
  const name = names[i];
184
220
  // Foldable consts live in `constCache`, not in a global.
185
221
  if (isConst && !mod.isNonLiteralConst(name)) continue;
222
+ const spreadTypes =
223
+ values.length === 1 && names.length > 1
224
+ ? _spreadValueTypes(spec, values[0])
225
+ : null;
186
226
  const rawType =
187
- spec.type ??
188
- (values.length === 1 && names.length > 1
189
- ? values[0]?._type?.types?.[i]
190
- : values[i]?._type);
227
+ spec.type ?? (spreadTypes ? spreadTypes[i] : values[i]?._type);
191
228
  const wType = toWasmType(rawType, checker, mod);
192
229
  const gIdx = mod.globals.length;
193
230
  let needsRuntimeZero = false;
@@ -252,6 +289,9 @@ export function _collectPackageGlobals(progs, mod, checker) {
252
289
  }
253
290
  }
254
291
 
292
+ if (values.length === 0 && _zeroHasString(rawType, mod))
293
+ needsRuntimeZero = true;
294
+
255
295
  let litNode = values?.[i];
256
296
  let sign = 1;
257
297
  if (
@@ -302,7 +342,7 @@ export function _collectPackageGlobals(progs, mod, checker) {
302
342
  if (needsRuntimeZero) pending.push({ name, zeroType: rawType });
303
343
  if (!isConst && i === 0 && values.length === 1 && names.length > 1) {
304
344
  // `var q, r = pair()`: one call fills every name.
305
- pending.push({ name, names, expr: values[0] });
345
+ pending.push({ name, names, expr: values[0], types: spreadTypes });
306
346
  }
307
347
  if (values[i] && !isConstInit && names.length === values.length) {
308
348
  (isConst ? pendingConsts : pending).push({
@@ -355,7 +395,7 @@ export function _orderConstInits(entries) {
355
395
  // initializer in declaration order and invoking package `init()` functions;
356
396
  // registered as the module start function.
357
397
  export function _makeGlobalInitFunc(pending, initNames = []) {
358
- const stmts = pending.map(({ name, names, expr, zeroType }) =>
398
+ const stmts = pending.map(({ name, names, expr, types, zeroType }) =>
359
399
  zeroType
360
400
  ? { kind: "GlobalZeroInit", name, goType: zeroType }
361
401
  : {
@@ -364,7 +404,7 @@ export function _makeGlobalInitFunc(pending, initNames = []) {
364
404
  ? names.map((n, i) => ({
365
405
  kind: "Ident",
366
406
  name: n,
367
- _type: expr._type?.types?.[i],
407
+ _type: types?.[i] ?? expr._type?.types?.[i],
368
408
  }))
369
409
  : [{ kind: "Ident", name, _type: expr._type }],
370
410
  op: "=",
@@ -1,14 +1,12 @@
1
1
  // src/backend/wasm/glue.js
2
2
  // Runtime imports and JS glue for executing GoFront WASM modules.
3
3
 
4
- import { WASM_IMPORTS_JS, WASM_STDLIB_JS } from "./boundary.js";
4
+ import { WASM_IMPORTS_JS } from "./boundary.js";
5
5
 
6
6
  // The import table is authored once as source text (it is spliced into the
7
7
  // production bundle by the facade); evaluate that same text here so tests
8
8
  // exercise exactly what ships. The text is a compile-time constant.
9
- const makeImports = new Function(
10
- `${WASM_IMPORTS_JS}\n${WASM_STDLIB_JS}\nreturn __gfw_imports;`,
11
- )();
9
+ const makeImports = new Function(`${WASM_IMPORTS_JS}\nreturn __gfw_imports;`)();
12
10
 
13
11
  function createWasmImports({
14
12
  stringTable = [],