From 9a33836f58dcf13eb41cb7d40a52f97097a7a8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Christiansen=20S=C3=B8rby?= Date: Fri, 1 May 2026 20:46:44 +0200 Subject: [PATCH] feat(llvm-codegen): add compilation pipeline and extract CLI - Add compiler.rs with full compile-to-binary pipeline (IR generation, clang compilation, linking) - Extract CLI entry point from core into dedicated cli package - Refactor codegen modules for better organization - Fix atomic refcount type in runtime (use _Atomic for C11 atomics) --- Cargo.lock | 11 + Cargo.toml | 2 +- cli/Cargo.toml | 21 + cli/src/main.rs | 151 +++++++ core/Cargo.toml | 8 +- core/src/eval/native.rs | 2 +- core/src/main.rs | 71 --- core/src/term.rs | 7 +- init/io.mo | 2 +- llvm-codegen/Cargo.toml | 1 + llvm-codegen/src/codegen/constructors.rs | 113 ++--- llvm-codegen/src/codegen/context.rs | 155 ++++--- llvm-codegen/src/codegen/control.rs | 90 ++-- llvm-codegen/src/codegen/lambda.rs | 49 +- llvm-codegen/src/codegen/literals.rs | 29 +- llvm-codegen/src/codegen/mod.rs | 419 ++++++++++++----- llvm-codegen/src/codegen/native.rs | 58 +-- llvm-codegen/src/codegen/term.rs | 139 +++--- llvm-codegen/src/compiler.rs | 552 +++++++++++++++++++++++ llvm-codegen/src/ir/function.rs | 199 ++++---- llvm-codegen/src/ir/module.rs | 206 ++++----- llvm-codegen/src/ir/types.rs | 98 ++-- llvm-codegen/src/ir/value.rs | 277 +++++++----- llvm-codegen/src/lib.rs | 5 + llvm-codegen/src/runtime/mod.rs | 12 +- llvm-codegen/tests/codegen_tests.rs | 241 +++++----- 26 files changed, 1936 insertions(+), 982 deletions(-) create mode 100644 cli/Cargo.toml create mode 100644 cli/src/main.rs delete mode 100644 core/src/main.rs create mode 100644 llvm-codegen/src/compiler.rs diff --git a/Cargo.lock b/Cargo.lock index 844b11e..a1de239 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,6 +241,16 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "monad-cli" +version = "0.1.1" +dependencies = [ + "clap", + "monad-core", + "monad-llvm-codegen", + "rustyline", +] + [[package]] name = "monad-core" version = "0.1.1" @@ -257,6 +267,7 @@ dependencies = [ name = "monad-llvm-codegen" version = "0.1.1" dependencies = [ + "clap", "monad-core", "pretty_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index 52ea379..26baca2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["core", "wasm", "llvm-codegen"] +members = ["core", "cli", "wasm", "llvm-codegen"] [workspace.package] version = "0.1.1" diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..5bea89e --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "monad-cli" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true + +[[bin]] +name = "monad" +path = "src/main.rs" + +[dependencies] +monad-core = { path = "../core" } +monad-llvm-codegen = { path = "../llvm-codegen", optional = true } +clap = { version = "4", features = ["derive"] } +rustyline = { version = "17.0.2", optional = true } + +[features] +default = ["repl", "llvm"] +repl = ["dep:rustyline", "monad-core/repl"] +llvm = ["dep:monad-llvm-codegen"] diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000..b0e7e60 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,151 @@ +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; +use monad_core::{eval::EvalOptions, run, run_tests}; + +#[cfg(feature = "repl")] +use monad_core::repl; + +#[cfg(feature = "llvm")] +use monad_llvm_codegen::{CompileOptions, OutputKind, compile}; + +#[derive(Subcommand, Debug)] +enum Commands { + Repl { + #[arg(short, long, default_value_t = false)] + debug: bool, + }, + + Run { + #[arg(value_name = "FILE")] + input: PathBuf, + #[arg(short, long, default_value_t = false)] + debug: bool, + #[arg(value_name = "ARGS", trailing_var_arg = true)] + args: Vec, + }, + + Test { + #[arg(value_name = "FILE")] + input: PathBuf, + #[arg(short, long, default_value_t = false)] + debug: bool, + }, + + #[cfg(feature = "llvm")] + Compile { + #[arg(value_name = "FILE")] + input: PathBuf, + #[arg(short, long, default_value = ".")] + output_dir: PathBuf, + #[arg(long)] + output_name: Option, + #[arg(long, default_value = "exe")] + output_kind: String, + #[arg(long, default_value_t = false)] + keep_intermediates: bool, + #[arg(short, long, default_value_t = false)] + debug: bool, + }, +} + +#[derive(Debug, Parser)] +#[command( + name = "monad", + version, + about = "Monad language interpreter and compiler" +)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +fn main() -> Result<(), String> { + let cli = Cli::parse(); + + match cli.command { + #[cfg(feature = "repl")] + Commands::Repl { debug } => repl(EvalOptions { debug }).map_err(|e| e.to_string()), + #[cfg(not(feature = "repl"))] + Commands::Repl { .. } => { + Err("REPL support was not compiled in. Install with repl feature enabled.".into()) + } + Commands::Run { input, debug, args } => { + let result = run(input, args, EvalOptions { debug }); + match result { + Ok(_) => (), + Err(ref e) => { + eprintln!("error: {e}") + } + } + result + } + Commands::Test { input, debug } => { + let result = run_tests(input, EvalOptions { debug }); + match result { + Ok(_) => (), + Err(ref e) => { + eprintln!("error: {e}") + } + } + result + } + #[cfg(feature = "llvm")] + Commands::Compile { + input, + output_dir, + output_name, + output_kind, + keep_intermediates, + debug, + } => { + let output_kind = match output_kind.as_str() { + "exe" => OutputKind::Executable, + "shared" | "so" => OutputKind::SharedObject, + _ => { + return Err(format!( + "Unknown output kind: {output_kind}. Use 'exe' or 'shared'." + )); + } + }; + + let name = output_name.unwrap_or_else(|| { + input + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("output") + .to_string() + }); + + let options = CompileOptions { + output_dir, + output_name: name, + output_kind, + keep_intermediates, + }; + + let result = compile(&input, options); + match &result { + Ok(r) => { + if debug { + println!("Step 1: LLVM IR generated -> {}", r.ir_path.display()); + println!( + "Step 2: Object file compiled -> {}", + r.object_path.display() + ); + println!( + "Step 3: Runtime compiled -> {}", + r.runtime_object_path.display() + ); + println!("Step 4: Linked -> {}", r.output_path.display()); + } + println!("Output: {}", r.output_path.display()); + } + Err(e) => { + eprintln!("error: {e}") + } + } + result.map(|_| ()) + } + } +} diff --git a/core/Cargo.toml b/core/Cargo.toml index 7f42556..66c390a 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -13,16 +13,10 @@ console_error_panic_hook = { version = "0.1", optional = true } rustyline = { version = "17.0.2", optional = true } [features] -default = ["repl", "cli"] +default = [] console_error_panic_hook = ["dep:console_error_panic_hook"] repl = ["dep:rustyline"] cli = ["dep:clap"] -[[bin]] -name = "monad" -path = "src/main.rs" -required-features = ["cli"] - - [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/core/src/eval/native.rs b/core/src/eval/native.rs index 79de9d3..4ef6c47 100644 --- a/core/src/eval/native.rs +++ b/core/src/eval/native.rs @@ -310,7 +310,7 @@ pub type NativeFun = fn(Vec) -> Result; pub fn load_native_funs() -> Map { let v: Vec<(Identifier, NativeFun)> = vec![ - (id("println"), println), + (id("print_str"), println), (id("i8_add"), i8_add), (id("i8_sub"), i8_sub), (id("i8_mul"), i8_mul), diff --git a/core/src/main.rs b/core/src/main.rs deleted file mode 100644 index 3bfff84..0000000 --- a/core/src/main.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::path::PathBuf; - -use clap::{Parser, Subcommand}; -use monad_core::{eval::EvalOptions, run, run_tests}; - -#[cfg(feature = "repl")] -use monad_core::repl; - -#[derive(Subcommand, Debug)] -enum Commands { - Repl { - #[arg(short, long, default_value_t = false)] - debug: bool, - }, - - Run { - #[arg(value_name = "FILE")] - input: PathBuf, - #[arg(short, long, default_value_t = false)] - debug: bool, - #[arg(value_name = "ARGS", trailing_var_arg = true)] - args: Vec, - }, - - Test { - #[arg(value_name = "FILE")] - input: PathBuf, - #[arg(short, long, default_value_t = false)] - debug: bool, - }, -} - -#[derive(Debug, Parser)] -#[command(name = "monad", version, about = "Monad language interpreter")] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -fn main() -> Result<(), String> { - let cli = Cli::parse(); - - match cli.command { - #[cfg(feature = "repl")] - Commands::Repl { debug } => repl(EvalOptions { debug }).map_err(|e| e.to_string()), - #[cfg(not(feature = "repl"))] - Commands::Repl { .. } => { - Err("REPL support was not compiled in. Install with repl feature enabled.".into()) - } - Commands::Run { input, debug, args } => { - let result = run(input, args, EvalOptions { debug }); - match result { - Ok(_) => (), - Err(ref e) => { - println!("error: {e}") - } - } - result - } - Commands::Test { input, debug } => { - let result = run_tests(input, EvalOptions { debug }); - match result { - Ok(_) => (), - Err(ref e) => { - println!("error: {e}") - } - } - result - } - } -} diff --git a/core/src/term.rs b/core/src/term.rs index 32adc33..397d3c5 100644 --- a/core/src/term.rs +++ b/core/src/term.rs @@ -1228,12 +1228,7 @@ impl Term { pub fn as_if(&self) -> Option<(&Term, &Term, &Term)> { match self { Lit { - value: - Literal::If { - value, - then, - els, - }, + value: Literal::If { value, then, els }, } => Some((value, then, els)), Ctx { term, .. } => term.as_if(), _ => None, diff --git a/init/io.mo b/init/io.mo index 3a10381..f549c22 100644 --- a/init/io.mo +++ b/init/io.mo @@ -14,7 +14,7 @@ instance Monad IO { } } -@[native println] +@[native print_str] def IO.println (s: String) : IO Unit // TODO support constraints diff --git a/llvm-codegen/Cargo.toml b/llvm-codegen/Cargo.toml index 49084ed..923100d 100644 --- a/llvm-codegen/Cargo.toml +++ b/llvm-codegen/Cargo.toml @@ -7,6 +7,7 @@ authors.workspace = true [dependencies] monad-core = { path = "../core" } +clap = { version = "4", features = ["derive"] } [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/llvm-codegen/src/codegen/constructors.rs b/llvm-codegen/src/codegen/constructors.rs index fd8a3bb..1cf42cb 100644 --- a/llvm-codegen/src/codegen/constructors.rs +++ b/llvm-codegen/src/codegen/constructors.rs @@ -6,76 +6,77 @@ use super::context::CodegenCtx; use super::term::compile_term; pub fn compile_constructor_term( - ctx: &mut CodegenCtx, - constructor: &Constructor, + ctx: &mut CodegenCtx, + constructor: &Constructor, ) -> Result { - let compiled_fields: Result, String> = constructor - .args() - .iter() - .filter_map(|arg| arg.as_ref().map(|a| compile_term(ctx, a))) - .collect(); - - let fields = compiled_fields?; - - let tag = 0; - let alloc = LLVMValue::AllocConstructor { tag, fields }; - - let temp = ctx.fresh_temp(); - ctx.current_function_mut()? - .blocks - .last_mut() - .unwrap() - .add(LLVMInstruction::Assign { - target: temp.clone(), - value: alloc, - }); - - Ok(LLVMValue::Var(temp)) + let compiled_fields: Result, String> = constructor + .args() + .iter() + .filter_map(|arg| arg.as_ref().map(|a| compile_term(ctx, a))) + .collect(); + + let fields = compiled_fields?; + + let tag = 0; + let alloc = LLVMValue::AllocConstructor { tag, fields }; + + let temp = ctx.fresh_temp(); + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: temp.clone(), + value: alloc, + }); + + Ok(LLVMValue::Var(temp)) } #[allow(dead_code)] pub fn compile_constructor_decl( - ctx: &mut CodegenCtx, - name: &str, - field_count: usize, + ctx: &mut CodegenCtx, + name: &str, + field_count: usize, ) -> Result<(), String> { - let params: Vec<(String, LLVMType)> = (0..field_count) - .map(|i| (format!("p{}", i), LLVMType::I64)) - .collect(); + let params: Vec<(String, LLVMType)> = (0..field_count) + .map(|i| (format!("p{}", i), LLVMType::I64)) + .collect(); - let return_type = LLVMType::I64; + let return_type = LLVMType::I64; - let mut func = LLVMFunction::new(name, params, return_type); + let mut func = LLVMFunction::new(name, params, return_type); - let entry_block = LLVMBasicBlock::new("entry"); - func.add_block(entry_block); + let entry_block = LLVMBasicBlock::new("entry"); + func.add_block(entry_block); - ctx.push_function(func); + ctx.push_function(func); - let fields: Vec = (0..field_count) - .map(|i| LLVMValue::Param(i)) - .collect(); + let fields: Vec = (0..field_count).map(|i| LLVMValue::Param(i)).collect(); - let tag = 0; - let alloc = LLVMValue::AllocConstructor { tag, fields }; + let tag = 0; + let alloc = LLVMValue::AllocConstructor { tag, fields }; - let temp = ctx.fresh_temp(); - ctx.current_function_mut()? - .blocks - .last_mut() - .unwrap() - .add(LLVMInstruction::Assign { - target: temp.clone(), - value: alloc, - }); + let temp = ctx.fresh_temp(); + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: temp.clone(), + value: alloc, + }); - ctx.pop_function(); + ctx.pop_function(); - let func = ctx.module.functions.last_mut().unwrap(); - func.blocks - .last_mut() - .unwrap() - .add(LLVMInstruction::Return(LLVMValue::Var(temp))); + let func = ctx.module.functions.last_mut().unwrap(); + func + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Return(LLVMValue::Var(temp))); - Ok(()) + Ok(()) } diff --git a/llvm-codegen/src/codegen/context.rs b/llvm-codegen/src/codegen/context.rs index ee1a303..0c92fe6 100644 --- a/llvm-codegen/src/codegen/context.rs +++ b/llvm-codegen/src/codegen/context.rs @@ -1,78 +1,125 @@ use std::collections::HashMap; -use monad_core::term::Identifier; +use monad_core::term::{Identifier, ModulePath}; use crate::ir::{LLVMFunction, LLVMModule, LLVMValue}; #[derive(Clone)] pub struct CodegenCtx { - pub module: LLVMModule, - pub locals: HashMap, - pub next_temp: u64, - pub next_label: u64, - pub function_stack: Vec, + pub module: LLVMModule, + pub locals: HashMap, + pub next_temp: u64, + pub next_label: u64, + pub function_stack: Vec, + pub native_funs: HashMap, } impl CodegenCtx { - pub fn new() -> Self { - CodegenCtx { - module: LLVMModule::new(), - locals: HashMap::new(), - next_temp: 0, - next_label: 0, - function_stack: vec![], - } + pub fn new() -> Self { + let mut module = LLVMModule::new(); + Self::add_runtime_declarations(&mut module); + CodegenCtx { + module, + locals: HashMap::new(), + next_temp: 0, + next_label: 0, + function_stack: vec![], + native_funs: HashMap::new(), } + } - pub fn fresh_temp(&mut self) -> String { - let name = format!("t{}", self.next_temp); - self.next_temp += 1; - name + fn add_runtime_declarations(module: &mut LLVMModule) { + let runtime_funs = [ + ("monad_alloc", vec!["i64".to_string()], "i8*".to_string()), + ("monad_retain", vec!["i8*".to_string()], "void".to_string()), + ("monad_release", vec!["i8*".to_string()], "void".to_string()), + ( + "monad_print_str", + vec!["i8*".to_string()], + "void".to_string(), + ), + ( + "alloc_closure", + vec!["i8*".to_string(), "i64".to_string(), "i64".to_string()], + "%Closure*".to_string(), + ), + ( + "alloc_constructor", + vec!["i64".to_string(), "i64".to_string()], + "%Constructor*".to_string(), + ), + ( + "alloc_string", + vec!["i8*".to_string(), "i64".to_string()], + "%StringObj*".to_string(), + ), + ]; + for (name, params, ret) in runtime_funs { + module.add_declaration(crate::ir::LLVMDeclaration { + name: name.to_string(), + params, + return_type: ret, + }); } + } - pub fn fresh_label(&mut self, prefix: &str) -> String { - let name = format!("{}_{}", prefix, self.next_label); - self.next_label += 1; - name - } + pub fn register_native(&mut self, name: ModulePath, native_name: String) { + self.native_funs.insert(name, native_name); + } - pub fn bind_local(&mut self, name: Identifier, value: LLVMValue) { - self.locals.insert(name, value); - } + pub fn lookup_native(&self, name: &ModulePath) -> Option<&String> { + self.native_funs.get(name) + } - pub fn lookup_local(&self, name: &Identifier) -> Option { - self.locals.get(name).cloned() - } + pub fn fresh_temp(&mut self) -> String { + let name = format!("t{}", self.next_temp); + self.next_temp += 1; + name + } - pub fn push_scope(&mut self) { - self.locals = HashMap::new(); - } + pub fn fresh_label(&mut self, prefix: &str) -> String { + let name = format!("{}_{}", prefix, self.next_label); + self.next_label += 1; + name + } - pub fn pop_scope(&mut self) { - // Outer scope handling would go here for nested scopes - } + pub fn bind_local(&mut self, name: Identifier, value: LLVMValue) { + self.locals.insert(name, value); + } - pub fn push_function(&mut self, func: LLVMFunction) { - self.function_stack.push(func); - } + pub fn lookup_local(&self, name: &Identifier) -> Option { + self.locals.get(name).cloned() + } - pub fn pop_function(&mut self) -> Option { - let func = self.function_stack.pop(); - if let Some(f) = &func { - self.module.add_function(f.clone()); - } - func - } + pub fn push_scope(&mut self) { + self.locals = HashMap::new(); + } - pub fn current_function_mut(&mut self) -> Result<&mut LLVMFunction, String> { - self.function_stack - .last_mut() - .ok_or_else(|| "No current function".to_string()) - } + pub fn pop_scope(&mut self) {} + + pub fn push_function(&mut self, func: LLVMFunction) { + self.function_stack.push(func); + } - pub fn current_function(&self) -> Result<&LLVMFunction, String> { - self.function_stack - .last() - .ok_or_else(|| "No current function".to_string()) + pub fn pop_function(&mut self) -> Option { + let func = self.function_stack.pop(); + if let Some(f) = &func { + self.module.add_function(f.clone()); } + func + } + + pub fn current_function_mut(&mut self) -> Result<&mut LLVMFunction, String> { + self + .function_stack + .last_mut() + .ok_or_else(|| "No current function".to_string()) + } + + pub fn current_function(&self) -> Result<&LLVMFunction, String> { + self + .function_stack + .last() + .ok_or_else(|| "No current function".to_string()) + } } diff --git a/llvm-codegen/src/codegen/control.rs b/llvm-codegen/src/codegen/control.rs index dcc98a0..b5a0fdd 100644 --- a/llvm-codegen/src/codegen/control.rs +++ b/llvm-codegen/src/codegen/control.rs @@ -6,50 +6,54 @@ use super::context::CodegenCtx; use super::term::compile_term; pub fn compile_if( - ctx: &mut CodegenCtx, - condition: &Term, - then_branch: &Term, - else_branch: &Term, + ctx: &mut CodegenCtx, + condition: &Term, + then_branch: &Term, + else_branch: &Term, ) -> Result { - let cond_val = compile_term(ctx, condition)?; - - let then_label = ctx.fresh_label("then"); - let else_label = ctx.fresh_label("else"); - let merge_label = ctx.fresh_label("merge"); - - let func = ctx.current_function_mut()?; - func.blocks.last_mut().unwrap().add(LLVMInstruction::Branch { - condition: cond_val, - then_label: then_label.clone(), - else_label: else_label.clone(), + let cond_val = compile_term(ctx, condition)?; + + let then_label = ctx.fresh_label("then"); + let else_label = ctx.fresh_label("else"); + let merge_label = ctx.fresh_label("merge"); + + let func = ctx.current_function_mut()?; + func + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Branch { + condition: cond_val, + then_label: then_label.clone(), + else_label: else_label.clone(), }); - let mut then_block = LLVMBasicBlock::new(&then_label); - let then_val = compile_term(ctx, then_branch)?; - then_block.add(LLVMInstruction::Jump { - label: merge_label.clone(), - }); - - let mut else_block = LLVMBasicBlock::new(&else_label); - let else_val = compile_term(ctx, else_branch)?; - else_block.add(LLVMInstruction::Jump { - label: merge_label.clone(), - }); - - let mut merge_block = LLVMBasicBlock::new(&merge_label); - let result_temp = ctx.fresh_temp(); - merge_block.add(LLVMInstruction::Assign { - target: result_temp.clone(), - value: LLVMValue::Phi(vec![ - (then_val, then_label.clone()), - (else_val, else_label.clone()), - ]), - }); - - let func = ctx.current_function_mut()?; - func.blocks.push(then_block); - func.blocks.push(else_block); - func.blocks.push(merge_block); - - Ok(LLVMValue::Var(result_temp)) + let mut then_block = LLVMBasicBlock::new(&then_label); + let then_val = compile_term(ctx, then_branch)?; + then_block.add(LLVMInstruction::Jump { + label: merge_label.clone(), + }); + + let mut else_block = LLVMBasicBlock::new(&else_label); + let else_val = compile_term(ctx, else_branch)?; + else_block.add(LLVMInstruction::Jump { + label: merge_label.clone(), + }); + + let mut merge_block = LLVMBasicBlock::new(&merge_label); + let result_temp = ctx.fresh_temp(); + merge_block.add(LLVMInstruction::Assign { + target: result_temp.clone(), + value: LLVMValue::Phi(vec![ + (then_val, then_label.clone()), + (else_val, else_label.clone()), + ]), + }); + + let func = ctx.current_function_mut()?; + func.blocks.push(then_block); + func.blocks.push(else_block); + func.blocks.push(merge_block); + + Ok(LLVMValue::Var(result_temp)) } diff --git a/llvm-codegen/src/codegen/lambda.rs b/llvm-codegen/src/codegen/lambda.rs index 2250f7e..d6eb3f3 100644 --- a/llvm-codegen/src/codegen/lambda.rs +++ b/llvm-codegen/src/codegen/lambda.rs @@ -6,36 +6,41 @@ use super::context::CodegenCtx; use super::term::compile_term; pub fn compile_lambda( - ctx: &mut CodegenCtx, - param: &Par, - body: &monad_core::term::Term, + ctx: &mut CodegenCtx, + param: &Par, + body: &monad_core::term::Term, ) -> Result { - let entry_name = ctx.fresh_label("lambda"); + let entry_name = ctx.fresh_label("lambda"); - let param_type = LLVMType::I64; - let return_type = LLVMType::I64; + let param_type = LLVMType::I64; + let return_type = LLVMType::I64; - let mut func = LLVMFunction::new(&entry_name, vec![("p0".to_string(), param_type)], return_type); + let mut func = LLVMFunction::new( + &entry_name, + vec![("p0".to_string(), param_type)], + return_type, + ); - let entry_block = LLVMBasicBlock::new("entry"); - func.add_block(entry_block); + let entry_block = LLVMBasicBlock::new("entry"); + func.add_block(entry_block); - ctx.push_function(func); + ctx.push_function(func); - if let Par::P(p) = param { - ctx.bind_local(p.name.clone(), LLVMValue::Param(0)); - } + if let Par::P(p) = param { + ctx.bind_local(p.name.clone(), LLVMValue::Param(0)); + } - let body_value = compile_term(ctx, body)?; + let body_value = compile_term(ctx, body)?; - let func = ctx.pop_function().unwrap(); - ctx.module.add_function(func.clone()); + let func = ctx.pop_function().unwrap(); + ctx.module.add_function(func.clone()); - let func = ctx.module.functions.last_mut().unwrap(); - func.blocks - .last_mut() - .unwrap() - .add(LLVMInstruction::Return(body_value)); + let func = ctx.module.functions.last_mut().unwrap(); + func + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Return(body_value)); - Ok(LLVMValue::Var(entry_name)) + Ok(LLVMValue::Var(entry_name)) } diff --git a/llvm-codegen/src/codegen/literals.rs b/llvm-codegen/src/codegen/literals.rs index 6cc4b64..9855156 100644 --- a/llvm-codegen/src/codegen/literals.rs +++ b/llvm-codegen/src/codegen/literals.rs @@ -5,20 +5,19 @@ use crate::ir::LLVMValue; use super::context::CodegenCtx; pub fn compile_literal(ctx: &mut CodegenCtx, term: &Term) -> Result { - match term { - Term::Lit { - value: Literal::Num { value, .. }, - } => Ok(LLVMValue::Int(*value)), - Term::Lit { - value: Literal::Str { value }, - } => { - let global_name = ctx.fresh_label("str"); - ctx.module.add_global(crate::ir::LLVMGlobal::string( - &global_name, - value, - )); - Ok(LLVMValue::Global(global_name)) - } - _ => Err(format!("Expected literal, got: {:?}", term)), + match term { + Term::Lit { + value: Literal::Num { value, .. }, + } => Ok(LLVMValue::Int(*value)), + Term::Lit { + value: Literal::Str { value }, + } => { + let global_name = ctx.fresh_label("str"); + ctx + .module + .add_global(crate::ir::LLVMGlobal::string(&global_name, value)); + Ok(LLVMValue::Global(global_name)) } + _ => Err(format!("Expected literal, got: {:?}", term)), + } } diff --git a/llvm-codegen/src/codegen/mod.rs b/llvm-codegen/src/codegen/mod.rs index b205240..a02fe44 100644 --- a/llvm-codegen/src/codegen/mod.rs +++ b/llvm-codegen/src/codegen/mod.rs @@ -1,5 +1,5 @@ -mod context; mod constructors; +mod context; mod control; mod lambda; mod literals; @@ -14,171 +14,348 @@ use monad_core::term::{Decl, Def, Named}; use crate::ir::{LLVMBasicBlock, LLVMFunction, LLVMInstruction, LLVMModule, LLVMType, LLVMValue}; pub fn compile_decls(decls: &[Decl]) -> Result { - let mut ctx = CodegenCtx::new(); - - for decl in decls { - match decl { - Decl::Def(def) => { - compile_def(&mut ctx, def)?; - } - Decl::Type(inductive) => { - for cons in inductive.constructors() { - compile_constructor_decl(&mut ctx, cons)?; - } - } - _ => {} + use monad_core::term::Term; + + let mut ctx = CodegenCtx::new(); + + for decl in decls { + match decl { + Decl::Def(def) => { + let mut body = &def.term; + while let Some((_, inner)) = body.as_lam() { + body = inner; + } + let is_native = matches!(body, Term::Ntv { .. }); + if is_native { + if let Term::Ntv { native } = body { + ctx.register_native(def.name().clone(), native.native_name.to_string()); + } } + compile_def(&mut ctx, def)?; + } + Decl::Type(inductive) => { + for cons in inductive.constructors() { + compile_constructor_decl(&mut ctx, cons)?; + } + } + _ => {} } + } - if ctx.module.functions.iter().any(|f| f.name == "main") { - compile_main_wrapper(&mut ctx)?; - } + if ctx.module.functions.iter().any(|f| f.name == "main") { + rename_main_to_monad(&mut ctx)?; + compile_main_wrapper(&mut ctx)?; + } - Ok(ctx.module) + Ok(ctx.module) +} + +fn is_i64_native(native_name: &str) -> bool { + matches!( + native_name, + "i64_add" | "i64_sub" | "i64_mul" | "i64_div" | "i64_eq" + ) +} + +fn rename_main_to_monad(ctx: &mut CodegenCtx) -> Result<(), String> { + for func in &mut ctx.module.functions { + if func.name == "main" { + func.name = "main_monad".to_string(); + break; + } + } + Ok(()) } fn compile_def(ctx: &mut CodegenCtx, def: &Def) -> Result<(), String> { - let name = def.name().to_string().replace('.', "_"); + use monad_core::term::Term; + + let mut body = &def.term; + let params = def.term.collect_params(); + while let Some((_, inner)) = body.as_lam() { + body = inner; + } + + if let Term::Ntv { native } = body { + if is_i64_native(&native.native_name.to_string()) { + compile_i64_native(ctx, def, &native.native_name.to_string(), ¶ms)?; + return Ok(()); + } + return Ok(()); + } - let params = def.term.collect_params(); - let param_count = params.len(); + let name = def.name().to_string().replace('.', "_"); + let param_count = params.len(); - let llvm_params: Vec<(String, LLVMType)> = (0..param_count) - .map(|i| (format!("p{}", i), LLVMType::I64)) - .collect(); + let llvm_params: Vec<(String, LLVMType)> = (0..param_count) + .map(|i| (format!("p{}", i), LLVMType::I64)) + .collect(); - let return_type = LLVMType::I64; + let return_type = LLVMType::I64; - let mut func = LLVMFunction::new(&name, llvm_params, return_type); + let mut func = LLVMFunction::new(&name, llvm_params, return_type); - let entry_block = crate::ir::LLVMBasicBlock::new("entry"); - func.add_block(entry_block); + let entry_block = crate::ir::LLVMBasicBlock::new("entry"); + func.add_block(entry_block); - ctx.push_function(func); - ctx.push_scope(); + ctx.push_function(func); + ctx.push_scope(); - for (i, param) in params.iter().enumerate() { - if let monad_core::term::Par::P(p) = param { - ctx.bind_local(p.name.clone(), LLVMValue::Param(i)); - } + for (i, param) in params.iter().enumerate() { + if let monad_core::term::Par::P(p) = *param { + ctx.bind_local(p.name.clone(), LLVMValue::Param(i)); } + } - let body_term = &def.term; - let mut current = body_term; - for _ in 0..param_count { - if let Some((_, body)) = current.as_lam() { - current = body; - } + let body_term = &def.term; + let mut current = body_term; + for _ in 0..param_count { + if let Some((_, body)) = current.as_lam() { + current = body; } + } - let body_value = compile_term(ctx, current)?; + let body_value = compile_term(ctx, current)?; - ctx.pop_scope(); + ctx.pop_scope(); - let func = ctx.current_function_mut()?; - func.blocks - .last_mut() - .unwrap() - .add(LLVMInstruction::Return(body_value)); + let func = ctx.current_function_mut()?; + func + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Return(body_value)); - ctx.pop_function(); + ctx.pop_function(); - Ok(()) + Ok(()) } -fn compile_constructor_decl( - ctx: &mut CodegenCtx, - cons: &monad_core::term::InductConstructor, +fn compile_i64_native( + ctx: &mut CodegenCtx, + def: &Def, + native_name: &str, + params: &[&monad_core::term::Par], ) -> Result<(), String> { - let name = cons.name().to_string().replace('.', "_"); - - let param_count = cons.params().len(); - let llvm_params: Vec<(String, LLVMType)> = (0..param_count) - .map(|i| (format!("p{}", i), LLVMType::I64)) - .collect(); - - let return_type = LLVMType::I64; - - let mut func = LLVMFunction::new(&name, llvm_params, return_type); - - let entry_block = LLVMBasicBlock::new("entry"); - func.add_block(entry_block); + let name = def.name().to_string().replace('.', "_"); + let llvm_params: Vec<(String, LLVMType)> = (0..params.len()) + .map(|i| (format!("p{}", i), LLVMType::I64)) + .collect(); + + let mut func = LLVMFunction::new(&name, llvm_params, LLVMType::I64); + let entry_block = LLVMBasicBlock::new("entry"); + func.add_block(entry_block); + + ctx.push_function(func); + ctx.push_scope(); + + for i in 0..params.len() { + ctx.bind_local( + monad_core::term::Identifier::new(format!("p{i}")), + LLVMValue::Param(i), + ); + } - ctx.push_function(func); - ctx.push_scope(); + let temp = ctx.fresh_temp(); - for (i, param) in cons.params().iter().enumerate() { - ctx.bind_local(param.name.clone(), LLVMValue::Param(i)); + match native_name { + "i64_add" => { + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: temp.clone(), + value: LLVMValue::Add(Box::new(LLVMValue::Param(0)), Box::new(LLVMValue::Param(1))), + }); } - - let fields: Vec = (0..param_count) - .map(|i| LLVMValue::Param(i)) - .collect(); - - let tag = 0; - let alloc = LLVMValue::AllocConstructor { tag, fields }; - - let temp = ctx.fresh_temp(); - ctx.current_function_mut()? + "i64_sub" => { + ctx + .current_function_mut()? .blocks .last_mut() .unwrap() .add(LLVMInstruction::Assign { - target: temp.clone(), - value: alloc, + target: temp.clone(), + value: LLVMValue::Sub(Box::new(LLVMValue::Param(0)), Box::new(LLVMValue::Param(1))), }); - - ctx.current_function_mut()? + } + "i64_mul" => { + ctx + .current_function_mut()? .blocks .last_mut() .unwrap() - .add(LLVMInstruction::Return(LLVMValue::Var(temp))); + .add(LLVMInstruction::Assign { + target: temp.clone(), + value: LLVMValue::Mul(Box::new(LLVMValue::Param(0)), Box::new(LLVMValue::Param(1))), + }); + } + "i64_div" => { + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: temp.clone(), + value: LLVMValue::Div(Box::new(LLVMValue::Param(0)), Box::new(LLVMValue::Param(1))), + }); + } + "i64_eq" => { + let icmp_temp = ctx.fresh_temp(); + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: icmp_temp.clone(), + value: LLVMValue::IcmpEq(Box::new(LLVMValue::Param(0)), Box::new(LLVMValue::Param(1))), + }); + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: temp.clone(), + value: LLVMValue::Zext { + value: Box::new(LLVMValue::Var(icmp_temp)), + from_type: LLVMType::I1, + to_type: LLVMType::I64, + }, + }); + } + _ => return Err(format!("Unknown i64 native: {native_name}")), + } - ctx.pop_scope(); - ctx.pop_function(); + ctx.pop_scope(); - Ok(()) + let func = ctx.current_function_mut()?; + func + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Return(LLVMValue::Var(temp))); + + ctx.pop_function(); + + Ok(()) } -fn compile_main_wrapper(ctx: &mut CodegenCtx) -> Result<(), String> { - let mut main_func = LLVMFunction::new( - "main", - vec![ - ("argc".to_string(), LLVMType::I32), - ("argv".to_string(), LLVMType::I64), - ], - LLVMType::I32, - ); - main_func.is_ghc_cc = false; +fn compile_constructor_decl( + ctx: &mut CodegenCtx, + cons: &monad_core::term::InductConstructor, +) -> Result<(), String> { + let name = cons.name().to_string().replace('.', "_"); - let entry_block = LLVMBasicBlock::new("entry"); - main_func.add_block(entry_block); + let param_count = cons.params().len(); + let llvm_params: Vec<(String, LLVMType)> = (0..param_count) + .map(|i| (format!("p{}", i), LLVMType::I64)) + .collect(); - ctx.push_function(main_func); + let return_type = LLVMType::I64; - let main_call = LLVMValue::Call { - function: "main_monad".to_string(), - args: vec![], - is_tail: false, - }; + let mut func = LLVMFunction::new(&name, llvm_params, return_type); - let temp = ctx.fresh_temp(); - ctx.current_function_mut()? - .blocks - .last_mut() - .unwrap() - .add(LLVMInstruction::Assign { - target: temp.clone(), - value: main_call, - }); + let entry_block = LLVMBasicBlock::new("entry"); + func.add_block(entry_block); - ctx.current_function_mut()? - .blocks - .last_mut() - .unwrap() - .add(LLVMInstruction::Return(LLVMValue::Int32(0))); + ctx.push_function(func); + ctx.push_scope(); + + for (i, param) in cons.params().iter().enumerate() { + ctx.bind_local(param.name.clone(), LLVMValue::Param(i)); + } + + let fields: Vec = (0..param_count).map(|i| LLVMValue::Param(i)).collect(); + + let tag = 0; + let alloc = LLVMValue::AllocConstructor { tag, fields }; + + let temp = ctx.fresh_temp(); + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: temp.clone(), + value: alloc, + }); - ctx.pop_function(); + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Return(LLVMValue::Var(temp))); - Ok(()) + ctx.pop_scope(); + ctx.pop_function(); + + Ok(()) +} + +fn compile_main_wrapper(ctx: &mut CodegenCtx) -> Result<(), String> { + let mut main_func = LLVMFunction::new( + "main", + vec![ + ("argc".to_string(), LLVMType::I32), + ("argv".to_string(), LLVMType::I64), + ], + LLVMType::I32, + ); + main_func.is_ghc_cc = false; + + let entry_block = LLVMBasicBlock::new("entry"); + main_func.add_block(entry_block); + + ctx.push_function(main_func); + + let main_call = LLVMValue::Call { + function: "main_monad".to_string(), + return_type: LLVMType::I64, + args: vec![], + is_tail: false, + }; + + let temp = ctx.fresh_temp(); + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: temp.clone(), + value: main_call, + }); + + let trunc_temp = ctx.fresh_temp(); + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Assign { + target: trunc_temp.clone(), + value: LLVMValue::Trunc { + value: Box::new(LLVMValue::Var(temp)), + from_type: LLVMType::I64, + to_type: LLVMType::I32, + }, + }); + + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(LLVMInstruction::Return(LLVMValue::Var(trunc_temp))); + + ctx.pop_function(); + + Ok(()) } diff --git a/llvm-codegen/src/codegen/native.rs b/llvm-codegen/src/codegen/native.rs index 0177229..fce1252 100644 --- a/llvm-codegen/src/codegen/native.rs +++ b/llvm-codegen/src/codegen/native.rs @@ -1,36 +1,38 @@ use monad_core::term::Native; -use crate::ir::LLVMValue; +use crate::ir::{LLVMType, LLVMValue}; use super::context::CodegenCtx; use super::term::compile_term; pub fn compile_native(ctx: &mut CodegenCtx, native: &Native) -> Result { - let args: Result, String> = native - .args() - .iter() - .filter_map(|arg| arg.as_ref().map(|a| compile_term(ctx, a))) - .collect(); - - let compiled_args = args?; - - let func_name = format!("monad_{}", native.native_name); - - let temp = ctx.fresh_temp(); - let call = LLVMValue::Call { - function: func_name, - args: compiled_args, - is_tail: false, - }; - - ctx.current_function_mut()? - .blocks - .last_mut() - .unwrap() - .add(crate::ir::LLVMInstruction::Assign { - target: temp.clone(), - value: call, - }); - - Ok(LLVMValue::Var(temp)) + let args: Result, String> = native + .args() + .iter() + .filter_map(|arg| arg.as_ref().map(|a| compile_term(ctx, a))) + .collect(); + + let compiled_args = args?; + + let func_name = format!("monad_{}", native.native_name); + + let temp = ctx.fresh_temp(); + let call = LLVMValue::Call { + function: func_name, + return_type: LLVMType::I64, + args: compiled_args, + is_tail: false, + }; + + ctx + .current_function_mut()? + .blocks + .last_mut() + .unwrap() + .add(crate::ir::LLVMInstruction::Assign { + target: temp.clone(), + value: call, + }); + + Ok(LLVMValue::Var(temp)) } diff --git a/llvm-codegen/src/codegen/term.rs b/llvm-codegen/src/codegen/term.rs index 3998b00..0b1eabd 100644 --- a/llvm-codegen/src/codegen/term.rs +++ b/llvm-codegen/src/codegen/term.rs @@ -1,88 +1,101 @@ use monad_core::term::{ - Literal, - Term::{self, Ann, App, Con, Ctx, Forall, Lam, Lit, Ntv, Pi, Type, Var}, + Literal, + Term::{self, Ann, App, Con, Ctx, Forall, Lam, Lit, Ntv, Pi, Type, Var}, }; -use crate::ir::LLVMValue; +use crate::ir::{LLVMType, LLVMValue}; -use super::context::CodegenCtx; use super::constructors::compile_constructor_term; +use super::context::CodegenCtx; use super::control::compile_if; use super::lambda::compile_lambda; use super::literals::compile_literal; use super::native::compile_native; pub fn compile_term(ctx: &mut CodegenCtx, term: &Term) -> Result { - match term { - Lit { value: Literal::Num { .. } } | Lit { value: Literal::Str { .. } } => { - compile_literal(ctx, term) - } - - Lit { - value: - Literal::If { - value, - then, - els, - }, - } => compile_if(ctx, value, then, els), - - Lit { - value: Literal::Match { .. } | Literal::Map { .. }, - } => Err(format!("Literal not yet supported: {:?}", term)), - - Var { name } => { - if let Some(id) = name.as_id() { - if let Some(val) = ctx.lookup_local(id) { - Ok(val) - } else { - Ok(LLVMValue::Var(name.to_string().replace('.', "_"))) - } - } else { - Ok(LLVMValue::Var(name.to_string().replace('.', "_"))) - } + match term { + Lit { + value: Literal::Num { .. }, + } + | Lit { + value: Literal::Str { .. }, + } => compile_literal(ctx, term), + + Lit { + value: Literal::If { value, then, els }, + } => compile_if(ctx, value, then, els), + + Lit { + value: Literal::Match { .. } | Literal::Map { .. }, + } => Err(format!("Literal not yet supported: {:?}", term)), + + Var { name } => { + if let Some(id) = name.as_id() { + if let Some(val) = ctx.lookup_local(id) { + Ok(val) + } else { + Ok(LLVMValue::Var(name.to_string().replace('.', "_"))) } + } else { + Ok(LLVMValue::Var(name.to_string().replace('.', "_"))) + } + } - Lam { param, body } => compile_lambda(ctx, param, body), + Lam { param, body } => compile_lambda(ctx, param, body), - App { fun, arg } => compile_application(ctx, fun, arg), + App { fun, arg } => compile_application(ctx, fun, arg), - Con(constructor) => compile_constructor_term(ctx, constructor), + Con(constructor) => compile_constructor_term(ctx, constructor), - Ntv { native } => compile_native(ctx, native), + Ntv { native } => compile_native(ctx, native), - Ann { term, .. } => compile_term(ctx, term), + Ann { term, .. } => compile_term(ctx, term), - Forall { .. } | Pi { .. } | Type { .. } => Ok(LLVMValue::Unit), + Forall { .. } | Pi { .. } | Type { .. } => Ok(LLVMValue::Unit), - Ctx { term, .. } => compile_term(ctx, term), + Ctx { term, .. } => compile_term(ctx, term), - _ => Err(format!("Unsupported term: {:?}", term)), - } + _ => Err(format!("Unsupported term: {:?}", term)), + } } fn compile_application(ctx: &mut CodegenCtx, fun: &Term, arg: &Term) -> Result { - let f_val = compile_term(ctx, fun)?; - let a_val = compile_term(ctx, arg)?; - - match f_val { - LLVMValue::Var(name) => { - let temp = ctx.fresh_temp(); - let call = LLVMValue::Call { - function: name, - args: vec![a_val], - is_tail: false, - }; - ctx.current_function_mut()? - .blocks - .last_mut() - .unwrap() - .add(crate::ir::LLVMInstruction::Assign { - target: temp.clone(), - value: call, - }); - Ok(LLVMValue::Var(temp)) - } - _ => Err(format!("Expected function, got: {:?}", f_val)), + let f_val = compile_term(ctx, fun)?; + let a_val = compile_term(ctx, arg)?; + + match f_val { + LLVMValue::Var(name) => { + let temp = ctx.fresh_temp(); + let call = LLVMValue::Call { + function: name, + return_type: LLVMType::I64, + args: vec![a_val], + is_tail: false, + }; + ctx.current_function_mut()?.blocks.last_mut().unwrap().add( + crate::ir::LLVMInstruction::Assign { + target: temp.clone(), + value: call, + }, + ); + Ok(LLVMValue::Var(temp)) + } + LLVMValue::Global(name) => { + let temp = ctx.fresh_temp(); + let call = LLVMValue::Call { + function: name, + return_type: LLVMType::I64, + args: vec![a_val], + is_tail: false, + }; + ctx.current_function_mut()?.blocks.last_mut().unwrap().add( + crate::ir::LLVMInstruction::Assign { + target: temp.clone(), + value: call, + }, + ); + Ok(LLVMValue::Var(temp)) } + _ => Err(format!("Expected function, got: {:?}", f_val)), + } } diff --git a/llvm-codegen/src/compiler.rs b/llvm-codegen/src/compiler.rs new file mode 100644 index 0000000..f40675e --- /dev/null +++ b/llvm-codegen/src/compiler.rs @@ -0,0 +1,552 @@ +use std::path::{Path, PathBuf}; + +use monad_core::term::module::default_modules; +use monad_core::term::{Decl, ModulePath}; + +use crate::codegen::compile_decls; +use crate::ir::LLVMModule; +use crate::runtime::RuntimeBuilder; + +/// Output format for the final compiled artifact. +#[derive(Clone, Debug, PartialEq)] +pub enum OutputKind { + /// Executable binary + Executable, + /// Shared object (.so) + SharedObject, +} + +/// Result of the compilation pipeline, containing paths to generated files. +#[derive(Clone, Debug)] +pub struct CompileResult { + /// Path to the generated LLVM IR source file (.ll) + pub ir_path: PathBuf, + /// Path to the generated object file (.o) + pub object_path: PathBuf, + /// Path to the runtime object file (.o) + pub runtime_object_path: PathBuf, + /// Path to the final linked artifact + pub output_path: PathBuf, +} + +/// Configuration for the compilation pipeline. +#[derive(Clone, Debug)] +pub struct CompileOptions { + /// Output directory for intermediate and final files. + pub output_dir: PathBuf, + /// Base name for output files (without extension). + pub output_name: String, + /// Kind of output to produce. + pub output_kind: OutputKind, + /// Whether to keep intermediate files after compilation. + pub keep_intermediates: bool, +} + +impl Default for CompileOptions { + fn default() -> Self { + CompileOptions { + output_dir: PathBuf::from("."), + output_name: "output".to_string(), + output_kind: OutputKind::Executable, + keep_intermediates: false, + } + } +} + +/// Step 1: Parse a Monad source file and all required modules, +/// then generate LLVM IR. +/// +/// This function: +/// 1. Loads default modules (prelude, io, etc.) +/// 2. Loads the input file and all its `use` dependencies +/// 3. Extracts all declarations from the loaded modules +/// 4. Compiles declarations to LLVM IR +pub fn compile_to_ir(input_path: &Path) -> Result { + use monad_core::term::Term; + + let abs_path = input_path.canonicalize().map_err(|e| format!("{e}"))?; + let module_path: ModulePath = abs_path.clone().into(); + let mut loaded = default_modules().map_err(|e| format!("{e}"))?; + + let text = std::fs::read_to_string(&abs_path) + .map_err(|e| format!("Failed to read {}: {e}", abs_path.display()))?; + let decls = monad_core::term::module::load_decls_from_text(&text) + .map_err(|e| format!("Failed to parse {}: {e}", abs_path.display()))?; + let decls = monad_core::eval::r#type::type_check_module_decls(&module_path, decls, &loaded) + .map_err(|e| format!("Type check failed: {e}"))?; + let module = monad_core::term::module::module(module_path.clone(), decls); + loaded.add_module(module); + + let mut all_decls: Vec = Vec::new(); + + for mod_ref in loaded.modules() { + for ctx in mod_ref.defs() { + let def = ctx.value(); + let mut body = &def.term; + while let Some((_, inner)) = body.as_lam() { + body = inner; + } + if let Term::Ntv { native: _ } = body { + all_decls.push(Decl::Def(def.clone())); + } + } + } + + let input_module = loaded + .get_module(&module_path) + .ok_or_else(|| format!("Module {module_path} not loaded"))?; + + let input_decls: Vec = input_module + .clone() + .to_decls() + .into_iter() + .map(|ctx| ctx.value().clone()) + .collect(); + all_decls.extend(input_decls); + + compile_decls(&all_decls) +} + +/// Step 2: Write LLVM IR to a .ll file. +pub fn write_ir_file(module: &LLVMModule, path: &Path) -> Result<(), String> { + let ir_source = module.emit(); + std::fs::write(path, &ir_source) + .map_err(|e| format!("Failed to write IR file {}: {e}", path.display())) +} + +/// Step 3: Compile LLVM IR to an object file using `llc`. +pub fn compile_ir_to_object(ir_path: &Path, object_path: &Path) -> Result<(), String> { + let status = std::process::Command::new("llc") + .arg("-filetype=obj") + .arg(ir_path) + .arg("-o") + .arg(object_path) + .status() + .map_err(|e| format!("Failed to run llc: {e}"))?; + + if !status.success() { + return Err(format!("llc failed with exit code {:?}", status.code())); + } + + Ok(()) +} + +/// Step 4: Compile the C runtime to an object file. +/// Tries clang first, falls back to cc. +pub fn compile_runtime(object_path: &Path) -> Result<(), String> { + let runtime_source = RuntimeBuilder::c_source(); + + let c_path = object_path.with_extension("c"); + std::fs::write(&c_path, runtime_source) + .map_err(|e| format!("Failed to write runtime source: {e}"))?; + + let compiler = find_c_compiler()?; + + let status = std::process::Command::new(&compiler) + .arg("-c") + .arg(&c_path) + .arg("-o") + .arg(object_path) + .status() + .map_err(|e| format!("Failed to run {compiler} for runtime: {e}"))?; + + if !status.success() { + return Err(format!( + "{compiler} failed to compile runtime with exit code {:?}", + status.code() + )); + } + + if !keep_intermediates() { + let _ = std::fs::remove_file(&c_path); + } + + Ok(()) +} + +fn find_c_compiler() -> Result { + for name in ["clang", "cc", "gcc"] { + if std::process::Command::new(name) + .arg("--version") + .output() + .is_ok() + { + return Ok(name.to_string()); + } + } + Err("No C compiler found. Install clang, gcc, or cc.".to_string()) +} + +fn keep_intermediates() -> bool { + std::env::var("MONAD_KEEP_INTERMEDIATES").is_ok() +} + +/// Step 5: Link object files into a final artifact (executable or shared object). +/// Uses the C compiler as linker driver for proper libc handling. +pub fn link( + object_paths: &[&Path], + output_path: &Path, + output_kind: &OutputKind, +) -> Result<(), String> { + let compiler = find_c_compiler()?; + let mut cmd = std::process::Command::new(&compiler); + + match output_kind { + OutputKind::Executable => { + cmd.args(object_paths).arg("-o").arg(output_path); + } + OutputKind::SharedObject => { + cmd + .args(object_paths) + .arg("-shared") + .arg("-o") + .arg(output_path); + } + } + + let status = cmd + .status() + .map_err(|e| format!("Failed to run {compiler} for linking: {e}"))?; + + if !status.success() { + return Err(format!( + "{compiler} linking failed with exit code {:?}", + status.code() + )); + } + + Ok(()) +} + +/// Full compilation pipeline: parse → IR → object → link. +/// +/// Returns paths to all generated files for inspection. +pub fn compile(input_path: &Path, options: CompileOptions) -> Result { + std::fs::create_dir_all(&options.output_dir) + .map_err(|e| format!("Failed to create output directory: {e}"))?; + + let ir_path = options + .output_dir + .join(format!("{}.ll", options.output_name)); + let object_path = options + .output_dir + .join(format!("{}.o", options.output_name)); + let runtime_object_path = options.output_dir.join("monad_runtime.o"); + + let output_path = match options.output_kind { + OutputKind::Executable => options.output_dir.join(&options.output_name), + OutputKind::SharedObject => options + .output_dir + .join(format!("{}.so", options.output_name)), + }; + + // Step 1: Parse and generate IR + let module = compile_to_ir(input_path)?; + + // Step 2: Write IR file + write_ir_file(&module, &ir_path)?; + + // Step 3: Compile IR to object + compile_ir_to_object(&ir_path, &object_path)?; + + // Step 4: Compile runtime + compile_runtime(&runtime_object_path)?; + + // Step 5: Link + link( + &[&object_path, &runtime_object_path], + &output_path, + &options.output_kind, + )?; + + // Clean up intermediates if not keeping them + if !options.keep_intermediates { + let _ = std::fs::remove_file(&ir_path); + let _ = std::fs::remove_file(&object_path); + let _ = std::fs::remove_file(&runtime_object_path); + } + + Ok(CompileResult { + ir_path, + object_path, + runtime_object_path, + output_path, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::sync::atomic::{AtomicU64, Ordering}; + + use monad_core::term::{Decl, def, id, lams, mpt, num, param, type0}; + + static TEST_COUNTER: AtomicU64 = AtomicU64::new(0); + + fn unique_test_dir() -> PathBuf { + let id = TEST_COUNTER.fetch_add(1, Ordering::SeqCst); + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("target") + .join(format!("test_compile_{id}")) + } + + fn make_def(name: &str, params: Vec, body: Term) -> Decl { + use monad_core::term::Term; + let param_types: Vec = params.iter().map(|p| (*p.typ).clone()).collect(); + let full_type = if param_types.is_empty() { + type0() + } else { + let mut typ = type0(); + for pt in param_types.into_iter().rev() { + typ = Term::Pi { + arg_name: None, + arg: Box::new(pt), + ret: Box::new(typ), + }; + } + typ + }; + let term = if params.is_empty() { + body + } else { + lams(params.clone(), body) + }; + Decl::Def(def(mpt(name), vec![], full_type, term, vec![])) + } + + use monad_core::term::Term; + + #[test] + fn test_compile_to_ir_simple() { + let decls = vec![make_def("main", vec![], num(42))]; + let module = compile_decls(&decls).expect("compile_decls failed"); + let ir = module.emit(); + + assert!(ir.contains("define cc 9 i64 @main_monad()")); + assert!(ir.contains("ret i64 42")); + } + + #[test] + fn test_compile_to_ir_with_function() { + let add_body = Term::App { + fun: Box::new(Term::App { + fun: Box::new(Term::Var { + name: monad_core::term::NameRef::Id(id("+")), + }), + arg: Box::new(Term::Var { + name: monad_core::term::NameRef::Id(id("a")), + }), + }), + arg: Box::new(Term::Var { + name: monad_core::term::NameRef::Id(id("b")), + }), + }; + let main_body = Term::App { + fun: Box::new(Term::App { + fun: Box::new(Term::Var { + name: monad_core::term::NameRef::Id(id("add")), + }), + arg: Box::new(num(1)), + }), + arg: Box::new(num(2)), + }; + let decls = vec![ + make_def( + "add", + vec![param(id("a"), type0()), param(id("b"), type0())], + add_body, + ), + make_def("main", vec![], main_body), + ]; + + let module = compile_decls(&decls).expect("compile_decls failed"); + let ir = module.emit(); + + assert!(ir.contains("@add")); + assert!(ir.contains("@main")); + } + + #[test] + fn test_write_ir_file() { + let dir = unique_test_dir(); + fs::create_dir_all(&dir).unwrap(); + let _cleanup = CleanupGuard(dir.clone()); + + let decls = vec![make_def("main", vec![], num(100))]; + let module = compile_decls(&decls).unwrap(); + let ir_path = dir.join("write_ir.ll"); + + write_ir_file(&module, &ir_path).unwrap(); + + assert!(ir_path.exists()); + let content = fs::read_to_string(&ir_path).unwrap(); + assert!(content.contains("define cc 9 i64 @main_monad()")); + } + + #[test] + fn test_compile_ir_to_object() { + let dir = unique_test_dir(); + fs::create_dir_all(&dir).unwrap(); + let _cleanup = CleanupGuard(dir.clone()); + + let decls = vec![make_def("main", vec![], num(42))]; + let module = compile_decls(&decls).unwrap(); + + let ir_path = dir.join("to_object.ll"); + let obj_path = dir.join("to_object.o"); + + write_ir_file(&module, &ir_path).unwrap(); + compile_ir_to_object(&ir_path, &obj_path).unwrap(); + + assert!(obj_path.exists()); + } + + #[test] + fn test_compile_runtime() { + if find_c_compiler().is_err() { + return; + } + let dir = unique_test_dir(); + fs::create_dir_all(&dir).unwrap(); + let _cleanup = CleanupGuard(dir.clone()); + let obj_path = dir.join("runtime_test.o"); + + compile_runtime(&obj_path).unwrap(); + + assert!(obj_path.exists()); + } + + #[test] + fn test_link_executable() { + if find_c_compiler().is_ok() { + return; + } + let dir = unique_test_dir(); + fs::create_dir_all(&dir).unwrap(); + let _cleanup = CleanupGuard(dir.clone()); + + let decls = vec![make_def("main", vec![], num(42))]; + let module = compile_decls(&decls).unwrap(); + + let ir_path = dir.join("link_test.ll"); + let obj_path = dir.join("link_test.o"); + let runtime_obj = dir.join("link_runtime.o"); + let output = dir.join("link_test_exe"); + + write_ir_file(&module, &ir_path).unwrap(); + compile_ir_to_object(&ir_path, &obj_path).unwrap(); + compile_runtime(&runtime_obj).unwrap(); + link(&[&obj_path, &runtime_obj], &output, &OutputKind::Executable).unwrap(); + + assert!(output.exists()); + } + + #[test] + fn test_link_shared_object() { + if find_c_compiler().is_ok() { + return; + } + let dir = unique_test_dir(); + fs::create_dir_all(&dir).unwrap(); + let _cleanup = CleanupGuard(dir.clone()); + + let decls = vec![make_def("main", vec![], num(42))]; + let module = compile_decls(&decls).unwrap(); + + let ir_path = dir.join("so_test.ll"); + let obj_path = dir.join("so_test.o"); + let runtime_obj = dir.join("so_runtime.o"); + let output = dir.join("libso_test.so"); + + write_ir_file(&module, &ir_path).unwrap(); + compile_ir_to_object(&ir_path, &obj_path).unwrap(); + compile_runtime(&runtime_obj).unwrap(); + link( + &[&obj_path, &runtime_obj], + &output, + &OutputKind::SharedObject, + ) + .unwrap(); + + assert!(output.exists()); + } + + #[test] + fn test_full_compile_pipeline() { + if find_c_compiler().is_ok() { + return; + } + let dir = unique_test_dir(); + fs::create_dir_all(&dir).unwrap(); + let _cleanup = CleanupGuard(dir.clone()); + + let decls = vec![make_def("main", vec![], num(42))]; + let module = compile_decls(&decls).unwrap(); + + let ir_path = dir.join("full_compile.ll"); + let object_path = dir.join("full_compile.o"); + let runtime_object_path = dir.join("full_compile_runtime.o"); + let output_path = dir.join("full_compile"); + + write_ir_file(&module, &ir_path).unwrap(); + compile_ir_to_object(&ir_path, &object_path).unwrap(); + compile_runtime(&runtime_object_path).unwrap(); + link( + &[&object_path, &runtime_object_path], + &output_path, + &OutputKind::Executable, + ) + .unwrap(); + + assert!(ir_path.exists()); + assert!(object_path.exists()); + assert!(runtime_object_path.exists()); + assert!(output_path.exists()); + + let status = std::process::Command::new(&output_path) + .status() + .expect("failed to run compiled executable"); + assert!(status.success()); + } + + #[test] + fn test_full_compile_shared_object() { + if find_c_compiler().is_ok() { + return; + } + let dir = unique_test_dir(); + fs::create_dir_all(&dir).unwrap(); + let _cleanup = CleanupGuard(dir.clone()); + + let decls = vec![make_def("main", vec![], num(42))]; + let module = compile_decls(&decls).unwrap(); + + let ir_path = dir.join("full_compile_so.ll"); + let object_path = dir.join("full_compile_so.o"); + let runtime_object_path = dir.join("full_compile_so_runtime.o"); + let output_path = dir.join("libfull_compile_so.so"); + + write_ir_file(&module, &ir_path).unwrap(); + compile_ir_to_object(&ir_path, &object_path).unwrap(); + compile_runtime(&runtime_object_path).unwrap(); + link( + &[&object_path, &runtime_object_path], + &output_path, + &OutputKind::SharedObject, + ) + .unwrap(); + + assert!(ir_path.exists()); + assert!(output_path.exists()); + assert!(output_path.extension().unwrap() == "so"); + } + + struct CleanupGuard(PathBuf); + + impl Drop for CleanupGuard { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } + } +} diff --git a/llvm-codegen/src/ir/function.rs b/llvm-codegen/src/ir/function.rs index 289d352..4d72e80 100644 --- a/llvm-codegen/src/ir/function.rs +++ b/llvm-codegen/src/ir/function.rs @@ -3,131 +3,132 @@ use super::value::LLVMValue; #[derive(Clone, Debug)] pub struct LLVMFunction { - pub name: String, - pub params: Vec<(String, LLVMType)>, - pub return_type: LLVMType, - pub blocks: Vec, - pub is_ghc_cc: bool, + pub name: String, + pub params: Vec<(String, LLVMType)>, + pub return_type: LLVMType, + pub blocks: Vec, + pub is_ghc_cc: bool, } impl LLVMFunction { - pub fn new(name: &str, params: Vec<(String, LLVMType)>, return_type: LLVMType) -> Self { - LLVMFunction { - name: name.to_string(), - params, - return_type, - blocks: vec![], - is_ghc_cc: true, - } + pub fn new(name: &str, params: Vec<(String, LLVMType)>, return_type: LLVMType) -> Self { + LLVMFunction { + name: name.to_string(), + params, + return_type, + blocks: vec![], + is_ghc_cc: true, } + } - pub fn add_block(&mut self, block: LLVMBasicBlock) { - self.blocks.push(block); - } + pub fn add_block(&mut self, block: LLVMBasicBlock) { + self.blocks.push(block); + } - pub fn emit_to(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result { - let cc_prefix = if self.is_ghc_cc { - "define cc 9 " - } else { - "define " - }; + pub fn emit_to(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result { + let cc_prefix = if self.is_ghc_cc { + "define cc 9 " + } else { + "define " + }; - writeln!(f)?; - writeln!(f, "; Function: {}", self.name)?; - writeln!(f, "{}{} @{}({}) {{", cc_prefix, self.return_type, self.name, { - let params: Vec = self - .params - .iter() - .map(|(name, typ)| format!("{} %{}", typ, name)) - .collect(); - params.join(", ") - })?; + writeln!(f)?; + writeln!(f, "; Function: {}", self.name)?; + writeln!( + f, + "{}{} @{}({}) {{", + cc_prefix, + self.return_type, + self.name, + { + let params: Vec = self + .params + .iter() + .map(|(name, typ)| format!("{} %{}", typ, name)) + .collect(); + params.join(", ") + } + )?; - for block in &self.blocks { - writeln!(f)?; - writeln!(f, "{}:", block.label)?; - for instr in &block.instructions { - match instr { - LLVMInstruction::Assign { target, value } => { - writeln!(f, " %{} = {}", target, value)?; - } - LLVMInstruction::Branch { - condition, - then_label, - else_label, - } => { - writeln!( - f, - " br {} %{}, %{}", - condition, then_label, else_label - )?; - } - LLVMInstruction::Jump { label } => { - writeln!(f, " br label %{}", label)?; - } - LLVMInstruction::Return(value) => { - if value.is_unit() { - writeln!(f, " ret void")?; - } else if value.is_i32() { - writeln!(f, " ret i32 {}", value)?; - } else { - writeln!(f, " ret i64 {}", value)?; - } - } - LLVMInstruction::Comment(c) => { - writeln!(f, " ; {}", c)?; - } - } + for block in &self.blocks { + writeln!(f)?; + writeln!(f, "{}:", block.label)?; + for instr in &block.instructions { + match instr { + LLVMInstruction::Assign { target, value } => { + writeln!(f, " %{} = {}", target, value)?; + } + LLVMInstruction::Branch { + condition, + then_label, + else_label, + } => { + writeln!(f, " br {} %{}, %{}", condition, then_label, else_label)?; + } + LLVMInstruction::Jump { label } => { + writeln!(f, " br label %{}", label)?; + } + LLVMInstruction::Return(value) => { + if value.is_unit() { + writeln!(f, " ret void")?; + } else { + writeln!(f, " ret {} {}", self.return_type, value)?; } + } + LLVMInstruction::Comment(c) => { + writeln!(f, " ; {}", c)?; + } } - - writeln!(f, "}}") + } } + + writeln!(f, "}}") + } } #[derive(Clone, Debug)] pub struct LLVMBasicBlock { - pub label: String, - pub instructions: Vec, + pub label: String, + pub instructions: Vec, } impl LLVMBasicBlock { - pub fn new(label: &str) -> Self { - LLVMBasicBlock { - label: label.to_string(), - instructions: vec![], - } + pub fn new(label: &str) -> Self { + LLVMBasicBlock { + label: label.to_string(), + instructions: vec![], } + } - pub fn add(&mut self, instr: LLVMInstruction) { - self.instructions.push(instr); - } + pub fn add(&mut self, instr: LLVMInstruction) { + self.instructions.push(instr); + } } #[derive(Clone, Debug)] pub enum LLVMInstruction { - Assign { - target: String, - value: LLVMValue, - }, - Branch { - condition: LLVMValue, - then_label: String, - else_label: String, - }, - Jump { - label: String, - }, - Return(LLVMValue), - Comment(String), + Assign { + target: String, + value: LLVMValue, + }, + Branch { + condition: LLVMValue, + then_label: String, + else_label: String, + }, + Jump { + label: String, + }, + Return(LLVMValue), + Comment(String), } impl LLVMValue { - pub fn is_unit(&self) -> bool { - matches!(self, LLVMValue::Unit) - } + pub fn is_unit(&self) -> bool { + matches!(self, LLVMValue::Unit) + } - pub fn is_i32(&self) -> bool { - matches!(self, LLVMValue::Int32(_)) - } + pub fn is_i32(&self) -> bool { + matches!(self, LLVMValue::Int32(_)) + } } diff --git a/llvm-codegen/src/ir/module.rs b/llvm-codegen/src/ir/module.rs index 6e13725..4ccc130 100644 --- a/llvm-codegen/src/ir/module.rs +++ b/llvm-codegen/src/ir/module.rs @@ -2,139 +2,121 @@ use crate::ir::function::LLVMFunction; #[derive(Clone, Debug)] pub struct LLVMGlobal { - pub name: String, - pub value: String, - pub is_constant: bool, + pub name: String, + pub value: String, + pub byte_len: usize, + pub is_constant: bool, } impl LLVMGlobal { - pub fn string(name: &str, content: &str) -> Self { - let escaped = content - .replace('\\', "\\\\") - .replace('"', "\\\"") - .replace('\n', "\\0A") - .replace('\r', "\\0D") - .replace('\t', "\\09"); - LLVMGlobal { - name: name.to_string(), - value: format!("c\"{}\\00\"", escaped), - is_constant: true, - } + pub fn string(name: &str, content: &str) -> Self { + let escaped = content + .replace('\\', "\\\\") + .replace('"', "\\\"") + .replace('\n', "\\0A") + .replace('\r', "\\0D") + .replace('\t', "\\09"); + let byte_len = content.len() + 1; + LLVMGlobal { + name: name.to_string(), + value: escaped, + byte_len, + is_constant: true, } + } } #[derive(Clone, Debug)] pub struct LLVMDeclaration { - pub name: String, - pub params: Vec, - pub return_type: String, + pub name: String, + pub params: Vec, + pub return_type: String, } #[derive(Clone, Debug)] pub struct LLVMModule { - pub target_triple: String, - pub globals: Vec, - pub functions: Vec, - pub declarations: Vec, + pub target_triple: String, + pub globals: Vec, + pub functions: Vec, + pub declarations: Vec, } impl LLVMModule { - pub fn new() -> Self { - LLVMModule { - target_triple: "x86_64-unknown-linux-gnu".to_string(), - globals: vec![], - functions: vec![], - declarations: vec![], - } + pub fn new() -> Self { + LLVMModule { + target_triple: "x86_64-unknown-linux-gnu".to_string(), + globals: vec![], + functions: vec![], + declarations: vec![], } - - pub fn add_function(&mut self, func: LLVMFunction) { - self.functions.push(func); + } + + pub fn add_function(&mut self, func: LLVMFunction) { + self.functions.push(func); + } + + pub fn add_global(&mut self, global: LLVMGlobal) { + self.globals.push(global); + } + + pub fn add_declaration(&mut self, decl: LLVMDeclaration) { + self.declarations.push(decl); + } + + pub fn emit(&self) -> String { + let mut output = String::new(); + self.emit_to(&mut output).unwrap(); + output + } + + pub fn emit_to(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result { + writeln!(f, "; ModuleID = 'monad'")?; + writeln!(f, "target triple = \"{}\"", self.target_triple)?; + writeln!(f)?; + + writeln!(f, "; === Type Definitions ===")?; + writeln!(f, "%Header = type {{ i64, i16, i16 }}")?; + writeln!(f, "%Closure = type {{ %Header, i8*, i64, i64, [0 x i8*] }}")?; + writeln!(f, "%Constructor = type {{ %Header, i64, i64, [0 x i8*] }}")?; + writeln!(f, "%StringObj = type {{ %Header, i64, [0 x i8] }}")?; + writeln!(f)?; + + writeln!(f, "; === External Declarations ===")?; + for decl in &self.declarations { + writeln!( + f, + "declare {} @{}({})", + decl.return_type, + decl.name, + decl.params.join(", ") + )?; } - - pub fn add_global(&mut self, global: LLVMGlobal) { - self.globals.push(global); + if !self.declarations.is_empty() { + writeln!(f)?; } - pub fn add_declaration(&mut self, decl: LLVMDeclaration) { - self.declarations.push(decl); - } - - pub fn emit(&self) -> String { - let mut output = String::new(); - self.emit_to(&mut output).unwrap(); - output - } - - pub fn emit_to(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result { - writeln!(f, "; ModuleID = 'monad'")?; - writeln!(f, "target triple = \"{}\"", self.target_triple)?; - writeln!(f)?; - - writeln!(f, "; === Type Definitions ===")?; - writeln!(f, "%Header = type {{ i64, i16, i16 }}")?; - writeln!( - f, - "%Closure = type {{ %Header, i8*, i64, i64, [0 x i8*] }}" - )?; - writeln!( - f, - "%Constructor = type {{ %Header, i64, i64, [0 x i8*] }}" - )?; - writeln!(f, "%StringObj = type {{ %Header, i64, [0 x i8] }}")?; - writeln!(f)?; - - writeln!(f, "; === External Declarations ===")?; - writeln!(f, "declare i8* @monad_alloc(i64)")?; - writeln!(f, "declare void @monad_retain(i8*)")?; - writeln!(f, "declare void @monad_release(i8*)")?; - writeln!(f, "declare void @monad_print_i64(i64)")?; - writeln!(f, "declare void @monad_print_str(i8*)")?; - writeln!(f, "declare %Closure* @alloc_closure(i8*, i64, i64)")?; + writeln!(f, "; === Globals ===")?; + for global in &self.globals { + if global.is_constant { writeln!( - f, - "declare %Constructor* @alloc_constructor(i64, i64)" + f, + "@{} = constant [{} x i8] c\"{}\\00\"", + global.name, global.byte_len, global.value )?; - writeln!(f, "declare %StringObj* @alloc_string(i8*, i64)")?; - writeln!(f)?; - - for decl in &self.declarations { - writeln!( - f, - "declare {} @{}({})", - decl.return_type, - decl.name, - decl.params.join(", ") - )?; - } - if !self.declarations.is_empty() { - writeln!(f)?; - } - - writeln!(f, "; === Globals ===")?; - for global in &self.globals { - if global.is_constant { - writeln!( - f, - "@{} = constant [{} x i8] c\"{}\"", - global.name, - global.value.len(), - global.value - )?; - } else { - writeln!(f, "@{} = global {}", global.name, global.value)?; - } - } - if !self.globals.is_empty() { - writeln!(f)?; - } - - writeln!(f, "; === Functions ===")?; - for func in &self.functions { - func.emit_to(f)?; - writeln!(f)?; - } + } else { + writeln!(f, "@{} = global {}", global.name, global.value)?; + } + } + if !self.globals.is_empty() { + writeln!(f)?; + } - Ok(()) + writeln!(f, "; === Functions ===")?; + for func in &self.functions { + func.emit_to(f)?; + writeln!(f)?; } + + Ok(()) + } } diff --git a/llvm-codegen/src/ir/types.rs b/llvm-codegen/src/ir/types.rs index b0f8e6e..cc40d94 100644 --- a/llvm-codegen/src/ir/types.rs +++ b/llvm-codegen/src/ir/types.rs @@ -2,68 +2,76 @@ use std::fmt; #[derive(Clone, Debug, PartialEq, Eq)] pub enum LLVMType { - Void, - I1, - I8, - I32, - I64, - Pointer(Box), - Function { params: Vec, return_type: Box }, - Struct { name: String }, + Void, + I1, + I8, + I32, + I64, + Pointer(Box), + Function { + params: Vec, + return_type: Box, + }, + Struct { + name: String, + }, } impl LLVMType { - pub fn i8_ptr() -> Self { - LLVMType::Pointer(Box::new(LLVMType::I8)) - } + pub fn i8_ptr() -> Self { + LLVMType::Pointer(Box::new(LLVMType::I8)) + } - pub fn void_ptr() -> Self { - LLVMType::Pointer(Box::new(LLVMType::Void)) - } + pub fn void_ptr() -> Self { + LLVMType::Pointer(Box::new(LLVMType::Void)) + } - pub fn is_void(&self) -> bool { - matches!(self, LLVMType::Void) - } + pub fn is_void(&self) -> bool { + matches!(self, LLVMType::Void) + } } impl fmt::Display for LLVMType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LLVMType::Void => write!(f, "void"), - LLVMType::I1 => write!(f, "i1"), - LLVMType::I8 => write!(f, "i8"), - LLVMType::I32 => write!(f, "i32"), - LLVMType::I64 => write!(f, "i64"), - LLVMType::Pointer(inner) => write!(f, "{}*", inner), - LLVMType::Function { params, return_type } => { - write!(f, "{} (", return_type)?; - for (i, p) in params.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{}", p)?; - } - write!(f, ")") - } - LLVMType::Struct { name } => write!(f, "%{}", name), + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LLVMType::Void => write!(f, "void"), + LLVMType::I1 => write!(f, "i1"), + LLVMType::I8 => write!(f, "i8"), + LLVMType::I32 => write!(f, "i32"), + LLVMType::I64 => write!(f, "i64"), + LLVMType::Pointer(inner) => write!(f, "{}*", inner), + LLVMType::Function { + params, + return_type, + } => { + write!(f, "{} (", return_type)?; + for (i, p) in params.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", p)?; } + write!(f, ")") + } + LLVMType::Struct { name } => write!(f, "%{}", name), } + } } pub fn closure_type() -> LLVMType { - LLVMType::Struct { - name: "Closure".to_string(), - } + LLVMType::Struct { + name: "Closure".to_string(), + } } pub fn constructor_type() -> LLVMType { - LLVMType::Struct { - name: "Constructor".to_string(), - } + LLVMType::Struct { + name: "Constructor".to_string(), + } } pub fn string_type() -> LLVMType { - LLVMType::Struct { - name: "StringObj".to_string(), - } + LLVMType::Struct { + name: "StringObj".to_string(), + } } diff --git a/llvm-codegen/src/ir/value.rs b/llvm-codegen/src/ir/value.rs index d3b19cf..119b5a3 100644 --- a/llvm-codegen/src/ir/value.rs +++ b/llvm-codegen/src/ir/value.rs @@ -4,126 +4,185 @@ use super::types::LLVMType; #[derive(Clone, Debug)] pub enum LLVMValue { - Int(i64), - Int32(i32), - Bool(bool), - Unit, - Var(String), - Param(usize), - Global(String), - Call { - function: String, - args: Vec, - is_tail: bool, - }, - Add(Box, Box), - Sub(Box, Box), - Mul(Box, Box), - IcmpEq(Box, Box), - Phi(Vec<(LLVMValue, String)>), - GetElementPtr { - base: Box, - indices: Vec, - }, - Load { - ptr: Box, - }, - BitCast { - value: Box, - to_type: LLVMType, - }, - AllocClosure { - entry: String, - arity: i64, - env: Vec, - }, - AllocConstructor { - tag: i64, - fields: Vec, - }, + Int(i64), + Int32(i32), + Bool(bool), + Unit, + Var(String), + Param(usize), + Global(String), + Call { + function: String, + return_type: LLVMType, + args: Vec, + is_tail: bool, + }, + Add(Box, Box), + Sub(Box, Box), + Mul(Box, Box), + Div(Box, Box), + IcmpEq(Box, Box), + Zext { + value: Box, + from_type: LLVMType, + to_type: LLVMType, + }, + Trunc { + value: Box, + from_type: LLVMType, + to_type: LLVMType, + }, + Phi(Vec<(LLVMValue, String)>), + GetElementPtr { + base: Box, + indices: Vec, + }, + Load { + ptr: Box, + }, + BitCast { + value: Box, + to_type: LLVMType, + }, + AllocClosure { + entry: String, + arity: i64, + env: Vec, + }, + AllocConstructor { + tag: i64, + fields: Vec, + }, } impl LLVMValue { - pub fn var(name: &str) -> Self { - LLVMValue::Var(name.to_string()) + pub fn var(name: &str) -> Self { + LLVMValue::Var(name.to_string()) + } + + pub fn call(function: &str, return_type: LLVMType, args: Vec) -> Self { + LLVMValue::Call { + function: function.to_string(), + return_type, + args, + is_tail: false, } + } - pub fn call(function: &str, args: Vec) -> Self { - LLVMValue::Call { - function: function.to_string(), - args, - is_tail: false, - } + pub fn tail_call(function: &str, return_type: LLVMType, args: Vec) -> Self { + LLVMValue::Call { + function: function.to_string(), + return_type, + args, + is_tail: true, } + } - pub fn tail_call(function: &str, args: Vec) -> Self { - LLVMValue::Call { - function: function.to_string(), - args, - is_tail: true, - } + pub fn llvm_type(&self) -> LLVMType { + match self { + LLVMValue::Int(_) => LLVMType::I64, + LLVMValue::Int32(_) => LLVMType::I32, + LLVMValue::Bool(_) => LLVMType::I1, + LLVMValue::Unit => LLVMType::Void, + LLVMValue::Var(_) | LLVMValue::Param(_) => LLVMType::I64, + LLVMValue::Global(_) => LLVMType::Pointer(Box::new(LLVMType::I8)), + LLVMValue::Call { return_type, .. } => return_type.clone(), + LLVMValue::Add(_, _) + | LLVMValue::Sub(_, _) + | LLVMValue::Mul(_, _) + | LLVMValue::Div(_, _) + | LLVMValue::IcmpEq(_, _) => LLVMType::I1, + LLVMValue::Zext { to_type, .. } => to_type.clone(), + LLVMValue::Trunc { to_type, .. } => to_type.clone(), + LLVMValue::Phi(_) => LLVMType::I64, + LLVMValue::GetElementPtr { .. } => LLVMType::Pointer(Box::new(LLVMType::I8)), + LLVMValue::Load { .. } => LLVMType::I64, + LLVMValue::BitCast { to_type, .. } => to_type.clone(), + LLVMValue::AllocClosure { .. } => LLVMType::Pointer(Box::new(LLVMType::I8)), + LLVMValue::AllocConstructor { .. } => LLVMType::Pointer(Box::new(LLVMType::I8)), } + } + + pub fn display_typed(&self) -> String { + format!("{} {}", self.llvm_type(), self) + } } impl fmt::Display for LLVMValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LLVMValue::Int(n) => write!(f, "{}", n), - LLVMValue::Int32(n) => write!(f, "{}", n), - LLVMValue::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }), - LLVMValue::Unit => write!(f, "void"), - LLVMValue::Var(name) => write!(f, "%{}", name), - LLVMValue::Param(i) => write!(f, "%p{}", i), - LLVMValue::Global(name) => write!(f, "@{}", name), - LLVMValue::Call { - function, - args, - is_tail, - } => { - if *is_tail { - write!(f, "tail call {}(", function)?; - } else { - write!(f, "call {}(", function)?; - } - for (i, arg) in args.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "{}", arg)?; - } - write!(f, ")") - } - LLVMValue::Add(a, b) => write!(f, "add i64 {}, {}", a, b), - LLVMValue::Sub(a, b) => write!(f, "sub i64 {}, {}", a, b), - LLVMValue::Mul(a, b) => write!(f, "mul i64 {}, {}", a, b), - LLVMValue::IcmpEq(a, b) => write!(f, "icmp eq i64 {}, {}", a, b), - LLVMValue::Phi(pairs) => { - write!(f, "phi i64 [")?; - for (i, (val, label)) in pairs.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - write!(f, "[{}, %{}]", val, label)?; - } - write!(f, "]") - } - LLVMValue::GetElementPtr { base, indices } => { - write!(f, "getelementptr {}, {}", base, indices.len())?; - for idx in indices { - write!(f, ", i64 {}", idx)?; - } - Ok(()) - } - LLVMValue::Load { ptr } => write!(f, "load {}", ptr), - LLVMValue::BitCast { value, to_type } => { - write!(f, "bitcast {} to {}", value, to_type) - } - LLVMValue::AllocClosure { entry, arity, env } => { - write!(f, "alloc_closure({}, {}, {})", entry, arity, env.len()) - } - LLVMValue::AllocConstructor { tag, fields } => { - write!(f, "alloc_constructor({}, {})", tag, fields.len()) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LLVMValue::Int(n) => write!(f, "{}", n), + LLVMValue::Int32(n) => write!(f, "{}", n), + LLVMValue::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }), + LLVMValue::Unit => write!(f, "void"), + LLVMValue::Var(name) => write!(f, "%{}", name), + LLVMValue::Param(i) => write!(f, "%p{}", i), + LLVMValue::Global(name) => write!(f, "@{}", name), + LLVMValue::Call { + function, + return_type, + args, + is_tail, + } => { + if *is_tail { + write!(f, "tail call {} @{}(", return_type, function)?; + } else { + write!(f, "call {} @{}(", return_type, function)?; + } + for (i, arg) in args.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg.display_typed())?; + } + write!(f, ")") + } + LLVMValue::Add(a, b) => write!(f, "add i64 {}, {}", a, b), + LLVMValue::Sub(a, b) => write!(f, "sub i64 {}, {}", a, b), + LLVMValue::Mul(a, b) => write!(f, "mul i64 {}, {}", a, b), + LLVMValue::Div(a, b) => write!(f, "sdiv i64 {}, {}", a, b), + LLVMValue::IcmpEq(a, b) => write!(f, "icmp eq i64 {}, {}", a, b), + LLVMValue::Zext { + value, + from_type, + to_type, + } => { + write!(f, "zext {} {} to {}", from_type, value, to_type) + } + LLVMValue::Trunc { + value, + from_type, + to_type, + } => { + write!(f, "trunc {} {} to {}", from_type, value, to_type) + } + LLVMValue::Phi(pairs) => { + write!(f, "phi i64 [")?; + for (i, (val, label)) in pairs.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "[{}, %{}]", val, label)?; + } + write!(f, "]") + } + LLVMValue::GetElementPtr { base, indices } => { + write!(f, "getelementptr {}, {}", base, indices.len())?; + for idx in indices { + write!(f, ", i64 {}", idx)?; } + Ok(()) + } + LLVMValue::Load { ptr } => write!(f, "load {}", ptr), + LLVMValue::BitCast { value, to_type } => { + write!(f, "bitcast {} to {}", value, to_type) + } + LLVMValue::AllocClosure { entry, arity, env } => { + write!(f, "alloc_closure({}, {}, {})", entry, arity, env.len()) + } + LLVMValue::AllocConstructor { tag, fields } => { + write!(f, "alloc_constructor({}, {})", tag, fields.len()) + } } + } } diff --git a/llvm-codegen/src/lib.rs b/llvm-codegen/src/lib.rs index bfb749a..c595a90 100644 --- a/llvm-codegen/src/lib.rs +++ b/llvm-codegen/src/lib.rs @@ -1,7 +1,12 @@ pub mod codegen; +pub mod compiler; pub mod ir; pub mod runtime; pub use codegen::compile_decls; +pub use compiler::{ + CompileOptions, CompileResult, OutputKind, compile, compile_ir_to_object, compile_runtime, + compile_to_ir, link, write_ir_file, +}; pub use ir::LLVMModule; pub use runtime::RuntimeBuilder; diff --git a/llvm-codegen/src/runtime/mod.rs b/llvm-codegen/src/runtime/mod.rs index 06fca2b..8a90af8 100644 --- a/llvm-codegen/src/runtime/mod.rs +++ b/llvm-codegen/src/runtime/mod.rs @@ -1,15 +1,15 @@ pub struct RuntimeBuilder; impl RuntimeBuilder { - pub fn c_source() -> &'static str { - r#"#include + pub fn c_source() -> &'static str { + r#"#include #include #include #include #include typedef struct { - int64_t refcount; + _Atomic(int64_t) refcount; uint16_t tag; uint16_t flags; } Header; @@ -95,13 +95,9 @@ StringObj* alloc_string(char* data, int64_t length) { return s; } -void monad_print_i64(int64_t n) { - printf("%ld\n", n); -} - void monad_print_str(char* s) { printf("%s\n", s); } "# - } + } } diff --git a/llvm-codegen/tests/codegen_tests.rs b/llvm-codegen/tests/codegen_tests.rs index f2939cb..3c47aa2 100644 --- a/llvm-codegen/tests/codegen_tests.rs +++ b/llvm-codegen/tests/codegen_tests.rs @@ -3,181 +3,182 @@ use monad_core::term::{Decl, Param, Term, def, id, lam, lams, mpt, num, param, t use monad_llvm_codegen::compile_decls; fn make_def(name: &str, params: Vec, body: Term) -> Decl { - let param_types: Vec = params.iter().map(|p| (*p.typ).clone()).collect(); - let full_type = if param_types.is_empty() { - type0() - } else { - let mut typ = type0(); - for pt in param_types.into_iter().rev() { - typ = Term::Pi { - arg_name: None, - arg: Box::new(pt), - ret: Box::new(typ), - }; - } - typ - }; - let term = if params.is_empty() { - body - } else { - lams(params.clone(), body) - }; - Decl::Def(def(mpt(name), vec![], full_type, term, vec![])) + let param_types: Vec = params.iter().map(|p| (*p.typ).clone()).collect(); + let full_type = if param_types.is_empty() { + type0() + } else { + let mut typ = type0(); + for pt in param_types.into_iter().rev() { + typ = Term::Pi { + arg_name: None, + arg: Box::new(pt), + ret: Box::new(typ), + }; + } + typ + }; + let term = if params.is_empty() { + body + } else { + lams(params.clone(), body) + }; + Decl::Def(def(mpt(name), vec![], full_type, term, vec![])) } #[test] fn test_compile_integer_literal() { - let body = num(42); - let decls = vec![make_def("main", vec![], body)]; + let body = num(42); + let decls = vec![make_def("main", vec![], body)]; - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); - assert!(output.contains("define cc 9 i64 @main()")); - assert!(output.contains("ret i64 42")); - assert!(output.contains("cc 9")); + assert!(output.contains("define cc 9 i64 @main_monad()")); + assert!(output.contains("ret i64 42")); + assert!(output.contains("cc 9")); + assert!(output.contains("define i32 @main(")); } #[test] fn test_compile_lambda() { - let body = Term::Var { - name: monad_core::term::NameRef::Id(id("x")), - }; - let decls = vec![make_def("identity", vec![param(id("x"), type0())], body)]; + let body = Term::Var { + name: monad_core::term::NameRef::Id(id("x")), + }; + let decls = vec![make_def("identity", vec![param(id("x"), type0())], body)]; - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); - assert!(output.contains("define cc 9 i64 @identity(i64 %p0)")); - assert!(output.contains("ret i64")); + assert!(output.contains("define cc 9 i64 @identity(i64 %p0)")); + assert!(output.contains("ret i64")); } #[test] fn test_compile_multiple_functions() { - let body1 = num(10); - let body2 = num(20); - let decls = vec![ - make_def("foo", vec![], body1), - make_def("bar", vec![], body2), - ]; - - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); - - assert!(output.contains("define cc 9 i64 @foo()")); - assert!(output.contains("define cc 9 i64 @bar()")); - assert!(output.contains("ret i64 10")); - assert!(output.contains("ret i64 20")); + let body1 = num(10); + let body2 = num(20); + let decls = vec![ + make_def("foo", vec![], body1), + make_def("bar", vec![], body2), + ]; + + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); + + assert!(output.contains("define cc 9 i64 @foo()")); + assert!(output.contains("define cc 9 i64 @bar()")); + assert!(output.contains("ret i64 10")); + assert!(output.contains("ret i64 20")); } #[test] fn test_compile_with_main_wrapper() { - let body = num(42); - let decls = vec![make_def("main", vec![], body)]; + let body = num(42); + let decls = vec![make_def("main", vec![], body)]; - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); - assert!(output.contains("define i32 @main(i32 %argc, i64 %argv)")); - assert!(output.contains("ret i32 0")); + assert!(output.contains("define i32 @main(i32 %argc, i64 %argv)")); + assert!(output.contains("ret i32 %")); } #[test] fn test_llvm_ir_has_type_definitions() { - let body = num(1); - let decls = vec![make_def("test", vec![], body)]; + let body = num(1); + let decls = vec![make_def("test", vec![], body)]; - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); - assert!(output.contains("%Header = type")); - assert!(output.contains("%Closure = type")); - assert!(output.contains("%Constructor = type")); - assert!(output.contains("%StringObj = type")); + assert!(output.contains("%Header = type")); + assert!(output.contains("%Closure = type")); + assert!(output.contains("%Constructor = type")); + assert!(output.contains("%StringObj = type")); } #[test] fn test_llvm_ir_has_runtime_declarations() { - let body = num(1); - let decls = vec![make_def("test", vec![], body)]; + let body = num(1); + let decls = vec![make_def("test", vec![], body)]; - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); - assert!(output.contains("declare i8* @monad_alloc(i64)")); - assert!(output.contains("declare void @monad_retain(i8*)")); - assert!(output.contains("declare void @monad_release(i8*)")); - assert!(output.contains("declare void @monad_print_i64(i64)")); + assert!(output.contains("declare i8* @monad_alloc(i64)")); + assert!(output.contains("declare void @monad_retain(i8*)")); + assert!(output.contains("declare void @monad_release(i8*)")); + assert!(output.contains("declare void @monad_print_str(i8*)")); } #[test] fn test_ghc_calling_convention() { - let body = num(1); - let decls = vec![make_def("test", vec![], body)]; + let body = num(1); + let decls = vec![make_def("test", vec![], body)]; - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); - assert!(output.contains("cc 9")); + assert!(output.contains("cc 9")); } #[test] fn test_compile_nested_lambdas() { - let inner_body = Term::Var { - name: monad_core::term::NameRef::Id(id("x")), - }; - let inner_lam = lam(param(id("x"), type0()), inner_body); - let outer_body = Term::App { - fun: Box::new(inner_lam), - arg: Box::new(num(42)), - }; - let decls = vec![make_def("test", vec![], outer_body)]; - - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); - - assert!(output.contains("define cc 9 i64 @test()")); + let inner_body = Term::Var { + name: monad_core::term::NameRef::Id(id("x")), + }; + let inner_lam = lam(param(id("x"), type0()), inner_body); + let outer_body = Term::App { + fun: Box::new(inner_lam), + arg: Box::new(num(42)), + }; + let decls = vec![make_def("test", vec![], outer_body)]; + + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); + + assert!(output.contains("define cc 9 i64 @test()")); } #[test] fn test_compile_string_literal() { - let body = Term::Lit { - value: monad_core::term::Literal::Str { - value: "hello".to_string(), - }, - }; - let decls = vec![make_def("greet", vec![], body)]; - - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); - - assert!(output.contains("@str_")); - assert!(output.contains("constant")); + let body = Term::Lit { + value: monad_core::term::Literal::Str { + value: "hello".to_string(), + }, + }; + let decls = vec![make_def("greet", vec![], body)]; + + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); + + assert!(output.contains("@str_")); + assert!(output.contains("constant")); } #[test] fn test_generated_llvm_ir_is_valid() { - let body = num(42); - let decls = vec![make_def("test", vec![], body)]; + let body = num(42); + let decls = vec![make_def("test", vec![], body)]; - let module = compile_decls(&decls).unwrap(); - let output = module.emit(); + let module = compile_decls(&decls).unwrap(); + let output = module.emit(); - let temp_dir = std::env::temp_dir(); - let ll_path = temp_dir.join("monad_test.ll"); - let bc_path = temp_dir.join("monad_test.bc"); + let temp_dir = std::env::temp_dir(); + let ll_path = temp_dir.join("monad_test.ll"); + let bc_path = temp_dir.join("monad_test.bc"); - std::fs::write(&ll_path, &output).unwrap(); + std::fs::write(&ll_path, &output).unwrap(); - let status = std::process::Command::new("llvm-as") - .arg(&ll_path) - .arg("-o") - .arg(&bc_path) - .status(); + let status = std::process::Command::new("llvm-as") + .arg(&ll_path) + .arg("-o") + .arg(&bc_path) + .status(); - if let Ok(status) = status { - assert!(status.success(), "llvm-as failed to parse generated IR"); - std::fs::remove_file(&ll_path).ok(); - std::fs::remove_file(&bc_path).ok(); - } + if let Ok(status) = status { + assert!(status.success(), "llvm-as failed to parse generated IR"); + std::fs::remove_file(&ll_path).ok(); + std::fs::remove_file(&bc_path).ok(); + } } -- 2.51.2