ferroc 0.3.0

A fast & lock-free memory allocator library
Documentation
#[cfg(not(sys_alloc))]
use core::ffi::c_char;
use core::{
    alloc::Layout,
    ffi::{c_int, c_void},
    num::NonZeroUsize,
    ptr::{self, NonNull},
};

use crate::{base::Mmap, Ferroc};

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_malloc")]
pub extern "C" fn malloc(size: usize) -> *mut c_void {
    let Some(size) = NonZeroUsize::new(size) else {
        return ptr::null_mut();
    };
    Ferroc
        .malloc(size, false)
        .map_or(ptr::null_mut(), |ptr| ptr.as_ptr().cast())
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_posix_memalign")]
pub unsafe extern "C" fn posix_memalign(
    slot: *mut *mut c_void,
    align: usize,
    size: usize,
) -> c_int {
    let Ok(layout) = Layout::from_size_align(size, align) else {
        return libc::EINVAL;
    };
    if slot.is_null() {
        return libc::EINVAL;
    }
    if let Ok(ptr) = Ferroc.allocate(layout) {
        slot.write(ptr.as_ptr().cast());
        return 0;
    }
    libc::ENOMEM
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_aligned_alloc")]
pub extern "C" fn aligned_alloc(align: usize, size: usize) -> *mut c_void {
    let Ok(layout) = Layout::from_size_align(size, align) else {
        return ptr::null_mut();
    };
    Ferroc
        .allocate(layout)
        .map_or(ptr::null_mut(), |ptr| ptr.as_ptr().cast())
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_memalign")]
pub extern "C" fn memalign(align: usize, size: usize) -> *mut c_void {
    let Ok(layout) = Layout::from_size_align(size, align) else {
        return ptr::null_mut();
    };
    Ferroc
        .allocate(layout)
        .map_or(ptr::null_mut(), |ptr| ptr.as_ptr().cast())
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_valloc")]
pub extern "C" fn valloc(size: usize) -> *mut c_void {
    aligned_alloc(Mmap.page_size(), size)
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_pvalloc")]
pub extern "C" fn pvalloc(size: usize) -> *mut c_void {
    let page_size = Mmap.page_size();
    let rounded_size = (size + page_size - 1) & !(page_size - 1);
    aligned_alloc(page_size, rounded_size)
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_malloc_usable_size")]
pub unsafe extern "C" fn malloc_usable_size(ptr: *mut c_void) -> usize {
    match NonNull::new(ptr) {
        Some(ptr) => Ferroc.layout_of(ptr.cast()).map_or(0, |l| l.size()),
        None => 0,
    }
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_free")]
pub unsafe extern "C" fn free(ptr: *mut c_void) {
    if let Some(ptr) = NonNull::new(ptr) {
        unsafe { Ferroc.free(ptr.cast()) }
    }
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_calloc")]
pub extern "C" fn calloc(nmemb: usize, size: usize) -> *mut c_void {
    let Some(size) = nmemb.checked_mul(size) else {
        return ptr::null_mut();
    };
    let Some(size) = NonZeroUsize::new(size) else {
        return ptr::null_mut();
    };
    Ferroc
        .malloc(size, true)
        .map_or(ptr::null_mut(), |ptr| ptr.as_ptr().cast())
}

#[no_mangle]
#[cfg_attr(not(sys_alloc), export_name = "fe_realloc")]
pub unsafe extern "C" fn realloc(ptr: *mut c_void, new_size: usize) -> *mut c_void {
    let Some(ptr) = NonNull::new(ptr) else {
        return malloc(new_size);
    };
    if new_size == 0 {
        free(ptr.as_ptr().cast());
        return malloc(new_size);
    }
    let Some(layout) = Ferroc.layout_of(ptr.cast()) else {
        return ptr::null_mut();
    };
    let old_size = layout.size();
    if (old_size / 2..old_size).contains(&new_size) {
        return ptr.as_ptr();
    }

    let new = malloc(new_size);
    if !new.is_null() {
        let copied = old_size.min(new_size);
        new.copy_from_nonoverlapping(ptr.as_ptr(), copied);
        if let Some(zeroed) = new_size.checked_sub(old_size) {
            new.add(copied).write_bytes(0, zeroed);
        }
        free(ptr.as_ptr().cast());
    }
    new
}

#[no_mangle]
#[cfg(not(sys_alloc))]
#[export_name = "fe_strdup"]
pub unsafe extern "C" fn strdup(s: *const c_char) -> *mut c_char {
    let len = libc::strlen(s);
    let ptr = malloc(len + 1).cast::<c_char>();
    if !ptr.is_null() {
        ptr.copy_from_nonoverlapping(s, len);
        ptr.add(len).write(0);
    }
    ptr
}

#[no_mangle]
#[cfg(not(sys_alloc))]
#[export_name = "fe_strndup"]
pub unsafe extern "C" fn strndup(s: *const c_char, n: usize) -> *mut c_char {
    let len = libc::strnlen(s, n);
    let ptr = malloc(len + 1).cast::<c_char>();
    if !ptr.is_null() {
        ptr.copy_from_nonoverlapping(s, len);
        ptr.add(len).write(0);
    }
    ptr
}

#[no_mangle]
#[cfg(not(sys_alloc))]
#[export_name = "fe_realpath"]
pub unsafe extern "C" fn realpath(name: *const c_char, resolved: *mut c_char) -> *mut c_char {
    if !resolved.is_null() {
        return libc::realpath(name, resolved);
    }
    let r = libc::realpath(name, ptr::null_mut());
    if r.is_null() {
        return ptr::null_mut();
    }
    let dupped = strdup(r);
    libc::free(r as *mut c_void);
    dupped
}

#[cfg(sys_alloc)]
mod forward {
    use super::*;

    macro_rules! forward {
        (@IMPL $name:ident($($aname:ident, $atype:ty),*) $(-> $ret:ty)? => $target:ident) => {
            #[no_mangle]
            pub unsafe extern "C" fn $name($($aname: $atype),*) $(-> $ret)? {
                $target($($aname),*)
            }
        };
        ($($name:ident($($aname:ident: $atype:ty),*) $(-> $ret:ty)? => $target:ident;)*) => {
            $(forward!(@IMPL $name($($aname, $atype),*) $(-> $ret)? => $target);)*
        };
    }

    forward! {
        __libc_malloc(size: usize) -> *mut c_void => malloc;
        __libc_calloc(count: usize, size: usize) -> *mut c_void => calloc;
        __libc_realloc(p: *mut c_void, size: usize) -> *mut c_void => realloc;
        __libc_free(p: *mut c_void) => free;
        __libc_cfree(p: *mut c_void) => free;
        __libc_valloc(size: usize) -> *mut c_void => valloc;
        __libc_pvalloc(size: usize) -> *mut c_void => pvalloc;
        __libc_memalign(align: usize, size: usize) -> *mut c_void => memalign;
        __posix_memalign(memptr: *mut *mut c_void, alignment: usize, size: usize) -> c_int
            => posix_memalign;
    }
}