porffor 0.2.0-a759814 → 0.2.0-a88bbe6

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 (56) hide show
  1. package/CONTRIBUTING.md +256 -0
  2. package/LICENSE +20 -20
  3. package/README.md +156 -87
  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} +64 -16
  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 +188 -0
  23. package/compiler/builtins/string.ts +1080 -0
  24. package/compiler/builtins.js +580 -272
  25. package/compiler/{codeGen.js → codegen.js} +1311 -467
  26. package/compiler/decompile.js +3 -4
  27. package/compiler/embedding.js +22 -22
  28. package/compiler/encoding.js +108 -10
  29. package/compiler/generated_builtins.js +1625 -0
  30. package/compiler/index.js +36 -34
  31. package/compiler/log.js +6 -3
  32. package/compiler/opt.js +57 -31
  33. package/compiler/parse.js +33 -23
  34. package/compiler/precompile.js +120 -0
  35. package/compiler/prefs.js +27 -0
  36. package/compiler/prototype.js +182 -42
  37. package/compiler/types.js +38 -0
  38. package/compiler/wasmSpec.js +31 -7
  39. package/compiler/wrap.js +176 -65
  40. package/package.json +9 -5
  41. package/porf +4 -0
  42. package/rhemyn/compile.js +46 -27
  43. package/rhemyn/parse.js +322 -320
  44. package/rhemyn/test/parse.js +58 -58
  45. package/runner/compare.js +34 -34
  46. package/runner/debug.js +122 -0
  47. package/runner/index.js +91 -11
  48. package/runner/profiler.js +102 -0
  49. package/runner/repl.js +42 -9
  50. package/runner/sizes.js +37 -37
  51. package/compiler/builtins/base64.js +0 -92
  52. package/runner/info.js +0 -89
  53. package/runner/profile.js +0 -46
  54. package/runner/results.json +0 -1
  55. package/runner/transform.js +0 -15
  56. 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);
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) => {
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) => {
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) => {
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,7 +98,11 @@ export default (funcs, globals, pages) => {
97
98
  }
98
99
  }
99
100
 
100
- if (process.argv.includes('-opt-inline-only')) return;
101
+ if (Prefs.optInlineOnly) return;
102
+
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; }, {});
101
106
 
102
107
  // wasm transform pass
