diff --git a/llvm/include/llvm/Transforms/Scalar/FloatDependencyFixup.h b/llvm/include/llvm/Transforms/Scalar/FloatDependencyFixup.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Transforms/Scalar/FloatDependencyFixup.h @@ -0,0 +1,38 @@ +//===- FloatDependencyFixup.h -----------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// \file +/// +/// Defines pass for fixing up dependencies of floating-point operations in +/// non-default floating-point environment. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_SCALAR_FLOATDEPENDENCYFIXUPPASS_H +#define LLVM_TRANSFORMS_SCALAR_FLOATDEPENDENCYFIXUPPASS_H + +#include "llvm/IR/PassManager.h" + +namespace llvm { + +/// Removes excessive dependencies between operations which are sensitive to +/// floating-point environment. +/// +/// A floating-point operation may depend on floating-point control modes (like +/// rounding mode) and/or change the state of the status flags (like overflow). +/// This interaction is modeled as a side effect associated with the operation. +/// In some cases the side effect actually does not exist or may be ignored. +/// Removing it can help optimizations. +class FloatDependencyFixupPass + : public PassInfoMixin { +public: + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); +}; + +} // namespace llvm + +#endif // LLVM_TRANSFORMS_SCALAR_FLOATDEPENDENCYFIXUPPASS_H diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp --- a/llvm/lib/Passes/PassBuilder.cpp +++ b/llvm/lib/Passes/PassBuilder.cpp @@ -155,6 +155,7 @@ #include "llvm/Transforms/Scalar/EarlyCSE.h" #include "llvm/Transforms/Scalar/FlattenCFG.h" #include "llvm/Transforms/Scalar/Float2Int.h" +#include "llvm/Transforms/Scalar/FloatDependencyFixup.h" #include "llvm/Transforms/Scalar/GVN.h" #include "llvm/Transforms/Scalar/GuardWidening.h" #include "llvm/Transforms/Scalar/IVUsersPrinter.h" diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp --- a/llvm/lib/Passes/PassBuilderPipelines.cpp +++ b/llvm/lib/Passes/PassBuilderPipelines.cpp @@ -82,6 +82,7 @@ #include "llvm/Transforms/Scalar/DivRemPairs.h" #include "llvm/Transforms/Scalar/EarlyCSE.h" #include "llvm/Transforms/Scalar/Float2Int.h" +#include "llvm/Transforms/Scalar/FloatDependencyFixup.h" #include "llvm/Transforms/Scalar/GVN.h" #include "llvm/Transforms/Scalar/IndVarSimplify.h" #include "llvm/Transforms/Scalar/InstSimplifyPass.h" @@ -1195,6 +1196,10 @@ // And finally clean up LCSSA form before generating code. OptimizePM.addPass(InstSimplifyPass()); + // This late dependency cleanup helps removal of dangling float operation, + // produced by the previous passes. + OptimizePM.addPass(FloatDependencyFixupPass()); + // This hoists/decomposes div/rem ops. It should run after other sink/hoist // passes to avoid re-sinking, but before SimplifyCFG because it can allow // flattening of blocks. diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -259,6 +259,7 @@ FUNCTION_PASS("dot-dom-only", DomTreeOnlyPrinterPass()) FUNCTION_PASS("fix-irreducible", FixIrreduciblePass()) FUNCTION_PASS("flattencfg", FlattenCFGPass()) +FUNCTION_PASS("float-trans", FloatDependencyFixupPass()) FUNCTION_PASS("make-guards-explicit", MakeGuardsExplicitPass()) FUNCTION_PASS("gvn-hoist", GVNHoistPass()) FUNCTION_PASS("gvn-sink", GVNSinkPass()) diff --git a/llvm/lib/Transforms/Scalar/CMakeLists.txt b/llvm/lib/Transforms/Scalar/CMakeLists.txt --- a/llvm/lib/Transforms/Scalar/CMakeLists.txt +++ b/llvm/lib/Transforms/Scalar/CMakeLists.txt @@ -14,6 +14,7 @@ EarlyCSE.cpp FlattenCFGPass.cpp Float2Int.cpp + FloatDependencyFixup.cpp GuardWidening.cpp GVN.cpp GVNHoist.cpp diff --git a/llvm/lib/Transforms/Scalar/FloatDependencyFixup.cpp b/llvm/lib/Transforms/Scalar/FloatDependencyFixup.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Transforms/Scalar/FloatDependencyFixup.cpp @@ -0,0 +1,68 @@ +//===- FloatDependencyFixup.cpp -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Scalar/FloatDependencyFixup.h" +#include "llvm/Analysis/ConstantFolding.h" +#include "llvm/Analysis/TargetLibraryInfo.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/ValueHandle.h" +#include "llvm/Transforms/Utils/Local.h" + +using namespace llvm; + +static bool hasOnlyConstantArgs(const ConstrainedFPIntrinsic *CI, + SmallVectorImpl &Args) { + Args.clear(); + for (const Use &Arg : CI->args()) { + if (auto *ConstArg = dyn_cast(Arg)) + Args.push_back(ConstArg); + else if (!isa(Arg)) + return false; + } + return true; +} + +PreservedAnalyses FloatDependencyFixupPass::run(Function &F, + FunctionAnalysisManager &AM) { + if (!F.hasFnAttribute(Attribute::StrictFP)) + return PreservedAnalyses::all(); + + auto &TLI = AM.getResult(F); + bool Changed = false; + + for (BasicBlock &BB : F) { + SmallVector DeadInstructions; + for (Instruction &I : BB) { + if (auto *CI = dyn_cast(&I)) { + if (CI->hasFnAttr(Attribute::ReadNone)) + continue; + SmallVector Args; + if (hasOnlyConstantArgs(CI, Args)) { + auto *Callee = dyn_cast(CI->getCalledOperand()); + if (!Callee) + continue; + if (Constant *Res = ConstantFoldCall(CI, Callee, Args, &TLI)) { + if (CI->getNumUses() != 0) + I.replaceAllUsesWith(Res); + Changed = true; + CI->addFnAttr(Attribute::ReadNone); + DeadInstructions.push_back(CI); + } + } + } + } + RecursivelyDeleteTriviallyDeadInstructions(DeadInstructions, &TLI); + } + + if (!Changed) + return PreservedAnalyses::all(); + + PreservedAnalyses PA; + PA.preserveSet(); + return PA; +} diff --git a/llvm/test/Other/new-pm-defaults.ll b/llvm/test/Other/new-pm-defaults.ll --- a/llvm/test/Other/new-pm-defaults.ll +++ b/llvm/test/Other/new-pm-defaults.ll @@ -253,6 +253,7 @@ ; CHECK-O-NEXT: Running pass: AlignmentFromAssumptionsPass ; CHECK-O-NEXT: Running pass: LoopSinkPass ; CHECK-O-NEXT: Running pass: InstSimplifyPass +; CHECK-O-NEXT: Running pass: FloatDependencyFixupPass ; CHECK-O-NEXT: Running pass: DivRemPairsPass ; CHECK-O-NEXT: Running pass: SimplifyCFGPass ; CHECK-O-NEXT: Running pass: CoroCleanupPass diff --git a/llvm/test/Other/new-pm-thinlto-defaults.ll b/llvm/test/Other/new-pm-thinlto-defaults.ll --- a/llvm/test/Other/new-pm-thinlto-defaults.ll +++ b/llvm/test/Other/new-pm-thinlto-defaults.ll @@ -221,6 +221,7 @@ ; CHECK-POSTLINK-O-NEXT: Running pass: AlignmentFromAssumptionsPass ; CHECK-POSTLINK-O-NEXT: Running pass: LoopSinkPass ; CHECK-POSTLINK-O-NEXT: Running pass: InstSimplifyPass +; CHECK-POSTLINK-O-NEXT: Running pass: FloatDependencyFixupPass ; CHECK-POSTLINK-O-NEXT: Running pass: DivRemPairsPass ; CHECK-POSTLINK-O-NEXT: Running pass: SimplifyCFGPass ; CHECK-O-NEXT: Running pass: CoroCleanupPass diff --git a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll --- a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll +++ b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll @@ -190,6 +190,7 @@ ; CHECK-O-NEXT: Running pass: AlignmentFromAssumptionsPass ; CHECK-O-NEXT: Running pass: LoopSinkPass ; CHECK-O-NEXT: Running pass: InstSimplifyPass +; CHECK-O-NEXT: Running pass: FloatDependencyFixupPass ; CHECK-O-NEXT: Running pass: DivRemPairsPass ; CHECK-O-NEXT: Running pass: SimplifyCFGPass ; CHECK-O-NEXT: Running pass: CoroCleanupPass diff --git a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll --- a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll +++ b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll @@ -202,6 +202,7 @@ ; CHECK-O-NEXT: Running pass: AlignmentFromAssumptionsPass ; CHECK-O-NEXT: Running pass: LoopSinkPass ; CHECK-O-NEXT: Running pass: InstSimplifyPass +; CHECK-O-NEXT: Running pass: FloatDependencyFixupPass ; CHECK-O-NEXT: Running pass: DivRemPairsPass ; CHECK-O-NEXT: Running pass: SimplifyCFGPass ; CHECK-O-NEXT: Running pass: CoroCleanupPass diff --git a/llvm/test/Transforms/FloatDependencyFixup/float-dep-fixup.ll b/llvm/test/Transforms/FloatDependencyFixup/float-dep-fixup.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/FloatDependencyFixup/float-dep-fixup.ll @@ -0,0 +1,127 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt -S -passes=float-trans %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. +; FIXME: must work with dynamic rounding mode as well. +define float @f_unused_dynamic_ignore() #0 { +; CHECK-LABEL: @f_unused_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 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 }