porffor 0.0.0-579ef36 → 0.0.0-61de729
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 +229 -61
- 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
|
|
@@ -295,10 +305,10 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
295
305
|
return idx;
|
296
306
|
};
|
297
307
|
|
298
|
-
const performLogicOp = (scope, op, left, right) => {
|
308
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
299
309
|
const checks = {
|
300
|
-
'||':
|
301
|
-
'&&':
|
310
|
+
'||': falsy,
|
311
|
+
'&&': truthy,
|
302
312
|
// todo: ??
|
303
313
|
};
|
304
314
|
|
@@ -310,7 +320,8 @@ const performLogicOp = (scope, op, left, right) => {
|
|
310
320
|
return [
|
311
321
|
...left,
|
312
322
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
313
|
-
...checks[op],
|
323
|
+
...checks[op](scope, [], leftType),
|
324
|
+
Opcodes.i32_to,
|
314
325
|
[ Opcodes.if, valtypeBinary ],
|
315
326
|
...right,
|
316
327
|
[ Opcodes.else ],
|
@@ -327,13 +338,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
327
338
|
|
328
339
|
scope.memory = true;
|
329
340
|
|
330
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
331
|
-
|
332
341
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
342
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
343
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
335
344
|
|
336
345
|
if (assign) {
|
346
|
+
const pointer = arrays.get(name ?? '$undeclared');
|
347
|
+
|
337
348
|
return [
|
338
349
|
// setup right
|
339
350
|
...right,
|
@@ -384,15 +395,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
384
395
|
|
385
396
|
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
386
397
|
|
387
|
-
|
398
|
+
// alloc/assign array
|
399
|
+
const [ , pointer ] = makeArray(scope, {
|
388
400
|
rawElements: new Array(0)
|
389
401
|
}, global, name, true, 'i16');
|
390
402
|
|
391
403
|
return [
|
392
|
-
// setup new/out array
|
393
|
-
...newOut,
|
394
|
-
[ Opcodes.drop ],
|
395
|
-
|
396
404
|
// setup left
|
397
405
|
...left,
|
398
406
|
Opcodes.i32_to_u,
|
@@ -458,75 +466,199 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
458
466
|
];
|
459
467
|
};
|
460
468
|
|
461
|
-
const
|
469
|
+
const compareStrings = (scope, left, right) => {
|
470
|
+
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
471
|
+
// todo: convert left and right to strings if not
|
472
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
473
|
+
// todo: optimize this if using literals/known lengths?
|
474
|
+
|
475
|
+
scope.memory = true;
|
476
|
+
|
477
|
+
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
478
|
+
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
479
|
+
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
480
|
+
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
481
|
+
|
482
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
483
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
484
|
+
|
485
|
+
return [
|
486
|
+
// setup left
|
487
|
+
...left,
|
488
|
+
Opcodes.i32_to_u,
|
489
|
+
[ Opcodes.local_tee, leftPointer ],
|
490
|
+
|
491
|
+
// setup right
|
492
|
+
...right,
|
493
|
+
Opcodes.i32_to_u,
|
494
|
+
[ Opcodes.local_tee, rightPointer ],
|
495
|
+
|
496
|
+
// fast path: check leftPointer == rightPointer
|
497
|
+
// use if (block) for everything after to "return" a value early
|
498
|
+
[ Opcodes.i32_ne ],
|
499
|
+
[ Opcodes.if, Valtype.i32 ],
|
500
|
+
|
501
|
+
// get lengths
|
502
|
+
[ Opcodes.local_get, leftPointer ],
|
503
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
504
|
+
[ Opcodes.local_tee, leftLength ],
|
505
|
+
|
506
|
+
[ Opcodes.local_get, rightPointer ],
|
507
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
508
|
+
[ Opcodes.local_tee, rightLength ],
|
509
|
+
|
510
|
+
// fast path: check leftLength != rightLength
|
511
|
+
[ Opcodes.i32_ne ],
|
512
|
+
[ Opcodes.if, Blocktype.void ],
|
513
|
+
...number(0, Valtype.i32),
|
514
|
+
[ Opcodes.br, 1 ],
|
515
|
+
[ Opcodes.end ],
|
516
|
+
|
517
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
518
|
+
|
519
|
+
// setup index end as length * sizeof i16 (2)
|
520
|
+
// we do this instead of having to do mul/div each iter for perf™
|
521
|
+
[ Opcodes.local_get, leftLength ],
|
522
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
523
|
+
[ Opcodes.i32_mul ],
|
524
|
+
[ Opcodes.local_set, indexEnd ],
|
525
|
+
|
526
|
+
// iterate over each char and check if eq
|
527
|
+
[ Opcodes.loop, Blocktype.void ],
|
528
|
+
|
529
|
+
// fetch left
|
530
|
+
[ Opcodes.local_get, index ],
|
531
|
+
[ Opcodes.local_get, leftPointer ],
|
532
|
+
[ Opcodes.i32_add ],
|
533
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
534
|
+
|
535
|
+
// fetch right
|
536
|
+
[ Opcodes.local_get, index ],
|
537
|
+
[ Opcodes.local_get, rightPointer ],
|
538
|
+
[ Opcodes.i32_add ],
|
539
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
540
|
+
|
541
|
+
// not equal, "return" false
|
542
|
+
[ Opcodes.i32_ne ],
|
543
|
+
[ Opcodes.if, Blocktype.void ],
|
544
|
+
...number(0, Valtype.i32),
|
545
|
+
[ Opcodes.br, 2 ],
|
546
|
+
[ Opcodes.end ],
|
547
|
+
|
548
|
+
// index += sizeof i16 (2)
|
549
|
+
[ Opcodes.local_get, index ],
|
550
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
551
|
+
[ Opcodes.i32_add ],
|
552
|
+
[ Opcodes.local_tee, index ],
|
553
|
+
|
554
|
+
// if index != index end (length * sizeof 16), loop
|
555
|
+
[ Opcodes.local_get, indexEnd ],
|
556
|
+
[ Opcodes.i32_ne ],
|
557
|
+
[ Opcodes.br_if, 0 ],
|
558
|
+
[ Opcodes.end ],
|
559
|
+
|
560
|
+
// no failed checks, so true!
|
561
|
+
...number(1, Valtype.i32),
|
562
|
+
|
563
|
+
// pointers match, so true
|
564
|
+
[ Opcodes.else ],
|
565
|
+
...number(1, Valtype.i32),
|
566
|
+
[ Opcodes.end ],
|
567
|
+
|
568
|
+
// convert i32 result to valtype
|
569
|
+
// do not do as automatically added by binary exp gen for equality ops
|
570
|
+
// Opcodes.i32_from_u
|
571
|
+
];
|
572
|
+
};
|
573
|
+
|
574
|
+
const truthy = (scope, wasm, type) => {
|
462
575
|
// arrays are always truthy
|
463
576
|
if (type === TYPES._array) return [
|
464
577
|
...wasm,
|
465
|
-
[ Opcodes.drop ],
|
466
|
-
number(
|
578
|
+
...(wasm.length === 0 ? [] : [ [ Opcodes.drop ] ]),
|
579
|
+
number(1)
|
467
580
|
];
|
468
581
|
|
469
582
|
if (type === TYPES.string) {
|
470
|
-
// if "" (length = 0)
|
583
|
+
// if not "" (length = 0)
|
471
584
|
return [
|
472
585
|
// pointer
|
473
586
|
...wasm,
|
587
|
+
Opcodes.i32_to_u,
|
474
588
|
|
475
589
|
// get length
|
476
590
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
477
591
|
|
478
|
-
// if length
|
479
|
-
[ Opcodes.i32_eqz ],
|
592
|
+
// if length != 0
|
593
|
+
/* [ Opcodes.i32_eqz ],
|
594
|
+
[ Opcodes.i32_eqz ], */
|
480
595
|
Opcodes.i32_from_u
|
481
596
|
]
|
482
597
|
}
|
483
598
|
|
484
|
-
// if
|
599
|
+
// if != 0
|
485
600
|
return [
|
486
601
|
...wasm,
|
487
602
|
|
488
|
-
|
489
|
-
Opcodes.
|
603
|
+
/* Opcodes.eqz,
|
604
|
+
[ Opcodes.i32_eqz ],
|
605
|
+
Opcodes.i32_from */
|
490
606
|
];
|
491
607
|
};
|
492
608
|
|
493
|
-
const
|
609
|
+
const falsy = (scope, wasm, type) => {
|
494
610
|
// arrays are always truthy
|
495
611
|
if (type === TYPES._array) return [
|
496
612
|
...wasm,
|
497
|
-
[ Opcodes.drop ],
|
498
|
-
number(
|
613
|
+
...(wasm.length === 0 ? [] : [ [ Opcodes.drop ] ]),
|
614
|
+
number(0)
|
499
615
|
];
|
500
616
|
|
501
617
|
if (type === TYPES.string) {
|
502
|
-
// if
|
618
|
+
// if "" (length = 0)
|
503
619
|
return [
|
504
620
|
// pointer
|
505
621
|
...wasm,
|
622
|
+
Opcodes.i32_to_u,
|
506
623
|
|
507
624
|
// get length
|
508
625
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
509
626
|
|
510
|
-
// if length
|
511
|
-
|
512
|
-
[ Opcodes.i32_eqz ], */
|
627
|
+
// if length == 0
|
628
|
+
[ Opcodes.i32_eqz ],
|
513
629
|
Opcodes.i32_from_u
|
514
630
|
]
|
515
631
|
}
|
516
632
|
|
517
|
-
// if
|
633
|
+
// if = 0
|
518
634
|
return [
|
519
635
|
...wasm,
|
520
636
|
|
521
|
-
|
522
|
-
|
523
|
-
Opcodes.i32_from */
|
637
|
+
...Opcodes.eqz,
|
638
|
+
Opcodes.i32_from_u
|
524
639
|
];
|
525
640
|
};
|
526
641
|
|
527
|
-
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$
|
642
|
+
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
528
643
|
if (op === '||' || op === '&&' || op === '??') {
|
529
|
-
return performLogicOp(scope, op, left, right);
|
644
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
645
|
+
}
|
646
|
+
|
647
|
+
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
648
|
+
|
649
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
650
|
+
if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
|
651
|
+
return [
|
652
|
+
...left,
|
653
|
+
...right,
|
654
|
+
|
655
|
+
// drop values
|
656
|
+
[ Opcodes.drop ],
|
657
|
+
[ Opcodes.drop ],
|
658
|
+
|
659
|
+
// return false (===)/true (!==)
|
660
|
+
...number(op === '===' ? 0 : 1, Valtype.i32)
|
661
|
+
];
|
530
662
|
}
|
531
663
|
|
532
664
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
@@ -539,8 +671,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
539
671
|
if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
|
540
672
|
|
541
673
|
// else leave bool ops
|
542
|
-
// todo: convert string to number if string and number
|
543
|
-
// todo: string
|
674
|
+
// todo: convert string to number if string and number/bool
|
675
|
+
// todo: string (>|>=|<|<=) string
|
676
|
+
|
677
|
+
// string comparison
|
678
|
+
if (op === '===' || op === '==') {
|
679
|
+
return compareStrings(scope, left, right);
|
680
|
+
}
|
681
|
+
|
682
|
+
if (op === '!==' || op === '!=') {
|
683
|
+
return [
|
684
|
+
...compareStrings(scope, left, right),
|
685
|
+
[ Opcodes.i32_eqz ]
|
686
|
+
];
|
687
|
+
}
|
544
688
|
}
|
545
689
|
|
546
690
|
let ops = operatorOpcode[valtype][op];
|
@@ -569,13 +713,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
569
713
|
];
|
570
714
|
};
|
571
715
|
|
716
|
+
let binaryExpDepth = 0;
|
572
717
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
718
|
+
binaryExpDepth++;
|
719
|
+
|
573
720
|
const out = [
|
574
721
|
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
575
722
|
];
|
576
723
|
|
577
724
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
578
725
|
|
726
|
+
binaryExpDepth--;
|
579
727
|
return out;
|
580
728
|
};
|
581
729
|
|
@@ -634,7 +782,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
634
782
|
};
|
635
783
|
|
636
784
|
const generateLogicExp = (scope, decl) => {
|
637
|
-
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
|
785
|
+
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
638
786
|
};
|
639
787
|
|
640
788
|
const TYPES = {
|
@@ -682,6 +830,7 @@ const getType = (scope, _name) => {
|
|
682
830
|
|
683
831
|
const getNodeType = (scope, node) => {
|
684
832
|
if (node.type === 'Literal') {
|
833
|
+
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
685
834
|
return TYPES[typeof node.value];
|
686
835
|
}
|
687
836
|
|
@@ -696,7 +845,7 @@ const getNodeType = (scope, node) => {
|
|
696
845
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
697
846
|
const name = node.callee.name;
|
698
847
|
const func = funcs.find(x => x.name === name);
|
699
|
-
if (func) return func.returnType
|
848
|
+
if (func) return func.returnType;
|
700
849
|
|
701
850
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
702
851
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
@@ -721,9 +870,7 @@ const getNodeType = (scope, node) => {
|
|
721
870
|
protoFunc = prototypeFuncs[baseType]?.[func];
|
722
871
|
}
|
723
872
|
|
724
|
-
if (protoFunc) return protoFunc.returnType
|
725
|
-
|
726
|
-
return TYPES.number;
|
873
|
+
if (protoFunc) return protoFunc.returnType;
|
727
874
|
}
|
728
875
|
|
729
876
|
if (node.type === 'ExpressionStatement') {
|
@@ -755,9 +902,6 @@ const getNodeType = (scope, node) => {
|
|
755
902
|
|
756
903
|
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
757
904
|
}
|
758
|
-
|
759
|
-
// default to number
|
760
|
-
return TYPES.number;
|
761
905
|
};
|
762
906
|
|
763
907
|
const generateLiteral = (scope, decl, global, name) => {
|
@@ -792,7 +936,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
792
936
|
|
793
937
|
return makeArray(scope, {
|
794
938
|
rawElements
|
795
|
-
}, global, name, false, 'i16');
|
939
|
+
}, global, name, false, 'i16')[0];
|
796
940
|
|
797
941
|
default:
|
798
942
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -941,10 +1085,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
941
1085
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
942
1086
|
|
943
1087
|
// register array
|
944
|
-
makeArray(scope, {
|
1088
|
+
const [ , pointer ] = makeArray(scope, {
|
945
1089
|
rawElements: new Array(0)
|
946
1090
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
947
|
-
pointer = arrays.get(baseName);
|
948
1091
|
|
949
1092
|
const [ local, isGlobal ] = lookupName(scope, baseName);
|
950
1093
|
|
@@ -980,10 +1123,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
980
1123
|
set: value => arrayUtil.setLength(pointer, value),
|
981
1124
|
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
982
1125
|
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
983
|
-
|
1126
|
+
return makeArray(scope, {
|
984
1127
|
rawElements: new Array(length)
|
985
1128
|
}, _global, _name, true, itemType);
|
986
|
-
return [ out, arrays.get(_name ?? '$undeclared') ];
|
987
1129
|
}),
|
988
1130
|
[ Opcodes.end ]
|
989
1131
|
];
|
@@ -1057,7 +1199,7 @@ const generateNew = (scope, decl, _global, _name) => {
|
|
1057
1199
|
// hack: basically treat this as a normal call for builtins for now
|
1058
1200
|
const name = mapName(decl.callee.name);
|
1059
1201
|
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)})`);
|
1202
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1061
1203
|
|
1062
1204
|
return generateCall(scope, decl, _global, _name);
|
1063
1205
|
};
|
@@ -1245,7 +1387,7 @@ const generateUnary = (scope, decl) => {
|
|
1245
1387
|
|
1246
1388
|
case '!':
|
1247
1389
|
// !=
|
1248
|
-
return falsy(scope, generate(scope, decl.argument));
|
1390
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1249
1391
|
|
1250
1392
|
case '~':
|
1251
1393
|
return [
|
@@ -1289,7 +1431,7 @@ const generateUnary = (scope, decl) => {
|
|
1289
1431
|
return out;
|
1290
1432
|
|
1291
1433
|
case 'typeof':
|
1292
|
-
const type = getNodeType(scope, decl.argument);
|
1434
|
+
const type = getNodeType(scope, decl.argument) ?? TYPES.number;
|
1293
1435
|
|
1294
1436
|
// for custom types, just return object
|
1295
1437
|
if (type > 0xffffffffffff7) return number(TYPES.object);
|
@@ -1422,9 +1564,28 @@ const generateWhile = (scope, decl) => {
|
|
1422
1564
|
return out;
|
1423
1565
|
};
|
1424
1566
|
|
1567
|
+
const generateForOf = (scope, decl) => {
|
1568
|
+
const out = [];
|
1569
|
+
|
1570
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1571
|
+
depth.push('while');
|
1572
|
+
|
1573
|
+
out.push(...generate(scope, decl.test));
|
1574
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1575
|
+
depth.push('if');
|
1576
|
+
|
1577
|
+
out.push(...generate(scope, decl.body));
|
1578
|
+
|
1579
|
+
out.push([ Opcodes.br, 1 ]);
|
1580
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
1581
|
+
depth.pop(); depth.pop();
|
1582
|
+
|
1583
|
+
return out;
|
1584
|
+
};
|
1585
|
+
|
1425
1586
|
const getNearestLoop = () => {
|
1426
1587
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1427
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1588
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1428
1589
|
}
|
1429
1590
|
|
1430
1591
|
return -1;
|
@@ -1514,7 +1675,16 @@ const allocPage = reason => {
|
|
1514
1675
|
let ind = pages.size;
|
1515
1676
|
pages.set(reason, ind);
|
1516
1677
|
|
1517
|
-
if (
|
1678
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
|
1679
|
+
|
1680
|
+
return ind;
|
1681
|
+
};
|
1682
|
+
|
1683
|
+
const freePage = reason => {
|
1684
|
+
let ind = pages.get(reason);
|
1685
|
+
pages.delete(reason);
|
1686
|
+
|
1687
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1518
1688
|
|
1519
1689
|
return ind;
|
1520
1690
|
};
|
@@ -1578,12 +1748,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1578
1748
|
|
1579
1749
|
scope.memory = true;
|
1580
1750
|
|
1581
|
-
return out;
|
1751
|
+
return [ out, pointer ];
|
1582
1752
|
};
|
1583
1753
|
|
1584
1754
|
let arrays = new Map();
|
1585
1755
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1586
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype);
|
1756
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1587
1757
|
};
|
1588
1758
|
|
1589
1759
|
export const generateMember = (scope, decl, _global, _name) => {
|
@@ -1644,10 +1814,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1644
1814
|
|
1645
1815
|
// string
|
1646
1816
|
|
1647
|
-
const newOut = makeArray(scope, {
|
1817
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1648
1818
|
rawElements: new Array(1)
|
1649
1819
|
}, _global, _name, true, 'i16');
|
1650
|
-
const newPointer = arrays.get(_name ?? '$undeclared');
|
1651
1820
|
|
1652
1821
|
return [
|
1653
1822
|
// setup new/out array
|
@@ -1753,7 +1922,7 @@ const generateFunc = (scope, decl) => {
|
|
1753
1922
|
name,
|
1754
1923
|
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
1755
1924
|
returns: innerScope.returns,
|
1756
|
-
returnType: innerScope.returnType
|
1925
|
+
returnType: innerScope.returnType,
|
1757
1926
|
locals: innerScope.locals,
|
1758
1927
|
memory: innerScope.memory,
|
1759
1928
|
throws: innerScope.throws,
|
@@ -1912,10 +2081,9 @@ const internalConstrs = {
|
|
1912
2081
|
|
1913
2082
|
// new Array(n)
|
1914
2083
|
|
1915
|
-
makeArray(scope, {
|
2084
|
+
const [ , pointer ] = makeArray(scope, {
|
1916
2085
|
rawElements: new Array(0)
|
1917
2086
|
}, global, name, true);
|
1918
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
1919
2087
|
|
1920
2088
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
1921
2089
|
|
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");
|