llvm / llvm/llvm-project

[Flang][FIR] `fir.array_coor` on non-pointer `intent(in)` assumed-shape arguments uses dynamic box stride instead of compile-time element size

Open
#220,386 1 comment 0 reactions 0 assignees View on GitHub
flang
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

For a non-pointer, non-allocatable `intent(in)` assumed-shape dummy argument, flang generates `fir.array_coor` with a box-typed memref (`!fir.box>`). In `XArrayCoorOpConversion` in `CodeGen.cpp`, `baseIsBoxed` is true, so CodeGen loads the byte stride from the descriptor on every element access. For scalar element access (`ext(i)` where `i` is a loop index), the stride is loop-invariant and — if the array is non-pointer and non-allocatable — statically known to be `sizeof(T)`. By keeping the stride load opaque inside `fir.array_coor` until CodeGen, it prevents LLVM from hoisting the stride load or optimizing it away entirely. GFortran eliminates this overhead via IPA-SRA, creating a `.isra.0` clone with flat pointer arguments.

## Reproducer

flang version 24.0.0

```
flang-new -O3 -march=z17 -c test_assumed_shape.f90
```

```fortran
module params
implicit none
integer, parameter :: nswbands = 19
integer, parameter :: pcols = 16
integer, parameter :: pver = 26
end module params

module rad_cnst
implicit none
contains
subroutine get_aer_props(ext, ssa, asm)
real(8), pointer, intent(out) :: ext(:)
real(8), pointer, intent(out) :: ssa(:)
real(8), pointer, intent(out) :: asm(:)
real(8), target, save :: ext_d(19)
real(8), target, save :: ssa_d(19)
real(8), target, save :: asm_d(19)
ext => ext_d
ssa => ssa_d
asm => asm_d
end subroutine
end module rad_cnst

module compute_mod
use params
implicit none
private
public :: aer_rad_props_sw
contains
subroutine get_nonhygro_rad_props(ncol, mass, ext, ssa, asm, tau, tau_w, tau_w_g, tau_w_f)
integer, intent(in) :: ncol
real(8), intent(in) :: mass(pcols, pver)
real(8), intent(in) :: ext(:)
real(8), intent(in) :: ssa(:)
real(8), intent(in) :: asm(:)
real(8), intent(out) :: tau (pcols, pver, nswbands)
real(8), intent(out) :: tau_w (pcols, pver, nswbands)
real(8), intent(out) :: tau_w_g(pcols, pver, nswbands)
real(8), intent(out) :: tau_w_f(pcols, pver, nswbands)
integer :: iswband
real(8) :: ext1, ssa1, asm1
do iswband = 1, nswbands
ext1 = ext(iswband)
ssa1 = ssa(iswband)
asm1 = asm(iswband)
tau (1:ncol, 1:pver, iswband) = mass(1:ncol, 1:pver) * ext1
tau_w (1:ncol, 1:pver, iswband) = mass(1:ncol, 1:pver) * ext1 * ssa1
tau_w_g(1:ncol, 1:pver, iswband) = mass(1:ncol, 1:pver) * ext1 * ssa1 * asm1
tau_w_f(1:ncol, 1:pver, iswband) = mass(1:ncol, 1:pver) * ext1 * ssa1 * asm1 * asm1
enddo
end subroutine get_nonhygro_rad_props

subroutine aer_rad_props_sw(ncol, numaerosols, mass, tau, tau_w, tau_w_g, tau_w_f)
use rad_cnst, only: get_aer_props
integer, intent(in) :: ncol
integer, intent(in) :: numaerosols
real(8), intent(in) :: mass(pcols, pver)
real(8), intent(out) :: tau (pcols, pver, nswbands)
real(8), intent(out) :: tau_w (pcols, pver, nswbands)
real(8), intent(out) :: tau_w_g(pcols, pver, nswbands)
real(8), intent(out) :: tau_w_f(pcols, pver, nswbands)
real(8), pointer :: n_ext(:), n_ssa(:), n_asm(:)
real(8) :: ta(pcols,pver,nswbands), tw(pcols,pver,nswbands)
real(8) :: twg(pcols,pver,nswbands), twf(pcols,pver,nswbands)
integer :: iaerosol
tau = 0.0d0; tau_w = 0.0d0; tau_w_g = 0.0d0; tau_w_f = 0.0d0
do iaerosol = 1, numaerosols
call get_aer_props(n_ext, n_ssa, n_asm)
call get_nonhygro_rad_props(ncol, mass, n_ext, n_ssa, n_asm, ta, tw, twg, twf)
tau(1:ncol,1:pver,:) = tau(1:ncol,1:pver,:) + ta(1:ncol,:,:)
tau_w(1:ncol,1:pver,:) = tau_w(1:ncol,1:pver,:) + tw(1:ncol,:,:)
enddo
end subroutine aer_rad_props_sw
end module compute_mod
```

