Download Game! Currently 71 players and visitors. Last logged in:GlauriCordobaDefaultCorrel

Blitzer's Blog >> 72243

Back to blogs index
Posted: 10 Sep 2026 10:37 [ permalink ]
To implement both Graceful Error Recovery (try/catch) and System Interrupts,
we are moving the virtual machine from a simple script executor into the realm
of true operating system-level architecture.
Here is how we implement an Exception Handler Stack for try/catch, and a
Lock-Free Interrupt Vector for hardware-style signal handling.
Part 1: The Exception Handler Stack (try / catch)
When a VM executes a try block, it doesn't just evaluate the code. It
registers a "save state" (a snapshot of the current call stack depth, data
stack depth, and environment). If an error occurs deeply nested inside a
function call, the VM "unwinds" the call stack back to this save state, pushes
the error message, and jumps to the catch block.
1. Expanding the ISA
const Opcodes = Object.assign(Opcodes || {}, {
  TRY: 'OP_TRY',         // Pushes a recovery state to the Exception Stack
  END_TRY: 'OP_END_TRY', // Pops the recovery state (on success)
  THROW: 'OP_THROW'      // Unwinds stacks and jumps to the nearest catch
address
});

2. The Compiler: Lowering try/catch
We introduce the AST structure: ["try", tryBody, "errorVar", catchBody].

3. The VM Execution
We add a tryStack to the VM state. We must also update the VM's internal error
generation (like TypeErrors) to use OP_THROW under the hood instead of
crashing the Node.js thread.


Now, if you hit a divide-by-zero, instead of throw new Error(...) in
JavaScript, you call if (!handleThrow("Divide by zero")) return;, and the
JSON-Lisp program will gracefully catch its own fault.
Part 2: System Interrupts
Hardware CPUs have an "Interrupt Request" (IRQ) line. When voltage is applied,
the CPU pauses its current program, jumps to an Interrupt Service Routine
(ISR), and then resumes.
In our architecture, the Main Thread is the hardware, and the Worker Thread is
the CPU. We can expand our lock-free buffer to allow the gateway to signal the
running program asynchronously.
1. Expanding the Lock-Free Buffer
We expand the SharedArrayBuffer from 12 bytes to 16 bytes.
Index 3 (bytes 12-15) becomes the Interrupt Flag.
/ Main Thread triggers an interrupt:
/ 1 = Graceful Shutdown Request, 2 = Memory Warning, 3 = Custom Signal
Atomics.store(sab_int32, 3, 1);

2. The VM Interrupt Vector
Inside the Worker Thread, we do not need a new opcode to check for interrupts.
The VM natively polls the buffer every few instructions. If the flag is
raised, the VM triggers a forced jump to a registered handler.
/ Add an opcode to register the ISR
const Opcodes = Object.assign(Opcodes || {}, {
  REGISTER_ISR: 'OP_REGISTER_ISR' // Pops a function and saves it as the
interrupt handler
});

/ Inside executeVMWorker:
let isrCodeObject = null;

while (callStack.length > 0) {
  // Check the lock-free interrupt flag every 10 instructions
  if (instructionsRun++ % 10 === 0) {
    const interruptSignal = Atomics.load(stateBuffer.int32, 3);
    
    if (interruptSignal > 0) {
      // Clear the interrupt flag atomically so we don't infinitely loop
      Atomics.store(stateBuffer.int32, 3, 0); 
      
      if (isrCodeObject) {
        // We have an ISR registered! We inject a Call Frame dynamically.
        // We push the signal code so the ISR knows what happened.
        dataStack.push(interruptSignal); 
        
        const isrEnv = new Env(isrCodeObject.closureEnv);
        isrEnv.define(isrCodeObject.params[0], dataStack.pop()); // Bind the
signal code
        
        // Push the interrupt frame ON TOP of the current execution.
        // When it finishes (RET), it will naturally resume the paused
program!
        callStack.push({ ip: 0, code: isrCodeObject.code, env: isrEnv });
        continue;
      } else {
        // Unhandled system interrupt! (e.g., Gateway demands graceful
shutdown)
        stateBuffer.writeState(5, interruptSignal); // Status 5: Interrupted
        return;
      }
    }
  }
  
  // ... rest of the VM loop ...
}

Writing an Interrupt-Aware Payload
This allows the JSON-Lisp program to define its own logic for what happens
when the Fastify API gateway tells it to cancel early or adjust its behavior
on the fly.
[
  "do",
  [
    "def", "handle_interrupt", ["signal_code"],
    [
      "if", ["=", "signal_code", 1],
      ["yield", ["str", "Gracefully wrapping up..."]],
      ["yield", ["str", "Ignoring unknown signal"]]
    ]
  ],
  ["register-isr", "handle_interrupt"],
  
  // ... enter massive computation loop ...
]

When the gateway executes Atomics.store(sab, 3, 1), the VM suspends the
computation loop, executes handle_interrupt, explicitly yields the cleanup
message back to the gateway, and then seamlessly resumes exactly where it left
off.