luau-vm 0.732.0

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

use crate::VmErrorResult;
use crate::gc::GcObject;
use crate::handle::{RawHandle, sealed::Sealed};
use crate::layout::Align8Byte;
use crate::memory::{LuaPage, MemoryRuntime};
use crate::thread::Thread;
use crate::types::LUA_TBUFFER;

#[repr(C)]
pub struct RawBuffer {
    pub tt: u8,
    pub marked: u8,
    pub memcat: u8,
    pub len: u32,
    pub data: [Align8Byte; 1],
}

pub const MAX_BUFFER_SIZE: usize = 1 << 30;

#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Buffer {
    raw: NonNull<RawBuffer>,
}

/// Unstable buffer allocation capability.
///
/// # Safety
///
/// Every operation requires a live thread and buffer/page handles from that
/// thread's VM. Sizes and deallocation pages must describe the original
/// allocation, and callers must preserve GC reachability while operating.
#[allow(
    clippy::missing_safety_doc,
    reason = "all methods share the capability-level safety contract"
)]
pub trait BufferRuntime: Sealed {
    /// `luaB_newbuffer`
    unsafe fn new_buffer_internal(&self, size: usize) -> VmErrorResult<Buffer>;

    /// `luaB_freebuffer`
    unsafe fn free_buffer(&self, buffer: Buffer, page: LuaPage);
}

impl Buffer {
    /// Constructs a non-owning buffer handle.
    ///
    /// # Safety
    ///
    /// `raw` must address a live buffer allocated by the owning VM. The caller
    /// must not use the handle after the buffer is collected or freed.
    pub const unsafe fn from_raw(raw: NonNull<RawBuffer>) -> Self {
        Self { raw }
    }

    pub const fn size_buffer(len: usize) -> usize {
        core::mem::offset_of!(RawBuffer, data) + if len > 8 { len } else { 8 }
    }

    pub fn len(&self) -> usize {
        unsafe { (*self.as_ptr()).len as usize }
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    pub const fn data_ptr(&self) -> *const u8 {
        unsafe { (&raw const (*self.raw.as_ptr()).data).cast::<u8>() }
    }

    /// Returns the mutable payload address without creating a reference.
    ///
    /// # Safety
    ///
    /// The buffer must remain live and the caller must enforce aliasing for
    /// every access through the returned pointer.
    pub unsafe fn data_mut_ptr(&self) -> *mut u8 {
        unsafe { (&raw mut (*self.raw.as_ptr()).data).cast::<u8>() }
    }
}

impl Sealed for Buffer {}

impl RawHandle for Buffer {
    type Raw = RawBuffer;

    fn as_ptr(&self) -> *mut Self::Raw {
        self.raw.as_ptr()
    }
}

impl AsRef<Buffer> for Buffer {
    fn as_ref(&self) -> &Buffer {
        self
    }
}

impl BufferRuntime for Thread {
    /// `luaB_newbuffer`
    unsafe fn new_buffer_internal(&self, size: usize) -> VmErrorResult<Buffer> {
        if size > MAX_BUFFER_SIZE {
            return unsafe { self.too_big() };
        }

        unsafe {
            let buffer = self.new_gco::<Buffer>(
                Buffer::size_buffer(size),
                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
            )?;
            GcObject::from(buffer).init_header(self, LUA_TBUFFER as u8);
            buffer.as_ptr().as_mut().unwrap_unchecked().len = size as u32;
            core::ptr::write_bytes(buffer.data_mut_ptr(), 0, size);

            Ok(buffer)
        }
    }

    /// `luaB_freebuffer`
    unsafe fn free_buffer(&self, buffer: Buffer, page: LuaPage) {
        unsafe {
            let raw = buffer.as_ptr().as_ref().unwrap_unchecked();
            self.free_gco(
                buffer.into(),
                Buffer::size_buffer(raw.len as usize),
                raw.memcat,
                page,
            );
        }
    }
}