gofront 1.7.0 → 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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  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 +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  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 +10 -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 +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  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 +512 -77
  34. package/src/backend/wasm/encode.js +43 -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/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.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,36 @@ 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
+
33
58
  export class FunctionEmitter {
34
59
  constructor(moduleEmitter, funcDecl, funcIndex) {
35
60
  this.mod = moduleEmitter;
@@ -118,7 +143,75 @@ export class FunctionEmitter {
118
143
  this.recoverOkLocalIndex = local;
119
144
  }
120
145
 
146
+ _checkYieldClosureReentry() {
147
+ if (!this.funcDecl._isYieldClosure || !this.funcDecl._iterStateName) return;
148
+ const stateLoc = this.resolveLocal(this.funcDecl._iterStateName);
149
+ if (!stateLoc?.isBoxed) return;
150
+ this.pushInstruction({ op: "local.get", index: stateLoc.index });
151
+ this.pushInstruction({
152
+ op: "struct.get",
153
+ typeIndex: stateLoc.boxInfo.typeIndex,
154
+ fieldIndex: 0,
155
+ });
156
+ this.pushInstruction({ op: "i64.const", value: 0n });
157
+ this.pushInstruction("i64.ne");
158
+ this.pushInstruction({ op: "if", blockType: "void" });
159
+ this.emitPanic(
160
+ "range function continued iteration after function for loop body returned false",
161
+ );
162
+ this.pushInstruction("end");
163
+ }
164
+
165
+ _finishYieldClosureBody() {
166
+ if (!this.funcDecl._isYieldClosure) return false;
167
+ const lastOp = this.body[this.body.length - 1]?.op;
168
+ if (lastOp !== "return" && lastOp !== "unreachable") {
169
+ this.pushInstruction({ op: "i32.const", value: 1 });
170
+ this.pushInstruction({ op: "return" });
171
+ }
172
+ return true;
173
+ }
174
+
175
+ _collectLabelTargets(body) {
176
+ const targets = new Map();
177
+ let nextTag = 3;
178
+ const scan = (node) => {
179
+ if (!node || typeof node !== "object") return;
180
+ if (Array.isArray(node)) {
181
+ for (const item of node) scan(item);
182
+ return;
183
+ }
184
+ if (node.kind === "LabeledStmt" && node.label) {
185
+ const bTag = nextTag++;
186
+ const cTag = nextTag++;
187
+ targets.set(bTag, { tag: bTag, label: node.label, role: "break" });
188
+ targets.set(cTag, { tag: cTag, label: node.label, role: "continue" });
189
+ } else if (node.kind === "ForStmt" && node.label) {
190
+ const bTag = nextTag++;
191
+ const cTag = nextTag++;
192
+ targets.set(bTag, { tag: bTag, label: node.label, role: "break" });
193
+ targets.set(cTag, { tag: cTag, label: node.label, role: "continue" });
194
+ }
195
+ for (const k of Object.keys(node)) {
196
+ if (k.startsWith("_")) continue;
197
+ scan(node[k]);
198
+ }
199
+ };
200
+ scan(body);
201
+ return targets;
202
+ }
203
+
204
+ _findLabelTarget(label, role) {
205
+ for (const target of this.rootFuncDecl?._labelTargets?.values() ?? []) {
206
+ if (target.label === label && target.role === role) return target;
207
+ }
208
+ return null;
209
+ }
210
+
121
211
  emitFunctionBody(body) {
212
+ if (!this.funcDecl._isClosure && !this.funcDecl._labelTargets) {
213
+ this.funcDecl._labelTargets = this._collectLabelTargets(body);
214
+ }
122
215
  if (
123
216
  this.funcDecl._isClosure &&
124
217
  !this.funcDecl._isTrampoline &&
@@ -132,6 +225,7 @@ export class FunctionEmitter {
132
225
  this._cacheScratchGlobals();
133
226
 
134
227
  this._emitRecoverArmPrologue(body);
228
+ this._checkYieldClosureReentry();
135
229
 
136
230
  const hasDef = hasDefer(body);
137
231
  this.hasDefer = hasDef;
@@ -143,31 +237,139 @@ export class FunctionEmitter {
143
237
 
144
238
  this._initNamedReturns();
145
239
 
146
- const needsOobBlock = this._needsOobBlock(body);
147
- if (needsOobBlock) {
148
- this.pushInstruction({ op: "block", blockType: "void" });
149
- this.pushControl("oob");
150
- }
240
+ const closeChecks = this._openCheckBlocks(body);
151
241
 
152
242
  this.emitBlock(body);
243
+ if (this._finishYieldClosureBody()) {
244
+ closeChecks?.(() => {});
245
+ return;
246
+ }
247
+
248
+ closeChecks?.(() =>
249
+ this.pushInstruction({
250
+ op: this.returnTypes.length === 0 ? "return" : "unreachable",
251
+ }),
252
+ );
253
+ }
153
254
 
154
- if (needsOobBlock) {
255
+ // Opens the nil and index-check blocks `body` needs and returns the
256
+ // function that closes them (null when none are needed). The closer runs
257
+ // `terminate` first unless the body already ends in a return or trap.
258
+ _openCheckBlocks(body) {
259
+ const nil = this._needsNilBlock(body);
260
+ const oob = this._needsOobBlock(body);
261
+ if (!nil && !oob) return null;
262
+ if (nil) this._openNilBlock();
263
+ if (oob) this._openOobBlocks();
264
+ return (terminate) => {
155
265
  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");
266
+ if (lastOp !== "return" && lastOp !== "unreachable") terminate();
267
+ if (oob) this._closeOobBlocks();
268
+ if (nil) this._closeNilBlock();
269
+ };
270
+ }
271
+
272
+ // Field access through a nil struct pointer branches (`br_on_null`) out
273
+ // of the body to a block wrapped around it, whose handler raises Go's
274
+ // panic, so `recover()` sees it where a trap would skip the defers.
275
+ _openNilBlock() {
276
+ this.pushInstruction({ op: "block", blockType: "void" });
277
+ this.pushControl("nil");
278
+ }
279
+
280
+ // Closes the block opened by _openNilBlock; the body must already end in
281
+ // a branch, return or unreachable.
282
+ _closeNilBlock() {
283
+ this.pushInstruction("end");
284
+ this.pushInstruction({
285
+ op: "call",
286
+ funcIndex: this.mod.getNilPanicFuncIndex(),
287
+ });
288
+ this.pushInstruction("unreachable");
289
+ }
164
290
 
291
+ // Index checks branch out of the body to one of two blocks wrapped around
292
+ // it, `oob32` for i32 indexes and `oob64` for i64 indexes, carrying the
293
+ // index and the check's site id as block results (`_pushOobSite`). The
294
+ // handlers after the blocks recompute that site's length and raise Go's
295
+ // `index out of range [i] with length n`. Nothing extra stays live in
296
+ // the hot path: a site id is an immediate.
297
+ _openOobBlocks() {
298
+ this.pushInstruction({
299
+ op: "block",
300
+ blockType: this.mod.getTypeIndex([], ["i64", "i32"]),
301
+ });
302
+ this.pushControl("oob64");
303
+ this.pushInstruction({
304
+ op: "block",
305
+ blockType: this.mod.getTypeIndex([], ["i32", "i32"]),
306
+ });
307
+ this.pushControl("oob32");
308
+ }
309
+
310
+ // Closes the blocks opened by _openOobBlocks; the body must already end
311
+ // in a branch, return or unreachable.
312
+ _closeOobBlocks() {
313
+ const site = this.acquireTemp("i32");
314
+ const idx = this.acquireTemp("i64");
315
+ const len = this.acquireTemp("i32");
316
+ // oob32 lands here with [i32 index, site]: widen the index and fall
317
+ // through to the oob64 handler.
318
+ this.pushInstruction("end");
319
+ this.pushInstruction({ op: "local.set", index: site });
320
+ this.pushInstruction("i64.extend_i32_s");
321
+ this.pushInstruction({ op: "local.get", index: site });
322
+ this.pushInstruction("end");
323
+ this.pushInstruction({ op: "local.set", index: site });
324
+ this.pushInstruction({ op: "local.set", index: idx });
325
+ // Sites whose length code is identical (same temp, same field) share
326
+ // one br_table target.
327
+ const codes = [];
328
+ const target = (this.oobSites ?? []).map((emitLen) => {
329
+ const saved = this.body;
330
+ this.body = [];
331
+ emitLen();
332
+ const code = this.body;
333
+ this.body = saved;
334
+ const key = JSON.stringify(code);
335
+ let k = codes.findIndex((c) => c.key === key);
336
+ if (k < 0) k = codes.push({ key, code }) - 1;
337
+ return k;
338
+ });
339
+ const sites = codes.map(({ code }) => () => {
340
+ for (const inst of code) this.pushInstruction(inst);
341
+ });
342
+ if (sites.length === 0) {
343
+ this.pushInstruction({ op: "i32.const", value: 0 });
344
+ } else {
345
+ // block $len (result i32) { block … block { br_table } emitLen_0; br $len … }
346
+ this.pushInstruction({ op: "block", blockType: "i32" });
347
+ for (let k = 0; k < sites.length; k++)
348
+ this.pushInstruction({ op: "block", blockType: "void" });
349
+ this.pushInstruction({ op: "local.get", index: site });
165
350
  this.pushInstruction({
166
- op: "call",
167
- funcIndex: this.mod.getBoundsPanicFuncIndex(),
351
+ op: "br_table",
352
+ targets: target,
353
+ defaultTarget: sites.length - 1,
168
354
  });
169
- this.pushInstruction("unreachable");
355
+ for (let k = 0; k < sites.length; k++) {
356
+ this.pushInstruction("end");
357
+ sites[k]();
358
+ this.pushInstruction({ op: "br", depth: sites.length - 1 - k });
359
+ }
360
+ this.pushInstruction("end");
170
361
  }
362
+ this.pushInstruction({ op: "local.set", index: len });
363
+ this.pushInstruction({ op: "local.get", index: idx });
364
+ this.pushInstruction({ op: "local.get", index: len });
365
+ this.pushInstruction({
366
+ op: "call",
367
+ funcIndex: this.mod.getIndexPanicFuncIndex(),
368
+ });
369
+ this.pushInstruction("unreachable");
370
+ this.releaseTemp(site, "i32");
371
+ this.releaseTemp(idx, "i64");
372
+ this.releaseTemp(len, "i32");
171
373
  }
172
374
 
173
375
  _initNamedReturns() {
@@ -180,13 +382,6 @@ export class FunctionEmitter {
180
382
  const varName = isBlank ? `__ret_blank$${i}` : r.name;
181
383
  const wType = this.toWasmType(r.type);
182
384
  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
385
  this.emitZeroValue(r.type, wType);
191
386
  const localInfo = this.locals.get(varName);
192
387
  if (localInfo?.isBoxed) {
@@ -196,6 +391,13 @@ export class FunctionEmitter {
196
391
  });
197
392
  }
198
393
  this.pushInstruction({ op: "local.set", index: localIdx });
394
+ this.namedReturnVars.push({
395
+ name: varName,
396
+ localIdx,
397
+ type: wType,
398
+ goType: r.type,
399
+ isBlank,
400
+ });
199
401
  }
200
402
  return true;
201
403
  }
@@ -256,30 +458,16 @@ export class FunctionEmitter {
256
458
  });
257
459
  this.pushControl("try_table");
258
460
 
259
- const needsOobBlock = this._needsOobBlock(body);
260
- if (needsOobBlock) {
261
- this.pushInstruction({ op: "block", blockType: "void" });
262
- this.pushControl("oob");
263
- }
461
+ const closeChecks = this._openCheckBlocks(body);
264
462
 
265
463
  this.emitBlock(body);
266
464
 
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
-
465
+ closeChecks?.(() =>
277
466
  this.pushInstruction({
278
- op: "call",
279
- funcIndex: this.mod.getBoundsPanicFuncIndex(),
280
- });
281
- this.pushInstruction("unreachable");
282
- }
467
+ op: "br",
468
+ depth: this.resolveBranchDepthToRole("runDefers"),
469
+ }),
470
+ );
283
471
 
284
472
  // Close try_table
285
473
  this.pushInstruction("end");
@@ -575,6 +763,52 @@ export class FunctionEmitter {
575
763
  return false;
576
764
  }
577
765
 
766
+ // True when the body reads or writes a field or the target of a pointer.
767
+ _needsNilBlock(node) {
768
+ if (!node || typeof node !== "object") return false;
769
+ if (Array.isArray(node)) return node.some((n) => this._needsNilBlock(n));
770
+ if (node.kind === "FuncLit") return false;
771
+ if (node.kind === "SelectorExpr" && node.expr?._type?.kind === "pointer")
772
+ return true;
773
+ if (this._isNilTrapSite(node)) return true;
774
+ for (const key of Object.keys(node)) {
775
+ if (key.startsWith("_")) continue;
776
+ if (this._needsNilBlock(node[key])) return true;
777
+ }
778
+ return false;
779
+ }
780
+
781
+ // A pointer dereference or a call through a func value: sites whose nil
782
+ // operand raises Go's panic through the `nil` block.
783
+ _isNilTrapSite(node) {
784
+ if (node.kind === "CallExpr") return this._isFuncValueCall(node);
785
+ const star =
786
+ (node.kind === "UnaryExpr" && node.op === "*") ||
787
+ node.kind === "StarExpr";
788
+ return star && (node.operand ?? node.expr)?._type?.kind === "pointer";
789
+ }
790
+
791
+ // A call through a func variable, parameter or expression (not a declared
792
+ // function or a method), which panics when the value is nil.
793
+ _isFuncValueCall(call) {
794
+ const f = call.func;
795
+ if (!f || call._isTypeConversion) return false;
796
+ if (f.kind === "Ident")
797
+ return (
798
+ isFuncType(f._type, this.mod.checker) &&
799
+ this.mod.resolveFuncIndex(f.name) === null
800
+ );
801
+ if (f.kind === "SelectorExpr") {
802
+ const t = f.expr?._type;
803
+ const base = t?.kind === "pointer" ? t.base : t;
804
+ const fields = (base?.underlying ?? base)?.fields;
805
+ return fields instanceof Map && fields.has(f.field);
806
+ }
807
+ return (
808
+ f.kind === "IndexExpr" || f.kind === "CallExpr" || f.kind === "ParenExpr"
809
+ );
810
+ }
811
+
578
812
  _needsOobBlock(node) {
579
813
  if (!node || typeof node !== "object") return false;
580
814
  if (Array.isArray(node)) {
@@ -640,6 +874,10 @@ export class FunctionEmitter {
640
874
  for (let i = 0; i < lhs.length; i++) {
641
875
  if (lhs[i].name === varName) {
642
876
  if (lhs[i]._type) return lhs[i]._type;
877
+ // `for k, v := range x`: the range expression is a tuple.
878
+ const rt = node.rhs?.[0]?._type;
879
+ if (node.rhs?.[0]?.kind === "RangeExpr" && rt?.kind === "tuple")
880
+ return rt.types?.[i] ?? null;
643
881
  if (node.rhs?.[i]?._type) return node.rhs[i]._type;
644
882
  }
645
883
  }
@@ -868,7 +1106,7 @@ export class FunctionEmitter {
868
1106
  for (const n of names) rawFields.push({ name: n, goType: f.type });
869
1107
  }
870
1108
  }
871
- if (rawFields.length > 0 && this.mod.getAnonStructType) {
1109
+ if (this.mod.getAnonStructType) {
872
1110
  return this.mod.getAnonStructType(rawFields);
873
1111
  }
874
1112
  }
@@ -882,7 +1120,13 @@ export class FunctionEmitter {
882
1120
  return [{ structInfo, fieldIndex: idx, field: structInfo.fields[idx] }];
883
1121
  }
884
1122
  for (const embed of structInfo.embeds) {
885
- const embedInfo = this.mod.structTypes.get(embed.name);
1123
+ // A generic instance is registered under its specialised name
1124
+ // (`Inner$int`), which the field's type carries.
1125
+ const ft = structInfo.fields[embed.fieldIndex]?.goType;
1126
+ const instName = (ft?.kind === "PointerType" ? ft.base : ft)?.name;
1127
+ const embedInfo =
1128
+ (instName && this.mod.structTypes.get(instName)) ??
1129
+ this.mod.structTypes.get(embed.name);
886
1130
  if (embedInfo) {
887
1131
  const sub = this._resolveFieldPath(embedInfo, fieldName);
888
1132
  if (sub) {
@@ -900,7 +1144,7 @@ export class FunctionEmitter {
900
1144
  return null;
901
1145
  }
902
1146
 
903
- emitCloneStruct(structInfo, baseWType) {
1147
+ emitCloneStruct(structInfo, baseWType, dstInfo = structInfo) {
904
1148
  const tmp = this.acquireTemp(baseWType);
905
1149
  this.pushInstruction({ op: "local.set", index: tmp });
906
1150
  for (let i = 0; i < structInfo.fields.length; i++) {
@@ -910,18 +1154,117 @@ export class FunctionEmitter {
910
1154
  typeIndex: structInfo.typeIndex,
911
1155
  fieldIndex: i,
912
1156
  });
1157
+ // Array and value-struct fields are values: copy them with the struct.
1158
+ const f = structInfo.fields[i];
1159
+ if (!f.goType || f.goType.kind === "pointer") continue;
1160
+ if (isArrayType(f.goType, this.mod.checker)) {
1161
+ this.emitCopyArray(f.goType);
1162
+ } else if (
1163
+ isStructType(f.goType, this.mod.checker, this.mod) &&
1164
+ !isPointerToStruct(f.goType, this.mod.checker, this.mod)
1165
+ ) {
1166
+ const inner = this._resolveStructInfo({ _type: f.goType });
1167
+ if (inner) this.emitCloneStruct(inner, f.wType);
1168
+ }
913
1169
  }
914
1170
  this.pushInstruction({
915
1171
  op: "struct.new",
916
- typeIndex: structInfo.typeIndex,
1172
+ typeIndex: dstInfo.typeIndex,
917
1173
  });
918
1174
  this.releaseTemp(tmp, baseWType);
919
1175
  }
920
1176
 
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) {
1177
+ // Copies the fixed-size array on top of the stack into a fresh array.
1178
+ // Elements that are themselves arrays or value structs are copied deeply.
1179
+ // A null source (an array slot that was never written, e.g. an element of
1180
+ // `make([][2]int, n)`) yields the zero array, so every copy is non-null.
1181
+ emitCopyArray(goType) {
1182
+ const elemType = this._getArrayElemType(goType);
1183
+ const arrInfo = this.mod.getArrayType(elemType);
1184
+ const arrWType = {
1185
+ kind: "ref",
1186
+ nullable: true,
1187
+ typeIndex: arrInfo.typeIndex,
1188
+ };
1189
+ const src = this.acquireTemp(arrWType);
1190
+ const dst = this.acquireTemp(arrWType);
1191
+ this.pushInstruction({ op: "local.tee", index: src });
1192
+ this.pushInstruction("ref.is_null");
1193
+ this.pushInstruction({ op: "if", blockType: arrWType });
1194
+ this.emitZeroValue(goType, arrWType);
1195
+ this.pushInstruction("else");
1196
+ this.pushInstruction({ op: "local.get", index: src });
1197
+ this.pushInstruction("array.len");
1198
+ this.pushInstruction({
1199
+ op: "array.new_default",
1200
+ typeIndex: arrInfo.typeIndex,
1201
+ });
1202
+ this.pushInstruction({ op: "local.set", index: dst });
1203
+ const elemIsArray =
1204
+ elemType?.kind !== "pointer" && isArrayType(elemType, this.mod.checker);
1205
+ const elemStruct =
1206
+ !elemIsArray &&
1207
+ isStructType(elemType, this.mod.checker, this.mod) &&
1208
+ !isPointerToStruct(elemType, this.mod.checker, this.mod)
1209
+ ? (this._resolveStructInfo({ _type: elemType }) ??
1210
+ this.mod.getStructType(elemType?.name))
1211
+ : null;
1212
+ if (elemIsArray || elemStruct) {
1213
+ const i = this.acquireTemp("i32");
1214
+ this.pushInstruction({ op: "i32.const", value: 0 });
1215
+ this.pushInstruction({ op: "local.set", index: i });
1216
+ this.pushInstruction({ op: "block", blockType: "void" });
1217
+ this.pushInstruction({ op: "loop", blockType: "void" });
1218
+ this.pushInstruction({ op: "local.get", index: i });
1219
+ this.pushInstruction({ op: "local.get", index: dst });
1220
+ this.pushInstruction("array.len");
1221
+ this.pushInstruction("i32.ge_u");
1222
+ this.pushInstruction({ op: "br_if", depth: 1 });
1223
+ this.pushInstruction({ op: "local.get", index: dst });
1224
+ this.pushInstruction({ op: "local.get", index: i });
1225
+ this.pushInstruction({ op: "local.get", index: src });
1226
+ this.pushInstruction({ op: "local.get", index: i });
1227
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1228
+ if (elemIsArray) this.emitCopyArray(elemType);
1229
+ else this.emitCloneStruct(elemStruct, arrInfo.elemWType);
1230
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1231
+ this.pushInstruction({ op: "local.get", index: i });
1232
+ this.pushInstruction({ op: "i32.const", value: 1 });
1233
+ this.pushInstruction("i32.add");
1234
+ this.pushInstruction({ op: "local.set", index: i });
1235
+ this.pushInstruction({ op: "br", depth: 0 });
1236
+ this.pushInstruction("end");
1237
+ this.pushInstruction("end");
1238
+ this.releaseTemp(i, "i32");
1239
+ } else {
1240
+ this.pushInstruction({ op: "local.get", index: dst });
1241
+ this.pushInstruction({ op: "i32.const", value: 0 });
1242
+ this.pushInstruction({ op: "local.get", index: src });
1243
+ this.pushInstruction({ op: "i32.const", value: 0 });
1244
+ this.pushInstruction({ op: "local.get", index: src });
1245
+ this.pushInstruction("array.len");
1246
+ this.pushInstruction({
1247
+ op: "array.copy",
1248
+ typeIndex: arrInfo.typeIndex,
1249
+ });
1250
+ }
1251
+ this.pushInstruction({ op: "local.get", index: dst });
1252
+ this.pushInstruction("end");
1253
+ this.releaseTemp(dst, arrWType);
1254
+ this.releaseTemp(src, arrWType);
1255
+ }
1256
+
1257
+ // Go copies structs and arrays on assignment. After `node` has been
1258
+ // emitted (value on the stack), copy it unless it is already a fresh
1259
+ // temporary (composite literal, call result, or a struct `*p` deref) or not
1260
+ // a value type at all.
1261
+ _emitCopyIfValue(node, goType, wType) {
1262
+ if (goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker)) {
1263
+ if (node.kind === "CompositeLit" || node._freshArray) return;
1264
+ if (node.kind === "CallExpr" && !node._isTypeConversion) return;
1265
+ this.emitCopyArray(goType);
1266
+ return;
1267
+ }
925
1268
  if (
926
1269
  !isStructType(goType, this.mod.checker, this.mod) ||
927
1270
  isPointerToStruct(goType, this.mod.checker, this.mod)
@@ -963,7 +1306,8 @@ export class FunctionEmitter {
963
1306
  const pkg = t.expr?.name;
964
1307
  if (pkg) return `${pkg}.${t.field}`;
965
1308
  }
966
- if (t.kind === "simd") return "simd.F32x4";
1309
+ if (t.kind === "simd")
1310
+ return t.simd === "Mask32x4" ? "simd.Mask32x4" : "simd.F32x4";
967
1311
  if (t.name) return t.name;
968
1312
  if (t.kind === "named") return t.name;
969
1313
  if (t.kind === "TypeName" || t.kind === "Ident") return t.name;
@@ -981,11 +1325,23 @@ export class FunctionEmitter {
981
1325
  return null;
982
1326
  }
983
1327
 
984
- emitInterfaceDispatcher(fn) {
985
- let candidates = this.mod.findInterfaceCandidates(
1328
+ // Concrete methods the dispatcher `fn` can call: same name, same signature.
1329
+ _dispatchCandidates(fn) {
1330
+ const candidates = this.mod.findInterfaceCandidates(
986
1331
  fn._ifaceType,
987
1332
  fn._methodName,
988
1333
  );
1334
+ if (!Array.isArray(fn.params)) return candidates;
1335
+ const paramTypes = this.mod.getFuncParamTypes(fn.name);
1336
+ const results = this.mod.funcReturnTypesByIdx;
1337
+ const want = results.get(fn._globalFuncIndex);
1338
+ return candidates.filter((c) =>
1339
+ sameSignature(c, fn.params, paramTypes, want, results.get(c.funcIndex)),
1340
+ );
1341
+ }
1342
+
1343
+ emitInterfaceDispatcher(fn) {
1344
+ let candidates = this._dispatchCandidates(fn);
989
1345
  const recvLocal = 0; // __recv is local 0
990
1346
  // Soft dispatchers (errors.Unwrap) return nil instead of panicking when
991
1347
  // the receiver is nil or has no such method.
@@ -1031,6 +1387,8 @@ export class FunctionEmitter {
1031
1387
  this.pushInstruction("i32.eqz");
1032
1388
  this.pushInstruction({ op: "br_if", depth: 0 });
1033
1389
 
1390
+ this._emitNilPtrBoxPanic(cand, recvLocal);
1391
+
1034
1392
  // Matched! Cast receiver
1035
1393
  this.pushInstruction({ op: "local.get", index: recvLocal });
1036
1394
  this.pushInstruction({
@@ -1046,6 +1404,12 @@ export class FunctionEmitter {
1046
1404
  });
1047
1405
  if (cand.embedPath) {
1048
1406
  for (const step of cand.embedPath) {
1407
+ if (step.castTypeIndex) {
1408
+ this.pushInstruction({
1409
+ op: "ref.cast",
1410
+ typeIndex: step.castTypeIndex,
1411
+ });
1412
+ }
1049
1413
  this.pushInstruction({
1050
1414
  op: "struct.get",
1051
1415
  typeIndex: step.parentTypeIndex,
@@ -1081,6 +1445,28 @@ export class FunctionEmitter {
1081
1445
  emitMiss("interface conversion: nil or unmatched type for method call");
1082
1446
  }
1083
1447
 
1448
+ // A value-receiver method reached through a pointer box reads the pointee
1449
+ // first, so a nil pointer inside the interface is a catchable panic.
1450
+ _emitNilPtrBoxPanic(cand, recvLocal) {
1451
+ if (!cand.nilPtrPanic) return;
1452
+ this.pushInstruction({ op: "local.get", index: recvLocal });
1453
+ this.pushInstruction({
1454
+ op: "ref.cast_null",
1455
+ typeIndex: cand.testTypeIndex,
1456
+ });
1457
+ this.pushInstruction({
1458
+ op: "struct.get",
1459
+ typeIndex: cand.testTypeIndex,
1460
+ fieldIndex: 0,
1461
+ });
1462
+ this.pushInstruction("ref.is_null");
1463
+ this.pushInstruction({ op: "if", blockType: "void" });
1464
+ this.emitPanic(
1465
+ "runtime error: invalid memory address or nil pointer dereference",
1466
+ );
1467
+ this.pushInstruction("end");
1468
+ }
1469
+
1084
1470
  _getArraySize(t, defaultSize = 0) {
1085
1471
  if (!t) return defaultSize;
1086
1472
  if (typeof t.size === "number") return t.size;
@@ -1088,6 +1474,10 @@ export class FunctionEmitter {
1088
1474
  if (typeof t.len === "number") return t.len;
1089
1475
  if (t.len?.value !== undefined) return Number(t.len.value);
1090
1476
  if (t.underlying) return this._getArraySize(t.underlying, defaultSize);
1477
+ if (t.name && this.mod.checker?.types?.has(t.name)) {
1478
+ const resolved = this.mod.checker.types.get(t.name);
1479
+ if (resolved !== t) return this._getArraySize(resolved, defaultSize);
1480
+ }
1091
1481
  return defaultSize;
1092
1482
  }
1093
1483
 
@@ -1119,6 +1509,22 @@ export class FunctionEmitter {
1119
1509
  wType !== null &&
1120
1510
  wType.kind === "ref"
1121
1511
  ) {
1512
+ if (
1513
+ (goType && isComplex(goType)) ||
1514
+ wType.typeName === "$complex64" ||
1515
+ wType.typeName === "$complex128"
1516
+ ) {
1517
+ const is64 = isComplex64(goType) || wType.typeName === "$complex64";
1518
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
1519
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
1520
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
1521
+ this.pushInstruction({
1522
+ op: "struct.new",
1523
+ typeIndex: info.typeIndex,
1524
+ typeName: info.name,
1525
+ });
1526
+ return;
1527
+ }
1122
1528
  if (
1123
1529
  goType &&
1124
1530
  goType.kind !== "pointer" &&
@@ -1126,7 +1532,10 @@ export class FunctionEmitter {
1126
1532
  ) {
1127
1533
  const sName =
1128
1534
  goType.name ?? (goType.kind === "named" ? goType.name : null);
1129
- const structInfo = this.mod.getStructType(sName);
1535
+ // An anonymous struct type has no name to look up.
1536
+ const structInfo =
1537
+ this.mod.getStructType(sName) ??
1538
+ this._resolveStructInfo({ _type: goType });
1130
1539
  if (structInfo) {
1131
1540
  for (const f of structInfo.fields) {
1132
1541
  this.emitZeroValue(f.goType, f.wType);
@@ -1143,10 +1552,15 @@ export class FunctionEmitter {
1143
1552
  goType.kind !== "pointer" &&
1144
1553
  isArrayType(goType, this.mod.checker)
1145
1554
  ) {
1146
- const elemType = goType.elem;
1555
+ const elemType = this._getArrayElemType(goType);
1147
1556
  const arrInfo = this.mod.getArrayType(elemType);
1148
1557
  const size = this._getArraySize(goType);
1149
- if (isStructType(elemType, this.mod.checker, this.mod)) {
1558
+ if (
1559
+ isStructType(elemType, this.mod.checker, this.mod) ||
1560
+ isComplex(elemType) ||
1561
+ (elemType?.kind !== "pointer" &&
1562
+ isArrayType(elemType, this.mod.checker))
1563
+ ) {
1150
1564
  for (let i = 0; i < size; i++) {
1151
1565
  this.emitZeroValue(elemType, arrInfo.elemWType);
1152
1566
  }
@@ -1286,8 +1700,9 @@ export class FunctionEmitter {
1286
1700
  return 0;
1287
1701
  }
1288
1702
 
1289
- resolveBranchDepthToOob() {
1290
- return this.resolveBranchDepthToRole("oob");
1703
+ // Depth of the index-check block for i32 or i64 indexes, or null.
1704
+ resolveBranchDepthToOob(width = 64) {
1705
+ return this.resolveBranchDepthToRole(width === 32 ? "oob32" : "oob64");
1291
1706
  }
1292
1707
 
1293
1708
  resolveBranchDepthToRole(targetRole) {
@@ -1308,17 +1723,43 @@ export class FunctionEmitter {
1308
1723
  }
1309
1724
  }
1310
1725
 
1726
+ // Go scopes: a block, `if`, `for` and `switch` (with their init
1727
+ // statements) end the names they declare, so an inner `x := 5` does not
1728
+ // rebind the outer `x` for the code after it.
1311
1729
  emitStmt(stmt) {
1312
1730
  if (!stmt) return;
1731
+ if (!SCOPED_STMTS.has(stmt.kind)) {
1732
+ this._emitStmtKind(stmt);
1733
+ return;
1734
+ }
1735
+ const saved = new Map(this.locals);
1736
+ try {
1737
+ this._emitStmtKind(stmt);
1738
+ } finally {
1739
+ this.locals = saved;
1740
+ }
1741
+ }
1742
+
1743
+ _emitStmtKind(stmt) {
1313
1744
  switch (stmt.kind) {
1314
1745
  case "Block":
1315
1746
  this.emitBlock(stmt);
1316
1747
  break;
1317
1748
 
1318
1749
  case "VarDecl":
1750
+ case "ConstDecl":
1319
1751
  this.emitVarDecl(stmt);
1320
1752
  break;
1321
1753
 
1754
+ // A package-level array or struct too large to build as a constant
1755
+ // expression gets its zero value when the module starts.
1756
+ case "GlobalZeroInit": {
1757
+ const g = this.mod.resolveGlobal(stmt.name);
1758
+ this.emitZeroValue(stmt.goType, g.type);
1759
+ this.pushInstruction({ op: "global.set", index: g.index });
1760
+ break;
1761
+ }
1762
+
1322
1763
  case "DefineStmt":
1323
1764
  this.emitDefineStmt(stmt);
1324
1765
  break;
@@ -1386,7 +1827,7 @@ export class FunctionEmitter {
1386
1827
  }
1387
1828
  }
1388
1829
 
1389
- function copyMethods(target, source) {
1830
+ export function copyMethods(target, source) {
1390
1831
  const descriptors = Object.getOwnPropertyDescriptors(source.prototype);
1391
1832
  delete descriptors.constructor;
1392
1833
  for (const key of Object.keys(descriptors)) {
@@ -1403,11 +1844,5 @@ copyMethods(FunctionEmitter, MapsEmitter);
1403
1844
  copyMethods(FunctionEmitter, StmtsEmitter);
1404
1845
  copyMethods(FunctionEmitter, ExprsEmitter);
1405
1846
  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);
1847
+ for (const emitter of stdlibEmitters) copyMethods(FunctionEmitter, emitter);
1412
1848
  copyMethods(FunctionEmitter, SharedEmitter);
1413
- copyMethods(FunctionEmitter, SimdEmitter);