Index: llvm/docs/LangRef.rst =================================================================== --- llvm/docs/LangRef.rst +++ llvm/docs/LangRef.rst @@ -1532,6 +1532,19 @@ 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. + .. _gc: Garbage Collector Strategy Names Index: llvm/include/llvm/Analysis/AliasAnalysis.h =================================================================== --- llvm/include/llvm/Analysis/AliasAnalysis.h +++ 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. /// Index: llvm/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -713,6 +713,7 @@ ATTR_KIND_SKIP_PROFILE = 85, ATTR_KIND_MEMORY = 86, ATTR_KIND_NOFPCLASS = 87, + ATTR_KIND_WRITABLE = 88, }; enum ComdatSelectionKindCodes { Index: llvm/include/llvm/IR/Attributes.td =================================================================== --- llvm/include/llvm/IR/Attributes.td +++ llvm/include/llvm/IR/Attributes.td @@ -300,6 +300,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]>; Index: llvm/lib/Analysis/AliasAnalysis.cpp =================================================================== --- llvm/lib/Analysis/AliasAnalysis.cpp +++ 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. Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -2060,6 +2060,8 @@ return Attribute::Hot; case bitc::ATTR_KIND_PRESPLIT_COROUTINE: return Attribute::PresplitCoroutine; + case bitc::ATTR_KIND_WRITABLE: + return Attribute::Writable; } } Index: llvm/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -809,6 +809,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: Index: llvm/lib/IR/Attributes.cpp =================================================================== --- llvm/lib/IR/Attributes.cpp +++ 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) Index: llvm/lib/Transforms/Scalar/LICM.cpp =================================================================== --- llvm/lib/Transforms/Scalar/LICM.cpp +++ llvm/lib/Transforms/Scalar/LICM.cpp @@ -2184,7 +2184,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; } Index: llvm/lib/Transforms/Utils/CodeExtractor.cpp =================================================================== --- llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -990,6 +990,7 @@ case Attribute::ImmArg: case Attribute::ByRef: case Attribute::WriteOnly: + case Attribute::Writable: // These are not really attributes. case Attribute::None: case Attribute::EndAttrKinds: Index: llvm/test/Bitcode/attributes.ll =================================================================== --- llvm/test/Bitcode/attributes.ll +++ llvm/test/Bitcode/attributes.ll @@ -511,6 +511,11 @@ ; CHECK: define void @f88() [[SKIPPROFILE:#[0-9]+]] define void @f88() skipprofile { ret void } +; CHECK: define void @f89(ptr writable %p) +define void @f89(ptr writable %p) { + ret void +} + ; CHECK: attributes #0 = { noreturn } ; CHECK: attributes #1 = { nounwind } ; CHECK: attributes #2 = { memory(none) } Index: llvm/test/Transforms/LICM/scalar-promote.ll =================================================================== --- llvm/test/Transforms/LICM/scalar-promote.ll +++ 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: Index: llvm/test/Verifier/pointer-only-attr.ll =================================================================== --- /dev/null +++ llvm/test/Verifier/pointer-only-attr.ll @@ -0,0 +1,7 @@ +; 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 +}