Skip to content
This repository was archived by the owner on Jun 10, 2024. It is now read-only.

Commit f682ccb

Browse files
authored
Merge pull request #195 from lumen/term/locked-heaps
Refactor to allow allocation in processes without mutable references
2 parents 9190a8a + a98dd57 commit f682ccb

26 files changed

+1191
-735
lines changed

liblumen_alloc/src/borrow/clone_to_process.rs

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use crate::erts::{AllocInProcess, Term};
1+
use core::alloc::{AllocErr, Layout};
2+
use core::mem;
3+
use core::ptr::NonNull;
4+
5+
use crate::erts::{self, HeapAlloc, HeapFragment, ProcessControlBlock, Term};
26

37
/// This trait represents cloning, like `Clone`, but specifically
48
/// in the context of terms which need to be cloned into the heap
@@ -15,7 +19,44 @@ use crate::erts::{AllocInProcess, Term};
1519
/// just be aware that any uses of `Clone` will allocate on the global heap
1620
pub trait CloneToProcess {
1721
/// Returns boxed copy of this value, performing any heap allocations
18-
/// using the process heap of `process`, or using heap fragments if
22+
/// using the process heap of `process`, possibly using heap fragments if
1923
/// there is not enough space for the cloned value
20-
fn clone_to_process<A: AllocInProcess>(&self, process: &mut A) -> Term;
24+
fn clone_to_process(&self, process: &ProcessControlBlock) -> Term {
25+
let mut heap = process.acquire_heap();
26+
match self.clone_to_heap(&mut heap) {
27+
Ok(term) => term,
28+
Err(_) => {
29+
drop(heap);
30+
let (term, mut frag) = self.clone_to_fragment().unwrap();
31+
process.attach_fragment(unsafe { frag.as_mut() });
32+
term
33+
}
34+
}
35+
}
36+
37+
/// Returns boxed copy of this value, performing any heap allocations
38+
/// using the given heap. If cloning requires allocation that exceeds
39+
/// the amount of memory available, this returns `Err(AllocErr)`, otherwise
40+
/// it returns `Ok(Term)`
41+
fn clone_to_heap<A: HeapAlloc>(&self, heap: &mut A) -> Result<Term, AllocErr>;
42+
43+
/// Returns boxed copy of this value and the heap fragment it was allocated into
44+
///
45+
/// If unable to allocate a heap fragment that fits this value, `Err(AllocErr)` is returned
46+
fn clone_to_fragment(&self) -> Result<(Term, NonNull<HeapFragment>), AllocErr> {
47+
let need = self.size_in_words();
48+
let layout = unsafe {
49+
let size = need * mem::size_of::<usize>();
50+
Layout::from_size_align_unchecked(size, mem::align_of::<Term>())
51+
};
52+
let mut frag = unsafe { HeapFragment::new(layout)? };
53+
let frag_ref = unsafe { frag.as_mut() };
54+
let term = self.clone_to_heap(frag_ref)?;
55+
Ok((term, frag))
56+
}
57+
58+
/// Returns the size in words needed to allocate this value
59+
fn size_in_words(&self) -> usize {
60+
erts::to_word_size(mem::size_of_val(self))
61+
}
2162
}

liblumen_alloc/src/erts/fragment.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use core::ptr::{self, NonNull};
33

44
use intrusive_collections::intrusive_adapter;
55
use intrusive_collections::{LinkedListLink, UnsafeRef};
6+
use liblumen_core::util::pointer::{distance_absolute, in_area};
67

78
use crate::std_alloc;
89

9-
use super::Term;
10+
use super::{HeapAlloc, Term};
1011

1112
// This adapter is used to track a list of heap fragments, attached to a process
1213
intrusive_adapter!(pub HeapFragmentAdapter = UnsafeRef<HeapFragment>: HeapFragment { link: LinkedListLink });
@@ -52,6 +53,8 @@ pub struct HeapFragment {
5253
pub link: LinkedListLink,
5354
// The memory region allocated for this fragment
5455
raw: RawFragment,
56+
// The amount of used memory in this fragment
57+
top: *mut u8,
5558
}
5659
impl HeapFragment {
5760
/// Returns the size (in bytes) of the fragment
@@ -80,11 +83,13 @@ impl HeapFragment {
8083
let align = layout.align();
8184
let ptr = std_alloc::alloc(full_layout)?.as_ptr() as *mut Self;
8285
let data = (ptr as *mut u8).offset(offset as isize);
86+
let top = data;
8387
ptr::write(
8488
ptr,
8589
Self {
8690
link: LinkedListLink::new(),
8791
raw: RawFragment { size, align, data },
92+
top,
8893
},
8994
);
9095
Ok(NonNull::new_unchecked(ptr))
@@ -105,3 +110,28 @@ impl Drop for HeapFragment {
105110
}
106111
}
107112
}
113+
impl HeapAlloc for HeapFragment {
114+
/// Perform a heap allocation.
115+
///
116+
/// If space on the process heap is not immediately available, then the allocation
117+
/// will be pushed into a heap fragment which will then be later moved on to the
118+
/// process heap during garbage collection
119+
unsafe fn alloc(&mut self, need: usize) -> Result<NonNull<Term>, AllocErr> {
120+
let base = self.raw.data as *mut Term;
121+
let top = self.top as *mut Term;
122+
let available = distance_absolute(top, base);
123+
if need > available {
124+
return Err(AllocErr);
125+
}
126+
127+
let new_top = base.offset(need as isize);
128+
debug_assert!(new_top <= self.raw.data.offset(self.raw.size as isize) as *mut Term);
129+
self.top = new_top as *mut u8;
130+
Ok(NonNull::new_unchecked(new_top))
131+
}
132+
133+
/// Returns true if the given pointer is owned by this process/heap
134+
fn is_owner<T>(&mut self, ptr: *const T) -> bool {
135+
in_area(ptr, self.raw.data, self.top)
136+
}
137+
}

0 commit comments

Comments
 (0)