porffor 0.16.0-ab08df866 → 0.16.0-b099006b8
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/compiler/assemble.js +5 -4
- package/compiler/builtins.js +2 -2
- package/compiler/codegen.js +5 -16
- package/compiler/cyclone.js +535 -0
- package/compiler/decompile.js +3 -1
- package/compiler/havoc.js +93 -0
- package/compiler/index.js +89 -6
- package/compiler/opt.js +1 -37
- package/compiler/pgo.js +207 -0
- package/compiler/prefs.js +6 -1
- package/compiler/wrap.js +53 -12
- package/no_pgo.txt +923 -0
- package/package.json +1 -1
- package/pgo.txt +916 -0
- package/runner/index.js +13 -8
- /package/runner/{profiler.js → profile.js} +0 -0
@@ -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
@@ -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/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 => {
|
135
|
+
if (x.length === 0 || !x.every((y, i) => i < 1 ? true : y === x[i - 1])) return false;
|
136
|
+
|
137
|
+
counts[0]++;
|
138
|
+
return x[0];
|
139
|
+
});
|
140
|
+
|
141
|
+
const integerOnlyF64s = localData[i].map((x, j) => {
|
142
|
+
if (localValues[j].type === Valtype.i32) return false; // already i32
|
143
|
+
if (x.length === 0 || !x.every(y => Number.isInteger(y))) return false;
|
144
|
+
|
145
|
+
counts[1]++;
|
146
|
+
return true;
|
147
|
+
});
|
148
|
+
|
149
|
+
func.consistents = consistents;
|
150
|
+
func.integerOnlyF64s = integerOnlyF64s;
|
151
|
+
|
152
|
+
log += ` ${func.name}: identified ${counts[0]}/${total} locals as consistent${Prefs.verbosePgo ? ':' : ''}\n`;
|
153
|
+
if (Prefs.verbosePgo) {
|
154
|
+
for (let j = 0; j < localData[i].length; j++) {
|
155
|
+
log += ` ${consistents[j] !== false ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m: ${new Set(localData[i][j]).size} unique values set\n`;
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
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`;
|
160
|
+
if (Prefs.verbosePgo) {
|
161
|
+
for (let j = 0; j < localData[i].length; j++) {
|
162
|
+
if (localValues[j].type !== Valtype.f64) continue;
|
163
|
+
log += ` ${integerOnlyF64s[j] ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m\n`;
|
164
|
+
}
|
165
|
+
|
166
|
+
log += '\n';
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
time(2, 'processed PGO data' + log);
|
171
|
+
time(3, 'optimizing using PGO data...');
|
172
|
+
|
173
|
+
log = '';
|
174
|
+
for (const x of funcs) {
|
175
|
+
const wasmFunc = wasmFuncs.find(y => y.name === x.name);
|
176
|
+
|
177
|
+
let targets = [];
|
178
|
+
for (let i = 0; i < x.integerOnlyF64s.length; i++) {
|
179
|
+
const c = x.integerOnlyF64s[i];
|
180
|
+
if (c === false) continue;
|
181
|
+
if (i < x.params.length) continue;
|
182
|
+
|
183
|
+
targets.push(i);
|
184
|
+
}
|
185
|
+
|
186
|
+
log += ` ${x.name}: replaced ${targets.length} f64 locals with i32s\n`;
|
187
|
+
if (targets.length > 0) Havoc.f64ToI32s(wasmFunc, targets);
|
188
|
+
|
189
|
+
targets = [];
|
190
|
+
let consts = [];
|
191
|
+
for (let i = 0; i < x.consistents.length; i++) {
|
192
|
+
const c = x.consistents[i];
|
193
|
+
if (c === false) continue;
|
194
|
+
if (i < x.params.length) 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/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;
|
package/compiler/wrap.js
CHANGED
@@ -6,8 +6,25 @@ import { TYPES } from './types.js';
|
|
6
6
|
import { log } from './log.js';
|
7
7
|
import Prefs from './prefs.js';
|
8
8
|
|
9
|
+
const fs = (typeof process?.version !== 'undefined' ? (await import('node:fs')) : undefined);
|
10
|
+
|
9
11
|
const bold = x => `\u001b[1m${x}\u001b[0m`;
|
10
12
|
|
13
|
+
export const readByteStr = (memory, ptr) => {
|
14
|
+
const length = (new Int32Array(memory.buffer, ptr, 1))[0];
|
15
|
+
return Array.from(new Uint8Array(memory.buffer, ptr + 4, length)).map(x => String.fromCharCode(x)).join('');
|
16
|
+
};
|
17
|
+
|
18
|
+
export const writeByteStr = (memory, ptr, str) => {
|
19
|
+
const length = str.length;
|
20
|
+
(new Int32Array(memory.buffer, ptr, 1))[0] = length;
|
21
|
+
|
22
|
+
const arr = new Uint8Array(memory.buffer, ptr + 4, length);
|
23
|
+
for (let i = 0; i < length; i++) {
|
24
|
+
arr[i] = str.charCodeAt(i);
|
25
|
+
}
|
26
|
+
};
|
27
|
+
|
11
28
|
const porfToJSValue = ({ memory, funcs, pages }, value, type) => {
|
12
29
|
switch (type) {
|
13
30
|
case TYPES.boolean: return Boolean(value);
|
@@ -101,28 +118,31 @@ const porfToJSValue = ({ memory, funcs, pages }, value, type) => {
|
|
101
118
|
}
|
102
119
|
};
|
103
120
|
|
104
|
-
export default
|
121
|
+
export default (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
|
105
122
|
const times = [];
|
106
123
|
|
107
124
|
const t1 = performance.now();
|
108
|
-
const { wasm, funcs, globals, tags, exceptions, pages, c } = compile(source, flags);
|
125
|
+
const { wasm, funcs, globals, tags, exceptions, pages, c } = typeof source === 'object' ? source : compile(source, flags);
|
109
126
|
|
110
127
|
globalThis.porfDebugInfo = { funcs, globals };
|
111
128
|
|
112
|
-
if (source.includes('export
|
129
|
+
if (source.includes?.('export ')) flags.push('module');
|
113
130
|
|
114
|
-
|
131
|
+
fs.writeFileSync('out.wasm', Buffer.from(wasm));
|
115
132
|
|
116
133
|
times.push(performance.now() - t1);
|
117
134
|
if (Prefs.profileCompiler) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
|
118
135
|
|
119
136
|
const backtrace = (funcInd, blobOffset) => {
|
120
|
-
if (funcInd == null || blobOffset == null
|
137
|
+
if (funcInd == null || blobOffset == null ||
|
138
|
+
Number.isNaN(funcInd) || Number.isNaN(blobOffset)) return false;
|
121
139
|
|
122
140
|
// convert blob offset -> function wasm offset.
|
123
141
|
// this is not good code and is somewhat duplicated
|
124
142
|
// I just want it to work for debugging, I don't care about perf/yes
|
125
143
|
const func = funcs.find(x => x.index === funcInd);
|
144
|
+
if (!func) return false;
|
145
|
+
|
126
146
|
const locals = Object.values(func.locals).sort((a, b) => a.idx - b.idx).slice(func.params.length).sort((a, b) => a.idx - b.idx);
|
127
147
|
|
128
148
|
let localDecl = [], typeCount = 0, lastType;
|
@@ -197,13 +217,15 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
197
217
|
|
198
218
|
let instance;
|
199
219
|
try {
|
200
|
-
let wasmEngine = WebAssembly;
|
201
|
-
if (Prefs.asur) {
|
202
|
-
|
203
|
-
|
204
|
-
}
|
205
|
-
|
206
|
-
0, { instance } = await wasmEngine.instantiate(wasm, {
|
220
|
+
// let wasmEngine = WebAssembly;
|
221
|
+
// if (Prefs.asur) {
|
222
|
+
// log.warning('wrap', 'using our !experimental! asur wasm engine instead of host to run');
|
223
|
+
// wasmEngine = await import('../asur/index.js');
|
224
|
+
// }
|
225
|
+
|
226
|
+
// 0, { instance } = await wasmEngine.instantiate(wasm, {
|
227
|
+
const module = new WebAssembly.Module(wasm);
|
228
|
+
instance = new WebAssembly.Instance(module, {
|
207
229
|
'': {
|
208
230
|
p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
|
209
231
|
c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
|
@@ -211,12 +233,31 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
|
|
211
233
|
u: () => performance.timeOrigin,
|
212
234
|
y: () => {},
|
213
235
|
z: () => {},
|
236
|
+
w: (ind, outPtr) => { // readArgv
|
237
|
+
const args = process.argv.slice(2).filter(x => !x.startsWith('-'));
|
238
|
+
const str = args[ind];
|
239
|
+
if (!str) return -1;
|
240
|
+
|
241
|
+
writeByteStr(memory, outPtr, str);
|
242
|
+
return str.length;
|
243
|
+
},
|
244
|
+
q: (pathPtr, outPtr) => { // readFile
|
245
|
+
try {
|
246
|
+
const path = readByteStr(memory, pathPtr);
|
247
|
+
const contents = fs.readFileSync(path, 'utf8');
|
248
|
+
writeByteStr(memory, outPtr, contents);
|
249
|
+
return contents.length;
|
250
|
+
} catch {
|
251
|
+
return -1;
|
252
|
+
}
|
253
|
+
},
|
214
254
|
...customImports
|
215
255
|
}
|
216
256
|
});
|
217
257
|
} catch (e) {
|
218
258
|
// only backtrace for runner, not test262/etc
|
219
259
|
if (!process.argv[1].includes('/runner')) throw e;
|
260
|
+
if (!(e instanceof WebAssembly.CompileError)) throw e;
|
220
261
|
|
221
262
|
const funcInd = parseInt(e.message.match(/function #([0-9]+) /)?.[1]);
|
222
263
|
const blobOffset = parseInt(e.message.split('@')?.[1]);
|