Skip to content

Commit 6b8ed78

Browse files
committed
[IR] Add writable attribute
This adds a writable attribute, which in conjunction with dereferenceable(N) states that a spurious store of N bytes is introduced on function entry. This implies that this many bytes are writable without trapping or introducing data races. See https://llvm.org/docs/Atomics.html#optimization-outside-atomic for why the second point is important. This attribute can be added to sret arguments. I believe Rust will also be able to use it for by-value (moved) arguments. Rust likely won't be able to use it for &mut arguments (tree borrows does not appear to allow spurious stores). In this patch the new attribute is only used by LICM scalar promotion. However, the actual motivation for this is to fix a correctness issue in call slot optimization, which needs this attribute to avoid optimization regressions. Followup to the discussion on D157499. Differential Revision: https://reviews.llvm.org/D158081
1 parent 50dec54 commit 6b8ed78

File tree

17 files changed

+210
-19
lines changed

17 files changed

+210
-19
lines changed

llvm/docs/LangRef.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,6 +1532,30 @@ Currently, only the following parameter attributes are defined:
15321532
If a function reads from a writeonly pointer argument, the behavior is
15331533
undefined.
15341534

1535+
``writable``
1536+
This attribute is only meaningful in conjunction with ``dereferenceable(N)``
1537+
or another attribute that implies the first ``N`` bytes of the pointer
1538+
argument are dereferenceable.
1539+
1540+
In that case, the attribute indicates that the first ``N`` bytes will be
1541+
(non-atomically) loaded and stored back on entry to the function.
1542+
1543+
This implies that it's possible to introduce spurious stores on entry to
1544+
the function without introducing traps or data races. This does not
1545+
necessarily hold throughout the whole function, as the pointer may escape
1546+
to a different thread during the execution of the function. See also the
1547+
:ref:`atomic optimization guide <Optimization outside atomic>`
1548+
1549+
The "other attributes" that imply dereferenceability are
1550+
``dereferenceable_or_null`` (if the pointer is non-null) and the
1551+
``sret``, ``byval``, ``byref``, ``inalloca``, ``preallocated`` family of
1552+
attributes. Note that not all of these combinations are useful, e.g.
1553+
``byval`` arguments are known to be writable even without this attribute.
1554+
1555+
The ``writable`` attribute cannot be combined with ``readnone``,
1556+
``readonly`` or a ``memory`` attribute that does not contain
1557+
``argmem: write``.
1558+
15351559
.. _gc:
15361560

15371561
Garbage Collector Strategy Names

