[llvm] Piotrekjeremicz/diproperty node (PR #215776)

Piotr Jeremicz via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 12 04:23:33 PDT 2026


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

None

>From 7f59a5354e50fb02c96162f90513e024b0ea5bfa Mon Sep 17 00:00:00 2001
From: Augusto Noronha <anoronha at apple.com>
Date: Tue, 28 Jul 2026 13:07:58 -0700
Subject: [PATCH 1/6] [DebugInfo] Draft implementation of DW_TAG_property

---
 llvm/docs/LangRef.md                          |  4 +
 llvm/include/llvm-c/DebugInfo.h               |  1 +
 llvm/include/llvm/BinaryFormat/Dwarf.def      | 15 ++++
 llvm/include/llvm/Bitcode/LLVMBitCodes.h      |  1 +
 llvm/include/llvm/IR/DIBuilder.h              | 13 +++
 llvm/include/llvm/IR/DebugInfoMetadata.h      | 85 +++++++++++++++++++
 llvm/include/llvm/IR/Metadata.def             |  1 +
 llvm/lib/AsmParser/LLParser.cpp               | 18 ++++
 llvm/lib/Bitcode/Reader/MetadataLoader.cpp    |  5 ++
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp     |  8 ++
 llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp     | 37 ++++++++
 llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h       |  2 +
 llvm/lib/IR/AsmWriter.cpp                     |  5 ++
 llvm/lib/IR/DIBuilder.cpp                     |  6 ++
 llvm/lib/IR/DebugInfo.cpp                     |  2 +
 llvm/lib/IR/DebugInfoMetadata.cpp             | 15 ++++
 llvm/lib/IR/LLVMContextImpl.h                 | 26 ++++++
 llvm/lib/IR/Verifier.cpp                      | 20 +++++
 .../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            | 41 +++++++++
 23 files changed, 406 insertions(+)
 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..dde58daa82bbe 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -7253,6 +7253,10 @@ be used for the structure type.
                      getter: "getFoo", attributes: 7, type: !2)
 ```
 
+##### DIProperty
+
+TODO: write this.
+
 ##### 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..9d80fb8f42573 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,16 @@ 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)
+// TODO: DWARF v6 adds DW_AT_property_forward, used by the DW_TAG_property_getter
+// / _setter / _stored entries to refer to whatever implements the accessor. Add
+// it here, in numeric order with the entries above.
+//
+// Get its code and form class from Table 8.5 of the DWARF 6 draft rather than
+// from any summary of the proposal. Earlier write-ups of this proposal
+// circulated a different code, which the committee did not end up using -- and
+// picking the wrong one produces object files that every consumer misreads,
+// which no test in this tree would catch. Verifying a code against the actual
+// spec table is the habit worth building here.
 
 // 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..a937a4ca6a9ab 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, getterForward]
 };
 
 // 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..de2bc2f311a24 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 GetterForward 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 *GetterForward);
+
     /// 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..62573d59fb204 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,90 @@ 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. This describes languages that
+/// synthesize a property over separate storage, such as a Swift property
+/// wrapper, where \c x is a generated getter over a stored member \c _x. A
+/// consumer can then read the storage directly instead of calling the getter.
+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 *GetterForward,
+                             StorageType Storage, bool ShouldCreate = true) {
+    return getImpl(Context, getCanonicalMDString(Context, Name), File, Line,
+                   Type, GetterForward, Storage, ShouldCreate);
+  }
+  LLVM_ABI static DIProperty *getImpl(LLVMContext &Context, MDString *Name,
+                                      Metadata *File, unsigned Line,
+                                      Metadata *Type, Metadata *GetterForward,
+                                      StorageType Storage,
+                                      bool ShouldCreate = true);
+
+  TempDIProperty cloneImpl() const {
+    return getTemporary(getContext(), getName(), getFile(), getLine(),
+                        getType(), getGetterForward());
+  }
+
+public:
+  DEFINE_MDNODE_GET(DIProperty,
+                    (StringRef Name, DIFile *File, unsigned Line, DIType *Type,
+                     DINode *GetterForward),
+                    (Name, File, Line, Type, GetterForward))
+  DEFINE_MDNODE_GET(DIProperty,
+                    (MDString * Name, Metadata *File, unsigned Line,
+                     Metadata *Type, Metadata *GetterForward),
+                    (Name, File, Line, Type, GetterForward))
+
+  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 *getGetterForward() const {
+    return cast_or_null<DINode>(getRawGetterForward());
+  }
+
+  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 *getRawGetterForward() 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..20fc99cf1ae3a 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,
+///                   getterForward: !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(getterForward, MDField, );
+  PARSE_MD_FIELDS();
+#undef VISIT_MD_FIELDS
+
+  Result = GET_OR_DISTINCT(DIProperty, (Context, name.Val, file.Val, line.Val,
+                                        type.Val, getterForward.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..00cdaa3c2cdea 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,10 @@ Error MetadataLoader::MetadataLoaderImpl::parseOneMetadata(
     NextMetadataNo++;
     break;
   }
+  case bitc::METADATA_PROPERTY: {
+    // TODO: implement this.
+    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..3988c14133ce6 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,12 @@ void ModuleBitcodeWriter::writeDIObjCProperty(const DIObjCProperty *N,
   Record.clear();
 }
 
+void ModuleBitcodeWriter::writeDIProperty(const DIProperty *N,
+                                          SmallVectorImpl<uint64_t> &Record,
+                                          unsigned Abbrev) {
+  // TODO: implement this.
+}
+
 void ModuleBitcodeWriter::writeDIImportedEntity(
     const DIImportedEntity *N, SmallVectorImpl<uint64_t> &Record,
     unsigned Abbrev) {
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
index 78c0769e49161..d63bd2e581229 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,35 @@ DIE &DwarfUnit::constructMemberDIE(DIE &Buffer, const DIDerivedType *DT) {
   return MemberDie;
 }
 
+void DwarfUnit::constructPropertyDIE(DIE &Buffer, const DIProperty *P) {
+  // TODO: Emit the property. The DIE tree this needs to produce is:
+  //
+  //   DW_TAG_property
+  //     DW_AT_name              <- P->getName()
+  //     DW_AT_type              <- P->getType()
+  //     DW_AT_decl_file/line    <- addSourceLine() has an overload for this
+  //     DW_TAG_property_getter          (a child DIE, not an attribute)
+  //       DW_AT_property_forward        (a reference to P->getGetterForward())
+  //
+  // createAndAddDIE, addString, addType and addDIEEntry are the helpers you
+  // want. constructMemberDIE, just above, is a good model to follow -- note in
+  // particular how it takes the metadata node as createAndAddDIE's third
+  // argument, and what that buys you.
+  //
+  // Two questions to answer rather than guess at:
+  //
+  //  - getGetterForward() hands you a DINode, but addDIEEntry needs a DIE. How
+  //    do you get from one to the other? The DW_AT_APPLE_property code at the
+  //    end of constructMemberDIE does the same lookup. What should happen when
+  //    that lookup finds nothing -- is it better to emit the getter child
+  //    anyway, or to leave it off?
+  //
+  //  - test/DebugInfo/Generic/property.ll declares two classes, Foo and Bar,
+  //    whose elements lists hold the same two nodes in opposite orders. Both
+  //    must produce the same DWARF. If only one of them does, the fix is
+  //    probably not inside this function.
+}
+
 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..1ed2625255d21 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h
@@ -228,6 +228,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);
@@ -389,6 +390,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/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index cad4f17b0db91..2a1ebb99f3351 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -2700,6 +2700,11 @@ static void writeDIObjCProperty(raw_ostream &Out, const DIObjCProperty *N,
   Out << ")";
 }
 
+static void writeDIProperty(raw_ostream &Out, const DIProperty *N,
+                            AsmWriterContext &WriterCtx) {
+  // TODO: implement this
+}
+
 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..6317db812dd02 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 *GetterForward) {
+  return DIProperty::get(VMContext, Name, File, LineNumber, Ty, GetterForward);
+}
+
 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..0f5422971ef76 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 *GetterForward, StorageType Storage,
+                                bool ShouldCreate) {
+  assert(isCanonical(Name) && "Expected canonical MDString");
+  DEFINE_GETIMPL_LOOKUP(DIProperty, (Name, File, Line, Type, GetterForward));
+  Metadata *Ops[] = {Name, File, Type, GetterForward};
+  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..4848597cd11af 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -1396,6 +1396,32 @@ template <> struct MDNodeKeyImpl<DIObjCProperty> {
   }
 };
 
+template <> struct MDNodeKeyImpl<DIProperty> {
+  MDString *Name;
+  Metadata *File;
+  unsigned Line;
+  Metadata *Type;
+  Metadata *GetterForward;
+
+  MDNodeKeyImpl(MDString *Name, Metadata *File, unsigned Line, Metadata *Type,
+                Metadata *GetterForward)
+      : Name(Name), File(File), Line(Line), Type(Type),
+        GetterForward(GetterForward) {}
+  MDNodeKeyImpl(const DIProperty *N)
+      : Name(N->getRawName()), File(N->getRawFile()), Line(N->getLine()),
+        Type(N->getRawType()), GetterForward(N->getRawGetterForward()) {}
+
+  bool isKeyOf(const DIProperty *RHS) const {
+    return Name == RHS->getRawName() && File == RHS->getRawFile() &&
+           Line == RHS->getLine() && Type == RHS->getRawType() &&
+           GetterForward == RHS->getRawGetterForward();
+  }
+
+  unsigned getHashValue() const {
+    return hash_combine(Name, File, Line, Type, GetterForward);
+  }
+};
+
 template <> struct MDNodeKeyImpl<DIImportedEntity> {
   unsigned Tag;
   Metadata *Scope;
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 407c7a9122d3c..5d004b5b52f30 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1770,6 +1770,26 @@ 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);
+  // TODO: Check getterForward. It ends up as the DW_AT_property_forward of a
+  // DW_TAG_property_getter, and the only thing the backend currently knows how
+  // to point at is the data member holding the property's backing storage.
+  //
+  // Note the DWARF specification is deliberately looser than we need to be
+  // here: it also allows a property getter to forward to a subprogram, a
+  // variable or a constant. Restricting this is a choice about what we support
+  // today, not a transcription of the spec, so the check should be tight enough
+  // that a wrong node is caught early rather than turning into bad DWARF.
+  //
+  // test/Verifier/diproperty.ll has the cases that must be rejected, and the
+  // diagnostic they expect.
+}
+
 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..d24a8de1f3785
--- /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, getterForward: !5)
+!5 = !DIProperty(name: "x", file: !1, line: 8, type: !2, getterForward: !4)
+
+!6 = !DIProperty(name: "x", file: !1, line: 8, type: !2, getterForward: !4)
diff --git a/llvm/test/DebugInfo/Generic/property.ll b/llvm/test/DebugInfo/Generic/property.ll
new file mode 100644
index 0000000000000..e7992531f1fbe
--- /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 a Swift property wrapper, where `x` is a synthesized
+; getter over the stored member `_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:   0x[[BAR_Y:[0-9a-f]+]]: DW_TAG_member
+; CHECK:     DW_AT_name ("_y")
+;
+; CHECK:   DW_TAG_property
+; CHECK:     DW_AT_name ("y")
+; CHECK:     DW_TAG_property_getter
+; CHECK:       DW_AT_property_forward (0x[[BAR_Y]] "_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_Swift, file: !3, producer: "hand written", isOptimized: false, emissionKind: FullDebug, retainedTypes: !4)
+!3 = !DIFile(filename: "t.swift", 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, getterForward: !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, getterForward: !12)
diff --git a/llvm/test/Verifier/diproperty.ll b/llvm/test/Verifier/diproperty.ll
new file mode 100644
index 0000000000000..64c56e86fb6b5
--- /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 getterForward must be a member
+!2 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getterForward: !1)
+
+; CHECK: property getterForward must be a member
+!3 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getterForward: !0)
+
+; A pointer is a DIDerivedType, but not a DW_TAG_member.
+; CHECK: property getterForward must be a member
+!4 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getterForward: !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..93ad970a879f7 100644
--- a/llvm/unittests/IR/MetadataTest.cpp
+++ b/llvm/unittests/IR/MetadataTest.cpp
@@ -4840,6 +4840,47 @@ 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 *GetterForward = GetMember("_x");
+
+  auto *N = DIProperty::get(Context, Name, File, Line, Type, GetterForward);
+
+  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(GetterForward, N->getGetterForward());
+  EXPECT_EQ(N, DIProperty::get(Context, Name, File, Line, Type, GetterForward));
+
+  EXPECT_NE(N,
+            DIProperty::get(Context, "other", File, Line, Type, GetterForward));
+  EXPECT_NE(
+      N, DIProperty::get(Context, Name, getFile(), Line, Type, GetterForward));
+  EXPECT_NE(
+      N, DIProperty::get(Context, Name, File, Line + 1, Type, GetterForward));
+  EXPECT_NE(N, DIProperty::get(Context, Name, File, Line, getBasicType("other"),
+                               GetterForward));
+  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) {

>From 8541c49298febc43b69a2b640f1d0ecffab95544 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Mon, 10 Aug 2026 19:33:59 +0200
Subject: [PATCH 2/6] [DebugInfo] Rename DIProperty operand `GetterForward` to
 `Getter`

---
 llvm/include/llvm/Bitcode/LLVMBitCodes.h  |  2 +-
 llvm/include/llvm/IR/DIBuilder.h          |  4 ++--
 llvm/include/llvm/IR/DebugInfoMetadata.h  | 22 +++++++++++-----------
 llvm/lib/AsmParser/LLParser.cpp           |  6 +++---
 llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp |  4 ++--
 llvm/lib/IR/DIBuilder.cpp                 |  4 ++--
 llvm/lib/IR/DebugInfoMetadata.cpp         |  6 +++---
 llvm/lib/IR/LLVMContextImpl.h             | 12 ++++++------
 llvm/lib/IR/Verifier.cpp                  |  2 +-
 llvm/test/Assembler/diproperty.ll         |  6 +++---
 llvm/test/DebugInfo/Generic/property.ll   |  4 ++--
 llvm/test/Verifier/diproperty.ll          | 12 ++++++------
 llvm/unittests/IR/MetadataTest.cpp        | 16 ++++++++--------
 13 files changed, 50 insertions(+), 50 deletions(-)

diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index a937a4ca6a9ab..3c6a742d67fb3 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -400,7 +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, getterForward]
+  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 de2bc2f311a24..1dbe153ffeaec 100644
--- a/llvm/include/llvm/IR/DIBuilder.h
+++ b/llvm/include/llvm/IR/DIBuilder.h
@@ -561,11 +561,11 @@ namespace llvm {
     /// \param File          File where this property is defined.
     /// \param LineNumber    Line number.
     /// \param Ty            Type of the property.
-    /// \param GetterForward The data member the getter forwards to, holding the
+    /// \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 *GetterForward);
+                                        DIDerivedType *Getter);
 
     /// Create debugging information entry for a class.
     /// \param Scope        Scope in which this class is defined.
diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h
index 62573d59fb204..cf6871cdf7405 100644
--- a/llvm/include/llvm/IR/DebugInfoMetadata.h
+++ b/llvm/include/llvm/IR/DebugInfoMetadata.h
@@ -4448,31 +4448,31 @@ class DIProperty : public DINode {
   ~DIProperty() = default;
 
   static DIProperty *getImpl(LLVMContext &Context, StringRef Name, DIFile *File,
-                             unsigned Line, DIType *Type, DINode *GetterForward,
+                             unsigned Line, DIType *Type, DINode *Getter,
                              StorageType Storage, bool ShouldCreate = true) {
     return getImpl(Context, getCanonicalMDString(Context, Name), File, Line,
-                   Type, GetterForward, Storage, ShouldCreate);
+                   Type, Getter, Storage, ShouldCreate);
   }
   LLVM_ABI static DIProperty *getImpl(LLVMContext &Context, MDString *Name,
                                       Metadata *File, unsigned Line,
-                                      Metadata *Type, Metadata *GetterForward,
+                                      Metadata *Type, Metadata *Getter,
                                       StorageType Storage,
                                       bool ShouldCreate = true);
 
   TempDIProperty cloneImpl() const {
     return getTemporary(getContext(), getName(), getFile(), getLine(),
-                        getType(), getGetterForward());
+                        getType(), getGetter());
   }
 
 public:
   DEFINE_MDNODE_GET(DIProperty,
                     (StringRef Name, DIFile *File, unsigned Line, DIType *Type,
-                     DINode *GetterForward),
-                    (Name, File, Line, Type, GetterForward))
+                     DINode *Getter),
+                    (Name, File, Line, Type, Getter))
   DEFINE_MDNODE_GET(DIProperty,
                     (MDString * Name, Metadata *File, unsigned Line,
-                     Metadata *Type, Metadata *GetterForward),
-                    (Name, File, Line, Type, GetterForward))
+                     Metadata *Type, Metadata *Getter),
+                    (Name, File, Line, Type, Getter))
 
   TempDIProperty clone() const { return cloneImpl(); }
 
