Skip to content

Commit 364e499

Browse files
committed
ZJIT: Merge local invalidation across block predecessors
1 parent 8fdf434 commit 364e499

2 files changed

Lines changed: 149 additions & 0 deletions

File tree

‎zjit/src/codegen_tests.rs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,6 +1737,21 @@ fn test_no_ep_escape_patch_point_after_send_does_not_repeat_send() {
17371737
assert_snapshot!(assert_compiles_allowing_exits("[test, test, test]"), @"[1, 2, 3]");
17381738
}
17391739

1740+
#[test]
1741+
fn test_local_invalidation_at_branch_merge_after_non_leaf_call() {
1742+
eval(r#"
1743+
def test(v)
1744+
y = 1
1745+
binding.local_variable_set(:y, v) if v
1746+
y
1747+
end
1748+
1749+
30.times { test(1) }
1750+
"#);
1751+
1752+
assert_snapshot!(assert_compiles_allowing_exits("test(2)"), @"2");
1753+
}
1754+
17401755
#[test]
17411756
fn test_function_stub_exit_initializes_block_param() {
17421757
set_mem_bytes(1024 * 1024);

‎zjit/src/hir.rs‎

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9001,6 +9001,133 @@ fn invalidates_locals(opcode: u32, operands: *const VALUE) -> bool {
90019001
}
90029002
}
90039003

9004+
fn enqueue_local_invalidation_state(
9005+
block: BlockId,
9006+
insn_idx: u32,
9007+
local_inval: bool,
9008+
entry_states: &mut HashMap<BlockId, bool>,
9009+
queue: &mut VecDeque<(BlockId, u32, bool)>,
9010+
) {
9011+
match entry_states.entry(block) {
9012+
std::collections::hash_map::Entry::Vacant(entry) => {
9013+
entry.insert(local_inval);
9014+
queue.push_back((block, insn_idx, local_inval));
9015+
}
9016+
std::collections::hash_map::Entry::Occupied(mut entry)
9017+
if local_inval && !*entry.get() =>
9018+
{
9019+
entry.insert(true);
9020+
queue.push_back((block, insn_idx, true));
9021+
}
9022+
_ => {}
9023+
}
9024+
}
9025+
9026+
/// Compute whether locals may have been invalidated on any path into each bytecode block.
9027+
/// Blocks are compiled once, so this must reach a fixed point before code generation rather than
9028+
/// inheriting the state from whichever predecessor happens to be visited first.
9029+
fn local_invalidation_at_block_entries(
9030+
iseq: *const rb_iseq_t,
9031+
jit_entry_insns: &[u32],
9032+
insn_idx_to_block: &HashMap<u32, BlockId>,
9033+
ep_escaped: bool,
9034+
) -> HashMap<BlockId, bool> {
9035+
let mut entry_states = HashMap::new();
9036+
let mut queue = VecDeque::new();
9037+
9038+
for &insn_idx in jit_entry_insns {
9039+
enqueue_local_invalidation_state(
9040+
insn_idx_to_block[&insn_idx],
9041+
insn_idx,
9042+
false,
9043+
&mut entry_states,
9044+
&mut queue,
9045+
);
9046+
}
9047+
9048+
let iseq_size = unsafe { get_iseq_encoded_size(iseq) };
9049+
while let Some((_block, mut insn_idx, mut local_inval)) = queue.pop_front() {
9050+
while insn_idx < iseq_size {
9051+
let pc = unsafe { rb_iseq_pc_at_idx(iseq, insn_idx) };
9052+
let opcode: u32 = unsafe { rb_zjit_insn_to_bare_insn(rb_iseq_opcode_at_pc(iseq, pc)) }
9053+
.try_into()
9054+
.unwrap();
9055+
9056+
if invalidates_locals(opcode, unsafe { pc.offset(1) }) {
9057+
local_inval = true;
9058+
}
9059+
9060+
if !ep_escaped && local_inval {
9061+
let level_zero_local_access = match opcode {
9062+
YARVINSN_getlocal_WC_0 | YARVINSN_setlocal_WC_0 => true,
9063+
YARVINSN_getlocal | YARVINSN_setlocal => get_arg(pc, 1).as_u32() == 0,
9064+
YARVINSN_checkkeyword => true,
9065+
_ => false,
9066+
};
9067+
if level_zero_local_access {
9068+
// Code generation inserts NoEPEscape here and clears this state.
9069+
local_inval = false;
9070+
}
9071+
}
9072+
9073+
insn_idx += insn_len(opcode as usize);
9074+
match opcode {
9075+
YARVINSN_branchunless
9076+
| YARVINSN_branchif
9077+
| YARVINSN_branchnil
9078+
| YARVINSN_branchunless_without_ints
9079+
| YARVINSN_branchif_without_ints
9080+
| YARVINSN_branchnil_without_ints => {
9081+
let target_idx = insn_idx_at_offset(insn_idx, get_arg(pc, 0).as_i64());
9082+
enqueue_local_invalidation_state(
9083+
insn_idx_to_block[&target_idx],
9084+
target_idx,
9085+
local_inval,
9086+
&mut entry_states,
9087+
&mut queue,
9088+
);
9089+
}
9090+
YARVINSN_opt_new => {
9091+
let target_idx = insn_idx_at_offset(insn_idx, get_arg(pc, 1).as_i64());
9092+
enqueue_local_invalidation_state(
9093+
insn_idx_to_block[&target_idx],
9094+
target_idx,
9095+
local_inval,
9096+
&mut entry_states,
9097+
&mut queue,
9098+
);
9099+
}
9100+
YARVINSN_jump | YARVINSN_jump_without_ints => {
9101+
let target_idx = insn_idx_at_offset(insn_idx, get_arg(pc, 0).as_i64());
9102+
enqueue_local_invalidation_state(
9103+
insn_idx_to_block[&target_idx],
9104+
target_idx,
9105+
local_inval,
9106+
&mut entry_states,
9107+
&mut queue,
9108+
);
9109+
break;
9110+
}
9111+
YARVINSN_leave | YARVINSN_opt_invokebuiltin_delegate_leave | YARVINSN_throw => break,
9112+
_ => {}
9113+
}
9114+
9115+
if let Some(&target) = insn_idx_to_block.get(&insn_idx) {
9116+
enqueue_local_invalidation_state(
9117+
target,
9118+
insn_idx,
9119+
local_inval,
9120+
&mut entry_states,
9121+
&mut queue,
9122+
);
9123+
break;
9124+
}
9125+
}
9126+
}
9127+
9128+
entry_states
9129+
}
9130+
90049131
/// The index of the self parameter in the HIR function
90059132
pub const SELF_PARAM_IDX: usize = 0;
90069133

@@ -9172,6 +9299,12 @@ fn add_iseq_to_hir(
91729299
// its EP is shared with other frames.
91739300
let seen_ep_escape = iseq_seen_ep_escape(iseq);
91749301
let ep_escaped = ep_starts_escaped || seen_ep_escape;
9302+
let local_inval_at_block_entry = local_invalidation_at_block_entries(
9303+
iseq,
9304+
&jit_entry_insns,
9305+
&insn_idx_to_block,
9306+
ep_escaped,
9307+
);
91759308

91769309
// Iteratively fill out basic blocks using a queue.
91779310
// TODO(max): Basic block arguments at edges
@@ -9187,6 +9320,7 @@ fn add_iseq_to_hir(
91879320
// Compile each block only once
91889321
if visited.contains(&block) { continue; }
91899322
visited.insert(block);
9323+
local_inval = local_inval_at_block_entry.get(&block).copied().unwrap_or(local_inval);
91909324

91919325
// Load basic block params first
91929326
let mut self_param = fun.push_insn(block, Insn::Param);

0 commit comments

Comments
 (0)