1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// CallInfo - Information about a single function call
// Equivalent to CallInfo structure in Lua C API (lstate.h)
use crate::gc::UpvaluePtr;
use crate::lua_value::Chunk;
/// Call status flags (equivalent to Lua's CIST_* flags)
pub mod call_status {
/// Packed nresults field: stores wanted_results + 1, so MULTRET (-1) becomes 0.
pub const CIST_NRESULTS_MASK: u32 = 0xFFFF;
/// Lua function (has bytecode)
pub const CIST_LUA: u32 = 0;
/// C function
pub const CIST_C: u32 = 1 << 21;
/// Function is a tail call
pub const CIST_TAIL: u32 = 1 << 22;
#[allow(unused)]
/// Call is running a for loop
pub const CIST_HOOKYIELD: u32 = 1 << 23;
/// Yieldable protected call (pcall body yielded)
pub const CIST_YPCALL: u32 = 1 << 24;
#[allow(unused)]
/// Call is in error-protected mode (pcall/xpcall)
pub const CIST_FRESH: u32 = 1 << 25;
/// Function is closing TBC variables during return
pub const CIST_CLSRET: u32 = 1 << 26;
/// Error recovery status saved across yield (precover)
pub const CIST_RECST: u32 = 1 << 20;
/// Pending metamethod finish operation (GET/SET) after yield resume.
/// When set, the `pending_finish_get` field contains a valid value
/// that must be handled in the startfunc loop before dispatching.
pub const CIST_PENDING_FINISH: u32 = 1 << 27;
/// xpcall: this C frame is for an xpcall call.
/// The error handler is stored at func_pos (base - func_offset),
/// and the actual body starts at func_pos + 1.
pub const CIST_XPCALL: u32 = 1 << 28;
/// Yieldable unprotected call (dofile body yielded)
/// Like CIST_YPCALL but without error catching — results are moved
/// without prepending true/false.
pub const CIST_YCALL: u32 = 1 << 29;
/// Frame was interrupted by a hook (set during hook callback).
/// Used by debug.getinfo to report namewhat="hook".
pub const CIST_HOOKED: u32 = 1 << 30;
/// Offset for __call metamethod count (bits 16-19)
pub const CIST_CCMT: u32 = 16;
/// Mask for __call metamethod count (0xf at bits 16-19)
pub const MAX_CCMT: u32 = 0xF << CIST_CCMT;
#[inline(always)]
pub fn with_nresults(call_status: u32, nresults: i32) -> u32 {
debug_assert!(nresults >= -1, "nresults must be >= -1");
debug_assert!(
nresults < (CIST_NRESULTS_MASK as i32),
"nresults out of range"
);
(call_status & !CIST_NRESULTS_MASK) | ((nresults + 1) as u32)
}
#[inline(always)]
pub fn get_nresults(call_status: u32) -> i32 {
((call_status & CIST_NRESULTS_MASK) as i32) - 1
}
/// Extract __call count from call_status
#[inline(always)]
pub fn get_ccmt_count(call_status: u32) -> u8 {
((call_status & MAX_CCMT) >> CIST_CCMT) as u8
}
/// Set __call count in call_status
#[inline(always)]
pub fn set_ccmt_count(call_status: u32, count: u8) -> u32 {
(call_status & !MAX_CCMT) | (((count as u32) << CIST_CCMT) & MAX_CCMT)
}
}
/// Information about a single function call on the call stack
/// This is similar to CallInfo in lstate.h
#[derive(Clone)]
pub struct CallInfo {
/// Base index in the stack for this call frame's registers
/// Combined with `func_offset`, this recovers the frame's function slot.
/// NOTE: This may be updated by VARARGPREP after stack rearrangement
pub base: usize,
/// Offset from original base to func position (for vararg functions after buildhiddenargs)
/// When nextraargs > 0 and buildhiddenargs was called:
/// - func_offset = totalargs + 1 (the shift amount)
/// Otherwise: func_offset = 1 (base - 1 = func)
pub func_offset: u32,
/// Top of stack for this frame (first free slot)
/// Equivalent to Lua's CallInfo.top
pub top: u32,
/// Program counter (for Lua functions only)
/// Points to next instruction to execute
/// Equivalent to Lua's CallInfo.u.l.savedpc
pub pc: u32,
/// Call status flags (CIST_*)
/// Low 16 bits pack expected nresults as (nresults + 1).
/// Equivalent to Lua's CallInfo.callstatus
pub call_status: u32,
/// Number of extra arguments in vararg functions
/// Equivalent to Lua's CallInfo.u.l.nextraargs
pub nextraargs: i32,
/// Cached raw pointer to the Chunk for Lua functions.
/// Avoids Rc deref in the startfunc header (hot path).
/// Null for C function frames.
/// Safety: valid as long as the frame is active (func keeps the Rc alive).
pub chunk_ptr: *const Chunk,
/// Cached pointer to the upvalue array for Lua closures.
/// Avoids the func → GcPtr → GcRClosure → LuaFunction → UpvalueStore enum match
/// chain on every GetUpval/SetUpval (saves 2-3 loads + 1 branch per access).
/// Null for C function frames (never accessed).
/// Safety: valid as long as this frame is active (func keeps the closure alive).
pub upvalue_ptrs: *const UpvaluePtr,
/// Reused i32 payload.
/// - When CIST_CLSRET is set: saved nres for return continuation.
/// - When CIST_PENDING_FINISH is set: pending finish destination/state.
pub aux_i32: i32,
}
impl CallInfo {
/// Create a new call frame for a Lua function
pub fn new_lua(base: usize, nparams: usize) -> Self {
Self {
base,
chunk_ptr: std::ptr::null(),
upvalue_ptrs: std::ptr::null(),
func_offset: 1, // Initially base - 1 = func
top: (base + nparams) as u32,
pc: 0,
call_status: call_status::with_nresults(call_status::CIST_LUA, -1),
nextraargs: 0,
aux_i32: -1,
}
}
/// Create a new call frame for a C function
pub fn new_c(base: usize, nparams: usize) -> Self {
Self {
base,
chunk_ptr: std::ptr::null(),
upvalue_ptrs: std::ptr::null(),
func_offset: 1,
top: (base + nparams) as u32,
pc: 0,
call_status: call_status::with_nresults(call_status::CIST_C, -1),
nextraargs: 0,
aux_i32: -1,
}
}
/// Check if this is a Lua function call
#[inline(always)]
pub fn is_lua(&self) -> bool {
self.call_status & call_status::CIST_C == 0
}
/// Check if this is a C function call
#[inline(always)]
pub fn is_c(&self) -> bool {
self.call_status & call_status::CIST_C != 0
}
/// Check if this is a tail call
#[inline(always)]
pub fn is_tail(&self) -> bool {
self.call_status & call_status::CIST_TAIL != 0
}
/// Mark as tail call
#[inline(always)]
pub fn set_tail(&mut self) {
self.call_status |= call_status::CIST_TAIL;
}
pub fn save_pc(&mut self, pc: usize) {
self.pc = pc as u32;
}
#[inline(always)]
pub fn nresults(&self) -> i32 {
call_status::get_nresults(self.call_status)
}
#[inline(always)]
pub fn set_nresults(&mut self, nresults: i32) {
self.call_status = call_status::with_nresults(self.call_status, nresults);
}
#[inline(always)]
pub fn saved_nres(&self) -> i32 {
self.aux_i32
}
#[inline(always)]
pub fn set_saved_nres(&mut self, nres: i32) {
self.aux_i32 = nres;
}
#[inline(always)]
pub fn pending_finish_get(&self) -> i32 {
self.aux_i32
}
#[inline(always)]
pub fn set_pending_finish_get(&mut self, value: i32) {
self.aux_i32 = value;
}
#[inline(always)]
pub fn func_index(&self) -> usize {
self.base - self.func_offset as usize
}
}
impl Default for CallInfo {
fn default() -> Self {
Self {
base: 0,
chunk_ptr: std::ptr::null(),
upvalue_ptrs: std::ptr::null(),
func_offset: 1,
top: 0,
pc: 0,
call_status: call_status::with_nresults(0, -1),
nextraargs: 0,
aux_i32: -1,
}
}
}