[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