use super::metadata::*;
use crate::plan::TransitiveClosure;
use crate::policy::space::CommonSpace;
use crate::policy::space::SFT;
use crate::util::conversions;
use crate::util::heap::layout::heap_layout::VMMap;
use crate::util::heap::PageResource;
use crate::util::malloc::*;
use crate::util::Address;
use crate::util::ObjectReference;
use crate::util::OpaquePointer;
use crate::vm::VMBinding;
use crate::vm::{ActivePlan, Collection, ObjectModel};
use crate::{policy::space::Space, util::heap::layout::vm_layout_constants::BYTES_IN_CHUNK};
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::{collections::HashSet, marker::PhantomData};
#[cfg(debug_assertions)]
use std::collections::HashMap;
#[cfg(debug_assertions)]
use std::sync::Mutex;
#[cfg(debug_assertions)]
const ASSERT_ALLOCATION: bool = false;
pub struct MallocSpace<VM: VMBinding> {
phantom: PhantomData<VM>,
active_bytes: AtomicUsize,
#[cfg(debug_assertions)]
active_mem: Mutex<HashMap<Address, usize>>,
}
impl<VM: VMBinding> SFT for MallocSpace<VM> {
fn name(&self) -> &str {
self.get_name()
}
fn is_live(&self, object: ObjectReference) -> bool {
is_marked(object)
}
fn is_movable(&self) -> bool {
false
}
#[cfg(feature = "sanity")]
fn is_sane(&self) -> bool {
true
}
fn initialize_header(&self, object: ObjectReference, _alloc: bool) {
trace!("initialize_header for object {}", object);
set_alloc_bit(object);
}
}
impl<VM: VMBinding> Space<VM> for MallocSpace<VM> {
fn as_space(&self) -> &dyn Space<VM> {
self
}
fn as_sft(&self) -> &(dyn SFT + Sync + 'static) {
self
}
fn get_page_resource(&self) -> &dyn PageResource<VM> {
unreachable!()
}
fn common(&self) -> &CommonSpace<VM> {
unreachable!()
}
unsafe fn unsafe_common_mut(&self) -> &mut CommonSpace<VM> {
unreachable!()
}
fn init(&mut self, _vm_map: &'static VMMap) {
}
fn release_multiple_pages(&mut self, _start: Address) {
unreachable!()
}
fn in_space(&self, object: ObjectReference) -> bool {
let ret = is_alloced_by_malloc(object);
#[cfg(debug_assertions)]
if ASSERT_ALLOCATION {
let addr = VM::VMObjectModel::object_start_ref(object);
let active_mem = self.active_mem.lock().unwrap();
if ret {
debug_assert!(
*active_mem.get(&addr).unwrap() != 0,
"active mem check failed for {} (object {}) - was freed",
addr,
object
);
} else {
debug_assert!(
(!active_mem.contains_key(&addr))
|| (active_mem.contains_key(&addr) && *active_mem.get(&addr).unwrap() == 0),
"mem check failed for {} (object {}): allocated = {}, size = {:?}",
addr,
object,
active_mem.contains_key(&addr),
if active_mem.contains_key(&addr) {
active_mem.get(&addr)
} else {
None
}
);
}
}
ret
}
fn address_in_space(&self, _start: Address) -> bool {
unreachable!("We do not know if an address is in malloc space. Use in_space() to check if an object is in malloc space.")
}
fn get_name(&self) -> &'static str {
"MallocSpace"
}
fn reserved_pages(&self) -> usize {
conversions::bytes_to_pages_up(self.active_bytes.load(Ordering::SeqCst))
}
unsafe fn release_all_chunks(&self) {
let mut released_chunks: HashSet<Address> = HashSet::new();
#[cfg(debug_assertions)]
let mut live_bytes = 0;
debug!(
"Used bytes before releasing: {}",
self.active_bytes.load(Ordering::Relaxed)
);
for chunk_start in ACTIVE_CHUNKS.read().unwrap().iter() {
debug!("Check active chunk {:?}", chunk_start);
let mut chunk_is_empty = true;
let mut address = *chunk_start;
let chunk_end = chunk_start.add(BYTES_IN_CHUNK);
while address < chunk_end {
trace!("Check address {}", address);
if is_alloced_object(address) {
let object = address.to_object_reference();
let obj_start = VM::VMObjectModel::object_start_ref(object);
let bytes = malloc_usable_size(obj_start.to_mut_ptr());
#[cfg(debug_assertions)]
if ASSERT_ALLOCATION {
debug_assert!(
self.active_mem.lock().unwrap().contains_key(&obj_start),
"Address {} with alloc bit is not in active_mem",
obj_start
);
debug_assert_eq!(self.active_mem.lock().unwrap().get(&obj_start), Some(&bytes), "Address {} size in active_mem does not match the size from malloc_usable_size", obj_start);
}
if !is_marked(object) {
trace!(
"Object {} has alloc bit but no mark bit, it is dead. ",
object
);
self.free(VM::VMObjectModel::object_start_ref(object));
trace!("free object {}", object);
unset_alloc_bit(object);
} else {
unset_mark_bit(object);
chunk_is_empty = false;
#[cfg(debug_assertions)]
{
live_bytes += malloc_usable_size(
VM::VMObjectModel::object_start_ref(object).to_mut_ptr(),
);
}
}
address += bytes;
} else {
address += VM::MIN_ALIGNMENT;
}
}
if chunk_is_empty {
debug!(
"Release malloc chunk {} to {}",
chunk_start,
*chunk_start + BYTES_IN_CHUNK
);
released_chunks.insert(*chunk_start);
}
}
debug!(
"Used bytes after releasing: {}",
self.active_bytes.load(Ordering::SeqCst)
);
#[cfg(debug_assertions)]
debug_assert_eq!(live_bytes, self.active_bytes.load(Ordering::SeqCst));
ACTIVE_CHUNKS.write().unwrap().retain(|c| {
debug!("Release malloc chunk {} to {}", *c, *c + BYTES_IN_CHUNK);
!released_chunks.contains(&*c)
});
}
}
impl<VM: VMBinding> MallocSpace<VM> {
pub fn new() -> Self {
MallocSpace {
phantom: PhantomData,
active_bytes: AtomicUsize::new(0),
#[cfg(debug_assertions)]
active_mem: Mutex::new(HashMap::new()),
}
}
pub fn alloc(&self, tls: OpaquePointer, size: usize) -> Address {
if VM::VMActivePlan::global().poll(false, self) {
VM::VMCollection::block_for_gc(tls);
return unsafe { Address::zero() };
}
let raw = unsafe { calloc(1, size) };
let address = Address::from_mut_ptr(raw);
if !address.is_zero() {
let actual_size = unsafe { malloc_usable_size(raw) };
if !is_meta_space_mapped(address) {
let chunk_start = conversions::chunk_align_down(address);
debug!(
"Add malloc chunk {} to {}",
chunk_start,
chunk_start + BYTES_IN_CHUNK
);
map_meta_space_for_chunk(chunk_start);
}
self.active_bytes.fetch_add(actual_size, Ordering::SeqCst);
#[cfg(debug_assertions)]
if ASSERT_ALLOCATION {
debug_assert!(actual_size != 0);
self.active_mem.lock().unwrap().insert(address, actual_size);
}
}
address
}
pub fn free(&self, addr: Address) {
let ptr = addr.to_mut_ptr();
let bytes = unsafe { malloc_usable_size(ptr) };
trace!("Free memory {:?}", ptr);
unsafe {
free(ptr);
}
self.active_bytes.fetch_sub(bytes, Ordering::SeqCst);
#[cfg(debug_assertions)]
if ASSERT_ALLOCATION {
self.active_mem.lock().unwrap().insert(addr, 0).unwrap();
}
}
#[inline]
pub fn trace_object<T: TransitiveClosure>(
&self,
trace: &mut T,
object: ObjectReference,
) -> ObjectReference {
if object.is_null() {
return object;
}
let address = object.to_address();
assert!(
self.in_space(object),
"Cannot mark an object {} that was not alloced by malloc.",
address,
);
if !is_marked(object) {
set_mark_bit(object);
trace.process_node(object);
}
object
}
}
impl<VM: VMBinding> Default for MallocSpace<VM> {
fn default() -> Self {
Self::new()
}
}