diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1532,6 +1532,30 @@ If a function reads from a writeonly pointer argument, the behavior is undefined. +``writable`` + This attribute is only meaningful in conjunction with ``dereferenceable(N)`` + or another attribute that implies the first ``N`` bytes of the pointer + argument are dereferenceable. + + In that case, the attribute indicates that the first ``N`` bytes will be + (non-atomically) loaded and stored back on entry to the function. + + This implies that it's possible to introduce spurious stores on entry to + the function without introducing traps or data races. This does not + necessarily hold throughout the whole function, as the pointer may escape + to a different thread during the execution of the function. See also the + :ref:`atomic optimization guide ` + + The "other attributes" that imply dereferenceability are + ``dereferenceable_or_null`` (if the pointer is non-null) and the + ``sret``, ``byval``, ``byref``, ``inalloca``, ``preallocated`` family of + attributes. Note that not all of these combinations are useful, e.g. + ``byval`` arguments are known to be writable even without this attribute. + + The ``writable`` attribute cannot be combined with ``readnone``, + ``readonly`` or a ``memory`` attribute that does not contain + ``argmem: write``. + .. _gc: Garbage Collector Strategy Names diff --git a/llvm/include/llvm/Analysis/AliasAnalysis.h b/llvm/include/llvm/Analysis/AliasAnalysis.h --- a/llvm/include/llvm/Analysis/AliasAnalysis.h +++ b/llvm/include/llvm/Analysis/AliasAnalysis.h @@ -875,10 +875,16 @@ /// Return true if the Object is writable, in the sense that any location based /// on this pointer that can be loaded can also be stored to without trapping. +/// Additionally, at the point Object is declared, stores can be introduced +/// without data races. At later points, this is only the case if the pointer +/// can not escape to a different thread. /// -/// By itself, this does not imply that introducing spurious stores is safe, -/// for example due to thread-safety reasons. -bool isWritableObject(const Value *Object); +/// If ExplicitlyDereferenceableOnly is set to true, this property only holds +/// for the part of Object that is explicitly marked as dereferenceable, e.g. +/// using the dereferenceable(N) attribute. It does not necessarily hold for +/// parts that are only known to be dereferenceable due to the presence of +/// loads. +bool isWritableObject(const Value *Object, bool &ExplicitlyDereferenceableOnly); /// A manager for alias analyses. /// diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -717,6 +717,7 @@ ATTR_KIND_MEMORY = 86, ATTR_KIND_NOFPCLASS = 87, ATTR_KIND_OPTIMIZE_FOR_DEBUGGING = 88, + ATTR_KIND_WRITABLE = 89, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -303,6 +303,9 @@ /// Function always comes back to callsite. def WillReturn : EnumAttr<"willreturn", [FnAttr]>; +/// Pointer argument is writable. +def Writable : EnumAttr<"writable", [ParamAttr]>; + /// Function only writes to memory. def WriteOnly : EnumAttr<"writeonly", [ParamAttr]>; diff --git a/llvm/lib/Analysis/AliasAnalysis.cpp b/llvm/lib/Analysis/AliasAnalysis.cpp --- a/llvm/lib/Analysis/AliasAnalysis.cpp +++ b/llvm/lib/Analysis/AliasAnalysis.cpp @@ -911,15 +911,23 @@ // We don't consider globals as writable: While the physical memory is writable, // we may not have provenance to perform the write. -bool llvm::isWritableObject(const Value *Object) { +bool llvm::isWritableObject(const Value *Object, + bool &ExplicitlyDereferenceableOnly) { + ExplicitlyDereferenceableOnly = false; + // TODO: Alloca might not be writable after its lifetime ends. // See https://github.com/llvm/llvm-project/issues/51838. if (isa(Object)) return true; - // TODO: Also handle sret. - if (auto *A = dyn_cast(Object)) + if (auto *A = dyn_cast(Object)) { + if (A->hasAttribute(Attribute::Writable)) { + ExplicitlyDereferenceableOnly = true; + return true; + } + return A->hasByValAttr(); + } // TODO: Noalias shouldn't imply writability, this should check for an // allocator function instead. diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2058,6 +2058,8 @@ return Attribute::Hot; case bitc::ATTR_KIND_PRESPLIT_COROUTINE: return Attribute::PresplitCoroutine; + case bitc::ATTR_KIND_WRITABLE: + return Attribute::Writable; } } diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -824,6 +824,8 @@ return bitc::ATTR_KIND_MUSTPROGRESS; case Attribute::PresplitCoroutine: return bitc::ATTR_KIND_PRESPLIT_COROUTINE; + case Attribute::Writable: + return bitc::ATTR_KIND_WRITABLE; case Attribute::EndAttrKinds: llvm_unreachable("Can not encode end-attribute kinds marker."); case Attribute::None: diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -1961,7 +1961,8 @@ .addAttribute(Attribute::ReadNone) .addAttribute(Attribute::ReadOnly) .addAttribute(Attribute::Dereferenceable) - .addAttribute(Attribute::DereferenceableOrNull); + .addAttribute(Attribute::DereferenceableOrNull) + .addAttribute(Attribute::Writable); if (ASK & ASK_UNSAFE_TO_DROP) Incompatible.addAttribute(Attribute::Nest) .addAttribute(Attribute::SwiftError) diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -1927,6 +1927,14 @@ "'noinline and alwaysinline' are incompatible!", V); + Check(!(Attrs.hasAttribute(Attribute::Writable) && + Attrs.hasAttribute(Attribute::ReadNone)), + "Attributes writable and readnone are incompatible!", V); + + Check(!(Attrs.hasAttribute(Attribute::Writable) && + Attrs.hasAttribute(Attribute::ReadOnly)), + "Attributes writable and readonly are incompatible!", V); + AttributeMask IncompatibleAttrs = AttributeFuncs::typeIncompatible(Ty); for (Attribute Attr : Attrs) { if (!Attr.isStringAttribute() && @@ -2131,6 +2139,11 @@ "Attributes 'minsize and optdebug' are incompatible!", V); } + Check(!Attrs.hasAttrSomewhere(Attribute::Writable) || + isModSet(Attrs.getMemoryEffects().getModRef(IRMemLocation::ArgMem)), + "Attribute writable and memory without argmem: write are incompatible!", + V); + if (Attrs.hasFnAttr("aarch64_pstate_sm_enabled")) { Check(!Attrs.hasFnAttr("aarch64_pstate_sm_compatible"), "Attributes 'aarch64_pstate_sm_enabled and " diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp --- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -7845,6 +7845,9 @@ // Clear existing attributes. A.removeAttrs(IRP, AttrKinds); + // Clear conflicting writable attribute. + if (isAssumedReadOnly()) + A.removeAttrs(IRP, Attribute::Writable); // Use the generic manifest method. return IRAttribute::manifest(A); @@ -8032,6 +8035,10 @@ ME = MemoryEffects::writeOnly(); A.removeAttrs(getIRPosition(), AttrKinds); + // Clear conflicting writable attribute. + if (ME.onlyReadsMemory()) + for (Argument &Arg : F.args()) + A.removeAttrs(IRPosition::argument(Arg), Attribute::Writable); return A.manifestAttrs(getIRPosition(), Attribute::getWithMemoryEffects(F.getContext(), ME)); } @@ -8066,6 +8073,11 @@ ME = MemoryEffects::writeOnly(); A.removeAttrs(getIRPosition(), AttrKinds); + // Clear conflicting writable attribute. + if (ME.onlyReadsMemory()) + for (Use &U : CB.args()) + A.removeAttrs(IRPosition::callsite_argument(CB, U.getOperandNo()), + Attribute::Writable); return A.manifestAttrs( getIRPosition(), Attribute::getWithMemoryEffects(CB.getContext(), ME)); } diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -285,6 +285,10 @@ if (NewME != OldME) { ++NumMemoryAttr; F->setMemoryEffects(NewME); + // Remove conflicting writable attributes. + if (!isModSet(NewME.getModRef(IRMemLocation::ArgMem))) + for (Argument &A : F->args()) + A.removeAttr(Attribute::Writable); Changed.insert(F); } } @@ -848,6 +852,9 @@ A->removeAttr(Attribute::WriteOnly); A->removeAttr(Attribute::ReadOnly); A->removeAttr(Attribute::ReadNone); + // Remove conflicting writable attribute. + if (R == Attribute::ReadNone || R == Attribute::ReadOnly) + A->removeAttr(Attribute::Writable); A->addAttr(R); if (R == Attribute::ReadOnly) ++NumReadOnlyArg; diff --git a/llvm/lib/Transforms/Scalar/LICM.cpp b/llvm/lib/Transforms/Scalar/LICM.cpp --- a/llvm/lib/Transforms/Scalar/LICM.cpp +++ b/llvm/lib/Transforms/Scalar/LICM.cpp @@ -2171,7 +2171,10 @@ // violating the memory model. if (StoreSafety == StoreSafetyUnknown) { Value *Object = getUnderlyingObject(SomePtr); - if (isWritableObject(Object) && + bool ExplicitlyDereferenceableOnly; + if (isWritableObject(Object, ExplicitlyDereferenceableOnly) && + (!ExplicitlyDereferenceableOnly || + isDereferenceablePointer(SomePtr, AccessTy, MDL)) && isThreadLocalObject(Object, CurLoop, DT, TTI)) StoreSafety = StoreSafe; } diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -994,6 +994,7 @@ case Attribute::ImmArg: case Attribute::ByRef: case Attribute::WriteOnly: + case Attribute::Writable: // These are not really attributes. case Attribute::None: case Attribute::EndAttrKinds: diff --git a/llvm/test/Bitcode/attributes.ll b/llvm/test/Bitcode/attributes.ll --- a/llvm/test/Bitcode/attributes.ll +++ b/llvm/test/Bitcode/attributes.ll @@ -511,12 +511,16 @@ ; CHECK: define void @f88() [[SKIPPROFILE:#[0-9]+]] define void @f88() skipprofile { ret void } -define void @f89() optdebug ; CHECK: define void @f89() [[OPTDEBUG:#[0-9]+]] -{ +define void @f89() optdebug { ret void; } +; CHECK: define void @f90(ptr writable %p) +define void @f90(ptr writable %p) { + ret void +} + ; CHECK: attributes #0 = { noreturn } ; CHECK: attributes #1 = { nounwind } ; CHECK: attributes #2 = { memory(none) } diff --git a/llvm/test/Transforms/FunctionAttrs/readattrs.ll b/llvm/test/Transforms/FunctionAttrs/readattrs.ll --- a/llvm/test/Transforms/FunctionAttrs/readattrs.ll +++ b/llvm/test/Transforms/FunctionAttrs/readattrs.ll @@ -230,7 +230,7 @@ ; ATTRIBUTOR-LABEL: define {{[^@]+}}@test8_2 ; ATTRIBUTOR-SAME: (ptr nocapture nofree writeonly [[P:%.*]]) #[[ATTR0]] { ; ATTRIBUTOR-NEXT: entry: -; ATTRIBUTOR-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR13:[0-9]+]] +; ATTRIBUTOR-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR14:[0-9]+]] ; ATTRIBUTOR-NEXT: store i32 10, ptr [[CALL]], align 4 ; ATTRIBUTOR-NEXT: ret void ; @@ -238,7 +238,7 @@ ; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@test8_2 ; ATTRIBUTOR-CGSCC-SAME: (ptr nofree writeonly [[P:%.*]]) #[[ATTR5:[0-9]+]] { ; ATTRIBUTOR-CGSCC-NEXT: entry: -; ATTRIBUTOR-CGSCC-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR13:[0-9]+]] +; ATTRIBUTOR-CGSCC-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR14:[0-9]+]] ; ATTRIBUTOR-CGSCC-NEXT: store i32 10, ptr [[CALL]], align 4 ; ATTRIBUTOR-CGSCC-NEXT: ret void ; @@ -260,13 +260,13 @@ ; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(write) ; ATTRIBUTOR-LABEL: define {{[^@]+}}@test9 ; ATTRIBUTOR-SAME: (<4 x ptr> [[PTRS:%.*]], <4 x i32> [[VAL:%.*]]) #[[ATTR0]] { -; ATTRIBUTOR-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> ) #[[ATTR14:[0-9]+]] +; ATTRIBUTOR-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> ) #[[ATTR15:[0-9]+]] ; ATTRIBUTOR-NEXT: ret void ; ; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(write) ; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@test9 ; ATTRIBUTOR-CGSCC-SAME: (<4 x ptr> [[PTRS:%.*]], <4 x i32> [[VAL:%.*]]) #[[ATTR0]] { -; ATTRIBUTOR-CGSCC-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> ) #[[ATTR14:[0-9]+]] +; ATTRIBUTOR-CGSCC-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> ) #[[ATTR15:[0-9]+]] ; ATTRIBUTOR-CGSCC-NEXT: ret void ; call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32>%val, <4 x ptr> %ptrs, i32 4, <4 x i1>) @@ -284,13 +284,13 @@ ; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(read) ; ATTRIBUTOR-LABEL: define {{[^@]+}}@test10 ; ATTRIBUTOR-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR7:[0-9]+]] { -; ATTRIBUTOR-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> , <4 x i32> undef) #[[ATTR15:[0-9]+]] +; ATTRIBUTOR-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> , <4 x i32> undef) #[[ATTR16:[0-9]+]] ; ATTRIBUTOR-NEXT: ret <4 x i32> [[RES]] ; ; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(read) ; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@test10 ; ATTRIBUTOR-CGSCC-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR8:[0-9]+]] { -; ATTRIBUTOR-CGSCC-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> , <4 x i32> undef) #[[ATTR15:[0-9]+]] +; ATTRIBUTOR-CGSCC-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> , <4 x i32> undef) #[[ATTR16:[0-9]+]] ; ATTRIBUTOR-CGSCC-NEXT: ret <4 x i32> [[RES]] ; %res = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> %ptrs, i32 4, <4 x i1>, <4 x i32>undef) @@ -719,5 +719,48 @@ call void @readonly_param(ptr %p) ["unknown"()] ret void } + +define i32 @writable_readonly(ptr writable dereferenceable(4) %p) { +; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) +; FNATTRS-LABEL: define {{[^@]+}}@writable_readonly +; FNATTRS-SAME: (ptr nocapture readonly dereferenceable(4) [[P:%.*]]) #[[ATTR15:[0-9]+]] { +; FNATTRS-NEXT: [[V:%.*]] = load i32, ptr [[P]], align 4 +; FNATTRS-NEXT: ret i32 [[V]] +; +; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@writable_readonly +; ATTRIBUTOR-SAME: (ptr nocapture nofree nonnull readonly dereferenceable(4) [[P:%.*]]) #[[ATTR13:[0-9]+]] { +; ATTRIBUTOR-NEXT: [[V:%.*]] = load i32, ptr [[P]], align 4 +; ATTRIBUTOR-NEXT: ret i32 [[V]] +; +; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) +; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@writable_readonly +; ATTRIBUTOR-CGSCC-SAME: (ptr nocapture nofree nonnull readonly dereferenceable(4) [[P:%.*]]) #[[ATTR13:[0-9]+]] { +; ATTRIBUTOR-CGSCC-NEXT: [[V:%.*]] = load i32, ptr [[P]], align 4 +; ATTRIBUTOR-CGSCC-NEXT: ret i32 [[V]] +; + %v = load i32, ptr %p + ret i32 %v +} + +define void @writable_readnone(ptr writable dereferenceable(4) %p) { +; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; FNATTRS-LABEL: define {{[^@]+}}@writable_readnone +; FNATTRS-SAME: (ptr nocapture readnone dereferenceable(4) [[P:%.*]]) #[[ATTR1]] { +; FNATTRS-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@writable_readnone +; ATTRIBUTOR-SAME: (ptr nocapture nofree nonnull readnone dereferenceable(4) [[P:%.*]]) #[[ATTR1]] { +; ATTRIBUTOR-NEXT: ret void +; +; ATTRIBUTOR-CGSCC: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) +; ATTRIBUTOR-CGSCC-LABEL: define {{[^@]+}}@writable_readnone +; ATTRIBUTOR-CGSCC-SAME: (ptr nocapture nofree nonnull readnone dereferenceable(4) [[P:%.*]]) #[[ATTR1]] { +; ATTRIBUTOR-CGSCC-NEXT: ret void +; + ret void +} + ;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line: ; COMMON: {{.*}} diff --git a/llvm/test/Transforms/LICM/scalar-promote.ll b/llvm/test/Transforms/LICM/scalar-promote.ll --- a/llvm/test/Transforms/LICM/scalar-promote.ll +++ b/llvm/test/Transforms/LICM/scalar-promote.ll @@ -885,9 +885,40 @@ ret void } -; TODO: The store can be promoted, as sret memory is writable. -define void @sret_cond_store(ptr sret(i32) noalias %ptr) { -; CHECK-LABEL: @sret_cond_store( +define void @cond_store_writable_dereferenceable(ptr noalias writable dereferenceable(4) %ptr) { +; CHECK-LABEL: @cond_store_writable_dereferenceable( +; CHECK-NEXT: [[PTR_PROMOTED:%.*]] = load i32, ptr [[PTR:%.*]], align 4 +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[V_INC1:%.*]] = phi i32 [ [[V_INC:%.*]], [[LOOP_LATCH:%.*]] ], [ [[PTR_PROMOTED]], [[TMP0:%.*]] ] +; CHECK-NEXT: [[C:%.*]] = icmp ult i32 [[V_INC1]], 10 +; CHECK-NEXT: br i1 [[C]], label [[LOOP_LATCH]], label [[EXIT:%.*]] +; CHECK: loop.latch: +; CHECK-NEXT: [[V_INC]] = add i32 [[V_INC1]], 1 +; CHECK-NEXT: br label [[LOOP]] +; CHECK: exit: +; CHECK-NEXT: [[V_INC1_LCSSA:%.*]] = phi i32 [ [[V_INC1]], [[LOOP]] ] +; CHECK-NEXT: store i32 [[V_INC1_LCSSA]], ptr [[PTR]], align 4 +; CHECK-NEXT: ret void +; + br label %loop + +loop: + %v = load i32, ptr %ptr + %c = icmp ult i32 %v, 10 + br i1 %c, label %loop.latch, label %exit + +loop.latch: + %v.inc = add i32 %v, 1 + store i32 %v.inc, ptr %ptr + br label %loop + +exit: + ret void +} + +define void @cond_store_writable_not_sufficiently_dereferenceable(ptr noalias writable dereferenceable(2) %ptr) { +; CHECK-LABEL: @cond_store_writable_not_sufficiently_dereferenceable( ; CHECK-NEXT: [[PTR_PROMOTED:%.*]] = load i32, ptr [[PTR:%.*]], align 4 ; CHECK-NEXT: br label [[LOOP:%.*]] ; CHECK: loop: diff --git a/llvm/test/Verifier/writable-attr.ll b/llvm/test/Verifier/writable-attr.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Verifier/writable-attr.ll @@ -0,0 +1,30 @@ +; RUN: not llvm-as -disable-output %s 2>&1 | FileCheck %s + +; CHECK: Attribute 'writable' applied to incompatible type! +; CHECK-NEXT: ptr @not_pointer +define void @not_pointer_writable(i32 writable %arg) { + ret void +} + +; CHECK: Attributes writable and readnone are incompatible! +; CHECK-NEXT: ptr @writable_readnone +define void @writable_readnone(ptr writable readnone %arg) { + ret void +} + +; CHECK: Attributes writable and readonly are incompatible! +; CHECK-NEXT: ptr @writable_readonly +define void @writable_readonly(ptr writable readonly %arg) { + ret void +} + +; CHECK: Attribute writable and memory without argmem: write are incompatible! +; CHECK-NEXT: ptr @writable_memory_argmem_read +define void @writable_memory_argmem_read(ptr writable %arg) memory(write, argmem: read) { + ret void +} + +; CHECK-NOT: incompatible +define void @writable_memory_argmem_write(ptr writable %arg) memory(read, argmem: write) { + ret void +}