porffor 0.0.0-579ef36 → 0.0.0-70c2792
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 +1 -1
- package/compiler/codeGen.js +199 -34
- package/compiler/index.js +10 -1
- package/compiler/opt.js +1 -1
- package/compiler/prototype.js +2 -4
- package/compiler/sections.js +3 -2
- package/compiler/wrap.js +11 -2
- package/package.json +1 -1
- package/runner/index.js +7 -0
- package/runner/repl.js +8 -13
- package/runner/transform.js +4 -26
- package/runner/version.js +10 -0
- package/t.js +31 -0
package/README.md
CHANGED
@@ -76,6 +76,7 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
76
76
|
- string member (char) access via `str[ind]` (eg `str[0]`)
|
77
77
|
- string concat (`+`) (eg `'a' + 'b'`)
|
78
78
|
- truthy/falsy (eg `!'' == true`)
|
79
|
+
- string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
|
79
80
|
|
80
81
|
### built-ins
|
81
82
|
|
@@ -106,7 +107,6 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
106
107
|
- arrays/strings inside arrays
|
107
108
|
- strings
|
108
109
|
- member setting
|
109
|
-
- equality
|
110
110
|
- more math operators (`**`, etc)
|
111
111
|
- `do { ... } while (...)`
|
112
112
|
- exceptions
|
package/compiler/codeGen.js
CHANGED
@@ -35,7 +35,14 @@ const debug = str => {
|
|
35
35
|
};
|
36
36
|
|
37
37
|
const todo = msg => {
|
38
|
-
|
38
|
+
class TodoError extends Error {
|
39
|
+
constructor(message) {
|
40
|
+
super(message);
|
41
|
+
this.name = 'TodoError';
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
throw new TodoError(`todo: ${msg}`);
|
39
46
|
|
40
47
|
const code = [];
|
41
48
|
|
@@ -101,6 +108,9 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
101
108
|
case 'WhileStatement':
|
102
109
|
return generateWhile(scope, decl);
|
103
110
|
|
111
|
+
/* case 'ForOfStatement':
|
112
|
+
return generateForOf(scope, decl); */
|
113
|
+
|
104
114
|
case 'BreakStatement':
|
105
115
|
return generateBreak(scope, decl);
|
106
116
|
|
@@ -327,13 +337,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
327
337
|
|
328
338
|
scope.memory = true;
|
329
339
|
|
330
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
331
|
-
|
332
340
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
341
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
342
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
335
343
|
|
336
344
|
if (assign) {
|
345
|
+
const pointer = arrays.get(name ?? '$undeclared');
|
346
|
+
|
337
347
|
return [
|
338
348
|
// setup right
|
339
349
|
...right,
|
@@ -384,15 +394,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
384
394
|
|
385
395
|
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
386
396
|
|
387
|
-
|
397
|
+
// alloc/assign array
|
398
|
+
const [ , pointer ] = makeArray(scope, {
|
388
399
|
rawElements: new Array(0)
|
389
400
|
}, global, name, true, 'i16');
|
390
401
|
|
391
402
|
return [
|
392
|
-
// setup new/out array
|
393
|
-
...newOut,
|
394
|
-
[ Opcodes.drop ],
|
395
|
-
|
396
403
|
// setup left
|
397
404
|
...left,
|
398
405
|
Opcodes.i32_to_u,
|
@@ -458,6 +465,111 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
458
465
|
];
|
459
466
|
};
|
460
467
|
|
468
|
+
const compareStrings = (scope, left, right) => {
|
469
|
+
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
470
|
+
// todo: convert left and right to strings if not
|
471
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
472
|
+
// todo: optimize this if using literals/known lengths?
|
473
|
+
|
474
|
+
scope.memory = true;
|
475
|
+
|
476
|
+
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
477
|
+
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
478
|
+
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
479
|
+
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
480
|
+
|
481
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
482
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
483
|
+
|
484
|
+
return [
|
485
|
+
// setup left
|
486
|
+
...left,
|
487
|
+
Opcodes.i32_to_u,
|
488
|
+
[ Opcodes.local_tee, leftPointer ],
|
489
|
+
|
490
|
+
// setup right
|
491
|
+
...right,
|
492
|
+
Opcodes.i32_to_u,
|
493
|
+
[ Opcodes.local_tee, rightPointer ],
|
494
|
+
|
495
|
+
// fast path: check leftPointer == rightPointer
|
496
|
+
// use if (block) for everything after to "return" a value early
|
497
|
+
[ Opcodes.i32_ne ],
|
498
|
+
[ Opcodes.if, Valtype.i32 ],
|
499
|
+
|
500
|
+
// get lengths
|
501
|
+
[ Opcodes.local_get, leftPointer ],
|
502
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
503
|
+
[ Opcodes.local_tee, leftLength ],
|
504
|
+
|
505
|
+
[ Opcodes.local_get, rightPointer ],
|
506
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
507
|
+
[ Opcodes.local_tee, rightLength ],
|
508
|
+
|
509
|
+
// fast path: check leftLength != rightLength
|
510
|
+
[ Opcodes.i32_ne ],
|
511
|
+
[ Opcodes.if, Blocktype.void ],
|
512
|
+
...number(0, Valtype.i32),
|
513
|
+
[ Opcodes.br, 1 ],
|
514
|
+
[ Opcodes.end ],
|
515
|
+
|
516
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
517
|
+
|
518
|
+
// setup index end as length * sizeof i16 (2)
|
519
|
+
// we do this instead of having to do mul/div each iter for perf™
|
520
|
+
[ Opcodes.local_get, leftLength ],
|
521
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
522
|
+
[ Opcodes.i32_mul ],
|
523
|
+
[ Opcodes.local_set, indexEnd ],
|
524
|
+
|
525
|
+
// iterate over each char and check if eq
|
526
|
+
[ Opcodes.loop, Blocktype.void ],
|
527
|
+
|
528
|
+
// fetch left
|
529
|
+
[ Opcodes.local_get, index ],
|
530
|
+
[ Opcodes.local_get, leftPointer ],
|
531
|
+
[ Opcodes.i32_add ],
|
532
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
533
|
+
|
534
|
+
// fetch right
|
535
|
+
[ Opcodes.local_get, index ],
|
536
|
+
[ Opcodes.local_get, rightPointer ],
|
537
|
+
[ Opcodes.i32_add ],
|
538
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
539
|
+
|
540
|
+
// not equal, "return" false
|
541
|
+
[ Opcodes.i32_ne ],
|
542
|
+
[ Opcodes.if, Blocktype.void ],
|
543
|
+
...number(0, Valtype.i32),
|
544
|
+
[ Opcodes.br, 2 ],
|
545
|
+
[ Opcodes.end ],
|
546
|
+
|
547
|
+
// index += sizeof i16 (2)
|
548
|
+
[ Opcodes.local_get, index ],
|
549
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
550
|
+
[ Opcodes.i32_add ],
|
551
|
+
[ Opcodes.local_tee, index ],
|
552
|
+
|
553
|
+
// if index != index end (length * sizeof 16), loop
|
554
|
+
[ Opcodes.local_get, indexEnd ],
|
555
|
+
[ Opcodes.i32_ne ],
|
556
|
+
[ Opcodes.br_if, 0 ],
|
557
|
+
[ Opcodes.end ],
|
558
|
+
|
559
|
+
// no failed checks, so true!
|
560
|
+
...number(1, Valtype.i32),
|
561
|
+
|
562
|
+
// pointers match, so true
|
563
|
+
[ Opcodes.else ],
|
564
|
+
...number(1, Valtype.i32),
|
565
|
+
[ Opcodes.end ],
|
566
|
+
|
567
|
+
// convert i32 result to valtype
|
568
|
+
// do not do as automatically added by binary exp gen for equality ops
|
569
|
+
// Opcodes.i32_from_u
|
570
|
+
];
|
571
|
+
};
|
572
|
+
|
461
573
|
const falsy = (scope, wasm, type) => {
|
462
574
|
// arrays are always truthy
|
463
575
|
if (type === TYPES._array) return [
|
@@ -524,11 +636,28 @@ const truthy = (scope, wasm, type) => {
|
|
524
636
|
];
|
525
637
|
};
|
526
638
|
|
527
|
-
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$
|
639
|
+
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
528
640
|
if (op === '||' || op === '&&' || op === '??') {
|
529
641
|
return performLogicOp(scope, op, left, right);
|
530
642
|
}
|
531
643
|
|
644
|
+
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
645
|
+
|
646
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
647
|
+
if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
|
648
|
+
return [
|
649
|
+
...left,
|
650
|
+
...right,
|
651
|
+
|
652
|
+
// drop values
|
653
|
+
[ Opcodes.drop ],
|
654
|
+
[ Opcodes.drop ],
|
655
|
+
|
656
|
+
// return false (===)/true (!==)
|
657
|
+
...number(op === '===' ? 0 : 1, Valtype.i32)
|
658
|
+
];
|
659
|
+
}
|
660
|
+
|
532
661
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
533
662
|
if (op === '+') {
|
534
663
|
// string concat (a + b)
|
@@ -539,8 +668,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
539
668
|
if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
|
540
669
|
|
541
670
|
// else leave bool ops
|
542
|
-
// todo: convert string to number if string and number
|
543
|
-
// todo: string
|
671
|
+
// todo: convert string to number if string and number/bool
|
672
|
+
// todo: string (>|>=|<|<=) string
|
673
|
+
|
674
|
+
// string comparison
|
675
|
+
if (op === '===' || op === '==') {
|
676
|
+
return compareStrings(scope, left, right);
|
677
|
+
}
|
678
|
+
|
679
|
+
if (op === '!==' || op === '!=') {
|
680
|
+
return [
|
681
|
+
...compareStrings(scope, left, right),
|
682
|
+
[ Opcodes.i32_eqz ]
|
683
|
+
];
|
684
|
+
}
|
544
685
|
}
|
545
686
|
|
546
687
|
let ops = operatorOpcode[valtype][op];
|
@@ -569,13 +710,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
569
710
|
];
|
570
711
|
};
|
571
712
|
|
713
|
+
let binaryExpDepth = 0;
|
572
714
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
715
|
+
binaryExpDepth++;
|
716
|
+
|
573
717
|
const out = [
|
574
718
|
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
575
719
|
];
|
576
720
|
|
577
721
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
578
722
|
|
723
|
+
binaryExpDepth--;
|
579
724
|
return out;
|
580
725
|
};
|
581
726
|
|
@@ -682,6 +827,7 @@ const getType = (scope, _name) => {
|
|
682
827
|
|
683
828
|
const getNodeType = (scope, node) => {
|
684
829
|
if (node.type === 'Literal') {
|
830
|
+
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
685
831
|
return TYPES[typeof node.value];
|
686
832
|
}
|
687
833
|
|
@@ -696,7 +842,7 @@ const getNodeType = (scope, node) => {
|
|
696
842
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
697
843
|
const name = node.callee.name;
|
698
844
|
const func = funcs.find(x => x.name === name);
|
699
|
-
if (func) return func.returnType
|
845
|
+
if (func) return func.returnType;
|
700
846
|
|
701
847
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
702
848
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
@@ -721,9 +867,7 @@ const getNodeType = (scope, node) => {
|
|
721
867
|
protoFunc = prototypeFuncs[baseType]?.[func];
|
722
868
|
}
|
723
869
|
|
724
|
-
if (protoFunc) return protoFunc.returnType
|
725
|
-
|
726
|
-
return TYPES.number;
|
870
|
+
if (protoFunc) return protoFunc.returnType;
|
727
871
|
}
|
728
872
|
|
729
873
|
if (node.type === 'ExpressionStatement') {
|
@@ -755,9 +899,6 @@ const getNodeType = (scope, node) => {
|
|
755
899
|
|
756
900
|
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
757
901
|
}
|
758
|
-
|
759
|
-
// default to number
|
760
|
-
return TYPES.number;
|
761
902
|
};
|
762
903
|
|
763
904
|
const generateLiteral = (scope, decl, global, name) => {
|
@@ -792,7 +933,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
792
933
|
|
793
934
|
return makeArray(scope, {
|
794
935
|
rawElements
|
795
|
-
}, global, name, false, 'i16');
|
936
|
+
}, global, name, false, 'i16')[0];
|
796
937
|
|
797
938
|
default:
|
798
939
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -941,10 +1082,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
941
1082
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
942
1083
|
|
943
1084
|
// register array
|
944
|
-
makeArray(scope, {
|
1085
|
+
const [ , pointer ] = makeArray(scope, {
|
945
1086
|
rawElements: new Array(0)
|
946
1087
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
947
|
-
pointer = arrays.get(baseName);
|
948
1088
|
|
949
1089
|
const [ local, isGlobal ] = lookupName(scope, baseName);
|
950
1090
|
|
@@ -980,10 +1120,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
980
1120
|
set: value => arrayUtil.setLength(pointer, value),
|
981
1121
|
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
982
1122
|
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
983
|
-
|
1123
|
+
return makeArray(scope, {
|
984
1124
|
rawElements: new Array(length)
|
985
1125
|
}, _global, _name, true, itemType);
|
986
|
-
return [ out, arrays.get(_name ?? '$undeclared') ];
|
987
1126
|
}),
|
988
1127
|
[ Opcodes.end ]
|
989
1128
|
];
|
@@ -1057,7 +1196,7 @@ const generateNew = (scope, decl, _global, _name) => {
|
|
1057
1196
|
// hack: basically treat this as a normal call for builtins for now
|
1058
1197
|
const name = mapName(decl.callee.name);
|
1059
1198
|
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1060
|
-
if (!builtinFuncs[name]) return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1199
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1061
1200
|
|
1062
1201
|
return generateCall(scope, decl, _global, _name);
|
1063
1202
|
};
|
@@ -1289,7 +1428,7 @@ const generateUnary = (scope, decl) => {
|
|
1289
1428
|
return out;
|
1290
1429
|
|
1291
1430
|
case 'typeof':
|
1292
|
-
const type = getNodeType(scope, decl.argument);
|
1431
|
+
const type = getNodeType(scope, decl.argument) ?? TYPES.number;
|
1293
1432
|
|
1294
1433
|
// for custom types, just return object
|
1295
1434
|
if (type > 0xffffffffffff7) return number(TYPES.object);
|
@@ -1422,9 +1561,28 @@ const generateWhile = (scope, decl) => {
|
|
1422
1561
|
return out;
|
1423
1562
|
};
|
1424
1563
|
|
1564
|
+
const generateForOf = (scope, decl) => {
|
1565
|
+
const out = [];
|
1566
|
+
|
1567
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1568
|
+
depth.push('while');
|
1569
|
+
|
1570
|
+
out.push(...generate(scope, decl.test));
|
1571
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1572
|
+
depth.push('if');
|
1573
|
+
|
1574
|
+
out.push(...generate(scope, decl.body));
|
1575
|
+
|
1576
|
+
out.push([ Opcodes.br, 1 ]);
|
1577
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
1578
|
+
depth.pop(); depth.pop();
|
1579
|
+
|
1580
|
+
return out;
|
1581
|
+
};
|
1582
|
+
|
1425
1583
|
const getNearestLoop = () => {
|
1426
1584
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1427
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1585
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1428
1586
|
}
|
1429
1587
|
|
1430
1588
|
return -1;
|
@@ -1514,7 +1672,16 @@ const allocPage = reason => {
|
|
1514
1672
|
let ind = pages.size;
|
1515
1673
|
pages.set(reason, ind);
|
1516
1674
|
|
1517
|
-
if (
|
1675
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
|
1676
|
+
|
1677
|
+
return ind;
|
1678
|
+
};
|
1679
|
+
|
1680
|
+
const freePage = reason => {
|
1681
|
+
let ind = pages.get(reason);
|
1682
|
+
pages.delete(reason);
|
1683
|
+
|
1684
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1518
1685
|
|
1519
1686
|
return ind;
|
1520
1687
|
};
|
@@ -1578,12 +1745,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1578
1745
|
|
1579
1746
|
scope.memory = true;
|
1580
1747
|
|
1581
|
-
return out;
|
1748
|
+
return [ out, pointer ];
|
1582
1749
|
};
|
1583
1750
|
|
1584
1751
|
let arrays = new Map();
|
1585
1752
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1586
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype);
|
1753
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1587
1754
|
};
|
1588
1755
|
|
1589
1756
|
export const generateMember = (scope, decl, _global, _name) => {
|
@@ -1644,10 +1811,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1644
1811
|
|
1645
1812
|
// string
|
1646
1813
|
|
1647
|
-
const newOut = makeArray(scope, {
|
1814
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1648
1815
|
rawElements: new Array(1)
|
1649
1816
|
}, _global, _name, true, 'i16');
|
1650
|
-
const newPointer = arrays.get(_name ?? '$undeclared');
|
1651
1817
|
|
1652
1818
|
return [
|
1653
1819
|
// setup new/out array
|
@@ -1753,7 +1919,7 @@ const generateFunc = (scope, decl) => {
|
|
1753
1919
|
name,
|
1754
1920
|
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
1755
1921
|
returns: innerScope.returns,
|
1756
|
-
returnType: innerScope.returnType
|
1922
|
+
returnType: innerScope.returnType,
|
1757
1923
|
locals: innerScope.locals,
|
1758
1924
|
memory: innerScope.memory,
|
1759
1925
|
throws: innerScope.throws,
|
@@ -1912,10 +2078,9 @@ const internalConstrs = {
|
|
1912
2078
|
|
1913
2079
|
// new Array(n)
|
1914
2080
|
|
1915
|
-
makeArray(scope, {
|
2081
|
+
const [ , pointer ] = makeArray(scope, {
|
1916
2082
|
rawElements: new Array(0)
|
1917
2083
|
}, global, name, true);
|
1918
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
1919
2084
|
|
1920
2085
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
1921
2086
|
|
package/compiler/index.js
CHANGED
@@ -14,7 +14,8 @@ const bold = x => `\u001b[1m${x}\u001b[0m`;
|
|
14
14
|
const areaColors = {
|
15
15
|
codegen: [ 20, 80, 250 ],
|
16
16
|
opt: [ 250, 20, 80 ],
|
17
|
-
sections: [ 20, 250, 80 ]
|
17
|
+
sections: [ 20, 250, 80 ],
|
18
|
+
alloc: [ 250, 250, 20 ]
|
18
19
|
};
|
19
20
|
|
20
21
|
globalThis.log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
|
@@ -38,6 +39,7 @@ const logFuncs = (funcs, globals, exceptions) => {
|
|
38
39
|
export default (code, flags) => {
|
39
40
|
globalThis.optLog = process.argv.includes('-opt-log');
|
40
41
|
globalThis.codeLog = process.argv.includes('-code-log');
|
42
|
+
globalThis.allocLog = process.argv.includes('-alloc-log');
|
41
43
|
|
42
44
|
for (const x in BuiltinPreludes) {
|
43
45
|
if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
|
@@ -63,5 +65,12 @@ export default (code, flags) => {
|
|
63
65
|
const sections = produceSections(funcs, globals, tags, pages, flags);
|
64
66
|
if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
|
65
67
|
|
68
|
+
if (allocLog) {
|
69
|
+
const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
|
70
|
+
const bytes = wasmPages * 65536;
|
71
|
+
log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
|
72
|
+
// console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
|
73
|
+
}
|
74
|
+
|
66
75
|
return { wasm: sections, funcs, globals, tags, exceptions, pages };
|
67
76
|
};
|
package/compiler/opt.js
CHANGED
@@ -20,7 +20,7 @@ export default (funcs, globals) => {
|
|
20
20
|
if (optLevel === 0) return;
|
21
21
|
|
22
22
|
const tailCall = process.argv.includes('-tail-call');
|
23
|
-
if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
|
23
|
+
if (tailCall) log('opt', 'warning: tail call proposal is not widely implemented! (you used -tail-call)');
|
24
24
|
|
25
25
|
if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
|
26
26
|
// inline pass (very WIP)
|
package/compiler/prototype.js
CHANGED
@@ -25,7 +25,6 @@ export const PrototypeFuncs = function() {
|
|
25
25
|
|
26
26
|
this[TYPES._array] = {
|
27
27
|
// lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
|
28
|
-
// todo: out of bounds (>) properly
|
29
28
|
at: (pointer, length, wIndex, iTmp) => [
|
30
29
|
...wIndex,
|
31
30
|
Opcodes.i32_to,
|
@@ -147,7 +146,6 @@ export const PrototypeFuncs = function() {
|
|
147
146
|
this[TYPES._array].push.noArgRetLength = true;
|
148
147
|
|
149
148
|
this[TYPES.string] = {
|
150
|
-
// todo: out of bounds properly
|
151
149
|
at: (pointer, length, wIndex, iTmp, arrayShell) => {
|
152
150
|
const [ newOut, newPointer ] = arrayShell(1, 'i16');
|
153
151
|
|
@@ -157,9 +155,9 @@ export const PrototypeFuncs = function() {
|
|
157
155
|
[ Opcodes.drop ],
|
158
156
|
|
159
157
|
...number(0, Valtype.i32), // base 0 for store later
|
160
|
-
Opcodes.i32_to_u,
|
161
158
|
|
162
159
|
...wIndex,
|
160
|
+
Opcodes.i32_to_u,
|
163
161
|
[ Opcodes.local_tee, iTmp ],
|
164
162
|
|
165
163
|
// if index < 0: access index + array length
|
@@ -265,7 +263,7 @@ export const PrototypeFuncs = function() {
|
|
265
263
|
},
|
266
264
|
};
|
267
265
|
|
268
|
-
this[TYPES.string].at.local =
|
266
|
+
this[TYPES.string].at.local = Valtype.i32;
|
269
267
|
this[TYPES.string].at.returnType = TYPES.string;
|
270
268
|
this[TYPES.string].charAt.returnType = TYPES.string;
|
271
269
|
this[TYPES.string].charCodeAt.local = Valtype.i32;
|
package/compiler/sections.js
CHANGED
@@ -36,7 +36,7 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
36
36
|
// tree shake imports
|
37
37
|
for (const f of funcs) {
|
38
38
|
for (const inst of f.wasm) {
|
39
|
-
if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
|
39
|
+
if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] < importedFuncs.length) {
|
40
40
|
const idx = inst[1];
|
41
41
|
const func = importedFuncs[idx];
|
42
42
|
|
@@ -51,10 +51,11 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
51
51
|
// fix call indexes for non-imports
|
52
52
|
const delta = importedFuncs.length - importFuncs.length;
|
53
53
|
for (const f of funcs) {
|
54
|
+
f.originalIndex = f.index;
|
54
55
|
f.index -= delta;
|
55
56
|
|
56
57
|
for (const inst of f.wasm) {
|
57
|
-
if (inst[0] === Opcodes.call && inst[1] >= importedFuncs.length) {
|
58
|
+
if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
|
58
59
|
inst[1] -= delta;
|
59
60
|
}
|
60
61
|
}
|
package/compiler/wrap.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import compile from './index.js';
|
2
2
|
import decompile from './decompile.js';
|
3
|
-
|
3
|
+
import fs from 'node:fs';
|
4
4
|
|
5
5
|
const bold = x => `\u001b[1m${x}\u001b[0m`;
|
6
6
|
|
@@ -27,7 +27,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
27
27
|
|
28
28
|
if (source.includes('export function')) flags.push('module');
|
29
29
|
|
30
|
-
|
30
|
+
fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
31
31
|
|
32
32
|
times.push(performance.now() - t1);
|
33
33
|
if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
|
@@ -90,6 +90,15 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
90
90
|
return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
|
91
91
|
}
|
92
92
|
|
93
|
+
case 'function': {
|
94
|
+
// wasm func index, including all imports
|
95
|
+
const func = funcs.find(x => (x.originalIndex ?? x.index) === ret);
|
96
|
+
if (!func) return ret;
|
97
|
+
|
98
|
+
// make fake empty func for repl/etc
|
99
|
+
return {[func.name]() {}}[func.name];
|
100
|
+
}
|
101
|
+
|
93
102
|
default: return ret;
|
94
103
|
}
|
95
104
|
} catch (e) {
|
package/package.json
CHANGED
package/runner/index.js
CHANGED
@@ -5,6 +5,12 @@ import fs from 'node:fs';
|
|
5
5
|
|
6
6
|
const file = process.argv.slice(2).find(x => x[0] !== '-');
|
7
7
|
if (!file) {
|
8
|
+
if (process.argv.includes('-v')) {
|
9
|
+
// just print version
|
10
|
+
console.log((await import('./version.js')).default);
|
11
|
+
process.exit(0);
|
12
|
+
}
|
13
|
+
|
8
14
|
// run repl if no file given
|
9
15
|
await import('./repl.js');
|
10
16
|
|
@@ -30,5 +36,6 @@ try {
|
|
30
36
|
exports.main();
|
31
37
|
if (cache) process.stdout.write(cache);
|
32
38
|
} catch (e) {
|
39
|
+
if (cache) process.stdout.write(cache);
|
33
40
|
console.error(`${e.constructor.name}: ${e.message}`);
|
34
41
|
}
|
package/runner/repl.js
CHANGED
@@ -1,14 +1,7 @@
|
|
1
1
|
import compile from '../compiler/wrap.js';
|
2
|
+
import rev from './version.js';
|
2
3
|
|
3
4
|
import repl from 'node:repl';
|
4
|
-
import fs from 'node:fs';
|
5
|
-
|
6
|
-
let rev = 'unknown';
|
7
|
-
try {
|
8
|
-
rev = fs.readFileSync(new URL('../.git/refs/heads/main', import.meta.url), 'utf8').trim().slice(0, 7);
|
9
|
-
} catch {
|
10
|
-
rev = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version.split('-')[1];
|
11
|
-
}
|
12
5
|
|
13
6
|
// process.argv.push('-O0'); // disable opts
|
14
7
|
|
@@ -48,15 +41,17 @@ const memoryToString = mem => {
|
|
48
41
|
return out;
|
49
42
|
};
|
50
43
|
|
44
|
+
const alwaysPrev = process.argv.includes('-prev');
|
45
|
+
|
51
46
|
let prev = '';
|
52
47
|
const run = async (source, _context, _filename, callback, run = true) => {
|
53
48
|
let toRun = prev + source.trim();
|
54
|
-
|
49
|
+
if (alwaysPrev) prev = toRun + ';\n';
|
55
50
|
|
56
51
|
const { exports, wasm, pages } = await compile(toRun, []);
|
57
|
-
fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
52
|
+
// fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
58
53
|
|
59
|
-
if (exports.$) {
|
54
|
+
if (run && exports.$) {
|
60
55
|
lastMemory = exports.$;
|
61
56
|
lastPages = [...pages.keys()];
|
62
57
|
}
|
@@ -64,8 +59,8 @@ const run = async (source, _context, _filename, callback, run = true) => {
|
|
64
59
|
const ret = run ? exports.main() : undefined;
|
65
60
|
callback(null, ret);
|
66
61
|
|
67
|
-
|
68
|
-
prev = toRun + ';\n';
|
62
|
+
if (!alwaysPrev && (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function '))) prev = toRun + ';\n';
|
63
|
+
// prev = toRun + ';\n';
|
69
64
|
};
|
70
65
|
|
71
66
|
const replServer = repl.start({ prompt: '> ', eval: run });
|
package/runner/transform.js
CHANGED
@@ -5,32 +5,10 @@ const file = process.argv.slice(2).find(x => x[0] !== '-');
|
|
5
5
|
|
6
6
|
const source = fs.readFileSync(file, 'utf8');
|
7
7
|
|
8
|
-
const underline = x => `\u001b[4m\u001b[1m${x}\u001b[0m`;
|
9
|
-
const bold = x => `\u001b[1m${x}\u001b[0m`;
|
10
|
-
|
11
|
-
let cache = '';
|
12
|
-
const print = str => {
|
13
|
-
cache += str;
|
14
|
-
|
15
|
-
if (str === '\n') {
|
16
|
-
process.stdout.write(cache);
|
17
|
-
cache = '';
|
18
|
-
}
|
19
|
-
};
|
20
|
-
|
21
8
|
const { wasm } = await compile(source);
|
22
9
|
|
23
|
-
|
24
|
-
|
25
|
-
if (!process.argv.includes('-no-run')) {
|
26
|
-
console.log(`\n\n${underline('output')}`);
|
27
|
-
const t2 = performance.now();
|
28
|
-
|
29
|
-
exports.main();
|
30
|
-
print('\n');
|
31
|
-
|
32
|
-
if (!raw) console.log(bold(`\n\nexecuted in ${(performance.now() - t2).toFixed(2)}ms`));
|
33
|
-
}
|
10
|
+
// const out = `(async () => { const print = str => process.stdout.write(str); (await WebAssembly.instantiate(Uint8Array.from([${wasm.toString()}]), {'': { p: i => print(i.toString()), c: i => print(String.fromCharCode(i))}})).instance.exports.m()})()`;
|
11
|
+
const out = `new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])),{'':{p:i=>process.stdout.write(i.toString())}}).exports.m()`;
|
34
12
|
|
35
|
-
|
36
|
-
|
13
|
+
console.log(out);
|
14
|
+
eval(out);
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import fs from 'node:fs';
|
2
|
+
|
3
|
+
let rev = 'unknown';
|
4
|
+
try {
|
5
|
+
rev = fs.readFileSync(new URL('../.git/refs/heads/main', import.meta.url), 'utf8').trim().slice(0, 7);
|
6
|
+
} catch {
|
7
|
+
rev = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version.split('-')[1].slice(0, 7);
|
8
|
+
}
|
9
|
+
|
10
|
+
export default rev;
|
package/t.js
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
let assert = Object();
|
2
|
+
|
3
|
+
assert._isSameValue = function (a, b) {
|
4
|
+
if (a === b) {
|
5
|
+
// Handle +/-0 vs. -/+0
|
6
|
+
return a !== 0 || 1 / a === 1 / b;
|
7
|
+
}
|
8
|
+
|
9
|
+
// Handle NaN vs. NaN
|
10
|
+
return a !== a && b !== b;
|
11
|
+
|
12
|
+
// return a === b;
|
13
|
+
};
|
14
|
+
|
15
|
+
assert.sameValue = function (actual, expected) {
|
16
|
+
/* try {
|
17
|
+
if (assert._isSameValue(actual, expected)) {
|
18
|
+
return;
|
19
|
+
}
|
20
|
+
} catch (error) {
|
21
|
+
throw new Test262Error('_isSameValue operation threw');
|
22
|
+
} */
|
23
|
+
|
24
|
+
if (assert._isSameValue(actual, expected)) {
|
25
|
+
return;
|
26
|
+
}
|
27
|
+
|
28
|
+
throw new Test262Error('assert.sameValue failed');
|
29
|
+
};
|
30
|
+
|
31
|
+
assert.sameValue("lego".charAt(), "l");
|