use core::alloc::Layout;
use core::cell::Cell;
use core::ptr::NonNull;
use std::rc::Rc;
use luau_vm::state::LuaAllocator;
use super::{Lua, LuaRef};
#[derive(Default)]
pub(crate) struct MemoryState {
used: Cell<usize>,
limit: Cell<usize>,
}
impl MemoryState {
fn can_grow_by(&self, amount: usize) -> bool {
let Some(used) = self.used.get().checked_add(amount) else {
return false;
};
let limit = self.limit.get();
limit == 0 || used <= limit
}
fn record_growth(&self, amount: usize) {
self.used.set(
self.used
.get()
.checked_add(amount)
.expect("successful allocator growth must fit in usize"),
);
}
fn record_shrink(&self, amount: usize) {
self.used.set(
self.used
.get()
.checked_sub(amount)
.expect("allocator accounting must remain balanced"),
);
}
fn set_limit(&self, limit: usize) -> usize {
self.limit.replace(limit)
}
}
pub(crate) struct TrackingAllocator<A> {
allocator: A,
memory: Rc<MemoryState>,
}
impl<A> TrackingAllocator<A> {
pub(crate) const fn new(allocator: A, memory: Rc<MemoryState>) -> Self {
Self { allocator, memory }
}
}
unsafe impl<A> LuaAllocator for TrackingAllocator<A>
where
A: LuaAllocator,
{
unsafe fn allocate(&self, layout: Layout) -> Option<NonNull<u8>> {
if !self.memory.can_grow_by(layout.size()) {
return None;
}
let allocation = unsafe { self.allocator.allocate(layout) }?;
self.memory.record_growth(layout.size());
Some(allocation)
}
unsafe fn reallocate(
&self,
pointer: NonNull<u8>,
old_layout: Layout,
new_layout: Layout,
) -> Option<NonNull<u8>> {
if new_layout.size() > old_layout.size()
&& !self
.memory
.can_grow_by(new_layout.size() - old_layout.size())
{
return None;
}
let allocation = unsafe { self.allocator.reallocate(pointer, old_layout, new_layout) }?;
match new_layout.size().cmp(&old_layout.size()) {
core::cmp::Ordering::Greater => self
.memory
.record_growth(new_layout.size() - old_layout.size()),
core::cmp::Ordering::Less => self
.memory
.record_shrink(old_layout.size() - new_layout.size()),
core::cmp::Ordering::Equal => {}
}
Some(allocation)
}
unsafe fn deallocate(&self, pointer: NonNull<u8>, layout: Layout) {
unsafe {
self.allocator.deallocate(pointer, layout);
}
self.memory.record_shrink(layout.size());
}
}
impl Lua {
pub fn used_memory(&self) -> usize {
self.lua_ref().used_memory()
}
pub fn set_memory_limit(&self, limit: usize) -> usize {
self.lua_ref().set_memory_limit(limit)
}
}
impl LuaRef<'_> {
pub fn used_memory(&self) -> usize {
self.runtime().memory().used.get()
}
pub fn set_memory_limit(&self, limit: usize) -> usize {
self.runtime().memory().set_limit(limit)
}
}