porffor 0.2.0-c7b7423 → 0.2.0-dcc06c8
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/README.md +89 -47
- package/compiler/2c.js +321 -71
- package/compiler/builtins/base64.ts +88 -0
- package/compiler/builtins/porffor.d.ts +10 -0
- package/compiler/builtins.js +178 -60
- package/compiler/codeGen.js +537 -271
- package/compiler/decompile.js +3 -3
- package/compiler/encoding.js +2 -116
- package/compiler/generated_builtins.js +3 -0
- package/compiler/index.js +22 -22
- package/compiler/opt.js +61 -26
- package/compiler/parse.js +13 -12
- package/compiler/precompile.js +79 -0
- package/compiler/prefs.js +22 -0
- package/compiler/prototype.js +176 -20
- package/compiler/sections.js +8 -7
- package/compiler/wasmSpec.js +6 -2
- package/compiler/wrap.js +103 -9
- package/demo.js +3 -0
- package/demo.ts +1 -0
- package/filesize.cmd +2 -0
- package/hello +0 -0
- package/package.json +1 -1
- package/porf +2 -0
- package/rhemyn/compile.js +2 -1
- package/runner/index.js +20 -3
- package/runner/repl.js +2 -2
- package/tmp.c +136 -53
- package/compiler/builtins/base64.js +0 -92
package/compiler/decompile.js
CHANGED
@@ -15,7 +15,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
15
15
|
if (name) out += `${makeSignature(params, returns)} ;; $${name} (${ind})\n`;
|
16
16
|
|
17
17
|
const justLocals = Object.values(locals).sort((a, b) => a.idx - b.idx).slice(params.length);
|
18
|
-
if (justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
|
18
|
+
if (name && justLocals.length > 0) out += ` local ${justLocals.map(x => invValtype[x.type]).join(' ')}\n`;
|
19
19
|
|
20
20
|
let i = -1, lastInst;
|
21
21
|
let byte = 0;
|
@@ -32,7 +32,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
32
32
|
inst = [ [ inst[0], inst[1] ], ...inst.slice(2) ];
|
33
33
|
}
|
34
34
|
|
35
|
-
if (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all) depth--;
|
35
|
+
if (depth > 0 && (inst[0] === Opcodes.end || inst[0] === Opcodes.else || inst[0] === Opcodes.catch_all)) depth--;
|
36
36
|
|
37
37
|
out += ' '.repeat(Math.max(0, depth * 2));
|
38
38
|
|
@@ -119,7 +119,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
119
119
|
export const highlightAsm = asm => asm
|
120
120
|
.replace(/(local|global|memory)\.[^\s]*/g, _ => `\x1B[31m${_}\x1B[0m`)
|
121
121
|
.replace(/(i(8|16|32|64)x[0-9]+|v128)(\.[^\s]*)?/g, _ => `\x1B[34m${_}\x1B[0m`)
|
122
|
-
.replace(/
|
122
|
+
.replace(/(i32|i64|f32|f64|drop)(\.[^\s]*)?/g, _ => `\x1B[36m${_}\x1B[0m`)
|
123
123
|
.replace(/(return_call|call|br_if|br|return|rethrow|throw)/g, _ => `\x1B[35m${_}\x1B[0m`)
|
124
124
|
.replace(/(block|loop|if|end|else|try|catch_all|catch|delegate)/g, _ => `\x1B[95m${_}\x1B[0m`)
|
125
125
|
.replace(/unreachable/g, _ => `\x1B[91m${_}\x1B[0m`)
|
package/compiler/encoding.js
CHANGED
@@ -105,119 +105,5 @@ export const read_unsignedLEB128 = _input => {
|
|
105
105
|
};
|
106
106
|
|
107
107
|
// ieee 754 binary64
|
108
|
-
|
109
|
-
|
110
|
-
// BSD 3-Clause. Copyright 2008 Fair Oaks Labs, Inc. (https://github.com/feross/ieee754/blob/master/LICENSE)
|
111
|
-
export const ieee754_binary64 = value => {
|
112
|
-
return [...new Uint8Array(new Float64Array([ value ]).buffer)];
|
113
|
-
|
114
|
-
let isLE = true, mLen = 52, nBytes = 8, offset = 0;
|
115
|
-
let buffer = new Array(nBytes).fill(0);
|
116
|
-
|
117
|
-
let e, m, c
|
118
|
-
let eLen = (nBytes * 8) - mLen - 1
|
119
|
-
const eMax = (1 << eLen) - 1
|
120
|
-
const eBias = eMax >> 1
|
121
|
-
const rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
|
122
|
-
let i = isLE ? 0 : (nBytes - 1)
|
123
|
-
const d = isLE ? 1 : -1
|
124
|
-
const s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
|
125
|
-
|
126
|
-
value = Math.abs(value)
|
127
|
-
|
128
|
-
if (isNaN(value) || value === Infinity) {
|
129
|
-
m = isNaN(value) ? 1 : 0
|
130
|
-
e = eMax
|
131
|
-
} else {
|
132
|
-
e = Math.floor(Math.log(value) / Math.LN2)
|
133
|
-
if (value * (c = Math.pow(2, -e)) < 1) {
|
134
|
-
e--
|
135
|
-
c *= 2
|
136
|
-
}
|
137
|
-
if (e + eBias >= 1) {
|
138
|
-
value += rt / c
|
139
|
-
} else {
|
140
|
-
value += rt * Math.pow(2, 1 - eBias)
|
141
|
-
}
|
142
|
-
if (value * c >= 2) {
|
143
|
-
e++
|
144
|
-
c /= 2
|
145
|
-
}
|
146
|
-
|
147
|
-
if (e + eBias >= eMax) {
|
148
|
-
m = 0
|
149
|
-
e = eMax
|
150
|
-
} else if (e + eBias >= 1) {
|
151
|
-
m = ((value * c) - 1) * Math.pow(2, mLen)
|
152
|
-
e = e + eBias
|
153
|
-
} else {
|
154
|
-
m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
|
155
|
-
e = 0
|
156
|
-
}
|
157
|
-
}
|
158
|
-
|
159
|
-
while (mLen >= 8) {
|
160
|
-
buffer[offset + i] = m & 0xff
|
161
|
-
i += d
|
162
|
-
m /= 256
|
163
|
-
mLen -= 8
|
164
|
-
}
|
165
|
-
|
166
|
-
e = (e << mLen) | m
|
167
|
-
eLen += mLen
|
168
|
-
while (eLen > 0) {
|
169
|
-
buffer[offset + i] = e & 0xff
|
170
|
-
i += d
|
171
|
-
e /= 256
|
172
|
-
eLen -= 8
|
173
|
-
}
|
174
|
-
|
175
|
-
buffer[offset + i - d] |= s * 128
|
176
|
-
|
177
|
-
return buffer;
|
178
|
-
};
|
179
|
-
|
180
|
-
export const read_ieee754_binary64 = buffer => {
|
181
|
-
return new Float64Array(new Uint8Array(buffer).buffer)[0];
|
182
|
-
|
183
|
-
let isLE = true, mLen = 52, nBytes = 8, offset = 0;
|
184
|
-
|
185
|
-
let e, m
|
186
|
-
const eLen = (nBytes * 8) - mLen - 1
|
187
|
-
const eMax = (1 << eLen) - 1
|
188
|
-
const eBias = eMax >> 1
|
189
|
-
let nBits = -7
|
190
|
-
let i = isLE ? (nBytes - 1) : 0
|
191
|
-
const d = isLE ? -1 : 1
|
192
|
-
let s = buffer[offset + i]
|
193
|
-
|
194
|
-
i += d
|
195
|
-
|
196
|
-
e = s & ((1 << (-nBits)) - 1)
|
197
|
-
s >>= (-nBits)
|
198
|
-
nBits += eLen
|
199
|
-
while (nBits > 0) {
|
200
|
-
e = (e * 256) + buffer[offset + i]
|
201
|
-
i += d
|
202
|
-
nBits -= 8
|
203
|
-
}
|
204
|
-
|
205
|
-
m = e & ((1 << (-nBits)) - 1)
|
206
|
-
e >>= (-nBits)
|
207
|
-
nBits += mLen
|
208
|
-
while (nBits > 0) {
|
209
|
-
m = (m * 256) + buffer[offset + i]
|
210
|
-
i += d
|
211
|
-
nBits -= 8
|
212
|
-
}
|
213
|
-
|
214
|
-
if (e === 0) {
|
215
|
-
e = 1 - eBias
|
216
|
-
} else if (e === eMax) {
|
217
|
-
return m ? NaN : ((s ? -1 : 1) * Infinity)
|
218
|
-
} else {
|
219
|
-
m = m + Math.pow(2, mLen)
|
220
|
-
e = e - eBias
|
221
|
-
}
|
222
|
-
return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
|
223
|
-
};
|
108
|
+
export const ieee754_binary64 = value => [...new Uint8Array(new Float64Array([ value ]).buffer)];
|
109
|
+
export const read_ieee754_binary64 = buffer => new Float64Array(new Uint8Array(buffer).buffer)[0];
|
package/compiler/index.js
CHANGED
@@ -4,8 +4,8 @@ import codeGen from './codeGen.js';
|
|
4
4
|
import opt from './opt.js';
|
5
5
|
import produceSections from './sections.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,21 +26,10 @@ const logFuncs = (funcs, globals, exceptions) => {
|
|
26
26
|
console.log();
|
27
27
|
};
|
28
28
|
|
29
|
-
const getArg = name => process.argv.find(x => x.startsWith(`-${name}=`))?.slice(name.length + 2);
|
30
|
-
|
31
29
|
const writeFileSync = (typeof process?.version !== 'undefined' ? (await import('node:fs')).writeFileSync : 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
35
|
if (flags.includes('info')) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
|
@@ -49,29 +38,35 @@ export default (code, flags) => {
|
|
49
38
|
const { funcs, globals, tags, exceptions, pages, data } = codeGen(program);
|
50
39
|
if (flags.includes('info')) 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);
|
44
|
+
opt(funcs, globals, pages, tags, exceptions);
|
56
45
|
if (flags.includes('info')) console.log(`3. optimized code 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
50
|
const sections = produceSections(funcs, globals, tags, pages, data, flags);
|
62
51
|
if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
|
63
52
|
|
64
|
-
if (allocLog) {
|
53
|
+
if (Prefs.allocLog) {
|
65
54
|
const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
|
66
55
|
const bytes = wasmPages * 65536;
|
67
56
|
log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
|
68
57
|
console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n') + '\n');
|
69
58
|
}
|
70
59
|
|
71
|
-
const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
|
60
|
+
const out = { wasm: sections, 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
|
+
writeFileSync(outFile, Buffer.from(sections));
|
67
|
+
|
68
|
+
if (process.version) process.exit();
|
69
|
+
}
|
75
70
|
|
76
71
|
if (target === 'c') {
|
77
72
|
const c = toc(out);
|
@@ -87,11 +82,16 @@ 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';
|
87
|
+
|
88
|
+
if (compiler === 'zig') compiler = [ 'zig', 'cc' ];
|
89
|
+
else compiler = [ compiler ];
|
92
90
|
|
93
91
|
const tmpfile = 'tmp.c';
|
94
|
-
const args = [ compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO, '-march=native' ];
|
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
97
|
writeFileSync(tmpfile, c);
|
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,12 +98,17 @@ export default (funcs, globals, pages) => {
|
|
97
98
|
}
|
98
99
|
}
|
99
100
|
|
100
|
-
if (
|
101
|
+
if (Prefs.optInlineOnly) return;
|
102
|
+
|
103
|
+
const tagUse = tags.reduce((acc, x) => { acc[x.idx] = 0; return acc; }, {});
|
104
|
+
const exceptionUse = exceptions.reduce((acc, _, i) => { acc[i] = 0; return acc; }, {});
|
101
105
|
|
102
106
|
// wasm transform pass
|
103
107
|
for (const f of funcs) {
|
104
108
|
const wasm = f.wasm;
|
105
109
|
|
110
|
+
const lastType = f.locals['#last_type'];
|
111
|
+
|
106
112
|
let runs = 2; // how many by default? add arg?
|
107
113
|
while (runs > 0) {
|
108
114
|
runs--;
|
@@ -125,6 +131,13 @@ export default (funcs, globals, pages) => {
|
|
125
131
|
if (inst[0] === Opcodes.local_get) getCount[inst[1]]++;
|
126
132
|
if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) setCount[inst[1]]++;
|
127
133
|
|
134
|
+
if (inst[0] === Opcodes.throw) {
|
135
|
+
tagUse[inst[1]]++;
|
136
|
+
|
137
|
+
const exceptId = read_signedLEB128(wasm[i - 1].slice(1));
|
138
|
+
exceptionUse[exceptId]++;
|
139
|
+
}
|
140
|
+
|
128
141
|
if (inst[0] === Opcodes.block) {
|
129
142
|
// remove unneeded blocks (no brs inside)
|
130
143
|
// block
|
@@ -141,7 +154,7 @@ export default (funcs, globals, pages) => {
|
|
141
154
|
depth--;
|
142
155
|
if (depth <= 0) break;
|
143
156
|
}
|
144
|
-
if (op === Opcodes.br || op === Opcodes.br_if) {
|
157
|
+
if (op === Opcodes.br || op === Opcodes.br_if || op === Opcodes.br_table) {
|
145
158
|
hasBranch = true;
|
146
159
|
break;
|
147
160
|
}
|
@@ -154,7 +167,7 @@ export default (funcs, globals, pages) => {
|
|
154
167
|
|
155
168
|
wasm.splice(j - 1, 1); // remove end of this block
|
156
169
|
|
157
|
-
if (optLog) log('opt', `removed unneeded block in for loop`);
|
170
|
+
if (Prefs.optLog) log('opt', `removed unneeded block in for loop`);
|
158
171
|
}
|
159
172
|
}
|
160
173
|
|
@@ -186,6 +199,7 @@ export default (funcs, globals, pages) => {
|
|
186
199
|
let missing = false;
|
187
200
|
if (type === 'Array') missing = !pages.hasArray;
|
188
201
|
if (type === 'String') missing = !pages.hasString;
|
202
|
+
if (type === 'ByteString') missing = !pages.hasByteString;
|
189
203
|
|
190
204
|
if (missing) {
|
191
205
|
let j = i, depth = 0;
|
@@ -202,7 +216,7 @@ export default (funcs, globals, pages) => {
|
|
202
216
|
i -= 4;
|
203
217
|
inst = wasm[i];
|
204
218
|
|
205
|
-
if (optLog) log('opt', `removed unneeded typeswitch check`);
|
219
|
+
if (Prefs.optLog) log('opt', `removed unneeded typeswitch check`);
|
206
220
|
}
|
207
221
|
}
|
208
222
|
|
@@ -221,16 +235,24 @@ export default (funcs, globals, pages) => {
|
|
221
235
|
}
|
222
236
|
|
223
237
|
if (checks === 0) {
|
238
|
+
// todo: review indexes below
|
224
239
|
wasm.splice(j - 1, 2, [ Opcodes.drop ]); // remove typeswitch start
|
225
240
|
wasm.splice(i - 1, 1); // remove this inst
|
226
241
|
|
227
|
-
if (optLog) log('opt', 'removed unneeded entire typeswitch');
|
242
|
+
if (Prefs.optLog) log('opt', 'removed unneeded entire typeswitch');
|
228
243
|
|
229
244
|
if (i > 0) i--;
|
230
245
|
continue;
|
231
246
|
}
|
232
247
|
}
|
233
248
|
|
249
|
+
// remove setting last type if it is never gotten
|
250
|
+
if (!f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType?.idx) {
|
251
|
+
// replace this inst with drop
|
252
|
+
wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
|
253
|
+
if (i > 0) i--;
|
254
|
+
}
|
255
|
+
|
234
256
|
if (i < 1) continue;
|
235
257
|
let lastInst = wasm[i - 1];
|
236
258
|
|
@@ -246,7 +268,7 @@ export default (funcs, globals, pages) => {
|
|
246
268
|
|
247
269
|
getCount[inst[1]]--;
|
248
270
|
i--;
|
249
|
-
// if (optLog) log('opt', `consolidated set, get -> tee`);
|
271
|
+
// if (Prefs.optLog) log('opt', `consolidated set, get -> tee`);
|
250
272
|
continue;
|
251
273
|
}
|
252
274
|
|
@@ -314,7 +336,7 @@ export default (funcs, globals, pages) => {
|
|
314
336
|
|
315
337
|
wasm.splice(i - 1, 2); // remove this inst and last
|
316
338
|
i -= 2;
|
317
|
-
// if (optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
|
339
|
+
// if (Prefs.optLog) log('opt', `removed redundant i32 -> i64 -> i32 conversion ops`);
|
318
340
|
continue;
|
319
341
|
}
|
320
342
|
|
@@ -327,7 +349,7 @@ export default (funcs, globals, pages) => {
|
|
327
349
|
|
328
350
|
wasm.splice(i - 1, 2); // remove this inst and last
|
329
351
|
i -= 2;
|
330
|
-
// if (optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
|
352
|
+
// if (Prefs.optLog) log('opt', `removed redundant i32 -> f64 -> i32 conversion ops`);
|
331
353
|
continue;
|
332
354
|
}
|
333
355
|
|
@@ -342,7 +364,7 @@ export default (funcs, globals, pages) => {
|
|
342
364
|
|
343
365
|
wasm.splice(i, 1); // remove this inst
|
344
366
|
i--;
|
345
|
-
if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
|
367
|
+
if (Prefs.optLog) log('opt', `converted const -> i32 convert into i32 const`);
|
346
368
|
continue;
|
347
369
|
}
|
348
370
|
|
@@ -357,7 +379,7 @@ export default (funcs, globals, pages) => {
|
|
357
379
|
|
358
380
|
wasm.splice(i, 1); // remove this inst
|
359
381
|
i--;
|
360
|
-
if (optLog) log('opt', `converted i32 const -> convert into const`);
|
382
|
+
if (Prefs.optLog) log('opt', `converted i32 const -> convert into const`);
|
361
383
|
continue;
|
362
384
|
}
|
363
385
|
|
@@ -372,7 +394,7 @@ export default (funcs, globals, pages) => {
|
|
372
394
|
|
373
395
|
wasm.splice(i, 1); // remove this inst (return)
|
374
396
|
i--;
|
375
|
-
if (optLog) log('opt', `tail called return, call`);
|
397
|
+
if (Prefs.optLog) log('opt', `tail called return, call`);
|
376
398
|
continue;
|
377
399
|
}
|
378
400
|
|
@@ -385,7 +407,7 @@ export default (funcs, globals, pages) => {
|
|
385
407
|
|
386
408
|
wasm.splice(i, 1); // remove this inst (return)
|
387
409
|
i--;
|
388
|
-
// if (optLog) log('opt', `removed redundant return at end`);
|
410
|
+
// if (Prefs.optLog) log('opt', `removed redundant return at end`);
|
389
411
|
continue;
|
390
412
|
}
|
391
413
|
|
@@ -415,7 +437,7 @@ export default (funcs, globals, pages) => {
|
|
415
437
|
// <nothing>
|
416
438
|
|
417
439
|
wasm.splice(i - 2, 3); // remove this, last, 2nd last insts
|
418
|
-
if (optLog) log('opt', `removed redundant inline param local handling`);
|
440
|
+
if (Prefs.optLog) log('opt', `removed redundant inline param local handling`);
|
419
441
|
i -= 3;
|
420
442
|
continue;
|
421
443
|
}
|
@@ -423,12 +445,12 @@ export default (funcs, globals, pages) => {
|
|
423
445
|
|
424
446
|
if (optLevel < 2) continue;
|
425
447
|
|
426
|
-
if (optLog) log('opt', `get counts: ${Object.keys(f.locals).map(x => `${x} (${f.locals[x].idx}): ${getCount[f.locals[x].idx]}`).join(', ')}`);
|
448
|
+
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
449
|
|
428
450
|
// remove unneeded var: remove pass
|
429
451
|
// 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
452
|
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})`);
|
453
|
+
if (Prefs.optLog) log('opt', `found unneeded locals candidates: ${unneededCandidates.join(', ')} (${unneededCandidates.length}/${Object.keys(getCount).length})`);
|
432
454
|
|
433
455
|
// note: disabled for now due to instability
|
434
456
|
if (unneededCandidates.length > 0 && false) for (let i = 0; i < wasm.length; i++) {
|
@@ -446,7 +468,7 @@ export default (funcs, globals, pages) => {
|
|
446
468
|
wasm.splice(i - 1, 2); // remove insts
|
447
469
|
i -= 2;
|
448
470
|
delete f.locals[Object.keys(f.locals)[inst[1]]]; // remove from locals
|
449
|
-
if (optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
|
471
|
+
if (Prefs.optLog) log('opt', `removed redundant local (get set ${inst[1]})`);
|
450
472
|
}
|
451
473
|
|
452
474
|
if (inst[0] === Opcodes.local_tee && unneededCandidates.includes(inst[1])) {
|
@@ -474,7 +496,7 @@ export default (funcs, globals, pages) => {
|
|
474
496
|
unneededCandidates.splice(unneededCandidates.indexOf(inst[1]), 1);
|
475
497
|
unneededCandidates = unneededCandidates.map(x => x > removedIdx ? (x - 1) : x);
|
476
498
|
|
477
|
-
if (optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
|
499
|
+
if (Prefs.optLog) log('opt', `removed redundant local ${localName} (tee ${inst[1]})`);
|
478
500
|
}
|
479
501
|
}
|
480
502
|
|
@@ -510,7 +532,7 @@ export default (funcs, globals, pages) => {
|
|
510
532
|
let b = lastInst[1];
|
511
533
|
|
512
534
|
const val = performWasmOp(inst[0], a, b);
|
513
|
-
if (optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
|
535
|
+
if (Prefs.optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
|
514
536
|
|
515
537
|
wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
|
516
538
|
i -= 2;
|
@@ -522,12 +544,25 @@ export default (funcs, globals, pages) => {
|
|
522
544
|
for (const x in useCount) {
|
523
545
|
if (useCount[x] === 0) {
|
524
546
|
const name = Object.keys(f.locals)[localIdxs.indexOf(parseInt(x))];
|
525
|
-
if (optLog) log('opt', `removed internal local ${x} (${name})`);
|
547
|
+
if (Prefs.optLog) log('opt', `removed internal local ${x} (${name})`);
|
526
548
|
delete f.locals[name];
|
527
549
|
}
|
528
550
|
}
|
529
551
|
|
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(', ')}`);
|
552
|
+
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(', ')}`);
|
553
|
+
}
|
554
|
+
}
|
555
|
+
|
556
|
+
for (const x in tagUse) {
|
557
|
+
if (tagUse[x] === 0) {
|
558
|
+
const el = tags.find(y => y.idx === x);
|
559
|
+
tags.splice(tags.indexOf(el), 1);
|
560
|
+
}
|
561
|
+
}
|
562
|
+
|
563
|
+
for (const x of Object.keys(exceptionUse).sort((a, b) => b - a)) {
|
564
|
+
if (exceptionUse[x] === 0) {
|
565
|
+
exceptions.splice(+x, 1);
|
531
566
|
}
|
532
567
|
}
|
533
568
|
|
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,8 +20,13 @@ await loadParser();
|
|
24
20
|
// - hermes-parser
|
25
21
|
// - @babel/parser
|
26
22
|
|
27
|
-
|
28
|
-
const
|
23
|
+
let parser, parse;
|
24
|
+
const loadParser = async (fallbackParser = 'acorn', forceParser) => {
|
25
|
+
parser = forceParser ?? process.argv.find(x => x.startsWith('-parser='))?.split('=')?.[1] ?? fallbackParser;
|
26
|
+
0, { parse } = (await import((globalThis.document ? 'https://esm.sh/' : '') + parser));
|
27
|
+
};
|
28
|
+
globalThis._porf_loadParser = loadParser;
|
29
|
+
await loadParser(types ? '@babel/parser' : undefined);
|
29
30
|
|
30
31
|
if (types && !['@babel/parser', 'hermes-parser'].includes(parser)) log.warning('parser', `passed -types with a parser (${parser}) which does not support`);
|
31
32
|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import fs from 'node:fs';
|
2
|
+
import { join } from 'node:path';
|
3
|
+
|
4
|
+
import { fileURLToPath } from 'node:url';
|
5
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
6
|
+
|
7
|
+
// import porfParse from './parse.js';
|
8
|
+
// import porfCodegen from './codeGen.js';
|
9
|
+
|
10
|
+
const argv = process.argv.slice();
|
11
|
+
|
12
|
+
const compile = async (file, [ _funcs, _globals ]) => {
|
13
|
+
const source = fs.readFileSync(file, 'utf8');
|
14
|
+
const first = source.slice(0, source.indexOf('\n'));
|
15
|
+
|
16
|
+
let args = ['-bytestring'];
|
17
|
+
if (file.endsWith('.ts')) args.push('-parse-types', '-opt-types');
|
18
|
+
if (first.startsWith('// @porf')) {
|
19
|
+
args = args.concat(first.slice('// @porf '.length).split(' '));
|
20
|
+
}
|
21
|
+
process.argv = argv.concat(args);
|
22
|
+
|
23
|
+
// globalThis.optLog = process.argv.includes('-opt-log');
|
24
|
+
// globalThis.codeLog = process.argv.includes('-code-log');
|
25
|
+
// globalThis.allocLog = process.argv.includes('-alloc-log');
|
26
|
+
// globalThis.regexLog = process.argv.includes('-regex-log');
|
27
|
+
|
28
|
+
// const porfParse = (await import(`./parse.js?_=${Date.now()}`)).default;
|
29
|
+
// const porfCodegen = (await import(`./codeGen.js?_=${Date.now()}`)).default;
|
30
|
+
|
31
|
+
// let { funcs, globals, data } = porfCodegen(porfParse(source, ['module']));
|
32
|
+
|
33
|
+
const porfCompile = (await import(`./index.js?_=${Date.now()}`)).default;
|
34
|
+
|
35
|
+
let { funcs, globals, data, exceptions } = porfCompile(source, ['module']);
|
36
|
+
|
37
|
+
funcs = funcs.filter(x => x.export);
|
38
|
+
for (const x of funcs) {
|
39
|
+
if (x.data) x.data = x.data.map(x => data[x]);
|
40
|
+
if (x.exceptions) x.exceptions = x.exceptions.map(x => {
|
41
|
+
const obj = exceptions[x];
|
42
|
+
if (obj) obj.exceptId = x;
|
43
|
+
return obj;
|
44
|
+
}).filter(x => x);
|
45
|
+
}
|
46
|
+
|
47
|
+
_funcs.push(...funcs);
|
48
|
+
_globals.push(...Object.values(globals));
|
49
|
+
};
|
50
|
+
|
51
|
+
const precompile = async () => {
|
52
|
+
const dir = join(__dirname, 'builtins');
|
53
|
+
|
54
|
+
let funcs = [], globals = [];
|
55
|
+
for (const file of fs.readdirSync(dir)) {
|
56
|
+
if (file.endsWith('.d.ts')) continue;
|
57
|
+
await compile(join(dir, file), [ funcs, globals ]);
|
58
|
+
}
|
59
|
+
|
60
|
+
// todo: globals, exceptions, pages per func
|
61
|
+
|
62
|
+
return `// autogenerated by precompile.js
|
63
|
+
|
64
|
+
export const BuiltinFuncs = function() {
|
65
|
+
${funcs.map(x => ` this.${x.name} = {
|
66
|
+
wasm: ${JSON.stringify(x.wasm)},
|
67
|
+
params: ${JSON.stringify(x.params)},
|
68
|
+
typedParams: true,
|
69
|
+
returns: ${JSON.stringify(x.returns)},
|
70
|
+
typedReturns: true,
|
71
|
+
locals: ${JSON.stringify(Object.values(x.locals).slice(x.params.length * 2).map(x => x.type))},
|
72
|
+
${x.pages && x.pages.size > 0 ? ` pages: ${JSON.stringify(Object.fromEntries(x.pages.entries()))},` : ''}
|
73
|
+
${x.data && x.data.length > 0 ? ` data: ${JSON.stringify(x.data)},` : ''}
|
74
|
+
${x.exceptions && x.exceptions.length > 0 ? ` exceptions: ${JSON.stringify(x.exceptions)},` : ''}
|
75
|
+
};`.replaceAll('\n\n', '\n')).join('\n')}
|
76
|
+
}`;
|
77
|
+
};
|
78
|
+
|
79
|
+
console.log(await precompile());
|
@@ -0,0 +1,22 @@
|
|
1
|
+
const cache = {};
|
2
|
+
const obj = new Proxy({}, {
|
3
|
+
get(_, p) {
|
4
|
+
// intentionally misses with undefined values cached
|
5
|
+
if (cache[p]) return cache[p];
|
6
|
+
|
7
|
+
return cache[p] = (() => {
|
8
|
+
// fooBar -> foo-bar
|
9
|
+
const name = p[0] === '_' ? p : p.replace(/[A-Z]/g, c => `-${c.toLowerCase()}`);
|
10
|
+
if (process.argv.includes('-' + name)) return true;
|
11
|
+
|
12
|
+
const valArg = process.argv.find(x => x.startsWith(`-${name}=`));
|
13
|
+
if (valArg) return valArg.slice(name.length + 2);
|
14
|
+
|
15
|
+
return undefined;
|
16
|
+
})();
|
17
|
+
}
|
18
|
+
});
|
19
|
+
|
20
|
+
obj.uncache = () => cache = {};
|
21
|
+
|
22
|
+
export default obj;
|