Skip to content

Commit 296fe29

Browse files
committed
Add global and generational roots
This requires ocaml-sys 0.19.1, which exposes the required runtime API.
1 parent d2c1713 commit 296fe29

File tree

6 files changed

+176
-9
lines changed

6 files changed

+176
-9
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ exclude = [
1616
features = [ "without-ocamlopt" ]
1717

1818
[dependencies]
19-
ocaml-sys = "^0.19"
19+
ocaml-sys = "^0.19.1"
2020
static_assertions = "1.1.0"
2121

2222
[features]

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ mod value;
356356
pub use crate::closure::{OCamlFn1, OCamlFn2, OCamlFn3, OCamlFn4, OCamlFn5};
357357
pub use crate::conv::{FromOCaml, ToOCaml};
358358
pub use crate::error::OCamlException;
359-
pub use crate::memory::OCamlRef;
359+
pub use crate::memory::{OCamlGenerationalRoot, OCamlGlobalRoot, OCamlRef};
360360
pub use crate::mlvalues::{
361361
OCamlBytes, OCamlFloat, OCamlInt, OCamlInt32, OCamlInt64, OCamlList, RawOCaml,
362362
};

src/memory.rs

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ use crate::{
77
runtime::OCamlRuntime,
88
value::OCaml,
99
};
10-
use core::{cell::UnsafeCell, marker::PhantomData, ptr};
10+
use core::{
11+
cell::{Cell, UnsafeCell},
12+
marker::PhantomData,
13+
pin::Pin,
14+
ptr,
15+
};
1116
pub use ocaml_sys::{
1217
caml_alloc, local_roots as ocaml_sys_local_roots, set_local_roots as ocaml_sys_set_local_roots,
1318
store_field,
@@ -122,6 +127,105 @@ impl<'a> OCamlRawRoot<'a> {
122127
}
123128
}
124129

130+
/// A global root for keeping OCaml values alive and tracked
131+
///
132+
/// This allows keeping a value around when exiting the stack frame.
133+
///
134+
/// See [`OCaml::register_global_root`].
135+
pub struct OCamlGlobalRoot<T> {
136+
pub(crate) cell: Pin<Box<Cell<RawOCaml>>>,
137+
_marker: PhantomData<Cell<T>>,
138+
}
139+
140+
impl<T> std::fmt::Debug for OCamlGlobalRoot<T> {
141+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142+
write!(f, "OCamlGlobalRoot({:#x})", self.cell.get())
143+
}
144+
}
145+
146+
impl<T> OCamlGlobalRoot<T> {
147+
// NOTE: we require initialisation here, unlike OCamlRoot which delays it
148+
// This is because we register with the GC in the constructor,
149+
// for easy pairing with Drop, and registering without initializing
150+
// would break OCaml runtime invariants.
151+
// Always registering with UNIT (like for GCFrame initialisation)
152+
// would also work, but for OCamlGenerationalRoot that would
153+
// make things slower (updating requires notifying the GC),
154+
// and it's better if the API is the same for both kinds of global roots.
155+
pub(crate) fn new(val: OCaml<T>) -> Self {
156+
let r = Self {
157+
cell: Box::pin(Cell::new(val.raw)),
158+
_marker: PhantomData,
159+
};
160+
unsafe { ocaml_sys::caml_register_global_root(r.cell.as_ptr()) };
161+
r
162+
}
163+
164+
/// Access the rooted value
165+
pub fn get_ref(&self) -> OCamlRef<T> {
166+
unsafe { OCamlCell::create_ref(self.cell.as_ptr()) }
167+
}
168+
169+
/// Replace the rooted value
170+
pub fn set(&self, val: OCaml<T>) {
171+
self.cell.replace(val.raw);
172+
}
173+
}
174+
175+
impl<T> Drop for OCamlGlobalRoot<T> {
176+
fn drop(&mut self) {
177+
unsafe { ocaml_sys::caml_remove_global_root(self.cell.as_ptr()) };
178+
}
179+
}
180+
181+
/// A global, GC-friendly root for keeping OCaml values alive and tracked
182+
///
183+
/// This allows keeping a value around when exiting the stack frame.
184+
///
185+
/// Unlike with [`OCamlGlobalRoot`], the GC doesn't have to walk
186+
/// referenced values on every minor collection. This makes collection
187+
/// faster, except if the value is short-lived and frequently updated.
188+
///
189+
/// See [`OCaml::register_generational_root`].
190+
pub struct OCamlGenerationalRoot<T> {
191+
pub(crate) cell: Pin<Box<Cell<RawOCaml>>>,
192+
_marker: PhantomData<Cell<T>>,
193+
}
194+
195+
impl<T> std::fmt::Debug for OCamlGenerationalRoot<T> {
196+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197+
write!(f, "OCamlGenerationalRoot({:#x})", self.cell.get())
198+
}
199+
}
200+
201+
impl<T> OCamlGenerationalRoot<T> {
202+
pub(crate) fn new(val: OCaml<T>) -> Self {
203+
let r = Self {
204+
cell: Box::pin(Cell::new(val.raw)),
205+
_marker: PhantomData,
206+
};
207+
unsafe { ocaml_sys::caml_register_generational_global_root(r.cell.as_ptr()) };
208+
r
209+
}
210+
211+
/// Access the rooted value
212+
pub fn get_ref(&self) -> OCamlRef<T> {
213+
unsafe { OCamlCell::create_ref(self.cell.as_ptr()) }
214+
}
215+
216+
/// Replace the rooted value
217+
pub fn set(&self, val: OCaml<T>) {
218+
unsafe { ocaml_sys::caml_modify_generational_global_root(self.cell.as_ptr(), val.raw) };
219+
debug_assert_eq!(self.cell.get(), val.raw);
220+
}
221+
}
222+
223+
impl<T> Drop for OCamlGenerationalRoot<T> {
224+
fn drop(&mut self) {
225+
unsafe { ocaml_sys::caml_remove_generational_global_root(self.cell.as_ptr()) };
226+
}
227+
}
228+
125229
pub struct OCamlCell<T> {
126230
cell: UnsafeCell<RawOCaml>,
127231
_marker: PhantomData<T>,

src/value.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) SimpleStaking and Tezedge Contributors
22
// SPDX-License-Identifier: MIT
33

