[llvm] [DebugInfo] Add DW_TAG_property support to LLVM DebugInfo (PR #215776)

Piotr Jeremicz via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 13 01:07:35 PDT 2026


https://github.com/piotrekjeremicz updated https://github.com/llvm/llvm-project/pull/215776

>From e03090c031a33c21f4e3046863da46d01b04fced Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Wed, 12 Aug 2026 22:15:52 +0200
Subject: [PATCH] [DebugInfo] Add DW_TAG_property support to LLVM DebugInfo

DWARF v6 adds DW_TAG_property to represent an entity accessed like a
data member but implemented via an accessor, such as an Objective-C
@property backed by an ivar. This adds a DIProperty metadata node,
plumbs it through the IR (LLParser/AsmWriter, bitcode, DIBuilder,
Verifier), and emits DW_TAG_property/DW_AT_property_forward DIEs in
DwarfUnit, anchored on the accessor's own subprogram DIE.

This covers the LLVM IR and DWARF layers only; Clang emission and
LLDB consumption are left for follow-up patches.

Assisted-by: Claude
---
 llvm/docs/LangRef.md                          | 14 ++++
 llvm/include/llvm-c/DebugInfo.h               |  1 +
 llvm/include/llvm/BinaryFormat/Dwarf.def      |  6 ++
 llvm/include/llvm/Bitcode/LLVMBitCodes.h      |  1 +
 llvm/include/llvm/IR/DIBuilder.h              | 13 +++
 llvm/include/llvm/IR/DebugInfoMetadata.h      | 80 +++++++++++++++++++
 llvm/include/llvm/IR/Metadata.def             |  1 +
 llvm/lib/AsmParser/LLParser.cpp               | 18 +++++
 llvm/lib/Bitcode/Reader/MetadataLoader.cpp    | 15 ++++
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp     | 16 ++++
 llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp    |  1 +
 llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp     | 35 ++++++++
 llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h       |  5 ++
 llvm/lib/DebugInfo/DWARF/DWARFDie.cpp         |  5 ++
 llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp    |  5 +-
 llvm/lib/IR/AsmWriter.cpp                     | 12 +++
 llvm/lib/IR/DIBuilder.cpp                     |  6 ++
 llvm/lib/IR/DebugInfo.cpp                     |  2 +
 llvm/lib/IR/DebugInfoMetadata.cpp             | 15 ++++
 llvm/lib/IR/LLVMContextImpl.h                 | 25 ++++++
 llvm/lib/IR/Verifier.cpp                      | 15 ++++
 .../DirectX/DXILWriter/DXILBitcodeWriter.cpp  |  4 +
 llvm/test/Assembler/diproperty.ll             | 20 +++++
 llvm/test/DebugInfo/Generic/property.ll       | 59 ++++++++++++++
 llvm/test/Verifier/diproperty.ll              | 18 +++++
 llvm/unittests/IR/MetadataTest.cpp            | 38 +++++++++
 26 files changed, 429 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/Assembler/diproperty.ll
 create mode 100644 llvm/test/DebugInfo/Generic/property.ll
 create mode 100644 llvm/test/Verifier/diproperty.ll

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index f2d19e4b6280c..6aa98e716343c 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -7253,6 +7253,20 @@ be used for the structure type.
                      getter: "getFoo", attributes: 7, type: !2)
 ```
 
+##### DIProperty
+
+`DIProperty` nodes represent DWARF `DW_TAG_property` entities: an entity
+that is syntactically accessed like a data member but whose access is
+implemented by an accessor. Currently only the getter is modelled, and it
+must point to the data member holding the property's backing storage (for
+example, an Objective-C `@property`'s backing ivar).
+
+```text
+!3 = !DIDerivedType(tag: DW_TAG_member, name: "_x", scope: !1, file: !2,
+                    line: 8, baseType: !4, size: 32)
+!5 = !DIProperty(name: "x", file: !2, line: 8, type: !4, getter: !3)
+```
+
 ##### DIImportedEntity
 
 `DIImportedEntity` nodes represent entities (such as modules) imported into a
diff --git a/llvm/include/llvm-c/DebugInfo.h b/llvm/include/llvm-c/DebugInfo.h
index 155cbe32ee15e..2f7afa3f16aba 100644
--- a/llvm/include/llvm-c/DebugInfo.h
+++ b/llvm/include/llvm-c/DebugInfo.h
@@ -211,6 +211,7 @@ enum {
   LLVMDIAssignIDMetadataKind,
   LLVMDISubrangeTypeMetadataKind,
   LLVMDIFixedPointTypeMetadataKind,
+  LLVMDIPropertyMetadataKind,
 };
 typedef unsigned LLVMMetadataKind;
 
diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.def b/llvm/include/llvm/BinaryFormat/Dwarf.def
index 32195aae562c3..054fa292b7c53 100644
--- a/llvm/include/llvm/BinaryFormat/Dwarf.def
+++ b/llvm/include/llvm/BinaryFormat/Dwarf.def
@@ -237,6 +237,11 @@ HANDLE_DW_TAG(0x0048, call_site, 5, DWARF, DW_KIND_NONE)
 HANDLE_DW_TAG(0x0049, call_site_parameter, 5, DWARF, DW_KIND_NONE)
 HANDLE_DW_TAG(0x004a, skeleton_unit, 5, DWARF, DW_KIND_NONE)
 HANDLE_DW_TAG(0x004b, immutable_type, 5, DWARF, DW_KIND_TYPE)
+// New in DWARF v6:
+HANDLE_DW_TAG(0x004c, property, 6, DWARF, DW_KIND_NONE)
+HANDLE_DW_TAG(0x004d, property_getter, 6, DWARF, DW_KIND_NONE)
+HANDLE_DW_TAG(0x004e, property_setter, 6, DWARF, DW_KIND_NONE)
+HANDLE_DW_TAG(0x004f, property_stored, 6, DWARF, DW_KIND_NONE)
 // Vendor extensions:
 HANDLE_DW_TAG(0x4081, MIPS_loop, 0, MIPS, DW_KIND_NONE)
 // Conflicting:
@@ -432,6 +437,7 @@ HANDLE_DW_AT(0x8c, loclists_base, 5, DWARF)
 // New in Dwarf v6:
 HANDLE_DW_AT(0x90, language_name, 6, DWARF)
 HANDLE_DW_AT(0x91, language_version, 6, DWARF)
+HANDLE_DW_AT(0x95, property_forward, 6, DWARF)
 
 // Vendor extensions:
 HANDLE_DW_AT(0x806, GHS_namespace_alias, 0, GHS)
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 358f9a65a80af..013a6042d74aa 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -400,6 +400,7 @@ enum MetadataCodes {
   METADATA_ASSIGN_ID = 47,        // [distinct, ...]
   METADATA_SUBRANGE_TYPE = 48,    // [distinct, ...]
   METADATA_FIXED_POINT_TYPE = 49, // [distinct, ...]
+  METADATA_PROPERTY = 50,         // [distinct, name, file, line, type, getter]
 };
 
 // The constants block (CONSTANTS_BLOCK_ID) describes emission for each
diff --git a/llvm/include/llvm/IR/DIBuilder.h b/llvm/include/llvm/IR/DIBuilder.h
index 3acb9c5f955e8..4f3031edb1ba1 100644
--- a/llvm/include/llvm/IR/DIBuilder.h
+++ b/llvm/include/llvm/IR/DIBuilder.h
@@ -554,6 +554,19 @@ namespace llvm {
                        StringRef GetterName, StringRef SetterName,
                        unsigned PropertyAttributes, DIType *Ty);
 
+    /// Create debugging information entry for a property, i.e. an entity that
+    /// is accessed like a data member but whose access is implemented by an
+    /// accessor.
+    /// \param Name          Property name.
+    /// \param File          File where this property is defined.
+    /// \param LineNumber    Line number.
+    /// \param Ty            Type of the property.
+    /// \param Getter        The data member the getter forwards to, holding
+    ///                      the property's backing storage.
+    LLVM_ABI DIProperty *createProperty(StringRef Name, DIFile *File,
+                                        unsigned LineNumber, DIType *Ty,
+                                        DIDerivedType *Getter);
+
     /// Create debugging information entry for a class.
     /// \param Scope        Scope in which this class is defined.
     /// \param Name         class name.
diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h
index 846342bee6071..8d793e2397931 100644
--- a/llvm/include/llvm/IR/DebugInfoMetadata.h
+++ b/llvm/include/llvm/IR/DebugInfoMetadata.h
@@ -223,6 +223,7 @@ class DINode : public MDNode {
     case DILocalVariableKind:
     case DILabelKind:
     case DIObjCPropertyKind:
+    case DIPropertyKind:
     case DIImportedEntityKind:
     case DIModuleKind:
     case DIGenericSubrangeKind:
@@ -4425,6 +4426,85 @@ class DIObjCProperty : public DINode {
   }
 };
 
+/// A property of a class or structure.
+///
+/// Represents a DWARF \c DW_TAG_property: an entity that is syntactically
+/// accessed like a data member, but whose access is implemented by invoking a
+/// user-defined or compiler-generated accessor.
+///
+/// Currently only the getter is modelled, and it must forward to a data
+/// member holding the property's backing storage.
+class DIProperty : public DINode {
+  friend class LLVMContextImpl;
+  friend class MDNode;
+
+  unsigned Line;
+
+  DIProperty(LLVMContext &C, StorageType Storage, unsigned Line,
+             ArrayRef<Metadata *> Ops);
+  ~DIProperty() = default;
+
+  static DIProperty *getImpl(LLVMContext &Context, StringRef Name, DIFile *File,
+                             unsigned Line, DIType *Type, DINode *Getter,
+                             StorageType Storage, bool ShouldCreate = true) {
+    return getImpl(Context, getCanonicalMDString(Context, Name), File, Line,
+                   Type, Getter, Storage, ShouldCreate);
+  }
+  LLVM_ABI static DIProperty *getImpl(LLVMContext &Context, MDString *Name,
+                                      Metadata *File, unsigned Line,
+                                      Metadata *Type, Metadata *Getter,
+                                      StorageType Storage,
+                                      bool ShouldCreate = true);
+
+  TempDIProperty cloneImpl() const {
+    return getTemporary(getContext(), getName(), getFile(), getLine(),
+                        getType(), getGetter());
+  }
+
+public:
+  DEFINE_MDNODE_GET(DIProperty,
+                    (StringRef Name, DIFile *File, unsigned Line, DIType *Type,
+                     DINode *Getter),
+                    (Name, File, Line, Type, Getter))
+  DEFINE_MDNODE_GET(DIProperty,
+                    (MDString * Name, Metadata *File, unsigned Line,
+                     Metadata *Type, Metadata *Getter),
+                    (Name, File, Line, Type, Getter))
+
+  TempDIProperty clone() const { return cloneImpl(); }
+
+  unsigned getLine() const { return Line; }
+  StringRef getName() const { return getStringOperand(0); }
+  DIFile *getFile() const { return cast_or_null<DIFile>(getRawFile()); }
+  DIType *getType() const { return cast_or_null<DIType>(getRawType()); }
+
+  /// The entity the getter forwards to, i.e. the target of
+  /// \c DW_AT_property_forward on this property's \c DW_TAG_property_getter
+  /// child. This is the data member holding the property's backing storage.
+  DINode *getGetter() const { return cast_or_null<DINode>(getRawGetter()); }
+
+  StringRef getFilename() const {
+    if (auto *F = getFile())
+      return F->getFilename();
+    return "";
+  }
+
+  StringRef getDirectory() const {
+    if (auto *F = getFile())
+      return F->getDirectory();
+    return "";
+  }
+
+  MDString *getRawName() const { return getOperandAs<MDString>(0); }
+  Metadata *getRawFile() const { return getOperand(1); }
+  Metadata *getRawType() const { return getOperand(2); }
+  Metadata *getRawGetter() const { return getOperand(3); }
+
+  static bool classof(const Metadata *MD) {
+    return MD->getMetadataID() == DIPropertyKind;
+  }
+};
+
 /// An imported module (C++ using directive or similar).
 ///
 /// Uses the SubclassData32 Metadata slot.
diff --git a/llvm/include/llvm/IR/Metadata.def b/llvm/include/llvm/IR/Metadata.def
index 511bf48707f00..49404674f4e7b 100644
--- a/llvm/include/llvm/IR/Metadata.def
+++ b/llvm/include/llvm/IR/Metadata.def
@@ -110,6 +110,7 @@ HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DIGlobalVariable)
 HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DILocalVariable)
 HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DILabel)
 HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DIObjCProperty)
+HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DIProperty)
 HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DIImportedEntity)
 HANDLE_SPECIALIZED_MDNODE_LEAF(DIAssignID)
 HANDLE_SPECIALIZED_MDNODE_BRANCH(DIMacroNode)
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index edff818b3b152..468373c82ff82 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -6700,6 +6700,24 @@ bool LLParser::parseDIObjCProperty(MDNode *&Result, bool IsDistinct) {
   return false;
 }
 
+/// parseDIProperty:
+///   ::= !DIProperty(name: "x", file: !1, line: 7, type: !2,
+///                   getter: !3)
+bool LLParser::parseDIProperty(MDNode *&Result, bool IsDistinct) {
+#define VISIT_MD_FIELDS(OPTIONAL, REQUIRED)                                    \
+  OPTIONAL(name, MDStringField, );                                             \
+  OPTIONAL(file, MDField, );                                                   \
+  OPTIONAL(line, LineField, );                                                 \
+  OPTIONAL(type, MDField, );                                                   \
+  OPTIONAL(getter, MDField, );
+  PARSE_MD_FIELDS();
+#undef VISIT_MD_FIELDS
+
+  Result = GET_OR_DISTINCT(DIProperty, (Context, name.Val, file.Val, line.Val,
+                                        type.Val, getter.Val));
+  return false;
+}
+
 /// parseDIImportedEntity:
 ///   ::= !DIImportedEntity(tag: DW_TAG_imported_module, scope: !0, entity: !1,
 ///                         line: 7, name: "foo", elements: !2)
diff --git a/llvm/lib/Bitcode/Reader/MetadataLoader.cpp b/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
index 8b7beb1a8ff9e..11ba00ccfaec5 100644
--- a/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
+++ b/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
@@ -996,6 +996,7 @@ MetadataLoader::MetadataLoaderImpl::lazyLoadModuleMetadataBlock() {
       case bitc::METADATA_LABEL:
       case bitc::METADATA_EXPRESSION:
       case bitc::METADATA_OBJC_PROPERTY:
+      case bitc::METADATA_PROPERTY:
       case bitc::METADATA_IMPORTED_ENTITY:
       case bitc::METADATA_GLOBAL_VAR_EXPR:
       case bitc::METADATA_GENERIC_SUBRANGE:
@@ -2416,6 +2417,20 @@ Error MetadataLoader::MetadataLoaderImpl::parseOneMetadata(
     NextMetadataNo++;
     break;
   }
+  case bitc::METADATA_PROPERTY: {
+    if (Record.size() != 6)
+      return error("Invalid record");
+
+    IsDistinct = Record[0];
+    MetadataList.assignValue(
+        GET_OR_DISTINCT(DIProperty, (Context, getMDString(Record[1]),
+                                     getMDOrNull(Record[2]), Record[3],
+                                     getDITypeRefOrNull(Record[4]),
+                                     getMDOrNull(Record[5]))),
+        NextMetadataNo);
+    NextMetadataNo++;
+    break;
+  }
   case bitc::METADATA_IMPORTED_ENTITY: {
     if (Record.size() < 6 || Record.size() > 8)
       return error("Invalid DIImportedEntity record");
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 571336c217797..e08d5b403c552 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -410,6 +410,8 @@ class ModuleBitcodeWriter : public ModuleBitcodeWriterBase {
                                        unsigned Abbrev);
   void writeDIObjCProperty(const DIObjCProperty *N,
                            SmallVectorImpl<uint64_t> &Record, unsigned Abbrev);
+  void writeDIProperty(const DIProperty *N, SmallVectorImpl<uint64_t> &Record,
+                       unsigned Abbrev);
   void writeDIImportedEntity(const DIImportedEntity *N,
                              SmallVectorImpl<uint64_t> &Record,
                              unsigned Abbrev);
@@ -2510,6 +2512,20 @@ void ModuleBitcodeWriter::writeDIObjCProperty(const DIObjCProperty *N,
   Record.clear();
 }
 
+void ModuleBitcodeWriter::writeDIProperty(const DIProperty *N,
+                                          SmallVectorImpl<uint64_t> &Record,
+                                          unsigned Abbrev) {
+  Record.push_back(N->isDistinct());
+  Record.push_back(VE.getMetadataOrNullID(N->getRawName()));
+  Record.push_back(VE.getMetadataOrNullID(N->getFile()));
+  Record.push_back(N->getLine());
+  Record.push_back(VE.getMetadataOrNullID(N->getType()));
+  Record.push_back(VE.getMetadataOrNullID(N->getGetter()));
+
+  Stream.EmitRecord(bitc::METADATA_PROPERTY, Record, Abbrev);
+  Record.clear();
+}
+
 void ModuleBitcodeWriter::writeDIImportedEntity(
     const DIImportedEntity *N, SmallVectorImpl<uint64_t> &Record,
     unsigned Abbrev) {
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
index 0b21819cb7bdf..c94ef946e20eb 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
@@ -1340,6 +1340,7 @@ void DwarfDebug::finalizeModuleInfo() {
     // Emit DW_AT_containing_type attribute to connect types with their
     // vtable holding type.
     TheCU.constructContainingTypeDIEs();
+    TheCU.constructPropertyForwardDIEs();
 
     // Add CU specific attributes if we need to add any.
     // If we're splitting the dwarf out now that we've got the entire
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
index 78c0769e49161..60e850a2774c1 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
@@ -510,6 +510,12 @@ void DwarfUnit::addSourceLine(DIE &Die, const DIObjCProperty *Ty) {
   addSourceLine(Die, Ty->getLine(), /*Column*/ 0, Ty->getFile());
 }
 
+void DwarfUnit::addSourceLine(DIE &Die, const DIProperty *P) {
+  assert(P);
+
+  addSourceLine(Die, P->getLine(), /*Column*/ 0, P->getFile());
+}
+
 void DwarfUnit::addConstantFPValue(DIE &Die, const ConstantFP *CFP) {
   // Pass this down to addConstantValue as an unsigned bag of bits.
   addConstantValue(Die, CFP->getValueAPF().bitcastToAPInt(), true);
@@ -1145,6 +1151,8 @@ void DwarfUnit::constructTypeDIE(DIE &Buffer, const DICompositeType *CTy) {
         if (unsigned PropertyAttributes = Property->getAttributes())
           addUInt(ElemDie, dwarf::DW_AT_APPLE_property_attribute, std::nullopt,
                   PropertyAttributes);
+      } else if (auto *Property = dyn_cast<DIProperty>(Element)) {
+        constructPropertyDIE(Buffer, Property);
       } else if (auto *Composite = dyn_cast<DICompositeType>(Element)) {
         if (Composite->getTag() == dwarf::DW_TAG_variant_part) {
           DIE &VariantPart = createAndAddDIE(Composite->getTag(), Buffer);
@@ -2012,6 +2020,33 @@ DIE &DwarfUnit::constructMemberDIE(DIE &Buffer, const DIDerivedType *DT) {
   return MemberDie;
 }
 
+void DwarfUnit::constructPropertyDIE(DIE &Buffer, const DIProperty *P) {
+  DIE &PropertyDie = createAndAddDIE(dwarf::DW_TAG_property, Buffer, P);
+  addString(PropertyDie, dwarf::DW_AT_name, P->getName());
+  if (DIType *Ty = P->getType())
+    addType(PropertyDie, Ty);
+  addSourceLine(PropertyDie, P);
+
+  if (DINode *Getter = P->getGetter()) {
+    DIE &GetterDie =
+        createAndAddDIE(dwarf::DW_TAG_property_getter, PropertyDie);
+    PropertyForwardMap.insert(std::make_pair(&GetterDie, Getter));
+  }
+}
+
+void DwarfUnit::constructPropertyForwardDIEs() {
+  for (auto &P : PropertyForwardMap) {
+    DIE &GetterDie = *P.first;
+    const DINode *Target = P.second;
+    if (!Target)
+      continue;
+    DIE *TargetDie = getDIE(Target);
+    if (!TargetDie)
+      continue;
+    addDIEEntry(GetterDie, dwarf::DW_AT_property_forward, *TargetDie);
+  }
+}
+
 DIE *DwarfUnit::getOrCreateStaticMemberDIE(const DIDerivedType *DT) {
   if (!DT)
     return nullptr;
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h
index 139fae5621940..42d537128642e 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h
@@ -73,6 +73,7 @@ class DwarfUnit : public DIEUnit {
   /// DW_AT_containing_type attribute. This attribute points to a DIE that
   /// corresponds to the MDNode mapped with the subprogram DIE.
   DenseMap<DIE *, const DINode *> ContainingTypeMap;
+  DenseMap<DIE *, const DINode *> PropertyForwardMap;
 
   DwarfUnit(dwarf::Tag, const DICompileUnit *Node, AsmPrinter *A,
             DwarfDebug *DW, DwarfFile *DWU, unsigned UniqueID = 0);
@@ -228,6 +229,7 @@ class DwarfUnit : public DIEUnit {
   void addSourceLine(DIE &Die, const DILabel *L);
   void addSourceLine(DIE &Die, const DIType *Ty);
   void addSourceLine(DIE &Die, const DIObjCProperty *Ty);
+  void addSourceLine(DIE &Die, const DIProperty *P);
 
   /// Add constant value entry in variable DIE.
   void addConstantValue(DIE &Die, const ConstantInt *CI, const DIType *Ty);
@@ -279,6 +281,8 @@ class DwarfUnit : public DIEUnit {
   /// Construct DIEs for types that contain vtables.
   void constructContainingTypeDIEs();
 
+  void constructPropertyForwardDIEs();
+
   /// Construct function argument DIEs.
   ///
   /// \returns The index of the object parameter in \c Args if one exists.
@@ -389,6 +393,7 @@ class DwarfUnit : public DIEUnit {
   void constructArrayTypeDIE(DIE &Buffer, const DICompositeType *CTy);
   void constructEnumTypeDIE(DIE &Buffer, const DICompositeType *CTy);
   DIE &constructMemberDIE(DIE &Buffer, const DIDerivedType *DT);
+  void constructPropertyDIE(DIE &Buffer, const DIProperty *P);
   void constructTemplateTypeParameterDIE(DIE &Buffer,
                                          const DITemplateTypeParameter *TP);
   void constructTemplateValueParameterDIE(DIE &Buffer,
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFDie.cpp b/llvm/lib/DebugInfo/DWARF/DWARFDie.cpp
index 997e2b60e8125..3deaa75b03f59 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFDie.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFDie.cpp
@@ -251,6 +251,11 @@ static void dumpAttribute(raw_ostream &OS, const DWARFDie &Die,
             Die.getAttributeValueAsReferencedDie(FormValue).getName(
                 DINameKind::LinkageName))
       OS << Space << "\"" << Name << '\"';
+  } else if (Attr == DW_AT_property_forward) {
+    if (const char *Name =
+            Die.getAttributeValueAsReferencedDie(FormValue).getName(
+                DINameKind::ShortName))
+      OS << Space << "\"" << Name << '\"';
   } else if (Attr == DW_AT_APPLE_property) {
     auto PropDIE = Die.getAttributeValueAsReferencedDie(FormValue);
     if (auto PropNameOrErr = getApplePropertyName(PropDIE))
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp b/llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp
index 32775f348bb22..ebd54e18bbca9 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp
@@ -1986,8 +1986,11 @@ void DWARFVerifier::verifyNameIndexCompleteness(
   case DW_TAG_GNU_template_template_param:
     return;
 
-  // Object members aren't globally visible.
+  // Object members aren't globally visible. Properties are accessed through
+  // their containing subprogram/type, not looked up globally by name, so
+  // they belong in the same category.
   case DW_TAG_member:
+  case DW_TAG_property:
     return;
 
   // DW_TAG_LLVM_annotation DIEs attach metadata to other DIEs.
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index cad4f17b0db91..35be0b18bad99 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -2700,6 +2700,18 @@ static void writeDIObjCProperty(raw_ostream &Out, const DIObjCProperty *N,
   Out << ")";
 }
 
+static void writeDIProperty(raw_ostream &Out, const DIProperty *N,
+                            AsmWriterContext &WriterCtx) {
+  Out << "!DIProperty(";
+  MDFieldPrinter Printer(Out, WriterCtx);
+  Printer.printString("name", N->getName());
+  Printer.printMetadata("file", N->getRawFile());
+  Printer.printInt("line", N->getLine());
+  Printer.printMetadata("type", N->getRawType());
+  Printer.printMetadata("getter", N->getRawGetter());
+  Out << ")";
+}
+
 static void writeDIImportedEntity(raw_ostream &Out, const DIImportedEntity *N,
                                   AsmWriterContext &WriterCtx) {
   Out << "!DIImportedEntity(";
diff --git a/llvm/lib/IR/DIBuilder.cpp b/llvm/lib/IR/DIBuilder.cpp
index fb18fc3cc90c5..44719331589bb 100644
--- a/llvm/lib/IR/DIBuilder.cpp
+++ b/llvm/lib/IR/DIBuilder.cpp
@@ -572,6 +572,12 @@ DIBuilder::createObjCProperty(StringRef Name, DIFile *File, unsigned LineNumber,
                              SetterName, PropertyAttributes, Ty);
 }
 
+DIProperty *DIBuilder::createProperty(StringRef Name, DIFile *File,
+                                      unsigned LineNumber, DIType *Ty,
+                                      DIDerivedType *Getter) {
+  return DIProperty::get(VMContext, Name, File, LineNumber, Ty, Getter);
+}
+
 DITemplateTypeParameter *
 DIBuilder::createTemplateTypeParameter(DIScope *Context, StringRef Name,
                                        DIType *Ty, bool isDefault) {
diff --git a/llvm/lib/IR/DebugInfo.cpp b/llvm/lib/IR/DebugInfo.cpp
index e164ec54ead60..a42707604e61f 100644
--- a/llvm/lib/IR/DebugInfo.cpp
+++ b/llvm/lib/IR/DebugInfo.cpp
@@ -273,6 +273,8 @@ void DebugInfoFinder::processType(DIType *DT) {
         processType(T);
       else if (auto *SP = dyn_cast<DISubprogram>(D))
         processSubprogram(SP);
+      else if (auto *P = dyn_cast<DIProperty>(D))
+        processType(P->getType());
       else if (auto *SR = dyn_cast_or_null<DISubrange>(D)) {
         auto VisitBound = [&](DISubrange::BoundType Bound) {
           if (auto *BV = dyn_cast_if_present<DIVariable *>(Bound))
diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp
index 88f7f2f6240e0..47b99613173b6 100644
--- a/llvm/lib/IR/DebugInfoMetadata.cpp
+++ b/llvm/lib/IR/DebugInfoMetadata.cpp
@@ -2624,6 +2624,21 @@ DIObjCProperty *DIObjCProperty::getImpl(
   DEFINE_GETIMPL_STORE(DIObjCProperty, (Line, Attributes), Ops);
 }
 
+DIProperty::DIProperty(LLVMContext &C, StorageType Storage, unsigned Line,
+                       ArrayRef<Metadata *> Ops)
+    : DINode(C, DIPropertyKind, Storage, dwarf::DW_TAG_property, Ops),
+      Line(Line) {}
+
+DIProperty *DIProperty::getImpl(LLVMContext &Context, MDString *Name,
+                                Metadata *File, unsigned Line, Metadata *Type,
+                                Metadata *Getter, StorageType Storage,
+                                bool ShouldCreate) {
+  assert(isCanonical(Name) && "Expected canonical MDString");
+  DEFINE_GETIMPL_LOOKUP(DIProperty, (Name, File, Line, Type, Getter));
+  Metadata *Ops[] = {Name, File, Type, Getter};
+  DEFINE_GETIMPL_STORE(DIProperty, (Line), Ops);
+}
+
 DIImportedEntity *DIImportedEntity::getImpl(LLVMContext &Context, unsigned Tag,
                                             Metadata *Scope, Metadata *Entity,
                                             Metadata *File, unsigned Line,
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 41c8a92c56eda..185b172423052 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -1396,6 +1396,31 @@ template <> struct MDNodeKeyImpl<DIObjCProperty> {
   }
 };
 
+template <> struct MDNodeKeyImpl<DIProperty> {
+  MDString *Name;
+  Metadata *File;
+  unsigned Line;
+  Metadata *Type;
+  Metadata *Getter;
+
+  MDNodeKeyImpl(MDString *Name, Metadata *File, unsigned Line, Metadata *Type,
+                Metadata *Getter)
+      : Name(Name), File(File), Line(Line), Type(Type), Getter(Getter) {}
+  MDNodeKeyImpl(const DIProperty *N)
+      : Name(N->getRawName()), File(N->getRawFile()), Line(N->getLine()),
+        Type(N->getRawType()), Getter(N->getRawGetter()) {}
+
+  bool isKeyOf(const DIProperty *RHS) const {
+    return Name == RHS->getRawName() && File == RHS->getRawFile() &&
+           Line == RHS->getLine() && Type == RHS->getRawType() &&
+           Getter == RHS->getRawGetter();
+  }
+
+  unsigned getHashValue() const {
+    return hash_combine(Name, File, Line, Type, Getter);
+  }
+};
+
 template <> struct MDNodeKeyImpl<DIImportedEntity> {
   unsigned Tag;
   Metadata *Scope;
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 407c7a9122d3c..61b040cf91303 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1770,6 +1770,21 @@ void Verifier::visitDIObjCProperty(const DIObjCProperty &N) {
     CheckDI(isa<DIFile>(F), "invalid file", &N, F);
 }
 
+void Verifier::visitDIProperty(const DIProperty &N) {
+  CheckDI(N.getTag() == dwarf::DW_TAG_property, "invalid tag", &N);
+  if (auto *T = N.getRawType())
+    CheckDI(isType(T), "invalid type ref", &N, T);
+  if (auto *F = N.getRawFile())
+    CheckDI(isa<DIFile>(F), "invalid file", &N, F);
+  // DWARF allows a property getter to forward to a subprogram, variable, or
+  // constant too, but the backend only knows how to forward to a member.
+  if (DINode *G = N.getGetter()) {
+    auto *DT = dyn_cast<DIDerivedType>(G);
+    CheckDI(DT && DT->getTag() == dwarf::DW_TAG_member,
+            "property getter must be a member", &N, G);
+  }
+}
+
 void Verifier::visitDIImportedEntity(const DIImportedEntity &N) {
   CheckDI(N.getTag() == dwarf::DW_TAG_imported_module ||
               N.getTag() == dwarf::DW_TAG_imported_declaration,
diff --git a/llvm/lib/Target/DirectX/DXILWriter/DXILBitcodeWriter.cpp b/llvm/lib/Target/DirectX/DXILWriter/DXILBitcodeWriter.cpp
index 6f14f70014b01..99ea68b466206 100644
--- a/llvm/lib/Target/DirectX/DXILWriter/DXILBitcodeWriter.cpp
+++ b/llvm/lib/Target/DirectX/DXILWriter/DXILBitcodeWriter.cpp
@@ -325,6 +325,10 @@ class DXILBitcodeWriter {
   }
   void writeDIObjCProperty(const DIObjCProperty *N,
                            SmallVectorImpl<uint64_t> &Record, unsigned Abbrev);
+  void writeDIProperty(const DIProperty *N, SmallVectorImpl<uint64_t> &Record,
+                       unsigned Abbrev) {
+    llvm_unreachable("DXIL cannot contain DIProperty Nodes");
+  }
   void writeDIImportedEntity(const DIImportedEntity *N,
                              SmallVectorImpl<uint64_t> &Record,
                              unsigned Abbrev);
diff --git a/llvm/test/Assembler/diproperty.ll b/llvm/test/Assembler/diproperty.ll
new file mode 100644
index 0000000000000..fe42ac5953a44
--- /dev/null
+++ b/llvm/test/Assembler/diproperty.ll
@@ -0,0 +1,20 @@
+; RUN: llvm-as < %s | llvm-dis | llvm-as | llvm-dis | FileCheck %s
+; RUN: verify-uselistorder %s
+
+; !5 and !6 are identical, so uniquing collapses them to a single node.
+; CHECK: !named = !{!0, !1, !2, !3, !5, !6, !6}
+!named = !{!0, !1, !2, !3, !4, !5, !6}
+
+!0 = distinct !{}
+!1 = !DIFile(filename: "path/to/file", directory: "/path/to/dir")
+!2 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!3 = !DICompositeType(tag: DW_TAG_class_type, name: "Foo", elements: !{!4, !5})
+
+; CHECK: !5 = !DIDerivedType(tag: DW_TAG_member, name: "_x", scope: !3, file: !1, line: 8, baseType: !2, size: 32)
+!4 = !DIDerivedType(tag: DW_TAG_member, name: "_x", scope: !3, file: !1,
+                    line: 8, baseType: !2, size: 32)
+
+; CHECK-NEXT: !6 = !DIProperty(name: "x", file: !1, line: 8, type: !2, getter: !5)
+!5 = !DIProperty(name: "x", file: !1, line: 8, type: !2, getter: !4)
+
+!6 = !DIProperty(name: "x", file: !1, line: 8, type: !2, getter: !4)
diff --git a/llvm/test/DebugInfo/Generic/property.ll b/llvm/test/DebugInfo/Generic/property.ll
new file mode 100644
index 0000000000000..025e595c9e47b
--- /dev/null
+++ b/llvm/test/DebugInfo/Generic/property.ll
@@ -0,0 +1,59 @@
+; UNSUPPORTED:  target={{.*}}-aix{{.*}}
+;
+; RUN: llc -filetype=obj -o %t.o %s
+; RUN: llvm-dwarfdump --debug-info %t.o | FileCheck %s
+; RUN: llvm-dwarfdump --verify %t.o | FileCheck %s --check-prefix=VERIFY
+
+; VERIFY: No errors.
+
+; A property whose getter forwards to the data member holding its backing
+; storage. This models an Objective-C `@property`, where `x` is a synthesized
+; getter over the backing ivar `_x`, so a consumer can read the storage
+; directly instead of calling the getter.
+;
+; "Foo" lists the member before the property, "Bar" lists the property before
+; the member. Emission must not depend on that order.
+
+; CHECK: DW_TAG_class_type
+; CHECK:   DW_AT_name ("Foo")
+;
+; CHECK:   0x[[FOO_X:[0-9a-f]+]]: DW_TAG_member
+; CHECK:     DW_AT_name ("_x")
+;
+; CHECK:   DW_TAG_property
+; CHECK:     DW_AT_name ("x")
+; CHECK:     DW_AT_type {{.*}} "Int"
+; CHECK:     DW_AT_decl_line (8)
+; CHECK:     DW_TAG_property_getter
+; CHECK:       DW_AT_property_forward (0x[[FOO_X]] "_x")
+
+; CHECK: DW_TAG_class_type
+; CHECK:   DW_AT_name ("Bar")
+;
+; CHECK:   DW_TAG_property
+; CHECK:     DW_AT_name ("y")
+; CHECK:     DW_TAG_property_getter
+; CHECK:       DW_AT_property_forward (0x[[BAR_Y:[0-9a-f]+]] "_y")
+;
+; CHECK:   0x[[BAR_Y]]: DW_TAG_member
+; CHECK:     DW_AT_name ("_y")
+
+!llvm.module.flags = !{!0, !1}
+!llvm.dbg.cu = !{!2}
+
+!0 = !{i32 7, !"Dwarf Version", i32 5}
+!1 = !{i32 2, !"Debug Info Version", i32 3}
+!2 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !3, producer: "hand written", isOptimized: false, emissionKind: FullDebug, retainedTypes: !4)
+!3 = !DIFile(filename: "t.m", directory: "/tmp")
+!4 = !{!5, !10}
+
+!5 = !DICompositeType(tag: DW_TAG_class_type, name: "Foo", scope: !3, file: !3, line: 7, size: 64, elements: !6)
+!6 = !{!7, !9}
+!7 = !DIDerivedType(tag: DW_TAG_member, name: "_x", scope: !5, file: !3, line: 8, baseType: !8, size: 64)
+!8 = !DIBasicType(name: "Int", size: 64, encoding: DW_ATE_signed)
+!9 = !DIProperty(name: "x", file: !3, line: 8, type: !8, getter: !7)
+
+!10 = !DICompositeType(tag: DW_TAG_class_type, name: "Bar", scope: !3, file: !3, line: 12, size: 64, elements: !11)
+!11 = !{!13, !12}
+!12 = !DIDerivedType(tag: DW_TAG_member, name: "_y", scope: !10, file: !3, line: 13, baseType: !8, size: 64)
+!13 = !DIProperty(name: "y", file: !3, line: 13, type: !8, getter: !12)
diff --git a/llvm/test/Verifier/diproperty.ll b/llvm/test/Verifier/diproperty.ll
new file mode 100644
index 0000000000000..53f2f6de3acde
--- /dev/null
+++ b/llvm/test/Verifier/diproperty.ll
@@ -0,0 +1,18 @@
+; RUN: not llvm-as < %s -disable-output 2>&1 | FileCheck %s
+
+!named = !{!0, !1, !2, !3, !4}
+
+!0 = !DIFile(filename: "path/to/file", directory: "/path/to/dir")
+!1 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+
+; A getter must forward to the data member holding the backing storage.
+; CHECK: property getter must be a member
+!2 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getter: !1)
+
+; CHECK: property getter must be a member
+!3 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getter: !0)
+
+; A pointer is a DIDerivedType, but not a DW_TAG_member.
+; CHECK: property getter must be a member
+!4 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getter: !5)
+!5 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !1, size: 64)
diff --git a/llvm/unittests/IR/MetadataTest.cpp b/llvm/unittests/IR/MetadataTest.cpp
index 65398b5ca2b1f..cbd190efb2f2d 100644
--- a/llvm/unittests/IR/MetadataTest.cpp
+++ b/llvm/unittests/IR/MetadataTest.cpp
@@ -4840,6 +4840,44 @@ TEST_F(DIObjCPropertyTest, get) {
   EXPECT_EQ(N, MDNode::replaceWithUniqued(std::move(Temp)));
 }
 
+typedef MetadataTest DIPropertyTest;
+
+TEST_F(DIPropertyTest, get) {
+  // The backing storage a property getter forwards to.
+  auto GetMember = [&](StringRef Name) {
+    return DIDerivedType::getDistinct(
+        Context, dwarf::DW_TAG_member, Name, nullptr, 0, nullptr,
+        getBasicType("basictype"), 8, 8, 0, std::nullopt, {}, DINode::FlagZero);
+  };
+
+  StringRef Name = "x";
+  DIFile *File = getFile();
+  unsigned Line = 5;
+  DIType *Type = getBasicType("basic");
+  DIDerivedType *Getter = GetMember("_x");
+
+  auto *N = DIProperty::get(Context, Name, File, Line, Type, Getter);
+
+  EXPECT_EQ(dwarf::DW_TAG_property, N->getTag());
+  EXPECT_EQ(Name, N->getName());
+  EXPECT_EQ(File, N->getFile());
+  EXPECT_EQ(Line, N->getLine());
+  EXPECT_EQ(Type, N->getType());
+  EXPECT_EQ(Getter, N->getGetter());
+  EXPECT_EQ(N, DIProperty::get(Context, Name, File, Line, Type, Getter));
+
+  EXPECT_NE(N, DIProperty::get(Context, "other", File, Line, Type, Getter));
+  EXPECT_NE(N, DIProperty::get(Context, Name, getFile(), Line, Type, Getter));
+  EXPECT_NE(N, DIProperty::get(Context, Name, File, Line + 1, Type, Getter));
+  EXPECT_NE(N, DIProperty::get(Context, Name, File, Line, getBasicType("other"),
+                               Getter));
+  EXPECT_NE(
+      N, DIProperty::get(Context, Name, File, Line, Type, GetMember("_other")));
+
+  TempDIProperty Temp = N->clone();
+  EXPECT_EQ(N, MDNode::replaceWithUniqued(std::move(Temp)));
+}
+
 typedef MetadataTest DIImportedEntityTest;
 
 TEST_F(DIImportedEntityTest, get) {



More information about the llvm-commits mailing list