llvm/include/llvm/Analysis/AliasAnalysis.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -875,10 +875,16 @@ bool isNotVisibleOnUnwind(const Value *Object,
875875

876876
/// Return true if the Object is writable, in the sense that any location based
877877
/// on this pointer that can be loaded can also be stored to without trapping.
878+
/// Additionally, at the point Object is declared, stores can be introduced
879+
/// without data races. At later points, this is only the case if the pointer
880+
/// can not escape to a different thread.
878881
///
879-
/// By itself, this does not imply that introducing spurious stores is safe,
880-
/// for example due to thread-safety reasons.
881-
bool isWritableObject(const Value *Object);
882+
/// If ExplicitlyDereferenceableOnly is set to true, this property only holds
883+
/// for the part of Object that is explicitly marked as dereferenceable, e.g.
884+
/// using the dereferenceable(N) attribute. It does not necessarily hold for
885+
/// parts that are only known to be dereferenceable due to the presence of
886+
/// loads.
887+
bool isWritableObject(const Value *Object, bool &ExplicitlyDereferenceableOnly);
882888

883889
/// A manager for alias analyses.
884890
///

llvm/include/llvm/Bitcode/LLVMBitCodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,7 @@ enum AttributeKindCodes {
717717
ATTR_KIND_MEMORY = 86,
718718
ATTR_KIND_NOFPCLASS = 87,
719719
ATTR_KIND_OPTIMIZE_FOR_DEBUGGING = 88,
720+
ATTR_KIND_WRITABLE = 89,
720721
};
721722

722723
enum ComdatSelectionKindCodes {

llvm/include/llvm/IR/Attributes.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@ def VScaleRange : IntAttr<"vscale_range", [FnAttr]>;
303303
/// Function always comes back to callsite.
304304
def WillReturn : EnumAttr<"willreturn", [FnAttr]>;
305305

306+
/// Pointer argument is writable.
307+
def Writable : EnumAttr<"writable", [ParamAttr]>;
308+
306309
/// Function only writes to memory.
307310
def WriteOnly : EnumAttr<"writeonly", [ParamAttr]>;
308311

llvm/lib/Analysis/AliasAnalysis.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -911,15 +911,23 @@ bool llvm::isNotVisibleOnUnwind(const Value *Object,
911911

912912
// We don't consider globals as writable: While the physical memory is writable,
913913
// we may not have provenance to perform the write.
914-
bool llvm::isWritableObject(const Value *Object) {
914+
bool llvm::isWritableObject(const Value *Object,
915+
bool &ExplicitlyDereferenceableOnly) {
916+
ExplicitlyDereferenceableOnly = false;
917+
915918
// TODO: Alloca might not be writable after its lifetime ends.
916919
// See https://github.com/llvm/llvm-project/issues/51838.
917920
if (isa<AllocaInst>(Object))
918921
return true;
919922

920-
// TODO: Also handle sret.
921-
if (auto *A = dyn_cast<Argument>(Object))
923+
if (auto *A = dyn_cast<Argument>(Object)) {
924+
if (A->hasAttribute(Attribute::Writable)) {
925+
ExplicitlyDereferenceableOnly = true;
926+
return true;
927+
}
928+
922929
return A->hasByValAttr();
930+
}
923931

924932
// TODO: Noalias shouldn't imply writability, this should check for an
925933
// allocator function instead.

llvm/lib/Bitcode/Reader/BitcodeReader.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
20582058
return Attribute::Hot;
20592059
case bitc::ATTR_KIND_PRESPLIT_COROUTINE:
20602060
return Attribute::PresplitCoroutine;
2061+
case bitc::ATTR_KIND_WRITABLE:
2062+
return Attribute::Writable;
20612063
}
20622064
}
20632065

llvm/lib/Bitcode/Writer/BitcodeWriter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
824824
return bitc::ATTR_KIND_MUSTPROGRESS;
825825
case Attribute::PresplitCoroutine:
826826
return bitc::ATTR_KIND_PRESPLIT_COROUTINE;
827+
case Attribute::Writable:
828+
return bitc::ATTR_KIND_WRITABLE;
827829
case Attribute::EndAttrKinds:
828830
llvm_unreachable("Can not encode end-attribute kinds marker.");
829831
case Attribute::None:

llvm/lib/IR/Attributes.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1961,7 +1961,8 @@ AttributeMask AttributeFuncs::typeIncompatible(Type *Ty,
19611961
.addAttribute(Attribute::ReadNone)
19621962
.addAttribute(Attribute::ReadOnly)
19631963
.addAttribute(Attribute::Dereferenceable)
1964-
.addAttribute(Attribute::DereferenceableOrNull);
1964+
.addAttribute(Attribute::DereferenceableOrNull)
1965+
.addAttribute(Attribute::Writable);
19651966
if (ASK & ASK_UNSAFE_TO_DROP)
19661967
Incompatible.addAttribute(Attribute::Nest)
19671968
.addAttribute(Attribute::SwiftError)

llvm/lib/IR/Verifier.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,6 +1927,14 @@ void Verifier::verifyParameterAttrs(AttributeSet Attrs, Type *Ty,
19271927
"'noinline and alwaysinline' are incompatible!",
19281928
V);
19291929

1930+
Check(!(Attrs.hasAttribute(Attribute::Writable) &&
1931+
Attrs.hasAttribute(Attribute::ReadNone)),
1932+
"Attributes writable and readnone are incompatible!", V);
1933+
1934+
Check(!(Attrs.hasAttribute(Attribute::Writable) &&
1935+
Attrs.hasAttribute(Attribute::ReadOnly)),
1936+
"Attributes writable and readonly are incompatible!", V);
1937+
19301938
AttributeMask IncompatibleAttrs = AttributeFuncs::typeIncompatible(Ty);
19311939
for (Attribute Attr : Attrs) {
19321940
if (!Attr.isStringAttribute() &&
@@ -2131,6 +2139,11 @@ void Verifier::verifyFunctionAttrs(FunctionType *FT, AttributeList Attrs,
21312139
"Attributes 'minsize and optdebug' are incompatible!", V);
21322140
}
21332141

2142+
Check(!Attrs.hasAttrSomewhere(Attribute::Writable) ||
2143+
isModSet(Attrs.getMemoryEffects().getModRef(IRMemLocation::ArgMem)),
2144+
"Attribute writable and memory without argmem: write are incompatible!",
2145+
V);
2146+
21342147
if (Attrs.hasFnAttr("aarch64_pstate_sm_enabled")) {
21352148
Check(!Attrs.hasFnAttr("aarch64_pstate_sm_compatible"),
21362149
"Attributes 'aarch64_pstate_sm_enabled and "

llvm/lib/Transforms/IPO/AttributorAttributes.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7845,6 +7845,9 @@ struct AAMemoryBehaviorImpl : public AAMemoryBehavior {
78457845

78467846
// Clear existing attributes.
78477847
A.removeAttrs(IRP, AttrKinds);
7848+
// Clear conflicting writable attribute.
7849+
if (isAssumedReadOnly())
7850+
A.removeAttrs(IRP, Attribute::Writable);
78487851

78497852
// Use the generic manifest method.
78507853
return IRAttribute::manifest(A);
@@ -8032,6 +8035,10 @@ struct AAMemoryBehaviorFunction final : public AAMemoryBehaviorImpl {
80328035
ME = MemoryEffects::writeOnly();
80338036

80348037
A.removeAttrs(getIRPosition(), AttrKinds);
8038+
// Clear conflicting writable attribute.
8039+
if (ME.onlyReadsMemory())
8040+
for (Argument &Arg : F.args())
8041+
A.removeAttrs(IRPosition::argument(Arg), Attribute::Writable);
80358042
return A.manifestAttrs(getIRPosition(),
80368043
Attribute::getWithMemoryEffects(F.getContext(), ME));
80378044
}
@@ -8066,6 +8073,11 @@ struct AAMemoryBehaviorCallSite final
80668073
ME = MemoryEffects::writeOnly();
80678074

80688075
A.removeAttrs(getIRPosition(), AttrKinds);
8076+
// Clear conflicting writable attribute.
8077+
if (ME.onlyReadsMemory())
8078+
for (Use &U : CB.args())
8079+
A.removeAttrs(IRPosition::callsite_argument(CB, U.getOperandNo()),
8080+
Attribute::Writable);
80698081
return A.manifestAttrs(
80708082
getIRPosition(), Attribute::getWithMemoryEffects(CB.getContext(), ME));
80718083
}

llvm/lib/Transforms/IPO/FunctionAttrs.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ static void addMemoryAttrs(const SCCNodeSet &SCCNodes, AARGetterT &&AARGetter,
285285
if (NewME != OldME) {
286286
++NumMemoryAttr;
287287
F->setMemoryEffects(NewME);
288+
// Remove conflicting writable attributes.
289+
if (!isModSet(NewME.getModRef(IRMemLocation::ArgMem)))
290+
for (Argument &A : F->args())
291+
A.removeAttr(Attribute::Writable);
288292
Changed.insert(F);
289293
}
290294
}
@@ -848,6 +852,9 @@ static bool addAccessAttr(Argument *A, Attribute::AttrKind R) {
848852
A->removeAttr(Attribute::WriteOnly);
849853
A->removeAttr(Attribute::ReadOnly);
850854
A->removeAttr(Attribute::ReadNone);
855+
// Remove conflicting writable attribute.
856+
if (R == Attribute::ReadNone || R == Attribute::ReadOnly)
857+
A->removeAttr(Attribute::Writable);
851858
A->addAttr(R);
852859
if (R == Attribute::ReadOnly)
853860
++NumReadOnlyArg;

llvm/lib/Transforms/Scalar/LICM.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2171,7 +2171,10 @@ bool llvm::promoteLoopAccessesToScalars(
21712171
// violating the memory model.
21722172
if (StoreSafety == StoreSafetyUnknown) {
21732173
Value *Object = getUnderlyingObject(SomePtr);
2174-
if (isWritableObject(Object) &&
2174+
bool ExplicitlyDereferenceableOnly;
2175+
if (isWritableObject(Object, ExplicitlyDereferenceableOnly) &&
2176+
(!ExplicitlyDereferenceableOnly ||
2177+
isDereferenceablePointer(SomePtr, AccessTy, MDL)) &&
21752178
isThreadLocalObject(Object, CurLoop, DT, TTI))
21762179
StoreSafety = StoreSafe;
21772180
}

llvm/lib/Transforms/Utils/CodeExtractor.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs,
994994
case Attribute::ImmArg:
995995
case Attribute::ByRef:
996996
case Attribute::WriteOnly:
997+
case Attribute::Writable:
997998
// These are not really attributes.
998999
case Attribute::None:
9991000
case Attribute::EndAttrKinds:

llvm/test/Bitcode/attributes.ll

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,12 +511,16 @@ define void @f87() fn_ret_thunk_extern { ret void }
511511
; CHECK: define void @f88() [[SKIPPROFILE:#[0-9]+]]
512512
define void @f88() skipprofile { ret void }
513513

514-
define void @f89() optdebug
515514
; CHECK: define void @f89() [[OPTDEBUG:#[0-9]+]]
516-
{
515+
define void @f89() optdebug {
517516
ret void;
518517
}
519518

519+
; CHECK: define void @f90(ptr writable %p)
520+
define void @f90(ptr writable %p) {
521+
ret void
522+
}
523+
520524
; CHECK: attributes #0 = { noreturn }
521525
; CHECK: attributes #1 = { nounwind }
522526
; CHECK: attributes #2 = { memory(none) }

llvm/test/Transforms/FunctionAttrs/readattrs.ll

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,15 +230,15 @@ define void @test8_2(ptr %p) {
230230
; ATTRIBUTOR-LABEL: define {{[^@]+}}@test8_2
231231
; ATTRIBUTOR-SAME: (ptr nocapture nofree writeonly [[P:%.*]]) #[[ATTR0]] {
232232
; ATTRIBUTOR-NEXT: entry:
233-
; ATTRIBUTOR-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR13:[0-9]+]]
233+
; ATTRIBUTOR-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR14:[0-9]+]]
234234
; ATTRIBUTOR-NEXT: store i32 10, ptr [[CALL]], align 4
235235
; ATTRIBUTOR-NEXT: ret void
236236
;
237237
; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(write)
238238
; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@test8_2
239239
; ATTRIBUTOR-CGSCC-SAME: (ptr nofree writeonly [[P:%.*]]) #[[ATTR5:[0-9]+]] {
240240
; ATTRIBUTOR-CGSCC-NEXT: entry:
241-
; ATTRIBUTOR-CGSCC-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR13:[0-9]+]]
241+
; ATTRIBUTOR-CGSCC-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR14:[0-9]+]]
242242
; ATTRIBUTOR-CGSCC-NEXT: store i32 10, ptr [[CALL]], align 4
243243
; ATTRIBUTOR-CGSCC-NEXT: ret void
244244
;
@@ -260,13 +260,13 @@ define void @test9(<4 x ptr> %ptrs, <4 x i32>%val) {
260260
; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(write)
261261
; ATTRIBUTOR-LABEL: define {{[^@]+}}@test9
262262
; ATTRIBUTOR-SAME: (<4 x ptr> [[PTRS:%.*]], <4 x i32> [[VAL:%.*]]) #[[ATTR0]] {
263-
; ATTRIBUTOR-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> <i1 true, i1 false, i1 true, i1 false>) #[[ATTR14:[0-9]+]]
263+
; ATTRIBUTOR-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> <i1 true, i1 false, i1 true, i1 false>) #[[ATTR15:[0-9]+]]
264264
; ATTRIBUTOR-NEXT: ret void
265265
;
266266
; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(write)
267267
; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@test9
268268
; ATTRIBUTOR-CGSCC-SAME: (<4 x ptr> [[PTRS:%.*]], <4 x i32> [[VAL:%.*]]) #[[ATTR0]] {
269-
; ATTRIBUTOR-CGSCC-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> <i1 true, i1 false, i1 true, i1 false>) #[[ATTR14:[0-9]+]]
269+
; ATTRIBUTOR-CGSCC-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> <i1 true, i1 false, i1 true, i1 false>) #[[ATTR15:[0-9]+]]
270270
; ATTRIBUTOR-CGSCC-NEXT: ret void
271271
;
272272
call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32>%val, <4 x ptr> %ptrs, i32 4, <4 x i1><i1 true, i1 false, i1 true, i1 false>)
@@ -284,13 +284,13 @@ define <4 x i32> @test10(<4 x ptr> %ptrs) {
284284
; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(read)
285285
; ATTRIBUTOR-LABEL: define {{[^@]+}}@test10
286286
; ATTRIBUTOR-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR7:[0-9]+]] {
287-
; ATTRIBUTOR-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, <4 x i32> undef) #[[ATTR15:[0-9]+]]
287+
; ATTRIBUTOR-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, <4 x i32> undef) #[[ATTR16:[0-9]+]]
288288
; ATTRIBUTOR-NEXT: ret <4 x i32> [[RES]]
289289
;
290290
; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(read)
291291
; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@test10
292292
; ATTRIBUTOR-CGSCC-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR8:[0-9]+]] {
293-
; ATTRIBUTOR-CGSCC-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, <4 x i32> undef) #[[ATTR15:[0-9]+]]
293+
; ATTRIBUTOR-CGSCC-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> <i1 true, i1 false, i1 true, i1 false>, <4 x i32> undef) #[[ATTR16:[0-9]+]]
294294
; ATTRIBUTOR-CGSCC-NEXT: ret <4 x i32> [[RES]]
295295
;
296296
%res = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> %ptrs, i32 4, <4 x i1><i1 true, i1 false, i1 true, i1 false>, <4 x i32>undef)
@@ -719,5 +719,48 @@ define void @op_bundle_readonly_unknown(ptr %p) {
719719
call void @readonly_param(ptr %p) ["unknown"()]
720720
ret void
721721
}
722+
723+
define i32 @writable_readonly(ptr writable dereferenceable(4) %p) {
724+
; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
725+
; FNATTRS-LABEL: define {{[^@]+}}@writable_readonly
726+
; FNATTRS-SAME: (ptr nocapture readonly dereferenceable(4) [[P:%.*]]) #[[ATTR15:[0-9]+]] {
727+
; FNATTRS-NEXT: [[V:%.*]] = load i32, ptr [[P]], align 4
728+
; FNATTRS-NEXT: ret i32 [[V]]
729+
;
730+
; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
731+
; ATTRIBUTOR-LABEL: define {{[^@]+}}@writable_readonly
732+
; ATTRIBUTOR-SAME: (ptr nocapture nofree nonnull readonly dereferenceable(4) [[P:%.*]]) #[[ATTR13:[0-9]+]] {
733+
; ATTRIBUTOR-NEXT: [[V:%.*]] = load i32, ptr [[P]], align 4
734+
; ATTRIBUTOR-NEXT: ret i32 [[V]]
735+
;
736+
; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
737+
; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@writable_readonly
738+
; ATTRIBUTOR-CGSCC-SAME: (ptr nocapture nofree nonnull readonly dereferenceable(4) [[P:%.*]]) #[[ATTR13:[0-9]+]] {
739+
; ATTRIBUTOR-CGSCC-NEXT: [[V:%.*]] = load i32, ptr [[P]], align 4
740+
; ATTRIBUTOR-CGSCC-NEXT: ret i32 [[V]]
741+
;
742+
%v = load i32, ptr %p
743+
ret i32 %v
744+
}
745+
746+
define void @writable_readnone(ptr writable dereferenceable(4) %p) {
747+
; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
748+
; FNATTRS-LABEL: define {{[^@]+}}@writable_readnone
749+
; FNATTRS-SAME: (ptr nocapture readnone dereferenceable(4) [[P:%.*]]) #[[ATTR1]] {
750+
; FNATTRS-NEXT: ret void
751+
;
752+
; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
753+
; ATTRIBUTOR-LABEL: define {{[^@]+}}@writable_readnone
754+
; ATTRIBUTOR-SAME: (ptr nocapture nofree nonnull readnone dereferenceable(4) [[P:%.*]]) #[[ATTR1]] {
755+
; ATTRIBUTOR-NEXT: ret void
756+
;
757+
; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
758+
; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@writable_readnone
759+
; ATTRIBUTOR-CGSCC-SAME: (ptr nocapture nofree nonnull readnone dereferenceable(4) [[P:%.*]]) #[[ATTR1]] {
760+
; ATTRIBUTOR-CGSCC-NEXT: ret void
761+
;
762+
ret void
763+
}
764+
722765
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
723766
; COMMON: {{.*}}

