luau-vm 0.732.0

Pure-Rust Luau virtual machine, garbage collector, and standard libraries
Documentation
use core::alloc::Layout;
use core::mem::align_of;
use core::ptr::NonNull;

use crate::gc::RawGcObject;
use crate::memory::RawLuaPage;
use crate::state::{RawCallInfo, RawLuaState, RawMainState};
use crate::value::RawTValue;

pub const VM_ALLOC_ALIGN: usize = {
    let mut align = align_of::<usize>();
    if align_of::<RawMainState>() > align {
        align = align_of::<RawMainState>();
    }
    if align_of::<RawLuaPage>() > align {
        align = align_of::<RawLuaPage>();
    }
    if align_of::<RawLuaState>() > align {
        align = align_of::<RawLuaState>();
    }
    if align_of::<RawTValue>() > align {
        align = align_of::<RawTValue>();
    }
    if align_of::<RawCallInfo>() > align {
        align = align_of::<RawCallInfo>();
    }
    if align_of::<RawGcObject>() > align {
        align = align_of::<RawGcObject>();
    }
    align
};

/// Allocates memory for a Luau state.
///
/// The VM passes exact Rust layouts for every allocation. Implementations must
/// return pointers that satisfy the requested layout and must deallocate or
/// reallocate pointers using the corresponding previous layout. Reallocation to
/// an equal or smaller size must not fail.
///
/// # Safety
///
/// Returned pointers must be valid for reads and writes of `layout.size()`
/// bytes, aligned to `layout.align()`, and remain allocated until passed back
/// to `reallocate` or `deallocate`. `reallocate` must preserve the first
/// `old_layout.size().min(new_layout.size())` bytes on success, and must
/// return `Some` when `new_layout.size() <= old_layout.size()`.
#[allow(
    clippy::missing_safety_doc,
    reason = "allocator methods share the unsafe trait contract above"
)]
pub unsafe trait LuaAllocator {
    unsafe fn allocate(&self, layout: Layout) -> Option<NonNull<u8>>;

    unsafe fn reallocate(
        &self,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Option<NonNull<u8>>;

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout);
}

#[derive(Debug, Default, Clone, Copy)]
pub struct SystemLuaAllocator;

unsafe impl LuaAllocator for SystemLuaAllocator {
    unsafe fn allocate(&self, layout: Layout) -> Option<NonNull<u8>> {
        debug_assert!(layout.size() > 0);
        unsafe { NonNull::new(std::alloc::alloc(layout)) }
    }

    unsafe fn reallocate(
        &self,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Option<NonNull<u8>> {
        debug_assert!(old_layout.size() > 0);
        debug_assert!(new_layout.size() > 0);

        if old_layout.align() == new_layout.align() {
            unsafe {
                NonNull::new(std::alloc::realloc(
                    ptr.as_ptr(),
                    old_layout,
                    new_layout.size(),
                ))
            }
        } else {
            let new_ptr = unsafe { self.allocate(new_layout)? };
            unsafe {
                core::ptr::copy_nonoverlapping(
                    ptr.as_ptr(),
                    new_ptr.as_ptr(),
                    old_layout.size().min(new_layout.size()),
                );
                self.deallocate(ptr, old_layout);
            }
            Some(new_ptr)
        }
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        debug_assert!(layout.size() > 0);
        unsafe { std::alloc::dealloc(ptr.as_ptr(), layout) };
    }
}

pub(crate) enum VmAllocator {
    System(SystemLuaAllocator),
    Custom(Box<dyn LuaAllocator>),
}

impl VmAllocator {
    pub(crate) fn system() -> Self {
        Self::System(SystemLuaAllocator)
    }

    pub(crate) fn custom<A: LuaAllocator + 'static>(allocator: A) -> Self {
        Self::Custom(Box::new(allocator))
    }

    pub(crate) unsafe fn allocate(&self, layout: Layout) -> Option<NonNull<u8>> {
        match &self {
            Self::System(allocator) => unsafe { allocator.allocate(layout) },
            Self::Custom(allocator) => unsafe { allocator.allocate(layout) },
        }
    }

    pub(crate) unsafe fn reallocate(
        &self,
        ptr: NonNull<u8>,
        old_layout: Layout,
        new_layout: Layout,
    ) -> Option<NonNull<u8>> {
        match &self {
            Self::System(allocator) => unsafe { allocator.reallocate(ptr, old_layout, new_layout) },
            Self::Custom(allocator) => unsafe { allocator.reallocate(ptr, old_layout, new_layout) },
        }
    }

    pub(crate) unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        match &self {
            Self::System(allocator) => unsafe { allocator.deallocate(ptr, layout) },
            Self::Custom(allocator) => unsafe { allocator.deallocate(ptr, layout) },
        }
    }
}