Skip to content

Commit 2ecb77d

Browse files
authored
Implement Insn::Param using the SP register (#39)
1 parent 65a00a4 commit 2ecb77d

File tree

6 files changed

+98
-19
lines changed

6 files changed

+98
-19
lines changed

vm.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,7 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co
10491049
// Invalidate JIT code that assumes cfp->ep == vm_base_ptr(cfp).
10501050
if (env->iseq) {
10511051
rb_yjit_invalidate_ep_is_bp(env->iseq);
1052+
rb_zjit_invalidate_ep_is_bp(env->iseq);
10521053
}
10531054

10541055
return (VALUE)env;

zjit.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, boo
1111
void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec);
1212
void rb_zjit_profile_iseq(const rb_iseq_t *iseq);
1313
void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop);
14+
void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq);
1415
#else
15-
void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {}
16-
void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec) {}
17-
void rb_zjit_profile_iseq(const rb_iseq_t *iseq) {}
18-
void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
16+
static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {}
17+
static inline void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec) {}
18+
static inline void rb_zjit_profile_iseq(const rb_iseq_t *iseq) {}
19+
static inline void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
20+
static inline void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq) {}
1921
#endif // #if USE_YJIT
2022

2123
#endif // #ifndef ZJIT_H

zjit/src/codegen.rs

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
use crate::{
2-
asm::CodeBlock, backend::lir, 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
3-
};
1+
use crate::{asm::CodeBlock, cruby::*, debug, virtualmem::CodePtr};
2+
use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption};
3+
use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, EC, SP};
4+
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
5+
use crate::hir_type::{types::Fixnum, Type};
46

57
/// Ephemeral code generation state
68
struct JITState {
@@ -28,6 +30,15 @@ impl JITState {
2830
}
2931
opnd
3032
}
33+
34+
/// Assume that this ISEQ doesn't escape EP. Return false if it's known to escape EP.
35+
fn assume_no_ep_escape(&mut self) -> bool {
36+
if iseq_escapes_ep(self.iseq) {
37+
return false;
38+
}
39+
track_no_ep_escape_assumption(self.iseq);
40+
true
41+
}
3142
}
3243

3344
/// Compile High-level IR into machine code
@@ -104,17 +115,23 @@ fn gen_const(val: VALUE) -> Opnd {
104115
}
105116

106117
/// Compile a method/block paramter read. For now, it only supports method parameters.
107-
fn gen_param(jit: &JITState, asm: &mut Assembler, local_idx: usize) -> Option<lir::Opnd> {
108-
// Get the EP of the current CFP
109-
// TODO: Use the SP register and invalidate on EP escape
110-
let ep_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_EP);
111-
let ep_reg = asm.load(ep_opnd);
112-
113-
// Load the local variable
114-
// val = *(vm_get_ep(GET_EP(), level) - idx);
118+
fn gen_param(jit: &mut JITState, asm: &mut Assembler, local_idx: usize) -> Option<lir::Opnd> {
115119
let ep_offset = local_idx_to_ep_offset(jit.iseq, local_idx);
116-
let offs = -(SIZEOF_VALUE_I32 * ep_offset);
117-
let local_opnd = Opnd::mem(64, ep_reg, offs);
120+
121+
let local_opnd = if jit.assume_no_ep_escape() {
122+
// Create a reference to the local variable using the SP register. We assume EP == BP.
123+
// TODO: Implement the invalidation in rb_zjit_invalidate_ep_is_bp()
124+
let offs = -(SIZEOF_VALUE_I32 * (ep_offset + 1));
125+
Opnd::mem(64, SP, offs)
126+
} else {
127+
// Get the EP of the current CFP
128+
let ep_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_EP);
129+
let ep_reg = asm.load(ep_opnd);
130+
131+
// Create a reference to the local variable using cfp->ep
132+
let offs = -(SIZEOF_VALUE_I32 * ep_offset);
133+
Opnd::mem(64, ep_reg, offs)
134+
};
118135

119136
Some(local_opnd)
120137
}

