Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Utilize PGO for windows x64 rustc dist builds #96978

Merged
merged 4 commits into from
Jul 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ jobs:
- name: dist-x86_64-msvc
env:
RUST_CONFIGURE_ARGS: "--build=x86_64-pc-windows-msvc --host=x86_64-pc-windows-msvc --target=x86_64-pc-windows-msvc --enable-full-tools --enable-profiler"
SCRIPT: python x.py dist
SCRIPT: PGO_HOST=x86_64-pc-windows-msvc src/ci/pgo.sh python x.py dist
DIST_REQUIRE_ALL_TOOLS: 1
os: windows-latest-xl
- name: dist-i686-msvc
Expand Down
33 changes: 31 additions & 2 deletions src/bootstrap/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::config::{LlvmLibunwind, TargetSelection};
use crate::dist;
use crate::native;
use crate::tool::SourceType;
use crate::util::get_clang_cl_resource_dir;
use crate::util::{exe, is_debug_info, is_dylib, output, symlink_dir, t, up_to_date};
use crate::LLVM_TOOLS;
use crate::{CLang, Compiler, DependencyType, GitRepo, Mode};
Expand Down Expand Up @@ -769,10 +770,38 @@ pub fn rustc_cargo_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetS
if let Some(s) = target_config.and_then(|c| c.llvm_config.as_ref()) {
cargo.env("CFG_LLVM_ROOT", s);
}
// Some LLVM linker flags (-L and -l) may be needed to link rustc_llvm.

// Some LLVM linker flags (-L and -l) may be needed to link `rustc_llvm`. Its build script
// expects these to be passed via the `LLVM_LINKER_FLAGS` env variable, separated by
// whitespace.
//
// For example:
// - on windows, when `clang-cl` is used with instrumentation, we need to manually add
// clang's runtime library resource directory so that the profiler runtime library can be
// 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 && target.contains("msvc") {
if let Some(ref clang_cl_path) = builder.config.llvm_clang_cl {
// Add clang's runtime library directory to the search path
let clang_rt_dir = get_clang_cl_resource_dir(clang_cl_path);
llvm_linker_flags.push_str(&format!("-L{}", clang_rt_dir.display()));
}
}

// The config can also specify its own llvm linker flags.
if let Some(ref s) = builder.config.llvm_ldflags {
cargo.env("LLVM_LINKER_FLAGS", s);
if !llvm_linker_flags.is_empty() {
llvm_linker_flags.push_str(" ");
}
llvm_linker_flags.push_str(s);
}

// Set the linker flags via the env var that `rustc_llvm`'s build script will read.
if !llvm_linker_flags.is_empty() {
cargo.env("LLVM_LINKER_FLAGS", llvm_linker_flags);
}

// Building with a static libstdc++ is only supported on linux right now,
// not for MSVC or macOS
if builder.config.llvm_static_stdcpp
Expand Down
18 changes: 17 additions & 1 deletion src/bootstrap/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use std::process::Command;

use crate::builder::{Builder, RunConfig, ShouldRun, Step};
use crate::config::TargetSelection;
use crate::util::get_clang_cl_resource_dir;
use crate::util::{self, exe, output, program_out_of_date, t, up_to_date};
use crate::{CLang, GitRepo};

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

let mut cfg = cmake::Config::new(builder.src.join("src/llvm-project/lld"));
configure_cmake(builder, target, &mut cfg, true, LdFlags::default());
let mut ldflags = LdFlags::default();

// When building LLD as part of a build with instrumentation on windows, for example
// 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 && target.contains("msvc") {
if let Some(clang_cl_path) = builder.config.llvm_clang_cl.as_ref() {
// Find clang's runtime library directory and push that as a search path to the
// cmake linker flags.
let clang_rt_dir = get_clang_cl_resource_dir(clang_cl_path);
ldflags.push_all(&format!("/libpath:{}", clang_rt_dir.display()));
}
}
lqd marked this conversation as resolved.
Show resolved Hide resolved