4+
use crate::memory::{OCamlGenerationalRoot, OCamlGlobalRoot};
45
use crate::{
56
error::OCamlFixnumConversionError, memory::OCamlCell, mlvalues::*, FromOCaml, OCamlRef,
67
OCamlRuntime,
@@ -109,6 +110,20 @@ impl<'a, T> OCaml<'a, T> {
109110
{
110111
RustT::from_ocaml(*self)
111112
}
113+
114+
/// Register a global root with the OCaml runtime
115+
///
116+
/// If the value is seldom modified ([`OCamlGlobalRoot::set`] isn't
117+
/// frequently used), [`OCaml::register_generational_root`] can be
118+
/// faster.
119+
pub fn register_global_root(self) -> OCamlGlobalRoot<T> {
120+
OCamlGlobalRoot::new(self)
121+
}
122+
123+
/// Register a GC-friendly global root with the OCaml runtime
124+
pub fn register_generational_root(self) -> OCamlGenerationalRoot<T> {
125+
OCamlGenerationalRoot::new(self)
126+
}
112127
}
113128

114129
impl OCaml<'static, ()> {

testing/rust-caller/build.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ fn main() {
88
let ocaml_callable_dir = "./ocaml";
99
let dune_dir = "../../_build/default/testing/rust-caller/ocaml";
1010
Command::new("opam")
11-
.args(&["exec", "--", "dune", "build", &format!("{}/callable.exe.o", ocaml_callable_dir)])
11+
.args(&[
12+
"exec",
13+
"--",
14+
"dune",
15+
"build",
16+
&format!("{}/callable.exe.o", ocaml_callable_dir),
17+
])
1218
.status()
1319
.expect("Dune failed");
1420
Command::new("rm")

testing/rust-caller/src/lib.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
extern crate ocaml_interop;
55

6+
#[cfg(test)]
7+
use ocaml_interop::OCamlInt64;
68
use ocaml_interop::{ocaml_frame, to_ocaml, OCaml, OCamlBytes, OCamlRuntime, ToOCaml};
79

810
mod ocaml {
@@ -255,7 +257,6 @@ fn test_variant_conversion() {
255257
);
256258
}
257259

258-
259260
#[test]
260261
#[serial]
261262
fn test_exception_handling_with_message() {
@@ -269,7 +270,10 @@ fn test_exception_handling_with_message() {
269270
});
270271
});
271272
assert_eq!(
272-
result.err().and_then(|err| Some(err.downcast_ref::<String>().unwrap().clone())).unwrap(),
273+
result
274+
.err()
275+
.and_then(|err| Some(err.downcast_ref::<String>().unwrap().clone()))
276+
.unwrap(),
273277
"OCaml exception, message: Some(\"my-error-message\")"
274278
);
275279
}
@@ -283,7 +287,10 @@ fn test_exception_handling_without_message() {
283287
ocaml::raises_nonmessage_exception(cr, &OCaml::unit());
284288
});
285289
assert_eq!(
286-
result.err().and_then(|err| Some(err.downcast_ref::<String>().unwrap().clone())).unwrap(),
290+
result
291+
.err()
292+
.and_then(|err| Some(err.downcast_ref::<String>().unwrap().clone()))
293+
.unwrap(),
287294
"OCaml exception, message: None"
288295
);
289296
}
@@ -297,7 +304,42 @@ fn test_exception_handling_nonblock_exception() {
297304
ocaml::raises_nonblock_exception(cr, &OCaml::unit());
298305
});
299306
assert_eq!(
300-
result.err().and_then(|err| Some(err.downcast_ref::<String>().unwrap().clone())).unwrap(),
307+
result
308+
.err()
309+
.and_then(|err| Some(err.downcast_ref::<String>().unwrap().clone()))
310+
.unwrap(),
301311
"OCaml exception, message: None"
302312
);
303-
}
313+
}
314+
315+
#[test]
316+
#[serial]
317+
fn test_global_roots() {
318+
OCamlRuntime::init_persistent();
319+
let mut cr = unsafe { OCamlRuntime::recover_handle() };
320+
let crr = &mut cr;
321+
322+
let i64: OCaml<OCamlInt64> = to_ocaml!(crr, 5);
323+
let root = i64.register_global_root();
324+
ocaml::gc_compact(crr, &OCaml::unit());
325+
root.set(to_ocaml!(crr, 6));
326+
ocaml::gc_compact(crr, &OCaml::unit());
327+
let i64_bis: i64 = crr.get(root.get_ref()).to_rust();
328+
assert_eq!(i64_bis, 6);
329+
}
330+
331+
#[test]
332+
#[serial]
333+
fn test_generational_roots() {
334+
OCamlRuntime::init_persistent();
335+
let mut cr = unsafe { OCamlRuntime::recover_handle() };
336+
let crr = &mut cr;
337+
338+
let i64: OCaml<OCamlInt64> = to_ocaml!(crr, 5);
339+
let root = i64.register_generational_root();
340+
ocaml::gc_compact(crr, &OCaml::unit());
341+
root.set(to_ocaml!(crr, 6));
342+
ocaml::gc_compact(crr, &OCaml::unit());
343+
let i64_bis: i64 = crr.get(root.get_ref()).to_rust();
344+
assert_eq!(i64_bis, 6);
345+
}

0 commit comments

Comments
 (0)