porffor 0.14.0-eca486960 → 0.16.0-594397507
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 +68 -3
- package/compiler/assemble.js +14 -0
- 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 +8 -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 +23 -7
- package/compiler/codegen.js +383 -172
- package/compiler/decompile.js +4 -0
- package/compiler/generated_builtins.js +503 -53
- package/compiler/index.js +2 -1
- package/compiler/parse.js +2 -2
- package/compiler/precompile.js +5 -4
- package/compiler/prefs.js +1 -1
- package/compiler/prototype.js +180 -157
- package/compiler/wrap.js +18 -4
- package/package.json +1 -1
- package/runner/index.js +5 -4
- package/runner/repl.js +18 -2
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: returnType ?? TYPES.number,
|
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
|
};
|
@@ -1249,7 +1269,17 @@ 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) return protoFuncs[0].returnType;
|
1274
|
+
}
|
1275
|
+
|
1276
|
+
if (protoFuncs.length > 0) {
|
1277
|
+
if (scope.locals['#last_type']) return getLastType(scope);
|
1278
|
+
|
1279
|
+
// presume
|
1280
|
+
// todo: warn here?
|
1281
|
+
return TYPES.number;
|
1282
|
+
}
|
1253
1283
|
}
|
1254
1284
|
|
1255
1285
|
if (name.startsWith('__Porffor_wasm_')) {
|
@@ -1435,16 +1465,11 @@ const countLeftover = wasm => {
|
|
1435
1465
|
else if (inst[0] === Opcodes.return) count = 0;
|
1436
1466
|
else if (inst[0] === Opcodes.call) {
|
1437
1467
|
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;
|
1468
|
+
if (inst[1] < importedFuncs.length) {
|
1469
|
+
func = importedFuncs[inst[1]];
|
1470
|
+
count = count - func.params + func.returns;
|
1443
1471
|
} else {
|
1444
|
-
|
1445
|
-
count -= func.params.length;
|
1446
|
-
} else count--;
|
1447
|
-
if (func) count += func.returns.length;
|
1472
|
+
count = count - func.params.length + func.returns.length;
|
1448
1473
|
}
|
1449
1474
|
} else if (inst[0] === Opcodes.call_indirect) {
|
1450
1475
|
count--; // funcidx
|
@@ -1467,7 +1492,7 @@ const disposeLeftover = wasm => {
|
|
1467
1492
|
const generateExp = (scope, decl) => {
|
1468
1493
|
const expression = decl.expression;
|
1469
1494
|
|
1470
|
-
const out = generate(scope, expression, undefined, undefined,
|
1495
|
+
const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
|
1471
1496
|
disposeLeftover(out);
|
1472
1497
|
|
1473
1498
|
return out;
|
@@ -1597,6 +1622,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1597
1622
|
|
1598
1623
|
if (!funcIndex[rhemynName]) {
|
1599
1624
|
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1625
|
+
func.internal = true;
|
1600
1626
|
|
1601
1627
|
funcIndex[func.name] = func.index;
|
1602
1628
|
funcs.push(func);
|
@@ -1704,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1704
1730
|
getI32: () => RTArrayUtil.getLengthI32(getPointer),
|
1705
1731
|
set: value => RTArrayUtil.setLength(getPointer, value),
|
1706
1732
|
setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
|
1707
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1733
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), getNodeType(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1708
1734
|
return makeArray(scope, {
|
1709
1735
|
rawElements: new Array(length)
|
1710
1736
|
}, _global, _name, true, itemType);
|
@@ -1718,7 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1718
1744
|
protoBC[x] = [
|
1719
1745
|
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1720
1746
|
...protoOut,
|
1721
|
-
...setLastType(scope, protoFunc.returnType
|
1747
|
+
...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
|
1722
1748
|
[ Opcodes.end ]
|
1723
1749
|
];
|
1724
1750
|
}
|
@@ -1768,11 +1794,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1768
1794
|
|
1769
1795
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1770
1796
|
|
1771
|
-
if (idx === undefined && name === scope.name) {
|
1772
|
-
// hack: calling self, func generator will fix later
|
1773
|
-
idx = -1;
|
1774
|
-
}
|
1775
|
-
|
1776
1797
|
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1777
1798
|
const wasmOps = {
|
1778
1799
|
// pointer, align, offset
|
@@ -1824,36 +1845,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1824
1845
|
const [ local, global ] = lookupName(scope, name);
|
1825
1846
|
if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1826
1847
|
|
1827
|
-
// todo: only works when
|
1828
|
-
|
1829
|
-
|
1848
|
+
// todo: only works when function uses typedParams and typedReturns
|
1849
|
+
|
1850
|
+
const indirectMode = Prefs.indirectCallMode ?? 'vararg';
|
1851
|
+
// options: vararg, strict
|
1852
|
+
// - strict: simpler, smaller size usage, no func argc lut needed.
|
1853
|
+
// ONLY works when arg count of call == arg count of function being called
|
1854
|
+
// - vararg: large size usage, cursed.
|
1855
|
+
// works when arg count of call != arg count of function being called*
|
1856
|
+
// * most of the time, some edgecases
|
1830
1857
|
|
1831
1858
|
funcs.table = true;
|
1859
|
+
scope.table = true;
|
1832
1860
|
|
1833
1861
|
let args = decl.arguments;
|
1834
|
-
let
|
1862
|
+
let out = [];
|
1863
|
+
|
1864
|
+
let locals = [];
|
1865
|
+
|
1866
|
+
if (indirectMode === 'vararg') {
|
1867
|
+
const minArgc = Prefs.indirectCallMinArgc ?? 3;
|
1868
|
+
|
1869
|
+
if (args.length < minArgc) {
|
1870
|
+
args = args.concat(new Array(minArgc - args.length).fill(DEFAULT_VALUE));
|
1871
|
+
}
|
1872
|
+
}
|
1835
1873
|
|
1836
1874
|
for (let i = 0; i < args.length; i++) {
|
1837
1875
|
const arg = args[i];
|
1838
|
-
|
1876
|
+
out = out.concat(generate(scope, arg));
|
1839
1877
|
|
1840
1878
|
if (valtypeBinary !== Valtype.i32 && (
|
1841
1879
|
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
1842
1880
|
(importedFuncs[name] && name.startsWith('profile'))
|
1843
1881
|
)) {
|
1844
|
-
|
1882
|
+
out.push(Opcodes.i32_to);
|
1883
|
+
}
|
1884
|
+
|
1885
|
+
out = out.concat(getNodeType(scope, arg));
|
1886
|
+
|
1887
|
+
if (indirectMode === 'vararg') {
|
1888
|
+
const typeLocal = localTmp(scope, `#indirect_arg${i}_type`, Valtype.i32);
|
1889
|
+
const valLocal = localTmp(scope, `#indirect_arg${i}_val`);
|
1890
|
+
|
1891
|
+
locals.push([valLocal, typeLocal]);
|
1892
|
+
|
1893
|
+
out.push(
|
1894
|
+
[ Opcodes.local_set, typeLocal ],
|
1895
|
+
[ Opcodes.local_set, valLocal ]
|
1896
|
+
);
|
1845
1897
|
}
|
1898
|
+
}
|
1846
1899
|
|
1847
|
-
|
1900
|
+
if (indirectMode === 'strict') {
|
1901
|
+
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1902
|
+
[TYPES.function]: [
|
1903
|
+
...argWasm,
|
1904
|
+
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1905
|
+
Opcodes.i32_to_u,
|
1906
|
+
[ Opcodes.call_indirect, args.length, 0 ],
|
1907
|
+
...setLastType(scope)
|
1908
|
+
],
|
1909
|
+
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1910
|
+
});
|
1848
1911
|
}
|
1849
1912
|
|
1913
|
+
// hi, I will now explain how vararg mode works:
|
1914
|
+
// wasm's indirect_call instruction requires you know the func type at compile-time
|
1915
|
+
// since we have varargs (variable argument count), we do not know it.
|
1916
|
+
// we could just store args in memory and not use wasm func args,
|
1917
|
+
// but that is slow (probably) and breaks js exports.
|
1918
|
+
// instead, we generate every* possibility of argc and use different indirect_call
|
1919
|
+
// ops for each one, with type depending on argc for that branch.
|
1920
|
+
// then we load the argc for the wanted function from a memory lut,
|
1921
|
+
// and call the branch with the matching argc we require.
|
1922
|
+
// sorry, yes it is very cursed (and size inefficient), but indirect calls
|
1923
|
+
// are kind of rare anyway (mostly callbacks) so I am not concerned atm.
|
1924
|
+
// *for argc 0-3, in future (todo:) the max number should be
|
1925
|
+
// dynamically changed to the max argc of any func in the js file.
|
1926
|
+
|
1927
|
+
const funcLocal = localTmp(scope, `#indirect_func`, Valtype.i32);
|
1928
|
+
|
1929
|
+
const gen = argc => {
|
1930
|
+
const out = [];
|
1931
|
+
for (let i = 0; i < argc; i++) {
|
1932
|
+
out.push(
|
1933
|
+
[ Opcodes.local_get, locals[i][0] ],
|
1934
|
+
[ Opcodes.local_get, locals[i][1] ]
|
1935
|
+
);
|
1936
|
+
}
|
1937
|
+
|
1938
|
+
out.push(
|
1939
|
+
[ Opcodes.local_get, funcLocal ],
|
1940
|
+
[ Opcodes.call_indirect, argc, 0 ],
|
1941
|
+
...setLastType(scope)
|
1942
|
+
)
|
1943
|
+
|
1944
|
+
return out;
|
1945
|
+
};
|
1946
|
+
|
1947
|
+
const tableBc = {};
|
1948
|
+
for (let i = 0; i <= args.length; i++) {
|
1949
|
+
tableBc[i] = gen(i);
|
1950
|
+
}
|
1951
|
+
|
1952
|
+
// todo/perf: check if we should use br_table here or just generate our own big if..elses
|
1953
|
+
|
1850
1954
|
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1851
1955
|
[TYPES.function]: [
|
1852
|
-
...
|
1956
|
+
...out,
|
1957
|
+
|
1853
1958
|
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1854
1959
|
Opcodes.i32_to_u,
|
1855
|
-
[ Opcodes.
|
1856
|
-
|
1960
|
+
[ Opcodes.local_set, funcLocal ],
|
1961
|
+
|
1962
|
+
...brTable([
|
1963
|
+
// get argc of func we are calling
|
1964
|
+
[ Opcodes.local_get, funcLocal ],
|
1965
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
1966
|
+
[ Opcodes.i32_mul ],
|
1967
|
+
|
1968
|
+
[ Opcodes.i32_load16_u, 0, ...unsignedLEB128(allocPage(scope, 'func argc lut') * pageSize), 'read_argc' ]
|
1969
|
+
], tableBc, valtypeBinary)
|
1857
1970
|
],
|
1858
1971
|
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1859
1972
|
});
|
@@ -1862,11 +1975,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1862
1975
|
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1863
1976
|
}
|
1864
1977
|
|
1865
|
-
const func = funcs.find(x => x.index === idx);
|
1866
|
-
|
1867
|
-
const userFunc = (funcIndex[name] && !importedFuncs[name] && !builtinFuncs[name] && !internalConstrs[name]) || idx === -1;
|
1978
|
+
const func = funcs[idx - importedFuncs.length]; // idx === scope.index ? scope : funcs.find(x => x.index === idx);
|
1979
|
+
const userFunc = func && !func.internal;
|
1868
1980
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1869
|
-
const typedReturns = (func
|
1981
|
+
const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
|
1870
1982
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1871
1983
|
|
1872
1984
|
let args = decl.arguments;
|
@@ -2011,16 +2123,17 @@ const brTable = (input, bc, returns) => {
|
|
2011
2123
|
}
|
2012
2124
|
|
2013
2125
|
for (let i = 0; i < count; i++) {
|
2014
|
-
if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2126
|
+
// if (i === 0) out.push([ Opcodes.block, returns, 'br table start' ]);
|
2127
|
+
if (i === 0) out.push([ Opcodes.block, returns ]);
|
2015
2128
|
else out.push([ Opcodes.block, Blocktype.void ]);
|
2016
2129
|
}
|
2017
2130
|
|
2018
|
-
const nums = keys.filter(x => +x);
|
2131
|
+
const nums = keys.filter(x => +x >= 0);
|
2019
2132
|
const offset = Math.min(...nums);
|
2020
2133
|
const max = Math.max(...nums);
|
2021
2134
|
|
2022
2135
|
const table = [];
|
2023
|
-
let br =
|
2136
|
+
let br = 0;
|
2024
2137
|
|
2025
2138
|
for (let i = offset; i <= max; i++) {
|
2026
2139
|
// if branch for this num, go to that block
|
@@ -2060,10 +2173,9 @@ const brTable = (input, bc, returns) => {
|
|
2060
2173
|
br--;
|
2061
2174
|
}
|
2062
2175
|
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
];
|
2176
|
+
out.push([ Opcodes.end ]);
|
2177
|
+
|
2178
|
+
return out;
|
2067
2179
|
};
|
2068
2180
|
|
2069
2181
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
@@ -2347,18 +2459,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2347
2459
|
Opcodes.i32_to_u,
|
2348
2460
|
|
2349
2461
|
// turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2350
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2462
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
2351
2463
|
[ Opcodes.i32_mul ],
|
2352
2464
|
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
2353
2465
|
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2354
2466
|
|
2355
2467
|
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2356
2468
|
[ Opcodes.local_get, pointerTmp ],
|
2357
|
-
[ Opcodes.load,
|
2358
|
-
], generate(scope, decl.right),
|
2469
|
+
[ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
2470
|
+
], generate(scope, decl.right), [
|
2471
|
+
[ Opcodes.local_get, pointerTmp ],
|
2472
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
2473
|
+
], getNodeType(scope, decl.right), false, name, true)),
|
2359
2474
|
[ Opcodes.local_tee, newValueTmp ],
|
2360
2475
|
|
2361
|
-
[ Opcodes.store,
|
2476
|
+
[ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
2362
2477
|
],
|
2363
2478
|
|
2364
2479
|
default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
|
@@ -2466,6 +2581,11 @@ const generateUnary = (scope, decl) => {
|
|
2466
2581
|
];
|
2467
2582
|
|
2468
2583
|
case '!':
|
2584
|
+
const arg = decl.argument;
|
2585
|
+
if (arg.type === 'UnaryExpression' && arg.operator === '!') {
|
2586
|
+
// !!x -> is x truthy
|
2587
|
+
return truthy(scope, generate(scope, arg.argument), getNodeType(scope, arg.argument), false, false);
|
2588
|
+
}
|
2469
2589
|
// !=
|
2470
2590
|
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
|
2471
2591
|
|
@@ -2769,12 +2889,15 @@ const generateForOf = (scope, decl) => {
|
|
2769
2889
|
// todo: optimize away counter and use end pointer
|
2770
2890
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2771
2891
|
[TYPES.array]: [
|
2772
|
-
...setType(scope, leftName, TYPES.number),
|
2773
|
-
|
2774
2892
|
[ Opcodes.loop, Blocktype.void ],
|
2775
2893
|
|
2776
2894
|
[ Opcodes.local_get, pointer ],
|
2777
|
-
[ Opcodes.load,
|
2895
|
+
[ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
2896
|
+
|
2897
|
+
...setType(scope, leftName, [
|
2898
|
+
[ Opcodes.local_get, pointer ],
|
2899
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
|
2900
|
+
]),
|
2778
2901
|
|
2779
2902
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2780
2903
|
|
@@ -2783,9 +2906,9 @@ const generateForOf = (scope, decl) => {
|
|
2783
2906
|
...generate(scope, decl.body),
|
2784
2907
|
[ Opcodes.end ],
|
2785
2908
|
|
2786
|
-
// increment iter pointer by valtype size
|
2909
|
+
// increment iter pointer by valtype size + 1
|
2787
2910
|
[ Opcodes.local_get, pointer ],
|
2788
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2911
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
2789
2912
|
[ Opcodes.i32_add ],
|
2790
2913
|
[ Opcodes.local_set, pointer ],
|
2791
2914
|
|
@@ -2901,6 +3024,44 @@ const generateForOf = (scope, decl) => {
|
|
2901
3024
|
[ Opcodes.end ],
|
2902
3025
|
[ Opcodes.end ]
|
2903
3026
|
],
|
3027
|
+
[TYPES.set]: [
|
3028
|
+
[ Opcodes.loop, Blocktype.void ],
|
3029
|
+
|
3030
|
+
[ Opcodes.local_get, pointer ],
|
3031
|
+
[ Opcodes.load, 0, ...unsignedLEB128(ValtypeSize.i32) ],
|
3032
|
+
|
3033
|
+
...setType(scope, leftName, [
|
3034
|
+
[ Opcodes.local_get, pointer ],
|
3035
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128(ValtypeSize.i32 + ValtypeSize[valtype]) ],
|
3036
|
+
]),
|
3037
|
+
|
3038
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
3039
|
+
|
3040
|
+
[ Opcodes.block, Blocktype.void ],
|
3041
|
+
[ Opcodes.block, Blocktype.void ],
|
3042
|
+
...generate(scope, decl.body),
|
3043
|
+
[ Opcodes.end ],
|
3044
|
+
|
3045
|
+
// increment iter pointer by valtype size + 1
|
3046
|
+
[ Opcodes.local_get, pointer ],
|
3047
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3048
|
+
[ Opcodes.i32_add ],
|
3049
|
+
[ Opcodes.local_set, pointer ],
|
3050
|
+
|
3051
|
+
// increment counter by 1
|
3052
|
+
[ Opcodes.local_get, counter ],
|
3053
|
+
...number(1, Valtype.i32),
|
3054
|
+
[ Opcodes.i32_add ],
|
3055
|
+
[ Opcodes.local_tee, counter ],
|
3056
|
+
|
3057
|
+
// loop if counter != length
|
3058
|
+
[ Opcodes.local_get, length ],
|
3059
|
+
[ Opcodes.i32_ne ],
|
3060
|
+
[ Opcodes.br_if, 1 ],
|
3061
|
+
|
3062
|
+
[ Opcodes.end ],
|
3063
|
+
[ Opcodes.end ]
|
3064
|
+
],
|
2904
3065
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2905
3066
|
}, Blocktype.void));
|
2906
3067
|
|
@@ -3002,14 +3163,18 @@ const generateThrow = (scope, decl) => {
|
|
3002
3163
|
};
|
3003
3164
|
|
3004
3165
|
const generateTry = (scope, decl) => {
|
3005
|
-
|
3166
|
+
// todo: handle control-flow pre-exit for finally
|
3167
|
+
// "Immediately before a control-flow statement (return, throw, break, continue) is executed in the try block or catch block."
|
3006
3168
|
|
3007
3169
|
const out = [];
|
3008
3170
|
|
3171
|
+
const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
|
3172
|
+
|
3009
3173
|
out.push([ Opcodes.try, Blocktype.void ]);
|
3010
3174
|
depth.push('try');
|
3011
3175
|
|
3012
3176
|
out.push(...generate(scope, decl.block));
|
3177
|
+
out.push(...finalizer);
|
3013
3178
|
|
3014
3179
|
if (decl.handler) {
|
3015
3180
|
depth.pop();
|
@@ -3017,6 +3182,7 @@ const generateTry = (scope, decl) => {
|
|
3017
3182
|
|
3018
3183
|
out.push([ Opcodes.catch_all ]);
|
3019
3184
|
out.push(...generate(scope, decl.handler.body));
|
3185
|
+
out.push(...finalizer);
|
3020
3186
|
}
|
3021
3187
|
|
3022
3188
|
out.push([ Opcodes.end ]);
|
@@ -3102,7 +3268,7 @@ const getAllocType = itemType => {
|
|
3102
3268
|
}
|
3103
3269
|
};
|
3104
3270
|
|
3105
|
-
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
3271
|
+
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype, typed = false) => {
|
3106
3272
|
const out = [];
|
3107
3273
|
|
3108
3274
|
scope.arrays ??= new Map();
|
@@ -3114,8 +3280,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3114
3280
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
3115
3281
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
3116
3282
|
|
3117
|
-
|
3118
|
-
|
3283
|
+
let page;
|
3284
|
+
if (Prefs.scopedPageNames) page = allocPage(scope, `${getAllocType(itemType)}: ${scope.name}/${uniqueName}`, itemType);
|
3285
|
+
else page = allocPage(scope, `${getAllocType(itemType)}: ${uniqueName}`, itemType);
|
3286
|
+
|
3287
|
+
// hack: use 1 for page 0 pointer for fast truthiness
|
3288
|
+
const ptr = page === 0 ? 1 : (page * pageSize);
|
3289
|
+
scope.arrays.set(name, ptr);
|
3119
3290
|
}
|
3120
3291
|
|
3121
3292
|
const pointer = scope.arrays.get(name);
|
@@ -3165,7 +3336,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3165
3336
|
|
3166
3337
|
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3167
3338
|
|
3168
|
-
// store length
|
3339
|
+
// store length
|
3169
3340
|
out.push(
|
3170
3341
|
...pointerWasm,
|
3171
3342
|
...number(length, Valtype.i32),
|
@@ -3173,14 +3344,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3173
3344
|
);
|
3174
3345
|
|
3175
3346
|
const storeOp = StoreOps[itemType];
|
3176
|
-
|
3347
|
+
const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
|
3177
3348
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3178
3349
|
if (elements[i] == null) continue;
|
3179
3350
|
|
3351
|
+
const offset = ValtypeSize.i32 + i * sizePerEl;
|
3180
3352
|
out.push(
|
3181
3353
|
...pointerWasm,
|
3182
3354
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
3183
|
-
[ storeOp,
|
3355
|
+
[ storeOp, 0, ...unsignedLEB128(offset) ],
|
3356
|
+
...(!typed ? [] : [ // typed presumes !useRawElements
|
3357
|
+
...pointerWasm,
|
3358
|
+
...getNodeType(scope, elements[i]),
|
3359
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128(offset + ValtypeSize[itemType]) ]
|
3360
|
+
])
|
3184
3361
|
);
|
3185
3362
|
}
|
3186
3363
|
|
@@ -3190,6 +3367,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3190
3367
|
return [ out, pointer ];
|
3191
3368
|
};
|
3192
3369
|
|
3370
|
+
const storeArray = (scope, array, index, element, aotPointer = null) => {
|
3371
|
+
if (!Array.isArray(element)) element = generate(scope, element);
|
3372
|
+
if (typeof index === 'number') index = number(index);
|
3373
|
+
|
3374
|
+
const offset = localTmp(scope, '#storeArray_offset', Valtype.i32);
|
3375
|
+
|
3376
|
+
return [
|
3377
|
+
// calculate offset
|
3378
|
+
...index,
|
3379
|
+
Opcodes.i32_to_u,
|
3380
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3381
|
+
[ Opcodes.i32_mul ],
|
3382
|
+
...(aotPointer ? [] : [
|
3383
|
+
...array,
|
3384
|
+
Opcodes.i32_to_u,
|
3385
|
+
[ Opcodes.i32_add ],
|
3386
|
+
]),
|
3387
|
+
[ Opcodes.local_set, offset ],
|
3388
|
+
|
3389
|
+
// store value
|
3390
|
+
[ Opcodes.local_get, offset ],
|
3391
|
+
...generate(scope, element),
|
3392
|
+
[ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3393
|
+
|
3394
|
+
// store type
|
3395
|
+
[ Opcodes.local_get, offset ],
|
3396
|
+
...getNodeType(scope, element),
|
3397
|
+
[ Opcodes.i32_store8, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
3398
|
+
];
|
3399
|
+
};
|
3400
|
+
|
3401
|
+
const loadArray = (scope, array, index, aotPointer = null) => {
|
3402
|
+
if (typeof index === 'number') index = number(index);
|
3403
|
+
|
3404
|
+
const offset = localTmp(scope, '#loadArray_offset', Valtype.i32);
|
3405
|
+
|
3406
|
+
return [
|
3407
|
+
// calculate offset
|
3408
|
+
...index,
|
3409
|
+
Opcodes.i32_to_u,
|
3410
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
3411
|
+
[ Opcodes.i32_mul ],
|
3412
|
+
...(aotPointer ? [] : [
|
3413
|
+
...array,
|
3414
|
+
Opcodes.i32_to_u,
|
3415
|
+
[ Opcodes.i32_add ],
|
3416
|
+
]),
|
3417
|
+
[ Opcodes.local_set, offset ],
|
3418
|
+
|
3419
|
+
// load value
|
3420
|
+
[ Opcodes.local_get, offset ],
|
3421
|
+
[ Opcodes.load, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3422
|
+
|
3423
|
+
// load type
|
3424
|
+
[ Opcodes.local_get, offset ],
|
3425
|
+
[ Opcodes.i32_load8_u, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32 + ValtypeSize[valtype]) ]
|
3426
|
+
];
|
3427
|
+
};
|
3428
|
+
|
3193
3429
|
const byteStringable = str => {
|
3194
3430
|
if (!Prefs.bytestring) return false;
|
3195
3431
|
|
@@ -3218,7 +3454,7 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
3218
3454
|
};
|
3219
3455
|
|
3220
3456
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
3221
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
3457
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
|
3222
3458
|
};
|
3223
3459
|
|
3224
3460
|
const generateObject = (scope, decl, global = false, name = '$undeclared') => {
|
@@ -3239,7 +3475,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
3239
3475
|
const name = decl.object.name;
|
3240
3476
|
const pointer = scope.arrays?.get(name);
|
3241
3477
|
|
3242
|
-
const aotPointer = Prefs.aotPointerOpt && pointer
|
3478
|
+
const aotPointer = Prefs.aotPointerOpt && pointer;
|
3243
3479
|
|
3244
3480
|
// hack: .name
|
3245
3481
|
if (decl.property.name === 'name') {
|
@@ -3259,8 +3495,7 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
3259
3495
|
if (decl.property.name === 'length') {
|
3260
3496
|
const func = funcs.find(x => x.name === name);
|
3261
3497
|
if (func) {
|
3262
|
-
const
|
3263
|
-
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
3498
|
+
const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
|
3264
3499
|
return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
|
3265
3500
|
}
|
3266
3501
|
|
@@ -3367,23 +3602,8 @@ const generateMember = (scope, decl, _global, _name) => {
|
|
3367
3602
|
|
3368
3603
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
3369
3604
|
[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)
|
3605
|
+
...loadArray(scope, object, property, aotPointer),
|
3606
|
+
...setLastType(scope)
|
3387
3607
|
],
|
3388
3608
|
|
3389
3609
|
[TYPES.string]: [
|
@@ -3501,33 +3721,39 @@ const generateFunc = (scope, decl) => {
|
|
3501
3721
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
3502
3722
|
const params = decl.params ?? [];
|
3503
3723
|
|
3504
|
-
// const innerScope = { ...scope };
|
3505
3724
|
// TODO: share scope/locals between !!!
|
3506
|
-
const
|
3725
|
+
const func = {
|
3507
3726
|
locals: {},
|
3508
3727
|
localInd: 0,
|
3509
3728
|
// value, type
|
3510
3729
|
returns: [ valtypeBinary, Valtype.i32 ],
|
3511
3730
|
throws: false,
|
3512
|
-
name
|
3731
|
+
name,
|
3732
|
+
index: currentFuncIndex++
|
3513
3733
|
};
|
3514
3734
|
|
3515
3735
|
if (typedInput && decl.returnType) {
|
3516
3736
|
const { type } = extractTypeAnnotation(decl.returnType);
|
3517
|
-
if (type != null && !Prefs.indirectCalls) {
|
3518
|
-
|
3519
|
-
|
3737
|
+
// if (type != null && !Prefs.indirectCalls) {
|
3738
|
+
if (type != null) {
|
3739
|
+
func.returnType = type;
|
3740
|
+
func.returns = [ valtypeBinary ];
|
3520
3741
|
}
|
3521
3742
|
}
|
3522
3743
|
|
3523
3744
|
for (let i = 0; i < params.length; i++) {
|
3524
|
-
|
3745
|
+
const name = params[i].name;
|
3746
|
+
// if (name == null) return todo('non-identifier args are not supported');
|
3747
|
+
|
3748
|
+
allocVar(func, name, false);
|
3525
3749
|
|
3526
3750
|
if (typedInput && params[i].typeAnnotation) {
|
3527
|
-
addVarMetadata(
|
3751
|
+
addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
|
3528
3752
|
}
|
3529
3753
|
}
|
3530
3754
|
|
3755
|
+
func.params = Object.values(func.locals).map(x => x.type);
|
3756
|
+
|
3531
3757
|
let body = objectHack(decl.body);
|
3532
3758
|
if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
|
3533
3759
|
// hack: () => 0 -> () => return 0
|
@@ -3537,37 +3763,23 @@ const generateFunc = (scope, decl) => {
|
|
3537
3763
|
};
|
3538
3764
|
}
|
3539
3765
|
|
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
3766
|
funcIndex[name] = func.index;
|
3767
|
+
funcs.push(func);
|
3548
3768
|
|
3549
|
-
|
3769
|
+
const wasm = generate(func, body);
|
3770
|
+
func.wasm = wasm;
|
3550
3771
|
|
3551
|
-
|
3552
|
-
for (const inst of wasm) {
|
3553
|
-
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
3554
|
-
inst[1] = func.index;
|
3555
|
-
}
|
3556
|
-
}
|
3772
|
+
if (name === 'main') func.gotLastType = true;
|
3557
3773
|
|
3558
3774
|
// add end return if not found
|
3559
3775
|
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
3560
3776
|
wasm.push(
|
3561
3777
|
...number(0),
|
3562
|
-
...(
|
3778
|
+
...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
|
3563
3779
|
[ Opcodes.return ]
|
3564
3780
|
);
|
3565
3781
|
}
|
3566
3782
|
|
3567
|
-
func.wasm = wasm;
|
3568
|
-
|
3569
|
-
funcs.push(func);
|
3570
|
-
|
3571
3783
|
return func;
|
3572
3784
|
};
|
3573
3785
|
|
@@ -3671,7 +3883,7 @@ const internalConstrs = {
|
|
3671
3883
|
generate: (scope, decl) => {
|
3672
3884
|
// todo: boolean object when used as constructor
|
3673
3885
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3674
|
-
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3886
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
|
3675
3887
|
},
|
3676
3888
|
type: TYPES.boolean,
|
3677
3889
|
length: 1
|
@@ -3816,9 +4028,8 @@ export default program => {
|
|
3816
4028
|
|
3817
4029
|
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3818
4030
|
|
3819
|
-
generateFunc(scope, program);
|
4031
|
+
const main = generateFunc(scope, program);
|
3820
4032
|
|
3821
|
-
const main = funcs[funcs.length - 1];
|
3822
4033
|
main.export = true;
|
3823
4034
|
main.returns = [ valtypeBinary, Valtype.i32 ];
|
3824
4035
|
|
@@ -3845,7 +4056,7 @@ export default program => {
|
|
3845
4056
|
}
|
3846
4057
|
|
3847
4058
|
// 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(
|
4059
|
+
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
|
3849
4060
|
|
3850
4061
|
return { funcs, globals, tags, exceptions, pages, data };
|
3851
4062
|
};
|