porffor 0.0.0-8c0bdaa → 0.0.0-b78ef90
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 +44 -11
- package/c +0 -0
- package/c.exe +0 -0
- package/compiler/2c.js +255 -0
- package/compiler/builtins.js +0 -1
- package/compiler/codeGen.js +346 -92
- package/compiler/decompile.js +3 -3
- package/compiler/encoding.js +4 -2
- package/compiler/index.js +53 -2
- package/compiler/opt.js +35 -5
- package/compiler/parse.js +2 -1
- package/compiler/prototype.js +4 -5
- package/compiler/sections.js +28 -2
- package/compiler/wrap.js +12 -1
- package/cool.exe +0 -0
- package/g +0 -0
- package/g.exe +0 -0
- package/hi.c +37 -0
- package/out +0 -0
- package/package.json +1 -1
- package/publish.js +3 -1
- package/r.js +1 -0
- package/rhemyn/README.md +37 -0
- package/rhemyn/compile.js +214 -0
- package/rhemyn/parse.js +319 -0
- package/rhemyn/test/parse.js +59 -0
- package/runner/index.js +52 -33
- package/runner/repl.js +8 -13
- package/runner/transform.js +5 -26
- package/runner/version.js +10 -0
- package/t.js +31 -0
- package/tmp.c +37 -0
package/README.md
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
# porffor
|
2
|
-
a basic experimental wip *aot* optimizing js -> wasm engine/compiler/runtime in js. not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
|
2
|
+
a basic experimental wip *aot* optimizing js -> wasm/c engine/compiler/runtime in js. not serious/intended for (real) use. (this is a straight forward, honest readme)<br>
|
3
3
|
age: ~1 month
|
4
4
|
|
5
5
|
## design
|
6
6
|
porffor is a very unique js engine, due a very different approach. it is seriously limited, but what it can do, it does pretty well. key differences:
|
7
7
|
- 100% aot compiled *(not jit)*
|
8
|
-
- everything is a number
|
9
8
|
- no constant runtime/preluded code
|
10
9
|
- least Wasm imports possible (only stdio)
|
11
10
|
|
@@ -18,6 +17,12 @@ porffor is mostly built from scratch, the only thing that is not is the parser (
|
|
18
17
|
- no variables between scopes (except args and globals)
|
19
18
|
- literal callees only in calls (eg `print()` works, `a = print; a()` does not)
|
20
19
|
|
20
|
+
## rhemyn
|
21
|
+
rhemyn is porffor's own regex engine; it compiles literal regex to wasm bytecode aot (remind you of anything?). it is quite basic and wip. see [its readme](rhemyn/README.md) for more details.
|
22
|
+
|
23
|
+
## 2c
|
24
|
+
2c is porffor's own wasm -> c compiler, using generated wasm bytecode and internal info to generate specific and efficient/fast c code. no boilerplate/preluded code or required external files, just for cli binaries (not like wasm2c very much at all).
|
25
|
+
|
21
26
|
## supported
|
22
27
|
see [optimizations](#optimizations) for opts implemented/supported.
|
23
28
|
|
@@ -76,6 +81,8 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
76
81
|
- string member (char) access via `str[ind]` (eg `str[0]`)
|
77
82
|
- string concat (`+`) (eg `'a' + 'b'`)
|
78
83
|
- truthy/falsy (eg `!'' == true`)
|
84
|
+
- string comparison (eg `'a' == 'a'`, `'a' != 'b'`)
|
85
|
+
- nullish coalescing operator (`??`)
|
79
86
|
|
80
87
|
### built-ins
|
81
88
|
|
@@ -99,18 +106,27 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
99
106
|
- intrinsic functions (see below)
|
100
107
|
- inlining wasm via ``asm`...``\` "macro"
|
101
108
|
|
102
|
-
##
|
109
|
+
## todo
|
110
|
+
no particular order and no guarentees, just what could happen soon™
|
111
|
+
|
103
112
|
- arrays
|
104
113
|
- member setting (`arr[0] = 2`)
|
105
114
|
- more of `Array` prototype
|
106
115
|
- arrays/strings inside arrays
|
116
|
+
- destructuring
|
117
|
+
- for .. of
|
107
118
|
- strings
|
108
119
|
- member setting
|
109
|
-
|
120
|
+
- objects
|
121
|
+
- basic object expressions (eg `{}`, `{ a: 0 }`)
|
122
|
+
- wasm
|
123
|
+
- *basic* wasm engine (interpreter) in js
|
110
124
|
- more math operators (`**`, etc)
|
111
125
|
- `do { ... } while (...)`
|
126
|
+
- rewrite `console.log` to work with strings/arrays
|
112
127
|
- exceptions
|
113
|
-
-
|
128
|
+
- rewrite to use actual strings (optional?)
|
129
|
+
- `try { } finally { }`
|
114
130
|
- rethrowing inside catch
|
115
131
|
- optimizations
|
116
132
|
- rewrite local indexes per func for smallest local header and remove unused idxs
|
@@ -118,8 +134,10 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
118
134
|
- remove const ifs (`if (true)`, etc)
|
119
135
|
- use data segments for initing arrays
|
120
136
|
|
121
|
-
##
|
122
|
-
|
137
|
+
## porfformance
|
138
|
+
*for the things it supports*, porffor is blazingly faster compared to most interpreters, and engines running without JIT. for those with JIT, it is not that much slower like a traditional interpreter would be.
|
139
|
+
|
140
|
+

|
123
141
|
|
124
142
|
## optimizations
|
125
143
|
mostly for reducing size. do not really care about compiler perf/time as long as it is reasonable. we do not use/rely on external opt tools (`wasm-opt`, etc), instead doing optimization inside the compiler itself creating even smaller code sizes than `wasm-opt` itself can produce as we have more internal information. (this also enables fast + small runtime use as a potential cursed jit in frontend).
|
@@ -135,6 +153,7 @@ mostly for reducing size. do not really care about compiler perf/time as long as
|
|
135
153
|
- `i64.extend_i32_s`, `i32.wrap_i64` -> ``
|
136
154
|
- `f64.convert_i32_u`, `i32.trunc_sat_f64_s` -> ``
|
137
155
|
- `return`, `end` -> `end`
|
156
|
+
- change const, convert to const of converted valtype (eg `f64.const`, `i32.trunc_sat_f64_s -> `i32.const`)
|
138
157
|
- remove some redundant sets/gets
|
139
158
|
- remove unneeded single just used vars
|
140
159
|
- remove unneeded blocks (no `br`s inside)
|
@@ -144,6 +163,9 @@ mostly for reducing size. do not really care about compiler perf/time as long as
|
|
144
163
|
- type cache/index (no repeated types)
|
145
164
|
- no main func if empty (and other exports)
|
146
165
|
|
166
|
+
## test262
|
167
|
+
porffor can run test262 via some hacks/transforms which remove unsupported features whilst still doing the same asserts (eg simpler error messages using literals only). it currently passes >10% (see latest commit desc for latest and details). use `node test262` to test, it will also show a difference of overall results between the last commit and current results.
|
168
|
+
|
147
169
|
## codebase
|
148
170
|
- `compiler`: contains the compiler itself
|
149
171
|
- `builtins.js`: all built-ins of the engine (spec, custom. vars, funcs)
|
@@ -164,6 +186,10 @@ mostly for reducing size. do not really care about compiler perf/time as long as
|
|
164
186
|
- `info.js`: runs with extra info printed
|
165
187
|
- `repl.js`: basic repl (uses `node:repl`)
|
166
188
|
|
189
|
+
- `rhemyn`: contains [rhemyn](#rhemyn) - the regex engine used by porffor
|
190
|
+
- `compile.js`: compiles regex ast into wasm bytecode
|
191
|
+
- `parse.js`: own regex parser
|
192
|
+
|
167
193
|
- `test`: contains many test files for majority of supported features
|
168
194
|
- `test262`: test262 runner and utils
|
169
195
|
|
@@ -181,8 +207,13 @@ basically nothing will work :). see files in `test` for examples.
|
|
181
207
|
you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ...` instead of `node ...`)
|
182
208
|
|
183
209
|
### flags
|
184
|
-
- `-
|
185
|
-
- `-
|
210
|
+
- `-target=wasm|c|native` (default: `wasm`) to set target output (native compiles c output to binary, see args below)
|
211
|
+
- `-target=c|native` only:
|
212
|
+
- `-o=out.c|out.exe|out` to set file to output c or binary
|
213
|
+
- `-target=native` only:
|
214
|
+
- `-compiler=clang` to set compiler binary (path/name) to use to compile
|
215
|
+
- `-cO=O3` to set compiler opt argument
|
216
|
+
- `-valtype=i32|i64|f64` (default: `f64`) to set valtype
|
186
217
|
- `-O0` to disable opt
|
187
218
|
- `-O1` (default) to enable basic opt (simplify insts, treeshake wasm imports)
|
188
219
|
- `-O2` to enable advanced opt (inlining)
|
@@ -190,11 +221,13 @@ you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ..
|
|
190
221
|
- `-no-run` to not run wasm output, just compile
|
191
222
|
- `-opt-log` to log some opts
|
192
223
|
- `-code-log` to log some codegen (you probably want `-funcs`)
|
193
|
-
- `-
|
224
|
+
- `-regex-log` to log some regex
|
225
|
+
- `-funcs` to log funcs
|
194
226
|
- `-opt-funcs` to log funcs after opt
|
195
227
|
- `-sections` to log sections as hex
|
196
228
|
- `-opt-no-inline` to not inline any funcs
|
197
|
-
- `-tail-call` to enable tail calls (not widely implemented)
|
229
|
+
- `-tail-call` to enable tail calls (experimental + not widely implemented)
|
230
|
+
- `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
|
198
231
|
|
199
232
|
## vscode extension
|
200
233
|
there is a vscode extension in `porffor-for-vscode` which tweaks js syntax highlighting to be nicer with porffor features (eg highlighting wasm inside of inline asm).
|
package/c
ADDED
Binary file
|
package/c.exe
ADDED
Binary file
|
package/compiler/2c.js
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
import { read_ieee754_binary64, read_signedLEB128 } from './encoding.js';
|
2
|
+
import { Blocktype, Opcodes, Valtype } from './wasmSpec.js';
|
3
|
+
import { operatorOpcode } from './expression.js';
|
4
|
+
|
5
|
+
const CValtype = {
|
6
|
+
i8: 'char',
|
7
|
+
i16: 'unsigned short', // presume all i16 stuff is unsigned
|
8
|
+
i32: 'long',
|
9
|
+
i32_u: 'unsigned long',
|
10
|
+
i64: 'long long',
|
11
|
+
i64_u: 'unsigned long long',
|
12
|
+
|
13
|
+
f32: 'float',
|
14
|
+
f64: 'double',
|
15
|
+
|
16
|
+
undefined: 'void'
|
17
|
+
};
|
18
|
+
|
19
|
+
const inv = (obj, keyMap = x => x) => Object.keys(obj).reduce((acc, x) => { acc[keyMap(obj[x])] = x; return acc; }, {});
|
20
|
+
const invOpcodes = inv(Opcodes);
|
21
|
+
|
22
|
+
for (const x in CValtype) {
|
23
|
+
if (Valtype[x]) CValtype[Valtype[x]] = CValtype[x];
|
24
|
+
}
|
25
|
+
|
26
|
+
const todo = msg => {
|
27
|
+
class TodoError extends Error {
|
28
|
+
constructor(message) {
|
29
|
+
super(message);
|
30
|
+
this.name = 'TodoError';
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
throw new TodoError(`todo: ${msg}`);
|
35
|
+
};
|
36
|
+
|
37
|
+
export default ({ funcs, globals, tags, exceptions, pages }) => {
|
38
|
+
const invOperatorOpcode = inv(operatorOpcode[valtype]);
|
39
|
+
const invGlobals = inv(globals, x => x.idx);
|
40
|
+
|
41
|
+
const includes = new Map();
|
42
|
+
let out = '';
|
43
|
+
|
44
|
+
for (const x in globals) {
|
45
|
+
const g = globals[x];
|
46
|
+
|
47
|
+
out += `${CValtype[g.type]} ${x}`;
|
48
|
+
if (x.init) out += ` ${x.init}`;
|
49
|
+
out += ';\n';
|
50
|
+
}
|
51
|
+
|
52
|
+
for (const [ x, p ] of pages) {
|
53
|
+
out += `${CValtype[p.type]} ${x.replace(': ', '_').replace(/[^0-9a-zA-Z_]/g, '')}[100]`;
|
54
|
+
out += ';\n';
|
55
|
+
}
|
56
|
+
|
57
|
+
if (out) out += '\n';
|
58
|
+
|
59
|
+
for (const f of funcs) {
|
60
|
+
const invLocals = inv(f.locals, x => x.idx);
|
61
|
+
if (f.returns.length > 1) todo('funcs returning >1 value unsupported');
|
62
|
+
|
63
|
+
const sanitize = str => str.replace(/[^0-9a-zA-Z_]/g, _ => String.fromCharCode(97 + _.charCodeAt(0) % 32));
|
64
|
+
|
65
|
+
const returns = f.returns.length === 1;
|
66
|
+
|
67
|
+
const shouldInline = ['f64_%'].includes(f.name);
|
68
|
+
out += `${f.name === 'main' ? 'int' : CValtype[f.returns[0]]} ${shouldInline ? 'inline ' : ''}${sanitize(f.name)}(${f.params.map((x, i) => `${CValtype[x]} ${invLocals[i]}`).join(', ')}) {\n`;
|
69
|
+
|
70
|
+
let depth = 1;
|
71
|
+
const line = (str, semi = true) => out += `${' '.repeat(depth * 2)}${str}${semi ? ';' : ''}\n`;
|
72
|
+
|
73
|
+
const localKeys = Object.keys(f.locals).sort((a, b) => f.locals[a].idx - f.locals[b].idx).slice(f.params.length).sort((a, b) => f.locals[a].idx - f.locals[b].idx);
|
74
|
+
for (const x of localKeys) {
|
75
|
+
const l = f.locals[x];
|
76
|
+
line(`${CValtype[l.type]} ${x}`);
|
77
|
+
}
|
78
|
+
|
79
|
+
if (localKeys.length !== 0) out += '\n';
|
80
|
+
|
81
|
+
let vals = [];
|
82
|
+
const endNeedsCurly = [], ignoreEnd = [];
|
83
|
+
let beginLoop = false, lastCond = false, ifTernary = false;
|
84
|
+
for (let _ = 0; _ < f.wasm.length; _++) {
|
85
|
+
const i = f.wasm[_];
|
86
|
+
|
87
|
+
if (invOperatorOpcode[i[0]]) {
|
88
|
+
const b = vals.pop();
|
89
|
+
const a = vals.pop();
|
90
|
+
|
91
|
+
let op = invOperatorOpcode[i[0]];
|
92
|
+
if (op.length === 3) op = op.slice(0, 2);
|
93
|
+
|
94
|
+
if (['==', '!=', '>', '>=', '<', '<='].includes(op)) lastCond = true;
|
95
|
+
else lastCond = false;
|
96
|
+
|
97
|
+
vals.push(`${a} ${op} ${b}`);
|
98
|
+
continue;
|
99
|
+
}
|
100
|
+
|
101
|
+
// misc insts
|
102
|
+
if (i[0] === 0xfc) {
|
103
|
+
switch (i[1]) {
|
104
|
+
// i32_trunc_sat_f64_s
|
105
|
+
case 0x02:
|
106
|
+
vals.push(`(${CValtype.i32})${vals.pop()}`);
|
107
|
+
break;
|
108
|
+
|
109
|
+
// i32_trunc_sat_f64_u
|
110
|
+
case 0x03:
|
111
|
+
vals.push(`(${CValtype.i32})(${CValtype.i32_u})${vals.pop()}`);
|
112
|
+
break;
|
113
|
+
}
|
114
|
+
|
115
|
+
lastCond = false;
|
116
|
+
continue;
|
117
|
+
}
|
118
|
+
|
119
|
+
switch (i[0]) {
|
120
|
+
case Opcodes.i32_const:
|
121
|
+
vals.push(read_signedLEB128(i.slice(1)).toString());
|
122
|
+
break;
|
123
|
+
|
124
|
+
case Opcodes.f64_const:
|
125
|
+
vals.push(read_ieee754_binary64(i.slice(1)).toExponential());
|
126
|
+
break;
|
127
|
+
|
128
|
+
case Opcodes.local_get:
|
129
|
+
vals.push(`${invLocals[i[1]]}`);
|
130
|
+
break;
|
131
|
+
|
132
|
+
case Opcodes.local_set:
|
133
|
+
line(`${invLocals[i[1]]} = ${vals.pop()}`);
|
134
|
+
break;
|
135
|
+
|
136
|
+
case Opcodes.local_tee:
|
137
|
+
vals.push(`${invLocals[i[1]]} = ${vals.pop()}`);
|
138
|
+
break;
|
139
|
+
|
140
|
+
case Opcodes.f64_trunc:
|
141
|
+
// vals.push(`trunc(${vals.pop()})`);
|
142
|
+
vals.push(`(int)(${vals.pop()})`); // this is ~10x faster with clang. what the fuck.
|
143
|
+
break;
|
144
|
+
|
145
|
+
case Opcodes.return:
|
146
|
+
line(`return${returns ? ` ${vals.pop()}` : ''}`);
|
147
|
+
break;
|
148
|
+
|
149
|
+
case Opcodes.if:
|
150
|
+
let cond = vals.pop();
|
151
|
+
if (!lastCond) {
|
152
|
+
if (cond.startsWith('(long)')) cond = `${cond.slice(6)} == 1e+0`;
|
153
|
+
else cond += ' == 1';
|
154
|
+
}
|
155
|
+
|
156
|
+
ifTernary = i[1] !== Blocktype.void;
|
157
|
+
if (ifTernary) {
|
158
|
+
ifTernary = cond;
|
159
|
+
break;
|
160
|
+
}
|
161
|
+
|
162
|
+
if (beginLoop) {
|
163
|
+
beginLoop = false;
|
164
|
+
line(`while (${cond}) {`, false);
|
165
|
+
|
166
|
+
depth++;
|
167
|
+
endNeedsCurly.push(true);
|
168
|
+
ignoreEnd.push(false, true);
|
169
|
+
break;
|
170
|
+
}
|
171
|
+
|
172
|
+
line(`if (${cond}) {`, false);
|
173
|
+
|
174
|
+
depth++;
|
175
|
+
endNeedsCurly.push(true);
|
176
|
+
ignoreEnd.push(false);
|
177
|
+
break;
|
178
|
+
|
179
|
+
case Opcodes.else:
|
180
|
+
if (ifTernary) break;
|
181
|
+
|
182
|
+
depth--;
|
183
|
+
line(`} else {`, false);
|
184
|
+
depth++;
|
185
|
+
break;
|
186
|
+
|
187
|
+
case Opcodes.loop:
|
188
|
+
// not doing properly, fake a while loop
|
189
|
+
beginLoop = true;
|
190
|
+
break;
|
191
|
+
|
192
|
+
case Opcodes.end:
|
193
|
+
if (ignoreEnd.pop()) break;
|
194
|
+
|
195
|
+
if (ifTernary) {
|
196
|
+
const b = vals.pop();
|
197
|
+
const a = vals.pop();
|
198
|
+
vals.push(`${ifTernary} ? ${a} : ${b}`);
|
199
|
+
break;
|
200
|
+
}
|
201
|
+
|
202
|
+
depth--;
|
203
|
+
if (endNeedsCurly.pop() === true) line('}', false);
|
204
|
+
break;
|
205
|
+
|
206
|
+
case Opcodes.call:
|
207
|
+
let func = funcs.find(x => x.index === i[1]);
|
208
|
+
if (!func) {
|
209
|
+
const importFunc = importFuncs[i[1]];
|
210
|
+
switch (importFunc.name) {
|
211
|
+
case 'print':
|
212
|
+
line(`printf("%f\\n", ${vals.pop()})`);
|
213
|
+
includes.set('stdio.h', true);
|
214
|
+
break;
|
215
|
+
}
|
216
|
+
break;
|
217
|
+
}
|
218
|
+
|
219
|
+
let args = [];
|
220
|
+
for (let j = 0; j < func.params.length; j++) args.unshift(vals.pop());
|
221
|
+
|
222
|
+
if (func.returns.length === 1) vals.push(`${sanitize(func.name)}(${args.join(', ')})`)
|
223
|
+
else line(`${sanitize(func.name)}(${args.join(', ')})`);
|
224
|
+
|
225
|
+
break;
|
226
|
+
|
227
|
+
case Opcodes.drop:
|
228
|
+
line(vals.pop());
|
229
|
+
break;
|
230
|
+
|
231
|
+
case Opcodes.br:
|
232
|
+
// ignore
|
233
|
+
// reset "stack"
|
234
|
+
vals = [];
|
235
|
+
break;
|
236
|
+
|
237
|
+
default:
|
238
|
+
log('2c', `unimplemented op: ${invOpcodes[i[0]]}`);
|
239
|
+
// todo(`unimplemented op: ${invOpcodes[i[0]]}`);
|
240
|
+
}
|
241
|
+
|
242
|
+
lastCond = false;
|
243
|
+
}
|
244
|
+
|
245
|
+
if (vals.length === 1 && returns) {
|
246
|
+
line(`return ${vals.pop()}`);
|
247
|
+
}
|
248
|
+
|
249
|
+
out += '}\n\n';
|
250
|
+
}
|
251
|
+
|
252
|
+
out = [...includes.keys()].map(x => `#include <${x}>`).join('\n') + '\n\n' + out;
|
253
|
+
|
254
|
+
return out;
|
255
|
+
};
|