porffor 0.2.0-09999e8 → 0.2.0-30ecc4a
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 +26 -12
- package/compiler/builtins.js +134 -6
- package/compiler/codeGen.js +279 -51
- package/compiler/decompile.js +3 -3
- package/compiler/index.js +1 -1
- package/compiler/opt.js +15 -3
- package/compiler/prototype.js +171 -16
- package/compiler/sections.js +1 -1
- package/compiler/wasmSpec.js +6 -2
- package/compiler/wrap.js +98 -8
- package/package.json +1 -1
package/compiler/codeGen.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128, encodeVector } from "./encoding.js";
|
3
3
|
import { operatorOpcode } from "./expression.js";
|
4
4
|
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
@@ -55,7 +55,7 @@ const todo = msg => {
|
|
55
55
|
};
|
56
56
|
|
57
57
|
const isFuncType = type => type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
58
|
-
const generate = (scope, decl, global = false, name = undefined) => {
|
58
|
+
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
59
59
|
switch (decl.type) {
|
60
60
|
case 'BinaryExpression':
|
61
61
|
return generateBinaryExp(scope, decl, global, name);
|
@@ -68,7 +68,12 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
68
68
|
|
69
69
|
case 'ArrowFunctionExpression':
|
70
70
|
case 'FunctionDeclaration':
|
71
|
-
generateFunc(scope, decl);
|
71
|
+
const func = generateFunc(scope, decl);
|
72
|
+
|
73
|
+
if (decl.type.endsWith('Expression')) {
|
74
|
+
return number(func.index);
|
75
|
+
}
|
76
|
+
|
72
77
|
return [];
|
73
78
|
|
74
79
|
case 'BlockStatement':
|
@@ -81,7 +86,7 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
81
86
|
return generateExp(scope, decl);
|
82
87
|
|
83
88
|
case 'CallExpression':
|
84
|
-
return generateCall(scope, decl, global, name);
|
89
|
+
return generateCall(scope, decl, global, name, valueUnused);
|
85
90
|
|
86
91
|
case 'NewExpression':
|
87
92
|
return generateNew(scope, decl, global, name);
|
@@ -680,6 +685,15 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
680
685
|
[ Opcodes.i32_eqz ], */
|
681
686
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
682
687
|
],
|
688
|
+
[TYPES._bytestring]: [ // duplicate of string
|
689
|
+
[ Opcodes.local_get, tmp ],
|
690
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
691
|
+
|
692
|
+
// get length
|
693
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
694
|
+
|
695
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
696
|
+
],
|
683
697
|
default: def
|
684
698
|
}, intOut ? Valtype.i32 : valtypeBinary)
|
685
699
|
];
|
@@ -707,6 +721,17 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
707
721
|
[ Opcodes.i32_eqz ],
|
708
722
|
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
709
723
|
],
|
724
|
+
[TYPES._bytestring]: [ // duplicate of string
|
725
|
+
[ Opcodes.local_get, tmp ],
|
726
|
+
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
727
|
+
|
728
|
+
// get length
|
729
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
730
|
+
|
731
|
+
// if length == 0
|
732
|
+
[ Opcodes.i32_eqz ],
|
733
|
+
...(intOut ? [] : [ Opcodes.i32_from_u ])
|
734
|
+
],
|
710
735
|
default: [
|
711
736
|
// if value == 0
|
712
737
|
[ Opcodes.local_get, tmp ],
|
@@ -947,6 +972,18 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
947
972
|
locals[nameParam(i)] = { idx: i, type: allLocals[i] };
|
948
973
|
}
|
949
974
|
|
975
|
+
if (typeof wasm === 'function') {
|
976
|
+
const scope = {
|
977
|
+
name,
|
978
|
+
params,
|
979
|
+
locals,
|
980
|
+
returns,
|
981
|
+
localInd: allLocals.length,
|
982
|
+
};
|
983
|
+
|
984
|
+
wasm = wasm(scope, { TYPES, typeSwitch, makeArray });
|
985
|
+
}
|
986
|
+
|
950
987
|
let baseGlobalIdx, i = 0;
|
951
988
|
for (const type of globalTypes) {
|
952
989
|
if (baseGlobalIdx === undefined) baseGlobalIdx = globalInd;
|
@@ -1027,7 +1064,8 @@ const TYPES = {
|
|
1027
1064
|
|
1028
1065
|
// these are not "typeof" types but tracked internally
|
1029
1066
|
_array: 0x10,
|
1030
|
-
_regexp: 0x11
|
1067
|
+
_regexp: 0x11,
|
1068
|
+
_bytestring: 0x12
|
1031
1069
|
};
|
1032
1070
|
|
1033
1071
|
const TYPE_NAMES = {
|
@@ -1041,7 +1079,8 @@ const TYPE_NAMES = {
|
|
1041
1079
|
[TYPES.bigint]: 'BigInt',
|
1042
1080
|
|
1043
1081
|
[TYPES._array]: 'Array',
|
1044
|
-
[TYPES._regexp]: 'RegExp'
|
1082
|
+
[TYPES._regexp]: 'RegExp',
|
1083
|
+
[TYPES._bytestring]: 'ByteString'
|
1045
1084
|
};
|
1046
1085
|
|
1047
1086
|
const getType = (scope, _name) => {
|
@@ -1094,6 +1133,8 @@ const getNodeType = (scope, node) => {
|
|
1094
1133
|
if (node.type === 'Literal') {
|
1095
1134
|
if (node.regex) return TYPES._regexp;
|
1096
1135
|
|
1136
|
+
if (typeof node.value === 'string' && byteStringable(node.value)) return TYPES._bytestring;
|
1137
|
+
|
1097
1138
|
return TYPES[typeof node.value];
|
1098
1139
|
}
|
1099
1140
|
|
@@ -1107,6 +1148,15 @@ const getNodeType = (scope, node) => {
|
|
1107
1148
|
|
1108
1149
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
1109
1150
|
const name = node.callee.name;
|
1151
|
+
if (!name) {
|
1152
|
+
// iife
|
1153
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1154
|
+
|
1155
|
+
// presume
|
1156
|
+
// todo: warn here?
|
1157
|
+
return TYPES.number;
|
1158
|
+
}
|
1159
|
+
|
1110
1160
|
const func = funcs.find(x => x.name === name);
|
1111
1161
|
|
1112
1162
|
if (func) {
|
@@ -1201,7 +1251,7 @@ const getNodeType = (scope, node) => {
|
|
1201
1251
|
if (node.operator === '!') return TYPES.boolean;
|
1202
1252
|
if (node.operator === 'void') return TYPES.undefined;
|
1203
1253
|
if (node.operator === 'delete') return TYPES.boolean;
|
1204
|
-
if (node.operator === 'typeof') return TYPES.string;
|
1254
|
+
if (node.operator === 'typeof') return process.argv.includes('-bytestring') ? TYPES._bytestring : TYPES.string;
|
1205
1255
|
|
1206
1256
|
return TYPES.number;
|
1207
1257
|
}
|
@@ -1210,7 +1260,9 @@ const getNodeType = (scope, node) => {
|
|
1210
1260
|
// hack: if something.length, number type
|
1211
1261
|
if (node.property.name === 'length') return TYPES.number;
|
1212
1262
|
|
1213
|
-
|
1263
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1264
|
+
|
1265
|
+
// presume
|
1214
1266
|
return TYPES.number;
|
1215
1267
|
}
|
1216
1268
|
|
@@ -1227,6 +1279,23 @@ const getNodeType = (scope, node) => {
|
|
1227
1279
|
return ret;
|
1228
1280
|
};
|
1229
1281
|
|
1282
|
+
const toString = (scope, wasm, type) => {
|
1283
|
+
const tmp = localTmp(scope, '#tostring_tmp');
|
1284
|
+
return [
|
1285
|
+
...wasm,
|
1286
|
+
[ Opcodes.local_set, tmp ],
|
1287
|
+
|
1288
|
+
...typeSwitch(scope, type, {
|
1289
|
+
[TYPES.string]: [
|
1290
|
+
[ Opcodes.local_get, tmp ]
|
1291
|
+
],
|
1292
|
+
[TYPES.undefined]: [
|
1293
|
+
// [ Opcodes.]
|
1294
|
+
]
|
1295
|
+
})
|
1296
|
+
]
|
1297
|
+
};
|
1298
|
+
|
1230
1299
|
const generateLiteral = (scope, decl, global, name) => {
|
1231
1300
|
if (decl.value === null) return number(NULL);
|
1232
1301
|
|
@@ -1244,16 +1313,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1244
1313
|
return number(decl.value ? 1 : 0);
|
1245
1314
|
|
1246
1315
|
case 'string':
|
1247
|
-
|
1248
|
-
const rawElements = new Array(str.length);
|
1249
|
-
let j = 0;
|
1250
|
-
for (let i = 0; i < str.length; i++) {
|
1251
|
-
rawElements[i] = str.charCodeAt(i);
|
1252
|
-
}
|
1253
|
-
|
1254
|
-
return makeArray(scope, {
|
1255
|
-
rawElements
|
1256
|
-
}, global, name, false, 'i16')[0];
|
1316
|
+
return makeString(scope, decl.value, global, name);
|
1257
1317
|
|
1258
1318
|
default:
|
1259
1319
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1274,9 +1334,9 @@ const countLeftover = wasm => {
|
|
1274
1334
|
|
1275
1335
|
if (depth === 0)
|
1276
1336
|
if ([Opcodes.throw,Opcodes.drop, Opcodes.local_set, Opcodes.global_set].includes(inst[0])) count--;
|
1277
|
-
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1337
|
+
else if ([null, Opcodes.i32_eqz, Opcodes.i64_eqz, Opcodes.f64_ceil, Opcodes.f64_floor, Opcodes.f64_trunc, Opcodes.f64_nearest, Opcodes.f64_sqrt, Opcodes.local_tee, Opcodes.i32_wrap_i64, Opcodes.i64_extend_i32_s, Opcodes.i64_extend_i32_u, Opcodes.f32_demote_f64, Opcodes.f64_promote_f32, Opcodes.f64_convert_i32_s, Opcodes.f64_convert_i32_u, Opcodes.i32_clz, Opcodes.i32_ctz, Opcodes.i32_popcnt, Opcodes.f64_neg, Opcodes.end, Opcodes.i32_trunc_sat_f64_s[0], Opcodes.i32x4_extract_lane, Opcodes.i16x8_extract_lane, Opcodes.i32_load, Opcodes.i64_load, Opcodes.f64_load, Opcodes.v128_load, Opcodes.i32_load16_u, Opcodes.i32_load16_s, Opcodes.i32_load8_u, Opcodes.i32_load8_s, Opcodes.memory_grow].includes(inst[0]) && (inst[0] !== 0xfc || inst[1] < 0x0a)) {}
|
1278
1338
|
else if ([Opcodes.local_get, Opcodes.global_get, Opcodes.f64_const, Opcodes.i32_const, Opcodes.i64_const, Opcodes.v128_const].includes(inst[0])) count++;
|
1279
|
-
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16].includes(inst[0])) count -= 2;
|
1339
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1280
1340
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1281
1341
|
else if (inst[0] === Opcodes.return) count = 0;
|
1282
1342
|
else if (inst[0] === Opcodes.call) {
|
@@ -1302,7 +1362,7 @@ const disposeLeftover = wasm => {
|
|
1302
1362
|
const generateExp = (scope, decl) => {
|
1303
1363
|
const expression = decl.expression;
|
1304
1364
|
|
1305
|
-
const out = generate(scope, expression);
|
1365
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1306
1366
|
disposeLeftover(out);
|
1307
1367
|
|
1308
1368
|
return out;
|
@@ -1360,7 +1420,7 @@ const RTArrayUtil = {
|
|
1360
1420
|
]
|
1361
1421
|
};
|
1362
1422
|
|
1363
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1423
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1364
1424
|
/* const callee = decl.callee;
|
1365
1425
|
const args = decl.arguments;
|
1366
1426
|
|
@@ -1479,10 +1539,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1479
1539
|
// use local for cached i32 length as commonly used
|
1480
1540
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1481
1541
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1482
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1483
1542
|
|
1484
1543
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1485
1544
|
|
1545
|
+
const rawPointer = [
|
1546
|
+
...generate(scope, target),
|
1547
|
+
Opcodes.i32_to_u
|
1548
|
+
];
|
1549
|
+
|
1550
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1551
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1552
|
+
|
1553
|
+
let allOptUnused = true;
|
1486
1554
|
let lengthI32CacheUsed = false;
|
1487
1555
|
const protoBC = {};
|
1488
1556
|
for (const x in protoCands) {
|
@@ -1502,6 +1570,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1502
1570
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1503
1571
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1504
1572
|
|
1573
|
+
let optUnused = false;
|
1505
1574
|
const protoOut = protoFunc(getPointer, {
|
1506
1575
|
getCachedI32: () => {
|
1507
1576
|
lengthI32CacheUsed = true;
|
@@ -1516,10 +1585,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1516
1585
|
return makeArray(scope, {
|
1517
1586
|
rawElements: new Array(length)
|
1518
1587
|
}, _global, _name, true, itemType);
|
1588
|
+
}, () => {
|
1589
|
+
optUnused = true;
|
1590
|
+
return unusedValue;
|
1519
1591
|
});
|
1520
1592
|
|
1593
|
+
if (!optUnused) allOptUnused = false;
|
1594
|
+
|
1521
1595
|
protoBC[x] = [
|
1522
|
-
[ Opcodes.block, valtypeBinary ],
|
1596
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1523
1597
|
...protoOut,
|
1524
1598
|
|
1525
1599
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1528,11 +1602,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1528
1602
|
];
|
1529
1603
|
}
|
1530
1604
|
|
1531
|
-
|
1532
|
-
...generate(scope, target),
|
1605
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1533
1606
|
|
1534
|
-
|
1535
|
-
|
1607
|
+
return [
|
1608
|
+
...(usePointerCache ? [
|
1609
|
+
...rawPointer,
|
1610
|
+
[ Opcodes.local_set, pointerLocal ],
|
1611
|
+
] : []),
|
1536
1612
|
|
1537
1613
|
...(!lengthI32CacheUsed ? [] : [
|
1538
1614
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1544,7 +1620,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1544
1620
|
|
1545
1621
|
// TODO: error better
|
1546
1622
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1547
|
-
}, valtypeBinary),
|
1623
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1548
1624
|
];
|
1549
1625
|
}
|
1550
1626
|
}
|
@@ -1591,7 +1667,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1591
1667
|
const func = funcs.find(x => x.index === idx);
|
1592
1668
|
|
1593
1669
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1594
|
-
const
|
1670
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1671
|
+
const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
|
1672
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1595
1673
|
|
1596
1674
|
let args = decl.arguments;
|
1597
1675
|
if (func && args.length < paramCount) {
|
@@ -1609,12 +1687,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1609
1687
|
let out = [];
|
1610
1688
|
for (const arg of args) {
|
1611
1689
|
out = out.concat(generate(scope, arg));
|
1612
|
-
if (
|
1690
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1613
1691
|
}
|
1614
1692
|
|
1615
1693
|
out.push([ Opcodes.call, idx ]);
|
1616
1694
|
|
1617
|
-
if (!
|
1695
|
+
if (!typedReturn) {
|
1618
1696
|
// let type;
|
1619
1697
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1620
1698
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1665,14 +1743,102 @@ const knownType = (scope, type) => {
|
|
1665
1743
|
return null;
|
1666
1744
|
};
|
1667
1745
|
|
1746
|
+
const brTable = (input, bc, returns) => {
|
1747
|
+
const out = [];
|
1748
|
+
const keys = Object.keys(bc);
|
1749
|
+
const count = keys.length;
|
1750
|
+
|
1751
|
+
if (count === 1) {
|
1752
|
+
// return [
|
1753
|
+
// ...input,
|
1754
|
+
// ...bc[keys[0]]
|
1755
|
+
// ];
|
1756
|
+
return bc[keys[0]];
|
1757
|
+
}
|
1758
|
+
|
1759
|
+
if (count === 2) {
|
1760
|
+
// just use if else
|
1761
|
+
const other = keys.find(x => x !== 'default');
|
1762
|
+
return [
|
1763
|
+
...input,
|
1764
|
+
...number(other, Valtype.i32),
|
1765
|
+
[ Opcodes.i32_eq ],
|
1766
|
+
[ Opcodes.if, returns ],
|
1767
|
+
...bc[other],
|
1768
|
+
[ Opcodes.else ],
|
1769
|
+
...bc.default,
|
1770
|
+
[ Opcodes.end ]
|
1771
|
+
];
|
1772
|
+
}
|
1773
|
+
|
1774
|
+
for (let i = 0; i < count; i++) {
|
1775
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1776
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1777
|
+
}
|
1778
|
+
|
1779
|
+
const nums = keys.filter(x => +x);
|
1780
|
+
const offset = Math.min(...nums);
|
1781
|
+
const max = Math.max(...nums);
|
1782
|
+
|
1783
|
+
const table = [];
|
1784
|
+
let br = 1;
|
1785
|
+
|
1786
|
+
for (let i = offset; i <= max; i++) {
|
1787
|
+
// if branch for this num, go to that block
|
1788
|
+
if (bc[i]) {
|
1789
|
+
table.push(br);
|
1790
|
+
br++;
|
1791
|
+
continue;
|
1792
|
+
}
|
1793
|
+
|
1794
|
+
// else default
|
1795
|
+
table.push(0);
|
1796
|
+
}
|
1797
|
+
|
1798
|
+
out.push(
|
1799
|
+
[ Opcodes.block, Blocktype.void ],
|
1800
|
+
...input,
|
1801
|
+
...(offset > 0 ? [
|
1802
|
+
...number(offset, Valtype.i32),
|
1803
|
+
[ Opcodes.i32_sub ]
|
1804
|
+
] : []),
|
1805
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1806
|
+
);
|
1807
|
+
|
1808
|
+
// if you can guess why we sort the wrong way and then reverse
|
1809
|
+
// (instead of just sorting the correct way)
|
1810
|
+
// dm me and if you are correct and the first person
|
1811
|
+
// I will somehow shout you out or something
|
1812
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1813
|
+
|
1814
|
+
br = count - 1;
|
1815
|
+
for (const x of orderedBc) {
|
1816
|
+
out.push(
|
1817
|
+
[ Opcodes.end ],
|
1818
|
+
...bc[x],
|
1819
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1820
|
+
);
|
1821
|
+
br--;
|
1822
|
+
}
|
1823
|
+
|
1824
|
+
return [
|
1825
|
+
...out,
|
1826
|
+
[ Opcodes.end, 'br table end' ]
|
1827
|
+
];
|
1828
|
+
};
|
1829
|
+
|
1668
1830
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1831
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1832
|
+
|
1669
1833
|
const known = knownType(scope, type);
|
1670
1834
|
if (known != null) {
|
1671
1835
|
return bc[known] ?? bc.default;
|
1672
1836
|
}
|
1673
1837
|
|
1674
|
-
|
1838
|
+
if (process.argv.includes('-typeswitch-use-brtable'))
|
1839
|
+
return brTable(type, bc, returns);
|
1675
1840
|
|
1841
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1676
1842
|
const out = [
|
1677
1843
|
...type,
|
1678
1844
|
[ Opcodes.local_set, tmp ],
|
@@ -1759,11 +1925,12 @@ const extractTypeAnnotation = decl => {
|
|
1759
1925
|
elementType = extractTypeAnnotation(a.elementType).type;
|
1760
1926
|
}
|
1761
1927
|
|
1928
|
+
const typeName = type;
|
1762
1929
|
type = typeAnnoToPorfType(type);
|
1763
1930
|
|
1764
1931
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1765
1932
|
|
1766
|
-
return { type, elementType };
|
1933
|
+
return { type, typeName, elementType };
|
1767
1934
|
};
|
1768
1935
|
|
1769
1936
|
const generateVar = (scope, decl) => {
|
@@ -1793,6 +1960,11 @@ const generateVar = (scope, decl) => {
|
|
1793
1960
|
}
|
1794
1961
|
|
1795
1962
|
let idx = allocVar(scope, name, global);
|
1963
|
+
|
1964
|
+
if (typedInput && x.id.typeAnnotation) {
|
1965
|
+
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1966
|
+
}
|
1967
|
+
|
1796
1968
|
if (x.init) {
|
1797
1969
|
out = out.concat(generate(scope, x.init, global, name));
|
1798
1970
|
|
@@ -1802,10 +1974,6 @@ const generateVar = (scope, decl) => {
|
|
1802
1974
|
|
1803
1975
|
// hack: this follows spec properly but is mostly unneeded 😅
|
1804
1976
|
// out.push(...setType(scope, name, x.init ? getNodeType(scope, x.init) : TYPES.undefined));
|
1805
|
-
|
1806
|
-
if (typedInput && x.id.typeAnnotation) {
|
1807
|
-
addVarMetadata(scope, name, global, extractTypeAnnotation(x.id));
|
1808
|
-
}
|
1809
1977
|
}
|
1810
1978
|
|
1811
1979
|
return out;
|
@@ -2052,6 +2220,8 @@ const generateUnary = (scope, decl) => {
|
|
2052
2220
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2053
2221
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2054
2222
|
|
2223
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2224
|
+
|
2055
2225
|
// object and internal types
|
2056
2226
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2057
2227
|
});
|
@@ -2233,13 +2403,14 @@ const generateForOf = (scope, decl) => {
|
|
2233
2403
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2234
2404
|
// hack: this is naughty and will break things!
|
2235
2405
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2236
|
-
if (pages.
|
2406
|
+
if (pages.hasAnyString) {
|
2237
2407
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2238
2408
|
rawElements: new Array(1)
|
2239
2409
|
}, isGlobal, leftName, true, 'i16');
|
2240
2410
|
}
|
2241
2411
|
|
2242
2412
|
// set type for local
|
2413
|
+
// todo: optimize away counter and use end pointer
|
2243
2414
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2244
2415
|
[TYPES._array]: [
|
2245
2416
|
...setType(scope, leftName, TYPES.number),
|
@@ -2425,6 +2596,8 @@ const allocPage = (reason, type) => {
|
|
2425
2596
|
|
2426
2597
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2427
2598
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2599
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2600
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2428
2601
|
|
2429
2602
|
const ind = pages.size;
|
2430
2603
|
pages.set(reason, { ind, type });
|
@@ -2458,7 +2631,8 @@ const StoreOps = {
|
|
2458
2631
|
f64: Opcodes.f64_store,
|
2459
2632
|
|
2460
2633
|
// expects i32 input!
|
2461
|
-
|
2634
|
+
i8: Opcodes.i32_store8,
|
2635
|
+
i16: Opcodes.i32_store16,
|
2462
2636
|
};
|
2463
2637
|
|
2464
2638
|
let data = [];
|
@@ -2477,6 +2651,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2477
2651
|
}
|
2478
2652
|
};
|
2479
2653
|
|
2654
|
+
const getAllocType = itemType => {
|
2655
|
+
switch (itemType) {
|
2656
|
+
case 'i8': return 'bytestring';
|
2657
|
+
case 'i16': return 'string';
|
2658
|
+
|
2659
|
+
default: return 'array';
|
2660
|
+
}
|
2661
|
+
};
|
2662
|
+
|
2480
2663
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2481
2664
|
const out = [];
|
2482
2665
|
|
@@ -2486,7 +2669,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2486
2669
|
|
2487
2670
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2488
2671
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2489
|
-
arrays.set(name, allocPage(`${itemType
|
2672
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2490
2673
|
}
|
2491
2674
|
|
2492
2675
|
const pointer = arrays.get(name);
|
@@ -2532,7 +2715,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2532
2715
|
out.push(
|
2533
2716
|
...number(0, Valtype.i32),
|
2534
2717
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2535
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2718
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2536
2719
|
);
|
2537
2720
|
}
|
2538
2721
|
|
@@ -2542,15 +2725,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2542
2725
|
return [ out, pointer ];
|
2543
2726
|
};
|
2544
2727
|
|
2728
|
+
const byteStringable = str => {
|
2729
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2730
|
+
|
2731
|
+
for (let i = 0; i < str.length; i++) {
|
2732
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2733
|
+
}
|
2734
|
+
|
2735
|
+
return true;
|
2736
|
+
};
|
2737
|
+
|
2545
2738
|
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2546
2739
|
const rawElements = new Array(str.length);
|
2740
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2547
2741
|
for (let i = 0; i < str.length; i++) {
|
2548
|
-
|
2742
|
+
const c = str.charCodeAt(i);
|
2743
|
+
rawElements[i] = c;
|
2744
|
+
|
2745
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2549
2746
|
}
|
2550
2747
|
|
2551
2748
|
return makeArray(scope, {
|
2552
2749
|
rawElements
|
2553
|
-
}, global, name, false, 'i16')[0];
|
2750
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2554
2751
|
};
|
2555
2752
|
|
2556
2753
|
let arrays = new Map();
|
@@ -2578,10 +2775,13 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2578
2775
|
];
|
2579
2776
|
}
|
2580
2777
|
|
2778
|
+
const object = generate(scope, decl.object);
|
2779
|
+
const property = generate(scope, decl.property);
|
2780
|
+
|
2581
2781
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2582
2782
|
// hack: this is naughty and will break things!
|
2583
|
-
let newOut = number(0,
|
2584
|
-
if (pages.
|
2783
|
+
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2784
|
+
if (pages.hasAnyString) {
|
2585
2785
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2586
2786
|
rawElements: new Array(1)
|
2587
2787
|
}, _global, _name, true, 'i16');
|
@@ -2590,7 +2790,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2590
2790
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2591
2791
|
[TYPES._array]: [
|
2592
2792
|
// get index as valtype
|
2593
|
-
...
|
2793
|
+
...property,
|
2594
2794
|
|
2595
2795
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2596
2796
|
Opcodes.i32_to_u,
|
@@ -2598,7 +2798,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2598
2798
|
[ Opcodes.i32_mul ],
|
2599
2799
|
|
2600
2800
|
...(aotPointer ? [] : [
|
2601
|
-
...
|
2801
|
+
...object,
|
2602
2802
|
Opcodes.i32_to_u,
|
2603
2803
|
[ Opcodes.i32_add ]
|
2604
2804
|
]),
|
@@ -2617,14 +2817,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2617
2817
|
|
2618
2818
|
...number(0, Valtype.i32), // base 0 for store later
|
2619
2819
|
|
2620
|
-
...
|
2621
|
-
|
2820
|
+
...property,
|
2622
2821
|
Opcodes.i32_to_u,
|
2822
|
+
|
2623
2823
|
...number(ValtypeSize.i16, Valtype.i32),
|
2624
2824
|
[ Opcodes.i32_mul ],
|
2625
2825
|
|
2626
2826
|
...(aotPointer ? [] : [
|
2627
|
-
...
|
2827
|
+
...object,
|
2628
2828
|
Opcodes.i32_to_u,
|
2629
2829
|
[ Opcodes.i32_add ]
|
2630
2830
|
]),
|
@@ -2641,6 +2841,34 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2641
2841
|
...number(TYPES.string, Valtype.i32),
|
2642
2842
|
setLastType(scope)
|
2643
2843
|
],
|
2844
|
+
[TYPES._bytestring]: [
|
2845
|
+
// setup new/out array
|
2846
|
+
...newOut,
|
2847
|
+
[ Opcodes.drop ],
|
2848
|
+
|
2849
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2850
|
+
|
2851
|
+
...property,
|
2852
|
+
Opcodes.i32_to_u,
|
2853
|
+
|
2854
|
+
...(aotPointer ? [] : [
|
2855
|
+
...object,
|
2856
|
+
Opcodes.i32_to_u,
|
2857
|
+
[ Opcodes.i32_add ]
|
2858
|
+
]),
|
2859
|
+
|
2860
|
+
// load current string ind {arg}
|
2861
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2862
|
+
|
2863
|
+
// store to new string ind 0
|
2864
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2865
|
+
|
2866
|
+
// return new string (page)
|
2867
|
+
...number(newPointer),
|
2868
|
+
|
2869
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2870
|
+
setLastType(scope)
|
2871
|
+
],
|
2644
2872
|
|
2645
2873
|
default: [ [ Opcodes.unreachable ] ]
|
2646
2874
|
});
|
package/compiler/decompile.js
CHANGED
@@ -15,7 +15,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
15
15
|
if (name) out += `${makeSignature(params, returns)} ;; $${name} (${ind})\n`;
|
16
16
|
|
17
17
|
const justLocals = Object.values(locals).sort((a, b) => a.idx - b.idx).slice(params.length);
|
18
|
-
if (justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
|
18
|
+
if (name && justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
|
19
19
|
|
20
20
|
let i = -1, lastInst;
|
21
21
|
let byte = 0;
|
@@ -32,7 +32,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
32
32
|
inst = [ [ inst[0], inst[1] ], ...inst.slice(2) ];
|
33
33
|
}
|
34
34
|
|
35
|
-
if (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all) depth--;
|
35
|
+
if (depth > 0 && (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all)) depth--;
|
36
36
|
|
37
37
|
out += ' '.repeat(Math.max(0, depth * 2));
|
38
38
|
|
@@ -119,7 +119,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
119
119
|
export const highlightAsm = asm => asm
|
120
120
|
.replace(/(local|global|memory)\.[^\s]*/g, _ => `\x1B[31m${_}\x1B[0m`)
|
121
121
|
.replace(/(i(8|16|32|64)x[0-9]+|v128)(\.[^\s]*)?/g, _ => `\x1B[34m${_}\x1B[0m`)
|
122
|
-
.replace(/
|
122
|
+
.replace(/(i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `\x1B[36m${_}\x1B[0m`)
|
123
123
|
.replace(/(return_call|call|br_if|br|return|rethrow|throw)/g, _ => `\x1B[35m${_}\x1B[0m`)
|
124
124
|
.replace(/(block|loop|if|end|else|try|catch_all|catch|delegate)/g, _ => `\x1B[95m${_}\x1B[0m`)
|
125
125
|
.replace(/unreachable/g, _ => `\x1B[91m${_}\x1B[0m`)
|
package/compiler/index.js
CHANGED
@@ -52,7 +52,7 @@ export default (code, flags) => {
|
|
52
52
|
if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
|
53
53
|
|
54
54
|
const t2 = performance.now();
|
55
|
-
opt(funcs, globals, pages);
|
55
|
+
opt(funcs, globals, pages, tags);
|
56
56
|
if (flags.includes('info')) console.log(`3. optimized code in ${(performance.now() - t2).toFixed(2)}ms`);
|
57
57
|
|
58
58
|
if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
|