From 5ea15fab19eb005c6032ea453c7a01ae32269336 Mon Sep 17 00:00:00 2001 From: Cyndy Ishida Date: Wed, 20 Dec 2023 08:47:10 -0800 Subject: [PATCH] [TextAPI] Add support to convert RecordSlices -> InterfaceFile (#75007) Introduce RecordVisitor. This is used for different clients that want to extract information out of RecordSlice types. The first and immediate use case is for serializing symbol information into TBD files. --- llvm/include/llvm/TextAPI/DylibReader.h | 5 + llvm/include/llvm/TextAPI/Record.h | 9 +- llvm/include/llvm/TextAPI/RecordVisitor.h | 54 ++++++++ llvm/include/llvm/TextAPI/RecordsSlice.h | 12 +- llvm/lib/TextAPI/BinaryReader/DylibReader.cpp | 10 ++ llvm/lib/TextAPI/CMakeLists.txt | 1 + llvm/lib/TextAPI/RecordVisitor.cpp | 65 ++++++++++ llvm/lib/TextAPI/RecordsSlice.cpp | 120 +++++++++++++++++- 8 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 llvm/include/llvm/TextAPI/RecordVisitor.h create mode 100644 llvm/lib/TextAPI/RecordVisitor.cpp diff --git a/llvm/include/llvm/TextAPI/DylibReader.h b/llvm/include/llvm/TextAPI/DylibReader.h index d99f22c59cf8..b556fbf6832a 100644 --- a/llvm/include/llvm/TextAPI/DylibReader.h +++ b/llvm/include/llvm/TextAPI/DylibReader.h @@ -38,6 +38,11 @@ struct ParseOption { /// \return List of record slices. Expected readFile(MemoryBufferRef Buffer, const ParseOption &Opt); +/// Get TAPI file representation of binary dylib. +/// +/// \param Buffer Data that points to dylib. +Expected> get(MemoryBufferRef Buffer); + } // namespace llvm::MachO::DylibReader #endif // LLVM_TEXTAPI_DYLIBREADER_H diff --git a/llvm/include/llvm/TextAPI/Record.h b/llvm/include/llvm/TextAPI/Record.h index 13d0bf6e6573..4bb1be9d0ad4 100644 --- a/llvm/include/llvm/TextAPI/Record.h +++ b/llvm/include/llvm/TextAPI/Record.h @@ -14,6 +14,7 @@ #ifndef LLVM_TEXTAPI_RECORD_H #define LLVM_TEXTAPI_RECORD_H +#include "llvm/ADT/MapVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/TextAPI/Symbol.h" @@ -50,7 +51,7 @@ class Record { public: Record() = default; Record(StringRef Name, RecordLinkage Linkage, SymbolFlags Flags) - : Name(Name), Linkage(Linkage), Flags(Flags) {} + : Name(Name), Linkage(Linkage), Flags(mergeFlags(Flags, Linkage)) {} bool isWeakDefined() const { return (Flags & SymbolFlags::WeakDefined) == SymbolFlags::WeakDefined; @@ -79,6 +80,10 @@ public: bool isRexported() const { return Linkage == RecordLinkage::Rexported; } StringRef getName() const { return Name; } + SymbolFlags getFlags() const { return Flags; } + +private: + SymbolFlags mergeFlags(SymbolFlags Flags, RecordLinkage Linkage); protected: StringRef Name; @@ -137,6 +142,7 @@ public: ObjCIVarRecord *addObjCIVar(StringRef IVar, RecordLinkage Linkage); ObjCIVarRecord *findObjCIVar(StringRef IVar) const; + std::vector getObjCIVars() const; private: RecordMap IVars; @@ -163,6 +169,7 @@ public: bool hasExceptionAttribute() const { return HasEHType; } bool addObjCCategory(ObjCCategoryRecord *Record); + std::vector getObjCCategories() const; private: bool HasEHType; diff --git a/llvm/include/llvm/TextAPI/RecordVisitor.h b/llvm/include/llvm/TextAPI/RecordVisitor.h new file mode 100644 index 000000000000..34e43f5b0027 --- /dev/null +++ b/llvm/include/llvm/TextAPI/RecordVisitor.h @@ -0,0 +1,54 @@ +//===- llvm/TextAPI/RecordSlice.h - TAPI RecordSlice ------------*- 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 +// +//===----------------------------------------------------------------------===// +/// +/// Defines the TAPI Record Visitor. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TEXTAPI_RECORDVISITOR_H +#define LLVM_TEXTAPI_RECORDVISITOR_H + +#include "llvm/TextAPI/Record.h" +#include "llvm/TextAPI/SymbolSet.h" + +namespace llvm { +namespace MachO { + +/// Base class for any usage of traversing over collected Records. +class RecordVisitor { +public: + virtual ~RecordVisitor(); + + virtual void visitGlobal(const GlobalRecord &) = 0; + virtual void visitObjCInterface(const ObjCInterfaceRecord &); + virtual void visitObjCCategory(const ObjCCategoryRecord &); +}; + +/// Specialized RecordVisitor for collecting exported symbols +/// and undefined symbols if RecordSlice being visited represents a +/// flat-namespaced library. +class SymbolConverter : public RecordVisitor { +public: + SymbolConverter(SymbolSet *Symbols, const Target &T, + const bool RecordUndefs = false) + : Symbols(Symbols), Targ(T), RecordUndefs(RecordUndefs) {} + void visitGlobal(const GlobalRecord &) override; + void visitObjCInterface(const ObjCInterfaceRecord &) override; + void visitObjCCategory(const ObjCCategoryRecord &) override; + +private: + void addIVars(const ArrayRef, StringRef ContainerName); + SymbolSet *Symbols; + const Target Targ; + const bool RecordUndefs; +}; + +} // end namespace MachO. +} // end namespace llvm. + +#endif // LLVM_TEXTAPI_RECORDVISITOR_H diff --git a/llvm/include/llvm/TextAPI/RecordsSlice.h b/llvm/include/llvm/TextAPI/RecordsSlice.h index 461a6d2dcc57..0f9e3fad1a30 100644 --- a/llvm/include/llvm/TextAPI/RecordsSlice.h +++ b/llvm/include/llvm/TextAPI/RecordsSlice.h @@ -14,11 +14,11 @@ #ifndef LLVM_TEXTAPI_RECORDSLICE_H #define LLVM_TEXTAPI_RECORDSLICE_H -#include "llvm/ADT/MapVector.h" #include "llvm/Support/Allocator.h" #include "llvm/TextAPI/InterfaceFile.h" #include "llvm/TextAPI/PackedVersion.h" #include "llvm/TextAPI/Record.h" +#include "llvm/TextAPI/RecordVisitor.h" namespace llvm { namespace MachO { @@ -133,6 +133,9 @@ public: Categories.empty(); } + // Visit all records known to RecordsSlice. + void visit(RecordVisitor &V) const; + struct BinaryAttrs { std::vector AllowableClients; std::vector RexportedLibraries; @@ -174,6 +177,12 @@ private: R->Linkage = std::max(R->Linkage, L); } + /// Update set flags of requested record. + /// + /// \param R The global record to update. + /// \param F Flags to update to. + void updateFlags(GlobalRecord *R, SymbolFlags F) { R->Flags = F; } + RecordMap Globals; RecordMap Classes; RecordMap> Categories; @@ -182,6 +191,7 @@ private: }; using Records = llvm::SmallVector, 4>; +std::unique_ptr convertToInterfaceFile(const Records &Slices); } // namespace MachO } // namespace llvm diff --git a/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp b/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp index b01130cee606..40b57b5e40ea 100644 --- a/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp +++ b/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp @@ -417,3 +417,13 @@ Expected DylibReader::readFile(MemoryBufferRef Buffer, return make_error(TextAPIErrorCode::EmptyResults); return Results; } + +Expected> +DylibReader::get(MemoryBufferRef Buffer) { + ParseOption Options; + auto SlicesOrErr = readFile(Buffer, Options); + if (!SlicesOrErr) + return SlicesOrErr.takeError(); + + return convertToInterfaceFile(*SlicesOrErr); +} diff --git a/llvm/lib/TextAPI/CMakeLists.txt b/llvm/lib/TextAPI/CMakeLists.txt index 75fc92f8e06a..2017a1ad6398 100644 --- a/llvm/lib/TextAPI/CMakeLists.txt +++ b/llvm/lib/TextAPI/CMakeLists.txt @@ -6,6 +6,7 @@ add_llvm_component_library(LLVMTextAPI PackedVersion.cpp Platform.cpp RecordsSlice.cpp + RecordVisitor.cpp Symbol.cpp SymbolSet.cpp Target.cpp diff --git a/llvm/lib/TextAPI/RecordVisitor.cpp b/llvm/lib/TextAPI/RecordVisitor.cpp new file mode 100644 index 000000000000..cee04e644755 --- /dev/null +++ b/llvm/lib/TextAPI/RecordVisitor.cpp @@ -0,0 +1,65 @@ +//===- RecordVisitor.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 +// +//===----------------------------------------------------------------------===// +/// +/// Implements the TAPI Record Visitor. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/TextAPI/RecordVisitor.h" + +using namespace llvm; +using namespace llvm::MachO; + +RecordVisitor::~RecordVisitor() {} +void RecordVisitor::visitObjCInterface(const ObjCInterfaceRecord &) {} +void RecordVisitor::visitObjCCategory(const ObjCCategoryRecord &) {} + +static bool shouldSkipRecord(const Record &R, const bool RecordUndefs) { + if (R.isExported()) + return false; + + // Skip non exported symbols unless for flat namespace libraries. + return !(RecordUndefs && R.isUndefined()); +} + +void SymbolConverter::visitGlobal(const GlobalRecord &GR) { + auto [SymName, SymKind] = parseSymbol(GR.getName(), GR.getFlags()); + if (shouldSkipRecord(GR, RecordUndefs)) + return; + Symbols->addGlobal(SymKind, SymName, GR.getFlags(), Targ); +} + +void SymbolConverter::addIVars(const ArrayRef IVars, + StringRef ContainerName) { + for (auto *IV : IVars) { + if (shouldSkipRecord(*IV, RecordUndefs)) + continue; + std::string Name = + ObjCIVarRecord::createScopedName(ContainerName, IV->getName()); + Symbols->addGlobal(SymbolKind::ObjectiveCInstanceVariable, Name, + IV->getFlags(), Targ); + } +} + +void SymbolConverter::visitObjCInterface(const ObjCInterfaceRecord &ObjCR) { + if (!shouldSkipRecord(ObjCR, RecordUndefs)) { + Symbols->addGlobal(SymbolKind::ObjectiveCClass, ObjCR.getName(), + ObjCR.getFlags(), Targ); + if (ObjCR.hasExceptionAttribute()) + Symbols->addGlobal(SymbolKind::ObjectiveCClassEHType, ObjCR.getName(), + ObjCR.getFlags(), Targ); + } + + addIVars(ObjCR.getObjCIVars(), ObjCR.getName()); + for (const auto *Cat : ObjCR.getObjCCategories()) + addIVars(Cat->getObjCIVars(), ObjCR.getName()); +} + +void SymbolConverter::visitObjCCategory(const ObjCCategoryRecord &Cat) { + addIVars(Cat.getObjCIVars(), Cat.getName()); +} diff --git a/llvm/lib/TextAPI/RecordsSlice.cpp b/llvm/lib/TextAPI/RecordsSlice.cpp index a220b255aea3..7ceffc7c9284 100644 --- a/llvm/lib/TextAPI/RecordsSlice.cpp +++ b/llvm/lib/TextAPI/RecordsSlice.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "llvm/TextAPI/RecordsSlice.h" +#include "llvm/ADT/SetVector.h" #include "llvm/TextAPI/Record.h" #include "llvm/TextAPI/Symbol.h" #include @@ -142,8 +143,10 @@ GlobalRecord *RecordsSlice::addGlobal(StringRef Name, RecordLinkage Linkage, if (Result.second) Result.first->second = std::make_unique(Name, Linkage, Flags, GV); - else + else { updateLinkage(Result.first->second.get(), Linkage); + updateFlags(Result.first->second.get(), Flags); + } return Result.first->second.get(); } @@ -164,6 +167,19 @@ ObjCInterfaceRecord *RecordsSlice::addObjCInterface(StringRef Name, return Result.first->second.get(); } +SymbolFlags Record::mergeFlags(SymbolFlags Flags, RecordLinkage Linkage) { + // Add Linkage properties into Flags. + switch (Linkage) { + case RecordLinkage::Rexported: + Flags |= SymbolFlags::Rexported; + return Flags; + case RecordLinkage::Undefined: + Flags |= SymbolFlags::Undefined; + return Flags; + default: + return Flags; + } +} bool ObjCInterfaceRecord::addObjCCategory(ObjCCategoryRecord *Record) { auto Result = Categories.insert({Name, Record}); @@ -188,11 +204,26 @@ ObjCCategoryRecord *RecordsSlice::addObjCCategory(StringRef ClassToExtend, return Result.first->second.get(); } +std::vector ObjCContainerRecord::getObjCIVars() const { + std::vector Records; + llvm::for_each(IVars, + [&](auto &Record) { Records.push_back(Record.second.get()); }); + return Records; +} + +std::vector +ObjCInterfaceRecord::getObjCCategories() const { + std::vector Records; + llvm::for_each(Categories, + [&](auto &Record) { Records.push_back(Record.second); }); + return Records; +} + ObjCIVarRecord *ObjCContainerRecord::addObjCIVar(StringRef IVar, RecordLinkage Linkage) { auto Result = IVars.insert({IVar, nullptr}); if (Result.second) - Result.first->second = std::make_unique(Name, Linkage); + Result.first->second = std::make_unique(IVar, Linkage); return Result.first->second.get(); } @@ -222,3 +253,88 @@ RecordsSlice::BinaryAttrs &RecordsSlice::getBinaryAttrs() { BA = std::make_unique(); return *BA; } + +void RecordsSlice::visit(RecordVisitor &V) const { + for (auto &G : Globals) + V.visitGlobal(*G.second); + for (auto &C : Classes) + V.visitObjCInterface(*C.second); + for (auto &Cat : Categories) + V.visitObjCCategory(*Cat.second); +} + +static std::unique_ptr +createInterfaceFile(const Records &Slices, StringRef InstallName) { + // Pickup symbols first. + auto Symbols = std::make_unique(); + for (auto &S : Slices) { + if (S->empty()) + continue; + auto &BA = S->getBinaryAttrs(); + if (BA.InstallName != InstallName) + continue; + + SymbolConverter Converter(Symbols.get(), S->getTarget(), + !BA.TwoLevelNamespace); + S->visit(Converter); + } + + auto File = std::make_unique(std::move(Symbols)); + File->setInstallName(InstallName); + // Assign other attributes. + for (auto &S : Slices) { + if (S->empty()) + continue; + auto &BA = S->getBinaryAttrs(); + if (BA.InstallName != InstallName) + continue; + const Target &Targ = S->getTarget(); + File->addTarget(Targ); + if (File->getFileType() == FileType::Invalid) + File->setFileType(BA.File); + if (BA.AppExtensionSafe && !File->isApplicationExtensionSafe()) + File->setApplicationExtensionSafe(); + if (BA.TwoLevelNamespace && !File->isTwoLevelNamespace()) + File->setTwoLevelNamespace(); + if (BA.OSLibNotForSharedCache && !File->isOSLibNotForSharedCache()) + File->setOSLibNotForSharedCache(); + if (File->getCurrentVersion().empty()) + File->setCurrentVersion(BA.CurrentVersion); + if (File->getCompatibilityVersion().empty()) + File->setCompatibilityVersion(BA.CompatVersion); + if (File->getSwiftABIVersion() == 0) + File->setSwiftABIVersion(BA.SwiftABI); + if (File->getPath().empty()) + File->setPath(BA.Path); + if (!BA.ParentUmbrella.empty()) + File->addParentUmbrella(Targ, BA.ParentUmbrella); + for (const auto &Client : BA.AllowableClients) + File->addAllowableClient(Client, Targ); + for (const auto &Lib : BA.RexportedLibraries) + File->addReexportedLibrary(Lib, Targ); + } + + return File; +} + +std::unique_ptr +llvm::MachO::convertToInterfaceFile(const Records &Slices) { + std::unique_ptr File; + if (Slices.empty()) + return File; + + SetVector InstallNames; + for (auto &S : Slices) { + auto Name = S->getBinaryAttrs().InstallName; + if (Name.empty()) + continue; + InstallNames.insert(Name); + } + + File = createInterfaceFile(Slices, *InstallNames.begin()); + for (auto it = std::next(InstallNames.begin()); it != InstallNames.end(); + ++it) + File->addDocument(createInterfaceFile(Slices, *it)); + + return File; +}