[llvm] b04b897 - [TextAPI] Introduce Records & RecordSlice Types (#74115)

via llvm-commits llvm-commits at lists.llvm.org
Sat Dec 2 13:19:05 PST 2023


Author: Cyndy Ishida
Date: 2023-12-02T13:19:01-08:00
New Revision: b04b89753daf9751a81ffbcfbfbe6c610fb88af8

URL: https://github.com/llvm/llvm-project/commit/b04b89753daf9751a81ffbcfbfbe6c610fb88af8
DIFF: https://github.com/llvm/llvm-project/commit/b04b89753daf9751a81ffbcfbfbe6c610fb88af8.diff

LOG: [TextAPI] Introduce Records & RecordSlice Types (#74115)

`Record`'s hold target triple specific information about APIs and
symbols. This holds information about the relationship between ObjC
symbols and their linkage properties. It will be used to compare and run
significant operations between the frontend representation of symbols in
AST and symbol information extracted from Mach-O binaries. This differs
from the lighter weight Symbol and SymbolSet class where they are
deduplicated across targets and only represent exported symbols, that
class is mostly used for serializing.

Added: 
    llvm/include/llvm/TextAPI/Record.h
    llvm/include/llvm/TextAPI/RecordsSlice.h
    llvm/lib/TextAPI/RecordsSlice.cpp
    llvm/unittests/TextAPI/RecordTests.cpp

Modified: 
    llvm/include/llvm/TextAPI/Symbol.h
    llvm/lib/TextAPI/CMakeLists.txt
    llvm/lib/TextAPI/Symbol.cpp
    llvm/unittests/TextAPI/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/TextAPI/Record.h b/llvm/include/llvm/TextAPI/Record.h
new file mode 100644
index 0000000000000..3b62af49902b7
--- /dev/null
+++ b/llvm/include/llvm/TextAPI/Record.h
@@ -0,0 +1,172 @@
+//===- llvm/TextAPI/Record.h - TAPI Record ----------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Implements the TAPI Record Types.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TEXTAPI_RECORD_H
+#define LLVM_TEXTAPI_RECORD_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/TextAPI/Symbol.h"
+#include <string>
+
+namespace llvm {
+namespace MachO {
+
+LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
+
+class RecordsSlice;
+
+// Defines a list of linkage types.
+enum class RecordLinkage : uint8_t {
+  // Unknown linkage.
+  Unknown = 0,
+
+  // Local, hidden or private extern linkage.
+  Internal = 1,
+
+  // Undefined linkage, it represents usage of external interface.
+  Undefined = 2,
+
+  // Re-exported linkage, record is defined in external interface.
+  Rexported = 3,
+
+  // Exported linkage.
+  Exported = 4,
+};
+
+/// Define Record. They represent API's in binaries that could be linkable
+/// symbols.
+class Record {
+public:
+  Record() = default;
+  Record(StringRef Name, RecordLinkage Linkage, SymbolFlags Flags)
+      : Name(Name), Linkage(Linkage), Flags(Flags) {}
+
+  bool isWeakDefined() const {
+    return (Flags & SymbolFlags::WeakDefined) == SymbolFlags::WeakDefined;
+  }
+
+  bool isWeakReferenced() const {
+    return (Flags & SymbolFlags::WeakReferenced) == SymbolFlags::WeakReferenced;
+  }
+
+  bool isThreadLocalValue() const {
+    return (Flags & SymbolFlags::ThreadLocalValue) ==
+           SymbolFlags::ThreadLocalValue;
+  }
+
+  bool isData() const {
+    return (Flags & SymbolFlags::Data) == SymbolFlags::Data;
+  }
+
+  bool isText() const {
+    return (Flags & SymbolFlags::Text) == SymbolFlags::Text;
+  }
+
+  bool isInternal() const { return Linkage == RecordLinkage::Internal; }
+  bool isUndefined() const { return Linkage == RecordLinkage::Undefined; }
+  bool isExported() const { return Linkage >= RecordLinkage::Rexported; }
+  bool isRexported() const { return Linkage == RecordLinkage::Rexported; }
+
+  StringRef getName() const { return Name; }
+
+protected:
+  StringRef Name;
+  RecordLinkage Linkage;
+  SymbolFlags Flags;
+
+  friend class RecordsSlice;
+};
+
+// Defines broadly non-objc records, categorized as variables or functions.
+class GlobalRecord : public Record {
+public:
+  enum class Kind : uint8_t {
+    Unknown = 0,
+    Variable = 1,
+    Function = 2,
+  };
+
+  GlobalRecord(StringRef Name, RecordLinkage Linkage, SymbolFlags Flags,
+               Kind GV)
+      : Record({Name, Linkage, Flags}), GV(GV) {}
+
+  bool isFunction() const { return GV == Kind::Function; }
+  bool isVariable() const { return GV == Kind::Variable; }
+
+private:
+  Kind GV;
+};
+
+// Define Objective-C instance variable records.
+class ObjCIVarRecord : public Record {
+public:
+  ObjCIVarRecord(StringRef Name, RecordLinkage Linkage)
+      : Record({Name, Linkage, SymbolFlags::Data}) {}
+
+  static std::string createScopedName(StringRef SuperClass, StringRef IVar) {
+    return (SuperClass + "." + IVar).str();
+  }
+};
+
+template <typename V, typename K = StringRef,
+          typename std::enable_if<std::is_base_of<Record, V>::value>::type * =
+              nullptr>
+using RecordMap = llvm::MapVector<K, std::unique_ptr<V>>;
+
+// Defines Objective-C record types that have assigned methods, properties,
+// instance variable (ivars) and protocols.
+class ObjCContainerRecord : public Record {
+public:
+  ObjCContainerRecord(StringRef Name, RecordLinkage Linkage)
+      : Record({Name, Linkage, SymbolFlags::Data}) {}
+
+  ObjCIVarRecord *addObjCIVar(StringRef IVar, RecordLinkage Linkage);
+  ObjCIVarRecord *findObjCIVar(StringRef IVar) const;
+
+private:
+  RecordMap<ObjCIVarRecord> IVars;
+};
+
+// Define Objective-C category types. They don't generate linkable symbols, but
+// they have assigned ivars that do.
+class ObjCCategoryRecord : public ObjCContainerRecord {
+public:
+  ObjCCategoryRecord(StringRef ClassToExtend, StringRef Name)
+      : ObjCContainerRecord(Name, RecordLinkage::Unknown),
+        ClassToExtend(ClassToExtend) {}
+
+private:
+  StringRef ClassToExtend;
+};
+
+// Define Objective-C Interfaces or class types.
+class ObjCInterfaceRecord : public ObjCContainerRecord {
+public:
+  ObjCInterfaceRecord(StringRef Name, RecordLinkage Linkage,
+                      bool HasEHType = false)
+      : ObjCContainerRecord(Name, Linkage), HasEHType(HasEHType) {}
+
+  bool hasExceptionAttribute() const { return HasEHType; }
+  bool addObjCCategory(ObjCCategoryRecord *Record);
+
+private:
+  bool HasEHType;
+  // Non-owning containers of categories that extend the class.
+  llvm::MapVector<StringRef, ObjCCategoryRecord *> Categories;
+};
+
+} // end namespace MachO.
+} // end namespace llvm.
+
+#endif // LLVM_TEXTAPI_RECORD_H

diff  --git a/llvm/include/llvm/TextAPI/RecordsSlice.h b/llvm/include/llvm/TextAPI/RecordsSlice.h
new file mode 100644
index 0000000000000..658e70e219453
--- /dev/null
+++ b/llvm/include/llvm/TextAPI/RecordsSlice.h
@@ -0,0 +1,183 @@
+//===- 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// \brief Implements the TAPI Record Collection Type.
+///
+//===----------------------------------------------------------------------===//
+
+#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"
+
+namespace llvm {
+namespace MachO {
+
+// Define collection of records for a library that are tied to a darwin target
+// triple.
+class RecordsSlice {
+public:
+  RecordsSlice(const llvm::Triple &T) : TargetTriple(T), Target(T) {}
+  /// Get target triple.
+  const llvm::Triple &getTriple() const { return TargetTriple; }
+  /// Get TAPI converted target.
+  const Target &getTarget() const { return Target; }
+
+  /// Add unspecified record to slice.
+  ///
+  /// Assign specific record type based on properties and symbol name.
+  ///
+  /// \param Name The name of symbol.
+  /// \param Flags The flags that describe attributes of the symbol.
+  /// \param GV The kind of global, if this represents a non obj-c global
+  /// symbol.
+  /// \param Linkage The linkage of symbol.
+  /// \return The non-owning pointer to added record in slice.
+  Record *addRecord(StringRef Name, SymbolFlags Flags,
+                    GlobalRecord::Kind GV = GlobalRecord::Kind::Unknown,
+                    RecordLinkage Linkage = RecordLinkage::Unknown);
+
+  /// Add non-ObjC global record.
+  ///
+  /// \param Name The name of symbol.
+  /// \param Flags The flags that describe attributes of the symbol.
+  /// \param GV The kind of global.
+  /// \param Linkage The linkage of symbol.
+  /// \return The non-owning pointer to added record in slice.
+  GlobalRecord *addGlobal(StringRef Name, RecordLinkage Linkage,
+                          GlobalRecord::Kind GV,
+                          SymbolFlags Flags = SymbolFlags::None);
+
+  /// Add ObjC Class record.
+  ///
+  /// \param Name The name of class, not symbol.
+  /// \param Linkage The linkage of symbol.
+  /// \param HasEHType Whether symbol represents an eh_type.
+  /// \return The non-owning pointer to added record in slice.
+  ObjCInterfaceRecord *addObjCInterface(StringRef Name, RecordLinkage Linkage,
+                                        bool HasEHType = false);
+
+  /// Add ObjC IVar record.
+  ///
+  /// \param Name The name of ivar, not symbol.
+  /// \param Linkage The linkage of symbol.
+  /// \return The non-owning pointer to added record in slice.
+  ObjCIVarRecord *addObjCIVar(ObjCContainerRecord *Container, StringRef Name,
+                              RecordLinkage Linkage);
+
+  /// Add ObjC Category record.
+  ///
+  /// \param ClassToExtend The name of class that is being extended by the
+  /// category, not symbol.
+  /// \param Category The name of category.
+  /// \return The non-owning pointer to added record in slice.
+  ObjCCategoryRecord *addObjCCategory(StringRef ClassToExtend,
+                                      StringRef Category);
+
+  /// Find ObjC Class.
+  ///
+  /// \param Name name of class, not full symbol name.
+  /// \return The non-owning pointer to record in slice.
+  ObjCInterfaceRecord *findObjCInterface(StringRef Name) const;
+
+  /// Find ObjC Category.
+  ///
+  /// \param ClassToExtend The name of class, not full symbol name.
+  /// \param Categories The name of category.
+  /// \return The non-owning pointer to record in slice.
+  ObjCCategoryRecord *findObjCCategory(StringRef ClassToExtend,
+                                       StringRef Category) const;
+
+  /// Find ObjC Container. This is commonly used for assigning for looking up
+  /// instance variables that are assigned to either a category or class.
+  ///
+  /// \param IsIVar If true, the name is the name of the IVar, otherwise it will
+  /// be looked up as the name of the container.
+  /// \param Name Either the name of ivar or name of container.
+  /// \return The non-owning pointer to record in
+  /// slice.
+  ObjCContainerRecord *findContainer(bool IsIVar, StringRef Name) const;
+
+  /// Find ObjC instance variable.
+  ///
+  /// \param IsScopedName This is used to determine how to parse the name.
+  /// \param Name Either the full name of the symbol or just the ivar.
+  /// \return The non-owning pointer to record in slice.
+  ObjCIVarRecord *findObjCIVar(bool IsScopedName, StringRef Name) const;
+
+  /// Find non-objc global.
+  ///
+  /// \param Name The name of symbol.
+  /// \param GV The Kind of global to find.
+  /// \return The non-owning pointer to record in slice.
+  GlobalRecord *
+  findGlobal(StringRef Name,
+             GlobalRecord::Kind GV = GlobalRecord::Kind::Unknown) const;
+
+  // Determine if library attributes were assigned.
+  bool hasBinaryAttrs() const { return BA.get(); }
+
+  // Determine if record slice is unassigned.
+  bool isEmpty() const {
+    return !hasBinaryAttrs() && Globals.empty() && Classes.empty() &&
+           Categories.empty();
+  }
+
+  struct BinaryAttrs {
+    std::vector<StringRef> AllowableClients;
+    std::vector<StringRef> RexportedLibraries;
+    std::vector<StringRef> RPaths;
+    StringRef ParentUmbrella;
+    StringRef InstallName;
+    StringRef UUID;
+    StringRef Path;
+    FileType fileType = FileType::Invalid;
+    llvm::MachO::PackedVersion CurrentVersion;
+    llvm::MachO::PackedVersion CompatVersion;
+    uint8_t SwiftABI = 0;
+    bool TwoLevelNamespace = false;
+    bool AppExtensionSafe = false;
+    bool OSLibNotForSharedCache = false;
+  };
+
+  /// Return reference to BinaryAttrs.
+  BinaryAttrs &getBinaryAttrs();
+
+private:
+  const llvm::Triple TargetTriple;
+  // Hold tapi converted triple to avoid unecessary casts.
+  const Target Target;
+
+  /// BumpPtrAllocator to store generated/copied strings.
+  llvm::BumpPtrAllocator StringAllocator;
+  StringRef copyString(StringRef String);
+
+  /// Promote linkage of requested record. It is no-op if linkage type is lower
+  /// than the current assignment.
+  ///
+  /// \param R The record to update.
+  /// \param L Linkage type to update to.
+  void updateLinkage(Record *R, RecordLinkage L) {
+    R->Linkage = std::max(R->Linkage, L);
+  }
+
+  RecordMap<GlobalRecord> Globals;
+  RecordMap<ObjCInterfaceRecord> Classes;
+  RecordMap<ObjCCategoryRecord, std::pair<StringRef, StringRef>> Categories;
+
+  std::unique_ptr<BinaryAttrs> BA{nullptr};
+};
+
+} // namespace MachO
+} // namespace llvm
+#endif // LLVM_TEXTAPI_RECORDSLICE_H

diff  --git a/llvm/include/llvm/TextAPI/Symbol.h b/llvm/include/llvm/TextAPI/Symbol.h
index 35d9a64dee1c1..fc7f600b2e020 100644
--- a/llvm/include/llvm/TextAPI/Symbol.h
+++ b/llvm/include/llvm/TextAPI/Symbol.h
@@ -160,6 +160,23 @@ class Symbol {
   SymbolFlags Flags;
 };
 
+/// Lightweight struct for passing around symbol information.
+struct SimpleSymbol {
+  StringRef Name;
+  SymbolKind Kind;
+
+  bool operator<(const SimpleSymbol &O) const {
+    return std::tie(Name, Kind) < std::tie(O.Name, O.Kind);
+  }
+};
+
+/// Determine SymbolKind from Flags and parsing Name.
+///
+/// \param Name The name of symbol.
+/// \param Flags The flags pre-determined for the symbol.
+SimpleSymbol parseSymbol(StringRef SymName,
+                         const SymbolFlags Flags = SymbolFlags::None);
+
 } // end namespace MachO.
 } // end namespace llvm.
 

