Skip to content

Commit 6bf7a17

Browse files
authored
YJIT: Load registers on JIT entry to reuse blocks (#12355)
1 parent 0b2f034 commit 6bf7a17

File tree

2 files changed

+69
-20
lines changed

2 files changed

+69
-20
lines changed

yjit/src/codegen.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,14 +1061,13 @@ fn gen_leave_exception(ocb: &mut OutlinedCb) -> Option<CodePtr> {
10611061
pub fn gen_entry_chain_guard(
10621062
asm: &mut Assembler,
10631063
ocb: &mut OutlinedCb,
1064-
iseq: IseqPtr,
1065-
insn_idx: u16,
1064+
blockid: BlockId,
10661065
) -> Option<PendingEntryRef> {
10671066
let entry = new_pending_entry();
10681067
let stub_addr = gen_entry_stub(entry.uninit_entry.as_ptr() as usize, ocb)?;
10691068

10701069
let pc_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC);
1071-
let expected_pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx.into()) };
1070+
let expected_pc = unsafe { rb_iseq_pc_at_idx(blockid.iseq, blockid.idx.into()) };
10721071
let expected_pc_opnd = Opnd::const_ptr(expected_pc as *const u8);
10731072

10741073
asm_comment!(asm, "guard expected PC");
@@ -1087,10 +1086,11 @@ pub fn gen_entry_chain_guard(
10871086
pub fn gen_entry_prologue(
10881087
cb: &mut CodeBlock,
10891088
ocb: &mut OutlinedCb,
1090-
iseq: IseqPtr,
1091-
insn_idx: u16,
1089+
blockid: BlockId,
1090+
stack_size: u8,
10921091
jit_exception: bool,
1093-
) -> Option<CodePtr> {
1092+
) -> Option<(CodePtr, RegMapping)> {
1093+
let iseq = blockid.iseq;
10941094
let code_ptr = cb.get_write_ptr();
10951095

10961096
let mut asm = Assembler::new(unsafe { get_iseq_body_local_table_size(iseq) });
@@ -1145,10 +1145,11 @@ pub fn gen_entry_prologue(
11451145
// If they don't match, then we'll jump to an entry stub and generate
11461146
// another PC check and entry there.
11471147
let pending_entry = if unsafe { get_iseq_flags_has_opt(iseq) } || jit_exception {
1148-
Some(gen_entry_chain_guard(&mut asm, ocb, iseq, insn_idx)?)
1148+
Some(gen_entry_chain_guard(&mut asm, ocb, blockid)?)
11491149
} else {
11501150
None
11511151
};
1152+
let reg_mapping = gen_entry_reg_mapping(&mut asm, blockid, stack_size);
11521153

11531154
asm.compile(cb, Some(ocb))?;
11541155

@@ -1166,8 +1167,37 @@ pub fn gen_entry_prologue(
11661167
.ok().expect("PendingEntry should be unique");
11671168
iseq_payload.entries.push(pending_entry.into_entry());
11681169
}
1169-
Some(code_ptr)
1170+
Some((code_ptr, reg_mapping))
1171+
}
1172+
}
1173+
1174+
/// Generate code to load registers for a JIT entry. When the entry block is compiled for
1175+
/// the first time, it loads no register. When it has been already compiled as a callee
1176+
/// block, it loads some registers to reuse the block.
1177+
pub fn gen_entry_reg_mapping(asm: &mut Assembler, blockid: BlockId, stack_size: u8) -> RegMapping {
1178+
// Find an existing callee block. If it's not found or uses no register, skip loading registers.
1179+
let mut ctx = Context::default();
1180+
ctx.set_stack_size(stack_size);
1181+
let reg_mapping = find_most_compatible_reg_mapping(blockid, &ctx).unwrap_or(RegMapping::default());
1182+
if reg_mapping == RegMapping::default() {
1183+
return reg_mapping;
11701184
}
1185+
1186+
// If found, load the same registers to reuse the block.
1187+
asm_comment!(asm, "reuse maps: {:?}", reg_mapping);
1188+
let local_table_size: u32 = unsafe { get_iseq_body_local_table_size(blockid.iseq) }.try_into().unwrap();
1189+
for &reg_opnd in reg_mapping.get_reg_opnds().iter() {
1190+
match reg_opnd {
1191+
RegOpnd::Local(local_idx) => {
1192+
let loaded_reg = TEMP_REGS[reg_mapping.get_reg(reg_opnd).unwrap()];
1193+
let loaded_temp = asm.local_opnd(local_table_size - local_idx as u32 + VM_ENV_DATA_SIZE - 1);
1194+
asm.load_into(Opnd::Reg(loaded_reg), loaded_temp);
1195+
}
1196+
RegOpnd::Stack(_) => unreachable!("find_most_compatible_reg_mapping should not leave {:?}", reg_opnd),
1197+
}
1198+
}
1199+
1200+
reg_mapping
11711201
}
11721202

11731203
// Generate code to check for interrupts and take a side-exit.

yjit/src/core.rs

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3201,16 +3201,33 @@ pub fn gen_entry_point(iseq: IseqPtr, ec: EcPtr, jit_exception: bool) -> Option<
32013201
let cb = CodegenGlobals::get_inline_cb();
32023202
let ocb = CodegenGlobals::get_outlined_cb();
32033203

3204+
let code_ptr = gen_entry_point_body(blockid, stack_size, ec, jit_exception, cb, ocb);
3205+
3206+
cb.mark_all_executable();
3207+
ocb.unwrap().mark_all_executable();
3208+
3209+
code_ptr
3210+
}
3211+
3212+
fn gen_entry_point_body(blockid: BlockId, stack_size: u8, ec: EcPtr, jit_exception: bool, cb: &mut CodeBlock, ocb: &mut OutlinedCb) -> Option<*const u8> {
32043213
// Write the interpreter entry prologue. Might be NULL when out of memory.
3205-
let code_ptr = gen_entry_prologue(cb, ocb, iseq, insn_idx, jit_exception);
3214+
let (code_ptr, reg_mapping) = gen_entry_prologue(cb, ocb, blockid, stack_size, jit_exception)?;
32063215

3207-
// Try to generate code for the entry block
3216+
// Find or compile a block version
32083217
let mut ctx = Context::default();
32093218
ctx.stack_size = stack_size;
3210-
let block = gen_block_series(blockid, &ctx, ec, cb, ocb);
3211-
3212-
cb.mark_all_executable();
3213-
ocb.unwrap().mark_all_executable();
3219+
ctx.reg_mapping = reg_mapping;
3220+
let block = match find_block_version(blockid, &ctx) {
3221+
// If an existing block is found, generate a jump to the block.
3222+
Some(blockref) => {
3223+
let mut asm = Assembler::new_without_iseq();
3224+
asm.jmp(unsafe { blockref.as_ref() }.start_addr.into());
3225+
asm.compile(cb, Some(ocb))?;
3226+
Some(blockref)
3227+
}
3228+
// If this block hasn't yet been compiled, generate blocks after the entry guard.
3229+
None => gen_block_series(blockid, &ctx, ec, cb, ocb),
3230+
};
32143231

32153232
match block {
32163233
// Compilation failed
@@ -3235,7 +3252,7 @@ pub fn gen_entry_point(iseq: IseqPtr, ec: EcPtr, jit_exception: bool) -> Option<
32353252
incr_counter!(compiled_iseq_entry);
32363253

32373254
// Compilation successful and block not empty
3238-
code_ptr.map(|ptr| ptr.raw_ptr(cb))
3255+
Some(code_ptr.raw_ptr(cb))
32393256
}
32403257

32413258
// Change the entry's jump target from an entry stub to a next entry
@@ -3310,20 +3327,22 @@ fn entry_stub_hit_body(
33103327
let cfp = unsafe { get_ec_cfp(ec) };
33113328
let iseq = unsafe { get_cfp_iseq(cfp) };
33123329
let insn_idx = iseq_pc_to_insn_idx(iseq, unsafe { get_cfp_pc(cfp) })?;
3330+
let blockid = BlockId { iseq, idx: insn_idx };
33133331
let stack_size: u8 = unsafe {
33143332
u8::try_from(get_cfp_sp(cfp).offset_from(get_cfp_bp(cfp))).ok()?
33153333
};
33163334

33173335
// Compile a new entry guard as a next entry
33183336
let next_entry = cb.get_write_ptr();
3319-
let mut asm = Assembler::new_without_iseq();
3320-
let pending_entry = gen_entry_chain_guard(&mut asm, ocb, iseq, insn_idx)?;
3337+
let mut asm = Assembler::new(unsafe { get_iseq_body_local_table_size(iseq) });
3338+
let pending_entry = gen_entry_chain_guard(&mut asm, ocb, blockid)?;
3339+
let reg_mapping = gen_entry_reg_mapping(&mut asm, blockid, stack_size);
33213340
asm.compile(cb, Some(ocb))?;
33223341

33233342
// Find or compile a block version
3324-
let blockid = BlockId { iseq, idx: insn_idx };
33253343
let mut ctx = Context::default();
33263344
ctx.stack_size = stack_size;
3345+
ctx.reg_mapping = reg_mapping;
33273346
let blockref = match find_block_version(blockid, &ctx) {
33283347
// If an existing block is found, generate a jump to the block.
33293348
Some(blockref) => {
@@ -3347,8 +3366,8 @@ fn entry_stub_hit_body(
33473366
get_or_create_iseq_payload(iseq).entries.push(pending_entry.into_entry());
33483367
}
33493368

3350-
// Let the stub jump to the block
3351-
blockref.map(|block| unsafe { block.as_ref() }.start_addr.raw_ptr(cb))
3369+
// Let the stub jump to the entry to load entry registers
3370+
Some(next_entry.raw_ptr(cb))
33523371
}
33533372

33543373
/// Generate a stub that calls entry_stub_hit

0 commit comments

Comments
 (0)