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

Cyndy Ishida via llvm-commits llvm-commits at lists.llvm.org
Fri Dec 1 09:39:31 PST 2023


https://github.com/cyndyishida created https://github.com/llvm/llvm-project/pull/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.

>From 1651f0d80f6eec7ce8f3b76a36459d7bb24b840e Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Thu, 30 Nov 2023 21:19:38 -0800
Subject: [PATCH] [TextAPI] Introduce Records & RecordSlice Types

`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.
---
 llvm/include/llvm/TextAPI/Record.h       | 179 ++++++++++++++++++
 llvm/include/llvm/TextAPI/RecordsSlice.h | 182 ++++++++++++++++++
 llvm/include/llvm/TextAPI/Symbol.h       |  17 ++
 llvm/lib/TextAPI/CMakeLists.txt          |   1 +
 llvm/lib/TextAPI/RecordsSlice.cpp        | 231 +++++++++++++++++++++++
 llvm/lib/TextAPI/Symbol.cpp              |  26 +++
 llvm/unittests/TextAPI/CMakeLists.txt    |   1 +
 llvm/unittests/TextAPI/RecordTests.cpp   | 118 ++++++++++++
 8 files changed, 755 insertions(+)
 create mode 100644 llvm/include/llvm/TextAPI/Record.h
 create mode 100644 llvm/include/llvm/TextAPI/RecordsSlice.h
 create mode 100644 llvm/lib/TextAPI/RecordsSlice.cpp
 create mode 100644 llvm/unittests/TextAPI/RecordTests.cpp

diff --git a/llvm/include/llvm/TextAPI/Record.h b/llvm/include/llvm/TextAPI/Record.h
new file mode 100644
index 000000000000000..482d380c57e5acf
--- /dev/null
+++ b/llvm/include/llvm/TextAPI/Record.h
@@ -0,0 +1,179 @@
+//===- 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 <set>
+#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; }
+
+  // FIXME: does this get called?
+  bool operator<(const Record &O) const {
+    return std::tie(Name, Linkage, Flags) <
+           std::tie(O.Name, O.Linkage, O.Flags);
+  }
+
+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.
+struct ObjCIVarRecord : public Record {
+public:
+  ObjCIVarRecord(StringRef Name, RecordLinkage Linkage)
+      : Record({Name, Linkage, SymbolFlags::Data}) {}
+
+  static std::string createSymbolName(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 IsEHType = false)
+      : ObjCContainerRecord(Name, Linkage), IsEHType(IsEHType) {}
+
+  bool hasExceptionAttribute() const { return IsEHType; }
+  bool addObjCCategory(ObjCCategoryRecord *Record);
+
+private:
+  bool IsEHType;
+  // 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 000000000000000..085983cd451e083
--- /dev/null
+++ b/llvm/include/llvm/TextAPI/RecordsSlice.h
@@ -0,0 +1,182 @@
+//===- 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 IsEHType Whether symbol represents an eh_type.
+  /// \return The non-owning pointer to added record in slice.
+  ObjCInterfaceRecord *addObjCInterface(StringRef Name, RecordLinkage Linkage,
+                                        bool IsEHType = 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 The full symbol name for instance variable.
+  /// \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 {
+    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;
+    StringRef ParentUmbrella;
+    std::vector<StringRef> AllowableClients;
+    std::vector<StringRef> RexportedLibraries;
+    std::vector<StringRef> RPaths;
+    StringRef InstallName;
+    StringRef UUID;
+    StringRef Path;
+  };
+
+  /// 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.
+  /// \return Whether linkage was updated.
+  bool updateLinkage(Record *R, RecordLinkage 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 35d9a64dee1c197..fc7f600b2e02002 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 4088757afb900ea..5622ae7c6d724ed 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 000000000000000..d3fde5c27d3359f
--- /dev/null
+++ b/llvm/lib/TextAPI/RecordsSlice.cpp
@@ -0,0 +1,231 @@
+//===- 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, /*IsEHType=*/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");
+}
+
+bool RecordsSlice::updateLinkage(Record *R, RecordLinkage L) {
+  if (R->Linkage >= L)
+    return false;
+  R->Linkage = L;
+  return true;
+}
+
+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 symbol 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 IsEHType) {
+  Name = copyString(Name);
+  auto Result = Classes.insert({Name, nullptr});
+  if (Result.second) {
+    Result.first->second =
+        std::make_unique<ObjCInterfaceRecord>(Name, Linkage, IsEHType);
+  } 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 20fa6362716acf3..c3756fcb84355fa 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 aeda32da5ca24c1..f34eaa2cb5ef671 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 000000000000000..48fab04f14fde9f
--- /dev/null
+++ b/llvm/unittests/TextAPI/RecordTests.cpp
@@ -0,0 +1,118 @@
+//===-- 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,
+                              /*IsEHType=*/true};
+
+  EXPECT_TRUE(Class.isExported());
+  EXPECT_EQ(Class.isExported(), ClassEH.isExported());
+  EXPECT_FALSE(Class.hasExceptionAttribute());
+  EXPECT_TRUE(ClassEH.hasExceptionAttribute());
+  EXPECT_EQ(ObjCIVarRecord::createSymbolName("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