Skip to content

Commit f81356f

Browse files
committed
feat(stable-api): add RARRAY_AREF and RARRAY_ASET with write barrier
Implements #671 - Add rarray_aref() and rarray_aset() to StableApiDefinition trait - Implement for Ruby 2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 4.0 - RARRAY_ASET uses rb_gc_writebarrier for GC correctness - Add C fallback in compiled.c - Add FFI wrappers in compiled.rs - Add public RARRAY_AREF and RARRAY_ASET macros - Add comprehensive parity tests - Add show-asm entries for assembly analysis Depends on write barrier implementation from batch3.
1 parent 4ac9a39 commit f81356f

13 files changed

Lines changed: 344 additions & 0 deletions

File tree

crates/rb-sys-tests/src/stable_api_test.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,166 @@ parity_test!(
185185
}
186186
);
187187

188+
#[rb_sys_test_helpers::ruby_test]
189+
fn test_rarray_aref_basic() {
190+
let ary = unsafe { rb_sys::rb_ary_new_capa(3) };
191+
unsafe {
192+
rb_sys::rb_ary_push(
193+
ary,
194+
rb_sys::rb_str_new_cstr("hello\0".as_ptr() as *const i8),
195+
);
196+
rb_sys::rb_ary_push(
197+
ary,
198+
rb_sys::rb_str_new_cstr("world\0".as_ptr() as *const i8),
199+
);
200+
}
201+
let idx = 0;
202+
203+
assert_ne!(stable_api::get_default().version(), (0, 0));
204+
205+
let rust_result = unsafe { stable_api::get_default().rarray_aref(ary, idx) };
206+
let compiled_c_result = unsafe { stable_api::get_compiled().rarray_aref(ary, idx) };
207+
208+
assert_eq!(
209+
compiled_c_result, rust_result,
210+
"compiled_c was {:?}, rust was {:?}",
211+
compiled_c_result, rust_result
212+
);
213+
}
214+
215+
#[rb_sys_test_helpers::ruby_test]
216+
fn test_rarray_aref_second_element() {
217+
let ary = unsafe { rb_sys::rb_ary_new_capa(3) };
218+
unsafe {
219+
rb_sys::rb_ary_push(
220+
ary,
221+
rb_sys::rb_str_new_cstr("hello\0".as_ptr() as *const i8),
222+
);
223+
rb_sys::rb_ary_push(
224+
ary,
225+
rb_sys::rb_str_new_cstr("world\0".as_ptr() as *const i8),
226+
);
227+
}
228+
let idx = 1;
229+
230+
assert_ne!(stable_api::get_default().version(), (0, 0));
231+
232+
let rust_result = unsafe { stable_api::get_default().rarray_aref(ary, idx) };
233+
let compiled_c_result = unsafe { stable_api::get_compiled().rarray_aref(ary, idx) };
234+
235+
assert_eq!(
236+
compiled_c_result, rust_result,
237+
"compiled_c was {:?}, rust was {:?}",
238+
compiled_c_result, rust_result
239+
);
240+
}
241+
242+
#[rb_sys_test_helpers::ruby_test]
243+
fn test_rarray_aref_evaled() {
244+
let ary = ruby_eval!("[1, 2, 3]");
245+
let idx = 1;
246+
247+
assert_ne!(stable_api::get_default().version(), (0, 0));
248+
249+
let rust_result = unsafe { stable_api::get_default().rarray_aref(ary, idx) };
250+
let compiled_c_result = unsafe { stable_api::get_compiled().rarray_aref(ary, idx) };
251+
252+
assert_eq!(
253+
compiled_c_result, rust_result,
254+
"compiled_c was {:?}, rust was {:?}",
255+
compiled_c_result, rust_result
256+
);
257+
}
258+
259+
#[rb_sys_test_helpers::ruby_test]
260+
fn test_rarray_aset_basic() {
261+
let ary = unsafe { rb_sys::rb_ary_new_capa(3) };
262+
unsafe {
263+
rb_sys::rb_ary_push(ary, rb_sys::Qnil as rb_sys::VALUE);
264+
}
265+
let val = unsafe { rb_sys::rb_str_new_cstr("test\0".as_ptr() as *const i8) };
266+
let idx = 0;
267+
268+
assert_ne!(stable_api::get_default().version(), (0, 0));
269+
270+
unsafe { stable_api::get_default().rarray_aset(ary, idx, val) };
271+
let rust_result = unsafe { stable_api::get_default().rarray_aref(ary, idx) };
272+
273+
// Reset for C test
274+
let ary2 = unsafe { rb_sys::rb_ary_new_capa(3) };
275+
unsafe {
276+
rb_sys::rb_ary_push(ary2, rb_sys::Qnil as rb_sys::VALUE);
277+
}
278+
unsafe { stable_api::get_compiled().rarray_aset(ary2, idx, val) };
279+
let compiled_c_result = unsafe { stable_api::get_compiled().rarray_aref(ary2, idx) };
280+
281+
assert_eq!(
282+
compiled_c_result, rust_result,
283+
"compiled_c was {:?}, rust was {:?}",
284+
compiled_c_result, rust_result
285+
);
286+
}
287+
288+
#[rb_sys_test_helpers::ruby_test]
289+
fn test_rarray_aset_replace() {
290+
let ary = unsafe { rb_sys::rb_ary_new_capa(3) };
291+
unsafe {
292+
rb_sys::rb_ary_push(ary, rb_sys::rb_str_new_cstr("old\0".as_ptr() as *const i8));
293+
rb_sys::rb_ary_push(
294+
ary,
295+
rb_sys::rb_str_new_cstr("value\0".as_ptr() as *const i8),
296+
);
297+
}
298+
let val = unsafe { rb_sys::rb_str_new_cstr("new\0".as_ptr() as *const i8) };
299+
let idx = 0;
300+
301+
assert_ne!(stable_api::get_default().version(), (0, 0));
302+
303+
unsafe { stable_api::get_default().rarray_aset(ary, idx, val) };
304+
let rust_result = unsafe { stable_api::get_default().rarray_aref(ary, idx) };
305+
306+
// Reset for C test
307+
let ary2 = unsafe { rb_sys::rb_ary_new_capa(3) };
308+
unsafe {
309+
rb_sys::rb_ary_push(ary2, rb_sys::rb_str_new_cstr("old\0".as_ptr() as *const i8));
310+
rb_sys::rb_ary_push(
311+
ary2,
312+
rb_sys::rb_str_new_cstr("value\0".as_ptr() as *const i8),
313+
);
314+
}
315+
unsafe { stable_api::get_compiled().rarray_aset(ary2, idx, val) };
316+
let compiled_c_result = unsafe { stable_api::get_compiled().rarray_aref(ary2, idx) };
317+
318+
assert_eq!(
319+
compiled_c_result, rust_result,
320+
"compiled_c was {:?}, rust was {:?}",
321+
compiled_c_result, rust_result
322+
);
323+
}
324+
325+
#[rb_sys_test_helpers::ruby_test]
326+
fn test_rarray_aset_evaled() {
327+
let ary = ruby_eval!("[1, 2, 3]");
328+
let val = ruby_eval!("42");
329+
let idx = 1;
330+
331+
assert_ne!(stable_api::get_default().version(), (0, 0));
332+
333+
unsafe { stable_api::get_default().rarray_aset(ary, idx, val) };
334+
let rust_result = unsafe { stable_api::get_default().rarray_aref(ary, idx) };
335+
336+
// Reset for C test
337+
let ary2 = ruby_eval!("[1, 2, 3]");
338+
unsafe { stable_api::get_compiled().rarray_aset(ary2, idx, val) };
339+
let compiled_c_result = unsafe { stable_api::get_compiled().rarray_aref(ary2, idx) };
340+
341+
assert_eq!(
342+
compiled_c_result, rust_result,
343+
"compiled_c was {:?}, rust was {:?}",
344+
compiled_c_result, rust_result
345+
);
346+
}
347+
188348
parity_test!(
189349
name: test_rbasic_class_of_array,
190350
func: rbasic_class,

crates/rb-sys/src/macros.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,38 @@ pub unsafe fn RARRAY_LEN(obj: VALUE) -> c_long {
112112
api().rarray_len(obj)
113113
}
114114

115+
/// Read array element at index (akin to `RARRAY_AREF`).
116+
///
117+
/// ### Safety
118+
///
119+
/// This function is unsafe because it dereferences a raw pointer in order to
120+
/// access internal Ruby memory.
121+
///
122+
/// - @param[in] obj An object of ::RArray.
123+
/// - @param[in] idx Index within the array (must be within bounds: 0..RARRAY_LEN(obj)).
124+
/// - @return The element at the given index.
125+
#[inline(always)]
126+
pub unsafe fn RARRAY_AREF(obj: VALUE, idx: isize) -> VALUE {
127+
api().rarray_aref(obj.into(), idx)
128+
}
129+
130+
/// Write array element at index (akin to `RARRAY_ASET`).
131+
///
132+
/// This function includes the GC write barrier for correctness.
133+
///
134+
/// ### Safety
135+
///
136+
/// This function is unsafe because it dereferences a raw pointer in order to
137+
/// access internal Ruby memory.
138+
///
139+
/// - @param[in] obj An object of ::RArray.
140+
/// - @param[in] idx Index within the array (must be within bounds: 0..RARRAY_LEN(obj)).
141+
/// - @param[in] val The value to set at the given index.
142+
#[inline(always)]
143+
pub unsafe fn RARRAY_ASET(obj: VALUE, idx: isize, val: VALUE) {
144+
api().rarray_aset(obj.into(), idx, val)
145+
}
146+
115147
/// Get the length of a Ruby string.
116148
///
117149
/// ### Safety

crates/rb-sys/src/stable_api.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ pub trait StableApiDefinition {
5858
/// is valid.
5959
unsafe fn rarray_const_ptr(&self, obj: VALUE) -> *const VALUE;
6060

61+
/// Get element from array by index (akin to `RARRAY_AREF`).
62+
///
63+
/// # Safety
64+
/// This function is unsafe because it dereferences a raw pointer to get
65+
/// access to underlying Ruby data. The caller must ensure that the pointer
66+
/// is valid and that the index is within bounds.
67+
unsafe fn rarray_aref(&self, obj: VALUE, idx: isize) -> VALUE;
68+
69+
/// Set element in array by index (akin to `RARRAY_ASET`).
70+
///
71+
/// Includes GC write barrier for safety.
72+
///
73+
/// # Safety
74+
/// This function is unsafe because it dereferences a raw pointer to get
75+
/// access to underlying Ruby data. The caller must ensure that the pointer
76+
/// is valid and that the index is within bounds.
77+
unsafe fn rarray_aset(&self, obj: VALUE, idx: isize, val: VALUE);
78+
6179
/// Get the class from a VALUE which contains an RBasic struct.
6280
///
6381
/// `VALUE` is a valid pointer to a non-immediate object.

crates/rb-sys/src/stable_api/compiled.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ impl_rarray_const_ptr(VALUE obj)
4141
return RARRAY_CONST_PTR(obj);
4242
}
4343

44+
VALUE
45+
impl_rarray_aref(VALUE obj, long idx)
46+
{
47+
return RARRAY_AREF(obj, idx);
48+
}
49+
50+
void
51+
impl_rarray_aset(VALUE obj, long idx, VALUE val)
52+
{
53+
RARRAY_ASET(obj, idx, val);
54+
}
55+
4456
VALUE
4557
impl_rbasic_class(VALUE obj)
4658
{

crates/rb-sys/src/stable_api/compiled.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ extern "C" {
2121
#[link_name = "impl_rarray_const_ptr"]
2222
fn impl_rarray_const_ptr(ary: VALUE) -> *const VALUE;
2323

24+
#[link_name = "impl_rarray_aref"]
25+
fn impl_rarray_aref(ary: VALUE, idx: c_long) -> VALUE;
26+
27+
#[link_name = "impl_rarray_aset"]
28+
fn impl_rarray_aset(ary: VALUE, idx: c_long, val: VALUE);
29+
2430
#[link_name = "impl_rbasic_class"]
2531
fn impl_rbasic_class(obj: VALUE) -> VALUE;
2632

@@ -159,6 +165,16 @@ impl StableApiDefinition for Definition {
159165
impl_rarray_const_ptr(obj)
160166
}
161167

168+
#[inline]
169+
unsafe fn rarray_aref(&self, obj: VALUE, idx: isize) -> VALUE {
170+
impl_rarray_aref(obj, idx as c_long)
171+
}
172+
173+
#[inline]
174+
unsafe fn rarray_aset(&self, obj: VALUE, idx: isize, val: VALUE) {
175+
impl_rarray_aset(obj, idx as c_long, val)
176+
}
177+
162178
#[inline]
163179
unsafe fn rbasic_class(&self, obj: VALUE) -> Option<NonNull<VALUE>> {
164180
NonNull::<VALUE>::new(impl_rbasic_class(obj) as _)

crates/rb-sys/src/stable_api/ruby_2_7.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ impl StableApiDefinition for Definition {
9595
ptr
9696
}
9797

98+
#[inline]
99+
unsafe fn rarray_aref(&self, obj: VALUE, idx: isize) -> VALUE {
100+
*self.rarray_const_ptr(obj).offset(idx)
101+
}
102+
103+
#[inline]
104+
unsafe fn rarray_aset(&self, obj: VALUE, idx: isize, val: VALUE) {
105+
let ptr = self.rarray_const_ptr(obj).cast_mut().offset(idx);
106+
self.rb_obj_write(obj, ptr, val);
107+
}
108+
98109
#[inline]
99110
unsafe fn rbasic_class(&self, obj: VALUE) -> Option<NonNull<VALUE>> {
100111
let rbasic = obj as *const crate::RBasic;

crates/rb-sys/src/stable_api/ruby_3_0.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ impl StableApiDefinition for Definition {
103103
}
104104
}
105105

106+
#[inline]
107+
unsafe fn rarray_aref(&self, obj: VALUE, idx: isize) -> VALUE {
108+
*self.rarray_const_ptr(obj).offset(idx)
109+
}
110+
111+
#[inline]
112+
unsafe fn rarray_aset(&self, obj: VALUE, idx: isize, val: VALUE) {
113+
let ptr = self.rarray_const_ptr(obj).cast_mut().offset(idx);
114+
self.rb_obj_write(obj, ptr, val);
115+
}
116+
106117
#[inline]
107118
unsafe fn rbasic_class(&self, obj: VALUE) -> Option<NonNull<VALUE>> {
108119
let rbasic = obj as *const crate::RBasic;

crates/rb-sys/src/stable_api/ruby_3_1.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ impl StableApiDefinition for Definition {
9696
ret
9797
}
9898

99+
#[inline]
100+
unsafe fn rarray_aref(&self, obj: VALUE, idx: isize) -> VALUE {
101+
*self.rarray_const_ptr(obj).offset(idx)
102+
}
103+
104+
#[inline]
105+
unsafe fn rarray_aset(&self, obj: VALUE, idx: isize, val: VALUE) {
106+
let ptr = self.rarray_const_ptr(obj).cast_mut().offset(idx);
107+
self.rb_obj_write(obj, ptr, val);
108+
}
109+
99110
#[inline]
100111
unsafe fn rbasic_class(&self, obj: VALUE) -> Option<NonNull<VALUE>> {
101112
let rbasic = obj as *const crate::RBasic;

crates/rb-sys/src/stable_api/ruby_3_2.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ impl StableApiDefinition for Definition {
9090
ptr
9191
}
9292

93+
#[inline]
94+
unsafe fn rarray_aref(&self, obj: VALUE, idx: isize) -> VALUE {
95+
*self.rarray_const_ptr(obj).offset(idx)
96+
}
97+
98+
#[inline]
99+
unsafe fn rarray_aset(&self, obj: VALUE, idx: isize, val: VALUE) {
100+
let ptr = self.rarray_const_ptr(obj).cast_mut().offset(idx);
101+
self.rb_obj_write(obj, ptr, val);
102+
}
103+
93104
#[inline]
94105
unsafe fn rbasic_class(&self, obj: VALUE) -> Option<NonNull<VALUE>> {
95106
let rbasic = obj as *const crate::RBasic;

crates/rb-sys/src/stable_api/ruby_3_3.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ impl StableApiDefinition for Definition {
8282
ptr
8383
}
8484

85+
#[inline]
86+
unsafe fn rarray_aref(&self, obj: VALUE, idx: isize) -> VALUE {
87+
*self.rarray_const_ptr(obj).offset(idx)
88+
}
89+
90+
#[inline]
91+
unsafe fn rarray_aset(&self, obj: VALUE, idx: isize, val: VALUE) {
92+
let ptr = self.rarray_const_ptr(obj).cast_mut().offset(idx);
93+
self.rb_obj_write(obj, ptr, val);
94+
}
95+
8596
#[inline]
8697
unsafe fn rbasic_class(&self, obj: VALUE) -> Option<NonNull<VALUE>> {
8798
let rbasic = obj as *const crate::RBasic;

0 commit comments

Comments
 (0)