Skip to content

Commit 4211b36

Browse files
committed
target: narrow the de-fusion claim to the backend that actually does it
1d8184f generalized from two x86 configurations to "reassoc de-fuses where the target has no fused instruction". Three 32-bit ARM DSP configurations falsify that: Cortex-M3 with no FPU and Cortex-A9 on VFPv3 both keep the fmaf call under reassoc contract, and Cortex-M4F keeps vfma.f32. So contract never de-fuses anywhere measured, and reassoc de-fuses on the X86 backend without +fma and nowhere else. The hazard is a per-backend expansion policy rather than a target-capability rule, which makes the case for consuming permissions stronger, not weaker: a delegated permission cannot be reasoned about from the target at all. The three ARM rows are the DSP-relevant configurations, so they are worth pinning on their own account - Cortex-M is where embedded DSP runs and has no 64-bit variant.
1 parent 1d8184f commit 4211b36

3 files changed

Lines changed: 55 additions & 25 deletions

File tree

docs/f32-contract-evidence.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ them apart:
3636
The audit point is the translated `.ll`: this flow runs no LLVM middle end, so
3737
permissions are final there. It is *not* the final point for the realized
3838
event graph, because `llc` still consumes fast-math flags — a `reassoc` on
39-
`llvm.fma` is enough for the backend to de-fuse it where the target has no
40-
fused instruction, and not where it has one
41-
(`test/Target/fp_permission_fmf.ll`). A delegated permission therefore makes
42-
the realized graph target dependent, which is why emitted is empty rather than
43-
merely bounded.
39+
`llvm.fma` is enough for the X86 backend without +fma to de-fuse it, while
40+
AArch64 and the 32-bit ARM DSP targets keep it fused even with no fused
41+
instruction to keep (`test/Target/fp_permission_fmf.ll`). Which graph runs is
42+
therefore a per-backend expansion policy that a delegated permission cannot
43+
bound, which is why emitted is empty rather than merely bounded.
4444

