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

@@ -82,7 +82,10 @@ impl CBackend {
self.emit_function(f)?;
}
Declaration::Let(let_decl) => {
self.emit_global_let(&let_decl.name, &let_decl.value)?;
// Skip run expressions - they're handled in the main wrapper
if !matches!(&let_decl.value, Expr::Run { .. }) {
self.emit_global_let(&let_decl.name, &let_decl.value)?;
}
}
_ => {}
}
@@ -245,12 +248,18 @@ impl CBackend {
Ok(())
}
fn mangle_name(&self, name: &str) -> String {
// Add suffix to avoid conflicts with C keywords and standard library
format!("{}_lux", name)
}
fn emit_forward_declarations(&mut self, program: &Program) -> Result<(), CGenError> {
for decl in &program.declarations {
if let Declaration::Function(f) = decl {
let ret_type = self.type_expr_to_c(&f.return_type)?;
let params = self.emit_params(&f.params)?;
self.writeln(&format!("{} {}({});", ret_type, f.name.name, params));
let mangled = self.mangle_name(&f.name.name);
self.writeln(&format!("{} {}({});", ret_type, mangled, params));
}
}
self.writeln("");
@@ -260,8 +269,9 @@ impl CBackend {
fn emit_function(&mut self, func: &FunctionDecl) -> Result<(), CGenError> {
let ret_type = self.type_expr_to_c(&func.return_type)?;
let params = self.emit_params(&func.params)?;
let mangled = self.mangle_name(&func.name.name);
self.writeln(&format!("{} {}({}) {{", ret_type, func.name.name, params));
self.writeln(&format!("{} {}({}) {{", ret_type, mangled, params));
self.indent += 1;
// Emit function body
@@ -365,7 +375,14 @@ impl CBackend {
let arg_strs: Result<Vec<_>, _> = args.iter().map(|a| self.emit_expr(a)).collect();
let args_str = arg_strs?.join(", ");
Ok(format!("{}({})", func_name, args_str))
// Mangle user-defined function names
let c_func_name = if self.functions.contains(&func_name) {
self.mangle_name(&func_name)
} else {
func_name
};
Ok(format!("{}({})", c_func_name, args_str))
}
Expr::Block { statements, result, .. } => {
@@ -517,7 +534,8 @@ impl CBackend {
if let Expr::Run { expr, .. } = &let_decl.value {
if let Expr::Call { func, .. } = expr.as_ref() {
if let Expr::Var(fn_name) = func.as_ref() {
self.writeln(&format!("{}();", fn_name.name));
let mangled = self.mangle_name(&fn_name.name);
self.writeln(&format!("{}();", mangled));
}
}
}
@@ -602,7 +620,7 @@ mod tests {
fn add(a: Int, b: Int): Int = a + b
"#;
let c_code = generate(source).unwrap();
assert!(c_code.contains("LuxInt add(LuxInt a, LuxInt b)"));
assert!(c_code.contains("LuxInt add_lux(LuxInt a, LuxInt b)"));
assert!(c_code.contains("return (a + b)"));
}
@@ -613,8 +631,8 @@ mod tests {
if n <= 1 then 1 else n * factorial(n - 1)
"#;
let c_code = generate(source).unwrap();
assert!(c_code.contains("LuxInt factorial(LuxInt n)"));
assert!(c_code.contains("factorial((n - 1))"));
assert!(c_code.contains("LuxInt factorial_lux(LuxInt n)"));
assert!(c_code.contains("factorial_lux((n - 1))"));
}
#[test]

View File

@@ -1,565 +0,0 @@
//! Cranelift-based native compiler for Lux
//!
//! This module compiles Lux programs to native machine code using Cranelift.
//! Currently supports a subset of the language for performance-critical code.
#![allow(dead_code)]
use crate::ast::{Expr, Program, Declaration, FunctionDecl, BinaryOp, UnaryOp, LiteralKind, Statement};
use cranelift_codegen::ir::{AbiParam, InstBuilder, Value, types};
use cranelift_codegen::ir::condcodes::IntCC;
use cranelift_codegen::Context;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{Module, Linkage, FuncId};
use std::collections::HashMap;
/// Errors that can occur during compilation
#[derive(Debug)]
pub struct CompileError {
pub message: String,
}
impl std::fmt::Display for CompileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Compile error: {}", self.message)
}
}
impl std::error::Error for CompileError {}
/// The JIT compiler for Lux
pub struct JitCompiler {
/// The Cranelift JIT module
module: JITModule,
/// Builder context (reusable)
builder_context: FunctionBuilderContext,
/// Cranelift context (reusable)
ctx: Context,
/// Compiled function pointers
functions: HashMap<String, *const u8>,
/// Function IDs for linking
func_ids: HashMap<String, FuncId>,
}
impl JitCompiler {
/// Create a new JIT compiler
pub fn new() -> Result<Self, CompileError> {
let builder = JITBuilder::new(cranelift_module::default_libcall_names())
.map_err(|e| CompileError { message: e.to_string() })?;
let module = JITModule::new(builder);
Ok(Self {
module,
builder_context: FunctionBuilderContext::new(),
ctx: Context::new(),
functions: HashMap::new(),
func_ids: HashMap::new(),
})
}
/// Compile a Lux function to native code
pub fn compile_function(&mut self, func: &FunctionDecl) -> Result<*const u8, CompileError> {
// Check if already compiled
if let Some(ptr) = self.functions.get(&func.name.name) {
return Ok(*ptr);
}
// Create function signature
let mut sig = self.module.make_signature();
for _ in &func.params {
sig.params.push(AbiParam::new(types::I64));
}
sig.returns.push(AbiParam::new(types::I64));
// Declare the function
let func_id = self.module
.declare_function(&func.name.name, Linkage::Local, &sig)
.map_err(|e| CompileError { message: e.to_string() })?;
self.func_ids.insert(func.name.name.clone(), func_id);
// Clear context for reuse
self.ctx.clear();
self.ctx.func.signature = sig;
// Clone func_ids for use in closure
let func_ids = self.func_ids.clone();
// Build the function body
{
let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context);
// Create entry block
let entry_block = builder.create_block();
builder.append_block_params_for_function_params(entry_block);
builder.switch_to_block(entry_block);
builder.seal_block(entry_block);
// Map parameter names to values
let mut variables: HashMap<String, Variable> = HashMap::new();
let params = builder.block_params(entry_block).to_vec();
for (i, param) in func.params.iter().enumerate() {
let var = Variable::from_u32(i as u32);
builder.declare_var(var, types::I64);
builder.def_var(var, params[i]);
variables.insert(param.name.name.clone(), var);
}
// Compile the function body
let var_count = variables.len();
let result = compile_expr(&mut builder, &func.body, &mut variables, var_count, &func_ids, &mut self.module)?;
// Return the result
builder.ins().return_(&[result]);
builder.finalize();
}
// Compile to machine code
self.module
.define_function(func_id, &mut self.ctx)
.map_err(|e| CompileError { message: e.to_string() })?;
self.module.clear_context(&mut self.ctx);
// Finalize and get the function pointer
self.module.finalize_definitions()
.map_err(|e| CompileError { message: e.to_string() })?;
let ptr = self.module.get_finalized_function(func_id);
self.functions.insert(func.name.name.clone(), ptr);
Ok(ptr)
}
/// Compile and run a simple function that takes no args and returns an i64
pub fn compile_and_run(&mut self, func: &FunctionDecl) -> Result<i64, CompileError> {
let ptr = self.compile_function(func)?;
// Cast to function pointer and call
let func_ptr: fn() -> i64 = unsafe { std::mem::transmute(ptr) };
Ok(func_ptr())
}
/// Compile a program and return pointers to all compiled functions
pub fn compile_program(&mut self, program: &Program) -> Result<(), CompileError> {
// First pass: declare all functions
for decl in &program.declarations {
if let Declaration::Function(func) = decl {
let mut sig = self.module.make_signature();
for _ in &func.params {
sig.params.push(AbiParam::new(types::I64));
}
sig.returns.push(AbiParam::new(types::I64));
let func_id = self.module
.declare_function(&func.name.name, Linkage::Local, &sig)
.map_err(|e| CompileError { message: e.to_string() })?;
self.func_ids.insert(func.name.name.clone(), func_id);
}
}
// Second pass: compile all functions
for decl in &program.declarations {
if let Declaration::Function(func) = decl {
self.compile_function_body(func)?;
}
}
// Finalize
self.module.finalize_definitions()
.map_err(|e| CompileError { message: e.to_string() })?;
// Store function pointers
for (name, func_id) in &self.func_ids {
let ptr = self.module.get_finalized_function(*func_id);
self.functions.insert(name.clone(), ptr);
}
Ok(())
}
/// Compile a function body (assumes function is already declared)
fn compile_function_body(&mut self, func: &FunctionDecl) -> Result<(), CompileError> {
let func_id = *self.func_ids.get(&func.name.name).ok_or_else(|| CompileError {
message: format!("Function not declared: {}", func.name.name),
})?;
// Create signature
let mut sig = self.module.make_signature();
for _ in &func.params {
sig.params.push(AbiParam::new(types::I64));
}
sig.returns.push(AbiParam::new(types::I64));
// Clear and set up context
self.ctx.clear();
self.ctx.func.signature = sig;
// Clone func_ids for use in closure
let func_ids = self.func_ids.clone();
// Build function
{
let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context);
let entry_block = builder.create_block();
builder.append_block_params_for_function_params(entry_block);
builder.switch_to_block(entry_block);
builder.seal_block(entry_block);
let mut variables: HashMap<String, Variable> = HashMap::new();
let params = builder.block_params(entry_block).to_vec();
for (i, param) in func.params.iter().enumerate() {
let var = Variable::from_u32(i as u32);
builder.declare_var(var, types::I64);
builder.def_var(var, params[i]);
variables.insert(param.name.name.clone(), var);
}
let var_count = variables.len();
let result = compile_expr(&mut builder, &func.body, &mut variables, var_count, &func_ids, &mut self.module)?;
builder.ins().return_(&[result]);
builder.finalize();
}
// Define the function
self.module
.define_function(func_id, &mut self.ctx)
.map_err(|e| CompileError { message: e.to_string() })?;
self.module.clear_context(&mut self.ctx);
Ok(())
}
/// Get a compiled function pointer by name
pub fn get_function(&self, name: &str) -> Option<*const u8> {
self.functions.get(name).copied()
}
/// Call a compiled function with given i64 arguments
pub unsafe fn call_function(&self, name: &str, args: &[i64]) -> Result<i64, CompileError> {
let ptr = self.get_function(name).ok_or_else(|| CompileError {
message: format!("Function not found: {}", name),
})?;
match args.len() {
0 => {
let f: fn() -> i64 = std::mem::transmute(ptr);
Ok(f())
}
1 => {
let f: fn(i64) -> i64 = std::mem::transmute(ptr);
Ok(f(args[0]))
}
2 => {
let f: fn(i64, i64) -> i64 = std::mem::transmute(ptr);
Ok(f(args[0], args[1]))
}
3 => {
let f: fn(i64, i64, i64) -> i64 = std::mem::transmute(ptr);
Ok(f(args[0], args[1], args[2]))
}
_ => Err(CompileError {
message: format!("Too many arguments: {}", args.len()),
}),
}
}
}
impl Default for JitCompiler {
fn default() -> Self {
Self::new().expect("Failed to create JIT compiler")
}
}
/// Compile an expression to Cranelift IR (free function to avoid borrow issues)
fn compile_expr(
builder: &mut FunctionBuilder,
expr: &Expr,
variables: &mut HashMap<String, Variable>,
next_var: usize,
func_ids: &HashMap<String, FuncId>,
module: &mut JITModule,
) -> Result<Value, CompileError> {
match expr {
Expr::Literal(lit) => {
match &lit.kind {
LiteralKind::Int(n) => {
Ok(builder.ins().iconst(types::I64, *n))
}
LiteralKind::Bool(b) => {
Ok(builder.ins().iconst(types::I64, if *b { 1 } else { 0 }))
}
_ => Err(CompileError {
message: "Unsupported literal type".to_string()
}),
}
}
Expr::Var(ident) => {
let var = variables.get(&ident.name).ok_or_else(|| CompileError {
message: format!("Undefined variable: {}", ident.name),
})?;
Ok(builder.use_var(*var))
}
Expr::BinaryOp { op, left, right, .. } => {
let lhs = compile_expr(builder, left, variables, next_var, func_ids, module)?;
let rhs = compile_expr(builder, right, variables, next_var, func_ids, module)?;
let result = match op {
BinaryOp::Add => builder.ins().iadd(lhs, rhs),
BinaryOp::Sub => builder.ins().isub(lhs, rhs),
BinaryOp::Mul => builder.ins().imul(lhs, rhs),
BinaryOp::Div => builder.ins().sdiv(lhs, rhs),
BinaryOp::Mod => builder.ins().srem(lhs, rhs),
BinaryOp::Eq => {
let cmp = builder.ins().icmp(IntCC::Equal, lhs, rhs);
builder.ins().uextend(types::I64, cmp)
}
BinaryOp::Ne => {
let cmp = builder.ins().icmp(IntCC::NotEqual, lhs, rhs);
builder.ins().uextend(types::I64, cmp)
}
BinaryOp::Lt => {
let cmp = builder.ins().icmp(IntCC::SignedLessThan, lhs, rhs);
builder.ins().uextend(types::I64, cmp)
}
BinaryOp::Le => {
let cmp = builder.ins().icmp(IntCC::SignedLessThanOrEqual, lhs, rhs);
builder.ins().uextend(types::I64, cmp)
}
BinaryOp::Gt => {
let cmp = builder.ins().icmp(IntCC::SignedGreaterThan, lhs, rhs);
builder.ins().uextend(types::I64, cmp)
}
BinaryOp::Ge => {
let cmp = builder.ins().icmp(IntCC::SignedGreaterThanOrEqual, lhs, rhs);
builder.ins().uextend(types::I64, cmp)
}
BinaryOp::And => builder.ins().band(lhs, rhs),
BinaryOp::Or => builder.ins().bor(lhs, rhs),
_ => return Err(CompileError {
message: format!("Unsupported binary operator: {:?}", op),
}),
};
Ok(result)
}
Expr::UnaryOp { op, operand, .. } => {
let val = compile_expr(builder, operand, variables, next_var, func_ids, module)?;
let result = match op {
UnaryOp::Neg => builder.ins().ineg(val),
UnaryOp::Not => {
let one = builder.ins().iconst(types::I64, 1);
builder.ins().bxor(val, one)
}
};
Ok(result)
}
Expr::If { condition, then_branch, else_branch, .. } => {
let cond_val = compile_expr(builder, condition, variables, next_var, func_ids, module)?;
// Create blocks
let then_block = builder.create_block();
let else_block = builder.create_block();
let merge_block = builder.create_block();
// Add block parameter for the result
builder.append_block_param(merge_block, types::I64);
// Branch based on condition
let zero = builder.ins().iconst(types::I64, 0);
let cmp = builder.ins().icmp(IntCC::NotEqual, cond_val, zero);
builder.ins().brif(cmp, then_block, &[], else_block, &[]);
// Then block
builder.switch_to_block(then_block);
builder.seal_block(then_block);
let then_val = compile_expr(builder, then_branch, variables, next_var, func_ids, module)?;
builder.ins().jump(merge_block, &[then_val]);
// Else block
builder.switch_to_block(else_block);
builder.seal_block(else_block);
let else_val = compile_expr(builder, else_branch, variables, next_var, func_ids, module)?;
builder.ins().jump(merge_block, &[else_val]);
// Merge block
builder.switch_to_block(merge_block);
builder.seal_block(merge_block);
Ok(builder.block_params(merge_block)[0])
}
Expr::Let { name, value, body, .. } => {
// Compile the value
let val = compile_expr(builder, value, variables, next_var, func_ids, module)?;
// Create a new variable
let var = Variable::from_u32(next_var as u32);
builder.declare_var(var, types::I64);
builder.def_var(var, val);
variables.insert(name.name.clone(), var);
// Compile the body with the new variable in scope
compile_expr(builder, body, variables, next_var + 1, func_ids, module)
}
Expr::Call { func, args, .. } => {
// Check if it's a direct function call
if let Expr::Var(name) = func.as_ref() {
// Look up the function
let func_id = *func_ids.get(&name.name).ok_or_else(|| CompileError {
message: format!("Unknown function: {}", name.name),
})?;
// Compile arguments
let mut arg_values = Vec::new();
for arg in args {
arg_values.push(compile_expr(builder, arg, variables, next_var, func_ids, module)?);
}
// Get function reference
let func_ref = module.declare_func_in_func(func_id, builder.func);
// Make the call
let call = builder.ins().call(func_ref, &arg_values);
Ok(builder.inst_results(call)[0])
} else {
Err(CompileError {
message: "Only direct function calls supported".to_string(),
})
}
}
Expr::Block { statements, result: block_result, .. } => {
let mut current_var = next_var;
// Compile all statements
for stmt in statements {
match stmt {
Statement::Let { name, value, .. } => {
let val = compile_expr(builder, value, variables, current_var, func_ids, module)?;
let var = Variable::from_u32(current_var as u32);
builder.declare_var(var, types::I64);
builder.def_var(var, val);
variables.insert(name.name.clone(), var);
current_var += 1;
}
Statement::Expr(expr) => {
compile_expr(builder, expr, variables, current_var, func_ids, module)?;
}
}
}
// Compile and return the result expression
compile_expr(builder, block_result, variables, current_var, func_ids, module)
}
expr => {
use crate::ast::LiteralKind;
let expr_type = match expr {
Expr::Literal(lit) => match &lit.kind {
LiteralKind::String(_) => "String literal",
LiteralKind::Float(_) => "Float literal",
LiteralKind::Char(_) => "Char literal",
LiteralKind::Unit => "Unit literal",
_ => "Literal",
},
Expr::EffectOp { effect, operation, .. } => {
return Err(CompileError {
message: format!("Effect operation '{}.{}' - effects are not supported in JIT",
effect.name, operation.name),
});
}
Expr::Field { .. } => "Field access (records)",
Expr::Lambda { .. } => "Lambda/closure",
Expr::Match { .. } => "Match expression",
Expr::List { .. } => "List literal",
Expr::Record { .. } => "Record literal",
Expr::Tuple { .. } => "Tuple literal",
Expr::Run { .. } => "Run expression (effects)",
Expr::Resume { .. } => "Resume expression (effects)",
_ => "Unknown expression",
};
Err(CompileError {
message: format!("Unsupported in JIT: {}", expr_type),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::Parser;
fn parse_function(src: &str) -> FunctionDecl {
let program = Parser::parse_source(src).expect("Parse error");
match &program.declarations[0] {
Declaration::Function(f) => f.clone(),
_ => panic!("Expected function"),
}
}
#[test]
fn test_simple_arithmetic() {
let func = parse_function("fn test(): Int = 1 + 2 * 3");
let mut jit = JitCompiler::new().unwrap();
let result = jit.compile_and_run(&func).unwrap();
assert_eq!(result, 7);
}
#[test]
fn test_conditionals() {
let func = parse_function("fn test(): Int = if 1 > 0 then 42 else 0");
let mut jit = JitCompiler::new().unwrap();
let result = jit.compile_and_run(&func).unwrap();
assert_eq!(result, 42);
}
#[test]
fn test_let_binding() {
let func = parse_function("fn test(): Int = { let x = 10; let y = 20; x + y }");
let mut jit = JitCompiler::new().unwrap();
let result = jit.compile_and_run(&func).unwrap();
assert_eq!(result, 30);
}
#[test]
fn test_recursive_fibonacci() {
use std::time::Instant;
// Compile a program with recursive fibonacci
let src = r#"
fn fib(n: Int): Int = if n <= 1 then n else fib(n - 1) + fib(n - 2)
"#;
let program = Parser::parse_source(src).expect("Parse error");
let mut jit = JitCompiler::new().unwrap();
let compile_start = Instant::now();
jit.compile_program(&program).unwrap();
let compile_time = compile_start.elapsed();
// Call fib(30)
let exec_start = Instant::now();
let result = unsafe { jit.call_function("fib", &[30]).unwrap() };
let exec_time = exec_start.elapsed();
println!("\n=== JIT Benchmark ===");
println!("Compile time: {:?}", compile_time);
println!("Execute fib(30) = {} in {:?}", result, exec_time);
println!("Total: {:?}", compile_time + exec_time);
assert_eq!(result, 832040);
}
}

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);
}
}