diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f5e9973..69be045 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: rust: [stable, beta, nightly] os: [ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Install Rust ( run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - run: cargo test @@ -26,7 +26,7 @@ jobs: name: Rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Install Rust run: rustup update stable && rustup default stable && rustup component add rustfmt - run: cargo fmt -- --check @@ -35,7 +35,7 @@ jobs: name: WebAssembly runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Install Rust run: rustup update stable && rustup default stable && rustup target add wasm32-unknown-unknown - run: cargo build --target wasm32-unknown-unknown @@ -45,7 +45,32 @@ jobs: name: external-platform runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Install Rust run: rustup update stable && rustup default stable && rustup target add x86_64-fortanix-unknown-sgx - run: cargo build --target x86_64-fortanix-unknown-sgx + + fuzz: + name: Build Fuzzers + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: rustup update nightly && rustup default nightly + - run: cargo install cargo-fuzz + - run: cargo fuzz build --dev + + miri: + name: Miri + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Miri + run: | + rustup toolchain install nightly --component miri + rustup override set nightly + cargo miri setup + - name: Test with Miri + run: cargo miri test + env: + MIRIFLAGS: -Zmiri-tree-borrows diff --git a/Cargo.toml b/Cargo.toml index dcf7cbd..5f6927c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dlmalloc" -version = "0.2.3" +version = "0.2.4" authors = ["Alex Crichton "] license = "MIT/Apache-2.0" readme = "README.md" @@ -10,6 +10,13 @@ documentation = "https://docs.rs/dlmalloc" description = """ A Rust port of the dlmalloc allocator """ +edition.workspace = true + +[workspace] +members = ['fuzz'] + +[workspace.package] +edition = '2021' [package.metadata.docs.rs] features = ['global'] @@ -25,21 +32,20 @@ libc = { version = "0.2", default-features = false } # `src/tools/rustc-std-workspace` folder core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' } compiler_builtins = { version = '0.1.0', optional = true } -once_cell = "1.9.0" cfg-if = "1.0" -[target.'cfg(target_os = "windows")'.dependencies] -windows = { version = "0.33.0", features = [ - "alloc", - "Win32_System_Memory", - "Win32_Foundation", - "Win32_System_Threading", - "Win32_Security", - "Win32_System_Diagnostics", - "Win32_System_Diagnostics_Debug" -] } - -[dev-dependencies ] -rand = "0.3" + +[target.'cfg(target_os = "windows")'.dependencies.windows-sys] +version = "0.52.0" +features = [ + "Win32_Foundation", + "Win32_System_Memory", + "Win32_System_Threading", + "Win32_System_SystemInformation", +] + +[dev-dependencies] +arbitrary = "1.3.2" +rand = { version = "0.8", features = ['small_rng'] } [profile.release] debug-assertions = true diff --git a/README.md b/README.md index 8679a3b..317d8c8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A port of [dlmalloc] to Rust. [Documentation](https://docs.rs/dlmalloc) -[dlmalloc]: http://gee.cs.oswego.edu/dl/html/malloc.html +[dlmalloc]: https://gee.cs.oswego.edu/dl/html/malloc.html ## Why dlmalloc? diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 0000000..b400c27 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,2 @@ +corpus +artifacts diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 0000000..fb9970f --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "dlmalloc-fuzz" +version = "0.0.1" +publish = false +edition.workspace = true + +[package.metadata] +cargo-fuzz = true + +[dependencies] +arbitrary = "1.3.2" +dlmalloc = { path = '..' } +libfuzzer-sys = "0.4.7" + +[[bin]] +name = "alloc" +path = "fuzz_targets/alloc.rs" +test = false +bench = false diff --git a/fuzz/fuzz_targets/alloc.rs b/fuzz/fuzz_targets/alloc.rs new file mode 100644 index 0000000..eddfb8b --- /dev/null +++ b/fuzz/fuzz_targets/alloc.rs @@ -0,0 +1,8 @@ +#![no_main] + +use arbitrary::Unstructured; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|bytes: &[u8]| { + let _ = dlmalloc_fuzz::run(&mut Unstructured::new(bytes)); +}); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs new file mode 100644 index 0000000..3b337bb --- /dev/null +++ b/fuzz/src/lib.rs @@ -0,0 +1,108 @@ +use arbitrary::{Result, Unstructured}; +use dlmalloc::Dlmalloc; +use std::cmp; + +const MAX_ALLOCATED: usize = 100 << 20; // 100 MB + +pub fn run(u: &mut Unstructured<'_>) -> Result<()> { + let mut a = Dlmalloc::new(); + let mut ptrs = Vec::new(); + let mut allocated = 0; + unsafe { + while u.arbitrary()? { + // If there are pointers to free then have a chance of deallocating + // a pointer. Try not to deallocate things until there's a "large" + // working set but afterwards give it a 50/50 chance of allocating + // or deallocating. + let free = match ptrs.len() { + 0 => false, + 0..=10_000 => u.ratio(1, 3)?, + _ => u.arbitrary()?, + }; + if free { + let idx = u.choose_index(ptrs.len())?; + let (ptr, size, align) = ptrs.swap_remove(idx); + allocated -= size; + a.free(ptr, size, align); + continue; + } + + // 1/100 chance of reallocating a pointer to a different size. + if ptrs.len() > 0 && u.ratio(1, 100)? { + let idx = u.choose_index(ptrs.len())?; + let (ptr, size, align) = ptrs.swap_remove(idx); + + // Arbitrarily choose whether to make this allocation either + // twice as large or half as small. + let new_size = if u.arbitrary()? { + u.int_in_range(size..=size * 2)? + } else if size > 10 { + u.int_in_range(size / 2..=size)? + } else { + continue; + }; + if allocated + new_size - size > MAX_ALLOCATED { + ptrs.push((ptr, size, align)); + continue; + } + allocated -= size; + allocated += new_size; + + // Perform the `realloc` and assert that all bytes were copied. + let mut tmp = Vec::new(); + for i in 0..cmp::min(size, new_size) { + tmp.push(*ptr.offset(i as isize)); + } + let ptr = a.realloc(ptr, size, align, new_size); + assert!(!ptr.is_null()); + for (i, byte) in tmp.iter().enumerate() { + assert_eq!(*byte, *ptr.offset(i as isize)); + } + ptrs.push((ptr, new_size, align)); + } + + // Aribtrarily choose a size to allocate as well as an alignment. + // Enable small sizes with standard alignment happening a fair bit. + let size = if u.arbitrary()? { + u.int_in_range(1..=128)? + } else { + u.int_in_range(1..=128 * 1024)? + }; + let align = if u.ratio(1, 10)? { + 1 << u.int_in_range(3..=8)? + } else { + 8 + }; + + if size + allocated > MAX_ALLOCATED { + continue; + } + allocated += size; + + // Choose arbitrarily between a zero-allocated chunk and a normal + // allocated chunk. + let zero = u.ratio(1, 50)?; + let ptr = if zero { + a.calloc(size, align) + } else { + a.malloc(size, align) + }; + for i in 0..size { + if zero { + assert_eq!(*ptr.offset(i as isize), 0); + } + *ptr.offset(i as isize) = 0xce; + } + ptrs.push((ptr, size, align)); + } + + // Deallocate everythign when we're done. + for (ptr, size, align) in ptrs { + a.free(ptr, size, align); + } + + a.destroy(); + } + + Ok(()) +} diff --git a/src/dlmalloc.rs b/src/dlmalloc.rs index a4b4b71..3d4066c 100644 --- a/src/dlmalloc.rs +++ b/src/dlmalloc.rs @@ -7,7 +7,7 @@ use core::cmp; use core::mem; use core::ptr; -use Allocator; +use crate::Allocator; pub struct Dlmalloc { smallmap: u32, @@ -91,22 +91,22 @@ impl Dlmalloc { Dlmalloc { smallmap: 0, treemap: 0, - smallbins: [0 as *mut _; (NSMALLBINS + 1) * 2], - treebins: [0 as *mut _; NTREEBINS], + smallbins: [ptr::null_mut(); (NSMALLBINS + 1) * 2], + treebins: [ptr::null_mut(); NTREEBINS], dvsize: 0, topsize: 0, - dv: 0 as *mut _, - top: 0 as *mut _, + dv: ptr::null_mut(), + top: ptr::null_mut(), footprint: 0, max_footprint: 0, seg: Segment { - base: 0 as *mut _, + base: ptr::null_mut(), size: 0, - next: 0 as *mut _, + next: ptr::null_mut(), flags: 0, }, trim_check: 0, - least_addr: 0 as *mut _, + least_addr: ptr::null_mut(), release_checks: 0, system_allocator, } @@ -201,7 +201,7 @@ impl Dlmalloc { } fn top_foot_size(&self) -> usize { - self.align_offset_usize(Chunk::mem_offset() as usize) + self.align_offset_usize(Chunk::mem_offset()) + self.pad_request(mem::size_of::()) + self.min_chunk_size() } @@ -799,7 +799,7 @@ impl Dlmalloc { let nextp = Chunk::plus_offset(p, mem::size_of::()); (*p).head = Chunk::fencepost_head(); nfences += 1; - if (&(*nextp).head as *const usize as *mut u8) < old_end { + if ptr::addr_of!((*nextp).head).cast::() < old_end { p = nextp; } else { break; @@ -940,13 +940,15 @@ impl Dlmalloc { } 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 + let idx = usize::try_from(idx * 2).unwrap(); + debug_assert!(idx < self.smallbins.len()); + self.smallbins.as_mut_ptr().add(idx).cast() } 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) + let idx = usize::try_from(idx).unwrap(); + debug_assert!(idx < self.treebins.len()); + self.treebins.as_mut_ptr().add(idx) } fn compute_tree_index(&self, size: usize) -> u32 { @@ -1642,6 +1644,26 @@ impl Dlmalloc { unsafe fn traverse_and_check(&self) -> usize { 0 } + + pub unsafe fn trim(&mut self, pad: usize) -> bool { + self.sys_trim(pad) + } + + pub unsafe fn destroy(mut self) -> usize { + let mut freed = 0; + let mut sp = &mut self.seg as *mut Segment; + while !sp.is_null() { + let base = (*sp).base; + let size = (*sp).size; + let can_free = !base.is_null() && !Segment::is_extern(sp); + sp = (*sp).next; + + if can_free && self.system_allocator.free(base, size) { + freed += size; + } + } + freed + } } const PINUSE: usize = 1 << 0; @@ -1719,19 +1741,19 @@ impl Chunk { } unsafe fn plus_offset(me: *mut Chunk, offset: usize) -> *mut Chunk { - (me as *mut u8).offset(offset as isize) as *mut Chunk + me.cast::().add(offset).cast() } unsafe fn minus_offset(me: *mut Chunk, offset: usize) -> *mut Chunk { - (me as *mut u8).offset(-(offset as isize)) as *mut Chunk + me.cast::().offset(-(offset as isize)).cast() } unsafe fn to_mem(me: *mut Chunk) -> *mut u8 { - (me as *mut u8).offset(Chunk::mem_offset()) + me.cast::().add(Chunk::mem_offset()) } - fn mem_offset() -> isize { - 2 * (mem::size_of::() as isize) + fn mem_offset() -> usize { + 2 * mem::size_of::() } unsafe fn from_mem(mem: *mut u8) -> *mut Chunk { @@ -1750,7 +1772,7 @@ impl TreeChunk { } unsafe fn chunk(me: *mut TreeChunk) -> *mut Chunk { - &mut (*me).chunk + ptr::addr_of_mut!((*me).chunk) } unsafe fn next(me: *mut TreeChunk) -> *mut TreeChunk { @@ -1782,14 +1804,14 @@ impl Segment { } unsafe fn top(seg: *mut Segment) -> *mut u8 { - (*seg).base.offset((*seg).size as isize) + (*seg).base.add((*seg).size) } } #[cfg(test)] mod tests { use super::*; - use System; + use crate::System; // Prime the allocator with some allocations such that there will be free // chunks in the treemap @@ -1817,6 +1839,7 @@ mod tests { } #[test] + #[cfg(not(miri))] // Test allocating the maximum request size with a non-empty treemap fn treemap_alloc_max() { let mut a = Dlmalloc::new(System::new()); diff --git a/src/dummy.rs b/src/dummy.rs index bd8d5b6..8654518 100644 --- a/src/dummy.rs +++ b/src/dummy.rs @@ -1,5 +1,5 @@ +use crate::Allocator; use core::ptr; -use Allocator; pub struct System { _priv: (), diff --git a/src/global.rs b/src/global.rs index c761fde..c2ce476 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,9 +1,8 @@ +use crate::Dlmalloc; use core::alloc::{GlobalAlloc, Layout}; use core::ops::{Deref, DerefMut}; -use Dlmalloc; - -pub use sys::enable_alloc_after_fork; +pub use crate::sys::enable_alloc_after_fork; /// An instance of a "global allocator" backed by `Dlmalloc` /// @@ -38,7 +37,7 @@ static mut DLMALLOC: Dlmalloc = Dlmalloc::new(); struct Instance; unsafe fn get() -> Instance { - ::sys::acquire_global_lock(); + crate::sys::acquire_global_lock(); Instance } @@ -57,6 +56,6 @@ impl DerefMut for Instance { impl Drop for Instance { fn drop(&mut self) { - ::sys::release_global_lock() + crate::sys::release_global_lock() } } diff --git a/src/lib.rs b/src/lib.rs index b0ecb2c..d3af158 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! A Rust port of the `dlmalloc` allocator. //! //! The `dlmalloc` allocator is described at -//! http://g.oswego.edu/dl/html/malloc.html and this Rust crate is a straight +//! and this Rust crate is a straight //! port of the C code for the allocator into Rust. The implementation is //! wrapped up in a `Dlmalloc` type and has support for Linux, OSX, and Wasm //! currently. @@ -16,7 +16,6 @@ #![deny(missing_docs)] #![cfg_attr(target_arch = "wasm64", feature(simd_wasm64))] - use core::cmp; use core::ptr; use sys::System; @@ -78,12 +77,15 @@ cfg_if::cfg_if! { if #[cfg(target_family = "wasm")] { #[path = "wasm.rs"] mod sys; - } else if #[cfg(any(target_os = "linux", target_os = "macos"))] { - #[path = "unix.rs"] - mod sys; } else if #[cfg(target_os = "windows")] { #[path = "windows.rs"] mod sys; + } else if #[cfg(target_os = "xous")] { + #[path = "xous.rs"] + mod sys; + } else if #[cfg(any(target_os = "linux", target_os = "macos"))] { + #[path = "unix.rs"] + mod sys; } else { #[path = "dummy.rs"] mod sys; @@ -139,7 +141,7 @@ impl Dlmalloc { /// method contracts. #[inline] pub unsafe fn free(&mut self, ptr: *mut u8, size: usize, align: usize) { - drop((size, align)); + let _ = (size, align); self.0.free(ptr) } @@ -172,4 +174,33 @@ impl Dlmalloc { res } } + + /// If possible, gives memory back to the system if there is unused memory + /// at the high end of the malloc pool or in unused segments. + /// + /// You can call this after freeing large blocks of memory to potentially + /// reduce the system-level memory requirements of a program. However, it + /// cannot guarantee to reduce memory. Under some allocation patterns, some + /// large free blocks of memory will be locked between two used chunks, so + /// they cannot be given back to the system. + /// + /// The `pad` argument represents the amount of free trailing space to + /// leave untrimmed. If this argument is zero, only the minimum amount of + /// memory to maintain internal data structures will be left. Non-zero + /// arguments can be supplied to maintain enough trailing space to service + /// future expected allocations without having to re-obtain memory from the + /// system. + /// + /// Returns `true` if it actually released any memory, else `false`. + pub unsafe fn trim(&mut self, pad: usize) -> bool { + self.0.trim(pad) + } + + /// Releases all allocations in this allocator back to the system, + /// consuming self and preventing further use. + /// + /// Returns the number of bytes released to the system. + pub unsafe fn destroy(self) -> usize { + self.0.destroy() + } } diff --git a/src/unix.rs b/src/unix.rs index a4f9914..e6eb0fd 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -1,7 +1,5 @@ -extern crate libc; - +use crate::Allocator; use core::ptr; -use Allocator; /// System setting for Linux pub struct System { @@ -21,7 +19,7 @@ unsafe impl Allocator for System { fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { let addr = unsafe { libc::mmap( - 0 as *mut _, + ptr::null_mut(), size, libc::PROT_WRITE | libc::PROT_READ, libc::MAP_ANON | libc::MAP_PRIVATE, @@ -32,18 +30,18 @@ unsafe impl Allocator for System { if addr == libc::MAP_FAILED { (ptr::null_mut(), 0, 0) } else { - (addr as *mut u8, size, 0) + (addr.cast(), size, 0) } } #[cfg(target_os = "linux")] fn remap(&self, ptr: *mut u8, oldsize: usize, newsize: usize, can_move: bool) -> *mut u8 { let flags = if can_move { libc::MREMAP_MAYMOVE } else { 0 }; - let ptr = unsafe { libc::mremap(ptr as *mut _, oldsize, newsize, flags) }; + let ptr = unsafe { libc::mremap(ptr.cast(), oldsize, newsize, flags) }; if ptr == libc::MAP_FAILED { ptr::null_mut() } else { - ptr as *mut u8 + ptr.cast() } } @@ -55,21 +53,21 @@ unsafe impl Allocator for System { #[cfg(target_os = "linux")] fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool { unsafe { - let rc = libc::mremap(ptr as *mut _, oldsize, newsize, 0); + let rc = libc::mremap(ptr.cast(), oldsize, newsize, 0); if rc != libc::MAP_FAILED { return true; } - libc::munmap(ptr.offset(newsize as isize) as *mut _, oldsize - newsize) == 0 + libc::munmap(ptr.add(newsize).cast(), oldsize - newsize) == 0 } } #[cfg(target_os = "macos")] fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool { - unsafe { libc::munmap(ptr.offset(newsize as isize) as *mut _, oldsize - newsize) == 0 } + unsafe { libc::munmap(ptr.add(newsize).cast(), oldsize - newsize) == 0 } } fn free(&self, ptr: *mut u8, size: usize) -> bool { - unsafe { libc::munmap(ptr as *mut _, size) == 0 } + unsafe { libc::munmap(ptr.cast(), size) == 0 } } fn can_release_part(&self, _flags: u32) -> bool { @@ -87,12 +85,12 @@ unsafe impl Allocator for System { #[cfg(feature = "global")] pub fn acquire_global_lock() { - unsafe { assert_eq!(libc::pthread_mutex_lock(&mut LOCK), 0) } + unsafe { assert_eq!(libc::pthread_mutex_lock(ptr::addr_of_mut!(LOCK)), 0) } } #[cfg(feature = "global")] pub fn release_global_lock() { - unsafe { assert_eq!(libc::pthread_mutex_unlock(&mut LOCK), 0) } + unsafe { assert_eq!(libc::pthread_mutex_unlock(ptr::addr_of_mut!(LOCK)), 0) } } #[cfg(feature = "global")] diff --git a/src/wasm.rs b/src/wasm.rs index 216fe43..b45e275 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -1,9 +1,9 @@ +use crate::Allocator; #[cfg(target_arch = "wasm32")] use core::arch::wasm32 as wasm; #[cfg(target_arch = "wasm64")] use core::arch::wasm64 as wasm; use core::ptr; -use Allocator; /// System setting for Wasm pub struct System { @@ -59,14 +59,17 @@ unsafe impl Allocator for System { #[cfg(feature = "global")] pub fn acquire_global_lock() { // single threaded, no need! + assert!(!cfg!(target_feature = "atomics")); } #[cfg(feature = "global")] pub fn release_global_lock() { // single threaded, no need! + assert!(!cfg!(target_feature = "atomics")); } #[cfg(feature = "global")] pub unsafe fn enable_alloc_after_fork() { // single threaded, no need! + assert!(!cfg!(target_feature = "atomics")); } diff --git a/src/windows.rs b/src/windows.rs index 2b81555..ba88adc 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -1,76 +1,88 @@ +use crate::Allocator; +use core::mem::MaybeUninit; use core::ptr; -use core::ffi::{c_void}; -use Allocator; +use windows_sys::Win32::System::Memory::*; +use windows_sys::Win32::System::SystemInformation::*; +#[cfg(feature = "global")] +use windows_sys::Win32::System::Threading::*; pub struct System { _priv: (), } - impl System { - pub const fn new() -> System { System { _priv: () } } + pub const fn new() -> System { + System { _priv: () } + } } unsafe impl Allocator for System { fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { - let addr = unsafe { - windows::Win32::System::Memory::VirtualAlloc( - ptr::null_mut(), - size, - windows::Win32::System::Memory::MEM_RESERVE | windows::Win32::System::Memory::MEM_COMMIT, - windows::Win32::System::Memory::PAGE_READWRITE, - ) - }; + let addr = unsafe { + VirtualAlloc( + ptr::null_mut(), + size, + MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE, + ) + }; - if addr == ptr::null_mut() { - (ptr::null_mut(), 0, 0) - } else { - (addr as *mut u8, size, 0) - } + if addr.is_null() { + (ptr::null_mut(), 0, 0) + } else { + (addr.cast(), size, 0) + } } - fn remap(&self, ptr: *mut u8, oldsize: usize, newsize: usize, can_move: bool) -> *mut u8 { + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { ptr::null_mut() } - fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool { false } + fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool { + unsafe { VirtualFree(ptr.add(newsize).cast(), oldsize - newsize, MEM_DECOMMIT) != 0 } + } - fn free(&self, ptr: *mut u8, size: usize) -> bool { - unsafe { - windows::Win32::System::Memory::VirtualFree( - ptr as *mut c_void, - 0, - windows::Win32::System::Memory::MEM_RELEASE).0 != 0 - } + fn free(&self, ptr: *mut u8, _size: usize) -> bool { + unsafe { VirtualFree(ptr.cast(), 0, MEM_DECOMMIT) != 0 } } - fn can_release_part(&self, _flags: u32) -> bool { true } + fn can_release_part(&self, _flags: u32) -> bool { + true + } - fn allocates_zeros(&self) -> bool { true } + fn allocates_zeros(&self) -> bool { + true + } - fn page_size(&self) -> usize { 4096 } + fn page_size(&self) -> usize { + unsafe { + let mut info = MaybeUninit::uninit(); + GetSystemInfo(info.as_mut_ptr()); + info.assume_init_ref().dwPageSize as usize + } + } } +// NB: `SRWLOCK_INIT` doesn't appear to be in `windows-sys` #[cfg(feature = "global")] -static mut LOCK: windows::Win32::Foundation::HANDLE = unsafe { - windows::Win32::System::Threading::CreateMutexA( - ptr::null_mut(), - false, - windows::core::PCSTR::default()) +static mut LOCK: SRWLOCK = SRWLOCK { + Ptr: ptr::null_mut(), }; #[cfg(feature = "global")] pub fn acquire_global_lock() { - unsafe { assert_ne!(windows::Win32::System::Threading::WaitForSingleObject(LOCK, u32::MAX), u32::MAX) }; + unsafe { + AcquireSRWLockExclusive(ptr::addr_of_mut!(LOCK)); + } } #[cfg(feature = "global")] pub fn release_global_lock() { - unsafe { assert_ne!(windows::Win32::System::Threading::ReleaseMutex(LOCK).0, 0) }; + unsafe { + ReleaseSRWLockExclusive(ptr::addr_of_mut!(LOCK)); + } } -/// Under consideration +/// Not needed on Windows #[cfg(feature = "global")] -pub unsafe fn enable_alloc_after_fork() { - // TODO(threadedstream): do I need to implement it? -} +pub unsafe fn enable_alloc_after_fork() {} diff --git a/src/xous.rs b/src/xous.rs new file mode 100644 index 0000000..14dbfa1 --- /dev/null +++ b/src/xous.rs @@ -0,0 +1,117 @@ +use crate::Allocator; +use core::ptr; + +pub struct System { + _priv: (), +} + +impl System { + pub const fn new() -> System { + System { _priv: () } + } +} + +#[cfg(target_arch = "riscv32")] +mod sys { + use core::arch::asm; + + pub fn increase_heap(length: usize) -> Result<(usize, usize), ()> { + let syscall_no_increase_heap = 10usize; + let memory_flags_read_write = 2usize | 4usize; + + let mut a0 = syscall_no_increase_heap; + let mut a1 = length; + let mut a2 = memory_flags_read_write; + + unsafe { + asm!( + "ecall", + inlateout("a0") a0, + inlateout("a1") a1, + inlateout("a2") a2, + out("a3") _, + out("a4") _, + out("a5") _, + out("a6") _, + out("a7") _, + ) + }; + + let result = a0; + let address = a1; + let length = a2; + + // 3 is the "MemoryRange" type, and the result is only valid + // if we get nonzero address and length. + if result == 3 && address != 0 && length != 0 { + Ok((address, length)) + } else { + Err(()) + } + } +} + +unsafe impl Allocator for System { + /// Allocate an additional `size` bytes on the heap, and return a new + /// chunk of memory, as well as the size of the allocation and some + /// flags. Since flags are unused on this platform, they will always + /// be `0`. + fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { + let size = if size == 0 { + 4096 + } else if size & 4095 == 0 { + size + } else { + size + (4096 - (size & 4095)) + }; + + if let Ok((address, length)) = sys::increase_heap(size) { + let start = address - size + length; + (start as *mut u8, size, 0) + } else { + (ptr::null_mut(), 0, 0) + } + } + + fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 { + // TODO + ptr::null_mut() + } + + fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool { + false + } + + fn free(&self, _ptr: *mut u8, _size: usize) -> bool { + false + } + + fn can_release_part(&self, _flags: u32) -> bool { + false + } + + fn allocates_zeros(&self) -> bool { + true + } + + fn page_size(&self) -> usize { + 4 * 1024 + } +} + +#[cfg(feature = "global")] +pub fn acquire_global_lock() { + // global feature should not be enabled + unimplemented!() +} + +#[cfg(feature = "global")] +pub fn release_global_lock() { + // global feature should not be enabled + unimplemented!() +} + +#[cfg(feature = "global")] +pub unsafe fn enable_alloc_after_fork() { + // platform does not support `fork()` call +} diff --git a/tests/smoke.rs b/tests/smoke.rs index cd69f94..8bcb5d7 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -1,9 +1,6 @@ -extern crate dlmalloc; -extern crate rand; - +use arbitrary::Unstructured; use dlmalloc::Dlmalloc; -use rand::Rng; -use std::cmp; +use rand::{rngs::SmallRng, RngCore, SeedableRng}; #[test] fn smoke() { @@ -23,69 +20,17 @@ fn smoke() { } } +#[path = "../fuzz/src/lib.rs"] +mod fuzz; + #[test] fn stress() { - let mut a = Dlmalloc::new(); - let mut rng = rand::thread_rng(); - let mut ptrs = Vec::new(); - let max = if cfg!(test_lots) { 1_000_000 } else { 1_000 }; - unsafe { - for _ in 0..max { - let free = - ptrs.len() > 0 && ((ptrs.len() < 10_000 && rng.gen_weighted_bool(3)) || rng.gen()); - if free { - let idx = rng.gen_range(0, ptrs.len()); - let (ptr, size, align) = ptrs.swap_remove(idx); - a.free(ptr, size, align); - continue; - } - - if ptrs.len() > 0 && rng.gen_weighted_bool(100) { - let idx = rng.gen_range(0, ptrs.len()); - let (ptr, size, align) = ptrs.swap_remove(idx); - let new_size = if rng.gen() { - rng.gen_range(size, size * 2) - } else if size > 10 { - rng.gen_range(size / 2, size) - } else { - continue; - }; - let mut tmp = Vec::new(); - for i in 0..cmp::min(size, new_size) { - tmp.push(*ptr.offset(i as isize)); - } - let ptr = a.realloc(ptr, size, align, new_size); - assert!(!ptr.is_null()); - for (i, byte) in tmp.iter().enumerate() { - assert_eq!(*byte, *ptr.offset(i as isize)); - } - ptrs.push((ptr, new_size, align)); - } - - let size = if rng.gen() { - rng.gen_range(1, 128) - } else { - rng.gen_range(1, 128 * 1024) - }; - let align = if rng.gen_weighted_bool(10) { - 1 << rng.gen_range(3, 8) - } else { - 8 - }; - - let zero = rng.gen_weighted_bool(50); - let ptr = if zero { - a.calloc(size, align) - } else { - a.malloc(size, align) - }; - for i in 0..size { - if zero { - assert_eq!(*ptr.offset(i as isize), 0); - } - *ptr.offset(i as isize) = 0xce; - } - ptrs.push((ptr, size, align)); - } + let mut rng = SmallRng::seed_from_u64(0); + let mut buf = vec![0; 4096]; + let iters = if cfg!(miri) { 5 } else { 2000 }; + for _ in 0..iters { + rng.fill_bytes(&mut buf); + let mut u = Unstructured::new(&buf); + let _ = fuzz::run(&mut u); } }