configure_cmake(builder, target, &mut cfg, true, ldflags);

// This is an awful, awful hack. Discovered when we migrated to using
// clang-cl to compile LLVM/LLD it turns out that LLD, when built out of
Expand Down
24 changes: 24 additions & 0 deletions src/bootstrap/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,3 +576,27 @@ fn absolute_windows(path: &std::path::Path) -> std::io::Result<std::path::PathBu
}
}
}

/// Adapted from https://github.com/llvm/llvm-project/blob/782e91224601e461c019e0a4573bbccc6094fbcd/llvm/cmake/modules/HandleLLVMOptions.cmake#L1058-L1079
///
/// When `clang-cl` is used with instrumentation, we need to add clang's runtime library resource
/// directory to the linker flags, otherwise there will be linker errors about the profiler runtime
/// missing. This function returns the path to that directory.
pub fn get_clang_cl_resource_dir(clang_cl_path: &str) -> PathBuf {
// Similar to how LLVM does it, to find clang's library runtime directory:
// - we ask `clang-cl` to locate the `clang_rt.builtins` lib.
let mut builtins_locator = Command::new(clang_cl_path);
builtins_locator.args(&["/clang:-print-libgcc-file-name", "/clang:--rtlib=compiler-rt"]);

let clang_rt_builtins = output(&mut builtins_locator);
let clang_rt_builtins = Path::new(clang_rt_builtins.trim());
assert!(
clang_rt_builtins.exists(),
"`clang-cl` must correctly locate the library runtime directory"
);

// - the profiler runtime will be located in the same directory as the builtins lib, like
// `$LLVM_DISTRO_ROOT/lib/clang/$LLVM_VERSION/lib/windows`.
let clang_rt_dir = clang_rt_builtins.parent().expect("The clang lib folder should exist");
clang_rt_dir.to_path_buf()
}
2 changes: 1 addition & 1 deletion src/ci/docker/host-x86_64/dist-x86_64-linux/build-clang.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -ex

source shared.sh

LLVM=llvmorg-14.0.2
LLVM=llvmorg-14.0.5

mkdir llvm-project
cd llvm-project
Expand Down
2 changes: 1 addition & 1 deletion src/ci/github-actions/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ jobs:
--target=x86_64-pc-windows-msvc
--enable-full-tools
--enable-profiler
SCRIPT: python x.py dist
SCRIPT: PGO_HOST=x86_64-pc-windows-msvc src/ci/pgo.sh python x.py dist
DIST_REQUIRE_ALL_TOOLS: 1
<<: *job-windows-xl

Expand Down
161 changes: 113 additions & 48 deletions src/ci/pgo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,82 @@

set -euxo pipefail

ci_dir=`cd $(dirname $0) && pwd`
source "$ci_dir/shared.sh"

# The root checkout, where the source is located
CHECKOUT=/checkout

DOWNLOADED_LLVM=/rustroot

# The main directory where the build occurs, which can be different between linux and windows
BUILD_ROOT=$CHECKOUT/obj

if isWindows; then
CHECKOUT=$(pwd)
DOWNLOADED_LLVM=$CHECKOUT/citools/clang-rust
BUILD_ROOT=$CHECKOUT
fi

# The various build artifacts used in other commands: to launch rustc builds, build the perf
# collector, and run benchmarks to gather profiling data
BUILD_ARTIFACTS=$BUILD_ROOT/build/$PGO_HOST
RUSTC_STAGE_0=$BUILD_ARTIFACTS/stage0/bin/rustc
CARGO_STAGE_0=$BUILD_ARTIFACTS/stage0/bin/cargo
RUSTC_STAGE_2=$BUILD_ARTIFACTS/stage2/bin/rustc

# Windows needs these to have the .exe extension
if isWindows; then
RUSTC_STAGE_0="${RUSTC_STAGE_0}.exe"
CARGO_STAGE_0="${CARGO_STAGE_0}.exe"
RUSTC_STAGE_2="${RUSTC_STAGE_2}.exe"
fi

