diff --git a/clang/include/clang/AST/VTableBuilder.h b/clang/include/clang/AST/VTableBuilder.h --- a/clang/include/clang/AST/VTableBuilder.h +++ b/clang/include/clang/AST/VTableBuilder.h @@ -238,6 +238,11 @@ typedef llvm::DenseMap AddressPointsMapTy; + // Mapping between the VTable index and address point index. This is useful + // when you don't care about the base subobjects and only want the address + // point for a given vtable index. + typedef llvm::SmallVector AddressPointsIndexMapTy; + private: // Stores the component indices of the first component of each virtual table in // the virtual table group. To save a little memory in the common case where @@ -253,6 +258,9 @@ /// Address points for all vtables. AddressPointsMapTy AddressPoints; + /// Address points for all vtable indices. + AddressPointsIndexMapTy AddressPointIndices; + public: VTableLayout(ArrayRef VTableIndices, ArrayRef VTableComponents, @@ -277,6 +285,10 @@ return AddressPoints; } + const AddressPointsIndexMapTy &getAddressPointIndices() const { + return AddressPointIndices; + } + size_t getNumVTables() const { if (VTableIndices.empty()) return 1; @@ -371,7 +383,17 @@ void computeVTableRelatedInformation(const CXXRecordDecl *RD) override; public: - ItaniumVTableContext(ASTContext &Context); + enum VTableComponentLayout { + /// Components in the vtable are pointers to other structs/functions. + Pointer, + + /// Components in the vtable are relative offsets between the vtable and the + /// other structs/functions. + Relative, + }; + + ItaniumVTableContext(ASTContext &Context, + VTableComponentLayout ComponentLayout = Pointer); ~ItaniumVTableContext() override; const VTableLayout &getVTableLayout(const CXXRecordDecl *RD) { @@ -402,6 +424,16 @@ static bool classof(const VTableContextBase *VT) { return !VT->isMicrosoft(); } + + VTableComponentLayout getVTableComponentLayout() const { + return ComponentLayout; + } + + bool isPointerLayout() const { return ComponentLayout == Pointer; } + bool isRelativeLayout() const { return ComponentLayout == Relative; } + +private: + VTableComponentLayout ComponentLayout; }; /// Holds information about the inheritance path to a virtual base or function diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -376,6 +376,9 @@ LANGOPT(SpeculativeLoadHardening, 1, 0, "Speculative load hardening enabled") +LANGOPT(RelativeCXXABIVTables, 1, 0, + "Use an ABI-incompatible v-table layout that uses relative references") + #undef LANGOPT #undef COMPATIBLE_LANGOPT #undef BENIGN_LANGOPT diff --git a/clang/include/clang/CodeGen/ConstantInitBuilder.h b/clang/include/clang/CodeGen/ConstantInitBuilder.h --- a/clang/include/clang/CodeGen/ConstantInitBuilder.h +++ b/clang/include/clang/CodeGen/ConstantInitBuilder.h @@ -226,6 +226,13 @@ add(getRelativeOffset(type, target)); } + /// Same as addRelativeOffset(), but instead relative to an element in this + /// aggregate, identified by its index. + void addRelativeOffsetToPosition(llvm::IntegerType *type, + llvm::Constant *target, size_t position) { + add(getRelativeOffsetToPosition(type, target, position)); + } + /// Add a relative offset to the target address, plus a small /// constant offset. This is primarily useful when the relative /// offset is known to be a multiple of (say) four and therefore @@ -298,10 +305,18 @@ /// position to be filled. This is computed with an indexed /// getelementptr rather than by computing offsets. /// - /// The returned pointer will have type T*, where T is the given - /// position. + /// The returned pointer will have type T*, where T is the given type. This + /// type can differ from the type of the actual element. llvm::Constant *getAddrOfCurrentPosition(llvm::Type *type); + /// Produce an address which points to a position in the aggregate being + /// constructed. This is computed with an indexed getelementptr rather than by + /// computing offsets. + /// + /// The returned pointer will have type T*, where T is the given type. This + /// type can differ from the type of the actual element. + llvm::Constant *getAddrOfPosition(llvm::Type *type, size_t position); + llvm::ArrayRef getGEPIndicesToCurrentPosition( llvm::SmallVectorImpl &indices) { getGEPIndicesTo(indices, Builder.Buffer.size()); @@ -319,6 +334,10 @@ llvm::Constant *getRelativeOffset(llvm::IntegerType *offsetType, llvm::Constant *target); + llvm::Constant *getRelativeOffsetToPosition(llvm::IntegerType *offsetType, + llvm::Constant *target, + size_t position); + CharUnits getOffsetFromGlobalTo(size_t index) const; }; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1299,6 +1299,13 @@ "fno-fine-grained-bitfield-accesses">, Group, Flags<[CC1Option]>, HelpText<"Use large-integer access for consecutive bitfield runs.">; +def fexperimental_relative_cxx_abi_vtables : Flag<["-"], "fexperimental-relative-c++-abi-vtables">, + Group, Flags<[CC1Option]>, + HelpText<"Use the experimental C++ class ABI for classes with virtual tables">; +def fno_experimental_relative_cxx_abi_vtables : Flag<["-"], "fno-experimental-relative-c++-abi-vtables">, + Group, Flags<[CC1Option]>, + HelpText<"Do not use the experimental C++ class ABI for classes with virtual tables">; + def flat__namespace : Flag<["-"], "flat_namespace">; def flax_vector_conversions_EQ : Joined<["-"], "flax-vector-conversions=">, Group, HelpText<"Enable implicit vector bit-casts">, Values<"none,integer,all">, Flags<[CC1Option]>; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -10600,10 +10600,15 @@ VTableContextBase *ASTContext::getVTableContext() { if (!VTContext.get()) { - if (Target->getCXXABI().isMicrosoft()) + auto ABI = Target->getCXXABI(); + if (ABI.isMicrosoft()) VTContext.reset(new MicrosoftVTableContext(*this)); - else - VTContext.reset(new ItaniumVTableContext(*this)); + else { + auto ComponentLayout = getLangOpts().RelativeCXXABIVTables + ? ItaniumVTableContext::Relative + : ItaniumVTableContext::Pointer; + VTContext.reset(new ItaniumVTableContext(*this, ComponentLayout)); + } } return VTContext.get(); } diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp --- a/clang/lib/AST/VTableBuilder.cpp +++ b/clang/lib/AST/VTableBuilder.cpp @@ -535,6 +535,8 @@ VBaseOffsetOffsetsMapTy; private: + const ItaniumVTableContext &VTables; + /// MostDerivedClass - The most derived class for which we're building vcall /// and vbase offsets. const CXXRecordDecl *MostDerivedClass; @@ -583,13 +585,15 @@ CharUnits getCurrentOffsetOffset() const; public: - VCallAndVBaseOffsetBuilder(const CXXRecordDecl *MostDerivedClass, + VCallAndVBaseOffsetBuilder(const ItaniumVTableContext &VTables, + const CXXRecordDecl *MostDerivedClass, const CXXRecordDecl *LayoutClass, const FinalOverriders *Overriders, BaseSubobject Base, bool BaseIsVirtual, CharUnits OffsetInLayoutClass) - : MostDerivedClass(MostDerivedClass), LayoutClass(LayoutClass), - Context(MostDerivedClass->getASTContext()), Overriders(Overriders) { + : VTables(VTables), MostDerivedClass(MostDerivedClass), + LayoutClass(LayoutClass), Context(MostDerivedClass->getASTContext()), + Overriders(Overriders) { // Add vcall and vbase offsets. AddVCallAndVBaseOffsets(Base, BaseIsVirtual, OffsetInLayoutClass); @@ -662,9 +666,13 @@ // vcall offset itself). int64_t OffsetIndex = -(int64_t)(3 + Components.size()); - CharUnits PointerWidth = - Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); - CharUnits OffsetOffset = PointerWidth * OffsetIndex; + // Under the relative ABI, the offset widths are 32-bit ints instead of + // pointer widths. + CharUnits OffsetWidth = Context.toCharUnitsFromBits( + VTables.isRelativeLayout() ? 32 + : Context.getTargetInfo().getPointerWidth(0)); + CharUnits OffsetOffset = OffsetWidth * OffsetIndex; + return OffsetOffset; } @@ -1271,13 +1279,13 @@ if (VCallOffsets.empty()) { // We don't have vcall offsets for this virtual base, go ahead and // build them. - VCallAndVBaseOffsetBuilder Builder(MostDerivedClass, MostDerivedClass, - /*Overriders=*/nullptr, - BaseSubobject(Offset.VirtualBase, - CharUnits::Zero()), - /*BaseIsVirtual=*/true, - /*OffsetInLayoutClass=*/ - CharUnits::Zero()); + VCallAndVBaseOffsetBuilder Builder( + VTables, MostDerivedClass, MostDerivedClass, + /*Overriders=*/nullptr, + BaseSubobject(Offset.VirtualBase, CharUnits::Zero()), + /*BaseIsVirtual=*/true, + /*OffsetInLayoutClass=*/ + CharUnits::Zero()); VCallOffsets = Builder.getVCallOffsets(); } @@ -1635,9 +1643,9 @@ VTableIndices.push_back(VTableIndex); // Add vcall and vbase offsets for this vtable. - VCallAndVBaseOffsetBuilder Builder(MostDerivedClass, LayoutClass, &Overriders, - Base, BaseIsVirtualInLayoutClass, - OffsetInLayoutClass); + VCallAndVBaseOffsetBuilder Builder( + VTables, MostDerivedClass, LayoutClass, &Overriders, Base, + BaseIsVirtualInLayoutClass, OffsetInLayoutClass); Components.append(Builder.components_begin(), Builder.components_end()); // Check if we need to add these vcall offsets. @@ -2200,12 +2208,40 @@ } } +static VTableLayout::AddressPointsIndexMapTy +MakeAddressPointIndices(const VTableLayout::AddressPointsMapTy &addressPoints, + unsigned numVTables) { + VTableLayout::AddressPointsIndexMapTy indexMap(numVTables); + + for (auto it = addressPoints.begin(); it != addressPoints.end(); ++it) { + const auto &addressPointLoc = it->second; + unsigned vtableIndex = addressPointLoc.VTableIndex; + unsigned addressPoint = addressPointLoc.AddressPointIndex; + if (indexMap[vtableIndex]) { + // Multiple BaseSubobjects can map to the same AddressPointLocation, but + // every vtable index should have a unique address point. + assert(indexMap[vtableIndex] == addressPoint && + "Every vtable index should have a unique address point. Found a " + "vtable that has two different address points."); + } else { + indexMap[vtableIndex] = addressPoint; + } + } + + // Note that by this point, not all the address may be initialized if the + // AddressPoints map is empty. This is ok if the map isn't needed. See + // MicrosoftVTableContext::computeVTableRelatedInformation() which uses an + // emprt map. + return indexMap; +} + VTableLayout::VTableLayout(ArrayRef VTableIndices, ArrayRef VTableComponents, ArrayRef VTableThunks, const AddressPointsMapTy &AddressPoints) : VTableComponents(VTableComponents), VTableThunks(VTableThunks), - AddressPoints(AddressPoints) { + AddressPoints(AddressPoints), AddressPointIndices(MakeAddressPointIndices( + AddressPoints, VTableIndices.size())) { if (VTableIndices.size() <= 1) assert(VTableIndices.size() == 1 && VTableIndices[0] == 0); else @@ -2221,8 +2257,9 @@ VTableLayout::~VTableLayout() { } -ItaniumVTableContext::ItaniumVTableContext(ASTContext &Context) - : VTableContextBase(/*MS=*/false) {} +ItaniumVTableContext::ItaniumVTableContext( + ASTContext &Context, VTableComponentLayout ComponentLayout) + : VTableContextBase(/*MS=*/false), ComponentLayout(ComponentLayout) {} ItaniumVTableContext::~ItaniumVTableContext() {} @@ -2251,7 +2288,7 @@ if (I != VirtualBaseClassOffsetOffsets.end()) return I->second; - VCallAndVBaseOffsetBuilder Builder(RD, RD, /*Overriders=*/nullptr, + VCallAndVBaseOffsetBuilder Builder(*this, RD, RD, /*Overriders=*/nullptr, BaseSubobject(RD, CharUnits::Zero()), /*BaseIsVirtual=*/false, /*OffsetInLayoutClass=*/CharUnits::Zero()); diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -253,8 +253,13 @@ // Compute the offset from the static and dynamic components. llvm::Value *baseOffset; if (!nonVirtualOffset.isZero()) { - baseOffset = llvm::ConstantInt::get(CGF.PtrDiffTy, - nonVirtualOffset.getQuantity()); + llvm::Type *OffsetType = + (CGF.CGM.getTarget().getCXXABI().isItaniumFamily() && + CGF.CGM.getItaniumVTableContext().isRelativeLayout()) + ? CGF.Int32Ty + : CGF.PtrDiffTy; + baseOffset = + llvm::ConstantInt::get(OffsetType, nonVirtualOffset.getQuantity()); if (virtualOffset) { baseOffset = CGF.Builder.CreateAdd(virtualOffset, baseOffset); } diff --git a/clang/lib/CodeGen/CGVTables.h b/clang/lib/CodeGen/CGVTables.h --- a/clang/lib/CodeGen/CGVTables.h +++ b/clang/lib/CodeGen/CGVTables.h @@ -62,16 +62,39 @@ bool ForVTable); void addVTableComponent(ConstantArrayBuilder &builder, - const VTableLayout &layout, unsigned idx, - llvm::Constant *rtti, - unsigned &nextVTableThunkIndex); + const VTableLayout &layout, unsigned componentIndex, + llvm::Constant *rtti, unsigned &nextVTableThunkIndex, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage); + + /// Add a 32-bit offset to a component relative to the vtable when using the + /// relative vtables ABI. The array builder points to the start of the vtable. + void addRelativeComponent(ConstantArrayBuilder &builder, + llvm::Constant *component, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage, + bool isCompleteDtor) const; + + /// Create a dso_local stub that will be used for a relative reference in the + /// relative vtable layout. This stub will just be a tail call to the original + /// function and propagate any function attributes from the original. If the + /// original function is already dso_local, the original is returned instead + /// and a stub is not created. + llvm::Function * + getOrCreateRelativeStub(llvm::Function *func, + llvm::GlobalValue::LinkageTypes stubLinkage, + bool isCompleteDtor) const; + + bool useRelativeLayout() const; + + llvm::Type *getVTableComponentType() const; public: /// Add vtable components for the given vtable layout to the given /// global initializer. void createVTableInitializer(ConstantStructBuilder &builder, - const VTableLayout &layout, - llvm::Constant *rtti); + const VTableLayout &layout, llvm::Constant *rtti, + bool vtableHasLocalLinkage); CodeGenVTables(CodeGenModule &CGM); @@ -124,6 +147,13 @@ /// arrays of pointers, with one struct element for each vtable in the vtable /// group. llvm::Type *getVTableType(const VTableLayout &layout); + + /// Generate a public facing alias for the vtable and make the vtable either + /// hidden or private. The alias will have the original linkage and visibility + /// of the vtable. This is used for cases under the relative vtables ABI + /// when a vtable may not be dso_local. + void GenerateRelativeVTableAlias(llvm::GlobalVariable *VTable, + llvm::StringRef AliasName); }; } // end namespace CodeGen diff --git a/clang/lib/CodeGen/CGVTables.cpp b/clang/lib/CodeGen/CGVTables.cpp --- a/clang/lib/CodeGen/CGVTables.cpp +++ b/clang/lib/CodeGen/CGVTables.cpp @@ -618,29 +618,178 @@ maybeEmitThunk(GD, Thunk, /*ForVTable=*/false); } -void CodeGenVTables::addVTableComponent( - ConstantArrayBuilder &builder, const VTableLayout &layout, - unsigned idx, llvm::Constant *rtti, unsigned &nextVTableThunkIndex) { - auto &component = layout.vtable_components()[idx]; +void CodeGenVTables::addRelativeComponent(ConstantArrayBuilder &builder, + llvm::Constant *component, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage, + bool isCompleteDtor) const { + // No need to get the offset of a nullptr. + if (component->isNullValue()) + return builder.add(llvm::ConstantInt::get(CGM.Int32Ty, 0)); + + auto *globalVal = + cast(component->stripPointerCastsAndAliases()); + llvm::Module &module = CGM.getModule(); + + // We don't want to copy the linkage of the vtable exactly because we still + // want the stub/proxy to be emitted for properly calculating the offset. + // Examples where there would be no symbol emitted are available_externally + // and private linkages. + auto stubLinkage = vtableHasLocalLinkage ? llvm::GlobalValue::InternalLinkage + : llvm::GlobalValue::ExternalLinkage; + + llvm::Constant *target; + if (auto *func = dyn_cast(globalVal)) { + target = getOrCreateRelativeStub(func, stubLinkage, isCompleteDtor); + } else { + llvm::SmallString<16> rttiProxyName(globalVal->getName()); + rttiProxyName.append(".rtti_proxy"); + + // The RTTI component may not always be emitted in the same linkage unit as + // the vtable. As a general case, we can make a dso_local proxy to the RTTI + // that points to the actual RTTI struct somewhere. This will result in a + // GOTPCREL relocation when taking the relative offset to the proxy. + llvm::GlobalVariable *proxy = module.getNamedGlobal(rttiProxyName); + if (!proxy) { + proxy = new llvm::GlobalVariable(module, globalVal->getType(), + /*isConstant=*/true, stubLinkage, + globalVal, rttiProxyName); + proxy->setDSOLocal(true); + proxy->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + if (!proxy->hasLocalLinkage()) { + proxy->setVisibility(llvm::GlobalValue::HiddenVisibility); + proxy->setComdat(module.getOrInsertComdat(rttiProxyName)); + } + } + target = proxy; + } + + builder.addRelativeOffsetToPosition(CGM.Int32Ty, target, + /*position=*/vtableAddressPoint); +} + +llvm::Function *CodeGenVTables::getOrCreateRelativeStub( + llvm::Function *func, llvm::GlobalValue::LinkageTypes stubLinkage, + bool isCompleteDtor) const { + // A complete object destructor can later be substituted in the vtable for an + // appropriate base object destructor when optimizations are enabled. This can + // happen for child classes that don't have their own destructor. In the case + // where a parent virtual destructor is not guaranteed to be in the same + // linkage unit as the child vtable, it's possible for an external reference + // for this destructor to be substituted into the child vtable, preventing it + // from being in rodata. If this function is a complete virtual destructor, we + // can just force a stub to be emitted for it. + if (func->isDSOLocal() && !isCompleteDtor) + return func; + + llvm::SmallString<16> stubName(func->getName()); + stubName.append(".stub"); + + // Instead of taking the offset between the vtable and virtual function + // directly, we emit a dso_local stub that just contains a tail call to the + // original virtual function and take the offset between that and the + // vtable. We do this because there are some cases where the original + // function that would've been inserted into the vtable is not dso_local + // which may require some kind of dynamic relocation which prevents the + // vtable from being readonly. On x86_64, taking the offset between the + // function and the vtable gets lowered to the offset between the PLT entry + // for the function and the vtable which gives us a PLT32 reloc. On AArch64, + // right now only CALL26 and JUMP26 instructions generate PLT relocations, + // so we manifest them with stubs that are just jumps to the original + // function. + auto &module = CGM.getModule(); + llvm::Function *stub = module.getFunction(stubName); + if (stub) { + assert(stub->isDSOLocal() && + "The previous definition of this stub should've been dso_local."); + return stub; + } + + stub = llvm::Function::Create(func->getFunctionType(), stubLinkage, stubName, + module); + + // Propogate function attributes. + stub->setAttributes(func->getAttributes()); + + stub->setDSOLocal(true); + stub->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + if (!stub->hasLocalLinkage()) { + stub->setVisibility(llvm::GlobalValue::HiddenVisibility); + stub->setComdat(module.getOrInsertComdat(stubName)); + } + + // Fill the stub with a tail call that will be optimized. + llvm::BasicBlock *block = + llvm::BasicBlock::Create(module.getContext(), "entry", stub); + llvm::IRBuilder<> block_builder(block); + llvm::SmallVector args; + for (auto &arg : stub->args()) + args.push_back(&arg); + llvm::CallInst *call = block_builder.CreateCall(func, args); + call->setAttributes(func->getAttributes()); + call->setTailCall(); + if (call->getType()->isVoidTy()) + block_builder.CreateRetVoid(); + else + block_builder.CreateRet(call); + + return stub; +} + +bool CodeGenVTables::useRelativeLayout() const { + return CGM.getTarget().getCXXABI().isItaniumFamily() && + CGM.getItaniumVTableContext().isRelativeLayout(); +} + +llvm::Type *CodeGenVTables::getVTableComponentType() const { + if (useRelativeLayout()) + return CGM.Int32Ty; + return CGM.Int8PtrTy; +} + +static void AddPointerLayoutOffset(const CodeGenModule &CGM, + ConstantArrayBuilder &builder, + CharUnits offset) { + builder.add(llvm::ConstantExpr::getIntToPtr( + llvm::ConstantInt::get(CGM.PtrDiffTy, offset.getQuantity()), + CGM.Int8PtrTy)); +} - auto addOffsetConstant = [&](CharUnits offset) { - builder.add(llvm::ConstantExpr::getIntToPtr( - llvm::ConstantInt::get(CGM.PtrDiffTy, offset.getQuantity()), - CGM.Int8PtrTy)); - }; +static void AddRelativeLayoutOffset(const CodeGenModule &CGM, + ConstantArrayBuilder &builder, + CharUnits offset) { + builder.add(llvm::ConstantInt::get(CGM.Int32Ty, offset.getQuantity())); +} + +void CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder, + const VTableLayout &layout, + unsigned componentIndex, + llvm::Constant *rtti, + unsigned &nextVTableThunkIndex, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage) { + auto &component = layout.vtable_components()[componentIndex]; + + auto addOffsetConstant = + useRelativeLayout() ? AddRelativeLayoutOffset : AddPointerLayoutOffset; switch (component.getKind()) { case VTableComponent::CK_VCallOffset: - return addOffsetConstant(component.getVCallOffset()); + return addOffsetConstant(CGM, builder, component.getVCallOffset()); case VTableComponent::CK_VBaseOffset: - return addOffsetConstant(component.getVBaseOffset()); + return addOffsetConstant(CGM, builder, component.getVBaseOffset()); case VTableComponent::CK_OffsetToTop: - return addOffsetConstant(component.getOffsetToTop()); + return addOffsetConstant(CGM, builder, component.getOffsetToTop()); case VTableComponent::CK_RTTI: - return builder.add(llvm::ConstantExpr::getBitCast(rtti, CGM.Int8PtrTy)); + if (useRelativeLayout()) + return addRelativeComponent(builder, rtti, vtableAddressPoint, + vtableHasLocalLinkage, + /*isCompleteDtor=*/false); + else + return builder.add(llvm::ConstantExpr::getBitCast(rtti, CGM.Int8PtrTy)); case VTableComponent::CK_FunctionPointer: case VTableComponent::CK_CompleteDtorPointer: @@ -674,11 +823,21 @@ ? MD->hasAttr() : (MD->hasAttr() || !MD->hasAttr()); if (!CanEmitMethod) - return builder.addNullPointer(CGM.Int8PtrTy); + return builder.add(llvm::ConstantExpr::getNullValue(CGM.Int8PtrTy)); // Method is acceptable, continue processing as usual. } auto getSpecialVirtualFn = [&](StringRef name) -> llvm::Constant * { + // FIXME(PR43094): When merging comdat groups, lld can select a local + // symbol as the signature symbol even though it cannot be accessed + // outside that symbol's TU. The relative vtables ABI would make + // __cxa_pure_virtual and __cxa_deleted_virtual local symbols, and + // depending on link order, the comdat groups could resolve to the one + // with the local symbol. As a temporary solution, fill these components + // with zero. We shouldn't be calling these in the first place anyway. + if (useRelativeLayout()) + return llvm::ConstantPointerNull::get(CGM.Int8PtrTy); + // For NVPTX devices in OpenMP emit special functon as null pointers, // otherwise linking ends up with unresolved references. if (CGM.getLangOpts().OpenMP && CGM.getLangOpts().OpenMPIsDevice && @@ -699,19 +858,20 @@ if (cast(GD.getDecl())->isPure()) { if (!PureVirtualFn) PureVirtualFn = - getSpecialVirtualFn(CGM.getCXXABI().GetPureVirtualCallName()); + getSpecialVirtualFn(CGM.getCXXABI().GetPureVirtualCallName()); fnPtr = PureVirtualFn; // Deleted virtual member functions. } else if (cast(GD.getDecl())->isDeleted()) { if (!DeletedVirtualFn) DeletedVirtualFn = - getSpecialVirtualFn(CGM.getCXXABI().GetDeletedVirtualCallName()); + getSpecialVirtualFn(CGM.getCXXABI().GetDeletedVirtualCallName()); fnPtr = DeletedVirtualFn; // Thunks. } else if (nextVTableThunkIndex < layout.vtable_thunks().size() && - layout.vtable_thunks()[nextVTableThunkIndex].first == idx) { + layout.vtable_thunks()[nextVTableThunkIndex].first == + componentIndex) { auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; nextVTableThunkIndex++; @@ -723,13 +883,19 @@ fnPtr = CGM.GetAddrOfFunction(GD, fnTy, /*ForVTable=*/true); } - fnPtr = llvm::ConstantExpr::getBitCast(fnPtr, CGM.Int8PtrTy); - builder.add(fnPtr); - return; + if (useRelativeLayout()) { + return addRelativeComponent( + builder, fnPtr, vtableAddressPoint, vtableHasLocalLinkage, + component.getKind() == VTableComponent::CK_CompleteDtorPointer); + } else + return builder.add(llvm::ConstantExpr::getBitCast(fnPtr, CGM.Int8PtrTy)); } case VTableComponent::CK_UnusedFunctionPointer: - return builder.addNullPointer(CGM.Int8PtrTy); + if (useRelativeLayout()) + return builder.add(llvm::ConstantExpr::getNullValue(CGM.Int32Ty)); + else + return builder.addNullPointer(CGM.Int8PtrTy); } llvm_unreachable("Unexpected vtable component kind"); @@ -737,34 +903,41 @@ llvm::Type *CodeGenVTables::getVTableType(const VTableLayout &layout) { SmallVector tys; - for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) { - tys.push_back(llvm::ArrayType::get(CGM.Int8PtrTy, layout.getVTableSize(i))); - } + llvm::Type *componentType = getVTableComponentType(); + for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) + tys.push_back(llvm::ArrayType::get(componentType, layout.getVTableSize(i))); return llvm::StructType::get(CGM.getLLVMContext(), tys); } void CodeGenVTables::createVTableInitializer(ConstantStructBuilder &builder, const VTableLayout &layout, - llvm::Constant *rtti) { + llvm::Constant *rtti, + bool vtableHasLocalLinkage) { + llvm::Type *componentType = getVTableComponentType(); + + const auto &addressPoints = layout.getAddressPointIndices(); unsigned nextVTableThunkIndex = 0; - for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) { - auto vtableElem = builder.beginArray(CGM.Int8PtrTy); - size_t thisIndex = layout.getVTableOffset(i); - size_t nextIndex = thisIndex + layout.getVTableSize(i); - for (unsigned i = thisIndex; i != nextIndex; ++i) { - addVTableComponent(vtableElem, layout, i, rtti, nextVTableThunkIndex); + for (unsigned vtableIndex = 0, endIndex = layout.getNumVTables(); + vtableIndex != endIndex; ++vtableIndex) { + auto vtableElem = builder.beginArray(componentType); + + size_t vtableStart = layout.getVTableOffset(vtableIndex); + size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex); + for (size_t componentIndex = vtableStart; componentIndex < vtableEnd; + ++componentIndex) { + addVTableComponent(vtableElem, layout, componentIndex, rtti, + nextVTableThunkIndex, addressPoints[vtableIndex], + vtableHasLocalLinkage); } vtableElem.finishAndAddTo(builder); } } -llvm::GlobalVariable * -CodeGenVTables::GenerateConstructionVTable(const CXXRecordDecl *RD, - const BaseSubobject &Base, - bool BaseIsVirtual, - llvm::GlobalVariable::LinkageTypes Linkage, - VTableAddressPointsMapTy& AddressPoints) { +llvm::GlobalVariable *CodeGenVTables::GenerateConstructionVTable( + const CXXRecordDecl *RD, const BaseSubobject &Base, bool BaseIsVirtual, + llvm::GlobalVariable::LinkageTypes Linkage, + VTableAddressPointsMapTy &AddressPoints) { if (CGDebugInfo *DI = CGM.getModuleDebugInfo()) DI->completeClassData(Base.getBase()); @@ -781,7 +954,14 @@ cast(CGM.getCXXABI().getMangleContext()) .mangleCXXCtorVTable(RD, Base.getBaseOffset().getQuantity(), Base.getBase(), Out); - StringRef Name = OutName.str(); + SmallString<256> Name(OutName); + + bool UsingRelativeLayout = getItaniumVTableContext().isRelativeLayout(); + bool VTableAliasExists = UsingRelativeLayout && CGM.getModule().getNamedAlias(Name); + if (VTableAliasExists) { + // We previously made the vtable hidden and changed its name. + Name.append(".local"); + } llvm::Type *VTType = getVTableType(*VTLayout); @@ -808,7 +988,8 @@ // Create and set the initializer. ConstantInitBuilder builder(CGM); auto components = builder.beginStruct(); - createVTableInitializer(components, *VTLayout, RTTI); + createVTableInitializer(components, *VTLayout, RTTI, + VTable->hasLocalLinkage()); components.finishAndSetAsInitializer(VTable); // Set properties only after the initializer has been set to ensure that the @@ -818,9 +999,64 @@ CGM.EmitVTableTypeMetadata(RD, VTable, *VTLayout.get()); + if (UsingRelativeLayout && !VTable->isDSOLocal()) + GenerateRelativeVTableAlias(VTable, OutName); + return VTable; } +// If the VTable is not dso_local, then we will not be able to indicate that +// the VTable does not need a relocation and move into rodata. An frequent +// time this can occur is for classes that should be made public from a DSO +// (like in libc++). For cases like these, we can make the vtable hidden or +// private and create a public alias with the same visibility and linkage as +// the original vtable type. +void CodeGenVTables::GenerateRelativeVTableAlias(llvm::GlobalVariable *VTable, + llvm::StringRef AliasName) { + assert(getItaniumVTableContext().isRelativeLayout() && + "Can only use this if the relative vtable ABI is used"); + assert(!VTable->isDSOLocal() && "This should be called only if the vtable is " + "not guaranteed to be dso_local"); + + // If the vtable is available_externally, we shouldn't (or need to) generate + // an alias for it in the first place since the vtable won't actually by + // emitted in this compilation unit. + if (VTable->hasAvailableExternallyLinkage()) + return; + + VTable->setName(AliasName + ".local"); + + auto Linkage = VTable->getLinkage(); + assert(llvm::GlobalAlias::isValidLinkage(Linkage) && + "Invalid vtable alias linkage"); + + llvm::GlobalAlias *VTableAlias = CGM.getModule().getNamedAlias(AliasName); + if (!VTableAlias) { + VTableAlias = llvm::GlobalAlias::create( + VTable->getValueType(), VTable->getAddressSpace(), Linkage, AliasName, + &CGM.getModule()); + } else { + assert(VTableAlias->getValueType() == VTable->getValueType()); + assert(VTableAlias->getLinkage() == Linkage); + } + VTableAlias->setVisibility(VTable->getVisibility()); + VTableAlias->setUnnamedAddr(VTable->getUnnamedAddr()); + + // Both of these imply dso_local for the vtable. + if (!VTable->hasComdat()) { + // If this is in a comdat, then we shouldn't make the linkage private due to + // an issue in lld where private symbols can be used as the key symbol when + // choosing the prevelant group. This leads to "relocation refers to a + // symbol in a discarded section". + VTable->setLinkage(llvm::GlobalValue::PrivateLinkage); + } else { + // We should at least make this hidden since we don't want to expose it. + VTable->setVisibility(llvm::GlobalValue::HiddenVisibility); + } + + VTableAlias->setAliasee(VTable); +} + static bool shouldEmitAvailableExternallyVTable(const CodeGenModule &CGM, const CXXRecordDecl *RD) { return CGM.getCodeGenOpts().OptimizationLevel > 0 && diff --git a/clang/lib/CodeGen/ConstantInitBuilder.cpp b/clang/lib/CodeGen/ConstantInitBuilder.cpp --- a/clang/lib/CodeGen/ConstantInitBuilder.cpp +++ b/clang/lib/CodeGen/ConstantInitBuilder.cpp @@ -128,8 +128,14 @@ llvm::Constant * ConstantAggregateBuilderBase::getRelativeOffset(llvm::IntegerType *offsetType, llvm::Constant *target) { + return getRelativeOffsetToPosition(offsetType, target, + Builder.SelfReferences.size()); +} + +llvm::Constant *ConstantAggregateBuilderBase::getRelativeOffsetToPosition( + llvm::IntegerType *offsetType, llvm::Constant *target, size_t position) { // Compute the address of the relative-address slot. - auto base = getAddrOfCurrentPosition(offsetType); + auto base = getAddrOfPosition(offsetType, position); // Subtract. base = llvm::ConstantExpr::getPtrToInt(base, Builder.CGM.IntPtrTy); @@ -145,6 +151,20 @@ } llvm::Constant * +ConstantAggregateBuilderBase::getAddrOfPosition(llvm::Type *type, + size_t position) { + // Make a global variable. We will replace this with a GEP to this + // position after installing the initializer. + auto dummy = new llvm::GlobalVariable(Builder.CGM.getModule(), type, true, + llvm::GlobalVariable::PrivateLinkage, + nullptr, ""); + Builder.SelfReferences.emplace_back(dummy); + auto &entry = Builder.SelfReferences.back(); + (void)getGEPIndicesTo(entry.Indices, position + Begin); + return dummy; +} + +llvm::Constant * ConstantAggregateBuilderBase::getAddrOfCurrentPosition(llvm::Type *type) { // Make a global variable. We will replace this with a GEP to this // position after installing the initializer. diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -703,9 +703,9 @@ TypeId = llvm::MetadataAsValue::get(CGF.getLLVMContext(), MD); } - llvm::Value *VFPAddr = Builder.CreateGEP(VTable, VTableOffset); - if (ShouldEmitVFEInfo) { + llvm::Value *VFPAddr = Builder.CreateGEP(VTable, VTableOffset); + // If doing VFE, load from the vtable with a type.checked.load intrinsic // call. Note that we use the GEP to calculate the address to load from // and pass 0 as the offset to the intrinsic. This is because every @@ -722,14 +722,25 @@ // When not doing VFE, emit a normal load, as it allows more // optimisations than type.checked.load. if (ShouldEmitCFICheck || ShouldEmitWPDInfo) { + llvm::Value *VFPAddr = Builder.CreateGEP(VTable, VTableOffset); CheckResult = Builder.CreateCall( CGM.getIntrinsic(llvm::Intrinsic::type_test), {Builder.CreateBitCast(VFPAddr, CGF.Int8PtrTy), TypeId}); } - VFPAddr = - Builder.CreateBitCast(VFPAddr, FTy->getPointerTo()->getPointerTo()); - VirtualFn = Builder.CreateAlignedLoad(VFPAddr, CGF.getPointerAlign(), - "memptr.virtualfn"); + + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + VirtualFn = CGF.Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::load_relative, + {VTableOffset->getType()}), + {VTable, VTableOffset}); + VirtualFn = CGF.Builder.CreateBitCast(VirtualFn, FTy->getPointerTo()); + } else { + llvm::Value *VFPAddr = CGF.Builder.CreateGEP(VTable, VTableOffset); + VFPAddr = CGF.Builder.CreateBitCast( + VFPAddr, FTy->getPointerTo()->getPointerTo()); + VirtualFn = CGF.Builder.CreateAlignedLoad( + VFPAddr, CGF.getPointerAlign(), "memptr.virtualfn"); + } } assert(VirtualFn && "Virtual fuction pointer not created!"); assert((!ShouldEmitCFICheck || !ShouldEmitVFEInfo || !ShouldEmitWPDInfo || @@ -1004,11 +1015,16 @@ llvm::Constant *MemPtr[2]; if (MD->isVirtual()) { uint64_t Index = CGM.getItaniumVTableContext().getMethodVTableIndex(MD); - - const ASTContext &Context = getContext(); - CharUnits PointerWidth = - Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); - uint64_t VTableOffset = (Index * PointerWidth.getQuantity()); + uint64_t VTableOffset; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + // Multiply by 4-byte relative offsets. + VTableOffset = Index * 4; + } else { + const ASTContext &Context = getContext(); + CharUnits PointerWidth = Context.toCharUnitsFromBits( + Context.getTargetInfo().getPointerWidth(0)); + VTableOffset = Index * PointerWidth.getQuantity(); + } if (UseARMMethodPtrABI) { // ARM C++ ABI 3.2.1: @@ -1422,8 +1438,19 @@ llvm::Value *Value = CGF.GetVTablePtr(ThisPtr, StdTypeInfoPtrTy->getPointerTo(), ClassDecl); - // Load the type info. - Value = CGF.Builder.CreateConstInBoundsGEP1_64(Value, -1ULL); + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + // Load the type info. + Value = CGF.Builder.CreateBitCast(Value, CGM.Int8PtrTy); + Value = CGF.Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::load_relative, {CGM.Int32Ty}), + {Value, llvm::ConstantInt::get(CGM.Int32Ty, -4)}); + + // Setup to dereference again since this is a proxy we accessed. + Value = CGF.Builder.CreateBitCast(Value, StdTypeInfoPtrTy->getPointerTo()); + } else { + // Load the type info. + Value = CGF.Builder.CreateConstInBoundsGEP1_64(Value, -1ULL); + } return CGF.Builder.CreateAlignedLoad(Value, CGF.getPointerAlign()); } @@ -1479,28 +1506,37 @@ Address ThisAddr, QualType SrcRecordTy, QualType DestTy) { - llvm::Type *PtrDiffLTy = - CGF.ConvertType(CGF.getContext().getPointerDiffType()); llvm::Type *DestLTy = CGF.ConvertType(DestTy); - auto *ClassDecl = cast(SrcRecordTy->castAs()->getDecl()); - // Get the vtable pointer. - llvm::Value *VTable = CGF.GetVTablePtr(ThisAddr, PtrDiffLTy->getPointerTo(), - ClassDecl); + llvm::Value *OffsetToTop; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + // Get the vtable pointer. + llvm::Value *VTable = + CGF.GetVTablePtr(ThisAddr, CGM.Int32Ty->getPointerTo(), ClassDecl); + + // Get the offset-to-top from the vtable. + OffsetToTop = + CGF.Builder.CreateConstInBoundsGEP1_32(/*Type=*/nullptr, VTable, -2U); + OffsetToTop = CGF.Builder.CreateAlignedLoad( + OffsetToTop, CharUnits::fromQuantity(4), "offset.to.top"); + } else { + llvm::Type *PtrDiffLTy = + CGF.ConvertType(CGF.getContext().getPointerDiffType()); - // Get the offset-to-top from the vtable. - llvm::Value *OffsetToTop = - CGF.Builder.CreateConstInBoundsGEP1_64(VTable, -2ULL); - OffsetToTop = - CGF.Builder.CreateAlignedLoad(OffsetToTop, CGF.getPointerAlign(), - "offset.to.top"); + // Get the vtable pointer. + llvm::Value *VTable = + CGF.GetVTablePtr(ThisAddr, PtrDiffLTy->getPointerTo(), ClassDecl); + // Get the offset-to-top from the vtable. + OffsetToTop = CGF.Builder.CreateConstInBoundsGEP1_64(VTable, -2ULL); + OffsetToTop = CGF.Builder.CreateAlignedLoad( + OffsetToTop, CGF.getPointerAlign(), "offset.to.top"); + } // Finally, add the offset to the pointer. llvm::Value *Value = ThisAddr.getPointer(); Value = CGF.EmitCastToVoidPtr(Value); Value = CGF.Builder.CreateInBoundsGEP(Value, OffsetToTop); - return CGF.Builder.CreateBitCast(Value, DestLTy); } @@ -1521,17 +1557,22 @@ CharUnits VBaseOffsetOffset = CGM.getItaniumVTableContext().getVirtualBaseOffsetOffset(ClassDecl, BaseClassDecl); - llvm::Value *VBaseOffsetPtr = CGF.Builder.CreateConstGEP1_64(VTablePtr, VBaseOffsetOffset.getQuantity(), "vbase.offset.ptr"); - VBaseOffsetPtr = CGF.Builder.CreateBitCast(VBaseOffsetPtr, - CGM.PtrDiffTy->getPointerTo()); - - llvm::Value *VBaseOffset = - CGF.Builder.CreateAlignedLoad(VBaseOffsetPtr, CGF.getPointerAlign(), - "vbase.offset"); + llvm::Value *VBaseOffset; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + VBaseOffsetPtr = + CGF.Builder.CreateBitCast(VBaseOffsetPtr, CGF.Int32Ty->getPointerTo()); + VBaseOffset = CGF.Builder.CreateAlignedLoad( + VBaseOffsetPtr, CharUnits::fromQuantity(4), "vbase.offset"); + } else { + VBaseOffsetPtr = CGF.Builder.CreateBitCast(VBaseOffsetPtr, + CGM.PtrDiffTy->getPointerTo()); + VBaseOffset = CGF.Builder.CreateAlignedLoad( + VBaseOffsetPtr, CGF.getPointerAlign(), "vbase.offset"); + } return VBaseOffset; } @@ -1679,10 +1720,11 @@ CGM.GetAddrOfRTTIDescriptor(CGM.getContext().getTagDeclType(RD)); // Create and set the initializer. - ConstantInitBuilder Builder(CGM); - auto Components = Builder.beginStruct(); - CGVT.createVTableInitializer(Components, VTLayout, RTTI); - Components.finishAndSetAsInitializer(VTable); + ConstantInitBuilder builder(CGM); + auto components = builder.beginStruct(); + CGVT.createVTableInitializer(components, VTLayout, RTTI, + llvm::GlobalValue::isLocalLinkage(Linkage)); + components.finishAndSetAsInitializer(VTable); // Set the correct linkage. VTable->setLinkage(Linkage); @@ -1706,6 +1748,9 @@ if (!VTable->isDeclarationForLinker()) CGM.EmitVTableTypeMetadata(RD, VTable, VTLayout); + + if (VTContext.isRelativeLayout() && !VTable->isDSOLocal()) + CGVT.GenerateRelativeVTableAlias(VTable, VTable->getName()); } bool ItaniumCXXABI::isVirtualOffsetNeededForVTableField( @@ -1795,7 +1840,9 @@ // Use pointer alignment for the vtable. Otherwise we would align them based // on the size of the initializer which doesn't make sense as only single // values are read. - unsigned PAlign = CGM.getTarget().getPointerAlign(0); + unsigned PAlign = CGM.getItaniumVTableContext().isRelativeLayout() + ? 32 + : CGM.getTarget().getPointerAlign(0); VTable = CGM.CreateOrReplaceCXXRuntimeVariable( Name, VTableType, llvm::GlobalValue::ExternalLinkage, @@ -1812,9 +1859,9 @@ Address This, llvm::Type *Ty, SourceLocation Loc) { - Ty = Ty->getPointerTo()->getPointerTo(); auto *MethodDecl = cast(GD.getDecl()); - llvm::Value *VTable = CGF.GetVTablePtr(This, Ty, MethodDecl->getParent()); + llvm::Value *VTable = CGF.GetVTablePtr( + This, Ty->getPointerTo()->getPointerTo(), MethodDecl->getParent()); uint64_t VTableIndex = CGM.getItaniumVTableContext().getMethodVTableIndex(GD); llvm::Value *VFunc; @@ -1825,10 +1872,21 @@ } else { CGF.EmitTypeMetadataCodeForVCall(MethodDecl->getParent(), VTable, Loc); - llvm::Value *VFuncPtr = - CGF.Builder.CreateConstInBoundsGEP1_64(VTable, VTableIndex, "vfn"); - auto *VFuncLoad = - CGF.Builder.CreateAlignedLoad(VFuncPtr, CGF.getPointerAlign()); + llvm::Value *VFuncLoad; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + VTable = CGF.Builder.CreateBitCast(VTable, CGM.Int8PtrTy); + llvm::Value *Load = CGF.Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::load_relative, {CGM.Int32Ty}), + {VTable, llvm::ConstantInt::get(CGM.Int32Ty, 4 * VTableIndex)}); + VFuncLoad = CGF.Builder.CreateBitCast(Load, Ty->getPointerTo()); + } else { + VTable = + CGF.Builder.CreateBitCast(VTable, Ty->getPointerTo()->getPointerTo()); + llvm::Value *VTableSlotPtr = + CGF.Builder.CreateConstInBoundsGEP1_64(VTable, VTableIndex, "vfn"); + VFuncLoad = + CGF.Builder.CreateAlignedLoad(VTableSlotPtr, CGF.getPointerAlign()); + } // Add !invariant.load md to virtual function load to indicate that // function didn't change inside vtable. @@ -1837,11 +1895,14 @@ // the same virtual function loads from the same vtable load, which won't // happen without enabled devirtualization with -fstrict-vtable-pointers. if (CGM.getCodeGenOpts().OptimizationLevel > 0 && - CGM.getCodeGenOpts().StrictVTablePointers) - VFuncLoad->setMetadata( - llvm::LLVMContext::MD_invariant_load, - llvm::MDNode::get(CGM.getLLVMContext(), - llvm::ArrayRef())); + CGM.getCodeGenOpts().StrictVTablePointers) { + if (auto *VFuncLoadInstr = dyn_cast(VFuncLoad)) { + VFuncLoadInstr->setMetadata( + llvm::LLVMContext::MD_invariant_load, + llvm::MDNode::get(CGM.getLLVMContext(), + llvm::ArrayRef())); + } + } VFunc = VFuncLoad; } @@ -1958,21 +2019,28 @@ // Perform the virtual adjustment if we have one. llvm::Value *ResultPtr; if (VirtualAdjustment) { - llvm::Type *PtrDiffTy = - CGF.ConvertType(CGF.getContext().getPointerDiffType()); - Address VTablePtrPtr = CGF.Builder.CreateElementBitCast(V, CGF.Int8PtrTy); llvm::Value *VTablePtr = CGF.Builder.CreateLoad(VTablePtrPtr); + llvm::Value *Offset; llvm::Value *OffsetPtr = CGF.Builder.CreateConstInBoundsGEP1_64(VTablePtr, VirtualAdjustment); + if (CGF.CGM.getItaniumVTableContext().isRelativeLayout()) { + // Load the adjustment offset from the vtable as a 32-bit int. + OffsetPtr = + CGF.Builder.CreateBitCast(OffsetPtr, CGF.Int32Ty->getPointerTo()); + Offset = + CGF.Builder.CreateAlignedLoad(OffsetPtr, CharUnits::fromQuantity(4)); + } else { + llvm::Type *PtrDiffTy = + CGF.ConvertType(CGF.getContext().getPointerDiffType()); - OffsetPtr = CGF.Builder.CreateBitCast(OffsetPtr, PtrDiffTy->getPointerTo()); - - // Load the adjustment offset from the vtable. - llvm::Value *Offset = - CGF.Builder.CreateAlignedLoad(OffsetPtr, CGF.getPointerAlign()); + OffsetPtr = + CGF.Builder.CreateBitCast(OffsetPtr, PtrDiffTy->getPointerTo()); + // Load the adjustment offset from the vtable. + Offset = CGF.Builder.CreateAlignedLoad(OffsetPtr, CGF.getPointerAlign()); + } // Adjust our pointer. ResultPtr = CGF.Builder.CreateInBoundsGEP(V.getPointer(), Offset); } else { @@ -3299,17 +3367,32 @@ break; } - llvm::Constant *VTable = - CGM.getModule().getOrInsertGlobal(VTableName, CGM.Int8PtrTy); + llvm::Constant *VTable = nullptr; + + // Check if the alias exists. If it doesn't, then get or create the global. + if (CGM.getItaniumVTableContext().isRelativeLayout()) + VTable = CGM.getModule().getNamedAlias(VTableName); + if (!VTable) + VTable = CGM.getModule().getOrInsertGlobal(VTableName, CGM.Int8PtrTy); + CGM.setDSOLocal(cast(VTable->stripPointerCasts())); llvm::Type *PtrDiffTy = - CGM.getTypes().ConvertType(CGM.getContext().getPointerDiffType()); + CGM.getTypes().ConvertType(CGM.getContext().getPointerDiffType()); // The vtable address point is 2. - llvm::Constant *Two = llvm::ConstantInt::get(PtrDiffTy, 2); - VTable = - llvm::ConstantExpr::getInBoundsGetElementPtr(CGM.Int8PtrTy, VTable, Two); + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + // The vtable address point is 8 bytes after its start: + // 4 for the offset to top + 4 for the relative offset to rtti. + llvm::Constant *Eight = llvm::ConstantInt::get(CGM.Int32Ty, 8); + VTable = llvm::ConstantExpr::getBitCast(VTable, CGM.Int8PtrTy); + VTable = + llvm::ConstantExpr::getInBoundsGetElementPtr(CGM.Int8Ty, VTable, Eight); + } else { + llvm::Constant *Two = llvm::ConstantInt::get(PtrDiffTy, 2); + VTable = llvm::ConstantExpr::getInBoundsGetElementPtr(CGM.Int8PtrTy, VTable, + Two); + } VTable = llvm::ConstantExpr::getBitCast(VTable, CGM.Int8PtrTy); Fields.push_back(VTable); diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -1688,10 +1688,11 @@ [](const VTableComponent &VTC) { return VTC.isRTTIKind(); })) RTTI = getMSCompleteObjectLocator(RD, *Info); - ConstantInitBuilder Builder(CGM); - auto Components = Builder.beginStruct(); - CGVT.createVTableInitializer(Components, VTLayout, RTTI); - Components.finishAndSetAsInitializer(VTable); + ConstantInitBuilder builder(CGM); + auto components = builder.beginStruct(); + CGVT.createVTableInitializer(components, VTLayout, RTTI, + VTable->hasLocalLinkage()); + components.finishAndSetAsInitializer(VTable); emitVTableTypeMetadata(*Info, RD, VTable); } diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -3410,6 +3410,11 @@ Opts.CompatibilityQualifiedIdBlockParamTypeChecking = Args.hasArg(OPT_fcompatibility_qualified_id_block_param_type_checking); + + Opts.RelativeCXXABIVTables = + Args.hasFlag(OPT_fexperimental_relative_cxx_abi_vtables, + OPT_fno_experimental_relative_cxx_abi_vtables, + /*default=*/false); } static bool isStrictlyPreprocessorAction(frontend::ActionKind Action) { diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/child-inheritted-from-parent-in-comdat.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/child-inheritted-from-parent-in-comdat.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/child-inheritted-from-parent-in-comdat.cpp @@ -0,0 +1,57 @@ +// Cross comdat example +// Parent VTable is in a comdat section. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: $_ZN1B3fooEv.stub = comdat any + +// The inline function is emitted in each module with the same comdat +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZTS1A = comdat any +// CHECK: $_ZTI1A = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any + +// The VTable is emitted everywhere used +// CHECK: $_ZTV1A = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// The VTable for B is emitted here since it has a key function which is defined in this module +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// The VTable for A is emitted here and in a comdat section since it has no key function, and is used in this module when creating an instance of A (in func()). +// CHECK: @_ZTV1A.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1A), align 4 + +// CHECK: @_ZTV1B = unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1B.local +// CHECK: @_ZTV1A = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local + +// CHECK: define void @_ZN1B3fooEv(%class.B* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define hidden void @_ZN1B3fooEv.stub(%class.B* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3fooEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + inline virtual void foo() {} +}; +class B : public A { +public: + void foo() override; +}; +void A_foo(A *a); + +void B::foo() {} +void func2() { + A a; + A_foo(&a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/child-vtable-in-comdat.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/child-vtable-in-comdat.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/child-vtable-in-comdat.cpp @@ -0,0 +1,66 @@ +// Cross comdat example +// Child VTable is in a comdat section. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: $_ZN1B3fooEv.stub = comdat any +// CHECK: $_ZN1A3fooEv.stub = comdat any + +// A comdat is emitted for B but not A +// CHECK: $_ZTV1B = comdat any +// CHECK: $_ZTS1B = comdat any +// CHECK: $_ZTI1B = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// VTable for B is emitted here since we access it when creating an instance of B. The VTable is also linkonce_odr and in its own comdat. +// CHECK: @_ZTV1B.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1B), align 4 + +// The RTTI objects aren’t that important, but it is good to know that they are emitted here since they are used in the vtable for B, and external references are used for RTTI stuff from A. +// CHECK: @_ZTVN10__cxxabiv120__si_class_type_infoE = external global i8* +// CHECK: @_ZTS1B = linkonce_odr constant [3 x i8] c"1B\00", comdat, align 1 +// CHECK: @_ZTI1A = external constant i8* +// CHECK: @_ZTI1B = linkonce_odr constant { i8*, i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv120__si_class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1B, i32 0, i32 0), i8* bitcast (i8** @_ZTI1A to i8*) }, comdat, align 8 +// CHECK: @_ZTI1B.rtti_proxy = hidden unnamed_addr constant { i8*, i8*, i8* }* @_ZTI1B, comdat + +// The VTable for A is available_externally, meaning it can have a definition in +// IR, but is never emitted in this compilation unit. Because it won't be +// emitted here, we cannot make an alias, but we won't need to in the first +// place. +// CHECK: @_ZTV1A = available_externally unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint (i8*** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTI1A.rtti_proxy = hidden unnamed_addr constant i8** @_ZTI1A, comdat + +// We will emit a vtable for B here, so it does have an alias, but we will not +// emit one for A. +// CHECK: @_ZTV1B = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1B.local +// CHECK-NOT: @_ZTV1A = {{.*}}alias + +// CHECK: define hidden void @_ZN1B3fooEv.stub(%class.B* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1B3fooEv(%class.B* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: declare void @_ZN1A3fooEv(%class.A*) unnamed_addr + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3fooEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; +class B : public A { +public: + inline void foo() override {} +}; +void A_foo(A *a); + +// func() is used so that the vtable for B is accessed when creating the instance. +void func() { + B b; + A_foo(&b); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-1.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-1.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-1.cpp @@ -0,0 +1,39 @@ +// Check the vtable layout for classes with key functions defined in different +// translation units. This TU only manifests the vtable for A. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +#include "cross-tu-header.h" + +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZN1A3barEv.stub = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// @_ZTV1A = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1A.local + +// A::foo() is still available for other modules to use since it is not marked with private or internal linkage. +// CHECK: define void @_ZN1A3fooEv(%class.A* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// The proxy that we take a reference to in the vtable has hidden visibility and external linkage so it can be used only by other modules in the same DSO. A::foo() is inlined into this stub since it is defined in the same module. +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// A::bar() is called within the module but not defined, even though the VTable for A is emitted here +// CHECK: declare void @_ZN1A3barEv(%class.A*) unnamed_addr + +// The stub for A::bar() is made private, so it will not appear in the symbol table and is only used in this module. We tail call here because A::bar() is not defined in the same module. +// CHECK: define hidden void @_ZN1A3barEv.stub(%class.A* %0) unnamed_addr {{#[0-9]+}} comdat { +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3barEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +void A::foo() {} +void A_foo(A *a) { a->foo(); } +void A_bar(A *a) { a->bar(); } diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-2.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-2.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-2.cpp @@ -0,0 +1,38 @@ +// Check the vtable layout for classes with key functions defined in different +// translation units. This TU manifests the vtable for B. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +#include "cross-tu-header.h" + +// CHECK: $_ZN1B3fooEv.stub = comdat any +// CHECK: $_ZN1A3barEv.stub = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any + +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local + +// A::bar() is defined outside of the module that defines the vtable for A +// CHECK: define void @_ZN1A3barEv(%class.A* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define void @_ZN1B3fooEv(%class.B* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// The stubs for B::foo() and A::bar() are hidden +// CHECK: define hidden void @_ZN1B3fooEv.stub(%class.B* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define hidden void @_ZN1A3barEv.stub(%class.A* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +void A::bar() {} +void B::foo() {} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/cross-tu-header.h b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-tu-header.h new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-tu-header.h @@ -0,0 +1,10 @@ +class A { +public: + virtual void foo(); + virtual void bar(); +}; + +class B : public A { +public: + void foo() override; +}; diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-inheritance.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-inheritance.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-inheritance.cpp @@ -0,0 +1,57 @@ +// Diamond inheritance. +// A more complicated multiple inheritance example that includes longer chain of inheritance and a common ancestor. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: %class.B = type { %class.A } +// CHECK: %class.A = type { i32 (...)** } +// CHECK: %class.C = type { %class.A } +// CHECK: %class.D = type { %class.B, %class.C } + +// VTable for B should contain offset to top (0), RTTI pointer, A::foo(), and B::barB(). +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// VTable for C should contain offset to top (0), RTTI pointer, A::foo(), and C::barC(). +// CHECK: @_ZTV1C.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// VTable for D should be similar to the mutiple inheritance example where this +// vtable contains 2 inner vtables: +// - 1st table containing D::foo(), B::barB(), and D::baz(). +// - 2nd table containing a thunk to D::foo() and C::barC(). +// CHECK: @_ZTV1D.local = private unnamed_addr constant { [5 x i32], [4 x i32] } { [5 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.D*)* @_ZN1D3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.D*)* @_ZN1D3bazEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 2) to i64)) to i32)], [4 x i32] [i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.D*)* @_ZThn8_N1D3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 2) to i64)) to i32)] }, align 4 + +// @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local +// @_ZTV1C = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1C.local +// @_ZTV1D = unnamed_addr alias { [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + virtual void barB(); +}; + +class C : public A { + virtual void barC(); +}; + +// Should be a struct with 2 arrays from 2 parents. +// The 1st contains D::foo(), B::barB(), and D::baz(). +// The 2nd contains C::barC(), and a thunk that points to D::foo(). +class D : public B, C { +public: + virtual void baz(); + void foo() override; +}; + +void B::barB() {} +void C::barC() {} +void D::foo() {} +void D::baz() {} + +void D_foo(D *d) { + d->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-virtual-inheritance.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-virtual-inheritance.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-virtual-inheritance.cpp @@ -0,0 +1,96 @@ +// Diamond virtual inheritance. +// This should cover virtual inheritance, construction vtables, and VTTs. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: %class.B = type { i32 (...)**, %class.A.base } +// CHECK: %class.A.base = type <{ i32 (...)**, i32 }> +// CHECK: %class.C = type { i32 (...)**, %class.A.base } +// CHECK: %class.D = type { %class.B.base, %class.C.base, %class.A.base } +// CHECK: %class.B.base = type { i32 (...)** } +// CHECK: %class.C.base = type { i32 (...)** } + +// Class A contains a vtable ptr, then int, then padding +// CHECK: %class.A = type <{ i32 (...)**, i32, [4 x i8] }> + +// VTable for B. Contains an extra field at the start for the virtual-base offset. +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32], [4 x i32] } { [4 x i32] [i32 8, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// VTT for B +// CHECK: @_ZTT1B = unnamed_addr constant [2 x i8*] [i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// VTable for C +// CHECK: @_ZTV1C.local = private unnamed_addr constant { [4 x i32], [4 x i32] } { [4 x i32] [i32 8, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// VTT for C +// CHECK: @_ZTT1C = unnamed_addr constant [2 x i8*] [i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// VTable for D +// CHECK: @_ZTV1D.local = private unnamed_addr constant { [5 x i32], [4 x i32], [4 x i32] } { [5 x i32] [i32 16, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.D*)* @_ZN1D3bazEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 8, i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -16, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 2, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 2, i32 3) to i64)) to i32)] }, align 4 + +// VTT for D +// CHECK: @_ZTT1D = unnamed_addr constant [7 x i8*] [i8* bitcast (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, inrange i32 1, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, inrange i32 1, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, inrange i32 2, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// Construction vtable for B-in-D +// CHECK: @_ZTC1D0_1B.local = private unnamed_addr constant { [4 x i32], [4 x i32] } { [4 x i32] [i32 16, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -16, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// Construction vtable for C-in-D +// CHECK: @_ZTC1D8_1C.local = private unnamed_addr constant { [4 x i32], [4 x i32] } { [4 x i32] [i32 8, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local +// CHECK: @_ZTV1C = unnamed_addr alias { [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local +// CHECK: @_ZTC1D0_1B = unnamed_addr alias { [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local +// CHECK: @_ZTC1D8_1C = unnamed_addr alias { [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local +// CHECK: @_ZTV1D = unnamed_addr alias { [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local + +// CHECK: define void @_Z5D_fooP1D(%class.D* %d) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[d:%[0-9]+]] = bitcast %class.D* %d to i8** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[d]], align 8 + +// This normally would've been -24 (8 bytes for the virtual call/base offset, 8 +// bytes for the offset to top, and 8 bytes for the RTTI pointer), but since all +// components in the vtable are halved to 4 bytes, the offset is halved to -12. +// CHECK-NEXT: [[vbase_offset_ptr:%[a-z0-9.]+]] = getelementptr i8, i8* [[vtable]], i64 -12 + +// CHECK-NEXT: [[vbase_offset_ptr2:%[a-z0-9.]+]] = bitcast i8* [[vbase_offset_ptr]] to i32* +// CHECK-NEXT: [[vbase_offset:%[a-z0-9.]+]] = load i32, i32* [[vbase_offset_ptr2]], align 4 +// CHECK-NEXT: [[d:%[0-9]+]] = bitcast %class.D* %d to i8* +// CHECK-NEXT: [[vbase_offset2:%.+]] = sext i32 [[vbase_offset]] to i64 +// CHECK-NEXT: [[add_ptr:%[a-z0-9.]+]] = getelementptr inbounds i8, i8* [[d]], i64 [[vbase_offset2]] +// CHECK-NEXT: [[a:%[0-9]+]] = bitcast i8* [[add_ptr]] to %class.A* +// CHECK-NEXT: [[a_i8_ptr:%[0-9]+]] = bitcast i8* [[add_ptr]] to i8** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[a_i8_ptr]], align 8 +// CHECK-NEXT: [[ptr:%[0-9]+]] = call i8* @llvm.load.relative.i32(i8* [[vtable]], i32 0) +// CHECK-NEXT: [[method:%[0-9]+]] = bitcast i8* [[ptr]] to void (%class.A*)* +// CHECK-NEXT: call void [[method]](%class.A* [[a]]) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); + int a; +}; + +class B : public virtual A { +public: + virtual void barB(); +}; + +class C : public virtual A { + virtual void barC(); +}; + +class D : public B, C { +public: + virtual void baz(); +}; + +void B::barB() {} +void C::barC() {} +void D::baz() {} + +void D_foo(D *d) { + d->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/dynamic-cast.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/dynamic-cast.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/dynamic-cast.cpp @@ -0,0 +1,78 @@ +// dynamic_cast +// Ensure that dynamic casting works normally + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O3 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define %class.A* @_Z6upcastP1B(%class.B* readnone %b) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[a:%[0-9]+]] = getelementptr %class.B, %class.B* %b, i64 0, i32 0 +// CHECK-NEXT: ret %class.A* [[a]] +// CHECK-NEXT: } + +// CHECK: define %class.B* @_Z8downcastP1A(%class.A* readonly %a) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[isnull:%[0-9]+]] = icmp eq %class.A* %a, null +// CHECK-NEXT: br i1 [[isnull]], label %[[dynamic_cast_end:[a-z0-9._]+]], label %[[dynamic_cast_notnull:[a-z0-9._]+]] +// CHECK: [[dynamic_cast_notnull]]: +// CHECK-NEXT: [[a:%[0-9]+]] = bitcast %class.A* %a to i8* +// CHECK-NEXT: [[as_b:%[0-9]+]] = tail call i8* @__dynamic_cast(i8* nonnull [[a]], i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*), i8* bitcast ({ i8*, i8*, i8* }* @_ZTI1B to i8*), i64 0) +// CHECK-NEXT: [[b:%[0-9]+]] = bitcast i8* [[as_b]] to %class.B* +// CHECK-NEXT: br label %[[dynamic_cast_end]] +// CHECK: [[dynamic_cast_end]]: +// CHECK-NEXT: [[res:%[0-9]+]] = phi %class.B* [ [[b]], %[[dynamic_cast_notnull]] ], [ null, %entry ] +// CHECK-NEXT: ret %class.B* [[res]] +// CHECK-NEXT: } + +// CHECK: declare i8* @__dynamic_cast(i8*, i8*, i8*, i64) local_unnamed_addr + +// CHECK: define %class.B* @_Z8selfcastP1B(%class.B* readnone returned %b) local_unnamed_addr +// CHECK-NEXT: entry +// CHECK-NEXT: ret %class.B* %b +// CHECK-NEXT: } + +// CHECK: define i8* @_Z9void_castP1B(%class.B* readonly %b) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[isnull:%[0-9]+]] = icmp eq %class.B* %b, null +// CHECK-NEXT: br i1 [[isnull]], label %[[dynamic_cast_end:[a-z0-9._]+]], label %[[dynamic_cast_notnull:[a-z0-9._]+]] +// CHECK: [[dynamic_cast_notnull]]: +// CHECK-NEXT: [[b2:%[0-9]+]] = bitcast %class.B* %b to i32** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i32*, i32** [[b2]], align 8 +// CHECK-NEXT: [[offset_ptr:%.+]] = getelementptr inbounds i32, i32* [[vtable]], i64 -2 +// CHECK-NEXT: [[offset_to_top:%.+]] = load i32, i32* [[offset_ptr]], align 4 +// CHECK-NEXT: [[b:%[0-9]+]] = bitcast %class.B* %b to i8* +// CHECK-NEXT: [[offset_to_top2:%.+]] = sext i32 [[offset_to_top]] to i64 +// CHECK-NEXT: [[casted:%.+]] = getelementptr inbounds i8, i8* [[b]], i64 [[offset_to_top2]] +// CHECK-NEXT: br label %[[dynamic_cast_end]] +// CHECK: [[dynamic_cast_end]]: +// CHECK-NEXT: [[res:%[0-9]+]] = phi i8* [ [[casted]], %[[dynamic_cast_notnull]] ], [ null, %entry ] +// CHECK-NEXT: ret i8* [[res]] +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + void foo() override; +}; + +void A::foo() {} +void B::foo() {} + +A *upcast(B *b) { + return dynamic_cast(b); +} + +B *downcast(A *a) { + return dynamic_cast(a); +} + +B *selfcast(B *b) { + return dynamic_cast(b); +} + +void *void_cast(B *b) { + return dynamic_cast(b); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/inheritted-virtual-function.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/inheritted-virtual-function.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/inheritted-virtual-function.cpp @@ -0,0 +1,29 @@ +// Check the layout of the vtable for a child class that inherits a virtual +// function but does not override it. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +class A { +public: + virtual void foo(); +}; + +// The VTable for B should look similar to the vtable for A but the component for foo() should point to A::foo() and the component for bar() should point to B::bar(). +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local + +class B : public A { +public: + virtual void bar(); +}; + +void A::foo() {} +void B::bar() {} + +void A_foo(A *a) { + a->foo(); +} + +void B_foo(B *b) { + b->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/inline-virtual-function.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/inline-virtual-function.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/inline-virtual-function.cpp @@ -0,0 +1,27 @@ +// The VTable is not in a comdat but the inline methods are. +// This doesn’t affect the vtable or the stubs we emit. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZN1A3barEv.stub = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// The vtable has a key function (A::foo()) so it does not have a comdat +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTV1A = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1A.local + +// We do not declare the stub with linkonce_odr so it can be emitted as .globl. +// CHECK: define hidden void @_ZN1A3barEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat { +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3barEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); // Key func + inline virtual void bar() {} +}; + +void A::foo() {} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/inlined-key-function.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/inlined-key-function.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/inlined-key-function.cpp @@ -0,0 +1,33 @@ +// Inline comdat method definition example. +// The VTable is in a comdat and defined anywhere the inline definition is. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZTV1A = comdat any +// CHECK: $_ZTS1A = comdat any +// CHECK: $_ZTI1A = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// The VTable is linkonce_odr and in a comdat here bc it’s key function is inline defined. +// CHECK: @_ZTV1A.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1A), align 4 +// CHECK: @_ZTV1A = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3fooEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; +void A_foo(A *a); + +inline void A::foo() {} + +void func() { + A a; + A_foo(&a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/member-function-pointer.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/member-function-pointer.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/member-function-pointer.cpp @@ -0,0 +1,47 @@ +// Member pointer to virtual function. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O3 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define void @_Z4funcP1AMS_FvvE(%class.A* %a, [2 x i64] %fn.coerce) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[fn_ptr:%.+]] = extractvalue [2 x i64] %fn.coerce, 0 +// CHECK-NEXT: [[adjust:%.+]] = extractvalue [2 x i64] %fn.coerce, 1 +// CHECK-NEXT: [[this:%.+]] = bitcast %class.A* %a to i8* +// CHECK-NEXT: [[this_adj:%.+]] = getelementptr inbounds i8, i8* [[this]], i64 [[adjust]] +// CHECK-NEXT: [[virtbit:%.+]] = and i64 [[fn_ptr]], 1 +// CHECK-NEXT: [[isvirt:%.+]] = icmp eq i64 [[virtbit]], 0 +// CHECK-NEXT: br i1 [[isvirt]], label %[[nonvirt:.+]], label %[[virt:.+]] +// CHECK: [[virt]]: + +// The loading of the virtual function here should be replaced with a llvm.load.relative() call. +// CHECK-NEXT: [[this:%.+]] = bitcast i8* [[this_adj]] to i8** +// CHECK-NEXT: [[vtable:%.+]] = load i8*, i8** [[this]], align 8 +// CHECK-NEXT: [[offset:%.+]] = add i64 [[fn_ptr]], -1 +// CHECK-NEXT: [[ptr:%.+]] = tail call i8* @llvm.load.relative.i64(i8* [[vtable]], i64 [[offset]]) +// CHECK-NEXT: [[method:%.+]] = bitcast i8* [[ptr]] to void (%class.A*)* +// CHECK-NEXT: br label %[[memptr_end:.+]] +// CHECK: [[nonvirt]]: +// CHECK-NEXT: [[method2:%.+]] = inttoptr i64 [[fn_ptr]] to void (%class.A*)* +// CHECK-NEXT: br label %[[memptr_end]] +// CHECK: [[memptr_end]]: +// CHECK-NEXT: [[method3:%.+]] = phi void (%class.A*)* [ [[method]], %[[virt]] ], [ [[method2]], %[[nonvirt]] ] +// CHECK-NEXT: [[a:%.+]] = bitcast i8* [[this_adj]] to %class.A* +// CHECK-NEXT: tail call void [[method3]](%class.A* [[a]]) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + void foo() override; +}; + +typedef void (A::*A_foo)(); + +void func(A *a, A_foo fn) { + (a->*fn)(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/multiple-inheritance.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/multiple-inheritance.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/multiple-inheritance.cpp @@ -0,0 +1,55 @@ +// Multiple inheritance. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: %class.C = type { %class.A, %class.B } +// CHECK: %class.A = type { i32 (...)** } +// CHECK: %class.B = type { i32 (...)** } + +// VTable for C contains 2 sub-vtables (represented as 2 structs). The first contains the components for B and the second contains the components for C. The RTTI ptr in both arrays still point to the RTTI struct for C. +// The component for bar() instead points to a thunk which redirects to C::bar() which overrides B::bar(). +// Now that we have a class with 2 parents, the offset to top in the second array is non-zero. +// CHECK: @_ZTV1C.local = private unnamed_addr constant { [4 x i32], [3 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32)], [3 x i32] [i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 1, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZThn8_N1C3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 1, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1C = unnamed_addr alias { [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local + +// CHECK: define void @_Z8C_foobarP1C(%class.C* %c) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[c:%[0-9]+]] = bitcast %class.C* %c to i8** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[c]], align 8 + +// Offset 0 to get first method +// CHECK-NEXT: [[ptr1:%[0-9]+]] = call i8* @llvm.load.relative.i32(i8* [[vtable]], i32 0) +// CHECK-NEXT: [[method1:%[0-9]+]] = bitcast i8* [[ptr1]] to void (%class.C*)* +// CHECK-NEXT: call void [[method1]](%class.C* %c) +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[c]], align 8 + +// Offset by 4 to get the next bar() +// CHECK-NEXT: [[ptr2:%[0-9]+]] = call i8* @llvm.load.relative.i32(i8* [[vtable]], i32 4) +// CHECK-NEXT: [[method2:%[0-9]+]] = bitcast i8* [[ptr2]] to void (%class.C*)* +// CHECK-NEXT: call void [[method2]](%class.C* %c) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +class B { + virtual void bar(); +}; + +class C : public A, public B { +public: + void foo() override; + void bar() override; +}; + +void C::foo() {} +void C::bar() {} + +void C_foobar(C *c) { + c->foo(); + c->bar(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/no-alias-when-dso-local.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/no-alias-when-dso-local.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/no-alias-when-dso-local.cpp @@ -0,0 +1,16 @@ +// Check that no alias is emitted when the vtable is already dso_local. This can +// happen if the class is hidden. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s --check-prefix=DEFAULT-VIS +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables -fvisibility hidden | FileCheck %s --check-prefix=HIDDEN-VIS + +// DEFAULT-VIS: @_ZTV1A.local = private unnamed_addr constant +// DEFAULT-VIS: @_ZTV1A = unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local +// HIDDEN-VIS-NOT: @_ZTV1A.local +// HIDDEN-VIS: @_ZTV1A = hidden unnamed_addr constant +class A { + public: + virtual void func(); +}; + +void A::func() {} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/no-stub-when-dso-local.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/no-stub-when-dso-local.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/no-stub-when-dso-local.cpp @@ -0,0 +1,49 @@ +// Check that we do not emit a stub for a virtual function if it is already +// known to be in the same linkage unit as the vtable. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s --check-prefixes=CHECK,NO-OPT +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables -O3 | FileCheck %s --check-prefixes=CHECK,OPT + +// The vtable offset is relative to _ZN1A3fooEv instead of a stub. We can do +// this since hidden functions are implicitly dso_local. +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [5 x i32] } { [5 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (%class.A* (%class.A*)* @_ZN1AD1Ev.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1AD0Ev.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// Despite the complete object destructor being hidden, we should still emit a +// stub for it because it's possible, when optimizations are enabled, for the +// dtor to be "devirtualized" into the destructor for a parent class if the +// child class doesn't implement its own dtor. For complete destructors, we +// always emit and use a stub. +// @_ZTV1B = hidden unnamed_addr constant { [5 x i32] } { [5 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1B, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1B, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (%class.B* (%class.B*)* @_ZN1BD1Ev.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1B, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1BD0Ev to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1B, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1A = unnamed_addr alias { [5 x i32] }, { [5 x i32] }* @_ZTV1A.local + +// CHECK: @_ZN1A3fooEv +// CHECK-NOT: @_ZN1A3fooEv.stub + +// The complete object destructor is hidden. +// NO-OPT: define linkonce_odr hidden %class.B* @_ZN1BD1Ev +// OPT-NOT: @_ZN1BD1Ev +// CHECK: @_ZN1BD1Ev.stub + +#define HIDDEN __attribute__((visibility("hidden"))) + +class A { +public: + HIDDEN virtual void foo(); + virtual ~A(); +}; + +class HIDDEN B : public A { +public: + virtual void foo(); +}; + +void A::foo() {} + +void A_foo(A *a) { + a->foo(); +} + +void B::foo() { + A::foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/override-pure-virtual-method.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/override-pure-virtual-method.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/override-pure-virtual-method.cpp @@ -0,0 +1,34 @@ +// Override pure virtual function. +// We instead emit zero for the pure virtual function component. See PR43094 for +// details. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 0, i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1A = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1A.local +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local + +// CHECK-NOT: declare void @__cxa_pure_virtual() unnamed_addr + +class A { +public: + virtual void foo() = 0; + virtual void bar(); +}; + +class B : public A { +public: + void foo() override; + void bar() override; +}; + +void A::bar() {} +void B::foo() {} +void B::bar() {} + +void A_foo(A *a) { + a->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/overriden-virtual-function.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/overriden-virtual-function.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/overriden-virtual-function.cpp @@ -0,0 +1,30 @@ +// Check the layout of the vtable for a child class that inherits a virtual +// function but does override it. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + void foo() override; + virtual void bar(); +}; + +void A::foo() {} +void B::foo() {} + +void A_foo(A *a) { + a->foo(); +} + +void B_foo(B *b) { + b->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/parent-and-child-in-comdats.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/parent-and-child-in-comdats.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/parent-and-child-in-comdats.cpp @@ -0,0 +1,62 @@ +// Cross comdat example +// Both the parent and child VTablea are in their own comdat sections. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// Comdats are emitted for both A and B in this module and for their respective implementations of foo(). +// CHECK: $_ZN1A3fooEv = comdat any +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZN1B3fooEv = comdat any +// CHECK: $_ZN1B3fooEv.stub = comdat any +// CHECK: $_ZTV1A = comdat any +// CHECK: $_ZTS1A = comdat any +// CHECK: $_ZTI1A = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any +// CHECK: $_ZTV1B = comdat any +// CHECK: $_ZTS1B = comdat any +// CHECK: $_ZTI1B = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any + +// Both the vtables for A and B are emitted and in their own comdats. +// CHECK: @_ZTV1A.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1A), align 4 +// CHECK: @_ZTV1B.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1B), align 4 + +// CHECK: @_ZTV1A = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local +// CHECK: @_ZTV1B = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1B.local + +// CHECK: declare void @_Z5A_fooP1A(%class.A*) + +// The stubs and implementations for foo() are in their own comdat sections. +// CHECK: define linkonce_odr void @_ZN1A3fooEv(%class.A* %this) unnamed_addr #{{[0-9]+}} comdat + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3fooEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define linkonce_odr void @_ZN1B3fooEv(%class.B* %this) unnamed_addr #{{[0-9]+}} comdat + +// CHECK: define hidden void @_ZN1B3fooEv.stub(%class.B* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1B3fooEv(%class.B* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + inline virtual void foo() {} +}; +class B : public A { +public: + inline void foo() override {} +}; +void A_foo(A *a); + +// func() is used so that the vtable for B is accessed when creating the instance. +void func() { + A a; + B b; + A_foo(&a); + A_foo(&b); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/parent-vtable-in-comdat.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/parent-vtable-in-comdat.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/parent-vtable-in-comdat.cpp @@ -0,0 +1,48 @@ +// Cross comdat example +// Parent VTable is in a comdat section. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// A::foo() has a comdat since it is an inline function +// CHECK: $_ZN1A3fooEv = comdat any +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZTV1A = comdat any +// CHECK: $_ZTS1A = comdat any + +// The VTable for A has its own comdat section bc it has no key function +// CHECK: $_ZTI1A = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// The VTable for A is emitted here and in a comdat section since it has no key function, and is used in this module when creating an instance of A. +// CHECK: @_ZTV1A.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1A), align 4 +// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +// CHECK: @_ZTS1A = linkonce_odr constant [3 x i8] c"1A\00", comdat, align 1 +// CHECK: @_ZTI1A = linkonce_odr constant { i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv117__class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i32 0, i32 0) }, comdat, align 8 +// CHECK: @_ZTI1A.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI1A, comdat +// CHECK: @_ZTV1A = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local + +// CHECK: define linkonce_odr void @_ZN1A3fooEv(%class.A* %this) unnamed_addr #{{[0-9]+}} comdat + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat { +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3fooEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + inline virtual void foo() {} +}; +class B : public A { +public: + void foo() override; +}; +void A_foo(A *a); + +void A_foo(A *a) { a->foo(); } + +// func() is only used to emit a vtable. +void func() { + A a; + A_foo(&a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/pass-byval-attributes.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/pass-byval-attributes.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/pass-byval-attributes.cpp @@ -0,0 +1,37 @@ +// ByVal attributes should propogate through to produce proper assembly and +// avoid "unpacking" structs within the stubs on x86_64. + +// RUN: %clang_cc1 %s -triple=x86_64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +struct LargeStruct { + char x[24]; + virtual ~LargeStruct() {} +}; + +struct fidl_string { + unsigned long long size; + char *data; +}; +static_assert(sizeof(fidl_string) == 16, ""); + +class Base { +public: + virtual void func(LargeStruct, fidl_string, LargeStruct, fidl_string) = 0; +}; + +class Derived : public Base { +public: + void func(LargeStruct, fidl_string, LargeStruct, fidl_string) override; +}; + +// The original function takes a byval pointer. +// CHECK: define void @_ZN7Derived4funcE11LargeStruct11fidl_stringS0_S1_(%class.Derived* %this, %struct.LargeStruct* %ls, i64 %sv1.coerce0, i8* %sv1.coerce1, %struct.LargeStruct* %ls2, %struct.fidl_string* byval(%struct.fidl_string) align 8 %sv2) unnamed_addr + +// So the stub should take and pass one also. +// CHECK: define hidden void @_ZN7Derived4funcE11LargeStruct11fidl_stringS0_S1_.stub(%class.Derived* %0, %struct.LargeStruct* %1, i64 %2, i8* %3, %struct.LargeStruct* %4, %struct.fidl_string* byval(%struct.fidl_string) align 8 %5) unnamed_addr {{#[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN7Derived4funcE11LargeStruct11fidl_stringS0_S1_(%class.Derived* %0, %struct.LargeStruct* %1, i64 %2, i8* %3, %struct.LargeStruct* %4, %struct.fidl_string* byval(%struct.fidl_string) align 8 %5) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +void Derived::func(LargeStruct ls, fidl_string sv1, LargeStruct ls2, fidl_string sv2) {} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/relative-vtables-flag.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/relative-vtables-flag.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/relative-vtables-flag.cpp @@ -0,0 +1,24 @@ +// Check the layout of the vtable for a normal class. +// The Fuchsia relative vtables ABI will be hidden behind a flag for now as part +// of a soft incremental rollout. This ABI should only be used if the flag for +// it is passed on Fuchsia. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck --check-prefix=RELATIVE-ABI %s +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fno-experimental-relative-c++-abi-vtables | FileCheck --check-prefix=DEFAULT-ABI %s +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm | FileCheck --check-prefix=DEFAULT-ABI %s + +// VTable contains offsets and references to the hidden symbols +// RELATIVE-ABI: @_ZTV1A.local = private unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// RELATIVE-ABI: @_ZTV1A = unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local +// DEFAULT-ABI: @_ZTV1A = unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*), i8* bitcast (void (%class.A*)* @_ZN1A3fooEv to i8*)] }, align 8 + +class A { +public: + virtual void foo(); +}; + +void A::foo() {} + +void A_foo(A *a) { + a->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/simple-vtable-definition.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/simple-vtable-definition.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/simple-vtable-definition.cpp @@ -0,0 +1,43 @@ +// Check the layout of the vtable for a normal class. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// We should be emitting comdats for each of the virtual function stubs and RTTI proxies +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// VTable contains offsets and references to the hidden symbols +// The vtable definition itself is private so we can take relative references to +// it. The vtable symbol will be exposed through a public alias. +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +// CHECK: @_ZTS1A = constant [3 x i8] c"1A\00", align 1 +// CHECK: @_ZTI1A = constant { i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv117__class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i32 0, i32 0) }, align 8 + +// The stub should be in a comdat +// CHECK: @_ZTI1A.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI1A, comdat + +// The vtable symbol is exposed through an alias. +// @_ZTV1A = dso_local unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local + +// CHECK: define void @_ZN1A3fooEv(%class.A* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// This function should be in a comdat +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +void A::foo() {} + +void A_foo(A *a) { + a->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/stub-linkages.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/stub-linkages.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/stub-linkages.cpp @@ -0,0 +1,51 @@ +// If the linkage of the class is internal, then the stubs and proxies should +// also be internally linked. + +// RUN: %clang_cc1 %s -triple=x86_64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// External linkage. +// CHECK: @_ZTI8External.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI8External, comdat + +class External { +public: + virtual void func(); +}; + +void External::func() {} + +// Internal linkage. +// CHECK: @_ZTIN12_GLOBAL__N_18InternalE.rtti_proxy = internal unnamed_addr constant { i8*, i8* }* @_ZTIN12_GLOBAL__N_18InternalE +namespace { + +class Internal { +public: + virtual void func(); +}; + +void Internal::func() {} + +} // namespace + +// This gets the same treatment as an externally available vtable. +// CHECK: @_ZTI11LinkOnceODR.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI11LinkOnceODR, comdat +class LinkOnceODR { +public: + virtual void func() {} // A method defined in the class definition results in this linkage for the vtable. +}; + +// Force an emission of a vtable for Internal by using it here. +void manifest_internal() { + Internal internal; + (void)internal; + LinkOnceODR linkonceodr; + (void)linkonceodr; +} + +// Aliases are typically emitted after the vtable definitions but before the +// function definitions. +// CHECK: @_ZTV8External = unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV8External.local +// CHECK: @_ZTV11LinkOnceODR = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV11LinkOnceODR.local + +// CHECK: define void @_ZN8External4funcEv +// CHECK: define internal void @_ZN12_GLOBAL__N_18Internal4funcEv.stub +// CHECK: define hidden void @_ZN11LinkOnceODR4funcEv.stub diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/thunk-mangling.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/thunk-mangling.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/thunk-mangling.cpp @@ -0,0 +1,31 @@ +// Check that virtual thunks are unaffected by the relative ABI. +// The offset of thunks is mangled into the symbol name, which could result in +// linking errors for binaries that want to look for symbols in SOs made with +// this ABI. +// Running that linked binary still won't work since we're using conflicting +// ABIs, but we should still be able to link. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// This would be normally n24 (3 ptr widths) but is 12 since the vtable is +// entierely made of i32s now. +// CHECK: _ZTv0_n12_N7Derived1fEi + +class Base { +public: + virtual int f(int x); + +private: + long x; +}; + +class Derived : public virtual Base { +public: + virtual int f(int x); + +private: + long y; +}; + +int Base::f(int x) { return x + 1; } +int Derived::f(int x) { return x + 2; } diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/type-info.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/type-info.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/type-info.cpp @@ -0,0 +1,77 @@ +// Check typeid() + type_info + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O3 -S -o - -emit-llvm -fcxx-exceptions -fexceptions -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: %class.A = type { i32 (...)** } +// CHECK: %class.B = type { %class.A } +// CHECK: %"class.std::type_info" = type { i32 (...)**, i8* } + +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZN1B3fooEv.stub = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any + +// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +// CHECK: @_ZTS1A = constant [3 x i8] c"1A\00", align 1 +// CHECK: @_ZTI1A = constant { i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv117__class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i32 0, i32 0) }, align 8 +// CHECK: @_ZTVN10__cxxabiv120__si_class_type_infoE = external global i8* +// CHECK: @_ZTS1B = constant [3 x i8] c"1B\00", align 1 +// CHECK: @_ZTI1B = constant { i8*, i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv120__si_class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1B, i32 0, i32 0), i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*) }, align 8 +// CHECK: @_ZTI1A.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI1A, comdat +// CHECK: @_ZTI1B.rtti_proxy = hidden unnamed_addr constant { i8*, i8*, i8* }* @_ZTI1B, comdat + +// CHECK: define {{.*}}%"class.std::type_info"* @_Z11getTypeInfov() local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret %"class.std::type_info"* bitcast ({ i8*, i8* }* @_ZTI1A to %"class.std::type_info"*) +// CHECK-NEXT: } + +// CHECK: define i8* @_Z7getNamev() local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i64 0, i64 0) +// CHECK-NEXT: } + +// CHECK: define i1 @_Z5equalP1A(%class.A* readonly %a) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[isnull:%[0-9]+]] = icmp eq %class.A* %a, null +// CHECK-NEXT: br i1 [[isnull]], label %[[bad_typeid:[a-z0-9._]+]], label %[[end:[a-z0-9.+]+]] +// CHECK: [[bad_typeid]]: +// CHECK-NEXT: tail call void @__cxa_bad_typeid() +// CHECK-NEXT: unreachable +// CHECK: [[end]]: +// CHECK-NEXT: [[type_info_ptr3:%[0-9]+]] = bitcast %class.A* %a to i8** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[type_info_ptr3]] +// CHECK-NEXT: [[type_info_ptr:%[0-9]+]] = tail call i8* @llvm.load.relative.i32(i8* [[vtable]], i32 -4) +// CHECK-NEXT: [[type_info_ptr2:%[0-9]+]] = bitcast i8* [[type_info_ptr]] to %"class.std::type_info"** +// CHECK-NEXT: [[type_info_ptr:%[0-9]+]] = load %"class.std::type_info"*, %"class.std::type_info"** [[type_info_ptr2]], align 8 +// CHECK-NEXT: [[name_ptr:%[a-z0-9._]+]] = getelementptr inbounds %"class.std::type_info", %"class.std::type_info"* [[type_info_ptr]], i64 0, i32 1 +// CHECK-NEXT: [[name:%[0-9]+]] = load i8*, i8** [[name_ptr]], align 8 +// CHECK-NEXT: [[eq:%[a-z0-9.]+]] = icmp eq i8* [[name]], getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1B, i64 0, i64 0) +// CHECK-NEXT: ret i1 [[eq]] +// CHECK-NEXT: } + +#include "../typeinfo" + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + void foo() override; +}; + +void A::foo() {} +void B::foo() {} + +const auto &getTypeInfo() { + return typeid(A); +} + +const char *getName() { + return typeid(A).name(); +} + +bool equal(A *a) { + return typeid(B) == typeid(*a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/vbase-offset.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/vbase-offset.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/vbase-offset.cpp @@ -0,0 +1,36 @@ +// Check that the pointer adjustment from the virtual base offset is loaded as a +// 32-bit int. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK-LABEL: @_ZTv0_n12_N7Derived1fEi( +// CHECK-NEXT: entry: +// CHECK: [[this:%.+]] = bitcast %class.Derived* %this1 to i8* +// CHECK-NEXT: [[this2:%.+]] = bitcast i8* [[this]] to i8** +// CHECK-NEXT: [[vtable:%.+]] = load i8*, i8** [[this2]], align 8 +// CHECK-NEXT: [[vbase_offset_ptr:%.+]] = getelementptr inbounds i8, i8* [[vtable]], i64 -12 +// CHECK-NEXT: [[vbase_offset_ptr2:%.+]] = bitcast i8* [[vbase_offset_ptr]] to i32* +// CHECK-NEXT: [[vbase_offset:%.+]] = load i32, i32* [[vbase_offset_ptr2]], align 4 +// CHECK-NEXT: [[adj_this:%.+]] = getelementptr inbounds i8, i8* [[this]], i32 [[vbase_offset]] +// CHECK-NEXT: [[adj_this2:%.+]] = bitcast i8* [[adj_this]] to %class.Derived* +// CHECK: [[call:%.+]] = tail call i32 @_ZN7Derived1fEi(%class.Derived* [[adj_this2]], i32 {{.*}}) +// CHECK: ret i32 [[call]] + +class Base { +public: + virtual int f(int x); + +private: + long x; +}; + +class Derived : public virtual Base { +public: + virtual int f(int x); + +private: + long y; +}; + +int Base::f(int x) { return x + 1; } +int Derived::f(int x) { return x + 2; } diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/virtual-function-call.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/virtual-function-call.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/virtual-function-call.cpp @@ -0,0 +1,22 @@ +// Check that we call llvm.load.relative() on a vtable function call. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O3 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define void @_Z5A_fooP1A(%class.A* %a) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[this:%[0-9]+]] = bitcast %class.A* %a to i8** +// CHECK-NEXT: %vtable1 = load i8*, i8** [[this]] +// CHECK-NEXT: [[func_ptr:%[0-9]+]] = tail call i8* @llvm.load.relative.i32(i8* %vtable1, i32 0) +// CHECK-NEXT: [[func:%[0-9]+]] = bitcast i8* [[func_ptr]] to void (%class.A*)* +// CHECK-NEXT: tail call void [[func]](%class.A* %a) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +void A_foo(A *a) { + a->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/vtable-hidden-when-in-comdat.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/vtable-hidden-when-in-comdat.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/vtable-hidden-when-in-comdat.cpp @@ -0,0 +1,19 @@ +// Check that a vtable is made hidden instead of private if the original vtable +// is not dso_local. The vtable will need to be hidden and not private so it can +// be used as acomdat key signature. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: @_ZTV1B.local = linkonce_odr hidden unnamed_addr constant +// CHECK: @_ZTV1B = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1B.local + +// The VTable will be in a comdat here since it has no key function. +class B { + public: + inline virtual void func() {} +}; + +// This is here just to manifest the vtable for B. +void func() { + B b; +}