porffor 0.2.0-f2bbe1f → 0.2.0-f647e42

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 (53) hide show
  1. package/CONTRIBUTING.md +239 -0
  2. package/LICENSE +20 -20
  3. package/README.md +154 -89
  4. package/asur/README.md +2 -0
  5. package/asur/index.js +1262 -0
  6. package/byg/index.js +237 -0
  7. package/compiler/2c.js +317 -72
  8. package/compiler/{sections.js → assemble.js} +63 -15
  9. package/compiler/builtins/annexb_string.js +72 -0
  10. package/compiler/builtins/annexb_string.ts +19 -0
  11. package/compiler/builtins/array.ts +145 -0
  12. package/compiler/builtins/base64.ts +151 -0
  13. package/compiler/builtins/crypto.ts +120 -0
  14. package/compiler/builtins/date.ts +1858 -0
  15. package/compiler/builtins/escape.ts +141 -0
  16. package/compiler/builtins/int.ts +147 -0
  17. package/compiler/builtins/number.ts +527 -0
  18. package/compiler/builtins/porffor.d.ts +59 -0
  19. package/compiler/builtins/string.ts +1055 -0
  20. package/compiler/builtins/tostring.ts +45 -0
  21. package/compiler/builtins.js +449 -269
  22. package/compiler/{codeGen.js → codegen.js} +1153 -418
  23. package/compiler/decompile.js +0 -1
  24. package/compiler/embedding.js +22 -22
  25. package/compiler/encoding.js +108 -10
  26. package/compiler/generated_builtins.js +1454 -0
  27. package/compiler/index.js +36 -34
  28. package/compiler/log.js +6 -3
  29. package/compiler/opt.js +51 -36
  30. package/compiler/parse.js +33 -23
  31. package/compiler/precompile.js +128 -0
  32. package/compiler/prefs.js +27 -0
  33. package/compiler/prototype.js +177 -37
  34. package/compiler/types.js +37 -0
  35. package/compiler/wasmSpec.js +30 -7
  36. package/compiler/wrap.js +56 -40
  37. package/package.json +9 -5
  38. package/porf +4 -0
  39. package/rhemyn/compile.js +46 -27
  40. package/rhemyn/parse.js +322 -320
  41. package/rhemyn/test/parse.js +58 -58
  42. package/runner/compare.js +34 -34
  43. package/runner/debug.js +122 -0
  44. package/runner/index.js +91 -11
  45. package/runner/profiler.js +102 -0
  46. package/runner/repl.js +42 -9
  47. package/runner/sizes.js +37 -37
  48. package/compiler/builtins/base64.js +0 -92
  49. package/runner/info.js +0 -89
  50. package/runner/profile.js +0 -46
  51. package/runner/results.json +0 -1
  52. package/runner/transform.js +0 -15
  53. package/util/enum.js +0 -20
package/compiler/index.js CHANGED
@@ -1,11 +1,11 @@
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
 
@@ -26,59 +26,54 @@ const logFuncs = (funcs, globals, exceptions) => {
26
26
  console.log();
27
27
  };
28
28
 
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);
29
+ const fs = (typeof process?.version !== 'undefined' ? (await import('node:fs')) : undefined);
32
30
  const execSync = (typeof process?.version !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
33
31
 
34
32
  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
33
  const t0 = performance.now();
45
34
  const program = parse(code, flags);
46
- if (flags.includes('info')) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
35
+ if (Prefs.profileCompiler) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
47
36
 
48
37
  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`);
38
+ const { funcs, globals, tags, exceptions, pages, data } = codegen(program);
39
+ if (Prefs.profileCompiler) console.log(`2. generated code in ${(performance.now() - t1).toFixed(2)}ms`);
51
40
 
52
- if (process.argv.includes('-funcs')) logFuncs(funcs, globals, exceptions);
41
+ if (Prefs.funcs) logFuncs(funcs, globals, exceptions);
53
42
 
54
43
  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`);
44
+ opt(funcs, globals, pages, tags, exceptions);
45
+ if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
57
46
 
58
- if (process.argv.includes('-opt-funcs')) logFuncs(funcs, globals, exceptions);
47
+ if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
59
48
 
60
49
  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`);
50
+ const wasm = assemble(funcs, globals, tags, pages, data, flags);
51
+ if (Prefs.profileCompiler) console.log(`4. assembled in ${(performance.now() - t3).toFixed(2)}ms`);
63
52
 
64
- if (allocLog) {
53
+ if (Prefs.allocLog) {
65
54
  const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
66
55
  const bytes = wasmPages * 65536;
67
56
  log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
68
57
  console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
69
58
  }
70
59
 
71
- const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
60
+ const out = { wasm, funcs, globals, tags, exceptions, pages, data };
61
+
62
+ const target = Prefs.target ?? 'wasm';
63
+ const outFile = Prefs.o;
72
64
 
73
- const target = getArg('target') ?? getArg('t') ?? 'wasm';
74
- const outFile = getArg('o');
65
+ if (target === 'wasm' && outFile) {
66
+ fs.writeFileSync(outFile, Buffer.from(wasm));
67
+
68
+ if (process.version) process.exit();
69
+ }
75
70
 
76
71
  if (target === 'c') {
77
72
  const c = toc(out);
78
73
  out.c = c;
79
74
 
80
75
  if (outFile) {
81
- writeFileSync(outFile, c);
76
+ fs.writeFileSync(outFile, c);
82
77
  } else {
83
78
  console.log(c);
84
79
  }
@@ -87,18 +82,25 @@ export default (code, flags) => {
87
82
  }
88
83
 
89
84
  if (target === 'native') {
90
- const compiler = getArg('compiler') ?? 'clang';
91
- const cO = getArg('cO') ?? 'Ofast';
85
+ let compiler = Prefs.compiler ?? 'clang';
86
+ const cO = Prefs._cO ?? 'Ofast';
92
87
 
93
- const tmpfile = 'tmp.c';
94
- const args = [ compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native' ];
88
+ if (compiler === 'zig') compiler = [ 'zig', 'cc' ];
89
+ else compiler = [ compiler ];
90
+
91
+ const tmpfile = 'porffor_tmp.c';
92
+ // 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' ];
93
+ // const args = [ ...compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native', '-s', '-ffast-math', '-fno-exceptions', '-target', 'x86_64-linux' ];
94
+ const args = [ ...compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native', '-s', '-ffast-math', '-fno-exceptions' ];
95
95
 
96
96
  const c = toc(out);
97
- writeFileSync(tmpfile, c);
97
+ fs.writeFileSync(tmpfile, c);
98
98
 
99
99
  // obvious command escape is obvious
100
100
  execSync(args.join(' '), { stdio: 'inherit' });
101
101
 
102
+ fs.unlinkSync(tmpfile);
103
+
102
104
  if (process.version) process.exit();
103
105
  }
104
106
 
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
@@ -2,6 +2,7 @@ import { Opcodes, Valtype } from "./wasmSpec.js";
2
2
  import { number } from "./embedding.js";
3
3
  import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
4
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) {
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) {
175
183
  let j = i;
176
184
  for (; j < wasm.length; j++) {
177
185
  const op = wasm[j];
@@ -192,6 +200,7 @@ export default (funcs, globals, pages, tags) => {
192
200
  let missing = false;
193
201
  if (type === 'Array') missing = !pages.hasArray;
194
202
  if (type === 'String') missing = !pages.hasString;
203
+ if (type === 'ByteString') missing = !pages.hasByteString;
195
204
 
196
205
  if (missing) {
197
206
  let j = i, depth = 0;
@@ -208,7 +217,7 @@ export default (funcs, globals, pages, tags) => {
208
217
  i -= 4;
209
218
  inst = wasm[i];
210
219
 
211
- if (optLog) log('opt', `removed unneeded typeswitch check`);
220
+ if (Prefs.optLog) log('opt', `removed unneeded typeswitch check`);
212
221
  }
213
222
  }
214
223
 
@@ -231,7 +240,7 @@ export default (funcs, globals, pages, tags) => {
231
240
  wasm.splice(j - 1, 2, [ Opcodes.drop ]); // remove typeswitch start
232
241
  wasm.splice(i - 1, 1); // remove this inst
233
242
 
234
- if (optLog) log('opt', 'removed unneeded entire typeswitch');
243
+ if (Prefs.optLog) log('opt', 'removed unneeded entire typeswitch');
235
244
 
236
245
  if (i > 0) i--;
237
246
  continue;
@@ -260,7 +269,7 @@ export default (funcs, globals, pages, tags) => {
260
269
 
261
270
  getCount[inst[1]]--;
262
271
  i--;
263
- // if (optLog) log('opt', `consolidated set, get -> tee`);
272
+ // if (Prefs.optLog) log('opt', `consolidated set, get -> tee`);
264
273
  continue;
265
274
  }
266
275
 
@@ -328,7 +337,7 @@ export default (funcs, globals, pages, tags) => {
328
337
 
329
338
  wasm.splice(i - 1, 2); // remove this inst and last
330
339
  i -= 2;
331
- // if (optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
340
+ // if (Prefs.optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
332
341
  continue;
333
342
  }
334
343
 
@@ -341,7 +350,7 @@ export default (funcs, globals, pages, tags) => {
341
350
 
342
351
  wasm.splice(i - 1, 2); // remove this inst and last
343
352
  i -= 2;
344
- // if (optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
353
+ // if (Prefs.optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
345
354
  continue;
346
355
  }
347
356
 
@@ -356,7 +365,7 @@ export default (funcs, globals, pages, tags) => {
356
365
 
357
366
  wasm.splice(i, 1); // remove this inst
358
367
  i--;
359
- if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
368
+ if (Prefs.optLog) log('opt', `converted const -> i32 convert into i32 const`);
360
369
  continue;
361
370
  }
362
371
 
@@ -371,7 +380,7 @@ export default (funcs, globals, pages, tags) => {
371
380
 
372
381
  wasm.splice(i, 1); // remove this inst
373
382
  i--;
374
- if (optLog) log('opt', `converted i32 const -> convert into const`);
383
+ if (Prefs.optLog) log('opt', `converted i32 const -> convert into const`);
375
384
  continue;
376
385
  }
377
386
 
@@ -386,7 +395,7 @@ export default (funcs, globals, pages, tags) => {
386
395
 
387
396
  wasm.splice(i, 1); // remove this inst (return)
388
397
  i--;
389
- if (optLog) log('opt', `tail called return, call`);
398
+ if (Prefs.optLog) log('opt', `tail called return, call`);
390
399
  continue;
391
400
  }
392
401
 
@@ -399,7 +408,7 @@ export default (funcs, globals, pages, tags) => {
399
408
 
400
409
  wasm.splice(i, 1); // remove this inst (return)
401
410
  i--;
402
- // if (optLog) log('opt', `removed redundant return at end`);
411
+ // if (Prefs.optLog) log('opt', `removed redundant return at end`);
403
412
  continue;
404
413
  }
405
414
 
@@ -429,7 +438,7 @@ export default (funcs, globals, pages, tags) => {
429
438
  // <nothing>
430
439
 
431
440
  wasm.splice(i - 2, 3); // remove this, last, 2nd last insts
432
- if (optLog) log('opt', `removed redundant inline param local handling`);
441
+ if (Prefs.optLog) log('opt', `removed redundant inline param local handling`);
433
442
  i -= 3;
434
443
  continue;
435
444
  }
@@ -437,12 +446,12 @@ export default (funcs, globals, pages, tags) => {
437
446
 
438
447
  if (optLevel < 2) continue;
439
448
 
440
- 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(', ')}`);
441
450
 
