Files
gps/tools/local/circt/include/llvm/IR/GenericFloatingPointPredicateUtils.h
T
2026-07-12 11:01:51 +03:00

486 lines
17 KiB
C++

//===- llvm/Support/GenericFloatingPointPredicateUtils.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
/// Utilities for dealing with flags related to floating point properties and
/// mode controls.
///
//===----------------------------------------------------------------------===/
#ifndef LLVM_ADT_GENERICFLOATINGPOINTPREDICATEUTILS_H
#define LLVM_ADT_GENERICFLOATINGPOINTPREDICATEUTILS_H
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/FloatingPointMode.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/Compiler.h"
#include <optional>
namespace llvm {
template <typename ContextT> class GenericFloatingPointPredicateUtils {
using ValueRefT = typename ContextT::ValueRefT;
using FunctionT = typename ContextT::FunctionT;
constexpr static ValueRefT Invalid = {};
private:
LLVM_ABI static DenormalMode queryDenormalMode(const FunctionT &F,
ValueRefT Val);
LLVM_ABI static bool lookThroughFAbs(const FunctionT &F, ValueRefT LHS,
ValueRefT &Src);
LLVM_ABI static std::optional<APFloat> matchConstantFloat(const FunctionT &F,
ValueRefT Val);
/// Return the return value for fcmpImpliesClass for a compare that produces
/// an exact class test.
static std::tuple<ValueRefT, FPClassTest, FPClassTest>
exactClass(ValueRefT V, FPClassTest M) {
return {V, M, ~M};
}
public:
/// Returns a pair of values, which if passed to llvm.is.fpclass, returns the
/// same result as an fcmp with the given operands.
static std::pair<ValueRefT, FPClassTest>
fcmpToClassTest(FCmpInst::Predicate Pred, const FunctionT &F, ValueRefT LHS,
ValueRefT RHS, bool LookThroughSrc) {
std::optional<APFloat> ConstRHS = matchConstantFloat(F, RHS);
if (!ConstRHS)
return {Invalid, fcAllFlags};
return fcmpToClassTest(Pred, F, LHS, *ConstRHS, LookThroughSrc);
}
static std::pair<ValueRefT, FPClassTest>
fcmpToClassTest(FCmpInst::Predicate Pred, const FunctionT &F, ValueRefT LHS,
const APFloat &ConstRHS, bool LookThroughSrc) {
auto [Src, ClassIfTrue, ClassIfFalse] =
fcmpImpliesClass(Pred, F, LHS, ConstRHS, LookThroughSrc);
if (Src && ClassIfTrue == ~ClassIfFalse)
return {Src, ClassIfTrue};
return {Invalid, fcAllFlags};
}
/// Compute the possible floating-point classes that \p LHS could be based on
/// fcmp \Pred \p LHS, \p RHS.
///
/// \returns { TestedValue, ClassesIfTrue, ClassesIfFalse }
///
/// If the compare returns an exact class test, ClassesIfTrue ==
/// ~ClassesIfFalse
///
/// This is a less exact version of fcmpToClassTest (e.g. fcmpToClassTest will
/// only succeed for a test of x > 0 implies positive, but not x > 1).
///
/// If \p LookThroughSrc is true, consider the input value when computing the
/// mask. This may look through sign bit operations.
///
/// If \p LookThroughSrc is false, ignore the source value (i.e. the first
/// pair element will always be LHS.
///
static std::tuple<ValueRefT, FPClassTest, FPClassTest>
fcmpImpliesClass(CmpInst::Predicate Pred, const FunctionT &F, ValueRefT LHS,
FPClassTest RHSClass, bool LookThroughSrc) {
assert(RHSClass != fcNone);
ValueRefT Src = LHS;
if (Pred == FCmpInst::FCMP_TRUE)
return exactClass(Src, fcAllFlags);
if (Pred == FCmpInst::FCMP_FALSE)
return exactClass(Src, fcNone);
const FPClassTest OrigClass = RHSClass;
const bool IsNegativeRHS = (RHSClass & fcNegative) == RHSClass;
const bool IsPositiveRHS = (RHSClass & fcPositive) == RHSClass;
const bool IsNaN = (RHSClass & ~fcNan) == fcNone;
if (IsNaN) {
// fcmp o__ x, nan -> false
// fcmp u__ x, nan -> true
return exactClass(Src, CmpInst::isOrdered(Pred) ? fcNone : fcAllFlags);
}
// fcmp ord x, zero|normal|subnormal|inf -> ~fcNan
if (Pred == FCmpInst::FCMP_ORD)
return exactClass(Src, ~fcNan);
// fcmp uno x, zero|normal|subnormal|inf -> fcNan
if (Pred == FCmpInst::FCMP_UNO)
return exactClass(Src, fcNan);
const bool IsFabs = LookThroughSrc && lookThroughFAbs(F, LHS, Src);
if (IsFabs)
RHSClass = llvm::inverse_fabs(RHSClass);
const bool IsZero = (OrigClass & fcZero) == OrigClass;
if (IsZero) {
assert(Pred != FCmpInst::FCMP_ORD && Pred != FCmpInst::FCMP_UNO);
// Compares with fcNone are only exactly equal to fcZero if input
// denormals are not flushed.
// TODO: Handle DAZ by expanding masks to cover subnormal cases.
DenormalMode Mode = queryDenormalMode(F, LHS);
if (Mode.Input != DenormalMode::IEEE)
return {Invalid, fcAllFlags, fcAllFlags};
auto ExactClass = [IsFabs, Src](FPClassTest Mask) {
if (IsFabs)
Mask = llvm::inverse_fabs(Mask);
return exactClass(Src, Mask);
};
switch (Pred) {
case FCmpInst::FCMP_OEQ: // Match x == 0.0
return exactClass(Src, fcZero);
case FCmpInst::FCMP_UEQ: // Match isnan(x) || (x == 0.0)
return exactClass(Src, fcZero | fcNan);
case FCmpInst::FCMP_UNE: // Match (x != 0.0)
return exactClass(Src, ~fcZero);
case FCmpInst::FCMP_ONE: // Match !isnan(x) && x != 0.0
return exactClass(Src, ~fcNan & ~fcZero);
case FCmpInst::FCMP_ORD:
// Canonical form of ord/uno is with a zero. We could also handle
// non-canonical other non-NaN constants or LHS == RHS.
return exactClass(Src, ~fcNan);
case FCmpInst::FCMP_UNO:
return exactClass(Src, fcNan);
case FCmpInst::FCMP_OGT: // x > 0
return ExactClass(fcPosSubnormal | fcPosNormal | fcPosInf);
case FCmpInst::FCMP_UGT: // isnan(x) || x > 0
return ExactClass(fcPosSubnormal | fcPosNormal | fcPosInf | fcNan);
case FCmpInst::FCMP_OGE: // x >= 0
return ExactClass(fcPositive | fcNegZero);
case FCmpInst::FCMP_UGE: // isnan(x) || x >= 0
return ExactClass(fcPositive | fcNegZero | fcNan);
case FCmpInst::FCMP_OLT: // x < 0
return ExactClass(fcNegSubnormal | fcNegNormal | fcNegInf);
case FCmpInst::FCMP_ULT: // isnan(x) || x < 0
return ExactClass(fcNegSubnormal | fcNegNormal | fcNegInf | fcNan);
case FCmpInst::FCMP_OLE: // x <= 0
return ExactClass(fcNegative | fcPosZero);
case FCmpInst::FCMP_ULE: // isnan(x) || x <= 0
return ExactClass(fcNegative | fcPosZero | fcNan);
default:
llvm_unreachable("all compare types are handled");
}
}
const bool IsDenormalRHS = (OrigClass & fcSubnormal) == OrigClass;
const bool IsInf = (OrigClass & fcInf) == OrigClass;
if (IsInf) {
FPClassTest Mask = fcAllFlags;
switch (Pred) {
case FCmpInst::FCMP_OEQ:
case FCmpInst::FCMP_UNE: {
// Match __builtin_isinf patterns
//
// fcmp oeq x, +inf -> is_fpclass x, fcPosInf
// fcmp oeq fabs(x), +inf -> is_fpclass x, fcInf
// fcmp oeq x, -inf -> is_fpclass x, fcNegInf
// fcmp oeq fabs(x), -inf -> is_fpclass x, 0 -> false
//
// fcmp une x, +inf -> is_fpclass x, ~fcPosInf
// fcmp une fabs(x), +inf -> is_fpclass x, ~fcInf
// fcmp une x, -inf -> is_fpclass x, ~fcNegInf
// fcmp une fabs(x), -inf -> is_fpclass x, fcAllFlags -> true
if (IsNegativeRHS) {
Mask = fcNegInf;
if (IsFabs)
Mask = fcNone;
} else {
Mask = fcPosInf;
if (IsFabs)
Mask |= fcNegInf;
}
break;
}
case FCmpInst::FCMP_ONE:
case FCmpInst::FCMP_UEQ: {
// Match __builtin_isinf patterns
// fcmp one x, -inf -> is_fpclass x, fcNegInf
// fcmp one fabs(x), -inf -> is_fpclass x, ~fcNegInf & ~fcNan
// fcmp one x, +inf -> is_fpclass x, ~fcNegInf & ~fcNan
// fcmp one fabs(x), +inf -> is_fpclass x, ~fcInf & fcNan
//
// fcmp ueq x, +inf -> is_fpclass x, fcPosInf|fcNan
// fcmp ueq (fabs x), +inf -> is_fpclass x, fcInf|fcNan
// fcmp ueq x, -inf -> is_fpclass x, fcNegInf|fcNan
// fcmp ueq fabs(x), -inf -> is_fpclass x, fcNan
if (IsNegativeRHS) {
Mask = ~fcNegInf & ~fcNan;
if (IsFabs)
Mask = ~fcNan;
} else {
Mask = ~fcPosInf & ~fcNan;
if (IsFabs)
Mask &= ~fcNegInf;
}
break;
}
case FCmpInst::FCMP_OLT:
case FCmpInst::FCMP_UGE: {
if (IsNegativeRHS) {
// No value is ordered and less than negative infinity.
// All values are unordered with or at least negative infinity.
// fcmp olt x, -inf -> false
// fcmp uge x, -inf -> true
Mask = fcNone;
break;
}
// fcmp olt fabs(x), +inf -> fcFinite
// fcmp uge fabs(x), +inf -> ~fcFinite
// fcmp olt x, +inf -> fcFinite|fcNegInf
// fcmp uge x, +inf -> ~(fcFinite|fcNegInf)
Mask = fcFinite;
if (!IsFabs)
Mask |= fcNegInf;
break;
}
case FCmpInst::FCMP_OGE:
case FCmpInst::FCMP_ULT: {
if (IsNegativeRHS) {
// fcmp oge x, -inf -> ~fcNan
// fcmp oge fabs(x), -inf -> ~fcNan
// fcmp ult x, -inf -> fcNan
// fcmp ult fabs(x), -inf -> fcNan
Mask = ~fcNan;
break;
}
// fcmp oge fabs(x), +inf -> fcInf
// fcmp oge x, +inf -> fcPosInf
// fcmp ult fabs(x), +inf -> ~fcInf
// fcmp ult x, +inf -> ~fcPosInf
Mask = fcPosInf;
if (IsFabs)
Mask |= fcNegInf;
break;
}
case FCmpInst::FCMP_OGT:
case FCmpInst::FCMP_ULE: {
if (IsNegativeRHS) {
// fcmp ogt x, -inf -> fcmp one x, -inf
// fcmp ogt fabs(x), -inf -> fcmp ord x, x
// fcmp ule x, -inf -> fcmp ueq x, -inf
// fcmp ule fabs(x), -inf -> fcmp uno x, x
Mask = IsFabs ? ~fcNan : ~(fcNegInf | fcNan);
break;
}
// No value is ordered and greater than infinity.
Mask = fcNone;
break;
}
case FCmpInst::FCMP_OLE:
case FCmpInst::FCMP_UGT: {
if (IsNegativeRHS) {
Mask = IsFabs ? fcNone : fcNegInf;
break;
}
// fcmp ole x, +inf -> fcmp ord x, x
// fcmp ole fabs(x), +inf -> fcmp ord x, x
// fcmp ole x, -inf -> fcmp oeq x, -inf
// fcmp ole fabs(x), -inf -> false
Mask = ~fcNan;
break;
}
default:
llvm_unreachable("all compare types are handled");
}
// Invert the comparison for the unordered cases.
if (FCmpInst::isUnordered(Pred))
Mask = ~Mask;
return exactClass(Src, Mask);
}
if (Pred == FCmpInst::FCMP_OEQ)
return {Src, RHSClass, fcAllFlags};
if (Pred == FCmpInst::FCMP_UEQ) {
FPClassTest Class = RHSClass | fcNan;
return {Src, Class, ~fcNan};
}
if (Pred == FCmpInst::FCMP_ONE)
return {Src, ~fcNan, RHSClass | fcNan};
if (Pred == FCmpInst::FCMP_UNE)
return {Src, fcAllFlags, RHSClass};
assert((RHSClass == fcNone || RHSClass == fcPosNormal ||
RHSClass == fcNegNormal || RHSClass == fcNormal ||
RHSClass == fcPosSubnormal || RHSClass == fcNegSubnormal ||
RHSClass == fcSubnormal) &&
"should have been recognized as an exact class test");
if (IsNegativeRHS) {
// TODO: Handle fneg(fabs)
if (IsFabs) {
// fabs(x) o> -k -> fcmp ord x, x
// fabs(x) u> -k -> true
// fabs(x) o< -k -> false
// fabs(x) u< -k -> fcmp uno x, x
switch (Pred) {
case FCmpInst::FCMP_OGT:
case FCmpInst::FCMP_OGE:
return {Src, ~fcNan, fcNan};
case FCmpInst::FCMP_UGT:
case FCmpInst::FCMP_UGE:
return {Src, fcAllFlags, fcNone};
case FCmpInst::FCMP_OLT:
case FCmpInst::FCMP_OLE:
return {Src, fcNone, fcAllFlags};
case FCmpInst::FCMP_ULT:
case FCmpInst::FCMP_ULE:
return {Src, fcNan, ~fcNan};
default:
break;
}
return {Invalid, fcAllFlags, fcAllFlags};
}
FPClassTest ClassesLE = fcNegInf | fcNegNormal;
FPClassTest ClassesGE = fcPositive | fcNegZero | fcNegSubnormal;
if (IsDenormalRHS)
ClassesLE |= fcNegSubnormal;
else
ClassesGE |= fcNegNormal;
switch (Pred) {
case FCmpInst::FCMP_OGT:
case FCmpInst::FCMP_OGE:
return {Src, ClassesGE, ~ClassesGE | RHSClass};
case FCmpInst::FCMP_UGT:
case FCmpInst::FCMP_UGE:
return {Src, ClassesGE | fcNan, ~(ClassesGE | fcNan) | RHSClass};
case FCmpInst::FCMP_OLT:
case FCmpInst::FCMP_OLE:
return {Src, ClassesLE, ~ClassesLE | RHSClass};
case FCmpInst::FCMP_ULT:
case FCmpInst::FCMP_ULE:
return {Src, ClassesLE | fcNan, ~(ClassesLE | fcNan) | RHSClass};
default:
break;
}
} else if (IsPositiveRHS) {
FPClassTest ClassesGE = fcPosNormal | fcPosInf;
FPClassTest ClassesLE = fcNegative | fcPosZero | fcPosSubnormal;
if (IsDenormalRHS)
ClassesGE |= fcPosSubnormal;
else
ClassesLE |= fcPosNormal;
if (IsFabs) {
ClassesGE = llvm::inverse_fabs(ClassesGE);
ClassesLE = llvm::inverse_fabs(ClassesLE);
}
switch (Pred) {
case FCmpInst::FCMP_OGT:
case FCmpInst::FCMP_OGE:
return {Src, ClassesGE, ~ClassesGE | RHSClass};
case FCmpInst::FCMP_UGT:
case FCmpInst::FCMP_UGE:
return {Src, ClassesGE | fcNan, ~(ClassesGE | fcNan) | RHSClass};
case FCmpInst::FCMP_OLT:
case FCmpInst::FCMP_OLE:
return {Src, ClassesLE, ~ClassesLE | RHSClass};
case FCmpInst::FCMP_ULT:
case FCmpInst::FCMP_ULE:
return {Src, ClassesLE | fcNan, ~(ClassesLE | fcNan) | RHSClass};
default:
break;
}
}
return {Invalid, fcAllFlags, fcAllFlags};
}
static std::tuple<ValueRefT, FPClassTest, FPClassTest>
fcmpImpliesClass(CmpInst::Predicate Pred, const FunctionT &F, ValueRefT LHS,
const APFloat &ConstRHS, bool LookThroughSrc) {
// We can refine checks against smallest normal / largest denormal to an
// exact class test.
if (!ConstRHS.isNegative() && ConstRHS.isSmallestNormalized()) {
ValueRefT Src = LHS;
const bool IsFabs = LookThroughSrc && lookThroughFAbs(F, LHS, Src);
FPClassTest Mask;
// Match pattern that's used in __builtin_isnormal.
switch (Pred) {
case FCmpInst::FCMP_OLT:
case FCmpInst::FCMP_UGE: {
// fcmp olt x, smallest_normal ->
// fcNegInf|fcNegNormal|fcSubnormal|fcZero fcmp olt fabs(x),
// smallest_normal -> fcSubnormal|fcZero fcmp uge x, smallest_normal ->
// fcNan|fcPosNormal|fcPosInf fcmp uge fabs(x), smallest_normal ->
// ~(fcSubnormal|fcZero)
Mask = fcZero | fcSubnormal;
if (!IsFabs)
Mask |= fcNegNormal | fcNegInf;
break;
}
case FCmpInst::FCMP_OGE:
case FCmpInst::FCMP_ULT: {
// fcmp oge x, smallest_normal -> fcPosNormal | fcPosInf
// fcmp oge fabs(x), smallest_normal -> fcInf | fcNormal
// fcmp ult x, smallest_normal -> ~(fcPosNormal | fcPosInf)
// fcmp ult fabs(x), smallest_normal -> ~(fcInf | fcNormal)
Mask = fcPosInf | fcPosNormal;
if (IsFabs)
Mask |= fcNegInf | fcNegNormal;
break;
}
default:
return fcmpImpliesClass(Pred, F, LHS, ConstRHS.classify(),
LookThroughSrc);
}
// Invert the comparison for the unordered cases.
if (FCmpInst::isUnordered(Pred))
Mask = ~Mask;
return exactClass(Src, Mask);
}
return fcmpImpliesClass(Pred, F, LHS, ConstRHS.classify(), LookThroughSrc);
}
static std::tuple<ValueRefT, FPClassTest, FPClassTest>
fcmpImpliesClass(CmpInst::Predicate Pred, const FunctionT &F, ValueRefT LHS,
ValueRefT RHS, bool LookThroughSrc) {
std::optional<APFloat> ConstRHS = matchConstantFloat(F, RHS);
if (!ConstRHS)
return {Invalid, fcAllFlags, fcAllFlags};
// TODO: Just call computeKnownFPClass for RHS to handle non-constants.
return fcmpImpliesClass(Pred, F, LHS, *ConstRHS, LookThroughSrc);
}
};
} // namespace llvm
#endif // LLVM_ADT_GENERICFLOATINGPOINTPREDICATEUTILS_H