Skip to content

Commit e66cfeb

Browse files
authored
[ValueTracking] Handle range attributes (#85143)
Handle the range attribute in ValueTracking.
1 parent f24d68a commit e66cfeb

File tree

9 files changed

+481
-14
lines changed

9 files changed

+481
-14
lines changed

llvm/include/llvm/IR/Argument.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
#include "llvm/ADT/Twine.h"
1717
#include "llvm/IR/Attributes.h"
1818
#include "llvm/IR/Value.h"
19+
#include <optional>
1920

2021
namespace llvm {
2122

23+
class ConstantRange;
24+
2225
/// This class represents an incoming formal argument to a Function. A formal
2326
/// argument, since it is ``formal'', does not contain an actual value but
2427
/// instead represents the type, argument number, and attributes of an argument
@@ -67,6 +70,10 @@ class Argument final : public Value {
6770
/// disallowed floating-point values. Otherwise, fcNone is returned.
6871
FPClassTest getNoFPClass() const;
6972

73+
/// If this argument has a range attribute, return the value range of the
74+
/// argument. Otherwise, std::nullopt is returned.
75+
std::optional<ConstantRange> getRange() const;
76+
7077
/// Return true if this argument has the byval attribute.
7178
bool hasByValAttr() const;
7279

llvm/include/llvm/IR/InstrTypes.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ namespace llvm {
4343
class StringRef;
4444
class Type;
4545
class Value;
46+
class ConstantRange;
4647

4748
namespace Intrinsic {
4849
typedef unsigned ID;
@@ -1917,7 +1918,7 @@ class CallBase : public Instruction {
19171918

19181919
// Look at the callee, if available.
19191920
if (const Function *F = getCalledFunction())
1920-
return F->getAttributes().getRetAttr(Kind);
1921+
return F->getRetAttribute(Kind);
19211922
return Attribute();
19221923
}
19231924

@@ -2154,6 +2155,10 @@ class CallBase : public Instruction {
21542155
/// parameter.
21552156
FPClassTest getParamNoFPClass(unsigned i) const;
21562157

2158+
/// If this return value has a range attribute, return the value range of the
2159+
/// argument. Otherwise, std::nullopt is returned.
2160+
std::optional<ConstantRange> getRange() const;
2161+
21572162
/// Return true if the return value is known to be not null.
21582163
/// This may be because it has the nonnull attribute, or because at least
21592164
/// one byte is dereferenceable and the pointer is in addrspace(0).

llvm/lib/Analysis/InstructionSimplify.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3736,15 +3736,10 @@ static std::optional<ConstantRange> getRange(Value *V,
37363736
if (MDNode *MD = IIQ.getMetadata(I, LLVMContext::MD_range))
37373737
return getConstantRangeFromMetadata(*MD);
37383738

3739-
Attribute Range;
3740-
if (const Argument *A = dyn_cast<Argument>(V)) {
3741-
Range = A->getAttribute(llvm::Attribute::Range);
3742-
} else if (const CallBase *CB = dyn_cast<CallBase>(V)) {
3743-
Range = CB->getRetAttr(llvm::Attribute::Range);
3744-
}
3745-
3746-
if (Range.isValid())
3747-
return Range.getRange();
3739+
if (const Argument *A = dyn_cast<Argument>(V))
3740+
return A->getRange();
3741+
else if (const CallBase *CB = dyn_cast<CallBase>(V))
3742+
return CB->getRange();
37483743

37493744
return std::nullopt;
37503745
}

llvm/lib/Analysis/ValueTracking.cpp

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,14 +1500,20 @@ static void computeKnownBitsFromOperator(const Operator *I,
15001500
break;
15011501
}
15021502
case Instruction::Call:
1503-
case Instruction::Invoke:
1503+
case Instruction::Invoke: {
15041504
// If range metadata is attached to this call, set known bits from that,
15051505
// and then intersect with known bits based on other properties of the
15061506
// function.
15071507
if (MDNode *MD =
15081508
Q.IIQ.getMetadata(cast<Instruction>(I), LLVMContext::MD_range))
15091509
computeKnownBitsFromRangeMetadata(*MD, Known);
1510-
if (const Value *RV = cast<CallBase>(I)->getReturnedArgOperand()) {
1510+
1511+
const auto *CB = cast<CallBase>(I);
1512+
1513+
if (std::optional<ConstantRange> Range = CB->getRange())
1514+
Known = Known.unionWith(Range->toKnownBits());
1515+
1516+
if (const Value *RV = CB->getReturnedArgOperand()) {
15111517
if (RV->getType() == I->getType()) {
15121518
computeKnownBits(RV, Known2, Depth + 1, Q);
15131519
Known = Known.unionWith(Known2);
@@ -1679,6 +1685,7 @@ static void computeKnownBitsFromOperator(const Operator *I,
16791685
}
16801686
}
16811687
break;
1688+
}
16821689
case Instruction::ShuffleVector: {
16831690
auto *Shuf = dyn_cast<ShuffleVectorInst>(I);
16841691
// FIXME: Do we need to handle ConstantExpr involving shufflevectors?
@@ -1933,6 +1940,10 @@ void computeKnownBits(const Value *V, const APInt &DemandedElts,
19331940
// assumptions. Confirm that we've handled them all.
19341941
assert(!isa<ConstantData>(V) && "Unhandled constant data!");
19351942

1943+
if (const auto *A = dyn_cast<Argument>(V))
1944+
if (std::optional<ConstantRange> Range = A->getRange())
1945+
Known = Range->toKnownBits();
1946+
19361947
// All recursive calls that increase depth must come after this.
19371948
if (Depth == MaxAnalysisRecursionDepth)
19381949
return;
@@ -2783,6 +2794,11 @@ static bool isKnownNonZeroFromOperator(const Operator *I,
27832794
} else {
27842795
if (MDNode *Ranges = Q.IIQ.getMetadata(Call, LLVMContext::MD_range))
27852796
return rangeMetadataExcludesValue(Ranges, APInt::getZero(BitWidth));
2797+
if (std::optional<ConstantRange> Range = Call->getRange()) {
2798+
const APInt ZeroValue(Range->getBitWidth(), 0);
2799+
if (!Range->contains(ZeroValue))
2800+
return true;
2801+
}
27862802
if (const Value *RV = Call->getReturnedArgOperand())
27872803
if (RV->getType() == I->getType() && isKnownNonZero(RV, Depth, Q))
27882804
return true;
@@ -2921,6 +2937,13 @@ bool isKnownNonZero(const Value *V, const APInt &DemandedElts, unsigned Depth,
29212937
return false;
29222938
}
29232939

2940+
if (const auto *A = dyn_cast<Argument>(V))
2941+
if (std::optional<ConstantRange> Range = A->getRange()) {
2942+
const APInt ZeroValue(Range->getBitWidth(), 0);
2943+
if (!Range->contains(ZeroValue))
2944+
return true;
2945+
}
2946+
29242947
if (!isa<Constant>(V) && isKnownNonZeroFromAssume(V, Q))
29252948
return true;
29262949

@@ -9146,12 +9169,19 @@ ConstantRange llvm::computeConstantRange(const Value *V, bool ForSigned,
91469169
// TODO: Return ConstantRange.
91479170
setLimitForFPToI(cast<Instruction>(V), Lower, Upper);
91489171
CR = ConstantRange::getNonEmpty(Lower, Upper);
9149-
}
9172+
} else if (const auto *A = dyn_cast<Argument>(V))
9173+
if (std::optional<ConstantRange> Range = A->getRange())
9174+
CR = *Range;
91509175

9151-
if (auto *I = dyn_cast<Instruction>(V))
9176+
if (auto *I = dyn_cast<Instruction>(V)) {
91529177
if (auto *Range = IIQ.getMetadata(I, LLVMContext::MD_range))
91539178
CR = CR.intersectWith(getConstantRangeFromMetadata(*Range));
91549179

9180+
if (const auto *CB = dyn_cast<CallBase>(V))
9181+
if (std::optional<ConstantRange> Range = CB->getRange())
9182+
CR = CR.intersectWith(*Range);
9183+
}
9184+
91559185
if (CtxI && AC) {
91569186
// Try to restrict the range based on information from assumptions.
91579187
for (auto &AssumeVH : AC->assumptionsFor(V)) {

llvm/lib/IR/Function.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "llvm/IR/Attributes.h"
2525
#include "llvm/IR/BasicBlock.h"
2626
#include "llvm/IR/Constant.h"
27+
#include "llvm/IR/ConstantRange.h"
2728
#include "llvm/IR/Constants.h"
2829
#include "llvm/IR/DerivedTypes.h"
2930
#include "llvm/IR/GlobalValue.h"
@@ -256,6 +257,13 @@ FPClassTest Argument::getNoFPClass() const {
256257
return getParent()->getParamNoFPClass(getArgNo());
257258
}
258259

260+
std::optional<ConstantRange> Argument::getRange() const {
261+
const Attribute RangeAttr = getAttribute(llvm::Attribute::Range);
262+
if (RangeAttr.isValid())
263+
return RangeAttr.getRange();
264+
return std::nullopt;
265+
}
266+
259267
bool Argument::hasNestAttr() const {
260268
if (!getType()->isPointerTy()) return false;
261269
return hasAttribute(Attribute::Nest);

llvm/lib/IR/Instructions.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "llvm/IR/Attributes.h"
2020
#include "llvm/IR/BasicBlock.h"
2121
#include "llvm/IR/Constant.h"
22+
#include "llvm/IR/ConstantRange.h"
2223
#include "llvm/IR/Constants.h"
2324
#include "llvm/IR/DataLayout.h"
2425
#include "llvm/IR/DerivedTypes.h"
@@ -395,6 +396,13 @@ FPClassTest CallBase::getParamNoFPClass(unsigned i) const {
395396
return Mask;
396397
}
397398

399+
std::optional<ConstantRange> CallBase::getRange() const {
400+
const Attribute RangeAttr = getRetAttr(llvm::Attribute::Range);
401+
if (RangeAttr.isValid())
402+
return RangeAttr.getRange();
403+
return std::nullopt;
404+
}
405+
398406
bool CallBase::isReturnNonNull() const {
399407
if (hasRetAttr(Attribute::NonNull))
400408
return true;

llvm/test/Analysis/ValueTracking/known-non-zero.ll

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1303,4 +1303,151 @@ define <2 x i1> @range_metadata_vec(ptr %p, <2 x i32> %x) {
13031303
ret <2 x i1> %cmp
13041304
}
13051305

1306+
define i1 @range_attr(i8 range(i8 1, 0) %x, i8 %y) {
1307+
; CHECK-LABEL: @range_attr(
1308+
; CHECK-NEXT: ret i1 false
1309+
;
1310+
%or = or i8 %y, %x
1311+
%cmp = icmp eq i8 %or, 0
1312+
ret i1 %cmp
1313+
}
1314+
1315+
define i1 @neg_range_attr(i8 range(i8 -1, 1) %x, i8 %y) {
1316+
; CHECK-LABEL: @neg_range_attr(
1317+
; CHECK-NEXT: [[I:%.*]] = or i8 [[Y:%.*]], [[X:%.*]]
1318+
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[I]], 0
1319+
; CHECK-NEXT: ret i1 [[CMP]]
1320+
;
1321+
%or = or i8 %y, %x
1322+
%cmp = icmp eq i8 %or, 0
1323+
ret i1 %cmp
1324+
}
1325+
1326+
declare range(i8 1, 0) i8 @returns_non_zero_range_helper()
1327+
declare range(i8 -1, 1) i8 @returns_contain_zero_range_helper()
1328+
1329+
define i1 @range_return(i8 %y) {
1330+
; CHECK-LABEL: @range_return(
1331+
; CHECK-NEXT: [[I:%.*]] = call i8 @returns_non_zero_range_helper()
1332+
; CHECK-NEXT: ret i1 false
1333+
;
1334+
%x = call i8 @returns_non_zero_range_helper()
1335+
%or = or i8 %y, %x
1336+
%cmp = icmp eq i8 %or, 0
1337+
ret i1 %cmp
1338+
}
1339+
1340+
define i1 @neg_range_return(i8 %y) {
1341+
; CHECK-LABEL: @neg_range_return(
1342+
; CHECK-NEXT: [[I:%.*]] = call i8 @returns_contain_zero_range_helper()
1343+
; CHECK-NEXT: [[OR:%.*]] = or i8 [[Y:%.*]], [[I]]
1344+
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[OR]], 0
1345+
; CHECK-NEXT: ret i1 [[CMP]]
1346+
;
1347+
%x = call i8 @returns_contain_zero_range_helper()
1348+
%or = or i8 %y, %x
1349+
%cmp = icmp eq i8 %or, 0
1350+
ret i1 %cmp
1351+
}
1352+
1353+
declare i8 @returns_i8_helper()
1354+
1355+
define i1 @range_call(i8 %y) {
1356+
; CHECK-LABEL: @range_call(
1357+
; CHECK-NEXT: [[I:%.*]] = call range(i8 1, 0) i8 @returns_i8_helper()
1358+
; CHECK-NEXT: ret i1 false
1359+
;
1360+
%x = call range(i8 1, 0) i8 @returns_i8_helper()
1361+
%or = or i8 %y, %x
1362+
%cmp = icmp eq i8 %or, 0
1363+
ret i1 %cmp
1364+
}
1365+
1366+
define i1 @neg_range_call(i8 %y) {
1367+
; CHECK-LABEL: @neg_range_call(
1368+
; CHECK-NEXT: [[I:%.*]] = call range(i8 -1, 1) i8 @returns_i8_helper()
1369+
; CHECK-NEXT: [[OR:%.*]] = or i8 [[Y:%.*]], [[I]]
1370+
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[OR]], 0
1371+
; CHECK-NEXT: ret i1 [[CMP]]
1372+
;
1373+
%x = call range(i8 -1, 1) i8 @returns_i8_helper()
1374+
%or = or i8 %y, %x
1375+
%cmp = icmp eq i8 %or, 0
1376+
ret i1 %cmp
1377+
}
1378+
1379+
define <2 x i1> @range_attr_vec(<2 x i8> range(i8 1, 0) %x, <2 x i8> %y) {
1380+
; CHECK-LABEL: @range_attr_vec(
1381+
; CHECK-NEXT: ret <2 x i1> <i1 true, i1 true>
1382+
;
1383+
%or = or <2 x i8> %y, %x
1384+
%cmp = icmp ne <2 x i8> %or, zeroinitializer
1385+
ret <2 x i1> %cmp
1386+
}
1387+
1388+
define <2 x i1> @neg_range_attr_vec(<2 x i8> range(i8 -1, 1) %x, <2 x i8> %y) {
1389+
; CHECK-LABEL: @neg_range_attr_vec(
1390+
; CHECK-NEXT: [[OR:%.*]] = or <2 x i8> [[Y:%.*]], [[X:%.*]]
1391+
; CHECK-NEXT: [[CMP:%.*]] = icmp ne <2 x i8> [[OR]], zeroinitializer
1392+
; CHECK-NEXT: ret <2 x i1> [[CMP]]
1393+
;
1394+
%or = or <2 x i8> %y, %x
1395+
%cmp = icmp ne <2 x i8> %or, zeroinitializer
1396+
ret <2 x i1> %cmp
1397+
}
1398+
1399+
declare range(i8 1, 0) <2 x i8> @returns_non_zero_range_helper_vec()
1400+
declare range(i8 -1, 1) <2 x i8> @returns_contain_zero_range_helper_vec()
1401+
1402+
define <2 x i1> @range_return_vec(<2 x i8> %y) {
1403+
; CHECK-LABEL: @range_return_vec(
1404+
; CHECK-NEXT: [[I:%.*]] = call <2 x i8> @returns_non_zero_range_helper_vec()
1405+
; CHECK-NEXT: ret <2 x i1> <i1 true, i1 true>
1406+
;
1407+
%x = call <2 x i8> @returns_non_zero_range_helper_vec()
1408+
%or = or <2 x i8> %y, %x
1409+
%cmp = icmp ne <2 x i8> %or, zeroinitializer
1410+
ret <2 x i1> %cmp
1411+
}
1412+
1413+
define <2 x i1> @neg_range_return_vec(<2 x i8> %y) {
1414+
; CHECK-LABEL: @neg_range_return_vec(
1415+
; CHECK-NEXT: [[I:%.*]] = call <2 x i8> @returns_contain_zero_range_helper_vec()
1416+
; CHECK-NEXT: [[OR:%.*]] = or <2 x i8> [[Y:%.*]], [[I]]
1417+
; CHECK-NEXT: [[CMP:%.*]] = icmp ne <2 x i8> [[OR]], zeroinitializer
1418+
; CHECK-NEXT: ret <2 x i1> [[CMP]]
1419+
;
1420+
%x = call <2 x i8> @returns_contain_zero_range_helper_vec()
1421+
%or = or <2 x i8> %y, %x
1422+
%cmp = icmp ne <2 x i8> %or, zeroinitializer
1423+
ret <2 x i1> %cmp
1424+
}
1425+
1426+
declare <2 x i8> @returns_i8_helper_vec()
1427+
1428+
define <2 x i1> @range_call_vec(<2 x i8> %y) {
1429+
; CHECK-LABEL: @range_call_vec(
1430+
; CHECK-NEXT: [[I:%.*]] = call range(i8 1, 0) <2 x i8> @returns_i8_helper_vec()
1431+
; CHECK-NEXT: ret <2 x i1> <i1 true, i1 true>
1432+
;
1433+
%x = call range(i8 1, 0) <2 x i8> @returns_i8_helper_vec()
1434+
%or = or <2 x i8> %y, %x
1435+
%cmp = icmp ne <2 x i8> %or, zeroinitializer
1436+
ret <2 x i1> %cmp
1437+
}
1438+
1439+
define <2 x i1> @neg_range_call_vec(<2 x i8> %y) {
1440+
; CHECK-LABEL: @neg_range_call_vec(
1441+
; CHECK-NEXT: [[I:%.*]] = call range(i8 -1, 1) <2 x i8> @returns_i8_helper_vec()
1442+
; CHECK-NEXT: [[OR:%.*]] = or <2 x i8> [[Y:%.*]], [[I]]
1443+
; CHECK-NEXT: [[CMP:%.*]] = icmp ne <2 x i8> [[OR]], zeroinitializer
1444+
; CHECK-NEXT: ret <2 x i1> [[CMP]]
1445+
;
1446+
%x = call range(i8 -1, 1) <2 x i8> @returns_i8_helper_vec()
1447+
%or = or <2 x i8> %y, %x
1448+
%cmp = icmp ne <2 x i8> %or, zeroinitializer
1449+
ret <2 x i1> %cmp
1450+
}
1451+
1452+
13061453
declare i32 @llvm.experimental.get.vector.length.i32(i32, i32, i1)

0 commit comments

Comments
 (0)