Skip to content

Commit 7c71bc3

Browse files
committed
Auto merge of #60262 - michaelwoerister:pgo-preinlining-pass, r=alexcrichton
PGO: Add a run-make test that makes sure that PGO profiling data is used by the compiler during optimizations. From the tests comment section: ``` # This test makes sure that PGO profiling data leads to cold functions being # marked as `cold` and hot functions with `inlinehint`. # The test program contains an `if` were actual execution only ever takes the # `else` branch. Accordingly, we expect the function that is never called to # be marked as cold. ``` r? @alexcrichton
2 parents 5b7baa5 + 7c4cc01 commit 7c71bc3

File tree

12 files changed

+132
-62
lines changed

12 files changed

+132
-62
lines changed

src/bootstrap/test.rs

+22
Original file line numberDiff line numberDiff line change
@@ -1231,6 +1231,28 @@ impl Step for Compiletest {
12311231
if let Some(ar) = builder.ar(target) {
12321232
cmd.arg("--ar").arg(ar);
12331233
}
1234+
1235+
// The llvm/bin directory contains many useful cross-platform
1236+
// tools. Pass the path to run-make tests so they can use them.
1237+
let llvm_bin_path = llvm_config.parent()
1238+
.expect("Expected llvm-config to be contained in directory");
1239+
assert!(llvm_bin_path.is_dir());
1240+
cmd.arg("--llvm-bin-dir").arg(llvm_bin_path);
1241+
1242+
// If LLD is available, add it to the PATH
1243+
if builder.config.lld_enabled {
1244+
let lld_install_root = builder.ensure(native::Lld {
1245+
target: builder.config.build,
1246+
});
1247+
1248+
let lld_bin_path = lld_install_root.join("bin");
1249+
1250+
let old_path = env::var_os("PATH").unwrap_or_default();
1251+
let new_path = env::join_paths(std::iter::once(lld_bin_path)
1252+
.chain(env::split_paths(&old_path)))
1253+
.expect("Could not add LLD bin path to PATH");
1254+
cmd.env("PATH", new_path);
1255+
}
12341256
}
12351257
}
12361258

src/bootstrap/tool.rs

-51
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use crate::Compiler;
99
use crate::builder::{Step, RunConfig, ShouldRun, Builder};
1010
use crate::util::{exe, add_lib_path};
1111
use crate::compile;
12-
use crate::native;
1312
use crate::channel::GitInfo;
1413
use crate::channel;
1514
use crate::cache::Interned;
@@ -698,56 +697,6 @@ impl<'a> Builder<'a> {
698697
}
699698
}
700699

701-
// Add the llvm/bin directory to PATH since it contains lots of
702-
// useful, platform-independent tools
703-
if tool.uses_llvm_tools() && !self.config.dry_run {
704-
let mut additional_paths = vec![];
705-
706-
if let Some(llvm_bin_path) = self.llvm_bin_path() {
707-
additional_paths.push(llvm_bin_path);
708-
}
709-
710-
// If LLD is available, add that too.
711-
if self.config.lld_enabled {
712-
let lld_install_root = self.ensure(native::Lld {
713-
target: self.config.build,
714-
});
715-
716-
let lld_bin_path = lld_install_root.join("bin");
717-
additional_paths.push(lld_bin_path);
718-
}
719-
720-
if host.contains("windows") {
721-
// On Windows, PATH and the dynamic library path are the same,
722-
// so we just add the LLVM bin path to lib_path
723-
lib_paths.extend(additional_paths);
724-
} else {
725-
let old_path = env::var_os("PATH").unwrap_or_default();
726-
let new_path = env::join_paths(additional_paths.into_iter()
727-
.chain(env::split_paths(&old_path)))
728-
.expect("Could not add LLVM bin path to PATH");
729-
cmd.env("PATH", new_path);
730-
}
731-
}
732-
733700
add_lib_path(lib_paths, cmd);
734701
}
735-
736-
fn llvm_bin_path(&self) -> Option<PathBuf> {
737-
if self.config.llvm_enabled() {
738-
let llvm_config = self.ensure(native::Llvm {
739-
target: self.config.build,
740-
emscripten: false,
741-
});
742-
743-
// Add the llvm/bin directory to PATH since it contains lots of
744-
// useful, platform-independent tools
745-
let llvm_bin_path = llvm_config.parent()
746-
.expect("Expected llvm-config to be contained in directory");
747-
assert!(llvm_bin_path.is_dir());
748-
Some(llvm_bin_path.to_path_buf())
749-
} else {
750-
None
751-
}
752-
}
753702
}

