porffor 0.14.0-725e2cb36 → 0.14.0-7f4307d22
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 +9 -3
- package/README.md +8 -12
- package/asur/index.js +1 -1
- package/compiler/2c.js +65 -2
- 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/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/set.ts +19 -7
- package/compiler/builtins/string.ts +1 -0
- package/compiler/builtins/symbol.ts +3 -2
- package/compiler/builtins.js +33 -6
- package/compiler/codegen.js +546 -256
- package/compiler/generated_builtins.js +501 -61
- package/compiler/index.js +2 -1
- package/compiler/parse.js +1 -1
- package/compiler/precompile.js +5 -4
- package/compiler/prefs.js +1 -1
- package/compiler/prototype.js +180 -157
- package/compiler/wrap.js +66 -37
- 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':
|
@@ -140,18 +140,23 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
140
140
|
case 'ArrayExpression':
|
141
141
|
return generateArray(scope, decl, global, name);
|
142
142
|
|
143
|
+
case 'ObjectExpression':
|
144
|
+
return generateObject(scope, decl, global, name);
|
145
|
+
|
143
146
|
case 'MemberExpression':
|
144
147
|
return generateMember(scope, decl, global, name);
|
145
148
|
|
146
149
|
case 'ExportNamedDeclaration':
|
147
|
-
|
148
|
-
const funcsBefore = funcs.length;
|
150
|
+
const funcsBefore = funcs.map(x => x.name);
|
149
151
|
generate(scope, decl.declaration);
|
150
152
|
|
151
|
-
|
152
|
-
|
153
|
-
const
|
154
|
-
|
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
|
+
}
|
155
160
|
}
|
156
161
|
|
157
162
|
return [];
|
@@ -200,19 +205,11 @@ const generate = (scope, decl, global = false, name = undefined, valueUnused = f
|
|
200
205
|
|
201
206
|
__Porffor_bs: str => [
|
202
207
|
...makeString(scope, str, global, name, true),
|
203
|
-
|
204
|
-
...(name ? setType(scope, name, TYPES.bytestring) : [
|
205
|
-
...number(TYPES.bytestring, Valtype.i32),
|
206
|
-
...setLastType(scope)
|
207
|
-
])
|
208
|
+
...(name ? setType(scope, name, TYPES.bytestring) : setLastType(scope, TYPES.bytestring))
|
208
209
|
],
|
209
210
|
__Porffor_s: str => [
|
210
211
|
...makeString(scope, str, global, name, false),
|
211
|
-
|
212
|
-
...(name ? setType(scope, name, TYPES.string) : [
|
213
|
-
...number(TYPES.string, Valtype.i32),
|
214
|
-
...setLastType(scope)
|
215
|
-
])
|
212
|
+
...(name ? setType(scope, name, TYPES.string) : setLastType(scope, TYPES.string))
|
216
213
|
],
|
217
214
|
};
|
218
215
|
|
@@ -366,7 +363,7 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
366
363
|
};
|
367
364
|
|
368
365
|
const isIntOp = op => op && ((op[0] >= 0x45 && op[0] <= 0x4f) || (op[0] >= 0x67 && op[0] <= 0x78) || op[0] === 0x41);
|
369
|
-
const
|
366
|
+
const isIntToFloatOp = op => op && (op[0] >= 0xb7 && op[0] <= 0xba);
|
370
367
|
|
371
368
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
372
369
|
const checks = {
|
@@ -383,10 +380,10 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
383
380
|
|
384
381
|
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
385
382
|
// (like if we are in an if condition - very common)
|
386
|
-
const
|
387
|
-
const
|
383
|
+
const leftWasInt = isIntToFloatOp(left[left.length - 1]);
|
384
|
+
const rightWasInt = isIntToFloatOp(right[right.length - 1]);
|
388
385
|
|
389
|
-
const canInt =
|
386
|
+
const canInt = leftWasInt && rightWasInt;
|
390
387
|
|
391
388
|
if (canInt) {
|
392
389
|
// remove int -> float conversions from left and right
|
@@ -400,13 +397,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
400
397
|
[ Opcodes.if, Valtype.i32 ],
|
401
398
|
...right,
|
402
399
|
// note type
|
403
|
-
...rightType,
|
404
|
-
...setLastType(scope),
|
400
|
+
...setLastType(scope, rightType),
|
405
401
|
[ Opcodes.else ],
|
406
402
|
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
407
403
|
// note type
|
408
|
-
...leftType,
|
409
|
-
...setLastType(scope),
|
404
|
+
...setLastType(scope, leftType),
|
410
405
|
[ Opcodes.end ],
|
411
406
|
Opcodes.i32_from
|
412
407
|
];
|
@@ -419,13 +414,11 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
419
414
|
[ Opcodes.if, valtypeBinary ],
|
420
415
|
...right,
|
421
416
|
// note type
|
422
|
-
...rightType,
|
423
|
-
...setLastType(scope),
|
417
|
+
...setLastType(scope, rightType),
|
424
418
|
[ Opcodes.else ],
|
425
419
|
[ Opcodes.local_get, localTmp(scope, 'logictmp') ],
|
426
420
|
// note type
|
427
|
-
...leftType,
|
428
|
-
...setLastType(scope),
|
421
|
+
...setLastType(scope, leftType),
|
429
422
|
[ Opcodes.end ]
|
430
423
|
];
|
431
424
|
};
|
@@ -453,11 +446,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
|
|
453
446
|
...number(0, Valtype.i32), // base 0 for store later
|
454
447
|
|
455
448
|
...number(pointer, Valtype.i32),
|
456
|
-
[ Opcodes.i32_load,
|
449
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
457
450
|
[ Opcodes.local_tee, leftLength ],
|
458
451
|
|
459
452
|
[ Opcodes.local_get, rightPointer ],
|
460
|
-
[ Opcodes.i32_load,
|
453
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
461
454
|
[ Opcodes.local_tee, rightLength ],
|
462
455
|
|
463
456
|
[ Opcodes.i32_add ],
|
@@ -513,11 +506,11 @@ const concatStrings = (scope, left, right, global, name, assign = false, bytestr
|
|
513
506
|
...number(0, Valtype.i32), // base 0 for store later
|
514
507
|
|
515
508
|
[ Opcodes.local_get, leftPointer ],
|
516
|
-
[ Opcodes.i32_load,
|
509
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
517
510
|
[ Opcodes.local_tee, leftLength ],
|
518
511
|
|
519
512
|
[ Opcodes.local_get, rightPointer ],
|
520
|
-
[ Opcodes.i32_load,
|
513
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
521
514
|
[ Opcodes.local_tee, rightLength ],
|
522
515
|
|
523
516
|
[ Opcodes.i32_add ],
|
@@ -595,11 +588,11 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
|
|
595
588
|
|
596
589
|
// get lengths
|
597
590
|
[ Opcodes.local_get, leftPointer ],
|
598
|
-
[ Opcodes.i32_load,
|
591
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
599
592
|
[ Opcodes.local_tee, leftLength ],
|
600
593
|
|
601
594
|
[ Opcodes.local_get, rightPointer ],
|
602
|
-
[ Opcodes.i32_load,
|
595
|
+
[ Opcodes.i32_load, 0, ...unsignedLEB128(0) ],
|
603
596
|
|
604
597
|
// fast path: check leftLength != rightLength
|
605
598
|
[ Opcodes.i32_ne ],
|
@@ -655,9 +648,9 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
|
|
655
648
|
[ Opcodes.i32_add ],
|
656
649
|
[ Opcodes.local_tee, index ],
|
657
650
|
|
658
|
-
// if index
|
651
|
+
// if index < index end (length * sizeof valtype), loop
|
659
652
|
[ Opcodes.local_get, indexEnd ],
|
660
|
-
[ Opcodes.
|
653
|
+
[ Opcodes.i32_lt_s ],
|
661
654
|
[ Opcodes.br_if, 0 ],
|
662
655
|
[ Opcodes.end ],
|
663
656
|
|
@@ -675,38 +668,50 @@ const compareStrings = (scope, left, right, bytestrings = false) => {
|
|
675
668
|
];
|
676
669
|
};
|
677
670
|
|
678
|
-
const truthy = (scope, wasm, type, intIn = false, intOut = false) => {
|
679
|
-
if (
|
671
|
+
const truthy = (scope, wasm, type, intIn = false, intOut = false, forceTruthyMode = undefined) => {
|
672
|
+
if (isIntToFloatOp(wasm[wasm.length - 1])) return [
|
680
673
|
...wasm,
|
681
674
|
...(!intIn && intOut ? [ Opcodes.i32_to_u ] : [])
|
682
675
|
];
|
683
676
|
// if (isIntOp(wasm[wasm.length - 1])) return [ ...wasm ];
|
684
677
|
|
678
|
+
// todo/perf: use knownType and custom bytecode here instead of typeSwitch
|
679
|
+
|
685
680
|
const useTmp = knownType(scope, type) == null;
|
686
681
|
const tmp = useTmp && localTmp(scope, `#logicinner_tmp${intIn ? '_int' : ''}`, intIn ? Valtype.i32 : valtypeBinary);
|
687
682
|
|
688
|
-
const def =
|
689
|
-
|
690
|
-
|
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 ]),
|
691
688
|
|
692
|
-
|
693
|
-
|
689
|
+
[ Opcodes.i32_eqz ],
|
690
|
+
[ Opcodes.i32_eqz ],
|
694
691
|
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
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');
|
699
709
|
|
700
710
|
return [
|
701
711
|
...wasm,
|
702
712
|
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
703
713
|
|
704
714
|
...typeSwitch(scope, type, {
|
705
|
-
// [TYPES.number]: def,
|
706
|
-
[TYPES.array]: [
|
707
|
-
// arrays are always truthy
|
708
|
-
...number(1, intOut ? Valtype.i32 : valtypeBinary)
|
709
|
-
],
|
710
715
|
[TYPES.string]: [
|
711
716
|
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
712
717
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
@@ -742,10 +747,6 @@ const falsy = (scope, wasm, type, intIn = false, intOut = false) => {
|
|
742
747
|
...(!useTmp ? [] : [ [ Opcodes.local_set, tmp ] ]),
|
743
748
|
|
744
749
|
...typeSwitch(scope, type, {
|
745
|
-
[TYPES.array]: [
|
746
|
-
// arrays are always truthy
|
747
|
-
...number(0, intOut ? Valtype.i32 : valtypeBinary)
|
748
|
-
],
|
749
750
|
[TYPES.string]: [
|
750
751
|
...(!useTmp ? [] : [ [ Opcodes.local_get, tmp ] ]),
|
751
752
|
...(intIn ? [] : [ Opcodes.i32_to_u ]),
|
@@ -989,7 +990,7 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
989
990
|
// if both are true
|
990
991
|
[ Opcodes.i32_and ],
|
991
992
|
[ Opcodes.if, Blocktype.void ],
|
992
|
-
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ]),
|
993
|
+
...compareStrings(scope, [ [ Opcodes.local_get, tmpLeft ] ], [ [ Opcodes.local_get, tmpRight ] ], false),
|
993
994
|
...(op === '!==' || op === '!=' ? [ [ Opcodes.i32_eqz ] ] : []),
|
994
995
|
[ Opcodes.br, 1 ],
|
995
996
|
[ Opcodes.end ],
|
@@ -1038,14 +1039,14 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
1038
1039
|
return out;
|
1039
1040
|
};
|
1040
1041
|
|
1041
|
-
const asmFuncToAsm = (func,
|
1042
|
-
return func(
|
1042
|
+
const asmFuncToAsm = (func, scope) => {
|
1043
|
+
return func(scope, {
|
1043
1044
|
TYPES, TYPE_NAMES, typeSwitch, makeArray, makeString, allocPage, internalThrow,
|
1044
|
-
builtin:
|
1045
|
-
let idx = funcIndex[
|
1046
|
-
if (idx
|
1047
|
-
includeBuiltin(null,
|
1048
|
-
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];
|
1049
1050
|
}
|
1050
1051
|
|
1051
1052
|
return idx;
|
@@ -1053,7 +1054,7 @@ const asmFuncToAsm = (func, { name = '#unknown_asm_func', params = [], locals =
|
|
1053
1054
|
});
|
1054
1055
|
};
|
1055
1056
|
|
1056
|
-
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 }) => {
|
1057
1058
|
const existing = funcs.find(x => x.name === name);
|
1058
1059
|
if (existing) return existing;
|
1059
1060
|
|
@@ -1071,7 +1072,22 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1071
1072
|
data.push(copy);
|
1072
1073
|
}
|
1073
1074
|
|
1074
|
-
|
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);
|
1075
1091
|
|
1076
1092
|
let baseGlobalIdx, i = 0;
|
1077
1093
|
for (const type of globalTypes) {
|
@@ -1090,19 +1106,14 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
1090
1106
|
}
|
1091
1107
|
}
|
1092
1108
|
|
1093
|
-
const
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
wasm,
|
1100
|
-
internal: true,
|
1101
|
-
index: currentFuncIndex++
|
1102
|
-
};
|
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
|
+
}
|
1103
1115
|
|
1104
|
-
|
1105
|
-
funcIndex[name] = func.index;
|
1116
|
+
func.wasm = wasm;
|
1106
1117
|
|
1107
1118
|
return func;
|
1108
1119
|
};
|
@@ -1194,9 +1205,10 @@ const getLastType = scope => {
|
|
1194
1205
|
return [ [ Opcodes.local_get, localTmp(scope, '#last_type', Valtype.i32) ] ];
|
1195
1206
|
};
|
1196
1207
|
|
1197
|
-
const setLastType = scope =>
|
1198
|
-
|
1199
|
-
|
1208
|
+
const setLastType = (scope, type = []) => [
|
1209
|
+
...(typeof type === 'number' ? number(type, Valtype.i32) : type),
|
1210
|
+
[ Opcodes.local_set, localTmp(scope, '#last_type', Valtype.i32) ]
|
1211
|
+
];
|
1200
1212
|
|
1201
1213
|
const getNodeType = (scope, node) => {
|
1202
1214
|
const ret = (() => {
|
@@ -1257,7 +1269,17 @@ const getNodeType = (scope, node) => {
|
|
1257
1269
|
|
1258
1270
|
const func = spl[spl.length - 1];
|
1259
1271
|
const protoFuncs = Object.keys(prototypeFuncs).filter(x => x != TYPES.bytestring && prototypeFuncs[x][func] != null);
|
1260
|
-
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
|
+
}
|
1261
1283
|
}
|
1262
1284
|
|
1263
1285
|
if (name.startsWith('__Porffor_wasm_')) {
|
@@ -1352,22 +1374,27 @@ const getNodeType = (scope, node) => {
|
|
1352
1374
|
}
|
1353
1375
|
|
1354
1376
|
if (node.type === 'MemberExpression') {
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
return TYPES.undefined;
|
1361
|
-
}
|
1377
|
+
const name = node.property.name;
|
1378
|
+
|
1379
|
+
if (name === 'length') {
|
1380
|
+
if (hasFuncWithName(node.object.name)) return TYPES.number;
|
1381
|
+
if (Prefs.fastLength) return TYPES.number;
|
1362
1382
|
}
|
1363
1383
|
|
1364
|
-
// hack: if something.length, number type
|
1365
|
-
if (node.property.name === 'length') return TYPES.number;
|
1366
1384
|
|
1367
|
-
|
1368
|
-
if (
|
1369
|
-
|
1370
|
-
|
1385
|
+
const objectKnownType = knownType(scope, getNodeType(scope, node.object));
|
1386
|
+
if (objectKnownType != null) {
|
1387
|
+
if (name === 'length') {
|
1388
|
+
if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(objectKnownType)) return TYPES.number;
|
1389
|
+
else return TYPES.undefined;
|
1390
|
+
}
|
1391
|
+
|
1392
|
+
if (node.computed) {
|
1393
|
+
if (objectKnownType === TYPES.string) return TYPES.string;
|
1394
|
+
if (objectKnownType === TYPES.bytestring) return TYPES.bytestring;
|
1395
|
+
if (objectKnownType === TYPES.array) return TYPES.number;
|
1396
|
+
}
|
1397
|
+
}
|
1371
1398
|
|
1372
1399
|
if (scope.locals['#last_type']) return getLastType(scope);
|
1373
1400
|
|
@@ -1438,16 +1465,11 @@ const countLeftover = wasm => {
|
|
1438
1465
|
else if (inst[0] === Opcodes.return) count = 0;
|
1439
1466
|
else if (inst[0] === Opcodes.call) {
|
1440
1467
|
let func = funcs.find(x => x.index === inst[1]);
|
1441
|
-
if (inst[1]
|
1442
|
-
|
1443
|
-
|
1444
|
-
count -= importedFuncs[inst[1]].params;
|
1445
|
-
count += importedFuncs[inst[1]].returns;
|
1468
|
+
if (inst[1] < importedFuncs.length) {
|
1469
|
+
func = importedFuncs[inst[1]];
|
1470
|
+
count = count - func.params + func.returns;
|
1446
1471
|
} else {
|
1447
|
-
|
1448
|
-
count -= func.params.length;
|
1449
|
-
} else count--;
|
1450
|
-
if (func) count += func.returns.length;
|
1472
|
+
count = count - func.params.length + func.returns.length;
|
1451
1473
|
}
|
1452
1474
|
} else if (inst[0] === Opcodes.call_indirect) {
|
1453
1475
|
count--; // funcidx
|
@@ -1470,7 +1492,7 @@ const disposeLeftover = wasm => {
|
|
1470
1492
|
const generateExp = (scope, decl) => {
|
1471
1493
|
const expression = decl.expression;
|
1472
1494
|
|
1473
|
-
const out = generate(scope, expression, undefined, undefined,
|
1495
|
+
const out = generate(scope, expression, undefined, undefined, Prefs.optUnused);
|
1474
1496
|
disposeLeftover(out);
|
1475
1497
|
|
1476
1498
|
return out;
|
@@ -1561,16 +1583,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1561
1583
|
out.splice(out.length - 1, 1);
|
1562
1584
|
|
1563
1585
|
const finalStatement = parsed.body[parsed.body.length - 1];
|
1564
|
-
out.push(
|
1565
|
-
...getNodeType(scope, finalStatement),
|
1566
|
-
...setLastType(scope)
|
1567
|
-
);
|
1586
|
+
out.push(...setLastType(scope, getNodeType(scope, finalStatement)));
|
1568
1587
|
} else if (countLeftover(out) === 0) {
|
1569
1588
|
out.push(...number(UNDEFINED));
|
1570
|
-
out.push(
|
1571
|
-
...number(TYPES.undefined, Valtype.i32),
|
1572
|
-
...setLastType(scope)
|
1573
|
-
);
|
1589
|
+
out.push(...setLastType(scope, TYPES.undefined));
|
1574
1590
|
}
|
1575
1591
|
|
1576
1592
|
// if (lastInst && lastInst[0] === Opcodes.drop) {
|
@@ -1606,6 +1622,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1606
1622
|
|
1607
1623
|
if (!funcIndex[rhemynName]) {
|
1608
1624
|
const func = Rhemyn[funcName](regex, currentFuncIndex++, rhemynName);
|
1625
|
+
func.internal = true;
|
1609
1626
|
|
1610
1627
|
funcIndex[func.name] = func.index;
|
1611
1628
|
funcs.push(func);
|
@@ -1622,8 +1639,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1622
1639
|
[ Opcodes.call, idx ],
|
1623
1640
|
Opcodes.i32_from_u,
|
1624
1641
|
|
1625
|
-
...
|
1626
|
-
...setLastType(scope)
|
1642
|
+
...setLastType(scope, TYPES.boolean)
|
1627
1643
|
];
|
1628
1644
|
}
|
1629
1645
|
|
@@ -1695,9 +1711,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1695
1711
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) {
|
1696
1712
|
protoBC[x] = [
|
1697
1713
|
...RTArrayUtil.getLength(getPointer),
|
1698
|
-
|
1699
|
-
...number(TYPES.number, Valtype.i32),
|
1700
|
-
...setLastType(scope)
|
1714
|
+
...setLastType(scope, TYPES.number)
|
1701
1715
|
];
|
1702
1716
|
continue;
|
1703
1717
|
}
|
@@ -1716,7 +1730,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1716
1730
|
getI32: () => RTArrayUtil.getLengthI32(getPointer),
|
1717
1731
|
set: value => RTArrayUtil.setLength(getPointer, value),
|
1718
1732
|
setI32: value => RTArrayUtil.setLengthI32(getPointer, value)
|
1719
|
-
}, 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) => {
|
1720
1734
|
return makeArray(scope, {
|
1721
1735
|
rawElements: new Array(length)
|
1722
1736
|
}, _global, _name, true, itemType);
|
@@ -1730,9 +1744,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1730
1744
|
protoBC[x] = [
|
1731
1745
|
[ Opcodes.block, unusedValue && optUnused ? Blocktype.void : valtypeBinary ],
|
1732
1746
|
...protoOut,
|
1733
|
-
|
1734
|
-
...number(protoFunc.returnType ?? TYPES.number, Valtype.i32),
|
1735
|
-
...setLastType(scope),
|
1747
|
+
...(unusedValue && optUnused ? [] : (protoFunc.returnType != null ? setLastType(scope, protoFunc.returnType) : setLastType(scope))),
|
1736
1748
|
[ Opcodes.end ]
|
1737
1749
|
];
|
1738
1750
|
}
|
@@ -1782,11 +1794,6 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1782
1794
|
|
1783
1795
|
if (idx === undefined && internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1784
1796
|
|
1785
|
-
if (idx === undefined && name === scope.name) {
|
1786
|
-
// hack: calling self, func generator will fix later
|
1787
|
-
idx = -1;
|
1788
|
-
}
|
1789
|
-
|
1790
1797
|
if (idx === undefined && name.startsWith('__Porffor_wasm_')) {
|
1791
1798
|
const wasmOps = {
|
1792
1799
|
// pointer, align, offset
|
@@ -1838,36 +1845,128 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1838
1845
|
const [ local, global ] = lookupName(scope, name);
|
1839
1846
|
if (!Prefs.indirectCalls || local == null) return internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true);
|
1840
1847
|
|
1841
|
-
// todo: only works when
|
1842
|
-
|
1843
|
-
|
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
|
1844
1857
|
|
1845
1858
|
funcs.table = true;
|
1859
|
+
scope.table = true;
|
1846
1860
|
|
1847
1861
|
let args = decl.arguments;
|
1848
|
-
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
|
+
}
|
1849
1873
|
|
1850
1874
|
for (let i = 0; i < args.length; i++) {
|
1851
1875
|
const arg = args[i];
|
1852
|
-
|
1876
|
+
out = out.concat(generate(scope, arg));
|
1853
1877
|
|
1854
1878
|
if (valtypeBinary !== Valtype.i32 && (
|
1855
1879
|
(builtinFuncs[name] && builtinFuncs[name].params[i * (typedParams ? 2 : 1)] === Valtype.i32) ||
|
1856
1880
|
(importedFuncs[name] && name.startsWith('profile'))
|
1857
1881
|
)) {
|
1858
|
-
|
1882
|
+
out.push(Opcodes.i32_to);
|
1859
1883
|
}
|
1860
1884
|
|
1861
|
-
|
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
|
+
);
|
1897
|
+
}
|
1898
|
+
}
|
1899
|
+
|
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
|
+
});
|
1862
1911
|
}
|
1863
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
|
+
|
1864
1954
|
return typeSwitch(scope, getNodeType(scope, decl.callee), {
|
1865
1955
|
[TYPES.function]: [
|
1866
|
-
...
|
1956
|
+
...out,
|
1957
|
+
|
1867
1958
|
[ global ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1868
1959
|
Opcodes.i32_to_u,
|
1869
|
-
[ Opcodes.
|
1870
|
-
|
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)
|
1871
1970
|
],
|
1872
1971
|
default: internalThrow(scope, 'TypeError', `${unhackName(name)} is not a function`, true)
|
1873
1972
|
});
|
@@ -1876,11 +1975,10 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1876
1975
|
return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`, true);
|
1877
1976
|
}
|
1878
1977
|
|
1879
|
-
const func = funcs.find(x => x.index === idx);
|
1880
|
-
|
1881
|
-
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;
|
1882
1980
|
const typedParams = userFunc || builtinFuncs[name]?.typedParams;
|
1883
|
-
const typedReturns = (func
|
1981
|
+
const typedReturns = (func && func.returnType == null) || builtinFuncs[name]?.typedReturns;
|
1884
1982
|
const paramCount = func && (typedParams ? func.params.length / 2 : func.params.length);
|
1885
1983
|
|
1886
1984
|
let args = decl.arguments;
|
@@ -1901,6 +1999,7 @@ const generateCall = (scope, decl, _global, _name, unusedValue = false) => {
|
|
1901
1999
|
const arg = args[i];
|
1902
2000
|
out = out.concat(generate(scope, arg));
|
1903
2001
|
|
2002
|
+
// todo: this should be used instead of the too many args thing above (by removing that)
|
1904
2003
|
if (i >= paramCount) {
|
1905
2004
|
// over param count of func, drop arg
|
1906
2005
|
out.push([ Opcodes.drop ]);
|
@@ -1985,8 +2084,11 @@ const knownType = (scope, type) => {
|
|
1985
2084
|
const idx = type[0][1];
|
1986
2085
|
|
1987
2086
|
// type idx = var idx + 1
|
1988
|
-
const
|
1989
|
-
if (
|
2087
|
+
const name = Object.values(scope.locals).find(x => x.idx === idx)?.name;
|
2088
|
+
if (name) {
|
2089
|
+
const local = scope.locals[name];
|
2090
|
+
if (local.metadata?.type != null) return v.metadata.type;
|
2091
|
+
}
|
1990
2092
|
}
|
1991
2093
|
|
1992
2094
|
return null;
|
@@ -2021,16 +2123,17 @@ const brTable = (input, bc, returns) => {
|
|
2021
2123
|
}
|
2022
2124
|
|
2023
2125
|
for (let i = 0; i < count; i++) {
|
2024
|
-
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 ]);
|
2025
2128
|
else out.push([ Opcodes.block, Blocktype.void ]);
|
2026
2129
|
}
|
2027
2130
|
|
2028
|
-
const nums = keys.filter(x => +x);
|
2131
|
+
const nums = keys.filter(x => +x >= 0);
|
2029
2132
|
const offset = Math.min(...nums);
|
2030
2133
|
const max = Math.max(...nums);
|
2031
2134
|
|
2032
2135
|
const table = [];
|
2033
|
-
let br =
|
2136
|
+
let br = 0;
|
2034
2137
|
|
2035
2138
|
for (let i = offset; i <= max; i++) {
|
2036
2139
|
// if branch for this num, go to that block
|
@@ -2070,10 +2173,9 @@ const brTable = (input, bc, returns) => {
|
|
2070
2173
|
br--;
|
2071
2174
|
}
|
2072
2175
|
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
];
|
2176
|
+
out.push([ Opcodes.end ]);
|
2177
|
+
|
2178
|
+
return out;
|
2077
2179
|
};
|
2078
2180
|
|
2079
2181
|
const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
@@ -2117,6 +2219,17 @@ const typeSwitch = (scope, type, bc, returns = valtypeBinary) => {
|
|
2117
2219
|
return out;
|
2118
2220
|
};
|
2119
2221
|
|
2222
|
+
const typeIsOneOf = (type, types, valtype = Valtype.i32) => {
|
2223
|
+
const out = [];
|
2224
|
+
|
2225
|
+
for (let i = 0; i < types.length; i++) {
|
2226
|
+
out.push(...type, ...number(types[i], valtype), valtype === Valtype.f64 ? [ Opcodes.f64_eq ] : [ Opcodes.i32_eq ]);
|
2227
|
+
if (i !== 0) out.push([ Opcodes.i32_or ]);
|
2228
|
+
}
|
2229
|
+
|
2230
|
+
return out;
|
2231
|
+
};
|
2232
|
+
|
2120
2233
|
const allocVar = (scope, name, global = false, type = true) => {
|
2121
2234
|
const target = global ? globals : scope.locals;
|
2122
2235
|
|
@@ -2133,7 +2246,7 @@ const allocVar = (scope, name, global = false, type = true) => {
|
|
2133
2246
|
|
2134
2247
|
if (type) {
|
2135
2248
|
let typeIdx = global ? globalInd++ : scope.localInd++;
|
2136
|
-
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32 };
|
2249
|
+
target[name + '#type'] = { idx: typeIdx, type: Valtype.i32, name };
|
2137
2250
|
}
|
2138
2251
|
|
2139
2252
|
return idx;
|
@@ -2346,18 +2459,21 @@ const generateAssign = (scope, decl, _global, _name, valueUnused = false) => {
|
|
2346
2459
|
Opcodes.i32_to_u,
|
2347
2460
|
|
2348
2461
|
// turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
2349
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2462
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
2350
2463
|
[ Opcodes.i32_mul ],
|
2351
2464
|
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
2352
2465
|
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
2353
2466
|
|
2354
2467
|
...(op === '=' ? generate(scope, decl.right) : performOp(scope, op, [
|
2355
2468
|
[ Opcodes.local_get, pointerTmp ],
|
2356
|
-
[ Opcodes.load,
|
2357
|
-
], 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)),
|
2358
2474
|
[ Opcodes.local_tee, newValueTmp ],
|
2359
2475
|
|
2360
|
-
[ Opcodes.store,
|
2476
|
+
[ Opcodes.store, 0, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
2361
2477
|
],
|
2362
2478
|
|
2363
2479
|
default: internalThrow(scope, 'TypeError', `Cannot assign member with non-array`)
|
@@ -2465,6 +2581,11 @@ const generateUnary = (scope, decl) => {
|
|
2465
2581
|
];
|
2466
2582
|
|
2467
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
|
+
}
|
2468
2589
|
// !=
|
2469
2590
|
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument), false, false);
|
2470
2591
|
|
@@ -2611,21 +2732,16 @@ const generateConditional = (scope, decl) => {
|
|
2611
2732
|
out.push([ Opcodes.if, valtypeBinary ]);
|
2612
2733
|
depth.push('if');
|
2613
2734
|
|
2614
|
-
out.push(...generate(scope, decl.consequent));
|
2615
|
-
|
2616
|
-
// note type
|
2617
2735
|
out.push(
|
2618
|
-
...
|
2619
|
-
...setLastType(scope)
|
2736
|
+
...generate(scope, decl.consequent),
|
2737
|
+
...setLastType(scope, getNodeType(scope, decl.consequent))
|
2620
2738
|
);
|
2621
2739
|
|
2622
2740
|
out.push([ Opcodes.else ]);
|
2623
|
-
out.push(...generate(scope, decl.alternate));
|
2624
2741
|
|
2625
|
-
// note type
|
2626
2742
|
out.push(
|
2627
|
-
...
|
2628
|
-
...setLastType(scope)
|
2743
|
+
...generate(scope, decl.alternate),
|
2744
|
+
...setLastType(scope, getNodeType(scope, decl.alternate))
|
2629
2745
|
);
|
2630
2746
|
|
2631
2747
|
out.push([ Opcodes.end ]);
|
@@ -2773,12 +2889,15 @@ const generateForOf = (scope, decl) => {
|
|
2773
2889
|
// todo: optimize away counter and use end pointer
|
2774
2890
|
out.push(...typeSwitch(scope, getNodeType(scope, decl.right), {
|
2775
2891
|
[TYPES.array]: [
|
2776
|
-
...setType(scope, leftName, TYPES.number),
|
2777
|
-
|
2778
2892
|
[ Opcodes.loop, Blocktype.void ],
|
2779
2893
|
|
2780
2894
|
[ Opcodes.local_get, pointer ],
|
2781
|
-
[ 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
|
+
]),
|
2782
2901
|
|
2783
2902
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
2784
2903
|
|
@@ -2787,9 +2906,9 @@ const generateForOf = (scope, decl) => {
|
|
2787
2906
|
...generate(scope, decl.body),
|
2788
2907
|
[ Opcodes.end ],
|
2789
2908
|
|
2790
|
-
// increment iter pointer by valtype size
|
2909
|
+
// increment iter pointer by valtype size + 1
|
2791
2910
|
[ Opcodes.local_get, pointer ],
|
2792
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
2911
|
+
...number(ValtypeSize[valtype] + 1, Valtype.i32),
|
2793
2912
|
[ Opcodes.i32_add ],
|
2794
2913
|
[ Opcodes.local_set, pointer ],
|
2795
2914
|
|
@@ -2905,6 +3024,44 @@ const generateForOf = (scope, decl) => {
|
|
2905
3024
|
[ Opcodes.end ],
|
2906
3025
|
[ Opcodes.end ]
|
2907
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
|
+
],
|
2908
3065
|
default: internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`)
|
2909
3066
|
}, Blocktype.void));
|
2910
3067
|
|
@@ -3006,14 +3163,18 @@ const generateThrow = (scope, decl) => {
|
|
3006
3163
|
};
|
3007
3164
|
|
3008
3165
|
const generateTry = (scope, decl) => {
|
3009
|
-
|
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."
|
3010
3168
|
|
3011
3169
|
const out = [];
|
3012
3170
|
|
3171
|
+
const finalizer = decl.finalizer ? generate(scope, decl.finalizer) : [];
|
3172
|
+
|
3013
3173
|
out.push([ Opcodes.try, Blocktype.void ]);
|
3014
3174
|
depth.push('try');
|
3015
3175
|
|
3016
3176
|
out.push(...generate(scope, decl.block));
|
3177
|
+
out.push(...finalizer);
|
3017
3178
|
|
3018
3179
|
if (decl.handler) {
|
3019
3180
|
depth.pop();
|
@@ -3021,6 +3182,7 @@ const generateTry = (scope, decl) => {
|
|
3021
3182
|
|
3022
3183
|
out.push([ Opcodes.catch_all ]);
|
3023
3184
|
out.push(...generate(scope, decl.handler.body));
|
3185
|
+
out.push(...finalizer);
|
3024
3186
|
}
|
3025
3187
|
|
3026
3188
|
out.push([ Opcodes.end ]);
|
@@ -3106,7 +3268,7 @@ const getAllocType = itemType => {
|
|
3106
3268
|
}
|
3107
3269
|
};
|
3108
3270
|
|
3109
|
-
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) => {
|
3110
3272
|
const out = [];
|
3111
3273
|
|
3112
3274
|
scope.arrays ??= new Map();
|
@@ -3118,8 +3280,13 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3118
3280
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
3119
3281
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
3120
3282
|
|
3121
|
-
|
3122
|
-
|
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);
|
3123
3290
|
}
|
3124
3291
|
|
3125
3292
|
const pointer = scope.arrays.get(name);
|
@@ -3169,7 +3336,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3169
3336
|
|
3170
3337
|
const pointerWasm = pointerTmp != null ? [ [ Opcodes.local_get, pointerTmp ] ] : number(pointer, Valtype.i32);
|
3171
3338
|
|
3172
|
-
// store length
|
3339
|
+
// store length
|
3173
3340
|
out.push(
|
3174
3341
|
...pointerWasm,
|
3175
3342
|
...number(length, Valtype.i32),
|
@@ -3177,14 +3344,20 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3177
3344
|
);
|
3178
3345
|
|
3179
3346
|
const storeOp = StoreOps[itemType];
|
3180
|
-
|
3347
|
+
const sizePerEl = ValtypeSize[itemType] + (typed ? 1 : 0);
|
3181
3348
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
3182
3349
|
if (elements[i] == null) continue;
|
3183
3350
|
|
3351
|
+
const offset = ValtypeSize.i32 + i * sizePerEl;
|
3184
3352
|
out.push(
|
3185
3353
|
...pointerWasm,
|
3186
3354
|
...(useRawElements ? number(elements[i], Valtype[valtype]) : generate(scope, elements[i])),
|
3187
|
-
[ 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
|
+
])
|
3188
3361
|
);
|
3189
3362
|
}
|
3190
3363
|
|
@@ -3194,6 +3367,65 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
3194
3367
|
return [ out, pointer ];
|
3195
3368
|
};
|
3196
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
|
+
|
3197
3429
|
const byteStringable = str => {
|
3198
3430
|
if (!Prefs.bytestring) return false;
|
3199
3431
|
|
@@ -3222,14 +3454,39 @@ const makeString = (scope, str, global = false, name = '$undeclared', forceBytes
|
|
3222
3454
|
};
|
3223
3455
|
|
3224
3456
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
3225
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
3457
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype, true)[0];
|
3226
3458
|
};
|
3227
3459
|
|
3228
|
-
|
3460
|
+
const generateObject = (scope, decl, global = false, name = '$undeclared') => {
|
3461
|
+
if (decl.properties.length > 0) return todo(scope, 'objects are not supported yet', true);
|
3462
|
+
|
3463
|
+
return [
|
3464
|
+
...number(1),
|
3465
|
+
...setLastType(scope, TYPES.object)
|
3466
|
+
];
|
3467
|
+
};
|
3468
|
+
|
3469
|
+
const withType = (scope, wasm, type) => [
|
3470
|
+
...wasm,
|
3471
|
+
...setLastType(scope, type)
|
3472
|
+
];
|
3473
|
+
|
3474
|
+
const generateMember = (scope, decl, _global, _name) => {
|
3229
3475
|
const name = decl.object.name;
|
3230
|
-
const pointer = scope.arrays?.get(name);
|
3231
3476
|
|
3232
|
-
|
3477
|
+
// hack: process.argv[n]
|
3478
|
+
if (name === '__process_argv') {
|
3479
|
+
const setPointer = scope.arrays?.get(_name);
|
3480
|
+
|
3481
|
+
return [
|
3482
|
+
...number(decl.property.value - 1),
|
3483
|
+
...(setPointer ? number(setPointer) : makeArray(scope, { elements: [] }, undefined, undefined, true, 'i8')[0]),
|
3484
|
+
[ Opcodes.call, importedFuncs.__Porffor_readArgv ]
|
3485
|
+
];
|
3486
|
+
}
|
3487
|
+
|
3488
|
+
const pointer = scope.arrays?.get(name);
|
3489
|
+
const aotPointer = Prefs.aotPointerOpt && pointer;
|
3233
3490
|
|
3234
3491
|
// hack: .name
|
3235
3492
|
if (decl.property.name === 'name') {
|
@@ -3239,9 +3496,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
3239
3496
|
// eg: __String_prototype_toLowerCase -> toLowerCase
|
3240
3497
|
if (nameProp.startsWith('__')) nameProp = nameProp.split('_').pop();
|
3241
3498
|
|
3242
|
-
return makeString(scope, nameProp, _global, _name, true);
|
3499
|
+
return withType(scope, makeString(scope, nameProp, _global, _name, true), TYPES.bytestring);
|
3243
3500
|
} else {
|
3244
|
-
return
|
3501
|
+
return withType(scope, number(0), TYPES.undefined);
|
3245
3502
|
}
|
3246
3503
|
}
|
3247
3504
|
|
@@ -3249,9 +3506,8 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
3249
3506
|
if (decl.property.name === 'length') {
|
3250
3507
|
const func = funcs.find(x => x.name === name);
|
3251
3508
|
if (func) {
|
3252
|
-
const
|
3253
|
-
|
3254
|
-
return number(typedParams ? func.params.length / 2 : func.params.length);
|
3509
|
+
const typedParams = !func.internal || builtinFuncs[name]?.typedParams;
|
3510
|
+
return withType(scope, number(typedParams ? func.params.length / 2 : func.params.length), TYPES.number);
|
3255
3511
|
}
|
3256
3512
|
|
3257
3513
|
if (builtinFuncs[name + '$constructor']) {
|
@@ -3261,24 +3517,88 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
3261
3517
|
const constructorFunc = builtinFuncs[name + '$constructor'];
|
3262
3518
|
const constructorParams = constructorFunc.typedParams ? (constructorFunc.params.length / 2) : constructorFunc.params.length;
|
3263
3519
|
|
3264
|
-
return number(Math.max(regularParams, constructorParams));
|
3520
|
+
return withType(scope, number(Math.max(regularParams, constructorParams)), TYPES.number);
|
3265
3521
|
}
|
3266
3522
|
|
3267
|
-
if (builtinFuncs[name]) return number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length);
|
3268
|
-
if (importedFuncs[name]) return number(importedFuncs[name].params);
|
3269
|
-
if (internalConstrs[name]) return number(internalConstrs[name].length ?? 0);
|
3523
|
+
if (builtinFuncs[name]) return withType(scope, number(builtinFuncs[name].typedParams ? (builtinFuncs[name].params.length / 2) : builtinFuncs[name].params.length), TYPES.number);
|
3524
|
+
if (importedFuncs[name]) return withType(scope, number(importedFuncs[name].params), TYPES.number);
|
3525
|
+
if (internalConstrs[name]) return withType(scope, number(internalConstrs[name].length ?? 0), TYPES.number);
|
3526
|
+
|
3527
|
+
if (Prefs.fastLength) {
|
3528
|
+
// presume valid length object
|
3529
|
+
return [
|
3530
|
+
...(aotPointer ? number(0, Valtype.i32) : [
|
3531
|
+
...generate(scope, decl.object),
|
3532
|
+
Opcodes.i32_to_u
|
3533
|
+
]),
|
3534
|
+
|
3535
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
|
3536
|
+
Opcodes.i32_from_u
|
3537
|
+
];
|
3538
|
+
}
|
3539
|
+
|
3540
|
+
const type = getNodeType(scope, decl.object);
|
3541
|
+
const known = knownType(scope, type);
|
3542
|
+
if (known != null) {
|
3543
|
+
if ([ TYPES.string, TYPES.bytestring, TYPES.array ].includes(known)) return [
|
3544
|
+
...(aotPointer ? number(0, Valtype.i32) : [
|
3545
|
+
...generate(scope, decl.object),
|
3546
|
+
Opcodes.i32_to_u
|
3547
|
+
]),
|
3548
|
+
|
3549
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
|
3550
|
+
Opcodes.i32_from_u
|
3551
|
+
];
|
3552
|
+
|
3553
|
+
return number(0);
|
3554
|
+
}
|
3270
3555
|
|
3271
3556
|
return [
|
3272
|
-
...(
|
3273
|
-
|
3274
|
-
|
3275
|
-
|
3557
|
+
...typeIsOneOf(getNodeType(scope, decl.object), [ TYPES.string, TYPES.bytestring, TYPES.array ]),
|
3558
|
+
[ Opcodes.if, valtypeBinary ],
|
3559
|
+
...(aotPointer ? number(0, Valtype.i32) : [
|
3560
|
+
...generate(scope, decl.object),
|
3561
|
+
Opcodes.i32_to_u
|
3562
|
+
]),
|
3276
3563
|
|
3277
|
-
|
3278
|
-
|
3564
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(aotPointer ? pointer : 0) ],
|
3565
|
+
Opcodes.i32_from_u,
|
3566
|
+
|
3567
|
+
...setLastType(scope, TYPES.number),
|
3568
|
+
[ Opcodes.else ],
|
3569
|
+
...number(0),
|
3570
|
+
...setLastType(scope, TYPES.undefined),
|
3571
|
+
[ Opcodes.end ]
|
3279
3572
|
];
|
3280
3573
|
}
|
3281
3574
|
|
3575
|
+
// todo: generate this array procedurally during builtinFuncs creation
|
3576
|
+
if (['size', 'description'].includes(decl.property.name)) {
|
3577
|
+
const bc = {};
|
3578
|
+
const cands = Object.keys(builtinFuncs).filter(x => x.startsWith('__') && x.endsWith('_prototype_' + decl.property.name + '$get'));
|
3579
|
+
|
3580
|
+
if (cands.length > 0) {
|
3581
|
+
for (const x of cands) {
|
3582
|
+
const type = TYPES[x.split('_prototype_')[0].slice(2).toLowerCase()];
|
3583
|
+
if (type == null) continue;
|
3584
|
+
|
3585
|
+
bc[type] = generateCall(scope, {
|
3586
|
+
callee: {
|
3587
|
+
type: 'Identifier',
|
3588
|
+
name: x
|
3589
|
+
},
|
3590
|
+
arguments: [ decl.object ],
|
3591
|
+
_protoInternalCall: true
|
3592
|
+
});
|
3593
|
+
}
|
3594
|
+
}
|
3595
|
+
|
3596
|
+
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
3597
|
+
...bc,
|
3598
|
+
default: withType(scope, number(0), TYPES.undefined)
|
3599
|
+
}, valtypeBinary);
|
3600
|
+
}
|
3601
|
+
|
3282
3602
|
const object = generate(scope, decl.object);
|
3283
3603
|
const property = generate(scope, decl.property);
|
3284
3604
|
|
@@ -3293,24 +3613,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
3293
3613
|
|
3294
3614
|
return typeSwitch(scope, getNodeType(scope, decl.object), {
|
3295
3615
|
[TYPES.array]: [
|
3296
|
-
|
3297
|
-
...property,
|
3298
|
-
|
3299
|
-
// convert to i32 and turn into byte offset by * valtypeSize (4 for i32, 8 for i64/f64)
|
3300
|
-
Opcodes.i32_to_u,
|
3301
|
-
...number(ValtypeSize[valtype], Valtype.i32),
|
3302
|
-
[ Opcodes.i32_mul ],
|
3303
|
-
|
3304
|
-
...(aotPointer ? [] : [
|
3305
|
-
...object,
|
3306
|
-
Opcodes.i32_to_u,
|
3307
|
-
[ Opcodes.i32_add ]
|
3308
|
-
]),
|
3309
|
-
|
3310
|
-
// read from memory
|
3311
|
-
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ],
|
3312
|
-
|
3313
|
-
...number(TYPES.number, Valtype.i32),
|
3616
|
+
...loadArray(scope, object, property, aotPointer),
|
3314
3617
|
...setLastType(scope)
|
3315
3618
|
],
|
3316
3619
|
|
@@ -3341,9 +3644,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
3341
3644
|
|
3342
3645
|
// return new string (page)
|
3343
3646
|
...number(newPointer),
|
3344
|
-
|
3345
|
-
...number(TYPES.string, Valtype.i32),
|
3346
|
-
...setLastType(scope)
|
3647
|
+
...setLastType(scope, TYPES.string)
|
3347
3648
|
],
|
3348
3649
|
[TYPES.bytestring]: [
|
3349
3650
|
// setup new/out array
|
@@ -3369,9 +3670,7 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
3369
3670
|
|
3370
3671
|
// return new string (page)
|
3371
3672
|
...number(newPointer),
|
3372
|
-
|
3373
|
-
...number(TYPES.bytestring, Valtype.i32),
|
3374
|
-
...setLastType(scope)
|
3673
|
+
...setLastType(scope, TYPES.bytestring)
|
3375
3674
|
],
|
3376
3675
|
|
3377
3676
|
default: internalThrow(scope, 'TypeError', 'Member expression is not supported for non-string non-array yet', true)
|
@@ -3396,7 +3695,7 @@ const objectHack = node => {
|
|
3396
3695
|
if (!objectName) objectName = objectHack(node.object)?.name?.slice?.(2);
|
3397
3696
|
|
3398
3697
|
// if .name or .length, give up (hack within a hack!)
|
3399
|
-
if (['name', 'length'].includes(node.property.name)) {
|
3698
|
+
if (['name', 'length', 'size', 'description'].includes(node.property.name)) {
|
3400
3699
|
node.object = objectHack(node.object);
|
3401
3700
|
return;
|
3402
3701
|
}
|
@@ -3433,33 +3732,39 @@ const generateFunc = (scope, decl) => {
|
|
3433
3732
|
const name = decl.id ? decl.id.name : `anonymous_${randId()}`;
|
3434
3733
|
const params = decl.params ?? [];
|
3435
3734
|
|
3436
|
-
// const innerScope = { ...scope };
|
3437
3735
|
// TODO: share scope/locals between !!!
|
3438
|
-
const
|
3736
|
+
const func = {
|
3439
3737
|
locals: {},
|
3440
3738
|
localInd: 0,
|
3441
3739
|
// value, type
|
3442
3740
|
returns: [ valtypeBinary, Valtype.i32 ],
|
3443
3741
|
throws: false,
|
3444
|
-
name
|
3742
|
+
name,
|
3743
|
+
index: currentFuncIndex++
|
3445
3744
|
};
|
3446
3745
|
|
3447
3746
|
if (typedInput && decl.returnType) {
|
3448
3747
|
const { type } = extractTypeAnnotation(decl.returnType);
|
3449
|
-
if (type != null && !Prefs.indirectCalls) {
|
3450
|
-
|
3451
|
-
|
3748
|
+
// if (type != null && !Prefs.indirectCalls) {
|
3749
|
+
if (type != null) {
|
3750
|
+
func.returnType = type;
|
3751
|
+
func.returns = [ valtypeBinary ];
|
3452
3752
|
}
|
3453
3753
|
}
|
3454
3754
|
|
3455
3755
|
for (let i = 0; i < params.length; i++) {
|
3456
|
-
|
3756
|
+
const name = params[i].name;
|
3757
|
+
// if (name == null) return todo('non-identifier args are not supported');
|
3758
|
+
|
3759
|
+
allocVar(func, name, false);
|
3457
3760
|
|
3458
3761
|
if (typedInput && params[i].typeAnnotation) {
|
3459
|
-
addVarMetadata(
|
3762
|
+
addVarMetadata(func, name, false, extractTypeAnnotation(params[i]));
|
3460
3763
|
}
|
3461
3764
|
}
|
3462
3765
|
|
3766
|
+
func.params = Object.values(func.locals).map(x => x.type);
|
3767
|
+
|
3463
3768
|
let body = objectHack(decl.body);
|
3464
3769
|
if (decl.type === 'ArrowFunctionExpression' && decl.expression) {
|
3465
3770
|
// hack: () => 0 -> () => return 0
|
@@ -3469,37 +3774,23 @@ const generateFunc = (scope, decl) => {
|
|
3469
3774
|
};
|
3470
3775
|
}
|
3471
3776
|
|
3472
|
-
const wasm = generate(innerScope, body);
|
3473
|
-
const func = {
|
3474
|
-
name,
|
3475
|
-
params: Object.values(innerScope.locals).slice(0, params.length * 2).map(x => x.type),
|
3476
|
-
index: currentFuncIndex++,
|
3477
|
-
...innerScope
|
3478
|
-
};
|
3479
3777
|
funcIndex[name] = func.index;
|
3778
|
+
funcs.push(func);
|
3480
3779
|
|
3481
|
-
|
3780
|
+
const wasm = generate(func, body);
|
3781
|
+
func.wasm = wasm;
|
3482
3782
|
|
3483
|
-
|
3484
|
-
for (const inst of wasm) {
|
3485
|
-
if (inst[0] === Opcodes.call && inst[1] === -1) {
|
3486
|
-
inst[1] = func.index;
|
3487
|
-
}
|
3488
|
-
}
|
3783
|
+
if (name === 'main') func.gotLastType = true;
|
3489
3784
|
|
3490
3785
|
// add end return if not found
|
3491
3786
|
if (name !== 'main' && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
3492
3787
|
wasm.push(
|
3493
3788
|
...number(0),
|
3494
|
-
...(
|
3789
|
+
...(func.returnType != null ? [] : number(TYPES.undefined, Valtype.i32)),
|
3495
3790
|
[ Opcodes.return ]
|
3496
3791
|
);
|
3497
3792
|
}
|
3498
3793
|
|
3499
|
-
func.wasm = wasm;
|
3500
|
-
|
3501
|
-
funcs.push(func);
|
3502
|
-
|
3503
3794
|
return func;
|
3504
3795
|
};
|
3505
3796
|
|
@@ -3603,7 +3894,7 @@ const internalConstrs = {
|
|
3603
3894
|
generate: (scope, decl) => {
|
3604
3895
|
// todo: boolean object when used as constructor
|
3605
3896
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
3606
|
-
return truthy(scope, generate(scope, arg), getNodeType(scope, arg));
|
3897
|
+
return truthy(scope, generate(scope, arg), getNodeType(scope, arg), false, false, 'full');
|
3607
3898
|
},
|
3608
3899
|
type: TYPES.boolean,
|
3609
3900
|
length: 1
|
@@ -3748,9 +4039,8 @@ export default program => {
|
|
3748
4039
|
|
3749
4040
|
if (Prefs.astLog) console.log(JSON.stringify(program.body.body, null, 2));
|
3750
4041
|
|
3751
|
-
generateFunc(scope, program);
|
4042
|
+
const main = generateFunc(scope, program);
|
3752
4043
|
|
3753
|
-
const main = funcs[funcs.length - 1];
|
3754
4044
|
main.export = true;
|
3755
4045
|
main.returns = [ valtypeBinary, Valtype.i32 ];
|
3756
4046
|
|
@@ -3777,7 +4067,7 @@ export default program => {
|
|
3777
4067
|
}
|
3778
4068
|
|
3779
4069
|
// if blank main func and other exports, remove it
|
3780
|
-
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(
|
4070
|
+
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(main.index - importedFuncs.length, 1);
|
3781
4071
|
|
3782
4072
|
return { funcs, globals, tags, exceptions, pages, data };
|
3783
4073
|
};
|