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
@@ -9,16 +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
- StdlibCoreEmitter,
18
- TimeEmitter,
19
- } from "./stdlib/index.js";
12
+ import { stdlibEmitters } from "./stdlib/index.js";
20
13
  import {
21
14
  isArrayType,
15
+ isComplex,
16
+ isComplex64,
17
+ isFuncType,
22
18
  isPointerToStruct,
23
19
  isSliceType,
24
20
  isStringType,
@@ -29,6 +25,36 @@ import {
29
25
  // The emitter is one class split over several files by concern. Methods from
30
26
  // the emit-*.js modules are assigned to FunctionEmitter.prototype below; `this`
31
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
+
32
58
  export class FunctionEmitter {
33
59
  constructor(moduleEmitter, funcDecl, funcIndex) {
34
60
  this.mod = moduleEmitter;
@@ -117,7 +143,75 @@ export class FunctionEmitter {
117
143
  this.recoverOkLocalIndex = local;
118
144
  }
119
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
+
120
211
  emitFunctionBody(body) {
212
+ if (!this.funcDecl._isClosure && !this.funcDecl._labelTargets) {
213
+ this.funcDecl._labelTargets = this._collectLabelTargets(body);
214
+ }
121
215
  if (
122
216
  this.funcDecl._isClosure &&
123
217
  !this.funcDecl._isTrampoline &&
@@ -131,6 +225,7 @@ export class FunctionEmitter {
131
225
  this._cacheScratchGlobals();
132
226
 
133
227
  this._emitRecoverArmPrologue(body);
228
+ this._checkYieldClosureReentry();
134
229
 
135
230
  const hasDef = hasDefer(body);
136
231
  this.hasDefer = hasDef;
@@ -142,31 +237,139 @@ export class FunctionEmitter {
142
237
 
143
238
  this._initNamedReturns();
144
239
 
145
- const needsOobBlock = this._needsOobBlock(body);
146
- if (needsOobBlock) {
147
- this.pushInstruction({ op: "block", blockType: "void" });
148
- this.pushControl("oob");
149
- }
240
+ const closeChecks = this._openCheckBlocks(body);
150
241
 
151
242
  this.emitBlock(body);
243
+ if (this._finishYieldClosureBody()) {
244
+ closeChecks?.(() => {});
245
+ return;
246
+ }
152
247
 
153
- if (needsOobBlock) {
248
+ closeChecks?.(() =>
249
+ this.pushInstruction({
250
+ op: this.returnTypes.length === 0 ? "return" : "unreachable",
251
+ }),
252
+ );
253
+ }
254
+
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) => {
154
265
  const lastOp = this.body[this.body.length - 1]?.op;
155
- if (lastOp !== "return" && lastOp !== "unreachable") {
156
- if (this.returnTypes.length === 0) {
157
- this.pushInstruction({ op: "return" });
158
- } else {
159
- this.pushInstruction({ op: "unreachable" });
160
- }
161
- }
162
- 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
+ }
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
+ }
163
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 });
164
350
  this.pushInstruction({
165
- op: "call",
166
- funcIndex: this.mod.getBoundsPanicFuncIndex(),
351
+ op: "br_table",
352
+ targets: target,
353
+ defaultTarget: sites.length - 1,
167
354
  });
168
- 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");
169
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");
170
373
  }
171
374
 
172
375
  _initNamedReturns() {
@@ -179,13 +382,6 @@ export class FunctionEmitter {
179
382
  const varName = isBlank ? `__ret_blank$${i}` : r.name;
180
383
  const wType = this.toWasmType(r.type);
181
384
  const localIdx = this.allocLocal(varName, wType, r.type, isBlank);
182
- this.namedReturnVars.push({
183
- name: varName,
184
- localIdx,
185
- type: wType,
186
- goType: r.type,
187
- isBlank,
188
- });
189
385
  this.emitZeroValue(r.type, wType);
190
386
  const localInfo = this.locals.get(varName);
191
387
  if (localInfo?.isBoxed) {
@@ -195,6 +391,13 @@ export class FunctionEmitter {
195
391
  });
196
392
  }
197
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
+ });
198
401
  }
199
402
  return true;
200
403
  }
