Skip to content

Commit b603bdd

Browse files
author
Kjetil Kjeka
committed
Embedded-linker: Add crate
1 parent 3e61f82 commit b603bdd

File tree

11 files changed

+417
-0
lines changed

11 files changed

+417
-0
lines changed

Cargo.lock

+11
Original file line numberDiff line numberDiff line change
@@ -1211,6 +1211,17 @@ dependencies = [
12111211
"stable_deref_trait",
12121212
]
12131213

1214+
[[package]]
1215+
name = "embedded-linker"
1216+
version = "0.0.1"
1217+
dependencies = [
1218+
"anyhow",
1219+
"clap",
1220+
"thiserror",
1221+
"tracing",
1222+
"tracing-subscriber",
1223+
]
1224+
12141225
[[package]]
12151226
name = "ena"
12161227
version = "0.14.2"

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ members = [
3333
"src/tools/expand-yaml-anchors",
3434
"src/tools/jsondocck",
3535
"src/tools/jsondoclint",
36+
"src/tools/embedded-linker",
3637
"src/tools/html-checker",
3738
"src/tools/bump-stage0",
3839
"src/tools/replace-version-placeholder",

src/bootstrap/src/core/build_steps/tool.rs

+2
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,8 @@ tool_extended!((self, builder),
803803
Rls, "src/tools/rls", "rls", stable=true, tool_std=true;
804804
RustDemangler, "src/tools/rust-demangler", "rust-demangler", stable=false, tool_std=true;
805805
Rustfmt, "src/tools/rustfmt", "rustfmt", stable=true, add_bins_to_sysroot = ["rustfmt", "cargo-fmt"];
806+
EmbeddedLinker, "src/tools/embedded-linker", "rust-embedded-linker", stable=false, add_bins_to_sysroot = ["rust-embedded-linker"];
807+
PtxLinker, "src/tools/embedded-linker", "rust-ptx-linker", stable=false, add_bins_to_sysroot = ["rust-ptx-linker"];
806808
);
807809

808810
impl<'a> Builder<'a> {

src/bootstrap/src/core/builder.rs

+1
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ impl<'a> Builder<'a> {
706706
tool::RustdocGUITest,
707707
tool::OptimizedDist,
708708
tool::CoverageDump,
709+
tool::EmbeddedLinker
709710
),
710711
Kind::Check | Kind::Clippy | Kind::Fix => describe!(
711712
check::Std,

src/tools/embedded-linker/Cargo.toml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "embedded-linker"
3+
version = "0.0.1"
4+
description = "Linker for embedded code without any system dependencies"
5+
license = "MIT OR Apache-2.0"
6+
edition = "2021"
7+
publish = false
8+
9+
[dependencies]
10+
anyhow = "1.0"
11+
tracing = "0.1"
12+
tracing-subscriber = {version = "0.3.0", features = ["std"] }
13+
clap = { version = "4.3", features = ["derive"] }
14+
thiserror = "1.0.24"

src/tools/embedded-linker/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Rust Embedded Linker
2+
3+
## 2DO
4+
### Relation to rust-ptx-linker
5+
Spiritual successor.
6+
- Reimplemented due to license issues
7+
- Support more targets
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use std::path::PathBuf;
2+
3+
use clap::Parser;
4+
5+
use embedded_linker::{Optimization, Session, Target};
6+
7+
#[derive(Debug, Parser)]
8+
/// Linker for embedded code without any system dependencies
9+
pub struct Args {
10+
/// Input LLVM bitcode file
11+
#[arg(long)]
12+
bitcode: Vec<PathBuf>,
13+
14+
/// Input Rust rlib archives
15+
#[arg(long)]
16+
rlib: Vec<PathBuf>,
17+
18+
/// Input Rust rlib archives where all global symbols should be kept
19+
#[arg(long)]
20+
whole_rlib: Vec<PathBuf>,
21+
22+
/// Input files directory
23+
#[arg(short = 'L')]
24+
input_dir: Vec<PathBuf>,
25+
26+
/// Target triple for which the code is compiled
27+
#[arg(long)]
28+
target: Target,
29+
30+
/// The target cpu
31+
#[arg(long)]
32+
target_cpu: Option<String>,
33+
34+
/// Write output to the filename
35+
#[arg(short, long)]
36+
output: PathBuf,
37+
38+
// Enable link time optimization
39+
#[arg(long)]
40+
lto: bool,
41+
42+
/// Emit debug information
43+
#[arg(long)]
44+
debug: bool,
45+
46+
/// The optimization level
47+
#[arg(short = 'O', value_enum, default_value = "0")]
48+
optimization: Optimization,
49+
}
50+
51+
fn main() -> anyhow::Result<()> {
52+
tracing_subscriber::FmtSubscriber::builder().with_max_level(tracing::Level::DEBUG).init();
53+
54+
let args = Args::parse();
55+
56+
let mut linker = Session::new(args.target, args.target_cpu, args.output);
57+
58+
for rlib in args.whole_rlib {
59+
linker.link_rlib(rlib, true)?;
60+
}
61+
62+
for rlib in args.rlib {
63+
linker.link_rlib(rlib, false)?;
64+
}
65+
66+
for bitcode in args.bitcode {
67+
linker.add_bitcode(bitcode, true)?;
68+
}
69+
70+
linker.lto(args.optimization, true, args.debug)
71+
}

src/tools/embedded-linker/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mod linker;
2+
mod opt;
3+
mod target;
4+
5+
pub use linker::Session;
6+
pub use opt::Optimization;
7+
pub use target::Target;
+230
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
use std::path::Path;
2+
use std::path::PathBuf;
3+
4+
use anyhow::Context;
5+
6+
use crate::Optimization;
7+
use crate::Target;
8+
9+
#[derive(Debug)]
10+
pub struct Session {
11+
target: Target,
12+
cpu: Option<String>,
13+
symbols: Vec<String>,
14+
bitcode: Vec<PathBuf>,
15+
16+
// Output files
17+
link_path: PathBuf,
18+
opt_path: PathBuf,
19+
sym_path: PathBuf,
20+
out_path: PathBuf,
21+
}
22+
23+
impl Session {
24+
pub fn new(target: crate::Target, cpu: Option<String>, out_path: PathBuf) -> Self {
25+
let link_path = out_path.with_extension("o");
26+
let opt_path = out_path.with_extension("optimized.o");
27+
let sym_path = out_path.with_extension("symbols.txt");
28+
29+
Session {
30+
target,
31+
cpu,
32+
symbols: Vec::new(),
33+
bitcode: Vec::new(),
34+
link_path,
35+
opt_path,
36+
sym_path,
37+
out_path,
38+
}
39+
}
40+
41+
/// Link a rlib into a bitcode object and add it to the list of files ready to be linked
42+
pub fn link_rlib(&mut self, path: impl AsRef<Path>, keep_symbols: bool) -> anyhow::Result<()> {
43+
let output_file_link = path.as_ref().with_extension("o");
44+
tracing::info!(
45+
"Linking rlib: {} into bitcode: {}",
46+
path.as_ref().display(),
47+
output_file_link.display(),
48+
);
49+
50+
let link_output = std::process::Command::new("llvm-link")
51+
.arg(path.as_ref())
52+
.arg("-o")
53+
.arg(&output_file_link)
54+
.arg("--ignore-non-bitcode")
55+
.output()
56+
.unwrap();
57+
58+
if !link_output.status.success() {
59+
tracing::error!(
60+
"llvm-link returned with Exit status: {}\n stdout: {}\n stderr: {}",
61+
link_output.status,
62+
String::from_utf8(link_output.stdout).unwrap(),
63+
String::from_utf8(link_output.stderr).unwrap(),
64+
);
65+
anyhow::bail!("llvm-link failed to link file {}", path.as_ref().display());
66+
}
67+
68+
self.add_bitcode(output_file_link, keep_symbols)
69+
}
70+
71+
/// Add a bitcode module ready to be linked
72+
pub fn add_bitcode(
73+
&mut self,
74+
path: impl AsRef<Path>,
75+
keep_symbols: bool,
76+
) -> anyhow::Result<()> {
77+
if keep_symbols {
78+
let nm_output = std::process::Command::new("llvm-nm")
79+
.arg("--extern-only")
80+
.arg("--export-symbols")
81+
.arg(path.as_ref())
82+
.output()
83+
.unwrap();
84+
85+
if !nm_output.status.success() {
86+
tracing::error!(
87+
"llvm-nm returned with Exit status: {}\n stdout: {}\n stderr: {}",
88+
nm_output.status,
89+
String::from_utf8(nm_output.stdout).unwrap(),
90+
String::from_utf8(nm_output.stderr).unwrap(),
91+
);
92+
anyhow::bail!(
93+
"llvm-nm failed to return symbols from file {}",
94+
path.as_ref().display()
95+
);
96+
}
97+
98+
let symbol_string = String::from_utf8(nm_output.stdout).unwrap();
99+
self.symbols
100+
.extend(symbol_string.split_whitespace().into_iter().map(|x| String::from(x)));
101+
}
102+
103+
self.bitcode.push(path.as_ref().to_owned());
104+
Ok(())
105+
}
106+
107+
fn link(&mut self) -> anyhow::Result<()> {
108+
tracing::info!("Linking {} bitcode files using llvm-link", self.bitcode.len());
109+
110+
let llvm_link_output = std::process::Command::new("llvm-link")
111+
.args(&self.bitcode)
112+
.arg("-o")
113+
.arg(&self.link_path)
114+
.output()
115+
.unwrap();
116+
117+
if !llvm_link_output.status.success() {
118+
tracing::error!(
119+
"llvm-link returned with Exit status: {}\n stdout: {}\n stderr: {}",
120+
llvm_link_output.status,
121+
String::from_utf8(llvm_link_output.stdout).unwrap(),
122+
String::from_utf8(llvm_link_output.stderr).unwrap(),
123+
);
124+
anyhow::bail!("llvm-link failed to link bitcode files {:?}", self.bitcode);
125+
}
126+
127+
Ok(())
128+
}
129+
130+
/// Optimize and compile to native format using `opt` and `llc`
131+
///
132+
/// Before this can be called `link` needs to be called
133+
fn optimize(
134+
&mut self,
135+
optimization: Optimization,
136+
mut internalize: bool,
137+
mut debug: bool,
138+
) -> anyhow::Result<()> {
139+
let mut passes = format!("default<{}>", optimization);
140+
141+
// FIXME(@kjetilkjeka) The whole corelib currently cannot be compiled for nvptx64 so everything relies on not using the troublesome symbols and removing them during linking
142+
if !internalize && self.target == crate::Target::Nvptx64NvidiaCuda {
143+
tracing::warn!("nvptx64 target detected - internalizing symbols");
144+
internalize = true;
145+
}
146+
147+
// FIXME(@kjetilkjeka) Debug symbol generation is broken for nvptx64 so we must remove them even in debug mode
148+
if debug && self.target == crate::Target::Nvptx64NvidiaCuda {
149+
tracing::warn!("nvptx64 target detected - stripping debug symbols");
150+
debug = false;
151+
}
152+
153+
if internalize {
154+
passes.push_str(",internalize,globaldce");
155+
let symbol_file_content = self.symbols.iter().fold(String::new(), |s, x| s + &x + "\n");
156+
std::fs::write(&self.sym_path, symbol_file_content)
157+
.context(format!("Failed to write symbol file: {}", self.sym_path.display()))?;
158+
}
159+
160+
tracing::info!("optimizing bitcode with passes: {}", passes);
161+
let mut opt_cmd = std::process::Command::new("opt");
162+
opt_cmd
163+
.arg(&self.link_path)
164+
.arg("-o")
165+
.arg(&self.opt_path)
166+
.arg(format!("--internalize-public-api-file={}", self.sym_path.display()))
167+
.arg(format!("--passes={}", passes));
168+
169+
if !debug {
170+
opt_cmd.arg("--strip-debug");
171+
}
172+
173+
let opt_output = opt_cmd.output().unwrap();
174+
175+
if !opt_output.status.success() {
176+
tracing::error!(
177+
"opt returned with Exit status: {}\n stdout: {}\n stderr: {}",
178+
opt_output.status,
179+
String::from_utf8(opt_output.stdout).unwrap(),
180+
String::from_utf8(opt_output.stderr).unwrap(),
181+
);
182+
anyhow::bail!("opt failed optimize bitcode: {}", self.link_path.display());
183+
};
184+
185+
Ok(())
186+
}
187+
188+
/// Compile to native format using `llc`
189+
///
190+
/// Before this can be called `optimize` needs to be called
191+
fn compile(&mut self) -> anyhow::Result<()> {
192+
let mut lcc_command = std::process::Command::new("llc");
193+
194+
if let Some(mcpu) = &self.cpu {
195+
lcc_command.arg("--mcpu").arg(mcpu);
196+
}
197+
198+
let lcc_output =
199+
lcc_command.arg(&self.opt_path).arg("-o").arg(&self.out_path).output().unwrap();
200+
201+
if !lcc_output.status.success() {
202+
tracing::error!(
203+
"llc returned with Exit status: {}\n stdout: {}\n stderr: {}",
204+
lcc_output.status,
205+
String::from_utf8(lcc_output.stdout).unwrap(),
206+
String::from_utf8(lcc_output.stderr).unwrap(),
207+
);
208+
209+
anyhow::bail!(
210+
"llc failed to compile {} into {}",
211+
self.opt_path.display(),
212+
self.out_path.display()
213+
);
214+
}
215+
216+
Ok(())
217+
}
218+
219+
/// Links, optimizes and compiles to the native format
220+
pub fn lto(
221+
&mut self,
222+
optimization: crate::Optimization,
223+
internalize: bool,
224+
debug: bool,
225+
) -> anyhow::Result<()> {
226+
self.link()?;
227+
self.optimize(optimization, internalize, debug)?;
228+
self.compile()
229+
}
230+
}

0 commit comments

Comments
 (0)