porffor 0.2.0-31c2539 → 0.2.0-4035760
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 +16 -5
- package/compiler/builtins.js +134 -6
- package/compiler/codeGen.js +271 -54
- package/compiler/decompile.js +3 -3
- package/compiler/index.js +1 -1
- package/compiler/opt.js +14 -2
- package/compiler/prototype.js +171 -16
- package/compiler/wasmSpec.js +6 -2
- package/compiler/wrap.js +101 -8
- package/package.json +1 -1
- package/r.js +45 -0
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) {
|
@@ -1125,7 +1175,7 @@ const getNodeType = (scope, node) => {
|
|
1125
1175
|
const spl = name.slice(2).split('_');
|
1126
1176
|
|
1127
1177
|
const func = spl[spl.length - 1];
|
1128
|
-
const protoFuncs = Object.
|
1178
|
+
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES._bytestring && prototypeFuncs[x][func] != null);
|
1129
1179
|
if (protoFuncs.length === 1) return protoFuncs[0].returnType ?? TYPES.number;
|
1130
1180
|
}
|
1131
1181
|
|
@@ -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,13 @@ 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
|
+
// ts hack
|
1264
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES.string) return TYPES.string;
|
1265
|
+
if (scope.locals[node.object.name]?.metadata?.type === TYPES._array) return TYPES.number;
|
1266
|
+
|
1267
|
+
if (scope.locals['#last_type']) return [ getLastType(scope) ];
|
1268
|
+
|
1269
|
+
// presume
|
1214
1270
|
return TYPES.number;
|
1215
1271
|
}
|
1216
1272
|
|
@@ -1244,16 +1300,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
1244
1300
|
return number(decl.value ? 1 : 0);
|
1245
1301
|
|
1246
1302
|
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];
|
1303
|
+
return makeString(scope, decl.value, global, name);
|
1257
1304
|
|
1258
1305
|
default:
|
1259
1306
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -1274,9 +1321,9 @@ const countLeftover = wasm => {
|
|
1274
1321
|
|
1275
1322
|
if (depth === 0)
|
1276
1323
|
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)) {}
|
1324
|
+
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
1325
|
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;
|
1326
|
+
else if ([Opcodes.i32_store, Opcodes.i64_store, Opcodes.f64_store, Opcodes.i32_store16, Opcodes.i32_store8].includes(inst[0])) count -= 2;
|
1280
1327
|
else if (Opcodes.memory_copy[0] === inst[0] && Opcodes.memory_copy[1] === inst[1]) count -= 3;
|
1281
1328
|
else if (inst[0] === Opcodes.return) count = 0;
|
1282
1329
|
else if (inst[0] === Opcodes.call) {
|
@@ -1302,7 +1349,7 @@ const disposeLeftover = wasm => {
|
|
1302
1349
|
const generateExp = (scope, decl) => {
|
1303
1350
|
const expression = decl.expression;
|
1304
1351
|
|
1305
|
-
const out = generate(scope, expression);
|
1352
|
+
const out = generate(scope, expression, undefined, undefined, true);
|
1306
1353
|
disposeLeftover(out);
|
1307
1354
|
|
1308
1355
|
return out;
|
@@ -1360,7 +1407,7 @@ const RTArrayUtil = {
|
|
1360
1407
|
]
|
1361
1408
|
};
|
1362
1409
|
|
1363
|
-
const generateCall = (scope, decl, _global, _name) => {
|
1410
|
+
const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
1364
1411
|
/* const callee = decl.callee;
|
1365
1412
|
const args = decl.arguments;
|
1366
1413
|
|
@@ -1469,8 +1516,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1469
1516
|
|
1470
1517
|
if (protoName) {
|
1471
1518
|
const protoCands = Object.keys(prototypeFuncs).reduce((acc, x) => {
|
1472
|
-
|
1473
|
-
if (f) acc[x] = f;
|
1519
|
+
if (Object.hasOwn(prototypeFuncs[x], protoName)) acc[x] = prototypeFuncs[x][protoName];
|
1474
1520
|
return acc;
|
1475
1521
|
}, {});
|
1476
1522
|
|
@@ -1479,10 +1525,18 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1479
1525
|
// use local for cached i32 length as commonly used
|
1480
1526
|
const lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1481
1527
|
const pointerLocal = localTmp(scope, '__proto_pointer_cache', Valtype.i32);
|
1482
|
-
const getPointer = [ [ Opcodes.local_get, pointerLocal ] ];
|
1483
1528
|
|
1484
1529
|
// TODO: long-term, prototypes should be their individual separate funcs
|
1485
1530
|
|
1531
|
+
const rawPointer = [
|
1532
|
+
...generate(scope, target),
|
1533
|
+
Opcodes.i32_to_u
|
1534
|
+
];
|
1535
|
+
|
1536
|
+
const usePointerCache = !Object.values(protoCands).every(x => x.noPointerCache === true);
|
1537
|
+
const getPointer = usePointerCache ? [ [ Opcodes.local_get, pointerLocal ] ] : rawPointer;
|
1538
|
+
|
1539
|
+
let allOptUnused = true;
|
1486
1540
|
let lengthI32CacheUsed = false;
|
1487
1541
|
const protoBC = {};
|
1488
1542
|
for (const x in protoCands) {
|
@@ -1502,6 +1556,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1502
1556
|
const protoLocal = protoFunc.local ? localTmp(scope, `__${protoName}_tmp`, protoFunc.local) : -1;
|
1503
1557
|
const protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${protoName}_tmp2`, protoFunc.local2) : -1;
|
1504
1558
|
|
1559
|
+
let optUnused = false;
|
1505
1560
|
const protoOut = protoFunc(getPointer, {
|
1506
1561
|
getCachedI32: () => {
|
1507
1562
|
lengthI32CacheUsed = true;
|
@@ -1516,10 +1571,15 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1516
1571
|
return makeArray(scope, {
|
1517
1572
|
rawElements: new Array(length)
|
1518
1573
|
}, _global, _name, true, itemType);
|
1574
|
+
}, () => {
|
1575
|
+
optUnused = true;
|
1576
|
+
return unusedValue;
|
1519
1577
|
});
|
1520
1578
|
|
1579
|
+
if (!optUnused) allOptUnused = false;
|
1580
|
+
|
1521
1581
|
protoBC[x] = [
|
1522
|
-
[ Opcodes.block, valtypeBinary ],
|
1582
|
+
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1523
1583
|
...protoOut,
|
1524
1584
|
|
1525
1585
|
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
@@ -1528,11 +1588,13 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1528
1588
|
];
|
1529
1589
|
}
|
1530
1590
|
|
1531
|
-
|
1532
|
-
...generate(scope, target),
|
1591
|
+
// todo: if some cands use optUnused and some don't, we will probably crash
|
1533
1592
|
|
1534
|
-
|
1535
|
-
|
1593
|
+
return [
|
1594
|
+
...(usePointerCache ? [
|
1595
|
+
...rawPointer,
|
1596
|
+
[ Opcodes.local_set, pointerLocal ],
|
1597
|
+
] : []),
|
1536
1598
|
|
1537
1599
|
...(!lengthI32CacheUsed ? [] : [
|
1538
1600
|
...RTArrayUtil.getLengthI32(getPointer),
|
@@ -1544,7 +1606,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1544
1606
|
|
1545
1607
|
// TODO: error better
|
1546
1608
|
default: internalThrow(scope, 'TypeError', `'${protoName}' proto func tried to be called on a type without an impl`)
|
1547
|
-
}, valtypeBinary),
|
1609
|
+
}, allOptUnused && unusedValue ? Blocktype.void : valtypeBinary),
|
1548
1610
|
];
|
1549
1611
|
}
|
1550
1612
|
}
|
@@ -1591,7 +1653,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1591
1653
|
const func = funcs.find(x => x.index === idx);
|
1592
1654
|
|
1593
1655
|
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1594
|
-
const
|
1656
|
+
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1657
|
+
const typedReturn = userFunc || builtinFuncs[name]?.typedReturn;
|
1658
|
+
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1595
1659
|
|
1596
1660
|
let args = decl.arguments;
|
1597
1661
|
if (func && args.length < paramCount) {
|
@@ -1609,12 +1673,12 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1609
1673
|
let out = [];
|
1610
1674
|
for (const arg of args) {
|
1611
1675
|
out = out.concat(generate(scope, arg));
|
1612
|
-
if (
|
1676
|
+
if (typedParams) out = out.concat(getNodeType(scope, arg));
|
1613
1677
|
}
|
1614
1678
|
|
1615
1679
|
out.push([ Opcodes.call, idx ]);
|
1616
1680
|
|
1617
|
-
if (!
|
1681
|
+
if (!typedReturn) {
|
1618
1682
|
// let type;
|
1619
1683
|
// if (builtinFuncs[name]) type = TYPES[builtinFuncs[name].returnType ?? 'number'];
|
1620
1684
|
// if (internalConstrs[name]) type = internalConstrs[name].type;
|
@@ -1665,14 +1729,102 @@ const knownType = (scope, type) => {
|
|
1665
1729
|
return null;
|
1666
1730
|
};
|
1667
1731
|
|
1732
|
+
const brTable = (input, bc, returns) => {
|
1733
|
+
const out = [];
|
1734
|
+
const keys = Object.keys(bc);
|
1735
|
+
const count = keys.length;
|
1736
|
+
|
1737
|
+
if (count === 1) {
|
1738
|
+
// return [
|
1739
|
+
// ...input,
|
1740
|
+
// ...bc[keys[0]]
|
1741
|
+
// ];
|
1742
|
+
return bc[keys[0]];
|
1743
|
+
}
|
1744
|
+
|
1745
|
+
if (count === 2) {
|
1746
|
+
// just use if else
|
1747
|
+
const other = keys.find(x => x !== 'default');
|
1748
|
+
return [
|
1749
|
+
...input,
|
1750
|
+
...number(other, Valtype.i32),
|
1751
|
+
[ Opcodes.i32_eq ],
|
1752
|
+
[ Opcodes.if, returns ],
|
1753
|
+
...bc[other],
|
1754
|
+
[ Opcodes.else ],
|
1755
|
+
...bc.default,
|
1756
|
+
[ Opcodes.end ]
|
1757
|
+
];
|
1758
|
+
}
|
1759
|
+
|
1760
|
+
for (let i = 0; i < count; i++) {
|
1761
|
+
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
1762
|
+
else out.push([ Opcodes.block, Blocktype.void ]);
|
1763
|
+
}
|
1764
|
+
|
1765
|
+
const nums = keys.filter(x => +x);
|
1766
|
+
const offset = Math.min(...nums);
|
1767
|
+
const max = Math.max(...nums);
|
1768
|
+
|
1769
|
+
const table = [];
|
1770
|
+
let br = 1;
|
1771
|
+
|
1772
|
+
for (let i = offset; i <= max; i++) {
|
1773
|
+
// if branch for this num, go to that block
|
1774
|
+
if (bc[i]) {
|
1775
|
+
table.push(br);
|
1776
|
+
br++;
|
1777
|
+
continue;
|
1778
|
+
}
|
1779
|
+
|
1780
|
+
// else default
|
1781
|
+
table.push(0);
|
1782
|
+
}
|
1783
|
+
|
1784
|
+
out.push(
|
1785
|
+
[ Opcodes.block, Blocktype.void ],
|
1786
|
+
...input,
|
1787
|
+
...(offset > 0 ? [
|
1788
|
+
...number(offset, Valtype.i32),
|
1789
|
+
[ Opcodes.i32_sub ]
|
1790
|
+
] : []),
|
1791
|
+
[ Opcodes.br_table, ...encodeVector(table), 0 ]
|
1792
|
+
);
|
1793
|
+
|
1794
|
+
// if you can guess why we sort the wrong way and then reverse
|
1795
|
+
// (instead of just sorting the correct way)
|
1796
|
+
// dm me and if you are correct and the first person
|
1797
|
+
// I will somehow shout you out or something
|
1798
|
+
const orderedBc = keys.sort((a, b) => b - a).reverse();
|
1799
|
+
|
1800
|
+
br = count - 1;
|
1801
|
+
for (const x of orderedBc) {
|
1802
|
+
out.push(
|
1803
|
+
[ Opcodes.end ],
|
1804
|
+
...bc[x],
|
1805
|
+
...(br === 0 ? [] : [ [ Opcodes.br, br ] ])
|
1806
|
+
);
|
1807
|
+
br--;
|
1808
|
+
}
|
1809
|
+
|
1810
|
+
return [
|
1811
|
+
...out,
|
1812
|
+
[ Opcodes.end, 'br table end' ]
|
1813
|
+
];
|
1814
|
+
};
|
1815
|
+
|
1668
1816
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
1817
|
+
if (!process.argv.includes('-bytestring')) delete bc[TYPES._bytestring];
|
1818
|
+
|
1669
1819
|
const known = knownType(scope, type);
|
1670
1820
|
if (known != null) {
|
1671
1821
|
return bc[known] ?? bc.default;
|
1672
1822
|
}
|
1673
1823
|
|
1674
|
-
|
1824
|
+
if (process.argv.includes('-typeswitch-use-brtable'))
|
1825
|
+
return brTable(type, bc, returns);
|
1675
1826
|
|
1827
|
+
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
1676
1828
|
const out = [
|
1677
1829
|
...type,
|
1678
1830
|
[ Opcodes.local_set, tmp ],
|
@@ -1762,6 +1914,8 @@ const extractTypeAnnotation = decl => {
|
|
1762
1914
|
const typeName = type;
|
1763
1915
|
type = typeAnnoToPorfType(type);
|
1764
1916
|
|
1917
|
+
if (type === TYPES._bytestring && !process.argv.includes('-bytestring')) type = TYPES.string;
|
1918
|
+
|
1765
1919
|
// if (decl.name) console.log(decl.name, { type, elementType });
|
1766
1920
|
|
1767
1921
|
return { type, typeName, elementType };
|
@@ -2054,6 +2208,8 @@ const generateUnary = (scope, decl) => {
|
|
2054
2208
|
[TYPES.undefined]: makeString(scope, 'undefined', false, '#typeof_result'),
|
2055
2209
|
[TYPES.function]: makeString(scope, 'function', false, '#typeof_result'),
|
2056
2210
|
|
2211
|
+
[TYPES._bytestring]: makeString(scope, 'string', false, '#typeof_result'),
|
2212
|
+
|
2057
2213
|
// object and internal types
|
2058
2214
|
default: makeString(scope, 'object', false, '#typeof_result'),
|
2059
2215
|
});
|
@@ -2235,13 +2391,14 @@ const generateForOf = (scope, decl) => {
|
|
2235
2391
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2236
2392
|
// hack: this is naughty and will break things!
|
2237
2393
|
let newOut = number(0, Valtype.f64), newPointer = -1;
|
2238
|
-
if (pages.
|
2394
|
+
if (pages.hasAnyString) {
|
2239
2395
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2240
2396
|
rawElements: new Array(1)
|
2241
2397
|
}, isGlobal, leftName, true, 'i16');
|
2242
2398
|
}
|
2243
2399
|
|
2244
2400
|
// set type for local
|
2401
|
+
// todo: optimize away counter and use end pointer
|
2245
2402
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2246
2403
|
[TYPES._array]: [
|
2247
2404
|
...setType(scope, leftName, TYPES.number),
|
@@ -2366,7 +2523,7 @@ const generateThrow = (scope, decl) => {
|
|
2366
2523
|
// hack: throw new X("...") -> throw "..."
|
2367
2524
|
if (!message && (decl.argument.type === 'NewExpression' || decl.argument.type === 'CallExpression')) {
|
2368
2525
|
constructor = decl.argument.callee.name;
|
2369
|
-
message = decl.argument.arguments[0]
|
2526
|
+
message = decl.argument.arguments[0]?.value ?? '';
|
2370
2527
|
}
|
2371
2528
|
|
2372
2529
|
if (tags.length === 0) tags.push({
|
@@ -2427,6 +2584,8 @@ const allocPage = (reason, type) => {
|
|
2427
2584
|
|
2428
2585
|
if (reason.startsWith('array:')) pages.hasArray = true;
|
2429
2586
|
if (reason.startsWith('string:')) pages.hasString = true;
|
2587
|
+
if (reason.startsWith('bytestring:')) pages.hasByteString = true;
|
2588
|
+
if (reason.includes('string:')) pages.hasAnyString = true;
|
2430
2589
|
|
2431
2590
|
const ind = pages.size;
|
2432
2591
|
pages.set(reason, { ind, type });
|
@@ -2460,7 +2619,8 @@ const StoreOps = {
|
|
2460
2619
|
f64: Opcodes.f64_store,
|
2461
2620
|
|
2462
2621
|
// expects i32 input!
|
2463
|
-
|
2622
|
+
i8: Opcodes.i32_store8,
|
2623
|
+
i16: Opcodes.i32_store16,
|
2464
2624
|
};
|
2465
2625
|
|
2466
2626
|
let data = [];
|
@@ -2479,6 +2639,15 @@ const compileBytes = (val, itemType, signed = true) => {
|
|
2479
2639
|
}
|
2480
2640
|
};
|
2481
2641
|
|
2642
|
+
const getAllocType = itemType => {
|
2643
|
+
switch (itemType) {
|
2644
|
+
case 'i8': return 'bytestring';
|
2645
|
+
case 'i16': return 'string';
|
2646
|
+
|
2647
|
+
default: return 'array';
|
2648
|
+
}
|
2649
|
+
};
|
2650
|
+
|
2482
2651
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
2483
2652
|
const out = [];
|
2484
2653
|
|
@@ -2488,7 +2657,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2488
2657
|
|
2489
2658
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
2490
2659
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
2491
|
-
arrays.set(name, allocPage(`${itemType
|
2660
|
+
arrays.set(name, allocPage(`${getAllocType(itemType)}: ${uniqueName}`, itemType) * pageSize);
|
2492
2661
|
}
|
2493
2662
|
|
2494
2663
|
const pointer = arrays.get(name);
|
@@ -2534,7 +2703,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2534
2703
|
out.push(
|
2535
2704
|
...number(0, Valtype.i32),
|
2536
2705
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
2537
|
-
[ storeOp, Math.log2(ValtypeSize[itemType]) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2706
|
+
[ storeOp, (Math.log2(ValtypeSize[itemType]) || 1) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + i * ValtypeSize[itemType]) ]
|
2538
2707
|
);
|
2539
2708
|
}
|
2540
2709
|
|
@@ -2544,15 +2713,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
2544
2713
|
return [ out, pointer ];
|
2545
2714
|
};
|
2546
2715
|
|
2716
|
+
const byteStringable = str => {
|
2717
|
+
if (!process.argv.includes('-bytestring')) return false;
|
2718
|
+
|
2719
|
+
for (let i = 0; i < str.length; i++) {
|
2720
|
+
if (str.charCodeAt(i) > 0xFF) return false;
|
2721
|
+
}
|
2722
|
+
|
2723
|
+
return true;
|
2724
|
+
};
|
2725
|
+
|
2547
2726
|
const makeString = (scope, str, global = false, name = '$undeclared') => {
|
2548
2727
|
const rawElements = new Array(str.length);
|
2728
|
+
let byteStringable = process.argv.includes('-bytestring');
|
2549
2729
|
for (let i = 0; i < str.length; i++) {
|
2550
|
-
|
2730
|
+
const c = str.charCodeAt(i);
|
2731
|
+
rawElements[i] = c;
|
2732
|
+
|
2733
|
+
if (byteStringable && c > 0xFF) byteStringable = false;
|
2551
2734
|
}
|
2552
2735
|
|
2553
2736
|
return makeArray(scope, {
|
2554
2737
|
rawElements
|
2555
|
-
}, global, name, false, 'i16')[0];
|
2738
|
+
}, global, name, false, byteStringable ? 'i8' : 'i16')[0];
|
2556
2739
|
};
|
2557
2740
|
|
2558
2741
|
let arrays = new Map();
|
@@ -2580,10 +2763,15 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2580
2763
|
];
|
2581
2764
|
}
|
2582
2765
|
|
2766
|
+
const object = generate(scope, decl.object);
|
2767
|
+
const property = generate(scope, decl.property);
|
2768
|
+
|
2769
|
+
console.log(decl.property, property);
|
2770
|
+
|
2583
2771
|
// // todo: we should only do this for strings but we don't know at compile-time :(
|
2584
2772
|
// hack: this is naughty and will break things!
|
2585
2773
|
let newOut = number(0, valtypeBinary), newPointer = -1;
|
2586
|
-
if (pages.
|
2774
|
+
if (pages.hasAnyString) {
|
2587
2775
|
0, [ newOut, newPointer ] = makeArray(scope, {
|
2588
2776
|
rawElements: new Array(1)
|
2589
2777
|
}, _global, _name, true, 'i16');
|
@@ -2592,7 +2780,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2592
2780
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
2593
2781
|
[TYPES._array]: [
|
2594
2782
|
// get index as valtype
|
2595
|
-
...
|
2783
|
+
...property,
|
2596
2784
|
|
2597
2785
|
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2598
2786
|
Opcodes.i32_to_u,
|
@@ -2600,7 +2788,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2600
2788
|
[ Opcodes.i32_mul ],
|
2601
2789
|
|
2602
2790
|
...(aotPointer ? [] : [
|
2603
|
-
...
|
2791
|
+
...object,
|
2604
2792
|
Opcodes.i32_to_u,
|
2605
2793
|
[ Opcodes.i32_add ]
|
2606
2794
|
]),
|
@@ -2619,14 +2807,14 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2619
2807
|
|
2620
2808
|
...number(0, Valtype.i32), // base 0 for store later
|
2621
2809
|
|
2622
|
-
...
|
2623
|
-
|
2810
|
+
...property,
|
2624
2811
|
Opcodes.i32_to_u,
|
2812
|
+
|
2625
2813
|
...number(ValtypeSize.i16, Valtype.i32),
|
2626
2814
|
[ Opcodes.i32_mul ],
|
2627
2815
|
|
2628
2816
|
...(aotPointer ? [] : [
|
2629
|
-
...
|
2817
|
+
...object,
|
2630
2818
|
Opcodes.i32_to_u,
|
2631
2819
|
[ Opcodes.i32_add ]
|
2632
2820
|
]),
|
@@ -2643,6 +2831,34 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
2643
2831
|
...number(TYPES.string, Valtype.i32),
|
2644
2832
|
setLastType(scope)
|
2645
2833
|
],
|
2834
|
+
[TYPES._bytestring]: [
|
2835
|
+
// setup new/out array
|
2836
|
+
...newOut,
|
2837
|
+
[ Opcodes.drop ],
|
2838
|
+
|
2839
|
+
...number(0, Valtype.i32), // base 0 for store later
|
2840
|
+
|
2841
|
+
...property,
|
2842
|
+
Opcodes.i32_to_u,
|
2843
|
+
|
2844
|
+
...(aotPointer ? [] : [
|
2845
|
+
...object,
|
2846
|
+
Opcodes.i32_to_u,
|
2847
|
+
[ Opcodes.i32_add ]
|
2848
|
+
]),
|
2849
|
+
|
2850
|
+
// load current string ind {arg}
|
2851
|
+
[ Opcodes.i32_load8_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
2852
|
+
|
2853
|
+
// store to new string ind 0
|
2854
|
+
[ Opcodes.i32_store8, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(newPointer + ValtypeSize.i32) ],
|
2855
|
+
|
2856
|
+
// return new string (page)
|
2857
|
+
...number(newPointer),
|
2858
|
+
|
2859
|
+
...number(TYPES._bytestring, Valtype.i32),
|
2860
|
+
setLastType(scope)
|
2861
|
+
],
|
2646
2862
|
|
2647
2863
|
default: [ [ Opcodes.unreachable ] ]
|
2648
2864
|
});
|
@@ -2661,11 +2877,14 @@ const objectHack = node => {
|
|
2661
2877
|
// if object is not identifier or another member exp, give up
|
2662
2878
|
if (node.object.type !== 'Identifier' && node.object.type !== 'MemberExpression') return node;
|
2663
2879
|
|
2664
|
-
if (!objectName) objectName = objectHack(node.object)
|
2880
|
+
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
2665
2881
|
|
2666
2882
|
// if .length, give up (hack within a hack!)
|
2667
2883
|
if (node.property.name === 'length') return node;
|
2668
2884
|
|
2885
|
+
// no object name, give up
|
2886
|
+
if (!objectName) return node;
|
2887
|
+
|
2669
2888
|
const name = '__' + objectName + '_' + node.property.name;
|
2670
2889
|
if (codeLog) log('codegen', `object hack! ${node.object.name}.${node.property.name} -> ${name}`);
|
2671
2890
|
|
@@ -2724,10 +2943,8 @@ const generateFunc = (scope, decl) => {
|
|
2724
2943
|
const func = {
|
2725
2944
|
name,
|
2726
2945
|
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
2727
|
-
|
2728
|
-
|
2729
|
-
throws: innerScope.throws,
|
2730
|
-
index: currentFuncIndex++
|
2946
|
+
index: currentFuncIndex++,
|
2947
|
+
...innerScope
|
2731
2948
|
};
|
2732
2949
|
funcIndex[name] = func.index;
|
2733
2950
|
|
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`)
|