mun_runtime/
gc.rs

1//! Exposes Mun garbage collection.
2
3use mun_capi_utils::error::ErrorHandle;
4use mun_capi_utils::{mun_error_try, try_deref_mut};
5use std::mem::ManuallyDrop;
6
7use crate::runtime::Runtime;
8use mun_memory::{ffi::Type, gc::GcRuntime};
9
10pub use mun_memory::gc::GcPtr;
11
12/// Allocates an object in the runtime of the given `ty`. If successful, `obj` is set,
13/// otherwise a non-zero error handle is returned.
14///
15/// If a non-zero error handle is returned, it must be manually destructed using
16/// [`mun_error_destroy`].
17///
18/// # Safety
19///
20/// This function receives raw pointers as parameters. If any of the arguments is a null pointer,
21/// an error will be returned. Passing pointers to invalid data, will lead to undefined behavior.
22#[no_mangle]
23pub unsafe extern "C" fn mun_gc_alloc(runtime: Runtime, ty: Type, obj: *mut GcPtr) -> ErrorHandle {
24    let runtime = mun_error_try!(runtime
25        .inner()
26        .map_err(|e| format!("invalid argument 'runtime': {e}")));
27    let ty = mun_error_try!(ty
28        .to_owned()
29        .map_err(|e| format!("invalid argument 'obj': {e}"))
30        .map(ManuallyDrop::new));
31    let obj = try_deref_mut!(obj);
32    *obj = runtime.gc().alloc(&ty);
33    ErrorHandle::default()
34}
35
36/// Retrieves the `ty` for the specified `obj` from the runtime. If successful, `ty` is set,
37/// otherwise a non-zero error handle is returned.
38///
39/// If a non-zero error handle is returned, it must be manually destructed using
40/// [`mun_error_destroy`].
41///
42/// # Safety
43///
44/// This function receives raw pointers as parameters. If any of the arguments is a null pointer,
45/// an error will be returned. Passing pointers to invalid data, will lead to undefined behavior.
46#[no_mangle]
47pub unsafe extern "C" fn mun_gc_ptr_type(
48    runtime: Runtime,
49    obj: GcPtr,
50    ty: *mut Type,
51) -> ErrorHandle {
52    let runtime = mun_error_try!(runtime
53        .inner()
54        .map_err(|e| format!("invalid argument 'runtime': {e}")));
55    let ty = try_deref_mut!(ty);
56    *ty = runtime.gc().ptr_type(obj).into();
57    ErrorHandle::default()
58}
59
60/// Roots the specified `obj`, which keeps it and objects it references alive. Objects marked as
61/// root, must call `mun_gc_unroot` before they can be collected. An object can be rooted multiple
62/// times, but you must make sure to call `mun_gc_unroot` an equal number of times before the
63/// object can be collected. If successful, `obj` has been rooted, otherwise a non-zero error handle
64/// is returned.
65///
66/// If a non-zero error handle is returned, it must be manually destructed using
67/// [`mun_error_destroy`].
68///
69/// # Safety
70///
71/// This function receives raw pointers as parameters. If any of the arguments is a null pointer,
72/// an error will be returned. Passing pointers to invalid data, will lead to undefined behavior.
73#[no_mangle]
74pub unsafe extern "C" fn mun_gc_root(runtime: Runtime, obj: GcPtr) -> ErrorHandle {
75    let runtime = mun_error_try!(runtime
76        .inner()
77        .map_err(|e| format!("invalid argument 'runtime': {e}")));
78    runtime.gc().root(obj);
79    ErrorHandle::default()
80}
81
82/// Unroots the specified `obj`, potentially allowing it and objects it references to be
83/// collected. An object can be rooted multiple times, so you must make sure to call `mun_gc_unroot`
84/// the same number of times as `mun_gc_root` was called before the object can be collected. If
85/// successful, `obj` has been unrooted, otherwise a non-zero error handle is returned.
86///
87/// If a non-zero error handle is returned, it must be manually destructed using
88/// [`mun_error_destroy`].
89///
90/// # Safety
91///
92/// This function receives raw pointers as parameters. If any of the arguments is a null pointer,
93/// an error will be returned. Passing pointers to invalid data, will lead to undefined behavior.
94#[no_mangle]
95pub unsafe extern "C" fn mun_gc_unroot(runtime: Runtime, obj: GcPtr) -> ErrorHandle {
96    let runtime = mun_error_try!(runtime
97        .inner()
98        .map_err(|e| format!("invalid argument 'runtime': {e}")));
99    runtime.gc().unroot(obj);
100    ErrorHandle::default()
101}
102
103/// Collects all memory that is no longer referenced by rooted objects. If successful, `reclaimed`
104/// is set, otherwise a non-zero error handle is returned. If `reclaimed` is `true`, memory was
105/// reclaimed, otherwise nothing happend. This behavior will likely change in the future.
106///
107/// If a non-zero error handle is returned, it must be manually destructed using
108/// [`mun_error_destroy`].
109///
110/// # Safety
111///
112/// This function receives raw pointers as parameters. If any of the arguments is a null pointer,
113/// an error will be returned. Passing pointers to invalid data, will lead to undefined behavior.
114#[no_mangle]
115pub unsafe extern "C" fn mun_gc_collect(runtime: Runtime, reclaimed: *mut bool) -> ErrorHandle {
116    let runtime = mun_error_try!(runtime
117        .inner()
118        .map_err(|e| format!("invalid argument 'runtime': {e}")));
119    let reclaimed = try_deref_mut!(reclaimed);
120    *reclaimed = runtime.gc_collect();
121    ErrorHandle::default()
122}
123
124#[cfg(test)]
125mod tests {
126    use mun_memory::ffi::{mun_type_equal, Type};
127    use mun_memory::gc::{HasIndirectionPtr, RawGcPtr};
128
129    use super::*;
130    use crate::{
131        runtime::mun_runtime_get_type_info_by_name, test_invalid_runtime, test_util::TestDriver,
132    };
133    use mun_capi_utils::error::mun_error_destroy;
134    use mun_capi_utils::{assert_error_snapshot, assert_getter1, assert_getter2};
135    use std::{
136        ffi::CString,
137        mem::{self},
138        ptr,
139    };
140
141    test_invalid_runtime!(
142        gc_alloc(Type::null(), ptr::null_mut()),
143        gc_ptr_type(mem::zeroed::<GcPtr>(), ptr::null_mut()),
144        gc_root(mem::zeroed::<GcPtr>()),
145        gc_unroot(mem::zeroed::<GcPtr>()),
146        gc_collect(ptr::null_mut())
147    );
148
149    #[test]
150    fn test_gc_alloc_invalid_type_info() {
151        let driver = TestDriver::new(
152            r#"
153        pub struct Foo;
154    "#,
155        );
156
157        assert_error_snapshot!(
158            unsafe { mun_gc_alloc(driver.runtime, Type::null(), ptr::null_mut()) },
159            @r###""invalid argument \'obj\': null pointer""###
160        );
161    }
162
163    #[test]
164    fn test_gc_alloc_invalid_obj() {
165        let driver = TestDriver::new(
166            r#"
167        pub struct Foo;
168    "#,
169        );
170
171        let type_name = CString::new("Foo").expect("Invalid type name.");
172        assert_getter2!(mun_runtime_get_type_info_by_name(
173            driver.runtime,
174            type_name.as_ptr(),
175            has_type,
176            ty,
177        ));
178        assert!(has_type);
179
180        assert_error_snapshot!(
181            unsafe { mun_gc_alloc(driver.runtime, ty, ptr::null_mut()) },
182            @r###""invalid argument \'obj\': null pointer""###
183        );
184    }
185
186    #[test]
187    fn test_gc_alloc() {
188        let driver = TestDriver::new(
189            r#"
190        pub struct Foo;
191    "#,
192        );
193
194        let type_name = CString::new("Foo").expect("Invalid type name.");
195        assert_getter2!(mun_runtime_get_type_info_by_name(
196            driver.runtime,
197            type_name.as_ptr(),
198            has_type,
199            ty,
200        ));
201        assert!(has_type);
202
203        assert_getter2!(mun_gc_alloc(driver.runtime, ty, obj));
204        assert_ne!(unsafe { obj.deref::<u8>() }, ptr::null());
205
206        assert_getter1!(mun_gc_collect(driver.runtime, reclaimed));
207        assert!(reclaimed);
208    }
209
210    #[test]
211    fn test_gc_ptr_type_invalid_type_info() {
212        let driver = TestDriver::new(
213            r#"
214        pub struct Foo;
215    "#,
216        );
217
218        assert_error_snapshot!(
219            unsafe {
220                let raw_ptr: RawGcPtr = ptr::null();
221                mun_gc_ptr_type(driver.runtime, raw_ptr.into(), ptr::null_mut())
222            },
223            @r###""invalid argument \'ty\': null pointer""###
224        );
225    }
226
227    #[test]
228    fn test_gc_ptr_type() {
229        let driver = TestDriver::new(
230            r#"
231        pub struct Foo;
232    "#,
233        );
234
235        let type_name = CString::new("Foo").expect("Invalid type name.");
236        assert_getter2!(mun_runtime_get_type_info_by_name(
237            driver.runtime,
238            type_name.as_ptr(),
239            has_type,
240            ty,
241        ));
242        assert!(has_type);
243
244        assert_getter2!(mun_gc_alloc(driver.runtime, ty, obj));
245        assert_ne!(unsafe { obj.deref::<u8>() }, ptr::null());
246
247        assert_getter2!(mun_gc_ptr_type(driver.runtime, obj, ptr_ty));
248        assert!(unsafe { mun_type_equal(ty, ptr_ty) });
249
250        assert_getter1!(mun_gc_collect(driver.runtime, reclaimed));
251        assert!(reclaimed);
252    }
253
254    #[test]
255    fn test_gc_rooting() {
256        let driver = TestDriver::new(
257            r#"
258        pub struct Foo;
259    "#,
260        );
261
262        let type_name = CString::new("Foo").expect("Invalid type name.");
263        assert_getter2!(mun_runtime_get_type_info_by_name(
264            driver.runtime,
265            type_name.as_ptr(),
266            has_type,
267            ty,
268        ));
269        assert!(has_type);
270
271        assert_getter2!(mun_gc_alloc(driver.runtime, ty, obj));
272        assert_ne!(unsafe { obj.deref::<u8>() }, ptr::null());
273
274        assert!(unsafe { mun_gc_root(driver.runtime, obj) }.is_ok());
275
276        assert_getter1!(mun_gc_collect(driver.runtime, reclaimed));
277        assert!(!reclaimed);
278
279        assert!(unsafe { mun_gc_unroot(driver.runtime, obj) }.is_ok());
280
281        assert_getter1!(mun_gc_collect(driver.runtime, reclaimed));
282        assert!(reclaimed);
283    }
284
285    #[test]
286    fn test_gc_ptr_collect_invalid_reclaimed() {
287        let driver = TestDriver::new(
288            r#"
289        pub struct Foo;
290
291        pub fn main() -> Foo { Foo }
292    "#,
293        );
294
295        assert_error_snapshot!(
296            unsafe { mun_gc_collect(driver.runtime, ptr::null_mut()) },
297            @r###""invalid argument \'reclaimed\': null pointer""###
298        );
299    }
300}