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
