gofront 1.6.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +45 -49
  2. package/package.json +12 -8
  3. package/src/backend/js/expressions.js +1627 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +508 -102
  6. package/src/backend/js/runtime.js +1997 -106
  7. package/src/backend/js/statements.js +931 -87
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +7 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +19 -2
  14. package/src/backend/js/stdlib/json.js +1167 -0
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +34 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +446 -72
  29. package/src/backend/wasm/emit-builtins.js +320 -13
  30. package/src/backend/wasm/emit-exprs.js +1404 -199
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +518 -74
  34. package/src/backend/wasm/encode.js +128 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/optimize.js +4 -2
  41. package/src/backend/wasm/prescan.js +397 -0
  42. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  43. package/src/backend/wasm/stdlib/core.js +1574 -199
  44. package/src/backend/wasm/stdlib/index.js +3 -5
  45. package/src/backend/wasm/stdlib/io.js +1567 -150
  46. package/src/backend/wasm/stdlib/packages.js +2 -0
  47. package/src/backend/wasm/stdlib/simd.js +499 -0
  48. package/src/backend/wasm/stdlib/time.js +1041 -12
  49. package/src/backend/wasm/types.js +221 -15
  50. package/src/backend/wasm/variadic.js +93 -0
  51. package/src/backend/wasm/wat.js +84 -20
  52. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  53. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  54. package/src/{vendor.js → cli/vendor.js} +89 -15
  55. package/src/compiler.js +107 -269
  56. package/src/dts-parser.js +71 -14
  57. package/src/index.js +15 -169
  58. package/src/{lexer.js → lexer/lexer.js} +102 -55
  59. package/src/{tokens.js → lexer/tokens.js} +1 -0
  60. package/src/link/sourcemap.js +97 -0
  61. package/src/link/stdlib.js +515 -0
  62. package/src/link/wasm.js +294 -0
  63. package/src/lower/bounds.js +394 -0
  64. package/src/lower/boxing.js +1 -6
  65. package/src/lower/captures.js +176 -32
  66. package/src/lower/embedding.js +2 -1
  67. package/src/lower/index.js +26 -18
  68. package/src/lower/json-codecs.js +1398 -0
  69. package/src/lower/ownership.js +22 -1
  70. package/src/lower/range.js +86 -1
  71. package/src/lower/reachability.js +132 -0
  72. package/src/parser/declarations.js +58 -13
  73. package/src/parser/expressions.js +29 -7
  74. package/src/parser/index.js +2 -1
  75. package/src/parser/statements.js +37 -22
  76. package/src/parser/struct-tag.js +144 -0
  77. package/src/{templ-parser.js → parser/templ.js} +138 -15
  78. package/src/parser/types.js +130 -13
  79. package/src/resolver.js +22 -1
  80. package/src/typechecker/assignability.js +215 -11
  81. package/src/typechecker/const-fold.js +356 -0
  82. package/src/typechecker/constants.js +256 -0
  83. package/src/typechecker/expressions.js +960 -99
  84. package/src/typechecker/index.js +491 -72
  85. package/src/typechecker/init-order.js +330 -0
  86. package/src/typechecker/resolve.js +596 -52
  87. package/src/typechecker/statements.js +157 -17
  88. package/src/typechecker/stdlib/core.js +333 -55
  89. package/src/typechecker/stdlib/excluded.js +898 -0
  90. package/src/typechecker/stdlib/extended.js +359 -26
  91. package/src/typechecker/{stdlib.js → stdlib/index.js} +6 -4
  92. package/src/typechecker/stdlib/simd.js +123 -0
  93. package/src/typechecker/termination.js +1 -1
  94. package/src/typechecker/types.js +165 -26
  95. package/stdlib/bytes/iter.go +192 -0
  96. package/stdlib/bytes/search.go +240 -0
  97. package/stdlib/bytes/split.go +218 -0
  98. package/stdlib/cmp/cmp.go +65 -0
  99. package/stdlib/encoding/json/errors.go +31 -0
  100. package/stdlib/encoding/json/indent.go +71 -0
  101. package/stdlib/encoding/json/number.go +12 -0
  102. package/stdlib/encoding/json/stream.go +173 -0
  103. package/stdlib/errors/overlay.go +12 -0
  104. package/stdlib/hash/hash.go +53 -0
  105. package/stdlib/internal/jsonrt/dec.go +738 -0
  106. package/stdlib/internal/jsonrt/enc.go +366 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +649 -0
  111. package/stdlib/io/io.go +346 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/iter.go +65 -0
  114. package/stdlib/math/extra.go +270 -0
  115. package/stdlib/slices/extra.go +73 -0
  116. package/stdlib/slices/iter.go +113 -0
  117. package/stdlib/sort/sort.go +561 -0
  118. package/stdlib/strconv/quote.go +341 -0
  119. package/stdlib/strings/extra.go +35 -0
  120. package/stdlib/strings/iter.go +122 -0
  121. package/stdlib/sync/extra.go +105 -0
  122. package/stdlib/unicode/unicode.go +112 -0
  123. package/stdlib/unicode/utf8/bytes.go +239 -0
  124. package/src/backend/wasm/const-fold.js +0 -205
  125. package/src/backend/wasm/emit-stdlib.js +0 -13
  126. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  127. /package/src/{colors.js → cli/colors.js} +0 -0
  128. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  129. /package/src/{minifier.js → cli/minifier.js} +0 -0
  130. /package/src/{project-config.js → cli/project-config.js} +0 -0
  131. /package/src/{pwa.js → cli/pwa.js} +0 -0
  132. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -1,21 +1,51 @@
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";
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";
5
14
  import {
6
15
  getMapKeyValTypes,
7
16
  isAnyType,
8
17
  isArrayType,
18
+ isComplex,
19
+ isComplex64,
9
20
  isFuncType,
10
21
  isMapType,
11
22
  isPointerToStruct,
23
+ isRuneSlice,
24
+ isScalarType,
12
25
  isSigned,
13
26
  isSliceType,
14
27
  isStringType,
15
28
  isStructType,
29
+ isTestingT,
16
30
  toWasmType,
17
31
  } from "./types.js";
18
32
 
