From 63a86b43bef0142bea50f4a16cb1b2463aec3d2f Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Thu, 21 Aug 2025 16:42:11 -0700 Subject: [PATCH] [CIR] Add support for emitting VTTs and related ojects This adds support for emitting virtual table tables (VTTs) and construction vtables. --- clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 4 + clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 8 + clang/lib/CIR/CodeGen/CIRGenVTables.cpp | 180 +++++++++++++++++- clang/lib/CIR/CodeGen/CIRGenVTables.h | 16 ++ clang/lib/CIR/Dialect/IR/CIRDataLayout.cpp | 9 - clang/test/CIR/CodeGen/vtt.cpp | 141 +++++++++++++- 6 files changed, 333 insertions(+), 25 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index df7ffbb4a2759..db50cb5148d1b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -105,6 +105,10 @@ class CIRGenCXXABI { virtual void emitVTableDefinitions(CIRGenVTables &cgvt, const CXXRecordDecl *rd) = 0; + /// Emit any tables needed to implement virtual inheritance. For Itanium, + /// this emits virtual table tables. + virtual void emitVirtualInheritanceTables(const CXXRecordDecl *rd) = 0; + /// Returns true if the given destructor type should be emitted as a linkonce /// delegating thunk, regardless of whether the dtor is defined in this TU or /// not. diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index 4fd5a278e1a99..80703b348aed7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -83,6 +83,7 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI { const clang::CXXRecordDecl *nearestVBase) override; void emitVTableDefinitions(CIRGenVTables &cgvt, const CXXRecordDecl *rd) override; + void emitVirtualInheritanceTables(const CXXRecordDecl *rd) override; bool doStructorsInitializeVPtrs(const CXXRecordDecl *vtableClass) override { return true; @@ -333,6 +334,13 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt, } } +void CIRGenItaniumCXXABI::emitVirtualInheritanceTables( + const CXXRecordDecl *rd) { + CIRGenVTables &vtables = cgm.getVTables(); + cir::GlobalOp vtt = vtables.getAddrOfVTT(rd); + vtables.emitVTTDefinition(vtt, cgm.getVTableLinkage(rd), rd); +} + void CIRGenItaniumCXXABI::emitDestructorCall( CIRGenFunction &cgf, const CXXDestructorDecl *dd, CXXDtorType type, bool forVirtualBase, bool delegating, Address thisAddr, QualType thisTy) { diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp index aca12aa62f55b..9fbc0f67b4b92 100644 --- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp @@ -15,6 +15,7 @@ #include "CIRGenCXXABI.h" #include "CIRGenModule.h" #include "mlir/IR/Types.h" +#include "clang/AST/VTTBuilder.h" #include "clang/AST/VTableBuilder.h" #include "llvm/ADT/SmallVector.h" @@ -60,7 +61,7 @@ void CIRGenVTables::generateClassData(const CXXRecordDecl *rd) { assert(!cir::MissingFeatures::generateDebugInfo()); if (rd->getNumVBases()) - cgm.errorNYI(rd->getSourceRange(), "emitVirtualInheritanceTables"); + cgm.getCXXABI().emitVirtualInheritanceTables(rd); cgm.getCXXABI().emitVTableDefinitions(*this, rd); } @@ -76,12 +77,6 @@ mlir::Attribute CIRGenVTables::getVTableComponent( assert(!cir::MissingFeatures::vtableRelativeLayout()); switch (component.getKind()) { - case VTableComponent::CK_VCallOffset: - cgm.errorNYI("getVTableComponent: VCallOffset"); - return mlir::Attribute(); - case VTableComponent::CK_VBaseOffset: - cgm.errorNYI("getVTableComponent: VBaseOffset"); - return mlir::Attribute(); case VTableComponent::CK_CompleteDtorPointer: cgm.errorNYI("getVTableComponent: CompleteDtorPointer"); return mlir::Attribute(); @@ -92,6 +87,14 @@ mlir::Attribute CIRGenVTables::getVTableComponent( cgm.errorNYI("getVTableComponent: UnusedFunctionPointer"); return mlir::Attribute(); + case VTableComponent::CK_VCallOffset: + return builder.getConstPtrAttr(builder.getUInt8PtrTy(), + component.getVCallOffset().getQuantity()); + + case VTableComponent::CK_VBaseOffset: + return builder.getConstPtrAttr(builder.getUInt8PtrTy(), + component.getVBaseOffset().getQuantity()); + case VTableComponent::CK_OffsetToTop: return builder.getConstPtrAttr(builder.getUInt8PtrTy(), component.getOffsetToTop().getQuantity()); @@ -175,6 +178,66 @@ void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp, cgm.setInitializer(vtableOp, vtableAttr); } +cir::GlobalOp CIRGenVTables::generateConstructionVTable( + const CXXRecordDecl *rd, const BaseSubobject &base, bool baseIsVirtual, + cir::GlobalLinkageKind linkage, VTableAddressPointsMapTy &addressPoints) { + assert(!cir::MissingFeatures::generateDebugInfo()); + + std::unique_ptr vtLayout( + getItaniumVTableContext().createConstructionVTableLayout( + base.getBase(), base.getBaseOffset(), baseIsVirtual, rd)); + + // Add the address points. + addressPoints = vtLayout->getAddressPoints(); + + // Get the mangled construction vtable name. + SmallString<256> outName; + llvm::raw_svector_ostream out(outName); + cast(cgm.getCXXABI().getMangleContext()) + .mangleCXXCtorVTable(rd, base.getBaseOffset().getQuantity(), + base.getBase(), out); + SmallString<256> name(outName); + + assert(!cir::MissingFeatures::vtableRelativeLayout()); + + cir::RecordType vtType = getVTableType(*vtLayout); + + // Construction vtable symbols are not part of the Itanium ABI, so we cannot + // guarantee that they actually will be available externally. Instead, when + // emitting an available_externally VTT, we provide references to an internal + // linkage construction vtable. The ABI only requires complete-object vtables + // to be the same for all instances of a type, not construction vtables. + if (linkage == cir::GlobalLinkageKind::AvailableExternallyLinkage) + linkage = cir::GlobalLinkageKind::InternalLinkage; + + llvm::Align align = cgm.getDataLayout().getABITypeAlign(vtType); + mlir::Location loc = cgm.getLoc(rd->getSourceRange()); + + // Create the variable that will hold the construction vtable. + cir::GlobalOp vtable = cgm.createOrReplaceCXXRuntimeVariable( + loc, name, vtType, linkage, CharUnits::fromQuantity(align)); + + // V-tables are always unnamed_addr. + assert(!cir::MissingFeatures::opGlobalUnnamedAddr()); + + mlir::Attribute rtti = cgm.getAddrOfRTTIDescriptor( + loc, cgm.getASTContext().getCanonicalTagType(base.getBase())); + + // Create and set the initializer. + createVTableInitializer(vtable, *vtLayout, rtti, + cir::isLocalLinkage(vtable.getLinkage())); + + // Set properties only after the initializer has been set to ensure that the + // GV is treated as definition and not declaration. + assert(!vtable.isDeclaration() && "Shouldn't set properties on declaration"); + cgm.setGVProperties(vtable, rd); + + assert(!cir::MissingFeatures::vtableEmitMetadata()); + assert(!cir::MissingFeatures::vtableRelativeLayout()); + + return vtable; +} + /// Compute the required linkage of the vtable for the given class. /// /// Note that we only call this at the end of the translation unit. @@ -226,6 +289,109 @@ cir::GlobalLinkageKind CIRGenModule::getVTableLinkage(const CXXRecordDecl *rd) { return cir::GlobalLinkageKind::ExternalLinkage; } +cir::GlobalOp CIRGenVTables::getAddrOfVTT(const CXXRecordDecl *rd) { + assert(rd->getNumVBases() && "Only classes with virtual bases need a VTT"); + + SmallString<256> outName; + llvm::raw_svector_ostream out(outName); + cast(cgm.getCXXABI().getMangleContext()) + .mangleCXXVTT(rd, out); + StringRef name = outName.str(); + + // This will also defer the definition of the VTT. + (void)cgm.getCXXABI().getAddrOfVTable(rd, CharUnits()); + + VTTBuilder builder(cgm.getASTContext(), rd, /*GenerateDefinition=*/false); + + auto arrayType = cir::ArrayType::get(cgm.getBuilder().getUInt8PtrTy(), + builder.getVTTComponents().size()); + llvm::Align align = + cgm.getDataLayout().getABITypeAlign(cgm.getBuilder().getUInt8PtrTy()); + cir::GlobalOp vtt = cgm.createOrReplaceCXXRuntimeVariable( + cgm.getLoc(rd->getSourceRange()), name, arrayType, + cir::GlobalLinkageKind::ExternalLinkage, CharUnits::fromQuantity(align)); + cgm.setGVProperties(vtt, rd); + return vtt; +} + +static cir::GlobalOp +getAddrOfVTTVTable(CIRGenVTables &cgvt, CIRGenModule &cgm, + const CXXRecordDecl *mostDerivedClass, + const VTTVTable &vtable, cir::GlobalLinkageKind linkage, + VTableLayout::AddressPointsMapTy &addressPoints) { + if (vtable.getBase() == mostDerivedClass) { + assert(vtable.getBaseOffset().isZero() && + "Most derived class vtable must have a zero offset!"); + // This is a regular vtable. + return cgm.getCXXABI().getAddrOfVTable(mostDerivedClass, CharUnits()); + } + return cgvt.generateConstructionVTable( + mostDerivedClass, vtable.getBaseSubobject(), vtable.isVirtual(), linkage, + addressPoints); +} + +/// Emit the definition of the given vtable. +void CIRGenVTables::emitVTTDefinition(cir::GlobalOp vttOp, + cir::GlobalLinkageKind linkage, + const CXXRecordDecl *rd) { + VTTBuilder builder(cgm.getASTContext(), rd, /*GenerateDefinition=*/true); + + mlir::MLIRContext *mlirContext = &cgm.getMLIRContext(); + + auto arrayType = cir::ArrayType::get(cgm.getBuilder().getUInt8PtrTy(), + builder.getVTTComponents().size()); + + SmallVector vtables; + SmallVector vtableAddressPoints; + for (const VTTVTable &vtt : builder.getVTTVTables()) { + vtableAddressPoints.push_back(VTableAddressPointsMapTy()); + vtables.push_back(getAddrOfVTTVTable(*this, cgm, rd, vtt, linkage, + vtableAddressPoints.back())); + } + + SmallVector vttComponents; + for (const VTTComponent &vttComponent : builder.getVTTComponents()) { + const VTTVTable &vttVT = builder.getVTTVTables()[vttComponent.VTableIndex]; + cir::GlobalOp vtable = vtables[vttComponent.VTableIndex]; + VTableLayout::AddressPointLocation addressPoint; + if (vttVT.getBase() == rd) { + // Just get the address point for the regular vtable. + addressPoint = + getItaniumVTableContext().getVTableLayout(rd).getAddressPoint( + vttComponent.VTableBase); + } else { + addressPoint = vtableAddressPoints[vttComponent.VTableIndex].lookup( + vttComponent.VTableBase); + assert(addressPoint.AddressPointIndex != 0 && + "Did not find ctor vtable address point!"); + } + + mlir::Attribute indices[2] = { + cgm.getBuilder().getI32IntegerAttr(addressPoint.VTableIndex), + cgm.getBuilder().getI32IntegerAttr(addressPoint.AddressPointIndex), + }; + + auto indicesAttr = mlir::ArrayAttr::get(mlirContext, indices); + cir::GlobalViewAttr init = cgm.getBuilder().getGlobalViewAttr( + cgm.getBuilder().getUInt8PtrTy(), vtable, indicesAttr); + + vttComponents.push_back(init); + } + + auto init = cir::ConstArrayAttr::get( + arrayType, mlir::ArrayAttr::get(mlirContext, vttComponents)); + + vttOp.setInitialValueAttr(init); + + // Set the correct linkage. + vttOp.setLinkage(linkage); + mlir::SymbolTable::setSymbolVisibility( + vttOp, CIRGenModule::getMLIRVisibility(vttOp)); + + if (cgm.supportsCOMDAT() && vttOp.isWeakForLinker()) + vttOp.setComdat(true); +} + void CIRGenVTables::emitThunks(GlobalDecl gd) { const CXXMethodDecl *md = cast(gd.getDecl())->getCanonicalDecl(); diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.h b/clang/lib/CIR/CodeGen/CIRGenVTables.h index 518d7d78f1737..8d352c9949109 100644 --- a/clang/lib/CIR/CodeGen/CIRGenVTables.h +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.h @@ -30,6 +30,9 @@ class CIRGenVTables { clang::VTableContextBase *vtContext; + /// Address points for a single vtable. + using VTableAddressPointsMapTy = clang::VTableLayout::AddressPointsMapTy; + mlir::Attribute getVTableComponent(const VTableLayout &layout, unsigned componentIndex, mlir::Attribute rtti, unsigned &nextVTableThunkIndex, @@ -55,6 +58,19 @@ class CIRGenVTables { return *llvm::cast(vtContext); } + /// Generate a construction vtable for the given base subobject. + cir::GlobalOp + generateConstructionVTable(const CXXRecordDecl *rd, const BaseSubobject &base, + bool baseIsVirtual, cir::GlobalLinkageKind linkage, + VTableAddressPointsMapTy &addressPoints); + + /// Get the address of the VTT for the given record decl. + cir::GlobalOp getAddrOfVTT(const CXXRecordDecl *rd); + + /// Emit the definition of the given vtable. + void emitVTTDefinition(cir::GlobalOp vttOp, cir::GlobalLinkageKind linkage, + const CXXRecordDecl *rd); + /// Emit the associated thunks for the given global decl. void emitThunks(GlobalDecl gd); diff --git a/clang/lib/CIR/Dialect/IR/CIRDataLayout.cpp b/clang/lib/CIR/Dialect/IR/CIRDataLayout.cpp index 42d45819de0f3..4635ce943b17c 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDataLayout.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDataLayout.cpp @@ -24,15 +24,6 @@ void CIRDataLayout::reset(mlir::DataLayoutSpecInterface spec) { } llvm::Align CIRDataLayout::getAlignment(mlir::Type ty, bool useABIAlign) const { - if (auto recTy = llvm::dyn_cast(ty)) { - // Packed record types always have an ABI alignment of one. - if (recTy && recTy.getPacked() && useABIAlign) - return llvm::Align(1); - - // Get the layout annotation... which is lazily created on demand. - llvm_unreachable("getAlignment()) for record type is not implemented"); - } - // FIXME(cir): This does not account for differnt address spaces, and relies // on CIR's data layout to give the proper alignment. assert(!cir::MissingFeatures::addressSpace()); diff --git a/clang/test/CIR/CodeGen/vtt.cpp b/clang/test/CIR/CodeGen/vtt.cpp index 631aab428840a..fcbfc07324c6d 100644 --- a/clang/test/CIR/CodeGen/vtt.cpp +++ b/clang/test/CIR/CodeGen/vtt.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -fclangir -emit-cir %s -o %t.cir // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -fclangir -emit-llvm %s -o %t-cir.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -emit-llvm %s -o %t.ll // RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s // Note: This test will be expanded to verify VTT emission and VTT implicit @@ -11,7 +11,7 @@ class A { public: int a; - virtual void v() {} + virtual void v(); }; class B : public virtual A { @@ -23,23 +23,146 @@ class B : public virtual A { class C : public virtual A { public: long c; - virtual void x() {} + virtual void x(); }; class D : public B, public C { public: long d; - virtual void y() {} + virtual void y(); }; // This is just here to force the record types to be emitted. void f(D *d) {} +// Trigger vtable and VTT emission for D. +void D::y() {} + // CIR: !rec_A2Ebase = !cir.record // CIR: !rec_B2Ebase = !cir.record // CIR: !rec_C2Ebase = !cir.record +// CIR: !rec_A = !cir.record}> +// CIR: !rec_B = !cir.record, !rec_A2Ebase, !cir.array}> +// CIR: !rec_C = !cir.record // CIR: !rec_D = !cir.record -// Nothing interesting to see here yet. -// LLVM: define{{.*}} void @_Z1fP1D -// OGCG: define{{.*}} void @_Z1fP1D +// CIR: !rec_anon_struct = !cir.record x 5>, !cir.array x 4>, !cir.array x 4>}> +// CIR: !rec_anon_struct1 = !cir.record x 4>, !cir.array x 4>}> + +// Vtable for D +// CIR: cir.global{{.*}} @_ZTV1D = #cir.vtable<{ +// CIR-SAME: #cir.const_array<[ +// CIR-SAME: #cir.ptr<40 : i64> : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZN1B1wEv> : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZN1D1yEv> : !cir.ptr +// CIR-SAME: ]> : !cir.array x 5>, +// CIR-SAME: #cir.const_array<[ +// CIR-SAME: #cir.ptr<24 : i64> : !cir.ptr, +// CIR-SAME: #cir.ptr<-16 : i64> : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZN1C1xEv> : !cir.ptr +// CIR-SAME: ]> : !cir.array x 4>, +// CIR-SAME: #cir.const_array<[ +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.ptr<-40 : i64> : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZN1A1vEv> : !cir.ptr +// CIR-SAME: ]> : !cir.array x 4> +// CIR-SAME: }> : !rec_anon_struct {alignment = 8 : i64} + +// LLVM: @_ZTV1D = global { [5 x ptr], [4 x ptr], [4 x ptr] } { +// LLVM-SAME: [5 x ptr] [ptr inttoptr (i64 40 to ptr), ptr null, ptr null, ptr @_ZN1B1wEv, ptr @_ZN1D1yEv], +// LLVM-SAME: [4 x ptr] [ptr inttoptr (i64 24 to ptr), ptr inttoptr (i64 -16 to ptr), ptr null, ptr @_ZN1C1xEv], +// LLVM-SAME: [4 x ptr] [ptr null, ptr inttoptr (i64 -40 to ptr), ptr null, ptr @_ZN1A1vEv] +// LLVM-SAME: }, align 8 + +// OGCG: @_ZTV1D = unnamed_addr constant { [5 x ptr], [4 x ptr], [4 x ptr] } { +// OGCG-SAME: [5 x ptr] [ptr inttoptr (i64 40 to ptr), ptr null, ptr null, ptr @_ZN1B1wEv, ptr @_ZN1D1yEv], +// OGCG-SAME: [4 x ptr] [ptr inttoptr (i64 24 to ptr), ptr inttoptr (i64 -16 to ptr), ptr null, ptr @_ZN1C1xEv], +// OGCG-SAME: [4 x ptr] [ptr null, ptr inttoptr (i64 -40 to ptr), ptr null, ptr @_ZN1A1vEv] +// OGCG-SAME: }, align 8 + +// VTT for D +// CIR: cir.global{{.*}} @_ZTT1D = #cir.const_array<[ +// CIR-SAME: #cir.global_view<@_ZTV1D, [0 : i32, 3 : i32]> : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZTC1D0_1B, [0 : i32, 3 : i32]> : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZTC1D0_1B, [1 : i32, 3 : i32]> : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZTC1D16_1C, [0 : i32, 3 : i32]> : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZTC1D16_1C, [1 : i32, 3 : i32]> : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZTV1D, [2 : i32, 3 : i32]> : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZTV1D, [1 : i32, 3 : i32]> : !cir.ptr +// CIR-SAME: ]> : !cir.array x 7> {alignment = 8 : i64} + +// LLVM: @_ZTT1D = global [7 x ptr] [ +// LLVM-SAME: ptr getelementptr inbounds nuw (i8, ptr @_ZTV1D, i64 24), +// LLVM-SAME: ptr getelementptr inbounds nuw (i8, ptr @_ZTC1D0_1B, i64 24), +// LLVM-SAME: ptr getelementptr inbounds nuw (i8, ptr @_ZTC1D0_1B, i64 56), +// LLVM-SAME: ptr getelementptr inbounds nuw (i8, ptr @_ZTC1D16_1C, i64 24), +// LLVM-SAME: ptr getelementptr inbounds nuw (i8, ptr @_ZTC1D16_1C, i64 56), +// LLVM-SAME: ptr getelementptr inbounds nuw (i8, ptr @_ZTV1D, i64 96), +// LLVM-SAME: ptr getelementptr inbounds nuw (i8, ptr @_ZTV1D, i64 64) +// LLVM-SAME: ], align 8 + +// OGCG: @_ZTT1D = unnamed_addr constant [7 x ptr] [ +// OGCG-SAME: ptr getelementptr inbounds inrange(-24, 16) ({ [5 x ptr], [4 x ptr], [4 x ptr] }, ptr @_ZTV1D, i32 0, i32 0, i32 3), +// OGCG-SAME: ptr getelementptr inbounds inrange(-24, 8) ({ [4 x ptr], [4 x ptr] }, ptr @_ZTC1D0_1B, i32 0, i32 0, i32 3), +// OGCG-SAME: ptr getelementptr inbounds inrange(-24, 8) ({ [4 x ptr], [4 x ptr] }, ptr @_ZTC1D0_1B, i32 0, i32 1, i32 3), +// OGCG-SAME: ptr getelementptr inbounds inrange(-24, 8) ({ [4 x ptr], [4 x ptr] }, ptr @_ZTC1D16_1C, i32 0, i32 0, i32 3), +// OGCG-SAME: ptr getelementptr inbounds inrange(-24, 8) ({ [4 x ptr], [4 x ptr] }, ptr @_ZTC1D16_1C, i32 0, i32 1, i32 3), +// OGCG-SAME: ptr getelementptr inbounds inrange(-24, 8) ({ [5 x ptr], [4 x ptr], [4 x ptr] }, ptr @_ZTV1D, i32 0, i32 2, i32 3), +// OGCG-SAME: ptr getelementptr inbounds inrange(-24, 8) ({ [5 x ptr], [4 x ptr], [4 x ptr] }, ptr @_ZTV1D, i32 0, i32 1, i32 3) +// OGCG-SAME: ], align 8 + +// Construction vtable for B-in-D +// CIR: cir.global{{.*}} @_ZTC1D0_1B = #cir.vtable<{ +// CIR-SAME: #cir.const_array<[ +// CIR-SAME: #cir.ptr<40 : i64> : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZN1B1wEv> : !cir.ptr +// CIR-SAME: ]> : !cir.array x 4>, +// CIR-SAME: #cir.const_array<[ +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.ptr<-40 : i64> : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZN1A1vEv> : !cir.ptr +// CIR-SAME: ]> : !cir.array x 4> +// CIR-SAME: }> : !rec_anon_struct1 {alignment = 8 : i64} + +// LLVM: @_ZTC1D0_1B = global { [4 x ptr], [4 x ptr] } { +// LLVM-SAME: [4 x ptr] [ptr inttoptr (i64 40 to ptr), ptr null, ptr null, ptr @_ZN1B1wEv], +// LLVM-SAME: [4 x ptr] [ptr null, ptr inttoptr (i64 -40 to ptr), ptr null, ptr @_ZN1A1vEv] +// LLVM-SAME: }, align 8 + +// OGCG: @_ZTC1D0_1B = unnamed_addr constant { [4 x ptr], [4 x ptr] } { +// OGCG-SAME: [4 x ptr] [ptr inttoptr (i64 40 to ptr), ptr null, ptr null, ptr @_ZN1B1wEv], +// OGCG-SAME: [4 x ptr] [ptr null, ptr inttoptr (i64 -40 to ptr), ptr null, ptr @_ZN1A1vEv] +// OGCG-SAME: }, align 8 + +// Construction vtable for C-in-D +// CIR: cir.global{{.*}} @_ZTC1D16_1C = #cir.vtable<{ +// CIR-SAME: #cir.const_array<[ +// CIR-SAME: #cir.ptr<24 : i64> : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZN1C1xEv> : !cir.ptr +// CIR-SAME: ]> : !cir.array x 4>, +// CIR-SAME: #cir.const_array<[ +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.ptr<-24 : i64> : !cir.ptr, +// CIR-SAME: #cir.ptr : !cir.ptr, +// CIR-SAME: #cir.global_view<@_ZN1A1vEv> : !cir.ptr +// CIR-SAME: ]> : !cir.array x 4> +// CIR-SAME: }> : !rec_anon_struct1 {alignment = 8 : i64} + +// LLVM: @_ZTC1D16_1C = global { [4 x ptr], [4 x ptr] } { +// LLVM-SAME: [4 x ptr] [ptr inttoptr (i64 24 to ptr), ptr null, ptr null, ptr @_ZN1C1xEv], +// LLVM-SAME: [4 x ptr] [ptr null, ptr inttoptr (i64 -24 to ptr), ptr null, ptr @_ZN1A1vEv] +// LLVM-SAME: }, align 8 + +// OGCG: @_ZTC1D16_1C = unnamed_addr constant { [4 x ptr], [4 x ptr] } { +// OGCG-SAME: [4 x ptr] [ptr inttoptr (i64 24 to ptr), ptr null, ptr null, ptr @_ZN1C1xEv], +// OGCG-SAME: [4 x ptr] [ptr null, ptr inttoptr (i64 -24 to ptr), ptr null, ptr @_ZN1A1vEv] +// OGCG-SAME: }, align 8