porffor 0.2.0-fde989a → 0.14.0-0d97d1e6a

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 (61) hide show
  1. package/CONTRIBUTING.md +256 -0
  2. package/LICENSE +20 -20
  3. package/README.md +131 -86
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +216 -0
  7. package/compiler/2c.js +2 -53
  8. package/compiler/{sections.js → assemble.js} +95 -21
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +18 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +76 -0
  13. package/compiler/builtins/boolean.ts +18 -0
  14. package/compiler/builtins/crypto.ts +120 -0
  15. package/compiler/builtins/date.ts +2067 -0
  16. package/compiler/builtins/escape.ts +141 -0
  17. package/compiler/builtins/function.ts +5 -0
  18. package/compiler/builtins/int.ts +145 -0
  19. package/compiler/builtins/number.ts +529 -0
  20. package/compiler/builtins/object.ts +4 -0
  21. package/compiler/builtins/porffor.d.ts +60 -0
  22. package/compiler/builtins/set.ts +187 -0
  23. package/compiler/builtins/string.ts +1080 -0
  24. package/compiler/builtins/symbol.ts +61 -0
  25. package/compiler/builtins.js +440 -285
  26. package/compiler/{codeGen.js → codegen.js} +1116 -489
  27. package/compiler/decompile.js +3 -4
  28. package/compiler/embedding.js +22 -22
  29. package/compiler/encoding.js +94 -10
  30. package/compiler/expression.js +1 -1
  31. package/compiler/generated_builtins.js +1670 -0
  32. package/compiler/index.js +27 -43
  33. package/compiler/log.js +6 -3
  34. package/compiler/opt.js +55 -41
  35. package/compiler/parse.js +38 -30
  36. package/compiler/precompile.js +120 -0
  37. package/compiler/prefs.js +31 -0
  38. package/compiler/prototype.js +31 -46
  39. package/compiler/types.js +38 -0
  40. package/compiler/wasmSpec.js +33 -8
  41. package/compiler/wrap.js +107 -71
  42. package/package.json +9 -5
  43. package/porf +2 -0
  44. package/rhemyn/compile.js +46 -27
  45. package/rhemyn/parse.js +322 -320
  46. package/rhemyn/test/parse.js +58 -58
  47. package/runner/compare.js +33 -34
  48. package/runner/debug.js +117 -0
  49. package/runner/index.js +78 -11
  50. package/runner/profiler.js +75 -0
  51. package/runner/repl.js +40 -13
  52. package/runner/sizes.js +37 -37
  53. package/runner/version.js +10 -8
  54. package/compiler/builtins/base64.js +0 -92
  55. package/filesize.cmd +0 -2
  56. package/runner/info.js +0 -89
  57. package/runner/profile.js +0 -46
  58. package/runner/results.json +0 -1
  59. package/runner/transform.js +0 -15
  60. package/tmp.c +0 -661
  61. package/util/enum.js +0 -20
package/compiler/index.js CHANGED
@@ -1,80 +1,64 @@
1
1
  import { underline, bold, log } from './log.js';
2
2
  import parse from './parse.js';
3
- import codeGen from './codeGen.js';
3
+ import codegen from './codegen.js';
4
4
  import opt from './opt.js';
5
- import produceSections from './sections.js';
5
+ import assemble from './assemble.js';
6
6
  import decompile from './decompile.js';
7
- import { BuiltinPreludes } from './builtins.js';
8
7
  import toc from './2c.js';
8
+ import Prefs from './prefs.js';
9
9
 
10
10
  globalThis.decompile = decompile;
11
11
 
12
12
  const logFuncs = (funcs, globals, exceptions) => {
13
13
  console.log('\n' + underline(bold('funcs')));
14
14
 
15
- const startIndex = funcs.sort((a, b) => a.index - b.index)[0].index;
16
15
  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();
23
16
  console.log(decompile(f.wasm, f.name, f.index, f.locals, f.params, f.returns, funcs, globals, exceptions));
24
17
  }
25
18
 
26
19
  console.log();
27
20
  };
28
21
 
