From 1d835539524cab6cd8b9210f34690e5e5e705f39 Mon Sep 17 00:00:00 2001 From: Aiden Fox Ivey Date: Thu, 23 Oct 2025 17:24:47 -0400 Subject: [PATCH 1/8] ZJIT: Inline << and push for Array in single arg case (#14926) Fixes https://github.com/Shopify/ruby/issues/813 --- zjit/src/cruby_methods.rs | 11 +++++++ zjit/src/hir.rs | 64 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index ee10eaa681c7e4..0d77b1d6cc7e11 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -203,6 +203,8 @@ pub fn init() -> Annotations { annotate!(rb_cArray, "reverse", types::ArrayExact, leaf, elidable); annotate!(rb_cArray, "join", types::StringExact); annotate!(rb_cArray, "[]", inline_array_aref); + annotate!(rb_cArray, "<<", inline_array_push); + annotate!(rb_cArray, "push", inline_array_push); annotate!(rb_cHash, "[]", inline_hash_aref); annotate!(rb_cHash, "size", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cHash, "empty?", types::BoolExact, no_gc, leaf, elidable); @@ -266,6 +268,15 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In None } +fn inline_array_push(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + // Inline only the case of `<<` or `push` when called with a single argument. + if let &[val] = args { + let _ = fun.push_insn(block, hir::Insn::ArrayPush { array: recv, val, state }); + return Some(recv); + } + None +} + fn inline_hash_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { if let &[key] = args { let result = fun.push_insn(block, hir::Insn::HashAref { hash: recv, key, state }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9f422c0146cce9..7ee2308eb5d888 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -14008,7 +14008,69 @@ mod opt_tests { PatchPoint MethodRedefined(Array@0x1000, <<@0x1008, cme:0x1010) PatchPoint NoSingletonClass(Array@0x1000) v26:ArrayExact = GuardType v9, ArrayExact - v27:BasicObject = CCallWithFrame <<@0x1038, v26, v13 + ArrayPush v26, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v26 + "); + } + + #[test] + fn test_optimize_array_push_single_arg() { + eval(" + def test(arr) + arr.push(1) + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + 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): + v13:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v24:ArrayExact = GuardType v9, ArrayExact + ArrayPush v24, v13 + IncrCounter inline_cfunc_optimized_send_count + CheckInterrupts + Return v24 + "); + } + + #[test] + fn test_do_not_optimize_array_push_multi_arg() { + eval(" + def test(arr) + arr.push(1,2,3) + end + test([]) + "); + assert_snapshot!(hir_string("test"), @r" + fn test@:3: + 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): + v13:Fixnum[1] = Const Value(1) + v14:Fixnum[2] = Const Value(2) + v15:Fixnum[3] = Const Value(3) + PatchPoint MethodRedefined(Array@0x1000, push@0x1008, cme:0x1010) + PatchPoint NoSingletonClass(Array@0x1000) + v26:ArrayExact = GuardType v9, ArrayExact + v27:BasicObject = CCallVariadic push@0x1038, v26, v13, v14, v15 CheckInterrupts Return v27 "); From f33cd1289e75c7091e61e54792a2b7e1f84ea697 Mon Sep 17 00:00:00 2001 From: Daniel Colson Date: Thu, 23 Oct 2025 17:47:58 -0400 Subject: [PATCH 2/8] ZJIT: Add tests for non-leaf classvar get and set (#14924) This commit adds tests that raise on `getclassvariable` and `setclassvariable` to exercise the non-leaf cases as suggested in https://github.com/ruby/ruby/pull/14918#discussion_r2453804603 --- test/ruby/test_zjit.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 13c78170177a20..e76e140ba13f2b 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -1675,6 +1675,20 @@ def self.test = @@x } end + def test_getclassvariable_raises + assert_compiles '"uninitialized class variable @@x in Foo"', %q{ + class Foo + def self.test = @@x + end + + begin + Foo.test + rescue NameError => e + e.message + end + } + end + def test_setclassvariable assert_compiles '42', %q{ class Foo @@ -1686,6 +1700,21 @@ def self.test = @@x = 42 } end + def test_setclassvariable_raises + assert_compiles '"can\'t modify frozen #: Foo"', %q{ + class Foo + def self.test = @@x = 42 + freeze + end + + begin + Foo.test + rescue FrozenError => e + e.message + end + } + end + def test_attr_reader assert_compiles '[4, 4]', %q{ class C From c2bce540f93aba77ddf89c7931a63b4e7108e466 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 23 Oct 2025 15:16:41 -0400 Subject: [PATCH 3/8] ZJIT: Replace `as usize` casts in codegen.rs The `as` casts are somewhat dangerous since when the type on either side change, it silently becomes a lossy conversion. This is why we have `IntoUsize` as well as other guaranteed lossless conversion utilities in stdlib. Use them. For pointers-to-address, `ptr::addr` is more informative. See also: https://tratt.net/laurie/blog/2021/static_integer_types.html --- zjit/src/codegen.rs | 36 ++++++++++++++++++------------------ zjit/src/virtualmem.rs | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 0a3be9277bb0e8..81608e5ae2528d 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -188,8 +188,8 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function_ptr: CodePtr) -> Result let (code_ptr, gc_offsets) = asm.compile(cb)?; assert!(gc_offsets.is_empty()); if get_option!(perf) { - let start_ptr = code_ptr.raw_ptr(cb) as usize; - let end_ptr = cb.get_write_ptr().raw_ptr(cb) as usize; + let start_ptr = code_ptr.raw_addr(cb); + let end_ptr = cb.get_write_ptr().raw_addr(cb); let code_size = end_ptr - start_ptr; let iseq_name = iseq_get_location(iseq, 0); register_with_perf(format!("entry for {iseq_name}"), start_ptr, code_size); @@ -298,8 +298,8 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Resul let result = asm.compile(cb); if let Ok((start_ptr, _)) = result { if get_option!(perf) { - let start_usize = start_ptr.raw_ptr(cb) as usize; - let end_usize = cb.get_write_ptr().raw_ptr(cb) as usize; + let start_usize = start_ptr.raw_addr(cb); + let end_usize = cb.get_write_ptr().raw_addr(cb); let code_size = end_usize - start_usize; let iseq_name = iseq_get_location(iseq, 0); register_with_perf(iseq_name, start_usize, code_size); @@ -508,7 +508,7 @@ fn gen_objtostring(jit: &mut JITState, asm: &mut Assembler, val: Opnd, cd: *cons gen_prepare_non_leaf_call(jit, asm, state); // TODO: Specialize for immediate types // Call rb_vm_objtostring(iseq, recv, cd) - let ret = asm_ccall!(asm, rb_vm_objtostring, VALUE(jit.iseq as usize).into(), val, (cd as usize).into()); + let ret = asm_ccall!(asm, rb_vm_objtostring, VALUE::from(jit.iseq).into(), val, Opnd::const_ptr(cd)); // TODO: Call `to_s` on the receiver if rb_vm_objtostring returns Qundef // Need to replicate what CALL_SIMPLE_METHOD does @@ -833,12 +833,12 @@ fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, val: fn gen_getclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) -> Opnd { gen_prepare_non_leaf_call(jit, asm, state); - asm_ccall!(asm, rb_vm_getclassvariable, VALUE(jit.iseq as usize).into(), CFP, id.0.into(), Opnd::const_ptr(ic)) + asm_ccall!(asm, rb_vm_getclassvariable, VALUE::from(jit.iseq).into(), CFP, id.0.into(), Opnd::const_ptr(ic)) } fn gen_setclassvar(jit: &mut JITState, asm: &mut Assembler, id: ID, val: Opnd, ic: *const iseq_inline_cvar_cache_entry, state: &FrameState) { gen_prepare_non_leaf_call(jit, asm, state); - asm_ccall!(asm, rb_vm_setclassvariable, VALUE(jit.iseq as usize).into(), CFP, id.0.into(), val, Opnd::const_ptr(ic)); + asm_ccall!(asm, rb_vm_setclassvariable, VALUE::from(jit.iseq).into(), CFP, id.0.into(), val, Opnd::const_ptr(ic)); } /// Look up global variables @@ -975,7 +975,7 @@ fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h asm_comment!(asm, "Load embedded ivar id={} index={}", id.contents_lossy(), index); - let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index as usize) as i32; + let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index.as_usize()) as i32; let self_val = asm.load(self_val); let ivar_opnd = Opnd::mem(64, self_val, offs); asm.load(ivar_opnd) @@ -990,7 +990,7 @@ fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 let tbl_opnd = asm.load(Opnd::mem(64, self_val, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32)); // Read the ivar from the extended table - let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index as usize) as i32); + let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index.as_usize()) as i32); asm.load(ivar_opnd) } @@ -1113,7 +1113,7 @@ fn gen_send( } asm.ccall( rb_vm_send as *const u8, - vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], ) } @@ -1136,7 +1136,7 @@ fn gen_send_forward( } asm.ccall( rb_vm_sendforward as *const u8, - vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], ) } @@ -1157,7 +1157,7 @@ fn gen_send_without_block( } asm.ccall( rb_vm_opt_send_without_block as *const u8, - vec![EC, CFP, (cd as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd)], ) } @@ -1263,7 +1263,7 @@ fn gen_invokeblock( } asm.ccall( rb_vm_invokeblock as *const u8, - vec![EC, CFP, (cd as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd)], ) } @@ -1285,7 +1285,7 @@ fn gen_invokesuper( } asm.ccall( rb_vm_invokesuper as *const u8, - vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()], + vec![EC, CFP, Opnd::const_ptr(cd), VALUE::from(blockiseq).into()], ) } @@ -1544,7 +1544,7 @@ fn gen_is_method_cfunc(jit: &JITState, asm: &mut Assembler, val: lir::Opnd, cd: unsafe extern "C" { fn rb_vm_method_cfunc_is(iseq: IseqPtr, cd: *const rb_call_data, recv: VALUE, cfunc: *const u8) -> VALUE; } - asm_ccall!(asm, rb_vm_method_cfunc_is, VALUE(jit.iseq as usize).into(), (cd as usize).into(), val, (cfunc as usize).into()) + asm_ccall!(asm, rb_vm_method_cfunc_is, VALUE::from(jit.iseq).into(), Opnd::const_ptr(cd), val, Opnd::const_ptr(cfunc)) } fn gen_is_bit_equal(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> lir::Opnd { @@ -1889,7 +1889,7 @@ fn param_opnd(idx: usize) -> Opnd { /// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details. pub fn local_idx_to_ep_offset(iseq: IseqPtr, local_idx: usize) -> i32 { let local_size = unsafe { get_iseq_body_local_table_size(iseq) }; - local_size_and_idx_to_ep_offset(local_size as usize, local_idx) + local_size_and_idx_to_ep_offset(local_size.as_usize(), local_idx) } /// Convert the number of locals and a local index to an offset from the EP @@ -2005,8 +2005,8 @@ c_callable! { rb_set_cfp_sp(cfp, sp); // Fill nils to uninitialized (non-argument) locals - let local_size = get_iseq_body_local_table_size(iseq) as usize; - let num_params = get_iseq_body_param_size(iseq) as usize; + let local_size = get_iseq_body_local_table_size(iseq).as_usize(); + let num_params = get_iseq_body_param_size(iseq).as_usize(); let base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, num_params) as isize); slice::from_raw_parts_mut(base, local_size - num_params).fill(Qnil); } diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index 5af5c0e8b999a4..2717dfcf225f23 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -86,7 +86,7 @@ impl CodePtr { /// Get the address of the code pointer. pub fn raw_addr(self, base: &impl CodePtrBase) -> usize { - self.raw_ptr(base) as usize + self.raw_ptr(base).addr() } /// Get the offset component for the code pointer. Useful finding the distance between two From 8de628dc8055e1d812fdf326e4a6f74ce11a283d Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Thu, 23 Oct 2025 15:22:23 -0400 Subject: [PATCH 4/8] ZJIT: s/as_usize/to_usize/ to comply with rust API guidelines When the name is `as_*`, the guideline expects the return type to be a reference type. Also, it's good to have contrast in the naming from the more dangerous `as usize` cast `IntoUsize` is meant to be preferred over. See: https://rust-lang.github.io/api-guidelines/naming.html --- zjit/src/cast.rs | 10 +++++----- zjit/src/codegen.rs | 20 ++++++++++---------- zjit/src/hir.rs | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/zjit/src/cast.rs b/zjit/src/cast.rs index c6d11ef4af18dc..52e2078cde3cc3 100644 --- a/zjit/src/cast.rs +++ b/zjit/src/cast.rs @@ -16,19 +16,19 @@ /// the method `into()` also causes a name conflict. pub(crate) trait IntoUsize { /// Convert to usize. Implementation conditional on width of [usize]. - fn as_usize(self) -> usize; + fn to_usize(self) -> usize; } #[cfg(target_pointer_width = "64")] impl IntoUsize for u64 { - fn as_usize(self) -> usize { + fn to_usize(self) -> usize { self as usize } } #[cfg(target_pointer_width = "64")] impl IntoUsize for u32 { - fn as_usize(self) -> usize { + fn to_usize(self) -> usize { self as usize } } @@ -36,7 +36,7 @@ impl IntoUsize for u32 { impl IntoUsize for u16 { /// Alias for `.into()`. For convenience so you could use the trait for /// all unsgined types. - fn as_usize(self) -> usize { + fn to_usize(self) -> usize { self.into() } } @@ -44,7 +44,7 @@ impl IntoUsize for u16 { impl IntoUsize for u8 { /// Alias for `.into()`. For convenience so you could use the trait for /// all unsgined types. - fn as_usize(self) -> usize { + fn to_usize(self) -> usize { self.into() } } diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 81608e5ae2528d..16b5e94d342263 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -736,7 +736,7 @@ fn gen_ccall_with_frame( }); asm_comment!(asm, "switch to new SP register"); - let sp_offset = (caller_stack_size + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; + let sp_offset = (caller_stack_size + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); asm.mov(SP, new_sp); @@ -792,7 +792,7 @@ fn gen_ccall_variadic( }); asm_comment!(asm, "switch to new SP register"); - let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; + let sp_offset = (state.stack().len() - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); asm.mov(SP, new_sp); @@ -975,7 +975,7 @@ fn gen_load_ivar_embedded(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h asm_comment!(asm, "Load embedded ivar id={} index={}", id.contents_lossy(), index); - let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index.as_usize()) as i32; + let offs = ROBJECT_OFFSET_AS_ARY as i32 + (SIZEOF_VALUE * index.to_usize()) as i32; let self_val = asm.load(self_val); let ivar_opnd = Opnd::mem(64, self_val, offs); asm.load(ivar_opnd) @@ -990,7 +990,7 @@ fn gen_load_ivar_extended(asm: &mut Assembler, self_val: Opnd, id: ID, index: u1 let tbl_opnd = asm.load(Opnd::mem(64, self_val, ROBJECT_OFFSET_AS_HEAP_FIELDS as i32)); // Read the ivar from the extended table - let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index.as_usize()) as i32); + let ivar_opnd = Opnd::mem(64, tbl_opnd, (SIZEOF_VALUE * index.to_usize()) as i32); asm.load(ivar_opnd) } @@ -1174,8 +1174,8 @@ fn gen_send_without_block_direct( ) -> lir::Opnd { gen_incr_counter(asm, Counter::iseq_optimized_send_count); - let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.as_usize(); - let stack_growth = state.stack_size() + local_size + unsafe { get_iseq_body_stack_max(iseq) }.as_usize(); + let local_size = unsafe { get_iseq_body_local_table_size(iseq) }.to_usize(); + let stack_growth = state.stack_size() + local_size + unsafe { get_iseq_body_stack_max(iseq) }.to_usize(); gen_stack_overflow_check(jit, asm, state, stack_growth); // Save cfp->pc and cfp->sp for the caller frame @@ -1211,7 +1211,7 @@ fn gen_send_without_block_direct( }); asm_comment!(asm, "switch to new SP register"); - let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.as_usize()) * SIZEOF_VALUE; + let sp_offset = (state.stack().len() + local_size - args.len() + VM_ENV_DATA_SIZE.to_usize()) * SIZEOF_VALUE; let new_sp = asm.add(SP, sp_offset.into()); asm.mov(SP, new_sp); @@ -1889,7 +1889,7 @@ fn param_opnd(idx: usize) -> Opnd { /// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details. pub fn local_idx_to_ep_offset(iseq: IseqPtr, local_idx: usize) -> i32 { let local_size = unsafe { get_iseq_body_local_table_size(iseq) }; - local_size_and_idx_to_ep_offset(local_size.as_usize(), local_idx) + local_size_and_idx_to_ep_offset(local_size.to_usize(), local_idx) } /// Convert the number of locals and a local index to an offset from the EP @@ -2005,8 +2005,8 @@ c_callable! { rb_set_cfp_sp(cfp, sp); // Fill nils to uninitialized (non-argument) locals - let local_size = get_iseq_body_local_table_size(iseq).as_usize(); - let num_params = get_iseq_body_param_size(iseq).as_usize(); + let local_size = get_iseq_body_local_table_size(iseq).to_usize(); + let num_params = get_iseq_body_param_size(iseq).to_usize(); let base = sp.offset(-local_size_and_idx_to_bp_offset(local_size, num_params) as isize); slice::from_raw_parts_mut(base, local_size - num_params).fill(Qnil); } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 7ee2308eb5d888..dbb9177ee3e6e8 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1873,7 +1873,7 @@ impl Function { /// Set self.param_types. They are copied to the param types of jit_entry_blocks. fn set_param_types(&mut self) { let iseq = self.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); + let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); let rest_param_idx = iseq_rest_param_idx(iseq); self.param_types.push(types::BasicObject); // self @@ -3885,7 +3885,7 @@ pub enum ParseError { /// Return the number of locals in the current ISEQ (includes parameters) fn num_locals(iseq: *const rb_iseq_t) -> usize { - (unsafe { get_iseq_body_local_table_size(iseq) }).as_usize() + (unsafe { get_iseq_body_local_table_size(iseq) }).to_usize() } /// If we can't handle the type of send (yet), bail out. @@ -4896,7 +4896,7 @@ fn compile_entry_block(fun: &mut Function, jit_entry_insns: &[u32]) { /// Compile initial locals for an entry_block for the interpreter fn compile_entry_state(fun: &mut Function, entry_block: BlockId) -> (InsnId, FrameState) { let iseq = fun.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); + let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); let rest_param_idx = iseq_rest_param_idx(iseq); let self_param = fun.push_insn(entry_block, Insn::LoadSelf); @@ -4929,7 +4929,7 @@ fn compile_jit_entry_block(fun: &mut Function, jit_entry_idx: usize, target_bloc /// Compile params and initial locals for a jit_entry_block fn compile_jit_entry_state(fun: &mut Function, jit_entry_block: BlockId) -> (InsnId, FrameState) { let iseq = fun.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.as_usize(); + let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); let self_param = fun.push_insn(jit_entry_block, Insn::Param); let mut entry_state = FrameState::new(iseq); From 8b0d405337781205412f7eb8fd7d3ae684f3469a Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 23 Oct 2025 20:53:46 +0100 Subject: [PATCH 5/8] [DOC] Tweaks for String#start_with? --- doc/string/start_with_p.rdoc | 11 +++++------ string.c | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/string/start_with_p.rdoc b/doc/string/start_with_p.rdoc index 5d1f9f954370e0..298a5572769ea8 100644 --- a/doc/string/start_with_p.rdoc +++ b/doc/string/start_with_p.rdoc @@ -1,10 +1,9 @@ -Returns whether +self+ starts with any of the given +string_or_regexp+. +Returns whether +self+ starts with any of the given +patterns+. -Matches patterns against the beginning of +self+. -For each given +string_or_regexp+, the pattern is: +For each argument, the pattern used is: -- +string_or_regexp+ itself, if it is a Regexp. -- Regexp.quote(string_or_regexp), if +string_or_regexp+ is a string. +- The pattern itself, if it is a Regexp. +- Regexp.quote(pattern), if it is a string. Returns +true+ if any pattern matches the beginning, +false+ otherwise: @@ -15,4 +14,4 @@ Returns +true+ if any pattern matches the beginning, +false+ otherwise: 'тест'.start_with?('т') # => true 'こんにちは'.start_with?('こ') # => true -Related: String#end_with?. +Related: see {Querying}[rdoc-ref:String@Querying]. diff --git a/string.c b/string.c index 1de87071272938..d90f606b821c2d 100644 --- a/string.c +++ b/string.c @@ -11214,7 +11214,7 @@ rb_str_rpartition(VALUE str, VALUE sep) /* * call-seq: - * start_with?(*string_or_regexp) -> true or false + * start_with?(*patterns) -> true or false * * :include: doc/string/start_with_p.rdoc * From 0227ad07a41eea95349a9f26946dbf00291da17f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 23 Oct 2025 21:34:55 +0100 Subject: [PATCH 6/8] [DOC] Tweaks for String#strip! --- string.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/string.c b/string.c index d90f606b821c2d..eec64ac63f959a 100644 --- a/string.c +++ b/string.c @@ -10482,10 +10482,12 @@ rb_str_rstrip(VALUE str) * call-seq: * strip! -> self or nil * - * Like String#strip, except that any modifications are made in +self+; - * returns +self+ if any modification are made, +nil+ otherwise. + * Like String#strip, except that: * - * Related: String#lstrip!, String#strip!. + * - Any modifications are made to +self+. + * - Returns +self+ if any modification are made, +nil+ otherwise. + * + * Related: see {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From ab94bce885314d0065514a88cd356a89642292b0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 23 Oct 2025 17:13:37 -0500 Subject: [PATCH 7/8] [DOC] Tweaks for String#squeeze! --- string.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/string.c b/string.c index eec64ac63f959a..373c76a26fc69e 100644 --- a/string.c +++ b/string.c @@ -8903,8 +8903,12 @@ rb_str_delete(int argc, VALUE *argv, VALUE str) * call-seq: * squeeze!(*selectors) -> self or nil * - * Like String#squeeze, but modifies +self+ in place. - * Returns +self+ if any changes were made, +nil+ otherwise. + * Like String#squeeze, except that: + * + * - Characters are squeezed in +self+ (not in a copy of +self+). + * - Returns +self+ if any changes are made, +nil+ otherwise. + * + * Related: See {Modifying}[rdoc-ref:String@Modifying]. */ static VALUE From 230276dd42e49059c920c3230268ebed776e74a1 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 23 Oct 2025 21:12:54 +0100 Subject: [PATCH 8/8] [DOC] Tweaks for String#strip --- string.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/string.c b/string.c index 373c76a26fc69e..55a7eebc5a579f 100644 --- a/string.c +++ b/string.c @@ -10525,15 +10525,15 @@ rb_str_strip_bang(VALUE str) * call-seq: * strip -> new_string * - * Returns a copy of the receiver with leading and trailing whitespace removed; + * Returns a copy of +self+ with leading and trailing whitespace removed; * see {Whitespace in Strings}[rdoc-ref:String@Whitespace+in+Strings]: * * whitespace = "\x00\t\n\v\f\r " * s = whitespace + 'abc' + whitespace - * s # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " + * # => "\u0000\t\n\v\f\r abc\u0000\t\n\v\f\r " * s.strip # => "abc" * - * Related: String#lstrip, String#rstrip. + * Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. */ static VALUE