Skip to content

Commit a677220

Browse files
authored
ZJIT: Stop duplicating context-less side exits (ruby#14215)
1 parent c30d900 commit a677220

5 files changed

Lines changed: 44 additions & 58 deletions

File tree

zjit/src/backend/arm64/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1372,7 +1372,7 @@ impl Assembler
13721372
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Option<(CodePtr, Vec<CodePtr>)> {
13731373
let asm = self.arm64_split();
13741374
let mut asm = asm.alloc_regs(regs)?;
1375-
asm.compile_side_exits()?;
1375+
asm.compile_side_exits();
13761376

13771377
// Create label instances in the code block
13781378
for (idx, name) in asm.label_names.iter().enumerate() {

zjit/src/backend/lir.rs

Lines changed: 24 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,6 @@ impl From<VALUE> for Opnd {
256256
}
257257
}
258258

259-
/// Set of things we need to restore for side exits.
260-
#[derive(Clone, Debug)]
261-
pub struct SideExitContext {
262-
pub pc: *const VALUE,
263-
pub stack: Vec<Opnd>,
264-
pub locals: Vec<Opnd>,
265-
}
266-
267259
/// Branch target (something that we can jump to)
268260
/// for branch instructions
269261
#[derive(Clone, Debug)]
@@ -275,9 +267,9 @@ pub enum Target
275267
Label(Label),
276268
/// Side exit to the interpreter
277269
SideExit {
278-
/// Context to restore on regular side exits. None for side exits right
279-
/// after JIT-to-JIT calls because we restore them before the JIT call.
280-
context: Option<SideExitContext>,
270+
pc: *const VALUE,
271+
stack: Vec<Opnd>,
272+
locals: Vec<Opnd>,
281273
/// We use this to enrich asm comments.
282274
reason: SideExitReason,
283275
/// Some if the side exit should write this label. We use it for patch points.
@@ -761,7 +753,7 @@ impl<'a> Iterator for InsnOpndIterator<'a> {
761753
Insn::Label(target) |
762754
Insn::LeaJumpTarget { target, .. } |
763755
Insn::PatchPoint(target) => {
764-
if let Target::SideExit { context: Some(SideExitContext { stack, locals, .. }), .. } = target {
756+
if let Target::SideExit { stack, locals, .. } = target {
765757
let stack_idx = self.idx;
766758
if stack_idx < stack.len() {
767759
let opnd = &stack[stack_idx];
@@ -786,7 +778,7 @@ impl<'a> Iterator for InsnOpndIterator<'a> {
786778
return Some(opnd);
787779
}
788780

789-
if let Target::SideExit { context: Some(SideExitContext { stack, locals, .. }), .. } = target {
781+
if let Target::SideExit { stack, locals, .. } = target {
790782
let stack_idx = self.idx - 1;
791783
if stack_idx < stack.len() {
792784
let opnd = &stack[stack_idx];
@@ -917,7 +909,7 @@ impl<'a> InsnOpndMutIterator<'a> {
917909
Insn::Label(target) |
918910
Insn::LeaJumpTarget { target, .. } |
919911
Insn::PatchPoint(target) => {
920-
if let Target::SideExit { context: Some(SideExitContext { stack, locals, .. }), .. } = target {
912+
if let Target::SideExit { stack, locals, .. } = target {
921913
let stack_idx = self.idx;
922914
if stack_idx < stack.len() {
923915
let opnd = &mut stack[stack_idx];
@@ -942,7 +934,7 @@ impl<'a> InsnOpndMutIterator<'a> {
942934
return Some(opnd);
943935
}
944936

945-
if let Target::SideExit { context: Some(SideExitContext { stack, locals, .. }), .. } = target {
937+
if let Target::SideExit { stack, locals, .. } = target {
946938
let stack_idx = self.idx - 1;
947939
if stack_idx < stack.len() {
948940
let opnd = &mut stack[stack_idx];
@@ -1555,8 +1547,7 @@ impl Assembler
15551547
}
15561548

15571549
/// Compile Target::SideExit and convert it into Target::CodePtr for all instructions
1558-
#[must_use]
1559-
pub fn compile_side_exits(&mut self) -> Option<()> {
1550+
pub fn compile_side_exits(&mut self) {
15601551
let mut targets = HashMap::new();
15611552
for (idx, insn) in self.insns.iter().enumerate() {
15621553
if let Some(target @ Target::SideExit { .. }) = insn.target() {
@@ -1567,7 +1558,7 @@ impl Assembler
15671558
for (idx, target) in targets {
15681559
// Compile a side exit. Note that this is past the split pass and alloc_regs(),
15691560
// so you can't use a VReg or an instruction that needs to be split.
1570-
if let Target::SideExit { context, reason, label } = target {
1561+
if let Target::SideExit { pc, stack, locals, reason, label } = target {
15711562
asm_comment!(self, "Exit: {reason}");
15721563
let side_exit_label = if let Some(label) = label {
15731564
Target::Label(label)
@@ -1578,26 +1569,24 @@ impl Assembler
15781569

15791570
// Restore the PC and the stack for regular side exits. We don't do this for
15801571
// side exits right after JIT-to-JIT calls, which restore them before the call.
1581-
if let Some(SideExitContext { pc, stack, locals }) = context {
1582-
asm_comment!(self, "write stack slots: {stack:?}");
1583-
for (idx, &opnd) in stack.iter().enumerate() {
1584-
self.store(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), opnd);
1585-
}
1572+
asm_comment!(self, "write stack slots: {stack:?}");
1573+
for (idx, &opnd) in stack.iter().enumerate() {
1574+
self.store(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), opnd);
1575+
}
15861576

1587-
asm_comment!(self, "write locals: {locals:?}");
1588-
for (idx, &opnd) in locals.iter().enumerate() {
1589-
self.store(Opnd::mem(64, SP, (-local_size_and_idx_to_ep_offset(locals.len(), idx) - 1) * SIZEOF_VALUE_I32), opnd);
1590-
}
1577+
asm_comment!(self, "write locals: {locals:?}");
1578+
for (idx, &opnd) in locals.iter().enumerate() {
1579+
self.store(Opnd::mem(64, SP, (-local_size_and_idx_to_ep_offset(locals.len(), idx) - 1) * SIZEOF_VALUE_I32), opnd);
1580+
}
15911581

1592-
asm_comment!(self, "save cfp->pc");
1593-
self.load_into(Opnd::Reg(Assembler::SCRATCH_REG), Opnd::const_ptr(pc));
1594-
self.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::Reg(Assembler::SCRATCH_REG));
1582+
asm_comment!(self, "save cfp->pc");
1583+
self.load_into(Opnd::Reg(Assembler::SCRATCH_REG), Opnd::const_ptr(pc));
1584+
self.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::Reg(Assembler::SCRATCH_REG));
15951585

1596-
asm_comment!(self, "save cfp->sp");
1597-
self.lea_into(Opnd::Reg(Assembler::SCRATCH_REG), Opnd::mem(64, SP, stack.len() as i32 * SIZEOF_VALUE_I32));
1598-
let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP);
1599-
self.store(cfp_sp, Opnd::Reg(Assembler::SCRATCH_REG));
1600-
}
1586+
asm_comment!(self, "save cfp->sp");
1587+
self.lea_into(Opnd::Reg(Assembler::SCRATCH_REG), Opnd::mem(64, SP, stack.len() as i32 * SIZEOF_VALUE_I32));
1588+
let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP);
1589+
self.store(cfp_sp, Opnd::Reg(Assembler::SCRATCH_REG));
16011590

16021591
asm_comment!(self, "exit to the interpreter");
16031592
self.frame_teardown(&[]); // matching the setup in :bb0-prologue:
@@ -1607,7 +1596,6 @@ impl Assembler
16071596
*self.insns[idx].target_mut().unwrap() = side_exit_label;
16081597
}
16091598
}
1610-
Some(())
16111599
}
16121600
}
16131601

zjit/src/backend/x86_64/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ impl Assembler
895895
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Option<(CodePtr, Vec<CodePtr>)> {
896896
let asm = self.x86_split();
897897
let mut asm = asm.alloc_regs(regs)?;
898-
asm.compile_side_exits()?;
898+
asm.compile_side_exits();
899899

900900
// Create label instances in the code block
901901
for (idx, name) in asm.label_names.iter().enumerate() {

zjit/src/codegen.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_ise
99
use crate::state::ZJITState;
1010
use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::compile_time_ns};
1111
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
12-
use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, SideExitContext, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SP};
12+
use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SP};
1313
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, Invariant, RangeType, SideExitReason, SideExitReason::*, SpecialObjectType, SELF_PARAM_IDX};
1414
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
1515
use crate::hir_type::{types, Type};
@@ -908,7 +908,7 @@ fn gen_send_without_block_direct(
908908
asm_comment!(asm, "side-exit if callee side-exits");
909909
asm.cmp(ret, Qundef.into());
910910
// Restore the C stack pointer on exit
911-
asm.je(Target::SideExit { context: None, reason: CalleeSideExit, label: None });
911+
asm.je(ZJITState::get_exit_code().into());
912912

913913
asm_comment!(asm, "restore SP register for the caller");
914914
let new_sp = asm.sub(SP, sp_offset.into());
@@ -1339,11 +1339,9 @@ fn build_side_exit(jit: &mut JITState, state: &FrameState, reason: SideExitReaso
13391339
}
13401340

13411341
let target = Target::SideExit {
1342-
context: Some(SideExitContext {
1343-
pc: state.pc,
1344-
stack,
1345-
locals,
1346-
}),
1342+
pc: state.pc,
1343+
stack,
1344+
locals,
13471345
reason,
13481346
label,
13491347
};
@@ -1414,7 +1412,7 @@ c_callable! {
14141412
if cb.has_dropped_bytes() || payload.status == IseqStatus::CantCompile {
14151413
// Exit to the interpreter
14161414
set_pc_and_sp(iseq, ec, sp);
1417-
return ZJITState::get_stub_exit().raw_ptr(cb);
1415+
return ZJITState::get_exit_code().raw_ptr(cb);
14181416
}
14191417

14201418
// Otherwise, attempt to compile the ISEQ. We have to mark_all_executable() beyond this point.
@@ -1424,7 +1422,7 @@ c_callable! {
14241422
} else {
14251423
// Exit to the interpreter
14261424
set_pc_and_sp(iseq, ec, sp);
1427-
ZJITState::get_stub_exit()
1425+
ZJITState::get_exit_code()
14281426
};
14291427
cb.mark_all_executable();
14301428
code_ptr.raw_ptr(cb)
@@ -1494,12 +1492,12 @@ fn gen_function_stub(cb: &mut CodeBlock, iseq: IseqPtr, branch: Rc<Branch>) -> O
14941492
asm.compile(cb)
14951493
}
14961494

1497-
/// Generate a trampoline that is used when a function stub fails to compile the ISEQ
1498-
pub fn gen_stub_exit(cb: &mut CodeBlock) -> Option<CodePtr> {
1495+
/// Generate a trampoline that is used when a function exits without restoring PC and the stack
1496+
pub fn gen_exit(cb: &mut CodeBlock) -> Option<CodePtr> {
14991497
let mut asm = Assembler::new();
15001498

15011499
asm_comment!(asm, "exit from function stub");
1502-
asm.frame_teardown(lir::JIT_PRESERVED_REGS);
1500+
asm.frame_teardown(&[]); // matching the setup in :bb0-prologue:
15031501
asm.cret(Qundef.into());
15041502

15051503
asm.compile(cb).map(|(code_ptr, gc_offsets)| {

zjit/src/state.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::codegen::gen_stub_exit;
1+
use crate::codegen::gen_exit;
22
use crate::cruby::{self, rb_bug_panic_hook, rb_vm_insns_count, EcPtr, Qnil, VALUE};
33
use crate::cruby_methods;
44
use crate::invariants::Invariants;
@@ -33,8 +33,8 @@ pub struct ZJITState {
3333
/// Properties of core library methods
3434
method_annotations: cruby_methods::Annotations,
3535

36-
/// Side-exit trampoline used when it fails to compile the ISEQ for a function stub
37-
stub_exit: CodePtr,
36+
/// Trampoline to side-exit without restoring PC or the stack
37+
exit_code: CodePtr,
3838
}
3939

4040
/// Private singleton instance of the codegen globals
@@ -83,7 +83,7 @@ impl ZJITState {
8383
#[cfg(test)]
8484
let mut cb = CodeBlock::new_dummy();
8585

86-
let stub_exit = gen_stub_exit(&mut cb).unwrap();
86+
let exit_code = gen_exit(&mut cb).unwrap();
8787

8888
// Initialize the codegen globals instance
8989
let zjit_state = ZJITState {
@@ -92,7 +92,7 @@ impl ZJITState {
9292
invariants: Invariants::default(),
9393
assert_compiles: false,
9494
method_annotations: cruby_methods::init(),
95-
stub_exit,
95+
exit_code,
9696
};
9797
unsafe { ZJIT_STATE = Some(zjit_state); }
9898
}
@@ -169,9 +169,9 @@ impl ZJITState {
169169
}
170170
}
171171

172-
/// Return a code pointer to the side-exit trampoline for function stubs
173-
pub fn get_stub_exit() -> CodePtr {
174-
ZJITState::get_instance().stub_exit
172+
/// Return a code pointer to the side-exit trampoline
173+
pub fn get_exit_code() -> CodePtr {
174+
ZJITState::get_instance().exit_code
175175
}
176176
}
177177

0 commit comments

Comments
 (0)