[flang] Fix references to a LOCAL variable of an enclosing DO CONCURRENT (#238647)
A `REDUCE` locality-spec on a nested `DO CONCURRENT` that names a
variable with `LOCAL` or `LOCAL_INIT` locality in the enclosing `DO
CONCURRENT` crashes lowering:
```fortran
subroutine s(n, nz, work)
integer :: n, nz, work(nz), j, k, tmp
do concurrent (k=1:nz) local(tmp)
tmp = 0
do concurrent (j=1:n) reduce(max:tmp)
tmp = max(tmp, j)
end do
work(k) = tmp
end do
end subroutine
```
```
Assertion `detail::isPresent(Val) && "dyn_cast on a non-existent value"' failed.
...
ReductionProcessor::processReductionArguments<fir::DeclareReductionOp, ...>
FirConverter::handleLocalitySpecs
```
Related references to such a variable silently used the wrong storage:
- a `VOLATILE` statement for it in a `BLOCK` gave the `BLOCK` its own
copy;
- a `BLOCK` that makes it implicitly `ASYNCHRONOUS` used the
subprogram's variable;
- a nested `LOCAL_INIT` copy was initialized from the subprogram's
variable instead of the enclosing construct's copy (F2023 11.1.7.5),
with delayed privatization (the default).
### Root cause
- `ScopeHandler::MakeHostAssocSymbol` copies all flags of the host
symbol. When the host is a construct entity of an enclosing `DO
CONCURRENT`, those flags include that construct's locality.
- `SymMap` keys a symbol with `LOCAL`/`LOCAL_INIT` locality on the
symbol itself and every other symbol on its ultimate symbol. A
host-associated name that inherited the flag got its own key: for a
nested `REDUCE` entity that key is bound only after
`processReductionArguments` runs (null address, the crash), and for a
`VOLATILE` name in a `BLOCK` it got fresh storage. A host-associated
name without the flag was keyed on the subprogram's variable, bypassing
the enclosing construct's copy.
- DO CONCURRENT delayed privatization looked up the original value from
the ultimate symbol.
### Fix
- **Semantics** (`resolve-names.cpp`): `MakeHostAssocSymbol` does not
copy the `Locality*` flags. Locality belongs to the construct entity
that owns the storage.
- **Lowering** (`SymbolMap.h/.cpp`): `SymMap::getKey` walks the
host-association chain and keys a symbol on the nearest
`LOCAL`/`LOCAL_INIT` entity, falling back to the ultimate symbol as
before. It replaces the previous key computation at all eight sites.
- **Lowering** (`Support/Utils.cpp`): `privatizeSymbol` looks up a DO
CONCURRENT locality entity's original value from its host symbol, so it
reaches the enclosing construct's copy. The ultimate symbol still names
the privatizer.
### Behavior changes beyond the crash
- A `LOCAL` entity nested in a `LOCAL_INIT` entity no longer inherits
`LOCAL_INIT`. It now gets a `local` locality op instead of `local_init`.
Both conform, since a `LOCAL` variable starts undefined.
- An OpenACC compute construct around an outer `LOCAL` / inner `REDUCE`
DO CONCURRENT nest used to stop with "not yet implemented: lowering
symbol to HLFIR". It now lowers.
### Tests
- `flang/test/Lower/do_concurrent_nested_locality.f90` (new): inner
`REDUCE` in an enclosing `LOCAL` and `LOCAL_INIT`, two `REDUCE` levels,
`LOCAL_INIT` in `LOCAL`, `LOCAL` in `LOCAL_INIT`, and `BLOCK`s with an
explicit `VOLATILE` statement and with implied `ASYNCHRONOUS`. It covers
delayed and non-delayed privatization where they differ.
- `flang/test/Semantics/symbol09.f90`: nested locality-specs and a
`VOLATILE` name in a `BLOCK` carry only their own locality.
`check-flang` passes.
Assisted-by: AI