feat: replace Cranelift JIT with C backend

Remove Cranelift JIT compiler and expose the existing C backend as the
compilation target. Generated C code can be compiled with GCC/Clang.

Changes:
- Remove cranelift-* dependencies from Cargo.toml
- Delete src/compiler.rs (565 lines of Cranelift code)
- Add compile_to_c() function with -o and --run flags
- Fix C backend name mangling (main -> main_lux) to avoid conflicts
- Update CLI help text and documentation

Usage:
  lux compile <file.lux>           # Output C to stdout
  lux compile <file.lux> -o out.c  # Write to file
  lux compile <file.lux> --run     # Compile and execute

C backend supports: functions, basic types, operators, if/then/else,
records, enums, Console.print. Future work: closures, lists, patterns.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 03:37:35 -05:00
parent 8c7354131e
commit 67437b8273
8 changed files with 132 additions and 999 deletions

View File

@@ -2,7 +2,6 @@
mod ast;
mod codegen;
mod compiler;
mod debugger;
mod diagnostics;
mod exhaustiveness;
@@ -141,13 +140,17 @@ fn main() {
handle_pkg_command(&args[2..]);
}
"compile" => {
// Compile and run with JIT
// Compile to C code
if args.len() < 3 {
eprintln!("Usage: lux compile <file.lux> [--benchmark]");
eprintln!("Usage: lux compile <file.lux> [-o output.c] [--run]");
std::process::exit(1);
}
let benchmark = args.iter().any(|a| a == "--benchmark");
compile_file(&args[2], benchmark);
let run_after = args.iter().any(|a| a == "--run");
let output_path = args.iter()
.position(|a| a == "-o")
.and_then(|i| args.get(i + 1))
.map(|s| s.as_str());
compile_to_c(&args[2], output_path, run_after);
}
path => {
// Run a file
@@ -166,7 +169,9 @@ fn print_help() {
println!("Usage:");
println!(" lux Start the REPL");
println!(" lux <file.lux> Run a file (interpreter)");
println!(" lux compile <file.lux> Compile and run with JIT (--benchmark for timing)");
println!(" lux compile <file.lux> Compile to C code (stdout)");
println!(" lux compile <f> -o out.c Compile to C file");
println!(" lux compile <f> --run Compile and execute");
println!(" lux fmt <file.lux> Format a file (--check to verify only)");
println!(" lux check <file.lux> Type check without running");
println!(" lux test [pattern] Run tests (optional pattern filter)");
@@ -257,11 +262,11 @@ fn check_file(path: &str) {
println!("{}: OK", path);
}
fn compile_file(path: &str, benchmark: bool) {
use compiler::JitCompiler;
fn compile_to_c(path: &str, output_path: Option<&str>, run_after: bool) {
use codegen::c_backend::CBackend;
use modules::ModuleLoader;
use std::path::Path;
use std::time::Instant;
use std::process::Command;
let file_path = Path::new(path);
let source = match std::fs::read_to_string(file_path) {
@@ -272,8 +277,7 @@ fn compile_file(path: &str, benchmark: bool) {
}
};
// Parse
let parse_start = Instant::now();
// Parse with module loading
let mut loader = ModuleLoader::new();
if let Some(parent) = file_path.parent() {
loader.add_search_path(parent.to_path_buf());
@@ -286,10 +290,8 @@ fn compile_file(path: &str, benchmark: bool) {
std::process::exit(1);
}
};
let parse_time = parse_start.elapsed();
// Type check
let check_start = Instant::now();
let mut checker = TypeChecker::new();
if let Err(errors) = checker.check_program_with_modules(&program, &loader) {
for error in errors {
@@ -298,63 +300,86 @@ fn compile_file(path: &str, benchmark: bool) {
}
std::process::exit(1);
}
let check_time = check_start.elapsed();
// Compile with JIT
let compile_start = Instant::now();
let mut jit = match JitCompiler::new() {
Ok(j) => j,
// Generate C code
let mut backend = CBackend::new();
let c_code = match backend.generate(&program) {
Ok(code) => code,
Err(e) => {
eprintln!("JIT initialization error: {}", e);
eprintln!("C codegen error: {}", e);
eprintln!();
eprintln!("Note: The C backend currently supports:");
eprintln!(" - Integer, Float, Bool, String, Char, Unit literals");
eprintln!(" - Arithmetic and comparison operators");
eprintln!(" - If/then/else conditionals");
eprintln!(" - Let bindings and blocks");
eprintln!(" - Function definitions and calls");
eprintln!(" - Records and enums (basic)");
eprintln!(" - Console.print effect");
eprintln!();
eprintln!("Not yet supported: closures, lists, pattern variable binding,");
eprintln!("other effects, higher-order functions.");
std::process::exit(1);
}
};
if let Err(e) = jit.compile_program(&program) {
eprintln!("Compilation error: {}", e);
eprintln!();
eprintln!("Note: The JIT compiler currently only supports:");
eprintln!(" - Integer arithmetic and comparisons");
eprintln!(" - Conditionals (if/then/else)");
eprintln!(" - Let bindings and blocks");
eprintln!(" - Function calls (including recursion)");
eprintln!();
eprintln!("Not yet supported: strings, floats, lists, records,");
eprintln!("pattern matching, effects, ADTs.");
std::process::exit(1);
}
let compile_time = compile_start.elapsed();
// Determine output
if run_after {
// Write to temp file, compile, and run
let temp_c = std::env::temp_dir().join("lux_output.c");
let temp_bin = std::env::temp_dir().join("lux_output");
// Find main function or last expression
let exec_start = Instant::now();
let result = if jit.get_function("main").is_some() {
unsafe { jit.call_function("main", &[]) }
} else {
// Try to find any function to call
eprintln!("No 'main' function found.");
eprintln!("Define a function like: fn main(): Int = ...");
std::process::exit(1);
};
let exec_time = exec_start.elapsed();
if let Err(e) = std::fs::write(&temp_c, &c_code) {
eprintln!("Error writing temp file: {}", e);
std::process::exit(1);
}
match result {
Ok(value) => {
println!("{}", value);
// Try to find a C compiler
let cc = std::env::var("CC").unwrap_or_else(|_| "cc".to_string());
if benchmark {
println!();
println!("=== JIT Benchmark ===");
println!("Parse time: {:?}", parse_time);
println!("Check time: {:?}", check_time);
println!("Compile time: {:?}", compile_time);
println!("Execute time: {:?}", exec_time);
println!("Total time: {:?}", parse_time + check_time + compile_time + exec_time);
let compile_result = Command::new(&cc)
.args(["-O2", "-o"])
.arg(&temp_bin)
.arg(&temp_c)
.output();
match compile_result {
Ok(output) => {
if !output.status.success() {
eprintln!("C compilation failed:");
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
std::process::exit(1);
}
}
Err(e) => {
eprintln!("Failed to run C compiler '{}': {}", cc, e);
eprintln!("Make sure gcc or clang is installed, or set CC environment variable.");
std::process::exit(1);
}
}
Err(e) => {
eprintln!("Execution error: {}", e);
// Run the compiled binary
let run_result = Command::new(&temp_bin).status();
match run_result {
Ok(status) => {
std::process::exit(status.code().unwrap_or(1));
}
Err(e) => {
eprintln!("Failed to run compiled binary: {}", e);
std::process::exit(1);
}
}
} else if let Some(out_path) = output_path {
// Write to specified file
if let Err(e) = std::fs::write(out_path, &c_code) {
eprintln!("Error writing file '{}': {}", out_path, e);
std::process::exit(1);
}
eprintln!("Compiled to {}", out_path);
eprintln!("Compile with: cc -O2 -o output {}", out_path);
} else {
// Print to stdout
println!("{}", c_code);
}
}