porffor 0.2.0-f2bbe1f → 0.2.0-f549952
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 +248 -0
- package/LICENSE +20 -20
- package/README.md +154 -89
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +317 -72
- 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 +145 -0
- package/compiler/builtins/base64.ts +76 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2071 -0
- package/compiler/builtins/escape.ts +141 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +527 -0
- package/compiler/builtins/porffor.d.ts +59 -0
- package/compiler/builtins/string.ts +1055 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +449 -269
- package/compiler/{codeGen.js → codegen.js} +1153 -418
- package/compiler/decompile.js +0 -1
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1481 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +51 -36
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +177 -37
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +30 -7
- package/compiler/wrap.js +56 -40
- package/package.json +9 -5
- package/porf +4 -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 +91 -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/runner/info.js +0 -89
- package/runner/profile.js +0 -46
- package/runner/results.json +0 -1
- package/runner/transform.js +0 -15
- 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,59 +26,54 @@ 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 };
|
61
|
+
|
62
|
+
const target = Prefs.target ?? 'wasm';
|
63
|
+
const outFile = Prefs.o;
|
72
64
|
|
73
|
-
|
74
|
-
|
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
|
-
|
91
|
-
const cO =
|
85
|
+
let compiler = Prefs.compiler ?? 'clang';
|
86
|
+
const cO = Prefs._cO ?? 'Ofast';
|
92
87
|
|
93
|
-
|
94
|
-
|
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
|
-
|
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];
|
@@ -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
|
-
|
550
|
-
|
551
|
-
|
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 =
|
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' ];
|
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;
|