Skip to main content

luau_vm/function/
closure.rs

1use core::mem::{offset_of, size_of};
2use core::ptr::{self, NonNull};
3
4use crate::Table;
5use crate::gc::GcObject;
6use crate::gc::RawGcObject;
7use crate::handle::RawHandle;
8use crate::native::{RawNativeContinuation, RawNativeFunction};
9use crate::string::{LuaString, RawTString, TString};
10use crate::table::RawLuaTable;
11use crate::value::{RawTValue, TValue, TValueCursor};
12
13use super::{Proto, RawProto};
14
15#[repr(C)]
16pub struct RawClosure {
17    pub tt: u8,
18    pub marked: u8,
19    pub memcat: u8,
20    pub is_native: u8,
21    pub n_upvalues: u8,
22    pub stack_size: u8,
23    pub preload: u8,
24    pub gc_list: *mut RawGcObject,
25    pub env: *mut RawLuaTable,
26    pub data: [u8; 0],
27}
28
29#[repr(C)]
30#[derive(Clone, Copy)]
31pub struct RawNativeClosure {
32    pub function: Option<RawNativeFunction>,
33    pub continuation: Option<RawNativeContinuation>,
34    pub debug_name_deprecated: Option<&'static str>,
35    pub debug_name: *mut RawTString,
36}
37
38#[repr(C)]
39#[derive(Clone, Copy)]
40pub struct RawLuaClosure {
41    pub proto: *mut RawProto,
42}
43
44#[derive(Clone, Copy, PartialEq, Eq)]
45#[repr(transparent)]
46/// Non-owning identity of a VM closure record.
47///
48/// # Safety model for unsafe methods
49///
50/// The closure and referenced environment, proto, upvalues, and GC objects
51/// must remain live in the same VM. The native/Luau variant and every index
52/// must match the allocated closure layout.
53pub struct Closure {
54    raw: NonNull<RawClosure>,
55}
56
57#[allow(
58    clippy::missing_safety_doc,
59    reason = "Closure's shared raw-handle contract is documented on Closure"
60)]
61impl Closure {
62    pub const unsafe fn from_raw(raw: NonNull<RawClosure>) -> Self {
63        Self { raw }
64    }
65
66    pub fn size_native_closure(n: usize) -> usize {
67        offset_of!(RawClosure, data) + size_of::<RawNativeClosure>() + size_of::<RawTValue>() * n
68    }
69
70    pub fn size_lua_closure(n: usize) -> usize {
71        offset_of!(RawClosure, data) + size_of::<RawLuaClosure>() + size_of::<RawTValue>() * n
72    }
73
74    /// `sizeclosure`
75    pub unsafe fn size(&self) -> usize {
76        unsafe {
77            if self.is_native() {
78                Closure::size_native_closure(
79                    self.as_ptr().as_ref().unwrap_unchecked().n_upvalues as usize,
80                )
81            } else {
82                Closure::size_lua_closure(
83                    self.as_ptr().as_ref().unwrap_unchecked().n_upvalues as usize,
84                )
85            }
86        }
87    }
88
89    pub unsafe fn is_native(&self) -> bool {
90        unsafe { self.as_ptr().as_ref().unwrap_unchecked().is_native != 0 }
91    }
92
93    pub unsafe fn is_lua(&self) -> bool {
94        unsafe { !self.is_native() }
95    }
96
97    pub unsafe fn proto(&self) -> Option<Proto> {
98        unsafe {
99            if self.is_lua() {
100                NonNull::new(self.lua_data().proto).map(|raw| Proto::from_raw(raw))
101            } else {
102                None
103            }
104        }
105    }
106
107    pub unsafe fn env(&self) -> Table {
108        unsafe {
109            Table::from_raw(NonNull::new_unchecked(
110                self.as_ptr().as_ref().unwrap_unchecked().env,
111            ))
112        }
113    }
114
115    pub unsafe fn gc_list(&self) -> Option<GcObject> {
116        unsafe {
117            NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().gc_list)
118                .map(|raw| GcObject::from_raw(raw))
119        }
120    }
121
122    pub unsafe fn set_gc_list(&self, gc_list: Option<GcObject>) {
123        unsafe {
124            self.as_ptr().as_mut().unwrap_unchecked().gc_list =
125                gc_list.map_or(ptr::null_mut(), |object| object.as_ptr());
126        }
127    }
128
129    pub unsafe fn set_env(&self, env: Table) {
130        unsafe {
131            self.as_ptr().as_mut().unwrap_unchecked().env = env.as_ptr();
132        }
133    }
134
135    pub unsafe fn native_data(&self) -> RawNativeClosure {
136        debug_assert!(unsafe { self.is_native() });
137        unsafe { self.data_ptr().cast::<RawNativeClosure>().read() }
138    }
139
140    pub unsafe fn native_data_ptr(&self) -> *mut RawNativeClosure {
141        debug_assert!(unsafe { self.is_native() });
142        self.data_ptr().cast()
143    }
144
145    pub unsafe fn set_native_data(&self, data: RawNativeClosure) {
146        unsafe { self.native_data_ptr().write(data) };
147    }
148
149    pub unsafe fn native_debug_name(&self) -> Option<LuaString> {
150        debug_assert!(unsafe { self.is_native() });
151        let data = unsafe { self.native_data() };
152        if luau_common::flags::LuauManagedDebugNames.get() {
153            NonNull::new(data.debug_name)
154                .map(|raw| LuaString::from_interned(unsafe { TString::from_raw(raw) }))
155        } else {
156            data.debug_name_deprecated
157                .map(|name| LuaString::from_static(luau_common::BStr::new(name.as_bytes())))
158        }
159    }
160
161    pub unsafe fn lua_data(&self) -> RawLuaClosure {
162        debug_assert!(unsafe { self.is_lua() });
163        unsafe { self.data_ptr().cast::<RawLuaClosure>().read() }
164    }
165
166    pub unsafe fn lua_data_ptr(&self) -> *mut RawLuaClosure {
167        debug_assert!(unsafe { self.is_lua() });
168        self.data_ptr().cast()
169    }
170
171    pub unsafe fn set_lua_proto(&self, proto: Proto) {
172        unsafe {
173            self.lua_data_ptr().write(RawLuaClosure {
174                proto: proto.as_ptr(),
175            })
176        };
177    }
178
179    pub unsafe fn native_upvalue(&self, index: usize) -> TValue {
180        unsafe {
181            debug_assert!(index < self.as_ptr().as_ref().unwrap_unchecked().n_upvalues as usize);
182            TValue::from_raw(NonNull::new_unchecked(
183                self.native_upvalues_ptr().add(index),
184            ))
185        }
186    }
187
188    pub unsafe fn native_upvalues(&self) -> TValueCursor {
189        unsafe { TValueCursor::from_ptr(self.native_upvalues_ptr()) }
190    }
191
192    pub unsafe fn lua_upvalue_ref(&self, index: usize) -> TValue {
193        unsafe {
194            debug_assert!(index < self.as_ptr().as_ref().unwrap_unchecked().n_upvalues as usize);
195            TValue::from_raw(NonNull::new_unchecked(
196                self.lua_upvalue_refs_ptr().add(index),
197            ))
198        }
199    }
200
201    pub unsafe fn lua_upvalue_refs(&self) -> TValueCursor {
202        unsafe { TValueCursor::from_ptr(self.lua_upvalue_refs_ptr()) }
203    }
204
205    fn data_ptr(&self) -> *mut u8 {
206        unsafe { self.as_ptr().cast::<u8>().add(offset_of!(RawClosure, data)) }
207    }
208
209    unsafe fn native_upvalues_ptr(&self) -> *mut RawTValue {
210        debug_assert!(unsafe { self.is_native() });
211        unsafe {
212            self.data_ptr()
213                .add(size_of::<RawNativeClosure>())
214                .cast::<RawTValue>()
215        }
216    }
217
218    unsafe fn lua_upvalue_refs_ptr(&self) -> *mut RawTValue {
219        debug_assert!(unsafe { self.is_lua() });
220        unsafe {
221            self.data_ptr()
222                .add(size_of::<RawLuaClosure>())
223                .cast::<RawTValue>()
224        }
225    }
226}
227impl crate::handle::sealed::Sealed for Closure {}
228impl RawHandle for Closure {
229    type Raw = RawClosure;
230
231    fn as_ptr(&self) -> *mut Self::Raw {
232        self.raw.as_ptr()
233    }
234}
235
236impl AsRef<Closure> for Closure {
237    fn as_ref(&self) -> &Closure {
238        self
239    }
240}