Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'xtask/src/pgo.rs')
| -rw-r--r-- | xtask/src/pgo.rs | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/xtask/src/pgo.rs b/xtask/src/pgo.rs new file mode 100644 index 0000000000..7f7b3311d9 --- /dev/null +++ b/xtask/src/pgo.rs @@ -0,0 +1,105 @@ +//! PGO (Profile-Guided Optimization) utilities. + +use anyhow::Context; +use std::env::consts::EXE_EXTENSION; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use xshell::{Cmd, Shell, cmd}; + +use crate::flags::PgoTrainingCrate; + +/// Decorates `ra_build_cmd` to add PGO instrumentation, and then runs the PGO instrumented +/// Rust Analyzer on itself to gather a PGO profile. +pub(crate) fn gather_pgo_profile<'a>( + sh: &'a Shell, + ra_build_cmd: Cmd<'a>, + target: &str, + train_crate: PgoTrainingCrate, +) -> anyhow::Result<PathBuf> { + let pgo_dir = std::path::absolute("rust-analyzer-pgo")?; + // Clear out any stale profiles + if pgo_dir.is_dir() { + std::fs::remove_dir_all(&pgo_dir)?; + } + std::fs::create_dir_all(&pgo_dir)?; + + // Figure out a path to `llvm-profdata` + let target_libdir = cmd!(sh, "rustc --print=target-libdir") + .read() + .context("cannot resolve target-libdir from rustc")?; + let target_bindir = PathBuf::from(target_libdir).parent().unwrap().join("bin"); + let llvm_profdata = target_bindir.join("llvm-profdata").with_extension(EXE_EXTENSION); + + // Build RA with PGO instrumentation + let cmd_gather = + ra_build_cmd.env("RUSTFLAGS", format!("-Cprofile-generate={}", pgo_dir.to_str().unwrap())); + cmd_gather.run().context("cannot build rust-analyzer with PGO instrumentation")?; + + let (train_path, label) = match &train_crate { + PgoTrainingCrate::RustAnalyzer => (PathBuf::from("."), "itself"), + PgoTrainingCrate::GitHub(repo) => { + (download_crate_for_training(sh, &pgo_dir, repo)?, repo.as_str()) + } + }; + + // Run RA either on itself or on a downloaded crate + eprintln!("Training RA on {label}..."); + cmd!( + sh, + "target/{target}/release/rust-analyzer analysis-stats -q --run-all-ide-things {train_path}" + ) + .run() + .context("cannot generate PGO profiles")?; + + // Merge profiles into a single file + let merged_profile = pgo_dir.join("merged.profdata"); + let profile_files = std::fs::read_dir(pgo_dir)?.filter_map(|entry| { + let entry = entry.ok()?; + if entry.path().extension() == Some(OsStr::new("profraw")) { + Some(entry.path().to_str().unwrap().to_owned()) + } else { + None + } + }); + cmd!(sh, "{llvm_profdata} merge {profile_files...} -o {merged_profile}").run().context( + "cannot merge PGO profiles. Do you have the rustup `llvm-tools` component installed?", + )?; + + Ok(merged_profile) +} + +/// Downloads a crate from GitHub, stores it into `pgo_dir` and returns a path to it. +fn download_crate_for_training(sh: &Shell, pgo_dir: &Path, repo: &str) -> anyhow::Result<PathBuf> { + let mut it = repo.splitn(2, '@'); + let repo = it.next().unwrap(); + let revision = it.next(); + + // FIXME: switch to `--revision` here around 2035 or so + let revision = + if let Some(revision) = revision { &["--branch", revision] as &[&str] } else { &[] }; + + let normalized_path = repo.replace("/", "-"); + let target_path = pgo_dir.join(normalized_path); + cmd!(sh, "git clone --depth 1 https://github.com/{repo} {revision...} {target_path}") + .run() + .with_context(|| "cannot download PGO training crate from {repo}")?; + + Ok(target_path) +} + +/// Helper function to create a build command for rust-analyzer +pub(crate) fn build_command<'a>( + sh: &'a Shell, + command: &str, + target_name: &str, + features: &[&str], +) -> Cmd<'a> { + cmd!( + sh, + "cargo {command} --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --target {target_name} {features...} --release" + ) +} + +pub(crate) fn apply_pgo_to_cmd<'a>(cmd: Cmd<'a>, profile_path: &Path) -> Cmd<'a> { + cmd.env("RUSTFLAGS", format!("-Cprofile-use={}", profile_path.to_str().unwrap())) +} |