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 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
- ## soon todo
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
- - `try { } finally {}`
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
+ ![Screenshot of comparison chart](https://github.com/CanadaHonk/porffor/assets/19228318/76c75264-cc68-4be1-8891-c06dc389d97a)
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 (internal representations)
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
+ }
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "a basic experimental wip aot optimizing js -> wasm engine/compiler/runtime in js",
4
- "version": "0.0.0-c743344",
4
+ "version": "0.0.0-ebc0491",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
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')) {
@@ -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);