Skip to content

Commit a2fe7ab

Browse files
committed
Uses ABx format with 16-bit signed offset
Optimizing cons-based list processing where nil marks end-of-list Only handles nil? (not empty?) due to different semantics for arrays
1 parent 174be98 commit a2fe7ab

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

src/bytecode.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ impl std::fmt::Debug for Op {
6868
Self::JUMP_IF_LE_IMM => write!(f, "JumpIfLeImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
6969
Self::JUMP_IF_GT_IMM => write!(f, "JumpIfGtImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
7070
Self::JUMP_IF_GE_IMM => write!(f, "JumpIfGeImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
71+
Self::JUMP_IF_NIL => write!(f, "JumpIfNil({}, {})", self.a(), self.sbx()),
72+
Self::JUMP_IF_NOT_NIL => write!(f, "JumpIfNotNil({}, {})", self.a(), self.sbx()),
7173
_ => write!(f, "Unknown(0x{:08x})", self.0),
7274
}
7375
}
@@ -124,6 +126,9 @@ impl Op {
124126
pub const JUMP_IF_LE_IMM: u8 = 45; // ABC: src, imm (i8), offset (i8) - jump if src <= imm
125127
pub const JUMP_IF_GT_IMM: u8 = 46; // ABC: src, imm (i8), offset (i8) - jump if src > imm
126128
pub const JUMP_IF_GE_IMM: u8 = 47; // ABC: src, imm (i8), offset (i8) - jump if src >= imm
129+
// Specialized nil check opcodes (common in list processing)
130+
pub const JUMP_IF_NIL: u8 = 48; // A: src, sBx: offset - jump if src is nil
131+
pub const JUMP_IF_NOT_NIL: u8 = 49; // A: src, sBx: offset - jump if src is NOT nil
127132

128133
// ========== Constructors ==========
129134

@@ -429,6 +434,17 @@ impl Op {
429434
Self::abc(Self::JUMP_IF_GE_IMM, src, imm as u8, offset as u8)
430435
}
431436

437+
// Specialized nil check (uses ABx format like JumpIfFalse for 16-bit offset)
438+
#[inline(always)]
439+
pub const fn jump_if_nil(src: Reg, offset: Offset) -> Self {
440+
Self::asbx(Self::JUMP_IF_NIL, src, offset)
441+
}
442+
443+
#[inline(always)]
444+
pub const fn jump_if_not_nil(src: Reg, offset: Offset) -> Self {
445+
Self::asbx(Self::JUMP_IF_NOT_NIL, src, offset)
446+
}
447+
432448
// ========== Jump patching helpers ==========
433449

434450
/// Check if this is a jump instruction (for patching)
@@ -440,6 +456,7 @@ impl Op {
440456
|| op == Self::JUMP_IF_GT || op == Self::JUMP_IF_GE
441457
|| op == Self::JUMP_IF_LT_IMM || op == Self::JUMP_IF_LE_IMM
442458
|| op == Self::JUMP_IF_GT_IMM || op == Self::JUMP_IF_GE_IMM
459+
|| op == Self::JUMP_IF_NIL || op == Self::JUMP_IF_NOT_NIL
443460
}
444461

445462
/// Patch the offset of a jump instruction

src/compiler.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,11 @@ impl Compiler {
511511
return self.compile_if_branches(args, dest, tail_pos, jump_to_else);
512512
}
513513

514+
// Try to use specialized nil check opcodes
515+
if let Some(jump_to_else) = self.try_compile_nil_check_jump(&args[0], dest)? {
516+
return self.compile_if_branches(args, dest, tail_pos, jump_to_else);
517+
}
518+
514519
// Fallback: compile condition into dest and use JumpIfFalse
515520
self.compile_expr(&args[0], dest, false)?;
516521

@@ -616,6 +621,38 @@ impl Compiler {
616621
Ok(Some(jump_pos))
617622
}
618623

624+
/// Try to compile a nil check condition as a specialized jump opcode.
625+
/// Returns Some(jump_pos) if successful, None if condition is not a nil check.
626+
///
627+
/// Handles pattern:
628+
/// - `(nil? x)` → JumpIfNotNil (jump to else if x is NOT nil)
629+
///
630+
/// Note: We don't optimize `(empty? x)` because it handles array-based lists
631+
/// differently (empty array [] is not nil but is empty).
632+
fn try_compile_nil_check_jump(&mut self, cond: &Value, dest: Reg) -> Result<Option<usize>, String> {
633+
let items = match cond.as_list() {
634+
Some(items) if items.len() == 2 => items,
635+
_ => return Ok(None),
636+
};
637+
638+
// Only optimize nil? - not empty? which has different semantics for array lists
639+
if items[0].as_symbol() != Some("nil?") {
640+
return Ok(None);
641+
}
642+
643+
let arg = &items[1];
644+
645+
// Compile argument into dest
646+
self.compile_expr(arg, dest, false)?;
647+
648+
// For `(if (nil? x) then else)`, we jump to else when condition is FALSE.
649+
// Condition is TRUE when x IS nil.
650+
// So we jump to else when x is NOT nil.
651+
let jump_pos = self.emit(Op::jump_if_not_nil(dest, 0));
652+
653+
Ok(Some(jump_pos))
654+
}
655+
619656
fn compile_def(&mut self, args: &[Value], dest: Reg) -> Result<(), String> {
620657
if args.len() != 2 {
621658
return Err("def expects exactly 2 arguments".to_string());

src/vm.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,25 @@ impl VM {
889889
}
890890
}
891891

892+
// Specialized nil check opcodes
893+
Op::JUMP_IF_NIL => {
894+
let src = instr.a();
895+
let offset = instr.sbx();
896+
let v = unsafe { self.registers.get_unchecked(base + src as usize) };
897+
if v.is_nil() {
898+
ip = (ip as isize + offset as isize) as usize;
899+
}
900+
}
901+
902+
Op::JUMP_IF_NOT_NIL => {
903+
let src = instr.a();
904+
let offset = instr.sbx();
905+
let v = unsafe { self.registers.get_unchecked(base + src as usize) };
906+
if !v.is_nil() {
907+
ip = (ip as isize + offset as isize) as usize;
908+
}
909+
}
910+
892911
_ => {
893912
return Err(format!("Unknown opcode: {}", instr.opcode()));
894913
}
@@ -1488,4 +1507,36 @@ mod tests {
14881507
let result = vm_eval("(do (def sum (fn (n acc) (if (<= n 0) acc (sum (- n 1) (+ acc n))))) (sum 100 0))").unwrap();
14891508
assert_eq!(result, Value::Int(5050));
14901509
}
1510+
1511+
#[test]
1512+
fn test_nil_check_jump() {
1513+
// Test specialized nil check opcodes
1514+
// These are generated for (if (nil? x) ...) patterns
1515+
1516+
// Direct nil check
1517+
assert_eq!(vm_eval("(if (nil? nil) 1 2)").unwrap(), Value::Int(1));
1518+
assert_eq!(vm_eval("(if (nil? 42) 1 2)").unwrap(), Value::Int(2));
1519+
1520+
// Variable nil check
1521+
assert_eq!(vm_eval("(let (x nil) (if (nil? x) 1 2))").unwrap(), Value::Int(1));
1522+
assert_eq!(vm_eval("(let (x 42) (if (nil? x) 1 2))").unwrap(), Value::Int(2));
1523+
1524+
// Recursive list processing using nil check (with cons-based lists)
1525+
let result = vm_eval("(do
1526+
(def list-length (fn (lst)
1527+
(if (nil? lst)
1528+
0
1529+
(+ 1 (list-length (cdr lst))))))
1530+
(list-length (cons 1 (cons 2 (cons 3 (cons 4 (cons 5 nil)))))))").unwrap();
1531+
assert_eq!(result, Value::Int(5));
1532+
1533+
// List sum using nil? (with cons-based lists)
1534+
let result = vm_eval("(do
1535+
(def list-sum (fn (lst acc)
1536+
(if (nil? lst)
1537+
acc
1538+
(list-sum (cdr lst) (+ acc (car lst))))))
1539+
(list-sum (cons 1 (cons 2 (cons 3 (cons 4 (cons 5 nil))))) 0))").unwrap();
1540+
assert_eq!(result, Value::Int(15));
1541+
}
14911542
}

0 commit comments

Comments
 (0)