@@ -4484,8 +4484,8 @@ class DIProperty : public DINode {
   /// 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 *getGetterForward() const {
-    return cast_or_null<DINode>(getRawGetterForward());
+  DINode *getGetter() const {
+    return cast_or_null<DINode>(getRawGetter());
   }
 
   StringRef getFilename() const {
@@ -4503,7 +4503,7 @@ class DIProperty : public DINode {
   MDString *getRawName() const { return getOperandAs<MDString>(0); }
   Metadata *getRawFile() const { return getOperand(1); }
   Metadata *getRawType() const { return getOperand(2); }
-  Metadata *getRawGetterForward() const { return getOperand(3); }
+  Metadata *getRawGetter() const { return getOperand(3); }
 
   static bool classof(const Metadata *MD) {
     return MD->getMetadataID() == DIPropertyKind;
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 20fc99cf1ae3a..468373c82ff82 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -6702,19 +6702,19 @@ bool LLParser::parseDIObjCProperty(MDNode *&Result, bool IsDistinct) {
 
 /// parseDIProperty:
 ///   ::= !DIProperty(name: "x", file: !1, line: 7, type: !2,
-///                   getterForward: !3)
+///                   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(getterForward, 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, getterForward.Val));
+                                        type.Val, getter.Val));
   return false;
 }
 
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
index d63bd2e581229..c32ad6fd4a106 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
@@ -2028,7 +2028,7 @@ void DwarfUnit::constructPropertyDIE(DIE &Buffer, const DIProperty *P) {
   //     DW_AT_type              <- P->getType()
   //     DW_AT_decl_file/line    <- addSourceLine() has an overload for this
   //     DW_TAG_property_getter          (a child DIE, not an attribute)
-  //       DW_AT_property_forward        (a reference to P->getGetterForward())
+  //       DW_AT_property_forward        (a reference to P->getGetter())
   //
   // createAndAddDIE, addString, addType and addDIEEntry are the helpers you
   // want. constructMemberDIE, just above, is a good model to follow -- note in
@@ -2037,7 +2037,7 @@ void DwarfUnit::constructPropertyDIE(DIE &Buffer, const DIProperty *P) {
   //
   // Two questions to answer rather than guess at:
   //
-  //  - getGetterForward() hands you a DINode, but addDIEEntry needs a DIE. How
+  //  - getGetter() hands you a DINode, but addDIEEntry needs a DIE. How
   //    do you get from one to the other? The DW_AT_APPLE_property code at the
   //    end of constructMemberDIE does the same lookup. What should happen when
   //    that lookup finds nothing -- is it better to emit the getter child
diff --git a/llvm/lib/IR/DIBuilder.cpp b/llvm/lib/IR/DIBuilder.cpp
index 6317db812dd02..44719331589bb 100644
--- a/llvm/lib/IR/DIBuilder.cpp
+++ b/llvm/lib/IR/DIBuilder.cpp
@@ -574,8 +574,8 @@ DIBuilder::createObjCProperty(StringRef Name, DIFile *File, unsigned LineNumber,
 
 DIProperty *DIBuilder::createProperty(StringRef Name, DIFile *File,
                                       unsigned LineNumber, DIType *Ty,
-                                      DIDerivedType *GetterForward) {
-  return DIProperty::get(VMContext, Name, File, LineNumber, Ty, GetterForward);
+                                      DIDerivedType *Getter) {
+  return DIProperty::get(VMContext, Name, File, LineNumber, Ty, Getter);
 }
 
 DITemplateTypeParameter *
diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp
index 0f5422971ef76..47b99613173b6 100644
--- a/llvm/lib/IR/DebugInfoMetadata.cpp
+++ b/llvm/lib/IR/DebugInfoMetadata.cpp
@@ -2631,11 +2631,11 @@ DIProperty::DIProperty(LLVMContext &C, StorageType Storage, unsigned Line,
 
 DIProperty *DIProperty::getImpl(LLVMContext &Context, MDString *Name,
                                 Metadata *File, unsigned Line, Metadata *Type,
-                                Metadata *GetterForward, StorageType Storage,
+                                Metadata *Getter, StorageType Storage,
                                 bool ShouldCreate) {
   assert(isCanonical(Name) && "Expected canonical MDString");
-  DEFINE_GETIMPL_LOOKUP(DIProperty, (Name, File, Line, Type, GetterForward));
-  Metadata *Ops[] = {Name, File, Type, GetterForward};
+  DEFINE_GETIMPL_LOOKUP(DIProperty, (Name, File, Line, Type, Getter));
+  Metadata *Ops[] = {Name, File, Type, Getter};
   DEFINE_GETIMPL_STORE(DIProperty, (Line), Ops);
 }
 
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 4848597cd11af..9914f98186457 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -1401,24 +1401,24 @@ template <> struct MDNodeKeyImpl<DIProperty> {
   Metadata *File;
   unsigned Line;
   Metadata *Type;
-  Metadata *GetterForward;
+  Metadata *Getter;
 
   MDNodeKeyImpl(MDString *Name, Metadata *File, unsigned Line, Metadata *Type,
-                Metadata *GetterForward)
+                Metadata *Getter)
       : Name(Name), File(File), Line(Line), Type(Type),
-        GetterForward(GetterForward) {}
+        Getter(Getter) {}
   MDNodeKeyImpl(const DIProperty *N)
       : Name(N->getRawName()), File(N->getRawFile()), Line(N->getLine()),
-        Type(N->getRawType()), GetterForward(N->getRawGetterForward()) {}
+        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() &&
-           GetterForward == RHS->getRawGetterForward();
+           Getter == RHS->getRawGetter();
   }
 
   unsigned getHashValue() const {
-    return hash_combine(Name, File, Line, Type, GetterForward);
+    return hash_combine(Name, File, Line, Type, Getter);
   }
 };
 
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 5d004b5b52f30..847023aa8b4ad 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1776,7 +1776,7 @@ void Verifier::visitDIProperty(const DIProperty &N) {
     CheckDI(isType(T), "invalid type ref", &N, T);
   if (auto *F = N.getRawFile())
     CheckDI(isa<DIFile>(F), "invalid file", &N, F);
-  // TODO: Check getterForward. It ends up as the DW_AT_property_forward of a
+  // TODO: Check getter. It ends up as the DW_AT_property_forward of a
   // DW_TAG_property_getter, and the only thing the backend currently knows how
   // to point at is the data member holding the property's backing storage.
   //
diff --git a/llvm/test/Assembler/diproperty.ll b/llvm/test/Assembler/diproperty.ll
index d24a8de1f3785..fe42ac5953a44 100644
--- a/llvm/test/Assembler/diproperty.ll
+++ b/llvm/test/Assembler/diproperty.ll
@@ -14,7 +14,7 @@
 !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, getterForward: !5)
-!5 = !DIProperty(name: "x", file: !1, line: 8, type: !2, getterForward: !4)
+; 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, getterForward: !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
index e7992531f1fbe..28cff08049b37 100644
--- a/llvm/test/DebugInfo/Generic/property.ll
+++ b/llvm/test/DebugInfo/Generic/property.ll
@@ -51,9 +51,9 @@
 !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, getterForward: !7)
+!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, getterForward: !12)
+!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
index 64c56e86fb6b5..53f2f6de3acde 100644
--- a/llvm/test/Verifier/diproperty.ll
+++ b/llvm/test/Verifier/diproperty.ll
@@ -6,13 +6,13 @@
 !1 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
 
 ; A getter must forward to the data member holding the backing storage.
-; CHECK: property getterForward must be a member
-!2 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getterForward: !1)
+; CHECK: property getter must be a member
+!2 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getter: !1)
 
