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:
parent
bd41935b02
commit
22c73f1ccb
Notes:
git
2025-04-18 13:48:24 +00:00
4
.github/workflows/zjit-macos.yml
vendored
4
.github/workflows/zjit-macos.yml
vendored
@ -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
|
||||
|
||||
|
4
.github/workflows/zjit-ubuntu.yml
vendored
4
.github/workflows/zjit-ubuntu.yml
vendored
@ -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
|
||||
|
||||
|
@ -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
1
vm.c
@ -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
2
zjit.h
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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: _ } => {
|
||||
/*
|
||||
|
@ -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)]
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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
14
zjit/src/invariants.rs
Normal 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");
|
||||
}
|
@ -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) {
|
||||
|
@ -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)*);
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user