mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-21 05:48:23 +00:00
feat(core/rust): introduce GcBox
This commit is contained in:
parent
a05ed10f1a
commit
2a896c44f6
@ -235,6 +235,7 @@ fn generate_micropython_bindings() {
|
|||||||
.allowlist_var("mp_type_fun_builtin_var")
|
.allowlist_var("mp_type_fun_builtin_var")
|
||||||
// gc
|
// gc
|
||||||
.allowlist_function("gc_alloc")
|
.allowlist_function("gc_alloc")
|
||||||
|
.allowlist_function("gc_free")
|
||||||
.allowlist_var("GC_ALLOC_FLAG_HAS_FINALISER")
|
.allowlist_var("GC_ALLOC_FLAG_HAS_FINALISER")
|
||||||
// iter
|
// iter
|
||||||
.allowlist_type("mp_obj_iter_buf_t")
|
.allowlist_type("mp_obj_iter_buf_t")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use core::{
|
use core::{
|
||||||
alloc::Layout,
|
alloc::Layout,
|
||||||
ops::Deref,
|
ops::{Deref, DerefMut},
|
||||||
ptr::{self, NonNull},
|
ptr::{self, NonNull},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,6 +33,10 @@ impl<T> Gc<T> {
|
|||||||
/// that have a base as their first element
|
/// that have a base as their first element
|
||||||
unsafe fn alloc(v: T, flags: u32) -> Result<Self, Error> {
|
unsafe fn alloc(v: T, flags: u32) -> Result<Self, Error> {
|
||||||
let layout = Layout::for_value(&v);
|
let layout = Layout::for_value(&v);
|
||||||
|
debug_assert!(
|
||||||
|
layout.size() > 0,
|
||||||
|
"Zero-sized allocations are not supported"
|
||||||
|
);
|
||||||
// TODO: Assert that `layout.align()` is the same as the GC alignment.
|
// TODO: Assert that `layout.align()` is the same as the GC alignment.
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - Unfortunately we cannot respect `layout.align()` as MicroPython GC does
|
// - Unfortunately we cannot respect `layout.align()` as MicroPython GC does
|
||||||
@ -161,3 +165,225 @@ impl<T: ?Sized> Deref for Gc<T> {
|
|||||||
unsafe { self.0.as_ref() }
|
unsafe { self.0.as_ref() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Box-like allocation on the GC heap.
|
||||||
|
///
|
||||||
|
/// Values allocated using GcBox are guaranteed to be only owned by that
|
||||||
|
/// particular GcBox instance. This makes them safe to mutate, and they run
|
||||||
|
/// destructors when dropped.
|
||||||
|
///
|
||||||
|
/// Suitable for use in cases where you would normally use Rust's native Box
|
||||||
|
/// type -- i.e., typically for storing sub-values in a struct, possibly also
|
||||||
|
/// for returning values from functions.
|
||||||
|
///
|
||||||
|
/// While general unsizing is not available, you can use the `coerce!` macro to
|
||||||
|
/// safely cast the box to a trait object.
|
||||||
|
///
|
||||||
|
/// # Safety and usage notes
|
||||||
|
///
|
||||||
|
/// One caveat of using GcBox is that it still always needs to be visible to the
|
||||||
|
/// GC -- that is, stored in a struct which is allocated on the GC heap, on the
|
||||||
|
/// call stack, or reachable from one of the GC roots.
|
||||||
|
///
|
||||||
|
/// Specifically, it is generally unsafe to store a GcBox in a global variable.
|
||||||
|
///
|
||||||
|
/// When a GcBox is stored in a struct, which itself is allocated via raw GC,
|
||||||
|
/// the containing struct might get GC'd, which will cause GcBox not to get
|
||||||
|
/// dropped, which in turn will prevent GcBox's contents from getting dropped.
|
||||||
|
pub struct GcBox<T: ?Sized>(Gc<T>);
|
||||||
|
|
||||||
|
impl<T> GcBox<T> {
|
||||||
|
/// Allocate memory on the heap managed by the MicroPython GC and then place
|
||||||
|
/// `value` into it.
|
||||||
|
///
|
||||||
|
/// `value` _will_ get its Drop implementation called when the GcBox is
|
||||||
|
/// dropped.
|
||||||
|
pub fn new(value: T) -> Result<Self, Error> {
|
||||||
|
Ok(Self(Gc::new(value)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> GcBox<T> {
|
||||||
|
/// Leak contents of the box as a pointer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The value will not be dropped. If necessary, the caller is responsible
|
||||||
|
/// for dropping it manually, e.g., via `ptr::drop_in_place`.
|
||||||
|
pub fn into_raw(this: Self) -> *mut T {
|
||||||
|
let result = Gc::into_raw(this.0);
|
||||||
|
core::mem::forget(this);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a `GcBox` from a raw pointer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This is only safe for pointers _allocated on the MicroPython GC heap_
|
||||||
|
/// via `gc_alloc()`. Specifically, unlike `Gc::from_raw`, it is unsafe
|
||||||
|
/// to construct a GcBox from ROM values, even those that are trackable
|
||||||
|
/// by the GC.
|
||||||
|
///
|
||||||
|
/// This is because the Drop implementation of GcBox calls `gc_free()` on
|
||||||
|
/// the pointer.
|
||||||
|
///
|
||||||
|
/// In addition, the caller must ensure that it is safe to apply box-like
|
||||||
|
/// semantics to the value, namely that:
|
||||||
|
/// * nobody else has the pointer to the value, so that it is safe to
|
||||||
|
/// create mutable references to it
|
||||||
|
/// * the value is going to be dropped when the GcBox is dropped.
|
||||||
|
pub unsafe fn from_raw(ptr: *mut T) -> Self {
|
||||||
|
// SAFETY: just a wrapper around Gc::from_raw
|
||||||
|
Self(unsafe { Gc::from_raw(ptr) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Leak contents of the box as a regular Gc allocation.
|
||||||
|
///
|
||||||
|
/// This gives up the unique ownership. It is no longer possible to safely
|
||||||
|
/// mutably borrow the value, and its destructor will not be called when
|
||||||
|
/// it is dropped. In exchange, it is possible to return Gc instance to
|
||||||
|
/// MicroPython.
|
||||||
|
pub fn leak(self) -> Gc<T> {
|
||||||
|
let inner = self.0;
|
||||||
|
core::mem::forget(self);
|
||||||
|
inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type-cast GcBox contents to a `dyn Trait` object.
|
||||||
|
macro_rules! coerce {
|
||||||
|
($t:path, $v:expr) => {
|
||||||
|
// SAFETY: we are just re-wrapping the pointer, so all safety requirements
|
||||||
|
// of `GcBox::from_raw` are upheld.
|
||||||
|
// Rust type system will not allow us to cast to a trait object that is not
|
||||||
|
// implemented by the type.
|
||||||
|
unsafe { GcBox::from_raw(GcBox::into_raw($v) as *mut dyn $t) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use coerce;
|
||||||
|
|
||||||
|
impl<T: ?Sized> Deref for GcBox<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.0.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> DerefMut for GcBox<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
// SAFETY: We are the sole owner of the allocated value, and we are borrowed
|
||||||
|
// mutably.
|
||||||
|
unsafe { Gc::as_mut(&mut self.0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ?Sized> Drop for GcBox<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let ptr = Gc::into_raw(self.0);
|
||||||
|
// SAFETY: We are the sole owner of the allocated value, and we are being
|
||||||
|
// dropped.
|
||||||
|
unsafe {
|
||||||
|
ptr::drop_in_place(ptr);
|
||||||
|
ffi::gc_free(ptr.cast());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use core::cell::Cell;
|
||||||
|
|
||||||
|
use crate::micropython::testutil::mpy_init;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct SignalDrop<'a>(&'a Cell<bool>);
|
||||||
|
|
||||||
|
impl Drop for SignalDrop<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Foo {
|
||||||
|
fn foo(&self) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Foo for SignalDrop<'_> {
|
||||||
|
fn foo(&self) -> i32 {
|
||||||
|
42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gc_nodrop() {
|
||||||
|
unsafe { mpy_init() };
|
||||||
|
|
||||||
|
let drop_signalled = Cell::new(false);
|
||||||
|
{
|
||||||
|
let _gc = Gc::new(SignalDrop(&drop_signalled)).unwrap();
|
||||||
|
}
|
||||||
|
assert!(!drop_signalled.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gcbox_drop() {
|
||||||
|
unsafe { mpy_init() };
|
||||||
|
|
||||||
|
let drop_signalled = Cell::new(false);
|
||||||
|
{
|
||||||
|
let _gcbox = GcBox::new(SignalDrop(&drop_signalled)).unwrap();
|
||||||
|
}
|
||||||
|
assert!(drop_signalled.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gc_raw_roundtrip() {
|
||||||
|
unsafe { mpy_init() };
|
||||||
|
|
||||||
|
let gc = Gc::new(42).unwrap();
|
||||||
|
let ptr = Gc::into_raw(gc);
|
||||||
|
let wrapped = unsafe { Gc::from_raw(ptr) };
|
||||||
|
let retrieved = Gc::into_raw(wrapped);
|
||||||
|
assert_eq!(ptr, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gcbox_raw_roundtrip() {
|
||||||
|
unsafe { mpy_init() };
|
||||||
|
|
||||||
|
let drop_signalled = Cell::new(false);
|
||||||
|
|
||||||
|
{
|
||||||
|
let gcbox = GcBox::new(SignalDrop(&drop_signalled)).unwrap();
|
||||||
|
assert!(!drop_signalled.get());
|
||||||
|
let ptr = GcBox::into_raw(gcbox);
|
||||||
|
assert!(!drop_signalled.get());
|
||||||
|
let wrapped = unsafe { GcBox::from_raw(ptr) };
|
||||||
|
assert!(!drop_signalled.get());
|
||||||
|
let retrieved = GcBox::into_raw(wrapped);
|
||||||
|
assert!(!drop_signalled.get());
|
||||||
|
assert_eq!(ptr, retrieved);
|
||||||
|
|
||||||
|
let _rewrapped = unsafe { GcBox::from_raw(ptr) };
|
||||||
|
}
|
||||||
|
assert!(drop_signalled.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_coerce() {
|
||||||
|
unsafe { mpy_init() };
|
||||||
|
|
||||||
|
let drop_signalled = Cell::new(false);
|
||||||
|
{
|
||||||
|
let gcbox = GcBox::new(SignalDrop(&drop_signalled)).unwrap();
|
||||||
|
let coerced: GcBox<dyn Foo> = coerce!(Foo, gcbox);
|
||||||
|
assert!(!drop_signalled.get());
|
||||||
|
assert_eq!(coerced.foo(), 42);
|
||||||
|
}
|
||||||
|
assert!(drop_signalled.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,7 +2,12 @@ use core::{convert::TryFrom, ptr};
|
|||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
use super::{ffi, gc::Gc, obj::Obj, runtime::catch_exception};
|
use super::{
|
||||||
|
ffi,
|
||||||
|
gc::{Gc, GcBox},
|
||||||
|
obj::Obj,
|
||||||
|
runtime::catch_exception,
|
||||||
|
};
|
||||||
|
|
||||||
pub type List = ffi::mp_obj_list_t;
|
pub type List = ffi::mp_obj_list_t;
|
||||||
|
|
||||||
@ -17,29 +22,29 @@ impl List {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_capacity(capacity: usize) -> Result<Gc<Self>, Error> {
|
pub fn with_capacity(capacity: usize) -> Result<GcBox<Self>, Error> {
|
||||||
// EXCEPTION: Will raise if allocation fails.
|
// EXCEPTION: Will raise if allocation fails.
|
||||||
catch_exception(|| unsafe {
|
catch_exception(|| unsafe {
|
||||||
let list = ffi::mp_obj_new_list(capacity, ptr::null_mut());
|
let list = ffi::mp_obj_new_list(capacity, ptr::null_mut());
|
||||||
// By default, the new list will have its len set to n. We want to preallocate
|
// By default, the new list will have its len set to n. We want to preallocate
|
||||||
// to a specific size and then use append() to add items, so we reset len to 0.
|
// to a specific size and then use append() to add items, so we reset len to 0.
|
||||||
ffi::mp_obj_list_set_len(list, 0);
|
ffi::mp_obj_list_set_len(list, 0);
|
||||||
Gc::from_raw(list.as_ptr().cast())
|
// SAFETY: list is freshly allocated so we are still its unique owner.
|
||||||
|
GcBox::from_raw(list.as_ptr().cast())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_iter<T, E>(iter: impl Iterator<Item = T>) -> Result<Gc<List>, Error>
|
pub fn from_iter<T, E>(iter: impl Iterator<Item = T>) -> Result<GcBox<List>, Error>
|
||||||
where
|
where
|
||||||
T: TryInto<Obj, Error = E>,
|
T: TryInto<Obj, Error = E>,
|
||||||
Error: From<E>,
|
Error: From<E>,
|
||||||
{
|
{
|
||||||
let max_size = iter.size_hint().1.unwrap_or(0);
|
let max_size = iter.size_hint().1.unwrap_or(0);
|
||||||
let mut gc_list = List::with_capacity(max_size)?;
|
let mut list = List::with_capacity(max_size)?;
|
||||||
let list = unsafe { Gc::as_mut(&mut gc_list) };
|
|
||||||
for value in iter {
|
for value in iter {
|
||||||
list.append(value.try_into()?)?;
|
list.append(value.try_into()?)?;
|
||||||
}
|
}
|
||||||
Ok(gc_list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal helper to get the `Obj` variant of this.
|
// Internal helper to get the `Obj` variant of this.
|
||||||
@ -155,7 +160,7 @@ mod tests {
|
|||||||
|
|
||||||
// create an upy list of 5 elements
|
// create an upy list of 5 elements
|
||||||
let vec: Vec<u8, 10> = (0..5).collect();
|
let vec: Vec<u8, 10> = (0..5).collect();
|
||||||
let list: Obj = List::from_iter(vec.iter().copied()).unwrap().into();
|
let list: Obj = List::from_iter(vec.iter().copied()).unwrap().leak().into();
|
||||||
|
|
||||||
// collect the elements into a Vec of maximum length 10, through an iterator
|
// collect the elements into a Vec of maximum length 10, through an iterator
|
||||||
let retrieved_vec: Vec<u8, 10> = IterBuf::new()
|
let retrieved_vec: Vec<u8, 10> = IterBuf::new()
|
||||||
@ -181,8 +186,7 @@ mod tests {
|
|||||||
unsafe { mpy_init() };
|
unsafe { mpy_init() };
|
||||||
|
|
||||||
let vec: Vec<u16, 17> = (0..17).collect();
|
let vec: Vec<u16, 17> = (0..17).collect();
|
||||||
let mut gc_list = List::from_iter(vec.iter().copied()).unwrap();
|
let mut list = List::from_iter(vec.iter().copied()).unwrap();
|
||||||
let list = unsafe { Gc::as_mut(&mut gc_list) };
|
|
||||||
|
|
||||||
for (i, value) in vec.iter().copied().enumerate() {
|
for (i, value) in vec.iter().copied().enumerate() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -197,7 +201,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let retrieved_vec: Vec<u16, 17> = IterBuf::new()
|
let retrieved_vec: Vec<u16, 17> = IterBuf::new()
|
||||||
.try_iterate(gc_list.into())
|
.try_iterate(list.leak().into())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(TryInto::try_into)
|
.map(TryInto::try_into)
|
||||||
.collect::<Result<Vec<u16, 17>, Error>>()
|
.collect::<Result<Vec<u16, 17>, Error>>()
|
||||||
@ -229,7 +233,7 @@ mod tests {
|
|||||||
let vec: Vec<u16, 5> = (0..5).collect();
|
let vec: Vec<u16, 5> = (0..5).collect();
|
||||||
let mut list = List::from_iter(vec.iter().copied()).unwrap();
|
let mut list = List::from_iter(vec.iter().copied()).unwrap();
|
||||||
|
|
||||||
let slice = unsafe { Gc::as_mut(&mut list).as_mut_slice() };
|
let slice = unsafe { list.as_mut_slice() };
|
||||||
assert_eq!(slice.len(), vec.len());
|
assert_eq!(slice.len(), vec.len());
|
||||||
assert_eq!(vec[0], TryInto::<u16>::try_into(slice[0]).unwrap());
|
assert_eq!(vec[0], TryInto::<u16>::try_into(slice[0]).unwrap());
|
||||||
|
|
||||||
@ -238,7 +242,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let retrieved_vec: Vec<u16, 5> = IterBuf::new()
|
let retrieved_vec: Vec<u16, 5> = IterBuf::new()
|
||||||
.try_iterate(list.into())
|
.try_iterate(list.leak().into())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(TryInto::try_into)
|
.map(TryInto::try_into)
|
||||||
.collect::<Result<Vec<u16, 5>, Error>>()
|
.collect::<Result<Vec<u16, 5>, Error>>()
|
||||||
|
Loading…
Reference in New Issue
Block a user