Skip to content

Commit e5a2031

Browse files
committed
ZJIT: Don't retain Proc and IFUNC block handlers in profiles
1 parent f45b8a9 commit e5a2031

3 files changed

Lines changed: 55 additions & 12 deletions

File tree

‎zjit/src/codegen_tests.rs‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6697,6 +6697,33 @@ fn test_profiled_singleton_class_does_not_retain_young_object() {
66976697
"), @"true");
66986698
}
66996699

6700+
#[test]
6701+
fn test_profiled_proc_block_handler_does_not_retain_proc() {
6702+
// Profile more than one call so that the profile can hold several Procs
6703+
rb_zjit_prepare_options();
6704+
let num_profiles = get_option!(num_profiles);
6705+
set_call_threshold(CallThreshold::from(num_profiles) + 2);
6706+
6707+
assert_snapshot!(inspect("
6708+
def profiled_proc_take = yield
6709+
def profiled_proc_forward(&blk) = profiled_proc_take(&blk)
6710+
6711+
PROFILED_PROC_OBJECTS = ObjectSpace::WeakMap.new
6712+
def profiled_proc_make(i)
6713+
obj = Object.new
6714+
PROFILED_PROC_OBJECTS[i] = obj
6715+
pr = proc { obj }
6716+
profiled_proc_forward(&pr)
6717+
nil
6718+
end
6719+
6720+
100.times { |i| profiled_proc_make(i) }
6721+
4.times { GC.start(full_mark: true, immediate_sweep: true) }
6722+
# Allow one object kept alive by conservative stack scanning
6723+
PROFILED_PROC_OBJECTS.keys.size <= 1
6724+
"), @"true");
6725+
}
6726+
67006727
#[test]
67016728
fn test_profile_under_nested_jit_call() {
67026729
assert_snapshot!(inspect("

‎zjit/src/hir.rs‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::{
1515
use crate::hir_type::{Type, types};
1616
use crate::hir_effect::{Effect, abstract_heaps, effects};
1717
use crate::bitset::BitSet;
18-
use crate::profile::{ProfiledType, SplatLength, TypeDistributionSummary};
18+
use crate::profile::{ProfiledType, SplatLength, TypeDistributionSummary, PROFILED_IFUNC_BLOCK_HANDLER, PROFILED_PROC_BLOCK_HANDLER};
1919
use crate::stats::{Counter, incr_counter};
2020
use SendFallbackReason::*;
2121

@@ -8974,7 +8974,7 @@ fn add_iseq_to_hir(
89748974
let obj = summary.bucket(0).class();
89758975
if unsafe { rb_IMEMO_TYPE_P(obj, imemo_iseq) == 1 } {
89768976
fun.count(block, Counter::invokeblock_handler_monomorphic_iseq);
8977-
} else if unsafe { rb_IMEMO_TYPE_P(obj, imemo_ifunc) == 1 } {
8977+
} else if obj == PROFILED_IFUNC_BLOCK_HANDLER {
89788978
fun.count(block, Counter::invokeblock_handler_monomorphic_ifunc);
89798979
} else {
89808980
fun.count(block, Counter::invokeblock_handler_monomorphic_other);
@@ -9001,15 +9001,15 @@ fn add_iseq_to_hir(
90019001
let obj = summary.bucket(0).class();
90029002
if unsafe { rb_IMEMO_TYPE_P(obj, imemo_iseq) == 1} {
90039003
fun.count(block, Counter::getblockparamproxy_handler_iseq);
9004-
} else if unsafe { rb_IMEMO_TYPE_P(obj, imemo_ifunc) == 1} {
9004+
} else if obj == PROFILED_IFUNC_BLOCK_HANDLER {
90059005
fun.count(block, Counter::getblockparamproxy_handler_ifunc);
90069006
}
90079007
else if obj.nil_p() {
90089008
fun.count(block, Counter::getblockparamproxy_handler_nil);
90099009
}
90109010
else if obj.symbol_p() {
90119011
fun.count(block, Counter::getblockparamproxy_handler_symbol);
9012-
} else if unsafe { rb_obj_is_proc(obj).test() } {
9012+
} else if obj == PROFILED_PROC_BLOCK_HANDLER {
90139013
fun.count(block, Counter::getblockparamproxy_handler_proc);
90149014
}
90159015
} else if summary.is_polymorphic() || summary.is_skewed_polymorphic() {
@@ -9699,12 +9699,10 @@ fn add_iseq_to_hir(
96999699
let obj = profiled_type.class();
97009700
if obj.nil_p() {
97019701
Some(Self::Nil)
9702-
} else if unsafe {
9703-
rb_IMEMO_TYPE_P(obj, imemo_iseq) == 1
9704-
|| rb_IMEMO_TYPE_P(obj, imemo_ifunc) == 1
9705-
} {
9702+
} else if unsafe { rb_IMEMO_TYPE_P(obj, imemo_iseq) == 1 }
9703+
|| obj == PROFILED_IFUNC_BLOCK_HANDLER {
97069704
Some(Self::IseqOrIfunc)
9707-
} else if unsafe { rb_obj_is_proc(obj).test() } {
9705+
} else if obj == PROFILED_PROC_BLOCK_HANDLER {
97089706
Some(Self::Proc)
97099707
} else {
97109708
None
@@ -10378,7 +10376,7 @@ fn add_iseq_to_hir(
1037810376
});
1037910377

1038010378
let is_ifunc = (flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT | VM_CALL_KWARG)) == 0
10381-
&& block_handler_class.is_some_and(|obj| unsafe { rb_IMEMO_TYPE_P(obj, imemo_ifunc) == 1 });
10379+
&& block_handler_class.is_some_and(|obj| obj == PROFILED_IFUNC_BLOCK_HANDLER);
1038210380

1038310381
// Collect the profiled ISEQ blocks that can be invoked directly with a JIT-to-JIT call.
1038410382
let mut fallback_reason = InvokeBlockNotSpecialized;

‎zjit/src/profile.rs‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ fn profile_block_handler(profiler: &mut Profiler, profile: &mut IseqProfile) {
235235
if entry.opnd_types.is_empty() {
236236
entry.opnd_types.resize(1, TypeDistribution::new());
237237
}
238-
let obj = profiler.peek_at_block_handler();
238+
let obj = block_handler_profile_value(profiler.peek_at_block_handler());
239239
let ty = ProfiledType::object(obj);
240240
VALUE::from(profiler.iseq).write_barrier(ty.class());
241241
entry.opnd_types[0].observe(ty);
@@ -252,11 +252,29 @@ fn profile_getblockparamproxy(profiler: &mut Profiler, profile: &mut IseqProfile
252252
let block_handler = unsafe { *ep.offset(VM_ENV_DATA_INDEX_SPECVAL as isize) };
253253
let untagged = unsafe { rb_vm_untag_block_handler(block_handler) };
254254

255-
let ty = ProfiledType::object(untagged);
255+
let ty = ProfiledType::object(block_handler_profile_value(untagged));
256256
VALUE::from(profiler.iseq).write_barrier(ty.class());
257257
entry.opnd_types[0].observe(ty);
258258
}
259259

260+
/// Fixnum values to represent a profiled block handler type without retaining its reference.
261+
/// If we kept them in profiles, imemo for IFUNC or a Proc object with a captured environment
262+
/// would be kept in memory. However, for IFUNC and Proc, we're only interested in the block
263+
/// handler type. We use Fixnum values because Fixnum cannot be a block handler.
264+
pub const PROFILED_IFUNC_BLOCK_HANDLER: VALUE = VALUE::fixnum_from_usize(1);
265+
pub const PROFILED_PROC_BLOCK_HANDLER: VALUE = VALUE::fixnum_from_usize(2);
266+
267+
/// Map an untagged block handler to the value recorded in the profile.
268+
fn block_handler_profile_value(block_handler: VALUE) -> VALUE {
269+
if unsafe { rb_IMEMO_TYPE_P(block_handler, imemo_ifunc) == 1 } {
270+
PROFILED_IFUNC_BLOCK_HANDLER
271+
} else if unsafe { rb_obj_is_proc(block_handler).test() } {
272+
PROFILED_PROC_BLOCK_HANDLER
273+
} else {
274+
block_handler
275+
}
276+
}
277+
260278
fn profile_invokesuper(profiler: &mut Profiler, profile: &mut IseqProfile) {
261279
let cme = unsafe { rb_vm_frame_method_entry(profiler.cfp) };
262280
let cme_value = VALUE(cme as usize); // CME is a T_IMEMO, which is a VALUE

0 commit comments

Comments
 (0)