|  | //! 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 | 
|  | } | 
|  | }) | 
|  | .collect::<Vec<_>>(); | 
|  |  | 
|  | if profile_files.is_empty() { | 
|  | anyhow::bail!( | 
|  | "rust-analyzer analysis-stats produced no pgo files. This is a bug in rust-analyzer; please file an issue." | 
|  | ); | 
|  | } | 
|  |  | 
|  | 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())) | 
|  | } |