Skip to content

Commit 6f0244e

Browse files
authored
ZJIT: Pull GetEP out of GetLocal (with level > 0) (ruby#16215)
Closes: Shopify#944 As a first step toward enabling CSE/LVN-based deduplication of frame EP loads, splitting `GetLocal` handling for `level > 0` into an explicit `GetEP + LoadField` path. A follow-up PR will handle the `level == 0` case.
1 parent ffbe288 commit 6f0244e

3 files changed

Lines changed: 76 additions & 38 deletions

File tree

zjit/src/hir.rs

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ pub enum Insn {
854854
/// Get a local variable from a higher scope or the heap.
855855
/// If `use_sp` is true, it uses the SP register to optimize the read.
856856
/// `rest_param` is used by infer_types to infer the ArrayExact type.
857+
/// TODO: Replace the level == 0 + use_sp path with LoadSP + LoadField.
857858
GetLocal { level: u32, ep_offset: u32, use_sp: bool, rest_param: bool },
858859
/// Check whether VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM is set in the environment flags.
859860
/// Returns CBool (0/1).
@@ -1169,7 +1170,7 @@ impl Insn {
11691170
Insn::GetEP { .. } => effects::Empty,
11701171
Insn::GetLEP { .. } => effects::Empty,
11711172
Insn::LoadSelf { .. } => Effect::read_write(abstract_heaps::Frame, abstract_heaps::Empty),
1172-
Insn::LoadField { .. } => Effect::read_write(abstract_heaps::Other, abstract_heaps::Empty),
1173+
Insn::LoadField { .. } => Effect::read_write(abstract_heaps::Memory, abstract_heaps::Empty),
11731174
Insn::StoreField { .. } => effects::Any,
11741175
Insn::WriteBarrier { .. } => effects::Any,
11751176
Insn::GetLocal { .. } => Effect::read_write(abstract_heaps::Locals, abstract_heaps::Empty),
@@ -1272,6 +1273,15 @@ pub struct InsnPrinter<'a> {
12721273
iseq: Option<IseqPtr>,
12731274
}
12741275

1276+
fn get_local_var_id(iseq: IseqPtr, level: u32, ep_offset: u32) -> ID {
1277+
let mut current_iseq = iseq;
1278+
for _ in 0..level {
1279+
current_iseq = unsafe { rb_get_iseq_body_parent_iseq(current_iseq) };
1280+
}
1281+
let local_idx = ep_offset_to_local_idx(current_iseq, ep_offset);
1282+
unsafe { rb_zjit_local_id(current_iseq, local_idx.try_into().unwrap()) }
1283+
}
1284+
12751285
/// Get the name of a local variable given iseq, level, and ep_offset.
12761286
/// Returns
12771287
/// - `":name"` if iseq is available and name is a real identifier,
@@ -1281,12 +1291,7 @@ pub struct InsnPrinter<'a> {
12811291
///
12821292
/// This mimics local_var_name() from iseq.c.
12831293
fn get_local_var_name_for_printer(iseq: Option<IseqPtr>, level: u32, ep_offset: u32) -> Option<String> {
1284-
let mut current_iseq = iseq?;
1285-
for _ in 0..level {
1286-
current_iseq = unsafe { rb_get_iseq_body_parent_iseq(current_iseq) };
1287-
}
1288-
let local_idx = ep_offset_to_local_idx(current_iseq, ep_offset);
1289-
let id: ID = unsafe { rb_zjit_local_id(current_iseq, local_idx.try_into().unwrap()) };
1294+
let id = get_local_var_id(iseq?, level, ep_offset);
12901295

12911296
if id.0 == 0 || unsafe { rb_id2str(id) } == Qfalse {
12921297
return Some(String::from("<empty>"));
@@ -3057,6 +3062,29 @@ impl Function {
30573062
self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_ELTS_SHARED as u64), mask_name: Some(ID!(RUBY_ELTS_SHARED)), reason: SideExitReason::GuardNotShared, state });
30583063
}
30593064

3065+
// TODO: This helper is currently used for level>0 local reads only.
3066+
// Split GetLocal(level==0) into explicit SP/EP helpers in a follow-up.
3067+
fn get_local_from_ep(
3068+
&mut self,
3069+
block: BlockId,
3070+
ep_offset: u32,
3071+
level: u32,
3072+
return_type: Type,
3073+
) -> InsnId {
3074+
let ep = self.push_insn(block, Insn::GetEP { level });
3075+
let local_id = get_local_var_id(self.iseq, level, ep_offset);
3076+
let ep_offset = i32::try_from(ep_offset)
3077+
.unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to i32"));
3078+
let offset = -(SIZEOF_VALUE_I32 * ep_offset);
3079+
3080+
self.push_insn(block, Insn::LoadField {
3081+
recv: ep,
3082+
id: local_id,
3083+
offset,
3084+
return_type,
3085+
})
3086+
}
3087+
30603088
/// Rewrite eligible Send opcodes into SendDirect
30613089
/// opcodes if we know the target ISEQ statically. This removes run-time method lookups and
30623090
/// opens the door for inlining.
@@ -6836,7 +6864,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
68366864
}
68376865
YARVINSN_getlocal_WC_1 => {
68386866
let ep_offset = get_arg(pc, 0).as_u32();
6839-
state.stack_push(fun.push_insn(block, Insn::GetLocal { ep_offset, level: 1, use_sp: false, rest_param: false }));
6867+
state.stack_push(fun.get_local_from_ep(block, ep_offset, 1, types::BasicObject));
68406868
}
68416869
YARVINSN_setlocal_WC_1 => {
68426870
let ep_offset = get_arg(pc, 0).as_u32();
@@ -6845,7 +6873,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
68456873
YARVINSN_getlocal => {
68466874
let ep_offset = get_arg(pc, 0).as_u32();
68476875
let level = get_arg(pc, 1).as_u32();
6848-
state.stack_push(fun.push_insn(block, Insn::GetLocal { ep_offset, level, use_sp: false, rest_param: false }));
6876+
if level == 0 {
6877+
state.stack_push(fun.push_insn(block, Insn::GetLocal { ep_offset, level, use_sp: false, rest_param: false }));
6878+
} else {
6879+
state.stack_push(fun.get_local_from_ep(block, ep_offset, level, types::BasicObject));
6880+
}
68496881
}
68506882
YARVINSN_setlocal => {
68516883
let ep_offset = get_arg(pc, 0).as_u32();
@@ -6956,12 +6988,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
69566988
}));
69576989