4545
`fast` permits two rewrites: `ReassociateReductionTerms` (regroup a
4646
reduction's additive tree) and `FuseMultiplyAdd` (select a fused event for a

include/ondrix/Dialect/ondsp/IR/OndspSemantics.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ enum class FastPermission {
3333
/// the operation — hands the choice to LLVM; no lowering does, so there is no
3434
/// entry point for it here.
3535
///
36-
/// Delegation is not portable: de-fusion of `llvm.fma` needs both `reassoc`
37-
/// and a target with no fused instruction, so a delegated permission makes the
38-
/// realized graph target dependent from one declaration. Measured, not derived
39-
/// — `test/Target/fp_permission_fmf.ll` pins it.
36+
/// Delegation is not portable: `reassoc` lets the X86 backend without +fma
37+
/// expand `llvm.fma`, while AArch64 and the 32-bit ARM DSP targets keep it
38+
/// fused even with no fused instruction to keep. Which graph runs is a
39+
/// per-backend expansion policy, so a delegated permission cannot be reasoned
40+
/// about from the target. Measured — `test/Target/fp_permission_fmf.ll`.
4041
inline mlir::arith::FastMathFlags consumeFastPermission(FastPermission permission) {
4142
(void)permission;
4243
return mlir::arith::FastMathFlags::none;

test/Target/fp_permission_fmf.ll

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
; RUN: llc --version | FileCheck %s --check-prefix=TOOLCHAIN
22
; RUN: llc -mtriple=x86_64-unknown-linux-gnu -mcpu=x86-64 -mattr=-fma,-fma4 %s -o - | FileCheck %s --check-prefix=NOFMA
33
; RUN: llc -mtriple=x86_64-unknown-linux-gnu -mcpu=x86-64 -mattr=+fma %s -o - | FileCheck %s --check-prefix=X86FMA --implicit-check-not=mulss
4-
; RUN: llc -mtriple=aarch64-unknown-linux-gnu %s -o - | FileCheck %s --check-prefix=ARM --implicit-check-not=fmul
4+
; RUN: llc -mtriple=aarch64-unknown-linux-gnu %s -o - | FileCheck %s --check-prefix=A64 --implicit-check-not=fmul
5+
; RUN: llc -mtriple=thumbv7em-none-eabi -mcpu=cortex-m4 -float-abi=hard %s -o - | FileCheck %s --check-prefix=M4F --implicit-check-not=vmul
6+
; RUN: llc -mtriple=thumbv7m-none-eabi -mcpu=cortex-m3 %s -o - | FileCheck %s --check-prefix=M3 --implicit-check-not=vmul
7+
; RUN: llc -mtriple=armv7a-none-eabi -mcpu=cortex-a9 -float-abi=hard %s -o - | FileCheck %s --check-prefix=A9 --implicit-check-not=vmul
58

69
; Measured, not derived: LangRef specifies llvm.fma as one fused event and one
7-
; of these configurations expands it anyway. Version, triple, cpu and mattr are
8-
; all pinned because another toolchain has to be measured again.
10+
; of these configurations expands it anyway.
911
;
10-
; De-fusion needs both reassoc and a target with no fused instruction, so a
11-
; delegated permission makes the realized graph target dependent. Rationale:
12-
; consumeFastPermission in OndspSemantics.h.
12+
; contract never de-fuses. reassoc de-fuses on the X86 backend without +fma and
13+
; nowhere else measured - not on AArch64, and not on the 32-bit ARM DSP targets
14+
; even where there is no fused instruction and the fallback is a libm call. So
15+
; the hazard is a per-backend expansion policy, not a target-capability rule,
16+
; and a delegated permission cannot be reasoned about from the target alone.
17+
; Rationale: consumeFastPermission in OndspSemantics.h.
1318

1419
; TOOLCHAIN: LLVM version 17.0.6
1520

@@ -19,33 +24,51 @@ declare float @llvm.fma.f32(float, float, float)
1924
; NOFMA: fmaf
2025
; X86FMA-LABEL: unflagged:
2126
; X86FMA: vfmadd
22-
; ARM-LABEL: unflagged:
23-
; ARM: fmadd
27+
; A64-LABEL: unflagged:
28+
; A64: fmadd
29+
; M4F-LABEL: unflagged:
30+
; M4F: vfma.f32
31+
; M3-LABEL: unflagged:
32+
; M3: bl{{.*}}fmaf
33+
; A9-LABEL: unflagged:
34+
; A9: {{b|bl}}{{.*}}fmaf
2435
define float @unflagged(float %a, float %b, float %c) {
2536
%r = call float @llvm.fma.f32(float %a, float %b, float %c)
2637
ret float %r
2738
}
2839

29-
; contract alone never licenses de-fusion, on any of the three.
40+
; contract alone never licenses de-fusion anywhere measured.
3041
; NOFMA-LABEL: contract_only:
3142
; NOFMA: fmaf
3243
; X86FMA-LABEL: contract_only:
3344
; X86FMA: vfmadd
34-
; ARM-LABEL: contract_only:
35-
; ARM: fmadd
45+
; A64-LABEL: contract_only:
46+
; A64: fmadd
47+
; M4F-LABEL: contract_only:
48+
; M4F: vfma.f32
49+
; M3-LABEL: contract_only:
50+
; M3: bl{{.*}}fmaf
51+
; A9-LABEL: contract_only:
52+
; A9: {{b|bl}}{{.*}}fmaf
3653
define float @contract_only(float %a, float %b, float %c) {
3754
%r = call contract float @llvm.fma.f32(float %a, float %b, float %c)
3855
ret float %r
3956
}
4057

41-
; reassoc does, but only where there is no fused instruction to keep.
58+
; reassoc does, on the X86 backend without +fma and nowhere else.
4259
; NOFMA-LABEL: reassoc_only:
4360
; NOFMA: mulss
4461
; NOFMA: addss
4562
; X86FMA-LABEL: reassoc_only:
4663
; X86FMA: vfmadd
47-
; ARM-LABEL: reassoc_only:
48-
; ARM: fmadd
64+
; A64-LABEL: reassoc_only:
65+
; A64: fmadd
66+
; M4F-LABEL: reassoc_only:
67+
; M4F: vfma.f32
68+
; M3-LABEL: reassoc_only:
69+
; M3: bl{{.*}}fmaf
70+
; A9-LABEL: reassoc_only:
71+
; A9: {{b|bl}}{{.*}}fmaf
4972
define float @reassoc_only(float %a, float %b, float %c) {
5073
%r = call reassoc float @llvm.fma.f32(float %a, float %b, float %c)
5174
ret float %r
@@ -56,8 +79,14 @@ define float @reassoc_only(float %a, float %b, float %c) {
5679
; NOFMA: addss
5780
; X86FMA-LABEL: reassoc_contract:
5881
; X86FMA: vfmadd
59-
; ARM-LABEL: reassoc_contract:
60-
; ARM: fmadd
82+
; A64-LABEL: reassoc_contract:
83+
; A64: fmadd
84+
; M4F-LABEL: reassoc_contract:
85+
; M4F: vfma.f32
86+
; M3-LABEL: reassoc_contract:
87+
; M3: bl{{.*}}fmaf
88+
; A9-LABEL: reassoc_contract:
89+
; A9: {{b|bl}}{{.*}}fmaf
6190
define float @reassoc_contract(float %a, float %b, float %c) {
6291
%r = call reassoc contract float @llvm.fma.f32(float %a, float %b, float %c)
6392
ret float %r

0 commit comments

Comments
 (0)