1 //! CLI tool to read Cranelift IR files and compile them into native code. 2 3 use crate::disasm::print_all; 4 use crate::utils::read_to_string; 5 use anyhow::{Context as _, Result}; 6 use clap::Parser; 7 use cranelift_codegen::Context; 8 use cranelift_codegen::print_errors::pretty_error; 9 use cranelift_codegen::settings::FlagsOrIsa; 10 use cranelift_codegen::timing; 11 use cranelift_reader::OwnedFlagsOrIsa; 12 use cranelift_reader::{ParseOptions, parse_sets_and_triple, parse_test}; 13 use std::path::Path; 14 use std::path::PathBuf; 15 16 /// Compiles Cranelift IR into target language 17 #[derive(Parser)] 18 pub struct Options { 19 /// Print the resulting Cranelift IR 20 #[arg(short)] 21 print: bool, 22 23 /// Print pass timing report 24 #[arg(short = 'T')] 25 report_times: bool, 26 27 /// Print machine code disassembly 28 #[arg(short = 'D', long)] 29 disasm: bool, 30 31 /// Configure Cranelift settings 32 #[arg(long = "set")] 33 settings: Vec<String>, 34 35 /// Specify the Cranelift target 36 #[arg(long = "target")] 37 target: String, 38 39 /// Specify an input file to be used. Use '-' for stdin. 40 files: Vec<PathBuf>, 41 42 /// Output object file 43 #[arg(short = 'o', long = "output")] 44 output: Option<PathBuf>, 45 } 46 47 pub fn run(options: &Options) -> Result<()> { 48 let parsed = parse_sets_and_triple(&options.settings, &options.target)?; 49 50 let mut module = match (&options.output, &parsed) { 51 (Some(output), OwnedFlagsOrIsa::Isa(isa)) => { 52 let builder = cranelift_object::ObjectBuilder::new( 53 isa.clone(), 54 output 55 .file_name() 56 .and_then(|s| s.to_str()) 57 .unwrap_or("a.out"), 58 cranelift_module::default_libcall_names(), 59 )?; 60 Some(cranelift_object::ObjectModule::new(builder)) 61 } 62 _ => None, 63 }; 64 65 for path in &options.files { 66 let name = String::from(path.as_os_str().to_string_lossy()); 67 handle_module(options, path, &name, parsed.as_fisa(), module.as_mut())?; 68 } 69 70 if let (Some(module), Some(output)) = (module, &options.output) { 71 let bytes = module.finish().emit()?; 72 std::fs::write(output, bytes)?; 73 } 74 75 Ok(()) 76 } 77 78 fn handle_module( 79 options: &Options, 80 path: &Path, 81 name: &str, 82 fisa: FlagsOrIsa, 83 module: Option<&mut impl cranelift_module::Module>, 84 ) -> Result<()> { 85 let buffer = read_to_string(&path)?; 86 let test_file = parse_test(&buffer, ParseOptions::default()) 87 .with_context(|| format!("failed to parse {name}"))?; 88 89 // If we have an isa from the command-line, use that. Otherwise if the 90 // file contains a unique isa, use that. 91 let isa = fisa.isa.or(test_file.isa_spec.unique_isa()); 92 93 let isa = match isa { 94 None => anyhow::bail!("compilation requires a target isa"), 95 Some(isa) => isa, 96 }; 97 98 for (func, _) in test_file.functions { 99 let mut context = Context::new(); 100 context.func = func; 101 102 // Compile and encode the result to machine code. 103 let compiled_code = context 104 .compile(isa, &mut Default::default()) 105 .map_err(|err| anyhow::anyhow!("{}", pretty_error(&err.func, err.inner)))?; 106 let code_info = compiled_code.code_info(); 107 108 if let Some(&mut ref mut module) = module { 109 let name = context.func.name.to_string(); 110 let fid = module.declare_function( 111 &name, 112 cranelift_module::Linkage::Export, 113 &context.func.signature, 114 )?; 115 module.define_function_with_control_plane( 116 fid, 117 &mut context, 118 &mut Default::default(), 119 )?; 120 } 121 122 if options.print { 123 println!("{}", context.func.display()); 124 } 125 126 if options.disasm { 127 let result = context.compiled_code().unwrap(); 128 print_all( 129 isa, 130 &context.func, 131 context.compiled_code().unwrap().code_buffer(), 132 code_info.total_size, 133 options.print, 134 result.buffer.relocs(), 135 result.buffer.traps(), 136 )?; 137 } 138 } 139 140 if options.report_times { 141 print!("{}", timing::take_current()); 142 } 143 144 Ok(()) 145 } 146