[clang] [clang][SPIR-V] Emit C++ vtable components in the generic address space (PR #212452)
Paulius Velesko via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 13 20:46:46 PDT 2026
https://github.com/pvelesko updated https://github.com/llvm/llvm-project/pull/212452
>From 26aaa87a3136b666bf5ca4352af8db76527c07b3 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
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 a function address. Functions live in the program
address space and the slots in the default globals address space, so on
spirv64 clang emits each slot as
addrspacecast (ptr @fn to ptr addrspace(1))
SPIR-V has no cast between two non-generic storage classes:
OpPtrCastToGeneric converts a Workgroup, CrossWorkgroup or Function
pointer to Generic, and OpGenericCastToPtr converts back. The
SPIRV-LLVM-Translator rejects the module ("Casts from private/local/global
address space are allowed only to generic") and the SPIR-V backend emits a
vtable initializer that spirv-val rejects.
Emit the pointer components as generic pointers on non-logical SPIR and
SPIR-V targets whose program address space is not already the generic
one: spir, spir64, spirv32, spirv64 and spirv64-intel (functions in
CodeSectionINTEL, which SPV_INTEL_function_pointers makes part of
Generic). The RTTI component, a global, is cast to generic, and
getVirtualFunctionPointer loads the slot with the component type.
spirv64-amd-amdhsa is unchanged because its functions are already generic,
and so is logical SPIR-V, which has no generic pointers. Other targets are
unaffected because the component type stays GlobalsInt8PtrTy.
---
clang/lib/CodeGen/CGVTables.cpp | 17 +++++-----
clang/lib/CodeGen/CodeGenModule.cpp | 7 ++++
clang/lib/CodeGen/CodeGenTypeCache.h | 3 ++
clang/lib/CodeGen/ItaniumCXXABI.cpp | 4 +--
.../CodeGenCXX/vtable-address-space-spirv.cpp | 32 +++++++++++++++++++
5 files changed, 53 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 1aa3fb1928279..c63e1e4e5d7f0 100644
--- a/clang/lib/CodeGen/CGVTables.cpp
+++ b/clang/lib/CodeGen/CGVTables.cpp
@@ -730,7 +730,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 {
@@ -742,7 +742,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,
@@ -780,7 +780,8 @@ void CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder,
vtableHasLocalLinkage,
/*isCompleteDtor=*/false);
else
- return builder.add(rtti);
+ return builder.add(llvm::ConstantExpr::getPointerBitCastOrAddrSpaceCast(
+ rtti, CGM.VTableComponentPtrTy));
case VTableComponent::CK_FunctionPointer:
case VTableComponent::CK_CompleteDtorPointer:
@@ -808,7 +809,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.
}
@@ -885,11 +886,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());
@@ -901,7 +902,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 bf03e10f9fb42..524d81b843730 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -591,11 +591,18 @@ 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()));
+ // SPIR-V can only cast a function pointer to the generic address space.
+ unsigned GenericAS = C.getTargetAddressSpace(LangAS::opencl_generic);
+ if (getTriple().isSPIROrSPIRV() && !getTriple().isSPIRVLogical() &&
+ DL.getProgramAddressSpace() != 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..b15ade84c5b92 100644
--- a/clang/lib/CodeGen/CodeGenTypeCache.h
+++ b/clang/lib/CodeGen/CodeGenTypeCache.h
@@ -72,6 +72,9 @@ struct CodeGenTypeCache {
llvm::PointerType *GlobalsInt8PtrTy;
};
+ /// Type of a pointer component of a C++ vtable.
+ 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 5bbb742a7b1ef..feb0f0e9acbc0 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -2275,8 +2275,8 @@ CGCallee ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF,
{VTable, llvm::ConstantInt::get(CGM.Int32Ty, ByteOffset)});
} else {
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..9687e34c3dedb
--- /dev/null
+++ b/clang/test/CodeGenCXX/vtable-address-space-spirv.cpp
@@ -0,0 +1,32 @@
+// 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 are already generic here, so the components stay global.
+// 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();
+};
+
+void A::f() {}
+
+// CHECK: @_ZTV1A ={{.*}}addrspace(1) constant { [4 x ptr addrspace(4)] } { [4 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))]
+// CHECK: @_ZTI1A ={{.*}}addrspace(1) constant { ptr addrspace(1), ptr addrspace(1) }
+
+// AMDGCNSPIRV: @_ZTV1A ={{.*}}addrspace(1) constant { [4 x ptr addrspace(1)] } { [4 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))]
+
+void call(A *a) { a->g(); }
+
+// 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]]
More information about the cfe-commits
mailing list