gofront 1.7.0 → 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 (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  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 +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  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 +10 -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 +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  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 +512 -77
  34. package/src/backend/wasm/encode.js +43 -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/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /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
  }
@@ -302,7 +371,7 @@ export class ExprsEmitter {
302
371
  this.pushInstruction({ op: "local.get", index: mTmp });
303
372
  this.emitExpr(elem.key, mapInfo.keyWType);
304
373
  this.emitExpr(elem.value, mapInfo.valWType);
305
- this._emitCopyIfValueStruct(elem.value, valType, mapInfo.valWType);
374
+ this._emitCopyIfValue(elem.value, valType, mapInfo.valWType);
306
375
  this.pushInstruction({
307
376
  op: "call",
308
377
  funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
@@ -315,33 +384,11 @@ export class ExprsEmitter {
315
384
  }
316
385
 
317
386
  if (isSliceType(litType, this.mod.checker)) {
318
- const elemType = litType.elem;
387
+ const elemType = this._getArrayElemType(litType);
319
388
  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
- });
389
+ const elems = lit.elems ?? [];
390
+ const count = compositeLitMaxIndex(elems) + 1;
391
+ this._emitArrayLitElems(elems, elemType, sliceInfo.arrInfo, count);
345
392
  this.pushInstruction({ op: "i32.const", value: 0 }); // offset
346
393
  this.pushInstruction({ op: "i32.const", value: count }); // len
347
394
  this.pushInstruction({ op: "i32.const", value: count }); // cap
@@ -353,22 +400,12 @@ export class ExprsEmitter {
353
400
  }
354
401
 
355
402
  if (isArrayType(litType, this.mod.checker)) {
356
- const elemType = litType.elem;
403
+ const elemType = this._getArrayElemType(litType);
357
404
  const arrInfo = this.mod.getArrayType(elemType);
358
- const count = lit.elems?.length ?? 0;
405
+ const elems = lit.elems ?? [];
406
+ const count = compositeLitMaxIndex(elems) + 1;
359
407
  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
- });
408
+ this._emitArrayLitElems(elems, elemType, arrInfo, targetSize);
372
409
  return;
373
410
  }
374
411
 
@@ -398,13 +435,13 @@ export class ExprsEmitter {
398
435
  const f = fields[i];
399
436
  if (isKeyed) {
400
437
  if (map.has(f.name)) {
401
- this.emitExpr(map.get(f.name), f.wType);
438
+ this._emitFieldInit(map.get(f.name), f);
402
439
  } else {
403
440
  this.emitZeroValue(f.goType, f.wType);
404
441
  }
405
442
  } else {
406
443
  if (i < (lit.elems?.length ?? 0)) {
407
- this.emitExpr(lit.elems[i], f.wType);
444
+ this._emitFieldInit(lit.elems[i], f);
408
445
  } else {
409
446
  this.emitZeroValue(f.goType, f.wType);
410
447
  }
@@ -417,9 +454,169 @@ export class ExprsEmitter {
417
454
  });
418
455
  }
419
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
+
420
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
+ }
421
614
  const structInfo = this._resolveStructInfo(expr.expr);
422
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
+ );
423
620
  throw new Error(`Cannot resolve struct for selector: ${expr.field}`);
424
621
  }
425
622
  const path = this._resolveFieldPath(structInfo, expr.field);
@@ -429,8 +626,7 @@ export class ExprsEmitter {
429
626
  );
430
627
  }
431
628
 
432
- const baseWType = this.toWasmType(expr.expr._type);
433
- this.emitExpr(expr.expr, baseWType);
629
+ this.emitSelectorBase(expr.expr);
434
630
 
435
631
  for (let i = 0; i < path.length; i++) {
436
632
  const step = path[i];
@@ -506,11 +702,9 @@ export class ExprsEmitter {
506
702
  );
507
703
  }
508
704
 
509
- const elemGoType = isSlice
510
- ? baseType.elem
511
- : isPtrToArr
512
- ? baseType.base.elem
513
- : baseType.elem;
705
+ const elemGoType = this._getArrayElemType(
706
+ isPtrToArr ? baseType.base : baseType,
707
+ );
514
708
  const arrInfo = this.mod.getArrayType(elemGoType);
515
709
  const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
516
710
 
@@ -604,11 +798,9 @@ export class ExprsEmitter {
604
798
  );
605
799
  }
606
800
 
607
- const elemGoType = isSlice
608
- ? baseType.elem
609
- : isPtrToArr
610
- ? baseType.base.elem
611
- : baseType.elem;
801
+ const elemGoType = this._getArrayElemType(
802
+ isPtrToArr ? baseType.base : baseType,
803
+ );
612
804
  const arrInfo = this.mod.getArrayType(elemGoType);
