Skip to content

Commit 87a8976

Browse files
committed
feat: Wire up LLVM AOT and JIT backends in CLI
- Add llvm-backend feature flag to zyntax_cli - Implement compile_llvm() for AOT compilation to native executables - Implement compile_and_run_llvm() for JIT execution - Add LlvmJit backend variant for REPL support - Use log:: macros instead of println/eprintln - Provide clear error messages when LLVM feature is disabled Backend options: - jit, cranelift: Cranelift JIT (default) - llvm, llvm-aot: LLVM AOT compilation - llvm-jit: LLVM JIT execution To enable LLVM: cargo build --release --features llvm-backend
1 parent 3d7c73d commit 87a8976

3 files changed

Lines changed: 267 additions & 9 deletions

File tree

crates/zyntax_cli/Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ path = "src/main.rs"
99

1010
[dependencies]
1111
zyntax_typed_ast = { path = "../typed_ast" }
12-
zyntax_compiler = { path = "../compiler" }
12+
zyntax_compiler = { path = "../compiler", features = ["cranelift-backend"] }
1313
zyn_peg = { path = "../zyn_peg" }
1414
pest = "2.7"
1515
pest_derive = "2.7"
@@ -46,3 +46,11 @@ log = "0.4"
4646
# REPL
4747
rustyline = "14.0"
4848
dirs = "5.0"
49+
50+
# LLVM backend (optional)
51+
inkwell = { version = "0.4", features = ["llvm17-0"], optional = true }
52+
53+
[features]
54+
default = []
55+
llvm-backend = ["zyntax_compiler/llvm-backend", "inkwell"]
56+
all-backends = ["llvm-backend"]
Lines changed: 243 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,129 @@
11
//! LLVM AOT backend compilation
2+
//!
3+
//! Compiles HIR modules to native executables using LLVM's optimizing compiler.
4+
//! This backend produces highly optimized machine code suitable for production use.
25
36
use colored::Colorize;
7+
use log::{debug, error, info};
48
use std::path::PathBuf;
59
use zyntax_compiler::hir::HirModule;
610