29
- const getArg = name => process.argv.find(x => x.startsWith(`-${name}=`))?.slice(name.length + 2);
30
-
31
- const writeFileSync = (typeof process?.version !== 'undefined' ? (await import('node:fs')).writeFileSync : undefined);
22
+ const fs = (typeof process?.version !== 'undefined' ? (await import('node:fs')) : undefined);
32
23
  const execSync = (typeof process?.version !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
33
24
 
34
25
  export default (code, flags) => {
35
- globalThis.optLog = process.argv.includes('-opt-log');
36
- globalThis.codeLog = process.argv.includes('-code-log');
37
- globalThis.allocLog = process.argv.includes('-alloc-log');
38
- globalThis.regexLog = process.argv.includes('-regex-log');
39
-
40
- for (const x in BuiltinPreludes) {
41
- if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
42
- }
43
-
44
26
  const t0 = performance.now();
45
27
  const program = parse(code, flags);
46
- if (flags.includes('info')) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
28
+ if (Prefs.profileCompiler) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
47
29
 
48
30
  const t1 = performance.now();
49
- const { funcs, globals, tags, exceptions, pages, data } = codeGen(program);
50
- if (flags.includes('info')) console.log(`2. generated code in ${(performance.now() - t1).toFixed(2)}ms`);
31
+ const { funcs, globals, tags, exceptions, pages, data } = codegen(program);
32
+ if (Prefs.profileCompiler) console.log(`2. generated code in ${(performance.now() - t1).toFixed(2)}ms`);
51
33
 
52
- if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
34
+ if (Prefs.funcs) logFuncs(funcs, globals, exceptions);
53
35
 
54
36
  const t2 = performance.now();
55
- opt(funcs, globals, pages, tags);
56
- if (flags.includes('info')) console.log(`3. optimized code in ${(performance.now() - t2).toFixed(2)}ms`);
37
+ opt(funcs, globals, pages, tags, exceptions);
38
+ if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
57
39
 
58
- if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
40
+ // if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
59
41
 
60
42
  const t3 = performance.now();
61
- const sections = produceSections(funcs, globals, tags, pages, data, flags);
62
- if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
43
+ const wasm = assemble(funcs, globals, tags, pages, data, flags);
44
+ if (Prefs.profileCompiler) console.log(`4. assembled in ${(performance.now() - t3).toFixed(2)}ms`);
45
+
46
+ if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
63
47
 
64
- if (allocLog) {
48
+ if (Prefs.allocLog) {
65
49
  const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
66
50
  const bytes = wasmPages * 65536;
67
51
  log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
68
52
  console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
69
53
  }
70
54
 
71
- const out = { wasm: sections, funcs, globals, tags, exceptions, pages, data };
55
+ const out = { wasm, funcs, globals, tags, exceptions, pages, data };
72
56
 
73
- const target = getArg('target') ?? getArg('t') ?? 'wasm';
74
- const outFile = getArg('o');
57
+ const target = Prefs.target ?? 'wasm';
58
+ const outFile = Prefs.o;
75
59
 
76
60
  if (target === 'wasm' && outFile) {
77
- writeFileSync(outFile, Buffer.from(sections));
61
+ fs.writeFileSync(outFile, Buffer.from(wasm));
78
62
 
79
63
  if (process.version) process.exit();
80
64
  }
@@ -84,7 +68,7 @@ export default (code, flags) => {
84
68
  out.c = c;
85
69
 
86
70
  if (outFile) {
87
- writeFileSync(outFile, c);
71
+ fs.writeFileSync(outFile, c);
88
72
  } else {
89
73
  console.log(c);
90
74
  }
@@ -93,23 +77,23 @@ export default (code, flags) => {
93
77
  }
94
78
 
95
79
  if (target === 'native') {
96
- let compiler = getArg('compiler') ?? 'clang';
97
- const cO = getArg('cO') ?? 'Ofast';
80
+ let compiler = Prefs.compiler ?? 'clang';
81
+ const cO = Prefs._cO ?? 'Ofast';
98
82
 
99
83
  if (compiler === 'zig') compiler = [ 'zig', 'cc' ];
100
84
  else compiler = [ compiler ];
101
85
 
102
- const tmpfile = 'tmp.c';
103
- // const args = [ compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native', '-s', '-fno-unwind-tables', '-fno-asynchronous-unwind-tables', '-ffunction-sections', '-fdata-sections', '-Wl', '-fno-ident', '-fno-exceptions', '-ffast-math' ];
104
- // const args = [ ...compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native', '-s', '-ffast-math', '-fno-exceptions', '-target', 'x86_64-linux' ];
86
+ const tmpfile = 'porffor_tmp.c';
105
87
  const args = [ ...compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native', '-s', '-ffast-math', '-fno-exceptions' ];
106
88
 
107
89
  const c = toc(out);
108
- writeFileSync(tmpfile, c);
90
+ fs.writeFileSync(tmpfile, c);
109
91
 
110
92
  // obvious command escape is obvious
111
93
  execSync(args.join(' '), { stdio: 'inherit' });
112
94
 
95
+ fs.unlinkSync(tmpfile);
96
+
113
97
  if (process.version) process.exit();
114
98
  }
115
99
 
package/compiler/log.js CHANGED
@@ -5,11 +5,14 @@ export const bold = x => `\u001b[1m${x}\u001b[0m`;
5
5
  const areaColors = {
6
6
  codegen: [ 20, 80, 250 ],
7
7
  opt: [ 250, 20, 80 ],
8
- sections: [ 20, 250, 80 ],
8
+ assemble: [ 20, 250, 80 ],
9
9
  alloc: [ 250, 250, 20 ],
10
- parser: [ 240, 240, 240 ],
11
- '2c': [ 20, 250, 250 ]
10
+ parse: [ 240, 240, 240 ],
11
+ '2c': [ 20, 250, 250 ],
12
+ wrap: [ 250, 100, 20 ]
12
13
  };
13
14
 
15
+ // for (const x in areaColors) console.log(rgb(areaColors[x][0], areaColors[x][1], areaColors[x][2], x));
16
+
14
17
  export const log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
15
18
  log.warning = (area, ...args) => log(area, '\u001b[93m' + args[0], ...args.slice(1), '\u001b[0m');
package/compiler/opt.js CHANGED
@@ -1,7 +1,8 @@
1
- import { Opcodes, Valtype } from "./wasmSpec.js";
2
- import { number } from "./embedding.js";
3
- import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
4
- import { log } from "./log.js";
1
+ import { Opcodes, Valtype } from './wasmSpec.js';
2
+ import { number } from './embedding.js';
3
+ import { read_signedLEB128, read_ieee754_binary64 } from './encoding.js';
4
+ import { log } from './log.js';
5
+ import Prefs from './prefs.js';
5
6
 
6
7
  const performWasmOp = (op, a, b) => {
7
8
  switch (op) {
@@ -11,21 +12,21 @@ const performWasmOp = (op, a, b) => {
11
12
  }
12
13
  };
13
14
 
14
- export default (funcs, globals, pages, tags) => {
15
+ export default (funcs, globals, pages, tags, exceptions) => {
15
16
  const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
16
17
  if (optLevel === 0) return;
17
18
 
18
- const tailCall = process.argv.includes('-tail-call');
19
+ const tailCall = Prefs.tailCall;
19
20
  if (tailCall) log.warning('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
20
21
 
21
- if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
22
+ if (optLevel >= 2 && !Prefs.optNoInline) {
22
23
  // inline pass (very WIP)
23
24
  // get candidates for inlining
24
25
  // todo: pick smart in future (if func is used <N times? or?)
25
26
  const callsSelf = f => f.wasm.some(x => x[0] === Opcodes.call && x[1] === f.index);
26
27
  const suitableReturns = wasm => wasm.reduce((acc, x) => acc + (x[0] === Opcodes.return), 0) <= 1;
27
28
  const candidates = funcs.filter(x => x.name !== 'main' && Object.keys(x.locals).length === x.params.length && (x.returns.length === 0 || suitableReturns(x.wasm)) && !callsSelf(x) && !x.throws).reverse();
28
- if (optLog) {
29
+ if (Prefs.optLog) {
29
30
  log('opt', `found inline candidates: ${candidates.map(x => x.name).join(', ')} (${candidates.length}/${funcs.length - 1})`);
30
31
 
31
32
  let reasons = {};
@@ -53,7 +54,7 @@ export default (funcs, globals, pages, tags) => {
53
54
  for (let i = 0; i < tWasm.length; i++) {
54
55
  const inst = tWasm[i];
55
56
  if (inst[0] === Opcodes.call && inst[1] === c.index) {
56
- if (optLog) log('opt', `inlining call for ${c.name} (in ${t.name})`);
57
+ if (Prefs.optLog) log('opt', `inlining call for ${c.name} (in ${t.name})`);
57
58
  tWasm.splice(i, 1); // remove this call
58
59
 
59
60
  // add params as locals and set in reverse order
@@ -80,7 +81,7 @@ export default (funcs, globals, pages, tags) => {
80
81
  // adjust local operands to go to correct param index
81
82
  for (const inst of iWasm) {
82
83
  if ((inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set) && inst[1] < c.params.length) {
83
- if (optLog) log('opt', `replacing local operand in inlined wasm (${inst[1]} -> ${paramIdx[inst[1]]})`);
84
+ if (Prefs.optLog) log('opt', `replacing local operand in inlined wasm (${inst[1]} -> ${paramIdx[inst[1]]})`);
84
85
  inst[1] = paramIdx[inst[1]];
85
86
  }
86
87
  }
@@ -97,9 +98,11 @@ export default (funcs, globals, pages, tags) => {
97
98
  }
98
99
  }
99
100
 
100
- if (process.argv.includes('-opt-inline-only')) return;
101
+ if (Prefs.optInlineOnly) return;
101
102
 
102
- const tagUse = tags.reduce((acc, x) => { acc[x.idx] = 0; return acc; }, {});
103
+ // todo: this breaks exceptions after due to indexes not being adjusted
104
+ // const tagUse = tags.reduce((acc, x) => { acc[x.idx] = 0; return acc; }, {});
105
+ // const exceptionUse = exceptions.reduce((acc, _, i) => { acc[i] = 0; return acc; }, {});
103
106
 
104
107
  // wasm transform pass
105
108
  for (const f of funcs) {
@@ -107,7 +110,7 @@ export default (funcs, globals, pages, tags) => {
107
110
 
108
111
  const lastType = f.locals['#last_type'];
109
112
 
110
- let runs = 2; // how many by default? add arg?
113
+ let runs = (+Prefs.optWasmRuns) || 2; // todo: how many by default?
111
114
  while (runs > 0) {
112
115
  runs--;
113
116
 
@@ -129,7 +132,12 @@ export default (funcs, globals, pages, tags) => {
129
132
  if (inst[0] === Opcodes.local_get) getCount[inst[1]]++;
130
133
  if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) setCount[inst[1]]++;
131
134
 
132
- if (inst[0] === Opcodes.throw) tagUse[inst[1]]++;
135
+ // if (inst[0] === Opcodes.throw) {
136
+ // tagUse[inst[1]]++;
137
+
138
+ // const exceptId = read_signedLEB128(wasm[i - 1].slice(1));
139
+ // exceptionUse[exceptId]++;
140
+ // }
133
141
 
134
142
  if (inst[0] === Opcodes.block) {
135
143
  // remove unneeded blocks (no brs inside)
@@ -160,18 +168,18 @@ export default (funcs, globals, pages, tags) => {
160
168
 
161
169
  wasm.splice(j - 1, 1); // remove end of this block
162
170
 
163
- if (optLog) log('opt', `removed unneeded block in for loop`);
171
+ if (Prefs.optLog) log('opt', `removed unneeded block in for loop`);
164
172
  }
165
173
  }
166
174
 
167
- if (inst[inst.length - 1] === 'string_only' && !pages.hasString) {
175
+ if (inst[inst.length - 1] === 'string_only' && !pages.hasAnyString && !Prefs.noRmUnusedTypes) {
168
176
  // remove this inst
169
177
  wasm.splice(i, 1);
170
178
  if (i > 0) i--;
171
179
  inst = wasm[i];
172
180
  }
173
181
 
174
- if (inst[inst.length - 1] === 'string_only|start' && !pages.hasString) {
182
+ if (inst[inst.length - 1] === 'string_only|start' && !pages.hasAnyString&& !Prefs.noRmUnusedTypes) {
175
183
  let j = i;
176
184
  for (; j < wasm.length; j++) {
177
185
  const op = wasm[j];
@@ -185,7 +193,7 @@ export default (funcs, globals, pages, tags) => {
185
193
  inst = wasm[i];
186
194
  }
187
195
 
188
- if (inst[0] === Opcodes.if && typeof inst[2] === 'string') {
196
+ if (inst[0] === Opcodes.if && typeof inst[2] === 'string' && !Prefs.noRmUnusedTypes) {
189
197
  // remove unneeded typeswitch checks
190
198
 
191
199
  const type = inst[2].split('|')[1];
@@ -209,7 +217,7 @@ export default (funcs, globals, pages, tags) => {
209
217
  i -= 4;
210
218
  inst = wasm[i];
211
219
 
212
- if (optLog) log('opt', `removed unneeded typeswitch check`);
220
+ if (Prefs.optLog) log('opt', `removed unneeded typeswitch check`);
213
221
  }
214
222
  }
215
223
 
@@ -232,7 +240,7 @@ export default (funcs, globals, pages, tags) => {
232
240
  wasm.splice(j - 1, 2, [ Opcodes.drop ]); // remove typeswitch start
233
241
  wasm.splice(i - 1, 1); // remove this inst
234
242
 
235
- if (optLog) log('opt', 'removed unneeded entire typeswitch');
243
+ if (Prefs.optLog) log('opt', 'removed unneeded entire typeswitch');
236
244
 
237
245
  if (i > 0) i--;
238
246
  continue;
@@ -261,7 +269,7 @@ export default (funcs, globals, pages, tags) => {
261
269
 
262
270
  getCount[inst[1]]--;
263
271
  i--;
264
- // if (optLog) log('opt', `consolidated set, get -> tee`);
272
+ // if (Prefs.optLog) log('opt', `consolidated set, get -> tee`);
265
273
  continue;
266
274
  }
267
275
 
@@ -329,7 +337,7 @@ export default (funcs, globals, pages, tags) => {
329
337
 
330
338
  wasm.splice(i - 1, 2); // remove this inst and last
331
339
  i -= 2;
332
- // if (optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
340
+ // if (Prefs.optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
333
341
  continue;
334
342
  }
335
343
 
@@ -342,7 +350,7 @@ export default (funcs, globals, pages, tags) => {
342
350
 
343
351
  wasm.splice(i - 1, 2); // remove this inst and last
344
352
  i -= 2;
345
- // if (optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
353
+ // if (Prefs.optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
346
354
  continue;
347
355
  }
348
356
 
@@ -357,7 +365,7 @@ export default (funcs, globals, pages, tags) => {
357
365
 
358
366
  wasm.splice(i, 1); // remove this inst
359
367
  i--;
360
- if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
368
+ if (Prefs.optLog) log('opt', `converted const -> i32 convert into i32 const`);
361
369
  continue;
362
370
  }
363
371
 
@@ -372,7 +380,7 @@ export default (funcs, globals, pages, tags) => {
372
380
 
373
381
  wasm.splice(i, 1); // remove this inst
374
382
  i--;
375
- if (optLog) log('opt', `converted i32 const -> convert into const`);
383
+ if (Prefs.optLog) log('opt', `converted i32 const -> convert into const`);
376
384
  continue;
377
385
  }
378
386
 
@@ -387,7 +395,7 @@ export default (funcs, globals, pages, tags) => {
387
395
 
388
396
  wasm.splice(i, 1); // remove this inst (return)
389
397
  i--;
390
- if (optLog) log('opt', `tail called return, call`);
398
+ if (Prefs.optLog) log('opt', `tail called return, call`);
391
399
  continue;
392
400
  }
393
401
 
@@ -400,7 +408,7 @@ export default (funcs, globals, pages, tags) => {
400
408
 
401
409
  wasm.splice(i, 1); // remove this inst (return)
402
410
  i--;
403
- // if (optLog) log('opt', `removed redundant return at end`);
411
+ // if (Prefs.optLog) log('opt', `removed redundant return at end`);
404
412
  continue;
405
413
  }
406
414
 
@@ -430,7 +438,7 @@ export default (funcs, globals, pages, tags) => {
430
438
  // <nothing>
431
439
 
432
440
  wasm.splice(i - 2, 3); // remove this, last, 2nd last insts
433
- if (optLog) log('opt', `removed redundant inline param local handling`);
441
+ if (Prefs.optLog) log('opt', `removed redundant inline param local handling`);
434
442
  i -= 3;
435
443
  continue;
436
444
  }
@@ -438,12 +446,12 @@ export default (funcs, globals, pages, tags) => {
438
446
 
439
447
  if (optLevel < 2) continue;
440
448
 
441
- if (optLog) log('opt', `get counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${getCount[f.locals[x].idx]}`).join(', ')}`);
449
+ if (Prefs.optLog) log('opt', `get counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${getCount[f.locals[x].idx]}`).join(', ')}`);
442
450
 
443
451
  // remove unneeded var: remove pass
444
452
  // locals only got once. we don't need to worry about sets/else as these are only candidates and we will check for matching set + get insts in wasm
445
453
  let unneededCandidates = Object.keys(getCount).filter(x => getCount[x] === 0 || (getCount[x] === 1 && setCount[x] === 0)).map(x => parseInt(x));
446
- if (optLog) log('opt', `found unneeded locals candidates: ${unneededCandidates.join(', ')} (${unneededCandidates.length}/${Object.keys(getCount).length})`);
454
+ if (Prefs.optLog) log('opt', `found unneeded locals candidates: ${unneededCandidates.join(', ')} (${unneededCandidates.length}/${Object.keys(getCount).length})`);
447
455
 
448
456
  // note: disabled for now due to instability
449
457
  if (unneededCandidates.length > 0 && false) for (let i = 0; i < wasm.length; i++) {
@@ -461,7 +469,7 @@ export default (funcs, globals, pages, tags) => {
461
469
  wasm.splice(i - 1, 2); // remove insts
462
470
  i -= 2;
463
471
  delete f.locals[Object.keys(f.locals)[inst[1]]]; // remove from locals
464
- if (optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
472
+ if (Prefs.optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
465
473
  }
466
474
 
467
475
  if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
@@ -489,7 +497,7 @@ export default (funcs, globals, pages, tags) => {
489
497
  unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
490
498
  unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
491
499
 
492
- if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
500
+ if (Prefs.optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
493
501
  }
494
502
  }
495
503
 
@@ -525,7 +533,7 @@ export default (funcs, globals, pages, tags) => {
525
533
  let b = lastInst[1];
526
534
 
527
535
  const val = performWasmOp(inst[0], a, b);
528
- if (optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
536
+ if (Prefs.optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
529
537
 
530
538
  wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
531
539
  i -= 2;
@@ -537,21 +545,27 @@ export default (funcs, globals, pages, tags) => {
537
545
  for (const x in useCount) {
538
546
  if (useCount[x] === 0) {
539
547
  const name = Object.keys(f.locals)[localIdxs.indexOf(parseInt(x))];
540
- if (optLog) log('opt', `removed internal local ${x} (${name})`);
548
+ if (Prefs.optLog) log('opt', `removed internal local ${x} (${name})`);
541
549
  delete f.locals[name];
542
550
  }
543
551
  }
544
552
 
545
- if (optLog) log('opt', `final use counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${useCount[f.locals[x].idx]}`).join(', ')}`);
553
+ if (Prefs.optLog) log('opt', `final use counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${useCount[f.locals[x].idx]}`).join(', ')}`);
546
554
  }
547
555
  }
548
556
 
549
- for (const x in tagUse) {
550
- if (tagUse[x] === 0) {
551
- const el = tags.find(y => y.idx === x);
552
- tags.splice(tags.indexOf(el), 1);
553
- }
554
- }
557
+ // for (const x in tagUse) {
558
+ // if (tagUse[x] === 0) {
559
+ // const el = tags.find(y => y.idx === x);
560
+ // tags.splice(tags.indexOf(el), 1);
561
+ // }
562
+ // }
563
+
564
+ // for (const x of Object.keys(exceptionUse).sort((a, b) => b - a)) {
565
+ // if (exceptionUse[x] === 0) {
566
+ // exceptions.splice(+x, 1);
567
+ // }
568
+ // }
555
569
 
556
570
  // return funcs;
557
571
  };
package/compiler/parse.js CHANGED
@@ -1,5 +1,5 @@
1
- import { log } from "./log.js";
2
- // import { parse } from 'acorn';
1
+ import { log } from './log.js';
2
+ import Prefs from './prefs.js';
3
3
 
4
4
  // deno compat
5
5
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
@@ -7,9 +7,11 @@ 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] !== '-');
11
+
10
12
  // should we try to support types (while parsing)
11
- const types = process.argv.includes('-parse-types');
12
- globalThis.typedInput = types && process.argv.includes('-opt-types');
13
+ const types = Prefs.parseTypes || file?.endsWith('.ts');
14
+ globalThis.typedInput = types && Prefs.optTypes;
13
15
 
14
16
  // todo: review which to use by default
15
17
  // supported parsers:
@@ -18,37 +20,43 @@ globalThis.typedInput = types && process.argv.includes('-opt-types');
18
20
  // - hermes-parser
19
21
  // - @babel/parser
20
22
 
21
- let parser, parse;
23
+ globalThis.parser = '';
24
+ let parse;
22
25
  const loadParser = async (fallbackParser = 'acorn', forceParser) => {
23
- parser = forceParser ?? process.argv.find(x => x.startsWith('-parser='))?.split('=')?.[1] ?? fallbackParser;
24
- 0, { parse } = (await import((globalThis.document ? 'https://esm.sh/' : '') + parser));
26
+ parser = forceParser ?? process.argv.find(x => x.startsWith('--parser='))?.split('=')?.[1] ?? fallbackParser;
27
+ 0, { parse } = (await import((globalThis.document || globalThis.Deno ? 'https://esm.sh/' : '') + parser));
25
28
  };
26
29
  globalThis._porf_loadParser = loadParser;
27
30
  await loadParser(types ? '@babel/parser' : undefined);
28
31
 
29
- if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('parser', `passed -types with a parser (${parser}) which does not support`);
32
+ if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('parse', `passed -parse-types with a parser (${parser}) which does not support`);
30
33
 
31
34
  export default (input, flags) => {
32
- const ast = parse(input, {
33
- // acorn
34
- ecmaVersion: 'latest',
35
-
36
- // meriyah
37
- next: true,
38
- module: flags.includes('module'),
39
- webcompat: true,
40
-
41
- // babel
42
- plugins: types ? ['estree', 'typescript'] : ['estree'],
43
-
44
- // multiple
45
- sourceType: flags.includes('module') ? 'module' : 'script',
46
- ranges: false,
47
- tokens: false,
48
- comments: false,
49
- });
50
-
51
- if (ast.type === 'File') return ast.program;
52
-
53
- return ast;
35
+ try {
36
+ const ast = parse(input, {
37
+ // acorn
38
+ ecmaVersion: 'latest',
39
+
40
+ // meriyah
41
+ next: true,
42
+ module: flags.includes('module'),
43
+ webcompat: true,
44
+
45
+ // babel
46
+ plugins: types ? ['estree', 'typescript'] : ['estree'],
47
+
48
+ // multiple
49
+ sourceType: flags.includes('module') ? 'module' : 'script',
50
+ ranges: false,
51
+ tokens: false,
52
+ comments: false,
53
+ });
54
+
55
+ if (ast.type === 'File') return ast.program;
56
+
57
+ return ast;
58
+ } catch (e) {
59
+ // normalize error class thrown by 3rd party parsers
60
+ throw new SyntaxError(e.message, { cause: e });
61
+ }
54
62
  };
@@ -0,0 +1,120 @@
1
+ import { Opcodes } from './wasmSpec.js';
2
+ import { TYPES } from './types.js';
3
+
4
+ import fs from 'node:fs';
5
+ import { join } from 'node:path';
6
+
7
+ import { fileURLToPath } from 'node:url';
8
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
9
+
10
+ const argv = process.argv.slice();
11
+
12
+ const compile = async (file, [ _funcs, _globals ]) => {
13
+ let source = fs.readFileSync(file, 'utf8');
14
+ let first = source.slice(0, source.indexOf('\n'));
15
+
16
+ if (first.startsWith('export default')) {
17
+ source = (await import(file)).default();
18
+ first = source.slice(0, source.indexOf('\n'));
19
+ }
20
+
21
+ let args = ['--bytestring', '--todo-time=compile', '--no-aot-pointer-opt', '--no-indirect-calls', '--no-treeshake-wasm-imports', '--no-rm-unused-types', '--scoped-page-names', '--funsafe-no-unlikely-proto-checks', '--parse-types', '--opt-types'];
22
+ if (first.startsWith('// @porf')) {
23
+ args = args.concat(first.slice('// @porf '.length).split(' '));
24
+ }
25
+ process.argv = argv.concat(args);
26
+
27
+ const porfCompile = (await import(`./index.js?_=${Date.now()}`)).default;
28
+
29
+ let { funcs, globals, data, exceptions } = porfCompile(source, ['module']);
30
+
31
+ const allocated = new Set();
32
+
33
+ const exports = funcs.filter(x => x.export && x.name !== 'main');
34
+ for (const x of exports) {
35
+ if (x.data) {
36
+ x.data = x.data.map(x => data[x]);
37
+ for (const y in x.data) {
38
+ x.data[y].offset -= x.data[0].offset;
39
+ }
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);
46
+
47
+ const locals = Object.keys(x.locals).reduce((acc, y) => {
48
+ acc[x.locals[y].idx] = { ...x.locals[y], name: y };
49
+ return acc;
50
+ }, {});
51
+
52
+ for (let i = 0; i < x.wasm.length; i++) {
53
+ const y = x.wasm[i];
54
+ const n = x.wasm[i + 1];
55
+ if (y[0] === Opcodes.call) {
56
+ const f = funcs.find(x => x.index === y[1]);
57
+ if (!f) continue;
58
+
59
+ y[1] = f.name;
60
+ }
61
+
62
+ if (y[0] === Opcodes.const && (n[0] === Opcodes.local_set || n[0] === Opcodes.local_tee)) {
63
+ const l = locals[n[1]];
64
+ if (!l) continue;
65
+ if (![TYPES.string, TYPES.array, TYPES.bytestring].includes(l.metadata?.type)) continue;
66
+ if (!x.pages) continue;
67
+
68
+ const pageName = [...x.pages.keys()].find(z => z.endsWith(l.name));
69
+ if (!pageName || allocated.has(pageName)) continue;
70
+ allocated.add(pageName);
71
+
72
+ y.splice(0, 10, 'alloc', pageName, x.pages.get(pageName).type, valtypeBinary);
73
+ }
74
+
75
+ if (y[0] === Opcodes.i32_const && n[0] === Opcodes.throw) {
76
+ const id = y[1];
77
+ y.splice(0, 10, 'throw', exceptions[id].constructor, exceptions[id].message);
78
+
79
+ // remove throw inst
80
+ x.wasm.splice(i + 1, 1);
81
+ }
82
+ }
83
+ }
84
+
85
+ _funcs.push(...exports);
86
+ _globals.push(...Object.values(globals));
87
+ };
88
+
89
+ const precompile = async () => {
90
+ const dir = join(__dirname, 'builtins');
91
+
92
+ let funcs = [], globals = [];
93
+ for (const file of fs.readdirSync(dir)) {
94
+ if (file.endsWith('.d.ts')) continue;
95
+ console.log(file);
96
+
97
+ await compile(join(dir, file), [ funcs, globals ]);
98
+ }
99
+
100
+ return `// autogenerated by compiler/precompile.js
101
+ import { number } from './embedding.js';
102
+
103
+ export const BuiltinFuncs = function() {
104
+ ${funcs.map(x => {
105
+ const wasm = JSON.stringify(x.wasm.filter(x => x.length && x[0] != null)).replace(/\["alloc","(.*?)","(.*?)",(.*?)\]/g, (_, reason, type, valtype) => `...number(allocPage(scope, '${reason}', '${type}') * pageSize, ${valtype})`).replace(/\[16,"(.*?)"]/g, (_, name) => `[16, builtin('${name}')]`).replace(/\["throw","(.*?)","(.*?)"\]/g, (_, constructor, message) => `...internalThrow(scope, '${constructor}', \`${message}\`)`);
106
+ return ` this.${x.name} = {
107
+ wasm: (scope, {${wasm.includes('allocPage(') ? 'allocPage,' : ''}${wasm.includes('builtin(') ? 'builtin,' : ''}${wasm.includes('internalThrow(') ? 'internalThrow,' : ''}}) => ${wasm},
108
+ params: ${JSON.stringify(x.params)},
109
+ typedParams: true,
110
+ returns: ${JSON.stringify(x.returns)},
111
+ ${x.returnType != null ? `returnType: ${JSON.stringify(x.returnType)}` : 'typedReturns: true'},
112
+ locals: ${JSON.stringify(Object.values(x.locals).slice(x.params.length).map(x => x.type))},
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')
116
+ }).join('\n')}
117
+ };`;
118
+ };
119
+
120
+ fs.writeFileSync(join(__dirname, 'generated_builtins.js'), await precompile());