porffor 0.14.0-f67c123a1 → 0.16.0-08983b6b1

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.
Files changed (45) hide show
  1. package/CONTRIBUTING.md +16 -10
  2. package/README.md +14 -30
  3. package/asur/index.js +1 -1
  4. package/compiler/2c.js +172 -59
  5. package/compiler/allocators.js +128 -0
  6. package/compiler/assemble.js +37 -5
  7. package/compiler/builtins/annexb_string.ts +1 -0
  8. package/compiler/builtins/array.ts +154 -7
  9. package/compiler/builtins/base64.ts +1 -0
  10. package/compiler/builtins/boolean.ts +3 -1
  11. package/compiler/builtins/console.ts +6 -0
  12. package/compiler/builtins/crypto.ts +1 -0
  13. package/compiler/builtins/date.ts +5 -30
  14. package/compiler/builtins/error.js +22 -0
  15. package/compiler/builtins/escape.ts +1 -2
  16. package/compiler/builtins/function.ts +2 -0
  17. package/compiler/builtins/int.ts +2 -0
  18. package/compiler/builtins/math.ts +410 -0
  19. package/compiler/builtins/number.ts +12 -21
  20. package/compiler/builtins/object.ts +2 -0
  21. package/compiler/builtins/porffor.d.ts +18 -0
  22. package/compiler/builtins/set.ts +22 -12
  23. package/compiler/builtins/string.ts +1 -0
  24. package/compiler/builtins/string_f64.ts +10 -0
  25. package/compiler/builtins/symbol.ts +62 -0
  26. package/compiler/builtins/z_ecma262.ts +62 -0
  27. package/compiler/builtins.js +50 -12
  28. package/compiler/codegen.js +826 -552
  29. package/compiler/cyclone.js +535 -0
  30. package/compiler/decompile.js +7 -1
  31. package/compiler/generated_builtins.js +635 -84
  32. package/compiler/havoc.js +93 -0
  33. package/compiler/index.js +108 -15
  34. package/compiler/opt.js +10 -44
  35. package/compiler/parse.js +3 -9
  36. package/compiler/pgo.js +212 -0
  37. package/compiler/precompile.js +17 -11
  38. package/compiler/prefs.js +13 -5
  39. package/compiler/prototype.js +200 -186
  40. package/compiler/wasmSpec.js +2 -2
  41. package/compiler/wrap.js +128 -44
  42. package/package.json +3 -5
  43. package/runner/index.js +31 -15
  44. package/runner/repl.js +18 -2
  45. /package/runner/{profiler.js → profile.js} +0 -0
package/compiler/wrap.js CHANGED
@@ -6,9 +6,26 @@ import { TYPES } from './types.js';
6
6
  import { log } from './log.js';
7
7
  import Prefs from './prefs.js';
8
8
 
9
+ const fs = (typeof process?.version !== 'undefined' ? (await import('node:fs')) : undefined);
10
+
9
11
  const bold = x => `\u001b[1m${x}\u001b[0m`;
10
12
 
