LLVM Bitcode Linker: Added crate
This commit is contained in:
parent
ed252e931e
commit
222ce4fdb8
11
Cargo.lock
11
Cargo.lock
@ -2265,6 +2265,17 @@ checksum = "f9d642685b028806386b2b6e75685faadd3eb65a85fff7df711ce18446a422da"
|
||||
name = "lld-wrapper"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "llvm-bitcode-linker"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
|
@ -34,6 +34,7 @@ members = [
|
||||
"src/tools/expand-yaml-anchors",
|
||||
"src/tools/jsondocck",
|
||||
"src/tools/jsondoclint",
|
||||
"src/tools/llvm-bitcode-linker",
|
||||
"src/tools/html-checker",
|
||||
"src/tools/bump-stage0",
|
||||
"src/tools/replace-version-placeholder",
|
||||
|
@ -795,6 +795,7 @@ fn run(mut $sel, $builder: &Builder<'_>) -> PathBuf {
|
||||
Rls, "src/tools/rls", "rls", stable=true, tool_std=true;
|
||||
RustDemangler, "src/tools/rust-demangler", "rust-demangler", stable=false, tool_std=true;
|
||||
Rustfmt, "src/tools/rustfmt", "rustfmt", stable=true, add_bins_to_sysroot = ["rustfmt", "cargo-fmt"];
|
||||
LlvmBitcodeLinker, "src/tools/llvm-bitcode-linker", "llvm-bitcode-linker", stable=false, add_bins_to_sysroot = ["llvm-bitcode-linker"];
|
||||
);
|
||||
|
||||
impl<'a> Builder<'a> {
|
||||
|
@ -763,6 +763,7 @@ macro_rules! describe {
|
||||
tool::RustdocGUITest,
|
||||
tool::OptimizedDist,
|
||||
tool::CoverageDump,
|
||||
tool::LlvmBitcodeLinker
|
||||
),
|
||||
Kind::Check | Kind::Clippy | Kind::Fix => describe!(
|
||||
check::Std,
|
||||
|
14
src/tools/llvm-bitcode-linker/Cargo.toml
Normal file
14
src/tools/llvm-bitcode-linker/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "llvm-bitcode-linker"
|
||||
version = "0.0.1"
|
||||
description = "A self-contained linker for llvm bitcode"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = {version = "0.3.0", features = ["std"] }
|
||||
clap = { version = "4.3", features = ["derive"] }
|
||||
thiserror = "1.0.24"
|
5
src/tools/llvm-bitcode-linker/README.md
Normal file
5
src/tools/llvm-bitcode-linker/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# LLVM Bitcode Linker
|
||||
The LLVM bitcode linker can be used to link targets without any dependency on system libraries.
|
||||
The code will be linked in llvm-bc before compiling to native code. For some of these targets
|
||||
(e.g. ptx) there does not exist a sensible way to link the native format at all. A bitcode linker
|
||||
is required to link code compiled for such targets.
|
62
src/tools/llvm-bitcode-linker/src/bin/llvm-bitcode-linker.rs
Normal file
62
src/tools/llvm-bitcode-linker/src/bin/llvm-bitcode-linker.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use llvm_bitcode_linker::{Optimization, Session, Target};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
/// Linker for embedded code without any system dependencies
|
||||
pub struct Args {
|
||||
/// Input files - objects, archives and static libraries.
|
||||
///
|
||||
/// An archive can be, but not required to be, a Rust rlib.
|
||||
files: Vec<PathBuf>,
|
||||
|
||||
/// A symbol that should be exported
|
||||
#[arg(long)]
|
||||
export_symbol: Vec<String>,
|
||||
|
||||
/// Input files directory
|
||||
#[arg(short = 'L')]
|
||||
input_dir: Vec<PathBuf>,
|
||||
|
||||
/// Target triple for which the code is compiled
|
||||
#[arg(long)]
|
||||
target: Target,
|
||||
|
||||
/// The target cpu
|
||||
#[arg(long)]
|
||||
target_cpu: Option<String>,
|
||||
|
||||
/// Write output to the filename
|
||||
#[arg(short, long)]
|
||||
output: PathBuf,
|
||||
|
||||
// Enable link time optimization
|
||||
#[arg(long)]
|
||||
lto: bool,
|
||||
|
||||
/// Emit debug information
|
||||
#[arg(long)]
|
||||
debug: bool,
|
||||
|
||||
/// The optimization level
|
||||
#[arg(short = 'O', value_enum, default_value = "0")]
|
||||
optimization: Optimization,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::FmtSubscriber::builder().with_max_level(tracing::Level::DEBUG).init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
let mut linker = Session::new(args.target, args.target_cpu, args.output);
|
||||
|
||||
linker.add_exported_symbols(args.export_symbol);
|
||||
|
||||
for rlib in args.files {
|
||||
linker.add_file(rlib);
|
||||
}
|
||||
|
||||
linker.lto(args.optimization, args.debug)
|
||||
}
|
7
src/tools/llvm-bitcode-linker/src/lib.rs
Normal file
7
src/tools/llvm-bitcode-linker/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod linker;
|
||||
mod opt;
|
||||
mod target;
|
||||
|
||||
pub use linker::Session;
|
||||
pub use opt::Optimization;
|
||||
pub use target::Target;
|
163
src/tools/llvm-bitcode-linker/src/linker.rs
Normal file
163
src/tools/llvm-bitcode-linker/src/linker.rs
Normal file
@ -0,0 +1,163 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::Optimization;
|
||||
use crate::Target;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Session {
|
||||
target: Target,
|
||||
cpu: 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>, 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,
|
||||
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()
|
||||
.unwrap();
|
||||
|
||||
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().unwrap();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let lcc_output =
|
||||
lcc_command.arg(&self.opt_path).arg("-o").arg(&self.out_path).output().unwrap();
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
53
src/tools/llvm-bitcode-linker/src/opt.rs
Normal file
53
src/tools/llvm-bitcode-linker/src/opt.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use std::fmt::Display;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Hash, Eq, PartialEq, clap::ValueEnum)]
|
||||
pub enum Optimization {
|
||||
#[default]
|
||||
#[value(name = "0")]
|
||||
O0,
|
||||
#[value(name = "1")]
|
||||
O1,
|
||||
#[value(name = "2")]
|
||||
O2,
|
||||
#[value(name = "3")]
|
||||
O3,
|
||||
#[value(name = "s")]
|
||||
Os,
|
||||
#[value(name = "z")]
|
||||
Oz,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
||||
/// An invalid optimization level
|
||||
#[error("invalid optimization level")]
|
||||
pub struct InvalidOptimizationLevel;
|
||||
|
||||
impl std::str::FromStr for Optimization {
|
||||
type Err = InvalidOptimizationLevel;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"0" | "O0" => Ok(Optimization::O0),
|
||||
"1" | "O1" => Ok(Optimization::O1),
|
||||
"2" | "O2" => Ok(Optimization::O2),
|
||||
"3" | "O3" => Ok(Optimization::O3),
|
||||
"s" | "Os" => Ok(Optimization::Os),
|
||||
"z" | "Oz" => Ok(Optimization::Oz),
|
||||
_ => Err(InvalidOptimizationLevel),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Optimization {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match *self {
|
||||
Optimization::O0 => write!(f, "O0"),
|
||||
Optimization::O1 => write!(f, "O1"),
|
||||
Optimization::O2 => write!(f, "O2"),
|
||||
Optimization::O3 => write!(f, "O3"),
|
||||
Optimization::Os => write!(f, "Os"),
|
||||
Optimization::Oz => write!(f, "Oz"),
|
||||
}
|
||||
}
|
||||
}
|
20
src/tools/llvm-bitcode-linker/src/target.rs
Normal file
20
src/tools/llvm-bitcode-linker/src/target.rs
Normal file
@ -0,0 +1,20 @@
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, clap::ValueEnum)]
|
||||
pub enum Target {
|
||||
Nvptx64NvidiaCuda,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, thiserror::Error)]
|
||||
/// The target is not supported by this linker
|
||||
#[error("unsupported target")]
|
||||
pub struct UnsupportedTarget;
|
||||
|
||||
impl std::str::FromStr for Target {
|
||||
type Err = UnsupportedTarget;
|
||||
|
||||
fn from_str(s: &str) -> Result<Target, UnsupportedTarget> {
|
||||
match s {
|
||||
"nvptx64-nvidia-cuda" => Ok(Target::Nvptx64NvidiaCuda),
|
||||
_ => Err(UnsupportedTarget),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user