diff --git a/compiler/backend_inkwell/src/lib.rs b/compiler/backend_inkwell/src/lib.rs index bc997345d..afdacf902 100644 --- a/compiler/backend_inkwell/src/lib.rs +++ b/compiler/backend_inkwell/src/lib.rs @@ -17,6 +17,7 @@ use inkwell::{ context::Context, module::Module, support::LLVMString, + targets::{InitializationConfig, Target, TargetMachine}, types::{ BasicMetadataTypeEnum, BasicType, FunctionType, IntType, PointerType, StructType, VoidType, }, @@ -29,7 +30,7 @@ use llvm_sys as _; use rustc_hash::FxHashMap; use std::{ collections::{HashMap, HashSet}, - path::PathBuf, + path::Path, sync::Arc, }; @@ -44,8 +45,9 @@ fn llvm_ir(db: &dyn LlvmIrDb, target: ExecutionTarget) -> Result { unrepresented_ids: HashSet, } +pub struct LlvmCandyModule<'ctx> { + module: Module<'ctx>, +} + +impl<'ctx> LlvmCandyModule<'ctx> { + pub fn compile_obj_and_link( + &self, + path: &str, + build_rt: bool, + debug: bool, + linker: &str, + ) -> Result<(), std::io::Error> { + if build_rt { + std::process::Command::new("make") + .args(["-C", "compiler/backend_inkwell/candy_runtime/", "clean"]) + .spawn()? + .wait()?; + + std::process::Command::new("make") + .args([ + "-C", + "compiler/backend_inkwell/candy_runtime/", + "candy_runtime.a", + ]) + .spawn()? + .wait()?; + } + let triple = TargetMachine::get_default_triple(); + Target::initialize_native(&InitializationConfig::default()).unwrap(); + let target = Target::from_triple(&triple).unwrap(); + + let target_machine = target + .create_target_machine( + &triple, + "generic", + "", + inkwell::OptimizationLevel::Default, + inkwell::targets::RelocMode::Default, + inkwell::targets::CodeModel::Default, + ) + .unwrap(); + + self.module + .set_data_layout(&target_machine.get_target_data().get_data_layout()); + self.module.set_triple(&triple); + + let o_path = format!("{path}.o"); + + target_machine + .write_to_file( + &self.module, + inkwell::targets::FileType::Object, + Path::new(&o_path), + ) + .unwrap(); + + std::process::Command::new(linker) + .args([ + "-dynamic-linker", + // TODO: This is not portable. + "/lib/ld-linux-x86-64.so.2", + "/usr/lib/crt1.o", + "/usr/lib/crti.o", + "-L/usr/lib", + "-lc", + &o_path, + "compiler/backend_inkwell/candy_runtime/candy_runtime.a", + "/usr/lib/crtn.o", + if debug { "-g" } else { "" }, + "-o", + o_path.as_str().strip_suffix(".candy.o").unwrap(), + ]) + .spawn()? + .wait()?; + Ok(()) + } +} + impl<'ctx> CodeGen<'ctx> { pub fn new(context: &'ctx Context, module_name: &str, mir: Arc) -> Self { let module = context.create_module(module_name); @@ -93,11 +173,10 @@ impl<'ctx> CodeGen<'ctx> { } pub fn compile( - &mut self, - path: &str, + mut self, print_llvm_ir: bool, print_main_output: bool, - ) -> Result { + ) -> Result, LLVMString> { let void_type = self.context.void_type(); let i8_type = self.context.i8_type(); let i32_type = self.context.i32_type(); @@ -234,54 +313,9 @@ impl<'ctx> CodeGen<'ctx> { self.module.print_to_stderr(); } self.module.verify()?; - if !path.is_empty() { - let bc_path = PathBuf::from(format!("{path}.bc")); - self.module.write_bitcode_to_path(&bc_path); - } - Ok(self.module.print_to_string()) - } - - pub fn compile_asm_and_link( - &self, - path: &str, - build_rt: bool, - debug: bool, - ) -> Result<(), std::io::Error> { - let bc_path = PathBuf::from(&format!("{path}.bc")); - std::process::Command::new("llc") - .arg(&bc_path) - .args(["-O3"]) - .spawn()? - .wait()?; - if build_rt { - std::process::Command::new("make") - .args(["-C", "compiler/backend_inkwell/candy_runtime/", "clean"]) - .spawn()? - .wait()?; - - std::process::Command::new("make") - .args([ - "-C", - "compiler/backend_inkwell/candy_runtime/", - "candy_runtime.a", - ]) - .spawn()? - .wait()?; - } - let s_path = PathBuf::from(format!("{path}.s")); - std::process::Command::new("clang") - .args([ - s_path.to_str().unwrap(), - "compiler/backend_inkwell/candy_runtime/candy_runtime.a", - if debug { "-g" } else { "" }, - "-O3", - "-flto", - "-o", - &s_path.to_str().unwrap().replace(".candy.s", ""), - ]) - .spawn()? - .wait()?; - Ok(()) + Ok(LlvmCandyModule { + module: self.module, + }) } fn compile_mir( diff --git a/compiler/cli/src/inkwell.rs b/compiler/cli/src/inkwell.rs index ca2a178d5..9a2c66179 100644 --- a/compiler/cli/src/inkwell.rs +++ b/compiler/cli/src/inkwell.rs @@ -40,6 +40,10 @@ pub(crate) struct Options { #[arg(short = 'g', default_value_t = false)] debug: bool, + /// The linker to be used. Defaults to `ld.lld` + #[arg(long, default_value = "ld.lld")] + linker: String, + /// The file or package to compile. If none is provided, compile the package /// of your current working directory. #[arg(value_hint = ValueHint::FilePath)] @@ -89,12 +93,12 @@ pub(crate) fn compile(options: Options) -> ProgramResult { } let context = candy_backend_inkwell::inkwell::context::Context::create(); - let mut codegen = CodeGen::new(&context, &path, mir); - codegen - .compile(&path, options.print_llvm_ir, options.print_main_output) + let codegen = CodeGen::new(&context, &path, mir); + let llvm_candy_module = codegen + .compile(options.print_llvm_ir, options.print_main_output) .map_err(|e| Exit::LlvmError(e.to_string()))?; - codegen - .compile_asm_and_link(&path, options.build_runtime, options.debug) + llvm_candy_module + .compile_obj_and_link(&path, options.build_runtime, options.debug, &options.linker) .map_err(|err| { error!("Failed to compile and link executable: {}", err); Exit::ExternalError