Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SimplifyCFG][Attributes] Preserve some non-equal attrs when intersecting #111014

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

goldsteinn
Copy link
Contributor

@goldsteinn goldsteinn commented Oct 3, 2024

  • [SimplifyCFG] Add tests for preserving some not exactly equal attrs; NFC
  • [SimplifyCFG][Attributes] Preserve some non-equal attrs when intersecting

If for example we are intersecting readonly with readnone, we
don't need to drop both, we can preserve readonly.

…ting

If for example we are intersecting `readonly` with `readnone`, we
don't need to drop both, we can preserve `readonly`.
@llvmbot
Copy link
Collaborator

llvmbot commented Oct 3, 2024

@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-llvm-transforms

Author: None (goldsteinn)

Changes
  • [SimplifyCFG] Add tests for preserving some not exactly equal attrs; NFC
  • [SimplifyCFG][Attributes] Preserve some non-equal attrs when intersecting

Full diff: https://github.com/llvm/llvm-project/pull/111014.diff

3 Files Affected:

  • (modified) llvm/lib/IR/Attributes.cpp (+50-5)
  • (modified) llvm/test/Transforms/SimplifyCFG/hoist-cb-diff-attrs.ll (+25-2)
  • (modified) llvm/test/Transforms/SimplifyCFG/sink-cb-diff-attrs.ll (+27-2)
diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index f2ba61ae51039e..aa5fdb48b74643 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -973,20 +973,28 @@ AttributeSet::intersectWith(LLVMContext &C, AttributeSet Other) const {
     // Loop through all attributes in both this and Other in sorted order. If
     // the attribute is only present in one of the sets, it will be set in
     // Attr0. If it is present in both sets both Attr0 and Attr1 will be set.
+    // If the attr is only present in one of the sets, WithoutCur will point to
+    // the set that doesn't contain Attr0.
     Attribute Attr0, Attr1;
-    if (ItBegin1 == ItEnd1)
+    const AttributeSet *WithoutCur = nullptr;
+    if (ItBegin1 == ItEnd1) {
+      WithoutCur = &Other;
       Attr0 = *ItBegin0++;
-    else if (ItBegin0 == ItEnd0)
+    } else if (ItBegin0 == ItEnd0) {
+      WithoutCur = this;
       Attr0 = *ItBegin1++;
-    else {
+    } else {
       int Cmp = ItBegin0->cmpKind(*ItBegin1);
       if (Cmp == 0) {
         Attr0 = *ItBegin0++;
         Attr1 = *ItBegin1++;
-      } else if (Cmp < 0)
+      } else if (Cmp < 0) {
+        WithoutCur = &Other;
         Attr0 = *ItBegin0++;
-      else
+      } else {
+        WithoutCur = this;
         Attr0 = *ItBegin1++;
+      }
     }
     assert(Attr0.isValid() && "Iteration should always yield a valid attr");
 
@@ -1011,14 +1019,51 @@ AttributeSet::intersectWith(LLVMContext &C, AttributeSet Other) const {
     // If we don't have both attributes, then fail if the attribute is
     // must-preserve or drop it otherwise.
     if (!Attr1.isValid()) {
+      assert(WithoutCur != nullptr &&
+             "Iteration with only one valid attr didn't set WithoutCur");
       if (Attribute::intersectMustPreserve(Kind))
         return std::nullopt;
+
+      // We can preserve some attributes without it being present in both this
+      // and Other. For example the intersection between ReadOnly and ReadNone
+      // is ReadOnly.
+      // NB: MemoryEffects don't need this extra logic. The `&` operator already
+      // does this type of intersection and missing memory effects implies
+      // unknown which we can't union.
+      switch (Kind) {
+        // ReadOnly + ReadNone -> ReadOnly
+      case Attribute::ReadOnly:
+        if (WithoutCur->hasAttribute(Attribute::ReadNone))
+          Intersected.addAttribute(Attr0);
+        break;
+        // ReadNone + ReadOnly -> ReadNone
+      case Attribute::ReadNone:
+        if (WithoutCur->hasAttribute(Attribute::ReadOnly))
+          Intersected.addAttribute(Attribute::ReadOnly);
+        break;
+        // Dereferenceable + DereferenceableOrNull -> DereferenceableOrNull
+      case Attribute::DereferenceableOrNull:
+      case Attribute::Dereferenceable: {
+        Attribute::AttrKind OtherKind = Kind == Attribute::Dereferenceable
+                                            ? Attribute::DereferenceableOrNull
+                                            : Attribute::Dereferenceable;
+        Attr1 = WithoutCur->getAttribute(OtherKind);
+        if (Attr1.isValid()) {
+          uint64_t NewVal =
+              std::min(Attr0.getValueAsInt(), Attr1.getValueAsInt());
+          Intersected.addRawIntAttr(Attribute::DereferenceableOrNull, NewVal);
+        }
+      } break;
+      default:
+        break;
+      }
       continue;
     }
 
     // We have both attributes so apply the intersection rule.
     assert(Attr1.hasKindAsEnum() && Kind == Attr1.getKindAsEnum() &&
            "Iterator picked up two different attributes in the same iteration");
+    assert(WithoutCur == nullptr && "Iteration two valid attrs set WithoutCur");
 
     // Attribute we can intersect with "and"
     if (Attribute::intersectWithAnd(Kind)) {
diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-cb-diff-attrs.ll b/llvm/test/Transforms/SimplifyCFG/hoist-cb-diff-attrs.ll
index ee6c0cfd43b253..079a86cf321ea0 100644
--- a/llvm/test/Transforms/SimplifyCFG/hoist-cb-diff-attrs.ll
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-cb-diff-attrs.ll
@@ -30,7 +30,7 @@ else:
 define ptr @test_hoist_int_attrs2(i1 %c, ptr %p, i64 %x) {
 ; CHECK-LABEL: define {{[^@]+}}@test_hoist_int_attrs2
 ; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) {
-; CHECK-NEXT:    [[R:%.*]] = call ptr @foo(ptr dereferenceable(50) [[P]], i64 range(i64 10, 1000) [[X]]) #[[ATTR1:[0-9]+]]
+; CHECK-NEXT:    [[R:%.*]] = call ptr @foo(ptr dereferenceable(50) dereferenceable_or_null(50) [[P]], i64 range(i64 10, 1000) [[X]]) #[[ATTR1:[0-9]+]]
 ; CHECK-NEXT:    br i1 [[C]], label [[COMMON_RET:%.*]], label [[ELSE:%.*]]
 ; CHECK:       common.ret:
 ; CHECK-NEXT:    ret ptr [[R]]
@@ -49,10 +49,33 @@ else:
   ret ptr %r2
 }
 
+define ptr @test_hoist_int_attrs3(i1 %c, ptr %p, ptr %p2, i64 %x) {
+; CHECK-LABEL: define {{[^@]+}}@test_hoist_int_attrs3
+; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], ptr [[P2:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT:    [[R:%.*]] = call ptr @foo2(ptr align 32 dereferenceable_or_null(100) [[P]], ptr align 32 dereferenceable_or_null(100) [[P2]], i64 range(i64 10, 100000) [[X]]) #[[ATTR0]]
+; CHECK-NEXT:    br i1 [[C]], label [[COMMON_RET:%.*]], label [[ELSE:%.*]]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret ptr [[R]]
+; CHECK:       else:
+; CHECK-NEXT:    call void @side.effect()
+; CHECK-NEXT:    br label [[COMMON_RET]]
+;
+  br i1 %c, label %if, label %else
+if:
+  %r = call ptr @foo2(ptr align 64 dereferenceable(100) %p, ptr dereferenceable(500) align 64 %p2, i64 range(i64 10, 1000) %x) memory(read)
+  ret ptr %r
+
+else:
+  %r2 = call ptr @foo2(ptr align 32 dereferenceable_or_null(200) %p, ptr dereferenceable_or_null(100) align 32 %p2, i64 range(i64 10000, 100000) %x) memory(write)
+  call void @side.effect()
+  ret ptr %r2
+}
+
+
 define ptr @test_hoist_bool_attrs2(i1 %c, ptr %p, i64 %x) {
 ; CHECK-LABEL: define {{[^@]+}}@test_hoist_bool_attrs2
 ; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) {
-; CHECK-NEXT:    [[R:%.*]] = call noundef ptr @foo(ptr nonnull [[P]], i64 noundef [[X]]) #[[ATTR2:[0-9]+]]
+; CHECK-NEXT:    [[R:%.*]] = call noundef ptr @foo(ptr nonnull readonly [[P]], i64 noundef [[X]]) #[[ATTR2:[0-9]+]]
 ; CHECK-NEXT:    br i1 [[C]], label [[COMMON_RET:%.*]], label [[ELSE:%.*]]
 ; CHECK:       common.ret:
 ; CHECK-NEXT:    ret ptr [[R]]
diff --git a/llvm/test/Transforms/SimplifyCFG/sink-cb-diff-attrs.ll b/llvm/test/Transforms/SimplifyCFG/sink-cb-diff-attrs.ll
index fe9d87080dd111..bb9a43aa48b02f 100644
--- a/llvm/test/Transforms/SimplifyCFG/sink-cb-diff-attrs.ll
+++ b/llvm/test/Transforms/SimplifyCFG/sink-cb-diff-attrs.ll
@@ -93,7 +93,7 @@ define ptr @test_sink_int_attrs2(i1 %c, ptr %p, i64 %x) {
 ; CHECK-NEXT:    call void @side.effect()
 ; CHECK-NEXT:    br label [[END]]
 ; CHECK:       end:
-; CHECK-NEXT:    [[R2:%.*]] = call ptr @foo(ptr dereferenceable(50) [[P]], i64 range(i64 10, 1000) [[X]]) #[[ATTR2:[0-9]+]]
+; CHECK-NEXT:    [[R2:%.*]] = call ptr @foo(ptr dereferenceable(50) dereferenceable_or_null(50) [[P]], i64 range(i64 10, 1000) [[X]]) #[[ATTR2:[0-9]+]]
 ; CHECK-NEXT:    ret ptr [[R2]]
 ;
   br i1 %c, label %if, label %else
@@ -110,6 +110,31 @@ end:
   ret ptr %pr
 }
 
+define ptr @test_sink_int_attrs3(i1 %c, ptr %p, i64 %x) {
+; CHECK-LABEL: define {{[^@]+}}@test_sink_int_attrs3
+; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) {
+; CHECK-NEXT:    br i1 [[C]], label [[IF:%.*]], label [[END:%.*]]
+; CHECK:       if:
+; CHECK-NEXT:    call void @side.effect()
+; CHECK-NEXT:    br label [[END]]
+; CHECK:       end:
+; CHECK-NEXT:    [[R2:%.*]] = call ptr @foo(ptr dereferenceable_or_null(50) [[P]], i64 range(i64 10, 1000) [[X]]) #[[ATTR2]]
+; CHECK-NEXT:    ret ptr [[R2]]
+;
+  br i1 %c, label %if, label %else
+if:
+  call void @side.effect()
+  %r = call ptr @foo(ptr dereferenceable_or_null(50) %p, i64 range(i64 10, 1000) %x) memory(read)
+  br label %end
+
+else:
+  %r2 = call ptr @foo(ptr dereferenceable(100) align 32 %p, i64 range(i64 11, 100) %x) memory(none)
+  br label %end
+end:
+  %pr = phi ptr [ %r, %if], [%r2, %else]
+  ret ptr %pr
+}
+
 define ptr @test_sink_bool_attrs2(i1 %c, ptr %p, i64 %x) {
 ; CHECK-LABEL: define {{[^@]+}}@test_sink_bool_attrs2
 ; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) {
@@ -118,7 +143,7 @@ define ptr @test_sink_bool_attrs2(i1 %c, ptr %p, i64 %x) {
 ; CHECK-NEXT:    call void @side.effect()
 ; CHECK-NEXT:    br label [[END]]
 ; CHECK:       end:
-; CHECK-NEXT:    [[R2:%.*]] = call noundef ptr @foo(ptr nonnull [[P]], i64 noundef [[X]]) #[[ATTR3:[0-9]+]]
+; CHECK-NEXT:    [[R2:%.*]] = call noundef ptr @foo(ptr nonnull readonly [[P]], i64 noundef [[X]]) #[[ATTR3:[0-9]+]]
 ; CHECK-NEXT:    ret ptr [[R2]]
 ;
   br i1 %c, label %if, label %else

@goldsteinn goldsteinn changed the title goldsteinn/attrs preserve non eq [SimplifyCFG][Attributes] Preserve some non-equal attrs when intersecting Oct 3, 2024
@goldsteinn
Copy link
Contributor Author

(Sorry realized I missed some of the deref + deref_or_null cases before).

@goldsteinn
Copy link
Contributor Author

NB This appears to currently be a no-op, although once we integrate this into more places it might be nice to have :)

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not willing to take this if it doesn't have an effect.

The readnone problem should be resolved by replacing it with readonly writeonly, which will also address this pain point in many other places, not just here.

@goldsteinn
Copy link
Contributor Author

Not willing to take this if it doesn't have an effect.

The readnone problem should be resolved by replacing it with readonly writeonly, which will also address this pain point in many other places, not just here.

Yeah that would help elsewhere.
Any thoughts on adding memoryeffects to params?

@nikic
Copy link
Contributor

nikic commented Oct 9, 2024

Any thoughts on adding memoryeffects to params?

MemoryEffects is the combination of Location + ModRef. For arguments, specifying the location is always "argmem", so we only need to specify the ModRef information, which is encoded by readonly/writeonly. So I don't think using MemoryEffects on arguments would make sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants