Skip to content

Commit 48f8d59

Browse files
committed
Add integer-spec compare-jump opcodes for tail-recur loops
1 parent ed054c8 commit 48f8d59

File tree

3 files changed

+79
-23
lines changed

3 files changed

+79
-23
lines changed

src/bytecode.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ impl std::fmt::Debug for Op {
8686
Self::GE_INT => write!(f, "GeInt({}, {}, {})", self.a(), self.b(), self.c()),
8787
Self::JUMP_IF_LE_INT_IMM => write!(f, "JumpIfLeIntImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
8888
Self::JUMP_IF_GT_INT_IMM => write!(f, "JumpIfGtIntImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
89+
Self::JUMP_IF_LT_INT_IMM => write!(f, "JumpIfLtIntImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
90+
Self::JUMP_IF_GE_INT_IMM => write!(f, "JumpIfGeIntImm({}, {}, {})", self.a(), self.b() as i8, self.c() as i8),
8991
_ => write!(f, "Unknown(0x{:08x})", self.0),
9092
}
9193
}
@@ -171,6 +173,8 @@ impl Op {
171173
// Combined compare-and-jump for integers (most common loop pattern)
172174
pub const JUMP_IF_LE_INT_IMM: u8 = 64; // ABC: src, imm (i8), offset (i8) - jump if src <= imm
173175
pub const JUMP_IF_GT_INT_IMM: u8 = 65; // ABC: src, imm (i8), offset (i8) - jump if src > imm (for opposite)
176+
pub const JUMP_IF_LT_INT_IMM: u8 = 66; // ABC: src, imm (i8), offset (i8) - jump if src < imm
177+
pub const JUMP_IF_GE_INT_IMM: u8 = 67; // ABC: src, imm (i8), offset (i8) - jump if src >= imm
174178

175179
// ========== Constructors ==========
176180

@@ -580,6 +584,16 @@ impl Op {
580584
Self::abc(Self::JUMP_IF_GT_INT_IMM, src, imm as u8, offset as u8)
581585
}
582586

587+
#[inline(always)]
588+
pub const fn jump_if_lt_int_imm(src: Reg, imm: i8, offset: i8) -> Self {
589+
Self::abc(Self::JUMP_IF_LT_INT_IMM, src, imm as u8, offset as u8)
590+
}
591+
592+
#[inline(always)]
593+
pub const fn jump_if_ge_int_imm(src: Reg, imm: i8, offset: i8) -> Self {
594+
Self::abc(Self::JUMP_IF_GE_INT_IMM, src, imm as u8, offset as u8)
595+
}
596+
583597
// ========== Jump patching helpers ==========
584598

585599
/// Check if this is a jump instruction (for patching)
@@ -593,6 +607,7 @@ impl Op {
593607
|| op == Self::JUMP_IF_GT_IMM || op == Self::JUMP_IF_GE_IMM
594608
|| op == Self::JUMP_IF_NIL || op == Self::JUMP_IF_NOT_NIL
595609
|| op == Self::JUMP_IF_LE_INT_IMM || op == Self::JUMP_IF_GT_INT_IMM
610+
|| op == Self::JUMP_IF_LT_INT_IMM || op == Self::JUMP_IF_GE_INT_IMM
596611
}
597612

598613
/// Patch the offset of a jump instruction
@@ -602,7 +617,12 @@ impl Op {
602617
let a = self.a();
603618
let b = self.b();
604619
// Combined compare-and-jump use ABC format with C as 8-bit offset
605-
if opcode >= Self::JUMP_IF_LT && opcode <= Self::JUMP_IF_GE_IMM {
620+
if (opcode >= Self::JUMP_IF_LT && opcode <= Self::JUMP_IF_GE_IMM)
621+
|| opcode == Self::JUMP_IF_LE_INT_IMM
622+
|| opcode == Self::JUMP_IF_GT_INT_IMM
623+
|| opcode == Self::JUMP_IF_LT_INT_IMM
624+
|| opcode == Self::JUMP_IF_GE_INT_IMM
625+
{
606626
// For these opcodes: A=left/src, B=right/imm, C=offset (i8)
607627
*self = Self::abc(opcode, a, b, new_offset as i8 as u8);
608628
} else if opcode == Self::JUMP {
@@ -671,7 +691,8 @@ impl Chunk {
671691
jump_targets[target] = true;
672692
}
673693
} else if (opcode >= Op::JUMP_IF_LT && opcode <= Op::JUMP_IF_GE_IMM)
674-
|| opcode == Op::JUMP_IF_LE_INT_IMM || opcode == Op::JUMP_IF_GT_INT_IMM {
694+
|| opcode == Op::JUMP_IF_LE_INT_IMM || opcode == Op::JUMP_IF_GT_INT_IMM
695+
|| opcode == Op::JUMP_IF_LT_INT_IMM || opcode == Op::JUMP_IF_GE_INT_IMM {
675696
// These use i8 offset in C byte
676697
let offset = op.c() as i8 as isize;
677698
let target = (i as isize + 1 + offset) as usize;
@@ -720,7 +741,8 @@ impl Chunk {
720741
}
721742
Op::JUMP_IF_LT_IMM | Op::JUMP_IF_LE_IMM |
722743
Op::JUMP_IF_GT_IMM | Op::JUMP_IF_GE_IMM |
723-
Op::JUMP_IF_LE_INT_IMM | Op::JUMP_IF_GT_INT_IMM => {
744+
Op::JUMP_IF_LE_INT_IMM | Op::JUMP_IF_GT_INT_IMM |
745+
Op::JUMP_IF_LT_INT_IMM | Op::JUMP_IF_GE_INT_IMM => {
724746
ever_live |= 1u128 << op.a();
725747
}
726748
_ => {}
@@ -869,7 +891,8 @@ impl Chunk {
869891

870892
Op::JUMP_IF_LT_IMM | Op::JUMP_IF_LE_IMM |
871893
Op::JUMP_IF_GT_IMM | Op::JUMP_IF_GE_IMM |
872-
Op::JUMP_IF_LE_INT_IMM | Op::JUMP_IF_GT_INT_IMM => {
894+
Op::JUMP_IF_LE_INT_IMM | Op::JUMP_IF_GT_INT_IMM |
895+
Op::JUMP_IF_LT_INT_IMM | Op::JUMP_IF_GE_INT_IMM => {
873896
let src = op.a();
874897
live |= 1u128 << src;
875898
}

src/compiler.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -540,21 +540,15 @@ impl Compiler {
540540
}
541541

542542
// Parameters occupy the first registers
543-
// For loop-optimized functions (self-recursive), assume numeric parameters are integers
544-
// This is a technique that enables integer-specialized opcodes for common patterns
545-
// like sum(n, acc) where n and acc are typically integers.
546-
let is_loop_optimized = name.is_some();
543+
// Note: We don't assume parameter types because:
544+
// >self-recursive functions may not be tail-recursive
545+
// >even tail-recursive functions might be called with different types
546+
// Integer-specialized opcodes will still fire for integer literals and
547+
// results of integer operations, which covers most hot paths
547548
for param in params {
548549
compiler.locals.push(param.clone());
549-
// For loop-optimized functions, assume parameters are Int
550-
// This enables integer-specialized opcodes in the hot loop
551-
if is_loop_optimized {
552-
compiler.reg_types.push(StaticType::Int);
553-
compiler.param_types.push(StaticType::Int);
554-
} else {
555-
compiler.reg_types.push(StaticType::Unknown);
556-
compiler.param_types.push(StaticType::Unknown);
557-
}
550+
compiler.reg_types.push(StaticType::Unknown);
551+
compiler.param_types.push(StaticType::Unknown);
558552
}
559553

560554
let dest = compiler.alloc_reg();
@@ -789,6 +783,9 @@ impl Compiler {
789783
/// - `<=` → JumpIfGt (jump if a > b)
790784
/// - `>` → JumpIfLe (jump if a <= b)
791785
/// - `>=` → JumpIfLt (jump if a < b)
786+
///
787+
/// When the left operand is known to be an integer, we use integer-specialized
788+
/// opcodes that skip type checking (faster in tight loops).
792789
fn try_compile_compare_jump(&mut self, cond: &Value, dest: Reg) -> Result<Option<usize>, String> {
793790
let items = match cond.as_list() {
794791
Some(items) if items.len() == 3 => items,
@@ -803,6 +800,10 @@ impl Compiler {
803800
let left = &items[1];
804801
let right = &items[2];
805802

803+
// Infer type of left operand to determine if we can use integer-specialized opcodes
804+
let left_type = self.infer_type(left);
805+
let use_int_opcodes = left_type == StaticType::Int;
806+
806807
// Check for immediate optimization: (< x 0), (<= n 10), etc.
807808
if let Some(imm) = right.as_int() {
808809
if imm >= i8::MIN as i64 && imm <= i8::MAX as i64 {
@@ -811,12 +812,22 @@ impl Compiler {
811812

812813
// Emit combined compare-jump with OPPOSITE comparison
813814
// placeholder offset 0, will be patched later
814-
let jump_pos = match op {
815-
"<" => self.emit(Op::jump_if_ge_imm(dest, imm as i8, 0)),
816-
"<=" => self.emit(Op::jump_if_gt_imm(dest, imm as i8, 0)),
817-
">" => self.emit(Op::jump_if_le_imm(dest, imm as i8, 0)),
818-
">=" => self.emit(Op::jump_if_lt_imm(dest, imm as i8, 0)),
819-
_ => unreachable!(),
815+
let jump_pos = if use_int_opcodes {
816+
match op {
817+
"<" => self.emit(Op::jump_if_ge_int_imm(dest, imm as i8, 0)),
818+
"<=" => self.emit(Op::jump_if_gt_int_imm(dest, imm as i8, 0)),
819+
">" => self.emit(Op::jump_if_le_int_imm(dest, imm as i8, 0)),
820+
">=" => self.emit(Op::jump_if_lt_int_imm(dest, imm as i8, 0)),
821+
_ => unreachable!(),
822+
}
823+
} else {
824+
match op {
825+
"<" => self.emit(Op::jump_if_ge_imm(dest, imm as i8, 0)),
826+
"<=" => self.emit(Op::jump_if_gt_imm(dest, imm as i8, 0)),
827+
">" => self.emit(Op::jump_if_le_imm(dest, imm as i8, 0)),
828+
">=" => self.emit(Op::jump_if_lt_imm(dest, imm as i8, 0)),
829+
_ => unreachable!(),
830+
}
820831
};
821832
return Ok(Some(jump_pos));
822833
}

src/vm.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,6 +1280,28 @@ impl VM {
12801280
}
12811281
}
12821282

1283+
Op::JUMP_IF_LT_INT_IMM => {
1284+
let src = instr.a();
1285+
let imm = instr.b() as i8 as i64;
1286+
let offset = instr.c() as i8;
1287+
let v = unsafe { self.registers.get_unchecked(base + src as usize) };
1288+
let x = unsafe { v.as_int_unchecked() };
1289+
if x < imm {
1290+
ip = (ip as isize + offset as isize) as usize;
1291+
}
1292+
}
1293+
1294+
Op::JUMP_IF_GE_INT_IMM => {
1295+
let src = instr.a();
1296+
let imm = instr.b() as i8 as i64;
1297+
let offset = instr.c() as i8;
1298+
let v = unsafe { self.registers.get_unchecked(base + src as usize) };
1299+
let x = unsafe { v.as_int_unchecked() };
1300+
if x >= imm {
1301+
ip = (ip as isize + offset as isize) as usize;
1302+
}
1303+
}
1304+
12831305
_ => {
12841306
return Err(format!("Unknown opcode: {}", instr.opcode()));
12851307
}

0 commit comments

Comments
 (0)