porffor 0.2.0-a759814 → 0.2.0-ae8cbb8
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 +156 -87
- 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} +64 -16
- 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/boolean.ts +6 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +2070 -0
- package/compiler/builtins/escape.ts +139 -0
- package/compiler/builtins/int.ts +147 -0
- package/compiler/builtins/number.ts +534 -0
- package/compiler/builtins/porffor.d.ts +59 -0
- package/compiler/builtins/string.ts +1070 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +580 -272
- package/compiler/{codeGen.js → codegen.js} +1297 -434
- package/compiler/decompile.js +3 -4
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +108 -10
- package/compiler/generated_builtins.js +1517 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +56 -30
- package/compiler/parse.js +33 -23
- package/compiler/precompile.js +128 -0
- package/compiler/prefs.js +27 -0
- package/compiler/prototype.js +182 -42
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +31 -7
- package/compiler/wrap.js +141 -43
- 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);
|
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) => {
|
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) => {
|
|
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 (
|
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?
|
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.
|
175
|
+
if (inst[inst.length - 1] === 'string_only' && !pages.hasAnyString) {
|
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.
|
182
|
+
if (inst[inst.length - 1] === 'string_only|start' && !pages.hasAnyString) {
|
171
183
|
let j = i;
|
172
184
|
for (; j < wasm.length; j++) {
|
173
185
|
const op = wasm[j];
|
@@ -188,6 +200,7 @@ export default (funcs, globals, pages) => {
|
|
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
|
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 =
|
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);
|