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
@@ -2,15 +2,25 @@
2
2
  // FunctionEmitter mixin: panic/recover, Go builtins (len, append, make, …) and
3
3
  // call expression dispatch (static, method, closure, package).
4
4
 
5
+ import { hasCallOrReceive } from "../../lower/walk.js";
6
+ import { typeStr } from "../../typechecker/types.js";
7
+ import { goTypeName } from "../shared/fmt-types.js";
5
8
  import {
9
+ anonIfaceName,
10
+ ERROR_STRUCT,
6
11
  getMapKeyValTypes,
7
12
  isArrayType,
13
+ isComplex,
14
+ isComplex64,
8
15
  isMapType,
9
16
  isNonEmptyInterface,
17
+ isScalarType,
18
+ isSigned,
10
19
  isSliceType,
11
20
  isStringType,
12
21
  isStructType,
13
- isTestingT,
22
+ isUnhashableGoType,
23
+ printOperandKind,
14
24
  toWasmType,
15
25
  } from "./types.js";
16
26
 
@@ -20,13 +30,32 @@ export class BuiltinsEmitter {
20
30
  const funcIdx = this.mod.getStringImportIndex();
21
31
  this.pushInstruction({ op: "i32.const", value: strIdx });
22
32
  this.pushInstruction({ op: "call", funcIndex: funcIdx });
23
- this.pushInstruction("any.convert_extern");
24
- this.emitPanicThrow();
33
+ this.emitRuntimePanicThrow();
34
+ }
35
+
36
+ // Raises a Go runtime error from the externref message on the stack. A
37
+ // program that uses `error` recovers it as an `error` value (as Go's
38
+ // `runtime.Error`); the host still reports the message text.
39
+ emitRuntimePanicThrow() {
40
+ if (!this.mod.hasErrorType) {
41
+ this.pushInstruction("any.convert_extern");
42
+ this.emitPanicThrow();
43
+ return;
44
+ }
45
+ const msgTmp = this.acquireTemp("externref");
46
+ this.pushInstruction({ op: "local.tee", index: msgTmp });
47
+ this.pushInstruction({
48
+ op: "call",
49
+ funcIndex: this.mod.resolveFuncIndex("__mk_error"),
50
+ });
51
+ this.emitPanicThrow(msgTmp);
52
+ this.releaseTemp(msgTmp, "externref");
25
53
  }
26
54
 
