Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 36 additions & 46 deletions crates/luars/src/gc/gc_object.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
LuaProto, LuaRawFunction, LuaRawTable,
gc::GcObjectKind,
gc::{GcObjectKind, Pooled},
lua_value::{CClosureFunction, LuaString, LuaUpvalue, LuaUserdata, RClosureFunction},
lua_vm::LuaState,
};
Expand Down Expand Up @@ -727,15 +727,15 @@ impl From<ProtoPtr> for GcObjectPtr {

// ============ GC-managed Objects ============
pub enum GcObjectOwner {
String(Box<GcString>),
Table(Box<GcTable>),
Function(Box<GcFunction>),
Upvalue(Box<GcUpvalue>),
String(Pooled<GcString>),
Table(Pooled<GcTable>),
Function(Pooled<GcFunction>),
Upvalue(Pooled<GcUpvalue>),
Thread(Box<GcThread>),
Userdata(Box<GcUserdata>),
CClosure(Box<GcCClosure>),
RClosure(Box<GcRClosure>),
Proto(Box<GcProto>),
Userdata(Pooled<GcUserdata>),
CClosure(Pooled<GcCClosure>),
RClosure(Pooled<GcRClosure>),
Proto(Pooled<GcProto>),
}

impl GcObjectOwner {
Expand All @@ -748,15 +748,15 @@ impl GcObjectOwner {
#[inline(always)]
fn raw_header_ptr(&self) -> *mut GcHeader {
match self {
GcObjectOwner::String(s) => Self::box_raw_ptr(s) as *mut GcHeader,
GcObjectOwner::Table(t) => Self::box_raw_ptr(t) as *mut GcHeader,
GcObjectOwner::Function(f) => Self::box_raw_ptr(f) as *mut GcHeader,
GcObjectOwner::CClosure(c) => Self::box_raw_ptr(c) as *mut GcHeader,
GcObjectOwner::RClosure(r) => Self::box_raw_ptr(r) as *mut GcHeader,
GcObjectOwner::Upvalue(u) => Self::box_raw_ptr(u) as *mut GcHeader,
GcObjectOwner::Thread(t) => Self::box_raw_ptr(t) as *mut GcHeader,
GcObjectOwner::Userdata(u) => Self::box_raw_ptr(u) as *mut GcHeader,
GcObjectOwner::Proto(p) => Self::box_raw_ptr(p) as *mut GcHeader,
GcObjectOwner::String(s) => s.as_ptr() as *mut GcHeader,
GcObjectOwner::Table(t) => t.as_ptr() as *mut GcHeader,
GcObjectOwner::Function(f) => f.as_ptr() as *mut GcHeader,
GcObjectOwner::CClosure(c) => c.as_ptr() as *mut GcHeader,
GcObjectOwner::RClosure(r) => r.as_ptr() as *mut GcHeader,
GcObjectOwner::Upvalue(u) => u.as_ptr() as *mut GcHeader,
GcObjectOwner::Thread(t) => t.as_ref() as *const _ as *mut GcHeader,
GcObjectOwner::Userdata(u) => u.as_ptr() as *mut GcHeader,
GcObjectOwner::Proto(p) => p.as_ptr() as *mut GcHeader,
}
}

Expand All @@ -768,92 +768,82 @@ impl GcObjectOwner {
unsafe { &*self.raw_header_ptr() }
}

/// Read the heap pointer from a `&Box<T>` without going through `Deref`,
/// avoiding Miri's Stacked Borrows retag that would be invalidated when
/// the Box moves into GcList. Box<T> is layout-compatible with *const T
/// on the default allocator — this reads the internal raw pointer directly.
#[inline(always)]
#[allow(clippy::borrowed_box)]
fn box_raw_ptr<T>(b: &Box<T>) -> *const T {
unsafe { *(b as *const Box<T> as *const *const T) }
}

/// Get type tag of this object
#[inline(always)]
pub fn as_str_ptr(&self) -> Option<StringPtr> {
match self {
GcObjectOwner::String(s) => Some(StringPtr::new(Self::box_raw_ptr(s))),
GcObjectOwner::String(s) => Some(StringPtr::new(s.as_ptr())),
_ => None,
}
}

pub fn as_table_ptr(&self) -> Option<TablePtr> {
match self {
GcObjectOwner::Table(t) => Some(TablePtr::new(Self::box_raw_ptr(t))),
GcObjectOwner::Table(t) => Some(TablePtr::new(t.as_ptr())),
_ => None,
}
}

pub fn as_function_ptr(&self) -> Option<FunctionPtr> {
match self {
GcObjectOwner::Function(f) => Some(FunctionPtr::new(Self::box_raw_ptr(f))),
GcObjectOwner::Function(f) => Some(FunctionPtr::new(f.as_ptr())),
_ => None,
}
}

pub fn as_upvalue_ptr(&self) -> Option<UpvaluePtr> {
match self {
GcObjectOwner::Upvalue(u) => Some(UpvaluePtr::new(Self::box_raw_ptr(u))),
GcObjectOwner::Upvalue(u) => Some(UpvaluePtr::new(u.as_ptr())),
_ => None,
}
}

pub fn as_thread_ptr(&self) -> Option<ThreadPtr> {
match self {
GcObjectOwner::Thread(t) => Some(ThreadPtr::new(Self::box_raw_ptr(t))),
GcObjectOwner::Thread(t) => Some(ThreadPtr::new(t.as_ref() as *const _)),
_ => None,
}
}

pub fn as_userdata_ptr(&self) -> Option<UserdataPtr> {
match self {
GcObjectOwner::Userdata(u) => Some(UserdataPtr::new(Self::box_raw_ptr(u))),
GcObjectOwner::Userdata(u) => Some(UserdataPtr::new(u.as_ptr())),
_ => None,
}
}

pub fn as_closure_ptr(&self) -> Option<CClosurePtr> {
match self {
GcObjectOwner::CClosure(c) => Some(CClosurePtr::new(Self::box_raw_ptr(c))),
GcObjectOwner::CClosure(c) => Some(CClosurePtr::new(c.as_ptr())),
_ => None,
}
}

pub fn as_rclosure_ptr(&self) -> Option<RClosurePtr> {
match self {
GcObjectOwner::RClosure(r) => Some(RClosurePtr::new(Self::box_raw_ptr(r))),
GcObjectOwner::RClosure(r) => Some(RClosurePtr::new(r.as_ptr())),
_ => None,
}
}

pub fn as_proto_ptr(&self) -> Option<ProtoPtr> {
match self {
GcObjectOwner::Proto(p) => Some(ProtoPtr::new(Self::box_raw_ptr(p))),
GcObjectOwner::Proto(p) => Some(ProtoPtr::new(p.as_ptr())),
_ => None,
}
}

pub fn as_gc_ptr(&self) -> GcObjectPtr {
match self {
GcObjectOwner::String(s) => GcObjectPtr::from(StringPtr::new(Self::box_raw_ptr(s))),
GcObjectOwner::Table(t) => GcObjectPtr::from(TablePtr::new(Self::box_raw_ptr(t))),
GcObjectOwner::Function(f) => GcObjectPtr::from(FunctionPtr::new(Self::box_raw_ptr(f))),
GcObjectOwner::Upvalue(u) => GcObjectPtr::from(UpvaluePtr::new(Self::box_raw_ptr(u))),
GcObjectOwner::Thread(t) => GcObjectPtr::from(ThreadPtr::new(Self::box_raw_ptr(t))),
GcObjectOwner::Userdata(u) => GcObjectPtr::from(UserdataPtr::new(Self::box_raw_ptr(u))),
GcObjectOwner::CClosure(c) => GcObjectPtr::from(CClosurePtr::new(Self::box_raw_ptr(c))),
GcObjectOwner::RClosure(r) => GcObjectPtr::from(RClosurePtr::new(Self::box_raw_ptr(r))),
GcObjectOwner::Proto(p) => GcObjectPtr::from(ProtoPtr::new(Self::box_raw_ptr(p))),
GcObjectOwner::String(s) => GcObjectPtr::from(StringPtr::new(s.as_ptr())),
GcObjectOwner::Table(t) => GcObjectPtr::from(TablePtr::new(t.as_ptr())),
GcObjectOwner::Function(f) => GcObjectPtr::from(FunctionPtr::new(f.as_ptr())),
GcObjectOwner::Upvalue(u) => GcObjectPtr::from(UpvaluePtr::new(u.as_ptr())),
GcObjectOwner::Thread(t) => GcObjectPtr::from(ThreadPtr::new(t.as_ref() as *const _)),
GcObjectOwner::Userdata(u) => GcObjectPtr::from(UserdataPtr::new(u.as_ptr())),
GcObjectOwner::CClosure(c) => GcObjectPtr::from(CClosurePtr::new(c.as_ptr())),
GcObjectOwner::RClosure(r) => GcObjectPtr::from(RClosurePtr::new(r.as_ptr())),
GcObjectOwner::Proto(p) => GcObjectPtr::from(ProtoPtr::new(p.as_ptr())),
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/luars/src/gc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
mod gc_kind;
mod gc_object;
mod object_allocator;
mod paged_pool;
mod string_interner;
mod table_allocator;

use std::collections::HashSet;

Expand All @@ -49,7 +51,9 @@ use crate::{
pub use gc_kind::*;
pub use gc_object::*;
pub use object_allocator::*;
pub use paged_pool::*;
pub use string_interner::*;
pub use table_allocator::*;

// GC Parameters (from lua.h)
pub const MINORMUL: usize = 0; // Minor collection multiplier
Expand Down
62 changes: 46 additions & 16 deletions crates/luars/src/gc/object_allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,25 @@ use crate::lua_vm::{CFunction, LuaState};
use crate::{
LuaRawFunction, LuaRawTable, LuaResult, LuaValue,
gc::{
GC, GcCClosure, GcFunction, GcObjectOwner, GcProto, GcRClosure, GcTable, GcThread,
GcUpvalue, GcUserdata, ProtoPtr, StringPtr, UpvaluePtr,
GC, GcCClosure, GcFunction, GcObjectOwner, GcProto, GcRClosure, GcString, GcTable,
GcThread, GcUpvalue, GcUserdata, PagedPool, ProtoPtr, StringPtr, TableAllocHandle,
UpvaluePtr,
},
};

pub type CreateResult = LuaResult<LuaValue>;

pub struct ObjectAllocator {
strings: StringInterner, // Private - use create_string() to intern
string_pool: PagedPool<GcString>,
table_pool: PagedPool<GcTable>,
function_pool: PagedPool<GcFunction>,
cclosure_pool: PagedPool<GcCClosure>,
rclosure_pool: PagedPool<GcRClosure>,
upvalue_pool: PagedPool<GcUpvalue>,
userdata_pool: PagedPool<GcUserdata>,
proto_pool: PagedPool<GcProto>,
table_allocator: TableAllocHandle,
}

impl Default for ObjectAllocator {
Expand All @@ -28,35 +38,45 @@ impl ObjectAllocator {
pub fn new() -> Self {
Self {
strings: StringInterner::new(),
string_pool: PagedPool::new(128),
table_pool: PagedPool::new(64),
function_pool: PagedPool::new(32),
cclosure_pool: PagedPool::new(16),
rclosure_pool: PagedPool::new(16),
upvalue_pool: PagedPool::new(64),
userdata_pool: PagedPool::new(16),
proto_pool: PagedPool::new(16),
table_allocator: TableAllocHandle::default(),
}
}

/// Create or intern a string (Lua-style with proper hash collision handling)
///
#[inline]
pub fn create_string(&mut self, gc: &mut GC, s: &str) -> CreateResult {
self.strings.intern(s, gc)
self.strings.intern(s, gc, &mut self.string_pool)
}

/// Create string from owned String (avoids clone if not already interned)
///
#[inline]
pub fn create_string_owned(&mut self, gc: &mut GC, s: String) -> CreateResult {
self.strings.intern_owned(s, gc)
self.strings.intern_owned(s, gc, &mut self.string_pool)
}

/// Create a Lua string-like value from raw bytes.
/// All short byte strings are interned so Lua string equality keeps its fast path.
#[inline]
pub fn create_bytes(&mut self, gc: &mut GC, bytes: &[u8]) -> CreateResult {
self.strings.intern_bytes(bytes, gc)
self.strings.intern_bytes(bytes, gc, &mut self.string_pool)
}

/// Create a raw byte string from Vec<u8> without requiring UTF-8.
/// This compatibility path now uses the same byte-string interning rules as `create_bytes`.
#[inline]
pub fn create_binary(&mut self, gc: &mut GC, data: Vec<u8>) -> CreateResult {
self.strings.intern_bytes_owned(data, gc)
self.strings
.intern_bytes_owned(data, gc, &mut self.string_pool)
}

// ==================== Table Operations ====================
Expand Down Expand Up @@ -93,8 +113,12 @@ impl ObjectAllocator {
0
};
let size = (base_size + array_bytes + hash_bytes) as u32;
let ptr = Box::new(GcTable::new(
LuaRawTable::new(array_size as u32, hash_size as u32),
let ptr = self.table_pool.alloc(GcTable::new(
LuaRawTable::new(
array_size as u32,
hash_size as u32,
self.table_allocator.clone(),
),
current_white,
size,
));
Expand All @@ -110,7 +134,11 @@ impl ObjectAllocator {
pub fn create_proto(&mut self, gc: &mut GC, chunk: LuaProto) -> LuaResult<ProtoPtr> {
let current_white = gc.current_white;
let size = std::mem::size_of::<GcProto>() as u32 + chunk.proto_data_size;
let gc_proto = GcObjectOwner::Proto(Box::new(GcProto::new(chunk, current_white, size)));
let gc_proto = GcObjectOwner::Proto(self.proto_pool.alloc(GcProto::new(
chunk,
current_white,
size,
)));
let ptr = gc_proto.as_proto_ptr().unwrap();
gc.trace_object(gc_proto)?;
Ok(ptr)
Expand All @@ -130,7 +158,7 @@ impl ObjectAllocator {
let upval_size = upvalue_store.len() * std::mem::size_of::<UpvaluePtr>();
let size = std::mem::size_of::<GcFunction>() as u32 + upval_size as u32;

let gc_func = GcObjectOwner::Function(Box::new(GcFunction::new(
let gc_func = GcObjectOwner::Function(self.function_pool.alloc(GcFunction::new(
LuaRawFunction::new(chunk, upvalue_store),
current_white,
size,
Expand All @@ -153,7 +181,7 @@ impl ObjectAllocator {
let current_white = gc.current_white;
let size = std::mem::size_of::<CFunction>() as u32
+ (upvalues.len() as u32 * std::mem::size_of::<LuaValue>() as u32);
let gc_func = GcObjectOwner::CClosure(Box::new(GcCClosure::new(
let gc_func = GcObjectOwner::CClosure(self.cclosure_pool.alloc(GcCClosure::new(
CClosureFunction::new(func, upvalues),
current_white,
size,
Expand All @@ -176,7 +204,7 @@ impl ObjectAllocator {
let current_white = gc.current_white;
let size = std::mem::size_of::<GcRClosure>() as u32
+ (upvalues.len() as u32 * std::mem::size_of::<LuaValue>() as u32);
let gc_func = GcObjectOwner::RClosure(Box::new(GcRClosure::new(
let gc_func = GcObjectOwner::RClosure(self.rclosure_pool.alloc(GcRClosure::new(
RClosureFunction::new(func, upvalues),
current_white,
size,
Expand All @@ -193,12 +221,14 @@ impl ObjectAllocator {
pub fn create_upvalue(&mut self, gc: &mut GC, upvalue: LuaUpvalue) -> LuaResult<UpvaluePtr> {
let current_white = gc.current_white;
let size = 64;
let mut boxed = Box::new(GcUpvalue::new(upvalue, current_white, size));
let mut pooled = self
.upvalue_pool
.alloc(GcUpvalue::new(upvalue, current_white, size));
// Fix up closed upvalue's v pointer to its own closed_value field.
// Must happen after boxing so the heap address is stable.
// No-op for open upvalues (v already points to valid stack slot).
boxed.data.fix_closed_ptr();
let gc_uv = GcObjectOwner::Upvalue(boxed);
pooled.data.fix_closed_ptr();
let gc_uv = GcObjectOwner::Upvalue(pooled);
let ptr = gc_uv.as_upvalue_ptr().unwrap();
gc.trace_object(gc_uv)?;
Ok(ptr)
Expand All @@ -210,7 +240,7 @@ impl ObjectAllocator {
pub fn create_userdata(&mut self, gc: &mut GC, userdata: LuaUserdata) -> CreateResult {
let current_white = gc.current_white;
let size = std::mem::size_of::<LuaUserdata>();
let gc_userdata = GcObjectOwner::Userdata(Box::new(GcUserdata::new(
let gc_userdata = GcObjectOwner::Userdata(self.userdata_pool.alloc(GcUserdata::new(
userdata,
current_white,
size as u32,
Expand Down
Loading
Loading