https://github.com/pvelesko updated 
https://github.com/llvm/llvm-project/pull/212452

>From de41c147e83f1d50b69fd970fef96dfb405f0b3d Mon Sep 17 00:00:00 2001
From: Paulius Velesko <[email protected]>
Date: Tue, 28 Jul 2026 13:13:35 +0300
Subject: [PATCH] [clang][SPIR-V] Emit C++ vtable components in the generic
 address space

A vtable slot holds the address of a function. Functions live in the
program address space, so materialising a slot needs an addrspacecast from
the program address space into the address space the components live in,
which is the default globals address space. On SPIR-V that produces

  addrspacecast (ptr @fn to ptr addrspace(1))

and SPIR-V only permits a cast into the generic address space, so the
module is rejected with "Casts from private/local/global address space are
allowed only to generic". Device side C++ virtual dispatch is therefore not
expressible today.

Emit the components as generic pointers on SPIR-V targets whose program
address space is neither the globals address space nor the generic one.
Casting the globals address space RTTI components into the generic address
space is the permitted direction.

The component address space is unchanged everywhere else, including
spirv64-amd-amdhsa, whose functions already live in the generic address
space, and logical SPIR-V, which has no generic address space.
---
 clang/lib/CodeGen/CGVTables.cpp               | 22 ++++++----
 clang/lib/CodeGen/CodeGenModule.cpp           | 20 +++++++++
 clang/lib/CodeGen/CodeGenTypeCache.h          |  5 +++
 clang/lib/CodeGen/ItaniumCXXABI.cpp           |  6 ++-
 .../CodeGenCXX/vtable-address-space-spirv.cpp | 41 +++++++++++++++++++
 5 files changed, 84 insertions(+), 10 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/vtable-address-space-spirv.cpp

diff --git a/clang/lib/CodeGen/CGVTables.cpp b/clang/lib/CodeGen/CGVTables.cpp
index 2d09ec90c013f..1e52c04308c25 100644
--- a/clang/lib/CodeGen/CGVTables.cpp
+++ b/clang/lib/CodeGen/CGVTables.cpp
@@ -735,7 +735,7 @@ void 
CodeGenVTables::addRelativeComponent(ConstantArrayBuilder &builder,
 llvm::Type *CodeGenModule::getVTableComponentType() const {
   if (getLangOpts().RelativeCXXABIVTables)
     return Int32Ty;
-  return GlobalsInt8PtrTy;
+  return VTableComponentPtrTy;
 }
 
 llvm::Type *CodeGenVTables::getVTableComponentType() const {
@@ -747,7 +747,7 @@ static void AddPointerLayoutOffset(const CodeGenModule &CGM,
                                    CharUnits offset) {
   builder.add(llvm::ConstantExpr::getIntToPtr(
       llvm::ConstantInt::getSigned(CGM.PtrDiffTy, offset.getQuantity()),
-      CGM.GlobalsInt8PtrTy));
+      CGM.VTableComponentPtrTy));
 }
 
 static void AddRelativeLayoutOffset(const CodeGenModule &CGM,
@@ -784,8 +784,14 @@ void 
CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder,
       return addRelativeComponent(builder, rtti, vtableAddressPoint,
                                   vtableHasLocalLinkage,
                                   /*isCompleteDtor=*/false);
-    else
+    else {
+      // The RTTI descriptor is a global, so it lives in the default globals
+      // address space, which is not necessarily where the components live.
+      if (rtti && rtti->getType() != CGM.VTableComponentPtrTy)
+        rtti = llvm::ConstantExpr::getAddrSpaceCast(rtti,
+                                                    CGM.VTableComponentPtrTy);
       return builder.add(rtti);
+    }
 
   case VTableComponent::CK_FunctionPointer:
   case VTableComponent::CK_CompleteDtorPointer:
@@ -813,7 +819,7 @@ void 
CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder,
         if (IsThunk)
           nextVTableThunkIndex++;
         return builder.add(
-            llvm::ConstantExpr::getNullValue(CGM.GlobalsInt8PtrTy));
+            llvm::ConstantExpr::getNullValue(CGM.VTableComponentPtrTy));
       }
       // Method is acceptable, continue processing as usual.
     }
@@ -888,11 +894,11 @@ void 
CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder,
       //       globals;  fixing said issue might be intrusive, and will be done
       //       later.
       unsigned FnAS = fnPtr->getType()->getPointerAddressSpace();
-      unsigned GVAS = CGM.GlobalsInt8PtrTy->getPointerAddressSpace();
+      unsigned GVAS = CGM.VTableComponentPtrTy->getPointerAddressSpace();
 
       if (FnAS != GVAS)
-        fnPtr =
-            llvm::ConstantExpr::getAddrSpaceCast(fnPtr, CGM.GlobalsInt8PtrTy);
+        fnPtr = llvm::ConstantExpr::getAddrSpaceCast(fnPtr,
+                                                     CGM.VTableComponentPtrTy);
       if (const auto &Schema =
           CGM.getCodeGenOpts().PointerAuth.CXXVirtualFunctionPointers)
         return builder.addSignedPointer(fnPtr, Schema, GD, QualType());
@@ -904,7 +910,7 @@ void 
CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder,
     if (RelativeCXXABIVTables)
       return builder.add(llvm::ConstantExpr::getNullValue(CGM.Int32Ty));
     else
-      return builder.addNullPointer(CGM.GlobalsInt8PtrTy);
+      return builder.addNullPointer(CGM.VTableComponentPtrTy);
   }
 
   llvm_unreachable("Unexpected vtable component kind");
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp 
b/clang/lib/CodeGen/CodeGenModule.cpp
index e17bd72c2f052..2017b582813ed 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -538,11 +538,31 @@ CodeGenModule::CodeGenModule(ASTContext &C,
       llvm::PointerType::get(LLVMContext, DL.getAllocaAddrSpace());
   GlobalsInt8PtrTy =
       llvm::PointerType::get(LLVMContext, DL.getDefaultGlobalsAddressSpace());
+  VTableComponentPtrTy = GlobalsInt8PtrTy;
   ProgramPtrTy =
       llvm::PointerType::get(LLVMContext, DL.getProgramAddressSpace());
   ConstGlobalsPtrTy = llvm::PointerType::get(
       LLVMContext, C.getTargetAddressSpace(GetGlobalConstantAddressSpace()));
 
+  // A vtable slot holds the address of a function, and functions live in the
+  // program address space, so materialising a vtable needs an addrspacecast
+  // from the program address space into the address space the components live
+  // in. SPIR-V only permits a cast into the generic address space, so unless
+  // the two already agree, or the program address space is the generic one, a
+  // vtable emitted in the default globals address space produces a module that
+  // no SPIR-V consumer accepts. Emit the components as generic pointers in
+  // that case; casting the globals-address-space RTTI components into the
+  // generic address space is the permitted direction. Logical SPIR-V has no
+  // generic address space, and no virtual functions either, so leave it alone.
+  if ((getTriple().isSPIR() || getTriple().isSPIRV()) &&
+      !getTriple().isSPIRVLogical()) {
+    unsigned GenericAS = C.getTargetAddressSpace(LangAS::opencl_generic);
+    unsigned ProgramAS = DL.getProgramAddressSpace();
+    if (ProgramAS != DL.getDefaultGlobalsAddressSpace() &&
+        ProgramAS != GenericAS)
+      VTableComponentPtrTy = llvm::PointerType::get(LLVMContext, GenericAS);
+  }
+
   // Build C++20 Module initializers.
   // TODO: Add Microsoft here once we know the mangling required for the
   // initializers.
diff --git a/clang/lib/CodeGen/CodeGenTypeCache.h 
b/clang/lib/CodeGen/CodeGenTypeCache.h
index 17eca207d7c80..2f57d3091432a 100644
--- a/clang/lib/CodeGen/CodeGenTypeCache.h
+++ b/clang/lib/CodeGen/CodeGenTypeCache.h
@@ -72,6 +72,11 @@ struct CodeGenTypeCache {
     llvm::PointerType *GlobalsInt8PtrTy;
   };
 
+  /// void* in the address space that C++ vtable components live in. This is
+  /// the default globals address space except on targets where casting a
+  /// function into that address space is not representable.
+  llvm::PointerType *VTableComponentPtrTy;
+
   /// Pointer in program address space
   llvm::PointerType *ProgramPtrTy;
 
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp 
b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index c17813140b10f..3af51154a5041 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -2323,9 +2323,11 @@ CGCallee 
ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF,
           CGM.getIntrinsic(llvm::Intrinsic::load_relative, {CGM.Int32Ty}),
           {VTable, llvm::ConstantInt::get(CGM.Int32Ty, ByteOffset)});
     } else {
+      // The slot holds a pointer in the vtable component address space, which
+      // is not necessarily the address space the vtable itself lives in.
       VTableSlotPtr = CGF.Builder.CreateConstInBoundsGEP1_64(
-          PtrTy, VTable, VTableIndex, "vfn");
-      VFuncLoad = CGF.Builder.CreateAlignedLoad(PtrTy, VTableSlotPtr,
+          ComponentTy, VTable, VTableIndex, "vfn");
+      VFuncLoad = CGF.Builder.CreateAlignedLoad(ComponentTy, VTableSlotPtr,
                                                 CGF.getPointerAlign());
     }
 
diff --git a/clang/test/CodeGenCXX/vtable-address-space-spirv.cpp 
b/clang/test/CodeGenCXX/vtable-address-space-spirv.cpp
new file mode 100644
index 0000000000000..9e82f5f8863a7
--- /dev/null
+++ b/clang/test/CodeGenCXX/vtable-address-space-spirv.cpp
@@ -0,0 +1,41 @@
+// SPIR-V only allows an addrspacecast into the generic address space, so the
+// components of a vtable, which hold the addresses of functions, cannot live 
in
+// the default globals address space there.
+
+// RUN: %clang_cc1 %s -triple=spirv64 -std=c++11 -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -triple=spirv32 -std=c++11 -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -triple=spir64-unknown-unknown -std=c++11 -emit-llvm -o 
- | FileCheck %s
+// RUN: %clang_cc1 %s -triple=spir-unknown-unknown -std=c++11 -emit-llvm -o - 
| FileCheck %s
+
+// Functions already live in the generic address space on this target, so a 
cast
+// into the globals address space is legal and the layout is left alone.
+// RUN: %clang_cc1 %s -triple=spirv64-amd-amdhsa -std=c++11 -emit-llvm -o - | 
FileCheck %s --check-prefix=AMDGCNSPIRV
+
+struct A {
+  virtual void f();
+  virtual void g();
+  virtual void h();
+};
+
+void A::f() {}
+
+// The vtable itself stays a global, only its components become generic.
+// CHECK: @_ZTV1A ={{.*}}addrspace(1) constant { [5 x ptr addrspace(4)] } { [5 
x ptr addrspace(4)] [ptr addrspace(4) null, ptr addrspace(4) addrspacecast (ptr 
addrspace(1) @_ZTI1A to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr 
@_ZN1A1fEv to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @_ZN1A1gEv 
to ptr addrspace(4)), ptr addrspace(4) addrspacecast (ptr @_ZN1A1hEv to ptr 
addrspace(4))]
+// CHECK: @_ZTI1A ={{.*}}addrspace(1) constant { ptr addrspace(1), ptr 
addrspace(1) }
+
+// AMDGCNSPIRV: @_ZTV1A ={{.*}}addrspace(1) constant { [5 x ptr addrspace(1)] 
} { [5 x ptr addrspace(1)] [ptr addrspace(1) null, ptr addrspace(1) @_ZTI1A, 
ptr addrspace(1) addrspacecast (ptr addrspace(4) @_ZN1A1fEv to ptr 
addrspace(1)), ptr addrspace(1) addrspacecast (ptr addrspace(4) @_ZN1A1gEv to 
ptr addrspace(1)), ptr addrspace(1) addrspacecast (ptr addrspace(4) @_ZN1A1hEv 
to ptr addrspace(1))]
+
+void call(A *a) { a->g(); }
+
+// The vtable pointer stays a globals-address-space pointer, the slot it points
+// at holds a generic pointer.
+// CHECK-LABEL: define {{.*}}@_Z4callP1A
+// CHECK: %[[VT:.*]] = load ptr addrspace(1),
+// CHECK: %[[SLOT:.*]] = getelementptr inbounds ptr addrspace(4), ptr 
addrspace(1) %[[VT]], i64 1
+// CHECK: %[[FN:.*]] = load ptr addrspace(4), ptr addrspace(1) %[[SLOT]]
+// CHECK: call {{.*}}addrspace(4) void %[[FN]]
+
+// AMDGCNSPIRV-LABEL: define {{.*}}@_Z4callP1A
+// AMDGCNSPIRV: %[[VT:.*]] = load ptr addrspace(1),
+// AMDGCNSPIRV: %[[SLOT:.*]] = getelementptr inbounds ptr addrspace(1), ptr 
addrspace(1) %[[VT]], i64 1
+// AMDGCNSPIRV: %[[FN:.*]] = load ptr addrspace(1), ptr addrspace(1) %[[SLOT]]

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to