Skip to content
/ rust Public
forked from rust-lang/rust

Commit a16925d

Browse files
committed
Implement BOLT optimization in the opt-dist tool
1 parent 3be07c1 commit a16925d

File tree

6 files changed

+164
-24
lines changed

6 files changed

+164
-24
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -2495,6 +2495,7 @@ dependencies = [
24952495
"serde_json",
24962496
"sysinfo",
24972497
"tar",
2498+
"tempfile",
24982499
"xz",
24992500
"zip",
25002501
]

src/tools/opt-dist/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ xz = "0.1"
2020
serde = { version = "1", features = ["derive"] }
2121
serde_json = "1"
2222
glob = "0.3"
23+
tempfile = "3.5"

src/tools/opt-dist/src/bolt.rs

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use anyhow::Context;
2+
3+
use crate::exec::cmd;
4+
use crate::training::LlvmBoltProfile;
5+
use camino::{Utf8Path, Utf8PathBuf};
6+
7+
use crate::utils::io::copy_file;
8+
9+
/// Instruments an artifact at the given `path` (in-place) with BOLT and then calls `func`.
10+
/// After this function finishes, the original file will be restored.
11+
pub fn with_bolt_instrumented<F: FnOnce() -> anyhow::Result<R>, R>(
12+
path: &Utf8Path,
13+
func: F,
14+
) -> anyhow::Result<R> {
15+
// Back up the original file.
16+
// It will be restored to its original state when this function exits.
17+
// By copying it, we break any existing hard links, so that they are not affected by the
18+
// instrumentation.
19+
let _backup_file = BackedUpFile::new(path)?;
20+
21+
let instrumented_path = tempfile::NamedTempFile::new()?.into_temp_path();
22+
23+
// Instrument the original file with BOLT, saving the result into `instrumented_path`
24+
cmd(&["llvm-bolt"])
25+
.arg("-instrument")
26+
.arg(path)
27+
// Make sure that each process will write its profiles into a separate file
28+
.arg("--instrumentation-file-append-pid")
29+
.arg("-o")
30+
.arg(instrumented_path.display())
31+
.run()
32+
.with_context(|| anyhow::anyhow!("Could not instrument {path} using BOLT"))?;
33+
34+
// Copy the instrumented artifact over the original one
35+
copy_file(&instrumented_path, path)?;
36+
37+
// Run the function that will make use of the instrumented artifact.
38+
// The original file will be restored when `_backup_file` is dropped.
39+
func()
40+
}
41+
42+
/// Optimizes the file at `path` with BOLT in-place using the given `profile`.
43+
pub fn bolt_optimize(path: &Utf8Path, profile: LlvmBoltProfile) -> anyhow::Result<()> {
44+
// Copy the artifact to a new location, so that we do not use the same input and output file.
45+
// BOLT cannot handle optimizing when the input and output is the same file, because it performs
46+
// in-place patching.
47+
let temp_path = tempfile::NamedTempFile::new()?.into_temp_path();
48+
copy_file(path, &temp_path)?;
49+
50+
cmd(&["llvm-bolt"])
51+
.arg(temp_path.display())
52+
.arg("-data")
53+
.arg(&profile.0)
54+
.arg("-o")
55+
.arg(path)
56+
// Reorder basic blocks within functions
57+
.arg("-reorder-blocks=ext-tsp")
58+
// Reorder functions within the binary
59+
.arg("-reorder-functions=hfsort+")
60+
// Split function code into hot and code regions
61+
.arg("-split-functions")
62+
// Split as many basic blocks as possible
63+
.arg("-split-all-cold")
64+
// Move jump tables to a separate section
65+
.arg("-jump-tables=move")
66+
// Fold functions with identical code
67+
.arg("-icf=1")
68+
// Try to reuse old text segments to reduce binary size
69+
.arg("--use-old-text")
70+
// Update DWARF debug info in the final binary
71+
.arg("-update-debug-sections")
72+
// Print optimization statistics
73+
.arg("-dyno-stats")
74+
.run()
75+
.with_context(|| anyhow::anyhow!("Could not optimize {path} with BOLT"))?;
76+
77+
Ok(())
78+
}
79+
80+
/// Copies a file to a temporary location and restores it (copies it back) when it is dropped.
81+
pub struct BackedUpFile {
82+
original: Utf8PathBuf,
83+
backup: tempfile::TempPath,
84+
}
85+
86+
impl BackedUpFile {
87+
pub fn new(file: &Utf8Path) -> anyhow::Result<Self> {
88+
let temp_path = tempfile::NamedTempFile::new()?.into_temp_path();
89+
copy_file(file, &temp_path)?;
90+
Ok(Self { backup: temp_path, original: file.to_path_buf() })
91+
}
92+
}
93+
94+
impl Drop for BackedUpFile {
95+
fn drop(&mut self) {
96+
copy_file(&self.backup, &self.original).expect("Cannot restore backed up file");
97+
}
98+
}

