llvm / llvm/llvm-project

[flang] [miscompilation] Silent miscompile at -O1+: f = sqrt(f) in a function is ignored

Open
#219,450 4 comments 0 reactions 0 assignees View on GitHub
flang miscompilation
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

# [flang] Miscompile at -O1 and above: function return is forwarded past the final store to the result variable

## Summary

When a function assigns its result variable more than once, and the final assignment
reads the variable's own previous value, flang returns the value from **before** the
final assignment. The final store is emitted but the `return` is forwarded to an
earlier load, skipping it.

This happens at `-O1`, `-O2` and `-O3`; `-O0` is correct. There is no diagnostic —
the program silently computes the wrong number.

## Reproducer

```fortran
module m
contains
real(kind=kind(0.d0)) function f()
f = 72.d0
f = sqrt(f)
end function f
end module m

program p
use m
real(kind=kind(0.d0)) :: r
r = f()
write(*,'(A,F12.6)') 'result: ', r
end program p
```

```
$ flang -O0 -o b bug.f90 && ./b
result: 8.485281 <-- correct, sqrt(72)

$ flang -O1 -o b bug.f90 && ./b
result: 72.000000 <-- wrong, the value before the sqrt
```

gfortran (any optimization level) prints `8.485281`.

## Root cause: the return is forwarded past the second store

`flang -fc1 -emit-fir` (unoptimized) is correct — the second store is followed by a
reload, and that reload is returned:

```mlir
fir.store %cst to %2 // f = 72.0
%3 = fir.load %2
%4 = math.sqrt %3 fastmath
fir.store %4 to %2 // f = sqrt(f)
%5 = fir.load %2
return %5 : f64 // correct
```

`flang -fc1 -emit-mlir -O1` returns `%3`, the value loaded *before* the sqrt, even
though the `fir.store %4` is still present:

```mlir
fir.store %cst to %2 // f = 72.0
%3 = fir.load %2
%4 = math.sqrt %3 fastmath
fir.store %4 to %2 // f = sqrt(f) <-- still here
return %3 : f64 // WRONG: %3 predates the store above
```

The resulting LLVM IR is simply:

```llvm
define noundef double @_QMmPf() local_unnamed_addr #0 {
ret double 7.200000e+01
}
```

## This is not LLVM's mid-end

Running LLVM's own optimizer over flang's **-O0** LLVM IR produces the correct
constant, so the generic pipeline handles this pattern fine:

```
$ flang -O0 -S -emit-llvm bug.f90 -o bug.ll
$ opt -O1 -S bug.ll -o -
define noundef double @_QMmPf() local_unnamed_addr #0 {
ret double f0x4020F876CCDF6CD9 ; = 8.48528137423857 = sqrt(72), correct
}
```

The wrong transformation therefore happens in flang's own MLIR/FIR pass pipeline,
before LLVM IR is generated. I was not able to isolate the specific pass
(`-mmlir --print-after-all` produced no dumps for me on this build).

## Scope

| Case | Result |
|---|---|
| Function result variable, `f = sqrt(f)` | **miscompiled** |
| Function local, `s = sqrt(s)` then `f = s` | **miscompiled** |
| Subroutine, `intent(out)` argument, `x = sqrt(x)` | correct |
| Same statements in a main program | correct |
| Accumulation inside a loop (`s = s + a(i)*a(i)`) | correct |

It is **not specific to `sqrt`**, which is consistent with a store-forwarding fault
rather than a bad intrinsic folder. All of these return the pre-assignment value:

- `f = -72.d0 ; f = abs(f)` returns `-72.0`
- `f = 1.d0 ; f = exp(f)` returns `1.0`
- `f = 72.d0 ; f = f*2.d0` returns `72.0`

Adding a statement that makes the value otherwise observable (e.g. a `write` of `f`
inside the function) hides the bug and the result becomes correct.

## Version

```
$ flang --version
Homebrew flang version 23.1.0
Target: arm64-apple-darwin25.6.0
```

Installed from the Homebrew `flang` bottle. I have not been able to test other
targets or an upstream build, so I cannot say whether this is arm64-specific.

## Why this matters

Found in ALF (https://alf.physik.uni-wuerzburg.de/), an auxiliary-field quantum
Monte Carlo package, in a Euclidean-norm helper:

```fortran
Real (Kind=Kind(0.d0)) function Xnorm_R(x_p)
Real (Kind=Kind(0.d0)), dimension(:), intent(in) :: x_p
integer :: i
Xnorm_R = 0.d0
do i = 1, size(x_p)
Xnorm_R = Xnorm_R + x_p(i)*x_p(i) ! in-loop accumulation: correct
enddo
Xnorm_R = sqrt(Xnorm_R) ! dropped at -O1+
end function Xnorm_R
```

The loop accumulates correctly, so the function returned the sum of squares instead
of its square root. That norm gates a Brillouin-zone membership test, so the
lattice construction silently rejected almost every point and the program failed
much later with an unrelated-looking error. The code is standard-conforming — the
function result variable is a normal variable that may be read once defined — and
the failure is silent, which makes this pattern (`x = f(x)` before returning) a
hazard for any numerical Fortran code built with flang at default optimization.

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the failure with the shown bug.f90 program at -O0 and -O1, then compare the -emit-fir and -emit-mlir outputs. Trace the flang MLIR/FIR optimization pipeline to identify where the return is forwarded before the final store. Done means the reproducer returns sqrt(72) at optimized levels and has regression coverage.

Written by the indexing model from the issue text.

Assessment

Tech stack
fortran
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.