Flang currently splits the stack-vs-heap decision for arrays across two opposing,
mutually exclusive cleanup passes: stack-arrays (moves heap temporaries to the
stack, run only under -fstack-arrays) and memory-allocation-opt (moves stack
allocations to the heap, effectively a no-op in the default pipeline). Because
they run in isolation and pull in opposite directions, the default behaviour is
hard to reason about and to tune. Two defaults are notably off: every automatic
(runtime-sized) array is stack-allocated — risking stack overflow for large or
recursive cases — while every compiler temporary emitted by HLFIR lowering is
heap-allocated, adding malloc/free overhead even for tiny temporaries.
This RFC proposes replacing both passes with a single, policy-driven allocation-placement pass that classifies each allocation (user variable vs.
temporary; runtime vs. constant size; small vs. large) and places it
accordingly, with tunable thresholds and a hook for target/region-specific
policies. Relative to today, the default (-fno-stack-arrays) policy changes two
behaviours: automatic/runtime-sized arrays go on the heap (matching gfortran
and nvfortran, avoiding stack overflow), and small constant-size temporaries go
on the stack (avoiding needless heap traffic). -fstack-arrays keeps its
meaning: place everything on the stack, best-effort.
Existing compiler behaviour (S = stack, H = heap)
Columns: User = named local array, Temp = compiler temporary; dyn = runtime
size, small/big = small/large constant size.
Compiler / mode
User dyn
User small
User big
Temp dyn
Temp small
Temp big
gfortran
H
S
S
S¹
S
S²
gfortran -fstack-arrays
S
S
S
S¹
S
S
nvfortran
H
S
S
H
H³
H³
nvfortran -Mstack_arrays
S
S
S
H
H³
H³
ifx
S
S
S
S
S
S
ifx -heap-arrays
H
S
S
H
H
H
flang (today)
S
S
S
H
H
H
flang (today) -fstack-arrays
S
S
S
S
S
S
flang (proposed)
H
S
S
H
S
H
flang (proposed) -fstack-arrays
S
S
S
S
S
S
¹ gfortran heaps array-constructor temporaries of runtime size (even with -fstack-arrays); cells show elementwise expression temporaries.
² gfortran heaps constant-size temporaries larger than ~64 KB
(-fmax-stack-var-size).
³ nvfortran keeps constant-size array-constructor temporaries on the stack;
cells show elementwise expression temporaries.
Implementation
This RFC is implemented by the following stack of LLVM PRs (merge in order):
This looks good to me in principle and is frequently requested by users.
It is unlikely, but please could you check for any serious benchmark regressions in the default pipeline? Users wanting peak performance should use -fstack-arrays, and it sounds like that won’t change, so I wouldn’t want to block this over a small regression. But I would prefer to avoid a large regression in the default pipeline.
Yes, -fstack-array behavior is unchanged and I agree it is the reference used when people run benchmarks/wants the best CPU perf.
I did a few runs with -O3 -ffast-math -fno-protect-parens -march=native (equivalent to -Ofast -fno-stack-arrays) on an AMD EPYC 9334, and there are a some changes in both directions.
The main impact is a big slow down on WRF that slows down from 123s to 255s at -fno-stack-arrays. You can also see that Polyhedron nf_11 and Spec bwaves gets some slow down with the new -fno-stack-array. This is all due to the move of automatic array on the heap. On the contrary, tonto from Spec gets some speed-up thanks to the move of small constant size array temporaries on the stack (closer perf to -fstack-arrays). So the proposed change will have some impact -fno-stack-arrays per in both directions. Overall, I think it is a net usability win for non HPC users to not have to use unlimited stack by default, and it looks like an acceptable trade-off for the HPC users of -fno-stack-arrays.
Polyhedron
Curent -fno-stack-arrays
Proposed -fno-stack-arrays
ac_11
4.425
4.426
aermod_11
3.931
3.817
air_11
1.535
1.527
capacita_11
12.64
12.61
channel2_11
25.35
25.38
doduc_11
5.237
5.19
fatigue2_11
32.79
32.82
gas_dyn2_11
24.34
24.47
induct2_11
17.45
17.46
linpk_11
1.735
1.783
mdbx_11
3.987
3.955
mp_prop_design_11
40.38
40.46
nf_11
3.153
4.389
protein_11
10.44
10.34
rnflow_11
9.267
9.298
tfft2_11
15.52
15.36
SPEC FP2006
410.bwaves(FTN)
153.2
160.6
416.gamess(FTN)
289.1
288.3
434.zeusmp(FTN)
82.69
83.59
437.leslie3d(FTN)
84.3
84.86
459.GemsFDTD(FTN)
138.5
138.2
465.tonto(FTN)
138.9
129.1
481.wrf(FTN,C)
124
255.23
SPEC FP2000
168.wupwise(FTN)
24.1
24.14
171.swim(FTN)
20.86
20.9
172.mgrid(FTN)
17.42
17.46
173.applu(FTN)
36.91
36.82
178.galgel(FTN)
11.76
11.72
187.facerec(FTN)
14.18
14.57
189.lucas(FTN)
14.32
14.31
191.fma3d(FTN)
23.14
23.39
301.apsi(FTN)
24.6
24.89
Note that WRF is already noticeably faster with -fstack-arrays than it is with -fno-stack-arrays (98s vs 123s) and the -fstack-arrays results are unchanged.
Here are some numbers of mine for wrf_r in spec2017 (I had that more easily to hand than 2006) on an aarch64 machine.
Compiler
-fstack-arrays
Runtime vs base Flang
Effect of -fstack-arrays
Base Flang 11640c8923e0
No
Baseline
—
RFC Flang cbf4a631fdbc
No
3.8% slower
—
gfortran 13.3.0
No
2.3% slower
—
Base Flang 11640c8923e0
Yes
Baseline
Unchanged vs baseline
RFC Flang cbf4a631fdbc
Yes
Unchanged vs baseline
3.7% faster
gfortran 13.3.0
Yes
0.9% faster
3.1% faster
I was interested in how gfortran compares as another compiler which puts automatic arrays on the heap. It is hard to extrapolate from this one result but it seems plausible that a worse result is inherent in using the heap for array temporaries on this benchmark.
These numbers look much less concerning than specfp2006 wrf. I wonder if something could be improved for that particular benchmark to make the impact less severe.
If my AI didn’t hallucinate, given your numbers: SPECFP2000 shows negligible change in geomean, polyhedron is 1.88% worse (mostly because of linkpk_11); but the spec2006 regression is quite bad. Over all three suites, if spec2006 wrf is excluded, the regression is only 1.08%.
I think something on the order of of 1% overall is acceptable if either
We find a way to improve the 106% regression in spec2006 wrf
Or if it turns out every compiler you listed other than llvm-flang (without extra flags) scores similarly on spec2006 wrf
(And similar if we discover other benchmarks with massive regressions like wrf).
I am open to other suggestions on how to address this without dramatically worsening flang by default.
Given the risk of regressions it would also be helpful if you include driver flags to emulate the old default.