From 74c3bd7718319868c89a6d2a5827802d2be2d272 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 27 Oct 2025 12:26:03 -0700 Subject: [PATCH 01/17] ZJIT: Remove a duplicated annotation (#14968) --- zjit/src/cruby_methods.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index b6b91ab0a6db7a..09d52b9f49c74a 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -189,7 +189,6 @@ pub fn init() -> Annotations { annotate!(rb_mKernel, "itself", inline_kernel_itself); annotate!(rb_mKernel, "block_given?", inline_kernel_block_given_p); - annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf); annotate!(rb_cString, "bytesize", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cString, "length", types::Fixnum, no_gc, leaf, elidable); From 1d897f5628ba9c2e64f2455affbd9c2fab79e99b Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:26:59 -0400 Subject: [PATCH 02/17] ZJIT: Elide unnecessary return statements --- zjit/src/backend/tests.rs | 4 ++-- zjit/src/cruby_methods.rs | 3 +-- zjit/src/hir.rs | 8 ++++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index 1b72777212cb23..b21e1586d5ccc2 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -63,10 +63,10 @@ fn test_alloc_regs() { } fn setup_asm() -> (Assembler, CodeBlock) { - return ( + ( Assembler::new(), CodeBlock::new_dummy() - ); + ) } // Test full codegen pipeline diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 09d52b9f49c74a..720683f407a8b1 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -255,8 +255,7 @@ fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hi fn inline_kernel_block_given_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { let &[] = args else { return None; }; // TODO(max): In local iseq types that are not ISEQ_TYPE_METHOD, rewrite to Constant false. - let result = fun.push_insn(block, hir::Insn::IsBlockGiven); - return Some(result); + Some(fun.push_insn(block, hir::Insn::IsBlockGiven)) } fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b358ac5d51ca4d..1b4dc5b011e7bd 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1428,7 +1428,7 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: // TODO(max): Support only_kwparam case where the local_idx is a positional parameter - return None; + None } YARVINSN_putnil => Some(IseqReturn::Value(Qnil)), YARVINSN_putobject => Some(IseqReturn::Value(unsafe { *rb_iseq_pc_at_idx(iseq, 1) })), @@ -2618,16 +2618,16 @@ impl Function { blockiseq, }); fun.make_equal_to(send_insn_id, ccall); - return Ok(()); + Ok(()) } // Variadic method -1 => { // func(int argc, VALUE *argv, VALUE recv) - return Err(()); + Err(()) } -2 => { // (self, args_ruby_array) - return Err(()); + Err(()) } _ => unreachable!("unknown cfunc kind: argc={argc}") } From a395d9c036e998217af4cfeed73550fc1c0275c8 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:27:55 -0400 Subject: [PATCH 03/17] ZJIT: Simplify complex type to BranchEncoder --- zjit/src/asm/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/zjit/src/asm/mod.rs b/zjit/src/asm/mod.rs index 32dc633a2941ce..dca2b7b0cf018a 100644 --- a/zjit/src/asm/mod.rs +++ b/zjit/src/asm/mod.rs @@ -19,6 +19,9 @@ pub mod arm64; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Label(pub usize); +/// The object that knows how to encode the branch instruction. +type BranchEncoder = Box; + /// Reference to an ASM label pub struct LabelRef { // Position in the code block where the label reference exists @@ -33,7 +36,7 @@ pub struct LabelRef { num_bytes: usize, /// The object that knows how to encode the branch instruction. - encode: Box, + encode: BranchEncoder, } /// Block of memory into which instructions can be assembled From 5c4d76c93a817df3e8acfdabad535bfa95671158 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:28:16 -0400 Subject: [PATCH 04/17] ZJIT: Remove unnecessary #[test] annotation --- zjit/src/backend/tests.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index b21e1586d5ccc2..02635e1066fca5 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -1,4 +1,3 @@ -#![cfg(test)] use crate::asm::CodeBlock; use crate::backend::lir::*; use crate::cruby::*; From c112368e3013fc1d27f217f40714a250019befa7 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:28:31 -0400 Subject: [PATCH 05/17] ZJIT: Use std::ptr::null instead of casts --- zjit/src/backend/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/backend/tests.rs b/zjit/src/backend/tests.rs index 02635e1066fca5..52547cb31fde65 100644 --- a/zjit/src/backend/tests.rs +++ b/zjit/src/backend/tests.rs @@ -197,8 +197,8 @@ fn test_c_call() #[test] fn test_alloc_ccall_regs() { let mut asm = Assembler::new(); - let out1 = asm.ccall(0 as *const u8, vec![]); - let out2 = asm.ccall(0 as *const u8, vec![out1]); + let out1 = asm.ccall(std::ptr::null::(), vec![]); + let out2 = asm.ccall(std::ptr::null::(), vec![out1]); asm.mov(EC, out2); let mut cb = CodeBlock::new_dummy(); asm.compile_with_regs(&mut cb, Assembler::get_alloc_regs()).unwrap(); From 79db7d5204c7177a80a6df6eb9b7c47866c0fa17 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:29:11 -0400 Subject: [PATCH 06/17] ZJIT: Remove unnecessary `as` casts --- zjit/src/hir.rs | 2 +- zjit/src/hir_type/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1b4dc5b011e7bd..4eba78b7523620 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2026,7 +2026,7 @@ impl Function { return true; } let frame_state = self.frame_state(state); - let iseq_insn_idx = frame_state.insn_idx as usize; + let iseq_insn_idx = frame_state.insn_idx; let Some(profiled_type) = self.profiled_type_of_at(val, iseq_insn_idx) else { return false; }; diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 7e6da62fd0ff44..7c10ef4425c6fd 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -252,7 +252,7 @@ impl Type { Const::CInt8(v) => Self::from_cint(types::CInt8, v as i64), Const::CInt16(v) => Self::from_cint(types::CInt16, v as i64), Const::CInt32(v) => Self::from_cint(types::CInt32, v as i64), - Const::CInt64(v) => Self::from_cint(types::CInt64, v as i64), + Const::CInt64(v) => Self::from_cint(types::CInt64, v), Const::CUInt8(v) => Self::from_cint(types::CUInt8, v as i64), Const::CUInt16(v) => Self::from_cint(types::CUInt16, v as i64), Const::CUInt32(v) => Self::from_cint(types::CUInt32, v as i64), From aabec60c2e4fd96ceb8239b45ed2388309239742 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:29:23 -0400 Subject: [PATCH 07/17] ZJIT: Elide unnecessary 'static annotation --- zjit/src/hir_type/hir_type.inc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index bad45a737644fb..a330440612a739 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -70,7 +70,7 @@ mod bits { pub const Symbol: u64 = DynamicSymbol | StaticSymbol; pub const TrueClass: u64 = 1u64 << 42; pub const Undef: u64 = 1u64 << 43; - pub const AllBitPatterns: [(&'static str, u64); 70] = [ + pub const AllBitPatterns: [(&str, u64); 70] = [ ("Any", Any), ("RubyValue", RubyValue), ("Immediate", Immediate), From fa0eab2848f6ed73f34bf5d1d7e0fe6bf4e2f6e4 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:29:41 -0400 Subject: [PATCH 08/17] ZJIT: Use .first() in lieu of .get(0) --- zjit/src/codegen.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index e16588e3e33bbd..1179b9bf16924b 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -2057,7 +2057,7 @@ fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &IseqCallRef) -> Result // We currently don't support JIT-to-JIT calls for ISEQs with optional arguments. // So we only need to use jit_entry_ptrs[0] for now. TODO(Shopify/ruby#817): Support optional arguments. - let Some(&jit_entry_ptr) = jit_entry_ptrs.get(0) else { + let Some(&jit_entry_ptr) = jit_entry_ptrs.first() else { return Err(CompileError::JitToJitOptional) }; From ac57a5c43ea6b12350eb7efbee50d6682021dc6c Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:29:52 -0400 Subject: [PATCH 09/17] ZJIT: Use .is_empty() for clarity --- zjit/src/cruby_methods.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 720683f407a8b1..9297327d739c24 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -237,7 +237,7 @@ fn no_inline(_fun: &mut hir::Function, _block: hir::BlockId, _recv: hir::InsnId, } fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { - if args.len() == 0 && fun.likely_a(recv, types::StringExact, state) { + if args.is_empty() && fun.likely_a(recv, types::StringExact, state) { let recv = fun.coerce_to(block, recv, types::StringExact, state); return Some(recv); } @@ -245,7 +245,7 @@ fn inline_string_to_s(fun: &mut hir::Function, block: hir::BlockId, recv: hir::I } fn inline_kernel_itself(_fun: &mut hir::Function, _block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], _state: hir::InsnId) -> Option { - if args.len() == 0 { + if args.is_empty() { // No need to coerce the receiver; that is done by the SendWithoutBlock rewriting. return Some(recv); } From a12aa2b5aa0598ccf5bb9ff0f9b5dfde8a873be5 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Fri, 24 Oct 2025 10:30:31 -0400 Subject: [PATCH 10/17] ZJIT: Since Param is unit struct, elide destructuring --- zjit/src/hir.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 4eba78b7523620..61014d5fd4f59b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -841,7 +841,7 @@ impl Insn { fn has_effects(&self) -> bool { match self { Insn::Const { .. } => false, - Insn::Param { .. } => false, + Insn::Param => false, Insn::StringCopy { .. } => false, Insn::NewArray { .. } => false, // NewHash's operands may be hashed and compared for equality, which could have @@ -1469,7 +1469,7 @@ impl Function { // Add an instruction to an SSA block pub fn push_insn(&mut self, block: BlockId, insn: Insn) -> InsnId { - let is_param = matches!(insn, Insn::Param { .. }); + let is_param = matches!(insn, Insn::Param); let id = self.new_insn(insn); if is_param { self.blocks[block.0].params.push(id); @@ -1576,7 +1576,7 @@ impl Function { use Insn::*; match &self.insns[insn_id.0] { result@(Const {..} - | Param {..} + | Param | GetConstantPath {..} | IsBlockGiven | PatchPoint {..} @@ -1771,7 +1771,7 @@ impl Function { fn infer_type(&self, insn: InsnId) -> Type { assert!(self.insns[insn.0].has_output()); match &self.insns[insn.0] { - Insn::Param { .. } => unimplemented!("params should not be present in block.insns"), + Insn::Param => unimplemented!("params should not be present in block.insns"), Insn::SetGlobal { .. } | Insn::Jump(_) | Insn::EntryPoint { .. } | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::SetClassVar { .. } | Insn::ArrayExtend { .. } @@ -1847,7 +1847,7 @@ impl Function { Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::DefinedIvar { pushval, .. } => Type::from_value(*pushval).union(types::NilClass), Insn::GetConstantPath { .. } => types::BasicObject, - Insn::IsBlockGiven { .. } => types::BoolExact, + Insn::IsBlockGiven => types::BoolExact, Insn::ArrayMax { .. } => types::BasicObject, Insn::GetGlobal { .. } => types::BasicObject, Insn::GetIvar { .. } => types::BasicObject, @@ -3024,7 +3024,7 @@ impl Function { fn worklist_traverse_single_insn(&self, insn: &Insn, worklist: &mut VecDeque) { match insn { &Insn::Const { .. } - | &Insn::Param { .. } + | &Insn::Param | &Insn::EntryPoint { .. } | &Insn::LoadPC | &Insn::LoadSelf From 68d9f7c3e6da11690ead9b525e08a8f2c4afc4be Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Mon, 27 Oct 2025 15:01:01 -0400 Subject: [PATCH 11/17] ZJIT: Remove unnecessary 'static annotation from gen_hir_type.rb --- zjit/src/hir_type/gen_hir_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 653efa6f8f2544..4e0ecc718f8f82 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -190,7 +190,7 @@ def add_union name, type_names subtypes = $bits[type_name].join(" | ") puts " pub const #{type_name}: u64 = #{subtypes};" } -puts " pub const AllBitPatterns: [(&'static str, u64); #{$bits.size}] = [" +puts " pub const AllBitPatterns: [(&str, u64); #{$bits.size}] = [" # Sort the bit patterns by decreasing value so that we can print the densest # possible to-string representation of a Type. For example, CSigned instead of # CInt8|CInt16|... From d97fb3b424cebb812012a4e8a497a88510be9b72 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 27 Oct 2025 16:45:50 -0400 Subject: [PATCH 12/17] ZJIT: Print out full path to --zjit-trace-exits output (#14966) * ZJIT: Print out full path to --zjit-trace-exits output This helps with any `chdir`-related issues. * Don't include dot Co-authored-by: Takashi Kokubun --------- Co-authored-by: Takashi Kokubun --- zjit.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zjit.rb b/zjit.rb index 8b44330b36043a..9ea83fe10f716f 100644 --- a/zjit.rb +++ b/zjit.rb @@ -283,6 +283,7 @@ def dump_locations # :nodoc: filename = "zjit_exits_#{Process.pid}.dump" n_bytes = dump_exit_locations(filename) - $stderr.puts("#{n_bytes} bytes written to #{filename}.") + absolute_filename = File.expand_path(filename) + $stderr.puts("#{n_bytes} bytes written to #{absolute_filename}") end end From 8d45e1f34e9a169987587d99a837b4ee035d7000 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 27 Oct 2025 18:53:28 -0400 Subject: [PATCH 13/17] ZJIT: Fix internal compiler error looking up profiles for trace_getinstancevariable (#14969) We treat getinstancevariable differently from other opcodes: it does not look at the stack for its self operand, but instead looks at `cfp->self`. In some cases, we might see the `trace_` variant in the front-end, so make sure we treat that the same. Example repro: ``` def test @foo end 28.times do test end trace = TracePoint.trace(:call) do |tp| puts tp.method_id end trace.enable do 30.times do test end end ``` --- zjit/src/hir.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 61014d5fd4f59b..e948ee4452ac67 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4021,7 +4021,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { .try_into() .unwrap(); - if opcode == YARVINSN_getinstancevariable { + // If TracePoint has been enabled after we have collected profiles, we'll see + // trace_getinstancevariable in the ISEQ. We have to treat it like getinstancevariable + // for profiling purposes: there is no operand on the stack to look up; we have + // profiled cfp->self. + if opcode == YARVINSN_getinstancevariable || opcode == YARVINSN_trace_getinstancevariable { profiles.profile_self(&exit_state, self_param); } else { profiles.profile_stack(&exit_state); @@ -7408,6 +7412,29 @@ mod tests { "); } + #[test] + fn test_trace_getinstancevariable() { + eval(" + def test = @foo + test + trace = TracePoint.trace(:call) { |tp| } + trace.enable { test } + "); + assert_contains_opcode("test", YARVINSN_trace_getinstancevariable); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + SideExit UnhandledYARVInsn(trace_getinstancevariable) + "); + } + #[test] fn test_setinstancevariable() { eval(" From 3fb96ee93bb8e541fb510bd2a1a454f82160ba96 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 15:41:08 -0700 Subject: [PATCH 14/17] ZJIT: Inline method calls to ISEQs that just do leaf Primitive calls --- zjit/src/hir.rs | 95 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e948ee4452ac67..706982625ade9d 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1379,6 +1379,7 @@ enum IseqReturn { Value(VALUE), LocalVariable(u32), Receiver, + InvokeLeafBuiltin(rb_builtin_function), } unsafe extern "C" { @@ -1390,10 +1391,6 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: // Expect only two instructions and one possible operand // NOTE: If an ISEQ has an optional keyword parameter with a default value that requires // computation, the ISEQ will always have more than two instructions and won't be inlined. - let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; - if !(2..=3).contains(&iseq_size) { - return None; - } // Get the first two instructions let first_insn = iseq_opcode_at_idx(iseq, 0); @@ -1436,6 +1433,16 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: YARVINSN_putobject_INT2FIX_1_ => Some(IseqReturn::Value(VALUE::fixnum_from_usize(1))), // We don't support invokeblock for now. Such ISEQs are likely not used by blocks anyway. YARVINSN_putself if captured_opnd.is_none() => Some(IseqReturn::Receiver), + YARVINSN_opt_invokebuiltin_delegate_leave => { + let pc = unsafe { rb_iseq_pc_at_idx(iseq, 0) }; + let bf: rb_builtin_function = unsafe { *get_arg(pc, 0).as_ptr() }; + let argc = bf.argc as usize; + if argc != 0 { return None; } + let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; + let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; + if !leaf { return None; } + Some(IseqReturn::InvokeLeafBuiltin(bf)) + } _ => None, } } @@ -2437,7 +2444,7 @@ impl Function { for insn_id in old_insns { match self.find(insn_id) { // Reject block ISEQs to avoid autosplat and other block parameter complications. - Insn::SendWithoutBlockDirect { recv, iseq, cd, args, .. } => { + Insn::SendWithoutBlockDirect { recv, iseq, cd, args, state, .. } => { let call_info = unsafe { (*cd).ci }; let ci_flags = unsafe { vm_ci_flag(call_info) }; // .send call is not currently supported for builtins @@ -2461,6 +2468,17 @@ impl Function { self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); self.make_equal_to(insn_id, recv); } + IseqReturn::InvokeLeafBuiltin(bf) => { + self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); + let replacement = self.push_insn(block, Insn::InvokeBuiltin { + bf, + args: vec![recv], + state, + leaf: true, + return_type: None, + }); + self.make_equal_to(insn_id, replacement); + } } } _ => { self.push_insn_id(block, insn_id); } @@ -10955,9 +10973,10 @@ mod opt_tests { bb2(v8:BasicObject, v9:BasicObject): v13:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, zero?@0x1008, cme:0x1010) - v22:BasicObject = SendWithoutBlockDirect v13, :zero? (0x1038) + IncrCounter inline_iseq_optimized_send_count + v24:BasicObject = InvokeBuiltin leaf _bi285, v13 CheckInterrupts - Return v22 + Return v24 "); } @@ -10986,9 +11005,10 @@ mod opt_tests { v18:ArrayExact = ArrayDup v16 PatchPoint MethodRedefined(Array@0x1008, first@0x1010, cme:0x1018) PatchPoint NoSingletonClass(Array@0x1008) - v30:BasicObject = SendWithoutBlockDirect v18, :first (0x1040) + IncrCounter inline_iseq_optimized_send_count + v32:BasicObject = InvokeBuiltin leaf _bi132, v18 CheckInterrupts - Return v30 + Return v32 "); } @@ -11015,9 +11035,10 @@ mod opt_tests { v21:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) - v24:BasicObject = SendWithoutBlockDirect v21, :class (0x1048) + IncrCounter inline_iseq_optimized_send_count + v26:BasicObject = InvokeBuiltin leaf _bi20, v21 CheckInterrupts - Return v24 + Return v26 "); } @@ -15345,6 +15366,58 @@ mod opt_tests { "); } + #[test] + fn test_inline_symbol_name() { + eval(" + def test(x) = x.to_s + test(:foo) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + v24:BasicObject = InvokeBuiltin leaf _bi12, v21 + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_inline_symbol_to_s() { + eval(" + def test(x) = x.to_s + test(:foo) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + v24:BasicObject = InvokeBuiltin leaf _bi12, v21 + CheckInterrupts + Return v24 + "); + } + #[test] fn test_optimize_stringexact_eq_stringexact() { eval(r#" From 46525fa7b8c91d05a24571337eff76f1023cb152 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 15:53:06 -0700 Subject: [PATCH 15/17] ZJIT: Add return_type to inlined InvokeBuiltin --- zjit/src/hir.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 706982625ade9d..8074a50e2b66a5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1379,7 +1379,8 @@ enum IseqReturn { Value(VALUE), LocalVariable(u32), Receiver, - InvokeLeafBuiltin(rb_builtin_function), + // Builtin descriptor and return type (if known) + InvokeLeafBuiltin(rb_builtin_function, Option), } unsafe extern "C" { @@ -1441,7 +1442,11 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags: let builtin_attrs = unsafe { rb_jit_iseq_builtin_attrs(iseq) }; let leaf = builtin_attrs & BUILTIN_ATTR_LEAF != 0; if !leaf { return None; } - Some(IseqReturn::InvokeLeafBuiltin(bf)) + // Check if this builtin is annotated + let return_type = ZJITState::get_method_annotations() + .get_builtin_properties(&bf) + .map(|props| props.return_type); + Some(IseqReturn::InvokeLeafBuiltin(bf, return_type)) } _ => None, } @@ -2468,14 +2473,14 @@ impl Function { self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); self.make_equal_to(insn_id, recv); } - IseqReturn::InvokeLeafBuiltin(bf) => { + IseqReturn::InvokeLeafBuiltin(bf, return_type) => { self.push_insn(block, Insn::IncrCounter(Counter::inline_iseq_optimized_send_count)); let replacement = self.push_insn(block, Insn::InvokeBuiltin { bf, args: vec![recv], state, leaf: true, - return_type: None, + return_type, }); self.make_equal_to(insn_id, replacement); } @@ -11036,7 +11041,7 @@ mod opt_tests { PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020) PatchPoint NoSingletonClass(Module@0x1010) IncrCounter inline_iseq_optimized_send_count - v26:BasicObject = InvokeBuiltin leaf _bi20, v21 + v26:Class = InvokeBuiltin leaf _bi20, v21 CheckInterrupts Return v26 "); From c3c254439f06073a9c7e167f89e1c1e97629d947 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 15:54:15 -0700 Subject: [PATCH 16/17] ZJIT: Annotate Symbol#to_s and Symbol#name as returning StringExact --- zjit/src/cruby_methods.rs | 2 ++ zjit/src/hir.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 9297327d739c24..6a16ae3e464d65 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -225,6 +225,8 @@ pub fn init() -> Annotations { annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); annotate_builtin!(rb_mKernel, "class", types::Class, leaf); + annotate_builtin!(rb_cSymbol, "name", types::StringExact); + annotate_builtin!(rb_cSymbol, "to_s", types::StringExact); Annotations { cfuncs: std::mem::take(cfuncs), diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 8074a50e2b66a5..776b1582a75022 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -8185,9 +8185,9 @@ mod tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = InvokeBuiltin leaf _bi28, v6 + v11:StringExact = InvokeBuiltin leaf _bi28, v6 Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:BasicObject): + bb3(v13:BasicObject, v14:StringExact): CheckInterrupts Return v14 "); @@ -8207,9 +8207,9 @@ mod tests { EntryPoint JIT(0) Jump bb2(v4) bb2(v6:BasicObject): - v11:BasicObject = InvokeBuiltin leaf _bi12, v6 + v11:StringExact = InvokeBuiltin leaf _bi12, v6 Jump bb3(v6, v11) - bb3(v13:BasicObject, v14:BasicObject): + bb3(v13:BasicObject, v14:StringExact): CheckInterrupts Return v14 "); @@ -15391,7 +15391,7 @@ mod opt_tests { PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) v21:StaticSymbol = GuardType v9, StaticSymbol IncrCounter inline_iseq_optimized_send_count - v24:BasicObject = InvokeBuiltin leaf _bi12, v21 + v24:StringExact = InvokeBuiltin leaf _bi12, v21 CheckInterrupts Return v24 "); @@ -15417,7 +15417,7 @@ mod opt_tests { PatchPoint MethodRedefined(Symbol@0x1000, to_s@0x1008, cme:0x1010) v21:StaticSymbol = GuardType v9, StaticSymbol IncrCounter inline_iseq_optimized_send_count - v24:BasicObject = InvokeBuiltin leaf _bi12, v21 + v24:StringExact = InvokeBuiltin leaf _bi12, v21 CheckInterrupts Return v24 "); From e5e32acc7ec020e3fd7a7dff76e19a6f39ffb3ab Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Fri, 24 Oct 2025 16:12:24 -0700 Subject: [PATCH 17/17] ZJIT: Annotate Kernel#frozen? as returning BoolExact --- zjit/src/cruby_methods.rs | 1 + zjit/src/hir.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 6a16ae3e464d65..dd33bb206a5bb3 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -225,6 +225,7 @@ pub fn init() -> Annotations { annotate_builtin!(rb_mKernel, "Float", types::Float); annotate_builtin!(rb_mKernel, "Integer", types::Integer); annotate_builtin!(rb_mKernel, "class", types::Class, leaf); + annotate_builtin!(rb_mKernel, "frozen?", types::BoolExact); annotate_builtin!(rb_cSymbol, "name", types::StringExact); annotate_builtin!(rb_cSymbol, "to_s", types::StringExact); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 776b1582a75022..e0e682ccb2fc71 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -15346,6 +15346,32 @@ mod opt_tests { "); } + #[test] + fn test_inline_kernel_frozen_p() { + eval(r#" + def test(o) = o.frozen? + test :foo + "#); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:BasicObject = GetLocal l0, SP@4 + Jump bb2(v1, v2) + bb1(v5:BasicObject, v6:BasicObject): + EntryPoint JIT(0) + Jump bb2(v5, v6) + bb2(v8:BasicObject, v9:BasicObject): + PatchPoint MethodRedefined(Symbol@0x1000, frozen?@0x1008, cme:0x1010) + v21:StaticSymbol = GuardType v9, StaticSymbol + IncrCounter inline_iseq_optimized_send_count + v24:BoolExact = InvokeBuiltin leaf _bi69, v21 + CheckInterrupts + Return v24 + "); + } + #[test] fn test_inline_integer_to_i() { eval(r#"