Skip to content

LLVM Bitcode Linker: A self contained linker for nvptx and other targets #117458

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
@@ -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"
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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",
23 changes: 19 additions & 4 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ use rustc_span::symbol::Symbol;
use rustc_target::spec::crt_objects::CrtObjects;
use rustc_target::spec::LinkSelfContainedComponents;
use rustc_target::spec::LinkSelfContainedDefault;
use rustc_target::spec::LinkerFlavorCli;
use rustc_target::spec::{Cc, LinkOutputKind, LinkerFlavor, Lld, PanicStrategy};
use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo};

@@ -1350,6 +1351,7 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
}
}
LinkerFlavor::Bpf => "bpf-linker",
LinkerFlavor::Llbc => "llvm-bitcode-linker",
LinkerFlavor::Ptx => "rust-ptx-linker",
}),
flavor,
@@ -1367,8 +1369,17 @@ pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {

// linker and linker flavor specified via command line have precedence over what the target
// specification specifies
let linker_flavor =
sess.opts.cg.linker_flavor.map(|flavor| sess.target.linker_flavor.with_cli_hints(flavor));
let linker_flavor = match sess.opts.cg.linker_flavor {
// The linker flavors that are non-target specific can be directly translated to LinkerFlavor
Some(LinkerFlavorCli::Llbc) => Some(LinkerFlavor::Llbc),
Some(LinkerFlavorCli::Ptx) => Some(LinkerFlavor::Ptx),
// The linker flavors that corresponds to targets needs logic that keeps the base LinkerFlavor
_ => sess
.opts
.cg
.linker_flavor
.map(|flavor| sess.target.linker_flavor.with_cli_hints(flavor)),
};
if let Some(ret) = infer_from(sess, sess.opts.cg.linker.clone(), linker_flavor) {
return ret;
}
@@ -2338,8 +2349,12 @@ fn add_order_independent_options(
});
}

if flavor == LinkerFlavor::Ptx {
// Provide the linker with fallback to internal `target-cpu`.
if flavor == LinkerFlavor::Llbc {
cmd.arg("--target");
cmd.arg(sess.target.llvm_target.as_ref());
cmd.arg("--target-cpu");
cmd.arg(&codegen_results.crate_info.target_cpu);
} else if flavor == LinkerFlavor::Ptx {
cmd.arg("--fallback-arch");
cmd.arg(&codegen_results.crate_info.target_cpu);
} else if flavor == LinkerFlavor::Bpf {
101 changes: 100 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/linker.rs
Original file line number Diff line number Diff line change
@@ -153,6 +153,7 @@ pub fn get_linker<'a>(
LinkerFlavor::Msvc(..) => Box::new(MsvcLinker { cmd, sess }) as Box<dyn Linker>,
LinkerFlavor::EmCc => Box::new(EmLinker { cmd, sess }) as Box<dyn Linker>,
LinkerFlavor::Bpf => Box::new(BpfLinker { cmd, sess }) as Box<dyn Linker>,
LinkerFlavor::Llbc => Box::new(LlbcLinker { cmd, sess }) as Box<dyn Linker>,
LinkerFlavor::Ptx => Box::new(PtxLinker { cmd, sess }) as Box<dyn Linker>,
}
}
@@ -1824,7 +1825,7 @@ impl<'a> Linker for PtxLinker<'a> {
}

Lto::No => {}
};
}
}

fn output_filename(&mut self, path: &Path) {
@@ -1862,6 +1863,104 @@ impl<'a> Linker for PtxLinker<'a> {
fn linker_plugin_lto(&mut self) {}
}

/// The `self-contained` LLVM bitcode linker
pub struct LlbcLinker<'a> {
cmd: Command,
sess: &'a Session,
}