103
108
  for (const f of funcs) {
@@ -105,7 +110,7 @@ export default (funcs, globals, pages) => {
105
110
 
106
111
  const lastType = f.locals['#last_type'];
107
112
 
108
- let runs = 2; // how many by default? add arg?
113
+ let runs = (+Prefs.optWasmRuns) || 2; // todo: how many by default?
109
114
  while (runs > 0) {
110
115
  runs--;
111
116
 
@@ -127,6 +132,13 @@ export default (funcs, globals, pages) => {
127
132
  if (inst[0] === Opcodes.local_get) getCount[inst[1]]++;
128
133
  if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) setCount[inst[1]]++;
129
134
 
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
+ // }
141
+
130
142
  if (inst[0] === Opcodes.block) {
131
143
  // remove unneeded blocks (no brs inside)
132
144
  // block
@@ -143,7 +155,7 @@ export default (funcs, globals, pages) => {
143
155
  depth--;
144
156
  if (depth <= 0) break;
145
157
  }
146
- if (op === Opcodes.br || op === Opcodes.br_if) {
158
+ if (op === Opcodes.br || op === Opcodes.br_if || op === Opcodes.br_table) {
147
159
  hasBranch = true;
148
160
  break;
149
161
  }
@@ -156,18 +168,18 @@ export default (funcs, globals, pages) => {
156
168
 
157
169
  wasm.splice(j - 1, 1); // remove end of this block
158
170
 
159
- if (optLog) log('opt', `removed unneeded block in for loop`);
171
+ if (Prefs.optLog) log('opt', `removed unneeded block in for loop`);
160
172
  }
161
173
  }
162
174
 
163
- if (inst[inst.length - 1] === 'string_only' && !pages.hasString) {
175
+ if (inst[inst.length - 1] === 'string_only' && !pages.hasAnyString && !Prefs.noRmUnusedTypes) {
164
176
  // remove this inst
165
177
  wasm.splice(i, 1);
166
178
  if (i > 0) i--;
167
179
  inst = wasm[i];
168
180
  }
169
181
 
170
- if (inst[inst.length - 1] === 'string_only|start' && !pages.hasString) {
182
+ if (inst[inst.length - 1] === 'string_only|start' && !pages.hasAnyString&& !Prefs.noRmUnusedTypes) {
171
183
  let j = i;
172
184
  for (; j < wasm.length; j++) {
173
185
  const op = wasm[j];
@@ -181,13 +193,14 @@ export default (funcs, globals, pages) => {
181
193
  inst = wasm[i];
182
194
  }
183
195
 
184
- if (inst[0] === Opcodes.if && typeof inst[2] === 'string') {
196
+ if (inst[0] === Opcodes.if && typeof inst[2] === 'string' && !Prefs.noRmUnusedTypes) {
185
197
  // remove unneeded typeswitch checks
186
198
 
187
199
  const type = inst[2].split('|')[1];
188
200
  let missing = false;
189
201
  if (type === 'Array') missing = !pages.hasArray;
190
202
  if (type === 'String') missing = !pages.hasString;
203
+ if (type === 'ByteString') missing = !pages.hasByteString;
191
204
 
192
205
  if (missing) {
193
206
  let j = i, depth = 0;
@@ -204,7 +217,7 @@ export default (funcs, globals, pages) => {
204
217
  i -= 4;
205
218
  inst = wasm[i];
206
219
 
207
- if (optLog) log('opt', `removed unneeded typeswitch check`);
220
+ if (Prefs.optLog) log('opt', `removed unneeded typeswitch check`);
208
221
  }
209
222
  }
210
223
 
@@ -227,7 +240,7 @@ export default (funcs, globals, pages) => {
227
240
  wasm.splice(j - 1, 2, [ Opcodes.drop ]); // remove typeswitch start
228
241
  wasm.splice(i - 1, 1); // remove this inst
229
242
 
230
- if (optLog) log('opt', 'removed unneeded entire typeswitch');
243
+ if (Prefs.optLog) log('opt', 'removed unneeded entire typeswitch');
231
244
 
232
245
  if (i > 0) i--;
233
246
  continue;
@@ -235,7 +248,7 @@ export default (funcs, globals, pages) => {
235
248
  }
236
249
 
237
250
  // remove setting last type if it is never gotten
238
- if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType.idx) {
251
+ if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType?.idx) {
239
252
  // replace this inst with drop
240
253
  wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
241
254
  if (i > 0) i--;
@@ -256,7 +269,7 @@ export default (funcs, globals, pages) => {
256
269
 
257
270
  getCount[inst[1]]--;
258
271
  i--;
259
- // if (optLog) log('opt', `consolidated set, get -> tee`);
272
+ // if (Prefs.optLog) log('opt', `consolidated set, get -> tee`);
260
273
  continue;
261
274
  }
262
275
 
@@ -324,7 +337,7 @@ export default (funcs, globals, pages) => {
324
337
 
325
338
  wasm.splice(i - 1, 2); // remove this inst and last
326
339
  i -= 2;
327
- // if (optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
340
+ // if (Prefs.optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
328
341
  continue;
329
342
  }
330
343
 
@@ -337,7 +350,7 @@ export default (funcs, globals, pages) => {
337
350
 
338
351
  wasm.splice(i - 1, 2); // remove this inst and last
339
352
  i -= 2;
340
- // if (optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
353
+ // if (Prefs.optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
341
354
  continue;
342
355
  }
343
356
 
@@ -352,7 +365,7 @@ export default (funcs, globals, pages) => {
352
365
 
353
366
  wasm.splice(i, 1); // remove this inst
354
367
  i--;
355
- if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
368
+ if (Prefs.optLog) log('opt', `converted const -> i32 convert into i32 const`);
356
369
  continue;
357
370
  }
358
371
 
@@ -367,7 +380,7 @@ export default (funcs, globals, pages) => {
367
380
 
368
381
  wasm.splice(i, 1); // remove this inst
369
382
  i--;
370
- if (optLog) log('opt', `converted i32 const -> convert into const`);
383
+ if (Prefs.optLog) log('opt', `converted i32 const -> convert into const`);
371
384
  continue;
372
385
  }
373
386
 
@@ -382,7 +395,7 @@ export default (funcs, globals, pages) => {
382
395
 
383
396
  wasm.splice(i, 1); // remove this inst (return)
384
397
  i--;
385
- if (optLog) log('opt', `tail called return, call`);
398
+ if (Prefs.optLog) log('opt', `tail called return, call`);
386
399
  continue;
387
400
  }
388
401
 
@@ -395,7 +408,7 @@ export default (funcs, globals, pages) => {
395
408
 
396
409
  wasm.splice(i, 1); // remove this inst (return)
397
410
  i--;
398
- // if (optLog) log('opt', `removed redundant return at end`);
411
+ // if (Prefs.optLog) log('opt', `removed redundant return at end`);
399
412
  continue;
400
413
  }
401
414
 
@@ -425,7 +438,7 @@ export default (funcs, globals, pages) => {
425
438
  // <nothing>
426
439
 
427
440
  wasm.splice(i - 2, 3); // remove this, last, 2nd last insts
428
- if (optLog) log('opt', `removed redundant inline param local handling`);
441
+ if (Prefs.optLog) log('opt', `removed redundant inline param local handling`);
429
442
  i -= 3;
430
443
  continue;
431
444
  }
@@ -433,12 +446,12 @@ export default (funcs, globals, pages) => {
433
446
 
434
447
  if (optLevel < 2) continue;
435
448
 
436
- 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(', ')}`);
437
450
 
438
451
  // remove unneeded var: remove pass
439
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
440
453
  let unneededCandidates = Object.keys(getCount).filter(x => getCount[x] === 0 || (getCount[x] === 1 && setCount[x] === 0)).map(x => parseInt(x));
441
- 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})`);
442
455
 
443
456
  // note: disabled for now due to instability
444
457
  if (unneededCandidates.length > 0 && false) for (let i = 0; i < wasm.length; i++) {
@@ -456,7 +469,7 @@ export default (funcs, globals, pages) => {
456
469
  wasm.splice(i - 1, 2); // remove insts
457
470
  i -= 2;
458
471
  delete f.locals[Object.keys(f.locals)[inst[1]]]; // remove from locals
459
- if (optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
472
+ if (Prefs.optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
460
473
  }
461
474
 
462
475
  if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
@@ -484,7 +497,7 @@ export default (funcs, globals, pages) => {
484
497
  unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
485
498
  unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
486
499
 
487
- if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
500
+ if (Prefs.optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
488
501
  }
489
502
  }
490
503
 
@@ -520,7 +533,7 @@ export default (funcs, globals, pages) => {
520
533
  let b = lastInst[1];
521
534
 
522
535
  const val = performWasmOp(inst[0], a, b);
523
- 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})`);
524
537
 
525
538
  wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
526
539
  i -= 2;
@@ -532,14 +545,27 @@ export default (funcs, globals, pages) => {
532
545
  for (const x in useCount) {
533
546
  if (useCount[x] === 0) {
534
547
  const name = Object.keys(f.locals)[localIdxs.indexOf(parseInt(x))];
535
- if (optLog) log('opt', `removed internal local ${x} (${name})`);
548
+ if (Prefs.optLog) log('opt', `removed internal local ${x} (${name})`);
536
549
  delete f.locals[name];
537
550
  }
538
551
  }
539
552
 
540
- 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(', ')}`);
541
554
  }
542
555
  }
543
556
 
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
+ // }
569
+
544
570
  // return funcs;
545
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,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-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());