src/tools/opt-dist/src/exec.rs

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::environment::Environment;
22
use crate::metrics::{load_metrics, record_metrics};
33
use crate::timer::TimerSection;
4-
use crate::training::{LlvmBoltProfile, LlvmPGOProfile, RustcPGOProfile};
4+
use crate::training::{LlvmPGOProfile, RustcPGOProfile};
55
use camino::{Utf8Path, Utf8PathBuf};
66
use std::collections::BTreeMap;
77
use std::fs::File;
@@ -16,7 +16,7 @@ pub struct CmdBuilder {
1616
}
1717

1818
impl CmdBuilder {
19-
pub fn arg(mut self, arg: &str) -> Self {
19+
pub fn arg<S: ToString>(mut self, arg: S) -> Self {
2020
self.args.push(arg.to_string());
2121
self
2222
}
@@ -154,13 +154,8 @@ impl Bootstrap {
154154
self
155155
}
156156

157-
pub fn llvm_bolt_instrument(mut self) -> Self {
158-
self.cmd = self.cmd.arg("--llvm-bolt-profile-generate");
159-
self
160-
}
161-
162-
pub fn llvm_bolt_optimize(mut self, profile: &LlvmBoltProfile) -> Self {
163-
self.cmd = self.cmd.arg("--llvm-bolt-profile-use").arg(profile.0.as_str());
157+
pub fn with_llvm_bolt_ldflags(mut self) -> Self {
158+
self.cmd = self.cmd.arg("--set").arg("llvm.ldflags=-Wl,-q");
164159
self
165160
}
166161

src/tools/opt-dist/src/main.rs

+28-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use crate::bolt::{bolt_optimize, with_bolt_instrumented};
12
use anyhow::Context;
23
use log::LevelFilter;
4+
use utils::io;
35

46
use crate::environment::{create_environment, Environment};
57
use crate::exec::Bootstrap;
@@ -12,6 +14,7 @@ use crate::utils::{
1214
with_log_group,
1315
};
1416

17+
mod bolt;
1518
mod environment;
1619
mod exec;
1720
mod metrics;
@@ -92,41 +95,51 @@ fn execute_pipeline(
9295
Ok(profile)
9396
})?;
9497

95-
let llvm_bolt_profile = if env.supports_bolt() {
98+
if env.supports_bolt() {
9699
// Stage 3: Build BOLT instrumented LLVM
97100
// We build a PGO optimized LLVM in this step, then instrument it with BOLT and gather BOLT profiles.
98101
// Note that we don't remove LLVM artifacts after this step, so that they are reused in the final dist build.
99102
// BOLT instrumentation is performed "on-the-fly" when the LLVM library is copied to the sysroot of rustc,
100103
// therefore the LLVM artifacts on disk are not "tainted" with BOLT instrumentation and they can be reused.
101104
timer.section("Stage 3 (LLVM BOLT)", |stage| {
102-
stage.section("Build BOLT instrumented LLVM", |stage| {
105+
stage.section("Build PGO optimized LLVM", |stage| {
103106
Bootstrap::build(env)
104-
.llvm_bolt_instrument()
107+
.with_llvm_bolt_ldflags()
105108
.llvm_pgo_optimize(&llvm_pgo_profile)
106109
.avoid_rustc_rebuild()
107110
.run(stage)
108111
})?;
109112

110-
let profile = stage.section("Gather profiles", |_| gather_llvm_bolt_profiles(env))?;
113+
// Find the path to the `libLLVM.so` file
114+
let llvm_lib = io::find_file_in_dir(
115+
&env.build_artifacts().join("stage2").join("lib"),
116+
"libLLVM",
117+
".so",
118+
)?;
119+
120+
// Instrument it and gather profiles
121+
let profile = with_bolt_instrumented(&llvm_lib, || {
122+
stage.section("Gather profiles", |_| gather_llvm_bolt_profiles(env))
123+
})?;
111124
print_free_disk_space()?;
112125

113-
// LLVM is not being cleared here, we want to reuse the previous PGO-optimized build
126+
// Now optimize the library with BOLT. The `libLLVM-XXX.so` library is actually hard-linked
127+
// from several places, and this specific path (`llvm_lib`) will *not* be packaged into
128+
// the final dist build. However, when BOLT optimizes an artifact, it does so *in-place*,
129+
// therefore it will actually optimize all the hard links, which means that the final
130+
// packaged `libLLVM.so` file *will* be BOLT optimized.
131+
bolt_optimize(&llvm_lib, profile).context("Could not optimize LLVM with BOLT")?;
114132

115-
Ok(Some(profile))
116-
})?
117-
} else {
118-
None
119-
};
133+
// LLVM is not being cleared here, we want to use the BOLT-optimized LLVM
134+
Ok(())
135+
})?;
136+
}
120137

121-
let mut dist = Bootstrap::dist(env, &dist_args)
138+
let dist = Bootstrap::dist(env, &dist_args)
122139
.llvm_pgo_optimize(&llvm_pgo_profile)
123140
.rustc_pgo_optimize(&rustc_pgo_profile)
124141
.avoid_rustc_rebuild();
125142

126-
if let Some(llvm_bolt_profile) = llvm_bolt_profile {
127-
dist = dist.llvm_bolt_optimize(&llvm_bolt_profile);
128-
}
129-
130143
// Final stage: Assemble the dist artifacts
131144
// The previous PGO optimized rustc build and PGO optimized LLVM builds should be reused.
132145
timer.section("Stage 4 (final build)", |stage| dist.run(stage))?;

src/tools/opt-dist/src/utils/io.rs

+32
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::Context;
22
use camino::{Utf8Path, Utf8PathBuf};
33
use fs_extra::dir::CopyOptions;
44
use std::fs::File;
5+
use std::path::Path;
56

67
/// Delete and re-create the directory.
78
pub fn reset_directory(path: &Utf8Path) -> anyhow::Result<()> {
@@ -17,6 +18,12 @@ pub fn copy_directory(src: &Utf8Path, dst: &Utf8Path) -> anyhow::Result<()> {
1718
Ok(())
1819
}
1920

21+
pub fn copy_file<S: AsRef<Path>, D: AsRef<Path>>(src: S, dst: D) -> anyhow::Result<()> {
22+
log::info!("Copying file {} to {}", src.as_ref().display(), dst.as_ref().display());
23+
std::fs::copy(src.as_ref(), dst.as_ref())?;
24+
Ok(())
25+
}
26+
2027
#[allow(unused)]
2128
pub fn move_directory(src: &Utf8Path, dst: &Utf8Path) -> anyhow::Result<()> {
2229
log::info!("Moving directory {src} to {dst}");
@@ -60,3 +67,28 @@ pub fn get_files_from_dir(
6067
.map(|p| p.map(|p| Utf8PathBuf::from_path_buf(p).unwrap()))
6168
.collect::<Result<Vec<_>, _>>()?)
6269
}
70+
71+
/// Finds a single file in the specified `directory` with the given `prefix` and `suffix`.
72+
pub fn find_file_in_dir(
73+
directory: &Utf8Path,
74+
prefix: &str,
75+
suffix: &str,
76+
) -> anyhow::Result<Utf8PathBuf> {
77+
let mut files = glob::glob(&format!("{directory}/{prefix}*{suffix}"))?
78+
.into_iter()
79+
.collect::<Result<Vec<_>, _>>()?;
80+
match files.pop() {
81+
Some(file) => {
82+
if !files.is_empty() {
83+
files.push(file);
84+
Err(anyhow::anyhow!(
85+
"More than one file with prefix {prefix} found in {directory}: {:?}",
86+
files
87+
))
88+
} else {
89+
Ok(Utf8PathBuf::from_path_buf(file).unwrap())
90+
}
91+
}
92+
None => Err(anyhow::anyhow!("No file with prefix {prefix} found in {directory}")),
93+
}
94+
}

0 commit comments

Comments
 (0)