porffor 0.58.14 → 0.58.16

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/compiler/opt.js CHANGED
@@ -22,12 +22,8 @@ export default (funcs, globals, pages, tags, exceptions) => {
22
22
  while (runs > 0) {
23
23
  runs--;
24
24
 
25
- // main pass
26
25
  for (let i = 0; i < wasm.length; i++) {
27
- let inst = wasm[i];
28
- inst = [ ...inst ];
29
- wasm[i] = inst;
30
-
26
+ const inst = wasm[i];
31
27
  if (inst[0] === Opcodes.block) {
32
28
  // remove unneeded blocks (no brs inside)
33
29
  // block
@@ -53,23 +49,22 @@ export default (funcs, globals, pages, tags, exceptions) => {
53
49
  if (!hasBranch) {
54
50
  wasm.splice(i, 1); // remove this inst (block)
55
51
  if (i > 0) i--;
56
- inst = wasm[i];
57
52
 
58
53
  wasm.splice(j - 1, 1); // remove end of this block
59
54
 
60
55
  if (Prefs.optLog) log('opt', `removed unneeded block in for loop`);
56
+ continue;
61
57
  }
62
58
  }
63
59
 
64
60
  // remove setting last type if it is never gotten
65
61
  if (!f.internal && !f.gotLastType && inst[0] === Opcodes.local_set && inst[1] === lastType) {
66
62
  // replace this inst with drop
67
- wasm.splice(i, 1, [ Opcodes.drop ]); // remove this and last inst
68
- if (i > 0) i--;
63
+ wasm[i] = [ Opcodes.drop ];
69
64
  }
70
65
 
71
66
  if (i < 1) continue;
72
- let lastInst = wasm[i - 1];
67
+ const lastInst = wasm[i - 1];
73
68
 
74
69
  if (lastInst[1] === inst[1] && lastInst[0] === Opcodes.local_set && inst[0] === Opcodes.local_get) {
75
70
  // replace set, get -> tee (sets and returns)
@@ -78,8 +73,8 @@ export default (funcs, globals, pages, tags, exceptions) => {
78
73
  // -->
79
74
  // local.tee 0
80
75
 
81
- lastInst[0] = Opcodes.local_tee; // replace last inst opcode (set -> tee)
82
76
  wasm.splice(i, 1); // remove this inst (get)
77
+ wasm[i - 1] = [ Opcodes.local_tee, ...lastInst.slice(1) ]; // replace last inst opcode (set -> tee)
83
78
 
84
79
  i--;
85
80
  // if (Prefs.optLog) log('opt', `consolidated set, get -> tee`);
@@ -105,8 +100,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
105
100
  // -->
106
101
  // local.set 0
107
102
 
108
- lastInst[0] = Opcodes.local_set; // change last op
109
-
103
+ wasm[i - 1] = [ Opcodes.local_set, lastInst[1] ]; // change last op
110
104
  wasm.splice(i, 1); // remove this inst
111
105
  i--;
112
106
  continue;
@@ -131,7 +125,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
131
125
  // -->
132
126
  // i32.eqz
133
127
 
134
- inst[0] = Opcodes.eqz[0][0]; // eq -> eqz
128
+ wasm[i] = [ Opcodes.eqz[0][0] ]; // eq -> eqz
135
129
  wasm.splice(i - 1, 1); // remove const 0
136
130
  i--;
137
131
  continue;
@@ -213,7 +207,7 @@ export default (funcs, globals, pages, tags, exceptions) => {
213
207
  // -->
214
208
  // return_call X
215
209
 
216
- lastInst[0] = Opcodes.return_call; // change last inst return -> return_call
210
+ wasm[i - 1] = [ Opcodes.return_call, ...lastInst.slice(1) ]; // change last inst return -> return_call
217
211
 
218
212
  wasm.splice(i, 1); // remove this inst (return)
219
213
  i--;
package/compiler/parse.js CHANGED
@@ -36,6 +36,7 @@ export default input => {
36
36
  next: true,
37
37
  module: Prefs.module,
38
38
  webcompat: true,
39
+ raw: true,
39
40
 
40
41
  // babel
41
42
  plugins: types ? ['estree', 'typescript'] : ['estree'],
@@ -46,10 +47,11 @@ export default input => {
46
47
  ranges: false,
47
48
  tokens: false,
48
49
  comments: false,
50
+ preserveParens: false,
49
51
 
50
52
  // oxc
51
53
  lang: types ? 'ts' : 'js',
52
- showSemanticErrors: true // sorry oxc pals but this default is bad
54
+ showSemanticErrors: true
53
55
  };
54
56
 
55
57
  let ast = parser === 'oxc-parser' ? parse('js', input, options) : parse(input, options);
@@ -249,7 +249,7 @@ ${funcs.map(x => {
249
249
  .replace(/\"local","(.*?)",(.*?)\]/g, (_, name, valtype) => `loc('${name}',${valtype})]`)
250
250
  .replace(/\[16,"(.*?)"]/g, (_, name) => `[16,builtin('${name}')]`)
251
251
  .replace(/\["funcref",(.*?),"(.*?)"]/g, (_1, op, name) => op === '65' ? `...i32ify(funcRef('${name}'))` : `...funcRef('${name}')`)
252
- .replace(/\["str",(.*?),"(.*?)",(.*?)]/g, (_1, op, str, forceBytestring) => op === '65' ? `...i32ify(makeString(_,"${str}",${forceBytestring ? 1 : 0}))` : `...makeString(_,"${str}",${forceBytestring ? 1 : 0})`)
252
+ .replace(/\["str",(.*?),"(.*?)",(.*?)]/g, (_1, op, str, bytestring) => op === '65' ? `...i32ify(makeString(_,"${str}",${bytestring}))` : `...makeString(_,"${str}",${bytestring === 'true' ? 1 : 0})`)
253
253
  .replace(/\["throw","(.*?)","(.*?)"\]/g, (_, constructor, message) => `...internalThrow(_,'${constructor}',\`${message}\`)`)
254
254
  .replace(/\["get object","(.*?)"\]/g, (_, objName) => `...generateIdent(_,{name:'${objName}'})`)
255
255
  .replace(/\[null,"typeswitch case start",\[(.*?)\]\],/g, (_, types) => `...t([${types}],()=>[`)
@@ -284,7 +284,7 @@ params:${JSON.stringify(x.params)},typedParams:1,returns:${JSON.stringify(x.retu
284
284
  locals:${JSON.stringify(locals.slice(x.params.length).map(x => x[1].type))},localNames:${JSON.stringify(locals.map(x => x[0]))},
285
285
  ${x.globalInits ? `globalInits:{${Object.keys(x.globalInits).map(y => `${y}:${rewriteWasm(x.globalInits[y])}`).join(',')}},` : ''}${x.data && Object.keys(x.data).length > 0 ? `data:${JSON.stringify(x.data)},` : ''}
286
286
  ${x.table ? `table:1,` : ''}${x.constr ? `constr:1,` : ''}${x.hasRestArgument ? `hasRestArgument:1,` : ''}${x.usesTag ? `usesTag:1,` : ''}${x.usesImports ? `usesImports:1,` : ''}
287
- }`.replaceAll('\n\n', '\n').replaceAll('\n\n', '\n').replaceAll('\n\n', '\n');
287
+ }`.replaceAll('\n\n', '\n').replaceAll('\n\n', '\n').replaceAll('\n\n', '\n').replaceAll(',\n}', '\n}');
288
288
  }).join('\n')}
289
289
  }`;
290
290
  };
@@ -14,54 +14,6 @@ export const PrototypeFuncs = function() {
14
14
  else zeroChecks = {};
15
15
 
16
16
  this[TYPES.array] = {
17
- // lX = local accessor of X ({ get, set }), iX = local index of X, wX = wasm ops of X
18
- at: ({ pointer, length, arg, iTmp, setType }) => [
19
- ...arg,
20
- Opcodes.i32_to,
21
- [ Opcodes.local_tee, iTmp ],
22
-
23
- // if index < 0: access index + array length
24
- number(0, Valtype.i32),
25
- [ Opcodes.i32_lt_s ],
26
- [ Opcodes.if, Blocktype.void ],
27
- [ Opcodes.local_get, iTmp ],
28
- ...length.getCachedI32(),
29
- [ Opcodes.i32_add ],
30
- [ Opcodes.local_set, iTmp ],
31
- [ Opcodes.end ],
32
-
33
- // if still < 0 or >= length: return undefined
34
- [ Opcodes.local_get, iTmp ],
35
- number(0, Valtype.i32),
36
- [ Opcodes.i32_lt_s ],
37
-
38
- [ Opcodes.local_get, iTmp ],
39
- ...length.getCachedI32(),
40
- [ Opcodes.i32_ge_s ],
41
- [ Opcodes.i32_or ],
42
-
43
- [ Opcodes.if, Blocktype.void ],
44
- number(UNDEFINED),
45
- ...setType(TYPES.undefined),
46
- [ Opcodes.br, 1 ],
47
- [ Opcodes.end ],
48
-
49
- [ Opcodes.local_get, iTmp ],
50
- number(ValtypeSize[valtype] + 1, Valtype.i32),
51
- [ Opcodes.i32_mul ],
52
- ...pointer,
53
- [ Opcodes.i32_add ],
54
- [ Opcodes.local_set, iTmp ],
55
-
56
- // read from memory
57
- [ Opcodes.local_get, iTmp ],
58
- [ Opcodes.load, 0, ValtypeSize.i32 ],
59
-
60
- [ Opcodes.local_get, iTmp ],
61
- [ Opcodes.i32_load8_u, 0, ValtypeSize.i32 + ValtypeSize[valtype] ],
62
- ...setType()
63
- ],
64
-
65
17
  pop: ({ pointer, length, iTmp, unusedValue, setType }) => [
66
18
  // if length == 0, noop
67
19
  ...length.getCachedI32(),
@@ -177,7 +129,6 @@ export const PrototypeFuncs = function() {
177
129
  ]
178
130
  };
179
131
 
180
- this[TYPES.array].at.local = Valtype.i32;
181
132
  this[TYPES.array].pop.local = Valtype.i32;
182
133
 
183
134
  this[TYPES.string] = {
package/compiler/wrap.js CHANGED
@@ -6,8 +6,6 @@ import './prefs.js';
6
6
 
7
7
  const fs = (typeof process?.version !== 'undefined' ? (await import('node:fs')) : undefined);
8
8
 
9
- const checkOOB = (memory, ptr) => ptr >= memory.buffer.byteLength;
10
-
11
9
  let dv;
12
10
  const read = (ta, memory, ptr, length) => {
13
11
  if (ta === Uint8Array) return new Uint8Array(memory.buffer, ptr, length);
@@ -43,7 +41,7 @@ const porfToJSValue = ({ memory, funcs, pages }, value, type, override = undefin
43
41
  case TYPES.booleanobject: return new Boolean(value);
44
42
 
45
43
  case TYPES.object: {
46
- if (value === 0 || checkOOB(memory, value)) return null;
44
+ if (value === 0) return null;
47
45
 
48
46
  const size = read(Uint16Array, memory, value, 1)[0];
49
47
 
@@ -0,0 +1,201 @@
1
+ // Track declared variables to avoid undefined references
2
+ let declaredVars = [];
3
+
4
+ const generators = {
5
+ // Literals - wrap in parens to avoid identifier issues like 9.prototype
6
+ number: () => {
7
+ const num = Math.random() < 0.5 ? Math.floor(Math.random() * 100) : Math.random() * 100;
8
+ return `(${num})`;
9
+ },
10
+ string: () => {
11
+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
12
+ const len = Math.floor(Math.random() * 8) + 1;
13
+ return `'${Array.from({length: len}, () => chars[Math.floor(Math.random() * chars.length)]).join('')}'`;
14
+ },
15
+ boolean: () => Math.random() < 0.5 ? 'true' : 'false',
16
+ null: () => 'null',
17
+ undefined: () => 'undefined',
18
+
19
+ // Arrays - simple arrays only
20
+ array: (depth = 0) => {
21
+ if (depth > 2) return '[]';
22
+ const len = Math.floor(Math.random() * 3);
23
+ if (len === 0) return '[]';
24
+ const items = Array.from({length: len}, () => generateSimple());
25
+ return `[${items.join(', ')}]`;
26
+ },
27
+
28
+ // Objects - simple objects only
29
+ object: (depth = 0) => {
30
+ if (depth > 2) return '{}';
31
+ const len = Math.floor(Math.random() * 3);
32
+ if (len === 0) return '{}';
33
+ const props = Array.from({length: len}, () => {
34
+ const key = String.fromCharCode(97 + Math.floor(Math.random() * 26));
35
+ // Ensure valid property syntax
36
+ return `'${key}': ${generateSimple()}`;
37
+ });
38
+ return `({${props.join(', ')}})`;
39
+ },
40
+
41
+ // Variables - only use declared ones
42
+ variable: () => {
43
+ if (declaredVars.length === 0) return '42';
44
+ return declaredVars[Math.floor(Math.random() * declaredVars.length)];
45
+ }
46
+ };
47
+
48
+ // Generate simple expressions (no complex nesting)
49
+ function generateSimple() {
50
+ const simpleTypes = ['number', 'string', 'boolean', 'null', 'undefined'];
51
+ if (declaredVars.length > 0 && Math.random() < 0.3) {
52
+ simpleTypes.push('variable');
53
+ }
54
+ const type = simpleTypes[Math.floor(Math.random() * simpleTypes.length)];
55
+ const result = generators[type]();
56
+ // Ensure we never return undefined/null that could cause syntax issues
57
+ return result || '42';
58
+ }
59
+
60
+ // Generate expressions with controlled complexity
61
+ function generateExpression(depth = 0) {
62
+ if (depth > 2) return generateSimple();
63
+
64
+ const roll = Math.random();
65
+
66
+ if (roll < 0.4) {
67
+ // Simple value
68
+ return generateSimple();
69
+ } else if (roll < 0.6) {
70
+ // Binary operation
71
+ const ops = ['+', '-', '*', '/', '%', '===', '!==', '>', '<', '>=', '<=', '&&', '||'];
72
+ const op = ops[Math.floor(Math.random() * ops.length)];
73
+ return `(${generateExpression(depth + 1)} ${op} ${generateExpression(depth + 1)})`;
74
+ } else if (roll < 0.75) {
75
+ // Unary operation
76
+ const ops = ['!', '-', '+', 'typeof'];
77
+ const op = ops[Math.floor(Math.random() * ops.length)];
78
+ return `(${op} ${generateExpression(depth + 1)})`;
79
+ } else if (roll < 0.85) {
80
+ // Function call
81
+ const builtins = ['Math.abs', 'Math.floor', 'Math.ceil', 'String', 'Number', 'Boolean'];
82
+ const func = builtins[Math.floor(Math.random() * builtins.length)];
83
+ const argCount = Math.floor(Math.random() * 2);
84
+ const args = Array.from({length: argCount}, () => generateExpression(depth + 1));
85
+ return `${func}(${args.join(', ')})`;
86
+ } else if (roll < 0.95) {
87
+ // Conditional
88
+ return `(${generateExpression(depth + 1)} ? ${generateExpression(depth + 1)} : ${generateExpression(depth + 1)})`;
89
+ } else {
90
+ // Array or object
91
+ return Math.random() < 0.5 ? generators.array(depth) : generators.object(depth);
92
+ }
93
+ }
94
+
95
+ function generateStatement(depth = 0, inFunction = false) {
96
+ if (depth > 2) return `${generateSimple()};`;
97
+
98
+ const roll = Math.random();
99
+
100
+ if (roll < 0.3) {
101
+ // Variable declaration
102
+ const varName = `var${declaredVars.length}`;
103
+ declaredVars.push(varName);
104
+ return `var ${varName} = ${generateExpression()};`;
105
+ } else if (roll < 0.5 && declaredVars.length > 0) {
106
+ // Variable assignment
107
+ const varName = declaredVars[Math.floor(Math.random() * declaredVars.length)];
108
+ const ops = ['=', '+=', '-=', '*='];
109
+ const op = ops[Math.floor(Math.random() * ops.length)];
110
+ return `${varName} ${op} ${generateExpression()};`;
111
+ } else if (roll < 0.65) {
112
+ // If statement
113
+ if (Math.random() < 0.5) {
114
+ return `if (${generateExpression()}) { ${generateStatement(depth + 1, inFunction)} }`;
115
+ } else {
116
+ return `if (${generateExpression()}) { ${generateStatement(depth + 1, inFunction)} } else { ${generateStatement(depth + 1, inFunction)} }`;
117
+ }
118
+ } else if (roll < 0.75) {
119
+ // Loop
120
+ if (Math.random() < 0.5) {
121
+ const limit = Math.floor(Math.random() * 5) + 1;
122
+ return `for (let i = 0; i < ${limit}; i++) { ${generateStatement(depth + 1, inFunction)} }`;
123
+ } else {
124
+ return `while (${generateExpression(depth + 1)}) { ${generateStatement(depth + 1, inFunction)} break; }`;
125
+ }
126
+ } else if (roll < 0.85) {
127
+ // Try/catch
128
+ return `try { ${generateStatement(depth + 1, inFunction)} } catch (e) { ${generateStatement(depth + 1, inFunction)} }`;
129
+ } else if (roll < 0.95 && inFunction) {
130
+ // Return (only in functions)
131
+ return `return ${generateExpression()};`;
132
+ } else {
133
+ // Expression statement
134
+ return `${generateExpression()};`;
135
+ }
136
+ }
137
+
138
+ function generateFunction() {
139
+ const name = `func${Math.floor(Math.random() * 100)}`;
140
+ const paramCount = Math.floor(Math.random() * 3);
141
+ const params = Array.from({length: paramCount}, (_, i) => `param${i}`);
142
+
143
+ // Save current variables and add parameters
144
+ const savedVars = [...declaredVars];
145
+ params.forEach(p => declaredVars.push(p));
146
+
147
+ const stmtCount = Math.floor(Math.random() * 3) + 1;
148
+ const statements = [];
149
+ for (let i = 0; i < stmtCount; i++) {
150
+ statements.push(' ' + generateStatement(0, true));
151
+ }
152
+
153
+ // Always add a simple return to avoid syntax issues
154
+ statements.push(' return ' + generateSimple() + ';');
155
+
156
+ // Restore variables
157
+ declaredVars = savedVars;
158
+
159
+ return `function ${name}(${params.join(', ')}) {\n${statements.join('\n')}\n}`;
160
+ }
161
+
162
+ function generateProgram(options = {}) {
163
+ const {
164
+ maxStatements = 8,
165
+ maxFunctions = 2,
166
+ includeStrictMode = false
167
+ } = options;
168
+
169
+ declaredVars = [];
170
+
171
+ let code = '';
172
+
173
+ if (includeStrictMode) {
174
+ code += "'use strict';\n";
175
+ }
176
+
177
+ // Always declare some initial variables
178
+ const varCount = Math.floor(Math.random() * 3) + 2;
179
+ for (let i = 0; i < varCount; i++) {
180
+ const varName = `var${i}`;
181
+ declaredVars.push(varName);
182
+ code += `var ${varName} = ${generateExpression()};\n`;
183
+ }
184
+
185
+ const funcCount = Math.floor(Math.random() * maxFunctions) + 1;
186
+ for (let i = 0; i < funcCount; i++) {
187
+ code += generateFunction() + '\n\n';
188
+ }
189
+
190
+ const stmtCount = Math.floor(Math.random() * maxStatements) + 2;
191
+ for (let i = 0; i < stmtCount; i++) {
192
+ code += generateStatement() + '\n';
193
+ }
194
+
195
+ // Always end with a result
196
+ code += `var result = ${generateExpression()};\n`;
197
+
198
+ return code;
199
+ }
200
+
201
+ export { generateExpression as generate, generateStatement, generateFunction, generateProgram };
package/fuzz/index.js ADDED
@@ -0,0 +1,270 @@
1
+ import cluster from 'node:cluster';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import process from 'node:process';
5
+ import { generateProgram } from './generator.js';
6
+
7
+ const workerDataPath = '/tmp/fuzzWorkerData.json';
8
+
9
+ if (cluster.isPrimary) {
10
+ const veryStart = performance.now();
11
+
12
+ // Parse CLI arguments
13
+ let threads = parseInt(process.argv.find(x => x.startsWith('--threads='))?.split('=')?.[1]);
14
+ if (Number.isNaN(threads)) {
15
+ threads = Math.max(1, os.cpus().length - 1);
16
+ console.log(`Using ${threads} threads (detected ${os.cpus().length} CPUs)`);
17
+ }
18
+
19
+ const duration = parseFloat(process.argv.find(x => x.startsWith('--duration='))?.split('=')?.[1]) || 60; // seconds
20
+ const maxStatements = parseInt(process.argv.find(x => x.startsWith('--max-statements='))?.split('=')?.[1]) || 8;
21
+ const maxFunctions = parseInt(process.argv.find(x => x.startsWith('--max-functions='))?.split('=')?.[1]) || 2;
22
+ const verbose = process.argv.includes('--verbose');
23
+ const saveFailures = process.argv.includes('--save-failures');
24
+ const plainResults = process.argv.includes('--plain-results');
25
+
26
+ console.log(`Starting fuzzer for ${duration}s with ${threads} threads`);
27
+ console.log(`Max statements: ${maxStatements}, Max functions: ${maxFunctions}`);
28
+
29
+ const table = (...arr) => {
30
+ let out = '';
31
+ const total = arr[0];
32
+ for (let i = 0; i < arr.length; i++) {
33
+ let icon = [ '🧪', '🤠', '❌', '💀', '🏗️', '💥', '⏰', '📝' ][i];
34
+ let iconDesc = [ 'total', 'pass', 'fail', 'runtime error', 'wasm compile error', 'compile error', 'timeout', 'todo' ][i];
35
+
36
+ let data = arr[i];
37
+ if (i > 0) data = ((data / total) * 100).toPrecision(2) + '%';
38
+ let str = `${plainResults ? iconDesc : icon} ${data}`;
39
+ if (i !== arr.length - 1) str += plainResults ? ' | ' : '\u001b[90m | \u001b[0m';
40
+ out += str;
41
+ }
42
+ return out;
43
+ };
44
+
45
+ // Stats tracking - same order as test262
46
+ let totalTests = 0;
47
+ let passes = 0;
48
+ let fails = 0;
49
+ let runtimeErrors = 0;
50
+ let wasmErrors = 0;
51
+ let compileErrors = 0;
52
+ let timeouts = 0;
53
+ let todos = 0;
54
+
55
+ const errorCounts = new Map();
56
+ const failureFiles = [];
57
+
58
+ const startTime = performance.now();
59
+ const endTime = startTime + (duration * 1000);
60
+
61
+ // Worker data
62
+ const workerData = {
63
+ maxStatements,
64
+ maxFunctions,
65
+ verbose,
66
+ saveFailures,
67
+ endTime
68
+ };
69
+ fs.writeFileSync(workerDataPath, JSON.stringify(workerData));
70
+
71
+ // Stats display
72
+ let lastUpdate = 0;
73
+ const updateInterval = 100;
74
+ let spinner = ['-', '\\', '|', '/'], spin = 0;
75
+
76
+ const updateStats = () => {
77
+ const now = performance.now();
78
+ if (now - lastUpdate < updateInterval) return;
79
+ lastUpdate = now;
80
+
81
+ const elapsed = (now - startTime) / 1000;
82
+ const remaining = Math.max(0, (endTime - now) / 1000);
83
+ const rate = totalTests / elapsed;
84
+
85
+ const tab = ` \u001b[90m${spinner[spin++ % 4]}\u001b[0m ` +
86
+ table(totalTests, passes, fails, runtimeErrors, wasmErrors, compileErrors, timeouts, todos);
87
+
88
+ process.stdout.write(`\r\u001b[K${tab} | ${rate.toFixed(0)}/s | ${remaining.toFixed(1)}s left`);
89
+ };
90
+
91
+ // Spawn workers with timeout handling like test262
92
+ const workers = [];
93
+
94
+ const spawn = (index) => {
95
+ const worker = cluster.fork();
96
+ workers[index] = worker;
97
+
98
+ let timeout;
99
+ const enqueue = () => {
100
+ if (performance.now() >= endTime) {
101
+ worker.kill();
102
+ return;
103
+ }
104
+
105
+ if (timeout) clearTimeout(timeout);
106
+
107
+ worker.send(null);
108
+ timeout = setTimeout(() => {
109
+ worker.kill();
110
+
111
+ totalTests++;
112
+ timeouts++;
113
+ errorCounts.set('timeout', (errorCounts.get('timeout') || 0) + 1);
114
+
115
+ if (saveFailures) {
116
+ const timestamp = Date.now();
117
+ const filename = `fuzz/failure_${timestamp}_timeout.js`;
118
+ fs.writeFileSync(filename, 'timeout');
119
+ failureFiles.push(filename);
120
+ }
121
+
122
+ updateStats();
123
+ spawn(index); // Respawn worker
124
+ }, 1000);
125
+ };
126
+
127
+ worker.on('message', msg => {
128
+ if (msg === null) {
129
+ enqueue();
130
+ return;
131
+ }
132
+
133
+ if (timeout) clearTimeout(timeout);
134
+
135
+ totalTests++;
136
+
137
+ // result encoding: pass=0, todo=1, wasmError=2, compileError=3, fail=4, timeout=5, runtimeError=6
138
+ const result = msg.result;
139
+
140
+ if (msg.error) {
141
+ const error = `${msg.error.name}: ${msg.error.message}`
142
+ .replace(/\([0-9]+:[0-9]+\)/, '')
143
+ .replace(/@\+[0-9]+/, '');
144
+ errorCounts.set(error, (errorCounts.get(error) || 0) + 1);
145
+ }
146
+
147
+ if (result === 0) {
148
+ passes++;
149
+ } else if (result === 1) {
150
+ todos++;
151
+ } else if (result === 2) {
152
+ wasmErrors++;
153
+ } else if (result === 3) {
154
+ compileErrors++;
155
+ } else if (result === 4) {
156
+ fails++;
157
+ } else if (result === 6) {
158
+ runtimeErrors++;
159
+ }
160
+
161
+ if (msg.code && (result !== 0 || verbose)) {
162
+ if (saveFailures && result !== 0) {
163
+ const timestamp = Date.now();
164
+ const resultNames = ['pass', 'todo', 'wasmError', 'compileError', 'fail', 'timeout', 'runtimeError'];
165
+ const filename = `fuzz/failure_${timestamp}_${resultNames[result]}.js`;
166
+ fs.writeFileSync(filename, msg.code);
167
+ failureFiles.push(filename);
168
+ }
169
+
170
+ if (verbose && result !== 0) {
171
+ const resultNames = ['PASS', 'TODO', 'WASM_ERROR', 'COMPILE_ERROR', 'FAIL', 'TIMEOUT', 'RUNTIME_ERROR'];
172
+ console.log('---');
173
+ console.log(`\n[${resultNames[result]}] ${msg.error?.message || 'Unknown error'}`);
174
+ console.log(msg.code);
175
+ console.log('---');
176
+ }
177
+ }
178
+
179
+ updateStats();
180
+ enqueue();
181
+ });
182
+ };
183
+
184
+ for (let i = 0; i < threads; i++) {
185
+ spawn(i);
186
+ }
187
+
188
+ // Handle cleanup
189
+ const cleanup = () => {
190
+ workers.forEach(worker => worker.kill());
191
+
192
+ console.log('\u001b[2F\u001b[0J\u001b[1m\u001b[4mfuzzing complete\u001b[0m');
193
+
194
+ console.log(table(totalTests, passes, fails, runtimeErrors, wasmErrors, compileErrors, timeouts, todos) + '\n\n');
195
+
196
+ if (errorCounts.size > 0) {
197
+ const sortedErrors = [...errorCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
198
+ sortedErrors.forEach(([error, count]) => {
199
+ console.log(`\u001b[90m${((count / totalTests) * 100).toPrecision(2).padStart(6, ' ')}%\u001b[0m \u001b[1m${error}\u001b[0m`);
200
+ });
201
+ }
202
+
203
+ if (failureFiles.length > 0) {
204
+ console.log(`\nFailure files saved: ${failureFiles.length}`);
205
+ failureFiles.slice(0, 5).forEach(file => console.log(` ${file}`));
206
+ if (failureFiles.length > 5) console.log(` ... and ${failureFiles.length - 5} more`);
207
+ }
208
+
209
+ const elapsed = (performance.now() - startTime) / 1000;
210
+ console.log(`\n\u001b[90mtook ${elapsed.toFixed(1)}s (${(totalTests / elapsed).toFixed(0)} tests/s)\u001b[0m`);
211
+
212
+ process.exit(0);
213
+ };
214
+
215
+ // Auto-stop after duration
216
+ if (duration !== Infinity) setTimeout(cleanup, duration * 1000);
217
+
218
+ // Handle Ctrl+C
219
+ process.on('SIGINT', cleanup);
220
+ process.on('SIGTERM', cleanup);
221
+ } else {
222
+ // Worker process
223
+ let { maxStatements, maxFunctions, verbose, saveFailures, endTime } = JSON.parse(fs.readFileSync(workerDataPath, 'utf8'));
224
+ endTime ??= Infinity;
225
+ const compile = (await import('../compiler/wrap.js')).default;
226
+
227
+ process.on('message', () => {
228
+ if (performance.now() >= endTime) {
229
+ process.exit(0);
230
+ }
231
+
232
+ const code = generateProgram({ maxStatements, maxFunctions });
233
+
234
+ let result = 0; // pass
235
+ let error = null;
236
+ let stage = 0;
237
+
238
+ try {
239
+ const out = compile(code, false, {});
240
+
241
+ try {
242
+ out.exports.main();
243
+ stage = 2;
244
+ result = 0; // pass
245
+ } catch (runtimeError) {
246
+ stage = 1;
247
+ error = runtimeError;
248
+ result = 6; // runtime error
249
+ }
250
+ } catch (compileError) {
251
+ stage = 0;
252
+ error = compileError;
253
+
254
+ // Classify compile errors same as test262
255
+ if (compileError.name === 'CompileError') {
256
+ result = 2; // wasm compile error
257
+ } else {
258
+ result = 3; // compile error
259
+ }
260
+ }
261
+
262
+ process.send({
263
+ result,
264
+ error: error ? { name: error.name, message: error.message } : null,
265
+ code: verbose || saveFailures ? code : null
266
+ });
267
+ });
268
+
269
+ process.send(null);
270
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "porffor",
3
3
  "description": "An ahead-of-time JavaScript compiler",
4
- "version": "0.58.14",
4
+ "version": "0.58.16",
5
5
  "author": "Oliver Medhurst <honk@goose.icu>",
6
6
  "license": "MIT",
7
7
  "scripts": {},
package/runtime/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs';
3
- globalThis.version = '0.58.14';
3
+ globalThis.version = '0.58.16';
4
4
 
5
5
  // deno compat
6
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {