commit d8af6e37a22cced59232f324d7d99fb43ad6db6b Author: Avril Date: Tue Jun 9 21:13:02 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..80aca69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +*~ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..184961a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "malloc-array" +description = "libc heap array allocator" +version = "0.1.0" +authors = ["Avril "] +edition = "2018" +license = "GPL v3" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["zst_noalloc"] + +# Assume Rust will free things allocated with malloc() properly. +assume_libc = [] + +# Do not allocate for ZSTs +zst_noalloc = [] + +# Use jemalloc instead of libc malloc +jemalloc = ["jemalloc-sys"] + +[dependencies] +libc = "0.2" +jemalloc-sys = { version = "0.3", optional = true } \ No newline at end of file diff --git a/src/alloc.rs b/src/alloc.rs new file mode 100644 index 0000000..7ea2705 --- /dev/null +++ b/src/alloc.rs @@ -0,0 +1,109 @@ +use std::{ + ffi::c_void, + error, + fmt, +}; +use crate::{ + ptr::{self,VoidPointer,}, +}; + +#[derive(Debug)] +pub struct Error; + +impl error::Error for Error{} +impl fmt::Display for Error +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "Allocation failed.") + } +} + +#[inline] +unsafe fn malloc_internal(sz: libc::size_t) -> *mut c_void +{ + #[cfg(feature="jemalloc")] + return jemalloc_sys::malloc(sz); + #[cfg(not(feature="jemalloc"))] + return libc::malloc(sz); +} +#[inline] +unsafe fn calloc_internal(nm: libc::size_t, sz: libc::size_t) -> *mut c_void +{ + #[cfg(feature="jemalloc")] + return jemalloc_sys::calloc(nm,sz); + #[cfg(not(feature="jemalloc"))] + return libc::calloc(nm,sz); +} +#[inline] +unsafe fn free_internal(ptr: *mut c_void) +{ + #[cfg(feature="jemalloc")] + return jemalloc_sys::free(ptr); + #[cfg(not(feature="jemalloc"))] + return libc::free(ptr); +} +#[inline] +unsafe fn realloc_internal(ptr: *mut c_void, sz: libc::size_t) -> *mut c_void +{ + #[cfg(feature="jemalloc")] + return jemalloc_sys::realloc(ptr,sz); + #[cfg(not(feature="jemalloc"))] + return libc::realloc(ptr,sz); +} + +const NULL_PTR: *mut c_void = 0 as *mut c_void; + +pub unsafe fn malloc(sz: usize) -> Result +{ + #[cfg(feature="zst_noalloc")] + if sz == 0 { + return Ok(ptr::NULL_PTR); + } + + match malloc_internal(sz as libc::size_t) + { + null if null == NULL_PTR => Err(Error), + ptr => Ok(ptr as VoidPointer), + } +} + +pub unsafe fn calloc(nm: usize, sz: usize) -> Result +{ + #[cfg(feature="zst_noalloc")] + if (nm*sz) == 0 { + return Ok(ptr::NULL_PTR); + } + + match calloc_internal(nm as libc::size_t, sz as libc::size_t) + { + null if null == NULL_PTR => Err(Error), + ptr => Ok(ptr as VoidPointer), + } +} + +pub unsafe fn free(ptr: VoidPointer) +{ + if ptr != crate::ptr::NULL_PTR { + free_internal(ptr as *mut c_void); + } +} + +pub unsafe fn realloc(ptr: VoidPointer, sz: usize) -> Result +{ + #[cfg(feature="zst_noalloc")] + if sz == 0 { + free(ptr); + return Ok(crate::ptr::NULL_PTR); + } + + if ptr == crate::ptr::NULL_PTR { + return malloc(sz); + } + + match realloc_internal(ptr as *mut c_void, sz as libc::size_t) + { + null if null == NULL_PTR => Err(Error), + ptr => Ok(ptr as VoidPointer), + } +} diff --git a/src/iter.rs b/src/iter.rs new file mode 100644 index 0000000..e9202fd --- /dev/null +++ b/src/iter.rs @@ -0,0 +1,72 @@ +use super::*; + +use std::{ + mem::{ + replace, + MaybeUninit, + forget, + }, +}; +use ptr::{ + VoidPointer, +}; + +pub struct IntoIter +{ + start: *mut T, + current: *mut T, + sz: usize, +} + +impl IntoIter +{ + fn current_offset(&self) -> usize + { + (self.current as usize) - (self.start as usize) + } + fn free_if_needed(&mut self) + { + if self.start != ptr::null() && self.current_offset() >= self.sz { + unsafe { + alloc::free(self.start as VoidPointer); + } + self.start = ptr::null(); + } + } +} + +impl Iterator for IntoIter +{ + type Item = T; + fn next(&mut self) -> Option + { + let output = if self.current_offset() >= self.sz { + None + } else { + unsafe { + let output = replace(&mut (*self.current), MaybeUninit::zeroed().assume_init()); + self.current = self.current.offset(1); + Some(output) + } + }; + self.free_if_needed(); + output + } +} + +impl IntoIterator for HeapArray +{ + type Item = T; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter + { + let output = Self::IntoIter { + start: self.ptr, + current: self.ptr, + sz: self.len(), + }; + forget(self); + output + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b760b56 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,377 @@ +#![allow(dead_code)] + +extern crate libc; +#[cfg(feature="jemalloc")] +extern crate jemalloc_sys; + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn as_slice() { + let heap = heap![unsafe 0, 1, 2, 3u8]; + + assert_eq!(heap.as_slice(), [0,1,2,3u8]); + } + + #[test] + fn non_trivial_type() { + let heap = heap!["test one".to_owned(), "test two".to_owned()]; + + assert_eq!(heap.as_slice(), ["test one", "test two"]); + } + + #[test] + fn zero_size() { + let heap: HeapArray = heap![]; + let heap_zst: HeapArray<()> = heap![(); 3]; + + assert_eq!(&heap.as_slice(), &[]); + assert_eq!(&heap_zst.as_slice(), &[(),(),()]); + } + + #[test] + fn into_iter() { + let primitive = heap![1,3,5,7,9u32]; + + for x in primitive.into_iter() + { + assert_eq!(x % 2, 1); + } + + let non = heap!["string one".to_owned(), "string two".to_owned()]; + + for x in non.into_iter() + { + assert_eq!(&x[..6], "string"); + } + } +} + +mod ptr; +mod alloc; +mod reinterpret; + +use std::{ + ops::{ + Drop, + Index,IndexMut, + Deref,DerefMut, + }, + borrow::{ + Borrow,BorrowMut, + }, + slice::{ + self, + SliceIndex, + }, +}; +use crate::{ + ptr::{ + VoidPointer, + }, +}; + +#[macro_export] +/// `vec![]`-like macro for creating `HeapArray` instances. +/// +/// Provices methods for creating safly accessable arrays using `malloc()` with a `Vec` like interface. +/// Also provides methods of optimising deallocations. +/// +/// # Usage +/// +/// Works like array definitions `[type; size]`, and like the `vec![]` macro `[value; size]`. Prepend the statement with `unsafe` (`[unsafe type|value; size]`) to prevent potentially redundant `drop()` calls. +/// +/// # Examples +/// +/// ```rust +/// use malloc_array::{heap, HeapArray}; +/// let ints = heap![unsafe 4u32; 32]; // Creates a 32 element `u32` array with each element set to `4`. +/// let ints = heap![unsafe u32; 32]; // Creates an uninitialised 32 element `u32` array. +/// let ints = heap![u32; 32]; // Same as above, except when `ints` is dropped, each element will be also dropped redundantly. +/// let strings = heap!["string one".to_owned(), "string two".to_owned()]; //Creates a 2 element string array. +/// let strings = heap![unsafe "memory".to_owned(), "leak".to_owned()]; //Same as above, except `drop()` will not be called on the 2 strings, potentially causing a memory leak. +/// let strings: HeapArray = heap![]; //Creates an empty `u8` array. +/// ``` +macro_rules! heap { + () => { + $crate::HeapArray::new_uninit(0) + }; + (@) => (0usize); + (@ $x:tt $($xs:tt)* ) => (1usize + $crate::heap!(@ $($xs)*)); + + (unsafe $($xs:tt)*) => { + { + #[allow(unused_unsafe)] + unsafe { + let mut output = $crate::heap!($($xs)*); + output.drop_check = false; + output + } + } + }; + + ($type:ty; $number:expr) => { + { + $crate::HeapArray::<$type>::new($number) + } + }; + ($value:expr; $number:expr) => { + { + let num = $number; + let mut ha = $crate::HeapArray::new_uninit(num); + + for x in 0..num { + ha[x] = $value; + } + + ha + } + }; + ($($n:expr),*) => { + { + let mut ha = $crate::HeapArray::new_uninit($crate::heap!(@ $($n)*)); + { + let fp = 0; + $( + let fp = fp + 1; + ha[fp-1] = $n; + )* + } + ha + } + }; +} + +pub struct HeapArray { + ptr: *mut T, + size: usize, + + /// Call `drop()` on sub-elements when `drop`ping the array. This is not needed for types that implement `Copy`. + pub drop_check: bool, +} + +impl HeapArray +{ + pub fn len_bytes(&self) -> usize + { + Self::element_size() * self.size + } + pub fn len(&self) -> usize + { + self.size + } + + const fn element_size() -> usize + { + std::mem::size_of::() + } + const fn is_single() -> bool + { + std::mem::size_of::() == 1 + } + pub fn new(size: usize) -> Self + { + Self { + ptr: unsafe{alloc::calloc(size, Self::element_size()).expect("calloc()")} as *mut T, + size, + drop_check: true, + } + } + pub fn new_uninit(size: usize) -> Self + { + Self { + ptr: unsafe{alloc::malloc(size * Self::element_size()).expect("malloc()")} as *mut T, + size, + drop_check: true, + } + } + pub fn new_repeat(initial: T, size: usize) -> Self + where T: Copy + { + let this = Self::new_uninit(size); + if size > 0 { + if Self::is_single() { + unsafe { + ptr::memset(this.ptr as *mut u8, reinterpret::bytes(initial), this.len_bytes()); + } + } else { + unsafe { + for x in 0..size { + *this.ptr.offset(x as isize) = initial; + } + } + } + } + this + } + pub fn new_range(initial: U, size: usize) -> Self + where T: Copy, + U: AsRef<[T]> + { + let initial = initial.as_ref(); + if size > 0 { + if initial.len() == 1 { + Self::new_repeat(initial[0], size) + } else { + let this = Self::new_uninit(size); + unsafe { + for x in 0..size { + *this.ptr.offset(x as isize) = initial[x % initial.len()]; + } + this + } + } + } else { + Self::new_uninit(size) + } + } + + pub fn as_slice(&self) -> &[T] + { + unsafe{slice::from_raw_parts(self.ptr, self.size)} + } + pub fn as_slice_mut(&mut self) -> &mut [T] + { + unsafe{slice::from_raw_parts_mut(self.ptr, self.size)} + } + pub fn as_ptr(&self) -> *const T + { + self.ptr as *const T + } + pub fn as_ptr_mut(&mut self) -> *mut T + { + self.ptr + } + pub fn memory(&self) -> &[u8] + { + unsafe { + slice::from_raw_parts(self.ptr as *const u8, self.len_bytes()) + } + } + pub fn memory_mut(&mut self) -> &mut [u8] + { + unsafe { + slice::from_raw_parts_mut(self.ptr as *mut u8, self.len_bytes()) + } + } + + #[allow(unused_mut)] + pub fn into_boxed_slice(mut self) -> Box<[T]> + { + #[cfg(feature="assume_libc")] + unsafe { + let bx = Box::from_raw(self.as_slice_mut() as *mut [T]); + std::mem::forget(self); + bx + } + #[cfg(not(feature="assume_libc"))] + { + let vec = Vec::from(self); + return vec.into_boxed_slice(); + } + } +} + +impl Index for HeapArray +where I: SliceIndex<[T]> +{ + type Output = >::Output; + fn index(&self, index: I) -> &Self::Output + { + &self.as_slice()[index] + } +} + + +impl IndexMut for HeapArray +where I: SliceIndex<[T]> +{ + fn index_mut(&mut self, index: I) -> &mut >::Output + { + &mut self.as_slice_mut()[index] + } +} + +impl Drop for HeapArray +{ + fn drop(&mut self) + { + if self.ptr != ptr::null::() { + if self.drop_check { + for i in 0..self.size + { + unsafe { + drop(ptr::take(self.ptr.offset(i as isize))); + } + } + } + unsafe{alloc::free(self.ptr as VoidPointer)}; + self.ptr = ptr::null::(); + } + } +} + +impl AsMut<[T]> for HeapArray +{ + fn as_mut(&mut self) -> &mut [T] + { + self.as_slice_mut() + } +} +impl AsRef<[T]> for HeapArray +{ + fn as_ref(&self) -> &[T] + { + self.as_slice() + } +} + +impl Deref for HeapArray +{ + type Target = [T]; + fn deref(&self) -> &Self::Target + { + self.as_slice() + } +} +impl DerefMut for HeapArray +{ + fn deref_mut(&mut self) -> &mut ::Target + { + self.as_slice_mut() + } +} + +impl Borrow<[T]> for HeapArray +{ + fn borrow(&self) -> &[T] + { + self.as_slice() + } +} +impl BorrowMut<[T]> for HeapArray +{ + fn borrow_mut(&mut self) -> &mut [T] + { + self.as_slice_mut() + } +} + +impl From> for Vec +{ + fn from(ha: HeapArray) -> Self + { + let mut output = Vec::with_capacity(ha.len()); + for item in ha.into_iter() + { + output.push(item); + } + output + } +} + +mod iter; +pub use iter::*; diff --git a/src/ptr.rs b/src/ptr.rs new file mode 100644 index 0000000..cfd6ddc --- /dev/null +++ b/src/ptr.rs @@ -0,0 +1,35 @@ +use std::{ + ffi::c_void, + mem::{ + self, + MaybeUninit, + }, +}; +use libc::{ + size_t, + c_int, +}; + +pub type VoidPointer = *mut (); +pub type ConstVoidPointer = *const (); + +pub const NULL_PTR: VoidPointer = 0 as VoidPointer; + +pub fn null() -> *mut T +{ + NULL_PTR as *mut T +} + +pub unsafe fn memset(ptr: *mut u8, value: u8, length: usize) +{ + libc::memset(ptr as *mut c_void, value as c_int, length as size_t); +} + +pub unsafe fn replace(ptr: *mut T, value: T) -> T +{ + mem::replace(&mut *ptr, value) +} +pub unsafe fn take(ptr: *mut T) -> T +{ + mem::replace(&mut *ptr, MaybeUninit::zeroed().assume_init()) +} diff --git a/src/reinterpret.rs b/src/reinterpret.rs new file mode 100644 index 0000000..1e8f1dc --- /dev/null +++ b/src/reinterpret.rs @@ -0,0 +1,15 @@ +use std::{ + mem::size_of, +}; + +#[inline] +pub unsafe fn bytes(input: T) -> U +where T: Copy, + U: Copy +{ + //let _array: [(); size_of::() - size_of::()]; // rust is silly.... + if size_of::() < size_of::() { + panic!("reinterpret: Expected at least {} bytes, got {}.", size_of::(), size_of::()); + } + return *((&input as *const T) as *const U) +}