[flang] No unwind tables are emitted for optimized code
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
Flang never sets the `uwtable` function attribute, and the driver does not accept any of the options that would ask for one (`-funwind-tables`, `-fasynchronous-unwind-tables`, and their `-fno-` forms are all rejected as unknown arguments). As a consequence, optimized Fortran objects contain no `.eh_frame` at all, so the runtime unwinder cannot walk through Fortran frames. Both clang and gfortran default to asynchronous unwind tables on Linux targets, so flang is the outlier here.
This breaks anything that relies on asynchronous stack unwinding while executing Fortran code like profilers or crash/signal handlers that call `backtrace()`.
## Details
`Function::needsUnwindTableEntry()` is what decides whether the backend emits unwind information:
```cpp
bool needsUnwindTableEntry() const {
return hasUWTable() || !doesNotThrow() || hasPersonalityFn();
}
```
Because flang never sets `uwtable`, this is true only while a function has not yet been proven `nounwind`. At `-O0` that happens to be the case, so unwind tables appear by accident. At `-O2` LLVM infers `nounwind` for ordinary computational Fortran routines and the unwind information disappears entirely. `-g` does not recover it.
## Reproducer
`hot.f90`:
```fortran
function fhot(n) bind(C, name="fhot") result(s)
use iso_c_binding
implicit none
integer(c_int), value :: n
real(c_double) :: s
integer(c_int) :: i
s = 0.0_c_double
do i = 1, n
s = s + sqrt(real(i, c_double))
end do
end function fhot
```
`bt.c` takes an asynchronous profiling signal while executing inside `fhot` and
reports how far `backtrace()` gets:
```c
#include
#include
#include
#include
extern double fhot(int);
static void *buf[32];
static int depth = 0;
static void handler(int sig) { depth = backtrace(buf, 32); }
int main(void) {
struct sigaction sa = {0};
sa.sa_handler = handler;
sigaction(SIGPROF, &sa, 0);
struct itimerval t = {{0, 0}, {0, 50000}};
setitimer(ITIMER_PROF, &t, 0);
volatile double s = fhot(400000000);
printf("%d\n", depth);
return 0;
}
```
```
$ clang -O0 -g -c bt.c -o bt.o
$ flang -O2 -c hot.f90 -o hot.o && clang hot.o bt.o -lm -o bt && ./bt
```
## Results
x86_64-unknown-linux-gnu, `fhot` compiled by each of the three compilers and linked against the same `bt.o`:
| compiler | flags | unwind sections in object | backtrace depth from inside `fhot` |
| --- | --- | --- | --- |
| flang | `-O0` | `.eh_frame` | 7 |
| flang | `-O2` | *(none)* | 3 |
| flang | `-O2 -g` | `.debug_frame` | 3 |
| clang | `-O2` | `.eh_frame` | 7 |
| gfortran | `-O2` | `.eh_frame` | 7 |
Depth 3 means the walk stops inside the Fortran frame and never reaches `main`. The corresponding function attributes show the difference:
```
flang -O2: attributes #0 = { nofree norecurse nosync nounwind memory(none) "target-cpu"="x86-64" }
clang -O2: attributes #0 = { nofree norecurse nosync nounwind memory(none) uwtable ... }
```
Contributor guide
Assessment
This issue has not been assessed yet.