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
@@ -9,17 +9,12 @@ import { ExprsEmitter } from "./emit-exprs.js";
9
9
  import { MapsEmitter } from "./emit-maps.js";
10
10
  import { SharedEmitter } from "./emit-shared.js";
11
11
  import { StmtsEmitter } from "./emit-stmts.js";
12
- import {
13
- BytesEmitter,
14
- ExtendedPackagesEmitter,
15
- IoEmitter,
16
- PurePackagesEmitter,
17
- SimdEmitter,
18
- StdlibCoreEmitter,
19
- TimeEmitter,
20
- } from "./stdlib/index.js";
12
+ import { stdlibEmitters } from "./stdlib/index.js";
21
13
  import {
22
14
  isArrayType,
15
+ isComplex,
16
+ isComplex64,
17
+ isFuncType,
23
18
  isPointerToStruct,
24
19
  isSliceType,
25
20
  isStringType,
@@ -30,6 +25,48 @@ import {
30
25
  // The emitter is one class split over several files by concern. Methods from
31
26
  // the emit-*.js modules are assigned to FunctionEmitter.prototype below; `this`
32
27
  // is shared, so cross-file calls are plain method calls.
28
+ const SCOPED_STMTS = new Set([
29
+ "Block",
30
+ "IfStmt",
31
+ "ForStmt",
32
+ "SwitchStmt",
33
+ "TypeSwitchStmt",
34
+ ]);
35
+
36
+ // A same-named method with another arity (strings.Reader.Reset(s) vs
37
+ // hash.Hash.Reset()) cannot satisfy this interface method. Neither can one
38
+ // whose parameters or results have other types (csv.Writer.Write(record
39
+ // []string) error vs io.Writer.Write(p []byte) (int, error)).
40
+ function sameTypes(a, b) {
41
+ return JSON.stringify(a) === JSON.stringify(b);
42
+ }
43
+
44
+ function sameParams(c, params, paramTypes) {
45
+ if (c.targetParamTypes.length !== params.length) return false;
46
+ if (paramTypes?.length !== params.length) return true;
47
+ return c.targetParamTypes.every(
48
+ (t, i) => i === 0 || sameTypes(t, paramTypes[i]),
49
+ );
50
+ }
51
+
52
+ function sameSignature(c, params, paramTypes, want, got) {
53
+ if (!Array.isArray(c.targetParamTypes)) return true;
54
+ if (!sameParams(c, params, paramTypes)) return false;
55
+ return want === undefined || got === undefined || sameTypes(got, want);
56
+ }
57
+
58
+ // True when a struct type embeds a pointer, directly or through an embedded
59
+ // struct: promoted method calls read through it.
60
+ function _embedsPointer(t, seen = new Set()) {
61
+ const base = t?.kind === "pointer" ? t.base : t;
62
+ const st = base?.underlying ?? base;
63
+ if (!st || seen.has(st)) return false;
64
+ seen.add(st);
65
+ return (st._embeds ?? []).some(
66
+ (e) => e.kind === "pointer" || _embedsPointer(e, seen),
67
+ );
68
+ }
69
+
33
70
  export class FunctionEmitter {
34
71
  constructor(moduleEmitter, funcDecl, funcIndex) {
35
72
  this.mod = moduleEmitter;
@@ -118,7 +155,75 @@ export class FunctionEmitter {
118
155
  this.recoverOkLocalIndex = local;
119
156
  }
120
157
 
158
+ _checkYieldClosureReentry() {
159
+ if (!this.funcDecl._isYieldClosure || !this.funcDecl._iterStateName) return;
160
+ const stateLoc = this.resolveLocal(this.funcDecl._iterStateName);
161
+ if (!stateLoc?.isBoxed) return;
162
+ this.pushInstruction({ op: "local.get", index: stateLoc.index });
163
+ this.pushInstruction({
164
+ op: "struct.get",
165
+ typeIndex: stateLoc.boxInfo.typeIndex,
166
+ fieldIndex: 0,
167
+ });
168
+ this.pushInstruction({ op: "i64.const", value: 0n });
169
+ this.pushInstruction("i64.ne");
170
+ this.pushInstruction({ op: "if", blockType: "void" });
171
+ this.emitPanic(
172
+ "range function continued iteration after function for loop body returned false",
173
+ );
174
+ this.pushInstruction("end");
175
+ }
176
+
177
+ _finishYieldClosureBody() {
178
+ if (!this.funcDecl._isYieldClosure) return false;
179
+ const lastOp = this.body[this.body.length - 1]?.op;
180
+ if (lastOp !== "return" && lastOp !== "unreachable") {
181
+ this.pushInstruction({ op: "i32.const", value: 1 });
182
+ this.pushInstruction({ op: "return" });
183
+ }
184
+ return true;
185
+ }
186
+
187
+ _collectLabelTargets(body) {
188
+ const targets = new Map();
189
+ let nextTag = 3;
190
+ const scan = (node) => {
191
+ if (!node || typeof node !== "object") return;
192
+ if (Array.isArray(node)) {
193
+ for (const item of node) scan(item);
194
+ return;
195
+ }
196
+ if (node.kind === "LabeledStmt" && node.label) {
197
+ const bTag = nextTag++;
198
+ const cTag = nextTag++;
199
+ targets.set(bTag, { tag: bTag, label: node.label, role: "break" });
200
+ targets.set(cTag, { tag: cTag, label: node.label, role: "continue" });
201
+ } else if (node.kind === "ForStmt" && node.label) {
202
+ const bTag = nextTag++;
203
+ const cTag = nextTag++;
204
+ targets.set(bTag, { tag: bTag, label: node.label, role: "break" });
205
+ targets.set(cTag, { tag: cTag, label: node.label, role: "continue" });
206
+ }
207
+ for (const k of Object.keys(node)) {
208
+ if (k.startsWith("_")) continue;
209
+ scan(node[k]);
210
+ }
211
+ };
212
+ scan(body);
213
+ return targets;
214
+ }
215
+
216
+ _findLabelTarget(label, role) {
217
+ for (const target of this.rootFuncDecl?._labelTargets?.values() ?? []) {
218
+ if (target.label === label && target.role === role) return target;
219
+ }
220
+ return null;
221
+ }
222
+
121
223
  emitFunctionBody(body) {
224
+ if (!this.funcDecl._isClosure && !this.funcDecl._labelTargets) {
225
+ this.funcDecl._labelTargets = this._collectLabelTargets(body);
226
+ }
122
227
  if (
123
228
  this.funcDecl._isClosure &&
124
229
  !this.funcDecl._isTrampoline &&
@@ -132,6 +237,7 @@ export class FunctionEmitter {
132
237
  this._cacheScratchGlobals();
133
238
 
134
239
  this._emitRecoverArmPrologue(body);
240
+ this._checkYieldClosureReentry();
135
241
 
136
242
  const hasDef = hasDefer(body);
137
243
  this.hasDefer = hasDef;
@@ -143,31 +249,139 @@ export class FunctionEmitter {
143
249
 
144
250
  this._initNamedReturns();
145
251
 
146
- const needsOobBlock = this._needsOobBlock(body);
147
- if (needsOobBlock) {
148
- this.pushInstruction({ op: "block", blockType: "void" });
149
- this.pushControl("oob");
150
- }
252
+ const closeChecks = this._openCheckBlocks(body);
151
253
 
152
254
  this.emitBlock(body);
255
+ if (this._finishYieldClosureBody()) {
256
+ closeChecks?.(() => {});
257
+ return;
258
+ }
153
259
 
154
- if (needsOobBlock) {
260
+ closeChecks?.(() =>
261
+ this.pushInstruction({
262
+ op: this.returnTypes.length === 0 ? "return" : "unreachable",
263
+ }),
264
+ );
265
+ }
266
+
267
+ // Opens the nil and index-check blocks `body` needs and returns the
268
+ // function that closes them (null when none are needed). The closer runs
269
+ // `terminate` first unless the body already ends in a return or trap.
270
+ _openCheckBlocks(body) {
271
+ const nil = this._needsNilBlock(body);
272
+ const oob = this._needsOobBlock(body);
273
+ if (!nil && !oob) return null;
274
+ if (nil) this._openNilBlock();
275
+ if (oob) this._openOobBlocks();
276
+ return (terminate) => {
155
277
  const lastOp = this.body[this.body.length - 1]?.op;
156
- if (lastOp !== "return" && lastOp !== "unreachable") {
157
- if (this.returnTypes.length === 0) {
158
- this.pushInstruction({ op: "return" });
159
- } else {
160
- this.pushInstruction({ op: "unreachable" });
161
- }
162
- }
163
- this.pushInstruction("end");
278
+ if (lastOp !== "return" && lastOp !== "unreachable") terminate();
279
+ if (oob) this._closeOobBlocks();
280
+ if (nil) this._closeNilBlock();
281
+ };
282
+ }
283
+
284
+ // Field access through a nil struct pointer branches (`br_on_null`) out
285
+ // of the body to a block wrapped around it, whose handler raises Go's
286
+ // panic, so `recover()` sees it where a trap would skip the defers.
287
+ _openNilBlock() {
288
+ this.pushInstruction({ op: "block", blockType: "void" });
289
+ this.pushControl("nil");
290
+ }
164
291
 
292
+ // Closes the block opened by _openNilBlock; the body must already end in
293
+ // a branch, return or unreachable.
294
+ _closeNilBlock() {
295
+ this.pushInstruction("end");
296
+ this.pushInstruction({
297
+ op: "call",
298
+ funcIndex: this.mod.getNilPanicFuncIndex(),
299
+ });
300
+ this.pushInstruction("unreachable");
301
+ }
302
+
303
+ // Index checks branch out of the body to one of two blocks wrapped around
304
+ // it, `oob32` for i32 indexes and `oob64` for i64 indexes, carrying the
305
+ // index and the check's site id as block results (`_pushOobSite`). The
306
+ // handlers after the blocks recompute that site's length and raise Go's
307
+ // `index out of range [i] with length n`. Nothing extra stays live in
308
+ // the hot path: a site id is an immediate.
309
+ _openOobBlocks() {
310
+ this.pushInstruction({
311
+ op: "block",
312
+ blockType: this.mod.getTypeIndex([], ["i64", "i32"]),
313
+ });
314
+ this.pushControl("oob64");
315
+ this.pushInstruction({
316
+ op: "block",
317
+ blockType: this.mod.getTypeIndex([], ["i32", "i32"]),
318
+ });
319
+ this.pushControl("oob32");
320
+ }
321
+
322
+ // Closes the blocks opened by _openOobBlocks; the body must already end
323
+ // in a branch, return or unreachable.
324
+ _closeOobBlocks() {
325
+ const site = this.acquireTemp("i32");
326
+ const idx = this.acquireTemp("i64");
327
+ const len = this.acquireTemp("i32");
328
+ // oob32 lands here with [i32 index, site]: widen the index and fall
329
+ // through to the oob64 handler.
330
+ this.pushInstruction("end");
331
+ this.pushInstruction({ op: "local.set", index: site });
332
+ this.pushInstruction("i64.extend_i32_s");
333
+ this.pushInstruction({ op: "local.get", index: site });
334
+ this.pushInstruction("end");
335
+ this.pushInstruction({ op: "local.set", index: site });
336
+ this.pushInstruction({ op: "local.set", index: idx });
337
+ // Sites whose length code is identical (same temp, same field) share
338
+ // one br_table target.
339
+ const codes = [];
340
+ const target = (this.oobSites ?? []).map((emitLen) => {
341
+ const saved = this.body;
342
+ this.body = [];
343
+ emitLen();
344
+ const code = this.body;
345
+ this.body = saved;
346
+ const key = JSON.stringify(code);
347
+ let k = codes.findIndex((c) => c.key === key);
348
+ if (k < 0) k = codes.push({ key, code }) - 1;
349
+ return k;
350
+ });
351
+ const sites = codes.map(({ code }) => () => {
352
+ for (const inst of code) this.pushInstruction(inst);
353
+ });
354
+ if (sites.length === 0) {
355
+ this.pushInstruction({ op: "i32.const", value: 0 });
356
+ } else {
357
+ // block $len (result i32) { block … block { br_table } emitLen_0; br $len … }
358
+ this.pushInstruction({ op: "block", blockType: "i32" });
359
+ for (let k = 0; k < sites.length; k++)
360
+ this.pushInstruction({ op: "block", blockType: "void" });
361
+ this.pushInstruction({ op: "local.get", index: site });
165
362
  this.pushInstruction({
166
- op: "call",
167
- funcIndex: this.mod.getBoundsPanicFuncIndex(),
363
+ op: "br_table",
364
+ targets: target,
365
+ defaultTarget: sites.length - 1,
168
366
  });
169
- this.pushInstruction("unreachable");
367
+ for (let k = 0; k < sites.length; k++) {
368
+ this.pushInstruction("end");
369
+ sites[k]();
370
+ this.pushInstruction({ op: "br", depth: sites.length - 1 - k });
371
+ }
372
+ this.pushInstruction("end");
170
373
  }
374
+ this.pushInstruction({ op: "local.set", index: len });
375
+ this.pushInstruction({ op: "local.get", index: idx });
376
+ this.pushInstruction({ op: "local.get", index: len });
377
+ this.pushInstruction({
378
+ op: "call",
379
+ funcIndex: this.mod.getIndexPanicFuncIndex(),
380
+ });
381
+ this.pushInstruction("unreachable");
382
+ this.releaseTemp(site, "i32");
383
+ this.releaseTemp(idx, "i64");
384
+ this.releaseTemp(len, "i32");
171
385
  }
172
386
 
173
387
  _initNamedReturns() {
@@ -180,13 +394,6 @@ export class FunctionEmitter {
180
394
  const varName = isBlank ? `__ret_blank$${i}` : r.name;
181
395
  const wType = this.toWasmType(r.type);
182
396
  const localIdx = this.allocLocal(varName, wType, r.type, isBlank);
183
- this.namedReturnVars.push({
184
- name: varName,
185
- localIdx,
186
- type: wType,
187
- goType: r.type,
188
- isBlank,
189
- });
190
397
  this.emitZeroValue(r.type, wType);
191
398
  const localInfo = this.locals.get(varName);
192
399
  if (localInfo?.isBoxed) {
@@ -196,6 +403,13 @@ export class FunctionEmitter {
196
403
  });
197
404
  }
198
405
  this.pushInstruction({ op: "local.set", index: localIdx });
406
+ this.namedReturnVars.push({
407
+ name: varName,
408
+ localIdx,
409
+ type: wType,
410
+ goType: r.type,
411
+ isBlank,
412
+ });
199
413
  }
200
414
  return true;
201
415
  }
@@ -256,30 +470,16 @@ export class FunctionEmitter {
256
470
  });
257
471
  this.pushControl("try_table");
258
472
 
259
- const needsOobBlock = this._needsOobBlock(body);
260
- if (needsOobBlock) {
261
- this.pushInstruction({ op: "block", blockType: "void" });
262
- this.pushControl("oob");
263
- }
473
+ const closeChecks = this._openCheckBlocks(body);
264
474
 
265
475
  this.emitBlock(body);
266
476
 
267
- if (needsOobBlock) {
268
- const lastOp = this.body[this.body.length - 1]?.op;
269
- if (lastOp !== "return" && lastOp !== "unreachable") {
270
- this.pushInstruction({
271
- op: "br",
272
- depth: this.resolveBranchDepthToRole("runDefers"),
273
- });
274
- }
275
- this.pushInstruction("end");
276
-
477
+ closeChecks?.(() =>
277
478
  this.pushInstruction({
278
- op: "call",
279
- funcIndex: this.mod.getBoundsPanicFuncIndex(),
280
- });
281
- this.pushInstruction("unreachable");
282
- }
479
+ op: "br",
480
+ depth: this.resolveBranchDepthToRole("runDefers"),
481
+ }),
482
+ );
283
483
 
284
484
  // Close try_table
285
485
  this.pushInstruction("end");
@@ -575,6 +775,67 @@ export class FunctionEmitter {
575
775
  return false;
576
776
  }
577
777
 
778
+ // True when the body reads or writes a field or the target of a pointer.
779
+ _needsNilBlock(node) {
780
+ if (!node || typeof node !== "object") return false;
781
+ if (Array.isArray(node)) return node.some((n) => this._needsNilBlock(n));
782
+ if (node.kind === "FuncLit") return false;
783
+ if (node.kind === "SelectorExpr" && node.expr?._type?.kind === "pointer")
784
+ return true;
785
+ if (this._isNilTrapSite(node)) return true;
786
+ for (const key of Object.keys(node)) {
787
+ if (key.startsWith("_")) continue;
788
+ if (this._needsNilBlock(node[key])) return true;
789
+ }
790
+ return false;
791
+ }
792
+
793
+ // A pointer dereference or a call through a func value: sites whose nil
794
+ // operand raises Go's panic through the `nil` block.
795
+ _isNilTrapSite(node) {
796
+ if (node.kind === "CallExpr")
797
+ return (
798
+ this._isFuncValueCall(node) ||
799
+ (node.func?.kind === "SelectorExpr" &&
800
+ _embedsPointer(node.func.expr?._type))
801
+ );
802
+ // Binding a method value on an interface checks it for nil.
803
+ if (node.kind === "NilCheckStmt") return true;
804
+ // A pointer to an array: indexing, slicing and ranging with a value
805
+ // read the array.
806
+ if (
807
+ node.kind === "IndexExpr" ||
808
+ node.kind === "SliceExpr" ||
809
+ node.kind === "RangeExpr"
810
+ )
811
+ return node.expr?._type?.kind === "pointer";
812
+ const star =
813
+ (node.kind === "UnaryExpr" && node.op === "*") ||
814
+ node.kind === "StarExpr";
815
+ return star && (node.operand ?? node.expr)?._type?.kind === "pointer";
816
+ }
817
+
818
+ // A call through a func variable, parameter or expression (not a declared
819
+ // function or a method), which panics when the value is nil.
820
+ _isFuncValueCall(call) {
821
+ const f = call.func;
822
+ if (!f || call._isTypeConversion) return false;
823
+ if (f.kind === "Ident")
824
+ return (
825
+ isFuncType(f._type, this.mod.checker) &&
826
+ this.mod.resolveFuncIndex(f.name) === null
827
+ );
828
+ if (f.kind === "SelectorExpr") {
829
+ const t = f.expr?._type;
830
+ const base = t?.kind === "pointer" ? t.base : t;
831
+ const fields = (base?.underlying ?? base)?.fields;
832
+ return fields instanceof Map && fields.has(f.field);
833
+ }
834
+ return (
835
+ f.kind === "IndexExpr" || f.kind === "CallExpr" || f.kind === "ParenExpr"
836
+ );
837
+ }
838
+
578
839
  _needsOobBlock(node) {
579
840
  if (!node || typeof node !== "object") return false;
580
841
  if (Array.isArray(node)) {
@@ -640,6 +901,10 @@ export class FunctionEmitter {
640
901
  for (let i = 0; i < lhs.length; i++) {
641
902
  if (lhs[i].name === varName) {
642
903
  if (lhs[i]._type) return lhs[i]._type;
904
+ // `for k, v := range x`: the range expression is a tuple.
905
+ const rt = node.rhs?.[0]?._type;
906
+ if (node.rhs?.[0]?.kind === "RangeExpr" && rt?.kind === "tuple")
907
+ return rt.types?.[i] ?? null;
643
908
  if (node.rhs?.[i]?._type) return node.rhs[i]._type;
644
909
  }
645
910
  }
@@ -655,11 +920,12 @@ export class FunctionEmitter {
655
920
  return null;
656
921
  }
657
922
 
658
- _buildEnvFields(capturedNames) {
923
+ _buildEnvFields(capturedNames, funcLit) {
659
924
  const envFields = [];
925
+ const types = this.captureAnalysis?.captureTypes?.get(funcLit);
660
926
  for (const name of capturedNames) {
661
927
  const isBoxed = this.mutatedCaptures.has(name);
662
- const goType = this._findCapturedVarGoType(name);
928
+ const goType = types?.get(name) ?? this._findCapturedVarGoType(name);
663
929
  const boxInfo = isBoxed ? this.mod.getBoxType(goType) : null;
664
930
  envFields.push({
665
931
  name,
@@ -675,7 +941,10 @@ export class FunctionEmitter {
675
941
  }
676
942
 
677
943
  _unpackClosureEnv() {
678
- const envFields = this._buildEnvFields(this.capturedNames);
944
+ const envFields = this._buildEnvFields(
945
+ this.capturedNames,
946
+ this.funcDecl._funcLit,
947
+ );
679
948
  const envInfo = this.mod.getEnvType(envFields);
680
949
  this.envInfo = envInfo;
681
950
  this.envFields = envFields;
@@ -868,7 +1137,7 @@ export class FunctionEmitter {
868
1137
  for (const n of names) rawFields.push({ name: n, goType: f.type });
869
1138
  }
870
1139
  }
871
- if (rawFields.length > 0 && this.mod.getAnonStructType) {
1140
+ if (this.mod.getAnonStructType) {
872
1141
  return this.mod.getAnonStructType(rawFields);
873
1142
  }
874
1143
  }
@@ -882,7 +1151,13 @@ export class FunctionEmitter {
882
1151
  return [{ structInfo, fieldIndex: idx, field: structInfo.fields[idx] }];
883
1152
  }
884
1153
  for (const embed of structInfo.embeds) {
885
- const embedInfo = this.mod.structTypes.get(embed.name);
1154
+ // A generic instance is registered under its specialised name
1155
+ // (`Inner$int`), which the field's type carries.
1156
+ const ft = structInfo.fields[embed.fieldIndex]?.goType;
1157
+ const instName = (ft?.kind === "PointerType" ? ft.base : ft)?.name;
1158
+ const embedInfo =
1159
+ (instName && this.mod.structTypes.get(instName)) ??
1160
+ this.mod.structTypes.get(embed.typeName ?? embed.name);
886
1161
  if (embedInfo) {
887
1162
  const sub = this._resolveFieldPath(embedInfo, fieldName);
888
1163
  if (sub) {
@@ -900,7 +1175,7 @@ export class FunctionEmitter {
900
1175
  return null;
901
1176
  }
902
1177
 
903
- emitCloneStruct(structInfo, baseWType) {
1178
+ emitCloneStruct(structInfo, baseWType, dstInfo = structInfo) {
904
1179
  const tmp = this.acquireTemp(baseWType);
905
1180
  this.pushInstruction({ op: "local.set", index: tmp });
906
1181
  for (let i = 0; i < structInfo.fields.length; i++) {
@@ -910,18 +1185,117 @@ export class FunctionEmitter {
910
1185
  typeIndex: structInfo.typeIndex,
911
1186
  fieldIndex: i,
912
1187
  });
1188
+ // Array and value-struct fields are values: copy them with the struct.
1189
+ const f = structInfo.fields[i];
1190
+ if (!f.goType || f.goType.kind === "pointer") continue;
1191
+ if (isArrayType(f.goType, this.mod.checker)) {
1192
+ this.emitCopyArray(f.goType);
1193
+ } else if (
1194
+ isStructType(f.goType, this.mod.checker, this.mod) &&
1195
+ !isPointerToStruct(f.goType, this.mod.checker, this.mod)
1196
+ ) {
1197
+ const inner = this._resolveStructInfo({ _type: f.goType });
1198
+ if (inner) this.emitCloneStruct(inner, f.wType);
1199
+ }
913
1200
  }
914
1201
  this.pushInstruction({
915
1202
  op: "struct.new",
916
- typeIndex: structInfo.typeIndex,
1203
+ typeIndex: dstInfo.typeIndex,
917
1204
  });
918
1205
  this.releaseTemp(tmp, baseWType);
919
1206
  }
920
1207
 
921
- // Go copies structs on assignment. After `node` has been emitted (value on
922
- // the stack), clone it unless it is already a fresh temporary
923
- // (composite literal or `*p` deref) or not a value struct at all.
924
- _emitCopyIfValueStruct(node, goType, wType) {
1208
+ // Copies the fixed-size array on top of the stack into a fresh array.
1209
+ // Elements that are themselves arrays or value structs are copied deeply.
1210
+ // A null source (an array slot that was never written, e.g. an element of
1211
+ // `make([][2]int, n)`) yields the zero array, so every copy is non-null.
1212
+ emitCopyArray(goType) {
1213
+ const elemType = this._getArrayElemType(goType);
1214
+ const arrInfo = this.mod.getArrayType(elemType);
1215
+ const arrWType = {
1216
+ kind: "ref",
1217
+ nullable: true,
1218
+ typeIndex: arrInfo.typeIndex,
1219
+ };
1220
+ const src = this.acquireTemp(arrWType);
1221
+ const dst = this.acquireTemp(arrWType);
1222
+ this.pushInstruction({ op: "local.tee", index: src });
1223
+ this.pushInstruction("ref.is_null");
1224
+ this.pushInstruction({ op: "if", blockType: arrWType });
1225
+ this.emitZeroValue(goType, arrWType);
1226
+ this.pushInstruction("else");
1227
+ this.pushInstruction({ op: "local.get", index: src });
1228
+ this.pushInstruction("array.len");
1229
+ this.pushInstruction({
1230
+ op: "array.new_default",
1231
+ typeIndex: arrInfo.typeIndex,
1232
+ });
1233
+ this.pushInstruction({ op: "local.set", index: dst });
1234
+ const elemIsArray =
1235
+ elemType?.kind !== "pointer" && isArrayType(elemType, this.mod.checker);
1236
+ const elemStruct =
1237
+ !elemIsArray &&
1238
+ isStructType(elemType, this.mod.checker, this.mod) &&
1239
+ !isPointerToStruct(elemType, this.mod.checker, this.mod)
1240
+ ? (this._resolveStructInfo({ _type: elemType }) ??
1241
+ this.mod.getStructType(elemType?.name))
1242
+ : null;
1243
+ if (elemIsArray || elemStruct) {
1244
+ const i = this.acquireTemp("i32");
1245
+ this.pushInstruction({ op: "i32.const", value: 0 });
1246
+ this.pushInstruction({ op: "local.set", index: i });
1247
+ this.pushInstruction({ op: "block", blockType: "void" });
1248
+ this.pushInstruction({ op: "loop", blockType: "void" });
1249
+ this.pushInstruction({ op: "local.get", index: i });
1250
+ this.pushInstruction({ op: "local.get", index: dst });
1251
+ this.pushInstruction("array.len");
1252
+ this.pushInstruction("i32.ge_u");
1253
+ this.pushInstruction({ op: "br_if", depth: 1 });
1254
+ this.pushInstruction({ op: "local.get", index: dst });
1255
+ this.pushInstruction({ op: "local.get", index: i });
1256
+ this.pushInstruction({ op: "local.get", index: src });
1257
+ this.pushInstruction({ op: "local.get", index: i });
1258
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1259
+ if (elemIsArray) this.emitCopyArray(elemType);
1260
+ else this.emitCloneStruct(elemStruct, arrInfo.elemWType);
1261
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1262
+ this.pushInstruction({ op: "local.get", index: i });
1263
+ this.pushInstruction({ op: "i32.const", value: 1 });
1264
+ this.pushInstruction("i32.add");
1265
+ this.pushInstruction({ op: "local.set", index: i });
1266
+ this.pushInstruction({ op: "br", depth: 0 });
1267
+ this.pushInstruction("end");
1268
+ this.pushInstruction("end");
1269
+ this.releaseTemp(i, "i32");
1270
+ } else {
1271
+ this.pushInstruction({ op: "local.get", index: dst });
1272
+ this.pushInstruction({ op: "i32.const", value: 0 });
1273
+ this.pushInstruction({ op: "local.get", index: src });
1274
+ this.pushInstruction({ op: "i32.const", value: 0 });
1275
+ this.pushInstruction({ op: "local.get", index: src });
1276
+ this.pushInstruction("array.len");
1277
+ this.pushInstruction({
1278
+ op: "array.copy",
1279
+ typeIndex: arrInfo.typeIndex,
1280
+ });
1281
+ }
1282
+ this.pushInstruction({ op: "local.get", index: dst });
1283
+ this.pushInstruction("end");
1284
+ this.releaseTemp(dst, arrWType);
1285
+ this.releaseTemp(src, arrWType);
1286
+ }
1287
+
1288
+ // Go copies structs and arrays on assignment. After `node` has been
1289
+ // emitted (value on the stack), copy it unless it is already a fresh
1290
+ // temporary (composite literal, call result, or a struct `*p` deref) or not
1291
+ // a value type at all.
1292
+ _emitCopyIfValue(node, goType, wType) {
1293
+ if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker)) {
1294
+ if (node.kind === "CompositeLit" || node._freshArray) return;
1295
+ if (node.kind === "CallExpr" && !node._isTypeConversion) return;
1296
+ this.emitCopyArray(goType);
1297
+ return;
1298
+ }
925
1299
  if (
926
1300
  !isStructType(goType, this.mod.checker, this.mod) ||
927
1301
  isPointerToStruct(goType, this.mod.checker, this.mod)
@@ -963,7 +1337,8 @@ export class FunctionEmitter {
963
1337
  const pkg = t.expr?.name;
964
1338
  if (pkg) return `${pkg}.${t.field}`;
965
1339
  }
966
- if (t.kind === "simd") return "simd.F32x4";
1340
+ if (t.kind === "simd")
1341
+ return t.simd === "Mask32x4" ? "simd.Mask32x4" : "simd.F32x4";
967
1342
  if (t.name) return t.name;
968
1343
  if (t.kind === "named") return t.name;
969
1344
  if (t.kind === "TypeName" || t.kind === "Ident") return t.name;
@@ -981,11 +1356,23 @@ export class FunctionEmitter {
981
1356
  return null;
982
1357
  }
983
1358
 
984
- emitInterfaceDispatcher(fn) {
985
- let candidates = this.mod.findInterfaceCandidates(
1359
+ // Concrete methods the dispatcher `fn` can call: same name, same signature.
1360
+ _dispatchCandidates(fn) {
1361
+ const candidates = this.mod.findInterfaceCandidates(
986
1362
  fn._ifaceType,
987
1363
  fn._methodName,
988
1364
  );
1365
+ if (!Array.isArray(fn.params)) return candidates;
1366
+ const paramTypes = this.mod.getFuncParamTypes(fn.name);
1367
+ const results = this.mod.funcReturnTypesByIdx;
1368
+ const want = results.get(fn._globalFuncIndex);
1369
+ return candidates.filter((c) =>
1370
+ sameSignature(c, fn.params, paramTypes, want, results.get(c.funcIndex)),
1371
+ );
1372
+ }
1373
+
1374
+ emitInterfaceDispatcher(fn) {
1375
+ let candidates = this._dispatchCandidates(fn);
989
1376
  const recvLocal = 0; // __recv is local 0
990
1377
  // Soft dispatchers (errors.Unwrap) return nil instead of panicking when
991
1378
  // the receiver is nil or has no such method.
@@ -1025,12 +1412,15 @@ export class FunctionEmitter {
1025
1412
 
1026
1413
  // 2. Iterate candidates
1027
1414
  for (const cand of candidates) {
1415
+ this._emitNilPtrCandidate(cand, fn, recvLocal);
1028
1416
  this.pushInstruction({ op: "block", blockType: "void" });
1029
1417
  this.pushInstruction({ op: "local.get", index: recvLocal });
1030
1418
  this.pushInstruction({ op: "ref.test", typeIndex: cand.testTypeIndex });
1031
1419
  this.pushInstruction("i32.eqz");
1032
1420
  this.pushInstruction({ op: "br_if", depth: 0 });
1033
1421
 
1422
+ this._emitNilPtrBoxPanic(cand, recvLocal);
1423
+
1034
1424
  // Matched! Cast receiver
1035
1425
  this.pushInstruction({ op: "local.get", index: recvLocal });
1036
1426
  this.pushInstruction({
@@ -1046,6 +1436,12 @@ export class FunctionEmitter {
1046
1436
  });
1047
1437
  if (cand.embedPath) {
1048
1438
  for (const step of cand.embedPath) {
1439
+ if (step.castTypeIndex) {
1440
+ this.pushInstruction({
1441
+ op: "ref.cast",
1442
+ typeIndex: step.castTypeIndex,
1443
+ });
1444
+ }
1049
1445
  this.pushInstruction({
1050
1446
  op: "struct.get",
1051
1447
  typeIndex: step.parentTypeIndex,
@@ -1081,6 +1477,58 @@ export class FunctionEmitter {
1081
1477
  emitMiss("interface conversion: nil or unmatched type for method call");
1082
1478
  }
1083
1479
 
1480
+ // A typed nil `*S` inside the interface is a marker object (see
1481
+ // getNilBoxType): a pointer-receiver method runs with a nil receiver, as in
1482
+ // Go; a value-receiver or promoted method must read through the nil pointer.
1483
+ _emitNilPtrCandidate(cand, fn, recvLocal) {
1484
+ if (cand.isBoxedValue || !cand.structInfo) return;
1485
+ if (cand.testTypeIndex !== cand.structInfo.typeIndex) return;
1486
+ const nilBox = this.mod.getNilBoxType({
1487
+ kind: "pointer",
1488
+ base: { kind: "named", name: cand.recvTypeName },
1489
+ });
1490
+ this.pushInstruction({ op: "block", blockType: "void" });
1491
+ this.pushInstruction({ op: "local.get", index: recvLocal });
1492
+ this.pushInstruction({ op: "ref.test", typeIndex: nilBox.typeIndex });
1493
+ this.pushInstruction("i32.eqz");
1494
+ this.pushInstruction({ op: "br_if", depth: 0 });
1495
+ if (cand.needsValueDeref || cand.embedPath) {
1496
+ this.emitPanic(
1497
+ "runtime error: invalid memory address or nil pointer dereference",
1498
+ );
1499
+ } else {
1500
+ this.pushInstruction({ op: "ref.null", heapType: cand.testTypeIndex });
1501
+ for (let p = 1; p < fn.params.length; p++) {
1502
+ this.pushInstruction({ op: "local.get", index: p });
1503
+ }
1504
+ this.pushInstruction({ op: "call", funcIndex: cand.funcIndex });
1505
+ this.pushInstruction("return");
1506
+ }
1507
+ this.pushInstruction("end");
1508
+ }
1509
+
1510
+ // A value-receiver method reached through a pointer box reads the pointee
1511
+ // first, so a nil pointer inside the interface is a catchable panic.
1512
+ _emitNilPtrBoxPanic(cand, recvLocal) {
1513
+ if (!cand.nilPtrPanic) return;
1514
+ this.pushInstruction({ op: "local.get", index: recvLocal });
1515
+ this.pushInstruction({
1516
+ op: "ref.cast_null",
1517
+ typeIndex: cand.testTypeIndex,
1518
+ });
1519
+ this.pushInstruction({
1520
+ op: "struct.get",
1521
+ typeIndex: cand.testTypeIndex,
1522
+ fieldIndex: 0,
1523
+ });
1524
+ this.pushInstruction("ref.is_null");
1525
+ this.pushInstruction({ op: "if", blockType: "void" });
1526
+ this.emitPanic(
1527
+ "runtime error: invalid memory address or nil pointer dereference",
1528
+ );
1529
+ this.pushInstruction("end");
1530
+ }
1531
+
1084
1532
  _getArraySize(t, defaultSize = 0) {
1085
1533
  if (!t) return defaultSize;
1086
1534
  if (typeof t.size === "number") return t.size;
@@ -1088,6 +1536,10 @@ export class FunctionEmitter {
1088
1536
  if (typeof t.len === "number") return t.len;
1089
1537
  if (t.len?.value !== undefined) return Number(t.len.value);
1090
1538
  if (t.underlying) return this._getArraySize(t.underlying, defaultSize);
1539
+ if (t.name && this.mod.checker?.types?.has(t.name)) {
1540
+ const resolved = this.mod.checker.types.get(t.name);
1541
+ if (resolved !== t) return this._getArraySize(resolved, defaultSize);
1542
+ }
1091
1543
  return defaultSize;
1092
1544
  }
1093
1545
 
@@ -1119,6 +1571,22 @@ export class FunctionEmitter {
1119
1571
  wType !== null &&
1120
1572
  wType.kind === "ref"
1121
1573
  ) {
1574
+ if (
1575
+ (goType && isComplex(goType)) ||
1576
+ wType.typeName === "$complex64" ||
1577
+ wType.typeName === "$complex128"
1578
+ ) {
1579
+ const is64 = isComplex64(goType) || wType.typeName === "$complex64";
1580
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
1581
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
1582
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
1583
+ this.pushInstruction({
1584
+ op: "struct.new",
1585
+ typeIndex: info.typeIndex,
1586
+ typeName: info.name,
1587
+ });
1588
+ return;
1589
+ }
1122
1590
  if (
1123
1591
  goType &&
1124
1592
  goType.kind !== "pointer" &&
@@ -1126,7 +1594,10 @@ export class FunctionEmitter {
1126
1594
  ) {
1127
1595
  const sName =
1128
1596
  goType.name ?? (goType.kind === "named" ? goType.name : null);
1129
- const structInfo = this.mod.getStructType(sName);
1597
+ // An anonymous struct type has no name to look up.
1598
+ const structInfo =
1599
+ this.mod.getStructType(sName) ??
1600
+ this._resolveStructInfo({ _type: goType });
1130
1601
  if (structInfo) {
1131
1602
  for (const f of structInfo.fields) {
1132
1603
  this.emitZeroValue(f.goType, f.wType);
@@ -1143,10 +1614,15 @@ export class FunctionEmitter {
1143
1614
  goType.kind !== "pointer" &&
1144
1615
  isArrayType(goType, this.mod.checker)
1145
1616
  ) {
1146
- const elemType = goType.elem;
1617
+ const elemType = this._getArrayElemType(goType);
1147
1618
  const arrInfo = this.mod.getArrayType(elemType);
1148
1619
  const size = this._getArraySize(goType);
1149
- if (isStructType(elemType, this.mod.checker, this.mod)) {
1620
+ if (
1621
+ isStructType(elemType, this.mod.checker, this.mod) ||
1622
+ isComplex(elemType) ||
1623
+ (elemType?.kind !== "pointer" &&
1624
+ isArrayType(elemType, this.mod.checker))
1625
+ ) {
1150
1626
  for (let i = 0; i < size; i++) {
1151
1627
  this.emitZeroValue(elemType, arrInfo.elemWType);
1152
1628
  }
@@ -1156,11 +1632,9 @@ export class FunctionEmitter {
1156
1632
  size,
1157
1633
  });
1158
1634
  } else {
1159
- this.pushInstruction({ op: "i32.const", value: size });
1160
- this.pushInstruction({
1161
- op: "array.new_default",
1162
- typeIndex: arrInfo.typeIndex,
1163
- });
1635
+ this._emitNewArray(elemType, arrInfo, () =>
1636
+ this.pushInstruction({ op: "i32.const", value: size }),
1637
+ );
1164
1638
  }
1165
1639
  return;
1166
1640
  }
@@ -1202,8 +1676,24 @@ export class FunctionEmitter {
1202
1676
  }
1203
1677
  }
1204
1678
 
1205
- allocTemp(wasmType) {
1206
- return this.acquireTemp(wasmType);
1679
+ // Pushes a new array of `arrInfo` with the length `emitLen` pushes. Elements
1680
+ // start at their zero value: strings are the empty string, not a null
1681
+ // reference, so the array reads, compares and prints like Go's.
1682
+ _emitNewArray(elemType, arrInfo, emitLen) {
1683
+ if (
1684
+ arrInfo.elemWType === "externref" &&
1685
+ isStringType(elemType, this.mod.checker)
1686
+ ) {
1687
+ this.emitZeroValue(elemType, "externref");
1688
+ emitLen();
1689
+ this.pushInstruction({ op: "array.new", typeIndex: arrInfo.typeIndex });
1690
+ return;
1691
+ }
1692
+ emitLen();
1693
+ this.pushInstruction({
1694
+ op: "array.new_default",
1695
+ typeIndex: arrInfo.typeIndex,
1696
+ });
1207
1697
  }
1208
1698
 
1209
1699
  // Temps are keyed by a canonical string so ref-typed temps (fresh object
@@ -1286,8 +1776,9 @@ export class FunctionEmitter {
1286
1776
  return 0;
1287
1777
  }
1288
1778
 
1289
- resolveBranchDepthToOob() {
1290
- return this.resolveBranchDepthToRole("oob");
1779
+ // Depth of the index-check block for i32 or i64 indexes, or null.
1780
+ resolveBranchDepthToOob(width = 64) {
1781
+ return this.resolveBranchDepthToRole(width === 32 ? "oob32" : "oob64");
1291
1782
  }
1292
1783
 
1293
1784
  resolveBranchDepthToRole(targetRole) {
@@ -1308,17 +1799,43 @@ export class FunctionEmitter {
1308
1799
  }
1309
1800
  }
1310
1801
 
1802
+ // Go scopes: a block, `if`, `for` and `switch` (with their init
1803
+ // statements) end the names they declare, so an inner `x := 5` does not
1804
+ // rebind the outer `x` for the code after it.
1311
1805
  emitStmt(stmt) {
1312
1806
  if (!stmt) return;
1807
+ if (!SCOPED_STMTS.has(stmt.kind)) {
1808
+ this._emitStmtKind(stmt);
1809
+ return;
1810
+ }
1811
+ const saved = new Map(this.locals);
1812
+ try {
1813
+ this._emitStmtKind(stmt);
1814
+ } finally {
1815
+ this.locals = saved;
1816
+ }
1817
+ }
1818
+
1819
+ _emitStmtKind(stmt) {
1313
1820
  switch (stmt.kind) {
1314
1821
  case "Block":
1315
1822
  this.emitBlock(stmt);
1316
1823
  break;
1317
1824
 
1318
1825
  case "VarDecl":
1826
+ case "ConstDecl":
1319
1827
  this.emitVarDecl(stmt);
1320
1828
  break;
1321
1829
 
1830
+ // A package-level array or struct too large to build as a constant
1831
+ // expression gets its zero value when the module starts.
1832
+ case "GlobalZeroInit": {
1833
+ const g = this.mod.resolveGlobal(stmt.name);
1834
+ this.emitZeroValue(stmt.goType, g.type);
1835
+ this.pushInstruction({ op: "global.set", index: g.index });
1836
+ break;
1837
+ }
1838
+
1322
1839
  case "DefineStmt":
1323
1840
  this.emitDefineStmt(stmt);
1324
1841
  break;
@@ -1355,10 +1872,25 @@ export class FunctionEmitter {
1355
1872
  this.emitExprStmt(stmt);
1356
1873
  break;
1357
1874
 
1875
+ // Evaluates `stmt.expr` and panics (recoverably) when it is nil.
1876
+ case "NilCheckStmt":
1877
+ this.emitExpr(stmt.expr, "anyref");
1878
+ this._emitNilBranch();
1879
+ this.pushInstruction("drop");
1880
+ break;
1881
+
1358
1882
  case "LabeledStmt":
1359
1883
  if (stmt.body) {
1360
- if (stmt.body.kind === "ForStmt") {
1884
+ // Stacked labels (`a: b: for`) all target the loop: outer
1885
+ // labels alias the innermost one.
1886
+ if (stmt.body.kind !== "LabeledStmt") {
1361
1887
  stmt.body.label = stmt.label;
1888
+ this._labelAliases?.delete(stmt.label);
1889
+ } else {
1890
+ let inner = stmt.body;
1891
+ while (inner.body?.kind === "LabeledStmt") inner = inner.body;
1892
+ this._labelAliases ??= new Map();
1893
+ this._labelAliases.set(stmt.label, inner.label);
1362
1894
  }
1363
1895
  this.emitStmt(stmt.body);
1364
1896
  }
@@ -1386,7 +1918,7 @@ export class FunctionEmitter {
1386
1918
  }
1387
1919
  }
1388
1920
 
1389
- function copyMethods(target, source) {
1921
+ export function copyMethods(target, source) {
1390
1922
  const descriptors = Object.getOwnPropertyDescriptors(source.prototype);
1391
1923
  delete descriptors.constructor;
1392
1924
  for (const key of Object.keys(descriptors)) {
@@ -1403,11 +1935,5 @@ copyMethods(FunctionEmitter, MapsEmitter);
1403
1935
  copyMethods(FunctionEmitter, StmtsEmitter);
1404
1936
  copyMethods(FunctionEmitter, ExprsEmitter);
1405
1937
  copyMethods(FunctionEmitter, BuiltinsEmitter);
1406
- copyMethods(FunctionEmitter, StdlibCoreEmitter);
1407
- copyMethods(FunctionEmitter, BytesEmitter);
1408
- copyMethods(FunctionEmitter, PurePackagesEmitter);
1409
- copyMethods(FunctionEmitter, IoEmitter);
1410
- copyMethods(FunctionEmitter, TimeEmitter);
1411
- copyMethods(FunctionEmitter, ExtendedPackagesEmitter);
1938
+ for (const emitter of stdlibEmitters) copyMethods(FunctionEmitter, emitter);
1412
1939
  copyMethods(FunctionEmitter, SharedEmitter);
1413
- copyMethods(FunctionEmitter, SimdEmitter);