use super::StableApiDefinition;
use crate::{
debug_ruby_assert_type,
internal::{RArray, RString, RTypedData},
ruby_value_type::RUBY_T_DATA,
value_type, ID, VALUE,
};
use std::{
ffi::c_void,
os::raw::{c_char, c_long},
ptr::NonNull,
time::Duration,
};
#[cfg(not(ruby_eq_4_0))]
compile_error!("This file should only be included in Ruby 4.0 builds");
pub struct Definition;
impl StableApiDefinition for Definition {
const VERSION_MAJOR: u32 = 4;
const VERSION_MINOR: u32 = 0;
#[inline(always)]
unsafe fn rstring_len(&self, obj: VALUE) -> c_long {
debug_ruby_assert_type!(
obj,
crate::ruby_value_type::RUBY_T_STRING,
"rstring_len called on non-T_STRING object"
);
let rstring: &RString = &*(obj as *const RString);
rstring.len
}
#[inline(always)]
unsafe fn rstring_ptr(&self, obj: VALUE) -> *const c_char {
debug_ruby_assert_type!(
obj,
crate::ruby_value_type::RUBY_T_STRING,
"rstring_ptr called on non-T_STRING object"
);
let rstring: &RString = &*(obj as *const RString);
let flags = rstring.basic.flags;
let is_heap = (flags & crate::ruby_rstring_flags::RSTRING_NOEMBED as VALUE) != 0;
if !is_heap {
std::ptr::addr_of!(rstring.as_.embed.ary) as *const _
} else {
rstring.as_.heap.ptr
}
}
#[inline(always)]
unsafe fn rarray_len(&self, obj: VALUE) -> c_long {
debug_ruby_assert_type!(
obj,
value_type::RUBY_T_ARRAY,
"rarray_len called on non-T_ARRAY object"
);
let rarray: &RArray = &*(obj as *const RArray);
let flags = rarray.basic.flags;
let is_embedded = (flags & crate::ruby_rarray_flags::RARRAY_EMBED_FLAG as VALUE) != 0;
if is_embedded {
let mut f = rarray.basic.flags;
f &= crate::ruby_rarray_flags::RARRAY_EMBED_LEN_MASK as VALUE;
f >>= crate::ruby_rarray_consts::RARRAY_EMBED_LEN_SHIFT as VALUE;
f as c_long
} else {
rarray.as_.heap.len
}
}
#[inline(always)]
unsafe fn rarray_const_ptr(&self, obj: VALUE) -> *const VALUE {
debug_ruby_assert_type!(
obj,
value_type::RUBY_T_ARRAY,
"rarray_const_ptr called on non-T_ARRAY object"
);
let rarray: &RArray = &*(obj as *const RArray);
let flags = rarray.basic.flags;
let is_embedded = (flags & crate::ruby_rarray_flags::RARRAY_EMBED_FLAG as VALUE) != 0;
if is_embedded {
std::ptr::addr_of!(rarray.as_.ary) as *const _
} else {
rarray.as_.heap.ptr
}
}
#[inline(always)]
unsafe fn rarray_aref(&self, obj: VALUE, idx: isize) -> VALUE {
*self.rarray_const_ptr(obj).offset(idx)
}
#[inline(always)]
unsafe fn rarray_aset(&self, obj: VALUE, idx: isize, val: VALUE) {
let ptr = self.rarray_const_ptr(obj).cast_mut().offset(idx);
self.rb_obj_write(obj, ptr, val);
}
#[inline(always)]
unsafe fn rbasic_class(&self, obj: VALUE) -> Option<NonNull<VALUE>> {
let rbasic = obj as *const crate::RBasic;
NonNull::<VALUE>::new((*rbasic).klass as _)
}
#[inline(always)]
unsafe fn frozen_p(&self, obj: VALUE) -> bool {
if self.special_const_p(obj) {
true
} else {
let rbasic = obj as *const crate::RBasic;
((*rbasic).flags & crate::ruby_fl_type::RUBY_FL_FREEZE as VALUE) != 0
}
}
#[inline(always)]
fn special_const_p(&self, value: VALUE) -> bool {
self.immediate_p(value) || !self.rb_test(value)
}
#[inline(always)]
unsafe fn bignum_positive_p(&self, obj: VALUE) -> bool {
let rbasic = obj as *const crate::RBasic;
((*rbasic).flags & crate::ruby_fl_type::RUBY_FL_USER1 as VALUE) != 0
}
#[inline(always)]
unsafe fn builtin_type(&self, obj: VALUE) -> crate::ruby_value_type {
let rbasic = obj as *const crate::RBasic;
let ret: u32 = ((*rbasic).flags & crate::ruby_value_type::RUBY_T_MASK as VALUE) as _;
std::mem::transmute::<_, crate::ruby_value_type>(ret)
}
#[inline(always)]
fn nil_p(&self, obj: VALUE) -> bool {
obj == (crate::Qnil as VALUE)
}
#[inline(always)]
fn fixnum_p(&self, obj: VALUE) -> bool {
(obj & crate::FIXNUM_FLAG as VALUE) != 0
}
#[inline(always)]
fn static_sym_p(&self, obj: VALUE) -> bool {
const SPECIAL_MASK: VALUE =
!(VALUE::MAX << crate::ruby_special_consts::RUBY_SPECIAL_SHIFT as VALUE);
const SYMBOL_FLAG: VALUE = crate::ruby_special_consts::RUBY_SYMBOL_FLAG as VALUE;
(obj & SPECIAL_MASK) == SYMBOL_FLAG
}
#[inline(always)]
fn flonum_p(&self, obj: VALUE) -> bool {
#[cfg(ruby_use_flonum = "true")]
let ret = (obj & crate::FLONUM_MASK as VALUE) == crate::FLONUM_FLAG as VALUE;
#[cfg(not(ruby_use_flonum = "true"))]
let ret = false;
ret
}
#[inline(always)]
fn immediate_p(&self, obj: VALUE) -> bool {
(obj & crate::special_consts::IMMEDIATE_MASK as VALUE) != 0
}
#[inline(always)]
fn rb_test(&self, obj: VALUE) -> bool {
(obj & !(crate::Qnil as VALUE)) != 0
}
#[inline(always)]
unsafe fn type_p(&self, obj: VALUE, ty: crate::ruby_value_type) -> bool {
use crate::ruby_special_consts::*;
use crate::ruby_value_type::*;
if !self.special_const_p(obj) {
self.builtin_type(obj) == ty
} else if obj == RUBY_Qfalse as _ {
ty == RUBY_T_FALSE
} else if obj == RUBY_Qnil as _ {
ty == RUBY_T_NIL
} else if obj == RUBY_Qtrue as _ {
ty == RUBY_T_TRUE
} else if obj == RUBY_Qundef as _ {
ty == RUBY_T_UNDEF
} else if self.fixnum_p(obj) {
ty == RUBY_T_FIXNUM
} else if self.static_sym_p(obj) {
ty == RUBY_T_SYMBOL
} else if self.flonum_p(obj) {
ty == RUBY_T_FLOAT
} else {
ty == self.builtin_type(obj)
}
}
#[inline(always)]
unsafe fn symbol_p(&self, obj: VALUE) -> bool {
if !self.special_const_p(obj) {
self.builtin_type(obj) == value_type::RUBY_T_SYMBOL
} else {
self.static_sym_p(obj)
}
}
#[inline(always)]
unsafe fn float_type_p(&self, obj: VALUE) -> bool {
if !self.special_const_p(obj) {
self.builtin_type(obj) == value_type::RUBY_T_FLOAT
} else {
self.flonum_p(obj)
}
}
#[inline(always)]
unsafe fn rb_type(&self, obj: VALUE) -> crate::ruby_value_type {
use crate::ruby_special_consts::*;
use crate::ruby_value_type::*;
if !self.special_const_p(obj) {
self.builtin_type(obj)
} else if obj == RUBY_Qfalse as _ {
RUBY_T_FALSE
} else if obj == RUBY_Qnil as _ {
RUBY_T_NIL
} else if obj == RUBY_Qtrue as _ {
RUBY_T_TRUE
} else if obj == RUBY_Qundef as _ {
RUBY_T_UNDEF
} else if self.fixnum_p(obj) {
RUBY_T_FIXNUM
} else if self.static_sym_p(obj) {
RUBY_T_SYMBOL
} else {
debug_assert!(self.flonum_p(obj));
RUBY_T_FLOAT
}
}
#[inline(always)]
unsafe fn dynamic_sym_p(&self, obj: VALUE) -> bool {
!self.special_const_p(obj) && self.builtin_type(obj) == value_type::RUBY_T_SYMBOL
}
#[inline(always)]
unsafe fn integer_type_p(&self, obj: VALUE) -> bool {
if !self.special_const_p(obj) {
self.builtin_type(obj) == value_type::RUBY_T_BIGNUM
} else {
self.fixnum_p(obj)
}
}
#[inline(always)]
unsafe fn rstring_interned_p(&self, obj: VALUE) -> bool {
debug_ruby_assert_type!(
obj,
value_type::RUBY_T_STRING,
"rstring_interned_p called on non-T_STRING object"
);
let rstring: &RString = &*(obj as *const RString);
let flags = rstring.basic.flags;
(flags & crate::ruby_rstring_flags::RSTRING_FSTR as VALUE) != 0
}
#[inline(always)]
fn thread_sleep(&self, duration: Duration) {
let seconds = duration.as_secs() as _;
let microseconds = duration.subsec_micros() as _;
let time = crate::timeval {
tv_sec: seconds,
tv_usec: microseconds,
};
unsafe { crate::rb_thread_wait_for(time) }
}
#[inline(always)]
unsafe fn rtypeddata_p(&self, obj: VALUE) -> bool {
debug_ruby_assert_type!(obj, RUBY_T_DATA, "rtypeddata_p called on non-T_DATA object");
let rbasic = obj as *const crate::RBasic;
((*rbasic).flags & crate::ruby_fl_type::RUBY_FL_USERPRIV0 as VALUE) != 0
}
#[inline(always)]
unsafe fn rtypeddata_type(&self, obj: VALUE) -> *const crate::rb_data_type_t {
debug_ruby_assert_type!(
obj,
RUBY_T_DATA,
"rtypeddata_type called on non-T_DATA object"
);
let rdata = obj as *const RTypedData;
((*rdata).type_ & !1) as *const crate::rb_data_type_t
}
#[inline(always)]
unsafe fn rtypeddata_get_data(&self, obj: VALUE) -> *mut c_void {
debug_ruby_assert_type!(
obj,
RUBY_T_DATA,
"rtypeddata_get_data called on non-T_DATA object"
);
let rdata = obj as *const RTypedData;
if ((*rdata).type_ & 1) != 0 {
const EMBEDDED_TYPED_DATA_SIZE: usize =
std::mem::size_of::<RTypedData>() - std::mem::size_of::<*mut c_void>();
(obj as *mut u8).add(EMBEDDED_TYPED_DATA_SIZE) as *mut c_void
} else {
let rdata = obj as *const RTypedData;
(*rdata).data
}
}
#[inline]
fn fix2long(&self, obj: VALUE) -> std::os::raw::c_long {
(obj as std::os::raw::c_long) >> 1
}
#[inline]
fn fix2ulong(&self, obj: VALUE) -> std::os::raw::c_ulong {
((obj as std::os::raw::c_long) >> 1) as std::os::raw::c_ulong
}
#[inline]
fn long2fix(&self, val: std::os::raw::c_long) -> VALUE {
(((val as VALUE) << 1) | crate::FIXNUM_FLAG as VALUE) as VALUE
}
#[inline]
fn fixable(&self, val: std::os::raw::c_long) -> bool {
(crate::special_consts::FIXNUM_MIN..=crate::special_consts::FIXNUM_MAX).contains(&val)
}
#[inline]
fn posfixable(&self, val: std::os::raw::c_ulong) -> bool {
val <= crate::special_consts::FIXNUM_MAX as std::os::raw::c_ulong
}
#[inline]
unsafe fn num2long(&self, obj: VALUE) -> std::os::raw::c_long {
if self.fixnum_p(obj) {
self.fix2long(obj)
} else {
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
if self.type_p(obj, crate::ruby_value_type::RUBY_T_BIGNUM) {
if let Some(v) = bignum_to_long_fast(obj) {
return v;
}
}
crate::rb_num2long(obj)
}
}
#[inline]
unsafe fn num2ulong(&self, obj: VALUE) -> std::os::raw::c_ulong {
if self.fixnum_p(obj) {
self.fix2ulong(obj)
} else {
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
if self.type_p(obj, crate::ruby_value_type::RUBY_T_BIGNUM) {
if let Some(v) = bignum_to_ulong_fast(obj) {
return v;
}
}
crate::rb_num2ulong(obj)
}
}
#[inline]
fn long2num(&self, val: std::os::raw::c_long) -> VALUE {
if self.fixable(val) {
self.long2fix(val)
} else {
unsafe { crate::rb_int2big(val as isize) }
}
}
#[inline]
fn ulong2num(&self, val: std::os::raw::c_ulong) -> VALUE {
if self.posfixable(val) {
self.long2fix(val as std::os::raw::c_long)
} else {
unsafe { crate::rb_uint2big(val as usize) }
}
}
#[inline(always)]
fn id2sym(&self, id: ID) -> VALUE {
((id as VALUE) << crate::ruby_special_consts::RUBY_SPECIAL_SHIFT as VALUE)
| crate::ruby_special_consts::RUBY_SYMBOL_FLAG as VALUE
}
#[inline(always)]
unsafe fn sym2id(&self, obj: VALUE) -> ID {
if self.static_sym_p(obj) {
(obj >> crate::ruby_special_consts::RUBY_SPECIAL_SHIFT as VALUE) as ID
} else {
crate::rb_sym2id(obj)
}
}
#[inline(always)]
unsafe fn rb_obj_write(&self, old: VALUE, slot: *mut VALUE, young: VALUE) -> VALUE {
*slot = young;
self.rb_obj_written(old, crate::Qundef as VALUE, young)
}
#[inline(always)]
unsafe fn rb_obj_written(&self, old: VALUE, _oldv: VALUE, young: VALUE) -> VALUE {
if !self.special_const_p(young) {
crate::rb_gc_writebarrier(old, young);
}
old
}
#[inline]
fn fl_able(&self, obj: VALUE) -> bool {
!self.special_const_p(obj)
}
#[inline(always)]
unsafe fn rstring_end(&self, obj: VALUE) -> *const c_char {
assert!(self.type_p(obj, crate::ruby_value_type::RUBY_T_STRING));
let ptr = self.rstring_ptr(obj);
let len = self.rstring_len(obj);
ptr.add(len as usize)
}
#[inline(always)]
unsafe fn rdata_ptr(&self, obj: VALUE) -> *mut c_void {
assert!(self.type_p(obj, RUBY_T_DATA));
let rdata = obj as *const RTypedData;
(*rdata).data
}
#[inline(always)]
unsafe fn rb_obj_freeze(&self, obj: VALUE) {
crate::rb_obj_freeze(obj);
}
#[inline(always)]
unsafe fn rb_obj_promoted(&self, obj: VALUE) -> bool {
if self.special_const_p(obj) {
false
} else {
self.rb_obj_promoted_raw(obj)
}
}
#[inline(always)]
unsafe fn rb_obj_promoted_raw(&self, obj: VALUE) -> bool {
let rbasic = obj as *const crate::RBasic;
((*rbasic).flags & crate::ruby_fl_type::RUBY_FL_PROMOTED as VALUE) != 0
}
#[inline(always)]
unsafe fn num2dbl(&self, obj: VALUE) -> std::os::raw::c_double {
if self.flonum_p(obj) {
#[cfg(ruby_use_flonum = "true")]
{
if obj != 0x8000000000000002 {
let b63 = obj >> 63;
let adjusted = ((2 - b63) | (obj & !0x03)) as u64;
let rotated = adjusted.rotate_right(3);
f64::from_bits(rotated)
} else {
0.0
}
}
#[cfg(not(ruby_use_flonum = "true"))]
{
crate::rb_num2dbl(obj)
}
} else if self.fixnum_p(obj) {
((obj as c_long) >> 1) as std::os::raw::c_double
} else if !self.special_const_p(obj)
&& self.builtin_type(obj) == crate::ruby_value_type::RUBY_T_FLOAT
{
#[cfg(not(target_pointer_width = "32"))]
{
let float_val_ptr =
(obj as *const crate::VALUE).add(2) as *const std::os::raw::c_double;
*float_val_ptr
}
#[cfg(target_pointer_width = "32")]
{
crate::rb_num2dbl(obj)
}
} else {
crate::rb_num2dbl(obj)
}
}
#[inline(always)]
fn dbl2num(&self, val: std::os::raw::c_double) -> VALUE {
#[cfg(ruby_use_flonum = "true")]
{
let bits = val.to_bits() as VALUE;
let exp_bits = (bits >> 60) & 0x7;
if bits != 0x3000_0000_0000_0000 && (exp_bits == 3 || exp_bits == 4) {
return (bits.rotate_left(3) & !0x01) | 0x02;
}
if bits == 0 {
return 0x8000_0000_0000_0002;
}
}
unsafe { crate::rb_float_new(val) }
}
#[inline(always)]
unsafe fn rhash_size(&self, obj: VALUE) -> usize {
#[repr(C)]
struct RHash33 {
basic: crate::RBasic,
ifnone: VALUE,
}
let rbasic = obj as *const crate::RBasic;
let flags = (*rbasic).flags;
let st_flag = crate::ruby_fl_type::RUBY_FL_USER3 as VALUE;
if (flags & st_flag) == 0 {
let mask: VALUE = (crate::ruby_fl_type::RUBY_FL_USER4 as VALUE)
| (crate::ruby_fl_type::RUBY_FL_USER5 as VALUE)
| (crate::ruby_fl_type::RUBY_FL_USER6 as VALUE)
| (crate::ruby_fl_type::RUBY_FL_USER7 as VALUE);
let shift = 16u32;
((flags & mask) >> shift) as usize
} else {
let st_ptr = (obj as usize + core::mem::size_of::<RHash33>()) as *const crate::st_table;
(*st_ptr).num_entries as usize
}
}
#[inline(always)]
unsafe fn rhash_empty_p(&self, obj: VALUE) -> bool {
self.rhash_size(obj) == 0
}
#[inline(always)]
unsafe fn encoding_get(&self, obj: VALUE) -> std::os::raw::c_int {
let rbasic = obj as *const crate::RBasic;
let flags = (*rbasic).flags;
let shift = crate::ruby_encoding_consts::RUBY_ENCODING_SHIFT as u32;
let inline_max =
crate::ruby_encoding_consts::RUBY_ENCODING_INLINE_MAX as std::os::raw::c_int;
let mask = crate::ruby_encoding_consts::RUBY_ENCODING_MASK as VALUE;
let inline_idx = ((flags & mask) >> shift) as std::os::raw::c_int;
if inline_idx == inline_max {
crate::rb_enc_get_index(obj)
} else {
inline_idx
}
}
}
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
#[repr(C)]
struct RBignum {
basic: crate::RBasic,
as_: RBignumAs,
}
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
#[repr(C)]
union RBignumAs {
heap: RBignumHeap,
ary: [u32; 2],
}
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
#[repr(C)]
#[derive(Copy, Clone)]
struct RBignumHeap {
len: usize,
digits: *const u32,
}
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
#[inline]
unsafe fn bignum_to_long_fast(obj: VALUE) -> Option<std::os::raw::c_long> {
let rb = obj as *const RBignum;
let flags = (*rb).basic.flags;
let embed_flag: VALUE = 16384;
let embed_len_mask: VALUE = 229376;
let embed_len_shift: u32 = 15;
let sign_flag: VALUE = 8192;
let positive = (flags & sign_flag) != 0;
let (len, digits_ptr) = if (flags & embed_flag) != 0 {
let len = ((flags & embed_len_mask) >> embed_len_shift) as usize;
let digits = (*rb).as_.ary.as_ptr();
(len, digits)
} else {
let len = (*rb).as_.heap.len;
let digits = (*rb).as_.heap.digits;
(len, digits)
};
match len {
0 => Some(0),
1 => {
let d0 = *digits_ptr as u64;
if positive {
Some(d0 as std::os::raw::c_long)
} else {
Some(-(d0 as i64) as std::os::raw::c_long)
}
}
2 => {
let lo = *digits_ptr as u64;
let hi = *digits_ptr.add(1) as u64;
let val = lo | (hi << 32);
if positive {
if val > i64::MAX as u64 {
return None; }
Some(val as std::os::raw::c_long)
} else {
if val > (i64::MAX as u64) + 1 {
return None; }
Some((val as i64).wrapping_neg() as std::os::raw::c_long)
}
}
_ => None, }
}
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
#[inline]
unsafe fn bignum_to_ulong_fast(obj: VALUE) -> Option<std::os::raw::c_ulong> {
let rb = obj as *const RBignum;
let flags = (*rb).basic.flags;
let embed_flag: VALUE = 16384;
let embed_len_mask: VALUE = 229376;
let embed_len_shift: u32 = 15;
let sign_flag: VALUE = 8192;
let positive = (flags & sign_flag) != 0;
if !positive {
return None; }
let (len, digits_ptr) = if (flags & embed_flag) != 0 {
let len = ((flags & embed_len_mask) >> embed_len_shift) as usize;
let digits = (*rb).as_.ary.as_ptr();
(len, digits)
} else {
let len = (*rb).as_.heap.len;
let digits = (*rb).as_.heap.digits;
(len, digits)
};
match len {
0 => Some(0),
1 => {
let d0 = *digits_ptr as u64;
Some(d0 as std::os::raw::c_ulong)
}
2 => {
let lo = *digits_ptr as u64;
let hi = *digits_ptr.add(1) as u64;
Some((lo | (hi << 32)) as std::os::raw::c_ulong)
}
_ => None, }
}