33
+ // Engine limit on `array.new_fixed` operands.
34
+ const MAX_NEW_FIXED = 10000;
35
+
36
+ // An untyped integral float constant (`1e18 + 1`) in an integer context is
37
+ // emitted from its exact integer value.
38
+ function constLitFor(val, targetWasmType) {
39
+ const i = constIntValue(val);
40
+ if (
41
+ val.k === "FLOAT" &&
42
+ i !== null &&
43
+ (targetWasmType === "i64" || targetWasmType === "i32")
44
+ )
45
+ return constToLit({ k: "INT", v: i });
46
+ return constToLit(val);
47
+ }
48
+
19
49
  export class ExprsEmitter {
20
50
  emitExpr(expr, targetWasmType = null) {
21
51
  if (!expr) return;
@@ -36,7 +66,7 @@ export class ExprsEmitter {
36
66
  }
37
67
 
38
68
  if (
39
- isStringType(goType, this.mod.checker) ||
69
+ (isStringType(goType, this.mod.checker) && goType?.kind !== "named") ||
40
70
  (expr.kind === "BasicLit" && expr.litKind === "STRING")
41
71
  ) {
42
72
  this._emitRawExpr(expr, "externref");
@@ -50,6 +80,7 @@ export class ExprsEmitter {
50
80
  // for type switches and assertions.
51
81
  if (isPointerToStruct(goType, this.mod.checker, this.mod)) {
52
82
  this._emitRawExpr(expr, this.toWasmType(goType));
83
+ this._emitNilAwareAny(goType, expr);
53
84
  return;
54
85
  }
55
86
  if (isStructType(goType, this.mod.checker, this.mod)) {
@@ -78,6 +109,14 @@ export class ExprsEmitter {
78
109
  ) {
79
110
  const refWType = this.toWasmType(goType);
80
111
  this._emitRawExpr(expr, refWType);
112
+ // An array stored in an interface is a copy.
113
+ if (isArrayType(goType, this.mod.checker))
114
+ this._emitCopyIfValue(expr, goType, refWType);
115
+ if (
116
+ isFuncType(goType, this.mod.checker) ||
117
+ isMapType(goType, this.mod.checker)
118
+ )
119
+ this._emitNilAwareAny(goType, expr);
81
120
  return;
82
121
  }
83
122
 
@@ -92,6 +131,23 @@ export class ExprsEmitter {
92
131
  this._emitRawExpr(expr, targetWasmType);
93
132
  }
94
133
 
134
+ // The nullable ref on the stack becomes an interface value: a nil map, func
135
+ // or pointer turns into the marker of its type (never a nil interface).
136
+ _emitNilAwareAny(goType, expr) {
137
+ if (expr.kind === "UnaryExpr" && expr.op === "&") return;
138
+ if (expr.kind === "CompositeLit" || expr.kind === "FuncLit") return;
139
+ const nilBox = this.mod.getNilBoxType(goType);
140
+ const tmp = this.acquireTemp(this.toWasmType(goType));
141
+ this.pushInstruction({ op: "local.tee", index: tmp });
142
+ this.pushInstruction("ref.is_null");
143
+ this.pushInstruction({ op: "if", blockType: "anyref" });
144
+ this.pushInstruction({ op: "global.get", index: nilBox.globalIndex });
145
+ this.pushInstruction("else");
146
+ this.pushInstruction({ op: "local.get", index: tmp });
147
+ this.pushInstruction("end");
148
+ this.releaseTemp(tmp, this.toWasmType(goType));
149
+ }
150
+
95
151
  _emitBoxValue(box) {
96
152
  this.pushInstruction({
97
153
  op: "ref.null",
@@ -116,6 +172,9 @@ export class ExprsEmitter {
116
172
  return { kind: "nil", name: "nil" };
117
173
  }
118
174
  }
175
+ if (expr.kind === "ImagLit") {
176
+ return { kind: "untyped", base: "complex128" };
177
+ }
119
178
  if (expr.kind === "Ident") {
120
179
  const local = this.resolveLocal(expr.name);
121
180
  if (local) return local.goType;
@@ -128,23 +187,46 @@ export class ExprsEmitter {
128
187
  return null;
129
188
  }
130
189
 
190
+ _emitLocalConst(ident, targetWasmType) {
191
+ const lit = constLitFor(ident._const, targetWasmType);
192
+ if (!lit) return false;
193
+ lit._type = ident._type;
194
+ this.emitBasicLit(lit, targetWasmType);
195
+ return true;
196
+ }
197
+
131
198
  _emitRawExpr(expr, targetWasmType = null) {
132
199
  if (expr.kind === "BinaryExpr" || expr.kind === "UnaryExpr") {
133
- const folded = foldConstExpr(expr, (name) =>
134
- this.mod.pkgConsts?.get(name),
135
- );
200
+ // The checker's exact value (src/typechecker/const-fold.js) when it has one.
201
+ const folded = expr._const
202
+ ? constLitFor(expr._const, targetWasmType)
203
+ : foldConstExpr(expr, (name) => this.mod.pkgConsts?.get(name));
136
204
  if (folded) {
137
205
  folded._type = expr._type;
138
206
  this.emitBasicLit(folded, targetWasmType);
139
207
  return;
140
208
  }
141
209
  }
210
+ // A function-local constant is a local slot (so closures can capture it)
211
+ // but reads as the checker's exact value, which adapts to the context.
212
+ if (
213
+ expr.kind === "Ident" &&
214
+ expr._const &&
215
+ this.resolveLocal(expr.name) &&
216
+ this._emitLocalConst(expr, targetWasmType)
217
+ )
218
+ return;
219
+ if (this._emitFoldedIntConversion(expr, targetWasmType)) return;
142
220
 
143
221
  switch (expr.kind) {
144
222
  case "BasicLit":
145
223
  this.emitBasicLit(expr, targetWasmType);
146
224
  break;
147
225
 
226
+ case "ImagLit":
227
+ this.emitImagLit(expr, targetWasmType);
228
+ break;
229
+
148
230
  case "Ident":
149
231
  this.emitIdent(expr, targetWasmType);
150
232
  break;
@@ -172,20 +254,7 @@ export class ExprsEmitter {
172
254
  case "SelectorExpr": {
173
255
  const deq = this._dequalify(expr);
174
256
  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);
257
+ else if (this._emitPkgConst(expr, targetWasmType)) break;
189
258
  else this.emitSelectorExpr(expr);
190
259
  break;
191
260
  }
@@ -275,6 +344,17 @@ export class ExprsEmitter {
275
344
 
276
345
  emitCompositeLit(lit) {
277
346
  const litType = lit._type ?? lit.typeExpr?._type ?? lit.typeExpr;
347
+ if (
348
+ litType?.kind === "simd" ||
349
+ litType?.name === "simd.F32x4" ||
350
+ litType?.underlying?.kind === "simd" ||
351
+ (lit.typeExpr?.kind === "SelectorExpr" &&
352
+ lit.typeExpr.field === "F32x4" &&
353
+ lit.typeExpr.expr?.name === "simd")
354
+ ) {
355
+ this.emitSimdCompositeLit(lit);
356
+ return;
357
+ }
278
358
  if (isMapType(litType, this.mod.checker)) {
279
359
  const { keyType, valType } = getMapKeyValTypes(litType, this.mod.checker);
280
360
  const mapInfo = this.mod.getMapType(keyType, valType);
@@ -291,7 +371,7 @@ export class ExprsEmitter {
291
371
  this.pushInstruction({ op: "local.get", index: mTmp });
292
372
  this.emitExpr(elem.key, mapInfo.keyWType);
293
373
  this.emitExpr(elem.value, mapInfo.valWType);
294
- this._emitCopyIfValueStruct(elem.value, valType, mapInfo.valWType);
374
+ this._emitCopyIfValue(elem.value, valType, mapInfo.valWType);
295
375
  this.pushInstruction({
296
376
  op: "call",
297
377
  funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
@@ -304,33 +384,11 @@ export class ExprsEmitter {
304
384
  }
305
385
 
306
386
  if (isSliceType(litType, this.mod.checker)) {
307
- const elemType = litType.elem;
387
+ const elemType = this._getArrayElemType(litType);
308
388
  const sliceInfo = this.mod.getSliceType(elemType);
309
- const count = lit.elems?.length ?? 0;
310
- if (count === 0) {
311
- this.pushInstruction({ op: "i32.const", value: 0 });
312
- this.pushInstruction({
313
- op: "array.new_default",
314
- typeIndex: sliceInfo.arrInfo.typeIndex,
315
- });
316
- this.pushInstruction({ op: "i32.const", value: 0 }); // offset
317
- this.pushInstruction({ op: "i32.const", value: 0 }); // len
318
- this.pushInstruction({ op: "i32.const", value: 0 }); // cap
319
- this.pushInstruction({
320
- op: "struct.new",
321
- typeIndex: sliceInfo.typeIndex,
322
- });
323
- return;
324
- }
325
- for (const elem of lit.elems) {
326
- this.emitExpr(elem, sliceInfo.elemWType);
327
- this._emitCopyIfValueStruct(elem, elem._type, sliceInfo.elemWType);
328
- }
329
- this.pushInstruction({
330
- op: "array.new_fixed",
331
- typeIndex: sliceInfo.arrInfo.typeIndex,
332
- size: count,
333
- });
389
+ const elems = lit.elems ?? [];
390
+ const count = compositeLitMaxIndex(elems) + 1;
391
+ this._emitArrayLitElems(elems, elemType, sliceInfo.arrInfo, count);
334
392
  this.pushInstruction({ op: "i32.const", value: 0 }); // offset
335
393
  this.pushInstruction({ op: "i32.const", value: count }); // len
336
394
  this.pushInstruction({ op: "i32.const", value: count }); // cap
@@ -342,22 +400,12 @@ export class ExprsEmitter {
342
400
  }
343
401
 
344
402
  if (isArrayType(litType, this.mod.checker)) {
345
- const elemType = litType.elem;
403
+ const elemType = this._getArrayElemType(litType);
346
404
  const arrInfo = this.mod.getArrayType(elemType);
347
- const count = lit.elems?.length ?? 0;
405
+ const elems = lit.elems ?? [];
406
+ const count = compositeLitMaxIndex(elems) + 1;
348
407
  const targetSize = this._getArraySize(litType, count);
349
- for (const elem of lit.elems ?? []) {
350
- this.emitExpr(elem, arrInfo.elemWType);
351
- this._emitCopyIfValueStruct(elem, elem._type, arrInfo.elemWType);
352
- }
353
- for (let i = count; i < targetSize; i++) {
354
- this.emitZeroValue(elemType, arrInfo.elemWType);
355
- }
356
- this.pushInstruction({
357
- op: "array.new_fixed",
358
- typeIndex: arrInfo.typeIndex,
359
- size: targetSize,
360
- });
408
+ this._emitArrayLitElems(elems, elemType, arrInfo, targetSize);
361
409
  return;
362
410
  }
363
411
 
@@ -387,13 +435,13 @@ export class ExprsEmitter {
387
435
  const f = fields[i];
388
436
  if (isKeyed) {
389
437
  if (map.has(f.name)) {
390
- this.emitExpr(map.get(f.name), f.wType);
438
+ this._emitFieldInit(map.get(f.name), f);
391
439
  } else {
392
440
  this.emitZeroValue(f.goType, f.wType);
393
441
  }
394
442
  } else {
395
443
  if (i < (lit.elems?.length ?? 0)) {
396
- this.emitExpr(lit.elems[i], f.wType);
444
+ this._emitFieldInit(lit.elems[i], f);
397
445
  } else {
398
446
  this.emitZeroValue(f.goType, f.wType);
399
447
  }
@@ -406,9 +454,169 @@ export class ExprsEmitter {
406
454
  });
407
455
  }
408
456
 
457
+ // Pushes a new array of `size` elements built from literal `elems`.
458
+ // Keyed elements (`{2: 7}`) go to their index; every other slot is the
459
+ // element zero value. With ascending keys and at most
460
+ // MAX_NEW_FIXED elements this is one `array.new_fixed`; otherwise the
461
+ // array is allocated first and the elements are stored in source order
462
+ // (Go evaluates them left to right whatever their keys).
463
+ _emitArrayLitElems(elems, elemType, arrInfo, size) {
464
+ const indices = compositeLitIndices(elems);
465
+ const ascending = indices.every((v, i) => i === 0 || v > indices[i - 1]);
466
+ const value = (e) => (e.kind === "KeyValueExpr" ? e.value : e);
467
+ const emitElem = (e) => {
468
+ this.emitExpr(e, arrInfo.elemWType);
469
+ // Copy by the element type: a struct stored in an `any` element
470
+ // is already boxed, and a keyed element wraps its value.
471
+ this._emitCopyIfValue(e, elemType, arrInfo.elemWType);
472
+ };
473
+ if (ascending && size <= MAX_NEW_FIXED) {
474
+ const slots = new Array(size).fill(null);
475
+ elems.forEach((e, i) => {
476
+ slots[indices[i]] = value(e);
477
+ });
478
+ for (const e of slots) {
479
+ if (e) emitElem(e);
480
+ else this.emitZeroValue(elemType, arrInfo.elemWType);
481
+ }
482
+ this.pushInstruction({
483
+ op: "array.new_fixed",
484
+ typeIndex: arrInfo.typeIndex,
485
+ size,
486
+ });
487
+ return;
488
+ }
489
+ const wType = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
490
+ const arr = this.acquireTemp(wType);
491
+ const set = (idx, emit) => {
492
+ this.pushInstruction({ op: "local.get", index: arr });
493
+ this.pushInstruction({ op: "i32.const", value: idx });
494
+ emit();
495
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
496
+ };
497
+ this.pushInstruction({ op: "i32.const", value: size });
498
+ this.pushInstruction({
499
+ op: "array.new_default",
500
+ typeIndex: arrInfo.typeIndex,
501
+ });
502
+ this.pushInstruction({ op: "local.set", index: arr });
503
+ // Numeric zero values are the array default; others are stored.
504
+ if (typeof arrInfo.elemWType !== "string") {
505
+ const given = new Set(indices);
506
+ for (let i = 0; i < size; i++)
507
+ if (!given.has(i))
508
+ set(i, () => this.emitZeroValue(elemType, arrInfo.elemWType));
509
+ }
510
+ elems.forEach((e, i) => {
511
+ set(indices[i], () => emitElem(value(e)));
512
+ });
513
+ this.pushInstruction({ op: "local.get", index: arr });
514
+ this.releaseTemp(arr, wType);
515
+ }
516
+
517
+ // A struct literal field initialiser; array values are copied in.
518
+ _emitFieldInit(value, f) {
519
+ this.emitExpr(value, f.wType);
520
+ if (f.goType?.kind !== "pointer" && isArrayType(f.goType, this.mod.checker))
521
+ this._emitCopyIfValue(value, f.goType, f.wType);
522
+ }
523
+
524
+ // With a pointer on the stack, branches to the function's `nil` block
525
+ // (_openNilBlock) when it is null. Outside one, the access traps.
526
+ _emitNilCheck(goType) {
527
+ if (goType?.kind !== "pointer") return;
528
+ this._emitNilBranch();
529
+ }
530
+
531
+ _emitNilBranch() {
532
+ const depth = this.resolveBranchDepthToRole("nil");
533
+ if (depth == null) return;
534
+ this.pushInstruction({ op: "br_on_null", depth });
535
+ }
536
+
537
+ // Emits the struct (or struct pointer) a field access or store goes
538
+ // through, nil-checked. A `*p` base is the pointee itself, not the copy a
539
+ // value read of `*p` makes, so a store through it lands in the pointee.
540
+ // Every selector read and every field store/op-assign/inc-dec starts here.
541
+ emitSelectorBase(node, wType = null, goType = node._type) {
542
+ const pointer = this._structDerefPointer(node);
543
+ if (pointer) {
544
+ this.emitSelectorBase(pointer);
545
+ return;
546
+ }
547
+ this.emitExpr(node, wType ?? this.toWasmType(goType));
548
+ this._emitNilCheck(goType);
549
+ }
550
+
551
+ // For `*p` (possibly parenthesised) with `p` pointing at a struct, the
552
+ // pointer `p`, which is the struct's own reference; otherwise null.
553
+ _structDerefPointer(node) {
554
+ let base = node;
555
+ while (base?.kind === "ParenExpr") base = base.expr;
556
+ if (
557
+ (base?.kind !== "UnaryExpr" || base.op !== "*") &&
558
+ base?.kind !== "StarExpr"
559
+ )
560
+ return null;
561
+ const operand = base.operand ?? base.expr;
562
+ return this._derefStructInfo(operand) ? operand : null;
563
+ }
564
+
565
+ _emitPkgConst(expr, targetWasmType) {
566
+ if (this._isMathConst(expr)) {
567
+ this.emitMathConst(expr.field, targetWasmType);
568
+ return true;
569
+ }
570
+ if (this._isUtf8Const(expr)) {
571
+ this.emitUtf8Const(expr.field, targetWasmType);
572
+ return true;
573
+ }
574
+ if (this._isStrconvConst(expr)) {
575
+ this.emitStrconvConst(expr.field, targetWasmType);
576
+ return true;
577
+ }
578
+ if (this._isPathConst(expr)) {
579
+ this.emitPathConst(expr.field, targetWasmType);
580
+ return true;
581
+ }
582
+ if (this._isIoConst(expr)) {
583
+ this.emitIoConst(expr.field, targetWasmType);
584
+ return true;
585
+ }
586
+ if (this._isOsConst?.(expr)) {
587
+ this.emitOsConst(expr.field, targetWasmType);
588
+ return true;
589
+ }
590
+ if (this._isTimeConst(expr)) {
591
+ this.emitTimeConst(expr.field, targetWasmType);
592
+ return true;
593
+ }
594
+ if (this._isBitsConst?.(expr)) {
595
+ this.emitBitsConst(expr.field, targetWasmType);
596
+ return true;
597
+ }
598
+ if (this._isExtendedPkgConst?.(expr)) {
599
+ this.emitExtendedPkgConst(expr, targetWasmType);
600
+ return true;
601
+ }
602
+ return false;
603
+ }
604
+
409
605
  emitSelectorExpr(expr) {
606
+ if (isTestingT(expr.expr?._type) && expr.field === "N") {
607
+ this.emitExpr(expr.expr, "externref");
608
+ this.pushInstruction({
609
+ op: "call",
610
+ funcIndex: this.mod.getStdlibImportIndex("testing_n"),
611
+ });
612
+ return;
613
+ }
410
614
  const structInfo = this._resolveStructInfo(expr.expr);
411
615
  if (!structInfo) {
616
+ if (expr.expr?.kind === "Ident" && expr.expr._type?.kind === "namespace")
617
+ throw new Error(
618
+ `${expr.expr.name}.${expr.field} cannot be used as a value in WASM`,
619
+ );
412
620
  throw new Error(`Cannot resolve struct for selector: ${expr.field}`);
413
621
  }
414
622
  const path = this._resolveFieldPath(structInfo, expr.field);
@@ -418,8 +626,7 @@ export class ExprsEmitter {
418
626
  );
419
627
  }
420
628
 
421
- const baseWType = this.toWasmType(expr.expr._type);
422
- this.emitExpr(expr.expr, baseWType);
629
+ this.emitSelectorBase(expr.expr);
423
630
 
424
631
  for (let i = 0; i < path.length; i++) {
425
632
  const step = path[i];
@@ -495,11 +702,9 @@ export class ExprsEmitter {
495
702
  );
496
703
  }
497
704
 
498
- const elemGoType = isSlice
499
- ? baseType.elem
500
- : isPtrToArr
501
- ? baseType.base.elem
502
- : baseType.elem;
705
+ const elemGoType = this._getArrayElemType(
706
+ isPtrToArr ? baseType.base : baseType,
707
+ );
503
708
  const arrInfo = this.mod.getArrayType(elemGoType);
504
709
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
505
710
 
@@ -593,11 +798,9 @@ export class ExprsEmitter {
593
798
  );
594
799
  }
595
800
 
596
- const elemGoType = isSlice
597
- ? baseType.elem
598
- : isPtrToArr
599
- ? baseType.base.elem
600
- : baseType.elem;
801
+ const elemGoType = this._getArrayElemType(
802
+ isPtrToArr ? baseType.base : baseType,
803
+ );
601
804
  const arrInfo = this.mod.getArrayType(elemGoType);
602
805
  const sliceInfo = this.mod.getSliceType(elemGoType);
603
806
 
@@ -746,8 +949,11 @@ export class ExprsEmitter {
746
949
  const isValStruct =
747
950
  isStructType(elemGoType, this.mod.checker, this.mod) &&
748
951
  !isPointerToStruct(elemGoType, this.mod.checker, this.mod);
952
+ const isValArray =
953
+ elemGoType?.kind !== "pointer" &&
954
+ isArrayType(elemGoType, this.mod.checker);
749
955
 
750
- if (!isValStruct) {
956
+ if (!isValStruct && !isValArray) {
751
957
  this.pushInstruction({
752
958
  op: "array.copy",
753
959
  typeIndexDst: arrInfo.typeIndex,
@@ -820,7 +1026,9 @@ export class ExprsEmitter {
820
1026
  this.pushInstruction("i32.add");
821
1027
  this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
822
1028
 
823
- if (sInfo) {
1029
+ if (isValArray) {
1030
+ this.emitCopyArray(elemGoType);
1031
+ } else if (sInfo) {
824
1032
  this.emitCloneStruct(sInfo, arrInfo.elemWType);
825
1033
  }
826
1034
 
@@ -858,7 +1066,9 @@ export class ExprsEmitter {
858
1066
  this.pushInstruction("i32.add");
859
1067
  this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
860
1068
 
861
- if (sInfo) {
1069
+ if (isValArray) {
1070
+ this.emitCopyArray(elemGoType);
1071
+ } else if (sInfo) {
862
1072
  this.emitCloneStruct(sInfo, arrInfo.elemWType);
863
1073
  }
864
1074
 
@@ -931,53 +1141,95 @@ export class ExprsEmitter {
931
1141
  return this.toWasmType(expr._type) === "i32";
932
1142
  }
933
1143
 
1144
+ // Panics with the generic out-of-range message (shared buffers, SIMD
1145
+ // loads) when the condition on the stack is true.
934
1146
  _emitBoundsBranch() {
935
- const oobDepth = this.resolveBranchDepthToOob();
936
- if (oobDepth !== null) {
937
- this.pushInstruction({ op: "br_if", depth: oobDepth });
938
- } else {
939
- this.pushInstruction({ op: "if", blockType: "void" });
940
- this.pushInstruction({
941
- op: "call",
942
- funcIndex: this.mod.getBoundsPanicFuncIndex(),
943
- });
944
- this.pushInstruction("unreachable");
945
- this.pushInstruction("end");
946
- }
1147
+ this.pushInstruction({ op: "if", blockType: "void" });
1148
+ this.pushInstruction({
1149
+ op: "call",
1150
+ funcIndex: this.mod.getBoundsPanicFuncIndex(),
1151
+ });
1152
+ this.pushInstruction("unreachable");
1153
+ this.pushInstruction("end");
1154
+ }
1155
+
1156
+ // If the condition on the stack is true, panics with Go's index message;
1157
+ // `pushIdx64` and `emitLen` push the i64 index and the i32 length.
1158
+ _emitIndexPanicIf(pushIdx64, emitLen) {
1159
+ this.pushInstruction({ op: "if", blockType: "void" });
1160
+ pushIdx64();
1161
+ emitLen();
1162
+ this.pushInstruction({
1163
+ op: "call",
1164
+ funcIndex: this.mod.getIndexPanicFuncIndex(),
1165
+ });
1166
+ this.pushInstruction("unreachable");
1167
+ this.pushInstruction("end");
1168
+ }
1169
+
1170
+ // Registers an index-check site and pushes its id: a failing check
1171
+ // carries the index and this id to the handler, which re-emits `emitLen`
1172
+ // for that site (its temps still hold the operand at the branch).
1173
+ _pushOobSite(emitLen) {
1174
+ this.oobSites ??= [];
1175
+ this.oobSites.push(emitLen);
1176
+ this.pushInstruction({ op: "i32.const", value: this.oobSites.length - 1 });
947
1177
  }
948
1178
 
949
1179
  // Pushes the bounds-checked i32 index onto the stack. `emitLen` must push
950
- // the i32 length. Constant and plain-local indexes are re-emitted instead
951
- // of spilled; everything else goes through one temp.
1180
+ // the i32 length. Inside a function body a failing check branches to the
1181
+ // shared handler (`_closeOobBlocks`) with the index and a site id, and the
1182
+ // handler reports Go's message; elsewhere the check panics inline.
952
1183
  _emitCheckedIndex(indexNode, emitLen) {
953
- const simple =
954
- (indexNode.kind === "BasicLit" && indexNode.litKind === "INT") ||
955
- (indexNode.kind === "Ident" && this.resolveLocal(indexNode.name));
1184
+ const depth32 = this.resolveBranchDepthToOob(32);
956
1185
  if (this._isI32IndexExpr(indexNode)) {
1186
+ const idx = this.acquireTemp("i32");
957
1187
  this.emitExpr(indexNode, "i32");
958
- let tmp = null;
959
- if (simple) {
960
- this.emitExpr(indexNode, "i32");
1188
+ this.pushInstruction({ op: "local.tee", index: idx });
1189
+ if (depth32 !== null) {
1190
+ this._pushOobSite(emitLen);
1191
+ this.pushInstruction({ op: "local.get", index: idx });
1192
+ emitLen();
1193
+ this.pushInstruction("i32.ge_u");
1194
+ this.pushInstruction({ op: "br_if", depth: depth32 });
1195
+ this.pushInstruction("drop");
961
1196
  } else {
962
- tmp = this.acquireTemp("i32");
963
- this.pushInstruction({ op: "local.tee", index: tmp });
964
- this.pushInstruction({ op: "local.get", index: tmp });
1197
+ this.pushInstruction({ op: "local.get", index: idx });
1198
+ emitLen();
1199
+ this.pushInstruction("i32.ge_u");
1200
+ this._emitIndexPanicIf(() => {
1201
+ this.pushInstruction({ op: "local.get", index: idx });
1202
+ this.pushInstruction("i64.extend_i32_s");
1203
+ }, emitLen);
965
1204
  }
966
- emitLen();
967
- this.pushInstruction("i32.ge_u");
968
- this._emitBoundsBranch();
969
- if (tmp !== null) this.releaseTemp(tmp, "i32");
1205
+ this.releaseTemp(idx, "i32");
970
1206
  return;
971
1207
  }
972
1208
 
973
1209
  const idx64 = this.acquireTemp("i64");
974
1210
  this.emitExpr(indexNode, "i64");
975
1211
  this.pushInstruction({ op: "local.tee", index: idx64 });
976
- emitLen();
977
- this.pushInstruction("i64.extend_i32_u");
978
- this.pushInstruction("i64.ge_u");
979
- this._emitBoundsBranch();
980
- this.pushInstruction({ op: "local.get", index: idx64 });
1212
+ if (depth32 !== null) {
1213
+ this._pushOobSite(emitLen);
1214
+ this.pushInstruction({ op: "local.get", index: idx64 });
1215
+ emitLen();
1216
+ this.pushInstruction("i64.extend_i32_u");
1217
+ this.pushInstruction("i64.ge_u");
1218
+ this.pushInstruction({
1219
+ op: "br_if",
1220
+ depth: this.resolveBranchDepthToOob(64),
1221
+ });
1222
+ this.pushInstruction("drop");
1223
+ } else {
1224
+ emitLen();
1225
+ this.pushInstruction("i64.extend_i32_u");
1226
+ this.pushInstruction("i64.ge_u");
1227
+ this._emitIndexPanicIf(
1228
+ () => this.pushInstruction({ op: "local.get", index: idx64 }),
1229
+ emitLen,
1230
+ );
1231
+ this.pushInstruction({ op: "local.get", index: idx64 });
1232
+ }
981
1233
  this.pushInstruction("i32.wrap_i64");
982
1234
  this.releaseTemp(idx64, "i64");
983
1235
  }
@@ -1075,7 +1327,83 @@ export class ExprsEmitter {
1075
1327
  }
1076
1328
  }
1077
1329
 
1330
+ // Integer conversions of constants (`int64(1e18 + 1)`) keep the exact value.
1331
+ _emitFoldedIntConversion(expr, targetWasmType) {
1332
+ if (expr.kind !== "TypeConversion" && expr.kind !== "CallExpr")
1333
+ return false;
1334
+ if (expr._const?.k !== "INT") return false;
1335
+ if (
1336
+ targetWasmType != null &&
1337
+ targetWasmType !== "i32" &&
1338
+ targetWasmType !== "i64"
1339
+ )
1340
+ return false;
1341
+ const lit = constToLit(expr._const);
1342
+ lit._type = expr._type;
1343
+ this.emitBasicLit(lit, targetWasmType);
1344
+ return true;
1345
+ }
1346
+
1347
+ // A float literal; an integral one in an integer context (`var y int = 2.0`)
1348
+ // emits its exact integer value.
1349
+ _emitFloatLit(lit, wType) {
1350
+ const i =
1351
+ wType === "i32" || wType === "i64"
1352
+ ? constIntValue(constFromLit(lit) ?? {})
1353
+ : null;
1354
+ if (i !== null) {
1355
+ this.pushInstruction(
1356
+ wType === "i32"
1357
+ ? { op: "i32.const", value: Number(BigInt.asIntN(32, i)) }
1358
+ : { op: "i64.const", value: BigInt.asIntN(64, i) },
1359
+ );
1360
+ return;
1361
+ }
1362
+ const n = Number(lit.value);
1363
+ if (wType === "f32") {
1364
+ this.pushInstruction({ op: "f32.const", value: n });
1365
+ } else {
1366
+ this.pushInstruction({ op: "f64.const", value: n });
1367
+ }
1368
+ }
1369
+
1370
+ // "complex64" | "complex128" when `w` is the struct type of a complex value.
1371
+ _complexKindOfWType(w) {
1372
+ if (typeof w?.typeIndex !== "number") return null;
1373
+ for (const [kind, info] of this.mod.complexTypes) {
1374
+ if (info.typeIndex === w.typeIndex) return kind;
1375
+ }
1376
+ return null;
1377
+ }
1378
+
1078
1379
  emitBasicLit(lit, targetWasmType) {
1380
+ const targetComplex = this._complexKindOfWType(targetWasmType);
1381
+ const isTargetComplex64 =
1382
+ lit._type?.name === "complex64" ||
1383
+ targetWasmType?.typeName === "$complex64" ||
1384
+ targetComplex === "complex64";
1385
+ const isTargetComplex128 =
1386
+ lit._type?.name === "complex128" ||
1387
+ lit._type?.base === "complex128" ||
1388
+ targetWasmType?.typeName === "$complex128" ||
1389
+ targetComplex === "complex128";
1390
+ if (isTargetComplex64 || isTargetComplex128) {
1391
+ const info = this.mod.getComplexType(
1392
+ isTargetComplex64 ? "complex64" : "complex128",
1393
+ );
1394
+ const s = String(lit.value);
1395
+ const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
1396
+ const n = Number(clean);
1397
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: n });
1398
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
1399
+ this.pushInstruction({
1400
+ op: "struct.new",
1401
+ typeIndex: info.typeIndex,
1402
+ typeName: info.name,
1403
+ });
1404
+ return;
1405
+ }
1406
+
1079
1407
  const wType =
1080
1408
  targetWasmType ??
1081
1409
  toWasmType(lit._type, this.mod.checker) ??
@@ -1097,12 +1425,7 @@ export class ExprsEmitter {
1097
1425
  }
1098
1426
 
1099
1427
  if (lit.litKind === "FLOAT") {
1100
- const n = Number(lit.value);
1101
- if (wType === "f32") {
1102
- this.pushInstruction({ op: "f32.const", value: n });
1103
- } else {
1104
- this.pushInstruction({ op: "f64.const", value: n });
1105
- }
1428
+ this._emitFloatLit(lit, wType);
1106
1429
  return;
1107
1430
  }
1108
1431
 
@@ -1130,6 +1453,22 @@ export class ExprsEmitter {
1130
1453
  }
1131
1454
  }
1132
1455
 
1456
+ emitImagLit(lit, targetWasmType) {
1457
+ const is64 =
1458
+ lit._type?.name === "complex64" ||
1459
+ targetWasmType?.typeName === "$complex64";
1460
+ const complexName = is64 ? "complex64" : "complex128";
1461
+ const info = this.mod.getComplexType(complexName);
1462
+ const imVal = Number(lit.value);
1463
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: 0.0 });
1464
+ this.pushInstruction({ op: `${info.elemWType}.const`, value: imVal });
1465
+ this.pushInstruction({
1466
+ op: "struct.new",
1467
+ typeIndex: info.typeIndex,
1468
+ typeName: info.name,
1469
+ });
1470
+ }
1471
+
1133
1472
  _emitNilValue(goType, targetWasmType) {
1134
1473
  const effectiveType = targetWasmType ?? this.toWasmType(goType);
1135
1474
  if (
@@ -1253,6 +1592,11 @@ export class ExprsEmitter {
1253
1592
  emitUnaryExpr(expr, targetWasmType = null) {
1254
1593
  const { op, operand } = expr;
1255
1594
 
1595
+ if (isComplex(expr._type) || isComplex(operand._type)) {
1596
+ this._emitComplexUnary(expr, op, operand, targetWasmType);
1597
+ return;
1598
+ }
1599
+
1256
1600
  // Optimize negative numeric literals directly
1257
1601
  if (op === "-" && operand.kind === "BasicLit") {
1258
1602
  if (operand.litKind === "INT" || operand.litKind === "FLOAT") {
@@ -1310,102 +1654,480 @@ export class ExprsEmitter {
1310
1654
  }
1311
1655
  break;
1312
1656
 
1313
- case "&": {
1314
- if (operand.kind === "Ident") {
1315
- const loc = this.resolveLocal(operand.name);
1316
- if (loc?.isBoxed) {
1317
- this.pushInstruction({ op: "local.get", index: loc.index });
1318
- return;
1319
- }
1320
- }
1321
- if (operand.kind === "CompositeLit") {
1322
- this.emitExpr(operand);
1323
- return;
1324
- }
1325
- const structInfo = this._resolveStructInfo(operand);
1326
- if (structInfo) {
1327
- this.emitExpr(operand);
1328
- return;
1329
- }
1330
- const opWType = this.toWasmType(operand._type);
1331
- const box = this.mod.getBoxType(operand._type);
1332
- this.emitExpr(operand, opWType);
1333
- this.pushInstruction({ op: "struct.new", typeIndex: box.typeIndex });
1657
+ case "&":
1658
+ return this._emitAddressOf(operand);
1659
+
1660
+ case "*":
1661
+ return this._emitDerefExpr(operand);
1662
+
1663
+ default:
1664
+ throw new Error(`Unsupported unary operator: ${op}`);
1665
+ }
1666
+ }
1667
+
1668
+ _emitComplexUnary(expr, op, operand, targetWasmType) {
1669
+ if (op === "+") {
1670
+ this.emitExpr(operand, targetWasmType);
1671
+ return;
1672
+ }
1673
+ if (op === "-") {
1674
+ if (operand.kind === "ImagLit") {
1675
+ this.emitImagLit(
1676
+ { ...operand, value: `-${operand.value}` },
1677
+ targetWasmType,
1678
+ );
1334
1679
  return;
1335
1680
  }
1681
+ const is64 =
1682
+ isComplex64(expr._type) ||
1683
+ isComplex64(operand._type) ||
1684
+ targetWasmType?.typeName === "$complex64";
1685
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
1686
+ const cWType = {
1687
+ kind: "ref",
1688
+ nullable: true,
1689
+ typeIndex: info.typeIndex,
1690
+ typeName: info.name,
1691
+ };
1692
+ const opTmp = this.acquireTemp(cWType);
1693
+ this.emitExpr(operand, cWType);
1694
+ this.pushInstruction({ op: "local.set", index: opTmp });
1336
1695
 
1337
- case "*": {
1338
- const structInfo = this._resolveStructInfo(operand);
1339
- if (structInfo) {
1340
- const baseWType = this.toWasmType(operand._type);
1341
- this.emitExpr(operand, baseWType);
1342
- this.emitCloneStruct(structInfo, baseWType);
1343
- return;
1344
- }
1345
- const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
1346
- const baseWType = {
1347
- kind: "ref",
1348
- nullable: true,
1349
- typeIndex: box.typeIndex,
1350
- };
1351
- this.emitExpr(operand, baseWType);
1696
+ for (let i = 0; i < 2; i++) {
1697
+ this.pushInstruction({ op: "local.get", index: opTmp });
1352
1698
  this.pushInstruction({
1353
1699
  op: "struct.get",
1354
- typeIndex: box.typeIndex,
1355
- fieldIndex: 0,
1700
+ typeIndex: info.typeIndex,
1701
+ typeName: info.name,
1702
+ fieldIndex: i,
1356
1703
  });
1357
- return;
1704
+ this.pushInstruction(`${info.elemWType}.neg`);
1358
1705
  }
1359
1706
 
1360
- default:
1361
- throw new Error(`Unsupported unary operator: ${op}`);
1707
+ this.pushInstruction({
1708
+ op: "struct.new",
1709
+ typeIndex: info.typeIndex,
1710
+ typeName: info.name,
1711
+ });
1712
+ this.releaseTemp(opTmp, cWType);
1362
1713
  }
1363
1714
  }
1364
1715
 
1365
- emitBinaryExpr(expr, targetWasmType = null) {
1366
- const { op, left, right } = expr;
1367
-
1368
- // Short-circuiting logical operators
1369
- if (op === "&&") {
1370
- this.emitExpr(left, "i32");
1371
- this.pushInstruction({ op: "if", blockType: "i32" });
1372
- this.emitExpr(right, "i32");
1373
- this.pushInstruction("else");
1374
- this.pushInstruction({ op: "i32.const", value: 0 });
1375
- this.pushInstruction("end");
1716
+ _emitAddressOf(operand) {
1717
+ if (operand.kind === "Ident") {
1718
+ const loc = this.resolveLocal(operand.name);
1719
+ if (loc?.isBoxed) {
1720
+ this.pushInstruction({ op: "local.get", index: loc.index });
1721
+ // A pointer to an array or struct is its reference itself.
1722
+ const t = loc.goType;
1723
+ if (
1724
+ this._isValueArray(t) ||
1725
+ (t?.kind !== "pointer" &&
1726
+ isStructType(t, this.mod.checker, this.mod) &&
1727
+ !isPointerToStruct(t, this.mod.checker, this.mod))
1728
+ )
1729
+ this.pushInstruction({
1730
+ op: "struct.get",
1731
+ typeIndex: loc.boxInfo.typeIndex,
1732
+ fieldIndex: 0,
1733
+ });
1734
+ return;
1735
+ }
1736
+ }
1737
+ if (operand.kind === "CompositeLit") {
1738
+ if (isStructType(operand._type, this.mod.checker, this.mod)) {
1739
+ this.emitExpr(operand);
1740
+ return;
1741
+ }
1742
+ }
1743
+ const structInfo = this._resolveStructInfo(operand);
1744
+ if (structInfo) {
1745
+ this.emitExpr(operand);
1376
1746
  return;
1377
1747
  }
1378
-
1379
- if (op === "||") {
1380
- this.emitExpr(left, "i32");
1381
- this.pushInstruction({ op: "if", blockType: "i32" });
1382
- this.pushInstruction({ op: "i32.const", value: 1 });
1383
- this.pushInstruction("else");
1384
- this.emitExpr(right, "i32");
1385
- this.pushInstruction("end");
1748
+ const opWType = this.toWasmType(operand._type);
1749
+ if (!isScalarType(operand._type, this.mod.checker, this.mod)) {
1750
+ this.emitExpr(operand, opWType);
1386
1751
  return;
1387
1752
  }
1753
+ const box = this.mod.getBoxType(operand._type);
1754
+ this.emitExpr(operand, opWType);
1755
+ this.pushInstruction({
1756
+ op: "struct.new",
1757
+ typeIndex: box.typeIndex,
1758
+ typeName: box.name,
1759
+ });
1760
+ }
1388
1761
 
1389
- // Division & Modulo with zero-guard and overflow-guard
1390
- if (op === "/" || op === "%") {
1391
- this.emitDivRem(op, left, right, targetWasmType);
1762
+ _isPointerGoType(t) {
1763
+ let x = t;
1764
+ if (x?.kind === "named" && x.underlying) x = x.underlying;
1765
+ return x?.kind === "pointer" || x?.kind === "PointerType";
1766
+ }
1767
+
1768
+ _ptrBaseType(t) {
1769
+ let x = t;
1770
+ if (x?.kind === "named" && x.underlying) x = x.underlying;
1771
+ return x?.kind === "pointer" ? x.base : null;
1772
+ }
1773
+
1774
+ // The struct a `*operand` reads or writes, or null when the operand points
1775
+ // at something else: `*pp` with `pp **T` loads a pointer, not a struct.
1776
+ _derefStructInfo(operand) {
1777
+ if (this._isPointerGoType(this._ptrBaseType(operand._type))) return null;
1778
+ return this._resolveStructInfo(operand);
1779
+ }
1780
+
1781
+ _emitDerefExpr(operand) {
1782
+ const structInfo = this._derefStructInfo(operand);
1783
+ if (structInfo) {
1784
+ const baseWType = this.toWasmType(operand._type);
1785
+ this.emitExpr(operand, baseWType);
1786
+ this._emitNilCheck(operand._type);
1787
+ this.emitCloneStruct(structInfo, baseWType);
1392
1788
  return;
1393
1789
  }
1394
-
1395
- // Shifts with Go semantics
1396
- if (op === "<<" || op === ">>") {
1397
- this.emitShift(op, left, right, targetWasmType);
1790
+ const operandGoType =
1791
+ operand._type ??
1792
+ (operand.kind === "Ident"
1793
+ ? this.resolveLocal(operand.name)?.goType
1794
+ : null) ??
1795
+ this._resolveExprGoType(operand);
1796
+ const baseGoType = operandGoType?.base ?? operandGoType;
1797
+ if (!isScalarType(baseGoType, this.mod.checker, this.mod)) {
1798
+ const baseWType = this.toWasmType(baseGoType);
1799
+ this.emitExpr(operand, baseWType);
1398
1800
  return;
1399
1801
  }
1802
+ const box = this.mod.getBoxType(baseGoType);
1803
+ const ptr = this.mod.getPtrType(baseGoType);
1804
+ const ptrWType = {
1805
+ kind: "ref",
1806
+ nullable: true,
1807
+ typeIndex: ptr.typeIndex,
1808
+ typeName: ptr.name,
1809
+ };
1810
+ this.emitExpr(operand, ptrWType);
1811
+ this._emitNilCheck(operandGoType);
1812
+ this.pushInstruction({
1813
+ op: "ref.cast",
1814
+ typeIndex: box.typeIndex,
1815
+ typeName: box.name,
1816
+ });
1817
+ this.pushInstruction({
1818
+ op: "struct.get",
1819
+ typeIndex: box.typeIndex,
1820
+ typeName: box.name,
1821
+ fieldIndex: 0,
1822
+ });
1823
+ }
1400
1824
 
1401
- // Standard binary operators
1402
- const isCmp =
1403
- op === "==" ||
1404
- op === "!=" ||
1405
- op === "<" ||
1406
- op === "<=" ||
1407
- op === ">" ||
1408
- op === ">=";
1825
+ _emitComplexEq(aTmp, bTmp, info, f, op) {
1826
+ for (let i = 0; i < 2; i++) {
1827
+ this.pushInstruction({ op: "local.get", index: aTmp });
1828
+ this.pushInstruction({
1829
+ op: "struct.get",
1830
+ typeIndex: info.typeIndex,
1831
+ typeName: info.name,
1832
+ fieldIndex: i,
1833
+ });
1834
+ this.pushInstruction({ op: "local.get", index: bTmp });
1835
+ this.pushInstruction({
1836
+ op: "struct.get",
1837
+ typeIndex: info.typeIndex,
1838
+ typeName: info.name,
1839
+ fieldIndex: i,
1840
+ });
1841
+ this.pushInstruction(`${f}.eq`);
1842
+ }
1843
+ this.pushInstruction("i32.and");
1844
+ if (op === "!=") this.pushInstruction("i32.eqz");
1845
+ }
1846
+
1847
+ _emitComplexAddSub(aTmp, bTmp, info, f, op) {
1848
+ const wasmOp = op === "+" ? `${f}.add` : `${f}.sub`;
1849
+ for (let i = 0; i < 2; i++) {
1850
+ this.pushInstruction({ op: "local.get", index: aTmp });
1851
+ this.pushInstruction({
1852
+ op: "struct.get",
1853
+ typeIndex: info.typeIndex,
1854
+ typeName: info.name,
1855
+ fieldIndex: i,
1856
+ });
1857
+ this.pushInstruction({ op: "local.get", index: bTmp });
1858
+ this.pushInstruction({
1859
+ op: "struct.get",
1860
+ typeIndex: info.typeIndex,
1861
+ typeName: info.name,
1862
+ fieldIndex: i,
1863
+ });
1864
+ this.pushInstruction(wasmOp);
1865
+ }
1866
+ this.pushInstruction({
1867
+ op: "struct.new",
1868
+ typeIndex: info.typeIndex,
1869
+ typeName: info.name,
1870
+ });
1871
+ }
1872
+
1873
+ _emitComplexMul(aTmp, bTmp, info, f) {
1874
+ this.pushInstruction({ op: "local.get", index: aTmp });
1875
+ this.pushInstruction({
1876
+ op: "struct.get",
1877
+ typeIndex: info.typeIndex,
1878
+ typeName: info.name,
1879
+ fieldIndex: 0,
1880
+ });
1881
+ this.pushInstruction({ op: "local.get", index: bTmp });
1882
+ this.pushInstruction({
1883
+ op: "struct.get",
1884
+ typeIndex: info.typeIndex,
1885
+ typeName: info.name,
1886
+ fieldIndex: 0,
1887
+ });
1888
+ this.pushInstruction(`${f}.mul`);
1889
+ this.pushInstruction({ op: "local.get", index: aTmp });
1890
+ this.pushInstruction({
1891
+ op: "struct.get",
1892
+ typeIndex: info.typeIndex,
1893
+ typeName: info.name,
1894
+ fieldIndex: 1,
1895
+ });
1896
+ this.pushInstruction({ op: "local.get", index: bTmp });
1897
+ this.pushInstruction({
1898
+ op: "struct.get",
1899
+ typeIndex: info.typeIndex,
1900
+ typeName: info.name,
1901
+ fieldIndex: 1,
1902
+ });
1903
+ this.pushInstruction(`${f}.mul`);
1904
+ this.pushInstruction(`${f}.sub`);
1905
+
1906
+ this.pushInstruction({ op: "local.get", index: aTmp });
1907
+ this.pushInstruction({
1908
+ op: "struct.get",
1909
+ typeIndex: info.typeIndex,
1910
+ typeName: info.name,
1911
+ fieldIndex: 0,
1912
+ });
1913
+ this.pushInstruction({ op: "local.get", index: bTmp });
1914
+ this.pushInstruction({
1915
+ op: "struct.get",
1916
+ typeIndex: info.typeIndex,
1917
+ typeName: info.name,
1918
+ fieldIndex: 1,
1919
+ });
1920
+ this.pushInstruction(`${f}.mul`);
1921
+ this.pushInstruction({ op: "local.get", index: aTmp });
1922
+ this.pushInstruction({
1923
+ op: "struct.get",
1924
+ typeIndex: info.typeIndex,
1925
+ typeName: info.name,
1926
+ fieldIndex: 1,
1927
+ });
1928
+ this.pushInstruction({ op: "local.get", index: bTmp });
1929
+ this.pushInstruction({
1930
+ op: "struct.get",
1931
+ typeIndex: info.typeIndex,
1932
+ typeName: info.name,
1933
+ fieldIndex: 0,
1934
+ });
1935
+ this.pushInstruction(`${f}.mul`);
1936
+ this.pushInstruction(`${f}.add`);
1937
+
1938
+ this.pushInstruction({
1939
+ op: "struct.new",
1940
+ typeIndex: info.typeIndex,
1941
+ typeName: info.name,
1942
+ });
1943
+ }
1944
+
1945
+ _emitComplexDiv(aTmp, bTmp, info, f) {
1946
+ const dTmp = this.acquireTemp(f);
1947
+ for (let i = 0; i < 2; i++) {
1948
+ this.pushInstruction({ op: "local.get", index: bTmp });
1949
+ this.pushInstruction({
1950
+ op: "struct.get",
1951
+ typeIndex: info.typeIndex,
1952
+ typeName: info.name,
1953
+ fieldIndex: i,
1954
+ });
1955
+ this.pushInstruction({ op: "local.get", index: bTmp });
1956
+ this.pushInstruction({
1957
+ op: "struct.get",
1958
+ typeIndex: info.typeIndex,
1959
+ typeName: info.name,
1960
+ fieldIndex: i,
1961
+ });
1962
+ this.pushInstruction(`${f}.mul`);
1963
+ if (i > 0) this.pushInstruction(`${f}.add`);
1964
+ }
1965
+ this.pushInstruction({ op: "local.set", index: dTmp });
1966
+
1967
+ this.pushInstruction({ op: "local.get", index: aTmp });
1968
+ this.pushInstruction({
1969
+ op: "struct.get",
1970
+ typeIndex: info.typeIndex,
1971
+ typeName: info.name,
1972
+ fieldIndex: 0,
1973
+ });
1974
+ this.pushInstruction({ op: "local.get", index: bTmp });
1975
+ this.pushInstruction({
1976
+ op: "struct.get",
1977
+ typeIndex: info.typeIndex,
1978
+ typeName: info.name,
1979
+ fieldIndex: 0,
1980
+ });
1981
+ this.pushInstruction(`${f}.mul`);
1982
+ this.pushInstruction({ op: "local.get", index: aTmp });
1983
+ this.pushInstruction({
1984
+ op: "struct.get",
1985
+ typeIndex: info.typeIndex,
1986
+ typeName: info.name,
1987
+ fieldIndex: 1,
1988
+ });
1989
+ this.pushInstruction({ op: "local.get", index: bTmp });
1990
+ this.pushInstruction({
1991
+ op: "struct.get",
1992
+ typeIndex: info.typeIndex,
1993
+ typeName: info.name,
1994
+ fieldIndex: 1,
1995
+ });
1996
+ this.pushInstruction(`${f}.mul`);
1997
+ this.pushInstruction(`${f}.add`);
1998
+ this.pushInstruction({ op: "local.get", index: dTmp });
1999
+ this.pushInstruction(`${f}.div`);
2000
+
2001
+ this.pushInstruction({ op: "local.get", index: aTmp });
2002
+ this.pushInstruction({
2003
+ op: "struct.get",
2004
+ typeIndex: info.typeIndex,
2005
+ typeName: info.name,
2006
+ fieldIndex: 1,
2007
+ });
2008
+ this.pushInstruction({ op: "local.get", index: bTmp });
2009
+ this.pushInstruction({
2010
+ op: "struct.get",
2011
+ typeIndex: info.typeIndex,
2012
+ typeName: info.name,
2013
+ fieldIndex: 0,
2014
+ });
2015
+ this.pushInstruction(`${f}.mul`);
2016
+ this.pushInstruction({ op: "local.get", index: aTmp });
2017
+ this.pushInstruction({
2018
+ op: "struct.get",
2019
+ typeIndex: info.typeIndex,
2020
+ typeName: info.name,
2021
+ fieldIndex: 0,
2022
+ });
2023
+ this.pushInstruction({ op: "local.get", index: bTmp });
2024
+ this.pushInstruction({
2025
+ op: "struct.get",
2026
+ typeIndex: info.typeIndex,
2027
+ typeName: info.name,
2028
+ fieldIndex: 1,
2029
+ });
2030
+ this.pushInstruction(`${f}.mul`);
2031
+ this.pushInstruction(`${f}.sub`);
2032
+ this.pushInstruction({ op: "local.get", index: dTmp });
2033
+ this.pushInstruction(`${f}.div`);
2034
+
2035
+ this.pushInstruction({
2036
+ op: "struct.new",
2037
+ typeIndex: info.typeIndex,
2038
+ typeName: info.name,
2039
+ });
2040
+ this.releaseTemp(dTmp, f);
2041
+ }
2042
+
2043
+ emitComplexBinary(expr, targetWasmType) {
2044
+ const { op, left, right } = expr;
2045
+ const is64 =
2046
+ isComplex64(expr._type) ||
2047
+ isComplex64(left._type) ||
2048
+ isComplex64(right._type) ||
2049
+ targetWasmType?.typeName === "$complex64";
2050
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
2051
+ const f = info.elemWType;
2052
+ const cWType = {
2053
+ kind: "ref",
2054
+ nullable: true,
2055
+ typeIndex: info.typeIndex,
2056
+ typeName: info.name,
2057
+ };
2058
+ const aTmp = this.acquireTemp(cWType);
2059
+ const bTmp = this.acquireTemp(cWType);
2060
+ this.emitExpr(left, cWType);
2061
+ this.pushInstruction({ op: "local.set", index: aTmp });
2062
+ this.emitExpr(right, cWType);
2063
+ this.pushInstruction({ op: "local.set", index: bTmp });
2064
+
2065
+ if (op === "==" || op === "!=") {
2066
+ this._emitComplexEq(aTmp, bTmp, info, f, op);
2067
+ } else if (op === "+" || op === "-") {
2068
+ this._emitComplexAddSub(aTmp, bTmp, info, f, op);
2069
+ } else if (op === "*") {
2070
+ this._emitComplexMul(aTmp, bTmp, info, f);
2071
+ } else if (op === "/") {
2072
+ this._emitComplexDiv(aTmp, bTmp, info, f);
2073
+ }
2074
+ this.releaseTemp(bTmp, cWType);
2075
+ this.releaseTemp(aTmp, cWType);
2076
+ }
2077
+
2078
+ emitBinaryExpr(expr, targetWasmType = null) {
2079
+ const { op, left, right } = expr;
2080
+
2081
+ if (
2082
+ isComplex(expr._type) ||
2083
+ isComplex(left._type) ||
2084
+ isComplex(right._type)
2085
+ ) {
2086
+ this.emitComplexBinary(expr, targetWasmType);
2087
+ return;
2088
+ }
2089
+
2090
+ // Short-circuiting logical operators
2091
+ if (op === "&&") {
2092
+ this.emitExpr(left, "i32");
2093
+ this.pushInstruction({ op: "if", blockType: "i32" });
2094
+ this.emitExpr(right, "i32");
2095
+ this.pushInstruction("else");
2096
+ this.pushInstruction({ op: "i32.const", value: 0 });
2097
+ this.pushInstruction("end");
2098
+ return;
2099
+ }
2100
+
2101
+ if (op === "||") {
2102
+ this.emitExpr(left, "i32");
2103
+ this.pushInstruction({ op: "if", blockType: "i32" });
2104
+ this.pushInstruction({ op: "i32.const", value: 1 });
2105
+ this.pushInstruction("else");
2106
+ this.emitExpr(right, "i32");
2107
+ this.pushInstruction("end");
2108
+ return;
2109
+ }
2110
+
2111
+ // Division & Modulo with zero-guard and overflow-guard
2112
+ if (op === "/" || op === "%") {
2113
+ this.emitDivRem(op, left, right, targetWasmType);
2114
+ return;
2115
+ }
2116
+
2117
+ // Shifts with Go semantics
2118
+ if (op === "<<" || op === ">>") {
2119
+ this.emitShift(op, left, right, targetWasmType);
2120
+ return;
2121
+ }
2122
+
2123
+ // Standard binary operators
2124
+ const isCmp =
2125
+ op === "==" ||
2126
+ op === "!=" ||
2127
+ op === "<" ||
2128
+ op === "<=" ||
2129
+ op === ">" ||
2130
+ op === ">=";
1409
2131
 
1410
2132
  const isNil = (n) =>
1411
2133
  (n.kind === "BasicLit" && n.litKind === "NIL") ||
@@ -1442,10 +2164,17 @@ export class ExprsEmitter {
1442
2164
  return;
1443
2165
  }
1444
2166
 
1445
- const leftType = left._type;
2167
+ // An untyped constant operand takes the type of the typed one
2168
+ // (`'a' < rune(b)`), so both sides share one wasm representation.
2169
+ const leftType =
2170
+ left._type?.kind === "untyped" &&
2171
+ right._type &&
2172
+ right._type.kind !== "untyped"
2173
+ ? right._type
2174
+ : left._type;
1446
2175
  const isLeftI32 = this._isI32IndexExpr(left);
1447
2176
  const isRightI32 = this._isI32IndexExpr(right);
1448
- const leftGoType = left._type ?? this._resolveExprGoType(left);
2177
+ const leftGoType = leftType ?? this._resolveExprGoType(left);
1449
2178
  const isIntType =
1450
2179
  !leftGoType ||
1451
2180
  (leftGoType.kind === "basic" &&
@@ -1486,6 +2215,72 @@ export class ExprsEmitter {
1486
2215
  this.emitBinaryOp(op, wType, leftType);
1487
2216
  }
1488
2217
 
2218
+ _findScalarPtrByWType(wType, map) {
2219
+ if (!map) return null;
2220
+ for (const info of map.values()) {
2221
+ if (info.typeIndex === wType.typeIndex || info.name === wType.typeName) {
2222
+ return info;
2223
+ }
2224
+ }
2225
+ return null;
2226
+ }
2227
+
2228
+ _getScalarPtrFromWType(wType) {
2229
+ if (!wType || typeof wType !== "object") return null;
2230
+ const ptr = this._findScalarPtrByWType(wType, this.mod.ptrTypes);
2231
+ if (ptr) {
2232
+ return {
2233
+ ptr,
2234
+ box: this.mod.getBoxType(ptr.goType),
2235
+ };
2236
+ }
2237
+ const box = this._findScalarPtrByWType(wType, this.mod.boxTypes);
2238
+ if (box) {
2239
+ return {
2240
+ ptr: this.mod.getPtrType(box.goType),
2241
+ box,
2242
+ };
2243
+ }
2244
+ return null;
2245
+ }
2246
+
2247
+ _getScalarPtrFromGoType(goType) {
2248
+ let g = goType;
2249
+ if (g?.kind === "TypeName" && this.mod.checker?.types?.has(g.name)) {
2250
+ g = this.mod.checker.types.get(g.name);
2251
+ }
2252
+ while (g?.kind === "named" && g.underlying) {
2253
+ g = g.underlying;
2254
+ }
2255
+ const isPtr =
2256
+ g?.kind === "pointer" ||
2257
+ g?.kind === "PointerType" ||
2258
+ g?.kind === "StarExpr";
2259
+ if (!isPtr || (!g.base && !g.elem)) return null;
2260
+ const base = g.base || g.elem;
2261
+ if (!isScalarType(base, this.mod.checker, this.mod)) {
2262
+ return null;
2263
+ }
2264
+ return {
2265
+ ptr: this.mod.getPtrType(base),
2266
+ box: this.mod.getBoxType(base),
2267
+ };
2268
+ }
2269
+
2270
+ _getScalarPtrInfo(wType, goType = null) {
2271
+ return (
2272
+ this._getScalarPtrFromGoType(goType) ?? this._getScalarPtrFromWType(wType)
2273
+ );
2274
+ }
2275
+
2276
+ _isScalarPtrType(goType, wType = null) {
2277
+ return this._getScalarPtrInfo(wType, goType) !== null;
2278
+ }
2279
+
2280
+ _emitScalarPtrEq(_ptrInfo) {
2281
+ this.pushInstruction("ref.eq");
2282
+ }
2283
+
1489
2284
  _emitStructEq(structInfo) {
1490
2285
  const bTmp = this.acquireTemp({
1491
2286
  kind: "ref",
@@ -1532,11 +2327,18 @@ export class ExprsEmitter {
1532
2327
  (f.goType?.kind === "named" ? f.goType.name : null),
1533
2328
  );
1534
2329
  this._emitStructEq(sInfo);
2330
+ } else if (this._isValueArray(f.goType)) {
2331
+ this._emitArrayEq(f.goType);
1535
2332
  } else if (isStringType(f.goType, this.mod.checker)) {
1536
2333
  const cmpIdx = this.mod.getStringCmpImportIndex("==");
1537
2334
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2335
+ } else if (f.wType === "v128") {
2336
+ this._emitSimdEq(f.goType);
1538
2337
  } else if (f.wType === "anyref") {
1539
2338
  this._emitAnyEq();
2339
+ } else if (this._isScalarPtrType(f.goType, f.wType)) {
2340
+ const ptrInfo = this._getScalarPtrInfo(f.wType, f.goType);
2341
+ this._emitScalarPtrEq(ptrInfo);
1540
2342
  } else if (typeof f.wType === "object" && f.wType !== null) {
1541
2343
  this.pushInstruction("ref.eq");
1542
2344
  } else {
@@ -1567,7 +2369,110 @@ export class ExprsEmitter {
1567
2369
  });
1568
2370
  }
1569
2371
 
2372
+ _isValueArray(goType) {
2373
+ return goType?.kind !== "pointer" && isArrayType(goType, this.mod.checker);
2374
+ }
2375
+
2376
+ // Go array equality: element-wise (arrays are values). Pops two arrays
2377
+ // and pushes an i32. A null array (a never-written slot) is the zero
2378
+ // array.
2379
+ _emitArrayEq(goType) {
2380
+ const elemType = this._getArrayElemType(goType);
2381
+ const arrInfo = this.mod.getArrayType(elemType);
2382
+ const arrWType = {
2383
+ kind: "ref",
2384
+ nullable: true,
2385
+ typeIndex: arrInfo.typeIndex,
2386
+ };
2387
+ const bTmp = this.acquireTemp(arrWType);
2388
+ const aTmp = this.acquireTemp(arrWType);
2389
+ for (const tmp of [bTmp, aTmp]) {
2390
+ this.pushInstruction({ op: "local.tee", index: tmp });
2391
+ this.pushInstruction("ref.is_null");
2392
+ this.pushInstruction({ op: "if", blockType: "void" });
2393
+ this.emitZeroValue(goType, arrWType);
2394
+ this.pushInstruction({ op: "local.set", index: tmp });
2395
+ this.pushInstruction("end");
2396
+ }
2397
+ const i = this.acquireTemp("i32");
2398
+ const resTmp = this.acquireTemp("i32");
2399
+ this.pushInstruction({ op: "i32.const", value: 1 });
2400
+ this.pushInstruction({ op: "local.set", index: resTmp });
2401
+ this.pushInstruction({ op: "i32.const", value: 0 });
2402
+ this.pushInstruction({ op: "local.set", index: i });
2403
+ this.pushInstruction({ op: "block", blockType: "void" });
2404
+ this.pushInstruction({ op: "loop", blockType: "void" });
2405
+ this.pushInstruction({ op: "local.get", index: i });
2406
+ this.pushInstruction({ op: "local.get", index: aTmp });
2407
+ this.pushInstruction("array.len");
2408
+ this.pushInstruction("i32.ge_u");
2409
+ this.pushInstruction({ op: "br_if", depth: 1 });
2410
+ for (const tmp of [aTmp, bTmp]) {
2411
+ this.pushInstruction({ op: "local.get", index: tmp });
2412
+ this.pushInstruction({ op: "local.get", index: i });
2413
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
2414
+ }
2415
+ this._emitElemEq(elemType, arrInfo.elemWType);
2416
+ this.pushInstruction("i32.eqz");
2417
+ this.pushInstruction({ op: "if", blockType: "void" });
2418
+ this.pushInstruction({ op: "i32.const", value: 0 });
2419
+ this.pushInstruction({ op: "local.set", index: resTmp });
2420
+ this.pushInstruction({ op: "br", depth: 2 });
2421
+ this.pushInstruction("end");
2422
+ this.pushInstruction({ op: "local.get", index: i });
2423
+ this.pushInstruction({ op: "i32.const", value: 1 });
2424
+ this.pushInstruction("i32.add");
2425
+ this.pushInstruction({ op: "local.set", index: i });
2426
+ this.pushInstruction({ op: "br", depth: 0 });
2427
+ this.pushInstruction("end");
2428
+ this.pushInstruction("end");
2429
+ this.pushInstruction({ op: "local.get", index: resTmp });
2430
+ this.releaseTemp(resTmp, "i32");
2431
+ this.releaseTemp(i, "i32");
2432
+ this.releaseTemp(aTmp, arrWType);
2433
+ this.releaseTemp(bTmp, arrWType);
2434
+ }
2435
+
2436
+ // Equality of two array elements on the stack -> i32.
2437
+ _emitElemEq(goType, wType) {
2438
+ if (this._isValueArray(goType)) {
2439
+ this._emitArrayEq(goType);
2440
+ } else if (
2441
+ isStructType(goType, this.mod.checker, this.mod) &&
2442
+ !isPointerToStruct(goType, this.mod.checker, this.mod)
2443
+ ) {
2444
+ this._emitStructEq(this._resolveStructInfo({ _type: goType }));
2445
+ } else {
2446
+ this.emitBinaryOp("==", wType, goType);
2447
+ }
2448
+ }
2449
+
1570
2450
  emitBinaryOp(op, wType, goType) {
2451
+ if ((op === "==" || op === "!=") && this._isValueArray(goType)) {
2452
+ this._emitArrayEq(goType);
2453
+ if (op === "!=") this.pushInstruction("i32.eqz");
2454
+ return;
2455
+ }
2456
+ if (isComplex(goType)) {
2457
+ const is64 = isComplex64(goType);
2458
+ const info = this.mod.getComplexType(is64 ? "complex64" : "complex128");
2459
+ const f = info.elemWType;
2460
+ const bTmp = this.acquireTemp(wType);
2461
+ const aTmp = this.acquireTemp(wType);
2462
+ this.pushInstruction({ op: "local.set", index: bTmp });
2463
+ this.pushInstruction({ op: "local.set", index: aTmp });
2464
+ if (op === "+" || op === "-") {
2465
+ this._emitComplexAddSub(aTmp, bTmp, info, f, op);
2466
+ } else if (op === "*") {
2467
+ this._emitComplexMul(aTmp, bTmp, info, f);
2468
+ } else if (op === "/") {
2469
+ this._emitComplexDiv(aTmp, bTmp, info, f);
2470
+ }
2471
+ this.releaseTemp(aTmp, wType);
2472
+ this.releaseTemp(bTmp, wType);
2473
+ return;
2474
+ }
2475
+
1571
2476
  const signed = isSigned(goType);
1572
2477
 
1573
2478
  switch (op) {
@@ -1635,8 +2540,13 @@ export class ExprsEmitter {
1635
2540
  if (isStringType(goType, this.mod.checker) || wType === "externref") {
1636
2541
  const cmpIdx = this.mod.getStringCmpImportIndex("==");
1637
2542
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2543
+ } else if (wType === "v128") {
2544
+ this._emitSimdEq(goType);
1638
2545
  } else if (wType === "anyref") {
1639
2546
  this._emitAnyEq();
2547
+ } else if (this._isScalarPtrType(goType, wType)) {
2548
+ const ptrInfo = this._getScalarPtrInfo(wType, goType);
2549
+ this._emitScalarPtrEq(ptrInfo);
1640
2550
  } else if (typeof wType === "object" && wType !== null) {
1641
2551
  this.pushInstruction("ref.eq");
1642
2552
  } else {
@@ -1647,9 +2557,16 @@ export class ExprsEmitter {
1647
2557
  if (isStringType(goType, this.mod.checker) || wType === "externref") {
1648
2558
  const cmpIdx = this.mod.getStringCmpImportIndex("!=");
1649
2559
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2560
+ } else if (wType === "v128") {
2561
+ this._emitSimdEq(goType);
2562
+ this.pushInstruction("i32.eqz");
1650
2563
  } else if (wType === "anyref") {
1651
2564
  this._emitAnyEq();
1652
2565
  this.pushInstruction("i32.eqz");
2566
+ } else if (this._isScalarPtrType(goType, wType)) {
2567
+ const ptrInfo = this._getScalarPtrInfo(wType, goType);
2568
+ this._emitScalarPtrEq(ptrInfo);
2569
+ this.pushInstruction("i32.eqz");
1653
2570
  } else if (typeof wType === "object" && wType !== null) {
1654
2571
  this.pushInstruction("ref.eq");
1655
2572
  this.pushInstruction("i32.eqz");
@@ -1837,6 +2754,21 @@ export class ExprsEmitter {
1837
2754
  this.pushInstruction({ op: "local.set", index: tmpVal });
1838
2755
 
1839
2756
  this.emitExpr(right, rightWType);
2757
+ // A negative signed count panics before the shift is evaluated.
2758
+ if (isSigned(right._type) && right.kind !== "BasicLit") {
2759
+ const tmpCount = this.acquireTemp(rightWType);
2760
+ this.pushInstruction({ op: "local.tee", index: tmpCount });
2761
+ this.pushInstruction({
2762
+ op: `${rightWType}.const`,
2763
+ value: rightWType === "i64" ? 0n : 0,
2764
+ });
2765
+ this.pushInstruction(`${rightWType}.lt_s`);
2766
+ this.pushInstruction({ op: "if", blockType: "void" });
2767
+ this.emitPanic("runtime error: negative shift amount");
2768
+ this.pushInstruction("end");
2769
+ this.pushInstruction({ op: "local.get", index: tmpCount });
2770
+ this.releaseTemp(tmpCount, rightWType);
2771
+ }
1840
2772
  if (wType === "i64" && rightWType === "i32") {
1841
2773
  this.pushInstruction("i64.extend_i32_u");
1842
2774
  } else if (wType === "i32" && rightWType === "i64") {
@@ -1879,32 +2811,91 @@ export class ExprsEmitter {
1879
2811
 
1880
2812
  emitTypeConversion(conv) {
1881
2813
  const { targetType, expr } = conv;
1882
- const fromType = expr._type;
1883
- const fromWType = toWasmType(fromType, this.mod.checker);
1884
- const toWType = toWasmType(targetType, this.mod.checker);
2814
+ const fromType = expr._type ?? this._resolveExprGoType(expr);
2815
+ const fromWType = this.toWasmType(fromType);
2816
+ const toWType = this.toWasmType(targetType);
2817
+
2818
+ // `[]T(nil)` and `(*T)(nil)`: the target type's nil, not a converted value.
2819
+ if (
2820
+ (expr.kind === "Ident" && expr.name === "nil") ||
2821
+ (expr.kind === "BasicLit" && expr.litKind === "NIL")
2822
+ ) {
2823
+ this._emitNilValue(targetType, toWType);
2824
+ return;
2825
+ }
2826
+
2827
+ // `any(x)` boxes x exactly like an assignment to an `any` variable.
2828
+ if (isAnyType(targetType, this.mod.checker)) {
2829
+ this.emitExpr(expr, "anyref");
2830
+ return;
2831
+ }
1885
2832
 
1886
2833
  this.emitExpr(expr, fromWType);
1887
2834
 
2835
+ if (isComplex(targetType)) {
2836
+ this._emitComplexConversion(targetType, fromType, fromWType);
2837
+ return;
2838
+ }
2839
+
2840
+ // Struct to a different defined struct type: a copy in the target's type.
2841
+ if (
2842
+ isStructType(fromType, this.mod.checker, this.mod) &&
2843
+ isStructType(targetType, this.mod.checker, this.mod) &&
2844
+ !isPointerToStruct(fromType, this.mod.checker, this.mod) &&
2845
+ !isPointerToStruct(targetType, this.mod.checker, this.mod)
2846
+ ) {
2847
+ const src = this._resolveStructInfo({ _type: fromType });
2848
+ const dst = this._resolveStructInfo({ _type: targetType });
2849
+ if (src && dst && src.typeIndex !== dst.typeIndex) {
2850
+ this.emitCloneStruct(src, fromWType, dst);
2851
+ return;
2852
+ }
2853
+ }
2854
+
1888
2855
  if (fromWType === toWType) {
1889
2856
  this.emitNarrowIntWrap(targetType);
1890
2857
  return;
1891
2858
  }
1892
2859
 
2860
+ // A named byte slice (`type MyBytes []byte`) converts like `[]byte`.
2861
+ const isByteElem = (t) => {
2862
+ const elem = this._getSliceElemType(t);
2863
+ return elem?.name === "byte" || elem?.name === "uint8";
2864
+ };
1893
2865
  if (
1894
2866
  fromWType === "externref" &&
1895
2867
  isSliceType(targetType, this.mod.checker) &&
1896
- (targetType.elem?.name === "byte" || targetType.elem?.name === "uint8")
2868
+ isByteElem(targetType)
1897
2869
  ) {
1898
- this._emitStringToByteSlice(targetType);
2870
+ this._emitStringToByteSlice({
2871
+ kind: "slice",
2872
+ elem: this._getSliceElemType(targetType),
2873
+ });
1899
2874
  return;
1900
2875
  }
1901
2876
 
1902
2877
  if (
1903
2878
  toWType === "externref" &&
1904
2879
  isSliceType(fromType, this.mod.checker) &&
1905
- (fromType.elem?.name === "byte" || fromType.elem?.name === "uint8")
2880
+ isByteElem(fromType)
1906
2881
  ) {
1907
- this._emitByteSliceToString(fromType);
2882
+ this._emitByteSliceToString({
2883
+ kind: "slice",
2884
+ elem: this._getSliceElemType(fromType),
2885
+ });
2886
+ return;
2887
+ }
2888
+
2889
+ if (
2890
+ fromWType === "externref" &&
2891
+ isRuneSlice(targetType, this.mod.checker)
2892
+ ) {
2893
+ this._emitStringToRuneSlice(targetType);
2894
+ return;
2895
+ }
2896
+
2897
+ if (toWType === "externref" && isRuneSlice(fromType, this.mod.checker)) {
2898
+ this._emitRuneSliceToString(fromType);
1908
2899
  return;
1909
2900
  }
1910
2901
 
@@ -1949,6 +2940,64 @@ export class ExprsEmitter {
1949
2940
  }
1950
2941
  }
1951
2942
 
2943
+ _emitComplexConversion(targetType, fromType, fromWType) {
2944
+ const isTarget64 = isComplex64(targetType);
2945
+ const targetInfo = this.mod.getComplexType(
2946
+ isTarget64 ? "complex64" : "complex128",
2947
+ );
2948
+ if (isComplex(fromType)) {
2949
+ const isFrom64 = isComplex64(fromType);
2950
+ if (isTarget64 === isFrom64) return;
2951
+ const fromInfo = this.mod.getComplexType(
2952
+ isFrom64 ? "complex64" : "complex128",
2953
+ );
2954
+ const fromWTypeRef = {
2955
+ kind: "ref",
2956
+ nullable: true,
2957
+ typeIndex: fromInfo.typeIndex,
2958
+ typeName: fromInfo.name,
2959
+ };
2960
+ const cTmp = this.acquireTemp(fromWTypeRef);
2961
+ this.pushInstruction({ op: "local.set", index: cTmp });
2962
+
2963
+ for (let i = 0; i < 2; i++) {
2964
+ this.pushInstruction({ op: "local.get", index: cTmp });
2965
+ this.pushInstruction({
2966
+ op: "struct.get",
2967
+ typeIndex: fromInfo.typeIndex,
2968
+ typeName: fromInfo.name,
2969
+ fieldIndex: i,
2970
+ });
2971
+ this.pushInstruction(isTarget64 ? "f32.demote_f64" : "f64.promote_f32");
2972
+ }
2973
+
2974
+ this.pushInstruction({
2975
+ op: "struct.new",
2976
+ typeIndex: targetInfo.typeIndex,
2977
+ typeName: targetInfo.name,
2978
+ });
2979
+ this.releaseTemp(cTmp, fromWTypeRef);
2980
+ return;
2981
+ }
2982
+
2983
+ // From numeric scalar
2984
+ const targetF = targetInfo.elemWType;
2985
+ const srcSign = isSigned(fromType) ? "s" : "u";
2986
+ if (fromWType === "i32" || fromWType === "i64") {
2987
+ this.pushInstruction(`${targetF}.convert_${fromWType}_${srcSign}`);
2988
+ } else if (fromWType === "f32" && targetF === "f64") {
2989
+ this.pushInstruction("f64.promote_f32");
2990
+ } else if (fromWType === "f64" && targetF === "f32") {
2991
+ this.pushInstruction("f32.demote_f64");
2992
+ }
2993
+ this.pushInstruction({ op: `${targetF}.const`, value: 0.0 });
2994
+ this.pushInstruction({
2995
+ op: "struct.new",
2996
+ typeIndex: targetInfo.typeIndex,
2997
+ typeName: targetInfo.name,
2998
+ });
2999
+ }
3000
+
1952
3001
  _emitStringToByteSlice(targetType) {
1953
3002
  const elemGo = targetType.elem;
1954
3003
  const sliceInfo = this.mod.getSliceType(elemGo);
@@ -2019,6 +3068,162 @@ export class ExprsEmitter {
2019
3068
  this.releaseTemp(strTmp, "externref");
2020
3069
  }
2021
3070
 
3071
+ // Pushes the code point at UTF-16 index `iTmp` of `strTmp` into `cpTmp`
3072
+ // and advances `iTmp` past it (two units for a surrogate pair).
3073
+ _emitNextCodePoint(strTmp, iTmp, cpTmp) {
3074
+ this.pushInstruction({ op: "local.get", index: strTmp });
3075
+ this.pushInstruction({ op: "local.get", index: iTmp });
3076
+ this.pushInstruction({
3077
+ op: "call",
3078
+ funcIndex: this.mod.getStringCodePointAtImportIndex(),
3079
+ });
3080
+ this.pushInstruction({ op: "local.set", index: cpTmp });
3081
+ this.pushInstruction({ op: "local.get", index: iTmp });
3082
+ this.pushInstruction({ op: "i32.const", value: 1 });
3083
+ this.pushInstruction("i32.add");
3084
+ this.pushInstruction({ op: "local.get", index: cpTmp });
3085
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
3086
+ this.pushInstruction("i32.gt_u");
3087
+ this.pushInstruction("i32.add");
3088
+ this.pushInstruction({ op: "local.set", index: iTmp });
3089
+ }
3090
+
3091
+ // Emits `block loop (br_if 1 (i >= n)) body br 0 end end`.
3092
+ _emitCountedLoop(iTmp, nTmp, body) {
3093
+ this.pushInstruction({ op: "block", blockType: "void" });
3094
+ this.pushInstruction({ op: "loop", blockType: "void" });
3095
+ this.pushInstruction({ op: "local.get", index: iTmp });
3096
+ this.pushInstruction({ op: "local.get", index: nTmp });
3097
+ this.pushInstruction("i32.ge_s");
3098
+ this.pushInstruction({ op: "br_if", depth: 1 });
3099
+ body();
3100
+ this.pushInstruction({ op: "br", depth: 0 });
3101
+ this.pushInstruction("end");
3102
+ this.pushInstruction("end");
3103
+ }
3104
+
3105
+ // []rune(s): one pass counts the code points, a second fills an exactly
3106
+ // sized array (cap == len, as for every slice GoFront creates).
3107
+ _emitStringToRuneSlice(targetType) {
3108
+ const sliceInfo = this.mod.getSliceType(this._getSliceElemType(targetType));
3109
+ const arrInfo = sliceInfo.arrInfo;
3110
+ const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
3111
+ const strTmp = this.acquireTemp("externref");
3112
+ const unitsTmp = this.acquireTemp("i32");
3113
+ const iTmp = this.acquireTemp("i32");
3114
+ const kTmp = this.acquireTemp("i32");
3115
+ const cpTmp = this.acquireTemp("i32");
3116
+ const arrTmp = this.acquireTemp(arrW);
3117
+ this.pushInstruction({ op: "local.tee", index: strTmp });
3118
+ this.pushInstruction({
3119
+ op: "call",
3120
+ funcIndex: this.mod.getStringLenImportIndex(),
3121
+ });
3122
+ this.pushInstruction({ op: "local.set", index: unitsTmp });
3123
+ const resetIndex = () => {
3124
+ this.pushInstruction({ op: "i32.const", value: 0 });
3125
+ this.pushInstruction({ op: "local.set", index: iTmp });
3126
+ this.pushInstruction({ op: "i32.const", value: 0 });
3127
+ this.pushInstruction({ op: "local.set", index: kTmp });
3128
+ };
3129
+ const bumpK = () => {
3130
+ this.pushInstruction({ op: "local.get", index: kTmp });
3131
+ this.pushInstruction({ op: "i32.const", value: 1 });
3132
+ this.pushInstruction("i32.add");
3133
+ this.pushInstruction({ op: "local.set", index: kTmp });
3134
+ };
3135
+ resetIndex();
3136
+ this._emitCountedLoop(iTmp, unitsTmp, () => {
3137
+ this._emitNextCodePoint(strTmp, iTmp, cpTmp);
3138
+ bumpK();
3139
+ });
3140
+ this.pushInstruction({ op: "local.get", index: kTmp });
3141
+ this.pushInstruction({
3142
+ op: "array.new_default",
3143
+ typeIndex: arrInfo.typeIndex,
3144
+ });
3145
+ this.pushInstruction({ op: "local.set", index: arrTmp });
3146
+ resetIndex();
3147
+ this._emitCountedLoop(iTmp, unitsTmp, () => {
3148
+ this._emitNextCodePoint(strTmp, iTmp, cpTmp);
3149
+ this.pushInstruction({ op: "local.get", index: arrTmp });
3150
+ this.pushInstruction({ op: "local.get", index: kTmp });
3151
+ this.pushInstruction({ op: "local.get", index: cpTmp });
3152
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
3153
+ bumpK();
3154
+ });
3155
+ this.pushInstruction({ op: "local.get", index: arrTmp });
3156
+ this.pushInstruction({ op: "i32.const", value: 0 });
3157
+ this.pushInstruction({ op: "local.get", index: kTmp });
3158
+ this.pushInstruction({ op: "local.get", index: kTmp });
3159
+ this.pushInstruction({ op: "struct.new", typeIndex: sliceInfo.typeIndex });
3160
+ this.releaseTemp(arrTmp, arrW);
3161
+ this.releaseTemp(cpTmp, "i32");
3162
+ this.releaseTemp(kTmp, "i32");
3163
+ this.releaseTemp(iTmp, "i32");
3164
+ this.releaseTemp(unitsTmp, "i32");
3165
+ this.releaseTemp(strTmp, "externref");
3166
+ }
3167
+
3168
+ // string(runes): concatenates `str_from_code_point` of each element.
3169
+ _emitRuneSliceToString(fromType) {
3170
+ const sliceInfo = this.mod.getSliceType(this._getSliceElemType(fromType));
3171
+ const arrInfo = sliceInfo.arrInfo;
3172
+ const sliceW = {
3173
+ kind: "ref",
3174
+ nullable: true,
3175
+ typeIndex: sliceInfo.typeIndex,
3176
+ };
3177
+ const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
3178
+ const sTmp = this.acquireTemp(sliceW);
3179
+ this.pushInstruction({ op: "local.set", index: sTmp });
3180
+ const arrTmp = this.acquireTemp(arrW);
3181
+ const offTmp = this.acquireTemp("i32");
3182
+ const lenTmp = this.acquireTemp("i32");
3183
+ const capTmp = this.acquireTemp("i32");
3184
+ this._emitSliceUnpack(sTmp, sliceInfo, {
3185
+ arr: arrTmp,
3186
+ off: offTmp,
3187
+ len: lenTmp,
3188
+ cap: capTmp,
3189
+ });
3190
+ this.releaseTemp(sTmp, sliceW);
3191
+ this.releaseTemp(capTmp, "i32");
3192
+ const accTmp = this.acquireTemp("externref");
3193
+ this.emitBasicLit({ kind: "BasicLit", litKind: "STRING", value: "" });
3194
+ this.pushInstruction({ op: "local.set", index: accTmp });
3195
+ const iTmp = this.acquireTemp("i32");
3196
+ this.pushInstruction({ op: "i32.const", value: 0 });
3197
+ this.pushInstruction({ op: "local.set", index: iTmp });
3198
+ this._emitCountedLoop(iTmp, lenTmp, () => {
3199
+ this.pushInstruction({ op: "local.get", index: accTmp });
3200
+ this.pushInstruction({ op: "local.get", index: arrTmp });
3201
+ this.pushInstruction({ op: "local.get", index: offTmp });
3202
+ this.pushInstruction({ op: "local.get", index: iTmp });
3203
+ this.pushInstruction("i32.add");
3204
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
3205
+ this.pushInstruction({
3206
+ op: "call",
3207
+ funcIndex: this.mod.getStringFromCodePointImportIndex(),
3208
+ });
3209
+ this.pushInstruction({
3210
+ op: "call",
3211
+ funcIndex: this.mod.getStringConcatImportIndex(),
3212
+ });
3213
+ this.pushInstruction({ op: "local.set", index: accTmp });
3214
+ this.pushInstruction({ op: "local.get", index: iTmp });
3215
+ this.pushInstruction({ op: "i32.const", value: 1 });
3216
+ this.pushInstruction("i32.add");
3217
+ this.pushInstruction({ op: "local.set", index: iTmp });
3218
+ });
3219
+ this.pushInstruction({ op: "local.get", index: accTmp });
3220
+ this.releaseTemp(iTmp, "i32");
3221
+ this.releaseTemp(accTmp, "externref");
3222
+ this.releaseTemp(lenTmp, "i32");
3223
+ this.releaseTemp(offTmp, "i32");
3224
+ this.releaseTemp(arrTmp, arrW);
3225
+ }
3226
+
2022
3227
  _emitByteSliceToString(fromType) {
2023
3228
  const elemGo = fromType.elem;
2024
3229
  const sliceInfo = this.mod.getSliceType(elemGo);