Skip to content

Commit 9f5b884

Browse files
authored
Merge pull request #19685 from fbernier/pgo-install
Add PGO support to install
2 parents 26c7960 + e2c48d3 commit 9f5b884

File tree

6 files changed

+175
-132
lines changed

6 files changed

+175
-132
lines changed

xtask/src/dist.rs

+7-102
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
use anyhow::Context;
22
use flate2::{Compression, write::GzEncoder};
3-
use std::env::consts::EXE_EXTENSION;
4-
use std::ffi::OsStr;
53
use std::{
6-
env,
74
fs::File,
85
io::{self, BufWriter},
96
path::{Path, PathBuf},
@@ -12,11 +9,11 @@ use time::OffsetDateTime;
129
use xshell::{Cmd, Shell, cmd};
1310
use zip::{DateTime, ZipWriter, write::SimpleFileOptions};
1411

15-
use crate::flags::PgoTrainingCrate;
1612
use crate::{
1713
date_iso,
18-
flags::{self, Malloc},
14+
flags::{self, Malloc, PgoTrainingCrate},
1915
project_root,
16+
util::detect_target,
2017
};
2118

2219
const VERSION_STABLE: &str = "0.3";
@@ -28,7 +25,7 @@ impl flags::Dist {
2825
let stable = sh.var("GITHUB_REF").unwrap_or_default().as_str() == "refs/heads/release";
2926

3027
let project_root = project_root();
31-
let target = Target::get(&project_root);
28+
let target = Target::get(&project_root, sh);
3229
let allocator = self.allocator();
3330
let dist = project_root.join("dist");
3431
sh.remove_path(&dist)?;
@@ -113,9 +110,9 @@ fn dist_server(
113110
let command = if linux_target && zig { "zigbuild" } else { "build" };
114111

115112
let pgo_profile = if let Some(train_crate) = pgo {
116-
Some(gather_pgo_profile(
113+
Some(crate::pgo::gather_pgo_profile(
117114
sh,
118-
build_command(sh, command, &target_name, features),
115+
crate::pgo::build_command(sh, command, &target_name, features),
119116
&target_name,
120117
train_crate,
121118
)?)
@@ -151,85 +148,6 @@ fn build_command<'a>(
151148
)
152149
}
153150

154-
/// Decorates `ra_build_cmd` to add PGO instrumentation, and then runs the PGO instrumented
155-
/// Rust Analyzer on itself to gather a PGO profile.
156-
fn gather_pgo_profile<'a>(
157-
sh: &'a Shell,
158-
ra_build_cmd: Cmd<'a>,
159-
target: &str,
160-
train_crate: PgoTrainingCrate,
161-
) -> anyhow::Result<PathBuf> {
162-
let pgo_dir = std::path::absolute("rust-analyzer-pgo")?;
163-
// Clear out any stale profiles
164-
if pgo_dir.is_dir() {
165-
std::fs::remove_dir_all(&pgo_dir)?;
166-
}
167-
std::fs::create_dir_all(&pgo_dir)?;
168-
169-
// Figure out a path to `llvm-profdata`
170-
let target_libdir = cmd!(sh, "rustc --print=target-libdir")
171-
.read()
172-
.context("cannot resolve target-libdir from rustc")?;
173-
let target_bindir = PathBuf::from(target_libdir).parent().unwrap().join("bin");
174-
let llvm_profdata = target_bindir.join("llvm-profdata").with_extension(EXE_EXTENSION);
175-
176-
// Build RA with PGO instrumentation
177-
let cmd_gather =
178-
ra_build_cmd.env("RUSTFLAGS", format!("-Cprofile-generate={}", pgo_dir.to_str().unwrap()));
179-
cmd_gather.run().context("cannot build rust-analyzer with PGO instrumentation")?;
180-
181-
let (train_path, label) = match &train_crate {
182-
PgoTrainingCrate::RustAnalyzer => (PathBuf::from("."), "itself"),
183-
PgoTrainingCrate::GitHub(repo) => {
184-
(download_crate_for_training(sh, &pgo_dir, repo)?, repo.as_str())
185-
}
186-
};
187-
188-
// Run RA either on itself or on a downloaded crate
189-
eprintln!("Training RA on {label}...");
190-
cmd!(
191-
sh,
192-
"target/{target}/release/rust-analyzer analysis-stats -q --run-all-ide-things {train_path}"
193-
)
194-
.run()
195-
.context("cannot generate PGO profiles")?;
196-
197-
// Merge profiles into a single file
198-
let merged_profile = pgo_dir.join("merged.profdata");
199-
let profile_files = std::fs::read_dir(pgo_dir)?.filter_map(|entry| {
200-
let entry = entry.ok()?;
201-
if entry.path().extension() == Some(OsStr::new("profraw")) {
202-
Some(entry.path().to_str().unwrap().to_owned())
203-
} else {
204-
None
205-
}
206-
});
207-
cmd!(sh, "{llvm_profdata} merge {profile_files...} -o {merged_profile}").run().context(
208-
"cannot merge PGO profiles. Do you have the rustup `llvm-tools` component installed?",
209-
)?;
210-
211-
Ok(merged_profile)
212-
}
213-
214-
/// Downloads a crate from GitHub, stores it into `pgo_dir` and returns a path to it.
215-
fn download_crate_for_training(sh: &Shell, pgo_dir: &Path, repo: &str) -> anyhow::Result<PathBuf> {
216-
let mut it = repo.splitn(2, '@');
217-
let repo = it.next().unwrap();
218-
let revision = it.next();
219-
220-
// FIXME: switch to `--revision` here around 2035 or so
221-
let revision =
222-
if let Some(revision) = revision { &["--branch", revision] as &[&str] } else { &[] };
223-
224-
let normalized_path = repo.replace("/", "-");
225-
let target_path = pgo_dir.join(normalized_path);
226-
cmd!(sh, "git clone --depth 1 https://github.com/{repo} {revision...} {target_path}")
227-
.run()
228-
.with_context(|| "cannot download PGO training crate from {repo}")?;
229-
230-
Ok(target_path)
231-
}
232-
233151
fn gzip(src_path: &Path, dest_path: &Path) -> anyhow::Result<()> {
234152
let mut encoder = GzEncoder::new(File::create(dest_path)?, Compression::best());
235153
let mut input = io::BufReader::new(File::open(src_path)?);
@@ -283,21 +201,8 @@ struct Target {
283201
}
284202

285203
impl Target {
286-
fn get(project_root: &Path) -> Self {
287-
let name = match env::var("RA_TARGET") {
288-
Ok(target) => target,
289-
_ => {
290-
if cfg!(target_os = "linux") {
291-
"x86_64-unknown-linux-gnu".to_owned()
292-
} else if cfg!(target_os = "windows") {
293-
"x86_64-pc-windows-msvc".to_owned()
294-
} else if cfg!(target_os = "macos") {
295-
"x86_64-apple-darwin".to_owned()
296-
} else {
297-
panic!("Unsupported OS, maybe try setting RA_TARGET")
298-
}
299-
}
300-
};
204+
fn get(project_root: &Path, sh: &Shell) -> Self {
205+
let name = detect_target(sh);
301206
let (name, libc_suffix) = match name.split_once('.') {
302207
Some((l, r)) => (l.to_owned(), Some(r.to_owned())),
303208
None => (name, None),

xtask/src/flags.rs

+28-24
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@ use std::{fmt, str::FromStr};
44

55
use crate::install::{ClientOpt, ProcMacroServerOpt, ServerOpt};
66

7+
#[derive(Debug, Clone)]
8+
pub enum PgoTrainingCrate {
9+
// Use RA's own sources for PGO training
10+
RustAnalyzer,
11+
// Download a Rust crate from `https://github.com/{0}` and use it for PGO training.
12+
GitHub(String),
13+
}
14+
15+
impl FromStr for PgoTrainingCrate {
16+
type Err = String;
17+
18+
fn from_str(s: &str) -> Result<Self, Self::Err> {
19+
match s {
20+
"rust-analyzer" => Ok(Self::RustAnalyzer),
21+
url => Ok(Self::GitHub(url.to_owned())),
22+
}
23+
}
24+
}
25+
726
xflags::xflags! {
827
src "./src/flags.rs"
928

@@ -29,6 +48,9 @@ xflags::xflags! {
2948

3049
/// build in release with debug info set to 2.
3150
optional --dev-rel
51+
52+
/// Apply PGO optimizations
53+
optional --pgo pgo: PgoTrainingCrate
3254
}
3355

3456
cmd fuzz-tests {}
@@ -109,18 +131,16 @@ pub enum XtaskCmd {
109131
Tidy(Tidy),
110132
}
111133

112-
#[derive(Debug)]
113-
pub struct Tidy {}
114-
115134
#[derive(Debug)]
116135
pub struct Install {
117136
pub client: bool,
118137
pub code_bin: Option<String>,
119138
pub server: bool,
120-
pub proc_macro_server: bool,
121139
pub mimalloc: bool,
122140
pub jemalloc: bool,
141+
pub proc_macro_server: bool,
123142
pub dev_rel: bool,
143+
pub pgo: Option<PgoTrainingCrate>,
124144
}
125145

126146
#[derive(Debug)]
@@ -143,25 +163,6 @@ pub struct RustcPush {
143163
pub branch: Option<String>,
144164
}
145165

146-
#[derive(Debug)]
147-
pub enum PgoTrainingCrate {
148-
// Use RA's own sources for PGO training
149-
RustAnalyzer,
150-
// Download a Rust crate from `https://github.com/{0}` and use it for PGO training.
151-
GitHub(String),
152-
}
153-
154-
impl FromStr for PgoTrainingCrate {
155-
type Err = String;
156-
157-
fn from_str(s: &str) -> Result<Self, Self::Err> {
158-
match s {
159-
"rust-analyzer" => Ok(Self::RustAnalyzer),
160-
url => Ok(Self::GitHub(url.to_owned())),
161-
}
162-
}
163-
}
164-
165166
#[derive(Debug)]
166167
pub struct Dist {
167168
pub mimalloc: bool,
@@ -195,6 +196,9 @@ pub struct Codegen {
195196
pub check: bool,
196197
}
197198

199+
#[derive(Debug)]
200+
pub struct Tidy;
201+
198202
impl Xtask {
199203
#[allow(dead_code)]
200204
pub fn from_env_or_exit() -> Self {
@@ -324,7 +328,7 @@ impl Install {
324328
} else {
325329
Malloc::System
326330
};
327-
Some(ServerOpt { malloc, dev_rel: self.dev_rel })
331+
Some(ServerOpt { malloc, dev_rel: self.dev_rel, pgo: self.pgo.clone() })
328332
}
329333
pub(crate) fn proc_macro_server(&self) -> Option<ProcMacroServerOpt> {
330334
if !self.proc_macro_server {

xtask/src/install.rs

+22-6
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use std::{env, path::PathBuf, str};
55
use anyhow::{Context, bail, format_err};
66
use xshell::{Shell, cmd};
77

8-
use crate::flags::{self, Malloc};
8+
use crate::{
9+
flags::{self, Malloc, PgoTrainingCrate},
10+
util::detect_target,
11+
};
912

1013
impl flags::Install {
1114
pub(crate) fn run(self, sh: &Shell) -> anyhow::Result<()> {
@@ -35,6 +38,7 @@ const VS_CODES: &[&str] = &["code", "code-exploration", "code-insiders", "codium
3538
pub(crate) struct ServerOpt {
3639
pub(crate) malloc: Malloc,
3740
pub(crate) dev_rel: bool,
41+
pub(crate) pgo: Option<PgoTrainingCrate>,
3842
}
3943

4044
pub(crate) struct ProcMacroServerOpt {
@@ -135,21 +139,33 @@ fn install_server(sh: &Shell, opts: ServerOpt) -> anyhow::Result<()> {
135139
let features = opts.malloc.to_features();
136140
let profile = if opts.dev_rel { "dev-rel" } else { "release" };
137141

138-
let cmd = cmd!(
142+
let mut install_cmd = cmd!(
139143
sh,
140144
"cargo install --path crates/rust-analyzer --profile={profile} --locked --force --features force-always-assert {features...}"
141145
);
142-
cmd.run()?;
146+
147+
if let Some(train_crate) = opts.pgo {
148+
let build_cmd = cmd!(
149+
sh,
150+
"cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --profile={profile} --locked --features force-always-assert {features...}"
151+
);
152+
153+
let target = detect_target(sh);
154+
let profile = crate::pgo::gather_pgo_profile(sh, build_cmd, &target, train_crate)?;
155+
install_cmd = crate::pgo::apply_pgo_to_cmd(install_cmd, &profile);
156+
}
157+
158+
install_cmd.run()?;
143159
Ok(())
144160
}
145161

146162
fn install_proc_macro_server(sh: &Shell, opts: ProcMacroServerOpt) -> anyhow::Result<()> {
147163
let profile = if opts.dev_rel { "dev-rel" } else { "release" };
148164

149-
let cmd = cmd!(
165+
cmd!(
150166
sh,
151167
"cargo +nightly install --path crates/proc-macro-srv-cli --profile={profile} --locked --force --features sysroot-abi"
152-
);
153-
cmd.run()?;
168+
).run()?;
169+
154170
Ok(())
155171
}

xtask/src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod codegen;
2222
mod dist;
2323
mod install;
2424
mod metrics;
25+
mod pgo;
2526
mod publish;
2627
mod release;
2728
mod tidy;

0 commit comments

Comments
 (0)