porffor 0.0.0-c743344 → 0.0.0-ee33579
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 +41 -10
- 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 +67 -11
- package/compiler/decompile.js +1 -1
- package/compiler/index.js +44 -2
- package/compiler/opt.js +1 -1
- package/compiler/parse.js +2 -1
- package/compiler/prototype.js +2 -1
- package/compiler/sections.js +25 -0
- package/compiler/wrap.js +5 -3
- 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/r.js +1 -0
- package/rhemyn/README.md +37 -0
- package/rhemyn/compile.js +214 -0
- package/rhemyn/parse.js +321 -0
- package/rhemyn/test/parse.js +59 -0
- package/runner/index.js +52 -40
- package/runner/transform.js +2 -1
- 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
|
|
@@ -101,17 +106,27 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
101
106
|
- intrinsic functions (see below)
|
102
107
|
- inlining wasm via ``asm`...``\` "macro"
|
103
108
|
|
104
|
-
##
|
109
|
+
## todo
|
110
|
+
no particular order and no guarentees, just what could happen soon™
|
111
|
+
|
105
112
|
- arrays
|
106
113
|
- member setting (`arr[0] = 2`)
|
107
114
|
- more of `Array` prototype
|
108
115
|
- arrays/strings inside arrays
|
116
|
+
- destructuring
|
117
|
+
- for .. of
|
109
118
|
- strings
|
110
119
|
- member setting
|
120
|
+
- objects
|
121
|
+
- basic object expressions (eg `{}`, `{ a: 0 }`)
|
122
|
+
- wasm
|
123
|
+
- *basic* wasm engine (interpreter) in js
|
111
124
|
- more math operators (`**`, etc)
|
112
125
|
- `do { ... } while (...)`
|
126
|
+
- rewrite `console.log` to work with strings/arrays
|
113
127
|
- exceptions
|
114
|
-
-
|
128
|
+
- rewrite to use actual strings (optional?)
|
129
|
+
- `try { } finally { }`
|
115
130
|
- rethrowing inside catch
|
116
131
|
- optimizations
|
117
132
|
- rewrite local indexes per func for smallest local header and remove unused idxs
|
@@ -119,8 +134,10 @@ these include some early (stage 1/0) and/or dead (last commit years ago) proposa
|
|
119
134
|
- remove const ifs (`if (true)`, etc)
|
120
135
|
- use data segments for initing arrays
|
121
136
|
|
122
|
-
##
|
123
|
-
|
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
|
+

|
124
141
|
|
125
142
|
## optimizations
|
126
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).
|
@@ -146,6 +163,9 @@ mostly for reducing size. do not really care about compiler perf/time as long as
|
|
146
163
|
- type cache/index (no repeated types)
|
147
164
|
- no main func if empty (and other exports)
|
148
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
|
+
|
149
169
|
## codebase
|
150
170
|
- `compiler`: contains the compiler itself
|
151
171
|
- `builtins.js`: all built-ins of the engine (spec, custom. vars, funcs)
|
@@ -166,6 +186,10 @@ mostly for reducing size. do not really care about compiler perf/time as long as
|
|
166
186
|
- `info.js`: runs with extra info printed
|
167
187
|
- `repl.js`: basic repl (uses `node:repl`)
|
168
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
|
+
|
169
193
|
- `test`: contains many test files for majority of supported features
|
170
194
|
- `test262`: test262 runner and utils
|
171
195
|
|
@@ -183,8 +207,13 @@ basically nothing will work :). see files in `test` for examples.
|
|
183
207
|
you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ...` instead of `node ...`)
|
184
208
|
|
185
209
|
### flags
|
186
|
-
- `-
|
187
|
-
- `-
|
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
|
188
217
|
- `-O0` to disable opt
|
189
218
|
- `-O1` (default) to enable basic opt (simplify insts, treeshake wasm imports)
|
190
219
|
- `-O2` to enable advanced opt (inlining)
|
@@ -192,11 +221,13 @@ you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ..
|
|
192
221
|
- `-no-run` to not run wasm output, just compile
|
193
222
|
- `-opt-log` to log some opts
|
194
223
|
- `-code-log` to log some codegen (you probably want `-funcs`)
|
195
|
-
- `-
|
224
|
+
- `-regex-log` to log some regex
|
225
|
+
- `-funcs` to log funcs
|
196
226
|
- `-opt-funcs` to log funcs after opt
|
197
227
|
- `-sections` to log sections as hex
|
198
228
|
- `-opt-no-inline` to not inline any funcs
|
199
|
-
- `-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?)
|
200
231
|
|
201
232
|
## vscode extension
|
202
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
|
+
};
|
package/compiler/builtins.js
CHANGED
package/compiler/codeGen.js
CHANGED
@@ -5,6 +5,7 @@ import { BuiltinFuncs, BuiltinVars, importedFuncs, NULL, UNDEFINED } from "./bui
|
|
5
5
|
import { PrototypeFuncs } from "./prototype.js";
|
6
6
|
import { number, i32x4 } from "./embedding.js";
|
7
7
|
import parse from "./parse.js";
|
8
|
+
import * as Rhemyn from "../rhemyn/compile.js";
|
8
9
|
|
9
10
|
let globals = {};
|
10
11
|
let globalInd = 0;
|
@@ -745,7 +746,7 @@ const generateBinaryExp = (scope, decl, _global, _name) => {
|
|
745
746
|
return out;
|
746
747
|
};
|
747
748
|
|
748
|
-
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType,
|
749
|
+
const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes = [], globalInits, returns, returnType, localNames = [], globalNames = [] }) => {
|
749
750
|
const existing = funcs.find(x => x.name === name);
|
750
751
|
if (existing) return existing;
|
751
752
|
|
@@ -781,7 +782,6 @@ const asmFunc = (name, { wasm, params, locals: localTypes, globals: globalTypes
|
|
781
782
|
returns,
|
782
783
|
returnType: TYPES[returnType ?? 'number'],
|
783
784
|
wasm,
|
784
|
-
memory,
|
785
785
|
internal: true,
|
786
786
|
index: currentFuncIndex++
|
787
787
|
};
|
@@ -814,7 +814,8 @@ const TYPES = {
|
|
814
814
|
bigint: 0xffffffffffff7,
|
815
815
|
|
816
816
|
// these are not "typeof" types but tracked internally
|
817
|
-
_array:
|
817
|
+
_array: 0xfffffffffff0f,
|
818
|
+
_regexp: 0xfffffffffff1f
|
818
819
|
};
|
819
820
|
|
820
821
|
const TYPE_NAMES = {
|
@@ -849,6 +850,8 @@ const getType = (scope, _name) => {
|
|
849
850
|
const getNodeType = (scope, node) => {
|
850
851
|
if (node.type === 'Literal') {
|
851
852
|
if (['number', 'boolean', 'string', 'undefined', 'object', 'function', 'symbol', 'bigint'].includes(node.value)) return TYPES.number;
|
853
|
+
if (node.regex) return TYPES._regexp;
|
854
|
+
|
852
855
|
return TYPES[typeof node.value];
|
853
856
|
}
|
854
857
|
|
@@ -882,6 +885,11 @@ const getNodeType = (scope, node) => {
|
|
882
885
|
|
883
886
|
// literal.func()
|
884
887
|
if (!name && node.callee.type === 'MemberExpression') {
|
888
|
+
if (node.callee.object.regex) {
|
889
|
+
const funcName = node.callee.property.name;
|
890
|
+
return Rhemyn[funcName] ? TYPES.boolean : TYPES.undefined;
|
891
|
+
}
|
892
|
+
|
885
893
|
const baseType = getNodeType(scope, node.callee.object);
|
886
894
|
|
887
895
|
const func = node.callee.property.name;
|
@@ -925,6 +933,11 @@ const getNodeType = (scope, node) => {
|
|
925
933
|
const generateLiteral = (scope, decl, global, name) => {
|
926
934
|
if (decl.value === null) return number(NULL);
|
927
935
|
|
936
|
+
if (decl.regex) {
|
937
|
+
scope.regex[name] = decl.regex;
|
938
|
+
return number(1);
|
939
|
+
}
|
940
|
+
|
928
941
|
switch (typeof decl.value) {
|
929
942
|
case 'number':
|
930
943
|
return number(decl.value);
|
@@ -1084,6 +1097,25 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1084
1097
|
|
1085
1098
|
// literal.func()
|
1086
1099
|
if (!name && decl.callee.type === 'MemberExpression') {
|
1100
|
+
// megahack for /regex/.func()
|
1101
|
+
if (decl.callee.object.regex) {
|
1102
|
+
const funcName = decl.callee.property.name;
|
1103
|
+
const func = Rhemyn[funcName](decl.callee.object.regex.pattern, currentFuncIndex++);
|
1104
|
+
|
1105
|
+
funcIndex[func.name] = func.index;
|
1106
|
+
funcs.push(func);
|
1107
|
+
|
1108
|
+
return [
|
1109
|
+
// make string arg
|
1110
|
+
...generate(scope, decl.arguments[0]),
|
1111
|
+
|
1112
|
+
// call regex func
|
1113
|
+
Opcodes.i32_to_u,
|
1114
|
+
[ Opcodes.call, func.index ],
|
1115
|
+
Opcodes.i32_from
|
1116
|
+
];
|
1117
|
+
}
|
1118
|
+
|
1087
1119
|
baseType = getNodeType(scope, decl.callee.object);
|
1088
1120
|
|
1089
1121
|
const func = decl.callee.property.name;
|
@@ -1096,6 +1128,31 @@ const generateCall = (scope, decl, _global, _name) => {
|
|
1096
1128
|
baseName = [...arrays.keys()].pop();
|
1097
1129
|
}
|
1098
1130
|
|
1131
|
+
if (protoName && baseType === TYPES.string && Rhemyn[protoName]) {
|
1132
|
+
const func = Rhemyn[protoName](decl.arguments[0].regex.pattern, currentFuncIndex++);
|
1133
|
+
|
1134
|
+
funcIndex[func.name] = func.index;
|
1135
|
+
funcs.push(func);
|
1136
|
+
|
1137
|
+
const pointer = arrays.get(baseName);
|
1138
|
+
const [ local, isGlobal ] = lookupName(scope, baseName);
|
1139
|
+
|
1140
|
+
return [
|
1141
|
+
...out,
|
1142
|
+
|
1143
|
+
...(pointer == null ? [
|
1144
|
+
[ isGlobal ? Opcodes.global_get : Opcodes.local_get, local.idx ],
|
1145
|
+
Opcodes.i32_to_u,
|
1146
|
+
] : [
|
1147
|
+
...number(pointer, Valtype.i32)
|
1148
|
+
]),
|
1149
|
+
|
1150
|
+
// call regex func
|
1151
|
+
[ Opcodes.call, func.index ],
|
1152
|
+
Opcodes.i32_from
|
1153
|
+
];
|
1154
|
+
}
|
1155
|
+
|
1099
1156
|
if (protoFunc) {
|
1100
1157
|
let pointer = arrays.get(baseName);
|
1101
1158
|
|
@@ -1705,19 +1762,19 @@ const generateAssignPat = (scope, decl) => {
|
|
1705
1762
|
};
|
1706
1763
|
|
1707
1764
|
let pages = new Map();
|
1708
|
-
const allocPage = reason => {
|
1709
|
-
if (pages.has(reason)) return pages.get(reason);
|
1765
|
+
const allocPage = (reason, type) => {
|
1766
|
+
if (pages.has(reason)) return pages.get(reason).ind;
|
1710
1767
|
|
1711
|
-
|
1712
|
-
pages.set(reason, ind);
|
1768
|
+
const ind = pages.size;
|
1769
|
+
pages.set(reason, { ind, type });
|
1713
1770
|
|
1714
|
-
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason}`);
|
1771
|
+
if (allocLog) log('alloc', `allocated new page of memory (${ind}) | ${reason} (type: ${type})`);
|
1715
1772
|
|
1716
1773
|
return ind;
|
1717
1774
|
};
|
1718
1775
|
|
1719
1776
|
const freePage = reason => {
|
1720
|
-
|
1777
|
+
const { ind } = pages.get(reason);
|
1721
1778
|
pages.delete(reason);
|
1722
1779
|
|
1723
1780
|
if (allocLog) log('alloc', `freed page of memory (${ind}) | ${reason}`);
|
@@ -1749,7 +1806,7 @@ const makeArray = (scope, decl, global = false, name = '$undeclared', initEmpty
|
|
1749
1806
|
if (!arrays.has(name) || name === '$undeclared') {
|
1750
1807
|
// todo: can we just have 1 undeclared array? probably not? but this is not really memory efficient
|
1751
1808
|
const uniqueName = name === '$undeclared' ? name + Math.random().toString().slice(2) : name;
|
1752
|
-
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}
|
1809
|
+
arrays.set(name, allocPage(`${itemType === 'i16' ? 'string' : 'array'}: ${uniqueName}`, itemType) * pageSize);
|
1753
1810
|
}
|
1754
1811
|
|
1755
1812
|
const pointer = arrays.get(name);
|
@@ -1929,7 +1986,6 @@ const generateFunc = (scope, decl) => {
|
|
1929
1986
|
localInd: 0,
|
1930
1987
|
returns: [ valtypeBinary ],
|
1931
1988
|
returnType: null,
|
1932
|
-
memory: false,
|
1933
1989
|
throws: false,
|
1934
1990
|
name
|
1935
1991
|
};
|
package/compiler/decompile.js
CHANGED
@@ -57,7 +57,7 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
|
|
57
57
|
out += ` ;; label @${depth}`;
|
58
58
|
}
|
59
59
|
|
60
|
-
if (inst[0] === Opcodes.br) {
|
60
|
+
if (inst[0] === Opcodes.br || inst[0] === Opcodes.br_if) {
|
61
61
|
out += ` ;; goto @${depth - inst[1]}`;
|
62
62
|
}
|
63
63
|
|
package/compiler/index.js
CHANGED
@@ -4,6 +4,8 @@ import opt from './opt.js';
|
|
4
4
|
import produceSections from './sections.js';
|
5
5
|
import decompile from './decompile.js';
|
6
6
|
import { BuiltinPreludes } from './builtins.js';
|
7
|
+
import toc from './2c.js';
|
8
|
+
|
7
9
|
|
8
10
|
globalThis.decompile = decompile;
|
9
11
|
|
@@ -15,7 +17,8 @@ const areaColors = {
|
|
15
17
|
codegen: [ 20, 80, 250 ],
|
16
18
|
opt: [ 250, 20, 80 ],
|
17
19
|
sections: [ 20, 250, 80 ],
|
18
|
-
alloc: [ 250, 250, 20 ]
|
20
|
+
alloc: [ 250, 250, 20 ],
|
21
|
+
'2c': [ 20, 250, 250 ]
|
19
22
|
};
|
20
23
|
|
21
24
|
globalThis.log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
|
@@ -36,10 +39,16 @@ const logFuncs = (funcs, globals, exceptions) => {
|
|
36
39
|
console.log();
|
37
40
|
};
|
38
41
|
|
42
|
+
const getArg = name => process.argv.find(x => x.startsWith(`-${name}=`))?.slice(name.length + 2);
|
43
|
+
|
44
|
+
const writeFileSync = (typeof process !== 'undefined' ? (await import('node:fs')).writeFileSync : undefined);
|
45
|
+
const execSync = (typeof process !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
|
46
|
+
|
39
47
|
export default (code, flags) => {
|
40
48
|
globalThis.optLog = process.argv.includes('-opt-log');
|
41
49
|
globalThis.codeLog = process.argv.includes('-code-log');
|
42
50
|
globalThis.allocLog = process.argv.includes('-alloc-log');
|
51
|
+
globalThis.regexLog = process.argv.includes('-regex-log');
|
43
52
|
|
44
53
|
for (const x in BuiltinPreludes) {
|
45
54
|
if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
|
@@ -72,5 +81,38 @@ export default (code, flags) => {
|
|
72
81
|
// console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
|
73
82
|
}
|
74
83
|
|
75
|
-
|
84
|
+
const out = { wasm: sections, funcs, globals, tags, exceptions, pages };
|
85
|
+
|
86
|
+
const target = getArg('target') ?? getArg('t') ?? 'wasm';
|
87
|
+
const outFile = getArg('o');
|
88
|
+
|
89
|
+
if (target === 'c') {
|
90
|
+
const c = toc(out);
|
91
|
+
|
92
|
+
if (outFile) {
|
93
|
+
writeFileSync(outFile, c);
|
94
|
+
} else {
|
95
|
+
console.log(c);
|
96
|
+
}
|
97
|
+
|
98
|
+
process.exit();
|
99
|
+
}
|
100
|
+
|
101
|
+
if (target === 'native') {
|
102
|
+
const compiler = getArg('compiler') ?? 'clang';
|
103
|
+
const cO = getArg('cO') ?? 'O3';
|
104
|
+
|
105
|
+
const tmpfile = 'tmp.c';
|
106
|
+
const args = [ compiler, tmpfile, '-o', outFile ?? (process.platform === 'win32' ? 'out.exe' : 'out'), '-' + cO ];
|
107
|
+
|
108
|
+
const c = toc(out);
|
109
|
+
writeFileSync(tmpfile, c);
|
110
|
+
|
111
|
+
// obvious command escape is obvious
|
112
|
+
execSync(args.join(' '), { stdio: 'inherit' });
|
113
|
+
|
114
|
+
process.exit();
|
115
|
+
}
|
116
|
+
|
117
|
+
return out;
|
76
118
|
};
|
package/compiler/opt.js
CHANGED
package/compiler/parse.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
import { parse } from 'acorn';
|
2
|
+
// const { parse } = (await import(globalThis.document ? 'https://esm.sh/acorn' : 'acorn'));
|
2
3
|
|
3
4
|
export default (input, flags) => {
|
4
5
|
return parse(input, {
|
package/compiler/prototype.js
CHANGED
@@ -15,7 +15,8 @@ const TYPES = {
|
|
15
15
|
bigint: 0xffffffffffff7,
|
16
16
|
|
17
17
|
// these are not "typeof" types but tracked internally
|
18
|
-
_array:
|
18
|
+
_array: 0xfffffffffff0f,
|
19
|
+
_regexp: 0xfffffffffff1f
|
19
20
|
};
|
20
21
|
|
21
22
|
// todo: turn these into built-ins once arrays and these become less hacky
|