-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[ConstantFold] Fold erf
and erff
when the input parameter is a constant value.
#113079
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
erf
and erff
when the input parameter is a constant value.
@llvm/pr-subscribers-llvm-analysis Author: None (c8ef) ChangesThis patch adds support for constant folding for the Full diff: https://github.com/llvm/llvm-project/pull/113079.diff 2 Files Affected:
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index c0104d2bc26112..082df2972dfda1 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -1671,8 +1671,8 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
Name == "cos" || Name == "cosf" ||
Name == "cosh" || Name == "coshf";
case 'e':
- return Name == "exp" || Name == "expf" ||
- Name == "exp2" || Name == "exp2f";
+ return Name == "exp" || Name == "expf" || Name == "exp2" ||
+ Name == "exp2f" || Name == "erf" || Name == "erff";
case 'f':
return Name == "fabs" || Name == "fabsf" ||
Name == "floor" || Name == "floorf" ||
@@ -2411,6 +2411,11 @@ static Constant *ConstantFoldScalarCall1(StringRef Name,
break;
case LibFunc_logl:
return nullptr;
+ case LibFunc_erf:
+ case LibFunc_erff:
+ if (TLI->has(Func))
+ return ConstantFoldFP(erf, APF, Ty);
+ break;
case LibFunc_nearbyint:
case LibFunc_nearbyintf:
case LibFunc_rint:
diff --git a/llvm/test/Transforms/InstCombine/erf.ll b/llvm/test/Transforms/InstCombine/erf.ll
new file mode 100644
index 00000000000000..e2f1abca074669
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/erf.ll
@@ -0,0 +1,185 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define float @erff_const() {
+; CHECK-LABEL: define float @erff_const() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 5.000000e-01)
+; CHECK-NEXT: ret float 0x3FE0A7EF60000000
+;
+ %r = call float @erff(float 5.000000e-01)
+ ret float %r
+}
+
+define double @erf_const() {
+; CHECK-LABEL: define double @erf_const() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double -5.000000e-01)
+; CHECK-NEXT: ret double 0xBFE0A7EF5C18EDD2
+;
+ %r = call double @erf(double -5.000000e-01)
+ ret double %r
+}
+
+define float @erff_zero() {
+; CHECK-LABEL: define float @erff_zero() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0.000000e+00)
+; CHECK-NEXT: ret float 0.000000e+00
+;
+ %r = call float @erff(float 0.000000e+00)
+ ret float %r
+}
+
+define double @erf_zero() {
+; CHECK-LABEL: define double @erf_zero() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0.000000e+00)
+; CHECK-NEXT: ret double 0.000000e+00
+;
+ %r = call double @erf(double 0.000000e+00)
+ ret double %r
+}
+
+define float @erff_neg_zero() {
+; CHECK-LABEL: define float @erff_neg_zero() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float -0.000000e+00)
+; CHECK-NEXT: ret float -0.000000e+00
+;
+ %r = call float @erff(float -0.000000e+00)
+ ret float %r
+}
+
+define double @erf_neg_zero() {
+; CHECK-LABEL: define double @erf_neg_zero() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double -0.000000e+00)
+; CHECK-NEXT: ret double -0.000000e+00
+;
+ %r = call double @erf(double -0.000000e+00)
+ ret double %r
+}
+
+define float @erff_inf() {
+; CHECK-LABEL: define float @erff_inf() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0x7FF0000000000000)
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0x7FF0000000000000)
+ ret float %r
+}
+
+define double @erf_inf() {
+; CHECK-LABEL: define double @erf_inf() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0x7FF0000000000000)
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0x7FF0000000000000)
+ ret double %r
+}
+
+define float @erff_inf_memory_none() {
+; CHECK-LABEL: define float @erff_inf_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0x7FF0000000000000) #[[ATTR0:[0-9]+]]
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0x7FF0000000000000) readnone
+ ret float %r
+}
+
+define double @erf_inf_memory_none() {
+; CHECK-LABEL: define double @erf_inf_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0x7FF0000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0x7FF0000000000000) readnone
+ ret double %r
+}
+
+define float @erff_neg_inf() {
+; CHECK-LABEL: define float @erff_neg_inf() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0xFFF0000000000000)
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0xFFF0000000000000)
+ ret float %r
+}
+
+define double @erf_neg_inf() {
+; CHECK-LABEL: define double @erf_neg_inf() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0xFFF0000000000000)
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0xFFF0000000000000)
+ ret double %r
+}
+
+define float @erff_neg_inf_memory_none() {
+; CHECK-LABEL: define float @erff_neg_inf_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0xFFF0000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0xFFF0000000000000) readnone
+ ret float %r
+}
+
+define double @erf_neg_inf_memory_none() {
+; CHECK-LABEL: define double @erf_neg_inf_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0xFFF0000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0xFFF0000000000000) readnone
+ ret double %r
+}
+
+define float @erff_nan() {
+; CHECK-LABEL: define float @erff_nan() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0x7FF8000000000000)
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0x7FF8000000000000)
+ ret float %r
+}
+
+define double @erf_nan() {
+; CHECK-LABEL: define double @erf_nan() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0x7FF8000000000000)
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0x7FF8000000000000)
+ ret double %r
+}
+
+define float @erff_nan_memory_none() {
+; CHECK-LABEL: define float @erff_nan_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0x7FF8000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0x7FF8000000000000) readnone
+ ret float %r
+}
+
+define double @erf_nan_memory_none() {
+; CHECK-LABEL: define double @erf_nan_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0x7FF8000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0x7FF8000000000000) readnone
+ ret double %r
+}
+
+define float @erff_poison() {
+; CHECK-LABEL: define float @erff_poison() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float poison)
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float poison)
+ ret float %r
+}
+
+define double @erf_poison() {
+; CHECK-LABEL: define double @erf_poison() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double poison)
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double poison)
+ ret double %r
+}
+
+declare float @erff(float)
+declare double @erf(double)
|
@llvm/pr-subscribers-llvm-transforms Author: None (c8ef) ChangesThis patch adds support for constant folding for the Full diff: https://github.com/llvm/llvm-project/pull/113079.diff 2 Files Affected:
diff --git a/llvm/lib/Analysis/ConstantFolding.cpp b/llvm/lib/Analysis/ConstantFolding.cpp
index c0104d2bc26112..082df2972dfda1 100644
--- a/llvm/lib/Analysis/ConstantFolding.cpp
+++ b/llvm/lib/Analysis/ConstantFolding.cpp
@@ -1671,8 +1671,8 @@ bool llvm::canConstantFoldCallTo(const CallBase *Call, const Function *F) {
Name == "cos" || Name == "cosf" ||
Name == "cosh" || Name == "coshf";
case 'e':
- return Name == "exp" || Name == "expf" ||
- Name == "exp2" || Name == "exp2f";
+ return Name == "exp" || Name == "expf" || Name == "exp2" ||
+ Name == "exp2f" || Name == "erf" || Name == "erff";
case 'f':
return Name == "fabs" || Name == "fabsf" ||
Name == "floor" || Name == "floorf" ||
@@ -2411,6 +2411,11 @@ static Constant *ConstantFoldScalarCall1(StringRef Name,
break;
case LibFunc_logl:
return nullptr;
+ case LibFunc_erf:
+ case LibFunc_erff:
+ if (TLI->has(Func))
+ return ConstantFoldFP(erf, APF, Ty);
+ break;
case LibFunc_nearbyint:
case LibFunc_nearbyintf:
case LibFunc_rint:
diff --git a/llvm/test/Transforms/InstCombine/erf.ll b/llvm/test/Transforms/InstCombine/erf.ll
new file mode 100644
index 00000000000000..e2f1abca074669
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/erf.ll
@@ -0,0 +1,185 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define float @erff_const() {
+; CHECK-LABEL: define float @erff_const() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 5.000000e-01)
+; CHECK-NEXT: ret float 0x3FE0A7EF60000000
+;
+ %r = call float @erff(float 5.000000e-01)
+ ret float %r
+}
+
+define double @erf_const() {
+; CHECK-LABEL: define double @erf_const() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double -5.000000e-01)
+; CHECK-NEXT: ret double 0xBFE0A7EF5C18EDD2
+;
+ %r = call double @erf(double -5.000000e-01)
+ ret double %r
+}
+
+define float @erff_zero() {
+; CHECK-LABEL: define float @erff_zero() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0.000000e+00)
+; CHECK-NEXT: ret float 0.000000e+00
+;
+ %r = call float @erff(float 0.000000e+00)
+ ret float %r
+}
+
+define double @erf_zero() {
+; CHECK-LABEL: define double @erf_zero() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0.000000e+00)
+; CHECK-NEXT: ret double 0.000000e+00
+;
+ %r = call double @erf(double 0.000000e+00)
+ ret double %r
+}
+
+define float @erff_neg_zero() {
+; CHECK-LABEL: define float @erff_neg_zero() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float -0.000000e+00)
+; CHECK-NEXT: ret float -0.000000e+00
+;
+ %r = call float @erff(float -0.000000e+00)
+ ret float %r
+}
+
+define double @erf_neg_zero() {
+; CHECK-LABEL: define double @erf_neg_zero() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double -0.000000e+00)
+; CHECK-NEXT: ret double -0.000000e+00
+;
+ %r = call double @erf(double -0.000000e+00)
+ ret double %r
+}
+
+define float @erff_inf() {
+; CHECK-LABEL: define float @erff_inf() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0x7FF0000000000000)
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0x7FF0000000000000)
+ ret float %r
+}
+
+define double @erf_inf() {
+; CHECK-LABEL: define double @erf_inf() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0x7FF0000000000000)
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0x7FF0000000000000)
+ ret double %r
+}
+
+define float @erff_inf_memory_none() {
+; CHECK-LABEL: define float @erff_inf_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0x7FF0000000000000) #[[ATTR0:[0-9]+]]
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0x7FF0000000000000) readnone
+ ret float %r
+}
+
+define double @erf_inf_memory_none() {
+; CHECK-LABEL: define double @erf_inf_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0x7FF0000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0x7FF0000000000000) readnone
+ ret double %r
+}
+
+define float @erff_neg_inf() {
+; CHECK-LABEL: define float @erff_neg_inf() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0xFFF0000000000000)
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0xFFF0000000000000)
+ ret float %r
+}
+
+define double @erf_neg_inf() {
+; CHECK-LABEL: define double @erf_neg_inf() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0xFFF0000000000000)
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0xFFF0000000000000)
+ ret double %r
+}
+
+define float @erff_neg_inf_memory_none() {
+; CHECK-LABEL: define float @erff_neg_inf_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0xFFF0000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0xFFF0000000000000) readnone
+ ret float %r
+}
+
+define double @erf_neg_inf_memory_none() {
+; CHECK-LABEL: define double @erf_neg_inf_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0xFFF0000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0xFFF0000000000000) readnone
+ ret double %r
+}
+
+define float @erff_nan() {
+; CHECK-LABEL: define float @erff_nan() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0x7FF8000000000000)
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0x7FF8000000000000)
+ ret float %r
+}
+
+define double @erf_nan() {
+; CHECK-LABEL: define double @erf_nan() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0x7FF8000000000000)
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0x7FF8000000000000)
+ ret double %r
+}
+
+define float @erff_nan_memory_none() {
+; CHECK-LABEL: define float @erff_nan_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float 0x7FF8000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float 0x7FF8000000000000) readnone
+ ret float %r
+}
+
+define double @erf_nan_memory_none() {
+; CHECK-LABEL: define double @erf_nan_memory_none() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0x7FF8000000000000) #[[ATTR0]]
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double 0x7FF8000000000000) readnone
+ ret double %r
+}
+
+define float @erff_poison() {
+; CHECK-LABEL: define float @erff_poison() {
+; CHECK-NEXT: [[R:%.*]] = call float @erff(float poison)
+; CHECK-NEXT: ret float [[R]]
+;
+ %r = call float @erff(float poison)
+ ret float %r
+}
+
+define double @erf_poison() {
+; CHECK-LABEL: define double @erf_poison() {
+; CHECK-NEXT: [[R:%.*]] = call double @erf(double poison)
+; CHECK-NEXT: ret double [[R]]
+;
+ %r = call double @erf(double poison)
+ ret double %r
+}
+
+declare float @erff(float)
+declare double @erf(double)
|
; CHECK-NEXT: [[R:%.*]] = call double @erf(double 0.000000e+00) | ||
; CHECK-NEXT: ret double 0.000000e+00 | ||
; | ||
%r = call double @erf(double 0.000000e+00) |
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.
None of these cases folded?
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.
IIUC the only cases that are not folded are NaN and Inf; zero input will be folded as expected.
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 call wasn't deleted though.
Plus the linux docs suggest it doesn't ever set errno
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 call wasn't deleted though.
Other libcall test case exhibits similar behavior, but it does not seem to affect the final fold result. I believe the unused instruction will be deleted anyway?
Plus the linux docs suggest it doesn't ever set errno
Yes, it is still blocked by the previously discussed issue and will be addressed later.
llvm-project/llvm/lib/Analysis/ConstantFolding.cpp
Lines 2237 to 2241 in d6360d2
/// We only fold functions with finite arguments. Folding NaN and inf is | |
/// likely to be aborted with an exception anyway, and some host libms | |
/// have known errors raising exceptions. | |
if (!U.isFinite()) | |
return nullptr; |
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.
Other libcall test case exhibits similar behavior, but it does not seem to affect the final fold result. I believe the unused instruction will be deleted anyway?
Then those are also broken. This call needs to be immediately deleted
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.
Not sure why the calls are not being deleted. The problem might be at the callsite, specifically in instsimplify
.
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.
Hm, it does seem these are deleted later. I would still expect them to be immediately killed, but this seems to be a more widespread issue
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.
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.
There are two issues here:
- The function should be marked as
willreturn
to ensure it is deleted from the worklist. This will be handled byInferFunctionAttrs
during compilation, but is not currently observed in testing. isMathLibCallNoop
should include the relevant library call.
@@ -3624,6 +3628,10 @@ bool llvm::isMathLibCallNoop(const CallBase *Call, | |||
case LibFunc_sqrtf: | |||
return Op.isNaN() || Op.isZero() || !Op.isNegative(); | |||
|
|||
case LibFunc_erf: | |||
case LibFunc_erff: | |||
return true; |
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'm unclear on if and when errno is written. The linux man page says no. The C standard seems to suggest it raises a range error, which depending on platform configuration may be errno and/or fp exception
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.
Maybe we can revert this change and keep the original call as it is?
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 current implementation is safe because underflow will be caught by FE_UNDERFLOW
and not folded at all. Hopefully it is good now?
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 don't think this is actually that safe, llvm isn't compiling with strictfp enabled
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.
When I locally test passing a parameter less than DBL_MIN * (std::sqrt(M_PI) / 2)
, I get a floating-point exception on both Windows and Linux.
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.
Yes, it will probably work on most hosts in practice but it's not structurally correct
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.
Would it be better to explicitly test if the absolute value of APF
is greater than DBL_MIN * (std::sqrt(M_PI) / 2)
?
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 appears that the handling of current overflow and underflow in isMathLibCallNoop
is similar to the exp
function. Therefore, after removing the code snippet in isMathLibCallNoop
, it will simply return false, indicating that it is not a no-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.
You would have to enable strictfp support (which barely works in llvm), or avoid calling the host function in any of the edge cases that you are relying on the exception
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.
Actually, I previously tried using erf, but I still couldn't achieve folding because it returns false in isMathLibCallNoop
. Therefore, it's at least safe. Perhaps we should calculate the underflow boundary and add it into isMathLibCallNoop
to enable constant folding.
This patch adds support for constant folding for the
erf
anderff
libc functions.