[llvm] [SPIR-V] Add legalize-addrspace-cast pass (PR #137598)

Nathan Gauër via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 28 02:01:12 PDT 2025


https://github.com/Keenuts created https://github.com/llvm/llvm-project/pull/137598

This commit adds a new pass in the backend which propagates the
addrspace of the pointers down to the last use, making sure the
addrspace remains consistent, and thus stripping any addrspacecast.
This is required to lower LLVM-IR to logical SPIR-V, which does not
support generic pointers.

This is now required as HLSL emits several address spaces, and thus
addrspacecasts in some cases:

Example 1: resource access

```llvm
%handle = tail call target("spirv.VulkanBuffer", ...)
%rptr = @llvm.spv.resource.getpointer(%handle, ...);
%cptr = addrspacecast ptr addrspace(11) %rptr to ptr
%fptr = load i32, ptr %cptr
```

Example 2: object methods

```llvm
define void @objectmethod(ptr %this) {
}

define void @foo(ptr addrspace(11) %object) {
  call void @objectmethod(ptr addrspacecast(addrspace(11) %object to ptr));
}
```

This is waiting on https://github.com/llvm/llvm-project/pull/135794

>From 4135a6382dc27963d71dd7aa300141b217527449 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Mon, 7 Apr 2025 17:39:10 +0200
Subject: [PATCH 1/2] [IPO] Prevent removal of some convergent attr

When a callee is marked as `convergent`, some targets like HLSL/SPIR-V
add a convergent token to the call.
This is valid if both functions are marked as `convergent`.

ADCE/BDCE and other DCE passes were allowed to remove convergence
intrinsics when their token were unused.
This meant a leaf function could lose all its convergence intrinsics.
This would allow further optimization to remove the `convergent`
attribute from the callee.
Issue was the caller was not updated, and we now had a convergence token
attached to a call function calling a non-convergent function.

This commit limits the removal of the `convergent` attribute when only
users are calls without convergence intrinsics.
If any other use is found (either a call to a convergent, or something
else like taking the function address), the removal is prevented.
---
 .../Transforms/IPO/AttributorAttributes.cpp   |  21 ++
 llvm/lib/Transforms/IPO/FunctionAttrs.cpp     |  21 +-
 llvm/test/Transforms/ADCE/convergence.ll      |  92 +++++++++
 llvm/test/Transforms/Attributor/convergent.ll | 186 ++++++++++++++----
 llvm/test/Transforms/BDCE/convergence.ll      |  62 ++++++
 .../Transforms/FunctionAttrs/convergent.ll    |  34 ++++
 6 files changed, 379 insertions(+), 37 deletions(-)
 create mode 100644 llvm/test/Transforms/ADCE/convergence.ll
 create mode 100644 llvm/test/Transforms/BDCE/convergence.ll

diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index ac56df3823e20..89f29b75b2142 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -2902,6 +2902,22 @@ struct AANonConvergentImpl : public AANonConvergent {
   }
 };
 
+static bool FunctionCalledWithConvergenceToken(const Function *F) {
+  for (auto &Use : F->uses()) {
+    CallBase *CB = dyn_cast<CallBase>(Use.getUser());
+    if (!CB)
+      continue;
+
+    // We are not called, just used as an argument.
+    if (CB->getCalledFunction() != F)
+      continue;
+
+    if (CB->getConvergenceControlToken())
+      return true;
+  }
+  return false;
+}
+
 struct AANonConvergentFunction final : AANonConvergentImpl {
   AANonConvergentFunction(const IRPosition &IRP, Attributor &A)
       : AANonConvergentImpl(IRP, A) {}
@@ -2929,6 +2945,11 @@ struct AANonConvergentFunction final : AANonConvergentImpl {
                                            UsedAssumedInformation)) {
       return indicatePessimisticFixpoint();
     }
+
+    const Function *F = this->getIRPosition().getAssociatedFunction();
+    if (FunctionCalledWithConvergenceToken(F))
+      return indicatePessimisticFixpoint();
+
     return ChangeStatus::UNCHANGED;
   }
 
diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index bbfed2ac2c090..a98b4b7fbfc03 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -1864,6 +1864,23 @@ static bool InstrBreaksNonConvergent(Instruction &I,
          !SCCNodes.contains(CB->getCalledFunction());
 }
 
+static bool FunctionRequiresConvergence(const Function *F) {
+  for (auto &Use : F->uses()) {
+    CallBase *CB = dyn_cast<CallBase>(Use.getUser());
+    if (!CB)
+      return true;
+
+    // We are not called, just used as an argument.
+    if (CB->getCalledFunction() != F)
+      continue;
+
+    if (CB->getConvergenceControlToken())
+      return true;
+  }
+
+  return false;
+}
+
 /// Helper for NoUnwind inference predicate InstrBreaksAttribute.
 static bool InstrBreaksNonThrowing(Instruction &I, const SCCNodeSet &SCCNodes) {
   if (!I.mayThrow(/* IncludePhaseOneUnwind */ true))
@@ -1967,7 +1984,9 @@ static void inferConvergent(const SCCNodeSet &SCCNodes,
   AI.registerAttrInference(AttributeInferer::InferenceDescriptor{
       Attribute::Convergent,
       // Skip non-convergent functions.
-      [](const Function &F) { return !F.isConvergent(); },
+      [](const Function &F) {
+        return !F.isConvergent() || FunctionRequiresConvergence(&F);
+      },
       // Instructions that break non-convergent assumption.
       [SCCNodes](Instruction &I) {
         return InstrBreaksNonConvergent(I, SCCNodes);
diff --git a/llvm/test/Transforms/ADCE/convergence.ll b/llvm/test/Transforms/ADCE/convergence.ll
new file mode 100644
index 0000000000000..5d7b9bd65c821
--- /dev/null
+++ b/llvm/test/Transforms/ADCE/convergence.ll
@@ -0,0 +1,92 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt %s -passes=adce -S | FileCheck %s
+
+; CHECK:      Function Attrs: convergent
+define i32 @foo(i32 %a) #0 {
+; CHECK-LABEL: define i32 @foo(
+; CHECK-SAME: i32 [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    ret i32 [[A]]
+;
+entry:
+  %tk = call token @llvm.experimental.convergence.entry()
+  ret i32 %a
+}
+
+; CHECK:      Function Attrs: convergent
+define void @bar() #0 {
+; CHECK-LABEL: define void @bar(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  %tk = call token @llvm.experimental.convergence.anchor()
+  ret void
+}
+
+; CHECK:      Function Attrs: convergent
+define void @baz() #0 {
+; CHECK-LABEL: define void @baz(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[HEADER:.*]]
+; CHECK:       [[HEADER]]:
+; CHECK-NEXT:    br i1 true, label %[[BODY:.*]], label %[[EXIT:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    br label %[[HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %tk0 = call token @llvm.experimental.convergence.entry()
+  br label %header
+
+header:
+  %tk1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %tk0) ]
+  br i1 true, label %body, label %exit
+
+body:
+  br label %header
+
+exit:
+  ret void
+}
+
+define void @indirect_inner() #0 {
+; CHECK-LABEL: define void @indirect_inner(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  %tk0 = call token @llvm.experimental.convergence.entry()
+  ret void
+}
+
+define void @indirect() #0 {
+; CHECK-LABEL: define void @indirect(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TK0:%.*]] = call token @llvm.experimental.convergence.entry()
+; CHECK-NEXT:    [[VAR:%.*]] = alloca ptr, align 8
+; CHECK-NEXT:    store ptr @indirect_inner, ptr [[VAR]], align 8
+; CHECK-NEXT:    [[PTR:%.*]] = load ptr, ptr [[VAR]], align 8
+; CHECK-NEXT:    call void [[PTR]]() #[[ATTR0]] [ "convergencectrl"(token [[TK0]]) ]
+; CHECK-NEXT:    ret void
+;
+entry:
+  %tk0 = call token @llvm.experimental.convergence.entry()
+  %var = alloca ptr, align 8
+  store ptr @indirect_inner, ptr %var, align 8
+  %ptr = load ptr, ptr %var, align 8
+  call void %ptr() convergent [ "convergencectrl"(token %tk0) ]
+  ret void
+}
+
+declare token @llvm.experimental.convergence.entry() #1
+declare token @llvm.experimental.convergence.anchor() #1
+declare token @llvm.experimental.convergence.loop() #1
+
+attributes #0 = { convergent }
+attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
diff --git a/llvm/test/Transforms/Attributor/convergent.ll b/llvm/test/Transforms/Attributor/convergent.ll
index 702d0bb0a9e63..f4ae905f4bd14 100644
--- a/llvm/test/Transforms/Attributor/convergent.ll
+++ b/llvm/test/Transforms/Attributor/convergent.ll
@@ -1,11 +1,11 @@
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
-; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal  -attributor-annotate-decl-cs  -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
-; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal  -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals all --version 5
+; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal  -attributor-annotate-decl-cs  -S %s | FileCheck %s --check-prefixes=CHECK,TUNIT
+; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal  -attributor-annotate-decl-cs -S %s | FileCheck %s --check-prefixes=CHECK,CGSCC
 
 define i32 @defined() convergent {
 ; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
-; CHECK-LABEL: define {{[^@]+}}@defined
-; CHECK-SAME: () #[[ATTR0:[0-9]+]] {
+; CHECK-LABEL: define noundef i32 @defined(
+; CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
 ; CHECK-NEXT:    ret i32 1
 ;
   ret i32 1
@@ -13,14 +13,14 @@ define i32 @defined() convergent {
 
 define i32 @calls_defined() convergent {
 ; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
-; TUNIT-LABEL: define {{[^@]+}}@calls_defined
-; TUNIT-SAME: () #[[ATTR0]] {
+; TUNIT-LABEL: define noundef i32 @calls_defined(
+; TUNIT-SAME: ) #[[ATTR0]] {
 ; TUNIT-NEXT:    ret i32 1
 ;
 ; CGSCC: Function Attrs: convergent mustprogress nofree nosync nounwind willreturn memory(none)
-; CGSCC-LABEL: define {{[^@]+}}@calls_defined
-; CGSCC-SAME: () #[[ATTR1:[0-9]+]] {
-; CGSCC-NEXT:    [[A:%.*]] = call noundef i32 @defined() #[[ATTR6:[0-9]+]]
+; CGSCC-LABEL: define noundef i32 @calls_defined(
+; CGSCC-SAME: ) #[[ATTR1:[0-9]+]] {
+; CGSCC-NEXT:    [[A:%.*]] = call noundef i32 @defined() #[[ATTR7:[0-9]+]]
 ; CGSCC-NEXT:    ret i32 [[A]]
 ;
   %a = call i32 @defined()
@@ -30,7 +30,7 @@ define i32 @calls_defined() convergent {
 declare void @declared_non_convergent()
 
 define void @calls_declared_non_convergent() convergent {
-; CHECK-LABEL: define {{[^@]+}}@calls_declared_non_convergent() {
+; CHECK-LABEL: define void @calls_declared_non_convergent() {
 ; CHECK-NEXT:    call void @declared_non_convergent()
 ; CHECK-NEXT:    ret void
 ;
@@ -43,14 +43,14 @@ declare i32 @declared_convergent() convergent
 
 define i32 @calls_declared_convergent() convergent {
 ; TUNIT: Function Attrs: convergent
-; TUNIT-LABEL: define {{[^@]+}}@calls_declared_convergent
-; TUNIT-SAME: () #[[ATTR1:[0-9]+]] {
+; TUNIT-LABEL: define i32 @calls_declared_convergent(
+; TUNIT-SAME: ) #[[ATTR1:[0-9]+]] {
 ; TUNIT-NEXT:    [[A:%.*]] = call i32 @declared_convergent()
 ; TUNIT-NEXT:    ret i32 [[A]]
 ;
 ; CGSCC: Function Attrs: convergent
-; CGSCC-LABEL: define {{[^@]+}}@calls_declared_convergent
-; CGSCC-SAME: () #[[ATTR2:[0-9]+]] {
+; CGSCC-LABEL: define i32 @calls_declared_convergent(
+; CGSCC-SAME: ) #[[ATTR2:[0-9]+]] {
 ; CGSCC-NEXT:    [[A:%.*]] = call i32 @declared_convergent()
 ; CGSCC-NEXT:    ret i32 [[A]]
 ;
@@ -58,9 +58,120 @@ define i32 @calls_declared_convergent() convergent {
   ret i32 %a
 }
 
+; Function declared convergent. Body not provided to prevent inlining
+; of the functions below.
+declare i32 @direct_call_convergent_attribute_with_token_declared(i32 %a)
+
+; This function could be declared non-convergent as it only calls
+; non-convergent functions, but it is being called with a convergence token,
+; hence it is not allowed to drop the convergence attribute.
+define i32 @direct_call_convergent_attribute_with_token_inner(i32 %a) convergent {
+; TUNIT: Function Attrs: convergent
+; TUNIT-LABEL: define i32 @direct_call_convergent_attribute_with_token_inner(
+; TUNIT-SAME: i32 [[A:%.*]]) #[[ATTR1]] {
+; TUNIT-NEXT:    [[RS:%.*]] = call i32 @direct_call_convergent_attribute_with_token_declared(i32 [[A]])
+; TUNIT-NEXT:    ret i32 [[RS]]
+;
+; CGSCC: Function Attrs: convergent
+; CGSCC-LABEL: define i32 @direct_call_convergent_attribute_with_token_inner(
+; CGSCC-SAME: i32 [[A:%.*]]) #[[ATTR2]] {
+; CGSCC-NEXT:    [[RS:%.*]] = call i32 @direct_call_convergent_attribute_with_token_declared(i32 [[A]])
+; CGSCC-NEXT:    ret i32 [[RS]]
+;
+  %rs = call i32 @direct_call_convergent_attribute_with_token_declared(i32 %a)
+  ret i32 %rs
+}
+
+define i32 @direct_call_convergent_attribute_with_token(i32 %a) convergent {
+; TUNIT: Function Attrs: convergent
+; TUNIT-LABEL: define i32 @direct_call_convergent_attribute_with_token(
+; TUNIT-SAME: i32 [[A:%.*]]) #[[ATTR1]] {
+; TUNIT-NEXT:    [[TK:%.*]] = call token @llvm.experimental.convergence.entry() #[[ATTR6:[0-9]+]]
+; TUNIT-NEXT:    [[RS:%.*]] = call i32 @direct_call_convergent_attribute_with_token_inner(i32 [[A]]) [ "convergencectrl"(token [[TK]]) ]
+; TUNIT-NEXT:    ret i32 [[RS]]
+;
+; CGSCC: Function Attrs: convergent
+; CGSCC-LABEL: define i32 @direct_call_convergent_attribute_with_token(
+; CGSCC-SAME: i32 [[A:%.*]]) #[[ATTR2]] {
+; CGSCC-NEXT:    [[TK:%.*]] = call token @llvm.experimental.convergence.entry() #[[ATTR8:[0-9]+]]
+; CGSCC-NEXT:    [[RS:%.*]] = call i32 @direct_call_convergent_attribute_with_token_inner(i32 [[A]]) [ "convergencectrl"(token [[TK]]) ]
+; CGSCC-NEXT:    ret i32 [[RS]]
+;
+  %tk = call token @llvm.experimental.convergence.entry()
+  %rs = call i32 @direct_call_convergent_attribute_with_token_inner(i32 %a) [ "convergencectrl"(token %tk) ]
+  ret i32 %rs
+}
+
+; Function declared non-convergent.
+declare i32 @indirect_call_declared(i32 %a)
+
+; Function declared convergent, but which is not required to be
+; marked convergent: the only used is through a call which is marked
+; as convergent, hence the function attribute can be dropped.
+define i32 @indirect_call_inner(i32 %a) convergent {
+; CHECK-LABEL: define i32 @indirect_call_inner(
+; CHECK-SAME: i32 [[A:%.*]]) {
+; CHECK-NEXT:    [[RS:%.*]] = call i32 @indirect_call_declared(i32 [[A]])
+; CHECK-NEXT:    ret i32 [[RS]]
+;
+  %rs = call i32 @indirect_call_declared(i32 %a)
+  ret i32 %rs
+}
+
+; Convergent function, calling a function and marking the call as convergent.
+define i32 @indirect_call(i32 %a) convergent {
+; TUNIT: Function Attrs: convergent
+; TUNIT-LABEL: define i32 @indirect_call(
+; TUNIT-SAME: i32 [[A:%.*]]) #[[ATTR1]] {
+; TUNIT-NEXT:    [[TK:%.*]] = call token @llvm.experimental.convergence.entry() #[[ATTR7:[0-9]+]]
+; TUNIT-NEXT:    [[RS:%.*]] = call i32 @indirect_call_inner(i32 [[A]]) #[[ATTR1]] [ "convergencectrl"(token [[TK]]) ]
+; TUNIT-NEXT:    ret i32 [[RS]]
+;
+; CGSCC: Function Attrs: convergent
+; CGSCC-LABEL: define i32 @indirect_call(
+; CGSCC-SAME: i32 [[A:%.*]]) #[[ATTR2]] {
+; CGSCC-NEXT:    [[TK:%.*]] = call token @llvm.experimental.convergence.entry() #[[ATTR7]]
+; CGSCC-NEXT:    [[RS:%.*]] = call i32 @indirect_call_inner(i32 [[A]]) #[[ATTR2]] [ "convergencectrl"(token [[TK]]) ]
+; CGSCC-NEXT:    ret i32 [[RS]]
+;
+  %tk = call token @llvm.experimental.convergence.entry()
+  %fptr = alloca ptr
+  store ptr @indirect_call_inner, ptr %fptr
+  %ic = load ptr, ptr %fptr
+  %rs = call i32 %ic(i32 %a) convergent [ "convergencectrl"(token %tk) ]
+  ret i32 %rs
+}
+
+; A non-convergent declaration.
+declare i32 @leaf_convergent_indirect_declared(i32 %a)
+
+; A function calling indirectly a non-convergent function.
+define i32 @leaf_convergent_indirect(i32 %a) convergent {
+; TUNIT: Function Attrs: convergent
+; TUNIT-LABEL: define i32 @leaf_convergent_indirect(
+; TUNIT-SAME: i32 [[A:%.*]]) #[[ATTR1]] {
+; TUNIT-NEXT:    [[TK:%.*]] = call token @llvm.experimental.convergence.entry() #[[ATTR7]]
+; TUNIT-NEXT:    [[RS:%.*]] = call i32 @leaf_convergent_indirect_declared(i32 [[A]]) #[[ATTR1]] [ "convergencectrl"(token [[TK]]) ]
+; TUNIT-NEXT:    ret i32 [[RS]]
+;
+; CGSCC: Function Attrs: convergent
+; CGSCC-LABEL: define i32 @leaf_convergent_indirect(
+; CGSCC-SAME: i32 [[A:%.*]]) #[[ATTR2]] {
+; CGSCC-NEXT:    [[TK:%.*]] = call token @llvm.experimental.convergence.entry() #[[ATTR7]]
+; CGSCC-NEXT:    [[RS:%.*]] = call i32 @leaf_convergent_indirect_declared(i32 [[A]]) #[[ATTR2]] [ "convergencectrl"(token [[TK]]) ]
+; CGSCC-NEXT:    ret i32 [[RS]]
+;
+  %tk = call token @llvm.experimental.convergence.entry()
+  %fptr = alloca ptr
+  store ptr @leaf_convergent_indirect_declared, ptr %fptr
+  %ic = load ptr, ptr %fptr
+  %rs = call i32 %ic(i32 %a) convergent [ "convergencectrl"(token %tk) ]
+  ret i32 %rs
+}
+
 define i32 @defined_with_asm(i32 %a, i32 %b) {
-; CHECK-LABEL: define {{[^@]+}}@defined_with_asm
-; CHECK-SAME: (i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-LABEL: define i32 @defined_with_asm(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
 ; CHECK-NEXT:    [[RESULT:%.*]] = add i32 [[A]], [[B]]
 ; CHECK-NEXT:    [[ASM_RESULT:%.*]] = call i32 asm sideeffect "addl $1, $0", "=r,r"(i32 [[RESULT]])
 ; CHECK-NEXT:    ret i32 [[ASM_RESULT]]
@@ -72,14 +183,14 @@ define i32 @defined_with_asm(i32 %a, i32 %b) {
 
 define i32 @calls_defined_with_asm(i32 %a, i32 %b) convergent {
 ; TUNIT: Function Attrs: convergent
-; TUNIT-LABEL: define {{[^@]+}}@calls_defined_with_asm
-; TUNIT-SAME: (i32 [[A:%.*]], i32 [[B:%.*]]) #[[ATTR1]] {
+; TUNIT-LABEL: define i32 @calls_defined_with_asm(
+; TUNIT-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) #[[ATTR1]] {
 ; TUNIT-NEXT:    [[C:%.*]] = call i32 @defined_with_asm(i32 [[A]], i32 [[B]])
 ; TUNIT-NEXT:    ret i32 [[C]]
 ;
 ; CGSCC: Function Attrs: convergent
-; CGSCC-LABEL: define {{[^@]+}}@calls_defined_with_asm
-; CGSCC-SAME: (i32 [[A:%.*]], i32 [[B:%.*]]) #[[ATTR2]] {
+; CGSCC-LABEL: define i32 @calls_defined_with_asm(
+; CGSCC-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) #[[ATTR2]] {
 ; CGSCC-NEXT:    [[C:%.*]] = call i32 @defined_with_asm(i32 [[A]], i32 [[B]])
 ; CGSCC-NEXT:    ret i32 [[C]]
 ;
@@ -91,15 +202,15 @@ declare void @llvm.convergent.copy.p0.p0.i64(ptr %dest, ptr %src, i64 %size, i1
 
 define void @calls_convergent_intrinsic(ptr %dest, ptr %src, i64 %size) convergent {
 ; TUNIT: Function Attrs: convergent mustprogress nofree nosync nounwind willreturn memory(argmem: readwrite)
-; TUNIT-LABEL: define {{[^@]+}}@calls_convergent_intrinsic
-; TUNIT-SAME: (ptr nofree [[DEST:%.*]], ptr nofree [[SRC:%.*]], i64 [[SIZE:%.*]]) #[[ATTR3:[0-9]+]] {
-; TUNIT-NEXT:    call void @llvm.convergent.copy.p0.p0.i64(ptr nofree [[DEST]], ptr nofree [[SRC]], i64 [[SIZE]], i1 noundef false) #[[ATTR5:[0-9]+]]
+; TUNIT-LABEL: define void @calls_convergent_intrinsic(
+; TUNIT-SAME: ptr nofree [[DEST:%.*]], ptr nofree [[SRC:%.*]], i64 [[SIZE:%.*]]) #[[ATTR3:[0-9]+]] {
+; TUNIT-NEXT:    call void @llvm.convergent.copy.p0.p0.i64(ptr nofree [[DEST]], ptr nofree [[SRC]], i64 [[SIZE]], i1 noundef false) #[[ATTR6]]
 ; TUNIT-NEXT:    ret void
 ;
 ; CGSCC: Function Attrs: convergent mustprogress nofree nosync nounwind willreturn memory(argmem: readwrite)
-; CGSCC-LABEL: define {{[^@]+}}@calls_convergent_intrinsic
-; CGSCC-SAME: (ptr nofree [[DEST:%.*]], ptr nofree [[SRC:%.*]], i64 [[SIZE:%.*]]) #[[ATTR4:[0-9]+]] {
-; CGSCC-NEXT:    call void @llvm.convergent.copy.p0.p0.i64(ptr nofree [[DEST]], ptr nofree [[SRC]], i64 [[SIZE]], i1 noundef false) #[[ATTR7:[0-9]+]]
+; CGSCC-LABEL: define void @calls_convergent_intrinsic(
+; CGSCC-SAME: ptr nofree [[DEST:%.*]], ptr nofree [[SRC:%.*]], i64 [[SIZE:%.*]]) #[[ATTR4:[0-9]+]] {
+; CGSCC-NEXT:    call void @llvm.convergent.copy.p0.p0.i64(ptr nofree [[DEST]], ptr nofree [[SRC]], i64 [[SIZE]], i1 noundef false) #[[ATTR8]]
 ; CGSCC-NEXT:    ret void
 ;
   call void @llvm.convergent.copy.p0.p0.i64(ptr %dest, ptr %src, i64 %size, i1 false)
@@ -110,15 +221,15 @@ declare void @llvm.memcpy.p0.p0.i64(ptr %dest, ptr %src, i64 %size, i1 %isVolati
 
 define void @calls_intrinsic(ptr %dest, ptr %src, i64 %size) convergent {
 ; TUNIT: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: readwrite)
-; TUNIT-LABEL: define {{[^@]+}}@calls_intrinsic
-; TUNIT-SAME: (ptr nofree writeonly captures(none) [[DEST:%.*]], ptr nofree readonly captures(none) [[SRC:%.*]], i64 [[SIZE:%.*]]) #[[ATTR2:[0-9]+]] {
-; TUNIT-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr nofree writeonly captures(none) [[DEST]], ptr nofree readonly captures(none) [[SRC]], i64 [[SIZE]], i1 noundef false) #[[ATTR5]]
+; TUNIT-LABEL: define void @calls_intrinsic(
+; TUNIT-SAME: ptr nofree writeonly captures(none) [[DEST:%.*]], ptr nofree readonly captures(none) [[SRC:%.*]], i64 [[SIZE:%.*]]) #[[ATTR2:[0-9]+]] {
+; TUNIT-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr nofree writeonly captures(none) [[DEST]], ptr nofree readonly captures(none) [[SRC]], i64 [[SIZE]], i1 noundef false) #[[ATTR6]]
 ; TUNIT-NEXT:    ret void
 ;
 ; CGSCC: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: readwrite)
-; CGSCC-LABEL: define {{[^@]+}}@calls_intrinsic
-; CGSCC-SAME: (ptr nofree writeonly captures(none) [[DEST:%.*]], ptr nofree readonly captures(none) [[SRC:%.*]], i64 [[SIZE:%.*]]) #[[ATTR3:[0-9]+]] {
-; CGSCC-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr nofree writeonly captures(none) [[DEST]], ptr nofree readonly captures(none) [[SRC]], i64 [[SIZE]], i1 noundef false) #[[ATTR7]]
+; CGSCC-LABEL: define void @calls_intrinsic(
+; CGSCC-SAME: ptr nofree writeonly captures(none) [[DEST:%.*]], ptr nofree readonly captures(none) [[SRC:%.*]], i64 [[SIZE:%.*]]) #[[ATTR3:[0-9]+]] {
+; CGSCC-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr nofree writeonly captures(none) [[DEST]], ptr nofree readonly captures(none) [[SRC]], i64 [[SIZE]], i1 noundef false) #[[ATTR8]]
 ; CGSCC-NEXT:    ret void
 ;
   call void @llvm.memcpy.p0.p0.i64(ptr %dest, ptr %src, i64 %size, i1 false)
@@ -133,7 +244,9 @@ attributes #0 = { convergent mustprogress nofree norecurse nosync nounwind willr
 ; TUNIT: attributes #[[ATTR2]] = { convergent mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: readwrite) }
 ; TUNIT: attributes #[[ATTR3]] = { convergent mustprogress nofree nosync nounwind willreturn memory(argmem: readwrite) }
 ; TUNIT: attributes #[[ATTR4:[0-9]+]] = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
-; TUNIT: attributes #[[ATTR5]] = { nofree willreturn }
+; TUNIT: attributes #[[ATTR5:[0-9]+]] = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
+; TUNIT: attributes #[[ATTR6]] = { nofree willreturn }
+; TUNIT: attributes #[[ATTR7]] = { nofree nosync willreturn }
 ;.
 ; CGSCC: attributes #[[ATTR0]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
 ; CGSCC: attributes #[[ATTR1]] = { convergent mustprogress nofree nosync nounwind willreturn memory(none) }
@@ -141,6 +254,7 @@ attributes #0 = { convergent mustprogress nofree norecurse nosync nounwind willr
 ; CGSCC: attributes #[[ATTR3]] = { convergent mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: readwrite) }
 ; CGSCC: attributes #[[ATTR4]] = { convergent mustprogress nofree nosync nounwind willreturn memory(argmem: readwrite) }
 ; CGSCC: attributes #[[ATTR5:[0-9]+]] = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
-; CGSCC: attributes #[[ATTR6]] = { nofree nosync willreturn }
-; CGSCC: attributes #[[ATTR7]] = { nofree willreturn }
+; CGSCC: attributes #[[ATTR6:[0-9]+]] = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
+; CGSCC: attributes #[[ATTR7]] = { nofree nosync willreturn }
+; CGSCC: attributes #[[ATTR8]] = { nofree willreturn }
 ;.
diff --git a/llvm/test/Transforms/BDCE/convergence.ll b/llvm/test/Transforms/BDCE/convergence.ll
new file mode 100644
index 0000000000000..51f279925b9f7
--- /dev/null
+++ b/llvm/test/Transforms/BDCE/convergence.ll
@@ -0,0 +1,62 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt %s -passes=bdce -S | FileCheck %s
+
+; CHECK:      Function Attrs: convergent
+define i32 @foo(i32 %a) #0 {
+; CHECK-LABEL: define i32 @foo(
+; CHECK-SAME: i32 [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    ret i32 [[A]]
+;
+entry:
+  %tk0 = call token @llvm.experimental.convergence.entry()
+  ret i32 %a
+}
+
+; CHECK:      Function Attrs: convergent
+define void @bar() #0 {
+; CHECK-LABEL: define void @bar(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    ret void
+;
+entry:
+  %tk0 = call token @llvm.experimental.convergence.anchor()
+  ret void
+}
+
+; CHECK:      Function Attrs: convergent
+define void @baz() #0 {
+; CHECK-LABEL: define void @baz(
+; CHECK-SAME: ) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br label %[[HEADER:.*]]
+; CHECK:       [[HEADER]]:
+; CHECK-NEXT:    br i1 true, label %[[BODY:.*]], label %[[EXIT:.*]]
+; CHECK:       [[BODY]]:
+; CHECK-NEXT:    br label %[[HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  %tk0 = call token @llvm.experimental.convergence.entry()
+  br label %header
+
+header:
+  %tk1 = call token @llvm.experimental.convergence.loop() [ "convergencectrl"(token %tk0) ]
+  br i1 true, label %body, label %exit
+
+body:
+  br label %header
+
+exit:
+  ret void
+}
+
+declare token @llvm.experimental.convergence.entry() #1
+declare token @llvm.experimental.convergence.anchor() #1
+declare token @llvm.experimental.convergence.loop() #1
+
+attributes #0 = { convergent }
+attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }
+
diff --git a/llvm/test/Transforms/FunctionAttrs/convergent.ll b/llvm/test/Transforms/FunctionAttrs/convergent.ll
index fe8029d39d924..20292a01febae 100644
--- a/llvm/test/Transforms/FunctionAttrs/convergent.ll
+++ b/llvm/test/Transforms/FunctionAttrs/convergent.ll
@@ -1,5 +1,7 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes
 ; RUN: opt -passes=function-attrs -S < %s | FileCheck %s
+; RUN: opt -passes=function-attrs -S < %s | FileCheck %s
+; RUN: opt -passes=function-attrs --aa-pipeline=basic-aa -S %s | FileCheck %s
 
 define i32 @nonleaf() convergent {
 ; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
@@ -129,3 +131,35 @@ define i32 @noopt_friend() convergent {
   %a = call i32 @noopt()
   ret i32 0
 }
+
+; A function which should normally be stripped of its convergent attribute,
+; but because it's used in a controlled convergence call, the attribute
+; remains.
+; This could be improved by propagating the non-convergence outside of the
+; function, but for the time being, we stop when we encounter this scenario.
+define i32 @leaf_noconvergent_used() convergent {
+; CHECK: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CHECK-LABEL: define {{[^@]+}}@leaf_noconvergent_used
+; CHECK-SAME: () #[[ATTR7:[0-9]+]] {
+; CHECK-NEXT:    ret i32 0
+;
+  ret i32 0
+}
+
+define i32 @nonleaf_convergent() convergent {
+; CHECK: Function Attrs: convergent mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CHECK-LABEL: define {{[^@]+}}@nonleaf_convergent
+; CHECK-SAME: () #[[ATTR7]] {
+; CHECK-NEXT:    [[TMP1:%.*]] = call token @llvm.experimental.convergence.entry()
+; CHECK-NEXT:    [[TMP2:%.*]] = call i32 @leaf_noconvergent_used() [ "convergencectrl"(token [[TMP1]]) ]
+; CHECK-NEXT:    ret i32 0
+;
+  %1 = call token @llvm.experimental.convergence.entry()
+  %2 = call i32 @leaf_noconvergent_used() [ "convergencectrl"(token %1) ]
+  ret i32 0
+}
+
+
+declare token @llvm.experimental.convergence.entry() #1
+
+attributes #1 = { convergent nocallback nofree nosync nounwind willreturn memory(none) }

>From 5cbc182850447209944aa485bf926cb122716e86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nathan=20Gau=C3=ABr?= <brioche at google.com>
Date: Thu, 3 Apr 2025 15:07:25 +0200
Subject: [PATCH 2/2] [SPIR-V] Add legalize-addrspace-cast pass

This commit adds a new pass in the backend which propagates the
addrspace of the pointers down to the last use, making sure the
addrspace remains consistent, and thus stripping any addrspacecast.
This is required to lower LLVM-IR to logical SPIR-V, which does not
support generic pointers.

This is now required as HLSL emits several address spaces, and thus
addrspacecasts in some cases:

Example 1: resource access

```llvm
%handle = tail call target("spirv.VulkanBuffer", ...)
%rptr = @llvm.spv.resource.getpointer(%handle, ...);
%cptr = addrspacecast ptr addrspace(11) %rptr to ptr
%fptr = load i32, ptr %cptr
```

Example 2: object methods

```llvm
define void @objectMethod(ptr %this) {
}

define void @foo(ptr addrspace(11) %object) {
  call void @objectMethod(ptr addrspacecast(addrspace(11) %object to ptr));
}
```
---
 llvm/lib/Target/SPIRV/CMakeLists.txt          |   1 +
 llvm/lib/Target/SPIRV/SPIRV.h                 |   1 +
 .../SPIRV/SPIRVLegalizeAddrspaceCast.cpp      | 141 ++++++++++++++++++
 llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp  |   2 +
 .../SPIRV/pointers/pointer-addrspacecast.ll   |  36 +++++
 .../pointers/resource-addrspacecast-2.ll      |  54 +++++++
 .../SPIRV/pointers/resource-addrspacecast.ll  |  37 +++++
 7 files changed, 272 insertions(+)
 create mode 100644 llvm/lib/Target/SPIRV/SPIRVLegalizeAddrspaceCast.cpp
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/pointer-addrspacecast.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/resource-addrspacecast-2.ll
 create mode 100644 llvm/test/CodeGen/SPIRV/pointers/resource-addrspacecast.ll

diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 4a2b534b948d6..8cc709ab2892c 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -28,6 +28,7 @@ add_llvm_target(SPIRVCodeGen
   SPIRVInstructionSelector.cpp
   SPIRVStripConvergentIntrinsics.cpp
   SPIRVLegalizePointerCast.cpp
+  SPIRVLegalizeAddrspaceCast.cpp
   SPIRVMergeRegionExitTargets.cpp
   SPIRVISelLowering.cpp
   SPIRVLegalizerInfo.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index 51728d1aa678d..63b3f8ee2b467 100644
--- a/llvm/lib/Target/SPIRV/SPIRV.h
+++ b/llvm/lib/Target/SPIRV/SPIRV.h
@@ -24,6 +24,7 @@ FunctionPass *createSPIRVStructurizerPass();
 FunctionPass *createSPIRVMergeRegionExitTargetsPass();
 FunctionPass *createSPIRVStripConvergenceIntrinsicsPass();
 FunctionPass *createSPIRVLegalizePointerCastPass(SPIRVTargetMachine *TM);
+FunctionPass *createSPIRVLegalizeAddrspaceCastPass(SPIRVTargetMachine *TM);
 FunctionPass *createSPIRVRegularizerPass();
 FunctionPass *createSPIRVPreLegalizerCombiner();
 FunctionPass *createSPIRVPreLegalizerPass();
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeAddrspaceCast.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeAddrspaceCast.cpp
new file mode 100644
index 0000000000000..f99dd3c428b6a
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeAddrspaceCast.cpp
@@ -0,0 +1,141 @@
+//===-- SPIRVLegalizeAddrspaceCast.cpp ----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "SPIRV.h"
+#include "SPIRVSubtarget.h"
+#include "SPIRVTargetMachine.h"
+#include "SPIRVUtils.h"
+#include "llvm/CodeGen/IntrinsicLowering.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Intrinsics.h"
+#include "llvm/IR/IntrinsicsSPIRV.h"
+#include "llvm/Transforms/Utils/Cloning.h"
+#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
+
+using namespace llvm;
+
+namespace llvm {
+void initializeSPIRVLegalizeAddrspaceCastPass(PassRegistry &);
+}
+
+class SPIRVLegalizeAddrspaceCast : public FunctionPass {
+
+public:
+  SPIRVLegalizeAddrspaceCast(SPIRVTargetMachine *TM)
+      : FunctionPass(ID), TM(TM) {
+    initializeSPIRVLegalizeAddrspaceCastPass(*PassRegistry::getPassRegistry());
+  };
+
+  void gatherAddrspaceCast(Function &F) {
+    WorkList.clear();
+    std::vector<User *> ToVisit;
+    for (auto &BB : F)
+      for (auto &I : BB)
+        ToVisit.push_back(&I);
+
+    std::unordered_set<User *> Visited;
+    while (ToVisit.size() > 0) {
+      User *I = ToVisit.back();
+      ToVisit.pop_back();
+      if (Visited.count(I) != 0)
+        continue;
+      Visited.insert(I);
+
+      if (AddrSpaceCastInst *AI = dyn_cast<AddrSpaceCastInst>(I))
+        WorkList.insert(AI);
+      else if (auto *AO = dyn_cast<AddrSpaceCastOperator>(I))
+        WorkList.insert(AO);
+
+      for (auto &O : I->operands())
+        if (User *U = dyn_cast<User>(&O))
+          ToVisit.push_back(U);
+    }
+  }
+
+  void propagateAddrspace(User *U) {
+    if (!U->getType()->isPointerTy())
+      return;
+
+    if (AddrSpaceCastOperator *AO = dyn_cast<AddrSpaceCastOperator>(U)) {
+      for (auto &Use : AO->uses())
+        WorkList.insert(Use.getUser());
+
+      AO->mutateType(AO->getPointerOperand()->getType());
+      AO->replaceAllUsesWith(AO->getPointerOperand());
+      DeadUsers.insert(AO);
+      return;
+    }
+
+    if (AddrSpaceCastInst *AC = dyn_cast<AddrSpaceCastInst>(U)) {
+      for (auto &Use : AC->uses())
+        WorkList.insert(Use.getUser());
+
+      AC->mutateType(AC->getPointerOperand()->getType());
+      AC->replaceAllUsesWith(AC->getPointerOperand());
+      return;
+    }
+
+    PointerType *NewType = nullptr;
+    for (Use &U : U->operands()) {
+      PointerType *PT = dyn_cast<PointerType>(U.get()->getType());
+      if (!PT)
+        continue;
+
+      if (NewType == nullptr)
+        NewType = PT;
+      else {
+        // We could imagine a function calls taking 2 pointers to distinct
+        // address spaces which returns a pointer. But we want to run this
+        // pass after inlining, so we'll assume this doesn't happen.
+        assert(NewType->getAddressSpace() == PT->getAddressSpace());
+      }
+    }
+
+    assert(NewType != nullptr);
+    U->mutateType(NewType);
+  }
+
+  virtual bool runOnFunction(Function &F) override {
+    const SPIRVSubtarget &ST = TM->getSubtarget<SPIRVSubtarget>(F);
+    GR = ST.getSPIRVGlobalRegistry();
+
+    DeadUsers.clear();
+    gatherAddrspaceCast(F);
+
+    while (WorkList.size() > 0) {
+      User *U = *WorkList.begin();
+      WorkList.erase(U);
+      propagateAddrspace(U);
+    }
+
+    for (User *U : DeadUsers) {
+      if (Instruction *I = dyn_cast<Instruction>(U))
+        I->eraseFromParent();
+    }
+    return DeadUsers.size() != 0;
+  }
+
+private:
+  SPIRVTargetMachine *TM = nullptr;
+  SPIRVGlobalRegistry *GR = nullptr;
+  std::unordered_set<User *> WorkList;
+  std::unordered_set<User *> DeadUsers;
+
+public:
+  static char ID;
+};
+
+char SPIRVLegalizeAddrspaceCast::ID = 0;
+INITIALIZE_PASS(SPIRVLegalizeAddrspaceCast, "spirv-legalize-addrspacecast",
+                "SPIRV legalize addrspacecast", false, false)
+
+FunctionPass *
+llvm::createSPIRVLegalizeAddrspaceCastPass(SPIRVTargetMachine *TM) {
+  return new SPIRVLegalizeAddrspaceCast(TM);
+}
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 68286737b972f..b4c13d879b58e 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -190,6 +190,8 @@ void SPIRVPassConfig::addIRPasses() {
   TargetPassConfig::addIRPasses();
 
   if (TM.getSubtargetImpl()->isVulkanEnv()) {
+    addPass(createSPIRVLegalizeAddrspaceCastPass(&getTM<SPIRVTargetMachine>()));
+
     // 1.  Simplify loop for subsequent transformations. After this steps, loops
     // have the following properties:
     //  - loops have a single entry edge (pre-header to loop header).
diff --git a/llvm/test/CodeGen/SPIRV/pointers/pointer-addrspacecast.ll b/llvm/test/CodeGen/SPIRV/pointers/pointer-addrspacecast.ll
new file mode 100644
index 0000000000000..4d5549dfab8d9
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/pointer-addrspacecast.ll
@@ -0,0 +1,36 @@
+; RUN: llc -verify-machineinstrs -O3 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O3 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
+
+; CHECK-DAG:                     %[[#uint:]] = OpTypeInt 32 0
+; CHECK-DAG:                   %[[#uint_0:]] = OpConstant %[[#uint]] 0
+; CHECK-DAG:                 %[[#ptr_uint:]] = OpTypePointer Private %[[#uint]]
+; CHECK-DAG:                      %[[#var:]] = OpVariable %[[#ptr_uint]] Private %[[#uint_0]]
+
+; CHECK-DAG:  OpName %[[#func_simple:]] "simple"
+; CHECK-DAG:  OpName %[[#func_chain:]] "chain"
+
+ at global = internal addrspace(10) global i32 zeroinitializer
+
+define void @simple() {
+; CHECK: %[[#func_simple]] = OpFunction
+entry:
+  %ptr = getelementptr i32, ptr addrspace(10) @global, i32 0
+  %casted = addrspacecast ptr addrspace(10) %ptr to ptr
+  %val = load i32, ptr %casted
+; CHECK: %{{.*}} = OpLoad %[[#uint]] %[[#var]] Aligned 4
+  ret void
+}
+
+define void @chain() {
+; CHECK: %[[#func_chain]] = OpFunction
+entry:
+  %a = getelementptr i32, ptr addrspace(10) @global, i32 0
+  %b = addrspacecast ptr addrspace(10) %a to ptr
+  %c = getelementptr i32, ptr %b, i32 0
+  %d = addrspacecast ptr %c to ptr addrspace(10)
+  %e = addrspacecast ptr addrspace(10) %d to ptr
+
+  %val = load i32, ptr %e
+; CHECK: %{{.*}} = OpLoad %[[#uint]] %[[#var]] Aligned 4
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/pointers/resource-addrspacecast-2.ll b/llvm/test/CodeGen/SPIRV/pointers/resource-addrspacecast-2.ll
new file mode 100644
index 0000000000000..93208c16ed4a5
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/resource-addrspacecast-2.ll
@@ -0,0 +1,54 @@
+; RUN: llc -verify-machineinstrs -O3 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - | FileCheck %s --match-full-lines
+; RUN: %if spirv-tools %{ llc -O3 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
+
+; FIXME(134119): enable-this once Offset decoration are added.
+; XFAIL: spirv-tools
+
+%S2 = type { { [10 x { i32, i32 } ] }, i32 }
+
+; CHECK-DAG:                     %[[#uint:]] = OpTypeInt 32 0
+; CHECK-DAG:                   %[[#uint_0:]] = OpConstant %[[#uint]] 0
+; CHECK-DAG:                   %[[#uint_1:]] = OpConstant %[[#uint]] 1
+; CHECK-DAG:                   %[[#uint_3:]] = OpConstant %[[#uint]] 3
+; CHECK-DAG:                  %[[#uint_10:]] = OpConstant %[[#uint]] 10
+; CHECK-DAG:                  %[[#uint_11:]] = OpConstant %[[#uint]] 11
+; CHECK-DAG:   %[[#ptr_StorageBuffer_uint:]] = OpTypePointer StorageBuffer %[[#uint]]
+
+; CHECK-DAG:       %[[#t_s2_s_a_s:]] = OpTypeStruct %[[#uint]] %[[#uint]]
+; CHECK-DAG:       %[[#t_s2_s_a:]] = OpTypeArray %[[#t_s2_s_a_s]] %[[#uint_10]]
+; CHECK-DAG:       %[[#t_s2_s:]] = OpTypeStruct %[[#t_s2_s_a]]
+; CHECK-DAG:       %[[#t_s2:]] = OpTypeStruct %[[#t_s2_s]] %[[#uint]]
+
+; CHECK-DAG: %[[#ptr_StorageBuffer_struct:]] = OpTypePointer StorageBuffer %[[#t_s2]]
+; CHECK-DAG:                     %[[#rarr:]] = OpTypeRuntimeArray %[[#t_s2]]
+; CHECK-DAG:              %[[#rarr_struct:]] = OpTypeStruct %[[#rarr]]
+; CHECK-DAG:       %[[#spirv_VulkanBuffer:]] = OpTypePointer StorageBuffer %[[#rarr_struct]]
+
+declare target("spirv.VulkanBuffer", [0 x %S2], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0s_Ss_12_1t(i32, i32, i32, i32, i1)
+
+define void @main() "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" {
+entry:
+  %handle = tail call target("spirv.VulkanBuffer", [0 x %S2], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0s_Ss_12_1t(i32 0, i32 0, i32 1, i32 0, i1 false)
+; CHECK:      %[[#resource:]] = OpVariable %[[#spirv_VulkanBuffer]] StorageBuffer
+
+  %ptr = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0s_Ss_12_1t(target("spirv.VulkanBuffer", [0 x %S2], 12, 1) %handle, i32 0)
+; CHECK: %[[#a:]] = OpCopyObject %[[#spirv_VulkanBuffer]] %[[#resource]]
+; CHECK: %[[#b:]] = OpAccessChain %[[#ptr_StorageBuffer_struct]] %[[#a:]] %[[#uint_0]] %[[#uint_0]]
+  %casted = addrspacecast ptr addrspace(11) %ptr to ptr
+
+; CHECK: %[[#ptr2:]] = OpInBoundsAccessChain %[[#ptr_StorageBuffer_uint]] %[[#b:]] %[[#uint_0]] %[[#uint_0]] %[[#uint_3]] %[[#uint_1]]
+  %ptr2 = getelementptr inbounds %S2, ptr %casted, i64 0, i32 0, i32 0, i32 3, i32 1
+
+; CHECK: OpStore %[[#ptr2]] %[[#uint_10]] Aligned 4
+  store i32 10, ptr %ptr2, align 4
+
+; Another store, but this time using LLVM's ability to load the first element
+; without an explicit GEP. The backend has to determine the ptr type and
+; generate the appropriate access chain.
+; CHECK: %[[#ptr3:]] = OpInBoundsAccessChain %[[#ptr_StorageBuffer_uint]] %[[#b:]] %[[#uint_0]] %[[#uint_0]] %[[#uint_0]] %[[#uint_0]]
+; CHECK: OpStore %[[#ptr3]] %[[#uint_11]] Aligned 4
+  store i32 11, ptr %casted, align 4
+  ret void
+}
+
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0s_S2s_12_1t(target("spirv.VulkanBuffer", [0 x %S2], 12, 1), i32)
diff --git a/llvm/test/CodeGen/SPIRV/pointers/resource-addrspacecast.ll b/llvm/test/CodeGen/SPIRV/pointers/resource-addrspacecast.ll
new file mode 100644
index 0000000000000..24a50c7177340
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/pointers/resource-addrspacecast.ll
@@ -0,0 +1,37 @@
+; RUN: llc -verify-machineinstrs -O3 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O3 -mtriple=spirv-unknown-vulkan1.3-compute %s -o - -filetype=obj | spirv-val %}
+
+; FIXME(134119): enable-this once Offset decoration are added.
+; XFAIL: spirv-tools
+
+%struct.S = type { i32 }
+
+; CHECK-DAG:                     %[[#uint:]] = OpTypeInt 32 0
+; CHECK-DAG:                   %[[#uint_0:]] = OpConstant %[[#uint]] 0
+; CHECK-DAG:                  %[[#uint_10:]] = OpConstant %[[#uint]] 10
+; CHECK-DAG:   %[[#ptr_StorageBuffer_uint:]] = OpTypePointer StorageBuffer %[[#uint]]
+; CHECK-DAG:                   %[[#struct:]] = OpTypeStruct %[[#uint]]
+; CHECK-DAG: %[[#ptr_StorageBuffer_struct:]] = OpTypePointer StorageBuffer %[[#struct]]
+; CHECK-DAG:                     %[[#rarr:]] = OpTypeRuntimeArray %[[#struct]]
+; CHECK-DAG:              %[[#rarr_struct:]] = OpTypeStruct %[[#rarr]]
+; CHECK-DAG:       %[[#spirv_VulkanBuffer:]] = OpTypePointer StorageBuffer %[[#rarr_struct]]
+
+declare target("spirv.VulkanBuffer", [0 x %struct.S], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0s_struct.Ss_12_1t(i32, i32, i32, i32, i1)
+
+define void @main() "hlsl.numthreads"="1,1,1" "hlsl.shader"="compute" {
+entry:
+  %handle = tail call target("spirv.VulkanBuffer", [0 x %struct.S], 12, 1) @llvm.spv.resource.handlefrombinding.tspirv.VulkanBuffer_a0s_struct.Ss_12_1t(i32 0, i32 0, i32 1, i32 0, i1 false)
+; CHECK:      %[[#resource:]] = OpVariable %[[#spirv_VulkanBuffer]] StorageBuffer
+
+  %ptr = tail call noundef align 4 dereferenceable(4) ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0s_struct.Ss_12_1t(target("spirv.VulkanBuffer", [0 x %struct.S], 12, 1) %handle, i32 0)
+; CHECK: %[[#a:]] = OpCopyObject %[[#spirv_VulkanBuffer]] %[[#resource]]
+; CHECK: %[[#b:]] = OpAccessChain %[[#ptr_StorageBuffer_struct]] %[[#a:]] %[[#uint_0]] %[[#uint_0]]
+; CHECK: %[[#c:]] = OpInBoundsAccessChain %[[#ptr_StorageBuffer_uint]] %[[#b:]] %[[#uint_0]]
+  %casted = addrspacecast ptr addrspace(11) %ptr to ptr
+
+; CHECK: OpStore %[[#c]] %[[#uint_10]] Aligned 4
+  store i32 10, ptr %casted, align 4
+  ret void
+}
+
+declare ptr addrspace(11) @llvm.spv.resource.getpointer.p11.tspirv.VulkanBuffer_a0s_struct.Ss_12_1t(target("spirv.VulkanBuffer", [0 x %struct.S], 12, 1), i32)



More information about the llvm-commits mailing list