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 }; @@ -450,11 +450,11 @@ // CHECK: define void @func_reg_state_lo(<4 x i32> %arg0, <4 x i32> %arg1, <4 x i32> %arg2, i32 %arg3, i32 %s.coerce0, float %s.coerce1, i32 %s.coerce2) void func_reg_state_lo(int4 arg0, int4 arg1, int4 arg2, int arg3, struct_arg_t s) { } -// CHECK: define void @func_reg_state_hi(<4 x i32> %arg0, <4 x i32> %arg1, <4 x i32> %arg2, i32 %arg3, i32 %arg4, %struct.struct_arg addrspace(5)* byval nocapture align 4 %s) +// CHECK: define void @func_reg_state_hi(<4 x i32> %arg0, <4 x i32> %arg1, <4 x i32> %arg2, i32 %arg3, i32 %arg4, %struct.struct_arg addrspace(5)* byval nocapture readnone align 4 %s) void func_reg_state_hi(int4 arg0, int4 arg1, int4 arg2, int arg3, int arg4, struct_arg_t s) { } // XXX - Why don't the inner structs flatten? -// CHECK: define void @func_reg_state_num_regs_nested_struct(<4 x i32> %arg0, i32 %arg1, i32 %arg2.coerce0, %struct.nested %arg2.coerce1, i32 %arg3.coerce0, %struct.nested %arg3.coerce1, %struct.num_regs_nested_struct addrspace(5)* byval nocapture align 8 %arg4) +// CHECK: define void @func_reg_state_num_regs_nested_struct(<4 x i32> %arg0, i32 %arg1, i32 %arg2.coerce0, %struct.nested %arg2.coerce1, i32 %arg3.coerce0, %struct.nested %arg3.coerce1, %struct.num_regs_nested_struct addrspace(5)* byval nocapture readnone align 8 %arg4) void func_reg_state_num_regs_nested_struct(int4 arg0, int arg1, num_regs_nested_struct arg2, num_regs_nested_struct arg3, num_regs_nested_struct arg4) { } // CHECK: define void @func_double_nested_struct_arg(<4 x i32> %arg0, i32 %arg1, i32 %arg2.coerce0, %struct.double_nested %arg2.coerce1, i16 %arg2.coerce2) @@ -469,7 +469,7 @@ // CHECK: define void @func_large_struct_padding_arg_direct(i8 %arg.coerce0, i32 %arg.coerce1, i8 %arg.coerce2, i32 %arg.coerce3, i8 %arg.coerce4, i8 %arg.coerce5, i16 %arg.coerce6, i16 %arg.coerce7, [3 x i8] %arg.coerce8, i64 %arg.coerce9, i32 %arg.coerce10, i8 %arg.coerce11, i32 %arg.coerce12, i16 %arg.coerce13, i8 %arg.coerce14) void func_large_struct_padding_arg_direct(large_struct_padding arg) { } -// CHECK: define void @func_large_struct_padding_arg_store(%struct.large_struct_padding addrspace(1)* nocapture %out, %struct.large_struct_padding addrspace(5)* byval nocapture readonly align 8 %arg) +// CHECK: define void @func_large_struct_padding_arg_store(%struct.large_struct_padding addrspace(1)* nocapture writeonly %out, %struct.large_struct_padding addrspace(5)* byval nocapture readonly align 8 %arg) void func_large_struct_padding_arg_store(global large_struct_padding* out, large_struct_padding arg) { *out = arg; } @@ -479,7 +479,7 @@ // Function signature from blender, nothing should be passed byval. The v3i32 // should not count as 4 passed registers. -// CHECK: define void @v3i32_pair_reg_count(%struct.int3_pair addrspace(5)* nocapture %arg0, <3 x i32> %arg1.coerce0, <3 x i32> %arg1.coerce1, <3 x i32> %arg2, <3 x i32> %arg3.coerce0, <3 x i32> %arg3.coerce1, <3 x i32> %arg4, float %arg5) +// CHECK: define void @v3i32_pair_reg_count(%struct.int3_pair addrspace(5)* nocapture readnone %arg0, <3 x i32> %arg1.coerce0, <3 x i32> %arg1.coerce1, <3 x i32> %arg2, <3 x i32> %arg3.coerce0, <3 x i32> %arg3.coerce1, <3 x i32> %arg4, float %arg5) void v3i32_pair_reg_count(int3_pair *arg0, int3_pair arg1, int3 arg2, int3_pair arg3, int3 arg4, float arg5) { } // Each short4 should fit pack into 2 registers. @@ -487,7 +487,7 @@ void v4i16_reg_count(short4 arg0, short4 arg1, short4 arg2, short4 arg3, short4 arg4, short4 arg5, struct_4regs arg6) { } -// CHECK: define void @v4i16_pair_reg_count_over(<4 x i16> %arg0, <4 x i16> %arg1, <4 x i16> %arg2, <4 x i16> %arg3, <4 x i16> %arg4, <4 x i16> %arg5, <4 x i16> %arg6, %struct.struct_4regs addrspace(5)* byval nocapture align 4 %arg7) +// CHECK: define void @v4i16_pair_reg_count_over(<4 x i16> %arg0, <4 x i16> %arg1, <4 x i16> %arg2, <4 x i16> %arg3, <4 x i16> %arg4, <4 x i16> %arg5, <4 x i16> %arg6, %struct.struct_4regs addrspace(5)* byval nocapture readnone align 4 %arg7) void v4i16_pair_reg_count_over(short4 arg0, short4 arg1, short4 arg2, short4 arg3, short4 arg4, short4 arg5, short4 arg6, struct_4regs arg7) { } @@ -495,7 +495,7 @@ void v3i16_reg_count(short3 arg0, short3 arg1, short3 arg2, short3 arg3, short3 arg4, short3 arg5, struct_4regs arg6) { } -// CHECK: define void @v3i16_reg_count_over(<3 x i16> %arg0, <3 x i16> %arg1, <3 x i16> %arg2, <3 x i16> %arg3, <3 x i16> %arg4, <3 x i16> %arg5, <3 x i16> %arg6, %struct.struct_4regs addrspace(5)* byval nocapture align 4 %arg7) +// CHECK: define void @v3i16_reg_count_over(<3 x i16> %arg0, <3 x i16> %arg1, <3 x i16> %arg2, <3 x i16> %arg3, <3 x i16> %arg4, <3 x i16> %arg5, <3 x i16> %arg6, %struct.struct_4regs addrspace(5)* byval nocapture readnone align 4 %arg7) void v3i16_reg_count_over(short3 arg0, short3 arg1, short3 arg2, short3 arg3, short3 arg4, short3 arg5, short3 arg6, struct_4regs arg7) { } @@ -505,7 +505,7 @@ short2 arg8, short2 arg9, short2 arg10, short2 arg11, struct_4regs arg13) { } -// CHECK: define void @v2i16_reg_count_over(<2 x i16> %arg0, <2 x i16> %arg1, <2 x i16> %arg2, <2 x i16> %arg3, <2 x i16> %arg4, <2 x i16> %arg5, <2 x i16> %arg6, <2 x i16> %arg7, <2 x i16> %arg8, <2 x i16> %arg9, <2 x i16> %arg10, <2 x i16> %arg11, <2 x i16> %arg12, %struct.struct_4regs addrspace(5)* byval nocapture align 4 %arg13) +// CHECK: define void @v2i16_reg_count_over(<2 x i16> %arg0, <2 x i16> %arg1, <2 x i16> %arg2, <2 x i16> %arg3, <2 x i16> %arg4, <2 x i16> %arg5, <2 x i16> %arg6, <2 x i16> %arg7, <2 x i16> %arg8, <2 x i16> %arg9, <2 x i16> %arg10, <2 x i16> %arg11, <2 x i16> %arg12, %struct.struct_4regs addrspace(5)* byval nocapture readnone align 4 %arg13) void v2i16_reg_count_over(short2 arg0, short2 arg1, short2 arg2, short2 arg3, short2 arg4, short2 arg5, short2 arg6, short2 arg7, short2 arg8, short2 arg9, short2 arg10, short2 arg11, @@ -515,7 +515,7 @@ void v2i8_reg_count(char2 arg0, char2 arg1, char2 arg2, char2 arg3, char2 arg4, char2 arg5, struct_4regs arg6) { } -// CHECK: define void @v2i8_reg_count_over(<2 x i8> %arg0, <2 x i8> %arg1, <2 x i8> %arg2, <2 x i8> %arg3, <2 x i8> %arg4, <2 x i8> %arg5, i32 %arg6, %struct.struct_4regs addrspace(5)* byval nocapture align 4 %arg7) +// CHECK: define void @v2i8_reg_count_over(<2 x i8> %arg0, <2 x i8> %arg1, <2 x i8> %arg2, <2 x i8> %arg3, <2 x i8> %arg4, <2 x i8> %arg5, i32 %arg6, %struct.struct_4regs addrspace(5)* byval nocapture readnone align 4 %arg7) void v2i8_reg_count_over(char2 arg0, char2 arg1, char2 arg2, char2 arg3, char2 arg4, char2 arg5, int arg6, struct_4regs arg7) { } 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 @@ -306,6 +306,43 @@ 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 @@ -59,6 +59,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. // // In the LLVM-TS and SPEC2006, 32 seems to not induce compile time overheads @@ -195,6 +202,15 @@ case Attribute::NoRecurse: NumFnNoRecurse++; return; + case Attribute::ReadNone: + NumFnArgumentReadNone++; + return; + case Attribute::ReadOnly: + NumFnArgumentReadOnly++; + return; + case Attribute::WriteOnly: + NumFnArgumentWriteOnly++; + return; default: return; } @@ -1320,6 +1336,330 @@ : 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), IntegerState(BEST_STATE) { + 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 isKnown(NO_ACCESSES); } + + /// See AAMemoryBehavior::isAssumedReadNone(); + virtual bool isAssumedReadNone() const override { + return isAssumed(NO_ACCESSES); + } + + /// See AAMemoryBehavior::isKnownReadOnly(); + virtual bool isKnownReadOnly() const override { return isKnown(NO_WRITES); } + + /// See AAMemoryBehavior::isAssumedReadOnly(); + virtual bool isAssumedReadOnly() const override { + return isAssumed(NO_WRITES); + } + + /// See AAMemoryBehavior::isKnownWriteOnly(); + virtual bool isKnownWriteOnly() const override { return isKnown(NO_READS); } + + /// See AAMemoryBehavior::isAssumedWriteOnly(); + virtual bool isAssumedWriteOnly() const override { + return isAssumed(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 void getKnownStateFromValue(const Value &V, IntegerState &State, + bool ValueOnly); + + /// 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; } + ///} +}; + +void AAMemoryBehaviorImpl::getKnownStateFromValue(const Value &V, + IntegerState &State, + bool ValueOnly) { + + if (const Argument *Arg = dyn_cast(&V)) { + if (Arg->hasAttribute(Attribute::ReadNone)) + State.addKnownBits(NO_ACCESSES); + else if (Arg->hasAttribute(Attribute::ReadOnly)) + State.addKnownBits(NO_WRITES); + else if (Arg->hasAttribute(Attribute::WriteOnly)) + State.addKnownBits(NO_READS); + if (!ValueOnly) + getKnownStateFromValue(*Arg->getParent(), State, ValueOnly); + } else if (const Function *Fn = dyn_cast(&V)) { + if (Fn->hasFnAttribute(Attribute::ReadNone)) + State.addKnownBits(NO_ACCESSES); + else if (Fn->hasFnAttribute(Attribute::ReadOnly)) + State.addKnownBits(NO_WRITES); + else if (Fn->hasFnAttribute(Attribute::WriteOnly)) + State.addKnownBits(NO_READS); + } +} + +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) + addKnownBits(NO_ACCESSES); + else + getKnownStateFromValue(Arg, *this, /* ValueOnly */ false); + } + + /// 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. + IntegerState ExistingState; + getKnownStateFromValue(Arg, ExistingState, + /* value only */ true); + if (ExistingState.isKnown(getAssumed())) + 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); + const AANoCapture *ArgNoCaptureAA = + A.getAAFor(*this, *ICS.getCalledValue(), ArgNo); + if (ArgNoCaptureAA && ArgNoCaptureAA->getState().isValidState() && + 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. + removeAssumedBits(NO_READS); + 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()) + removeAssumedBits(NO_WRITES); + return; + + case Instruction::Call: + case Instruction::Invoke: { + // For call sites we look at the argument memory behavior attribute (this + // could be recursive!) in order to restrict our own state. + ImmutableCallSite ICS(UserI); + + // Give up on operand bundles. + if (ICS.isBundleOperand(U)) { + indicateFixpoint(/* Optimistic */ false); + return; + } + + if (ICS.isCallee(U)) + break; + + // 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". + intersectAssumedBits(ArgMemAccBehavior->getAssumed()); + 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); + IntegerState ExistingState; + getKnownStateFromValue(CalleeArg, ExistingState, + /* value only */ false); + intersectAssumedBits(ExistingState.getKnown()); + return; + } + } + break; + } + }; + + // Generally, look at the "may-properties" and adjust the assumed state. + if (UserI->mayReadFromMemory()) + removeAssumedBits(NO_READS); + if (UserI->mayWriteToMemory()) + removeAssumedBits(NO_WRITES); +} + +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()) { + const AANoCapture *ArgNoCaptureAA = A.getAAFor(*this, Arg); + if (!ArgNoCaptureAA || !ArgNoCaptureAA->getState().isValidState() || + !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); + } + + return (AssumedState != getAssumed()) ? ChangeStatus::CHANGED + : ChangeStatus::UNCHANGED; +} + /// ---------------------------------------------------------------------------- /// Attributor /// ---------------------------------------------------------------------------- @@ -1522,6 +1862,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: @@ -322,7 +321,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local void @negative_test_not_captured_but_returned_call_0b(i64* %a) +; CHECK: define dso_local void @negative_test_not_captured_but_returned_call_0b(i64* writeonly %a) ; define dso_local void @negative_test_not_captured_but_returned_call_0b(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: @@ -354,7 +353,7 @@ ; } ; ; There should *not* be a no-capture attribute on %a -; CHECK: define dso_local void @negative_test_not_captured_but_returned_call_1b(i64* %a) +; CHECK: define dso_local void @negative_test_not_captured_but_returned_call_1b(i64* writeonly %a) ; define dso_local void @negative_test_not_captured_but_returned_call_1b(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); @@ -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 @@ -411,7 +411,7 @@ ; } ; ; FNATTR: define dso_local double* @bitcast(i32* readnone %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] -; ATTRIBUTOR: define dso_local double* @bitcast(i32* returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @bitcast(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindUwtable]] ; BOTH: define dso_local double* @bitcast(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindReadnoneUwtable]] ; define dso_local double* @bitcast(i32* %b) #0 { @@ -431,7 +431,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) [[NoInlineNoRecurseNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @bitcasts_select_and_phi(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindUwtable]] ; 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 { @@ -466,7 +466,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) [[NoInlineNoRecurseNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_arg_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindUwtable]] ; 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 { @@ -501,7 +501,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) [[NoInlineNoRecurseNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_undef_arg_arg(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindUwtable]] ; 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 { @@ -536,7 +536,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) [[NoInlineNoRecurseNoUnwindUwtable]] +; ATTRIBUTOR: define dso_local double* @ret_undef_arg_undef(i32* readnone returned "no-capture-maybe-returned" %b) [[NoInlineNoRecurseNoUnwindUwtable]] ; 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 @@ -33,7 +33,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 @@ -41,7 +41,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 @@ -49,7 +49,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 @@ -61,7 +61,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)