[InstCombine] Remove side effect of replaced constrained intrinsics
authorSerge Pavlov <sepavloff@gmail.com>
Thu, 5 May 2022 05:02:42 +0000 (12:02 +0700)
committerSerge Pavlov <sepavloff@gmail.com>
Sat, 7 May 2022 12:04:11 +0000 (19:04 +0700)
If a constrained intrinsic call was replaced by some value, it was not
removed in some cases. The dangling instruction resulted in useless
instructions executed in runtime. It happened because constrained
intrinsics usually have side effect, it is used to model the interaction
with floating-point environment. In some cases side effect is actually
absent or can be ignored.

This change adds specific treatment of constrained intrinsics so that
their side effect can be removed if it actually absents.

Differential Revision: https://reviews.llvm.org/D118426

llvm/include/llvm/Analysis/InstructionSimplify.h
llvm/lib/Analysis/InstructionSimplify.cpp
llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
llvm/test/Transforms/InstCombine/constrained.ll [new file with mode: 0644]

index 8f6ed3a..edffcd9 100644 (file)
@@ -301,6 +301,15 @@ Value *SimplifyBinOp(unsigned Opcode, Value *LHS, Value *RHS, FastMathFlags FMF,
 /// Given a callsite, fold the result or return null.
 Value *SimplifyCall(CallBase *Call, const SimplifyQuery &Q);
 
+/// Given a constrained FP intrinsic call, tries to compute its simplified
+/// version. Returns a simplified result or null.
+///
+/// This function provides an additional contract: it guarantees that if
+/// simplification succeeds that the intrinsic is side effect free. As a result,
+/// successful simplification can be used to delete the intrinsic not just
+/// replace its result.
+Value *SimplifyConstrainedFPCall(CallBase *Call, const SimplifyQuery &Q);
+
 /// Given an operand for a Freeze, see if we can fold the result.
 /// If not, this returns null.
 Value *SimplifyFreezeInst(Value *Op, const SimplifyQuery &Q);
index c299ef3..0bb433f 100644 (file)
@@ -6230,6 +6230,15 @@ Value *llvm::SimplifyCall(CallBase *Call, const SimplifyQuery &Q) {
   return nullptr;
 }
 
+Value *llvm::SimplifyConstrainedFPCall(CallBase *Call, const SimplifyQuery &Q) {
+  assert(isa<ConstrainedFPIntrinsic>(Call));
+  if (Value *V = tryConstantFoldCall(Call, Q))
+    return V;
+  if (Value *Ret = simplifyIntrinsic(Call, Q))
+    return Ret;
+  return nullptr;
+}
+
 /// Given operands for a Freeze, see if we can fold the result.
 static Value *SimplifyFreezeInst(Value *Op0, const SimplifyQuery &Q) {
   // Use a utility function defined in ValueTracking.
index c96919c..6534ecc 100644 (file)
@@ -1237,6 +1237,15 @@ Instruction *InstCombinerImpl::visitCallInst(CallInst &CI) {
       return NewCall;
   }
 
+  // Unused constrained FP intrinsic calls may have declared side effect, which
+  // prevents it from being removed. In some cases however the side effect is
+  // actually absent. To detect this case, call SimplifyConstrainedFPCall. If it
+  // returns a replacement, the call may be removed.
+  if (CI.use_empty() && isa<ConstrainedFPIntrinsic>(CI)) {
+    if (Value *V = SimplifyConstrainedFPCall(&CI, SQ.getWithInstruction(&CI)))
+      return eraseInstFromFunction(CI);
+  }
+
   Intrinsic::ID IID = II->getIntrinsicID();
   switch (IID) {
   case Intrinsic::objectsize:
diff --git a/llvm/test/Transforms/InstCombine/constrained.ll b/llvm/test/Transforms/InstCombine/constrained.ll
new file mode 100644 (file)
index 0000000..b5ef71e
--- /dev/null
@@ -0,0 +1,125 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S -instcombine %s | FileCheck %s
+
+; Treatment of operation with unused result.
+
+; If operation does not raise exceptions, it may be removed even in strict mode.
+define float @f_unused_precise() #0 {
+; CHECK-LABEL: @f_unused_precise(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fadd.f32(float 1.0, float 1.0, metadata !"round.upward", metadata !"fpexcept.strict") #0
+  ret float 1.0
+}
+
+; If operation raises exceptions, it cannot be removed in strict mode.
+define float @f_unused_strict() #0 {
+; CHECK-LABEL: @f_unused_strict(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.experimental.constrained.fdiv.f32(float 1.000000e+00, float 3.000000e+00, metadata !"round.tonearest", metadata !"fpexcept.strict") #[[ATTR0:[0-9]+]]
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fdiv.f32(float 1.0, float 3.0, metadata !"round.tonearest", metadata !"fpexcept.strict") #0
+  ret float 1.0
+}
+
+; If operation raises exceptions, it can be removed in non-strict mode.
+define float @f_unused_ignore() #0 {
+; CHECK-LABEL: @f_unused_ignore(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fdiv.f32(float 1.0, float 3.0, metadata !"round.towardzero", metadata !"fpexcept.ignore") #0
+  ret float 1.0
+}
+
+; If operation raises exceptions, it can be removed in non-strict mode even if rounding mode is dynamic.
+define float @f_unused_dynamic_ignore() #0 {
+; CHECK-LABEL: @f_unused_dynamic_ignore(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fdiv.f32(float 1.0, float 3.0, metadata !"round.dynamic", metadata !"fpexcept.ignore") #0
+  ret float 1.0
+}
+
+; If operation raises exceptions, it can be removed in "maytrap" mode.
+define float @f_unused_maytrap() #0 {
+; CHECK-LABEL: @f_unused_maytrap(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret float 1.000000e+00
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fdiv.f32(float 1.0, float 3.0, metadata !"round.tonearest", metadata !"fpexcept.maytrap") #0
+  ret float 1.0
+}
+
+; Constant evaluation.
+
+; If operation does not raise exceptions, it may be folded even in strict mode.
+define float @f_eval_precise() #0 {
+; CHECK-LABEL: @f_eval_precise(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret float 2.000000e+00
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fadd.f32(float 1.0, float 1.0, metadata !"round.upward", metadata !"fpexcept.strict") #0
+  ret float %result
+}
+
+; If operation raises exceptions, it cannot be folded in strict mode.
+define float @f_eval_strict() #0 {
+; CHECK-LABEL: @f_eval_strict(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.experimental.constrained.fdiv.f32(float 1.000000e+00, float 3.000000e+00, metadata !"round.upward", metadata !"fpexcept.strict") #[[ATTR0]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fdiv.f32(float 1.0, float 3.0, metadata !"round.upward", metadata !"fpexcept.strict") #0
+  ret float %result
+}
+
+; If operation raises exceptions, it can be folded in non-strict mode.
+define float @f_eval_ignore() #0 {
+; CHECK-LABEL: @f_eval_ignore(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret float 0x3FD5555540000000
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fdiv.f32(float 1.0, float 3.0, metadata !"round.downward", metadata !"fpexcept.ignore") #0
+  ret float %result
+}
+
+; if result is imprecise, it cannot be folded if rounding mode is dynamic.
+define float @f_eval_dynamic_ignore() #0 {
+; CHECK-LABEL: @f_eval_dynamic_ignore(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.experimental.constrained.fdiv.f32(float 1.000000e+00, float 3.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.ignore") #[[ATTR0]]
+; CHECK-NEXT:    ret float [[RESULT]]
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fdiv.f32(float 1.0, float 3.0, metadata !"round.dynamic", metadata !"fpexcept.ignore") #0
+  ret float %result
+}
+
+; If result is imprecise and rounding mode is not dynamic, operation can be folded in "maytrap" mode.
+define float @f_eval_maytrap() #0 {
+; CHECK-LABEL: @f_eval_maytrap(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    ret float 0x3FD5555560000000
+;
+entry:
+  %result = call float @llvm.experimental.constrained.fdiv.f32(float 1.0, float 3.0, metadata !"round.tonearest", metadata !"fpexcept.maytrap") #0
+  ret float %result
+}
+
+
+declare float @llvm.experimental.constrained.fadd.f32(float, float, metadata, metadata)
+declare float @llvm.experimental.constrained.fdiv.f32(float, float, metadata, metadata)
+
+attributes #0 = { strictfp }