clif_util/
compile.rs

1//! CLI tool to read Cranelift IR files and compile them into native code.
2
3use crate::disasm::print_all;
4use crate::utils::read_to_string;
5use anyhow::{Context as _, Result};
6use clap::Parser;
7use cranelift_codegen::print_errors::pretty_error;
8use cranelift_codegen::settings::FlagsOrIsa;
9use cranelift_codegen::timing;
10use cranelift_codegen::Context;
11use cranelift_reader::OwnedFlagsOrIsa;
12use cranelift_reader::{parse_sets_and_triple, parse_test, ParseOptions};
13use std::path::Path;
14use std::path::PathBuf;
15
16/// Compiles Cranelift IR into target language
17#[derive(Parser)]
18pub 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
47pub 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
78fn 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}