-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[InstCombine] Ensure Safe Handling of Flags in foldFNegIntoConstant #94148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
return BinaryOperator::CreateFMulFMF( | ||
X, NegC, | ||
I.getFastMathFlags() & | ||
cast<FPMathOperator>(I.getOperand(0))->getFastMathFlags()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pull the cast to FPMathOperator up and use it for the no-infs check?
return BinaryOperator::CreateFMulFMF( | ||
X, NegC, | ||
I.getFastMathFlags() & | ||
cast<FPMathOperator>(I.getOperand(0))->getFastMathFlags()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the correct logic is and flags, but can or-in NSZ or nnan from either op
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we do it this way?
FastMathFlags FMF = cast<FPMathOperator>(I.getOperand(0))->getFastMathFlags();
if (I.getFastMathFlags().noInfs() && !FMF.noInfs()) {
return BinaryOperator::CreateFMulFMF(X, NegC, FMF);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It really ought to be a function that produces the new correct FMF, rather than conditionally preserving the original flags
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, we need to check all alive2 cases and then generate the flags.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alive2: https://alive2.llvm.org/ce/z/DHmA7a
I apologize for the delay; I was quite busy. Based on my observation, I came up with this combination: if we have nnan
in any instruction, we can propagate flags to our transformation.
FastMathFlags NF1 = F1 & F2;
FastMathFlags NF2 = F1 | F2;
if (NF2.noNaNs()) {
return F2;
}
if (NF1.any() || (F1.none() && F2.none())) {
return NF1;
}
if (F1.all() || F2.all()) {
return F2;
}
return NF1;
Let me know what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NF1.any() || (F1.none() && F2.none())
case is equivalent to just NF1.any.
Not sure why you need the all || all fixup either
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right. I was just making combinations, so is that what you're looking for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ninf is the only problematic flag, since ninf on the fneg does not imply fneg on fmul, and fmul inf * 0 = nan.
So you need something like
Flags = FnegFlags | FMulFlags
Flags.ninf &= FmulFlags.ninf
@@ -2653,8 +2653,15 @@ static Instruction *foldFNegIntoConstant(Instruction &I, const DataLayout &DL) { | |||
// Fold negation into constant operand. | |||
// -(X * C) --> X * (-C) | |||
if (match(FNegOp, m_FMul(m_Value(X), m_Constant(C)))) | |||
if (Constant *NegC = ConstantFoldUnaryOpOperand(Instruction::FNeg, C, DL)) | |||
if (Constant *NegC = ConstantFoldUnaryOpOperand(Instruction::FNeg, C, DL)) { | |||
if (match(C, m_AnyZeroFP()) && I.getFastMathFlags().noInfs()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Directionally this feels wrong. A constant, but not known literal 0/-0 may end up resolving to a 0 (e.g. degenerate constant expression casts or undef). I think this should first be written to handle the flags conservatively regardless of the value and we can try to refine for the known-non-0 cases if necessary
Why not just copy FMF from FNegOp (the fmul) instead of from I (the fneg)? It's simple and correct and it seems pretty unlikely that you'd ever get a real world case where the flags on the fneg would add any extra useful information. |
Since it was breaking the |
Personally I think all cases in this function should be copying FMF from In some cases you might be able to infer additional flags from |
Maybe we should just update the expected output for that test? It is not at all clear to me that propagating |
This is a test we are breaking: ; With nsz and reassoc: Y - ((X * 5) + Y) --> X * -5
define float @sub_add_neg_x(float %x, float %y) {
; CHECK-LABEL: @sub_add_neg_x(
; CHECK-NEXT: [[R:%.*]] = fmul float [[X:%.*]], -5.000000e+00
; CHECK-NEXT: ret float [[R]]
;
%mul = fmul float %x, 5.000000e+00
%add = fadd float %mul, %y
%r = fsub nsz reassoc float %y, %add
ret float %r
} |
reverse ping |
677324d
to
bf2263b
Compare
If we directly use |
Do you have an example? Those tests might just be wrong |
@arsenm Now check that out. Does it look alright? |
FastMathFlags Flag = I.getFastMathFlags() | FNegOp->getFastMathFlags(); | ||
Flag.setNoInfs(I.getFastMathFlags().noInfs() && | ||
FNegOp->getFastMathFlags().noInfs()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid repeated calls to I.getFastMathFlags() and FNegOp->getFastMathFlags()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
%f2 = fneg nnan ninf <2 x float> %f1 | ||
ret <2 x float> %f2 | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a test where ninf is present on both, and is preservable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me know if we need more tests of that kind?.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ninf combinations are most important because you need to special case them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should also test reassoc on one, other, and both instructions. Best to require it be present on both instructions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@arsenm We require the reassoc
flag to be present on both instructions before propagating it, but only if noinf
is also present, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, there is no direct connection between reassoc and ninf. They both separately are anded
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change causes the test to fail:
; With nsz and reassoc: Y - ((X * 5) + Y) --> X * -5
define float @sub_add_neg_x(float %x, float %y) {
; CHECK-LABEL: @sub_add_neg_x(
; CHECK-NEXT: [[R:%.*]] = fmul nsz float [[X:%.*]], -5.000000e+00
; CHECK-NEXT: ret float [[R]]
;
%mul = fmul float %x, 5.000000e+00
%add = fadd float %mul, %y
%r = fsub nsz reassoc float %y, %add
ret float %r
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fail, meaning it now preserves nsz and not the reassoc? That's fine. That is more in line with what the LangRef states the rules are for reassoc: https://llvm.org/docs/LangRef.html#rewrite-based-flags
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
; With nsz and reassoc: Y - ((X * 5) + Y) --> X * -5
That transformation should require nnan
or isKnownNeverNaN(Y)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somewhere else's bug though
25542cd
to
8a40c65
Compare
%f1 = fmul ninf <2 x float> %a, <float 0.000000, float -0.000000> | ||
%f2 = fneg nnan ninf <2 x float> %f1 | ||
ret <2 x float> %f2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have tested the ninf preservation case above with the scalar case, but this covers it
%f2 = fneg nnan ninf <2 x float> %f1 | ||
ret <2 x float> %f2 | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ninf combinations are most important because you need to special case them
@llvm/pr-subscribers-llvm-transforms Author: None (SahilPatidar) ChangesFix #93769 alive2: https://alive2.llvm.org/ce/z/MHShQY Full diff: https://github.com/llvm/llvm-project/pull/94148.diff 2 Files Affected:
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
index e5c3a20e1a6487..9e9c3fc7b25fba 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAddSub.cpp
@@ -2757,8 +2757,13 @@ static Instruction *foldFNegIntoConstant(Instruction &I, const DataLayout &DL) {
// Fold negation into constant operand.
// -(X * C) --> X * (-C)
if (match(FNegOp, m_FMul(m_Value(X), m_Constant(C))))
- if (Constant *NegC = ConstantFoldUnaryOpOperand(Instruction::FNeg, C, DL))
- return BinaryOperator::CreateFMulFMF(X, NegC, &I);
+ if (Constant *NegC = ConstantFoldUnaryOpOperand(Instruction::FNeg, C, DL)) {
+ FastMathFlags FMF = I.getFastMathFlags();
+ FastMathFlags OpFMF = FNegOp->getFastMathFlags();
+ FastMathFlags Flag = FMF | OpFMF;
+ Flag.setNoInfs(FMF.noInfs() && OpFMF.noInfs());
+ return BinaryOperator::CreateFMulFMF(X, NegC, Flag);
+ }
// -(X / C) --> X / (-C)
if (match(FNegOp, m_FDiv(m_Value(X), m_Constant(C))))
if (Constant *NegC = ConstantFoldUnaryOpOperand(Instruction::FNeg, C, DL))
diff --git a/llvm/test/Transforms/InstCombine/fneg.ll b/llvm/test/Transforms/InstCombine/fneg.ll
index 3c4088832feaaa..4d68faf178d9cf 100644
--- a/llvm/test/Transforms/InstCombine/fneg.ll
+++ b/llvm/test/Transforms/InstCombine/fneg.ll
@@ -1109,4 +1109,106 @@ define float @test_fneg_select_maxnum(float %x) {
ret float %neg
}
+define float @test_fneg_ninf_mul_with_anyzero(float %a) {
+; CHECK-LABEL: @test_fneg_ninf_mul_with_anyzero(
+; CHECK-NEXT: [[F:%.*]] = fmul float [[A:%.*]], -0.000000e+00
+; CHECK-NEXT: ret float [[F]]
+;
+ %mul = fmul float %a, 0.0
+ %f = fneg ninf float %mul
+ ret float %f
+}
+
+define float @test_fsub_ninf_mul_with_anyzero(float %a) {
+; CHECK-LABEL: @test_fsub_ninf_mul_with_anyzero(
+; CHECK-NEXT: [[F2:%.*]] = fmul nsz float [[A:%.*]], -0.000000e+00
+; CHECK-NEXT: ret float [[F2]]
+;
+ %f1 = fmul nsz float %a, 0.000000
+ %f2 = fsub ninf float -0.000000, %f1
+ ret float %f2
+}
+
+define float @test_fneg_nnan_mul_with_anyzero(float %a) {
+; CHECK-LABEL: @test_fneg_nnan_mul_with_anyzero(
+; CHECK-NEXT: [[TMP1:%.*]] = fneg nnan float [[A:%.*]]
+; CHECK-NEXT: [[F2:%.*]] = call nnan float @llvm.copysign.f32(float 0.000000e+00, float [[TMP1]])
+; CHECK-NEXT: ret float [[F2]]
+;
+ %f1 = fmul ninf float %a, 0.000000
+ %f2 = fneg nnan float %f1
+ ret float %f2
+}
+
+define float @test_fneg_nsz_mul_with_anyzero(float %a) {
+; CHECK-LABEL: @test_fneg_nsz_mul_with_anyzero(
+; CHECK-NEXT: [[F2:%.*]] = fmul nsz float [[A:%.*]], -0.000000e+00
+; CHECK-NEXT: ret float [[F2]]
+;
+ %f1 = fmul ninf float %a, 0.000000
+ %f2 = fneg nsz float %f1
+ ret float %f2
+}
+
+define float @test_fneg_ninf_mul_nnan_with_const(float %a) {
+; CHECK-LABEL: @test_fneg_ninf_mul_nnan_with_const(
+; CHECK-NEXT: [[TMP1:%.*]] = fneg float [[A:%.*]]
+; CHECK-NEXT: [[F2:%.*]] = call float @llvm.copysign.f32(float 0.000000e+00, float [[TMP1]])
+; CHECK-NEXT: ret float [[F2]]
+;
+ %f1 = fmul nnan float %a, 0.000000
+ %f2 = fneg ninf float %f1
+ ret float %f2
+}
+
+define float @test_fneg_ninf_mul_nsz_with_const(float %a) {
+; CHECK-LABEL: @test_fneg_ninf_mul_nsz_with_const(
+; CHECK-NEXT: [[F2:%.*]] = fmul nsz float [[A:%.*]], -0.000000e+00
+; CHECK-NEXT: ret float [[F2]]
+;
+ %f1 = fmul nsz float %a, 0.000000
+ %f2 = fneg ninf float %f1
+ ret float %f2
+}
+
+define <2 x float> @test_fneg_ninf_mul_nnan_with_vec_const(<2 x float> %a) {
+; CHECK-LABEL: @test_fneg_ninf_mul_nnan_with_vec_const(
+; CHECK-NEXT: [[F2:%.*]] = fmul nnan <2 x float> [[A:%.*]], <float -0.000000e+00, float 0.000000e+00>
+; CHECK-NEXT: ret <2 x float> [[F2]]
+;
+ %f1 = fmul nnan <2 x float> %a, <float 0.000000, float -0.000000>
+ %f2 = fneg ninf <2 x float> %f1
+ ret <2 x float> %f2
+}
+
+define <2 x float> @test_fneg_ninf_mul_nsz_with_vec_const(<2 x float> %a) {
+; CHECK-LABEL: @test_fneg_ninf_mul_nsz_with_vec_const(
+; CHECK-NEXT: [[F2:%.*]] = fmul nsz <2 x float> [[A:%.*]], <float -0.000000e+00, float 0.000000e+00>
+; CHECK-NEXT: ret <2 x float> [[F2]]
+;
+ %f1 = fmul nsz <2 x float> %a, <float 0.000000, float -0.000000>
+ %f2 = fneg ninf <2 x float> %f1
+ ret <2 x float> %f2
+}
+
+define <2 x float> @test_fneg_nnan_ninf_mul_with_vec_const(<2 x float> %a) {
+; CHECK-LABEL: @test_fneg_nnan_ninf_mul_with_vec_const(
+; CHECK-NEXT: [[F2:%.*]] = fmul nnan <2 x float> [[A:%.*]], <float -0.000000e+00, float 0.000000e+00>
+; CHECK-NEXT: ret <2 x float> [[F2]]
+;
+ %f1 = fmul <2 x float> %a, <float 0.000000, float -0.000000>
+ %f2 = fneg nnan ninf <2 x float> %f1
+ ret <2 x float> %f2
+}
+
+define <2 x float> @test_fneg_nnan_ninf_mul_ninf_with_vec_const(<2 x float> %a) {
+; CHECK-LABEL: @test_fneg_nnan_ninf_mul_ninf_with_vec_const(
+; CHECK-NEXT: [[F2:%.*]] = fmul nnan ninf <2 x float> [[A:%.*]], <float -0.000000e+00, float 0.000000e+00>
+; CHECK-NEXT: ret <2 x float> [[F2]]
+;
+ %f1 = fmul ninf <2 x float> %a, <float 0.000000, float -0.000000>
+ %f2 = fneg nnan ninf <2 x float> %f1
+ ret <2 x float> %f2
+}
+
!0 = !{}
|
%f2 = fneg nnan ninf <2 x float> %f1 | ||
ret <2 x float> %f2 | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should also test reassoc on one, other, and both instructions. Best to require it be present on both instructions
FastMathFlags FMF = I.getFastMathFlags(); | ||
FastMathFlags OpFMF = FNegOp->getFastMathFlags(); | ||
FastMathFlags Flag = FMF | OpFMF; | ||
Flag.setNoInfs(FMF.noInfs() && OpFMF.noInfs()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should and the rewrite flags too (reassoc, contract, afn). We probably should have a helper for the rewrite flags
e2e5860
to
cad89bf
Compare
static FastMathFlags combineFastMathFlagsForFNeg(FastMathFlags FMF, | ||
FastMathFlags OpFMF) { | ||
FastMathFlags Flag = FMF | OpFMF; | ||
Flag.setNoInfs(FMF.noInfs() && OpFMF.noInfs()); | ||
Flag.setAllowReassoc(FMF.allowReassoc() && OpFMF.allowReassoc()); | ||
Flag.setApproxFunc(FMF.approxFunc() && OpFMF.approxFunc()); | ||
Flag.setAllowReciprocal(FMF.allowReciprocal() && OpFMF.allowReciprocal()); | ||
Flag.setAllowContract(FMF.allowContract() && OpFMF.allowContract()); | ||
return Flag; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have an | value flag & rewrite flag helper in FastMathFlags but can do API cleanups later
cad89bf
to
8f6a647
Compare
@@ -2742,6 +2742,17 @@ Instruction *InstCombinerImpl::visitSub(BinaryOperator &I) { | |||
return TryToNarrowDeduceFlags(); | |||
} | |||
|
|||
static FastMathFlags combineFastMathFlagsForFNeg(FastMathFlags FMF, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name is too specific, but I'm not sure what to call this. Also, it should be directly in FastMathFlags.
Maybe unionWithRewriteIntersect? Plus the no infinity special case can be applied after
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we try this approach in FMF
?
FastMathFlags Result = this->Flags & Other.Flags;
Result.setNoNaNs(this->noNaNs() || Other.noNaNs());
Result.setNoSignedZeros(this->noSignedZeros() || Other.noSignedZeros());
return Result;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8f6a647
to
699be90
Compare
Any plans on landing this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thank you!
However, this PR only fixes FMF propagation for fneg + fmul
. The remaining part of the original issue (fsub + fmul
and fneg + fdiv
) is still outstanding: https://alive2.llvm.org/ce/z/Jt3n6P
Double confirmed that they don't exist: https://alive2.llvm.org/ce/z/GELV_d |
Fix #93769
alive2: https://alive2.llvm.org/ce/z/MHShQY