Skip to content

Commit d8d7ea5

Browse files
committed
[ConstantFPRange] Add support for mul/div
1 parent ae1cd7c commit d8d7ea5

File tree

3 files changed

+262
-0
lines changed

3 files changed

+262
-0
lines changed

llvm/include/llvm/IR/ConstantFPRange.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,15 @@ class [[nodiscard]] ConstantFPRange {
230230
/// Return a new range representing the possible values resulting
231231
/// from a subtraction of a value in this range and a value in \p Other.
232232
LLVM_ABI ConstantFPRange sub(const ConstantFPRange &Other) const;
233+
234+
/// Return a new range representing the possible values resulting
235+
/// from a multiplication of a value in this range and a value in \p Other.
236+
LLVM_ABI ConstantFPRange mul(const ConstantFPRange &Other) const;
237+
238+
/// Return a new range representing the possible values resulting
239+
/// from a division of a value in this range and a value in
240+
/// \p Other.
241+
LLVM_ABI ConstantFPRange div(const ConstantFPRange &Other) const;
233242
};
234243

235244
inline raw_ostream &operator<<(raw_ostream &OS, const ConstantFPRange &CR) {

llvm/lib/IR/ConstantFPRange.cpp

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,147 @@ ConstantFPRange ConstantFPRange::sub(const ConstantFPRange &Other) const {
506506
// fsub X, Y = fadd X, (fneg Y)
507507
return add(Other.negate());
508508
}
509+
510+
/// Represent a contiguous range of values sharing the same sign.
511+
struct SameSignRange {
512+
bool HasZero;
513+
bool HasNonZero;
514+
bool HasInf;
515+
// The lower and upper bounds of the range (inclusive).
516+
// The sign is dropped and infinities are excluded.
517+
std::optional<std::pair<APFloat, APFloat>> FinitePart;
518+
519+
explicit SameSignRange(const APFloat &Lower, const APFloat &Upper)
520+
: HasZero(Lower.isZero()), HasNonZero(!Upper.isZero()),
521+
HasInf(Upper.isInfinity()) {
522+
assert(!Lower.isNegative() && !Upper.isNegative() &&
523+
"The sign should be dropped.");
524+
assert(strictCompare(Lower, Upper) != APFloat::cmpGreaterThan &&
525+
"Empty set.");
526+
if (!Lower.isInfinity())
527+
FinitePart = {Lower,
528+
HasInf ? APFloat::getLargest(Lower.getSemantics()) : Upper};
529+
}
530+
};
531+
532+
/// Split the range into positive and negative components.
533+
static void splitPosNeg(const APFloat &Lower, const APFloat &Upper,
534+
std::optional<SameSignRange> &NegPart,
535+
std::optional<SameSignRange> &PosPart) {
536+
assert(strictCompare(Lower, Upper) != APFloat::cmpGreaterThan &&
537+
"Non-NaN part is empty.");
538+
if (Lower.isNegative() == Upper.isNegative()) {
539+
if (Lower.isNegative())
540+
NegPart = SameSignRange{abs(Upper), abs(Lower)};
541+
else
542+
PosPart = SameSignRange{Lower, Upper};
543+
return;
544+
}
545+
auto &Sem = Lower.getSemantics();
546+
NegPart = SameSignRange{APFloat::getZero(Sem), abs(Lower)};
547+
PosPart = SameSignRange{APFloat::getZero(Sem), Upper};
548+
}
549+
550+
ConstantFPRange ConstantFPRange::mul(const ConstantFPRange &Other) const {
551+
auto &Sem = getSemantics();
552+
bool ResMayBeQNaN = ((MayBeQNaN || MayBeSNaN) && !Other.isEmptySet()) ||
553+
((Other.MayBeQNaN || Other.MayBeSNaN) && !isEmptySet());
554+
if (isNaNOnly() || Other.isNaNOnly())
555+
return getNaNOnly(Sem, /*MayBeQNaN=*/ResMayBeQNaN,
556+
/*MayBeSNaN=*/false);
557+
std::optional<SameSignRange> LHSNeg, LHSPos, RHSNeg, RHSPos;
558+
splitPosNeg(Lower, Upper, LHSNeg, LHSPos);
559+
splitPosNeg(Other.Lower, Other.Upper, RHSNeg, RHSPos);
560+
APFloat ResLower = APFloat::getInf(Sem, /*Negative=*/false);
561+
APFloat ResUpper = APFloat::getInf(Sem, /*Negative=*/true);
562+
auto Update = [&](std::optional<SameSignRange> &LHS,
563+
std::optional<SameSignRange> &RHS, bool Negative) {
564+
if (!LHS || !RHS)
565+
return;
566+
// 0 * inf = QNaN
567+
ResMayBeQNaN |= LHS->HasZero && RHS->HasInf;
568+
ResMayBeQNaN |= RHS->HasZero && LHS->HasInf;
569+
// NonZero * inf = inf
570+
if ((LHS->HasInf && RHS->HasNonZero) || (RHS->HasInf && LHS->HasNonZero))
571+
(Negative ? ResLower : ResUpper) = APFloat::getInf(Sem, Negative);
572+
// Finite * Finite
573+
if (LHS->FinitePart && RHS->FinitePart) {
574+
APFloat NewLower = LHS->FinitePart->first * RHS->FinitePart->first;
575+
APFloat NewUpper = LHS->FinitePart->second * RHS->FinitePart->second;
576+
if (Negative) {
577+
ResLower = minnum(ResLower, -NewUpper);
578+
ResUpper = maxnum(ResUpper, -NewLower);
579+
} else {
580+
ResLower = minnum(ResLower, NewLower);
581+
ResUpper = maxnum(ResUpper, NewUpper);
582+
}
583+
}
584+
};
585+
Update(LHSNeg, RHSNeg, /*Negative=*/false);
586+
Update(LHSNeg, RHSPos, /*Negative=*/true);
587+
Update(LHSPos, RHSNeg, /*Negative=*/true);
588+
Update(LHSPos, RHSPos, /*Negative=*/false);
589+
return ConstantFPRange(ResLower, ResUpper, ResMayBeQNaN, /*MayBeSNaN=*/false);
590+
}
591+
592+
ConstantFPRange ConstantFPRange::div(const ConstantFPRange &Other) const {
593+
auto &Sem = getSemantics();
594+
bool ResMayBeQNaN = ((MayBeQNaN || MayBeSNaN) && !Other.isEmptySet()) ||
595+
((Other.MayBeQNaN || Other.MayBeSNaN) && !isEmptySet());
596+
if (isNaNOnly() || Other.isNaNOnly())
597+
return getNaNOnly(Sem, /*MayBeQNaN=*/ResMayBeQNaN,
598+
/*MayBeSNaN=*/false);
599+
std::optional<SameSignRange> LHSNeg, LHSPos, RHSNeg, RHSPos;
600+
splitPosNeg(Lower, Upper, LHSNeg, LHSPos);
601+
splitPosNeg(Other.Lower, Other.Upper, RHSNeg, RHSPos);
602+
APFloat ResLower = APFloat::getInf(Sem, /*Negative=*/false);
603+
APFloat ResUpper = APFloat::getInf(Sem, /*Negative=*/true);
604+
auto Update = [&](std::optional<SameSignRange> &LHS,
605+
std::optional<SameSignRange> &RHS, bool Negative) {
606+
if (!LHS || !RHS)
607+
return;
608+
// inf / inf = QNaN 0 / 0 = QNaN
609+
ResMayBeQNaN |= LHS->HasInf && RHS->HasInf;
610+
ResMayBeQNaN |= LHS->HasZero && RHS->HasZero;
611+
// It is not straightforward to infer HasNonZeroFinite = HasFinite &&
612+
// HasNonZero. By definitions we have:
613+
// HasFinite = HasNonZeroFinite || HasZero
614+
// HasNonZero = HasNonZeroFinite || HasInf
615+
// Since the range is contiguous, if both HasFinite and HasNonZero are true,
616+
// HasNonZeroFinite must be true.
617+
bool LHSHasNonZeroFinite = LHS->FinitePart && LHS->HasNonZero;
618+
bool RHSHasNonZeroFinite = RHS->FinitePart && RHS->HasNonZero;
619+
// inf / Finite = inf FiniteNonZero / 0 = inf
620+
if ((LHS->HasInf && RHS->FinitePart) ||
621+
(LHSHasNonZeroFinite && RHS->HasZero))
622+
(Negative ? ResLower : ResUpper) = APFloat::getInf(Sem, Negative);
623+
// Finite / inf = 0
624+
if (LHS->FinitePart && RHS->HasInf) {
625+
APFloat Zero = APFloat::getZero(Sem, /*Negative=*/Negative);
626+
ResLower = minnum(ResLower, Zero);
627+
ResUpper = maxnum(ResUpper, Zero);
628+
}
629+
// Finite / FiniteNonZero
630+
if (LHS->FinitePart && RHSHasNonZeroFinite) {
631+
assert(!RHS->FinitePart->second.isZero() &&
632+
"Divisor should be non-zero.");
633+
APFloat NewLower = LHS->FinitePart->first / RHS->FinitePart->second;
634+
APFloat NewUpper = LHS->FinitePart->second /
635+
(RHS->FinitePart->first.isZero()
636+
? APFloat::getSmallest(Sem, /*Negative=*/false)
637+
: RHS->FinitePart->first);
638+
if (Negative) {
639+
ResLower = minnum(ResLower, -NewUpper);
640+
ResUpper = maxnum(ResUpper, -NewLower);
641+
} else {
642+
ResLower = minnum(ResLower, NewLower);
643+
ResUpper = maxnum(ResUpper, NewUpper);
644+
}
645+
}
646+
};
647+
Update(LHSNeg, RHSNeg, /*Negative=*/false);
648+
Update(LHSNeg, RHSPos, /*Negative=*/true);
649+
Update(LHSPos, RHSNeg, /*Negative=*/true);
650+
Update(LHSPos, RHSPos, /*Negative=*/false);
651+
return ConstantFPRange(ResLower, ResUpper, ResMayBeQNaN, /*MayBeSNaN=*/false);
652+
}

llvm/unittests/IR/ConstantFPRangeTest.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,4 +1065,113 @@ TEST_F(ConstantFPRangeTest, sub) {
10651065
#endif
10661066
}
10671067

1068+
TEST_F(ConstantFPRangeTest, mul) {
1069+
EXPECT_EQ(Full.mul(Full), NonNaN.unionWith(QNaN));
1070+
EXPECT_EQ(Full.mul(Empty), Empty);
1071+
EXPECT_EQ(Empty.mul(Full), Empty);
1072+
EXPECT_EQ(Empty.mul(Empty), Empty);
1073+
EXPECT_EQ(One.mul(One), ConstantFPRange(APFloat(1.0)));
1074+
EXPECT_EQ(Some.mul(Some),
1075+
ConstantFPRange::getNonNaN(APFloat(-9.0), APFloat(9.0)));
1076+
EXPECT_EQ(SomePos.mul(SomeNeg),
1077+
ConstantFPRange::getNonNaN(APFloat(-9.0), APFloat(-0.0)));
1078+
EXPECT_EQ(PosInf.mul(PosInf), PosInf);
1079+
EXPECT_EQ(NegInf.mul(NegInf), PosInf);
1080+
EXPECT_EQ(PosInf.mul(Finite), NonNaN.unionWith(QNaN));
1081+
EXPECT_EQ(NegInf.mul(Finite), NonNaN.unionWith(QNaN));
1082+
EXPECT_EQ(PosInf.mul(NegInf), NegInf);
1083+
EXPECT_EQ(NegInf.mul(PosInf), NegInf);
1084+
EXPECT_EQ(PosZero.mul(NegZero), NegZero);
1085+
EXPECT_EQ(PosZero.mul(Zero), Zero);
1086+
EXPECT_EQ(NegZero.mul(NegZero), PosZero);
1087+
EXPECT_EQ(NegZero.mul(Zero), Zero);
1088+
EXPECT_EQ(NaN.mul(NaN), QNaN);
1089+
EXPECT_EQ(NaN.mul(Finite), QNaN);
1090+
1091+
#if defined(EXPENSIVE_CHECKS)
1092+
EnumerateTwoInterestingConstantFPRanges(
1093+
[](const ConstantFPRange &LHS, const ConstantFPRange &RHS) {
1094+
ConstantFPRange Res = LHS.mul(RHS);
1095+
ConstantFPRange Expected =
1096+
ConstantFPRange::getEmpty(LHS.getSemantics());
1097+
EnumerateValuesInConstantFPRange(
1098+
LHS,
1099+
[&](const APFloat &LHSC) {
1100+
EnumerateValuesInConstantFPRange(
1101+
RHS,
1102+
[&](const APFloat &RHSC) {
1103+
APFloat Prod = LHSC * RHSC;
1104+
EXPECT_TRUE(Res.contains(Prod))
1105+
<< "Wrong result for " << LHS << " * " << RHS
1106+
<< ". The result " << Res << " should contain " << Prod;
1107+
if (!Expected.contains(Prod))
1108+
Expected = Expected.unionWith(ConstantFPRange(Prod));
1109+
},
1110+
/*IgnoreNaNPayload=*/true);
1111+
},
1112+
/*IgnoreNaNPayload=*/true);
1113+
EXPECT_EQ(Res, Expected)
1114+
<< "Suboptimal result for " << LHS << " * " << RHS << ". Expected "
1115+
<< Expected << ", but got " << Res;
1116+
},
1117+
SparseLevel::SpecialValuesOnly);
1118+
#endif
1119+
}
1120+
1121+
TEST_F(ConstantFPRangeTest, div) {
1122+
EXPECT_EQ(Full.div(Full), NonNaN.unionWith(QNaN));
1123+
EXPECT_EQ(Full.div(Empty), Empty);
1124+
EXPECT_EQ(Empty.div(Full), Empty);
1125+
EXPECT_EQ(Empty.div(Empty), Empty);
1126+
EXPECT_EQ(One.div(One), ConstantFPRange(APFloat(1.0)));
1127+
EXPECT_EQ(Some.div(Some), NonNaN.unionWith(QNaN));
1128+
EXPECT_EQ(SomePos.div(SomeNeg),
1129+
ConstantFPRange(APFloat::getInf(Sem, /*Negative=*/true),
1130+
APFloat::getZero(Sem, /*Negative=*/true),
1131+
/*MayBeQNaN=*/true, /*MayBeSNaN=*/false));
1132+
EXPECT_EQ(PosInf.div(PosInf), QNaN);
1133+
EXPECT_EQ(NegInf.div(NegInf), QNaN);
1134+
EXPECT_EQ(PosInf.div(Finite), NonNaN);
1135+
EXPECT_EQ(NegInf.div(Finite), NonNaN);
1136+
EXPECT_EQ(PosInf.div(NegInf), QNaN);
1137+
EXPECT_EQ(NegInf.div(PosInf), QNaN);
1138+
EXPECT_EQ(Zero.div(Zero), QNaN);
1139+
EXPECT_EQ(SomePos.div(PosInf), PosZero);
1140+
EXPECT_EQ(SomeNeg.div(PosInf), NegZero);
1141+
EXPECT_EQ(PosInf.div(SomePos), PosInf);
1142+
EXPECT_EQ(NegInf.div(SomeNeg), PosInf);
1143+
EXPECT_EQ(NegInf.div(Some), NonNaN);
1144+
EXPECT_EQ(NaN.div(NaN), QNaN);
1145+
EXPECT_EQ(NaN.div(Finite), QNaN);
1146+
1147+
#if defined(EXPENSIVE_CHECKS)
1148+
EnumerateTwoInterestingConstantFPRanges(
1149+
[](const ConstantFPRange &LHS, const ConstantFPRange &RHS) {
1150+
ConstantFPRange Res = LHS.div(RHS);
1151+
ConstantFPRange Expected =
1152+
ConstantFPRange::getEmpty(LHS.getSemantics());
1153+
EnumerateValuesInConstantFPRange(
1154+
LHS,
1155+
[&](const APFloat &LHSC) {
1156+
EnumerateValuesInConstantFPRange(
1157+
RHS,
1158+
[&](const APFloat &RHSC) {
1159+
APFloat Val = LHSC / RHSC;
1160+
EXPECT_TRUE(Res.contains(Val))
1161+
<< "Wrong result for " << LHS << " / " << RHS
1162+
<< ". The result " << Res << " should contain " << Val;
1163+
if (!Expected.contains(Val))
1164+
Expected = Expected.unionWith(ConstantFPRange(Val));
1165+
},
1166+
/*IgnoreNaNPayload=*/true);
1167+
},
1168+
/*IgnoreNaNPayload=*/true);
1169+
EXPECT_EQ(Res, Expected)
1170+
<< "Suboptimal result for " << LHS << " / " << RHS << ". Expected "
1171+
<< Expected << ", but got " << Res;
1172+
},
1173+
SparseLevel::SpecialValuesOnly);
1174+
#endif
1175+
}
1176+
10681177
} // anonymous namespace

0 commit comments

Comments
 (0)