blob: f694c08f9b93788fc85ad8793e58d60638882ddf [file] [log] [blame]
use anyhow::Context;
use crate::exec::cmd;
use crate::training::BoltProfile;
use camino::{Utf8Path, Utf8PathBuf};
use crate::utils::io::copy_file;
/// Instruments an artifact at the given `path` (in-place) with BOLT and then calls `func`.
/// After this function finishes, the original file will be restored.
pub fn with_bolt_instrumented<F: FnOnce(&Utf8Path) -> anyhow::Result<R>, R>(
path: &Utf8Path,
func: F,
) -> anyhow::Result<R> {
// Back up the original file.
// It will be restored to its original state when this function exits.
// By copying it, we break any existing hard links, so that they are not affected by the
// instrumentation.
let _backup_file = BackedUpFile::new(path)?;
let instrumented_path = tempfile::NamedTempFile::new()?.into_temp_path();
let profile_dir =
tempfile::TempDir::new().context("Could not create directory for BOLT profiles")?;
let profile_prefix = profile_dir.path().join("prof.fdata");
let profile_prefix = Utf8Path::from_path(&profile_prefix).unwrap();
// Instrument the original file with BOLT, saving the result into `instrumented_path`
cmd(&["llvm-bolt"])
.arg("-instrument")
.arg(path)
.arg(&format!("--instrumentation-file={profile_prefix}"))
// Make sure that each process will write its profiles into a separate file
.arg("--instrumentation-file-append-pid")
.arg("-o")
.arg(instrumented_path.display())
.run()
.with_context(|| anyhow::anyhow!("Could not instrument {path} using BOLT"))?;
// Copy the instrumented artifact over the original one
copy_file(&instrumented_path, path)?;
// Run the function that will make use of the instrumented artifact.
// The original file will be restored when `_backup_file` is dropped.
func(profile_prefix)
}
/// Optimizes the file at `path` with BOLT in-place using the given `profile`.
pub fn bolt_optimize(path: &Utf8Path, profile: &BoltProfile) -> anyhow::Result<()> {
// Copy the artifact to a new location, so that we do not use the same input and output file.
// BOLT cannot handle optimizing when the input and output is the same file, because it performs
// in-place patching.
let temp_path = tempfile::NamedTempFile::new()?.into_temp_path();
copy_file(path, &temp_path)?;
cmd(&["llvm-bolt"])
.arg(temp_path.display())
.arg("-data")
.arg(&profile.0)
.arg("-o")
.arg(path)
// Reorder basic blocks within functions
.arg("-reorder-blocks=ext-tsp")
// Reorder functions within the binary
.arg("-reorder-functions=hfsort+")
// Split function code into hot and code regions
.arg("-split-functions")
// Split as many basic blocks as possible
.arg("-split-all-cold")
// Move jump tables to a separate section
.arg("-jump-tables=move")
// Fold functions with identical code
.arg("-icf=1")
// The following flag saves about 50 MiB of libLLVM.so size.
// However, it succeeds very non-deterministically. To avoid frequent artifact size swings,
// it is kept disabled for now.
// FIXME(kobzol): try to re-enable this once BOLT in-place rewriting is merged or after
// we bump LLVM.
// Try to reuse old text segments to reduce binary size
// .arg("--use-old-text")
// Update DWARF debug info in the final binary
.arg("-update-debug-sections")
// Print optimization statistics
.arg("-dyno-stats")
.run()
.with_context(|| anyhow::anyhow!("Could not optimize {path} with BOLT"))?;
Ok(())
}
/// Copies a file to a temporary location and restores it (copies it back) when it is dropped.
pub struct BackedUpFile {
original: Utf8PathBuf,
backup: tempfile::TempPath,
}
impl BackedUpFile {
pub fn new(file: &Utf8Path) -> anyhow::Result<Self> {
let temp_path = tempfile::NamedTempFile::new()?.into_temp_path();
copy_file(file, &temp_path)?;
Ok(Self { backup: temp_path, original: file.to_path_buf() })
}
}
impl Drop for BackedUpFile {
fn drop(&mut self) {
copy_file(&self.backup, &self.original).expect("Cannot restore backed up file");
}
}