Skip to content

Commit 174be98

Browse files
committed
Register-Register: JumpIfLt(left, right, offset)
Register-Immediate: JumpIfLtImm(src, imm, offset) Uses 8-bit signed offset (127 instructions aprox)
1 parent f6bdade commit 174be98

File tree

3 files changed

+287
-3
lines changed

3 files changed

+287
-3
lines changed

src/bytecode.rs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ impl std::fmt::Debug for Op {
6060
Self::LE_IMM => write!(f, "LeImm({}, {}, {})", self.a(), self.b(), self.c() as i8),
6161
Self::GT_IMM => write!(f, "GtImm({}, {}, {})", self.a(), self.b(), self.c() as i8),
6262
Self::GE_IMM => write!(f, "GeImm({}, {}, {})", self.a(), self.b(), self.c() as i8),
63+
Self::JUMP_IF_LT => write!(f, "JumpIfLt({}, {}, {})", self.a(), self.b(), self.c() as i8),
64+
Self::JUMP_IF_LE => write!(f, "JumpIfLe({}, {}, {})", self.a(), self.b(), self.c() as i8),
65+
Self::JUMP_IF_GT => write!(f, "JumpIfGt({}, {}, {})", self.a(), self.b(), self.c() as i8),
66+
Self::JUMP_IF_GE => write!(f, "JumpIfGe({}, {}, {})", self.a(), self.b(), self.c() as i8),
67+
Self::JUMP_IF_LT_IMM => write!(f, "JumpIfLtImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
68+
Self::JUMP_IF_LE_IMM => write!(f, "JumpIfLeImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
69+
Self::JUMP_IF_GT_IMM => write!(f, "JumpIfGtImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
70+
Self::JUMP_IF_GE_IMM => write!(f, "JumpIfGeImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
6371
_ => write!(f, "Unknown(0x{:08x})", self.0),
6472
}
6573
}
@@ -107,6 +115,15 @@ impl Op {
107115
pub const LE_IMM: u8 = 37; // ABC: dest, src, imm (C is i8) - src <= imm
108116
pub const GT_IMM: u8 = 38; // ABC: dest, src, imm (C is i8) - src > imm
109117
pub const GE_IMM: u8 = 39; // ABC: dest, src, imm (C is i8) - src >= imm
118+
// Combined compare-and-jump opcodes (saves a register + dispatch overhead)
119+
pub const JUMP_IF_LT: u8 = 40; // ABC: left, right, offset (i8) - jump if left < right
120+
pub const JUMP_IF_LE: u8 = 41; // ABC: left, right, offset (i8) - jump if left <= right
121+
pub const JUMP_IF_GT: u8 = 42; // ABC: left, right, offset (i8) - jump if left > right
122+
pub const JUMP_IF_GE: u8 = 43; // ABC: left, right, offset (i8) - jump if left >= right
123+
pub const JUMP_IF_LT_IMM: u8 = 44; // ABC: src, imm (i8), offset (i8) - jump if src < imm
124+
pub const JUMP_IF_LE_IMM: u8 = 45; // ABC: src, imm (i8), offset (i8) - jump if src <= imm
125+
pub const JUMP_IF_GT_IMM: u8 = 46; // ABC: src, imm (i8), offset (i8) - jump if src > imm
126+
pub const JUMP_IF_GE_IMM: u8 = 47; // ABC: src, imm (i8), offset (i8) - jump if src >= imm
110127

111128
// ========== Constructors ==========
112129

@@ -370,22 +387,72 @@ impl Op {
370387
Self::abc(Self::GE_IMM, dest, src, imm as u8)
371388
}
372389

390+
// Combined compare-and-jump (register vs register)
391+
#[inline(always)]
392+
pub const fn jump_if_lt(left: Reg, right: Reg, offset: i8) -> Self {
393+
Self::abc(Self::JUMP_IF_LT, left, right, offset as u8)
394+
}
395+
396+
#[inline(always)]
397+
pub const fn jump_if_le(left: Reg, right: Reg, offset: i8) -> Self {
398+
Self::abc(Self::JUMP_IF_LE, left, right, offset as u8)
399+
}
400+
401+
#[inline(always)]
402+
pub const fn jump_if_gt(left: Reg, right: Reg, offset: i8) -> Self {
403+
Self::abc(Self::JUMP_IF_GT, left, right, offset as u8)
404+
}
405+
406+
#[inline(always)]
407+
pub const fn jump_if_ge(left: Reg, right: Reg, offset: i8) -> Self {
408+
Self::abc(Self::JUMP_IF_GE, left, right, offset as u8)
409+
}
410+
411+
// Combined compare-and-jump (register vs immediate)
412+
#[inline(always)]
413+
pub const fn jump_if_lt_imm(src: Reg, imm: i8, offset: i8) -> Self {
414+
Self::abc(Self::JUMP_IF_LT_IMM, src, imm as u8, offset as u8)
415+
}
416+
417+
#[inline(always)]
418+
pub const fn jump_if_le_imm(src: Reg, imm: i8, offset: i8) -> Self {
419+
Self::abc(Self::JUMP_IF_LE_IMM, src, imm as u8, offset as u8)
420+
}
421+
422+
#[inline(always)]
423+
pub const fn jump_if_gt_imm(src: Reg, imm: i8, offset: i8) -> Self {
424+
Self::abc(Self::JUMP_IF_GT_IMM, src, imm as u8, offset as u8)
425+
}
426+
427+
#[inline(always)]
428+
pub const fn jump_if_ge_imm(src: Reg, imm: i8, offset: i8) -> Self {
429+
Self::abc(Self::JUMP_IF_GE_IMM, src, imm as u8, offset as u8)
430+
}
431+
373432
// ========== Jump patching helpers ==========
374433

375434
/// Check if this is a jump instruction (for patching)
376435
#[inline(always)]
377436
pub const fn is_jump(self) -> bool {
378437
let op = self.opcode();
379438
op == Self::JUMP || op == Self::JUMP_IF_FALSE || op == Self::JUMP_IF_TRUE
439+
|| op == Self::JUMP_IF_LT || op == Self::JUMP_IF_LE
440+
|| op == Self::JUMP_IF_GT || op == Self::JUMP_IF_GE
441+
|| op == Self::JUMP_IF_LT_IMM || op == Self::JUMP_IF_LE_IMM
442+
|| op == Self::JUMP_IF_GT_IMM || op == Self::JUMP_IF_GE_IMM
380443
}
381444

382445
/// Patch the offset of a jump instruction
383446
#[inline(always)]
384447
pub fn patch_offset(&mut self, new_offset: i16) {
385448
let opcode = self.opcode();
386449
let a = self.a();
387-
// Reconstruct with new offset
388-
if opcode == Self::JUMP {
450+
let b = self.b();
451+
// Combined compare-and-jump use ABC format with C as 8-bit offset
452+
if opcode >= Self::JUMP_IF_LT && opcode <= Self::JUMP_IF_GE_IMM {
453+
// For these opcodes: A=left/src, B=right/imm, C=offset (i8)
454+
*self = Self::abc(opcode, a, b, new_offset as i8 as u8);
455+
} else if opcode == Self::JUMP {
389456
*self = Self::make_sbx(opcode, new_offset);
390457
} else {
391458
*self = Self::asbx(opcode, a, new_offset);

src/compiler.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,12 +505,23 @@ impl Compiler {
505505
return Err("if expects 2 or 3 arguments".to_string());
506506
}
507507

508-
// Compile condition into dest
508+
// Try to use combined compare-and-jump opcodes for comparison conditions
509+
if let Some(jump_to_else) = self.try_compile_compare_jump(&args[0], dest)? {
510+
// Successfully emitted a combined compare-and-jump opcode
511+
return self.compile_if_branches(args, dest, tail_pos, jump_to_else);
512+
}
513+
514+
// Fallback: compile condition into dest and use JumpIfFalse
509515
self.compile_expr(&args[0], dest, false)?;
510516

511517
// Jump to else if false
512518
let jump_to_else = self.emit(Op::jump_if_false(dest, 0));
513519

520+
self.compile_if_branches(args, dest, tail_pos, jump_to_else)
521+
}
522+
523+
/// Compile the then/else branches of an if expression
524+
fn compile_if_branches(&mut self, args: &[Value], dest: Reg, tail_pos: bool, jump_to_else: usize) -> Result<(), String> {
514525
// Compile then branch
515526
self.compile_expr(&args[1], dest, tail_pos)?;
516527

@@ -543,6 +554,68 @@ impl Compiler {
543554
Ok(())
544555
}
545556

557+
/// Try to compile a comparison condition as a combined compare-and-jump opcode.
558+
/// Returns Some(jump_pos) if successful, None if condition is not a simple comparison.
559+
///
560+
/// For `(if (< a b) then else)`, we need to jump to else when condition is FALSE.
561+
/// So we emit the OPPOSITE comparison:
562+
/// - `<` → JumpIfGe (jump if a >= b)
563+
/// - `<=` → JumpIfGt (jump if a > b)
564+
/// - `>` → JumpIfLe (jump if a <= b)
565+
/// - `>=` → JumpIfLt (jump if a < b)
566+
fn try_compile_compare_jump(&mut self, cond: &Value, dest: Reg) -> Result<Option<usize>, String> {
567+
let items = match cond.as_list() {
568+
Some(items) if items.len() == 3 => items,
569+
_ => return Ok(None),
570+
};
571+
572+
let op = match items[0].as_symbol() {
573+
Some(s) if s == "<" || s == "<=" || s == ">" || s == ">=" => s,
574+
_ => return Ok(None),
575+
};
576+
577+
let left = &items[1];
578+
let right = &items[2];
579+
580+
// Check for immediate optimization: (< x 0), (<= n 10), etc.
581+
if let Some(imm) = right.as_int() {
582+
if imm >= i8::MIN as i64 && imm <= i8::MAX as i64 {
583+
// Compile left operand into dest
584+
self.compile_expr(left, dest, false)?;
585+
586+
// Emit combined compare-jump with OPPOSITE comparison
587+
// placeholder offset 0, will be patched later
588+
let jump_pos = match op {
589+
"<" => self.emit(Op::jump_if_ge_imm(dest, imm as i8, 0)),
590+
"<=" => self.emit(Op::jump_if_gt_imm(dest, imm as i8, 0)),
591+
">" => self.emit(Op::jump_if_le_imm(dest, imm as i8, 0)),
592+
">=" => self.emit(Op::jump_if_lt_imm(dest, imm as i8, 0)),
593+
_ => unreachable!(),
594+
};
595+
return Ok(Some(jump_pos));
596+
}
597+
}
598+
599+
// Register-register comparison
600+
// Compile left into dest
601+
self.compile_expr(left, dest, false)?;
602+
// Compile right into temp register
603+
let right_reg = self.alloc_reg();
604+
self.compile_expr(right, right_reg, false)?;
605+
606+
// Emit combined compare-jump with OPPOSITE comparison
607+
let jump_pos = match op {
608+
"<" => self.emit(Op::jump_if_ge(dest, right_reg, 0)),
609+
"<=" => self.emit(Op::jump_if_gt(dest, right_reg, 0)),
610+
">" => self.emit(Op::jump_if_le(dest, right_reg, 0)),
611+
">=" => self.emit(Op::jump_if_lt(dest, right_reg, 0)),
612+
_ => unreachable!(),
613+
};
614+
615+
self.free_reg(); // free right_reg
616+
Ok(Some(jump_pos))
617+
}
618+
546619
fn compile_def(&mut self, args: &[Value], dest: Reg) -> Result<(), String> {
547620
if args.len() != 2 {
548621
return Err("def expects exactly 2 arguments".to_string());

src/vm.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,120 @@ impl VM {
775775
unsafe { *self.registers.get_unchecked_mut(base + dest as usize) = result };
776776
}
777777

778+
// Combined compare-and-jump (register vs register)
779+
Op::JUMP_IF_LT => {
780+
let left = instr.a();
781+
let right = instr.b();
782+
let offset = instr.c() as i8;
783+
let va = unsafe { self.registers.get_unchecked(base + left as usize) };
784+
let vb = unsafe { self.registers.get_unchecked(base + right as usize) };
785+
if compare_values(va, vb)? == std::cmp::Ordering::Less {
786+
ip = (ip as isize + offset as isize) as usize;
787+
}
788+
}
789+
790+
Op::JUMP_IF_LE => {
791+
let left = instr.a();
792+
let right = instr.b();
793+
let offset = instr.c() as i8;
794+
let va = unsafe { self.registers.get_unchecked(base + left as usize) };
795+
let vb = unsafe { self.registers.get_unchecked(base + right as usize) };
796+
if compare_values(va, vb)? != std::cmp::Ordering::Greater {
797+
ip = (ip as isize + offset as isize) as usize;
798+
}
799+
}
800+
801+
Op::JUMP_IF_GT => {
802+
let left = instr.a();
803+
let right = instr.b();
804+
let offset = instr.c() as i8;
805+
let va = unsafe { self.registers.get_unchecked(base + left as usize) };
806+
let vb = unsafe { self.registers.get_unchecked(base + right as usize) };
807+
if compare_values(va, vb)? == std::cmp::Ordering::Greater {
808+
ip = (ip as isize + offset as isize) as usize;
809+
}
810+
}
811+
812+
Op::JUMP_IF_GE => {
813+
let left = instr.a();
814+
let right = instr.b();
815+
let offset = instr.c() as i8;
816+
let va = unsafe { self.registers.get_unchecked(base + left as usize) };
817+
let vb = unsafe { self.registers.get_unchecked(base + right as usize) };
818+
if compare_values(va, vb)? != std::cmp::Ordering::Less {
819+
ip = (ip as isize + offset as isize) as usize;
820+
}
821+
}
822+
823+
// Combined compare-and-jump (register vs immediate)
824+
Op::JUMP_IF_LT_IMM => {
825+
let src = instr.a();
826+
let imm = instr.b() as i8 as i64;
827+
let offset = instr.c() as i8;
828+
let v = unsafe { self.registers.get_unchecked(base + src as usize) };
829+
let should_jump = if let Some(x) = v.as_int() {
830+
x < imm
831+
} else if let Some(x) = v.as_float() {
832+
x < imm as f64
833+
} else {
834+
return Err("< expects a number".to_string());
835+
};
836+
if should_jump {
837+
ip = (ip as isize + offset as isize) as usize;
838+
}
839+
}
840+
841+
Op::JUMP_IF_LE_IMM => {
842+
let src = instr.a();
843+
let imm = instr.b() as i8 as i64;
844+
let offset = instr.c() as i8;
845+
let v = unsafe { self.registers.get_unchecked(base + src as usize) };
846+
let should_jump = if let Some(x) = v.as_int() {
847+
x <= imm
848+
} else if let Some(x) = v.as_float() {
849+
x <= imm as f64
850+
} else {
851+
return Err("<= expects a number".to_string());
852+
};
853+
if should_jump {
854+
ip = (ip as isize + offset as isize) as usize;
855+
}
856+
}
857+
858+
Op::JUMP_IF_GT_IMM => {
859+
let src = instr.a();
860+
let imm = instr.b() as i8 as i64;
861+
let offset = instr.c() as i8;
862+
let v = unsafe { self.registers.get_unchecked(base + src as usize) };
863+
let should_jump = if let Some(x) = v.as_int() {
864+
x > imm
865+
} else if let Some(x) = v.as_float() {
866+
x > imm as f64
867+
} else {
868+
return Err("> expects a number".to_string());
869+
};
870+
if should_jump {
871+
ip = (ip as isize + offset as isize) as usize;
872+
}
873+
}
874+
875+
Op::JUMP_IF_GE_IMM => {
876+
let src = instr.a();
877+
let imm = instr.b() as i8 as i64;
878+
let offset = instr.c() as i8;
879+
let v = unsafe { self.registers.get_unchecked(base + src as usize) };
880+
let should_jump = if let Some(x) = v.as_int() {
881+
x >= imm
882+
} else if let Some(x) = v.as_float() {
883+
x >= imm as f64
884+
} else {
885+
return Err(">= expects a number".to_string());
886+
};
887+
if should_jump {
888+
ip = (ip as isize + offset as isize) as usize;
889+
}
890+
}
891+
778892
_ => {
779893
return Err(format!("Unknown opcode: {}", instr.opcode()));
780894
}
@@ -1344,4 +1458,34 @@ mod tests {
13441458
let result = vm_eval("(do (def count-down (fn (n) (if (<= n 0) 0 (+ 1 (count-down (- n 1)))))) (count-down 10))").unwrap();
13451459
assert_eq!(result, Value::Int(10));
13461460
}
1461+
1462+
#[test]
1463+
fn test_combined_compare_jump() {
1464+
// Test combined compare-and-jump opcodes
1465+
// These are generated for (if (< a b) ...) patterns
1466+
1467+
// Immediate variants
1468+
assert_eq!(vm_eval("(if (< 5 10) 1 2)").unwrap(), Value::Int(1));
1469+
assert_eq!(vm_eval("(if (< 15 10) 1 2)").unwrap(), Value::Int(2));
1470+
assert_eq!(vm_eval("(if (<= 10 10) 1 2)").unwrap(), Value::Int(1));
1471+
assert_eq!(vm_eval("(if (<= 11 10) 1 2)").unwrap(), Value::Int(2));
1472+
assert_eq!(vm_eval("(if (> 15 10) 1 2)").unwrap(), Value::Int(1));
1473+
assert_eq!(vm_eval("(if (> 5 10) 1 2)").unwrap(), Value::Int(2));
1474+
assert_eq!(vm_eval("(if (>= 10 10) 1 2)").unwrap(), Value::Int(1));
1475+
assert_eq!(vm_eval("(if (>= 9 10) 1 2)").unwrap(), Value::Int(2));
1476+
1477+
// Variable vs immediate (uses JumpIfXxxImm)
1478+
assert_eq!(vm_eval("(let (x 5) (if (< x 10) 1 2))").unwrap(), Value::Int(1));
1479+
assert_eq!(vm_eval("(let (x 15) (if (< x 10) 1 2))").unwrap(), Value::Int(2));
1480+
assert_eq!(vm_eval("(let (n 0) (if (<= n 0) 1 2))").unwrap(), Value::Int(1));
1481+
assert_eq!(vm_eval("(let (n 1) (if (<= n 0) 1 2))").unwrap(), Value::Int(2));
1482+
1483+
// Variable vs variable (uses JumpIfXxx)
1484+
assert_eq!(vm_eval("(let (a 5 b 10) (if (< a b) 1 2))").unwrap(), Value::Int(1));
1485+
assert_eq!(vm_eval("(let (a 15 b 10) (if (< a b) 1 2))").unwrap(), Value::Int(2));
1486+
1487+
// Recursive function using combined compare-jump
1488+
let result = vm_eval("(do (def sum (fn (n acc) (if (<= n 0) acc (sum (- n 1) (+ acc n))))) (sum 100 0))").unwrap();
1489+
assert_eq!(result, Value::Int(5050));
1490+
}
13471491
}

0 commit comments

Comments
 (0)