gofront 1.8.0 → 1.9.1

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 (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  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 +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  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 +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -2,13 +2,16 @@
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 { goTypeName } from "../shared/fmt-types.js";
5
7
  import {
6
8
  anonIfaceName,
9
+ ERROR_STRUCT,
7
10
  getMapKeyValTypes,
8
11
  isArrayType,
9
- isBoolGoType,
10
12
  isComplex,
11
13
  isComplex64,
14
+ isFuncType,
12
15
  isMapType,
13
16
  isNonEmptyInterface,
14
17
  isScalarType,
@@ -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();
31
34
  }
32
35
 
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() {
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");
53
+ }
54
+
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,72 @@ 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;
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
+ const ifaceType = structInfo.fields[embed.fieldIndex]?.goType;
1269
+ if (this.mod.embeddedInterfaceHas(ifaceType, method))
1270
+ return { chain: [{ structInfo, embed }], ifaceType };
1271
+ if (this.mod.resolveFuncIndex(methodName) !== null)
1272
+ return { chain: [{ structInfo, embed }], methodName };
1273
+ const inner = this.mod.getStructType(typeName);
1274
+ const deeper = inner && this._findEmbeddedMethod(inner, method, seen);
1275
+ if (deeper)
1276
+ return {
1277
+ ...deeper,
1278
+ chain: [{ structInfo, embed }, ...deeper.chain],
1279
+ };
1390
1280
  }
1391
- if (
1392
- recvTypeName === "base64.Encoding" ||
1393
- recvTypeName === "encoding/base64.Encoding"
1394
- ) {
1395
- this.emitBase64EncodingMethodCall(field, expr, args, targetWasmType);
1396
- return true;
1397
- }
1398
- return false;
1281
+ return null;
1399
1282
  }
1400
1283
 
1401
1284
  _emitEmbeddedMethodCall(recvTypeName, func, args, recvType) {
1402
1285
  const structInfo = this.mod.getStructType(recvTypeName);
1403
1286
  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
- }
1287
+ const found = this._findEmbeddedMethod(structInfo, func.field);
1288
+ if (!found) return false;
1289
+ const { chain, methodName, ifaceType } = found;
1290
+ // A method promoted from an embedded interface is dispatched on the
1291
+ // interface value the last embed holds.
1292
+ const dispatchName = ifaceType
1293
+ ? `__dispatch_${this._getReceiverTypeName(ifaceType)}_${func.field}`
1294
+ : null;
1295
+ const subName = dispatchName ?? methodName;
1296
+ const subIdx = this.mod.resolveFuncIndex(subName);
1297
+ if (subIdx === null)
1298
+ throw new Error(`internal: no interface dispatcher ${subName}`);
1299
+ const targetParamTypes = this.mod.getFuncParamTypes(subName);
1300
+ const valueRecv =
1301
+ !ifaceType &&
1302
+ this.mod.getFuncParamGoTypes(methodName)[0]?.kind !== "pointer";
1303
+ this.emitExpr(func.expr, this.toWasmType(recvType));
1304
+ this._emitNilCheck(func.expr._type);
1305
+ for (const [i, { structInfo: owner, embed }] of chain.entries()) {
1306
+ this.pushInstruction({
1307
+ op: "struct.get",
1308
+ typeIndex: owner.typeIndex,
1309
+ fieldIndex: embed.fieldIndex,
1310
+ });
1311
+ // Reading through an embedded nil pointer (to the next embedded
1312
+ // struct, or as a value receiver) is a recoverable nil dereference.
1313
+ const ft = owner.fields[embed.fieldIndex]?.goType;
1314
+ const isPtr = ft?.kind === "pointer" || ft?.kind === "PointerType";
1315
+ if (isPtr && (i < chain.length - 1 || valueRecv)) this._emitNilBranch();
1426
1316
  }
1427
- return false;
1317
+ for (let i = 0; i < args.length; i++) {
1318
+ const pType = targetParamTypes[i + 1] ?? null;
1319
+ this.emitExpr(args[i], pType);
1320
+ }
1321
+ this.pushInstruction({
1322
+ op: "call",
1323
+ funcIndex: subIdx,
1324
+ });
1325
+ return true;
1428
1326
  }
1429
1327
 
