Skip to main content

luaur_repl_cli/functions/
counters_dump.rs

1use core::ffi::{c_char, c_int, c_void, CStr};
2
3use luaur_vm::functions::lua_getcounters::lua_getcounters;
4use luaur_vm::functions::lua_getinfo::lua_getinfo;
5use luaur_vm::macros::lua_getref::lua_getref;
6use luaur_vm::macros::lua_pop::lua_pop;
7use luaur_vm::records::lua_debug::LuaDebug;
8
9use crate::functions::counters_function_callback::counters_function_callback;
10use crate::functions::counters_init::G_COUNTERS;
11use crate::functions::counters_value_callback::counters_value_callback;
12use crate::records::module_counters::ModuleCounters;
13
14extern "C" {
15    fn fopen(path: *const c_char, mode: *const c_char) -> *mut c_void;
16    fn fclose(stream: *mut c_void) -> c_int;
17    fn fprintf(stream: *mut c_void, format: *const c_char, ...) -> c_int;
18    fn fputs(s: *const c_char, stream: *mut c_void) -> c_int;
19}
20
21// `extern "C"` adapters bridging the counter callbacks to lua_getcounters'
22// `lua_CounterFunction` / `lua_CounterValue` function-pointer types.
23unsafe extern "C" fn function_callback_cb(
24    context: *mut c_void,
25    function: *const c_char,
26    line_defined: c_int,
27) {
28    counters_function_callback(context, function, line_defined);
29}
30
31unsafe extern "C" fn value_callback_cb(context: *mut c_void, kind: c_int, line: c_int, hits: u64) {
32    counters_value_callback(context, kind, line, hits);
33}
34
35// Faithful port of `void countersDump(const char* path)`.
36pub fn counters_dump(path: &str) {
37    unsafe {
38        let counters_ptr = core::ptr::addr_of_mut!(G_COUNTERS);
39        let l = (*counters_ptr).l;
40
41        let refs = (*counters_ptr).module_refs.clone();
42        for fref in refs {
43            lua_getref(l, fref);
44
45            // C++ `lua_Debug ar = {}`.
46            let mut ar: LuaDebug = core::mem::zeroed();
47            lua_getinfo(l, -1, c"s".as_ptr(), &mut ar as *mut LuaDebug);
48
49            (*counters_ptr).module_counters.push(ModuleCounters {
50                name: if ar.short_src.is_null() {
51                    alloc::string::String::new()
52                } else {
53                    CStr::from_ptr(ar.short_src).to_string_lossy().into_owned()
54                },
55                ..Default::default()
56            });
57            // Stable pointer to the just-pushed element; the callbacks only
58            // mutate this element (never the outer vector), matching C++.
59            let module_counters = (*counters_ptr).module_counters.last_mut().unwrap()
60                as *mut ModuleCounters as *mut c_void;
61
62            lua_getcounters(
63                l,
64                -1,
65                module_counters,
66                Some(function_callback_cb),
67                Some(value_callback_cb),
68            );
69
70            lua_pop(l, 1);
71        }
72
73        let path_c = alloc::format!("{}\0", path);
74        let f = fopen(path_c.as_ptr() as *const c_char, c"wb".as_ptr());
75        if f.is_null() {
76            eprintln!("Error opening counters file (callgrind) {}", path);
77            return;
78        }
79
80        fputs(c"version: 1\n".as_ptr(), f);
81        fputs(c"creator: Luau REPL\n".as_ptr(), f);
82        fputs(c"events: Regular Fallback VmExit\n".as_ptr(), f);
83
84        for module_counter in (*counters_ptr).module_counters.iter() {
85            let name_c = alloc::format!("{}\0", module_counter.name);
86            fprintf(f, c"fl=%s\n".as_ptr(), name_c.as_ptr());
87
88            for function_counter in module_counter.functions.iter() {
89                let fn_name_c = alloc::format!("{}\0", function_counter.name);
90                fprintf(f, c"fn=%s\n".as_ptr(), fn_name_c.as_ptr());
91
92                // BTreeMap already iterates by ascending line, matching the C++
93                // "sorted by line" presentation requirement.
94                for (line, counters) in function_counter.counters.iter() {
95                    if counters.regularExecuted != 0
96                        || counters.fallbackExecuted != 0
97                        || counters.vmExitTaken != 0
98                    {
99                        fprintf(
100                            f,
101                            c"%d %lld %lld %lld\n".as_ptr(),
102                            *line as c_int,
103                            counters.regularExecuted as core::ffi::c_longlong,
104                            counters.fallbackExecuted as core::ffi::c_longlong,
105                            counters.vmExitTaken as core::ffi::c_longlong,
106                        );
107                    }
108                }
109            }
110        }
111
112        fclose(f);
113
114        println!(
115            "Counters data written to {} ({} modules)",
116            path,
117            (*counters_ptr).module_counters.len()
118        );
119    }
120}