blob: dafd847e7680895297eb92352191d95936dbe2db [file] [log] [blame] [edit]
use std::path::PathBuf;
use anyhow::Context;
use crate::{Optimization, Target};
#[derive(Debug)]
pub struct Session {
target: Target,
cpu: Option<String>,
feature: Option<String>,
symbols: Vec<String>,
/// A file that `llvm-link` supports, like a bitcode file or an archive.
files: Vec<PathBuf>,
// Output files
link_path: PathBuf,
opt_path: PathBuf,
sym_path: PathBuf,
out_path: PathBuf,
}
impl Session {
pub fn new(
target: crate::Target,
cpu: Option<String>,
feature: Option<String>,
out_path: PathBuf,
) -> Self {
let link_path = out_path.with_extension("o");
let opt_path = out_path.with_extension("optimized.o");
let sym_path = out_path.with_extension("symbols.txt");
Session {
target,
cpu,
feature,
symbols: Vec::new(),
files: Vec::new(),
link_path,
opt_path,
sym_path,
out_path,
}
}
/// Add a file, like an rlib or bitcode file that should be linked
pub fn add_file(&mut self, path: PathBuf) {
self.files.push(path);
}
/// Add a Vec of symbols to the list of exported symbols
pub fn add_exported_symbols(&mut self, symbols: Vec<String>) {
self.symbols.extend(symbols);
}
/// Reads every file that was added to the session and link them without optimization.
///
/// The resulting artifact will be written to a file that can later be read to perform
/// optimizations and/or compilation from bitcode to the final artifact.
fn link(&mut self) -> anyhow::Result<()> {
tracing::info!("Linking {} files using llvm-link", self.files.len());
let llvm_link_output = std::process::Command::new("llvm-link")
.arg("--ignore-non-bitcode")
.args(&self.files)
.arg("-o")
.arg(&self.link_path)
.output()
.context("An error occured when calling llvm-link. Make sure the llvm-tools component is installed.")?;
if !llvm_link_output.status.success() {
tracing::error!(
"llvm-link returned with Exit status: {}\n stdout: {}\n stderr: {}",
llvm_link_output.status,
String::from_utf8(llvm_link_output.stdout).unwrap(),
String::from_utf8(llvm_link_output.stderr).unwrap(),
);
anyhow::bail!("llvm-link failed to link files {:?}", self.files);
}
Ok(())
}
/// Optimize and compile to native format using `opt` and `llc`
///
/// Before this can be called `link` needs to be called
fn optimize(&mut self, optimization: Optimization, mut debug: bool) -> anyhow::Result<()> {
let mut passes = format!("default<{}>", optimization);
// FIXME(@kjetilkjeka) Debug symbol generation is broken for nvptx64 so we must remove them even in debug mode
if debug && self.target == crate::Target::Nvptx64NvidiaCuda {
tracing::warn!("nvptx64 target detected - stripping debug symbols");
debug = false;
}
// We add an internalize pass as the rust compiler as we require exported symbols to be explicitly marked
passes.push_str(",internalize,globaldce");
let symbol_file_content = self.symbols.iter().fold(String::new(), |s, x| s + &x + "\n");
std::fs::write(&self.sym_path, symbol_file_content)
.context(format!("Failed to write symbol file: {}", self.sym_path.display()))?;
tracing::info!("optimizing bitcode with passes: {}", passes);
let mut opt_cmd = std::process::Command::new("opt");
opt_cmd
.arg(&self.link_path)
.arg("-o")
.arg(&self.opt_path)
.arg(format!("--internalize-public-api-file={}", self.sym_path.display()))
.arg(format!("--passes={}", passes));
if !debug {
opt_cmd.arg("--strip-debug");
}
let opt_output = opt_cmd.output().context(
"An error occured when calling opt. Make sure the llvm-tools component is installed.",
)?;
if !opt_output.status.success() {
tracing::error!(
"opt returned with Exit status: {}\n stdout: {}\n stderr: {}",
opt_output.status,
String::from_utf8(opt_output.stdout).unwrap(),
String::from_utf8(opt_output.stderr).unwrap(),
);
anyhow::bail!("opt failed optimize bitcode: {}", self.link_path.display());
};
Ok(())
}
/// Compile the optimized bitcode file to native format using `llc`
///
/// Before this can be called `optimize` needs to be called
fn compile(&mut self) -> anyhow::Result<()> {
let mut lcc_command = std::process::Command::new("llc");
if let Some(mcpu) = &self.cpu {
lcc_command.arg("--mcpu").arg(mcpu);
}
if let Some(mattr) = &self.feature {
lcc_command.arg(&format!("--mattr={}", mattr));
}
let lcc_output = lcc_command
.arg(&self.opt_path)
.arg("-o").arg(&self.out_path)
.output()
.context("An error occured when calling llc. Make sure the llvm-tools component is installed.")?;
if !lcc_output.status.success() {
tracing::error!(
"llc returned with Exit status: {}\n stdout: {}\n stderr: {}",
lcc_output.status,
String::from_utf8(lcc_output.stdout).unwrap(),
String::from_utf8(lcc_output.stderr).unwrap(),
);
anyhow::bail!(
"llc failed to compile {} into {}",
self.opt_path.display(),
self.out_path.display()
);
}
Ok(())
}
/// Links, optimizes and compiles to the native format
pub fn lto(&mut self, optimization: crate::Optimization, debug: bool) -> anyhow::Result<()> {
self.link()?;
self.optimize(optimization, debug)?;
self.compile()
}
}