Implement FixnumAdd and stub PatchPoint/GuardType (https://github.com/Shopify/zjit/pull/30)

* Implement FixnumAdd and stub PatchPoint/GuardType

Co-authored-by: Max Bernstein <max.bernstein@shopify.com>
Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>

* Clone Target for arm64

* Use $create instead of use create

Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>

* Fix misindentation from suggested changes

* Drop an unneeded variable for mut

* Load operand into a register only if necessary

---------

Co-authored-by: Max Bernstein <max.bernstein@shopify.com>
Co-authored-by: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com>
Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
This commit is contained in:
Takashi Kokubun 2025-03-05 13:47:25 -08:00
parent bd41935b02
commit 22c73f1ccb
Notes: git 2025-04-18 13:48:24 +00:00
14 changed files with 193 additions and 182 deletions

View File

@ -33,14 +33,14 @@ jobs:
rust_version: 1.85.0
- test_task: 'btest'
zjit_opts: '--zjit-call-threshold=1'
zjit_opts: '--zjit-call-threshold=2'
configure: '--enable-zjit=dev'
btests: '../src/bootstraptest/test_zjit.rb'
rust_version: 1.85.0
- test_task: 'check'
# Test without ZJIT for now
#zjit_opts: '--zjit-call-threshold=1'
#zjit_opts: '--zjit-call-threshold=2'
configure: '--enable-zjit'
rust_version: 1.85.0

View File

@ -39,14 +39,14 @@ jobs:
rust_version: 1.85.0
- test_task: 'btest'
zjit_opts: '--zjit-call-threshold=1'
zjit_opts: '--zjit-call-threshold=2'
configure: '--enable-zjit=dev'
btests: '../src/bootstraptest/test_zjit.rb'
rust_version: 1.85.0
- test_task: 'check'
# Test without ZJIT for now
#zjit_opts: '--zjit-call-threshold=1'
#zjit_opts: '--zjit-call-threshold=2'
configure: '--enable-zjit'
rust_version: 1.85.0

View File

@ -3,10 +3,15 @@
assert_equal 'nil', %q{
def test = nil
test.inspect
test; test.inspect
}
assert_equal '1', %q{
def test = 1
test
test; test
}
assert_equal '3', %q{
def test = 1 + 2
test; test
}

1
vm.c
View File

@ -2199,6 +2199,7 @@ rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass)
rb_id2name(me->called_id)
);
rb_yjit_bop_redefined(flag, (enum ruby_basic_operators)bop);
rb_zjit_bop_redefined(flag, (enum ruby_basic_operators)bop);
ruby_vm_redefined_flag[bop] |= flag;
}
}

2
zjit.h
View File

@ -10,10 +10,12 @@ extern uint64_t rb_zjit_call_threshold;
void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception);
void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec);
void rb_zjit_profile_iseq(const rb_iseq_t *iseq);
void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop);
#else
void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {}
void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec) {}
void rb_zjit_profile_iseq(const rb_iseq_t *iseq) {}
void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
#endif // #if USE_YJIT
#endif // #ifndef ZJIT_H

View File

@ -878,10 +878,7 @@ pub fn jmp_label(cb: &mut CodeBlock, label_idx: usize) {
}
/// Encode a relative jump to a pointer at a 32-bit offset (direct or conditional)
fn write_jcc_ptr(_cb: &mut CodeBlock, _op0: u8, _op1: u8, _dst_ptr: CodePtr) {
todo!();
/*
fn write_jcc_ptr(cb: &mut CodeBlock, op0: u8, op1: u8, dst_ptr: CodePtr) {
// Write the opcode
if op0 != 0xFF {
cb.write_byte(op0);
@ -904,7 +901,6 @@ fn write_jcc_ptr(_cb: &mut CodeBlock, _op0: u8, _op1: u8, _dst_ptr: CodePtr) {
//cb.dropped_bytes = true;
panic!("we should refactor to avoid dropped_bytes");
}
*/
}
/// jcc - relative jumps to a pointer (32-bit offset)

View File

