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