711
/// Compile HIR module with LLVM AOT backend
12+
#[cfg(feature = "llvm-backend")]
13+
pub fn compile_llvm(
14+
module: HirModule,
15+
output: Option<PathBuf>,
16+
opt_level: u8,
17+
verbose: bool,
18+
) -> Result<(), Box<dyn std::error::Error>> {
19+
use inkwell::context::Context;
20+
use inkwell::targets::{
21+
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine,
22+
};
23+
use inkwell::OptimizationLevel;
24+
use zyntax_compiler::llvm_backend::LLVMBackend;
25+
26+
let output_path = output.unwrap_or_else(|| PathBuf::from("a.out"));
27+
28+
if verbose {
29+
info!("Initializing LLVM backend...");
30+
}
31+
32+
// Initialize LLVM targets
33+
Target::initialize_native(&InitializationConfig::default())
34+
.map_err(|e| format!("Failed to initialize LLVM target: {}", e))?;
35+
36+
// Create LLVM context and backend
37+
let context = Context::create();
38+
let mut backend = LLVMBackend::new(&context, "zyntax_aot");
39+
40+
if verbose {
41+
info!("Compiling HIR to LLVM IR...");
42+
}
43+
44+
// Compile HIR module to LLVM IR
45+
let llvm_ir = backend
46+
.compile_module(&module)
47+
.map_err(|e| format!("Compilation failed: {}", e))?;
48+
49+
if verbose {
50+
info!("Generated {} bytes of LLVM IR", llvm_ir.len());
51+
}
52+
53+
// Get target triple for the host machine
54+
let triple = TargetMachine::get_default_triple();
55+
let target = Target::from_triple(&triple)
56+
.map_err(|e| format!("Failed to get target: {}", e))?;
57+
58+
// Map optimization level
59+
let llvm_opt = match opt_level {
60+
0 => OptimizationLevel::None,
61+
1 => OptimizationLevel::Less,
62+
2 => OptimizationLevel::Default,
63+
_ => OptimizationLevel::Aggressive,
64+
};
65+
66+
if verbose {
67+
info!(
68+
"Target: {}, Optimization: {:?}",
69+
triple.as_str().to_str().unwrap_or("unknown"),
70+
llvm_opt
71+
);
72+
}
73+
74+
// Create target machine
75+
let target_machine = target
76+
.create_target_machine(
77+
&triple,
78+
"generic",
79+
"",
80+
llvm_opt,
81+
RelocMode::Default,
82+
CodeModel::Default,
83+
)
84+
.ok_or("Failed to create target machine")?;
85+
86+
// Write object file
87+
let obj_path = output_path.with_extension("o");
88+
89+
if verbose {
90+
info!("Writing object file to {:?}...", obj_path);
91+
}
92+
93+
target_machine
94+
.write_to_file(backend.module(), FileType::Object, &obj_path)
95+
.map_err(|e| format!("Failed to write object file: {}", e))?;
96+
97+
// Link to create executable
98+
if verbose {
99+
info!("Linking executable...");
100+
}
101+
102+
let status = std::process::Command::new("cc")
103+
.arg(&obj_path)
104+
.arg("-o")
105+
.arg(&output_path)
106+
.status()
107+
.map_err(|e| format!("Failed to run linker: {}", e))?;
108+
109+
if !status.success() {
110+
return Err(format!("Linker failed with status: {}", status).into());
111+
}
112+
113+
// Clean up object file
114+
let _ = std::fs::remove_file(&obj_path);
115+
116+
println!(
117+
"{} Successfully compiled to {}",
118+
"success:".green().bold(),
119+
output_path.display()
120+
);
121+
122+
Ok(())
123+
}
124+
125+
/// Compile HIR module with LLVM AOT backend (stub when feature not enabled)
126+
#[cfg(not(feature = "llvm-backend"))]
8127
pub fn compile_llvm(
9128
_module: HirModule,
10129
output: Option<PathBuf>,
@@ -13,9 +132,129 @@ pub fn compile_llvm(
13132
) -> Result<(), Box<dyn std::error::Error>> {
14133
let output_path = output.unwrap_or_else(|| PathBuf::from("a.out"));
15134

16-
// TODO: Implement LLVM backend compilation
17-
eprintln!("{} LLVM backend not yet implemented", "error:".red());
18-
eprintln!("Output would be: {}", output_path.display());
135+
error!("LLVM backend not enabled");
136+
error!("Rebuild with: cargo build --release --features llvm-backend");
137+
debug!("Output would be: {}", output_path.display());
138+
139+
Err("LLVM backend not enabled. Rebuild with --features llvm-backend".into())
140+
}
141+
142+
/// Compile and run with LLVM JIT backend
143+
#[cfg(feature = "llvm-backend")]
144+
pub fn compile_and_run_llvm(
145+
module: HirModule,
146+
opt_level: u8,
147+
verbose: bool,
148+
) -> Result<i64, Box<dyn std::error::Error>> {
149+
use inkwell::context::Context;
150+
use inkwell::OptimizationLevel;
151+
use zyntax_compiler::llvm_jit_backend::LLVMJitBackend;
152+
153+
if verbose {
154+
info!("Initializing LLVM JIT backend...");
155+
}
156+
157+
// Create LLVM context
158+
let context = Context::create();
159+
160+
// Map optimization level
161+
let llvm_opt = match opt_level {
162+
0 => OptimizationLevel::None,
163+
1 => OptimizationLevel::Less,
164+
2 => OptimizationLevel::Default,
165+
_ => OptimizationLevel::Aggressive,
166+
};
167+
168+
// Create JIT backend
169+
let mut backend = LLVMJitBackend::with_opt_level(&context, llvm_opt)
170+
.map_err(|e| format!("Failed to create JIT backend: {}", e))?;
171+
172+
if verbose {
173+
info!("Compiling module with LLVM JIT...");
174+
}
175+
176+
// Compile module
177+
backend
178+
.compile_module(&module)
179+
.map_err(|e| format!("JIT compilation failed: {}", e))?;
180+
181+
// Find main function
182+
let (main_id, main_fn) = module
183+
.functions
184+
.iter()
185+
.find(|(_, func)| {
186+
func.name
187+
.resolve_global()
188+
.map(|name| name == "main")
189+
.unwrap_or(false)
190+
})
191+
.ok_or("No 'main' function found in module")?;
192+
193+
let main_id = *main_id;
194+
195+
// Get function pointer
196+
let fn_ptr = backend
197+
.get_function_pointer(main_id)
198+
.ok_or("Failed to get main function pointer")?;
199+
200+
if verbose {
201+
debug!("Executing main() at {:?}...", fn_ptr);
202+
}
203+
204+
// Execute main function
205+
let result = unsafe {
206+
if main_fn.signature.returns.is_empty() {
207+
let f: fn() = std::mem::transmute(fn_ptr);
208+
f();
209+
0
210+
} else {
211+
match &main_fn.signature.returns[0] {
212+
zyntax_compiler::hir::HirType::I32 => {
213+
let f: fn() -> i32 = std::mem::transmute(fn_ptr);
214+
f() as i64
215+
}
216+
zyntax_compiler::hir::HirType::I64 => {
217+
let f: fn() -> i64 = std::mem::transmute(fn_ptr);
218+
f()
219+
}
220+
zyntax_compiler::hir::HirType::F32 => {
221+
let f: fn() -> f32 = std::mem::transmute(fn_ptr);
222+
f() as i64
223+
}
224+
zyntax_compiler::hir::HirType::F64 => {
225+
let f: fn() -> f64 = std::mem::transmute(fn_ptr);
226+
f() as i64
227+
}
228+
zyntax_compiler::hir::HirType::Void => {
229+
let f: fn() = std::mem::transmute(fn_ptr);
230+
f();
231+
0
232+
}
233+
_ => {
234+
return Err(format!(
235+
"Unsupported return type: {:?}",
236+
main_fn.signature.returns[0]
237+
)
238+
.into());
239+
}
240+
}
241+
}
242+
};
243+
244+
println!("{} main() returned: {}", "result:".green().bold(), result);
245+
246+
Ok(result)
247+
}
248+
249+
/// Compile and run with LLVM JIT backend (stub when feature not enabled)
250+
#[cfg(not(feature = "llvm-backend"))]
251+
pub fn compile_and_run_llvm(
252+
_module: HirModule,
253+
_opt_level: u8,
254+
_verbose: bool,
255+
) -> Result<i64, Box<dyn std::error::Error>> {
256+
error!("LLVM JIT backend not enabled");
257+
error!("Rebuild with: cargo build --release --features llvm-backend");
19258

20-
Err("LLVM backend not implemented".into())
259+
Err("LLVM backend not enabled. Rebuild with --features llvm-backend".into())
21260
}

crates/zyntax_cli/src/backends/mod.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,23 @@ use std::path::PathBuf;
77
use zyntax_compiler::hir::HirModule;
88

99
pub use cranelift_jit::compile_jit;
10-
pub use llvm_aot::compile_llvm;
10+
pub use llvm_aot::{compile_llvm, compile_and_run_llvm};
1111

1212
/// Backend type
1313
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1414
pub enum Backend {
1515
CraneliftJit,
1616
LlvmAot,
17+
LlvmJit,
1718
}
1819

1920
impl Backend {
2021
pub fn from_str(s: &str) -> Result<Self, Box<dyn std::error::Error>> {
2122
match s {
2223
"jit" | "cranelift" => Ok(Backend::CraneliftJit),
23-
"llvm" | "aot" => Ok(Backend::LlvmAot),
24-
_ => Err(format!("Unknown backend: {}", s).into()),
24+
"llvm" | "aot" | "llvm-aot" => Ok(Backend::LlvmAot),
25+
"llvm-jit" => Ok(Backend::LlvmJit),
26+
_ => Err(format!("Unknown backend: {}. Use: jit, cranelift, llvm, llvm-aot, llvm-jit", s).into()),
2527
}
2628
}
2729
}
@@ -38,6 +40,14 @@ pub fn compile(
3840
match backend {
3941
Backend::CraneliftJit => compile_jit(module, opt_level, run, verbose),
4042
Backend::LlvmAot => compile_llvm(module, output, opt_level, verbose),
43+
Backend::LlvmJit => {
44+
if run {
45+
compile_and_run_llvm(module, opt_level, verbose)?;
46+
Ok(())
47+
} else {
48+
Err("LLVM JIT backend requires --run flag".into())
49+
}
50+
}
4151
}
4252
}
4353

@@ -50,6 +60,7 @@ pub fn compile_and_run_repl(
5060
) -> Result<i64, Box<dyn std::error::Error>> {
5161
match backend {
5262
Backend::CraneliftJit => cranelift_jit::compile_and_run_repl(module, opt_level, verbose),
53-
Backend::LlvmAot => Err("LLVM backend does not support REPL mode".into()),
63+
Backend::LlvmAot => Err("LLVM AOT backend does not support REPL mode. Use --backend llvm-jit".into()),
64+
Backend::LlvmJit => compile_and_run_llvm(module, opt_level, verbose),
5465
}
5566
}

0 commit comments

Comments
 (0)