[llvm] [IR] Add writerange attribute conveying a pointer argument's write range in a function (PR #227191)
Kunal Chadha via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 28 22:49:33 PDT 2026
https://github.com/chadhakunal created https://github.com/llvm/llvm-project/pull/227191
Adds a new pointer parameter attribute, `writerange((Lo,Hi), ...)`, that bounds the byte ranges a function may write to. This lets `getModRefInfo` prove `NoModRef` for a location a call provably never touches, letting GVN eliminate a now-redundant load.
Fixes #221792
>From 61daeb9c8e2d0aa114220e4a06a3674026cc5834 Mon Sep 17 00:00:00 2001
From: Kunal Chadha <kunachad at qti.qualcomm.com>
Date: Mon, 28 Sep 2026 22:45:00 -0700
Subject: [PATCH] [IR] Add writerange attribute conveying a pointer argument's
write range in a function
---
llvm/include/llvm/AsmParser/LLParser.h | 1 +
llvm/include/llvm/Bitcode/LLVMBitCodes.h | 1 +
llvm/include/llvm/IR/Attributes.h | 6 +++
llvm/include/llvm/IR/Attributes.td | 3 ++
llvm/lib/Analysis/BasicAliasAnalysis.cpp | 45 +++++++++++++++++++-
llvm/lib/AsmParser/LLParser.cpp | 47 +++++++++++++++++++++
llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 2 +
llvm/lib/Bitcode/Writer/BitcodeWriter.cpp | 2 +
llvm/lib/IR/Attributes.cpp | 20 +++++++++
llvm/lib/IR/Verifier.cpp | 10 +++++
llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 35 +++++++++++++++
llvm/lib/Transforms/Utils/CodeExtractor.cpp | 1 +
llvm/test/Transforms/GVN/writerange.ll | 34 +++++++++++++++
13 files changed, 205 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/Transforms/GVN/writerange.ll
diff --git a/llvm/include/llvm/AsmParser/LLParser.h b/llvm/include/llvm/AsmParser/LLParser.h
index 70b98dae850fd2..b0fd3a26c1157a 100644
--- a/llvm/include/llvm/AsmParser/LLParser.h
+++ b/llvm/include/llvm/AsmParser/LLParser.h
@@ -414,6 +414,7 @@ namespace llvm {
bool inAttrGrp, LocTy &BuiltinLoc);
bool parseRangeAttr(AttrBuilder &B);
bool parseInitializesAttr(AttrBuilder &B);
+ bool parseWriteRangeAttr(AttrBuilder &B);
bool parseCapturesAttr(AttrBuilder &B);
bool parseRequiredTypeAttr(AttrBuilder &B, lltok::Kind AttrToken,
Attribute::AttrKind AttrKind);
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index d9ae1d0c195a63..f411cd96f6361c 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -833,6 +833,7 @@ enum AttributeKindCodes {
ATTR_KIND_FLATTEN = 108,
ATTR_KIND_NOIPA = 109,
ATTR_KIND_NOFREEOBJ = 110,
+ ATTR_KIND_WRITE_RANGE = 111,
};
enum ComdatSelectionKindCodes {
diff --git a/llvm/include/llvm/IR/Attributes.h b/llvm/include/llvm/IR/Attributes.h
index 7ff7def7525fa2..b704bf7fb7d01b 100644
--- a/llvm/include/llvm/IR/Attributes.h
+++ b/llvm/include/llvm/IR/Attributes.h
@@ -363,6 +363,9 @@ class Attribute {
/// Returns the value of the initializes attribute.
LLVM_ABI ArrayRef<ConstantRange> getInitializes() const;
+ /// Returns the value of the writerange attribute.
+ LLVM_ABI ArrayRef<ConstantRange> getWriteRange() const;
+
/// The Attribute is converted to a string of equivalent mnemonic. This
/// is, presumably, for writing out the mnemonics for the assembly writer.
LLVM_ABI std::string getAsString(bool InAttrGrp = false) const;
@@ -1370,6 +1373,9 @@ class AttrBuilder {
/// Add initializes attribute.
LLVM_ABI AttrBuilder &addInitializesAttr(const ConstantRangeList &CRL);
+ /// Add writerange attribute.
+ LLVM_ABI AttrBuilder &addWriteRangeAttr(const ConstantRangeList &CRL);
+
/// Add 0 or more parameter attributes which are equivalent to metadata
/// attached to \p I. e.g. !align -> align. This assumes the argument type is
/// the same as the original instruction and the attribute is compatible.
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index c43b499061ddc7..937f1677a60cfa 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -151,6 +151,9 @@ def InAlloca : TypeAttr<"inalloca", IntersectPreserve, [ParamAttr, ABIAttr]>;
/// Pointer argument memory is initialized.
def Initializes : ConstantRangeListAttr<"initializes", IntersectPreserve, [ParamAttr]>;
+/// Pointer argument memory is written to.
+def WriteRange : ConstantRangeListAttr<"writerange", IntersectPreserve, [ParamAttr]>;
+
/// Source said inlining was desirable.
def InlineHint : EnumAttr<"inlinehint", IntersectAnd, [FnAttr]>;
diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 3aece3ca7d7695..91a419c235406a 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -942,6 +942,38 @@ AliasResult BasicAAResult::alias(const MemoryLocation &LocA,
return aliasCheck(LocA.Ptr, LocA.Size, LocB.Ptr, LocB.Size, AAQI, CtxI);
}
+static bool ArgWriteRangeDisjointFromLoc(const CallBase *Call,
+ unsigned ArgIdx,
+ const Value *Arg,
+ const MemoryLocation &Loc,
+ const DataLayout &DL) {
+ if (!Call->paramHasAttr(ArgIdx, Attribute::WriteRange))
+ return false;
+ if (!Loc.Size.hasValue() || !Loc.Size.isPrecise() ||
+ Loc.Size.getValue().isScalable())
+ return false;
+
+ unsigned IndexWidth = DL.getIndexTypeSizeInBits(Arg->getType());
+ APInt LocOffset(IndexWidth, 0);
+ const Value *LocBase = Loc.Ptr->stripAndAccumulateConstantOffsets(
+ DL, LocOffset, /*AllowNonInbounds=*/true);
+ if (LocBase != Arg)
+ return false;
+
+ int64_t Lo = LocOffset.getSExtValue();
+ int64_t Hi = Lo + static_cast<int64_t>(Loc.Size.getValue().getFixedValue());
+
+ ArrayRef<ConstantRange> Ranges =
+ Call->getParamAttr(ArgIdx, Attribute::WriteRange).getWriteRange();
+ for (const ConstantRange &CR : Ranges) {
+ int64_t RLo = CR.getLower().getSExtValue();
+ int64_t RHi = CR.getUpper().getSExtValue();
+ if (Lo < RHi && RLo < Hi)
+ return false; // Overlaps; cannot rule out.
+ }
+ return true; // Disjoint from every declared write range.
+}
+
/// Checks to see if the specified callsite can clobber the specified memory
/// object.
///
@@ -1031,8 +1063,16 @@ ModRefInfo BasicAAResult::getModRefInfo(const CallBase *Call,
? MemoryLocation::getForArgument(Call, ArgIdx, TLI)
: MemoryLocation::getBeforeOrAfter(Arg);
AliasResult ArgAlias = AAQI.AAR.alias(ArgLoc, Loc, AAQI, Call);
- if (ArgAlias != AliasResult::NoAlias)
- NewArgMR |= ArgMR & AAQI.AAR.getArgModRefInfo(Call, ArgIdx);
+
+ if (ArgAlias != AliasResult::NoAlias) {
+ ModRefInfo PerArgMR = ArgMR & AAQI.AAR.getArgModRefInfo(Call, ArgIdx);
+
+ if (Call->isArgOperand(&U) && isModSet(PerArgMR) &&
+ ArgWriteRangeDisjointFromLoc(Call, ArgIdx, Arg, Loc, DL))
+ PerArgMR &= ~ModRefInfo::Mod;
+
+ NewArgMR |= PerArgMR;
+ }
// Exit early if we cannot improve over the original ArgMR.
if (NewArgMR == ArgMR)
@@ -1551,6 +1591,7 @@ AliasResult BasicAAResult::aliasCheck(const Value *V1, LocationSize V1Size,
const Instruction *CtxI) {
// If either of the memory references is empty, it doesn't matter what the
// pointer values are.
+
if (V1Size.isZero() || V2Size.isZero())
return AliasResult::NoAlias;
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index aaee06685fc2ad..528b7cbe98aa8a 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -1806,6 +1806,8 @@ bool LLParser::parseEnumAttribute(Attribute::AttrKind Attr, AttrBuilder &B,
return parseRangeAttr(B);
case Attribute::Initializes:
return parseInitializesAttr(B);
+ case Attribute::WriteRange:
+ return parseWriteRangeAttr(B);
case Attribute::Captures:
return parseCapturesAttr(B);
default:
@@ -3541,6 +3543,51 @@ bool LLParser::parseInitializesAttr(AttrBuilder &B) {
return false;
}
+/// parseWriteRangeAttr
+/// ::= writerange((Lo1,Hi1),(Lo2,Hi2),...)
+bool LLParser::parseWriteRangeAttr(AttrBuilder &B) {
+ Lex.Lex();
+
+ auto ParseAPSInt = [&](APInt &Val) {
+ if (Lex.getKind() != lltok::APSInt)
+ return tokError("expected integer");
+ Val = Lex.getAPSIntVal().extend(64);
+ Lex.Lex();
+ return false;
+ };
+
+ if (parseToken(lltok::lparen, "expected '('"))
+ return true;
+
+ SmallVector<ConstantRange, 2> RangeList;
+ do {
+ APInt Lower, Upper;
+ if (parseToken(lltok::lparen, "expected '('"))
+ return true;
+
+ if (ParseAPSInt(Lower) || parseToken(lltok::comma, "expected ','") ||
+ ParseAPSInt(Upper))
+ return true;
+
+ if (Lower == Upper)
+ return tokError("the range should not represent the full or empty set!");
+
+ if (parseToken(lltok::rparen, "expected ')'"))
+ return true;
+
+ RangeList.push_back(ConstantRange(Lower, Upper));
+ } while (EatIfPresent(lltok::comma));
+
+ if (parseToken(lltok::rparen, "expected ')'"))
+ return true;
+
+ auto CRLOrNull = ConstantRangeList::getConstantRangeList(RangeList);
+ if (!CRLOrNull.has_value())
+ return tokError("Invalid (unordered or overlapping) range list");
+ B.addWriteRangeAttr(*CRLOrNull);
+ return false;
+}
+
bool LLParser::parseCapturesAttr(AttrBuilder &B) {
CaptureComponents Other = CaptureComponents::None;
std::optional<CaptureComponents> Ret;
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index 4b338ae6237ecf..115ff2870f4edd 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -2322,6 +2322,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
return Attribute::Range;
case bitc::ATTR_KIND_INITIALIZES:
return Attribute::Initializes;
+ case bitc::ATTR_KIND_WRITE_RANGE:
+ return Attribute::WriteRange;
case bitc::ATTR_KIND_CORO_ELIDE_SAFE:
return Attribute::CoroElideSafe;
case bitc::ATTR_KIND_NO_EXT:
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 34e62d2d8f7d0e..6365fc9490e74e 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -1004,6 +1004,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
return bitc::ATTR_KIND_RANGE;
case Attribute::Initializes:
return bitc::ATTR_KIND_INITIALIZES;
+ case Attribute::WriteRange:
+ return bitc::ATTR_KIND_WRITE_RANGE;
case Attribute::NoExt:
return bitc::ATTR_KIND_NO_EXT;
case Attribute::Captures:
diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index 7f5f5f34af3899..f0ede4d02feda8 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -522,6 +522,12 @@ ArrayRef<ConstantRange> Attribute::getInitializes() const {
return pImpl->getValueAsConstantRangeList();
}
+ArrayRef<ConstantRange> Attribute::getWriteRange() const {
+ assert(hasAttribute(Attribute::WriteRange) &&
+ "Trying to get writerange attr from non-ConstantRangeList attribute");
+ return pImpl->getValueAsConstantRangeList();
+}
+
static const char *getModRefStr(ModRefInfo MR) {
switch (MR) {
case ModRefInfo::NoModRef:
@@ -736,6 +742,16 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
return Result;
}
+ if (hasAttribute(Attribute::WriteRange)) {
+ std::string Result;
+ raw_string_ostream OS(Result);
+ ConstantRangeList CRL = getWriteRange();
+ OS << "writerange(";
+ CRL.print(OS);
+ OS << ")";
+ return Result;
+ }
+
// Convert target-dependent attributes to strings of the form:
//
// "kind"
@@ -2357,6 +2373,10 @@ AttrBuilder &AttrBuilder::addInitializesAttr(const ConstantRangeList &CRL) {
return addConstantRangeListAttr(Attribute::Initializes, CRL.rangesRef());
}
+AttrBuilder &AttrBuilder::addWriteRangeAttr(const ConstantRangeList &CRL) {
+ return addConstantRangeListAttr(Attribute::WriteRange, CRL.rangesRef());
+}
+
AttrBuilder &AttrBuilder::addFromEquivalentMetadata(const Instruction &I) {
if (I.hasMetadata(LLVMContext::MD_nonnull))
addAttribute(Attribute::NonNull);
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 2502d574dd9928..11f999fe485728 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -2392,6 +2392,16 @@ void Verifier::verifyParameterAttrs(AttributeSet Attrs, Type *Ty,
"Attribute 'initializes' does not support unordered ranges", V);
}
+ if (Attrs.hasAttribute(Attribute::WriteRange)) {
+ auto Ranges = Attrs.getAttribute(Attribute::WriteRange).getWriteRange();
+ Check(!Ranges.empty(), "Attribute 'writerange' does not support empty list",
+ V);
+ Check(ConstantRangeList::isOrderedRanges(Ranges),
+ "Attribute 'writerange' does not support unordered ranges", V);
+ Check(!Attrs.hasAttribute(Attribute::ReadNone),
+ "Attribute 'writerange' is incompatible with 'readnone'", V);
+ }
+
if (Attrs.hasAttribute(Attribute::NoFPClass)) {
uint64_t Val = Attrs.getAttribute(Attribute::NoFPClass).getValueAsInt();
Check(Val != 0, "Attribute 'nofpclass' must have at least one test bit set",
diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index a713ead683476e..33b4ec901713a6 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -1115,6 +1115,35 @@ static bool addAccessAttrs(Argument *A, ArgAccessProperties Props) {
return true;
}
+static bool inferWriteRange(Argument &A, Function &F) {
+ auto ArgumentUses = collectArgumentUsesPerBlock(A, F);
+
+ ConstantRangeList WriteCRL;
+ for (auto &BBInfo : llvm::make_second_range(ArgumentUses.UsesPerBlock)) {
+ for (auto &Info : llvm::make_second_range(BBInfo.Insts)) {
+ if (Info.ArgAccessType == ArgumentAccessInfo::AccessType::Write)
+ WriteCRL = WriteCRL.unionWith(Info.AccessRanges);
+ else if (Info.ArgAccessType != ArgumentAccessInfo::AccessType::Read)
+ return false; // Unknown or WriteWithSideEffect: can't bound this arg.
+ }
+ }
+
+ if (WriteCRL.empty())
+ return false;
+
+ if (A.hasAttribute(Attribute::WriteRange)) {
+ ConstantRangeList PreviousCRL(
+ A.getAttribute(Attribute::WriteRange).getWriteRange());
+ if (PreviousCRL == WriteCRL)
+ return false;
+ WriteCRL = WriteCRL.unionWith(PreviousCRL);
+ }
+
+ A.addAttr(Attribute::get(A.getContext(), Attribute::WriteRange,
+ WriteCRL.rangesRef()));
+ return true;
+}
+
static bool inferInitializes(Argument &A, Function &F) {
auto ArgumentUses = collectArgumentUsesPerBlock(A, F);
// No write anywhere in the function, bail.
@@ -1311,6 +1340,12 @@ static void addArgumentAttrs(const SCCNodeSet &SCCNodes,
if (DetermineAccessAttrsForSingleton(&A))
Changed.insert(F);
}
+
+ if (!A.onlyReadsMemory()) {
+ if(inferWriteRange(A, *F))
+ Changed.insert(F);
+ }
+
if (!SkipInitializes && !A.onlyReadsMemory()) {
if (inferInitializes(A, *F))
Changed.insert(F);
diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
index c18cca1b295830..14da9d10ec66cd 100644
--- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -1055,6 +1055,7 @@ Function *CodeExtractor::constructFunctionDeclaration(
case Attribute::ImmArg:
case Attribute::ByRef:
case Attribute::WriteOnly:
+ case Attribute::WriteRange:
case Attribute::Writable:
case Attribute::DeadOnUnwind:
case Attribute::Range:
diff --git a/llvm/test/Transforms/GVN/writerange.ll b/llvm/test/Transforms/GVN/writerange.ll
new file mode 100644
index 00000000000000..2d320553278a6c
--- /dev/null
+++ b/llvm/test/Transforms/GVN/writerange.ll
@@ -0,0 +1,34 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes='function-attrs,gvn,instcombine' -S < %s | FileCheck %s
+
+%struct.V = type { i64, i64 }
+
+define internal fastcc void @makeSize(ptr noundef %v) noinline {
+; CHECK-LABEL: define internal fastcc void @makeSize(
+; CHECK-SAME: ptr nofree noundef writeonly captures(none) initializes((8, 16)) writerange((8, 16)) [[V:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[CAPACITY:%.*]] = getelementptr inbounds nuw i8, ptr [[V]], i64 8
+; CHECK-NEXT: store i64 16, ptr [[CAPACITY]], align 8
+; CHECK-NEXT: ret void
+;
+entry:
+ %capacity = getelementptr inbounds nuw i8, ptr %v, i64 8
+ store i64 16, ptr %capacity, align 8
+ ret void
+}
+
+define i64 @test() {
+; CHECK-LABEL: define i64 @test(
+; CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: ret i64 1
+;
+entry:
+ %v = alloca %struct.V, align 8
+ store i64 0, ptr %v, align 8
+ call fastcc void @makeSize(ptr noundef %v)
+ %0 = load i64, ptr %v, align 8
+ %cmp = icmp eq i64 %0, 0
+ %conv = select i1 %cmp, i64 1, i64 2
+ ret i64 %conv
+}
More information about the llvm-commits
mailing list