llvm / llvm/llvm-project

[flang] VOLATILE attribute acquired by host association is ignored in lowering (loads/stores not volatile)

Open
#208,588 1 comment 0 reactions 0 assignees View on GitHub
flang:fir-hlfir
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

When a variable acquires the `VOLATILE` attribute **locally through host association** — an internal subprogram or a BLOCK construct whose specification part contains `VOLATILE :: name` naming a host variable (F2023 8.5.20 p2, 19.5.1.4 p1) — flang's semantics resolves it correctly (the `HostAssocDetails` symbol carries `VOLATILE`, `-fdebug-dump-symbols` shows e.g. `n, VOLATILE: HostAssoc => n`), but **lowering drops the volatility entirely**: the `hlfir.declare` gets no `fortran_attrs` and no volatile-qualified type, so LLVM receives ordinary loads/stores and optimizes them as non-volatile. At `-O2` a canonical polling loop is miscompiled into a load-free unconditional infinite loop.

The equivalent **use-associated** case is handled correctly, so this is specific to the host-association path.

### Reproducers

Internal subprogram (`repro-1-hostassoc.f90`):

```fortran
subroutine host
integer :: n = 0
call inner
contains
subroutine inner
volatile :: n
do while (n == 0)
end do
end subroutine
end subroutine
```

BLOCK construct (`repro-2-block.f90`):

```fortran
subroutine blk
integer :: n = 0
block
volatile :: n
do while (n == 0)
end do
end block
end subroutine
```

Controls: a directly declared `integer, volatile :: n` local is handled correctly, and so is the use-associated equivalent (`repro-3-useassoc.f90`):

```fortran
module m
integer :: n = 0
end module
subroutine poll
use m
volatile :: n
do while (n == 0)
end do
end subroutine
```

### Actual behavior

`flang -fc1 -emit-hlfir repro-1-hostassoc.f90`: zero volatile markers. The captured variable's declare inside the internal procedure is plain:

```mlir
func.func private @_QFhostPinner() attributes {fir.host_symbol = @_QPhost, ...} {
%2:2 = hlfir.declare %1 {uniq_name = "_QFhostEn"} : (!fir.ref) -> (!fir.ref, !fir.ref)
```

Same for the BLOCK case (zero volatile markers). Consequently at `flang -S -emit-llvm -O2` the polling loop loses its load entirely — for both reproducers:

```llvm
define void @host_() local_unnamed_addr #0 {
br label %1
1: ; preds = %1, %0
br label %1
}
```

A conforming program whose volatile variable is modified by external means (the entire purpose of F2023 8.5.20) hangs.

### Expected behavior

What the correctly-handled use-association case produces (`repro-3-useassoc.f90`):

```mlir
%2 = fir.volatile_cast %1 : (!fir.ref) -> !fir.ref
%3:2 = hlfir.declare %2 {fortran_attrs = #fir.var_attrs, uniq_name = "_QMmEn"} : (!fir.ref) -> ...
```

and at `-O2` the loop keeps a volatile load:

```llvm
1: ; preds = %1, %0
%2 = load volatile i32, ptr @_QMmEn, align 4, !tbaa !4
%3 = icmp eq i32 %2, 0
br i1 %3, label %1, label %4
```

The host-association shapes should produce the same volatile-qualified accesses within the scoping unit that has the attribute (the host's own accesses correctly stay non-volatile).

### Analysis

(Note: this analysis was generated by AI, requires verification, don't just blindly implement it!)

The volatility of an `hlfir.declare` is driven purely by its `FortranVariableFlagsAttr`: `hlfir::DeclareOp::build`
(`flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp`, via `updateDeclaredInputTypeWithVolatility`) inserts the `fir.volatile_cast` and
volatile-qualifies the type when the flags contain `fortran_volatile`. That attr is computed by `Fortran::lower::translateSymbolAttributes`.

- `Fortran::lower::genDeclareSymbol` (`flang/lib/Lower/ConvertVariable.cpp`, the `fir::ExtendedValue` overload) passes **`sym.GetUltimate()`** to `translateSymbolAttributes`. `GetUltimate()` follows the `HostAssocDetails` link to the host symbol, which legitimately lacks `VOLATILE` — so the locally-acquired attribute is stripped. There is an in-tree FIXME at exactly this spot admitting the problem:
```cpp
// FIXME: Using the ultimate symbol for translating symbol attributes will
// lead to situations where the VOLATILE/ASYNCHRONOUS attributes are not
// propagated to the hlfir.declare (these attributes can be added when
// using module variables).
```
- The sibling static `genDeclareSymbol` overload in the same file (the `mlir::Value base` one) passes the **local** `sym` — which is why the use-association case works: the use-associated symbol's own `attrs()` carry `VOLATILE`.
- The designator lowering has the same pattern: `HlfirDesignatorBuilder` (`flang/lib/Lower/ConvertExprToHLFIR.cpp`) decides
volatility from `designatorNode.get().GetUltimate().attrs()`.
- The comment in `genSymbolType` (`flang/lib/Lower/ConvertType.cpp`, ~L248) still says building the type from the ultimate symbol "relies on the fact volatile and asynchronous are not reflected in fir types" — that premise is stale now that `!fir.ref` exists.

The same `GetUltimate()` stripping applies to a locally-acquired `ASYNCHRONOUS` attribute (per the FIXME), though that has no directly observable IR marker today.

Note: `-fdebug-dump-symbols` confirms semantics is right for both broken shapes (the BLOCK case matches the coverage in
`flang/test/Semantics/symbol05.f90`), so this is purely a lowering issue. A related-but-distinct semantics bug for *submodules* (where even the symbol identity is lost) is #208362; once that is fixed, submodule specification parts will presumably join the shapes affected by this lowering issue.

### Environment

```
flang version 23.0.0git (llvm-project commit d776ee4d4cae)
Target: x86_64-unknown-linux-gnu
Build config: +assertions
```

Contributor guide

Open the contributing guide

Research direction

Start in flang/lib/Lower/ConvertVariable.cpp, especially genDeclareSymbol and its FIXME, then inspect HlfirDesignatorBuilder in flang/lib/Lower/ConvertExprToHLFIR.cpp and genSymbolType in flang/lib/Lower/ConvertType.cpp. Run the supplied host-association reproducers through HLFIR and optimized LLVM emission, comparing them with repro-3-useassoc.f90. Done means host-associated VOLATILE accesses retain volatile-qualified declarations and loads while host-scope accesses remain non-volatile.

Written by the indexing model from the issue text.

Assessment

Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.