impl<'a> Linker for LlbcLinker<'a> {
fn cmd(&mut self) -> &mut Command {
&mut self.cmd
}

fn set_output_kind(&mut self, _output_kind: LinkOutputKind, _out_filename: &Path) {}

fn link_dylib_by_name(&mut self, _name: &str, _verbatim: bool, _as_needed: bool) {
panic!("external dylibs not supported")
}

fn link_staticlib_by_name(
&mut self,
_name: &str,
_verbatim: bool,
_whole_archive: bool,
_search_paths: &SearchPaths,
) {
panic!("staticlibs not supported")
}

fn link_staticlib_by_path(&mut self, path: &Path, _whole_archive: bool) {
self.cmd.arg(path);
}

fn include_path(&mut self, path: &Path) {
self.cmd.arg("-L").arg(path);
}

fn debuginfo(&mut self, _strip: Strip, _: &[PathBuf]) {
self.cmd.arg("--debug");
}

fn add_object(&mut self, path: &Path) {
self.cmd.arg(path);
}

fn optimize(&mut self) {
match self.sess.opts.optimize {
OptLevel::No => "-O0",
OptLevel::Less => "-O1",
OptLevel::Default => "-O2",
OptLevel::Aggressive => "-O3",
OptLevel::Size => "-Os",
OptLevel::SizeMin => "-Oz",
};
}

fn output_filename(&mut self, path: &Path) {
self.cmd.arg("-o").arg(path);
}

fn framework_path(&mut self, _path: &Path) {
panic!("frameworks not supported")
}

fn full_relro(&mut self) {}

fn partial_relro(&mut self) {}

fn no_relro(&mut self) {}

fn gc_sections(&mut self, _keep_metadata: bool) {}

fn no_gc_sections(&mut self) {}

fn pgo_gen(&mut self) {}

fn no_crt_objects(&mut self) {}

fn no_default_libraries(&mut self) {}

fn control_flow_guard(&mut self) {}

fn ehcont_guard(&mut self) {}

fn export_symbols(&mut self, _tmpdir: &Path, _crate_type: CrateType, symbols: &[String]) {
match _crate_type {
CrateType::Cdylib => {
for sym in symbols {
self.cmd.arg("--export-symbol").arg(sym);
}
}
_ => (),
}
}

fn subsystem(&mut self, _subsystem: &str) {}

fn linker_plugin_lto(&mut self) {}
}

pub struct BpfLinker<'a> {
cmd: Command,
sess: &'a Session,
22 changes: 19 additions & 3 deletions compiler/rustc_target/src/spec/mod.rs
Original file line number Diff line number Diff line change
@@ -123,6 +123,8 @@ pub enum LinkerFlavor {
Bpf,
/// Linker tool for Nvidia PTX.
Ptx,
/// LLVM bitcode linker that can be used as a `self-contained` linker
Llbc,
}

