Skip to content

Instantly share code, notes, and snippets.

@recmo
Last active August 7, 2025 03:57
Show Gist options
  • Save recmo/56ccd445210e26b6e92f2574f93cb0b3 to your computer and use it in GitHub Desktop.
Save recmo/56ccd445210e26b6e92f2574f93cb0b3 to your computer and use it in GitHub Desktop.
#!/usr/bin/env rust-script
// cargo-deps: thiserror = "2.0"
//!
//! Implements a tail-calling interpreter in Rust.
//!
#![feature(explicit_tail_calls)]
use std::mem::transmute;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum InterpreterError {
#[error("Stack underflow")]
StackUnderflow,
}
/// This should compile to the minimal inlined asm sequence
///
/// x86_64:
/// ```asm
/// mov rax, qword ptr [rdi + 8]
/// add rdi, 8
/// jmp rax
/// ```
///
/// aarch64:
/// ```asm
/// ldr x2, [x0, #8]!
/// br x2
/// ```
#[inline(always)]
pub fn dispatch(mut pc: *const u64, stack: &mut Vec<u64>) -> Result<(), InterpreterError> {
let handler: fn(*const u64, &mut Vec<u64>) -> Result<(), InterpreterError> =
unsafe { transmute(*pc) };
pc = unsafe { pc.add(1) };
become handler(pc, stack)
}
macro_rules! imm {
($pc:ident) => {
unsafe {
let value = $pc.read();
$pc = $pc.add(1);
value
}
};
}
pub fn stop(pc: *const u64, stack: &mut Vec<u64>) -> Result<(), InterpreterError> {
Ok(())
}
pub fn nop(mut pc: *const u64, stack: &mut Vec<u64>) -> Result<(), InterpreterError> {
become dispatch(pc, stack)
}
pub fn immediate(mut pc: *const u64, stack: &mut Vec<u64>) -> Result<(), InterpreterError> {
let a = imm!(pc);
stack.push(a);
become dispatch(pc, stack)
}
pub fn add(mut pc: *const u64, stack: &mut Vec<u64>) -> Result<(), InterpreterError> {
let a = stack.pop().ok_or(InterpreterError::StackUnderflow)?;
let b = stack.pop().ok_or(InterpreterError::StackUnderflow)?;
stack.push(a + b);
become dispatch(pc, stack)
}
pub fn print(mut pc: *const u64, stack: &mut Vec<u64>) -> Result<(), InterpreterError> {
let a = stack.pop().ok_or(InterpreterError::StackUnderflow)?;
eprintln!("Value: {}", a);
become dispatch(pc, stack)
}
pub fn main() {
let code = [
immediate as u64,
42,
immediate as u64,
1337,
add as u64,
print as u64,
stop as u64,
];
let mut stack = Vec::new();
dispatch(code.as_ptr(), &mut stack).unwrap_or_else(|e| eprintln!("Interpreter error: {}", e));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment