@@ -4962,6 +4962,62 @@ impl Function {
49624962 self.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoEPEscape(iseq), state: reload_exit_id });
49634963 }
49644964
4965+ /// After a call that takes a block iseq, reload the locals that the block (or any iseq nested
4966+ /// within it) may have written. This covers syntactically visible local writes where the
4967+ /// environment does not escape. Exordinary modifications through `Binding` and debug.h APIs are
4968+ /// handled via patchpoints.
4969+ fn reload_locals_modified_by_block(
4970+ &mut self,
4971+ block: BlockId,
4972+ iseq: IseqPtr,
4973+ blockiseq: IseqPtr,
4974+ state: &mut FrameState,
4975+ ep_escaped: bool,
4976+ ) {
4977+ let to_reload: &mut dyn Iterator<Item = usize> = if ep_escaped {
4978+ // Reload everything when working with an escaped environment
4979+ &mut (0..state.locals.len())
4980+ } else {
4981+ // When not escaped, only reload syntactically visible local modifications
4982+ let params = unsafe { iseq.params() };
4983+ let block_param_local_idx: Option<usize> = if params.flags.has_block() != 0 {
4984+ params.block_start.try_into().ok()
4985+ } else {
4986+ None
4987+ };
4988+ let outer_variables = unsafe { blockiseq.outer_variables() };
4989+ &mut (0..state.locals.len()).filter(move |&local_idx| {
4990+ let id = unsafe { rb_zjit_local_id(iseq, local_idx.try_into().unwrap()) };
4991+ let access = outer_variables.local_access(id);
4992+ if block_param_local_idx == Some(local_idx) {
4993+ // The block param slot is special: `getblockparam` come from a syntactic read,
4994+ // but operationally can write to the local slot. So, reload it whenever the
4995+ // block references it at all (read or write), not just on a setlocal.
4996+ access.is_some()
4997+ } else {
4998+ access == Some(OuterLocalAccess::ReadWrite)
4999+ }
5000+ })
5001+ };
5002+
5003+ let mut base: Option<InsnId> = None;
5004+ for local_idx in to_reload {
5005+ let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
5006+ let ep_offset_u32 = u32::try_from(ep_offset)
5007+ .unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
5008+ let recv = *base.get_or_insert_with(|| {
5009+ let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
5010+ self.push_insn(block, base_insn)
5011+ });
5012+ let val = if !ep_escaped {
5013+ self.get_local_from_sp(block, iseq, recv, ep_offset_u32, types::BasicObject)
5014+ } else {
5015+ self.get_local_from_ep(block, iseq, recv, ep_offset_u32, 0, types::BasicObject)
5016+ };
5017+ state.setlocal(ep_offset_u32, val);
5018+ }
5019+ }
5020+
49655021 fn count_not_inlined_cfunc(&mut self, block: BlockId, cme: *const rb_callable_method_entry_t) {
49665022 let owner = unsafe { (*cme).owner };
49675023 let called_id = unsafe { (*cme).called_id };
@@ -8653,29 +8709,12 @@ fn add_iseq_to_hir(
86538709 let send = fun.push_insn(block, Insn::Send { recv, cd, block: block_handler, args, state: exit_id, reason: Uncategorized(opcode) });
86548710 state.stack_push(send);
86558711
8656- if let Some(BlockHandler::BlockIseq(_ )) = block_handler {
8712+ if let Some(BlockHandler::BlockIseq(blockiseq )) = block_handler {
86578713 // Reload locals that may have been modified by the blockiseq.
8658- // TODO: Avoid reloading locals that are not referenced by the blockiseq
8659- // or not used after this. Max thinks we could eventually DCE them.
86608714 if !ep_escaped && !state.locals.is_empty() {
86618715 fun.gen_post_send_no_ep_escape_patch_point(block, &state, insn_idx);
86628716 }
8663- let mut base: Option<InsnId> = None;
8664- for local_idx in 0..state.locals.len() {
8665- let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
8666- let ep_offset_u32 = u32::try_from(ep_offset)
8667- .unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
8668- let recv = *base.get_or_insert_with(|| {
8669- let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
8670- fun.push_insn(block, base_insn)
8671- });
8672- let val = if !ep_escaped {
8673- fun.get_local_from_sp(block, iseq, recv, ep_offset_u32, types::BasicObject)
8674- } else {
8675- fun.get_local_from_ep(block, iseq, recv, ep_offset_u32, 0, types::BasicObject)
8676- };
8677- state.setlocal(ep_offset_u32, val);
8678- }
8717+ fun.reload_locals_modified_by_block(block, iseq, blockiseq, &mut state, ep_escaped);
86798718 }
86808719 }
86818720 YARVINSN_sendforward => {
@@ -8706,22 +8745,7 @@ fn add_iseq_to_hir(
87068745 if !ep_escaped && !state.locals.is_empty() {
87078746 fun.gen_post_send_no_ep_escape_patch_point(block, &state, insn_idx);
87088747 }
8709- let mut base: Option<InsnId> = None;
8710- for local_idx in 0..state.locals.len() {
8711- let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
8712- let ep_offset_u32 = u32::try_from(ep_offset)
8713- .unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
8714- let recv = *base.get_or_insert_with(|| {
8715- let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
8716- fun.push_insn(block, base_insn)
8717- });
8718- let val = if !ep_escaped {
8719- fun.get_local_from_sp(block, iseq, recv, ep_offset_u32, types::BasicObject)
8720- } else {
8721- fun.get_local_from_ep(block, iseq, recv, ep_offset_u32, 0, types::BasicObject)
8722- };
8723- state.setlocal(ep_offset_u32, val);
8724- }
8748+ fun.reload_locals_modified_by_block(block, iseq, blockiseq, &mut state, ep_escaped);
87258749 }
87268750 }
87278751 YARVINSN_invokesuper => {
@@ -8746,27 +8770,10 @@ fn add_iseq_to_hir(
87468770
87478771 if !blockiseq.is_null() {
87488772 // Reload locals that may have been modified by the blockiseq.
8749- // TODO: Avoid reloading locals that are not referenced by the blockiseq
8750- // or not used after this. Max thinks we could eventually DCE them.
87518773 if !ep_escaped && !state.locals.is_empty() {
87528774 fun.gen_post_send_no_ep_escape_patch_point(block, &state, insn_idx);
87538775 }
8754- let mut base: Option<InsnId> = None;
8755- for local_idx in 0..state.locals.len() {
8756- let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
8757- let ep_offset_u32 = u32::try_from(ep_offset)
8758- .unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
8759- let recv = *base.get_or_insert_with(|| {
8760- let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
8761- fun.push_insn(block, base_insn)
8762- });
8763- let val = if !ep_escaped {
8764- fun.get_local_from_sp(block, iseq, recv, ep_offset_u32, types::BasicObject)
8765- } else {
8766- fun.get_local_from_ep(block, iseq, recv, ep_offset_u32, 0, types::BasicObject)
8767- };
8768- state.setlocal(ep_offset_u32, val);
8769- }
8776+ fun.reload_locals_modified_by_block(block, iseq, blockiseq, &mut state, ep_escaped);
87708777 }
87718778 }
87728779 YARVINSN_invokesuperforward => {
@@ -8793,27 +8800,10 @@ fn add_iseq_to_hir(
87938800
87948801 if !blockiseq.is_null() {
87958802 // Reload locals that may have been modified by the blockiseq.
8796- // TODO: Avoid reloading locals that are not referenced by the blockiseq
8797- // or not used after this. Max thinks we could eventually DCE them.
87988803 if !ep_escaped && !state.locals.is_empty() {
87998804 fun.gen_post_send_no_ep_escape_patch_point(block, &state, insn_idx);
88008805 }
8801- let mut base: Option<InsnId> = None;
8802- for local_idx in 0..state.locals.len() {
8803- let ep_offset = local_idx_to_ep_offset(iseq, local_idx);
8804- let ep_offset_u32 = u32::try_from(ep_offset)
8805- .unwrap_or_else(|_| panic!("Could not convert ep_offset {ep_offset} to u32"));
8806- let recv = *base.get_or_insert_with(|| {
8807- let base_insn = if !ep_escaped { Insn::LoadSP } else { Insn::GetEP { level: 0 } };
8808- fun.push_insn(block, base_insn)
8809- });
8810- let val = if !ep_escaped {
8811- fun.get_local_from_sp(block, iseq, recv, ep_offset_u32, types::BasicObject)
8812- } else {
8813- fun.get_local_from_ep(block, iseq, recv, ep_offset_u32, 0, types::BasicObject)
8814- };
8815- state.setlocal(ep_offset_u32, val);
8816- }
8806+ fun.reload_locals_modified_by_block(block, iseq, blockiseq, &mut state, ep_escaped);
88178807 }
88188808 }
88198809 YARVINSN_invokeblock => {
0 commit comments