porffor 0.0.0-e6047ea → 0.0.0-e975d7a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/compiler/2c.js +349 -349
- package/compiler/codeGen.js +207 -58
- package/compiler/embedding.js +9 -5
- package/compiler/index.js +3 -3
- package/compiler/opt.js +14 -18
- package/compiler/prototype.js +168 -29
- package/compiler/sections.js +19 -4
- package/package.json +1 -1
- package/r.js +39 -1
- package/runner/index.js +4 -2
- package/runner/info.js +37 -2
package/compiler/codeGen.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from "./wasmSpec.js";
|
2
|
-
import { signedLEB128, unsignedLEB128 } from "./encoding.js";
|
2
|
+
import { ieee754_binary64, signedLEB128, unsignedLEB128 } from "./encoding.js";
|
3
3
|
import { operatorOpcode } from "./expression.js";
|
4
4
|
import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./builtins.js";
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
6
|
-
import { number, i32x4 } from "./embedding.js";
|
6
|
+
import { number, i32x4, enforceOneByte, enforceTwoBytes, enforceFourBytes, enforceEightBytes } from "./embedding.js";
|
7
7
|
import parse from "./parse.js";
|
8
8
|
import * as Rhemyn from "../rhemyn/compile.js";
|
9
9
|
|
@@ -326,6 +326,8 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
326
326
|
return idx;
|
327
327
|
};
|
328
328
|
|
329
|
+
const isIntOp = op => op[0] >= 0xb7 && op[0] <= 0xba;
|
330
|
+
|
329
331
|
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
330
332
|
const checks = {
|
331
333
|
'||': falsy,
|
@@ -338,6 +340,32 @@ const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
|
338
340
|
// generic structure for {a} OP {b}
|
339
341
|
// -->
|
340
342
|
// _ = {a}; if (OP_CHECK) {b} else _
|
343
|
+
|
344
|
+
// if we can, use int tmp and convert at the end to help prevent unneeded conversions
|
345
|
+
// (like if we are in an if condition - very common)
|
346
|
+
const leftIsInt = isIntOp(left[left.length - 1]);
|
347
|
+
const rightIsInt = isIntOp(right[right.length - 1]);
|
348
|
+
|
349
|
+
const canInt = leftIsInt && rightIsInt;
|
350
|
+
|
351
|
+
if (canInt) {
|
352
|
+
// remove int -> float conversions from left and right
|
353
|
+
left.pop();
|
354
|
+
right.pop();
|
355
|
+
|
356
|
+
return [
|
357
|
+
...left,
|
358
|
+
[ Opcodes.local_tee, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
359
|
+
...checks[op](scope, [], leftType, true),
|
360
|
+
[ Opcodes.if, Valtype.i32 ],
|
361
|
+
...right,
|
362
|
+
[ Opcodes.else ],
|
363
|
+
[ Opcodes.local_get, localTmp(scope, 'logictmpi', Valtype.i32) ],
|
364
|
+
[ Opcodes.end ],
|
365
|
+
Opcodes.i32_from
|
366
|
+
];
|
367
|
+
}
|
368
|
+
|
341
369
|
return [
|
342
370
|
...left,
|
343
371
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
@@ -361,6 +389,9 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
361
389
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
362
390
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
363
391
|
|
392
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
393
|
+
if (aotWFA) addVarMeta(name, { wellFormed: undefined });
|
394
|
+
|
364
395
|
if (assign) {
|
365
396
|
const pointer = arrays.get(name ?? '$undeclared');
|
366
397
|
|
@@ -588,12 +619,12 @@ const compareStrings = (scope, left, right) => {
|
|
588
619
|
];
|
589
620
|
};
|
590
621
|
|
591
|
-
const truthy = (scope, wasm, type) => {
|
622
|
+
const truthy = (scope, wasm, type, int = false) => {
|
592
623
|
// arrays are always truthy
|
593
624
|
if (type === TYPES._array) return [
|
594
625
|
...wasm,
|
595
626
|
[ Opcodes.drop ],
|
596
|
-
...number(1)
|
627
|
+
...number(1, int ? Valtype.i32 : valtypeBinary)
|
597
628
|
];
|
598
629
|
|
599
630
|
if (type === TYPES.string) {
|
@@ -609,8 +640,8 @@ const truthy = (scope, wasm, type) => {
|
|
609
640
|
// if length != 0
|
610
641
|
/* [ Opcodes.i32_eqz ],
|
611
642
|
[ Opcodes.i32_eqz ], */
|
612
|
-
Opcodes.i32_from_u
|
613
|
-
]
|
643
|
+
...(int ? [] : [ Opcodes.i32_from_u ])
|
644
|
+
];
|
614
645
|
}
|
615
646
|
|
616
647
|
// if != 0
|
@@ -623,12 +654,12 @@ const truthy = (scope, wasm, type) => {
|
|
623
654
|
];
|
624
655
|
};
|
625
656
|
|
626
|
-
const falsy = (scope, wasm, type) => {
|
657
|
+
const falsy = (scope, wasm, type, int = false) => {
|
627
658
|
// arrays are always truthy
|
628
659
|
if (type === TYPES._array) return [
|
629
660
|
...wasm,
|
630
661
|
[ Opcodes.drop ],
|
631
|
-
...number(0)
|
662
|
+
...number(0, int ? Valtype.i32 : valtypeBinary)
|
632
663
|
];
|
633
664
|
|
634
665
|
if (type === TYPES.string) {
|
@@ -643,7 +674,7 @@ const falsy = (scope, wasm, type) => {
|
|
643
674
|
|
644
675
|
// if length == 0
|
645
676
|
[ Opcodes.i32_eqz ],
|
646
|
-
Opcodes.i32_from_u
|
677
|
+
...(int ? [] : [ Opcodes.i32_from_u ])
|
647
678
|
]
|
648
679
|
}
|
649
680
|
|
@@ -651,31 +682,29 @@ const falsy = (scope, wasm, type) => {
|
|
651
682
|
return [
|
652
683
|
...wasm,
|
653
684
|
|
654
|
-
...Opcodes.eqz,
|
655
|
-
Opcodes.i32_from_u
|
685
|
+
...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
|
656
686
|
];
|
657
687
|
};
|
658
688
|
|
659
|
-
const nullish = (scope, wasm, type) => {
|
689
|
+
const nullish = (scope, wasm, type, int = false) => {
|
660
690
|
// undefined
|
661
691
|
if (type === TYPES.undefined) return [
|
662
692
|
...wasm,
|
663
693
|
[ Opcodes.drop ],
|
664
|
-
...number(1)
|
694
|
+
...number(1, int ? Valtype.i32 : valtypeBinary)
|
665
695
|
];
|
666
696
|
|
667
697
|
// null (if object and = "0")
|
668
698
|
if (type === TYPES.object) return [
|
669
699
|
...wasm,
|
670
|
-
...Opcodes.eqz,
|
671
|
-
Opcodes.i32_from_u
|
700
|
+
...(int ? [ [ Opcodes.i32_eqz ] ] : [ ...Opcodes.eqz, Opcodes.i32_from_u ])
|
672
701
|
];
|
673
702
|
|
674
703
|
// not
|
675
704
|
return [
|
676
705
|
...wasm,
|
677
706
|
[ Opcodes.drop ],
|
678
|
-
...number(0)
|
707
|
+
...number(0, int ? Valtype.i32 : valtypeBinary)
|
679
708
|
];
|
680
709
|
};
|
681
710
|
|
@@ -686,29 +715,44 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
686
715
|
|
687
716
|
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
688
717
|
|
689
|
-
|
690
|
-
|
718
|
+
const eqOp = ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op);
|
719
|
+
|
720
|
+
if (leftType && rightType && (
|
721
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
722
|
+
((op === '===' || op === '!==') && leftType !== rightType) ||
|
723
|
+
|
724
|
+
// if equality op and an operand is undefined, return false
|
725
|
+
(eqOp && leftType === TYPES.undefined ^ rightType === TYPES.undefined)
|
726
|
+
)) {
|
727
|
+
// undefined == null
|
728
|
+
if (((leftType === TYPES.undefined && rightType === TYPES.object) || (leftType === TYPES.object && rightType === TYPES.undefined)) && (op === '==' || op === '!=')) return [
|
729
|
+
...(leftType === TYPES.object ? left : right),
|
730
|
+
...Opcodes.eqz,
|
731
|
+
...(op === '!=' ? [ [ Opcodes.i32_eqz ] ] : [])
|
732
|
+
];
|
733
|
+
|
691
734
|
return [
|
692
735
|
...left,
|
693
|
-
...right,
|
694
|
-
|
695
|
-
// drop values
|
696
736
|
[ Opcodes.drop ],
|
737
|
+
|
738
|
+
...right,
|
697
739
|
[ Opcodes.drop ],
|
698
740
|
|
699
|
-
// return
|
700
|
-
...number(op === '===' ?
|
741
|
+
// return true (!=/!==) or false (else)
|
742
|
+
...number(op === '!=' || op === '!==' ? 1 : 0, Valtype.i32)
|
701
743
|
];
|
702
744
|
}
|
703
745
|
|
746
|
+
// todo: niche null hell with 0
|
747
|
+
|
704
748
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
705
749
|
if (op === '+') {
|
706
750
|
// string concat (a + b)
|
707
751
|
return concatStrings(scope, left, right, _global, _name, assign);
|
708
752
|
}
|
709
753
|
|
710
|
-
//
|
711
|
-
if (!
|
754
|
+
// not an equality op, NaN
|
755
|
+
if (!eqOp) return number(NaN);
|
712
756
|
|
713
757
|
// else leave bool ops
|
714
758
|
// todo: convert string to number if string and number/bool
|
@@ -974,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
974
1018
|
case 'bigint': return number(TYPES.bigint);
|
975
1019
|
}
|
976
1020
|
|
1021
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
1022
|
+
let wellFormed = aotWFA ? true : undefined;
|
1023
|
+
|
977
1024
|
const str = decl.value;
|
978
1025
|
const rawElements = new Array(str.length);
|
1026
|
+
let j = 0;
|
979
1027
|
for (let i = 0; i < str.length; i++) {
|
980
1028
|
rawElements[i] = str.charCodeAt(i);
|
1029
|
+
|
1030
|
+
if (wellFormed) {
|
1031
|
+
// check if surrogate
|
1032
|
+
if ((str.charCodeAt(j) & 0xF800) === 0xD800) {
|
1033
|
+
// unpaired trailing surrogate
|
1034
|
+
if (str.charCodeAt(j) >= 0xDC00) {
|
1035
|
+
wellFormed = false;
|
1036
|
+
}
|
1037
|
+
|
1038
|
+
// unpaired leading surrogate
|
1039
|
+
// if (++j >= str.length || (str.charCodeAt(j) & 0xFC00) != 0xDC00) {
|
1040
|
+
if ((str.charCodeAt(++j) & 0xFC00) != 0xDC00) {
|
1041
|
+
wellFormed = false;
|
1042
|
+
}
|
1043
|
+
}
|
1044
|
+
|
1045
|
+
j++;
|
1046
|
+
}
|
981
1047
|
}
|
982
1048
|
|
1049
|
+
// console.log(wellFormed, str);
|
1050
|
+
|
1051
|
+
if (aotWFA) addVarMeta(name, { wellFormed });
|
1052
|
+
|
983
1053
|
return makeArray(scope, {
|
984
1054
|
rawElements
|
985
1055
|
}, global, name, false, 'i16')[0];
|
@@ -1198,28 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1198
1268
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
|
1199
1269
|
|
1200
1270
|
let protoLocal = protoFunc.local ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp`, protoFunc.local) : -1;
|
1271
|
+
let protoLocal2 = protoFunc.local2 ? localTmp(scope, `__${TYPE_NAMES[baseType]}_${protoName}_tmp2`, protoFunc.local2) : -1;
|
1201
1272
|
|
1202
1273
|
// use local for cached i32 length as commonly used
|
1203
1274
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1204
1275
|
|
1276
|
+
let lengthI32CacheUsed = false;
|
1277
|
+
|
1278
|
+
const protoOut = protoFunc(pointer, {
|
1279
|
+
getCachedI32: () => {
|
1280
|
+
lengthI32CacheUsed = true;
|
1281
|
+
return [ [ Opcodes.local_get, lengthLocal ] ]
|
1282
|
+
},
|
1283
|
+
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1284
|
+
get: () => arrayUtil.getLength(pointer),
|
1285
|
+
getI32: () => arrayUtil.getLengthI32(pointer),
|
1286
|
+
set: value => arrayUtil.setLength(pointer, value),
|
1287
|
+
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1288
|
+
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, protoLocal2, (length, itemType) => {
|
1289
|
+
return makeArray(scope, {
|
1290
|
+
rawElements: new Array(length)
|
1291
|
+
}, _global, _name, true, itemType);
|
1292
|
+
}, varMetadata.get(baseName));
|
1293
|
+
|
1205
1294
|
return [
|
1206
1295
|
...out,
|
1207
1296
|
|
1208
|
-
...
|
1209
|
-
|
1297
|
+
...(!lengthI32CacheUsed ? [] : [
|
1298
|
+
...arrayUtil.getLengthI32(pointer),
|
1299
|
+
[ Opcodes.local_set, lengthLocal ],
|
1300
|
+
]),
|
1210
1301
|
|
1211
1302
|
[ Opcodes.block, valtypeBinary ],
|
1212
|
-
...
|
1213
|
-
cachedI32: [ [ Opcodes.local_get, lengthLocal ] ],
|
1214
|
-
get: arrayUtil.getLength(pointer),
|
1215
|
-
getI32: arrayUtil.getLengthI32(pointer),
|
1216
|
-
set: value => arrayUtil.setLength(pointer, value),
|
1217
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1218
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1219
|
-
return makeArray(scope, {
|
1220
|
-
rawElements: new Array(length)
|
1221
|
-
}, _global, _name, true, itemType);
|
1222
|
-
}),
|
1303
|
+
...protoOut,
|
1223
1304
|
[ Opcodes.end ]
|
1224
1305
|
];
|
1225
1306
|
}
|
@@ -1279,7 +1360,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1279
1360
|
if (func && func.throws) scope.throws = true;
|
1280
1361
|
|
1281
1362
|
for (const arg of args) {
|
1282
|
-
out.
|
1363
|
+
out = out.concat(generate(scope, arg));
|
1283
1364
|
}
|
1284
1365
|
|
1285
1366
|
out.push([ Opcodes.call, idx ]);
|
@@ -1290,7 +1371,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1290
1371
|
const generateNew = (scope, decl, _global, _name) => {
|
1291
1372
|
// hack: basically treat this as a normal call for builtins for now
|
1292
1373
|
const name = mapName(decl.callee.name);
|
1293
|
-
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1374
|
+
if (internalConstrs[name] && !internalConstrs[name].notConstr) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1294
1375
|
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1295
1376
|
|
1296
1377
|
return generateCall(scope, decl, _global, _name);
|
@@ -1308,12 +1389,12 @@ const unhackName = name => {
|
|
1308
1389
|
};
|
1309
1390
|
|
1310
1391
|
const generateVar = (scope, decl) => {
|
1311
|
-
|
1392
|
+
let out = [];
|
1312
1393
|
|
1313
1394
|
const topLevel = scope.name === 'main';
|
1314
1395
|
|
1315
1396
|
// global variable if in top scope (main) and var ..., or if wanted
|
1316
|
-
const global = decl.kind === 'var';
|
1397
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1317
1398
|
const target = global ? globals : scope.locals;
|
1318
1399
|
|
1319
1400
|
for (const x of decl.declarations) {
|
@@ -1350,7 +1431,7 @@ const generateVar = (scope, decl) => {
|
|
1350
1431
|
|
1351
1432
|
// x.init ??= DEFAULT_VALUE;
|
1352
1433
|
if (x.init) {
|
1353
|
-
out.
|
1434
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1354
1435
|
|
1355
1436
|
// if our value is the result of a function, infer the type from that func's return value
|
1356
1437
|
if (out[out.length - 1][0] === Opcodes.call) {
|
@@ -1419,20 +1500,22 @@ const generateAssign = (scope, decl) => {
|
|
1419
1500
|
];
|
1420
1501
|
}
|
1421
1502
|
|
1503
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1504
|
+
|
1505
|
+
// arr[i] | str[i]
|
1422
1506
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1423
|
-
// arr[i] | str[i]
|
1424
1507
|
const name = decl.left.object.name;
|
1425
1508
|
const pointer = arrays.get(name);
|
1426
1509
|
|
1427
1510
|
const aotPointer = pointer != null;
|
1428
1511
|
|
1429
|
-
const newValueTmp = localTmp(scope, '
|
1512
|
+
const newValueTmp = localTmp(scope, '__member_setter_val_tmp');
|
1513
|
+
const pointerTmp = op === '=' ? -1 : localTmp(scope, '__member_setter_ptr_tmp', Valtype.i32);
|
1430
1514
|
|
1431
1515
|
const parentType = getNodeType(scope, decl.left.object);
|
1432
1516
|
|
1433
|
-
const op = decl.operator.slice(0, -1);
|
1434
1517
|
return [
|
1435
|
-
...(aotPointer ?
|
1518
|
+
...(aotPointer ? [] : [
|
1436
1519
|
...generate(scope, decl.left.object),
|
1437
1520
|
Opcodes.i32_to_u
|
1438
1521
|
]),
|
@@ -1444,9 +1527,13 @@ const generateAssign = (scope, decl) => {
|
|
1444
1527
|
Opcodes.i32_to_u,
|
1445
1528
|
...number(ValtypeSize[valtype], Valtype.i32),
|
1446
1529
|
[ Opcodes.i32_mul ],
|
1447
|
-
[ Opcodes.i32_add ],
|
1530
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1531
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1448
1532
|
|
1449
|
-
...(op === '' ? generate(scope, decl.right, false, name) : performOp(scope, op,
|
1533
|
+
...(op === '=' ? generate(scope, decl.right, false, name) : performOp(scope, op, [
|
1534
|
+
[ Opcodes.local_get, pointerTmp ],
|
1535
|
+
[ Opcodes.load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128((aotPointer ? pointer : 0) + ValtypeSize.i32) ]
|
1536
|
+
], generate(scope, decl.right), parentType === TYPES._array ? TYPES.number : TYPES.string, getNodeType(scope, decl.right), false, name, true)),
|
1450
1537
|
[ Opcodes.local_tee, newValueTmp ],
|
1451
1538
|
|
1452
1539
|
...(parentType === TYPES._array ? [
|
@@ -1463,10 +1550,10 @@ const generateAssign = (scope, decl) => {
|
|
1463
1550
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1464
1551
|
|
1465
1552
|
if (local === undefined) {
|
1466
|
-
// todo: this should be a
|
1553
|
+
// todo: this should be a sloppy mode only thing
|
1467
1554
|
|
1468
1555
|
// only allow = for this
|
1469
|
-
if (
|
1556
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1470
1557
|
|
1471
1558
|
if (builtinVars[name]) {
|
1472
1559
|
// just return rhs (eg `NaN = 2`)
|
@@ -1475,14 +1562,14 @@ const generateAssign = (scope, decl) => {
|
|
1475
1562
|
|
1476
1563
|
// set global and return (eg a = 2)
|
1477
1564
|
return [
|
1478
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1565
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1479
1566
|
[ Opcodes.global_get, globals[name].idx ]
|
1480
1567
|
];
|
1481
1568
|
}
|
1482
1569
|
|
1483
1570
|
typeStates[name] = getNodeType(scope, decl.right);
|
1484
1571
|
|
1485
|
-
if (
|
1572
|
+
if (op === '=') {
|
1486
1573
|
// typeStates[name] = getNodeType(scope, decl.right);
|
1487
1574
|
|
1488
1575
|
return [
|
@@ -1492,7 +1579,6 @@ const generateAssign = (scope, decl) => {
|
|
1492
1579
|
];
|
1493
1580
|
}
|
1494
1581
|
|
1495
|
-
const op = decl.operator.slice(0, -1);
|
1496
1582
|
if (op === '||' || op === '&&' || op === '??') {
|
1497
1583
|
// todo: is this needed?
|
1498
1584
|
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
@@ -1948,10 +2034,28 @@ const StoreOps = {
|
|
1948
2034
|
i16: Opcodes.i32_store16
|
1949
2035
|
};
|
1950
2036
|
|
2037
|
+
let data = [];
|
2038
|
+
|
2039
|
+
const compileBytes = (val, itemType, signed = true) => {
|
2040
|
+
switch (itemType) {
|
2041
|
+
case 'i8': return [ val % 256 ];
|
2042
|
+
case 'i16': return [ val % 256, Math.floor(val / 256) ];
|
2043
|
+
|
2044
|
+
case 'i32':
|
2045
|
+
case 'i64':
|
2046
|
+
return enforceFourBytes(signedLEB128(val));
|
2047
|
+
|
2048
|
+
case 'f64': return ieee754_binary64(val);
|
2049
|
+
}
|
2050
|
+
};
|
2051
|
+
|
1951
2052
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1952
2053
|
const out = [];
|
1953
2054
|
|
2055
|
+
let firstAssign = false;
|
1954
2056
|
if (!arrays.has(name) || name === '$undeclared') {
|
2057
|
+
firstAssign = true;
|
2058
|
+
|
1955
2059
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1956
2060
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1957
2061
|
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
@@ -1962,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1962
2066
|
const useRawElements = !!decl.rawElements;
|
1963
2067
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1964
2068
|
|
2069
|
+
const valtype = itemTypeToValtype[itemType];
|
1965
2070
|
const length = elements.length;
|
1966
2071
|
|
2072
|
+
if (firstAssign && useRawElements) {
|
2073
|
+
let bytes = compileBytes(length, 'i32');
|
2074
|
+
|
2075
|
+
if (!initEmpty) for (let i = 0; i < length; i++) {
|
2076
|
+
if (elements[i] == null) continue;
|
2077
|
+
|
2078
|
+
bytes.push(...compileBytes(elements[i], itemType));
|
2079
|
+
}
|
2080
|
+
|
2081
|
+
data.push({
|
2082
|
+
offset: pointer,
|
2083
|
+
bytes
|
2084
|
+
});
|
2085
|
+
|
2086
|
+
// local value as pointer
|
2087
|
+
out.push(...number(pointer));
|
2088
|
+
|
2089
|
+
return [ out, pointer ];
|
2090
|
+
}
|
2091
|
+
|
1967
2092
|
// store length as 0th array
|
1968
2093
|
out.push(
|
1969
2094
|
...number(0, Valtype.i32),
|
@@ -1972,7 +2097,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1972
2097
|
);
|
1973
2098
|
|
1974
2099
|
const storeOp = StoreOps[itemType];
|
1975
|
-
const valtype = itemTypeToValtype[itemType];
|
1976
2100
|
|
1977
2101
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1978
2102
|
if (elements[i] == null) continue;
|
@@ -1995,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
|
|
1995
2119
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1996
2120
|
};
|
1997
2121
|
|
2122
|
+
let varMetadata = new Map();
|
2123
|
+
const addVarMeta = (_name, obj) => {
|
2124
|
+
const name = _name ?? '$undeclared';
|
2125
|
+
if (!varMetadata.has(name)) varMetadata.set(name, {});
|
2126
|
+
|
2127
|
+
const meta = varMetadata.get(name);
|
2128
|
+
for (const k in obj) {
|
2129
|
+
meta[k] = obj[k];
|
2130
|
+
}
|
2131
|
+
};
|
2132
|
+
|
1998
2133
|
export const generateMember = (scope, decl, _global, _name) => {
|
1999
2134
|
const type = getNodeType(scope, decl.object);
|
2000
2135
|
|
@@ -2296,10 +2431,10 @@ const generateFunc = (scope, decl) => {
|
|
2296
2431
|
};
|
2297
2432
|
|
2298
2433
|
const generateCode = (scope, decl) => {
|
2299
|
-
|
2434
|
+
let out = [];
|
2300
2435
|
|
2301
2436
|
for (const x of decl.body) {
|
2302
|
-
out.
|
2437
|
+
out = out.concat(generate(scope, x));
|
2303
2438
|
}
|
2304
2439
|
|
2305
2440
|
return out;
|
@@ -2335,6 +2470,18 @@ const internalConstrs = {
|
|
2335
2470
|
];
|
2336
2471
|
},
|
2337
2472
|
type: TYPES._array
|
2473
|
+
},
|
2474
|
+
|
2475
|
+
__Array_of: {
|
2476
|
+
// this is not a constructor but best fits internal structure here
|
2477
|
+
generate: (scope, decl, global, name) => {
|
2478
|
+
// Array.of(i0, i1, ...)
|
2479
|
+
return generateArray(scope, {
|
2480
|
+
elements: decl.arguments
|
2481
|
+
}, global, name);
|
2482
|
+
},
|
2483
|
+
type: TYPES._array,
|
2484
|
+
notConstr: true
|
2338
2485
|
}
|
2339
2486
|
};
|
2340
2487
|
|
@@ -2348,7 +2495,9 @@ export default program => {
|
|
2348
2495
|
depth = [];
|
2349
2496
|
typeStates = {};
|
2350
2497
|
arrays = new Map();
|
2498
|
+
varMetadata = new Map();
|
2351
2499
|
pages = new Map();
|
2500
|
+
data = [];
|
2352
2501
|
currentFuncIndex = importedFuncs.length;
|
2353
2502
|
|
2354
2503
|
globalThis.valtype = 'f64';
|
@@ -2425,5 +2574,5 @@ export default program => {
|
|
2425
2574
|
// if blank main func and other exports, remove it
|
2426
2575
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2427
2576
|
|
2428
|
-
return { funcs, globals, tags, exceptions, pages };
|
2577
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2429
2578
|
};
|
package/compiler/embedding.js
CHANGED
@@ -9,11 +9,15 @@ export const number = (n, valtype = valtypeBinary) => {
|
|
9
9
|
}
|
10
10
|
};
|
11
11
|
|
12
|
-
const
|
12
|
+
export const enforceOneByte = arr => [ arr[0] ?? 0 ];
|
13
|
+
export const enforceTwoBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0 ];
|
14
|
+
export const enforceFourBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0 ];
|
15
|
+
export const enforceEightBytes = arr => [ arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 0, arr[4] ?? 0, arr[5] ?? 0, arr[6] ?? 0, arr[7] ?? 0 ];
|
16
|
+
|
13
17
|
export const i32x4 = (a, b, c, d) => [ [
|
14
18
|
...Opcodes.v128_const,
|
15
|
-
...
|
16
|
-
...
|
17
|
-
...
|
18
|
-
...
|
19
|
+
...enforceFourBytes(signedLEB128(a)),
|
20
|
+
...enforceFourBytes(signedLEB128(b)),
|
21
|
+
...enforceFourBytes(signedLEB128(c)),
|
22
|
+
...enforceFourBytes(signedLEB128(d))
|
19
23
|
] ];
|
package/compiler/index.js
CHANGED
@@ -59,7 +59,7 @@ export default (code, flags) => {
|
|
59
59
|
if (flags.includes('info')) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
|
60
60
|
|
61
61
|
const t1 = performance.now();
|
62
|
-
const { funcs, globals, tags, exceptions, pages } = codeGen(program);
|
62
|
+
const { funcs, globals, tags, exceptions, pages, data } = codeGen(program);
|
63
63
|
if (flags.includes('info')) console.log(`2. generated code in ${(performance.now() - t1).toFixed(2)}ms`);
|
64
64
|
|
65
65
|
if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
|
@@ -71,14 +71,14 @@ export default (code, flags) => {
|
|
71
71
|
if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
|
72
72
|
|
73
73
|
const t3 = performance.now();
|
74
|
-
const sections = produceSections(funcs, globals, tags, pages, flags);
|
74
|
+
const sections = produceSections(funcs, globals, tags, pages, data, flags);
|
75
75
|
if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
|
76
76
|
|
77
77
|
if (allocLog) {
|
78
78
|
const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
|
79
79
|
const bytes = wasmPages * 65536;
|
80
80
|
log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
|
81
|
-
|
81
|
+
console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
|
82
82
|
}
|
83
83
|
|
84
84
|
const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
|
package/compiler/opt.js
CHANGED
@@ -317,28 +317,24 @@ export default (funcs, globals) => {
|
|
317
317
|
continue;
|
318
318
|
}
|
319
319
|
|
320
|
-
|
321
|
-
const
|
320
|
+
// remove unneeded before get with update exprs (n++, etc) when value is unused
|
321
|
+
if (i < wasm.length - 4 && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get && wasm[i + 1][0] === Opcodes.const && [Opcodes.add, Opcodes.sub].includes(wasm[i + 2][0]) && wasm[i + 3][0] === Opcodes.local_set && wasm[i + 3][1] === inst[1] && (wasm[i + 4][0] === Opcodes.drop || wasm[i + 4][0] === Opcodes.br)) {
|
322
|
+
// local.get 1
|
323
|
+
// local.get 1
|
324
|
+
// -->
|
325
|
+
// local.get 1
|
322
326
|
|
323
|
-
|
324
|
-
|
325
|
-
if (lastLastInst[0] === Opcodes.end && lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_get && inst[0] === Opcodes.local_get) {
|
326
|
-
// local.get 1
|
327
|
-
// local.get 1
|
328
|
-
// -->
|
329
|
-
// local.get 1
|
330
|
-
|
331
|
-
// remove drop at the end as well
|
332
|
-
if (wasm[i + 4][0] === Opcodes.drop) {
|
333
|
-
wasm.splice(i + 4, 1);
|
334
|
-
}
|
327
|
+
// remove drop at the end as well
|
328
|
+
if (wasm[i + 4][0] === Opcodes.drop) wasm.splice(i + 4, 1);
|
335
329
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
}
|
330
|
+
wasm.splice(i, 1); // remove this inst (second get)
|
331
|
+
i--;
|
332
|
+
continue;
|
340
333
|
}
|
341
334
|
|
335
|
+
if (i < 2) continue;
|
336
|
+
const lastLastInst = wasm[i - 2];
|
337
|
+
|
342
338
|
if (lastLastInst[1] === inst[1] && inst[0] === Opcodes.local_get && lastInst[0] === Opcodes.local_tee && lastLastInst[0] === Opcodes.local_set) {
|
343
339
|
// local.set x
|
344
340
|
// local.tee y
|