diff  --git a/llvm/lib/TextAPI/CMakeLists.txt b/llvm/lib/TextAPI/CMakeLists.txt
index 4088757afb900..5622ae7c6d724 100644
--- a/llvm/lib/TextAPI/CMakeLists.txt
+++ b/llvm/lib/TextAPI/CMakeLists.txt
@@ -5,6 +5,7 @@ add_llvm_component_library(LLVMTextAPI
   TextStubV5.cpp
   PackedVersion.cpp
   Platform.cpp
+  RecordsSlice.cpp
   Symbol.cpp
   SymbolSet.cpp
   Target.cpp

diff  --git a/llvm/lib/TextAPI/RecordsSlice.cpp b/llvm/lib/TextAPI/RecordsSlice.cpp
new file mode 100644
index 0000000000000..a220b255aea38
--- /dev/null
+++ b/llvm/lib/TextAPI/RecordsSlice.cpp
@@ -0,0 +1,224 @@
+//===- RecordsSlice.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 Records Slice APIs.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/TextAPI/RecordsSlice.h"
+#include "llvm/TextAPI/Record.h"
+#include "llvm/TextAPI/Symbol.h"
+#include <utility>
+
+using namespace llvm;
+using namespace llvm::MachO;
+
+Record *RecordsSlice::addRecord(StringRef Name, SymbolFlags Flags,
+                                GlobalRecord::Kind GV, RecordLinkage Linkage) {
+  // Find a specific Record type to capture.
+  auto [APIName, SymKind] = parseSymbol(Name, Flags);
+  Name = APIName;
+  switch (SymKind) {
+  case SymbolKind::GlobalSymbol:
+    return addGlobal(Name, Linkage, GV, Flags);
+  case SymbolKind::ObjectiveCClass:
+    return addObjCInterface(Name, Linkage);
+  case SymbolKind::ObjectiveCClassEHType:
+    return addObjCInterface(Name, Linkage, /*HasEHType=*/true);
+  case SymbolKind::ObjectiveCInstanceVariable: {
+    auto [Super, IVar] = Name.split('.');
+    // Attempt to find super class.
+    ObjCContainerRecord *Container = findContainer(/*isIVar=*/false, Super);
+    // If not found, create extension since there is no mapped class symbol.
+    if (Container == nullptr)
+      Container = addObjCCategory(Super, {});
+    return addObjCIVar(Container, IVar, Linkage);
+  }
+  }
+
+  llvm_unreachable("unexpected symbol kind when adding to Record Slice");
+}
+
+ObjCContainerRecord *RecordsSlice::findContainer(bool IsIVar,
+                                                 StringRef Name) const {
+  StringRef Super = IsIVar ? Name.split('.').first : Name;
+  ObjCContainerRecord *Container = findObjCInterface(Super);
+  // Ivars can only exist with extensions, if they did not come from
+  // class.
+  if (Container == nullptr)
+    Container = findObjCCategory(Super, "");
+  return Container;
+}
+
+template <typename R, typename C = RecordMap<R>, typename K = StringRef>
+R *findRecord(K Key, const C &Container) {
+  const auto *Record = Container.find(Key);
+  if (Record == Container.end())
+    return nullptr;
+  return Record->second.get();
+}
+
+GlobalRecord *RecordsSlice::findGlobal(StringRef Name,
+                                       GlobalRecord::Kind GV) const {
+  auto *Record = findRecord<GlobalRecord>(Name, Globals);
+  if (!Record)
+    return nullptr;
+
+  switch (GV) {
+  case GlobalRecord::Kind::Variable: {
+    if (!Record->isVariable())
+      return nullptr;
+    break;
+  }
+  case GlobalRecord::Kind::Function: {
+    if (!Record->isFunction())
+      return nullptr;
+    break;
+  }
+  case GlobalRecord::Kind::Unknown:
+    return Record;
+  }
+
+  return Record;
+}
+
+ObjCInterfaceRecord *RecordsSlice::findObjCInterface(StringRef Name) const {
+  return findRecord<ObjCInterfaceRecord>(Name, Classes);
+}
+
+ObjCCategoryRecord *RecordsSlice::findObjCCategory(StringRef ClassToExtend,
+                                                   StringRef Category) const {
+  return findRecord<ObjCCategoryRecord>(std::make_pair(ClassToExtend, Category),
+                                        Categories);
+}
+
+ObjCIVarRecord *ObjCContainerRecord::findObjCIVar(StringRef IVar) const {
+  return findRecord<ObjCIVarRecord>(IVar, IVars);
+}
+
+ObjCIVarRecord *RecordsSlice::findObjCIVar(bool IsScopedName,
+                                           StringRef Name) const {
+  // If scoped name, the name of the container is known.
+  if (IsScopedName) {
+    // IVar does not exist if there is not a container assigned to it.
+    auto *Container = findContainer(/*IsIVar=*/true, Name);
+    if (!Container)
+      return nullptr;
+
+    StringRef IVar = Name.substr(Name.find_first_of('.') + 1);
+    return Container->findObjCIVar(IVar);
+  }
+
+  // Otherwise traverse through containers and attempt to find IVar.
+  auto getIVar = [Name](auto &Records) -> ObjCIVarRecord * {
+    for (const auto &[_, Container] : Records) {
+      if (auto *IVarR = Container->findObjCIVar(Name))
+        return IVarR;
+    }
+    return nullptr;
+  };
+
+  if (auto *IVarRecord = getIVar(Classes))
+    return IVarRecord;
+
+  return getIVar(Categories);
+}
+
+GlobalRecord *RecordsSlice::addGlobal(StringRef Name, RecordLinkage Linkage,
+                                      GlobalRecord::Kind GV,
+                                      SymbolFlags Flags) {
+  if (GV == GlobalRecord::Kind::Function)
+    Flags |= SymbolFlags::Text;
+  else if (GV == GlobalRecord::Kind::Variable)
+    Flags |= SymbolFlags::Data;
+
+  Name = copyString(Name);
+  auto Result = Globals.insert({Name, nullptr});
+  if (Result.second)
+    Result.first->second =
+        std::make_unique<GlobalRecord>(Name, Linkage, Flags, GV);
+  else
+    updateLinkage(Result.first->second.get(), Linkage);
+  return Result.first->second.get();
+}
+
+ObjCInterfaceRecord *RecordsSlice::addObjCInterface(StringRef Name,
+                                                    RecordLinkage Linkage,
+                                                    bool HasEHType) {
+  Name = copyString(Name);
+  auto Result = Classes.insert({Name, nullptr});
+  if (Result.second) {
+    Result.first->second =
+        std::make_unique<ObjCInterfaceRecord>(Name, Linkage, HasEHType);
+  } else {
+    // ObjC classes represent multiple symbols that could have competing
+    // linkages, in those cases assign the largest one.
+    if (Linkage >= RecordLinkage::Rexported)
+      updateLinkage(Result.first->second.get(), Linkage);
+  }
+
+  return Result.first->second.get();
+}
+
+bool ObjCInterfaceRecord::addObjCCategory(ObjCCategoryRecord *Record) {
+  auto Result = Categories.insert({Name, Record});
+  return Result.second;
+}
+
+ObjCCategoryRecord *RecordsSlice::addObjCCategory(StringRef ClassToExtend,
+                                                  StringRef Category) {
+  Category = copyString(Category);
+
+  // Add owning record first into record slice.
+  auto Result =
+      Categories.insert({std::make_pair(ClassToExtend, Category), nullptr});
+  if (Result.second)
+    Result.first->second =
+        std::make_unique<ObjCCategoryRecord>(ClassToExtend, Category);
+
+  // Then add reference to it in in the class.
+  if (auto *ObjCClass = findObjCInterface(ClassToExtend))
+    ObjCClass->addObjCCategory(Result.first->second.get());
+
+  return Result.first->second.get();
+}
+
+ObjCIVarRecord *ObjCContainerRecord::addObjCIVar(StringRef IVar,
+                                                 RecordLinkage Linkage) {
+  auto Result = IVars.insert({IVar, nullptr});
+  if (Result.second)
+    Result.first->second = std::make_unique<ObjCIVarRecord>(Name, Linkage);
+  return Result.first->second.get();
+}
+
+ObjCIVarRecord *RecordsSlice::addObjCIVar(ObjCContainerRecord *Container,
+                                          StringRef Name,
+                                          RecordLinkage Linkage) {
+  Name = copyString(Name);
+  ObjCIVarRecord *Record = Container->addObjCIVar(Name, Linkage);
+  updateLinkage(Record, Linkage);
+  return Record;
+}
+
+StringRef RecordsSlice::copyString(StringRef String) {
+  if (String.empty())
+    return {};
+
+  if (StringAllocator.identifyObject(String.data()))
+    return String;
+
+  void *Ptr = StringAllocator.Allocate(String.size(), 1);
+  memcpy(Ptr, String.data(), String.size());
+  return StringRef(reinterpret_cast<const char *>(Ptr), String.size());
+}
+
+RecordsSlice::BinaryAttrs &RecordsSlice::getBinaryAttrs() {
+  if (!hasBinaryAttrs())
+    BA = std::make_unique<BinaryAttrs>();
+  return *BA;
+}

diff  --git a/llvm/lib/TextAPI/Symbol.cpp b/llvm/lib/TextAPI/Symbol.cpp
index 20fa6362716ac..c3756fcb84355 100644
--- a/llvm/lib/TextAPI/Symbol.cpp
+++ b/llvm/lib/TextAPI/Symbol.cpp
@@ -72,5 +72,31 @@ bool Symbol::operator==(const Symbol &O) const {
          std::tie(O.Name, O.Kind, O.Targets, RHSFlags);
 }
 
+SimpleSymbol parseSymbol(StringRef SymName, const SymbolFlags Flags) {
+  if (SymName.startswith(ObjC1ClassNamePrefix))
+    return {SymName.drop_front(ObjC1ClassNamePrefix.size()),
+            SymbolKind::ObjectiveCClass};
+  if (SymName.startswith(ObjC2ClassNamePrefix))
+    return {SymName.drop_front(ObjC2ClassNamePrefix.size()),
+            SymbolKind::ObjectiveCClass};
+  if (SymName.startswith(ObjC2MetaClassNamePrefix))
+    return {SymName.drop_front(ObjC2MetaClassNamePrefix.size()),
+            SymbolKind::ObjectiveCClass};
+  if (SymName.startswith(ObjC2EHTypePrefix)) {
+    // When classes without ehtype are used in try/catch blocks
+    // a weak-defined symbol is exported. In those cases, treat these as a
+    // global instead.
+    if ((Flags & SymbolFlags::WeakDefined) == SymbolFlags::WeakDefined)
+      return {SymName, SymbolKind::GlobalSymbol};
+    return {SymName.drop_front(ObjC2EHTypePrefix.size()),
+            SymbolKind::ObjectiveCClassEHType};
+  }
+
+  if (SymName.startswith(ObjC2IVarPrefix))
+    return {SymName.drop_front(ObjC2IVarPrefix.size()),
+            SymbolKind::ObjectiveCInstanceVariable};
+  return {SymName, SymbolKind::GlobalSymbol};
+}
+
 } // end namespace MachO.
 } // end namespace llvm.