src/test/run-make-fulldeps/cross-lang-lto-clang/Makefile

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ all: cpp-executable rust-executable
99

1010
cpp-executable:
1111
$(RUSTC) -Clinker-plugin-lto=on -o $(TMPDIR)/librustlib-xlto.a -Copt-level=2 -Ccodegen-units=1 ./rustlib.rs
12-
$(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) $(CLANG) -flto=thin -fuse-ld=lld -L $(TMPDIR) -lrustlib-xlto -o $(TMPDIR)/cmain ./cmain.c -O3
12+
$(CLANG) -flto=thin -fuse-ld=lld -L $(TMPDIR) -lrustlib-xlto -o $(TMPDIR)/cmain ./cmain.c -O3
1313
# Make sure we don't find a call instruction to the function we expect to
1414
# always be inlined.
15-
$(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) llvm-objdump -d $(TMPDIR)/cmain | $(CGREP) -v -e "call.*rust_always_inlined"
15+
"$(LLVM_BIN_DIR)"/llvm-objdump -d $(TMPDIR)/cmain | $(CGREP) -v -e "call.*rust_always_inlined"
1616
# As a sanity check, make sure we do find a call instruction to a
1717
# non-inlined function
18-
$(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) llvm-objdump -d $(TMPDIR)/cmain | $(CGREP) -e "call.*rust_never_inlined"
18+
"$(LLVM_BIN_DIR)"/llvm-objdump -d $(TMPDIR)/cmain | $(CGREP) -e "call.*rust_never_inlined"
1919

2020
rust-executable:
21-
$(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) $(CLANG) ./clib.c -flto=thin -c -o $(TMPDIR)/clib.o -O2
21+
$(CLANG) ./clib.c -flto=thin -c -o $(TMPDIR)/clib.o -O2
2222
(cd $(TMPDIR); $(AR) crus ./libxyz.a ./clib.o)
2323
$(RUSTC) -Clinker-plugin-lto=on -L$(TMPDIR) -Copt-level=2 -Clinker=$(CLANG) -Clink-arg=-fuse-ld=lld ./main.rs -o $(TMPDIR)/rsmain
24-
$(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) llvm-objdump -d $(TMPDIR)/rsmain | $(CGREP) -e "call.*c_never_inlined"
25-
$(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) llvm-objdump -d $(TMPDIR)/rsmain | $(CGREP) -v -e "call.*c_always_inlined"
24+
"$(LLVM_BIN_DIR)"/llvm-objdump -d $(TMPDIR)/rsmain | $(CGREP) -e "call.*c_never_inlined"
25+
"$(LLVM_BIN_DIR)"/llvm-objdump -d $(TMPDIR)/rsmain | $(CGREP) -v -e "call.*c_always_inlined"

src/test/run-make-fulldeps/cross-lang-lto-upstream-rlibs/Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ all: staticlib.rs upstream.rs
1212

1313
# Check No LTO
1414
$(RUSTC) staticlib.rs -C linker-plugin-lto -Ccodegen-units=1 -L. -o $(TMPDIR)/staticlib.a
15-
(cd $(TMPDIR); $(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) llvm-ar x ./staticlib.a)
15+
(cd $(TMPDIR); "$(LLVM_BIN_DIR)"/llvm-ar x ./staticlib.a)
1616
# Make sure the upstream object file was included
1717
ls $(TMPDIR)/upstream.*.rcgu.o
1818

