porffor 0.2.0-c7b7423 → 0.2.0-c87ffeb
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/.vscode/launch.json +18 -0
- package/LICENSE +20 -20
- package/README.md +131 -71
- package/asur/README.md +2 -0
- package/asur/index.js +1262 -0
- package/byg/index.js +237 -0
- package/compiler/2c.js +322 -72
- package/compiler/{sections.js → assemble.js} +63 -15
- package/compiler/builtins/annexb_string.js +72 -0
- package/compiler/builtins/annexb_string.ts +19 -0
- package/compiler/builtins/array.ts +145 -0
- package/compiler/builtins/base64.ts +151 -0
- package/compiler/builtins/crypto.ts +120 -0
- package/compiler/builtins/date.ts +7 -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 +42 -0
- package/compiler/builtins/string.ts +1055 -0
- package/compiler/builtins/tostring.ts +45 -0
- package/compiler/builtins.js +601 -269
- package/compiler/{codeGen.js → codegen.js} +1231 -472
- package/compiler/decompile.js +3 -3
- package/compiler/embedding.js +22 -22
- package/compiler/encoding.js +98 -114
- package/compiler/generated_builtins.js +695 -0
- package/compiler/index.js +36 -34
- package/compiler/log.js +6 -3
- package/compiler/opt.js +65 -29
- package/compiler/parse.js +42 -35
- package/compiler/precompile.js +123 -0
- package/compiler/prefs.js +26 -0
- package/compiler/prototype.js +177 -37
- package/compiler/types.js +37 -0
- package/compiler/wasmSpec.js +30 -7
- package/compiler/wrap.js +138 -42
- package/package.json +9 -5
- package/porf +4 -0
- package/rhemyn/compile.js +5 -3
- package/rhemyn/parse.js +323 -320
- package/rhemyn/test/parse.js +58 -58
- package/runner/compare.js +34 -34
- package/runner/debug.js +122 -0
- package/runner/index.js +49 -10
- package/runner/profiler.js +102 -0
- package/runner/repl.js +42 -9
- package/runner/sizes.js +37 -37
- package/test262_changes_from_1afe9b87d2_to_04-09.md +270 -0
- 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/tmp.c +0 -69
- 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,13 +98,19 @@ 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) {
|
104
109
|
const wasm = f.wasm;
|
105
110
|
|
106
|
-
|
111
|
+
const lastType = f.locals['#last_type'];
|
112
|
+
|
113
|
+
let runs = (+Prefs.optWasmRuns) || 2; // todo: how many by default?
|
107
114
|
while (runs > 0) {
|
108
115
|
runs--;
|
109
116
|
|
@@ -125,6 +132,13 @@ export default (funcs, globals, pages) => {
|
|
125
132
|
if (inst[0] === Opcodes.local_get) getCount[inst[1]]++;
|
126
133
|
if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) setCount[inst[1]]++;
|
127
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
|
+
|
128
142
|
if (inst[0] === Opcodes.block) {
|
129
143
|
// remove unneeded blocks (no brs inside)
|
130
144
|
// block
|
@@ -141,7 +155,7 @@ export default (funcs, globals, pages) => {
|
|
141
155
|
depth--;
|
142
156
|
if (depth <= 0) break;
|
143
157
|
}
|
144
|
-
if (op === Opcodes.br || op === Opcodes.br_if) {
|
158
|
+
if (op === Opcodes.br || op === Opcodes.br_if || op === Opcodes.br_table) {
|
145
159
|
hasBranch = true;
|
146
160
|
break;
|
147
161
|
}
|
@@ -154,18 +168,18 @@ export default (funcs, globals, pages) => {
|
|
154
168
|
|
155
169
|
wasm.splice(j - 1, 1); // remove end of this block
|
156
170
|
|
157
|
-
if (optLog) log('opt', `removed unneeded block in for loop`);
|
171
|
+
if (Prefs.optLog) log('opt', `removed unneeded block in for loop`);
|
158
172
|
}
|
159
173
|
}
|
160
174
|
|
161
|
-
if (inst[inst.length - 1] === 'string_only' && !pages.
|
175
|
+
if (inst[inst.length - 1] === 'string_only' && !pages.hasAnyString) {
|
162
176
|
// remove this inst
|
163
177
|
wasm.splice(i, 1);
|
164
178
|
if (i > 0) i--;
|
165
179
|
inst = wasm[i];
|
166
180
|
}
|
167
181
|
|
168
|
-
if (inst[inst.length - 1] === 'string_only|start' && !pages.
|
182
|
+
if (inst[inst.length - 1] === 'string_only|start' && !pages.hasAnyString) {
|
169
183
|
let j = i;
|
170
184
|
for (; j < wasm.length; j++) {
|
171
185
|
const op = wasm[j];
|
@@ -186,6 +200,7 @@ export default (funcs, globals, pages) => {
|
|
186
200
|
let missing = false;
|
187
201
|
if (type === 'Array') missing = !pages.hasArray;
|
188
202
|
if (type === 'String') missing = !pages.hasString;
|
203
|
+
if (type === 'ByteString') missing = !pages.hasByteString;
|
189
204
|
|
190
205
|
if (missing) {
|
191
206
|
let j = i, depth = 0;
|
@@ -202,7 +217,7 @@ export default (funcs, globals, pages) => {
|
|
202
217
|
i -= 4;
|
203
218
|
inst = wasm[i];
|
204
219
|
|
205
|
-
if (optLog) log('opt', `removed unneeded typeswitch check`);
|
220
|
+
if (Prefs.optLog) log('opt', `removed unneeded typeswitch check`);
|
206
221
|
}
|
207
222
|
}
|
208
223
|
|
@@ -221,16 +236,24 @@ export default (funcs, globals, pages) => {
|
|
221
236
|
}
|
222
237
|
|
223
238
|
if (checks === 0) {
|
239
|
+
// todo: review indexes below
|
224
240
|
wasm.splice(j - 1, 2, [ Opcodes.drop ]); // remove typeswitch start
|
225
241
|
wasm.splice(i - 1, 1); // remove this inst
|
226
242
|
|
227
|
-
if (optLog) log('opt', 'removed unneeded entire typeswitch');
|
243
|
+
if (Prefs.optLog) log('opt', 'removed unneeded entire typeswitch');
|
228
244
|
|
229
245
|
if (i > 0) i--;
|
230
246
|
continue;
|
231
247
|
}
|
232
248
|
}
|
233
249
|
|
250
|
+
// remove setting last type if it is never gotten
|
251
|
+
if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType?.idx) {
|
252
|
+
// replace this inst with drop
|
253
|
+
wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
|
254
|
+
if (i > 0) i--;
|
255
|
+
}
|
256
|
+
|
234
257
|
if (i < 1) continue;
|
235
258
|
let lastInst = wasm[i - 1];
|
236
259
|
|
@@ -246,7 +269,7 @@ export default (funcs, globals, pages) => {
|
|
246
269
|
|
247
270
|
getCount[inst[1]]--;
|
248
271
|
i--;
|
249
|
-
// if (optLog) log('opt', `consolidated set, get -> tee`);
|
272
|
+
// if (Prefs.optLog) log('opt', `consolidated set, get -> tee`);
|
250
273
|
continue;
|
251
274
|
}
|
252
275
|
|
@@ -314,7 +337,7 @@ export default (funcs, globals, pages) => {
|
|
314
337
|
|
315
338
|
wasm.splice(i - 1, 2); // remove this inst and last
|
316
339
|
i -= 2;
|
317
|
-
// if (optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
|
340
|
+
// if (Prefs.optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
|
318
341
|
continue;
|
319
342
|
}
|
320
343
|
|
@@ -327,7 +350,7 @@ export default (funcs, globals, pages) => {
|
|
327
350
|
|
328
351
|
wasm.splice(i - 1, 2); // remove this inst and last
|
329
352
|
i -= 2;
|
330
|
-
// if (optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
|
353
|
+
// if (Prefs.optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
|
331
354
|
continue;
|
332
355
|
}
|
333
356
|
|
@@ -342,7 +365,7 @@ export default (funcs, globals, pages) => {
|
|
342
365
|
|
343
366
|
wasm.splice(i, 1); // remove this inst
|
344
367
|
i--;
|
345
|
-
if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
|
368
|
+
if (Prefs.optLog) log('opt', `converted const -> i32 convert into i32 const`);
|
346
369
|
continue;
|
347
370
|
}
|
348
371
|
|
@@ -357,7 +380,7 @@ export default (funcs, globals, pages) => {
|
|
357
380
|
|
358
381
|
wasm.splice(i, 1); // remove this inst
|
359
382
|
i--;
|
360
|
-
if (optLog) log('opt', `converted i32 const -> convert into const`);
|
383
|
+
if (Prefs.optLog) log('opt', `converted i32 const -> convert into const`);
|
361
384
|
continue;
|
362
385
|
}
|
363
386
|
|
@@ -372,7 +395,7 @@ export default (funcs, globals, pages) => {
|
|
372
395
|
|
373
396
|
wasm.splice(i, 1); // remove this inst (return)
|
374
397
|
i--;
|
375
|
-
if (optLog) log('opt', `tail called return, call`);
|
398
|
+
if (Prefs.optLog) log('opt', `tail called return, call`);
|
376
399
|
continue;
|
377
400
|
}
|
378
401
|
|
@@ -385,7 +408,7 @@ export default (funcs, globals, pages) => {
|
|
385
408
|
|
386
409
|
wasm.splice(i, 1); // remove this inst (return)
|
387
410
|
i--;
|
388
|
-
// if (optLog) log('opt', `removed redundant return at end`);
|
411
|
+
// if (Prefs.optLog) log('opt', `removed redundant return at end`);
|
389
412
|
continue;
|
390
413
|
}
|
391
414
|
|
@@ -415,7 +438,7 @@ export default (funcs, globals, pages) => {
|
|
415
438
|
// <nothing>
|
416
439
|
|
417
440
|
wasm.splice(i - 2, 3); // remove this, last, 2nd last insts
|
418
|
-
if (optLog) log('opt', `removed redundant inline param local handling`);
|
441
|
+
if (Prefs.optLog) log('opt', `removed redundant inline param local handling`);
|
419
442
|
i -= 3;
|
420
443
|
continue;
|
421
444
|
}
|
@@ -423,12 +446,12 @@ export default (funcs, globals, pages) => {
|
|
423
446
|
|
424
447
|
if (optLevel < 2) continue;
|
425
448
|
|
426
|
-
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(', ')}`);
|
427
450
|
|
428
451
|
// remove unneeded var: remove pass
|
429
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
|
430
453
|
let unneededCandidates = Object.keys(getCount).filter(x => getCount[x] === 0 || (getCount[x] === 1 && setCount[x] === 0)).map(x => parseInt(x));
|
431
|
-
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})`);
|
432
455
|
|
433
456
|
// note: disabled for now due to instability
|
434
457
|
if (unneededCandidates.length > 0 && false) for (let i = 0; i < wasm.length; i++) {
|
@@ -446,7 +469,7 @@ export default (funcs, globals, pages) => {
|
|
446
469
|
wasm.splice(i - 1, 2); // remove insts
|
447
470
|
i -= 2;
|
448
471
|
delete f.locals[Object.keys(f.locals)[inst[1]]]; // remove from locals
|
449
|
-
if (optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
|
472
|
+
if (Prefs.optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
|
450
473
|
}
|
451
474
|
|
452
475
|
if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
|
@@ -474,7 +497,7 @@ export default (funcs, globals, pages) => {
|
|
474
497
|
unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
|
475
498
|
unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
|
476
499
|
|
477
|
-
if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
|
500
|
+
if (Prefs.optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
|
478
501
|
}
|
479
502
|
}
|
480
503
|
|
@@ -510,7 +533,7 @@ export default (funcs, globals, pages) => {
|
|
510
533
|
let b = lastInst[1];
|
511
534
|
|
512
535
|
const val = performWasmOp(inst[0], a, b);
|
513
|
-
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})`);
|
514
537
|
|
515
538
|
wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
|
516
539
|
i -= 2;
|
@@ -522,14 +545,27 @@ export default (funcs, globals, pages) => {
|
|
522
545
|
for (const x in useCount) {
|
523
546
|
if (useCount[x] === 0) {
|
524
547
|
const name = Object.keys(f.locals)[localIdxs.indexOf(parseInt(x))];
|
525
|
-
if (optLog) log('opt', `removed internal local ${x} (${name})`);
|
548
|
+
if (Prefs.optLog) log('opt', `removed internal local ${x} (${name})`);
|
526
549
|
delete f.locals[name];
|
527
550
|
}
|
528
551
|
}
|
529
552
|
|
530
|
-
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(', ')}`);
|
531
554
|
}
|
532
555
|
}
|
533
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
|
+
|
534
570
|
// return funcs;
|
535
571
|
};
|
package/compiler/parse.js
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
import { log } from "./log.js";
|
2
|
+
import Prefs from './prefs.js';
|
3
|
+
|
4
|
+
// import { parse } from 'acorn';
|
2
5
|
|
3
6
|
// deno compat
|
4
7
|
if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
|
@@ -6,16 +9,9 @@ if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
|
|
6
9
|
globalThis.process = { argv: ['', '', ...Deno.args], stdout: { write: str => Deno.writeAllSync(Deno.stdout, textEncoder.encode(str)) } };
|
7
10
|
}
|
8
11
|
|
9
|
-
//
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
const loadParser = async () => {
|
14
|
-
parser = process.argv.find(x => x.startsWith('-parser='))?.split('=')?.[1] ?? 'acorn';
|
15
|
-
0, { parse } = (await import((globalThis.document ? 'https://esm.sh/' : '') + parser));
|
16
|
-
};
|
17
|
-
globalThis._porf_loadParser = loadParser;
|
18
|
-
await loadParser();
|
12
|
+
// should we try to support types (while parsing)
|
13
|
+
const types = Prefs.parseTypes;
|
14
|
+
globalThis.typedInput = types && Prefs.optTypes;
|
19
15
|
|
20
16
|
// todo: review which to use by default
|
21
17
|
// supported parsers:
|
@@ -24,32 +20,43 @@ await loadParser();
|
|
24
20
|
// - hermes-parser
|
25
21
|
// - @babel/parser
|
26
22
|
|
27
|
-
|
28
|
-
|
23
|
+
globalThis.parser = '';
|
24
|
+
let parse;
|
25
|
+
const loadParser = async (fallbackParser = 'acorn', forceParser) => {
|
26
|
+
parser = forceParser ?? process.argv.find(x => x.startsWith('-parser='))?.split('=')?.[1] ?? fallbackParser;
|
27
|
+
0, { parse } = (await import((globalThis.document || globalThis.Deno ? 'https://esm.sh/' : '') + parser));
|
28
|
+
};
|
29
|
+
globalThis._porf_loadParser = loadParser;
|
30
|
+
await loadParser(types ? '@babel/parser' : undefined);
|
29
31
|
|
30
|
-
if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('
|
32
|
+
if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('parse', `passed -parse-types with a parser (${parser}) which does not support`);
|
31
33
|
|
32
34
|
export default (input, flags) => {
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
35
|
+
try {
|
36
|
+
const ast = parse(input, {
|
37
|
+
// acorn
|
38
|
+
ecmaVersion: 'latest',
|
39
|
+
|
40
|
+
// meriyah
|
41
|
+
next: true,
|
42
|
+
module: flags.includes('module'),
|
43
|
+
webcompat: true,
|
44
|
+
|
45
|
+
// babel
|
46
|
+
plugins: types ? ['estree', 'typescript'] : ['estree'],
|
47
|
+
|
48
|
+
// multiple
|
49
|
+
sourceType: flags.includes('module') ? 'module' : 'script',
|
50
|
+
ranges: false,
|
51
|
+
tokens: false,
|
52
|
+
comments: false,
|
53
|
+
});
|
54
|
+
|
55
|
+
if (ast.type === 'File') return ast.program;
|
56
|
+
|
57
|
+
return ast;
|
58
|
+
} catch (e) {
|
59
|
+
// normalize error class thrown by 3rd party parsers
|
60
|
+
throw new SyntaxError(e.message, { cause: e });
|
61
|
+
}
|
55
62
|
};
|
@@ -0,0 +1,123 @@
|
|
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', '-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);
|
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
|
+
await compile(join(dir, file), [ funcs, globals ]);
|
97
|
+
}
|
98
|
+
|
99
|
+
// ${x.pages && x.pages.size > 0 ? ` pages: ${JSON.stringify(Object.fromEntries(x.pages.entries()))},` : ''}
|
100
|
+
// ${x.used && x.used.length > 0 ? ` used: ${JSON.stringify(x.used)},` : ''}
|
101
|
+
|
102
|
+
return `// autogenerated by compiler/precompile.js
|
103
|
+
import { number } from './embedding.js';
|
104
|
+
|
105
|
+
export const BuiltinFuncs = function() {
|
106
|
+
${funcs.map(x => ` this.${x.name} = {
|
107
|
+
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}')]`)},
|
108
|
+
params: ${JSON.stringify(x.params)},
|
109
|
+
typedParams: true,
|
110
|
+
returns: ${JSON.stringify(x.returns)},
|
111
|
+
${x.returnType != null ? `returnType: ${JSON.stringify(x.returnType)}` : 'typedReturns: true'},
|
112
|
+
locals: ${JSON.stringify(Object.values(x.locals).slice(x.params.length).map(x => x.type))},
|
113
|
+
localNames: ${JSON.stringify(Object.keys(x.locals))},
|
114
|
+
${x.data && x.data.length > 0 ? ` data: ${JSON.stringify(x.data)},` : ''}
|
115
|
+
${x.exceptions && x.exceptions.length > 0 ? ` exceptions: ${JSON.stringify(x.exceptions)},` : ''}
|
116
|
+
};`.replaceAll('\n\n', '\n').replaceAll('\n\n', '\n')).join('\n')}
|
117
|
+
};`;
|
118
|
+
};
|
119
|
+
|
120
|
+
const code = await precompile();
|
121
|
+
// console.log(code);
|
122
|
+
|
123
|
+
fs.writeFileSync(join(__dirname, 'generated_builtins.js'), code);
|