27
- // Consumes the anyref message on the stack, pushes a PanicNode, and raises the panic via
28
- // `env.panic`; `unreachable` tells the validator control never returns.
29
- emitPanicThrow() {
55
+ // Consumes the anyref value on the stack, pushes a PanicNode, and raises the
56
+ // panic via `env.panic` (with the value, or the externref message in
57
+ // `hostMsgTmp`); `unreachable` tells the validator control never returns.
58
+ emitPanicThrow(hostMsgTmp = null) {
30
59
  const panicNodeTypeIndex = this.mod.getPanicNodeTypeIndex();
31
60
  const panicGlobal = this.mod.getPanicGlobalIndex();
32
61
  const tmpVal = this.acquireTemp("anyref");
@@ -43,8 +72,12 @@ export class BuiltinsEmitter {
43
72
  this.pushInstruction({ op: "global.set", index: panicGlobal });
44
73
 
45
74
  // Prepare externref for env.panic
46
- this.pushInstruction({ op: "local.get", index: tmpVal });
47
- this.pushInstruction("extern.convert_any");
75
+ if (hostMsgTmp !== null) {
76
+ this.pushInstruction({ op: "local.get", index: hostMsgTmp });
77
+ } else {
78
+ this.pushInstruction({ op: "local.get", index: tmpVal });
79
+ this.pushInstruction("extern.convert_any");
80
+ }
48
81
  this.pushInstruction({
49
82
  op: "call",
50
83
  funcIndex: this.mod.getPanicImportIndex(),
@@ -65,9 +98,32 @@ export class BuiltinsEmitter {
65
98
  return null;
66
99
  }
67
100
 
101
+ // `len`/`cap` of an array or array pointer: the constant length. An
102
+ // operand containing a call or receive is still evaluated, and its value
103
+ // (a nil array pointer included) is dropped without being dereferenced.
104
+ _emitConstArrayLen(call, targetWasmType) {
105
+ const arg = call.args[0];
106
+ if (hasCallOrReceive(arg)) {
107
+ this.emitExpr(
108
+ arg,
109
+ this.toWasmType(arg._type ?? this._resolveExprGoType(arg)),
110
+ );
111
+ this.pushInstruction("drop");
112
+ }
113
+ this.pushInstruction({ op: "i32.const", value: call._constLen });
114
+ if ((targetWasmType ?? "i64") === "i64")
115
+ this.pushInstruction("i64.extend_i32_u");
116
+ }
117
+
68
118
  _emitBuiltinLen(call, targetWasmType) {
69
119
  const arg = call.args[0];
70
120
  const argType = arg._type ?? this._resolveExprGoType(arg);
121
+ // `len` of an array (or a pointer to one) is a constant, and the
122
+ // operand is not evaluated unless it contains a call or a receive.
123
+ if (call._constLen != null) {
124
+ this._emitConstArrayLen(call, targetWasmType);
125
+ return;
126
+ }
71
127
  if (this._sharedInfo(argType)) {
72
128
  this.emitSharedLen(arg, targetWasmType);
73
129
  return;
@@ -135,6 +191,10 @@ export class BuiltinsEmitter {
135
191
  _emitBuiltinCap(call, targetWasmType) {
136
192
  const arg = call.args[0];
137
193
  const argType = arg._type;
194
+ if (call._constLen != null) {
195
+ this._emitConstArrayLen(call, targetWasmType);
196
+ return;
197
+ }
138
198
  if (isSliceType(argType, this.mod.checker)) {
139
199
  const elemType = this._getSliceElemType(argType);
140
200
  const sliceInfo = this.mod.getSliceType(elemType);
@@ -217,18 +277,26 @@ export class BuiltinsEmitter {
217
277
  this.pushInstruction({ op: "local.set", index: capTmp });
218
278
  }
219
279
 
280
+ // Go: a negative len is a len panic; otherwise cap < len is a cap panic.
220
281
  this.pushInstruction({ op: "local.get", index: lenTmp });
221
- this.pushInstruction({ op: "local.get", index: capTmp });
222
- this.pushInstruction("i32.gt_u");
223
- this.pushInstruction({ op: "local.get", index: capTmp });
224
282
  this.pushInstruction({ op: "i32.const", value: 0 });
225
283
  this.pushInstruction("i32.lt_s");
226
- this.pushInstruction("i32.or");
227
284
  this.pushInstruction({ op: "if", blockType: "void" });
228
285
  this.emitPanic("runtime error: makeslice: len out of range");
229
286
  this.pushInstruction("end");
287
+ this.pushInstruction({ op: "local.get", index: capTmp });
288
+ this.pushInstruction({ op: "local.get", index: lenTmp });
289
+ this.pushInstruction("i32.lt_s");
290
+ this.pushInstruction({ op: "if", blockType: "void" });
291
+ this.emitPanic("runtime error: makeslice: cap out of range");
292
+ this.pushInstruction("end");
230
293
 
231
- if (isStructType(elemGoType, this.mod.checker, this.mod)) {
294
+ if (
295
+ isStructType(elemGoType, this.mod.checker, this.mod) ||
296
+ isComplex(elemGoType) ||
297
+ (elemGoType?.kind !== "pointer" &&
298
+ isArrayType(elemGoType, this.mod.checker))
299
+ ) {
232
300
  const arrTmp = this.acquireTemp({
233
301
  kind: "ref",
234
302
  nullable: true,
@@ -283,11 +351,9 @@ export class BuiltinsEmitter {
283
351
  typeIndex: arrInfo.typeIndex,
284
352
  });
285
353
  } else {
286
- this.pushInstruction({ op: "local.get", index: capTmp });
287
- this.pushInstruction({
288
- op: "array.new_default",
289
- typeIndex: arrInfo.typeIndex,
290
- });
354
+ this._emitNewArray(elemGoType, arrInfo, () =>
355
+ this.pushInstruction({ op: "local.get", index: capTmp }),
356
+ );
291
357
  this.pushInstruction({ op: "i32.const", value: 0 });
292
358
  this.pushInstruction({ op: "local.get", index: lenTmp });
293
359
  this.pushInstruction({ op: "local.get", index: capTmp });
@@ -302,6 +368,49 @@ export class BuiltinsEmitter {
302
368
  }
303
369
  }
304
370
 
371
+ _emitBuiltinNew(call) {
372
+ const targetType =
373
+ call._type?.base ??
374
+ (call.args[0]?.kind === "TypeExpr"
375
+ ? call.args[0].type
376
+ : call.args[0]?._type) ??
377
+ call.args[0];
378
+ if (isStructType(targetType, this.mod.checker, this.mod)) {
379
+ let structInfo = this._resolveStructInfo({ _type: targetType });
380
+ if (!structInfo) {
381
+ const typeName =
382
+ targetType.kind === "named" ? targetType.name : targetType.name;
383
+ if (typeName) structInfo = this.mod.getStructType(typeName);
384
+ }
385
+ if (!structInfo) {
386
+ throw new Error(
387
+ `Unknown struct in new: ${targetType.name ?? targetType}`,
388
+ );
389
+ }
390
+ for (const f of structInfo.fields) {
391
+ this.emitZeroValue(f.goType, f.wType);
392
+ }
393
+ this.pushInstruction({
394
+ op: "struct.new",
395
+ typeIndex: structInfo.typeIndex,
396
+ });
397
+ return;
398
+ }
399
+ if (isArrayType(targetType, this.mod.checker)) {
400
+ const wType = this.toWasmType(targetType);
401
+ this.emitZeroValue(targetType, wType);
402
+ return;
403
+ }
404
+ const box = this.mod.getBoxType(targetType);
405
+ const valWType = this.toWasmType(targetType);
406
+ this.emitZeroValue(targetType, valWType);
407
+ this.pushInstruction({
408
+ op: "struct.new",
409
+ typeIndex: box.typeIndex,
410
+ typeName: box.name,
411
+ });
412
+ }
413
+
305
414
  // With `newLen` already computed: when it exceeds the old capacity, allocate
306
415
  // a doubled (min 2, at least newLen) backing array and copy the old elements
307
416
  // into it at offset 0; otherwise reuse the old array, offset and capacity.
@@ -343,11 +452,9 @@ export class BuiltinsEmitter {
343
452
  this.pushInstruction({ op: "local.set", index: newCapTmp });
344
453
  this.pushInstruction("end");
345
454
 
346
- this.pushInstruction({ op: "local.get", index: newCapTmp });
347
- this.pushInstruction({
348
- op: "array.new_default",
349
- typeIndex: arrInfo.typeIndex,
350
- });
455
+ this._emitNewArray(elemGoType, arrInfo, () =>
456
+ this.pushInstruction({ op: "local.get", index: newCapTmp }),
457
+ );
351
458
  this.pushInstruction({ op: "local.set", index: newArrTmp });
352
459
  this.pushInstruction({ op: "i32.const", value: 0 });
353
460
  this.pushInstruction({ op: "local.set", index: newOffTmp });
@@ -374,6 +481,17 @@ export class BuiltinsEmitter {
374
481
  this.pushInstruction("end");
375
482
  }
376
483
 
484
+ // The source operand of `append(b, x...)` and `copy(b, x)`: a string
485
+ // (the only non-slice form Go allows) is converted to a fresh byte slice.
486
+ _emitSliceOperand(node, elemGoType, sliceWType) {
487
+ if (isStringType(node._type, this.mod.checker)) {
488
+ this.emitExpr(node, "externref");
489
+ this._emitStringToByteSlice({ kind: "slice", elem: elemGoType });
490
+ return;
491
+ }
492
+ this.emitExpr(node, sliceWType);
493
+ }
494
+
377
495
  _emitBuiltinAppend(call) {
378
496
  const { args } = call;
379
497
  const sNode = args[0];
@@ -428,7 +546,7 @@ export class BuiltinsEmitter {
428
546
 
429
547
  if (args.length === 2 && args[1]._spread) {
430
548
  const s2Tmp = this.acquireTemp(sliceWType);
431
- this.emitExpr(args[1], sliceWType);
549
+ this._emitSliceOperand(args[1], elemGoType, sliceWType);
432
550
  this.pushInstruction({ op: "local.set", index: s2Tmp });
433
551
 
434
552
  const s2ArrTmp = this.acquireTemp(arrWType);
@@ -487,7 +605,7 @@ export class BuiltinsEmitter {
487
605
  this.pushInstruction("i32.add");
488
606
  }
489
607
  this.emitExpr(elemNode, arrInfo.elemWType);
490
- this._emitCopyIfValueStruct(elemNode, elemGoType, arrInfo.elemWType);
608
+ this._emitCopyIfValue(elemNode, elemGoType, arrInfo.elemWType);
491
609
  this.pushInstruction({
492
610
  op: "array.set",
493
611
  typeIndex: arrInfo.typeIndex,
@@ -538,7 +656,7 @@ export class BuiltinsEmitter {
538
656
  this.pushInstruction({ op: "local.set", index: dstTmp });
539
657
 
540
658
  const srcTmp = this.acquireTemp(sliceWType);
541
- this.emitExpr(srcNode, sliceWType);
659
+ this._emitSliceOperand(srcNode, elemGoType, sliceWType);
542
660
  this.pushInstruction({ op: "local.set", index: srcTmp });
543
661
 
544
662
  const nTmp = this.acquireTemp("i32");
@@ -666,13 +784,21 @@ export class BuiltinsEmitter {
666
784
  for (let i = 0; i < args.length; i++) {
667
785
  const arg = args[i];
668
786
  const isLast = i === args.length - 1 && isPrintln;
669
- const isBool =
670
- arg._type?.name === "bool" ||
671
- (arg.kind === "BasicLit" && arg.litKind === "BOOL");
672
- const wType = toWasmType(arg._type, this.mod.checker);
673
- this.emitExpr(arg, wType);
787
+ const { wType, isBool, widen } = printOperandKind(arg, this.mod.checker);
788
+ this.emitExpr(
789
+ arg,
790
+ widen ? "i32" : toWasmType(arg._type, this.mod.checker),
791
+ );
792
+ if (widen) this.pushInstruction("i64.extend_i32_u");
674
793
  const logFuncIdx = this.mod.getLogImportIndex(wType, isLast, isBool);
675
794
  this.pushInstruction({ op: "call", funcIndex: logFuncIdx });
795
+ // `print` never separates operands; `println` always does.
796
+ if (!isPrintln) {
797
+ this.pushInstruction({
798
+ op: "call",
799
+ funcIndex: this.mod.getPrintResetIndex(),
800
+ });
801
+ }
676
802
  }
677
803
  }
678
804
 
@@ -688,7 +814,11 @@ export class BuiltinsEmitter {
688
814
  } else {
689
815
  const goType = this._resolveExprGoType(arg);
690
816
  const wType = toWasmType(goType, this.mod.checker);
691
- if (
817
+ if (goType?.kind === "named" && wType === "externref") {
818
+ // A defined string type keeps its identity (and methods) in the
819
+ // panic value, so `recover()` returns it, not the bare string.
820
+ this.emitExpr(arg, "anyref");
821
+ } else if (
692
822
  isStringType(goType, this.mod.checker) ||
693
823
  (arg.kind === "BasicLit" && arg.litKind === "STRING") ||
694
824
  wType === "externref"
@@ -795,6 +925,136 @@ export class BuiltinsEmitter {
795
925
  }
796
926
  }
797
927
 
928
+ _emitBuiltinComplex(call, targetWasmType) {
929
+ const arg0 = call.args[0];
930
+ const arg1 = call.args[1];
931
+ const callType = call._type;
932
+ const is64 =
933
+ callType?.name === "complex64" ||
934
+ arg0._type?.name === "float32" ||
935
+ arg1._type?.name === "float32" ||
936
+ this._isComplex64Target(targetWasmType);
937
+ const complexName = is64 ? "complex64" : "complex128";
938
+ const info = this.mod.getComplexType(complexName);
939
+ this.emitExpr(arg0, info.elemWType);
940
+ this.emitExpr(arg1, info.elemWType);
941
+ this.pushInstruction({
942
+ op: "struct.new",
943
+ typeIndex: info.typeIndex,
944
+ typeName: info.name,
945
+ });
946
+ }
947
+
948
+ _emitBuiltinReal(call, targetWasmType) {
949
+ const arg = call.args[0];
950
+ const argType = arg._type ?? this._resolveExprGoType(arg);
951
+ const is64 = isComplex64(argType);
952
+ const complexName = is64 ? "complex64" : "complex128";
953
+ const info = this.mod.getComplexType(complexName);
954
+ const complexWType = {
955
+ kind: "ref",
956
+ nullable: true,
957
+ typeIndex: info.typeIndex,
958
+ typeName: info.name,
959
+ };
960
+ this.emitExpr(arg, complexWType);
961
+ this.pushInstruction({
962
+ op: "struct.get",
963
+ typeIndex: info.typeIndex,
964
+ typeName: info.name,
965
+ fieldIndex: 0,
966
+ });
967
+ if (targetWasmType && targetWasmType !== info.elemWType) {
968
+ if (info.elemWType === "f32" && targetWasmType === "f64") {
969
+ this.pushInstruction("f64.promote_f32");
970
+ } else if (info.elemWType === "f64" && targetWasmType === "f32") {
971
+ this.pushInstruction("f32.demote_f64");
972
+ }
973
+ }
974
+ }
975
+
976
+ _emitBuiltinImag(call, targetWasmType) {
977
+ const arg = call.args[0];
978
+ const argType = arg._type ?? this._resolveExprGoType(arg);
979
+ const is64 = isComplex64(argType);
980
+ const complexName = is64 ? "complex64" : "complex128";
981
+ const info = this.mod.getComplexType(complexName);
982
+ const complexWType = {
983
+ kind: "ref",
984
+ nullable: true,
985
+ typeIndex: info.typeIndex,
986
+ typeName: info.name,
987
+ };
988
+ this.emitExpr(arg, complexWType);
989
+ this.pushInstruction({
990
+ op: "struct.get",
991
+ typeIndex: info.typeIndex,
992
+ typeName: info.name,
993
+ fieldIndex: 1,
994
+ });
995
+ if (targetWasmType && targetWasmType !== info.elemWType) {
996
+ if (info.elemWType === "f32" && targetWasmType === "f64") {
997
+ this.pushInstruction("f64.promote_f32");
998
+ } else if (info.elemWType === "f64" && targetWasmType === "f32") {
999
+ this.pushInstruction("f32.demote_f64");
1000
+ }
1001
+ }
1002
+ }
1003
+
1004
+ // min/max fold left over the operands: floats use f32/f64.min/max (NaN
1005
+ // propagates, -0 < +0), integers `select` on lt/gt, strings compare via the
1006
+ // string import. A single operand is the result itself.
1007
+ _emitBuiltinMinMax(call, targetWasmType) {
1008
+ const isMin = call.func.name === "min";
1009
+ const goType = call._type ?? this._resolveExprGoType(call.args[0]);
1010
+ const wType =
1011
+ goType?.kind === "untyped" && typeof targetWasmType === "string"
1012
+ ? targetWasmType
1013
+ : toWasmType(goType, this.mod.checker);
1014
+ const [first, ...rest] = call.args;
1015
+ this.emitExpr(first, wType);
1016
+ if (rest.length === 0) return;
1017
+ const acc = this.acquireTemp(wType);
1018
+ const next = this.acquireTemp(wType);
1019
+ const isStr = isStringType(goType, this.mod.checker);
1020
+ const isFloatType = wType === "f32" || wType === "f64";
1021
+ const cmp = isMin ? "lt" : "gt";
1022
+ for (const arg of rest) {
1023
+ this.pushInstruction({ op: "local.set", index: acc });
1024
+ this.emitExpr(arg, wType);
1025
+ this.pushInstruction({ op: "local.set", index: next });
1026
+ if (isFloatType) {
1027
+ this.pushInstruction({ op: "local.get", index: acc });
1028
+ this.pushInstruction({ op: "local.get", index: next });
1029
+ this.pushInstruction(`${wType}.${isMin ? "min" : "max"}`);
1030
+ continue;
1031
+ }
1032
+ if (isStr) {
1033
+ // next < acc (min) / next > acc (max) ? next : acc
1034
+ this.pushInstruction({ op: "local.get", index: next });
1035
+ this.pushInstruction({ op: "local.get", index: acc });
1036
+ this.pushInstruction({
1037
+ op: "call",
1038
+ funcIndex: this.mod.getStringCmpImportIndex(isMin ? "<" : ">"),
1039
+ });
1040
+ this.pushInstruction({ op: "if", blockType: wType });
1041
+ this.pushInstruction({ op: "local.get", index: next });
1042
+ this.pushInstruction("else");
1043
+ this.pushInstruction({ op: "local.get", index: acc });
1044
+ this.pushInstruction("end");
1045
+ continue;
1046
+ }
1047
+ this.pushInstruction({ op: "local.get", index: next });
1048
+ this.pushInstruction({ op: "local.get", index: acc });
1049
+ this.pushInstruction({ op: "local.get", index: next });
1050
+ this.pushInstruction({ op: "local.get", index: acc });
1051
+ this.pushInstruction(`${wType}.${cmp}_${isSigned(goType) ? "s" : "u"}`);
1052
+ this.pushInstruction("select");
1053
+ }
1054
+ this.releaseTemp(next, wType);
1055
+ this.releaseTemp(acc, wType);
1056
+ }
1057
+
798
1058
  _emitBuiltinCall(call, targetWasmType) {
799
1059
  const { func } = call;
800
1060
  if (func.kind !== "Ident") return false;
@@ -808,6 +1068,9 @@ export class BuiltinsEmitter {
808
1068
  case "cap":
809
1069
  this._emitBuiltinCap(call, targetWasmType);
810
1070
  return true;
1071
+ case "new":
1072
+ this._emitBuiltinNew(call);
1073
+ return true;
811
1074
  case "make":
812
1075
  this._emitBuiltinMake(call);
813
1076
  return true;
@@ -833,6 +1096,19 @@ export class BuiltinsEmitter {
833
1096
  case "recover":
834
1097
  this._emitBuiltinRecover(call, targetWasmType);
835
1098
  return true;
1099
+ case "complex":
1100
+ this._emitBuiltinComplex(call, targetWasmType);
1101
+ return true;
1102
+ case "real":
1103
+ this._emitBuiltinReal(call, targetWasmType);
1104
+ return true;
1105
+ case "imag":
1106
+ this._emitBuiltinImag(call, targetWasmType);
1107
+ return true;
1108
+ case "min":
1109
+ case "max":
1110
+ this._emitBuiltinMinMax(call, targetWasmType);
1111
+ return true;
836
1112
  default:
837
1113
  return false;
838
1114
  }
@@ -841,95 +1117,17 @@ export class BuiltinsEmitter {
841
1117
  _emitPackageCall(call, targetWasmType) {
842
1118
  const { func, args } = call;
843
1119
  if (func.kind !== "SelectorExpr") return false;
844
- if (isTestingT(func.expr?._type)) {
845
- this.emitTestingCall(func, args);
846
- return true;
847
- }
1120
+ if (
1121
+ func.expr?.name &&
1122
+ (this.resolveLocal(func.expr.name) ||
1123
+ this.mod.resolveGlobal(func.expr.name))
1124
+ )
1125
+ return false;
848
1126
  const pkgName = func.expr?.name;
849
1127
  switch (pkgName) {
850
- case "math":
851
- this.emitMathCall(func.field, args);
852
- return true;
853
- case "bits":
854
- this.emitBitsCall(func.field, args);
855
- return true;
856
- case "maps":
857
- this.emitMapsCall(func.field, args, targetWasmType);
858
- return true;
859
- case "slices":
860
- this.emitSlicesCall(func.field, args, targetWasmType);
861
- return true;
862
- case "strings":
863
- if (func.field === "NewReader") {
864
- this.emitStringsNewReader(call, targetWasmType);
865
- return true;
866
- }
867
- this.emitStringsCall(func.field, args, targetWasmType);
868
- return true;
869
- case "strconv":
870
- this.emitStrconvCall(func.field, args);
871
- return true;
872
- case "fmt":
873
- if (func.field.startsWith("Fprint")) {
874
- this.emitFprintCall(func.field, call, targetWasmType);
875
- return true;
876
- }
877
- if (func.field.startsWith("Sscan")) {
878
- this.emitSscanCall(func.field, call, targetWasmType);
879
- return true;
880
- }
881
- this.emitFmtCall(func.field, args);
882
- return true;
883
- case "errors":
884
- this.emitErrorsCall(func.field, args);
885
- return true;
886
- case "utf8":
887
- this.emitUtf8Call(func.field, args, targetWasmType);
888
- return true;
889
- case "sort":
890
- this.emitSortCall(func.field, args, targetWasmType);
891
- return true;
892
1128
  case "shared":
893
1129
  this.emitSharedNew(func.field, args);
894
1130
  return true;
895
- case "unicode":
896
- this.emitUnicodeCall(func.field, args, targetWasmType);
897
- return true;
898
- case "rand":
899
- this.emitRandCall(func.field, args, targetWasmType);
900
- return true;
901
- case "bytes":
902
- if (func.field === "NewReader") {
903
- this.emitBytesNewReader(call, targetWasmType);
904
- return true;
905
- }
906
- this.emitBytesCall(func.field, args, targetWasmType);
907
- return true;
908
- case "path":
909
- case "filepath":
910
- this.emitPathCall(func.field, args);
911
- return true;
912
- case "html":
913
- this.emitHtmlCall(func.field, args);
914
- return true;
915
- case "io":
916
- this.emitIoCall(func.field, call, targetWasmType);
917
- return true;
918
- case "time":
919
- this.emitTimeCall(func.field, call, targetWasmType);
920
- return true;
921
- case "binary":
922
- this.emitBinaryCall(func.field, call, targetWasmType);
923
- return true;
924
- case "sha256":
925
- this.emitSha256Call(func.field, call, targetWasmType);
926
- return true;
927
- case "hex":
928
- this.emitHexCall(func.field, call, targetWasmType);
929
- return true;
930
- case "base64":
931
- this.emitBase64Call(func.field, call, targetWasmType);
932
- return true;
933
1131
  case "simd":
934
1132
  this.emitSimdPackageCall(func.field, args, targetWasmType);
935
1133
  return true;
@@ -961,29 +1159,13 @@ export class BuiltinsEmitter {
961
1159
  func.expr,
962
1160
  args,
963
1161
  targetWasmType,
1162
+ recvTypeName === "simd.Mask32x4" ||
1163
+ recvType?.underlying?.simd === "Mask32x4" ||
1164
+ recvType?.simd === "Mask32x4",
964
1165
  );
965
1166
  }
966
- if (
967
- this._emitStdlibReceiverMethod(
968
- recvTypeName,
969
- func.field,
970
- func.expr,
971
- args,
972
- targetWasmType,
973
- )
974
- ) {
975
- return true;
976
- }
977
- if (recvTypeName === "strings.Builder") {
978
- this.emitBuilderMethodCall(func.field, func.expr, args);
979
- return true;
980
- }
981
- if (recvTypeName === "strings.Replacer") {
982
- this.emitReplacerMethodCall(func.field, func.expr, args);
983
- return true;
984
- }
985
1167
  if (isNonEmptyInterface(recvType, this.mod.checker)) {
986
- const ifaceName = recvTypeName ?? "anon";
1168
+ const ifaceName = recvTypeName ?? anonIfaceName(recvType);
987
1169
  const dispatchName = `__dispatch_${ifaceName}_${func.field}`;
988
1170
  const targetFuncIdx = this.mod.resolveFuncIndex(dispatchName);
989
1171
  if (targetFuncIdx !== null) {
@@ -994,7 +1176,7 @@ export class BuiltinsEmitter {
994
1176
  const pType = targetParamTypes[i + 1] ?? null;
995
1177
  const pGoType = paramGoTypes[i + 1] ?? null;
996
1178
  this.emitExpr(args[i], pType);
997
- this._emitCopyIfValueStruct(args[i], pGoType, pType);
1179
+ this._emitCopyIfValue(args[i], pGoType, pType);
998
1180
  }
999
1181
  this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
1000
1182
  return true;
@@ -1016,14 +1198,20 @@ export class BuiltinsEmitter {
1016
1198
  if (targetFuncIdx !== null) {
1017
1199
  const recvWType = targetParamTypes[0] ?? null;
1018
1200
  const recvGoType = this.mod.getFuncParamGoTypes(methodName)[0] ?? null;
1019
- this.emitExpr(func.expr, recvWType);
1020
- this._emitCopyIfValueStruct(func.expr, recvGoType, recvWType);
1201
+ this.emitExpr(this._recvOperand(func, recvGoType), recvWType);
1202
+ // `p.M()` with a value receiver reads `*p`: a nil `p` panics here.
1203
+ if (
1204
+ recvGoType?.kind !== "pointer" &&
1205
+ isStructType(recvGoType, this.mod.checker, this.mod)
1206
+ )
1207
+ this._emitNilCheck(func.expr._type);
1208
+ this._emitCopyIfValue(func.expr, recvGoType, recvWType);
1021
1209
  const paramGoTypes = this.mod.getFuncParamGoTypes(methodName);
1022
1210
  for (let i = 0; i < args.length; i++) {
1023
1211
  const pType = targetParamTypes[i + 1] ?? null;
1024
1212
  const pGoType = paramGoTypes[i + 1] ?? null;
1025
1213
  this.emitExpr(args[i], pType);
1026
- this._emitCopyIfValueStruct(args[i], pGoType, pType);
1214
+ this._emitCopyIfValue(args[i], pGoType, pType);
1027
1215
  }
1028
1216
  this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
1029
1217
  return true;
@@ -1032,111 +1220,97 @@ export class BuiltinsEmitter {
1032
1220
  return false;
1033
1221
  }
1034
1222
 
1035
- _emitStdlibReceiverMethod(
1036
- recvTypeName,
1037
- field,
1038
- expr,
1039
- args,
1040
- targetWasmType = null,
1041
- ) {
1042
- if (recvTypeName === "strings.Builder") {
1043
- this.emitBuilderMethodCall(field, expr, args, targetWasmType);
1044
- return true;
1045
- }
1046
- if (recvTypeName === "strings.Replacer") {
1047
- this.emitReplacerMethodCall(field, expr, args);
1048
- return true;
1049
- }
1050
- if (recvTypeName === "bytes.Buffer") {
1051
- this.emitBytesBufferMethodCall(field, expr, args, targetWasmType);
1052
- return true;
1053
- }
1054
- if (recvTypeName === "strings.Reader") {
1055
- this.emitStringsReaderMethodCall(field, expr, args, targetWasmType);
1056
- return true;
1057
- }
1058
- if (recvTypeName === "bytes.Reader") {
1059
- this.emitBytesReaderMethodCall(field, expr, args, targetWasmType);
1060
- return true;
1061
- }
1062
- if (recvTypeName === "time.Time") {
1063
- this.emitTimeMethodCall(field, expr, args, targetWasmType);
1064
- return true;
1065
- }
1066
- if (recvTypeName === "time.Duration") {
1067
- this.emitDurationMethodCall(field, expr, args, targetWasmType);
1068
- return true;
1069
- }
1070
- if (recvTypeName === "io.LimitedReader") {
1071
- this.emitIoLimitedReaderMethodCall(field, expr, args, targetWasmType);
1072
- return true;
1073
- }
1074
- if (recvTypeName === "sync.Mutex" || recvTypeName === "sync.RWMutex") {
1075
- this.emitSyncMutexMethodCall(field, expr, args, targetWasmType);
1076
- return true;
1077
- }
1078
- if (recvTypeName === "sync.Once") {
1079
- this.emitSyncOnceMethodCall(field, expr, args, targetWasmType);
1080
- return true;
1081
- }
1082
- if (recvTypeName === "sync.WaitGroup") {
1083
- this.emitSyncWaitGroupMethodCall(field, expr, args, targetWasmType);
1084
- return true;
1223
+ // The receiver operand of a method call: `p.Get()` with a value receiver
1224
+ // and `p *N` (a named scalar or slice) passes `*p`.
1225
+ _recvOperand(func, recvGoType) {
1226
+ const t = func.expr._type;
1227
+ // A pointer receiver on `*p` gets `p` itself, so the method mutates *p.
1228
+ if (recvGoType?.kind === "pointer") {
1229
+ const pointer = this._structDerefPointer(func.expr);
1230
+ if (pointer) return pointer;
1085
1231
  }
1086
1232
  if (
1087
- recvTypeName === "binary.LittleEndian" ||
1088
- recvTypeName === "binary.BigEndian" ||
1089
- recvTypeName === "encoding/binary.LittleEndian" ||
1090
- recvTypeName === "encoding/binary.BigEndian" ||
1091
- recvTypeName === "binary.ByteOrder" ||
1092
- recvTypeName === "encoding/binary.ByteOrder"
1093
- ) {
1094
- this.emitBinaryOrderMethodCall(
1095
- recvTypeName,
1096
- field,
1097
- expr,
1098
- args,
1099
- targetWasmType,
1100
- );
1101
- return true;
1102
- }
1233
+ t?.kind === "pointer" &&
1234
+ recvGoType?.kind !== "pointer" &&
1235
+ (isScalarType(t.base, this.mod.checker, this.mod) ||
1236
+ isSliceType(t.base, this.mod.checker))
1237
+ )
1238
+ return { kind: "UnaryExpr", op: "*", operand: func.expr, _type: t.base };
1239
+ return this._ptrRecvOperand(func);
1240
+ }
1241
+
1242
+ // `x.M()` with a pointer receiver on a named scalar value passes `&x`
1243
+ // (the typechecker boxed `x`).
1244
+ _ptrRecvOperand(func) {
1245
+ const t = func.expr._type;
1246
+ if (!func._type?._ptrRecv || t?.kind !== "named") return func.expr;
1103
1247
  if (
1104
- recvTypeName === "base64.Encoding" ||
1105
- recvTypeName === "encoding/base64.Encoding"
1106
- ) {
1107
- this.emitBase64EncodingMethodCall(field, expr, args, targetWasmType);
1108
- return true;
1248
+ !isScalarType(t, this.mod.checker, this.mod) &&
1249
+ !isSliceType(t, this.mod.checker)
1250
+ )
1251
+ return func.expr;
1252
+ return {
1253
+ kind: "UnaryExpr",
1254
+ op: "&",
1255
+ operand: func.expr,
1256
+ _type: { kind: "pointer", base: t },
1257
+ };
1258
+ }
1259
+
1260
+ // The embedded struct (possibly several levels down) that declares or
1261
+ // forwards `method`: the chain of embeds to follow and the method's name.
1262
+ _findEmbeddedMethod(structInfo, method, seen = new Set()) {
1263
+ if (seen.has(structInfo)) return null;
1264
+ seen.add(structInfo);
1265
+ for (const embed of structInfo.embeds ?? []) {
1266
+ const typeName = embed.typeName ?? embed.name;
1267
+ const methodName = `${typeName}.${method}`;
1268
+ if (this.mod.resolveFuncIndex(methodName) !== null)
1269
+ return { chain: [{ structInfo, embed }], methodName };
1270
+ const inner = this.mod.getStructType(typeName);
1271
+ const deeper = inner && this._findEmbeddedMethod(inner, method, seen);
1272
+ if (deeper)
1273
+ return {
1274
+ chain: [{ structInfo, embed }, ...deeper.chain],
1275
+ methodName: deeper.methodName,
1276
+ };
1109
1277
  }
1110
- return false;
1278
+ return null;
1111
1279
  }
1112
1280
 
1113
1281
  _emitEmbeddedMethodCall(recvTypeName, func, args, recvType) {
1114
1282
  const structInfo = this.mod.getStructType(recvTypeName);
1115
1283
  if (!structInfo) return false;
1116
- for (const embed of structInfo.embeds) {
1117
- const embedMethodName = `${embed.name}.${func.field}`;
1118
- const subIdx = this.mod.resolveFuncIndex(embedMethodName);
1119
- if (subIdx !== null) {
1120
- const targetParamTypes = this.mod.getFuncParamTypes(embedMethodName);
1121
- const baseWType = this.toWasmType(recvType);
1122
- this.emitExpr(func.expr, baseWType);
1123
- this.pushInstruction({
1124
- op: "struct.get",
1125
- typeIndex: structInfo.typeIndex,
1126
- fieldIndex: embed.fieldIndex,
1127
- });
1128
- for (let i = 0; i < args.length; i++) {
1129
- const pType = targetParamTypes[i + 1] ?? null;
1130
- this.emitExpr(args[i], pType);
1131
- }
1132
- this.pushInstruction({
1133
- op: "call",
1134
- funcIndex: subIdx,
1135
- });
1136
- return true;
1137
- }
1284
+ const found = this._findEmbeddedMethod(structInfo, func.field);
1285
+ if (!found) return false;
1286
+ const { chain, methodName } = found;
1287
+ const subIdx = this.mod.resolveFuncIndex(methodName);
1288
+ const targetParamTypes = this.mod.getFuncParamTypes(methodName);
1289
+ const valueRecv =
1290
+ this.mod.getFuncParamGoTypes(methodName)[0]?.kind !== "pointer";
1291
+ this.emitExpr(func.expr, this.toWasmType(recvType));
1292
+ this._emitNilCheck(func.expr._type);
1293
+ for (const [i, { structInfo: owner, embed }] of chain.entries()) {
1294
+ this.pushInstruction({
1295
+ op: "struct.get",
1296
+ typeIndex: owner.typeIndex,
1297
+ fieldIndex: embed.fieldIndex,
1298
+ });
1299
+ // Reading through an embedded nil pointer (to the next embedded
1300
+ // struct, or as a value receiver) is a recoverable nil dereference.
1301
+ const ft = owner.fields[embed.fieldIndex]?.goType;
1302
+ const isPtr = ft?.kind === "pointer" || ft?.kind === "PointerType";
1303
+ if (isPtr && (i < chain.length - 1 || valueRecv)) this._emitNilBranch();
1138
1304
  }
1139
- return false;
1305
+ for (let i = 0; i < args.length; i++) {
1306
+ const pType = targetParamTypes[i + 1] ?? null;
1307
+ this.emitExpr(args[i], pType);
1308
+ }
1309
+ this.pushInstruction({
1310
+ op: "call",
1311
+ funcIndex: subIdx,
1312
+ });
1313
+ return true;
1140
1314
  }
1141
1315
 
1142
1316
  _emitStaticCall(call) {
@@ -1149,12 +1323,18 @@ export class BuiltinsEmitter {
1149
1323
  if (targetFuncIdx !== null) {
1150
1324
  const paramTypes = this.mod.getFuncParamTypes(func.name);
1151
1325
  const paramGoTypes = this.mod.getFuncParamGoTypes(func.name);
1326
+ if (call._multiForward) {
1327
+ // `f(g())`: g's results are f's arguments as they come.
1328
+ this.emitExpr(args[0], null);
1329
+ this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
1330
+ return true;
1331
+ }
1152
1332
  for (let i = 0; i < args.length; i++) {
1153
1333
  const arg = args[i];
1154
1334
  const pType = paramTypes[i] ?? null;
1155
1335
  const pGoType = paramGoTypes[i] ?? null;
1156
1336
  this.emitExpr(arg, pType);
1157
- this._emitCopyIfValueStruct(arg, pGoType, pType);
1337
+ this._emitCopyIfValue(arg, pGoType, pType);
1158
1338
  }
1159
1339
  this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
1160
1340
  return true;
@@ -1175,6 +1355,8 @@ export class BuiltinsEmitter {
1175
1355
 
1176
1356
  const closureTmp = this.acquireTemp(closureWType);
1177
1357
  this.emitExpr(func, closureWType);
1358
+ // Calling a nil func value panics like a nil pointer dereference.
1359
+ this._emitNilBranch();
1178
1360
  this.pushInstruction({ op: "local.set", index: closureTmp });
1179
1361
 
1180
1362
  // Push param 0: env (anyref)
@@ -1191,7 +1373,7 @@ export class BuiltinsEmitter {
1191
1373
  const pWType = closureInfo.paramWTypes[i] ?? null;
1192
1374
  const pGoType = closureInfo.sig.params[i] ?? null;
1193
1375
  this.emitExpr(arg, pWType);
1194
- this._emitCopyIfValueStruct(arg, pGoType, pWType);
1376
+ this._emitCopyIfValue(arg, pGoType, pWType);
1195
1377
  }
1196
1378
 
1197
1379
  // Push funcref
@@ -1244,4 +1426,499 @@ export class BuiltinsEmitter {
1244
1426
 
1245
1427
  this._emitClosureCall(call);
1246
1428
  }
1429
+
1430
+ emitSliceClear(arg, argType) {
1431
+ const elemGoType = this._getSliceElemType(argType);
1432
+ const sliceInfo = this.mod.getSliceType(elemGoType);
1433
+ const arrInfo = sliceInfo.arrInfo;
1434
+
1435
+ const sliceTmp = this.acquireTemp({
1436
+ kind: "ref",
1437
+ nullable: true,
1438
+ typeIndex: sliceInfo.typeIndex,
1439
+ });
1440
+ this.emitExpr(arg, {
1441
+ kind: "ref",
1442
+ nullable: true,
1443
+ typeIndex: sliceInfo.typeIndex,
1444
+ });
1445
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
1446
+
1447
+ this.pushInstruction({ op: "block", blockType: "void" });
1448
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1449
+ this.pushInstruction("ref.is_null");
1450
+ this.pushInstruction({ op: "br_if", depth: 0 });
1451
+
1452
+ const arrTmp = this.acquireTemp({
1453
+ kind: "ref",
1454
+ nullable: true,
1455
+ typeIndex: arrInfo.typeIndex,
1456
+ });
1457
+ const offTmp = this.acquireTemp("i32");
1458
+ const lenTmp = this.acquireTemp("i32");
1459
+ const iLoc = this.acquireTemp("i32");
1460
+
1461
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
1462
+ arr: arrTmp,
1463
+ off: offTmp,
1464
+ len: lenTmp,
1465
+ });
1466
+
1467
+ this.pushInstruction({ op: "i32.const", value: 0 });
1468
+ this.pushInstruction({ op: "local.set", index: iLoc });
1469
+
1470
+ this.pushInstruction({ op: "block", blockType: "void" });
1471
+ this.pushInstruction({ op: "loop", blockType: "void" });
1472
+ this.pushInstruction({ op: "local.get", index: iLoc });
1473
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1474
+ this.pushInstruction("i32.ge_s");
1475
+ this.pushInstruction({ op: "br_if", depth: 1 });
1476
+
1477
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1478
+ this.pushInstruction({ op: "local.get", index: offTmp });
1479
+ this.pushInstruction({ op: "local.get", index: iLoc });
1480
+ this.pushInstruction("i32.add");
1481
+ this.emitZeroValue(elemGoType, arrInfo.elemWType);
1482
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1483
+
1484
+ this.pushInstruction({ op: "local.get", index: iLoc });
1485
+ this.pushInstruction({ op: "i32.const", value: 1 });
1486
+ this.pushInstruction("i32.add");
1487
+ this.pushInstruction({ op: "local.set", index: iLoc });
1488
+ this.pushInstruction({ op: "br", depth: 0 });
1489
+ this.pushInstruction("end");
1490
+ this.pushInstruction("end");
1491
+
1492
+ this.releaseTemp(iLoc, "i32");
1493
+ this.releaseTemp(lenTmp, "i32");
1494
+ this.releaseTemp(offTmp, "i32");
1495
+ this.releaseTemp(arrTmp, {
1496
+ kind: "ref",
1497
+ nullable: true,
1498
+ typeIndex: arrInfo.typeIndex,
1499
+ });
1500
+
1501
+ this.pushInstruction("end");
1502
+ this.releaseTemp(sliceTmp, {
1503
+ kind: "ref",
1504
+ nullable: true,
1505
+ typeIndex: sliceInfo.typeIndex,
1506
+ });
1507
+ }
1508
+
1509
+ // Pushes an integer-ish argument as i32 (Go ints are i64 in this backend).
1510
+ _emitI32Arg(arg) {
1511
+ const wType = toWasmType(arg?._type, this.mod.checker);
1512
+ if (wType === "i64") {
1513
+ this.emitExpr(arg, "i64");
1514
+ this.pushInstruction("i32.wrap_i64");
1515
+ } else {
1516
+ this.emitExpr(arg, "i32");
1517
+ }
1518
+ }
1519
+
1520
+ // `a == b` for two anyref operands (interface values). `ref.eq` only
1521
+ // accepts eqref, so host strings (internalised externs) are compared via
1522
+ // `str_eq` and anything else by identity.
1523
+ _emitAnyEq() {
1524
+ const idx = this.mod.resolveFuncIndex("__any_eq");
1525
+ if (idx === null) {
1526
+ throw new Error(
1527
+ "internal: anyref equality emitted but __any_eq was not declared (usesAnyEq scan missed a site)",
1528
+ );
1529
+ }
1530
+ this.pushInstruction({ op: "call", funcIndex: idx });
1531
+ }
1532
+
1533
+ // Every `[wasm type index, Go type name]` an `any` can hold in this module,
1534
+ // more specific types last so they overwrite their supertypes.
1535
+ _dynamicTypeNames() {
1536
+ const named = (name) => {
1537
+ const i = name.indexOf("$$");
1538
+ return i < 0
1539
+ ? goTypeName({ kind: "named", name })
1540
+ : `${name.slice(i + 2)}.${name.slice(0, i)}`;
1541
+ };
1542
+ const mod = this.mod;
1543
+ const names = [];
1544
+ for (const s of mod.sliceTypes.values())
1545
+ names.push([
1546
+ s.typeIndex,
1547
+ goTypeName({ kind: "slice", elem: s.elemGoType }),
1548
+ ]);
1549
+ for (const m of mod.mapTypes.values())
1550
+ names.push([
1551
+ m.typeIndex,
1552
+ goTypeName({ kind: "map", key: m.keyGoType, value: m.valGoType }),
1553
+ ]);
1554
+ for (const info of new Set(mod.structTypes.values())) {
1555
+ if (info.isAnon) continue;
1556
+ names.push([
1557
+ info.typeIndex,
1558
+ info.name === ERROR_STRUCT
1559
+ ? "*errors.errorString"
1560
+ : `*${named(info.name)}`,
1561
+ ]);
1562
+ }
1563
+ for (const box of mod.ifaceBoxTypes.values())
1564
+ names.push([box.typeIndex, goTypeName(box.goType)]);
1565
+ for (const info of mod.taggedSliceTypes.values())
1566
+ names.push([info.typeIndex, goTypeName(info.goType)]);
1567
+ for (const info of mod.nilBoxTypes.values())
1568
+ names.push([info.typeIndex, goTypeName(info.goType)]);
1569
+ return names;
1570
+ }
1571
+
1572
+ // Body of `__ta_fail(v any, src string, target string, iface int32)`: panics
1573
+ // with Go's failed type assertion message, naming the dynamic type of `v`
1574
+ // from the types the module knows (`src` is the static interface type, or
1575
+ // the first method the target interface needs when `iface` is 1).
1576
+ _emitTypeAssertFail() {
1577
+ const strConst = (text) => {
1578
+ this.pushInstruction({
1579
+ op: "i32.const",
1580
+ value: this.mod.internString(text),
1581
+ });
1582
+ this.pushInstruction({
1583
+ op: "call",
1584
+ funcIndex: this.mod.getStringImportIndex(),
1585
+ });
1586
+ };
1587
+ const concat = () =>
1588
+ this.pushInstruction({
1589
+ op: "call",
1590
+ funcIndex: this.mod.getStringConcatImportIndex(),
1591
+ });
1592
+ const dyn = this.allocLocal(null, "externref");
1593
+ strConst("interface {}");
1594
+ this.pushInstruction({ op: "local.set", index: dyn });
1595
+ const when = (typeIndex, text) => {
1596
+ this.pushInstruction({ op: "local.get", index: 0 });
1597
+ this.pushInstruction({ op: "ref.test", typeIndex });
1598
+ this.pushInstruction({ op: "if", blockType: "void" });
1599
+ strConst(text);
1600
+ this.pushInstruction({ op: "local.set", index: dyn });
1601
+ this.pushInstruction("end");
1602
+ };
1603
+ for (const [typeIndex, text] of this._dynamicTypeNames())
1604
+ when(typeIndex, text);
1605
+ this.pushInstruction({ op: "local.get", index: 0 });
1606
+ this.pushInstruction({
1607
+ op: "call",
1608
+ funcIndex: this.mod.getIsStringImportIndex(),
1609
+ });
1610
+ this.pushInstruction({ op: "if", blockType: "void" });
1611
+ strConst("string");
1612
+ this.pushInstruction({ op: "local.set", index: dyn });
1613
+ this.pushInstruction("end");
1614
+ this.pushInstruction({ op: "local.get", index: 0 });
1615
+ this.pushInstruction("ref.is_null");
1616
+ this.pushInstruction({ op: "if", blockType: "void" });
1617
+ strConst("nil");
1618
+ this.pushInstruction({ op: "local.set", index: dyn });
1619
+ this.pushInstruction("end");
1620
+
1621
+ // An interface target: `<dyn> is not <target>: missing method <M>`.
1622
+ this.pushInstruction({ op: "local.get", index: 3 });
1623
+ this.pushInstruction({ op: "if", blockType: "void" });
1624
+ this.pushInstruction({ op: "local.get", index: 0 });
1625
+ this.pushInstruction("ref.is_null");
1626
+ this.pushInstruction({ op: "if", blockType: "void" });
1627
+ strConst("interface conversion: interface is nil, not ");
1628
+ this.pushInstruction({ op: "local.get", index: 2 });
1629
+ concat();
1630
+ this.emitRuntimePanicThrow();
1631
+ this.pushInstruction("end");
1632
+ strConst("interface conversion: ");
1633
+ this.pushInstruction({ op: "local.get", index: dyn });
1634
+ concat();
1635
+ strConst(" is not ");
1636
+ concat();
1637
+ this.pushInstruction({ op: "local.get", index: 2 });
1638
+ concat();
1639
+ strConst(": missing method ");
1640
+ concat();
1641
+ this.pushInstruction({ op: "local.get", index: 1 });
1642
+ concat();
1643
+ this.emitRuntimePanicThrow();
1644
+ this.pushInstruction("end");
1645
+
1646
+ strConst("interface conversion: ");
1647
+ this.pushInstruction({ op: "local.get", index: 1 });
1648
+ concat();
1649
+ strConst(" is ");
1650
+ concat();
1651
+ this.pushInstruction({ op: "local.get", index: dyn });
1652
+ concat();
1653
+ strConst(", not ");
1654
+ concat();
1655
+ this.pushInstruction({ op: "local.get", index: 2 });
1656
+ concat();
1657
+ this.emitRuntimePanicThrow();
1658
+ }
1659
+
1660
+ emitRuntimeHelper(fn) {
1661
+ const errInfo = this.mod.getStructType(ERROR_STRUCT);
1662
+ switch (fn._isRuntimeHelper) {
1663
+ case "any_eq": {
1664
+ const bothNull = () => {
1665
+ this.pushInstruction({ op: "local.get", index: 0 });
1666
+ this.pushInstruction("ref.is_null");
1667
+ this.pushInstruction({ op: "local.get", index: 1 });
1668
+ this.pushInstruction("ref.is_null");
1669
+ };
1670
+ bothNull();
1671
+ this.pushInstruction("i32.and");
1672
+ this.pushInstruction({ op: "if", blockType: "void" });
1673
+ this.pushInstruction({ op: "i32.const", value: 1 });
1674
+ this.pushInstruction("return");
1675
+ this.pushInstruction("end");
1676
+ bothNull();
1677
+ this.pushInstruction("i32.or");
1678
+ this.pushInstruction({ op: "if", blockType: "void" });
1679
+ this.pushInstruction({ op: "i32.const", value: 0 });
1680
+ this.pushInstruction("return");
1681
+ this.pushInstruction("end");
1682
+
1683
+ // Check unhashable / uncomparable types
1684
+ const checkUncomparable = (typeIndex) => {
1685
+ this.pushInstruction({ op: "local.get", index: 0 });
1686
+ this.pushInstruction({ op: "ref.test", typeIndex });
1687
+ this.pushInstruction({ op: "local.get", index: 1 });
1688
+ this.pushInstruction({ op: "ref.test", typeIndex });
1689
+ this.pushInstruction("i32.or");
1690
+ this.pushInstruction({ op: "if", blockType: "void" });
1691
+ this.emitPanic("runtime error: comparing uncomparable type");
1692
+ this.pushInstruction("end");
1693
+ };
1694
+ for (const s of this.mod.sliceTypes.values())
1695
+ checkUncomparable(s.typeIndex);
1696
+ for (const m of this.mod.mapTypes.values())
1697
+ checkUncomparable(m.typeIndex);
1698
+ for (const c of this.mod.closureTypes.values())
1699
+ checkUncomparable(c.typeIndex);
1700
+ for (const b of this.mod.ifaceBoxTypes.values()) {
1701
+ if (isUnhashableGoType(b.goType, this.mod.checker))
1702
+ checkUncomparable(b.typeIndex);
1703
+ }
1704
+ for (const a of this.mod.arrayTypes.values()) {
1705
+ if (isUnhashableGoType(a.elemGoType, this.mod.checker))
1706
+ checkUncomparable(a.typeIndex);
1707
+ }
1708
+
1709
+ // Boxed values compare by value (Go: any(3) == any(3), struct values, etc.).
1710
+ for (const box of this.mod.ifaceBoxTypes.values()) {
1711
+ if (isUnhashableGoType(box.goType, this.mod.checker)) continue;
1712
+ for (const idx of [0, 1]) {
1713
+ this.pushInstruction({ op: "local.get", index: idx });
1714
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
1715
+ }
1716
+ this.pushInstruction("i32.and");
1717
+ this.pushInstruction({ op: "if", blockType: "void" });
1718
+ const f1 = this.allocLocal(null, box.wType);
1719
+ const f2 = this.allocLocal(null, box.wType);
1720
+ for (const [src, dst] of [
1721
+ [0, f1],
1722
+ [1, f2],
1723
+ ]) {
1724
+ this.pushInstruction({ op: "local.get", index: src });
1725
+ this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
1726
+ this.pushInstruction({
1727
+ op: "struct.get",
1728
+ typeIndex: box.typeIndex,
1729
+ fieldIndex: 0,
1730
+ });
1731
+ this.pushInstruction({ op: "local.set", index: dst });
1732
+ }
1733
+ this.emitKeyEq(f1, f2, box.goType);
1734
+ this.pushInstruction("return");
1735
+ this.pushInstruction("end");
1736
+ }
1737
+
1738
+ // Strings
1739
+ this.pushInstruction({ op: "local.get", index: 0 });
1740
+ this.pushInstruction({
1741
+ op: "call",
1742
+ funcIndex: this.mod.getIsStringImportIndex(),
1743
+ });
1744
+ this.pushInstruction({ op: "local.get", index: 1 });
1745
+ this.pushInstruction({
1746
+ op: "call",
1747
+ funcIndex: this.mod.getIsStringImportIndex(),
1748
+ });
1749
+ this.pushInstruction("i32.and");
1750
+ this.pushInstruction({ op: "if", blockType: "void" });
1751
+ this.pushInstruction({ op: "local.get", index: 0 });
1752
+ this.pushInstruction("extern.convert_any");
1753
+ this.pushInstruction({ op: "local.get", index: 1 });
1754
+ this.pushInstruction("extern.convert_any");
1755
+ this.pushInstruction({
1756
+ op: "call",
1757
+ funcIndex: this.mod.getStringCmpImportIndex("=="),
1758
+ });
1759
+ this.pushInstruction("return");
1760
+ this.pushInstruction("end");
1761
+
1762
+ // Heap reference equality
1763
+ this.pushInstruction({ op: "local.get", index: 0 });
1764
+ this.pushInstruction({ op: "ref.test", heapType: "eq" });
1765
+ this.pushInstruction({ op: "local.get", index: 1 });
1766
+ this.pushInstruction({ op: "ref.test", heapType: "eq" });
1767
+ this.pushInstruction("i32.and");
1768
+ this.pushInstruction({ op: "if", blockType: "void" });
1769
+ this.pushInstruction({ op: "local.get", index: 0 });
1770
+ this.pushInstruction({ op: "ref.cast", heapType: "eq" });
1771
+ this.pushInstruction({ op: "local.get", index: 1 });
1772
+ this.pushInstruction({ op: "ref.cast", heapType: "eq" });
1773
+ this.pushInstruction("ref.eq");
1774
+ this.pushInstruction("return");
1775
+ this.pushInstruction("end");
1776
+
1777
+ this.pushInstruction({ op: "i32.const", value: 0 });
1778
+ this.pushInstruction("return");
1779
+ return;
1780
+ }
1781
+ case "any_hash": {
1782
+ this.pushInstruction({ op: "local.get", index: 0 });
1783
+ this.pushInstruction("ref.is_null");
1784
+ this.pushInstruction({ op: "if", blockType: "void" });
1785
+ this.pushInstruction({ op: "i32.const", value: 0 });
1786
+ this.pushInstruction("return");
1787
+ this.pushInstruction("end");
1788
+
1789
+ // Check unhashable types
1790
+ const checkUnhashable = (typeIndex, goType) => {
1791
+ this.pushInstruction({ op: "local.get", index: 0 });
1792
+ this.pushInstruction({ op: "ref.test", typeIndex });
1793
+ this.pushInstruction({ op: "if", blockType: "void" });
1794
+ this.emitPanic(
1795
+ goType
1796
+ ? `runtime error: hash of unhashable type ${typeStr(goType)}`
1797
+ : "runtime error: hash of unhashable type",
1798
+ );
1799
+ this.pushInstruction("end");
1800
+ };
1801
+ // A slice type first built from source syntax (a `TypeName`) names
1802
+ // its element as written.
1803
+ const spelled = (t) =>
1804
+ t?.kind === "TypeName" ? { kind: "basic", name: t.name } : t;
1805
+ for (const sliceInfo of this.mod.sliceTypes.values())
1806
+ checkUnhashable(sliceInfo.typeIndex, {
1807
+ kind: "slice",
1808
+ elem: spelled(sliceInfo.elemGoType),
1809
+ });
1810
+ for (const mapInfo of this.mod.mapTypes.values())
1811
+ checkUnhashable(mapInfo.typeIndex, {
1812
+ kind: "map",
1813
+ key: mapInfo.keyGoType,
1814
+ value: mapInfo.valGoType,
1815
+ });
1816
+ for (const closureInfo of this.mod.closureTypes.values())
1817
+ checkUnhashable(closureInfo.typeIndex);
1818
+ for (const box of this.mod.ifaceBoxTypes.values()) {
1819
+ if (isUnhashableGoType(box.goType, this.mod.checker))
1820
+ checkUnhashable(box.typeIndex);
1821
+ }
1822
+ for (const arrInfo of this.mod.arrayTypes.values()) {
1823
+ if (isUnhashableGoType(arrInfo.elemGoType, this.mod.checker))
1824
+ checkUnhashable(arrInfo.typeIndex);
1825
+ }
1826
+
1827
+ // Check boxed values (scalars, struct values)
1828
+ for (const box of this.mod.ifaceBoxTypes.values()) {
1829
+ if (isUnhashableGoType(box.goType, this.mod.checker)) continue;
1830
+ this.pushInstruction({ op: "local.get", index: 0 });
1831
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
1832
+ this.pushInstruction({ op: "if", blockType: "void" });
1833
+ const valLoc = this.allocLocal(null, box.wType);
1834
+ this.pushInstruction({ op: "local.get", index: 0 });
1835
+ this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
1836
+ this.pushInstruction({
1837
+ op: "struct.get",
1838
+ typeIndex: box.typeIndex,
1839
+ fieldIndex: 0,
1840
+ });
1841
+ this.pushInstruction({ op: "local.set", index: valLoc });
1842
+ this.pushInstruction({ op: "i32.const", value: box.typeIndex });
1843
+ this.pushInstruction({ op: "i32.const", value: 31 });
1844
+ this.pushInstruction("i32.mul");
1845
+ this.emitKeyHash(valLoc, box.goType);
1846
+ this.pushInstruction("i32.add");
1847
+ this.pushInstruction("return");
1848
+ this.pushInstruction("end");
1849
+ }
1850
+
1851
+ // Check string
1852
+ this.pushInstruction({ op: "local.get", index: 0 });
1853
+ this.pushInstruction({
1854
+ op: "call",
1855
+ funcIndex: this.mod.getIsStringImportIndex(),
1856
+ });
1857
+ this.pushInstruction({ op: "if", blockType: "void" });
1858
+ this.pushInstruction({ op: "local.get", index: 0 });
1859
+ this.pushInstruction("extern.convert_any");
1860
+ this.pushInstruction({
1861
+ op: "call",
1862
+ funcIndex: this.mod.getStringHashImportIndex(),
1863
+ });
1864
+ this.pushInstruction("return");
1865
+ this.pushInstruction("end");
1866
+
1867
+ this.pushInstruction({ op: "i32.const", value: 0 });
1868
+ this.pushInstruction("return");
1869
+ return;
1870
+ }
1871
+ case "ta_fail":
1872
+ this._emitTypeAssertFail();
1873
+ return;
1874
+ case "error_msg":
1875
+ this.pushInstruction({ op: "local.get", index: 0 });
1876
+ this.pushInstruction({
1877
+ op: "struct.get",
1878
+ typeIndex: errInfo.typeIndex,
1879
+ fieldIndex: 0,
1880
+ });
1881
+ this.pushInstruction("return");
1882
+ return;
1883
+ case "mk_error":
1884
+ // nil message (strconv success) → nil error
1885
+ this.pushInstruction({ op: "local.get", index: 0 });
1886
+ this.pushInstruction("any.convert_extern");
1887
+ this.pushInstruction("ref.is_null");
1888
+ this.pushInstruction({ op: "if", blockType: "void" });
1889
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
1890
+ this.pushInstruction("return");
1891
+ this.pushInstruction("end");
1892
+ this.pushInstruction({ op: "local.get", index: 0 });
1893
+ this.pushInstruction({
1894
+ op: "struct.new",
1895
+ typeIndex: errInfo.typeIndex,
1896
+ });
1897
+ this.pushInstruction("return");
1898
+ return;
1899
+ case "error_str":
1900
+ this.pushInstruction({ op: "local.get", index: 0 });
1901
+ this.pushInstruction("ref.is_null");
1902
+ this.pushInstruction({ op: "if", blockType: "void" });
1903
+ this.pushInstruction({
1904
+ op: "i32.const",
1905
+ value: this.mod.internString("<nil>"),
1906
+ });
1907
+ this.pushInstruction({
1908
+ op: "call",
1909
+ funcIndex: this.mod.getStringImportIndex(),
1910
+ });
1911
+ this.pushInstruction("return");
1912
+ this.pushInstruction("end");
1913
+ this.pushInstruction({ op: "local.get", index: 0 });
1914
+ this.pushInstruction({
1915
+ op: "call",
1916
+ funcIndex: this.mod.resolveFuncIndex("__dispatch_error_Error"),
1917
+ });
1918
+ this.pushInstruction("return");
1919
+ return;
1920
+ default:
1921
+ throw new Error(`Unknown runtime helper: ${fn._isRuntimeHelper}`);
1922
+ }
1923
+ }
1247
1924
  }