Rollup merge of #117458 - kjetilkjeka:embedded-linker, r=petrochenkov

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

This PR introduces a new linker named `llvm-bitcode-linker`. It is a `self-contained` linker that can be used to link programs in `llbc` before optimizing and compiling to native code. It will first be used internally in the Rust compiler to enable tests for the `nvptx64-nvidia-cuda` target as the original `rust-ptx-linker` is deprecated. It will then be provided to users of the `nvptx64-nvidia-cuda` target with the purpose of linking ptx. More targets than nvptx will also be supported eventually.

The PR introduces a new unstable `LinkerFlavor` for the compiler. The compiler will also not be shipped with rustc but most likely instead be shipped in it's own unstable component (a follow up PR will be opened for this). This means that merging this PR should not add any stability guarantees.

When more details of `self-contained` is implemented it will only be possible to use the linker when `-Clink-self-contained=+linker` is passed.

<details>
  <summary>Original Description</summary>

**When this PR was created it was focused a bit differently. The original text is preserved here in case there's some interests in it**

I have experimenting with approaches to replace the ptx-linker and enable the nvptx target tests again. I think it's time to get some feedback on the approach.

### The problem
The only useful linker for the nvptx target is [this crate](https://github.com/denzp/rust-ptx-linker). Since this linker performs linking on llvm bitcode it needs to track the llvm version of rustc and use the same format. It has not been maintained for 3+ years and must be considered abandoned. Over the years rust have upgraded LLVM while the linker has been left to bitrot. It is no longer in a usable state.

Due to the difficulty of keeping the ptx-linker up to date outside of tree the nvptx tests was [disabled a long time ago](f8f9a2869c). It was [previously discussed](https://github.com/rust-lang/rust/pull/96842#issuecomment-1146470177) if adding the ptx-linker to the rust repo would be a possibility. My efforts in doing this stopped at getting an answered if the license would prohibit it from inclusion in the [Rust repo](https://github.com/rust-lang/rust/pull/96842#issuecomment-1148397554). I therefore concluded that a re-write would be necessary.

### The possible solution presented here
The llvm tools know perfectly well how to link and optimize llvm bitcode. Each of them only perform a single task, and are therefore a bit cumbersome to call with the current linker approach rustc takes.

This PR adds a simple tool (current name `embedded-linker`) which can link self contained (often embedded) programs in llvm bitcode before compiling to the target format. Optimization will also be performed if lto is enabled. The rust compiler will make a single invocation to this tool, while the tool will orchestrate the many calls to the llvm tools.

### The questions
 - Is having control over the nvptx linking and therefore also tests worth it to add such tool? or should the tool live outside the rust repo?
 - Is the approach of calling llvm tools acceptable? Or would we want to keep the ptx-linker approach of using the llvm library? The tools seems to provide more simplicity and stability, but more intermediate files are being written. Perhaps there also are some performance penalty for the calling tools approach.
 - What is the process for adding such tool? MCP?
 - Does adding `llvm-link` to the llvm-tool component require any process?
 - Does it require some sort of FCP to remove ptx-linker as the default linker for ptx? Or is it sufficient that using the upstream ptx-linker is broken in its current state. it is possible to use a somewhat patched version of ptx-linker.
</details>
This commit is contained in:
Jubilee 2024-03-11 09:29:32 -07:00 committed by GitHub
commit e1ceadcdfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 529 additions and 25 deletions

View File

@ -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"

View File

@ -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",

View File

@ -24,6 +24,7 @@
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 @@ fn infer_from(
}
}
LinkerFlavor::Bpf => "bpf-linker",
LinkerFlavor::Llbc => "llvm-bitcode-linker",
LinkerFlavor::Ptx => "rust-ptx-linker",
}),
flavor,
@ -1367,8 +1369,17 @@ fn infer_from(
// 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 {

View File

@ -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 @@ fn optimize(&mut self) {
}
Lto::No => {}
};
}
}
fn output_filename(&mut self, path: &Path) {
@ -1862,6 +1863,104 @@ fn subsystem(&mut self, _subsystem: &str) {}
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,

View File

@ -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 @@ pub fn is_unstable(&self) -> bool {
| LinkerFlavorCli::Msvc(Lld::Yes)
| LinkerFlavorCli::EmCc
| LinkerFlavorCli::Bpf
| LinkerFlavorCli::Llbc
| LinkerFlavorCli::Ptx => true,
LinkerFlavorCli::Gcc
| LinkerFlavorCli::Ld
@ -219,6 +223,7 @@ fn from_cli_json(cli: LinkerFlavorCli, lld_flavor: LldFlavor, is_gnu: bool) -> L
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 @@ fn to_cli(self) -> LinkerFlavorCli {
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 @@ fn to_cli_counterpart(self) -> LinkerFlavorCli {
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 @@ fn infer_cli_hints(cli: LinkerFlavorCli) -> (Option<Cc>, Option<Lld>) {
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 @@ fn with_hints(self, (cc_hint, lld_hint): (Option<Cc>, Option<Lld>)) -> LinkerFla
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,8 +363,8 @@ pub fn with_linker_hints(self, linker_stem: &str) -> 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(..))
@ -364,11 +372,14 @@ pub fn check_compatibility(self, cli: LinkerFlavorCli) -> Option<String> {
| (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 @@ pub fn lld_flavor(self) -> LldFlavor {
| LinkerFlavor::Unix(..)
| LinkerFlavor::EmCc
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => LldFlavor::Ld,
LinkerFlavor::Darwin(..) => LldFlavor::Ld64,
LinkerFlavor::WasmLld(..) => LldFlavor::Wasm,
@ -412,6 +424,7 @@ pub fn uses_lld(self) -> bool {
| LinkerFlavor::Msvc(_)
| LinkerFlavor::Unix(_)
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => false,
}
}
@ -431,6 +444,7 @@ pub fn uses_cc(self) -> bool {
| LinkerFlavor::Msvc(_)
| LinkerFlavor::Unix(_)
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => false,
}
}
@ -480,6 +494,7 @@ pub fn desc(self) -> &'static str {
(LinkerFlavorCli::Msvc(Lld::No)) "msvc"
(LinkerFlavorCli::EmCc) "em-cc"
(LinkerFlavorCli::Bpf) "bpf"
(LinkerFlavorCli::Llbc) "llbc"
(LinkerFlavorCli::Ptx) "ptx"
// Legacy stable flavors
@ -2228,6 +2243,7 @@ fn add_link_args_iter(
| LinkerFlavor::Unix(..)
| LinkerFlavor::EmCc
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => {}
}
}

View File

@ -1,3 +1,4 @@
use crate::spec::LinkSelfContainedDefault;
use crate::spec::{LinkerFlavor, MergeFunctions, PanicStrategy, Target, TargetOptions};
pub fn target() -> Target {
@ -52,6 +53,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()
},
}

View File

@ -56,7 +56,10 @@ fn check_consistency(&self) {
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)
}
}

View File

@ -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

View File

@ -54,6 +54,7 @@ o("cargo-native-static", "build.cargo-native-static", "static native libraries i
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

View File

@ -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`

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -1843,6 +1843,16 @@ fn run(self, builder: &Builder<'_>) -> Compiler {
}
}
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);

View File

@ -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> {

View File

@ -763,6 +763,7 @@ macro_rules! describe {
tool::RustdocGUITest,
tool::OptimizedDist,
tool::CoverageDump,
tool::LlvmBitcodeLinker
),
Kind::Check | Kind::Clippy | Kind::Fix => describe!(
check::Std,

View File

@ -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 @@ struct Rust {
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 @@ fn get_table(option: &str) -> Result<TomlConfig, toml::de::Error> {
codegen_backends,
lld,
llvm_tools,
llvm_bitcode_linker,
deny_warnings,
backtrace_on_ice,
verify_llvm_ir,
@ -1650,6 +1653,7 @@ fn get_table(option: &str) -> Result<TomlConfig, toml::de::Error> {
}
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

View File

@ -64,6 +64,7 @@
"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
];

View File

@ -146,4 +146,9 @@ pub fn find_recent_config_change_ids(current_id: usize) -> Vec<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.",
},
];

View File

@ -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 \

View 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"

View 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.

View 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)
}

View File

@ -0,0 +1,7 @@
mod linker;
mod opt;
mod target;
pub use linker::Session;
pub use opt::Optimization;
pub use target::Target;

View 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()
}
}

View 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"),
}
}
}

View 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),
}
}
}

View File

@ -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]

View File

@ -1,7 +1,6 @@
//@ assembly-output: emit-asm
//@ compile-flags: --crate-type rlib
//@ only-nvptx64
//@ ignore-nvptx64
#![no_std]

View File

@ -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]

View File

@ -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 struct ManyNumerics {
#[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 struct ManyNumerics {
#[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]

View File

@ -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]