rb_sys/memory.rs
1/// Prevents premature destruction of local objects.
2///
3/// Ruby's garbage collector is conservative; it scans the C level machine stack as well.
4/// Possible in-use Ruby objects must remain visible on stack, to be properly marked as such.
5/// However, Rust's compiler optimizations might remove the references to these objects from
6/// the stack when they are not being used directly.
7///
8/// Consider the following example:
9///
10/// ```ignore
11/// use rb_sys::{rb_str_new_cstr, rb_str_cat_cstr, RSTRING_PTR, rb_gc_guard};
12///
13/// unsafe {
14/// let s = rb_str_new_cstr(" world\0".as_ptr() as _);
15/// let sptr = RSTRING_PTR(s);
16/// let t = rb_str_new_cstr("hello,\0".as_ptr() as _); // Possible GC invocation
17/// let u = rb_str_cat_cstr(t, sptr);
18/// rb_gc_guard!(s); // ensure `s` (and thus `sptr`) do not get GC-ed
19/// }
20/// ```
21///
22/// In this example, without the `rb_gc_guard!`, the last use of `s` is before the last use
23/// of `sptr`. Compilers could think `s` and `t` are allowed to overlap. That would
24/// eliminate `s` from the stack, while `sptr` is still in use. If our GC runs at that
25/// very moment, `s` gets swept out, which also destroys `sptr`.
26///
27/// In order to prevent this scenario, `rb_gc_guard!` must be placed after the last use
28/// of `sptr`. Placing `rb_gc_guard!` before dereferencing `sptr` would be of no use.
29///
30/// Using the `rb_gc_guard!` macro has the following advantages:
31///
32/// - the intent of the macro use is clear.
33///
34/// - `rb_gc_guard!` only affects its call site, without negatively affecting other systems.
35///
36/// # Example
37/// ```no_run
38/// use rb_sys::{rb_utf8_str_new_cstr, rb_gc_guard};
39///
40/// let my_string = unsafe { rb_utf8_str_new_cstr("hello world\0".as_ptr() as _) };
41/// let _ = rb_gc_guard!(my_string);
42/// ```
43#[macro_export]
44macro_rules! rb_gc_guard {
45 ($v:expr) => {{
46 // This matches Ruby's RB_GC_GUARD implementation:
47 //
48 // volatile VALUE *rb_gc_guarded_ptr = &(v);
49 // __asm__("" : : "m"(rb_gc_guarded_ptr));
50 //
51 // The empty asm with "m" (memory) constraint tells the compiler:
52 // 1. The value must be in memory (not just a register)
53 // 2. The compiler cannot reorder or eliminate this memory access
54 //
55 // In Rust, we achieve this by:
56 // 1. Taking a reference to force stack allocation
57 // 2. Using read_volatile to prevent optimization
58 let rb_gc_guarded_ptr: *const $crate::VALUE = &$v;
59 // SAFETY: rb_gc_guarded_ptr points to a valid, aligned VALUE on the
60 // stack (created on the line above). The read is volatile to ensure
61 // the compiler keeps the VALUE visible for conservative GC scanning.
62 unsafe { std::ptr::read_volatile(rb_gc_guarded_ptr) }
63 }};
64}