gofront 1.9.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/package.json +2 -2
  3. package/src/backend/js/expressions.js +329 -58
  4. package/src/backend/js/index.js +90 -10
  5. package/src/backend/js/runtime.js +51 -9
  6. package/src/backend/js/statements.js +193 -84
  7. package/src/backend/js/type-info.js +123 -8
  8. package/src/backend/shared/fmt-types.js +30 -1
  9. package/src/backend/wasm/emit-builtins.js +250 -9
  10. package/src/backend/wasm/emit-exprs.js +63 -22
  11. package/src/backend/wasm/emit-shared.js +4 -1
  12. package/src/backend/wasm/emit-stmts.js +239 -205
  13. package/src/backend/wasm/emit.js +73 -26
  14. package/src/backend/wasm/globals.js +1 -0
  15. package/src/backend/wasm/index.js +8 -0
  16. package/src/backend/wasm/module-emitter.js +211 -57
  17. package/src/backend/wasm/monomorph.js +36 -2
  18. package/src/backend/wasm/natives.js +4 -0
  19. package/src/backend/wasm/types.js +32 -3
  20. package/src/backend/wasm/variadic.js +6 -1
  21. package/src/compiler.js +6 -1
  22. package/src/lexer/lexer.js +1 -0
  23. package/src/link/stdlib.js +1 -0
  24. package/src/link/type-names.js +72 -16
  25. package/src/lower/embedding.js +43 -15
  26. package/src/lower/fmt-formatters.js +38 -14
  27. package/src/lower/functions.js +223 -7
  28. package/src/lower/gen-types.js +21 -2
  29. package/src/lower/index.js +7 -1
  30. package/src/lower/json-codecs.js +114 -6
  31. package/src/parser/declarations.js +4 -2
  32. package/src/parser/expressions.js +25 -41
  33. package/src/parser/statements.js +107 -0
  34. package/src/parser/types.js +11 -0
  35. package/src/typechecker/assignability.js +94 -12
  36. package/src/typechecker/constants.js +124 -2
  37. package/src/typechecker/expressions.js +171 -17
  38. package/src/typechecker/index.js +158 -29
  39. package/src/typechecker/operand-text.js +1 -0
  40. package/src/typechecker/resolve.js +308 -39
  41. package/src/typechecker/statements.js +171 -21
  42. package/src/typechecker/types.js +84 -2
@@ -62,10 +62,39 @@ export function goTypeName(t, specialized = null) {
62
62
  return `[]${go(t.elem)}`;
63
63
  case "array":
64
64
  case "ArrayType":
65
- return `[${t.size ?? 0}]${go(t.elem)}`;
65
+ // A parsed array type carries its length as a literal node.
66
+ return `[${typeof t.size === "object" ? (t.size?.value ?? 0) : (t.size ?? 0)}]${go(t.elem)}`;
66
67
  case "map":
67
68
  case "MapType":
68
69
  return `map[${go(t.key)}]${go(t.value)}`;
70
+ case "func": {
71
+ // A func without results is `func()`, not `func() void`.
72
+ const rets = (t.returns ?? [])
73
+ .filter((r) => !(r?.kind === "basic" && r.name === "void"))
74
+ .map(go);
75
+ const ret =
76
+ rets.length === 0
77
+ ? ""
78
+ : rets.length === 1
79
+ ? ` ${rets[0]}`
80
+ : ` (${rets.join(", ")})`;
81
+ return `func(${(t.params ?? []).map(go).join(", ")})${ret}`;
82
+ }
83
+ case "FuncType": {
84
+ // A parsed func type: parameters and the result as written.
85
+ const rt = t.returnType;
86
+ const rets = !rt ? [] : rt.kind === "TupleType" ? rt.types : [rt];
87
+ const ret =
88
+ rets.length === 0
89
+ ? ""
90
+ : rets.length === 1
91
+ ? ` ${go(rets[0])}`
92
+ : ` (${rets.map(go).join(", ")})`;
93
+ const params = (t.params ?? []).map((p) =>
94
+ p.variadic ? `...${go(p.type)}` : go(p.type),
95
+ );
96
+ return `func(${params.join(", ")})${ret}`;
97
+ }
69
98
  default:
70
99
  return "interface {}";
71
100
  }
