@@ -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
90059132pub 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