porffor 0.14.0-f67c123a1 → 0.16.0-053a03e10

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.
Files changed (43) hide show
  1. package/CONTRIBUTING.md +15 -9
  2. package/README.md +9 -13
  3. package/asur/index.js +1 -1
  4. package/compiler/2c.js +104 -51
  5. package/compiler/assemble.js +30 -4
  6. package/compiler/builtins/annexb_string.ts +1 -0
  7. package/compiler/builtins/array.ts +84 -4
  8. package/compiler/builtins/base64.ts +1 -0
  9. package/compiler/builtins/boolean.ts +3 -1
  10. package/compiler/builtins/console.ts +6 -0
  11. package/compiler/builtins/crypto.ts +1 -0
  12. package/compiler/builtins/date.ts +2 -0
  13. package/compiler/builtins/error.js +22 -0
  14. package/compiler/builtins/escape.ts +1 -2
  15. package/compiler/builtins/function.ts +2 -0
  16. package/compiler/builtins/int.ts +2 -0
  17. package/compiler/builtins/math.ts +410 -0
  18. package/compiler/builtins/number.ts +2 -0
  19. package/compiler/builtins/object.ts +2 -0
  20. package/compiler/builtins/porffor.d.ts +11 -0
  21. package/compiler/builtins/set.ts +20 -7
  22. package/compiler/builtins/string.ts +1 -0
  23. package/compiler/builtins/symbol.ts +62 -0
  24. package/compiler/builtins.js +50 -12
  25. package/compiler/codegen.js +576 -276
  26. package/compiler/cyclone.js +535 -0
  27. package/compiler/decompile.js +7 -1
  28. package/compiler/generated_builtins.js +555 -60
  29. package/compiler/havoc.js +93 -0
  30. package/compiler/index.js +80 -14
  31. package/compiler/opt.js +3 -39
  32. package/compiler/parse.js +2 -2
  33. package/compiler/pgo.js +206 -0
  34. package/compiler/precompile.js +5 -4
  35. package/compiler/prefs.js +12 -3
  36. package/compiler/prototype.js +180 -157
  37. package/compiler/wrap.js +127 -44
  38. package/no_pgo.txt +923 -0
  39. package/package.json +1 -1
  40. package/pgo.txt +916 -0
  41. package/runner/index.js +18 -12
  42. package/runner/repl.js +18 -2
  43. /package/runner/{profiler.js → profile.js} +0 -0
