mirror of https://github.com/rust-lang/rust.git
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:
commit
e1ceadcdfe
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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,8 +363,8 @@ 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(..))
|
||||
|
@ -364,11 +372,14 @@ impl LinkerFlavor {
|
|||
| (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
|
||||
|
@ -2228,6 +2243,7 @@ fn add_link_args_iter(
|
|||
| LinkerFlavor::Unix(..)
|
||||
| LinkerFlavor::EmCc
|
||||
| LinkerFlavor::Bpf
|
||||
| LinkerFlavor::Llbc
|
||||
| LinkerFlavor::Ptx => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
];
|
||||
|
|
|
@ -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.",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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"
|
|
@ -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.
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mod linker;
|
||||
mod opt;
|
||||
mod target;
|
||||
|
||||
pub use linker::Session;
|
||||
pub use opt::Optimization;
|
||||
pub use target::Target;
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,7 +1,6 @@
|
|||
//@ assembly-output: emit-asm
|
||||
//@ compile-flags: --crate-type rlib
|
||||
//@ only-nvptx64
|
||||
//@ ignore-nvptx64
|
||||
|
||||
#![no_std]
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue