jdoerfert created this revision.
jdoerfert added reviewers: homerdin, hfinkel, fedor.sergeev, sanjoy, spatel, 
nlopes, nicholas, reames.
Herald added subscribers: cfe-commits, bollu, aheejin, hiraditya, eraman, 
sbc100, javed.absar, nhaehnle, jvesely.
Herald added projects: clang, LLVM.
jdoerfert added parent revisions: D59922: [Attributor] Deduce "no-capture" 
argument attribute, D59979: [Attributor][NFC] Add helper functions to deal wit 
bit-encodings.

Deduce the memory behavior, aka "read-none", "read-only", or
"write-only", for function arguments.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D59980

Files:
  clang/test/CodeGen/arm-vfp16-arguments.c
  clang/test/CodeGen/systemz-inline-asm.c
  clang/test/CodeGenCXX/wasm-args-returns.cpp
  clang/test/CodeGenObjC/os_log.m
  clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl
  clang/test/CodeGenOpenCL/amdgpu-call-kernel.cl
  clang/test/CodeGenOpenCL/kernels-have-spir-cc-by-default.cl
  llvm/include/llvm/Transforms/IPO/Attributor.h
  llvm/lib/Transforms/IPO/Attributor.cpp
  llvm/test/Transforms/FunctionAttrs/SCC1.ll
  llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
  llvm/test/Transforms/FunctionAttrs/arg_returned.ll
  llvm/test/Transforms/FunctionAttrs/nocapture.ll
  llvm/test/Transforms/FunctionAttrs/readattrs.ll

Index: llvm/test/Transforms/FunctionAttrs/readattrs.ll
===================================================================
--- llvm/test/Transforms/FunctionAttrs/readattrs.ll
+++ 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)
Index: llvm/test/Transforms/FunctionAttrs/nocapture.ll
===================================================================
--- llvm/test/Transforms/FunctionAttrs/nocapture.ll
+++ 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)
Index: llvm/test/Transforms/FunctionAttrs/arg_returned.ll
===================================================================
--- llvm/test/Transforms/FunctionAttrs/arg_returned.ll
+++ 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* "no-capture-maybe-returned" %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 {
Index: llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
===================================================================
--- llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
+++ llvm/test/Transforms/FunctionAttrs/arg_nocapture.ll
@@ -128,8 +128,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);
@@ -258,7 +257,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:
@@ -274,7 +273,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:
@@ -290,7 +289,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:
@@ -306,7 +305,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:
@@ -338,7 +337,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:
@@ -375,10 +374,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()
 
Index: llvm/test/Transforms/FunctionAttrs/SCC1.ll
===================================================================
--- llvm/test/Transforms/FunctionAttrs/SCC1.ll
+++ 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)
Index: llvm/lib/Transforms/IPO/Attributor.cpp
===================================================================
--- llvm/lib/Transforms/IPO/Attributor.cpp
+++ llvm/lib/Transforms/IPO/Attributor.cpp
@@ -50,6 +50,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<unsigned>
     MaxFixpointIterations("attributor-max-iterations", cl::Hidden,
@@ -154,6 +161,15 @@
   case Attribute::NoReturn:
     NumFnNoReturn++;
     return;
+  case Attribute::ReadNone:
+    NumFnArgumentReadNone++;
+    return;
+  case Attribute::ReadOnly:
+    NumFnArgumentReadOnly++;
+    return;
+  case Attribute::WriteOnly:
+    NumFnArgumentWriteOnly++;
+    return;
   default:
     return;
   }
@@ -961,6 +977,319 @@
                                         : 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<char, 0, 3> {
+
+  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;
+  }
+
+  /// Extract 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<Attribute> &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<Argument>(&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);
+  }
+
+  return Known;
+}
+
+void AAMemoryBehaviorImpl::getDeducedAttributes(
+    SmallVectorImpl<Attribute> &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<Argument>(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<const Use *, 32> Uses;
+
+  /// Container for visited (transitive) uses of the associated argument.
+  SmallPtrSet<const Use *, 32> VisitedUses;
+};
+
+ChangeStatus AAMemoryBehaviorArgument::manifest(Attributor &A) {
+  Argument &Arg = cast<Argument>(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<LoadInst>(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<AANoCapture>(*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<StoreInst>(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 are "OK" here because we know that the argument did
+    // not espace and the general behavior of the instruction is taken into
+    // account below.
+    if (ICS.isArgOperand(U)) {
+      unsigned ArgNo = ICS.getArgumentNo(U);
+
+      // Adjust the possible access behavior based on the information on the
+      // argument.
+      auto *ArgMemAccBehavior = A.getAAFor<AAMemoryBehaviorArgument>(
+          *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<Argument>(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<AANoCapture>(*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<Instruction>(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
 /// ----------------------------------------------------------------------------
@@ -1152,6 +1481,7 @@
     // is also derived but as a "function return attribute" (see above).
     if (Arg.getType()->isPointerTy()) {
       registerAA(*new AANoCaptureArgument(Arg));
+      registerAA(*new AAMemoryBehaviorArgument(Arg));
     }
   }
 
Index: llvm/include/llvm/Transforms/IPO/Attributor.h
===================================================================
--- llvm/include/llvm/Transforms/IPO/Attributor.h
+++ llvm/include/llvm/Transforms/IPO/Attributor.h
@@ -281,6 +281,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
 /// ----------------------------------------------------------------------------
Index: clang/test/CodeGenOpenCL/kernels-have-spir-cc-by-default.cl
===================================================================
--- clang/test/CodeGenOpenCL/kernels-have-spir-cc-by-default.cl
+++ 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;
 }
Index: clang/test/CodeGenOpenCL/amdgpu-call-kernel.cl
===================================================================
--- clang/test/CodeGenOpenCL/amdgpu-call-kernel.cl
+++ 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)
Index: clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl
===================================================================
--- clang/test/CodeGenOpenCL/amdgpu-abi-struct-coerce.cl
+++ 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 };
Index: clang/test/CodeGenObjC/os_log.m
===================================================================
--- clang/test/CodeGenObjC/os_log.m
+++ 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());
Index: clang/test/CodeGenCXX/wasm-args-returns.cpp
===================================================================
--- clang/test/CodeGenCXX/wasm-args-returns.cpp
+++ 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
Index: clang/test/CodeGen/systemz-inline-asm.c
===================================================================
--- clang/test/CodeGen/systemz-inline-asm.c
+++ 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)
Index: clang/test/CodeGen/arm-vfp16-arguments.c
===================================================================
--- clang/test/CodeGen/arm-vfp16-arguments.c
+++ 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()
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to