442
451
  // remove unneeded var: remove pass
443
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
444
453
  let unneededCandidates = Object.keys(getCount).filter(x => getCount[x] === 0 || (getCount[x] === 1 && setCount[x] === 0)).map(x => parseInt(x));
445
- 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})`);
446
455
 
447
456
  // note: disabled for now due to instability
448
457
  if (unneededCandidates.length > 0 && false) for (let i = 0; i < wasm.length; i++) {
@@ -460,7 +469,7 @@ export default (funcs, globals, pages, tags) => {
460
469
  wasm.splice(i - 1, 2); // remove insts
461
470
  i -= 2;
462
471
  delete f.locals[Object.keys(f.locals)[inst[1]]]; // remove from locals
463
- if (optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
472
+ if (Prefs.optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
464
473
  }
465
474
 
466
475
  if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
@@ -488,7 +497,7 @@ export default (funcs, globals, pages, tags) => {
488
497
  unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
489
498
  unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
490
499
 
491
- if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
500
+ if (Prefs.optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
492
501
  }
493
502
  }
494
503
 
@@ -524,7 +533,7 @@ export default (funcs, globals, pages, tags) => {
524
533
  let b = lastInst[1];
525
534
 
526
535
  const val = performWasmOp(inst[0], a, b);
527
- 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})`);
528
537
 
529
538
  wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
530
539
  i -= 2;
@@ -536,21 +545,27 @@ export default (funcs, globals, pages, tags) => {
536
545
  for (const x in useCount) {
537
546
  if (useCount[x] === 0) {
538
547
  const name = Object.keys(f.locals)[localIdxs.indexOf(parseInt(x))];
539
- if (optLog) log('opt', `removed internal local ${x} (${name})`);
548
+ if (Prefs.optLog) log('opt', `removed internal local ${x} (${name})`);
540
549
  delete f.locals[name];
541
550
  }
542
551
  }
543
552
 
544
- 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(', ')}`);
545
554
  }
546
555
  }
547
556
 
548
- for (const x in tagUse) {
549
- if (tagUse[x] === 0) {
550
- const el = tags.find(y => y.idx === x);
551
- tags.splice(tags.indexOf(el), 1);
552
- }
553
- }
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
+ // }
554
569
 
555
570
  // return funcs;
556
571
  };
package/compiler/parse.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { log } from "./log.js";
2
+ import Prefs from './prefs.js';
3
+
2
4
  // import { parse } from 'acorn';
3
5
 
4
6
  // deno compat
@@ -7,9 +9,11 @@ if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
7
9
  globalThis.process = { argv: ['', '', ...Deno.args], stdout: { write: str => Deno.writeAllSync(Deno.stdout, textEncoder.encode(str)) } };
8
10
  }
9
11
 
12
+ const file = process.argv.slice(2).find(x => x[0] !== '-');
13
+
10
14
  // 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');
15
+ const types = Prefs.parseTypes || file?.endsWith('.ts');
16
+ globalThis.typedInput = types && Prefs.optTypes;
13
17
 
14
18
  // todo: review which to use by default
15
19
  // supported parsers:
@@ -18,37 +22,43 @@ globalThis.typedInput = types && process.argv.includes('-opt-types');
18
22
  // - hermes-parser
19
23
  // - @babel/parser
20
24
 
21
- let parser, parse;
25
+ globalThis.parser = '';
26
+ let parse;
22
27
  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));
28
+ parser = forceParser ?? process.argv.find(x => x.startsWith('--parser='))?.split('=')?.[1] ?? fallbackParser;
29
+ 0, { parse } = (await import((globalThis.document || globalThis.Deno ? 'https://esm.sh/' : '') + parser));
25
30
  };
26
31
  globalThis._porf_loadParser = loadParser;
27
32
  await loadParser(types ? '@babel/parser' : undefined);
28
33
 
29
- if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('parser', `passed -types with a parser (${parser}) which does not support`);
34
+ if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('parse', `passed -parse-types with a parser (${parser}) which does not support`);
30
35
 
31
36
  export default (input, flags) => {
32
- const ast = parse(input, {
33
- // acorn
34
- ecmaVersion: 'latest',
37
+ try {
38
+ const ast = parse(input, {
39
+ // acorn
40
+ ecmaVersion: 'latest',
35
41
 
36
- // meriyah
37
- next: true,
38
- module: flags.includes('module'),
39
- webcompat: true,
42
+ // meriyah
43
+ next: true,
44
+ module: flags.includes('module'),
45
+ webcompat: true,
40
46
 
41
- // babel
42
- plugins: types ? ['estree', 'typescript'] : ['estree'],
47
+ // babel
48
+ plugins: types ? ['estree', 'typescript'] : ['estree'],
43
49
 
44
- // multiple
45
- sourceType: flags.includes('module') ? 'module' : 'script',
46
- ranges: false,
47
- tokens: false,
48
- comments: false,
49
- });
50
+ // multiple
51
+ sourceType: flags.includes('module') ? 'module' : 'script',
52
+ ranges: false,
53
+ tokens: false,
54
+ comments: false,
55
+ });
50
56
 
51
- if (ast.type === 'File') return ast.program;
57
+ if (ast.type === 'File') return ast.program;
52
58
 
53
- return ast;
59
+ return ast;
60
+ } catch (e) {
61
+ // normalize error class thrown by 3rd party parsers
62
+ throw new SyntaxError(e.message, { cause: e });
63
+ }
54
64
  };
@@ -0,0 +1,128 @@
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
+ // import porfParse from './parse.js';
11
+ // import porfCodegen from './codeGen.js';
12
+
13
+ const argv = process.argv.slice();
14
+
15
+ const compile = async (file, [ _funcs, _globals ]) => {
16
+ let source = fs.readFileSync(file, 'utf8');
17
+ let first = source.slice(0, source.indexOf('\n'));
18
+
19
+ if (first.startsWith('export default')) {
20
+ source = (await import(file)).default();
21
+ first = source.slice(0, source.indexOf('\n'));
22
+ }
23
+
24
+ let args = ['--bytestring', '--todo-time=compile', '--no-aot-pointer-opt', '--no-treeshake-wasm-imports', '--scoped-page-names', '--parse-types', '--opt-types'];
25
+ if (first.startsWith('// @porf')) {
26
+ args = args.concat(first.slice('// @porf '.length).split(' '));
27
+ }
28
+ process.argv = argv.concat(args);
29
+
30
+ // const porfParse = (await import(`./parse.js?_=${Date.now()}`)).default;
31
+ // const porfCodegen = (await import(`./codeGen.js?_=${Date.now()}`)).default;
32
+
33
+ // let { funcs, globals, data } = porfCodegen(porfParse(source, ['module']));
34
+
35
+ const porfCompile = (await import(`./index.js?_=${Date.now()}`)).default;
36
+
37
+ let { funcs, globals, data, exceptions } = porfCompile(source, ['module']);
38
+
39
+ const allocated = new Set();
40
+
41
+ const exports = funcs.filter(x => x.export && x.name !== 'main');
42
+ for (const x of exports) {
43
+ if (x.data) {
44
+ x.data = x.data.map(x => data[x]);
45
+ for (const y in x.data) {
46
+ x.data[y].offset -= x.data[0].offset;
47
+ }
48
+ }
49
+ if (x.exceptions) x.exceptions = x.exceptions.map(x => {
50
+ const obj = exceptions[x];
51
+ if (obj) obj.exceptId = x;
52
+ return obj;
53
+ }).filter(x => x);
54
+
55
+ const locals = Object.keys(x.locals).reduce((acc, y) => {
56
+ acc[x.locals[y].idx] = { ...x.locals[y], name: y };
57
+ return acc;
58
+ }, {});
59
+
60
+ for (let i = 0; i < x.wasm.length; i++) {
61
+ const y = x.wasm[i];
62
+ const n = x.wasm[i + 1];
63
+ if (y[0] === Opcodes.call) {
64
+ const f = funcs.find(x => x.index === y[1]);
65
+ if (!f) continue;
66
+
67
+ y[1] = f.name;
68
+ }
69
+
70
+ if (y[0] === Opcodes.const && (n[0] === Opcodes.local_set || n[0] === Opcodes.local_tee)) {
71
+ const l = locals[n[1]];
72
+ if (!l) continue;
73
+ if (![TYPES.string, TYPES._array, TYPES._bytestring].includes(l.metadata?.type)) continue;
74
+ if (!x.pages) continue;
75
+
76
+ const pageName = [...x.pages.keys()].find(z => z.endsWith(l.name));
77
+ if (!pageName || allocated.has(pageName)) continue;
78
+ allocated.add(pageName);
79
+
80
+ y.splice(0, 10, 'alloc', pageName, x.pages.get(pageName).type, valtypeBinary);
81
+ // y.push(x.pages.get(pageName));
82
+ }
83
+ }
84
+ }
85
+
86
+ _funcs.push(...exports);
87
+ _globals.push(...Object.values(globals));
88
+ };
89
+
90
+ const precompile = async () => {
91
+ const dir = join(__dirname, 'builtins');
92
+
93
+ let funcs = [], globals = [];
94
+ for (const file of fs.readdirSync(dir)) {
95
+ if (file.endsWith('.d.ts')) continue;
96
+ console.log(file);
97
+
98
+ await compile(join(dir, file), [ funcs, globals ]);
99
+ }
100
+
101
+ // const a = funcs.find(x => x.name === '__ecma262_ToUTCDTSF');
102
+ // console.log(Object.values(a.locals).slice(a.params.length));
103
+
104
+ // ${x.pages && x.pages.size > 0 ? ` pages: ${JSON.stringify(Object.fromEntries(x.pages.entries()))},` : ''}
105
+ // ${x.used && x.used.length > 0 ? ` used: ${JSON.stringify(x.used)},` : ''}
106
+
107
+ return `// autogenerated by compiler/precompile.js
108
+ import { number } from './embedding.js';
109
+
110
+ export const BuiltinFuncs = function() {
111
+ ${funcs.map(x => ` this.${x.name} = {
112
+ wasm: (scope, { allocPage, builtin }) => ${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}')]`)},
113
+ params: ${JSON.stringify(x.params)},
114
+ typedParams: true,
115
+ returns: ${JSON.stringify(x.returns)},
116
+ ${x.returnType != null ? `returnType: ${JSON.stringify(x.returnType)}` : 'typedReturns: true'},
117
+ locals: ${JSON.stringify(Object.values(x.locals).slice(x.params.length).map(x => x.type))},
118
+ localNames: ${JSON.stringify(Object.keys(x.locals))},
119
+ ${x.data && x.data.length > 0 ? ` data: ${JSON.stringify(x.data)},` : ''}
120
+ ${x.exceptions && x.exceptions.length > 0 ? ` exceptions: ${JSON.stringify(x.exceptions)},` : ''}
121
+ };`.replaceAll('\n\n', '\n').replaceAll('\n\n', '\n')).join('\n')}
122
+ };`;
123
+ };
124
+
125
+ const code = await precompile();
126
+ // console.log(code);
127
+
128
+ fs.writeFileSync(join(__dirname, 'generated_builtins.js'), code);
@@ -0,0 +1,27 @@
1
+ const onByDefault = [ 'bytestring', 'aotPointerOpt', 'treeshakeWasmImports' ];
2
+
3
+ let cache = {};
4
+ const obj = new Proxy({}, {
5
+ get(_, p) {
6
+ // intentionally misses with undefined values cached
7
+ if (cache[p]) return cache[p];
8
+
9
+ return cache[p] = (() => {
10
+ // fooBar -> foo-bar
11
+ const name = p[0] === '_' ? p : p.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`);
12
+ const prefix = name.length === 1 ? '-' : '--';
13
+ if (process.argv.includes(prefix + name)) return true;
14
+ if (process.argv.includes(prefix + 'no-' + name)) return false;
15
+
16
+ const valArg = process.argv.find(x => x.startsWith(`${prefix}${name}=`));
17
+ if (valArg) return valArg.slice(name.length + 1 + prefix.length);
18
+
19
+ if (onByDefault.includes(p)) return true;
20
+ return undefined;
21
+ })();
22
+ }
23
+ });
24
+
25
+ obj.uncache = () => cache = {};
26
+
27
+ export default obj;