gofront 1.6.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -1,20 +1,59 @@
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 { 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,
16
27
  } from "./types.js";
17
28
 
29
+ function hasReturnInBlock(n) {
30
+ let found = false;
31
+ const check = (x) => {
32
+ if (!x || typeof x !== "object" || found) return;
33
+ if (Array.isArray(x)) {
34
+ for (const elem of x) check(elem);
35
+ return;
36
+ }
37
+ if (x.kind === "ReturnStmt") {
38
+ found = true;
39
+ return;
40
+ }
41
+ if (x.kind === "FuncLit" && !x._isYieldClosure) return;
42
+ for (const k of Object.keys(x)) {
43
+ if (k.startsWith("_")) continue;
44
+ check(x[k]);
45
+ }
46
+ };
47
+ check(n);
48
+ return found;
49
+ }
50
+
51
+ // A plain `string`: the one string type that lives in `any` as the bare
52
+ // string. A named string type is boxed like any other scalar (see emitExpr).
53
+ function isBareStringType(t, checker) {
54
+ return isStringType(t, checker) && t.kind !== "named";
55
+ }
56
+
18
57
  export class StmtsEmitter {
19
58
  emitVarDecl(stmt) {
20
59
  const decls = stmt.decls ?? stmt.specs ?? [stmt];
@@ -31,7 +70,7 @@ export class StmtsEmitter {
31
70
  if (values?.[i]) {
32
71
  const r = values[i];
33
72
  this.emitExpr(r, wType);
34
- this._emitCopyIfValueStruct(r, rawType, wType);
73
+ this._emitCopyIfValue(r, rawType, wType);
35
74
  if (localInfo?.isBoxed) {
36
75
  this.pushInstruction({
37
76
  op: "struct.new",
@@ -88,9 +127,11 @@ export class StmtsEmitter {
88
127
  return;
89
128
  }
90
129
 
130
+ const rhsVals = this._parallelAssignValues(lhsNodes, rhsNodes, true);
91
131
  for (let i = 0; i < lhsNodes.length; i++) {
92
- this._emitDefineSingle(lhsNodes[i], rhsNodes[i]);
132
+ this._emitDefineSingle(lhsNodes[i], rhsVals[i]);
93
133
  }
134
+ this._releaseParallelTemps(rhsVals, rhsNodes);
94
135
  }
95
136
 
96
137
  _emitDefineSingle(l, r) {
@@ -100,11 +141,17 @@ export class StmtsEmitter {
100
141
  this.pushInstruction("drop");
101
142
  return;
102
143
  }
144
+ if (l._redecl && this.resolveLocal(l.name)) {
145
+ this._emitAssignSingle(l, r, "=");
146
+ return;
147
+ }
103
148
  const wType = this.toWasmType(r._type);
149
+ // The value is computed before the new name exists: `x := x * 2`
150
+ // in an inner block reads the outer `x`.
151
+ this.emitExpr(r, wType);
152
+ this._emitCopyIfValue(r, r._type, wType);
104
153
  const idx = this.allocLocal(l.name, wType, r._type);
105
154
  const localInfo = this.locals.get(l.name);
106
- this.emitExpr(r, wType);
107
- this._emitCopyIfValueStruct(r, r._type, wType);
108
155
  if (localInfo?.isBoxed) {
109
156
  this.pushInstruction({
110
157
  op: "struct.new",
@@ -114,7 +161,38 @@ export class StmtsEmitter {
114
161
  this.pushInstruction({ op: "local.set", index: idx });
115
162
  }
116
163
 
164
+ // Stores a multi-value call into temporaries (last result first), then
165
+ // assigns each one to its target. Used when a target is not a plain new
166
+ // local: an index, field or dereference, or a name redeclared by `:=`.
167
+ _emitTupleViaTemps(lhsNodes, rhsNode, assignOne, hoist) {
168
+ const tupleTypes = rhsNode._type.types;
169
+ this.emitExpr(rhsNode);
170
+ const temps = [];
171
+ for (let i = lhsNodes.length - 1; i >= 0; i--) {
172
+ const goType = tupleTypes[i];
173
+ const wType = this.toWasmType(goType);
174
+ const name = `__tup_tmp_${this.tempCounter++}`;
175
+ const idx = this.allocLocal(name, wType, goType, true);
176
+ this.pushInstruction({ op: "local.set", index: idx });
177
+ temps[i] = { kind: "Ident", name, _type: goType, _parTmp: true };
178
+ }
179
+ const undo = hoist ? this._hoistLhsOperands(lhsNodes) : null;
180
+ for (let i = 0; i < lhsNodes.length; i++) assignOne(lhsNodes[i], temps[i]);
181
+ undo?.();
182
+ for (const t of temps) this.locals.delete(t.name);
183
+ }
184
+
185
+ _isPlainTupleTarget(l) {
186
+ return l.kind === "Ident";
187
+ }
188
+
117
189
  _emitTupleDefine(lhsNodes, rhsNode) {
190
+ if (lhsNodes.some((l) => !this._isPlainTupleTarget(l) || l._redecl)) {
191
+ this._emitTupleViaTemps(lhsNodes, rhsNode, (l, t) =>
192
+ this._emitDefineSingle(l, t),
193
+ );
194
+ return;
195
+ }
118
196
  const tupleTypes = rhsNode._type.types;
119
197
  const localIndices = [];
120
198
  for (let i = 0; i < lhsNodes.length; i++) {
@@ -200,11 +278,190 @@ export class StmtsEmitter {
200
278
  return;
201
279
  }
202
280
 
281
+ const hoisted = op === "=" ? this._hoistLhsOperands(lhsNodes) : null;
282
+ const rhsVals = this._parallelAssignValues(lhsNodes, rhsNodes);
203
283
  for (let i = 0; i < lhsNodes.length; i++) {
204
- this._emitAssignSingle(lhsNodes[i], rhsNodes[i], op);
284
+ this._emitAssignSingle(lhsNodes[i], rhsVals[i], op);
285
+ }
286
+ this._releaseParallelTemps(rhsVals, rhsNodes);
287
+ hoisted?.();
288
+ }
289
+
290
+ // `k, xs[k] = 1, 5`: Go evaluates the operands of every index and field
291
+ // target before any assignment. An operand that mentions a variable
292
+ // assigned by the same statement is evaluated into a temporary first.
293
+ // Returns a function that restores the AST and releases the temporaries.
294
+ _hoistLhsOperands(lhsNodes) {
295
+ if (lhsNodes.length < 2) return null;
296
+ const assigned = new Set(
297
+ lhsNodes
298
+ .filter((l) => l.kind === "Ident" && l.name !== "_")
299
+ .map((l) => l.name),
300
+ );
301
+ if (assigned.size === 0) return null;
302
+ const undo = [];
303
+ const names = [];
304
+ for (const l of lhsNodes) {
305
+ let keys;
306
+ if (l.kind === "IndexExpr") keys = ["expr", "index"];
307
+ else if (l.kind === "SelectorExpr" && !l._isMethodExpr) keys = ["expr"];
308
+ else continue;
309
+ for (const key of keys) {
310
+ const operand = l[key];
311
+ if (!this._mentions(operand, assigned)) continue;
312
+ const goType = operand._type ?? this._resolveExprGoType(operand);
313
+ // A struct or array value names the variable's storage in Go.
314
+ if (
315
+ key === "expr" &&
316
+ (this._isValueStruct(goType) || this._isValueArray(goType))
317
+ )
318
+ continue;
319
+ const wType = this.toWasmType(goType);
320
+ const name = `__lhs_tmp_${this.tempCounter++}`;
321
+ const idx = this.allocLocal(name, wType, goType, true);
322
+ this.emitExpr(operand, wType);
323
+ this.pushInstruction({ op: "local.set", index: idx });
324
+ undo.push([l, key, operand]);
325
+ names.push(name);
326
+ l[key] = { kind: "Ident", name, _type: goType, _parTmp: true };
327
+ }
328
+ }
329
+ if (undo.length === 0) return null;
330
+ return () => {
331
+ for (const [node, key, v] of undo) node[key] = v;
332
+ for (const n of names) this.locals.delete(n);
333
+ };
334
+ }
335
+
336
+ // `x, y = y, x`: Go evaluates every right-hand side before assigning.
337
+ // When a later value could see an earlier assignment, every value that
338
+ // is not pure is stored in a temporary first (in source order, so side
339
+ // effects keep their order). Pure values stay inline, so `i, n = 0,
340
+ // len(xs)` is unchanged. A definition only exposes its redeclared
341
+ // names: a new name is not visible to the values being assigned to it.
342
+ _parallelAssignValues(lhsNodes, rhsNodes, define = false) {
343
+ if (lhsNodes.length < 2 || rhsNodes.length !== lhsNodes.length)
344
+ return rhsNodes;
345
+ const roots = new Set(
346
+ lhsNodes.map((l) => rootIdentName(l)).filter((n) => n && n !== "_"),
347
+ );
348
+ const pure = rhsNodes.map((r) => this._independentOf(r, roots));
349
+ const redecl = lhsNodes.some((l) => l._redecl);
350
+ const observes = (r, i) =>
351
+ define ? this._mentions(r, roots) || (redecl && !pure[i]) : !pure[i];
352
+ if (!rhsNodes.some((r, i) => i > 0 && observes(r, i))) return rhsNodes;
353
+ const inPlace = lhsNodes.some(
354
+ (l) => this._isInPlaceArrayStore(l) || this._isStableTarget(l),
355
+ );
356
+ return rhsNodes.map((r, i) => {
357
+ if (pure[i]) return r;
358
+ const goType = r._type ?? this._resolveExprGoType(r);
359
+ const wType = this.toWasmType(goType);
360
+ const name = `__par_tmp_${this.tempCounter++}`;
361
+ const idx = this.allocLocal(name, wType, goType, true);
362
+ this.emitExpr(r, wType);
363
+ // An array temp must snapshot the value when a target is stored in
364
+ // place (`a, b = b, a` with `p := &a`): the store would otherwise
365
+ // change what the temp holds. The snapshot is already a fresh
366
+ // copy, so assigning from the temp does not copy it again.
367
+ const isArr = this._isValueArray(goType);
368
+ const snapshot = inPlace && (isArr || this._isValueStruct(goType));
369
+ if (snapshot) this._emitCopyIfValue(r, goType, wType);
370
+ this.pushInstruction({ op: "local.set", index: idx });
371
+ return {
372
+ kind: "Ident",
373
+ name,
374
+ _type: goType,
375
+ _parTmp: true,
376
+ _freshArray: snapshot && isArr,
377
+ };
378
+ });
379
+ }
380
+
381
+ // True when assigning to `l` writes into an existing array object (so an
382
+ // earlier-read array value sharing that object would see the write): a
383
+ // boxed (address-taken) array local, `*p` on an array pointer, or an
384
+ // element of an array.
385
+ _isInPlaceArrayStore(l) {
386
+ if (l.kind === "Ident") {
387
+ const info = this.resolveLocal(l.name);
388
+ return !!info?.isBoxed && this._isValueArray(info.goType);
389
+ }
390
+ if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
391
+ const t = (l.operand ?? l.expr)?._type;
392
+ return this._isValueArray(t?.base ?? t);
393
+ }
394
+ if (l.kind === "IndexExpr") {
395
+ const t = l.expr._type ?? this._resolveExprGoType(l.expr);
396
+ return (
397
+ this._isValueArray(t) ||
398
+ (t?.kind === "pointer" && this._isValueArray(t.base))
399
+ );
400
+ }
401
+ return false;
402
+ }
403
+
404
+ // True when `l` is an array or struct target that a whole-value
405
+ // assignment writes in place (see `_needsStableStore`).
406
+ _isStableTarget(l) {
407
+ const t = l._type ?? this._resolveExprGoType(l);
408
+ return this._needsStableStore(l, t);
409
+ }
410
+
411
+ _mentions(node, names) {
412
+ if (!node || typeof node !== "object") return false;
413
+ if (node.kind === "Ident" && names.has(node.name)) return true;
414
+ return someChild(node, (child) => this._mentions(child, names));
415
+ }
416
+
417
+ _releaseParallelTemps(rhsVals, rhsNodes) {
418
+ if (rhsVals === rhsNodes) return;
419
+ for (const r of rhsVals) if (r?._parTmp) this.locals.delete(r.name);
420
+ }
421
+
422
+ // True when evaluating `node` cannot observe an assignment to `roots`
423
+ // (or through a pointer) and has no side effects.
424
+ _independentOf(node, roots) {
425
+ if (!node || typeof node !== "object") return true;
426
+ switch (node.kind) {
427
+ case "BasicLit":
428
+ return true;
429
+ case "Ident":
430
+ return this._independentIdent(node, roots);
431
+ case "BinaryExpr":
432
+ return (
433
+ this._independentOf(node.left, roots) &&
434
+ this._independentOf(node.right, roots)
435
+ );
436
+ case "UnaryExpr":
437
+ return (
438
+ node.op !== "*" &&
439
+ node.op !== "&" &&
440
+ this._independentOf(node.operand, roots)
441
+ );
442
+ case "CallExpr":
443
+ return this._isPureLenCall(node, roots);
444
+ default:
445
+ return false;
205
446
  }
206
447
  }
207
448
 
449
+ _independentIdent(node, roots) {
450
+ if (node._const || node.name === "nil") return true;
451
+ if (roots.has(node.name)) return false;
452
+ const local = this.resolveLocal(node.name);
453
+ return !!local && !local.isBoxed;
454
+ }
455
+
456
+ // `len`/`cap` of a pure operand.
457
+ _isPureLenCall(node, roots) {
458
+ const fn = node.func;
459
+ if (fn?.kind !== "Ident" || (fn.name !== "len" && fn.name !== "cap"))
460
+ return false;
461
+ if (this.resolveLocal(fn.name) || node.args?.length !== 1) return false;
462
+ return this._independentOf(node.args[0], roots);
463
+ }
464
+
208
465
  _emitAssignToTarget(target, wasmType, goType, emitValue) {
209
466
  const tempName = `__assign_tmp_${this.tempCounter++}`;
210
467
  const effectiveGoType = goType ??
@@ -244,10 +501,48 @@ export class StmtsEmitter {
244
501
  this.allocLocal(l.name, wType, r._type);
245
502
  localInfo = this.locals.get(l.name);
246
503
  }
504
+ if (
505
+ localInfo &&
506
+ op !== ":=" &&
507
+ this._needsStableStore(l, localInfo.goType)
508
+ ) {
509
+ this._emitStableStore(localInfo.goType, r, () => {
510
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
511
+ if (localInfo.isBoxed)
512
+ this.pushInstruction({
513
+ op: "struct.get",
514
+ typeIndex: localInfo.boxInfo.typeIndex,
515
+ fieldIndex: 0,
516
+ });
517
+ });
518
+ return;
519
+ }
520
+ const globalStable =
521
+ !localInfo && op !== ":=" && this.mod.resolveGlobal(l.name);
522
+ if (globalStable && this._needsStableStore(l, globalStable.goType)) {
523
+ this._emitStableStore(globalStable.goType, r, () => {
524
+ this.pushInstruction({ op: "global.get", index: globalStable.index });
525
+ });
526
+ return;
527
+ }
247
528
  if (localInfo) {
529
+ if (localInfo.isBoxed && this._isValueArray(localInfo.goType)) {
530
+ // Pointers to the array (`&a`) hold its reference: write in place.
531
+ this._emitArrayStoreInto(localInfo.goType, r, () => {
532
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
533
+ this.pushInstruction({
534
+ op: "struct.get",
535
+ typeIndex: localInfo.boxInfo.typeIndex,
536
+ fieldIndex: 0,
537
+ });
538
+ });
539
+ return;
540
+ }
248
541
  if (localInfo.isBoxed) {
249
542
  this.pushInstruction({ op: "local.get", index: localInfo.index });
250
543
  this.emitExpr(r, localInfo.boxInfo.wType);
544
+ if (isArrayType(localInfo.goType, this.mod.checker))
545
+ this._emitCopyIfValue(r, localInfo.goType, localInfo.boxInfo.wType);
251
546
  this.pushInstruction({
252
547
  op: "struct.set",
253
548
  typeIndex: localInfo.boxInfo.typeIndex,
@@ -256,13 +551,13 @@ export class StmtsEmitter {
256
551
  return;
257
552
  }
258
553
  this.emitExpr(r, localInfo.type);
259
- this._emitCopyIfValueStruct(r, localInfo.goType, localInfo.type);
554
+ this._emitCopyIfValue(r, localInfo.goType, localInfo.type);
260
555
  this.pushInstruction({ op: "local.set", index: localInfo.index });
261
556
  } else {
262
557
  const globalInfo = this.mod.resolveGlobal(l.name);
263
558
  if (globalInfo) {
264
559
  this.emitExpr(r, globalInfo.type);
265
- this._emitCopyIfValueStruct(r, globalInfo.goType, globalInfo.type);
560
+ this._emitCopyIfValue(r, globalInfo.goType, globalInfo.type);
266
561
  this.pushInstruction({
267
562
  op: "global.set",
268
563
  index: globalInfo.index,
@@ -292,7 +587,20 @@ export class StmtsEmitter {
292
587
  this._resolveExprGoType(l.expr);
293
588
  const baseWType = this.toWasmType(exprGoType);
294
589
 
295
- this.emitExpr(l.expr, baseWType);
590
+ if (this._needsStableStore(l, lastStep.field.goType)) {
591
+ this._emitStableStore(lastStep.field.goType, r, () => {
592
+ this.emitSelectorBase(l.expr, baseWType, exprGoType);
593
+ for (const step of path) {
594
+ this.pushInstruction({
595
+ op: "struct.get",
596
+ typeIndex: step.structInfo.typeIndex,
597
+ fieldIndex: step.fieldIndex,
598
+ });
599
+ }
600
+ });
601
+ return;
602
+ }
603
+ this.emitSelectorBase(l.expr, baseWType, exprGoType);
296
604
  for (let i = 0; i < path.length - 1; i++) {
297
605
  const step = path[i];
298
606
  this.pushInstruction({
@@ -302,11 +610,7 @@ export class StmtsEmitter {
302
610
  });
303
611
  }
304
612
  this.emitExpr(r, lastStep.field.wType);
305
- this._emitCopyIfValueStruct(
306
- r,
307
- lastStep.field.goType,
308
- lastStep.field.wType,
309
- );
613
+ this._emitCopyIfValue(r, lastStep.field.goType, lastStep.field.wType);
310
614
  this.pushInstruction({
311
615
  op: "struct.set",
312
616
  typeIndex: lastStep.structInfo.typeIndex,
@@ -315,57 +619,8 @@ export class StmtsEmitter {
315
619
  return;
316
620
  }
317
621
 
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);
622
+ if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
623
+ this._emitDerefAssign(l, r);
369
624
  return;
370
625
  }
371
626
 
@@ -381,7 +636,7 @@ export class StmtsEmitter {
381
636
  this.emitExpr(baseNode, mapInfo.wType);
382
637
  this.emitExpr(l.index, mapInfo.keyWType);
383
638
  this.emitExpr(r, mapInfo.valWType);
384
- this._emitCopyIfValueStruct(r, valType, mapInfo.valWType);
639
+ this._emitCopyIfValue(r, valType, mapInfo.valWType);
385
640
  this.pushInstruction({
386
641
  op: "call",
387
642
  funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
@@ -400,18 +655,29 @@ export class StmtsEmitter {
400
655
  baseType?.kind === "pointer" &&
401
656
  isArrayType(baseType.base, this.mod.checker);
402
657
 
403
- const elemGoType = isSlice
404
- ? baseType.elem
405
- : isPtrToArr
406
- ? baseType.base.elem
407
- : baseType.elem;
658
+ const elemGoType = this._getArrayElemType(
659
+ isPtrToArr ? baseType.base : baseType,
660
+ );
408
661
  const arrInfo = this.mod.getArrayType(elemGoType);
409
662
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
410
663
 
664
+ // A whole struct or array stored into an element whose address is
665
+ // taken writes into the existing element, so `p := &xs[0]` stays valid.
666
+ if (this._needsStableStore(l, elemGoType)) {
667
+ this._emitStableStore(elemGoType, r, () => {
668
+ this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
669
+ this.pushInstruction({
670
+ op: "array.get",
671
+ typeIndex: arrInfo.typeIndex,
672
+ });
673
+ });
674
+ return;
675
+ }
676
+
411
677
  this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
412
678
 
413
679
  this.emitExpr(r, arrInfo.elemWType);
414
- this._emitCopyIfValueStruct(r, elemGoType, arrInfo.elemWType);
680
+ this._emitCopyIfValue(r, elemGoType, arrInfo.elemWType);
415
681
 
416
682
  this.pushInstruction({
417
683
  op: "array.set",
@@ -421,7 +687,325 @@ export class StmtsEmitter {
421
687
  }
422
688
  }
423
689
 
690
+ // A range variable that a closure captures and mutates lives in a box,
691
+ // but the loop writes the plain value each iteration. It gets a raw
692
+ // local here, and `_emitRangeBoxes` (at the top of the body) wraps the
693
+ // value in a fresh box per iteration, so each iteration's closures keep
694
+ // their own variable (Go 1.22) and the loop never stores a raw value
695
+ // into a box local.
696
+ _allocRangeVar(name, wType, goType) {
697
+ if (!this.mutatedCaptures.has(name))
698
+ return this.allocLocal(name, wType, goType);
699
+ const idx = this.allocLocal(name, wType, goType, true);
700
+ if (!this._pendingRangeBoxes) this._pendingRangeBoxes = [];
701
+ this._pendingRangeBoxes.push({ name, idx, wType, goType });
702
+ return idx;
703
+ }
704
+
705
+ _emitRangeBoxes() {
706
+ const pending = this._pendingRangeBoxes ?? [];
707
+ this._pendingRangeBoxes = [];
708
+ for (const { name, idx, wType, goType } of pending) {
709
+ const boxIdx = this.allocLocal(name, wType, goType);
710
+ const info = this.locals.get(name);
711
+ this.pushInstruction({ op: "local.get", index: idx });
712
+ this.pushInstruction({
713
+ op: "struct.new",
714
+ typeIndex: info.boxInfo.typeIndex,
715
+ });
716
+ this.pushInstruction({ op: "local.set", index: boxIdx });
717
+ }
718
+ }
719
+
720
+ // The struct that holds the last field of `path`: a promoted field
721
+ // (`o.N` on an embedded `Inner`) lives on the embedded struct.
722
+ _fieldHolderWType(path, baseWType) {
723
+ if (path.length <= 1) return baseWType;
724
+ const holder = path[path.length - 1].structInfo;
725
+ return { kind: "ref", nullable: true, typeIndex: holder.typeIndex };
726
+ }
727
+
728
+ // Copy the array value `r` into the existing array that `emitDst` pushes,
729
+ // so every pointer to that array sees the new elements.
730
+ _emitArrayStoreInto(goType, r, emitDst) {
731
+ const elemGoType = this._getArrayElemType(goType);
732
+ const arrInfo = this.mod.getArrayType(elemGoType);
733
+ const wType = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
734
+ if (this._isValueStruct(elemGoType)) {
735
+ this._emitStructElemsStoreInto(elemGoType, arrInfo, wType, r, emitDst);
736
+ return;
737
+ }
738
+ const dst = this.acquireTemp(wType);
739
+ emitDst();
740
+ this.pushInstruction({ op: "local.tee", index: dst });
741
+ this.pushInstruction({ op: "i32.const", value: 0 });
742
+ this.emitExpr(r, wType);
743
+ this._emitCopyIfValue(r, goType, wType);
744
+ this.pushInstruction({ op: "i32.const", value: 0 });
745
+ this.pushInstruction({ op: "local.get", index: dst });
746
+ this.pushInstruction("array.len");
747
+ this.pushInstruction({
748
+ op: "array.copy",
749
+ typeIndex: arrInfo.typeIndex,
750
+ });
751
+ this.releaseTemp(dst, wType);
752
+ }
753
+
754
+ _isValueStruct(goType) {
755
+ return (
756
+ !!goType &&
757
+ goType.kind !== "pointer" &&
758
+ isStructType(goType, this.mod.checker, this.mod) &&
759
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
760
+ );
761
+ }
762
+
763
+ // An array of structs holds element references, so `array.copy` would
764
+ // swap them and leave `&a[i]` pointing at the old element. Copy each
765
+ // element's fields into the existing element struct instead.
766
+ _emitStructElemsStoreInto(elemGoType, arrInfo, wType, r, emitDst) {
767
+ const elemInfo = this._resolveStructInfo({ _type: elemGoType });
768
+ const eType = arrInfo.elemWType;
769
+ const dst = this.acquireTemp(wType);
770
+ const src = this.acquireTemp(wType);
771
+ const i = this.acquireTemp("i32");
772
+ const d = this.acquireTemp(eType);
773
+ const s = this.acquireTemp(eType);
774
+ emitDst();
775
+ this.pushInstruction({ op: "local.set", index: dst });
776
+ this.emitExpr(r, wType);
777
+ this.pushInstruction({ op: "local.set", index: src });
778
+ this.pushInstruction({ op: "i32.const", value: 0 });
779
+ this.pushInstruction({ op: "local.set", index: i });
780
+ this.pushInstruction({ op: "block", blockType: "void" });
781
+ this.pushInstruction({ op: "loop", blockType: "void" });
782
+ this.pushInstruction({ op: "local.get", index: i });
783
+ this.pushInstruction({ op: "local.get", index: dst });
784
+ this.pushInstruction("array.len");
785
+ this.pushInstruction("i32.ge_s");
786
+ this.pushInstruction({ op: "br_if", depth: 1 });
787
+ for (const [tmp, arr] of [
788
+ [d, dst],
789
+ [s, src],
790
+ ]) {
791
+ this.pushInstruction({ op: "local.get", index: arr });
792
+ this.pushInstruction({ op: "local.get", index: i });
793
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
794
+ this.pushInstruction({ op: "local.set", index: tmp });
795
+ }
796
+ this._emitStructFieldsInto(elemInfo, d, s);
797
+ this.pushInstruction({ op: "local.get", index: i });
798
+ this.pushInstruction({ op: "i32.const", value: 1 });
799
+ this.pushInstruction("i32.add");
800
+ this.pushInstruction({ op: "local.set", index: i });
801
+ this.pushInstruction({ op: "br", depth: 0 });
802
+ this.pushInstruction("end");
803
+ this.pushInstruction("end");
804
+ this.releaseTemp(s, eType);
805
+ this.releaseTemp(d, eType);
806
+ this.releaseTemp(i, "i32");
807
+ this.releaseTemp(src, wType);
808
+ this.releaseTemp(dst, wType);
809
+ }
810
+
811
+ // Whole-value assignment to a variable (or a field of one) whose address
812
+ // is taken writes into the existing storage, so earlier pointers to it or
813
+ // to its parts see the new value (Task 0.33). Other variables keep the
814
+ // plain local.set / struct.set path.
815
+ _needsStableStore(l, goType) {
816
+ if (!goType || goType.kind === "pointer") return false;
817
+ const isArr = this._isValueArray(goType);
818
+ if (
819
+ !isArr &&
820
+ !(
821
+ isStructType(goType, this.mod.checker, this.mod) &&
822
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
823
+ )
824
+ )
825
+ return false;
826
+ const root = rootIdentName(l);
827
+ if (!root) return false;
828
+ // Any code may hold the address of a package-level variable.
829
+ if (!this.resolveLocal(root)) return !!this.mod.resolveGlobal(root);
830
+ const decl = this.rootFuncDecl._sourceDecl ?? this.rootFuncDecl;
831
+ const ctx = this.mod.lowerResult?.ownership?.get(decl);
832
+ return Boolean(ctx?.fnMutates(root, true));
833
+ }
834
+
835
+ _emitStableStore(goType, r, emitDst) {
836
+ if (this._isValueArray(goType)) {
837
+ this._emitArrayStoreInto(goType, r, emitDst);
838
+ return;
839
+ }
840
+ const info = this._resolveStructInfo({ _type: goType });
841
+ const wType = this.toWasmType(goType);
842
+ const dst = this.acquireTemp(wType);
843
+ const src = this.acquireTemp(wType);
844
+ emitDst();
845
+ this.pushInstruction({ op: "local.set", index: dst });
846
+ this.emitExpr(r, wType);
847
+ this.pushInstruction({ op: "local.set", index: src });
848
+ this._emitStructFieldsInto(info, dst, src);
849
+ this.releaseTemp(src, wType);
850
+ this.releaseTemp(dst, wType);
851
+ }
852
+
853
+ // Copies every field of the struct in `src` into the struct in `dst`;
854
+ // array and value-struct fields are copied into the existing storage.
855
+ _emitStructFieldsInto(info, dst, src) {
856
+ const get = (tmp, i) => {
857
+ this.pushInstruction({ op: "local.get", index: tmp });
858
+ this.pushInstruction({
859
+ op: "struct.get",
860
+ typeIndex: info.typeIndex,
861
+ fieldIndex: i,
862
+ });
863
+ };
864
+ for (let i = 0; i < info.fields.length; i++) {
865
+ const f = info.fields[i];
866
+ const gt = f.goType;
867
+ if (gt && gt.kind !== "pointer" && this._isValueArray(gt)) {
868
+ const arrInfo = this.mod.getArrayType(this._getArrayElemType(gt));
869
+ get(dst, i);
870
+ this.pushInstruction({ op: "i32.const", value: 0 });
871
+ get(src, i);
872
+ const elem = this._getArrayElemType(gt);
873
+ if (
874
+ isArrayType(elem, this.mod.checker) ||
875
+ isStructType(elem, this.mod.checker, this.mod)
876
+ )
877
+ this.emitCopyArray(gt);
878
+ this.pushInstruction({ op: "i32.const", value: 0 });
879
+ get(dst, i);
880
+ this.pushInstruction("array.len");
881
+ this.pushInstruction({
882
+ op: "array.copy",
883
+ typeIndex: arrInfo.typeIndex,
884
+ });
885
+ continue;
886
+ }
887
+ const inner =
888
+ gt &&
889
+ gt.kind !== "pointer" &&
890
+ isStructType(gt, this.mod.checker, this.mod) &&
891
+ !isPointerToStruct(gt, this.mod.checker, this.mod)
892
+ ? this._resolveStructInfo({ _type: gt })
893
+ : null;
894
+ if (inner) {
895
+ const d = this.acquireTemp(f.wType);
896
+ const s = this.acquireTemp(f.wType);
897
+ get(dst, i);
898
+ this.pushInstruction({ op: "local.set", index: d });
899
+ get(src, i);
900
+ this.pushInstruction({ op: "local.set", index: s });
901
+ this._emitStructFieldsInto(inner, d, s);
902
+ this.releaseTemp(s, f.wType);
903
+ this.releaseTemp(d, f.wType);
904
+ continue;
905
+ }
906
+ this.pushInstruction({ op: "local.get", index: dst });
907
+ get(src, i);
908
+ this.pushInstruction({
909
+ op: "struct.set",
910
+ typeIndex: info.typeIndex,
911
+ fieldIndex: i,
912
+ });
913
+ }
914
+ }
915
+
916
+ _emitStructDerefAssign(operand, r, structInfo) {
917
+ const baseWType = this.toWasmType(operand._type);
918
+ const tmpL = this.acquireTemp(baseWType);
919
+ const tmpR = this.acquireTemp(baseWType);
920
+ this.emitExpr(operand, baseWType);
921
+ this.pushInstruction({ op: "local.set", index: tmpL });
922
+ this.emitExpr(r, baseWType);
923
+ this.pushInstruction({ op: "local.set", index: tmpR });
924
+ for (let i = 0; i < structInfo.fields.length; i++) {
925
+ this.pushInstruction({ op: "local.get", index: tmpL });
926
+ this.pushInstruction({ op: "local.get", index: tmpR });
927
+ this.pushInstruction({
928
+ op: "struct.get",
929
+ typeIndex: structInfo.typeIndex,
930
+ fieldIndex: i,
931
+ });
932
+ this.pushInstruction({
933
+ op: "struct.set",
934
+ typeIndex: structInfo.typeIndex,
935
+ fieldIndex: i,
936
+ });
937
+ }
938
+ this.releaseTemp(tmpR, baseWType);
939
+ this.releaseTemp(tmpL, baseWType);
940
+ }
941
+
942
+ _emitDerefAssign(l, r) {
943
+ const operand = l.operand ?? l.expr;
944
+ const structInfo = this._derefStructInfo(operand);
945
+ if (structInfo) {
946
+ this._emitStructDerefAssign(operand, r, structInfo);
947
+ return;
948
+ }
949
+ const operandGoType =
950
+ operand._type ??
951
+ (operand.kind === "Ident"
952
+ ? this.resolveLocal(operand.name)?.goType
953
+ : null) ??
954
+ this._resolveExprGoType(operand);
955
+ const baseGoType = operandGoType?.base ?? operandGoType;
956
+ if (this._isValueArray(baseGoType)) {
957
+ this._emitArrayStoreInto(baseGoType, r, () => {
958
+ this.emitExpr(operand);
959
+ this._emitNilCheck(operandGoType);
960
+ });
961
+ return;
962
+ }
963
+ if (!isScalarType(baseGoType, this.mod.checker, this.mod)) {
964
+ return;
965
+ }
966
+ const box = this.mod.getBoxType(baseGoType);
967
+ const ptr = this.mod.getPtrType(baseGoType);
968
+ const ptrWType = {
969
+ kind: "ref",
970
+ nullable: true,
971
+ typeIndex: ptr.typeIndex,
972
+ typeName: ptr.name,
973
+ };
974
+ const ptrTmp = this.acquireTemp(ptrWType);
975
+ const valTmp = this.acquireTemp(box.wType);
976
+ this.emitExpr(operand, ptrWType);
977
+ this.pushInstruction({ op: "local.set", index: ptrTmp });
978
+ this.emitExpr(r, box.wType);
979
+ this.pushInstruction({ op: "local.set", index: valTmp });
980
+
981
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
982
+ this.pushInstruction({
983
+ op: "ref.cast",
984
+ typeIndex: box.typeIndex,
985
+ typeName: box.name,
986
+ });
987
+ this.pushInstruction({ op: "local.get", index: valTmp });
988
+ this.pushInstruction({
989
+ op: "struct.set",
990
+ typeIndex: box.typeIndex,
991
+ typeName: box.name,
992
+ fieldIndex: 0,
993
+ });
994
+
995
+ this.releaseTemp(valTmp, box.wType);
996
+ this.releaseTemp(ptrTmp, ptrWType);
997
+ }
998
+
424
999
  _emitTupleAssign(lhsNodes, rhsNode, op) {
1000
+ if (op === "=" && lhsNodes.some((l) => !this._isPlainTupleTarget(l))) {
1001
+ this._emitTupleViaTemps(
1002
+ lhsNodes,
1003
+ rhsNode,
1004
+ (l, t) => this._emitAssignSingle(l, t, "="),
1005
+ true,
1006
+ );
1007
+ return;
1008
+ }
425
1009
  const tupleTypes = rhsNode._type.types;
426
1010
  const dests = [];
427
1011
  for (let i = 0; i < lhsNodes.length; i++) {
@@ -509,12 +1093,20 @@ export class StmtsEmitter {
509
1093
  this.releaseTemp(okTmp, "i32");
510
1094
  }
511
1095
 
1096
+ // Pushes a comma-ok result (map value / asserted value). An array is a
1097
+ // value: the stored one must not alias the map entry or boxed interface.
1098
+ _emitGetTempValue(tempIdx, goType) {
1099
+ this.pushInstruction({ op: "local.get", index: tempIdx });
1100
+ if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker))
1101
+ this.emitCopyArray(goType);
1102
+ }
1103
+
512
1104
  _emitStoreLhsFromTemp(lhsNode, tempIdx, wType, goType, isDefine) {
513
1105
  if (lhsNode.kind !== "Ident" || lhsNode.name === "_") return;
514
1106
  if (isDefine) {
515
1107
  const idx = this.allocLocal(lhsNode.name, wType, goType);
516
1108
  const local = this.locals.get(lhsNode.name);
517
- this.pushInstruction({ op: "local.get", index: tempIdx });
1109
+ this._emitGetTempValue(tempIdx, goType);
518
1110
  if (local?.isBoxed) {
519
1111
  this.pushInstruction({
520
1112
  op: "struct.new",
@@ -528,19 +1120,19 @@ export class StmtsEmitter {
528
1120
  if (local) {
529
1121
  if (local.isBoxed) {
530
1122
  this.pushInstruction({ op: "local.get", index: local.index });
531
- this.pushInstruction({ op: "local.get", index: tempIdx });
1123
+ this._emitGetTempValue(tempIdx, goType);
532
1124
  this.pushInstruction({
533
1125
  op: "struct.set",
534
1126
  typeIndex: local.boxInfo.typeIndex,
535
1127
  fieldIndex: 0,
536
1128
  });
537
1129
  } else {
538
- this.pushInstruction({ op: "local.get", index: tempIdx });
1130
+ this._emitGetTempValue(tempIdx, goType);
539
1131
  this.pushInstruction({ op: "local.set", index: local.index });
540
1132
  }
541
1133
  } else {
542
1134
  const global = this.mod.resolveGlobal(lhsNode.name);
543
- this.pushInstruction({ op: "local.get", index: tempIdx });
1135
+ this._emitGetTempValue(tempIdx, goType);
544
1136
  this.pushInstruction({ op: "global.set", index: global.index });
545
1137
  }
546
1138
  }
@@ -591,6 +1183,15 @@ export class StmtsEmitter {
591
1183
  return goType.name ?? null;
592
1184
  }
593
1185
 
1186
+ // The struct a type assertion target refers to; an anonymous struct type
1187
+ // has no name to look up.
1188
+ _structInfoOfType(goType) {
1189
+ return (
1190
+ this.mod.getStructType(this._structNameOf(goType)) ??
1191
+ this._resolveStructInfo({ _type: goType })
1192
+ );
1193
+ }
1194
+
594
1195
  _emitTypeTest(targetGoType) {
595
1196
  if (!targetGoType) {
596
1197
  this.pushInstruction("drop");
@@ -638,7 +1239,9 @@ export class StmtsEmitter {
638
1239
  return;
639
1240
  }
640
1241
 
641
- if (isStringType(targetGoType, this.mod.checker)) {
1242
+ // A named string type is boxed like any other scalar (see emitExpr); only
1243
+ // a plain `string` lives in `any` as the bare string.
1244
+ if (isBareStringType(targetGoType, this.mod.checker)) {
642
1245
  const isStrIdx = this.mod.getIsStringImportIndex();
643
1246
  this.pushInstruction({ op: "call", funcIndex: isStrIdx });
644
1247
  return;
@@ -646,19 +1249,16 @@ export class StmtsEmitter {
646
1249
 
647
1250
  if (isFuncType(targetGoType, this.mod.checker)) {
648
1251
  const closureInfo = this.mod.getClosureType(targetGoType);
649
- this.pushInstruction({
650
- op: "ref.test",
651
- typeIndex: closureInfo.typeIndex,
652
- });
1252
+ this._emitRefTestOrNil(targetGoType, closureInfo.typeIndex);
653
1253
  return;
654
1254
  }
655
1255
 
656
1256
  // `*T` lives in `any` as the bare struct ref; a `T` value as a boxed
657
1257
  // clone (see emitExpr). Fall through to the scalar box for values.
658
1258
  if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
659
- const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
1259
+ const sInfo = this._structInfoOfType(targetGoType);
660
1260
  if (sInfo) {
661
- this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
1261
+ this._emitRefTestOrNil(targetGoType, sInfo.typeIndex);
662
1262
  return;
663
1263
  }
664
1264
  }
@@ -681,6 +1281,17 @@ export class StmtsEmitter {
681
1281
  }
682
1282
  }
683
1283
 
1284
+ // A map lives in `any` as its bare map struct.
1285
+ if (isMapType(targetGoType, this.mod.checker)) {
1286
+ const { keyType, valType } = getMapKeyValTypes(
1287
+ targetGoType,
1288
+ this.mod.checker,
1289
+ );
1290
+ const mInfo = this.mod.getMapType(keyType, valType);
1291
+ this._emitRefTestOrNil(targetGoType, mInfo.typeIndex);
1292
+ return;
1293
+ }
1294
+
684
1295
  // Scalar types
685
1296
  const box = this.mod.getIfaceBoxType(targetGoType);
686
1297
  if (box) {
@@ -692,37 +1303,65 @@ export class StmtsEmitter {
692
1303
  this.pushInstruction({ op: "i32.const", value: 1 });
693
1304
  }
694
1305
 
1306
+ // `ref.test` for a map, func or `*T`, which also accepts the typed-nil
1307
+ // marker of the same Go type (see getNilBoxType).
1308
+ _emitRefTestOrNil(goType, typeIndex) {
1309
+ const nilBox = this.mod.getNilBoxType(goType);
1310
+ const tmp = this.acquireTemp("anyref");
1311
+ this.pushInstruction({ op: "local.set", index: tmp });
1312
+ this.pushInstruction({ op: "local.get", index: tmp });
1313
+ this.pushInstruction({ op: "ref.test", typeIndex });
1314
+ this.pushInstruction({ op: "local.get", index: tmp });
1315
+ this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
1316
+ this.pushInstruction("i32.or");
1317
+ this.releaseTemp(tmp, "anyref");
1318
+ }
1319
+
1320
+ // `ref.cast_null` to `typeIndex`, turning the typed-nil marker of `goType`
1321
+ // back into a nil value.
1322
+ _emitRefCastOrNil(goType, typeIndex) {
1323
+ const nilBox = this.mod.getNilBoxType(goType);
1324
+ const tmp = this.acquireTemp("anyref");
1325
+ this.pushInstruction({ op: "local.set", index: tmp });
1326
+ this.pushInstruction({ op: "local.get", index: tmp });
1327
+ this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
1328
+ this.pushInstruction({
1329
+ op: "if",
1330
+ blockType: { kind: "ref", nullable: true, typeIndex },
1331
+ });
1332
+ this.pushInstruction({ op: "ref.null", heapType: typeIndex });
1333
+ this.pushInstruction("else");
1334
+ this.pushInstruction({ op: "local.get", index: tmp });
1335
+ this.pushInstruction({ op: "ref.cast_null", typeIndex });
1336
+ this.pushInstruction("end");
1337
+ this.releaseTemp(tmp, "anyref");
1338
+ }
1339
+
695
1340
  _emitTypeCast(targetGoType, targetWType) {
696
1341
  if (isInterfaceType(targetGoType, this.mod.checker)) {
697
1342
  return;
698
1343
  }
699
1344
 
700
- if (isStringType(targetGoType, this.mod.checker)) {
1345
+ if (isBareStringType(targetGoType, this.mod.checker)) {
701
1346
  this.pushInstruction("extern.convert_any");
702
1347
  return;
703
1348
  }
704
1349
 
705
1350
  if (isFuncType(targetGoType, this.mod.checker)) {
706
1351
  const closureInfo = this.mod.getClosureType(targetGoType);
707
- this.pushInstruction({
708
- op: "ref.cast_null",
709
- typeIndex: closureInfo.typeIndex,
710
- });
1352
+ this._emitRefCastOrNil(targetGoType, closureInfo.typeIndex);
711
1353
  return;
712
1354
  }
713
1355
 
714
1356
  if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
715
- const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
1357
+ const sInfo = this._structInfoOfType(targetGoType);
716
1358
  if (sInfo) {
717
- this.pushInstruction({
718
- op: "ref.cast_null",
719
- typeIndex: sInfo.typeIndex,
720
- });
1359
+ this._emitRefCastOrNil(targetGoType, sInfo.typeIndex);
721
1360
  return;
722
1361
  }
723
1362
  }
724
1363
  if (isStructType(targetGoType, this.mod.checker, this.mod)) {
725
- const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
1364
+ const sInfo = this._structInfoOfType(targetGoType);
726
1365
  const box = this.mod.getIfaceBoxType(targetGoType);
727
1366
  if (sInfo && box) {
728
1367
  this._emitUnbox(box);
@@ -755,6 +1394,16 @@ export class StmtsEmitter {
755
1394
  }
756
1395
  }
757
1396
 
1397
+ if (isMapType(targetGoType, this.mod.checker)) {
1398
+ const { keyType, valType } = getMapKeyValTypes(
1399
+ targetGoType,
1400
+ this.mod.checker,
1401
+ );
1402
+ const mInfo = this.mod.getMapType(keyType, valType);
1403
+ this._emitRefCastOrNil(targetGoType, mInfo.typeIndex);
1404
+ return;
1405
+ }
1406
+
758
1407
  // Scalar types
759
1408
  const box = this.mod.getIfaceBoxType(targetGoType);
760
1409
  if (box) {
@@ -780,6 +1429,44 @@ export class StmtsEmitter {
780
1429
  const l = Array.isArray(lhs) ? lhs[0] : lhs;
781
1430
  const r = Array.isArray(rhs) ? rhs[0] : rhs;
782
1431
 
1432
+ if (isComplex(l._type)) {
1433
+ const synthBinary = {
1434
+ kind: "BinaryExpr",
1435
+ op: baseOp,
1436
+ left: l,
1437
+ right: r,
1438
+ _type: l._type,
1439
+ };
1440
+ const is64 = isComplex64(l._type);
1441
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
1442
+ const cWType = {
1443
+ kind: "ref",
1444
+ nullable: true,
1445
+ typeIndex: info.typeIndex,
1446
+ typeName: info.name,
1447
+ };
1448
+ if (l.kind === "Ident") {
1449
+ const localInfo = this.resolveLocal(l.name);
1450
+ const globalInfo = !localInfo ? this.mod.resolveGlobal(l.name) : null;
1451
+ if (localInfo?.isBoxed) {
1452
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
1453
+ this.emitComplexBinary(synthBinary, cWType);
1454
+ this.pushInstruction({
1455
+ op: "struct.set",
1456
+ typeIndex: localInfo.boxInfo.typeIndex,
1457
+ fieldIndex: 0,
1458
+ });
1459
+ } else if (localInfo) {
1460
+ this.emitComplexBinary(synthBinary, cWType);
1461
+ this.pushInstruction({ op: "local.set", index: localInfo.index });
1462
+ } else if (globalInfo) {
1463
+ this.emitComplexBinary(synthBinary, cWType);
1464
+ this.pushInstruction({ op: "global.set", index: globalInfo.index });
1465
+ }
1466
+ return;
1467
+ }
1468
+ }
1469
+
783
1470
  if (l.kind === "SelectorExpr") {
784
1471
  const structInfo = this._resolveStructInfo(l.expr);
785
1472
  if (!structInfo) {
@@ -796,9 +1483,10 @@ export class StmtsEmitter {
796
1483
  const lastStep = path[path.length - 1];
797
1484
  const targetType = lastStep.field.wType;
798
1485
  const baseWType = this.toWasmType(l.expr._type);
1486
+ const holderWType = this._fieldHolderWType(path, baseWType);
799
1487
 
800
- const baseTmp = this.acquireTemp(baseWType);
801
- this.emitExpr(l.expr, baseWType);
1488
+ const baseTmp = this.acquireTemp(holderWType);
1489
+ this.emitSelectorBase(l.expr, baseWType);
802
1490
  for (let i = 0; i < path.length - 1; i++) {
803
1491
  const step = path[i];
804
1492
  this.pushInstruction({
@@ -825,7 +1513,7 @@ export class StmtsEmitter {
825
1513
  typeIndex: lastStep.structInfo.typeIndex,
826
1514
  fieldIndex: lastStep.fieldIndex,
827
1515
  });
828
- this.releaseTemp(baseTmp, baseWType);
1516
+ this.releaseTemp(baseTmp, holderWType);
829
1517
  return;
830
1518
  }
831
1519
 
@@ -882,11 +1570,9 @@ export class StmtsEmitter {
882
1570
  baseType?.kind === "pointer" &&
883
1571
  isArrayType(baseType.base, this.mod.checker);
884
1572
 
885
- const elemGoType = isSlice
886
- ? baseType.elem
887
- : isPtrToArr
888
- ? baseType.base.elem
889
- : baseType.elem;
1573
+ const elemGoType = this._getArrayElemType(
1574
+ isPtrToArr ? baseType.base : baseType,
1575
+ );
890
1576
  const arrInfo = this.mod.getArrayType(elemGoType);
891
1577
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
892
1578
  const targetType = arrInfo.elemWType;
@@ -925,32 +1611,56 @@ export class StmtsEmitter {
925
1611
 
926
1612
  if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
927
1613
  const operand = l.operand ?? l.expr;
928
- const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
929
- if (box) {
930
- const baseWType = {
1614
+ const operandGoType =
1615
+ operand._type ??
1616
+ (operand.kind === "Ident"
1617
+ ? this.resolveLocal(operand.name)?.goType
1618
+ : null) ??
1619
+ this._resolveExprGoType(operand);
1620
+ const baseGoType = operandGoType?.base ?? operandGoType;
1621
+ if (!isScalarType(baseGoType, this.mod.checker, this.mod)) return;
1622
+ const box = this.mod.getBoxType(baseGoType);
1623
+ const ptr = this.mod.getPtrType(baseGoType);
1624
+ if (box && ptr) {
1625
+ const ptrWType = {
931
1626
  kind: "ref",
932
1627
  nullable: true,
933
- typeIndex: box.typeIndex,
1628
+ typeIndex: ptr.typeIndex,
1629
+ typeName: ptr.name,
934
1630
  };
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 });
1631
+ const targetType = box.wType;
1632
+ const ptrTmp = this.acquireTemp(ptrWType);
1633
+ this.emitExpr(operand, ptrWType);
1634
+ this.pushInstruction({ op: "local.set", index: ptrTmp });
1635
+
1636
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
1637
+ this.pushInstruction({
1638
+ op: "ref.cast",
1639
+ typeIndex: box.typeIndex,
1640
+ typeName: box.name,
1641
+ });
1642
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
1643
+ this.pushInstruction({
1644
+ op: "ref.cast",
1645
+ typeIndex: box.typeIndex,
1646
+ typeName: box.name,
1647
+ });
940
1648
  this.pushInstruction({
941
1649
  op: "struct.get",
942
1650
  typeIndex: box.typeIndex,
1651
+ typeName: box.name,
943
1652
  fieldIndex: 0,
944
1653
  });
945
- const targetType = box.wType;
946
1654
  this.emitExpr(r, targetType);
947
- this.emitBinaryOp(baseOp, targetType, l._type ?? operand._type?.base);
1655
+ this.emitBinaryOp(baseOp, targetType, l._type ?? baseGoType);
948
1656
  this.pushInstruction({
949
1657
  op: "struct.set",
950
1658
  typeIndex: box.typeIndex,
1659
+ typeName: box.name,
951
1660
  fieldIndex: 0,
952
1661
  });
953
- this.releaseTemp(boxTmp, baseWType);
1662
+
1663
+ this.releaseTemp(ptrTmp, ptrWType);
954
1664
  return;
955
1665
  }
956
1666
  }
@@ -1038,36 +1748,56 @@ export class StmtsEmitter {
1038
1748
  expr.kind === "StarExpr"
1039
1749
  ) {
1040
1750
  const operand = expr.operand ?? expr.expr;
1041
- const structInfo = this._resolveStructInfo(operand);
1751
+ const structInfo = this._derefStructInfo(operand);
1042
1752
  if (!structInfo) {
1043
- const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
1044
- if (box) {
1045
- const baseWType = {
1753
+ const operandGoType =
1754
+ operand._type ??
1755
+ (operand.kind === "Ident"
1756
+ ? this.resolveLocal(operand.name)?.goType
1757
+ : null) ??
1758
+ this._resolveExprGoType(operand);
1759
+ const baseGoType = operandGoType?.base ?? operandGoType;
1760
+ if (!isScalarType(baseGoType, this.mod.checker, this.mod)) return;
1761
+ const box = this.mod.getBoxType(baseGoType);
1762
+ const ptr = this.mod.getPtrType(baseGoType);
1763
+ if (box && ptr) {
1764
+ const ptrWType = {
1046
1765
  kind: "ref",
1047
1766
  nullable: true,
1048
- typeIndex: box.typeIndex,
1767
+ typeIndex: ptr.typeIndex,
1768
+ typeName: ptr.name,
1049
1769
  };
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 });
1770
+ const ptrTmp = this.acquireTemp(ptrWType);
1771
+ this.emitExpr(operand, ptrWType);
1772
+ this.pushInstruction({ op: "local.set", index: ptrTmp });
1773
+
1774
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
1775
+ this.pushInstruction({
1776
+ op: "ref.cast",
1777
+ typeIndex: box.typeIndex,
1778
+ typeName: box.name,
1779
+ });
1780
+ this.pushInstruction({ op: "local.get", index: ptrTmp });
1781
+ this.pushInstruction({
1782
+ op: "ref.cast",
1783
+ typeIndex: box.typeIndex,
1784
+ typeName: box.name,
1785
+ });
1055
1786
  this.pushInstruction({
1056
1787
  op: "struct.get",
1057
1788
  typeIndex: box.typeIndex,
1789
+ typeName: box.name,
1058
1790
  fieldIndex: 0,
1059
1791
  });
1060
- this._emitIncDecStep(
1061
- op,
1062
- box.wType,
1063
- operand._type?.base ?? operand._type,
1064
- );
1792
+ this._emitIncDecStep(op, box.wType, baseGoType);
1065
1793
  this.pushInstruction({
1066
1794
  op: "struct.set",
1067
1795
  typeIndex: box.typeIndex,
1796
+ typeName: box.name,
1068
1797
  fieldIndex: 0,
1069
1798
  });
1070
- this.releaseTemp(boxTmp, baseWType);
1799
+
1800
+ this.releaseTemp(ptrTmp, ptrWType);
1071
1801
  return;
1072
1802
  }
1073
1803
  }
@@ -1081,9 +1811,10 @@ export class StmtsEmitter {
1081
1811
  const lastStep = path[path.length - 1];
1082
1812
  const targetType = lastStep.field.wType;
1083
1813
  const baseWType = this.toWasmType(expr.expr._type);
1814
+ const holderWType = this._fieldHolderWType(path, baseWType);
1084
1815
 
1085
- const baseTmp = this.acquireTemp(baseWType);
1086
- this.emitExpr(expr.expr, baseWType);
1816
+ const baseTmp = this.acquireTemp(holderWType);
1817
+ this.emitSelectorBase(expr.expr, baseWType);
1087
1818
  for (let i = 0; i < path.length - 1; i++) {
1088
1819
  const step = path[i];
1089
1820
  this.pushInstruction({
@@ -1170,11 +1901,9 @@ export class StmtsEmitter {
1170
1901
  baseType?.kind === "pointer" &&
1171
1902
  isArrayType(baseType.base, this.mod.checker);
1172
1903
 
1173
- const elemGoType = isSlice
1174
- ? baseType.elem
1175
- : isPtrToArr
1176
- ? baseType.base.elem
1177
- : baseType.elem;
1904
+ const elemGoType = this._getArrayElemType(
1905
+ isPtrToArr ? baseType.base : baseType,
1906
+ );
1178
1907
  const arrInfo = this.mod.getArrayType(elemGoType);
1179
1908
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
1180
1909
  const targetType = arrInfo.elemWType;
@@ -1325,7 +2054,7 @@ export class StmtsEmitter {
1325
2054
  !this.capturedNames?.includes(lhs[0].name) &&
1326
2055
  !this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
1327
2056
  const wType = isNonEscaping ? "i32" : "i64";
1328
- const lIdx = this.allocLocal(lhs[0].name, wType, {
2057
+ const lIdx = this._allocRangeVar(lhs[0].name, wType, {
1329
2058
  kind: "basic",
1330
2059
  name: "int",
1331
2060
  });
@@ -1378,6 +2107,7 @@ export class StmtsEmitter {
1378
2107
  // Emit body
1379
2108
  const bodyBlock = stmt.body ?? stmt.block;
1380
2109
  if (bodyBlock) {
2110
+ this._emitRangeBoxes();
1381
2111
  this.emitBlock(bodyBlock);
1382
2112
  }
1383
2113
 
@@ -1425,7 +2155,7 @@ export class StmtsEmitter {
1425
2155
  let idxLocalInfo = null;
1426
2156
  if (lhs[0] && lhs[0].name !== "_") {
1427
2157
  if (!isAssign) {
1428
- const lIdx = this.allocLocal(lhs[0].name, "i64", {
2158
+ const lIdx = this._allocRangeVar(lhs[0].name, "i64", {
1429
2159
  kind: "basic",
1430
2160
  name: "int",
1431
2161
  });
@@ -1440,7 +2170,7 @@ export class StmtsEmitter {
1440
2170
  let valLocalInfo = null;
1441
2171
  if (lhs[1] && lhs[1].name !== "_") {
1442
2172
  if (!isAssign) {
1443
- const lIdx = this.allocLocal(lhs[1].name, "i32", {
2173
+ const lIdx = this._allocRangeVar(lhs[1].name, "i32", {
1444
2174
  kind: "basic",
1445
2175
  name: "rune",
1446
2176
  });
@@ -1519,6 +2249,7 @@ export class StmtsEmitter {
1519
2249
  // Body
1520
2250
  const bodyBlock = stmt.body ?? stmt.block;
1521
2251
  if (bodyBlock) {
2252
+ this._emitRangeBoxes();
1522
2253
  this.emitBlock(bodyBlock);
1523
2254
  }
1524
2255
 
@@ -1596,7 +2327,11 @@ export class StmtsEmitter {
1596
2327
  let keyLocalInfo = null;
1597
2328
  if (lhs[0] && lhs[0].name !== "_") {
1598
2329
  if (!isAssign) {
1599
- const lIdx = this.allocLocal(lhs[0].name, mapInfo.keyWType, keyType);
2330
+ const lIdx = this._allocRangeVar(
2331
+ lhs[0].name,
2332
+ mapInfo.keyWType,
2333
+ keyType,
2334
+ );
1600
2335
  keyLocalInfo = { index: lIdx, type: mapInfo.keyWType };
1601
2336
  } else {
1602
2337
  keyLocalInfo =
@@ -1608,7 +2343,11 @@ export class StmtsEmitter {
1608
2343
  let valLocalInfo = null;
1609
2344
  if (lhs[1] && lhs[1].name !== "_") {
1610
2345
  if (!isAssign) {
1611
- const lIdx = this.allocLocal(lhs[1].name, mapInfo.valWType, valType);
2346
+ const lIdx = this._allocRangeVar(
2347
+ lhs[1].name,
2348
+ mapInfo.valWType,
2349
+ valType,
2350
+ );
1612
2351
  valLocalInfo = { index: lIdx, type: mapInfo.valWType };
1613
2352
  } else {
1614
2353
  valLocalInfo =
@@ -1707,6 +2446,11 @@ export class StmtsEmitter {
1707
2446
  if (sInfo) {
1708
2447
  this.emitCloneStruct(sInfo, mapInfo.valWType);
1709
2448
  }
2449
+ } else if (
2450
+ valType?.kind !== "pointer" &&
2451
+ isArrayType(valType, this.mod.checker)
2452
+ ) {
2453
+ this.emitCopyArray(valType);
1710
2454
  }
1711
2455
  if (this.resolveLocal(lhs[1].name)) {
1712
2456
  this.pushInstruction({
@@ -1724,6 +2468,7 @@ export class StmtsEmitter {
1724
2468
  // Body
1725
2469
  const bodyBlock = stmt.body ?? stmt.block;
1726
2470
  if (bodyBlock) {
2471
+ this._emitRangeBoxes();
1727
2472
  this.emitBlock(bodyBlock);
1728
2473
  }
1729
2474
 
@@ -1768,11 +2513,9 @@ export class StmtsEmitter {
1768
2513
  iterType?.kind === "pointer" &&
1769
2514
  isArrayType(iterType.base, this.mod.checker);
1770
2515
 
1771
- const elemGoType = isSlice
1772
- ? iterType.elem
1773
- : isPtrToArr
1774
- ? iterType.base.elem
1775
- : iterType.elem;
2516
+ const elemGoType = this._getArrayElemType(
2517
+ isPtrToArr ? iterType.base : iterType,
2518
+ );
1776
2519
  const arrInfo = this.mod.getArrayType(elemGoType);
1777
2520
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
1778
2521
 
@@ -1810,6 +2553,10 @@ export class StmtsEmitter {
1810
2553
  this.releaseTemp(sliceTmp, sliceWType);
1811
2554
  } else {
1812
2555
  this.emitExpr(iterExpr, arrWType);
2556
+ // Go ranges over a copy of an array (the range expression is
2557
+ // evaluated once), so body writes to the array do not reach `v`.
2558
+ if (hasVal && !isPtrToArr)
2559
+ this._emitCopyIfValue(iterExpr, iterType, arrWType);
1813
2560
  if (hasVal) {
1814
2561
  this.pushInstruction({ op: "local.set", index: arrTmp });
1815
2562
  this.pushInstruction({ op: "i32.const", value: 0 });
@@ -1833,7 +2580,7 @@ export class StmtsEmitter {
1833
2580
  !this.capturedNames?.includes(lhs[0].name) &&
1834
2581
  !this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
1835
2582
  const wType = isNonEscaping ? "i32" : "i64";
1836
- const lIdx = this.allocLocal(lhs[0].name, wType, {
2583
+ const lIdx = this._allocRangeVar(lhs[0].name, wType, {
1837
2584
  kind: "basic",
1838
2585
  name: "int",
1839
2586
  });
@@ -1847,7 +2594,7 @@ export class StmtsEmitter {
1847
2594
  let valLocalInfo = null;
1848
2595
  if (lhs[1] && lhs[1].name !== "_") {
1849
2596
  if (!isAssign) {
1850
- const lIdx = this.allocLocal(
2597
+ const lIdx = this._allocRangeVar(
1851
2598
  lhs[1].name,
1852
2599
  arrInfo.elemWType,
1853
2600
  elemGoType,
@@ -1912,6 +2659,11 @@ export class StmtsEmitter {
1912
2659
  if (sInfo) {
1913
2660
  this.emitCloneStruct(sInfo, arrInfo.elemWType);
1914
2661
  }
2662
+ } else if (
2663
+ elemGoType?.kind !== "pointer" &&
2664
+ isArrayType(elemGoType, this.mod.checker)
2665
+ ) {
2666
+ this.emitCopyArray(elemGoType);
1915
2667
  }
1916
2668
  if (this.resolveLocal(lhs[1].name)) {
1917
2669
  this.pushInstruction({ op: "local.set", index: valLocalInfo.index });
@@ -1926,6 +2678,7 @@ export class StmtsEmitter {
1926
2678
  // Body
1927
2679
  const bodyBlock = stmt.body ?? stmt.block;
1928
2680
  if (bodyBlock) {
2681
+ this._emitRangeBoxes();
1929
2682
  this.emitBlock(bodyBlock);
1930
2683
  }
1931
2684
 
@@ -1955,7 +2708,102 @@ export class StmtsEmitter {
1955
2708
  restoreShadowed();
1956
2709
  }
1957
2710
 
2711
+ _initIterState(stmt) {
2712
+ if (!stmt._iterStateName) return;
2713
+ let stateLoc = this.resolveLocal(stmt._iterStateName);
2714
+ if (!stateLoc) {
2715
+ this.allocLocal(stmt._iterStateName, "i64", {
2716
+ kind: "basic",
2717
+ name: "int",
2718
+ });
2719
+ stateLoc = this.resolveLocal(stmt._iterStateName);
2720
+ }
2721
+ if (stateLoc?.isBoxed) {
2722
+ this.pushInstruction({ op: "i64.const", value: 0n });
2723
+ this.pushInstruction({
2724
+ op: "struct.new",
2725
+ typeIndex: stateLoc.boxInfo.typeIndex,
2726
+ });
2727
+ this.pushInstruction({ op: "local.set", index: stateLoc.index });
2728
+ }
2729
+ }
2730
+
2731
+ _emitIteratorRange(stmt, rangeExpr) {
2732
+ const funcLit = getOrCreateIteratorFuncLit(stmt);
2733
+ const iterExpr = rangeExpr.expr;
2734
+ const iterType = this._resolveExprGoType(iterExpr) ?? iterExpr._type;
2735
+ const iterInfo = this.mod.getClosureType(iterType);
2736
+ const iterWType = {
2737
+ kind: "ref",
2738
+ nullable: true,
2739
+ typeIndex: iterInfo.typeIndex,
2740
+ };
2741
+
2742
+ this._initIterState(stmt);
2743
+
2744
+ const iterTmp = this.acquireTemp(iterWType);
2745
+ this.emitExpr(iterExpr, iterWType);
2746
+ this.pushInstruction({ op: "local.set", index: iterTmp });
2747
+
2748
+ const yieldClosureInfo = this.mod.getClosureType(funcLit._type ?? funcLit);
2749
+ const yieldClosureWType = {
2750
+ kind: "ref",
2751
+ nullable: true,
2752
+ typeIndex: yieldClosureInfo.typeIndex,
2753
+ };
2754
+ const yieldTmp = this.acquireTemp(yieldClosureWType);
2755
+ this.emitFuncLit(funcLit);
2756
+ this.pushInstruction({ op: "local.set", index: yieldTmp });
2757
+
2758
+ // Outer block for break
2759
+ this.pushInstruction({ op: "block", blockType: "void" });
2760
+ this.pushControl("break", stmt.label);
2761
+
2762
+ // Push env of iterTmp (param 0)
2763
+ this.pushInstruction({ op: "local.get", index: iterTmp });
2764
+ this.pushInstruction({
2765
+ op: "struct.get",
2766
+ typeIndex: iterInfo.typeIndex,
2767
+ fieldIndex: 1,
2768
+ });
2769
+
2770
+ // Push yield callback closure (param 1)
2771
+ this.pushInstruction({ op: "local.get", index: yieldTmp });
2772
+
2773
+ // Push funcref of iterTmp
2774
+ this.pushInstruction({ op: "local.get", index: iterTmp });
2775
+ this.pushInstruction({
2776
+ op: "struct.get",
2777
+ typeIndex: iterInfo.typeIndex,
2778
+ fieldIndex: 0,
2779
+ });
2780
+
2781
+ // Call iterator via call_ref
2782
+ this.pushInstruction({
2783
+ op: "call_ref",
2784
+ typeIndex: iterInfo.funcTypeIndex,
2785
+ });
2786
+
2787
+ // If iterator returned any values, drop them
2788
+ for (let i = 0; i < (iterInfo.returnWTypes?.length ?? 0); i++) {
2789
+ this.pushInstruction("drop");
2790
+ }
2791
+
2792
+ this.pushInstruction("end");
2793
+
2794
+ this.releaseTemp(yieldTmp, yieldClosureWType);
2795
+ this.releaseTemp(iterTmp, iterWType);
2796
+
2797
+ this._emitIteratorUnwind(stmt);
2798
+ }
2799
+
1958
2800
  emitForStmt(stmt) {
2801
+ if (isIteratorRangeFor(stmt)) {
2802
+ const rangeExpr =
2803
+ stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init.rhs[0];
2804
+ this._emitIteratorRange(stmt, rangeExpr);
2805
+ return;
2806
+ }
1959
2807
  if (isRangeFor(stmt) || stmt.cond?.kind === "RangeExpr") {
1960
2808
  this.emitRangeForStmt(stmt);
1961
2809
  return;
@@ -2000,14 +2848,23 @@ export class StmtsEmitter {
2000
2848
  this.pushInstruction({ op: "block", blockType: "void" });
2001
2849
  this.pushControl("continue", stmt.label);
2002
2850
 
2851
+ // The body is its own scope: `i := i` must not rebind the loop
2852
+ // variable that the post statement increments.
2003
2853
  const bodyBlock = stmt.body ?? stmt.block;
2004
2854
  if (bodyBlock) {
2005
- this.emitBlock(bodyBlock);
2855
+ const outer = new Map(this.locals);
2856
+ try {
2857
+ this.emitBlock(bodyBlock);
2858
+ } finally {
2859
+ this.locals = outer;
2860
+ }
2006
2861
  }
2007
2862
 
2008
2863
  // End inner continue block
2009
2864
  this.pushInstruction("end");
2010
2865
 
2866
+ this._emitPerIterationBoxes(stmt);
2867
+
2011
2868
  if (iv) {
2012
2869
  this.pushInstruction({ op: "local.get", index: ivLocalInfo.index });
2013
2870
  this.pushInstruction({ op: "i32.const", value: iv.step });
@@ -2035,6 +2892,46 @@ export class StmtsEmitter {
2035
2892
  }
2036
2893
  }
2037
2894
 
2895
+ // Go 1.22 per-iteration loop variables. For each three-clause `for`
2896
+ // variable that a closure in this loop captures, give the next iteration a
2897
+ // fresh copy before the post statement: closures created in this
2898
+ // iteration keep their own variable, the post statement (and the next
2899
+ // iteration) work on the new one, which starts from the current value.
2900
+ // A boxed scalar gets a new box; a value struct or array (a ref) is also
2901
+ // cloned. Uncaptured variables stay plain locals with no allocation.
2902
+ _emitPerIterationBoxes(stmt) {
2903
+ for (const name of loopVarsCapturedInLoop(stmt)) {
2904
+ const info = this.locals.get(name);
2905
+ if (!info) continue;
2906
+ const goType = info.goType;
2907
+ const cloneValue = (wType) => {
2908
+ if (goType?.kind === "pointer") return;
2909
+ if (isArrayType(goType, this.mod.checker)) {
2910
+ this.emitCopyArray(goType);
2911
+ } else if (isStructType(goType, this.mod.checker, this.mod)) {
2912
+ const sInfo = this._resolveStructInfo({ _type: goType });
2913
+ if (sInfo) this.emitCloneStruct(sInfo, wType);
2914
+ }
2915
+ };
2916
+ this.pushInstruction({ op: "local.get", index: info.index });
2917
+ if (info.isBoxed) {
2918
+ this.pushInstruction({
2919
+ op: "struct.get",
2920
+ typeIndex: info.boxInfo.typeIndex,
2921
+ fieldIndex: 0,
2922
+ });
2923
+ cloneValue(info.boxInfo.wType);
2924
+ this.pushInstruction({
2925
+ op: "struct.new",
2926
+ typeIndex: info.boxInfo.typeIndex,
2927
+ });
2928
+ } else {
2929
+ cloneValue(info.type);
2930
+ }
2931
+ this.pushInstruction({ op: "local.set", index: info.index });
2932
+ }
2933
+ }
2934
+
2038
2935
  _detectInductionVar(stmt) {
2039
2936
  if (stmt.init?.kind !== "DefineStmt") return null;
2040
2937
  const lhs = stmt.init.lhs ?? [];
@@ -2193,7 +3090,91 @@ export class StmtsEmitter {
2193
3090
  return false;
2194
3091
  }
2195
3092
 
3093
+ // Each case clause is its own scope: a name declared in one clause must not
3094
+ // stay visible in the clauses after it.
3095
+ _emitClauseBody(stmts) {
3096
+ const outer = new Map(this.locals);
3097
+ try {
3098
+ for (const s of stmts ?? []) this.emitStmt(s);
3099
+ } finally {
3100
+ this.locals = outer;
3101
+ }
3102
+ }
3103
+
3104
+ // A switch with `fallthrough` dispatches to one block per clause, in source
3105
+ // order, so a clause that falls through simply runs into the next body.
3106
+ _emitFallthroughSwitch(stmt) {
3107
+ const getCaseVals = (c) => c.list ?? c.values ?? [];
3108
+ const cases = stmt.cases ?? [];
3109
+ const n = cases.length;
3110
+ const isFall = (s) =>
3111
+ s?.kind === "BranchStmt" && s.keyword === "fallthrough";
3112
+
3113
+ this.pushInstruction({ op: "block", blockType: "void" });
3114
+ this.pushControl("break", stmt.label);
3115
+ if (stmt.init) this.emitStmt(stmt.init);
3116
+
3117
+ let tagGoType = null;
3118
+ let tagWType = null;
3119
+ let tagTmp = -1;
3120
+ if (stmt.tag) {
3121
+ tagGoType = stmt.tag._type ?? this._resolveExprGoType(stmt.tag);
3122
+ tagWType = this.toWasmType(tagGoType);
3123
+ tagTmp = this.acquireTemp(tagWType);
3124
+ this.emitExpr(stmt.tag, tagWType);
3125
+ this.pushInstruction({ op: "local.set", index: tagTmp });
3126
+ }
3127
+ for (let i = 0; i < n; i++) {
3128
+ this.pushInstruction({ op: "block", blockType: "void" });
3129
+ }
3130
+ let defaultIdx = -1;
3131
+ for (let i = 0; i < n; i++) {
3132
+ const vals = getCaseVals(cases[i]);
3133
+ if (vals.length === 0) {
3134
+ defaultIdx = i;
3135
+ continue;
3136
+ }
3137
+ for (let j = 0; j < vals.length; j++) {
3138
+ if (stmt.tag) {
3139
+ this.pushInstruction({ op: "local.get", index: tagTmp });
3140
+ this.emitExpr(vals[j], tagWType);
3141
+ this.emitBinaryOp("==", tagWType, tagGoType);
3142
+ } else {
3143
+ this.emitExpr(vals[j], "i32");
3144
+ }
3145
+ if (j > 0) this.pushInstruction("i32.or");
3146
+ }
3147
+ // The innermost block ends the first clause, so clause i is depth i.
3148
+ this.pushInstruction({ op: "br_if", depth: i });
3149
+ }
3150
+ this.pushInstruction({ op: "br", depth: defaultIdx >= 0 ? defaultIdx : n });
3151
+ if (stmt.tag) this.releaseTemp(tagTmp, tagWType);
3152
+
3153
+ for (let i = 0; i < n; i++) {
3154
+ this.pushInstruction("end");
3155
+ const body = cases[i].stmts ?? [];
3156
+ const falls = isFall(body[body.length - 1]);
3157
+ this._emitClauseBody(falls ? body.slice(0, -1) : body);
3158
+ if (!falls && i < n - 1) {
3159
+ this.pushInstruction({ op: "br", depth: n - 1 - i });
3160
+ }
3161
+ }
3162
+ this.pushInstruction("end");
3163
+ }
3164
+
2196
3165
  emitSwitchStmt(stmt) {
3166
+ if (
3167
+ (stmt.cases ?? []).some((c) => {
3168
+ const b = c.stmts ?? [];
3169
+ return (
3170
+ b[b.length - 1]?.kind === "BranchStmt" &&
3171
+ b[b.length - 1].keyword === "fallthrough"
3172
+ );
3173
+ })
3174
+ ) {
3175
+ this._emitFallthroughSwitch(stmt);
3176
+ return;
3177
+ }
2197
3178
  this.pushInstruction({ op: "block", blockType: "void" });
2198
3179
  this.pushControl("break", stmt.label);
2199
3180
 
@@ -2234,12 +3215,12 @@ export class StmtsEmitter {
2234
3215
  this.pushInstruction({ op: "if", blockType: "void" });
2235
3216
  ifCount++;
2236
3217
 
2237
- for (const s of c.stmts ?? []) this.emitStmt(s);
3218
+ this._emitClauseBody(c.stmts);
2238
3219
  this.pushInstruction("else");
2239
3220
  }
2240
3221
 
2241
3222
  if (defaultCase) {
2242
- for (const s of defaultCase.stmts ?? []) this.emitStmt(s);
3223
+ this._emitClauseBody(defaultCase.stmts);
2243
3224
  }
2244
3225
 
2245
3226
  for (let i = 0; i < ifCount; i++) {
@@ -2285,8 +3266,13 @@ export class StmtsEmitter {
2285
3266
  ifCount++;
2286
3267
 
2287
3268
  if (stmt.assign) {
2288
- const targetGoType =
2289
- types.length === 1
3269
+ // `case nil` binds the switch expression's own (null) value.
3270
+ const isNilCase =
3271
+ types.length === 1 &&
3272
+ (types[0].name === "nil" || types[0]._type?.name === "nil");
3273
+ const targetGoType = isNilCase
3274
+ ? (stmt.expr._type ?? { kind: "basic", name: "any" })
3275
+ : types.length === 1
2290
3276
  ? (types[0]._type ?? types[0])
2291
3277
  : { kind: "basic", name: "any" };
2292
3278
  const targetWType = this.toWasmType(targetGoType);
@@ -2295,18 +3281,22 @@ export class StmtsEmitter {
2295
3281
  targetWType,
2296
3282
  targetGoType,
2297
3283
  );
2298
- if (types.length === 1) {
3284
+ if (types.length === 1 && !isNilCase) {
2299
3285
  this.pushInstruction({ op: "local.get", index: tswTmp });
2300
3286
  this._emitTypeCast(targetGoType, targetWType);
3287
+ // The bound array is a copy of the boxed value.
3288
+ if (
3289
+ targetGoType?.kind !== "pointer" &&
3290
+ isArrayType(targetGoType, this.mod.checker)
3291
+ )
3292
+ this.emitCopyArray(targetGoType);
2301
3293
  } else {
2302
3294
  this.pushInstruction({ op: "local.get", index: tswTmp });
2303
3295
  }
2304
3296
  this.pushInstruction({ op: "local.set", index: assignLocal });
2305
3297
  }
2306
3298
 
2307
- for (const s of c.stmts ?? []) {
2308
- this.emitStmt(s);
2309
- }
3299
+ this._emitClauseBody(c.stmts);
2310
3300
 
2311
3301
  this.pushInstruction("else");
2312
3302
  }
@@ -2320,9 +3310,7 @@ export class StmtsEmitter {
2320
3310
  this.pushInstruction({ op: "local.get", index: tswTmp });
2321
3311
  this.pushInstruction({ op: "local.set", index: assignLocal });
2322
3312
  }
2323
- for (const s of defaultCase.stmts ?? []) {
2324
- this.emitStmt(s);
2325
- }
3313
+ this._emitClauseBody(defaultCase.stmts);
2326
3314
  }
2327
3315
 
2328
3316
  for (let i = 0; i < ifCount; i++) {
@@ -2340,13 +3328,199 @@ export class StmtsEmitter {
2340
3328
  this.pushInstruction("end");
2341
3329
  }
2342
3330
 
3331
+ _propagateYieldExit(tag) {
3332
+ if (this.funcDecl._iterStateName) {
3333
+ const outerLoc = this.resolveLocal(this.funcDecl._iterStateName);
3334
+ if (outerLoc?.isBoxed) {
3335
+ this.pushInstruction({ op: "local.get", index: outerLoc.index });
3336
+ this.pushInstruction({ op: "i64.const", value: tag });
3337
+ this.pushInstruction({
3338
+ op: "struct.set",
3339
+ typeIndex: outerLoc.boxInfo.typeIndex,
3340
+ fieldIndex: 0,
3341
+ });
3342
+ }
3343
+ }
3344
+ this.pushInstruction({ op: "i32.const", value: 0 });
3345
+ this.pushInstruction({ op: "return" });
3346
+ }
3347
+
3348
+ _unwindIteratorReturn() {
3349
+ if (this.funcDecl._isYieldClosure) {
3350
+ this._propagateYieldExit(2n);
3351
+ return;
3352
+ }
3353
+ if (this.hasDefer) {
3354
+ const depth = this.resolveBranchDepthToRole("runDefers");
3355
+ this.pushInstruction({ op: "br", depth });
3356
+ return;
3357
+ }
3358
+ const rootNamed = this.funcDecl.returnType?._namedReturns ?? [];
3359
+ for (const nr of rootNamed) {
3360
+ const loc = this.locals.get(nr.name);
3361
+ if (loc?.isBoxed) {
3362
+ this.pushInstruction({ op: "local.get", index: loc.index });
3363
+ this.pushInstruction({
3364
+ op: "struct.get",
3365
+ typeIndex: loc.boxInfo.typeIndex,
3366
+ fieldIndex: 0,
3367
+ });
3368
+ } else if (loc) {
3369
+ this.pushInstruction({ op: "local.get", index: loc.index });
3370
+ }
3371
+ }
3372
+ this.pushInstruction({ op: "return" });
3373
+ }
3374
+
3375
+ _unwindIteratorTargets(stmt, stateTmp, labelTargets) {
3376
+ for (const target of labelTargets) {
3377
+ this.pushInstruction({ op: "local.get", index: stateTmp });
3378
+ this.pushInstruction({ op: "i64.const", value: BigInt(target.tag) });
3379
+ this.pushInstruction("i64.eq");
3380
+ this.pushInstruction({ op: "if", blockType: "void" });
3381
+ if (target.role !== "break" || stmt.label !== target.label) {
3382
+ if (this.funcDecl._isYieldClosure) {
3383
+ this._propagateYieldExit(BigInt(target.tag));
3384
+ } else {
3385
+ const depth = this.resolveBranchDepth(
3386
+ target.label,
3387
+ target.role === "continue",
3388
+ );
3389
+ this.pushInstruction({ op: "br", depth });
3390
+ }
3391
+ }
3392
+ this.pushInstruction("end");
3393
+ }
3394
+ }
3395
+
3396
+ _emitIteratorUnwind(stmt) {
3397
+ if (!stmt._iterStateName) return;
3398
+ const stateLoc = this.resolveLocal(stmt._iterStateName);
3399
+ if (!stateLoc?.isBoxed) return;
3400
+
3401
+ const funcLit = getOrCreateIteratorFuncLit(stmt);
3402
+ const hasRet = hasReturnInBlock(funcLit?.body);
3403
+ const labelTargets = Array.from(
3404
+ this.rootFuncDecl?._labelTargets?.values() ?? [],
3405
+ );
3406
+ if (!hasRet && labelTargets.length === 0) return;
3407
+
3408
+ const stateTmp = this.acquireTemp("i64");
3409
+ this.pushInstruction({ op: "local.get", index: stateLoc.index });
3410
+ this.pushInstruction({
3411
+ op: "struct.get",
3412
+ typeIndex: stateLoc.boxInfo.typeIndex,
3413
+ fieldIndex: 0,
3414
+ });
3415
+ this.pushInstruction({ op: "local.set", index: stateTmp });
3416
+
3417
+ if (hasRet) {
3418
+ this.pushInstruction({ op: "local.get", index: stateTmp });
3419
+ this.pushInstruction({ op: "i64.const", value: 2n });
3420
+ this.pushInstruction("i64.eq");
3421
+ this.pushInstruction({ op: "if", blockType: "void" });
3422
+ this._unwindIteratorReturn();
3423
+ this.pushInstruction("end");
3424
+ }
3425
+
3426
+ this._unwindIteratorTargets(stmt, stateTmp, labelTargets);
3427
+ this.releaseTemp(stateTmp, "i64");
3428
+ }
3429
+
3430
+ _tryEmitYieldBranch(stmt) {
3431
+ if (!this.funcDecl._isYieldClosure) return false;
3432
+ const isContinue = stmt.keyword === "continue";
3433
+ const targetRole = isContinue ? "continue" : "break";
3434
+ const hasInnerTarget = this.controlStack.some((c) =>
3435
+ stmt.label
3436
+ ? c.label === stmt.label && c.role === targetRole
3437
+ : c.role === targetRole,
3438
+ );
3439
+ if (hasInnerTarget) return false;
3440
+
3441
+ if (!stmt.label) {
3442
+ if (stmt.keyword === "break") {
3443
+ this._propagateYieldExit(1n);
3444
+ return true;
3445
+ }
3446
+ if (stmt.keyword === "continue") {
3447
+ this.pushInstruction({ op: "i32.const", value: 1 });
3448
+ this.pushInstruction({ op: "return" });
3449
+ return true;
3450
+ }
3451
+ return false;
3452
+ }
3453
+
3454
+ if (
3455
+ stmt.keyword === "continue" &&
3456
+ stmt.label === this.funcDecl._forStmt?.label
3457
+ ) {
3458
+ this.pushInstruction({ op: "i32.const", value: 1 });
3459
+ this.pushInstruction({ op: "return" });
3460
+ return true;
3461
+ }
3462
+
3463
+ const target = this._findLabelTarget(stmt.label, targetRole);
3464
+ const tag = target ? BigInt(target.tag) : 1n;
3465
+ this._propagateYieldExit(tag);
3466
+ return true;
3467
+ }
3468
+
3469
+ _emitYieldReturn(stmt) {
3470
+ const values = stmt.values ?? [];
3471
+ const rootNamed = this.rootFuncDecl?.returnType?._namedReturns ?? [];
3472
+ if (values.length === 1 && rootNamed.length > 1) {
3473
+ this.emitExpr(values[0]);
3474
+ for (let i = rootNamed.length - 1; i >= 0; i--) {
3475
+ this._assignToNamedReturnVar(rootNamed[i]);
3476
+ }
3477
+ } else if (values.length > 0 && rootNamed.length > 0) {
3478
+ const temps = [];
3479
+ for (let i = 0; i < values.length; i++) {
3480
+ const val = values[i];
3481
+ const nr = rootNamed[i];
3482
+ const targetWType = this.toWasmType(nr?.type ?? val._type);
3483
+ this.emitExpr(val, targetWType);
3484
+ this._emitCopyReturnValue(val, targetWType);
3485
+ const tmp = this.acquireTemp(targetWType);
3486
+ this.pushInstruction({ op: "local.set", index: tmp });
3487
+ temps.push({ tmp, type: targetWType });
3488
+ }
3489
+ for (let i = 0; i < temps.length; i++) {
3490
+ this.pushInstruction({ op: "local.get", index: temps[i].tmp });
3491
+ this._assignToNamedReturnVar(rootNamed[i]);
3492
+ this.releaseTemp(temps[i].tmp, temps[i].type);
3493
+ }
3494
+ }
3495
+
3496
+ if (this.funcDecl._iterStateName) {
3497
+ const stateLoc = this.resolveLocal(this.funcDecl._iterStateName);
3498
+ if (stateLoc?.isBoxed) {
3499
+ this.pushInstruction({ op: "local.get", index: stateLoc.index });
3500
+ this.pushInstruction({ op: "i64.const", value: 2n });
3501
+ this.pushInstruction({
3502
+ op: "struct.set",
3503
+ typeIndex: stateLoc.boxInfo.typeIndex,
3504
+ fieldIndex: 0,
3505
+ });
3506
+ }
3507
+ }
3508
+ this.pushInstruction({ op: "i32.const", value: 0 });
3509
+ this.pushInstruction({ op: "return" });
3510
+ }
3511
+
2343
3512
  emitBranchStmt(stmt) {
3513
+ if (this._tryEmitYieldBranch(stmt)) return;
2344
3514
  const isContinue = stmt.keyword === "continue";
2345
3515
  const depth = this.resolveBranchDepth(stmt.label, isContinue);
2346
3516
  this.pushInstruction({ op: "br", depth });
2347
3517
  }
2348
3518
 
2349
3519
  emitReturnStmt(stmt) {
3520
+ if (this.funcDecl._isYieldClosure) {
3521
+ this._emitYieldReturn(stmt);
3522
+ return;
3523
+ }
2350
3524
  const values = stmt.values ?? [];
2351
3525
  if (this.hasDefer) {
2352
3526
  if (this.namedReturnVars?.length > 0) {
@@ -2365,7 +3539,7 @@ export class StmtsEmitter {
2365
3539
  const val = values[i];
2366
3540
  const targetWType = this.returnTypes[i] ?? null;
2367
3541
  this.emitExpr(val, targetWType);
2368
- this._emitCopyIfValueStruct(val, val._type, targetWType);
3542
+ this._emitCopyReturnValue(val, targetWType);
2369
3543
  const tmp = this.acquireTemp(targetWType);
2370
3544
  this.pushInstruction({ op: "local.set", index: tmp });
2371
3545
  temps.push({ tmp, type: targetWType });
@@ -2390,7 +3564,7 @@ export class StmtsEmitter {
2390
3564
  const val = values[i];
2391
3565
  const targetWType = this.returnTypes[i] ?? null;
2392
3566
  this.emitExpr(val, targetWType);
2393
- this._emitCopyIfValueStruct(val, val._type, targetWType);
3567
+ this._emitCopyReturnValue(val, targetWType);
2394
3568
  this.pushInstruction({
2395
3569
  op: "local.set",
2396
3570
  index: this.returnTempLocals[i],
@@ -2421,17 +3595,38 @@ export class StmtsEmitter {
2421
3595
  return;
2422
3596
  }
2423
3597
 
3598
+ if (values.length === 1 && this.returnTypes.length > 1) {
3599
+ this.emitExpr(values[0]);
3600
+ this.pushInstruction("return");
3601
+ return;
3602
+ }
3603
+
2424
3604
  for (let i = 0; i < values.length; i++) {
2425
3605
  const val = values[i];
2426
3606
  const targetWType = this.returnTypes[i] ?? null;
2427
3607
  this.emitExpr(val, targetWType);
2428
- this._emitCopyIfValueStruct(val, val._type, targetWType);
3608
+ this._emitCopyReturnValue(val, targetWType);
2429
3609
  }
2430
3610
  this.pushInstruction("return");
2431
3611
  }
2432
3612
 
3613
+ // A value returned as an interface was copied when it was boxed; copying it
3614
+ // again by its own type would treat the box as the struct.
3615
+ _emitCopyReturnValue(val, targetWType) {
3616
+ if (targetWType === "anyref") return;
3617
+ this._emitCopyIfValue(val, val._type, targetWType);
3618
+ }
3619
+
2433
3620
  _assignToNamedReturnVar(nr) {
3621
+ if (!nr?.name || nr.name === "_") {
3622
+ this.pushInstruction("drop");
3623
+ return;
3624
+ }
2434
3625
  const loc = this.locals.get(nr.name);
3626
+ if (!loc) {
3627
+ this.pushInstruction("drop");
3628
+ return;
3629
+ }
2435
3630
  if (loc.isBoxed) {
2436
3631
  const valTmp = this.acquireTemp(loc.boxInfo.wType);
2437
3632
  this.pushInstruction({ op: "local.set", index: valTmp });