Skip to content

Commit 3aca5f4

Browse files
committed
Switch from tree-walking to bytecode VM for ALL execution
1 parent c01b628 commit 3aca5f4

File tree

4 files changed

+173
-32
lines changed

4 files changed

+173
-32
lines changed

src/compiler.rs

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,68 @@ impl Compiler {
692692
self.compile_expr(&args[args.len() - 1], dest, tail_pos)
693693
}
694694

695+
/// Try to compile a binary operation using specialized opcodes
696+
/// Returns Some(true) if compiled, Some(false) if not applicable, Err on error
697+
fn try_compile_binary_op(&mut self, op: &str, args: &[Value], dest: Reg) -> Result<Option<bool>, String> {
698+
// Binary arithmetic/comparison operators
699+
if args.len() == 2 {
700+
let make_binary_op: Option<fn(Reg, Reg, Reg) -> Op> = match op {
701+
"+" => Some(Op::Add),
702+
"-" => Some(Op::Sub),
703+
"*" => Some(Op::Mul),
704+
"/" => Some(Op::Div),
705+
"mod" => Some(Op::Mod),
706+
"<" => Some(Op::Lt),
707+
"<=" => Some(Op::Le),
708+
">" => Some(Op::Gt),
709+
">=" => Some(Op::Ge),
710+
"=" => Some(Op::Eq),
711+
"!=" => Some(Op::Ne),
712+
_ => None,
713+
};
714+
715+
if let Some(make_op) = make_binary_op {
716+
// Compile both arguments
717+
let a_reg = self.alloc_reg();
718+
self.compile_expr(&args[0], a_reg, false)?;
719+
let b_reg = self.alloc_reg();
720+
self.compile_expr(&args[1], b_reg, false)?;
721+
722+
// Emit the operation
723+
self.emit(make_op(dest, a_reg, b_reg));
724+
725+
// Free temp registers
726+
self.free_reg();
727+
self.free_reg();
728+
729+
return Ok(Some(true));
730+
}
731+
}
732+
733+
// Unary operators
734+
if args.len() == 1 {
735+
if op == "not" {
736+
let a_reg = self.alloc_reg();
737+
self.compile_expr(&args[0], a_reg, false)?;
738+
self.emit(Op::Not(dest, a_reg));
739+
self.free_reg();
740+
return Ok(Some(true));
741+
}
742+
743+
// Unary minus: (- x)
744+
if op == "-" {
745+
let a_reg = self.alloc_reg();
746+
self.compile_expr(&args[0], a_reg, false)?;
747+
self.emit(Op::Neg(dest, a_reg));
748+
self.free_reg();
749+
return Ok(Some(true));
750+
}
751+
}
752+
753+
// Not a specialized operation
754+
Ok(None)
755+
}
756+
695757
fn compile_call(&mut self, items: &[Value], dest: Reg, tail_pos: bool) -> Result<(), String> {
696758
// Try constant folding for the entire call expression (including pure user functions)
697759
let call_expr = Value::list(items.to_vec());
@@ -701,6 +763,15 @@ impl Compiler {
701763
return Ok(());
702764
}
703765

766+
// Try to compile as specialized binary operation
767+
if let Some(op) = items[0].as_symbol() {
768+
if let Some(result) = self.try_compile_binary_op(op, &items[1..], dest)? {
769+
if result {
770+
return Ok(());
771+
}
772+
}
773+
}
774+
704775
let func_reg = self.alloc_reg();
705776

706777
// Compile function
@@ -863,11 +934,15 @@ mod tests {
863934

864935
#[test]
865936
fn test_no_fold_with_variables() {
866-
// If any arg is not a constant, don't fold
937+
// If any arg is not a constant, don't fold to LoadConst
867938
let chunk = compile_str("(+ x 1)").unwrap();
868-
// Should have a call, not just LoadConst
869-
let has_call = chunk.code.iter().any(|op| matches!(op, Op::Call(_, _, _) | Op::TailCall(_, _)));
870-
assert!(has_call);
939+
// Should use Op::Add (specialized opcode), not fold to LoadConst
940+
let has_add = chunk.code.iter().any(|op| matches!(op, Op::Add(_, _, _)));
941+
assert!(has_add, "Expected Op::Add for (+ x 1)");
942+
// Should NOT have LoadConst as first instruction (that would mean folding)
943+
// First instruction should be GetGlobal for 'x'
944+
let first_is_get_global = matches!(chunk.code[0], Op::GetGlobal(_, _));
945+
assert!(first_is_get_global, "First op should be GetGlobal for variable x");
871946
}
872947

873948
#[test]

src/lib.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,25 @@ pub mod parser;
66
pub mod value;
77
pub mod vm;
88

9+
pub use compiler::Compiler;
910
pub use eval::{eval, standard_env, Env};
1011
pub use macros::{expand, MacroRegistry};
1112
pub use parser::{parse, parse_all};
1213
pub use value::Value;
14+
pub use vm::{standard_vm, VM};
1315

14-
/// Convenience function to evaluate a string in a standard environment
16+
/// Convenience function to evaluate a string using the bytecode VM
1517
pub fn run(input: &str) -> Result<Value, String> {
1618
let expr = parse(input)?;
17-
let env = standard_env();
18-
eval(&expr, &env)
19+
let chunk = Compiler::compile(&expr)?;
20+
let mut vm = standard_vm();
21+
vm.run(chunk)
1922
}
2023

21-
/// Convenience function to evaluate multiple expressions
24+
/// Convenience function to evaluate multiple expressions using the bytecode VM
2225
pub fn run_all(input: &str) -> Result<Value, String> {
2326
let exprs = parse_all(input)?;
24-
let env = standard_env();
25-
let mut result = Value::Nil;
26-
for expr in exprs {
27-
result = eval(&expr, &env)?;
28-
}
29-
Ok(result)
27+
let chunk = Compiler::compile_all(&exprs)?;
28+
let mut vm = standard_vm();
29+
vm.run(chunk)
3030
}

src/main.rs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use lisp_vm::{eval, expand, parse, parse_all, standard_env, MacroRegistry, Value};
1+
use lisp_vm::{expand, parse, parse_all, standard_env, standard_vm, Compiler, MacroRegistry, Value};
22
use std::env;
33
use std::fs;
44
use std::io::{self, BufRead, Write};
@@ -25,25 +25,38 @@ fn run_file(filename: &str) -> Result<Value, String> {
2525
.map_err(|e| format!("Could not read file '{}': {}", filename, e))?;
2626

2727
let exprs = parse_all(&contents)?;
28+
29+
// For macro expansion, we still need the tree-walking environment
2830
let env = standard_env();
2931
let macros = MacroRegistry::new();
3032

31-
let mut result = Value::Nil;
32-
for expr in exprs {
33-
let expanded = expand(&expr, &macros, &env)?;
34-
result = eval(&expanded, &env)?;
35-
}
33+
// Expand macros on all expressions first
34+
let expanded: Result<Vec<Value>, String> = exprs
35+
.iter()
36+
.map(|expr| expand(expr, &macros, &env))
37+
.collect();
38+
let expanded = expanded?;
39+
40+
// Compile all expressions to bytecode
41+
let chunk = Compiler::compile_all(&expanded)?;
3642

37-
Ok(result)
43+
// Execute via bytecode VM
44+
let mut vm = standard_vm();
45+
vm.run(chunk)
3846
}
3947

4048
fn run_repl() {
41-
println!("Lisp VM v0.1.0");
49+
println!("Lisp VM v0.1.0 (bytecode)");
4250
println!("Type :q or :quit to exit.");
4351
println!();
4452

53+
// For macro expansion, we still need the tree-walking environment
4554
let env = standard_env();
4655
let macros = MacroRegistry::new();
56+
57+
// Create a single VM instance to maintain globals across expressions
58+
let mut vm = standard_vm();
59+
4760
let stdin = io::stdin();
4861
let mut stdout = io::stdout();
4962

@@ -66,17 +79,26 @@ fn run_repl() {
6679

6780
match parse(line) {
6881
Ok(expr) => {
69-
// First expand macros, then evaluate
82+
// First expand macros
7083
match expand(&expr, &macros, &env) {
71-
Ok(expanded) => match eval(&expanded, &env) {
72-
Ok(result) => {
73-
// Don't display nil results (common REPL behavior)
74-
if !result.is_nil() {
75-
println!("{}", result);
84+
Ok(expanded) => {
85+
// Compile to bytecode
86+
match Compiler::compile(&expanded) {
87+
Ok(chunk) => {
88+
// Execute via VM
89+
match vm.run(chunk) {
90+
Ok(result) => {
91+
// Don't display nil results (common REPL behavior)
92+
if !result.is_nil() {
93+
println!("{}", result);
94+
}
95+
}
96+
Err(e) => eprintln!("Error: {}", e),
97+
}
7698
}
99+
Err(e) => eprintln!("Compile error: {}", e),
77100
}
78-
Err(e) => eprintln!("Error: {}", e),
79-
},
101+
}
80102
Err(e) => eprintln!("Macro error: {}", e),
81103
}
82104
}

src/vm.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use std::cell::RefCell;
44
use std::collections::HashMap;
55
use std::rc::Rc;
66

7-
const MAX_REGISTERS: usize = 256;
8-
const MAX_FRAMES: usize = 256;
7+
const MAX_REGISTERS: usize = 8192; // Increased for deep recursion (fib needs ~30 depth)
8+
const MAX_FRAMES: usize = 1024;
99

1010
#[derive(Clone)]
1111
struct CallFrame {
@@ -565,6 +565,50 @@ pub fn standard_vm() -> VM {
565565
Ok(Value::Nil)
566566
}));
567567

568+
// Type predicates
569+
vm.define_global("nil?", native("nil?", |args| {
570+
if args.len() != 1 { return Err("nil? expects 1 argument".to_string()); }
571+
Ok(Value::Bool(matches!(args[0], Value::Nil)))
572+
}));
573+
574+
vm.define_global("int?", native("int?", |args| {
575+
if args.len() != 1 { return Err("int? expects 1 argument".to_string()); }
576+
Ok(Value::Bool(matches!(args[0], Value::Int(_))))
577+
}));
578+
579+
vm.define_global("float?", native("float?", |args| {
580+
if args.len() != 1 { return Err("float? expects 1 argument".to_string()); }
581+
Ok(Value::Bool(matches!(args[0], Value::Float(_))))
582+
}));
583+
584+
vm.define_global("string?", native("string?", |args| {
585+
if args.len() != 1 { return Err("string? expects 1 argument".to_string()); }
586+
Ok(Value::Bool(matches!(args[0], Value::String(_))))
587+
}));
588+
589+
vm.define_global("list?", native("list?", |args| {
590+
if args.len() != 1 { return Err("list? expects 1 argument".to_string()); }
591+
Ok(Value::Bool(matches!(args[0], Value::List(_))))
592+
}));
593+
594+
vm.define_global("fn?", native("fn?", |args| {
595+
if args.len() != 1 { return Err("fn? expects 1 argument".to_string()); }
596+
Ok(Value::Bool(matches!(args[0], Value::Function(_) | Value::NativeFunction(_) | Value::CompiledFunction(_))))
597+
}));
598+
599+
vm.define_global("symbol?", native("symbol?", |args| {
600+
if args.len() != 1 { return Err("symbol? expects 1 argument".to_string()); }
601+
Ok(Value::Bool(matches!(args[0], Value::Symbol(_))))
602+
}));
603+
604+
// Symbol operations (useful for macros)
605+
vm.define_global("gensym", native("gensym", |_args| {
606+
use std::sync::atomic::{AtomicU64, Ordering};
607+
static COUNTER: AtomicU64 = AtomicU64::new(0);
608+
let id = COUNTER.fetch_add(1, Ordering::SeqCst);
609+
Ok(Value::symbol(&format!("G__{}", id)))
610+
}));
611+
568612
vm
569613
}
570614

0 commit comments

Comments
 (0)