From 701e0975abc74defc4efe8363b465c46099079df Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 18 Sep 2025 22:02:19 -0400 Subject: [PATCH 1/2] ZJIT: Count writes to the VM frame (#14597) This is a) a lot of memory traffic and b) is another good proxy for our ability to strength reduce method calls. --- zjit.rb | 5 +++++ zjit/src/codegen.rs | 4 ++++ zjit/src/stats.rs | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/zjit.rb b/zjit.rb index 7d2fd3a10e3e00..f0c988d5a0592f 100644 --- a/zjit.rb +++ b/zjit.rb @@ -58,6 +58,11 @@ def stats_string :gc_time_ns, :invalidation_time_ns, + :vm_write_pc_count, + :vm_write_sp_count, + :vm_write_locals_count, + :vm_write_stack_count, + :code_region_bytes, :side_exit_count, :total_insn_count, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 15c6d1a2ed70f4..165e68c791e190 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1593,6 +1593,7 @@ fn gen_prepare_call_with_gc(asm: &mut Assembler, state: &FrameState, leaf: bool) let opcode: usize = state.get_opcode().try_into().unwrap(); let next_pc: *const VALUE = unsafe { state.pc.offset(insn_len(opcode) as isize) }; + gen_incr_counter(asm, Counter::vm_write_pc_count); asm_comment!(asm, "save PC to CFP"); asm.mov(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_PC), Opnd::const_ptr(next_pc)); @@ -1611,6 +1612,7 @@ fn gen_save_sp(asm: &mut Assembler, stack_size: usize) { // code, and ZJIT's codegen currently assumes the SP register doesn't move, e.g. gen_param(). // So we don't update the SP register here. We could update the SP register to avoid using // an extra register for asm.lea(), but you'll need to manage the SP offset like YJIT does. + gen_incr_counter(asm, Counter::vm_write_sp_count); asm_comment!(asm, "save SP to CFP: {}", stack_size); let sp_addr = asm.lea(Opnd::mem(64, SP, stack_size as i32 * SIZEOF_VALUE_I32)); let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP); @@ -1620,6 +1622,7 @@ fn gen_save_sp(asm: &mut Assembler, stack_size: usize) { /// Spill locals onto the stack. fn gen_spill_locals(jit: &JITState, asm: &mut Assembler, state: &FrameState) { // TODO: Avoid spilling locals that have been spilled before and not changed. + gen_incr_counter(asm, Counter::vm_write_locals_count); asm_comment!(asm, "spill locals"); for (idx, &insn_id) in state.locals().enumerate() { asm.mov(Opnd::mem(64, SP, (-local_idx_to_ep_offset(jit.iseq, idx) - 1) * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)); @@ -1630,6 +1633,7 @@ fn gen_spill_locals(jit: &JITState, asm: &mut Assembler, state: &FrameState) { fn gen_spill_stack(jit: &JITState, asm: &mut Assembler, state: &FrameState) { // This function does not call gen_save_sp() at the moment because // gen_send_without_block_direct() spills stack slots above SP for arguments. + gen_incr_counter(asm, Counter::vm_write_stack_count); asm_comment!(asm, "spill stack"); for (idx, &insn_id) in state.stack().enumerate() { asm.mov(Opnd::mem(64, SP, idx as i32 * SIZEOF_VALUE_I32), jit.get_opnd(insn_id)); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index a47e78939addb7..0a0daa5d9c54d6 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -150,6 +150,12 @@ make_counters! { send_fallback_optimized, send_fallback_missing, send_fallback_refined, + + // Writes to the VM frame + vm_write_pc_count, + vm_write_sp_count, + vm_write_locals_count, + vm_write_stack_count, } /// Increase a counter by a specified amount From 8edb029ed97e3d8de47b66c9ed84daa76143a444 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 18 Sep 2025 20:20:29 -0700 Subject: [PATCH 2/2] ZJIT: Put exit reasons later in stats_string (#14599) --- zjit.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zjit.rb b/zjit.rb index f0c988d5a0592f..42cfe1cb91bea4 100644 --- a/zjit.rb +++ b/zjit.rb @@ -39,12 +39,14 @@ def stats_string buf = +"***ZJIT: Printing ZJIT statistics on exit***\n" stats = self.stats - # Show exit reasons, ordered by the typical amount of exits for the prefix at the time + # Show non-exit counters + print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20) + print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback def_types', buf:, stats:, limit: 20) + + # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20) - print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20) - print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback def_types', buf:, stats:, limit: 20) # Show the most important stats ratio_in_zjit at the end print_counters([