YJIT: Load registers on JIT entry to reuse blocks (#12355)

This commit is contained in:
Takashi Kokubun 2024-12-17 09:32:42 -08:00 committed by GitHub
parent 0b2f034208
commit 6bf7a1765f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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

View file

@ -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 &reg_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(

View file

@ -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