# Make sure we have a temporary PGO work folder
PGO_TMP=/tmp/tmp-pgo
mkdir -p $PGO_TMP
rm -rf $PGO_TMP/*

RUSTC_PERF=$PGO_TMP/rustc-perf

# Compile several crates to gather execution PGO profiles.
# Arg0 => profiles (Debug, Opt)
# Arg1 => scenarios (Full, IncrFull, All)
# Arg2 => crates (syn, cargo, ...)
gather_profiles () {
cd /checkout/obj
cd $BUILD_ROOT

# Compile libcore, both in opt-level=0 and opt-level=3
RUSTC_BOOTSTRAP=1 ./build/$PGO_HOST/stage2/bin/rustc \
--edition=2021 --crate-type=lib ../library/core/src/lib.rs
RUSTC_BOOTSTRAP=1 ./build/$PGO_HOST/stage2/bin/rustc \
--edition=2021 --crate-type=lib -Copt-level=3 ../library/core/src/lib.rs
RUSTC_BOOTSTRAP=1 $RUSTC_STAGE_2 \
--edition=2021 --crate-type=lib $CHECKOUT/library/core/src/lib.rs \
--out-dir $PGO_TMP
RUSTC_BOOTSTRAP=1 $RUSTC_STAGE_2 \
--edition=2021 --crate-type=lib -Copt-level=3 $CHECKOUT/library/core/src/lib.rs \
--out-dir $PGO_TMP

cd rustc-perf
cd $RUSTC_PERF

# Run rustc-perf benchmarks
# Benchmark using profile_local with eprintln, which essentially just means
# don't actually benchmark -- just make sure we run rustc a bunch of times.
RUST_LOG=collector=debug \
RUSTC=/checkout/obj/build/$PGO_HOST/stage0/bin/rustc \
RUSTC=$RUSTC_STAGE_0 \
RUSTC_BOOTSTRAP=1 \
/checkout/obj/build/$PGO_HOST/stage0/bin/cargo run -p collector --bin collector -- \
profile_local \
eprintln \
/checkout/obj/build/$PGO_HOST/stage2/bin/rustc \
--id Test \
--profiles $1 \
--cargo /checkout/obj/build/$PGO_HOST/stage0/bin/cargo \
--scenarios $2 \
--include $3

cd /checkout/obj
$CARGO_STAGE_0 run -p collector --bin collector -- \
profile_local \
eprintln \
$RUSTC_STAGE_2 \
--id Test \
--profiles $1 \
--cargo $CARGO_STAGE_0 \
--scenarios $2 \
--include $3

cd $BUILD_ROOT
}

rm -rf /tmp/rustc-pgo

# This path has to be absolute
LLVM_PROFILE_DIRECTORY_ROOT=/tmp/llvm-pgo
LLVM_PROFILE_DIRECTORY_ROOT=$PGO_TMP/llvm-pgo

# We collect LLVM profiling information and rustc profiling information in
# separate phases. This increases build time -- though not by a huge amount --
Expand All @@ -49,69 +87,93 @@ LLVM_PROFILE_DIRECTORY_ROOT=/tmp/llvm-pgo
# LLVM IR PGO does not respect LLVM_PROFILE_FILE, so we have to set the profiling file
# path through our custom environment variable. We include the PID in the directory path
# to avoid updates to profile files being lost because of race conditions.
LLVM_PROFILE_DIR=${LLVM_PROFILE_DIRECTORY_ROOT}/prof-%p python3 ../x.py build \
LLVM_PROFILE_DIR=${LLVM_PROFILE_DIRECTORY_ROOT}/prof-%p python3 $CHECKOUT/x.py build \
--target=$PGO_HOST \
--host=$PGO_HOST \
--stage 2 library/std \
--llvm-profile-generate

# Compile rustc perf
cp -r /tmp/rustc-perf ./
chown -R $(whoami): ./rustc-perf
cd rustc-perf

# Build the collector ahead of time, which is needed to make sure the rustc-fake
# binary used by the collector is present.
RUSTC=/checkout/obj/build/$PGO_HOST/stage0/bin/rustc \
# Compile rustc-perf:
# - get the expected commit source code: on linux, the Dockerfile downloads a source archive before
# running this script. On Windows, we do that here.
if isLinux; then
cp -r /tmp/rustc-perf $RUSTC_PERF
chown -R $(whoami): $RUSTC_PERF
else
# rustc-perf version from 2022-05-18
PERF_COMMIT=f66cc8f3e04392b0e2fd811f21fd1ece6ebaded3
retry curl -LS -o $PGO_TMP/perf.zip \
https://github.com/rust-lang/rustc-perf/archive/$PERF_COMMIT.zip && \
cd $PGO_TMP && unzip -q perf.zip && \
mv rustc-perf-$PERF_COMMIT $RUSTC_PERF && \
rm perf.zip
fi

# - build rustc-perf's collector ahead of time, which is needed to make sure the rustc-fake binary
# used by the collector is present.
cd $RUSTC_PERF

RUSTC=$RUSTC_STAGE_0 \
RUSTC_BOOTSTRAP=1 \
/checkout/obj/build/$PGO_HOST/stage0/bin/cargo build -p collector
$CARGO_STAGE_0 build -p collector

# Here we're profiling LLVM, so we only care about `Debug` and `Opt`, because we want to stress
# codegen. We also profile some of the most prolific crates.
gather_profiles "Debug,Opt" "Full" \
"syn-1.0.89,cargo-0.60.0,serde-1.0.136,ripgrep-13.0.0,regex-1.5.5,clap-3.1.6,hyper-0.14.18"
"syn-1.0.89,cargo-0.60.0,serde-1.0.136,ripgrep-13.0.0,regex-1.5.5,clap-3.1.6,hyper-0.14.18"

LLVM_PROFILE_MERGED_FILE=/tmp/llvm-pgo.profdata
LLVM_PROFILE_MERGED_FILE=$PGO_TMP/llvm-pgo.profdata

# Merge the profile data we gathered for LLVM
# Note that this uses the profdata from the clang we used to build LLVM,
# which likely has a different version than our in-tree clang.
/rustroot/bin/llvm-profdata merge -o ${LLVM_PROFILE_MERGED_FILE} ${LLVM_PROFILE_DIRECTORY_ROOT}
$DOWNLOADED_LLVM/bin/llvm-profdata merge -o ${LLVM_PROFILE_MERGED_FILE} ${LLVM_PROFILE_DIRECTORY_ROOT}

echo "LLVM PGO statistics"
du -sh ${LLVM_PROFILE_MERGED_FILE}
du -sh ${LLVM_PROFILE_DIRECTORY_ROOT}
echo "Profile file count"
find ${LLVM_PROFILE_DIRECTORY_ROOT} -type f | wc -l

# We don't need the individual .profraw files now that they have been merged into a final .profdata
rm -r $LLVM_PROFILE_DIRECTORY_ROOT

# Rustbuild currently doesn't support rebuilding LLVM when PGO options
# change (or any other llvm-related options); so just clear out the relevant
# directories ourselves.
rm -r ./build/$PGO_HOST/llvm ./build/$PGO_HOST/lld
rm -r $BUILD_ARTIFACTS/llvm $BUILD_ARTIFACTS/lld

# Okay, LLVM profiling is done, switch to rustc PGO.

# The path has to be absolute
RUSTC_PROFILE_DIRECTORY_ROOT=/tmp/rustc-pgo
RUSTC_PROFILE_DIRECTORY_ROOT=$PGO_TMP/rustc-pgo

python3 ../x.py build --target=$PGO_HOST --host=$PGO_HOST \
python3 $CHECKOUT/x.py build --target=$PGO_HOST --host=$PGO_HOST \
--stage 2 library/std \
--rust-profile-generate=${RUSTC_PROFILE_DIRECTORY_ROOT}

# Here we're profiling the `rustc` frontend, so we also include `Check`.
# The benchmark set includes various stress tests that put the frontend under pressure.
# The profile data is written into a single filepath that is being repeatedly merged when each
# rustc invocation ends. Empirically, this can result in some profiling data being lost.
# That's why we override the profile path to include the PID. This will produce many more profiling
# files, but the resulting profile will produce a slightly faster rustc binary.
LLVM_PROFILE_FILE=${RUSTC_PROFILE_DIRECTORY_ROOT}/default_%m_%p.profraw gather_profiles \
"Check,Debug,Opt" "All" \
"externs,ctfe-stress-5,cargo-0.60.0,token-stream-stress,match-stress,tuple-stress,diesel-1.4.8,bitmaps-3.1.0"

RUSTC_PROFILE_MERGED_FILE=/tmp/rustc-pgo.profdata
if isLinux; then
# The profile data is written into a single filepath that is being repeatedly merged when each
# rustc invocation ends. Empirically, this can result in some profiling data being lost. That's
# why we override the profile path to include the PID. This will produce many more profiling
# files, but the resulting profile will produce a slightly faster rustc binary.
LLVM_PROFILE_FILE=${RUSTC_PROFILE_DIRECTORY_ROOT}/default_%m_%p.profraw gather_profiles \
"Check,Debug,Opt" "All" \
"externs,ctfe-stress-5,cargo-0.60.0,token-stream-stress,match-stress,tuple-stress,diesel-1.4.8,bitmaps-3.1.0"
else
# On windows, we don't do that yet (because it generates a lot of data, hitting disk space
# limits on the builder), and use the default profraw merging behavior.
gather_profiles \
"Check,Debug,Opt" "All" \
"externs,ctfe-stress-5,cargo-0.60.0,token-stream-stress,match-stress,tuple-stress,diesel-1.4.8,bitmaps-3.1.0"
fi

RUSTC_PROFILE_MERGED_FILE=$PGO_TMP/rustc-pgo.profdata

# Merge the profile data we gathered
./build/$PGO_HOST/llvm/bin/llvm-profdata \
$BUILD_ARTIFACTS/llvm/bin/llvm-profdata \
merge -o ${RUSTC_PROFILE_MERGED_FILE} ${RUSTC_PROFILE_DIRECTORY_ROOT}

echo "Rustc PGO statistics"
Expand All @@ -120,10 +182,13 @@ du -sh ${RUSTC_PROFILE_DIRECTORY_ROOT}
echo "Profile file count"
find ${RUSTC_PROFILE_DIRECTORY_ROOT} -type f | wc -l

# We don't need the individual .profraw files now that they have been merged into a final .profdata
rm -r $RUSTC_PROFILE_DIRECTORY_ROOT

# Rustbuild currently doesn't support rebuilding LLVM when PGO options
# change (or any other llvm-related options); so just clear out the relevant
# directories ourselves.
rm -r ./build/$PGO_HOST/llvm ./build/$PGO_HOST/lld
rm -r $BUILD_ARTIFACTS/llvm $BUILD_ARTIFACTS/lld

# This produces the actual final set of artifacts, using both the LLVM and rustc
# collected profiling data.
Expand Down
3 changes: 2 additions & 1 deletion src/ci/scripts/install-clang.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/bash
# ignore-tidy-linelength
# This script installs clang on the local machine. Note that we don't install
# clang on Linux since its compiler story is just so different. Each container
# has its own toolchain configured appropriately already.
Expand All @@ -9,7 +10,7 @@ IFS=$'\n\t'
source "$(cd "$(dirname "$0")" && pwd)/../shared.sh"

# Update both macOS's and Windows's tarballs when bumping the version here.
LLVM_VERSION="14.0.2"
LLVM_VERSION="14.0.5"

if isMacOS; then
# If the job selects a specific Xcode version, use that instead of
Expand Down