llvm / llvm/llvm-project

[flang] No unwind tables are emitted for optimized code

Open
#217,595 1 comment 0 reactions 1 assignee Claimed by @abidh View on GitHub
flang:ir
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.