porffor 0.16.0-fe07da0f4 → 0.17.0-55999d22b

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.
@@ -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
- // if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
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.allocLog) {
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 (process.version) process.exit();
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 && !Prefs.noRmUnusedTypes) {
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&& !Prefs.noRmUnusedTypes) {
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' && !Prefs.noRmUnusedTypes) {
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?.idx) {
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',
@@ -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
+ };
@@ -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
- if (x.exceptions) x.exceptions = x.exceptions.map(x => {
42
- const obj = exceptions[x];
43
- if (obj) obj.exceptId = x;
44
- return obj;
45
- }).filter(x => x);
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 = [];