[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
Sat Oct 17 14:56:58 PDT 2026
https://github.com/pvelesko updated https://github.com/llvm/llvm-project/pull/212452
>From 58812b7cf646589bf46faab40462eea40775aa2d 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.
EmitTypeid loads the RTTI slot of the vtable, which is now a generic
pointer, so load it with the component type and cast it to the
std::type_info pointer type.
---
clang/lib/CodeGen/CGVTables.cpp | 17 ++++---
clang/lib/CodeGen/CodeGenModule.cpp | 7 +++
clang/lib/CodeGen/CodeGenTypeCache.h | 3 ++
clang/lib/CodeGen/ItaniumCXXABI.cpp | 14 +++--
.../CodeGenCXX/vtable-address-space-spirv.cpp | 51 +++++++++++++++++++
5 files changed, 80 insertions(+), 12 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 1aa3fb19282794..c63e1e4e5d7f02 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 bf03e10f9fb422..524d81b8437309 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 17eca207d7c807..b15ade84c5b928 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 5bbb742a7b1efa..ade346fe0be0c7 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -1588,8 +1588,14 @@ llvm::Value *ItaniumCXXABI::EmitTypeid(CodeGenFunction &CGF,
{Value, llvm::ConstantInt::getSigned(CGM.Int32Ty, -4)});
} else {
// Load the type info.
- Value =
- CGF.Builder.CreateConstInBoundsGEP1_64(StdTypeInfoPtrTy, Value, -1ULL);
+ // Only SPIR-V changes the component type; other targets load type_info*.
+ llvm::Type *ComponentTy = CGM.VTableComponentPtrTy == CGM.GlobalsInt8PtrTy
+ ? StdTypeInfoPtrTy
+ : CGM.VTableComponentPtrTy;
+ Value = CGF.Builder.CreateConstInBoundsGEP1_64(ComponentTy, Value, -1ULL);
+ Value = CGF.Builder.CreateAlignedLoad(ComponentTy, Value,
+ CGF.getPointerAlign());
+ return CGF.Builder.CreateAddrSpaceCast(Value, StdTypeInfoPtrTy);
}
return CGF.Builder.CreateAlignedLoad(StdTypeInfoPtrTy, Value,
CGF.getPointerAlign());
@@ -2275,8 +2281,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 00000000000000..a0457c5830bdea
--- /dev/null
+++ b/clang/test/CodeGenCXX/vtable-address-space-spirv.cpp
@@ -0,0 +1,51 @@
+// 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
+
+namespace std {
+struct type_info {
+ virtual ~type_info();
+};
+} // namespace std
+
+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]]
+
+const std::type_info &ti(A *a) { return typeid(*a); }
+
+// CHECK-LABEL: define {{.*}}@_Z2tiP1A
+// CHECK: %[[TVT:.*]] = load ptr addrspace(1),
+// CHECK: %[[TSLOT:.*]] = getelementptr inbounds ptr addrspace(4), ptr addrspace(1) %[[TVT]], i64 -1
+// CHECK: %[[TI:.*]] = load ptr addrspace(4), ptr addrspace(1) %[[TSLOT]]
+// CHECK: addrspacecast ptr addrspace(4) %[[TI]] to ptr
+
+// AMDGCNSPIRV-LABEL: define {{.*}}@_Z2tiP1A
+// AMDGCNSPIRV: %[[TVT:.*]] = load ptr addrspace(1),
+// AMDGCNSPIRV: %[[TSLOT:.*]] = getelementptr inbounds ptr addrspace(4), ptr addrspace(1) %[[TVT]], i64 -1
+// AMDGCNSPIRV: %[[TI:.*]] = load ptr addrspace(4), ptr addrspace(1) %[[TSLOT]]
More information about the cfe-commits
mailing list