@@ -3,7 +3,6 @@
3
3
  // call expression dispatch (static, method, closure, package).
4
4
 
5
5
  import { hasCallOrReceive } from "../../lower/walk.js";
6
- import { typeStr } from "../../typechecker/types.js";
7
6
  import { goTypeName } from "../shared/fmt-types.js";
8
7
  import {
9
8
  anonIfaceName,
@@ -12,6 +11,7 @@ import {
12
11
  isArrayType,
13
12
  isComplex,
14
13
  isComplex64,
14
+ isFuncType,
15
15
  isMapType,
16
16
  isNonEmptyInterface,
17
17
  isScalarType,
@@ -1265,14 +1265,17 @@ export class BuiltinsEmitter {
1265
1265
  for (const embed of structInfo.embeds ?? []) {
1266
1266
  const typeName = embed.typeName ?? embed.name;
1267
1267
  const methodName = `${typeName}.${method}`;
1268
+ const ifaceType = structInfo.fields[embed.fieldIndex]?.goType;
1269
+ if (this.mod.embeddedInterfaceHas(ifaceType, method))
1270
+ return { chain: [{ structInfo, embed }], ifaceType };
1268
1271
  if (this.mod.resolveFuncIndex(methodName) !== null)
1269
1272
  return { chain: [{ structInfo, embed }], methodName };
1270
1273
  const inner = this.mod.getStructType(typeName);
1271
1274
  const deeper = inner && this._findEmbeddedMethod(inner, method, seen);
1272
1275
  if (deeper)
1273
1276
  return {
1277
+ ...deeper,
1274
1278
  chain: [{ structInfo, embed }, ...deeper.chain],
1275
- methodName: deeper.methodName,
1276
1279
  };
1277
1280
  }
1278
1281
  return null;
@@ -1283,10 +1286,19 @@ export class BuiltinsEmitter {
1283
1286
  if (!structInfo) return false;
1284
1287
  const found = this._findEmbeddedMethod(structInfo, func.field);
1285
1288
  if (!found) return false;
1286
- const { chain, methodName } = found;
1287
- const subIdx = this.mod.resolveFuncIndex(methodName);
1288
- const targetParamTypes = this.mod.getFuncParamTypes(methodName);
1289
+ const { chain, methodName, ifaceType } = found;
1290
+ // A method promoted from an embedded interface is dispatched on the
1291
+ // interface value the last embed holds.
1292
+ const dispatchName = ifaceType
1293
+ ? `__dispatch_${this._getReceiverTypeName(ifaceType)}_${func.field}`
1294
+ : null;
1295
+ const subName = dispatchName ?? methodName;
1296
+ const subIdx = this.mod.resolveFuncIndex(subName);
1297
+ if (subIdx === null)
1298
+ throw new Error(`internal: no interface dispatcher ${subName}`);
1299
+ const targetParamTypes = this.mod.getFuncParamTypes(subName);
1289
1300
  const valueRecv =
1301
+ !ifaceType &&
1290
1302
  this.mod.getFuncParamGoTypes(methodName)[0]?.kind !== "pointer";
1291
1303
  this.emitExpr(func.expr, this.toWasmType(recvType));
1292
1304
  this._emitNilCheck(func.expr._type);
@@ -1393,6 +1405,41 @@ export class BuiltinsEmitter {
1393
1405
  this.releaseTemp(closureTmp, closureWType);
1394
1406
  }
1395
1407
 
1408
+ // `g(f())` that lowering left unsplit (it splits only statement operands):
1409
+ // f's results go into locals, and g is called with idents carrying each
1410
+ // element's interface-box marks, so each is boxed like any argument. Also
1411
+ // used for method calls, whose argument path expects one value per
1412
+ // parameter. Only when g's callee evaluates no call, so f() still runs
1413
+ // before anything else the call evaluates.
1414
+ _splitForwardedArgs(call) {
1415
+ if (!call._multiForward || call.args?.length !== 1) return null;
1416
+ const inner = call.args[0];
1417
+ const types = inner?._type?.types;
1418
+ if (!Array.isArray(types) || types.length === 0) return null;
1419
+ const holders =
1420
+ inner._fwdIface?.length === types.length ? inner._fwdIface : null;
1421
+ const isMethod =
1422
+ call.func?.kind === "SelectorExpr" &&
1423
+ call.func.expr?._type?.kind !== "namespace";
1424
+ if (!holders && !isMethod) return null;
1425
+ if (hasCallOrReceive(call.func)) return null;
1426
+ this.emitExpr(inner, null);
1427
+ this._fwdArgCounter = (this._fwdArgCounter ?? 0) + 1;
1428
+ const names = types.map((_, i) => `__fwdarg$${this._fwdArgCounter}$${i}`);
1429
+ const locals = types.map((t, i) =>
1430
+ this.allocLocal(names[i], this.toWasmType(t), t, true),
1431
+ );
1432
+ for (let i = locals.length - 1; i >= 0; i--)
1433
+ this.pushInstruction({ op: "local.set", index: locals[i] });
1434
+ const args = names.map((name, i) => ({
1435
+ kind: "Ident",
1436
+ name,
1437
+ _type: types[i],
1438
+ ...(holders?.[i] ?? {}),
1439
+ }));
1440
+ return { ...call, args, _multiForward: false };
1441
+ }
1442
+
1396
1443
  emitCallExpr(call, targetWasmType = null) {
1397
1444
  if (call._isTypeConversion) {
1398
1445
  this.emitTypeConversion({
@@ -1412,6 +1459,12 @@ export class BuiltinsEmitter {
1412
1459
  return;
1413
1460
  }
1414
1461
 
1462
+ const forwarded = this._splitForwardedArgs(call);
1463
+ if (forwarded) {
1464
+ this.emitCallExpr(forwarded, targetWasmType);
1465
+ return;
1466
+ }
1467
+
1415
1468
  if (this._emitPackageCall(call, targetWasmType)) {
1416
1469
  return;
1417
1470
  }
@@ -1560,6 +1613,15 @@ export class BuiltinsEmitter {
1560
1613
  : `*${named(info.name)}`,
1561
1614
  ]);
1562
1615
  }
1616
+ for (const c of mod.closureTypes.values())
1617
+ names.push([
1618
+ c.typeIndex,
1619
+ goTypeName({
1620
+ kind: "func",
1621
+ params: c.sig.params,
1622
+ returns: c.sig.returns,
1623
+ }),
1624
+ ]);
1563
1625
  for (const box of mod.ifaceBoxTypes.values())
1564
1626
  names.push([box.typeIndex, goTypeName(box.goType)]);
1565
1627
  for (const info of mod.taggedSliceTypes.values())
@@ -1657,6 +1719,123 @@ export class BuiltinsEmitter {
1657
1719
  this.emitRuntimePanicThrow();
1658
1720
  }
1659
1721
 
1722
+ // `any_eq` arm for two arrays of one wasm array type (an array stored in
1723
+ // an interface is the array ref itself): equal lengths and elements.
1724
+ _emitAnyArrayEqArm(arrInfo) {
1725
+ const ref = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
1726
+ for (const idx of [0, 1]) {
1727
+ this.pushInstruction({ op: "local.get", index: idx });
1728
+ this.pushInstruction({ op: "ref.test", typeIndex: arrInfo.typeIndex });
1729
+ }
1730
+ this.pushInstruction("i32.and");
1731
+ this.pushInstruction({ op: "if", blockType: "void" });
1732
+ const a1 = this.allocLocal(null, ref);
1733
+ const a2 = this.allocLocal(null, ref);
1734
+ for (const [src, dst] of [
1735
+ [0, a1],
1736
+ [1, a2],
1737
+ ]) {
1738
+ this.pushInstruction({ op: "local.get", index: src });
1739
+ this.pushInstruction({ op: "ref.cast", typeIndex: arrInfo.typeIndex });
1740
+ this.pushInstruction({ op: "local.set", index: dst });
1741
+ }
1742
+ const len = this.allocLocal(null, "i32");
1743
+ this.pushInstruction({ op: "local.get", index: a1 });
1744
+ this.pushInstruction("array.len");
1745
+ this.pushInstruction({ op: "local.tee", index: len });
1746
+ this.pushInstruction({ op: "local.get", index: a2 });
1747
+ this.pushInstruction("array.len");
1748
+ this.pushInstruction("i32.ne");
1749
+ this.pushInstruction({ op: "if", blockType: "void" });
1750
+ this.pushInstruction({ op: "i32.const", value: 0 });
1751
+ this.pushInstruction("return");
1752
+ this.pushInstruction("end");
1753
+ const i = this.allocLocal(null, "i32");
1754
+ const e1 = this.allocLocal(null, arrInfo.elemWType);
1755
+ const e2 = this.allocLocal(null, arrInfo.elemWType);
1756
+ this.pushInstruction({ op: "i32.const", value: 0 });
1757
+ this.pushInstruction({ op: "local.set", index: i });
1758
+ this.pushInstruction({ op: "block", blockType: "void" });
1759
+ this.pushInstruction({ op: "loop", blockType: "void" });
1760
+ this.pushInstruction({ op: "local.get", index: i });
1761
+ this.pushInstruction({ op: "local.get", index: len });
1762
+ this.pushInstruction("i32.ge_u");
1763
+ this.pushInstruction({ op: "br_if", depth: 1 });
1764
+ for (const [arr, dst] of [
1765
+ [a1, e1],
1766
+ [a2, e2],
1767
+ ]) {
1768
+ this.pushInstruction({ op: "local.get", index: arr });
1769
+ this.pushInstruction({ op: "local.get", index: i });
1770
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1771
+ this.pushInstruction({ op: "local.set", index: dst });
1772
+ }
1773
+ this.emitKeyEq(e1, e2, arrInfo.elemGoType);
1774
+ this.pushInstruction("i32.eqz");
1775
+ this.pushInstruction({ op: "if", blockType: "void" });
1776
+ this.pushInstruction({ op: "i32.const", value: 0 });
1777
+ this.pushInstruction("return");
1778
+ this.pushInstruction("end");
1779
+ this.pushInstruction({ op: "local.get", index: i });
1780
+ this.pushInstruction({ op: "i32.const", value: 1 });
1781
+ this.pushInstruction("i32.add");
1782
+ this.pushInstruction({ op: "local.set", index: i });
1783
+ this.pushInstruction({ op: "br", depth: 0 });
1784
+ this.pushInstruction("end");
1785
+ this.pushInstruction("end");
1786
+ this.pushInstruction({ op: "i32.const", value: 1 });
1787
+ this.pushInstruction("return");
1788
+ this.pushInstruction("end");
1789
+ }
1790
+
1791
+ // `any_hash` arm for an array: the wasm array type, then each element,
1792
+ // as `emitKeyHash` combines a static array key (so equal arrays under
1793
+ // `any_eq` hash equal).
1794
+ _emitAnyArrayHashArm(arrInfo) {
1795
+ const ref = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
1796
+ this.pushInstruction({ op: "local.get", index: 0 });
1797
+ this.pushInstruction({ op: "ref.test", typeIndex: arrInfo.typeIndex });
1798
+ this.pushInstruction({ op: "if", blockType: "void" });
1799
+ const arr = this.allocLocal(null, ref);
1800
+ const h = this.allocLocal(null, "i32");
1801
+ const i = this.allocLocal(null, "i32");
1802
+ const e = this.allocLocal(null, arrInfo.elemWType);
1803
+ this.pushInstruction({ op: "local.get", index: 0 });
1804
+ this.pushInstruction({ op: "ref.cast", typeIndex: arrInfo.typeIndex });
1805
+ this.pushInstruction({ op: "local.set", index: arr });
1806
+ this.pushInstruction({ op: "i32.const", value: arrInfo.typeIndex });
1807
+ this.pushInstruction({ op: "local.set", index: h });
1808
+ this.pushInstruction({ op: "i32.const", value: 0 });
1809
+ this.pushInstruction({ op: "local.set", index: i });
1810
+ this.pushInstruction({ op: "block", blockType: "void" });
1811
+ this.pushInstruction({ op: "loop", blockType: "void" });
1812
+ this.pushInstruction({ op: "local.get", index: i });
1813
+ this.pushInstruction({ op: "local.get", index: arr });
1814
+ this.pushInstruction("array.len");
1815
+ this.pushInstruction("i32.ge_u");
1816
+ this.pushInstruction({ op: "br_if", depth: 1 });
1817
+ this.pushInstruction({ op: "local.get", index: arr });
1818
+ this.pushInstruction({ op: "local.get", index: i });
1819
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1820
+ this.pushInstruction({ op: "local.set", index: e });
1821
+ this.pushInstruction({ op: "local.get", index: h });
1822
+ this.pushInstruction({ op: "i32.const", value: 31 });
1823
+ this.pushInstruction("i32.mul");
1824
+ this.emitKeyHash(e, arrInfo.elemGoType);
1825
+ this.pushInstruction("i32.add");
1826
+ this.pushInstruction({ op: "local.set", index: h });
1827
+ this.pushInstruction({ op: "local.get", index: i });
1828
+ this.pushInstruction({ op: "i32.const", value: 1 });
1829
+ this.pushInstruction("i32.add");
1830
+ this.pushInstruction({ op: "local.set", index: i });
1831
+ this.pushInstruction({ op: "br", depth: 0 });
1832
+ this.pushInstruction("end");
1833
+ this.pushInstruction("end");
1834
+ this.pushInstruction({ op: "local.get", index: h });
1835
+ this.pushInstruction("return");
1836
+ this.pushInstruction("end");
1837
+ }
1838
+
1660
1839
  emitRuntimeHelper(fn) {
1661
1840
  const errInfo = this.mod.getStructType(ERROR_STRUCT);
1662
1841
  switch (fn._isRuntimeHelper) {
@@ -1735,6 +1914,12 @@ export class BuiltinsEmitter {
1735
1914
  this.pushInstruction("end");
1736
1915
  }
1737
1916
 
1917
+ // Arrays compare element by element.
1918
+ for (const a of [...this.mod.arrayTypes.values()]) {
1919
+ if (!isUnhashableGoType(a.elemGoType, this.mod.checker))
1920
+ this._emitAnyArrayEqArm(a);
1921
+ }
1922
+
1738
1923
  // Strings
1739
1924
  this.pushInstruction({ op: "local.get", index: 0 });
1740
1925
  this.pushInstruction({
@@ -1778,6 +1963,47 @@ export class BuiltinsEmitter {
1778
1963
  this.pushInstruction("return");
1779
1964
  return;
1780
1965
  }
1966
+ case "any_ref": {
1967
+ // A tagged slice is a fresh header on each conversion to an `any`:
1968
+ // its backing array is the identity every copy shares.
1969
+ for (const info of this.mod.taggedSliceTypes.values()) {
1970
+ this.pushInstruction({ op: "local.get", index: 0 });
1971
+ this.pushInstruction({ op: "ref.test", typeIndex: info.typeIndex });
1972
+ this.pushInstruction({ op: "if", blockType: "void" });
1973
+ this.pushInstruction({ op: "local.get", index: 0 });
1974
+ this.pushInstruction({ op: "ref.cast", typeIndex: info.typeIndex });
1975
+ this.pushInstruction({
1976
+ op: "struct.get",
1977
+ typeIndex: info.typeIndex,
1978
+ fieldIndex: 0,
1979
+ });
1980
+ this.pushInstruction("return");
1981
+ this.pushInstruction("end");
1982
+ }
1983
+ // A boxed defined map or func value: the reference inside the box.
1984
+ for (const box of this.mod.ifaceBoxTypes.values()) {
1985
+ if (
1986
+ !isMapType(box.goType, this.mod.checker) &&
1987
+ !isFuncType(box.goType, this.mod.checker)
1988
+ )
1989
+ continue;
1990
+ this.pushInstruction({ op: "local.get", index: 0 });
1991
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
1992
+ this.pushInstruction({ op: "if", blockType: "void" });
1993
+ this.pushInstruction({ op: "local.get", index: 0 });
1994
+ this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
1995
+ this.pushInstruction({
1996
+ op: "struct.get",
1997
+ typeIndex: box.typeIndex,
1998
+ fieldIndex: 0,
1999
+ });
2000
+ this.pushInstruction("return");
2001
+ this.pushInstruction("end");
2002
+ }
2003
+ this.pushInstruction({ op: "local.get", index: 0 });
2004
+ this.pushInstruction("return");
2005
+ return;
2006
+ }
1781
2007
  case "any_hash": {
1782
2008
  this.pushInstruction({ op: "local.get", index: 0 });
1783
2009
  this.pushInstruction("ref.is_null");
@@ -1786,14 +2012,15 @@ export class BuiltinsEmitter {
1786
2012
  this.pushInstruction("return");
1787
2013
  this.pushInstruction("end");
1788
2014
 
1789
- // Check unhashable types
2015
+ // Check unhashable types. `goType` names the dynamic type in the
2016
+ // panic, as Go does (`main.S`, `main.L`, `func()`).
1790
2017
  const checkUnhashable = (typeIndex, goType) => {
1791
2018
  this.pushInstruction({ op: "local.get", index: 0 });
1792
2019
  this.pushInstruction({ op: "ref.test", typeIndex });
1793
2020
  this.pushInstruction({ op: "if", blockType: "void" });
1794
2021
  this.emitPanic(
1795
2022
  goType
1796
- ? `runtime error: hash of unhashable type ${typeStr(goType)}`
2023
+ ? `runtime error: hash of unhashable type ${goTypeName(goType)}`
1797
2024
  : "runtime error: hash of unhashable type",
1798
2025
  );
1799
2026
  this.pushInstruction("end");
@@ -1802,6 +2029,10 @@ export class BuiltinsEmitter {
1802
2029
  // its element as written.
1803
2030
  const spelled = (t) =>
1804
2031
  t?.kind === "TypeName" ? { kind: "basic", name: t.name } : t;
2032
+ // Tagged slices (a defined slice type in an interface) are subtypes
2033
+ // of the plain slice type, so they are tested first.
2034
+ for (const info of this.mod.taggedSliceTypes.values())
2035
+ checkUnhashable(info.typeIndex, info.goType);
1805
2036
  for (const sliceInfo of this.mod.sliceTypes.values())
1806
2037
  checkUnhashable(sliceInfo.typeIndex, {
1807
2038
  kind: "slice",
@@ -1814,10 +2045,14 @@ export class BuiltinsEmitter {
1814
2045
  value: mapInfo.valGoType,
1815
2046
  });
1816
2047
  for (const closureInfo of this.mod.closureTypes.values())
1817
- checkUnhashable(closureInfo.typeIndex);
2048
+ checkUnhashable(closureInfo.typeIndex, {
2049
+ kind: "func",
2050
+ params: closureInfo.sig.params,
2051
+ returns: closureInfo.sig.returns,
2052
+ });
1818
2053
  for (const box of this.mod.ifaceBoxTypes.values()) {
1819
2054
  if (isUnhashableGoType(box.goType, this.mod.checker))
1820
- checkUnhashable(box.typeIndex);
2055
+ checkUnhashable(box.typeIndex, box.goType);
1821
2056
  }
1822
2057
  for (const arrInfo of this.mod.arrayTypes.values()) {
1823
2058
  if (isUnhashableGoType(arrInfo.elemGoType, this.mod.checker))
@@ -1848,6 +2083,12 @@ export class BuiltinsEmitter {
1848
2083
  this.pushInstruction("end");
1849
2084
  }
1850
2085
 
2086
+ // Arrays hash element by element.
2087
+ for (const a of [...this.mod.arrayTypes.values()]) {
2088
+ if (!isUnhashableGoType(a.elemGoType, this.mod.checker))
2089
+ this._emitAnyArrayHashArm(a);
2090
+ }
2091
+
1851
2092
  // Check string
1852
2093
  this.pushInstruction({ op: "local.get", index: 0 });
1853
2094
  this.pushInstruction({
@@ -105,38 +105,38 @@ export class ExprsEmitter {
105
105
  return;
106
106
  }
107
107
 
108
- // A defined func or map type with methods is boxed to keep its identity,
109
- // so interface dispatch can tell it from the plain func or map type.
108
+ // A defined func type, and every array and map type, is boxed to keep
109
+ // its identity: arrays and maps share one wasm type across Go types
110
+ // (`[2]int` and `[2]int64`), and a defined func type has methods that
111
+ // interface dispatch must tell from the plain func type.
110
112
  if (this._isMethodNamedRef(goType)) {
111
113
  const box = this.mod.getIfaceBoxType(this._resolveTestTarget(goType));
112
114
  this._emitRawExpr(expr, box.wType);
115
+ // An array stored in an interface is a copy.
116
+ if (isArrayType(box.goType, this.mod.checker))
117
+ this._emitCopyIfValue(expr, goType, box.wType);
113
118
  this._emitBoxValue(box);
114
119
  return;
115
120
  }
116
121
 
117
122
  if (
118
123
  isSliceType(goType, this.mod.checker) ||
119
- isArrayType(goType, this.mod.checker) ||
120
- isFuncType(goType, this.mod.checker) ||
121
- isMapType(goType, this.mod.checker)
124
+ isFuncType(goType, this.mod.checker)
122
125
  ) {
123
126
  const refWType = this.toWasmType(goType);
124
127
  this._emitRawExpr(expr, refWType);
125
- // An array stored in an interface is a copy.
126
- if (isArrayType(goType, this.mod.checker))
127
- this._emitCopyIfValue(expr, goType, refWType);
128
128
  if (isTaggedSliceType(goType, this.mod.checker))
129
129
  this._emitTagSlice(goType, refWType);
130
- if (
131
- isFuncType(goType, this.mod.checker) ||
132
- isMapType(goType, this.mod.checker)
133
- )
130
+ if (isFuncType(goType, this.mod.checker))
134
131
  this._emitNilAwareAny(goType, expr);
135
132
  return;
136
133
  }
137
134
 
138
135
  // Scalar type -> box into struct
139
- const scalarType = goType ?? { kind: "basic", name: "int" };
136
+ // An untyped rune constant boxes as `rune` (int32), like Go.
137
+ const scalarType = goType?.rune
138
+ ? { kind: "basic", name: "int32" }
139
+ : (goType ?? { kind: "basic", name: "int" });
140
140
  const box = this.mod.getIfaceBoxType(scalarType);
141
141
  this._emitRawExpr(expr, box.wType);
142
142
  this._emitBoxValue(box);
@@ -421,6 +421,10 @@ export class ExprsEmitter {
421
421
  );
422
422
  }
423
423
  this.pushInstruction({ op: "local.get", index: local.index });
424
+ // A non-escaping `range n` index lives in an i32 local, but a
425
+ // closure (such as a range-over-func body) sees a Go `int`.
426
+ if (!field.isBoxed && local.type === "i32" && field.wType === "i64")
427
+ this.pushInstruction("i64.extend_i32_s");
424
428
  }
425
429
 
426
430
  this.pushInstruction({
@@ -1575,6 +1579,13 @@ export class ExprsEmitter {
1575
1579
  this._emitNilValue(ident._type, targetWasmType);
1576
1580
  return;
1577
1581
  }
1582
+ // A named untyped complex constant that takes type complex64 is built
1583
+ // from its initializer, like a complex literal, not read from its
1584
+ // complex128 global.
1585
+ if (ident._complexInit && this._isComplex64Target(targetWasmType)) {
1586
+ this.emitExpr(ident._complexInit, targetWasmType);
1587
+ return;
1588
+ }
1578
1589
 
1579
1590
  const localInfo = this.resolveLocal(ident.name);
1580
1591
  if (localInfo) {
@@ -1646,10 +1657,17 @@ export class ExprsEmitter {
1646
1657
  throw new Error(`Unresolved identifier: ${ident.name}`);
1647
1658
  }
1648
1659
 
1660
+ // Unary arithmetic on a complex value; `&c` and `*p` of one only move
1661
+ // its pointer.
1662
+ _isComplexArith(expr, op, operand) {
1663
+ if (op === "&" || op === "*") return false;
1664
+ return isComplex(expr._type) || isComplex(operand._type);
1665
+ }
1666
+
1649
1667
  emitUnaryExpr(expr, targetWasmType = null) {
1650
1668
  const { op, operand } = expr;
1651
1669
 
1652
- if (isComplex(expr._type) || isComplex(operand._type)) {
1670
+ if (this._isComplexArith(expr, op, operand)) {
1653
1671
  this._emitComplexUnary(expr, op, operand, targetWasmType);
1654
1672
  return;
1655
1673
  }
@@ -1770,19 +1788,25 @@ export class ExprsEmitter {
1770
1788
  }
1771
1789
  }
1772
1790
 
1791
+ // True when a pointer to a `t` variable is the value's own reference (an
1792
+ // array or struct) rather than a box around it (scalars and complex).
1793
+ _isOwnPointerValue(t) {
1794
+ if (this._isValueArray(t)) return true;
1795
+ return (
1796
+ t?.kind !== "pointer" &&
1797
+ !isComplex(t) &&
1798
+ isStructType(t, this.mod.checker, this.mod) &&
1799
+ !isPointerToStruct(t, this.mod.checker, this.mod)
1800
+ );
1801
+ }
1802
+
1773
1803
  _emitAddressOf(operand) {
1774
1804
  if (operand.kind === "Ident") {
1775
1805
  const loc = this.resolveLocal(operand.name);
1776
1806
  if (loc?.isBoxed) {
1777
1807
  this.pushInstruction({ op: "local.get", index: loc.index });
1778
1808
  // A pointer to an array or struct is its reference itself.
1779
- const t = loc.goType;
1780
- if (
1781
- this._isValueArray(t) ||
1782
- (t?.kind !== "pointer" &&
1783
- isStructType(t, this.mod.checker, this.mod) &&
1784
- !isPointerToStruct(t, this.mod.checker, this.mod))
1785
- )
1809
+ if (this._isOwnPointerValue(loc.goType))
1786
1810
  this.pushInstruction({
1787
1811
  op: "struct.get",
1788
1812
  typeIndex: loc.boxInfo.typeIndex,
@@ -1831,7 +1855,8 @@ export class ExprsEmitter {
1831
1855
  // The struct a `*operand` reads or writes, or null when the operand points
1832
1856
  // at something else: `*pp` with `pp **T` loads a pointer, not a struct.
1833
1857
  _derefStructInfo(operand) {
1834
- if (this._isPointerGoType(this._ptrBaseType(operand._type))) return null;
1858
+ const base = this._ptrBaseType(operand._type);
1859
+ if (this._isPointerGoType(base) || isComplex(base)) return null;
1835
1860
  return this._resolveStructInfo(operand);
1836
1861
  }
1837
1862
 
@@ -2491,10 +2516,26 @@ export class ExprsEmitter {
2491
2516
  this.releaseTemp(bTmp, arrWType);
2492
2517
  }
2493
2518
 
2519
+ // Equality of two complex values on the stack -> i32.
2520
+ _emitComplexElemEq(goType, wType) {
2521
+ const info = this.mod.getComplexType(
2522
+ isComplex64(goType) ? "complex64" : "complex128",
2523
+ );
2524
+ const bTmp = this.acquireTemp(wType);
2525
+ const aTmp = this.acquireTemp(wType);
2526
+ this.pushInstruction({ op: "local.set", index: bTmp });
2527
+ this.pushInstruction({ op: "local.set", index: aTmp });
2528
+ this._emitComplexEq(aTmp, bTmp, info, info.elemWType, "==");
2529
+ this.releaseTemp(aTmp, wType);
2530
+ this.releaseTemp(bTmp, wType);
2531
+ }
2532
+
2494
2533
  // Equality of two array elements on the stack -> i32.
2495
2534
  _emitElemEq(goType, wType) {
2496
2535
  if (this._isValueArray(goType)) {
2497
2536
  this._emitArrayEq(goType);
2537
+ } else if (isComplex(goType)) {
2538
+ this._emitComplexElemEq(goType, wType);
2498
2539
  } else if (
2499
2540
  isStructType(goType, this.mod.checker, this.mod) &&
2500
2541
  !isPointerToStruct(goType, this.mod.checker, this.mod)
@@ -381,7 +381,10 @@ export class SharedEmitter {
381
381
  store(lhs[1], valLocalInfo);
382
382
  }
383
383
 
384
- if (bodyBlock) this.emitBlock(bodyBlock);
384
+ if (bodyBlock) {
385
+ this._emitRangeBoxes();
386
+ this.emitBlock(bodyBlock);
387
+ }
385
388
 
386
389
  this.pushInstruction("end");
387
390
  this.pushInstruction({ op: "local.get", index: idxTmp });