-; CHECK: property getterForward must be a member
-!3 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getterForward: !0)
+; 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 getterForward must be a member
-!4 = !DIProperty(name: "x", file: !0, line: 8, type: !1, getterForward: !5)
+; 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 93ad970a879f7..c856bfbfc6955 100644
--- a/llvm/unittests/IR/MetadataTest.cpp
+++ b/llvm/unittests/IR/MetadataTest.cpp
@@ -4854,26 +4854,26 @@ TEST_F(DIPropertyTest, get) {
   DIFile *File = getFile();
   unsigned Line = 5;
   DIType *Type = getBasicType("basic");
-  DIDerivedType *GetterForward = GetMember("_x");
+  DIDerivedType *Getter = GetMember("_x");
 
-  auto *N = DIProperty::get(Context, Name, File, Line, Type, GetterForward);
+  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(GetterForward, N->getGetterForward());
-  EXPECT_EQ(N, DIProperty::get(Context, Name, File, Line, Type, GetterForward));
+  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, GetterForward));
+            DIProperty::get(Context, "other", File, Line, Type, Getter));
   EXPECT_NE(
-      N, DIProperty::get(Context, Name, getFile(), Line, Type, GetterForward));
+      N, DIProperty::get(Context, Name, getFile(), Line, Type, Getter));
   EXPECT_NE(
-      N, DIProperty::get(Context, Name, File, Line + 1, Type, GetterForward));
+      N, DIProperty::get(Context, Name, File, Line + 1, Type, Getter));
   EXPECT_NE(N, DIProperty::get(Context, Name, File, Line, getBasicType("other"),
-                               GetterForward));
+                               Getter));
   EXPECT_NE(
       N, DIProperty::get(Context, Name, File, Line, Type, GetMember("_other")));
 