## FIR (`-mmlir --mlir-print-ir-before=loop-versioning`)

`nswbands = 19` is a compile-time constant. Inside the `iswband = 1, nswbands` loop in `get_nonhygro_rad_props`:

```mlir
%29 = fir.array_coor %10 %28 : (!fir.box>, i64) -> !fir.ref
%33 = fir.array_coor %18 %32 : (!fir.box>, i64) -> !fir.ref
%37 = fir.array_coor %7 %36 : (!fir.box>, i64) -> !fir.ref
```

`%10`, `%18`, `%7` are the `fir.declare` results for `ext`, `ssa`, `asm` — all `!fir.box>`. In `XArrayCoorOpConversion::doRewrite` (`CodeGen.cpp`), `baseIsBoxed = mlir::isa(coor.getMemref().getType())` is true for all three, so CodeGen calls `getStrideFromBox` on every access instead of using `sizeof(f64)`.

## Assembly (`-O3 -march=z17`)

The generated assembly shows that stride and offset resolution for `ext`, `ssa`, `asm` is dynamic within the `iswband` loop body (`.LBB1_40`), based on descriptor fields spilled to the stack.

```asm
.LBB1_39:
la %r1, 1(%r1) ; ++iswband
cgije %r1, 20, .LBB1_73 ; exit if iswband == 20
.LBB1_40:
chsi 164(%r15), 0 ; LoopVersioning contiguity check
jle .LBB1_39
lg %r1, 184(%r15) ; iswband
lg %r5, 168(%r15) ; ext byte stride (from descriptor, spilled to stack)
aghi %r1, -1
msgrkc %r5, %r1, %r5 ; ext offset = (iswband-1) * stride
ld %f0, 0(%r5,%r11) ; ext(iswband)
lg %r5, 176(%r15) ; ssa byte stride (from descriptor, spilled to stack)
msgrkc %r5, %r1, %r5 ; ssa offset = (iswband-1) * stride
...
msgc %r1, 216(%r15) ; asm offset = (iswband-1) * stride
```

GFortran creates an IPA-SRA clone (`.isra.0`) with flat pointer arguments. The induction variable advances by a compile-time static element size (`sizeof(f64) = 8`).

```asm
ag %r4, 200(%r15) ; advance ext pointer by element size
ag %r5, 208(%r15) ; advance ssa pointer
ag %r8, 216(%r15) ; advance asm pointer
brctg %r3, .L126 ; decrement counter, branch if != 0
.L126:
j .L29 ; next iswband iteration
.L29:
vleg %v20, 0(%r11), 0 ; ext(iswband) — direct load
vleg %v22, 0(%r12), 0 ; ssa(iswband)
vleg %v17, 0(%r1), 0 ; asm(iswband)
```

Contributor guide

Open the contributing guide

Research direction

Start in CodeGen.cpp at XArrayCoorOpConversion::doRewrite and inspect how boxed memrefs reach getStrideFromBox. Reproduce with the provided Fortran program using flang-new -O3 -march=z17, then inspect FIR and assembly. Done means non-pointer, non-allocatable intent(in) assumed-shape scalar accesses use the compile-time element size without repeated descriptor stride loads, while existing behavior remains correct.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.