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.
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/backend/js/expressions.js +329 -58
- package/src/backend/js/index.js +90 -10
- package/src/backend/js/runtime.js +51 -9
- package/src/backend/js/statements.js +193 -84
- package/src/backend/js/type-info.js +123 -8
- package/src/backend/shared/fmt-types.js +30 -1
- package/src/backend/wasm/emit-builtins.js +250 -9
- package/src/backend/wasm/emit-exprs.js +63 -22
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +239 -205
- package/src/backend/wasm/emit.js +73 -26
- package/src/backend/wasm/globals.js +1 -0
- package/src/backend/wasm/index.js +8 -0
- package/src/backend/wasm/module-emitter.js +211 -57
- package/src/backend/wasm/monomorph.js +36 -2
- package/src/backend/wasm/natives.js +4 -0
- package/src/backend/wasm/types.js +32 -3
- package/src/backend/wasm/variadic.js +6 -1
- package/src/compiler.js +6 -1
- package/src/lexer/lexer.js +1 -0
- package/src/link/stdlib.js +1 -0
- package/src/link/type-names.js +72 -16
- package/src/lower/embedding.js +43 -15
- package/src/lower/fmt-formatters.js +38 -14
- package/src/lower/functions.js +223 -7
- package/src/lower/gen-types.js +21 -2
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +114 -6
- package/src/parser/declarations.js +4 -2
- package/src/parser/expressions.js +25 -41
- package/src/parser/statements.js +107 -0
- package/src/parser/types.js +11 -0
- package/src/typechecker/assignability.js +94 -12
- package/src/typechecker/constants.js +124 -2
- package/src/typechecker/expressions.js +171 -17
- package/src/typechecker/index.js +158 -29
- package/src/typechecker/operand-text.js +1 -0
- package/src/typechecker/resolve.js +308 -39
- package/src/typechecker/statements.js +171 -21
- 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
|
-
|
|
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
|
-
|
|
1288
|
-
|
|
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 ${
|
|
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
|
|
109
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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 });
|