Skip to main content

luau_vm/
buffer.rs

1use core::ptr::NonNull;
2
3use crate::VmErrorResult;
4use crate::gc::GcObject;
5use crate::handle::{RawHandle, sealed::Sealed};
6use crate::layout::Align8Byte;
7use crate::memory::{LuaPage, MemoryRuntime};
8use crate::thread::Thread;
9use crate::types::LUA_TBUFFER;
10
11#[repr(C)]
12pub struct RawBuffer {
13    pub tt: u8,
14    pub marked: u8,
15    pub memcat: u8,
16    pub len: u32,
17    pub data: [Align8Byte; 1],
18}
19
20pub const MAX_BUFFER_SIZE: usize = 1 << 30;
21
22#[derive(Clone, Copy)]
23#[repr(transparent)]
24pub struct Buffer {
25    raw: NonNull<RawBuffer>,
26}
27
28/// Unstable buffer allocation capability.
29///
30/// # Safety
31///
32/// Every operation requires a live thread and buffer/page handles from that
33/// thread's VM. Sizes and deallocation pages must describe the original
34/// allocation, and callers must preserve GC reachability while operating.
35#[allow(
36    clippy::missing_safety_doc,
37    reason = "all methods share the capability-level safety contract"
38)]
39pub trait BufferRuntime: Sealed {
40    /// `luaB_newbuffer`
41    unsafe fn new_buffer_internal(&self, size: usize) -> VmErrorResult<Buffer>;
42
43    /// `luaB_freebuffer`
44    unsafe fn free_buffer(&self, buffer: Buffer, page: LuaPage);
45}
46
47impl Buffer {
48    /// Constructs a non-owning buffer handle.
49    ///
50    /// # Safety
51    ///
52    /// `raw` must address a live buffer allocated by the owning VM. The caller
53    /// must not use the handle after the buffer is collected or freed.
54    pub const unsafe fn from_raw(raw: NonNull<RawBuffer>) -> Self {
55        Self { raw }
56    }
57
58    pub const fn size_buffer(len: usize) -> usize {
59        core::mem::offset_of!(RawBuffer, data) + if len > 8 { len } else { 8 }
60    }
61
62    pub fn len(&self) -> usize {
63        unsafe { (*self.as_ptr()).len as usize }
64    }
65
66    pub fn is_empty(&self) -> bool {
67        self.len() == 0
68    }
69
70    pub const fn data_ptr(&self) -> *const u8 {
71        unsafe { (&raw const (*self.raw.as_ptr()).data).cast::<u8>() }
72    }
73
74    /// Returns the mutable payload address without creating a reference.
75    ///
76    /// # Safety
77    ///
78    /// The buffer must remain live and the caller must enforce aliasing for
79    /// every access through the returned pointer.
80    pub unsafe fn data_mut_ptr(&self) -> *mut u8 {
81        unsafe { (&raw mut (*self.raw.as_ptr()).data).cast::<u8>() }
82    }
83}
84
85impl Sealed for Buffer {}
86
87impl RawHandle for Buffer {
88    type Raw = RawBuffer;
89
90    fn as_ptr(&self) -> *mut Self::Raw {
91        self.raw.as_ptr()
92    }
93}
94
95impl AsRef<Buffer> for Buffer {
96    fn as_ref(&self) -> &Buffer {
97        self
98    }
99}
100
101impl BufferRuntime for Thread {
102    /// `luaB_newbuffer`
103    unsafe fn new_buffer_internal(&self, size: usize) -> VmErrorResult<Buffer> {
104        if size > MAX_BUFFER_SIZE {
105            return unsafe { self.too_big() };
106        }
107
108        unsafe {
109            let buffer = self.new_gco::<Buffer>(
110                Buffer::size_buffer(size),
111                self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
112            )?;
113            GcObject::from(buffer).init_header(self, LUA_TBUFFER as u8);
114            buffer.as_ptr().as_mut().unwrap_unchecked().len = size as u32;
115            core::ptr::write_bytes(buffer.data_mut_ptr(), 0, size);
116
117            Ok(buffer)
118        }
119    }
120
121    /// `luaB_freebuffer`
122    unsafe fn free_buffer(&self, buffer: Buffer, page: LuaPage) {
123        unsafe {
124            let raw = buffer.as_ptr().as_ref().unwrap_unchecked();
125            self.free_gco(
126                buffer.into(),
127                Buffer::size_buffer(raw.len as usize),
128                raw.memcat,
129                page,
130            );
131        }
132    }
133}