@@ -255,30 +458,16 @@ export class FunctionEmitter {
255
458
  });
256
459
  this.pushControl("try_table");
257
460
 
258
- const needsOobBlock = this._needsOobBlock(body);
259
- if (needsOobBlock) {
260
- this.pushInstruction({ op: "block", blockType: "void" });
261
- this.pushControl("oob");
262
- }
461
+ const closeChecks = this._openCheckBlocks(body);
263
462
 
264
463
  this.emitBlock(body);
265
464
 
266
- if (needsOobBlock) {
267
- const lastOp = this.body[this.body.length - 1]?.op;
268
- if (lastOp !== "return" && lastOp !== "unreachable") {
269
- this.pushInstruction({
270
- op: "br",
271
- depth: this.resolveBranchDepthToRole("runDefers"),
272
- });
273
- }
274
- this.pushInstruction("end");
275
-
465
+ closeChecks?.(() =>
276
466
  this.pushInstruction({
277
- op: "call",
278
- funcIndex: this.mod.getBoundsPanicFuncIndex(),
279
- });
280
- this.pushInstruction("unreachable");
281
- }
467
+ op: "br",
468
+ depth: this.resolveBranchDepthToRole("runDefers"),
469
+ }),
470
+ );
282
471
 
283
472
  // Close try_table
284
473
  this.pushInstruction("end");
@@ -574,6 +763,52 @@ export class FunctionEmitter {
574
763
  return false;
575
764
  }