llvm/test/Transforms/LICM/scalar-promote.ll

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -885,9 +885,40 @@ exit:
885885
ret void
886886
}
887887

888-
; TODO: The store can be promoted, as sret memory is writable.
889-
define void @sret_cond_store(ptr sret(i32) noalias %ptr) {
890-
; CHECK-LABEL: @sret_cond_store(
888+
define void @cond_store_writable_dereferenceable(ptr noalias writable dereferenceable(4) %ptr) {
889+
; CHECK-LABEL: @cond_store_writable_dereferenceable(
890+
; CHECK-NEXT: [[PTR_PROMOTED:%.*]] = load i32, ptr [[PTR:%.*]], align 4
891+
; CHECK-NEXT: br label [[LOOP:%.*]]
892+
; CHECK: loop:
893+
; CHECK-NEXT: [[V_INC1:%.*]] = phi i32 [ [[V_INC:%.*]], [[LOOP_LATCH:%.*]] ], [ [[PTR_PROMOTED]], [[TMP0:%.*]] ]
894+
; CHECK-NEXT: [[C:%.*]] = icmp ult i32 [[V_INC1]], 10
895+
; CHECK-NEXT: br i1 [[C]], label [[LOOP_LATCH]], label [[EXIT:%.*]]
896+
; CHECK: loop.latch:
897+
; CHECK-NEXT: [[V_INC]] = add i32 [[V_INC1]], 1
898+
; CHECK-NEXT: br label [[LOOP]]
899+
; CHECK: exit:
900+
; CHECK-NEXT: [[V_INC1_LCSSA:%.*]] = phi i32 [ [[V_INC1]], [[LOOP]] ]
901+
; CHECK-NEXT: store i32 [[V_INC1_LCSSA]], ptr [[PTR]], align 4
902+
; CHECK-NEXT: ret void
903+
;
904+
br label %loop
905+
906+
loop:
907+
%v = load i32, ptr %ptr
908+
%c = icmp ult i32 %v, 10
909+
br i1 %c, label %loop.latch, label %exit
910+
911+
loop.latch:
912+
%v.inc = add i32 %v, 1
913+
store i32 %v.inc, ptr %ptr
914+
br label %loop
915+
916+
exit:
917+
ret void
918+
}
919+
920+
define void @cond_store_writable_not_sufficiently_dereferenceable(ptr noalias writable dereferenceable(2) %ptr) {
921+
; CHECK-LABEL: @cond_store_writable_not_sufficiently_dereferenceable(
891922
; CHECK-NEXT: [[PTR_PROMOTED:%.*]] = load i32, ptr [[PTR:%.*]], align 4
892923
; CHECK-NEXT: br label [[LOOP:%.*]]
893924
; CHECK: loop:

0 commit comments

Comments
 (0)