Polling... Status: 0, Val: 0
Execution ended. Final captured state: { status: 0, value: 0 }
user@elli:~/Downloads/Remote/AST$
==
You hit a classic Node.js concurrency "gotcha"!
Status 0 means the buffer was in its initial Init state. The worker thread
never even wrote its first instruction.
Why did this happen?
Spinning up a new V8 isolate (a Worker Thread) in Node.js has a cold-boot
overhead of about 30 to 50 milliseconds. Because our DEADLINE_MS was set to
10, the main thread aggressively killed the worker before it even had a chance
to start executing the VM loop!
To see the engine actually crunch the numbers and report intermediate states,
we need to do two things:
* Increase the deadline to account for the worker boot time.
* Provide a real heavyPayload (an infinite recursive counter) so we have
something to observe.
Here is the exact patch to get the numbers flowing.
1. Add the < (Less Than) Operator
To write our recursive loop, we need a comparison operator. Add LT to your
Opcodes at the top:
const Opcodes = {
// ... existing opcodes
LT: 'OP_LT', // Add this
// ...
In compile() (around the +, *, = checks), add:
else if (op === '<') { bytecode.push({ op: Opcodes.LT }); }
In executeVMWorker()'s switch statement, add:
case Opcodes.LT: {
const right = dataStack.pop();
const left = dataStack.pop();
dataStack.push(left < right);
break;
}
2. The Updated Main/Worker Block
Replace your existing if (isMainThread) block with this. It includes a real
recursive payload and a 500ms deadline.
const { Worker, isMainThread, workerData } = require('worker_threads');
if (isMainThread) {
const sab = new SharedArrayBuffer(12);
const stateBuffer = new StateBuffer(sab);
// A heavy payload: An infinite recursive counter
const heavyPayload = [
"let", [],
[
"def", "count_up", ["n"],
// Keep adding 1 to n forever
["count_up", ["+", "n", 1]]
],
// Start counting at 0
["count_up", 0]
];
const bytecode = compile(heavyPayload);
// We set a gasLimit of 50,000 instructions
const worker = new Worker(__filename, {
workerData: { bytecode, sab, gasLimit: 50000 }
});
const DEADLINE_MS = 500; // Allow 500ms for bootup + execution
const startTime = Date.now();
const pollInterval = setInterval(() => {
const currentState = stateBuffer.readState();
// Only log if the worker has actually started (Status > 0)
if (currentState.status > 0) {
console.log(`Polling... Status: ${currentState.status}, Current N:
${currentState.value}`);
}
if (Date.now() - startTime >= DEADLINE_MS || currentState.status >= 2) {
clearInterval(pollInterval);
worker.terminate();
console.log(`
Execution ended.`);
console.log(`Final captured state:`, stateBuffer.readState());
}
}, 10); // Poll every 10ms
} else {
// --- WORKER THREAD LOGIC ---
const stateBuffer = new StateBuffer(workerData.sab);
executeVMWorker(workerData.bytecode, stateBuffer, workerData.gasLimit);
}
What you will see when you run this:
When you run node mvp.js, you'll see a brief pause (the worker booting), and
then the console will light up with the main thread synchronously reading the
intermediate counts straight out of the shared memory:
Polling... Status: 1, Current N: 1420
Polling... Status: 1, Current N: 2855
Polling... Status: 1, Current N: 4120
...
Execution ended.
Final captured state: { status: 3, value: 8333 }
Notice the final status is 3 (Cancelled/Out of Gas) because the worker
successfully hit the 50,000 instruction gasLimit and gracefully terminated
itself before blowing out the memory stack!