porffor 0.16.0-fe07da0f4 → 0.17.0-048c6f2ee
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 +2 -2
- package/README.md +5 -17
- package/compiler/2c.js +123 -75
- package/compiler/allocators.js +128 -0
- package/compiler/assemble.js +12 -5
- package/compiler/builtins/array.ts +72 -5
- package/compiler/builtins/base64.ts +28 -24
- package/compiler/builtins/date.ts +3 -30
- package/compiler/builtins/number.ts +10 -21
- package/compiler/builtins/porffor.d.ts +10 -0
- package/compiler/builtins/set.ts +7 -5
- package/compiler/builtins/string_f64.ts +10 -0
- package/compiler/builtins/z_ecma262.ts +62 -0
- package/compiler/builtins.js +26 -6
- package/compiler/codegen.js +364 -393
- package/compiler/cyclone.js +535 -0
- package/compiler/decompile.js +3 -1
- package/compiler/generated_builtins.js +153 -77
- package/compiler/havoc.js +93 -0
- package/compiler/index.js +104 -7
- package/compiler/opt.js +10 -44
- package/compiler/parse.js +2 -8
- package/compiler/pgo.js +212 -0
- package/compiler/precompile.js +12 -7
- package/compiler/prefs.js +8 -4
- package/compiler/prototype.js +34 -43
- package/compiler/wasmSpec.js +2 -2
- package/compiler/wrap.js +72 -20
- package/package.json +3 -5
- package/runner/index.js +26 -11
- /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,26 @@ 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
|
+
|
48
|
+
if (Prefs.profileCompiler) console.log(`0. began compilation (host runtime startup) in ${performance.now().toFixed(2)}ms`);
|
49
|
+
|
26
50
|
const t0 = performance.now();
|
27
51
|
const program = parse(code, flags);
|
28
52
|
if (Prefs.profileCompiler) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
|
@@ -35,9 +59,49 @@ export default (code, flags) => {
|
|
35
59
|
|
36
60
|
const t2 = performance.now();
|
37
61
|
opt(funcs, globals, pages, tags, exceptions);
|
38
|
-
if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
|
39
62
|
|
40
|
-
|
63
|
+
if (Prefs.pgo) {
|
64
|
+
if (Prefs.pgoLog) {
|
65
|
+
const oldSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
|
66
|
+
const t = performance.now();
|
67
|
+
|
68
|
+
pgo.run({ funcs, globals, tags, exceptions, pages, data });
|
69
|
+
opt(funcs, globals, pages, tags, exceptions);
|
70
|
+
|
71
|
+
console.log(`PGO total time: ${(performance.now() - t).toFixed(2)}ms`);
|
72
|
+
|
73
|
+
const newSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
|
74
|
+
console.log(`PGO size diff: ${oldSize - newSize} bytes (${oldSize} -> ${newSize})\n`);
|
75
|
+
} else {
|
76
|
+
pgo.run({ funcs, globals, tags, exceptions, pages, data });
|
77
|
+
opt(funcs, globals, pages, tags, exceptions);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
if (Prefs.cyclone) {
|
82
|
+
if (Prefs.cycloneLog) {
|
83
|
+
const oldSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
|
84
|
+
const t = performance.now();
|
85
|
+
|
86
|
+
for (const x of funcs) {
|
87
|
+
const preOps = x.wasm.length;
|
88
|
+
cyclone(x.wasm);
|
89
|
+
|
90
|
+
console.log(`${x.name}: ${preOps} -> ${x.wasm.length} ops`);
|
91
|
+
}
|
92
|
+
|
93
|
+
console.log(`cyclone total time: ${(performance.now() - t).toFixed(2)}ms`);
|
94
|
+
|
95
|
+
const newSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
|
96
|
+
console.log(`cyclone size diff: ${oldSize - newSize} bytes (${oldSize} -> ${newSize})\n`);
|
97
|
+
} else {
|
98
|
+
for (const x of funcs) {
|
99
|
+
cyclone(x.wasm);
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
|
41
105
|
|
42
106
|
const t3 = performance.now();
|
43
107
|
const wasm = assemble(funcs, globals, tags, pages, data, flags);
|
@@ -45,7 +109,7 @@ export default (code, flags) => {
|
|
45
109
|
|
46
110
|
if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
|
47
111
|
|
48
|
-
if (Prefs.
|
112
|
+
if (Prefs.compileAllocLog) {
|
49
113
|
const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
|
50
114
|
const bytes = wasmPages * 65536;
|
51
115
|
log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
|
@@ -54,9 +118,6 @@ export default (code, flags) => {
|
|
54
118
|
|
55
119
|
const out = { wasm, funcs, globals, tags, exceptions, pages, data };
|
56
120
|
|
57
|
-
const target = Prefs.target ?? 'wasm';
|
58
|
-
const outFile = Prefs.o;
|
59
|
-
|
60
121
|
if (target === 'wasm' && outFile) {
|
61
122
|
fs.writeFileSync(outFile, Buffer.from(wasm));
|
62
123
|
|
@@ -77,6 +138,8 @@ export default (code, flags) => {
|
|
77
138
|
}
|
78
139
|
|
79
140
|
if (target === 'native') {
|
141
|
+
outFile ??= Prefs.native ? './porffor_tmp' : file.split('/').at(-1).split('.').at(0, -1).join('.');
|
142
|
+
|
80
143
|
let compiler = Prefs.compiler ?? 'clang';
|
81
144
|
const cO = Prefs._cO ?? 'Ofast';
|
82
145
|
|
@@ -87,7 +150,12 @@ export default (code, flags) => {
|
|
87
150
|
const args = [ ...compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO ];
|
88
151
|
if (!Prefs.compiler) args.push('-flto=thin', '-march=native', '-s', '-ffast-math', '-fno-exceptions', '-fno-ident', '-fno-asynchronous-unwind-tables', '-ffunction-sections', '-fdata-sections', '-Wl,--gc-sections');
|
89
152
|
|
153
|
+
const t4 = performance.now();
|
90
154
|
const c = toc(out);
|
155
|
+
if (Prefs.profileCompiler) console.log(`5. compiled to c in ${(performance.now() - t4).toFixed(2)}ms`);
|
156
|
+
|
157
|
+
const t5 = performance.now();
|
158
|
+
|
91
159
|
fs.writeFileSync(tmpfile, c);
|
92
160
|
|
93
161
|
// obvious command escape is obvious
|
@@ -95,7 +163,36 @@ export default (code, flags) => {
|
|
95
163
|
|
96
164
|
fs.unlinkSync(tmpfile);
|
97
165
|
|
98
|
-
if (
|
166
|
+
if (Prefs.profileCompiler) console.log(`6. compiled to native (using ${compiler}) in ${(performance.now() - t5).toFixed(2)}ms`);
|
167
|
+
|
168
|
+
if (process.version) {
|
169
|
+
if (Prefs.native) {
|
170
|
+
const cleanup = () => {
|
171
|
+
try {
|
172
|
+
fs.unlinkSync(outFile);
|
173
|
+
} catch {}
|
174
|
+
};
|
175
|
+
|
176
|
+
process.on('exit', cleanup);
|
177
|
+
process.on('beforeExit', cleanup);
|
178
|
+
process.on('SIGINT', () => {
|
179
|
+
cleanup();
|
180
|
+
process.exit();
|
181
|
+
});
|
182
|
+
|
183
|
+
const runArgs = process.argv.slice(2).filter(x => !x.startsWith('-'));
|
184
|
+
try {
|
185
|
+
execSync([ outFile, ...runArgs.slice(1) ].join(' '), { stdio: 'inherit' });
|
186
|
+
} catch {}
|
187
|
+
}
|
188
|
+
|
189
|
+
if (!Prefs.native && globalThis.file) {
|
190
|
+
const total = performance.now();
|
191
|
+
console.log(`\u001b[90m[${total.toFixed(2)}ms]\u001b[0m \u001b[92mcompiled ${globalThis.file} -> ${outFile}\u001b[0m`);
|
192
|
+
}
|
193
|
+
|
194
|
+
process.exit();
|
195
|
+
}
|
99
196
|
}
|
100
197
|
|
101
198
|
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) {
|
@@ -125,6 +125,8 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
125
125
|
// main pass
|
126
126
|
for (let i = 0; i < wasm.length; i++) {
|
127
127
|
let inst = wasm[i];
|
128
|
+
inst = [ ...inst ];
|
129
|
+
wasm[i] = inst;
|
128
130
|
|
129
131
|
if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) depth.push(inst[0]);
|
130
132
|
if (inst[0] === Opcodes.end) depth.pop();
|
@@ -172,14 +174,14 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
172
174
|
}
|
173
175
|
}
|
174
176
|
|
175
|
-
if (inst[inst.length - 1] === 'string_only' && !pages.hasAnyString &&
|
177
|
+
if (inst[inst.length - 1] === 'string_only' && !pages.hasAnyString && Prefs.rmUnusedTypes) {
|
176
178
|
// remove this inst
|
177
179
|
wasm.splice(i, 1);
|
178
180
|
if (i > 0) i--;
|
179
181
|
inst = wasm[i];
|
180
182
|
}
|
181
183
|
|
182
|
-
if (inst[inst.length - 1] === 'string_only|start' && !pages.hasAnyString&&
|
184
|
+
if (inst[inst.length - 1] === 'string_only|start' && !pages.hasAnyString && Prefs.rmUnusedTypes) {
|
183
185
|
let j = i;
|
184
186
|
for (; j < wasm.length; j++) {
|
185
187
|
const op = wasm[j];
|
@@ -193,7 +195,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
193
195
|
inst = wasm[i];
|
194
196
|
}
|
195
197
|
|
196
|
-
if (inst[0] === Opcodes.if && typeof inst[2] === 'string' &&
|
198
|
+
if (inst[0] === Opcodes.if && typeof inst[2] === 'string' && Prefs.rmUnusedTypes) {
|
197
199
|
// remove unneeded typeswitch checks
|
198
200
|
|
199
201
|
const type = inst[2].split('|')[1];
|
@@ -221,7 +223,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
221
223
|
}
|
222
224
|
}
|
223
225
|
|
224
|
-
if (inst[0] === Opcodes.end && inst[1] === 'TYPESWITCH_end') {
|
226
|
+
if (inst[0] === Opcodes.end && inst[1] === 'TYPESWITCH_end' && Prefs.rmUnusedTypes) {
|
225
227
|
// remove unneeded entire typeswitch
|
226
228
|
|
227
229
|
let j = i - 1, depth = -1, checks = 0;
|
@@ -248,7 +250,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
248
250
|
}
|
249
251
|
|
250
252
|
// remove setting last type if it is never gotten
|
251
|
-
if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType
|
253
|
+
if (!f.internal && !f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType) {
|
252
254
|
// replace this inst with drop
|
253
255
|
wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
|
254
256
|
if (i > 0) i--;
|
@@ -354,7 +356,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
354
356
|
continue;
|
355
357
|
}
|
356
358
|
|
357
|
-
if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
|
359
|
+
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
360
|
// change const and immediate i32 convert to i32 const
|
359
361
|
// f64.const 0
|
360
362
|
// i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
|
@@ -369,7 +371,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
369
371
|
continue;
|
370
372
|
}
|
371
373
|
|
372
|
-
if (lastInst[0] === Opcodes.i32_const && (inst === Opcodes.i32_from || inst === Opcodes.i32_from_u)) {
|
374
|
+
if (lastInst[0] === Opcodes.i32_const && (inst[0] === Opcodes.i32_from[0] || inst[0] === Opcodes.i32_from_u[0])) {
|
373
375
|
// change i32 const and immediate convert to const (opposite way of previous)
|
374
376
|
// i32.const 0
|
375
377
|
// f64.convert_i32_s || f64.convert_i32_u
|
@@ -504,42 +506,6 @@ export default (funcs, globals, pages, tags, exceptions) => {
|
|
504
506
|
const useCount = {};
|
505
507
|
for (const x in f.locals) useCount[f.locals[x].idx] = 0;
|
506
508
|
|
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
509
|
const localIdxs = Object.values(f.locals).map(x => x.idx);
|
544
510
|
// remove unused locals (cleanup)
|
545
511
|
for (const x in useCount) {
|
package/compiler/parse.js
CHANGED
@@ -1,16 +1,10 @@
|
|
1
1
|
import { log } from './log.js';
|
2
2
|
import Prefs from './prefs.js';
|
3
3
|
|
4
|
-
// deno compat
|
5
|
-
if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
|
6
|
-
const textEncoder = new TextEncoder();
|
7
|
-
globalThis.process = { argv: ['', '', ...Deno.args], stdout: { write: str => Deno.writeAllSync(Deno.stdout, textEncoder.encode(str)) } };
|
8
|
-
}
|
9
|
-
|
10
4
|
const file = process.argv.slice(2).find(x => x[0] !== '-' && !['run', 'wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm'].includes(x));
|
11
5
|
|
12
6
|
// should we try to support types (while parsing)
|
13
|
-
const types = Prefs.parseTypes || file?.endsWith('.ts');
|
7
|
+
const types = Prefs.parseTypes || Prefs.t || file?.endsWith('.ts');
|
14
8
|
globalThis.typedInput = types && Prefs.optTypes;
|
15
9
|
|
16
10
|
// todo: review which to use by default
|
@@ -43,7 +37,7 @@ export default (input, flags) => {
|
|
43
37
|
webcompat: true,
|
44
38
|
|
45
39
|
// babel
|
46
|
-
plugins: types ? ['estree', 'typescript'] : ['estree'],
|
40
|
+
plugins: types || flags.includes('typed') ? ['estree', 'typescript'] : ['estree'],
|
47
41
|
|
48
42
|
// multiple
|
49
43
|
sourceType: flags.includes('module') ? 'module' : 'script',
|
package/compiler/pgo.js
ADDED
@@ -0,0 +1,212 @@
|
|
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
|
+
Prefs._profileCompiler = Prefs.profileCompiler;
|
82
|
+
Prefs.profileCompiler = false;
|
83
|
+
|
84
|
+
const { exports } = wrap(obj, [], {
|
85
|
+
y: n => {
|
86
|
+
activeFunc = n;
|
87
|
+
},
|
88
|
+
z: (i, n) => {
|
89
|
+
if (activeFunc == null) throw 'fail';
|
90
|
+
localData[activeFunc][i].push(n);
|
91
|
+
},
|
92
|
+
w: (ind, outPtr) => { // readArgv
|
93
|
+
const pgoInd = process.argv.indexOf('--pgo');
|
94
|
+
const args = process.argv.slice(pgoInd).filter(x => !x.startsWith('-'));
|
95
|
+
const str = args[ind - 1];
|
96
|
+
if (pgoInd === -1 || !str) {
|
97
|
+
if (Prefs.pgoLog) console.log('\nPGO warning: script was expecting arguments, please specify args to use for PGO after --pgo arg');
|
98
|
+
return -1;
|
99
|
+
}
|
100
|
+
|
101
|
+
writeByteStr(exports.$, outPtr, str);
|
102
|
+
return str.length;
|
103
|
+
}
|
104
|
+
}, () => {});
|
105
|
+
|
106
|
+
exports.main();
|
107
|
+
} catch (e) {
|
108
|
+
throw e;
|
109
|
+
}
|
110
|
+
|
111
|
+
Prefs.profileCompiler = Prefs._profileCompiler;
|
112
|
+
|
113
|
+
for (const x of funcs) {
|
114
|
+
const wasmFunc = wasmFuncs.find(y => y.name === x.name);
|
115
|
+
wasmFunc.wasm = wasmFunc.originalWasm;
|
116
|
+
delete wasmFunc.originalWasm;
|
117
|
+
}
|
118
|
+
|
119
|
+
if (abort) {
|
120
|
+
console.log('aborting PGO!');
|
121
|
+
return false;
|
122
|
+
}
|
123
|
+
|
124
|
+
time(1, `ran with PGO logging`);
|
125
|
+
time(2, 'processing PGO data...');
|
126
|
+
|
127
|
+
// process data
|
128
|
+
let log = '';
|
129
|
+
for (let i = 0; i < localData.length; i++) {
|
130
|
+
const func = funcs[i];
|
131
|
+
const total = localData[i].length;
|
132
|
+
|
133
|
+
const localKeys = Object.keys(func.locals).sort((a, b) => a.idx - b.idx);
|
134
|
+
const localValues = Object.values(func.locals).sort((a, b) => a.idx - b.idx);
|
135
|
+
func.localKeys = localKeys;
|
136
|
+
func.localValues = localValues;
|
137
|
+
|
138
|
+
let counts = new Array(10).fill(0);
|
139
|
+
const consistents = localData[i].map((x, j) => {
|
140
|
+
if (j < func.params.length) return false; // param
|
141
|
+
if (x.length === 0 || !x.every((y, i) => i < 1 ? true : y === x[i - 1])) return false; // not consistent
|
142
|
+
|
143
|
+
counts[0]++;
|
144
|
+
return x[0];
|
145
|
+
});
|
146
|
+
|
147
|
+
const integerOnlyF64s = localData[i].map((x, j) => {
|
148
|
+
if (j < func.params.length) return false; // param
|
149
|
+
if (localValues[j].type === Valtype.i32) return false; // already i32
|
150
|
+
if (x.length === 0 || !x.every(y => Number.isInteger(y))) return false; // not all integer values
|
151
|
+
|
152
|
+
counts[1]++;
|
153
|
+
return true;
|
154
|
+
});
|
155
|
+
|
156
|
+
func.consistents = consistents;
|
157
|
+
func.integerOnlyF64s = integerOnlyF64s;
|
158
|
+
|
159
|
+
log += ` ${func.name}: identified ${counts[0]}/${total} locals as consistent${Prefs.verbosePgo ? ':' : ''}\n`;
|
160
|
+
if (Prefs.verbosePgo) {
|
161
|
+
for (let j = func.params.length; j < localData[i].length; j++) {
|
162
|
+
log += ` ${consistents[j] !== false ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m: ${new Set(localData[i][j]).size} unique values set\n`;
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
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`;
|
167
|
+
if (Prefs.verbosePgo) {
|
168
|
+
for (let j = func.params.length; j < localData[i].length; j++) {
|
169
|
+
if (localValues[j].type !== Valtype.f64) continue;
|
170
|
+
log += ` ${integerOnlyF64s[j] ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m\n`;
|
171
|
+
}
|
172
|
+
|
173
|
+
log += '\n';
|
174
|
+
}
|
175
|
+
}
|
176
|
+
|
177
|
+
time(2, 'processed PGO data' + log);
|
178
|
+
time(3, 'optimizing using PGO data...');
|
179
|
+
|
180
|
+
log = '';
|
181
|
+
for (const x of funcs) {
|
182
|
+
const wasmFunc = wasmFuncs.find(y => y.name === x.name);
|
183
|
+
|
184
|
+
let targets = [];
|
185
|
+
for (let i = 0; i < x.integerOnlyF64s.length; i++) {
|
186
|
+
const c = x.integerOnlyF64s[i];
|
187
|
+
if (c === false) continue;
|
188
|
+
|
189
|
+
targets.push(i);
|
190
|
+
}
|
191
|
+
|
192
|
+
log += ` ${x.name}: replaced ${targets.length} f64 locals with i32s\n`;
|
193
|
+
if (targets.length > 0) Havoc.f64ToI32s(wasmFunc, targets);
|
194
|
+
|
195
|
+
targets = [];
|
196
|
+
let consts = [];
|
197
|
+
for (let i = 0; i < x.consistents.length; i++) {
|
198
|
+
const c = x.consistents[i];
|
199
|
+
if (c === false) continue;
|
200
|
+
|
201
|
+
targets.push(i);
|
202
|
+
|
203
|
+
const valtype = x.localValues[i].type;
|
204
|
+
consts.push(number(c, valtype)[0]);
|
205
|
+
}
|
206
|
+
|
207
|
+
log += ` ${x.name}: replaced ${targets.length} locals with consts\n`;
|
208
|
+
if (targets.length > 0) Havoc.localsToConsts(wasmFunc, targets, consts, { localKeys: x.localKeys });
|
209
|
+
}
|
210
|
+
|
211
|
+
time(3, 'optimized using PGO data' + log);
|
212
|
+
};
|
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
|
|
@@ -35,14 +35,17 @@ const compile = async (file, [ _funcs, _globals ]) => {
|
|
35
35
|
if (x.data) {
|
36
36
|
x.data = x.data.map(x => data[x]);
|
37
37
|
for (const y in x.data) {
|
38
|
-
x.data[y].offset -= x.data[0].offset;
|
38
|
+
if (x.data[y].offset != null) x.data[y].offset -= x.data[0].offset;
|
39
39
|
}
|
40
40
|
}
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
|
42
|
+
if (x.exceptions) {
|
43
|
+
x.exceptions = x.exceptions.map(x => {
|
44
|
+
const obj = exceptions[x];
|
45
|
+
if (obj) obj.exceptId = x;
|
46
|
+
return obj;
|
47
|
+
}).filter(x => x);
|
48
|
+
}
|
46
49
|
|
47
50
|
const locals = Object.keys(x.locals).reduce((acc, y) => {
|
48
51
|
acc[x.locals[y].idx] = { ...x.locals[y], name: y };
|
@@ -87,6 +90,8 @@ const compile = async (file, [ _funcs, _globals ]) => {
|
|
87
90
|
};
|
88
91
|
|
89
92
|
const precompile = async () => {
|
93
|
+
if (globalThis._porf_loadParser) await globalThis._porf_loadParser('@babel/parser');
|
94
|
+
|
90
95
|
const dir = join(__dirname, 'builtins');
|
91
96
|
|
92
97
|
let funcs = [], globals = [];
|