Skip to main content

shape_value/v2/
refcount.rs

1//! Inline refcount operations for v2 heap objects.
2//!
3//! These free functions operate on raw `*const HeapHeader` pointers, suitable
4//! for use from JIT-generated code and FFI. The caller is responsible for
5//! pointer validity.
6
7use super::heap_header::HeapHeader;
8use std::sync::atomic::Ordering;
9
10/// Increment the reference count of a v2 heap object.
11///
12/// # Safety
13/// `ptr` must point to a valid, live `HeapHeader`.
14#[inline(always)]
15pub unsafe fn v2_retain(ptr: *const HeapHeader) {
16    unsafe { (*ptr).refcount.fetch_add(1, Ordering::Relaxed) };
17}
18
19/// Decrement the reference count of a v2 heap object.
20/// Returns `true` if the count reached zero (caller must deallocate).
21///
22/// Uses Release ordering on the decrement and an Acquire fence when the
23/// count reaches zero, ensuring all prior writes are visible before deallocation.
24///
25/// # Safety
26/// `ptr` must point to a valid, live `HeapHeader`.
27/// If this returns `true`, the caller must deallocate the object and must not
28/// access it again.
29#[inline(always)]
30pub unsafe fn v2_release(ptr: *const HeapHeader) -> bool {
31    let old = unsafe { (*ptr).refcount.fetch_sub(1, Ordering::Release) };
32    if old == 1 {
33        std::sync::atomic::fence(Ordering::Acquire);
34        true // caller must dealloc
35    } else {
36        false
37    }
38}
39
40/// Get the current reference count of a v2 heap object.
41///
42/// # Safety
43/// `ptr` must point to a valid, live `HeapHeader`.
44#[inline(always)]
45pub unsafe fn v2_get_refcount(ptr: *const HeapHeader) -> u32 {
46    unsafe { (*ptr).refcount.load(Ordering::Relaxed) }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::v2::heap_header::HEAP_KIND_V2_TYPED_ARRAY;
53
54    #[test]
55    fn test_v2_retain_increments() {
56        let h = HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY);
57        unsafe {
58            let ptr = &h as *const HeapHeader;
59            assert_eq!(v2_get_refcount(ptr), 1);
60
61            v2_retain(ptr);
62            assert_eq!(v2_get_refcount(ptr), 2);
63
64            v2_retain(ptr);
65            assert_eq!(v2_get_refcount(ptr), 3);
66        }
67    }
68
69    #[test]
70    fn test_v2_release_decrements() {
71        let h = HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY);
72        unsafe {
73            let ptr = &h as *const HeapHeader;
74            v2_retain(ptr); // 2
75            v2_retain(ptr); // 3
76
77            assert!(!v2_release(ptr)); // 3 -> 2
78            assert_eq!(v2_get_refcount(ptr), 2);
79
80            assert!(!v2_release(ptr)); // 2 -> 1
81            assert_eq!(v2_get_refcount(ptr), 1);
82
83            assert!(v2_release(ptr)); // 1 -> 0, caller must dealloc
84        }
85    }
86
87    #[test]
88    fn test_v2_release_returns_true_on_last_ref() {
89        let h = HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY);
90        unsafe {
91            let ptr = &h as *const HeapHeader;
92            assert!(v2_release(ptr)); // 1 -> 0
93        }
94    }
95
96    #[test]
97    fn test_v2_retain_release_thread_safety() {
98        use std::sync::Arc;
99
100        // Box the header so it has a stable address, then wrap in Arc for sharing.
101        let header = Arc::new(HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY));
102
103        let threads: Vec<_> = (0..8)
104            .map(|_| {
105                let h = Arc::clone(&header);
106                std::thread::spawn(move || {
107                    let ptr = &*h as *const HeapHeader;
108                    for _ in 0..1000 {
109                        unsafe { v2_retain(ptr) };
110                    }
111                    for _ in 0..1000 {
112                        unsafe { v2_release(ptr) };
113                    }
114                })
115            })
116            .collect();
117
118        for t in threads {
119            t.join().unwrap();
120        }
121
122        unsafe {
123            let ptr = &*header as *const HeapHeader;
124            assert_eq!(v2_get_refcount(ptr), 1);
125        }
126    }
127
128    #[test]
129    fn test_v2_refcount_operations_match_header_methods() {
130        let h = HeapHeader::new(HEAP_KIND_V2_TYPED_ARRAY);
131        unsafe {
132            let ptr = &h as *const HeapHeader;
133
134            // Free-function and method should agree
135            assert_eq!(v2_get_refcount(ptr), h.get_refcount());
136
137            v2_retain(ptr);
138            assert_eq!(v2_get_refcount(ptr), h.get_refcount());
139            assert_eq!(h.get_refcount(), 2);
140
141            let should_dealloc = v2_release(ptr);
142            assert!(!should_dealloc);
143            assert_eq!(v2_get_refcount(ptr), h.get_refcount());
144            assert_eq!(h.get_refcount(), 1);
145        }
146    }
147}