1use core::mem::offset_of;
2use core::ptr::{self, NonNull};
3
4use crate::gc::{GcObject, RawGcObject};
5use crate::handle::RawHandle;
6use crate::state::{GlobalState, VmAllocator};
7use crate::types::LUA_TNIL;
8
9use super::{GCO_LINK_OFFSET, LUA_PAGE_PADDING};
10
11pub struct GcoPageWalk {
16 pub(super) current: *mut u8,
17 pub(super) end: *mut u8,
18 pub(super) block_size: i32,
19 pub(super) steps: i32,
20}
21#[allow(
22 clippy::missing_safety_doc,
23 reason = "GcoPageWalk's shared traversal contract is documented on GcoPageWalk"
24)]
25impl GcoPageWalk {
26 pub unsafe fn next(&mut self) -> Option<GcObject> {
27 while self.current != self.end {
28 let block = self.current.cast::<RawGcObject>();
29 self.current = unsafe { self.current.add(self.block_size as usize) };
30 self.steps += 1;
31
32 if unsafe { (*block).tt } == LUA_TNIL as u8 {
33 continue;
34 }
35
36 return Some(unsafe { GcObject::from_raw(NonNull::new_unchecked(block)) });
37 }
38
39 None
40 }
41
42 pub fn steps(&self) -> i32 {
43 self.steps
44 }
45}
46
47#[repr(C)]
48pub struct RawLuaPage {
49 pub prev: *mut RawLuaPage,
50 pub next: *mut RawLuaPage,
51 pub list_prev: *mut RawLuaPage,
52 pub list_next: *mut RawLuaPage,
53 pub page_size: i32,
54 pub block_size: i32,
55 pub free_list: *mut u8,
56 pub free_next: i32,
57 pub busy_blocks: i32,
58 pub padding: [u8; LUA_PAGE_PADDING],
59 pub data: [u8; 1],
60}
61
62#[derive(Clone, Copy)]
63#[repr(transparent)]
64pub(crate) struct LuaPageList {
65 head: NonNull<*mut RawLuaPage>,
66}
67
68#[derive(Clone, Copy, PartialEq, Eq)]
69#[repr(transparent)]
70pub struct LuaPage {
78 raw: NonNull<RawLuaPage>,
79}
80
81impl LuaPageList {
82 pub(crate) const unsafe fn from_raw(head: NonNull<*mut RawLuaPage>) -> Self {
83 Self { head }
84 }
85
86 pub(crate) unsafe fn get(&self) -> *mut RawLuaPage {
87 unsafe { *self.head.as_ptr() }
88 }
89
90 pub(crate) unsafe fn set(&self, page: *mut RawLuaPage) {
91 unsafe {
92 *self.head.as_ptr() = page;
93 }
94 }
95
96 pub(crate) unsafe fn link_page(&self, page: LuaPage) {
97 unsafe {
98 let page_ref = page.as_ptr().as_mut().unwrap_unchecked();
99 page_ref.list_next = self.get();
100 if let Some(mut next) = NonNull::new(page_ref.list_next) {
101 next.as_mut().list_prev = page.as_ptr();
102 }
103 self.set(page.as_ptr());
104 }
105 }
106
107 pub(crate) unsafe fn unlink_page(&self, page: LuaPage) {
108 unsafe {
109 let page_ref = page.as_ptr().as_mut().unwrap_unchecked();
110 if let Some(mut list_next) = NonNull::new(page_ref.list_next) {
111 list_next.as_mut().list_prev = page_ref.list_prev;
112 }
113
114 if let Some(mut list_prev) = NonNull::new(page_ref.list_prev) {
115 list_prev.as_mut().list_next = page_ref.list_next;
116 } else if self.get() == page.as_ptr() {
117 self.set(page_ref.list_next);
118 }
119
120 page_ref.list_prev = ptr::null_mut();
121 page_ref.list_next = ptr::null_mut();
122 }
123 }
124}
125
126impl GlobalState {
127 pub(crate) fn allocator(&self) -> NonNull<VmAllocator> {
128 unsafe { self.as_ptr().as_ref().unwrap_unchecked().allocator }
129 }
130
131 pub(crate) unsafe fn free_page_list(&self, size_class_index: usize) -> LuaPageList {
132 unsafe {
133 let slots = (&raw mut (*self.as_ptr()).free_pages).cast::<*mut RawLuaPage>();
134 LuaPageList::from_raw(NonNull::new_unchecked(slots.add(size_class_index)))
135 }
136 }
137
138 pub(crate) unsafe fn free_gco_page_list(&self, size_class_index: usize) -> LuaPageList {
139 unsafe {
140 let slots = (&raw mut (*self.as_ptr()).free_gco_pages).cast::<*mut RawLuaPage>();
141 LuaPageList::from_raw(NonNull::new_unchecked(slots.add(size_class_index)))
142 }
143 }
144
145 pub(crate) unsafe fn all_page_list(&self) -> LuaPageList {
146 unsafe {
147 LuaPageList::from_raw(NonNull::new_unchecked(&raw mut (*self.as_ptr()).all_pages))
148 }
149 }
150
151 pub(crate) unsafe fn all_gco_page_list(&self) -> LuaPageList {
152 unsafe {
153 LuaPageList::from_raw(NonNull::new_unchecked(
154 &raw mut (*self.as_ptr()).all_gco_pages,
155 ))
156 }
157 }
158
159 pub fn all_gco_pages(&self) -> Option<LuaPage> {
160 unsafe { NonNull::new((*self.as_ptr()).all_gco_pages).map(|raw| LuaPage::from_raw(raw)) }
161 }
162
163 pub fn sweep_gco_page(&self) -> Option<LuaPage> {
164 unsafe { NonNull::new((*self.as_ptr()).sweep_gco_page).map(|raw| LuaPage::from_raw(raw)) }
165 }
166
167 pub fn set_sweep_gco_page(&self, page: Option<LuaPage>) {
168 unsafe {
169 (*self.as_ptr()).sweep_gco_page = page.map_or(ptr::null_mut(), |page| page.as_ptr());
170 }
171 }
172}
173
174#[allow(
175 clippy::missing_safety_doc,
176 reason = "LuaPage's shared raw-handle contract is documented on LuaPage"
177)]
178impl LuaPage {
179 pub const unsafe fn from_raw(raw: NonNull<RawLuaPage>) -> Self {
180 Self { raw }
181 }
182
183 pub fn data_ptr(&self) -> *const u8 {
184 unsafe { self.as_ptr().cast::<u8>().add(offset_of!(RawLuaPage, data)) }
185 }
186
187 pub fn data_ptr_mut(&self) -> *mut u8 {
188 unsafe { self.as_ptr().cast::<u8>().add(offset_of!(RawLuaPage, data)) }
189 }
190
191 pub unsafe fn block(&self, offset: i32) -> *mut u8 {
198 unsafe { self.data_ptr_mut().add(offset as usize) }
199 }
200
201 pub fn page_blocks(&self) -> i32 {
202 let raw = unsafe { self.as_ptr().as_ref().unwrap_unchecked() };
203 ((raw.page_size as usize - offset_of!(RawLuaPage, data)) / raw.block_size as usize) as i32
204 }
205
206 pub unsafe fn metadata_slot(block: *mut u8) -> *mut *mut u8 {
211 block.cast::<*mut u8>()
212 }
213
214 pub unsafe fn free_gco_link_slot(block: *mut u8) -> *mut *mut u8 {
219 unsafe { block.add(GCO_LINK_OFFSET).cast::<*mut u8>() }
220 }
221
222 pub unsafe fn gco_walk(&self) -> (GcoPageWalk, i32) {
223 let page = unsafe { self.as_ptr().as_ref().unwrap_unchecked() };
224 let block_count =
225 (page.page_size as usize - offset_of!(RawLuaPage, data)) / page.block_size as usize;
226
227 (
228 GcoPageWalk {
229 current: unsafe {
230 self.data_ptr_mut()
231 .add((page.free_next + page.block_size) as usize)
232 },
233 end: unsafe {
234 self.data_ptr_mut()
235 .add(block_count * page.block_size as usize)
236 },
237 block_size: page.block_size,
238 steps: 0,
239 },
240 page.busy_blocks,
241 )
242 }
243
244 pub unsafe fn next_page(&self) -> Option<LuaPage> {
246 NonNull::new(unsafe { self.as_ptr().as_ref().unwrap_unchecked().list_next })
247 .map(|page| unsafe { Self::from_raw(page) })
248 }
249
250 pub unsafe fn visit_page(
252 &self,
253 context: *mut (),
254 visitor: unsafe fn(*mut (), LuaPage, GcObject) -> bool,
255 ) {
256 unsafe {
257 let (mut walk, mut busy) = self.gco_walk();
258 while let Some(gco) = walk.next() {
259 let should_delete = visitor(context, *self, gco);
260 if should_delete {
261 busy -= 1;
262 if busy == 0 {
263 break;
264 }
265 }
266 }
267 }
268 }
269}
270
271impl crate::handle::sealed::Sealed for LuaPage {}
272
273impl RawHandle for LuaPage {
274 type Raw = RawLuaPage;
275
276 fn as_ptr(&self) -> *mut Self::Raw {
277 self.raw.as_ptr()
278 }
279}
280
281impl AsRef<LuaPage> for LuaPage {
282 fn as_ref(&self) -> &LuaPage {
283 self
284 }
285}