porffor 0.58.15 → 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.
@@ -4583,6 +4583,7 @@ const generateConditional = (scope, decl) => {
4583
4583
  );
4584
4584
 
4585
4585
  out.push([ Opcodes.end ]);
4586
+ depth.pop();
4586
4587
  inferBranchEnd(scope);
4587
4588
 
4588
4589
  return out;
@@ -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.15",
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.15';
3
+ globalThis.version = '0.58.16';
4
4
 
5
5
  // deno compat
6
6
  if (typeof process === 'undefined' && typeof Deno !== 'undefined') {
package/AGENT.md DELETED
@@ -1,13 +0,0 @@
1
- you are an assistant coder for Porffor - a WIP js/ts -> wasm/native ahead-of-time compiler. the codebase is written in js and ts.
2
-
3
- IMPORTANT:
4
- - built-ins apis (Date, String.prototype, etc) are written in ts inside `compiler/builtins`
5
- - once you update a file inside that directory, you MUST run `./porf precompile` to compile to make your changes effective
6
-
7
- test your work using the test262 tools available, iterate independently but do not get stuck on one chain of thought or approach. paths for test262 tools should be relative to the 'test262/test' directory (e.g., 'built-ins/RegExp' instead of 'test262/test/built-ins/RegExp').
8
-
9
- - always use single quotes instead of double quotes (unless needed)
10
- - after finishing, check if your work/diff could be simplified
11
- - to just evaluate code in the engine, use the command: `./porf -p "..."`
12
- - to get an overview of the most failing directories, use the command: `node test262/fails.cjs`
13
- - when working in `compiler/builtins`, inline code instead of using utility/helper functions
package/foo.js DELETED
@@ -1,22 +0,0 @@
1
- // (() => {
2
- // let y = Math.random();
3
- // let x = 0;
4
- // if (x === 0) {
5
- // x = 1337;
6
- // } else {
7
- // x = 42;
8
- // }
9
- // Porffor.log(x);
10
- // })()
11
-
12
- const fn = function() {}
13
-
14
- class C {
15
- async *m() { return 42; } a; b = 42;
16
- c = fn;
17
-
18
- }
19
-
20
- new C();
21
-
22
- console.log(Object.prototype.hasOwnProperty.call(C, "a"))