diff --git a/clang/test/CodeGen/arm-vfp16-arguments.c b/clang/test/CodeGen/arm-vfp16-arguments.c --- a/clang/test/CodeGen/arm-vfp16-arguments.c +++ b/clang/test/CodeGen/arm-vfp16-arguments.c @@ -71,6 +71,6 @@ hfa_t ghfa; hfa_t test_ret_hfa(void) { return ghfa; } -// CHECK-SOFT: define void @test_ret_hfa(%struct.hfa_t* noalias nocapture sret %agg.result) +// CHECK-SOFT: define void @test_ret_hfa(%struct.hfa_t* noalias nocapture sret writeonly %agg.result) // CHECK-HARD: define arm_aapcs_vfpcc [2 x <2 x i32>] @test_ret_hfa() // CHECK-FULL: define arm_aapcs_vfpcc %struct.hfa_t @test_ret_hfa() diff --git a/clang/test/CodeGen/systemz-inline-asm.c b/clang/test/CodeGen/systemz-inline-asm.c --- a/clang/test/CodeGen/systemz-inline-asm.c +++ b/clang/test/CodeGen/systemz-inline-asm.c @@ -123,7 +123,7 @@ long double test_f128(long double f, long double g) { asm("axbr %0, %2" : "=f" (f) : "0" (f), "f" (g)); return f; -// CHECK: define void @test_f128(fp128* noalias nocapture sret [[DEST:%.*]], fp128* nocapture readonly, fp128* nocapture readonly) +// CHECK: define void @test_f128(fp128* noalias nocapture sret writeonly [[DEST:%.*]], fp128* nocapture readonly, fp128* nocapture readonly) // CHECK: %f = load fp128, fp128* %0 // CHECK: %g = load fp128, fp128* %1 // CHECK: [[RESULT:%.*]] = tail call fp128 asm "axbr $0, $2", "=f,0,f"(fp128 %f, fp128 %g) diff --git a/clang/test/CodeGenCXX/wasm-args-returns.cpp b/clang/test/CodeGenCXX/wasm-args-returns.cpp --- a/clang/test/CodeGenCXX/wasm-args-returns.cpp +++ b/clang/test/CodeGenCXX/wasm-args-returns.cpp @@ -30,7 +30,7 @@ double d, e; }; test(two_fields); -// CHECK: define void @_Z7forward10two_fields(%struct.two_fields* noalias nocapture sret %{{.*}}, %struct.two_fields* byval nocapture readonly align 8 %{{.*}}) +// CHECK: define void @_Z7forward10two_fields(%struct.two_fields* noalias nocapture sret writeonly %{{.*}}, %struct.two_fields* byval nocapture readonly align 8 %{{.*}}) // // CHECK: define void @_Z15test_two_fieldsv() // CHECK: %[[tmp:.*]] = alloca %struct.two_fields, align 8 diff --git a/clang/test/CodeGenObjC/os_log.m b/clang/test/CodeGenObjC/os_log.m --- a/clang/test/CodeGenObjC/os_log.m +++ b/clang/test/CodeGenObjC/os_log.m @@ -14,7 +14,7 @@ #ifdef __x86_64__ // CHECK-LABEL: define i8* @test_builtin_os_log // CHECK-O0-LABEL: define i8* @test_builtin_os_log -// CHECK: (i8* returned "no-capture-maybe-returned" %[[BUF:.*]]) +// CHECK: (i8* returned writeonly "no-capture-maybe-returned" %[[BUF:.*]]) // CHECK-O0: (i8* %[[BUF:.*]]) void *test_builtin_os_log(void *buf) { return __builtin_os_log_format(buf, "capabilities: %@", GenString()); diff --git a/clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl b/clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl --- a/clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl +++ b/clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl @@ -309,7 +309,7 @@ // CHECK: void @func_different_size_type_pair_arg(i64 %arg1.coerce0, i32 %arg1.coerce1) void func_different_size_type_pair_arg(different_size_type_pair arg1) { } -// CHECK: void @func_flexible_array_arg(%struct.flexible_array addrspace(5)* byval nocapture align 4 %arg) +// CHECK: void @func_flexible_array_arg(%struct.flexible_array addrspace(5)* byval nocapture readnone align 4 %arg) void func_flexible_array_arg(flexible_array arg) { } // CHECK: define float @func_f32_ret() @@ -404,14 +404,14 @@ return s; } -// CHECK: define void @func_ret_struct_arr32(%struct.struct_arr32 addrspace(5)* noalias nocapture sret %agg.result) +// CHECK: define void @func_ret_struct_arr32(%struct.struct_arr32 addrspace(5)* noalias nocapture sret writeonly %agg.result) struct_arr32 func_ret_struct_arr32() { struct_arr32 s = { 0 }; return s; } -// CHECK: define void @func_ret_struct_arr33(%struct.struct_arr33 addrspace(5)* noalias nocapture sret %agg.result) +// CHECK: define void @func_ret_struct_arr33(%struct.struct_arr33 addrspace(5)* noalias nocapture sret writeonly %agg.result) struct_arr33 func_ret_struct_arr33() { struct_arr33 s = { 0 }; @@ -440,7 +440,7 @@ return s; } -// CHECK: define void @func_flexible_array_ret(%struct.flexible_array addrspace(5)* noalias nocapture sret %agg.result) +// CHECK: define void @func_flexible_array_ret(%struct.flexible_array addrspace(5)* noalias nocapture sret writeonly %agg.result) flexible_array func_flexible_array_ret() { flexible_array s = { 0 }; diff --git a/clang/test/CodeGenOpenCL/amdgpu-call-kernel.cl b/clang/test/CodeGenOpenCL/amdgpu-call-kernel.cl --- a/clang/test/CodeGenOpenCL/amdgpu-call-kernel.cl +++ b/clang/test/CodeGenOpenCL/amdgpu-call-kernel.cl @@ -1,6 +1,6 @@ // REQUIRES: amdgpu-registered-target // RUN: %clang_cc1 -triple amdgcn-unknown-unknown -S -emit-llvm -o - %s | FileCheck %s -// CHECK: define amdgpu_kernel void @test_call_kernel(i32 addrspace(1)* nocapture %out) +// CHECK: define amdgpu_kernel void @test_call_kernel(i32 addrspace(1)* nocapture writeonly %out) // CHECK: store i32 4, i32 addrspace(1)* %out, align 4 kernel void test_kernel(global int *out) diff --git a/clang/test/CodeGenOpenCL/kernels-have-spir-cc-by-default.cl b/clang/test/CodeGenOpenCL/kernels-have-spir-cc-by-default.cl --- a/clang/test/CodeGenOpenCL/kernels-have-spir-cc-by-default.cl +++ b/clang/test/CodeGenOpenCL/kernels-have-spir-cc-by-default.cl @@ -28,7 +28,7 @@ // CHECK: spir_kernel // AMDGCN: define amdgpu_kernel void @test_single // CHECK: struct.int_single* byval nocapture -// CHECK: i32* nocapture %output +// CHECK: i32* nocapture writeonly %output output[0] = input.a; } @@ -36,7 +36,7 @@ // CHECK: spir_kernel // AMDGCN: define amdgpu_kernel void @test_pair // CHECK: struct.int_pair* byval nocapture -// CHECK: i32* nocapture %output +// CHECK: i32* nocapture writeonly %output output[0] = (int)input.a; output[1] = (int)input.b; } @@ -45,7 +45,7 @@ // CHECK: spir_kernel // AMDGCN: define amdgpu_kernel void @test_kernel // CHECK: struct.test_struct* byval nocapture -// CHECK: i32* nocapture %output +// CHECK: i32* nocapture writeonly %output output[0] = input.elementA; output[1] = input.elementB; output[2] = (int)input.elementC; @@ -59,7 +59,7 @@ void test_function(int_pair input, global int* output) { // CHECK-NOT: spir_kernel // AMDGCN-NOT: define amdgpu_kernel void @test_function -// CHECK: i64 %input.coerce0, i64 %input.coerce1, i32* nocapture %output +// CHECK: i64 %input.coerce0, i64 %input.coerce1, i32* nocapture writeonly %output output[0] = (int)input.a; output[1] = (int)input.b; } diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h --- a/llvm/include/llvm/Transforms/IPO/Attributor.h +++ b/llvm/include/llvm/Transforms/IPO/Attributor.h @@ -295,6 +295,45 @@ static constexpr Attribute::AttrKind ID = Attribute::NoCapture; }; +/// An abstract interface for all nocapture attributes. +struct AAMemoryBehavior : public AbstractAttribute { + + /// See AbstractAttribute::AbstractAttribute(...). + AAMemoryBehavior(Value &V) : AbstractAttribute(V) {} + + /// Return true if we know that the underlying value is not read or accessed + /// in its respective scope. + virtual bool isKnownReadNone() const = 0; + + /// Return true if we assume that the underlying value is not read or accessed + /// in its respective scope. + virtual bool isAssumedReadNone() const = 0; + + /// Return true if we know that the underlying value is not accessed + /// (=written) in its respective scope. + virtual bool isKnownReadOnly() const = 0; + + /// Return true if we assume that the underlying value is not accessed + /// (=written) in its respective scope. + virtual bool isAssumedReadOnly() const = 0; + + /// Return true if we know that the underlying value is not read in its + /// respective scope. + virtual bool isKnownWriteOnly() const = 0; + + /// Return true if we assume that the underlying value is not read in its + /// respective scope. + virtual bool isAssumedWriteOnly() const = 0; + + /// See AbstractState::getAttrKind(). + Attribute::AttrKind getAttrKind() const override { + return ID; + } + + /// The identifier used by the Attributor for this class of attributes. + static constexpr Attribute::AttrKind ID = Attribute::ReadNone; +}; + /// ---------------------------------------------------------------------------- /// Pass (Manager) Boilerplate /// ---------------------------------------------------------------------------- diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp --- a/llvm/lib/Transforms/IPO/Attributor.cpp +++ b/llvm/lib/Transforms/IPO/Attributor.cpp @@ -56,6 +56,13 @@ STATISTIC(NumFnArgumentNoCapture, "Number of function arguments marked no-capture"); +STATISTIC(NumFnArgumentReadNone, + "Number of function arguments marked read-none"); +STATISTIC(NumFnArgumentReadOnly, + "Number of function arguments marked read-only"); +STATISTIC(NumFnArgumentWriteOnly, + "Number of function arguments marked write-only"); + // TODO: Determine a good default value. static cl::opt MaxFixpointIterations("attributor-max-iterations", cl::Hidden, @@ -165,6 +172,15 @@ case Attribute::NoReturn: NumFnNoReturn++; return; + case Attribute::ReadNone: + NumFnArgumentReadNone++; + return; + case Attribute::ReadOnly: + NumFnArgumentReadOnly++; + return; + case Attribute::WriteOnly: + NumFnArgumentWriteOnly++; + return; default: return; } @@ -1159,6 +1175,326 @@ : ChangeStatus::CHANGED; } +/// -------------------- Memory Behavior Attributes ---------------------------- +/// Includes read-none, read-only, and write-only. +/// ---------------------------------------------------------------------------- + +/// A class to hold the state of for memory behaviour attributes. +/// +/// The state is encoded in the bits of the "char" variables known and assumed +/// as defined by the encoding bits. +struct AAMemoryBehaviorImpl : public AAMemoryBehavior, + IntegerState { + + AAMemoryBehaviorImpl(Value &V) : AAMemoryBehavior(V) { + assert(getAssumed() == BEST_STATE && "Expected optimistic initialization!"); + } + + /// State encoding bits. A set bit in the state means the property holds. + /// BEST_STATE is the best possible state, 0 the worst possible state. + enum { + NO_READS = 1 << 0, + NO_WRITES = 1 << 1, + NO_ACCESSES = NO_READS | NO_WRITES, + + BEST_STATE = NO_ACCESSES, + }; + + /// See AAMemoryBehavior::isKnownReadNone(); + virtual bool isKnownReadNone() const override { + return testBits(getKnown(), NO_ACCESSES); + } + + /// See AAMemoryBehavior::isAssumedReadNone(); + virtual bool isAssumedReadNone() const override { + return testBits(getAssumed(), NO_ACCESSES); + } + + /// See AAMemoryBehavior::isKnownReadOnly(); + virtual bool isKnownReadOnly() const override { + return testBits(getKnown(), NO_WRITES); + } + + /// See AAMemoryBehavior::isAssumedReadOnly(); + virtual bool isAssumedReadOnly() const override { + return testBits(getAssumed(), NO_WRITES); + } + + /// See AAMemoryBehavior::isKnownWriteOnly(); + virtual bool isKnownWriteOnly() const override { + return testBits(getKnown(), NO_READS); + } + + /// See AAMemoryBehavior::isAssumedWriteOnly(); + virtual bool isAssumedWriteOnly() const override { + return testBits(getAssumed(), NO_READS); + } + + /// See AbstractState::getAsStr(). + const std::string getAsStr() const override { + std::string S = ""; + if (isAssumedReadNone()) + S = "readnone"; + else if (isAssumedReadOnly()) + S = "readonly"; + else if (isAssumedWriteOnly()) + S = "writeonly"; + else + S = "may-read/write"; + + return S; + } + + /// Return the memory behavior information of \p V encoded in the IR. + static decltype(Known) getKnownStateFromValue(const Value &V); + + /// See AbstractAttribute::getDeducedAttributes(Attributor &A). + virtual void + getDeducedAttributes(SmallVectorImpl &Attrs) const override; + + /// See AbstractAttribute::getState() + ///{ + AbstractState &getState() override { return *this; } + const AbstractState &getState() const override { return *this; } + ///} +}; + +decltype(AAMemoryBehaviorImpl::Known) +AAMemoryBehaviorImpl::getKnownStateFromValue(const Value &V) { + decltype(AAMemoryBehaviorImpl::Known) Known = 0; + + if (const Argument *Arg = dyn_cast(&V)) { + if (Arg->hasAttribute(Attribute::ReadNone)) + setBits(Known, NO_ACCESSES); + else if (Arg->hasAttribute(Attribute::ReadOnly)) + setBits(Known, NO_WRITES); + else if (Arg->hasAttribute(Attribute::WriteOnly)) + setBits(Known, NO_READS); + setBits(Known, getKnownStateFromValue(*Arg->getParent())); + } else if (const Function *Fn = dyn_cast(&V)) { + if (Fn->hasFnAttribute(Attribute::ReadNone)) + setBits(Known, NO_ACCESSES); + else if (Fn->hasFnAttribute(Attribute::ReadOnly)) + setBits(Known, NO_WRITES); + else if (Fn->hasFnAttribute(Attribute::WriteOnly)) + setBits(Known, NO_READS); + } + + return Known; +} + +void AAMemoryBehaviorImpl::getDeducedAttributes( + SmallVectorImpl &Attrs) const { + LLVMContext &Ctx = getAnchoredValue().getContext(); + assert(Attrs.size() == 0); + if (isAssumedReadNone()) + Attrs.push_back(Attribute::get(Ctx, Attribute::ReadNone)); + else if (isAssumedReadOnly()) + Attrs.push_back(Attribute::get(Ctx, Attribute::ReadOnly)); + else if (isAssumedWriteOnly()) + Attrs.push_back(Attribute::get(Ctx, Attribute::WriteOnly)); + assert(Attrs.size() <= 1); +} + +/// An AA to represent the memory behavior argument attributes. +struct AAMemoryBehaviorArgument final : public AAMemoryBehaviorImpl { + + /// See AAMemoryBehaviorImpl::AAMemoryBehaviorImpl(...). + AAMemoryBehaviorArgument(Argument &Arg) : AAMemoryBehaviorImpl(Arg) {} + + /// See AbstractAttribute::initialize(...). + void initialize(Attributor &A) override { + Argument &Arg = cast(getAnchoredValue()); + + if (Arg.getNumUses() == 0) + setBits(Known, NO_ACCESSES); + else + Known = getKnownStateFromValue(Arg); + } + + /// See AbstractAttribute::updateImpl(Attributor &A). + virtual ChangeStatus updateImpl(Attributor &A) override; + + /// See AbstractAttribute::getManifestPosition(). + virtual ManifestPosition getManifestPosition() const override { + return MP_ARGUMENT; + } + + /// See AbstractAttribute::manifest(Attributor &A). + virtual ChangeStatus manifest(Attributor &A) override; + +private: + /// Return true if users of \p UserI might access the underlying + /// variable/location described by \p U. + bool followUseIn(Attributor &A, const Use *U, const Instruction *UserI); + + /// Update the state according to the effect of use \p U in \p UserI. + void analyzeUseIn(Attributor &A, const Use *U, const Instruction *UserI); + + /// Container for (transitive) uses of the associated argument. + SmallVector Uses; + + /// Container for visited (transitive) uses of the associated argument. + SmallPtrSet VisitedUses; +}; + +ChangeStatus AAMemoryBehaviorArgument::manifest(Attributor &A) { + Argument &Arg = cast(getAnchoredValue()); + + // Check if we would improve the existing attributes first. + decltype(Assumed) ExistingState = getKnownStateFromValue(Arg); + if (testBits(ExistingState, Assumed)) + return ChangeStatus::UNCHANGED; + + // Remove existing attributes as they not always mix well with derived ones. + Arg.removeAttr(Attribute::ReadNone); + Arg.removeAttr(Attribute::ReadOnly); + Arg.removeAttr(Attribute::WriteOnly); + return AbstractAttribute::manifest(A); +} + +bool AAMemoryBehaviorArgument::followUseIn(Attributor &A, const Use *U, + const Instruction *UserI) { + if (isa(UserI)) { + // The loaded value is unrelated to the pointer argument, no need to + // follow the users of the load. + return false; + } + + ImmutableCallSite ICS(UserI); + if (ICS) { + if (!ICS.isArgOperand(U)) + return true; + + // If the use is a call argument known not to be captured, the users of + // the call do not need to be visited because they have to be unrelated to + // the input. Note that this check is not trivial even though we disallow + // general capturing of the underlying argument. The reason is that the + // call might the argument "through return", which we allow and for which we + // need to check call users. + unsigned ArgNo = ICS.getArgumentNo(U); + AANoCapture *ArgNoCaptureAA = + A.getAAFor(*this, *ICS.getCalledValue(), ArgNo); + if (ArgNoCaptureAA && ArgNoCaptureAA->isAssumedNoCapture()) + return false; + } + + // By default we follow all uses assuming UserI might leak information on U. + return true; +} + +void AAMemoryBehaviorArgument::analyzeUseIn(Attributor &A, const Use *U, + const Instruction *UserI) { + assert(UserI->mayReadOrWriteMemory()); + + switch (UserI->getOpcode()) { + case Instruction::Load: + // Loads cause the NO_READS property to disappear. + setBits(unsetBits(Assumed, NO_READS), Known); + return; + + case Instruction::Store: + // Stores cause the NO_WRITES property to disappear if the use is the + // pointer operand. Note that we do assume that capturing was taken care of + // somewhere else. + if (cast(UserI)->getPointerOperand() == U->get()) + setBits(unsetBits(Assumed, NO_WRITES), Known); + return; + + case Instruction::Call: + case Instruction::Invoke: { + ImmutableCallSite ICS(UserI); + // For call sites we look at the argument memory behavior attribute (this + // could be recursive!) in order to restrict our own state. + + // Note that operand bundle uses should never make it till here as these + // would cause the argument to escape. + assert(ICS.isArgOperand(U)); + + // Adjust the possible access behavior based on the information on the + // argument. + unsigned ArgNo = ICS.getArgumentNo(U); + auto *ArgMemAccBehavior = A.getAAFor( + *this, *ICS.getCalledValue(), ArgNo); + if (ArgMemAccBehavior && ArgMemAccBehavior->getState().isValidState()) { + // "assumed" has at most the same bits as the ArgMemAccBehavior assumed + // and at least "known". + setBits(intersectBits(Assumed, ArgMemAccBehavior->getAssumed()), Known); + return; + } + + if (const Function *Callee = ICS.getCalledFunction()) { + if (Callee->arg_size() > ArgNo) { + // "assumed" has at most the same bits known to hold for the argument + // and at least "known". + const Argument &CalleeArg = *(Callee->arg_begin() + ArgNo); + setBits(intersectBits(Assumed, getKnownStateFromValue(CalleeArg)), + Known); + return; + } + } + break; + } + }; + + // Generally, look at the "may-properties" and adjust the assumed state. + if (UserI->mayReadFromMemory()) + setBits(unsetBits(Assumed, NO_READS), Known); + if (UserI->mayWriteToMemory()) + setBits(unsetBits(Assumed, NO_WRITES), Known); +} + +ChangeStatus AAMemoryBehaviorArgument::updateImpl(Attributor &A) { + assert(isAssumedReadOnly() || isAssumedWriteOnly()); + + // The associated argument. + Argument &Arg = cast(getAnchoredValue()); + + // First make sure the argument is not captured (except through "return"), if + // it is, any information derived would be irrelevant anyway. + if (!Arg.hasNoCaptureAttr()) { + AANoCapture *ArgNoCaptureAA = A.getAAFor(*this, Arg); + if (!ArgNoCaptureAA || !ArgNoCaptureAA->isAssumedNoCaptureMaybeReturned()) { + assert(!isAtFixpoint()); + indicateFixpoint(/* Optimistic */ false); + return ChangeStatus::CHANGED; + } + } + + // The current assumed state used to determine a change. + auto AssumedState = getAssumed(); + + // The first time update is called we initialize the use vector with all + // direct argument uses. Transitive uses are collected every time because the + // need to explore more of them may only be known in subsequent updates. + bool ExploreUses = VisitedUses.empty(); + if (ExploreUses) { + for (const Use &U : Arg.uses()) + if (VisitedUses.insert(&U).second) + Uses.push_back(&U); + } + + // Visit and expand uses until all are analyzed or a fixpoint is reached. + for (unsigned i = 0; i < Uses.size() && !isAtFixpoint(); i++) { + const Use *U = Uses[i]; + Instruction *UserI = cast(U->getUser()); + + // Check if the users of UserI should also be visited. + if (followUseIn(A, U, UserI)) + for (const Use &UserIUse : UserI->uses()) + if (VisitedUses.insert(&UserIUse).second) + Uses.push_back(&UserIUse); + + // If UserI might touch memory we analyze the use in detail. + if (UserI->mayReadOrWriteMemory()) + analyzeUseIn(A, U, UserI); + } + + assert(testBits(Assumed, Known) && "Update created an invalid state!"); + return (AssumedState != getAssumed()) ? ChangeStatus::CHANGED + : ChangeStatus::UNCHANGED; +} + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -1358,6 +1694,7 @@ // is also derived but as a "function return attribute" (see above). if (Arg.getType()->isPointerTy()) { registerAA(*new AANoCaptureArgument(Arg)); + registerAA(*new AAMemoryBehaviorArgument(Arg)); } } diff --git a/llvm/test/Transforms/FunctionAttrs/SCC1.ll b/llvm/test/Transforms/FunctionAttrs/SCC1.ll --- a/llvm/test/Transforms/FunctionAttrs/SCC1.ll +++ b/llvm/test/Transforms/FunctionAttrs/SCC1.ll @@ -46,7 +46,7 @@ ; target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -; CHECK: define dso_local i32* @external_ret2_nrw(i32* nocapture %n0, i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %w0) #[[NOUNWIND:[0-9]*]] +; CHECK: define dso_local i32* @external_ret2_nrw(i32* nocapture readnone %n0, i32* nocapture readonly %r0, i32* returned writeonly "no-capture-maybe-returned" %w0) #[[NOUNWIND:[0-9]*]] define dso_local i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %call = call i32* @internal_ret0_nw(i32* %n0, i32* %w0) @@ -56,7 +56,7 @@ ret i32* %call3 } -; CHECK: define internal i32* @internal_ret0_nw(i32* returned "no-capture-maybe-returned" %n0, i32* nocapture %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret0_nw(i32* readnone returned "no-capture-maybe-returned" %n0, i32* nocapture writeonly %w0) #[[NOUNWIND]] define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) { entry: %r0 = alloca i32, align 4 @@ -84,7 +84,7 @@ ret i32* %retval.0 } -; CHECK: define internal i32* @internal_ret1_rrw(i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %r1, i32* nocapture %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret1_rrw(i32* nocapture readonly %r0, i32* readonly returned "no-capture-maybe-returned" %r1, i32* nocapture writeonly %w0) #[[NOUNWIND]] define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -115,7 +115,7 @@ ret i32* %retval.0 } -; CHECK: define dso_local i32* @external_sink_ret2_nrw(i32* nocapture readnone %n0, i32* nocapture readonly %r0, i32* returned "no-capture-maybe-returned" %w0) #[[NOREC_NOUNWIND:[0-9]*]] +; CHECK: define dso_local i32* @external_sink_ret2_nrw(i32* nocapture readnone %n0, i32* nocapture readonly %r0, i32* returned writeonly "no-capture-maybe-returned" %w0) #[[NOREC_NOUNWIND:[0-9]*]] define dso_local i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %tobool = icmp ne i32* %n0, null @@ -133,7 +133,7 @@ ret i32* %w0 } -; CHECK: define internal i32* @internal_ret1_rw(i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %w0) #[[NOUNWIND]] +; CHECK: define internal i32* @internal_ret1_rw(i32* nocapture readonly %r0, i32* returned writeonly "no-capture-maybe-returned" %w0) #[[NOUNWIND]] define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) { entry: %0 = load i32, i32* %r0, align 4 @@ -158,7 +158,7 @@ ret i32* %retval.0 } -; CHECK: define dso_local i32* @external_source_ret2_nrw(i32* nocapture %n0, i32* nocapture %r0, i32* returned "no-capture-maybe-returned" %w0) #[[NOUNWIND]] +; CHECK: define dso_local i32* @external_source_ret2_nrw(i32* nocapture readnone %n0, i32* nocapture readonly %r0, i32* returned writeonly "no-capture-maybe-returned" %w0) #[[NOUNWIND]] define dso_local i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) { entry: %call = call i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) diff --git a/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll b/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll --- a/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll +++ b/llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll @@ -129,8 +129,7 @@ ; ; CHECK: define dso_local i64* @scc_B(double* readnone returned "no-capture-maybe-returned" %a) ; -; FIXME: readnone missing for %s -; CHECK: define dso_local i8* @scc_C(i16* returned "no-capture-maybe-returned" %a) +; CHECK: define dso_local i8* @scc_C(i16* readnone returned "no-capture-maybe-returned" %a) ; ; float *scc_A(int *a) { ; return (float*)(a ? (int*)scc_A((int*)scc_B((double*)scc_C((short*)a))) : a); @@ -259,7 +258,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local i64* @not_captured_but_returned_0(i64* returned "no-capture-maybe-returned" %a) +; CHECK: define dso_local i64* @not_captured_but_returned_0(i64* returned writeonly "no-capture-maybe-returned" %a) ; define dso_local i64* @not_captured_but_returned_0(i64* %a) #0 { entry: @@ -275,7 +274,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local nonnull i64* @not_captured_but_returned_1(i64* "no-capture-maybe-returned" %a) +; CHECK: define dso_local nonnull i64* @not_captured_but_returned_1(i64* writeonly "no-capture-maybe-returned" %a) ; define dso_local i64* @not_captured_but_returned_1(i64* %a) #0 { entry: @@ -291,7 +290,7 @@ ; not_captured_but_returned_1(a); ; } ; -; CHECK: define dso_local void @test_not_captured_but_returned_calls(i64* nocapture %a) +; CHECK: define dso_local void @test_not_captured_but_returned_calls(i64* nocapture writeonly %a) ; define dso_local void @test_not_captured_but_returned_calls(i64* %a) #0 { entry: @@ -307,7 +306,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local i64* @negative_test_not_captured_but_returned_call_0a(i64* returned "no-capture-maybe-returned" %a) +; CHECK: define dso_local i64* @negative_test_not_captured_but_returned_call_0a(i64* returned writeonly "no-capture-maybe-returned" %a) ; define dso_local i64* @negative_test_not_captured_but_returned_call_0a(i64* %a) #0 { entry: @@ -339,7 +338,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local nonnull i64* @negative_test_not_captured_but_returned_call_1a(i64* "no-capture-maybe-returned" %a) +; CHECK: define dso_local nonnull i64* @negative_test_not_captured_but_returned_call_1a(i64* writeonly "no-capture-maybe-returned" %a) ; define dso_local i64* @negative_test_not_captured_but_returned_call_1a(i64* %a) #0 { entry: @@ -376,10 +375,10 @@ ; ; FNATTR: define dso_local i32* @ret_arg_or_unknown(i32* %b) ; FNATTR: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* %b) -; ATTRIBUTOR: define dso_local i32* @ret_arg_or_unknown(i32* "no-capture-maybe-returned" %b) -; ATTRIBUTOR: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* "no-capture-maybe-returned" %b) -; BOTH: define dso_local i32* @ret_arg_or_unknown(i32* "no-capture-maybe-returned" %b) -; BOTH: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* "no-capture-maybe-returned" %b) +; ATTRIBUTOR: define dso_local i32* @ret_arg_or_unknown(i32* readnone "no-capture-maybe-returned" %b) +; ATTRIBUTOR: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* readnone "no-capture-maybe-returned" %b) +; BOTH: define dso_local i32* @ret_arg_or_unknown(i32* readnone "no-capture-maybe-returned" %b) +; BOTH: define dso_local i32* @ret_arg_or_unknown_through_phi(i32* readnone "no-capture-maybe-returned" %b) ; declare dso_local i32* @unknown() diff --git a/llvm/test/Transforms/FunctionAttrs/arg_returned.ll b/llvm/test/Transforms/FunctionAttrs/arg_returned.ll --- a/llvm/test/Transforms/FunctionAttrs/arg_returned.ll +++ b/llvm/test/Transforms/FunctionAttrs/arg_returned.ll @@ -180,8 +180,8 @@ ; FNATTR: define dso_local double* @ptr_scc_r1(double* %a, double* readnone %r, double* nocapture readnone %b) [[NoInlineNoUnwindReadnoneUwtable]] ; FNATTR: define dso_local double* @ptr_scc_r2(double* readnone %a, double* readnone %b, double* readnone %r) [[NoInlineNoUnwindReadnoneUwtable]] ; -; ATTRIBUTOR: define dso_local double* @ptr_sink_r0(double* returned "no-capture-maybe-returned" %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @ptr_scc_r1(double* %a, double* returned %r, double* nocapture %b) [[NoInlineNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @ptr_sink_r0(double* readnone returned "no-capture-maybe-returned" %r) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] +; ATTRIBUTOR: define dso_local double* @ptr_scc_r1(double* %a, double* returned %r, double* nocapture readnone %b) [[NoInlineNoUnwindReadnoneUwtable]] ; ATTRIBUTOR: define dso_local double* @ptr_scc_r2(double* %a, double* %b, double* returned %r) [[NoInlineNoUnwindReadnoneUwtable]] ; ; double* ptr_scc_r1(double* a, double* b, double* r); @@ -266,7 +266,7 @@ ; return *a ? a : ret0(ret0(ret0(...ret0(a)...))); ; } ; -; FEW_IT: define dso_local i32* @ret0(i32* %a) +; FEW_IT: define dso_local i32* @ret0(i32* readonly "no-capture-maybe-returned" %a) ; FNATTR: define dso_local i32* @ret0(i32* readonly %a) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; BOTH: define dso_local i32* @ret0(i32* readonly returned "no-capture-maybe-returned" %a) [[NoInlineNoReturnNoUnwindReadonlyUwtable:#[0-9]*]] define dso_local i32* @ret0(i32* %a) #0 { @@ -309,7 +309,7 @@ ; ; BOTH: define dso_local i32* @calls_unknown_fn(i32* readnone returned "no-capture-maybe-returned" %r) [[NoInlineNoUnwindUwtable]] ; FNATTR: define dso_local i32* @calls_unknown_fn(i32* readnone %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] -; ATTRIBUTOR: define dso_local i32* @calls_unknown_fn(i32* returned "no-capture-maybe-returned" %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] +; ATTRIBUTOR: define dso_local i32* @calls_unknown_fn(i32* readnone returned "no-capture-maybe-returned" %r) [[NoInlineNoUnwindUwtable:#[0-9]*]] ; declare void @unknown_fn(i32* (i32*)*) #0 @@ -417,7 +417,7 @@ ; } ; ; FNATTR: define dso_local double* @bitcast(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @bitcast(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @bitcast(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] ; BOTH: define dso_local double* @bitcast(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @bitcast(i32* %b) #0 { @@ -437,7 +437,7 @@ ; } ; ; FNATTR: define dso_local double* @bitcasts_select_and_phi(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @bitcasts_select_and_phi(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @bitcasts_select_and_phi(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] ; BOTH: define dso_local double* @bitcasts_select_and_phi(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @bitcasts_select_and_phi(i32* %b) #0 { @@ -472,7 +472,7 @@ ; } ; ; FNATTR: define dso_local double* @ret_arg_arg_undef(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @ret_arg_arg_undef(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_arg_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] ; BOTH: define dso_local double* @ret_arg_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @ret_arg_arg_undef(i32* %b) #0 { @@ -507,7 +507,7 @@ ; } ; ; FNATTR: define dso_local double* @ret_undef_arg_arg(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @ret_undef_arg_arg(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_undef_arg_arg(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] ; BOTH: define dso_local double* @ret_undef_arg_arg(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @ret_undef_arg_arg(i32* %b) #0 { @@ -542,7 +542,7 @@ ; } ; ; FNATTR: define dso_local double* @ret_undef_arg_undef(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @ret_undef_arg_undef(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_undef_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoUnwindUwtable]] ; BOTH: define dso_local double* @ret_undef_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @ret_undef_arg_undef(i32* %b) #0 { diff --git a/llvm/test/Transforms/FunctionAttrs/nocapture.ll b/llvm/test/Transforms/FunctionAttrs/nocapture.ll --- a/llvm/test/Transforms/FunctionAttrs/nocapture.ll +++ b/llvm/test/Transforms/FunctionAttrs/nocapture.ll @@ -134,15 +134,14 @@ ret void } -; CHECK: define void @test1_1(i8* nocapture %x1_1, i8* nocapture %y1_1) -; It would be acceptable to add readnone to %y1_1 and %y1_2. +; CHECK: define void @test1_1(i8* nocapture readnone %x1_1, i8* nocapture readnone %y1_1) define void @test1_1(i8* %x1_1, i8* %y1_1) { call i8* @test1_2(i8* %x1_1, i8* %y1_1) store i32* null, i32** @g ret void } -; CHECK: define i8* @test1_2(i8* nocapture %x1_2, i8* returned "no-capture-maybe-returned" %y1_2) +; CHECK: define i8* @test1_2(i8* nocapture readnone %x1_2, i8* readnone returned "no-capture-maybe-returned" %y1_2) define i8* @test1_2(i8* %x1_2, i8* %y1_2) { call void @test1_1(i8* %x1_2, i8* %y1_2) store i32* null, i32** @g @@ -156,7 +155,7 @@ ret void } -; CHECK: define void @test3(i8* nocapture %x3, i8* nocapture readnone %y3, i8* nocapture %z3) +; CHECK: define void @test3(i8* nocapture readnone %x3, i8* nocapture readnone %y3, i8* nocapture readnone %z3) define void @test3(i8* %x3, i8* %y3, i8* %z3) { call void @test3(i8* %z3, i8* %y3, i8* %x3) store i32* null, i32** @g @@ -237,7 +236,7 @@ ret void } -; CHECK: @nocaptureStrip(i8* nocapture %p) +; CHECK: @nocaptureStrip(i8* nocapture writeonly %p) define void @nocaptureStrip(i8* %p) { entry: %b = call i8* @llvm.strip.invariant.group.p0i8(i8* %p) 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 @@ -32,7 +32,7 @@ ret void } -; CHECK: define void @test5(i8** nocapture %p, i8* %q) +; CHECK: define void @test5(i8** nocapture writeonly %p, i8* %q) ; Missed optz'n: we could make %q readnone, but don't break test6! define void @test5(i8** %p, i8* %q) { store i8* %q, i8** %p @@ -40,7 +40,7 @@ } declare void @test6_1() -; CHECK: define void @test6_2(i8** nocapture %p, i8* %q) +; CHECK: define void @test6_2(i8** nocapture writeonly %p, i8* %q) ; This is not a missed optz'n. define void @test6_2(i8** %p, i8* %q) { store i8* %q, i8** %p @@ -48,7 +48,7 @@ ret void } -; CHECK: define void @test7_1(i32* inalloca nocapture %a) +; CHECK: define void @test7_1(i32* inalloca nocapture readnone %a) ; inalloca parameters are always considered written define void @test7_1(i32* inalloca %a) { ret void @@ -60,7 +60,7 @@ ret i32* %p } -; CHECK: define void @test8_2(i32* nocapture %p) +; CHECK: define void @test8_2(i32* nocapture writeonly %p) define void @test8_2(i32* %p) { entry: %call = call i32* @test8_1(i32* %p)