Skip to content

Commit 9dd6f52

Browse files
committed
extend bootstrap for PGO on windows
When building LLVM/LLD as part of a build that asks LLVM to generate profiles, e.g. when doing PGO, cmake or clang-cl don't automatically link clang's profiler runtime in, causing undefined reference errors at link-time. We do that manually, by adding clang's resource library folder to the library search path: - for LLVM itself, by extending the linker args that `rustc_llvm`'s build script uses, to avoid the linker errors when linking `rustc_driver`. - for LLD, by extending cmake's linker flags during the LLD build step.
1 parent 94f8ee1 commit 9dd6f52

File tree

3 files changed

+72
-3
lines changed

3 files changed

+72
-3
lines changed

Diff for: src/bootstrap/compile.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use crate::config::{LlvmLibunwind, TargetSelection};
2525
use crate::dist;
2626
use crate::native;
2727
use crate::tool::SourceType;
28+
use crate::util::get_clang_cl_resource_dir;
2829
use crate::util::{exe, is_debug_info, is_dylib, output, symlink_dir, t, up_to_date};
2930
use crate::LLVM_TOOLS;
3031
use crate::{CLang, Compiler, DependencyType, GitRepo, Mode};
@@ -769,10 +770,38 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetS
769770
if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) {
770771
cargo.env("CFG_LLVM_ROOT", s);
771772
}
772-
// Some LLVM linker flags (-L and -l) may be needed to link rustc_llvm.
773+
774+
// Some LLVM linker flags (-L and -l) may be needed to link `rustc_llvm`. Its build script
775+
// expects these to be passed via the `LLVM_LINKER_FLAGS` env variable, separated by
776+
// whitespace.
777+
//
778+
// For example:
779+
// - on windows, when `clang-cl` is used with instrumentation, we need to manually add
780+
// clang's runtime library resource directory so that the profiler runtime library can be
781+
// found. This is to avoid the linker errors about undefined references to
782+
// `__llvm_profile_instrument_memop` when linking `rustc_driver`.
783+
let mut llvm_linker_flags = String::new();
784+
if builder.config.llvm_profile_generate && target.contains("msvc") {
785+
if let Some(ref clang_cl_path) = builder.config.llvm_clang_cl {
786+
// Add clang's runtime library directory to the search path
787+
let clang_rt_dir = get_clang_cl_resource_dir(clang_cl_path);
788+
llvm_linker_flags.push_str(&format!("-L{}", clang_rt_dir.display()));
789+
}
790+
}
791+
792+
// The config can also specify its own llvm linker flags.
773793
if let Some(ref s) = builder.config.llvm_ldflags {
774-
cargo.env("LLVM_LINKER_FLAGS", s);
794+
if !llvm_linker_flags.is_empty() {
795+
llvm_linker_flags.push_str(" ");
796+
}
797+
llvm_linker_flags.push_str(s);
798+
}
799+
800+
// Set the linker flags via the env var that `rustc_llvm`'s build script will read.
801+
if !llvm_linker_flags.is_empty() {
802+
cargo.env("LLVM_LINKER_FLAGS", llvm_linker_flags);
775803
}
804+
776805
// Building with a static libstdc++ is only supported on linux right now,
777806
// not for MSVC or macOS
778807
if builder.config.llvm_static_stdcpp

Diff for: src/bootstrap/native.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::process::Command;
1818

1919
use crate::builder::{Builder, RunConfig, ShouldRun, Step};
2020
use crate::config::TargetSelection;
21+
use crate::util::get_clang_cl_resource_dir;
2122
use crate::util::{self, exe, output, program_out_of_date, t, up_to_date};
2223
use crate::{CLang, GitRepo};
2324

@@ -755,7 +756,22 @@ impl Step for Lld {
755756
t!(fs::create_dir_all(&out_dir));
756757

757758
let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/lld"));
758-
configure_cmake(builder, target, &mut cfg, true, LdFlags::default());
759+
let mut ldflags = LdFlags::default();
760+
761+
// When building LLD as part of a build with instrumentation on windows, for example
762+
// when doing PGO on CI, cmake or clang-cl don't automatically link clang's
763+
// profiler runtime in. In that case, we need to manually ask cmake to do it, to avoid
764+
// linking errors, much like LLVM's cmake setup does in that situation.
765+
if builder.config.llvm_profile_generate && target.contains("msvc") {
766+
if let Some(clang_cl_path) = builder.config.llvm_clang_cl.as_ref() {
767+
// Find clang's runtime library directory and push that as a search path to the
768+
// cmake linker flags.
769+
let clang_rt_dir = get_clang_cl_resource_dir(clang_cl_path);
770+
ldflags.push_all(&format!("/libpath:{}", clang_rt_dir.display()));
771+
}
772+
}
773+
774+
configure_cmake(builder, target, &mut cfg, true, ldflags);
759775

760776
// This is an awful, awful hack. Discovered when we migrated to using
761777
// clang-cl to compile LLVM/LLD it turns out that LLD, when built out of

Diff for: src/bootstrap/util.rs

+24
Original file line numberDiff line numberDiff line change
@@ -576,3 +576,27 @@ fn absolute_windows(path: &std::path::Path) -> std::io::Result<std::path::PathBu
576576
}
577577
}
578578
}
579+
580+
/// Adapted from https://github.com/llvm/llvm-project/blob/782e91224601e461c019e0a4573bbccc6094fbcd/llvm/cmake/modules/HandleLLVMOptions.cmake#L1058-L1079
581+
///
582+
/// When `clang-cl` is used with instrumentation, we need to add clang's runtime library resource
583+
/// directory to the linker flags, otherwise there will be linker errors about the profiler runtime
584+
/// missing. This function returns the path to that directory.
585+
pub fn get_clang_cl_resource_dir(clang_cl_path: &str) -> PathBuf {
586+
// Similar to how LLVM does it, to find clang's library runtime directory:
587+
// - we ask `clang-cl` to locate the `clang_rt.builtins` lib.
588+
let mut builtins_locator = Command::new(clang_cl_path);
589+
builtins_locator.args(&["/clang:-print-libgcc-file-name", "/clang:--rtlib=compiler-rt"]);
590+
591+
let clang_rt_builtins = output(&mut builtins_locator);
592+
let clang_rt_builtins = Path::new(clang_rt_builtins.trim());
593+
assert!(
594+
clang_rt_builtins.exists(),
595+
"`clang-cl` must correctly locate the library runtime directory"
596+
);
597+
598+
// - the profiler runtime will be located in the same directory as the builtins lib, like
599+
// `$LLVM_DISTRO_ROOT/lib/clang/$LLVM_VERSION/lib/windows`.
600+
let clang_rt_dir = clang_rt_builtins.parent().expect("The clang lib folder should exist");
601+
clang_rt_dir.to_path_buf()
602+
}

0 commit comments

Comments
 (0)