porffor 0.14.0-eca486960 → 0.16.0-053a03e10
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 +15 -9
- package/README.md +9 -13
- package/asur/index.js +1 -1
- package/compiler/2c.js +104 -51
- package/compiler/assemble.js +18 -3
- package/compiler/builtins/annexb_string.ts +1 -0
- package/compiler/builtins/array.ts +84 -4
- package/compiler/builtins/base64.ts +1 -0
- package/compiler/builtins/boolean.ts +2 -0
- package/compiler/builtins/console.ts +6 -0
- package/compiler/builtins/crypto.ts +1 -0
- package/compiler/builtins/date.ts +2 -0
- package/compiler/builtins/error.js +22 -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/porffor.d.ts +11 -0
- package/compiler/builtins/set.ts +15 -1
- package/compiler/builtins/string.ts +1 -0
- package/compiler/builtins/symbol.ts +8 -7
- package/compiler/builtins.js +46 -10
- package/compiler/codegen.js +384 -192
- package/compiler/cyclone.js +535 -0
- package/compiler/decompile.js +6 -0
- package/compiler/generated_builtins.js +503 -53
- package/compiler/havoc.js +93 -0
- package/compiler/index.js +78 -7
- package/compiler/opt.js +3 -39
- package/compiler/parse.js +2 -2
- package/compiler/pgo.js +206 -0
- package/compiler/precompile.js +5 -4
- package/compiler/prefs.js +7 -2
- package/compiler/prototype.js +180 -157
- package/compiler/wrap.js +71 -16
- package/no_pgo.txt +923 -0
- package/package.json +1 -1
- package/pgo.txt +916 -0
- package/runner/index.js +18 -12
- package/runner/repl.js +18 -2
- /package/runner/{profiler.js → profile.js} +0 -0
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
|
@@ -444,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
|
|
444
446
|
...number(0, Valtype.i32), // base 0 for store later
|
445
447
|
|
446
448
|
...number(pointer, Valtype.i32),
|
447
|
-
[ Opcodes.i32_load,
|
449
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
448
450
|
[ Opcodes.local_tee, leftLength ],
|
449
451
|
|
450
452
|
[ Opcodes.local_get, rightPointer ],
|
451
|
-
[ Opcodes.i32_load,
|
453
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
452
454
|
[ Opcodes.local_tee, rightLength ],
|
453
455
|
|
454
456
|
[ Opcodes.i32_add ],
|
@@ -504,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
|
|
504
506
|
...number(0, Valtype.i32), // base 0 for store later
|
505
507
|
|
506
508
|
[ Opcodes.local_get, leftPointer ],
|
507
|
-
[ Opcodes.i32_load,
|
509
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
508
510
|
[ Opcodes.local_tee, leftLength ],
|
509
511
|
|
510
512
|
[ Opcodes.local_get, rightPointer ],
|
511
|
-
[ Opcodes.i32_load,
|
513
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
512
514
|
[ Opcodes.local_tee, rightLength ],
|
513
515
|
|
514
516
|
[ Opcodes.i32_add ],
|
@@ -586,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
|
|
586
588
|
|
587
589
|
// get lengths
|
588
590
|
[ Opcodes.local_get, leftPointer ],
|
589
|
-
[ Opcodes.i32_load,
|
591
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
590
592
|
[ Opcodes.local_tee, leftLength ],
|
591
593
|
|
592
594
|
[ Opcodes.local_get, rightPointer ],
|
593
|
-
[ Opcodes.i32_load,
|
595
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
594
596
|
|
595
597
|
// fast path: check leftLength != rightLength
|
596
598
|
[ Opcodes.i32_ne ],
|
@@ -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,38 +668,50 @@ 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
|
];
|
674
676
|
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
675
677
|
|
678
|
+
// todo/perf: use knownType and custom bytecode here instead of typeSwitch
|
679
|
+
|
676
680
|
const useTmp = knownType(scope, type) == null;
|
677
681
|
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
678
682
|
|
679
|
-
const def =
|
680
|
-
|
681
|
-
|
683
|
+
const def = (truthyMode => {
|
684
|
+
if (truthyMode === 'full') return [
|
685
|
+
// if value != 0 or NaN
|
686
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
687
|
+
...(intIn ? [ ] : [ Opcodes.i32_to ]),
|
682
688
|
|
683
|
-
|
684
|
-
|
689
|
+
[ Opcodes.i32_eqz ],
|
690
|
+
[ Opcodes.i32_eqz ],
|
685
691
|
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
692
|
+
...(intOut ? [] : [ Opcodes.i32_from ]),
|
693
|
+
];
|
694
|
+
|
695
|
+
if (truthyMode === 'no_negative') return [
|
696
|
+
// if value != 0 or NaN, non-binary output. negative numbers not truthy :/
|
697
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
698
|
+
...(intIn ? [] : [ Opcodes.i32_to ]),
|
699
|
+
...(intOut ? [] : [ Opcodes.i32_from ])
|
700
|
+
];
|
701
|
+
|
702
|
+
if (truthyMode === 'no_nan_negative') return [
|
703
|
+
// simpler and faster but makes NaN truthy and negative numbers not truthy,
|
704
|
+
// plus non-binary output
|
705
|
+
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
706
|
+
...(!intOut || (intIn && intOut) ? [] : [ Opcodes.i32_to_u ])
|
707
|
+
];
|
708
|
+
})(forceTruthyMode ?? Prefs.truthy ?? 'full');
|
690
709
|
|
691
710
|
return [
|
692
711
|
...wasm,
|
693
712
|
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
694
713
|
|
695
714
|
...typeSwitch(scope, type, {
|
696
|
-
// [TYPES.number]: def,
|
697
|
-
[TYPES.array]: [
|
698
|
-
// arrays are always truthy
|
699
|
-
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
700
|
-
],
|
701
715
|
[TYPES.string]: [
|
702
716
|
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
703
717
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
@@ -733,10 +747,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
733
747
|
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
734
748
|
|
735
749
|
...typeSwitch(scope, type, {
|
736
|
-
[TYPES.array]: [
|
737
|
-
// arrays are always truthy
|
738
|
-
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
739
|
-
],
|
740
750
|
[TYPES.string]: [
|
741
751
|
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
742
752
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
@@ -980,7 +990,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
980
990
|
// if both are true
|
981
991
|
[ Opcodes.i32_and ],
|
982
992
|
[ Opcodes.if, Blocktype.void ],
|
983
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
993
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
|
984
994
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
985
995
|
[ Opcodes.br, 1 ],
|
986
996
|
[ Opcodes.end ],
|
@@ -1029,14 +1039,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
1029
1039
|
return out;
|
1030
1040
|
};
|
1031
1041
|
|
1032
|
-
const asmFuncToAsm = (func,
|
1033
|
-
return func(
|
1042
|
+
const asmFuncToAsm = (func, scope) => {
|
1043
|
+
return func(scope, {
|
1034
1044
|
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
|
1035
|
-
builtin:
|
1036
|
-
let idx = funcIndex[
|
1037
|
-
if (idx
|
1038
|
-
includeBuiltin(null,
|
1039
|
-
idx = funcIndex[
|
1045
|
+
builtin: n => {
|
1046
|
+
let idx = funcIndex[n] ?? importedFuncs[n];
|
1047
|
+
if (idx == null && builtinFuncs[n]) {
|
1048
|
+
includeBuiltin(null, n);
|
1049
|
+
idx = funcIndex[n];
|
1040
1050
|
}
|
1041
1051
|
|
1042
1052
|
return idx;
|
@@ -1044,7 +1054,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
|
|
1044
1054
|
});
|
1045
1055
|
};
|
1046
1056
|
|
1047
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [] }) => {
|
1057
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [], data: _data = [], table = false }) => {
|
1048
1058
|
const existing = funcs.find(x => x.name === name);
|
1049
1059
|
if (existing) return existing;
|
1050
1060
|
|
@@ -1062,7 +1072,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1062
1072
|
data.push(copy);
|
1063
1073
|
}
|
1064
1074
|
|
1065
|
-
|
1075
|
+
const func = {
|
1076
|
+
name,
|
1077
|
+
params,
|
1078
|
+
locals,
|
1079
|
+
localInd: allLocals.length,
|
1080
|
+
returns,
|
1081
|
+
returnType,
|
1082
|
+
internal: true,
|
1083
|
+
index: currentFuncIndex++,
|
1084
|
+
table
|
1085
|
+
};
|
1086
|
+
|
1087
|
+
funcs.push(func);
|
1088
|
+
funcIndex[name] = func.index;
|
1089
|
+
|
1090
|
+
if (typeof wasm === 'function') wasm = asmFuncToAsm(wasm, func);
|
1066
1091
|
|
1067
1092
|
let baseGlobalIdx, i = 0;
|
1068
1093
|
for (const type of globalTypes) {
|
@@ -1081,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1081
1106
|
}
|
1082
1107
|
}
|
1083
1108
|
|
1084
|
-
const
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
wasm,
|
1091
|
-
internal: true,
|
1092
|
-
index: currentFuncIndex++
|
1093
|
-
};
|
1109
|
+
if (table) for (const inst of wasm) {
|
1110
|
+
if (inst[0] === Opcodes.i32_load16_u && inst.at(-1) === 'read_argc') {
|
1111
|
+
inst.splice(2, 99);
|
1112
|
+
inst.push(...unsignedLEB128(allocPage({}, 'func argc lut') * pageSize));
|
1113
|
+
}
|
1114
|
+
}
|
1094
1115
|
|
1095
|
-
|
1096
|
-
funcIndex[name] = func.index;
|
1116
|
+
func.wasm = wasm;
|
1097
1117
|
|
1098
1118
|
return func;
|
1099
1119
|
};
|
@@ -1234,7 +1254,7 @@ const getNodeType = (scope, node) => {
|
|
1234
1254
|
const func = funcs.find(x => x.name === name);
|
1235
1255
|
|
1236
1256
|
if (func) {
|
1237
|
-
if (func.returnType) return func.returnType;
|
1257
|
+
if (func.returnType != null) return func.returnType;
|
1238
1258
|
}
|
1239
1259
|
|
1240
1260
|
if (builtinFuncs[name] && !builtinFuncs[name].typedReturns) return builtinFuncs[name].returnType ?? TYPES.number;
|
@@ -1249,7 +1269,9 @@ const getNodeType = (scope, node) => {
|
|
1249
1269
|
|
1250
1270
|
const func = spl[spl.length - 1];
|
1251
1271
|
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1252
|
-
if (protoFuncs.length === 1)
|
1272
|
+
if (protoFuncs.length === 1) {
|
1273
|
+
if (protoFuncs[0].returnType != null) return protoFuncs[0].returnType;
|
1274
|
+
}
|
1253
1275
|
}
|
1254
1276
|
|
1255
1277
|
if (name.startsWith('__Porffor_wasm_')) {
|
@@ -1435,16 +1457,11 @@ const countLeftover = wasm => {
|
|
1435
1457
|
else if (inst[0] === Opcodes.return) count = 0;
|
1436
1458
|
else if (inst[0] === Opcodes.call) {
|
1437
1459
|
let func = funcs.find(x => x.index === inst[1]);
|
1438
|
-
if (inst[1]
|
1439
|
-
|
1440
|
-
|
1441
|
-
count -= importedFuncs[inst[1]].params;
|
1442
|
-
count += importedFuncs[inst[1]].returns;
|
1460
|
+
if (inst[1] < importedFuncs.length) {
|
1461
|
+
func = importedFuncs[inst[1]];
|
1462
|
+
count = count - func.params + func.returns;
|
1443
1463
|
} else {
|
1444
|
-
|
1445
|
-
count -= func.params.length;
|
1446
|
-
} else count--;
|
1447
|
-
if (func) count += func.returns.length;
|
1464
|
+
count = count - func.params.length + func.returns.length;
|
1448
1465
|
}
|
1449
1466
|
} else if (inst[0] === Opcodes.call_indirect) {
|
1450
1467
|
count--; // funcidx
|
@@ -1467,7 +1484,7 @@ const disposeLeftover = wasm => {
|
|
1467
1484
|
const generateExp = (scope, decl) => {
|
1468
1485
|
const expression = decl.expression;
|
1469
1486
|
|
1470
|
-
const out = generate(scope, expression, undefined, undefined,
|
1487
|
+
const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
|
1471
1488
|
disposeLeftover(out);
|
1472
1489
|
|
1473
1490
|
return out;
|
@@ -1597,6 +1614,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1597
1614
|
|
1598
1615
|
if (!funcIndex[rhemynName]) {
|
1599
1616
|
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1617
|
+
func.internal = true;
|
1600
1618
|
|
1601
1619
|
funcIndex[func.name] = func.index;
|
1602
1620
|
funcs.push(func);
|
@@ -1704,7 +1722,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1704
1722
|
getI32: () => RTArrayUtil.getLengthI32(getPointer),
|
1705
1723
|
set: value => RTArrayUtil.setLength(getPointer, value),
|
1706
1724
|
setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
|
1707
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1725
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1708
1726
|
return makeArray(scope, {
|
1709
1727
|
rawElements: new Array(length)
|
1710
1728
|
}, _global, _name, true, itemType);
|
@@ -1718,7 +1736,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1718
1736
|
protoBC[x] = [
|
1719
1737
|
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1720
1738
|
...protoOut,
|
1721
|
-
...setLastType(scope, protoFunc.returnType
|
1739
|
+
...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
|
1722
1740
|
[ Opcodes.end ]
|
1723
1741
|
];
|
1724
1742
|
}
|
@@ -1768,11 +1786,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1768
1786
|
|
1769
1787
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1770
1788
|
|
1771
|
-
if (idx === undefined && name === scope.name) {
|
1772
|
-
// hack: calling self, func generator will fix later
|
1773
|
-
idx = -1;
|
1774
|
-
}
|
1775
|
-
|
1776
1789
|
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1777
1790
|
const wasmOps = {
|
1778
1791
|
// pointer, align, offset
|
@@ -1794,7 +1807,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1794
1807
|
f64_store: { imms: 2, args: [ true, false ], returns: 0 },
|
1795
1808
|
|
1796
1809
|
// value
|
1797
|
-
i32_const: { imms: 1, args: [], returns:
|
1810
|
+
i32_const: { imms: 1, args: [], returns: 0 },
|
1798
1811
|
};
|
1799
1812
|
|
1800
1813
|
const opName = name.slice('__Porffor_wasm_'.length);
|
@@ -1824,36 +1837,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1824
1837
|
const [ local, global ] = lookupName(scope, name);
|
1825
1838
|
if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1826
1839
|
|
1827
|
-
// todo: only works when
|
1828
|
-
|
1829
|
-
|
1840
|
+
// todo: only works when function uses typedParams and typedReturns
|
1841
|
+
|
1842
|
+
const indirectMode = Prefs.indirectCallMode ?? 'vararg';
|
1843
|
+
// options: vararg, strict
|
1844
|
+
// - strict: simpler, smaller size usage, no func argc lut needed.
|
1845
|
+
// ONLY works when arg count of call == arg count of function being called
|
1846
|
+
// - vararg: large size usage, cursed.
|
1847
|
+
// works when arg count of call != arg count of function being called*
|
1848
|
+
// * most of the time, some edgecases
|
1830
1849
|
|
1831
1850
|
funcs.table = true;
|
1851
|
+
scope.table = true;
|
1832
1852
|
|
1833
1853
|
let args = decl.arguments;
|
1834
|
-
let
|
1854
|
+
let out = [];
|
1855
|
+
|
1856
|
+
let locals = [];
|
1857
|
+
|
1858
|
+
if (indirectMode === 'vararg') {
|
1859
|
+
const minArgc = Prefs.indirectCallMinArgc ?? 3;
|
1860
|
+
|
1861
|
+
if (args.length < minArgc) {
|
1862
|
+
args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
|
1863
|
+
}
|
1864
|
+
}
|
1835
1865
|
|
1836
1866
|
for (let i = 0; i < args.length; i++) {
|
1837
1867
|
const arg = args[i];
|
1838
|
-
|
1868
|
+
out = out.concat(generate(scope, arg));
|
1839
1869
|
|
1840
1870
|
if (valtypeBinary !== Valtype.i32 && (
|
1841
1871
|
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
1842
1872
|
(importedFuncs[name] && name.startsWith('profile'))
|
1843
1873
|
)) {
|
1844
|
-
|
1874
|
+
out.push(Opcodes.i32_to);
|
1875
|
+
}
|
1876
|
+
|
1877
|
+
out = out.concat(getNodeType(scope, arg));
|
1878
|
+
|
1879
|
+
if (indirectMode === 'vararg') {
|
1880
|
+
const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
|
1881
|
+
const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
|
1882
|
+
|
1883
|
+
locals.push([valLocal, typeLocal]);
|
1884
|
+
|
1885
|
+
out.push(
|
1886
|
+
[ Opcodes.local_set, typeLocal ],
|
1887
|
+
[ Opcodes.local_set, valLocal ]
|
1888
|
+
);
|
1845
1889
|
}
|
1890
|
+
}
|
1891
|
+
|
1892
|
+
if (indirectMode === 'strict') {
|
1893
|
+
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1894
|
+
[TYPES.function]: [
|
1895
|
+
...argWasm,
|
1896
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1897
|
+
Opcodes.i32_to_u,
|
1898
|
+
[ Opcodes.call_indirect, args.length, 0 ],
|
1899
|
+
...setLastType(scope)
|
1900
|
+
],
|
1901
|
+
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1902
|
+
});
|
1903
|
+
}
|
1904
|
+
|
1905
|
+
// hi, I will now explain how vararg mode works:
|
1906
|
+
// wasm's indirect_call instruction requires you know the func type at compile-time
|
1907
|
+
// since we have varargs (variable argument count), we do not know it.
|
1908
|
+
// we could just store args in memory and not use wasm func args,
|
1909
|
+
// but that is slow (probably) and breaks js exports.
|
1910
|
+
// instead, we generate every* possibility of argc and use different indirect_call
|
1911
|
+
// ops for each one, with type depending on argc for that branch.
|
1912
|
+
// then we load the argc for the wanted function from a memory lut,
|
1913
|
+
// and call the branch with the matching argc we require.
|
1914
|
+
// sorry, yes it is very cursed (and size inefficient), but indirect calls
|
1915
|
+
// are kind of rare anyway (mostly callbacks) so I am not concerned atm.
|
1916
|
+
// *for argc 0-3, in future (todo:) the max number should be
|
1917
|
+
// dynamically changed to the max argc of any func in the js file.
|
1918
|
+
|
1919
|
+
const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
|
1920
|
+
|
1921
|
+
const gen = argc => {
|
1922
|
+
const out = [];
|
1923
|
+
for (let i = 0; i < argc; i++) {
|
1924
|
+
out.push(
|
1925
|
+
[ Opcodes.local_get, locals[i][0] ],
|
1926
|
+
[ Opcodes.local_get, locals[i][1] ]
|
1927
|
+
);
|
1928
|
+
}
|
1929
|
+
|
1930
|
+
out.push(
|
1931
|
+
[ Opcodes.local_get, funcLocal ],
|
1932
|
+
[ Opcodes.call_indirect, argc, 0 ],
|
1933
|
+
...setLastType(scope)
|
1934
|
+
)
|
1935
|
+
|
1936
|
+
return out;
|
1937
|
+
};
|
1846
1938
|
|
1847
|
-
|
1939
|
+
const tableBc = {};
|
1940
|
+
for (let i = 0; i <= args.length; i++) {
|
1941
|
+
tableBc[i] = gen(i);
|
1848
1942
|
}
|
1849
1943
|
|
1944
|
+
// todo/perf: check if we should use br_table here or just generate our own big if..elses
|
1945
|
+
|
1850
1946
|
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1851
1947
|
[TYPES.function]: [
|
1852
|
-
...
|
1948
|
+
...out,
|
1949
|
+
|
1853
1950
|
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1854
1951
|
Opcodes.i32_to_u,
|
1855
|
-
[ Opcodes.
|
1856
|
-
|
1952
|
+
[ Opcodes.local_set, funcLocal ],
|
1953
|
+
|
1954
|
+
...brTable([
|
1955
|
+
// get argc of func we are calling
|
1956
|
+
[ Opcodes.local_get, funcLocal ],
|
1957
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
1958
|
+
[ Opcodes.i32_mul ],
|
1959
|
+
|
1960
|
+
[ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
|
1961
|
+
], tableBc, valtypeBinary)
|
1857
1962
|
],
|
1858
1963
|
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1859
1964
|
});
|
@@ -1862,11 +1967,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1862
1967
|
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1863
1968
|
}
|
1864
1969
|
|
1865
|
-
const func = funcs.find(x => x.index === idx);
|
1866
|
-
|
1867
|
-
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1970
|
+
const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
|
1971
|
+
const userFunc = func && !func.internal;
|
1868
1972
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1869
|
-
const typedReturns = (
|
1973
|
+
const typedReturns = (userFunc && func.returnType == null) || builtinFuncs[name]?.typedReturns;
|
1870
1974
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1871
1975
|
|
1872
1976
|
let args = decl.arguments;
|
@@ -1895,8 +1999,8 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1895
1999
|
}
|
1896
2000
|
|
1897
2001
|
if (valtypeBinary !== Valtype.i32 && (
|
1898
|
-
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32)
|
1899
|
-
(importedFuncs[name] && name.startsWith('profile'))
|
2002
|
+
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32)
|
2003
|
+
// (importedFuncs[name] && name.startsWith('profile'))
|
1900
2004
|
)) {
|
1901
2005
|
out.push(Opcodes.i32_to);
|
1902
2006
|
}
|
@@ -2011,16 +2115,17 @@ const brTable = (input, bc, returns) => {
|
|
2011
2115
|
}
|
2012
2116
|
|
2013
2117
|
for (let i = 0; i < count; i++) {
|
2014
|
-
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2118
|
+
// if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2119
|
+
if (i === 0) out.push([ Opcodes.block, returns ]);
|
2015
2120
|
else out.push([ Opcodes.block, Blocktype.void ]);
|
2016
2121
|
}
|
2017
2122
|
|
2018
|
-
const nums = keys.filter(x => +x);
|
2123
|
+
const nums = keys.filter(x => +x >= 0);
|
2019
2124
|
const offset = Math.min(...nums);
|
2020
2125
|
const max = Math.max(...nums);
|
2021
2126
|
|
2022
2127
|
const table = [];
|
2023
|
-
let br =
|
2128
|
+
let br = 0;
|
2024
2129
|
|
2025
2130
|
for (let i = offset; i <= max; i++) {
|
2026
2131
|
// if branch for this num, go to that block
|
@@ -2060,10 +2165,9 @@ const brTable = (input, bc, returns) => {
|
|
2060
2165
|
br--;
|
2061
2166
|
}
|
2062
2167
|
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
];
|
2168
|
+
out.push([ Opcodes.end ]);
|
2169
|
+
|
2170
|
+
return out;
|
2067
2171
|
};
|
2068
2172
|
|
2069
2173
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
@@ -2077,7 +2181,7 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
2077
2181
|
if (Prefs.typeswitchUseBrtable)
|
2078
2182
|
return brTable(type, bc, returns);
|
2079
2183
|
|
2080
|
-
const tmp = localTmp(scope, '#typeswitch_tmp', Valtype.i32);
|
2184
|
+
const tmp = localTmp(scope, '#typeswitch_tmp' + (Prefs.typeswitchUniqueTmp ? randId() : ''), Valtype.i32);
|
2081
2185
|
const out = [
|
2082
2186
|
...type,
|
2083
2187
|
[ Opcodes.local_set, tmp ],
|
@@ -2347,18 +2451,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2347
2451
|
Opcodes.i32_to_u,
|
2348
2452
|
|
2349
2453
|
// turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2350
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2454
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
2351
2455
|
[ Opcodes.i32_mul ],
|
2352
2456
|
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
2353
2457
|
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2354
2458
|
|
2355
2459
|
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2356
2460
|
[ Opcodes.local_get, pointerTmp ],
|
2357
|
-
[ Opcodes.load,
|
2358
|
-
], generate(scope, decl.right),
|
2461
|
+
[ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
2462
|
+
], generate(scope, decl.right), [
|
2463
|
+
[ Opcodes.local_get, pointerTmp ],
|
2464
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
2465
|
+
], getNodeType(scope, decl.right), false, name, true)),
|
2359
2466
|
[ Opcodes.local_tee, newValueTmp ],
|
2360
2467
|
|
2361
|
-
[ Opcodes.store,
|
2468
|
+
[ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
2362
2469
|
],
|
2363
2470
|
|
2364
2471
|
default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
|
@@ -2456,7 +2563,7 @@ const generateUnary = (scope, decl) => {
|
|
2456
2563
|
// * -1
|
2457
2564
|
|
2458
2565
|
if (decl.prefix && decl.argument.type === 'Literal' && typeof decl.argument.value === 'number') {
|
2459
|
-
// if
|
2566
|
+
// if -n, just return that as a const
|
2460
2567
|
return number(-1 * decl.argument.value);
|
2461
2568
|
}
|
2462
2569
|
|
@@ -2466,11 +2573,16 @@ const generateUnary = (scope, decl) => {
|
|
2466
2573
|
];
|
2467
2574
|
|
2468
2575
|
case '!':
|
2576
|
+
const arg = decl.argument;
|
2577
|
+
if (arg.type === 'UnaryExpression' && arg.operator === '!') {
|
2578
|
+
// opt: !!x -> is x truthy
|
2579
|
+
return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
|
2580
|
+
}
|
2581
|
+
|
2469
2582
|
// !=
|
2470
|
-
return falsy(scope, generate(scope,
|
2583
|
+
return falsy(scope, generate(scope, arg), getNodeType(scope, arg), false, false);
|
2471
2584
|
|
2472
2585
|
case '~':
|
2473
|
-
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
2474
2586
|
return [
|
2475
2587
|
...generate(scope, decl.argument),
|
2476
2588
|
Opcodes.i32_to,
|
@@ -2769,12 +2881,15 @@ const generateForOf = (scope, decl) => {
|
|
2769
2881
|
// todo: optimize away counter and use end pointer
|
2770
2882
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2771
2883
|
[TYPES.array]: [
|
2772
|
-
...setType(scope, leftName, TYPES.number),
|
2773
|
-
|
2774
2884
|
[ Opcodes.loop, Blocktype.void ],
|
2775
2885
|
|
2776
2886
|
[ Opcodes.local_get, pointer ],
|
2777
|
-
[ Opcodes.load,
|
2887
|
+
[ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2888
|
+
|
2889
|
+
...setType(scope, leftName, [
|
2890
|
+
[ Opcodes.local_get, pointer ],
|
2891
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
|
2892
|
+
]),
|
2778
2893
|
|
2779
2894
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2780
2895
|
|
@@ -2783,9 +2898,9 @@ const generateForOf = (scope, decl) => {
|
|
2783
2898
|
...generate(scope, decl.body),
|
2784
2899
|
[ Opcodes.end ],
|
2785
2900
|
|
2786
|
-
// increment iter pointer by valtype size
|
2901
|
+
// increment iter pointer by valtype size + 1
|
2787
2902
|
[ Opcodes.local_get, pointer ],
|
2788
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2903
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
2789
2904
|
[ Opcodes.i32_add ],
|
2790
2905
|
[ Opcodes.local_set, pointer ],
|
2791
2906
|
|
@@ -2901,6 +3016,44 @@ const generateForOf = (scope, decl) => {
|
|
2901
3016
|
[ Opcodes.end ],
|
2902
3017
|
[ Opcodes.end ]
|
2903
3018
|
],
|
3019
|
+
[TYPES.set]: [
|
3020
|
+
[ Opcodes.loop, Blocktype.void ],
|
3021
|
+
|
3022
|
+
[ Opcodes.local_get, pointer ],
|
3023
|
+
[ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
3024
|
+
|
3025
|
+
...setType(scope, leftName, [
|
3026
|
+
[ Opcodes.local_get, pointer ],
|
3027
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
|
3028
|
+
]),
|
3029
|
+
|
3030
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
3031
|
+
|
3032
|
+
[ Opcodes.block, Blocktype.void ],
|
3033
|
+
[ Opcodes.block, Blocktype.void ],
|
3034
|
+
...generate(scope, decl.body),
|
3035
|
+
[ Opcodes.end ],
|
3036
|
+
|
3037
|
+
// increment iter pointer by valtype size + 1
|
3038
|
+
[ Opcodes.local_get, pointer ],
|
3039
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3040
|
+
[ Opcodes.i32_add ],
|
3041
|
+
[ Opcodes.local_set, pointer ],
|
3042
|
+
|
3043
|
+
// increment counter by 1
|
3044
|
+
[ Opcodes.local_get, counter ],
|
3045
|
+
...number(1, Valtype.i32),
|
3046
|
+
[ Opcodes.i32_add ],
|
3047
|
+
[ Opcodes.local_tee, counter ],
|
3048
|
+
|
3049
|
+
// loop if counter != length
|
3050
|
+
[ Opcodes.local_get, length ],
|
3051
|
+
[ Opcodes.i32_ne ],
|
3052
|
+
[ Opcodes.br_if, 1 ],
|
3053
|
+
|
3054
|
+
[ Opcodes.end ],
|
3055
|
+
[ Opcodes.end ]
|
3056
|
+
],
|
2904
3057
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2905
3058
|
}, Blocktype.void));
|
2906
3059
|
|
@@ -3002,14 +3155,18 @@ const generateThrow = (scope, decl) => {
|
|
3002
3155
|
};
|
3003
3156
|
|
3004
3157
|
const generateTry = (scope, decl) => {
|
3005
|
-
|
3158
|
+
// todo: handle control-flow pre-exit for finally
|
3159
|
+
// "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
|
3006
3160
|
|
3007
3161
|
const out = [];
|
3008
3162
|
|
3163
|
+
const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
|
3164
|
+
|
3009
3165
|
out.push([ Opcodes.try, Blocktype.void ]);
|
3010
3166
|
depth.push('try');
|
3011
3167
|
|
3012
3168
|
out.push(...generate(scope, decl.block));
|
3169
|
+
out.push(...finalizer);
|
3013
3170
|
|
3014
3171
|
if (decl.handler) {
|
3015
3172
|
depth.pop();
|
@@ -3017,6 +3174,7 @@ const generateTry = (scope, decl) => {
|
|
3017
3174
|
|
3018
3175
|
out.push([ Opcodes.catch_all ]);
|
3019
3176
|
out.push(...generate(scope, decl.handler.body));
|
3177
|
+
out.push(...finalizer);
|
3020
3178
|
}
|
3021
3179
|
|
3022
3180
|
out.push([ Opcodes.end ]);
|
@@ -3102,7 +3260,7 @@ const getAllocType = itemType => {
|
|
3102
3260
|
}
|
3103
3261
|
};
|
3104
3262
|
|
3105
|
-
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
3263
|
+
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
|
3106
3264
|
const out = [];
|
3107
3265
|
|
3108
3266
|
scope.arrays ??= new Map();
|
@@ -3114,8 +3272,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3114
3272
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
3115
3273
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
3116
3274
|
|
3117
|
-
|
3118
|
-
|
3275
|
+
let page;
|
3276
|
+
if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
|
3277
|
+
else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
|
3278
|
+
|
3279
|
+
// hack: use 1 for page 0 pointer for fast truthiness
|
3280
|
+
const ptr = page === 0 ? 1 : (page * pageSize);
|
3281
|
+
scope.arrays.set(name, ptr);
|
3119
3282
|
}
|
3120
3283
|
|
3121
3284
|
const pointer = scope.arrays.get(name);
|
@@ -3165,7 +3328,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3165
3328
|
|
3166
3329
|
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3167
3330
|
|
3168
|
-
// store length
|
3331
|
+
// store length
|
3169
3332
|
out.push(
|
3170
3333
|
...pointerWasm,
|
3171
3334
|
...number(length, Valtype.i32),
|
@@ -3173,14 +3336,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3173
3336
|
);
|
3174
3337
|
|
3175
3338
|
const storeOp = StoreOps[itemType];
|
3176
|
-
|
3339
|
+
const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
|
3177
3340
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3178
3341
|
if (elements[i] == null) continue;
|
3179
3342
|
|
3343
|
+
const offset = ValtypeSize.i32 + i * sizePerEl;
|
3180
3344
|
out.push(
|
3181
3345
|
...pointerWasm,
|
3182
3346
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
3183
|
-
[ storeOp,
|
3347
|
+
[ storeOp, 0, ...unsignedLEB128(offset) ],
|
3348
|
+
...(!typed ? [] : [ // typed presumes !useRawElements
|
3349
|
+
...pointerWasm,
|
3350
|
+
...getNodeType(scope, elements[i]),
|
3351
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
|
3352
|
+
])
|
3184
3353
|
);
|
3185
3354
|
}
|
3186
3355
|
|
@@ -3190,6 +3359,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3190
3359
|
return [ out, pointer ];
|
3191
3360
|
};
|
3192
3361
|
|
3362
|
+
const storeArray = (scope, array, index, element, aotPointer = null) => {
|
3363
|
+
if (!Array.isArray(element)) element = generate(scope, element);
|
3364
|
+
if (typeof index === 'number') index = number(index);
|
3365
|
+
|
3366
|
+
const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
|
3367
|
+
|
3368
|
+
return [
|
3369
|
+
// calculate offset
|
3370
|
+
...index,
|
3371
|
+
Opcodes.i32_to_u,
|
3372
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3373
|
+
[ Opcodes.i32_mul ],
|
3374
|
+
...(aotPointer ? [] : [
|
3375
|
+
...array,
|
3376
|
+
Opcodes.i32_to_u,
|
3377
|
+
[ Opcodes.i32_add ],
|
3378
|
+
]),
|
3379
|
+
[ Opcodes.local_set, offset ],
|
3380
|
+
|
3381
|
+
// store value
|
3382
|
+
[ Opcodes.local_get, offset ],
|
3383
|
+
...generate(scope, element),
|
3384
|
+
[ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3385
|
+
|
3386
|
+
// store type
|
3387
|
+
[ Opcodes.local_get, offset ],
|
3388
|
+
...getNodeType(scope, element),
|
3389
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
3390
|
+
];
|
3391
|
+
};
|
3392
|
+
|
3393
|
+
const loadArray = (scope, array, index, aotPointer = null) => {
|
3394
|
+
if (typeof index === 'number') index = number(index);
|
3395
|
+
|
3396
|
+
const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
|
3397
|
+
|
3398
|
+
return [
|
3399
|
+
// calculate offset
|
3400
|
+
...index,
|
3401
|
+
Opcodes.i32_to_u,
|
3402
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3403
|
+
[ Opcodes.i32_mul ],
|
3404
|
+
...(aotPointer ? [] : [
|
3405
|
+
...array,
|
3406
|
+
Opcodes.i32_to_u,
|
3407
|
+
[ Opcodes.i32_add ],
|
3408
|
+
]),
|
3409
|
+
[ Opcodes.local_set, offset ],
|
3410
|
+
|
3411
|
+
// load value
|
3412
|
+
[ Opcodes.local_get, offset ],
|
3413
|
+
[ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3414
|
+
|
3415
|
+
// load type
|
3416
|
+
[ Opcodes.local_get, offset ],
|
3417
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
3418
|
+
];
|
3419
|
+
};
|
3420
|
+
|
3193
3421
|
const byteStringable = str => {
|
3194
3422
|
if (!Prefs.bytestring) return false;
|
3195
3423
|
|
@@ -3218,7 +3446,7 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
3218
3446
|
};
|
3219
3447
|
|
3220
3448
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
3221
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
3449
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
|
3222
3450
|
};
|
3223
3451
|
|
3224
3452
|
const generateObject = (scope, decl, global = false, name = '$undeclared') => {
|
@@ -3239,7 +3467,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
3239
3467
|
const name = decl.object.name;
|
3240
3468
|
const pointer = scope.arrays?.get(name);
|
3241
3469
|
|
3242
|
-
const aotPointer = Prefs.aotPointerOpt && pointer
|
3470
|
+
const aotPointer = Prefs.aotPointerOpt && pointer;
|
3243
3471
|
|
3244
3472
|
// hack: .name
|
3245
3473
|
if (decl.property.name === 'name') {
|
@@ -3259,8 +3487,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
3259
3487
|
if (decl.property.name === 'length') {
|
3260
3488
|
const func = funcs.find(x => x.name === name);
|
3261
3489
|
if (func) {
|
3262
|
-
const
|
3263
|
-
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3490
|
+
const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
|
3264
3491
|
return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
|
3265
3492
|
}
|
3266
3493
|
|
@@ -3275,7 +3502,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
3275
3502
|
}
|
3276
3503
|
|
3277
3504
|
if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
|
3278
|
-
if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
|
3505
|
+
if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params.length ?? importedFuncs[name].params), TYPES.number);
|
3279
3506
|
if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
|
3280
3507
|
|
3281
3508
|
if (Prefs.fastLength) {
|
@@ -3367,23 +3594,8 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
3367
3594
|
|
3368
3595
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
3369
3596
|
[TYPES.array]: [
|
3370
|
-
|
3371
|
-
...
|
3372
|
-
|
3373
|
-
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
3374
|
-
Opcodes.i32_to_u,
|
3375
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
3376
|
-
[ Opcodes.i32_mul ],
|
3377
|
-
|
3378
|
-
...(aotPointer ? [] : [
|
3379
|
-
...object,
|
3380
|
-
Opcodes.i32_to_u,
|
3381
|
-
[ Opcodes.i32_add ]
|
3382
|
-
]),
|
3383
|
-
|
3384
|
-
// read from memory
|
3385
|
-
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3386
|
-
...setLastType(scope, TYPES.number)
|
3597
|
+
...loadArray(scope, object, property, aotPointer),
|
3598
|
+
...setLastType(scope)
|
3387
3599
|
],
|
3388
3600
|
|
3389
3601
|
[TYPES.string]: [
|
@@ -3501,33 +3713,39 @@ const generateFunc = (scope, decl) => {
|
|
3501
3713
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
3502
3714
|
const params = decl.params ?? [];
|
3503
3715
|
|
3504
|
-
// const innerScope = { ...scope };
|
3505
3716
|
// TODO: share scope/locals between !!!
|
3506
|
-
const
|
3717
|
+
const func = {
|
3507
3718
|
locals: {},
|
3508
3719
|
localInd: 0,
|
3509
3720
|
// value, type
|
3510
3721
|
returns: [ valtypeBinary, Valtype.i32 ],
|
3511
3722
|
throws: false,
|
3512
|
-
name
|
3723
|
+
name,
|
3724
|
+
index: currentFuncIndex++
|
3513
3725
|
};
|
3514
3726
|
|
3515
3727
|
if (typedInput && decl.returnType) {
|
3516
3728
|
const { type } = extractTypeAnnotation(decl.returnType);
|
3517
|
-
if (type != null && !Prefs.indirectCalls) {
|
3518
|
-
|
3519
|
-
|
3729
|
+
// if (type != null && !Prefs.indirectCalls) {
|
3730
|
+
if (type != null) {
|
3731
|
+
func.returnType = type;
|
3732
|
+
func.returns = [ valtypeBinary ];
|
3520
3733
|
}
|
3521
3734
|
}
|
3522
3735
|
|
3523
3736
|
for (let i = 0; i < params.length; i++) {
|
3524
|
-
|
3737
|
+
const name = params[i].name;
|
3738
|
+
// if (name == null) return todo('non-identifier args are not supported');
|
3739
|
+
|
3740
|
+
allocVar(func, name, false);
|
3525
3741
|
|
3526
3742
|
if (typedInput && params[i].typeAnnotation) {
|
3527
|
-
addVarMetadata(
|
3743
|
+
addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
|
3528
3744
|
}
|
3529
3745
|
}
|
3530
3746
|
|
3747
|
+
func.params = Object.values(func.locals).map(x => x.type);
|
3748
|
+
|
3531
3749
|
let body = objectHack(decl.body);
|
3532
3750
|
if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
|
3533
3751
|
// hack: () => 0 -> () => return 0
|
@@ -3537,37 +3755,23 @@ const generateFunc = (scope, decl) => {
|
|
3537
3755
|
};
|
3538
3756
|
}
|
3539
3757
|
|
3540
|
-
const wasm = generate(innerScope, body);
|
3541
|
-
const func = {
|
3542
|
-
name,
|
3543
|
-
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
3544
|
-
index: currentFuncIndex++,
|
3545
|
-
...innerScope
|
3546
|
-
};
|
3547
3758
|
funcIndex[name] = func.index;
|
3759
|
+
funcs.push(func);
|
3548
3760
|
|
3549
|
-
|
3761
|
+
const wasm = generate(func, body);
|
3762
|
+
func.wasm = wasm;
|
3550
3763
|
|
3551
|
-
|
3552
|
-
for (const inst of wasm) {
|
3553
|
-
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
3554
|
-
inst[1] = func.index;
|
3555
|
-
}
|
3556
|
-
}
|
3764
|
+
if (name === 'main') func.gotLastType = true;
|
3557
3765
|
|
3558
3766
|
// add end return if not found
|
3559
3767
|
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
3560
3768
|
wasm.push(
|
3561
3769
|
...number(0),
|
3562
|
-
...(
|
3770
|
+
...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
|
3563
3771
|
[ Opcodes.return ]
|
3564
3772
|
);
|
3565
3773
|
}
|
3566
3774
|
|
3567
|
-
func.wasm = wasm;
|
3568
|
-
|
3569
|
-
funcs.push(func);
|
3570
|
-
|
3571
3775
|
return func;
|
3572
3776
|
};
|
3573
3777
|
|
@@ -3671,7 +3875,7 @@ const internalConstrs = {
|
|
3671
3875
|
generate: (scope, decl) => {
|
3672
3876
|
// todo: boolean object when used as constructor
|
3673
3877
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3674
|
-
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3878
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
|
3675
3879
|
},
|
3676
3880
|
type: TYPES.boolean,
|
3677
3881
|
length: 1
|
@@ -3767,19 +3971,8 @@ export default program => {
|
|
3767
3971
|
data = [];
|
3768
3972
|
currentFuncIndex = importedFuncs.length;
|
3769
3973
|
|
3770
|
-
globalThis.valtype = 'f64';
|
3771
|
-
|
3772
|
-
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
3773
|
-
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
3774
|
-
|
3775
|
-
globalThis.valtypeBinary = Valtype[valtype];
|
3776
|
-
|
3777
3974
|
const valtypeInd = ['i32', 'i64', 'f64'].indexOf(valtype);
|
3778
3975
|
|
3779
|
-
globalThis.pageSize = PageSize;
|
3780
|
-
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
3781
|
-
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
3782
|
-
|
3783
3976
|
// set generic opcodes for current valtype
|
3784
3977
|
Opcodes.const = [ Opcodes.i32_const, Opcodes.i64_const, Opcodes.f64_const ][valtypeInd];
|
3785
3978
|
Opcodes.eq = [ Opcodes.i32_eq, Opcodes.i64_eq, Opcodes.f64_eq ][valtypeInd];
|
@@ -3816,9 +4009,8 @@ export default program => {
|
|
3816
4009
|
|
3817
4010
|
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3818
4011
|
|
3819
|
-
generateFunc(scope, program);
|
4012
|
+
const main = generateFunc(scope, program);
|
3820
4013
|
|
3821
|
-
const main = funcs[funcs.length - 1];
|
3822
4014
|
main.export = true;
|
3823
4015
|
main.returns = [ valtypeBinary, Valtype.i32 ];
|
3824
4016
|
|
@@ -3845,7 +4037,7 @@ export default program => {
|
|
3845
4037
|
}
|
3846
4038
|
|
3847
4039
|
// if blank main func and other exports, remove it
|
3848
|
-
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(
|
4040
|
+
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
|
3849
4041
|
|
3850
4042
|
return { funcs, globals, tags, exceptions, pages, data };
|
3851
4043
|
};
|