From 7a09df45f2619a672eaab763c2e2a0c5199d5daa Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 19 Nov 2025 16:08:55 -0500 Subject: [PATCH 01/10] Name the `iseq->body->param` struct and update bindings for JITs This will make reading the parameters nicer for the JITs. Should be no-op for the C side. --- vm_core.h | 2 +- yjit/src/cruby_bindings.inc.rs | 8 +- zjit/bindgen/src/main.rs | 8 +- zjit/src/cruby.rs | 9 +- zjit/src/cruby_bindings.inc.rs | 776 ++++++++++++++++++++++++++++++++- 5 files changed, 787 insertions(+), 16 deletions(-) diff --git a/vm_core.h b/vm_core.h index 0f83f2b2e9e015..28d585deb2ab01 100644 --- a/vm_core.h +++ b/vm_core.h @@ -431,7 +431,7 @@ struct rb_iseq_constant_body { * size = M+N+O+(*1)+K+(&1)+(**1) // parameter size. */ - struct { + struct rb_iseq_parameters { struct { unsigned int has_lead : 1; unsigned int has_opt : 1; diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index a6aef48313ad71..66d4e5111d7bbd 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -490,7 +490,7 @@ pub const BUILTIN_ATTR_C_TRACE: rb_builtin_attr = 8; pub type rb_builtin_attr = u32; #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword { +pub struct rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword { pub num: ::std::os::raw::c_int, pub required_num: ::std::os::raw::c_int, pub bits_start: ::std::os::raw::c_int, @@ -942,12 +942,14 @@ pub const DEFINED_REF: defined_type = 15; pub const DEFINED_FUNC: defined_type = 16; pub const DEFINED_CONST_FROM: defined_type = 17; pub type defined_type = u32; -pub type rb_seq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; +pub type rb_seq_param_keyword_struct = + rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword; pub const ROBJECT_OFFSET_AS_HEAP_FIELDS: jit_bindgen_constants = 16; pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; pub type jit_bindgen_constants = u32; -pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; +pub type rb_iseq_param_keyword_struct = + rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword; extern "C" { pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); pub fn rb_class_attached_object(klass: VALUE) -> VALUE; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 7873a209777605..bac17f4a6d4452 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -424,8 +424,12 @@ fn main() { .blocklist_type("VALUE") .blocklist_type("ID") - .opaque_type("rb_iseq_t") - .blocklist_type("rb_iseq_t") + // Avoid binding to stuff we don't use + .blocklist_item("rb_thread_struct.*") + .opaque_type("rb_thread_struct.*") + .blocklist_item("iseq_inline_storage_entry_.*") + .opaque_type("iseq_inline_storage_entry") + .opaque_type("iseq_compile_data") // Finish the builder and generate the bindings. .generate() diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index a854f2e07c0667..771f256037ef4f 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -104,6 +104,7 @@ pub type RedefinitionFlag = u32; #[allow(unsafe_op_in_unsafe_fn)] #[allow(dead_code)] +#[allow(unnecessary_transmutes)] // https://github.com/rust-lang/rust-bindgen/issues/2807 #[allow(clippy::all)] // warning meant to help with reading; not useful for generated code mod autogened { use super::*; @@ -246,14 +247,6 @@ pub fn insn_len(opcode: usize) -> u32 { } } -/// Opaque iseq type for opaque iseq pointers from vm_core.h -/// See: -#[repr(C)] -pub struct rb_iseq_t { - _data: [u8; 0], - _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, -} - /// An object handle similar to VALUE in the C code. Our methods assume /// that this is a handle. Sometimes the C code briefly uses VALUE as /// an unsigned integer type and don't necessarily store valid handles but diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 0fde4e3ab70a93..66126b627c0fd3 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -410,6 +410,11 @@ pub const BOP_INCLUDE_P: ruby_basic_operators = 33; pub const BOP_LAST_: ruby_basic_operators = 34; pub type ruby_basic_operators = u32; pub type rb_serial_t = ::std::os::raw::c_ulonglong; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_id_table { + _unused: [u8; 0], +} pub const imemo_env: imemo_type = 0; pub const imemo_cref: imemo_type = 1; pub const imemo_svar: imemo_type = 2; @@ -475,6 +480,7 @@ pub const VM_METHOD_TYPE_OPTIMIZED: rb_method_type_t = 9; pub const VM_METHOD_TYPE_MISSING: rb_method_type_t = 10; pub const VM_METHOD_TYPE_REFINED: rb_method_type_t = 11; pub type rb_method_type_t = u32; +pub type rb_iseq_t = rb_iseq_struct; pub type rb_cfunc_t = ::std::option::Option VALUE>; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -497,7 +503,22 @@ pub const OPTIMIZED_METHOD_TYPE_STRUCT_AREF: method_optimized_type = 3; pub const OPTIMIZED_METHOD_TYPE_STRUCT_ASET: method_optimized_type = 4; pub const OPTIMIZED_METHOD_TYPE__MAX: method_optimized_type = 5; pub type method_optimized_type = u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_code_position_struct { + pub lineno: ::std::os::raw::c_int, + pub column: ::std::os::raw::c_int, +} +pub type rb_code_position_t = rb_code_position_struct; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_code_location_struct { + pub beg_pos: rb_code_position_t, + pub end_pos: rb_code_position_t, +} +pub type rb_code_location_t = rb_code_location_struct; pub type rb_num_t = ::std::os::raw::c_ulong; +pub type rb_snum_t = ::std::os::raw::c_long; pub const RUBY_TAG_NONE: ruby_tag_type = 0; pub const RUBY_TAG_RETURN: ruby_tag_type = 1; pub const RUBY_TAG_BREAK: ruby_tag_type = 2; @@ -534,6 +555,23 @@ pub struct iseq_inline_iv_cache_entry { pub struct iseq_inline_cvar_cache_entry { pub entry: *mut rb_cvar_class_tbl_entry, } +#[repr(C)] +#[repr(align(8))] +#[derive(Copy, Clone)] +pub struct iseq_inline_storage_entry { + pub _bindgen_opaque_blob: [u64; 2usize], +} +#[repr(C)] +pub struct rb_iseq_location_struct { + pub pathobj: VALUE, + pub base_label: VALUE, + pub label: VALUE, + pub first_lineno: ::std::os::raw::c_int, + pub node_id: ::std::os::raw::c_int, + pub code_location: rb_code_location_t, +} +pub type rb_iseq_location_t = rb_iseq_location_struct; +pub type iseq_bits_t = usize; pub const ISEQ_TYPE_TOP: rb_iseq_type = 0; pub const ISEQ_TYPE_METHOD: rb_iseq_type = 1; pub const ISEQ_TYPE_BLOCK: rb_iseq_type = 2; @@ -549,9 +587,611 @@ pub const BUILTIN_ATTR_SINGLE_NOARG_LEAF: rb_builtin_attr = 2; pub const BUILTIN_ATTR_INLINE_BLOCK: rb_builtin_attr = 4; pub const BUILTIN_ATTR_C_TRACE: rb_builtin_attr = 8; pub type rb_builtin_attr = u32; +pub type rb_jit_func_t = ::std::option::Option< + unsafe extern "C" fn( + arg1: *mut rb_execution_context_struct, + arg2: *mut rb_control_frame_struct, + ) -> VALUE, +>; +#[repr(C)] +pub struct rb_iseq_constant_body { + pub type_: rb_iseq_type, + pub iseq_size: ::std::os::raw::c_uint, + pub iseq_encoded: *mut VALUE, + pub param: rb_iseq_constant_body_rb_iseq_parameters, + pub location: rb_iseq_location_t, + pub insns_info: rb_iseq_constant_body_iseq_insn_info, + pub local_table: *const ID, + pub lvar_states: *mut rb_iseq_constant_body_lvar_state, + pub catch_table: *mut iseq_catch_table, + pub parent_iseq: *const rb_iseq_struct, + pub local_iseq: *mut rb_iseq_struct, + pub is_entries: *mut iseq_inline_storage_entry, + pub call_data: *mut rb_call_data, + pub variable: rb_iseq_constant_body__bindgen_ty_1, + pub local_table_size: ::std::os::raw::c_uint, + pub ic_size: ::std::os::raw::c_uint, + pub ise_size: ::std::os::raw::c_uint, + pub ivc_size: ::std::os::raw::c_uint, + pub icvarc_size: ::std::os::raw::c_uint, + pub ci_size: ::std::os::raw::c_uint, + pub stack_max: ::std::os::raw::c_uint, + pub builtin_attrs: ::std::os::raw::c_uint, + pub prism: bool, + pub mark_bits: rb_iseq_constant_body__bindgen_ty_2, + pub outer_variables: *mut rb_id_table, + pub mandatory_only_iseq: *const rb_iseq_t, + pub jit_entry: rb_jit_func_t, + pub jit_entry_calls: ::std::os::raw::c_ulong, + pub jit_exception: rb_jit_func_t, + pub jit_exception_calls: ::std::os::raw::c_ulong, + pub zjit_payload: *mut ::std::os::raw::c_void, +} #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword { +pub struct rb_iseq_constant_body_rb_iseq_parameters { + pub flags: rb_iseq_constant_body_rb_iseq_parameters__bindgen_ty_1, + pub size: ::std::os::raw::c_uint, + pub lead_num: ::std::os::raw::c_int, + pub opt_num: ::std::os::raw::c_int, + pub rest_start: ::std::os::raw::c_int, + pub post_start: ::std::os::raw::c_int, + pub post_num: ::std::os::raw::c_int, + pub block_start: ::std::os::raw::c_int, + pub opt_table: *const VALUE, + pub keyword: *const rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword, +} +#[repr(C)] +#[repr(align(4))] +#[derive(Debug, Copy, Clone)] +pub struct rb_iseq_constant_body_rb_iseq_parameters__bindgen_ty_1 { + pub _bitfield_align_1: [u8; 0], + pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>, + pub __bindgen_padding_0: u16, +} +impl rb_iseq_constant_body_rb_iseq_parameters__bindgen_ty_1 { + #[inline] + pub fn has_lead(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_lead(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(0usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_lead_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 0usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_lead_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 0usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_opt(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_opt(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(1usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_opt_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 1usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_opt_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 1usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_rest(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(2usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_rest(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(2usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_rest_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 2usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_rest_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 2usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_post(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_post(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(3usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_post_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 3usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_post_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 3usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_kw(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_kw(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(4usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_kw_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 4usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_kw_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 4usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_kwrest(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_kwrest(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(5usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_kwrest_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 5usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_kwrest_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 5usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn has_block(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(6usize, 1u8) as u32) } + } + #[inline] + pub fn set_has_block(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(6usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn has_block_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 6usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_has_block_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 6usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn ambiguous_param0(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u32) } + } + #[inline] + pub fn set_ambiguous_param0(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(7usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn ambiguous_param0_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 7usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_ambiguous_param0_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 7usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn accepts_no_kwarg(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u32) } + } + #[inline] + pub fn set_accepts_no_kwarg(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(8usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn accepts_no_kwarg_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 8usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_accepts_no_kwarg_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 8usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn ruby2_keywords(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(9usize, 1u8) as u32) } + } + #[inline] + pub fn set_ruby2_keywords(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(9usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn ruby2_keywords_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 9usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_ruby2_keywords_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 9usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn anon_rest(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(10usize, 1u8) as u32) } + } + #[inline] + pub fn set_anon_rest(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(10usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn anon_rest_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 10usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_anon_rest_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 10usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn anon_kwrest(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(11usize, 1u8) as u32) } + } + #[inline] + pub fn set_anon_kwrest(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(11usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn anon_kwrest_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 11usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_anon_kwrest_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 11usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn use_block(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(12usize, 1u8) as u32) } + } + #[inline] + pub fn set_use_block(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(12usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn use_block_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 12usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_use_block_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 12usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn forwardable(&self) -> ::std::os::raw::c_uint { + unsafe { ::std::mem::transmute(self._bitfield_1.get(13usize, 1u8) as u32) } + } + #[inline] + pub fn set_forwardable(&mut self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + self._bitfield_1.set(13usize, 1u8, val as u64) + } + } + #[inline] + pub unsafe fn forwardable_raw(this: *const Self) -> ::std::os::raw::c_uint { + unsafe { + ::std::mem::transmute(<__BindgenBitfieldUnit<[u8; 2usize]>>::raw_get( + ::std::ptr::addr_of!((*this)._bitfield_1), + 13usize, + 1u8, + ) as u32) + } + } + #[inline] + pub unsafe fn set_forwardable_raw(this: *mut Self, val: ::std::os::raw::c_uint) { + unsafe { + let val: u32 = ::std::mem::transmute(val); + <__BindgenBitfieldUnit<[u8; 2usize]>>::raw_set( + ::std::ptr::addr_of_mut!((*this)._bitfield_1), + 13usize, + 1u8, + val as u64, + ) + } + } + #[inline] + pub fn new_bitfield_1( + has_lead: ::std::os::raw::c_uint, + has_opt: ::std::os::raw::c_uint, + has_rest: ::std::os::raw::c_uint, + has_post: ::std::os::raw::c_uint, + has_kw: ::std::os::raw::c_uint, + has_kwrest: ::std::os::raw::c_uint, + has_block: ::std::os::raw::c_uint, + ambiguous_param0: ::std::os::raw::c_uint, + accepts_no_kwarg: ::std::os::raw::c_uint, + ruby2_keywords: ::std::os::raw::c_uint, + anon_rest: ::std::os::raw::c_uint, + anon_kwrest: ::std::os::raw::c_uint, + use_block: ::std::os::raw::c_uint, + forwardable: ::std::os::raw::c_uint, + ) -> __BindgenBitfieldUnit<[u8; 2usize]> { + let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default(); + __bindgen_bitfield_unit.set(0usize, 1u8, { + let has_lead: u32 = unsafe { ::std::mem::transmute(has_lead) }; + has_lead as u64 + }); + __bindgen_bitfield_unit.set(1usize, 1u8, { + let has_opt: u32 = unsafe { ::std::mem::transmute(has_opt) }; + has_opt as u64 + }); + __bindgen_bitfield_unit.set(2usize, 1u8, { + let has_rest: u32 = unsafe { ::std::mem::transmute(has_rest) }; + has_rest as u64 + }); + __bindgen_bitfield_unit.set(3usize, 1u8, { + let has_post: u32 = unsafe { ::std::mem::transmute(has_post) }; + has_post as u64 + }); + __bindgen_bitfield_unit.set(4usize, 1u8, { + let has_kw: u32 = unsafe { ::std::mem::transmute(has_kw) }; + has_kw as u64 + }); + __bindgen_bitfield_unit.set(5usize, 1u8, { + let has_kwrest: u32 = unsafe { ::std::mem::transmute(has_kwrest) }; + has_kwrest as u64 + }); + __bindgen_bitfield_unit.set(6usize, 1u8, { + let has_block: u32 = unsafe { ::std::mem::transmute(has_block) }; + has_block as u64 + }); + __bindgen_bitfield_unit.set(7usize, 1u8, { + let ambiguous_param0: u32 = unsafe { ::std::mem::transmute(ambiguous_param0) }; + ambiguous_param0 as u64 + }); + __bindgen_bitfield_unit.set(8usize, 1u8, { + let accepts_no_kwarg: u32 = unsafe { ::std::mem::transmute(accepts_no_kwarg) }; + accepts_no_kwarg as u64 + }); + __bindgen_bitfield_unit.set(9usize, 1u8, { + let ruby2_keywords: u32 = unsafe { ::std::mem::transmute(ruby2_keywords) }; + ruby2_keywords as u64 + }); + __bindgen_bitfield_unit.set(10usize, 1u8, { + let anon_rest: u32 = unsafe { ::std::mem::transmute(anon_rest) }; + anon_rest as u64 + }); + __bindgen_bitfield_unit.set(11usize, 1u8, { + let anon_kwrest: u32 = unsafe { ::std::mem::transmute(anon_kwrest) }; + anon_kwrest as u64 + }); + __bindgen_bitfield_unit.set(12usize, 1u8, { + let use_block: u32 = unsafe { ::std::mem::transmute(use_block) }; + use_block as u64 + }); + __bindgen_bitfield_unit.set(13usize, 1u8, { + let forwardable: u32 = unsafe { ::std::mem::transmute(forwardable) }; + forwardable as u64 + }); + __bindgen_bitfield_unit + } +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword { pub num: ::std::os::raw::c_int, pub required_num: ::std::os::raw::c_int, pub bits_start: ::std::os::raw::c_int, @@ -560,6 +1200,66 @@ pub struct rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword { pub default_values: *mut VALUE, } #[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_iseq_constant_body_iseq_insn_info { + pub body: *const iseq_insn_info_entry, + pub positions: *mut ::std::os::raw::c_uint, + pub size: ::std::os::raw::c_uint, + pub succ_index_table: *mut succ_index_table, +} +pub const lvar_uninitialized: rb_iseq_constant_body_lvar_state = 0; +pub const lvar_initialized: rb_iseq_constant_body_lvar_state = 1; +pub const lvar_reassigned: rb_iseq_constant_body_lvar_state = 2; +pub type rb_iseq_constant_body_lvar_state = u32; +#[repr(C)] +pub struct rb_iseq_constant_body__bindgen_ty_1 { + pub flip_count: rb_snum_t, + pub script_lines: VALUE, + pub coverage: VALUE, + pub pc2branchindex: VALUE, + pub original_iseq: *mut VALUE, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union rb_iseq_constant_body__bindgen_ty_2 { + pub list: *mut iseq_bits_t, + pub single: iseq_bits_t, +} +#[repr(C)] +pub struct rb_iseq_struct { + pub flags: VALUE, + pub wrapper: VALUE, + pub body: *mut rb_iseq_constant_body, + pub aux: rb_iseq_struct__bindgen_ty_1, +} +#[repr(C)] +pub struct rb_iseq_struct__bindgen_ty_1 { + pub compile_data: __BindgenUnionField<*mut iseq_compile_data>, + pub loader: __BindgenUnionField, + pub exec: __BindgenUnionField, + pub bindgen_union_field: [u64; 2usize], +} +#[repr(C)] +pub struct rb_iseq_struct__bindgen_ty_1__bindgen_ty_1 { + pub obj: VALUE, + pub index: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_iseq_struct__bindgen_ty_1__bindgen_ty_2 { + pub local_hooks: *mut rb_hook_list_struct, + pub global_trace_events: rb_event_flag_t, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_hook_list_struct { + pub hooks: *mut rb_event_hook_struct, + pub events: rb_event_flag_t, + pub running: ::std::os::raw::c_uint, + pub need_clean: bool, + pub is_local: bool, +} +#[repr(C)] pub struct rb_captured_block { pub self_: VALUE, pub ep: *const VALUE, @@ -1076,6 +1776,67 @@ pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), >; +#[repr(C)] +#[repr(align(8))] +#[derive(Debug, Copy, Clone)] +pub struct iseq_compile_data { + pub _bindgen_opaque_blob: [u64; 24usize], +} +#[repr(C)] +#[derive(Copy, Clone)] +pub union iseq_compile_data__bindgen_ty_1 { + pub list: *mut iseq_bits_t, + pub single: iseq_bits_t, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iseq_compile_data__bindgen_ty_2 { + pub storage_head: *mut iseq_compile_data_storage, + pub storage_current: *mut iseq_compile_data_storage, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iseq_compile_data__bindgen_ty_3 { + pub storage_head: *mut iseq_compile_data_storage, + pub storage_current: *mut iseq_compile_data_storage, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iseq_insn_info_entry { + pub line_no: ::std::os::raw::c_int, + pub node_id: ::std::os::raw::c_int, + pub events: rb_event_flag_t, +} +pub const CATCH_TYPE_RESCUE: rb_catch_type = 3; +pub const CATCH_TYPE_ENSURE: rb_catch_type = 5; +pub const CATCH_TYPE_RETRY: rb_catch_type = 7; +pub const CATCH_TYPE_BREAK: rb_catch_type = 9; +pub const CATCH_TYPE_REDO: rb_catch_type = 11; +pub const CATCH_TYPE_NEXT: rb_catch_type = 13; +pub type rb_catch_type = u32; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct iseq_catch_table_entry { + pub type_: rb_catch_type, + pub iseq: *mut rb_iseq_t, + pub start: ::std::os::raw::c_uint, + pub end: ::std::os::raw::c_uint, + pub cont: ::std::os::raw::c_uint, + pub sp: ::std::os::raw::c_uint, +} +#[repr(C, packed)] +pub struct iseq_catch_table { + pub size: ::std::os::raw::c_uint, + pub entries: __IncompleteArrayField, +} +#[repr(C)] +#[derive(Debug)] +pub struct iseq_compile_data_storage { + pub next: *mut iseq_compile_data_storage, + pub pos: ::std::os::raw::c_uint, + pub size: ::std::os::raw::c_uint, + pub buff: __IncompleteArrayField<::std::os::raw::c_char>, +} pub const DEFINED_NOT_DEFINED: defined_type = 0; pub const DEFINED_NIL: defined_type = 1; pub const DEFINED_IVAR: defined_type = 2; @@ -1100,7 +1861,18 @@ pub const ROBJECT_OFFSET_AS_ARY: jit_bindgen_constants = 16; pub const RUBY_OFFSET_RSTRING_LEN: jit_bindgen_constants = 16; pub type jit_bindgen_constants = u32; pub const rb_invalid_shape_id: shape_id_t = 4294967295; -pub type rb_iseq_param_keyword_struct = rb_iseq_constant_body__bindgen_ty_1_rb_iseq_param_keyword; +pub type rb_iseq_param_keyword_struct = + rb_iseq_constant_body_rb_iseq_parameters_rb_iseq_param_keyword; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct succ_index_table { + pub _address: u8, +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rb_event_hook_struct { + pub _address: u8, +} unsafe extern "C" { pub fn ruby_xfree(ptr: *mut ::std::os::raw::c_void); pub fn rb_class_attached_object(klass: VALUE) -> VALUE; From 07ddb0ed73b1ea61cb717f4424f7b2909d8c0ab3 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 19 Nov 2025 16:39:29 -0500 Subject: [PATCH 02/10] ZJIT: Read `iseq->body->param` directly instead of through FFI Going through a call to a C function just to read a bitfield was a little extreme. We did it to be super conservative since bitfields have historically been the trigger of many bugs and surprises. Let's try directly accessing them with code from rust-bindgen. If this ends up causing issues, we can use the FFI approach behind nicer wrappers. In any case, directly access regular struct fields such as `lead_num` and `opt_num` to remove boilerplate. --- zjit/src/codegen.rs | 9 +++++---- zjit/src/cruby.rs | 37 ++++++++++++++++++------------------- zjit/src/hir.rs | 39 ++++++++++++++++++++++----------------- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 082db3fae44108..d00ab500d72796 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -1293,10 +1293,11 @@ fn gen_send_without_block_direct( let mut c_args = vec![recv]; c_args.extend(&args); - let num_optionals_passed = if unsafe { get_iseq_flags_has_opt(iseq) } { + let params = unsafe { iseq.params() }; + let num_optionals_passed = if params.flags.has_opt() != 0 { // See vm_call_iseq_setup_normal_opt_start in vm_inshelper.c - let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) } as u32; - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) } as u32; + let lead_num = params.lead_num as u32; + let opt_num = params.opt_num as u32; assert!(args.len() as u32 <= lead_num + opt_num); let num_optionals_passed = args.len() as u32 - lead_num; num_optionals_passed @@ -2212,7 +2213,7 @@ c_callable! { // Fill nils to uninitialized (non-argument) locals let local_size = get_iseq_body_local_table_size(iseq).to_usize(); - let num_params = get_iseq_body_param_size(iseq).to_usize(); + let num_params = iseq.params().size.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/cruby.rs b/zjit/src/cruby.rs index 771f256037ef4f..443ed0d86e3a99 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -192,21 +192,7 @@ pub use rb_get_iseq_body_local_iseq as get_iseq_body_local_iseq; pub use rb_get_iseq_body_iseq_encoded as get_iseq_body_iseq_encoded; pub use rb_get_iseq_body_stack_max as get_iseq_body_stack_max; pub use rb_get_iseq_body_type as get_iseq_body_type; -pub use rb_get_iseq_flags_has_lead as get_iseq_flags_has_lead; -pub use rb_get_iseq_flags_has_opt as get_iseq_flags_has_opt; -pub use rb_get_iseq_flags_has_kw as get_iseq_flags_has_kw; -pub use rb_get_iseq_flags_has_rest as get_iseq_flags_has_rest; -pub use rb_get_iseq_flags_has_post as get_iseq_flags_has_post; -pub use rb_get_iseq_flags_has_kwrest as get_iseq_flags_has_kwrest; -pub use rb_get_iseq_flags_has_block as get_iseq_flags_has_block; -pub use rb_get_iseq_flags_ambiguous_param0 as get_iseq_flags_ambiguous_param0; -pub use rb_get_iseq_flags_accepts_no_kwarg as get_iseq_flags_accepts_no_kwarg; pub use rb_get_iseq_body_local_table_size as get_iseq_body_local_table_size; -pub use rb_get_iseq_body_param_keyword as get_iseq_body_param_keyword; -pub use rb_get_iseq_body_param_size as get_iseq_body_param_size; -pub use rb_get_iseq_body_param_lead_num as get_iseq_body_param_lead_num; -pub use rb_get_iseq_body_param_opt_num as get_iseq_body_param_opt_num; -pub use rb_get_iseq_body_param_opt_table as get_iseq_body_param_opt_table; pub use rb_get_cikw_keyword_len as get_cikw_keyword_len; pub use rb_get_cikw_keywords_idx as get_cikw_keywords_idx; pub use rb_get_call_data_ci as get_call_data_ci; @@ -306,11 +292,10 @@ pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool { } /// Index of the local variable that has a rest parameter if any -pub fn iseq_rest_param_idx(iseq: IseqPtr) -> Option { - if !iseq.is_null() && unsafe { get_iseq_flags_has_rest(iseq) } { - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; - let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) }; - Some(opt_num + lead_num) +pub fn iseq_rest_param_idx(params: &IseqParameters) -> Option { + // TODO(alan): replace with `params.rest_start` + if params.flags.has_rest() != 0 { + Some(params.opt_num + params.lead_num) } else { None } @@ -686,6 +671,20 @@ impl VALUE { } } +pub type IseqParameters = rb_iseq_constant_body_rb_iseq_parameters; + +/// Extension trait to enable method calls on [`IseqPtr`] +pub trait IseqAccess { + unsafe fn params<'a>(self) -> &'a IseqParameters; +} + +impl IseqAccess for IseqPtr { + /// Get a description of the ISEQ's signature. Analogous to `ISEQ_BODY(iseq)->param` in C. + unsafe fn params<'a>(self) -> &'a IseqParameters { + unsafe { &(*(*self).body).param } + } +} + impl From for VALUE { /// For `.into()` convenience fn from(iseq: IseqPtr) -> Self { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 172b177c456f3b..961fe1c1428825 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1497,14 +1497,15 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq can_send = false; function.push_insn(block, Insn::IncrCounter(counter)); }; + let params = unsafe { iseq.params() }; use Counter::*; - if unsafe { rb_get_iseq_flags_has_rest(iseq) } { count_failure(complex_arg_pass_param_rest) } - if unsafe { rb_get_iseq_flags_has_post(iseq) } { count_failure(complex_arg_pass_param_post) } - if unsafe { rb_get_iseq_flags_has_kw(iseq) } { count_failure(complex_arg_pass_param_kw) } - if unsafe { rb_get_iseq_flags_has_kwrest(iseq) } { count_failure(complex_arg_pass_param_kwrest) } - if unsafe { rb_get_iseq_flags_has_block(iseq) } { count_failure(complex_arg_pass_param_block) } - if unsafe { rb_get_iseq_flags_forwardable(iseq) } { count_failure(complex_arg_pass_param_forwardable) } + if 0 != params.flags.has_rest() { count_failure(complex_arg_pass_param_rest) } + if 0 != params.flags.has_post() { count_failure(complex_arg_pass_param_post) } + if 0 != params.flags.has_kw() { count_failure(complex_arg_pass_param_kw) } + if 0 != params.flags.has_kwrest() { count_failure(complex_arg_pass_param_kwrest) } + if 0 != params.flags.has_block() { count_failure(complex_arg_pass_param_block) } + if 0 != params.flags.forwardable() { count_failure(complex_arg_pass_param_forwardable) } if !can_send { function.set_dynamic_send_reason(send_insn, ComplexArgPass); @@ -1512,8 +1513,8 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq } // Because we exclude e.g. post parameters above, they are also excluded from the sum below. - let lead_num = unsafe { get_iseq_body_param_lead_num(iseq) }; - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; + let lead_num = params.lead_num; + let opt_num = params.opt_num; can_send = c_int::try_from(args.len()) .as_ref() .map(|argc| (lead_num..=lead_num + opt_num).contains(argc)) @@ -2086,8 +2087,9 @@ 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) }.to_usize(); - let rest_param_idx = iseq_rest_param_idx(iseq); + let params = unsafe { iseq.params() }; + let param_size = params.size.to_usize(); + let rest_param_idx = iseq_rest_param_idx(params); self.param_types.push(types::BasicObject); // self for local_idx in 0..param_size { @@ -4596,11 +4598,12 @@ fn insn_idx_at_offset(idx: u32, offset: i64) -> u32 { /// List of insn_idx that starts a JIT entry block pub fn jit_entry_insns(iseq: IseqPtr) -> Vec { // TODO(alan): Make an iterator type for this instead of copying all of the opt_table each call - let opt_num = unsafe { get_iseq_body_param_opt_num(iseq) }; + let params = unsafe { iseq.params() }; + let opt_num = params.opt_num; if opt_num > 0 { let mut result = vec![]; - let opt_table = unsafe { get_iseq_body_param_opt_table(iseq) }; // `opt_num + 1` entries + let opt_table = params.opt_table; // `opt_num + 1` entries for opt_idx in 0..=opt_num as isize { let insn_idx = unsafe { opt_table.offset(opt_idx).read().as_u32() }; result.push(insn_idx); @@ -5715,8 +5718,9 @@ fn compile_entry_state(fun: &mut Function) -> (InsnId, FrameState) { fun.push_insn(entry_block, Insn::EntryPoint { jit_entry_idx: None }); let iseq = fun.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); - let rest_param_idx = iseq_rest_param_idx(iseq); + let params = unsafe { iseq.params() }; + let param_size = params.size.to_usize(); + let rest_param_idx = iseq_rest_param_idx(params); let self_param = fun.push_insn(entry_block, Insn::LoadSelf); let mut entry_state = FrameState::new(iseq); @@ -5748,9 +5752,10 @@ 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, jit_entry_idx: usize) -> (InsnId, FrameState) { let iseq = fun.iseq; - let param_size = unsafe { get_iseq_body_param_size(iseq) }.to_usize(); - let opt_num: usize = unsafe { get_iseq_body_param_opt_num(iseq) }.try_into().expect("iseq param opt_num >= 0"); - let lead_num: usize = unsafe { get_iseq_body_param_lead_num(iseq) }.try_into().expect("iseq param lead_num >= 0"); + let params = unsafe { iseq.params() }; + let param_size = params.size.to_usize(); + let opt_num: usize = params.opt_num.try_into().expect("iseq param opt_num >= 0"); + let lead_num: usize = params.lead_num.try_into().expect("iseq param lead_num >= 0"); let passed_opt_num = jit_entry_idx; let self_param = fun.push_insn(jit_entry_block, Insn::Param); From 9764306c48619e2d168235a4864c43f9e0db78e0 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Wed, 28 May 2025 13:26:10 -0700 Subject: [PATCH 03/10] Accurate GC.stat under multi-Ractor mode --- gc.c | 2 +- gc/default/default.c | 35 ++++++++++++++++++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/gc.c b/gc.c index c9fa18f42b8e8e..26afb4e71817bc 100644 --- a/gc.c +++ b/gc.c @@ -180,13 +180,13 @@ rb_gc_vm_barrier(void) rb_vm_barrier(); } -#if USE_MODULAR_GC void * rb_gc_get_ractor_newobj_cache(void) { return GET_RACTOR()->newobj_cache; } +#if USE_MODULAR_GC void rb_gc_initialize_vm_context(struct rb_gc_vm_context *context) { diff --git a/gc/default/default.c b/gc/default/default.c index 42561543d1a7c7..3d40b3dddfdbf8 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -2216,6 +2216,17 @@ rb_gc_impl_size_allocatable_p(size_t size) } static const size_t ALLOCATED_COUNT_STEP = 1024; +static void +ractor_cache_flush_count(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache) +{ + for (int heap_idx = 0; heap_idx < HEAP_COUNT; heap_idx++) { + rb_ractor_newobj_heap_cache_t *heap_cache = &cache->heap_caches[heap_idx]; + + rb_heap_t *heap = &heaps[heap_idx]; + RUBY_ATOMIC_SIZE_ADD(heap->total_allocated_objects, heap_cache->allocated_objects_count); + heap_cache->allocated_objects_count = 0; + } +} static inline VALUE ractor_cache_allocate_slot(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *cache, @@ -2240,19 +2251,11 @@ ractor_cache_allocate_slot(rb_objspace_t *objspace, rb_ractor_newobj_cache_t *ca rb_asan_unpoison_object(obj, true); heap_cache->freelist = p->next; - if (rb_gc_multi_ractor_p()) { - heap_cache->allocated_objects_count++; - rb_heap_t *heap = &heaps[heap_idx]; - if (heap_cache->allocated_objects_count >= ALLOCATED_COUNT_STEP) { - RUBY_ATOMIC_SIZE_ADD(heap->total_allocated_objects, heap_cache->allocated_objects_count); - heap_cache->allocated_objects_count = 0; - } - } - else { - rb_heap_t *heap = &heaps[heap_idx]; - heap->total_allocated_objects++; - GC_ASSERT(heap->total_slots >= - (heap->total_allocated_objects - heap->total_freed_objects - heap->final_slots_count)); + heap_cache->allocated_objects_count++; + rb_heap_t *heap = &heaps[heap_idx]; + if (heap_cache->allocated_objects_count >= ALLOCATED_COUNT_STEP) { + RUBY_ATOMIC_SIZE_ADD(heap->total_allocated_objects, heap_cache->allocated_objects_count); + heap_cache->allocated_objects_count = 0; } #if RGENGC_CHECK_MODE @@ -5172,6 +5175,8 @@ gc_verify_internal_consistency_(rb_objspace_t *objspace) /* check counters */ + ractor_cache_flush_count(objspace, rb_gc_get_ractor_newobj_cache()); + if (!is_lazy_sweeping(objspace) && !finalizing && !rb_gc_multi_ractor_p()) { @@ -7510,6 +7515,8 @@ rb_gc_impl_stat(void *objspace_ptr, VALUE hash_or_sym) setup_gc_stat_symbols(); + ractor_cache_flush_count(objspace, rb_gc_get_ractor_newobj_cache()); + if (RB_TYPE_P(hash_or_sym, T_HASH)) { hash = hash_or_sym; } @@ -7662,6 +7669,8 @@ rb_gc_impl_stat_heap(void *objspace_ptr, VALUE heap_name, VALUE hash_or_sym) { rb_objspace_t *objspace = objspace_ptr; + ractor_cache_flush_count(objspace, rb_gc_get_ractor_newobj_cache()); + setup_gc_stat_heap_symbols(); if (NIL_P(heap_name)) { From 9d04fb52aff73ba2b73753f6d172c2d21322a3bc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 21 Nov 2025 10:51:29 +0900 Subject: [PATCH 04/10] CI: cmake in scoop seems unused --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index f52c76c8eaa6c4..3dce96def3e92e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -81,7 +81,7 @@ jobs: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser iwr -useb get.scoop.sh | iex Join-Path (Resolve-Path ~).Path "scoop\shims" >> $Env:GITHUB_PATH - scoop install vcpkg uutils-coreutils cmake@3.31.6 + scoop install vcpkg uutils-coreutils shell: pwsh - name: Restore vcpkg artifact From a26f8235283b27dc1c5018addd0dfac209aaaa17 Mon Sep 17 00:00:00 2001 From: sue445 Date: Thu, 20 Nov 2025 22:34:01 +0900 Subject: [PATCH 05/10] [ruby/rubygems] Add go_gem/rake_task for Go native extention gem skeleton https://github.com/ruby/rubygems/commit/64f92d2da0 --- lib/bundler/templates/newgem/Rakefile.tt | 6 ++++++ spec/bundler/commands/newgem_spec.rb | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index 172183d4b410d8..dfb9edaa39a13f 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -59,6 +59,12 @@ Rake::ExtensionTask.new("<%= config[:underscored_name] %>", GEMSPEC) do |ext| end <% end -%> +<% if config[:ext] == "go" -%> +require "go_gem/rake_task" + +GoGem::RakeTask.new("<%= config[:underscored_name] %>") +<% end -%> + <% end -%> <% if default_task_names.size == 1 -%> task default: <%= default_task_names.first.inspect %> diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 7a837bd08f0112..1d158726bed6a0 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -1829,6 +1829,14 @@ def create_temporary_dir(dir) expect(bundled_app("#{gem_name}/ext/#{gem_name}/go.mod").read).to include("module github.com/bundleuser/#{gem_name}") end + it "includes go_gem extension in Rakefile" do + expect(bundled_app("#{gem_name}/Rakefile").read).to include(<<~RUBY) + require "go_gem/rake_task" + + GoGem::RakeTask.new("#{gem_name}") + RUBY + end + context "with --no-ci" do let(:flags) { "--ext=go --no-ci" } From 9aa09b4620fd633ef2ffaff4bdae5e344b173ee6 Mon Sep 17 00:00:00 2001 From: sue445 Date: Fri, 21 Nov 2025 09:50:27 +0900 Subject: [PATCH 06/10] [ruby/rubygems] Fixed RuboCop offense in Rakefile generated by `bundle gem` ``` Offenses: Rakefile:18:1: C: [Correctable] Layout/EmptyLines: Extra blank line detected. Diff: @@ -11,4 +11,5 @@ ext.lib_dir = "lib/test_gem" end + task default: :compile https://github.com/ruby/rubygems/commit/8c414729df --- lib/bundler/templates/newgem/Rakefile.tt | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index dfb9edaa39a13f..83f10009c7042e 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -64,7 +64,6 @@ require "go_gem/rake_task" GoGem::RakeTask.new("<%= config[:underscored_name] %>") <% end -%> - <% end -%> <% if default_task_names.size == 1 -%> task default: <%= default_task_names.first.inspect %> From 8b116ee8b982f838d8464b7ee310f37bc3282efb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 16 Nov 2025 12:28:31 -0800 Subject: [PATCH 07/10] [ruby/rubygems] create a gem version instead of comparing with a string https://github.com/ruby/rubygems/commit/c1e3d4d63b --- lib/rubygems.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index c398c985f58556..8530a2a893cb5d 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -287,7 +287,7 @@ def self.activate_and_load_bin_path(name, exec_name = nil, *requirements) # RubyGems now uses this new `Gem.activate_and_load_bin_path` helper in # binstubs, which is of course not overridden in Bundler since it didn't # exist at the time. So, include the override here to workaround that. - load ENV["BUNDLE_BIN_PATH"] if ENV["BUNDLE_BIN_PATH"] && spec.version <= "2.5.22" + load ENV["BUNDLE_BIN_PATH"] if ENV["BUNDLE_BIN_PATH"] && spec.version <= Gem::Version.create("2.5.22") # Make sure there's no version of Bundler in `$LOAD_PATH` that's different # from the version we just activated. If that was the case (it happens @@ -666,7 +666,7 @@ def self.load_safe_marshal # warnings in platform constants def self.load_bundler_extensions(version) - return unless version <= "2.6.9" + return unless version <= Gem::Version.create("2.6.9") previous_platforms = {} From 917e77be5e5bf13b22009bec5568aa031138a605 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Nov 2025 17:26:40 -0800 Subject: [PATCH 08/10] [ruby/rubygems] Deprecate comparing Gem::Version objects with strings Comparing version objects is a huge bottleneck in dependency solvers (like inside Bundler). I would like to make comparing version objects cheaper. Right now we support comparing version objects with strings by trying to coerce the string to a version. So for example: ```ruby Gem::Version.new("1") <=> "12" ``` I would like to deprecate and remove support for this feature so that we can reduce the overhead of `def <=>`. I'm not sure what version of RubyGems we could remove this from though. https://github.com/ruby/rubygems/commit/81b7602183 --- lib/rubygems/version.rb | 12 +++++++++--- test/rubygems/test_gem_version.rb | 19 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index c9fffc1cb7ca93..43a0e4e78375d5 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -339,11 +339,17 @@ def approximate_recommendation ## # Compares this version with +other+ returning -1, 0, or 1 if the # other version is larger, the same, or smaller than this - # one. Attempts to compare to something that's not a - # Gem::Version or a valid version String return +nil+. + # one. +other+ must be an instance of Gem::Version, comparing with + # other types may raise an exception. def <=>(other) - return self <=> self.class.new(other) if (String === other) && self.class.correct?(other) + if String === other + unless Gem::Deprecate.skip + warn "comparing version objects with strings is deprecated and will be removed" + end + return unless self.class.correct?(other) + return self <=> self.class.new(other) + end return unless Gem::Version === other return 0 if @version == other.version || canonical_segments == other.canonical_segments diff --git a/test/rubygems/test_gem_version.rb b/test/rubygems/test_gem_version.rb index 1d963daa65bac6..ad2a11c631803f 100644 --- a/test/rubygems/test_gem_version.rb +++ b/test/rubygems/test_gem_version.rb @@ -154,11 +154,20 @@ def test_spaceship assert_equal(-1, v("5.a") <=> v("5.0.0.rc2")) assert_equal(1, v("5.x") <=> v("5.0.0.rc2")) - assert_equal(0, v("1.9.3") <=> "1.9.3") - assert_equal(1, v("1.9.3") <=> "1.9.2.99") - assert_equal(-1, v("1.9.3") <=> "1.9.3.1") - - assert_nil v("1.0") <=> "whatever" + [ + [0, "1.9.3"], + [1, "1.9.2.99"], + [-1, "1.9.3.1"], + [nil, "whatever"], + ].each do |cmp, string_ver| + expected = "comparing version objects with strings is deprecated and will be removed\n" + + actual_stdout, actual_stderr = capture_output do + assert_equal(cmp, v("1.9.3") <=> string_ver) + end + assert_empty actual_stdout + assert_equal expected, actual_stderr + end end def test_approximate_recommendation From ee002a5ee061e82da639e499a36810a747998747 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 21 Nov 2025 11:22:01 +0900 Subject: [PATCH 09/10] [ruby/rubygems] Respect `BUNDLE_VERSION` config at Gem::BundlerVersionFinder If we use "system" variable in BUNDLE_VERSION on Bundler configuration, we can use bundler version provided by system installation. But the current logic returns the first activated version of bundler like 2.7.2. It makes to confuse users. https://github.com/ruby/rubygems/commit/4eb66d9549 --- lib/rubygems/bundler_version_finder.rb | 31 ++++++++ test/rubygems/helper.rb | 3 + .../test_gem_bundler_version_finder.rb | 70 +++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index 602e00c1d866fb..c930c2e19c264f 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -2,6 +2,8 @@ module Gem::BundlerVersionFinder def self.bundler_version + return if bundle_config_version == "system" + v = ENV["BUNDLER_VERSION"] v = nil if v&.empty? @@ -78,4 +80,33 @@ def self.lockfile_contents File.read(lockfile) end private_class_method :lockfile_contents + + def self.bundle_config_version + config_file = bundler_config_file + return unless config_file && File.file?(config_file) + + contents = File.read(config_file) + contents =~ /^BUNDLE_VERSION:\s*["']?([^"'\s]+)["']?\s*$/ + + $1 + end + private_class_method :bundle_config_version + + def self.bundler_config_file + # see Bundler::Settings#global_config_file and local_config_file + # global + if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty? + ENV["BUNDLE_CONFIG"] + elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty? + ENV["BUNDLE_USER_CONFIG"] + elsif ENV["BUNDLE_USER_HOME"] && !ENV["BUNDLE_USER_HOME"].empty? + ENV["BUNDLE_USER_HOME"] + "config" + elsif Gem.user_home && !Gem.user_home.empty? + Gem.user_home + ".bundle/config" + else + # local + "config" + end + end + private_class_method :bundler_config_file end diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb index 53bed0b4151b97..6e0be10ef53044 100644 --- a/test/rubygems/helper.rb +++ b/test/rubygems/helper.rb @@ -335,6 +335,9 @@ def setup ENV["XDG_STATE_HOME"] = nil ENV["SOURCE_DATE_EPOCH"] = nil ENV["BUNDLER_VERSION"] = nil + ENV["BUNDLE_CONFIG"] = nil + ENV["BUNDLE_USER_CONFIG"] = nil + ENV["BUNDLE_USER_HOME"] = nil ENV["RUBYGEMS_PREVENT_UPDATE_SUGGESTION"] = "true" @current_dir = Dir.pwd diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb index 908f9278323c09..a773d6249b5967 100644 --- a/test/rubygems/test_gem_bundler_version_finder.rb +++ b/test/rubygems/test_gem_bundler_version_finder.rb @@ -2,6 +2,7 @@ require_relative "helper" require "rubygems/bundler_version_finder" +require "tempfile" class TestGemBundlerVersionFinder < Gem::TestCase def setup @@ -56,6 +57,75 @@ def test_bundler_version_with_bundle_update_bundler assert_nil bvf.bundler_version end + def test_bundler_version_with_bundle_config + config_content = <<~CONFIG + BUNDLE_VERSION: "system" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_config_file, f.path) do + assert_nil bvf.bundler_version + end + end + end + + def test_bundler_version_with_bundle_config_single_quoted + config_with_single_quoted_version = <<~CONFIG + BUNDLE_VERSION: 'system' + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_with_single_quoted_version) + f.flush + + bvf.stub(:bundler_config_file, f.path) do + assert_nil bvf.bundler_version + end + end + end + + def test_bundler_version_with_bundle_config_version + ENV["BUNDLER_VERSION"] = "1.1.1.1" + + config_content = <<~CONFIG + BUNDLE_VERSION: "1.2.3" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_config_file, f.path) do + assert_equal v("1.1.1.1"), bvf.bundler_version + end + end + end + + def test_bundler_version_with_bundle_config_non_existent_file + bvf.stub(:bundler_config_file, "/non/existent/path") do + assert_nil bvf.bundler_version + end + end + + def test_bundler_version_with_bundle_config_without_version + config_without_version = <<~CONFIG + BUNDLE_JOBS: "8" + BUNDLE_GEM__TEST: "minitest" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_without_version) + f.flush + + bvf.stub(:bundler_config_file, f.path) do + assert_nil bvf.bundler_version + end + end + end + def test_bundler_version_with_lockfile bvf.stub(:lockfile_contents, "") do assert_nil bvf.bundler_version From 451c12099472764f1190df8db451dbeaedd1b1c8 Mon Sep 17 00:00:00 2001 From: Philip Arndt Date: Fri, 21 Nov 2025 17:33:57 +1300 Subject: [PATCH 10/10] [ruby/rubygems] Check for file existence before deletion from cache (https://github.com/ruby/rubygems/pull/9095) * Rescue when deleting a non-existent cached gem file When a gem was in the cache, but another process deletes it first, this delete command fails. To work around this, I'm rescuing from Errno::ENOENT and swalling the error. The file is gone, and we can move on. * Apply suggestion from @kou Co-authored-by: Sutou Kouhei --------- https://github.com/ruby/rubygems/commit/b30bcbc648 Co-authored-by: Hiroshi SHIBATA Co-authored-by: Sutou Kouhei --- lib/bundler/runtime.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 9b2416402bb74c..5eb827dcb2a8ed 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -240,7 +240,11 @@ def prune_gem_cache(resolve, cache_path) cached.each do |path| Bundler.ui.info " * #{File.basename(path)}" - File.delete(path) + + begin + File.delete(path) + rescue Errno::ENOENT + end end end end