613
805
  const sliceInfo = this.mod.getSliceType(elemGoType);
614
806
 
@@ -757,8 +949,11 @@ export class ExprsEmitter {
757
949
  const isValStruct =
758
950
  isStructType(elemGoType, this.mod.checker, this.mod) &&
759
951
  !isPointerToStruct(elemGoType, this.mod.checker, this.mod);
952
+ const isValArray =
953
+ elemGoType?.kind !== "pointer" &&
954
+ isArrayType(elemGoType, this.mod.checker);
760
955
 
761
- if (!isValStruct) {
956
+ if (!isValStruct && !isValArray) {
762
957
  this.pushInstruction({
763
958
  op: "array.copy",
764
959
  typeIndexDst: arrInfo.typeIndex,
@@ -831,7 +1026,9 @@ export class ExprsEmitter {
831
1026
  this.pushInstruction("i32.add");
832
1027
  this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
833
1028
 
834
- if (sInfo) {
1029
+ if (isValArray) {
1030
+ this.emitCopyArray(elemGoType);
1031
+ } else if (sInfo) {
835
1032
  this.emitCloneStruct(sInfo, arrInfo.elemWType);
836
1033
  }
837
1034
 
@@ -869,7 +1066,9 @@ export class ExprsEmitter {
869
1066
  this.pushInstruction("i32.add");
870
1067
  this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
871
1068
 
872
- if (sInfo) {
1069
+ if (isValArray) {
1070
+ this.emitCopyArray(elemGoType);
1071
+ } else if (sInfo) {
873
1072
  this.emitCloneStruct(sInfo, arrInfo.elemWType);
874
1073
  }
875
1074
 
@@ -942,53 +1141,95 @@ export class ExprsEmitter {
942
1141
  return this.toWasmType(expr._type) === "i32";
943
1142
  }
944
1143
 
1144
+ // Panics with the generic out-of-range message (shared buffers, SIMD
1145
+ // loads) when the condition on the stack is true.
945
1146
  _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
- }
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 });
958
1177
  }
959
1178
 
960
1179
  // 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.
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.
963
1183
  _emitCheckedIndex(indexNode, emitLen) {
964
- const simple =
965
- (indexNode.kind === "BasicLit" && indexNode.litKind === "INT") ||
966
- (indexNode.kind === "Ident" && this.resolveLocal(indexNode.name));
1184
+ const depth32 = this.resolveBranchDepthToOob(32);
967
1185
  if (this._isI32IndexExpr(indexNode)) {
1186
+ const idx = this.acquireTemp("i32");
968
1187
  this.emitExpr(indexNode, "i32");
969
- let tmp = null;
970
- if (simple) {
971
- 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");
972
1196
  } else {
973
- tmp = this.acquireTemp("i32");
974
- this.pushInstruction({ op: "local.tee", index: tmp });
975
- 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);
976
1204
  }
977
- emitLen();
978
- this.pushInstruction("i32.ge_u");
979
- this._emitBoundsBranch();
980
- if (tmp !== null) this.releaseTemp(tmp, "i32");
1205
+ this.releaseTemp(idx, "i32");
981
1206
  return;
982
1207
  }
983
1208
 
984
1209
  const idx64 = this.acquireTemp("i64");
985
1210
  this.emitExpr(indexNode, "i64");
986
1211
  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 });
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
+ }
992
1233
  this.pushInstruction("i32.wrap_i64");
993
1234
  this.releaseTemp(idx64, "i64");
994
1235
  }
@@ -1086,7 +1327,83 @@ export class ExprsEmitter {
1086
1327
  }
1087
1328
  }
1088
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
+
1089
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
+
1090
1407
  const wType =
1091
1408
  targetWasmType ??
1092
1409
  toWasmType(lit._type, this.mod.checker) ??
@@ -1108,12 +1425,7 @@ export class ExprsEmitter {
1108
1425
  }
1109
1426
 
1110
1427
  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
- }
1428
+ this._emitFloatLit(lit, wType);
1117
1429
  return;
1118
1430
  }
1119
1431
 
@@ -1141,6 +1453,22 @@ export class ExprsEmitter {
1141
1453
  }
1142
1454
  }
