diff --git a/clang/cmake/caches/Fuchsia-stage2.cmake b/clang/cmake/caches/Fuchsia-stage2.cmake --- a/clang/cmake/caches/Fuchsia-stage2.cmake +++ b/clang/cmake/caches/Fuchsia-stage2.cmake @@ -28,6 +28,7 @@ set(CLANG_ENABLE_STATIC_ANALYZER ON CACHE BOOL "") set(CLANG_PLUGIN_SUPPORT OFF CACHE BOOL "") +set(LIBCXXABI_RELATIVE_VTABLES_ABI_ENABLED ON CACHE BOOL "") set(ENABLE_EXPERIMENTAL_NEW_PASS_MANAGER ON CACHE BOOL "") set(ENABLE_LINKER_BUILD_ID ON CACHE BOOL "") set(ENABLE_X86_RELAX_RELOCATIONS ON CACHE BOOL "") @@ -108,7 +109,7 @@ set(FUCHSIA_x86_64_NAME x64) set(FUCHSIA_riscv64_NAME riscv64) foreach(target x86_64;aarch64;riscv64) - set(FUCHSIA_${target}_COMPILER_FLAGS "-I${FUCHSIA_SDK}/pkg/fdio/include") + set(FUCHSIA_${target}_COMPILER_FLAGS "-I${FUCHSIA_SDK}/pkg/fdio/include -Xclang -fexperimental-relative-c++-abi-vtables") set(FUCHSIA_${target}_LINKER_FLAGS "-L${FUCHSIA_SDK}/arch/${FUCHSIA_${target}_NAME}/lib") set(FUCHSIA_${target}_SYSROOT "${FUCHSIA_SDK}/arch/${FUCHSIA_${target}_NAME}/sysroot") endforeach() 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 @@ -371,7 +371,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 +412,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 @@ -352,6 +352,9 @@ LANGOPT(RegisterStaticDestructors, 1, 1, "Register C++ static destructors") COMPATIBLE_VALUE_LANGOPT(MaxTokens, 32, 0, "Max number of tokens per TU or 0") +LANGOPT(RelativeCXXABIVTables, 1, 0, + "allow use of clang's relative C++ ABI " + "for classes with vtables on targets that support this") ENUM_LANGOPT(SignReturnAddressScope, SignReturnAddressScopeKind, 2, SignReturnAddressScopeKind::None, "Scope of return address signing") 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 @@ -1343,6 +1343,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 @@ -10387,10 +10387,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. @@ -2221,8 +2229,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 +2260,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 @@ -66,10 +66,24 @@ llvm::Constant *rtti, unsigned &nextVTableThunkIndex); + /// Get the constant to be added to a VTable. + llvm::Constant *getVTableComponent(const VTableLayout &layout, + unsigned componentIdx, + llvm::Constant *rtti, + unsigned &nextVTableThunkIndex); + + /// Make a function pointer into a relative integer when using the relative + /// vtables ABI. + llvm::Constant *makeComponentRelative(llvm::Constant *C, + llvm::GlobalVariable *VTable, + unsigned vtableIdx, + unsigned lastAddrPoint) const; + public: /// Add vtable components for the given vtable layout to the given /// global initializer. void createVTableInitializer(ConstantStructBuilder &builder, + llvm::GlobalVariable *VTable, const VTableLayout &layout, llvm::Constant *rtti); 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,148 @@ maybeEmitThunk(GD, Thunk, /*ForVTable=*/false); } +llvm::Constant *CodeGenVTables::makeComponentRelative( + llvm::Constant *C, llvm::GlobalVariable *VTable, unsigned vtableIdx, + unsigned lastAddrPoint) const { + // No need to get the offset of a nullptr. + if (C->isNullValue()) + return llvm::ConstantInt::get(CGM.Int32Ty, 0); + + llvm::Type *Int32Ty = CGM.Int32Ty; + llvm::Type *PtrDiffTy = + CGM.getTypes().ConvertType(CGM.getContext().getPointerDiffType()); + llvm::Type *VTableTy = VTable->getValueType(); + + llvm::Constant *gep = llvm::ConstantExpr::getGetElementPtr( + VTableTy, VTable, + llvm::ArrayRef{ + llvm::ConstantInt::get(Int32Ty, 0), + llvm::ConstantInt::get(Int32Ty, vtableIdx), + llvm::ConstantInt::get(Int32Ty, lastAddrPoint)}); + + llvm::Constant *AddrPointInt = + llvm::ConstantExpr::getPtrToInt(gep, PtrDiffTy); + llvm::Constant *Target; + + auto *GlobalVal = cast(C->stripPointerCastsAndAliases()); + llvm::Module &M = CGM.getModule(); + llvm::SmallString<16> Name(GlobalVal->getName()); + + // We don't want to copy the linkage of the vtable exactly because we still + // want the stub/proxy to be emitted for properlly caluclating the offset. + // Examples where there would bo no symbol emitted are available_externally + // and private linkages. + auto StubLinkage = VTable->hasLocalLinkage() + ? llvm::GlobalValue::InternalLinkage + : llvm::GlobalValue::ExternalLinkage; + + if (auto *Func = dyn_cast(GlobalVal)) { + llvm::SmallString<16> StubName(Name); + StubName.append(".stub"); + + // Take offset from stub to the vtable component. + llvm::Function *HiddenFunc = M.getFunction(StubName); + if (!HiddenFunc) { + // Make the stub private. + llvm::Function *Stub = llvm::Function::Create(Func->getFunctionType(), + StubLinkage, StubName, M); + + // Propogate byval attributes. + Stub->setAttributes(Func->getAttributes()); + + Stub->setDSOLocal(true); + Stub->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + if (!llvm::GlobalValue::isLocalLinkage(StubLinkage)) { + Stub->setVisibility(llvm::GlobalValue::HiddenVisibility); + Stub->setComdat(M.getOrInsertComdat(StubName)); + } + + // Fill the stub with a tail call that will be optimized. + llvm::BasicBlock *BB = + llvm::BasicBlock::Create(M.getContext(), "entry", Stub); + llvm::IRBuilder<> Builder(BB); + llvm::SmallVector Args; + for (auto &Arg : Stub->args()) + Args.push_back(&Arg); + llvm::CallInst *Call = Builder.CreateCall(Func, Args); + Call->setAttributes(Func->getAttributes()); + Call->setTailCall(); + if (Call->getType()->isVoidTy()) + Builder.CreateRetVoid(); + else + Builder.CreateRet(Call); + + HiddenFunc = Stub; + } + Target = HiddenFunc; + } else { + llvm::SmallString<16> RTTIProxyName(Name); + RTTIProxyName.append(".rtti_proxy"); + + // Component is RTTI. Create a private variable that points to this RTTI + // struct and take the offset of that. + llvm::GlobalVariable *GV = M.getNamedGlobal(RTTIProxyName); + if (!GV) { + GV = new llvm::GlobalVariable(CGM.getModule(), GlobalVal->getType(), + /*isConstant=*/true, StubLinkage, GlobalVal, + RTTIProxyName); + GV->setDSOLocal(true); + GV->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + if (!llvm::GlobalValue::isLocalLinkage(StubLinkage)) { + GV->setVisibility(llvm::GlobalValue::HiddenVisibility); + GV->setComdat(M.getOrInsertComdat(RTTIProxyName)); + } + } + Target = GV; + } + + llvm::Constant *ptrToInt = llvm::ConstantExpr::getPtrToInt(Target, PtrDiffTy); + return llvm::ConstantExpr::getIntegerCast( + llvm::ConstantExpr::getSub(ptrToInt, AddrPointInt), Int32Ty, + /*isSigned=*/true); +} + void CodeGenVTables::addVTableComponent( ConstantArrayBuilder &builder, const VTableLayout &layout, unsigned idx, llvm::Constant *rtti, unsigned &nextVTableThunkIndex) { - auto &component = layout.vtable_components()[idx]; + builder.add(getVTableComponent(layout, idx, rtti, nextVTableThunkIndex)); +} - auto addOffsetConstant = [&](CharUnits offset) { - builder.add(llvm::ConstantExpr::getIntToPtr( - llvm::ConstantInt::get(CGM.PtrDiffTy, offset.getQuantity()), - CGM.Int8PtrTy)); - }; +static llvm::Constant *AddOffsetConstant(const CodeGenModule &CGM, + CharUnits offset) { + return llvm::ConstantExpr::getIntToPtr( + llvm::ConstantInt::get(CGM.PtrDiffTy, offset.getQuantity()), + CGM.Int8PtrTy); +} + +static llvm::Constant *AddRelativeOffsetConstant(const CodeGenModule &CGM, + CharUnits offset) { + return llvm::ConstantInt::get(CGM.Int32Ty, offset.getQuantity()); +} + +llvm::Constant * +CodeGenVTables::getVTableComponent(const VTableLayout &layout, + unsigned componentIdx, llvm::Constant *rtti, + unsigned &nextVTableThunkIndex) { + auto &component = layout.vtable_components()[componentIdx]; + + bool UseRelativeLayout = CGM.getTarget().getCXXABI().isItaniumFamily() && + CGM.getItaniumVTableContext().isRelativeLayout(); + auto addOffsetConstant = + UseRelativeLayout ? AddRelativeOffsetConstant : AddOffsetConstant; switch (component.getKind()) { case VTableComponent::CK_VCallOffset: - return addOffsetConstant(component.getVCallOffset()); + return addOffsetConstant(CGM, component.getVCallOffset()); case VTableComponent::CK_VBaseOffset: - return addOffsetConstant(component.getVBaseOffset()); + return addOffsetConstant(CGM, component.getVBaseOffset()); case VTableComponent::CK_OffsetToTop: - return addOffsetConstant(component.getOffsetToTop()); + return addOffsetConstant(CGM, component.getOffsetToTop()); case VTableComponent::CK_RTTI: - return builder.add(llvm::ConstantExpr::getBitCast(rtti, CGM.Int8PtrTy)); + return llvm::ConstantExpr::getBitCast(rtti, CGM.Int8PtrTy); case VTableComponent::CK_FunctionPointer: case VTableComponent::CK_CompleteDtorPointer: @@ -674,11 +793,23 @@ ? MD->hasAttr() : (MD->hasAttr() || !MD->hasAttr()); if (!CanEmitMethod) - return builder.addNullPointer(CGM.Int8PtrTy); + return llvm::ConstantExpr::getNullValue(CGM.Int8PtrTy); // Method is acceptable, continue processing as usual. } auto getSpecialVirtualFn = [&](StringRef name) -> llvm::Constant * { + // FIXME: There is currently an issue (PR43094) where + // when merging comdat groups, lld can select a local symbol as the + // signature symbol even though it cannot be accessed outside that + // symbol's module. A result of this ABI would make __cxa_pure_virtual + // (and it's destructor equivalent) local symbols, and depending on + // link order, the comdat groups would resolve to the one with the + // local symbol. As a temporary solution, since we shouldn't be + // calling these functions in the first place, fill these components + // with zero. + if (UseRelativeLayout) + return llvm::Constant::getNullValue(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 +830,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 == + componentIdx) { auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; nextVTableThunkIndex++; @@ -723,48 +855,88 @@ fnPtr = CGM.GetAddrOfFunction(GD, fnTy, /*ForVTable=*/true); } - fnPtr = llvm::ConstantExpr::getBitCast(fnPtr, CGM.Int8PtrTy); - builder.add(fnPtr); - return; + return llvm::ConstantExpr::getBitCast(fnPtr, CGM.Int8PtrTy); } case VTableComponent::CK_UnusedFunctionPointer: - return builder.addNullPointer(CGM.Int8PtrTy); + return llvm::ConstantExpr::getNullValue(CGM.Int8PtrTy); } llvm_unreachable("Unexpected vtable component kind"); } llvm::Type *CodeGenVTables::getVTableType(const VTableLayout &layout) { + bool UseRelativeLayout = CGM.getTarget().getCXXABI().isItaniumFamily() && + CGM.getItaniumVTableContext().isRelativeLayout(); SmallVector tys; + llvm::Type *ComponentType; + if (UseRelativeLayout) + ComponentType = CGM.Int32Ty; + else + ComponentType = CGM.Int8PtrTy; for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) { - tys.push_back(llvm::ArrayType::get(CGM.Int8PtrTy, layout.getVTableSize(i))); + tys.push_back(llvm::ArrayType::get(ComponentType, layout.getVTableSize(i))); } return llvm::StructType::get(CGM.getLLVMContext(), tys); } void CodeGenVTables::createVTableInitializer(ConstantStructBuilder &builder, + llvm::GlobalVariable *VTable, const VTableLayout &layout, llvm::Constant *rtti) { + bool UseRelativeLayout = CGM.getTarget().getCXXABI().isItaniumFamily() && + CGM.getItaniumVTableContext().isRelativeLayout(); + llvm::Type *ComponentType; + if (UseRelativeLayout) + ComponentType = CGM.Int32Ty; + else + ComponentType = CGM.Int8PtrTy; + 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 VTableIdx = 0; VTableIdx != layout.getNumVTables(); + ++VTableIdx) { + auto vtableElem = builder.beginArray(ComponentType); + + unsigned LastAddressPoint = 0; + size_t thisIndex = layout.getVTableOffset(VTableIdx); + size_t nextIndex = thisIndex + layout.getVTableSize(VTableIdx); + for (unsigned ComponentIdx = thisIndex; ComponentIdx < nextIndex; + ++ComponentIdx) { + if (UseRelativeLayout) { + const auto &Components = layout.vtable_components(); + + // We want to point to the first function in the vtable. This will come + // after the typeinfo component, so we can just check if the previous + // component was RTTI kind. + if (ComponentIdx > 0 && Components[ComponentIdx - 1].isRTTIKind()) + LastAddressPoint = ComponentIdx; + + llvm::Constant *Component = getVTableComponent( + layout, ComponentIdx, rtti, nextVTableThunkIndex); + + const auto &LayoutComponent = Components[ComponentIdx]; + if (LayoutComponent.isFunctionPointerKind()) { + Component = makeComponentRelative(Component, VTable, VTableIdx, + LastAddressPoint - thisIndex); + } else if (LayoutComponent.isRTTIKind()) { + Component = makeComponentRelative(Component, VTable, VTableIdx, 2); + } + + vtableElem.add(Component); + } else { + addVTableComponent(vtableElem, layout, ComponentIdx, rtti, + nextVTableThunkIndex); + } } 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()); @@ -808,7 +980,7 @@ // Create and set the initializer. ConstantInitBuilder builder(CGM); auto components = builder.beginStruct(); - createVTableInitializer(components, *VTLayout, RTTI); + createVTableInitializer(components, VTable, *VTLayout, RTTI); components.finishAndSetAsInitializer(VTable); // Set properties only after the initializer has been set to ensure that the 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 @@ -702,9 +702,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 @@ -721,14 +721,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 || @@ -1003,11 +1014,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: @@ -1421,8 +1437,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()); } @@ -1478,28 +1505,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. - llvm::Value *OffsetToTop = - CGF.Builder.CreateConstInBoundsGEP1_64(VTable, -2ULL); - OffsetToTop = - CGF.Builder.CreateAlignedLoad(OffsetToTop, CGF.getPointerAlign(), - "offset.to.top"); + // 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 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); } @@ -1520,17 +1556,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; } @@ -1678,15 +1719,15 @@ llvm::Constant *RTTI = 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); - // Set the correct linkage. VTable->setLinkage(Linkage); + // Create and set the initializer. + ConstantInitBuilder builder(CGM); + auto components = builder.beginStruct(); + CGVT.createVTableInitializer(components, VTable, VTLayout, RTTI); + components.finishAndSetAsInitializer(VTable); + if (CGM.supportsCOMDAT() && VTable->isWeakForLinker()) VTable->setComdat(CGM.getModule().getOrInsertComdat(VTable->getName())); @@ -1795,7 +1836,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 +1855,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 +1868,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 +1891,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 +2015,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 { @@ -3301,12 +3365,21 @@ 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 @@ -1690,10 +1690,10 @@ [](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, VTable, VTLayout, RTTI); + components.finishAndSetAsInitializer(VTable); emitVTableTypeMetadata(*Info, RD, VTable); } diff --git a/clang/lib/Driver/ToolChains/Fuchsia.cpp b/clang/lib/Driver/ToolChains/Fuchsia.cpp --- a/clang/lib/Driver/ToolChains/Fuchsia.cpp +++ b/clang/lib/Driver/ToolChains/Fuchsia.cpp @@ -159,6 +159,12 @@ CmdArgs.push_back("-lc"); } + if (Args.hasFlag(options::OPT_fexperimental_relative_cxx_abi_vtables, + options::OPT_fno_experimental_relative_cxx_abi_vtables, + /*default=*/false)) { + CmdArgs.push_back("-fexperimental-relative-c++-abi-vtables"); + } + C.addCommand(std::make_unique(JA, *this, Exec, CmdArgs, Inputs)); } 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 @@ -3357,6 +3357,11 @@ } Opts.BranchTargetEnforcement = Args.hasArg(OPT_mbranch_target_enforce); + + 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/Fuchsia/child-inheritted-from-parent-in-comdat.cpp b/clang/test/CodeGenCXX/Fuchsia/child-inheritted-from-parent-in-comdat.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/child-inheritted-from-parent-in-comdat.cpp @@ -0,0 +1,54 @@ +// Cross comdat example +// Parent VTable is in a comdat section. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local 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, 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, 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 = linkonce_odr dso_local 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, 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)] }, comdat, align 4 + +// CHECK: define dso_local 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/Fuchsia/child-vtable-in-comdat.cpp b/clang/test/CodeGenCXX/Fuchsia/child-vtable-in-comdat.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/child-vtable-in-comdat.cpp @@ -0,0 +1,55 @@ +// Cross comdat example +// Child VTable is in a comdat section. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = linkonce_odr dso_local 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat, 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 dso_local constant [3 x i8] c"1B\00", comdat, align 1 +// CHECK: @_ZTI1A = external constant i8* +// CHECK: @_ZTI1B = linkonce_odr dso_local 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 +// CHECK: @_ZTI1A.rtti_proxy = hidden unnamed_addr constant i8** @_ZTI1A, 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: } + +// 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/Fuchsia/cross-translation-unit-1.cpp b/clang/test/CodeGenCXX/Fuchsia/cross-translation-unit-1.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/cross-translation-unit-1.cpp @@ -0,0 +1,38 @@ +// 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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local 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, 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// A::foo() is still available for other modules to use since it is not marked with private or internal linkage. +// CHECK: define dso_local 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/Fuchsia/cross-translation-unit-2.cpp b/clang/test/CodeGenCXX/Fuchsia/cross-translation-unit-2.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/cross-translation-unit-2.cpp @@ -0,0 +1,37 @@ +// 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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local 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, 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// A::bar() is defined outside of the module that defines the vtable for A +// CHECK: define dso_local void @_ZN1A3barEv(%class.A* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define dso_local 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/Fuchsia/cross-tu-header.h b/clang/test/CodeGenCXX/Fuchsia/cross-tu-header.h new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/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/Fuchsia/diamond-inheritance.cpp b/clang/test/CodeGenCXX/Fuchsia/diamond-inheritance.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/diamond-inheritance.cpp @@ -0,0 +1,53 @@ +// 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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local 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, 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, 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, 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 = dso_local 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, 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, 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, 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 = dso_local 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, 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, 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, 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, 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, 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, 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, i32 0, i32 1, i32 2) to i64)) to i32)] }, align 4 + +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/Fuchsia/diamond-virtual-inheritance.cpp b/clang/test/CodeGenCXX/Fuchsia/diamond-virtual-inheritance.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/diamond-virtual-inheritance.cpp @@ -0,0 +1,90 @@ +// Diamond virtual inheritance. +// This should cover virtual inheritance, construction vtables, and VTTs. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local 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, 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] }, { [4 x i32], [4 x i32] }* @_ZTV1B, 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, i32 0, i32 1, 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] }, { [4 x i32], [4 x i32] }* @_ZTV1B, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// VTT for B +// CHECK: @_ZTT1B = dso_local unnamed_addr constant [2 x i8*] [i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B, 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, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// VTable for C +// CHECK: @_ZTV1C = dso_local 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, 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] }, { [4 x i32], [4 x i32] }* @_ZTV1C, 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, i32 0, i32 1, 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] }, { [4 x i32], [4 x i32] }* @_ZTV1C, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// VTT for C +// CHECK: @_ZTT1C = dso_local unnamed_addr constant [2 x i8*] [i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C, 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, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// VTable for D +// CHECK: @_ZTV1D = dso_local 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, 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], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D, 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, 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, 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], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D, 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, i32 0, i32 2, i32 2) 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, i32 0, i32 2, i32 3) to i64)) to i32)] }, align 4 + +// VTT for D +// CHECK: @_ZTT1D = dso_local 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, 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, 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, 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, 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, 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, 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, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// Construction vtable for B-in-D +// CHECK: @_ZTC1D0_1B = dso_local 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, 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] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B, 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, i32 0, i32 1, 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] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// Construction vtable for C-in-D +// CHECK: @_ZTC1D8_1C = dso_local 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, 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] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C, 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, i32 0, i32 1, 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] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// CHECK: define dso_local 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/Fuchsia/dynamic-cast.cpp b/clang/test/CodeGenCXX/Fuchsia/dynamic-cast.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/dynamic-cast.cpp @@ -0,0 +1,78 @@ +// dynamic_cast +// Ensure that dynamic casting works normally + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -O3 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define dso_local %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 dso_local %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 dso_local %class.B* @_Z8selfcastP1B(%class.B* readnone returned %b) local_unnamed_addr +// CHECK-NEXT: entry +// CHECK-NEXT: ret %class.B* %b +// CHECK-NEXT: } + +// CHECK: define dso_local 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/Fuchsia/inheritted-virtual-function.cpp b/clang/test/CodeGenCXX/Fuchsia/inheritted-virtual-function.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/inheritted-virtual-function.cpp @@ -0,0 +1,28 @@ +// 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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local 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, 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +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/Fuchsia/inline-virtual-function.cpp b/clang/test/CodeGenCXX/Fuchsia/inline-virtual-function.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/inline-virtual-function.cpp @@ -0,0 +1,26 @@ +// 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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local 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, 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// 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/Fuchsia/inlined-key-function.cpp b/clang/test/CodeGenCXX/Fuchsia/inlined-key-function.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/inlined-key-function.cpp @@ -0,0 +1,32 @@ +// 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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = linkonce_odr dso_local 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, 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)] }, comdat, align 4 + +// 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/Fuchsia/member-function-pointer.cpp b/clang/test/CodeGenCXX/Fuchsia/member-function-pointer.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/member-function-pointer.cpp @@ -0,0 +1,47 @@ +// Member pointer to virtual function. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -O3 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define dso_local 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: [[a:%.+]] = bitcast i8* [[this_adj]] to %class.A* +// 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: 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/Fuchsia/multiple-inheritance.cpp b/clang/test/CodeGenCXX/Fuchsia/multiple-inheritance.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/multiple-inheritance.cpp @@ -0,0 +1,53 @@ +// Multiple inheritance. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local 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, 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, 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, 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, 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, i32 0, i32 1, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: define dso_local 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/Fuchsia/override-pure-virtual-method.cpp b/clang/test/CodeGenCXX/Fuchsia/override-pure-virtual-method.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/override-pure-virtual-method.cpp @@ -0,0 +1,31 @@ +// 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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: @_ZTV1A = dso_local 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1B = dso_local 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, 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// 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/Fuchsia/overriden-virtual-function.cpp b/clang/test/CodeGenCXX/Fuchsia/overriden-virtual-function.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/overriden-virtual-function.cpp @@ -0,0 +1,28 @@ +// 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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: @_ZTV1B = dso_local 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, 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +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/Fuchsia/parent-and-child-in-comdats.cpp b/clang/test/CodeGenCXX/Fuchsia/parent-and-child-in-comdats.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/parent-and-child-in-comdats.cpp @@ -0,0 +1,59 @@ +// Cross comdat example +// Both the parent and child VTablea are in their own comdat sections. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -pic-is-pie -ffunction-sections -fdata-sections -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 = linkonce_odr dso_local 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, 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)] }, comdat, align 4 +// CHECK: @_ZTV1B = linkonce_odr dso_local 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, 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, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat, align 4 + +// CHECK: declare void @_Z5A_fooP1A(%class.A*) + +// The stubs and implementations for foo() are in their own comdat sections. +// CHECK: define linkonce_odr dso_local 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 dso_local 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/Fuchsia/parent-vtable-in-comdat.cpp b/clang/test/CodeGenCXX/Fuchsia/parent-vtable-in-comdat.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/parent-vtable-in-comdat.cpp @@ -0,0 +1,47 @@ +// Cross comdat example +// Parent VTable is in a comdat section. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -pic-is-pie -ffunction-sections -fdata-sections -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 = linkonce_odr dso_local 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, 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)] }, comdat, align 4 +// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +// CHECK: @_ZTS1A = linkonce_odr dso_local constant [3 x i8] c"1A\00", comdat, align 1 +// CHECK: @_ZTI1A = linkonce_odr dso_local 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: define linkonce_odr dso_local 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/Fuchsia/pass-byval-attributes.cpp b/clang/test/CodeGenCXX/Fuchsia/pass-byval-attributes.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/pass-byval-attributes.cpp @@ -0,0 +1,79 @@ +// 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 -std=c++17 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +typedef unsigned long long uint64_t; +typedef unsigned int size_t; + +struct LargeStruct { + char x[24]; + virtual ~LargeStruct() {} +}; + +typedef struct fidl_string { + // Number of UTF-8 code units (bytes), must be 0 if |data| is null. + uint64_t size; + + // Pointer to UTF-8 code units (bytes) or null + char *data; +} fidl_string_t; + +class StringView final : private fidl_string_t { +public: + StringView() : fidl_string_t{} {} + constexpr StringView(const char *data, uint64_t size) + : fidl_string_t{size, const_cast(data)} {} + + // Constructs a fidl::StringView referencing a string literal. For example: + // + // fidl::StringView view("hello"); + // view.size() == 5; + // + template + constexpr explicit StringView(const char (&literal)[N]) + : fidl_string_t{N - 1, const_cast(literal)} { + static_assert(N > 0, "Empty string should be null-terminated"); + } + + uint64_t size() const { return fidl_string_t::size; } + void set_size(uint64_t size) { fidl_string_t::size = size; } + + const char *data() const { return fidl_string_t::data; } + void set_data(const char *data) { fidl_string_t::data = const_cast(data); } + + bool is_null() const { return fidl_string_t::data == nullptr; } + bool empty() const { return fidl_string_t::size == 0; } + + const char &at(size_t offset) const { return data()[offset]; } + + const char &operator[](size_t offset) const { return at(offset); } + + const char *begin() const { return data(); } + const char *cbegin() const { return data(); } + + const char *end() const { return data() + size(); } + const char *cend() const { return data() + size(); } +}; + +class Base { +public: + virtual void func(LargeStruct, StringView, LargeStruct, StringView) = 0; +}; + +class Derived : public Base { +public: + void func(LargeStruct, StringView, LargeStruct, StringView) override; +}; + +// The original function takes a byval pointer. +// CHECK: define dso_local void @_ZN7Derived4funcE11LargeStruct10StringViewS0_S1_(%class.Derived* %this, %struct.LargeStruct* %ls, i64 %sv1.coerce0, i8* %sv1.coerce1, %struct.LargeStruct* %ls2, %class.StringView* byval(%class.StringView) align 8 %sv2) unnamed_addr + +// So the stub should take and pass one also. +// CHECK: define hidden void @_ZN7Derived4funcE11LargeStruct10StringViewS0_S1_.stub(%class.Derived* %0, %struct.LargeStruct* %1, i64 %2, i8* %3, %struct.LargeStruct* %4, %class.StringView* byval(%class.StringView) align 8 %5) unnamed_addr {{#[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN7Derived4funcE11LargeStruct10StringViewS0_S1_(%class.Derived* %0, %struct.LargeStruct* %1, i64 %2, i8* %3, %struct.LargeStruct* %4, %class.StringView* byval(%class.StringView) align 8 %5) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +void Derived::func(LargeStruct ls, StringView sv1, LargeStruct ls2, StringView sv2) {} diff --git a/clang/test/CodeGenCXX/Fuchsia/relative-vtables-flag.cpp b/clang/test/CodeGenCXX/Fuchsia/relative-vtables-flag.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/relative-vtables-flag.cpp @@ -0,0 +1,23 @@ +// 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 -std=c++17 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck --check-prefix=RELATIVE-ABI %s +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fno-experimental-relative-c++-abi-vtables | FileCheck --check-prefix=DEFAULT-ABI %s +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm | FileCheck --check-prefix=DEFAULT-ABI %s + +// VTable contains offsets and references to the hidden symbols +// RELATIVE-ABI: @_ZTV1A = dso_local 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, 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 +// DEFAULT-ABI: @_ZTV1A = dso_local 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/Fuchsia/simple-vtable-definition.cpp b/clang/test/CodeGenCXX/Fuchsia/simple-vtable-definition.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/simple-vtable-definition.cpp @@ -0,0 +1,38 @@ +// Check the layout of the vtable for a normal class. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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 +// CHECK: @_ZTV1A = dso_local 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, 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: @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +// CHECK: @_ZTS1A = dso_local constant [3 x i8] c"1A\00", align 1 +// CHECK: @_ZTI1A = dso_local 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 + +// CHECK: define dso_local 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/Fuchsia/stub-linkages.cpp b/clang/test/CodeGenCXX/Fuchsia/stub-linkages.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/stub-linkages.cpp @@ -0,0 +1,49 @@ +// 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 -std=c++17 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// External linkage. +// CHECK: @_ZTV8External = dso_local unnamed_addr constant +// CHECK: @_ZTI8External.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI8External, comdat + +class External { +public: + virtual void func(); +}; + +void External::func() {} + +// Internal linkage. +// CHECK: @_ZTVN12_GLOBAL__N_18InternalE = internal unnamed_addr constant +// 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: @_ZTV11LinkOnceODR = linkonce_odr dso_local unnamed_addr constant +// 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; +} + +// CHECK: define dso_local void @_ZN8External4funcEv +// CHECK: define internal void @_ZN12_GLOBAL__N_18Internal4funcEv.stub +// CHECK: define hidden void @_ZN11LinkOnceODR4funcEv.stub diff --git a/clang/test/CodeGenCXX/Fuchsia/thunk-mangling.cpp b/clang/test/CodeGenCXX/Fuchsia/thunk-mangling.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/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 -std=c++17 -O1 -pic-is-pie -ffunction-sections -fdata-sections -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/Fuchsia/type-info.cpp b/clang/test/CodeGenCXX/Fuchsia/type-info.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/type-info.cpp @@ -0,0 +1,77 @@ +// Check typeid() + type_info + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -std=c++17 -O3 -pic-is-pie -ffunction-sections -fdata-sections -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 = dso_local constant [3 x i8] c"1A\00", align 1 +// CHECK: @_ZTI1A = dso_local 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 = dso_local constant [3 x i8] c"1B\00", align 1 +// CHECK: @_ZTI1B = dso_local 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 dso_local dereferenceable(16) %"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 dso_local 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 dso_local 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/Fuchsia/vbase-offset.cpp b/clang/test/CodeGenCXX/Fuchsia/vbase-offset.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/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 -std=c++17 -pic-is-pie -ffunction-sections -fdata-sections -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/Fuchsia/virtual-function-call.cpp b/clang/test/CodeGenCXX/Fuchsia/virtual-function-call.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/Fuchsia/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 -std=c++17 -O3 -pic-is-pie -ffunction-sections -fdata-sections -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define dso_local 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/constructor-destructor-return-this.cpp b/clang/test/CodeGenCXX/constructor-destructor-return-this.cpp --- a/clang/test/CodeGenCXX/constructor-destructor-return-this.cpp +++ b/clang/test/CodeGenCXX/constructor-destructor-return-this.cpp @@ -5,6 +5,8 @@ //RUN: | FileCheck --check-prefix=CHECKARM %s //RUN: %clang_cc1 %s -emit-llvm -o - -triple=x86_64-unknown-fuchsia | FileCheck --check-prefix=CHECKFUCHSIA %s //RUN: %clang_cc1 %s -emit-llvm -o - -triple=aarch64-unknown-fuchsia | FileCheck --check-prefix=CHECKFUCHSIA %s +//RUN: %clang_cc1 %s -emit-llvm -o - -triple=x86_64-unknown-fuchsia -fexperimental-relative-c++-abi-vtables | FileCheck --check-prefix=CHECKFUCHSIA-RELATIVE-ABI %s +//RUN: %clang_cc1 %s -emit-llvm -o - -triple=aarch64-unknown-fuchsia -fexperimental-relative-c++-abi-vtables | FileCheck --check-prefix=CHECKFUCHSIA-RELATIVE-ABI %s //RUN: %clang_cc1 %s -emit-llvm -o - -triple=i386-pc-win32 -fno-rtti | FileCheck --check-prefix=CHECKMS %s // FIXME: these tests crash on the bots when run with -triple=x86_64-pc-win32 @@ -151,8 +153,13 @@ // Verify that virtual calls to destructors are not marked with a 'returned' // this parameter at the call site... -// CHECKARM,CHECKFUCHSIA: [[VFN:%.*]] = getelementptr inbounds %class.E* (%class.E*)*, %class.E* (%class.E*)** -// CHECKARM,CHECKFUCHSIA: [[THUNK:%.*]] = load %class.E* (%class.E*)*, %class.E* (%class.E*)** [[VFN]] +// CHECKARM: [[VFN:%.*]] = getelementptr inbounds %class.E* (%class.E*)*, %class.E* (%class.E*)** +// CHECKARM: [[THUNK:%.*]] = load %class.E* (%class.E*)*, %class.E* (%class.E*)** [[VFN]] +// CHECKFUCHSIA: [[VFN:%.*]] = getelementptr inbounds %class.E* (%class.E*)*, %class.E* (%class.E*)** +// CHECKFUCHSIA: [[THUNK:%.*]] = load %class.E* (%class.E*)*, %class.E* (%class.E*)** [[VFN]] +// CHECKFUCHSIA-RELATIVE-ABI: [[VTABLE:%.+]] = bitcast %class.E* (%class.E*)** +// CHECKFUCHSIA-RELATIVE-ABI: [[THUNK_PTR:%.+]] = call i8* @llvm.load.relative.i32(i8* [[VTABLE]], i32 0) +// CHECKFUCHSIA-RELATIVE-ABI: [[THUNK:%.+]] = bitcast i8* [[THUNK_PTR]] to %class.E* (%class.E*)* // CHECKARM,CHECKFUCHSIA: call %class.E* [[THUNK]](%class.E* % // ...but static calls create declarations with 'returned' this diff --git a/libcxxabi/CMakeLists.txt b/libcxxabi/CMakeLists.txt --- a/libcxxabi/CMakeLists.txt +++ b/libcxxabi/CMakeLists.txt @@ -171,6 +171,11 @@ option(LIBCXXABI_HERMETIC_STATIC_LIBRARY "Do not export any symbols from the static library." OFF) +option(LIBCXXABI_RELATIVE_VTABLES_ABI_ENABLED + "Use different configurations of libcxxabi functions supported for the \ + Relative VTables ABI, which replaces vtable function pointers with offsets \ + between those virtual functions and the vtable itself." OFF) + #=============================================================================== # Configure System #=============================================================================== @@ -470,6 +475,10 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LIBCXXABI_CXX_FLAGS}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${LIBCXXABI_C_FLAGS}") +if (LIBCXXABI_RELATIVE_VTABLES_ABI_ENABLED) + add_definitions(-D_LIBCXXABI_RELATIVE_VTABLES_ABI_ENABLED) +endif() + #=============================================================================== # Setup Source Code #=============================================================================== diff --git a/libcxxabi/src/private_typeinfo.cpp b/libcxxabi/src/private_typeinfo.cpp --- a/libcxxabi/src/private_typeinfo.cpp +++ b/libcxxabi/src/private_typeinfo.cpp @@ -614,10 +614,26 @@ // Possible future optimization: Take advantage of src2dst_offset // Get (dynamic_ptr, dynamic_type) from static_ptr - void **vtable = *static_cast(static_ptr); - ptrdiff_t offset_to_derived = reinterpret_cast(vtable[-2]); +#ifdef _LIBCXXABI_RELATIVE_VTABLES_ABI_ENABLED + // The vtable address will point to the first virtual function, which is 8 + // bytes after the start of the vtable (4 for the offset from top + 4 for the typeinfo component). + uint32_t* vtable = *reinterpret_cast(static_ptr); + uint32_t offset_to_derived = vtable[-2]; + const void* dynamic_ptr = static_cast(static_ptr) + offset_to_derived; - const __class_type_info* dynamic_type = static_cast(vtable[-1]); + + // The typeinfo component is now a relative offset to a proxy. + int32_t offset_to_ti_proxy = *reinterpret_cast(vtable - 4); + uint8_t *ptr_to_ti_proxy = vtable + offset_to_ti_proxy; + const __class_type_info* dynamic_type = *(reinterpret_cast<__class_type_info **>(ptr_to_ti_proxy)); +#else + void** vtable = *static_cast(static_ptr); + ptrdiff_t offset_to_derived = reinterpret_cast(vtable[-2]); + const void* dynamic_ptr = + static_cast(static_ptr) + offset_to_derived; + const __class_type_info* dynamic_type = + static_cast(vtable[-1]); +#endif // Initialize answer to nullptr. This will be changed from the search // results if a non-null answer is found. Regardless, this is what will diff --git a/llvm/lib/IR/Constants.cpp b/llvm/lib/IR/Constants.cpp --- a/llvm/lib/IR/Constants.cpp +++ b/llvm/lib/IR/Constants.cpp @@ -575,8 +575,10 @@ return false; // Relative pointers do not need to be dynamically relocated. - if (auto *LHSGV = dyn_cast(LHSOp0->stripPointerCasts())) - if (auto *RHSGV = dyn_cast(RHSOp0->stripPointerCasts())) + if (auto *LHSGV = + dyn_cast(LHSOp0->stripInBoundsConstantOffsets())) + if (auto *RHSGV = + dyn_cast(RHSOp0->stripInBoundsConstantOffsets())) if (LHSGV->isDSOLocal() && RHSGV->isDSOLocal()) return false; } diff --git a/llvm/test/CodeGen/X86/relptr-rodata.ll b/llvm/test/CodeGen/X86/relptr-rodata.ll --- a/llvm/test/CodeGen/X86/relptr-rodata.ll +++ b/llvm/test/CodeGen/X86/relptr-rodata.ll @@ -19,3 +19,27 @@ ; CHECK: relro2: ; CHECK: .long hidden-relro2 @relro2 = constant i32 trunc (i64 sub (i64 ptrtoint (i8* @hidden to i64), i64 ptrtoint (i32* @relro2 to i64)) to i32) + +; CHECK: .section .rodata.cst16 +; CHECK-NEXT: .globl _ZTV4Test +; CHECK: _ZTV4Test +; CHECK: .quad 0 +; CHECK: .long (_ZTI4Test.rtti_proxy-_ZTV4Test)-8 +; CHECK: .long (_ZN4TestD1Ev.stub@PLT-_ZTV4Test)-12 + +@_ZTI4Test = external global i8 +@_ZTI4Test.rtti_proxy = hidden unnamed_addr constant i8* @_ZTI4Test + +declare void @_ZN4TestD2Ev() +define hidden void @_ZN4TestD1Ev.stub() unnamed_addr { +entry: + tail call void @_ZN4TestD2Ev() + ret void +} + +@_ZTV4Test = dso_local unnamed_addr constant { { i8*, i32, i32 } } { + { i8*, i32, i32 } { + i8* null, + i32 trunc (i64 sub (i64 ptrtoint (i8** @_ZTI4Test.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i32, i32 } }, { { i8*, i32, i32 } }* @_ZTV4Test, i32 0, i32 0, i32 1) to i64)) to i32), + i32 trunc (i64 sub (i64 ptrtoint (void ()* @_ZN4TestD1Ev.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ { i8*, i32, i32 } }, { { i8*, i32, i32 } }* @_ZTV4Test, i32 0, i32 0, i32 2) to i64)) to i32) + } }, align 8