porffor 0.16.0-ab08df866 → 0.16.0-b099006b8

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.
@@ -0,0 +1,93 @@
1
+ // havoc: wasm rewrite library (it wreaks havoc upon wasm bytecode hence "havoc")
2
+ import { Opcodes, Valtype } from './wasmSpec.js';
3
+
4
+ export const localsToConsts = (func, targets, consts, { localKeys }) => {
5
+ const { wasm, locals } = func;
6
+
7
+ // update locals object
8
+ localKeys ??= Object.keys(locals).sort((a, b) => a.idx - b.idx);
9
+ for (const x of targets) {
10
+ delete locals[localKeys[x]];
11
+ }
12
+
13
+ const idxLut = {};
14
+ for (const x in locals) {
15
+ let i = locals[x].idx;
16
+ const o = i;
17
+ for (let j = 0; j < targets.length; j++) {
18
+ if (o > targets[j]) i--;
19
+ }
20
+
21
+ locals[x].idx = i;
22
+ idxLut[o] = i;
23
+ }
24
+
25
+ // update wasm
26
+ for (let i = 0; i < wasm.length; i++) {
27
+ const op = wasm[i];
28
+ const opcode = op[0];
29
+ const idx = op[1];
30
+
31
+ if (opcode >= 0x20 && opcode <= 0x22) { // local.* op
32
+ if (targets.includes(idx)) {
33
+ if (opcode === Opcodes.local_get || opcode === Opcodes.local_tee) {
34
+ // get/tee -> const
35
+ const c = consts[targets.indexOf(idx)];
36
+ wasm.splice(i, 1, c);
37
+ continue;
38
+ }
39
+
40
+ // set -> drop
41
+ wasm.splice(i, 1, [ Opcodes.drop ]);
42
+ continue;
43
+ }
44
+
45
+ // adjust index to compensate for removed
46
+ wasm[i] = [ opcode, idxLut[idx] ];
47
+ }
48
+ }
49
+ };
50
+
51
+ export const f64ToI32s = (func, targets) => {
52
+ const { wasm, locals } = func;
53
+
54
+ // update locals object
55
+ for (const x in locals) {
56
+ let i = locals[x].idx;
57
+ if (targets.includes(i)) {
58
+ locals[x].type = Valtype.i32;
59
+ }
60
+ }
61
+
62
+ // update wasm
63
+ for (let i = 0; i < wasm.length; i++) {
64
+ const op = wasm[i];
65
+ const opcode = op[0];
66
+ const idx = op[1];
67
+
68
+ if (opcode >= 0x20 && opcode <= 0x22 && targets.includes(idx)) { // local.* op
69
+ if (opcode === Opcodes.local_get) {
70
+ // add i32 -> f64 after
71
+ wasm.splice(i + 1, 0, Opcodes.i32_from);
72
+ continue;
73
+ }
74
+
75
+ if (opcode === Opcodes.local_set) {
76
+ // add f64 -> i32 before
77
+ wasm.splice(i, 0, Opcodes.i32_to);
78
+ i++;
79
+ continue;
80
+ }
81
+
82
+ if (opcode === Opcodes.local_tee) {
83
+ // add f64 -> i32 before
84
+ wasm.splice(i, 0, Opcodes.i32_to);
85
+ i++;
86
+
87
+ // add i32 -> f64 after
88
+ wasm.splice(i + 1, 0, Opcodes.i32_from);
89
+ continue;
90
+ }
91
+ }
92
+ }
93
+ };
package/compiler/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { underline, bold, log } from './log.js';
2
+ import { Valtype, PageSize } from './wasmSpec.js';
2
3
  import parse from './parse.js';
3
4
  import codegen from './codegen.js';
4
5
  import opt from './opt.js';
5
6
  import assemble from './assemble.js';
6
7
  import decompile from './decompile.js';
7
8
  import toc from './2c.js';