@@ -22,7 +22,7 @@ all: staticlib.rs upstream.rs
2222
# Check ThinLTO
2323
$(RUSTC) upstream.rs -C linker-plugin-lto -Ccodegen-units=1 -Clto=thin
2424
$(RUSTC) staticlib.rs -C linker-plugin-lto -Ccodegen-units=1 -Clto=thin -L. -o $(TMPDIR)/staticlib.a
25-
(cd $(TMPDIR); $(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) llvm-ar x ./staticlib.a)
25+
(cd $(TMPDIR); "$(LLVM_BIN_DIR)"/llvm-ar x ./staticlib.a)
2626
ls $(TMPDIR)/upstream.*.rcgu.o
2727

2828
else

src/test/run-make-fulldeps/cross-lang-lto/Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ ifndef IS_WINDOWS
1010
# -Clinker-plugin-lto.
1111

1212
# this only succeeds for bitcode files
13-
ASSERT_IS_BITCODE_OBJ=($(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) llvm-bcanalyzer $(1))
14-
EXTRACT_OBJS=(cd $(TMPDIR); rm -f ./*.o; $(LD_LIB_PATH_ENVVAR)=$(REAL_LD_LIBRARY_PATH) llvm-ar x $(1))
13+
ASSERT_IS_BITCODE_OBJ=("$(LLVM_BIN_DIR)"/llvm-bcanalyzer $(1))
14+
EXTRACT_OBJS=(cd $(TMPDIR); rm -f ./*.o; "$(LLVM_BIN_DIR)"/llvm-ar x $(1))
1515

1616
BUILD_LIB=$(RUSTC) lib.rs -Copt-level=2 -Clinker-plugin-lto -Ccodegen-units=1
1717
BUILD_EXE=$(RUSTC) main.rs -Copt-level=2 -Clinker-plugin-lto -Ccodegen-units=1 --emit=obj
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# needs-profiler-support
2+
3+
-include ../tools.mk
4+
5+
# This test makes sure that PGO profiling data leads to cold functions being
6+
# marked as `cold` and hot functions with `inlinehint`.
7+
# The test program contains an `if` were actual execution only ever takes the
8+
# `else` branch. Accordingly, we expect the function that is never called to
9+
# be marked as cold.
10+
#
11+
# The program is compiled with `-Copt-level=s` because this setting disables
12+
# LLVM's pre-inlining pass (i.e. a pass that does some inlining before it adds
13+
# the profiling instrumentation). Disabling this pass leads to rather
14+
# predictable IR which we need for this test to be stable.
15+
16+
COMMON_FLAGS=-Copt-level=s -Ccodegen-units=1
17+
18+
# LLVM doesn't support instrumenting binaries that use SEH:
19+
# https://bugs.llvm.org/show_bug.cgi?id=41279
20+
#
21+
# Things work fine with -Cpanic=abort though.
22+
ifdef IS_MSVC
23+
COMMON_FLAGS+= -Cpanic=abort
24+
endif
25+
26+
ifeq ($(UNAME),Darwin)
27+
# macOS does not have the `tac` command, but `tail -r` does the same thing
28+
TAC := tail -r
29+
else
30+
# some other platforms don't support the `-r` flag for `tail`, so use `tac`
31+
TAC := tac
32+
endif
33+
34+
all:
35+
# Compile the test program with instrumentation
36+
$(RUSTC) $(COMMON_FLAGS) -Z pgo-gen="$(TMPDIR)" main.rs
37+
# Run it in order to generate some profiling data
38+
$(call RUN,main some-argument) || exit 1
39+
# Postprocess the profiling data so it can be used by the compiler
40+
"$(LLVM_BIN_DIR)"/llvm-profdata merge \
41+
-o "$(TMPDIR)"/merged.profdata \
42+
"$(TMPDIR)"/default_*.profraw
43+
# Compile the test program again, making use of the profiling data
44+
$(RUSTC) $(COMMON_FLAGS) -Z pgo-use="$(TMPDIR)"/merged.profdata --emit=llvm-ir main.rs
45+
# Check that the generate IR contains some things that we expect
46+
#
47+
# We feed the file into LLVM FileCheck tool *in reverse* so that we see the
48+
# line with the function name before the line with the function attributes.
49+
# FileCheck only supports checking that something matches on the next line,
50+
# but not if something matches on the previous line.
51+
$(TAC) "$(TMPDIR)"/main.ll | "$(LLVM_FILECHECK)" filecheck-patterns.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Add a check that the IR contains some expected metadata
2+
CHECK: !{!"ProfileFormat", !"InstrProf"}
3+
CHECK: !"ProfileSummary"
4+
5+
# Make sure that the hot function is marked with `inlinehint`
6+
CHECK: define {{.*}} @hot_function
7+
CHECK-NEXT: Function Attrs:{{.*}}inlinehint
8+
9+
# Make sure that the cold function is marked with `cold`
10+
CHECK: define {{.*}} @cold_function
11+
CHECK-NEXT: Function Attrs:{{.*}}cold
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#[no_mangle]
2+
pub fn cold_function(c: u8) {
3+
println!("cold {}", c);
4+
}
5+
6+
#[no_mangle]
7+
pub fn hot_function(c: u8) {
8+
std::env::set_var(format!("var{}", c), format!("hot {}", c));
9+
}
10+
11+
fn main() {
12+
let arg = std::env::args().skip(1).next().unwrap();
13+
14+
for i in 0 .. 1000_000 {
15+
let some_value = arg.as_bytes()[i % arg.len()];
16+
if some_value == b'!' {
17+
// This branch is never taken at runtime
18+
cold_function(some_value);
19+
} else {
20+
hot_function(some_value);
21+
}
22+
}
23+
}

src/test/run-make-fulldeps/tools.mk

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ DYLIB = $(TMPDIR)/$(1).dll
4747
STATICLIB = $(TMPDIR)/$(1).lib
4848
STATICLIB_GLOB = $(1)*.lib
4949
BIN = $(1).exe
50+
LLVM_FILECHECK := $(shell cygpath -u "$(LLVM_FILECHECK)")
5051
else
5152
RUN = $(TARGET_RPATH_ENV) $(RUN_BINFILE)
5253
FAIL = $(TARGET_RPATH_ENV) $(RUN_BINFILE) && exit 1 || exit 0

src/tools/compiletest/src/common.rs

+3
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ pub struct Config {
144144
/// The LLVM `FileCheck` binary path.
145145
pub llvm_filecheck: Option<PathBuf>,
146146

147+
/// Path to LLVM's bin directory.
148+
pub llvm_bin_dir: Option<PathBuf>,
149+
147150
/// The valgrind path.
148151
pub valgrind_path: Option<String>,
149152

src/tools/compiletest/src/main.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
221221
"LIST",
222222
)
223223
.reqopt("", "llvm-cxxflags", "C++ flags for LLVM", "FLAGS")
224+
.optopt("", "llvm-bin-dir", "Path to LLVM's `bin` directory", "PATH")
224225
.optopt("", "nodejs", "the name of nodejs", "PATH")
225226
.optopt(
226227
"",
@@ -306,7 +307,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
306307
valgrind_path: matches.opt_str("valgrind-path"),
307308
force_valgrind: matches.opt_present("force-valgrind"),
308309
run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
309-
llvm_filecheck: matches.opt_str("llvm-filecheck").map(|s| PathBuf::from(&s)),
310+
llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
311+
llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
310312
src_base,
311313
build_base: opt_path(matches, "build-base"),
312314
stage_id: matches.opt_str("stage-id").unwrap(),

src/tools/compiletest/src/runtest.rs

+8
Original file line numberDiff line numberDiff line change
@@ -2691,6 +2691,14 @@ impl<'test> TestCx<'test> {
26912691
cmd.env("CLANG", clang);
26922692
}
26932693

2694+
if let Some(ref filecheck) = self.config.llvm_filecheck {
2695+
cmd.env("LLVM_FILECHECK", filecheck);
2696+
}
2697+
2698+
if let Some(ref llvm_bin_dir) = self.config.llvm_bin_dir {
2699+
cmd.env("LLVM_BIN_DIR", llvm_bin_dir);
2700+
}
2701+
26942702
// We don't want RUSTFLAGS set from the outside to interfere with
26952703
// compiler flags set in the test cases:
26962704
cmd.env_remove("RUSTFLAGS");

0 commit comments

Comments
 (0)