1143
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
+
1144
1472
  _emitNilValue(goType, targetWasmType) {
1145
1473
  const effectiveType = targetWasmType ?? this.toWasmType(goType);
1146
1474
  if (
@@ -1264,6 +1592,11 @@ export class ExprsEmitter {
1264
1592
  emitUnaryExpr(expr, targetWasmType = null) {
1265
1593
  const { op, operand } = expr;
1266
1594
 
1595
+ if (isComplex(expr._type) || isComplex(operand._type)) {
1596
+ this._emitComplexUnary(expr, op, operand, targetWasmType);
1597
+ return;
1598
+ }
1599
+
1267
1600
  // Optimize negative numeric literals directly
1268
1601
  if (op === "-" && operand.kind === "BasicLit") {
1269
1602
  if (operand.litKind === "INT" || operand.litKind === "FLOAT") {
@@ -1321,106 +1654,484 @@ export class ExprsEmitter {
1321
1654
  }
1322
1655
  break;
1323
1656
 
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 });
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
+ );
1345
1679
  return;
1346
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 });
1347
1695
 
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);
1696
+ for (let i = 0; i < 2; i++) {
1697
+ this.pushInstruction({ op: "local.get", index: opTmp });
1363
1698
  this.pushInstruction({
1364
1699
  op: "struct.get",
1365
- typeIndex: box.typeIndex,
1366
- fieldIndex: 0,
1700
+ typeIndex: info.typeIndex,
1701
+ typeName: info.name,
1702
+ fieldIndex: i,
1367
1703
  });
1368
- return;
1704
+ this.pushInstruction(`${info.elemWType}.neg`);
1369
1705
  }
1370
1706
 
1371
- default:
1372
- 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);
1373
1713
  }
1374
1714
  }
1375
1715
 
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");
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);
1387
1746
  return;
1388
1747
  }
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");
1748
+ const opWType = this.toWasmType(operand._type);
1749
+ if (!isScalarType(operand._type, this.mod.checker, this.mod)) {
1750
+ this.emitExpr(operand, opWType);
1397
1751
  return;
1398
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
+ }
1399
1761
 
1400
- // Division & Modulo with zero-guard and overflow-guard
1401
- if (op === "/" || op === "%") {
1402
- 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);
1403
1788
  return;
1404
1789
  }
1405
-
1406
- // Shifts with Go semantics
1407
- if (op === "<<" || op === ">>") {
1408
- 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);
1409
1800
  return;
1410
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
+ }
1411
1824
 
1412
- // Standard binary operators
1413
- const isCmp =
1414
- op === "==" ||
1415
- op === "!=" ||
1416
- op === "<" ||
1417
- op === "<=" ||
1418
- op === ">" ||
1419
- op === ">=";
1420
-
1421
- const isNil = (n) =>
1422
- (n.kind === "BasicLit" && n.litKind === "NIL") ||
1423
- (n.kind === "Ident" && n.name === "nil");
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 === ">=";
2131
+
2132
+ const isNil = (n) =>
2133
+ (n.kind === "BasicLit" && n.litKind === "NIL") ||
2134
+ (n.kind === "Ident" && n.name === "nil");
1424
2135
 