>From 917f99be116f004ecd81e7c30fbbd82411c425ff Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Mon, 10 Aug 2026 20:19:39 +0200
Subject: [PATCH 3/6] [DebugInfo] Emit `DW_TAG_property` DIEs in DwarfUnit

---
 llvm/include/llvm/BinaryFormat/Dwarf.def   | 11 +----
 llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp |  1 +
 llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp  | 49 ++++++++++------------
 llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h    |  3 ++
 4 files changed, 28 insertions(+), 36 deletions(-)

diff --git a/llvm/include/llvm/BinaryFormat/Dwarf.def b/llvm/include/llvm/BinaryFormat/Dwarf.def
index 9d80fb8f42573..054fa292b7c53 100644
--- a/llvm/include/llvm/BinaryFormat/Dwarf.def
+++ b/llvm/include/llvm/BinaryFormat/Dwarf.def
@@ -437,16 +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)
-// TODO: DWARF v6 adds DW_AT_property_forward, used by the DW_TAG_property_getter
-// / _setter / _stored entries to refer to whatever implements the accessor. Add
-// it here, in numeric order with the entries above.
-//
-// Get its code and form class from Table 8.5 of the DWARF 6 draft rather than
-// from any summary of the proposal. Earlier write-ups of this proposal
-// circulated a different code, which the committee did not end up using -- and
-// picking the wrong one produces object files that every consumer misreads,
-// which no test in this tree would catch. Verifying a code against the actual
-// spec table is the habit worth building here.
+HANDLE_DW_AT(0x95, property_forward, 6, DWARF)
 
 // Vendor extensions:
 HANDLE_DW_AT(0x806, GHS_namespace_alias, 0, GHS)
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 c32ad6fd4a106..52ba8d2e8a3b2 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.cpp
@@ -2021,32 +2021,29 @@ DIE &DwarfUnit::constructMemberDIE(DIE &Buffer, const DIDerivedType *DT) {
 }
 
 void DwarfUnit::constructPropertyDIE(DIE &Buffer, const DIProperty *P) {
-  // TODO: Emit the property. The DIE tree this needs to produce is:
-  //
-  //   DW_TAG_property
-  //     DW_AT_name              <- P->getName()
-  //     DW_AT_type              <- P->getType()
-  //     DW_AT_decl_file/line    <- addSourceLine() has an overload for this
-  //     DW_TAG_property_getter          (a child DIE, not an attribute)
-  //       DW_AT_property_forward        (a reference to P->getGetter())
-  //
-  // createAndAddDIE, addString, addType and addDIEEntry are the helpers you
-  // want. constructMemberDIE, just above, is a good model to follow -- note in
-  // particular how it takes the metadata node as createAndAddDIE's third
-  // argument, and what that buys you.
-  //
-  // Two questions to answer rather than guess at:
-  //
-  //  - getGetter() hands you a DINode, but addDIEEntry needs a DIE. How
-  //    do you get from one to the other? The DW_AT_APPLE_property code at the
-  //    end of constructMemberDIE does the same lookup. What should happen when
-  //    that lookup finds nothing -- is it better to emit the getter child
-  //    anyway, or to leave it off?
-  //
-  //  - test/DebugInfo/Generic/property.ll declares two classes, Foo and Bar,
-  //    whose elements lists hold the same two nodes in opposite orders. Both
-  //    must produce the same DWARF. If only one of them does, the fix is
-  //    probably not inside this function.
+  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) {
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h b/llvm/lib/CodeGen/AsmPrinter/DwarfUnit.h
index 1ed2625255d21..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);
@@ -280,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.

