porffor 0.16.0-e5b6b94c8 → 0.16.0-f9dde1759
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/CONTRIBUTING.md +4 -3
- package/compiler/2c.js +53 -65
- package/compiler/assemble.js +5 -4
- package/compiler/builtins/console.ts +1 -1
- package/compiler/builtins/porffor.d.ts +11 -0
- package/compiler/builtins/set.ts +3 -2
- package/compiler/builtins/symbol.ts +6 -6
- package/compiler/builtins.js +25 -5
- package/compiler/codegen.js +20 -42
- package/compiler/cyclone.js +535 -0
- package/compiler/decompile.js +3 -1
- package/compiler/generated_builtins.js +1 -1
- package/compiler/havoc.js +93 -0
- package/compiler/index.js +89 -6
- package/compiler/opt.js +3 -39
- package/compiler/parse.js +2 -2
- package/compiler/pgo.js +207 -0
- package/compiler/precompile.js +3 -1
- package/compiler/prefs.js +6 -1
- package/compiler/wrap.js +53 -12
- package/no_pgo.txt +923 -0
- package/package.json +3 -5
- package/pgo.txt +916 -0
- package/runner/index.js +21 -11
- /package/runner/{profiler.js → profile.js} +0 -0
package/compiler/decompile.js
CHANGED
@@ -4,6 +4,8 @@ import { read_ieee754_binary64, read_signedLEB128, read_unsignedLEB128 } from '.
|
|
4
4
|
const inv = (obj, keyMap = x => x) => Object.keys(obj).reduce((acc, x) => { acc[keyMap(obj[x])] = x; return acc; }, {});
|
5
5
|
const invOpcodes = inv(Opcodes);
|
6
6
|
const invValtype = inv(Valtype);
|
7
|
+
globalThis.invOpcodes = invOpcodes;
|
8
|
+
globalThis.invValtype = invValtype;
|
7
9
|
|
8
10
|
export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = [], funcs = [], globals = {}, exceptions = []) => {
|
9
11
|
const invLocals = inv(locals, x => x.idx);
|
@@ -91,7 +93,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
91
93
|
if (callFunc) out += ` ;; $${callFunc.name} ${makeSignature(callFunc.params, callFunc.returns)}`;
|
92
94
|
if (globalThis.importFuncs && inst[1] < importFuncs.length) {
|
93
95
|
const importFunc = importFuncs[inst[1]];
|
94
|
-
out += ` ;; import ${importFunc.name} ${makeSignature(new Array(importFunc.params).fill(valtypeBinary), new Array(importFunc.returns).fill(valtypeBinary),)}`;
|
96
|
+
out += ` ;; import ${importFunc.name} ${makeSignature(typeof importFunc.params === 'object' ? importFunc.params : new Array(importFunc.params).fill(valtypeBinary), new Array(importFunc.returns).fill(valtypeBinary),)}`;
|
95
97
|
}
|
96
98
|
}
|
97
99
|
|
@@ -1748,7 +1748,7 @@ export const BuiltinFuncs = function() {
|
|
1748
1748
|
localNames: ["iterable","iterable#type","out","type","forof_base_pointer","forof_length","forof_counter","x","x#type","#last_type","#typeswitch_tmp"],
|
1749
1749
|
};
|
1750
1750
|
this.__Set_prototype_union = {
|
1751
|
-
wasm: (scope, {builtin,internalThrow,}) => [[32,2],[32,3],[16, builtin('__Porffor_rawType')],[68,0,0,0,0,0,0,52,64],[98],[4,64],...internalThrow(scope, 'TypeError', `Set.union
|
1751
|
+
wasm: (scope, {builtin,internalThrow,}) => [[32,2],[32,3],[16, builtin('__Porffor_rawType')],[68,0,0,0,0,0,0,52,64],[98],[4,64],...internalThrow(scope, 'TypeError', `Set.prototype.union's 'other' argument must be a Set`),[11],[32,0],[65,20],[16, builtin('Set$constructor')],[33,4],[32,2],[252,3],[33,5],[65,0],[33,7],[32,5],[40,1,0],[33,6],[32,3],[33,11],[2,64],[32,11],[65,2],[70],[4,64,"TYPESWITCH|String"],[65,2],[33,9],[3,64],[65,0],[32,5],[47,0,4],[59,0,3],[68,0,0,0,0,0,0,240,191],[33,8],[2,64],[32,4],[65,20],[32,8],[32,9],[16, builtin('__Set_prototype_add')],[26],[26],[32,5],[65,2],[106],[33,5],[32,7],[65,1],[106],[34,7],[32,6],[71],[13,1],[11],[11],[12,1],[11],[32,11],[65,16],[70],[4,64,"TYPESWITCH|Array"],[3,64],[32,5],[43,0,4],[32,5],[45,0,12],[33,9],[33,8],[2,64],[32,4],[65,20],[32,8],[32,9],[16, builtin('__Set_prototype_add')],[26],[26],[32,5],[65,9],[106],[33,5],[32,7],[65,1],[106],[34,7],[32,6],[71],[13,1],[11],[11],[12,1],[11],[32,11],[65,18],[70],[4,64,"TYPESWITCH|ByteString"],[65,18],[33,9],[3,64],[65,0],[32,5],[32,7],[106],[45,0,4],[58,0,3],[68,0,0,0,0,0,0,240,191],[33,8],[2,64],[32,4],[65,20],[32,8],[32,9],[16, builtin('__Set_prototype_add')],[26],[26],[32,7],[65,1],[106],[34,7],[32,6],[71],[13,1],[11],[11],[12,1],[11],[32,11],[65,20],[70],[4,64,"TYPESWITCH|Set"],[3,64],[32,5],[43,0,4],[32,5],[45,0,12],[33,9],[33,8],[2,64],[32,4],[65,20],[32,8],[32,9],[16, builtin('__Set_prototype_add')],[26],[26],[32,5],[65,9],[106],[33,5],[32,7],[65,1],[106],[34,7],[32,6],[71],[13,1],[11],[11],[12,1],[11],...internalThrow(scope, 'TypeError', `Tried for..of on non-iterable type`),[11,"TYPESWITCH_end"],[32,4],[65,20],[15]],
|
1752
1752
|
params: [124,127,124,127],
|
1753
1753
|
typedParams: true,
|
1754
1754
|
returns: [124,127],
|
@@ -0,0 +1,93 @@
|
|
1
|
+
// havoc: wasm rewrite library (it wreaks havoc upon wasm bytecode hence "havoc")
|
2
|
+
import { Opcodes, Valtype } from './wasmSpec.js';
|
3
|
+
|
4
|
+
export const localsToConsts = (func, targets, consts, { localKeys }) => {
|
5
|
+
const { wasm, locals } = func;
|
6
|
+
|
7
|
+
// update locals object
|
8
|
+
localKeys ??= Object.keys(locals).sort((a, b) => a.idx - b.idx);
|
9
|
+
for (const x of targets) {
|
10
|
+
delete locals[localKeys[x]];
|
11
|
+
}
|
12
|
+
|
13
|
+
const idxLut = {};
|
14
|
+
for (const x in locals) {
|
15
|
+
let i = locals[x].idx;
|
16
|
+
const o = i;
|
17
|
+
for (let j = 0; j < targets.length; j++) {
|
18
|
+
if (o > targets[j]) i--;
|
19
|
+
}
|
20
|
+
|
21
|
+
locals[x].idx = i;
|
22
|
+
idxLut[o] = i;
|
23
|
+
}
|
24
|
+
|
25
|
+
// update wasm
|
26
|
+
for (let i = 0; i < wasm.length; i++) {
|
27
|
+
const op = wasm[i];
|
28
|
+
const opcode = op[0];
|
29
|
+
const idx = op[1];
|
30
|
+
|
31
|
+
if (opcode >= 0x20 && opcode <= 0x22) { // local.* op
|
32
|
+
if (targets.includes(idx)) {
|
33
|
+
if (opcode === Opcodes.local_get || opcode === Opcodes.local_tee) {
|
34
|
+
// get/tee -> const
|
35
|
+
const c = consts[targets.indexOf(idx)];
|
36
|
+
wasm.splice(i, 1, c);
|
37
|
+
continue;
|
38
|
+
}
|
39
|
+
|
40
|
+
// set -> drop
|
41
|
+
wasm.splice(i, 1, [ Opcodes.drop ]);
|
42
|
+
continue;
|
43
|
+
}
|
44
|
+
|
45
|
+
// adjust index to compensate for removed
|
46
|
+
wasm[i] = [ opcode, idxLut[idx] ];
|
47
|
+
}
|
48
|
+
}
|
49
|
+
};
|
50
|
+
|
51
|
+
export const f64ToI32s = (func, targets) => {
|
52
|
+
const { wasm, locals } = func;
|
53
|
+
|
54
|
+
// update locals object
|
55
|
+
for (const x in locals) {
|
56
|
+
let i = locals[x].idx;
|
57
|
+
if (targets.includes(i)) {
|
58
|
+
locals[x].type = Valtype.i32;
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
// update wasm
|
63
|
+
for (let i = 0; i < wasm.length; i++) {
|
64
|
+
const op = wasm[i];
|
65
|
+
const opcode = op[0];
|
66
|
+
const idx = op[1];
|
67
|
+
|
68
|
+
if (opcode >= 0x20 && opcode <= 0x22 && targets.includes(idx)) { // local.* op
|
69
|
+
if (opcode === Opcodes.local_get) {
|
70
|
+
// add i32 -> f64 after
|
71
|
+
wasm.splice(i + 1, 0, Opcodes.i32_from);
|
72
|
+
continue;
|
73
|
+
}
|
74
|
+
|
75
|
+
if (opcode === Opcodes.local_set) {
|
76
|
+
// add f64 -> i32 before
|
77
|
+
wasm.splice(i, 0, Opcodes.i32_to);
|
78
|
+
i++;
|
79
|
+
continue;
|
80
|
+
}
|
81
|
+
|
82
|
+
if (opcode === Opcodes.local_tee) {
|
83
|
+
// add f64 -> i32 before
|
84
|
+
wasm.splice(i, 0, Opcodes.i32_to);
|
85
|
+
i++;
|
86
|
+
|
87
|
+
// add i32 -> f64 after
|
88
|
+
wasm.splice(i + 1, 0, Opcodes.i32_from);
|
89
|
+
continue;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
};
|
package/compiler/index.js
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
import { underline, bold, log } from './log.js';
|
2
|
+
import { Valtype, PageSize } from './wasmSpec.js';
|
2
3
|
import parse from './parse.js';
|
3
4
|
import codegen from './codegen.js';
|
4
5
|
import opt from './opt.js';
|
5
6
|
import assemble from './assemble.js';
|
6
7
|
import decompile from './decompile.js';
|
7
8
|
import toc from './2c.js';
|
9
|
+
import * as pgo from './pgo.js';
|
10
|
+
import cyclone from './cyclone.js';
|
8
11
|
import Prefs from './prefs.js';
|
9
12
|
|
10
13
|
globalThis.decompile = decompile;
|
@@ -13,6 +16,7 @@ const logFuncs = (funcs, globals, exceptions) => {
|
|
13
16
|
console.log('\n' + underline(bold('funcs')));
|
14
17
|
|
15
18
|
for (const f of funcs) {
|
19
|
+
if (f.internal) continue;
|
16
20
|
console.log(decompile(f.wasm, f.name, f.index, f.locals, f.params, f.returns, funcs, globals, exceptions));
|
17
21
|
}
|
18
22
|
|
@@ -23,6 +27,24 @@ const fs = (typeof process?.version !== 'undefined' ? (await import('node:fs'))
|
|
23
27
|
const execSync = (typeof process?.version !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
|
24
28
|
|
25
29
|
export default (code, flags) => {
|
30
|
+
let target = Prefs.target ?? 'wasm';
|
31
|
+
if (Prefs.native) target = 'native';
|
32
|
+
|
33
|
+
let outFile = Prefs.o;
|
34
|
+
|
35
|
+
globalThis.valtype = 'f64';
|
36
|
+
const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
|
37
|
+
if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
|
38
|
+
globalThis.valtypeBinary = Valtype[valtype];
|
39
|
+
|
40
|
+
globalThis.pageSize = PageSize;
|
41
|
+
const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
|
42
|
+
if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
|
43
|
+
|
44
|
+
// enable pgo by default for c/native
|
45
|
+
if (target !== 'wasm') Prefs.pgo = Prefs.pgo === false ? false : true;
|
46
|
+
if (Prefs.pgo) pgo.setup();
|
47
|
+
|
26
48
|
const t0 = performance.now();
|
27
49
|
const program = parse(code, flags);
|
28
50
|
if (Prefs.profileCompiler) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
|
@@ -35,9 +57,49 @@ export default (code, flags) => {
|
|
35
57
|
|
36
58
|
const t2 = performance.now();
|
37
59
|
opt(funcs, globals, pages, tags, exceptions);
|
38
|
-
if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
|
39
60
|
|
40
|
-
|
61
|
+
if (Prefs.pgo) {
|
62
|
+
if (Prefs.pgoLog) {
|
63
|
+
const oldSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
|
64
|
+
const t = performance.now();
|
65
|
+
|
66
|
+
pgo.run({ funcs, globals, tags, exceptions, pages, data });
|
67
|
+
opt(funcs, globals, pages, tags, exceptions);
|
68
|
+
|
69
|
+
console.log(`PGO total time: ${(performance.now() - t).toFixed(2)}ms`);
|
70
|
+
|
71
|
+
const newSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
|
72
|
+
console.log(`PGO size diff: ${oldSize - newSize} bytes (${oldSize} -> ${newSize})\n`);
|
73
|
+
} else {
|
74
|
+
pgo.run({ funcs, globals, tags, exceptions, pages, data });
|
75
|
+
opt(funcs, globals, pages, tags, exceptions);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
if (Prefs.cyclone) {
|
80
|
+
if (Prefs.cycloneLog) {
|
81
|
+
const oldSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
|
82
|
+
const t = performance.now();
|
83
|
+
|
84
|
+
for (const x of funcs) {
|
85
|
+
const preOps = x.wasm.length;
|
86
|
+
cyclone(x.wasm);
|
87
|
+
|
88
|
+
console.log(`${x.name}: ${preOps} -> ${x.wasm.length} ops`);
|
89
|
+
}
|
90
|
+
|
91
|
+
console.log(`cyclone total time: ${(performance.now() - t).toFixed(2)}ms`);
|
92
|
+
|
93
|
+
const newSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
|
94
|
+
console.log(`cyclone size diff: ${oldSize - newSize} bytes (${oldSize} -> ${newSize})\n`);
|
95
|
+
} else {
|
96
|
+
for (const x of funcs) {
|
97
|
+
cyclone(x.wasm);
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
|
41
103
|
|
42
104
|
const t3 = performance.now();
|
43
105
|
const wasm = assemble(funcs, globals, tags, pages, data, flags);
|
@@ -54,9 +116,6 @@ export default (code, flags) => {
|
|
54
116
|
|
55
117
|
const out = { wasm, funcs, globals, tags, exceptions, pages, data };
|
56
118
|
|
57
|
-
const target = Prefs.target ?? 'wasm';
|
58
|
-
const outFile = Prefs.o;
|
59
|
-
|
60
119
|
if (target === 'wasm' && outFile) {
|
61
120
|
fs.writeFileSync(outFile, Buffer.from(wasm));
|
62
121
|
|
@@ -77,6 +136,8 @@ export default (code, flags) => {
|
|
77
136
|
}
|
78
137
|
|
79
138
|
if (target === 'native') {
|
139
|
+
outFile ??= Prefs.native ? './porffor_tmp' : file.split('/').at(-1).split('.').at(0, -1).join('.');
|
140
|
+
|
80
141
|
let compiler = Prefs.compiler ?? 'clang';
|
81
142
|
const cO = Prefs._cO ?? 'Ofast';
|
82
143
|
|
@@ -95,7 +156,29 @@ export default (code, flags) => {
|
|
95
156
|
|
96
157
|
fs.unlinkSync(tmpfile);
|
97
158
|
|
98
|
-
if (process.version)
|
159
|
+
if (process.version) {
|
160
|
+
if (Prefs.native) {
|
161
|
+
const cleanup = () => {
|
162
|
+
try {
|
163
|
+
fs.unlinkSync(outFile);
|
164
|
+
} catch {}
|
165
|
+
};
|
166
|
+
|
167
|
+
process.on('exit', cleanup);
|
168
|
+
process.on('beforeExit', cleanup);
|
169
|
+
process.on('SIGINT', () => {
|
170
|
+
cleanup();
|
171
|
+
process.exit();
|
172
|
+
});
|
173
|
+
|
174
|
+
const runArgs = process.argv.slice(2).filter(x => !x.startsWith('-'));
|
175
|
+
try {
|
176
|
+
execSync([ outFile, ...runArgs.slice(1) ].join(' '), { stdio: 'inherit' });
|
177
|
+
} catch {}
|
178
|
+
}
|
179
|
+
|
180
|
+
process.exit();
|
181
|
+
}
|
99
182
|
}
|
100
183
|
|
101
184
|
return out;
|
package/compiler/opt.js
CHANGED
@@ -108,7 +108,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
108
108
|
for (const f of funcs) {
|
109
109
|
const wasm = f.wasm;
|
110
110
|
|
111
|
-
const lastType = f.locals['#last_type'];
|
111
|
+
const lastType = f.locals['#last_type']?.idx;
|
112
112
|
|
113
113
|
let runs = (+Prefs.optWasmRuns) || 2; // todo: how many by default?
|
114
114
|
while (runs > 0) {
|
@@ -248,7 +248,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
248
248
|
}
|
249
249
|
|
250
250
|
// remove setting last type if it is never gotten
|
251
|
-
if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType
|
251
|
+
if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType) {
|
252
252
|
// replace this inst with drop
|
253
253
|
wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
|
254
254
|
if (i > 0) i--;
|
@@ -354,7 +354,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
354
354
|
continue;
|
355
355
|
}
|
356
356
|
|
357
|
-
if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
|
357
|
+
if (lastInst[0] === Opcodes.const && inst[0] === Opcodes.i32_to[0] && (inst[1] === Opcodes.i32_to[1] || inst[1] === Opcodes.i32_to_u[1])) {
|
358
358
|
// change const and immediate i32 convert to i32 const
|
359
359
|
// f64.const 0
|
360
360
|
// i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
|
@@ -504,42 +504,6 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
504
504
|
const useCount = {};
|
505
505
|
for (const x in f.locals) useCount[f.locals[x].idx] = 0;
|
506
506
|
|
507
|
-
// final pass
|
508
|
-
depth = [];
|
509
|
-
for (let i = 0; i < wasm.length; i++) {
|
510
|
-
let inst = wasm[i];
|
511
|
-
if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) useCount[inst[1]]++;
|
512
|
-
|
513
|
-
if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) depth.push(inst[0]);
|
514
|
-
if (inst[0] === Opcodes.end) depth.pop();
|
515
|
-
|
516
|
-
if (i < 2) continue;
|
517
|
-
const lastInst = wasm[i - 1];
|
518
|
-
const lastLastInst = wasm[i - 2];
|
519
|
-
|
520
|
-
// todo: add more math ops
|
521
|
-
if (optLevel >= 3 && (inst[0] === Opcodes.add || inst[0] === Opcodes.sub || inst[0] === Opcodes.mul) && lastLastInst[0] === Opcodes.const && lastInst[0] === Opcodes.const) {
|
522
|
-
// inline const math ops
|
523
|
-
// i32.const a
|
524
|
-
// i32.const b
|
525
|
-
// i32.add
|
526
|
-
// -->
|
527
|
-
// i32.const a + b
|
528
|
-
|
529
|
-
// does not work with leb encoded
|
530
|
-
if (lastInst.length > 2 || lastLastInst.length > 2) continue;
|
531
|
-
|
532
|
-
let a = lastLastInst[1];
|
533
|
-
let b = lastInst[1];
|
534
|
-
|
535
|
-
const val = performWasmOp(inst[0], a, b);
|
536
|
-
if (Prefs.optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
|
537
|
-
|
538
|
-
wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
|
539
|
-
i -= 2;
|
540
|
-
}
|
541
|
-
}
|
542
|
-
|
543
507
|
const localIdxs = Object.values(f.locals).map(x => x.idx);
|
544
508
|
// remove unused locals (cleanup)
|
545
509
|
for (const x in useCount) {
|
package/compiler/parse.js
CHANGED
@@ -10,7 +10,7 @@ if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
|
|
10
10
|
const file = process.argv.slice(2).find(x => x[0] !== '-' && !['run', 'wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm'].includes(x));
|
11
11
|
|
12
12
|
// should we try to support types (while parsing)
|
13
|
-
const types = Prefs.parseTypes || file?.endsWith('.ts');
|
13
|
+
const types = Prefs.parseTypes || Prefs.t || file?.endsWith('.ts');
|
14
14
|
globalThis.typedInput = types && Prefs.optTypes;
|
15
15
|
|
16
16
|
// todo: review which to use by default
|
@@ -43,7 +43,7 @@ export default (input, flags) => {
|
|
43
43
|
webcompat: true,
|
44
44
|
|
45
45
|
// babel
|
46
|
-
plugins: types ? ['estree', 'typescript'] : ['estree'],
|
46
|
+
plugins: types || flags.includes('typed') ? ['estree', 'typescript'] : ['estree'],
|
47
47
|
|
48
48
|
// multiple
|
49
49
|
sourceType: flags.includes('module') ? 'module' : 'script',
|
package/compiler/pgo.js
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
import { Opcodes, Valtype } from './wasmSpec.js';
|
2
|
+
import { number } from './embedding.js';
|
3
|
+
import { importedFuncs } from './builtins.js';
|
4
|
+
import Prefs from './prefs.js';
|
5
|
+
import assemble from './assemble.js';
|
6
|
+
import wrap, { writeByteStr } from './wrap.js';
|
7
|
+
import * as Havoc from './havoc.js';
|
8
|
+
|
9
|
+
export const setup = () => {
|
10
|
+
importedFuncs[importedFuncs.profile2].params = [ Valtype.i32, valtypeBinary ];
|
11
|
+
|
12
|
+
// enable these prefs by default for pgo
|
13
|
+
Prefs.typeswitchUniqueTmp = Prefs.typeswitchUniqueTmp === false ? false : true;
|
14
|
+
Prefs.cyclone = Prefs.cyclone === false ? false : true;
|
15
|
+
};
|
16
|
+
|
17
|
+
export const run = obj => {
|
18
|
+
const wasmFuncs = obj.funcs;
|
19
|
+
|
20
|
+
let starts = {};
|
21
|
+
const time = (id, msg) => {
|
22
|
+
if (!Prefs.pgoLog) return;
|
23
|
+
|
24
|
+
if (!starts[id]) {
|
25
|
+
process.stdout.write(msg);
|
26
|
+
starts[id] = performance.now();
|
27
|
+
} else {
|
28
|
+
process.stdout.write(`\r${' '.repeat(50)}\r[${(performance.now() - starts[id]).toFixed(2)}ms] ${msg}\n`);
|
29
|
+
}
|
30
|
+
};
|
31
|
+
|
32
|
+
time(0, `injecting PGO logging...`);
|
33
|
+
|
34
|
+
let funcs = [];
|
35
|
+
for (let i = 0; i < wasmFuncs.length; i++) {
|
36
|
+
const { name, internal, params, locals, wasm } = wasmFuncs[i];
|
37
|
+
if (internal) continue; // ignore internal funcs
|
38
|
+
wasmFuncs[i].originalWasm = structuredClone(wasm);
|
39
|
+
|
40
|
+
const invLocals = Object.keys(locals).reduce((acc, x) => { acc[locals[x].idx] = locals[x]; return acc; }, {});
|
41
|
+
|
42
|
+
const id = funcs.length;
|
43
|
+
funcs.push({ name, id, locals, params, invLocals });
|
44
|
+
|
45
|
+
wasm.unshift(
|
46
|
+
// mark active func
|
47
|
+
...number(i, Valtype.i32),
|
48
|
+
[ Opcodes.call, importedFuncs.profile1 ],
|
49
|
+
|
50
|
+
// log args
|
51
|
+
...params.flatMap((_, i) => [
|
52
|
+
...number(i, Valtype.i32),
|
53
|
+
[ Opcodes.local_get, i ],
|
54
|
+
...(invLocals[i].type !== Valtype.f64 ? [ Opcodes.i32_from ] : []),
|
55
|
+
[ Opcodes.call, importedFuncs.profile2 ]
|
56
|
+
])
|
57
|
+
);
|
58
|
+
|
59
|
+
for (let j = 0; j < wasm.length; j++) {
|
60
|
+
const inst = wasm[j];
|
61
|
+
if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) {
|
62
|
+
wasm.splice(j + 1, 0,
|
63
|
+
...number(inst[1], Valtype.i32),
|
64
|
+
[ Opcodes.local_get, inst[1] ],
|
65
|
+
...(invLocals[inst[1]].type !== Valtype.f64 ? [ Opcodes.i32_from ] : []),
|
66
|
+
[ Opcodes.call, importedFuncs.profile2 ]
|
67
|
+
);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
let localData = funcs.map(x => new Array(Object.keys(x.locals).length).fill(0).map(() => []));
|
73
|
+
|
74
|
+
time(0, `injected PGO logging`);
|
75
|
+
time(1, `running with PGO logging...`);
|
76
|
+
|
77
|
+
let activeFunc = null, abort = false;
|
78
|
+
try {
|
79
|
+
obj.wasm = assemble(obj.funcs, obj.globals, obj.tags, obj.pages, obj.data, obj.flags, true);
|
80
|
+
|
81
|
+
const { exports } = wrap(obj, [], {
|
82
|
+
y: n => {
|
83
|
+
activeFunc = n;
|
84
|
+
},
|
85
|
+
z: (i, n) => {
|
86
|
+
if (activeFunc == null) throw 'fail';
|
87
|
+
localData[activeFunc][i].push(n);
|
88
|
+
},
|
89
|
+
w: (ind, outPtr) => { // readArgv
|
90
|
+
const pgoInd = process.argv.indexOf('--pgo');
|
91
|
+
const args = process.argv.slice(pgoInd).filter(x => !x.startsWith('-'));
|
92
|
+
const str = args[ind - 1];
|
93
|
+
if (pgoInd === -1 || !str) {
|
94
|
+
if (Prefs.pgoLog) console.log('\nPGO warning: script was expecting arguments, please specify args to use for PGO after --pgo arg');
|
95
|
+
return -1;
|
96
|
+
}
|
97
|
+
|
98
|
+
writeByteStr(exports.$, outPtr, str);
|
99
|
+
return str.length;
|
100
|
+
}
|
101
|
+
}, () => {});
|
102
|
+
|
103
|
+
exports.main();
|
104
|
+
} catch (e) {
|
105
|
+
throw e;
|
106
|
+
}
|
107
|
+
|
108
|
+
for (const x of funcs) {
|
109
|
+
const wasmFunc = wasmFuncs.find(y => y.name === x.name);
|
110
|
+
wasmFunc.wasm = wasmFunc.originalWasm;
|
111
|
+
delete wasmFunc.originalWasm;
|
112
|
+
}
|
113
|
+
|
114
|
+
if (abort) {
|
115
|
+
console.log('aborting PGO!');
|
116
|
+
return false;
|
117
|
+
}
|
118
|
+
|
119
|
+
time(1, `ran with PGO logging`);
|
120
|
+
time(2, 'processing PGO data...');
|
121
|
+
|
122
|
+
// process data
|
123
|
+
let log = '';
|
124
|
+
for (let i = 0; i < localData.length; i++) {
|
125
|
+
const func = funcs[i];
|
126
|
+
const total = localData[i].length;
|
127
|
+
|
128
|
+
const localKeys = Object.keys(func.locals).sort((a, b) => a.idx - b.idx);
|
129
|
+
const localValues = Object.values(func.locals).sort((a, b) => a.idx - b.idx);
|
130
|
+
func.localKeys = localKeys;
|
131
|
+
func.localValues = localValues;
|
132
|
+
|
133
|
+
let counts = new Array(10).fill(0);
|
134
|
+
const consistents = localData[i].map((x, j) => {
|
135
|
+
if (j < func.params.length) return false; // param
|
136
|
+
if (x.length === 0 || !x.every((y, i) => i < 1 ? true : y === x[i - 1])) return false; // not consistent
|
137
|
+
|
138
|
+
counts[0]++;
|
139
|
+
return x[0];
|
140
|
+
});
|
141
|
+
|
142
|
+
const integerOnlyF64s = localData[i].map((x, j) => {
|
143
|
+
if (j < func.params.length) return false; // param
|
144
|
+
if (localValues[j].type === Valtype.i32) return false; // already i32
|
145
|
+
if (x.length === 0 || !x.every(y => Number.isInteger(y))) return false; // not all integer values
|
146
|
+
|
147
|
+
counts[1]++;
|
148
|
+
return true;
|
149
|
+
});
|
150
|
+
|
151
|
+
func.consistents = consistents;
|
152
|
+
func.integerOnlyF64s = integerOnlyF64s;
|
153
|
+
|
154
|
+
log += ` ${func.name}: identified ${counts[0]}/${total} locals as consistent${Prefs.verbosePgo ? ':' : ''}\n`;
|
155
|
+
if (Prefs.verbosePgo) {
|
156
|
+
for (let j = func.params.length; j < localData[i].length; j++) {
|
157
|
+
log += ` ${consistents[j] !== false ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m: ${new Set(localData[i][j]).size} unique values set\n`;
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
log += ` ${func.name}: identified ${counts[1]}/${localValues.reduce((acc, x) => acc + (x.type === Valtype.f64 ? 1 : 0), 0)} f64 locals as integer usage only${Prefs.verbosePgo ? ':' : ''}\n`;
|
162
|
+
if (Prefs.verbosePgo) {
|
163
|
+
for (let j = func.params.length; j < localData[i].length; j++) {
|
164
|
+
if (localValues[j].type !== Valtype.f64) continue;
|
165
|
+
log += ` ${integerOnlyF64s[j] ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m\n`;
|
166
|
+
}
|
167
|
+
|
168
|
+
log += '\n';
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
time(2, 'processed PGO data' + log);
|
173
|
+
time(3, 'optimizing using PGO data...');
|
174
|
+
|
175
|
+
log = '';
|
176
|
+
for (const x of funcs) {
|
177
|
+
const wasmFunc = wasmFuncs.find(y => y.name === x.name);
|
178
|
+
|
179
|
+
let targets = [];
|
180
|
+
for (let i = 0; i < x.integerOnlyF64s.length; i++) {
|
181
|
+
const c = x.integerOnlyF64s[i];
|
182
|
+
if (c === false) continue;
|
183
|
+
|
184
|
+
targets.push(i);
|
185
|
+
}
|
186
|
+
|
187
|
+
log += ` ${x.name}: replaced ${targets.length} f64 locals with i32s\n`;
|
188
|
+
if (targets.length > 0) Havoc.f64ToI32s(wasmFunc, targets);
|
189
|
+
|
190
|
+
targets = [];
|
191
|
+
let consts = [];
|
192
|
+
for (let i = 0; i < x.consistents.length; i++) {
|
193
|
+
const c = x.consistents[i];
|
194
|
+
if (c === false) continue;
|
195
|
+
|
196
|
+
targets.push(i);
|
197
|
+
|
198
|
+
const valtype = x.localValues[i].type;
|
199
|
+
consts.push(number(c, valtype)[0]);
|
200
|
+
}
|
201
|
+
|
202
|
+
log += ` ${x.name}: replaced ${targets.length} locals with consts\n`;
|
203
|
+
if (targets.length > 0) Havoc.localsToConsts(wasmFunc, targets, consts, { localKeys: x.localKeys });
|
204
|
+
}
|
205
|
+
|
206
|
+
time(3, 'optimized using PGO data' + log);
|
207
|
+
};
|
package/compiler/precompile.js
CHANGED
@@ -26,7 +26,7 @@ const compile = async (file, [ _funcs, _globals ]) => {
|
|
26
26
|
|
27
27
|
const porfCompile = (await import(`./index.js?_=${Date.now()}`)).default;
|
28
28
|
|
29
|
-
let { funcs, globals, data, exceptions } = porfCompile(source, ['module']);
|
29
|
+
let { funcs, globals, data, exceptions } = porfCompile(source, ['module', 'typed']);
|
30
30
|
|
31
31
|
const allocated = new Set();
|
32
32
|
|
@@ -87,6 +87,8 @@ const compile = async (file, [ _funcs, _globals ]) => {
|
|
87
87
|
};
|
88
88
|
|
89
89
|
const precompile = async () => {
|
90
|
+
if (globalThis._porf_loadParser) await globalThis._porf_loadParser('@babel/parser');
|
91
|
+
|
90
92
|
const dir = join(__dirname, 'builtins');
|
91
93
|
|
92
94
|
let funcs = [], globals = [];
|
package/compiler/prefs.js
CHANGED
@@ -23,9 +23,14 @@ const obj = new Proxy({}, {
|
|
23
23
|
// do not cache in web demo as args are changed live
|
24
24
|
if (!globalThis.document) cache[p] = ret;
|
25
25
|
return ret;
|
26
|
+
},
|
27
|
+
|
28
|
+
set(_, p, v) {
|
29
|
+
cache[p] = v;
|
30
|
+
return true;
|
26
31
|
}
|
27
32
|
});
|
28
33
|
|
29
|
-
|
34
|
+
export const uncache = () => cache = {};
|
30
35
|
|
31
36
|
export default obj;
|