9
+ import * as pgo from './pgo.js';
10
+ import cyclone from './cyclone.js';
8
11
  import Prefs from './prefs.js';
9
12
 
10
13
  globalThis.decompile = decompile;
@@ -13,6 +16,7 @@ const logFuncs = (funcs, globals, exceptions) => {
13
16
  console.log('\n' + underline(bold('funcs')));
14
17
 
15
18
  for (const f of funcs) {
19
+ if (f.internal) continue;
16
20
  console.log(decompile(f.wasm, f.name, f.index, f.locals, f.params, f.returns, funcs, globals, exceptions));
17
21
  }
18
22
 
@@ -23,6 +27,24 @@ const fs = (typeof process?.version !== 'undefined' ? (await import('node:fs'))
23
27
  const execSync = (typeof process?.version !== 'undefined' ? (await import('node:child_process')).execSync : undefined);
24
28
 
25
29
  export default (code, flags) => {
30
+ let target = Prefs.target ?? 'wasm';
31
+ if (Prefs.native) target = 'native';
32
+
33
+ let outFile = Prefs.o;
34
+
35
+ globalThis.valtype = 'f64';
36
+ const valtypeOpt = process.argv.find(x => x.startsWith('--valtype='));
37
+ if (valtypeOpt) valtype = valtypeOpt.split('=')[1];
38
+ globalThis.valtypeBinary = Valtype[valtype];
39
+
40
+ globalThis.pageSize = PageSize;
41
+ const pageSizeOpt = process.argv.find(x => x.startsWith('--page-size='));
42
+ if (pageSizeOpt) pageSize = parseInt(pageSizeOpt.split('=')[1]) * 1024;
43
+
44
+ // enable pgo by default for c/native
45
+ if (target !== 'wasm') Prefs.pgo = Prefs.pgo === false ? false : true;
46
+ if (Prefs.pgo) pgo.setup();
47
+
26
48
  const t0 = performance.now();
27
49
  const program = parse(code, flags);
28
50
  if (Prefs.profileCompiler) console.log(`1. parsed in ${(performance.now() - t0).toFixed(2)}ms`);
@@ -35,9 +57,49 @@ export default (code, flags) => {
35
57
 
36
58
  const t2 = performance.now();
37
59
  opt(funcs, globals, pages, tags, exceptions);
38
- if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
39
60
 
40
- // if (Prefs.optFuncs) logFuncs(funcs, globals, exceptions);
61
+ if (Prefs.pgo) {
62
+ if (Prefs.pgoLog) {
63
+ const oldSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
64
+ const t = performance.now();
65
+
66
+ pgo.run({ funcs, globals, tags, exceptions, pages, data });
67
+ opt(funcs, globals, pages, tags, exceptions);
68
+
69
+ console.log(`PGO total time: ${(performance.now() - t).toFixed(2)}ms`);
70
+
71
+ const newSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
72
+ console.log(`PGO size diff: ${oldSize - newSize} bytes (${oldSize} -> ${newSize})\n`);
73
+ } else {
74
+ pgo.run({ funcs, globals, tags, exceptions, pages, data });
75
+ opt(funcs, globals, pages, tags, exceptions);
76
+ }
77
+ }
78
+
79
+ if (Prefs.cyclone) {
80
+ if (Prefs.cycloneLog) {
81
+ const oldSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
82
+ const t = performance.now();
83
+
84
+ for (const x of funcs) {
85
+ const preOps = x.wasm.length;
86
+ cyclone(x.wasm);
87
+
88
+ console.log(`${x.name}: ${preOps} -> ${x.wasm.length} ops`);
89
+ }
90
+
91
+ console.log(`cyclone total time: ${(performance.now() - t).toFixed(2)}ms`);
92
+
93
+ const newSize = assemble(funcs, globals, tags, pages, data, flags, true).byteLength;
94
+ console.log(`cyclone size diff: ${oldSize - newSize} bytes (${oldSize} -> ${newSize})\n`);
95
+ } else {
96
+ for (const x of funcs) {
97
+ cyclone(x.wasm);
98
+ }
99
+ }
100
+ }
101
+
102
+ if (Prefs.profileCompiler) console.log(`3. optimized in ${(performance.now() - t2).toFixed(2)}ms`);
41
103
 
42
104
  const t3 = performance.now();
43
105
  const wasm = assemble(funcs, globals, tags, pages, data, flags);
@@ -54,9 +116,6 @@ export default (code, flags) => {
54
116
 
55
117
  const out = { wasm, funcs, globals, tags, exceptions, pages, data };
56
118
 
57
- const target = Prefs.target ?? 'wasm';
58
- const outFile = Prefs.o;
59
-
60
119
  if (target === 'wasm' && outFile) {
61
120
  fs.writeFileSync(outFile, Buffer.from(wasm));
62
121
 
@@ -77,6 +136,8 @@ export default (code, flags) => {
77
136
  }
78
137
 
79
138
  if (target === 'native') {
139
+ outFile ??= Prefs.native ? './porffor_tmp' : file.split('/').at(-1).split('.').at(0, -1).join('.');
140
+
80
141
  let compiler = Prefs.compiler ?? 'clang';
81
142
  const cO = Prefs._cO ?? 'Ofast';
82
143
 
@@ -95,7 +156,29 @@ export default (code, flags) => {
95
156
 
96
157
  fs.unlinkSync(tmpfile);
97
158
 
98
- if (process.version) process.exit();
159
+ if (process.version) {
160
+ if (Prefs.native) {
161
+ const cleanup = () => {
162
+ try {
163
+ fs.unlinkSync(outFile);
164
+ } catch {}
165
+ };
166
+
167
+ process.on('exit', cleanup);
168
+ process.on('beforeExit', cleanup);
169
+ process.on('SIGINT', () => {
170
+ cleanup();
171
+ process.exit();
172
+ });
173
+
174
+ const runArgs = process.argv.slice(2).filter(x => !x.startsWith('-'));
175
+ try {
176
+ execSync([ outFile, ...runArgs.slice(1) ].join(' '), { stdio: 'inherit' });
177
+ } catch {}
178
+ }
179
+
180
+ process.exit();
181
+ }
99
182
  }
100
183
 
101
184
  return out;
package/compiler/opt.js CHANGED
@@ -354,7 +354,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
354
354
  continue;
355
355
  }
356
356
 
357
- if (lastInst[0] === Opcodes.const && (inst === Opcodes.i32_to || inst === Opcodes.i32_to_u)) {
357
+ if (lastInst[0] === Opcodes.const && inst[0] === Opcodes.i32_to[0] && (inst[1] === Opcodes.i32_to[1] || inst[1] === Opcodes.i32_to_u[1])) {
358
358
  // change const and immediate i32 convert to i32 const
359
359
  // f64.const 0
360
360
  // i32.trunc_sat_f64_s || i32.trunc_sat_f64_u
@@ -504,42 +504,6 @@ export default (funcs, globals, pages, tags, exceptions) => {
504
504
  const useCount = {};
505
505
  for (const x in f.locals) useCount[f.locals[x].idx] = 0;
506
506
 
507
- // final pass
508
- depth = [];
509
- for (let i = 0; i < wasm.length; i++) {
510
- let inst = wasm[i];
511
- if (inst[0] === Opcodes.local_get || inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) useCount[inst[1]]++;
512
-
513
- if (inst[0] === Opcodes.if || inst[0] === Opcodes.loop || inst[0] === Opcodes.block) depth.push(inst[0]);
514
- if (inst[0] === Opcodes.end) depth.pop();
515
-
516
- if (i < 2) continue;
517
- const lastInst = wasm[i - 1];
518
- const lastLastInst = wasm[i - 2];
519
-
520
- // todo: add more math ops
521
- if (optLevel >= 3 && (inst[0] === Opcodes.add || inst[0] === Opcodes.sub || inst[0] === Opcodes.mul) && lastLastInst[0] === Opcodes.const && lastInst[0] === Opcodes.const) {
522
- // inline const math ops
523
- // i32.const a
524
- // i32.const b
525
- // i32.add
526
- // -->
527
- // i32.const a + b
528
-
529
- // does not work with leb encoded
530
- if (lastInst.length > 2 || lastLastInst.length > 2) continue;
531
-
532
- let a = lastLastInst[1];
533
- let b = lastInst[1];
534
-
535
- const val = performWasmOp(inst[0], a, b);
536
- if (Prefs.optLog) log('opt', `inlined math op (${a} ${inst[0].toString(16)} ${b} -> ${val})`);
537
-
538
- wasm.splice(i - 2, 3, ...number(val)); // remove consts, math op and add new const
539
- i -= 2;
540
- }
541
- }
542
-
543
507
  const localIdxs = Object.values(f.locals).map(x => x.idx);
544
508
  // remove unused locals (cleanup)
545
509
  for (const x in useCount) {
@@ -0,0 +1,207 @@
1
+ import { Opcodes, Valtype } from './wasmSpec.js';
2
+ import { number } from './embedding.js';
3
+ import { importedFuncs } from './builtins.js';
4
+ import Prefs from './prefs.js';
5
+ import assemble from './assemble.js';
6
+ import wrap, { writeByteStr } from './wrap.js';
7
+ import * as Havoc from './havoc.js';
8
+
9
+ export const setup = () => {
10
+ importedFuncs[importedFuncs.profile2].params = [ Valtype.i32, valtypeBinary ];
11
+
12
+ // enable these prefs by default for pgo
13
+ Prefs.typeswitchUniqueTmp = Prefs.typeswitchUniqueTmp === false ? false : true;
14
+ Prefs.cyclone = Prefs.cyclone === false ? false : true;;
15
+ };
16
+
17
+ export const run = obj => {
18
+ const wasmFuncs = obj.funcs;
19
+
20
+ let starts = {};
21
+ const time = (id, msg) => {
22
+ if (!Prefs.pgoLog) return;
23
+
24
+ if (!starts[id]) {
25
+ process.stdout.write(msg);
26
+ starts[id] = performance.now();
27
+ } else {
28
+ process.stdout.write(`\r${' '.repeat(50)}\r[${(performance.now() - starts[id]).toFixed(2)}ms] ${msg}\n`);
29
+ }
30
+ };
31
+
32
+ time(0, `injecting PGO logging...`);
33
+
34
+ let funcs = [];
35
+ for (let i = 0; i < wasmFuncs.length; i++) {
36
+ const { name, internal, params, locals, wasm } = wasmFuncs[i];
37
+ if (internal) continue; // ignore internal funcs
38
+ wasmFuncs[i].originalWasm = structuredClone(wasm);
39
+
40
+ const invLocals = Object.keys(locals).reduce((acc, x) => { acc[locals[x].idx] = locals[x]; return acc; }, {});
41
+
42
+ const id = funcs.length;
43
+ funcs.push({ name, id, locals, params, invLocals });
44
+
45
+ wasm.unshift(
46
+ // mark active func
47
+ ...number(i, Valtype.i32),
48
+ [ Opcodes.call, importedFuncs.profile1 ],
49
+
50
+ // log args
51
+ ...params.flatMap((_, i) => [
52
+ ...number(i, Valtype.i32),
53
+ [ Opcodes.local_get, i ],
54
+ ...(invLocals[i].type !== Valtype.f64 ? [ Opcodes.i32_from ] : []),
55
+ [ Opcodes.call, importedFuncs.profile2 ]
56
+ ])
57
+ );
58
+
59
+ for (let j = 0; j < wasm.length; j++) {
60
+ const inst = wasm[j];
61
+ if (inst[0] === Opcodes.local_set || inst[0] === Opcodes.local_tee) {
62
+ wasm.splice(j + 1, 0,
63
+ ...number(inst[1], Valtype.i32),
64
+ [ Opcodes.local_get, inst[1] ],
65
+ ...(invLocals[inst[1]].type !== Valtype.f64 ? [ Opcodes.i32_from ] : []),
66
+ [ Opcodes.call, importedFuncs.profile2 ]
67
+ );
68
+ }
69
+ }
70
+ }
71
+
72
+ let localData = funcs.map(x => new Array(Object.keys(x.locals).length).fill(0).map(() => []));
73
+
74
+ time(0, `injected PGO logging`);
75
+ time(1, `running with PGO logging...`);
76
+
77
+ let activeFunc = null, abort = false;
78
+ try {
79
+ obj.wasm = assemble(obj.funcs, obj.globals, obj.tags, obj.pages, obj.data, obj.flags, true);
80
+
81
+ const { exports } = wrap(obj, [], {
82
+ y: n => {
83
+ activeFunc = n;
84
+ },
85
+ z: (i, n) => {
86
+ if (activeFunc == null) throw 'fail';
87
+ localData[activeFunc][i].push(n);
88
+ },
89
+ w: (ind, outPtr) => { // readArgv
90
+ const pgoInd = process.argv.indexOf('--pgo');
91
+ const args = process.argv.slice(pgoInd).filter(x => !x.startsWith('-'));
92
+ const str = args[ind - 1];
93
+ if (pgoInd === -1 || !str) {
94
+ if (Prefs.pgoLog) console.log('\nPGO warning: script was expecting arguments, please specify args to use for PGO after --pgo arg');
95
+ return -1;
96
+ }
97
+
98
+ writeByteStr(exports.$, outPtr, str);
99
+ return str.length;
100
+ }
101
+ }, () => {});
102
+
103
+ exports.main();
104
+ } catch (e) {
105
+ throw e;
106
+ }
107
+
108
+ for (const x of funcs) {
109
+ const wasmFunc = wasmFuncs.find(y => y.name === x.name);
110
+ wasmFunc.wasm = wasmFunc.originalWasm;
111
+ delete wasmFunc.originalWasm;
112
+ }
113
+
114
+ if (abort) {
115
+ console.log('aborting PGO!');
116
+ return false;
117
+ }
118
+
119
+ time(1, `ran with PGO logging`);
120
+ time(2, 'processing PGO data...');
121
+
122
+ // process data
123
+ let log = '';
124
+ for (let i = 0; i < localData.length; i++) {
125
+ const func = funcs[i];
126
+ const total = localData[i].length;
127
+
128
+ const localKeys = Object.keys(func.locals).sort((a, b) => a.idx - b.idx);
129
+ const localValues = Object.values(func.locals).sort((a, b) => a.idx - b.idx);
130
+ func.localKeys = localKeys;
131
+ func.localValues = localValues;
132
+
133
+ let counts = new Array(10).fill(0);
134
+ const consistents = localData[i].map(x => {
135
+ if (x.length === 0 || !x.every((y, i) => i < 1 ? true : y === x[i - 1])) return false;
136
+
137
+ counts[0]++;
138
+ return x[0];
139
+ });
140
+
141
+ const integerOnlyF64s = localData[i].map((x, j) => {
142
+ if (localValues[j].type === Valtype.i32) return false; // already i32
143
+ if (x.length === 0 || !x.every(y => Number.isInteger(y))) return false;
144
+
145
+ counts[1]++;
146
+ return true;
147
+ });
148
+
149
+ func.consistents = consistents;
150
+ func.integerOnlyF64s = integerOnlyF64s;
151
+
152
+ log += ` ${func.name}: identified ${counts[0]}/${total} locals as consistent${Prefs.verbosePgo ? ':' : ''}\n`;
153
+ if (Prefs.verbosePgo) {
154
+ for (let j = 0; j < localData[i].length; j++) {
155
+ log += ` ${consistents[j] !== false ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m: ${new Set(localData[i][j]).size} unique values set\n`;
156
+ }
157
+ }
158
+
159
+ log += ` ${func.name}: identified ${counts[1]}/${localValues.reduce((acc, x) => acc + (x.type === Valtype.f64 ? 1 : 0), 0)} f64 locals as integer usage only${Prefs.verbosePgo ? ':' : ''}\n`;
160
+ if (Prefs.verbosePgo) {
161
+ for (let j = 0; j < localData[i].length; j++) {
162
+ if (localValues[j].type !== Valtype.f64) continue;
163
+ log += ` ${integerOnlyF64s[j] ? '\u001b[92m' : '\u001b[91m'}${localKeys[j]}\u001b[0m\n`;
164
+ }
165
+
166
+ log += '\n';
167
+ }
168
+ }
169
+
170
+ time(2, 'processed PGO data' + log);
171
+ time(3, 'optimizing using PGO data...');
172
+
173
+ log = '';
174
+ for (const x of funcs) {
175
+ const wasmFunc = wasmFuncs.find(y => y.name === x.name);
176
+
177
+ let targets = [];
178
+ for (let i = 0; i < x.integerOnlyF64s.length; i++) {
179
+ const c = x.integerOnlyF64s[i];
180
+ if (c === false) continue;
181
+ if (i < x.params.length) continue;
182
+
183
+ targets.push(i);
184
+ }
185
+
186
+ log += ` ${x.name}: replaced ${targets.length} f64 locals with i32s\n`;
187
+ if (targets.length > 0) Havoc.f64ToI32s(wasmFunc, targets);
188
+
189
+ targets = [];
190
+ let consts = [];
191
+ for (let i = 0; i < x.consistents.length; i++) {
192
+ const c = x.consistents[i];
193
+ if (c === false) continue;
194
+ if (i < x.params.length) continue;
195
+
196
+ targets.push(i);
197
+
198
+ const valtype = x.localValues[i].type;
199
+ consts.push(number(c, valtype)[0]);
200
+ }
201
+
202
+ log += ` ${x.name}: replaced ${targets.length} locals with consts\n`;
203
+ if (targets.length > 0) Havoc.localsToConsts(wasmFunc, targets, consts, { localKeys: x.localKeys });
204
+ }
205
+
206
+ time(3, 'optimized using PGO data' + log);
207
+ };
package/compiler/prefs.js CHANGED
@@ -23,9 +23,14 @@ const obj = new Proxy({}, {
23
23
  // do not cache in web demo as args are changed live
24
24
  if (!globalThis.document) cache[p] = ret;
25
25
  return ret;
26
+ },
27
+
28
+ set(_, p, v) {
29
+ cache[p] = v;
30
+ return true;
26
31
  }
27
32
  });
28
33
 
29
- obj.uncache = () => cache = {};
34
+ export const uncache = () => cache = {};
30
35
 
31
36
  export default obj;
package/compiler/wrap.js CHANGED
@@ -6,8 +6,25 @@ 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
 
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
+
11
28
  const porfToJSValue = ({ memory, funcs, pages }, value, type) => {
12
29
  switch (type) {
13
30
  case TYPES.boolean: return Boolean(value);
@@ -101,28 +118,31 @@ const porfToJSValue = ({ memory, funcs, pages }, value, type) => {
101
118
  }
102
119
  };
103
120
 
104
- export default async (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
121
+ export default (source, flags = [ 'module' ], customImports = {}, print = str => process.stdout.write(str)) => {
105
122
  const times = [];
106
123
 
107
124
  const t1 = performance.now();
108
- const { wasm, funcs, globals, tags, exceptions, pages, c } = compile(source, flags);
125
+ const { wasm, funcs, globals, tags, exceptions, pages, c } = typeof source === 'object' ? source : compile(source, flags);
109
126
 
110
127
  globalThis.porfDebugInfo = { funcs, globals };
111
128
 
112
- if (source.includes('export function')) flags.push('module');
129
+ if (source.includes?.('export ')) flags.push('module');
113
130
 
114
- // (await import('node:fs')).writeFileSync('out.wasm', Buffer.from(wasm));
131
+ fs.writeFileSync('out.wasm', Buffer.from(wasm));
115
132
 
116
133
  times.push(performance.now() - t1);
117
134
  if (Prefs.profileCompiler) console.log(bold(`compiled in ${times[0].toFixed(2)}ms`));
118
135
 
119
136
  const backtrace = (funcInd, blobOffset) => {
120
- if (funcInd == null || blobOffset == null) return false;
137
+ if (funcInd == null || blobOffset == null ||
138
+ Number.isNaN(funcInd) || Number.isNaN(blobOffset)) return false;
121
139
 
122
140
  // convert blob offset -> function wasm offset.
123
141
  // this is not good code and is somewhat duplicated
124
142
  // I just want it to work for debugging, I don't care about perf/yes
125
143
  const func = funcs.find(x => x.index === funcInd);
144
+ if (!func) return false;
145
+
126
146
  const locals = Object.values(func.locals).sort((a, b) => a.idx - b.idx).slice(func.params.length).sort((a, b) => a.idx - b.idx);
127
147
 
128
148
  let localDecl = [], typeCount = 0, lastType;
@@ -197,13 +217,15 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
197
217
 
198
218
  let instance;
199
219
  try {
200
- let wasmEngine = WebAssembly;
201
- if (Prefs.asur) {
202
- log.warning('wrap', 'using our !experimental! asur wasm engine instead of host to run');
203
- wasmEngine = await import('../asur/index.js');
204
- }
205
-
206
- 0, { instance } = await wasmEngine.instantiate(wasm, {
220
+ // let wasmEngine = WebAssembly;
221
+ // if (Prefs.asur) {
222
+ // log.warning('wrap', 'using our !experimental! asur wasm engine instead of host to run');
223
+ // wasmEngine = await import('../asur/index.js');
224
+ // }
225
+
226
+ // 0, { instance } = await wasmEngine.instantiate(wasm, {
227
+ const module = new WebAssembly.Module(wasm);
228
+ instance = new WebAssembly.Instance(module, {
207
229
  '': {
208
230
  p: valtype === 'i64' ? i => print(Number(i).toString()) : i => print(i.toString()),
209
231
  c: valtype === 'i64' ? i => print(String.fromCharCode(Number(i))) : i => print(String.fromCharCode(i)),
@@ -211,12 +233,31 @@ export default async (source, flags = [ 'module' ], customImports = {}, print =
211
233
  u: () => performance.timeOrigin,
212
234
  y: () => {},
213
235
  z: () => {},
236
+ w: (ind, outPtr) => { // readArgv
237
+ const args = process.argv.slice(2).filter(x => !x.startsWith('-'));
238
+ const str = args[ind];
239
+ if (!str) return -1;
240
+
241
+ writeByteStr(memory, outPtr, str);
242
+ return str.length;
243
+ },
244
+ q: (pathPtr, outPtr) => { // readFile
245
+ try {
246
+ const path = readByteStr(memory, pathPtr);
247
+ const contents = fs.readFileSync(path, 'utf8');
248
+ writeByteStr(memory, outPtr, contents);
249
+ return contents.length;
250
+ } catch {
251
+ return -1;
252
+ }
253
+ },
214
254
  ...customImports
215
255
  }
216
256
  });
217
257
  } catch (e) {
218
258
  // only backtrace for runner, not test262/etc
219
259
  if (!process.argv[1].includes('/runner')) throw e;
260
+ if (!(e instanceof WebAssembly.CompileError)) throw e;
220
261
 
221
262
  const funcInd = parseInt(e.message.match(/function #([0-9]+) /)?.[1]);
222
263
  const blobOffset = parseInt(e.message.split('@')?.[1]);