@@ -0,0 +1,93 @@
1
+ // havoc: wasm rewrite library
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;
@@ -12,14 +15,8 @@ globalThis.decompile = decompile;
12
15
  const logFuncs = (funcs, globals, exceptions) => {
13
16
  console.log('\n' + underline(bold('funcs')));
14
17
 
15
- const startIndex = funcs.sort((a, b) => a.index - b.index)[0].index;
16
18
  for (const f of funcs) {
17
- console.log(`${underline(f.name)} (${f.index - startIndex})`);
18
-
19
- console.log(`params: ${f.params.map((_, i) => Object.keys(f.locals)[Object.values(f.locals).indexOf(Object.values(f.locals).find(x => x.idx === i))]).join(', ')}`);
20
- console.log(`returns: ${f.returns.length > 0 ? true : false}`);
21
- console.log(`locals: ${Object.keys(f.locals).sort((a, b) => f.locals[a].idx - f.locals[b].idx).map(x => `${x} (${f.locals[x].idx})`).join(', ')}`);
22
- console.log();
19
+ if (f.internal) continue;
23
20
  console.log(decompile(f.wasm, f.name, f.index, f.locals, f.params, f.returns, funcs, globals, exceptions));
24
21
  }
25
22
 
@@ -30,6 +27,24 @@ const fs = (typeof process?.version !== 'undefined' ? (await import('node:fs'))
30
27
  const execSync = (typeof process?.version !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
31
28
 
32
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
+
33
48
  const t0 = performance.now();
34
49
  const program = parse(code, flags);
35
50
  if (Prefs.profileCompiler) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
@@ -42,14 +57,56 @@ export default (code, flags) => {
42
57
 
43
58
  const t2 = performance.now();
44
59
  opt(funcs, globals, pages, tags, exceptions);
45
- if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
46
60
 
47
- 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`);
48
103
 
49
104
  const t3 = performance.now();
50
105
  const wasm = assemble(funcs, globals, tags, pages, data, flags);
51
106
  if (Prefs.profileCompiler) console.log(`4. assembled in ${(performance.now() - t3).toFixed(2)}ms`);
52
107
 
108
+ if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
109
+
53
110
  if (Prefs.allocLog) {
54
111
  const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
55
112
  const bytes = wasmPages * 65536;
@@ -59,9 +116,6 @@ export default (code, flags) => {
59
116
 
60
117
  const out = { wasm, funcs, globals, tags, exceptions, pages, data };
61
118
 
62
- const target = Prefs.target ?? 'wasm';
63
- const outFile = Prefs.o;
64
-
65
119
  if (target === 'wasm' && outFile) {
66
120
  fs.writeFileSync(outFile, Buffer.from(wasm));
67
121
 
@@ -82,6 +136,8 @@ export default (code, flags) => {
82
136
  }
83
137
 
84
138
  if (target === 'native') {
139
+ outFile ??= Prefs.native ? './porffor_tmp' : file.split('/').at(-1).split('.').at(0, -1).join('.');
140
+
85
141
  let compiler = Prefs.compiler ?? 'clang';
86
142
  const cO = Prefs._cO ?? 'Ofast';
87
143
 
@@ -89,7 +145,8 @@ export default (code, flags) => {
89
145
  else compiler = [ compiler ];
90
146
 
91
147
  const tmpfile = 'porffor_tmp.c';
92
- const args = [ ...compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native', '-s', '-ffast-math', '-fno-exceptions' ];
148
+ const args = [ ...compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO ];
149
+ 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');
93
150
 
94
151
  const c = toc(out);
95
152
  fs.writeFileSync(tmpfile, c);
@@ -99,7 +156,16 @@ export default (code, flags) => {
99
156
 
100
157
  fs.unlinkSync(tmpfile);
101
158
 
102
- if (process.version) process.exit();
159
+ if (process.version) {
160
+ if (Prefs.native) {
161
+ const runArgs = process.argv.slice(2).filter(x => !x.startsWith('-'));
162
+ process.on('beforeExit', () => { fs.unlinkSync(outFile); });
163
+
164
+ execSync([ outFile, ...runArgs.slice(1) ].join(' '), { stdio: 'inherit' });
165
+ }
166
+
167
+ process.exit();
168
+ }
103
169
  }
104
170
 
105
171
  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
@@ -7,10 +7,10 @@ if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
7
7
  globalThis.process = { argv: ['', '', ...Deno.args], stdout: { write: str => Deno.writeAllSync(Deno.stdout, textEncoder.encode(str)) } };
8
8
  }
9
9
 
10
- const file = process.argv.slice(2).find(x => x[0] !== '-');
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
@@ -0,0 +1,206 @@
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 args = process.argv.slice(process.argv.indexOf('--pgo')).filter(x => !x.startsWith('-'));
91
+ const str = args[ind - 1];
92
+ if (!str) {
93
+ if (Prefs.pgoLog) console.log('\nPGO warning: script was expecting arguments, please specify args to use for PGO after --pgo arg');
94
+ return -1;
95
+ }
96
+
97
+ writeByteStr(exports.$, outPtr, str);
98
+ return str.length;
99
+ }
100
+ }, () => {});
101
+
102
+ exports.main();
103
+ } catch (e) {
104
+ throw e;
105
+ }
106
+
107
+ for (const x of funcs) {
108
+ const wasmFunc = wasmFuncs.find(y => y.name === x.name);
109
+ wasmFunc.wasm = wasmFunc.originalWasm;
110
+ delete wasmFunc.originalWasm;
111
+ }
112
+
113
+ if (abort) {
114
+ console.log('aborting PGO!');
115
+ return false;
116
+ }
117
+
118
+ time(1, `ran with PGO logging`);
119
+ time(2, 'processing PGO data...');
120
+
121
+ // process data
122
+ let log = '';
123
+ for (let i = 0; i < localData.length; i++) {
124
+ const func = funcs[i];
125
+ const total = localData[i].length;
126
+
127
+ const localKeys = Object.keys(func.locals).sort((a, b) => a.idx - b.idx);
128
+ const localValues = Object.values(func.locals).sort((a, b) => a.idx - b.idx);
129
+ func.localKeys = localKeys;
130
+ func.localValues = localValues;
131
+
132
+ let counts = new Array(10).fill(0);
133
+ const consistents = localData[i].map(x => {
134
+ if (x.length === 0 || !x.every((y, i) => i < 1 ? true : y === x[i - 1])) return false;
135
+
136
+ counts[0]++;
137
+ return x[0];
138
+ });
139
+
140
+ const integerOnlyF64s = localData[i].map((x, j) => {
141
+ if (localValues[j].type === Valtype.i32) return false; // already i32
142
+ if (x.length === 0 || !x.every(y => Number.isInteger(y))) return false;
143
+
144
+ counts[1]++;
145
+ return true;
146
+ });
147
+
148
+ func.consistents = consistents;
149
+ func.integerOnlyF64s = integerOnlyF64s;
150
+
151
+ log += ` ${func.name}: identified ${counts[0]}/${total} locals as consistent${Prefs.verbosePgo ? ':' : ''}\n`;
152
+ if (Prefs.verbosePgo) {
153
+ for (let j = 0; j < localData[i].length; j++) {
154
+ log += ` ${consistents[j] !== false ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m: ${new Set(localData[i][j]).size} unique values set\n`;
155
+ }
156
+ }
157
+
158
+ 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`;
159
+ if (Prefs.verbosePgo) {
160
+ for (let j = 0; j < localData[i].length; j++) {
161
+ if (localValues[j].type !== Valtype.f64) continue;
162
+ log += ` ${integerOnlyF64s[j] ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m\n`;
163
+ }
164
+
165
+ log += '\n';
166
+ }
167
+ }
168
+
169
+ time(2, 'processed PGO data' + log);
170
+ time(3, 'optimizing using PGO data...');
171
+
172
+ log = '';
173
+ for (const x of funcs) {
174
+ const wasmFunc = wasmFuncs.find(y => y.name === x.name);
175
+
176
+ let targets = [];
177
+ for (let i = 0; i < x.integerOnlyF64s.length; i++) {
178
+ const c = x.integerOnlyF64s[i];
179
+ if (c === false) continue;
180
+ if (i < x.params.length) continue;
181
+
182
+ targets.push(i);
183
+ }
184
+
185
+ log += ` ${x.name}: replaced ${targets.length} f64 locals with i32s\n`;
186
+ if (targets.length > 0) Havoc.f64ToI32s(wasmFunc, targets);
187
+
188
+ targets = [];
189
+ let consts = [];
190
+ for (let i = 0; i < x.consistents.length; i++) {
191
+ const c = x.consistents[i];
192
+ if (c === false) continue;
193
+ if (i < x.params.length) continue;
194
+
195
+ targets.push(i);
196
+
197
+ const valtype = x.localValues[i].type;
198
+ consts.push(number(c, valtype)[0]);
199
+ }
200
+
201
+ log += ` ${x.name}: replaced ${targets.length} locals with consts\n`;
202
+ if (targets.length > 0) Havoc.localsToConsts(wasmFunc, targets, consts, { localKeys: x.localKeys });
203
+ }
204
+
205
+ time(3, 'optimized using PGO data' + log);
206
+ };
@@ -18,9 +18,9 @@ const compile = async (file, [ _funcs, _globals ]) => {
18
18
  first = source.slice(0, source.indexOf('\n'));
19
19
  }
20
20
 
21
- let args = ['--bytestring', '--todo-time=compile', '--no-aot-pointer-opt', '--no-treeshake-wasm-imports', '--no-rm-unused-types', '--scoped-page-names', '--funsafe-no-unlikely-proto-checks', '--parse-types', '--opt-types'];
21
+ let args = ['--bytestring', '--todo-time=compile', '--truthy=no_nan_negative', '--no-treeshake-wasm-imports', '--no-rm-unused-types', '--scoped-page-names', '--funsafe-no-unlikely-proto-checks', '--fast-length', '--parse-types', '--opt-types'];
22
22
  if (first.startsWith('// @porf')) {
23
- args = args.concat(first.slice('// @porf '.length).split(' '));
23
+ args = first.slice('// @porf '.length).split(' ').concat(args);
24
24
  }
25
25
  process.argv = argv.concat(args);
26
26
 
@@ -111,8 +111,9 @@ ${funcs.map(x => {
111
111
  ${x.returnType != null ? `returnType: ${JSON.stringify(x.returnType)}` : 'typedReturns: true'},
112
112
  locals: ${JSON.stringify(Object.values(x.locals).slice(x.params.length).map(x => x.type))},
113
113
  localNames: ${JSON.stringify(Object.keys(x.locals))},
114
- ${x.data && x.data.length > 0 ? ` data: ${JSON.stringify(x.data)}` : ''}
115
- };`.replaceAll('\n\n', '\n').replaceAll('\n\n', '\n')
114
+ ${x.data && x.data.length > 0 ? ` data: ${JSON.stringify(x.data)},` : ''}
115
+ ${x.table ? ` table: true` : ''}
116
+ };`.replaceAll('\n\n', '\n').replaceAll('\n\n', '\n').replaceAll('\n\n', '\n');
116
117
  }).join('\n')}
117
118
  };`;
118
119
  };
package/compiler/prefs.js CHANGED
@@ -1,4 +1,4 @@
1
- const onByDefault = [ 'bytestring', 'aotPointerOpt', 'treeshakeWasmImports', 'alwaysMemory' ];
1
+ const onByDefault = [ 'bytestring', 'treeshakeWasmImports', 'alwaysMemory', 'indirectCalls', 'optUnused' ];
2
2
 
3
3
  let cache = {};
4
4
  const obj = new Proxy({}, {
@@ -6,7 +6,7 @@ const obj = new Proxy({}, {
6
6
  // intentionally misses with undefined values cached
7
7
  if (cache[p]) return cache[p];
8
8
 
9
- return cache[p] = (() => {
9
+ const ret = (() => {
10
10
  // fooBar -> foo-bar
11
11
  const name = p[0] === '_' ? p : p.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`);
12
12
  const prefix = name.length === 1 ? '-' : '--';
@@ -19,9 +19,18 @@ const obj = new Proxy({}, {
19
19
  if (onByDefault.includes(p)) return true;
20
20
  return undefined;
21
21
  })();
22
+
23
+ // do not cache in web demo as args are changed live
24
+ if (!globalThis.document) cache[p] = ret;
25
+ return ret;
26
+ },
27
+
28
+ set(_, p, v) {
29
+ cache[p] = v;
30
+ return true;
22
31
  }
23
32
  });
24
33
 
25
- obj.uncache = () => cache = {};
34
+ export const uncache = () => cache = {};
26
35
 
27
36
  export default obj;