/// Linker flavors available externally through command line (`-Clinker-flavor`)
@@ -141,6 +143,7 @@ pub enum LinkerFlavorCli {
EmCc,
Bpf,
Ptx,
Llbc,

// Legacy stable values
Gcc,
@@ -160,6 +163,7 @@ impl LinkerFlavorCli {
| LinkerFlavorCli::Msvc(Lld::Yes)
| LinkerFlavorCli::EmCc
| LinkerFlavorCli::Bpf
| LinkerFlavorCli::Llbc
| LinkerFlavorCli::Ptx => true,
LinkerFlavorCli::Gcc
| LinkerFlavorCli::Ld
@@ -219,6 +223,7 @@ impl LinkerFlavor {
LinkerFlavorCli::Msvc(lld) => LinkerFlavor::Msvc(lld),
LinkerFlavorCli::EmCc => LinkerFlavor::EmCc,
LinkerFlavorCli::Bpf => LinkerFlavor::Bpf,
LinkerFlavorCli::Llbc => LinkerFlavor::Llbc,
LinkerFlavorCli::Ptx => LinkerFlavor::Ptx,

// Below: legacy stable values
@@ -258,6 +263,7 @@ impl LinkerFlavor {
LinkerFlavor::Msvc(..) => LinkerFlavorCli::Msvc(Lld::No),
LinkerFlavor::EmCc => LinkerFlavorCli::Em,
LinkerFlavor::Bpf => LinkerFlavorCli::Bpf,
LinkerFlavor::Llbc => LinkerFlavorCli::Llbc,
LinkerFlavor::Ptx => LinkerFlavorCli::Ptx,
}
}
@@ -272,6 +278,7 @@ impl LinkerFlavor {
LinkerFlavor::Msvc(lld) => LinkerFlavorCli::Msvc(lld),
LinkerFlavor::EmCc => LinkerFlavorCli::EmCc,
LinkerFlavor::Bpf => LinkerFlavorCli::Bpf,
LinkerFlavor::Llbc => LinkerFlavorCli::Llbc,
LinkerFlavor::Ptx => LinkerFlavorCli::Ptx,
}
}
@@ -286,6 +293,7 @@ impl LinkerFlavor {
LinkerFlavorCli::Msvc(lld) => (Some(Cc::No), Some(lld)),
LinkerFlavorCli::EmCc => (Some(Cc::Yes), Some(Lld::Yes)),
LinkerFlavorCli::Bpf | LinkerFlavorCli::Ptx => (None, None),
LinkerFlavorCli::Llbc => (None, None),

// Below: legacy stable values
LinkerFlavorCli::Gcc => (Some(Cc::Yes), None),
@@ -340,7 +348,7 @@ impl LinkerFlavor {
LinkerFlavor::WasmLld(cc) => LinkerFlavor::WasmLld(cc_hint.unwrap_or(cc)),
LinkerFlavor::Unix(cc) => LinkerFlavor::Unix(cc_hint.unwrap_or(cc)),
LinkerFlavor::Msvc(lld) => LinkerFlavor::Msvc(lld_hint.unwrap_or(lld)),
LinkerFlavor::EmCc | LinkerFlavor::Bpf | LinkerFlavor::Ptx => self,
LinkerFlavor::EmCc | LinkerFlavor::Bpf | LinkerFlavor::Llbc | LinkerFlavor::Ptx => self,
}
}

@@ -355,20 +363,23 @@ impl LinkerFlavor {
pub fn check_compatibility(self, cli: LinkerFlavorCli) -> Option<String> {
let compatible = |cli| {
// The CLI flavor should be compatible with the target if:
// 1. they are counterparts: they have the same principal flavor.
match (self, cli) {
// 1. they are counterparts: they have the same principal flavor.
(LinkerFlavor::Gnu(..), LinkerFlavorCli::Gnu(..))
| (LinkerFlavor::Darwin(..), LinkerFlavorCli::Darwin(..))
| (LinkerFlavor::WasmLld(..), LinkerFlavorCli::WasmLld(..))
| (LinkerFlavor::Unix(..), LinkerFlavorCli::Unix(..))
| (LinkerFlavor::Msvc(..), LinkerFlavorCli::Msvc(..))
| (LinkerFlavor::EmCc, LinkerFlavorCli::EmCc)
| (LinkerFlavor::Bpf, LinkerFlavorCli::Bpf)
| (LinkerFlavor::Llbc, LinkerFlavorCli::Llbc)
| (LinkerFlavor::Ptx, LinkerFlavorCli::Ptx) => return true,
// 2. The linker flavor is independent of target and compatible
(LinkerFlavor::Ptx, LinkerFlavorCli::Llbc) => return true,
_ => {}
}

// 2. or, the flavor is legacy and survives this roundtrip.
// 3. or, the flavor is legacy and survives this roundtrip.
cli == self.with_cli_hints(cli).to_cli()
};
(!compatible(cli)).then(|| {
@@ -387,6 +398,7 @@ impl LinkerFlavor {
| LinkerFlavor::Unix(..)
| LinkerFlavor::EmCc
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => LldFlavor::Ld,
LinkerFlavor::Darwin(..) => LldFlavor::Ld64,
LinkerFlavor::WasmLld(..) => LldFlavor::Wasm,
@@ -412,6 +424,7 @@ impl LinkerFlavor {
| LinkerFlavor::Msvc(_)
| LinkerFlavor::Unix(_)
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => false,
}
}
@@ -431,6 +444,7 @@ impl LinkerFlavor {
| LinkerFlavor::Msvc(_)
| LinkerFlavor::Unix(_)
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => false,
}
}
@@ -480,6 +494,7 @@ linker_flavor_cli_impls! {
(LinkerFlavorCli::Msvc(Lld::No)) "msvc"
(LinkerFlavorCli::EmCc) "em-cc"
(LinkerFlavorCli::Bpf) "bpf"
(LinkerFlavorCli::Llbc) "llbc"
(LinkerFlavorCli::Ptx) "ptx"

// Legacy stable flavors
@@ -2205,6 +2220,7 @@ fn add_link_args_iter(
| LinkerFlavor::Unix(..)
| LinkerFlavor::EmCc
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => {}
}
}
4 changes: 4 additions & 0 deletions compiler/rustc_target/src/spec/targets/nvptx64_nvidia_cuda.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::spec::LinkSelfContainedDefault;
use crate::spec::{LinkerFlavor, MergeFunctions, PanicStrategy, Target, TargetOptions};

pub fn target() -> Target {
@@ -47,6 +48,9 @@ pub fn target() -> Target {
// The LLVM backend does not support stack canaries for this target
supports_stack_protector: false,

// Support using `self-contained` linkers like the llvm-bitcode-linker
link_self_contained: LinkSelfContainedDefault::True,

..Default::default()
},
}
5 changes: 4 additions & 1 deletion compiler/rustc_target/src/spec/tests/tests_impl.rs
Original file line number Diff line number Diff line change
@@ -56,7 +56,10 @@ impl Target {
LinkerFlavor::Msvc(..) => {
assert_matches!(flavor, LinkerFlavor::Msvc(..))
}
LinkerFlavor::EmCc | LinkerFlavor::Bpf | LinkerFlavor::Ptx => {
LinkerFlavor::EmCc
| LinkerFlavor::Bpf
| LinkerFlavor::Ptx
| LinkerFlavor::Llbc => {
assert_eq!(flavor, self.linker_flavor)
}
}
4 changes: 4 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
@@ -679,6 +679,10 @@
# sysroot.
#llvm-tools = true

# Indicates whether the `self-contained` llvm-bitcode-linker, will be made available
# in the sysroot
#llvm-bitcode-linker = false

# Whether to deny warnings in crates
#deny-warnings = true

2 changes: 2 additions & 0 deletions src/bootstrap/configure.py
Original file line number Diff line number Diff line change
@@ -54,6 +54,7 @@ def v(*args):
o("profiler", "build.profiler", "build the profiler runtime")
o("full-tools", None, "enable all tools")
o("lld", "rust.lld", "build lld")
o("llvm-bitcode-linker", "rust.llvm-bitcode-linker", "build llvm bitcode linker")
o("clang", "llvm.clang", "build clang")
o("use-libcxx", "llvm.use-libcxx", "build LLVM with libc++")
o("control-flow-guard", "rust.control-flow-guard", "Enable Control Flow Guard")
@@ -366,6 +367,7 @@ def apply_args(known_args, option_checking, config):
set('rust.codegen-backends', ['llvm'], config)
set('rust.lld', True, config)
set('rust.llvm-tools', True, config)
set('rust.llvm-bitcode-linker', True, config)
set('build.extended', True, config)
elif option.name in ['option-checking', 'verbose-configure']:
# this was handled above
2 changes: 2 additions & 0 deletions src/bootstrap/defaults/config.compiler.toml
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@ lto = "off"
# Forces frame pointers to be used with `-Cforce-frame-pointers`.
# This can be helpful for profiling at a small performance cost.
frame-pointers = true
# Build the llvm-bitcode-linker as it is required for running nvptx tests
llvm-bitcode-linker = true

[llvm]
# Having this set to true disrupts compiler development workflows for people who use `llvm.download-ci-llvm = true`
2 changes: 2 additions & 0 deletions src/bootstrap/defaults/config.dist.toml
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@ download-ci-llvm = false
# Make sure they don't get set when installing from source.
channel = "nightly"
download-rustc = false
# Build the llvm-bitcode-linker as it is required for running nvptx tests
llvm-bitcode-linker = true

[dist]
# Use better compression when preparing tarballs.
2 changes: 2 additions & 0 deletions src/bootstrap/defaults/config.library.toml
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ bench-stage = 0
incremental = true
# Make the compiler and standard library faster to build, at the expense of a ~20% runtime slowdown.
lto = "off"
# Build the llvm-bitcode-linker as it is required for running nvptx tests
llvm-bitcode-linker = true

[llvm]
# Will download LLVM from CI if available on your platform.
2 changes: 2 additions & 0 deletions src/bootstrap/defaults/config.tools.toml
Original file line number Diff line number Diff line change
@@ -12,6 +12,8 @@ incremental = true
# Using these defaults will download the stage2 compiler (see `download-rustc`
# setting) and the stage2 toolchain should therefore be used for these defaults.
download-rustc = "if-unchanged"
# Build the llvm-bitcode-linker as it is required for running nvptx tests
llvm-bitcode-linker = true

[build]
# Document with the in-tree rustdoc by default, since `download-rustc` makes it quick to compile.
10 changes: 10 additions & 0 deletions src/bootstrap/src/core/build_steps/compile.rs
Original file line number Diff line number Diff line change
@@ -1843,6 +1843,16 @@ impl Step for Assemble {
}
}

if builder.config.llvm_bitcode_linker_enabled {
let src_path = builder.ensure(crate::core::build_steps::tool::LlvmBitcodeLinker {
compiler: build_compiler,
target: target_compiler.host,
extra_features: vec![],
});
let tool_exe = exe("llvm-bitcode-linker", target_compiler.host);
builder.copy(&src_path, &libdir_bin.join(&tool_exe));
}

// Ensure that `libLLVM.so` ends up in the newly build compiler directory,
// so that it can be found when the newly built `rustc` is run.
dist::maybe_install_llvm_runtime(builder, target_compiler.host, &sysroot);
1 change: 1 addition & 0 deletions src/bootstrap/src/core/build_steps/tool.rs
Original file line number Diff line number Diff line change
@@ -795,6 +795,7 @@ tool_extended!((self, builder),
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> {
1 change: 1 addition & 0 deletions src/bootstrap/src/core/builder.rs
Original file line number Diff line number Diff line change
@@ -763,6 +763,7 @@ impl<'a> Builder<'a> {
tool::RustdocGUITest,
tool::OptimizedDist,
tool::CoverageDump,
tool::LlvmBitcodeLinker
),
Kind::Check | Kind::Clippy | Kind::Fix => describe!(
check::Std,
4 changes: 4 additions & 0 deletions src/bootstrap/src/core/config/config.rs
Original file line number Diff line number Diff line change
@@ -236,6 +236,7 @@ pub struct Config {
pub lld_mode: LldMode,
pub lld_enabled: bool,
pub llvm_tools_enabled: bool,
pub llvm_bitcode_linker_enabled: bool,

pub llvm_cflags: Option<String>,
pub llvm_cxxflags: Option<String>,
@@ -1099,6 +1100,7 @@ define_config! {
dist_src: Option<bool> = "dist-src",
save_toolstates: Option<String> = "save-toolstates",
codegen_backends: Option<Vec<String>> = "codegen-backends",
llvm_bitcode_linker: Option<bool> = "llvm-bitcode-linker",
lld: Option<bool> = "lld",
lld_mode: Option<LldMode> = "use-lld",
llvm_tools: Option<bool> = "llvm-tools",
@@ -1571,6 +1573,7 @@ impl Config {
codegen_backends,
lld,
llvm_tools,
llvm_bitcode_linker,
deny_warnings,
backtrace_on_ice,
verify_llvm_ir,
@@ -1650,6 +1653,7 @@ impl Config {
}
set(&mut config.lld_mode, lld_mode);
set(&mut config.lld_enabled, lld);
set(&mut config.llvm_bitcode_linker_enabled, llvm_bitcode_linker);

if matches!(config.lld_mode, LldMode::SelfContained)
&& !config.lld_enabled
1 change: 1 addition & 0 deletions src/bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
@@ -64,6 +64,7 @@ const LLVM_TOOLS: &[&str] = &[
"llvm-ar", // used for creating and modifying archive files
"llvm-as", // used to convert LLVM assembly to LLVM bitcode
"llvm-dis", // used to disassemble LLVM bitcode
"llvm-link", // Used to link LLVM bitcode
"llc", // used to compile LLVM bytecode
"opt", // used to optimize LLVM bytecode
];
5 changes: 5 additions & 0 deletions src/bootstrap/src/utils/change_tracker.rs
Original file line number Diff line number Diff line change
@@ -146,4 +146,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
severity: ChangeSeverity::Info,
summary: "a new `target.*.runner` option is available to specify a wrapper executable required to run tests for a target",
},
ChangeInfo {
change_id: 117458,
severity: ChangeSeverity::Info,
summary: "New option `rust.llvm-bitcode-linker` that will build the llvm-bitcode-linker.",
},
];
2 changes: 1 addition & 1 deletion src/ci/docker/host-x86_64/dist-various-2/Dockerfile
Original file line number Diff line number Diff line change
@@ -135,7 +135,7 @@ ENV TARGETS=$TARGETS,x86_64-unknown-uefi
# Luckily one of the folders is /usr/local/include so symlink /usr/include/x86_64-linux-gnu/asm there
RUN ln -s /usr/include/x86_64-linux-gnu/asm /usr/local/include/asm

ENV RUST_CONFIGURE_ARGS --enable-extended --enable-lld --disable-docs \
ENV RUST_CONFIGURE_ARGS --enable-extended --enable-lld --enable-llvm-bitcode-linker --disable-docs \
--set target.wasm32-wasi.wasi-root=/wasm32-wasip1 \
--set target.wasm32-wasip1.wasi-root=/wasm32-wasip1 \
--set target.wasm32-wasi-preview1-threads.wasi-root=/wasm32-wasi-preview1-threads \
14 changes: 14 additions & 0 deletions src/tools/llvm-bitcode-linker/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions src/tools/llvm-bitcode-linker/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 62 additions & 0 deletions src/tools/llvm-bitcode-linker/src/bin/llvm-bitcode-linker.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 0 deletions src/tools/llvm-bitcode-linker/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod linker;
mod opt;
mod target;

pub use linker::Session;
pub use opt::Optimization;
pub use target::Target;
163 changes: 163 additions & 0 deletions src/tools/llvm-bitcode-linker/src/linker.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 53 additions & 0 deletions src/tools/llvm-bitcode-linker/src/opt.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions src/tools/llvm-bitcode-linker/src/target.rs
Original file line number Diff line number Diff line change
@@ -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),
}
}
}
3 changes: 1 addition & 2 deletions tests/assembly/nvptx-arch-default.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//@ assembly-output: ptx-linker
//@ compile-flags: --crate-type cdylib
//@ compile-flags: --crate-type cdylib -Z unstable-options -Clinker-flavor=llbc
//@ only-nvptx64
//@ ignore-nvptx64

#![no_std]

1 change: 0 additions & 1 deletion tests/assembly/nvptx-arch-emit-asm.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//@ assembly-output: emit-asm
//@ compile-flags: --crate-type rlib
//@ only-nvptx64
//@ ignore-nvptx64

#![no_std]

3 changes: 1 addition & 2 deletions tests/assembly/nvptx-arch-target-cpu.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//@ assembly-output: ptx-linker
//@ compile-flags: --crate-type cdylib -C target-cpu=sm_50
//@ compile-flags: --crate-type cdylib -C target-cpu=sm_50 -Z unstable-options -Clinker-flavor=llbc
//@ only-nvptx64
//@ ignore-nvptx64

#![no_std]

13 changes: 6 additions & 7 deletions tests/assembly/nvptx-kernel-abi/nvptx-kernel-args-abi-v7.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//@ assembly-output: ptx-linker
//@ compile-flags: --crate-type cdylib -C target-cpu=sm_86
//@ compile-flags: --crate-type cdylib -C target-cpu=sm_86 -Z unstable-options -Clinker-flavor=llbc
//@ only-nvptx64
//@ ignore-nvptx64

// The following ABI tests are made with nvcc 11.6 does.
//
@@ -226,10 +225,11 @@ pub unsafe extern "ptx-kernel" fn f_byte_array_arg(_a: [u8; 5]) {}
#[no_mangle]
pub unsafe extern "ptx-kernel" fn f_float_array_arg(_a: [f32; 5]) {}

// CHECK: .visible .entry f_u128_array_arg(
// CHECK: .param .align 16 .b8 f_u128_array_arg_param_0[80]
#[no_mangle]
pub unsafe extern "ptx-kernel" fn f_u128_array_arg(_a: [u128; 5]) {}
// FIXME: u128 started to break compilation with disabled CI
// NO_CHECK: .visible .entry f_u128_array_arg(
// NO_CHECK: .param .align 16 .b8 f_u128_array_arg_param_0[80]
//#[no_mangle]
//pub unsafe extern "ptx-kernel" fn f_u128_array_arg(_a: [u128; 5]) {}

// CHECK: .visible .entry f_u32_slice_arg(
// CHECK: .param .u64 f_u32_slice_arg_param_0
@@ -247,7 +247,6 @@ pub unsafe extern "ptx-kernel" fn f_tuple_u8_u8_arg(_a: (u8, u8)) {}
#[no_mangle]
pub unsafe extern "ptx-kernel" fn f_tuple_u32_u32_arg(_a: (u32, u32)) {}


// CHECK: .visible .entry f_tuple_u8_u8_u32_arg(
// CHECK: .param .align 4 .b8 f_tuple_u8_u8_u32_arg_param_0[8]
#[no_mangle]
5 changes: 2 additions & 3 deletions tests/assembly/nvptx-safe-naming.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//@ assembly-output: ptx-linker
//@ compile-flags: --crate-type cdylib
//@ compile-flags: --crate-type cdylib -Z unstable-options -Clinker-flavor=llbc
//@ only-nvptx64
//@ ignore-nvptx64

#![feature(abi_ptx)]
#![no_std]
@@ -10,7 +9,7 @@
extern crate breakpoint_panic_handler;

// Verify function name doesn't contain unacceaptable characters.
// CHECK: .func (.param .b32 func_retval0) [[IMPL_FN:[a-zA-Z0-9$_]+square[a-zA-Z0-9$_]+]](
// CHECK: .func (.param .b32 func_retval0) [[IMPL_FN:[a-zA-Z0-9$_]+square[a-zA-Z0-9$_]+]]

// CHECK-LABEL: .visible .entry top_kernel(
#[no_mangle]