porffor 0.0.0-828ee15 → 0.0.0-ba812f2

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.
@@ -42,8 +42,8 @@ export default (wasm, name = '', ind = 0, locals = {}, params = [], returns = []
42
42
  out += ` ${read_ieee754_binary64(inst.slice(1))}`;
43
43
  } else if (inst[0] === Opcodes.i32_const || inst[0] === Opcodes.i64_const) {
44
44
  out += ` ${read_signedLEB128(inst.slice(1))}`;
45
- } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store) {
46
- out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`
45
+ } else if (inst[0] === Opcodes.i32_load || inst[0] === Opcodes.i64_load || inst[0] === Opcodes.f64_load || inst[0] === Opcodes.i32_store || inst[0] === Opcodes.i64_store || inst[0] === Opcodes.f64_store || inst[0] === Opcodes.i32_store16 || inst[0] === Opcodes.i32_load16_u) {
46
+ out += ` ${inst[1]} ${read_unsignedLEB128(inst.slice(2))}`;
47
47
  } else for (const operand of inst.slice(1)) {
48
48
  if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) {
49
49
  if (operand === Blocktype.void) continue;
@@ -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
 
@@ -22,15 +22,15 @@ export const encodeLocal = (count, type) => [
22
22
  type
23
23
  ];
24
24
 
25
+ // todo: this only works with integers within 32 bit range
25
26
  export const signedLEB128 = n => {
26
- // todo: this only works with integers within 32 bit range
27
+ n |= 0;
27
28
 
28
29
  // just input for small numbers (for perf as common)
29
30
  if (n >= 0 && n <= 63) return [ n ];
30
31
  if (n >= -64 && n <= 0) return [ 128 + n ];
31
32
 
32
33
  const buffer = [];
33
- n |= 0;
34
34
 
35
35
  while (true) {
36
36
  let byte = n & 0x7f;
@@ -50,6 +50,8 @@ export const signedLEB128 = n => {
50
50
  };
51
51
 
52
52
  export const unsignedLEB128 = n => {
53
+ n |= 0;
54
+
53
55
  // just input for small numbers (for perf as common)
54
56
  if (n >= 0 && n <= 127) return [ n ];
55
57
 
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
 
@@ -14,7 +16,9 @@ const bold = x => `\u001b[1m${x}\u001b[0m`;
14
16
  const areaColors = {
15
17
  codegen: [ 20, 80, 250 ],
16
18
  opt: [ 250, 20, 80 ],
17
- sections: [ 20, 250, 80 ]
19
+ sections: [ 20, 250, 80 ],
20
+ alloc: [ 250, 250, 20 ],
21
+ '2c': [ 20, 250, 250 ]
18
22
  };
19
23
 
20
24
  globalThis.log = (area, ...args) => console.log(`\u001b[90m[\u001b[0m${rgb(...areaColors[area], area)}\u001b[90m]\u001b[0m`, ...args);
@@ -35,9 +39,16 @@ const logFuncs = (funcs, globals, exceptions) => {
35
39
  console.log();
36
40
  };
37
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
+
38
47
  export default (code, flags) => {
39
48
  globalThis.optLog = process.argv.includes('-opt-log');
40
49
  globalThis.codeLog = process.argv.includes('-code-log');
50
+ globalThis.allocLog = process.argv.includes('-alloc-log');
51
+ globalThis.regexLog = process.argv.includes('-regex-log');
41
52
 
42
53
  for (const x in BuiltinPreludes) {
43
54
  if (code.indexOf(x + '(') !== -1) code = BuiltinPreludes[x] + code;
@@ -63,5 +74,45 @@ export default (code, flags) => {
63
74
  const sections = produceSections(funcs, globals, tags, pages, flags);
64
75
  if (flags.includes('info')) console.log(`4. produced sections in ${(performance.now() - t3).toFixed(2)}ms`);
65
76
 
66
- return { wasm: sections, funcs, globals, tags, exceptions, pages };
77
+ if (allocLog) {
78
+ const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
79
+ const bytes = wasmPages * 65536;
80
+ log('alloc', `\x1B[1mallocated ${bytes / 1024}KiB\x1B[0m for ${pages.size} things using ${wasmPages} Wasm page${wasmPages === 1 ? '' : 's'}`);
81
+ // console.log([...pages.keys()].map(x => `\x1B[36m - ${x}\x1B[0m`).join('\n'));
82
+ }
83
+
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;
67
118
  };
package/compiler/opt.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Opcodes, Valtype } from "./wasmSpec.js";
2
2
  import { number } from "./embedding.js";
3
+ import { read_signedLEB128, read_ieee754_binary64 } from "./encoding.js";
3
4
 
4
5
  // deno compat
5
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
@@ -20,7 +21,7 @@ export default (funcs, globals) => {
20
21
  if (optLevel === 0) return;
21
22
 
22
23
  const tailCall = process.argv.includes('-tail-call');
23
- if (tailCall) log('opt', 'tail call proposal is not widely implemented! (you used -tail-call)');
24
+ if (tailCall) log('opt', 'warning: tail call proposal is not widely implemented! (you used -tail-call)');
24
25
 
25
26
  if (optLevel >= 2 && !process.argv.includes('-opt-no-inline')) {
26
27
  // inline pass (very WIP)
@@ -95,7 +96,6 @@ export default (funcs, globals) => {
95
96
  }
96
97
 
97
98
  if (t.index > c.index) t.index--; // adjust index if after removed func
98
- if (c.memory) t.memory = true;
99
99
  }
100
100
 
101
101
  funcs.splice(funcs.indexOf(c), 1); // remove func from funcs
@@ -142,7 +142,7 @@ export default (funcs, globals) => {
142
142
  depth--;
143
143
  if (depth <= 0) break;
144
144
  }
145
- if (op === Opcodes.br) {
145
+ if (op === Opcodes.br || op === Opcodes.br_if) {
146
146
  hasBranch = true;
147
147
  break;
148
148
  }
@@ -213,9 +213,9 @@ export default (funcs, globals) => {
213
213
  // i32.const 0
214
214
  // drop
215
215
  // -->
216
- // <nothing>>
216
+ // <nothing>
217
217
 
218
- wasm.splice(i - 1, 2); // remove this inst
218
+ wasm.splice(i - 1, 2); // remove these inst
219
219
  i -= 2;
220
220
  continue;
221
221
  }
@@ -259,6 +259,36 @@ export default (funcs, globals) => {
259
259
  continue;
260
260
  }
261
261
 
262
+ if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
263
+ // change const and immediate i32 convert to i32 const
264
+ // f64.const 0
265
+ // i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
266
+ // -->
267
+ // i32.const 0
268
+
269
+ wasm[i - 1] = number((valtype === 'f64' ? read_ieee754_binary64 : read_signedLEB128)(lastInst.slice(1)), Valtype.i32)[0]; // f64.const -> i32.const
270
+
271
+ wasm.splice(i, 1); // remove this inst
272
+ i--;
273
+ if (optLog) log('opt', `converted const -> i32 convert into i32 const`);
274
+ continue;
275
+ }
276
+
277
+ if (lastInst[0] === Opcodes.i32_const && (inst === Opcodes.i32_from || inst === Opcodes.i32_from_u)) {
278
+ // change i32 const and immediate convert to const (opposite way of previous)
279
+ // i32.const 0
280
+ // f64.convert_i32_s || f64.convert_i32_u
281
+ // -->
282
+ // f64.const 0
283
+
284
+ wasm[i - 1] = number(read_signedLEB128(lastInst.slice(1)))[0]; // i32.const -> f64.const
285
+
286
+ wasm.splice(i, 1); // remove this inst
287
+ i--;
288
+ if (optLog) log('opt', `converted i32 const -> convert into const`);
289
+ continue;
290
+ }
291
+
262
292
  if (tailCall && lastInst[0] === Opcodes.call && inst[0] === Opcodes.return) {
263
293
  // replace call, return with tail calls (return_call)
264
294
  // call X
package/compiler/parse.js CHANGED
@@ -1,4 +1,5 @@
1
- const { parse } = (await import(globalThis.document ? 'https://esm.sh/acorn' : 'acorn'));
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, {
@@ -15,7 +15,8 @@ const TYPES = {
15
15
  bigint: 0xffffffffffff7,
16
16
 
17
17
  // these are not "typeof" types but tracked internally
18
- _array: 0xffffffffffff8
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
@@ -25,7 +26,6 @@ export const PrototypeFuncs = function() {
25
26
 
26
27
  this[TYPES._array] = {
27
28
  // lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
28
- // todo: out of bounds (>) properly
29
29
  at: (pointer, length, wIndex, iTmp) => [
30
30
  ...wIndex,
31
31
  Opcodes.i32_to,
@@ -147,7 +147,6 @@ export const PrototypeFuncs = function() {
147
147
  this[TYPES._array].push.noArgRetLength = true;
148
148
 
149
149
  this[TYPES.string] = {
150
- // todo: out of bounds properly
151
150
  at: (pointer, length, wIndex, iTmp, arrayShell) => {
152
151
  const [ newOut, newPointer ] = arrayShell(1, 'i16');
153
152
 
@@ -157,9 +156,9 @@ export const PrototypeFuncs = function() {
157
156
  [ Opcodes.drop ],
158
157
 
159
158
  ...number(0, Valtype.i32), // base 0 for store later
160
- Opcodes.i32_to_u,
161
159
 
162
160
  ...wIndex,
161
+ Opcodes.i32_to_u,
163
162
  [ Opcodes.local_tee, iTmp ],
164
163
 
165
164
  // if index < 0: access index + array length
@@ -265,7 +264,7 @@ export const PrototypeFuncs = function() {
265
264
  },
266
265
  };
267
266
 
268
- this[TYPES.string].at.local = valtypeBinary;
267
+ this[TYPES.string].at.local = Valtype.i32;
269
268
  this[TYPES.string].at.returnType = TYPES.string;
270
269
  this[TYPES.string].charAt.returnType = TYPES.string;
271
270
  this[TYPES.string].charCodeAt.local = Valtype.i32;
@@ -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]}`);
@@ -36,7 +51,7 @@ export default (funcs, globals, tags, pages, flags) => {
36
51
  // tree shake imports
37
52
  for (const f of funcs) {
38
53
  for (const inst of f.wasm) {
39
- if (inst[0] === Opcodes.call && inst[1] < importedFuncs.length) {
54
+ if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] < importedFuncs.length) {
40
55
  const idx = inst[1];
41
56
  const func = importedFuncs[idx];
42
57
 
@@ -51,15 +66,17 @@ export default (funcs, globals, tags, pages, flags) => {
51
66
  // fix call indexes for non-imports
52
67
  const delta = importedFuncs.length - importFuncs.length;
53
68
  for (const f of funcs) {
69
+ f.originalIndex = f.index;
54
70
  f.index -= delta;
55
71
 
56
72
  for (const inst of f.wasm) {
57
- if (inst[0] === Opcodes.call && inst[1] >= importedFuncs.length) {
73
+ if ((inst[0] === Opcodes.call || inst[0] === Opcodes.return_call) && inst[1] >= importedFuncs.length) {
58
74
  inst[1] -= delta;
59
75
  }
60
76
  }
61
77
  }
62
78
  }
79
+ globalThis.importFuncs = importFuncs;
63
80
 
64
81
  if (optLog) log('sections', `treeshake: using ${importFuncs.length}/${importedFuncs.length} imports`);
65
82
 
@@ -73,6 +90,14 @@ export default (funcs, globals, tags, pages, flags) => {
73
90
  encodeVector(funcs.map(x => getType(x.params, x.returns))) // type indexes
74
91
  );
75
92
 
93
+ // compilation hints section - unspec v8 only
94
+ // https://github.com/WebAssembly/design/issues/1473#issuecomment-1431274746
95
+ const chSection = !compileHints ? [] : customSection(
96
+ 'compilationHints',
97
+ // for now just do everything as optimise eager
98
+ encodeVector(funcs.map(_ => chHint(0x02, 0x02, 0x02)))
99
+ );
100
+
76
101
  const globalSection = Object.keys(globals).length === 0 ? [] : createSection(
77
102
  Section.global,
78
103
  encodeVector(Object.keys(globals).map(x => [ globals[x].type, 0x01, ...number(globals[x].init ?? 0, globals[x].type).flat(), Opcodes.end ]))
@@ -145,6 +170,7 @@ export default (funcs, globals, tags, pages, flags) => {
145
170
  ...typeSection,
146
171
  ...importSection,
147
172
  ...funcSection,
173
+ ...chSection,
148
174
  ...memorySection,
149
175
  ...tagSection,
150
176
  ...globalSection,
package/compiler/wrap.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import compile from './index.js';
2
2
  import decompile from './decompile.js';
3
- // import fs from 'node:fs';
3
+ import fs from 'node:fs';
4
4
 
5
5
  const bold = x => `\u001b[1m${x}\u001b[0m`;
6
6
 
7
7
  const typeBase = 0xffffffffffff0;
8
+ const internalTypeBase = 0xfffffffffff0f;
8
9
  const TYPES = {
9
10
  [typeBase]: 'number',
10
11
  [typeBase + 1]: 'boolean',
@@ -16,7 +17,8 @@ const TYPES = {
16
17
  [typeBase + 7]: 'bigint',
17
18
 
18
19
  // internal
19
- [typeBase + 8]: '_array'
20
+ [internalTypeBase]: '_array',
21
+ [internalTypeBase + 1]: '_regexp'
20
22
  };
21
23
 
22
24
  export default async (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
@@ -27,7 +29,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
27
29
 
28
30
  if (source.includes('export function')) flags.push('module');
29
31
 
30
- // fs.writeFileSync('out.wasm', Buffer.from(wasm));
32
+ fs.writeFileSync('out.wasm', Buffer.from(wasm));
31
33
 
32
34
  times.push(performance.now() - t1);
33
35
  if (flags.includes('info')) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
@@ -90,6 +92,15 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
90
92
  return Array.from(new Uint16Array(memory.buffer, pointer + 4, length)).map(x => String.fromCharCode(x)).join('');
91
93
  }
92
94
 
95
+ case 'function': {
96
+ // wasm func index, including all imports
97
+ const func = funcs.find(x => (x.originalIndex ?? x.index) === ret);
98
+ if (!func) return ret;
99
+
100
+ // make fake empty func for repl/etc
101
+ return {[func.name]() {}}[func.name];
102
+ }
103
+
93
104
  default: return ret;
94
105
  }
95
106
  } catch (e) {
package/cool.exe ADDED
Binary file
package/g ADDED
Binary file
package/g.exe ADDED
Binary file
package/hi.c ADDED
@@ -0,0 +1,37 @@
1
+ #include <stdio.h>
2
+
3
+ double inline f64_f(double x, double y) {
4
+ return x - (int)(x / y) * y;
5
+ }
6
+
7
+ double isPrime(double number) {
8
+ double i;
9
+
10
+ if (number < 2e+0) {
11
+ return 0e+0;
12
+ }
13
+ i = 2e+0;
14
+ while (i < number) {
15
+ if (f64_f(number, i) == 0e+0) {
16
+ return 0e+0;
17
+ }
18
+ i = i + 1e+0;
19
+ }
20
+ return 1e+0;
21
+ }
22
+
23
+ int main() {
24
+ double sum;
25
+ double counter;
26
+
27
+ sum = 0e+0;
28
+ counter = 0e+0;
29
+ while (counter <= 1e+5) {
30
+ if (isPrime(counter) == 1e+0) {
31
+ sum = sum + counter;
32
+ }
33
+ counter = counter + 1e+0;
34
+ }
35
+ printf("%f\n", sum);
36
+ }
37
+
package/out ADDED
Binary file
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-828ee15",
4
+ "version": "0.0.0-ba812f2",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
7
  "dependencies": {
package/r.js ADDED
@@ -0,0 +1 @@
1
+ /a(b)/.test('hi');
@@ -0,0 +1,37 @@
1
+ # rhemyn
2
+ a basic experimental wip regex engine/aot wasm compiler in js. regex engine for porffor. uses own regex parser, no dependencies (excluding porffor internals). <br>
3
+ age: ~1 day
4
+
5
+ made for use with porffor but could possibly be adapted, implementation/library notes:
6
+ - exposes functions for each regex "operation" (eg test, match)
7
+ - given a regex pattern string (eg `a+`), it returns a "function" object
8
+ - wasm function returned expects an i32 pointer to a utf-16 string (can add utf-8 option later if someone else actually wants to use this)
9
+
10
+ ## syntax
11
+ 🟢 supported 🟡 partial 🟠 parsed only 🔴 unsupported
12
+
13
+ - 🟢 literal characters (eg `a`)
14
+ - 🟢 escaping (eg `\.\n\cJ\x0a\u000a`)
15
+ - 🟢 character itself (eg `\.`)
16
+ - 🟢 escape sequences (eg `\n`)
17
+ - 🟢 control character (eg `\cJ`)
18
+ - 🟢 unicode code points (eg `\x00`, `\u0000`)
19
+ - 🟢 sets (eg `[ab]`)
20
+ - 🟢 ranges (eg `[a-z]`)
21
+ - 🟢 negated sets (eg `[^ab]`)
22
+ - 🟢 metacharacters
23
+ - 🟢 dot (eg `a.b`)
24
+ - 🟢 digit, not digit (eg `\d\D`)
25
+ - 🟢 word, not word (eg `\w\W`)
26
+ - 🟢 whitespace, not whitespace (eg `\s\S`)
27
+ - 🟠 quantifiers
28
+ - 🟠 star (eg `a*`)
29
+ - 🟠 plus (eg `a+`)
30
+ - 🟠 optional (eg `a?`)
31
+ - 🟠 lazy modifier (eg `a*?`)
32
+ - 🔴 n repetitions (eg `a{4}`)
33
+ - 🔴 n-m repetitions (eg `a{2,4}`)
34
+ - 🔴 anchors
35
+ - 🔴 beginning (eg `^a`)
36
+ - 🔴 end (eg `a$`)
37
+ - 🔴 word boundary assertion (eg `\b\B`)