[Attributor] Swap range metadata to attribute for calls. (#108835)

This commit is contained in:
Andreas Jonson 2025-07-05 16:47:03 +02:00 committed by GitHub
parent 9d994d1c08
commit 0a067dc107
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 99 additions and 44 deletions

View File

@ -990,6 +990,13 @@ static bool addIfNotExistent(LLVMContext &Ctx, const Attribute &Attr,
AB.addAttribute(Attr);
return true;
}
if (Attr.isConstantRangeAttribute()) {
Attribute::AttrKind Kind = Attr.getKindAsEnum();
if (!ForceReplace && AttrSet.hasAttribute(Kind))
return false;
AB.addAttribute(Attr);
return true;
}
llvm_unreachable("Expected enum or string attribute!");
}

View File

@ -9172,44 +9172,58 @@ struct AAValueConstantRangeImpl : AAValueConstantRange {
return MDNode::get(Ctx, LowAndHigh);
}
/// Return true if \p Assumed is included in \p KnownRanges.
static bool isBetterRange(const ConstantRange &Assumed, MDNode *KnownRanges) {
/// Return true if \p Assumed is included in ranges from instruction \p I.
static bool isBetterRange(const ConstantRange &Assumed,
const Instruction &I) {
if (Assumed.isFullSet())
return false;
if (!KnownRanges)
return true;
std::optional<ConstantRange> Known;
// If multiple ranges are annotated in IR, we give up to annotate assumed
// range for now.
if (const auto *CB = dyn_cast<CallBase>(&I)) {
Known = CB->getRange();
} else if (MDNode *KnownRanges = I.getMetadata(LLVMContext::MD_range)) {
// If multiple ranges are annotated in IR, we give up to annotate assumed
// range for now.
// TODO: If there exists a known range which containts assumed range, we
// can say assumed range is better.
if (KnownRanges->getNumOperands() > 2)
return false;
// TODO: If there exists a known range which containts assumed range, we
// can say assumed range is better.
if (KnownRanges->getNumOperands() > 2)
return false;
ConstantInt *Lower =
mdconst::extract<ConstantInt>(KnownRanges->getOperand(0));
ConstantInt *Upper =
mdconst::extract<ConstantInt>(KnownRanges->getOperand(1));
ConstantInt *Lower =
mdconst::extract<ConstantInt>(KnownRanges->getOperand(0));
ConstantInt *Upper =
mdconst::extract<ConstantInt>(KnownRanges->getOperand(1));
ConstantRange Known(Lower->getValue(), Upper->getValue());
return Known.contains(Assumed) && Known != Assumed;
Known.emplace(Lower->getValue(), Upper->getValue());
}
return !Known || (*Known != Assumed && Known->contains(Assumed));
}
/// Helper function to set range metadata.
static bool
setRangeMetadataIfisBetterRange(Instruction *I,
const ConstantRange &AssumedConstantRange) {
auto *OldRangeMD = I->getMetadata(LLVMContext::MD_range);
if (isBetterRange(AssumedConstantRange, OldRangeMD)) {
if (!AssumedConstantRange.isEmptySet()) {
I->setMetadata(LLVMContext::MD_range,
getMDNodeForConstantRange(I->getType(), I->getContext(),
AssumedConstantRange));
return true;
}
if (isBetterRange(AssumedConstantRange, *I)) {
I->setMetadata(LLVMContext::MD_range,
getMDNodeForConstantRange(I->getType(), I->getContext(),
AssumedConstantRange));
return true;
}
return false;
}
/// Helper function to set range return attribute.
static bool
setRangeRetAttrIfisBetterRange(Attributor &A, const IRPosition &IRP,
Instruction *I,
const ConstantRange &AssumedConstantRange) {
if (isBetterRange(AssumedConstantRange, *I)) {
A.manifestAttrs(IRP,
Attribute::get(I->getContext(), Attribute::Range,
AssumedConstantRange),
/*ForceReplace*/ true);
return true;
}
return false;
}
@ -9226,9 +9240,13 @@ struct AAValueConstantRangeImpl : AAValueConstantRange {
if (Instruction *I = dyn_cast<Instruction>(&V)) {
assert(I == getCtxI() && "Should not annotate an instruction which is "
"not the context instruction");
if (isa<CallInst>(I) || isa<LoadInst>(I))
if (isa<LoadInst>(I))
if (setRangeMetadataIfisBetterRange(I, AssumedConstantRange))
Changed = ChangeStatus::CHANGED;
if (isa<CallInst>(I))
if (setRangeRetAttrIfisBetterRange(A, getIRPosition(), I,
AssumedConstantRange))
Changed = ChangeStatus::CHANGED;
}
}
@ -9624,10 +9642,11 @@ struct AAValueConstantRangeCallSiteReturned
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
// If it is a load instruction with range metadata, use the metadata.
if (CallInst *CI = dyn_cast<CallInst>(&getAssociatedValue()))
if (auto *RangeMD = CI->getMetadata(LLVMContext::MD_range))
intersectKnown(getConstantRangeFromMetadata(*RangeMD));
// If it is a call instruction with range attribute, use the range.
if (CallInst *CI = dyn_cast<CallInst>(&getAssociatedValue())) {
if (std::optional<ConstantRange> Range = CI->getRange())
intersectKnown(*Range);
}
AAValueConstantRangeImpl::initialize(A);
}

View File

@ -68,7 +68,7 @@ define i64 @fn2c() {
; CGSCC-NEXT: entry:
; CGSCC-NEXT: [[CONV:%.*]] = sext i32 undef to i64
; CGSCC-NEXT: [[ADD:%.*]] = add i64 42, [[CONV]]
; CGSCC-NEXT: [[CALL2:%.*]] = call i64 @fn1(i64 [[ADD]]) #[[ATTR2]], !range [[RNG0:![0-9]+]]
; CGSCC-NEXT: [[CALL2:%.*]] = call range(i64 -2147483606, 2147483690) i64 @fn1(i64 [[ADD]]) #[[ATTR2]]
; CGSCC-NEXT: ret i64 [[CALL2]]
;
entry:
@ -91,13 +91,11 @@ entry:
ret i64 %cond
}
;.
; TUNIT: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
;.
; CGSCC: attributes #[[ATTR0]] = { mustprogress nofree nosync nounwind willreturn memory(none) }
; CGSCC: attributes #[[ATTR1]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
; CGSCC: attributes #[[ATTR2]] = { nofree nosync willreturn }
;.
; TUNIT: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
;.
; CGSCC: [[RNG0]] = !{i64 -2147483606, i64 2147483690}
;.
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
; CHECK: {{.*}}

View File

@ -19,7 +19,7 @@ define i32 @test0-range-check(ptr %p) {
; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
; TUNIT-LABEL: define {{[^@]+}}@test0-range-check
; TUNIT-SAME: (ptr nofree readonly align 4 captures(none) [[P:%.*]]) #[[ATTR0]] {
; TUNIT-NEXT: [[A:%.*]] = tail call i32 @test0(ptr nofree noundef readonly align 4 captures(none) [[P]]) #[[ATTR3:[0-9]+]], !range [[RNG0]]
; TUNIT-NEXT: [[A:%.*]] = tail call range(i32 0, 10) i32 @test0(ptr nofree noundef readonly align 4 captures(none) [[P]]) #[[ATTR3:[0-9]+]]
; TUNIT-NEXT: ret i32 [[A]]
;
; CGSCC: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(argmem: read)
@ -32,6 +32,40 @@ define i32 @test0-range-check(ptr %p) {
ret i32 %a
}
define i32 @test0-range-check-smaller-current-range-attr(ptr %p) {
; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
; TUNIT-LABEL: define {{[^@]+}}@test0-range-check-smaller-current-range-attr
; TUNIT-SAME: (ptr nofree readonly align 4 captures(none) [[P:%.*]]) #[[ATTR0]] {
; TUNIT-NEXT: [[A:%.*]] = tail call range(i32 2, 5) i32 @test0(ptr nofree noundef readonly align 4 captures(none) [[P]]) #[[ATTR3]]
; TUNIT-NEXT: ret i32 [[A]]
;
; CGSCC: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(argmem: read)
; CGSCC-LABEL: define {{[^@]+}}@test0-range-check-smaller-current-range-attr
; CGSCC-SAME: (ptr nofree noundef nonnull readonly align 4 captures(none) dereferenceable(4) [[P:%.*]]) #[[ATTR1]] {
; CGSCC-NEXT: [[A:%.*]] = tail call range(i32 2, 5) i32 @test0(ptr nofree noundef nonnull readonly align 4 captures(none) dereferenceable(4) [[P]]) #[[ATTR5]]
; CGSCC-NEXT: ret i32 [[A]]
;
%a = tail call range(i32 2, 5) i32 @test0(ptr %p)
ret i32 %a
}
define i32 @test0-range-check-larger-current-range-attr(ptr %p) {
; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
; TUNIT-LABEL: define {{[^@]+}}@test0-range-check-larger-current-range-attr
; TUNIT-SAME: (ptr nofree readonly align 4 captures(none) [[P:%.*]]) #[[ATTR0]] {
; TUNIT-NEXT: [[A:%.*]] = tail call range(i32 0, 10) i32 @test0(ptr nofree noundef readonly align 4 captures(none) [[P]]) #[[ATTR3]]
; TUNIT-NEXT: ret i32 [[A]]
;
; CGSCC: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(argmem: read)
; CGSCC-LABEL: define {{[^@]+}}@test0-range-check-larger-current-range-attr
; CGSCC-SAME: (ptr nofree noundef nonnull readonly align 4 captures(none) dereferenceable(4) [[P:%.*]]) #[[ATTR1]] {
; CGSCC-NEXT: [[A:%.*]] = tail call range(i32 0, 100) i32 @test0(ptr nofree noundef nonnull readonly align 4 captures(none) dereferenceable(4) [[P]]) #[[ATTR5]]
; CGSCC-NEXT: ret i32 [[A]]
;
%a = tail call range(i32 0, 100) i32 @test0(ptr %p)
ret i32 %a
}
declare void @use3-dummy(i1, i1, i1)
define void @use3(i1, i1, i1) {
; CHECK-LABEL: define {{[^@]+}}@use3
@ -48,7 +82,7 @@ define void @test0-icmp-check(ptr %p){
; ret = [0, 10)
; TUNIT-LABEL: define {{[^@]+}}@test0-icmp-check
; TUNIT-SAME: (ptr nofree readonly align 4 captures(none) [[P:%.*]]) {
; TUNIT-NEXT: [[RET:%.*]] = tail call i32 @test0(ptr nofree noundef readonly align 4 captures(none) [[P]]) #[[ATTR3]], !range [[RNG0]]
; TUNIT-NEXT: [[RET:%.*]] = tail call range(i32 0, 10) i32 @test0(ptr nofree noundef readonly align 4 captures(none) [[P]]) #[[ATTR3]]
; TUNIT-NEXT: [[CMP_EQ_1:%.*]] = icmp eq i32 [[RET]], 10
; TUNIT-NEXT: [[CMP_EQ_2:%.*]] = icmp eq i32 [[RET]], 9
; TUNIT-NEXT: [[CMP_EQ_3:%.*]] = icmp eq i32 [[RET]], 8
@ -284,7 +318,7 @@ define i1 @test1-check(ptr %p) {
; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
; TUNIT-LABEL: define {{[^@]+}}@test1-check
; TUNIT-SAME: (ptr nofree readonly align 4 captures(none) [[P:%.*]]) #[[ATTR0]] {
; TUNIT-NEXT: [[RES:%.*]] = tail call i32 @test1(ptr nofree noundef readonly align 4 captures(none) [[P]]) #[[ATTR3]], !range [[RNG2:![0-9]+]]
; TUNIT-NEXT: [[RES:%.*]] = tail call range(i32 200, 1091) i32 @test1(ptr nofree noundef readonly align 4 captures(none) [[P]]) #[[ATTR3]]
; TUNIT-NEXT: [[CMP:%.*]] = icmp eq i32 [[RES]], 500
; TUNIT-NEXT: ret i1 [[CMP]]
;
@ -624,7 +658,7 @@ define dso_local i32 @test4-g2(i32 %u) {
; TUNIT-LABEL: define {{[^@]+}}@test4-g2
; TUNIT-SAME: (i32 [[U:%.*]]) #[[ATTR1]] {
; TUNIT-NEXT: entry:
; TUNIT-NEXT: [[CALL:%.*]] = tail call i32 @test4-f2(i32 [[U]]) #[[ATTR4]], !range [[RNG3:![0-9]+]]
; TUNIT-NEXT: [[CALL:%.*]] = tail call range(i32 1, -2147483648) i32 @test4-f2(i32 [[U]]) #[[ATTR4]]
; TUNIT-NEXT: ret i32 [[CALL]]
;
; CGSCC: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(none)
@ -1760,6 +1794,7 @@ declare void @barney(i32 signext, i32 signext)
!0 = !{i32 0, i32 10}
!1 = !{i32 10, i32 100}
!2 = !{i32 2, i32 5}
;.
; TUNIT: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }
; TUNIT: attributes #[[ATTR1]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
@ -1778,8 +1813,6 @@ declare void @barney(i32 signext, i32 signext)
;.
; TUNIT: [[RNG0]] = !{i32 0, i32 10}
; TUNIT: [[RNG1]] = !{i32 10, i32 100}
; TUNIT: [[RNG2]] = !{i32 200, i32 1091}
; TUNIT: [[RNG3]] = !{i32 1, i32 -2147483648}
;.
; CGSCC: [[RNG0]] = !{i32 0, i32 10}
; CGSCC: [[RNG1]] = !{i32 10, i32 100}

View File

@ -1114,7 +1114,7 @@ define i32 @test(i1 %c) {
; TUNIT-LABEL: define {{[^@]+}}@test
; TUNIT-SAME: (i1 [[C:%.*]]) {
; TUNIT-NEXT: [[R1:%.*]] = call i32 @ctx_test1(i1 noundef [[C]])
; TUNIT-NEXT: [[R2:%.*]] = call i32 @ctx_test2(i1 noundef [[C]]), !range [[RNG0:![0-9]+]]
; TUNIT-NEXT: [[R2:%.*]] = call range(i32 0, -2147483648) i32 @ctx_test2(i1 noundef [[C]])
; TUNIT-NEXT: [[ADD:%.*]] = add i32 [[R1]], [[R2]]
; TUNIT-NEXT: ret i32 [[ADD]]
;
@ -1689,8 +1689,6 @@ define i32 @readExtInitZeroInit() {
; TUNIT: attributes #[[ATTR15]] = { nosync nounwind memory(read) }
; TUNIT: attributes #[[ATTR16]] = { nounwind memory(write) }
;.
; TUNIT: [[RNG0]] = !{i32 0, i32 -2147483648}
;.
; CGSCC: attributes #[[ATTR0:[0-9]+]] = { nocallback nofree nosync nounwind willreturn }
; CGSCC: attributes #[[ATTR1]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
; CGSCC: attributes #[[ATTR2]] = { memory(readwrite, argmem: none) }