There is no way to disable FMAs with ‘fast’ ops in LLVM. I would like to propose that LLVM’s -fp-contract=off should disable fusion, regardless of any other flags since the Clang option suggests this to be the case:
$ clang --help | grep fp-contract
-ffp-contract= Form fused FP ops (e.g. FMAs): fast (everywhere) | on (according to FP_CONTRACT pragma, default) | off (never fuse)
Current behaviour in LLVM 8.0 below:
$ cat fma.ll
define double @fmadd(double %a, double %b, double %c) {
%mul = fmul fast double %b, %a
%add = fadd fast double %mul, %c
ret double %add
}
$ llc -mattr=+fma fma.ll -fp-contract=off -o - | grep vfmadd
vfmadd213sd %xmm2, %xmm1, %xmm0 # xmm0 = (xmm1 * xmm0) + xmm2
It still generates an fma due to the logic in DAGCombiner:
bool CanFuse = Options.UnsafeFPMath || isContractable(N);
bool AllowFusionGlobally = (Options.AllowFPOpFusion == FPOpFusion::Fast ||
CanFuse || HasFMAD);
In this case, UnsafeFPMath is false but isContractable() is true since the FADD node is tagged as ‘fast’. A simple fix would just be to check for -fp-contract=off, however, I also found there is disagreement in the LLVM -fp-contract option itself:
in TargetOptions.h, =off maps to FPOpFusion::Strict and says “Never fuse FP-ops”, yet the actual cl::opt for =off/Strict says: “Only fuse FP ops when the result won’t be affected”.
Which is it supposed to be? At a minimum we should clear up the discrepancy, but there are two general approaches I see:
Option 1:
-
rename Strict to Off in llvm and always diable FMAs with this option
-
does not require changes to Clang
Example logic:
bool AllowFusionGlobally = Options.AllowFPOpFusion != FPOpFusion::Off &&
(Options.AllowFPOpFusion == FPOpFusion::Fast ||
CanFuse || HasFMAD);
Option 2:
-
keep =strict, add =off to turn off FMAs
-
add =strict to clang as it does not currently exist
-
tie uses of ::Strict to the presence of FMAD (which might have been the intention?)
Example logic:
bool AllowFusionGlobally = Options.AllowFPOpFusion != FPOpFusion::Off &&
(Options.AllowFPOpFusion == FPOpFusion::Fast ||
CanFuse ||
(HasFMAD && Options.AllowFPOpFusion == FPOpFusion::Strict));
Is there context I am not aware of for ::Strict and ISD::FMAD? I could see value in generating FMAs when its expanded form would be identical – but curious if that was actually the intent or not. If it is, perhaps we could allow “Standard/on” to fuse ops if FMAD is available instead of the “Strict” level? In any case, we should still have a way to explicitly turn off FMAs.
Thanks,
Scott