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

For SPIR-V, clang emits every vtable slot in the default globals address space, 
so each virtual function slot is `addrspacecast (ptr @fn to ptr addrspace(1))`. 
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 not expressible today.

This emits the components as generic pointers on SPIR-V targets whose program 
address space is neither the globals address space nor the generic one, i.e. 
`spirv32`, `spirv64`, `spirv64-intel-*`, `spir`, `spir64`. Casting the globals 
address space RTTI components into generic is the permitted direction.

Unchanged for every other target, including `spirv64-amd-amdhsa` (program AS is 
already generic) and logical SPIR-V (no generic address space).

Verified: emitted IR is byte identical for x86_64, aarch64, armv7, riscv64, 
powerpc64le, i686-pc-windows-msvc, amdgcn-amd-amdhsa, nvptx64 and 
spirv64-amd-amdhsa across the whole `clang/test/CodeGen*` corpus, and the 
`clang/test/CodeGen*` lit failure set is unchanged. After the change, `spirv64` 
output translates with `llvm-spirv --spirv-ext=+SPV_INTEL_function_pointers` 
and the resulting binary is accepted by `clBuildProgram` and `zeModuleCreate` 
on an Intel Arc GPU.

Draft: feedback wanted on the scoping predicate.


>From af3fa354eef8e61124f3ccdac253c8f6a73f73bc 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 817b2b5e6a69d..b9af368a991a3 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -537,11 +537,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 5c5fefe32c06c..b2f41ad16b819 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -2325,9 +2325,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