From 71568a9e28d6aa5ed580cbedcb96634452555143 Mon Sep 17 00:00:00 2001 From: Leonard Chan Date: Thu, 11 Jun 2020 11:17:08 -0700 Subject: [PATCH 01/12] [clang] Frontend components for the relative vtables ABI (round 2) This patch contains all of the clang changes from D72959. - Generalize the relative vtables ABI such that it can be used by other targets. - Add an enum VTableComponentLayout which controls whether components in the vtable should be pointers to other structs or relative offsets to those structs. Other ABIs can change this enum to restructure how components in the vtable are laid out/accessed. - Add methods to ConstantInitBuilder for inserting relative offsets to a specified position in the aggregate being constructed. - Fix failing tests under new PM and ASan and MSan issues. See D72959 for background info. Differential Revision: https://reviews.llvm.org/D77592 --- clang/include/clang/AST/VTableBuilder.h | 34 +- clang/include/clang/Basic/LangOptions.def | 3 + .../clang/CodeGen/ConstantInitBuilder.h | 23 +- clang/include/clang/Driver/Options.td | 7 + clang/lib/AST/ASTContext.cpp | 11 +- clang/lib/AST/VTableBuilder.cpp | 77 +++-- clang/lib/CodeGen/CGClass.cpp | 9 +- clang/lib/CodeGen/CGVTables.cpp | 319 +++++++++++++++--- clang/lib/CodeGen/CGVTables.h | 40 ++- clang/lib/CodeGen/ConstantInitBuilder.cpp | 22 +- clang/lib/CodeGen/ItaniumCXXABI.cpp | 209 ++++++++---- clang/lib/CodeGen/MicrosoftCXXABI.cpp | 9 +- clang/lib/Frontend/CompilerInvocation.cpp | 5 + .../available_externally-vtable.cpp | 23 ++ ...child-inheritted-from-parent-in-comdat.cpp | 53 +++ .../child-vtable-in-comdat.cpp | 55 +++ .../cross-translation-unit-1.cpp | 39 +++ .../cross-translation-unit-2.cpp | 38 +++ .../RelativeVTablesABI/cross-tu-header.h | 10 + .../diamond-inheritance.cpp | 57 ++++ .../diamond-virtual-inheritance.cpp | 96 ++++++ .../RelativeVTablesABI/dynamic-cast.cpp | 78 +++++ .../inheritted-virtual-function.cpp | 29 ++ .../inline-virtual-function.cpp | 23 ++ .../inlined-key-function.cpp | 29 ++ .../member-function-pointer.cpp | 47 +++ .../multiple-inheritance.cpp | 55 +++ .../no-alias-when-dso-local.cpp | 16 + .../no-stub-when-dso-local.cpp | 49 +++ .../override-pure-virtual-method.cpp | 34 ++ .../overriden-virtual-function.cpp | 30 ++ .../parent-and-child-in-comdats.cpp | 62 ++++ .../parent-vtable-in-comdat.cpp | 48 +++ .../pass-byval-attributes.cpp | 37 ++ .../relative-vtables-flag.cpp | 24 ++ .../simple-vtable-definition.cpp | 43 +++ .../RelativeVTablesABI/stub-linkages.cpp | 51 +++ .../RelativeVTablesABI/thunk-mangling.cpp | 31 ++ .../RelativeVTablesABI/type-info.cpp | 77 +++++ .../RelativeVTablesABI/vbase-offset.cpp | 36 ++ .../virtual-function-call.cpp | 22 ++ .../vtable-hidden-when-in-comdat.cpp | 19 ++ 42 files changed, 1839 insertions(+), 140 deletions(-) create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/available_externally-vtable.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/child-inheritted-from-parent-in-comdat.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/child-vtable-in-comdat.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-1.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-2.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/cross-tu-header.h create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/diamond-inheritance.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/diamond-virtual-inheritance.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/dynamic-cast.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/inheritted-virtual-function.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/inline-virtual-function.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/inlined-key-function.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/member-function-pointer.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/multiple-inheritance.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/no-alias-when-dso-local.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/no-stub-when-dso-local.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/override-pure-virtual-method.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/overriden-virtual-function.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/parent-and-child-in-comdats.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/parent-vtable-in-comdat.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/pass-byval-attributes.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/relative-vtables-flag.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/simple-vtable-definition.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/stub-linkages.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/thunk-mangling.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/type-info.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/vbase-offset.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/virtual-function-call.cpp create mode 100644 clang/test/CodeGenCXX/RelativeVTablesABI/vtable-hidden-when-in-comdat.cpp diff --git a/clang/include/clang/AST/VTableBuilder.h b/clang/include/clang/AST/VTableBuilder.h index 43c84292c09157..e6557e4389196e 100644 --- a/clang/include/clang/AST/VTableBuilder.h +++ b/clang/include/clang/AST/VTableBuilder.h @@ -238,6 +238,11 @@ class VTableLayout { typedef llvm::DenseMap AddressPointsMapTy; + // Mapping between the VTable index and address point index. This is useful + // when you don't care about the base subobjects and only want the address + // point for a given vtable index. + typedef llvm::SmallVector AddressPointsIndexMapTy; + private: // Stores the component indices of the first component of each virtual table in // the virtual table group. To save a little memory in the common case where @@ -253,6 +258,9 @@ class VTableLayout { /// Address points for all vtables. AddressPointsMapTy AddressPoints; + /// Address points for all vtable indices. + AddressPointsIndexMapTy AddressPointIndices; + public: VTableLayout(ArrayRef VTableIndices, ArrayRef VTableComponents, @@ -277,6 +285,10 @@ class VTableLayout { return AddressPoints; } + const AddressPointsIndexMapTy &getAddressPointIndices() const { + return AddressPointIndices; + } + size_t getNumVTables() const { if (VTableIndices.empty()) return 1; @@ -371,7 +383,17 @@ class ItaniumVTableContext : public VTableContextBase { void computeVTableRelatedInformation(const CXXRecordDecl *RD) override; public: - ItaniumVTableContext(ASTContext &Context); + enum VTableComponentLayout { + /// Components in the vtable are pointers to other structs/functions. + Pointer, + + /// Components in the vtable are relative offsets between the vtable and the + /// other structs/functions. + Relative, + }; + + ItaniumVTableContext(ASTContext &Context, + VTableComponentLayout ComponentLayout = Pointer); ~ItaniumVTableContext() override; const VTableLayout &getVTableLayout(const CXXRecordDecl *RD) { @@ -402,6 +424,16 @@ class ItaniumVTableContext : public VTableContextBase { 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 index caddae213a2693..9236327f688f43 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -376,6 +376,9 @@ LANGOPT(BranchTargetEnforcement, 1, 0, "Branch-target enforcement enabled") LANGOPT(SpeculativeLoadHardening, 1, 0, "Speculative load hardening enabled") +LANGOPT(RelativeCXXABIVTables, 1, 0, + "Use an ABI-incompatible v-table layout that uses relative references") + #undef LANGOPT #undef COMPATIBLE_LANGOPT #undef BENIGN_LANGOPT diff --git a/clang/include/clang/CodeGen/ConstantInitBuilder.h b/clang/include/clang/CodeGen/ConstantInitBuilder.h index fd07e91ba6ae23..88e357a0c29c06 100644 --- a/clang/include/clang/CodeGen/ConstantInitBuilder.h +++ b/clang/include/clang/CodeGen/ConstantInitBuilder.h @@ -226,6 +226,13 @@ class ConstantAggregateBuilderBase { add(getRelativeOffset(type, target)); } + /// Same as addRelativeOffset(), but instead relative to an element in this + /// aggregate, identified by its index. + void addRelativeOffsetToPosition(llvm::IntegerType *type, + llvm::Constant *target, size_t position) { + add(getRelativeOffsetToPosition(type, target, position)); + } + /// Add a relative offset to the target address, plus a small /// constant offset. This is primarily useful when the relative /// offset is known to be a multiple of (say) four and therefore @@ -298,10 +305,18 @@ class ConstantAggregateBuilderBase { /// position to be filled. This is computed with an indexed /// getelementptr rather than by computing offsets. /// - /// The returned pointer will have type T*, where T is the given - /// position. + /// The returned pointer will have type T*, where T is the given type. This + /// type can differ from the type of the actual element. llvm::Constant *getAddrOfCurrentPosition(llvm::Type *type); + /// Produce an address which points to a position in the aggregate being + /// constructed. This is computed with an indexed getelementptr rather than by + /// computing offsets. + /// + /// The returned pointer will have type T*, where T is the given type. This + /// type can differ from the type of the actual element. + llvm::Constant *getAddrOfPosition(llvm::Type *type, size_t position); + llvm::ArrayRef getGEPIndicesToCurrentPosition( llvm::SmallVectorImpl &indices) { getGEPIndicesTo(indices, Builder.Buffer.size()); @@ -319,6 +334,10 @@ class ConstantAggregateBuilderBase { llvm::Constant *getRelativeOffset(llvm::IntegerType *offsetType, llvm::Constant *target); + llvm::Constant *getRelativeOffsetToPosition(llvm::IntegerType *offsetType, + llvm::Constant *target, + size_t position); + CharUnits getOffsetFromGlobalTo(size_t index) const; }; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index b84b9293779f76..1f4fea9a0be5a4 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1299,6 +1299,13 @@ def fno_fine_grained_bitfield_accesses : Flag<["-"], "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 index 60482ea0664d63..e2df64107c9e1c 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -10600,10 +10600,15 @@ bool ASTContext::isNearlyEmpty(const CXXRecordDecl *RD) const { 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 index 0bff976905fcd8..013ebe23ec9188 100644 --- a/clang/lib/AST/VTableBuilder.cpp +++ b/clang/lib/AST/VTableBuilder.cpp @@ -535,6 +535,8 @@ class VCallAndVBaseOffsetBuilder { 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 @@ class VCallAndVBaseOffsetBuilder { 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 @@ CharUnits VCallAndVBaseOffsetBuilder::getCurrentOffsetOffset() const { // 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 @@ ThisAdjustment ItaniumVTableBuilder::ComputeThisAdjustment( 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 @@ void ItaniumVTableBuilder::LayoutPrimaryAndSecondaryVTables( VTableIndices.push_back(VTableIndex); // Add vcall and vbase offsets for this vtable. - VCallAndVBaseOffsetBuilder Builder(MostDerivedClass, LayoutClass, &Overriders, - Base, BaseIsVirtualInLayoutClass, - OffsetInLayoutClass); + VCallAndVBaseOffsetBuilder Builder( + VTables, MostDerivedClass, LayoutClass, &Overriders, Base, + BaseIsVirtualInLayoutClass, OffsetInLayoutClass); Components.append(Builder.components_begin(), Builder.components_end()); // Check if we need to add these vcall offsets. @@ -2200,12 +2208,40 @@ void ItaniumVTableBuilder::dumpLayout(raw_ostream &Out) { } } +static VTableLayout::AddressPointsIndexMapTy +MakeAddressPointIndices(const VTableLayout::AddressPointsMapTy &addressPoints, + unsigned numVTables) { + VTableLayout::AddressPointsIndexMapTy indexMap(numVTables); + + for (auto it = addressPoints.begin(); it != addressPoints.end(); ++it) { + const auto &addressPointLoc = it->second; + unsigned vtableIndex = addressPointLoc.VTableIndex; + unsigned addressPoint = addressPointLoc.AddressPointIndex; + if (indexMap[vtableIndex]) { + // Multiple BaseSubobjects can map to the same AddressPointLocation, but + // every vtable index should have a unique address point. + assert(indexMap[vtableIndex] == addressPoint && + "Every vtable index should have a unique address point. Found a " + "vtable that has two different address points."); + } else { + indexMap[vtableIndex] = addressPoint; + } + } + + // Note that by this point, not all the address may be initialized if the + // AddressPoints map is empty. This is ok if the map isn't needed. See + // MicrosoftVTableContext::computeVTableRelatedInformation() which uses an + // emprt map. + return indexMap; +} + VTableLayout::VTableLayout(ArrayRef VTableIndices, ArrayRef VTableComponents, ArrayRef VTableThunks, const AddressPointsMapTy &AddressPoints) : VTableComponents(VTableComponents), VTableThunks(VTableThunks), - AddressPoints(AddressPoints) { + AddressPoints(AddressPoints), AddressPointIndices(MakeAddressPointIndices( + AddressPoints, VTableIndices.size())) { if (VTableIndices.size() <= 1) assert(VTableIndices.size() == 1 && VTableIndices[0] == 0); else @@ -2221,8 +2257,9 @@ VTableLayout::VTableLayout(ArrayRef VTableIndices, VTableLayout::~VTableLayout() { } -ItaniumVTableContext::ItaniumVTableContext(ASTContext &Context) - : VTableContextBase(/*MS=*/false) {} +ItaniumVTableContext::ItaniumVTableContext( + ASTContext &Context, VTableComponentLayout ComponentLayout) + : VTableContextBase(/*MS=*/false), ComponentLayout(ComponentLayout) {} ItaniumVTableContext::~ItaniumVTableContext() {} @@ -2251,7 +2288,7 @@ ItaniumVTableContext::getVirtualBaseOffsetOffset(const CXXRecordDecl *RD, 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 index c0980df4066157..1d78b3fccb13ea 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -253,8 +253,13 @@ ApplyNonVirtualAndVirtualOffset(CodeGenFunction &CGF, Address addr, // 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.cpp b/clang/lib/CodeGen/CGVTables.cpp index 6a0a848a49df92..276993581117e5 100644 --- a/clang/lib/CodeGen/CGVTables.cpp +++ b/clang/lib/CodeGen/CGVTables.cpp @@ -618,29 +618,178 @@ void CodeGenVTables::EmitThunks(GlobalDecl GD) { maybeEmitThunk(GD, Thunk, /*ForVTable=*/false); } -void CodeGenVTables::addVTableComponent( - ConstantArrayBuilder &builder, const VTableLayout &layout, - unsigned idx, llvm::Constant *rtti, unsigned &nextVTableThunkIndex) { - auto &component = layout.vtable_components()[idx]; +void CodeGenVTables::addRelativeComponent(ConstantArrayBuilder &builder, + llvm::Constant *component, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage, + bool isCompleteDtor) const { + // No need to get the offset of a nullptr. + if (component->isNullValue()) + return builder.add(llvm::ConstantInt::get(CGM.Int32Ty, 0)); + + auto *globalVal = + cast(component->stripPointerCastsAndAliases()); + llvm::Module &module = CGM.getModule(); + + // We don't want to copy the linkage of the vtable exactly because we still + // want the stub/proxy to be emitted for properly calculating the offset. + // Examples where there would be no symbol emitted are available_externally + // and private linkages. + auto stubLinkage = vtableHasLocalLinkage ? llvm::GlobalValue::InternalLinkage + : llvm::GlobalValue::ExternalLinkage; + + llvm::Constant *target; + if (auto *func = dyn_cast(globalVal)) { + target = getOrCreateRelativeStub(func, stubLinkage, isCompleteDtor); + } else { + llvm::SmallString<16> rttiProxyName(globalVal->getName()); + rttiProxyName.append(".rtti_proxy"); + + // The RTTI component may not always be emitted in the same linkage unit as + // the vtable. As a general case, we can make a dso_local proxy to the RTTI + // that points to the actual RTTI struct somewhere. This will result in a + // GOTPCREL relocation when taking the relative offset to the proxy. + llvm::GlobalVariable *proxy = module.getNamedGlobal(rttiProxyName); + if (!proxy) { + proxy = new llvm::GlobalVariable(module, globalVal->getType(), + /*isConstant=*/true, stubLinkage, + globalVal, rttiProxyName); + proxy->setDSOLocal(true); + proxy->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + if (!proxy->hasLocalLinkage()) { + proxy->setVisibility(llvm::GlobalValue::HiddenVisibility); + proxy->setComdat(module.getOrInsertComdat(rttiProxyName)); + } + } + target = proxy; + } + + builder.addRelativeOffsetToPosition(CGM.Int32Ty, target, + /*position=*/vtableAddressPoint); +} - auto addOffsetConstant = [&](CharUnits offset) { - builder.add(llvm::ConstantExpr::getIntToPtr( - llvm::ConstantInt::get(CGM.PtrDiffTy, offset.getQuantity()), - CGM.Int8PtrTy)); - }; +llvm::Function *CodeGenVTables::getOrCreateRelativeStub( + llvm::Function *func, llvm::GlobalValue::LinkageTypes stubLinkage, + bool isCompleteDtor) const { + // A complete object destructor can later be substituted in the vtable for an + // appropriate base object destructor when optimizations are enabled. This can + // happen for child classes that don't have their own destructor. In the case + // where a parent virtual destructor is not guaranteed to be in the same + // linkage unit as the child vtable, it's possible for an external reference + // for this destructor to be substituted into the child vtable, preventing it + // from being in rodata. If this function is a complete virtual destructor, we + // can just force a stub to be emitted for it. + if (func->isDSOLocal() && !isCompleteDtor) + return func; + + llvm::SmallString<16> stubName(func->getName()); + stubName.append(".stub"); + + // Instead of taking the offset between the vtable and virtual function + // directly, we emit a dso_local stub that just contains a tail call to the + // original virtual function and take the offset between that and the + // vtable. We do this because there are some cases where the original + // function that would've been inserted into the vtable is not dso_local + // which may require some kind of dynamic relocation which prevents the + // vtable from being readonly. On x86_64, taking the offset between the + // function and the vtable gets lowered to the offset between the PLT entry + // for the function and the vtable which gives us a PLT32 reloc. On AArch64, + // right now only CALL26 and JUMP26 instructions generate PLT relocations, + // so we manifest them with stubs that are just jumps to the original + // function. + auto &module = CGM.getModule(); + llvm::Function *stub = module.getFunction(stubName); + if (stub) { + assert(stub->isDSOLocal() && + "The previous definition of this stub should've been dso_local."); + return stub; + } + + stub = llvm::Function::Create(func->getFunctionType(), stubLinkage, stubName, + module); + + // Propogate function attributes. + stub->setAttributes(func->getAttributes()); + + stub->setDSOLocal(true); + stub->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + if (!stub->hasLocalLinkage()) { + stub->setVisibility(llvm::GlobalValue::HiddenVisibility); + stub->setComdat(module.getOrInsertComdat(stubName)); + } + + // Fill the stub with a tail call that will be optimized. + llvm::BasicBlock *block = + llvm::BasicBlock::Create(module.getContext(), "entry", stub); + llvm::IRBuilder<> block_builder(block); + llvm::SmallVector args; + for (auto &arg : stub->args()) + args.push_back(&arg); + llvm::CallInst *call = block_builder.CreateCall(func, args); + call->setAttributes(func->getAttributes()); + call->setTailCall(); + if (call->getType()->isVoidTy()) + block_builder.CreateRetVoid(); + else + block_builder.CreateRet(call); + + return stub; +} + +bool CodeGenVTables::useRelativeLayout() const { + return CGM.getTarget().getCXXABI().isItaniumFamily() && + CGM.getItaniumVTableContext().isRelativeLayout(); +} + +llvm::Type *CodeGenVTables::getVTableComponentType() const { + if (useRelativeLayout()) + return CGM.Int32Ty; + return CGM.Int8PtrTy; +} + +static void AddPointerLayoutOffset(const CodeGenModule &CGM, + ConstantArrayBuilder &builder, + CharUnits offset) { + builder.add(llvm::ConstantExpr::getIntToPtr( + llvm::ConstantInt::get(CGM.PtrDiffTy, offset.getQuantity()), + CGM.Int8PtrTy)); +} + +static void AddRelativeLayoutOffset(const CodeGenModule &CGM, + ConstantArrayBuilder &builder, + CharUnits offset) { + builder.add(llvm::ConstantInt::get(CGM.Int32Ty, offset.getQuantity())); +} + +void CodeGenVTables::addVTableComponent(ConstantArrayBuilder &builder, + const VTableLayout &layout, + unsigned componentIndex, + llvm::Constant *rtti, + unsigned &nextVTableThunkIndex, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage) { + auto &component = layout.vtable_components()[componentIndex]; + + auto addOffsetConstant = + useRelativeLayout() ? AddRelativeLayoutOffset : AddPointerLayoutOffset; switch (component.getKind()) { case VTableComponent::CK_VCallOffset: - return addOffsetConstant(component.getVCallOffset()); + return addOffsetConstant(CGM, builder, component.getVCallOffset()); case VTableComponent::CK_VBaseOffset: - return addOffsetConstant(component.getVBaseOffset()); + return addOffsetConstant(CGM, builder, component.getVBaseOffset()); case VTableComponent::CK_OffsetToTop: - return addOffsetConstant(component.getOffsetToTop()); + return addOffsetConstant(CGM, builder, component.getOffsetToTop()); case VTableComponent::CK_RTTI: - return builder.add(llvm::ConstantExpr::getBitCast(rtti, CGM.Int8PtrTy)); + if (useRelativeLayout()) + return addRelativeComponent(builder, rtti, vtableAddressPoint, + vtableHasLocalLinkage, + /*isCompleteDtor=*/false); + else + return builder.add(llvm::ConstantExpr::getBitCast(rtti, CGM.Int8PtrTy)); case VTableComponent::CK_FunctionPointer: case VTableComponent::CK_CompleteDtorPointer: @@ -674,11 +823,21 @@ void CodeGenVTables::addVTableComponent( ? MD->hasAttr() : (MD->hasAttr() || !MD->hasAttr()); if (!CanEmitMethod) - return builder.addNullPointer(CGM.Int8PtrTy); + return builder.add(llvm::ConstantExpr::getNullValue(CGM.Int8PtrTy)); // Method is acceptable, continue processing as usual. } auto getSpecialVirtualFn = [&](StringRef name) -> llvm::Constant * { + // FIXME(PR43094): When merging comdat groups, lld can select a local + // symbol as the signature symbol even though it cannot be accessed + // outside that symbol's TU. The relative vtables ABI would make + // __cxa_pure_virtual and __cxa_deleted_virtual local symbols, and + // depending on link order, the comdat groups could resolve to the one + // with the local symbol. As a temporary solution, fill these components + // with zero. We shouldn't be calling these in the first place anyway. + if (useRelativeLayout()) + return llvm::ConstantPointerNull::get(CGM.Int8PtrTy); + // For NVPTX devices in OpenMP emit special functon as null pointers, // otherwise linking ends up with unresolved references. if (CGM.getLangOpts().OpenMP && CGM.getLangOpts().OpenMPIsDevice && @@ -699,19 +858,20 @@ void CodeGenVTables::addVTableComponent( if (cast(GD.getDecl())->isPure()) { if (!PureVirtualFn) PureVirtualFn = - getSpecialVirtualFn(CGM.getCXXABI().GetPureVirtualCallName()); + getSpecialVirtualFn(CGM.getCXXABI().GetPureVirtualCallName()); fnPtr = PureVirtualFn; // Deleted virtual member functions. } else if (cast(GD.getDecl())->isDeleted()) { if (!DeletedVirtualFn) DeletedVirtualFn = - getSpecialVirtualFn(CGM.getCXXABI().GetDeletedVirtualCallName()); + getSpecialVirtualFn(CGM.getCXXABI().GetDeletedVirtualCallName()); fnPtr = DeletedVirtualFn; // Thunks. } else if (nextVTableThunkIndex < layout.vtable_thunks().size() && - layout.vtable_thunks()[nextVTableThunkIndex].first == idx) { + layout.vtable_thunks()[nextVTableThunkIndex].first == + componentIndex) { auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; nextVTableThunkIndex++; @@ -723,13 +883,19 @@ void CodeGenVTables::addVTableComponent( fnPtr = CGM.GetAddrOfFunction(GD, fnTy, /*ForVTable=*/true); } - fnPtr = llvm::ConstantExpr::getBitCast(fnPtr, CGM.Int8PtrTy); - builder.add(fnPtr); - return; + if (useRelativeLayout()) { + return addRelativeComponent( + builder, fnPtr, vtableAddressPoint, vtableHasLocalLinkage, + component.getKind() == VTableComponent::CK_CompleteDtorPointer); + } else + return builder.add(llvm::ConstantExpr::getBitCast(fnPtr, CGM.Int8PtrTy)); } case VTableComponent::CK_UnusedFunctionPointer: - return builder.addNullPointer(CGM.Int8PtrTy); + if (useRelativeLayout()) + return builder.add(llvm::ConstantExpr::getNullValue(CGM.Int32Ty)); + else + return builder.addNullPointer(CGM.Int8PtrTy); } llvm_unreachable("Unexpected vtable component kind"); @@ -737,34 +903,41 @@ void CodeGenVTables::addVTableComponent( llvm::Type *CodeGenVTables::getVTableType(const VTableLayout &layout) { SmallVector tys; - for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) { - tys.push_back(llvm::ArrayType::get(CGM.Int8PtrTy, layout.getVTableSize(i))); - } + llvm::Type *componentType = getVTableComponentType(); + for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) + tys.push_back(llvm::ArrayType::get(componentType, layout.getVTableSize(i))); return llvm::StructType::get(CGM.getLLVMContext(), tys); } void CodeGenVTables::createVTableInitializer(ConstantStructBuilder &builder, const VTableLayout &layout, - llvm::Constant *rtti) { + llvm::Constant *rtti, + bool vtableHasLocalLinkage) { + llvm::Type *componentType = getVTableComponentType(); + + const auto &addressPoints = layout.getAddressPointIndices(); unsigned nextVTableThunkIndex = 0; - for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) { - auto vtableElem = builder.beginArray(CGM.Int8PtrTy); - size_t thisIndex = layout.getVTableOffset(i); - size_t nextIndex = thisIndex + layout.getVTableSize(i); - for (unsigned i = thisIndex; i != nextIndex; ++i) { - addVTableComponent(vtableElem, layout, i, rtti, nextVTableThunkIndex); + for (unsigned vtableIndex = 0, endIndex = layout.getNumVTables(); + vtableIndex != endIndex; ++vtableIndex) { + auto vtableElem = builder.beginArray(componentType); + + size_t vtableStart = layout.getVTableOffset(vtableIndex); + size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex); + for (size_t componentIndex = vtableStart; componentIndex < vtableEnd; + ++componentIndex) { + addVTableComponent(vtableElem, layout, componentIndex, rtti, + nextVTableThunkIndex, addressPoints[vtableIndex], + vtableHasLocalLinkage); } vtableElem.finishAndAddTo(builder); } } -llvm::GlobalVariable * -CodeGenVTables::GenerateConstructionVTable(const CXXRecordDecl *RD, - const BaseSubobject &Base, - bool BaseIsVirtual, - llvm::GlobalVariable::LinkageTypes Linkage, - VTableAddressPointsMapTy& AddressPoints) { +llvm::GlobalVariable *CodeGenVTables::GenerateConstructionVTable( + const CXXRecordDecl *RD, const BaseSubobject &Base, bool BaseIsVirtual, + llvm::GlobalVariable::LinkageTypes Linkage, + VTableAddressPointsMapTy &AddressPoints) { if (CGDebugInfo *DI = CGM.getModuleDebugInfo()) DI->completeClassData(Base.getBase()); @@ -781,7 +954,15 @@ CodeGenVTables::GenerateConstructionVTable(const CXXRecordDecl *RD, cast(CGM.getCXXABI().getMangleContext()) .mangleCXXCtorVTable(RD, Base.getBaseOffset().getQuantity(), Base.getBase(), Out); - StringRef Name = OutName.str(); + SmallString<256> Name(OutName); + + bool UsingRelativeLayout = getItaniumVTableContext().isRelativeLayout(); + bool VTableAliasExists = + UsingRelativeLayout && CGM.getModule().getNamedAlias(Name); + if (VTableAliasExists) { + // We previously made the vtable hidden and changed its name. + Name.append(".local"); + } llvm::Type *VTType = getVTableType(*VTLayout); @@ -808,7 +989,8 @@ CodeGenVTables::GenerateConstructionVTable(const CXXRecordDecl *RD, // Create and set the initializer. ConstantInitBuilder builder(CGM); auto components = builder.beginStruct(); - createVTableInitializer(components, *VTLayout, RTTI); + createVTableInitializer(components, *VTLayout, RTTI, + VTable->hasLocalLinkage()); components.finishAndSetAsInitializer(VTable); // Set properties only after the initializer has been set to ensure that the @@ -818,9 +1000,68 @@ CodeGenVTables::GenerateConstructionVTable(const CXXRecordDecl *RD, CGM.EmitVTableTypeMetadata(RD, VTable, *VTLayout.get()); + if (UsingRelativeLayout && !VTable->isDSOLocal()) + GenerateRelativeVTableAlias(VTable, OutName); + return VTable; } +// If the VTable is not dso_local, then we will not be able to indicate that +// the VTable does not need a relocation and move into rodata. A frequent +// time this can occur is for classes that should be made public from a DSO +// (like in libc++). For cases like these, we can make the vtable hidden or +// private and create a public alias with the same visibility and linkage as +// the original vtable type. +void CodeGenVTables::GenerateRelativeVTableAlias(llvm::GlobalVariable *VTable, + llvm::StringRef AliasNameRef) { + assert(getItaniumVTableContext().isRelativeLayout() && + "Can only use this if the relative vtable ABI is used"); + assert(!VTable->isDSOLocal() && "This should be called only if the vtable is " + "not guaranteed to be dso_local"); + + // If the vtable is available_externally, we shouldn't (or need to) generate + // an alias for it in the first place since the vtable won't actually by + // emitted in this compilation unit. + if (VTable->hasAvailableExternallyLinkage()) + return; + + // Create a new string in the event the alias is already the name of the + // vtable. Using the reference directly could lead to use of an inititialized + // value in the module's StringMap. + llvm::SmallString<256> AliasName(AliasNameRef); + VTable->setName(AliasName + ".local"); + + auto Linkage = VTable->getLinkage(); + assert(llvm::GlobalAlias::isValidLinkage(Linkage) && + "Invalid vtable alias linkage"); + + llvm::GlobalAlias *VTableAlias = CGM.getModule().getNamedAlias(AliasName); + if (!VTableAlias) { + VTableAlias = llvm::GlobalAlias::create(VTable->getValueType(), + VTable->getAddressSpace(), Linkage, + AliasName, &CGM.getModule()); + } else { + assert(VTableAlias->getValueType() == VTable->getValueType()); + assert(VTableAlias->getLinkage() == Linkage); + } + VTableAlias->setVisibility(VTable->getVisibility()); + VTableAlias->setUnnamedAddr(VTable->getUnnamedAddr()); + + // Both of these imply dso_local for the vtable. + if (!VTable->hasComdat()) { + // If this is in a comdat, then we shouldn't make the linkage private due to + // an issue in lld where private symbols can be used as the key symbol when + // choosing the prevelant group. This leads to "relocation refers to a + // symbol in a discarded section". + VTable->setLinkage(llvm::GlobalValue::PrivateLinkage); + } else { + // We should at least make this hidden since we don't want to expose it. + VTable->setVisibility(llvm::GlobalValue::HiddenVisibility); + } + + VTableAlias->setAliasee(VTable); +} + static bool shouldEmitAvailableExternallyVTable(const CodeGenModule &CGM, const CXXRecordDecl *RD) { return CGM.getCodeGenOpts().OptimizationLevel > 0 && diff --git a/clang/lib/CodeGen/CGVTables.h b/clang/lib/CodeGen/CGVTables.h index a47841bfc6c3a6..bdfc075ee30539 100644 --- a/clang/lib/CodeGen/CGVTables.h +++ b/clang/lib/CodeGen/CGVTables.h @@ -62,16 +62,39 @@ class CodeGenVTables { bool ForVTable); void addVTableComponent(ConstantArrayBuilder &builder, - const VTableLayout &layout, unsigned idx, - llvm::Constant *rtti, - unsigned &nextVTableThunkIndex); + const VTableLayout &layout, unsigned componentIndex, + llvm::Constant *rtti, unsigned &nextVTableThunkIndex, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage); + + /// Add a 32-bit offset to a component relative to the vtable when using the + /// relative vtables ABI. The array builder points to the start of the vtable. + void addRelativeComponent(ConstantArrayBuilder &builder, + llvm::Constant *component, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage, + bool isCompleteDtor) const; + + /// Create a dso_local stub that will be used for a relative reference in the + /// relative vtable layout. This stub will just be a tail call to the original + /// function and propagate any function attributes from the original. If the + /// original function is already dso_local, the original is returned instead + /// and a stub is not created. + llvm::Function * + getOrCreateRelativeStub(llvm::Function *func, + llvm::GlobalValue::LinkageTypes stubLinkage, + bool isCompleteDtor) const; + + bool useRelativeLayout() const; + + llvm::Type *getVTableComponentType() const; public: /// Add vtable components for the given vtable layout to the given /// global initializer. void createVTableInitializer(ConstantStructBuilder &builder, - const VTableLayout &layout, - llvm::Constant *rtti); + const VTableLayout &layout, llvm::Constant *rtti, + bool vtableHasLocalLinkage); CodeGenVTables(CodeGenModule &CGM); @@ -124,6 +147,13 @@ class CodeGenVTables { /// arrays of pointers, with one struct element for each vtable in the vtable /// group. llvm::Type *getVTableType(const VTableLayout &layout); + + /// Generate a public facing alias for the vtable and make the vtable either + /// hidden or private. The alias will have the original linkage and visibility + /// of the vtable. This is used for cases under the relative vtables ABI + /// when a vtable may not be dso_local. + void GenerateRelativeVTableAlias(llvm::GlobalVariable *VTable, + llvm::StringRef AliasNameRef); }; } // end namespace CodeGen diff --git a/clang/lib/CodeGen/ConstantInitBuilder.cpp b/clang/lib/CodeGen/ConstantInitBuilder.cpp index 2d63d88020bed5..326f079e82fa49 100644 --- a/clang/lib/CodeGen/ConstantInitBuilder.cpp +++ b/clang/lib/CodeGen/ConstantInitBuilder.cpp @@ -128,8 +128,14 @@ void ConstantAggregateBuilderBase::addSize(CharUnits size) { llvm::Constant * ConstantAggregateBuilderBase::getRelativeOffset(llvm::IntegerType *offsetType, llvm::Constant *target) { + return getRelativeOffsetToPosition(offsetType, target, + Builder.SelfReferences.size()); +} + +llvm::Constant *ConstantAggregateBuilderBase::getRelativeOffsetToPosition( + llvm::IntegerType *offsetType, llvm::Constant *target, size_t position) { // Compute the address of the relative-address slot. - auto base = getAddrOfCurrentPosition(offsetType); + auto base = getAddrOfPosition(offsetType, position); // Subtract. base = llvm::ConstantExpr::getPtrToInt(base, Builder.CGM.IntPtrTy); @@ -144,6 +150,20 @@ ConstantAggregateBuilderBase::getRelativeOffset(llvm::IntegerType *offsetType, return offset; } +llvm::Constant * +ConstantAggregateBuilderBase::getAddrOfPosition(llvm::Type *type, + size_t position) { + // Make a global variable. We will replace this with a GEP to this + // position after installing the initializer. + auto dummy = new llvm::GlobalVariable(Builder.CGM.getModule(), type, true, + llvm::GlobalVariable::PrivateLinkage, + nullptr, ""); + Builder.SelfReferences.emplace_back(dummy); + auto &entry = Builder.SelfReferences.back(); + (void)getGEPIndicesTo(entry.Indices, position + Begin); + return dummy; +} + llvm::Constant * ConstantAggregateBuilderBase::getAddrOfCurrentPosition(llvm::Type *type) { // Make a global variable. We will replace this with a GEP to this diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index 677d6524fabdf9..a4f99992780c90 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -703,9 +703,9 @@ CGCallee ItaniumCXXABI::EmitLoadOfMemberFunctionPointer( TypeId = llvm::MetadataAsValue::get(CGF.getLLVMContext(), MD); } - llvm::Value *VFPAddr = Builder.CreateGEP(VTable, VTableOffset); - if (ShouldEmitVFEInfo) { + llvm::Value *VFPAddr = Builder.CreateGEP(VTable, VTableOffset); + // If doing VFE, load from the vtable with a type.checked.load intrinsic // call. Note that we use the GEP to calculate the address to load from // and pass 0 as the offset to the intrinsic. This is because every @@ -722,14 +722,25 @@ CGCallee ItaniumCXXABI::EmitLoadOfMemberFunctionPointer( // When not doing VFE, emit a normal load, as it allows more // optimisations than type.checked.load. if (ShouldEmitCFICheck || ShouldEmitWPDInfo) { + llvm::Value *VFPAddr = Builder.CreateGEP(VTable, VTableOffset); CheckResult = Builder.CreateCall( CGM.getIntrinsic(llvm::Intrinsic::type_test), {Builder.CreateBitCast(VFPAddr, CGF.Int8PtrTy), TypeId}); } - VFPAddr = - Builder.CreateBitCast(VFPAddr, FTy->getPointerTo()->getPointerTo()); - VirtualFn = Builder.CreateAlignedLoad(VFPAddr, CGF.getPointerAlign(), - "memptr.virtualfn"); + + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + VirtualFn = CGF.Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::load_relative, + {VTableOffset->getType()}), + {VTable, VTableOffset}); + VirtualFn = CGF.Builder.CreateBitCast(VirtualFn, FTy->getPointerTo()); + } else { + llvm::Value *VFPAddr = CGF.Builder.CreateGEP(VTable, VTableOffset); + VFPAddr = CGF.Builder.CreateBitCast( + VFPAddr, FTy->getPointerTo()->getPointerTo()); + VirtualFn = CGF.Builder.CreateAlignedLoad( + VFPAddr, CGF.getPointerAlign(), "memptr.virtualfn"); + } } assert(VirtualFn && "Virtual fuction pointer not created!"); assert((!ShouldEmitCFICheck || !ShouldEmitVFEInfo || !ShouldEmitWPDInfo || @@ -1004,11 +1015,16 @@ llvm::Constant *ItaniumCXXABI::BuildMemberPointer(const CXXMethodDecl *MD, llvm::Constant *MemPtr[2]; if (MD->isVirtual()) { uint64_t Index = CGM.getItaniumVTableContext().getMethodVTableIndex(MD); - - const ASTContext &Context = getContext(); - CharUnits PointerWidth = - Context.toCharUnitsFromBits(Context.getTargetInfo().getPointerWidth(0)); - uint64_t VTableOffset = (Index * PointerWidth.getQuantity()); + uint64_t VTableOffset; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + // Multiply by 4-byte relative offsets. + VTableOffset = Index * 4; + } else { + const ASTContext &Context = getContext(); + CharUnits PointerWidth = Context.toCharUnitsFromBits( + Context.getTargetInfo().getPointerWidth(0)); + VTableOffset = Index * PointerWidth.getQuantity(); + } if (UseARMMethodPtrABI) { // ARM C++ ABI 3.2.1: @@ -1422,8 +1438,19 @@ llvm::Value *ItaniumCXXABI::EmitTypeid(CodeGenFunction &CGF, llvm::Value *Value = CGF.GetVTablePtr(ThisPtr, StdTypeInfoPtrTy->getPointerTo(), ClassDecl); - // Load the type info. - Value = CGF.Builder.CreateConstInBoundsGEP1_64(Value, -1ULL); + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + // Load the type info. + Value = CGF.Builder.CreateBitCast(Value, CGM.Int8PtrTy); + Value = CGF.Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::load_relative, {CGM.Int32Ty}), + {Value, llvm::ConstantInt::get(CGM.Int32Ty, -4)}); + + // Setup to dereference again since this is a proxy we accessed. + Value = CGF.Builder.CreateBitCast(Value, StdTypeInfoPtrTy->getPointerTo()); + } else { + // Load the type info. + Value = CGF.Builder.CreateConstInBoundsGEP1_64(Value, -1ULL); + } return CGF.Builder.CreateAlignedLoad(Value, CGF.getPointerAlign()); } @@ -1479,28 +1506,37 @@ llvm::Value *ItaniumCXXABI::EmitDynamicCastToVoid(CodeGenFunction &CGF, Address ThisAddr, QualType SrcRecordTy, QualType DestTy) { - llvm::Type *PtrDiffLTy = - CGF.ConvertType(CGF.getContext().getPointerDiffType()); llvm::Type *DestLTy = CGF.ConvertType(DestTy); - auto *ClassDecl = cast(SrcRecordTy->castAs()->getDecl()); - // Get the vtable pointer. - llvm::Value *VTable = CGF.GetVTablePtr(ThisAddr, PtrDiffLTy->getPointerTo(), - ClassDecl); + llvm::Value *OffsetToTop; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + // Get the vtable pointer. + llvm::Value *VTable = + CGF.GetVTablePtr(ThisAddr, CGM.Int32Ty->getPointerTo(), ClassDecl); + + // Get the offset-to-top from the vtable. + OffsetToTop = + CGF.Builder.CreateConstInBoundsGEP1_32(/*Type=*/nullptr, VTable, -2U); + OffsetToTop = CGF.Builder.CreateAlignedLoad( + OffsetToTop, CharUnits::fromQuantity(4), "offset.to.top"); + } else { + llvm::Type *PtrDiffLTy = + CGF.ConvertType(CGF.getContext().getPointerDiffType()); - // Get the offset-to-top from the vtable. - llvm::Value *OffsetToTop = - CGF.Builder.CreateConstInBoundsGEP1_64(VTable, -2ULL); - OffsetToTop = - CGF.Builder.CreateAlignedLoad(OffsetToTop, CGF.getPointerAlign(), - "offset.to.top"); + // Get the vtable pointer. + llvm::Value *VTable = + CGF.GetVTablePtr(ThisAddr, PtrDiffLTy->getPointerTo(), ClassDecl); + // Get the offset-to-top from the vtable. + OffsetToTop = CGF.Builder.CreateConstInBoundsGEP1_64(VTable, -2ULL); + OffsetToTop = CGF.Builder.CreateAlignedLoad( + OffsetToTop, CGF.getPointerAlign(), "offset.to.top"); + } // Finally, add the offset to the pointer. llvm::Value *Value = ThisAddr.getPointer(); Value = CGF.EmitCastToVoidPtr(Value); Value = CGF.Builder.CreateInBoundsGEP(Value, OffsetToTop); - return CGF.Builder.CreateBitCast(Value, DestLTy); } @@ -1521,17 +1557,22 @@ ItaniumCXXABI::GetVirtualBaseClassOffset(CodeGenFunction &CGF, CharUnits VBaseOffsetOffset = CGM.getItaniumVTableContext().getVirtualBaseOffsetOffset(ClassDecl, BaseClassDecl); - llvm::Value *VBaseOffsetPtr = CGF.Builder.CreateConstGEP1_64(VTablePtr, VBaseOffsetOffset.getQuantity(), "vbase.offset.ptr"); - VBaseOffsetPtr = CGF.Builder.CreateBitCast(VBaseOffsetPtr, - CGM.PtrDiffTy->getPointerTo()); - - llvm::Value *VBaseOffset = - CGF.Builder.CreateAlignedLoad(VBaseOffsetPtr, CGF.getPointerAlign(), - "vbase.offset"); + llvm::Value *VBaseOffset; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + VBaseOffsetPtr = + CGF.Builder.CreateBitCast(VBaseOffsetPtr, CGF.Int32Ty->getPointerTo()); + VBaseOffset = CGF.Builder.CreateAlignedLoad( + VBaseOffsetPtr, CharUnits::fromQuantity(4), "vbase.offset"); + } else { + VBaseOffsetPtr = CGF.Builder.CreateBitCast(VBaseOffsetPtr, + CGM.PtrDiffTy->getPointerTo()); + VBaseOffset = CGF.Builder.CreateAlignedLoad( + VBaseOffsetPtr, CGF.getPointerAlign(), "vbase.offset"); + } return VBaseOffset; } @@ -1679,10 +1720,11 @@ void ItaniumCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT, CGM.GetAddrOfRTTIDescriptor(CGM.getContext().getTagDeclType(RD)); // Create and set the initializer. - ConstantInitBuilder Builder(CGM); - auto Components = Builder.beginStruct(); - CGVT.createVTableInitializer(Components, VTLayout, RTTI); - Components.finishAndSetAsInitializer(VTable); + ConstantInitBuilder builder(CGM); + auto components = builder.beginStruct(); + CGVT.createVTableInitializer(components, VTLayout, RTTI, + llvm::GlobalValue::isLocalLinkage(Linkage)); + components.finishAndSetAsInitializer(VTable); // Set the correct linkage. VTable->setLinkage(Linkage); @@ -1706,6 +1748,9 @@ void ItaniumCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT, if (!VTable->isDeclarationForLinker()) CGM.EmitVTableTypeMetadata(RD, VTable, VTLayout); + + if (VTContext.isRelativeLayout() && !VTable->isDSOLocal()) + CGVT.GenerateRelativeVTableAlias(VTable, VTable->getName()); } bool ItaniumCXXABI::isVirtualOffsetNeededForVTableField( @@ -1795,7 +1840,9 @@ llvm::GlobalVariable *ItaniumCXXABI::getAddrOfVTable(const CXXRecordDecl *RD, // Use pointer alignment for the vtable. Otherwise we would align them based // on the size of the initializer which doesn't make sense as only single // values are read. - unsigned PAlign = CGM.getTarget().getPointerAlign(0); + unsigned PAlign = CGM.getItaniumVTableContext().isRelativeLayout() + ? 32 + : CGM.getTarget().getPointerAlign(0); VTable = CGM.CreateOrReplaceCXXRuntimeVariable( Name, VTableType, llvm::GlobalValue::ExternalLinkage, @@ -1812,9 +1859,9 @@ CGCallee ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF, Address This, llvm::Type *Ty, SourceLocation Loc) { - Ty = Ty->getPointerTo()->getPointerTo(); auto *MethodDecl = cast(GD.getDecl()); - llvm::Value *VTable = CGF.GetVTablePtr(This, Ty, MethodDecl->getParent()); + llvm::Value *VTable = CGF.GetVTablePtr( + This, Ty->getPointerTo()->getPointerTo(), MethodDecl->getParent()); uint64_t VTableIndex = CGM.getItaniumVTableContext().getMethodVTableIndex(GD); llvm::Value *VFunc; @@ -1825,10 +1872,21 @@ CGCallee ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF, } else { CGF.EmitTypeMetadataCodeForVCall(MethodDecl->getParent(), VTable, Loc); - llvm::Value *VFuncPtr = - CGF.Builder.CreateConstInBoundsGEP1_64(VTable, VTableIndex, "vfn"); - auto *VFuncLoad = - CGF.Builder.CreateAlignedLoad(VFuncPtr, CGF.getPointerAlign()); + llvm::Value *VFuncLoad; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + VTable = CGF.Builder.CreateBitCast(VTable, CGM.Int8PtrTy); + llvm::Value *Load = CGF.Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::load_relative, {CGM.Int32Ty}), + {VTable, llvm::ConstantInt::get(CGM.Int32Ty, 4 * VTableIndex)}); + VFuncLoad = CGF.Builder.CreateBitCast(Load, Ty->getPointerTo()); + } else { + VTable = + CGF.Builder.CreateBitCast(VTable, Ty->getPointerTo()->getPointerTo()); + llvm::Value *VTableSlotPtr = + CGF.Builder.CreateConstInBoundsGEP1_64(VTable, VTableIndex, "vfn"); + VFuncLoad = + CGF.Builder.CreateAlignedLoad(VTableSlotPtr, CGF.getPointerAlign()); + } // Add !invariant.load md to virtual function load to indicate that // function didn't change inside vtable. @@ -1837,11 +1895,14 @@ CGCallee ItaniumCXXABI::getVirtualFunctionPointer(CodeGenFunction &CGF, // the same virtual function loads from the same vtable load, which won't // happen without enabled devirtualization with -fstrict-vtable-pointers. if (CGM.getCodeGenOpts().OptimizationLevel > 0 && - CGM.getCodeGenOpts().StrictVTablePointers) - VFuncLoad->setMetadata( - llvm::LLVMContext::MD_invariant_load, - llvm::MDNode::get(CGM.getLLVMContext(), - llvm::ArrayRef())); + CGM.getCodeGenOpts().StrictVTablePointers) { + if (auto *VFuncLoadInstr = dyn_cast(VFuncLoad)) { + VFuncLoadInstr->setMetadata( + llvm::LLVMContext::MD_invariant_load, + llvm::MDNode::get(CGM.getLLVMContext(), + llvm::ArrayRef())); + } + } VFunc = VFuncLoad; } @@ -1958,21 +2019,28 @@ static llvm::Value *performTypeAdjustment(CodeGenFunction &CGF, // Perform the virtual adjustment if we have one. llvm::Value *ResultPtr; if (VirtualAdjustment) { - llvm::Type *PtrDiffTy = - CGF.ConvertType(CGF.getContext().getPointerDiffType()); - Address VTablePtrPtr = CGF.Builder.CreateElementBitCast(V, CGF.Int8PtrTy); llvm::Value *VTablePtr = CGF.Builder.CreateLoad(VTablePtrPtr); + llvm::Value *Offset; llvm::Value *OffsetPtr = CGF.Builder.CreateConstInBoundsGEP1_64(VTablePtr, VirtualAdjustment); + if (CGF.CGM.getItaniumVTableContext().isRelativeLayout()) { + // Load the adjustment offset from the vtable as a 32-bit int. + OffsetPtr = + CGF.Builder.CreateBitCast(OffsetPtr, CGF.Int32Ty->getPointerTo()); + Offset = + CGF.Builder.CreateAlignedLoad(OffsetPtr, CharUnits::fromQuantity(4)); + } else { + llvm::Type *PtrDiffTy = + CGF.ConvertType(CGF.getContext().getPointerDiffType()); - OffsetPtr = CGF.Builder.CreateBitCast(OffsetPtr, PtrDiffTy->getPointerTo()); - - // Load the adjustment offset from the vtable. - llvm::Value *Offset = - CGF.Builder.CreateAlignedLoad(OffsetPtr, CGF.getPointerAlign()); + OffsetPtr = + CGF.Builder.CreateBitCast(OffsetPtr, PtrDiffTy->getPointerTo()); + // Load the adjustment offset from the vtable. + Offset = CGF.Builder.CreateAlignedLoad(OffsetPtr, CGF.getPointerAlign()); + } // Adjust our pointer. ResultPtr = CGF.Builder.CreateInBoundsGEP(V.getPointer(), Offset); } else { @@ -3299,17 +3367,32 @@ void ItaniumRTTIBuilder::BuildVTablePointer(const Type *Ty) { break; } - llvm::Constant *VTable = - CGM.getModule().getOrInsertGlobal(VTableName, CGM.Int8PtrTy); + llvm::Constant *VTable = nullptr; + + // Check if the alias exists. If it doesn't, then get or create the global. + if (CGM.getItaniumVTableContext().isRelativeLayout()) + VTable = CGM.getModule().getNamedAlias(VTableName); + if (!VTable) + VTable = CGM.getModule().getOrInsertGlobal(VTableName, CGM.Int8PtrTy); + CGM.setDSOLocal(cast(VTable->stripPointerCasts())); llvm::Type *PtrDiffTy = - CGM.getTypes().ConvertType(CGM.getContext().getPointerDiffType()); + CGM.getTypes().ConvertType(CGM.getContext().getPointerDiffType()); // The vtable address point is 2. - llvm::Constant *Two = llvm::ConstantInt::get(PtrDiffTy, 2); - VTable = - llvm::ConstantExpr::getInBoundsGetElementPtr(CGM.Int8PtrTy, VTable, Two); + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + // The vtable address point is 8 bytes after its start: + // 4 for the offset to top + 4 for the relative offset to rtti. + llvm::Constant *Eight = llvm::ConstantInt::get(CGM.Int32Ty, 8); + VTable = llvm::ConstantExpr::getBitCast(VTable, CGM.Int8PtrTy); + VTable = + llvm::ConstantExpr::getInBoundsGetElementPtr(CGM.Int8Ty, VTable, Eight); + } else { + llvm::Constant *Two = llvm::ConstantInt::get(PtrDiffTy, 2); + VTable = llvm::ConstantExpr::getInBoundsGetElementPtr(CGM.Int8PtrTy, VTable, + Two); + } VTable = llvm::ConstantExpr::getBitCast(VTable, CGM.Int8PtrTy); Fields.push_back(VTable); diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp index 76881e973f4134..a9d87f135b6525 100644 --- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp +++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp @@ -1688,10 +1688,11 @@ void MicrosoftCXXABI::emitVTableDefinitions(CodeGenVTables &CGVT, [](const VTableComponent &VTC) { return VTC.isRTTIKind(); })) RTTI = getMSCompleteObjectLocator(RD, *Info); - ConstantInitBuilder Builder(CGM); - auto Components = Builder.beginStruct(); - CGVT.createVTableInitializer(Components, VTLayout, RTTI); - Components.finishAndSetAsInitializer(VTable); + ConstantInitBuilder builder(CGM); + auto components = builder.beginStruct(); + CGVT.createVTableInitializer(components, VTLayout, RTTI, + VTable->hasLocalLinkage()); + components.finishAndSetAsInitializer(VTable); emitVTableTypeMetadata(*Info, RD, VTable); } diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 29e92ceb6be145..3aea9164046ca4 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -3410,6 +3410,11 @@ static void ParseLangArgs(LangOptions &Opts, ArgList &Args, InputKind IK, Opts.CompatibilityQualifiedIdBlockParamTypeChecking = Args.hasArg(OPT_fcompatibility_qualified_id_block_param_type_checking); + + Opts.RelativeCXXABIVTables = + Args.hasFlag(OPT_fexperimental_relative_cxx_abi_vtables, + OPT_fno_experimental_relative_cxx_abi_vtables, + /*default=*/false); } static bool isStrictlyPreprocessorAction(frontend::ActionKind Action) { diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/available_externally-vtable.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/available_externally-vtable.cpp new file mode 100644 index 00000000000000..bdd66ac13d5c7e --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/available_externally-vtable.cpp @@ -0,0 +1,23 @@ +// Check that available_externally vtables do not receive aliases. +// We check this specifically under the legacy pass manager because the new pass +// manager seems to remove available_externally vtables from the IR entirely. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables -fno-experimental-new-pass-manager | FileCheck %s + +// The VTable for A is available_externally, meaning it can have a definition in +// IR, but is never emitted in this compilation unit. Because it won't be +// emitted here, we cannot make an alias, but we won't need to in the first +// place. +// CHECK: @_ZTV1A = available_externally unnamed_addr constant { [3 x i32] } +// CHECK-NOT: @_ZTV1A = {{.*}}alias + +class A { +public: + virtual void foo(); +}; +void A_foo(A *a); + +void func() { + A a; + A_foo(&a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/child-inheritted-from-parent-in-comdat.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/child-inheritted-from-parent-in-comdat.cpp new file mode 100644 index 00000000000000..90977986168aa8 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/child-inheritted-from-parent-in-comdat.cpp @@ -0,0 +1,53 @@ +// Cross comdat example +// Parent VTable is in a comdat section. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: $_ZN1B3fooEv.stub = comdat any + +// The inline function is emitted in each module with the same comdat +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZTS1A = comdat any +// CHECK: $_ZTI1A = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any + +// The VTable is emitted everywhere used +// CHECK: $_ZTV1A = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// The VTable for B is emitted here since it has a key function which is defined in this module +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// The VTable for A is emitted here and in a comdat section since it has no key function, and is used in this module when creating an instance of A (in func()). +// CHECK: @_ZTV1A.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1A), align 4 + +// CHECK: @_ZTV1B = unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1B.local +// CHECK: @_ZTV1A = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local + +// CHECK: define void @_ZN1B3fooEv(%class.B* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define hidden void @_ZN1B3fooEv.stub(%class.B* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* {{.*}}%0) unnamed_addr #{{[0-9]+}} comdat + +class A { +public: + inline virtual void foo() {} +}; +class B : public A { +public: + void foo() override; +}; +void A_foo(A *a); + +void B::foo() {} +void func2() { + A a; + A_foo(&a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/child-vtable-in-comdat.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/child-vtable-in-comdat.cpp new file mode 100644 index 00000000000000..b74a8c85445145 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/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 -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK-DAG: $_ZN1B3fooEv.stub = comdat any +// CHECK-DAG: $_ZN1A3fooEv.stub = comdat any + +// A comdat is emitted for B but not A +// CHECK-DAG: $_ZTV1B = comdat any +// CHECK-DAG: $_ZTS1B = comdat any +// CHECK-DAG: $_ZTI1B = comdat any +// CHECK-DAG: $_ZTI1B.rtti_proxy = comdat any +// CHECK-DAG: $_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-DAG: @_ZTV1B.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1B), align 4 + +// The RTTI objects aren’t that important, but it is good to know that they are emitted here since they are used in the vtable for B, and external references are used for RTTI stuff from A. +// CHECK-DAG: @_ZTVN10__cxxabiv120__si_class_type_infoE = external global i8* +// CHECK-DAG: @_ZTS1B = +// CHECK-DAG: @_ZTI1A = +// CHECK-DAG: @_ZTI1B = +// CHECK-DAG: @_ZTI1B.rtti_proxy = hidden unnamed_addr constant { i8*, i8*, i8* }* @_ZTI1B, comdat + +// We will emit a vtable for B here, so it does have an alias, but we will not +// emit one for A. +// CHECK: @_ZTV1B = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1B.local +// CHECK-NOT: @_ZTV1A = {{.*}}alias + +// CHECK: define hidden void @_ZN1B3fooEv.stub(%class.B* {{.*}}%0) unnamed_addr #{{[0-9]+}} comdat + +// CHECK: declare void @_ZN1A3fooEv(%class.A*) unnamed_addr + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3fooEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; +class B : public A { +public: + inline void foo() override {} +}; +void A_foo(A *a); + +// func() is used so that the vtable for B is accessed when creating the instance. +void func() { + B b; + A_foo(&b); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-1.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-1.cpp new file mode 100644 index 00000000000000..97ab7eb3b2a7bd --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-1.cpp @@ -0,0 +1,39 @@ +// Check the vtable layout for classes with key functions defined in different +// translation units. This TU only manifests the vtable for A. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +#include "cross-tu-header.h" + +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZN1A3barEv.stub = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// @_ZTV1A = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1A.local + +// A::foo() is still available for other modules to use since it is not marked with private or internal linkage. +// CHECK: define void @_ZN1A3fooEv(%class.A* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// The proxy that we take a reference to in the vtable has hidden visibility and external linkage so it can be used only by other modules in the same DSO. A::foo() is inlined into this stub since it is defined in the same module. +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// A::bar() is called within the module but not defined, even though the VTable for A is emitted here +// CHECK: declare void @_ZN1A3barEv(%class.A*) unnamed_addr + +// The stub for A::bar() is made private, so it will not appear in the symbol table and is only used in this module. We tail call here because A::bar() is not defined in the same module. +// CHECK: define hidden void @_ZN1A3barEv.stub(%class.A* %0) unnamed_addr {{#[0-9]+}} comdat { +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3barEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +void A::foo() {} +void A_foo(A *a) { a->foo(); } +void A_bar(A *a) { a->bar(); } diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-2.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-2.cpp new file mode 100644 index 00000000000000..46a17b2faf5e46 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-translation-unit-2.cpp @@ -0,0 +1,38 @@ +// Check the vtable layout for classes with key functions defined in different +// translation units. This TU manifests the vtable for B. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +#include "cross-tu-header.h" + +// CHECK: $_ZN1B3fooEv.stub = comdat any +// CHECK: $_ZN1A3barEv.stub = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any + +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local + +// A::bar() is defined outside of the module that defines the vtable for A +// CHECK: define void @_ZN1A3barEv(%class.A* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define void @_ZN1B3fooEv(%class.B* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// The stubs for B::foo() and A::bar() are hidden +// CHECK: define hidden void @_ZN1B3fooEv.stub(%class.B* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define hidden void @_ZN1A3barEv.stub(%class.A* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +void A::bar() {} +void B::foo() {} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/cross-tu-header.h b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-tu-header.h new file mode 100644 index 00000000000000..73b09c055a986b --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/cross-tu-header.h @@ -0,0 +1,10 @@ +class A { +public: + virtual void foo(); + virtual void bar(); +}; + +class B : public A { +public: + void foo() override; +}; diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-inheritance.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-inheritance.cpp new file mode 100644 index 00000000000000..95d80d1040c234 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-inheritance.cpp @@ -0,0 +1,57 @@ +// Diamond inheritance. +// A more complicated multiple inheritance example that includes longer chain of inheritance and a common ancestor. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: %class.B = type { %class.A } +// CHECK: %class.A = type { i32 (...)** } +// CHECK: %class.C = type { %class.A } +// CHECK: %class.D = type { %class.B, %class.C } + +// VTable for B should contain offset to top (0), RTTI pointer, A::foo(), and B::barB(). +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// VTable for C should contain offset to top (0), RTTI pointer, A::foo(), and C::barC(). +// CHECK: @_ZTV1C.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// VTable for D should be similar to the mutiple inheritance example where this +// vtable contains 2 inner vtables: +// - 1st table containing D::foo(), B::barB(), and D::baz(). +// - 2nd table containing a thunk to D::foo() and C::barC(). +// CHECK: @_ZTV1D.local = private unnamed_addr constant { [5 x i32], [4 x i32] } { [5 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.D*)* @_ZN1D3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.D*)* @_ZN1D3bazEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 2) to i64)) to i32)], [4 x i32] [i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.D*)* @_ZThn8_N1D3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 2) to i64)) to i32)] }, align 4 + +// @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local +// @_ZTV1C = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1C.local +// @_ZTV1D = unnamed_addr alias { [5 x i32], [4 x i32] }, { [5 x i32], [4 x i32] }* @_ZTV1D.local + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + virtual void barB(); +}; + +class C : public A { + virtual void barC(); +}; + +// Should be a struct with 2 arrays from 2 parents. +// The 1st contains D::foo(), B::barB(), and D::baz(). +// The 2nd contains C::barC(), and a thunk that points to D::foo(). +class D : public B, C { +public: + virtual void baz(); + void foo() override; +}; + +void B::barB() {} +void C::barC() {} +void D::foo() {} +void D::baz() {} + +void D_foo(D *d) { + d->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-virtual-inheritance.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-virtual-inheritance.cpp new file mode 100644 index 00000000000000..b4f7bc5ffe0b58 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/diamond-virtual-inheritance.cpp @@ -0,0 +1,96 @@ +// Diamond virtual inheritance. +// This should cover virtual inheritance, construction vtables, and VTTs. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: %class.B = type { i32 (...)**, %class.A.base } +// CHECK: %class.A.base = type <{ i32 (...)**, i32 }> +// CHECK: %class.C = type { i32 (...)**, %class.A.base } +// CHECK: %class.D = type { %class.B.base, %class.C.base, %class.A.base } +// CHECK: %class.B.base = type { i32 (...)** } +// CHECK: %class.C.base = type { i32 (...)** } + +// Class A contains a vtable ptr, then int, then padding +// CHECK: %class.A = type <{ i32 (...)**, i32, [4 x i8] }> + +// VTable for B. Contains an extra field at the start for the virtual-base offset. +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32], [4 x i32] } { [4 x i32] [i32 8, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// VTT for B +// CHECK: @_ZTT1B = unnamed_addr constant [2 x i8*] [i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// VTable for C +// CHECK: @_ZTV1C.local = private unnamed_addr constant { [4 x i32], [4 x i32] } { [4 x i32] [i32 8, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// VTT for C +// CHECK: @_ZTT1C = unnamed_addr constant [2 x i8*] [i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// VTable for D +// CHECK: @_ZTV1D.local = private unnamed_addr constant { [5 x i32], [4 x i32], [4 x i32] } { [5 x i32] [i32 16, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.D*)* @_ZN1D3bazEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 8, i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 1, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -16, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1D.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 2, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, i32 2, i32 3) to i64)) to i32)] }, align 4 + +// VTT for D +// CHECK: @_ZTT1D = unnamed_addr constant [7 x i8*] [i8* bitcast (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, inrange i32 1, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, inrange i32 0, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, inrange i32 1, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, inrange i32 2, i32 3) to i8*), i8* bitcast (i32* getelementptr inbounds ({ [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local, i32 0, inrange i32 1, i32 3) to i8*)], align 8 + +// Construction vtable for B-in-D +// CHECK: @_ZTC1D0_1B.local = private unnamed_addr constant { [4 x i32], [4 x i32] } { [4 x i32] [i32 16, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B4barBEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -16, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// Construction vtable for C-in-D +// CHECK: @_ZTC1D8_1C.local = private unnamed_addr constant { [4 x i32], [4 x i32] } { [4 x i32] [i32 8, i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, i32 0, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C4barCEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, i32 0, i32 3) to i64)) to i32)], [4 x i32] [i32 0, i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, i32 1, i32 3) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local, i32 0, i32 1, i32 3) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1B.local +// CHECK: @_ZTV1C = unnamed_addr alias { [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTV1C.local +// CHECK: @_ZTC1D0_1B = unnamed_addr alias { [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D0_1B.local +// CHECK: @_ZTC1D8_1C = unnamed_addr alias { [4 x i32], [4 x i32] }, { [4 x i32], [4 x i32] }* @_ZTC1D8_1C.local +// CHECK: @_ZTV1D = unnamed_addr alias { [5 x i32], [4 x i32], [4 x i32] }, { [5 x i32], [4 x i32], [4 x i32] }* @_ZTV1D.local + +// CHECK: define void @_Z5D_fooP1D(%class.D* %d) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[d:%[0-9]+]] = bitcast %class.D* %d to i8** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[d]], align 8 + +// This normally would've been -24 (8 bytes for the virtual call/base offset, 8 +// bytes for the offset to top, and 8 bytes for the RTTI pointer), but since all +// components in the vtable are halved to 4 bytes, the offset is halved to -12. +// CHECK-NEXT: [[vbase_offset_ptr:%[a-z0-9.]+]] = getelementptr i8, i8* [[vtable]], i64 -12 + +// CHECK-NEXT: [[vbase_offset_ptr2:%[a-z0-9.]+]] = bitcast i8* [[vbase_offset_ptr]] to i32* +// CHECK-NEXT: [[vbase_offset:%[a-z0-9.]+]] = load i32, i32* [[vbase_offset_ptr2]], align 4 +// CHECK-NEXT: [[d:%[0-9]+]] = bitcast %class.D* %d to i8* +// CHECK-NEXT: [[vbase_offset2:%.+]] = sext i32 [[vbase_offset]] to i64 +// CHECK-NEXT: [[add_ptr:%[a-z0-9.]+]] = getelementptr inbounds i8, i8* [[d]], i64 [[vbase_offset2]] +// CHECK-NEXT: [[a:%[0-9]+]] = bitcast i8* [[add_ptr]] to %class.A* +// CHECK-NEXT: [[a_i8_ptr:%[0-9]+]] = bitcast i8* [[add_ptr]] to i8** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[a_i8_ptr]], align 8 +// CHECK-NEXT: [[ptr:%[0-9]+]] = call i8* @llvm.load.relative.i32(i8* [[vtable]], i32 0) +// CHECK-NEXT: [[method:%[0-9]+]] = bitcast i8* [[ptr]] to void (%class.A*)* +// CHECK-NEXT: call void [[method]](%class.A* [[a]]) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); + int a; +}; + +class B : public virtual A { +public: + virtual void barB(); +}; + +class C : public virtual A { + virtual void barC(); +}; + +class D : public B, C { +public: + virtual void baz(); +}; + +void B::barB() {} +void C::barC() {} +void D::baz() {} + +void D_foo(D *d) { + d->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/dynamic-cast.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/dynamic-cast.cpp new file mode 100644 index 00000000000000..56b56a1b939841 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/dynamic-cast.cpp @@ -0,0 +1,78 @@ +// dynamic_cast +// Ensure that dynamic casting works normally + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O3 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define %class.A* @_Z6upcastP1B(%class.B* readnone %b) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[a:%[0-9]+]] = getelementptr %class.B, %class.B* %b, i64 0, i32 0 +// CHECK-NEXT: ret %class.A* [[a]] +// CHECK-NEXT: } + +// CHECK: define %class.B* @_Z8downcastP1A(%class.A* readonly %a) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[isnull:%[0-9]+]] = icmp eq %class.A* %a, null +// CHECK-NEXT: br i1 [[isnull]], label %[[dynamic_cast_end:[a-z0-9._]+]], label %[[dynamic_cast_notnull:[a-z0-9._]+]] +// CHECK: [[dynamic_cast_notnull]]: +// CHECK-NEXT: [[a:%[0-9]+]] = bitcast %class.A* %a to i8* +// CHECK-NEXT: [[as_b:%[0-9]+]] = tail call i8* @__dynamic_cast(i8* nonnull [[a]], i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*), i8* bitcast ({ i8*, i8*, i8* }* @_ZTI1B to i8*), i64 0) +// CHECK-NEXT: [[b:%[0-9]+]] = bitcast i8* [[as_b]] to %class.B* +// CHECK-NEXT: br label %[[dynamic_cast_end]] +// CHECK: [[dynamic_cast_end]]: +// CHECK-NEXT: [[res:%[0-9]+]] = phi %class.B* [ [[b]], %[[dynamic_cast_notnull]] ], [ null, %entry ] +// CHECK-NEXT: ret %class.B* [[res]] +// CHECK-NEXT: } + +// CHECK: declare i8* @__dynamic_cast(i8*, i8*, i8*, i64) local_unnamed_addr + +// CHECK: define %class.B* @_Z8selfcastP1B(%class.B* readnone returned %b) local_unnamed_addr +// CHECK-NEXT: entry +// CHECK-NEXT: ret %class.B* %b +// CHECK-NEXT: } + +// CHECK: define i8* @_Z9void_castP1B(%class.B* readonly %b) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[isnull:%[0-9]+]] = icmp eq %class.B* %b, null +// CHECK-NEXT: br i1 [[isnull]], label %[[dynamic_cast_end:[a-z0-9._]+]], label %[[dynamic_cast_notnull:[a-z0-9._]+]] +// CHECK: [[dynamic_cast_notnull]]: +// CHECK-NEXT: [[b2:%[0-9]+]] = bitcast %class.B* %b to i32** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i32*, i32** [[b2]], align 8 +// CHECK-NEXT: [[offset_ptr:%.+]] = getelementptr inbounds i32, i32* [[vtable]], i64 -2 +// CHECK-NEXT: [[offset_to_top:%.+]] = load i32, i32* [[offset_ptr]], align 4 +// CHECK-NEXT: [[b:%[0-9]+]] = bitcast %class.B* %b to i8* +// CHECK-NEXT: [[offset_to_top2:%.+]] = sext i32 [[offset_to_top]] to i64 +// CHECK-NEXT: [[casted:%.+]] = getelementptr inbounds i8, i8* [[b]], i64 [[offset_to_top2]] +// CHECK-NEXT: br label %[[dynamic_cast_end]] +// CHECK: [[dynamic_cast_end]]: +// CHECK-NEXT: [[res:%[0-9]+]] = phi i8* [ [[casted]], %[[dynamic_cast_notnull]] ], [ null, %entry ] +// CHECK-NEXT: ret i8* [[res]] +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + void foo() override; +}; + +void A::foo() {} +void B::foo() {} + +A *upcast(B *b) { + return dynamic_cast(b); +} + +B *downcast(A *a) { + return dynamic_cast(a); +} + +B *selfcast(B *b) { + return dynamic_cast(b); +} + +void *void_cast(B *b) { + return dynamic_cast(b); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/inheritted-virtual-function.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/inheritted-virtual-function.cpp new file mode 100644 index 00000000000000..3eee8f0d07bb49 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/inheritted-virtual-function.cpp @@ -0,0 +1,29 @@ +// Check the layout of the vtable for a child class that inherits a virtual +// function but does not override it. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +class A { +public: + virtual void foo(); +}; + +// The VTable for B should look similar to the vtable for A but the component for foo() should point to A::foo() and the component for bar() should point to B::bar(). +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local + +class B : public A { +public: + virtual void bar(); +}; + +void A::foo() {} +void B::bar() {} + +void A_foo(A *a) { + a->foo(); +} + +void B_foo(B *b) { + b->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/inline-virtual-function.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/inline-virtual-function.cpp new file mode 100644 index 00000000000000..741fbf4f0bba90 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/inline-virtual-function.cpp @@ -0,0 +1,23 @@ +// The VTable is not in a comdat but the inline methods are. +// This doesn’t affect the vtable or the stubs we emit. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZN1A3barEv.stub = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// The vtable has a key function (A::foo()) so it does not have a comdat +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTV1A = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1A.local + +// We do not declare the stub with linkonce_odr so it can be emitted as .globl. +// CHECK: define hidden void @_ZN1A3barEv.stub(%class.A* {{.*}}%0) unnamed_addr #{{[0-9]+}} comdat + +class A { +public: + virtual void foo(); // Key func + inline virtual void bar() {} +}; + +void A::foo() {} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/inlined-key-function.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/inlined-key-function.cpp new file mode 100644 index 00000000000000..d2c69e78a62ea8 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/inlined-key-function.cpp @@ -0,0 +1,29 @@ +// Inline comdat method definition example. +// The VTable is in a comdat and defined anywhere the inline definition is. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZTV1A = comdat any +// CHECK: $_ZTS1A = comdat any +// CHECK: $_ZTI1A = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// The VTable is linkonce_odr and in a comdat here bc it’s key function is inline defined. +// CHECK: @_ZTV1A.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1A), align 4 +// CHECK: @_ZTV1A = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* {{.*}}%0) unnamed_addr #{{[0-9]+}} comdat + +class A { +public: + virtual void foo(); +}; +void A_foo(A *a); + +inline void A::foo() {} + +void func() { + A a; + A_foo(&a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/member-function-pointer.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/member-function-pointer.cpp new file mode 100644 index 00000000000000..396dd15c482bd6 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/member-function-pointer.cpp @@ -0,0 +1,47 @@ +// Member pointer to virtual function. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O3 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define void @_Z4funcP1AMS_FvvE(%class.A* %a, [2 x i64] %fn.coerce) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[fn_ptr:%.+]] = extractvalue [2 x i64] %fn.coerce, 0 +// CHECK-NEXT: [[adjust:%.+]] = extractvalue [2 x i64] %fn.coerce, 1 +// CHECK-NEXT: [[this:%.+]] = bitcast %class.A* %a to i8* +// CHECK-NEXT: [[this_adj:%.+]] = getelementptr inbounds i8, i8* [[this]], i64 [[adjust]] +// CHECK-NEXT: [[virtbit:%.+]] = and i64 [[fn_ptr]], 1 +// CHECK-NEXT: [[isvirt:%.+]] = icmp eq i64 [[virtbit]], 0 +// CHECK-NEXT: br i1 [[isvirt]], label %[[nonvirt:.+]], label %[[virt:.+]] +// CHECK: [[virt]]: + +// The loading of the virtual function here should be replaced with a llvm.load.relative() call. +// CHECK-NEXT: [[this:%.+]] = bitcast i8* [[this_adj]] to i8** +// CHECK-NEXT: [[vtable:%.+]] = load i8*, i8** [[this]], align 8 +// CHECK-NEXT: [[offset:%.+]] = add i64 [[fn_ptr]], -1 +// CHECK-NEXT: [[ptr:%.+]] = tail call i8* @llvm.load.relative.i64(i8* [[vtable]], i64 [[offset]]) +// CHECK-NEXT: [[method:%.+]] = bitcast i8* [[ptr]] to void (%class.A*)* +// CHECK-NEXT: br label %[[memptr_end:.+]] +// CHECK: [[nonvirt]]: +// CHECK-NEXT: [[method2:%.+]] = inttoptr i64 [[fn_ptr]] to void (%class.A*)* +// CHECK-NEXT: br label %[[memptr_end]] +// CHECK: [[memptr_end]]: +// CHECK-NEXT: [[method3:%.+]] = phi void (%class.A*)* [ [[method]], %[[virt]] ], [ [[method2]], %[[nonvirt]] ] +// CHECK-NEXT: [[a:%.+]] = bitcast i8* [[this_adj]] to %class.A* +// CHECK-NEXT: tail call void [[method3]](%class.A* [[a]]) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + void foo() override; +}; + +typedef void (A::*A_foo)(); + +void func(A *a, A_foo fn) { + (a->*fn)(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/multiple-inheritance.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/multiple-inheritance.cpp new file mode 100644 index 00000000000000..76125c299686ba --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/multiple-inheritance.cpp @@ -0,0 +1,55 @@ +// Multiple inheritance. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: %class.C = type { %class.A, %class.B } +// CHECK: %class.A = type { i32 (...)** } +// CHECK: %class.B = type { i32 (...)** } + +// VTable for C contains 2 sub-vtables (represented as 2 structs). The first contains the components for B and the second contains the components for C. The RTTI ptr in both arrays still point to the RTTI struct for C. +// The component for bar() instead points to a thunk which redirects to C::bar() which overrides B::bar(). +// Now that we have a class with 2 parents, the offset to top in the second array is non-zero. +// CHECK: @_ZTV1C.local = private unnamed_addr constant { [4 x i32], [3 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZN1C3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32)], [3 x i32] [i32 -8, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i32, i32, i8*, i64, i8*, i64 }** @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 1, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.C*)* @_ZThn8_N1C3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local, i32 0, i32 1, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1C = unnamed_addr alias { [4 x i32], [3 x i32] }, { [4 x i32], [3 x i32] }* @_ZTV1C.local + +// CHECK: define void @_Z8C_foobarP1C(%class.C* %c) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[c:%[0-9]+]] = bitcast %class.C* %c to i8** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[c]], align 8 + +// Offset 0 to get first method +// CHECK-NEXT: [[ptr1:%[0-9]+]] = call i8* @llvm.load.relative.i32(i8* [[vtable]], i32 0) +// CHECK-NEXT: [[method1:%[0-9]+]] = bitcast i8* [[ptr1]] to void (%class.C*)* +// CHECK-NEXT: call void [[method1]](%class.C* %c) +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[c]], align 8 + +// Offset by 4 to get the next bar() +// CHECK-NEXT: [[ptr2:%[0-9]+]] = call i8* @llvm.load.relative.i32(i8* [[vtable]], i32 4) +// CHECK-NEXT: [[method2:%[0-9]+]] = bitcast i8* [[ptr2]] to void (%class.C*)* +// CHECK-NEXT: call void [[method2]](%class.C* %c) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +class B { + virtual void bar(); +}; + +class C : public A, public B { +public: + void foo() override; + void bar() override; +}; + +void C::foo() {} +void C::bar() {} + +void C_foobar(C *c) { + c->foo(); + c->bar(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/no-alias-when-dso-local.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/no-alias-when-dso-local.cpp new file mode 100644 index 00000000000000..bbfe9ad6820719 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/no-alias-when-dso-local.cpp @@ -0,0 +1,16 @@ +// Check that no alias is emitted when the vtable is already dso_local. This can +// happen if the class is hidden. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s --check-prefix=DEFAULT-VIS +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables -fvisibility hidden | FileCheck %s --check-prefix=HIDDEN-VIS + +// DEFAULT-VIS: @_ZTV1A.local = private unnamed_addr constant +// DEFAULT-VIS: @_ZTV1A = unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local +// HIDDEN-VIS-NOT: @_ZTV1A.local +// HIDDEN-VIS: @_ZTV1A = hidden unnamed_addr constant +class A { +public: + virtual void func(); +}; + +void A::func() {} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/no-stub-when-dso-local.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/no-stub-when-dso-local.cpp new file mode 100644 index 00000000000000..bb4b6ae94a0350 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/no-stub-when-dso-local.cpp @@ -0,0 +1,49 @@ +// Check that we do not emit a stub for a virtual function if it is already +// known to be in the same linkage unit as the vtable. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s --check-prefixes=CHECK,NO-OPT +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables -O3 | FileCheck %s --check-prefixes=CHECK,OPT + +// The vtable offset is relative to _ZN1A3fooEv instead of a stub. We can do +// this since hidden functions are implicitly dso_local. +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [5 x i32] } { [5 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (%class.A* (%class.A*)* @_ZN1AD1Ev.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1AD0Ev.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// Despite the complete object destructor being hidden, we should still emit a +// stub for it because it's possible, when optimizations are enabled, for the +// dtor to be "devirtualized" into the destructor for a parent class if the +// child class doesn't implement its own dtor. For complete destructors, we +// always emit and use a stub. +// @_ZTV1B = hidden unnamed_addr constant { [5 x i32] } { [5 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1B, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1B, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (%class.B* (%class.B*)* @_ZN1BD1Ev.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1B, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1BD0Ev to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [5 x i32] }, { [5 x i32] }* @_ZTV1B, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1A = unnamed_addr alias { [5 x i32] }, { [5 x i32] }* @_ZTV1A.local + +// CHECK: @_ZN1A3fooEv +// CHECK-NOT: @_ZN1A3fooEv.stub + +// The complete object destructor is hidden. +// NO-OPT: define linkonce_odr hidden %class.B* @_ZN1BD1Ev +// OPT-NOT: @_ZN1BD1Ev +// CHECK: @_ZN1BD1Ev.stub + +#define HIDDEN __attribute__((visibility("hidden"))) + +class A { +public: + HIDDEN virtual void foo(); + virtual ~A(); +}; + +class HIDDEN B : public A { +public: + virtual void foo(); +}; + +void A::foo() {} + +void A_foo(A *a) { + a->foo(); +} + +void B::foo() { + A::foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/override-pure-virtual-method.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/override-pure-virtual-method.cpp new file mode 100644 index 00000000000000..1388a5a0b99043 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/override-pure-virtual-method.cpp @@ -0,0 +1,34 @@ +// Override pure virtual function. +// We instead emit zero for the pure virtual function component. See PR43094 for +// details. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 0, i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1A = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1A.local +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local + +// CHECK-NOT: declare void @__cxa_pure_virtual() unnamed_addr + +class A { +public: + virtual void foo() = 0; + virtual void bar(); +}; + +class B : public A { +public: + void foo() override; + void bar() override; +}; + +void A::bar() {} +void B::foo() {} +void B::bar() {} + +void A_foo(A *a) { + a->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/overriden-virtual-function.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/overriden-virtual-function.cpp new file mode 100644 index 00000000000000..e3b3bb62f27bd9 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/overriden-virtual-function.cpp @@ -0,0 +1,30 @@ +// Check the layout of the vtable for a child class that inherits a virtual +// function but does override it. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: @_ZTV1B.local = private unnamed_addr constant { [4 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3barEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [4 x i32] }, { [4 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 + +// CHECK: @_ZTV1B = unnamed_addr alias { [4 x i32] }, { [4 x i32] }* @_ZTV1B.local + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + void foo() override; + virtual void bar(); +}; + +void A::foo() {} +void B::foo() {} + +void A_foo(A *a) { + a->foo(); +} + +void B_foo(B *b) { + b->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/parent-and-child-in-comdats.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/parent-and-child-in-comdats.cpp new file mode 100644 index 00000000000000..70a65825a5dbff --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/parent-and-child-in-comdats.cpp @@ -0,0 +1,62 @@ +// Cross comdat example +// Both the parent and child VTablea are in their own comdat sections. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// Comdats are emitted for both A and B in this module and for their respective implementations of foo(). +// CHECK: $_ZN1A3fooEv = comdat any +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZN1B3fooEv = comdat any +// CHECK: $_ZN1B3fooEv.stub = comdat any +// CHECK: $_ZTV1A = comdat any +// CHECK: $_ZTS1A = comdat any +// CHECK: $_ZTI1A = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any +// CHECK: $_ZTV1B = comdat any +// CHECK: $_ZTS1B = comdat any +// CHECK: $_ZTI1B = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any + +// Both the vtables for A and B are emitted and in their own comdats. +// CHECK: @_ZTV1A.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1A), align 4 +// CHECK: @_ZTV1B.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8*, i8* }** @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.B*)* @_ZN1B3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1B), align 4 + +// CHECK: @_ZTV1A = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local +// CHECK: @_ZTV1B = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1B.local + +// CHECK: declare void @_Z5A_fooP1A(%class.A*) + +// The stubs and implementations for foo() are in their own comdat sections. +// CHECK: define linkonce_odr void @_ZN1A3fooEv(%class.A* %this) unnamed_addr #{{[0-9]+}} comdat + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3fooEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// CHECK: define linkonce_odr void @_ZN1B3fooEv(%class.B* %this) unnamed_addr #{{[0-9]+}} comdat + +// CHECK: define hidden void @_ZN1B3fooEv.stub(%class.B* %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1B3fooEv(%class.B* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + inline virtual void foo() {} +}; +class B : public A { +public: + inline void foo() override {} +}; +void A_foo(A *a); + +// func() is used so that the vtable for B is accessed when creating the instance. +void func() { + A a; + B b; + A_foo(&a); + A_foo(&b); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/parent-vtable-in-comdat.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/parent-vtable-in-comdat.cpp new file mode 100644 index 00000000000000..d22cf802689ffb --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/parent-vtable-in-comdat.cpp @@ -0,0 +1,48 @@ +// Cross comdat example +// Parent VTable is in a comdat section. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// A::foo() has a comdat since it is an inline function +// CHECK: $_ZN1A3fooEv = comdat any +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZTV1A = comdat any +// CHECK: $_ZTS1A = comdat any + +// The VTable for A has its own comdat section bc it has no key function +// CHECK: $_ZTI1A = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// The VTable for A is emitted here and in a comdat section since it has no key function, and is used in this module when creating an instance of A. +// CHECK: @_ZTV1A.local = linkonce_odr hidden unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, comdat($_ZTV1A), align 4 +// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +// CHECK: @_ZTS1A = linkonce_odr constant [3 x i8] c"1A\00", comdat, align 1 +// CHECK: @_ZTI1A = linkonce_odr constant { i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv117__class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i32 0, i32 0) }, comdat, align 8 +// CHECK: @_ZTI1A.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI1A, comdat +// CHECK: @_ZTV1A = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local + +// CHECK: define linkonce_odr void @_ZN1A3fooEv(%class.A* %this) unnamed_addr #{{[0-9]+}} comdat + +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* %0) unnamed_addr #{{[0-9]+}} comdat { +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN1A3fooEv(%class.A* %0) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + inline virtual void foo() {} +}; +class B : public A { +public: + void foo() override; +}; +void A_foo(A *a); + +void A_foo(A *a) { a->foo(); } + +// func() is only used to emit a vtable. +void func() { + A a; + A_foo(&a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/pass-byval-attributes.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/pass-byval-attributes.cpp new file mode 100644 index 00000000000000..43bd234e5f8301 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/pass-byval-attributes.cpp @@ -0,0 +1,37 @@ +// ByVal attributes should propogate through to produce proper assembly and +// avoid "unpacking" structs within the stubs on x86_64. + +// RUN: %clang_cc1 %s -triple=x86_64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +struct LargeStruct { + char x[24]; + virtual ~LargeStruct() {} +}; + +struct fidl_string { + unsigned long long size; + char *data; +}; +static_assert(sizeof(fidl_string) == 16, ""); + +class Base { +public: + virtual void func(LargeStruct, fidl_string, LargeStruct, fidl_string) = 0; +}; + +class Derived : public Base { +public: + void func(LargeStruct, fidl_string, LargeStruct, fidl_string) override; +}; + +// The original function takes a byval pointer. +// CHECK: define void @_ZN7Derived4funcE11LargeStruct11fidl_stringS0_S1_(%class.Derived* %this, %struct.LargeStruct* %ls, i64 %sv1.coerce0, i8* %sv1.coerce1, %struct.LargeStruct* %ls2, %struct.fidl_string* byval(%struct.fidl_string) align 8 %sv2) unnamed_addr + +// So the stub should take and pass one also. +// CHECK: define hidden void @_ZN7Derived4funcE11LargeStruct11fidl_stringS0_S1_.stub(%class.Derived* %0, %struct.LargeStruct* %1, i64 %2, i8* %3, %struct.LargeStruct* %4, %struct.fidl_string* byval(%struct.fidl_string) align 8 %5) unnamed_addr {{#[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: tail call void @_ZN7Derived4funcE11LargeStruct11fidl_stringS0_S1_(%class.Derived* %0, %struct.LargeStruct* %1, i64 %2, i8* %3, %struct.LargeStruct* %4, %struct.fidl_string* byval(%struct.fidl_string) align 8 %5) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +void Derived::func(LargeStruct ls, fidl_string sv1, LargeStruct ls2, fidl_string sv2) {} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/relative-vtables-flag.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/relative-vtables-flag.cpp new file mode 100644 index 00000000000000..01cef5055dd54e --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/relative-vtables-flag.cpp @@ -0,0 +1,24 @@ +// Check the layout of the vtable for a normal class. +// The Fuchsia relative vtables ABI will be hidden behind a flag for now as part +// of a soft incremental rollout. This ABI should only be used if the flag for +// it is passed on Fuchsia. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck --check-prefix=RELATIVE-ABI %s +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fno-experimental-relative-c++-abi-vtables | FileCheck --check-prefix=DEFAULT-ABI %s +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm | FileCheck --check-prefix=DEFAULT-ABI %s + +// VTable contains offsets and references to the hidden symbols +// RELATIVE-ABI: @_ZTV1A.local = private unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// RELATIVE-ABI: @_ZTV1A = unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local +// DEFAULT-ABI: @_ZTV1A = unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*), i8* bitcast (void (%class.A*)* @_ZN1A3fooEv to i8*)] }, align 8 + +class A { +public: + virtual void foo(); +}; + +void A::foo() {} + +void A_foo(A *a) { + a->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/simple-vtable-definition.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/simple-vtable-definition.cpp new file mode 100644 index 00000000000000..99e63313ac38e6 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/simple-vtable-definition.cpp @@ -0,0 +1,43 @@ +// Check the layout of the vtable for a normal class. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// We should be emitting comdats for each of the virtual function stubs and RTTI proxies +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any + +// VTable contains offsets and references to the hidden symbols +// The vtable definition itself is private so we can take relative references to +// it. The vtable symbol will be exposed through a public alias. +// CHECK: @_ZTV1A.local = private unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint ({ i8*, i8* }** @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (void (%class.A*)* @_ZN1A3fooEv.stub to i64), i64 ptrtoint (i32* getelementptr inbounds ({ [3 x i32] }, { [3 x i32] }* @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4 +// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +// CHECK: @_ZTS1A = constant [3 x i8] c"1A\00", align 1 +// CHECK: @_ZTI1A = constant { i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv117__class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i32 0, i32 0) }, align 8 + +// The stub should be in a comdat +// CHECK: @_ZTI1A.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI1A, comdat + +// The vtable symbol is exposed through an alias. +// @_ZTV1A = dso_local unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1A.local + +// CHECK: define void @_ZN1A3fooEv(%class.A* nocapture %this) unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +// This function should be in a comdat +// CHECK: define hidden void @_ZN1A3fooEv.stub(%class.A* nocapture %0) unnamed_addr #{{[0-9]+}} comdat +// CHECK-NEXT: entry: +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +void A::foo() {} + +void A_foo(A *a) { + a->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/stub-linkages.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/stub-linkages.cpp new file mode 100644 index 00000000000000..53360091bf5577 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/stub-linkages.cpp @@ -0,0 +1,51 @@ +// If the linkage of the class is internal, then the stubs and proxies should +// also be internally linked. + +// RUN: %clang_cc1 %s -triple=x86_64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// External linkage. +// CHECK: @_ZTI8External.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI8External, comdat + +class External { +public: + virtual void func(); +}; + +void External::func() {} + +// Internal linkage. +// CHECK: @_ZTIN12_GLOBAL__N_18InternalE.rtti_proxy = internal unnamed_addr constant { i8*, i8* }* @_ZTIN12_GLOBAL__N_18InternalE +namespace { + +class Internal { +public: + virtual void func(); +}; + +void Internal::func() {} + +} // namespace + +// This gets the same treatment as an externally available vtable. +// CHECK: @_ZTI11LinkOnceODR.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI11LinkOnceODR, comdat +class LinkOnceODR { +public: + virtual void func() {} // A method defined in the class definition results in this linkage for the vtable. +}; + +// Force an emission of a vtable for Internal by using it here. +void manifest_internal() { + Internal internal; + (void)internal; + LinkOnceODR linkonceodr; + (void)linkonceodr; +} + +// Aliases are typically emitted after the vtable definitions but before the +// function definitions. +// CHECK: @_ZTV8External = unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV8External.local +// CHECK: @_ZTV11LinkOnceODR = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV11LinkOnceODR.local + +// CHECK: define void @_ZN8External4funcEv +// CHECK: define internal void @_ZN12_GLOBAL__N_18Internal4funcEv.stub +// CHECK: define hidden void @_ZN11LinkOnceODR4funcEv.stub diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/thunk-mangling.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/thunk-mangling.cpp new file mode 100644 index 00000000000000..fb1da64e93f433 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/thunk-mangling.cpp @@ -0,0 +1,31 @@ +// Check that virtual thunks are unaffected by the relative ABI. +// The offset of thunks is mangled into the symbol name, which could result in +// linking errors for binaries that want to look for symbols in SOs made with +// this ABI. +// Running that linked binary still won't work since we're using conflicting +// ABIs, but we should still be able to link. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O1 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// This would be normally n24 (3 ptr widths) but is 12 since the vtable is +// entierely made of i32s now. +// CHECK: _ZTv0_n12_N7Derived1fEi + +class Base { +public: + virtual int f(int x); + +private: + long x; +}; + +class Derived : public virtual Base { +public: + virtual int f(int x); + +private: + long y; +}; + +int Base::f(int x) { return x + 1; } +int Derived::f(int x) { return x + 2; } diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/type-info.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/type-info.cpp new file mode 100644 index 00000000000000..86b73193c08cc9 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/type-info.cpp @@ -0,0 +1,77 @@ +// Check typeid() + type_info + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O3 -S -o - -emit-llvm -fcxx-exceptions -fexceptions -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: %class.A = type { i32 (...)** } +// CHECK: %class.B = type { %class.A } +// CHECK: %"class.std::type_info" = type { i32 (...)**, i8* } + +// CHECK: $_ZN1A3fooEv.stub = comdat any +// CHECK: $_ZN1B3fooEv.stub = comdat any +// CHECK: $_ZTI1A.rtti_proxy = comdat any +// CHECK: $_ZTI1B.rtti_proxy = comdat any + +// CHECK: @_ZTVN10__cxxabiv117__class_type_infoE = external global i8* +// CHECK: @_ZTS1A = constant [3 x i8] c"1A\00", align 1 +// CHECK: @_ZTI1A = constant { i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv117__class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i32 0, i32 0) }, align 8 +// CHECK: @_ZTVN10__cxxabiv120__si_class_type_infoE = external global i8* +// CHECK: @_ZTS1B = constant [3 x i8] c"1B\00", align 1 +// CHECK: @_ZTI1B = constant { i8*, i8*, i8* } { i8* getelementptr inbounds (i8, i8* bitcast (i8** @_ZTVN10__cxxabiv120__si_class_type_infoE to i8*), i32 8), i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1B, i32 0, i32 0), i8* bitcast ({ i8*, i8* }* @_ZTI1A to i8*) }, align 8 +// CHECK: @_ZTI1A.rtti_proxy = hidden unnamed_addr constant { i8*, i8* }* @_ZTI1A, comdat +// CHECK: @_ZTI1B.rtti_proxy = hidden unnamed_addr constant { i8*, i8*, i8* }* @_ZTI1B, comdat + +// CHECK: define {{.*}}%"class.std::type_info"* @_Z11getTypeInfov() local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret %"class.std::type_info"* bitcast ({ i8*, i8* }* @_ZTI1A to %"class.std::type_info"*) +// CHECK-NEXT: } + +// CHECK: define i8* @_Z7getNamev() local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: ret i8* getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1A, i64 0, i64 0) +// CHECK-NEXT: } + +// CHECK: define i1 @_Z5equalP1A(%class.A* readonly %a) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[isnull:%[0-9]+]] = icmp eq %class.A* %a, null +// CHECK-NEXT: br i1 [[isnull]], label %[[bad_typeid:[a-z0-9._]+]], label %[[end:[a-z0-9.+]+]] +// CHECK: [[bad_typeid]]: +// CHECK-NEXT: tail call void @__cxa_bad_typeid() +// CHECK-NEXT: unreachable +// CHECK: [[end]]: +// CHECK-NEXT: [[type_info_ptr3:%[0-9]+]] = bitcast %class.A* %a to i8** +// CHECK-NEXT: [[vtable:%[a-z0-9]+]] = load i8*, i8** [[type_info_ptr3]] +// CHECK-NEXT: [[type_info_ptr:%[0-9]+]] = tail call i8* @llvm.load.relative.i32(i8* [[vtable]], i32 -4) +// CHECK-NEXT: [[type_info_ptr2:%[0-9]+]] = bitcast i8* [[type_info_ptr]] to %"class.std::type_info"** +// CHECK-NEXT: [[type_info_ptr:%[0-9]+]] = load %"class.std::type_info"*, %"class.std::type_info"** [[type_info_ptr2]], align 8 +// CHECK-NEXT: [[name_ptr:%[a-z0-9._]+]] = getelementptr inbounds %"class.std::type_info", %"class.std::type_info"* [[type_info_ptr]], i64 0, i32 1 +// CHECK-NEXT: [[name:%[0-9]+]] = load i8*, i8** [[name_ptr]], align 8 +// CHECK-NEXT: [[eq:%[a-z0-9.]+]] = icmp eq i8* [[name]], getelementptr inbounds ([3 x i8], [3 x i8]* @_ZTS1B, i64 0, i64 0) +// CHECK-NEXT: ret i1 [[eq]] +// CHECK-NEXT: } + +#include "../typeinfo" + +class A { +public: + virtual void foo(); +}; + +class B : public A { +public: + void foo() override; +}; + +void A::foo() {} +void B::foo() {} + +const auto &getTypeInfo() { + return typeid(A); +} + +const char *getName() { + return typeid(A).name(); +} + +bool equal(A *a) { + return typeid(B) == typeid(*a); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/vbase-offset.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/vbase-offset.cpp new file mode 100644 index 00000000000000..178f453deac390 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/vbase-offset.cpp @@ -0,0 +1,36 @@ +// Check that the pointer adjustment from the virtual base offset is loaded as a +// 32-bit int. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK-LABEL: @_ZTv0_n12_N7Derived1fEi( +// CHECK-NEXT: entry: +// CHECK: [[this:%.+]] = bitcast %class.Derived* %this1 to i8* +// CHECK-NEXT: [[this2:%.+]] = bitcast i8* [[this]] to i8** +// CHECK-NEXT: [[vtable:%.+]] = load i8*, i8** [[this2]], align 8 +// CHECK-NEXT: [[vbase_offset_ptr:%.+]] = getelementptr inbounds i8, i8* [[vtable]], i64 -12 +// CHECK-NEXT: [[vbase_offset_ptr2:%.+]] = bitcast i8* [[vbase_offset_ptr]] to i32* +// CHECK-NEXT: [[vbase_offset:%.+]] = load i32, i32* [[vbase_offset_ptr2]], align 4 +// CHECK-NEXT: [[adj_this:%.+]] = getelementptr inbounds i8, i8* [[this]], i32 [[vbase_offset]] +// CHECK-NEXT: [[adj_this2:%.+]] = bitcast i8* [[adj_this]] to %class.Derived* +// CHECK: [[call:%.+]] = tail call i32 @_ZN7Derived1fEi(%class.Derived* [[adj_this2]], i32 {{.*}}) +// CHECK: ret i32 [[call]] + +class Base { +public: + virtual int f(int x); + +private: + long x; +}; + +class Derived : public virtual Base { +public: + virtual int f(int x); + +private: + long y; +}; + +int Base::f(int x) { return x + 1; } +int Derived::f(int x) { return x + 2; } diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/virtual-function-call.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/virtual-function-call.cpp new file mode 100644 index 00000000000000..0b0b5bd5faa7e8 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/virtual-function-call.cpp @@ -0,0 +1,22 @@ +// Check that we call llvm.load.relative() on a vtable function call. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -O3 -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: define void @_Z5A_fooP1A(%class.A* %a) local_unnamed_addr +// CHECK-NEXT: entry: +// CHECK-NEXT: [[this:%[0-9]+]] = bitcast %class.A* %a to i8** +// CHECK-NEXT: %vtable1 = load i8*, i8** [[this]] +// CHECK-NEXT: [[func_ptr:%[0-9]+]] = tail call i8* @llvm.load.relative.i32(i8* %vtable1, i32 0) +// CHECK-NEXT: [[func:%[0-9]+]] = bitcast i8* [[func_ptr]] to void (%class.A*)* +// CHECK-NEXT: tail call void [[func]](%class.A* %a) +// CHECK-NEXT: ret void +// CHECK-NEXT: } + +class A { +public: + virtual void foo(); +}; + +void A_foo(A *a) { + a->foo(); +} diff --git a/clang/test/CodeGenCXX/RelativeVTablesABI/vtable-hidden-when-in-comdat.cpp b/clang/test/CodeGenCXX/RelativeVTablesABI/vtable-hidden-when-in-comdat.cpp new file mode 100644 index 00000000000000..82b05b30904e51 --- /dev/null +++ b/clang/test/CodeGenCXX/RelativeVTablesABI/vtable-hidden-when-in-comdat.cpp @@ -0,0 +1,19 @@ +// Check that a vtable is made hidden instead of private if the original vtable +// is not dso_local. The vtable will need to be hidden and not private so it can +// be used as acomdat key signature. + +// RUN: %clang_cc1 %s -triple=aarch64-unknown-fuchsia -S -o - -emit-llvm -fexperimental-relative-c++-abi-vtables | FileCheck %s + +// CHECK: @_ZTV1B.local = linkonce_odr hidden unnamed_addr constant +// CHECK: @_ZTV1B = linkonce_odr unnamed_addr alias { [3 x i32] }, { [3 x i32] }* @_ZTV1B.local + +// The VTable will be in a comdat here since it has no key function. +class B { +public: + inline virtual void func() {} +}; + +// This is here just to manifest the vtable for B. +void func() { + B b; +} From 70ad73b6b76838bd7c72123922102b175e5d478a Mon Sep 17 00:00:00 2001 From: sameeran joshi Date: Sun, 19 Apr 2020 16:40:37 +0530 Subject: [PATCH 02/12] [flang] Semantics for SELECT TYPE Summary: Added support for all semantic checks except C1157 was previously implemented. Address review comments. Reviewers: PeteSteinfeld, tskeith, klausler, DavidTruby, kiranktp, anchu-rajendran, sscalpone Subscribers: kiranchandramohan, llvm-commits, flang-commits Tags: #llvm, #flang Differential Revision: https://reviews.llvm.org/D79851 --- flang/lib/Semantics/CMakeLists.txt | 1 + flang/lib/Semantics/assignment.cpp | 2 +- flang/lib/Semantics/check-call.cpp | 2 +- flang/lib/Semantics/check-select-type.cpp | 262 ++++++++++++++++++++++ flang/lib/Semantics/check-select-type.h | 31 +++ flang/lib/Semantics/resolve-names.cpp | 13 ++ flang/lib/Semantics/semantics.cpp | 4 +- flang/test/Semantics/selecttype01.f90 | 241 ++++++++++++++++++++ flang/test/Semantics/selecttype02.f90 | 51 +++++ flang/test/Semantics/selecttype03.f90 | 123 ++++++++++ 10 files changed, 727 insertions(+), 3 deletions(-) create mode 100644 flang/lib/Semantics/check-select-type.cpp create mode 100644 flang/lib/Semantics/check-select-type.h create mode 100644 flang/test/Semantics/selecttype01.f90 create mode 100644 flang/test/Semantics/selecttype02.f90 create mode 100644 flang/test/Semantics/selecttype03.f90 diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt index ff2eba6d12e0ba..4fd75bc60f0077 100644 --- a/flang/lib/Semantics/CMakeLists.txt +++ b/flang/lib/Semantics/CMakeLists.txt @@ -21,6 +21,7 @@ add_flang_library(FortranSemantics check-purity.cpp check-return.cpp check-select-rank.cpp + check-select-type.cpp check-stop.cpp compute-offsets.cpp expression.cpp diff --git a/flang/lib/Semantics/assignment.cpp b/flang/lib/Semantics/assignment.cpp index 657e618c1d7e65..ab8f5e4fdf561a 100644 --- a/flang/lib/Semantics/assignment.cpp +++ b/flang/lib/Semantics/assignment.cpp @@ -75,7 +75,7 @@ void AssignmentContext::Analyze(const parser::AssignmentStmt &stmt) { const Scope &scope{context_.FindScope(lhsLoc)}; if (auto whyNot{WhyNotModifiable(lhsLoc, lhs, scope, true)}) { if (auto *msg{Say(lhsLoc, - "Left-hand side of assignment is not modifiable"_err_en_US)}) { + "Left-hand side of assignment is not modifiable"_err_en_US)}) { // C1158 msg->Attach(*whyNot); } } diff --git a/flang/lib/Semantics/check-call.cpp b/flang/lib/Semantics/check-call.cpp index 163944533eacf6..282a8776103a69 100644 --- a/flang/lib/Semantics/check-call.cpp +++ b/flang/lib/Semantics/check-call.cpp @@ -332,7 +332,7 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy, if (auto why{WhyNotModifiable( messages.at(), actual, *scope, vectorSubscriptIsOk)}) { if (auto *msg{messages.Say( - "Actual argument associated with %s %s must be definable"_err_en_US, + "Actual argument associated with %s %s must be definable"_err_en_US, // C1158 reason, dummyName)}) { msg->Attach(*why); } diff --git a/flang/lib/Semantics/check-select-type.cpp b/flang/lib/Semantics/check-select-type.cpp new file mode 100644 index 00000000000000..5b430440dffb17 --- /dev/null +++ b/flang/lib/Semantics/check-select-type.cpp @@ -0,0 +1,262 @@ +//===-- lib/Semantics/check-select-type.cpp -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "check-select-type.h" +#include "flang/Common/idioms.h" +#include "flang/Common/reference.h" +#include "flang/Evaluate/fold.h" +#include "flang/Evaluate/type.h" +#include "flang/Parser/parse-tree.h" +#include "flang/Semantics/semantics.h" +#include "flang/Semantics/tools.h" +#include + +namespace Fortran::semantics { + +class TypeCaseValues { +public: + TypeCaseValues(SemanticsContext &c, const evaluate::DynamicType &t) + : context_{c}, selectorType_{t} {} + void Check(const std::list &cases) { + for (const auto &c : cases) { + AddTypeCase(c); + } + if (!hasErrors_) { + ReportConflictingTypeCases(); + } + } + +private: + void AddTypeCase(const parser::SelectTypeConstruct::TypeCase &c) { + const auto &stmt{std::get>(c.t)}; + const parser::TypeGuardStmt &typeGuardStmt{stmt.statement}; + const auto &guard{std::get(typeGuardStmt.t)}; + if (std::holds_alternative(guard.u)) { + typeCases_.emplace_back(stmt, std::nullopt); + } else if (std::optional type{GetGuardType(guard)}) { + if (PassesChecksOnGuard(guard, *type)) { + typeCases_.emplace_back(stmt, *type); + } else { + hasErrors_ = true; + } + } else { + hasErrors_ = true; + } + } + + std::optional GetGuardType( + const parser::TypeGuardStmt::Guard &guard) { + return std::visit( + common::visitors{ + [](const parser::Default &) + -> std::optional { + return std::nullopt; + }, + [](const parser::TypeSpec &typeSpec) { + return evaluate::DynamicType::From(typeSpec.declTypeSpec); + }, + [](const parser::DerivedTypeSpec &spec) + -> std::optional { + if (const auto *derivedTypeSpec{spec.derivedTypeSpec}) { + return evaluate::DynamicType(*derivedTypeSpec); + } + return std::nullopt; + }, + }, + guard.u); + } + + bool PassesChecksOnGuard(const parser::TypeGuardStmt::Guard &guard, + const evaluate::DynamicType &guardDynamicType) { + return std::visit( + common::visitors{ + [](const parser::Default &) { return true; }, + [&](const parser::TypeSpec &typeSpec) { + if (const DeclTypeSpec * spec{typeSpec.declTypeSpec}) { + if (spec->category() == DeclTypeSpec::Character && + !guardDynamicType.IsAssumedLengthCharacter()) { // C1160 + context_.Say(parser::FindSourceLocation(typeSpec), + "The type specification statement must have " + "LEN type parameter as assumed"_err_en_US); + return false; + } + if (const DerivedTypeSpec * derived{spec->AsDerived()}) { + return PassesDerivedTypeChecks( + *derived, parser::FindSourceLocation(typeSpec)); + } + return false; + } + return false; + }, + [&](const parser::DerivedTypeSpec &x) { + if (const semantics::DerivedTypeSpec * + derived{x.derivedTypeSpec}) { + return PassesDerivedTypeChecks( + *derived, parser::FindSourceLocation(x)); + } + return false; + }, + }, + guard.u); + } + + bool PassesDerivedTypeChecks(const semantics::DerivedTypeSpec &derived, + parser::CharBlock sourceLoc) const { + for (const auto &pair : derived.parameters()) { + if (pair.second.isLen() && !pair.second.isAssumed()) { // C1160 + context_.Say(sourceLoc, + "The type specification statement must have " + "LEN type parameter as assumed"_err_en_US); + return false; + } + } + if (!IsExtensibleType(&derived)) { // C1161 + context_.Say(sourceLoc, + "The type specification statement must not specify " + "a type with a SEQUENCE attribute or a BIND attribute"_err_en_US); + return false; + } + if (!selectorType_.IsUnlimitedPolymorphic()) { // C1162 + if (const semantics::Scope * guardScope{derived.typeSymbol().scope()}) { + if (const auto *selDerivedTypeSpec{ + evaluate::GetDerivedTypeSpec(selectorType_)}) { + if (!(derived == *selDerivedTypeSpec) && + !guardScope->FindComponent(selDerivedTypeSpec->name())) { + context_.Say(sourceLoc, + "Type specification '%s' must be an extension" + " of TYPE '%s'"_err_en_US, + derived.AsFortran(), selDerivedTypeSpec->AsFortran()); + return false; + } + } + } + } + return true; + } + + struct TypeCase { + explicit TypeCase(const parser::Statement &s, + std::optional guardTypeDynamic) + : stmt{s} { + SetGuardType(guardTypeDynamic); + } + + void SetGuardType(std::optional guardTypeDynamic) { + const auto &guard{GetGuardFromStmt(stmt)}; + std::visit(common::visitors{ + [&](const parser::Default &) {}, + [&](const auto &) { guardType_ = *guardTypeDynamic; }, + }, + guard.u); + } + + bool IsDefault() const { + const auto &guard{GetGuardFromStmt(stmt)}; + return std::holds_alternative(guard.u); + } + + bool IsTypeSpec() const { + const auto &guard{GetGuardFromStmt(stmt)}; + return std::holds_alternative(guard.u); + } + + bool IsDerivedTypeSpec() const { + const auto &guard{GetGuardFromStmt(stmt)}; + return std::holds_alternative(guard.u); + } + + const parser::TypeGuardStmt::Guard &GetGuardFromStmt( + const parser::Statement &stmt) const { + const parser::TypeGuardStmt &typeGuardStmt{stmt.statement}; + return std::get(typeGuardStmt.t); + } + + std::optional guardType() const { + return guardType_; + } + + std::string AsFortran() const { + std::string result; + if (this->guardType()) { + auto type{*this->guardType()}; + result += type.AsFortran(); + } else { + result += "DEFAULT"; + } + return result; + } + const parser::Statement &stmt; + std::optional guardType_; // is this POD? + }; + + // Returns true if and only if the values are different + // Does apple to apple comparision, in case of TypeSpec or DerivedTypeSpec + // checks for kinds as well. + static bool TypesAreDifferent(const TypeCase &x, const TypeCase &y) { + if (x.IsDefault()) { // C1164 + return !y.IsDefault(); + } else if (x.IsTypeSpec() && y.IsTypeSpec()) { // C1163 + return !AreTypeKindCompatible(x, y); + } else if (x.IsDerivedTypeSpec() && y.IsDerivedTypeSpec()) { // C1163 + return !AreTypeKindCompatible(x, y); + } + return true; + } + + static bool AreTypeKindCompatible(const TypeCase &x, const TypeCase &y) { + return (*x.guardType()).IsTkCompatibleWith((*y.guardType())); + } + + void ReportConflictingTypeCases() { + for (auto iter{typeCases_.begin()}; iter != typeCases_.end(); ++iter) { + parser::Message *msg{nullptr}; + for (auto p{typeCases_.begin()}; p != typeCases_.end(); ++p) { + if (p->stmt.source.begin() < iter->stmt.source.begin() && + !TypesAreDifferent(*p, *iter)) { + if (!msg) { + msg = &context_.Say(iter->stmt.source, + "Type specification '%s' conflicts with " + "previous type specification"_err_en_US, + iter->AsFortran()); + } + msg->Attach(p->stmt.source, + "Conflicting type specification '%s'"_en_US, p->AsFortran()); + } + } + } + } + + SemanticsContext &context_; + const evaluate::DynamicType &selectorType_; + std::list typeCases_; + bool hasErrors_{false}; +}; + +void SelectTypeChecker::Enter(const parser::SelectTypeConstruct &construct) { + const auto &selectTypeStmt{ + std::get>(construct.t)}; + const auto &selectType{selectTypeStmt.statement}; + const auto &unResolvedSel{std::get(selectType.t)}; + const auto *selector{GetExprFromSelector(unResolvedSel)}; + + if (!selector) { + return; // expression semantics failed on Selector + } + if (auto exprType{selector->GetType()}) { + const auto &typeCaseList{ + std::get>( + construct.t)}; + TypeCaseValues{context_, *exprType}.Check(typeCaseList); + } +} + +const SomeExpr *SelectTypeChecker::GetExprFromSelector( + const parser::Selector &selector) { + return std::visit([](const auto &x) { return GetExpr(x); }, selector.u); +} +} // namespace Fortran::semantics diff --git a/flang/lib/Semantics/check-select-type.h b/flang/lib/Semantics/check-select-type.h new file mode 100644 index 00000000000000..87b58e7c226571 --- /dev/null +++ b/flang/lib/Semantics/check-select-type.h @@ -0,0 +1,31 @@ +//===-- lib/Semantics/check-select-type.h -----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef FORTRAN_SEMANTICS_CHECK_SELECT_TYPE_H_ +#define FORTRAN_SEMANTICS_CHECK_SELECT_TYPE_H_ + +#include "flang/Semantics/semantics.h" + +namespace Fortran::parser { +struct SelectTypeConstruct; +struct Selector; +} // namespace Fortran::parser + +namespace Fortran::semantics { + +class SelectTypeChecker : public virtual BaseChecker { +public: + explicit SelectTypeChecker(SemanticsContext &context) : context_{context} {}; + void Enter(const parser::SelectTypeConstruct &); + +private: + const SomeExpr *GetExprFromSelector(const parser::Selector &); + SemanticsContext &context_; +}; +} // namespace Fortran::semantics +#endif // FORTRAN_SEMANTICS_CHECK_SELECT_TYPE_H_ diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp index 218dcc07b270b1..4e159b5d2f7719 100644 --- a/flang/lib/Semantics/resolve-names.cpp +++ b/flang/lib/Semantics/resolve-names.cpp @@ -5147,6 +5147,12 @@ void ConstructVisitor::Post(const parser::SelectTypeStmt &x) { // This isn't a name in the current scope, it is in each TypeGuardStmt MakePlaceholder(*name, MiscDetails::Kind::SelectTypeAssociateName); association.name = &*name; + auto exprType{association.selector.expr->GetType()}; + if (exprType && !exprType->IsPolymorphic()) { // C1159 + Say(association.selector.source, + "Selector '%s' in SELECT TYPE statement must be " + "polymorphic"_err_en_US); + } } else { if (const Symbol * whole{UnwrapWholeSymbolDataRef(association.selector.expr)}) { @@ -5156,6 +5162,13 @@ void ConstructVisitor::Post(const parser::SelectTypeStmt &x) { "Selector is not a variable"_err_en_US); association = {}; } + if (const DeclTypeSpec * type{whole->GetType()}) { + if (!type->IsPolymorphic()) { // C1159 + Say(association.selector.source, + "Selector '%s' in SELECT TYPE statement must be " + "polymorphic"_err_en_US); + } + } } else { Say(association.selector.source, // C1157 "Selector is not a named variable: 'associate-name =>' is required"_err_en_US); diff --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp index 4eacb9972ebe72..b8327213682b6e 100644 --- a/flang/lib/Semantics/semantics.cpp +++ b/flang/lib/Semantics/semantics.cpp @@ -26,6 +26,7 @@ #include "check-purity.h" #include "check-return.h" #include "check-select-rank.h" +#include "check-select-type.h" #include "check-stop.h" #include "compute-offsets.h" #include "mod-file.h" @@ -157,7 +158,8 @@ using StatementSemanticsPass2 = SemanticsVisitor; + PurityChecker, ReturnStmtChecker, SelectRankConstructChecker, + SelectTypeChecker, StopChecker>; static bool PerformStatementSemantics( SemanticsContext &context, parser::Program &program) { diff --git a/flang/test/Semantics/selecttype01.f90 b/flang/test/Semantics/selecttype01.f90 new file mode 100644 index 00000000000000..fe9838ae2760f9 --- /dev/null +++ b/flang/test/Semantics/selecttype01.f90 @@ -0,0 +1,241 @@ +! RUN: %S/test_errors.sh %s %t %f18 +! Test for checking select type constraints, +module m1 + use ISO_C_BINDING + type shape + integer :: color + logical :: filled + integer :: x + integer :: y + end type shape + + type, extends(shape) :: rectangle + integer :: length + integer :: width + end type rectangle + + type, extends(rectangle) :: square + end type square + + type, extends(square) :: extsquare + end type + + type :: unrelated + logical :: some_logical + end type + + type withSequence + SEQUENCE + integer :: x + end type + + type, BIND(C) :: withBind + INTEGER(c_int) ::int_in_c + end type + + TYPE(shape), TARGET :: shape_obj + TYPE(rectangle), TARGET :: rect_obj + TYPE(square), TARGET :: squr_obj + !define polymorphic objects + class(*), pointer :: unlim_polymorphic + class(shape), pointer :: shape_lim_polymorphic +end +module m + type :: t(n) + integer, len :: n + end type +contains + subroutine CheckC1160( a ) + class(*), intent(in) :: a + select type ( a ) + !ERROR: The type specification statement must have LEN type parameter as assumed + type is ( character(len=10) ) !<-- assumed length-type + ! OK + type is ( character(len=*) ) + !ERROR: The type specification statement must have LEN type parameter as assumed + type is ( t(n=10) ) + ! OK + type is ( t(n=*) ) !<-- assumed length-type + !ERROR: Derived type 'character' not found + class is ( character(len=10) ) !<-- assumed length-type + end select + end subroutine + + subroutine s() + type derived(param) + integer, len :: param + class(*), allocatable :: x + end type + TYPE(derived(10)) :: a + select type (ax => a%x) + class is (derived(param=*)) + print *, "hello" + end select + end subroutine s +end module + +subroutine CheckC1157 + use m1 + integer, parameter :: const_var=10 + !ERROR: Selector is not a named variable: 'associate-name =>' is required + select type(10) + end select + !ERROR: Selector is not a named variable: 'associate-name =>' is required + select type(const_var) + end select + !ERROR: Selector is not a named variable: 'associate-name =>' is required + select type (4.999) + end select + !ERROR: Selector is not a named variable: 'associate-name =>' is required + select type (shape_obj%x) + end select +end subroutine + +!CheckPloymorphicSelectorType +subroutine CheckC1159a + integer :: int_variable + real :: real_variable + complex :: complex_var = cmplx(3.0, 4.0) + logical :: log_variable + character (len=10) :: char_variable = "OM" + !ERROR: Selector 'int_variable' in SELECT TYPE statement must be polymorphic + select type (int_variable) + end select + !ERROR: Selector 'real_variable' in SELECT TYPE statement must be polymorphic + select type (real_variable) + end select + !ERROR: Selector 'complex_var' in SELECT TYPE statement must be polymorphic + select type(complex_var) + end select + !ERROR: Selector 'logical_variable' in SELECT TYPE statement must be polymorphic + select type(logical_variable) + end select + !ERROR: Selector 'char_variable' in SELECT TYPE statement must be polymorphic + select type(char_variable) + end select +end + +subroutine CheckC1159b + integer :: x + !ERROR: Selector 'x' in SELECT TYPE statement must be polymorphic + select type (a => x) + type is (integer) + print *,'integer ',a + end select +end + +subroutine CheckC1159c + !ERROR: Selector 'x' in SELECT TYPE statement must be polymorphic + select type (a => x) + type is (integer) + print *,'integer ',a + end select +end + +subroutine s(arg) + class(*) :: arg + select type (arg) + type is (integer) + end select +end + +subroutine CheckC1161 + use m1 + shape_lim_polymorphic => rect_obj + select type(shape_lim_polymorphic) + !ERROR: The type specification statement must not specify a type with a SEQUENCE attribute or a BIND attribute + type is (withSequence) + !ERROR: The type specification statement must not specify a type with a SEQUENCE attribute or a BIND attribute + type is (withBind) + end select +end + +subroutine CheckC1162 + use m1 + class(rectangle), pointer :: rectangle_polymorphic + !not unlimited polymorphic objects + select type (rectangle_polymorphic) + !ERROR: Type specification 'shape' must be an extension of TYPE 'rectangle' + type is (shape) + !ERROR: Type specification 'unrelated' must be an extension of TYPE 'rectangle' + type is (unrelated) + !all are ok + type is (square) + type is (extsquare) + !Handle same types + type is (rectangle) + end select + + !Unlimited polymorphic objects are allowed. + unlim_polymorphic => rect_obj + select type (unlim_polymorphic) + type is (shape) + type is (unrelated) + end select +end + +subroutine CheckC1163 + use m1 + !assign dynamically + shape_lim_polymorphic => rect_obj + unlim_polymorphic => shape_obj + select type (shape_lim_polymorphic) + type is (shape) + !ERROR: Type specification 'shape' conflicts with previous type specification + type is (shape) + class is (square) + !ERROR: Type specification 'square' conflicts with previous type specification + class is (square) + end select +end + +subroutine CheckC1164 + use m1 + shape_lim_polymorphic => rect_obj + unlim_polymorphic => shape_obj + select type (shape_lim_polymorphic) + CLASS DEFAULT + !ERROR: Type specification 'DEFAULT' conflicts with previous type specification + CLASS DEFAULT + TYPE IS (shape) + TYPE IS (rectangle) + !ERROR: Type specification 'DEFAULT' conflicts with previous type specification + CLASS DEFAULT + end select + + !Saving computation if some error in guard by not computing RepeatingCases + select type (shape_lim_polymorphic) + CLASS DEFAULT + CLASS DEFAULT + !ERROR: The type specification statement must not specify a type with a SEQUENCE attribute or a BIND attribute + TYPE IS(withSequence) + end select +end subroutine + +subroutine WorkingPolymorphism + use m1 + !assign dynamically + shape_lim_polymorphic => rect_obj + unlim_polymorphic => shape_obj + select type (shape_lim_polymorphic) + type is (shape) + print *, "hello shape" + type is (rectangle) + print *, "hello rect" + type is (square) + print *, "hello square" + CLASS DEFAULT + print *, "default" + end select + print *, "unlim polymorphism" + select type (unlim_polymorphic) + type is (shape) + print *, "hello shape" + type is (rectangle) + print *, "hello rect" + type is (square) + print *, "hello square" + CLASS DEFAULT + print *, "default" + end select +end diff --git a/flang/test/Semantics/selecttype02.f90 b/flang/test/Semantics/selecttype02.f90 new file mode 100644 index 00000000000000..3f4226ec7c03f6 --- /dev/null +++ b/flang/test/Semantics/selecttype02.f90 @@ -0,0 +1,51 @@ +! RUN: %S/test_errors.sh %s %t %f18 +module m1 + use ISO_C_BINDING + type shape + integer :: color + logical :: filled + integer :: x + integer :: y + end type shape + type, extends(shape) :: rectangle + integer :: length + integer :: width + end type rectangle + type, extends(rectangle) :: square + end type square + + TYPE(shape), TARGET :: shape_obj + TYPE(rectangle), TARGET :: rect_obj + !define polymorphic objects + class(shape), pointer :: shape_lim_polymorphic +end +subroutine C1165a + use m1 + shape_lim_polymorphic => rect_obj + label : select type (shape_lim_polymorphic) + end select label + label1 : select type (shape_lim_polymorphic) + !ERROR: SELECT TYPE construct name required but missing + end select + select type (shape_lim_polymorphic) + !ERROR: SELECT TYPE construct name unexpected + end select label2 + select type (shape_lim_polymorphic) + end select +end subroutine +subroutine C1165b + use m1 + shape_lim_polymorphic => rect_obj +!type-guard-stmt realted checks +label : select type (shape_lim_polymorphic) + type is (shape) label + end select label + select type (shape_lim_polymorphic) + !ERROR: SELECT TYPE name not allowed + type is (shape) label + end select +label : select type (shape_lim_polymorphic) + !ERROR: SELECT TYPE name mismatch + type is (shape) labelll + end select label +end subroutine diff --git a/flang/test/Semantics/selecttype03.f90 b/flang/test/Semantics/selecttype03.f90 new file mode 100644 index 00000000000000..e989eb15fe3376 --- /dev/null +++ b/flang/test/Semantics/selecttype03.f90 @@ -0,0 +1,123 @@ +! RUN: %S/test_errors.sh %s %t %f18 +! Test various conditions in C1158. +implicit none + +type :: t1 + integer :: i +end type + +type, extends(t1) :: t2 +end type + +type(t1),target :: x1 +type(t2),target :: x2 + +class(*), pointer :: ptr +class(t1), pointer :: p_or_c +!vector subscript related +class(t1),DIMENSION(:,:),allocatable::array1 +class(t2),DIMENSION(:,:),allocatable::array2 +integer, dimension(2) :: V +V = (/ 1,2 /) +allocate(array1(3,3)) +allocate(array2(3,3)) + +! A) associate with function, i.e (other than variables) +select type ( y => fun(1) ) + type is (t1) + print *, rank(y%i) +end select + +select type ( y => fun(1) ) + type is (t1) + !ERROR: Left-hand side of assignment is not modifiable + y%i = 1 !VDC + type is (t2) + !ERROR: Actual argument associated with INTENT(IN OUT) dummy argument 'z=' must be definable + call sub_with_in_and_inout_param(y,y) !VDC +end select + +! B) associated with a variable: +p_or_c => x1 +select type ( a => p_or_c ) + type is (t1) + a%i = 10 +end select + +select type ( a => p_or_c ) + type is (t1) +end select + +!C)Associate with with vector subscript +select type (b => array1(V,2)) + type is (t1) + !ERROR: Left-hand side of assignment is not modifiable + b%i = 1 !VDC + type is (t2) + !ERROR: Actual argument associated with INTENT(IN OUT) dummy argument 'z=' must be definable + call sub_with_in_and_inout_param_vector(b,b) !VDC +end select +select type(b => foo(1) ) + type is (t1) + !ERROR: Left-hand side of assignment is not modifiable + b%i = 1 !VDC + type is (t2) + !ERROR: Actual argument associated with INTENT(IN OUT) dummy argument 'z=' must be definable + call sub_with_in_and_inout_param_vector(b,b) !VDC +end select + +!D) Have no association and should be ok. +!1. points to function +ptr => fun(1) +select type ( ptr ) +type is (t1) + ptr%i = 1 +end select + +!2. points to variable +ptr=>x1 +select type (ptr) + type is (t1) + ptr%i = 10 +end select + +contains + + function fun(i) + class(t1),pointer :: fun + integer :: i + if (i>0) then + fun => x1 + else if (i<0) then + fun => x2 + else + fun => NULL() + end if + end function + + function foo(i) + integer :: i + class(t1),DIMENSION(:),allocatable :: foo + integer, dimension(2) :: U + U = (/ 1,2 /) + if (i>0) then + foo = array1(2,U) + else if (i<0) then + !ERROR: No intrinsic or user-defined ASSIGNMENT(=) matches operand types TYPE(t1) and TYPE(t2) + foo = array2(2,U) + end if + end function + + subroutine sub_with_in_and_inout_param(y, z) + type(t2), INTENT(IN) :: y + class(t2), INTENT(INOUT) :: z + z%i = 10 + end subroutine + + subroutine sub_with_in_and_inout_param_vector(y, z) + type(t2),DIMENSION(:), INTENT(IN) :: y + class(t2),DIMENSION(:), INTENT(INOUT) :: z + z%i = 10 + end subroutine + +end From 43101d10dbd58d48df732f974e078fd82376039e Mon Sep 17 00:00:00 2001 From: Alexey Bataev Date: Thu, 11 Jun 2020 11:28:59 -0400 Subject: [PATCH 03/12] [OPENMP50]Codegen for scan directive in simd loops. Added codegen for scan directives in simd loop. The codegen transforms original code: ``` int x = 0; #pragma omp simd reduction(inscan, +: x) for (..) { #pragma omp scan inclusive(x) } ``` into ``` int x = 0; for (..) { int x_priv = 0; x = x_priv + x; x_priv = x; } ``` and ``` int x = 0; #pragma omp simd reduction(inscan, +: x) for (..) { #pragma omp scan exclusive(x) } ``` into ``` int x = 0; for (..) { int x_priv = 0; int temp = x; x = x_priv + x; x_priv = temp; } ``` Differential revision: https://reviews.llvm.org/D78232 --- clang/lib/CodeGen/CGStmtOpenMP.cpp | 123 ++++++++++++- clang/lib/Sema/SemaOpenMP.cpp | 49 +++-- clang/test/OpenMP/scan_codegen.cpp | 277 +++++++++++++++++++++++++++++ 3 files changed, 427 insertions(+), 22 deletions(-) create mode 100644 clang/test/OpenMP/scan_codegen.cpp diff --git a/clang/lib/CodeGen/CGStmtOpenMP.cpp b/clang/lib/CodeGen/CGStmtOpenMP.cpp index e9569d4e565826..d51693a4551ad5 100644 --- a/clang/lib/CodeGen/CGStmtOpenMP.cpp +++ b/clang/lib/CodeGen/CGStmtOpenMP.cpp @@ -1730,7 +1730,13 @@ void CodeGenFunction::EmitOMPLoopBody(const OMPLoopDirective &D, // executed in reverse order. OMPBeforeScanBlock = createBasicBlock("omp.before.scan.bb"); OMPAfterScanBlock = createBasicBlock("omp.after.scan.bb"); - OMPScanExitBlock = createBasicBlock("omp.exit.inscan.bb"); + // No need to allocate inscan exit block, in simd mode it is selected in the + // codegen for the scan directive. + if (D.getDirectiveKind() != OMPD_simd && + (!getLangOpts().OpenMPSimd || + isOpenMPSimdDirective(D.getDirectiveKind()))) { + OMPScanExitBlock = createBasicBlock("omp.exit.inscan.bb"); + } OMPScanDispatch = createBasicBlock("omp.inscan.dispatch"); EmitBranch(OMPScanDispatch); EmitBlock(OMPBeforeScanBlock); @@ -2083,6 +2089,15 @@ void CodeGenFunction::EmitOMPSimdInit(const OMPLoopDirective &D, if (const auto *C = D.getSingleClause()) if (C->getKind() == OMPC_ORDER_concurrent) LoopStack.setParallel(/*Enable=*/true); + if ((D.getDirectiveKind() == OMPD_simd || + (getLangOpts().OpenMPSimd && + isOpenMPSimdDirective(D.getDirectiveKind()))) && + llvm::any_of(D.getClausesOfKind(), + [](const OMPReductionClause *C) { + return C->getModifier() == OMPC_REDUCTION_inscan; + })) + // Disable parallel access in case of prefix sum. + LoopStack.setParallel(/*Enable=*/false); } void CodeGenFunction::EmitOMPSimdFinal( @@ -2278,6 +2293,8 @@ static void emitOMPSimdRegion(CodeGenFunction &CGF, const OMPLoopDirective &S, } void CodeGenFunction::EmitOMPSimdDirective(const OMPSimdDirective &S) { + ParentLoopDirectiveForScanRegion ScanRegion(*this, S); + OMPFirstScanLoop = true; auto &&CodeGen = [&S](CodeGenFunction &CGF, PrePostActionTy &Action) { emitOMPSimdRegion(CGF, S, Action); }; @@ -4199,14 +4216,15 @@ void CodeGenFunction::EmitOMPDepobjDirective(const OMPDepobjDirective &S) { } void CodeGenFunction::EmitOMPScanDirective(const OMPScanDirective &S) { - // Do not emit code for non-simd directives in simd-only mode. - if (getLangOpts().OpenMPSimd && !OMPParentLoopDirectiveForScan) + if (!OMPParentLoopDirectiveForScan) return; const OMPExecutableDirective &ParentDir = *OMPParentLoopDirectiveForScan; + bool IsInclusive = S.hasClausesOfKind(); SmallVector Shareds; SmallVector Privates; SmallVector LHSs; SmallVector RHSs; + SmallVector ReductionOps; SmallVector CopyOps; SmallVector CopyArrayTemps; SmallVector CopyArrayElems; @@ -4217,13 +4235,109 @@ void CodeGenFunction::EmitOMPScanDirective(const OMPScanDirective &S) { Privates.append(C->privates().begin(), C->privates().end()); LHSs.append(C->lhs_exprs().begin(), C->lhs_exprs().end()); RHSs.append(C->rhs_exprs().begin(), C->rhs_exprs().end()); + ReductionOps.append(C->reduction_ops().begin(), C->reduction_ops().end()); CopyOps.append(C->copy_ops().begin(), C->copy_ops().end()); CopyArrayTemps.append(C->copy_array_temps().begin(), C->copy_array_temps().end()); CopyArrayElems.append(C->copy_array_elems().begin(), C->copy_array_elems().end()); } - bool IsInclusive = S.hasClausesOfKind(); + if (ParentDir.getDirectiveKind() == OMPD_simd || + (getLangOpts().OpenMPSimd && + isOpenMPSimdDirective(ParentDir.getDirectiveKind()))) { + // For simd directive and simd-based directives in simd only mode, use the + // following codegen: + // int x = 0; + // #pragma omp simd reduction(inscan, +: x) + // for (..) { + // + // #pragma omp scan inclusive(x) + // + // } + // is transformed to: + // int x = 0; + // for (..) { + // int x_priv = 0; + // + // x = x_priv + x; + // x_priv = x; + // + // } + // and + // int x = 0; + // #pragma omp simd reduction(inscan, +: x) + // for (..) { + // + // #pragma omp scan exclusive(x) + // + // } + // to + // int x = 0; + // for (..) { + // int x_priv = 0; + // + // int temp = x; + // x = x_priv + x; + // x_priv = temp; + // + // } + llvm::BasicBlock *OMPScanReduce = createBasicBlock("omp.inscan.reduce"); + EmitBranch(IsInclusive + ? OMPScanReduce + : BreakContinueStack.back().ContinueBlock.getBlock()); + EmitBlock(OMPScanDispatch); + { + // New scope for correct construction/destruction of temp variables for + // exclusive scan. + LexicalScope Scope(*this, S.getSourceRange()); + EmitBranch(IsInclusive ? OMPBeforeScanBlock : OMPAfterScanBlock); + EmitBlock(OMPScanReduce); + if (!IsInclusive) { + // Create temp var and copy LHS value to this temp value. + // TMP = LHS; + for (unsigned I = 0, E = CopyArrayElems.size(); I < E; ++I) { + const Expr *PrivateExpr = Privates[I]; + const Expr *TempExpr = CopyArrayTemps[I]; + EmitAutoVarDecl( + *cast(cast(TempExpr)->getDecl())); + LValue DestLVal = EmitLValue(TempExpr); + LValue SrcLVal = EmitLValue(LHSs[I]); + EmitOMPCopy(PrivateExpr->getType(), DestLVal.getAddress(*this), + SrcLVal.getAddress(*this), + cast(cast(LHSs[I])->getDecl()), + cast(cast(RHSs[I])->getDecl()), + CopyOps[I]); + } + } + CGM.getOpenMPRuntime().emitReduction( + *this, ParentDir.getEndLoc(), Privates, LHSs, RHSs, ReductionOps, + {/*WithNowait=*/true, /*SimpleReduction=*/true, OMPD_simd}); + for (unsigned I = 0, E = CopyArrayElems.size(); I < E; ++I) { + const Expr *PrivateExpr = Privates[I]; + LValue DestLVal; + LValue SrcLVal; + if (IsInclusive) { + DestLVal = EmitLValue(RHSs[I]); + SrcLVal = EmitLValue(LHSs[I]); + } else { + const Expr *TempExpr = CopyArrayTemps[I]; + DestLVal = EmitLValue(RHSs[I]); + SrcLVal = EmitLValue(TempExpr); + } + EmitOMPCopy(PrivateExpr->getType(), DestLVal.getAddress(*this), + SrcLVal.getAddress(*this), + cast(cast(LHSs[I])->getDecl()), + cast(cast(RHSs[I])->getDecl()), + CopyOps[I]); + } + } + EmitBranch(IsInclusive ? OMPAfterScanBlock : OMPBeforeScanBlock); + OMPScanExitBlock = IsInclusive + ? BreakContinueStack.back().ContinueBlock.getBlock() + : OMPScanReduce; + EmitBlock(OMPAfterScanBlock); + return; + } if (!IsInclusive) { EmitBranch(BreakContinueStack.back().ContinueBlock.getBlock()); EmitBlock(OMPScanExitBlock); @@ -6377,6 +6491,7 @@ void CodeGenFunction::EmitSimpleOMPExecutableDirective( } if (isOpenMPSimdDirective(D.getDirectiveKind())) { (void)GlobalsScope.Privatize(); + ParentLoopDirectiveForScanRegion ScanRegion(CGF, D); emitOMPSimdRegion(CGF, cast(D), Action); } else { if (const auto *LD = dyn_cast(&D)) { diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index 3c7acbe9a0783a..68b3ba930c469b 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -15150,24 +15150,37 @@ static bool actOnOMPReductionKindClause( S.ActOnFinishFullExpr(CopyOpRes.get(), /*DiscardedValue=*/true); if (!CopyOpRes.isUsable()) continue; - // Build temp array for prefix sum. - auto *Dim = new (S.Context) - OpaqueValueExpr(ELoc, S.Context.getSizeType(), VK_RValue); - QualType ArrayTy = - S.Context.getVariableArrayType(PrivateTy, Dim, ArrayType::Normal, - /*IndexTypeQuals=*/0, {ELoc, ELoc}); - VarDecl *TempArrayVD = - buildVarDecl(S, ELoc, ArrayTy, D->getName(), - D->hasAttrs() ? &D->getAttrs() : nullptr); - // Add a constructor to the temp decl. - S.ActOnUninitializedDecl(TempArrayVD); - TempArrayRes = buildDeclRefExpr(S, TempArrayVD, ArrayTy, ELoc); - TempArrayElem = - S.DefaultFunctionArrayLvalueConversion(TempArrayRes.get()); - auto *Idx = new (S.Context) - OpaqueValueExpr(ELoc, S.Context.getSizeType(), VK_RValue); - TempArrayElem = S.CreateBuiltinArraySubscriptExpr(TempArrayElem.get(), - ELoc, Idx, ELoc); + // For simd directive and simd-based directives in simd mode no need to + // construct temp array, need just a single temp element. + if (Stack->getCurrentDirective() == OMPD_simd || + (S.getLangOpts().OpenMPSimd && + isOpenMPSimdDirective(Stack->getCurrentDirective()))) { + VarDecl *TempArrayVD = + buildVarDecl(S, ELoc, PrivateTy, D->getName(), + D->hasAttrs() ? &D->getAttrs() : nullptr); + // Add a constructor to the temp decl. + S.ActOnUninitializedDecl(TempArrayVD); + TempArrayRes = buildDeclRefExpr(S, TempArrayVD, PrivateTy, ELoc); + } else { + // Build temp array for prefix sum. + auto *Dim = new (S.Context) + OpaqueValueExpr(ELoc, S.Context.getSizeType(), VK_RValue); + QualType ArrayTy = + S.Context.getVariableArrayType(PrivateTy, Dim, ArrayType::Normal, + /*IndexTypeQuals=*/0, {ELoc, ELoc}); + VarDecl *TempArrayVD = + buildVarDecl(S, ELoc, ArrayTy, D->getName(), + D->hasAttrs() ? &D->getAttrs() : nullptr); + // Add a constructor to the temp decl. + S.ActOnUninitializedDecl(TempArrayVD); + TempArrayRes = buildDeclRefExpr(S, TempArrayVD, ArrayTy, ELoc); + TempArrayElem = + S.DefaultFunctionArrayLvalueConversion(TempArrayRes.get()); + auto *Idx = new (S.Context) + OpaqueValueExpr(ELoc, S.Context.getSizeType(), VK_RValue); + TempArrayElem = S.CreateBuiltinArraySubscriptExpr(TempArrayElem.get(), + ELoc, Idx, ELoc); + } } // OpenMP [2.15.4.6, Restrictions, p.2] diff --git a/clang/test/OpenMP/scan_codegen.cpp b/clang/test/OpenMP/scan_codegen.cpp new file mode 100644 index 00000000000000..3dc9c232974e67 --- /dev/null +++ b/clang/test/OpenMP/scan_codegen.cpp @@ -0,0 +1,277 @@ +// RUN: %clang_cc1 -verify -fopenmp -fopenmp-version=50 -x c++ -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -fopenmp -fopenmp-version=50 -x c++ -triple x86_64-unknown-unknown -emit-pch -o %t %s +// RUN: %clang_cc1 -fopenmp -fopenmp-version=50 -x c++ -triple x86_64-unknown-unknown -include-pch %t -verify %s -emit-llvm -o - | FileCheck %s + +// RUN: %clang_cc1 -verify -fopenmp-simd -fopenmp-version=50 -x c++ -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck --check-prefix SIMD-ONLY0 %s +// RUN: %clang_cc1 -fopenmp-simd -fopenmp-version=50 -x c++ -triple x86_64-unknown-unknown -emit-pch -o %t %s +// RUN: %clang_cc1 -fopenmp-simd -fopenmp-version=50 -x c++ -triple x86_64-unknown-unknown -include-pch %t -verify %s -emit-llvm -o - | FileCheck --check-prefix SIMD-ONLY0 %s +// SIMD-ONLY0-NOT: {{__kmpc|__tgt}} +// +// expected-no-diagnostics +#ifndef HEADER +#define HEADER +void foo(); +void bar(); + +// CHECK-LABEL: baz +void baz() { + int a = 0; + + // CHECK: store i32 0, i32* [[A_ADDR:%.+]], + // CHECK: store i32 0, i32* [[OMP_CNT:%.+]], + // CHECK: br label %[[OMP_HEADER:.+]] + + // CHECK: [[OMP_HEADER]]: + // CHECK: [[CNT_VAL:%.+]] = load i32, i32* [[OMP_CNT]], + // CHECK: [[CMP:%.+]] = icmp slt i32 [[CNT_VAL]], 10 + // CHECK: br i1 [[CMP]], label %[[OMP_BODY:.+]], label %[[OMP_END:.+]] +#pragma omp simd reduction(inscan, + : a) + for (int i = 0; i < 10; ++i) { + // CHECK: [[OMP_BODY]]: + + // i = OMP_CNT*1 + 0; + // CHECK: [[CNT_VAL:%.+]] = load i32, i32* [[OMP_CNT]], + // CHECK: [[MUL:%.+]] = mul nsw i32 [[CNT_VAL]], 1 + // CHECK: [[ADD:%.+]] = add nsw i32 0, [[MUL]] + // CHECK: store i32 [[ADD]], i32* [[I_ADDR:%.+]], + + // A_PRIV = 0; + // CHECK: store i32 0, i32* [[A_PRIV_ADDR:%.+]], + + // goto DISPATCH; + // CHECK: br label %[[DISPATCH:[^,]+]] + + // INPUT_PHASE: + // foo(); + // goto REDUCE; + // CHECK: [[INPUT_PHASE:.+]]: + // CHECK: call void @{{.*}}foo{{.*}}() + // CHECK: br label %[[REDUCE:[^,]+]] + foo(); + + // DISPATCH: + // goto INPUT_PHASE; + // CHECK: [[DISPATCH]]: + // CHECK: br label %[[INPUT_PHASE]] + + // REDUCE: + // A = A_PRIV + A; + // A_PRIV = A; + // goto SCAN_PHASE; + // CHECK: [[REDUCE]]: + // CHECK: [[A:%.+]] = load i32, i32* [[A_ADDR]], + // CHECK: [[A_PRIV:%.+]] = load i32, i32* [[A_PRIV_ADDR]], + // CHECK: [[SUM:%.+]] = add nsw i32 [[A]], [[A_PRIV]] + // CHECK: store i32 [[SUM]], i32* [[A_ADDR]], + // CHECK: [[A:%.+]] = load i32, i32* [[A_ADDR]], + // CHECK: store i32 [[A]], i32* [[A_PRIV_ADDR]], + // CHECK: br label %[[SCAN_PHASE:[^,]+]] +#pragma omp scan inclusive(a) + + // SCAN_PHASE: + // bar(); + // goto CONTINUE; + // CHECK: [[SCAN_PHASE]]: + // CHECK: call void @{{.*}}bar{{.*}}() + // CHECK: br label %[[CONTINUE:[^,]+]] + bar(); + + // CHECK: [[CONTINUE]]: + // CHECK: br label %[[INC_BLOCK:[^,]+]] + + // ++OMP_CNT; + // CHECK: [[INC_BLOCK]]: + // CHECK: [[CNT:%.+]] = load i32, i32* [[OMP_CNT]], + // CHECK: [[INC:%.+]] = add nsw i32 [[CNT]], 1 + // CHECK: store i32 [[INC]], i32* [[OMP_CNT]], + // CHECK: br label %[[OMP_HEADER]] + } + // CHECK: [[OMP_END]]: +} + +struct S { + int a; + S() {} + ~S() {} + S& operator+(const S&); + S& operator=(const S&); +}; + +// CHECK-LABEL: xyz +void xyz() { + S s[2]; + + // CHECK: [[S_BEGIN:%.+]] = getelementptr inbounds [2 x %struct.S], [2 x %struct.S]* [[S_ADDR:%.+]], i{{.+}} 0, i{{.+}} 0 + // CHECK: [[S_END:%.+]] = getelementptr {{.*}}%struct.S, %struct.S* [[S_BEGIN]], i{{.+}} 2 + // CHECK: br label %[[ARRAY_INIT:.+]] + // CHECK: [[ARRAY_INIT]]: + // CHECK: [[S_CUR:%.+]] = phi %struct.S* [ [[S_BEGIN]], %{{.+}} ], [ [[S_NEXT:%.+]], %[[ARRAY_INIT]] ] + // CHECK: call void [[CONSTR:@.+]](%struct.S* [[S_CUR]]) + // CHECK: [[S_NEXT]] = getelementptr inbounds %struct.S, %struct.S* [[S_CUR]], i{{.+}} 1 + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[S_NEXT]], [[S_END]] + // CHECK: br i1 [[IS_DONE]], label %[[DONE:.+]], label %[[ARRAY_INIT]] + // CHECK: [[DONE]]: + // CHECK: store i32 0, i32* [[OMP_CNT:%.+]], + // CHECK: br label %[[OMP_HEADER:.+]] + + // CHECK: [[OMP_HEADER]]: + // CHECK: [[CNT_VAL:%.+]] = load i32, i32* [[OMP_CNT]], + // CHECK: [[CMP:%.+]] = icmp slt i32 [[CNT_VAL]], 10 + // CHECK: br i1 [[CMP]], label %[[OMP_BODY:.+]], label %[[OMP_END:.+]] +#pragma omp simd reduction(inscan, + : s) + for (int i = 0; i < 10; ++i) { + // CHECK: [[OMP_BODY]]: + + // i = OMP_CNT*1 + 0; + // CHECK: [[CNT_VAL:%.+]] = load i32, i32* [[OMP_CNT]], + // CHECK: [[MUL:%.+]] = mul nsw i32 [[CNT_VAL]], 1 + // CHECK: [[ADD:%.+]] = add nsw i32 0, [[MUL]] + // CHECK: store i32 [[ADD]], i32* [[I_ADDR:%.+]], + + // S S_PRIV[2]; + // CHECK: [[S_BEGIN:%.+]] = getelementptr inbounds [2 x %struct.S], [2 x %struct.S]* [[S_PRIV_ADDR:%.+]], i{{.+}} 0, i{{.+}} 0 + // CHECK: [[S_END:%.+]] = getelementptr {{.*}}%struct.S, %struct.S* [[S_BEGIN]], i{{.+}} 2 + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[S_BEGIN]], [[S_END]] + // CHECK: br i1 [[IS_DONE]], label %[[DONE:.+]], label %[[ARRAY_INIT:[^,]+]] + // CHECK: [[ARRAY_INIT]]: + // CHECK: [[S_CUR:%.+]] = phi %struct.S* [ [[S_BEGIN]], %[[OMP_BODY]] ], [ [[S_NEXT:%.+]], %[[ARRAY_INIT]] ] + // CHECK: call void [[CONSTR]](%struct.S* [[S_CUR]]) + // CHECK: [[S_NEXT]] = getelementptr {{.*}}%struct.S, %struct.S* [[S_CUR]], i{{.+}} 1 + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[S_NEXT]], [[S_END]] + // CHECK: br i1 [[IS_DONE]], label %[[DONE:.+]], label %[[ARRAY_INIT]] + // CHECK: [[DONE]]: + // CHECK: [[LHS_BEGIN:%.+]] = bitcast [2 x %struct.S]* [[S_ADDR]] to %struct.S* + // CHECK: [[RHS_BEGIN:%.+]] = bitcast [2 x %struct.S]* [[S_PRIV_ADDR]] to %struct.S* + + // goto DISPATCH; + // CHECK: br label %[[DISPATCH:[^,]+]] + + // SCAN_PHASE: + // foo(); + // goto CONTINUE; + // CHECK: [[SCAN_PHASE:.+]]: + // CHECK: call void @{{.*}}foo{{.*}}() + // CHECK: br label %[[CONTINUE:[^,]+]] + foo(); + + // DISPATCH: + // goto INPUT_PHASE; + // CHECK: [[DISPATCH]]: + // CHECK: br label %[[INPUT_PHASE:[^,]+]] + + // REDUCE: + // TEMP = S; + // S = S_PRIV + S; + // S_PRIV = TEMP; + // goto SCAN_PHASE; + // CHECK: [[REDUCE:.+]]: + + // S TEMP[2]; + // CHECK: [[TEMP_ARR_BEG:%.+]] = getelementptr inbounds [2 x %struct.S], [2 x %struct.S]* [[TEMP_ARR:%.+]], i32 0, i32 0 + // CHECK: [[TEMP_ARR_END:%.+]] = getelementptr inbounds %struct.S, %struct.S* [[TEMP_ARR_BEG]], i64 2 + // CHECK: br label %[[BODY:[^,]+]] + // CHECK: [[BODY]]: + // CHECK: [[CUR:%.+]] = phi %struct.S* [ [[TEMP_ARR_BEG]], %[[REDUCE]] ], [ [[NEXT:%.+]], %[[BODY]] ] + // CHECK: call void [[CONSTR]](%struct.S* [[CUR]]) + // CHECK: [[NEXT]] = getelementptr inbounds %struct.S, %struct.S* [[CUR]], i64 1 + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[NEXT]], [[TEMP_ARR_END]] + // CHECK: br i1 [[IS_DONE]], label %[[EXIT:[^,]+]], label %[[BODY]] + // CHECK: [[EXIT]]: + + // TEMP = S; + // CHECK: [[TEMP_ARR_BEG:%.+]] = getelementptr inbounds [2 x %struct.S], [2 x %struct.S]* [[TEMP_ARR]], i32 0, i32 0 + // CHECK: [[TEMP_ARR_END:%.+]] = getelementptr %struct.S, %struct.S* [[TEMP_ARR_BEG]], i64 2 + // CHECK: [[IS_EMPTY:%.+]] = icmp eq %struct.S* [[TEMP_ARR_BEG]], [[TEMP_ARR_END]] + // CHECK: br i1 [[IS_EMPTY]], label %[[EXIT:[^,]+]], label %[[BODY:[^,]+]] + // CHECK: [[BODY]]: + // CHECK: [[CUR_SRC:%.+]] = phi %struct.S* [ [[LHS_BEGIN]], %{{.+}} ], [ [[SRC_NEXT:%.+]], %[[BODY]] ] + // CHECK: [[CUR_DEST:%.+]] = phi %struct.S* [ [[TEMP_ARR_BEG]], %{{.+}} ], [ [[DEST_NEXT:%.+]], %[[BODY]] ] + // CHECK: call {{.*}}%struct.S* [[S_COPY:@.+]](%struct.S* [[CUR_DEST]], %struct.S* {{.*}}[[CUR_SRC]]) + // CHECK: [[DEST_NEXT:%.+]] = getelementptr %struct.S, %struct.S* [[CUR_DEST]], i32 1 + // CHECK: [[SRC_NEXT:%.+]] = getelementptr %struct.S, %struct.S* [[CUR_SRC]], i32 1 + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[DEST_NEXT]], [[TEMP_ARR_END]] + // CHECK: br i1 [[IS_DONE]], label %[[EXIT]], label %[[BODY]] + // CHECK: [[EXIT]]: + + // S = S_PRIV + S; + // CHECK: [[LHS_END:%.+]] = getelementptr {{.*}}%struct.S, %struct.S* [[LHS_BEGIN]], i{{.+}} 2 + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[LHS_BEGIN]], [[LHS_END]] + // CHECK: br i1 [[IS_DONE]], label %[[DONE:.+]], label %[[ARRAY_REDUCE_COPY:[^,]+]] + // CHECK: [[ARRAY_REDUCE_COPY]]: + // CHECK: [[SRC_CUR:%.+]] = phi %struct.S* [ [[RHS_BEGIN]], %[[EXIT]] ], [ [[SRC_NEXT:%.+]], %[[ARRAY_REDUCE_COPY]] ] + // CHECK: [[DEST_CUR:%.+]] = phi %struct.S* [ [[LHS_BEGIN]], %[[EXIT]] ], [ [[DEST_NEXT:%.+]], %[[ARRAY_REDUCE_COPY]] ] + // CHECK: [[SUM:%.+]] = call {{.*}}%struct.S* @{{.+}}(%struct.S* [[DEST_CUR]], %struct.S* {{.*}}[[SRC_CUR]]) + // CHECK: call {{.*}}%struct.S* [[S_COPY]](%struct.S* [[DEST_CUR]], %struct.S* {{.*}}[[SUM]]) + // CHECK: [[DEST_NEXT]] = getelementptr {{.*}}%struct.S, %struct.S* [[DEST_CUR]], i{{.+}} 1 + // CHECK: [[SRC_NEXT]] = getelementptr {{.*}}%struct.S, %struct.S* [[SRC_CUR]], i{{.+}} 1 + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[DEST_NEXT]], [[LHS_END]] + // CHECK: br i1 [[IS_DONE]], label %[[DONE:.+]], label %[[ARRAY_REDUCE_COPY]] + // CHECK: [[DONE]]: + + // S_PRIV = TEMP; + // CHECK: [[TEMP_ARR_BEG:%.+]] = bitcast [2 x %struct.S]* [[TEMP_ARR]] to %struct.S* + // CHECK: [[RHS_END:%.+]] = getelementptr %struct.S, %struct.S* [[RHS_BEGIN]], i64 2 + // CHECK: [[IS_EMPTY:%.+]] = icmp eq %struct.S* [[RHS_BEGIN]], [[RHS_END]] + // CHECK: br i1 [[IS_EMPTY]], label %[[EXIT:[^,]+]], label %[[BODY:[^,]+]] + // CHECK: [[BODY]]: + // CHECK: [[CUR_SRC:%.+]] = phi %struct.S* [ [[TEMP_ARR_BEG]], %[[DONE]] ], [ [[SRC_NEXT:%.+]], %[[BODY]] ] + // CHECK: [[CUR_DEST:%.+]] = phi %struct.S* [ [[RHS_BEGIN]], %[[DONE]] ], [ [[DEST_NEXT:%.+]], %[[BODY]] ] + // CHECK: call {{.*}}%struct.S* [[S_COPY]](%struct.S* [[CUR_DEST]], %struct.S* {{.*}}[[CUR_SRC]]) + // CHECK: [[DEST_NEXT]] = getelementptr %struct.S, %struct.S* [[CUR_DEST]], i32 1 + // CHECK: [[SRC_NEXT]] = getelementptr %struct.S, %struct.S* [[CUR_SRC]], i32 1 + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[DEST_NEXT]], [[RHS_END]] + // CHECK: br i1 [[IS_DONE]], label %[[DONE:[^,]+]], label %[[BODY]] + // CHECK: [[DONE]]: + + // TEMP.~S() + // CHECK: [[TEMP_ARR_BEG:%.+]] = getelementptr inbounds [2 x %struct.S], [2 x %struct.S]* [[TEMP_ARR]], i32 0, i32 0 + // CHECK: [[TEMP_ARR_END:%.+]] = getelementptr inbounds %struct.S, %struct.S* [[TEMP_ARR_BEG]], i64 2 + // CHECK: br label %[[BODY:[^,]+]] + // CHECK: [[BODY]]: + // CHECK: [[CUR:%.+]] = phi %struct.S* [ [[TEMP_ARR_END]], %[[DONE]] ], [ [[PREV:%.+]], %[[BODY]] ] + // CHECK: [[PREV]] = getelementptr inbounds %struct.S, %struct.S* [[CUR]], i64 -1 + // CHECK: call void [[DESTR:@.+]](%struct.S* [[PREV]]) + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[PREV]], [[TEMP_ARR_BEG]] + // CHECK: br i1 [[IS_DONE]], label %[[EXIT:[^,]+]], label %[[BODY]] + // CHECK: [[EXIT]]: + + // goto SCAN_PHASE; + // CHECK: br label %[[SCAN_PHASE]] +#pragma omp scan exclusive(s) + + // INPUT_PHASE: + // bar(); + // goto REDUCE; + // CHECK: [[INPUT_PHASE]]: + // CHECK: call void @{{.*}}bar{{.*}}() + // CHECK: br label %[[REDUCE]] + bar(); + + // CHECK: [[CONTINUE]]: + + // S_PRIV[2].~S(); + // CHECK: [[S_BEGIN:%.+]] = getelementptr inbounds [2 x %struct.S], [2 x %struct.S]* [[S_PRIV_ADDR]], i{{.+}} 0, i{{.+}} 0 + // CHECK: [[S_END:%.+]] = getelementptr {{.*}}%struct.S, %struct.S* [[S_BEGIN]], i{{.+}} 2 + // CHECK: br label %[[ARRAY_DESTR:[^,]+]] + // CHECK: [[ARRAY_DESTR]]: + // CHECK: [[S_CUR:%.+]] = phi %struct.S* [ [[S_END]], %[[CONTINUE]] ], [ [[S_PREV:%.+]], %[[ARRAY_DESTR]] ] + // CHECK: [[S_PREV]] = getelementptr {{.*}}%struct.S, %struct.S* [[S_CUR]], i{{.+}} -1 + // CHECK: call void [[DESTR]](%struct.S* [[S_PREV]]) + // CHECK: [[IS_DONE:%.+]] = icmp eq %struct.S* [[S_PREV]], [[S_BEGIN]] + // CHECK: br i1 [[IS_DONE]], label %[[DONE:.+]], label %[[ARRAY_DESTR]] + // CHECK: [[DONE]]: + // CHECK: br label %[[INC_BLOCK:[^,]+]] + + // ++OMP_CNT; + // CHECK: [[INC_BLOCK]]: + // CHECK: [[CNT:%.+]] = load i32, i32* [[OMP_CNT]], + // CHECK: [[INC:%.+]] = add nsw i32 [[CNT]], 1 + // CHECK: store i32 [[INC]], i32* [[OMP_CNT]], + // CHECK: br label %[[OMP_HEADER]] + } + // CHECK: [[OMP_END]]: +} + +// CHECK-NOT: !{!"llvm.loop.parallel_accesses" + +#endif // HEADER From e619e9d5f542e0764362c2b6762ecd31640651cd Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 11 Jun 2020 13:50:40 -0400 Subject: [PATCH 04/12] [libc++abi] Simplify the logic for finding libc++ from libc++abi Since we have the monorepo, libc++abi's build requires a sibling checkout of the libc++ sources. Hence, the logic for finding libc++ can be greatly simplified. --- libcxxabi/CMakeLists.txt | 50 ++++------------------------------------ libcxxabi/www/index.html | 3 +-- 2 files changed, 6 insertions(+), 47 deletions(-) diff --git a/libcxxabi/CMakeLists.txt b/libcxxabi/CMakeLists.txt index bb79987f7477ab..d4ffcf89017e04 100644 --- a/libcxxabi/CMakeLists.txt +++ b/libcxxabi/CMakeLists.txt @@ -126,53 +126,13 @@ if (NOT LIBCXXABI_ENABLE_SHARED AND NOT LIBCXXABI_ENABLE_STATIC) message(FATAL_ERROR "libc++abi must be built as either a shared or static library.") endif() -if (LLVM_EXTERNAL_LIBCXX_SOURCE_DIR) - set(LIBCXXABI_LIBCXX_SRC_DIRS ${LLVM_EXTERNAL_LIBCXX_SOURCE_DIR}) -else() - set(LIBCXXABI_LIBCXX_SRC_DIRS - "${LLVM_MAIN_SRC_DIR}/projects/libcxx" - "${LLVM_MAIN_SRC_DIR}/runtimes/libcxx" - "${LLVM_MAIN_SRC_DIR}/../libcxx" - ) -endif() - -set(LIBCXXABI_LIBCXX_INCLUDE_DIRS "") -foreach(dir ${LIBCXXABI_LIBCXX_SRC_DIRS}) - list(APPEND LIBCXXABI_LIBCXX_INCLUDE_DIRS "${dir}/include") -endforeach() - -find_path( - LIBCXXABI_LIBCXX_INCLUDES - __config - PATHS ${LIBCXXABI_LIBCXX_INCLUDES} - ${LIBCXXABI_LIBCXX_PATH}/include - ${CMAKE_BINARY_DIR}/${LIBCXXABI_LIBCXX_INCLUDES} - ${LIBCXXABI_LIBCXX_INCLUDE_DIRS} - ${LLVM_INCLUDE_DIR}/c++/v1 - NO_DEFAULT_PATH - NO_CMAKE_FIND_ROOT_PATH - ) +set(LIBCXXABI_LIBCXX_PATH "${CMAKE_CURRENT_LIST_DIR}/../libcxx") +set(LIBCXXABI_LIBCXX_INCLUDES "${LIBCXXABI_LIBCXX_PATH}/include") -set(LIBCXXABI_LIBCXX_INCLUDES "${LIBCXXABI_LIBCXX_INCLUDES}" CACHE PATH - "Specify path to libc++ includes." FORCE) - -find_path( - LIBCXXABI_LIBCXX_PATH - utils/libcxx/test/__init__.py - PATHS ${LIBCXXABI_LIBCXX_PATH} - ${LIBCXXABI_LIBCXX_INCLUDES}/../ - ${LIBCXXABI_LIBCXX_SRC_DIRS} - NO_DEFAULT_PATH - NO_CMAKE_FIND_ROOT_PATH - ) - -if (LIBCXXABI_LIBCXX_PATH STREQUAL "LIBCXXABI_LIBCXX_PATH-NOTFOUND") - message(WARNING "LIBCXXABI_LIBCXX_PATH was not specified and couldn't be infered.") - set(LIBCXXABI_LIBCXX_PATH "") -endif() - -set(LIBCXXABI_LIBCXX_PATH "${LIBCXXABI_LIBCXX_PATH}" CACHE PATH +set(LIBCXXABI_LIBCXX_PATH "${CMAKE_CURRENT_LIST_DIR}/../libcxx" CACHE PATH "Specify path to libc++ source." FORCE) +set(LIBCXXABI_LIBCXX_INCLUDES "${LIBCXXABI_LIBCXX_PATH}/include" CACHE PATH + "Specify path to libc++ includes." FORCE) option(LIBCXXABI_HERMETIC_STATIC_LIBRARY "Do not export any symbols from the static library." OFF) diff --git a/libcxxabi/www/index.html b/libcxxabi/www/index.html index 3fdff0dea8354d..b8fd20f4f9f79c 100644 --- a/libcxxabi/www/index.html +++ b/libcxxabi/www/index.html @@ -94,8 +94,7 @@