1425
2136
  if ((op === "==" || op === "!=") && (isNil(left) || isNil(right))) {
1426
2137
  const nonNil = isNil(right) ? left : right;
@@ -1453,10 +2164,17 @@ export class ExprsEmitter {
1453
2164
  return;
1454
2165
  }
1455
2166
 
1456
- 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;
1457
2175
  const isLeftI32 = this._isI32IndexExpr(left);
1458
2176
  const isRightI32 = this._isI32IndexExpr(right);
1459
- const leftGoType = left._type ?? this._resolveExprGoType(left);
2177
+ const leftGoType = leftType ?? this._resolveExprGoType(left);
1460
2178
  const isIntType =
1461
2179
  !leftGoType ||
1462
2180
  (leftGoType.kind === "basic" &&
@@ -1497,6 +2215,72 @@ export class ExprsEmitter {
1497
2215
  this.emitBinaryOp(op, wType, leftType);
1498
2216
  }
1499
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
+
1500
2284
  _emitStructEq(structInfo) {
1501
2285
  const bTmp = this.acquireTemp({
1502
2286
  kind: "ref",
@@ -1543,11 +2327,18 @@ export class ExprsEmitter {
1543
2327
  (f.goType?.kind === "named" ? f.goType.name : null),
1544
2328
  );
1545
2329
  this._emitStructEq(sInfo);
2330
+ } else if (this._isValueArray(f.goType)) {
2331
+ this._emitArrayEq(f.goType);
1546
2332
  } else if (isStringType(f.goType, this.mod.checker)) {
1547
2333
  const cmpIdx = this.mod.getStringCmpImportIndex("==");
1548
2334
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2335
+ } else if (f.wType === "v128") {
2336
+ this._emitSimdEq(f.goType);
1549
2337
  } else if (f.wType === "anyref") {
1550
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);
1551
2342
  } else if (typeof f.wType === "object" && f.wType !== null) {
1552
2343
  this.pushInstruction("ref.eq");
1553
2344
  } else {
@@ -1578,7 +2369,110 @@ export class ExprsEmitter {
1578
2369
  });
1579
2370
  }
1580
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
+
1581
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
+
1582
2476
  const signed = isSigned(goType);
1583
2477
 
1584
2478
  switch (op) {
@@ -1646,8 +2540,13 @@ export class ExprsEmitter {
1646
2540
  if (isStringType(goType, this.mod.checker) || wType === "externref") {
1647
2541
  const cmpIdx = this.mod.getStringCmpImportIndex("==");
1648
2542
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2543
+ } else if (wType === "v128") {
2544
+ this._emitSimdEq(goType);
1649
2545
  } else if (wType === "anyref") {
1650
2546
  this._emitAnyEq();
2547
+ } else if (this._isScalarPtrType(goType, wType)) {
2548
+ const ptrInfo = this._getScalarPtrInfo(wType, goType);
2549
+ this._emitScalarPtrEq(ptrInfo);
1651
2550
  } else if (typeof wType === "object" && wType !== null) {
1652
2551
  this.pushInstruction("ref.eq");
1653
2552
  } else {
@@ -1658,9 +2557,16 @@ export class ExprsEmitter {
1658
2557
  if (isStringType(goType, this.mod.checker) || wType === "externref") {
1659
2558
  const cmpIdx = this.mod.getStringCmpImportIndex("!=");
1660
2559
  this.pushInstruction({ op: "call", funcIndex: cmpIdx });
2560
+ } else if (wType === "v128") {
2561
+ this._emitSimdEq(goType);
2562
+ this.pushInstruction("i32.eqz");
1661
2563
  } else if (wType === "anyref") {
1662
2564
  this._emitAnyEq();
1663
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");
1664
2570
  } else if (typeof wType === "object" && wType !== null) {
1665
2571
  this.pushInstruction("ref.eq");
1666
2572
  this.pushInstruction("i32.eqz");
@@ -1848,6 +2754,21 @@ export class ExprsEmitter {
1848
2754
  this.pushInstruction({ op: "local.set", index: tmpVal });
1849
2755
 
1850
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
+ }
1851
2772
  if (wType === "i64" && rightWType === "i32") {
1852
2773
  this.pushInstruction("i64.extend_i32_u");
1853
2774
  } else if (wType === "i32" && rightWType === "i64") {
@@ -1890,32 +2811,91 @@ export class ExprsEmitter {
1890
2811
 
1891
2812
  emitTypeConversion(conv) {
1892
2813
  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);
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
+ }
1896
2832
 
1897
2833
  this.emitExpr(expr, fromWType);
1898
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
+
1899
2855
  if (fromWType === toWType) {
1900
2856
  this.emitNarrowIntWrap(targetType);
1901
2857
  return;
1902
2858
  }
1903
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
+ };
1904
2865
  if (
1905
2866
  fromWType === "externref" &&
1906
2867
  isSliceType(targetType, this.mod.checker) &&
1907
- (targetType.elem?.name === "byte" || targetType.elem?.name === "uint8")
2868
+ isByteElem(targetType)
1908
2869
  ) {
1909
- this._emitStringToByteSlice(targetType);
2870
+ this._emitStringToByteSlice({
2871
+ kind: "slice",
2872
+ elem: this._getSliceElemType(targetType),
2873
+ });
1910
2874
  return;
1911
2875
  }
1912
2876
 
1913
2877
  if (
1914
2878
  toWType === "externref" &&
1915
2879
  isSliceType(fromType, this.mod.checker) &&
1916
- (fromType.elem?.name === "byte" || fromType.elem?.name === "uint8")
2880
+ isByteElem(fromType)
1917
2881
  ) {
1918
- 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);
1919
2899
  return;
1920
2900
  }
1921
2901
 
@@ -1960,6 +2940,64 @@ export class ExprsEmitter {
1960
2940
  }
1961
2941
  }
1962
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
+
1963
3001
  _emitStringToByteSlice(targetType) {
1964
3002
  const elemGo = targetType.elem;
1965
3003
  const sliceInfo = this.mod.getSliceType(elemGo);
@@ -2030,6 +3068,162 @@ export class ExprsEmitter {
2030
3068
  this.releaseTemp(strTmp, "externref");
2031
3069
  }
2032
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
+
2033
3227
  _emitByteSliceToString(fromType) {
2034
3228
  const elemGo = fromType.elem;
2035
3229
  const sliceInfo = this.mod.getSliceType(elemGo);