porffor 0.0.0-70c2792 → 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 +24 -4
- package/compiler/codeGen.js +85 -52
- package/compiler/decompile.js +2 -2
- package/compiler/encoding.js +4 -2
- package/compiler/opt.js +33 -3
- package/compiler/sections.js +24 -0
- package/package.json +1 -1
- package/runner/index.js +12 -0
package/README.md
CHANGED
@@ -77,6 +77,7 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
77
77
|
- string concat (`+`) (eg `'a' + 'b'`)
|
78
78
|
- truthy/falsy (eg `!'' == true`)
|
79
79
|
- string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
|
80
|
+
- nullish coalescing operator (`??`)
|
80
81
|
|
81
82
|
### built-ins
|
82
83
|
|
@@ -100,17 +101,29 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
100
101
|
- intrinsic functions (see below)
|
101
102
|
- inlining wasm via ``asm`...``\` "macro"
|
102
103
|
|
103
|
-
##
|
104
|
+
## todo
|
105
|
+
no particular order and no guarentees, just what could happen soon™
|
106
|
+
|
104
107
|
- arrays
|
105
108
|
- member setting (`arr[0] = 2`)
|
106
109
|
- more of `Array` prototype
|
107
110
|
- arrays/strings inside arrays
|
111
|
+
- destructuring
|
112
|
+
- for .. of
|
108
113
|
- strings
|
109
114
|
- member setting
|
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
@@ -174,7 +174,6 @@ const generate = (scope, decl, global = false, name = undefined) => {
|
|
174
174
|
}
|
175
175
|
|
176
176
|
if (asm[0] === 'memory') {
|
177
|
-
scope.memory = true;
|
178
177
|
allocPage('asm instrinsic');
|
179
178
|
// todo: add to store/load offset insts
|
180
179
|
continue;
|
@@ -288,7 +287,7 @@ const generateReturn = (scope, decl) => {
|
|
288
287
|
];
|
289
288
|
}
|
290
289
|
|
291
|
-
|
290
|
+
scope.returnType = getNodeType(scope, decl.argument);
|
292
291
|
|
293
292
|
return [
|
294
293
|
...generate(scope, decl.argument),
|
@@ -305,11 +304,11 @@ const localTmp = (scope, name, type = valtypeBinary) => {
|
|
305
304
|
return idx;
|
306
305
|
};
|
307
306
|
|
308
|
-
const performLogicOp = (scope, op, left, right) => {
|
307
|
+
const performLogicOp = (scope, op, left, right, leftType, rightType) => {
|
309
308
|
const checks = {
|
310
|
-
'||':
|
311
|
-
'&&':
|
312
|
-
|
309
|
+
'||': falsy,
|
310
|
+
'&&': truthy,
|
311
|
+
'??': nullish
|
313
312
|
};
|
314
313
|
|
315
314
|
if (!checks[op]) return todo(`logic operator ${op} not implemented yet`);
|
@@ -320,7 +319,8 @@ const performLogicOp = (scope, op, left, right) => {
|
|
320
319
|
return [
|
321
320
|
...left,
|
322
321
|
[ Opcodes.local_tee, localTmp(scope, 'logictmp') ],
|
323
|
-
...checks[op],
|
322
|
+
...checks[op](scope, [], leftType),
|
323
|
+
Opcodes.i32_to,
|
324
324
|
[ Opcodes.if, valtypeBinary ],
|
325
325
|
...right,
|
326
326
|
[ Opcodes.else ],
|
@@ -335,8 +335,6 @@ const concatStrings = (scope, left, right, global, name, assign) => {
|
|
335
335
|
// todo: optimize by looking up names in arrays and using that if exists?
|
336
336
|
// todo: optimize this if using literals/known lengths?
|
337
337
|
|
338
|
-
scope.memory = true;
|
339
|
-
|
340
338
|
const rightPointer = localTmp(scope, 'concat_right_pointer', Valtype.i32);
|
341
339
|
const rightLength = localTmp(scope, 'concat_right_length', Valtype.i32);
|
342
340
|
const leftLength = localTmp(scope, 'concat_left_length', Valtype.i32);
|
@@ -471,8 +469,6 @@ const compareStrings = (scope, left, right) => {
|
|
471
469
|
// todo: optimize by looking up names in arrays and using that if exists?
|
472
470
|
// todo: optimize this if using literals/known lengths?
|
473
471
|
|
474
|
-
scope.memory = true;
|
475
|
-
|
476
472
|
const leftPointer = localTmp(scope, 'compare_left_pointer', Valtype.i32);
|
477
473
|
const leftLength = localTmp(scope, 'compare_left_length', Valtype.i32);
|
478
474
|
const rightPointer = localTmp(scope, 'compare_right_pointer', Valtype.i32);
|
@@ -570,75 +566,100 @@ const compareStrings = (scope, left, right) => {
|
|
570
566
|
];
|
571
567
|
};
|
572
568
|
|
573
|
-
const
|
569
|
+
const truthy = (scope, wasm, type) => {
|
574
570
|
// arrays are always truthy
|
575
571
|
if (type === TYPES._array) return [
|
576
572
|
...wasm,
|
577
573
|
[ Opcodes.drop ],
|
578
|
-
number(
|
574
|
+
...number(1)
|
579
575
|
];
|
580
576
|
|
581
577
|
if (type === TYPES.string) {
|
582
|
-
// if "" (length = 0)
|
578
|
+
// if not "" (length = 0)
|
583
579
|
return [
|
584
580
|
// pointer
|
585
581
|
...wasm,
|
582
|
+
Opcodes.i32_to_u,
|
586
583
|
|
587
584
|
// get length
|
588
585
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
589
586
|
|
590
|
-
// if length
|
591
|
-
[ Opcodes.i32_eqz ],
|
587
|
+
// if length != 0
|
588
|
+
/* [ Opcodes.i32_eqz ],
|
589
|
+
[ Opcodes.i32_eqz ], */
|
592
590
|
Opcodes.i32_from_u
|
593
591
|
]
|
594
592
|
}
|
595
593
|
|
596
|
-
// if
|
594
|
+
// if != 0
|
597
595
|
return [
|
598
596
|
...wasm,
|
599
597
|
|
600
|
-
|
601
|
-
Opcodes.
|
598
|
+
/* Opcodes.eqz,
|
599
|
+
[ Opcodes.i32_eqz ],
|
600
|
+
Opcodes.i32_from */
|
602
601
|
];
|
603
602
|
};
|
604
603
|
|
605
|
-
const
|
604
|
+
const falsy = (scope, wasm, type) => {
|
606
605
|
// arrays are always truthy
|
607
606
|
if (type === TYPES._array) return [
|
608
607
|
...wasm,
|
609
608
|
[ Opcodes.drop ],
|
610
|
-
number(
|
609
|
+
...number(0)
|
611
610
|
];
|
612
611
|
|
613
612
|
if (type === TYPES.string) {
|
614
|
-
// if
|
613
|
+
// if "" (length = 0)
|
615
614
|
return [
|
616
615
|
// pointer
|
617
616
|
...wasm,
|
617
|
+
Opcodes.i32_to_u,
|
618
618
|
|
619
619
|
// get length
|
620
620
|
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, 0 ],
|
621
621
|
|
622
|
-
// if length
|
623
|
-
|
624
|
-
[ Opcodes.i32_eqz ], */
|
622
|
+
// if length == 0
|
623
|
+
[ Opcodes.i32_eqz ],
|
625
624
|
Opcodes.i32_from_u
|
626
625
|
]
|
627
626
|
}
|
628
627
|
|
629
|
-
// if
|
628
|
+
// if = 0
|
630
629
|
return [
|
631
630
|
...wasm,
|
632
631
|
|
633
|
-
|
634
|
-
|
635
|
-
|
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)
|
636
657
|
];
|
637
658
|
};
|
638
659
|
|
639
660
|
const performOp = (scope, op, left, right, leftType, rightType, _global = false, _name = '$undeclared', assign = false) => {
|
640
661
|
if (op === '||' || op === '&&' || op === '??') {
|
641
|
-
return performLogicOp(scope, op, left, right);
|
662
|
+
return performLogicOp(scope, op, left, right, leftType, rightType);
|
642
663
|
}
|
643
664
|
|
644
665
|
if (codeLog && (!leftType || !rightType)) log('codegen', 'untracked type in op', op, _name, '\n' + new Error().stack.split('\n').slice(1).join('\n'));
|
@@ -779,7 +800,7 @@ const includeBuiltin = (scope, builtin) => {
|
|
779
800
|
};
|
780
801
|
|
781
802
|
const generateLogicExp = (scope, decl) => {
|
782
|
-
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));
|
783
804
|
};
|
784
805
|
|
785
806
|
const TYPES = {
|
@@ -943,7 +964,8 @@ const generateLiteral = (scope, decl, global, name) => {
|
|
943
964
|
const countLeftover = wasm => {
|
944
965
|
let count = 0, depth = 0;
|
945
966
|
|
946
|
-
for (
|
967
|
+
for (let i = 0; i < wasm.length; i++) {
|
968
|
+
const inst = wasm[i];
|
947
969
|
if (depth === 0 && (inst[0] === Opcodes.if || inst[0] === Opcodes.block || inst[0] === Opcodes.loop)) {
|
948
970
|
if (inst[0] === Opcodes.if) count--;
|
949
971
|
if (inst[1] !== Blocktype.void) count++;
|
@@ -1047,7 +1069,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1047
1069
|
}
|
1048
1070
|
|
1049
1071
|
let out = [];
|
1050
|
-
let protoFunc, protoName, baseType, baseName
|
1072
|
+
let protoFunc, protoName, baseType, baseName;
|
1051
1073
|
// ident.func()
|
1052
1074
|
if (name && name.startsWith('__')) {
|
1053
1075
|
const spl = name.slice(2).split('_');
|
@@ -1070,11 +1092,11 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1070
1092
|
|
1071
1093
|
out = generate(scope, decl.callee.object);
|
1072
1094
|
out.push([ Opcodes.drop ]);
|
1095
|
+
|
1096
|
+
baseName = [...arrays.keys()].pop();
|
1073
1097
|
}
|
1074
1098
|
|
1075
1099
|
if (protoFunc) {
|
1076
|
-
scope.memory = true;
|
1077
|
-
|
1078
1100
|
let pointer = arrays.get(baseName);
|
1079
1101
|
|
1080
1102
|
if (pointer == null) {
|
@@ -1082,7 +1104,7 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1082
1104
|
if (codeLog) log('codegen', 'cloning unknown dynamic pointer');
|
1083
1105
|
|
1084
1106
|
// register array
|
1085
|
-
|
1107
|
+
0, [ , pointer ] = makeArray(scope, {
|
1086
1108
|
rawElements: new Array(0)
|
1087
1109
|
}, _global, baseName, true, baseType === TYPES.string ? 'i16' : valtype);
|
1088
1110
|
|
@@ -1180,7 +1202,6 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1180
1202
|
args = args.slice(0, func.params.length);
|
1181
1203
|
}
|
1182
1204
|
|
1183
|
-
if (func && func.memory) scope.memory = true;
|
1184
1205
|
if (func && func.throws) scope.throws = true;
|
1185
1206
|
|
1186
1207
|
for (const arg of args) {
|
@@ -1304,8 +1325,6 @@ const generateAssign = (scope, decl) => {
|
|
1304
1325
|
const name = decl.left.object.name;
|
1305
1326
|
const pointer = arrays.get(name);
|
1306
1327
|
|
1307
|
-
scope.memory = true;
|
1308
|
-
|
1309
1328
|
const aotPointer = pointer != null;
|
1310
1329
|
|
1311
1330
|
const newValueTmp = localTmp(scope, '__length_setter_tmp');
|
@@ -1356,8 +1375,27 @@ const generateAssign = (scope, decl) => {
|
|
1356
1375
|
];
|
1357
1376
|
}
|
1358
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
|
+
|
1359
1397
|
return [
|
1360
|
-
...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),
|
1361
1399
|
[ isGlobal ? Opcodes.global_set : Opcodes.local_set, local.idx ],
|
1362
1400
|
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ]
|
1363
1401
|
];
|
@@ -1384,13 +1422,14 @@ const generateUnary = (scope, decl) => {
|
|
1384
1422
|
|
1385
1423
|
case '!':
|
1386
1424
|
// !=
|
1387
|
-
return falsy(scope, generate(scope, decl.argument));
|
1425
|
+
return falsy(scope, generate(scope, decl.argument), getNodeType(scope, decl.argument));
|
1388
1426
|
|
1389
1427
|
case '~':
|
1428
|
+
// todo: does not handle Infinity properly (should convert to 0) (but opt const converting saves us sometimes)
|
1390
1429
|
return [
|
1391
1430
|
...generate(scope, decl.argument),
|
1392
1431
|
Opcodes.i32_to,
|
1393
|
-
[ Opcodes.i32_const, signedLEB128(-1) ],
|
1432
|
+
[ Opcodes.i32_const, ...signedLEB128(-1) ],
|
1394
1433
|
[ Opcodes.i32_xor ],
|
1395
1434
|
Opcodes.i32_from
|
1396
1435
|
];
|
@@ -1743,8 +1782,6 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1743
1782
|
// local value as pointer
|
1744
1783
|
out.push(...number(pointer));
|
1745
1784
|
|
1746
|
-
scope.memory = true;
|
1747
|
-
|
1748
1785
|
return [ out, pointer ];
|
1749
1786
|
};
|
1750
1787
|
|
@@ -1763,8 +1800,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1763
1800
|
const name = decl.object.name;
|
1764
1801
|
const pointer = arrays.get(name);
|
1765
1802
|
|
1766
|
-
scope.memory = true;
|
1767
|
-
|
1768
1803
|
const aotPointer = pointer != null;
|
1769
1804
|
|
1770
1805
|
return [
|
@@ -1784,8 +1819,6 @@ export const generateMember = (scope, decl, _global, _name) => {
|
|
1784
1819
|
const name = decl.object.name;
|
1785
1820
|
const pointer = arrays.get(name);
|
1786
1821
|
|
1787
|
-
scope.memory = true;
|
1788
|
-
|
1789
1822
|
const aotPointer = pointer != null;
|
1790
1823
|
|
1791
1824
|
if (type === TYPES._array) {
|
@@ -1895,6 +1928,7 @@ const generateFunc = (scope, decl) => {
|
|
1895
1928
|
locals: {},
|
1896
1929
|
localInd: 0,
|
1897
1930
|
returns: [ valtypeBinary ],
|
1931
|
+
returnType: null,
|
1898
1932
|
memory: false,
|
1899
1933
|
throws: false,
|
1900
1934
|
name
|
@@ -1921,7 +1955,6 @@ const generateFunc = (scope, decl) => {
|
|
1921
1955
|
returns: innerScope.returns,
|
1922
1956
|
returnType: innerScope.returnType,
|
1923
1957
|
locals: innerScope.locals,
|
1924
|
-
memory: innerScope.memory,
|
1925
1958
|
throws: innerScope.throws,
|
1926
1959
|
index: currentFuncIndex++
|
1927
1960
|
};
|
@@ -1936,6 +1969,8 @@ const generateFunc = (scope, decl) => {
|
|
1936
1969
|
|
1937
1970
|
if (name !== 'main' && func.returns.length !== 0 && wasm[wasm.length - 1]?.[0] !== Opcodes.return && countLeftover(wasm) === 0) {
|
1938
1971
|
wasm.push(...number(0), [ Opcodes.return ]);
|
1972
|
+
|
1973
|
+
if (func.returnType === null) func.returnType = TYPES.undefined;
|
1939
1974
|
}
|
1940
1975
|
|
1941
1976
|
// change v128 params into many <type> (i32x4 -> i32/etc) instead as unsupported param valtype
|
@@ -1946,9 +1981,7 @@ const generateFunc = (scope, decl) => {
|
|
1946
1981
|
if (local.type === Valtype.v128) {
|
1947
1982
|
vecParams++;
|
1948
1983
|
|
1949
|
-
/*
|
1950
|
-
|
1951
|
-
wasm.unshift( // add v128 load for param
|
1984
|
+
/* wasm.unshift( // add v128 load for param
|
1952
1985
|
[ Opcodes.i32_const, 0 ],
|
1953
1986
|
[ ...Opcodes.v128_load, 0, i * 16 ],
|
1954
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/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') {
|
@@ -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/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]}`);
|
@@ -74,6 +89,14 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
74
89
|
encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
|
75
90
|
);
|
76
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
|
+
|
77
100
|
const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
|
78
101
|
Section.global,
|
79
102
|
encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
|
@@ -146,6 +169,7 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
146
169
|
...typeSection,
|
147
170
|
...importSection,
|
148
171
|
...funcSection,
|
172
|
+
...chSection,
|
149
173
|
...memorySection,
|
150
174
|
...tagSection,
|
151
175
|
...globalSection,
|
package/package.json
CHANGED
package/runner/index.js
CHANGED
@@ -3,6 +3,18 @@
|
|
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) {
|
8
20
|
if (process.argv.includes('-v')) {
|