zjit/src/invariants.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
1-
use crate::{cruby::{ruby_basic_operators, RedefinitionFlag}, zjit_enabled_p};
1+
use std::collections::HashSet;
2+
3+
use crate::{cruby::{ruby_basic_operators, IseqPtr, RedefinitionFlag}, state::ZJITState, zjit_enabled_p};
4+
5+
/// Used to track all of the various block references that contain assumptions
6+
/// about the state of the virtual machine.
7+
#[derive(Default)]
8+
pub struct Invariants {
9+
/// Set of ISEQs that are known to escape EP
10+
ep_escape_iseqs: HashSet<IseqPtr>,
11+
12+
/// Set of ISEQs whose JIT code assumes that it doesn't escape EP
13+
no_ep_escape_iseqs: HashSet<IseqPtr>,
14+
}
215

316
/// Called when a basic operator is redefined. Note that all the blocks assuming
417
/// the stability of different operators are invalidated together and we don't
@@ -12,3 +25,33 @@ pub extern "C" fn rb_zjit_bop_redefined(_klass: RedefinitionFlag, _bop: ruby_bas
1225

1326
unimplemented!("Invalidation on BOP redefinition is not implemented yet");
1427
}
28+
29+
/// Invalidate blocks for a given ISEQ that assumes environment pointer is
30+
/// equal to base pointer.
31+
#[unsafe(no_mangle)]
32+
pub extern "C" fn rb_zjit_invalidate_ep_is_bp(iseq: IseqPtr) {
33+
// Skip tracking EP escapes on boot. We don't need to invalidate anything during boot.
34+
if !ZJITState::has_instance() {
35+
return;
36+
}
37+
38+
// Remember that this ISEQ may escape EP
39+
let invariants = ZJITState::get_invariants();
40+
invariants.ep_escape_iseqs.insert(iseq);
41+
42+
// If the ISEQ has been compiled assuming it doesn't escape EP, invalidate the JIT code.
43+
if invariants.no_ep_escape_iseqs.contains(&iseq) {
44+
unimplemented!("Invalidation on EP escape is not implemented yet");
45+
}
46+
}
47+
48+
/// Track that JIT code for a ISEQ will assume that base pointer is equal to environment pointer.
49+
pub fn track_no_ep_escape_assumption(iseq: IseqPtr) {
50+
let invariants = ZJITState::get_invariants();
51+
invariants.no_ep_escape_iseqs.insert(iseq);
52+
}
53+
54+
/// Returns true if a given ISEQ has previously escaped environment pointer.
55+
pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool {
56+
ZJITState::get_invariants().ep_escape_iseqs.contains(&iseq)
57+
}

zjit/src/profile.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ impl IseqPayload {
9292
self.opnd_types.get(&insn_idx).map(|types| types.as_slice())
9393
}
9494

95+
/// Return true if top-two stack operands are Fixnums
9596
pub fn have_two_fixnums(&self, insn_idx: usize) -> bool {
9697
match self.get_operand_types(insn_idx) {
9798
Some([left, right]) => left.is_subtype(Fixnum) && right.is_subtype(Fixnum),

zjit/src/state.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::invariants::Invariants;
12
use crate::options::Options;
23
use crate::asm::CodeBlock;
34

@@ -8,6 +9,9 @@ pub struct ZJITState {
89

910
/// ZJIT command-line options
1011
options: Options,
12+
13+
/// Assumptions that require invalidation
14+
invariants: Invariants,
1115
}
1216

1317
/// Private singleton instance of the codegen globals
@@ -59,10 +63,16 @@ impl ZJITState {
5963
let zjit_state = ZJITState {
6064
code_block: cb,
6165
options,
66+
invariants: Invariants::default(),
6267
};
6368
unsafe { ZJIT_STATE = Some(zjit_state); }
6469
}
6570

71+
/// Return true if zjit_state has been initialized
72+
pub fn has_instance() -> bool {
73+
unsafe { ZJIT_STATE.as_mut().is_some() }
74+
}
75+
6676
/// Get a mutable reference to the codegen globals instance
6777
fn get_instance() -> &'static mut ZJITState {
6878
unsafe { ZJIT_STATE.as_mut().unwrap() }
@@ -73,8 +83,13 @@ impl ZJITState {
7383
&mut ZJITState::get_instance().code_block
7484
}
7585

76-
// Get a mutable reference to the options
86+
/// Get a mutable reference to the options
7787
pub fn get_options() -> &'static mut Options {
7888
&mut ZJITState::get_instance().options
7989
}
90+
91+
/// Get a mutable reference to the invariants
92+
pub fn get_invariants() -> &'static mut Invariants {
93+
&mut ZJITState::get_instance().invariants
94+
}
8095
}

0 commit comments

Comments
 (0)