porffor 0.14.0-af9ac5ad4 → 0.14.0-b1e1c2265
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/CONTRIBUTING.md +4 -2
- package/compiler/2c.js +3 -0
- package/compiler/assemble.js +14 -0
- package/compiler/builtins/annexb_string.ts +1 -0
- package/compiler/builtins/array.ts +78 -0
- package/compiler/builtins/base64.ts +1 -0
- package/compiler/builtins/boolean.ts +2 -0
- package/compiler/builtins/crypto.ts +1 -0
- package/compiler/builtins/date.ts +2 -0
- package/compiler/builtins/escape.ts +1 -2
- package/compiler/builtins/function.ts +2 -0
- package/compiler/builtins/int.ts +2 -0
- package/compiler/builtins/math.ts +410 -0
- package/compiler/builtins/number.ts +2 -0
- package/compiler/builtins/object.ts +2 -0
- package/compiler/builtins/set.ts +2 -0
- package/compiler/builtins/string.ts +1 -0
- package/compiler/builtins/symbol.ts +2 -0
- package/compiler/builtins.js +3 -4
- package/compiler/codegen.js +229 -153
- package/compiler/generated_builtins.js +318 -31
- package/compiler/precompile.js +5 -4
- package/package.json +1 -1
package/compiler/codegen.js
CHANGED
@@ -40,11 +40,11 @@ const todo = (scope, msg, expectsValue = undefined) => {
|
|
40
40
|
}
|
41
41
|
};
|
42
42
|
|
43
|
-
const isFuncType = type =>
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
const isFuncType = type =>
|
44
|
+
type === 'FunctionDeclaration' || type === 'FunctionExpression' || type === 'ArrowFunctionExpression';
|
45
|
+
const hasFuncWithName = name =>
|
46
|
+
funcIndex[name] != null || builtinFuncs[name] != null || importedFuncs[name] != null || internalConstrs[name] != null;
|
47
|
+
|
48
48
|
const generate = (scope, decl, global = false, name = undefined, valueUnused = false) => {
|
49
49
|
switch (decl.type) {
|
50
50
|
case 'BinaryExpression':
|
@@ -147,14 +147,16 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
147
147
|
return generateMember(scope, decl, global, name);
|
148
148
|
|
149
149
|
case 'ExportNamedDeclaration':
|
150
|
-
|
151
|
-
const funcsBefore = funcs.length;
|
150
|
+
const funcsBefore = funcs.map(x => x.name);
|
152
151
|
generate(scope, decl.declaration);
|
153
152
|
|
154
|
-
|
155
|
-
|
156
|
-
const
|
157
|
-
|
153
|
+
// set new funcs as exported
|
154
|
+
if (funcsBefore.length !== funcs.length) {
|
155
|
+
const newFuncs = funcs.filter(x => !funcsBefore.includes(x.name)).filter(x => !x.internal);
|
156
|
+
|
157
|
+
for (const x of newFuncs) {
|
158
|
+
x.export = true;
|
159
|
+
}
|
158
160
|
}
|
159
161
|
|
160
162
|
return [];
|
@@ -361,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
361
363
|
};
|
362
364
|
|
363
365
|
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
364
|
-
const
|
366
|
+
const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
365
367
|
|
366
368
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
367
369
|
const checks = {
|
@@ -378,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
378
380
|
|
379
381
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
380
382
|
// (like if we are in an if condition - very common)
|
381
|
-
const
|
382
|
-
const
|
383
|
+
const leftWasInt = isIntToFloatOp(left[left.length - 1]);
|
384
|
+
const rightWasInt = isIntToFloatOp(right[right.length - 1]);
|
383
385
|
|
384
|
-
const canInt =
|
386
|
+
const canInt = leftWasInt && rightWasInt;
|
385
387
|
|
386
388
|
if (canInt) {
|
387
389
|
// remove int -> float conversions from left and right
|
@@ -646,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
|
|
646
648
|
[ Opcodes.i32_add ],
|
647
649
|
[ Opcodes.local_tee, index ],
|
648
650
|
|
649
|
-
// if index
|
651
|
+
// if index < index end (length * sizeof valtype), loop
|
650
652
|
[ Opcodes.local_get, indexEnd ],
|
651
|
-
[ Opcodes.
|
653
|
+
[ Opcodes.i32_lt_s ],
|
652
654
|
[ Opcodes.br_if, 0 ],
|
653
655
|
[ Opcodes.end ],
|
654
656
|
|
@@ -666,8 +668,8 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
|
|
666
668
|
];
|
667
669
|
};
|
668
670
|
|
669
|
-
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
670
|
-
if (
|
671
|
+
const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
|
672
|
+
if (isIntToFloatOp(wasm[wasm.length - 1])) return [
|
671
673
|
...wasm,
|
672
674
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
673
675
|
];
|
@@ -676,47 +678,38 @@ const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
676
678
|
const useTmp = knownType(scope, type) == null;
|
677
679
|
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
678
680
|
|
679
|
-
const def =
|
680
|
-
|
681
|
-
|
681
|
+
const def = (truthyMode => {
|
682
|
+
if (truthyMode === 'full') return [
|
683
|
+
// if value != 0 or NaN
|
684
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
685
|
+
...(intIn ? [ ] : [ Opcodes.i32_to ]),
|
682
686
|
|
683
|
-
|
684
|
-
|
687
|
+
[ Opcodes.i32_eqz ],
|
688
|
+
[ Opcodes.i32_eqz ],
|
685
689
|
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ]),
|
704
|
-
|
705
|
-
/* Opcodes.eqz,
|
706
|
-
[ Opcodes.i32_eqz ],
|
707
|
-
Opcodes.i32_from */
|
708
|
-
];
|
690
|
+
...(intOut ? [] : [ Opcodes.i32_from ]),
|
691
|
+
];
|
692
|
+
|
693
|
+
if (truthyMode === 'no_negative') return [
|
694
|
+
// if value != 0 or NaN, non-binary output. negative numbers not truthy :/
|
695
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
696
|
+
...(intIn ? [] : [ Opcodes.i32_to ]),
|
697
|
+
...(intOut ? [] : [ Opcodes.i32_from ])
|
698
|
+
];
|
699
|
+
|
700
|
+
if (truthyMode === 'no_nan_negative') return [
|
701
|
+
// simpler and faster but makes NaN truthy and negative numbers not truthy,
|
702
|
+
// plus non-binary output
|
703
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
704
|
+
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
|
705
|
+
];
|
706
|
+
})(forceTruthyMode ?? Prefs.truthy ?? 'full');
|
709
707
|
|
710
708
|
return [
|
711
709
|
...wasm,
|
712
710
|
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
713
711
|
|
714
712
|
...typeSwitch(scope, type, {
|
715
|
-
// [TYPES.number]: def,
|
716
|
-
[TYPES.array]: [
|
717
|
-
// arrays are always truthy
|
718
|
-
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
719
|
-
],
|
720
713
|
[TYPES.string]: [
|
721
714
|
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
722
715
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
@@ -752,10 +745,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
752
745
|
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
753
746
|
|
754
747
|
...typeSwitch(scope, type, {
|
755
|
-
[TYPES.array]: [
|
756
|
-
// arrays are always truthy
|
757
|
-
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
758
|
-
],
|
759
748
|
[TYPES.string]: [
|
760
749
|
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
761
750
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
@@ -999,7 +988,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
999
988
|
// if both are true
|
1000
989
|
[ Opcodes.i32_and ],
|
1001
990
|
[ Opcodes.if, Blocktype.void ],
|
1002
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
991
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
|
1003
992
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
1004
993
|
[ Opcodes.br, 1 ],
|
1005
994
|
[ Opcodes.end ],
|
@@ -1048,14 +1037,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
1048
1037
|
return out;
|
1049
1038
|
};
|
1050
1039
|
|
1051
|
-
const asmFuncToAsm = (func,
|
1052
|
-
return func(
|
1040
|
+
const asmFuncToAsm = (func, scope) => {
|
1041
|
+
return func(scope, {
|
1053
1042
|
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
|
1054
|
-
builtin:
|
1055
|
-
let idx = funcIndex[
|
1056
|
-
if (idx
|
1057
|
-
includeBuiltin(null,
|
1058
|
-
idx = funcIndex[
|
1043
|
+
builtin: n => {
|
1044
|
+
let idx = funcIndex[n] ?? importedFuncs[n];
|
1045
|
+
if (idx == null && builtinFuncs[n]) {
|
1046
|
+
includeBuiltin(null, n);
|
1047
|
+
idx = funcIndex[n];
|
1059
1048
|
}
|
1060
1049
|
|
1061
1050
|
return idx;
|
@@ -1063,7 +1052,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
|
|
1063
1052
|
});
|
1064
1053
|
};
|
1065
1054
|
|
1066
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [],
|
1055
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
|
1067
1056
|
const existing = funcs.find(x => x.name === name);
|
1068
1057
|
if (existing) return existing;
|
1069
1058
|
|
@@ -1081,7 +1070,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1081
1070
|
data.push(copy);
|
1082
1071
|
}
|
1083
1072
|
|
1084
|
-
|
1073
|
+
const func = {
|
1074
|
+
name,
|
1075
|
+
params,
|
1076
|
+
locals,
|
1077
|
+
localInd: allLocals.length,
|
1078
|
+
returns,
|
1079
|
+
returnType: returnType ?? TYPES.number,
|
1080
|
+
internal: true,
|
1081
|
+
index: currentFuncIndex++,
|
1082
|
+
table
|
1083
|
+
};
|
1084
|
+
|
1085
|
+
funcs.push(func);
|
1086
|
+
funcIndex[name] = func.index;
|
1087
|
+
|
1088
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
|
1085
1089
|
|
1086
1090
|
let baseGlobalIdx, i = 0;
|
1087
1091
|
for (const type of globalTypes) {
|
@@ -1100,25 +1104,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1100
1104
|
}
|
1101
1105
|
}
|
1102
1106
|
|
1103
|
-
const
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
returns,
|
1108
|
-
returnType: returnType ?? TYPES.number,
|
1109
|
-
wasm,
|
1110
|
-
internal: true,
|
1111
|
-
index: currentFuncIndex++
|
1112
|
-
};
|
1113
|
-
|
1114
|
-
if (callsSelf) for (const inst of wasm) {
|
1115
|
-
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
1116
|
-
inst[1] = func.index;
|
1107
|
+
if (table) for (const inst of wasm) {
|
1108
|
+
if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
|
1109
|
+
inst.splice(2, 99);
|
1110
|
+
inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
|
1117
1111
|
}
|
1118
1112
|
}
|
1119
1113
|
|
1120
|
-
|
1121
|
-
funcIndex[name] = func.index;
|
1114
|
+
func.wasm = wasm;
|
1122
1115
|
|
1123
1116
|
return func;
|
1124
1117
|
};
|
@@ -1470,16 +1463,11 @@ const countLeftover = wasm => {
|
|
1470
1463
|
else if (inst[0] === Opcodes.return) count = 0;
|
1471
1464
|
else if (inst[0] === Opcodes.call) {
|
1472
1465
|
let func = funcs.find(x => x.index === inst[1]);
|
1473
|
-
if (inst[1]
|
1474
|
-
|
1475
|
-
|
1476
|
-
count -= importedFuncs[inst[1]].params;
|
1477
|
-
count += importedFuncs[inst[1]].returns;
|
1466
|
+
if (inst[1] < importedFuncs.length) {
|
1467
|
+
func = importedFuncs[inst[1]];
|
1468
|
+
count = count - func.params + func.returns;
|
1478
1469
|
} else {
|
1479
|
-
|
1480
|
-
count -= func.params.length;
|
1481
|
-
} else count--;
|
1482
|
-
if (func) count += func.returns.length;
|
1470
|
+
count = count - func.params.length + func.returns.length;
|
1483
1471
|
}
|
1484
1472
|
} else if (inst[0] === Opcodes.call_indirect) {
|
1485
1473
|
count--; // funcidx
|
@@ -1632,6 +1620,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1632
1620
|
|
1633
1621
|
if (!funcIndex[rhemynName]) {
|
1634
1622
|
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1623
|
+
func.internal = true;
|
1635
1624
|
|
1636
1625
|
funcIndex[func.name] = func.index;
|
1637
1626
|
funcs.push(func);
|
@@ -1803,11 +1792,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1803
1792
|
|
1804
1793
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1805
1794
|
|
1806
|
-
if (idx === undefined && name === scope.name) {
|
1807
|
-
// hack: calling self, func generator will fix later
|
1808
|
-
idx = -1;
|
1809
|
-
}
|
1810
|
-
|
1811
1795
|
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1812
1796
|
const wasmOps = {
|
1813
1797
|
// pointer, align, offset
|
@@ -1859,36 +1843,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1859
1843
|
const [ local, global ] = lookupName(scope, name);
|
1860
1844
|
if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1861
1845
|
|
1862
|
-
// todo: only works when
|
1863
|
-
|
1864
|
-
|
1846
|
+
// todo: only works when function uses typedParams and typedReturns
|
1847
|
+
|
1848
|
+
const indirectMode = Prefs.indirectCallMode ?? 'vararg';
|
1849
|
+
// options: vararg, strict
|
1850
|
+
// - strict: simpler, smaller size usage, no func argc lut needed.
|
1851
|
+
// ONLY works when arg count of call == arg count of function being called
|
1852
|
+
// - vararg: large size usage, cursed.
|
1853
|
+
// works when arg count of call != arg count of function being called*
|
1854
|
+
// * most of the time, some edgecases
|
1865
1855
|
|
1866
1856
|
funcs.table = true;
|
1857
|
+
scope.table = true;
|
1867
1858
|
|
1868
1859
|
let args = decl.arguments;
|
1869
|
-
let
|
1860
|
+
let out = [];
|
1861
|
+
|
1862
|
+
let locals = [];
|
1863
|
+
|
1864
|
+
if (indirectMode === 'vararg') {
|
1865
|
+
const minArgc = Prefs.indirectCallMinArgc ?? 3;
|
1866
|
+
|
1867
|
+
if (args.length < minArgc) {
|
1868
|
+
args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
|
1869
|
+
}
|
1870
|
+
}
|
1870
1871
|
|
1871
1872
|
for (let i = 0; i < args.length; i++) {
|
1872
1873
|
const arg = args[i];
|
1873
|
-
|
1874
|
+
out = out.concat(generate(scope, arg));
|
1874
1875
|
|
1875
1876
|
if (valtypeBinary !== Valtype.i32 && (
|
1876
1877
|
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
1877
1878
|
(importedFuncs[name] && name.startsWith('profile'))
|
1878
1879
|
)) {
|
1879
|
-
|
1880
|
+
out.push(Opcodes.i32_to);
|
1881
|
+
}
|
1882
|
+
|
1883
|
+
out = out.concat(getNodeType(scope, arg));
|
1884
|
+
|
1885
|
+
if (indirectMode === 'vararg') {
|
1886
|
+
const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
|
1887
|
+
const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
|
1888
|
+
|
1889
|
+
locals.push([valLocal, typeLocal]);
|
1890
|
+
|
1891
|
+
out.push(
|
1892
|
+
[ Opcodes.local_set, typeLocal ],
|
1893
|
+
[ Opcodes.local_set, valLocal ]
|
1894
|
+
);
|
1895
|
+
}
|
1896
|
+
}
|
1897
|
+
|
1898
|
+
if (indirectMode === 'strict') {
|
1899
|
+
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1900
|
+
[TYPES.function]: [
|
1901
|
+
...argWasm,
|
1902
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1903
|
+
Opcodes.i32_to_u,
|
1904
|
+
[ Opcodes.call_indirect, args.length, 0 ],
|
1905
|
+
...setLastType(scope)
|
1906
|
+
],
|
1907
|
+
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1908
|
+
});
|
1909
|
+
}
|
1910
|
+
|
1911
|
+
// hi, I will now explain how vararg mode works:
|
1912
|
+
// wasm's indirect_call instruction requires you know the func type at compile-time
|
1913
|
+
// since we have varargs (variable argument count), we do not know it.
|
1914
|
+
// we could just store args in memory and not use wasm func args,
|
1915
|
+
// but that is slow (probably) and breaks js exports.
|
1916
|
+
// instead, we generate every* possibility of argc and use different indirect_call
|
1917
|
+
// ops for each one, with type depending on argc for that branch.
|
1918
|
+
// then we load the argc for the wanted function from a memory lut,
|
1919
|
+
// and call the branch with the matching argc we require.
|
1920
|
+
// sorry, yes it is very cursed (and size inefficient), but indirect calls
|
1921
|
+
// are kind of rare anyway (mostly callbacks) so I am not concerned atm.
|
1922
|
+
// *for argc 0-3, in future (todo:) the max number should be
|
1923
|
+
// dynamically changed to the max argc of any func in the js file.
|
1924
|
+
|
1925
|
+
const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
|
1926
|
+
|
1927
|
+
const gen = argc => {
|
1928
|
+
const out = [];
|
1929
|
+
for (let i = 0; i < argc; i++) {
|
1930
|
+
out.push(
|
1931
|
+
[ Opcodes.local_get, locals[i][0] ],
|
1932
|
+
[ Opcodes.local_get, locals[i][1] ]
|
1933
|
+
);
|
1880
1934
|
}
|
1881
1935
|
|
1882
|
-
|
1936
|
+
out.push(
|
1937
|
+
[ Opcodes.local_get, funcLocal ],
|
1938
|
+
[ Opcodes.call_indirect, argc, 0 ],
|
1939
|
+
...setLastType(scope)
|
1940
|
+
)
|
1941
|
+
|
1942
|
+
return out;
|
1943
|
+
};
|
1944
|
+
|
1945
|
+
const tableBc = {};
|
1946
|
+
for (let i = 0; i <= args.length; i++) {
|
1947
|
+
tableBc[i] = gen(i);
|
1883
1948
|
}
|
1884
1949
|
|
1950
|
+
// todo/perf: check if we should use br_table here or just generate our own big if..elses
|
1951
|
+
|
1885
1952
|
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1886
1953
|
[TYPES.function]: [
|
1887
|
-
...
|
1954
|
+
...out,
|
1955
|
+
|
1888
1956
|
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1889
1957
|
Opcodes.i32_to_u,
|
1890
|
-
[ Opcodes.
|
1891
|
-
|
1958
|
+
[ Opcodes.local_set, funcLocal ],
|
1959
|
+
|
1960
|
+
...brTable([
|
1961
|
+
// get argc of func we are calling
|
1962
|
+
[ Opcodes.local_get, funcLocal ],
|
1963
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
1964
|
+
[ Opcodes.i32_mul ],
|
1965
|
+
|
1966
|
+
[ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
|
1967
|
+
], tableBc, valtypeBinary)
|
1892
1968
|
],
|
1893
1969
|
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1894
1970
|
});
|
@@ -1897,11 +1973,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1897
1973
|
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1898
1974
|
}
|
1899
1975
|
|
1900
|
-
const func = funcs.find(x => x.index === idx);
|
1901
|
-
|
1902
|
-
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1976
|
+
const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
|
1977
|
+
const userFunc = func && !func.internal;
|
1903
1978
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1904
|
-
const typedReturns = (func
|
1979
|
+
const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
|
1905
1980
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1906
1981
|
|
1907
1982
|
let args = decl.arguments;
|
@@ -2046,16 +2121,17 @@ const brTable = (input, bc, returns) => {
|
|
2046
2121
|
}
|
2047
2122
|
|
2048
2123
|
for (let i = 0; i < count; i++) {
|
2049
|
-
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2124
|
+
// if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2125
|
+
if (i === 0) out.push([ Opcodes.block, returns ]);
|
2050
2126
|
else out.push([ Opcodes.block, Blocktype.void ]);
|
2051
2127
|
}
|
2052
2128
|
|
2053
|
-
const nums = keys.filter(x => +x);
|
2129
|
+
const nums = keys.filter(x => +x >= 0);
|
2054
2130
|
const offset = Math.min(...nums);
|
2055
2131
|
const max = Math.max(...nums);
|
2056
2132
|
|
2057
2133
|
const table = [];
|
2058
|
-
let br =
|
2134
|
+
let br = 0;
|
2059
2135
|
|
2060
2136
|
for (let i = offset; i <= max; i++) {
|
2061
2137
|
// if branch for this num, go to that block
|
@@ -2095,10 +2171,9 @@ const brTable = (input, bc, returns) => {
|
|
2095
2171
|
br--;
|
2096
2172
|
}
|
2097
2173
|
|
2098
|
-
|
2099
|
-
|
2100
|
-
|
2101
|
-
];
|
2174
|
+
out.push([ Opcodes.end ]);
|
2175
|
+
|
2176
|
+
return out;
|
2102
2177
|
};
|
2103
2178
|
|
2104
2179
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
@@ -2504,6 +2579,7 @@ const generateUnary = (scope, decl) => {
|
|
2504
2579
|
];
|
2505
2580
|
|
2506
2581
|
case '!':
|
2582
|
+
// todo/perf: optimize !!
|
2507
2583
|
// !=
|
2508
2584
|
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
|
2509
2585
|
|
@@ -3043,14 +3119,18 @@ const generateThrow = (scope, decl) => {
|
|
3043
3119
|
};
|
3044
3120
|
|
3045
3121
|
const generateTry = (scope, decl) => {
|
3046
|
-
|
3122
|
+
// todo: handle control-flow pre-exit for finally
|
3123
|
+
// "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
|
3047
3124
|
|
3048
3125
|
const out = [];
|
3049
3126
|
|
3127
|
+
const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
|
3128
|
+
|
3050
3129
|
out.push([ Opcodes.try, Blocktype.void ]);
|
3051
3130
|
depth.push('try');
|
3052
3131
|
|
3053
3132
|
out.push(...generate(scope, decl.block));
|
3133
|
+
out.push(...finalizer);
|
3054
3134
|
|
3055
3135
|
if (decl.handler) {
|
3056
3136
|
depth.pop();
|
@@ -3058,6 +3138,7 @@ const generateTry = (scope, decl) => {
|
|
3058
3138
|
|
3059
3139
|
out.push([ Opcodes.catch_all ]);
|
3060
3140
|
out.push(...generate(scope, decl.handler.body));
|
3141
|
+
out.push(...finalizer);
|
3061
3142
|
}
|
3062
3143
|
|
3063
3144
|
out.push([ Opcodes.end ]);
|
@@ -3155,8 +3236,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3155
3236
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
3156
3237
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
3157
3238
|
|
3158
|
-
|
3159
|
-
|
3239
|
+
let page;
|
3240
|
+
if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
|
3241
|
+
else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
|
3242
|
+
|
3243
|
+
// hack: use 1 for page 0 pointer for fast truthiness
|
3244
|
+
const ptr = page === 0 ? 1 : (page * pageSize);
|
3245
|
+
scope.arrays.set(name, ptr);
|
3160
3246
|
}
|
3161
3247
|
|
3162
3248
|
const pointer = scope.arrays.get(name);
|
@@ -3365,8 +3451,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
3365
3451
|
if (decl.property.name === 'length') {
|
3366
3452
|
const func = funcs.find(x => x.name === name);
|
3367
3453
|
if (func) {
|
3368
|
-
const
|
3369
|
-
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3454
|
+
const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
|
3370
3455
|
return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
|
3371
3456
|
}
|
3372
3457
|
|
@@ -3592,33 +3677,39 @@ const generateFunc = (scope, decl) => {
|
|
3592
3677
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
3593
3678
|
const params = decl.params ?? [];
|
3594
3679
|
|
3595
|
-
// const innerScope = { ...scope };
|
3596
3680
|
// TODO: share scope/locals between !!!
|
3597
|
-
const
|
3681
|
+
const func = {
|
3598
3682
|
locals: {},
|
3599
3683
|
localInd: 0,
|
3600
3684
|
// value, type
|
3601
3685
|
returns: [ valtypeBinary, Valtype.i32 ],
|
3602
3686
|
throws: false,
|
3603
|
-
name
|
3687
|
+
name,
|
3688
|
+
index: currentFuncIndex++
|
3604
3689
|
};
|
3605
3690
|
|
3606
3691
|
if (typedInput && decl.returnType) {
|
3607
3692
|
const { type } = extractTypeAnnotation(decl.returnType);
|
3608
|
-
if (type != null && !Prefs.indirectCalls) {
|
3609
|
-
|
3610
|
-
|
3693
|
+
// if (type != null && !Prefs.indirectCalls) {
|
3694
|
+
if (type != null) {
|
3695
|
+
func.returnType = type;
|
3696
|
+
func.returns = [ valtypeBinary ];
|
3611
3697
|
}
|
3612
3698
|
}
|
3613
3699
|
|
3614
3700
|
for (let i = 0; i < params.length; i++) {
|
3615
|
-
|
3701
|
+
const name = params[i].name;
|
3702
|
+
// if (name == null) return todo('non-identifier args are not supported');
|
3703
|
+
|
3704
|
+
allocVar(func, name, false);
|
3616
3705
|
|
3617
3706
|
if (typedInput && params[i].typeAnnotation) {
|
3618
|
-
addVarMetadata(
|
3707
|
+
addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
|
3619
3708
|
}
|
3620
3709
|
}
|
3621
3710
|
|
3711
|
+
func.params = Object.values(func.locals).map(x => x.type);
|
3712
|
+
|
3622
3713
|
let body = objectHack(decl.body);
|
3623
3714
|
if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
|
3624
3715
|
// hack: () => 0 -> () => return 0
|
@@ -3628,37 +3719,23 @@ const generateFunc = (scope, decl) => {
|
|
3628
3719
|
};
|
3629
3720
|
}
|
3630
3721
|
|
3631
|
-
const wasm = generate(innerScope, body);
|
3632
|
-
const func = {
|
3633
|
-
name,
|
3634
|
-
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
3635
|
-
index: currentFuncIndex++,
|
3636
|
-
...innerScope
|
3637
|
-
};
|
3638
3722
|
funcIndex[name] = func.index;
|
3723
|
+
funcs.push(func);
|
3639
3724
|
|
3640
|
-
|
3725
|
+
const wasm = generate(func, body);
|
3726
|
+
func.wasm = wasm;
|
3641
3727
|
|
3642
|
-
|
3643
|
-
for (const inst of wasm) {
|
3644
|
-
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
3645
|
-
inst[1] = func.index;
|
3646
|
-
}
|
3647
|
-
}
|
3728
|
+
if (name === 'main') func.gotLastType = true;
|
3648
3729
|
|
3649
3730
|
// add end return if not found
|
3650
3731
|
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
3651
3732
|
wasm.push(
|
3652
3733
|
...number(0),
|
3653
|
-
...(
|
3734
|
+
...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
|
3654
3735
|
[ Opcodes.return ]
|
3655
3736
|
);
|
3656
3737
|
}
|
3657
3738
|
|
3658
|
-
func.wasm = wasm;
|
3659
|
-
|
3660
|
-
funcs.push(func);
|
3661
|
-
|
3662
3739
|
return func;
|
3663
3740
|
};
|
3664
3741
|
|
@@ -3762,7 +3839,7 @@ const internalConstrs = {
|
|
3762
3839
|
generate: (scope, decl) => {
|
3763
3840
|
// todo: boolean object when used as constructor
|
3764
3841
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3765
|
-
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3842
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
|
3766
3843
|
},
|
3767
3844
|
type: TYPES.boolean,
|
3768
3845
|
length: 1
|
@@ -3907,9 +3984,8 @@ export default program => {
|
|
3907
3984
|
|
3908
3985
|
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3909
3986
|
|
3910
|
-
generateFunc(scope, program);
|
3987
|
+
const main = generateFunc(scope, program);
|
3911
3988
|
|
3912
|
-
const main = funcs[funcs.length - 1];
|
3913
3989
|
main.export = true;
|
3914
3990
|
main.returns = [ valtypeBinary, Valtype.i32 ];
|
3915
3991
|
|
@@ -3936,7 +4012,7 @@ export default program => {
|
|
3936
4012
|
}
|
3937
4013
|
|
3938
4014
|
// if blank main func and other exports, remove it
|
3939
|
-
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(
|
4015
|
+
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
|
3940
4016
|
|
3941
4017
|
return { funcs, globals, tags, exceptions, pages, data };
|
3942
4018
|
};
|