gofront 1.8.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 (177) hide show
  1. package/README.md +3 -3
  2. package/package.json +5 -4
  3. package/src/backend/js/expressions.js +849 -371
  4. package/src/backend/js/index.js +241 -147
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +512 -2547
  7. package/src/backend/js/statements.js +506 -103
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +177 -13
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +63 -171
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +643 -256
  16. package/src/backend/wasm/emit-exprs.js +124 -57
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-stmts.js +325 -75
  19. package/src/backend/wasm/emit.js +104 -13
  20. package/src/backend/wasm/globals.js +47 -8
  21. package/src/backend/wasm/glue.js +2 -4
  22. package/src/backend/wasm/index.js +36 -530
  23. package/src/backend/wasm/method-expr.js +12 -1
  24. package/src/backend/wasm/module-emitter.js +92 -717
  25. package/src/backend/wasm/monomorph.js +103 -6
  26. package/src/backend/wasm/natives.js +596 -0
  27. package/src/backend/wasm/prescan.js +5 -153
  28. package/src/backend/wasm/prune-funcs.js +89 -0
  29. package/src/backend/wasm/stdlib/index.js +2 -32
  30. package/src/backend/wasm/types.js +68 -20
  31. package/src/backend/wasm/variadic.js +3 -3
  32. package/src/cli/cli-core.js +5 -1
  33. package/src/cli/test-runner.js +115 -342
  34. package/src/compiler.js +108 -130
  35. package/src/lexer/lexer.js +105 -11
  36. package/src/lexer/templ-lexer.js +61 -18
  37. package/src/lexer/tokens.js +1 -0
  38. package/src/link/stdlib.js +354 -132
  39. package/src/link/type-names.js +148 -0
  40. package/src/link/wasm.js +30 -6
  41. package/src/lower/bounds.js +153 -0
  42. package/src/lower/captures.js +92 -33
  43. package/src/lower/embedding.js +32 -3
  44. package/src/lower/errors-as.js +196 -0
  45. package/src/lower/fmt-formatters.js +905 -0
  46. package/src/lower/functions.js +2 -0
  47. package/src/lower/gen-types.js +274 -0
  48. package/src/lower/index.js +7 -1
  49. package/src/lower/json-codecs.js +228 -247
  50. package/src/lower/ownership.js +25 -7
  51. package/src/lower/reachability.js +69 -51
  52. package/src/lower/walk.js +12 -0
  53. package/src/parser/declarations.js +28 -5
  54. package/src/parser/expressions.js +86 -16
  55. package/src/parser/statements.js +1 -0
  56. package/src/parser/templ.js +44 -5
  57. package/src/parser/types.js +1 -0
  58. package/src/resolver.js +1 -2
  59. package/src/typechecker/assignability.js +124 -11
  60. package/src/typechecker/const-fold.js +70 -27
  61. package/src/typechecker/constants.js +32 -6
  62. package/src/typechecker/expressions.js +326 -98
  63. package/src/typechecker/index.js +208 -33
  64. package/src/typechecker/init-order.js +136 -40
  65. package/src/typechecker/operand-text.js +40 -0
  66. package/src/typechecker/resolve.js +192 -36
  67. package/src/typechecker/statements.js +122 -11
  68. package/src/typechecker/stdlib/excluded.js +9 -5
  69. package/src/typechecker/stdlib/index.js +25 -9
  70. package/src/typechecker/termination.js +10 -1
  71. package/src/typechecker/types.js +203 -573
  72. package/stdlib/bytes/buffer.go +479 -0
  73. package/stdlib/bytes/bytes.go +1010 -0
  74. package/stdlib/bytes/reader.go +161 -0
  75. package/stdlib/context/context.go +115 -0
  76. package/stdlib/crypto/rand/rand.go +34 -0
  77. package/stdlib/crypto/sha256/sha256.go +249 -0
  78. package/stdlib/encoding/base64/base64.go +331 -0
  79. package/stdlib/encoding/binary/binary.go +332 -0
  80. package/stdlib/encoding/hex/hex.go +140 -0
  81. package/stdlib/encoding/json/json.go +62 -0
  82. package/stdlib/errors/errors.go +174 -0
  83. package/stdlib/fmt/errors.go +110 -0
  84. package/stdlib/fmt/format.go +732 -0
  85. package/stdlib/fmt/print.go +1232 -0
  86. package/stdlib/gom/gom.go +293 -0
  87. package/stdlib/html/html.go +251 -0
  88. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  89. package/stdlib/internal/jsonrt/dec.go +171 -38
  90. package/stdlib/internal/jsonrt/enc.go +186 -36
  91. package/stdlib/internal/jsonrt/scanner.go +17 -8
  92. package/stdlib/io/io.go +157 -1
  93. package/stdlib/maps/maps.go +131 -0
  94. package/stdlib/math/bits/bits.go +507 -0
  95. package/stdlib/math/extra.go +16 -14
  96. package/stdlib/math/math.go +439 -0
  97. package/stdlib/math/rand/rand.go +297 -0
  98. package/stdlib/os/os.go +93 -0
  99. package/stdlib/path/filepath/filepath.go +47 -0
  100. package/stdlib/path/path.go +394 -0
  101. package/stdlib/regexp/regexp.go +231 -0
  102. package/stdlib/slices/slices.go +526 -0
  103. package/stdlib/slices/sort.go +209 -0
  104. package/stdlib/slices/zsort_cmp.go +480 -0
  105. package/stdlib/slices/zsort_ordered.go +482 -0
  106. package/stdlib/sort/sort.go +391 -452
  107. package/stdlib/sort/zsort_func.go +480 -0
  108. package/stdlib/sort/zsort_iface.go +480 -0
  109. package/stdlib/strconv/float.go +1699 -0
  110. package/stdlib/strconv/isprint.go +752 -0
  111. package/stdlib/strconv/quote.go +235 -50
  112. package/stdlib/strconv/strconv.go +616 -0
  113. package/stdlib/strings/builder.go +95 -0
  114. package/stdlib/strings/reader.go +163 -0
  115. package/stdlib/strings/replacer.go +102 -0
  116. package/stdlib/strings/strings.go +807 -0
  117. package/stdlib/sync/sync.go +268 -0
  118. package/stdlib/testing/async_js.go +61 -0
  119. package/stdlib/testing/benchmark.go +297 -0
  120. package/stdlib/testing/testing.go +523 -0
  121. package/stdlib/time/duration.go +233 -0
  122. package/stdlib/time/format.go +745 -0
  123. package/stdlib/time/parse.go +790 -0
  124. package/stdlib/time/sleep_js.go +63 -0
  125. package/stdlib/time/time.go +834 -0
  126. package/stdlib/unicode/unicode.go +141 -3
  127. package/stdlib/unicode/utf8/utf8.go +464 -0
  128. package/src/backend/js/fmt-runtime.js +0 -573
  129. package/src/backend/js/stdlib/base64.js +0 -12
  130. package/src/backend/js/stdlib/binary.js +0 -102
  131. package/src/backend/js/stdlib/bits.js +0 -119
  132. package/src/backend/js/stdlib/builder.js +0 -98
  133. package/src/backend/js/stdlib/bytes.js +0 -368
  134. package/src/backend/js/stdlib/errors.js +0 -45
  135. package/src/backend/js/stdlib/fmt.js +0 -517
  136. package/src/backend/js/stdlib/gom.js +0 -155
  137. package/src/backend/js/stdlib/hex.js +0 -17
  138. package/src/backend/js/stdlib/html.js +0 -20
  139. package/src/backend/js/stdlib/io.js +0 -137
  140. package/src/backend/js/stdlib/json.js +0 -1167
  141. package/src/backend/js/stdlib/maps.js +0 -37
  142. package/src/backend/js/stdlib/math.js +0 -85
  143. package/src/backend/js/stdlib/os.js +0 -13
  144. package/src/backend/js/stdlib/path.js +0 -48
  145. package/src/backend/js/stdlib/rand.js +0 -32
  146. package/src/backend/js/stdlib/regexp.js +0 -54
  147. package/src/backend/js/stdlib/sha256.js +0 -12
  148. package/src/backend/js/stdlib/shared.js +0 -17
  149. package/src/backend/js/stdlib/simd.js +0 -34
  150. package/src/backend/js/stdlib/slices.js +0 -107
  151. package/src/backend/js/stdlib/sort.js +0 -30
  152. package/src/backend/js/stdlib/strconv.js +0 -79
  153. package/src/backend/js/stdlib/strings.js +0 -301
  154. package/src/backend/js/stdlib/sync.js +0 -13
  155. package/src/backend/js/stdlib/testing.js +0 -41
  156. package/src/backend/js/stdlib/time.js +0 -152
  157. package/src/backend/js/stdlib/unicode.js +0 -25
  158. package/src/backend/js/stdlib/utf8.js +0 -26
  159. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  160. package/src/backend/wasm/stdlib/core.js +0 -10278
  161. package/src/backend/wasm/stdlib/io.js +0 -3578
  162. package/src/backend/wasm/stdlib/packages.js +0 -2402
  163. package/src/backend/wasm/stdlib/pure.js +0 -720
  164. package/src/backend/wasm/stdlib/time.js +0 -2229
  165. package/src/typechecker/stdlib/core.js +0 -1079
  166. package/src/typechecker/stdlib/extended.js +0 -1210
  167. package/stdlib/bytes/iter.go +0 -192
  168. package/stdlib/bytes/search.go +0 -240
  169. package/stdlib/bytes/split.go +0 -218
  170. package/stdlib/errors/overlay.go +0 -12
  171. package/stdlib/maps/iter.go +0 -65
  172. package/stdlib/slices/extra.go +0 -73
  173. package/stdlib/slices/iter.go +0 -113
  174. package/stdlib/strings/extra.go +0 -35
  175. package/stdlib/strings/iter.go +0 -122
  176. package/stdlib/sync/extra.go +0 -105
  177. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -2,11 +2,14 @@
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 {
6
9
  anonIfaceName,
10
+ ERROR_STRUCT,
7
11
  getMapKeyValTypes,
8
12
  isArrayType,
9
- isBoolGoType,
10
13
  isComplex,
11
14
  isComplex64,
12
15
  isMapType,
@@ -16,7 +19,8 @@ import {
16
19
  isSliceType,
17
20
  isStringType,
18
21
  isStructType,
19
- isTestingT,
22
+ isUnhashableGoType,
23
+ printOperandKind,
20
24
  toWasmType,
21
25
  } from "./types.js";
22
26
 
@@ -26,13 +30,32 @@ export class BuiltinsEmitter {
26
30
  const funcIdx = this.mod.getStringImportIndex();
27
31
  this.pushInstruction({ op: "i32.const", value: strIdx });
28
32
  this.pushInstruction({ op: "call", funcIndex: funcIdx });
29
- this.pushInstruction("any.convert_extern");
30
- 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");
31
53
  }
32
54
 
33
- // Consumes the anyref message on the stack, pushes a PanicNode, and raises the panic via
34
- // `env.panic`; `unreachable` tells the validator control never returns.
35
- 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) {
36
59
  const panicNodeTypeIndex = this.mod.getPanicNodeTypeIndex();
37
60
  const panicGlobal = this.mod.getPanicGlobalIndex();
38
61
  const tmpVal = this.acquireTemp("anyref");
@@ -49,8 +72,12 @@ export class BuiltinsEmitter {
49
72
  this.pushInstruction({ op: "global.set", index: panicGlobal });
50
73
 
51
74
  // Prepare externref for env.panic
52
- this.pushInstruction({ op: "local.get", index: tmpVal });
53
- 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
+ }
54
81
  this.pushInstruction({
55
82
  op: "call",
56
83
  funcIndex: this.mod.getPanicImportIndex(),
@@ -71,9 +98,32 @@ export class BuiltinsEmitter {
71
98
  return null;
72
99
  }
73
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
+
74
118
  _emitBuiltinLen(call, targetWasmType) {
75
119
  const arg = call.args[0];
76
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
+ }
77
127
  if (this._sharedInfo(argType)) {
78
128
  this.emitSharedLen(arg, targetWasmType);
79
129
  return;
@@ -141,6 +191,10 @@ export class BuiltinsEmitter {
141
191
  _emitBuiltinCap(call, targetWasmType) {
142
192
  const arg = call.args[0];
143
193
  const argType = arg._type;
194
+ if (call._constLen != null) {
195
+ this._emitConstArrayLen(call, targetWasmType);
196
+ return;
197
+ }
144
198
  if (isSliceType(argType, this.mod.checker)) {
145
199
  const elemType = this._getSliceElemType(argType);
146
200
  const sliceInfo = this.mod.getSliceType(elemType);
@@ -223,16 +277,19 @@ export class BuiltinsEmitter {
223
277
  this.pushInstruction({ op: "local.set", index: capTmp });
224
278
  }
225
279
 
280
+ // Go: a negative len is a len panic; otherwise cap < len is a cap panic.
226
281
  this.pushInstruction({ op: "local.get", index: lenTmp });
227
- this.pushInstruction({ op: "local.get", index: capTmp });
228
- this.pushInstruction("i32.gt_u");
229
- this.pushInstruction({ op: "local.get", index: capTmp });
230
282
  this.pushInstruction({ op: "i32.const", value: 0 });
231
283
  this.pushInstruction("i32.lt_s");
232
- this.pushInstruction("i32.or");
233
284
  this.pushInstruction({ op: "if", blockType: "void" });
234
285
  this.emitPanic("runtime error: makeslice: len out of range");
235
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");
236
293
 
237
294
  if (
238
295
  isStructType(elemGoType, this.mod.checker, this.mod) ||
@@ -294,11 +351,9 @@ export class BuiltinsEmitter {
294
351
  typeIndex: arrInfo.typeIndex,
295
352
  });
296
353
  } else {
297
- this.pushInstruction({ op: "local.get", index: capTmp });
298
- this.pushInstruction({
299
- op: "array.new_default",
300
- typeIndex: arrInfo.typeIndex,
301
- });
354
+ this._emitNewArray(elemGoType, arrInfo, () =>
355
+ this.pushInstruction({ op: "local.get", index: capTmp }),
356
+ );
302
357
  this.pushInstruction({ op: "i32.const", value: 0 });
303
358
  this.pushInstruction({ op: "local.get", index: lenTmp });
304
359
  this.pushInstruction({ op: "local.get", index: capTmp });
@@ -397,11 +452,9 @@ export class BuiltinsEmitter {
397
452
  this.pushInstruction({ op: "local.set", index: newCapTmp });
398
453
  this.pushInstruction("end");
399
454
 
400
- this.pushInstruction({ op: "local.get", index: newCapTmp });
401
- this.pushInstruction({
402
- op: "array.new_default",
403
- typeIndex: arrInfo.typeIndex,
404
- });
455
+ this._emitNewArray(elemGoType, arrInfo, () =>
456
+ this.pushInstruction({ op: "local.get", index: newCapTmp }),
457
+ );
405
458
  this.pushInstruction({ op: "local.set", index: newArrTmp });
406
459
  this.pushInstruction({ op: "i32.const", value: 0 });
407
460
  this.pushInstruction({ op: "local.set", index: newOffTmp });
@@ -731,13 +784,21 @@ export class BuiltinsEmitter {
731
784
  for (let i = 0; i < args.length; i++) {
732
785
  const arg = args[i];
733
786
  const isLast = i === args.length - 1 && isPrintln;
734
- const isBool =
735
- isBoolGoType(arg._type) ||
736
- (arg.kind === "BasicLit" && arg.litKind === "BOOL");
737
- const wType = toWasmType(arg._type, this.mod.checker);
738
- 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");
739
793
  const logFuncIdx = this.mod.getLogImportIndex(wType, isLast, isBool);
740
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
+ }
741
802
  }
742
803
  }
743
804
 
@@ -753,7 +814,11 @@ export class BuiltinsEmitter {
753
814
  } else {
754
815
  const goType = this._resolveExprGoType(arg);
755
816
  const wType = toWasmType(goType, this.mod.checker);
756
- 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 (
757
822
  isStringType(goType, this.mod.checker) ||
758
823
  (arg.kind === "BasicLit" && arg.litKind === "STRING") ||
759
824
  wType === "externref"
@@ -868,7 +933,7 @@ export class BuiltinsEmitter {
868
933
  callType?.name === "complex64" ||
869
934
  arg0._type?.name === "float32" ||
870
935
  arg1._type?.name === "float32" ||
871
- targetWasmType?.typeName === "$complex64";
936
+ this._isComplex64Target(targetWasmType);
872
937
  const complexName = is64 ? "complex64" : "complex128";
873
938
  const info = this.mod.getComplexType(complexName);
874
939
  this.emitExpr(arg0, info.elemWType);
@@ -1052,10 +1117,6 @@ export class BuiltinsEmitter {
1052
1117
  _emitPackageCall(call, targetWasmType) {
1053
1118
  const { func, args } = call;
1054
1119
  if (func.kind !== "SelectorExpr") return false;
1055
- if (isTestingT(func.expr?._type)) {
1056
- this.emitTestingCall(func, args);
1057
- return true;
1058
- }
1059
1120
  if (
1060
1121
  func.expr?.name &&
1061
1122
  (this.resolveLocal(func.expr.name) ||
@@ -1064,97 +1125,9 @@ export class BuiltinsEmitter {
1064
1125
  return false;
1065
1126
  const pkgName = func.expr?.name;
1066
1127
  switch (pkgName) {
1067
- case "math":
1068
- this.emitMathCall(func.field, args);
1069
- return true;
1070
- case "bits":
1071
- this.emitBitsCall(func.field, args);
1072
- return true;
1073
- case "maps":
1074
- this.emitMapsCall(func.field, args, targetWasmType);
1075
- return true;
1076
- case "slices":
1077
- this.emitSlicesCall(func.field, args, targetWasmType);
1078
- return true;
1079
- case "strings":
1080
- if (func.field === "NewReader") {
1081
- this.emitStringsNewReader(call, targetWasmType);
1082
- return true;
1083
- }
1084
- this.emitStringsCall(func.field, args, targetWasmType);
1085
- return true;
1086
- case "strconv":
1087
- this.emitStrconvCall(func.field, args);
1088
- return true;
1089
- case "fmt":
1090
- if (func.field.startsWith("Fprint")) {
1091
- this.emitFprintCall(func.field, call, targetWasmType);
1092
- return true;
1093
- }
1094
- if (func.field.startsWith("Sscan")) {
1095
- this.emitSscanCall(func.field, call, targetWasmType);
1096
- return true;
1097
- }
1098
- this.emitFmtCall(func.field, args);
1099
- return true;
1100
- case "errors":
1101
- this.emitErrorsCall(func.field, args);
1102
- return true;
1103
- case "utf8":
1104
- this.emitUtf8Call(func.field, args, targetWasmType);
1105
- return true;
1106
- case "sort":
1107
- this.emitSortCall(func.field, args, targetWasmType);
1108
- return true;
1109
1128
  case "shared":
1110
1129
  this.emitSharedNew(func.field, args);
1111
1130
  return true;
1112
- case "unicode":
1113
- this.emitUnicodeCall(func.field, args, targetWasmType);
1114
- return true;
1115
- case "rand":
1116
- this.emitRandCall(func.field, args, targetWasmType);
1117
- return true;
1118
- case "bytes":
1119
- if (func.field === "NewReader") {
1120
- this.emitBytesNewReader(call, targetWasmType);
1121
- return true;
1122
- }
1123
- if (func.field === "NewBuffer") {
1124
- this.emitBytesNewBuffer(call, targetWasmType);
1125
- return true;
1126
- }
1127
- if (func.field === "NewBufferString") {
1128
- this.emitBytesNewBufferString(call, targetWasmType);
1129
- return true;
1130
- }
1131
- this.emitBytesCall(func.field, args, targetWasmType);
1132
- return true;
1133
- case "path":
1134
- case "filepath":
1135
- this.emitPathCall(func.field, args);
1136
- return true;
1137
- case "html":
1138
- this.emitHtmlCall(func.field, args);
1139
- return true;
1140
- case "io":
1141
- this.emitIoCall(func.field, call, targetWasmType);
1142
- return true;
1143
- case "time":
1144
- this.emitTimeCall(func.field, call, targetWasmType);
1145
- return true;
1146
- case "binary":
1147
- this.emitBinaryCall(func.field, call, targetWasmType);
1148
- return true;
1149
- case "sha256":
1150
- this.emitSha256Call(func.field, call, targetWasmType);
1151
- return true;
1152
- case "hex":
1153
- this.emitHexCall(func.field, call, targetWasmType);
1154
- return true;
1155
- case "base64":
1156
- this.emitBase64Call(func.field, call, targetWasmType);
1157
- return true;
1158
1131
  case "simd":
1159
1132
  this.emitSimdPackageCall(func.field, args, targetWasmType);
1160
1133
  return true;
@@ -1191,25 +1164,6 @@ export class BuiltinsEmitter {
1191
1164
  recvType?.simd === "Mask32x4",
1192
1165
  );
1193
1166
  }
1194
- if (
1195
- this._emitStdlibReceiverMethod(
1196
- recvTypeName,
1197
- func.field,
1198
- func.expr,
1199
- args,
1200
- targetWasmType,
1201
- )
1202
- ) {
1203
- return true;
1204
- }
1205
- if (recvTypeName === "strings.Builder") {
1206
- this.emitBuilderMethodCall(func.field, func.expr, args);
1207
- return true;
1208
- }
1209
- if (recvTypeName === "strings.Replacer") {
1210
- this.emitReplacerMethodCall(func.field, func.expr, args);
1211
- return true;
1212
- }
1213
1167
  if (isNonEmptyInterface(recvType, this.mod.checker)) {
1214
1168
  const ifaceName = recvTypeName ?? anonIfaceName(recvType);
1215
1169
  const dispatchName = `__dispatch_${ifaceName}_${func.field}`;
@@ -1303,128 +1257,60 @@ export class BuiltinsEmitter {
1303
1257
  };
1304
1258
  }
1305
1259
 
1306
- _emitStdlibReceiverMethod(
1307
- recvTypeName,
1308
- field,
1309
- expr,
1310
- args,
1311
- targetWasmType = null,
1312
- ) {
1313
- if (recvTypeName === "strings.Builder") {
1314
- this.emitBuilderMethodCall(field, expr, args, targetWasmType);
1315
- return true;
1316
- }
1317
- if (recvTypeName === "strings.Replacer") {
1318
- this.emitReplacerMethodCall(field, expr, args);
1319
- return true;
1320
- }
1321
- if (recvTypeName === "bytes.Buffer") {
1322
- this.emitBytesBufferMethodCall(field, expr, args, targetWasmType);
1323
- return true;
1324
- }
1325
- if (recvTypeName === "strings.Reader") {
1326
- this.emitStringsReaderMethodCall(field, expr, args, targetWasmType);
1327
- return true;
1328
- }
1329
- if (recvTypeName === "bytes.Reader") {
1330
- this.emitBytesReaderMethodCall(field, expr, args, targetWasmType);
1331
- return true;
1332
- }
1333
- if (recvTypeName === "time.Time") {
1334
- this.emitTimeMethodCall(field, expr, args, targetWasmType);
1335
- return true;
1336
- }
1337
- if (recvTypeName === "time.Duration") {
1338
- this.emitDurationMethodCall(field, expr, args, targetWasmType);
1339
- return true;
1340
- }
1341
- if (
1342
- recvTypeName === "time.Month" ||
1343
- recvTypeName === "time.Weekday" ||
1344
- recvTypeName === "time.Location"
1345
- ) {
1346
- this.emitTimeAuxMethodCall(
1347
- recvTypeName,
1348
- field,
1349
- expr,
1350
- args,
1351
- targetWasmType,
1352
- );
1353
- return true;
1354
- }
1355
- if (recvTypeName === "io.LimitedReader") {
1356
- this.emitIoLimitedReaderMethodCall(field, expr, args, targetWasmType);
1357
- return true;
1358
- }
1359
- if (
1360
- (recvTypeName === "sync.Mutex" || recvTypeName === "sync.RWMutex") &&
1361
- field !== "RLocker"
1362
- ) {
1363
- this.emitSyncMutexMethodCall(field, expr, args, targetWasmType);
1364
- return true;
1365
- }
1366
- if (recvTypeName === "sync.Once") {
1367
- this.emitSyncOnceMethodCall(field, expr, args, targetWasmType);
1368
- return true;
1369
- }
1370
- if (recvTypeName === "sync.WaitGroup") {
1371
- this.emitSyncWaitGroupMethodCall(field, expr, args, targetWasmType);
1372
- return true;
1373
- }
1374
- if (
1375
- recvTypeName === "binary.LittleEndian" ||
1376
- recvTypeName === "binary.BigEndian" ||
1377
- recvTypeName === "encoding/binary.LittleEndian" ||
1378
- recvTypeName === "encoding/binary.BigEndian" ||
1379
- recvTypeName === "binary.ByteOrder" ||
1380
- recvTypeName === "encoding/binary.ByteOrder"
1381
- ) {
1382
- this.emitBinaryOrderMethodCall(
1383
- recvTypeName,
1384
- field,
1385
- expr,
1386
- args,
1387
- targetWasmType,
1388
- );
1389
- return true;
1390
- }
1391
- if (
1392
- recvTypeName === "base64.Encoding" ||
1393
- recvTypeName === "encoding/base64.Encoding"
1394
- ) {
1395
- this.emitBase64EncodingMethodCall(field, expr, args, targetWasmType);
1396
- return true;
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
+ };
1397
1277
  }
1398
- return false;
1278
+ return null;
1399
1279
  }
1400
1280
 
1401
1281
  _emitEmbeddedMethodCall(recvTypeName, func, args, recvType) {
1402
1282
  const structInfo = this.mod.getStructType(recvTypeName);
1403
1283
  if (!structInfo) return false;
1404
- for (const embed of structInfo.embeds) {
1405
- const embedMethodName = `${embed.name}.${func.field}`;
1406
- const subIdx = this.mod.resolveFuncIndex(embedMethodName);
1407
- if (subIdx !== null) {
1408
- const targetParamTypes = this.mod.getFuncParamTypes(embedMethodName);
1409
- const baseWType = this.toWasmType(recvType);
1410
- this.emitExpr(func.expr, baseWType);
1411
- this.pushInstruction({
1412
- op: "struct.get",
1413
- typeIndex: structInfo.typeIndex,
1414
- fieldIndex: embed.fieldIndex,
1415
- });
1416
- for (let i = 0; i < args.length; i++) {
1417
- const pType = targetParamTypes[i + 1] ?? null;
1418
- this.emitExpr(args[i], pType);
1419
- }
1420
- this.pushInstruction({
1421
- op: "call",
1422
- funcIndex: subIdx,
1423
- });
1424
- return true;
1425
- }
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();
1426
1304
  }
1427
- 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;
1428
1314
  }
1429
1315
 
1430
1316
  _emitStaticCall(call) {
@@ -1437,6 +1323,12 @@ export class BuiltinsEmitter {
1437
1323
  if (targetFuncIdx !== null) {
1438
1324
  const paramTypes = this.mod.getFuncParamTypes(func.name);
1439
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
+ }
1440
1332
  for (let i = 0; i < args.length; i++) {
1441
1333
  const arg = args[i];
1442
1334
  const pType = paramTypes[i] ?? null;
@@ -1534,4 +1426,499 @@ export class BuiltinsEmitter {
1534
1426
 
1535
1427
  this._emitClosureCall(call);
1536
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
+ }
1537
1924
  }