Skip to content

Commit

Permalink
Unrolled build for rust-lang#117458
Browse files Browse the repository at this point in the history
Rollup merge of rust-lang#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](rust-lang@f8f9a28). It was [previously discussed](rust-lang#96842 (comment)) 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](rust-lang#96842 (comment)). 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>
  • Loading branch information
rust-timer committed Mar 11, 2024
2 parents 65cd843 + 843dd28 commit 4a922e7
Show file tree
Hide file tree
Showing 32 changed files with 529 additions and 25 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
23 changes: 19 additions & 4 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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,
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down
101 changes: 100 additions & 1 deletion compiler/rustc_codegen_ssa/src/back/linker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}
}
Expand Down Expand Up @@ -1824,7 +1825,7 @@ impl<'a> Linker for PtxLinker<'a> {
}

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

fn output_filename(&mut self, path: &Path) {
Expand Down Expand Up @@ -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,
Expand Down
22 changes: 19 additions & 3 deletions compiler/rustc_target/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand All @@ -141,6 +143,7 @@ pub enum LinkerFlavorCli {
EmCc,
Bpf,
Ptx,
Llbc,

// Legacy stable values
Gcc,
Expand All @@ -160,6 +163,7 @@ impl LinkerFlavorCli {
| LinkerFlavorCli::Msvc(Lld::Yes)
| LinkerFlavorCli::EmCc
| LinkerFlavorCli::Bpf
| LinkerFlavorCli::Llbc
| LinkerFlavorCli::Ptx => true,
LinkerFlavorCli::Gcc
| LinkerFlavorCli::Ld
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}
Expand All @@ -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,
}
}
Expand All @@ -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),
Expand Down Expand Up @@ -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,
}
}

Expand All @@ -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(|| {
Expand All @@ -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,
Expand All @@ -412,6 +424,7 @@ impl LinkerFlavor {
| LinkerFlavor::Msvc(_)
| LinkerFlavor::Unix(_)
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => false,
}
}
Expand All @@ -431,6 +444,7 @@ impl LinkerFlavor {
| LinkerFlavor::Msvc(_)
| LinkerFlavor::Unix(_)
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => false,
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2220,6 +2235,7 @@ fn add_link_args_iter(
| LinkerFlavor::Unix(..)
| LinkerFlavor::EmCc
| LinkerFlavor::Bpf
| LinkerFlavor::Llbc
| LinkerFlavor::Ptx => {}
}
}
Expand Down
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 {
Expand Down Expand Up @@ -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()
},
}
Expand Down
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
Expand Up @@ -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)
}
}
Expand Down
4 changes: 4 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions src/bootstrap/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/bootstrap/defaults/config.compiler.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down

0 comments on commit 4a922e7

Please sign in to comment.