Get it and get involved!

  • cd llvm-project
  • mkdir build-libcxxabi && cd build-libcxxabi
  • -
  • cmake -DLIBCXXABI_LIBCXX_PATH=path/to/libcxx ../libcxxabi # on - linux you may need -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
  • +
  • cmake ../libcxxabi # on linux you may need -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
  • make
  • By default CMake uses llvm-config to locate the required From 12459ec92686573e68ba9b18dd5ea90a1a4e9bc7 Mon Sep 17 00:00:00 2001 From: Eli Friedman Date: Thu, 11 Jun 2020 12:13:18 -0700 Subject: [PATCH 05/12] [AArch64] Regenerate SVE test llvm-ir-to-intrinsic.ll. --- .../CodeGen/AArch64/llvm-ir-to-intrinsic.ll | 411 ++++++++++-------- 1 file changed, 229 insertions(+), 182 deletions(-) diff --git a/llvm/test/CodeGen/AArch64/llvm-ir-to-intrinsic.ll b/llvm/test/CodeGen/AArch64/llvm-ir-to-intrinsic.ll index 32663e4f0c857f..786dc4bd0aef15 100644 --- a/llvm/test/CodeGen/AArch64/llvm-ir-to-intrinsic.ll +++ b/llvm/test/CodeGen/AArch64/llvm-ir-to-intrinsic.ll @@ -1,3 +1,4 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py ; RUN: llc -mtriple=aarch64-linux-gnu -mattr=+sve < %s | FileCheck %s ; @@ -5,50 +6,55 @@ ; define @sdiv_i32( %a, %b) { -; CHECK-LABEL: @sdiv_i32 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: sdiv z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK-LABEL: sdiv_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: sdiv z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %div = sdiv %a, %b ret %div } define @sdiv_i64( %a, %b) { -; CHECK-LABEL: @sdiv_i64 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: sdiv z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK-LABEL: sdiv_i64: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: sdiv z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %div = sdiv %a, %b ret %div } define @sdiv_split_i32( %a, %b) { -; CHECK-LABEL: @sdiv_split_i32 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: sdiv z0.s, p0/m, z0.s, z2.s -; CHECK-DAG: sdiv z1.s, p0/m, z1.s, z3.s -; CHECK-NEXT: ret +; CHECK-LABEL: sdiv_split_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: sdiv z0.s, p0/m, z0.s, z2.s +; CHECK-NEXT: sdiv z1.s, p0/m, z1.s, z3.s +; CHECK-NEXT: ret %div = sdiv %a, %b ret %div } define @sdiv_widen_i32( %a, %b) { -; CHECK-LABEL: @sdiv_widen_i32 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: sxtw z1.d, p0/m, z1.d -; CHECK-DAG: sxtw z0.d, p0/m, z0.d -; CHECK-DAG: sdiv z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK-LABEL: sdiv_widen_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: sxtw z1.d, p0/m, z1.d +; CHECK-NEXT: sxtw z0.d, p0/m, z0.d +; CHECK-NEXT: sdiv z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %div = sdiv %a, %b ret %div } define @sdiv_split_i64( %a, %b) { -; CHECK-LABEL: @sdiv_split_i64 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: sdiv z0.d, p0/m, z0.d, z2.d -; CHECK-DAG: sdiv z1.d, p0/m, z1.d, z3.d -; CHECK-NEXT: ret +; CHECK-LABEL: sdiv_split_i64: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: sdiv z0.d, p0/m, z0.d, z2.d +; CHECK-NEXT: sdiv z1.d, p0/m, z1.d, z3.d +; CHECK-NEXT: ret %div = sdiv %a, %b ret %div } @@ -58,50 +64,55 @@ define @sdiv_split_i64( %a, @udiv_i32( %a, %b) { -; CHECK-LABEL: @udiv_i32 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: udiv z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK-LABEL: udiv_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: udiv z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %div = udiv %a, %b ret %div } define @udiv_i64( %a, %b) { -; CHECK-LABEL: @udiv_i64 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: udiv z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK-LABEL: udiv_i64: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: udiv z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %div = udiv %a, %b ret %div } define @udiv_split_i32( %a, %b) { -; CHECK-LABEL: @udiv_split_i32 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: udiv z0.s, p0/m, z0.s, z2.s -; CHECK-DAG: udiv z1.s, p0/m, z1.s, z3.s -; CHECK-NEXT: ret +; CHECK-LABEL: udiv_split_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: udiv z0.s, p0/m, z0.s, z2.s +; CHECK-NEXT: udiv z1.s, p0/m, z1.s, z3.s +; CHECK-NEXT: ret %div = udiv %a, %b ret %div } define @udiv_widen_i32( %a, %b) { -; CHECK-LABEL: @udiv_widen_i32 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: and z1.d, z1.d, #0xffffffff -; CHECK-DAG: and z0.d, z0.d, #0xffffffff -; CHECK-DAG: udiv z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK-LABEL: udiv_widen_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: and z1.d, z1.d, #0xffffffff +; CHECK-NEXT: and z0.d, z0.d, #0xffffffff +; CHECK-NEXT: udiv z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %div = udiv %a, %b ret %div } define @udiv_split_i64( %a, %b) { -; CHECK-LABEL: @udiv_split_i64 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: udiv z0.d, p0/m, z0.d, z2.d -; CHECK-DAG: udiv z1.d, p0/m, z1.d, z3.d -; CHECK-NEXT: ret +; CHECK-LABEL: udiv_split_i64: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: udiv z0.d, p0/m, z0.d, z2.d +; CHECK-NEXT: udiv z1.d, p0/m, z1.d, z3.d +; CHECK-NEXT: ret %div = udiv %a, %b ret %div } @@ -111,20 +122,22 @@ define @udiv_split_i64( %a, @smin_i8( %a, %b, %c) { -; CHECK-LABEL: @smin_i8 -; CHECK-DAG: ptrue p0.b -; CHECK-DAG: smin z0.b, p0/m, z0.b, z1.b -; CHECK-NEXT: ret +; CHECK-LABEL: smin_i8: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.b +; CHECK-NEXT: smin z0.b, p0/m, z0.b, z1.b +; CHECK-NEXT: ret %cmp = icmp slt %a, %b %min = select %cmp, %a, %b ret %min } define @smin_i16( %a, %b, %c) { -; CHECK-LABEL: @smin_i16 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: smin z0.h, p0/m, z0.h, z1.h -; CHECK-NEXT: ret +; CHECK-LABEL: smin_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: smin z0.h, p0/m, z0.h, z1.h +; CHECK-NEXT: ret %cmp = icmp slt %a, %b %min = select %cmp, %a, %b ret %min @@ -132,9 +145,10 @@ define @smin_i16( %a, %b define @smin_i32( %a, %b, %c) { ; CHECK-LABEL: smin_i32: -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: smin z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: smin z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %cmp = icmp slt %a, %b %min = select %cmp, %a, %b ret %min @@ -142,9 +156,10 @@ define @smin_i32( %a, %b define @smin_i64( %a, %b, %c) { ; CHECK-LABEL: smin_i64: -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: smin z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: smin z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %cmp = icmp slt %a, %b %min = select %cmp, %a, %b ret %min @@ -155,20 +170,22 @@ define @smin_i64( %a, %b ; define @umin_i8( %a, %b, %c) { -; CHECK-LABEL: @umin_i8 -; CHECK-DAG: ptrue p0.b -; CHECK-DAG: umin z0.b, p0/m, z0.b, z1.b -; CHECK-NEXT: ret +; CHECK-LABEL: umin_i8: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.b +; CHECK-NEXT: umin z0.b, p0/m, z0.b, z1.b +; CHECK-NEXT: ret %cmp = icmp ult %a, %b %min = select %cmp, %a, %b ret %min } define @umin_i16( %a, %b, %c) { -; CHECK-LABEL: @umin_i16 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: umin z0.h, p0/m, z0.h, z1.h -; CHECK-NEXT: ret +; CHECK-LABEL: umin_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: umin z0.h, p0/m, z0.h, z1.h +; CHECK-NEXT: ret %cmp = icmp ult %a, %b %min = select %cmp, %a, %b ret %min @@ -176,9 +193,10 @@ define @umin_i16( %a, %b define @umin_i32( %a, %b, %c) { ; CHECK-LABEL: umin_i32: -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: umin z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: umin z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %cmp = icmp ult %a, %b %min = select %cmp, %a, %b ret %min @@ -186,9 +204,10 @@ define @umin_i32( %a, %b define @umin_i64( %a, %b, %c) { ; CHECK-LABEL: umin_i64: -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: umin z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: umin z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %cmp = icmp ult %a, %b %min = select %cmp, %a, %b ret %min @@ -199,20 +218,22 @@ define @umin_i64( %a, %b ; define @smax_i8( %a, %b, %c) { -; CHECK-LABEL: @smax_i8 -; CHECK-DAG: ptrue p0.b -; CHECK-DAG: smax z0.b, p0/m, z0.b, z1.b -; CHECK-NEXT: ret +; CHECK-LABEL: smax_i8: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.b +; CHECK-NEXT: smax z0.b, p0/m, z0.b, z1.b +; CHECK-NEXT: ret %cmp = icmp sgt %a, %b %min = select %cmp, %a, %b ret %min } define @smax_i16( %a, %b, %c) { -; CHECK-LABEL: @smax_i16 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: smax z0.h, p0/m, z0.h, z1.h -; CHECK-NEXT: ret +; CHECK-LABEL: smax_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: smax z0.h, p0/m, z0.h, z1.h +; CHECK-NEXT: ret %cmp = icmp sgt %a, %b %min = select %cmp, %a, %b ret %min @@ -220,9 +241,10 @@ define @smax_i16( %a, %b define @smax_i32( %a, %b, %c) { ; CHECK-LABEL: smax_i32: -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: smax z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: smax z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %cmp = icmp sgt %a, %b %min = select %cmp, %a, %b ret %min @@ -230,9 +252,10 @@ define @smax_i32( %a, %b define @smax_i64( %a, %b, %c) { ; CHECK-LABEL: smax_i64: -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: smax z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: smax z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %cmp = icmp sgt %a, %b %min = select %cmp, %a, %b ret %min @@ -243,20 +266,22 @@ define @smax_i64( %a, %b ; define @umax_i8( %a, %b, %c) { -; CHECK-LABEL: @umax_i8 -; CHECK-DAG: ptrue p0.b -; CHECK-DAG: umax z0.b, p0/m, z0.b, z1.b -; CHECK-NEXT: ret +; CHECK-LABEL: umax_i8: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.b +; CHECK-NEXT: umax z0.b, p0/m, z0.b, z1.b +; CHECK-NEXT: ret %cmp = icmp ugt %a, %b %min = select %cmp, %a, %b ret %min } define @umax_i16( %a, %b, %c) { -; CHECK-LABEL: @umax_i16 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: umax z0.h, p0/m, z0.h, z1.h -; CHECK-NEXT: ret +; CHECK-LABEL: umax_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: umax z0.h, p0/m, z0.h, z1.h +; CHECK-NEXT: ret %cmp = icmp ugt %a, %b %min = select %cmp, %a, %b ret %min @@ -264,9 +289,10 @@ define @umax_i16( %a, %b define @umax_i32( %a, %b, %c) { ; CHECK-LABEL: umax_i32: -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: umax z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: umax z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %cmp = icmp ugt %a, %b %min = select %cmp, %a, %b ret %min @@ -274,9 +300,10 @@ define @umax_i32( %a, %b define @umax_i64( %a, %b, %c) { ; CHECK-LABEL: umax_i64: -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: umax z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: umax z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %cmp = icmp ugt %a, %b %min = select %cmp, %a, %b ret %min @@ -287,57 +314,64 @@ define @umax_i64( %a, %b ; define @asr_i8( %a, %b){ -; CHECK-LABEL: @asr_i8 -; CHECK-DAG: ptrue p0.b -; CHECK-DAG: asr z0.b, p0/m, z0.b, z1.b -; CHECK-NEXT: ret +; CHECK-LABEL: asr_i8: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.b +; CHECK-NEXT: asr z0.b, p0/m, z0.b, z1.b +; CHECK-NEXT: ret %shr = ashr %a, %b ret %shr } define @asr_i16( %a, %b){ -; CHECK-LABEL: @asr_i16 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: asr z0.h, p0/m, z0.h, z1.h -; CHECK-NEXT: ret +; CHECK-LABEL: asr_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: asr z0.h, p0/m, z0.h, z1.h +; CHECK-NEXT: ret %shr = ashr %a, %b ret %shr } define @asr_i32( %a, %b){ -; CHECK-LABEL: @asr_i32 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: asr z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK-LABEL: asr_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: asr z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %shr = ashr %a, %b ret %shr } define @asr_i64( %a, %b){ -; CHECK-LABEL: @asr_i64 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: asr z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK-LABEL: asr_i64: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: asr z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %shr = ashr %a, %b ret %shr } define @asr_split_i16( %a, %b){ -; CHECK-LABEL: @asr_split_i16 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: asr z0.h, p0/m, z0.h, z2.h -; CHECK-DAG: asr z1.h, p0/m, z1.h, z3.h -; CHECK-NEXT: ret +; CHECK-LABEL: asr_split_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: asr z0.h, p0/m, z0.h, z2.h +; CHECK-NEXT: asr z1.h, p0/m, z1.h, z3.h +; CHECK-NEXT: ret %shr = ashr %a, %b ret %shr } define @asr_promote_i32( %a, %b){ -; CHECK-LABEL: @asr_promote_i32 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: and z1.d, z1.d, #0xffffffff -; CHECK-DAG: asr z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK-LABEL: asr_promote_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: sxtw z0.d, p0/m, z0.d +; CHECK-NEXT: and z1.d, z1.d, #0xffffffff +; CHECK-NEXT: asr z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %shr = ashr %a, %b ret %shr } @@ -347,57 +381,63 @@ define @asr_promote_i32( %a, @lsl_i8( %a, %b){ -; CHECK-LABEL: @lsl_i8 -; CHECK-DAG: ptrue p0.b -; CHECK-DAG: lsl z0.b, p0/m, z0.b, z1.b -; CHECK-NEXT: ret +; CHECK-LABEL: lsl_i8: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.b +; CHECK-NEXT: lsl z0.b, p0/m, z0.b, z1.b +; CHECK-NEXT: ret %shl = shl %a, %b ret %shl } define @lsl_i16( %a, %b){ -; CHECK-LABEL: @lsl_i16 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: lsl z0.h, p0/m, z0.h, z1.h -; CHECK-NEXT: ret +; CHECK-LABEL: lsl_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: lsl z0.h, p0/m, z0.h, z1.h +; CHECK-NEXT: ret %shl = shl %a, %b ret %shl } define @lsl_i32( %a, %b){ -; CHECK-LABEL: @lsl_i32 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: lsl z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK-LABEL: lsl_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: lsl z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %shl = shl %a, %b ret %shl } define @lsl_i64( %a, %b){ -; CHECK-LABEL: @lsl_i64 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: lsl z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK-LABEL: lsl_i64: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: lsl z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %shl = shl %a, %b ret %shl } define @lsl_split_i64( %a, %b){ -; CHECK-LABEL: @lsl_split_i64 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: lsl z0.d, p0/m, z0.d, z2.d -; CHECK-DAG: lsl z1.d, p0/m, z1.d, z3.d -; CHECK-NEXT: ret +; CHECK-LABEL: lsl_split_i64: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: lsl z0.d, p0/m, z0.d, z2.d +; CHECK-NEXT: lsl z1.d, p0/m, z1.d, z3.d +; CHECK-NEXT: ret %shl = shl %a, %b ret %shl } define @lsl_promote_i16( %a, %b){ -; CHECK-LABEL: @lsl_promote_i16 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: and z1.s, z1.s, #0xffff -; CHECK-DAG: lsl z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK-LABEL: lsl_promote_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: and z1.s, z1.s, #0xffff +; CHECK-NEXT: lsl z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %shl = shl %a, %b ret %shl } @@ -407,57 +447,64 @@ define @lsl_promote_i16( %a, @lsr_i8( %a, %b){ -; CHECK-LABEL: @lsr_i8 -; CHECK-DAG: ptrue p0.b -; CHECK-DAG: lsr z0.b, p0/m, z0.b, z1.b -; CHECK-NEXT: ret +; CHECK-LABEL: lsr_i8: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.b +; CHECK-NEXT: lsr z0.b, p0/m, z0.b, z1.b +; CHECK-NEXT: ret %shr = lshr %a, %b ret %shr } define @lsr_i16( %a, %b){ -; CHECK-LABEL: @lsr_i16 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: lsr z0.h, p0/m, z0.h, z1.h -; CHECK-NEXT: ret +; CHECK-LABEL: lsr_i16: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: lsr z0.h, p0/m, z0.h, z1.h +; CHECK-NEXT: ret %shr = lshr %a, %b ret %shr } define @lsr_i32( %a, %b){ -; CHECK-LABEL: @lsr_i32 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: lsr z0.s, p0/m, z0.s, z1.s -; CHECK-NEXT: ret +; CHECK-LABEL: lsr_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: lsr z0.s, p0/m, z0.s, z1.s +; CHECK-NEXT: ret %shr = lshr %a, %b ret %shr } define @lsr_i64( %a, %b){ -; CHECK-LABEL: @lsr_i64 -; CHECK-DAG: ptrue p0.d -; CHECK-DAG: lsr z0.d, p0/m, z0.d, z1.d -; CHECK-NEXT: ret +; CHECK-LABEL: lsr_i64: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.d +; CHECK-NEXT: lsr z0.d, p0/m, z0.d, z1.d +; CHECK-NEXT: ret %shr = lshr %a, %b ret %shr } define @lsr_promote_i8( %a, %b){ -; CHECK-LABEL: @lsr_promote_i8 -; CHECK-DAG: ptrue p0.h -; CHECK-DAG: and z1.h, z1.h, #0xff -; CHECK-DAG: lsr z0.h, p0/m, z0.h, z1.h -; CHECK-NEXT: ret +; CHECK-LABEL: lsr_promote_i8: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.h +; CHECK-NEXT: and z1.h, z1.h, #0xff +; CHECK-NEXT: and z0.h, z0.h, #0xff +; CHECK-NEXT: lsr z0.h, p0/m, z0.h, z1.h +; CHECK-NEXT: ret %shr = lshr %a, %b ret %shr } define @lsr_split_i32( %a, %b){ -; CHECK-LABEL: @lsr_split_i32 -; CHECK-DAG: ptrue p0.s -; CHECK-DAG: lsr z0.s, p0/m, z0.s, z2.s -; CHECK-DAG: lsr z1.s, p0/m, z1.s, z3.s -; CHECK-NEXT: ret +; CHECK-LABEL: lsr_split_i32: +; CHECK: // %bb.0: +; CHECK-NEXT: ptrue p0.s +; CHECK-NEXT: lsr z0.s, p0/m, z0.s, z2.s +; CHECK-NEXT: lsr z1.s, p0/m, z1.s, z3.s +; CHECK-NEXT: ret %shr = lshr %a, %b ret %shr } From 269d843720382a8beafdf156cd01f9791faff66c Mon Sep 17 00:00:00 2001 From: Craig Topper Date: Thu, 11 Jun 2020 12:18:52 -0700 Subject: [PATCH 06/12] [X86] Replace TB with PS on instructions that are documented in the SDM with 'NP' 'NP' means that the instruction is not recognized with a 66, F2 or F3 prefix. It will either #UD or decode to a different instruction. All of the cases are here should fall into the #UD variety since we should be detecting the collision with other instructions when we build the disassembler tables. --- llvm/lib/Target/X86/X86InstrFPStack.td | 8 ++++---- llvm/lib/Target/X86/X86InstrInfo.td | 6 +++--- llvm/lib/Target/X86/X86InstrSGX.td | 6 +++--- llvm/lib/Target/X86/X86InstrSSE.td | 12 +++++------ llvm/lib/Target/X86/X86InstrSystem.td | 28 +++++++++++++------------- llvm/lib/Target/X86/X86InstrTSX.td | 4 ++-- llvm/lib/Target/X86/X86InstrVMX.td | 2 +- 7 files changed, 33 insertions(+), 33 deletions(-) diff --git a/llvm/lib/Target/X86/X86InstrFPStack.td b/llvm/lib/Target/X86/X86InstrFPStack.td index 2e59374a3bae0c..4d007bd3090a0b 100644 --- a/llvm/lib/Target/X86/X86InstrFPStack.td +++ b/llvm/lib/Target/X86/X86InstrFPStack.td @@ -748,20 +748,20 @@ def FCOMPP : I<0xDE, MRM_D9, (outs), (ins), "fcompp", []>; let Uses = [FPSW, FPCW] in { def FXSAVE : I<0xAE, MRM0m, (outs), (ins opaquemem:$dst), - "fxsave\t$dst", [(int_x86_fxsave addr:$dst)]>, TB, + "fxsave\t$dst", [(int_x86_fxsave addr:$dst)]>, PS, Requires<[HasFXSR]>; def FXSAVE64 : RI<0xAE, MRM0m, (outs), (ins opaquemem:$dst), "fxsave64\t$dst", [(int_x86_fxsave64 addr:$dst)]>, - TB, Requires<[HasFXSR, In64BitMode]>; + PS, Requires<[HasFXSR, In64BitMode]>; } // Uses = [FPSW, FPCW] let Defs = [FPSW, FPCW] in { def FXRSTOR : I<0xAE, MRM1m, (outs), (ins opaquemem:$src), "fxrstor\t$src", [(int_x86_fxrstor addr:$src)]>, - TB, Requires<[HasFXSR]>; + PS, Requires<[HasFXSR]>; def FXRSTOR64 : RI<0xAE, MRM1m, (outs), (ins opaquemem:$src), "fxrstor64\t$src", [(int_x86_fxrstor64 addr:$src)]>, - TB, Requires<[HasFXSR, In64BitMode]>; + PS, Requires<[HasFXSR, In64BitMode]>; } // Defs = [FPSW, FPCW] } // SchedRW diff --git a/llvm/lib/Target/X86/X86InstrInfo.td b/llvm/lib/Target/X86/X86InstrInfo.td index 8179afe882010e..146f6ba8b7fed4 100644 --- a/llvm/lib/Target/X86/X86InstrInfo.td +++ b/llvm/lib/Target/X86/X86InstrInfo.td @@ -2793,11 +2793,11 @@ let SchedRW = [WriteStore] in { def MOVDIRI32 : I<0xF9, MRMDestMem, (outs), (ins i32mem:$dst, GR32:$src), "movdiri\t{$src, $dst|$dst, $src}", [(int_x86_directstore32 addr:$dst, GR32:$src)]>, - T8, Requires<[HasMOVDIRI]>; + T8PS, Requires<[HasMOVDIRI]>; def MOVDIRI64 : RI<0xF9, MRMDestMem, (outs), (ins i64mem:$dst, GR64:$src), "movdiri\t{$src, $dst|$dst, $src}", [(int_x86_directstore64 addr:$dst, GR64:$src)]>, - T8, Requires<[In64BitMode, HasMOVDIRI]>; + T8PS, Requires<[In64BitMode, HasMOVDIRI]>; } // SchedRW //===----------------------------------------------------------------------===// @@ -3004,7 +3004,7 @@ def CLWB : I<0xAE, MRM6m, (outs), (ins i8mem:$src), "clwb\t$src", let Predicates = [HasCLDEMOTE], SchedRW = [WriteLoad] in def CLDEMOTE : I<0x1C, MRM0m, (outs), (ins i8mem:$src), "cldemote\t$src", - [(int_x86_cldemote addr:$src)]>, TB; + [(int_x86_cldemote addr:$src)]>, PS; //===----------------------------------------------------------------------===// // Subsystems. diff --git a/llvm/lib/Target/X86/X86InstrSGX.td b/llvm/lib/Target/X86/X86InstrSGX.td index 747f5aa86653d6..6439f717accb96 100644 --- a/llvm/lib/Target/X86/X86InstrSGX.td +++ b/llvm/lib/Target/X86/X86InstrSGX.td @@ -17,13 +17,13 @@ let SchedRW = [WriteSystem], Predicates = [HasSGX] in { // ENCLS - Execute an Enclave System Function of Specified Leaf Number def ENCLS : I<0x01, MRM_CF, (outs), (ins), - "encls", []>, TB; + "encls", []>, PS; // ENCLU - Execute an Enclave User Function of Specified Leaf Number def ENCLU : I<0x01, MRM_D7, (outs), (ins), - "enclu", []>, TB; + "enclu", []>, PS; // ENCLV - Execute an Enclave VMM Function of Specified Leaf Number def ENCLV : I<0x01, MRM_C0, (outs), (ins), - "enclv", []>, TB; + "enclv", []>, PS; } // SchedRW diff --git a/llvm/lib/Target/X86/X86InstrSSE.td b/llvm/lib/Target/X86/X86InstrSSE.td index dd9b98eaecea1e..013f9f7bd25b8f 100644 --- a/llvm/lib/Target/X86/X86InstrSSE.td +++ b/llvm/lib/Target/X86/X86InstrSSE.td @@ -3218,11 +3218,11 @@ def VSTMXCSR : VPSI<0xAE, MRM3m, (outs), (ins i32mem:$dst), let mayLoad=1, hasSideEffects=1 in def LDMXCSR : I<0xAE, MRM2m, (outs), (ins i32mem:$src), "ldmxcsr\t$src", [(int_x86_sse_ldmxcsr addr:$src)]>, - TB, Sched<[WriteLDMXCSR]>; + PS, Sched<[WriteLDMXCSR]>; let mayStore=1, hasSideEffects=1 in def STMXCSR : I<0xAE, MRM3m, (outs), (ins i32mem:$dst), "stmxcsr\t$dst", [(int_x86_sse_stmxcsr addr:$dst)]>, - TB, Sched<[WriteSTMXCSR]>; + PS, Sched<[WriteSTMXCSR]>; //===---------------------------------------------------------------------===// // SSE2 - Move Aligned/Unaligned Packed Integer Instructions @@ -6623,7 +6623,7 @@ multiclass SHAI_binop Opc, string OpcodeStr, Intrinsic IntId, [!if(UsesXMM0, (set VR128:$dst, (IntId VR128:$src1, VR128:$src2, XMM0)), (set VR128:$dst, (IntId VR128:$src1, VR128:$src2)))]>, - T8, Sched<[sched]>; + T8PS, Sched<[sched]>; def rm : I Opc, string OpcodeStr, Intrinsic IntId, (set VR128:$dst, (IntId VR128:$src1, (memop addr:$src2), XMM0)), (set VR128:$dst, (IntId VR128:$src1, - (memop addr:$src2))))]>, T8, + (memop addr:$src2))))]>, T8PS, Sched<[sched.Folded, sched.ReadAfterFold]>; } @@ -6644,7 +6644,7 @@ let Constraints = "$src1 = $dst", Predicates = [HasSHA] in { "sha1rnds4\t{$src3, $src2, $dst|$dst, $src2, $src3}", [(set VR128:$dst, (int_x86_sha1rnds4 VR128:$src1, VR128:$src2, - (i8 timm:$src3)))]>, TA, + (i8 timm:$src3)))]>, TAPS, Sched<[SchedWriteVecIMul.XMM]>; def SHA1RNDS4rmi : Ii8<0xCC, MRMSrcMem, (outs VR128:$dst), (ins VR128:$src1, i128mem:$src2, u8imm:$src3), @@ -6652,7 +6652,7 @@ let Constraints = "$src1 = $dst", Predicates = [HasSHA] in { [(set VR128:$dst, (int_x86_sha1rnds4 VR128:$src1, (memop addr:$src2), - (i8 timm:$src3)))]>, TA, + (i8 timm:$src3)))]>, TAPS, Sched<[SchedWriteVecIMul.XMM.Folded, SchedWriteVecIMul.XMM.ReadAfterFold]>; diff --git a/llvm/lib/Target/X86/X86InstrSystem.td b/llvm/lib/Target/X86/X86InstrSystem.td index 8469632fafe698..82b619747d79ee 100644 --- a/llvm/lib/Target/X86/X86InstrSystem.td +++ b/llvm/lib/Target/X86/X86InstrSystem.td @@ -512,12 +512,12 @@ let SchedRW = [WriteSystem] in { let SchedRW = [WriteSystem] in { let Predicates = [HasXSAVE] in { let Defs = [EDX, EAX], Uses = [ECX] in - def XGETBV : I<0x01, MRM_D0, (outs), (ins), "xgetbv", []>, TB; + def XGETBV : I<0x01, MRM_D0, (outs), (ins), "xgetbv", []>, PS; let Uses = [EDX, EAX, ECX] in def XSETBV : I<0x01, MRM_D1, (outs), (ins), "xsetbv", - [(int_x86_xsetbv ECX, EDX, EAX)]>, TB; + [(int_x86_xsetbv ECX, EDX, EAX)]>, PS; } // HasXSAVE @@ -542,22 +542,22 @@ def XSAVEOPT64 : RI<0xAE, MRM6m, (outs), (ins opaquemem:$dst), [(int_x86_xsaveopt64 addr:$dst, EDX, EAX)]>, PS, Requires<[HasXSAVEOPT, In64BitMode]>; def XSAVEC : I<0xC7, MRM4m, (outs), (ins opaquemem:$dst), "xsavec\t$dst", - [(int_x86_xsavec addr:$dst, EDX, EAX)]>, TB, Requires<[HasXSAVEC]>; + [(int_x86_xsavec addr:$dst, EDX, EAX)]>, PS, Requires<[HasXSAVEC]>; def XSAVEC64 : RI<0xC7, MRM4m, (outs), (ins opaquemem:$dst), "xsavec64\t$dst", - [(int_x86_xsavec64 addr:$dst, EDX, EAX)]>, TB, Requires<[HasXSAVEC, In64BitMode]>; + [(int_x86_xsavec64 addr:$dst, EDX, EAX)]>, PS, Requires<[HasXSAVEC, In64BitMode]>; def XSAVES : I<0xC7, MRM5m, (outs), (ins opaquemem:$dst), "xsaves\t$dst", - [(int_x86_xsaves addr:$dst, EDX, EAX)]>, TB, Requires<[HasXSAVES]>; + [(int_x86_xsaves addr:$dst, EDX, EAX)]>, PS, Requires<[HasXSAVES]>; def XSAVES64 : RI<0xC7, MRM5m, (outs), (ins opaquemem:$dst), "xsaves64\t$dst", - [(int_x86_xsaves64 addr:$dst, EDX, EAX)]>, TB, Requires<[HasXSAVE, In64BitMode]>; + [(int_x86_xsaves64 addr:$dst, EDX, EAX)]>, PS, Requires<[HasXSAVE, In64BitMode]>; def XRSTORS : I<0xC7, MRM3m, (outs), (ins opaquemem:$dst), "xrstors\t$dst", - [(int_x86_xrstors addr:$dst, EDX, EAX)]>, TB, Requires<[HasXSAVES]>; + [(int_x86_xrstors addr:$dst, EDX, EAX)]>, PS, Requires<[HasXSAVES]>; def XRSTORS64 : RI<0xC7, MRM3m, (outs), (ins opaquemem:$dst), "xrstors64\t$dst", - [(int_x86_xrstors64 addr:$dst, EDX, EAX)]>, TB, Requires<[HasXSAVES, In64BitMode]>; + [(int_x86_xrstors64 addr:$dst, EDX, EAX)]>, PS, Requires<[HasXSAVES, In64BitMode]>; } // Uses } // SchedRW @@ -590,10 +590,10 @@ let Defs = [RAX, RDX, RSI], Uses = [RAX, RSI] in let SchedRW = [WriteSystem] in { let Defs = [EAX, EDX], Uses = [ECX] in def RDPKRUr : I<0x01, MRM_EE, (outs), (ins), "rdpkru", - [(set EAX, (X86rdpkru ECX)), (implicit EDX)]>, TB; + [(set EAX, (X86rdpkru ECX)), (implicit EDX)]>, PS; let Uses = [EAX, ECX, EDX] in def WRPKRUr : I<0x01, MRM_EF, (outs), (ins), "wrpkru", - [(X86wrpkru EAX, EDX, ECX)]>, TB; + [(X86wrpkru EAX, EDX, ECX)]>, PS; } // SchedRW //===----------------------------------------------------------------------===// @@ -653,15 +653,15 @@ let Predicates = [In64BitMode, HasINVPCID] in { //===----------------------------------------------------------------------===// // SMAP Instruction let Defs = [EFLAGS], SchedRW = [WriteSystem] in { - def CLAC : I<0x01, MRM_CA, (outs), (ins), "clac", []>, TB; - def STAC : I<0x01, MRM_CB, (outs), (ins), "stac", []>, TB; + def CLAC : I<0x01, MRM_CA, (outs), (ins), "clac", []>, PS; + def STAC : I<0x01, MRM_CB, (outs), (ins), "stac", []>, PS; } //===----------------------------------------------------------------------===// // SMX Instruction let SchedRW = [WriteSystem] in { let Uses = [RAX, RBX, RCX, RDX], Defs = [RAX, RBX, RCX] in { - def GETSEC : I<0x37, RawFrm, (outs), (ins), "getsec", []>, TB; + def GETSEC : I<0x37, RawFrm, (outs), (ins), "getsec", []>, PS; } // Uses, Defs } // SchedRW @@ -729,6 +729,6 @@ def PTWRITE64r : RI<0xAE, MRM4r, (outs), (ins GR64:$dst), let SchedRW = [WriteSystem] in { let Uses = [RAX, RBX, RCX, RDX], Defs = [RAX, RBX, RCX, RDX, EFLAGS] in - def PCONFIG : I<0x01, MRM_C5, (outs), (ins), "pconfig", []>, TB, + def PCONFIG : I<0x01, MRM_C5, (outs), (ins), "pconfig", []>, PS, Requires<[HasPCONFIG]>; } // SchedRW diff --git a/llvm/lib/Target/X86/X86InstrTSX.td b/llvm/lib/Target/X86/X86InstrTSX.td index 1bdb9741676a5c..28563eeb44840a 100644 --- a/llvm/lib/Target/X86/X86InstrTSX.td +++ b/llvm/lib/Target/X86/X86InstrTSX.td @@ -37,11 +37,11 @@ def XABORT_DEF : I<0, Pseudo, (outs), (ins), "# XABORT DEF", []>; } def XEND : I<0x01, MRM_D5, (outs), (ins), - "xend", [(int_x86_xend)]>, TB, Requires<[HasRTM]>; + "xend", [(int_x86_xend)]>, PS, Requires<[HasRTM]>; let Defs = [EFLAGS] in def XTEST : I<0x01, MRM_D6, (outs), (ins), - "xtest", [(set EFLAGS, (X86xtest))]>, TB, Requires<[HasRTM]>; + "xtest", [(set EFLAGS, (X86xtest))]>, PS, Requires<[HasRTM]>; def XABORT : Ii8<0xc6, MRM_F8, (outs), (ins i8imm:$imm), "xabort\t$imm", diff --git a/llvm/lib/Target/X86/X86InstrVMX.td b/llvm/lib/Target/X86/X86InstrVMX.td index 37bc4ce2e05370..d204a33358eab3 100644 --- a/llvm/lib/Target/X86/X86InstrVMX.td +++ b/llvm/lib/Target/X86/X86InstrVMX.td @@ -37,7 +37,7 @@ def VMCLEARm : I<0xC7, MRM6m, (outs), (ins i64mem:$vmcs), "vmclear\t$vmcs", []>, PD; // OF 01 D4 -def VMFUNC : I<0x01, MRM_D4, (outs), (ins), "vmfunc", []>, TB; +def VMFUNC : I<0x01, MRM_D4, (outs), (ins), "vmfunc", []>, PS; // 0F 01 C2 def VMLAUNCH : I<0x01, MRM_C2, (outs), (ins), "vmlaunch", []>, TB; From 118c13c691a5b3b0c5760061a89d5ef7a319c15b Mon Sep 17 00:00:00 2001 From: Siva Chandra Reddy Date: Tue, 2 Jun 2020 14:04:57 -0700 Subject: [PATCH 07/12] [libc] Add implementation of few floating point manipulation functions. Implementations of copysign[f], frexp[f], logb[f], and modf[f] are added. Reviewers: asteinhauser Differential Revision: https://reviews.llvm.org/D81134 --- libc/config/linux/aarch64/entrypoints.txt | 8 ++ libc/config/linux/api.td | 8 ++ libc/config/linux/x86_64/entrypoints.txt | 8 ++ libc/spec/stdc.td | 14 ++ libc/src/math/CMakeLists.txt | 80 ++++++++++++ libc/src/math/copysign.cpp | 18 +++ libc/src/math/copysign.h | 18 +++ libc/src/math/copysignf.cpp | 18 +++ libc/src/math/copysignf.h | 18 +++ libc/src/math/frexp.cpp | 18 +++ libc/src/math/frexp.h | 18 +++ libc/src/math/frexpf.cpp | 18 +++ libc/src/math/frexpf.h | 18 +++ libc/src/math/logb.cpp | 16 +++ libc/src/math/logb.h | 18 +++ libc/src/math/logbf.cpp | 16 +++ libc/src/math/logbf.h | 18 +++ libc/src/math/modf.cpp | 18 +++ libc/src/math/modf.h | 18 +++ libc/src/math/modff.cpp | 18 +++ libc/src/math/modff.h | 18 +++ libc/test/src/math/CMakeLists.txt | 96 ++++++++++++++ libc/test/src/math/copysign_test.cpp | 63 +++++++++ libc/test/src/math/copysignf_test.cpp | 65 ++++++++++ libc/test/src/math/frexp_test.cpp | 141 ++++++++++++++++++++ libc/test/src/math/frexpf_test.cpp | 150 ++++++++++++++++++++++ libc/test/src/math/logb_test.cpp | 99 ++++++++++++++ libc/test/src/math/logbf_test.cpp | 99 ++++++++++++++ libc/test/src/math/modf_test.cpp | 130 +++++++++++++++++++ libc/test/src/math/modff_test.cpp | 135 +++++++++++++++++++ libc/utils/FPUtil/BitPatterns.h | 6 + libc/utils/FPUtil/CMakeLists.txt | 1 + libc/utils/FPUtil/FloatOperations.h | 24 ++-- libc/utils/FPUtil/ManipulationFunctions.h | 102 +++++++++++++++ 34 files changed, 1503 insertions(+), 10 deletions(-) create mode 100644 libc/src/math/copysign.cpp create mode 100644 libc/src/math/copysign.h create mode 100644 libc/src/math/copysignf.cpp create mode 100644 libc/src/math/copysignf.h create mode 100644 libc/src/math/frexp.cpp create mode 100644 libc/src/math/frexp.h create mode 100644 libc/src/math/frexpf.cpp create mode 100644 libc/src/math/frexpf.h create mode 100644 libc/src/math/logb.cpp create mode 100644 libc/src/math/logb.h create mode 100644 libc/src/math/logbf.cpp create mode 100644 libc/src/math/logbf.h create mode 100644 libc/src/math/modf.cpp create mode 100644 libc/src/math/modf.h create mode 100644 libc/src/math/modff.cpp create mode 100644 libc/src/math/modff.h create mode 100644 libc/test/src/math/copysign_test.cpp create mode 100644 libc/test/src/math/copysignf_test.cpp create mode 100644 libc/test/src/math/frexp_test.cpp create mode 100644 libc/test/src/math/frexpf_test.cpp create mode 100644 libc/test/src/math/logb_test.cpp create mode 100644 libc/test/src/math/logbf_test.cpp create mode 100644 libc/test/src/math/modf_test.cpp create mode 100644 libc/test/src/math/modff_test.cpp create mode 100644 libc/utils/FPUtil/ManipulationFunctions.h diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt index ca3157b295e8c6..044b0b5ae0ec6c 100644 --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -8,6 +8,8 @@ set(TARGET_LIBC_ENTRYPOINTS set(TARGET_LIBM_ENTRYPOINTS # math.h entrypoints + libc.src.math.copysign + libc.src.math.copysignf libc.src.math.ceil libc.src.math.ceilf libc.src.math.cosf @@ -17,6 +19,12 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.fabsf libc.src.math.floor libc.src.math.floorf + libc.src.math.frexp + libc.src.math.frexpf + libc.src.math.logb + libc.src.math.logbf + libc.src.math.modf + libc.src.math.modff libc.src.math.round libc.src.math.roundf libc.src.math.sincosf diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td index 1d20c5fd12b042..f2b7050661d370 100644 --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -150,6 +150,8 @@ def MathAPI : PublicAPI<"math.h"> { FloatT, ]; let Functions = [ + "copysign", + "copysignf", "ceil", "ceilf", "cosf", @@ -157,6 +159,12 @@ def MathAPI : PublicAPI<"math.h"> { "fabsf", "floor", "floorf", + "frexp", + "frexpf", + "logb", + "logbf", + "modf", + "modff", "expf", "exp2f", "round", diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index a6bc492ac67e5f..93b0dfdcb3f38b 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -45,6 +45,8 @@ set(TARGET_LIBC_ENTRYPOINTS set(TARGET_LIBM_ENTRYPOINTS # math.h entrypoints + libc.src.math.copysign + libc.src.math.copysignf libc.src.math.ceil libc.src.math.ceilf libc.src.math.cosf @@ -54,6 +56,12 @@ set(TARGET_LIBM_ENTRYPOINTS libc.src.math.fabsf libc.src.math.floor libc.src.math.floorf + libc.src.math.frexp + libc.src.math.frexpf + libc.src.math.logb + libc.src.math.logbf + libc.src.math.modf + libc.src.math.modff libc.src.math.round libc.src.math.roundf libc.src.math.sincosf diff --git a/libc/spec/stdc.td b/libc/spec/stdc.td index a9bdfbd710ae60..b31f89d3d73a95 100644 --- a/libc/spec/stdc.td +++ b/libc/spec/stdc.td @@ -20,6 +20,8 @@ def StdC : StandardSpec<"stdc"> { PtrType ThrdTTypePtr = PtrType; PtrType IntPtr = PtrType; + PtrType FloatPtr = PtrType; + PtrType DoublePtr = PtrType; NamedType SigHandlerT = NamedType<"__sighandler_t">; @@ -187,6 +189,9 @@ def StdC : StandardSpec<"stdc"> { ], [], // Enumerations [ + FunctionSpec<"copysign", RetValSpec, [ArgSpec, ArgSpec]>, + FunctionSpec<"copysignf", RetValSpec, [ArgSpec, ArgSpec]>, + FunctionSpec<"ceil", RetValSpec, [ArgSpec]>, FunctionSpec<"ceilf", RetValSpec, [ArgSpec]>, @@ -196,6 +201,15 @@ def StdC : StandardSpec<"stdc"> { FunctionSpec<"floor", RetValSpec, [ArgSpec]>, FunctionSpec<"floorf", RetValSpec, [ArgSpec]>, + FunctionSpec<"frexp", RetValSpec, [ArgSpec, ArgSpec]>, + FunctionSpec<"frexpf", RetValSpec, [ArgSpec, ArgSpec]>, + + FunctionSpec<"logb", RetValSpec, [ArgSpec]>, + FunctionSpec<"logbf", RetValSpec, [ArgSpec]>, + + FunctionSpec<"modf", RetValSpec, [ArgSpec, ArgSpec]>, + FunctionSpec<"modff", RetValSpec, [ArgSpec, ArgSpec]>, + FunctionSpec<"cosf", RetValSpec, [ArgSpec]>, FunctionSpec<"sinf", RetValSpec, [ArgSpec]>, diff --git a/libc/src/math/CMakeLists.txt b/libc/src/math/CMakeLists.txt index 701ee6eb04481c..c74e7e4ab5ffd0 100644 --- a/libc/src/math/CMakeLists.txt +++ b/libc/src/math/CMakeLists.txt @@ -189,3 +189,83 @@ add_entrypoint_object( .math_utils libc.include.math ) + +add_entrypoint_object( + copysign + SRCS + copysign.cpp + HDRS + copysign.h + DEPENDS + libc.utils.FPUtil.fputil +) + +add_entrypoint_object( + copysignf + SRCS + copysignf.cpp + HDRS + copysignf.h + DEPENDS + libc.utils.FPUtil.fputil +) + +add_entrypoint_object( + frexp + SRCS + frexp.cpp + HDRS + frexp.h + DEPENDS + libc.utils.FPUtil.fputil +) + +add_entrypoint_object( + frexpf + SRCS + frexpf.cpp + HDRS + frexpf.h + DEPENDS + libc.utils.FPUtil.fputil +) + +add_entrypoint_object( + logb + SRCS + logb.cpp + HDRS + logb.h + DEPENDS + libc.utils.FPUtil.fputil +) + +add_entrypoint_object( + logbf + SRCS + logbf.cpp + HDRS + logbf.h + DEPENDS + libc.utils.FPUtil.fputil +) + +add_entrypoint_object( + modf + SRCS + modf.cpp + HDRS + modf.h + DEPENDS + libc.utils.FPUtil.fputil +) + +add_entrypoint_object( + modff + SRCS + modff.cpp + HDRS + modff.h + DEPENDS + libc.utils.FPUtil.fputil +) diff --git a/libc/src/math/copysign.cpp b/libc/src/math/copysign.cpp new file mode 100644 index 00000000000000..5eee71f9812390 --- /dev/null +++ b/libc/src/math/copysign.cpp @@ -0,0 +1,18 @@ +//===-- Implementation of copysign function -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/common.h" +#include "utils/FPUtil/ManipulationFunctions.h" + +namespace __llvm_libc { + +double LLVM_LIBC_ENTRYPOINT(copysign)(double x, double y) { + return fputil::copysign(x, y); +} + +} // namespace __llvm_libc diff --git a/libc/src/math/copysign.h b/libc/src/math/copysign.h new file mode 100644 index 00000000000000..edffe1b082fe04 --- /dev/null +++ b/libc/src/math/copysign.h @@ -0,0 +1,18 @@ +//===-- Implementation header for copysign ----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MATH_COPYSIGN_H +#define LLVM_LIBC_SRC_MATH_COPYSIGN_H + +namespace __llvm_libc { + +double copysign(double x, double y); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_COPYSIGN_H diff --git a/libc/src/math/copysignf.cpp b/libc/src/math/copysignf.cpp new file mode 100644 index 00000000000000..c730dd37b7aef5 --- /dev/null +++ b/libc/src/math/copysignf.cpp @@ -0,0 +1,18 @@ +//===-- Implementation of copysignf function ------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/common.h" +#include "utils/FPUtil/ManipulationFunctions.h" + +namespace __llvm_libc { + +float LLVM_LIBC_ENTRYPOINT(copysignf)(float x, float y) { + return fputil::copysign(x, y); +} + +} // namespace __llvm_libc diff --git a/libc/src/math/copysignf.h b/libc/src/math/copysignf.h new file mode 100644 index 00000000000000..5b7f1132252d67 --- /dev/null +++ b/libc/src/math/copysignf.h @@ -0,0 +1,18 @@ +//===-- Implementation header for copysignf ---------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MATH_COPYSIGNF_H +#define LLVM_LIBC_SRC_MATH_COPYSIGNF_H + +namespace __llvm_libc { + +float copysignf(float x, float y); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_COPYSIGNF_H diff --git a/libc/src/math/frexp.cpp b/libc/src/math/frexp.cpp new file mode 100644 index 00000000000000..8449929347bb2e --- /dev/null +++ b/libc/src/math/frexp.cpp @@ -0,0 +1,18 @@ +//===-- Implementation of frexp function ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/common.h" +#include "utils/FPUtil/ManipulationFunctions.h" + +namespace __llvm_libc { + +double LLVM_LIBC_ENTRYPOINT(frexp)(double x, int *exp) { + return fputil::frexp(x, *exp); +} + +} // namespace __llvm_libc diff --git a/libc/src/math/frexp.h b/libc/src/math/frexp.h new file mode 100644 index 00000000000000..9258243188360b --- /dev/null +++ b/libc/src/math/frexp.h @@ -0,0 +1,18 @@ +//===-- Implementation header for frexp -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MATH_FREXP_H +#define LLVM_LIBC_SRC_MATH_FREXP_H + +namespace __llvm_libc { + +double frexp(double x, int *exp); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_FREXP_H diff --git a/libc/src/math/frexpf.cpp b/libc/src/math/frexpf.cpp new file mode 100644 index 00000000000000..94dac9f5903058 --- /dev/null +++ b/libc/src/math/frexpf.cpp @@ -0,0 +1,18 @@ +//===-- Implementation of frexpf function ---------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/common.h" +#include "utils/FPUtil/ManipulationFunctions.h" + +namespace __llvm_libc { + +float LLVM_LIBC_ENTRYPOINT(frexpf)(float x, int *exp) { + return fputil::frexp(x, *exp); +} + +} // namespace __llvm_libc diff --git a/libc/src/math/frexpf.h b/libc/src/math/frexpf.h new file mode 100644 index 00000000000000..ed303d2c76dd54 --- /dev/null +++ b/libc/src/math/frexpf.h @@ -0,0 +1,18 @@ +//===-- Implementation header for frexpf ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MATH_FREXPF_H +#define LLVM_LIBC_SRC_MATH_FREXPF_H + +namespace __llvm_libc { + +float frexpf(float x, int *exp); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_FREXPF_H diff --git a/libc/src/math/logb.cpp b/libc/src/math/logb.cpp new file mode 100644 index 00000000000000..7475785ebf7e64 --- /dev/null +++ b/libc/src/math/logb.cpp @@ -0,0 +1,16 @@ +//===-- Implementation of logb function -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/common.h" +#include "utils/FPUtil/ManipulationFunctions.h" + +namespace __llvm_libc { + +double LLVM_LIBC_ENTRYPOINT(logb)(double x) { return fputil::logb(x); } + +} // namespace __llvm_libc diff --git a/libc/src/math/logb.h b/libc/src/math/logb.h new file mode 100644 index 00000000000000..b875dcd702babe --- /dev/null +++ b/libc/src/math/logb.h @@ -0,0 +1,18 @@ +//===-- Implementation header for logb --------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MATH_LOGB_H +#define LLVM_LIBC_SRC_MATH_LOGB_H + +namespace __llvm_libc { + +double logb(double x); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_LOGB_H diff --git a/libc/src/math/logbf.cpp b/libc/src/math/logbf.cpp new file mode 100644 index 00000000000000..bfe8e8590b97ea --- /dev/null +++ b/libc/src/math/logbf.cpp @@ -0,0 +1,16 @@ +//===-- Implementation of logbf function ---------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/common.h" +#include "utils/FPUtil/ManipulationFunctions.h" + +namespace __llvm_libc { + +float LLVM_LIBC_ENTRYPOINT(logbf)(float x) { return fputil::logb(x); } + +} // namespace __llvm_libc diff --git a/libc/src/math/logbf.h b/libc/src/math/logbf.h new file mode 100644 index 00000000000000..46dcd3c91d628c --- /dev/null +++ b/libc/src/math/logbf.h @@ -0,0 +1,18 @@ +//===-- Implementation header for logbf -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MATH_LOGBF_H +#define LLVM_LIBC_SRC_MATH_LOGBF_H + +namespace __llvm_libc { + +float logbf(float x); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_LOGBF_H diff --git a/libc/src/math/modf.cpp b/libc/src/math/modf.cpp new file mode 100644 index 00000000000000..51466004111b84 --- /dev/null +++ b/libc/src/math/modf.cpp @@ -0,0 +1,18 @@ +//===-- Implementation of modf function -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/common.h" +#include "utils/FPUtil/ManipulationFunctions.h" + +namespace __llvm_libc { + +double LLVM_LIBC_ENTRYPOINT(modf)(double x, double *iptr) { + return fputil::modf(x, *iptr); +} + +} // namespace __llvm_libc diff --git a/libc/src/math/modf.h b/libc/src/math/modf.h new file mode 100644 index 00000000000000..1dc732f6e89aba --- /dev/null +++ b/libc/src/math/modf.h @@ -0,0 +1,18 @@ +//===-- Implementation header for modf --------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MATH_MODF_H +#define LLVM_LIBC_SRC_MATH_MODF_H + +namespace __llvm_libc { + +double modf(double x, double *iptr); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_MODF_H diff --git a/libc/src/math/modff.cpp b/libc/src/math/modff.cpp new file mode 100644 index 00000000000000..4847fe24a9ecf0 --- /dev/null +++ b/libc/src/math/modff.cpp @@ -0,0 +1,18 @@ +//===-- Implementation of modf function -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "src/__support/common.h" +#include "utils/FPUtil/ManipulationFunctions.h" + +namespace __llvm_libc { + +float LLVM_LIBC_ENTRYPOINT(modff)(float x, float *iptr) { + return fputil::modf(x, *iptr); +} + +} // namespace __llvm_libc diff --git a/libc/src/math/modff.h b/libc/src/math/modff.h new file mode 100644 index 00000000000000..21457e0d2e81aa --- /dev/null +++ b/libc/src/math/modff.h @@ -0,0 +1,18 @@ +//===-- Implementation header for modff -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_SRC_MATH_MODFF_H +#define LLVM_LIBC_SRC_MATH_MODFF_H + +namespace __llvm_libc { + +float modff(float x, float *iptr); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_MATH_MODFF_H diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt index c381ea8a2e81e8..ebc03d3452cc7c 100644 --- a/libc/test/src/math/CMakeLists.txt +++ b/libc/test/src/math/CMakeLists.txt @@ -228,3 +228,99 @@ add_math_unittest( libc.src.math.exp2f libc.utils.FPUtil.fputil ) + +add_math_unittest( + copysign_test + SUITE + libc_math_unittests + SRCS + copysign_test.cpp + DEPENDS + libc.include.math + libc.src.math.copysign + libc.utils.FPUtil.fputil +) + +add_math_unittest( + copysignf_test + SUITE + libc_math_unittests + SRCS + copysignf_test.cpp + DEPENDS + libc.include.math + libc.src.math.copysignf + libc.utils.FPUtil.fputil +) + +add_math_unittest( + frexp_test + SUITE + libc_math_unittests + SRCS + frexp_test.cpp + DEPENDS + libc.include.math + libc.src.math.frexp + libc.utils.FPUtil.fputil +) + +add_math_unittest( + frexpf_test + SUITE + libc_math_unittests + SRCS + frexpf_test.cpp + DEPENDS + libc.include.math + libc.src.math.frexpf + libc.utils.FPUtil.fputil +) + +add_math_unittest( + logb_test + SUITE + libc_math_unittests + SRCS + logb_test.cpp + DEPENDS + libc.include.math + libc.src.math.logb + libc.utils.FPUtil.fputil +) + +add_math_unittest( + logbf_test + SUITE + libc_math_unittests + SRCS + logbf_test.cpp + DEPENDS + libc.include.math + libc.src.math.logbf + libc.utils.FPUtil.fputil +) + +add_math_unittest( + modf_test + SUITE + libc_math_unittests + SRCS + modf_test.cpp + DEPENDS + libc.include.math + libc.src.math.modf + libc.utils.FPUtil.fputil +) + +add_math_unittest( + modff_test + SUITE + libc_math_unittests + SRCS + modff_test.cpp + DEPENDS + libc.include.math + libc.src.math.modff + libc.utils.FPUtil.fputil +) diff --git a/libc/test/src/math/copysign_test.cpp b/libc/test/src/math/copysign_test.cpp new file mode 100644 index 00000000000000..e0638467c5b9d0 --- /dev/null +++ b/libc/test/src/math/copysign_test.cpp @@ -0,0 +1,63 @@ +//===-- Unittests for copysign --------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/math.h" +#include "src/math/copysign.h" +#include "utils/FPUtil/BitPatterns.h" +#include "utils/FPUtil/FloatOperations.h" +#include "utils/FPUtil/FloatProperties.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::fputil::valueAsBits; +using __llvm_libc::fputil::valueFromBits; + +using BitPatterns = __llvm_libc::fputil::BitPatterns; +using Properties = __llvm_libc::fputil::FloatProperties; + +TEST(CopySignTest, SpecialNumbers) { + EXPECT_EQ(BitPatterns::aNegativeQuietNaN, + valueAsBits(__llvm_libc::copysign( + valueFromBits(BitPatterns::aQuietNaN), -1.0))); + EXPECT_EQ(BitPatterns::aQuietNaN, + valueAsBits(__llvm_libc::copysign( + valueFromBits(BitPatterns::aNegativeQuietNaN), 1.0))); + + EXPECT_EQ(BitPatterns::aNegativeSignallingNaN, + valueAsBits(__llvm_libc::copysign( + valueFromBits(BitPatterns::aSignallingNaN), -1.0))); + EXPECT_EQ(BitPatterns::aSignallingNaN, + valueAsBits(__llvm_libc::copysign( + valueFromBits(BitPatterns::aNegativeSignallingNaN), 1.0))); + + EXPECT_EQ(BitPatterns::negInf, valueAsBits(__llvm_libc::copysign( + valueFromBits(BitPatterns::inf), -1.0))); + EXPECT_EQ(BitPatterns::inf, valueAsBits(__llvm_libc::copysign( + valueFromBits(BitPatterns::negInf), 1.0))); + + EXPECT_EQ(BitPatterns::negZero, valueAsBits(__llvm_libc::copysign( + valueFromBits(BitPatterns::zero), -1.0))); + EXPECT_EQ(BitPatterns::zero, valueAsBits(__llvm_libc::copysign( + valueFromBits(BitPatterns::negZero), 1.0))); +} + +TEST(CopySignTest, InDoubleRange) { + using BitsType = Properties::BitsType; + constexpr BitsType count = 1000000; + constexpr BitsType step = UINT64_MAX / count; + for (BitsType i = 0, v = 0; i <= count; ++i, v += step) { + double x = valueFromBits(v); + if (isnan(x) || isinf(x) || x == 0) + continue; + + double res1 = __llvm_libc::copysign(x, -x); + ASSERT_TRUE(res1 == -x); + + double res2 = __llvm_libc::copysign(x, x); + ASSERT_TRUE(res2 == x); + } +} diff --git a/libc/test/src/math/copysignf_test.cpp b/libc/test/src/math/copysignf_test.cpp new file mode 100644 index 00000000000000..cb2cf518bde251 --- /dev/null +++ b/libc/test/src/math/copysignf_test.cpp @@ -0,0 +1,65 @@ +//===-- Unittests for copysignf +//--------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/math.h" +#include "src/math/copysignf.h" +#include "utils/FPUtil/BitPatterns.h" +#include "utils/FPUtil/FloatOperations.h" +#include "utils/FPUtil/FloatProperties.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::fputil::valueAsBits; +using __llvm_libc::fputil::valueFromBits; + +using BitPatterns = __llvm_libc::fputil::BitPatterns; +using Properties = __llvm_libc::fputil::FloatProperties; + +TEST(CopySignFTest, SpecialNumbers) { + EXPECT_EQ(BitPatterns::aNegativeQuietNaN, + valueAsBits(__llvm_libc::copysignf( + valueFromBits(BitPatterns::aQuietNaN), -1.0f))); + EXPECT_EQ(BitPatterns::aQuietNaN, + valueAsBits(__llvm_libc::copysignf( + valueFromBits(BitPatterns::aNegativeQuietNaN), 1.0f))); + + EXPECT_EQ(BitPatterns::aNegativeSignallingNaN, + valueAsBits(__llvm_libc::copysignf( + valueFromBits(BitPatterns::aSignallingNaN), -1.0f))); + EXPECT_EQ(BitPatterns::aSignallingNaN, + valueAsBits(__llvm_libc::copysignf( + valueFromBits(BitPatterns::aNegativeSignallingNaN), 1.0f))); + + EXPECT_EQ(BitPatterns::negInf, valueAsBits(__llvm_libc::copysignf( + valueFromBits(BitPatterns::inf), -1.0f))); + EXPECT_EQ(BitPatterns::inf, valueAsBits(__llvm_libc::copysignf( + valueFromBits(BitPatterns::negInf), 1.0f))); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::copysignf(valueFromBits(BitPatterns::zero), + -1.0f))); + EXPECT_EQ(BitPatterns::zero, valueAsBits(__llvm_libc::copysignf( + valueFromBits(BitPatterns::negZero), 1.0f))); +} + +TEST(CopySignFTest, InDoubleRange) { + using BitsType = Properties::BitsType; + constexpr BitsType count = 1000000; + constexpr BitsType step = UINT32_MAX / count; + for (BitsType i = 0, v = 0; i <= count; ++i, v += step) { + float x = valueFromBits(v); + if (isnan(x) || isinf(x) || x == 0) + continue; + + float res1 = __llvm_libc::copysignf(x, -x); + ASSERT_TRUE(res1 == -x); + + float res2 = __llvm_libc::copysignf(x, x); + ASSERT_TRUE(res2 == x); + } +} diff --git a/libc/test/src/math/frexp_test.cpp b/libc/test/src/math/frexp_test.cpp new file mode 100644 index 00000000000000..a7fb953fb4fb87 --- /dev/null +++ b/libc/test/src/math/frexp_test.cpp @@ -0,0 +1,141 @@ +//===-- Unittests for frexp -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/math.h" +#include "src/math/frexp.h" +#include "utils/FPUtil/BitPatterns.h" +#include "utils/FPUtil/FloatOperations.h" +#include "utils/FPUtil/FloatProperties.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::fputil::valueAsBits; +using __llvm_libc::fputil::valueFromBits; + +using BitPatterns = __llvm_libc::fputil::BitPatterns; +using Properties = __llvm_libc::fputil::FloatProperties; + +TEST(FrexpTest, SpecialNumbers) { + int exponent; + + EXPECT_EQ(BitPatterns::aQuietNaN, + valueAsBits(__llvm_libc::frexp( + valueFromBits(BitPatterns::aQuietNaN), &exponent))); + EXPECT_EQ(BitPatterns::aNegativeQuietNaN, + valueAsBits(__llvm_libc::frexp( + valueFromBits(BitPatterns::aNegativeQuietNaN), &exponent))); + + EXPECT_EQ(BitPatterns::aSignallingNaN, + valueAsBits(__llvm_libc::frexp( + valueFromBits(BitPatterns::aSignallingNaN), &exponent))); + EXPECT_EQ( + BitPatterns::aNegativeSignallingNaN, + valueAsBits(__llvm_libc::frexp( + valueFromBits(BitPatterns::aNegativeSignallingNaN), &exponent))); + + EXPECT_EQ(BitPatterns::inf, valueAsBits(__llvm_libc::frexp( + valueFromBits(BitPatterns::inf), &exponent))); + EXPECT_EQ(BitPatterns::negInf, + valueAsBits(__llvm_libc::frexp(valueFromBits(BitPatterns::negInf), + &exponent))); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::frexp(valueFromBits(BitPatterns::zero), + &exponent))); + EXPECT_EQ(exponent, 0); + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::frexp(valueFromBits(BitPatterns::negZero), + &exponent))); + EXPECT_EQ(exponent, 0); +} + +TEST(FrexpTest, PowersOfTwo) { + int exponent; + + EXPECT_EQ(valueAsBits(0.5), valueAsBits(__llvm_libc::frexp(1.0, &exponent))); + EXPECT_EQ(exponent, 1); + EXPECT_EQ(valueAsBits(-0.5), + valueAsBits(__llvm_libc::frexp(-1.0, &exponent))); + EXPECT_EQ(exponent, 1); + + EXPECT_EQ(valueAsBits(0.5), valueAsBits(__llvm_libc::frexp(2.0, &exponent))); + EXPECT_EQ(exponent, 2); + EXPECT_EQ(valueAsBits(-0.5), + valueAsBits(__llvm_libc::frexp(-2.0, &exponent))); + EXPECT_EQ(exponent, 2); + + EXPECT_EQ(valueAsBits(0.5), valueAsBits(__llvm_libc::frexp(4.0, &exponent))); + EXPECT_EQ(exponent, 3); + EXPECT_EQ(valueAsBits(-0.5), + valueAsBits(__llvm_libc::frexp(-4.0, &exponent))); + EXPECT_EQ(exponent, 3); + + EXPECT_EQ(valueAsBits(0.5), valueAsBits(__llvm_libc::frexp(8.0, &exponent))); + EXPECT_EQ(exponent, 4); + EXPECT_EQ(valueAsBits(-0.5), + valueAsBits(__llvm_libc::frexp(-8.0, &exponent))); + EXPECT_EQ(exponent, 4); + + EXPECT_EQ(valueAsBits(0.5), valueAsBits(__llvm_libc::frexp(16.0, &exponent))); + EXPECT_EQ(exponent, 5); + EXPECT_EQ(valueAsBits(-0.5), + valueAsBits(__llvm_libc::frexp(-16.0, &exponent))); + EXPECT_EQ(exponent, 5); + + EXPECT_EQ(valueAsBits(0.5), valueAsBits(__llvm_libc::frexp(32.0, &exponent))); + EXPECT_EQ(exponent, 6); + EXPECT_EQ(valueAsBits(-0.5), + valueAsBits(__llvm_libc::frexp(-32.0, &exponent))); + EXPECT_EQ(exponent, 6); + + EXPECT_EQ(valueAsBits(0.5), valueAsBits(__llvm_libc::frexp(64.0, &exponent))); + EXPECT_EQ(exponent, 7); + EXPECT_EQ(valueAsBits(-0.5), + valueAsBits(__llvm_libc::frexp(-64.0, &exponent))); + EXPECT_EQ(exponent, 7); +} + +TEST(FrexpTest, SomeIntegers) { + int exponent; + + EXPECT_EQ(valueAsBits(0.75), + valueAsBits(__llvm_libc::frexp(24.0, &exponent))); + EXPECT_EQ(exponent, 5); + EXPECT_EQ(valueAsBits(-0.75), + valueAsBits(__llvm_libc::frexp(-24.0, &exponent))); + EXPECT_EQ(exponent, 5); + + EXPECT_EQ(valueAsBits(0.625), + valueAsBits(__llvm_libc::frexp(40.0, &exponent))); + EXPECT_EQ(exponent, 6); + EXPECT_EQ(valueAsBits(-0.625), + valueAsBits(__llvm_libc::frexp(-40.0, &exponent))); + EXPECT_EQ(exponent, 6); + + EXPECT_EQ(valueAsBits(0.78125), + valueAsBits(__llvm_libc::frexp(800.0, &exponent))); + EXPECT_EQ(exponent, 10); + EXPECT_EQ(valueAsBits(-0.78125), + valueAsBits(__llvm_libc::frexp(-800.0, &exponent))); + EXPECT_EQ(exponent, 10); +} + +TEST(FrexpTest, InDoubleRange) { + using BitsType = Properties::BitsType; + constexpr BitsType count = 1000000; + constexpr BitsType step = UINT64_MAX / count; + for (BitsType i = 0, v = 0; i <= count; ++i, v += step) { + double x = valueFromBits(v); + if (isnan(x) || isinf(x) || x == 0.0) + continue; + int exponent; + double frac = __llvm_libc::frexp(x, &exponent); + + ASSERT_TRUE(__llvm_libc::fputil::abs(frac) < 1.0); + ASSERT_TRUE(__llvm_libc::fputil::abs(frac) >= 0.5); + } +} diff --git a/libc/test/src/math/frexpf_test.cpp b/libc/test/src/math/frexpf_test.cpp new file mode 100644 index 00000000000000..d90532b6343661 --- /dev/null +++ b/libc/test/src/math/frexpf_test.cpp @@ -0,0 +1,150 @@ +//===-- Unittests for frexpf +//-----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/math.h" +#include "src/math/frexpf.h" +#include "utils/FPUtil/BitPatterns.h" +#include "utils/FPUtil/FloatOperations.h" +#include "utils/FPUtil/FloatProperties.h" +#include "utils/MPFRWrapper/MPFRUtils.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::fputil::valueAsBits; +using __llvm_libc::fputil::valueFromBits; + +using BitPatterns = __llvm_libc::fputil::BitPatterns; +using Properties = __llvm_libc::fputil::FloatProperties; + +TEST(FrexpfTest, SpecialNumbers) { + int exponent; + + EXPECT_EQ(BitPatterns::aQuietNaN, + valueAsBits(__llvm_libc::frexpf( + valueFromBits(BitPatterns::aQuietNaN), &exponent))); + EXPECT_EQ(BitPatterns::aNegativeQuietNaN, + valueAsBits(__llvm_libc::frexpf( + valueFromBits(BitPatterns::aNegativeQuietNaN), &exponent))); + + EXPECT_EQ(BitPatterns::aSignallingNaN, + valueAsBits(__llvm_libc::frexpf( + valueFromBits(BitPatterns::aSignallingNaN), &exponent))); + EXPECT_EQ( + BitPatterns::aNegativeSignallingNaN, + valueAsBits(__llvm_libc::frexpf( + valueFromBits(BitPatterns::aNegativeSignallingNaN), &exponent))); + + EXPECT_EQ(BitPatterns::inf, valueAsBits(__llvm_libc::frexpf( + valueFromBits(BitPatterns::inf), &exponent))); + EXPECT_EQ(BitPatterns::negInf, + valueAsBits(__llvm_libc::frexpf(valueFromBits(BitPatterns::negInf), + &exponent))); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::frexpf(valueFromBits(BitPatterns::zero), + &exponent))); + EXPECT_EQ(exponent, 0); + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::frexpf(valueFromBits(BitPatterns::negZero), + &exponent))); + EXPECT_EQ(exponent, 0); +} + +TEST(FrexpfTest, PowersOfTwo) { + int exponent; + + EXPECT_EQ(valueAsBits(0.5f), + valueAsBits(__llvm_libc::frexpf(1.0f, &exponent))); + EXPECT_EQ(exponent, 1); + EXPECT_EQ(valueAsBits(-0.5f), + valueAsBits(__llvm_libc::frexpf(-1.0f, &exponent))); + EXPECT_EQ(exponent, 1); + + EXPECT_EQ(valueAsBits(0.5f), + valueAsBits(__llvm_libc::frexpf(2.0f, &exponent))); + EXPECT_EQ(exponent, 2); + EXPECT_EQ(valueAsBits(-0.5f), + valueAsBits(__llvm_libc::frexpf(-2.0f, &exponent))); + EXPECT_EQ(exponent, 2); + + EXPECT_EQ(valueAsBits(0.5f), + valueAsBits(__llvm_libc::frexpf(4.0f, &exponent))); + EXPECT_EQ(exponent, 3); + EXPECT_EQ(valueAsBits(-0.5f), + valueAsBits(__llvm_libc::frexpf(-4.0f, &exponent))); + EXPECT_EQ(exponent, 3); + + EXPECT_EQ(valueAsBits(0.5f), + valueAsBits(__llvm_libc::frexpf(8.0f, &exponent))); + EXPECT_EQ(exponent, 4); + EXPECT_EQ(valueAsBits(-0.5f), + valueAsBits(__llvm_libc::frexpf(-8.0f, &exponent))); + EXPECT_EQ(exponent, 4); + + EXPECT_EQ(valueAsBits(0.5f), + valueAsBits(__llvm_libc::frexpf(16.0f, &exponent))); + EXPECT_EQ(exponent, 5); + EXPECT_EQ(valueAsBits(-0.5f), + valueAsBits(__llvm_libc::frexpf(-16.0f, &exponent))); + EXPECT_EQ(exponent, 5); + + EXPECT_EQ(valueAsBits(0.5f), + valueAsBits(__llvm_libc::frexpf(32.0f, &exponent))); + EXPECT_EQ(exponent, 6); + EXPECT_EQ(valueAsBits(-0.5f), + valueAsBits(__llvm_libc::frexpf(-32.0f, &exponent))); + EXPECT_EQ(exponent, 6); + + EXPECT_EQ(valueAsBits(0.5f), + valueAsBits(__llvm_libc::frexpf(64.0f, &exponent))); + EXPECT_EQ(exponent, 7); + EXPECT_EQ(valueAsBits(-0.5f), + valueAsBits(__llvm_libc::frexpf(-64.0f, &exponent))); + EXPECT_EQ(exponent, 7); +} + +TEST(FrexpTest, SomeIntegers) { + int exponent; + + EXPECT_EQ(valueAsBits(0.75f), + valueAsBits(__llvm_libc::frexpf(24.0f, &exponent))); + EXPECT_EQ(exponent, 5); + EXPECT_EQ(valueAsBits(-0.75f), + valueAsBits(__llvm_libc::frexpf(-24.0f, &exponent))); + EXPECT_EQ(exponent, 5); + + EXPECT_EQ(valueAsBits(0.625f), + valueAsBits(__llvm_libc::frexpf(40.0f, &exponent))); + EXPECT_EQ(exponent, 6); + EXPECT_EQ(valueAsBits(-0.625f), + valueAsBits(__llvm_libc::frexpf(-40.0f, &exponent))); + EXPECT_EQ(exponent, 6); + + EXPECT_EQ(valueAsBits(0.78125f), + valueAsBits(__llvm_libc::frexpf(800.0f, &exponent))); + EXPECT_EQ(exponent, 10); + EXPECT_EQ(valueAsBits(-0.78125f), + valueAsBits(__llvm_libc::frexpf(-800.0f, &exponent))); + EXPECT_EQ(exponent, 10); +} + +TEST(FrexpfTest, InFloatRange) { + using BitsType = Properties::BitsType; + constexpr BitsType count = 1000000; + constexpr BitsType step = UINT32_MAX / count; + for (BitsType i = 0, v = 0; i <= count; ++i, v += step) { + float x = valueFromBits(v); + if (isnan(x) || isinf(x) || x == 0.0) + continue; + int exponent; + float frac = __llvm_libc::frexpf(x, &exponent); + + ASSERT_TRUE(__llvm_libc::fputil::abs(frac) < 1.0f); + ASSERT_TRUE(__llvm_libc::fputil::abs(frac) >= 0.5f); + } +} diff --git a/libc/test/src/math/logb_test.cpp b/libc/test/src/math/logb_test.cpp new file mode 100644 index 00000000000000..5f8e7538748971 --- /dev/null +++ b/libc/test/src/math/logb_test.cpp @@ -0,0 +1,99 @@ +//===-- Unittests for logb ------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/math.h" +#include "src/math/logb.h" +#include "utils/FPUtil/BitPatterns.h" +#include "utils/FPUtil/FloatOperations.h" +#include "utils/FPUtil/FloatProperties.h" +#include "utils/FPUtil/ManipulationFunctions.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::fputil::valueAsBits; +using __llvm_libc::fputil::valueFromBits; + +using BitPatterns = __llvm_libc::fputil::BitPatterns; +using Properties = __llvm_libc::fputil::FloatProperties; + +TEST(LogbTest, SpecialNumbers) { + EXPECT_EQ( + BitPatterns::aQuietNaN, + valueAsBits(__llvm_libc::logb(valueFromBits(BitPatterns::aQuietNaN)))); + EXPECT_EQ(BitPatterns::aNegativeQuietNaN, + valueAsBits(__llvm_libc::logb( + valueFromBits(BitPatterns::aNegativeQuietNaN)))); + + EXPECT_EQ(BitPatterns::aSignallingNaN, + valueAsBits( + __llvm_libc::logb(valueFromBits(BitPatterns::aSignallingNaN)))); + EXPECT_EQ(BitPatterns::aNegativeSignallingNaN, + valueAsBits(__llvm_libc::logb( + valueFromBits(BitPatterns::aNegativeSignallingNaN)))); + + EXPECT_EQ(BitPatterns::inf, + valueAsBits(__llvm_libc::logb(valueFromBits(BitPatterns::inf)))); + EXPECT_EQ(BitPatterns::inf, + valueAsBits(__llvm_libc::logb(valueFromBits(BitPatterns::negInf)))); + + EXPECT_EQ(BitPatterns::negInf, + valueAsBits(__llvm_libc::logb(valueFromBits(BitPatterns::zero)))); + EXPECT_EQ(BitPatterns::negInf, valueAsBits(__llvm_libc::logb( + valueFromBits(BitPatterns::negZero)))); +} + +TEST(LogbTest, PowersOfTwo) { + EXPECT_EQ(valueAsBits(0.0), valueAsBits(__llvm_libc::logb(1.0))); + EXPECT_EQ(valueAsBits(0.0), valueAsBits(__llvm_libc::logb(-1.0))); + + EXPECT_EQ(valueAsBits(1.0), valueAsBits(__llvm_libc::logb(2.0))); + EXPECT_EQ(valueAsBits(1.0), valueAsBits(__llvm_libc::logb(-2.0))); + + EXPECT_EQ(valueAsBits(2.0), valueAsBits(__llvm_libc::logb(4.0))); + EXPECT_EQ(valueAsBits(2.0), valueAsBits(__llvm_libc::logb(-4.0))); + + EXPECT_EQ(valueAsBits(3.0), valueAsBits(__llvm_libc::logb(8.0))); + EXPECT_EQ(valueAsBits(3.0), valueAsBits(__llvm_libc::logb(-8.0))); + + EXPECT_EQ(valueAsBits(4.0), valueAsBits(__llvm_libc::logb(16.0))); + EXPECT_EQ(valueAsBits(4.0), valueAsBits(__llvm_libc::logb(-16.0))); + + EXPECT_EQ(valueAsBits(5.0), valueAsBits(__llvm_libc::logb(32.0))); + EXPECT_EQ(valueAsBits(5.0), valueAsBits(__llvm_libc::logb(-32.0))); +} + +TEST(LogbTest, SomeIntegers) { + EXPECT_EQ(valueAsBits(1.0), valueAsBits(__llvm_libc::logb(3.0))); + EXPECT_EQ(valueAsBits(1.0), valueAsBits(__llvm_libc::logb(-3.0))); + + EXPECT_EQ(valueAsBits(2.0), valueAsBits(__llvm_libc::logb(7.0))); + EXPECT_EQ(valueAsBits(2.0), valueAsBits(__llvm_libc::logb(-7.0))); + + EXPECT_EQ(valueAsBits(3.0), valueAsBits(__llvm_libc::logb(10.0))); + EXPECT_EQ(valueAsBits(3.0), valueAsBits(__llvm_libc::logb(-10.0))); + + EXPECT_EQ(valueAsBits(4.0), valueAsBits(__llvm_libc::logb(31.0))); + EXPECT_EQ(valueAsBits(4.0), valueAsBits(__llvm_libc::logb(-31.0))); + + EXPECT_EQ(valueAsBits(5.0), valueAsBits(__llvm_libc::logb(55.0))); + EXPECT_EQ(valueAsBits(5.0), valueAsBits(__llvm_libc::logb(-55.0))); +} + +TEST(LogbTest, InDoubleRange) { + using BitsType = Properties::BitsType; + constexpr BitsType count = 10000000; + constexpr BitsType step = UINT64_MAX / count; + for (BitsType i = 0, v = 0; i <= count; ++i, v += step) { + double x = valueFromBits(v); + if (isnan(x) || isinf(x) || x == 0.0) + continue; + + int exponent; + __llvm_libc::fputil::frexp(x, exponent); + ASSERT_TRUE(double(exponent) == __llvm_libc::logb(x) + 1.0); + } +} diff --git a/libc/test/src/math/logbf_test.cpp b/libc/test/src/math/logbf_test.cpp new file mode 100644 index 00000000000000..7cd690ce38690b --- /dev/null +++ b/libc/test/src/math/logbf_test.cpp @@ -0,0 +1,99 @@ +//===-- Unittests for logbf -----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/math.h" +#include "src/math/logbf.h" +#include "utils/FPUtil/BitPatterns.h" +#include "utils/FPUtil/FloatOperations.h" +#include "utils/FPUtil/FloatProperties.h" +#include "utils/FPUtil/ManipulationFunctions.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::fputil::valueAsBits; +using __llvm_libc::fputil::valueFromBits; + +using BitPatterns = __llvm_libc::fputil::BitPatterns; +using Properties = __llvm_libc::fputil::FloatProperties; + +TEST(LogbfTest, SpecialNumbers) { + EXPECT_EQ( + BitPatterns::aQuietNaN, + valueAsBits(__llvm_libc::logbf(valueFromBits(BitPatterns::aQuietNaN)))); + EXPECT_EQ(BitPatterns::aNegativeQuietNaN, + valueAsBits(__llvm_libc::logbf( + valueFromBits(BitPatterns::aNegativeQuietNaN)))); + + EXPECT_EQ(BitPatterns::aSignallingNaN, + valueAsBits(__llvm_libc::logbf( + valueFromBits(BitPatterns::aSignallingNaN)))); + EXPECT_EQ(BitPatterns::aNegativeSignallingNaN, + valueAsBits(__llvm_libc::logbf( + valueFromBits(BitPatterns::aNegativeSignallingNaN)))); + + EXPECT_EQ(BitPatterns::inf, + valueAsBits(__llvm_libc::logbf(valueFromBits(BitPatterns::inf)))); + EXPECT_EQ(BitPatterns::inf, valueAsBits(__llvm_libc::logbf( + valueFromBits(BitPatterns::negInf)))); + + EXPECT_EQ(BitPatterns::negInf, + valueAsBits(__llvm_libc::logbf(valueFromBits(BitPatterns::zero)))); + EXPECT_EQ(BitPatterns::negInf, valueAsBits(__llvm_libc::logbf( + valueFromBits(BitPatterns::negZero)))); +} + +TEST(LogbfTest, PowersOfTwo) { + EXPECT_EQ(valueAsBits(0.0f), valueAsBits(__llvm_libc::logbf(1.0f))); + EXPECT_EQ(valueAsBits(0.0f), valueAsBits(__llvm_libc::logbf(-1.0f))); + + EXPECT_EQ(valueAsBits(1.0f), valueAsBits(__llvm_libc::logbf(2.0f))); + EXPECT_EQ(valueAsBits(1.0f), valueAsBits(__llvm_libc::logbf(-2.0f))); + + EXPECT_EQ(valueAsBits(2.0f), valueAsBits(__llvm_libc::logbf(4.0f))); + EXPECT_EQ(valueAsBits(2.0f), valueAsBits(__llvm_libc::logbf(-4.0f))); + + EXPECT_EQ(valueAsBits(3.0f), valueAsBits(__llvm_libc::logbf(8.0f))); + EXPECT_EQ(valueAsBits(3.0f), valueAsBits(__llvm_libc::logbf(-8.0f))); + + EXPECT_EQ(valueAsBits(4.0f), valueAsBits(__llvm_libc::logbf(16.0f))); + EXPECT_EQ(valueAsBits(4.0f), valueAsBits(__llvm_libc::logbf(-16.0f))); + + EXPECT_EQ(valueAsBits(5.0f), valueAsBits(__llvm_libc::logbf(32.0f))); + EXPECT_EQ(valueAsBits(5.0f), valueAsBits(__llvm_libc::logbf(-32.0f))); +} + +TEST(LogbTest, SomeIntegers) { + EXPECT_EQ(valueAsBits(1.0f), valueAsBits(__llvm_libc::logbf(3.0f))); + EXPECT_EQ(valueAsBits(1.0f), valueAsBits(__llvm_libc::logbf(-3.0f))); + + EXPECT_EQ(valueAsBits(2.0f), valueAsBits(__llvm_libc::logbf(7.0f))); + EXPECT_EQ(valueAsBits(2.0f), valueAsBits(__llvm_libc::logbf(-7.0f))); + + EXPECT_EQ(valueAsBits(3.0f), valueAsBits(__llvm_libc::logbf(10.0f))); + EXPECT_EQ(valueAsBits(3.0f), valueAsBits(__llvm_libc::logbf(-10.0f))); + + EXPECT_EQ(valueAsBits(4.0f), valueAsBits(__llvm_libc::logbf(31.0f))); + EXPECT_EQ(valueAsBits(4.0f), valueAsBits(__llvm_libc::logbf(-31.0f))); + + EXPECT_EQ(valueAsBits(5.0f), valueAsBits(__llvm_libc::logbf(55.0f))); + EXPECT_EQ(valueAsBits(5.0f), valueAsBits(__llvm_libc::logbf(-55.0f))); +} + +TEST(LogbfTest, InDoubleRange) { + using BitsType = Properties::BitsType; + constexpr BitsType count = 10000000; + constexpr BitsType step = UINT32_MAX / count; + for (BitsType i = 0, v = 0; i <= count; ++i, v += step) { + float x = valueFromBits(v); + if (isnan(x) || isinf(x) || x == 0.0) + continue; + + int exponent; + __llvm_libc::fputil::frexp(x, exponent); + ASSERT_TRUE(float(exponent) == __llvm_libc::logbf(x) + 1.0); + } +} diff --git a/libc/test/src/math/modf_test.cpp b/libc/test/src/math/modf_test.cpp new file mode 100644 index 00000000000000..fa4436d641511f --- /dev/null +++ b/libc/test/src/math/modf_test.cpp @@ -0,0 +1,130 @@ +//===-- Unittests for modf ------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/math.h" +#include "src/math/modf.h" +#include "utils/FPUtil/BitPatterns.h" +#include "utils/FPUtil/FloatOperations.h" +#include "utils/FPUtil/FloatProperties.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::fputil::valueAsBits; +using __llvm_libc::fputil::valueFromBits; + +using BitPatterns = __llvm_libc::fputil::BitPatterns; +using Properties = __llvm_libc::fputil::FloatProperties; + +TEST(ModfTest, SpecialNumbers) { + double integral; + + EXPECT_EQ(BitPatterns::aQuietNaN, + valueAsBits(__llvm_libc::modf(valueFromBits(BitPatterns::aQuietNaN), + &integral))); + EXPECT_EQ(BitPatterns::aNegativeQuietNaN, + valueAsBits(__llvm_libc::modf( + valueFromBits(BitPatterns::aNegativeQuietNaN), &integral))); + + EXPECT_EQ(BitPatterns::aSignallingNaN, + valueAsBits(__llvm_libc::modf( + valueFromBits(BitPatterns::aSignallingNaN), &integral))); + EXPECT_EQ( + BitPatterns::aNegativeSignallingNaN, + valueAsBits(__llvm_libc::modf( + valueFromBits(BitPatterns::aNegativeSignallingNaN), &integral))); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits( + __llvm_libc::modf(valueFromBits(BitPatterns::inf), &integral))); + EXPECT_EQ(valueAsBits(integral), BitPatterns::inf); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modf(valueFromBits(BitPatterns::negInf), + &integral))); + EXPECT_EQ(valueAsBits(integral), BitPatterns::negInf); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::modf(valueFromBits(BitPatterns::zero), + &integral))); + EXPECT_EQ(valueAsBits(integral), BitPatterns::zero); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modf(valueFromBits(BitPatterns::negZero), + &integral))); + EXPECT_EQ(valueAsBits(integral), BitPatterns::negZero); +} + +TEST(ModfTest, Integers) { + double integral; + + EXPECT_EQ(BitPatterns::zero, valueAsBits(__llvm_libc::modf(1.0, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(1.0)); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modf(-1.0, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-1.0)); + + EXPECT_EQ(BitPatterns::zero, valueAsBits(__llvm_libc::modf(10.0, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(10.0)); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modf(-10.0, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-10.0)); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::modf(12345.0, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(12345.0)); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modf(-12345.0, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-12345.0)); +} + +TEST(ModfTest, Fractions) { + double integral; + + EXPECT_EQ(valueAsBits(0.5), valueAsBits(__llvm_libc::modf(1.5, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(1.0)); + + EXPECT_EQ(valueAsBits(-0.5), valueAsBits(__llvm_libc::modf(-1.5, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-1.0)); + + EXPECT_EQ(valueAsBits(0.75), + valueAsBits(__llvm_libc::modf(10.75, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(10.0)); + + EXPECT_EQ(valueAsBits(-0.75), + valueAsBits(__llvm_libc::modf(-10.75, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-10.0)); + + EXPECT_EQ(valueAsBits(0.125), + valueAsBits(__llvm_libc::modf(100.125, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(100.0)); + + EXPECT_EQ(valueAsBits(-0.125), + valueAsBits(__llvm_libc::modf(-100.125, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-100.0)); +} + +TEST(ModfTest, InDoubleRange) { + using BitsType = Properties::BitsType; + constexpr BitsType count = 10000000; + constexpr BitsType step = UINT64_MAX / count; + for (BitsType i = 0, v = 0; i <= count; ++i, v += step) { + double x = valueFromBits(v); + if (isnan(x) || isinf(x) || x == 0.0) { + // These conditions have been tested in other tests. + continue; + } + + double integral; + double frac = __llvm_libc::modf(x, &integral); + ASSERT_TRUE(__llvm_libc::fputil::abs(frac) < 1.0); + ASSERT_TRUE(__llvm_libc::fputil::trunc(x) == integral); + ASSERT_TRUE(integral + frac == x); + } +} diff --git a/libc/test/src/math/modff_test.cpp b/libc/test/src/math/modff_test.cpp new file mode 100644 index 00000000000000..db3b7c801212a0 --- /dev/null +++ b/libc/test/src/math/modff_test.cpp @@ -0,0 +1,135 @@ +//===-- Unittests for modfff +//-----------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "include/math.h" +#include "src/math/modff.h" +#include "utils/FPUtil/BitPatterns.h" +#include "utils/FPUtil/FloatOperations.h" +#include "utils/FPUtil/FloatProperties.h" +#include "utils/UnitTest/Test.h" + +using __llvm_libc::fputil::valueAsBits; +using __llvm_libc::fputil::valueFromBits; + +using BitPatterns = __llvm_libc::fputil::BitPatterns; +using Properties = __llvm_libc::fputil::FloatProperties; + +TEST(ModffTest, SpecialNumbers) { + float integral; + + EXPECT_EQ(BitPatterns::aQuietNaN, + valueAsBits(__llvm_libc::modff( + valueFromBits(BitPatterns::aQuietNaN), &integral))); + EXPECT_EQ(BitPatterns::aNegativeQuietNaN, + valueAsBits(__llvm_libc::modff( + valueFromBits(BitPatterns::aNegativeQuietNaN), &integral))); + + EXPECT_EQ(BitPatterns::aSignallingNaN, + valueAsBits(__llvm_libc::modff( + valueFromBits(BitPatterns::aSignallingNaN), &integral))); + EXPECT_EQ( + BitPatterns::aNegativeSignallingNaN, + valueAsBits(__llvm_libc::modff( + valueFromBits(BitPatterns::aNegativeSignallingNaN), &integral))); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::modff(valueFromBits(BitPatterns::inf), + &integral))); + EXPECT_EQ(valueAsBits(integral), BitPatterns::inf); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modff(valueFromBits(BitPatterns::negInf), + &integral))); + EXPECT_EQ(valueAsBits(integral), BitPatterns::negInf); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::modff(valueFromBits(BitPatterns::zero), + &integral))); + EXPECT_EQ(valueAsBits(integral), BitPatterns::zero); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modff(valueFromBits(BitPatterns::negZero), + &integral))); + EXPECT_EQ(valueAsBits(integral), BitPatterns::negZero); +} + +TEST(ModffTest, Integers) { + float integral; + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::modff(1.0f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(1.0f)); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modff(-1.0f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-1.0f)); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::modff(10.0f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(10.0f)); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modff(-10.0f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-10.0f)); + + EXPECT_EQ(BitPatterns::zero, + valueAsBits(__llvm_libc::modff(12345.0f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(12345.0f)); + + EXPECT_EQ(BitPatterns::negZero, + valueAsBits(__llvm_libc::modff(-12345.0f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-12345.0f)); +} + +TEST(ModfTest, Fractions) { + float integral; + + EXPECT_EQ(valueAsBits(0.5f), + valueAsBits(__llvm_libc::modff(1.5f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(1.0f)); + + EXPECT_EQ(valueAsBits(-0.5f), + valueAsBits(__llvm_libc::modff(-1.5f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-1.0f)); + + EXPECT_EQ(valueAsBits(0.75f), + valueAsBits(__llvm_libc::modff(10.75f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(10.0f)); + + EXPECT_EQ(valueAsBits(-0.75f), + valueAsBits(__llvm_libc::modff(-10.75f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-10.0f)); + + EXPECT_EQ(valueAsBits(0.125f), + valueAsBits(__llvm_libc::modff(100.125f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(100.0f)); + + EXPECT_EQ(valueAsBits(-0.125f), + valueAsBits(__llvm_libc::modff(-100.125f, &integral))); + EXPECT_EQ(valueAsBits(integral), valueAsBits(-100.0f)); +} + +TEST(ModffTest, InDoubleRange) { + using BitsType = Properties::BitsType; + constexpr BitsType count = 10000000; + constexpr BitsType step = UINT32_MAX / count; + for (BitsType i = 0, v = 0; i <= count; ++i, v += step) { + float x = valueFromBits(v); + if (isnan(x) || isinf(x) || x == 0.0f) { + // These conditions have been tested in other tests. + continue; + } + + float integral; + float frac = __llvm_libc::modff(x, &integral); + ASSERT_TRUE(__llvm_libc::fputil::abs(frac) < 1.0f); + ASSERT_TRUE(__llvm_libc::fputil::trunc(x) == integral); + ASSERT_TRUE(integral + frac == x); + } +} diff --git a/libc/utils/FPUtil/BitPatterns.h b/libc/utils/FPUtil/BitPatterns.h index 35c58a43d9068d..439ccbcdcb92be 100644 --- a/libc/utils/FPUtil/BitPatterns.h +++ b/libc/utils/FPUtil/BitPatterns.h @@ -11,6 +11,12 @@ #include "FloatProperties.h" +#include + +static_assert( + FLT_RADIX == 2, + "LLVM libc only supports radix 2 IEEE 754 floating point formats."); + namespace __llvm_libc { namespace fputil { diff --git a/libc/utils/FPUtil/CMakeLists.txt b/libc/utils/FPUtil/CMakeLists.txt index b50ede179fcf23..21013ee1771f7d 100644 --- a/libc/utils/FPUtil/CMakeLists.txt +++ b/libc/utils/FPUtil/CMakeLists.txt @@ -4,6 +4,7 @@ add_header_library( BitPatterns.h FloatOperations.h FloatProperties.h + ManipulationFunctions.h DEPS libc.utils.CPP.standalone_cpp ) diff --git a/libc/utils/FPUtil/FloatOperations.h b/libc/utils/FPUtil/FloatOperations.h index d841237176a5cc..b599aad37f4138 100644 --- a/libc/utils/FPUtil/FloatOperations.h +++ b/libc/utils/FPUtil/FloatOperations.h @@ -57,26 +57,30 @@ static inline int getExponent(T x) { return getExponentFromBits(valueAsBits(x)); } +template static inline bool bitsAreInf(BitsType bits) { + using FPType = typename FloatType::Type; + return ((bits & BitPatterns::inf) == BitPatterns::inf) && + ((bits & FloatProperties::mantissaMask) == 0); +} + // Return true if x is infinity (positive or negative.) template ::Value, int> = 0> static inline bool isInf(T x) { - using Properties = FloatProperties; - using BitsType = typename FloatProperties::BitsType; - BitsType bits = valueAsBits(x); - return ((bits & BitPatterns::inf) == BitPatterns::inf) && - ((bits & Properties::mantissaMask) == 0); + return bitsAreInf(valueAsBits(x)); +} + +template static inline bool bitsAreNaN(BitsType bits) { + using FPType = typename FloatType::Type; + return ((bits & BitPatterns::inf) == BitPatterns::inf) && + ((bits & FloatProperties::mantissaMask) != 0); } // Return true if x is a NAN (quiet or signalling.) template ::Value, int> = 0> static inline bool isNaN(T x) { - using Properties = FloatProperties; - using BitsType = typename FloatProperties::BitsType; - BitsType bits = valueAsBits(x); - return ((bits & BitPatterns::inf) == BitPatterns::inf) && - ((bits & Properties::mantissaMask) != 0); + return bitsAreNaN(valueAsBits(x)); } template static inline bool bitsAreInfOrNaN(BitsType bits) { diff --git a/libc/utils/FPUtil/ManipulationFunctions.h b/libc/utils/FPUtil/ManipulationFunctions.h new file mode 100644 index 00000000000000..a59c0a7e4cf961 --- /dev/null +++ b/libc/utils/FPUtil/ManipulationFunctions.h @@ -0,0 +1,102 @@ +//===-- Common operations on floating point numbers -------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "BitPatterns.h" +#include "FloatOperations.h" +#include "FloatProperties.h" + +#include "utils/CPP/TypeTraits.h" + +#ifndef LLVM_LIBC_UTILS_FPUTIL_MANIPULATION_FUNCTIONS_H +#define LLVM_LIBC_UTILS_FPUTIL_MANIPULATION_FUNCTIONS_H + +namespace __llvm_libc { +namespace fputil { + +template ::Value, int> = 0> +static inline T frexp(T x, int &exp) { + using Properties = FloatProperties; + using BitsType = typename Properties::BitsType; + + auto bits = valueAsBits(x); + if (bitsAreInfOrNaN(bits)) + return x; + if (bitsAreZero(bits)) { + exp = 0; + return x; + } + + exp = getExponentFromBits(bits) + 1; + + static constexpr BitsType resultExponent = + Properties::exponentOffset - BitsType(1); + // Capture the sign and mantissa part. + bits &= (Properties::mantissaMask | Properties::signMask); + // Insert the new exponent. + bits |= (resultExponent << Properties::mantissaWidth); + + return valueFromBits(bits); +} + +template ::Value, int> = 0> +static inline T modf(T x, T &iptr) { + auto bits = valueAsBits(x); + if (bitsAreZero(bits) || bitsAreNaN(bits)) { + iptr = x; + return x; + } else if (bitsAreInf(bits)) { + iptr = x; + return bits & FloatProperties::signMask + ? valueFromBits(BitPatterns::negZero) + : valueFromBits(BitPatterns::zero); + } else { + iptr = trunc(x); + if (x == iptr) { + // If x is already an integer value, then return zero with the right + // sign. + return bits & FloatProperties::signMask + ? valueFromBits(BitPatterns::negZero) + : valueFromBits(BitPatterns::zero); + } else { + return x - iptr; + } + } +} + +template ::Value, int> = 0> +static inline T copysign(T x, T y) { + constexpr auto signMask = FloatProperties::signMask; + auto xbits = valueAsBits(x); + auto ybits = valueAsBits(y); + return valueFromBits((xbits & ~signMask) | (ybits & signMask)); +} + +template ::Value, int> = 0> +static inline T logb(T x) { + auto bits = valueAsBits(x); + if (bitsAreZero(bits)) { + // TODO(Floating point exception): Raise div-by-zero exception. + // TODO(errno): POSIX requires setting errno to ERANGE. + return valueFromBits(BitPatterns::negInf); + } else if (bitsAreInf(bits)) { + return valueFromBits(BitPatterns::inf); + } else if (bitsAreNaN(bits)) { + return x; + } else { + return getExponentFromBits(bits); + } +} + +} // namespace fputil +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_FPUTIL_MANIPULATION_FUNCTIONS_H From a06f000326e3362ffb5634957447dd434abab0f9 Mon Sep 17 00:00:00 2001 From: Bruno Ricci Date: Thu, 11 Jun 2020 17:24:04 +0100 Subject: [PATCH 08/12] [clang][NFC] Remove two hard-coded lists of ArrayTypeTrait and ExpressionTrait These two were missed in 78e636b3f2f0b0487130b31fade4f95ab179a18c. --- clang/lib/Parse/ParseExprCXX.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index d7947777977ae4..48277dc854663b 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -3649,18 +3649,24 @@ case tok::kw_ ## Spelling: return BTT_ ## Name; } static ArrayTypeTrait ArrayTypeTraitFromTokKind(tok::TokenKind kind) { - switch(kind) { - default: llvm_unreachable("Not a known binary type trait"); - case tok::kw___array_rank: return ATT_ArrayRank; - case tok::kw___array_extent: return ATT_ArrayExtent; + switch (kind) { + default: + llvm_unreachable("Not a known array type trait"); +#define ARRAY_TYPE_TRAIT(Spelling, Name, Key) \ + case tok::kw_##Spelling: \ + return ATT_##Name; +#include "clang/Basic/TokenKinds.def" } } static ExpressionTrait ExpressionTraitFromTokKind(tok::TokenKind kind) { - switch(kind) { - default: llvm_unreachable("Not a known unary expression trait."); - case tok::kw___is_lvalue_expr: return ET_IsLValueExpr; - case tok::kw___is_rvalue_expr: return ET_IsRValueExpr; + switch (kind) { + default: + llvm_unreachable("Not a known unary expression trait."); +#define EXPRESSION_TRAIT(Spelling, Name, Key) \ + case tok::kw_##Spelling: \ + return ET_##Name; +#include "clang/Basic/TokenKinds.def" } } From efb0413a5cf9b1481c9b6169c8685f8d71f6de84 Mon Sep 17 00:00:00 2001 From: Bruno Ricci Date: Thu, 11 Jun 2020 17:29:15 +0100 Subject: [PATCH 09/12] [clang][NFC] Assert that the enumerator value of {Type,ArrayType,UnaryExprOrType,Expression}Traits is valid and does not overflow in the bit-field for its storage in more places. This is a follow-up to 78e636b3f2f0b0487130b31fade4f95ab179a18c. NFC. --- clang/include/clang/AST/Expr.h | 11 ++++++++++- clang/include/clang/AST/ExprCXX.h | 4 ++++ clang/lib/AST/Expr.cpp | 3 +++ clang/lib/AST/ExprCXX.cpp | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 670c0fe80b4e1f..d31f582264b50f 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -2509,7 +2509,11 @@ class UnaryExprOrTypeTraitExpr : public Expr { SourceLocation rp) : Expr(UnaryExprOrTypeTraitExprClass, resultType, VK_RValue, OK_Ordinary), OpLoc(op), RParenLoc(rp) { + assert(ExprKind <= UETT_Last && "invalid enum value!"); UnaryExprOrTypeTraitExprBits.Kind = ExprKind; + assert(static_cast(ExprKind) == + UnaryExprOrTypeTraitExprBits.Kind && + "UnaryExprOrTypeTraitExprBits.Kind overflow!"); UnaryExprOrTypeTraitExprBits.IsType = true; Argument.Ty = TInfo; setDependence(computeDependence(this)); @@ -2526,7 +2530,12 @@ class UnaryExprOrTypeTraitExpr : public Expr { UnaryExprOrTypeTrait getKind() const { return static_cast(UnaryExprOrTypeTraitExprBits.Kind); } - void setKind(UnaryExprOrTypeTrait K) { UnaryExprOrTypeTraitExprBits.Kind = K;} + void setKind(UnaryExprOrTypeTrait K) { + assert(K <= UETT_Last && "invalid enum value!"); + UnaryExprOrTypeTraitExprBits.Kind = K; + assert(static_cast(K) == UnaryExprOrTypeTraitExprBits.Kind && + "UnaryExprOrTypeTraitExprBits.Kind overflow!"); + } bool isArgumentType() const { return UnaryExprOrTypeTraitExprBits.IsType; } QualType getArgumentType() const { diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index 379f762275c63d..82036a295002cf 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -2749,6 +2749,8 @@ class ArrayTypeTraitExpr : public Expr { : Expr(ArrayTypeTraitExprClass, ty, VK_RValue, OK_Ordinary), ATT(att), Value(value), Dimension(dimension), Loc(loc), RParen(rparen), QueriedType(queried) { + assert(att <= ATT_Last && "invalid enum value!"); + assert(static_cast(att) == ATT && "ATT overflow!"); setDependence(computeDependence(this)); } @@ -2813,6 +2815,8 @@ class ExpressionTraitExpr : public Expr { : Expr(ExpressionTraitExprClass, resultType, VK_RValue, OK_Ordinary), ET(et), Value(value), Loc(loc), RParen(rparen), QueriedExpression(queried) { + assert(et <= ET_Last && "invalid enum value!"); + assert(static_cast(et) == ET && "ET overflow!"); setDependence(computeDependence(this)); } diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index 71cc159d06fb61..89eb8e9c022038 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -1535,7 +1535,10 @@ UnaryExprOrTypeTraitExpr::UnaryExprOrTypeTraitExpr( SourceLocation op, SourceLocation rp) : Expr(UnaryExprOrTypeTraitExprClass, resultType, VK_RValue, OK_Ordinary), OpLoc(op), RParenLoc(rp) { + assert(ExprKind <= UETT_Last && "invalid enum value!"); UnaryExprOrTypeTraitExprBits.Kind = ExprKind; + assert(static_cast(ExprKind) == UnaryExprOrTypeTraitExprBits.Kind && + "UnaryExprOrTypeTraitExprBits.Kind overflow!"); UnaryExprOrTypeTraitExprBits.IsType = false; Argument.Ex = E; setDependence(computeDependence(this)); diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index 9d285550ef906b..9d4df28c747395 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -1579,6 +1579,7 @@ TypeTraitExpr::TypeTraitExpr(QualType T, SourceLocation Loc, TypeTrait Kind, SourceLocation RParenLoc, bool Value) : Expr(TypeTraitExprClass, T, VK_RValue, OK_Ordinary), Loc(Loc), RParenLoc(RParenLoc) { + assert(Kind <= TT_Last && "invalid enum value!"); TypeTraitExprBits.Kind = Kind; assert(static_cast(Kind) == TypeTraitExprBits.Kind && "TypeTraitExprBits.Kind overflow!"); From a9250c281a875d91fb5dd1c8f4ad8ee4ff61b75d Mon Sep 17 00:00:00 2001 From: Bruno Ricci Date: Thu, 11 Jun 2020 17:36:28 +0100 Subject: [PATCH 10/12] [clang] TextNodeDumper: Dump the trait spelling of {Type,ArrayType,Expression}TraitExpr nodes using the new helper functions introduced in 78e636b3f2f0b0487130b31fade4f95ab179a18c. --- clang/include/clang/AST/TextNodeDumper.h | 3 ++ clang/lib/AST/TextNodeDumper.cpp | 12 ++++++ clang/test/AST/ast-dump-traits.cpp | 55 ++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 clang/test/AST/ast-dump-traits.cpp diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h index 4636c8ef65d365..b069bd09287a8b 100644 --- a/clang/include/clang/AST/TextNodeDumper.h +++ b/clang/include/clang/AST/TextNodeDumper.h @@ -259,6 +259,9 @@ class TextNodeDumper void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *Node); void VisitCXXNewExpr(const CXXNewExpr *Node); void VisitCXXDeleteExpr(const CXXDeleteExpr *Node); + void VisitTypeTraitExpr(const TypeTraitExpr *Node); + void VisitArrayTypeTraitExpr(const ArrayTypeTraitExpr *Node); + void VisitExpressionTraitExpr(const ExpressionTraitExpr *Node); void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *Node); void VisitExprWithCleanups(const ExprWithCleanups *Node); void VisitUnresolvedLookupExpr(const UnresolvedLookupExpr *Node); diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp index 609a9d7ac2df62..7007aa833f3f91 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -949,6 +949,18 @@ void TextNodeDumper::VisitCXXDeleteExpr(const CXXDeleteExpr *Node) { } } +void TextNodeDumper::VisitTypeTraitExpr(const TypeTraitExpr *Node) { + OS << " " << getTraitSpelling(Node->getTrait()); +} + +void TextNodeDumper::VisitArrayTypeTraitExpr(const ArrayTypeTraitExpr *Node) { + OS << " " << getTraitSpelling(Node->getTrait()); +} + +void TextNodeDumper::VisitExpressionTraitExpr(const ExpressionTraitExpr *Node) { + OS << " " << getTraitSpelling(Node->getTrait()); +} + void TextNodeDumper::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *Node) { if (const ValueDecl *VD = Node->getExtendingDecl()) { diff --git a/clang/test/AST/ast-dump-traits.cpp b/clang/test/AST/ast-dump-traits.cpp new file mode 100644 index 00000000000000..92931e2ac67a34 --- /dev/null +++ b/clang/test/AST/ast-dump-traits.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ast-dump %s | FileCheck -strict-whitespace %s + +void test_type_trait() { + // An unary type trait. + enum E {}; + (void) __is_enum(E); + // A binary type trait. + (void) __is_same(int ,float); + // An n-ary type trait. + (void) __is_constructible(int, int, int, int); +} + +void test_array_type_trait() { + // An array type trait. + (void) __array_rank(int[10][20]); +} + +void test_expression_trait() { + // An expression trait. + (void) __is_lvalue_expr(1); +} + +void test_unary_expr_or_type_trait() { + // Some UETTs. + (void) sizeof(int); + (void) alignof(int); + (void) __alignof(int); +} +// CHECK: TranslationUnitDecl {{.*}} <> +// CHECK: |-FunctionDecl {{.*}} <{{.*}}ast-dump-traits.cpp:3:1, line:11:1> line:3:6 test_type_trait 'void ()' +// CHECK-NEXT: | `-CompoundStmt {{.*}} +// CHECK-NEXT: | |-DeclStmt {{.*}} +// CHECK-NEXT: | | `-EnumDecl {{.*}} col:8 referenced E +// CHECK-NEXT: | |-CStyleCastExpr {{.*}} 'void' +// CHECK-NEXT: | | `-TypeTraitExpr {{.*}} 'bool' __is_enum +// CHECK-NEXT: | |-CStyleCastExpr {{.*}} 'void' +// CHECK-NEXT: | | `-TypeTraitExpr {{.*}} 'bool' __is_same +// CHECK-NEXT: | `-CStyleCastExpr {{.*}} 'void' +// CHECK-NEXT: | `-TypeTraitExpr {{.*}} 'bool' __is_constructible +// CHECK-NEXT: |-FunctionDecl {{.*}} line:13:6 test_array_type_trait 'void ()' +// CHECK-NEXT: | `-CompoundStmt {{.*}} +// CHECK-NEXT: | `-CStyleCastExpr {{.*}} 'void' +// CHECK-NEXT: | `-ArrayTypeTraitExpr {{.*}} 'unsigned long' __array_rank +// CHECK-NEXT: |-FunctionDecl {{.*}} line:18:6 test_expression_trait 'void ()' +// CHECK-NEXT: | `-CompoundStmt {{.*}} +// CHECK-NEXT: | `-CStyleCastExpr {{.*}} 'void' +// CHECK-NEXT: | `-ExpressionTraitExpr {{.*}} 'bool' __is_lvalue_expr +// CHECK-NEXT: `-FunctionDecl {{.*}} line:23:6 test_unary_expr_or_type_trait 'void ()' +// CHECK-NEXT: `-CompoundStmt {{.*}} +// CHECK-NEXT: |-CStyleCastExpr {{.*}} 'void' +// CHECK-NEXT: | `-UnaryExprOrTypeTraitExpr {{.*}} 'unsigned long' sizeof 'int' +// CHECK-NEXT: |-CStyleCastExpr {{.*}} 'void' +// CHECK-NEXT: | `-UnaryExprOrTypeTraitExpr {{.*}} 'unsigned long' alignof 'int' +// CHECK-NEXT: `-CStyleCastExpr {{.*}} 'void' +// CHECK-NEXT: `-UnaryExprOrTypeTraitExpr {{.*}} 'unsigned long' __alignof 'int' From c08ea0771682de45964dc10759d6a57ce65c482d Mon Sep 17 00:00:00 2001 From: Erich Keane Date: Mon, 1 Jun 2020 12:14:34 -0700 Subject: [PATCH 11/12] Add to the Coding Standard our that single-line bodies omit braces This is a rule that seems to have been enforced for the better part of the decade, so we should document it for new contributors. Differential Revision: https://reviews.llvm.org/D80947 --- llvm/docs/CodingStandards.rst | 77 ++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 15 deletions(-) diff --git a/llvm/docs/CodingStandards.rst b/llvm/docs/CodingStandards.rst index 76763653121561..adf6e903694117 100644 --- a/llvm/docs/CodingStandards.rst +++ b/llvm/docs/CodingStandards.rst @@ -669,15 +669,15 @@ copy. .. code-block:: c++ // Typically there's no reason to copy. - for (const auto &Val : Container) { observe(Val); } - for (auto &Val : Container) { Val.change(); } + for (const auto &Val : Container) observe(Val); + for (auto &Val : Container) Val.change(); // Remove the reference if you really want a new copy. for (auto Val : Container) { Val.change(); saveSomewhere(Val); } // Copy pointers, but make it clear that they're pointers. - for (const auto *Ptr : Container) { observe(*Ptr); } - for (auto *Ptr : Container) { Ptr->change(); } + for (const auto *Ptr : Container) observe(*Ptr); + for (auto *Ptr : Container) Ptr->change(); Beware of non-determinism due to ordering of pointers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -884,7 +884,7 @@ It is much preferred to format the code like this: .. code-block:: c++ Value *doSomething(Instruction *I) { - // Terminators never need 'something' done to them because ... + // Terminators never need 'something' done to them because ... if (I->isTerminator()) return 0; @@ -896,7 +896,7 @@ It is much preferred to format the code like this: // This is really just here for example. if (!doOtherThing(I)) return 0; - + ... some long code .... } @@ -1000,7 +1000,7 @@ Or better yet (in this case) as: Type = Context.getsigjmp_bufType(); else Type = Context.getjmp_bufType(); - + if (Type.isNull()) { Error = Signed ? ASTContext::GE_Missing_sigjmp_buf : ASTContext::GE_Missing_jmp_buf; @@ -1010,7 +1010,7 @@ Or better yet (in this case) as: The idea is to reduce indentation and the amount of code you have to keep track of when reading the code. - + Turn Predicate Loops into Predicate Functions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -1081,7 +1081,7 @@ In general, names should be in camel case (e.g. ``TextFileReader`` and * **Variable names** should be nouns (as they represent state). The name should be camel case, and start with an upper case letter (e.g. ``Leader`` or ``Boats``). - + * **Function names** should be verb phrases (as they represent actions), and command-like function should be imperative. The name should be camel case, and start with a lower case letter (e.g. ``openFile()`` or ``isFoo()``). @@ -1091,7 +1091,7 @@ In general, names should be in camel case (e.g. ``TextFileReader`` and discriminator for a union, or an indicator of a subclass. When an enum is used for something like this, it should have a ``Kind`` suffix (e.g. ``ValueKind``). - + * **Enumerators** (e.g. ``enum { Foo, Bar }``) and **public member variables** should start with an upper-case letter, just like types. Unless the enumerators are defined in their own small namespace or inside a class, @@ -1107,7 +1107,7 @@ In general, names should be in camel case (e.g. ``TextFileReader`` and MaxSize = 42, Density = 12 }; - + As an exception, classes that mimic STL classes can have member names in STL's style of lower-case words separated by underscores (e.g. ``begin()``, ``push_back()``, and ``empty()``). Classes that provide multiple @@ -1359,7 +1359,7 @@ prefer it. The use of ``#include `` in library files is hereby **forbidden**, because many common implementations transparently inject a `static constructor`_ into every translation unit that includes it. - + Note that using the other stream headers (```` for example) is not problematic in this regard --- just ````. However, ``raw_ostream`` provides various APIs that are better performing for almost every use than @@ -1491,7 +1491,7 @@ being closed by a ``}``. For example: public: explicit Grokable() { ... } virtual ~Grokable() = 0; - + ... }; @@ -1540,8 +1540,8 @@ as possible, and only use them for class declarations. For example: }; } // end anonymous namespace - static void runHelper() { - ... + static void runHelper() { + ... } bool StringSort::operator<(const char *RHS) const { @@ -1569,6 +1569,53 @@ you have no immediate way to tell if this function is local to the file. In contrast, when the function is marked static, you don't need to cross-reference faraway places in the file to tell that the function is local. +Don't Use Braces on Simple Single-Statement Bodies of if/else/loop Statements +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When writing the body of an ``if``, ``else``, or loop statement, omit the braces to +avoid unnecessary line noise. However, braces should be used in cases where the +omission of braces harm the readability and maintainability of the code. + +Readability is harmed when a single statement is accompanied by a comment that loses +its meaning if hoisted above the ``if`` or loop statement. Similarly, braces should +be used when single-statement body is complex enough that it becomes difficult to see +where the block containing the following statement began. An ``if``/``else`` chain or +a loop is considered a single statement for this rule, and this rule applies recursively. +This list is not exhaustive, for example, readability is also harmed if an +``if``/``else`` chain starts using braced bodies partway through and does not continue +on with braced bodies. + +Maintainability is harmed if the body of an ``if`` ends with a (directly or indirectly) +nested ``if`` statement with no ``else``. Braces on the outer ``if`` would help to avoid +running into a "dangling else" situation. + + +Note that comments should only be hoisted for loops and +``if``, and not in ``else if`` or ``else``, where it would be unclear whether the comment +belonged to the preceeding condition, or the ``else``. + +.. code-block:: c++ + + // Omit the braces, since the body is simple and clearly associated with the if. + if (isa(D)) + handleFunctionDecl(D); + else if (isa(D)) + handleVarDecl(D); + else { + // In this else case, it is necessary that we explain the situation with this + // surprisingly long comment, so it would be unclear without the braces whether + // the following statement is in the scope of the else. + handleOtherDecl(D); + } + + // This should also omit braces. The for loop contains only a single statement, + // so it shouldn't have braces. The if also only contains a single statement (the + // for loop), so it also should omit braces. + if (isa(D)) + for(auto *A : D.attrs()) + handleAttr(A); + + See Also ======== From 6239d67001843722cb5d6e08d4368492fa5dbd9e Mon Sep 17 00:00:00 2001 From: Aditya Nandakumar Date: Thu, 11 Jun 2020 12:29:12 -0700 Subject: [PATCH 12/12] [GISel][NFC]: Add unit test for clarifying CSE behavior Add a unit test that shows how CSE works if we install an observer at the machine function level and not use the CSEMIRBuilder to build instructions. https://reviews.llvm.org/D81625 --- llvm/unittests/CodeGen/GlobalISel/CSETest.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/llvm/unittests/CodeGen/GlobalISel/CSETest.cpp b/llvm/unittests/CodeGen/GlobalISel/CSETest.cpp index 1c86d5ff9943fb..556f4f29b992e0 100644 --- a/llvm/unittests/CodeGen/GlobalISel/CSETest.cpp +++ b/llvm/unittests/CodeGen/GlobalISel/CSETest.cpp @@ -77,6 +77,25 @@ TEST_F(AArch64GISelMITest, TestCSE) { auto Undef0 = CSEB.buildUndef(s32); auto Undef1 = CSEB.buildUndef(s32); EXPECT_EQ(&*Undef0, &*Undef1); + + // If the observer is installed to the MF, CSE can also + // track new instructions built without the CSEBuilder and + // the newly built instructions are available for CSEing next + // time a build call is made through the CSEMIRBuilder. + // Additionally, the CSE implementation lazily hashes instructions + // (every build call) to give chance for the instruction to be fully + // built (say using .addUse().addDef().. so on). + GISelObserverWrapper WrapperObserver(&CSEInfo); + RAIIMFObsDelInstaller Installer(*MF, WrapperObserver); + MachineIRBuilder RegularBuilder(*MF); + RegularBuilder.setInsertPt(*EntryMBB, EntryMBB->begin()); + auto NonCSEFMul = RegularBuilder.buildInstr(TargetOpcode::G_AND) + .addDef(MRI->createGenericVirtualRegister(s32)) + .addUse(Copies[0]) + .addUse(Copies[1]); + auto CSEFMul = + CSEB.buildInstr(TargetOpcode::G_AND, {s32}, {Copies[0], Copies[1]}); + EXPECT_EQ(&*CSEFMul, &*NonCSEFMul); } TEST_F(AArch64GISelMITest, TestCSEConstantConfig) {