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.
@@ -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 requires '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]],
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
- // if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
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) process.exit();
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?.idx) {
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',
@@ -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
+ };
@@ -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
- obj.uncache = () => cache = {};
34
+ export const uncache = () => cache = {};
30
35
 
31
36
  export default obj;