diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/dlmalloc/src/dlmalloc.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/dlmalloc/src/dlmalloc.rs')
-rw-r--r-- | vendor/dlmalloc/src/dlmalloc.rs | 1829 |
1 files changed, 1829 insertions, 0 deletions
diff --git a/vendor/dlmalloc/src/dlmalloc.rs b/vendor/dlmalloc/src/dlmalloc.rs new file mode 100644 index 000000000..a4b4b71b4 --- /dev/null +++ b/vendor/dlmalloc/src/dlmalloc.rs @@ -0,0 +1,1829 @@ +// This is a version of dlmalloc.c ported to Rust. You can find the original +// source at ftp://g.oswego.edu/pub/misc/malloc.c +// +// The original source was written by Doug Lea and released to the public domain + +use core::cmp; +use core::mem; +use core::ptr; + +use Allocator; + +pub struct Dlmalloc<A> { + smallmap: u32, + treemap: u32, + smallbins: [*mut Chunk; (NSMALLBINS + 1) * 2], + treebins: [*mut TreeChunk; NTREEBINS], + dvsize: usize, + topsize: usize, + dv: *mut Chunk, + top: *mut Chunk, + footprint: usize, + max_footprint: usize, + seg: Segment, + trim_check: usize, + least_addr: *mut u8, + release_checks: usize, + system_allocator: A, +} +unsafe impl<A: Send> Send for Dlmalloc<A> {} + +// TODO: document this +const NSMALLBINS: usize = 32; +const NTREEBINS: usize = 32; +const SMALLBIN_SHIFT: usize = 3; +const TREEBIN_SHIFT: usize = 8; + +// TODO: runtime configurable? documentation? +const DEFAULT_GRANULARITY: usize = 64 * 1024; +const DEFAULT_TRIM_THRESHOLD: usize = 2 * 1024 * 1024; +const MAX_RELEASE_CHECK_RATE: usize = 4095; + +#[repr(C)] +struct Chunk { + prev_foot: usize, + head: usize, + prev: *mut Chunk, + next: *mut Chunk, +} + +#[repr(C)] +struct TreeChunk { + chunk: Chunk, + child: [*mut TreeChunk; 2], + parent: *mut TreeChunk, + index: u32, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct Segment { + base: *mut u8, + size: usize, + next: *mut Segment, + flags: u32, +} + +fn align_up(a: usize, alignment: usize) -> usize { + debug_assert!(alignment.is_power_of_two()); + (a + (alignment - 1)) & !(alignment - 1) +} + +fn left_bits(x: u32) -> u32 { + (x << 1) | (!(x << 1)).wrapping_add(1) +} + +fn least_bit(x: u32) -> u32 { + x & (!x + 1) +} + +fn leftshift_for_tree_index(x: u32) -> u32 { + let x = x as usize; + if x == NTREEBINS - 1 { + 0 + } else { + (mem::size_of::<usize>() * 8 - 1 - ((x >> 1) + TREEBIN_SHIFT - 2)) as u32 + } +} + +impl<A> Dlmalloc<A> { + pub const fn new(system_allocator: A) -> Dlmalloc<A> { + Dlmalloc { + smallmap: 0, + treemap: 0, + smallbins: [0 as *mut _; (NSMALLBINS + 1) * 2], + treebins: [0 as *mut _; NTREEBINS], + dvsize: 0, + topsize: 0, + dv: 0 as *mut _, + top: 0 as *mut _, + footprint: 0, + max_footprint: 0, + seg: Segment { + base: 0 as *mut _, + size: 0, + next: 0 as *mut _, + flags: 0, + }, + trim_check: 0, + least_addr: 0 as *mut _, + release_checks: 0, + system_allocator, + } + } +} + +impl<A: Allocator> Dlmalloc<A> { + // TODO: can we get rid of this? + pub fn malloc_alignment(&self) -> usize { + mem::size_of::<usize>() * 2 + } + + // TODO: dox + fn chunk_overhead(&self) -> usize { + mem::size_of::<usize>() + } + + fn mmap_chunk_overhead(&self) -> usize { + 2 * mem::size_of::<usize>() + } + + // TODO: dox + fn min_large_size(&self) -> usize { + 1 << TREEBIN_SHIFT + } + + // TODO: dox + fn max_small_size(&self) -> usize { + self.min_large_size() - 1 + } + + // TODO: dox + fn max_small_request(&self) -> usize { + self.max_small_size() - (self.malloc_alignment() - 1) - self.chunk_overhead() + } + + // TODO: dox + fn min_chunk_size(&self) -> usize { + align_up(mem::size_of::<Chunk>(), self.malloc_alignment()) + } + + // TODO: dox + fn min_request(&self) -> usize { + self.min_chunk_size() - self.chunk_overhead() - 1 + } + + // TODO: dox + fn max_request(&self) -> usize { + // min_sys_alloc_space: the largest `X` such that + // pad_request(X - 1) -- minus 1, because requests of exactly + // `max_request` will not be honored + // + self.top_foot_size() + // + self.malloc_alignment() + // + DEFAULT_GRANULARITY + // == + // usize::MAX + let min_sys_alloc_space = + ((!0 - (DEFAULT_GRANULARITY + self.top_foot_size() + self.malloc_alignment()) + 1) + & !self.malloc_alignment()) + - self.chunk_overhead() + + 1; + + cmp::min((!self.min_chunk_size() + 1) << 2, min_sys_alloc_space) + } + + fn pad_request(&self, amt: usize) -> usize { + align_up(amt + self.chunk_overhead(), self.malloc_alignment()) + } + + fn small_index(&self, size: usize) -> u32 { + (size >> SMALLBIN_SHIFT) as u32 + } + + fn small_index2size(&self, idx: u32) -> usize { + (idx as usize) << SMALLBIN_SHIFT + } + + fn is_small(&self, s: usize) -> bool { + s >> SMALLBIN_SHIFT < NSMALLBINS + } + + fn is_aligned(&self, a: usize) -> bool { + a & (self.malloc_alignment() - 1) == 0 + } + + fn align_offset(&self, addr: *mut u8) -> usize { + self.align_offset_usize(addr as usize) + } + + fn align_offset_usize(&self, addr: usize) -> usize { + align_up(addr, self.malloc_alignment()) - (addr as usize) + } + + fn top_foot_size(&self) -> usize { + self.align_offset_usize(Chunk::mem_offset() as usize) + + self.pad_request(mem::size_of::<Segment>()) + + self.min_chunk_size() + } + + fn mmap_foot_pad(&self) -> usize { + 4 * mem::size_of::<usize>() + } + + fn align_as_chunk(&self, ptr: *mut u8) -> *mut Chunk { + unsafe { + let chunk = Chunk::to_mem(ptr as *mut Chunk); + ptr.offset(self.align_offset(chunk) as isize) as *mut Chunk + } + } + + fn request2size(&self, req: usize) -> usize { + if req < self.min_request() { + self.min_chunk_size() + } else { + self.pad_request(req) + } + } + + unsafe fn overhead_for(&self, p: *mut Chunk) -> usize { + if Chunk::mmapped(p) { + self.mmap_chunk_overhead() + } else { + self.chunk_overhead() + } + } + + pub unsafe fn calloc_must_clear(&self, ptr: *mut u8) -> bool { + !self.system_allocator.allocates_zeros() || !Chunk::mmapped(Chunk::from_mem(ptr)) + } + + pub unsafe fn malloc(&mut self, size: usize) -> *mut u8 { + self.check_malloc_state(); + + let nb; + if size <= self.max_small_request() { + nb = self.request2size(size); + let mut idx = self.small_index(nb); + let smallbits = self.smallmap >> idx; + + // Check the bin for `idx` (the lowest bit) but also check the next + // bin up to use that to satisfy our request, if needed. + if smallbits & 0b11 != 0 { + // If our the lowest bit, our `idx`, is unset then bump up the + // index as we'll be using the next bucket up. + idx += !smallbits & 1; + + let b = self.smallbin_at(idx); + let p = (*b).prev; + self.unlink_first_small_chunk(b, p, idx); + let smallsize = self.small_index2size(idx); + Chunk::set_inuse_and_pinuse(p, smallsize); + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, nb); + return ret; + } + + if nb > self.dvsize { + // If there's some other bin with some memory, then we just use + // the next smallest bin + if smallbits != 0 { + let leftbits = (smallbits << idx) & left_bits(1 << idx); + let leastbit = least_bit(leftbits); + let i = leastbit.trailing_zeros(); + let b = self.smallbin_at(i); + let p = (*b).prev; + debug_assert_eq!(Chunk::size(p), self.small_index2size(i)); + self.unlink_first_small_chunk(b, p, i); + let smallsize = self.small_index2size(i); + let rsize = smallsize - nb; + if mem::size_of::<usize>() != 4 && rsize < self.min_chunk_size() { + Chunk::set_inuse_and_pinuse(p, smallsize); + } else { + Chunk::set_size_and_pinuse_of_inuse_chunk(p, nb); + let r = Chunk::plus_offset(p, nb); + Chunk::set_size_and_pinuse_of_free_chunk(r, rsize); + self.replace_dv(r, rsize); + } + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, nb); + return ret; + } else if self.treemap != 0 { + let mem = self.tmalloc_small(nb); + if !mem.is_null() { + self.check_malloced_chunk(mem, nb); + self.check_malloc_state(); + return mem; + } + } + } + } else if size >= self.max_request() { + // TODO: translate this to unsupported + return ptr::null_mut(); + } else { + nb = self.pad_request(size); + if self.treemap != 0 { + let mem = self.tmalloc_large(nb); + if !mem.is_null() { + self.check_malloced_chunk(mem, nb); + self.check_malloc_state(); + return mem; + } + } + } + + // use the `dv` node if we can, splitting it if necessary or otherwise + // exhausting the entire chunk + if nb <= self.dvsize { + let rsize = self.dvsize - nb; + let p = self.dv; + if rsize >= self.min_chunk_size() { + self.dv = Chunk::plus_offset(p, nb); + self.dvsize = rsize; + let r = self.dv; + Chunk::set_size_and_pinuse_of_free_chunk(r, rsize); + Chunk::set_size_and_pinuse_of_inuse_chunk(p, nb); + } else { + let dvs = self.dvsize; + self.dvsize = 0; + self.dv = ptr::null_mut(); + Chunk::set_inuse_and_pinuse(p, dvs); + } + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, nb); + self.check_malloc_state(); + return ret; + } + + // Split the top node if we can + if nb < self.topsize { + self.topsize -= nb; + let rsize = self.topsize; + let p = self.top; + self.top = Chunk::plus_offset(p, nb); + let r = self.top; + (*r).head = rsize | PINUSE; + Chunk::set_size_and_pinuse_of_inuse_chunk(p, nb); + self.check_top_chunk(self.top); + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, nb); + self.check_malloc_state(); + return ret; + } + + self.sys_alloc(nb) + } + + /// allocates system resources + unsafe fn sys_alloc(&mut self, size: usize) -> *mut u8 { + self.check_malloc_state(); + // keep in sync with max_request + let asize = align_up( + size + self.top_foot_size() + self.malloc_alignment(), + DEFAULT_GRANULARITY, + ); + + let (tbase, tsize, flags) = self.system_allocator.alloc(asize); + if tbase.is_null() { + return tbase; + } + + self.footprint += tsize; + self.max_footprint = cmp::max(self.max_footprint, self.footprint); + + if self.top.is_null() { + if self.least_addr.is_null() || tbase < self.least_addr { + self.least_addr = tbase; + } + self.seg.base = tbase; + self.seg.size = tsize; + self.seg.flags = flags; + self.release_checks = MAX_RELEASE_CHECK_RATE; + self.init_bins(); + let tsize = tsize - self.top_foot_size(); + self.init_top(tbase as *mut Chunk, tsize); + // let mn = Chunk::next(Chunk::from_mem(self as *mut _ as *mut u8)); + // let top_foot_size = self.top_foot_size(); + // self.init_top(mn, tbase as usize + tsize - mn as usize - top_foot_size); + } else { + let mut sp = &mut self.seg as *mut Segment; + while !sp.is_null() && tbase != Segment::top(sp) { + sp = (*sp).next; + } + if !sp.is_null() + && !Segment::is_extern(sp) + && Segment::sys_flags(sp) == flags + && Segment::holds(sp, self.top as *mut u8) + { + (*sp).size += tsize; + let ptr = self.top; + let size = self.topsize + tsize; + self.init_top(ptr, size); + } else { + self.least_addr = cmp::min(tbase, self.least_addr); + let mut sp = &mut self.seg as *mut Segment; + while !sp.is_null() && (*sp).base != tbase.offset(tsize as isize) { + sp = (*sp).next; + } + if !sp.is_null() && !Segment::is_extern(sp) && Segment::sys_flags(sp) == flags { + let oldbase = (*sp).base; + (*sp).base = tbase; + (*sp).size += tsize; + return self.prepend_alloc(tbase, oldbase, size); + } else { + self.add_segment(tbase, tsize, flags); + } + } + } + + if size < self.topsize { + self.topsize -= size; + let rsize = self.topsize; + let p = self.top; + self.top = Chunk::plus_offset(p, size); + let r = self.top; + (*r).head = rsize | PINUSE; + Chunk::set_size_and_pinuse_of_inuse_chunk(p, size); + let ret = Chunk::to_mem(p); + self.check_top_chunk(self.top); + self.check_malloced_chunk(ret, size); + self.check_malloc_state(); + return ret; + } + + return ptr::null_mut(); + } + + pub unsafe fn realloc(&mut self, oldmem: *mut u8, bytes: usize) -> *mut u8 { + if bytes >= self.max_request() { + return ptr::null_mut(); + } + let nb = self.request2size(bytes); + let oldp = Chunk::from_mem(oldmem); + let newp = self.try_realloc_chunk(oldp, nb, true); + if !newp.is_null() { + self.check_inuse_chunk(newp); + return Chunk::to_mem(newp); + } + let ptr = self.malloc(bytes); + if !ptr.is_null() { + let oc = Chunk::size(oldp) - self.overhead_for(oldp); + ptr::copy_nonoverlapping(oldmem, ptr, cmp::min(oc, bytes)); + self.free(oldmem); + } + return ptr; + } + + unsafe fn try_realloc_chunk(&mut self, p: *mut Chunk, nb: usize, can_move: bool) -> *mut Chunk { + let oldsize = Chunk::size(p); + let next = Chunk::plus_offset(p, oldsize); + + if Chunk::mmapped(p) { + self.mmap_resize(p, nb, can_move) + } else if oldsize >= nb { + let rsize = oldsize - nb; + if rsize >= self.min_chunk_size() { + let r = Chunk::plus_offset(p, nb); + Chunk::set_inuse(p, nb); + Chunk::set_inuse(r, rsize); + self.dispose_chunk(r, rsize); + } + p + } else if next == self.top { + // extend into top + if oldsize + self.topsize <= nb { + return ptr::null_mut(); + } + let newsize = oldsize + self.topsize; + let newtopsize = newsize - nb; + let newtop = Chunk::plus_offset(p, nb); + Chunk::set_inuse(p, nb); + (*newtop).head = newtopsize | PINUSE; + self.top = newtop; + self.topsize = newtopsize; + p + } else if next == self.dv { + // extend into dv + let dvs = self.dvsize; + if oldsize + dvs < nb { + return ptr::null_mut(); + } + let dsize = oldsize + dvs - nb; + if dsize >= self.min_chunk_size() { + let r = Chunk::plus_offset(p, nb); + let n = Chunk::plus_offset(r, dsize); + Chunk::set_inuse(p, nb); + Chunk::set_size_and_pinuse_of_free_chunk(r, dsize); + Chunk::clear_pinuse(n); + self.dvsize = dsize; + self.dv = r; + } else { + // exhaust dv + let newsize = oldsize + dvs; + Chunk::set_inuse(p, newsize); + self.dvsize = 0; + self.dv = ptr::null_mut(); + } + return p; + } else if !Chunk::cinuse(next) { + // extend into the next free chunk + let nextsize = Chunk::size(next); + if oldsize + nextsize < nb { + return ptr::null_mut(); + } + let rsize = oldsize + nextsize - nb; + self.unlink_chunk(next, nextsize); + if rsize < self.min_chunk_size() { + let newsize = oldsize + nextsize; + Chunk::set_inuse(p, newsize); + } else { + let r = Chunk::plus_offset(p, nb); + Chunk::set_inuse(p, nb); + Chunk::set_inuse(r, rsize); + self.dispose_chunk(r, rsize); + } + p + } else { + ptr::null_mut() + } + } + + unsafe fn mmap_resize(&mut self, oldp: *mut Chunk, nb: usize, can_move: bool) -> *mut Chunk { + let oldsize = Chunk::size(oldp); + // Can't shrink mmap regions below a small size + if self.is_small(nb) { + return ptr::null_mut(); + } + + // Keep the old chunk if it's big enough but not too big + if oldsize >= nb + mem::size_of::<usize>() && (oldsize - nb) <= (DEFAULT_GRANULARITY << 1) { + return oldp; + } + + let offset = (*oldp).prev_foot; + let oldmmsize = oldsize + offset + self.mmap_foot_pad(); + let newmmsize = + self.mmap_align(nb + 6 * mem::size_of::<usize>() + self.malloc_alignment() - 1); + let ptr = self.system_allocator.remap( + (oldp as *mut u8).offset(-(offset as isize)), + oldmmsize, + newmmsize, + can_move, + ); + if ptr.is_null() { + return ptr::null_mut(); + } + let newp = ptr.offset(offset as isize) as *mut Chunk; + let psize = newmmsize - offset - self.mmap_foot_pad(); + (*newp).head = psize; + (*Chunk::plus_offset(newp, psize)).head = Chunk::fencepost_head(); + (*Chunk::plus_offset(newp, psize + mem::size_of::<usize>())).head = 0; + self.least_addr = cmp::min(ptr, self.least_addr); + self.footprint = self.footprint + newmmsize - oldmmsize; + self.max_footprint = cmp::max(self.max_footprint, self.footprint); + self.check_mmapped_chunk(newp); + return newp; + } + + fn mmap_align(&self, a: usize) -> usize { + align_up(a, self.system_allocator.page_size()) + } + + // Only call this with power-of-two alignment and alignment > + // `self.malloc_alignment()` + pub unsafe fn memalign(&mut self, mut alignment: usize, bytes: usize) -> *mut u8 { + if alignment < self.min_chunk_size() { + alignment = self.min_chunk_size(); + } + if bytes >= self.max_request() - alignment { + return ptr::null_mut(); + } + let nb = self.request2size(bytes); + let req = nb + alignment + self.min_chunk_size() - self.chunk_overhead(); + let mem = self.malloc(req); + if mem.is_null() { + return mem; + } + let mut p = Chunk::from_mem(mem); + if mem as usize & (alignment - 1) != 0 { + // Here we find an aligned sopt inside the chunk. Since we need to + // give back leading space in a chunk of at least `min_chunk_size`, + // if the first calculation places us at a spot with less than + // `min_chunk_size` leader we can move to the next aligned spot. + // we've allocated enough total room so that this is always possible + let br = + Chunk::from_mem(((mem as usize + alignment - 1) & (!alignment + 1)) as *mut u8); + let pos = if (br as usize - p as usize) > self.min_chunk_size() { + br as *mut u8 + } else { + (br as *mut u8).offset(alignment as isize) + }; + let newp = pos as *mut Chunk; + let leadsize = pos as usize - p as usize; + let newsize = Chunk::size(p) - leadsize; + + // for mmapped chunks just adjust the offset + if Chunk::mmapped(p) { + (*newp).prev_foot = (*p).prev_foot + leadsize; + (*newp).head = newsize; + } else { + // give back the leader, use the rest + Chunk::set_inuse(newp, newsize); + Chunk::set_inuse(p, leadsize); + self.dispose_chunk(p, leadsize); + } + p = newp; + } + + // give back spare room at the end + if !Chunk::mmapped(p) { + let size = Chunk::size(p); + if size > nb + self.min_chunk_size() { + let remainder_size = size - nb; + let remainder = Chunk::plus_offset(p, nb); + Chunk::set_inuse(p, nb); + Chunk::set_inuse(remainder, remainder_size); + self.dispose_chunk(remainder, remainder_size); + } + } + + let mem = Chunk::to_mem(p); + debug_assert!(Chunk::size(p) >= nb); + debug_assert_eq!(align_up(mem as usize, alignment), mem as usize); + self.check_inuse_chunk(p); + return mem; + } + + // consolidate and bin a chunk, differs from exported versions of free + // mainly in that the chunk need not be marked as inuse + unsafe fn dispose_chunk(&mut self, mut p: *mut Chunk, mut psize: usize) { + let next = Chunk::plus_offset(p, psize); + if !Chunk::pinuse(p) { + let prevsize = (*p).prev_foot; + if Chunk::mmapped(p) { + psize += prevsize + self.mmap_foot_pad(); + if self + .system_allocator + .free((p as *mut u8).offset(-(prevsize as isize)), psize) + { + self.footprint -= psize; + } + return; + } + let prev = Chunk::minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if p != self.dv { + self.unlink_chunk(p, prevsize); + } else if (*next).head & INUSE == INUSE { + self.dvsize = psize; + Chunk::set_free_with_pinuse(p, psize, next); + return; + } + } + + if !Chunk::cinuse(next) { + // consolidate forward + if next == self.top { + self.topsize += psize; + let tsize = self.topsize; + self.top = p; + (*p).head = tsize | PINUSE; + if p == self.dv { + self.dv = ptr::null_mut(); + self.dvsize = 0; + } + return; + } else if next == self.dv { + self.dvsize += psize; + let dsize = self.dvsize; + self.dv = p; + Chunk::set_size_and_pinuse_of_free_chunk(p, dsize); + return; + } else { + let nsize = Chunk::size(next); + psize += nsize; + self.unlink_chunk(next, nsize); + Chunk::set_size_and_pinuse_of_free_chunk(p, psize); + if p == self.dv { + self.dvsize = psize; + return; + } + } + } else { + Chunk::set_free_with_pinuse(p, psize, next); + } + self.insert_chunk(p, psize); + } + + unsafe fn init_top(&mut self, ptr: *mut Chunk, size: usize) { + let offset = self.align_offset(Chunk::to_mem(ptr)); + let p = Chunk::plus_offset(ptr, offset); + let size = size - offset; + + self.top = p; + self.topsize = size; + (*p).head = size | PINUSE; + (*Chunk::plus_offset(p, size)).head = self.top_foot_size(); + self.trim_check = DEFAULT_TRIM_THRESHOLD; + } + + unsafe fn init_bins(&mut self) { + for i in 0..NSMALLBINS as u32 { + let bin = self.smallbin_at(i); + (*bin).next = bin; + (*bin).prev = bin; + } + } + + unsafe fn prepend_alloc(&mut self, newbase: *mut u8, oldbase: *mut u8, size: usize) -> *mut u8 { + let p = self.align_as_chunk(newbase); + let mut oldfirst = self.align_as_chunk(oldbase); + let psize = oldfirst as usize - p as usize; + let q = Chunk::plus_offset(p, size); + let mut qsize = psize - size; + Chunk::set_size_and_pinuse_of_inuse_chunk(p, size); + + debug_assert!(oldfirst > q); + debug_assert!(Chunk::pinuse(oldfirst)); + debug_assert!(qsize >= self.min_chunk_size()); + + // consolidate the remainder with the first chunk of the old base + if oldfirst == self.top { + self.topsize += qsize; + let tsize = self.topsize; + self.top = q; + (*q).head = tsize | PINUSE; + self.check_top_chunk(q); + } else if oldfirst == self.dv { + self.dvsize += qsize; + let dsize = self.dvsize; + self.dv = q; + Chunk::set_size_and_pinuse_of_free_chunk(q, dsize); + } else { + if !Chunk::inuse(oldfirst) { + let nsize = Chunk::size(oldfirst); + self.unlink_chunk(oldfirst, nsize); + oldfirst = Chunk::plus_offset(oldfirst, nsize); + qsize += nsize; + } + Chunk::set_free_with_pinuse(q, qsize, oldfirst); + self.insert_chunk(q, qsize); + self.check_free_chunk(q); + } + + let ret = Chunk::to_mem(p); + self.check_malloced_chunk(ret, size); + self.check_malloc_state(); + return ret; + } + + // add a segment to hold a new noncontiguous region + unsafe fn add_segment(&mut self, tbase: *mut u8, tsize: usize, flags: u32) { + // TODO: what in the world is this function doing + + // Determine locations and sizes of segment, fenceposts, and the old top + let old_top = self.top as *mut u8; + let oldsp = self.segment_holding(old_top); + let old_end = Segment::top(oldsp); + let ssize = self.pad_request(mem::size_of::<Segment>()); + let offset = ssize + mem::size_of::<usize>() * 4 + self.malloc_alignment() - 1; + let rawsp = old_end.offset(-(offset as isize)); + let offset = self.align_offset(Chunk::to_mem(rawsp as *mut Chunk)); + let asp = rawsp.offset(offset as isize); + let csp = if asp < old_top.offset(self.min_chunk_size() as isize) { + old_top + } else { + asp + }; + let sp = csp as *mut Chunk; + let ss = Chunk::to_mem(sp) as *mut Segment; + let tnext = Chunk::plus_offset(sp, ssize); + let mut p = tnext; + let mut nfences = 0; + + // reset the top to our new space + let size = tsize - self.top_foot_size(); + self.init_top(tbase as *mut Chunk, size); + + // set up our segment record + debug_assert!(self.is_aligned(ss as usize)); + Chunk::set_size_and_pinuse_of_inuse_chunk(sp, ssize); + *ss = self.seg; // push our current record + self.seg.base = tbase; + self.seg.size = tsize; + self.seg.flags = flags; + self.seg.next = ss; + + // insert trailing fences + loop { + let nextp = Chunk::plus_offset(p, mem::size_of::<usize>()); + (*p).head = Chunk::fencepost_head(); + nfences += 1; + if (&(*nextp).head as *const usize as *mut u8) < old_end { + p = nextp; + } else { + break; + } + } + debug_assert!(nfences >= 2); + + // insert the rest of the old top into a bin as an ordinary free chunk + if csp != old_top { + let q = old_top as *mut Chunk; + let psize = csp as usize - old_top as usize; + let tn = Chunk::plus_offset(q, psize); + Chunk::set_free_with_pinuse(q, psize, tn); + self.insert_chunk(q, psize); + } + + self.check_top_chunk(self.top); + self.check_malloc_state(); + } + + unsafe fn segment_holding(&self, ptr: *mut u8) -> *mut Segment { + let mut sp = &self.seg as *const Segment as *mut Segment; + while !sp.is_null() { + if (*sp).base <= ptr && ptr < Segment::top(sp) { + return sp; + } + sp = (*sp).next; + } + ptr::null_mut() + } + + unsafe fn tmalloc_small(&mut self, size: usize) -> *mut u8 { + let leastbit = least_bit(self.treemap); + let i = leastbit.trailing_zeros(); + let mut v = *self.treebin_at(i); + let mut t = v; + let mut rsize = Chunk::size(TreeChunk::chunk(t)) - size; + + loop { + t = TreeChunk::leftmost_child(t); + if t.is_null() { + break; + } + let trem = Chunk::size(TreeChunk::chunk(t)) - size; + if trem < rsize { + rsize = trem; + v = t; + } + } + + let vc = TreeChunk::chunk(v); + let r = Chunk::plus_offset(vc, size) as *mut TreeChunk; + debug_assert_eq!(Chunk::size(vc), rsize + size); + self.unlink_large_chunk(v); + if rsize < self.min_chunk_size() { + Chunk::set_inuse_and_pinuse(vc, rsize + size); + } else { + let rc = TreeChunk::chunk(r); + Chunk::set_size_and_pinuse_of_inuse_chunk(vc, size); + Chunk::set_size_and_pinuse_of_free_chunk(rc, rsize); + self.replace_dv(rc, rsize); + } + Chunk::to_mem(vc) + } + + unsafe fn tmalloc_large(&mut self, size: usize) -> *mut u8 { + let mut v = ptr::null_mut(); + let mut rsize = !size + 1; + let idx = self.compute_tree_index(size); + let mut t = *self.treebin_at(idx); + if !t.is_null() { + // Traverse thre tree for this bin looking for a node with size + // equal to the `size` above. + let mut sizebits = size << leftshift_for_tree_index(idx); + // Keep track of the deepest untaken right subtree + let mut rst = ptr::null_mut(); + loop { + let csize = Chunk::size(TreeChunk::chunk(t)); + if csize >= size && csize - size < rsize { + v = t; + rsize = csize - size; + if rsize == 0 { + break; + } + } + let rt = (*t).child[1]; + t = (*t).child[(sizebits >> (mem::size_of::<usize>() * 8 - 1)) & 1]; + if !rt.is_null() && rt != t { + rst = rt; + } + if t.is_null() { + // Reset `t` to the least subtree holding sizes greater than + // the `size` above, breaking out + t = rst; + break; + } + sizebits <<= 1; + } + } + + // Set t to the root of the next non-empty treebin + if t.is_null() && v.is_null() { + let leftbits = left_bits(1 << idx) & self.treemap; + if leftbits != 0 { + let leastbit = least_bit(leftbits); + let i = leastbit.trailing_zeros(); + t = *self.treebin_at(i); + } + } + + // Find the smallest of this tree or subtree + while !t.is_null() { + let csize = Chunk::size(TreeChunk::chunk(t)); + if csize >= size && csize - size < rsize { + rsize = csize - size; + v = t; + } + t = TreeChunk::leftmost_child(t); + } + + // If dv is a better fit, then return null so malloc will use it + if v.is_null() || (self.dvsize >= size && !(rsize < self.dvsize - size)) { + return ptr::null_mut(); + } + + let vc = TreeChunk::chunk(v); + let r = Chunk::plus_offset(vc, size); + debug_assert_eq!(Chunk::size(vc), rsize + size); + self.unlink_large_chunk(v); + if rsize < self.min_chunk_size() { + Chunk::set_inuse_and_pinuse(vc, rsize + size); + } else { + Chunk::set_size_and_pinuse_of_inuse_chunk(vc, size); + Chunk::set_size_and_pinuse_of_free_chunk(r, rsize); + self.insert_chunk(r, rsize); + } + Chunk::to_mem(vc) + } + + unsafe fn smallbin_at(&mut self, idx: u32) -> *mut Chunk { + debug_assert!(((idx * 2) as usize) < self.smallbins.len()); + &mut *self.smallbins.get_unchecked_mut((idx as usize) * 2) as *mut *mut Chunk as *mut Chunk + } + + unsafe fn treebin_at(&mut self, idx: u32) -> *mut *mut TreeChunk { + debug_assert!((idx as usize) < self.treebins.len()); + &mut *self.treebins.get_unchecked_mut(idx as usize) + } + + fn compute_tree_index(&self, size: usize) -> u32 { + let x = size >> TREEBIN_SHIFT; + if x == 0 { + 0 + } else if x > 0xffff { + NTREEBINS as u32 - 1 + } else { + let k = mem::size_of_val(&x) * 8 - 1 - (x.leading_zeros() as usize); + ((k << 1) + (size >> (k + TREEBIN_SHIFT - 1) & 1)) as u32 + } + } + + unsafe fn unlink_first_small_chunk(&mut self, head: *mut Chunk, next: *mut Chunk, idx: u32) { + let ptr = (*next).prev; + debug_assert!(next != head); + debug_assert!(next != ptr); + debug_assert_eq!(Chunk::size(next), self.small_index2size(idx)); + if head == ptr { + self.clear_smallmap(idx); + } else { + (*ptr).next = head; + (*head).prev = ptr; + } + } + + unsafe fn replace_dv(&mut self, chunk: *mut Chunk, size: usize) { + let dvs = self.dvsize; + debug_assert!(self.is_small(dvs)); + if dvs != 0 { + let dv = self.dv; + self.insert_small_chunk(dv, dvs); + } + self.dvsize = size; + self.dv = chunk; + } + + unsafe fn insert_chunk(&mut self, chunk: *mut Chunk, size: usize) { + if self.is_small(size) { + self.insert_small_chunk(chunk, size); + } else { + self.insert_large_chunk(chunk as *mut TreeChunk, size); + } + } + + unsafe fn insert_small_chunk(&mut self, chunk: *mut Chunk, size: usize) { + let idx = self.small_index(size); + let head = self.smallbin_at(idx); + let mut f = head; + debug_assert!(size >= self.min_chunk_size()); + if !self.smallmap_is_marked(idx) { + self.mark_smallmap(idx); + } else { + f = (*head).prev; + } + + (*head).prev = chunk; + (*f).next = chunk; + (*chunk).prev = f; + (*chunk).next = head; + } + + unsafe fn insert_large_chunk(&mut self, chunk: *mut TreeChunk, size: usize) { + let idx = self.compute_tree_index(size); + let h = self.treebin_at(idx); + (*chunk).index = idx; + (*chunk).child[0] = ptr::null_mut(); + (*chunk).child[1] = ptr::null_mut(); + let chunkc = TreeChunk::chunk(chunk); + if !self.treemap_is_marked(idx) { + self.mark_treemap(idx); + *h = chunk; + (*chunk).parent = h as *mut TreeChunk; // TODO: dubious? + (*chunkc).next = chunkc; + (*chunkc).prev = chunkc; + } else { + let mut t = *h; + let mut k = size << leftshift_for_tree_index(idx); + loop { + if Chunk::size(TreeChunk::chunk(t)) != size { + let c = &mut (*t).child[(k >> mem::size_of::<usize>() * 8 - 1) & 1]; + k <<= 1; + if !c.is_null() { + t = *c; + } else { + *c = chunk; + (*chunk).parent = t; + (*chunkc).next = chunkc; + (*chunkc).prev = chunkc; + break; + } + } else { + let tc = TreeChunk::chunk(t); + let f = (*tc).prev; + (*f).next = chunkc; + (*tc).prev = chunkc; + (*chunkc).prev = f; + (*chunkc).next = tc; + (*chunk).parent = ptr::null_mut(); + break; + } + } + } + } + + unsafe fn smallmap_is_marked(&self, idx: u32) -> bool { + self.smallmap & (1 << idx) != 0 + } + + unsafe fn mark_smallmap(&mut self, idx: u32) { + self.smallmap |= 1 << idx; + } + + unsafe fn clear_smallmap(&mut self, idx: u32) { + self.smallmap &= !(1 << idx); + } + + unsafe fn treemap_is_marked(&self, idx: u32) -> bool { + self.treemap & (1 << idx) != 0 + } + + unsafe fn mark_treemap(&mut self, idx: u32) { + self.treemap |= 1 << idx; + } + + unsafe fn clear_treemap(&mut self, idx: u32) { + self.treemap &= !(1 << idx); + } + + unsafe fn unlink_chunk(&mut self, chunk: *mut Chunk, size: usize) { + if self.is_small(size) { + self.unlink_small_chunk(chunk, size) + } else { + self.unlink_large_chunk(chunk as *mut TreeChunk); + } + } + + unsafe fn unlink_small_chunk(&mut self, chunk: *mut Chunk, size: usize) { + let f = (*chunk).prev; + let b = (*chunk).next; + let idx = self.small_index(size); + debug_assert!(chunk != b); + debug_assert!(chunk != f); + debug_assert_eq!(Chunk::size(chunk), self.small_index2size(idx)); + if b == f { + self.clear_smallmap(idx); + } else { + (*f).next = b; + (*b).prev = f; + } + } + + unsafe fn unlink_large_chunk(&mut self, chunk: *mut TreeChunk) { + let xp = (*chunk).parent; + let mut r; + if TreeChunk::next(chunk) != chunk { + let f = TreeChunk::prev(chunk); + r = TreeChunk::next(chunk); + (*f).chunk.next = TreeChunk::chunk(r); + (*r).chunk.prev = TreeChunk::chunk(f); + } else { + let mut rp = &mut (*chunk).child[1]; + if rp.is_null() { + rp = &mut (*chunk).child[0]; + } + r = *rp; + if !rp.is_null() { + loop { + let mut cp = &mut (**rp).child[1]; + if cp.is_null() { + cp = &mut (**rp).child[0]; + } + if cp.is_null() { + break; + } + rp = cp; + } + r = *rp; + *rp = ptr::null_mut(); + } + } + + if xp.is_null() { + return; + } + + let h = self.treebin_at((*chunk).index); + if chunk == *h { + *h = r; + if r.is_null() { + self.clear_treemap((*chunk).index); + } + } else { + if (*xp).child[0] == chunk { + (*xp).child[0] = r; + } else { + (*xp).child[1] = r; + } + } + + if !r.is_null() { + (*r).parent = xp; + let c0 = (*chunk).child[0]; + if !c0.is_null() { + (*r).child[0] = c0; + (*c0).parent = r; + } + let c1 = (*chunk).child[1]; + if !c1.is_null() { + (*r).child[1] = c1; + (*c1).parent = r; + } + } + } + + pub unsafe fn free(&mut self, mem: *mut u8) { + self.check_malloc_state(); + + let mut p = Chunk::from_mem(mem); + let mut psize = Chunk::size(p); + let next = Chunk::plus_offset(p, psize); + if !Chunk::pinuse(p) { + let prevsize = (*p).prev_foot; + + if Chunk::mmapped(p) { + psize += prevsize + self.mmap_foot_pad(); + if self + .system_allocator + .free((p as *mut u8).offset(-(prevsize as isize)), psize) + { + self.footprint -= psize; + } + return; + } + + let prev = Chunk::minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if p != self.dv { + self.unlink_chunk(p, prevsize); + } else if (*next).head & INUSE == INUSE { + self.dvsize = psize; + Chunk::set_free_with_pinuse(p, psize, next); + return; + } + } + + // Consolidate forward if we can + if !Chunk::cinuse(next) { + if next == self.top { + self.topsize += psize; + let tsize = self.topsize; + self.top = p; + (*p).head = tsize | PINUSE; + if p == self.dv { + self.dv = ptr::null_mut(); + self.dvsize = 0; + } + if self.should_trim(tsize) { + self.sys_trim(0); + } + return; + } else if next == self.dv { + self.dvsize += psize; + let dsize = self.dvsize; + self.dv = p; + Chunk::set_size_and_pinuse_of_free_chunk(p, dsize); + return; + } else { + let nsize = Chunk::size(next); + psize += nsize; + self.unlink_chunk(next, nsize); + Chunk::set_size_and_pinuse_of_free_chunk(p, psize); + if p == self.dv { + self.dvsize = psize; + return; + } + } + } else { + Chunk::set_free_with_pinuse(p, psize, next); + } + + if self.is_small(psize) { + self.insert_small_chunk(p, psize); + self.check_free_chunk(p); + } else { + self.insert_large_chunk(p as *mut TreeChunk, psize); + self.check_free_chunk(p); + self.release_checks -= 1; + if self.release_checks == 0 { + self.release_unused_segments(); + } + } + } + + fn should_trim(&self, size: usize) -> bool { + size > self.trim_check + } + + unsafe fn sys_trim(&mut self, mut pad: usize) -> bool { + let mut released = 0; + if pad < self.max_request() && !self.top.is_null() { + pad += self.top_foot_size(); + if self.topsize > pad { + let unit = DEFAULT_GRANULARITY; + let extra = ((self.topsize - pad + unit - 1) / unit - 1) * unit; + let sp = self.segment_holding(self.top as *mut u8); + debug_assert!(!sp.is_null()); + + if !Segment::is_extern(sp) { + if Segment::can_release_part(&self.system_allocator, sp) { + if (*sp).size >= extra && !self.has_segment_link(sp) { + let newsize = (*sp).size - extra; + if self + .system_allocator + .free_part((*sp).base, (*sp).size, newsize) + { + released = extra; + } + } + } + } + + if released != 0 { + (*sp).size -= released; + self.footprint -= released; + let top = self.top; + let topsize = self.topsize - released; + self.init_top(top, topsize); + self.check_top_chunk(self.top); + } + } + + released += self.release_unused_segments(); + + if released == 0 && self.topsize > self.trim_check { + self.trim_check = usize::max_value(); + } + } + + released != 0 + } + + unsafe fn has_segment_link(&self, ptr: *mut Segment) -> bool { + let mut sp = &self.seg as *const Segment as *mut Segment; + while !sp.is_null() { + if Segment::holds(ptr, sp as *mut u8) { + return true; + } + sp = (*sp).next; + } + false + } + + /// Unmap and unlink any mapped segments that don't contain used chunks + unsafe fn release_unused_segments(&mut self) -> usize { + let mut released = 0; + let mut nsegs = 0; + let mut pred = &mut self.seg as *mut Segment; + let mut sp = (*pred).next; + while !sp.is_null() { + let base = (*sp).base; + let size = (*sp).size; + let next = (*sp).next; + nsegs += 1; + + if Segment::can_release_part(&self.system_allocator, sp) && !Segment::is_extern(sp) { + let p = self.align_as_chunk(base); + let psize = Chunk::size(p); + // We can unmap if the first chunk holds the entire segment and + // isn't pinned. + let chunk_top = (p as *mut u8).offset(psize as isize); + let top = base.offset((size - self.top_foot_size()) as isize); + if !Chunk::inuse(p) && chunk_top >= top { + let tp = p as *mut TreeChunk; + debug_assert!(Segment::holds(sp, sp as *mut u8)); + if p == self.dv { + self.dv = ptr::null_mut(); + self.dvsize = 0; + } else { + self.unlink_large_chunk(tp); + } + if self.system_allocator.free(base, size) { + released += size; + self.footprint -= size; + // unlink our obsolete record + sp = pred; + (*sp).next = next; + } else { + // back out if we can't unmap + self.insert_large_chunk(tp, psize); + } + } + } + pred = sp; + sp = next; + } + self.release_checks = if nsegs > MAX_RELEASE_CHECK_RATE { + nsegs + } else { + MAX_RELEASE_CHECK_RATE + }; + return released; + } + + // Sanity checks + + unsafe fn check_any_chunk(&self, p: *mut Chunk) { + if !cfg!(debug_assertions) { + return; + } + debug_assert!( + self.is_aligned(Chunk::to_mem(p) as usize) || (*p).head == Chunk::fencepost_head() + ); + debug_assert!(p as *mut u8 >= self.least_addr); + } + + unsafe fn check_top_chunk(&self, p: *mut Chunk) { + if !cfg!(debug_assertions) { + return; + } + let sp = self.segment_holding(p as *mut u8); + let sz = (*p).head & !INUSE; + debug_assert!(!sp.is_null()); + debug_assert!( + self.is_aligned(Chunk::to_mem(p) as usize) || (*p).head == Chunk::fencepost_head() + ); + debug_assert!(p as *mut u8 >= self.least_addr); + debug_assert_eq!(sz, self.topsize); + debug_assert!(sz > 0); + debug_assert_eq!( + sz, + (*sp).base as usize + (*sp).size - p as usize - self.top_foot_size() + ); + debug_assert!(Chunk::pinuse(p)); + debug_assert!(!Chunk::pinuse(Chunk::plus_offset(p, sz))); + } + + unsafe fn check_malloced_chunk(&self, mem: *mut u8, s: usize) { + if !cfg!(debug_assertions) { + return; + } + if mem.is_null() { + return; + } + let p = Chunk::from_mem(mem); + let sz = (*p).head & !INUSE; + self.check_inuse_chunk(p); + debug_assert_eq!(align_up(sz, self.malloc_alignment()), sz); + debug_assert!(sz >= self.min_chunk_size()); + debug_assert!(sz >= s); + debug_assert!(Chunk::mmapped(p) || sz < (s + self.min_chunk_size())); + } + + unsafe fn check_inuse_chunk(&self, p: *mut Chunk) { + self.check_any_chunk(p); + debug_assert!(Chunk::inuse(p)); + debug_assert!(Chunk::pinuse(Chunk::next(p))); + debug_assert!(Chunk::mmapped(p) || Chunk::pinuse(p) || Chunk::next(Chunk::prev(p)) == p); + if Chunk::mmapped(p) { + self.check_mmapped_chunk(p); + } + } + + unsafe fn check_mmapped_chunk(&self, p: *mut Chunk) { + if !cfg!(debug_assertions) { + return; + } + let sz = Chunk::size(p); + let len = sz + (*p).prev_foot + self.mmap_foot_pad(); + debug_assert!(Chunk::mmapped(p)); + debug_assert!( + self.is_aligned(Chunk::to_mem(p) as usize) || (*p).head == Chunk::fencepost_head() + ); + debug_assert!(p as *mut u8 >= self.least_addr); + debug_assert!(!self.is_small(sz)); + debug_assert_eq!(align_up(len, self.system_allocator.page_size()), len); + debug_assert_eq!((*Chunk::plus_offset(p, sz)).head, Chunk::fencepost_head()); + debug_assert_eq!( + (*Chunk::plus_offset(p, sz + mem::size_of::<usize>())).head, + 0 + ); + } + + unsafe fn check_free_chunk(&self, p: *mut Chunk) { + if !cfg!(debug_assertions) { + return; + } + let sz = Chunk::size(p); + let next = Chunk::plus_offset(p, sz); + self.check_any_chunk(p); + debug_assert!(!Chunk::inuse(p)); + debug_assert!(!Chunk::pinuse(Chunk::next(p))); + debug_assert!(!Chunk::mmapped(p)); + if p != self.dv && p != self.top { + if sz >= self.min_chunk_size() { + debug_assert_eq!(align_up(sz, self.malloc_alignment()), sz); + debug_assert!(self.is_aligned(Chunk::to_mem(p) as usize)); + debug_assert_eq!((*next).prev_foot, sz); + debug_assert!(Chunk::pinuse(p)); + debug_assert!(next == self.top || Chunk::inuse(next)); + debug_assert_eq!((*(*p).next).prev, p); + debug_assert_eq!((*(*p).prev).next, p); + } else { + debug_assert_eq!(sz, mem::size_of::<usize>()); + } + } + } + + unsafe fn check_malloc_state(&mut self) { + if !cfg!(debug_assertions) { + return; + } + for i in 0..NSMALLBINS { + self.check_smallbin(i as u32); + } + for i in 0..NTREEBINS { + self.check_treebin(i as u32); + } + if self.dvsize != 0 { + self.check_any_chunk(self.dv); + debug_assert_eq!(self.dvsize, Chunk::size(self.dv)); + debug_assert!(self.dvsize >= self.min_chunk_size()); + let dv = self.dv; + debug_assert!(!self.bin_find(dv)); + } + if !self.top.is_null() { + self.check_top_chunk(self.top); + debug_assert!(self.topsize > 0); + let top = self.top; + debug_assert!(!self.bin_find(top)); + } + let total = self.traverse_and_check(); + debug_assert!(total <= self.footprint); + debug_assert!(self.footprint <= self.max_footprint); + } + + unsafe fn check_smallbin(&mut self, idx: u32) { + if !cfg!(debug_assertions) { + return; + } + let b = self.smallbin_at(idx); + let mut p = (*b).next; + let empty = self.smallmap & (1 << idx) == 0; + if p == b { + debug_assert!(empty) + } + if !empty { + while p != b { + let size = Chunk::size(p); + self.check_free_chunk(p); + debug_assert_eq!(self.small_index(size), idx); + debug_assert!((*p).next == b || Chunk::size((*p).next) == Chunk::size(p)); + let q = Chunk::next(p); + if (*q).head != Chunk::fencepost_head() { + self.check_inuse_chunk(q); + } + p = (*p).next; + } + } + } + + unsafe fn check_treebin(&mut self, idx: u32) { + if !cfg!(debug_assertions) { + return; + } + let tb = self.treebin_at(idx); + let t = *tb; + let empty = self.treemap & (1 << idx) == 0; + if t.is_null() { + debug_assert!(empty); + } + if !empty { + self.check_tree(t); + } + } + + unsafe fn check_tree(&mut self, t: *mut TreeChunk) { + if !cfg!(debug_assertions) { + return; + } + let tc = TreeChunk::chunk(t); + let tindex = (*t).index; + let tsize = Chunk::size(tc); + let idx = self.compute_tree_index(tsize); + debug_assert_eq!(tindex, idx); + debug_assert!(tsize >= self.min_large_size()); + debug_assert!(tsize >= self.min_size_for_tree_index(idx)); + debug_assert!(idx == NTREEBINS as u32 - 1 || tsize < self.min_size_for_tree_index(idx + 1)); + + let mut u = t; + let mut head = ptr::null_mut::<TreeChunk>(); + loop { + let uc = TreeChunk::chunk(u); + self.check_any_chunk(uc); + debug_assert_eq!((*u).index, tindex); + debug_assert_eq!(Chunk::size(uc), tsize); + debug_assert!(!Chunk::inuse(uc)); + debug_assert!(!Chunk::pinuse(Chunk::next(uc))); + debug_assert_eq!((*(*uc).next).prev, uc); + debug_assert_eq!((*(*uc).prev).next, uc); + let left = (*u).child[0]; + let right = (*u).child[1]; + if (*u).parent.is_null() { + debug_assert!(left.is_null()); + debug_assert!(right.is_null()); + } else { + debug_assert!(head.is_null()); + head = u; + debug_assert!((*u).parent != u); + debug_assert!( + (*(*u).parent).child[0] == u + || (*(*u).parent).child[1] == u + || *((*u).parent as *mut *mut TreeChunk) == u + ); + if !left.is_null() { + debug_assert_eq!((*left).parent, u); + debug_assert!(left != u); + self.check_tree(left); + } + if !right.is_null() { + debug_assert_eq!((*right).parent, u); + debug_assert!(right != u); + self.check_tree(right); + } + if !left.is_null() && !right.is_null() { + debug_assert!( + Chunk::size(TreeChunk::chunk(left)) < Chunk::size(TreeChunk::chunk(right)) + ); + } + } + + u = TreeChunk::prev(u); + if u == t { + break; + } + } + debug_assert!(!head.is_null()); + } + + fn min_size_for_tree_index(&self, idx: u32) -> usize { + let idx = idx as usize; + (1 << ((idx >> 1) + TREEBIN_SHIFT)) | ((idx & 1) << ((idx >> 1) + TREEBIN_SHIFT - 1)) + } + + unsafe fn bin_find(&mut self, chunk: *mut Chunk) -> bool { + let size = Chunk::size(chunk); + if self.is_small(size) { + let sidx = self.small_index(size); + let b = self.smallbin_at(sidx); + if !self.smallmap_is_marked(sidx) { + return false; + } + let mut p = b; + loop { + if p == chunk { + return true; + } + p = (*p).prev; + if p == b { + return false; + } + } + } else { + let tidx = self.compute_tree_index(size); + if !self.treemap_is_marked(tidx) { + return false; + } + let mut t = *self.treebin_at(tidx); + let mut sizebits = size << leftshift_for_tree_index(tidx); + while !t.is_null() && Chunk::size(TreeChunk::chunk(t)) != size { + t = (*t).child[(sizebits >> (mem::size_of::<usize>() * 8 - 1)) & 1]; + sizebits <<= 1; + } + if t.is_null() { + return false; + } + let mut u = t; + let chunk = chunk as *mut TreeChunk; + loop { + if u == chunk { + return true; + } + u = TreeChunk::prev(u); + if u == t { + return false; + } + } + } + } + + unsafe fn traverse_and_check(&self) -> usize { + 0 + } +} + +const PINUSE: usize = 1 << 0; +const CINUSE: usize = 1 << 1; +const FLAG4: usize = 1 << 2; +const INUSE: usize = PINUSE | CINUSE; +const FLAG_BITS: usize = PINUSE | CINUSE | FLAG4; + +impl Chunk { + unsafe fn fencepost_head() -> usize { + INUSE | mem::size_of::<usize>() + } + + unsafe fn size(me: *mut Chunk) -> usize { + (*me).head & !FLAG_BITS + } + + unsafe fn next(me: *mut Chunk) -> *mut Chunk { + (me as *mut u8).offset(((*me).head & !FLAG_BITS) as isize) as *mut Chunk + } + + unsafe fn prev(me: *mut Chunk) -> *mut Chunk { + (me as *mut u8).offset(-((*me).prev_foot as isize)) as *mut Chunk + } + + unsafe fn cinuse(me: *mut Chunk) -> bool { + (*me).head & CINUSE != 0 + } + + unsafe fn pinuse(me: *mut Chunk) -> bool { + (*me).head & PINUSE != 0 + } + + unsafe fn clear_pinuse(me: *mut Chunk) { + (*me).head &= !PINUSE; + } + + unsafe fn inuse(me: *mut Chunk) -> bool { + (*me).head & INUSE != PINUSE + } + + unsafe fn mmapped(me: *mut Chunk) -> bool { + (*me).head & INUSE == 0 + } + + unsafe fn set_inuse(me: *mut Chunk, size: usize) { + (*me).head = ((*me).head & PINUSE) | size | CINUSE; + let next = Chunk::plus_offset(me, size); + (*next).head |= PINUSE; + } + + unsafe fn set_inuse_and_pinuse(me: *mut Chunk, size: usize) { + (*me).head = PINUSE | size | CINUSE; + let next = Chunk::plus_offset(me, size); + (*next).head |= PINUSE; + } + + unsafe fn set_size_and_pinuse_of_inuse_chunk(me: *mut Chunk, size: usize) { + (*me).head = size | PINUSE | CINUSE; + } + + unsafe fn set_size_and_pinuse_of_free_chunk(me: *mut Chunk, size: usize) { + (*me).head = size | PINUSE; + Chunk::set_foot(me, size); + } + + unsafe fn set_free_with_pinuse(p: *mut Chunk, size: usize, n: *mut Chunk) { + Chunk::clear_pinuse(n); + Chunk::set_size_and_pinuse_of_free_chunk(p, size); + } + + unsafe fn set_foot(me: *mut Chunk, size: usize) { + let next = Chunk::plus_offset(me, size); + (*next).prev_foot = size; + } + + unsafe fn plus_offset(me: *mut Chunk, offset: usize) -> *mut Chunk { + (me as *mut u8).offset(offset as isize) as *mut Chunk + } + + unsafe fn minus_offset(me: *mut Chunk, offset: usize) -> *mut Chunk { + (me as *mut u8).offset(-(offset as isize)) as *mut Chunk + } + + unsafe fn to_mem(me: *mut Chunk) -> *mut u8 { + (me as *mut u8).offset(Chunk::mem_offset()) + } + + fn mem_offset() -> isize { + 2 * (mem::size_of::<usize>() as isize) + } + + unsafe fn from_mem(mem: *mut u8) -> *mut Chunk { + mem.offset(-2 * (mem::size_of::<usize>() as isize)) as *mut Chunk + } +} + +impl TreeChunk { + unsafe fn leftmost_child(me: *mut TreeChunk) -> *mut TreeChunk { + let left = (*me).child[0]; + if left.is_null() { + (*me).child[1] + } else { + left + } + } + + unsafe fn chunk(me: *mut TreeChunk) -> *mut Chunk { + &mut (*me).chunk + } + + unsafe fn next(me: *mut TreeChunk) -> *mut TreeChunk { + (*TreeChunk::chunk(me)).next as *mut TreeChunk + } + + unsafe fn prev(me: *mut TreeChunk) -> *mut TreeChunk { + (*TreeChunk::chunk(me)).prev as *mut TreeChunk + } +} + +const EXTERN: u32 = 1 << 0; + +impl Segment { + unsafe fn is_extern(seg: *mut Segment) -> bool { + (*seg).flags & EXTERN != 0 + } + + unsafe fn can_release_part<A: Allocator>(system_allocator: &A, seg: *mut Segment) -> bool { + system_allocator.can_release_part((*seg).flags >> 1) + } + + unsafe fn sys_flags(seg: *mut Segment) -> u32 { + (*seg).flags >> 1 + } + + unsafe fn holds(seg: *mut Segment, addr: *mut u8) -> bool { + (*seg).base <= addr && addr < Segment::top(seg) + } + + unsafe fn top(seg: *mut Segment) -> *mut u8 { + (*seg).base.offset((*seg).size as isize) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use System; + + // Prime the allocator with some allocations such that there will be free + // chunks in the treemap + unsafe fn setup_treemap<A: Allocator>(a: &mut Dlmalloc<A>) { + let large_request_size = NSMALLBINS * (1 << SMALLBIN_SHIFT); + assert!(!a.is_small(large_request_size)); + let large_request1 = a.malloc(large_request_size); + assert_ne!(large_request1, ptr::null_mut()); + let large_request2 = a.malloc(large_request_size); + assert_ne!(large_request2, ptr::null_mut()); + a.free(large_request1); + assert_ne!(a.treemap, 0); + } + + #[test] + // Test allocating, with a non-empty treemap, a specific size that used to + // trigger an integer overflow bug + fn treemap_alloc_overflow_minimal() { + let mut a = Dlmalloc::new(System::new()); + unsafe { + setup_treemap(&mut a); + let min_idx31_size = (0xc000 << TREEBIN_SHIFT) - a.chunk_overhead() + 1; + assert_ne!(a.malloc(min_idx31_size), ptr::null_mut()); + } + } + + #[test] + // Test allocating the maximum request size with a non-empty treemap + fn treemap_alloc_max() { + let mut a = Dlmalloc::new(System::new()); + unsafe { + setup_treemap(&mut a); + let max_request_size = a.max_request() - 1; + assert_eq!(a.malloc(max_request_size), ptr::null_mut()); + } + } +} |