[OperandBundles] Treat "deopt" operand bundles specially
authorSanjoy Das <sanjoy@playingwithpointers.com>
Thu, 26 Nov 2015 01:16:05 +0000 (01:16 +0000)
committerSanjoy Das <sanjoy@playingwithpointers.com>
Thu, 26 Nov 2015 01:16:05 +0000 (01:16 +0000)
Teach LLVM optimize to more precisely in the presence of "deopt" operand
bundles.  "deopt" operand bundles imply that the call they're attached
to is at least `readonly` (i.e. they don't imply clobber semantics), and
they don't capture their bundle operands.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@254118 91177308-0d34-0410-b5e6-96231b3b80d8

include/llvm/IR/InstrTypes.h
test/Feature/OperandBundles/adce.ll
test/Feature/OperandBundles/basic-aa-argmemonly.ll
test/Feature/OperandBundles/dse.ll
test/Feature/OperandBundles/early-cse.ll
test/Feature/OperandBundles/function-attrs.ll

index 32dceeac3fc23b32a31e8188d10312789f431797..81de6999cdb180849d7b677c4f8dc756956ab10c 100644 (file)
@@ -21,6 +21,7 @@
 #include "llvm/IR/Attributes.h"
 #include "llvm/IR/DerivedTypes.h"
 #include "llvm/IR/Instruction.h"
+#include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/OperandTraits.h"
 
 namespace llvm {
@@ -1126,6 +1127,9 @@ struct OperandBundleUse {
   /// Currently there is no way to have attributes on operand bundles differ on
   /// a per operand granularity.
   bool operandsHaveAttr(Attribute::AttrKind A) const {
+    if (isDeoptOperandBundle())
+      return A == Attribute::ReadOnly || A == Attribute::NoCapture;
+
     // Conservative answer:  no operands have any attributes.
     return false;
   };
@@ -1144,6 +1148,11 @@ struct OperandBundleUse {
     return Tag->getValue();
   }
 
+  /// \brief Return true if this is a "deopt" operand bundle.
+  bool isDeoptOperandBundle() const {
+    return getTagID() == LLVMContext::OB_deopt;
+  }
+
 private:
   /// \brief Pointer to an entry in LLVMContextImpl::getOrInsertBundleTag.
   StringMapEntry<uint32_t> *Tag;
@@ -1361,10 +1370,16 @@ public:
   /// \brief Return true if this operand bundle user has operand bundles that
   /// may write to the heap.
   bool hasClobberingOperandBundles() const {
-    // Implementation note: this is a conservative implementation of operand
-    // bundle semantics, where *any* operand bundle forces a callsite to be
-    // read-write.
-    return hasOperandBundles();
+    for (auto &BOI : bundle_op_infos()) {
+      if (BOI.Tag->second == LLVMContext::OB_deopt)
+        continue;
+
+      // This instruction has an operand bundle that is not a "deopt" operand
+      // bundle.  Assume the worst.
+      return true;
+    }
+
+    return false;
   }
 
 protected:
index 33bb87e5fc1425ffa77e701f6e114119ff174130..a729ba7106891a6ed86fa72e9edae2f53dbf6cf2 100644 (file)
@@ -39,3 +39,11 @@ define void @test3() {
   call void @readnone_function() readnone [ "tag"() ]
   ret void
 }
+
+define void @test4() {
+; CHECK-LABEL: @test4(
+ entry:
+; CHECK-NOT: @readonly_function()
+  call void @readonly_function() [ "deopt"() ]
+  ret void
+}
index 1f3b378868e7a0add301d1cf4dcb9507b6f74b87..aa9445886060217e93ad5ba8e4dab0e3630b364a 100644 (file)
@@ -26,3 +26,26 @@ define i32 @test1(i32* %P, i32* noalias %P2) {
   ret i32 %diff
 ; CHECK: ret i32 0
 }
+
+define i32 @test2(i32* %P, i32* noalias %P2) {
+; Note: in this test we //can// GVN %v1 and %v2 into one value in theory.  Calls
+; with deopt operand bundles are not argmemonly because they *read* the entire
+; heap, but they don't write to any location in the heap if the callee does not
+; deoptimize the caller.  This fact, combined with the fact that
+; @argmemonly_function is, well, an argmemonly function, can be used to conclude
+; that %P is not written to at the callsite.  However LLVM currently cannot
+; describe the "does not write to non-args, and reads the entire heap" effect on
+; a callsite.
+
+; CHECK-LABEL: @test2(
+  %v1 = load i32, i32* %P
+; CHECK: %v1 = load i32, i32* %P
+  call void @argmemonly_function(i32* %P2) [ "deopt"() ]
+; CHECK: call void @argmemonly_function(
+  %v2 = load i32, i32* %P
+; CHECK: %v2 = load i32, i32* %P
+  %diff = sub i32 %v1, %v2
+; CHECK: %diff = sub i32 %v1, %v2
+  ret i32 %diff
+; CHECK: ret i32 %diff
+}
index cc5647a2fa0528a06d9d61e5ccf55328a9a9d7c0..9ddf7f02e384bb7f9e05225c156b21e5537e5e2f 100644 (file)
@@ -27,3 +27,36 @@ define i8* @test_1() {
 
   ret i8* %m
 }
+
+define void @test_2() {
+; Since the deopt operand bundle does not escape %m (see caveat below), it is
+; legal to elide the final store that location.
+
+; CHECK-LABEL: @test_2(
+  %m = call i8* @malloc(i32 24)
+  tail call void @f() [ "deopt"(i8* %m) ]
+  store i8 -19, i8* %m
+  ret void
+
+; CHECK:  tail call void @f() [ "deopt"(i8* %m) ]
+; CHECK-NEXT  ret void
+}
+
+define i8* @test_3() {
+; Since the deopt operand bundle does not escape %m (see caveat below), @f
+; cannot observe the stores to %m
+
+; CHECK-LABEL: @test_3(
+  %m = call i8* @malloc(i32 24)
+  tail call void @f() [ "deopt"(i8* %m) ]
+  store i8 -19, i8* %m
+  tail call void @f()
+  store i8 101, i8* %m
+  ret i8* %m
+}
+
+
+; Caveat: technically, %m can only escape if the calling function is deoptimized
+; at the call site (i.e. the call returns to the "deopt" continuation).  Since
+; the calling function body will be invalidated in that case, the calling
+; function can be optimized under the assumption that %m does not escape.
index 076ce3baeab7fdc4a01da5d6d44f326e4f065254..fc201479d8ce85bd3d7cb98c32776b9631952989 100644 (file)
@@ -2,8 +2,8 @@
 
 ; While it is normally okay to do memory optimizations over calls to
 ; @readonly_function and @readnone_function, we cannot do that if
-; they're carrying operand bundles since the presence of unknown
-; operand bundles implies arbitrary memory effects.
+; they're carrying unknown operand bundles since the presence of
+; unknown operand bundles implies arbitrary memory effects.
 
 declare void @readonly_function() readonly nounwind
 declare void @readnone_function() readnone nounwind
@@ -69,3 +69,21 @@ define void @test5(i32* %x) {
 ; CHECK: store i32 200, i32* %x
   ret void
 }
+
+define void @test6(i32* %x) {
+; The "deopt" operand bundle does not make the call to
+; @readonly_function read-write; and so the nounwind readonly call can
+; be deleted.
+
+; CHECK-LABEL: @test6(
+ entry:
+
+; CHECK-NEXT: entry:
+; CHECK-NEXT:  store i32 200, i32* %x
+; CHECK-NEXT:  ret void
+
+  store i32 100, i32* %x
+  call void @readonly_function() [ "deopt"() ]
+  store i32 200, i32* %x
+  ret void
+}
index 3d429b3e9d9c835fb8fbb019c0109d95d8d820dc..808f396fed8b363e0fb94034d90838dc31c4bde1 100644 (file)
@@ -22,3 +22,12 @@ define void @test_1(i32* %x) {
   call void @f_readnone() [ "foo"(i32* %x) ]
   ret void
 }
+
+define void @test_2(i32* %x) {
+; The "deopt" operand bundle does not capture or write to %x.
+
+; CHECK-LABEL: define void @test_2(i32* nocapture readonly %x)
+ entry:
+  call void @f_readonly() [ "deopt"(i32* %x) ]
+  ret void
+}