[llvm] GlobalsModRef, ValueTracking: Look through threadlocal.address intrinsic (PR #88418)

Matthias Braun via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 17 13:34:21 PDT 2024


https://github.com/MatzeB updated https://github.com/llvm/llvm-project/pull/88418

>From 417d51065ada190287c680ef0a4ad599a8f640ea Mon Sep 17 00:00:00 2001
From: Matthias Braun <matze at braunis.de>
Date: Tue, 16 Apr 2024 17:18:11 -0700
Subject: [PATCH 1/2] Add some tests for getUnderlyingObject

---
 llvm/unittests/Analysis/ValueTrackingTest.cpp | 72 +++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 8738af91b652b8..c59ed041373001 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -1247,6 +1247,78 @@ TEST_F(ValueTrackingTest, computePtrAlignment) {
   EXPECT_EQ(getKnownAlignment(A, DL, CxtI3, &AC, &DT), Align(16));
 }
 
+TEST_F(ValueTrackingTest, getUnderlyingObjectCastsAliases) {
+  parseAssembly(R"IR(
+    @gvar = global i32 0
+    @alias = alias i32, i32* @gvar
+    @alias_interposable = weak alias i32, i32* @gvar
+    define void @test() {
+      %A = call ptr @llvm.ssa.copy.p0(ptr @gvar)
+      %A2 = call ptr @llvm.ssa.copy.p0(ptr bitcast(ptr @gvar to ptr))
+      %A3 = call ptr addrspace(42) @llvm.ssa.copy.p42(ptr addrspace(42) addrspacecast(ptr @gvar to ptr addrspace(42)))
+      %A4 = call ptr @llvm.ssa.copy.p0(ptr @alias)
+      %A5 = call ptr @llvm.ssa.copy.p0(ptr @alias_interposable)
+      ret void
+    }
+)IR");
+  Value *gvar = A->getOperand(0);
+  EXPECT_EQ(getUnderlyingObject(A->getOperand(0)), gvar);
+  EXPECT_EQ(getUnderlyingObject(A2->getOperand(0)), gvar);
+  EXPECT_EQ(getUnderlyingObject(A3->getOperand(0)), gvar);
+  EXPECT_EQ(getUnderlyingObject(A4->getOperand(0)), gvar);
+  EXPECT_EQ(getUnderlyingObject(A4->getOperand(0)), gvar);
+  // should not skip interposable alias
+  EXPECT_EQ(getUnderlyingObject(A5->getOperand(0)), A5->getOperand(0));
+}
+
+TEST_F(ValueTrackingTest, getUnderlyingObjectIntrinsics) {
+  parseAssembly(R"IR(
+    define void @test(ptr %arg) {
+      ; intrinsic with Return<> arg attribute
+      %A = call ptr @llvm.objc.retain(ptr %arg)
+      %A2 = call ptr @llvm.ssa.copy(ptr %arg)
+      ; special cased intrinsics
+      %A3 = call ptr @llvm.launder.invariant.group(ptr %arg)
+      ret void
+    }
+)IR");
+  Value *arg = F->getArg(0);
+  EXPECT_EQ(getUnderlyingObject(A), arg);
+  EXPECT_EQ(getUnderlyingObject(A2), arg);
+  EXPECT_EQ(getUnderlyingObject(A3), arg);
+}
+
+TEST_F(ValueTrackingTest, getUnderlyingObjectPtrInt) {
+  parseAssembly(R"IR(
+    define void @test(i64 %arg, ptr %arg1) {
+      %A = inttoptr i64 %arg to ptr
+      %t0 = ptrtoint ptr %arg1 to i64
+      %A2 = inttoptr i64 %t0 to ptr
+      ret void
+    }
+)IR");
+  // Should not skip anything here
+  EXPECT_EQ(getUnderlyingObject(A->getOperand(0)), A->getOperand(0));
+  EXPECT_EQ(getUnderlyingObject(A2->getOperand(0)), A2->getOperand(0));
+}
+
+TEST_F(ValueTrackingTest, getUnderlyingObjectPhi) {
+  parseAssembly(R"IR(
+    @gvar = global i32 0
+    define void @test() {
+    entry:
+      %A = call ptr @llvm.ssa.copy.p0(ptr @gvar)
+      br label %block2
+
+    block2:
+      %A2 = phi ptr [ @gvar, %entry ]
+      ret void
+    }
+)IR");
+  Value *gvar = A->getOperand(0);
+  EXPECT_EQ(getUnderlyingObject(A2->getOperand(0)), gvar);
+}
+
 TEST_F(ComputeKnownBitsTest, ComputeKnownBits) {
   parseAssembly(
       "define i32 @test(i32 %a, i32 %b) {\n"

>From 927802d39684e94423937f8e197699e88553f1d3 Mon Sep 17 00:00:00 2001
From: Matthias Braun <matze at braunis.de>
Date: Thu, 11 Apr 2024 10:33:00 -0700
Subject: [PATCH 2/2] GlobalsModRef, ValueTracking: Look through
 threadlocal.address intrinsic

---
 llvm/include/llvm/Analysis/ValueTracking.h    |  10 +-
 llvm/lib/Analysis/GlobalsModRef.cpp           |   8 ++
 llvm/lib/Analysis/ValueTracking.cpp           |   4 +
 .../GlobalsModRef/nonescaping-noalias.ll      | 134 +++++++++++++++---
 llvm/unittests/Analysis/ValueTrackingTest.cpp |  16 +++
 5 files changed, 145 insertions(+), 27 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index e1c41b3b55ccfb..bab7c8868532db 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -690,11 +690,11 @@ inline Value *getArgumentAliasingToReturnedPointer(CallBase *Call,
 bool isIntrinsicReturningPointerAliasingArgumentWithoutCapturing(
     const CallBase *Call, bool MustPreserveNullness);
 
-/// This method strips off any GEP address adjustments and pointer casts from
-/// the specified value, returning the original object being addressed. Note
-/// that the returned value has pointer type if the specified value does. If
-/// the MaxLookup value is non-zero, it limits the number of instructions to
-/// be stripped off.
+/// This method strips off any GEP address adjustments, pointer casts
+/// or `llvm.threadlocal.address` from the specified value \p V, returning the
+/// original object being addressed. Note that the returned value has pointer
+/// type if the specified value does. If the \p MaxLookup value is non-zero, it
+/// limits the number of instructions to be stripped off.
 const Value *getUnderlyingObject(const Value *V, unsigned MaxLookup = 6);
 inline Value *getUnderlyingObject(Value *V, unsigned MaxLookup = 6) {
   // Force const to avoid infinite recursion.
diff --git a/llvm/lib/Analysis/GlobalsModRef.cpp b/llvm/lib/Analysis/GlobalsModRef.cpp
index 527f19b194eeb9..1ceb1b26294183 100644
--- a/llvm/lib/Analysis/GlobalsModRef.cpp
+++ b/llvm/lib/Analysis/GlobalsModRef.cpp
@@ -344,6 +344,14 @@ bool GlobalsAAResult::AnalyzeUsesOfPointer(Value *V,
       if (AnalyzeUsesOfPointer(I, Readers, Writers, OkayStoreDest))
         return true;
     } else if (auto *Call = dyn_cast<CallBase>(I)) {
+      if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(I)) {
+        if (II->getIntrinsicID() == Intrinsic::threadlocal_address &&
+            V == II->getArgOperand(0)) {
+          if (AnalyzeUsesOfPointer(II, Readers, Writers))
+            return true;
+          continue;
+        }
+      }
       // Make sure that this is just the function being called, not that it is
       // passing into the function.
       if (Call->isDataOperand(&U)) {
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index ab2f43e1033fa1..bc4c3000ff58f8 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -6255,6 +6255,10 @@ bool llvm::isIntrinsicReturningPointerAliasingArgumentWithoutCapturing(
     return true;
   case Intrinsic::ptrmask:
     return !MustPreserveNullness;
+  case Intrinsic::threadlocal_address:
+    // The underlying variable changes with thread ID. The Thread ID may change
+    // at coroutine yield points.
+    return !Call->getParent()->getParent()->isPresplitCoroutine();
   default:
     return false;
   }
diff --git a/llvm/test/Analysis/GlobalsModRef/nonescaping-noalias.ll b/llvm/test/Analysis/GlobalsModRef/nonescaping-noalias.ll
index d109b3b8748ba7..ba83da81361d7a 100644
--- a/llvm/test/Analysis/GlobalsModRef/nonescaping-noalias.ll
+++ b/llvm/test/Analysis/GlobalsModRef/nonescaping-noalias.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 4
 ; RUN: opt < %s -aa-pipeline=basic-aa,globals-aa -passes='require<globals-aa>,gvn' -S | FileCheck %s
 ;
 ; This tests the safe no-alias conclusions of GMR -- when there is
@@ -11,10 +12,13 @@ define i32 @test1(ptr %param) {
 ; Ensure that we can fold a store to a load of a global across a store to
 ; a parameter when the global is non-escaping.
 ;
-; CHECK-LABEL: @test1(
-; CHECK: store i32 42, ptr @g1
-; CHECK-NOT: load i32
-; CHECK: ret i32 42
+; CHECK-LABEL: define i32 @test1(
+; CHECK-SAME: ptr [[PARAM:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    store i32 42, ptr @g1, align 4
+; CHECK-NEXT:    store i32 7, ptr [[PARAM]], align 4
+; CHECK-NEXT:    ret i32 42
+;
 entry:
   store i32 42, ptr @g1
   store i32 7, ptr %param
@@ -22,6 +26,67 @@ entry:
   ret i32 %v
 }
 
+ at g1_tls = internal thread_local global i32 0
+
+define i32 @test1_tls(ptr %param) {
+; Ensure that we can fold a store to a load of a global across a store to
+; a parameter when the global is non-escaping.
+;
+; CHECK-LABEL: define i32 @test1_tls(
+; CHECK-SAME: ptr [[PARAM:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[P:%.*]] = call ptr @llvm.threadlocal.address.p0(ptr @g1_tls)
+; CHECK-NEXT:    store i32 42, ptr [[P]], align 4
+; CHECK-NEXT:    store i32 7, ptr [[PARAM]], align 4
+; CHECK-NEXT:    ret i32 42
+;
+entry:
+  %p = call ptr @llvm.threadlocal.address(ptr @g1_tls)
+  store i32 42, ptr %p
+  store i32 7, ptr %param
+  %p2 = call ptr @llvm.threadlocal.address(ptr @g1_tls)
+  %v = load i32, ptr %p2
+  ret i32 %v
+}
+
+define ptr @test1_tls_noopt(ptr %coro, ptr %param) presplitcoroutine {
+; CHECK-LABEL: define ptr @test1_tls_noopt(
+; CHECK-SAME: ptr [[CORO:%.*]], ptr [[PARAM:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[P:%.*]] = call ptr @llvm.threadlocal.address.p0(ptr @g1_tls)
+; CHECK-NEXT:    store i32 42, ptr [[P]], align 4
+; CHECK-NEXT:    [[TMP0:%.*]] = call i8 @llvm.coro.suspend(token none, i1 false)
+; CHECK-NEXT:    switch i8 [[TMP0]], label [[SUSPEND:%.*]] [
+; CHECK-NEXT:      i8 0, label [[RESUME:%.*]]
+; CHECK-NEXT:      i8 1, label [[SUSPEND]]
+; CHECK-NEXT:    ]
+; CHECK:       resume:
+; CHECK-NEXT:    [[P2:%.*]] = call ptr @llvm.threadlocal.address.p0(ptr @g1_tls)
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[P2]], align 4
+; CHECK-NEXT:    store i32 [[V]], ptr [[PARAM]], align 4
+; CHECK-NEXT:    ret ptr [[CORO]]
+; CHECK:       suspend:
+; CHECK-NEXT:    [[TMP1:%.*]] = call i1 @llvm.coro.end(ptr [[CORO]], i1 false, token none)
+; CHECK-NEXT:    ret ptr [[CORO]]
+;
+entry:
+  %p = call ptr @llvm.threadlocal.address(ptr @g1_tls)
+  store i32 42, ptr %p
+
+  %0 = call i8 @llvm.coro.suspend(token none, i1 false)
+  switch i8 %0, label %suspend [i8 0, label %resume
+  i8 1, label %suspend]
+resume:
+  %p2 = call ptr @llvm.threadlocal.address(ptr @g1_tls)
+  %v = load i32, ptr %p2
+  store i32 %v, ptr %param, align 4
+  ret ptr %coro
+
+suspend:
+  call i1 @llvm.coro.end(ptr %coro, i1 0)
+  ret ptr %coro
+}
+
 declare ptr @f()
 
 define i32 @test2() {
@@ -29,10 +94,13 @@ define i32 @test2() {
 ; the pointer returned by a function call. Since the global could not escape,
 ; this function cannot be returning its address.
 ;
-; CHECK-LABEL: @test2(
-; CHECK: store i32 42, ptr @g1
-; CHECK-NOT: load i32
-; CHECK: ret i32 42
+; CHECK-LABEL: define i32 @test2() {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[PTR:%.*]] = call ptr @f() #[[ATTR3:[0-9]+]]
+; CHECK-NEXT:    store i32 42, ptr @g1, align 4
+; CHECK-NEXT:    store i32 7, ptr [[PTR]], align 4
+; CHECK-NEXT:    ret i32 42
+;
 entry:
   %ptr = call ptr @f() readnone
   store i32 42, ptr @g1
@@ -48,11 +116,13 @@ define i32 @test3() {
 ; the pointer loaded from that global. Because the global does not escape, it
 ; cannot alias a pointer loaded out of a global.
 ;
-; CHECK-LABEL: @test3(
-; CHECK: store i32 42, ptr @g1
-; CHECK: store i32 7, ptr
-; CHECK-NOT: load i32
-; CHECK: ret i32 42
+; CHECK-LABEL: define i32 @test3() {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    store i32 42, ptr @g1, align 4
+; CHECK-NEXT:    [[PTR1:%.*]] = load ptr, ptr @g2, align 8
+; CHECK-NEXT:    store i32 7, ptr [[PTR1]], align 4
+; CHECK-NEXT:    ret i32 42
+;
 entry:
   store i32 42, ptr @g1
   %ptr1 = load ptr, ptr @g2
@@ -71,10 +141,27 @@ define i32 @test4(ptr %param, i32 %n, i1 %c1, i1 %c2, i1 %c3) {
 ; Note that we can't eliminate the load here because it is used in a PHI and
 ; GVN doesn't try to do real DCE. The store is still forwarded by GVN though.
 ;
-; CHECK-LABEL: @test4(
-; CHECK: store i32 42, ptr @g1
-; CHECK: store i32 7, ptr
-; CHECK: ret i32 42
+; CHECK-LABEL: define i32 @test4(
+; CHECK-SAME: ptr [[PARAM:%.*]], i32 [[N:%.*]], i1 [[C1:%.*]], i1 [[C2:%.*]], i1 [[C3:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[CALL:%.*]] = call ptr @f()
+; CHECK-NEXT:    store i32 42, ptr @g1, align 4
+; CHECK-NEXT:    [[PTR1:%.*]] = load ptr, ptr @g2, align 8
+; CHECK-NEXT:    [[PTR2:%.*]] = select i1 [[C1]], ptr [[PTR1]], ptr [[PARAM]]
+; CHECK-NEXT:    [[PTR3:%.*]] = select i1 [[C3]], ptr [[PTR2]], ptr @g3
+; CHECK-NEXT:    [[PTR4_PRE:%.*]] = load ptr, ptr getelementptr inbounds ([10 x ptr], ptr @g4, i32 0, i32 1), align 8
+; CHECK-NEXT:    br label [[LOOP:%.*]]
+; CHECK:       loop:
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    [[PTR:%.*]] = phi ptr [ [[PTR3]], [[ENTRY]] ], [ [[PTR5:%.*]], [[LOOP]] ]
+; CHECK-NEXT:    store i32 7, ptr [[PTR]], align 4
+; CHECK-NEXT:    [[PTR5]] = select i1 [[C2]], ptr [[PTR4_PRE]], ptr [[CALL]]
+; CHECK-NEXT:    [[INC]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[TEST:%.*]] = icmp slt i32 [[INC]], [[N]]
+; CHECK-NEXT:    br i1 [[TEST]], label [[LOOP]], label [[EXIT:%.*]]
+; CHECK:       exit:
+; CHECK-NEXT:    ret i32 42
+;
 entry:
   %call = call ptr @f()
   store i32 42, ptr @g1
@@ -102,11 +189,14 @@ define i32 @test5(ptr %param) {
 ; Ensure that we can fold a store to a load of a global across a store to
 ; a parameter that has been dereferenced when the global is non-escaping.
 ;
-; CHECK-LABEL: @test5(
-; CHECK: %p = load ptr
-; CHECK: store i32 42, ptr @g1
-; CHECK-NOT: load i32
-; CHECK: ret i32 42
+; CHECK-LABEL: define i32 @test5(
+; CHECK-SAME: ptr [[PARAM:%.*]]) {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[P:%.*]] = load ptr, ptr [[PARAM]], align 8
+; CHECK-NEXT:    store i32 42, ptr @g1, align 4
+; CHECK-NEXT:    store i32 7, ptr [[P]], align 4
+; CHECK-NEXT:    ret i32 42
+;
 entry:
   %p = load ptr, ptr %param
   store i32 42, ptr @g1
diff --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index c59ed041373001..cc39e598f37fa3 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -1273,12 +1273,14 @@ TEST_F(ValueTrackingTest, getUnderlyingObjectCastsAliases) {
 
 TEST_F(ValueTrackingTest, getUnderlyingObjectIntrinsics) {
   parseAssembly(R"IR(
+    @tlsvar = thread_local global i32 0
     define void @test(ptr %arg) {
       ; intrinsic with Return<> arg attribute
       %A = call ptr @llvm.objc.retain(ptr %arg)
       %A2 = call ptr @llvm.ssa.copy(ptr %arg)
       ; special cased intrinsics
       %A3 = call ptr @llvm.launder.invariant.group(ptr %arg)
+      %A4 = call ptr @llvm.threadlocal.address(ptr @tlsvar)
       ret void
     }
 )IR");
@@ -1286,6 +1288,20 @@ TEST_F(ValueTrackingTest, getUnderlyingObjectIntrinsics) {
   EXPECT_EQ(getUnderlyingObject(A), arg);
   EXPECT_EQ(getUnderlyingObject(A2), arg);
   EXPECT_EQ(getUnderlyingObject(A3), arg);
+  Value *tlsvar = M->getNamedGlobal("tlsvar");
+  EXPECT_EQ(getUnderlyingObject(A4), tlsvar);
+}
+
+TEST_F(ValueTrackingTest, getUnderlyingObjectNoSkipTLS) {
+  parseAssembly(R"IR(
+    @tlsvar = thread_local global i32 0
+    define void @test() presplitcoroutine {
+      %A = call ptr @llvm.threadlocal.address(ptr @tlsvar)
+      ret void
+    }
+)IR");
+  // Should not skip `threadlocal.address` when functions can switch thread ids.
+  EXPECT_EQ(getUnderlyingObject(A), A);
 }
 
 TEST_F(ValueTrackingTest, getUnderlyingObjectPtrInt) {



More information about the llvm-commits mailing list