gofront 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -1,21 +1,55 @@
1
1
  // src/backend/wasm/emit-exprs.js
2
2
  // FunctionEmitter mixin: expression emission (literals, operators, indexing, conversions).
3
3
 
4
- import { foldConstExpr } from "./const-fold.js";
5
4
  import {
5
+ constFromLit,
6
+ constIntValue,
7
+ constToLit,
8
+ foldConstExpr,
9
+ } from "../../typechecker/const-fold.js";
10
+ import {
11
+ compositeLitIndices,
12
+ compositeLitMaxIndex,
13
+ } from "../../typechecker/types.js";
14
+ import { goTypeName } from "../shared/fmt-types.js";
15
+ import { emitWasmNative } from "./natives.js";
16
+ import {
17
+ getInterfaceMethodList,
6
18
  getMapKeyValTypes,
7
19
  isAnyType,
8
20
  isArrayType,
21
+ isComplex,
22
+ isComplex64,
9
23
  isFuncType,
10
24
  isMapType,
25
+ isNonEmptyInterface,
11
26
  isPointerToStruct,
27
+ isRuneSlice,
28
+ isScalarType,
12
29
  isSigned,
13
30
  isSliceType,
14
31
  isStringType,
15
32
  isStructType,
33
+ isTaggedSliceType,
16
34
  toWasmType,
17
35
  } from "./types.js";
18
36
 
37
+ // Engine limit on `array.new_fixed` operands.
38
+ const MAX_NEW_FIXED = 10000;
39
+
40
+ // An untyped integral float constant (`1e18 + 1`) in an integer context is
41
+ // emitted from its exact integer value.
42
+ function constLitFor(val, targetWasmType) {
43
+ const i = constIntValue(val);
44
+ if (
45
+ val.k === "FLOAT" &&
46
+ i !== null &&
47
+ (targetWasmType === "i64" || targetWasmType === "i32")
48
+ )
49
+ return constToLit({ k: "INT", v: i });
50
+ return constToLit(val);
51
+ }
52
+
19
53
  export class ExprsEmitter {
20
54
  emitExpr(expr, targetWasmType = null) {
21
55
  if (!expr) return;
@@ -36,7 +70,7 @@ export class ExprsEmitter {
36
70
  }
37
71
 
38
72
  if (
39
- isStringType(goType, this.mod.checker) ||
73
+ (isStringType(goType, this.mod.checker) && goType?.kind !== "named") ||
40
74
  (expr.kind === "BasicLit" && expr.litKind === "STRING")
41
75
  ) {
42
76
  this._emitRawExpr(expr, "externref");
@@ -50,6 +84,7 @@ export class ExprsEmitter {
50
84
  // for type switches and assertions.
51
85
  if (isPointerToStruct(goType, this.mod.checker, this.mod)) {
52
86
  this._emitRawExpr(expr, this.toWasmType(goType));
87
+ this._emitNilAwareAny(goType, expr);
53
88
  return;
54
89
  }
55
90
  if (isStructType(goType, this.mod.checker, this.mod)) {
@@ -70,6 +105,15 @@ export class ExprsEmitter {
70
105
  return;
71
106
  }
72
107
 
108
+ // A defined func or map type with methods is boxed to keep its identity,
109
+ // so interface dispatch can tell it from the plain func or map type.
110
+ if (this._isMethodNamedRef(goType)) {
111
+ const box = this.mod.getIfaceBoxType(this._resolveTestTarget(goType));
112
+ this._emitRawExpr(expr, box.wType);
113
+ this._emitBoxValue(box);
114
+ return;
115
+ }
116
+
73
117
  if (
74
118
  isSliceType(goType, this.mod.checker) ||
75
119
  isArrayType(goType, this.mod.checker) ||
@@ -78,6 +122,16 @@ export class ExprsEmitter {
78
122
  ) {
79
123
  const refWType = this.toWasmType(goType);
80
124
  this._emitRawExpr(expr, refWType);
125
+ // An array stored in an interface is a copy.
126
+ if (isArrayType(goType, this.mod.checker))
127
+ this._emitCopyIfValue(expr, goType, refWType);
128
+ if (isTaggedSliceType(goType, this.mod.checker))
129
+ this._emitTagSlice(goType, refWType);
130
+ if (
131
+ isFuncType(goType, this.mod.checker) ||
132
+ isMapType(goType, this.mod.checker)
133
+ )
134
+ this._emitNilAwareAny(goType, expr);
81
135
  return;
82
136
  }
83
137
 
@@ -92,6 +146,102 @@ export class ExprsEmitter {
92
146
  this._emitRawExpr(expr, targetWasmType);
93
147
  }
94
148
 
149
+ // Panics with Go's failed type assertion message: the static interface
150
+ // type, the dynamic type of the value in `anyTmp` and the target type.
151
+ _emitTypeAssertPanic(expr, anyTmp) {
152
+ const checker = this.mod.checker;
153
+ const target = this._resolveTestTarget(expr._type ?? expr.type);
154
+ const iface = isNonEmptyInterface(target, checker);
155
+ this.pushInstruction({ op: "local.get", index: anyTmp });
156
+ if (iface) {
157
+ const names = getInterfaceMethodList(target.underlying ?? target)
158
+ .map((m) => m.name)
159
+ .sort();
160
+ this._pushStringConst(names[0] ?? "");
161
+ } else {
162
+ this._pushStringConst(this._assertSrcName(expr.expr?._type));
163
+ }
164
+ this._pushStringConst(
165
+ target.name === "error" ? "error" : goTypeName(target),
166
+ );
167
+ this.pushInstruction({ op: "i32.const", value: iface ? 1 : 0 });
168
+ this.pushInstruction({
169
+ op: "call",
170
+ funcIndex: this.mod.resolveFuncIndex("__ta_fail"),
171
+ });
172
+ this.pushInstruction("unreachable");
173
+ }
174
+
175
+ // The static interface type of an assertion operand as Go names it.
176
+ _assertSrcName(srcType) {
177
+ if (!srcType || !isNonEmptyInterface(srcType, this.mod.checker))
178
+ return "interface {}";
179
+ return srcType.name === "error" ? "error" : goTypeName(srcType);
180
+ }
181
+
182
+ // Pushes the interned string `text` as an externref.
183
+ _pushStringConst(text) {
184
+ this.pushInstruction({
185
+ op: "i32.const",
186
+ value: this.mod.internString(text),
187
+ });
188
+ this.pushInstruction({
189
+ op: "call",
190
+ funcIndex: this.mod.getStringImportIndex(),
191
+ });
192
+ }
193
+
194
+ // The slice on the stack becomes the tagged subtype of its Go type, so a
195
+ // type test can tell `[]byte` from `[]bool` (see getTaggedSliceType). The
196
+ // copy shares the backing array; slice headers are immutable.
197
+ _emitTagSlice(goType, refWType) {
198
+ const tagged = this.mod.getTaggedSliceType(
199
+ goType,
200
+ this._getSliceElemType(goType),
201
+ );
202
+ const base = tagged.base;
203
+ const tmp = this.acquireTemp(refWType);
204
+ this.pushInstruction({ op: "local.tee", index: tmp });
205
+ this.pushInstruction("ref.is_null");
206
+ this.pushInstruction({
207
+ op: "if",
208
+ blockType: { kind: "ref", nullable: true, typeIndex: base.typeIndex },
209
+ });
210
+ this.pushInstruction({ op: "global.get", index: base.emptyGlobalIndex });
211
+ this.pushInstruction("else");
212
+ this.pushInstruction({ op: "local.get", index: tmp });
213
+ this.pushInstruction("end");
214
+ this.pushInstruction({ op: "local.set", index: tmp });
215
+ for (let i = 0; i < 4; i++) {
216
+ this.pushInstruction({ op: "local.get", index: tmp });
217
+ this.pushInstruction({
218
+ op: "struct.get",
219
+ typeIndex: base.typeIndex,
220
+ fieldIndex: i,
221
+ });
222
+ }
223
+ this.pushInstruction({ op: "ref.null", heapType: tagged.tagTypeIndex });
224
+ this.pushInstruction({ op: "struct.new", typeIndex: tagged.typeIndex });
225
+ this.releaseTemp(tmp, refWType);
226
+ }
227
+
228
+ // The nullable ref on the stack becomes an interface value: a nil map, func
229
+ // or pointer turns into the marker of its type (never a nil interface).
230
+ _emitNilAwareAny(goType, expr) {
231
+ if (expr.kind === "UnaryExpr" && expr.op === "&") return;
232
+ if (expr.kind === "CompositeLit" || expr.kind === "FuncLit") return;
233
+ const nilBox = this.mod.getNilBoxType(goType);
234
+ const tmp = this.acquireTemp(this.toWasmType(goType));
235
+ this.pushInstruction({ op: "local.tee", index: tmp });
236
+ this.pushInstruction("ref.is_null");
237
+ this.pushInstruction({ op: "if", blockType: "anyref" });
238
+ this.pushInstruction({ op: "global.get", index: nilBox.globalIndex });
239
+ this.pushInstruction("else");
240
+ this.pushInstruction({ op: "local.get", index: tmp });
241
+ this.pushInstruction("end");
242
+ this.releaseTemp(tmp, this.toWasmType(goType));
243
+ }
244
+
95
245
  _emitBoxValue(box) {
96
246
  this.pushInstruction({
97
247
  op: "ref.null",
@@ -116,6 +266,9 @@ export class ExprsEmitter {
116
266
  return { kind: "nil", name: "nil" };
117
267
  }
118
268
  }
269
+ if (expr.kind === "ImagLit") {
270
+ return { kind: "untyped", base: "complex128" };
271
+ }
119
272
  if (expr.kind === "Ident") {
120
273
  const local = this.resolveLocal(expr.name);
121
274
  if (local) return local.goType;
@@ -128,23 +281,46 @@ export class ExprsEmitter {
128
281
  return null;
129
282
  }
130
283
 
284
+ _emitLocalConst(ident, targetWasmType) {
285
+ const lit = constLitFor(ident._const, targetWasmType);
286
+ if (!lit) return false;
287
+ lit._type = ident._type;
288
+ this.emitBasicLit(lit, targetWasmType);
289
+ return true;
290
+ }
291
+
131
292
  _emitRawExpr(expr, targetWasmType = null) {
132
293
  if (expr.kind === "BinaryExpr" || expr.kind === "UnaryExpr") {
133
- const folded = foldConstExpr(expr, (name) =>
134
- this.mod.pkgConsts?.get(name),
135
- );
294
+ // The checker's exact value (src/typechecker/const-fold.js) when it has one.
295
+ const folded = expr._const
296
+ ? constLitFor(expr._const, targetWasmType)
297
+ : foldConstExpr(expr, (name) => this.mod.pkgConsts?.get(name));
136
298
  if (folded) {
137
299
  folded._type = expr._type;
138
300
  this.emitBasicLit(folded, targetWasmType);
139
301
  return;
140
302
  }
141
303
  }
304
+ // A function-local constant is a local slot (so closures can capture it)
305
+ // but reads as the checker's exact value, which adapts to the context.
306
+ if (
307
+ expr.kind === "Ident" &&
308
+ expr._const &&
309
+ this.resolveLocal(expr.name) &&
310
+ this._emitLocalConst(expr, targetWasmType)
311
+ )
312
+ return;
313
+ if (this._emitFoldedIntConversion(expr, targetWasmType)) return;
142
314
 
143
315
  switch (expr.kind) {
144
316
  case "BasicLit":
145
317
  this.emitBasicLit(expr, targetWasmType);
146
318
  break;
147
319
 
320
+ case "ImagLit":
321
+ this.emitImagLit(expr, targetWasmType);
322
+ break;
323
+
148
324
  case "Ident":
149
325
  this.emitIdent(expr, targetWasmType);
150
326
  break;
@@ -158,7 +334,8 @@ export class ExprsEmitter {
158
334
  break;
159
335
 
160
336
  case "CallExpr":
161
- this.emitCallExpr(expr, targetWasmType);
337
+ if (!emitWasmNative(this, expr, targetWasmType))
338
+ this.emitCallExpr(expr, targetWasmType);
162
339
  break;
163
340
 
164
341
  case "TypeConversion":
@@ -172,20 +349,6 @@ export class ExprsEmitter {
172
349
  case "SelectorExpr": {
173
350
  const deq = this._dequalify(expr);
174
351
  if (deq) this.emitIdent(deq, targetWasmType);
175
- else if (this._isMathConst(expr))
176
- this.emitMathConst(expr.field, targetWasmType);
177
- else if (this._isUtf8Const(expr))
178
- this.emitUtf8Const(expr.field, targetWasmType);
179
- else if (this._isStrconvConst(expr))
180
- this.emitStrconvConst(expr.field, targetWasmType);
181
- else if (this._isPathConst(expr))
182
- this.emitPathConst(expr.field, targetWasmType);
183
- else if (this._isIoConst(expr))
184
- this.emitIoConst(expr.field, targetWasmType);
185
- else if (this._isTimeConst(expr))
186
- this.emitTimeConst(expr.field, targetWasmType);
187
- else if (this._isExtendedPkgConst?.(expr))
188
- this.emitExtendedPkgConst(expr, targetWasmType);
189
352
  else this.emitSelectorExpr(expr);
190
353
  break;
191
354
  }
@@ -217,7 +380,7 @@ export class ExprsEmitter {
217
380
  this._emitTypeTest(targetGoType);
218
381
  this.pushInstruction("i32.eqz");
219
382
  this.pushInstruction({ op: "if", blockType: "void" });
220
- this.emitPanic("interface conversion: type assertion failed");
383
+ this._emitTypeAssertPanic(expr, anyTmp);
221
384
  this.pushInstruction("end");
222
385
 
223
386
  this.pushInstruction({ op: "local.get", index: anyTmp });
@@ -247,7 +410,7 @@ export class ExprsEmitter {
247
410
  if (capturedNames.length === 0) {
248
411
  this.pushInstruction({ op: "ref.null", heapType: "any" });
249
412
  } else {
250
- const envFields = this._buildEnvFields(capturedNames);
413
+ const envFields = this._buildEnvFields(capturedNames, funcLit);
251
414
  const envInfo = this.mod.getEnvType(envFields);
252
415
 
253
416
  for (const field of envFields) {
@@ -302,7 +465,7 @@ export class ExprsEmitter {
302
465
  this.pushInstruction({ op: "local.get", index: mTmp });
303
466
  this.emitExpr(elem.key, mapInfo.keyWType);
304
467
  this.emitExpr(elem.value, mapInfo.valWType);
305
- this._emitCopyIfValueStruct(elem.value, valType, mapInfo.valWType);
468
+ this._emitCopyIfValue(elem.value, valType, mapInfo.valWType);
306
469
  this.pushInstruction({
307
470
  op: "call",
308
471
  funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
@@ -315,33 +478,11 @@ export class ExprsEmitter {
315
478
  }
316
479
 
317
480
  if (isSliceType(litType, this.mod.checker)) {
318
- const elemType = litType.elem;
481
+ const elemType = this._getArrayElemType(litType);
319
482
  const sliceInfo = this.mod.getSliceType(elemType);
320
- const count = lit.elems?.length ?? 0;
321
- if (count === 0) {
322
- this.pushInstruction({ op: "i32.const", value: 0 });
323
- this.pushInstruction({
324
- op: "array.new_default",
325
- typeIndex: sliceInfo.arrInfo.typeIndex,
326
- });
327
- this.pushInstruction({ op: "i32.const", value: 0 }); // offset
328
- this.pushInstruction({ op: "i32.const", value: 0 }); // len
329
- this.pushInstruction({ op: "i32.const", value: 0 }); // cap
330
- this.pushInstruction({
331
- op: "struct.new",
332
- typeIndex: sliceInfo.typeIndex,
333
- });
334
- return;
335
- }
336
- for (const elem of lit.elems) {
337
- this.emitExpr(elem, sliceInfo.elemWType);
338
- this._emitCopyIfValueStruct(elem, elem._type, sliceInfo.elemWType);
339
- }
340
- this.pushInstruction({
341
- op: "array.new_fixed",
342
- typeIndex: sliceInfo.arrInfo.typeIndex,
343
- size: count,
344
- });
483
+ const elems = lit.elems ?? [];
484
+ const count = compositeLitMaxIndex(elems) + 1;
485
+ this._emitArrayLitElems(elems, elemType, sliceInfo.arrInfo, count);
345
486
  this.pushInstruction({ op: "i32.const", value: 0 }); // offset
346
487
  this.pushInstruction({ op: "i32.const", value: count }); // len
347
488
  this.pushInstruction({ op: "i32.const", value: count }); // cap
@@ -353,22 +494,12 @@ export class ExprsEmitter {
353
494
  }
354
495
 
355
496
  if (isArrayType(litType, this.mod.checker)) {
356
- const elemType = litType.elem;
497
+ const elemType = this._getArrayElemType(litType);
357
498
  const arrInfo = this.mod.getArrayType(elemType);
358
- const count = lit.elems?.length ?? 0;
499
+ const elems = lit.elems ?? [];
500
+ const count = compositeLitMaxIndex(elems) + 1;
359
501
  const targetSize = this._getArraySize(litType, count);
360
- for (const elem of lit.elems ?? []) {
361
- this.emitExpr(elem, arrInfo.elemWType);
362
- this._emitCopyIfValueStruct(elem, elem._type, arrInfo.elemWType);
363
- }
364
- for (let i = count; i < targetSize; i++) {
365
- this.emitZeroValue(elemType, arrInfo.elemWType);
366
- }
367
- this.pushInstruction({
368
- op: "array.new_fixed",
369
- typeIndex: arrInfo.typeIndex,
370
- size: targetSize,
371
- });
502
+ this._emitArrayLitElems(elems, elemType, arrInfo, targetSize);
372
503
  return;
373
504
  }
374
505
 
@@ -398,13 +529,13 @@ export class ExprsEmitter {
398
529
  const f = fields[i];
399
530
  if (isKeyed) {
400
531
  if (map.has(f.name)) {
401
- this.emitExpr(map.get(f.name), f.wType);
532
+ this._emitFieldInit(map.get(f.name), f);
402
533
  } else {
403
534
  this.emitZeroValue(f.goType, f.wType);
404
535
  }
405
536
  } else {
406
537
  if (i < (lit.elems?.length ?? 0)) {
407
- this.emitExpr(lit.elems[i], f.wType);
538
+ this._emitFieldInit(lit.elems[i], f);
408
539
  } else {
409
540
  this.emitZeroValue(f.goType, f.wType);
410
541
  }
@@ -417,9 +548,121 @@ export class ExprsEmitter {
417
548
  });
418
549
  }
419
550
 
551
+ // Pushes a new array of `size` elements built from literal `elems`.
552
+ // Keyed elements (`{2: 7}`) go to their index; every other slot is the
553
+ // element zero value. With ascending keys and at most
554
+ // MAX_NEW_FIXED elements this is one `array.new_fixed`; otherwise the
555
+ // array is allocated first and the elements are stored in source order
556
+ // (Go evaluates them left to right whatever their keys).
557
+ _emitArrayLitElems(elems, elemType, arrInfo, size) {
558
+ const indices = compositeLitIndices(elems);
559
+ const ascending = indices.every((v, i) => i === 0 || v > indices[i - 1]);
560
+ const value = (e) => (e.kind === "KeyValueExpr" ? e.value : e);
561
+ const emitElem = (e) => {
562
+ this.emitExpr(e, arrInfo.elemWType);
563
+ // Copy by the element type: a struct stored in an `any` element
564
+ // is already boxed, and a keyed element wraps its value.
565
+ this._emitCopyIfValue(e, elemType, arrInfo.elemWType);
566
+ };
567
+ if (ascending && size <= MAX_NEW_FIXED) {
568
+ const slots = new Array(size).fill(null);
569
+ elems.forEach((e, i) => {
570
+ slots[indices[i]] = value(e);
571
+ });
572
+ for (const e of slots) {
573
+ if (e) emitElem(e);
574
+ else this.emitZeroValue(elemType, arrInfo.elemWType);
575
+ }
576
+ this.pushInstruction({
577
+ op: "array.new_fixed",
578
+ typeIndex: arrInfo.typeIndex,
579
+ size,
580
+ });
581
+ return;
582
+ }
583
+ const wType = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
584
+ const arr = this.acquireTemp(wType);
585
+ const set = (idx, emit) => {
586
+ this.pushInstruction({ op: "local.get", index: arr });
587
+ this.pushInstruction({ op: "i32.const", value: idx });
588
+ emit();
589
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
590
+ };
591
+ this.pushInstruction({ op: "i32.const", value: size });
592
+ this.pushInstruction({
593
+ op: "array.new_default",
594
+ typeIndex: arrInfo.typeIndex,
595
+ });
596
+ this.pushInstruction({ op: "local.set", index: arr });
597
+ // Numeric zero values are the array default; others are stored.
598
+ if (typeof arrInfo.elemWType !== "string") {
599
+ const given = new Set(indices);
600
+ for (let i = 0; i < size; i++)
601
+ if (!given.has(i))
602
+ set(i, () => this.emitZeroValue(elemType, arrInfo.elemWType));
603
+ }
604
+ elems.forEach((e, i) => {
605
+ set(indices[i], () => emitElem(value(e)));
606
+ });
607
+ this.pushInstruction({ op: "local.get", index: arr });
608
+ this.releaseTemp(arr, wType);
609
+ }
610
+
611
+ // A struct literal field initialiser; array values are copied in.
612
+ _emitFieldInit(value, f) {
613
+ this.emitExpr(value, f.wType);
614
+ if (f.goType?.kind !== "pointer" && isArrayType(f.goType, this.mod.checker))
615
+ this._emitCopyIfValue(value, f.goType, f.wType);
616
+ }
617
+
618
+ // With a pointer on the stack, branches to the function's `nil` block
619
+ // (_openNilBlock) when it is null. Outside one, the access traps.
620
+ _emitNilCheck(goType) {
621
+ if (goType?.kind !== "pointer") return;
622
+ this._emitNilBranch();
623
+ }
624
+
625
+ _emitNilBranch() {
626
+ const depth = this.resolveBranchDepthToRole("nil");
627
+ if (depth == null) return;
628
+ this.pushInstruction({ op: "br_on_null", depth });
629
+ }
630
+
631
+ // Emits the struct (or struct pointer) a field access or store goes
632
+ // through, nil-checked. A `*p` base is the pointee itself, not the copy a
633
+ // value read of `*p` makes, so a store through it lands in the pointee.
634
+ // Every selector read and every field store/op-assign/inc-dec starts here.
635
+ emitSelectorBase(node, wType = null, goType = node._type) {
636
+ const pointer = this._structDerefPointer(node);
637
+ if (pointer) {
638
+ this.emitSelectorBase(pointer);
639
+ return;
640
+ }
641
+ this.emitExpr(node, wType ?? this.toWasmType(goType));
642
+ this._emitNilCheck(goType);
643
+ }
644
+
645
+ // For `*p` (possibly parenthesised) with `p` pointing at a struct, the
646
+ // pointer `p`, which is the struct's own reference; otherwise null.
647
+ _structDerefPointer(node) {
648
+ let base = node;
649
+ while (base?.kind === "ParenExpr") base = base.expr;
650
+ if (
651
+ (base?.kind !== "UnaryExpr" || base.op !== "*") &&
652
+ base?.kind !== "StarExpr"
653
+ )
654
+ return null;
655
+ const operand = base.operand ?? base.expr;
656
+ return this._derefStructInfo(operand) ? operand : null;
657
+ }
658
+
420
659
  emitSelectorExpr(expr) {
421
660
  const structInfo = this._resolveStructInfo(expr.expr);
422
661
  if (!structInfo) {
662
+ if (expr.expr?.kind === "Ident" && expr.expr._type?.kind === "namespace")
663
+ throw new Error(
664
+ `${expr.expr.name}.${expr.field} cannot be used as a value in WASM`,
665
+ );
423
666
  throw new Error(`Cannot resolve struct for selector: ${expr.field}`);
424
667
  }
425
668
  const path = this._resolveFieldPath(structInfo, expr.field);
@@ -429,8 +672,7 @@ export class ExprsEmitter {
429
672
  );
430
673
  }
431
674
 
432
- const baseWType = this.toWasmType(expr.expr._type);
433
- this.emitExpr(expr.expr, baseWType);
675
+ this.emitSelectorBase(expr.expr);
434
676
 
435
677
  for (let i = 0; i < path.length; i++) {
436
678
  const step = path[i];
@@ -506,11 +748,9 @@ export class ExprsEmitter {
506
748
  );
507
749
  }
508
750
 
509
- const elemGoType = isSlice
510
- ? baseType.elem
511
- : isPtrToArr
512
- ? baseType.base.elem
513
- : baseType.elem;
751
+ const elemGoType = this._getArrayElemType(
752
+ isPtrToArr ? baseType.base : baseType,
753
+ );
514
754
  const arrInfo = this.mod.getArrayType(elemGoType);
515
755
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
516
756
 
@@ -604,11 +844,9 @@ export class ExprsEmitter {
604
844
  );
605
845
  }
606
846
 
607
- const elemGoType = isSlice
608
- ? baseType.elem
609
- : isPtrToArr
610
- ? baseType.base.elem
611
- : baseType.elem;
847
+ const elemGoType = this._getArrayElemType(
848
+ isPtrToArr ? baseType.base : baseType,
849
+ );
612
850
  const arrInfo = this.mod.getArrayType(elemGoType);
613
851
  const sliceInfo = this.mod.getSliceType(elemGoType);
614
852
 
@@ -757,8 +995,11 @@ export class ExprsEmitter {
757
995
  const isValStruct =
758
996
  isStructType(elemGoType, this.mod.checker, this.mod) &&
759
997
  !isPointerToStruct(elemGoType, this.mod.checker, this.mod);
998
+ const isValArray =
999
+ elemGoType?.kind !== "pointer" &&
1000
+ isArrayType(elemGoType, this.mod.checker);
760
1001
 
761
- if (!isValStruct) {
1002
+ if (!isValStruct && !isValArray) {
762
1003
  this.pushInstruction({
763
1004
  op: "array.copy",
764
1005
  typeIndexDst: arrInfo.typeIndex,
@@ -831,7 +1072,9 @@ export class ExprsEmitter {
831
1072
  this.pushInstruction("i32.add");
832
1073
  this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
833
1074
 
834
- if (sInfo) {
1075
+ if (isValArray) {
1076
+ this.emitCopyArray(elemGoType);
1077
+ } else if (sInfo) {
835
1078
  this.emitCloneStruct(sInfo, arrInfo.elemWType);
836
1079
  }
837
1080
 
@@ -869,7 +1112,9 @@ export class ExprsEmitter {
869
1112
  this.pushInstruction("i32.add");
870
1113
  this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
871
1114
 
872
- if (sInfo) {
1115
+ if (isValArray) {
1116
+ this.emitCopyArray(elemGoType);
1117
+ } else if (sInfo) {
873
1118
  this.emitCloneStruct(sInfo, arrInfo.elemWType);
874
1119
  }
875
1120
 
@@ -942,53 +1187,95 @@ export class ExprsEmitter {
942
1187
  return this.toWasmType(expr._type) === "i32";
943
1188
  }
944
1189
 
1190
+ // Panics with the generic out-of-range message (shared buffers, SIMD
1191
+ // loads) when the condition on the stack is true.
945
1192
  _emitBoundsBranch() {
946
- const oobDepth = this.resolveBranchDepthToOob();
947
- if (oobDepth !== null) {
948
- this.pushInstruction({ op: "br_if", depth: oobDepth });
949
- } else {
950
- this.pushInstruction({ op: "if", blockType: "void" });
951
- this.pushInstruction({
952
- op: "call",
953
- funcIndex: this.mod.getBoundsPanicFuncIndex(),
954
- });
955
- this.pushInstruction("unreachable");
956
- this.pushInstruction("end");
957
- }
1193
+ this.pushInstruction({ op: "if", blockType: "void" });
1194
+ this.pushInstruction({
1195
+ op: "call",
1196
+ funcIndex: this.mod.getBoundsPanicFuncIndex(),
1197
+ });
1198
+ this.pushInstruction("unreachable");
1199
+ this.pushInstruction("end");
1200
+ }
1201
+
1202
+ // If the condition on the stack is true, panics with Go's index message;
1203
+ // `pushIdx64` and `emitLen` push the i64 index and the i32 length.
1204
+ _emitIndexPanicIf(pushIdx64, emitLen) {
1205
+ this.pushInstruction({ op: "if", blockType: "void" });
1206
+ pushIdx64();
1207
+ emitLen();
1208
+ this.pushInstruction({
1209
+ op: "call",
1210
+ funcIndex: this.mod.getIndexPanicFuncIndex(),
1211
+ });
1212
+ this.pushInstruction("unreachable");
1213
+ this.pushInstruction("end");
1214
+ }
1215
+
1216
+ // Registers an index-check site and pushes its id: a failing check
1217
+ // carries the index and this id to the handler, which re-emits `emitLen`
1218
+ // for that site (its temps still hold the operand at the branch).
1219
+ _pushOobSite(emitLen) {
1220
+ this.oobSites ??= [];
1221
+ this.oobSites.push(emitLen);
1222
+ this.pushInstruction({ op: "i32.const", value: this.oobSites.length - 1 });
958
1223
  }
959
1224
 
960
1225
  // Pushes the bounds-checked i32 index onto the stack. `emitLen` must push
961
- // the i32 length. Constant and plain-local indexes are re-emitted instead
962
- // of spilled; everything else goes through one temp.
1226
+ // the i32 length. Inside a function body a failing check branches to the
1227
+ // shared handler (`_closeOobBlocks`) with the index and a site id, and the
1228
+ // handler reports Go's message; elsewhere the check panics inline.
963
1229
  _emitCheckedIndex(indexNode, emitLen) {
964
- const simple =
965
- (indexNode.kind === "BasicLit" && indexNode.litKind === "INT") ||
966
- (indexNode.kind === "Ident" && this.resolveLocal(indexNode.name));
1230
+ const depth32 = this.resolveBranchDepthToOob(32);
967
1231
  if (this._isI32IndexExpr(indexNode)) {
1232
+ const idx = this.acquireTemp("i32");
968
1233
  this.emitExpr(indexNode, "i32");
969
- let tmp = null;
970
- if (simple) {
971
- this.emitExpr(indexNode, "i32");
1234
+ this.pushInstruction({ op: "local.tee", index: idx });
1235
+ if (depth32 !== null) {
1236
+ this._pushOobSite(emitLen);
1237
+ this.pushInstruction({ op: "local.get", index: idx });
1238
+ emitLen();
1239
+ this.pushInstruction("i32.ge_u");
1240
+ this.pushInstruction({ op: "br_if", depth: depth32 });
1241
+ this.pushInstruction("drop");
972
1242
  } else {
973
- tmp = this.acquireTemp("i32");
974
- this.pushInstruction({ op: "local.tee", index: tmp });
975
- this.pushInstruction({ op: "local.get", index: tmp });
1243
+ this.pushInstruction({ op: "local.get", index: idx });
1244
+ emitLen();
1245
+ this.pushInstruction("i32.ge_u");
1246
+ this._emitIndexPanicIf(() => {
1247
+ this.pushInstruction({ op: "local.get", index: idx });
1248
+ this.pushInstruction("i64.extend_i32_s");
1249
+ }, emitLen);
976
1250
  }
977
- emitLen();
978
- this.pushInstruction("i32.ge_u");
979
- this._emitBoundsBranch();
980
- if (tmp !== null) this.releaseTemp(tmp, "i32");
1251
+ this.releaseTemp(idx, "i32");
981
1252
  return;
982
1253
  }
983
1254
 
984
1255
  const idx64 = this.acquireTemp("i64");
985
1256
  this.emitExpr(indexNode, "i64");
986
1257
  this.pushInstruction({ op: "local.tee", index: idx64 });
987
- emitLen();
988
- this.pushInstruction("i64.extend_i32_u");
989
- this.pushInstruction("i64.ge_u");
990
- this._emitBoundsBranch();
991
- this.pushInstruction({ op: "local.get", index: idx64 });
1258
+ if (depth32 !== null) {
1259
+ this._pushOobSite(emitLen);
1260
+ this.pushInstruction({ op: "local.get", index: idx64 });
1261
+ emitLen();
1262
+ this.pushInstruction("i64.extend_i32_u");
1263
+ this.pushInstruction("i64.ge_u");
1264
+ this.pushInstruction({
1265
+ op: "br_if",
1266
+ depth: this.resolveBranchDepthToOob(64),
1267
+ });
1268
+ this.pushInstruction("drop");
1269
+ } else {
1270
+ emitLen();
1271
+ this.pushInstruction("i64.extend_i32_u");
1272
+ this.pushInstruction("i64.ge_u");
1273
+ this._emitIndexPanicIf(
1274
+ () => this.pushInstruction({ op: "local.get", index: idx64 }),
1275
+ emitLen,
1276
+ );
1277
+ this.pushInstruction({ op: "local.get", index: idx64 });
1278
+ }
992
1279
  this.pushInstruction("i32.wrap_i64");
993
1280
  this.releaseTemp(idx64, "i64");
994
1281
  }
@@ -1055,12 +1342,14 @@ export class ExprsEmitter {
1055
1342
  constIdx < arrayLen
1056
1343
  ) {
1057
1344
  this.emitExpr(baseNode, arrWType);
1345
+ this._emitNilCheck(rawBaseType);
1058
1346
  this.pushInstruction({ op: "i32.const", value: constIdx });
1059
1347
  return;
1060
1348
  }
1061
1349
 
1062
1350
  const arrTmp = this.acquireTemp(arrWType);
1063
1351
  this.emitExpr(baseNode, arrWType);
1352
+ this._emitNilCheck(rawBaseType);
1064
1353
  this.pushInstruction({ op: "local.tee", index: arrTmp });
1065
1354
  this._emitCheckedIndex(indexNode, () => {
1066
1355
  this.pushInstruction({ op: "local.get", index: arrTmp });
@@ -1086,7 +1375,92 @@ export class ExprsEmitter {
1086
1375
  }
1087
1376
  }
1088
1377
 
1378
+ // Integer conversions of constants (`int64(1e18 + 1)`) keep the exact value.
1379
+ _emitFoldedIntConversion(expr, targetWasmType) {
1380
+ if (expr.kind !== "TypeConversion" && expr.kind !== "CallExpr")
1381
+ return false;
1382
+ if (expr._const?.k !== "INT") return false;
1383
+ if (
1384
+ targetWasmType != null &&
1385
+ targetWasmType !== "i32" &&
1386
+ targetWasmType !== "i64"
1387
+ )
1388
+ return false;
1389
+ const lit = constToLit(expr._const);
1390
+ lit._type = expr._type;
1391
+ this.emitBasicLit(lit, targetWasmType);
1392
+ return true;
1393
+ }
1394
+
1395
+ // A float literal; an integral one in an integer context (`var y int = 2.0`)
1396
+ // emits its exact integer value.
1397
+ _emitFloatLit(lit, wType) {
1398
+ const i =
1399
+ wType === "i32" || wType === "i64"
1400
+ ? constIntValue(constFromLit(lit) ?? {})
1401
+ : null;
1402
+ if (i !== null) {
1403
+ this.pushInstruction(
1404
+ wType === "i32"
1405
+ ? { op: "i32.const", value: Number(BigInt.asIntN(32, i)) }
1406
+ : { op: "i64.const", value: BigInt.asIntN(64, i) },
1407
+ );
1408
+ return;
1409
+ }
1410
+ const n = Number(lit.value);
1411
+ if (wType === "f32") {
1412
+ this.pushInstruction({ op: "f32.const", value: n });
1413
+ } else {
1414
+ this.pushInstruction({ op: "f64.const", value: n });
1415
+ }
1416
+ }
1417
+
1418
+ // "complex64" | "complex128" when `w` is the struct type of a complex value.
1419
+ _complexKindOfWType(w) {
1420
+ if (typeof w?.typeIndex !== "number") return null;
1421
+ for (const [kind, info] of this.mod.complexTypes) {
1422
+ if (info.typeIndex === w.typeIndex) return kind;
1423
+ }
1424
+ return null;
1425
+ }
1426
+
1427
+ // True when the target wasm type is the `$complex64` struct, whether it is
1428
+ // named or only carries the struct's type index.
1429
+ _isComplex64Target(w) {
1430
+ return (
1431
+ w?.typeName === "$complex64" ||
1432
+ this._complexKindOfWType(w) === "complex64"
1433
+ );
1434
+ }
1435
+
1089
1436
  emitBasicLit(lit, targetWasmType) {
1437
+ const targetComplex = this._complexKindOfWType(targetWasmType);
1438
+ const isTargetComplex64 =
1439
+ lit._type?.name === "complex64" ||
1440
+ targetWasmType?.typeName === "$complex64" ||
1441
+ targetComplex === "complex64";
1442
+ const isTargetComplex128 =
1443
+ lit._type?.name === "complex128" ||
1444
+ lit._type?.base === "complex128" ||
1445
+ targetWasmType?.typeName === "$complex128" ||
1446
+ targetComplex === "complex128";
1447
+ if (isTargetComplex64 || isTargetComplex128) {
1448
+ const info = this.mod.getComplexType(
1449
+ isTargetComplex64 ? "complex64" : "complex128",
1450
+ );
1451
+ const s = String(lit.value);
1452
+ const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
1453
+ const n = Number(clean);
1454
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: n });
1455
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
1456
+ this.pushInstruction({
1457
+ op: "struct.new",
1458
+ typeIndex: info.typeIndex,
1459
+ typeName: info.name,
1460
+ });
1461
+ return;
1462
+ }
1463
+
1090
1464
  const wType =
1091
1465
  targetWasmType ??
1092
1466
  toWasmType(lit._type, this.mod.checker) ??
@@ -1108,12 +1482,7 @@ export class ExprsEmitter {
1108
1482
  }
1109
1483
 
1110
1484
  if (lit.litKind === "FLOAT") {
1111
- const n = Number(lit.value);
1112
- if (wType === "f32") {
1113
- this.pushInstruction({ op: "f32.const", value: n });
1114
- } else {
1115
- this.pushInstruction({ op: "f64.const", value: n });
1116
- }
1485
+ this._emitFloatLit(lit, wType);
1117
1486
  return;
1118
1487
  }
1119
1488
 
@@ -1141,6 +1510,22 @@ export class ExprsEmitter {
1141
1510
  }
1142
1511
  }
1143
1512
 
1513
+ emitImagLit(lit, targetWasmType) {
1514
+ const is64 =
1515
+ lit._type?.name === "complex64" ||
1516
+ this._isComplex64Target(targetWasmType);
1517
+ const complexName = is64 ? "complex64" : "complex128";
1518
+ const info = this.mod.getComplexType(complexName);
1519
+ const imVal = Number(lit.value);
1520
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
1521
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: imVal });
1522
+ this.pushInstruction({
1523
+ op: "struct.new",
1524
+ typeIndex: info.typeIndex,
1525
+ typeName: info.name,
1526
+ });
1527
+ }
1528
+
1144
1529
  _emitNilValue(goType, targetWasmType) {
1145
1530
  const effectiveType = targetWasmType ?? this.toWasmType(goType);
1146
1531
  if (
@@ -1264,6 +1649,11 @@ export class ExprsEmitter {
1264
1649
  emitUnaryExpr(expr, targetWasmType = null) {
1265
1650
  const { op, operand } = expr;
1266
1651
 
1652
+ if (isComplex(expr._type) || isComplex(operand._type)) {
1653
+ this._emitComplexUnary(expr, op, operand, targetWasmType);
1654
+ return;
1655
+ }
1656
+
1267
1657
  // Optimize negative numeric literals directly
1268
1658
  if (op === "-" && operand.kind === "BasicLit") {
1269
1659
  if (operand.litKind === "INT" || operand.litKind === "FLOAT") {
@@ -1321,91 +1711,470 @@ export class ExprsEmitter {
1321
1711
  }
1322
1712
  break;
1323
1713
 
1324
- case "&": {
1325
- if (operand.kind === "Ident") {
1326
- const loc = this.resolveLocal(operand.name);
1327
- if (loc?.isBoxed) {
1328
- this.pushInstruction({ op: "local.get", index: loc.index });
1329
- return;
1330
- }
1331
- }
1332
- if (operand.kind === "CompositeLit") {
1333
- this.emitExpr(operand);
1334
- return;
1335
- }
1336
- const structInfo = this._resolveStructInfo(operand);
1337
- if (structInfo) {
1338
- this.emitExpr(operand);
1339
- return;
1340
- }
1341
- const opWType = this.toWasmType(operand._type);
1342
- const box = this.mod.getBoxType(operand._type);
1343
- this.emitExpr(operand, opWType);
1344
- this.pushInstruction({ op: "struct.new", typeIndex: box.typeIndex });
1714
+ case "&":
1715
+ return this._emitAddressOf(operand);
1716
+
1717
+ case "*":
1718
+ return this._emitDerefExpr(operand);
1719
+
1720
+ default:
1721
+ throw new Error(`Unsupported unary operator: ${op}`);
1722
+ }
1723
+ }
1724
+
1725
+ _emitComplexUnary(expr, op, operand, targetWasmType) {
1726
+ if (op === "+") {
1727
+ this.emitExpr(operand, targetWasmType);
1728
+ return;
1729
+ }
1730
+ if (op === "-") {
1731
+ if (operand.kind === "ImagLit") {
1732
+ this.emitImagLit(
1733
+ { ...operand, value: `-${operand.value}` },
1734
+ targetWasmType,
1735
+ );
1345
1736
  return;
1346
1737
  }
1738
+ const is64 =
1739
+ isComplex64(expr._type) ||
1740
+ isComplex64(operand._type) ||
1741
+ this._isComplex64Target(targetWasmType);
1742
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
1743
+ const cWType = {
1744
+ kind: "ref",
1745
+ nullable: true,
1746
+ typeIndex: info.typeIndex,
1747
+ typeName: info.name,
1748
+ };
1749
+ const opTmp = this.acquireTemp(cWType);
1750
+ this.emitExpr(operand, cWType);
1751
+ this.pushInstruction({ op: "local.set", index: opTmp });
1347
1752
 
1348
- case "*": {
1349
- const structInfo = this._resolveStructInfo(operand);
1350
- if (structInfo) {
1351
- const baseWType = this.toWasmType(operand._type);
1352
- this.emitExpr(operand, baseWType);
1353
- this.emitCloneStruct(structInfo, baseWType);
1354
- return;
1355
- }
1356
- const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
1357
- const baseWType = {
1358
- kind: "ref",
1359
- nullable: true,
1360
- typeIndex: box.typeIndex,
1361
- };
1362
- this.emitExpr(operand, baseWType);
1753
+ for (let i = 0; i < 2; i++) {
1754
+ this.pushInstruction({ op: "local.get", index: opTmp });
1363
1755
  this.pushInstruction({
1364
1756
  op: "struct.get",
1365
- typeIndex: box.typeIndex,
1366
- fieldIndex: 0,
1757
+ typeIndex: info.typeIndex,
1758
+ typeName: info.name,
1759
+ fieldIndex: i,
1367
1760
  });
1368
- return;
1761
+ this.pushInstruction(`${info.elemWType}.neg`);
1369
1762
  }
1370
1763
 
1371
- default:
1372
- throw new Error(`Unsupported unary operator: ${op}`);
1764
+ this.pushInstruction({
1765
+ op: "struct.new",
1766
+ typeIndex: info.typeIndex,
1767
+ typeName: info.name,
1768
+ });
1769
+ this.releaseTemp(opTmp, cWType);
1373
1770
  }
1374
1771
  }
1375
1772
 
1376
- emitBinaryExpr(expr, targetWasmType = null) {
1377
- const { op, left, right } = expr;
1378
-
1379
- // Short-circuiting logical operators
1380
- if (op === "&&") {
1381
- this.emitExpr(left, "i32");
1382
- this.pushInstruction({ op: "if", blockType: "i32" });
1383
- this.emitExpr(right, "i32");
1384
- this.pushInstruction("else");
1385
- this.pushInstruction({ op: "i32.const", value: 0 });
1386
- this.pushInstruction("end");
1773
+ _emitAddressOf(operand) {
1774
+ if (operand.kind === "Ident") {
1775
+ const loc = this.resolveLocal(operand.name);
1776
+ if (loc?.isBoxed) {
1777
+ this.pushInstruction({ op: "local.get", index: loc.index });
1778
+ // A pointer to an array or struct is its reference itself.
1779
+ const t = loc.goType;
1780
+ if (
1781
+ this._isValueArray(t) ||
1782
+ (t?.kind !== "pointer" &&
1783
+ isStructType(t, this.mod.checker, this.mod) &&
1784
+ !isPointerToStruct(t, this.mod.checker, this.mod))
1785
+ )
1786
+ this.pushInstruction({
1787
+ op: "struct.get",
1788
+ typeIndex: loc.boxInfo.typeIndex,
1789
+ fieldIndex: 0,
1790
+ });
1791
+ return;
1792
+ }
1793
+ }
1794
+ if (operand.kind === "CompositeLit") {
1795
+ if (isStructType(operand._type, this.mod.checker, this.mod)) {
1796
+ this.emitExpr(operand);
1797
+ return;
1798
+ }
1799
+ }
1800
+ const structInfo = this._resolveStructInfo(operand);
1801
+ if (structInfo) {
1802
+ this.emitExpr(operand);
1387
1803
  return;
1388
1804
  }
1389
-
1390
- if (op === "||") {
1391
- this.emitExpr(left, "i32");
1392
- this.pushInstruction({ op: "if", blockType: "i32" });
1393
- this.pushInstruction({ op: "i32.const", value: 1 });
1394
- this.pushInstruction("else");
1395
- this.emitExpr(right, "i32");
1396
- this.pushInstruction("end");
1805
+ const opWType = this.toWasmType(operand._type);
1806
+ if (!isScalarType(operand._type, this.mod.checker, this.mod)) {
1807
+ this.emitExpr(operand, opWType);
1397
1808
  return;
1398
1809
  }
1810
+ const box = this.mod.getBoxType(operand._type);
1811
+ this.emitExpr(operand, opWType);
1812
+ this.pushInstruction({
1813
+ op: "struct.new",
1814
+ typeIndex: box.typeIndex,
1815
+ typeName: box.name,
1816
+ });
1817
+ }
1399
1818
 
1400
- // Division & Modulo with zero-guard and overflow-guard
1401
- if (op === "/" || op === "%") {
1402
- this.emitDivRem(op, left, right, targetWasmType);
1819
+ _isPointerGoType(t) {
1820
+ let x = t;
1821
+ if (x?.kind === "named" && x.underlying) x = x.underlying;
1822
+ return x?.kind === "pointer" || x?.kind === "PointerType";
1823
+ }
1824
+
1825
+ _ptrBaseType(t) {
1826
+ let x = t;
1827
+ if (x?.kind === "named" && x.underlying) x = x.underlying;
1828
+ return x?.kind === "pointer" ? x.base : null;
1829
+ }
1830
+
1831
+ // The struct a `*operand` reads or writes, or null when the operand points
1832
+ // at something else: `*pp` with `pp **T` loads a pointer, not a struct.
1833
+ _derefStructInfo(operand) {
1834
+ if (this._isPointerGoType(this._ptrBaseType(operand._type))) return null;
1835
+ return this._resolveStructInfo(operand);
1836
+ }
1837
+
1838
+ _emitDerefExpr(operand) {
1839
+ const structInfo = this._derefStructInfo(operand);
1840
+ if (structInfo) {
1841
+ const baseWType = this.toWasmType(operand._type);
1842
+ this.emitExpr(operand, baseWType);
1843
+ this._emitNilCheck(operand._type);
1844
+ this.emitCloneStruct(structInfo, baseWType);
1403
1845
  return;
1404
1846
  }
1405
-
1406
- // Shifts with Go semantics
1407
- if (op === "<<" || op === ">>") {
1408
- this.emitShift(op, left, right, targetWasmType);
1847
+ const operandGoType =
1848
+ operand._type ??
1849
+ (operand.kind === "Ident"
1850
+ ? this.resolveLocal(operand.name)?.goType
1851
+ : null) ??
1852
+ this._resolveExprGoType(operand);
1853
+ const baseGoType = operandGoType?.base ?? operandGoType;
1854
+ if (!isScalarType(baseGoType, this.mod.checker, this.mod)) {
1855
+ const baseWType = this.toWasmType(baseGoType);
1856
+ this.emitExpr(operand, baseWType);
1857
+ this._emitNilCheck(operandGoType);
1858
+ return;
1859
+ }
1860
+ const box = this.mod.getBoxType(baseGoType);
1861
+ const ptr = this.mod.getPtrType(baseGoType);
1862
+ const ptrWType = {
1863
+ kind: "ref",
1864
+ nullable: true,
1865
+ typeIndex: ptr.typeIndex,
1866
+ typeName: ptr.name,
1867
+ };
1868
+ this.emitExpr(operand, ptrWType);
1869
+ this._emitNilCheck(operandGoType);
1870
+ this.pushInstruction({
1871
+ op: "ref.cast",
1872
+ typeIndex: box.typeIndex,
1873
+ typeName: box.name,
1874
+ });
1875
+ this.pushInstruction({
1876
+ op: "struct.get",
1877
+ typeIndex: box.typeIndex,
1878
+ typeName: box.name,
1879
+ fieldIndex: 0,
1880
+ });
1881
+ }
1882
+
1883
+ _emitComplexEq(aTmp, bTmp, info, f, op) {
1884
+ for (let i = 0; i < 2; i++) {
1885
+ this.pushInstruction({ op: "local.get", index: aTmp });
1886
+ this.pushInstruction({
1887
+ op: "struct.get",
1888
+ typeIndex: info.typeIndex,
1889
+ typeName: info.name,
1890
+ fieldIndex: i,
1891
+ });
1892
+ this.pushInstruction({ op: "local.get", index: bTmp });
1893
+ this.pushInstruction({
1894
+ op: "struct.get",
1895
+ typeIndex: info.typeIndex,
1896
+ typeName: info.name,
1897
+ fieldIndex: i,
1898
+ });
1899
+ this.pushInstruction(`${f}.eq`);
1900
+ }
1901
+ this.pushInstruction("i32.and");
1902
+ if (op === "!=") this.pushInstruction("i32.eqz");
1903
+ }
1904
+
1905
+ _emitComplexAddSub(aTmp, bTmp, info, f, op) {
1906
+ const wasmOp = op === "+" ? `${f}.add` : `${f}.sub`;
1907
+ for (let i = 0; i < 2; i++) {
1908
+ this.pushInstruction({ op: "local.get", index: aTmp });
1909
+ this.pushInstruction({
1910
+ op: "struct.get",
1911
+ typeIndex: info.typeIndex,
1912
+ typeName: info.name,
1913
+ fieldIndex: i,
1914
+ });
1915
+ this.pushInstruction({ op: "local.get", index: bTmp });
1916
+ this.pushInstruction({
1917
+ op: "struct.get",
1918
+ typeIndex: info.typeIndex,
1919
+ typeName: info.name,
1920
+ fieldIndex: i,
1921
+ });
1922
+ this.pushInstruction(wasmOp);
1923
+ }
1924
+ this.pushInstruction({
1925
+ op: "struct.new",
1926
+ typeIndex: info.typeIndex,
1927
+ typeName: info.name,
1928
+ });
1929
+ }
1930
+
1931
+ _emitComplexMul(aTmp, bTmp, info, f) {
1932
+ this.pushInstruction({ op: "local.get", index: aTmp });
1933
+ this.pushInstruction({
1934
+ op: "struct.get",
1935
+ typeIndex: info.typeIndex,
1936
+ typeName: info.name,
1937
+ fieldIndex: 0,
1938
+ });
1939
+ this.pushInstruction({ op: "local.get", index: bTmp });
1940
+ this.pushInstruction({
1941
+ op: "struct.get",
1942
+ typeIndex: info.typeIndex,
1943
+ typeName: info.name,
1944
+ fieldIndex: 0,
1945
+ });
1946
+ this.pushInstruction(`${f}.mul`);
1947
+ this.pushInstruction({ op: "local.get", index: aTmp });
1948
+ this.pushInstruction({
1949
+ op: "struct.get",
1950
+ typeIndex: info.typeIndex,
1951
+ typeName: info.name,
1952
+ fieldIndex: 1,
1953
+ });
1954
+ this.pushInstruction({ op: "local.get", index: bTmp });
1955
+ this.pushInstruction({
1956
+ op: "struct.get",
1957
+ typeIndex: info.typeIndex,
1958
+ typeName: info.name,
1959
+ fieldIndex: 1,
1960
+ });
1961
+ this.pushInstruction(`${f}.mul`);
1962
+ this.pushInstruction(`${f}.sub`);
1963
+
1964
+ this.pushInstruction({ op: "local.get", index: aTmp });
1965
+ this.pushInstruction({
1966
+ op: "struct.get",
1967
+ typeIndex: info.typeIndex,
1968
+ typeName: info.name,
1969
+ fieldIndex: 0,
1970
+ });
1971
+ this.pushInstruction({ op: "local.get", index: bTmp });
1972
+ this.pushInstruction({
1973
+ op: "struct.get",
1974
+ typeIndex: info.typeIndex,
1975
+ typeName: info.name,
1976
+ fieldIndex: 1,
1977
+ });
1978
+ this.pushInstruction(`${f}.mul`);
1979
+ this.pushInstruction({ op: "local.get", index: aTmp });
1980
+ this.pushInstruction({
1981
+ op: "struct.get",
1982
+ typeIndex: info.typeIndex,
1983
+ typeName: info.name,
1984
+ fieldIndex: 1,
1985
+ });
1986
+ this.pushInstruction({ op: "local.get", index: bTmp });
1987
+ this.pushInstruction({
1988
+ op: "struct.get",
1989
+ typeIndex: info.typeIndex,
1990
+ typeName: info.name,
1991
+ fieldIndex: 0,
1992
+ });
1993
+ this.pushInstruction(`${f}.mul`);
1994
+ this.pushInstruction(`${f}.add`);
1995
+
1996
+ this.pushInstruction({
1997
+ op: "struct.new",
1998
+ typeIndex: info.typeIndex,
1999
+ typeName: info.name,
2000
+ });
2001
+ }
2002
+
2003
+ _emitComplexDiv(aTmp, bTmp, info, f) {
2004
+ const dTmp = this.acquireTemp(f);
2005
+ for (let i = 0; i < 2; i++) {
2006
+ this.pushInstruction({ op: "local.get", index: bTmp });
2007
+ this.pushInstruction({
2008
+ op: "struct.get",
2009
+ typeIndex: info.typeIndex,
2010
+ typeName: info.name,
2011
+ fieldIndex: i,
2012
+ });
2013
+ this.pushInstruction({ op: "local.get", index: bTmp });
2014
+ this.pushInstruction({
2015
+ op: "struct.get",
2016
+ typeIndex: info.typeIndex,
2017
+ typeName: info.name,
2018
+ fieldIndex: i,
2019
+ });
2020
+ this.pushInstruction(`${f}.mul`);
2021
+ if (i > 0) this.pushInstruction(`${f}.add`);
2022
+ }
2023
+ this.pushInstruction({ op: "local.set", index: dTmp });
2024
+
2025
+ this.pushInstruction({ op: "local.get", index: aTmp });
2026
+ this.pushInstruction({
2027
+ op: "struct.get",
2028
+ typeIndex: info.typeIndex,
2029
+ typeName: info.name,
2030
+ fieldIndex: 0,
2031
+ });
2032
+ this.pushInstruction({ op: "local.get", index: bTmp });
2033
+ this.pushInstruction({
2034
+ op: "struct.get",
2035
+ typeIndex: info.typeIndex,
2036
+ typeName: info.name,
2037
+ fieldIndex: 0,
2038
+ });
2039
+ this.pushInstruction(`${f}.mul`);
2040
+ this.pushInstruction({ op: "local.get", index: aTmp });
2041
+ this.pushInstruction({
2042
+ op: "struct.get",
2043
+ typeIndex: info.typeIndex,
2044
+ typeName: info.name,
2045
+ fieldIndex: 1,
2046
+ });
2047
+ this.pushInstruction({ op: "local.get", index: bTmp });
2048
+ this.pushInstruction({
2049
+ op: "struct.get",
2050
+ typeIndex: info.typeIndex,
2051
+ typeName: info.name,
2052
+ fieldIndex: 1,
2053
+ });
2054
+ this.pushInstruction(`${f}.mul`);
2055
+ this.pushInstruction(`${f}.add`);
2056
+ this.pushInstruction({ op: "local.get", index: dTmp });
2057
+ this.pushInstruction(`${f}.div`);
2058
+
2059
+ this.pushInstruction({ op: "local.get", index: aTmp });
2060
+ this.pushInstruction({
2061
+ op: "struct.get",
2062
+ typeIndex: info.typeIndex,
2063
+ typeName: info.name,
2064
+ fieldIndex: 1,
2065
+ });
2066
+ this.pushInstruction({ op: "local.get", index: bTmp });
2067
+ this.pushInstruction({
2068
+ op: "struct.get",
2069
+ typeIndex: info.typeIndex,
2070
+ typeName: info.name,
2071
+ fieldIndex: 0,
2072
+ });
2073
+ this.pushInstruction(`${f}.mul`);
2074
+ this.pushInstruction({ op: "local.get", index: aTmp });
2075
+ this.pushInstruction({
2076
+ op: "struct.get",
2077
+ typeIndex: info.typeIndex,
2078
+ typeName: info.name,
2079
+ fieldIndex: 0,
2080
+ });
2081
+ this.pushInstruction({ op: "local.get", index: bTmp });
2082
+ this.pushInstruction({
2083
+ op: "struct.get",
2084
+ typeIndex: info.typeIndex,
2085
+ typeName: info.name,
2086
+ fieldIndex: 1,
2087
+ });
2088
+ this.pushInstruction(`${f}.mul`);
2089
+ this.pushInstruction(`${f}.sub`);
2090
+ this.pushInstruction({ op: "local.get", index: dTmp });
2091
+ this.pushInstruction(`${f}.div`);
2092
+
2093
+ this.pushInstruction({
2094
+ op: "struct.new",
2095
+ typeIndex: info.typeIndex,
2096
+ typeName: info.name,
2097
+ });
2098
+ this.releaseTemp(dTmp, f);
2099
+ }
2100
+
2101
+ emitComplexBinary(expr, targetWasmType) {
2102
+ const { op, left, right } = expr;
2103
+ const is64 =
2104
+ isComplex64(expr._type) ||
2105
+ isComplex64(left._type) ||
2106
+ isComplex64(right._type) ||
2107
+ this._isComplex64Target(targetWasmType);
2108
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
2109
+ const f = info.elemWType;
2110
+ const cWType = {
2111
+ kind: "ref",
2112
+ nullable: true,
2113
+ typeIndex: info.typeIndex,
2114
+ typeName: info.name,
2115
+ };
2116
+ const aTmp = this.acquireTemp(cWType);
2117
+ const bTmp = this.acquireTemp(cWType);
2118
+ this.emitExpr(left, cWType);
2119
+ this.pushInstruction({ op: "local.set", index: aTmp });
2120
+ this.emitExpr(right, cWType);
2121
+ this.pushInstruction({ op: "local.set", index: bTmp });
2122
+
2123
+ if (op === "==" || op === "!=") {
2124
+ this._emitComplexEq(aTmp, bTmp, info, f, op);
2125
+ } else if (op === "+" || op === "-") {
2126
+ this._emitComplexAddSub(aTmp, bTmp, info, f, op);
2127
+ } else if (op === "*") {
2128
+ this._emitComplexMul(aTmp, bTmp, info, f);
2129
+ } else if (op === "/") {
2130
+ this._emitComplexDiv(aTmp, bTmp, info, f);
2131
+ }
2132
+ this.releaseTemp(bTmp, cWType);
2133
+ this.releaseTemp(aTmp, cWType);
2134
+ }
2135
+
2136
+ emitBinaryExpr(expr, targetWasmType = null) {
2137
+ const { op, left, right } = expr;
2138
+
2139
+ if (
2140
+ isComplex(expr._type) ||
2141
+ isComplex(left._type) ||
2142
+ isComplex(right._type)
2143
+ ) {
2144
+ this.emitComplexBinary(expr, targetWasmType);
2145
+ return;
2146
+ }
2147
+
2148
+ // Short-circuiting logical operators
2149
+ if (op === "&&") {
2150
+ this.emitExpr(left, "i32");
2151
+ this.pushInstruction({ op: "if", blockType: "i32" });
2152
+ this.emitExpr(right, "i32");
2153
+ this.pushInstruction("else");
2154
+ this.pushInstruction({ op: "i32.const", value: 0 });
2155
+ this.pushInstruction("end");
2156
+ return;
2157
+ }
2158
+
2159
+ if (op === "||") {
2160
+ this.emitExpr(left, "i32");
2161
+ this.pushInstruction({ op: "if", blockType: "i32" });
2162
+ this.pushInstruction({ op: "i32.const", value: 1 });
2163
+ this.pushInstruction("else");
2164
+ this.emitExpr(right, "i32");
2165
+ this.pushInstruction("end");
2166
+ return;
2167
+ }
2168
+
2169
+ // Division & Modulo with zero-guard and overflow-guard
2170
+ if (op === "/" || op === "%") {
2171
+ this.emitDivRem(op, left, right, targetWasmType);
2172
+ return;
2173
+ }
2174
+
2175
+ // Shifts with Go semantics
2176
+ if (op === "<<" || op === ">>") {
2177
+ this.emitShift(op, left, right, targetWasmType);
1409
2178
  return;
1410
2179
  }
1411
2180
 
@@ -1453,10 +2222,17 @@ export class ExprsEmitter {
1453
2222
  return;
1454
2223
  }
1455
2224
 
1456
- const leftType = left._type;
2225
+ // An untyped constant operand takes the type of the typed one
2226
+ // (`'a' < rune(b)`), so both sides share one wasm representation.
2227
+ const leftType =
2228
+ left._type?.kind === "untyped" &&
2229
+ right._type &&
2230
+ right._type.kind !== "untyped"
2231
+ ? right._type
2232
+ : left._type;
1457
2233
  const isLeftI32 = this._isI32IndexExpr(left);
1458
2234
  const isRightI32 = this._isI32IndexExpr(right);
1459
- const leftGoType = left._type ?? this._resolveExprGoType(left);
2235
+ const leftGoType = leftType ?? this._resolveExprGoType(left);
1460
2236
  const isIntType =
1461
2237
  !leftGoType ||
1462
2238
  (leftGoType.kind === "basic" &&
@@ -1497,6 +2273,72 @@ export class ExprsEmitter {
1497
2273
  this.emitBinaryOp(op, wType, leftType);
1498
2274
  }
1499
2275
 
2276
+ _findScalarPtrByWType(wType, map) {
2277
+ if (!map) return null;
2278
+ for (const info of map.values()) {
2279
+ if (info.typeIndex === wType.typeIndex || info.name === wType.typeName) {
2280
+ return info;
2281
+ }
2282
+ }
2283
+ return null;
2284
+ }
2285
+
2286
+ _getScalarPtrFromWType(wType) {
2287
+ if (!wType || typeof wType !== "object") return null;
2288
+ const ptr = this._findScalarPtrByWType(wType, this.mod.ptrTypes);
2289
+ if (ptr) {
2290
+ return {
2291
+ ptr,
2292
+ box: this.mod.getBoxType(ptr.goType),
2293
+ };
2294
+ }
2295
+ const box = this._findScalarPtrByWType(wType, this.mod.boxTypes);
2296
+ if (box) {
2297
+ return {
2298
+ ptr: this.mod.getPtrType(box.goType),
2299
+ box,
2300
+ };
2301
+ }
2302
+ return null;
2303
+ }
2304
+
2305
+ _getScalarPtrFromGoType(goType) {
2306
+ let g = goType;
2307
+ if (g?.kind === "TypeName" && this.mod.checker?.types?.has(g.name)) {
2308
+ g = this.mod.checker.types.get(g.name);
2309
+ }
2310
+ while (g?.kind === "named" && g.underlying) {
2311
+ g = g.underlying;
2312
+ }
2313
+ const isPtr =
2314
+ g?.kind === "pointer" ||
2315
+ g?.kind === "PointerType" ||
2316
+ g?.kind === "StarExpr";
2317
+ if (!isPtr || (!g.base && !g.elem)) return null;
2318
+ const base = g.base || g.elem;
2319
+ if (!isScalarType(base, this.mod.checker, this.mod)) {
2320
+ return null;
2321
+ }
2322
+ return {
2323
+ ptr: this.mod.getPtrType(base),
2324
+ box: this.mod.getBoxType(base),
2325
+ };
2326
+ }
2327
+
2328
+ _getScalarPtrInfo(wType, goType = null) {
2329
+ return (
2330
+ this._getScalarPtrFromGoType(goType) ?? this._getScalarPtrFromWType(wType)
2331
+ );
2332
+ }
2333
+
2334
+ _isScalarPtrType(goType, wType = null) {
2335
+ return this._getScalarPtrInfo(wType, goType) !== null;
2336
+ }
2337
+
2338
+ _emitScalarPtrEq(_ptrInfo) {
2339
+ this.pushInstruction("ref.eq");
2340
+ }
2341
+
1500
2342
  _emitStructEq(structInfo) {
1501
2343
  const bTmp = this.acquireTemp({
1502
2344
  kind: "ref",
@@ -1543,11 +2385,18 @@ export class ExprsEmitter {
1543
2385
  (f.goType?.kind === "named" ? f.goType.name : null),
1544
2386
  );
1545
2387
  this._emitStructEq(sInfo);
2388
+ } else if (this._isValueArray(f.goType)) {
2389
+ this._emitArrayEq(f.goType);
1546
2390
  } else if (isStringType(f.goType, this.mod.checker)) {
1547
2391
  const cmpIdx = this.mod.getStringCmpImportIndex("==");
1548
2392
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2393
+ } else if (f.wType === "v128") {
2394
+ this._emitSimdEq(f.goType);
1549
2395
  } else if (f.wType === "anyref") {
1550
2396
  this._emitAnyEq();
2397
+ } else if (this._isScalarPtrType(f.goType, f.wType)) {
2398
+ const ptrInfo = this._getScalarPtrInfo(f.wType, f.goType);
2399
+ this._emitScalarPtrEq(ptrInfo);
1551
2400
  } else if (typeof f.wType === "object" && f.wType !== null) {
1552
2401
  this.pushInstruction("ref.eq");
1553
2402
  } else {
@@ -1578,7 +2427,110 @@ export class ExprsEmitter {
1578
2427
  });
1579
2428
  }
1580
2429
 
2430
+ _isValueArray(goType) {
2431
+ return goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker);
2432
+ }
2433
+
2434
+ // Go array equality: element-wise (arrays are values). Pops two arrays
2435
+ // and pushes an i32. A null array (a never-written slot) is the zero
2436
+ // array.
2437
+ _emitArrayEq(goType) {
2438
+ const elemType = this._getArrayElemType(goType);
2439
+ const arrInfo = this.mod.getArrayType(elemType);
2440
+ const arrWType = {
2441
+ kind: "ref",
2442
+ nullable: true,
2443
+ typeIndex: arrInfo.typeIndex,
2444
+ };
2445
+ const bTmp = this.acquireTemp(arrWType);
2446
+ const aTmp = this.acquireTemp(arrWType);
2447
+ for (const tmp of [bTmp, aTmp]) {
2448
+ this.pushInstruction({ op: "local.tee", index: tmp });
2449
+ this.pushInstruction("ref.is_null");
2450
+ this.pushInstruction({ op: "if", blockType: "void" });
2451
+ this.emitZeroValue(goType, arrWType);
2452
+ this.pushInstruction({ op: "local.set", index: tmp });
2453
+ this.pushInstruction("end");
2454
+ }
2455
+ const i = this.acquireTemp("i32");
2456
+ const resTmp = this.acquireTemp("i32");
2457
+ this.pushInstruction({ op: "i32.const", value: 1 });
2458
+ this.pushInstruction({ op: "local.set", index: resTmp });
2459
+ this.pushInstruction({ op: "i32.const", value: 0 });
2460
+ this.pushInstruction({ op: "local.set", index: i });
2461
+ this.pushInstruction({ op: "block", blockType: "void" });
2462
+ this.pushInstruction({ op: "loop", blockType: "void" });
2463
+ this.pushInstruction({ op: "local.get", index: i });
2464
+ this.pushInstruction({ op: "local.get", index: aTmp });
2465
+ this.pushInstruction("array.len");
2466
+ this.pushInstruction("i32.ge_u");
2467
+ this.pushInstruction({ op: "br_if", depth: 1 });
2468
+ for (const tmp of [aTmp, bTmp]) {
2469
+ this.pushInstruction({ op: "local.get", index: tmp });
2470
+ this.pushInstruction({ op: "local.get", index: i });
2471
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
2472
+ }
2473
+ this._emitElemEq(elemType, arrInfo.elemWType);
2474
+ this.pushInstruction("i32.eqz");
2475
+ this.pushInstruction({ op: "if", blockType: "void" });
2476
+ this.pushInstruction({ op: "i32.const", value: 0 });
2477
+ this.pushInstruction({ op: "local.set", index: resTmp });
2478
+ this.pushInstruction({ op: "br", depth: 2 });
2479
+ this.pushInstruction("end");
2480
+ this.pushInstruction({ op: "local.get", index: i });
2481
+ this.pushInstruction({ op: "i32.const", value: 1 });
2482
+ this.pushInstruction("i32.add");
2483
+ this.pushInstruction({ op: "local.set", index: i });
2484
+ this.pushInstruction({ op: "br", depth: 0 });
2485
+ this.pushInstruction("end");
2486
+ this.pushInstruction("end");
2487
+ this.pushInstruction({ op: "local.get", index: resTmp });
2488
+ this.releaseTemp(resTmp, "i32");
2489
+ this.releaseTemp(i, "i32");
2490
+ this.releaseTemp(aTmp, arrWType);
2491
+ this.releaseTemp(bTmp, arrWType);
2492
+ }
2493
+
2494
+ // Equality of two array elements on the stack -> i32.
2495
+ _emitElemEq(goType, wType) {
2496
+ if (this._isValueArray(goType)) {
2497
+ this._emitArrayEq(goType);
2498
+ } else if (
2499
+ isStructType(goType, this.mod.checker, this.mod) &&
2500
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
2501
+ ) {
2502
+ this._emitStructEq(this._resolveStructInfo({ _type: goType }));
2503
+ } else {
2504
+ this.emitBinaryOp("==", wType, goType);
2505
+ }
2506
+ }
2507
+
1581
2508
  emitBinaryOp(op, wType, goType) {
2509
+ if ((op === "==" || op === "!=") && this._isValueArray(goType)) {
2510
+ this._emitArrayEq(goType);
2511
+ if (op === "!=") this.pushInstruction("i32.eqz");
2512
+ return;
2513
+ }
2514
+ if (isComplex(goType)) {
2515
+ const is64 = isComplex64(goType);
2516
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
2517
+ const f = info.elemWType;
2518
+ const bTmp = this.acquireTemp(wType);
2519
+ const aTmp = this.acquireTemp(wType);
2520
+ this.pushInstruction({ op: "local.set", index: bTmp });
2521
+ this.pushInstruction({ op: "local.set", index: aTmp });
2522
+ if (op === "+" || op === "-") {
2523
+ this._emitComplexAddSub(aTmp, bTmp, info, f, op);
2524
+ } else if (op === "*") {
2525
+ this._emitComplexMul(aTmp, bTmp, info, f);
2526
+ } else if (op === "/") {
2527
+ this._emitComplexDiv(aTmp, bTmp, info, f);
2528
+ }
2529
+ this.releaseTemp(aTmp, wType);
2530
+ this.releaseTemp(bTmp, wType);
2531
+ return;
2532
+ }
2533
+
1582
2534
  const signed = isSigned(goType);
1583
2535
 
1584
2536
  switch (op) {
@@ -1646,8 +2598,13 @@ export class ExprsEmitter {
1646
2598
  if (isStringType(goType, this.mod.checker) || wType === "externref") {
1647
2599
  const cmpIdx = this.mod.getStringCmpImportIndex("==");
1648
2600
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2601
+ } else if (wType === "v128") {
2602
+ this._emitSimdEq(goType);
1649
2603
  } else if (wType === "anyref") {
1650
2604
  this._emitAnyEq();
2605
+ } else if (this._isScalarPtrType(goType, wType)) {
2606
+ const ptrInfo = this._getScalarPtrInfo(wType, goType);
2607
+ this._emitScalarPtrEq(ptrInfo);
1651
2608
  } else if (typeof wType === "object" && wType !== null) {
1652
2609
  this.pushInstruction("ref.eq");
1653
2610
  } else {
@@ -1658,9 +2615,16 @@ export class ExprsEmitter {
1658
2615
  if (isStringType(goType, this.mod.checker) || wType === "externref") {
1659
2616
  const cmpIdx = this.mod.getStringCmpImportIndex("!=");
1660
2617
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2618
+ } else if (wType === "v128") {
2619
+ this._emitSimdEq(goType);
2620
+ this.pushInstruction("i32.eqz");
1661
2621
  } else if (wType === "anyref") {
1662
2622
  this._emitAnyEq();
1663
2623
  this.pushInstruction("i32.eqz");
2624
+ } else if (this._isScalarPtrType(goType, wType)) {
2625
+ const ptrInfo = this._getScalarPtrInfo(wType, goType);
2626
+ this._emitScalarPtrEq(ptrInfo);
2627
+ this.pushInstruction("i32.eqz");
1664
2628
  } else if (typeof wType === "object" && wType !== null) {
1665
2629
  this.pushInstruction("ref.eq");
1666
2630
  this.pushInstruction("i32.eqz");
@@ -1693,6 +2657,8 @@ export class ExprsEmitter {
1693
2657
  emitNarrowIntWrap(goType) {
1694
2658
  if (!goType) return;
1695
2659
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
2660
+ const declared = this.mod.checker?.types?.get(goType.name);
2661
+ if (declared?.kind === "named") return this.emitNarrowIntWrap(declared);
1696
2662
  return this.emitNarrowIntWrap({ kind: "basic", name: goType.name });
1697
2663
  }
1698
2664
  if (goType.kind === "named" && goType.underlying) {
@@ -1720,7 +2686,14 @@ export class ExprsEmitter {
1720
2686
  }
1721
2687
 
1722
2688
  emitDivRem(op, left, right, targetWasmType = null) {
1723
- const goType = left._type;
2689
+ // An untyped constant left operand takes the type of the typed right
2690
+ // one (`(1<<31) % uint32(n)` divides unsigned).
2691
+ const goType =
2692
+ left._type?.kind === "untyped" &&
2693
+ right._type &&
2694
+ right._type.kind !== "untyped"
2695
+ ? right._type
2696
+ : left._type;
1724
2697
  const wType = targetWasmType ?? toWasmType(goType, this.mod.checker);
1725
2698
 
1726
2699
  if (wType === "f32" || wType === "f64") {
@@ -1848,6 +2821,21 @@ export class ExprsEmitter {
1848
2821
  this.pushInstruction({ op: "local.set", index: tmpVal });
1849
2822
 
1850
2823
  this.emitExpr(right, rightWType);
2824
+ // A negative signed count panics before the shift is evaluated.
2825
+ if (isSigned(right._type) && right.kind !== "BasicLit") {
2826
+ const tmpCount = this.acquireTemp(rightWType);
2827
+ this.pushInstruction({ op: "local.tee", index: tmpCount });
2828
+ this.pushInstruction({
2829
+ op: `${rightWType}.const`,
2830
+ value: rightWType === "i64" ? 0n : 0,
2831
+ });
2832
+ this.pushInstruction(`${rightWType}.lt_s`);
2833
+ this.pushInstruction({ op: "if", blockType: "void" });
2834
+ this.emitPanic("runtime error: negative shift amount");
2835
+ this.pushInstruction("end");
2836
+ this.pushInstruction({ op: "local.get", index: tmpCount });
2837
+ this.releaseTemp(tmpCount, rightWType);
2838
+ }
1851
2839
  if (wType === "i64" && rightWType === "i32") {
1852
2840
  this.pushInstruction("i64.extend_i32_u");
1853
2841
  } else if (wType === "i32" && rightWType === "i64") {
@@ -1890,32 +2878,91 @@ export class ExprsEmitter {
1890
2878
 
1891
2879
  emitTypeConversion(conv) {
1892
2880
  const { targetType, expr } = conv;
1893
- const fromType = expr._type;
1894
- const fromWType = toWasmType(fromType, this.mod.checker);
1895
- const toWType = toWasmType(targetType, this.mod.checker);
2881
+ const fromType = expr._type ?? this._resolveExprGoType(expr);
2882
+ const fromWType = this.toWasmType(fromType);
2883
+ const toWType = this.toWasmType(targetType);
2884
+
2885
+ // `[]T(nil)` and `(*T)(nil)`: the target type's nil, not a converted value.
2886
+ if (
2887
+ (expr.kind === "Ident" && expr.name === "nil") ||
2888
+ (expr.kind === "BasicLit" && expr.litKind === "NIL")
2889
+ ) {
2890
+ this._emitNilValue(targetType, toWType);
2891
+ return;
2892
+ }
2893
+
2894
+ // `any(x)` boxes x exactly like an assignment to an `any` variable.
2895
+ if (isAnyType(targetType, this.mod.checker)) {
2896
+ this.emitExpr(expr, "anyref");
2897
+ return;
2898
+ }
1896
2899
 
1897
2900
  this.emitExpr(expr, fromWType);
1898
2901
 
2902
+ if (isComplex(targetType)) {
2903
+ this._emitComplexConversion(targetType, fromType, fromWType);
2904
+ return;
2905
+ }
2906
+
2907
+ // Struct to a different defined struct type: a copy in the target's type.
2908
+ if (
2909
+ isStructType(fromType, this.mod.checker, this.mod) &&
2910
+ isStructType(targetType, this.mod.checker, this.mod) &&
2911
+ !isPointerToStruct(fromType, this.mod.checker, this.mod) &&
2912
+ !isPointerToStruct(targetType, this.mod.checker, this.mod)
2913
+ ) {
2914
+ const src = this._resolveStructInfo({ _type: fromType });
2915
+ const dst = this._resolveStructInfo({ _type: targetType });
2916
+ if (src && dst && src.typeIndex !== dst.typeIndex) {
2917
+ this.emitCloneStruct(src, fromWType, dst);
2918
+ return;
2919
+ }
2920
+ }
2921
+
1899
2922
  if (fromWType === toWType) {
1900
2923
  this.emitNarrowIntWrap(targetType);
1901
2924
  return;
1902
2925
  }
1903
2926
 
2927
+ // A named byte slice (`type MyBytes []byte`) converts like `[]byte`.
2928
+ const isByteElem = (t) => {
2929
+ const elem = this._getSliceElemType(t);
2930
+ return elem?.name === "byte" || elem?.name === "uint8";
2931
+ };
1904
2932
  if (
1905
2933
  fromWType === "externref" &&
1906
2934
  isSliceType(targetType, this.mod.checker) &&
1907
- (targetType.elem?.name === "byte" || targetType.elem?.name === "uint8")
2935
+ isByteElem(targetType)
1908
2936
  ) {
1909
- this._emitStringToByteSlice(targetType);
2937
+ this._emitStringToByteSlice({
2938
+ kind: "slice",
2939
+ elem: this._getSliceElemType(targetType),
2940
+ });
1910
2941
  return;
1911
2942
  }
1912
2943
 
1913
2944
  if (
1914
2945
  toWType === "externref" &&
1915
2946
  isSliceType(fromType, this.mod.checker) &&
1916
- (fromType.elem?.name === "byte" || fromType.elem?.name === "uint8")
2947
+ isByteElem(fromType)
1917
2948
  ) {
1918
- this._emitByteSliceToString(fromType);
2949
+ this._emitByteSliceToString({
2950
+ kind: "slice",
2951
+ elem: this._getSliceElemType(fromType),
2952
+ });
2953
+ return;
2954
+ }
2955
+
2956
+ if (
2957
+ fromWType === "externref" &&
2958
+ isRuneSlice(targetType, this.mod.checker)
2959
+ ) {
2960
+ this._emitStringToRuneSlice(targetType);
2961
+ return;
2962
+ }
2963
+
2964
+ if (toWType === "externref" && isRuneSlice(fromType, this.mod.checker)) {
2965
+ this._emitRuneSliceToString(fromType);
1919
2966
  return;
1920
2967
  }
1921
2968
 
@@ -1960,6 +3007,64 @@ export class ExprsEmitter {
1960
3007
  }
1961
3008
  }
1962
3009
 
3010
+ _emitComplexConversion(targetType, fromType, fromWType) {
3011
+ const isTarget64 = isComplex64(targetType);
3012
+ const targetInfo = this.mod.getComplexType(
3013
+ isTarget64 ? "complex64" : "complex128",
3014
+ );
3015
+ if (isComplex(fromType)) {
3016
+ const isFrom64 = isComplex64(fromType);
3017
+ if (isTarget64 === isFrom64) return;
3018
+ const fromInfo = this.mod.getComplexType(
3019
+ isFrom64 ? "complex64" : "complex128",
3020
+ );
3021
+ const fromWTypeRef = {
3022
+ kind: "ref",
3023
+ nullable: true,
3024
+ typeIndex: fromInfo.typeIndex,
3025
+ typeName: fromInfo.name,
3026
+ };
3027
+ const cTmp = this.acquireTemp(fromWTypeRef);
3028
+ this.pushInstruction({ op: "local.set", index: cTmp });
3029
+
3030
+ for (let i = 0; i < 2; i++) {
3031
+ this.pushInstruction({ op: "local.get", index: cTmp });
3032
+ this.pushInstruction({
3033
+ op: "struct.get",
3034
+ typeIndex: fromInfo.typeIndex,
3035
+ typeName: fromInfo.name,
3036
+ fieldIndex: i,
3037
+ });
3038
+ this.pushInstruction(isTarget64 ? "f32.demote_f64" : "f64.promote_f32");
3039
+ }
3040
+
3041
+ this.pushInstruction({
3042
+ op: "struct.new",
3043
+ typeIndex: targetInfo.typeIndex,
3044
+ typeName: targetInfo.name,
3045
+ });
3046
+ this.releaseTemp(cTmp, fromWTypeRef);
3047
+ return;
3048
+ }
3049
+
3050
+ // From numeric scalar
3051
+ const targetF = targetInfo.elemWType;
3052
+ const srcSign = isSigned(fromType) ? "s" : "u";
3053
+ if (fromWType === "i32" || fromWType === "i64") {
3054
+ this.pushInstruction(`${targetF}.convert_${fromWType}_${srcSign}`);
3055
+ } else if (fromWType === "f32" && targetF === "f64") {
3056
+ this.pushInstruction("f64.promote_f32");
3057
+ } else if (fromWType === "f64" && targetF === "f32") {
3058
+ this.pushInstruction("f32.demote_f64");
3059
+ }
3060
+ this.pushInstruction({ op: `${targetF}.const`, value: 0.0 });
3061
+ this.pushInstruction({
3062
+ op: "struct.new",
3063
+ typeIndex: targetInfo.typeIndex,
3064
+ typeName: targetInfo.name,
3065
+ });
3066
+ }
3067
+
1963
3068
  _emitStringToByteSlice(targetType) {
1964
3069
  const elemGo = targetType.elem;
1965
3070
  const sliceInfo = this.mod.getSliceType(elemGo);
@@ -2030,6 +3135,162 @@ export class ExprsEmitter {
2030
3135
  this.releaseTemp(strTmp, "externref");
2031
3136
  }
2032
3137
 
3138
+ // Pushes the code point at UTF-16 index `iTmp` of `strTmp` into `cpTmp`
3139
+ // and advances `iTmp` past it (two units for a surrogate pair).
3140
+ _emitNextCodePoint(strTmp, iTmp, cpTmp) {
3141
+ this.pushInstruction({ op: "local.get", index: strTmp });
3142
+ this.pushInstruction({ op: "local.get", index: iTmp });
3143
+ this.pushInstruction({
3144
+ op: "call",
3145
+ funcIndex: this.mod.getStringCodePointAtImportIndex(),
3146
+ });
3147
+ this.pushInstruction({ op: "local.set", index: cpTmp });
3148
+ this.pushInstruction({ op: "local.get", index: iTmp });
3149
+ this.pushInstruction({ op: "i32.const", value: 1 });
3150
+ this.pushInstruction("i32.add");
3151
+ this.pushInstruction({ op: "local.get", index: cpTmp });
3152
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
3153
+ this.pushInstruction("i32.gt_u");
3154
+ this.pushInstruction("i32.add");
3155
+ this.pushInstruction({ op: "local.set", index: iTmp });
3156
+ }
3157
+
3158
+ // Emits `block loop (br_if 1 (i >= n)) body br 0 end end`.
3159
+ _emitCountedLoop(iTmp, nTmp, body) {
3160
+ this.pushInstruction({ op: "block", blockType: "void" });
3161
+ this.pushInstruction({ op: "loop", blockType: "void" });
3162
+ this.pushInstruction({ op: "local.get", index: iTmp });
3163
+ this.pushInstruction({ op: "local.get", index: nTmp });
3164
+ this.pushInstruction("i32.ge_s");
3165
+ this.pushInstruction({ op: "br_if", depth: 1 });
3166
+ body();
3167
+ this.pushInstruction({ op: "br", depth: 0 });
3168
+ this.pushInstruction("end");
3169
+ this.pushInstruction("end");
3170
+ }
3171
+
3172
+ // []rune(s): one pass counts the code points, a second fills an exactly
3173
+ // sized array (cap == len, as for every slice GoFront creates).
3174
+ _emitStringToRuneSlice(targetType) {
3175
+ const sliceInfo = this.mod.getSliceType(this._getSliceElemType(targetType));
3176
+ const arrInfo = sliceInfo.arrInfo;
3177
+ const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
3178
+ const strTmp = this.acquireTemp("externref");
3179
+ const unitsTmp = this.acquireTemp("i32");
3180
+ const iTmp = this.acquireTemp("i32");
3181
+ const kTmp = this.acquireTemp("i32");
3182
+ const cpTmp = this.acquireTemp("i32");
3183
+ const arrTmp = this.acquireTemp(arrW);
3184
+ this.pushInstruction({ op: "local.tee", index: strTmp });
3185
+ this.pushInstruction({
3186
+ op: "call",
3187
+ funcIndex: this.mod.getStringLenImportIndex(),
3188
+ });
3189
+ this.pushInstruction({ op: "local.set", index: unitsTmp });
3190
+ const resetIndex = () => {
3191
+ this.pushInstruction({ op: "i32.const", value: 0 });
3192
+ this.pushInstruction({ op: "local.set", index: iTmp });
3193
+ this.pushInstruction({ op: "i32.const", value: 0 });
3194
+ this.pushInstruction({ op: "local.set", index: kTmp });
3195
+ };
3196
+ const bumpK = () => {
3197
+ this.pushInstruction({ op: "local.get", index: kTmp });
3198
+ this.pushInstruction({ op: "i32.const", value: 1 });
3199
+ this.pushInstruction("i32.add");
3200
+ this.pushInstruction({ op: "local.set", index: kTmp });
3201
+ };
3202
+ resetIndex();
3203
+ this._emitCountedLoop(iTmp, unitsTmp, () => {
3204
+ this._emitNextCodePoint(strTmp, iTmp, cpTmp);
3205
+ bumpK();
3206
+ });
3207
+ this.pushInstruction({ op: "local.get", index: kTmp });
3208
+ this.pushInstruction({
3209
+ op: "array.new_default",
3210
+ typeIndex: arrInfo.typeIndex,
3211
+ });
3212
+ this.pushInstruction({ op: "local.set", index: arrTmp });
3213
+ resetIndex();
3214
+ this._emitCountedLoop(iTmp, unitsTmp, () => {
3215
+ this._emitNextCodePoint(strTmp, iTmp, cpTmp);
3216
+ this.pushInstruction({ op: "local.get", index: arrTmp });
3217
+ this.pushInstruction({ op: "local.get", index: kTmp });
3218
+ this.pushInstruction({ op: "local.get", index: cpTmp });
3219
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
3220
+ bumpK();
3221
+ });
3222
+ this.pushInstruction({ op: "local.get", index: arrTmp });
3223
+ this.pushInstruction({ op: "i32.const", value: 0 });
3224
+ this.pushInstruction({ op: "local.get", index: kTmp });
3225
+ this.pushInstruction({ op: "local.get", index: kTmp });
3226
+ this.pushInstruction({ op: "struct.new", typeIndex: sliceInfo.typeIndex });
3227
+ this.releaseTemp(arrTmp, arrW);
3228
+ this.releaseTemp(cpTmp, "i32");
3229
+ this.releaseTemp(kTmp, "i32");
3230
+ this.releaseTemp(iTmp, "i32");
3231
+ this.releaseTemp(unitsTmp, "i32");
3232
+ this.releaseTemp(strTmp, "externref");
3233
+ }
3234
+
3235
+ // string(runes): concatenates `str_from_code_point` of each element.
3236
+ _emitRuneSliceToString(fromType) {
3237
+ const sliceInfo = this.mod.getSliceType(this._getSliceElemType(fromType));
3238
+ const arrInfo = sliceInfo.arrInfo;
3239
+ const sliceW = {
3240
+ kind: "ref",
3241
+ nullable: true,
3242
+ typeIndex: sliceInfo.typeIndex,
3243
+ };
3244
+ const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
3245
+ const sTmp = this.acquireTemp(sliceW);
3246
+ this.pushInstruction({ op: "local.set", index: sTmp });
3247
+ const arrTmp = this.acquireTemp(arrW);
3248
+ const offTmp = this.acquireTemp("i32");
3249
+ const lenTmp = this.acquireTemp("i32");
3250
+ const capTmp = this.acquireTemp("i32");
3251
+ this._emitSliceUnpack(sTmp, sliceInfo, {
3252
+ arr: arrTmp,
3253
+ off: offTmp,
3254
+ len: lenTmp,
3255
+ cap: capTmp,
3256
+ });
3257
+ this.releaseTemp(sTmp, sliceW);
3258
+ this.releaseTemp(capTmp, "i32");
3259
+ const accTmp = this.acquireTemp("externref");
3260
+ this.emitBasicLit({ kind: "BasicLit", litKind: "STRING", value: "" });
3261
+ this.pushInstruction({ op: "local.set", index: accTmp });
3262
+ const iTmp = this.acquireTemp("i32");
3263
+ this.pushInstruction({ op: "i32.const", value: 0 });
3264
+ this.pushInstruction({ op: "local.set", index: iTmp });
3265
+ this._emitCountedLoop(iTmp, lenTmp, () => {
3266
+ this.pushInstruction({ op: "local.get", index: accTmp });
3267
+ this.pushInstruction({ op: "local.get", index: arrTmp });
3268
+ this.pushInstruction({ op: "local.get", index: offTmp });
3269
+ this.pushInstruction({ op: "local.get", index: iTmp });
3270
+ this.pushInstruction("i32.add");
3271
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
3272
+ this.pushInstruction({
3273
+ op: "call",
3274
+ funcIndex: this.mod.getStringFromCodePointImportIndex(),
3275
+ });
3276
+ this.pushInstruction({
3277
+ op: "call",
3278
+ funcIndex: this.mod.getStringConcatImportIndex(),
3279
+ });
3280
+ this.pushInstruction({ op: "local.set", index: accTmp });
3281
+ this.pushInstruction({ op: "local.get", index: iTmp });
3282
+ this.pushInstruction({ op: "i32.const", value: 1 });
3283
+ this.pushInstruction("i32.add");
3284
+ this.pushInstruction({ op: "local.set", index: iTmp });
3285
+ });
3286
+ this.pushInstruction({ op: "local.get", index: accTmp });
3287
+ this.releaseTemp(iTmp, "i32");
3288
+ this.releaseTemp(accTmp, "externref");
3289
+ this.releaseTemp(lenTmp, "i32");
3290
+ this.releaseTemp(offTmp, "i32");
3291
+ this.releaseTemp(arrTmp, arrW);
3292
+ }
3293
+
2033
3294
  _emitByteSliceToString(fromType) {
2034
3295
  const elemGo = fromType.elem;
2035
3296
  const sliceInfo = this.mod.getSliceType(elemGo);