mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
YJIT: Load registers on JIT entry to reuse blocks (#12355)
This commit is contained in:
parent
0b2f034208
commit
6bf7a1765f
Notes:
git
2024-12-17 17:33:01 +00:00
Merged-By: maximecb <maximecb@ruby-lang.org>
2 changed files with 70 additions and 21 deletions
|
@ -1061,14 +1061,13 @@ fn gen_leave_exception(ocb: &mut OutlinedCb) -> Option<CodePtr> {
|
|||
pub fn gen_entry_chain_guard(
|
||||
asm: &mut Assembler,
|
||||
ocb: &mut OutlinedCb,
|
||||
iseq: IseqPtr,
|
||||
insn_idx: u16,
|
||||
blockid: BlockId,
|
||||
) -> Option<PendingEntryRef> {
|
||||
let entry = new_pending_entry();
|
||||
let stub_addr = gen_entry_stub(entry.uninit_entry.as_ptr() as usize, ocb)?;
|
||||
|
||||
let pc_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC);
|
||||
let expected_pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx.into()) };
|
||||
let expected_pc = unsafe { rb_iseq_pc_at_idx(blockid.iseq, blockid.idx.into()) };
|
||||
let expected_pc_opnd = Opnd::const_ptr(expected_pc as *const u8);
|
||||
|
||||
asm_comment!(asm, "guard expected PC");
|
||||
|
@ -1087,10 +1086,11 @@ pub fn gen_entry_chain_guard(
|
|||
pub fn gen_entry_prologue(
|
||||
cb: &mut CodeBlock,
|
||||
ocb: &mut OutlinedCb,
|
||||
iseq: IseqPtr,
|
||||
insn_idx: u16,
|
||||
blockid: BlockId,
|
||||
stack_size: u8,
|
||||
jit_exception: bool,
|
||||
) -> Option<CodePtr> {
|
||||
) -> Option<(CodePtr, RegMapping)> {
|
||||
let iseq = blockid.iseq;
|
||||
let code_ptr = cb.get_write_ptr();
|
||||
|
||||
let mut asm = Assembler::new(unsafe { get_iseq_body_local_table_size(iseq) });
|
||||
|
@ -1145,10 +1145,11 @@ pub fn gen_entry_prologue(
|
|||
// If they don't match, then we'll jump to an entry stub and generate
|
||||
// another PC check and entry there.
|
||||
let pending_entry = if unsafe { get_iseq_flags_has_opt(iseq) } || jit_exception {
|
||||
Some(gen_entry_chain_guard(&mut asm, ocb, iseq, insn_idx)?)
|
||||
Some(gen_entry_chain_guard(&mut asm, ocb, blockid)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let reg_mapping = gen_entry_reg_mapping(&mut asm, blockid, stack_size);
|
||||
|
||||
asm.compile(cb, Some(ocb))?;
|
||||
|
||||
|
@ -1166,10 +1167,39 @@ pub fn gen_entry_prologue(
|
|||
.ok().expect("PendingEntry should be unique");
|
||||
iseq_payload.entries.push(pending_entry.into_entry());
|
||||
}
|
||||
Some(code_ptr)
|
||||
Some((code_ptr, reg_mapping))
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate code to load registers for a JIT entry. When the entry block is compiled for
|
||||
/// the first time, it loads no register. When it has been already compiled as a callee
|
||||
/// block, it loads some registers to reuse the block.
|
||||
pub fn gen_entry_reg_mapping(asm: &mut Assembler, blockid: BlockId, stack_size: u8) -> RegMapping {
|
||||
// Find an existing callee block. If it's not found or uses no register, skip loading registers.
|
||||
let mut ctx = Context::default();
|
||||
ctx.set_stack_size(stack_size);
|
||||
let reg_mapping = find_most_compatible_reg_mapping(blockid, &ctx).unwrap_or(RegMapping::default());
|
||||
if reg_mapping == RegMapping::default() {
|
||||
return reg_mapping;
|
||||
}
|
||||
|
||||
// If found, load the same registers to reuse the block.
|
||||
asm_comment!(asm, "reuse maps: {:?}", reg_mapping);
|
||||
let local_table_size: u32 = unsafe { get_iseq_body_local_table_size(blockid.iseq) }.try_into().unwrap();
|
||||
for ®_opnd in reg_mapping.get_reg_opnds().iter() {
|
||||
match reg_opnd {
|
||||
RegOpnd::Local(local_idx) => {
|
||||
let loaded_reg = TEMP_REGS[reg_mapping.get_reg(reg_opnd).unwrap()];
|
||||
let loaded_temp = asm.local_opnd(local_table_size - local_idx as u32 + VM_ENV_DATA_SIZE - 1);
|
||||
asm.load_into(Opnd::Reg(loaded_reg), loaded_temp);
|
||||
}
|
||||
RegOpnd::Stack(_) => unreachable!("find_most_compatible_reg_mapping should not leave {:?}", reg_opnd),
|
||||
}
|
||||
}
|
||||
|
||||
reg_mapping
|
||||
}
|
||||
|
||||
// Generate code to check for interrupts and take a side-exit.
|
||||
// Warning: this function clobbers REG0
|
||||
fn gen_check_ints(
|
||||
|
|
|
@ -3201,17 +3201,34 @@ pub fn gen_entry_point(iseq: IseqPtr, ec: EcPtr, jit_exception: bool) -> Option<
|
|||
let cb = CodegenGlobals::get_inline_cb();
|
||||
let ocb = CodegenGlobals::get_outlined_cb();
|
||||
|
||||
// Write the interpreter entry prologue. Might be NULL when out of memory.
|
||||
let code_ptr = gen_entry_prologue(cb, ocb, iseq, insn_idx, jit_exception);
|
||||
|
||||
// Try to generate code for the entry block
|
||||
let mut ctx = Context::default();
|
||||
ctx.stack_size = stack_size;
|
||||
let block = gen_block_series(blockid, &ctx, ec, cb, ocb);
|
||||
let code_ptr = gen_entry_point_body(blockid, stack_size, ec, jit_exception, cb, ocb);
|
||||
|
||||
cb.mark_all_executable();
|
||||
ocb.unwrap().mark_all_executable();
|
||||
|
||||
code_ptr
|
||||
}
|
||||
|
||||
fn gen_entry_point_body(blockid: BlockId, stack_size: u8, ec: EcPtr, jit_exception: bool, cb: &mut CodeBlock, ocb: &mut OutlinedCb) -> Option<*const u8> {
|
||||
// Write the interpreter entry prologue. Might be NULL when out of memory.
|
||||
let (code_ptr, reg_mapping) = gen_entry_prologue(cb, ocb, blockid, stack_size, jit_exception)?;
|
||||
|
||||
// Find or compile a block version
|
||||
let mut ctx = Context::default();
|
||||
ctx.stack_size = stack_size;
|
||||
ctx.reg_mapping = reg_mapping;
|
||||
let block = match find_block_version(blockid, &ctx) {
|
||||
// If an existing block is found, generate a jump to the block.
|
||||
Some(blockref) => {
|
||||
let mut asm = Assembler::new_without_iseq();
|
||||
asm.jmp(unsafe { blockref.as_ref() }.start_addr.into());
|
||||
asm.compile(cb, Some(ocb))?;
|
||||
Some(blockref)
|
||||
}
|
||||
// If this block hasn't yet been compiled, generate blocks after the entry guard.
|
||||
None => gen_block_series(blockid, &ctx, ec, cb, ocb),
|
||||
};
|
||||
|
||||
match block {
|
||||
// Compilation failed
|
||||
None => {
|
||||
|
@ -3235,7 +3252,7 @@ pub fn gen_entry_point(iseq: IseqPtr, ec: EcPtr, jit_exception: bool) -> Option<
|
|||
incr_counter!(compiled_iseq_entry);
|
||||
|
||||
// Compilation successful and block not empty
|
||||
code_ptr.map(|ptr| ptr.raw_ptr(cb))
|
||||
Some(code_ptr.raw_ptr(cb))
|
||||
}
|
||||
|
||||
// Change the entry's jump target from an entry stub to a next entry
|
||||
|
@ -3310,20 +3327,22 @@ fn entry_stub_hit_body(
|
|||
let cfp = unsafe { get_ec_cfp(ec) };
|
||||
let iseq = unsafe { get_cfp_iseq(cfp) };
|
||||
let insn_idx = iseq_pc_to_insn_idx(iseq, unsafe { get_cfp_pc(cfp) })?;
|
||||
let blockid = BlockId { iseq, idx: insn_idx };
|
||||
let stack_size: u8 = unsafe {
|
||||
u8::try_from(get_cfp_sp(cfp).offset_from(get_cfp_bp(cfp))).ok()?
|
||||
};
|
||||
|
||||
// Compile a new entry guard as a next entry
|
||||
let next_entry = cb.get_write_ptr();
|
||||
let mut asm = Assembler::new_without_iseq();
|
||||
let pending_entry = gen_entry_chain_guard(&mut asm, ocb, iseq, insn_idx)?;
|
||||
let mut asm = Assembler::new(unsafe { get_iseq_body_local_table_size(iseq) });
|
||||
let pending_entry = gen_entry_chain_guard(&mut asm, ocb, blockid)?;
|
||||
let reg_mapping = gen_entry_reg_mapping(&mut asm, blockid, stack_size);
|
||||
asm.compile(cb, Some(ocb))?;
|
||||
|
||||
// Find or compile a block version
|
||||
let blockid = BlockId { iseq, idx: insn_idx };
|
||||
let mut ctx = Context::default();
|
||||
ctx.stack_size = stack_size;
|
||||
ctx.reg_mapping = reg_mapping;
|
||||
let blockref = match find_block_version(blockid, &ctx) {
|
||||
// If an existing block is found, generate a jump to the block.
|
||||
Some(blockref) => {
|
||||
|
@ -3347,8 +3366,8 @@ fn entry_stub_hit_body(
|
|||
get_or_create_iseq_payload(iseq).entries.push(pending_entry.into_entry());
|
||||
}
|
||||
|
||||
// Let the stub jump to the block
|
||||
blockref.map(|block| unsafe { block.as_ref() }.start_addr.raw_ptr(cb))
|
||||
// Let the stub jump to the entry to load entry registers
|
||||
Some(next_entry.raw_ptr(cb))
|
||||
}
|
||||
|
||||
/// Generate a stub that calls entry_stub_hit
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue