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

|
138
|
+
|
122
139
|
## test262
|
123
140
|
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.
|
124
141
|
|
@@ -192,11 +209,12 @@ you can also use deno (`deno run -A ...` instead of `node ...`), or bun (`bun ..
|
|
192
209
|
- `-no-run` to not run wasm output, just compile
|
193
210
|
- `-opt-log` to log some opts
|
194
211
|
- `-code-log` to log some codegen (you probably want `-funcs`)
|
195
|
-
- `-funcs` to log funcs
|
212
|
+
- `-funcs` to log funcs
|
196
213
|
- `-opt-funcs` to log funcs after opt
|
197
214
|
- `-sections` to log sections as hex
|
198
215
|
- `-opt-no-inline` to not inline any funcs
|
199
|
-
- `-tail-call` to enable tail calls (not widely implemented)
|
216
|
+
- `-tail-call` to enable tail calls (experimental + not widely implemented)
|
217
|
+
- `-compile-hints` to enable V8 compilation hints (experimental + doesn't seem to do much?)
|
200
218
|
|
201
219
|
## vscode extension
|
202
220
|
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).
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import { Blocktype, Opcodes, Valtype, PageSize, ValtypeSize } from '../wasmSpec.js';
|
2
|
+
import { signedLEB128, unsignedLEB128 } from '../encoding.js';
|
3
|
+
import parse from './parse.js';
|
4
|
+
|
5
|
+
// local indexes
|
6
|
+
const BasePointer = 0; // base string pointer
|
7
|
+
const IterPointer = 1; // this iteration base pointer
|
8
|
+
const Counter = 2; // what char we are running on
|
9
|
+
const Pointer = 3; // next char BYTE pointer
|
10
|
+
const Length = 4;
|
11
|
+
const Tmp = 5;
|
12
|
+
|
13
|
+
const generate = (node, get = true) => {
|
14
|
+
let out = [];
|
15
|
+
switch (node.type) {
|
16
|
+
case 'Expression':
|
17
|
+
out = [
|
18
|
+
// set length local
|
19
|
+
[ Opcodes.local_get, BasePointer ],
|
20
|
+
[ Opcodes.i32_load, Math.log2(ValtypeSize.i32) - 1, ...unsignedLEB128(0) ],
|
21
|
+
[ Opcodes.local_set, Length ],
|
22
|
+
|
23
|
+
// set iter pointer local as base + sizeof i32 initially
|
24
|
+
[ Opcodes.local_get, BasePointer ],
|
25
|
+
...number(ValtypeSize.i32, Valtype.i32),
|
26
|
+
[ Opcodes.i32_add ],
|
27
|
+
[ Opcodes.local_set, IterPointer ],
|
28
|
+
|
29
|
+
[ Opcodes.loop, Blocktype.void ],
|
30
|
+
|
31
|
+
// reset pointer as iter pointer
|
32
|
+
[ Opcodes.local_get, IterPointer ],
|
33
|
+
[ Opcodes.local_set, Pointer ],
|
34
|
+
|
35
|
+
[ Opcodes.block, Blocktype.void ],
|
36
|
+
// generate checks
|
37
|
+
...generate(x),
|
38
|
+
|
39
|
+
// reached end without branching out, successful match
|
40
|
+
...number(1, Valtype.i32),
|
41
|
+
[ Opcodes.return ],
|
42
|
+
|
43
|
+
[ Opcodes.end ],
|
44
|
+
|
45
|
+
// increment iter pointer by sizeof i16
|
46
|
+
[ Opcodes.local_get, IterPointer ],
|
47
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
48
|
+
[ Opcodes.i32_add ],
|
49
|
+
[ Opcodes.local_set, IterPointer ],
|
50
|
+
|
51
|
+
// increment counter by 1, check if eq length, if not loop
|
52
|
+
[ Opcodes.local_get, Counter ],
|
53
|
+
...number(1, Valtype.i32),
|
54
|
+
[ Opcodes.i32_add ],
|
55
|
+
[ Opcodes.local_tee, Counter ],
|
56
|
+
|
57
|
+
[ Opcodes.local_get, Length ],
|
58
|
+
[ Opcodes.i32_ne ],
|
59
|
+
[ Opcodes.br_if, 1 ],
|
60
|
+
|
61
|
+
[ Opcodes.end ],
|
62
|
+
|
63
|
+
// no match, return 0
|
64
|
+
...number(0, Valtype.i32)
|
65
|
+
];
|
66
|
+
|
67
|
+
break;
|
68
|
+
|
69
|
+
case 'Character':
|
70
|
+
out = generateChar(node, get);
|
71
|
+
break;
|
72
|
+
|
73
|
+
case 'Set':
|
74
|
+
out = generateSet(node, get);
|
75
|
+
break;
|
76
|
+
|
77
|
+
case 'Group':
|
78
|
+
out = generateGroup(node, get);
|
79
|
+
break;
|
80
|
+
|
81
|
+
case 'Range':
|
82
|
+
out = generateRange(node, get);
|
83
|
+
break;
|
84
|
+
}
|
85
|
+
|
86
|
+
return out;
|
87
|
+
};
|
88
|
+
|
89
|
+
const getNextChar = () => [
|
90
|
+
// get char from pointer
|
91
|
+
[ Opcodes.local_get, Pointer ],
|
92
|
+
[ Opcodes.i32_load16_u, Math.log2(ValtypeSize.i16) - 1, ...unsignedLEB128(0) ],
|
93
|
+
|
94
|
+
// pointer += sizeof i16
|
95
|
+
[ Opcodes.local_get, Pointer ],
|
96
|
+
...number(ValtypeSize.i16, Valtype.i32),
|
97
|
+
[ Opcodes.i32_add ],
|
98
|
+
[ Opcodes.local_set, Pointer ]
|
99
|
+
];
|
100
|
+
|
101
|
+
const checkFailure = () => [
|
102
|
+
// surely we do not need to do this for every single mismatch, right?
|
103
|
+
/* [ Opcodes.if, Blocktype.void ],
|
104
|
+
...number(0, Valtype.i32),
|
105
|
+
[ Opcodes.return ],
|
106
|
+
[ Opcodes.end ], */
|
107
|
+
|
108
|
+
[ Opcodes.br, 1 ]
|
109
|
+
];
|
110
|
+
|
111
|
+
const generateChar = (node, get) => {
|
112
|
+
return [
|
113
|
+
...(get ? getNextChar() : []),
|
114
|
+
...number(String.fromCharCode(node.char), Valtype.i32),
|
115
|
+
[ Opcodes.i32_ne ],
|
116
|
+
...checkFailure()
|
117
|
+
];
|
118
|
+
};
|
119
|
+
|
120
|
+
const generateSet = (node, get) => {
|
121
|
+
const out = [
|
122
|
+
...(get ? getNextChar() : []),
|
123
|
+
[ Opcodes.local_set, Tmp ],
|
124
|
+
];
|
125
|
+
|
126
|
+
for (const x of node.body) {
|
127
|
+
out = [
|
128
|
+
...out,
|
129
|
+
[ Opcodes.local_get, Tmp ],
|
130
|
+
...generate(x, false)
|
131
|
+
];
|
132
|
+
}
|
133
|
+
|
134
|
+
out = out.concat(new Array(node.body.length - 1).fill([ Opcodes.i32_or ]));
|
135
|
+
|
136
|
+
return [
|
137
|
+
...out,
|
138
|
+
...checkFailure()
|
139
|
+
];
|
140
|
+
};
|
141
|
+
|
142
|
+
const generateGroup = (node, get) => {
|
143
|
+
|
144
|
+
};
|
145
|
+
|
146
|
+
const generateRange = (node, get) => {
|
147
|
+
|
148
|
+
};
|
149
|
+
|
150
|
+
|
151
|
+
export const match = regex => {
|
152
|
+
|
153
|
+
};
|
154
|
+
|
155
|
+
export const test = regex => {
|
156
|
+
const code = generate(parse(regex));
|
157
|
+
};
|
158
|
+
|
159
|
+
const wasmify = code => {
|
160
|
+
const funcs = [{
|
161
|
+
name: 'exp',
|
162
|
+
export: true,
|
163
|
+
params: [ Valtype.i32 ],
|
164
|
+
returns: [ Valtype.i32 ],
|
165
|
+
locals: {
|
166
|
+
iterPointer: { idx: 1, type: Valtype.i32 },
|
167
|
+
counter: { idx: 2, type: Valtype.i32 },
|
168
|
+
pointer: { idx: 3, type: Valtype.i32 },
|
169
|
+
length: { idx: 4, type: Valtype.i32 },
|
170
|
+
tmp: { idx: 5, type: Valtype.i32 },
|
171
|
+
}
|
172
|
+
}]
|
173
|
+
};
|
@@ -0,0 +1,125 @@
|
|
1
|
+
const State = {
|
2
|
+
none: 0,
|
3
|
+
insideSet: 1
|
4
|
+
};
|
5
|
+
|
6
|
+
const Quantifiers = {
|
7
|
+
'*': [ 0 ], // 0 -
|
8
|
+
'+': [ 1 ], // 1 -
|
9
|
+
'?': [ 0, 1 ], // 0 - 1
|
10
|
+
};
|
11
|
+
const QuantifierKeys = Object.keys(Quantifiers);
|
12
|
+
|
13
|
+
export default str => {
|
14
|
+
const out = {
|
15
|
+
type: 'Expression',
|
16
|
+
body: []
|
17
|
+
};
|
18
|
+
let node = out, parents = [];
|
19
|
+
|
20
|
+
let state = State.none, escape = false;
|
21
|
+
for (let i = 0; i < str.length; i++) {
|
22
|
+
const c = str[i];
|
23
|
+
|
24
|
+
const addChar = () => {
|
25
|
+
node.body.push({
|
26
|
+
type: 'Character',
|
27
|
+
char: c
|
28
|
+
});
|
29
|
+
};
|
30
|
+
|
31
|
+
const seek = () => {
|
32
|
+
const cNext = str[++i];
|
33
|
+
|
34
|
+
if (cNext === '\\') return str[++i];
|
35
|
+
return cNext;
|
36
|
+
};
|
37
|
+
|
38
|
+
if (escape) {
|
39
|
+
addChar();
|
40
|
+
|
41
|
+
escape = false;
|
42
|
+
continue;
|
43
|
+
}
|
44
|
+
|
45
|
+
if (c === '\\') {
|
46
|
+
escape = true;
|
47
|
+
continue;
|
48
|
+
}
|
49
|
+
|
50
|
+
switch (state) {
|
51
|
+
case State.none:
|
52
|
+
if (c === '[') {
|
53
|
+
parents.push(node);
|
54
|
+
node = {
|
55
|
+
type: 'Set',
|
56
|
+
body: []
|
57
|
+
};
|
58
|
+
|
59
|
+
parents.at(-1).body.push(node);
|
60
|
+
|
61
|
+
state = State.insideSet;
|
62
|
+
continue;
|
63
|
+
}
|
64
|
+
|
65
|
+
if (c === '(') {
|
66
|
+
parents.push(node);
|
67
|
+
node = {
|
68
|
+
type: 'Group',
|
69
|
+
body: []
|
70
|
+
};
|
71
|
+
|
72
|
+
parents.at(-1).body.push(node);
|
73
|
+
continue;
|
74
|
+
}
|
75
|
+
|
76
|
+
if (c === ')') {
|
77
|
+
if (node.type !== 'Group') throw new SyntaxError('Unmatched closing parenthesis');
|
78
|
+
|
79
|
+
node = parents.pop();
|
80
|
+
continue;
|
81
|
+
}
|
82
|
+
|
83
|
+
if (QuantifierKeys.includes(c)) {
|
84
|
+
const amount = Quantifiers[c];
|
85
|
+
node.body.at(-1).quantifier = amount;
|
86
|
+
continue;
|
87
|
+
}
|
88
|
+
|
89
|
+
addChar();
|
90
|
+
break;
|
91
|
+
|
92
|
+
case State.insideSet:
|
93
|
+
if (c === ']') {
|
94
|
+
state = State.none;
|
95
|
+
node = parents.pop();
|
96
|
+
|
97
|
+
continue;
|
98
|
+
}
|
99
|
+
|
100
|
+
// range
|
101
|
+
if (c === '-') {
|
102
|
+
const from = node.body.pop().char;
|
103
|
+
const to = seek();
|
104
|
+
|
105
|
+
node.body.push({
|
106
|
+
type: 'Range',
|
107
|
+
from,
|
108
|
+
to
|
109
|
+
});
|
110
|
+
continue;
|
111
|
+
}
|
112
|
+
|
113
|
+
addChar();
|
114
|
+
break;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
// still in a group by the end
|
119
|
+
if (node.type !== 'Expression') throw new SyntaxError('Unmatched opening parenthesis');
|
120
|
+
|
121
|
+
// still in a set by the end
|
122
|
+
if (state === State.insideSet) throw new SyntaxError('Unmatched opening square bracket');
|
123
|
+
|
124
|
+
return out;
|
125
|
+
};
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import util from 'node:util';
|
2
|
+
|
3
|
+
import parse from '../parse.js';
|
4
|
+
|
5
|
+
const tests = {
|
6
|
+
'a': {},
|
7
|
+
'a(b)': {},
|
8
|
+
'a(b(c))': {},
|
9
|
+
'ab': {},
|
10
|
+
'[ab]': {},
|
11
|
+
'[a-z]': {},
|
12
|
+
'a*': {},
|
13
|
+
'a+': {},
|
14
|
+
'a?': {},
|
15
|
+
'a(b)+': {}
|
16
|
+
}
|
17
|
+
|
18
|
+
for (const str in tests) {
|
19
|
+
console.log(str, util.inspect(parse(str), false, null, true));
|
20
|
+
}
|
package/compiler/sections.js
CHANGED
@@ -8,11 +8,26 @@ const createSection = (type, data) => [
|
|
8
8
|
...encodeVector(data)
|
9
9
|
];
|
10
10
|
|
11
|
+
const customSection = (name, data) => [
|
12
|
+
Section.custom,
|
13
|
+
...encodeVector([...encodeString(name), ...data])
|
14
|
+
];
|
15
|
+
|
16
|
+
const chHint = (topTier, baselineTier, strategy) => {
|
17
|
+
// 1 byte of 4 2 bit components: spare, top tier, baseline tier, compilation strategy
|
18
|
+
// tiers: 0x00 = default, 0x01 = baseline (liftoff), 0x02 = optimized (turbofan)
|
19
|
+
// strategy: 0x00 = default, 0x01 = lazy, 0x02 = eager, 0x03 = lazy baseline, eager top tier
|
20
|
+
return (strategy | (baselineTier << 2) | (topTier << 4));
|
21
|
+
};
|
22
|
+
|
11
23
|
export default (funcs, globals, tags, pages, flags) => {
|
12
24
|
const types = [], typeCache = {};
|
13
25
|
|
14
26
|
const optLevel = parseInt(process.argv.find(x => x.startsWith('-O'))?.[2] ?? 1);
|
15
27
|
|
28
|
+
const compileHints = process.argv.includes('-compile-hints');
|
29
|
+
if (compileHints) log('sections', 'warning: compile hints is V8 only w/ experimental arg! (you used -compile-hints)');
|
30
|
+
|
16
31
|
const getType = (params, returns) => {
|
17
32
|
const hash = `${params.join(',')}_${returns.join(',')}`;
|
18
33
|
if (optLog) log('sections', `getType(${JSON.stringify(params)}, ${JSON.stringify(returns)}) -> ${hash} | cache: ${typeCache[hash]}`);
|
@@ -74,6 +89,14 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
74
89
|
encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
|
75
90
|
);
|
76
91
|
|
92
|
+
// compilation hints section - unspec v8 only
|
93
|
+
// https://github.com/WebAssembly/design/issues/1473#issuecomment-1431274746
|
94
|
+
const chSection = !compileHints ? [] : customSection(
|
95
|
+
'compilationHints',
|
96
|
+
// for now just do everything as optimise eager
|
97
|
+
encodeVector(funcs.map(_ => chHint(0x02, 0x02, 0x02)))
|
98
|
+
);
|
99
|
+
|
77
100
|
const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
|
78
101
|
Section.global,
|
79
102
|
encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
|
@@ -146,6 +169,7 @@ export default (funcs, globals, tags, pages, flags) => {
|
|
146
169
|
...typeSection,
|
147
170
|
...importSection,
|
148
171
|
...funcSection,
|
172
|
+
...chSection,
|
149
173
|
...memorySection,
|
150
174
|
...tagSection,
|
151
175
|
...globalSection,
|
package/package.json
CHANGED
package/r.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/a(b)/.test('hi');
|
package/runner/index.js
CHANGED
@@ -3,6 +3,18 @@
|
|
3
3
|
import compile from '../compiler/wrap.js';
|
4
4
|
import fs from 'node:fs';
|
5
5
|
|
6
|
+
if (process.argv.includes('-compile-hints')) {
|
7
|
+
const v8 = await import('node:v8');
|
8
|
+
v8.setFlagsFromString(`--experimental-wasm-compilation-hints`);
|
9
|
+
|
10
|
+
// see also these flags:
|
11
|
+
// --experimental-wasm-branch-hinting
|
12
|
+
// --experimental-wasm-extended-const
|
13
|
+
// --experimental-wasm-inlining (?)
|
14
|
+
// --experimental-wasm-js-inlining (?)
|
15
|
+
// --experimental-wasm-return-call (on by default)
|
16
|
+
}
|
17
|
+
|
6
18
|
const file = process.argv.slice(2).find(x => x[0] !== '-');
|
7
19
|
if (!file) {
|
8
20
|
if (process.argv.includes('-v')) {
|
package/runner/transform.js
CHANGED
@@ -8,7 +8,8 @@ const source = fs.readFileSync(file, 'utf8');
|
|
8
8
|
const { wasm } = await compile(source);
|
9
9
|
|
10
10
|
// const out = `(async () => { const print = str => process.stdout.write(str); (await WebAssembly.instantiate(Uint8Array.from([${wasm.toString()}]), {'': { p: i => print(i.toString()), c: i => print(String.fromCharCode(i))}})).instance.exports.m()})()`;
|
11
|
-
const out = `new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])),{'':{p:i=>process.stdout.write(i.toString())}}).exports.m()`;
|
11
|
+
// const out = `new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])),{'':{p:i=>process.stdout.write(i.toString())}}).exports.m()`;
|
12
|
+
const out = `const a=new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([${wasm.toString()}])));const b=a.exports.m();console.log(Array.from(new Uint16Array(a.exports.$.buffer,b+4,new Int32Array(a.exports.$.buffer,b,1))).map(x=>String.fromCharCode(x)).join(''))`;
|
12
13
|
|
13
14
|
console.log(out);
|
14
15
|
eval(out);
|