@ -843,9 +843,9 @@ impl Assembler
bcond(cb, CONDITION, InstructionOffset::from_bytes(bytes));
});
},
//Target::SideExit { .. } => {
// unreachable!("Target::SideExit should have been compiled by compile_side_exit")
//},
Target::SideExit { .. } => {
unreachable!("Target::SideExit should have been compiled by compile_side_exits")
},
};
}
@ -900,23 +900,6 @@ impl Assembler
ldr_post(cb, opnd, A64Opnd::new_mem(64, C_SP_REG, C_SP_STEP));
}
/*
/// Compile a side exit if Target::SideExit is given.
fn compile_side_exit(
target: Target,
asm: &mut Assembler,
ocb: &mut Option<&mut OutlinedCb>,
) -> Result<Target, EmitError> {
if let Target::SideExit { counter, context } = target {
let side_exit = asm.get_side_exit(&context.unwrap(), Some(counter), ocb.as_mut().unwrap())
.ok_or(EmitError::OutOfMemory)?;
Ok(Target::SideExitPtr(side_exit))
} else {
Ok(target)
}
}
*/
// dbg!(&self.insns);
// List of GC offsets
@ -1220,40 +1203,40 @@ impl Assembler
b(cb, InstructionOffset::from_bytes(bytes));
});
},
//Target::SideExit { .. } => {
// unreachable!("Target::SideExit should have been compiled by compile_side_exit")
//},
Target::SideExit { .. } => {
unreachable!("Target::SideExit should have been compiled by compile_side_exits")
},
};
},
Insn::Je(target) | Insn::Jz(target) => {
emit_conditional_jump::<{Condition::EQ}>(cb, *target);
emit_conditional_jump::<{Condition::EQ}>(cb, target.clone());
},
Insn::Jne(target) | Insn::Jnz(target) | Insn::JoMul(target) => {
emit_conditional_jump::<{Condition::NE}>(cb, *target);
emit_conditional_jump::<{Condition::NE}>(cb, target.clone());
},
Insn::Jl(target) => {
emit_conditional_jump::<{Condition::LT}>(cb, *target);
emit_conditional_jump::<{Condition::LT}>(cb, target.clone());
},
Insn::Jg(target) => {
emit_conditional_jump::<{Condition::GT}>(cb, *target);
emit_conditional_jump::<{Condition::GT}>(cb, target.clone());
},
Insn::Jge(target) => {
emit_conditional_jump::<{Condition::GE}>(cb, *target);
emit_conditional_jump::<{Condition::GE}>(cb, target.clone());
},
Insn::Jbe(target) => {
emit_conditional_jump::<{Condition::LS}>(cb, *target);
emit_conditional_jump::<{Condition::LS}>(cb, target.clone());
},
Insn::Jb(target) => {
emit_conditional_jump::<{Condition::CC}>(cb, *target);
emit_conditional_jump::<{Condition::CC}>(cb, target.clone());
},
Insn::Jo(target) => {
emit_conditional_jump::<{Condition::VS}>(cb, *target);
emit_conditional_jump::<{Condition::VS}>(cb, target.clone());
},
Insn::Joz(opnd, target) => {
emit_cmp_zero_jump(cb, opnd.into(), true, *target);
emit_cmp_zero_jump(cb, opnd.into(), true, target.clone());
},
Insn::Jonz(opnd, target) => {
emit_cmp_zero_jump(cb, opnd.into(), false, *target);
emit_cmp_zero_jump(cb, opnd.into(), false, target.clone());
},
Insn::IncrCounter { mem: _, value: _ } => {
/*

View File

@ -1,6 +1,6 @@
use std::fmt;
use std::mem::take;
use crate::cruby::VALUE;
use crate::{cruby::VALUE, hir::FrameState};
use crate::backend::current::*;
use crate::virtualmem::CodePtr;
use crate::asm::CodeBlock;
@ -290,13 +290,13 @@ impl From<VALUE> for Opnd {
/// Branch target (something that we can jump to)
/// for branch instructions
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[derive(Clone, Debug)]
pub enum Target
{
/// Pointer to a piece of YJIT-generated code
CodePtr(CodePtr),
// Side exit with a counter
//SideExit { counter: Counter, context: Option<SideExitContext> },
SideExit(FrameState),
/// Pointer to a side exit code
SideExitPtr(CodePtr),
/// A label within the generated code
@ -305,12 +305,6 @@ pub enum Target
impl Target
{
/*
pub fn side_exit(counter: Counter) -> Target {
Target::SideExit { counter, context: None }
}
*/
pub fn unwrap_label_idx(&self) -> usize {
match self {
Target::Label(idx) => *idx,
@ -966,56 +960,6 @@ impl fmt::Debug for Insn {
}
}
/// Set of variables used for generating side exits
/*
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct SideExitContext {
/// PC of the instruction being compiled
pub pc: *mut VALUE,
/// Context fields used by get_generic_ctx()
pub stack_size: u8,
pub sp_offset: i8,
pub reg_mapping: RegMapping,
pub is_return_landing: bool,
pub is_deferred: bool,
}
impl SideExitContext {
/// Convert PC and Context into SideExitContext
pub fn new(pc: *mut VALUE, ctx: Context) -> Self {
let exit_ctx = SideExitContext {
pc,
stack_size: ctx.get_stack_size(),
sp_offset: ctx.get_sp_offset(),
reg_mapping: ctx.get_reg_mapping(),
is_return_landing: ctx.is_return_landing(),
is_deferred: ctx.is_deferred(),
};
if cfg!(debug_assertions) {
// Assert that we're not losing any mandatory metadata
assert_eq!(exit_ctx.get_ctx(), ctx.get_generic_ctx());
}
exit_ctx
}
/// Convert SideExitContext to Context
fn get_ctx(&self) -> Context {
let mut ctx = Context::default();
ctx.set_stack_size(self.stack_size);
ctx.set_sp_offset(self.sp_offset);
ctx.set_reg_mapping(self.reg_mapping);
if self.is_return_landing {
ctx.set_as_return_landing();
}
if self.is_deferred {
ctx.mark_as_deferred();
}
ctx
}
}
*/
/// Initial capacity for asm.insns vector
const ASSEMBLER_INSNS_CAPACITY: usize = 256;
@ -1155,19 +1099,6 @@ impl Assembler
}
}
// Set a side exit context to Target::SideExit
/*
if let Some(Target::SideExit { context, .. }) = insn.target_mut() {
// We should skip this when this instruction is being copied from another Assembler.
if context.is_none() {
*context = Some(SideExitContext::new(
self.side_exit_pc.unwrap(),
self.ctx.with_stack_size(self.side_exit_stack_size.unwrap()),
));
}
}
*/
self.insns.push(insn);
self.live_ranges.push(insn_idx);
}
@ -1635,8 +1566,10 @@ impl Assembler
/// Compile the instructions down to machine code.
/// Can fail due to lack of code memory and inopportune code placement, among other reasons.
#[must_use]
pub fn compile(self, cb: &mut CodeBlock) -> Option<(CodePtr, Vec<u32>)>
pub fn compile(mut self, cb: &mut CodeBlock) -> Option<(CodePtr, Vec<u32>)>
{
self.compile_side_exits(cb)?;
#[cfg(feature = "disasm")]
let start_addr = cb.get_write_ptr();
let alloc_regs = Self::get_alloc_regs();
@ -1651,6 +1584,29 @@ impl Assembler
ret
}
/// Compile Target::SideExit and convert it into Target::CodePtr for all instructions
#[must_use]
pub fn compile_side_exits(&mut self, cb: &mut CodeBlock) -> Option<()> {
for insn in self.insns.iter_mut() {
if let Some(target) = insn.target_mut() {
if let Target::SideExit(state) = target {
let side_exit_ptr = cb.get_write_ptr();
let mut asm = Assembler::new();
asm_comment!(asm, "side exit: {:?}", state);
asm.ccall(Self::rb_zjit_side_exit as *const u8, vec![]);
asm.compile(cb)?;
*target = Target::CodePtr(side_exit_ptr);
}
}
}
Some(())
}
#[unsafe(no_mangle)]
extern "C" fn rb_zjit_side_exit() {
unimplemented!("side exits are not implemented yet");
}
/*
/// Compile with a limited number of registers. Used only for unit tests.
#[cfg(test)]

View File

@ -696,7 +696,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jmp_ptr(cb, code_ptr),
Target::Label(label_idx) => jmp_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
}
@ -704,7 +704,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => je_ptr(cb, code_ptr),
Target::Label(label_idx) => je_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
}
@ -712,7 +712,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jne_ptr(cb, code_ptr),
Target::Label(label_idx) => jne_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
}
@ -720,7 +720,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jl_ptr(cb, code_ptr),
Target::Label(label_idx) => jl_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
},
@ -728,7 +728,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jg_ptr(cb, code_ptr),
Target::Label(label_idx) => jg_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
},
@ -736,7 +736,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jge_ptr(cb, code_ptr),
Target::Label(label_idx) => jge_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
},
@ -744,7 +744,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jbe_ptr(cb, code_ptr),
Target::Label(label_idx) => jbe_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
},
@ -752,7 +752,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jb_ptr(cb, code_ptr),
Target::Label(label_idx) => jb_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
},
@ -760,7 +760,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jz_ptr(cb, code_ptr),
Target::Label(label_idx) => jz_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
}
@ -768,7 +768,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jnz_ptr(cb, code_ptr),
Target::Label(label_idx) => jnz_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
}
@ -777,7 +777,7 @@ impl Assembler
match *target {
Target::CodePtr(code_ptr) | Target::SideExitPtr(code_ptr) => jo_ptr(cb, code_ptr),
Target::Label(label_idx) => jo_label(cb, label_idx),
//Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exit"),
Target::SideExit { .. } => unreachable!("Target::SideExit should have been compiled by compile_side_exits"),
}
}

View File

@ -1,13 +1,6 @@
use crate::{
asm::CodeBlock,
backend::lir::{EC, CFP, SP, C_ARG_OPNDS, Assembler, Opnd, asm_comment},
cruby::*,
debug,
hir::{Function, InsnId, Insn, Const},
virtualmem::CodePtr
asm::CodeBlock, backend::lir::{asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, EC, SP}, cruby::*, debug, hir::{Const, FrameState, Function, Insn, InsnId}, hir_type::{types::Fixnum, Type}, virtualmem::CodePtr
};
#[cfg(feature = "disasm")]
use crate::get_option;
/// Ephemeral code generation state
struct JITState {
@ -40,15 +33,19 @@ pub fn gen_function(cb: &mut CodeBlock, function: &Function, iseq: IseqPtr) -> O
if !matches!(*insn, Insn::Snapshot { .. }) {
asm_comment!(asm, "Insn: {:04} {:?}", insn_idx, insn);
}
match *insn {
Insn::Const { val: Const::Value(val) } => gen_const(&mut jit, insn_id, val),
Insn::Return { val } => gen_return(&jit, &mut asm, val)?,
match insn {
Insn::Const { val: Const::Value(val) } => gen_const(&mut jit, insn_id, *val),
Insn::Snapshot { .. } => {}, // we don't need to do anything for this instruction at the moment
Insn::Return { val } => gen_return(&jit, &mut asm, *val)?,
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(&mut jit, &mut asm, insn_id, *left, *right, state)?,
Insn::GuardType { val, guard_type, state } => gen_guard_type(&mut jit, &mut asm, insn_id, *val, *guard_type, state)?,
Insn::PatchPoint(_) => {}, // For now, rb_zjit_bop_redefined() panics. TODO: leave a patch point and fix rb_zjit_bop_redefined()
_ => {
debug!("ZJIT: gen_function: unexpected insn {:?}", insn);
return None;
}
}
debug!("Compiled insn: {:04} {:?}", insn_idx, insn);
}
// Generate code if everything can be compiled
@ -105,3 +102,44 @@ fn gen_return(jit: &JITState, asm: &mut Assembler, val: InsnId) -> Option<()> {
Some(())
}
/// Compile Fixnum + Fixnum
fn gen_fixnum_add(jit: &mut JITState, asm: &mut Assembler, insn_id: InsnId, left: InsnId, right: InsnId, state: &FrameState) -> Option<()> {
let left_opnd = jit.opnds[left.0]?;
let right_opnd = jit.opnds[right.0]?;
// Load left into a register if left is a constant. The backend doesn't support sub(imm, imm).
let left_reg = match left_opnd {
Opnd::Value(_) => asm.load(left_opnd),
_ => left_opnd,
};
// Add arg0 + arg1 and test for overflow
let left_untag = asm.sub(left_reg, Opnd::Imm(1));
let out_val = asm.add(left_untag, right_opnd);
asm.jo(Target::SideExit(state.clone()));
jit.opnds[insn_id.0] = Some(out_val);
Some(())
}
/// Compile a type check with a side exit
fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, insn_id: InsnId, val: InsnId, guard_type: Type, state: &FrameState) -> Option<()> {
let opnd = jit.opnds[val.0]?;
if guard_type.is_subtype(Fixnum) {
// Load opnd into a register if opnd is a constant. The backend doesn't support test(imm, imm) yet.
let opnd_reg = match opnd {
Opnd::Value(_) => asm.load(opnd),
_ => opnd,
};
// Check if opnd is Fixnum
asm.test(opnd_reg, Opnd::UImm(RUBY_FIXNUM_FLAG as u64));
asm.jz(Target::SideExit(state.clone()));
} else {
unimplemented!("unsupported type: {guard_type}");
}
jit.opnds[insn_id.0] = Some(opnd);
Some(())
}

View File

@ -192,21 +192,21 @@ pub enum Insn {
Return { val: InsnId },
/// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >=
FixnumAdd { left: InsnId, right: InsnId },
FixnumSub { left: InsnId, right: InsnId },
FixnumMult { left: InsnId, right: InsnId },
FixnumDiv { left: InsnId, right: InsnId },
FixnumMod { left: InsnId, right: InsnId },
FixnumEq { left: InsnId, right: InsnId },
FixnumNeq { left: InsnId, right: InsnId },
FixnumLt { left: InsnId, right: InsnId },
FixnumLe { left: InsnId, right: InsnId },
FixnumGt { left: InsnId, right: InsnId },
FixnumGe { left: InsnId, right: InsnId },
FixnumAdd { left: InsnId, right: InsnId, state: FrameState },
FixnumSub { left: InsnId, right: InsnId, state: FrameState },
FixnumMult { left: InsnId, right: InsnId, state: FrameState },
FixnumDiv { left: InsnId, right: InsnId, state: FrameState },
FixnumMod { left: InsnId, right: InsnId, state: FrameState },
FixnumEq { left: InsnId, right: InsnId, state: FrameState },
FixnumNeq { left: InsnId, right: InsnId, state: FrameState },
FixnumLt { left: InsnId, right: InsnId, state: FrameState },
FixnumLe { left: InsnId, right: InsnId, state: FrameState },
FixnumGt { left: InsnId, right: InsnId, state: FrameState },
FixnumGe { left: InsnId, right: InsnId, state: FrameState },
/// Side-exist if val doesn't have the expected type.
// TODO: Replace is_fixnum with the type lattice
GuardType { val: InsnId, guard_type: Type },
GuardType { val: InsnId, guard_type: Type, state: FrameState },
/// Generate no code (or padding if necessary) and insert a patch point
/// that can be rewritten to a side exit when the Invariant is broken.
@ -422,18 +422,18 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> {
}
}
Insn::Return { val } => { write!(f, "Return {val}")?; }
Insn::FixnumAdd { left, right } => { write!(f, "FixnumAdd {left}, {right}")?; },
Insn::FixnumSub { left, right } => { write!(f, "FixnumSub {left}, {right}")?; },
Insn::FixnumMult { left, right } => { write!(f, "FixnumMult {left}, {right}")?; },
Insn::FixnumDiv { left, right } => { write!(f, "FixnumDiv {left}, {right}")?; },
Insn::FixnumMod { left, right } => { write!(f, "FixnumMod {left}, {right}")?; },
Insn::FixnumEq { left, right } => { write!(f, "FixnumEq {left}, {right}")?; },
Insn::FixnumNeq { left, right } => { write!(f, "FixnumNeq {left}, {right}")?; },
Insn::FixnumLt { left, right } => { write!(f, "FixnumLt {left}, {right}")?; },
Insn::FixnumLe { left, right } => { write!(f, "FixnumLe {left}, {right}")?; },
Insn::FixnumGt { left, right } => { write!(f, "FixnumGt {left}, {right}")?; },
Insn::FixnumGe { left, right } => { write!(f, "FixnumGe {left}, {right}")?; },
Insn::GuardType { val, guard_type } => { write!(f, "GuardType {val}, {guard_type}")?; },
Insn::FixnumAdd { left, right, .. } => { write!(f, "FixnumAdd {left}, {right}")?; },
Insn::FixnumSub { left, right, .. } => { write!(f, "FixnumSub {left}, {right}")?; },
Insn::FixnumMult { left, right, .. } => { write!(f, "FixnumMult {left}, {right}")?; },
Insn::FixnumDiv { left, right, .. } => { write!(f, "FixnumDiv {left}, {right}")?; },
Insn::FixnumMod { left, right, .. } => { write!(f, "FixnumMod {left}, {right}")?; },
Insn::FixnumEq { left, right, .. } => { write!(f, "FixnumEq {left}, {right}")?; },
Insn::FixnumNeq { left, right, .. } => { write!(f, "FixnumNeq {left}, {right}")?; },
Insn::FixnumLt { left, right, .. } => { write!(f, "FixnumLt {left}, {right}")?; },
Insn::FixnumLe { left, right, .. } => { write!(f, "FixnumLe {left}, {right}")?; },
Insn::FixnumGt { left, right, .. } => { write!(f, "FixnumGt {left}, {right}")?; },
Insn::FixnumGe { left, right, .. } => { write!(f, "FixnumGe {left}, {right}")?; },
Insn::GuardType { val, guard_type, .. } => { write!(f, "GuardType {val}, {guard_type}")?; },
Insn::PatchPoint(invariant) => { write!(f, "PatchPoint {invariant:}")?; },
insn => { write!(f, "{insn:?}")?; }
}
@ -448,7 +448,7 @@ impl<'a> std::fmt::Display for FunctionPrinter<'a> {
pub struct FrameState {
iseq: IseqPtr,
// Ruby bytecode instruction pointer
pc: VALUE,
pub pc: VALUE,
stack: Vec<InsnId>,
locals: Vec<InsnId>,
@ -769,8 +769,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_plus | YARVINSN_zjit_opt_plus => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_PLUS }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumAdd { left, right }));
state.push(fun.push_insn(block, Insn::FixnumAdd { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -780,8 +781,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_minus | YARVINSN_zjit_opt_minus => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_MINUS }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumSub { left, right }));
state.push(fun.push_insn(block, Insn::FixnumSub { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -791,8 +793,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_mult | YARVINSN_zjit_opt_mult => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_MULT }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumMult { left, right }));
state.push(fun.push_insn(block, Insn::FixnumMult { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -802,8 +805,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_div | YARVINSN_zjit_opt_div => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_DIV }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumDiv { left, right }));
state.push(fun.push_insn(block, Insn::FixnumDiv { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -813,8 +817,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_mod | YARVINSN_zjit_opt_mod => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_MOD }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumMod { left, right }));
state.push(fun.push_insn(block, Insn::FixnumMod { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -825,8 +830,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_eq | YARVINSN_zjit_opt_eq => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_EQ }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumEq { left, right }));
state.push(fun.push_insn(block, Insn::FixnumEq { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -836,8 +842,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_neq | YARVINSN_zjit_opt_neq => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_NEQ }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumNeq { left, right }));
state.push(fun.push_insn(block, Insn::FixnumNeq { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -847,8 +854,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_lt | YARVINSN_zjit_opt_lt => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_LT }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumLt { left, right }));
state.push(fun.push_insn(block, Insn::FixnumLt { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -858,8 +866,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_le | YARVINSN_zjit_opt_le => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_LE }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumLe { left, right }));
state.push(fun.push_insn(block, Insn::FixnumLe { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -869,8 +878,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_gt | YARVINSN_zjit_opt_gt => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_GT }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumGt { left, right }));
state.push(fun.push_insn(block, Insn::FixnumGt { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -880,8 +890,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
YARVINSN_opt_ge | YARVINSN_zjit_opt_ge => {
if payload.have_two_fixnums(current_insn_idx as usize) {
fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_GE }));
let exit_state = state.clone();
let (left, right) = guard_two_fixnums(&mut state, &mut fun, block)?;
state.push(fun.push_insn(block, Insn::FixnumGe { left, right }));
state.push(fun.push_insn(block, Insn::FixnumGe { left, right, state: exit_state }));
} else {
let right = state.pop()?;
let left = state.pop()?;
@ -938,9 +949,9 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
match get_option!(dump_hir) {
Some(DumpHIR::WithoutSnapshot) => print!("HIR:\n{}", FunctionPrinter::without_snapshot(&fun)),
Some(DumpHIR::All) => print!("HIR:\n{}", FunctionPrinter::with_snapshot(&fun)),
Some(DumpHIR::Raw) => print!("HIR:\n{:#?}", &fun),
Some(DumpHIR::WithoutSnapshot) => println!("HIR:\n{}", FunctionPrinter::without_snapshot(&fun)),
Some(DumpHIR::All) => println!("HIR:\n{}", FunctionPrinter::with_snapshot(&fun)),
Some(DumpHIR::Raw) => println!("HIR:\n{:#?}", &fun),
None => {},
}
@ -949,8 +960,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
/// Generate guards for two fixnum outputs
fn guard_two_fixnums(state: &mut FrameState, fun: &mut Function, block: BlockId) -> Result<(InsnId, InsnId), ParseError> {
let left = fun.push_insn(block, Insn::GuardType { val: state.stack_opnd(1)?, guard_type: Fixnum });
let right = fun.push_insn(block, Insn::GuardType { val: state.stack_opnd(0)?, guard_type: Fixnum });
let left = fun.push_insn(block, Insn::GuardType { val: state.stack_opnd(1)?, guard_type: Fixnum, state: state.clone() });
let right = fun.push_insn(block, Insn::GuardType { val: state.stack_opnd(0)?, guard_type: Fixnum, state: state.clone() });
// Pop operands after guards for side exits
state.pop()?;

14
zjit/src/invariants.rs Normal file
View File

@ -0,0 +1,14 @@
use crate::{cruby::{ruby_basic_operators, RedefinitionFlag}, zjit_enabled_p};
/// Called when a basic operator is redefined. Note that all the blocks assuming
/// the stability of different operators are invalidated together and we don't
/// do fine-grained tracking.
#[unsafe(no_mangle)]
pub extern "C" fn rb_zjit_bop_redefined(_klass: RedefinitionFlag, _bop: ruby_basic_operators) {
// If ZJIT isn't enabled, do nothing
if !zjit_enabled_p() {
return;
}
unimplemented!("Invalidation on BOP redefinition is not implemented yet");
}

View File

@ -19,6 +19,7 @@ mod backend;
mod disasm;
mod options;
mod profile;
mod invariants;
use codegen::gen_function;
use options::{debug, get_option, Options};
@ -29,6 +30,11 @@ use crate::cruby::*;
#[unsafe(no_mangle)]
pub static mut rb_zjit_enabled_p: bool = false;
/// Like rb_zjit_enabled_p, but for Rust code.
pub fn zjit_enabled_p() -> bool {
unsafe { rb_zjit_enabled_p }
}
/// Initialize ZJIT, given options allocated by rb_zjit_init_options()
#[unsafe(no_mangle)]
pub extern "C" fn rb_zjit_init(options: *const u8) {

View File

@ -109,8 +109,7 @@ fn parse_option(options: &mut Options, str_ptr: *const std::os::raw::c_char) ->
/// Macro to print a message only when --zjit-debug is given
macro_rules! debug {
($($msg:tt)*) => {
use crate::options::get_option;
if get_option!(debug) {
if $crate::options::get_option!(debug) {
eprintln!($($msg)*);
}
};