From 017255b2e7f2763a613bac8a56a8bbecb07f36bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 7 Jul 2026 15:51:51 +0200 Subject: [PATCH 1/5] Add new PGO config section to bootstrap --- bootstrap.example.toml | 14 +++++ src/bootstrap/src/core/build_steps/compile.rs | 14 ++--- src/bootstrap/src/core/build_steps/dist.rs | 2 +- src/bootstrap/src/core/config/config.rs | 60 +++++++++++++++---- src/bootstrap/src/core/config/flags.rs | 8 ++- src/bootstrap/src/core/config/toml/mod.rs | 18 +++++- src/bootstrap/src/core/config/toml/pgo.rs | 30 ++++++++++ src/bootstrap/src/core/config/toml/rust.rs | 6 +- src/bootstrap/src/utils/change_tracker.rs | 5 ++ 9 files changed, 131 insertions(+), 26 deletions(-) create mode 100644 src/bootstrap/src/core/config/toml/pgo.rs diff --git a/bootstrap.example.toml b/bootstrap.example.toml index 1ca4bc9159e7c..caa342e360d34 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -973,6 +973,20 @@ #dist.vendor = if "is a tarball source" || "is a git repository" { true } else { false } +# ============================================================================= +# Profile-guided optimization options +# +# Configure the PGO profile to be used for compilation, or a path where the +# profile will be written by an instrumented binary, for individual components +# ============================================================================= +[pgo] +# Use the following profile to PGO optimize the Rust compiler. +#rustc.use = "/tmp/profiles/foo.profraw" +# Instrument the Rust compiler, so that when executed, it will gather profiles +# to this path. +#rustc.generate = "/tmp/profiles/foo.profraw" + + # ============================================================================= # Options for specific targets # diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 3216fd671c3f8..5a762867320c1 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1293,12 +1293,12 @@ pub fn rustc_cargo( cargo.rustflag("-Clink-args=-Wl,--icf=all"); } - if builder.config.rust_profile_use.is_some() && builder.config.rust_profile_generate.is_some() { - panic!("Cannot use and generate PGO profiles at the same time"); - } - let is_collecting = if let Some(path) = &builder.config.rust_profile_generate { + eprintln!("Profile: {:?}", builder.config.rust_pgo.generate_profile); + + let is_collecting = if let Some(path) = &builder.config.rust_pgo.generate_profile { if build_compiler.stage == 1 { - cargo.rustflag(&format!("-Cprofile-generate={path}")); + cargo + .rustflag(&format!("-Cprofile-generate={}", path.to_str().expect("non-UTF8 path"))); // Apparently necessary to avoid overflowing the counters during // a Cargo build profile cargo.rustflag("-Cllvm-args=-vp-counters-per-site=4"); @@ -1306,9 +1306,9 @@ pub fn rustc_cargo( } else { false } - } else if let Some(path) = &builder.config.rust_profile_use { + } else if let Some(path) = &builder.config.rust_pgo.use_profile { if build_compiler.stage == 1 { - cargo.rustflag(&format!("-Cprofile-use={path}")); + cargo.rustflag(&format!("-Cprofile-use={}", path.to_str().expect("non-UTF8 path"))); if builder.is_verbose() { cargo.rustflag("-Cllvm-args=-pgo-warn-missing-function"); } diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index 114387d963a6d..d9aa6f7c18815 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -3035,7 +3035,7 @@ impl Step for ReproducibleArtifacts { fn run(self, builder: &Builder<'_>) -> Self::Output { let mut added_anything = false; let tarball = Tarball::new(builder, "reproducible-artifacts", &self.target.triple); - if let Some(path) = builder.config.rust_profile_use.as_ref() { + if let Some(path) = builder.config.rust_pgo.use_profile.as_ref() { tarball.add_file(path, ".", FileType::Regular); added_anything = true; } diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index 267e21c599728..ddd2442802d10 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -41,6 +41,7 @@ use crate::core::config::toml::dist::Dist; use crate::core::config::toml::gcc::Gcc; use crate::core::config::toml::install::Install; use crate::core::config::toml::llvm::Llvm; +use crate::core::config::toml::pgo::{Pgo, PgoConfig}; use crate::core::config::toml::rust::{ BootstrapOverrideLld, Rust, RustOptimize, check_incompatible_options_for_ci_rustc, parse_codegen_backends, @@ -225,14 +226,13 @@ pub struct Config { pub rust_remap_debuginfo: bool, pub rust_new_symbol_mangling: Option, pub rust_annotate_moves_size_limit: Option, - pub rust_profile_use: Option, - pub rust_profile_generate: Option, pub rust_lto: RustcLto, pub rust_validate_mir_opts: Option, pub rust_std_features: BTreeSet, pub rust_break_on_ice: bool, pub rust_parallel_frontend_threads: Option, pub rust_rustflags: Vec, + pub rust_pgo: PgoConfig, pub llvm_profile_use: Option, pub llvm_profile_generate: bool, @@ -456,8 +456,20 @@ impl Config { // Now load the TOML config, as soon as possible let (mut toml, toml_path) = load_toml_config(&src, flags_config, &get_toml); - postprocess_toml(&mut toml, &src, toml_path.clone(), &exec_ctx, &flags_set, &get_toml); + let TomlConfig { + change_id: toml_change_id, + build: toml_build, + install: toml_install, + llvm: toml_llvm, + gcc: toml_gcc, + rust: toml_rust, + target: toml_target, + dist: toml_dist, + pgo: toml_pgo, + profile: _, + include: _, + } = toml; // Now override TOML values with flags, to make sure that we won't later override flags with // TOML values by accident instead, because flags have higher priority. @@ -523,7 +535,7 @@ impl Config { ccache: build_ccache, exclude: build_exclude, compiletest_allow_stage0: build_compiletest_allow_stage0, - } = toml.build.unwrap_or_default(); + } = toml_build.unwrap_or_default(); let Install { prefix: install_prefix, @@ -533,7 +545,7 @@ impl Config { libdir: install_libdir, mandir: install_mandir, datadir: install_datadir, - } = toml.install.unwrap_or_default(); + } = toml_install.unwrap_or_default(); let Rust { optimize: rust_optimize, @@ -595,7 +607,7 @@ impl Config { std_features: rust_std_features, break_on_ice: rust_break_on_ice, rustflags: rust_rustflags, - } = toml.rust.unwrap_or_default(); + } = toml_rust.unwrap_or_default(); let Llvm { optimize: llvm_optimize, @@ -627,7 +639,7 @@ impl Config { enable_warnings: llvm_enable_warnings, download_ci_llvm: llvm_download_ci_llvm, build_config: llvm_build_config, - } = toml.llvm.unwrap_or_default(); + } = toml_llvm.unwrap_or_default(); let Dist { sign_folder: dist_sign_folder, @@ -637,12 +649,35 @@ impl Config { compression_profile: dist_compression_profile, include_mingw_linker: dist_include_mingw_linker, vendor: dist_vendor, - } = toml.dist.unwrap_or_default(); + } = toml_dist.unwrap_or_default(); let Gcc { download_ci_gcc: gcc_download_ci_gcc, libgccjit_libs_dir: gcc_libgccjit_libs_dir, - } = toml.gcc.unwrap_or_default(); + } = toml_gcc.unwrap_or_default(); + + let Pgo { rustc: pgo_rustc } = toml_pgo.unwrap_or_default(); + let mut pgo_rustc = pgo_rustc.unwrap_or_default(); + + // Backcompat: flags have priority over config + if flags_rust_profile_use.is_some() || flags_rust_profile_generate.is_some() { + eprintln!( + "WARNING: the `--rust-profile-generate` and `--rust-profile-use` flags have been deprecated. Configure PGO through the config file instead, in the [pgo.rustc] section." + ); + } + if rust_profile_use.is_some() || rust_profile_generate.is_some() { + eprintln!( + "WARNING: the `rust.profile-generate` and `rust.profile-use` config options have been deprecated. Configure PGO through the config file instead, in the [pgo.rustc] section." + ); + } + + pgo_rustc.use_profile = + flags_rust_profile_use.or(pgo_rustc.use_profile).or(rust_profile_use); + pgo_rustc.generate_profile = + flags_rust_profile_generate.or(pgo_rustc.generate_profile).or(rust_profile_generate); + if pgo_rustc.use_profile.is_some() && pgo_rustc.generate_profile.is_some() { + panic!("Cannot use and generate rust PGO profiles at the same time"); + } if rust_bootstrap_override_lld.is_some() && rust_bootstrap_override_lld_legacy.is_some() { panic!( @@ -877,7 +912,7 @@ impl Config { // Linux targets for which the user explicitly overrode the used linker let mut targets_with_user_linker_override = HashSet::new(); - if let Some(t) = toml.target { + if let Some(t) = toml_target { for (triple, cfg) in t { let TomlTarget { cc: target_cc, @@ -1337,7 +1372,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to cargo_info, cargo_native_static: build_cargo_native_static.unwrap_or(false), ccache, - change_id: toml.change_id.inner, + change_id: toml_change_id.inner, channel, ci_env, clippy_info, @@ -1496,8 +1531,7 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to .or(rust_overflow_checks) .unwrap_or(rust_debug == Some(true)), rust_parallel_frontend_threads: rust_parallel_frontend_threads.map(threads_from_config), - rust_profile_generate: flags_rust_profile_generate.or(rust_profile_generate), - rust_profile_use: flags_rust_profile_use.or(rust_profile_use), + rust_pgo: pgo_rustc, rust_randomize_layout: rust_randomize_layout.unwrap_or(false), rust_remap_debuginfo: rust_remap_debuginfo.unwrap_or(false), rust_rpath: rust_rpath.unwrap_or(true), diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index bf171f1de34e0..d6020262e0579 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -153,11 +153,14 @@ pub struct Flags { /// generate PGO profile with rustc build #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] - pub rust_profile_generate: Option, + // FIXME: Remove this option at the end of 2026 + pub rust_profile_generate: Option, /// use PGO profile for rustc build + // FIXME: Remove this option at the end of 2026 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] - pub rust_profile_use: Option, + pub rust_profile_use: Option, /// use PGO profile for LLVM build + // FIXME: Remove this option at the end of 2026 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] pub llvm_profile_use: Option, // LLVM doesn't support a custom location for generating profile @@ -165,6 +168,7 @@ pub struct Flags { // // llvm_out/build/profiles/ is the location this writes to. /// generate PGO profile with llvm built for rustc + // FIXME: Remove this option at the end of 2026 #[arg(global = true, long)] pub llvm_profile_generate: bool, /// Enable BOLT link flags diff --git a/src/bootstrap/src/core/config/toml/mod.rs b/src/bootstrap/src/core/config/toml/mod.rs index f6dc5b67e1010..74c21617746bf 100644 --- a/src/bootstrap/src/core/config/toml/mod.rs +++ b/src/bootstrap/src/core/config/toml/mod.rs @@ -15,6 +15,7 @@ pub mod dist; pub mod gcc; pub mod install; pub mod llvm; +pub mod pgo; pub mod rust; pub mod target; @@ -27,6 +28,7 @@ use llvm::Llvm; use rust::Rust; use target::TomlTarget; +use crate::core::config::toml::pgo::Pgo; use crate::core::config::{Merge, ReplaceOpt}; use crate::{Config, HashMap, HashSet, Path, PathBuf, exit, fs, t}; @@ -47,6 +49,7 @@ pub(crate) struct TomlConfig { pub(super) rust: Option, pub(super) target: Option>, pub(super) dist: Option, + pub(super) pgo: Option, pub(super) profile: Option, pub(super) include: Option>, } @@ -56,7 +59,19 @@ impl Merge for TomlConfig { &mut self, parent_config_path: Option, included_extensions: &mut HashSet, - TomlConfig { build, install, llvm, gcc, rust, dist, target, profile, change_id, include }: Self, + TomlConfig { + build, + install, + llvm, + gcc, + rust, + dist, + target, + pgo, + profile, + change_id, + include, + }: Self, replace: ReplaceOpt, ) { fn do_merge(x: &mut Option, y: Option, replace: ReplaceOpt) { @@ -78,6 +93,7 @@ impl Merge for TomlConfig { do_merge(&mut self.gcc, gcc, replace); do_merge(&mut self.rust, rust, replace); do_merge(&mut self.dist, dist, replace); + do_merge(&mut self.pgo, pgo, replace); match (self.target.as_mut(), target) { (_, None) => {} diff --git a/src/bootstrap/src/core/config/toml/pgo.rs b/src/bootstrap/src/core/config/toml/pgo.rs new file mode 100644 index 0000000000000..dde76f078fc39 --- /dev/null +++ b/src/bootstrap/src/core/config/toml/pgo.rs @@ -0,0 +1,30 @@ +//! This module defines the `Pgo` struct, which represents the `[pgo]` table +//! in the `bootstrap.toml` configuration file. +//! +//! The `[pgo]` table contains options related PGO (Profile-Guided Optimization) of various +//! components built by bootstrap. + +use serde::{Deserialize, Deserializer}; + +use crate::core::config::Merge; +use crate::core::config::toml::ReplaceOpt; +use crate::{HashSet, PathBuf, define_config, exit}; + +#[derive(Clone, Default, Debug, serde_derive::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PgoConfig { + /// Use the given PGO profile to optimize a component. + #[serde(default, rename = "use")] + pub use_profile: Option, + /// Build a component with PGO instrumentation. Once executed, the profiles will be stored + /// into this path. + #[serde(default, rename = "generate")] + pub generate_profile: Option, +} + +define_config! { + #[derive(Default)] + struct Pgo { + rustc: Option = "rustc", + } +} diff --git a/src/bootstrap/src/core/config/toml/rust.rs b/src/bootstrap/src/core/config/toml/rust.rs index 2facf839521d3..5c0fd37e46c2e 100644 --- a/src/bootstrap/src/core/config/toml/rust.rs +++ b/src/bootstrap/src/core/config/toml/rust.rs @@ -64,8 +64,10 @@ define_config! { ehcont_guard: Option = "ehcont-guard", new_symbol_mangling: Option = "new-symbol-mangling", annotate_moves_size_limit: Option = "annotate-moves-size-limit", - profile_generate: Option = "profile-generate", - profile_use: Option = "profile-use", + // FIXME: Remove this option at the end of 2026 + profile_generate: Option = "profile-generate", + // FIXME: Remove this option at the end of 2026 + profile_use: Option = "profile-use", // ignored; this is set from an env var set by bootstrap.py download_rustc: Option = "download-rustc", lto: Option = "lto", diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs index b34c1b55700f8..94a7acf30d4e9 100644 --- a/src/bootstrap/src/utils/change_tracker.rs +++ b/src/bootstrap/src/utils/change_tracker.rs @@ -636,4 +636,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[ severity: ChangeSeverity::Info, summary: "New option `rust.compress-debuginfo` allows configuring whether Rust and C/C++ debuginfo should be compressed.", }, + ChangeInfo { + change_id: 999999, + severity: ChangeSeverity::Info, + summary: "New config section `pgo` was introduced, to configure PGO profiling options. The `--rust-profile-use`/`--rust-profile-generate`/`--llvm-profile-use`/`--llvm-profile-generate` flags and the `rust.profile-use`/`rust.profile-generate` config options have been deprecated.", + }, ]; From 33da2c897239a35693804f91e3697cf481d170d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 7 Jul 2026 16:04:32 +0200 Subject: [PATCH 2/5] Port LLVM PGO to the new PGO config section --- bootstrap.example.toml | 6 ++- src/bootstrap/src/core/build_steps/compile.rs | 2 +- src/bootstrap/src/core/build_steps/dist.rs | 2 +- src/bootstrap/src/core/build_steps/llvm.rs | 15 +++--- src/bootstrap/src/core/config/config.rs | 46 ++++++++++++++++--- src/bootstrap/src/core/config/flags.rs | 2 +- src/bootstrap/src/core/config/toml/pgo.rs | 1 + 7 files changed, 58 insertions(+), 16 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index caa342e360d34..b48f7ad7da742 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -985,7 +985,11 @@ # Instrument the Rust compiler, so that when executed, it will gather profiles # to this path. #rustc.generate = "/tmp/profiles/foo.profraw" - +# Use the following profile to PGO optimize LLVM. +#llvm.use = "/tmp/profiles/foo.profraw" +# Instrument LLVM, so that when executed, it will gather profiles +# Note: for LLVM specifically, this should be a directory, not a file path. +#llvm.generate = "/tmp/profiles" # ============================================================================= # Options for specific targets diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 5a762867320c1..3ef6428b2f746 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1464,7 +1464,7 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect // found. This is to avoid the linker errors about undefined references to // `__llvm_profile_instrument_memop` when linking `rustc_driver`. let mut llvm_linker_flags = String::new(); - if builder.config.llvm_profile_generate + if builder.config.llvm_pgo.generate_profile.is_some() && target.is_msvc() && let Some(ref clang_cl_path) = builder.config.llvm_clang_cl { diff --git a/src/bootstrap/src/core/build_steps/dist.rs b/src/bootstrap/src/core/build_steps/dist.rs index d9aa6f7c18815..fdb13d3ca6a34 100644 --- a/src/bootstrap/src/core/build_steps/dist.rs +++ b/src/bootstrap/src/core/build_steps/dist.rs @@ -3039,7 +3039,7 @@ impl Step for ReproducibleArtifacts { tarball.add_file(path, ".", FileType::Regular); added_anything = true; } - if let Some(path) = builder.config.llvm_profile_use.as_ref() { + if let Some(path) = builder.config.llvm_pgo.use_profile.as_ref() { tarball.add_file(path, ".", FileType::Regular); added_anything = true; } diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs index 6f1b8532f031b..f40b384a589ba 100644 --- a/src/bootstrap/src/core/build_steps/llvm.rs +++ b/src/bootstrap/src/core/build_steps/llvm.rs @@ -19,7 +19,7 @@ use build_helper::git::PathFreshness; use crate::core::build_steps::llvm; use crate::core::builder::{Builder, RunConfig, ShouldRun, Step, StepMetadata}; -use crate::core::config::{Config, TargetSelection}; +use crate::core::config::{Config, LlvmPgoGenerationMode, TargetSelection}; use crate::utils::build_stamp::{BuildStamp, generate_smart_stamp_hash}; use crate::utils::exec::command; use crate::utils::helpers::{ @@ -369,14 +369,17 @@ impl Step for Llvm { // This flag makes sure `FileCheck` is copied in the final binaries directory. cfg.define("LLVM_INSTALL_UTILS", "ON"); - if builder.config.llvm_profile_generate { + if let Some(mode) = builder.config.llvm_pgo.generate_profile.as_ref() { cfg.define("LLVM_BUILD_INSTRUMENTED", "IR"); - if let Ok(llvm_profile_dir) = std::env::var("LLVM_PROFILE_DIR") { - cfg.define("LLVM_PROFILE_DATA_DIR", llvm_profile_dir); + match mode { + LlvmPgoGenerationMode::Implicit => {} + LlvmPgoGenerationMode::Directory(llvm_profile_dir) => { + cfg.define("LLVM_PROFILE_DATA_DIR", llvm_profile_dir); + } } cfg.define("LLVM_BUILD_RUNTIME", "No"); } - if let Some(path) = builder.config.llvm_profile_use.as_ref() { + if let Some(path) = builder.config.llvm_pgo.use_profile.as_ref() { cfg.define("LLVM_PROFDATA_FILE", path); } @@ -1326,7 +1329,7 @@ impl Step for Lld { // when doing PGO on CI, cmake or clang-cl don't automatically link clang's // profiler runtime in. In that case, we need to manually ask cmake to do it, to avoid // linking errors, much like LLVM's cmake setup does in that situation. - if builder.config.llvm_profile_generate + if builder.config.llvm_pgo.generate_profile.is_some() && target.is_msvc() && let Some(clang_cl_path) = builder.config.llvm_clang_cl.as_ref() { diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index ddd2442802d10..a76792a58252b 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -191,6 +191,7 @@ pub struct Config { pub llvm_cxxflags: Option, pub llvm_ldflags: Option, pub llvm_use_libcxx: bool, + pub llvm_pgo: LlvmPgoConfig, // gcc codegen options pub gcc_ci_mode: GccCiMode, @@ -234,8 +235,6 @@ pub struct Config { pub rust_rustflags: Vec, pub rust_pgo: PgoConfig, - pub llvm_profile_use: Option, - pub llvm_profile_generate: bool, pub llvm_libunwind_default: Option, pub enable_bolt_settings: bool, @@ -656,8 +655,7 @@ impl Config { libgccjit_libs_dir: gcc_libgccjit_libs_dir, } = toml_gcc.unwrap_or_default(); - let Pgo { rustc: pgo_rustc } = toml_pgo.unwrap_or_default(); - let mut pgo_rustc = pgo_rustc.unwrap_or_default(); + let Pgo { rustc: pgo_rustc, llvm: pgo_llvm } = toml_pgo.unwrap_or_default(); // Backcompat: flags have priority over config if flags_rust_profile_use.is_some() || flags_rust_profile_generate.is_some() { @@ -670,7 +668,13 @@ impl Config { "WARNING: the `rust.profile-generate` and `rust.profile-use` config options have been deprecated. Configure PGO through the config file instead, in the [pgo.rustc] section." ); } + if flags_llvm_profile_use.is_some() || flags_llvm_profile_generate { + eprintln!( + "WARNING: the `--llvm-profile-generate` and `--llvm-profile-use` flags have been deprecated. Configure PGO through the config file instead, in the [pgo.llvm] section." + ); + } + let mut pgo_rustc = pgo_rustc.unwrap_or_default(); pgo_rustc.use_profile = flags_rust_profile_use.or(pgo_rustc.use_profile).or(rust_profile_use); pgo_rustc.generate_profile = @@ -679,6 +683,23 @@ impl Config { panic!("Cannot use and generate rust PGO profiles at the same time"); } + let pgo_llvm = pgo_llvm.unwrap_or_default(); + let pgo_llvm = LlvmPgoConfig { + use_profile: flags_llvm_profile_use.or(pgo_llvm.use_profile), + generate_profile: if flags_llvm_profile_generate { + Some(if let Ok(llvm_profile_dir) = std::env::var("LLVM_PROFILE_DIR") { + LlvmPgoGenerationMode::Directory(PathBuf::from(llvm_profile_dir)) + } else { + LlvmPgoGenerationMode::Implicit + }) + } else { + pgo_llvm.generate_profile.map(LlvmPgoGenerationMode::Directory) + }, + }; + if pgo_llvm.use_profile.is_some() && pgo_llvm.generate_profile.is_some() { + panic!("Cannot use and generate LLVM PGO profiles at the same time"); + } + if rust_bootstrap_override_lld.is_some() && rust_bootstrap_override_lld_legacy.is_some() { panic!( "Cannot use both `rust.use-lld` and `rust.bootstrap-override-lld`. Please use only `rust.bootstrap-override-lld`" @@ -1463,10 +1484,9 @@ NOTE: Please add `--stage 2` to your command line, or if you're sure you want to ), llvm_offload: llvm_offload.unwrap_or(false), llvm_optimize: llvm_optimize.unwrap_or(true), + llvm_pgo: pgo_llvm, llvm_plugins: llvm_plugin.unwrap_or(false), llvm_polly: llvm_polly.unwrap_or(false), - llvm_profile_generate: flags_llvm_profile_generate, - llvm_profile_use: flags_llvm_profile_use, llvm_release_debuginfo: llvm_release_debuginfo.unwrap_or(false), llvm_static_stdcpp: llvm_static_libstdcpp.unwrap_or(false), llvm_targets, @@ -2064,6 +2084,20 @@ fn compute_src_directory(src_dir: Option, exec_ctx: &ExecutionContext) None } +#[derive(Clone)] +pub enum LlvmPgoGenerationMode { + /// Enable PGO instrumentation that will write profiles into a default path. + Implicit, + /// Enable PGO instrumentation that will write profiles into the specified directory. + Directory(PathBuf), +} + +#[derive(Clone)] +pub struct LlvmPgoConfig { + pub use_profile: Option, + pub generate_profile: Option, +} + /// Loads bootstrap TOML config and returns the config together with a path from where /// it was loaded. /// `src` is the source root directory, and `config_path` is an optionally provided path to the diff --git a/src/bootstrap/src/core/config/flags.rs b/src/bootstrap/src/core/config/flags.rs index d6020262e0579..775de587b3e54 100644 --- a/src/bootstrap/src/core/config/flags.rs +++ b/src/bootstrap/src/core/config/flags.rs @@ -162,7 +162,7 @@ pub struct Flags { /// use PGO profile for LLVM build // FIXME: Remove this option at the end of 2026 #[arg(global = true, value_hint = clap::ValueHint::FilePath, long, value_name = "PROFILE")] - pub llvm_profile_use: Option, + pub llvm_profile_use: Option, // LLVM doesn't support a custom location for generating profile // information. // diff --git a/src/bootstrap/src/core/config/toml/pgo.rs b/src/bootstrap/src/core/config/toml/pgo.rs index dde76f078fc39..b845f3c182b53 100644 --- a/src/bootstrap/src/core/config/toml/pgo.rs +++ b/src/bootstrap/src/core/config/toml/pgo.rs @@ -26,5 +26,6 @@ define_config! { #[derive(Default)] struct Pgo { rustc: Option = "rustc", + llvm: Option = "llvm", } } From b576701d43c6ac88af39c9bd31e6ae136957af54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 7 Jul 2026 16:13:00 +0200 Subject: [PATCH 3/5] Use the new bootstrap PGO options in `opt-dist` --- src/tools/opt-dist/src/exec.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs index a3935f9835956..7c6b07b8c8bb0 100644 --- a/src/tools/opt-dist/src/exec.rs +++ b/src/tools/opt-dist/src/exec.rs @@ -134,20 +134,21 @@ impl Bootstrap { pub fn llvm_pgo_instrument(mut self, profile_dir: &Utf8Path) -> Self { self.cmd = self .cmd - .arg("--llvm-profile-generate") - .env("LLVM_PROFILE_DIR", profile_dir.join("prof-%p").as_str()); + .arg("--set") + .arg(format!(r#"pgo.llvm.generate="{}""#, profile_dir.join("prof-%p").as_str())); self } pub fn llvm_pgo_optimize(mut self, profile: Option<&LlvmPGOProfile>) -> Self { if let Some(prof) = profile { - self.cmd = self.cmd.arg("--llvm-profile-use").arg(prof.0.as_str()); + self.cmd = self.cmd.arg("--set").arg(format!(r#"pgo.llvm.use="{}""#, prof.0.as_str())); } self } pub fn rustc_pgo_instrument(mut self, profile_dir: &Utf8Path) -> Self { - self.cmd = self.cmd.arg("--rust-profile-generate").arg(profile_dir.as_str()); + self.cmd = + self.cmd.arg("--set").arg(format!(r#"pgo.rustc.generate="{}""#, profile_dir.as_str())); self } @@ -162,7 +163,7 @@ impl Bootstrap { } pub fn rustc_pgo_optimize(mut self, profile: &RustcPGOProfile) -> Self { - self.cmd = self.cmd.arg("--rust-profile-use").arg(profile.0.as_str()); + self.cmd = self.cmd.arg("--set").arg(format!(r#"pgo.rustc.use="{}""#, profile.0.as_str())); self } From 1f6b1d5683f47618b29c0bc5514c6e8b571dc8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 7 Jul 2026 16:34:10 +0200 Subject: [PATCH 4/5] Fix bootstrap example TOML --- bootstrap.example.toml | 10 +++++----- src/bootstrap/src/core/build_steps/compile.rs | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bootstrap.example.toml b/bootstrap.example.toml index b48f7ad7da742..2c9a5ab1a152c 100644 --- a/bootstrap.example.toml +++ b/bootstrap.example.toml @@ -979,17 +979,17 @@ # Configure the PGO profile to be used for compilation, or a path where the # profile will be written by an instrumented binary, for individual components # ============================================================================= -[pgo] + # Use the following profile to PGO optimize the Rust compiler. -#rustc.use = "/tmp/profiles/foo.profraw" +#pgo.rustc.use = "/tmp/profiles/foo.profraw" # Instrument the Rust compiler, so that when executed, it will gather profiles # to this path. -#rustc.generate = "/tmp/profiles/foo.profraw" +#pgo.rustc.generate = "/tmp/profiles/foo.profraw" # Use the following profile to PGO optimize LLVM. -#llvm.use = "/tmp/profiles/foo.profraw" +#pgo.llvm.use = "/tmp/profiles/foo.profraw" # Instrument LLVM, so that when executed, it will gather profiles # Note: for LLVM specifically, this should be a directory, not a file path. -#llvm.generate = "/tmp/profiles" +#pgo.llvm.generate = "/tmp/profiles" # ============================================================================= # Options for specific targets diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 3ef6428b2f746..bc6d1f94d698b 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1293,8 +1293,6 @@ pub fn rustc_cargo( cargo.rustflag("-Clink-args=-Wl,--icf=all"); } - eprintln!("Profile: {:?}", builder.config.rust_pgo.generate_profile); - let is_collecting = if let Some(path) = &builder.config.rust_pgo.generate_profile { if build_compiler.stage == 1 { cargo From f25731fb5b85d5388297c7977c5c4aa765212b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 9 Jul 2026 20:17:18 +0200 Subject: [PATCH 5/5] Normalize paths on Windows --- src/tools/opt-dist/src/exec.rs | 25 +++++++++++++++++-------- src/tools/opt-dist/src/tests.rs | 8 ++++---- src/tools/opt-dist/src/utils/io.rs | 6 ++++++ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/tools/opt-dist/src/exec.rs b/src/tools/opt-dist/src/exec.rs index 7c6b07b8c8bb0..d53ec45f9f49c 100644 --- a/src/tools/opt-dist/src/exec.rs +++ b/src/tools/opt-dist/src/exec.rs @@ -8,6 +8,7 @@ use crate::environment::Environment; use crate::metrics::{load_metrics, record_metrics}; use crate::timer::TimerSection; use crate::training::{BoltProfile, LlvmPGOProfile, RustcPGOProfile}; +use crate::utils::io::normalize_path; #[derive(Default)] pub struct CmdBuilder { @@ -132,23 +133,28 @@ impl Bootstrap { } pub fn llvm_pgo_instrument(mut self, profile_dir: &Utf8Path) -> Self { - self.cmd = self - .cmd - .arg("--set") - .arg(format!(r#"pgo.llvm.generate="{}""#, profile_dir.join("prof-%p").as_str())); + self.cmd = self.cmd.arg("--set").arg(format!( + r#"pgo.llvm.generate="{}""#, + normalize_path(&profile_dir.join("prof-%p")).as_str() + )); self } pub fn llvm_pgo_optimize(mut self, profile: Option<&LlvmPGOProfile>) -> Self { if let Some(prof) = profile { - self.cmd = self.cmd.arg("--set").arg(format!(r#"pgo.llvm.use="{}""#, prof.0.as_str())); + self.cmd = self + .cmd + .arg("--set") + .arg(format!(r#"pgo.llvm.use="{}""#, normalize_path(&prof.0).as_str())); } self } pub fn rustc_pgo_instrument(mut self, profile_dir: &Utf8Path) -> Self { - self.cmd = - self.cmd.arg("--set").arg(format!(r#"pgo.rustc.generate="{}""#, profile_dir.as_str())); + self.cmd = self + .cmd + .arg("--set") + .arg(format!(r#"pgo.rustc.generate="{}""#, normalize_path(profile_dir).as_str())); self } @@ -163,7 +169,10 @@ impl Bootstrap { } pub fn rustc_pgo_optimize(mut self, profile: &RustcPGOProfile) -> Self { - self.cmd = self.cmd.arg("--set").arg(format!(r#"pgo.rustc.use="{}""#, profile.0.as_str())); + self.cmd = self + .cmd + .arg("--set") + .arg(format!(r#"pgo.rustc.use="{}""#, normalize_path(&profile.0).as_str())); self } diff --git a/src/tools/opt-dist/src/tests.rs b/src/tools/opt-dist/src/tests.rs index 1aacfeaf538e1..e0e2c487db4c5 100644 --- a/src/tools/opt-dist/src/tests.rs +++ b/src/tools/opt-dist/src/tests.rs @@ -5,7 +5,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use crate::environment::{Environment, executable_extension}; use crate::exec::cmd; -use crate::utils::io::{copy_directory, find_file_in_dir, unpack_archive}; +use crate::utils::io::{copy_directory, find_file_in_dir, normalize_path, unpack_archive}; /// Run tests on optimized dist artifacts. pub fn run_tests(env: &Environment) -> anyhow::Result<()> { @@ -94,9 +94,9 @@ compiletest-allow-stage0=true [target.{host_triple}] llvm-config = "{llvm_config}" "#, - rustc = rustc_path.to_string().replace('\\', "/"), - cargo = cargo_path.to_string().replace('\\', "/"), - llvm_config = llvm_config.to_string().replace('\\', "/") + rustc = normalize_path(&rustc_path), + cargo = normalize_path(&cargo_path), + llvm_config = normalize_path(&llvm_config) ); log::info!("Using following `bootstrap.toml` for running tests:\n{config_content}"); diff --git a/src/tools/opt-dist/src/utils/io.rs b/src/tools/opt-dist/src/utils/io.rs index ce425bdfece2c..3e3ad36cb0e90 100644 --- a/src/tools/opt-dist/src/utils/io.rs +++ b/src/tools/opt-dist/src/utils/io.rs @@ -85,3 +85,9 @@ pub fn find_file_in_dir( )), } } + +/// Normalizes a file path so that it can be used on the CLI or in bootstrap config file. +/// Replaces backslashes with forward slashes on Windows. +pub fn normalize_path(path: &Utf8Path) -> Utf8PathBuf { + path.to_string().replace('\\', "/").into() +}