porffor 0.0.0-745e995 → 0.0.0-758fed5
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 +25 -5
- package/compiler/codeGen.js +198 -98
- 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 +27 -2
- package/compiler/wrap.js +11 -2
- package/package.json +1 -1
- package/runner/index.js +19 -0
- package/runner/repl.js +6 -11
- 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
|
|
@@ -99,18 +101,29 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
99
101
|
- intrinsic functions (see below)
|
100
102
|
- inlining wasm via ``asm`...``\` "macro"
|
101
103
|
|
102
|
-
##
|
104
|
+
## todo
|
105
|
+
no particular order and no guarentees, just what could happen soon™
|
106
|
+
|
103
107
|
- arrays
|
104
108
|
- member setting (`arr[0] = 2`)
|
105
109
|
- more of `Array` prototype
|
106
110
|
- arrays/strings inside arrays
|
111
|
+
- destructuring
|
112
|
+
- for .. of
|
107
113
|
- strings
|
108
114
|
- member setting
|
109
|
-
|
115
|
+
- objects
|
116
|
+
- basic object expressions (eg `{}`, `{ a: 0 }`)
|
117
|
+
- wasm
|
118
|
+
- *basic* wasm engine (interpreter) in js
|
119
|
+
- regex
|
120
|
+
- *basic* regex engine (in wasm compiled aot or js interpreter?)
|
110
121
|
- more math operators (`**`, etc)
|
111
122
|
- `do { ... } while (...)`
|
123
|
+
- rewrite `console.log` to work with strings/arrays
|
112
124
|
- exceptions
|
113
|
-
-
|
125
|
+
- rewrite to use actual strings (optional?)
|
126
|
+
- `try { } finally { }`
|
114
127
|
- rethrowing inside catch
|
115
128
|
- optimizations
|
116
129
|
- rewrite local indexes per func for smallest local header and remove unused idxs
|
@@ -118,6 +131,11 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
118
131
|
- remove const ifs (`if (true)`, etc)
|
119
132
|
- use data segments for initing arrays
|
120
133
|
|
134
|
+
## porfformance
|
135
|
+
*for the things it supports*, porffor is blazingly faster compared to most interpreters, and engines running without JIT. for those with JIT, it is not that much slower like a traditional interpreter would be.
|
136
|
+
|
137
|
+

|
138
|
+
|
121
139
|
## test262
|
122
140
|
porffor can run test262 via some hacks/transforms which remove unsupported features whilst still doing the same asserts (eg simpler error messages using literals only). it currently passes >10% (see latest commit desc for latest and details). use `node test262` to test, it will also show a difference of overall results between the last commit and current results.
|
123
141
|
|
@@ -135,6 +153,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
|
|
135
153
|
- `i64.extend_i32_s`, `i32.wrap_i64` -> ``
|
136
154
|
- `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
|
137
155
|
- `return`, `end` -> `end`
|
156
|
+
- change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
|
138
157
|
- remove some redundant sets/gets
|
139
158
|
- remove unneeded single just used vars
|
140
159
|
- remove unneeded blocks (no `br`s inside)
|
@@ -190,11 +209,12 @@ you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ..
|
|
190
209
|
- `-no-run` to not run wasm output, just compile
|
191
210
|
- `-opt-log` to log some opts
|
192
211
|
- `-code-log` to log some codegen (you probably want `-funcs`)
|
193
|
-
- `-funcs` to log funcs
|
212
|
+
- `-funcs` to log funcs
|
194
213
|
- `-opt-funcs` to log funcs after opt
|
195
214
|
- `-sections` to log sections as hex
|
196
215
|
- `-opt-no-inline` to not inline any funcs
|
197
|
-
- `-tail-call` to enable tail calls (not widely implemented)
|
216
|
+
- `-tail-call` to enable tail calls (experimental + not widely implemented)
|
217
|
+
- `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
|
198
218
|
|
199
219
|
## vscode extension
|
200
220
|
there is a vscode extension in `porffor-for-vscode` which tweaks js syntax highlighting to be nicer with porffor features (eg highlighting wasm inside of inline asm).
|
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,8 +335,6 @@ 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
338
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
331
339
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
332
340
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
@@ -455,38 +463,37 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
455
463
|
];
|
456
464
|
};
|
457
465
|
|
458
|
-
const compareStrings = (scope, left, right
|
466
|
+
const compareStrings = (scope, left, right) => {
|
459
467
|
// todo: this should be rewritten into a built-in/func: String.prototype.concat
|
460
468
|
// todo: convert left and right to strings if not
|
461
469
|
// todo: optimize by looking up names in arrays and using that if exists?
|
462
470
|
// todo: optimize this if using literals/known lengths?
|
463
471
|
|
464
|
-
|
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);
|
465
476
|
|
466
|
-
const
|
467
|
-
const
|
468
|
-
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
469
|
-
const leftPointer = localTmp(scope, 'concat_left_pointer', Valtype.i32);
|
470
|
-
|
471
|
-
// alloc/assign array
|
472
|
-
const [ , pointer ] = makeArray(scope, {
|
473
|
-
rawElements: new Array(0)
|
474
|
-
}, global, name, true, 'i16');
|
477
|
+
const index = localTmp(scope, 'compare_index', Valtype.i32);
|
478
|
+
const indexEnd = localTmp(scope, 'compare_index_end', Valtype.i32);
|
475
479
|
|
476
480
|
return [
|
477
481
|
// setup left
|
478
482
|
...left,
|
479
483
|
Opcodes.i32_to_u,
|
480
|
-
[ Opcodes.
|
484
|
+
[ Opcodes.local_tee, leftPointer ],
|
481
485
|
|
482
486
|
// setup right
|
483
487
|
...right,
|
484
488
|
Opcodes.i32_to_u,
|
485
|
-
[ Opcodes.
|
489
|
+
[ Opcodes.local_tee, rightPointer ],
|
486
490
|
|
487
|
-
//
|
488
|
-
|
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 ],
|
489
495
|
|
496
|
+
// get lengths
|
490
497
|
[ Opcodes.local_get, leftPointer ],
|
491
498
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
492
499
|
[ Opcodes.local_tee, leftLength ],
|
@@ -495,119 +502,164 @@ const compareStrings = (scope, left, right, global, name) => {
|
|
495
502
|
[ Opcodes.i32_load, Math.log2(ValtypeSize[valtype]) - 1, ...unsignedLEB128(0) ],
|
496
503
|
[ Opcodes.local_tee, rightLength ],
|
497
504
|
|
498
|
-
|
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 ],
|
499
511
|
|
500
|
-
//
|
501
|
-
[ Opcodes.i32_store, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(pointer) ],
|
512
|
+
// no fast path for length = 0 as it would probably be slower for most of the time?
|
502
513
|
|
503
|
-
//
|
504
|
-
//
|
505
|
-
|
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 ],
|
506
520
|
|
507
|
-
//
|
521
|
+
// iterate over each char and check if eq
|
522
|
+
[ Opcodes.loop, Blocktype.void ],
|
523
|
+
|
524
|
+
// fetch left
|
525
|
+
[ Opcodes.local_get, index ],
|
508
526
|
[ Opcodes.local_get, leftPointer ],
|
509
|
-
...number(ValtypeSize.i32, Valtype.i32),
|
510
527
|
[ Opcodes.i32_add ],
|
528
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(ValtypeSize.i32) ],
|
511
529
|
|
512
|
-
//
|
513
|
-
|
514
|
-
[
|
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) ],
|
515
535
|
|
516
|
-
//
|
517
|
-
|
518
|
-
|
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 ],
|
519
542
|
|
520
|
-
|
543
|
+
// index += sizeof i16 (2)
|
544
|
+
[ Opcodes.local_get, index ],
|
521
545
|
...number(ValtypeSize.i16, Valtype.i32),
|
522
|
-
[ Opcodes.i32_mul ],
|
523
546
|
[ Opcodes.i32_add ],
|
547
|
+
[ Opcodes.local_tee, index ],
|
524
548
|
|
525
|
-
//
|
526
|
-
[ Opcodes.local_get,
|
527
|
-
|
528
|
-
[ Opcodes.
|
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 ],
|
529
554
|
|
530
|
-
//
|
531
|
-
|
532
|
-
...number(ValtypeSize.i16, Valtype.i32),
|
533
|
-
[ Opcodes.i32_mul ],
|
555
|
+
// no failed checks, so true!
|
556
|
+
...number(1, Valtype.i32),
|
534
557
|
|
535
|
-
|
558
|
+
// pointers match, so true
|
559
|
+
[ Opcodes.else ],
|
560
|
+
...number(1, Valtype.i32),
|
561
|
+
[ Opcodes.end ],
|
536
562
|
|
537
|
-
//
|
538
|
-
|
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
|
539
566
|
];
|
540
567
|
};
|
541
568
|
|
542
|
-
const
|
569
|
+
const truthy = (scope, wasm, type) => {
|
543
570
|
// arrays are always truthy
|
544
571
|
if (type === TYPES._array) return [
|
545
572
|
...wasm,
|
546
573
|
[ Opcodes.drop ],
|
547
|
-
number(
|
574
|
+
...number(1)
|
548
575
|
];
|
549
576
|
|
550
577
|
if (type === TYPES.string) {
|
551
|
-
// if "" (length = 0)
|
578
|
+
// if not "" (length = 0)
|
552
579
|
return [
|
553
580
|
// pointer
|
554
581
|
...wasm,
|
582
|
+
Opcodes.i32_to_u,
|
555
583
|
|
556
584
|
// get length
|
557
585
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
558
586
|
|
559
|
-
// if length
|
560
|
-
[ Opcodes.i32_eqz ],
|
587
|
+
// if length != 0
|
588
|
+
/* [ Opcodes.i32_eqz ],
|
589
|
+
[ Opcodes.i32_eqz ], */
|
561
590
|
Opcodes.i32_from_u
|
562
591
|
]
|
563
592
|
}
|
564
593
|
|
565
|
-
// if
|
594
|
+
// if != 0
|
566
595
|
return [
|
567
596
|
...wasm,
|
568
597
|
|
569
|
-
|
570
|
-
Opcodes.
|
598
|
+
/* Opcodes.eqz,
|
599
|
+
[ Opcodes.i32_eqz ],
|
600
|
+
Opcodes.i32_from */
|
571
601
|
];
|
572
602
|
};
|
573
603
|
|
574
|
-
const
|
604
|
+
const falsy = (scope, wasm, type) => {
|
575
605
|
// arrays are always truthy
|
576
606
|
if (type === TYPES._array) return [
|
577
607
|
...wasm,
|
578
608
|
[ Opcodes.drop ],
|
579
|
-
number(
|
609
|
+
...number(0)
|
580
610
|
];
|
581
611
|
|
582
612
|
if (type === TYPES.string) {
|
583
|
-
// if
|
613
|
+
// if "" (length = 0)
|
584
614
|
return [
|
585
615
|
// pointer
|
586
616
|
...wasm,
|
617
|
+
Opcodes.i32_to_u,
|
587
618
|
|
588
619
|
// get length
|
589
620
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
590
621
|
|
591
|
-
// if length
|
592
|
-
|
593
|
-
[ Opcodes.i32_eqz ], */
|
622
|
+
// if length == 0
|
623
|
+
[ Opcodes.i32_eqz ],
|
594
624
|
Opcodes.i32_from_u
|
595
625
|
]
|
596
626
|
}
|
597
627
|
|
598
|
-
// if
|
628
|
+
// if = 0
|
599
629
|
return [
|
600
630
|
...wasm,
|
601
631
|
|
602
|
-
|
603
|
-
|
604
|
-
|
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)
|
605
657
|
];
|
606
658
|
};
|
607
659
|
|
608
660
|
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
609
661
|
if (op === '||' || op === '&&' || op === '??') {
|
610
|
-
return performLogicOp(scope, op, left, right);
|
662
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
611
663
|
}
|
612
664
|
|
613
665
|
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
@@ -640,9 +692,16 @@ const performOp = (scope, op, left, right, leftType, rightType, _global = false,
|
|
640
692
|
// todo: convert string to number if string and number/bool
|
641
693
|
// todo: string (>|>=|<|<=) string
|
642
694
|
|
643
|
-
// string
|
644
|
-
if (op === '===') {
|
695
|
+
// string comparison
|
696
|
+
if (op === '===' || op === '==') {
|
697
|
+
return compareStrings(scope, left, right);
|
698
|
+
}
|
645
699
|
|
700
|
+
if (op === '!==' || op === '!=') {
|
701
|
+
return [
|
702
|
+
...compareStrings(scope, left, right),
|
703
|
+
[ Opcodes.i32_eqz ]
|
704
|
+
];
|
646
705
|
}
|
647
706
|
}
|
648
707
|
|
@@ -741,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
741
800
|
};
|
742
801
|
|
743
802
|
const generateLogicExp = (scope, decl) => {
|
744
|
-
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));
|
745
804
|
};
|
746
805
|
|
747
806
|
const TYPES = {
|
@@ -789,6 +848,7 @@ const getType = (scope, _name) => {
|
|
789
848
|
|
790
849
|
const getNodeType = (scope, node) => {
|
791
850
|
if (node.type === 'Literal') {
|
851
|
+
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
792
852
|
return TYPES[typeof node.value];
|
793
853
|
}
|
794
854
|
|
@@ -904,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
904
964
|
const countLeftover = wasm => {
|
905
965
|
let count = 0, depth = 0;
|
906
966
|
|
907
|
-
for (
|
967
|
+
for (let i = 0; i < wasm.length; i++) {
|
968
|
+
const inst = wasm[i];
|
908
969
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
909
970
|
if (inst[0] === Opcodes.if) count--;
|
910
971
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1008,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1008
1069
|
}
|
1009
1070
|
|
1010
1071
|
let out = [];
|
1011
|
-
let protoFunc, protoName, baseType, baseName
|
1072
|
+
let protoFunc, protoName, baseType, baseName;
|
1012
1073
|
// ident.func()
|
1013
1074
|
if (name && name.startsWith('__')) {
|
1014
1075
|
const spl = name.slice(2).split('_');
|
@@ -1031,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1031
1092
|
|
1032
1093
|
out = generate(scope, decl.callee.object);
|
1033
1094
|
out.push([ Opcodes.drop ]);
|
1095
|
+
|
1096
|
+
baseName = [...arrays.keys()].pop();
|
1034
1097
|
}
|
1035
1098
|
|
1036
1099
|
if (protoFunc) {
|
1037
|
-
scope.memory = true;
|
1038
|
-
|
1039
1100
|
let pointer = arrays.get(baseName);
|
1040
1101
|
|
1041
1102
|
if (pointer == null) {
|
@@ -1043,7 +1104,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1043
1104
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
1044
1105
|
|
1045
1106
|
// register array
|
1046
|
-
|
1107
|
+
0, [ , pointer ] = makeArray(scope, {
|
1047
1108
|
rawElements: new Array(0)
|
1048
1109
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
1049
1110
|
|
@@ -1141,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1141
1202
|
args = args.slice(0, func.params.length);
|
1142
1203
|
}
|
1143
1204
|
|
1144
|
-
if (func && func.memory) scope.memory = true;
|
1145
1205
|
if (func && func.throws) scope.throws = true;
|
1146
1206
|
|
1147
1207
|
for (const arg of args) {
|
@@ -1157,7 +1217,7 @@ const generateNew = (scope, decl, _global, _name) => {
|
|
1157
1217
|
// hack: basically treat this as a normal call for builtins for now
|
1158
1218
|
const name = mapName(decl.callee.name);
|
1159
1219
|
if (internalConstrs[name]) return internalConstrs[name].generate(scope, decl, _global, _name);
|
1160
|
-
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)})`);
|
1161
1221
|
|
1162
1222
|
return generateCall(scope, decl, _global, _name);
|
1163
1223
|
};
|
@@ -1265,8 +1325,6 @@ const generateAssign = (scope, decl) => {
|
|
1265
1325
|
const name = decl.left.object.name;
|
1266
1326
|
const pointer = arrays.get(name);
|
1267
1327
|
|
1268
|
-
scope.memory = true;
|
1269
|
-
|
1270
1328
|
const aotPointer = pointer != null;
|
1271
1329
|
|
1272
1330
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1317,8 +1375,27 @@ const generateAssign = (scope, decl) => {
|
|
1317
1375
|
];
|
1318
1376
|
}
|
1319
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
|
+
|
1320
1397
|
return [
|
1321
|
-
...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),
|
1322
1399
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1323
1400
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1324
1401
|
];
|
@@ -1345,13 +1422,14 @@ const generateUnary = (scope, decl) => {
|
|
1345
1422
|
|
1346
1423
|
case '!':
|
1347
1424
|
// !=
|
1348
|
-
return falsy(scope, generate(scope, decl.argument));
|
1425
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1349
1426
|
|
1350
1427
|
case '~':
|
1428
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1351
1429
|
return [
|
1352
1430
|
...generate(scope, decl.argument),
|
1353
1431
|
Opcodes.i32_to,
|
1354
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1432
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1355
1433
|
[ Opcodes.i32_xor ],
|
1356
1434
|
Opcodes.i32_from
|
1357
1435
|
];
|
@@ -1522,9 +1600,28 @@ const generateWhile = (scope, decl) => {
|
|
1522
1600
|
return out;
|
1523
1601
|
};
|
1524
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
|
+
|
1525
1622
|
const getNearestLoop = () => {
|
1526
1623
|
for (let i = depth.length - 1; i >= 0; i--) {
|
1527
|
-
if (depth[i] === 'while' || depth[i] === 'for') return i;
|
1624
|
+
if (depth[i] === 'while' || depth[i] === 'for' || depth[i] === 'forof') return i;
|
1528
1625
|
}
|
1529
1626
|
|
1530
1627
|
return -1;
|
@@ -1614,7 +1711,16 @@ const allocPage = reason => {
|
|
1614
1711
|
let ind = pages.size;
|
1615
1712
|
pages.set(reason, ind);
|
1616
1713
|
|
1617
|
-
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}`);
|
1618
1724
|
|
1619
1725
|
return ind;
|
1620
1726
|
};
|
@@ -1676,8 +1782,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1676
1782
|
// local value as pointer
|
1677
1783
|
out.push(...number(pointer));
|
1678
1784
|
|
1679
|
-
scope.memory = true;
|
1680
|
-
|
1681
1785
|
return [ out, pointer ];
|
1682
1786
|
};
|
1683
1787
|
|
@@ -1696,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1696
1800
|
const name = decl.object.name;
|
1697
1801
|
const pointer = arrays.get(name);
|
1698
1802
|
|
1699
|
-
scope.memory = true;
|
1700
|
-
|
1701
1803
|
const aotPointer = pointer != null;
|
1702
1804
|
|
1703
1805
|
return [
|
@@ -1717,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1717
1819
|
const name = decl.object.name;
|
1718
1820
|
const pointer = arrays.get(name);
|
1719
1821
|
|
1720
|
-
scope.memory = true;
|
1721
|
-
|
1722
1822
|
const aotPointer = pointer != null;
|
1723
1823
|
|
1724
1824
|
if (type === TYPES._array) {
|
@@ -1828,6 +1928,7 @@ const generateFunc = (scope, decl) => {
|
|
1828
1928
|
locals: {},
|
1829
1929
|
localInd: 0,
|
1830
1930
|
returns: [ valtypeBinary ],
|
1931
|
+
returnType: null,
|
1831
1932
|
memory: false,
|
1832
1933
|
throws: false,
|
1833
1934
|
name
|
@@ -1854,7 +1955,6 @@ const generateFunc = (scope, decl) => {
|
|
1854
1955
|
returns: innerScope.returns,
|
1855
1956
|
returnType: innerScope.returnType,
|
1856
1957
|
locals: innerScope.locals,
|
1857
|
-
memory: innerScope.memory,
|
1858
1958
|
throws: innerScope.throws,
|
1859
1959
|
index: currentFuncIndex++
|
1860
1960
|
};
|
@@ -1869,6 +1969,8 @@ const generateFunc = (scope, decl) => {
|
|
1869
1969
|
|
1870
1970
|
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1871
1971
|
wasm.push(...number(0), [ Opcodes.return ]);
|
1972
|
+
|
1973
|
+
if (func.returnType === null) func.returnType = TYPES.undefined;
|
1872
1974
|
}
|
1873
1975
|
|
1874
1976
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1879,9 +1981,7 @@ const generateFunc = (scope, decl) => {
|
|
1879
1981
|
if (local.type === Valtype.v128) {
|
1880
1982
|
vecParams++;
|
1881
1983
|
|
1882
|
-
/*
|
1883
|
-
|
1884
|
-
wasm.unshift( // add v128 load for param
|
1984
|
+
/* wasm.unshift( // add v128 load for param
|
1885
1985
|
[ Opcodes.i32_const, 0 ],
|
1886
1986
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1887
1987
|
[ Opcodes.local_set, local.idx ]
|
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
@@ -8,11 +8,26 @@ const createSection = (type, data) => [
|
|
8
8
|
...encodeVector(data)
|
9
9
|
];
|
10
10
|
|
11
|
+
const customSection = (name, data) => [
|
12
|
+
Section.custom,
|
13
|
+
...encodeVector([...encodeString(name), ...data])
|
14
|
+
];
|
15
|
+
|
16
|
+
const chHint = (topTier, baselineTier, strategy) => {
|
17
|
+
// 1 byte of 4 2 bit components: spare, top tier, baseline tier, compilation strategy
|
18
|
+
// tiers: 0x00 = default, 0x01 = baseline (liftoff), 0x02 = optimized (turbofan)
|
19
|
+
// strategy: 0x00 = default, 0x01 = lazy, 0x02 = eager, 0x03 = lazy baseline, eager top tier
|
20
|
+
return (strategy | (baselineTier << 2) | (topTier << 4));
|
21
|
+
};
|
22
|
+
|
11
23
|
export default (funcs, globals, tags, pages, flags) => {
|
12
24
|
const types = [], typeCache = {};
|
13
25
|
|
14
26
|
const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
|
15
27
|
|
28
|
+
const compileHints = process.argv.includes('-compile-hints');
|
29
|
+
if (compileHints) log('sections', 'warning: compile hints is V8 only w/ experimental arg! (you used -compile-hints)');
|
30
|
+
|
16
31
|
const getType = (params, returns) => {
|
17
32
|
const hash = `${params.join(',')}_${returns.join(',')}`;
|
18
33
|
if (optLog) log('sections', `getType(${JSON.stringify(params)}, ${JSON.stringify(returns)}) -> ${hash} | cache: ${typeCache[hash]}`);
|
@@ -36,7 +51,7 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
36
51
|
// tree shake imports
|
37
52
|
for (const f of funcs) {
|
38
53
|
for (const inst of f.wasm) {
|
39
|
-
if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
|
54
|
+
if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] < importedFuncs.length) {
|
40
55
|
const idx = inst[1];
|
41
56
|
const func = importedFuncs[idx];
|
42
57
|
|
@@ -51,10 +66,11 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
51
66
|
// fix call indexes for non-imports
|
52
67
|
const delta = importedFuncs.length - importFuncs.length;
|
53
68
|
for (const f of funcs) {
|
69
|
+
f.originalIndex = f.index;
|
54
70
|
f.index -= delta;
|
55
71
|
|
56
72
|
for (const inst of f.wasm) {
|
57
|
-
if (inst[0] === Opcodes.call && inst[1] >= importedFuncs.length) {
|
73
|
+
if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
|
58
74
|
inst[1] -= delta;
|
59
75
|
}
|
60
76
|
}
|
@@ -73,6 +89,14 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
73
89
|
encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
|
74
90
|
);
|
75
91
|
|
92
|
+
// compilation hints section - unspec v8 only
|
93
|
+
// https://github.com/WebAssembly/design/issues/1473#issuecomment-1431274746
|
94
|
+
const chSection = !compileHints ? [] : customSection(
|
95
|
+
'compilationHints',
|
96
|
+
// for now just do everything as optimise eager
|
97
|
+
encodeVector(funcs.map(_ => chHint(0x02, 0x02, 0x02)))
|
98
|
+
);
|
99
|
+
|
76
100
|
const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
|
77
101
|
Section.global,
|
78
102
|
encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
|
@@ -145,6 +169,7 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
145
169
|
...typeSection,
|
146
170
|
...importSection,
|
147
171
|
...funcSection,
|
172
|
+
...chSection,
|
148
173
|
...memorySection,
|
149
174
|
...tagSection,
|
150
175
|
...globalSection,
|
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
@@ -3,8 +3,26 @@
|
|
3
3
|
import compile from '../compiler/wrap.js';
|
4
4
|
import fs from 'node:fs';
|
5
5
|
|
6
|
+
if (process.argv.includes('-compile-hints')) {
|
7
|
+
const v8 = await import('node:v8');
|
8
|
+
v8.setFlagsFromString(`--experimental-wasm-compilation-hints`);
|
9
|
+
|
10
|
+
// see also these flags:
|
11
|
+
// --experimental-wasm-branch-hinting
|
12
|
+
// --experimental-wasm-extended-const
|
13
|
+
// --experimental-wasm-inlining (?)
|
14
|
+
// --experimental-wasm-js-inlining (?)
|
15
|
+
// --experimental-wasm-return-call (on by default)
|
16
|
+
}
|
17
|
+
|
6
18
|
const file = process.argv.slice(2).find(x => x[0] !== '-');
|
7
19
|
if (!file) {
|
20
|
+
if (process.argv.includes('-v')) {
|
21
|
+
// just print version
|
22
|
+
console.log((await import('./version.js')).default);
|
23
|
+
process.exit(0);
|
24
|
+
}
|
25
|
+
|
8
26
|
// run repl if no file given
|
9
27
|
await import('./repl.js');
|
10
28
|
|
@@ -30,5 +48,6 @@ try {
|
|
30
48
|
exports.main();
|
31
49
|
if (cache) process.stdout.write(cache);
|
32
50
|
} catch (e) {
|
51
|
+
if (cache) process.stdout.write(cache);
|
33
52
|
console.error(`${e.constructor.name}: ${e.message}`);
|
34
53
|
}
|
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,13 +41,15 @@ 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
54
|
if (run && exports.$) {
|
60
55
|
lastMemory = exports.$;
|
@@ -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
|
-
if (source.includes(' = ') || source.includes('let ') || source.includes('var ') || source.includes('const ') || source.includes('function ')) prev = toRun + ';\n';
|
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");
|