Skip to main content

luaur_repl_cli/functions/
profiler_trigger.rs

1use alloc::collections::BTreeMap;
2use alloc::string::String;
3use core::ffi::CStr;
4use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
5use std::thread::JoinHandle;
6
7use luaur_vm::functions::lua_callbacks::lua_callbacks;
8use luaur_vm::functions::lua_getinfo::lua_getinfo;
9use luaur_vm::records::lua_callbacks::LuaCallbacks;
10use luaur_vm::records::lua_debug::LuaDebug;
11use luaur_vm::records::lua_state::lua_State;
12
13// Trigger-side view of Profiler.cpp's file-static `gProfiler`. The sampling
14// thread publishes `ticks` (atomic) and the VM-thread trigger consumes them,
15// keeping `current_ticks`, the `stack_scratch` reuse buffer, the accumulated
16// per-stack `data` and the per-GC-state timing array — exactly the fields
17// `profilerTrigger` reads and writes. (Profiler.cpp uses a
18// DenseHashMap<string,uint64> for `data`; a BTreeMap captures the same
19// stack→ticks accumulation.)
20pub(crate) struct ProfilerTriggerState {
21    // static state (Profiler.cpp: callbacks / frequency / thread)
22    pub(crate) callbacks: *mut LuaCallbacks,
23    pub(crate) frequency: i32,
24    pub(crate) thread: Option<JoinHandle<()>>,
25    // loop<->trigger communication
26    pub(crate) exit: AtomicBool,
27    pub(crate) ticks: AtomicU64,
28    pub(crate) samples: AtomicU64,
29    // trigger-private + statistics
30    pub(crate) current_ticks: u64,
31    pub(crate) stack_scratch: String,
32    pub(crate) data: Option<BTreeMap<String, u64>>,
33    pub(crate) gc: [u64; 16],
34}
35
36pub(crate) static mut G_PROFILER: ProfilerTriggerState = ProfilerTriggerState {
37    callbacks: core::ptr::null_mut(),
38    frequency: 1000,
39    thread: None,
40    exit: AtomicBool::new(false),
41    ticks: AtomicU64::new(0),
42    samples: AtomicU64::new(0),
43    current_ticks: 0,
44    stack_scratch: String::new(),
45    data: None,
46    gc: [0; 16],
47};
48
49// Faithful port of Profiler.cpp's `static void profilerTrigger(lua_State* L, int gc)`.
50pub unsafe fn profiler_trigger(l: *mut lua_State, gc: i32) {
51    let profiler = core::ptr::addr_of_mut!(G_PROFILER).as_mut().unwrap();
52
53    let current_ticks = profiler.ticks.load(Ordering::Relaxed);
54    let elapsed_ticks = current_ticks - profiler.current_ticks;
55
56    if elapsed_ticks != 0 {
57        let stack = &mut profiler.stack_scratch;
58        stack.clear();
59
60        if gc > 0 {
61            stack.push_str("GC,GC,");
62        }
63
64        let mut ar: LuaDebug = core::mem::zeroed();
65        let mut level = 0;
66        while lua_getinfo(l, level, c"sn".as_ptr(), &mut ar as *mut LuaDebug) != 0 {
67            if !stack.is_empty() {
68                stack.push(';');
69            }
70
71            if !ar.short_src.is_null() {
72                stack.push_str(&CStr::from_ptr(ar.short_src).to_string_lossy());
73            }
74            stack.push(',');
75            if !ar.name.is_null() {
76                stack.push_str(&CStr::from_ptr(ar.name).to_string_lossy());
77            }
78            stack.push(',');
79            if ar.linedefined > 0 {
80                use core::fmt::Write;
81                let _ = write!(stack, "{}", ar.linedefined);
82            }
83
84            level += 1;
85        }
86
87        if !stack.is_empty() {
88            let key = stack.clone();
89            let data = profiler.data.get_or_insert_with(BTreeMap::new);
90            *data.entry(key).or_insert(0) += elapsed_ticks;
91        }
92
93        if gc > 0 {
94            profiler.gc[gc as usize] += elapsed_ticks;
95        }
96    }
97
98    profiler.current_ticks = current_ticks;
99
100    if !profiler.callbacks.is_null() {
101        (*profiler.callbacks).interrupt = None;
102    } else {
103        // The sampling-thread shim does not publish a callbacks pointer, so
104        // clear the live state's interrupt directly — equivalent to C++'s
105        // `gProfiler.callbacks->interrupt = nullptr`.
106        (*lua_callbacks(l)).interrupt = None;
107    }
108}