69586990
// Push modified block: read Proc from EP.
6959-
let modified_val = fun.push_insn(modified_block, Insn::GetLocal {
6960-
ep_offset,
6961-
level,
6962-
use_sp: false,
6963-
rest_param: false,
6964-
});
6991+
let modified_val = if level == 0 {
6992+
fun.push_insn(modified_block, Insn::GetLocal { ep_offset, level, use_sp: false, rest_param: false })
6993+
} else {
6994+
fun.get_local_from_ep(modified_block, ep_offset, level, types::BasicObject)
6995+
};
69656996
finish_getblockparam_branch(
69666997
&mut fun,
69676998
modified_block,

zjit/src/hir/opt_tests.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4461,14 +4461,15 @@ mod hir_opt_tests {
44614461
bb3(v6:BasicObject):
44624462
v10:CBool = IsBlockParamModified l1
44634463
IfTrue v10, bb4(v6)
4464-
v19:BasicObject = GetBlockParam :block, l1, EP@3
4465-
Jump bb6(v6, v19)
4464+
v20:BasicObject = GetBlockParam :block, l1, EP@3
4465+
Jump bb6(v6, v20)
44664466
bb4(v11:BasicObject):
4467-
v17:BasicObject = GetLocal :block, l1, EP@3
4468-
Jump bb6(v11, v17)
4469-
bb6(v21:BasicObject, v22:BasicObject):
4467+
v17:CPtr = GetEP 1
4468+
v18:BasicObject = LoadField v17, :block@0x1000
4469+
Jump bb6(v11, v18)
4470+
bb6(v22:BasicObject, v23:BasicObject):
44704471
CheckInterrupts
4471-
Return v22
4472+
Return v23
44724473
");
44734474
}
44744475

zjit/src/hir/tests.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -946,18 +946,23 @@ pub mod hir_build_tests {
946946
v4:BasicObject = LoadArg :self@0
947947
Jump bb3(v4)
948948
bb3(v6:BasicObject):
949-
v10:BasicObject = GetLocal :l2, l2, EP@4
950-
SetLocal :l1, l1, EP@3, v10
951-
v15:BasicObject = GetLocal :l1, l1, EP@3
952-
v17:BasicObject = GetLocal :l2, l2, EP@4
953-
v20:BasicObject = Send v15, :+, v17 # SendFallbackReason: Uncategorized(opt_plus)
954-
SetLocal :l2, l2, EP@4, v20
955-
v25:BasicObject = GetLocal :l2, l2, EP@4
956-
v27:BasicObject = GetLocal :l3, l3, EP@5
957-
v30:BasicObject = Send v25, :+, v27 # SendFallbackReason: Uncategorized(opt_plus)
958-
SetLocal :l3, l3, EP@5, v30
949+
v10:CPtr = GetEP 2
950+
v11:BasicObject = LoadField v10, :l2@0x1000
951+
SetLocal :l1, l1, EP@3, v11
952+
v16:CPtr = GetEP 1
953+
v17:BasicObject = LoadField v16, :l1@0x1001
954+
v19:CPtr = GetEP 2
955+
v20:BasicObject = LoadField v19, :l2@0x1000
956+
v23:BasicObject = Send v17, :+, v20 # SendFallbackReason: Uncategorized(opt_plus)
957+
SetLocal :l2, l2, EP@4, v23
958+
v28:CPtr = GetEP 2
959+
v29:BasicObject = LoadField v28, :l2@0x1000
960+
v31:CPtr = GetEP 3
961+
v32:BasicObject = LoadField v31, :l3@0x1002
962+
v35:BasicObject = Send v29, :+, v32 # SendFallbackReason: Uncategorized(opt_plus)
963+
SetLocal :l3, l3, EP@5, v35
959964
CheckInterrupts
960-
Return v30
965+
Return v35
961966
"
962967
);
963968
}
@@ -3088,14 +3093,15 @@ pub mod hir_build_tests {
30883093
IfTrue v10, bb4(v6)
30893094
Jump bb5(v6)
30903095
bb4(v11:BasicObject):
3091-
v17:BasicObject = GetLocal :block, l1, EP@3
3092-
Jump bb6(v11, v17)
3096+
v17:CPtr = GetEP 1
3097+
v18:BasicObject = LoadField v17, :block@0x1000
3098+
Jump bb6(v11, v18)
30933099
bb5(v13:BasicObject):
3094-
v19:BasicObject = GetBlockParam :block, l1, EP@3
3095-
Jump bb6(v13, v19)
3096-
bb6(v21:BasicObject, v22:BasicObject):
3100+
v20:BasicObject = GetBlockParam :block, l1, EP@3
3101+
Jump bb6(v13, v20)
3102+
bb6(v22:BasicObject, v23:BasicObject):
30973103
CheckInterrupts
3098-
Return v22
3104+
Return v23
30993105
");
31003106
}
31013107

0 commit comments

Comments
 (0)