porffor 0.0.0-8c0bdaa → 0.0.0-c743344
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 +3 -1
- package/compiler/codeGen.js +281 -83
- package/compiler/decompile.js +2 -2
- package/compiler/encoding.js +4 -2
- package/compiler/index.js +10 -1
- package/compiler/opt.js +34 -4
- package/compiler/prototype.js +2 -4
- package/compiler/sections.js +3 -2
- package/compiler/wrap.js +11 -2
- package/package.json +1 -1
- package/publish.js +3 -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,8 @@ 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'`)
|
80
|
+
- nullish coalescing operator (`??`)
|
79
81
|
|
80
82
|
### built-ins
|
81
83
|
|
@@ -106,7 +108,6 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
106
108
|
- arrays/strings inside arrays
|
107
109
|
- strings
|
108
110
|
- member setting
|
109
|
-
- equality
|
110
111
|
- more math operators (`**`, etc)
|
111
112
|
- `do { ... } while (...)`
|
112
113
|
- exceptions
|
@@ -135,6 +136,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
|
|
135
136
|
- `i64.extend_i32_s`, `i32.wrap_i64` -> ``
|
136
137
|
- `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
|
137
138
|
- `return`, `end` -> `end`
|
139
|
+
- change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
|
138
140
|
- remove some redundant sets/gets
|
139
141
|
- remove unneeded single just used vars
|
140
142
|
- remove unneeded blocks (no `br`s inside)
|
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
|
|
@@ -164,7 +174,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
164
174
|
}
|
165
175
|
|
166
176
|
if (asm[0] === 'memory') {
|
167
|
-
scope.memory = true;
|
168
177
|
allocPage('asm instrinsic');
|
169
178
|
// todo: add to store/load offset insts
|
170
179
|
continue;
|
@@ -278,7 +287,7 @@ const generateReturn = (scope, decl) => {
|
|
278
287
|
];
|
279
288
|
}
|
280
289
|
|
281
|
-
|
290
|
+
scope.returnType = getNodeType(scope, decl.argument);
|
282
291
|
|
283
292
|
return [
|
284
293
|
...generate(scope, decl.argument),
|
@@ -295,11 +304,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
295
304
|
return idx;
|
296
305
|
};
|
297
306
|
|
298
|
-
const performLogicOp = (scope, op, left, right) => {
|
307
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
299
308
|
const checks = {
|
300
|
-
'||':
|
301
|
-
'&&':
|
302
|
-
|
309
|
+
'||': falsy,
|
310
|
+
'&&': truthy,
|
311
|
+
'??': nullish
|
303
312
|
};
|
304
313
|
|
305
314
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -310,7 +319,8 @@ const performLogicOp = (scope, op, left, right) => {
|
|
310
319
|
return [
|
311
320
|
...left,
|
312
321
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
313
|
-
...checks[op],
|
322
|
+
...checks[op](scope, [], leftType),
|
323
|
+
Opcodes.i32_to,
|
314
324
|
[ Opcodes.if, valtypeBinary ],
|
315
325
|
...right,
|
316
326
|
[ Opcodes.else ],
|
@@ -325,15 +335,13 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
325
335
|
// todo: optimize by looking up names in arrays and using that if exists?
|
326
336
|
// todo: optimize this if using literals/known lengths?
|
327
337
|
|
328
|
-
scope.memory = true;
|
329
|
-
|
330
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
331
|
-
|
332
338
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
333
339
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
334
340
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
335
341
|
|
336
342
|
if (assign) {
|
343
|
+
const pointer = arrays.get(name ?? '$undeclared');
|
344
|
+
|
337
345
|
return [
|
338
346
|
// setup right
|
339
347
|
...right,
|
@@ -384,15 +392,12 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
384
392
|
|
385
393
|
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
386
394
|
|
387
|
-
|
395
|
+
// alloc/assign array
|
396
|
+
const [ , pointer ] = makeArray(scope, {
|
388
397
|
rawElements: new Array(0)
|
389
398
|
}, global, name, true, 'i16');
|
390
399
|
|
391
400
|
return [
|
392
|
-
// setup new/out array
|
393
|
-
...newOut,
|
394
|
-
[ Opcodes.drop ],
|
395
|
-
|
396
401
|
// setup left
|
397
402
|
...left,
|
398
403
|
Opcodes.i32_to_u,
|
@@ -458,75 +463,220 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
458
463
|
];
|
459
464
|
};
|
460
465
|
|
461
|
-
const
|
466
|
+
const compareStrings = (scope, left, right) => {
|
467
|
+
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
468
|
+
// todo: convert left and right to strings if not
|
469
|
+
// todo: optimize by looking up names in arrays and using that if exists?
|
470
|
+
// todo: optimize this if using literals/known lengths?
|
471
|
+
|
472
|
+
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
473
|
+
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
474
|
+
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
475
|
+
const rightLength = localTmp(scope, 'compare_right_length', Valtype.i32);
|
476
|
+
|
477
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
478
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
479
|
+
|
480
|
+
return [
|
481
|
+
// setup left
|
482
|
+
...left,
|
483
|
+
Opcodes.i32_to_u,
|
484
|
+
[ Opcodes.local_tee, leftPointer ],
|
485
|
+
|
486
|
+
// setup right
|
487
|
+
...right,
|
488
|
+
Opcodes.i32_to_u,
|
489
|
+
[ Opcodes.local_tee, rightPointer ],
|
490
|
+
|
491
|
+
// fast path: check leftPointer == rightPointer
|
492
|
+
// use if (block) for everything after to "return" a value early
|
493
|
+
[ Opcodes.i32_ne ],
|
494
|
+
[ Opcodes.if, Valtype.i32 ],
|
495
|
+
|
496
|
+
// get lengths
|
497
|
+
[ Opcodes.local_get, leftPointer ],
|
498
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
499
|
+
[ Opcodes.local_tee, leftLength ],
|
500
|
+
|
501
|
+
[ Opcodes.local_get, rightPointer ],
|
502
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
503
|
+
[ Opcodes.local_tee, rightLength ],
|
504
|
+
|
505
|
+
// fast path: check leftLength != rightLength
|
506
|
+
[ Opcodes.i32_ne ],
|
507
|
+
[ Opcodes.if, Blocktype.void ],
|
508
|
+
...number(0, Valtype.i32),
|
509
|
+
[ Opcodes.br, 1 ],
|
510
|
+
[ Opcodes.end ],
|
511
|
+
|
512
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
513
|
+
|
514
|
+
// setup index end as length * sizeof i16 (2)
|
515
|
+
// we do this instead of having to do mul/div each iter for perf™
|
516
|
+
[ Opcodes.local_get, leftLength ],
|
517
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
518
|
+
[ Opcodes.i32_mul ],
|
519
|
+
[ Opcodes.local_set, indexEnd ],
|
520
|
+
|
521
|
+
// iterate over each char and check if eq
|
522
|
+
[ Opcodes.loop, Blocktype.void ],
|
523
|
+
|
524
|
+
// fetch left
|
525
|
+
[ Opcodes.local_get, index ],
|
526
|
+
[ Opcodes.local_get, leftPointer ],
|
527
|
+
[ Opcodes.i32_add ],
|
528
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
529
|
+
|
530
|
+
// fetch right
|
531
|
+
[ Opcodes.local_get, index ],
|
532
|
+
[ Opcodes.local_get, rightPointer ],
|
533
|
+
[ Opcodes.i32_add ],
|
534
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
535
|
+
|
536
|
+
// not equal, "return" false
|
537
|
+
[ Opcodes.i32_ne ],
|
538
|
+
[ Opcodes.if, Blocktype.void ],
|
539
|
+
...number(0, Valtype.i32),
|
540
|
+
[ Opcodes.br, 2 ],
|
541
|
+
[ Opcodes.end ],
|
542
|
+
|
543
|
+
// index += sizeof i16 (2)
|
544
|
+
[ Opcodes.local_get, index ],
|
545
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
546
|
+
[ Opcodes.i32_add ],
|
547
|
+
[ Opcodes.local_tee, index ],
|
548
|
+
|
549
|
+
// if index != index end (length * sizeof 16), loop
|
550
|
+
[ Opcodes.local_get, indexEnd ],
|
551
|
+
[ Opcodes.i32_ne ],
|
552
|
+
[ Opcodes.br_if, 0 ],
|
553
|
+
[ Opcodes.end ],
|
554
|
+
|
555
|
+
// no failed checks, so true!
|
556
|
+
...number(1, Valtype.i32),
|
557
|
+
|
558
|
+
// pointers match, so true
|
559
|
+
[ Opcodes.else ],
|
560
|
+
...number(1, Valtype.i32),
|
561
|
+
[ Opcodes.end ],
|
562
|
+
|
563
|
+
// convert i32 result to valtype
|
564
|
+
// do not do as automatically added by binary exp gen for equality ops
|
565
|
+
// Opcodes.i32_from_u
|
566
|
+
];
|
567
|
+
};
|
568
|
+
|
569
|
+
const truthy = (scope, wasm, type) => {
|
462
570
|
// arrays are always truthy
|
463
571
|
if (type === TYPES._array) return [
|
464
572
|
...wasm,
|
465
573
|
[ Opcodes.drop ],
|
466
|
-
number(
|
574
|
+
...number(1)
|
467
575
|
];
|
468
576
|
|
469
577
|
if (type === TYPES.string) {
|
470
|
-
// if "" (length = 0)
|
578
|
+
// if not "" (length = 0)
|
471
579
|
return [
|
472
580
|
// pointer
|
473
581
|
...wasm,
|
582
|
+
Opcodes.i32_to_u,
|
474
583
|
|
475
584
|
// get length
|
476
585
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
477
586
|
|
478
|
-
// if length
|
479
|
-
[ Opcodes.i32_eqz ],
|
587
|
+
// if length != 0
|
588
|
+
/* [ Opcodes.i32_eqz ],
|
589
|
+
[ Opcodes.i32_eqz ], */
|
480
590
|
Opcodes.i32_from_u
|
481
591
|
]
|
482
592
|
}
|
483
593
|
|
484
|
-
// if
|
594
|
+
// if != 0
|
485
595
|
return [
|
486
596
|
...wasm,
|
487
597
|
|
488
|
-
|
489
|
-
Opcodes.
|
598
|
+
/* Opcodes.eqz,
|
599
|
+
[ Opcodes.i32_eqz ],
|
600
|
+
Opcodes.i32_from */
|
490
601
|
];
|
491
602
|
};
|
492
603
|
|
493
|
-
const
|
604
|
+
const falsy = (scope, wasm, type) => {
|
494
605
|
// arrays are always truthy
|
495
606
|
if (type === TYPES._array) return [
|
496
607
|
...wasm,
|
497
608
|
[ Opcodes.drop ],
|
498
|
-
number(
|
609
|
+
...number(0)
|
499
610
|
];
|
500
611
|
|
501
612
|
if (type === TYPES.string) {
|
502
|
-
// if
|
613
|
+
// if "" (length = 0)
|
503
614
|
return [
|
504
615
|
// pointer
|
505
616
|
...wasm,
|
617
|
+
Opcodes.i32_to_u,
|
506
618
|
|
507
619
|
// get length
|
508
620
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
509
621
|
|
510
|
-
// if length
|
511
|
-
|
512
|
-
[ Opcodes.i32_eqz ], */
|
622
|
+
// if length == 0
|
623
|
+
[ Opcodes.i32_eqz ],
|
513
624
|
Opcodes.i32_from_u
|
514
625
|
]
|
515
626
|
}
|
516
627
|
|
517
|
-
// if
|
628
|
+
// if = 0
|
518
629
|
return [
|
519
630
|
...wasm,
|
520
631
|
|
521
|
-
|
522
|
-
|
523
|
-
|
632
|
+
...Opcodes.eqz,
|
633
|
+
Opcodes.i32_from_u
|
634
|
+
];
|
635
|
+
};
|
636
|
+
|
637
|
+
const nullish = (scope, wasm, type) => {
|
638
|
+
// undefined
|
639
|
+
if (type === TYPES.undefined) return [
|
640
|
+
...wasm,
|
641
|
+
[ Opcodes.drop ],
|
642
|
+
...number(1)
|
643
|
+
];
|
644
|
+
|
645
|
+
// null (if object and = "0")
|
646
|
+
if (type === TYPES.object) return [
|
647
|
+
...wasm,
|
648
|
+
...Opcodes.eqz,
|
649
|
+
Opcodes.i32_from_u
|
650
|
+
];
|
651
|
+
|
652
|
+
// not
|
653
|
+
return [
|
654
|
+
...wasm,
|
655
|
+
[ Opcodes.drop ],
|
656
|
+
...number(0)
|
524
657
|
];
|
525
658
|
};
|
526
659
|
|
527
|
-
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$
|
660
|
+
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
528
661
|
if (op === '||' || op === '&&' || op === '??') {
|
529
|
-
return performLogicOp(scope, op, left, right);
|
662
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
663
|
+
}
|
664
|
+
|
665
|
+
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
666
|
+
|
667
|
+
// if strict (in)equal and known types mismatch, return false (===)/true (!==)
|
668
|
+
if ((op === '===' || op === '!==') && leftType && rightType && leftType !== rightType) {
|
669
|
+
return [
|
670
|
+
...left,
|
671
|
+
...right,
|
672
|
+
|
673
|
+
// drop values
|
674
|
+
[ Opcodes.drop ],
|
675
|
+
[ Opcodes.drop ],
|
676
|
+
|
677
|
+
// return false (===)/true (!==)
|
678
|
+
...number(op === '===' ? 0 : 1, Valtype.i32)
|
679
|
+
];
|
530
680
|
}
|
531
681
|
|
532
682
|
if (leftType === TYPES.string || rightType === TYPES.string) {
|
@@ -539,8 +689,20 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
539
689
|
if (!['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(op)) return number(NaN);
|
540
690
|
|
541
691
|
// else leave bool ops
|
542
|
-
// todo: convert string to number if string and number
|
543
|
-
// todo: string
|
692
|
+
// todo: convert string to number if string and number/bool
|
693
|
+
// todo: string (>|>=|<|<=) string
|
694
|
+
|
695
|
+
// string comparison
|
696
|
+
if (op === '===' || op === '==') {
|
697
|
+
return compareStrings(scope, left, right);
|
698
|
+
}
|
699
|
+
|
700
|
+
if (op === '!==' || op === '!=') {
|
701
|
+
return [
|
702
|
+
...compareStrings(scope, left, right),
|
703
|
+
[ Opcodes.i32_eqz ]
|
704
|
+
];
|
705
|
+
}
|
544
706
|
}
|
545
707
|
|
546
708
|
let ops = operatorOpcode[valtype][op];
|
@@ -569,13 +731,17 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
569
731
|
];
|
570
732
|
};
|
571
733
|
|
734
|
+
let binaryExpDepth = 0;
|
572
735
|
const generateBinaryExp = (scope, decl, _global, _name) => {
|
736
|
+
binaryExpDepth++;
|
737
|
+
|
573
738
|
const out = [
|
574
739
|
...performOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right), _global, _name)
|
575
740
|
];
|
576
741
|
|
577
742
|
if (valtype !== 'i32' && ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(decl.operator)) out.push(Opcodes.i32_from_u);
|
578
743
|
|
744
|
+
binaryExpDepth--;
|
579
745
|
return out;
|
580
746
|
};
|
581
747
|
|
@@ -634,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
634
800
|
};
|
635
801
|
|
636
802
|
const generateLogicExp = (scope, decl) => {
|
637
|
-
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right));
|
803
|
+
return performLogicOp(scope, decl.operator, generate(scope, decl.left), generate(scope, decl.right), getNodeType(scope, decl.left), getNodeType(scope, decl.right));
|
638
804
|
};
|
639
805
|
|
640
806
|
const TYPES = {
|
@@ -682,6 +848,7 @@ const getType = (scope, _name) => {
|
|
682
848
|
|
683
849
|
const getNodeType = (scope, node) => {
|
684
850
|
if (node.type === 'Literal') {
|
851
|
+
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
685
852
|
return TYPES[typeof node.value];
|
686
853
|
}
|
687
854
|
|
@@ -696,7 +863,7 @@ const getNodeType = (scope, node) => {
|
|
696
863
|
if (node.type === 'CallExpression' || node.type === 'NewExpression') {
|
697
864
|
const name = node.callee.name;
|
698
865
|
const func = funcs.find(x => x.name === name);
|
699
|
-
if (func) return func.returnType
|
866
|
+
if (func) return func.returnType;
|
700
867
|
|
701
868
|
if (builtinFuncs[name]) return TYPES[builtinFuncs[name].returnType ?? 'number'];
|
702
869
|
if (internalConstrs[name]) return internalConstrs[name].type;
|
@@ -721,9 +888,7 @@ const getNodeType = (scope, node) => {
|
|
721
888
|
protoFunc = prototypeFuncs[baseType]?.[func];
|
722
889
|
}
|
723
890
|
|
724
|
-
if (protoFunc) return protoFunc.returnType
|
725
|
-
|
726
|
-
return TYPES.number;
|
891
|
+
if (protoFunc) return protoFunc.returnType;
|
727
892
|
}
|
728
893
|
|
729
894
|
if (node.type === 'ExpressionStatement') {
|
@@ -755,9 +920,6 @@ const getNodeType = (scope, node) => {
|
|
755
920
|
|
756
921
|
if (objectType === TYPES.string && node.computed) return TYPES.string;
|
757
922
|
}
|
758
|
-
|
759
|
-
// default to number
|
760
|
-
return TYPES.number;
|
761
923
|
};
|
762
924
|
|
763
925
|
const generateLiteral = (scope, decl, global, name) => {
|
@@ -792,7 +954,7 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
792
954
|
|
793
955
|
return makeArray(scope, {
|
794
956
|
rawElements
|
795
|
-
}, global, name, false, 'i16');
|
957
|
+
}, global, name, false, 'i16')[0];
|
796
958
|
|
797
959
|
default:
|
798
960
|
return todo(`cannot generate literal of type ${typeof decl.value}`);
|
@@ -802,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
802
964
|
const countLeftover = wasm => {
|
803
965
|
let count = 0, depth = 0;
|
804
966
|
|
805
|
-
for (
|
967
|
+
for (let i = 0; i < wasm.length; i++) {
|
968
|
+
const inst = wasm[i];
|
806
969
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
807
970
|
if (inst[0] === Opcodes.if) count--;
|
808
971
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -906,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
906
1069
|
}
|
907
1070
|
|
908
1071
|
let out = [];
|
909
|
-
let protoFunc, protoName, baseType, baseName
|
1072
|
+
let protoFunc, protoName, baseType, baseName;
|
910
1073
|
// ident.func()
|
911
1074
|
if (name && name.startsWith('__')) {
|
912
1075
|
const spl = name.slice(2).split('_');
|
@@ -929,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
929
1092
|
|
930
1093
|
out = generate(scope, decl.callee.object);
|
931
1094
|
out.push([ Opcodes.drop ]);
|
1095
|
+
|
1096
|
+
baseName = [...arrays.keys()].pop();
|
932
1097
|
}
|
933
1098
|
|
934
1099
|
if (protoFunc) {
|
935
|
-
scope.memory = true;
|
936
|
-
|
937
1100
|
let pointer = arrays.get(baseName);
|
938
1101
|
|
939
1102
|
if (pointer == null) {
|
@@ -941,10 +1104,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
941
1104
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
942
1105
|
|
943
1106
|
// register array
|
944
|
-
makeArray(scope, {
|
1107
|
+
0, [ , pointer ] = makeArray(scope, {
|
945
1108
|
rawElements: new Array(0)
|
946
1109
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
947
|
-
pointer = arrays.get(baseName);
|
948
1110
|
|
949
1111
|
const [ local, isGlobal ] = lookupName(scope, baseName);
|
950
1112
|
|
@@ -980,10 +1142,9 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
980
1142
|
set: value => arrayUtil.setLength(pointer, value),
|
981
1143
|
setI32: value => arrayUtil.setLengthI32(pointer, value)
|
982
1144
|
}, generate(scope, decl.arguments[0] ?? DEFAULT_VALUE), protoLocal, (length, itemType) => {
|
983
|
-
|
1145
|
+
return makeArray(scope, {
|
984
1146
|
rawElements: new Array(length)
|
985
1147
|
}, _global, _name, true, itemType);
|
986
|
-
return [ out, arrays.get(_name ?? '$undeclared') ];
|
987
1148
|
}),
|
988
1149
|
[ Opcodes.end ]
|
989
1150
|
];
|
@@ -1041,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1041
1202
|
args = args.slice(0, func.params.length);
|
1042
1203
|
}
|
1043
1204
|
|
1044
|
-
if (func && func.memory) scope.memory = true;
|
1045
1205
|
if (func && func.throws) scope.throws = true;
|
1046
1206
|
|
1047
1207
|
for (const arg of args) {
|
@@ -1057,7 +1217,7 @@ const generateNew = (scope, decl, _global, _name) => {
|
|
1057
1217
|
// hack: basically treat this as a normal call for builtins for now
|
1058
1218
|
const name = mapName(decl.callee.name);
|
1059
1219
|
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)})`);
|
1220
|
+
if (!builtinFuncs[name]) return todo(`new statement is not supported yet`); // return todo(`new statement is not supported yet (new ${unhackName(name)})`);
|
1061
1221
|
|
1062
1222
|
return generateCall(scope, decl, _global, _name);
|
1063
1223
|
};
|
@@ -1165,8 +1325,6 @@ const generateAssign = (scope, decl) => {
|
|
1165
1325
|
const name = decl.left.object.name;
|
1166
1326
|
const pointer = arrays.get(name);
|
1167
1327
|
|
1168
|
-
scope.memory = true;
|
1169
|
-
|
1170
1328
|
const aotPointer = pointer != null;
|
1171
1329
|
|
1172
1330
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1217,8 +1375,27 @@ const generateAssign = (scope, decl) => {
|
|
1217
1375
|
];
|
1218
1376
|
}
|
1219
1377
|
|
1378
|
+
const op = decl.operator.slice(0, -1);
|
1379
|
+
if (op === '||' || op === '&&' || op === '??') {
|
1380
|
+
// todo: is this needed?
|
1381
|
+
// for logical assignment ops, it is not left @= right ~= left = left @ right
|
1382
|
+
// instead, left @ (left = right)
|
1383
|
+
// eg, x &&= y ~= x && (x = y)
|
1384
|
+
|
1385
|
+
return [
|
1386
|
+
...performOp(scope, op, [
|
1387
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1388
|
+
], [
|
1389
|
+
...generate(scope, decl.right),
|
1390
|
+
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1391
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1392
|
+
], getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1393
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1394
|
+
];
|
1395
|
+
}
|
1396
|
+
|
1220
1397
|
return [
|
1221
|
-
...performOp(scope,
|
1398
|
+
...performOp(scope, op, [ [ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ] ], generate(scope, decl.right), getType(scope, name), getNodeType(scope, decl.right), isGlobal, name, true),
|
1222
1399
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1223
1400
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1224
1401
|
];
|
@@ -1245,13 +1422,14 @@ const generateUnary = (scope, decl) => {
|
|
1245
1422
|
|
1246
1423
|
case '!':
|
1247
1424
|
// !=
|
1248
|
-
return falsy(scope, generate(scope, decl.argument));
|
1425
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1249
1426
|
|
1250
1427
|
case '~':
|
1428
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1251
1429
|
return [
|
1252
1430
|
...generate(scope, decl.argument),
|
1253
1431
|
Opcodes.i32_to,
|
1254
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1432
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1255
1433
|
[ Opcodes.i32_xor ],
|
1256
1434
|
Opcodes.i32_from
|
1257
1435
|
];
|
@@ -1289,7 +1467,7 @@ const generateUnary = (scope, decl) => {
|
|
1289
1467
|
return out;
|
1290
1468
|
|
1291
1469
|
case 'typeof':
|
1292
|
-
const type = getNodeType(scope, decl.argument);
|
1470
|
+
const type = getNodeType(scope, decl.argument) ?? TYPES.number;
|
1293
1471
|
|
1294
1472
|
// for custom types, just return object
|
1295
1473
|
if (type > 0xffffffffffff7) return number(TYPES.object);
|
@@ -1422,9 +1600,28 @@ const generateWhile = (scope, decl) => {
|
|
1422
1600
|
return out;
|
1423
1601
|
};
|
1424
1602
|
|
1603
|
+
const generateForOf = (scope, decl) => {
|
1604
|
+
const out = [];
|
1605
|
+
|
1606
|
+
out.push([ Opcodes.loop, Blocktype.void ]);
|
1607
|
+
depth.push('while');
|
1608
|
+
|
1609
|
+
out.push(...generate(scope, decl.test));
|
1610
|
+
out.push(Opcodes.i32_to, [ Opcodes.if, Blocktype.void ]);
|
1611
|
+
depth.push('if');
|
1612
|
+
|
1613
|
+
out.push(...generate(scope, decl.body));
|
1614
|
+
|
1615
|
+
out.push([ Opcodes.br, 1 ]);
|
1616
|
+
out.push([ Opcodes.end ], [ Opcodes.end ]);
|
1617
|
+
depth.pop(); depth.pop();
|
1618
|
+
|
1619
|
+
return out;
|
1620
|
+
};
|
1621
|
+
|
1425
1622
|
const getNearestLoop = () => {
|
1426
1623
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1427
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1624
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1428
1625
|
}
|
1429
1626
|
|
1430
1627
|
return -1;
|
@@ -1514,7 +1711,16 @@ const allocPage = reason => {
|
|
1514
1711
|
let ind = pages.size;
|
1515
1712
|
pages.set(reason, ind);
|
1516
1713
|
|
1517
|
-
if (
|
1714
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
|
1715
|
+
|
1716
|
+
return ind;
|
1717
|
+
};
|
1718
|
+
|
1719
|
+
const freePage = reason => {
|
1720
|
+
let ind = pages.get(reason);
|
1721
|
+
pages.delete(reason);
|
1722
|
+
|
1723
|
+
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
1518
1724
|
|
1519
1725
|
return ind;
|
1520
1726
|
};
|
@@ -1576,14 +1782,12 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1576
1782
|
// local value as pointer
|
1577
1783
|
out.push(...number(pointer));
|
1578
1784
|
|
1579
|
-
|
1580
|
-
|
1581
|
-
return out;
|
1785
|
+
return [ out, pointer ];
|
1582
1786
|
};
|
1583
1787
|
|
1584
1788
|
let arrays = new Map();
|
1585
1789
|
const generateArray = (scope, decl, global = false, name = '$undeclared', initEmpty = false) => {
|
1586
|
-
return makeArray(scope, decl, global, name, initEmpty, valtype);
|
1790
|
+
return makeArray(scope, decl, global, name, initEmpty, valtype)[0];
|
1587
1791
|
};
|
1588
1792
|
|
1589
1793
|
export const generateMember = (scope, decl, _global, _name) => {
|
@@ -1596,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1596
1800
|
const name = decl.object.name;
|
1597
1801
|
const pointer = arrays.get(name);
|
1598
1802
|
|
1599
|
-
scope.memory = true;
|
1600
|
-
|
1601
1803
|
const aotPointer = pointer != null;
|
1602
1804
|
|
1603
1805
|
return [
|
@@ -1617,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1617
1819
|
const name = decl.object.name;
|
1618
1820
|
const pointer = arrays.get(name);
|
1619
1821
|
|
1620
|
-
scope.memory = true;
|
1621
|
-
|
1622
1822
|
const aotPointer = pointer != null;
|
1623
1823
|
|
1624
1824
|
if (type === TYPES._array) {
|
@@ -1644,10 +1844,9 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1644
1844
|
|
1645
1845
|
// string
|
1646
1846
|
|
1647
|
-
const newOut = makeArray(scope, {
|
1847
|
+
const [ newOut, newPointer ] = makeArray(scope, {
|
1648
1848
|
rawElements: new Array(1)
|
1649
1849
|
}, _global, _name, true, 'i16');
|
1650
|
-
const newPointer = arrays.get(_name ?? '$undeclared');
|
1651
1850
|
|
1652
1851
|
return [
|
1653
1852
|
// setup new/out array
|
@@ -1729,6 +1928,7 @@ const generateFunc = (scope, decl) => {
|
|
1729
1928
|
locals: {},
|
1730
1929
|
localInd: 0,
|
1731
1930
|
returns: [ valtypeBinary ],
|
1931
|
+
returnType: null,
|
1732
1932
|
memory: false,
|
1733
1933
|
throws: false,
|
1734
1934
|
name
|
@@ -1753,9 +1953,8 @@ const generateFunc = (scope, decl) => {
|
|
1753
1953
|
name,
|
1754
1954
|
params: Object.values(innerScope.locals).slice(0, params.length).map(x => x.type),
|
1755
1955
|
returns: innerScope.returns,
|
1756
|
-
returnType: innerScope.returnType
|
1956
|
+
returnType: innerScope.returnType,
|
1757
1957
|
locals: innerScope.locals,
|
1758
|
-
memory: innerScope.memory,
|
1759
1958
|
throws: innerScope.throws,
|
1760
1959
|
index: currentFuncIndex++
|
1761
1960
|
};
|
@@ -1770,6 +1969,8 @@ const generateFunc = (scope, decl) => {
|
|
1770
1969
|
|
1771
1970
|
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1772
1971
|
wasm.push(...number(0), [ Opcodes.return ]);
|
1972
|
+
|
1973
|
+
if (func.returnType === null) func.returnType = TYPES.undefined;
|
1773
1974
|
}
|
1774
1975
|
|
1775
1976
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1780,9 +1981,7 @@ const generateFunc = (scope, decl) => {
|
|
1780
1981
|
if (local.type === Valtype.v128) {
|
1781
1982
|
vecParams++;
|
1782
1983
|
|
1783
|
-
/*
|
1784
|
-
|
1785
|
-
wasm.unshift( // add v128 load for param
|
1984
|
+
/* wasm.unshift( // add v128 load for param
|
1786
1985
|
[ Opcodes.i32_const, 0 ],
|
1787
1986
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1788
1987
|
[ Opcodes.local_set, local.idx ]
|
@@ -1912,10 +2111,9 @@ const internalConstrs = {
|
|
1912
2111
|
|
1913
2112
|
// new Array(n)
|
1914
2113
|
|
1915
|
-
makeArray(scope, {
|
2114
|
+
const [ , pointer ] = makeArray(scope, {
|
1916
2115
|
rawElements: new Array(0)
|
1917
2116
|
}, global, name, true);
|
1918
|
-
const pointer = arrays.get(name ?? '$undeclared');
|
1919
2117
|
|
1920
2118
|
const arg = decl.arguments[0] ?? DEFAULT_VALUE;
|
1921
2119
|
|
package/compiler/decompile.js
CHANGED
@@ -42,8 +42,8 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
42
42
|
out += ` ${read_ieee754_binary64(inst.slice(1))}`;
|
43
43
|
} else if (inst[0] === Opcodes.i32_const || inst[0] === Opcodes.i64_const) {
|
44
44
|
out += ` ${read_signedLEB128(inst.slice(1))}`;
|
45
|
-
} else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store) {
|
46
|
-
out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}
|
45
|
+
} else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store || inst[0] === Opcodes.i32_store16 || inst[0] === Opcodes.i32_load16_u) {
|
46
|
+
out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`;
|
47
47
|
} else for (const operand of inst.slice(1)) {
|
48
48
|
if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) {
|
49
49
|
if (operand === Blocktype.void) continue;
|
package/compiler/encoding.js
CHANGED
@@ -22,15 +22,15 @@ export const encodeLocal = (count, type) => [
|
|
22
22
|
type
|
23
23
|
];
|
24
24
|
|
25
|
+
// todo: this only works with integers within 32 bit range
|
25
26
|
export const signedLEB128 = n => {
|
26
|
-
|
27
|
+
n |= 0;
|
27
28
|
|
28
29
|
// just input for small numbers (for perf as common)
|
29
30
|
if (n >= 0 && n <= 63) return [ n ];
|
30
31
|
if (n >= -64 && n <= 0) return [ 128 + n ];
|
31
32
|
|
32
33
|
const buffer = [];
|
33
|
-
n |= 0;
|
34
34
|
|
35
35
|
while (true) {
|
36
36
|
let byte = n & 0x7f;
|
@@ -50,6 +50,8 @@ export const signedLEB128 = n => {
|
|
50
50
|
};
|
51
51
|
|
52
52
|
export const unsignedLEB128 = n => {
|
53
|
+
n |= 0;
|
54
|
+
|
53
55
|
// just input for small numbers (for perf as common)
|
54
56
|
if (n >= 0 && n <= 127) return [ n ];
|
55
57
|
|
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
@@ -1,5 +1,6 @@
|
|
1
1
|
import { Opcodes, Valtype } from "./wasmSpec.js";
|
2
2
|
import { number } from "./embedding.js";
|
3
|
+
import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
|
3
4
|
|
4
5
|
// deno compat
|
5
6
|
if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
|
@@ -20,7 +21,7 @@ export default (funcs, globals) => {
|
|
20
21
|
if (optLevel === 0) return;
|
21
22
|
|
22
23
|
const tailCall = process.argv.includes('-tail-call');
|
23
|
-
if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
|
24
|
+
if (tailCall) log('opt', 'warning: tail call proposal is not widely implemented! (you used -tail-call)');
|
24
25
|
|
25
26
|
if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
|
26
27
|
// inline pass (very WIP)
|
@@ -95,7 +96,6 @@ export default (funcs, globals) => {
|
|
95
96
|
}
|
96
97
|
|
97
98
|
if (t.index > c.index) t.index--; // adjust index if after removed func
|
98
|
-
if (c.memory) t.memory = true;
|
99
99
|
}
|
100
100
|
|
101
101
|
funcs.splice(funcs.indexOf(c), 1); // remove func from funcs
|
@@ -213,9 +213,9 @@ export default (funcs, globals) => {
|
|
213
213
|
// i32.const 0
|
214
214
|
// drop
|
215
215
|
// -->
|
216
|
-
// <nothing
|
216
|
+
// <nothing>
|
217
217
|
|
218
|
-
wasm.splice(i - 1, 2); // remove
|
218
|
+
wasm.splice(i - 1, 2); // remove these inst
|
219
219
|
i -= 2;
|
220
220
|
continue;
|
221
221
|
}
|
@@ -259,6 +259,36 @@ export default (funcs, globals) => {
|
|
259
259
|
continue;
|
260
260
|
}
|
261
261
|
|
262
|
+
if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
|
263
|
+
// change const and immediate i32 convert to i32 const
|
264
|
+
// f64.const 0
|
265
|
+
// i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
|
266
|
+
// -->
|
267
|
+
// i32.const 0
|
268
|
+
|
269
|
+
wasm[i - 1] = number((valtype === 'f64' ? read_ieee754_binary64 : read_signedLEB128)(lastInst.slice(1)), Valtype.i32)[0]; // f64.const -> i32.const
|
270
|
+
|
271
|
+
wasm.splice(i, 1); // remove this inst
|
272
|
+
i--;
|
273
|
+
if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
|
274
|
+
continue;
|
275
|
+
}
|
276
|
+
|
277
|
+
if (lastInst[0] === Opcodes.i32_const && (inst === Opcodes.i32_from || inst === Opcodes.i32_from_u)) {
|
278
|
+
// change i32 const and immediate convert to const (opposite way of previous)
|
279
|
+
// i32.const 0
|
280
|
+
// f64.convert_i32_s || f64.convert_i32_u
|
281
|
+
// -->
|
282
|
+
// f64.const 0
|
283
|
+
|
284
|
+
wasm[i - 1] = number(read_signedLEB128(lastInst.slice(1)))[0]; // i32.const -> f64.const
|
285
|
+
|
286
|
+
wasm.splice(i, 1); // remove this inst
|
287
|
+
i--;
|
288
|
+
if (optLog) log('opt', `converted i32 const -> convert into const`);
|
289
|
+
continue;
|
290
|
+
}
|
291
|
+
|
262
292
|
if (tailCall && lastInst[0] === Opcodes.call && inst[0] === Opcodes.return) {
|
263
293
|
// replace call, return with tail calls (return_call)
|
264
294
|
// call X
|
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/publish.js
CHANGED
@@ -6,6 +6,8 @@ const rev = fs.readFileSync('.git/refs/heads/main', 'utf8').trim().slice(0, 7);
|
|
6
6
|
const packageJson = fs.readFileSync('package.json', 'utf8');
|
7
7
|
fs.writeFileSync('package.json', packageJson.replace('"0.0.0"', `"0.0.0-${rev}"`));
|
8
8
|
|
9
|
+
console.log(rev, packageJson);
|
10
|
+
|
9
11
|
execSync(`npm publish`, { stdio: 'inherit' });
|
10
12
|
|
11
|
-
|
13
|
+
fs.writeFileSync('package.json', packageJson);
|
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
|
|
@@ -17,7 +10,7 @@ globalThis.valtype = 'f64';
|
|
17
10
|
const valtypeOpt = process.argv.find(x => x.startsWith('-valtype='));
|
18
11
|
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
19
12
|
|
20
|
-
console.log(`welcome to porffor rev ${rev}`);
|
13
|
+
console.log(`welcome to porffor rev ${rev.slice(0, 7)}`);
|
21
14
|
console.log(`info: using opt ${process.argv.find(x => x.startsWith('-O')) ?? '-O1'} and valtype ${valtype}`);
|
22
15
|
console.log();
|
23
16
|
|
@@ -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
|
-
prev = toRun + ';\n';
|
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,7 +59,7 @@ 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
|
-
|
62
|
+
if (!alwaysPrev && (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function '))) prev = toRun + ';\n';
|
68
63
|
// prev = toRun + ';\n';
|
69
64
|
};
|
70
65
|
|
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");
|