1430
1328
  _emitStaticCall(call) {
@@ -1437,6 +1335,12 @@ export class BuiltinsEmitter {
1437
1335
  if (targetFuncIdx !== null) {
1438
1336
  const paramTypes = this.mod.getFuncParamTypes(func.name);
1439
1337
  const paramGoTypes = this.mod.getFuncParamGoTypes(func.name);
1338
+ if (call._multiForward) {
1339
+ // `f(g())`: g's results are f's arguments as they come.
1340
+ this.emitExpr(args[0], null);
1341
+ this.pushInstruction({ op: "call", funcIndex: targetFuncIdx });
1342
+ return true;
1343
+ }
1440
1344
  for (let i = 0; i < args.length; i++) {
1441
1345
  const arg = args[i];
1442
1346
  const pType = paramTypes[i] ?? null;
@@ -1501,6 +1405,41 @@ export class BuiltinsEmitter {
1501
1405
  this.releaseTemp(closureTmp, closureWType);
1502
1406
  }
1503
1407
 
1408
+ // `g(f())` that lowering left unsplit (it splits only statement operands):
1409
+ // f's results go into locals, and g is called with idents carrying each
1410
+ // element's interface-box marks, so each is boxed like any argument. Also
1411
+ // used for method calls, whose argument path expects one value per
1412
+ // parameter. Only when g's callee evaluates no call, so f() still runs
1413
+ // before anything else the call evaluates.
1414
+ _splitForwardedArgs(call) {
1415
+ if (!call._multiForward || call.args?.length !== 1) return null;
1416
+ const inner = call.args[0];
1417
+ const types = inner?._type?.types;
1418
+ if (!Array.isArray(types) || types.length === 0) return null;
1419
+ const holders =
1420
+ inner._fwdIface?.length === types.length ? inner._fwdIface : null;
1421
+ const isMethod =
1422
+ call.func?.kind === "SelectorExpr" &&
1423
+ call.func.expr?._type?.kind !== "namespace";
1424
+ if (!holders && !isMethod) return null;
1425
+ if (hasCallOrReceive(call.func)) return null;
1426
+ this.emitExpr(inner, null);
1427
+ this._fwdArgCounter = (this._fwdArgCounter ?? 0) + 1;
1428
+ const names = types.map((_, i) => `__fwdarg$${this._fwdArgCounter}$${i}`);
1429
+ const locals = types.map((t, i) =>
1430
+ this.allocLocal(names[i], this.toWasmType(t), t, true),
1431
+ );
1432
+ for (let i = locals.length - 1; i >= 0; i--)
1433
+ this.pushInstruction({ op: "local.set", index: locals[i] });
1434
+ const args = names.map((name, i) => ({
1435
+ kind: "Ident",
1436
+ name,
1437
+ _type: types[i],
1438
+ ...(holders?.[i] ?? {}),
1439
+ }));
1440
+ return { ...call, args, _multiForward: false };
1441
+ }
1442
+
1504
1443
  emitCallExpr(call, targetWasmType = null) {
1505
1444
  if (call._isTypeConversion) {
1506
1445
  this.emitTypeConversion({
@@ -1520,6 +1459,12 @@ export class BuiltinsEmitter {
1520
1459
  return;
1521
1460
  }
1522
1461
 
1462
+ const forwarded = this._splitForwardedArgs(call);
1463
+ if (forwarded) {
1464
+ this.emitCallExpr(forwarded, targetWasmType);
1465
+ return;
1466
+ }
1467
+
1523
1468
  if (this._emitPackageCall(call, targetWasmType)) {
1524
1469
  return;
1525
1470
  }
@@ -1534,4 +1479,687 @@ export class BuiltinsEmitter {
1534
1479
 
1535
1480
  this._emitClosureCall(call);
1536
1481
  }
1482
+
1483
+ emitSliceClear(arg, argType) {
1484
+ const elemGoType = this._getSliceElemType(argType);
1485
+ const sliceInfo = this.mod.getSliceType(elemGoType);
1486
+ const arrInfo = sliceInfo.arrInfo;
1487
+
1488
+ const sliceTmp = this.acquireTemp({
1489
+ kind: "ref",
1490
+ nullable: true,
1491
+ typeIndex: sliceInfo.typeIndex,
1492
+ });
1493
+ this.emitExpr(arg, {
1494
+ kind: "ref",
1495
+ nullable: true,
1496
+ typeIndex: sliceInfo.typeIndex,
1497
+ });
1498
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
1499
+
1500
+ this.pushInstruction({ op: "block", blockType: "void" });
1501
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1502
+ this.pushInstruction("ref.is_null");
1503
+ this.pushInstruction({ op: "br_if", depth: 0 });
1504
+
1505
+ const arrTmp = this.acquireTemp({
1506
+ kind: "ref",
1507
+ nullable: true,
1508
+ typeIndex: arrInfo.typeIndex,
1509
+ });
1510
+ const offTmp = this.acquireTemp("i32");
1511
+ const lenTmp = this.acquireTemp("i32");
1512
+ const iLoc = this.acquireTemp("i32");
1513
+
1514
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
1515
+ arr: arrTmp,
1516
+ off: offTmp,
1517
+ len: lenTmp,
1518
+ });
1519
+
1520
+ this.pushInstruction({ op: "i32.const", value: 0 });
1521
+ this.pushInstruction({ op: "local.set", index: iLoc });
1522
+
1523
+ this.pushInstruction({ op: "block", blockType: "void" });
1524
+ this.pushInstruction({ op: "loop", blockType: "void" });
1525
+ this.pushInstruction({ op: "local.get", index: iLoc });
1526
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1527
+ this.pushInstruction("i32.ge_s");
1528
+ this.pushInstruction({ op: "br_if", depth: 1 });
1529
+
1530
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1531
+ this.pushInstruction({ op: "local.get", index: offTmp });
1532
+ this.pushInstruction({ op: "local.get", index: iLoc });
1533
+ this.pushInstruction("i32.add");
1534
+ this.emitZeroValue(elemGoType, arrInfo.elemWType);
1535
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1536
+
1537
+ this.pushInstruction({ op: "local.get", index: iLoc });
1538
+ this.pushInstruction({ op: "i32.const", value: 1 });
1539
+ this.pushInstruction("i32.add");
1540
+ this.pushInstruction({ op: "local.set", index: iLoc });
1541
+ this.pushInstruction({ op: "br", depth: 0 });
1542
+ this.pushInstruction("end");
1543
+ this.pushInstruction("end");
1544
+
1545
+ this.releaseTemp(iLoc, "i32");
1546
+ this.releaseTemp(lenTmp, "i32");
1547
+ this.releaseTemp(offTmp, "i32");
1548
+ this.releaseTemp(arrTmp, {
1549
+ kind: "ref",
1550
+ nullable: true,
1551
+ typeIndex: arrInfo.typeIndex,
1552
+ });
1553
+
1554
+ this.pushInstruction("end");
1555
+ this.releaseTemp(sliceTmp, {
1556
+ kind: "ref",
1557
+ nullable: true,
1558
+ typeIndex: sliceInfo.typeIndex,
1559
+ });
1560
+ }
1561
+
1562
+ // Pushes an integer-ish argument as i32 (Go ints are i64 in this backend).
1563
+ _emitI32Arg(arg) {
1564
+ const wType = toWasmType(arg?._type, this.mod.checker);
1565
+ if (wType === "i64") {
1566
+ this.emitExpr(arg, "i64");
1567
+ this.pushInstruction("i32.wrap_i64");
1568
+ } else {
1569
+ this.emitExpr(arg, "i32");
1570
+ }
1571
+ }
1572
+
1573
+ // `a == b` for two anyref operands (interface values). `ref.eq` only
1574
+ // accepts eqref, so host strings (internalised externs) are compared via
1575
+ // `str_eq` and anything else by identity.
1576
+ _emitAnyEq() {
1577
+ const idx = this.mod.resolveFuncIndex("__any_eq");
1578
+ if (idx === null) {
1579
+ throw new Error(
1580
+ "internal: anyref equality emitted but __any_eq was not declared (usesAnyEq scan missed a site)",
1581
+ );
1582
+ }
1583
+ this.pushInstruction({ op: "call", funcIndex: idx });
1584
+ }
1585
+
1586
+ // Every `[wasm type index, Go type name]` an `any` can hold in this module,
1587
+ // more specific types last so they overwrite their supertypes.
1588
+ _dynamicTypeNames() {
1589
+ const named = (name) => {
1590
+ const i = name.indexOf("$$");
1591
+ return i < 0
1592
+ ? goTypeName({ kind: "named", name })
1593
+ : `${name.slice(i + 2)}.${name.slice(0, i)}`;
1594
+ };
1595
+ const mod = this.mod;
1596
+ const names = [];
1597
+ for (const s of mod.sliceTypes.values())
1598
+ names.push([
1599
+ s.typeIndex,
1600
+ goTypeName({ kind: "slice", elem: s.elemGoType }),
1601
+ ]);
1602
+ for (const m of mod.mapTypes.values())
1603
+ names.push([
1604
+ m.typeIndex,
1605
+ goTypeName({ kind: "map", key: m.keyGoType, value: m.valGoType }),
1606
+ ]);
1607
+ for (const info of new Set(mod.structTypes.values())) {
1608
+ if (info.isAnon) continue;
1609
+ names.push([
1610
+ info.typeIndex,
1611
+ info.name === ERROR_STRUCT
1612
+ ? "*errors.errorString"
1613
+ : `*${named(info.name)}`,
1614
+ ]);
1615
+ }
1616
+ for (const c of mod.closureTypes.values())
1617
+ names.push([
1618
+ c.typeIndex,
1619
+ goTypeName({
1620
+ kind: "func",
1621
+ params: c.sig.params,
1622
+ returns: c.sig.returns,
1623
+ }),
1624
+ ]);
1625
+ for (const box of mod.ifaceBoxTypes.values())
1626
+ names.push([box.typeIndex, goTypeName(box.goType)]);
1627
+ for (const info of mod.taggedSliceTypes.values())
1628
+ names.push([info.typeIndex, goTypeName(info.goType)]);
1629
+ for (const info of mod.nilBoxTypes.values())
1630
+ names.push([info.typeIndex, goTypeName(info.goType)]);
1631
+ return names;
1632
+ }
1633
+
1634
+ // Body of `__ta_fail(v any, src string, target string, iface int32)`: panics
1635
+ // with Go's failed type assertion message, naming the dynamic type of `v`
1636
+ // from the types the module knows (`src` is the static interface type, or
1637
+ // the first method the target interface needs when `iface` is 1).
1638
+ _emitTypeAssertFail() {
1639
+ const strConst = (text) => {
1640
+ this.pushInstruction({
1641
+ op: "i32.const",
1642
+ value: this.mod.internString(text),
1643
+ });
1644
+ this.pushInstruction({
1645
+ op: "call",
1646
+ funcIndex: this.mod.getStringImportIndex(),
1647
+ });
1648
+ };
1649
+ const concat = () =>
1650
+ this.pushInstruction({
1651
+ op: "call",
1652
+ funcIndex: this.mod.getStringConcatImportIndex(),
1653
+ });
1654
+ const dyn = this.allocLocal(null, "externref");
1655
+ strConst("interface {}");
1656
+ this.pushInstruction({ op: "local.set", index: dyn });
1657
+ const when = (typeIndex, text) => {
1658
+ this.pushInstruction({ op: "local.get", index: 0 });
1659
+ this.pushInstruction({ op: "ref.test", typeIndex });
1660
+ this.pushInstruction({ op: "if", blockType: "void" });
1661
+ strConst(text);
1662
+ this.pushInstruction({ op: "local.set", index: dyn });
1663
+ this.pushInstruction("end");
1664
+ };
1665
+ for (const [typeIndex, text] of this._dynamicTypeNames())
1666
+ when(typeIndex, text);
1667
+ this.pushInstruction({ op: "local.get", index: 0 });
1668
+ this.pushInstruction({
1669
+ op: "call",
1670
+ funcIndex: this.mod.getIsStringImportIndex(),
1671
+ });
1672
+ this.pushInstruction({ op: "if", blockType: "void" });
1673
+ strConst("string");
1674
+ this.pushInstruction({ op: "local.set", index: dyn });
1675
+ this.pushInstruction("end");
1676
+ this.pushInstruction({ op: "local.get", index: 0 });
1677
+ this.pushInstruction("ref.is_null");
1678
+ this.pushInstruction({ op: "if", blockType: "void" });
1679
+ strConst("nil");
1680
+ this.pushInstruction({ op: "local.set", index: dyn });
1681
+ this.pushInstruction("end");
1682
+
1683
+ // An interface target: `<dyn> is not <target>: missing method <M>`.
1684
+ this.pushInstruction({ op: "local.get", index: 3 });
1685
+ this.pushInstruction({ op: "if", blockType: "void" });
1686
+ this.pushInstruction({ op: "local.get", index: 0 });
1687
+ this.pushInstruction("ref.is_null");
1688
+ this.pushInstruction({ op: "if", blockType: "void" });
1689
+ strConst("interface conversion: interface is nil, not ");
1690
+ this.pushInstruction({ op: "local.get", index: 2 });
1691
+ concat();
1692
+ this.emitRuntimePanicThrow();
1693
+ this.pushInstruction("end");
1694
+ strConst("interface conversion: ");
1695
+ this.pushInstruction({ op: "local.get", index: dyn });
1696
+ concat();
1697
+ strConst(" is not ");
1698
+ concat();
1699
+ this.pushInstruction({ op: "local.get", index: 2 });
1700
+ concat();
1701
+ strConst(": missing method ");
1702
+ concat();
1703
+ this.pushInstruction({ op: "local.get", index: 1 });
1704
+ concat();
1705
+ this.emitRuntimePanicThrow();
1706
+ this.pushInstruction("end");
1707
+
1708
+ strConst("interface conversion: ");
1709
+ this.pushInstruction({ op: "local.get", index: 1 });
1710
+ concat();
1711
+ strConst(" is ");
1712
+ concat();
1713
+ this.pushInstruction({ op: "local.get", index: dyn });
1714
+ concat();
1715
+ strConst(", not ");
1716
+ concat();
1717
+ this.pushInstruction({ op: "local.get", index: 2 });
1718
+ concat();
1719
+ this.emitRuntimePanicThrow();
1720
+ }
1721
+
1722
+ // `any_eq` arm for two arrays of one wasm array type (an array stored in
1723
+ // an interface is the array ref itself): equal lengths and elements.
1724
+ _emitAnyArrayEqArm(arrInfo) {
1725
+ const ref = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
1726
+ for (const idx of [0, 1]) {
1727
+ this.pushInstruction({ op: "local.get", index: idx });
1728
+ this.pushInstruction({ op: "ref.test", typeIndex: arrInfo.typeIndex });
1729
+ }
1730
+ this.pushInstruction("i32.and");
1731
+ this.pushInstruction({ op: "if", blockType: "void" });
1732
+ const a1 = this.allocLocal(null, ref);
1733
+ const a2 = this.allocLocal(null, ref);
1734
+ for (const [src, dst] of [
1735
+ [0, a1],
1736
+ [1, a2],
1737
+ ]) {
1738
+ this.pushInstruction({ op: "local.get", index: src });
1739
+ this.pushInstruction({ op: "ref.cast", typeIndex: arrInfo.typeIndex });
1740
+ this.pushInstruction({ op: "local.set", index: dst });
1741
+ }
1742
+ const len = this.allocLocal(null, "i32");
1743
+ this.pushInstruction({ op: "local.get", index: a1 });
1744
+ this.pushInstruction("array.len");
1745
+ this.pushInstruction({ op: "local.tee", index: len });
1746
+ this.pushInstruction({ op: "local.get", index: a2 });
1747
+ this.pushInstruction("array.len");
1748
+ this.pushInstruction("i32.ne");
1749
+ this.pushInstruction({ op: "if", blockType: "void" });
1750
+ this.pushInstruction({ op: "i32.const", value: 0 });
1751
+ this.pushInstruction("return");
1752
+ this.pushInstruction("end");
1753
+ const i = this.allocLocal(null, "i32");
1754
+ const e1 = this.allocLocal(null, arrInfo.elemWType);
1755
+ const e2 = this.allocLocal(null, arrInfo.elemWType);
1756
+ this.pushInstruction({ op: "i32.const", value: 0 });
1757
+ this.pushInstruction({ op: "local.set", index: i });
1758
+ this.pushInstruction({ op: "block", blockType: "void" });
1759
+ this.pushInstruction({ op: "loop", blockType: "void" });
1760
+ this.pushInstruction({ op: "local.get", index: i });
1761
+ this.pushInstruction({ op: "local.get", index: len });
1762
+ this.pushInstruction("i32.ge_u");
1763
+ this.pushInstruction({ op: "br_if", depth: 1 });
1764
+ for (const [arr, dst] of [
1765
+ [a1, e1],
1766
+ [a2, e2],
1767
+ ]) {
1768
+ this.pushInstruction({ op: "local.get", index: arr });
1769
+ this.pushInstruction({ op: "local.get", index: i });
1770
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1771
+ this.pushInstruction({ op: "local.set", index: dst });
1772
+ }
1773
+ this.emitKeyEq(e1, e2, arrInfo.elemGoType);
1774
+ this.pushInstruction("i32.eqz");
1775
+ this.pushInstruction({ op: "if", blockType: "void" });
1776
+ this.pushInstruction({ op: "i32.const", value: 0 });
1777
+ this.pushInstruction("return");
1778
+ this.pushInstruction("end");
1779
+ this.pushInstruction({ op: "local.get", index: i });
1780
+ this.pushInstruction({ op: "i32.const", value: 1 });
1781
+ this.pushInstruction("i32.add");
1782
+ this.pushInstruction({ op: "local.set", index: i });
1783
+ this.pushInstruction({ op: "br", depth: 0 });
1784
+ this.pushInstruction("end");
1785
+ this.pushInstruction("end");
1786
+ this.pushInstruction({ op: "i32.const", value: 1 });
1787
+ this.pushInstruction("return");
1788
+ this.pushInstruction("end");
1789
+ }
1790
+
1791
+ // `any_hash` arm for an array: the wasm array type, then each element,
1792
+ // as `emitKeyHash` combines a static array key (so equal arrays under
1793
+ // `any_eq` hash equal).
1794
+ _emitAnyArrayHashArm(arrInfo) {
1795
+ const ref = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
1796
+ this.pushInstruction({ op: "local.get", index: 0 });
1797
+ this.pushInstruction({ op: "ref.test", typeIndex: arrInfo.typeIndex });
1798
+ this.pushInstruction({ op: "if", blockType: "void" });
1799
+ const arr = this.allocLocal(null, ref);
1800
+ const h = this.allocLocal(null, "i32");
1801
+ const i = this.allocLocal(null, "i32");
1802
+ const e = this.allocLocal(null, arrInfo.elemWType);
1803
+ this.pushInstruction({ op: "local.get", index: 0 });
1804
+ this.pushInstruction({ op: "ref.cast", typeIndex: arrInfo.typeIndex });
1805
+ this.pushInstruction({ op: "local.set", index: arr });
1806
+ this.pushInstruction({ op: "i32.const", value: arrInfo.typeIndex });
1807
+ this.pushInstruction({ op: "local.set", index: h });
1808
+ this.pushInstruction({ op: "i32.const", value: 0 });
1809
+ this.pushInstruction({ op: "local.set", index: i });
1810
+ this.pushInstruction({ op: "block", blockType: "void" });
1811
+ this.pushInstruction({ op: "loop", blockType: "void" });
1812
+ this.pushInstruction({ op: "local.get", index: i });
1813
+ this.pushInstruction({ op: "local.get", index: arr });
1814
+ this.pushInstruction("array.len");
1815
+ this.pushInstruction("i32.ge_u");
1816
+ this.pushInstruction({ op: "br_if", depth: 1 });
1817
+ this.pushInstruction({ op: "local.get", index: arr });
1818
+ this.pushInstruction({ op: "local.get", index: i });
1819
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1820
+ this.pushInstruction({ op: "local.set", index: e });
1821
+ this.pushInstruction({ op: "local.get", index: h });
1822
+ this.pushInstruction({ op: "i32.const", value: 31 });
1823
+ this.pushInstruction("i32.mul");
1824
+ this.emitKeyHash(e, arrInfo.elemGoType);
1825
+ this.pushInstruction("i32.add");
1826
+ this.pushInstruction({ op: "local.set", index: h });
1827
+ this.pushInstruction({ op: "local.get", index: i });
1828
+ this.pushInstruction({ op: "i32.const", value: 1 });
1829
+ this.pushInstruction("i32.add");
1830
+ this.pushInstruction({ op: "local.set", index: i });
1831
+ this.pushInstruction({ op: "br", depth: 0 });
1832
+ this.pushInstruction("end");
1833
+ this.pushInstruction("end");
1834
+ this.pushInstruction({ op: "local.get", index: h });
1835
+ this.pushInstruction("return");
1836
+ this.pushInstruction("end");
1837
+ }
1838
+
1839
+ emitRuntimeHelper(fn) {
1840
+ const errInfo = this.mod.getStructType(ERROR_STRUCT);
1841
+ switch (fn._isRuntimeHelper) {
1842
+ case "any_eq": {
1843
+ const bothNull = () => {
1844
+ this.pushInstruction({ op: "local.get", index: 0 });
1845
+ this.pushInstruction("ref.is_null");
1846
+ this.pushInstruction({ op: "local.get", index: 1 });
1847
+ this.pushInstruction("ref.is_null");
1848
+ };
1849
+ bothNull();
1850
+ this.pushInstruction("i32.and");
1851
+ this.pushInstruction({ op: "if", blockType: "void" });
1852
+ this.pushInstruction({ op: "i32.const", value: 1 });
1853
+ this.pushInstruction("return");
1854
+ this.pushInstruction("end");
1855
+ bothNull();
1856
+ this.pushInstruction("i32.or");
1857
+ this.pushInstruction({ op: "if", blockType: "void" });
1858
+ this.pushInstruction({ op: "i32.const", value: 0 });
1859
+ this.pushInstruction("return");
1860
+ this.pushInstruction("end");
1861
+
1862
+ // Check unhashable / uncomparable types
1863
+ const checkUncomparable = (typeIndex) => {
1864
+ this.pushInstruction({ op: "local.get", index: 0 });
1865
+ this.pushInstruction({ op: "ref.test", typeIndex });
1866
+ this.pushInstruction({ op: "local.get", index: 1 });
1867
+ this.pushInstruction({ op: "ref.test", typeIndex });
1868
+ this.pushInstruction("i32.or");
1869
+ this.pushInstruction({ op: "if", blockType: "void" });
1870
+ this.emitPanic("runtime error: comparing uncomparable type");
1871
+ this.pushInstruction("end");
1872
+ };
1873
+ for (const s of this.mod.sliceTypes.values())
1874
+ checkUncomparable(s.typeIndex);
1875
+ for (const m of this.mod.mapTypes.values())
1876
+ checkUncomparable(m.typeIndex);
1877
+ for (const c of this.mod.closureTypes.values())
1878
+ checkUncomparable(c.typeIndex);
1879
+ for (const b of this.mod.ifaceBoxTypes.values()) {
1880
+ if (isUnhashableGoType(b.goType, this.mod.checker))
1881
+ checkUncomparable(b.typeIndex);
1882
+ }
1883
+ for (const a of this.mod.arrayTypes.values()) {
1884
+ if (isUnhashableGoType(a.elemGoType, this.mod.checker))
1885
+ checkUncomparable(a.typeIndex);
1886
+ }
1887
+
1888
+ // Boxed values compare by value (Go: any(3) == any(3), struct values, etc.).
1889
+ for (const box of this.mod.ifaceBoxTypes.values()) {
1890
+ if (isUnhashableGoType(box.goType, this.mod.checker)) continue;
1891
+ for (const idx of [0, 1]) {
1892
+ this.pushInstruction({ op: "local.get", index: idx });
1893
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
1894
+ }
1895
+ this.pushInstruction("i32.and");
1896
+ this.pushInstruction({ op: "if", blockType: "void" });
1897
+ const f1 = this.allocLocal(null, box.wType);
1898
+ const f2 = this.allocLocal(null, box.wType);
1899
+ for (const [src, dst] of [
1900
+ [0, f1],
1901
+ [1, f2],
1902
+ ]) {
1903
+ this.pushInstruction({ op: "local.get", index: src });
1904
+ this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
1905
+ this.pushInstruction({
1906
+ op: "struct.get",
1907
+ typeIndex: box.typeIndex,
1908
+ fieldIndex: 0,
1909
+ });
1910
+ this.pushInstruction({ op: "local.set", index: dst });
1911
+ }
1912
+ this.emitKeyEq(f1, f2, box.goType);
1913
+ this.pushInstruction("return");
1914
+ this.pushInstruction("end");
1915
+ }
1916
+
1917
+ // Arrays compare element by element.
1918
+ for (const a of [...this.mod.arrayTypes.values()]) {
1919
+ if (!isUnhashableGoType(a.elemGoType, this.mod.checker))
1920
+ this._emitAnyArrayEqArm(a);
1921
+ }
1922
+
1923
+ // Strings
1924
+ this.pushInstruction({ op: "local.get", index: 0 });
1925
+ this.pushInstruction({
1926
+ op: "call",
1927
+ funcIndex: this.mod.getIsStringImportIndex(),
1928
+ });
1929
+ this.pushInstruction({ op: "local.get", index: 1 });
1930
+ this.pushInstruction({
1931
+ op: "call",
1932
+ funcIndex: this.mod.getIsStringImportIndex(),
1933
+ });
1934
+ this.pushInstruction("i32.and");
1935
+ this.pushInstruction({ op: "if", blockType: "void" });
1936
+ this.pushInstruction({ op: "local.get", index: 0 });
1937
+ this.pushInstruction("extern.convert_any");
1938
+ this.pushInstruction({ op: "local.get", index: 1 });
1939
+ this.pushInstruction("extern.convert_any");
1940
+ this.pushInstruction({
1941
+ op: "call",
1942
+ funcIndex: this.mod.getStringCmpImportIndex("=="),
1943
+ });
1944
+ this.pushInstruction("return");
1945
+ this.pushInstruction("end");
1946
+
1947
+ // Heap reference equality
1948
+ this.pushInstruction({ op: "local.get", index: 0 });
1949
+ this.pushInstruction({ op: "ref.test", heapType: "eq" });
1950
+ this.pushInstruction({ op: "local.get", index: 1 });
1951
+ this.pushInstruction({ op: "ref.test", heapType: "eq" });
1952
+ this.pushInstruction("i32.and");
1953
+ this.pushInstruction({ op: "if", blockType: "void" });
1954
+ this.pushInstruction({ op: "local.get", index: 0 });
1955
+ this.pushInstruction({ op: "ref.cast", heapType: "eq" });
1956
+ this.pushInstruction({ op: "local.get", index: 1 });
1957
+ this.pushInstruction({ op: "ref.cast", heapType: "eq" });
1958
+ this.pushInstruction("ref.eq");
1959
+ this.pushInstruction("return");
1960
+ this.pushInstruction("end");
1961
+
1962
+ this.pushInstruction({ op: "i32.const", value: 0 });
1963
+ this.pushInstruction("return");
1964
+ return;
1965
+ }
1966
+ case "any_ref": {
1967
+ // A tagged slice is a fresh header on each conversion to an `any`:
1968
+ // its backing array is the identity every copy shares.
1969
+ for (const info of this.mod.taggedSliceTypes.values()) {
1970
+ this.pushInstruction({ op: "local.get", index: 0 });
1971
+ this.pushInstruction({ op: "ref.test", typeIndex: info.typeIndex });
1972
+ this.pushInstruction({ op: "if", blockType: "void" });
1973
+ this.pushInstruction({ op: "local.get", index: 0 });
1974
+ this.pushInstruction({ op: "ref.cast", typeIndex: info.typeIndex });
1975
+ this.pushInstruction({
1976
+ op: "struct.get",
1977
+ typeIndex: info.typeIndex,
1978
+ fieldIndex: 0,
1979
+ });
1980
+ this.pushInstruction("return");
1981
+ this.pushInstruction("end");
1982
+ }
1983
+ // A boxed defined map or func value: the reference inside the box.
1984
+ for (const box of this.mod.ifaceBoxTypes.values()) {
1985
+ if (
1986
+ !isMapType(box.goType, this.mod.checker) &&
1987
+ !isFuncType(box.goType, this.mod.checker)
1988
+ )
1989
+ continue;
1990
+ this.pushInstruction({ op: "local.get", index: 0 });
1991
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
1992
+ this.pushInstruction({ op: "if", blockType: "void" });
1993
+ this.pushInstruction({ op: "local.get", index: 0 });
1994
+ this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
1995
+ this.pushInstruction({
1996
+ op: "struct.get",
1997
+ typeIndex: box.typeIndex,
1998
+ fieldIndex: 0,
1999
+ });
2000
+ this.pushInstruction("return");
2001
+ this.pushInstruction("end");
2002
+ }
2003
+ this.pushInstruction({ op: "local.get", index: 0 });
2004
+ this.pushInstruction("return");
2005
+ return;
2006
+ }
2007
+ case "any_hash": {
2008
+ this.pushInstruction({ op: "local.get", index: 0 });
2009
+ this.pushInstruction("ref.is_null");
2010
+ this.pushInstruction({ op: "if", blockType: "void" });
2011
+ this.pushInstruction({ op: "i32.const", value: 0 });
2012
+ this.pushInstruction("return");
2013
+ this.pushInstruction("end");
2014
+
2015
+ // Check unhashable types. `goType` names the dynamic type in the
2016
+ // panic, as Go does (`main.S`, `main.L`, `func()`).
2017
+ const checkUnhashable = (typeIndex, goType) => {
2018
+ this.pushInstruction({ op: "local.get", index: 0 });
2019
+ this.pushInstruction({ op: "ref.test", typeIndex });
2020
+ this.pushInstruction({ op: "if", blockType: "void" });
2021
+ this.emitPanic(
2022
+ goType
2023
+ ? `runtime error: hash of unhashable type ${goTypeName(goType)}`
2024
+ : "runtime error: hash of unhashable type",
2025
+ );
2026
+ this.pushInstruction("end");
2027
+ };
2028
+ // A slice type first built from source syntax (a `TypeName`) names
2029
+ // its element as written.
2030
+ const spelled = (t) =>
2031
+ t?.kind === "TypeName" ? { kind: "basic", name: t.name } : t;
2032
+ // Tagged slices (a defined slice type in an interface) are subtypes
2033
+ // of the plain slice type, so they are tested first.
2034
+ for (const info of this.mod.taggedSliceTypes.values())
2035
+ checkUnhashable(info.typeIndex, info.goType);
2036
+ for (const sliceInfo of this.mod.sliceTypes.values())
2037
+ checkUnhashable(sliceInfo.typeIndex, {
2038
+ kind: "slice",
2039
+ elem: spelled(sliceInfo.elemGoType),
2040
+ });
2041
+ for (const mapInfo of this.mod.mapTypes.values())
2042
+ checkUnhashable(mapInfo.typeIndex, {
2043
+ kind: "map",
2044
+ key: mapInfo.keyGoType,
2045
+ value: mapInfo.valGoType,
2046
+ });
2047
+ for (const closureInfo of this.mod.closureTypes.values())
2048
+ checkUnhashable(closureInfo.typeIndex, {
2049
+ kind: "func",
2050
+ params: closureInfo.sig.params,
2051
+ returns: closureInfo.sig.returns,
2052
+ });
2053
+ for (const box of this.mod.ifaceBoxTypes.values()) {
2054
+ if (isUnhashableGoType(box.goType, this.mod.checker))
2055
+ checkUnhashable(box.typeIndex, box.goType);
2056
+ }
2057
+ for (const arrInfo of this.mod.arrayTypes.values()) {
2058
+ if (isUnhashableGoType(arrInfo.elemGoType, this.mod.checker))
2059
+ checkUnhashable(arrInfo.typeIndex);
2060
+ }
2061
+
2062
+ // Check boxed values (scalars, struct values)
2063
+ for (const box of this.mod.ifaceBoxTypes.values()) {
2064
+ if (isUnhashableGoType(box.goType, this.mod.checker)) continue;
2065
+ this.pushInstruction({ op: "local.get", index: 0 });
2066
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
2067
+ this.pushInstruction({ op: "if", blockType: "void" });
2068
+ const valLoc = this.allocLocal(null, box.wType);
2069
+ this.pushInstruction({ op: "local.get", index: 0 });
2070
+ this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
2071
+ this.pushInstruction({
2072
+ op: "struct.get",
2073
+ typeIndex: box.typeIndex,
2074
+ fieldIndex: 0,
2075
+ });
2076
+ this.pushInstruction({ op: "local.set", index: valLoc });
2077
+ this.pushInstruction({ op: "i32.const", value: box.typeIndex });
2078
+ this.pushInstruction({ op: "i32.const", value: 31 });
2079
+ this.pushInstruction("i32.mul");
2080
+ this.emitKeyHash(valLoc, box.goType);
2081
+ this.pushInstruction("i32.add");
2082
+ this.pushInstruction("return");
2083
+ this.pushInstruction("end");
2084
+ }
2085
+
2086
+ // Arrays hash element by element.
2087
+ for (const a of [...this.mod.arrayTypes.values()]) {
2088
+ if (!isUnhashableGoType(a.elemGoType, this.mod.checker))
2089
+ this._emitAnyArrayHashArm(a);
2090
+ }
2091
+
2092
+ // Check string
2093
+ this.pushInstruction({ op: "local.get", index: 0 });
2094
+ this.pushInstruction({
2095
+ op: "call",
2096
+ funcIndex: this.mod.getIsStringImportIndex(),
2097
+ });
2098
+ this.pushInstruction({ op: "if", blockType: "void" });
2099
+ this.pushInstruction({ op: "local.get", index: 0 });
2100
+ this.pushInstruction("extern.convert_any");
2101
+ this.pushInstruction({
2102
+ op: "call",
2103
+ funcIndex: this.mod.getStringHashImportIndex(),
2104
+ });
2105
+ this.pushInstruction("return");
2106
+ this.pushInstruction("end");
2107
+
2108
+ this.pushInstruction({ op: "i32.const", value: 0 });
2109
+ this.pushInstruction("return");
2110
+ return;
2111
+ }
2112
+ case "ta_fail":
2113
+ this._emitTypeAssertFail();
2114
+ return;
2115
+ case "error_msg":
2116
+ this.pushInstruction({ op: "local.get", index: 0 });
2117
+ this.pushInstruction({
2118
+ op: "struct.get",
2119
+ typeIndex: errInfo.typeIndex,
2120
+ fieldIndex: 0,
2121
+ });
2122
+ this.pushInstruction("return");
2123
+ return;
2124
+ case "mk_error":
2125
+ // nil message (strconv success) → nil error
2126
+ this.pushInstruction({ op: "local.get", index: 0 });
2127
+ this.pushInstruction("any.convert_extern");
2128
+ this.pushInstruction("ref.is_null");
2129
+ this.pushInstruction({ op: "if", blockType: "void" });
2130
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
2131
+ this.pushInstruction("return");
2132
+ this.pushInstruction("end");
2133
+ this.pushInstruction({ op: "local.get", index: 0 });
2134
+ this.pushInstruction({
2135
+ op: "struct.new",
2136
+ typeIndex: errInfo.typeIndex,
2137
+ });
2138
+ this.pushInstruction("return");
2139
+ return;
2140
+ case "error_str":
2141
+ this.pushInstruction({ op: "local.get", index: 0 });
2142
+ this.pushInstruction("ref.is_null");
2143
+ this.pushInstruction({ op: "if", blockType: "void" });
2144
+ this.pushInstruction({
2145
+ op: "i32.const",
2146
+ value: this.mod.internString("<nil>"),
2147
+ });
2148
+ this.pushInstruction({
2149
+ op: "call",
2150
+ funcIndex: this.mod.getStringImportIndex(),
2151
+ });
2152
+ this.pushInstruction("return");
2153
+ this.pushInstruction("end");
2154
+ this.pushInstruction({ op: "local.get", index: 0 });
2155
+ this.pushInstruction({
2156
+ op: "call",
2157
+ funcIndex: this.mod.resolveFuncIndex("__dispatch_error_Error"),
2158
+ });
2159
+ this.pushInstruction("return");
2160
+ return;
2161
+ default:
2162
+ throw new Error(`Unknown runtime helper: ${fn._isRuntimeHelper}`);
2163
+ }
2164
+ }
1537
2165
  }