#![deny(unsafe_op_in_unsafe_fn)] use crate::{roc_alloc, roc_dealloc, storage::Storage}; use core::{ cell::Cell, cmp::{self, Ordering}, fmt::Debug, mem, ops::Deref, ptr::{self, NonNull}, }; #[repr(C)] pub struct RocBox { contents: NonNull, } impl RocBox { pub fn new(contents: T) -> Self { let alignment = Self::alloc_alignment(); let bytes = mem::size_of::() + alignment; let ptr = unsafe { roc_alloc(bytes, alignment as u32) }; if ptr.is_null() { todo!("Call roc_panic with the info that an allocation failed."); } // Initialize the reference count. let refcount_one = Storage::new_reference_counted(); unsafe { ptr.cast::().write(refcount_one) }; let contents = unsafe { let contents_ptr = ptr.cast::().add(alignment).cast::(); core::ptr::write(contents_ptr, contents); // We already verified that the original alloc pointer was non-null, // and this one is the alloc pointer with `alignment` bytes added to it, // so it should be non-null too. NonNull::new_unchecked(contents_ptr) }; Self { contents } } /// # Safety /// /// The box must be unique in order to leak it safely pub unsafe fn leak(self) -> *mut T { let ptr = self.contents.as_ptr(); core::mem::forget(self); ptr } #[inline(always)] fn alloc_alignment() -> usize { mem::align_of::().max(mem::align_of::()) } pub fn into_inner(self) -> T { unsafe { ptr::read(self.contents.as_ptr()) } } fn storage(&self) -> &Cell { let alignment = Self::alloc_alignment(); unsafe { &*self .contents .as_ptr() .cast::() .sub(alignment) .cast::>() } } } impl Deref for RocBox { type Target = T; fn deref(&self) -> &Self::Target { unsafe { self.contents.as_ref() } } } impl PartialEq> for RocBox where T: PartialEq, { fn eq(&self, other: &RocBox) -> bool { self.deref() == other.deref() } } impl Eq for RocBox where T: Eq {} impl PartialOrd> for RocBox where T: PartialOrd, { fn partial_cmp(&self, other: &RocBox) -> Option { let self_contents = unsafe { self.contents.as_ref() }; let other_contents = unsafe { other.contents.as_ref() }; self_contents.partial_cmp(other_contents) } } impl Ord for RocBox where T: Ord, { fn cmp(&self, other: &Self) -> Ordering { let self_contents = unsafe { self.contents.as_ref() }; let other_contents = unsafe { other.contents.as_ref() }; self_contents.cmp(other_contents) } } impl core::hash::Hash for RocBox { fn hash(&self, state: &mut H) { self.contents.hash(state) } } impl Debug for RocBox where T: Debug, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.deref().fmt(f) } } impl Clone for RocBox { fn clone(&self) -> Self { let storage = self.storage(); let mut new_storage = storage.get(); // Increment the reference count if !new_storage.is_readonly() { new_storage.increment_reference_count(); storage.set(new_storage); } Self { contents: self.contents, } } } impl Drop for RocBox { fn drop(&mut self) { let storage = self.storage(); let contents = self.contents; // Decrease the list's reference count. let mut new_storage = storage.get(); let needs_dealloc = new_storage.decrease(); if needs_dealloc { unsafe { // Drop the stored contents. let contents_ptr = contents.as_ptr(); mem::drop::(ptr::read(contents_ptr)); let alignment = Self::alloc_alignment(); // Release the memory. roc_dealloc( contents.as_ptr().cast::().sub(alignment).cast(), alignment as u32, ); } } else if !new_storage.is_readonly() { // Write the storage back. storage.set(new_storage); } } }