Description
This reports a bug with the -C link-dead-code
option is enabled, when targeting MSVC and when LLVM InstrProf counter reports are enabled. The bug is probably not specific to InstrProf, but InstrProf sets the conditions for exposing the bug.
I hope someone with better familiarity with the -C link-dead-code
option, and the MSVC linker options it enables and/or interacts with, can help determine the root cause of the issue, and provide a fix or better workaround that allows InstrProf and -Z instrument-coverage
to work with -C link-dead-code
on MSVC.
Brief summary of error: When running a program compiled with -Z instrument-coverage
and -C link-dead-code
, the program should generate an InstrProf counter report upon program exit, but instead it generates a zero-length file and almost always crashes with a Segmentation fault
.
Background: The InstrProf counter report (a file generated at program exit, called default.profraw
unless overridden by setting LLVM_PROFILE_FILE
) is generated by the LLVM compiler-rt
source that is compiled into a rust library by rust-lang's rust/library/profiler-builtins/
.
The -Z instrument-coverage
flag injects LLVM intrinsics that increment global counter variables during program execution, and the profiler-builtins
library sets up atexit()
callbacks that gather the final counter values and write them to the default.profraw
file.
I've documented my tests and observations, in case they are helpful to someone with more knowledge in this area.
I compared working InstrProf-enabled binaries in a C program compiled by Clang to the failing Rust-based binaries, stepping through the counter report generation code of both programs using Visual Studio debugger, the debugger's disassembled view, and by inspecting the instructions and call graphs in IDA Pro for both.
I observed that the Rust binary had some notable (and clearly incorrect) differences, such as:
- Some
jmp
instructions appear to be jumping to the wrong offset (4 bytes off) - Some instructions and variables (such as instructions initializing fields of a
Header
structure for the counter report) were simply not present in the Rust binary. They are present in the C binary, and map to sequential assignments in thecompiler-rt
source, but they are not shown in the disassembled views. The debugger skips over the source for these statements, and the variables involved cannot be evaluated. The debugger complains that certain variables were "optimized out".
My best guess is the MSVC linker is first miscalculating the jmp
addresses for some functions, and then, when the miscalculated addresses don't line up to known functions, an optimization pass may be deciding that some variables are not used (because their functions don't appear to be called), so code that updates those variables is optimized out.
Note that PR #76002 overcomes what was originally thought to be a bug related to the -Zinstrument-coverage
implementation. Prior to PR #76002, the -Zinstrument-coverage
option always automatically enabled -Clink-dead-code
by default, to ensure dead code was still counted, as zero executions, in coverage reports. PR #76002 works around the problem by only enabling -Clink-dead-code
by default for non-MSVC platforms.
Since PR #76002, the bug can still be reproduced on MSVC by enabling -Clink-dead-code
explicitly (as shown in the Code
section below).
Code
fn testfunc() {
}
fn main() {
let mut i = 0;
loop {
if i >= 10 {
break;
}
testfunc();
i += 1;
}
}
Meta
rustc --version --verbose
:
binary: rustc
commit-hash: unknown
commit-date: unknown
host: x86_64-pc-windows-msvc
release: 1.47.0-dev
LLVM version: 11.0
Error output
The following example explicitly enables or disables -Clink-dead-code
. The default value was to enable this setting, prior to PR #76002, and as of this PR the default is now to not enable this setting.
From an msys2 terminal:
$ build/x86_64-pc-windows-msvc/stage1/bin/rustc -Zinstrument-coverage -Clink-dead-code=no basic.rs
$ ./basic
Hello world
$ ls -l default.profraw
-rw-r--r-- 1 richkadel Domain Users 168 Aug 28 10:43 default.profraw
$ build/x86_64-pc-windows-msvc/stage1/bin/rustc -Zinstrument-coverage -Clink-dead-code basic.rs
$ ./basic
Hello world
Segmentation fault
and also generates an empty default.profraw
file.