Skip to main content

luaur_compile_cli/functions/
main.rs

1use alloc::string::String;
2use alloc::vec::Vec;
3use core::ffi::{c_char, c_int, CStr};
4use std::ffi::CString;
5
6use luaur_cli_lib::functions::get_source_files::get_source_files;
7use luaur_cli_lib::functions::set_luau_flags_default::set_luau_flags_default;
8use luaur_cli_lib::functions::set_luau_flags_flags_alt_b::set_luau_flags_c_char;
9use luaur_code_gen::enums::function_stats_flags::FunctionStatsFlags;
10use luaur_code_gen::enums::target::Target;
11use luaur_code_gen::records::lowering_stats::FunctionStats_Enable;
12use luaur_common::functions::assert_handler::assert_handler;
13
14use crate::enums::compile_format::CompileFormat;
15use crate::enums::record_stats::RecordStats;
16use crate::functions::assertion_handler::assertion_handler;
17use crate::functions::compile_file::compile_file;
18use crate::functions::display_help::display_help;
19use crate::functions::escape_filename::escape_filename;
20use crate::functions::get_compile_format::get_compile_format;
21use crate::functions::serialize_compile_stats::{serialize_compile_stats, FILE};
22use crate::records::compile_stats::CompileStats;
23use crate::records::global_options::globalOptions;
24
25extern "C" {
26    fn fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE;
27    fn fclose(stream: *mut FILE) -> c_int;
28    fn fprintf(stream: *mut FILE, format: *const c_char, ...) -> c_int;
29}
30
31unsafe extern "C" fn assertion_handler_adapter(
32    expr: *const c_char,
33    file: *const c_char,
34    line: i32,
35    function: *const c_char,
36) -> i32 {
37    assertion_handler(expr, file, line, function)
38}
39
40fn atoi_like(value: &str) -> i32 {
41    let bytes = value.as_bytes();
42    let mut i = 0;
43
44    while i < bytes.len() && bytes[i].is_ascii_whitespace() {
45        i += 1;
46    }
47
48    let mut sign = 1i64;
49    if i < bytes.len() {
50        if bytes[i] == b'-' {
51            sign = -1;
52            i += 1;
53        } else if bytes[i] == b'+' {
54            i += 1;
55        }
56    }
57
58    let mut result = 0i64;
59    while i < bytes.len() && bytes[i].is_ascii_digit() {
60        result = result
61            .saturating_mul(10)
62            .saturating_add((bytes[i] - b'0') as i64);
63        i += 1;
64    }
65
66    (result.saturating_mul(sign)).clamp(i32::MIN as i64, i32::MAX as i64) as i32
67}
68
69pub fn main(argc: i32, argv: *mut *mut c_char) -> i32 {
70    *assert_handler() = Some(assertion_handler_adapter);
71
72    set_luau_flags_default();
73
74    let mut compile_format = CompileFormat::Text;
75    let mut assembly_target = Target::Host;
76    let mut record_stats = RecordStats::None;
77    let mut stats_file = String::from("stats.json");
78    let mut bytecode_summary = false;
79    let mut dump_constants = false;
80
81    for i in 1..argc as usize {
82        let arg_ptr = unsafe { *argv.add(i) };
83        let arg = unsafe { CStr::from_ptr(arg_ptr).to_string_lossy().into_owned() };
84
85        if arg == "-h" || arg == "--help" {
86            display_help(unsafe { *argv });
87            return 0;
88        } else if arg.starts_with("-O") {
89            let level = atoi_like(&arg[2..]);
90            if level < 0 || level > 2 {
91                eprintln!("Error: Optimization level must be between 0 and 2 inclusive.");
92                return 1;
93            }
94            unsafe {
95                globalOptions.optimizationLevel = level;
96            }
97        } else if arg.starts_with("-g") {
98            let level = atoi_like(&arg[2..]);
99            if level < 0 || level > 2 {
100                eprintln!("Error: Debug level must be between 0 and 2 inclusive.");
101                return 1;
102            }
103            unsafe {
104                globalOptions.debugLevel = level;
105            }
106        } else if arg.starts_with("-t") {
107            let level = atoi_like(&arg[2..]);
108            if level < 0 || level > 1 {
109                eprintln!("Error: Type info level must be between 0 and 1 inclusive.");
110                return 1;
111            }
112            unsafe {
113                globalOptions.typeInfoLevel = level;
114            }
115        } else if let Some(value) = arg.strip_prefix("--target=") {
116            if value == "a64" {
117                assembly_target = Target::A64;
118            } else if value == "a64_nf" {
119                assembly_target = Target::A64_NoFeatures;
120            } else if value == "x64" {
121                assembly_target = Target::X64_SystemV;
122            } else if value == "x64_ms" {
123                assembly_target = Target::X64_Windows;
124            } else {
125                eprintln!("Error: unknown target");
126                return 1;
127            }
128        } else if arg == "--timetrace" {
129            luaur_common::FFlag::DebugLuauTimeTracing.set(true);
130        } else if let Some(value) = arg.strip_prefix("--record-stats=") {
131            if value == "total" {
132                record_stats = RecordStats::Total;
133            } else if value == "file" {
134                record_stats = RecordStats::File;
135            } else if value == "function" {
136                record_stats = RecordStats::Function;
137            } else {
138                eprintln!("Error: unknown 'granularity' for '--record-stats'.");
139                return 1;
140            }
141        } else if arg.starts_with("--bytecode-summary") {
142            bytecode_summary = true;
143        } else if arg == "--dump-constants" {
144            dump_constants = true;
145        } else if let Some(value) = arg.strip_prefix("--stats-file=") {
146            stats_file = String::from(value);
147
148            if stats_file.is_empty() {
149                eprintln!("Error: filename missing for '--stats-file'.\n");
150                return 1;
151            }
152        } else if arg.starts_with("--fflags=") {
153            unsafe {
154                set_luau_flags_c_char(arg_ptr.add(9));
155            }
156        } else if arg.starts_with("--vector-lib=") {
157            unsafe {
158                globalOptions.vectorLib = arg_ptr.add(13);
159            }
160        } else if arg.starts_with("--vector-ctor=") {
161            unsafe {
162                globalOptions.vectorCtor = arg_ptr.add(14);
163            }
164        } else if arg.starts_with("--vector-type=") {
165            unsafe {
166                globalOptions.vectorType = arg_ptr.add(14);
167            }
168        } else if arg.starts_with("--parse-cst") {
169            unsafe {
170                globalOptions.parseCst = true;
171            }
172        } else if arg.starts_with("--only-parse") {
173            unsafe {
174                globalOptions.onlyParse = true;
175            }
176        } else if arg.starts_with("--") {
177            if let Some(format) = get_compile_format(&arg[2..]) {
178                compile_format = format;
179            } else {
180                eprintln!("Error: Unrecognized option '{}'.\n", arg);
181                display_help(unsafe { *argv });
182                return 1;
183            }
184        } else if arg.starts_with('-') {
185            eprintln!("Error: Unrecognized option '{}'.\n", arg);
186            display_help(unsafe { *argv });
187            return 1;
188        }
189    }
190
191    if bytecode_summary && record_stats != RecordStats::Function {
192        eprintln!("'Error: Required '--record-stats=function' for '--bytecode-summary'.");
193        return 1;
194    }
195
196    if luaur_common::FFlag::DebugLuauTimeTracing.get() {
197        eprintln!(
198            "To run with --timetrace, Luau has to be built with LUAU_ENABLE_TIME_TRACE enabled"
199        );
200        return 1;
201    }
202
203    let files = get_source_files(argc, argv);
204    let file_count = files.len();
205    let mut stats = CompileStats::default();
206
207    let mut file_stats: Vec<CompileStats> = Vec::new();
208    if record_stats == RecordStats::File || record_stats == RecordStats::Function {
209        file_stats.reserve(file_count);
210    }
211
212    let mut failed = 0;
213    let function_stats = (if record_stats == RecordStats::Function {
214        FunctionStats_Enable
215    } else {
216        0
217    }) | if bytecode_summary {
218        FunctionStatsFlags::FunctionStats_BytecodeSummary as u32
219    } else {
220        0
221    };
222
223    for path in &files {
224        let mut file_stat = CompileStats::default();
225        file_stat.lower_stats.function_stats_flags = function_stats;
226
227        let ok = match CString::new(path.as_str()) {
228            Ok(path_c) => compile_file(
229                path_c.as_ptr(),
230                compile_format,
231                assembly_target,
232                &mut file_stat,
233                dump_constants,
234            ),
235            Err(_) => {
236                eprintln!("Error opening {}", path);
237                false
238            }
239        };
240
241        if !ok {
242            failed += 1;
243        }
244
245        stats += &file_stat;
246
247        if record_stats == RecordStats::File || record_stats == RecordStats::Function {
248            file_stats.push(file_stat);
249        }
250    }
251
252    if compile_format == CompileFormat::Null {
253        println!(
254            "Compiled {} KLOC into {} KB bytecode (read {:.2}s, parse {:.2}s, compile {:.2}s)",
255            (stats.lines / 1000) as i32,
256            (stats.bytecode / 1024) as i32,
257            stats.read_time,
258            stats.parse_time,
259            stats.compile_time
260        );
261    } else if compile_format == CompileFormat::CodegenNull {
262        println!(
263            "Compiled {} KLOC into {} KB bytecode => {} KB native code ({:.2}x) (read {:.2}s, parse {:.2}s, compile {:.2}s, codegen {:.2}s)",
264            (stats.lines / 1000) as i32,
265            (stats.bytecode / 1024) as i32,
266            (stats.codegen / 1024) as i32,
267            if stats.bytecode == 0 {
268                0.0
269            } else {
270                stats.codegen as f64 / stats.bytecode as f64
271            },
272            stats.read_time,
273            stats.parse_time,
274            stats.compile_time,
275            stats.codegen_time
276        );
277
278        println!(
279            "Lowering: regalloc failed: {}, lowering failed {}; spills to stack: {}, spills to restore: {}, max spill slot {}",
280            stats.lower_stats.reg_alloc_errors,
281            stats.lower_stats.lowering_errors,
282            stats.lower_stats.spills_to_slot,
283            stats.lower_stats.spills_to_restore,
284            stats.lower_stats.max_spill_slots_used
285        );
286    }
287
288    if record_stats != RecordStats::None {
289        let stats_file_c = match CString::new(stats_file.as_str()) {
290            Ok(stats_file_c) => stats_file_c,
291            Err(_) => {
292                eprintln!("Unable to open 'stats.json'");
293                return 1;
294            }
295        };
296
297        let fp = unsafe { fopen(stats_file_c.as_ptr(), c"w".as_ptr()) };
298
299        if fp.is_null() {
300            eprintln!("Unable to open 'stats.json'");
301            return 1;
302        }
303
304        if record_stats == RecordStats::Total {
305            serialize_compile_stats(fp, &stats);
306        } else if record_stats == RecordStats::File || record_stats == RecordStats::Function {
307            unsafe {
308                fprintf(fp, c"{\n".as_ptr());
309            }
310
311            for i in 0..file_count {
312                let escaped = escape_filename(&files[i]);
313                let escaped_c = CString::new(escaped).expect("escaped filename contains NUL");
314                unsafe {
315                    fprintf(fp, c"    \"%s\": ".as_ptr(), escaped_c.as_ptr());
316                }
317                serialize_compile_stats(fp, &file_stats[i]);
318                unsafe {
319                    if i == file_count - 1 {
320                        fprintf(fp, c"\n".as_ptr());
321                    } else {
322                        fprintf(fp, c",\n".as_ptr());
323                    }
324                }
325            }
326
327            unsafe {
328                fprintf(fp, c"}".as_ptr());
329            }
330        }
331
332        unsafe {
333            fclose(fp);
334        }
335    }
336
337    if failed != 0 {
338        1
339    } else {
340        0
341    }
342}