Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Implementing RStruct methods for StableApiDefinition in order to support TruffleRuby #467

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions crates/rb-sys-tests/src/stable_api_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,3 +655,159 @@ parity_test!(
std::time::Duration::from_millis(100)
}
);

parity_test!(
name: test_rb_rstruct_len_embedded,
func: rstruct_len,
data_factory: {
ruby_eval!("Person = Struct.new(:name, :age); Person.new('Matz', 59)")
},
expected: 2
);

parity_test!(
name: test_rb_rstruct_len_heap,
func: rstruct_len,
data_factory: {
ruby_eval!("
field_names = (0..80).map { |i| \"field#{i}\" };
LargeStruct = Struct.new(*field_names.map(&:to_sym)) do
def initialize(*)
super
# Initialize all nil fields with 0 for demonstration
members.each do |field|
self[field] = 0 if self[field].nil?
end
end
end;

LargeStruct.new
")
},
expected: 81
);

#[rb_sys_test_helpers::ruby_test]
fn test_rb_rstruct_get_embedded() {
use rb_sys::stable_api;
let data = {
ruby_eval!(
"
Person = Struct.new(:name, :age);
Person.new('Matz', 59);
"
)
};
assert_ne!(stable_api::get_default().version(), (0, 0));
#[allow(unused)]
let rust_result = unsafe { stable_api::get_default().rstruct_get(data, 0) };
#[allow(unused_unsafe)]
let compiled_c_result = unsafe { stable_api::get_compiled().rstruct_get(data, 0) };
assert_eq!(
compiled_c_result, rust_result,
"compiled_c was {:?}, rust was {:?}",
compiled_c_result, rust_result
);
}

#[rb_sys_test_helpers::ruby_test]
fn test_rb_rstruct_get_heap() {
use rb_sys::stable_api;
let data = {
ruby_eval!(
"
field_names = (0..80).map { |i| \"field#{i}\" };
LargeStruct = Struct.new(*field_names.map(&:to_sym)) do
def initialize(*)
super
members.each do |field|
self[field] = 0 if self[field].nil?
end
end
end;

st = LargeStruct.new;
st[:field79] = true;
st
"
)
};
assert_ne!(stable_api::get_default().version(), (0, 0));
#[allow(unused)]
let rust_result = unsafe { stable_api::get_default().rstruct_get(data, 79) };
#[allow(unused_unsafe)]
let compiled_c_result = unsafe { stable_api::get_compiled().rstruct_get(data, 79) };
assert_eq!(
compiled_c_result, rust_result,
"compiled_c was {:?}, rust was {:?}",
compiled_c_result, rust_result
);
}

#[rb_sys_test_helpers::ruby_test]
fn test_rb_rstruct_set_embedded() {
use rb_sys::stable_api;
let data = {
ruby_eval!(
"
Person = Struct.new(:name, :age);
Person.new('Matz', 59);
"
)
};
assert_ne!(stable_api::get_default().version(), (0, 0));
#[allow(unused)]
let rust_result = unsafe {
stable_api::get_default().rstruct_set(data, 0, rb_sys::Qfalse as VALUE);
stable_api::get_default().rstruct_get(data, 0)
};
#[allow(unused_unsafe)]
let compiled_c_result = unsafe {
stable_api::get_compiled().rstruct_set(data, 0, rb_sys::Qfalse as VALUE);
stable_api::get_compiled().rstruct_get(data, 0)
};
assert_eq!(
compiled_c_result, rust_result,
"compiled_c was {:?}, rust was {:?}",
compiled_c_result, rust_result
);
}

#[rb_sys_test_helpers::ruby_test]
fn test_rb_rstruct_set_heap() {
use rb_sys::stable_api;
let data = {
ruby_eval!(
"
field_names = (0..80).map { |i| \"field#{i}\" };
LargeStruct = Struct.new(*field_names.map(&:to_sym)) do
def initialize(*)
super
members.each do |field|
self[field] = 0 if self[field].nil?
end
end
end;

st = LargeStruct.new;
st
"
)
};
assert_ne!(stable_api::get_default().version(), (0, 0));
#[allow(unused)]
let rust_result = unsafe {
stable_api::get_default().rstruct_set(data, 79, rb_sys::Qfalse as VALUE);
stable_api::get_default().rstruct_get(data, 79)
};
#[allow(unused_unsafe)]
let compiled_c_result = unsafe {
stable_api::get_compiled().rstruct_set(data, 79, rb_sys::Qfalse as VALUE);
stable_api::get_compiled().rstruct_get(data, 79)
};
assert_eq!(
compiled_c_result, rust_result,
"compiled_c was {:?}, rust was {:?}",
compiled_c_result, rust_result
);
}
31 changes: 31 additions & 0 deletions crates/rb-sys/src/stable_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use crate::VALUE;
use std::{
ffi::{c_int, CStr},
os::raw::{c_char, c_long},
ptr::NonNull,
time::Duration,
Expand Down Expand Up @@ -175,6 +176,36 @@ pub trait StableApiDefinition {

/// Blocks the current thread until the given duration has passed.
fn thread_sleep(&self, duration: Duration);

/// Defines a struct type with the given name and members.
///
/// # Example
///
fn rstruct_define(&self, name: &CStr, members: &[&CStr]) -> VALUE;

/// Accesses an indexed member of the struct.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer to get
/// access to underlying struct data. The caller must ensure that the
/// `VALUE` is a valid pointer to a struct.
unsafe fn rstruct_get(&self, st: VALUE, idx: c_int) -> VALUE;

/// Sets an indexed member of the struct.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer to get
/// access to underlying struct data. The caller must ensure that the
/// `VALUE` is a valid pointer to a struct.
unsafe fn rstruct_set(&self, st: VALUE, idx: c_int, value: VALUE);

/// Returns the number of struct members.
///
/// # Safety
/// This function is unsafe because it dereferences a raw pointer to get
/// access to underlying struct data. The caller must ensure that the
/// `VALUE` is a valid pointer to an RStruct.
unsafe fn rstruct_len(&self, obj: VALUE) -> c_long;
}

#[cfg(stable_api_enable_compiled_mod)]
Expand Down
20 changes: 20 additions & 0 deletions crates/rb-sys/src/stable_api/compiled.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,23 @@ impl_thread_sleep(struct timeval time) {
rb_thread_wait_for(time);
}

long
impl_rstruct_len(VALUE st) {
return RSTRUCT_LEN(st);
}

VALUE
impl_rstruct_define(char* name, ...) {
return rb_struct_define(name, NULL);
}

VALUE
impl_rstruct_get(VALUE st, int idx) {
return RSTRUCT_GET(st, idx);
}

VALUE
impl_rstruct_set(VALUE st, int idx, VALUE value) {
return RSTRUCT_SET(st, idx, value);
}

39 changes: 39 additions & 0 deletions crates/rb-sys/src/stable_api/compiled.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::StableApiDefinition;
use crate::{ruby_value_type, timeval, VALUE};
use std::{
ffi::{c_int, CStr},
os::raw::{c_char, c_long},
ptr::NonNull,
time::Duration,
Expand Down Expand Up @@ -79,6 +80,18 @@ extern "C" {

#[link_name = "impl_thread_sleep"]
fn impl_thread_sleep(interval: timeval);

#[link_name = "impl_rstruct_define"]
fn impl_rstruct_define(name: &CStr, members: &[*const c_char]) -> VALUE;

#[link_name = "impl_rstruct_get"]
fn impl_rstruct_get(st: VALUE, idx: c_int) -> VALUE;

#[link_name = "impl_rstruct_set"]
fn impl_rstruct_set(st: VALUE, idx: c_int, value: VALUE);

#[link_name = "impl_rstruct_len"]
fn impl_rstruct_len(obj: VALUE) -> c_long;
}

pub struct Definition;
Expand Down Expand Up @@ -112,6 +125,7 @@ impl StableApiDefinition for Definition {
NonNull::<VALUE>::new(impl_rbasic_class(obj) as _)
}

#[inline]
unsafe fn frozen_p(&self, obj: VALUE) -> bool {
impl_frozen_p(obj)
}
Expand Down Expand Up @@ -213,4 +227,29 @@ impl StableApiDefinition for Definition {

unsafe { impl_thread_sleep(time) }
}

#[inline]
fn rstruct_define(&self, name: &CStr, members: &[&CStr]) -> VALUE {
let mut members: Vec<*const c_char> = members
.iter()
.map(|m| m.as_ptr() as *const c_char)
.collect();
members.push(std::ptr::null());
unsafe { impl_rstruct_define(name, members.as_ref()) }
}

#[inline]
unsafe fn rstruct_get(&self, st: VALUE, idx: c_int) -> VALUE {
impl_rstruct_get(st, idx)
}

#[inline]
unsafe fn rstruct_set(&self, st: VALUE, idx: c_int, value: VALUE) {
impl_rstruct_set(st, idx, value)
}

#[inline]
unsafe fn rstruct_len(&self, st: VALUE) -> c_long {
impl_rstruct_len(st)
}
}
80 changes: 79 additions & 1 deletion crates/rb-sys/src/stable_api/ruby_2_6.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,39 @@ use crate::ruby_rarray_flags::*;
use crate::ruby_rstring_flags::*;
use crate::{
internal::{RArray, RString},
value_type, VALUE,
ruby_fl_type, value_type, VALUE,
};
use std::{
ffi::{c_int, CStr},
os::raw::{c_char, c_long},
ptr::NonNull,
time::Duration,
};

const RSTRUCT_EMBED_LEN_MASK: VALUE =
(ruby_fl_type::RUBY_FL_USER2 as VALUE) | (ruby_fl_type::RUBY_FL_USER1 as VALUE);
const RSTRUCT_EMBED_LEN_SHIFT: VALUE = (ruby_fl_type::RUBY_FL_USHIFT as VALUE) + 1;
const RSTRUCT_EMBED_LEN_MAX: usize = 3;

#[repr(C)]
#[derive(Clone, Copy)]
pub(crate) struct HeapStructData {
pub(crate) len: std::ffi::c_long,
pub(crate) ptr: *const VALUE,
}

#[repr(C)]
pub(crate) union RStructUnion {
pub(crate) heap: HeapStructData,
pub(crate) ary: [VALUE; RSTRUCT_EMBED_LEN_MAX],
}

#[repr(C)]
pub struct RStruct {
pub(crate) basic: crate::RBasic,
pub(crate) as_: RStructUnion,
}

#[cfg(not(ruby_eq_2_6))]
compile_error!("This file should only be included in Ruby 2.6 builds");

Expand Down Expand Up @@ -276,4 +301,57 @@ impl StableApiDefinition for Definition {

unsafe { crate::rb_thread_wait_for(time) }
}

#[inline]
fn rstruct_define(&self, name: &CStr, members: &[&CStr]) -> VALUE {
let mut members: Vec<*const c_char> = members
.iter()
.map(|m| m.as_ptr() as *const c_char)
.collect();
members.push(std::ptr::null());
unsafe { crate::rb_struct_define(name.as_ptr(), members) }
}

#[inline]
unsafe fn rstruct_get(&self, st: VALUE, idx: c_int) -> VALUE {
let rbasic = st as *const crate::RBasic;
let rstruct = st as *const RStruct;
let slice: &[VALUE] = if ((*rbasic).flags & RSTRUCT_EMBED_LEN_MASK as VALUE) != 0 {
(*rstruct).as_.ary.as_slice()
} else {
let ptr = (*rstruct).as_.heap.ptr;
let len = (*rstruct).as_.heap.len as _;
std::slice::from_raw_parts(ptr, len)
};

slice[idx as usize]
}

#[inline]
unsafe fn rstruct_set(&self, st: VALUE, idx: c_int, value: VALUE) {
let rbasic = st as *const crate::RBasic;
let rstruct = st as *mut RStruct;
let slice: &mut [VALUE] = if ((*rbasic).flags & RSTRUCT_EMBED_LEN_MASK as VALUE) != 0 {
(*rstruct).as_.ary.as_mut_slice()
} else {
let ptr = (*rstruct).as_.heap.ptr as *mut _;
let len = (*rstruct).as_.heap.len as _;
std::slice::from_raw_parts_mut(ptr, len)
};

slice[idx as usize] = value;
}

#[inline]
unsafe fn rstruct_len(&self, st: VALUE) -> c_long {
let rbasic = st as *const crate::RBasic;
if ((*rbasic).flags & RSTRUCT_EMBED_LEN_MASK as VALUE) != 0 {
let mut ret = ((*rbasic).flags & RSTRUCT_EMBED_LEN_MASK as VALUE) as c_long;
ret >>= RSTRUCT_EMBED_LEN_SHIFT;
ret
} else {
let rstruct = st as *const RStruct;
(*rstruct).as_.heap.len
}
}
}
Loading
Loading