porffor 0.2.0-fde989a → 0.2.0-fdf0fc5
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.
- package/CONTRIBUTING.md +256 -0
- package/LICENSE +20 -20
- package/README.md +123 -85
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +1 -1
- package/compiler/{sections.js → assemble.js} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +18 -0
- package/compiler/builtins/array.ts +149 -0
- package/compiler/builtins/base64.ts +76 -0
- package/compiler/builtins/boolean.ts +20 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2070 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/function.ts +7 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +534 -0
- package/compiler/builtins/object.ts +6 -0
- package/compiler/builtins/porffor.d.ts +59 -0
- package/compiler/builtins/set.ts +5 -0
- package/compiler/builtins/string.ts +1080 -0
- package/compiler/builtins.js +435 -279
- package/compiler/{codeGen.js → codegen.js} +1034 -404
- package/compiler/decompile.js +0 -1
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1526 -0
- package/compiler/index.js +25 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +50 -36
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +27 -42
- package/compiler/types.js +38 -0
- package/compiler/wasmSpec.js +28 -8
- package/compiler/wrap.js +51 -46
- package/package.json +9 -5
- package/porf +2 -0
- package/rhemyn/compile.js +46 -27
- package/rhemyn/parse.js +322 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +78 -11
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/compiler/builtins/base64.js +0 -92
- package/filesize.cmd +0 -2
- package/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- package/tmp.c +0 -661
- 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
|
3
|
+
import codegen from './codegen.js';
|
4
4
|
import opt from './opt.js';
|
5
|
-
import
|
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,55 +26,44 @@ const logFuncs = (funcs, globals, exceptions) => {
|
|
26
26
|
console.log();
|
27
27
|
};
|
28
28
|
|
29
|
-
const
|
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 (
|
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 } =
|
50
|
-
if (
|
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 (
|
41
|
+
if (Prefs.funcs) logFuncs(funcs, globals, exceptions);
|
53
42
|
|
54
43
|
const t2 = performance.now();
|
55
|
-
opt(funcs, globals, pages, tags);
|
56
|
-
if (
|
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 (
|
47
|
+
if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
|
59
48
|
|
60
49
|
const t3 = performance.now();
|
61
|
-
const
|
62
|
-
if (
|
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
|
60
|
+
const out = { wasm, funcs, globals, tags, exceptions, pages, data };
|
72
61
|
|
73
|
-
const target =
|
74
|
-
const outFile =
|
62
|
+
const target = Prefs.target ?? 'wasm';
|
63
|
+
const outFile = Prefs.o;
|
75
64
|
|
76
65
|
if (target === 'wasm' && outFile) {
|
77
|
-
writeFileSync(outFile, Buffer.from(
|
66
|
+
fs.writeFileSync(outFile, Buffer.from(wasm));
|
78
67
|
|
79
68
|
if (process.version) process.exit();
|
80
69
|
}
|
@@ -84,7 +73,7 @@ export default (code, flags) => {
|
|
84
73
|
out.c = c;
|
85
74
|
|
86
75
|
if (outFile) {
|
87
|
-
writeFileSync(outFile, c);
|
76
|
+
fs.writeFileSync(outFile, c);
|
88
77
|
} else {
|
89
78
|
console.log(c);
|
90
79
|
}
|
@@ -93,23 +82,25 @@ export default (code, flags) => {
|
|
93
82
|
}
|
94
83
|
|
95
84
|
if (target === 'native') {
|
96
|
-
let compiler =
|
97
|
-
const cO =
|
85
|
+
let compiler = Prefs.compiler ?? 'clang';
|
86
|
+
const cO = Prefs._cO ?? 'Ofast';
|
98
87
|
|
99
88
|
if (compiler === 'zig') compiler = [ 'zig', 'cc' ];
|
100
89
|
else compiler = [ compiler ];
|
101
90
|
|
102
|
-
const tmpfile = '
|
91
|
+
const tmpfile = 'porffor_tmp.c';
|
103
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' ];
|
104
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' ];
|
105
94
|
const args = [ ...compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native', '-s', '-ffast-math', '-fno-exceptions' ];
|
106
95
|
|
107
96
|
const c = toc(out);
|
108
|
-
writeFileSync(tmpfile, c);
|
97
|
+
fs.writeFileSync(tmpfile, c);
|
109
98
|
|
110
99
|
// obvious command escape is obvious
|
111
100
|
execSync(args.join(' '), { stdio: 'inherit' });
|
112
101
|
|
102
|
+
fs.unlinkSync(tmpfile);
|
103
|
+
|
113
104
|
if (process.version) process.exit();
|
114
105
|
}
|
115
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
|
-
|
8
|
+
assemble: [ 20, 250, 80 ],
|
9
9
|
alloc: [ 250, 250, 20 ],
|
10
|
-
|
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 =
|
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 && !
|
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 (
|
101
|
+
if (Prefs.optInlineOnly) return;
|
101
102
|
|
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; }, {});
|
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?
|
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)
|
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.
|
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.
|
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];
|
@@ -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
|
-
|
551
|
-
|
552
|
-
|
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,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 =
|
12
|
-
globalThis.typedInput = 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
|
-
|
25
|
+
globalThis.parser = '';
|
26
|
+
let parse;
|
22
27
|
const loadParser = async (fallbackParser = 'acorn', forceParser) => {
|
23
|
-
parser = forceParser ?? process.argv.find(x => x.startsWith('
|
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('
|
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
|
-
|
33
|
-
|
34
|
-
|
37
|
+
try {
|
38
|
+
const ast = parse(input, {
|
39
|
+
// acorn
|
40
|
+
ecmaVersion: 'latest',
|
35
41
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
42
|
+
// meriyah
|
43
|
+
next: true,
|
44
|
+
module: flags.includes('module'),
|
45
|
+
webcompat: true,
|
40
46
|
|
41
|
-
|
42
|
-
|
47
|
+
// babel
|
48
|
+
plugins: types ? ['estree', 'typescript'] : ['estree'],
|
43
49
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
+
// multiple
|
51
|
+
sourceType: flags.includes('module') ? 'module' : 'script',
|
52
|
+
ranges: false,
|
53
|
+
tokens: false,
|
54
|
+
comments: false,
|
55
|
+
});
|
50
56
|
|
51
|
-
|
57
|
+
if (ast.type === 'File') return ast.program;
|
52
58
|
|
53
|
-
|
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', 'alwaysMemory' ];
|
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;
|