diff  --git a/llvm/unittests/TextAPI/CMakeLists.txt b/llvm/unittests/TextAPI/CMakeLists.txt
index aeda32da5ca24..f34eaa2cb5ef6 100644
--- a/llvm/unittests/TextAPI/CMakeLists.txt
+++ b/llvm/unittests/TextAPI/CMakeLists.txt
@@ -8,6 +8,7 @@ add_llvm_unittest(TextAPITests
   TextStubV3Tests.cpp
   TextStubV4Tests.cpp
   TextStubV5Tests.cpp
+  RecordTests.cpp
   )
 
 target_link_libraries(TextAPITests PRIVATE LLVMTestingSupport)

diff  --git a/llvm/unittests/TextAPI/RecordTests.cpp b/llvm/unittests/TextAPI/RecordTests.cpp
new file mode 100644
index 0000000000000..076137de6ff1f
--- /dev/null
+++ b/llvm/unittests/TextAPI/RecordTests.cpp
@@ -0,0 +1,117 @@
+//===-- RecordTests.cpp - TextAPI Record Type Test-------------------------===//
+//
+// 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 "llvm/TargetParser/Triple.h"
+#include "llvm/TextAPI/RecordsSlice.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+using namespace llvm::MachO;
+
+namespace TAPIRecord {
+
+TEST(TAPIRecord, Simple) {
+  GlobalRecord API{"_sym", RecordLinkage::Rexported,
+                   SymbolFlags::Rexported | SymbolFlags::Text |
+                       SymbolFlags::ThreadLocalValue,
+                   GlobalRecord::Kind::Function};
+  EXPECT_TRUE(API.isExported());
+  EXPECT_TRUE(API.isText());
+  EXPECT_TRUE(API.isRexported());
+  EXPECT_TRUE(API.isFunction());
+  EXPECT_TRUE(API.isThreadLocalValue());
+  EXPECT_FALSE(API.isInternal());
+  EXPECT_FALSE(API.isUndefined());
+  EXPECT_FALSE(API.isWeakDefined());
+  EXPECT_FALSE(API.isWeakReferenced());
+  EXPECT_FALSE(API.isVariable());
+}
+
+TEST(TAPIRecord, SimpleObjC) {
+  ObjCInterfaceRecord Class{"NSObject", RecordLinkage::Exported};
+  ObjCInterfaceRecord ClassEH{"NSObject", RecordLinkage::Exported,
+                              /*HasEHType=*/true};
+
+  EXPECT_TRUE(Class.isExported());
+  EXPECT_EQ(Class.isExported(), ClassEH.isExported());
+  EXPECT_FALSE(Class.hasExceptionAttribute());
+  EXPECT_TRUE(ClassEH.hasExceptionAttribute());
+  EXPECT_EQ(ObjCIVarRecord::createScopedName("NSObject", "var"),
+            "NSObject.var");
+}
+
+TEST(TAPIRecord, SimpleSlice) {
+  Triple T("arm64-apple-macosx13.3");
+  RecordsSlice Slice(T);
+  EXPECT_TRUE(Slice.isEmpty());
+  Slice.addRecord("_OBJC_CLASS_$_NSObject", SymbolFlags::None,
+                  GlobalRecord::Kind::Unknown, RecordLinkage::Rexported);
+  Slice.addRecord("_OBJC_METACLASS_$_NSObject", SymbolFlags::None,
+                  GlobalRecord::Kind::Unknown, RecordLinkage::Rexported);
+  Slice.addRecord("_OBJC_IVAR_$_NSConcreteValue.typeInfo", SymbolFlags::None,
+                  GlobalRecord::Kind::Unknown, RecordLinkage::Exported);
+  Slice.addRecord("_OBJC_IVAR_$_NSObject.objInfo", SymbolFlags::None,
+                  GlobalRecord::Kind::Unknown, RecordLinkage::Exported);
+  Slice.addRecord("_foo", SymbolFlags::WeakDefined | SymbolFlags::Rexported,
+                  GlobalRecord::Kind::Variable, RecordLinkage::Rexported);
+  EXPECT_FALSE(Slice.isEmpty());
+
+  // Check global.
+  EXPECT_FALSE(Slice.findGlobal("_foo", GlobalRecord::Kind::Function));
+  auto *Global = Slice.findGlobal("_foo");
+  ASSERT_TRUE(Global);
+  EXPECT_TRUE(Global->isVariable());
+  EXPECT_TRUE(Global->isWeakDefined());
+  EXPECT_TRUE(Global->isRexported());
+  EXPECT_TRUE(Global->isData());
+
+  // Check class.
+  auto *Class = Slice.findObjCInterface("NSObject");
+  ASSERT_TRUE(Class);
+  EXPECT_TRUE(Class->isRexported());
+  EXPECT_TRUE(Class->isData());
+  EXPECT_FALSE(Class->hasExceptionAttribute());
+  auto ClassIVar = Class->findObjCIVar("objInfo");
+  ASSERT_TRUE(ClassIVar);
+  EXPECT_TRUE(ClassIVar->isExported());
+  EXPECT_FALSE(ClassIVar->isRexported());
+
+  // Check fall-back extension.
+  auto *Cat = Slice.findObjCCategory("NSConcreteValue", "");
+  ASSERT_TRUE(Cat);
+  // There is not linkage information for categories.
+  EXPECT_FALSE(Cat->isExported());
+  EXPECT_FALSE(Cat->isInternal());
+  auto CatIVar = Cat->findObjCIVar("typeInfo");
+  EXPECT_TRUE(CatIVar);
+  EXPECT_TRUE(CatIVar->isExported());
+  EXPECT_FALSE(CatIVar->isRexported());
+
+  // Find IVars directly.
+  auto TIIVar =
+      Slice.findObjCIVar(/*IsScopedName=*/true, "NSConcreteValue.typeInfo");
+  ASSERT_TRUE(TIIVar);
+  EXPECT_EQ(CatIVar->getName(), TIIVar->getName());
+
+  auto OIIVar = Slice.findObjCIVar(/*IsScopedName=*/false, "objInfo");
+  ASSERT_TRUE(OIIVar);
+  EXPECT_EQ(ClassIVar->getName(), OIIVar->getName());
+
+  EXPECT_FALSE(Slice.findObjCIVar(/*IsScopedName=*/true, "typeInfo"));
+}
+
+TEST(TAPIRecord, LibraryAttrs) {
+  Triple T("arm64-apple-ios15.1");
+  RecordsSlice Slice(T);
+  EXPECT_TRUE(Slice.isEmpty());
+
+  auto BA = Slice.getBinaryAttrs();
+  EXPECT_TRUE(Slice.hasBinaryAttrs());
+}
+
+} // namespace TAPIRecord


        


More information about the llvm-commits mailing list