576
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
+
577
812
  _needsOobBlock(node) {
578
813
  if (!node || typeof node !== "object") return false;
579
814
  if (Array.isArray(node)) {
@@ -639,6 +874,10 @@ export class FunctionEmitter {
639
874
  for (let i = 0; i < lhs.length; i++) {
640
875
  if (lhs[i].name === varName) {
641
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;
642
881
  if (node.rhs?.[i]?._type) return node.rhs[i]._type;
643
882
  }
644
883
  }
@@ -867,7 +1106,7 @@ export class FunctionEmitter {
867
1106
  for (const n of names) rawFields.push({ name: n, goType: f.type });
868
1107
  }
869
1108
  }
870
- if (rawFields.length > 0 && this.mod.getAnonStructType) {
1109
+ if (this.mod.getAnonStructType) {
871
1110
  return this.mod.getAnonStructType(rawFields);
872
1111
  }
873
1112
  }
@@ -881,7 +1120,13 @@ export class FunctionEmitter {
881
1120
  return [{ structInfo, fieldIndex: idx, field: structInfo.fields[idx] }];
882
1121
  }
883
1122
  for (const embed of structInfo.embeds) {
884
- 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);
885
1130
  if (embedInfo) {
886
1131
  const sub = this._resolveFieldPath(embedInfo, fieldName);
887
1132
  if (sub) {
@@ -899,7 +1144,7 @@ export class FunctionEmitter {
899
1144
  return null;
900
1145
  }
901
1146
 
902
- emitCloneStruct(structInfo, baseWType) {
1147
+ emitCloneStruct(structInfo, baseWType, dstInfo = structInfo) {
903
1148
  const tmp = this.acquireTemp(baseWType);
904
1149
  this.pushInstruction({ op: "local.set", index: tmp });
905
1150
  for (let i = 0; i < structInfo.fields.length; i++) {
@@ -909,18 +1154,117 @@ export class FunctionEmitter {
909
1154
  typeIndex: structInfo.typeIndex,
910
1155
  fieldIndex: i,
911
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
+ }
912
1169
  }
913
1170
  this.pushInstruction({
914
1171
  op: "struct.new",
915
- typeIndex: structInfo.typeIndex,
1172
+ typeIndex: dstInfo.typeIndex,
916
1173
  });
917
1174
  this.releaseTemp(tmp, baseWType);
918
1175
  }
919
1176
 
920
- // Go copies structs on assignment. After `node` has been emitted (value on
921
- // the stack), clone it unless it is already a fresh temporary
922
- // (composite literal or `*p` deref) or not a value struct at all.
923
- _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
+ }
924
1268
  if (
925
1269
  !isStructType(goType, this.mod.checker, this.mod) ||
926
1270
  isPointerToStruct(goType, this.mod.checker, this.mod)
@@ -958,6 +1302,12 @@ export class FunctionEmitter {
958
1302
  if (recvType) {
959
1303
  let t = recvType;
960
1304
  while (t.kind === "pointer") t = t.base;
1305
+ if (t.kind === "SelectorExpr") {
1306
+ const pkg = t.expr?.name;
1307
+ if (pkg) return `${pkg}.${t.field}`;
1308
+ }
1309
+ if (t.kind === "simd")
1310
+ return t.simd === "Mask32x4" ? "simd.Mask32x4" : "simd.F32x4";
961
1311
  if (t.name) return t.name;
962
1312
  if (t.kind === "named") return t.name;
963
1313
  if (t.kind === "TypeName" || t.kind === "Ident") return t.name;
@@ -975,11 +1325,23 @@ export class FunctionEmitter {
975
1325
  return null;
976
1326
  }
977
1327
 
978
- emitInterfaceDispatcher(fn) {
979
- 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(
980
1331
  fn._ifaceType,
981
1332
  fn._methodName,
982
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);
983
1345
  const recvLocal = 0; // __recv is local 0
984
1346
  // Soft dispatchers (errors.Unwrap) return nil instead of panicking when
985
1347
  // the receiver is nil or has no such method.
@@ -1025,6 +1387,8 @@ export class FunctionEmitter {
1025
1387
  this.pushInstruction("i32.eqz");
1026
1388
  this.pushInstruction({ op: "br_if", depth: 0 });
1027
1389
 
1390
+ this._emitNilPtrBoxPanic(cand, recvLocal);
1391
+
1028
1392
  // Matched! Cast receiver
1029
1393
  this.pushInstruction({ op: "local.get", index: recvLocal });
1030
1394
  this.pushInstruction({
@@ -1040,6 +1404,12 @@ export class FunctionEmitter {
1040
1404
  });
1041
1405
  if (cand.embedPath) {
1042
1406
  for (const step of cand.embedPath) {
1407
+ if (step.castTypeIndex) {
1408
+ this.pushInstruction({
1409
+ op: "ref.cast",
1410
+ typeIndex: step.castTypeIndex,
1411
+ });
1412
+ }
1043
1413
  this.pushInstruction({
1044
1414
  op: "struct.get",
1045
1415
  typeIndex: step.parentTypeIndex,
@@ -1075,6 +1445,28 @@ export class FunctionEmitter {
1075
1445
  emitMiss("interface conversion: nil or unmatched type for method call");
1076
1446
  }
1077
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
+
1078
1470
  _getArraySize(t, defaultSize = 0) {
1079
1471
  if (!t) return defaultSize;
1080
1472
  if (typeof t.size === "number") return t.size;
@@ -1082,6 +1474,10 @@ export class FunctionEmitter {
1082
1474
  if (typeof t.len === "number") return t.len;
1083
1475
  if (t.len?.value !== undefined) return Number(t.len.value);
1084
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
+ }
1085
1481
  return defaultSize;
1086
1482
  }
1087
1483
 
@@ -1106,11 +1502,29 @@ export class FunctionEmitter {
1106
1502
  this.pushInstruction({ op: "f32.const", value: 0.0 });
1107
1503
  } else if (wType === "f64") {
1108
1504
  this.pushInstruction({ op: "f64.const", value: 0.0 });
1505
+ } else if (wType === "v128") {
1506
+ this.pushInstruction({ op: "v128.const", bytes: new Array(16).fill(0) });
1109
1507
  } else if (
1110
1508
  typeof wType === "object" &&
1111
1509
  wType !== null &&
1112
1510
  wType.kind === "ref"
1113
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
+ }
1114
1528
  if (
1115
1529
  goType &&
1116
1530
  goType.kind !== "pointer" &&
@@ -1118,7 +1532,10 @@ export class FunctionEmitter {
1118
1532
  ) {
1119
1533
  const sName =
1120
1534
  goType.name ?? (goType.kind === "named" ? goType.name : null);
1121
- 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 });
1122
1539
  if (structInfo) {
1123
1540
  for (const f of structInfo.fields) {
1124
1541
  this.emitZeroValue(f.goType, f.wType);
@@ -1135,10 +1552,15 @@ export class FunctionEmitter {
1135
1552
  goType.kind !== "pointer" &&
1136
1553
  isArrayType(goType, this.mod.checker)
1137
1554
  ) {
1138
- const elemType = goType.elem;
1555
+ const elemType = this._getArrayElemType(goType);
1139
1556
  const arrInfo = this.mod.getArrayType(elemType);
1140
1557
  const size = this._getArraySize(goType);
1141
- 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
+ ) {
1142
1564
  for (let i = 0; i < size; i++) {
1143
1565
  this.emitZeroValue(elemType, arrInfo.elemWType);
1144
1566
  }
@@ -1278,8 +1700,9 @@ export class FunctionEmitter {
1278
1700
  return 0;
1279
1701
  }
1280
1702
 
1281
- resolveBranchDepthToOob() {
1282
- 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");
1283
1706
  }
1284
1707
 
1285
1708
  resolveBranchDepthToRole(targetRole) {
@@ -1300,17 +1723,43 @@ export class FunctionEmitter {
1300
1723
  }
1301
1724
  }
1302
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.
1303
1729
  emitStmt(stmt) {
1304
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) {
1305
1744
  switch (stmt.kind) {
1306
1745
  case "Block":
1307
1746
  this.emitBlock(stmt);
1308
1747
  break;
1309
1748
 
1310
1749
  case "VarDecl":
1750
+ case "ConstDecl":
1311
1751
  this.emitVarDecl(stmt);
1312
1752
  break;
1313
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
+
1314
1763
  case "DefineStmt":
1315
1764
  this.emitDefineStmt(stmt);
1316
1765
  break;
@@ -1378,7 +1827,7 @@ export class FunctionEmitter {
1378
1827
  }
1379
1828
  }
1380
1829
 
1381
- function copyMethods(target, source) {
1830
+ export function copyMethods(target, source) {
1382
1831
  const descriptors = Object.getOwnPropertyDescriptors(source.prototype);
1383
1832
  delete descriptors.constructor;
1384
1833
  for (const key of Object.keys(descriptors)) {
@@ -1395,10 +1844,5 @@ copyMethods(FunctionEmitter, MapsEmitter);
1395
1844
  copyMethods(FunctionEmitter, StmtsEmitter);
1396
1845
  copyMethods(FunctionEmitter, ExprsEmitter);
1397
1846
  copyMethods(FunctionEmitter, BuiltinsEmitter);
1398
- copyMethods(FunctionEmitter, StdlibCoreEmitter);
1399
- copyMethods(FunctionEmitter, BytesEmitter);
1400
- copyMethods(FunctionEmitter, PurePackagesEmitter);
1401
- copyMethods(FunctionEmitter, IoEmitter);
1402
- copyMethods(FunctionEmitter, TimeEmitter);
1403
- copyMethods(FunctionEmitter, ExtendedPackagesEmitter);
1847
+ for (const emitter of stdlibEmitters) copyMethods(FunctionEmitter, emitter);
1404
1848
  copyMethods(FunctionEmitter, SharedEmitter);