>From 439f207b9b83fe4ff11811e279cd834fc632e1b2 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Mon, 10 Aug 2026 21:20:34 +0200
Subject: [PATCH 4/6] [DebugInfo] Finish DIProperty bitcode, printing, and
 verification stubs

---
 llvm/lib/Bitcode/Reader/MetadataLoader.cpp | 13 ++++++++++++-
 llvm/lib/Bitcode/Writer/BitcodeWriter.cpp  | 10 +++++++++-
 llvm/lib/DebugInfo/DWARF/DWARFDie.cpp      |  5 +++++
 llvm/lib/IR/AsmWriter.cpp                  |  9 ++++++++-
 llvm/lib/IR/Verifier.cpp                   | 19 +++++++------------
 5 files changed, 41 insertions(+), 15 deletions(-)

diff --git a/llvm/lib/Bitcode/Reader/MetadataLoader.cpp b/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
index 00cdaa3c2cdea..0a370e0eaa0eb 100644
--- a/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
+++ b/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
@@ -2418,7 +2418,18 @@ Error MetadataLoader::MetadataLoaderImpl::parseOneMetadata(
     break;
   }
   case bitc::METADATA_PROPERTY: {
-    // TODO: implement this.
+    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: {
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 3988c14133ce6..e08d5b403c552 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -2515,7 +2515,15 @@ void ModuleBitcodeWriter::writeDIObjCProperty(const DIObjCProperty *N,
 void ModuleBitcodeWriter::writeDIProperty(const DIProperty *N,
                                           SmallVectorImpl<uint64_t> &Record,
                                           unsigned Abbrev) {
-  // TODO: implement this.
+  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(
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/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index 2a1ebb99f3351..35be0b18bad99 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -2702,7 +2702,14 @@ static void writeDIObjCProperty(raw_ostream &Out, const DIObjCProperty *N,
 
 static void writeDIProperty(raw_ostream &Out, const DIProperty *N,
                             AsmWriterContext &WriterCtx) {
-  // TODO: implement this
+  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,
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 847023aa8b4ad..61b040cf91303 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1776,18 +1776,13 @@ void Verifier::visitDIProperty(const DIProperty &N) {
     CheckDI(isType(T), "invalid type ref", &N, T);
   if (auto *F = N.getRawFile())
     CheckDI(isa<DIFile>(F), "invalid file", &N, F);
-  // TODO: Check getter. It ends up as the DW_AT_property_forward of a
-  // DW_TAG_property_getter, and the only thing the backend currently knows how
-  // to point at is the data member holding the property's backing storage.
-  //
-  // Note the DWARF specification is deliberately looser than we need to be
-  // here: it also allows a property getter to forward to a subprogram, a
-  // variable or a constant. Restricting this is a choice about what we support
-  // today, not a transcription of the spec, so the check should be tight enough
-  // that a wrong node is caught early rather than turning into bad DWARF.
-  //
-  // test/Verifier/diproperty.ll has the cases that must be rejected, and the
-  // diagnostic they expect.
+  // 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) {

>From 8e2cf3358d9117a94431a78709d7c0603f6157e0 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Mon, 10 Aug 2026 21:20:57 +0200
Subject: [PATCH 5/6] [DebugInfo] Fix Bar's CHECK order in property.ll

---
 llvm/test/DebugInfo/Generic/property.ll | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/test/DebugInfo/Generic/property.ll b/llvm/test/DebugInfo/Generic/property.ll
index 28cff08049b37..052e736779cf1 100644
--- a/llvm/test/DebugInfo/Generic/property.ll
+++ b/llvm/test/DebugInfo/Generic/property.ll
@@ -30,13 +30,13 @@
 ; CHECK: DW_TAG_class_type
 ; CHECK:   DW_AT_name ("Bar")
 ;
-; CHECK:   0x[[BAR_Y:[0-9a-f]+]]: DW_TAG_member
-; CHECK:     DW_AT_name ("_y")
-;
 ; CHECK:   DW_TAG_property
 ; CHECK:     DW_AT_name ("y")
 ; CHECK:     DW_TAG_property_getter
-; CHECK:       DW_AT_property_forward (0x[[BAR_Y]] "_y")
+; 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}

>From 775e44a7bd45fc0b4f75224f047f7b7173bdc599 Mon Sep 17 00:00:00 2001
From: Piotrek Jeremicz <piotrek at jeremicz.com>
Date: Wed, 12 Aug 2026 12:12:56 +0200
Subject: [PATCH 6/6] [DebugInfo] Exclude DW_TAG_property from name-index
 completeness checks

Properties are accessed through their containing subprogram/type, not
looked up globally by name, so they belong in the same
not-globally-visible category as DW_TAG_member.
---
 llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

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.



More information about the llvm-commits mailing list