11
- const porfToJSValue = (memory, funcs, value, type) => {
13
+ export const readByteStr = (memory, ptr) => {
14
+ const length = (new Int32Array(memory.buffer, ptr, 1))[0];
15
+ return Array.from(new Uint8Array(memory.buffer, ptr + 4, length)).map(x => String.fromCharCode(x)).join('');
16
+ };
17
+
18
+ export const writeByteStr = (memory, ptr, str) => {
19
+ const length = str.length;
20
+ (new Int32Array(memory.buffer, ptr, 1))[0] = length;
21
+
22
+ const arr = new Uint8Array(memory.buffer, ptr + 4, length);
23
+ for (let i = 0; i < length; i++) {
24
+ arr[i] = str.charCodeAt(i);
25
+ }
26
+ };
27
+
28
+ const porfToJSValue = ({ memory, funcs, pages }, value, type) => {
12
29
  switch (type) {
13
30
  case TYPES.boolean: return Boolean(value);
14
31
  case TYPES.undefined: return undefined;
@@ -41,9 +58,22 @@ const porfToJSValue = (memory, funcs, value, type) => {
41
58
  case TYPES.array: {
42
59
  const length = (new Int32Array(memory.buffer, value, 1))[0];
43
60
 
44
- // have to slice because of memory alignment (?)
45
- const buf = memory.buffer.slice(value + 4, value + 4 + 8 * length);
46
- return Array.from(new Float64Array(buf, 0, length));
61
+ const out = [];
62
+ for (let i = 0; i < length; i++) {
63
+ const offset = value + 4 + (i * 9);
64
+
65
+ // have to slice because of memory alignment (?)
66
+ const v = (new Float64Array(memory.buffer.slice(offset, offset + 8), 0, 1))[0];
67
+ const t = (new Uint8Array(memory.buffer, offset + 8, 1))[0];
68
+
69
+ // console.log(`reading value at index ${i}...`)
70
+ // console.log(' memory:', Array.from(new Uint8Array(memory.buffer, offset, 9)).map(x => x.toString(16).padStart(2, '0')).join(' '));
71
+ // console.log(' read:', { value: v, type: t }, '\n');
72
+
73
+ out.push(porfToJSValue({ memory, funcs, pages }, v, t));
74
+ }
75
+
76
+ return out;
47
77
  }
48
78
 
49
79
  case TYPES.date: {
@@ -66,66 +96,54 @@ const porfToJSValue = (memory, funcs, value, type) => {
66
96
  // console.log(' memory:', Array.from(new Uint8Array(memory.buffer, offset, 9)).map(x => x.toString(16).padStart(2, '0')).join(' '));
67
97
  // console.log(' read:', { value: v, type: t }, '\n');
68
98
 
69
- out.add(porfToJSValue(memory, funcs, v, t));
99
+ out.add(porfToJSValue({ memory, funcs, pages }, v, t));
70
100
  }
71
101
 
72
102
  return out;
73
103
  }
74
104
 
105
+ case TYPES.symbol: {
106
+ const descStore = pages.get('bytestring: __Porffor_symbol_descStore/ptr').ind * pageSize;
107
+ if (!descStore) return Symbol();
108
+
109
+ const offset = descStore + 4 + ((value - 1) * 9);
110
+
111
+ const v = (new Float64Array(memory.buffer.slice(offset, offset + 8), 0, 1))[0];
112
+ const t = (new Uint8Array(memory.buffer, offset + 8, 1))[0];
113
+
114
+ const desc = porfToJSValue({ memory, funcs, pages }, v, t);
115
+ return Symbol(desc);
116
+ }
117
+
75
118
  default: return value;
76
119
  }
77
120
  };
78
121
 
79
- export default async (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
122
+ export default (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
80
123
  const times = [];
81
124
 
82
125
  const t1 = performance.now();
83
- const { wasm, funcs, globals, tags, exceptions, pages, c } = compile(source, flags);
126
+ const { wasm, funcs, globals, tags, exceptions, pages, c } = typeof source === 'object' ? source : compile(source, flags);
84
127
 
85
128
  globalThis.porfDebugInfo = { funcs, globals };
86
129
 
87
- if (source.includes('export function')) flags.push('module');
130
+ if (source.includes?.('export ')) flags.push('module');
88
131
 
89
- // (await import('node:fs')).writeFileSync('out.wasm', Buffer.from(wasm));
132
+ // fs.writeFileSync('out.wasm', Buffer.from(wasm));
90
133
 
91
134
  times.push(performance.now() - t1);
92
135
  if (Prefs.profileCompiler) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
93
136
 
94
- const t2 = performance.now();
95
-
96
- let instance;
97
- try {
98
- let wasmEngine = WebAssembly;
99
- if (Prefs.asur) {
100
- log.warning('wrap', 'using our !experimental! asur wasm engine instead of host to run');
101
- wasmEngine = await import('../asur/index.js');
102
- }
103
-
104
- 0, { instance } = await wasmEngine.instantiate(wasm, {
105
- '': {
106
- p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
107
- c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
108
- t: () => performance.now(),
109
- u: () => performance.timeOrigin,
110
- y: () => {},
111
- z: () => {},
112
- ...customImports
113
- }
114
- });
115
- } catch (e) {
116
- // only backtrace for runner, not test262/etc
117
- if (!process.argv[1].includes('/runner')) throw e;
118
-
119
- const funcInd = parseInt(e.message.match(/function #([0-9]+) /)?.[1]);
120
- const blobOffset = parseInt(e.message.split('@')?.[1]);
121
-
122
- if (!funcInd) throw e;
137
+ const backtrace = (funcInd, blobOffset) => {
138
+ if (funcInd == null || blobOffset == null ||
139
+ Number.isNaN(funcInd) || Number.isNaN(blobOffset)) return false;
123
140
 
124
141
  // convert blob offset -> function wasm offset.
125
142
  // this is not good code and is somewhat duplicated
126
143
  // I just want it to work for debugging, I don't care about perf/yes
127
-
128
144
  const func = funcs.find(x => x.index === funcInd);
145
+ if (!func) return false;
146
+
129
147
  const locals = Object.values(func.locals).sort((a, b) => a.idx - b.idx).slice(func.params.length).sort((a, b) => a.idx - b.idx);
130
148
 
131
149
  let localDecl = [], typeCount = 0, lastType;
@@ -142,7 +160,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
142
160
 
143
161
  if (typeCount !== 0) localDecl.push(encodeLocal(typeCount, lastType));
144
162
 
145
- const toFind = encodeVector(localDecl).concat(func.wasm.flat().filter(x => x != null && x <= 0xff).slice(0, 40));
163
+ const toFind = encodeVector(localDecl).concat(func.wasm.flat().filter(x => x != null && x <= 0xff).slice(0, 60));
146
164
 
147
165
  let i = 0;
148
166
  for (; i < wasm.length; i++) {
@@ -157,7 +175,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
157
175
  if (!mismatch) break;
158
176
  }
159
177
 
160
- if (i === wasm.length) throw e;
178
+ if (i === wasm.length) return false;
161
179
 
162
180
  const offset = (blobOffset - i) + encodeVector(localDecl).length;
163
181
 
@@ -168,7 +186,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
168
186
  if (cumLen === offset) break;
169
187
  }
170
188
 
171
- if (cumLen !== offset) throw e;
189
+ if (cumLen !== offset) return false;
172
190
 
173
191
  i -= 1;
174
192
 
@@ -193,6 +211,59 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
193
211
  console.log(decomp.join('\n'));
194
212
  console.log('\x1B[90m...\x1B[0m\n');
195
213
 
214
+ return true;
215
+ };
216
+
217
+ const t2 = performance.now();
218
+
219
+ let instance;
220
+ try {
221
+ // let wasmEngine = WebAssembly;
222
+ // if (Prefs.asur) {
223
+ // log.warning('wrap', 'using our !experimental! asur wasm engine instead of host to run');
224
+ // wasmEngine = await import('../asur/index.js');
225
+ // }
226
+
227
+ // 0, { instance } = await wasmEngine.instantiate(wasm, {
228
+ const module = new WebAssembly.Module(wasm);
229
+ instance = new WebAssembly.Instance(module, {
230
+ '': {
231
+ p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
232
+ c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
233
+ t: () => performance.now(),
234
+ u: () => performance.timeOrigin,
235
+ y: () => {},
236
+ z: () => {},
237
+ w: (ind, outPtr) => { // readArgv
238
+ const args = process.argv.slice(2).filter(x => !x.startsWith('-'));
239
+ const str = args[ind];
240
+ if (!str) return -1;
241
+
242
+ writeByteStr(memory, outPtr, str);
243
+ return str.length;
244
+ },
245
+ q: (pathPtr, outPtr) => { // readFile
246
+ try {
247
+ const path = readByteStr(memory, pathPtr);
248
+ const contents = fs.readFileSync(path, 'utf8');
249
+ writeByteStr(memory, outPtr, contents);
250
+ return contents.length;
251
+ } catch {
252
+ return -1;
253
+ }
254
+ },
255
+ ...customImports
256
+ }
257
+ });
258
+ } catch (e) {
259
+ // only backtrace for runner, not test262/etc
260
+ if (!process.argv[1].includes('/runner')) throw e;
261
+ if (!(e instanceof WebAssembly.CompileError)) throw e;
262
+
263
+ const funcInd = parseInt(e.message.match(/function #([0-9]+) /)?.[1]);
264
+ const blobOffset = parseInt(e.message.split('@')?.[1]);
265
+
266
+ backtrace(funcInd, blobOffset);
196
267
  throw e;
197
268
  }
198
269
 
@@ -200,6 +271,7 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
200
271
  if (Prefs.profileCompiler) console.log(`instantiated in ${times[1].toFixed(2)}ms`);
201
272
 
202
273
  const exports = {};
274
+ const rawValues = process.argv.includes('-i');
203
275
 
204
276
  const exceptTag = instance.exports['0'], memory = instance.exports['$'];
205
277
  for (const x in instance.exports) {
@@ -218,10 +290,11 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
218
290
  exports[func.name] = function() {
219
291
  try {
220
292
  const ret = exp.apply(this, arguments);
221
-
222
293
  if (ret == null) return undefined;
223
294
 
224
- return porfToJSValue(memory, funcs, ret[0], ret[1]);
295
+ if (rawValues) return { value: ret[0], type: ret[1], js: porfToJSValue({ memory, funcs, pages }, ret[0], ret[1]) };
296
+
297
+ return porfToJSValue({ memory, funcs, pages }, ret[0], ret[1]);
225
298
  } catch (e) {
226
299
  if (e.is && e.is(exceptTag)) {
227
300
  const exceptId = e.getArg(exceptTag, 0);
@@ -236,6 +309,17 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
236
309
  throw new constructor(exception.message);
237
310
  }
238
311
 
312
+ if (e instanceof WebAssembly.RuntimeError) {
313
+ // only backtrace for runner, not test262/etc
314
+ if (!process.argv[1].includes('/runner')) throw e;
315
+
316
+ const match = e.stack.match(/wasm-function\[([0-9]+)\]:([0-9a-z]+)/) ?? [];
317
+ const funcInd = parseInt(match[1]);
318
+ const blobOffset = parseInt(match[2]);
319
+
320
+ backtrace(funcInd, blobOffset);
321
+ }
322
+
239
323
  throw e;
240
324
  }
241
325
  };
package/package.json CHANGED
@@ -1,12 +1,10 @@
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.14.0-f67c123a1",
4
+ "version": "0.16.0-08983b6b1",
5
5
  "author": "CanadaHonk",
6
6
  "license": "MIT",
7
- "scripts": {
8
- "precompile": "node ./compiler/precompile.js"
9
- },
7
+ "scripts": {},
10
8
  "dependencies": {
11
9
  "acorn": "^8.11.3",
12
10
  "node-repl-polyfill": "^0.1.1"
@@ -28,5 +26,5 @@
28
26
  "bugs": {
29
27
  "url": "https://github.com/CanadaHonk/porffor/issues"
30
28
  },
31
- "homepage": "https://porffor.goose.icu"
29
+ "homepage": "https://porffor.dev"
32
30
  }
package/runner/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
-
3
- import compile from '../compiler/wrap.js';
4
2
  import fs from 'node:fs';
5
3
 
4
+ // deno compat
5
+ if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
6
+ globalThis.process = await import('node:process');
7
+ }
8
+
6
9
  const start = performance.now();
7
10
 
8
11
  if (process.argv.includes('--compile-hints')) {
@@ -25,7 +28,7 @@ if (process.argv.includes('--help')) {
25
28
  console.log(`Usage: \x1B[1mporf [command] path/to/script.js [...prefs] [...args]\x1B[0m`);
26
29
 
27
30
  // commands
28
- console.log(`\n\u001b[4mCommands\x1B[0m`);
31
+ console.log(`\n\x1B[1mCommands:\x1B[0m`);
29
32
  for (const [ cmd, [ color, desc ] ] of Object.entries({
30
33
  run: [ 34, 'Run a JS file' ],
31
34
  wasm: [ 34, 'Compile a JS file to a Wasm binary\n' ],
@@ -59,10 +62,22 @@ if (process.argv.includes('--help')) {
59
62
  }
60
63
 
61
64
  let file = process.argv.slice(2).find(x => x[0] !== '-');
62
- if (['run', 'wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm'].includes(file)) {
65
+ if (['precompile', 'run', 'wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm'].includes(file)) {
66
+ // remove this arg
67
+ process.argv.splice(process.argv.indexOf(file), 1);
68
+
69
+ if (file === 'precompile') {
70
+ await import('../compiler/precompile.js');
71
+ await new Promise(() => {}); // do nothing for the rest of this file
72
+ }
73
+
63
74
  if (file === 'profile') {
64
- process.argv.splice(process.argv.indexOf(file), 1);
65
- await import('./profiler.js');
75
+ await import('./profile.js');
76
+ await new Promise(() => {}); // do nothing for the rest of this file
77
+ }
78
+
79
+ if (file === 'debug') {
80
+ await import('./debug.js');
66
81
  await new Promise(() => {}); // do nothing for the rest of this file
67
82
  }
68
83
 
@@ -74,13 +89,7 @@ if (['run', 'wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm'].includes(fi
74
89
  process.argv.push('--asur', '--wasm-debug');
75
90
  }
76
91
 
77
- if (file === 'debug') {
78
- process.argv.splice(process.argv.indexOf(file), 1);
79
- await import('./debug.js');
80
- await new Promise(() => {}); // do nothing for the rest of this file
81
- }
82
-
83
- file = process.argv.slice(process.argv.indexOf(file) + 1).find(x => x[0] !== '-');
92
+ file = process.argv.slice(2).find(x => x[0] !== '-');
84
93
 
85
94
  const nonOptOutFile = process.argv.slice(process.argv.indexOf(file) + 1).find(x => x[0] !== '-');
86
95
  if (nonOptOutFile) {
@@ -88,6 +97,8 @@ if (['run', 'wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm'].includes(fi
88
97
  }
89
98
  }
90
99
 
100
+ globalThis.file = file;
101
+
91
102
  if (!file) {
92
103
  if (process.argv.includes('-v') || process.argv.includes('--version')) {
93
104
  // just print version
@@ -102,6 +113,8 @@ if (!file) {
102
113
 
103
114
  const source = fs.readFileSync(file, 'utf8');
104
115
 
116
+ const compile = (await import('../compiler/wrap.js')).default;
117
+
105
118
  let cache = '';
106
119
  const print = str => {
107
120
  /* cache += str;
@@ -114,17 +127,20 @@ const print = str => {
114
127
  process.stdout.write(str);
115
128
  };
116
129
 
130
+ let runStart;
117
131
  try {
118
132
  if (process.argv.includes('-b')) {
119
133
  const { wasm, exports } = await compile(source, process.argv.includes('--module') ? [ 'module' ] : [], {}, print);
120
134
 
135
+ runStart = performance.now();
121
136
  if (!process.argv.includes('--no-run')) exports.main();
122
137
 
123
138
  console.log(`\n\nwasm size: ${wasm.byteLength} bytes`);
124
139
  } else {
125
140
  const { exports } = await compile(source, process.argv.includes('--module') ? [ 'module' ] : [], {}, print);
126
141
 
127
- if (!process.argv.includes('-no-run')) exports.main();
142
+ runStart = performance.now();
143
+ if (!process.argv.includes('--no-run')) exports.main();
128
144
  }
129
145
  // if (cache) process.stdout.write(cache);
130
146
  } catch (e) {
@@ -132,4 +148,4 @@ try {
132
148
  console.error(process.argv.includes('-i') ? e : `${e.constructor.name}: ${e.message}`);
133
149
  }
134
150
 
135
- if (process.argv.includes('-t')) console.log(`${process.argv.includes('-b') ? '' : '\n\n'}total time: ${(performance.now() - start).toFixed(2)}ms`);
151
+ if (process.argv.includes('-t')) console.log(`${process.argv.includes('-b') ? '' : '\n\n'}total time: ${(performance.now() - start).toFixed(2)}ms\nexecution time: ${(performance.now() - runStart).toFixed(2)}ms`);
package/runner/repl.js CHANGED
@@ -1,6 +1,11 @@
1
+ import { TYPE_NAMES } from '../compiler/types.js';
1
2
  import compile from '../compiler/wrap.js';
2
3
  import version from './version.js';
3
4
 
5
+ import util from 'node:util';
6
+
7
+ process.argv.push('--no-opt-unused');
8
+
4
9
  let repl;
5
10
  try {
6
11
  // try importing node:repl
@@ -86,10 +91,21 @@ const run = async (source, _context, _filename, callback, run = true) => {
86
91
  lastPages = [...pages.keys()];
87
92
  }
88
93
 
89
- const ret = run ? exports.main() : undefined;
90
- callback(null, ret);
94
+ let ret = run ? exports.main() : undefined;
95
+ let value, type;
96
+ if (ret?.type != null) {
97
+ value = ret.value;
98
+ type = ret.type;
99
+ ret = ret.js;
100
+ }
101
+
102
+ console.log(util.inspect(ret, false, 2, true), (value != null ? `\x1B[34m\x1B[3m(value: ${value}, type: ${TYPE_NAMES[type]})\x1B[0m` : ''));
103
+
104
+ // callback(null, ret);
91
105
 
92
106
  prev = prev + ';\n' + source.trim();
107
+
108
+ callback();
93
109
  };
94
110
 
95
111
  const replServer = repl.start({ prompt: '> ', eval: run });
File without changes