porffor 0.0.0-d650361 → 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 +4 -3
- package/compiler/2c.js +349 -349
- package/compiler/codeGen.js +180 -47
- package/compiler/embedding.js +9 -5
- package/compiler/index.js +3 -3
- package/compiler/opt.js +14 -18
- package/compiler/prototype.js +83 -4
- package/compiler/sections.js +19 -4
- package/package.json +1 -1
- 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
|
|
@@ -989,12 +1018,38 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
989
1018
|
case 'bigint': return number(TYPES.bigint);
|
990
1019
|
}
|
991
1020
|
|
1021
|
+
const aotWFA = process.argv.includes('-aot-well-formed-string-approximation');
|
1022
|
+
let wellFormed = aotWFA ? true : undefined;
|
1023
|
+
|
992
1024
|
const str = decl.value;
|
993
1025
|
const rawElements = new Array(str.length);
|
1026
|
+
let j = 0;
|
994
1027
|
for (let i = 0; i < str.length; i++) {
|
995
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
|
+
}
|
996
1047
|
}
|
997
1048
|
|
1049
|
+
// console.log(wellFormed, str);
|
1050
|
+
|
1051
|
+
if (aotWFA) addVarMeta(name, { wellFormed });
|
1052
|
+
|
998
1053
|
return makeArray(scope, {
|
999
1054
|
rawElements
|
1000
1055
|
}, global, name, false, 'i16')[0];
|
@@ -1213,29 +1268,39 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1213
1268
|
if (protoFunc.noArgRetLength && decl.arguments.length === 0) return arrayUtil.getLength(pointer)
|
1214
1269
|
|
1215
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;
|
1216
1272
|
|
1217
1273
|
// use local for cached i32 length as commonly used
|
1218
1274
|
let lengthLocal = localTmp(scope, '__proto_length_cache', Valtype.i32);
|
1219
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
|
+
|
1220
1294
|
return [
|
1221
1295
|
...out,
|
1222
1296
|
|
1223
|
-
...
|
1224
|
-
|
1297
|
+
...(!lengthI32CacheUsed ? [] : [
|
1298
|
+
...arrayUtil.getLengthI32(pointer),
|
1299
|
+
[ Opcodes.local_set, lengthLocal ],
|
1300
|
+
]),
|
1225
1301
|
|
1226
1302
|
[ Opcodes.block, valtypeBinary ],
|
1227
|
-
...
|
1228
|
-
getCachedI32: () => [ [ Opcodes.local_get, lengthLocal ] ],
|
1229
|
-
setCachedI32: () => [ [ Opcodes.local_set, lengthLocal ] ],
|
1230
|
-
get: () => arrayUtil.getLength(pointer),
|
1231
|
-
getI32: () => arrayUtil.getLengthI32(pointer),
|
1232
|
-
set: value => arrayUtil.setLength(pointer, value),
|
1233
|
-
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
1234
|
-
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
1235
|
-
return makeArray(scope, {
|
1236
|
-
rawElements: new Array(length)
|
1237
|
-
}, _global, _name, true, itemType);
|
1238
|
-
}),
|
1303
|
+
...protoOut,
|
1239
1304
|
[ Opcodes.end ]
|
1240
1305
|
];
|
1241
1306
|
}
|
@@ -1306,7 +1371,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1306
1371
|
const generateNew = (scope, decl, _global, _name) => {
|
1307
1372
|
// hack: basically treat this as a normal call for builtins for now
|
1308
1373
|
const name = mapName(decl.callee.name);
|
1309
|
-
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);
|
1310
1375
|
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1311
1376
|
|
1312
1377
|
return generateCall(scope, decl, _global, _name);
|
@@ -1324,12 +1389,12 @@ const unhackName = name => {
|
|
1324
1389
|
};
|
1325
1390
|
|
1326
1391
|
const generateVar = (scope, decl) => {
|
1327
|
-
|
1392
|
+
let out = [];
|
1328
1393
|
|
1329
1394
|
const topLevel = scope.name === 'main';
|
1330
1395
|
|
1331
1396
|
// global variable if in top scope (main) and var ..., or if wanted
|
1332
|
-
const global = decl.kind === 'var';
|
1397
|
+
const global = topLevel || decl._bare; // decl.kind === 'var';
|
1333
1398
|
const target = global ? globals : scope.locals;
|
1334
1399
|
|
1335
1400
|
for (const x of decl.declarations) {
|
@@ -1366,7 +1431,7 @@ const generateVar = (scope, decl) => {
|
|
1366
1431
|
|
1367
1432
|
// x.init ??= DEFAULT_VALUE;
|
1368
1433
|
if (x.init) {
|
1369
|
-
out.
|
1434
|
+
out = out.concat(generate(scope, x.init, global, name));
|
1370
1435
|
|
1371
1436
|
// if our value is the result of a function, infer the type from that func's return value
|
1372
1437
|
if (out[out.length - 1][0] === Opcodes.call) {
|
@@ -1435,20 +1500,22 @@ const generateAssign = (scope, decl) => {
|
|
1435
1500
|
];
|
1436
1501
|
}
|
1437
1502
|
|
1503
|
+
const op = decl.operator.slice(0, -1) || '=';
|
1504
|
+
|
1505
|
+
// arr[i] | str[i]
|
1438
1506
|
if (decl.left.type === 'MemberExpression' && decl.left.computed) {
|
1439
|
-
// arr[i] | str[i]
|
1440
1507
|
const name = decl.left.object.name;
|
1441
1508
|
const pointer = arrays.get(name);
|
1442
1509
|
|
1443
1510
|
const aotPointer = pointer != null;
|
1444
1511
|
|
1445
|
-
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);
|
1446
1514
|
|
1447
1515
|
const parentType = getNodeType(scope, decl.left.object);
|
1448
1516
|
|
1449
|
-
const op = decl.operator.slice(0, -1);
|
1450
1517
|
return [
|
1451
|
-
...(aotPointer ?
|
1518
|
+
...(aotPointer ? [] : [
|
1452
1519
|
...generate(scope, decl.left.object),
|
1453
1520
|
Opcodes.i32_to_u
|
1454
1521
|
]),
|
@@ -1460,9 +1527,13 @@ const generateAssign = (scope, decl) => {
|
|
1460
1527
|
Opcodes.i32_to_u,
|
1461
1528
|
...number(ValtypeSize[valtype], Valtype.i32),
|
1462
1529
|
[ Opcodes.i32_mul ],
|
1463
|
-
[ Opcodes.i32_add ],
|
1530
|
+
...(aotPointer ? [] : [ [ Opcodes.i32_add ] ]),
|
1531
|
+
...(op === '=' ? [] : [ [ Opcodes.local_tee, pointerTmp ] ]),
|
1464
1532
|
|
1465
|
-
...(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)),
|
1466
1537
|
[ Opcodes.local_tee, newValueTmp ],
|
1467
1538
|
|
1468
1539
|
...(parentType === TYPES._array ? [
|
@@ -1479,10 +1550,10 @@ const generateAssign = (scope, decl) => {
|
|
1479
1550
|
const [ local, isGlobal ] = lookupName(scope, name);
|
1480
1551
|
|
1481
1552
|
if (local === undefined) {
|
1482
|
-
// todo: this should be a
|
1553
|
+
// todo: this should be a sloppy mode only thing
|
1483
1554
|
|
1484
1555
|
// only allow = for this
|
1485
|
-
if (
|
1556
|
+
if (op !== '=') return internalThrow(scope, 'ReferenceError', `${unhackName(name)} is not defined`);
|
1486
1557
|
|
1487
1558
|
if (builtinVars[name]) {
|
1488
1559
|
// just return rhs (eg `NaN = 2`)
|
@@ -1491,14 +1562,14 @@ const generateAssign = (scope, decl) => {
|
|
1491
1562
|
|
1492
1563
|
// set global and return (eg a = 2)
|
1493
1564
|
return [
|
1494
|
-
...generateVar(scope, { kind: 'var', declarations: [ { id: { name }, init: decl.right } ] }),
|
1565
|
+
...generateVar(scope, { kind: 'var', _bare: true, declarations: [ { id: { name }, init: decl.right } ] }),
|
1495
1566
|
[ Opcodes.global_get, globals[name].idx ]
|
1496
1567
|
];
|
1497
1568
|
}
|
1498
1569
|
|
1499
1570
|
typeStates[name] = getNodeType(scope, decl.right);
|
1500
1571
|
|
1501
|
-
if (
|
1572
|
+
if (op === '=') {
|
1502
1573
|
// typeStates[name] = getNodeType(scope, decl.right);
|
1503
1574
|
|
1504
1575
|
return [
|
@@ -1508,7 +1579,6 @@ const generateAssign = (scope, decl) => {
|
|
1508
1579
|
];
|
1509
1580
|
}
|
1510
1581
|
|
1511
|
-
const op = decl.operator.slice(0, -1);
|
1512
1582
|
if (op === '||' || op === '&&' || op === '??') {
|
1513
1583
|
// todo: is this needed?
|
1514
1584
|
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
@@ -1964,10 +2034,28 @@ const StoreOps = {
|
|
1964
2034
|
i16: Opcodes.i32_store16
|
1965
2035
|
};
|
1966
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
|
+
|
1967
2052
|
const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false, itemType = valtype) => {
|
1968
2053
|
const out = [];
|
1969
2054
|
|
2055
|
+
let firstAssign = false;
|
1970
2056
|
if (!arrays.has(name) || name === '$undeclared') {
|
2057
|
+
firstAssign = true;
|
2058
|
+
|
1971
2059
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1972
2060
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1973
2061
|
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
@@ -1978,8 +2066,29 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1978
2066
|
const useRawElements = !!decl.rawElements;
|
1979
2067
|
const elements = useRawElements ? decl.rawElements : decl.elements;
|
1980
2068
|
|
2069
|
+
const valtype = itemTypeToValtype[itemType];
|
1981
2070
|
const length = elements.length;
|
1982
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
|
+
|
1983
2092
|
// store length as 0th array
|
1984
2093
|
out.push(
|
1985
2094
|
...number(0, Valtype.i32),
|
@@ -1988,7 +2097,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1988
2097
|
);
|
1989
2098
|
|
1990
2099
|
const storeOp = StoreOps[itemType];
|
1991
|
-
const valtype = itemTypeToValtype[itemType];
|
1992
2100
|
|
1993
2101
|
if (!initEmpty) for (let i = 0; i < length; i++) {
|
1994
2102
|
if (elements[i] == null) continue;
|
@@ -2011,6 +2119,17 @@ const generateArray = (scope, decl, global = false, name = '$undeclared', initEm
|
|
2011
2119
|
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
2012
2120
|
};
|
2013
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
|
+
|
2014
2133
|
export const generateMember = (scope, decl, _global, _name) => {
|
2015
2134
|
const type = getNodeType(scope, decl.object);
|
2016
2135
|
|
@@ -2351,6 +2470,18 @@ const internalConstrs = {
|
|
2351
2470
|
];
|
2352
2471
|
},
|
2353
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
|
2354
2485
|
}
|
2355
2486
|
};
|
2356
2487
|
|
@@ -2364,7 +2495,9 @@ export default program => {
|
|
2364
2495
|
depth = [];
|
2365
2496
|
typeStates = {};
|
2366
2497
|
arrays = new Map();
|
2498
|
+
varMetadata = new Map();
|
2367
2499
|
pages = new Map();
|
2500
|
+
data = [];
|
2368
2501
|
currentFuncIndex = importedFuncs.length;
|
2369
2502
|
|
2370
2503
|
globalThis.valtype = 'f64';
|
@@ -2441,5 +2574,5 @@ export default program => {
|
|
2441
2574
|
// if blank main func and other exports, remove it
|
2442
2575
|
if (main.wasm.length === 0 && funcs.reduce((acc, x) => acc + (x.export ? 1 : 0), 0) > 1) funcs.splice(funcs.length - 1, 1);
|
2443
2576
|
|
2444
|
-
return { funcs, globals, tags, exceptions, pages };
|
2577
|
+
return { funcs, globals, tags, exceptions, pages, data };
|
2445
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
|
package/compiler/prototype.js
CHANGED
@@ -23,7 +23,9 @@ const TYPES = {
|
|
23
23
|
|
24
24
|
export const PrototypeFuncs = function() {
|
25
25
|
const noUnlikelyChecks = process.argv.includes('-funsafe-no-unlikely-proto-checks');
|
26
|
-
|
26
|
+
let zeroChecks = process.argv.find(x => x.startsWith('-funsafe-zero-proto-checks='));
|
27
|
+
if (zeroChecks) zeroChecks = zeroChecks.split('=')[1].split(',').reduce((acc, x) => { acc[x.toLowerCase()] = true; return acc; }, {});
|
28
|
+
else zeroChecks = {};
|
27
29
|
|
28
30
|
this[TYPES._array] = {
|
29
31
|
// lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
|
@@ -203,7 +205,7 @@ export const PrototypeFuncs = function() {
|
|
203
205
|
this[TYPES._array].fill.returnType = TYPES._array;
|
204
206
|
|
205
207
|
this[TYPES.string] = {
|
206
|
-
at: (pointer, length, wIndex, iTmp, arrayShell) => {
|
208
|
+
at: (pointer, length, wIndex, iTmp, _, arrayShell) => {
|
207
209
|
const [ newOut, newPointer ] = arrayShell(1, 'i16');
|
208
210
|
|
209
211
|
return [
|
@@ -258,7 +260,7 @@ export const PrototypeFuncs = function() {
|
|
258
260
|
},
|
259
261
|
|
260
262
|
// todo: out of bounds properly
|
261
|
-
charAt: (pointer, length, wIndex,
|
263
|
+
charAt: (pointer, length, wIndex, _1, _2, arrayShell) => {
|
262
264
|
const [ newOut, newPointer ] = arrayShell(1, 'i16');
|
263
265
|
|
264
266
|
return [
|
@@ -290,7 +292,7 @@ export const PrototypeFuncs = function() {
|
|
290
292
|
...wIndex,
|
291
293
|
Opcodes.i32_to,
|
292
294
|
|
293
|
-
...(
|
295
|
+
...(zeroChecks.charcodeat ? [] : [
|
294
296
|
[ Opcodes.local_set, iTmp ],
|
295
297
|
|
296
298
|
// index < 0
|
@@ -322,10 +324,87 @@ export const PrototypeFuncs = function() {
|
|
322
324
|
Opcodes.i32_from_u
|
323
325
|
];
|
324
326
|
},
|
327
|
+
|
328
|
+
isWellFormed: (pointer, length, wIndex, iTmp, iTmp2, arrayShell, { wellFormed } = {}) => {
|
329
|
+
// aot approx metadata
|
330
|
+
if (wellFormed != null) return number(wellFormed ? 1 : 0);
|
331
|
+
|
332
|
+
return [
|
333
|
+
// note: we cannot presume it begins as 0 in case it was used previously
|
334
|
+
...number(0, Valtype.i32),
|
335
|
+
[ Opcodes.local_set, iTmp ],
|
336
|
+
|
337
|
+
[ Opcodes.loop, Blocktype.void ],
|
338
|
+
|
339
|
+
[ Opcodes.block, Blocktype.void ],
|
340
|
+
|
341
|
+
[ Opcodes.local_get, iTmp ],
|
342
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32) ],
|
343
|
+
[ Opcodes.local_set, iTmp2 ],
|
344
|
+
|
345
|
+
// if not surrogate, continue
|
346
|
+
[ Opcodes.local_get, iTmp2 ],
|
347
|
+
...number(0xF800, Valtype.i32),
|
348
|
+
[ Opcodes.i32_and ],
|
349
|
+
...number(0xD800, Valtype.i32),
|
350
|
+
[ Opcodes.i32_ne ],
|
351
|
+
[ Opcodes.br_if, 0 ],
|
352
|
+
|
353
|
+
// if not leading surrogate, return false
|
354
|
+
[ Opcodes.local_get, iTmp2 ],
|
355
|
+
...number(0xDC00, Valtype.i32),
|
356
|
+
[ Opcodes.i32_ge_s ],
|
357
|
+
[ Opcodes.if, Blocktype.void ],
|
358
|
+
...number(0),
|
359
|
+
[ Opcodes.br, 3 ],
|
360
|
+
[ Opcodes.end ],
|
361
|
+
|
362
|
+
// if not followed by trailing surrogate, return false
|
363
|
+
[ Opcodes.local_get, iTmp ],
|
364
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(pointer + ValtypeSize.i32 + ValtypeSize.i16) ],
|
365
|
+
...number(0xFC00, Valtype.i32),
|
366
|
+
[ Opcodes.i32_and ],
|
367
|
+
...number(0xDC00, Valtype.i32),
|
368
|
+
[ Opcodes.i32_ne ],
|
369
|
+
[ Opcodes.if, Blocktype.void ],
|
370
|
+
...number(0),
|
371
|
+
[ Opcodes.br, 3 ],
|
372
|
+
[ Opcodes.end ],
|
373
|
+
|
374
|
+
// bump index again since gone through two valid chars
|
375
|
+
[ Opcodes.local_get, iTmp ],
|
376
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
377
|
+
[ Opcodes.i32_add ],
|
378
|
+
[ Opcodes.local_set, iTmp ],
|
379
|
+
|
380
|
+
[ Opcodes.end ],
|
381
|
+
|
382
|
+
// bump pointer and loop if not at the end
|
383
|
+
[ Opcodes.local_get, iTmp ],
|
384
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
385
|
+
[ Opcodes.i32_add ],
|
386
|
+
[ Opcodes.local_tee, iTmp ],
|
387
|
+
|
388
|
+
...length.getCachedI32(),
|
389
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
390
|
+
[ Opcodes.i32_mul ],
|
391
|
+
[ Opcodes.i32_ne ],
|
392
|
+
[ Opcodes.br_if, 0 ],
|
393
|
+
|
394
|
+
[ Opcodes.end ],
|
395
|
+
|
396
|
+
// return true
|
397
|
+
...number(1)
|
398
|
+
]
|
399
|
+
}
|
325
400
|
};
|
326
401
|
|
327
402
|
this[TYPES.string].at.local = Valtype.i32;
|
328
403
|
this[TYPES.string].at.returnType = TYPES.string;
|
329
404
|
this[TYPES.string].charAt.returnType = TYPES.string;
|
330
405
|
this[TYPES.string].charCodeAt.local = Valtype.i32;
|
406
|
+
|
407
|
+
this[TYPES.string].isWellFormed.local = Valtype.i32;
|
408
|
+
this[TYPES.string].isWellFormed.local2 = Valtype.i32;
|
409
|
+
this[TYPES.string].isWellFormed.returnType = TYPES.boolean;
|
331
410
|
};
|