Repository: rust
Issue: rustc `-Clink-dead-code` causes MSVC linker to produce invalid binaries when LLVM InstrProf counters are enabled
Labels: I-ICE,T-compiler,O-windows-msvc,C-bug,link-dead-code

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](https://github.com/richkadel/rust/blob/coverage-msvc-segfault-discuss/coverage-msvc-segfault-notes.md), 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 the `compiler-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

```Rust
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.