gofront 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -1,20 +1,60 @@
1
1
  // src/backend/wasm/emit-stmts.js
2
2
  // FunctionEmitter mixin: statement emission (decls, assignment, control flow, return).
3
3
 
4
- import { isIntRangeType, isRangeFor } from "../../lower/range.js";
4
+ import { loopVarsCapturedInLoop } from "../../lower/captures.js";
5
+ import { rootIdentName } from "../../lower/ownership.js";
6
+ import {
7
+ getOrCreateIteratorFuncLit,
8
+ isIntRangeType,
9
+ isIteratorRangeFor,
10
+ isRangeFor,
11
+ } from "../../lower/range.js";
12
+ import { hasCallOrReceive, someChild } from "../../lower/walk.js";
5
13
  import {
6
14
  getMapKeyValTypes,
7
15
  isArrayType,
16
+ isComplex,
17
+ isComplex64,
8
18
  isFuncType,
9
19
  isInterfaceType,
10
20
  isMapType,
11
21
  isNonEmptyInterface,
12
22
  isPointerToStruct,
23
+ isScalarType,
13
24
  isSliceType,
14
25
  isStringType,
15
26
  isStructType,
27
+ isTaggedSliceType,
16
28
  } from "./types.js";
17
29
 
30
+ function hasReturnInBlock(n) {
31
+ let found = false;
32
+ const check = (x) => {
33
+ if (!x || typeof x !== "object" || found) return;
34
+ if (Array.isArray(x)) {
35
+ for (const elem of x) check(elem);
36
+ return;
37
+ }
38
+ if (x.kind === "ReturnStmt") {
39
+ found = true;
40
+ return;
41
+ }
42
+ if (x.kind === "FuncLit" && !x._isYieldClosure) return;
43
+ for (const k of Object.keys(x)) {
44
+ if (k.startsWith("_")) continue;
45
+ check(x[k]);
46
+ }
47
+ };
48
+ check(n);
49
+ return found;
50
+ }
51
+
52
+ // A plain `string`: the one string type that lives in `any` as the bare
53
+ // string. A named string type is boxed like any other scalar (see emitExpr).
54
+ function isBareStringType(t, checker) {
55
+ return isStringType(t, checker) && t.kind !== "named";
56
+ }
57
+
18
58
  export class StmtsEmitter {
19
59
  emitVarDecl(stmt) {
20
60
  const decls = stmt.decls ?? stmt.specs ?? [stmt];
@@ -22,6 +62,15 @@ export class StmtsEmitter {
22
62
  const names = spec.names ?? (spec.name ? [spec.name] : []);
23
63
  const values = spec.value ?? (spec.init ? [spec.init] : []);
24
64
 
65
+ // `var a, b = f()` / `var v, ok = m[k]` bind like `a, b := ...`.
66
+ if (names.length > 1 && values.length === 1) {
67
+ this.emitDefineStmt({
68
+ lhs: names.map((name) => ({ kind: "Ident", name })),
69
+ rhs: values,
70
+ });
71
+ continue;
72
+ }
73
+
25
74
  for (let i = 0; i < names.length; i++) {
26
75
  const name = names[i];
27
76
  const rawType = spec.type ?? values[i]?._type;
@@ -31,7 +80,7 @@ export class StmtsEmitter {
31
80
  if (values?.[i]) {
32
81
  const r = values[i];
33
82
  this.emitExpr(r, wType);
34
- this._emitCopyIfValueStruct(r, rawType, wType);
83
+ this._emitCopyIfValue(r, rawType, wType);
35
84
  if (localInfo?.isBoxed) {
36
85
  this.pushInstruction({
37
86
  op: "struct.new",
@@ -88,9 +137,11 @@ export class StmtsEmitter {
88
137
  return;
89
138
  }
90
139
 
140
+ const rhsVals = this._parallelAssignValues(lhsNodes, rhsNodes, true);
91
141
  for (let i = 0; i < lhsNodes.length; i++) {
92
- this._emitDefineSingle(lhsNodes[i], rhsNodes[i]);
142
+ this._emitDefineSingle(lhsNodes[i], rhsVals[i]);
93
143
  }
144
+ this._releaseParallelTemps(rhsVals, rhsNodes);
94
145
  }
95
146
 
96
147
  _emitDefineSingle(l, r) {
@@ -100,11 +151,17 @@ export class StmtsEmitter {
100
151
  this.pushInstruction("drop");
101
152
  return;
102
153
  }
154
+ if (l._redecl && this.resolveLocal(l.name)) {
155
+ this._emitAssignSingle(l, r, "=");
156
+ return;
157
+ }
103
158
  const wType = this.toWasmType(r._type);
159
+ // The value is computed before the new name exists: `x := x * 2`
160
+ // in an inner block reads the outer `x`.
161
+ this.emitExpr(r, wType);
162
+ this._emitCopyIfValue(r, r._type, wType);
104
163
  const idx = this.allocLocal(l.name, wType, r._type);
105
164
  const localInfo = this.locals.get(l.name);
106
- this.emitExpr(r, wType);
107
- this._emitCopyIfValueStruct(r, r._type, wType);
108
165
  if (localInfo?.isBoxed) {
109
166
  this.pushInstruction({
110
167
  op: "struct.new",
@@ -114,7 +171,38 @@ export class StmtsEmitter {
114
171
  this.pushInstruction({ op: "local.set", index: idx });
115
172
  }
116
173
 
174
+ // Stores a multi-value call into temporaries (last result first), then
175
+ // assigns each one to its target. Used when a target is not a plain new
176
+ // local: an index, field or dereference, or a name redeclared by `:=`.
177
+ _emitTupleViaTemps(lhsNodes, rhsNode, assignOne, hoist) {
178
+ const tupleTypes = rhsNode._type.types;
179
+ this.emitExpr(rhsNode);
180
+ const temps = [];
181
+ for (let i = lhsNodes.length - 1; i >= 0; i--) {
182
+ const goType = tupleTypes[i];
183
+ const wType = this.toWasmType(goType);
184
+ const name = `__tup_tmp_${this.tempCounter++}`;
185
+ const idx = this.allocLocal(name, wType, goType, true);
186
+ this.pushInstruction({ op: "local.set", index: idx });
187
+ temps[i] = { kind: "Ident", name, _type: goType, _parTmp: true };
188
+ }
189
+ const undo = hoist ? this._hoistLhsOperands(lhsNodes) : null;
190
+ for (let i = 0; i < lhsNodes.length; i++) assignOne(lhsNodes[i], temps[i]);
191
+ undo?.();
192
+ for (const t of temps) this.locals.delete(t.name);
193
+ }
194
+
195
+ _isPlainTupleTarget(l) {
196
+ return l.kind === "Ident";
197
+ }
198
+
117
199
  _emitTupleDefine(lhsNodes, rhsNode) {
200
+ if (lhsNodes.some((l) => !this._isPlainTupleTarget(l) || l._redecl)) {
201
+ this._emitTupleViaTemps(lhsNodes, rhsNode, (l, t) =>
202
+ this._emitDefineSingle(l, t),
203
+ );
204
+ return;
205
+ }
118
206
  const tupleTypes = rhsNode._type.types;
119
207
  const localIndices = [];
120
208
  for (let i = 0; i < lhsNodes.length; i++) {
@@ -200,9 +288,192 @@ export class StmtsEmitter {
200
288
  return;
201
289
  }
202
290
 
291
+ const hoisted = op === "=" ? this._hoistLhsOperands(lhsNodes) : null;
292
+ const rhsVals = this._parallelAssignValues(lhsNodes, rhsNodes);
203
293
  for (let i = 0; i < lhsNodes.length; i++) {
204
- this._emitAssignSingle(lhsNodes[i], rhsNodes[i], op);
294
+ this._emitAssignSingle(lhsNodes[i], rhsVals[i], op);
205
295
  }
296
+ this._releaseParallelTemps(rhsVals, rhsNodes);
297
+ hoisted?.();
298
+ }
299
+
300
+ // `k, xs[k] = 1, 5`: Go evaluates the operands of every index and field
301
+ // target before any assignment. An operand that mentions a variable
302
+ // assigned by the same statement is evaluated into a temporary first.
303
+ // Returns a function that restores the AST and releases the temporaries.
304
+ _hoistLhsOperands(lhsNodes) {
305
+ if (lhsNodes.length < 2) return null;
306
+ const assigned = new Set(
307
+ lhsNodes
308
+ .filter((l) => l.kind === "Ident" && l.name !== "_")
309
+ .map((l) => l.name),
310
+ );
311
+ if (assigned.size === 0) return null;
312
+ const undo = [];
313
+ const names = [];
314
+ const visit = (l) => {
315
+ let keys;
316
+ if (l.kind === "IndexExpr") keys = ["expr", "index"];
317
+ else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
318
+ else return;
319
+ for (const key of keys) {
320
+ const operand = l[key];
321
+ if (!this._mentions(operand, assigned)) continue;
322
+ const goType = operand._type ?? this._resolveExprGoType(operand);
323
+ // A struct or array value names the variable's storage in Go: hoist
324
+ // the operands inside it (`ps[n].A`) instead of the value.
325
+ if (
326
+ key === "expr" &&
327
+ (this._isValueStruct(goType) || this._isValueArray(goType))
328
+ ) {
329
+ visit(operand);
330
+ continue;
331
+ }
332
+ const wType = this.toWasmType(goType);
333
+ const name = `__lhs_tmp_${this.tempCounter++}`;
334
+ const idx = this.allocLocal(name, wType, goType, true);
335
+ this.emitExpr(operand, wType);
336
+ this.pushInstruction({ op: "local.set", index: idx });
337
+ undo.push([l, key, operand]);
338
+ names.push(name);
339
+ l[key] = { kind: "Ident", name, _type: goType, _parTmp: true };
340
+ }
341
+ };
342
+ for (const l of lhsNodes) visit(l);
343
+ if (undo.length === 0) return null;
344
+ return () => {
345
+ for (const [node, key, v] of undo) node[key] = v;
346
+ for (const n of names) this.locals.delete(n);
347
+ };
348
+ }
349
+
350
+ // `x, y = y, x`: Go evaluates every right-hand side before assigning.
351
+ // When a later value could see an earlier assignment, every value that
352
+ // is not pure is stored in a temporary first (in source order, so side
353
+ // effects keep their order). Pure values stay inline, so `i, n = 0,
354
+ // len(xs)` is unchanged. A definition only exposes its redeclared
355
+ // names: a new name is not visible to the values being assigned to it.
356
+ _parallelAssignValues(lhsNodes, rhsNodes, define = false) {
357
+ if (lhsNodes.length < 2 || rhsNodes.length !== lhsNodes.length)
358
+ return rhsNodes;
359
+ const roots = new Set(
360
+ lhsNodes.map((l) => rootIdentName(l)).filter((n) => n && n !== "_"),
361
+ );
362
+ const pure = rhsNodes.map((r) => this._independentOf(r, roots));
363
+ const redecl = lhsNodes.some((l) => l._redecl);
364
+ const observes = (r, i) =>
365
+ define ? this._mentions(r, roots) || (redecl && !pure[i]) : !pure[i];
366
+ if (!rhsNodes.some((r, i) => i > 0 && observes(r, i))) return rhsNodes;
367
+ const inPlace = lhsNodes.some(
368
+ (l) => this._isInPlaceArrayStore(l) || this._isStableTarget(l),
369
+ );
370
+ return rhsNodes.map((r, i) => {
371
+ if (pure[i]) return r;
372
+ const goType = r._type ?? this._resolveExprGoType(r);
373
+ const wType = this.toWasmType(goType);
374
+ const name = `__par_tmp_${this.tempCounter++}`;
375
+ const idx = this.allocLocal(name, wType, goType, true);
376
+ this.emitExpr(r, wType);
377
+ // An array temp must snapshot the value when a target is stored in
378
+ // place (`a, b = b, a` with `p := &a`): the store would otherwise
379
+ // change what the temp holds. The snapshot is already a fresh
380
+ // copy, so assigning from the temp does not copy it again.
381
+ const isArr = this._isValueArray(goType);
382
+ const snapshot = inPlace && (isArr || this._isValueStruct(goType));
383
+ if (snapshot) this._emitCopyIfValue(r, goType, wType);
384
+ this.pushInstruction({ op: "local.set", index: idx });
385
+ return {
386
+ kind: "Ident",
387
+ name,
388
+ _type: goType,
389
+ _parTmp: true,
390
+ _freshArray: snapshot && isArr,
391
+ };
392
+ });
393
+ }
394
+
395
+ // True when assigning to `l` writes into an existing array object (so an
396
+ // earlier-read array value sharing that object would see the write): a
397
+ // boxed (address-taken) array local, `*p` on an array pointer, or an
398
+ // element of an array.
399
+ _isInPlaceArrayStore(l) {
400
+ if (l.kind === "Ident") {
401
+ const info = this.resolveLocal(l.name);
402
+ return !!info?.isBoxed && this._isValueArray(info.goType);
403
+ }
404
+ if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
405
+ const t = (l.operand ?? l.expr)?._type;
406
+ return this._isValueArray(t?.base ?? t);
407
+ }
408
+ if (l.kind === "IndexExpr") {
409
+ const t = l.expr._type ?? this._resolveExprGoType(l.expr);
410
+ return (
411
+ this._isValueArray(t) ||
412
+ (t?.kind === "pointer" && this._isValueArray(t.base))
413
+ );
414
+ }
415
+ return false;
416
+ }
417
+
418
+ // True when `l` is an array or struct target that a whole-value
419
+ // assignment writes in place (see `_needsStableStore`).
420
+ _isStableTarget(l) {
421
+ const t = l._type ?? this._resolveExprGoType(l);
422
+ return this._needsStableStore(l, t);
423
+ }
424
+
425
+ _mentions(node, names) {
426
+ if (!node || typeof node !== "object") return false;
427
+ if (node.kind === "Ident" && names.has(node.name)) return true;
428
+ return someChild(node, (child) => this._mentions(child, names));
429
+ }
430
+
431
+ _releaseParallelTemps(rhsVals, rhsNodes) {
432
+ if (rhsVals === rhsNodes) return;
433
+ for (const r of rhsVals) if (r?._parTmp) this.locals.delete(r.name);
434
+ }
435
+
436
+ // True when evaluating `node` cannot observe an assignment to `roots`
437
+ // (or through a pointer) and has no side effects.
438
+ _independentOf(node, roots) {
439
+ if (!node || typeof node !== "object") return true;
440
+ switch (node.kind) {
441
+ case "BasicLit":
442
+ return true;
443
+ case "Ident":
444
+ return this._independentIdent(node, roots);
445
+ case "BinaryExpr":
446
+ return (
447
+ this._independentOf(node.left, roots) &&
448
+ this._independentOf(node.right, roots)
449
+ );
450
+ case "UnaryExpr":
451
+ return (
452
+ node.op !== "*" &&
453
+ node.op !== "&" &&
454
+ this._independentOf(node.operand, roots)
455
+ );
456
+ case "CallExpr":
457
+ return this._isPureLenCall(node, roots);
458
+ default:
459
+ return false;
460
+ }
461
+ }
462
+
463
+ _independentIdent(node, roots) {
464
+ if (node._const || node.name === "nil") return true;
465
+ if (roots.has(node.name)) return false;
466
+ const local = this.resolveLocal(node.name);
467
+ return !!local && !local.isBoxed;
468
+ }
469
+
470
+ // `len`/`cap` of a pure operand.
471
+ _isPureLenCall(node, roots) {
472
+ const fn = node.func;
473
+ if (fn?.kind !== "Ident" || (fn.name !== "len" && fn.name !== "cap"))
474
+ return false;
475
+ if (this.resolveLocal(fn.name) || node.args?.length !== 1) return false;
476
+ return this._independentOf(node.args[0], roots);
206
477
  }
207
478
 
208
479
  _emitAssignToTarget(target, wasmType, goType, emitValue) {
@@ -244,10 +515,48 @@ export class StmtsEmitter {
244
515
  this.allocLocal(l.name, wType, r._type);
245
516
  localInfo = this.locals.get(l.name);
246
517
  }
518
+ if (
519
+ localInfo &&
520
+ op !== ":=" &&
521
+ this._needsStableStore(l, localInfo.goType)
522
+ ) {
523
+ this._emitStableStore(localInfo.goType, r, () => {
524
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
525
+ if (localInfo.isBoxed)
526
+ this.pushInstruction({
527
+ op: "struct.get",
528
+ typeIndex: localInfo.boxInfo.typeIndex,
529
+ fieldIndex: 0,
530
+ });
531
+ });
532
+ return;
533
+ }
534
+ const globalStable =
535
+ !localInfo && op !== ":=" && this.mod.resolveGlobal(l.name);
536
+ if (globalStable && this._needsStableStore(l, globalStable.goType)) {
537
+ this._emitStableStore(globalStable.goType, r, () => {
538
+ this.pushInstruction({ op: "global.get", index: globalStable.index });
539
+ });
540
+ return;
541
+ }
247
542
  if (localInfo) {
543
+ if (localInfo.isBoxed && this._isValueArray(localInfo.goType)) {
544
+ // Pointers to the array (`&a`) hold its reference: write in place.
545
+ this._emitArrayStoreInto(localInfo.goType, r, () => {
546
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
547
+ this.pushInstruction({
548
+ op: "struct.get",
549
+ typeIndex: localInfo.boxInfo.typeIndex,
550
+ fieldIndex: 0,
551
+ });
552
+ });
553
+ return;
554
+ }
248
555
  if (localInfo.isBoxed) {
249
556
  this.pushInstruction({ op: "local.get", index: localInfo.index });
250
557
  this.emitExpr(r, localInfo.boxInfo.wType);
558
+ if (isArrayType(localInfo.goType, this.mod.checker))
559
+ this._emitCopyIfValue(r, localInfo.goType, localInfo.boxInfo.wType);
251
560
  this.pushInstruction({
252
561
  op: "struct.set",
253
562
  typeIndex: localInfo.boxInfo.typeIndex,
@@ -256,13 +565,13 @@ export class StmtsEmitter {
256
565
  return;
257
566
  }
258
567
  this.emitExpr(r, localInfo.type);
259
- this._emitCopyIfValueStruct(r, localInfo.goType, localInfo.type);
568
+ this._emitCopyIfValue(r, localInfo.goType, localInfo.type);
260
569
  this.pushInstruction({ op: "local.set", index: localInfo.index });
261
570
  } else {
262
571
  const globalInfo = this.mod.resolveGlobal(l.name);
263
572
  if (globalInfo) {
264
573
  this.emitExpr(r, globalInfo.type);
265
- this._emitCopyIfValueStruct(r, globalInfo.goType, globalInfo.type);
574
+ this._emitCopyIfValue(r, globalInfo.goType, globalInfo.type);
266
575
  this.pushInstruction({
267
576
  op: "global.set",
268
577
  index: globalInfo.index,
@@ -292,7 +601,20 @@ export class StmtsEmitter {
292
601
  this._resolveExprGoType(l.expr);
293
602
  const baseWType = this.toWasmType(exprGoType);
294
603
 
295
- this.emitExpr(l.expr, baseWType);
604
+ if (this._needsStableStore(l, lastStep.field.goType)) {
605
+ this._emitStableStore(lastStep.field.goType, r, () => {
606
+ this.emitSelectorBase(l.expr, baseWType, exprGoType);
607
+ for (const step of path) {
608
+ this.pushInstruction({
609
+ op: "struct.get",
610
+ typeIndex: step.structInfo.typeIndex,
611
+ fieldIndex: step.fieldIndex,
612
+ });
613
+ }
614
+ });
615
+ return;
616
+ }
617
+ this.emitSelectorBase(l.expr, baseWType, exprGoType);
296
618
  for (let i = 0; i < path.length - 1; i++) {
297
619
  const step = path[i];
298
620
  this.pushInstruction({
@@ -302,11 +624,7 @@ export class StmtsEmitter {
302
624
  });
303
625
  }
304
626
  this.emitExpr(r, lastStep.field.wType);
305
- this._emitCopyIfValueStruct(
306
- r,
307
- lastStep.field.goType,
308
- lastStep.field.wType,
309
- );
627
+ this._emitCopyIfValue(r, lastStep.field.goType, lastStep.field.wType);
310
628
  this.pushInstruction({
311
629
  op: "struct.set",
312
630
  typeIndex: lastStep.structInfo.typeIndex,
@@ -315,57 +633,8 @@ export class StmtsEmitter {
315
633
  return;
316
634
  }
317
635
 
318
- if (l.kind === "UnaryExpr" && l.op === "*") {
319
- const structInfo = this._resolveStructInfo(l.operand);
320
- if (structInfo) {
321
- const baseWType = this.toWasmType(l.operand._type);
322
- const tmpL = this.acquireTemp(baseWType);
323
- const tmpR = this.acquireTemp(baseWType);
324
- this.emitExpr(l.operand, baseWType);
325
- this.pushInstruction({ op: "local.set", index: tmpL });
326
- this.emitExpr(r, baseWType);
327
- this.pushInstruction({ op: "local.set", index: tmpR });
328
- for (let i = 0; i < structInfo.fields.length; i++) {
329
- this.pushInstruction({ op: "local.get", index: tmpL });
330
- this.pushInstruction({ op: "local.get", index: tmpR });
331
- this.pushInstruction({
332
- op: "struct.get",
333
- typeIndex: structInfo.typeIndex,
334
- fieldIndex: i,
335
- });
336
- this.pushInstruction({
337
- op: "struct.set",
338
- typeIndex: structInfo.typeIndex,
339
- fieldIndex: i,
340
- });
341
- }
342
- this.releaseTemp(tmpR, baseWType);
343
- this.releaseTemp(tmpL, baseWType);
344
- return;
345
- }
346
- const operandGoType =
347
- l.operand._type ??
348
- (l.operand.kind === "Ident"
349
- ? this.resolveLocal(l.operand.name)?.goType
350
- : null) ??
351
- this._resolveExprGoType(l.operand);
352
- const box = this.mod.getBoxType(operandGoType?.base ?? operandGoType);
353
- const baseWType = {
354
- kind: "ref",
355
- nullable: true,
356
- typeIndex: box.typeIndex,
357
- };
358
- const boxTmp = this.acquireTemp(baseWType);
359
- this.emitExpr(l.operand, baseWType);
360
- this.pushInstruction({ op: "local.set", index: boxTmp });
361
- this.pushInstruction({ op: "local.get", index: boxTmp });
362
- this.emitExpr(r, box.wType);
363
- this.pushInstruction({
364
- op: "struct.set",
365
- typeIndex: box.typeIndex,
366
- fieldIndex: 0,
367
- });
368
- this.releaseTemp(boxTmp, baseWType);
636
+ if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
637
+ this._emitDerefAssign(l, r);
369
638
  return;
370
639
  }
371
640
 
@@ -381,7 +650,7 @@ export class StmtsEmitter {
381
650
  this.emitExpr(baseNode, mapInfo.wType);
382
651
  this.emitExpr(l.index, mapInfo.keyWType);
383
652
  this.emitExpr(r, mapInfo.valWType);
384
- this._emitCopyIfValueStruct(r, valType, mapInfo.valWType);
653
+ this._emitCopyIfValue(r, valType, mapInfo.valWType);
385
654
  this.pushInstruction({
386
655
  op: "call",
387
656
  funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
@@ -400,18 +669,33 @@ export class StmtsEmitter {
400
669
  baseType?.kind === "pointer" &&
401
670
  isArrayType(baseType.base, this.mod.checker);
402
671
 
403
- const elemGoType = isSlice
404
- ? baseType.elem
405
- : isPtrToArr
406
- ? baseType.base.elem
407
- : baseType.elem;
672
+ const elemGoType = this._getArrayElemType(
673
+ isPtrToArr ? baseType.base : baseType,
674
+ );
408
675
  const arrInfo = this.mod.getArrayType(elemGoType);
409
676
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
410
677
 
678
+ // A whole struct or array stored into an element whose address is
679
+ // taken writes into the existing element, so `p := &xs[0]` stays valid.
680
+ if (this._needsStableStore(l, elemGoType)) {
681
+ if (sliceInfo) {
682
+ this._emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo);
683
+ return;
684
+ }
685
+ this._emitStableStore(elemGoType, r, () => {
686
+ this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
687
+ this.pushInstruction({
688
+ op: "array.get",
689
+ typeIndex: arrInfo.typeIndex,
690
+ });
691
+ });
692
+ return;
693
+ }
694
+
411
695
  this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
412
696
 
413
697
  this.emitExpr(r, arrInfo.elemWType);
414
- this._emitCopyIfValueStruct(r, elemGoType, arrInfo.elemWType);
698
+ this._emitCopyIfValue(r, elemGoType, arrInfo.elemWType);
415
699
 
416
700
  this.pushInstruction({
417
701
  op: "array.set",
@@ -421,7 +705,398 @@ export class StmtsEmitter {
421
705
  }
422
706
  }
423
707
 
708
+ // A range variable that a closure captures and mutates lives in a box,
709
+ // but the loop writes the plain value each iteration. It gets a raw
710
+ // local here, and `_emitRangeBoxes` (at the top of the body) wraps the
711
+ // value in a fresh box per iteration, so each iteration's closures keep
712
+ // their own variable (Go 1.22) and the loop never stores a raw value
713
+ // into a box local.
714
+ _allocRangeVar(name, wType, goType) {
715
+ if (!this.mutatedCaptures.has(name))
716
+ return this.allocLocal(name, wType, goType);
717
+ const idx = this.allocLocal(name, wType, goType, true);
718
+ if (!this._pendingRangeBoxes) this._pendingRangeBoxes = [];
719
+ this._pendingRangeBoxes.push({ name, idx, wType, goType });
720
+ return idx;
721
+ }
722
+
723
+ _emitRangeBoxes() {
724
+ const pending = this._pendingRangeBoxes ?? [];
725
+ this._pendingRangeBoxes = [];
726
+ for (const { name, idx, wType, goType } of pending) {
727
+ const boxIdx = this.allocLocal(name, wType, goType);
728
+ const info = this.locals.get(name);
729
+ this.pushInstruction({ op: "local.get", index: idx });
730
+ this.pushInstruction({
731
+ op: "struct.new",
732
+ typeIndex: info.boxInfo.typeIndex,
733
+ });
734
+ this.pushInstruction({ op: "local.set", index: boxIdx });
735
+ }
736
+ }
737
+
738
+ // The struct that holds the last field of `path`: a promoted field
739
+ // (`o.N` on an embedded `Inner`) lives on the embedded struct.
740
+ _fieldHolderWType(path, baseWType) {
741
+ if (path.length <= 1) return baseWType;
742
+ const holder = path[path.length - 1].structInfo;
743
+ return { kind: "ref", nullable: true, typeIndex: holder.typeIndex };
744
+ }
745
+
746
+ // Copy the array value `r` into the existing array that `emitDst` pushes,
747
+ // so every pointer to that array sees the new elements.
748
+ _emitArrayStoreInto(goType, r, emitDst) {
749
+ const elemGoType = this._getArrayElemType(goType);
750
+ const arrInfo = this.mod.getArrayType(elemGoType);
751
+ const wType = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
752
+ if (this._isValueStruct(elemGoType)) {
753
+ this._emitStructElemsStoreInto(elemGoType, arrInfo, wType, r, emitDst);
754
+ return;
755
+ }
756
+ const dst = this.acquireTemp(wType);
757
+ emitDst();
758
+ this.pushInstruction({ op: "local.tee", index: dst });
759
+ this.pushInstruction({ op: "i32.const", value: 0 });
760
+ this.emitExpr(r, wType);
761
+ this._emitCopyIfValue(r, goType, wType);
762
+ this.pushInstruction({ op: "i32.const", value: 0 });
763
+ this.pushInstruction({ op: "local.get", index: dst });
764
+ this.pushInstruction("array.len");
765
+ this.pushInstruction({
766
+ op: "array.copy",
767
+ typeIndex: arrInfo.typeIndex,
768
+ });
769
+ this.releaseTemp(dst, wType);
770
+ }
771
+
772
+ _isValueStruct(goType) {
773
+ return (
774
+ !!goType &&
775
+ goType.kind !== "pointer" &&
776
+ isStructType(goType, this.mod.checker, this.mod) &&
777
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
778
+ );
779
+ }
780
+
781
+ // An array of structs holds element references, so `array.copy` would
782
+ // swap them and leave `&a[i]` pointing at the old element. Copy each
783
+ // element's fields into the existing element struct instead.
784
+ _emitStructElemsStoreInto(elemGoType, arrInfo, wType, r, emitDst) {
785
+ const elemInfo = this._resolveStructInfo({ _type: elemGoType });
786
+ const eType = arrInfo.elemWType;
787
+ const dst = this.acquireTemp(wType);
788
+ const src = this.acquireTemp(wType);
789
+ const i = this.acquireTemp("i32");
790
+ const d = this.acquireTemp(eType);
791
+ const s = this.acquireTemp(eType);
792
+ emitDst();
793
+ this.pushInstruction({ op: "local.set", index: dst });
794
+ this.emitExpr(r, wType);
795
+ this.pushInstruction({ op: "local.set", index: src });
796
+ this.pushInstruction({ op: "i32.const", value: 0 });
797
+ this.pushInstruction({ op: "local.set", index: i });
798
+ this.pushInstruction({ op: "block", blockType: "void" });
799
+ this.pushInstruction({ op: "loop", blockType: "void" });
800
+ this.pushInstruction({ op: "local.get", index: i });
801
+ this.pushInstruction({ op: "local.get", index: dst });
802
+ this.pushInstruction("array.len");
803
+ this.pushInstruction("i32.ge_s");
804
+ this.pushInstruction({ op: "br_if", depth: 1 });
805
+ for (const [tmp, arr] of [
806
+ [d, dst],
807
+ [s, src],
808
+ ]) {
809
+ this.pushInstruction({ op: "local.get", index: arr });
810
+ this.pushInstruction({ op: "local.get", index: i });
811
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
812
+ this.pushInstruction({ op: "local.set", index: tmp });
813
+ }
814
+ this._emitStructFieldsInto(elemInfo, d, s);
815
+ this.pushInstruction({ op: "local.get", index: i });
816
+ this.pushInstruction({ op: "i32.const", value: 1 });
817
+ this.pushInstruction("i32.add");
818
+ this.pushInstruction({ op: "local.set", index: i });
819
+ this.pushInstruction({ op: "br", depth: 0 });
820
+ this.pushInstruction("end");
821
+ this.pushInstruction("end");
822
+ this.releaseTemp(s, eType);
823
+ this.releaseTemp(d, eType);
824
+ this.releaseTemp(i, "i32");
825
+ this.releaseTemp(src, wType);
826
+ this.releaseTemp(dst, wType);
827
+ }
828
+
829
+ // Whole-value assignment to a variable (or a field of one) whose address
830
+ // is taken writes into the existing storage, so earlier pointers to it or
831
+ // to its parts see the new value (Task 0.33). Other variables keep the
832
+ // plain local.set / struct.set path.
833
+ _needsStableStore(l, goType) {
834
+ if (!goType || goType.kind === "pointer") return false;
835
+ const isArr = this._isValueArray(goType);
836
+ if (
837
+ !isArr &&
838
+ !(
839
+ isStructType(goType, this.mod.checker, this.mod) &&
840
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
841
+ )
842
+ )
843
+ return false;
844
+ // Any callee may hold `&xs[i]` of a slice element, so when the program
845
+ // takes one, a whole-value store into an element writes in place.
846
+ if (this.mod.lowerResult?.elemAddrTaken && this._isSliceElemTarget(l))
847
+ return true;
848
+ const root = rootIdentName(l);
849
+ if (!root) return false;
850
+ // Any code may hold the address of a package-level variable.
851
+ if (!this.resolveLocal(root)) return !!this.mod.resolveGlobal(root);
852
+ const decl = this.rootFuncDecl._sourceDecl ?? this.rootFuncDecl;
853
+ const ctx = this.mod.lowerResult?.ownership?.get(decl);
854
+ return Boolean(ctx?.fnMutates(root, true));
855
+ }
856
+
857
+ // `xs[i] = v` into a slice element in place. Capacity beyond the length
858
+ // may hold null elements (a reslice exposes them), so a null element is
859
+ // first replaced by a zero value that the store then fills.
860
+ _emitSliceElemStableStore(l, elemGoType, r, sliceInfo, arrInfo) {
861
+ const arrT = {
862
+ kind: "ref",
863
+ nullable: true,
864
+ typeIndex: arrInfo.typeIndex,
865
+ };
866
+ const arr = this.acquireTemp(arrT);
867
+ const idx = this.acquireTemp("i32");
868
+ this._emitElemAddr(l.expr, l.index, sliceInfo, arrInfo);
869
+ this.pushInstruction({ op: "local.set", index: idx });
870
+ this.pushInstruction({ op: "local.set", index: arr });
871
+ const getElem = () => {
872
+ this.pushInstruction({ op: "local.get", index: arr });
873
+ this.pushInstruction({ op: "local.get", index: idx });
874
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
875
+ };
876
+ getElem();
877
+ this.pushInstruction("ref.is_null");
878
+ this.pushInstruction({ op: "if", blockType: "void" });
879
+ this.pushInstruction({ op: "local.get", index: arr });
880
+ this.pushInstruction({ op: "local.get", index: idx });
881
+ this.emitZeroValue(elemGoType, arrInfo.elemWType);
882
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
883
+ this.pushInstruction("end");
884
+ this._emitStableStore(elemGoType, r, getElem);
885
+ this.releaseTemp(idx, "i32");
886
+ this.releaseTemp(arr, arrT);
887
+ }
888
+
889
+ // True when `l` is `xs[i]` on a slice (not an array or map).
890
+ _isSliceElemTarget(l) {
891
+ if (l.kind !== "IndexExpr") return false;
892
+ const t = l.expr._type ?? this._resolveExprGoType(l.expr);
893
+ return isSliceType(t, this.mod.checker) && !this._sharedInfo(t);
894
+ }
895
+
896
+ _emitStableStore(goType, r, emitDst) {
897
+ if (this._isValueArray(goType)) {
898
+ this._emitArrayStoreInto(goType, r, emitDst);
899
+ return;
900
+ }
901
+ const info = this._resolveStructInfo({ _type: goType });
902
+ const wType = this.toWasmType(goType);
903
+ const dst = this.acquireTemp(wType);
904
+ const src = this.acquireTemp(wType);
905
+ emitDst();
906
+ this.pushInstruction({ op: "local.set", index: dst });
907
+ this.emitExpr(r, wType);
908
+ this.pushInstruction({ op: "local.set", index: src });
909
+ this._emitStructFieldsInto(info, dst, src);
910
+ this.releaseTemp(src, wType);
911
+ this.releaseTemp(dst, wType);
912
+ }
913
+
914
+ // Copies every field of the struct in `src` into the struct in `dst`;
915
+ // array and value-struct fields are copied into the existing storage.
916
+ _emitStructFieldsInto(info, dst, src) {
917
+ const get = (tmp, i) => {
918
+ this.pushInstruction({ op: "local.get", index: tmp });
919
+ this.pushInstruction({
920
+ op: "struct.get",
921
+ typeIndex: info.typeIndex,
922
+ fieldIndex: i,
923
+ });
924
+ };
925
+ for (let i = 0; i < info.fields.length; i++) {
926
+ const f = info.fields[i];
927
+ const gt = f.goType;
928
+ if (gt && gt.kind !== "pointer" && this._isValueArray(gt)) {
929
+ const arrInfo = this.mod.getArrayType(this._getArrayElemType(gt));
930
+ get(dst, i);
931
+ this.pushInstruction({ op: "i32.const", value: 0 });
932
+ get(src, i);
933
+ const elem = this._getArrayElemType(gt);
934
+ if (
935
+ isArrayType(elem, this.mod.checker) ||
936
+ isStructType(elem, this.mod.checker, this.mod)
937
+ )
938
+ this.emitCopyArray(gt);
939
+ this.pushInstruction({ op: "i32.const", value: 0 });
940
+ get(dst, i);
941
+ this.pushInstruction("array.len");
942
+ this.pushInstruction({
943
+ op: "array.copy",
944
+ typeIndex: arrInfo.typeIndex,
945
+ });
946
+ continue;
947
+ }
948
+ const inner =
949
+ gt &&
950
+ gt.kind !== "pointer" &&
951
+ isStructType(gt, this.mod.checker, this.mod) &&
952
+ !isPointerToStruct(gt, this.mod.checker, this.mod)
953
+ ? this._resolveStructInfo({ _type: gt })
954
+ : null;
955
+ if (inner) {
956
+ const d = this.acquireTemp(f.wType);
957
+ const s = this.acquireTemp(f.wType);
958
+ get(dst, i);
959
+ this.pushInstruction({ op: "local.set", index: d });
960
+ get(src, i);
961
+ this.pushInstruction({ op: "local.set", index: s });
962
+ this._emitStructFieldsInto(inner, d, s);
963
+ this.releaseTemp(s, f.wType);
964
+ this.releaseTemp(d, f.wType);
965
+ continue;
966
+ }
967
+ this.pushInstruction({ op: "local.get", index: dst });
968
+ get(src, i);
969
+ this.pushInstruction({
970
+ op: "struct.set",
971
+ typeIndex: info.typeIndex,
972
+ fieldIndex: i,
973
+ });
974
+ }
975
+ }
976
+
977
+ _emitStructDerefAssign(operand, r, structInfo) {
978
+ const baseWType = this.toWasmType(operand._type);
979
+ const tmpL = this.acquireTemp(baseWType);
980
+ const tmpR = this.acquireTemp(baseWType);
981
+ this.emitExpr(operand, baseWType);
982
+ this.pushInstruction({ op: "local.set", index: tmpL });
983
+ this.emitExpr(r, baseWType);
984
+ this.pushInstruction({ op: "local.set", index: tmpR });
985
+ this._emitNilGuard(tmpL, operand._type);
986
+ for (let i = 0; i < structInfo.fields.length; i++) {
987
+ this.pushInstruction({ op: "local.get", index: tmpL });
988
+ this.pushInstruction({ op: "local.get", index: tmpR });
989
+ this.pushInstruction({
990
+ op: "struct.get",
991
+ typeIndex: structInfo.typeIndex,
992
+ fieldIndex: i,
993
+ });
994
+ this.pushInstruction({
995
+ op: "struct.set",
996
+ typeIndex: structInfo.typeIndex,
997
+ fieldIndex: i,
998
+ });
999
+ }
1000
+ this.releaseTemp(tmpR, baseWType);
1001
+ this.releaseTemp(tmpL, baseWType);
1002
+ }
1003
+
1004
+ // Raises Go's nil-dereference panic when the pointer held in `tmp` is nil
1005
+ // (a plain cast or field write would trap, which `recover` cannot see).
1006
+ _emitNilGuard(tmp, goType) {
1007
+ if (goType?.kind !== "pointer") return;
1008
+ if (this.resolveBranchDepthToRole("nil") == null) return;
1009
+ this.pushInstruction({ op: "local.get", index: tmp });
1010
+ this._emitNilBranch();
1011
+ this.pushInstruction("drop");
1012
+ }
1013
+
1014
+ _emitDerefAssign(l, r) {
1015
+ const operand = l.operand ?? l.expr;
1016
+ const structInfo = this._derefStructInfo(operand);
1017
+ if (structInfo) {
1018
+ this._emitStructDerefAssign(operand, r, structInfo);
1019
+ return;
1020
+ }
1021
+ const operandGoType =
1022
+ operand._type ??
1023
+ (operand.kind === "Ident"
1024
+ ? this.resolveLocal(operand.name)?.goType
1025
+ : null) ??
1026
+ this._resolveExprGoType(operand);
1027
+ const baseGoType = operandGoType?.base ?? operandGoType;
1028
+ if (this._isValueArray(baseGoType)) {
1029
+ this._emitArrayStoreInto(baseGoType, r, () => {
1030
+ this.emitExpr(operand);
1031
+ this._emitNilCheck(operandGoType);
1032
+ });
1033
+ return;
1034
+ }
1035
+ if (!isScalarType(baseGoType, this.mod.checker, this.mod)) {
1036
+ return;
1037
+ }
1038
+ const box = this.mod.getBoxType(baseGoType);
1039
+ const ptr = this.mod.getPtrType(baseGoType);
1040
+ const ptrWType = {
1041
+ kind: "ref",
1042
+ nullable: true,
1043
+ typeIndex: ptr.typeIndex,
1044
+ typeName: ptr.name,
1045
+ };
1046
+ const ptrTmp = this.acquireTemp(ptrWType);
1047
+ const valTmp = this.acquireTemp(box.wType);
1048
+ this.emitExpr(operand, ptrWType);
1049
+ this.pushInstruction({ op: "local.set", index: ptrTmp });
1050
+ this._emitNilGuard(ptrTmp, operandGoType);
1051
+ this.emitExpr(r, box.wType);
1052
+ this.pushInstruction({ op: "local.set", index: valTmp });
1053
+
1054
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
1055
+ this.pushInstruction({
1056
+ op: "ref.cast",
1057
+ typeIndex: box.typeIndex,
1058
+ typeName: box.name,
1059
+ });
1060
+ this.pushInstruction({ op: "local.get", index: valTmp });
1061
+ this.pushInstruction({
1062
+ op: "struct.set",
1063
+ typeIndex: box.typeIndex,
1064
+ typeName: box.name,
1065
+ fieldIndex: 0,
1066
+ });
1067
+
1068
+ this.releaseTemp(valTmp, box.wType);
1069
+ this.releaseTemp(ptrTmp, ptrWType);
1070
+ }
1071
+
1072
+ // A plain variable target of interface type receiving a concrete result
1073
+ // (`var x any; x, y = two()`) needs the value converted first.
1074
+ _needsTupleConversion(lhsNodes, tupleTypes) {
1075
+ return lhsNodes.some((l, i) => {
1076
+ if (l.kind !== "Ident" || l.name === "_") return false;
1077
+ const dest = this.resolveLocal(l.name) ?? this.mod.resolveGlobal(l.name);
1078
+ const checker = this.mod.checker;
1079
+ return (
1080
+ isInterfaceType(dest?.goType ?? l._type, checker) &&
1081
+ !isInterfaceType(tupleTypes[i], checker)
1082
+ );
1083
+ });
1084
+ }
1085
+
424
1086
  _emitTupleAssign(lhsNodes, rhsNode, op) {
1087
+ if (
1088
+ op === "=" &&
1089
+ (lhsNodes.some((l) => !this._isPlainTupleTarget(l)) ||
1090
+ this._needsTupleConversion(lhsNodes, rhsNode._type.types))
1091
+ ) {
1092
+ this._emitTupleViaTemps(
1093
+ lhsNodes,
1094
+ rhsNode,
1095
+ (l, t) => this._emitAssignSingle(l, t, "="),
1096
+ true,
1097
+ );
1098
+ return;
1099
+ }
425
1100
  const tupleTypes = rhsNode._type.types;
426
1101
  const dests = [];
427
1102
  for (let i = 0; i < lhsNodes.length; i++) {
@@ -509,12 +1184,83 @@ export class StmtsEmitter {
509
1184
  this.releaseTemp(okTmp, "i32");
510
1185
  }
511
1186
 
1187
+ // Copies the struct or array a map entry holds (a range key or value is a
1188
+ // copy, not the stored value).
1189
+ _emitCopyMapEntry(goType, wType) {
1190
+ const isStruct =
1191
+ isStructType(goType, this.mod.checker, this.mod) &&
1192
+ !isPointerToStruct(goType, this.mod.checker, this.mod);
1193
+ if (isStruct) {
1194
+ const sInfo = this._resolveStructInfo({ _type: goType });
1195
+ if (sInfo) this.emitCloneStruct(sInfo, wType);
1196
+ } else if (
1197
+ goType?.kind !== "pointer" &&
1198
+ isArrayType(goType, this.mod.checker)
1199
+ ) {
1200
+ this.emitCopyArray(goType);
1201
+ }
1202
+ }
1203
+
1204
+ // Pushes a comma-ok result (map value / asserted value). An array or
1205
+ // struct is a value: the stored one must not alias the map entry or boxed
1206
+ // interface.
1207
+ _emitGetTempValue(tempIdx, goType) {
1208
+ this.pushInstruction({ op: "local.get", index: tempIdx });
1209
+ if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker))
1210
+ this.emitCopyArray(goType);
1211
+ else if (
1212
+ isStructType(goType, this.mod.checker, this.mod) &&
1213
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
1214
+ ) {
1215
+ // A struct read out of a map or an interface is a copy.
1216
+ const info =
1217
+ this._resolveStructInfo({ _type: goType }) ??
1218
+ this.mod.getStructType(goType?.name);
1219
+ if (info) this.emitCloneStruct(info, this.toWasmType(goType));
1220
+ }
1221
+ }
1222
+
1223
+ _storeTargetWType(lhsNode) {
1224
+ if (lhsNode.kind !== "Ident") return null;
1225
+ const local = this.resolveLocal(lhsNode.name);
1226
+ if (local)
1227
+ return local.isBoxed
1228
+ ? local.boxInfo?.typeEntry?.fields?.[0]?.type
1229
+ : local.type;
1230
+ return this.mod.resolveGlobal(lhsNode.name)?.type ?? null;
1231
+ }
1232
+
512
1233
  _emitStoreLhsFromTemp(lhsNode, tempIdx, wType, goType, isDefine) {
513
- if (lhsNode.kind !== "Ident" || lhsNode.name === "_") return;
1234
+ if (lhsNode.kind === "Ident" && lhsNode.name === "_") return;
1235
+ const redeclared =
1236
+ isDefine && lhsNode._redecl && this.resolveLocal(lhsNode.name);
1237
+ // `var x any; x, ok = m[k]`: a scalar or struct result is boxed on the
1238
+ // way into the interface-typed variable.
1239
+ const target = isDefine ? null : this._storeTargetWType(lhsNode);
1240
+ const boxesIntoAny = target === "anyref" && wType !== "anyref";
1241
+ if (lhsNode.kind !== "Ident" || redeclared || boxesIntoAny) {
1242
+ // An index, field or dereference target (or a name `:=` redeclares):
1243
+ // assign from a named view of the temporary.
1244
+ const name = `__ok_tmp_${this.tempCounter++}`;
1245
+ this.locals.set(name, {
1246
+ index: tempIdx,
1247
+ type: wType,
1248
+ goType,
1249
+ isParam: false,
1250
+ isBoxed: false,
1251
+ });
1252
+ this._emitAssignSingle(
1253
+ lhsNode,
1254
+ { kind: "Ident", name, _type: goType, _parTmp: true },
1255
+ "=",
1256
+ );
1257
+ this.locals.delete(name);
1258
+ return;
1259
+ }
514
1260
  if (isDefine) {
515
1261
  const idx = this.allocLocal(lhsNode.name, wType, goType);
516
1262
  const local = this.locals.get(lhsNode.name);
517
- this.pushInstruction({ op: "local.get", index: tempIdx });
1263
+ this._emitGetTempValue(tempIdx, goType);
518
1264
  if (local?.isBoxed) {
519
1265
  this.pushInstruction({
520
1266
  op: "struct.new",
@@ -528,19 +1274,19 @@ export class StmtsEmitter {
528
1274
  if (local) {
529
1275
  if (local.isBoxed) {
530
1276
  this.pushInstruction({ op: "local.get", index: local.index });
531
- this.pushInstruction({ op: "local.get", index: tempIdx });
1277
+ this._emitGetTempValue(tempIdx, goType);
532
1278
  this.pushInstruction({
533
1279
  op: "struct.set",
534
1280
  typeIndex: local.boxInfo.typeIndex,
535
1281
  fieldIndex: 0,
536
1282
  });
537
1283
  } else {
538
- this.pushInstruction({ op: "local.get", index: tempIdx });
1284
+ this._emitGetTempValue(tempIdx, goType);
539
1285
  this.pushInstruction({ op: "local.set", index: local.index });
540
1286
  }
541
1287
  } else {
542
1288
  const global = this.mod.resolveGlobal(lhsNode.name);
543
- this.pushInstruction({ op: "local.get", index: tempIdx });
1289
+ this._emitGetTempValue(tempIdx, goType);
544
1290
  this.pushInstruction({ op: "global.set", index: global.index });
545
1291
  }
546
1292
  }
@@ -591,7 +1337,38 @@ export class StmtsEmitter {
591
1337
  return goType.name ?? null;
592
1338
  }
593
1339
 
1340
+ // The struct a type assertion target refers to; an anonymous struct type
1341
+ // has no name to look up.
1342
+ _structInfoOfType(goType) {
1343
+ return (
1344
+ this.mod.getStructType(this._structNameOf(goType)) ??
1345
+ this._resolveStructInfo({ _type: goType })
1346
+ );
1347
+ }
1348
+
1349
+ // A bare type name in a case list or assertion (`case L:`, `case error:`)
1350
+ // is the declared type, not its underlying one: a defined string type is
1351
+ // boxed apart from `string`, and `error` carries a method set.
1352
+ _resolveTestTarget(t) {
1353
+ if ((t?.kind !== "TypeName" && t?.kind !== "Ident") || !t.name) return t;
1354
+ if (t.name === "nil" || t.name === "any" || t.name === "interface{}")
1355
+ return t;
1356
+ return this.mod.checker?.types?.get(t.name) ?? t;
1357
+ }
1358
+
1359
+ // A defined func type, or a defined map type that has methods, lives in
1360
+ // `any` as a box (so `iter.Seq[int]` is not a plain `func(func(int) bool)`).
1361
+ _isMethodNamedRef(goType) {
1362
+ const t = this._resolveTestTarget(goType);
1363
+ if (t?.kind !== "named") return false;
1364
+ return (
1365
+ isFuncType(t, this.mod.checker) ||
1366
+ (t.methods?.size > 0 && isMapType(t, this.mod.checker))
1367
+ );
1368
+ }
1369
+
594
1370
  _emitTypeTest(targetGoType) {
1371
+ targetGoType = this._resolveTestTarget(targetGoType);
595
1372
  if (!targetGoType) {
596
1373
  this.pushInstruction("drop");
597
1374
  this.pushInstruction({ op: "i32.const", value: 1 });
@@ -610,62 +1387,45 @@ export class StmtsEmitter {
610
1387
  }
611
1388
 
612
1389
  if (isNonEmptyInterface(targetGoType, this.mod.checker)) {
613
- const candidates = this.mod.getTypesImplementingInterface(targetGoType);
614
- if (candidates.length === 0) {
615
- this.pushInstruction("drop");
616
- this.pushInstruction({ op: "i32.const", value: 0 });
617
- return;
618
- }
619
- const anyTmp = this.acquireTemp("anyref");
620
- this.pushInstruction({ op: "local.set", index: anyTmp });
621
-
622
- this.pushInstruction({ op: "local.get", index: anyTmp });
623
- this.pushInstruction("ref.is_null");
624
- this.pushInstruction("i32.eqz");
625
-
626
- for (let i = 0; i < candidates.length; i++) {
627
- this.pushInstruction({ op: "local.get", index: anyTmp });
628
- this.pushInstruction({
629
- op: "ref.test",
630
- typeIndex: candidates[i].typeIndex,
631
- });
632
- if (i > 0) {
633
- this.pushInstruction("i32.or");
634
- }
635
- }
636
- this.pushInstruction("i32.and");
637
- this.releaseTemp(anyTmp, "anyref");
1390
+ this._emitInterfaceTypeTest(targetGoType);
638
1391
  return;
639
1392
  }
640
1393
 
641
- if (isStringType(targetGoType, this.mod.checker)) {
1394
+ // A named string type is boxed like any other scalar (see emitExpr); only
1395
+ // a plain `string` lives in `any` as the bare string.
1396
+ if (isBareStringType(targetGoType, this.mod.checker)) {
642
1397
  const isStrIdx = this.mod.getIsStringImportIndex();
643
1398
  this.pushInstruction({ op: "call", funcIndex: isStrIdx });
644
1399
  return;
645
1400
  }
646
1401
 
1402
+ if (this._isMethodNamedRef(targetGoType)) {
1403
+ const box = this.mod.getIfaceBoxType(targetGoType);
1404
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
1405
+ return;
1406
+ }
1407
+
647
1408
  if (isFuncType(targetGoType, this.mod.checker)) {
648
1409
  const closureInfo = this.mod.getClosureType(targetGoType);
649
- this.pushInstruction({
650
- op: "ref.test",
651
- typeIndex: closureInfo.typeIndex,
652
- });
1410
+ this._emitRefTestOrNil(targetGoType, closureInfo.typeIndex);
653
1411
  return;
654
1412
  }
655
1413
 
656
1414
  // `*T` lives in `any` as the bare struct ref; a `T` value as a boxed
657
1415
  // clone (see emitExpr). Fall through to the scalar box for values.
658
1416
  if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
659
- const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
1417
+ const sInfo = this._structInfoOfType(targetGoType);
660
1418
  if (sInfo) {
661
- this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
1419
+ this._emitRefTestOrNil(targetGoType, sInfo.typeIndex);
662
1420
  return;
663
1421
  }
664
1422
  }
665
1423
 
666
1424
  if (isSliceType(targetGoType, this.mod.checker)) {
667
1425
  const elem = this._getSliceElemType(targetGoType);
668
- const sInfo = this.mod.getSliceType(elem);
1426
+ const sInfo = isTaggedSliceType(targetGoType, this.mod.checker)
1427
+ ? this.mod.getTaggedSliceType(targetGoType, elem)
1428
+ : this.mod.getSliceType(elem);
669
1429
  if (sInfo) {
670
1430
  this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
671
1431
  return;
@@ -676,11 +1436,22 @@ export class StmtsEmitter {
676
1436
  const elem = this._getArrayElemType(targetGoType);
677
1437
  const aInfo = this.mod.getArrayType(elem);
678
1438
  if (aInfo) {
679
- this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
1439
+ this._emitArrayTypeTest(targetGoType, aInfo);
680
1440
  return;
681
1441
  }
682
1442
  }
683
1443
 
1444
+ // A map lives in `any` as its bare map struct.
1445
+ if (isMapType(targetGoType, this.mod.checker)) {
1446
+ const { keyType, valType } = getMapKeyValTypes(
1447
+ targetGoType,
1448
+ this.mod.checker,
1449
+ );
1450
+ const mInfo = this.mod.getMapType(keyType, valType);
1451
+ this._emitRefTestOrNil(targetGoType, mInfo.typeIndex);
1452
+ return;
1453
+ }
1454
+
684
1455
  // Scalar types
685
1456
  const box = this.mod.getIfaceBoxType(targetGoType);
686
1457
  if (box) {
@@ -692,37 +1463,139 @@ export class StmtsEmitter {
692
1463
  this.pushInstruction({ op: "i32.const", value: 1 });
693
1464
  }
694
1465
 
695
- _emitTypeCast(targetGoType, targetWType) {
696
- if (isInterfaceType(targetGoType, this.mod.checker)) {
1466
+ // `[3]int` and `[2]int` share one wasm array type, so the test also compares
1467
+ // the length.
1468
+ _emitArrayTypeTest(goType, aInfo) {
1469
+ let t = goType?._assertType ?? goType;
1470
+ for (let i = 0; i < 16 && t && t.kind !== "array"; i++)
1471
+ t = t.underlying ?? this.mod.checker?.types?.get(t.name);
1472
+ const size = typeof t?.size === "number" ? t.size : null;
1473
+ if (size === null) {
1474
+ this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
697
1475
  return;
698
1476
  }
1477
+ const tmp = this.acquireTemp("anyref");
1478
+ this.pushInstruction({ op: "local.tee", index: tmp });
1479
+ this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
1480
+ this.pushInstruction({ op: "if", blockType: "i32" });
1481
+ this.pushInstruction({ op: "local.get", index: tmp });
1482
+ this.pushInstruction({ op: "ref.cast", typeIndex: aInfo.typeIndex });
1483
+ this.pushInstruction("array.len");
1484
+ this.pushInstruction({ op: "i32.const", value: size });
1485
+ this.pushInstruction("i32.eq");
1486
+ this.pushInstruction("else");
1487
+ this.pushInstruction({ op: "i32.const", value: 0 });
1488
+ this.pushInstruction("end");
1489
+ this.releaseTemp(tmp, "anyref");
1490
+ }
699
1491
 
700
- if (isStringType(targetGoType, this.mod.checker)) {
1492
+ // Whether the interface value on the stack holds a type implementing the
1493
+ // non-empty interface: a `ref.test` over every implementing type (a typed
1494
+ // nil `*S` marker included).
1495
+ _emitInterfaceTypeTest(targetGoType) {
1496
+ const candidates = this.mod.getTypesImplementingInterface(targetGoType);
1497
+ if (candidates.length === 0) {
1498
+ this.pushInstruction("drop");
1499
+ this.pushInstruction({ op: "i32.const", value: 0 });
1500
+ return;
1501
+ }
1502
+ const anyTmp = this.acquireTemp("anyref");
1503
+ this.pushInstruction({ op: "local.set", index: anyTmp });
1504
+
1505
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1506
+ this.pushInstruction("ref.is_null");
1507
+ this.pushInstruction("i32.eqz");
1508
+
1509
+ for (let i = 0; i < candidates.length; i++) {
1510
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1511
+ this.pushInstruction({
1512
+ op: "ref.test",
1513
+ typeIndex: candidates[i].typeIndex,
1514
+ });
1515
+ if (candidates[i].nilTypeIndex !== undefined) {
1516
+ this.pushInstruction({ op: "local.get", index: anyTmp });
1517
+ this.pushInstruction({
1518
+ op: "ref.test",
1519
+ typeIndex: candidates[i].nilTypeIndex,
1520
+ });
1521
+ this.pushInstruction("i32.or");
1522
+ }
1523
+ if (i > 0) {
1524
+ this.pushInstruction("i32.or");
1525
+ }
1526
+ }
1527
+ this.pushInstruction("i32.and");
1528
+ this.releaseTemp(anyTmp, "anyref");
1529
+ }
1530
+
1531
+ // `ref.test` for a map, func or `*T`, which also accepts the typed-nil
1532
+ // marker of the same Go type (see getNilBoxType).
1533
+ _emitRefTestOrNil(goType, typeIndex) {
1534
+ const nilBox = this.mod.getNilBoxType(goType);
1535
+ const tmp = this.acquireTemp("anyref");
1536
+ this.pushInstruction({ op: "local.set", index: tmp });
1537
+ this.pushInstruction({ op: "local.get", index: tmp });
1538
+ this.pushInstruction({ op: "ref.test", typeIndex });
1539
+ this.pushInstruction({ op: "local.get", index: tmp });
1540
+ this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
1541
+ this.pushInstruction("i32.or");
1542
+ this.releaseTemp(tmp, "anyref");
1543
+ }
1544
+
1545
+ // `ref.cast_null` to `typeIndex`, turning the typed-nil marker of `goType`
1546
+ // back into a nil value.
1547
+ _emitRefCastOrNil(goType, typeIndex) {
1548
+ const nilBox = this.mod.getNilBoxType(goType);
1549
+ const tmp = this.acquireTemp("anyref");
1550
+ this.pushInstruction({ op: "local.set", index: tmp });
1551
+ this.pushInstruction({ op: "local.get", index: tmp });
1552
+ this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
1553
+ this.pushInstruction({
1554
+ op: "if",
1555
+ blockType: { kind: "ref", nullable: true, typeIndex },
1556
+ });
1557
+ this.pushInstruction({ op: "ref.null", heapType: typeIndex });
1558
+ this.pushInstruction("else");
1559
+ this.pushInstruction({ op: "local.get", index: tmp });
1560
+ this.pushInstruction({ op: "ref.cast_null", typeIndex });
1561
+ this.pushInstruction("end");
1562
+ this.releaseTemp(tmp, "anyref");
1563
+ }
1564
+
1565
+ // An interface target keeps the value as is; a boxed defined func or map
1566
+ // type is unboxed. True when the cast is done.
1567
+ _emitCastNoRepack(targetGoType) {
1568
+ if (isInterfaceType(targetGoType, this.mod.checker)) return true;
1569
+ if (!this._isMethodNamedRef(targetGoType)) return false;
1570
+ this._emitUnbox(
1571
+ this.mod.getIfaceBoxType(this._resolveTestTarget(targetGoType)),
1572
+ );
1573
+ return true;
1574
+ }
1575
+
1576
+ _emitTypeCast(targetGoType, targetWType) {
1577
+ if (this._emitCastNoRepack(targetGoType)) return;
1578
+
1579
+ if (isBareStringType(targetGoType, this.mod.checker)) {
701
1580
  this.pushInstruction("extern.convert_any");
702
1581
  return;
703
1582
  }
704
1583
 
705
1584
  if (isFuncType(targetGoType, this.mod.checker)) {
706
1585
  const closureInfo = this.mod.getClosureType(targetGoType);
707
- this.pushInstruction({
708
- op: "ref.cast_null",
709
- typeIndex: closureInfo.typeIndex,
710
- });
1586
+ this._emitRefCastOrNil(targetGoType, closureInfo.typeIndex);
711
1587
  return;
712
1588
  }
713
1589
 
714
1590
  if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
715
- const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
1591
+ const sInfo = this._structInfoOfType(targetGoType);
716
1592
  if (sInfo) {
717
- this.pushInstruction({
718
- op: "ref.cast_null",
719
- typeIndex: sInfo.typeIndex,
720
- });
1593
+ this._emitRefCastOrNil(targetGoType, sInfo.typeIndex);
721
1594
  return;
722
1595
  }
723
1596
  }
724
1597
  if (isStructType(targetGoType, this.mod.checker, this.mod)) {
725
- const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
1598
+ const sInfo = this._structInfoOfType(targetGoType);
726
1599
  const box = this.mod.getIfaceBoxType(targetGoType);
727
1600
  if (sInfo && box) {
728
1601
  this._emitUnbox(box);
@@ -755,6 +1628,16 @@ export class StmtsEmitter {
755
1628
  }
756
1629
  }
757
1630
 
1631
+ if (isMapType(targetGoType, this.mod.checker)) {
1632
+ const { keyType, valType } = getMapKeyValTypes(
1633
+ targetGoType,
1634
+ this.mod.checker,
1635
+ );
1636
+ const mInfo = this.mod.getMapType(keyType, valType);
1637
+ this._emitRefCastOrNil(targetGoType, mInfo.typeIndex);
1638
+ return;
1639
+ }
1640
+
758
1641
  // Scalar types
759
1642
  const box = this.mod.getIfaceBoxType(targetGoType);
760
1643
  if (box) {
@@ -780,6 +1663,44 @@ export class StmtsEmitter {
780
1663
  const l = Array.isArray(lhs) ? lhs[0] : lhs;
781
1664
  const r = Array.isArray(rhs) ? rhs[0] : rhs;
782
1665
 
1666
+ if (isComplex(l._type)) {
1667
+ const synthBinary = {
1668
+ kind: "BinaryExpr",
1669
+ op: baseOp,
1670
+ left: l,
1671
+ right: r,
1672
+ _type: l._type,
1673
+ };
1674
+ const is64 = isComplex64(l._type);
1675
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
1676
+ const cWType = {
1677
+ kind: "ref",
1678
+ nullable: true,
1679
+ typeIndex: info.typeIndex,
1680
+ typeName: info.name,
1681
+ };
1682
+ if (l.kind === "Ident") {
1683
+ const localInfo = this.resolveLocal(l.name);
1684
+ const globalInfo = !localInfo ? this.mod.resolveGlobal(l.name) : null;
1685
+ if (localInfo?.isBoxed) {
1686
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
1687
+ this.emitComplexBinary(synthBinary, cWType);
1688
+ this.pushInstruction({
1689
+ op: "struct.set",
1690
+ typeIndex: localInfo.boxInfo.typeIndex,
1691
+ fieldIndex: 0,
1692
+ });
1693
+ } else if (localInfo) {
1694
+ this.emitComplexBinary(synthBinary, cWType);
1695
+ this.pushInstruction({ op: "local.set", index: localInfo.index });
1696
+ } else if (globalInfo) {
1697
+ this.emitComplexBinary(synthBinary, cWType);
1698
+ this.pushInstruction({ op: "global.set", index: globalInfo.index });
1699
+ }
1700
+ return;
1701
+ }
1702
+ }
1703
+
783
1704
  if (l.kind === "SelectorExpr") {
784
1705
  const structInfo = this._resolveStructInfo(l.expr);
785
1706
  if (!structInfo) {
@@ -796,9 +1717,10 @@ export class StmtsEmitter {
796
1717
  const lastStep = path[path.length - 1];
797
1718
  const targetType = lastStep.field.wType;
798
1719
  const baseWType = this.toWasmType(l.expr._type);
1720
+ const holderWType = this._fieldHolderWType(path, baseWType);
799
1721
 
800
- const baseTmp = this.acquireTemp(baseWType);
801
- this.emitExpr(l.expr, baseWType);
1722
+ const baseTmp = this.acquireTemp(holderWType);
1723
+ this.emitSelectorBase(l.expr, baseWType);
802
1724
  for (let i = 0; i < path.length - 1; i++) {
803
1725
  const step = path[i];
804
1726
  this.pushInstruction({
@@ -825,7 +1747,7 @@ export class StmtsEmitter {
825
1747
  typeIndex: lastStep.structInfo.typeIndex,
826
1748
  fieldIndex: lastStep.fieldIndex,
827
1749
  });
828
- this.releaseTemp(baseTmp, baseWType);
1750
+ this.releaseTemp(baseTmp, holderWType);
829
1751
  return;
830
1752
  }
831
1753
 
@@ -882,11 +1804,9 @@ export class StmtsEmitter {
882
1804
  baseType?.kind === "pointer" &&
883
1805
  isArrayType(baseType.base, this.mod.checker);
884
1806
 
885
- const elemGoType = isSlice
886
- ? baseType.elem
887
- : isPtrToArr
888
- ? baseType.base.elem
889
- : baseType.elem;
1807
+ const elemGoType = this._getArrayElemType(
1808
+ isPtrToArr ? baseType.base : baseType,
1809
+ );
890
1810
  const arrInfo = this.mod.getArrayType(elemGoType);
891
1811
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
892
1812
  const targetType = arrInfo.elemWType;
@@ -925,32 +1845,57 @@ export class StmtsEmitter {
925
1845
 
926
1846
  if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
927
1847
  const operand = l.operand ?? l.expr;
928
- const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
929
- if (box) {
930
- const baseWType = {
1848
+ const operandGoType =
1849
+ operand._type ??
1850
+ (operand.kind === "Ident"
1851
+ ? this.resolveLocal(operand.name)?.goType
1852
+ : null) ??
1853
+ this._resolveExprGoType(operand);
1854
+ const baseGoType = operandGoType?.base ?? operandGoType;
1855
+ if (!isScalarType(baseGoType, this.mod.checker, this.mod)) return;
1856
+ const box = this.mod.getBoxType(baseGoType);
1857
+ const ptr = this.mod.getPtrType(baseGoType);
1858
+ if (box && ptr) {
1859
+ const ptrWType = {
931
1860
  kind: "ref",
932
1861
  nullable: true,
933
- typeIndex: box.typeIndex,
1862
+ typeIndex: ptr.typeIndex,
1863
+ typeName: ptr.name,
934
1864
  };
935
- const boxTmp = this.acquireTemp(baseWType);
936
- this.emitExpr(operand, baseWType);
937
- this.pushInstruction({ op: "local.set", index: boxTmp });
938
- this.pushInstruction({ op: "local.get", index: boxTmp });
939
- this.pushInstruction({ op: "local.get", index: boxTmp });
1865
+ const targetType = box.wType;
1866
+ const ptrTmp = this.acquireTemp(ptrWType);
1867
+ this.emitExpr(operand, ptrWType);
1868
+ this.pushInstruction({ op: "local.set", index: ptrTmp });
1869
+ this._emitNilGuard(ptrTmp, operandGoType);
1870
+
1871
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
1872
+ this.pushInstruction({
1873
+ op: "ref.cast",
1874
+ typeIndex: box.typeIndex,
1875
+ typeName: box.name,
1876
+ });
1877
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
1878
+ this.pushInstruction({
1879
+ op: "ref.cast",
1880
+ typeIndex: box.typeIndex,
1881
+ typeName: box.name,
1882
+ });
940
1883
  this.pushInstruction({
941
1884
  op: "struct.get",
942
1885
  typeIndex: box.typeIndex,
1886
+ typeName: box.name,
943
1887
  fieldIndex: 0,
944
1888
  });
945
- const targetType = box.wType;
946
1889
  this.emitExpr(r, targetType);
947
- this.emitBinaryOp(baseOp, targetType, l._type ?? operand._type?.base);
1890
+ this.emitBinaryOp(baseOp, targetType, l._type ?? baseGoType);
948
1891
  this.pushInstruction({
949
1892
  op: "struct.set",
950
1893
  typeIndex: box.typeIndex,
1894
+ typeName: box.name,
951
1895
  fieldIndex: 0,
952
1896
  });
953
- this.releaseTemp(boxTmp, baseWType);
1897
+
1898
+ this.releaseTemp(ptrTmp, ptrWType);
954
1899
  return;
955
1900
  }
956
1901
  }
@@ -1038,36 +1983,57 @@ export class StmtsEmitter {
1038
1983
  expr.kind === "StarExpr"
1039
1984
  ) {
1040
1985
  const operand = expr.operand ?? expr.expr;
1041
- const structInfo = this._resolveStructInfo(operand);
1986
+ const structInfo = this._derefStructInfo(operand);
1042
1987
  if (!structInfo) {
1043
- const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
1044
- if (box) {
1045
- const baseWType = {
1988
+ const operandGoType =
1989
+ operand._type ??
1990
+ (operand.kind === "Ident"
1991
+ ? this.resolveLocal(operand.name)?.goType
1992
+ : null) ??
1993
+ this._resolveExprGoType(operand);
1994
+ const baseGoType = operandGoType?.base ?? operandGoType;
1995
+ if (!isScalarType(baseGoType, this.mod.checker, this.mod)) return;
1996
+ const box = this.mod.getBoxType(baseGoType);
1997
+ const ptr = this.mod.getPtrType(baseGoType);
1998
+ if (box && ptr) {
1999
+ const ptrWType = {
1046
2000
  kind: "ref",
1047
2001
  nullable: true,
1048
- typeIndex: box.typeIndex,
2002
+ typeIndex: ptr.typeIndex,
2003
+ typeName: ptr.name,
1049
2004
  };
1050
- const boxTmp = this.acquireTemp(baseWType);
1051
- this.emitExpr(operand, baseWType);
1052
- this.pushInstruction({ op: "local.set", index: boxTmp });
1053
- this.pushInstruction({ op: "local.get", index: boxTmp });
1054
- this.pushInstruction({ op: "local.get", index: boxTmp });
2005
+ const ptrTmp = this.acquireTemp(ptrWType);
2006
+ this.emitExpr(operand, ptrWType);
2007
+ this.pushInstruction({ op: "local.set", index: ptrTmp });
2008
+ this._emitNilGuard(ptrTmp, operandGoType);
2009
+
2010
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
2011
+ this.pushInstruction({
2012
+ op: "ref.cast",
2013
+ typeIndex: box.typeIndex,
2014
+ typeName: box.name,
2015
+ });
2016
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
2017
+ this.pushInstruction({
2018
+ op: "ref.cast",
2019
+ typeIndex: box.typeIndex,
2020
+ typeName: box.name,
2021
+ });
1055
2022
  this.pushInstruction({
1056
2023
  op: "struct.get",
1057
2024
  typeIndex: box.typeIndex,
2025
+ typeName: box.name,
1058
2026
  fieldIndex: 0,
1059
2027
  });
1060
- this._emitIncDecStep(
1061
- op,
1062
- box.wType,
1063
- operand._type?.base ?? operand._type,
1064
- );
2028
+ this._emitIncDecStep(op, box.wType, baseGoType);
1065
2029
  this.pushInstruction({
1066
2030
  op: "struct.set",
1067
2031
  typeIndex: box.typeIndex,
2032
+ typeName: box.name,
1068
2033
  fieldIndex: 0,
1069
2034
  });
1070
- this.releaseTemp(boxTmp, baseWType);
2035
+
2036
+ this.releaseTemp(ptrTmp, ptrWType);
1071
2037
  return;
1072
2038
  }
1073
2039
  }
@@ -1081,9 +2047,10 @@ export class StmtsEmitter {
1081
2047
  const lastStep = path[path.length - 1];
1082
2048
  const targetType = lastStep.field.wType;
1083
2049
  const baseWType = this.toWasmType(expr.expr._type);
2050
+ const holderWType = this._fieldHolderWType(path, baseWType);
1084
2051
 
1085
- const baseTmp = this.acquireTemp(baseWType);
1086
- this.emitExpr(expr.expr, baseWType);
2052
+ const baseTmp = this.acquireTemp(holderWType);
2053
+ this.emitSelectorBase(expr.expr, baseWType);
1087
2054
  for (let i = 0; i < path.length - 1; i++) {
1088
2055
  const step = path[i];
1089
2056
  this.pushInstruction({
@@ -1170,11 +2137,9 @@ export class StmtsEmitter {
1170
2137
  baseType?.kind === "pointer" &&
1171
2138
  isArrayType(baseType.base, this.mod.checker);
1172
2139
 
1173
- const elemGoType = isSlice
1174
- ? baseType.elem
1175
- : isPtrToArr
1176
- ? baseType.base.elem
1177
- : baseType.elem;
2140
+ const elemGoType = this._getArrayElemType(
2141
+ isPtrToArr ? baseType.base : baseType,
2142
+ );
1178
2143
  const arrInfo = this.mod.getArrayType(elemGoType);
1179
2144
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
1180
2145
  const targetType = arrInfo.elemWType;
@@ -1325,7 +2290,7 @@ export class StmtsEmitter {
1325
2290
  !this.capturedNames?.includes(lhs[0].name) &&
1326
2291
  !this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
1327
2292
  const wType = isNonEscaping ? "i32" : "i64";
1328
- const lIdx = this.allocLocal(lhs[0].name, wType, {
2293
+ const lIdx = this._allocRangeVar(lhs[0].name, wType, {
1329
2294
  kind: "basic",
1330
2295
  name: "int",
1331
2296
  });
@@ -1378,6 +2343,7 @@ export class StmtsEmitter {
1378
2343
  // Emit body
1379
2344
  const bodyBlock = stmt.body ?? stmt.block;
1380
2345
  if (bodyBlock) {
2346
+ this._emitRangeBoxes();
1381
2347
  this.emitBlock(bodyBlock);
1382
2348
  }
1383
2349
 
@@ -1425,7 +2391,7 @@ export class StmtsEmitter {
1425
2391
  let idxLocalInfo = null;
1426
2392
  if (lhs[0] && lhs[0].name !== "_") {
1427
2393
  if (!isAssign) {
1428
- const lIdx = this.allocLocal(lhs[0].name, "i64", {
2394
+ const lIdx = this._allocRangeVar(lhs[0].name, "i64", {
1429
2395
  kind: "basic",
1430
2396
  name: "int",
1431
2397
  });
@@ -1440,7 +2406,7 @@ export class StmtsEmitter {
1440
2406
  let valLocalInfo = null;
1441
2407
  if (lhs[1] && lhs[1].name !== "_") {
1442
2408
  if (!isAssign) {
1443
- const lIdx = this.allocLocal(lhs[1].name, "i32", {
2409
+ const lIdx = this._allocRangeVar(lhs[1].name, "i32", {
1444
2410
  kind: "basic",
1445
2411
  name: "rune",
1446
2412
  });
@@ -1519,6 +2485,7 @@ export class StmtsEmitter {
1519
2485
  // Body
1520
2486
  const bodyBlock = stmt.body ?? stmt.block;
1521
2487
  if (bodyBlock) {
2488
+ this._emitRangeBoxes();
1522
2489
  this.emitBlock(bodyBlock);
1523
2490
  }
1524
2491
 
@@ -1596,7 +2563,11 @@ export class StmtsEmitter {
1596
2563
  let keyLocalInfo = null;
1597
2564
  if (lhs[0] && lhs[0].name !== "_") {
1598
2565
  if (!isAssign) {
1599
- const lIdx = this.allocLocal(lhs[0].name, mapInfo.keyWType, keyType);
2566
+ const lIdx = this._allocRangeVar(
2567
+ lhs[0].name,
2568
+ mapInfo.keyWType,
2569
+ keyType,
2570
+ );
1600
2571
  keyLocalInfo = { index: lIdx, type: mapInfo.keyWType };
1601
2572
  } else {
1602
2573
  keyLocalInfo =
@@ -1608,7 +2579,11 @@ export class StmtsEmitter {
1608
2579
  let valLocalInfo = null;
1609
2580
  if (lhs[1] && lhs[1].name !== "_") {
1610
2581
  if (!isAssign) {
1611
- const lIdx = this.allocLocal(lhs[1].name, mapInfo.valWType, valType);
2582
+ const lIdx = this._allocRangeVar(
2583
+ lhs[1].name,
2584
+ mapInfo.valWType,
2585
+ valType,
2586
+ );
1612
2587
  valLocalInfo = { index: lIdx, type: mapInfo.valWType };
1613
2588
  } else {
1614
2589
  valLocalInfo =
@@ -1678,6 +2653,7 @@ export class StmtsEmitter {
1678
2653
  typeIndex: mapInfo.entryTypeIndex,
1679
2654
  fieldIndex: 0,
1680
2655
  });
2656
+ this._emitCopyMapEntry(keyType, mapInfo.keyWType);
1681
2657
  if (this.resolveLocal(lhs[0].name)) {
1682
2658
  this.pushInstruction({
1683
2659
  op: "local.set",
@@ -1699,15 +2675,7 @@ export class StmtsEmitter {
1699
2675
  typeIndex: mapInfo.entryTypeIndex,
1700
2676
  fieldIndex: 1,
1701
2677
  });
1702
- const isValStruct =
1703
- isStructType(valType, this.mod.checker, this.mod) &&
1704
- !isPointerToStruct(valType, this.mod.checker, this.mod);
1705
- if (isValStruct) {
1706
- const sInfo = this._resolveStructInfo({ _type: valType });
1707
- if (sInfo) {
1708
- this.emitCloneStruct(sInfo, mapInfo.valWType);
1709
- }
1710
- }
2678
+ this._emitCopyMapEntry(valType, mapInfo.valWType);
1711
2679
  if (this.resolveLocal(lhs[1].name)) {
1712
2680
  this.pushInstruction({
1713
2681
  op: "local.set",
@@ -1724,6 +2692,7 @@ export class StmtsEmitter {
1724
2692
  // Body
1725
2693
  const bodyBlock = stmt.body ?? stmt.block;
1726
2694
  if (bodyBlock) {
2695
+ this._emitRangeBoxes();
1727
2696
  this.emitBlock(bodyBlock);
1728
2697
  }
1729
2698
 
@@ -1768,11 +2737,9 @@ export class StmtsEmitter {
1768
2737
  iterType?.kind === "pointer" &&
1769
2738
  isArrayType(iterType.base, this.mod.checker);
1770
2739
 
1771
- const elemGoType = isSlice
1772
- ? iterType.elem
1773
- : isPtrToArr
1774
- ? iterType.base.elem
1775
- : iterType.elem;
2740
+ const elemGoType = this._getArrayElemType(
2741
+ isPtrToArr ? iterType.base : iterType,
2742
+ );
1776
2743
  const arrInfo = this.mod.getArrayType(elemGoType);
1777
2744
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
1778
2745
 
@@ -1808,8 +2775,21 @@ export class StmtsEmitter {
1808
2775
  });
1809
2776
  }
1810
2777
  this.releaseTemp(sliceTmp, sliceWType);
2778
+ } else if (!hasVal && !hasCallOrReceive(iterExpr)) {
2779
+ // Ranging over an array for its indexes only does not evaluate it
2780
+ // (`range p` on a nil array pointer is valid): the length is the
2781
+ // type's.
2782
+ this.pushInstruction({
2783
+ op: "i32.const",
2784
+ value: this._getArraySize(isPtrToArr ? iterType.base : iterType),
2785
+ });
2786
+ this.pushInstruction({ op: "local.set", index: lenTmp });
1811
2787
  } else {
1812
2788
  this.emitExpr(iterExpr, arrWType);
2789
+ // Go ranges over a copy of an array (the range expression is
2790
+ // evaluated once), so body writes to the array do not reach `v`.
2791
+ if (hasVal && !isPtrToArr)
2792
+ this._emitCopyIfValue(iterExpr, iterType, arrWType);
1813
2793
  if (hasVal) {
1814
2794
  this.pushInstruction({ op: "local.set", index: arrTmp });
1815
2795
  this.pushInstruction({ op: "i32.const", value: 0 });
@@ -1833,7 +2813,7 @@ export class StmtsEmitter {
1833
2813
  !this.capturedNames?.includes(lhs[0].name) &&
1834
2814
  !this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
1835
2815
  const wType = isNonEscaping ? "i32" : "i64";
1836
- const lIdx = this.allocLocal(lhs[0].name, wType, {
2816
+ const lIdx = this._allocRangeVar(lhs[0].name, wType, {
1837
2817
  kind: "basic",
1838
2818
  name: "int",
1839
2819
  });
@@ -1847,7 +2827,7 @@ export class StmtsEmitter {
1847
2827
  let valLocalInfo = null;
1848
2828
  if (lhs[1] && lhs[1].name !== "_") {
1849
2829
  if (!isAssign) {
1850
- const lIdx = this.allocLocal(
2830
+ const lIdx = this._allocRangeVar(
1851
2831
  lhs[1].name,
1852
2832
  arrInfo.elemWType,
1853
2833
  elemGoType,
@@ -1912,6 +2892,11 @@ export class StmtsEmitter {
1912
2892
  if (sInfo) {
1913
2893
  this.emitCloneStruct(sInfo, arrInfo.elemWType);
1914
2894
  }
2895
+ } else if (
2896
+ elemGoType?.kind !== "pointer" &&
2897
+ isArrayType(elemGoType, this.mod.checker)
2898
+ ) {
2899
+ this.emitCopyArray(elemGoType);
1915
2900
  }
1916
2901
  if (this.resolveLocal(lhs[1].name)) {
1917
2902
  this.pushInstruction({ op: "local.set", index: valLocalInfo.index });
@@ -1926,6 +2911,7 @@ export class StmtsEmitter {
1926
2911
  // Body
1927
2912
  const bodyBlock = stmt.body ?? stmt.block;
1928
2913
  if (bodyBlock) {
2914
+ this._emitRangeBoxes();
1929
2915
  this.emitBlock(bodyBlock);
1930
2916
  }
1931
2917
 
@@ -1955,7 +2941,102 @@ export class StmtsEmitter {
1955
2941
  restoreShadowed();
1956
2942
  }
1957
2943
 
2944
+ _initIterState(stmt) {
2945
+ if (!stmt._iterStateName) return;
2946
+ let stateLoc = this.resolveLocal(stmt._iterStateName);
2947
+ if (!stateLoc) {
2948
+ this.allocLocal(stmt._iterStateName, "i64", {
2949
+ kind: "basic",
2950
+ name: "int",
2951
+ });
2952
+ stateLoc = this.resolveLocal(stmt._iterStateName);
2953
+ }
2954
+ if (stateLoc?.isBoxed) {
2955
+ this.pushInstruction({ op: "i64.const", value: 0n });
2956
+ this.pushInstruction({
2957
+ op: "struct.new",
2958
+ typeIndex: stateLoc.boxInfo.typeIndex,
2959
+ });
2960
+ this.pushInstruction({ op: "local.set", index: stateLoc.index });
2961
+ }
2962
+ }
2963
+
2964
+ _emitIteratorRange(stmt, rangeExpr) {
2965
+ const funcLit = getOrCreateIteratorFuncLit(stmt);
2966
+ const iterExpr = rangeExpr.expr;
2967
+ const iterType = this._resolveExprGoType(iterExpr) ?? iterExpr._type;
2968
+ const iterInfo = this.mod.getClosureType(iterType);
2969
+ const iterWType = {
2970
+ kind: "ref",
2971
+ nullable: true,
2972
+ typeIndex: iterInfo.typeIndex,
2973
+ };
2974
+
2975
+ this._initIterState(stmt);
2976
+
2977
+ const iterTmp = this.acquireTemp(iterWType);
2978
+ this.emitExpr(iterExpr, iterWType);
2979
+ this.pushInstruction({ op: "local.set", index: iterTmp });
2980
+
2981
+ const yieldClosureInfo = this.mod.getClosureType(funcLit._type ?? funcLit);
2982
+ const yieldClosureWType = {
2983
+ kind: "ref",
2984
+ nullable: true,
2985
+ typeIndex: yieldClosureInfo.typeIndex,
2986
+ };
2987
+ const yieldTmp = this.acquireTemp(yieldClosureWType);
2988
+ this.emitFuncLit(funcLit);
2989
+ this.pushInstruction({ op: "local.set", index: yieldTmp });
2990
+
2991
+ // Outer block for break
2992
+ this.pushInstruction({ op: "block", blockType: "void" });
2993
+ this.pushControl("break", stmt.label);
2994
+
2995
+ // Push env of iterTmp (param 0)
2996
+ this.pushInstruction({ op: "local.get", index: iterTmp });
2997
+ this.pushInstruction({
2998
+ op: "struct.get",
2999
+ typeIndex: iterInfo.typeIndex,
3000
+ fieldIndex: 1,
3001
+ });
3002
+
3003
+ // Push yield callback closure (param 1)
3004
+ this.pushInstruction({ op: "local.get", index: yieldTmp });
3005
+
3006
+ // Push funcref of iterTmp
3007
+ this.pushInstruction({ op: "local.get", index: iterTmp });
3008
+ this.pushInstruction({
3009
+ op: "struct.get",
3010
+ typeIndex: iterInfo.typeIndex,
3011
+ fieldIndex: 0,
3012
+ });
3013
+
3014
+ // Call iterator via call_ref
3015
+ this.pushInstruction({
3016
+ op: "call_ref",
3017
+ typeIndex: iterInfo.funcTypeIndex,
3018
+ });
3019
+
3020
+ // If iterator returned any values, drop them
3021
+ for (let i = 0; i < (iterInfo.returnWTypes?.length ?? 0); i++) {
3022
+ this.pushInstruction("drop");
3023
+ }
3024
+
3025
+ this.pushInstruction("end");
3026
+
3027
+ this.releaseTemp(yieldTmp, yieldClosureWType);
3028
+ this.releaseTemp(iterTmp, iterWType);
3029
+
3030
+ this._emitIteratorUnwind(stmt);
3031
+ }
3032
+
1958
3033
  emitForStmt(stmt) {
3034
+ if (isIteratorRangeFor(stmt)) {
3035
+ const rangeExpr =
3036
+ stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init.rhs[0];
3037
+ this._emitIteratorRange(stmt, rangeExpr);
3038
+ return;
3039
+ }
1959
3040
  if (isRangeFor(stmt) || stmt.cond?.kind === "RangeExpr") {
1960
3041
  this.emitRangeForStmt(stmt);
1961
3042
  return;
@@ -2000,14 +3081,23 @@ export class StmtsEmitter {
2000
3081
  this.pushInstruction({ op: "block", blockType: "void" });
2001
3082
  this.pushControl("continue", stmt.label);
2002
3083
 
3084
+ // The body is its own scope: `i := i` must not rebind the loop
3085
+ // variable that the post statement increments.
2003
3086
  const bodyBlock = stmt.body ?? stmt.block;
2004
3087
  if (bodyBlock) {
2005
- this.emitBlock(bodyBlock);
3088
+ const outer = new Map(this.locals);
3089
+ try {
3090
+ this.emitBlock(bodyBlock);
3091
+ } finally {
3092
+ this.locals = outer;
3093
+ }
2006
3094
  }
2007
3095
 
2008
3096
  // End inner continue block
2009
3097
  this.pushInstruction("end");
2010
3098
 
3099
+ this._emitPerIterationBoxes(stmt);
3100
+
2011
3101
  if (iv) {
2012
3102
  this.pushInstruction({ op: "local.get", index: ivLocalInfo.index });
2013
3103
  this.pushInstruction({ op: "i32.const", value: iv.step });
@@ -2035,6 +3125,46 @@ export class StmtsEmitter {
2035
3125
  }
2036
3126
  }
2037
3127
 
3128
+ // Go 1.22 per-iteration loop variables. For each three-clause `for`
3129
+ // variable that a closure in this loop captures, give the next iteration a
3130
+ // fresh copy before the post statement: closures created in this
3131
+ // iteration keep their own variable, the post statement (and the next
3132
+ // iteration) work on the new one, which starts from the current value.
3133
+ // A boxed scalar gets a new box; a value struct or array (a ref) is also
3134
+ // cloned. Uncaptured variables stay plain locals with no allocation.
3135
+ _emitPerIterationBoxes(stmt) {
3136
+ for (const name of loopVarsCapturedInLoop(stmt)) {
3137
+ const info = this.locals.get(name);
3138
+ if (!info) continue;
3139
+ const goType = info.goType;
3140
+ const cloneValue = (wType) => {
3141
+ if (goType?.kind === "pointer") return;
3142
+ if (isArrayType(goType, this.mod.checker)) {
3143
+ this.emitCopyArray(goType);
3144
+ } else if (isStructType(goType, this.mod.checker, this.mod)) {
3145
+ const sInfo = this._resolveStructInfo({ _type: goType });
3146
+ if (sInfo) this.emitCloneStruct(sInfo, wType);
3147
+ }
3148
+ };
3149
+ this.pushInstruction({ op: "local.get", index: info.index });
3150
+ if (info.isBoxed) {
3151
+ this.pushInstruction({
3152
+ op: "struct.get",
3153
+ typeIndex: info.boxInfo.typeIndex,
3154
+ fieldIndex: 0,
3155
+ });
3156
+ cloneValue(info.boxInfo.wType);
3157
+ this.pushInstruction({
3158
+ op: "struct.new",
3159
+ typeIndex: info.boxInfo.typeIndex,
3160
+ });
3161
+ } else {
3162
+ cloneValue(info.type);
3163
+ }
3164
+ this.pushInstruction({ op: "local.set", index: info.index });
3165
+ }
3166
+ }
3167
+
2038
3168
  _detectInductionVar(stmt) {
2039
3169
  if (stmt.init?.kind !== "DefineStmt") return null;
2040
3170
  const lhs = stmt.init.lhs ?? [];
@@ -2193,7 +3323,102 @@ export class StmtsEmitter {
2193
3323
  return false;
2194
3324
  }
2195
3325
 
3326
+ // Each case clause is its own scope: a name declared in one clause must not
3327
+ // stay visible in the clauses after it.
3328
+ _emitClauseBody(stmts) {
3329
+ const outer = new Map(this.locals);
3330
+ try {
3331
+ for (const s of stmts ?? []) this.emitStmt(s);
3332
+ } finally {
3333
+ this.locals = outer;
3334
+ }
3335
+ }
3336
+
3337
+ // A switch with `fallthrough` dispatches to one block per clause, in source
3338
+ // order, so a clause that falls through simply runs into the next body.
3339
+ _emitFallthroughSwitch(stmt) {
3340
+ const getCaseVals = (c) => c.list ?? c.values ?? [];
3341
+ const cases = stmt.cases ?? [];
3342
+ const n = cases.length;
3343
+ const isFall = (s) =>
3344
+ s?.kind === "BranchStmt" && s.keyword === "fallthrough";
3345
+
3346
+ this.pushInstruction({ op: "block", blockType: "void" });
3347
+ this.pushControl("break", stmt.label);
3348
+ if (stmt.init) this.emitStmt(stmt.init);
3349
+
3350
+ let tagGoType = null;
3351
+ let tagWType = null;
3352
+ let tagTmp = -1;
3353
+ if (stmt.tag) {
3354
+ tagGoType = stmt.tag._type ?? this._resolveExprGoType(stmt.tag);
3355
+ tagWType = this.toWasmType(tagGoType);
3356
+ tagTmp = this.acquireTemp(tagWType);
3357
+ this.emitExpr(stmt.tag, tagWType);
3358
+ this.pushInstruction({ op: "local.set", index: tagTmp });
3359
+ }
3360
+ for (let i = 0; i < n; i++) {
3361
+ this.pushInstruction({ op: "block", blockType: "void" });
3362
+ }
3363
+ let defaultIdx = -1;
3364
+ for (let i = 0; i < n; i++) {
3365
+ const vals = getCaseVals(cases[i]);
3366
+ if (vals.length === 0) {
3367
+ defaultIdx = i;
3368
+ continue;
3369
+ }
3370
+ this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
3371
+ // The innermost block ends the first clause, so clause i is depth i.
3372
+ this.pushInstruction({ op: "br_if", depth: i });
3373
+ }
3374
+ this.pushInstruction({ op: "br", depth: defaultIdx >= 0 ? defaultIdx : n });
3375
+ if (stmt.tag) this.releaseTemp(tagTmp, tagWType);
3376
+
3377
+ for (let i = 0; i < n; i++) {
3378
+ this.pushInstruction("end");
3379
+ const body = cases[i].stmts ?? [];
3380
+ const falls = isFall(body[body.length - 1]);
3381
+ this._emitClauseBody(falls ? body.slice(0, -1) : body);
3382
+ if (!falls && i < n - 1) {
3383
+ this.pushInstruction({ op: "br", depth: n - 1 - i });
3384
+ }
3385
+ }
3386
+ this.pushInstruction("end");
3387
+ }
3388
+
3389
+ // Leaves an i32 on the stack: whether any expression of a case list matches.
3390
+ // Later expressions are not evaluated once one matches, as in Go.
3391
+ _emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType) {
3392
+ for (let j = 0; j < vals.length; j++) {
3393
+ if (stmt.tag) {
3394
+ this.pushInstruction({ op: "local.get", index: tagTmp });
3395
+ this.emitExpr(vals[j], tagWType);
3396
+ this._emitElemEq(tagGoType, tagWType);
3397
+ } else {
3398
+ this.emitExpr(vals[j], "i32");
3399
+ }
3400
+ if (j < vals.length - 1) {
3401
+ this.pushInstruction({ op: "if", blockType: "i32" });
3402
+ this.pushInstruction({ op: "i32.const", value: 1 });
3403
+ this.pushInstruction("else");
3404
+ }
3405
+ }
3406
+ for (let j = 0; j < vals.length - 1; j++) this.pushInstruction("end");
3407
+ }
3408
+
2196
3409
  emitSwitchStmt(stmt) {
3410
+ if (
3411
+ (stmt.cases ?? []).some((c) => {
3412
+ const b = c.stmts ?? [];
3413
+ return (
3414
+ b[b.length - 1]?.kind === "BranchStmt" &&
3415
+ b[b.length - 1].keyword === "fallthrough"
3416
+ );
3417
+ })
3418
+ ) {
3419
+ this._emitFallthroughSwitch(stmt);
3420
+ return;
3421
+ }
2197
3422
  this.pushInstruction({ op: "block", blockType: "void" });
2198
3423
  this.pushControl("break", stmt.label);
2199
3424
 
@@ -2221,25 +3446,16 @@ export class StmtsEmitter {
2221
3446
  let ifCount = 0;
2222
3447
  for (const c of nonDefaultCases) {
2223
3448
  const vals = getCaseVals(c);
2224
- for (let i = 0; i < vals.length; i++) {
2225
- if (stmt.tag) {
2226
- this.pushInstruction({ op: "local.get", index: tagTmp });
2227
- this.emitExpr(vals[i], tagWType);
2228
- this.emitBinaryOp("==", tagWType, tagGoType);
2229
- } else {
2230
- this.emitExpr(vals[i], "i32");
2231
- }
2232
- if (i > 0) this.pushInstruction("i32.or");
2233
- }
3449
+ this._emitCaseListMatch(stmt, vals, tagTmp, tagWType, tagGoType);
2234
3450
  this.pushInstruction({ op: "if", blockType: "void" });
2235
3451
  ifCount++;
2236
3452
 
2237
- for (const s of c.stmts ?? []) this.emitStmt(s);
3453
+ this._emitClauseBody(c.stmts);
2238
3454
  this.pushInstruction("else");
2239
3455
  }
2240
3456
 
2241
3457
  if (defaultCase) {
2242
- for (const s of defaultCase.stmts ?? []) this.emitStmt(s);
3458
+ this._emitClauseBody(defaultCase.stmts);
2243
3459
  }
2244
3460
 
2245
3461
  for (let i = 0; i < ifCount; i++) {
@@ -2252,6 +3468,17 @@ export class StmtsEmitter {
2252
3468
  this.pushInstruction("end");
2253
3469
  }
2254
3470
 
3471
+ // Wraps the value on the stack in a box when the just-declared local `name`
3472
+ // is captured and mutated.
3473
+ _emitBoxIfCaptured(name) {
3474
+ const info = this.locals.get(name);
3475
+ if (info?.isBoxed)
3476
+ this.pushInstruction({
3477
+ op: "struct.new",
3478
+ typeIndex: info.boxInfo.typeIndex,
3479
+ });
3480
+ }
3481
+
2255
3482
  emitTypeSwitchStmt(stmt) {
2256
3483
  this.pushInstruction({ op: "block", blockType: "void" });
2257
3484
  this.pushControl("break", stmt.label);
@@ -2285,9 +3512,14 @@ export class StmtsEmitter {
2285
3512
  ifCount++;
2286
3513
 
2287
3514
  if (stmt.assign) {
2288
- const targetGoType =
2289
- types.length === 1
2290
- ? (types[0]._type ?? types[0])
3515
+ // `case nil` binds the switch expression's own (null) value.
3516
+ const isNilCase =
3517
+ types.length === 1 &&
3518
+ (types[0].name === "nil" || types[0]._type?.name === "nil");
3519
+ const targetGoType = isNilCase
3520
+ ? (stmt.expr._type ?? { kind: "basic", name: "any" })
3521
+ : types.length === 1
3522
+ ? (types[0]._type ?? this._resolveTestTarget(types[0]))
2291
3523
  : { kind: "basic", name: "any" };
2292
3524
  const targetWType = this.toWasmType(targetGoType);
2293
3525
  const assignLocal = this.allocLocal(
@@ -2295,18 +3527,23 @@ export class StmtsEmitter {
2295
3527
  targetWType,
2296
3528
  targetGoType,
2297
3529
  );
2298
- if (types.length === 1) {
3530
+ if (types.length === 1 && !isNilCase) {
2299
3531
  this.pushInstruction({ op: "local.get", index: tswTmp });
2300
3532
  this._emitTypeCast(targetGoType, targetWType);
3533
+ // The bound array is a copy of the boxed value.
3534
+ if (
3535
+ targetGoType?.kind !== "pointer" &&
3536
+ isArrayType(targetGoType, this.mod.checker)
3537
+ )
3538
+ this.emitCopyArray(targetGoType);
2301
3539
  } else {
2302
3540
  this.pushInstruction({ op: "local.get", index: tswTmp });
2303
3541
  }
3542
+ this._emitBoxIfCaptured(stmt.assign);
2304
3543
  this.pushInstruction({ op: "local.set", index: assignLocal });
2305
3544
  }
2306
3545
 
2307
- for (const s of c.stmts ?? []) {
2308
- this.emitStmt(s);
2309
- }
3546
+ this._emitClauseBody(c.stmts);
2310
3547
 
2311
3548
  this.pushInstruction("else");
2312
3549
  }
@@ -2318,11 +3555,10 @@ export class StmtsEmitter {
2318
3555
  name: "any",
2319
3556
  });
2320
3557
  this.pushInstruction({ op: "local.get", index: tswTmp });
3558
+ this._emitBoxIfCaptured(stmt.assign);
2321
3559
  this.pushInstruction({ op: "local.set", index: assignLocal });
2322
3560
  }
2323
- for (const s of defaultCase.stmts ?? []) {
2324
- this.emitStmt(s);
2325
- }
3561
+ this._emitClauseBody(defaultCase.stmts);
2326
3562
  }
2327
3563
 
2328
3564
  for (let i = 0; i < ifCount; i++) {
@@ -2340,13 +3576,201 @@ export class StmtsEmitter {
2340
3576
  this.pushInstruction("end");
2341
3577
  }
2342
3578
 
3579
+ _propagateYieldExit(tag) {
3580
+ if (this.funcDecl._iterStateName) {
3581
+ const outerLoc = this.resolveLocal(this.funcDecl._iterStateName);
3582
+ if (outerLoc?.isBoxed) {
3583
+ this.pushInstruction({ op: "local.get", index: outerLoc.index });
3584
+ this.pushInstruction({ op: "i64.const", value: tag });
3585
+ this.pushInstruction({
3586
+ op: "struct.set",
3587
+ typeIndex: outerLoc.boxInfo.typeIndex,
3588
+ fieldIndex: 0,
3589
+ });
3590
+ }
3591
+ }
3592
+ this.pushInstruction({ op: "i32.const", value: 0 });
3593
+ this.pushInstruction({ op: "return" });
3594
+ }
3595
+
3596
+ _unwindIteratorReturn() {
3597
+ if (this.funcDecl._isYieldClosure) {
3598
+ this._propagateYieldExit(2n);
3599
+ return;
3600
+ }
3601
+ if (this.hasDefer) {
3602
+ const depth = this.resolveBranchDepthToRole("runDefers");
3603
+ this.pushInstruction({ op: "br", depth });
3604
+ return;
3605
+ }
3606
+ const rootNamed = this.funcDecl.returnType?._namedReturns ?? [];
3607
+ for (const nr of rootNamed) {
3608
+ const loc = this.locals.get(nr.name);
3609
+ if (loc?.isBoxed) {
3610
+ this.pushInstruction({ op: "local.get", index: loc.index });
3611
+ this.pushInstruction({
3612
+ op: "struct.get",
3613
+ typeIndex: loc.boxInfo.typeIndex,
3614
+ fieldIndex: 0,
3615
+ });
3616
+ } else if (loc) {
3617
+ this.pushInstruction({ op: "local.get", index: loc.index });
3618
+ }
3619
+ }
3620
+ this.pushInstruction({ op: "return" });
3621
+ }
3622
+
3623
+ _unwindIteratorTargets(stmt, stateTmp, labelTargets) {
3624
+ for (const target of labelTargets) {
3625
+ this.pushInstruction({ op: "local.get", index: stateTmp });
3626
+ this.pushInstruction({ op: "i64.const", value: BigInt(target.tag) });
3627
+ this.pushInstruction("i64.eq");
3628
+ this.pushInstruction({ op: "if", blockType: "void" });
3629
+ if (target.role !== "break" || stmt.label !== target.label) {
3630
+ if (this.funcDecl._isYieldClosure) {
3631
+ this._propagateYieldExit(BigInt(target.tag));
3632
+ } else {
3633
+ const depth = this.resolveBranchDepth(
3634
+ target.label,
3635
+ target.role === "continue",
3636
+ );
3637
+ this.pushInstruction({ op: "br", depth });
3638
+ }
3639
+ }
3640
+ this.pushInstruction("end");
3641
+ }
3642
+ }
3643
+
3644
+ _emitIteratorUnwind(stmt) {
3645
+ if (!stmt._iterStateName) return;
3646
+ const stateLoc = this.resolveLocal(stmt._iterStateName);
3647
+ if (!stateLoc?.isBoxed) return;
3648
+
3649
+ const funcLit = getOrCreateIteratorFuncLit(stmt);
3650
+ const hasRet = hasReturnInBlock(funcLit?.body);
3651
+ const labelTargets = Array.from(
3652
+ this.rootFuncDecl?._labelTargets?.values() ?? [],
3653
+ );
3654
+ if (!hasRet && labelTargets.length === 0) return;
3655
+
3656
+ const stateTmp = this.acquireTemp("i64");
3657
+ this.pushInstruction({ op: "local.get", index: stateLoc.index });
3658
+ this.pushInstruction({
3659
+ op: "struct.get",
3660
+ typeIndex: stateLoc.boxInfo.typeIndex,
3661
+ fieldIndex: 0,
3662
+ });
3663
+ this.pushInstruction({ op: "local.set", index: stateTmp });
3664
+
3665
+ if (hasRet) {
3666
+ this.pushInstruction({ op: "local.get", index: stateTmp });
3667
+ this.pushInstruction({ op: "i64.const", value: 2n });
3668
+ this.pushInstruction("i64.eq");
3669
+ this.pushInstruction({ op: "if", blockType: "void" });
3670
+ this._unwindIteratorReturn();
3671
+ this.pushInstruction("end");
3672
+ }
3673
+
3674
+ this._unwindIteratorTargets(stmt, stateTmp, labelTargets);
3675
+ this.releaseTemp(stateTmp, "i64");
3676
+ }
3677
+
3678
+ _tryEmitYieldBranch(stmt) {
3679
+ if (!this.funcDecl._isYieldClosure) return false;
3680
+ const isContinue = stmt.keyword === "continue";
3681
+ const targetRole = isContinue ? "continue" : "break";
3682
+ const hasInnerTarget = this.controlStack.some((c) =>
3683
+ stmt.label
3684
+ ? c.label === stmt.label && c.role === targetRole
3685
+ : c.role === targetRole,
3686
+ );
3687
+ if (hasInnerTarget) return false;
3688
+
3689
+ if (!stmt.label) {
3690
+ if (stmt.keyword === "break") {
3691
+ this._propagateYieldExit(1n);
3692
+ return true;
3693
+ }
3694
+ if (stmt.keyword === "continue") {
3695
+ this.pushInstruction({ op: "i32.const", value: 1 });
3696
+ this.pushInstruction({ op: "return" });
3697
+ return true;
3698
+ }
3699
+ return false;
3700
+ }
3701
+
3702
+ if (
3703
+ stmt.keyword === "continue" &&
3704
+ stmt.label === this.funcDecl._forStmt?.label
3705
+ ) {
3706
+ this.pushInstruction({ op: "i32.const", value: 1 });
3707
+ this.pushInstruction({ op: "return" });
3708
+ return true;
3709
+ }
3710
+
3711
+ const target = this._findLabelTarget(stmt.label, targetRole);
3712
+ const tag = target ? BigInt(target.tag) : 1n;
3713
+ this._propagateYieldExit(tag);
3714
+ return true;
3715
+ }
3716
+
3717
+ _emitYieldReturn(stmt) {
3718
+ const values = stmt.values ?? [];
3719
+ const rootNamed = this.rootFuncDecl?.returnType?._namedReturns ?? [];
3720
+ if (values.length === 1 && rootNamed.length > 1) {
3721
+ this.emitExpr(values[0]);
3722
+ for (let i = rootNamed.length - 1; i >= 0; i--) {
3723
+ this._assignToNamedReturnVar(rootNamed[i]);
3724
+ }
3725
+ } else if (values.length > 0 && rootNamed.length > 0) {
3726
+ const temps = [];
3727
+ for (let i = 0; i < values.length; i++) {
3728
+ const val = values[i];
3729
+ const nr = rootNamed[i];
3730
+ const targetWType = this.toWasmType(nr?.type ?? val._type);
3731
+ this.emitExpr(val, targetWType);
3732
+ this._emitCopyReturnValue(val, targetWType);
3733
+ const tmp = this.acquireTemp(targetWType);
3734
+ this.pushInstruction({ op: "local.set", index: tmp });
3735
+ temps.push({ tmp, type: targetWType });
3736
+ }
3737
+ for (let i = 0; i < temps.length; i++) {
3738
+ this.pushInstruction({ op: "local.get", index: temps[i].tmp });
3739
+ this._assignToNamedReturnVar(rootNamed[i]);
3740
+ this.releaseTemp(temps[i].tmp, temps[i].type);
3741
+ }
3742
+ }
3743
+
3744
+ if (this.funcDecl._iterStateName) {
3745
+ const stateLoc = this.resolveLocal(this.funcDecl._iterStateName);
3746
+ if (stateLoc?.isBoxed) {
3747
+ this.pushInstruction({ op: "local.get", index: stateLoc.index });
3748
+ this.pushInstruction({ op: "i64.const", value: 2n });
3749
+ this.pushInstruction({
3750
+ op: "struct.set",
3751
+ typeIndex: stateLoc.boxInfo.typeIndex,
3752
+ fieldIndex: 0,
3753
+ });
3754
+ }
3755
+ }
3756
+ this.pushInstruction({ op: "i32.const", value: 0 });
3757
+ this.pushInstruction({ op: "return" });
3758
+ }
3759
+
2343
3760
  emitBranchStmt(stmt) {
3761
+ const alias = stmt.label && this._labelAliases?.get(stmt.label);
3762
+ if (alias) stmt = { ...stmt, label: alias };
3763
+ if (this._tryEmitYieldBranch(stmt)) return;
2344
3764
  const isContinue = stmt.keyword === "continue";
2345
3765
  const depth = this.resolveBranchDepth(stmt.label, isContinue);
2346
3766
  this.pushInstruction({ op: "br", depth });
2347
3767
  }
2348
3768
 
2349
3769
  emitReturnStmt(stmt) {
3770
+ if (this.funcDecl._isYieldClosure) {
3771
+ this._emitYieldReturn(stmt);
3772
+ return;
3773
+ }
2350
3774
  const values = stmt.values ?? [];
2351
3775
  if (this.hasDefer) {
2352
3776
  if (this.namedReturnVars?.length > 0) {
@@ -2365,7 +3789,7 @@ export class StmtsEmitter {
2365
3789
  const val = values[i];
2366
3790
  const targetWType = this.returnTypes[i] ?? null;
2367
3791
  this.emitExpr(val, targetWType);
2368
- this._emitCopyIfValueStruct(val, val._type, targetWType);
3792
+ this._emitCopyReturnValue(val, targetWType);
2369
3793
  const tmp = this.acquireTemp(targetWType);
2370
3794
  this.pushInstruction({ op: "local.set", index: tmp });
2371
3795
  temps.push({ tmp, type: targetWType });
@@ -2390,7 +3814,7 @@ export class StmtsEmitter {
2390
3814
  const val = values[i];
2391
3815
  const targetWType = this.returnTypes[i] ?? null;
2392
3816
  this.emitExpr(val, targetWType);
2393
- this._emitCopyIfValueStruct(val, val._type, targetWType);
3817
+ this._emitCopyReturnValue(val, targetWType);
2394
3818
  this.pushInstruction({
2395
3819
  op: "local.set",
2396
3820
  index: this.returnTempLocals[i],
@@ -2421,17 +3845,38 @@ export class StmtsEmitter {
2421
3845
  return;
2422
3846
  }
2423
3847
 
3848
+ if (values.length === 1 && this.returnTypes.length > 1) {
3849
+ this.emitExpr(values[0]);
3850
+ this.pushInstruction("return");
3851
+ return;
3852
+ }
3853
+
2424
3854
  for (let i = 0; i < values.length; i++) {
2425
3855
  const val = values[i];
2426
3856
  const targetWType = this.returnTypes[i] ?? null;
2427
3857
  this.emitExpr(val, targetWType);
2428
- this._emitCopyIfValueStruct(val, val._type, targetWType);
3858
+ this._emitCopyReturnValue(val, targetWType);
2429
3859
  }
2430
3860
  this.pushInstruction("return");
2431
3861
  }
2432
3862
 
3863
+ // A value returned as an interface was copied when it was boxed; copying it
3864
+ // again by its own type would treat the box as the struct.
3865
+ _emitCopyReturnValue(val, targetWType) {
3866
+ if (targetWType === "anyref") return;
3867
+ this._emitCopyIfValue(val, val._type, targetWType);
3868
+ }
3869
+
2433
3870
  _assignToNamedReturnVar(nr) {
3871
+ if (!nr?.name || nr.name === "_") {
3872
+ this.pushInstruction("drop");
3873
+ return;
3874
+ }
2434
3875
  const loc = this.locals.get(nr.name);
3876
+ if (!loc) {
3877
+ this.pushInstruction("drop");
3878
+ return;
3879
+ }
2435
3880
  if (loc.isBoxed) {
2436
3881
  const valTmp = this.acquireTemp(loc.boxInfo.wType);
2437
3882
  this.pushInstruction({ op: "local.set", index: valTmp });