[llvm] [DebugInfo] Add DILayerLoc/DILayerLocList and DILocation irlayers operand (PR #215666)
Alexander Yermolovich via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 16:50:19 PDT 2026
https://github.com/ayermolo updated https://github.com/llvm/llvm-project/pull/215666
>From e1383dcd780326e5fbe42d2b8b65d7d619766160 Mon Sep 17 00:00:00 2001
From: Alexander Yermolovich <ayermolovich at nvidia.com>
Date: Tue, 11 Aug 2026 20:51:44 +0000
Subject: [PATCH 1/4] [DebugInfo] Add DILayerLoc/DILayerLocList and DILocation
irlayers operand
Programs lowered through intermediate IRs lose their position in those IRs by
the time they reach a backend: a DILocation records only the original source
coordinate. Add an optional `irlayers` operand carrying one coordinate per
intermediate level, so a debugger can step through the IR text a program was
actually compiled from.
Layers are independent of inlinedAt; a location may carry either, both, or
neither. The presence flag costs one bit of atomGroup, narrowing it from 61 to
60. Textual IR, bitcode and MIR all round-trip the new operand.
---
llvm/docs/KeyInstructionsDebugInfo.md | 2 +-
llvm/docs/LangRef.md | 49 +++
llvm/include/llvm-c/DebugInfo.h | 2 +
llvm/include/llvm/Bitcode/LLVMBitCodes.h | 2 +
llvm/include/llvm/IR/DebugInfoMetadata.h | 174 +++++++-
llvm/include/llvm/IR/DebugLoc.h | 7 +-
llvm/include/llvm/IR/Metadata.def | 2 +
llvm/lib/AsmParser/LLParser.cpp | 44 +-
llvm/lib/Bitcode/Reader/BitcodeReader.cpp | 9 +-
llvm/lib/Bitcode/Reader/MetadataLoader.cpp | 48 ++-
llvm/lib/Bitcode/Writer/BitcodeWriter.cpp | 30 ++
llvm/lib/CodeGen/MIRParser/MIParser.cpp | 13 +-
llvm/lib/IR/AsmWriter.cpp | 23 +
llvm/lib/IR/DebugInfoMetadata.cpp | 131 +++++-
llvm/lib/IR/DebugLoc.cpp | 12 +-
llvm/lib/IR/LLVMContextImpl.h | 64 ++-
llvm/lib/IR/Verifier.cpp | 16 +
.../DirectX/DXILWriter/DXILBitcodeWriter.cpp | 8 +
.../intermediate-loc-metadata-location.ll | 58 +++
.../Bitcode/intermediate-loc-roundtrip.ll | 57 +++
.../instructions-debug-location-irlayers.mir | 64 +++
llvm/test/Verifier/intermediate-layerloc.ll | 37 ++
llvm/unittests/IR/DebugInfoTest.cpp | 402 ++++++++++++++++++
23 files changed, 1194 insertions(+), 60 deletions(-)
create mode 100644 llvm/test/Bitcode/intermediate-loc-metadata-location.ll
create mode 100644 llvm/test/Bitcode/intermediate-loc-roundtrip.ll
create mode 100644 llvm/test/CodeGen/MIR/X86/instructions-debug-location-irlayers.mir
create mode 100644 llvm/test/Verifier/intermediate-layerloc.ll
diff --git a/llvm/docs/KeyInstructionsDebugInfo.md b/llvm/docs/KeyInstructionsDebugInfo.md
index 22f83f2b0a381..0ea9b84c180f1 100644
--- a/llvm/docs/KeyInstructionsDebugInfo.md
+++ b/llvm/docs/KeyInstructionsDebugInfo.md
@@ -45,7 +45,7 @@ From the perspective of a source-level debugger user:
Details:
-1. *The metadata* - The two new `DILocation` fields are `atomGroup` and `atomRank` and are both are unsigned integers. `atomGroup` is 61 bits and `atomRank` 3 bits. Instructions in the same function with the same `(atomGroup, inlinedAt)` pair are part of the same source atom. `atomRank` determines `is_stmt` preference within that group, where a lower number is higher precedence. Higher rank instructions act as "backup" `is_stmt` locations, providing good fallback locations if/when the primary candidate gets optimized away. The default values of 0 indicate the instruction isn’t interesting - it's not an `is_stmt` candidate. If `keyInstructions` in `DISubprogram` is false (default) then the new `DILocation` metadata is ignored for the function (including inlined instances) when emitting DWARF.
+1. *The metadata* - The two new `DILocation` fields are `atomGroup` and `atomRank` and are both are unsigned integers. `atomGroup` is 60 bits and `atomRank` 3 bits; the remaining bit of the 64-bit field is `DILocation`'s `irlayers` flag. A group that does not fit in 60 bits is clamped to 0, so an out-of-range value means "not interesting" rather than silently aliasing another group. Instructions in the same function with the same `(atomGroup, inlinedAt)` pair are part of the same source atom. `atomRank` determines `is_stmt` preference within that group, where a lower number is higher precedence. Higher rank instructions act as "backup" `is_stmt` locations, providing good fallback locations if/when the primary candidate gets optimized away. The default values of 0 indicate the instruction isn’t interesting - it's not an `is_stmt` candidate. If `keyInstructions` in `DISubprogram` is false (default) then the new `DILocation` metadata is ignored for the function (including inlined instances) when emitting DWARF.
2. *Clang annotates key instructions* with the new metadata. Variable assignments (stores, memory intrinsics), control flow (branches and their conditions, some unconditional branches), and exception handling instructions are annotated. Calls are ignored as they're unconditionally marked `is_stmt`.
diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 3a616e8a29fcf..c1642705ab481 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -7170,6 +7170,55 @@ mandatory, and points at an {ref}`DILexicalBlockFile`, an
!0 = !DILocation(line: 2900, column: 42, scope: !1, inlinedAt: !2)
```
+The optional `irlayers:` field points at a {ref}`DILayerLocList`, giving the
+instruction's position in one or more intermediate IRs it was lowered through, in
+addition to its primary source position. It is independent of `inlinedAt:`; a
+location may have either, both, or neither.
+
+```text
+!0 = !DILocation(line: 2900, column: 42, scope: !1, irlayers: !3)
+```
+
+(DILayerLoc)=
+
+##### DILayerLoc
+
+`DILayerLoc` nodes represent a source position in one intermediate IR level that
+a program was lowered through — for example a tile IR or an MLIR module produced
+part-way through compilation. The `kind:` field names the level and the `file:`
+field points at a {ref}`DIFile` for it; both are mandatory. `line:` and
+`column:` are the position within that file.
+
+Unlike a {ref}`DILocation`, a `DILayerLoc` has no scope and no inlined-at
+context: it is a bare coordinate in a file, not a location in a scope tree.
+
+```text
+!0 = !DILayerLoc(line: 100, column: 1, file: !1, kind: "tile ir")
+```
+
+(DILayerLocList)=
+
+##### DILayerLocList
+
+`DILayerLocList` nodes hold a list of {ref}`DILayerLoc` operands, and are
+referenced by a {ref}`DILocation`'s `irlayers:` field.
+
+Operand order is preserved and is part of the node's identity — two lists with
+the same entries in a different order are different nodes — and a consumer sees
+the entries in that order. LLVM itself attaches no meaning to the order: it does
+not define which level comes first and does not check any particular arrangement,
+so any convention (such as listing levels in lowering order) is an agreement
+between a producer and its consumer.
+
+Both node types are uniqued, so instructions sharing a position at some level
+share the corresponding node.
+
+```text
+!0 = !DILayerLocList(!1, !2)
+!1 = !DILayerLoc(line: 100, column: 1, file: !3, kind: "tile ir")
+!2 = !DILayerLoc(line: 7, column: 3, file: !4, kind: "gpu ir")
+```
+
(DILocalVariable)=
##### DILocalVariable
diff --git a/llvm/include/llvm-c/DebugInfo.h b/llvm/include/llvm-c/DebugInfo.h
index 155cbe32ee15e..21b1e021ff310 100644
--- a/llvm/include/llvm-c/DebugInfo.h
+++ b/llvm/include/llvm-c/DebugInfo.h
@@ -211,6 +211,8 @@ enum {
LLVMDIAssignIDMetadataKind,
LLVMDISubrangeTypeMetadataKind,
LLVMDIFixedPointTypeMetadataKind,
+ LLVMDILayerLocMetadataKind,
+ LLVMDILayerLocListMetadataKind,
};
typedef unsigned LLVMMetadataKind;
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index 358f9a65a80af..3f7db3456ea78 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -400,6 +400,8 @@ enum MetadataCodes {
METADATA_ASSIGN_ID = 47, // [distinct, ...]
METADATA_SUBRANGE_TYPE = 48, // [distinct, ...]
METADATA_FIXED_POINT_TYPE = 49, // [distinct, ...]
+ METADATA_LAYERLOC = 50, // [distinct, line, column, file, kind]
+ METADATA_LAYERLOCLIST = 51, // [distinct, n x layerloc]
};
// The constants block (CONSTANTS_BLOCK_ID) describes emission for each
diff --git a/llvm/include/llvm/IR/DebugInfoMetadata.h b/llvm/include/llvm/IR/DebugInfoMetadata.h
index 33a1a6e482555..ab6ad11913495 100644
--- a/llvm/include/llvm/IR/DebugInfoMetadata.h
+++ b/llvm/include/llvm/IR/DebugInfoMetadata.h
@@ -2658,6 +2658,114 @@ class DISubprogram : public DILocalScope {
}
};
+/// A single intermediate-IR layer location.
+///
+/// One source coordinate in an intermediate IR level (e.g. TileIR, MLIR) that
+/// sits between the high-level source and the final LLVM IR. Lightweight: it
+/// references its \a DIFile directly (no scope, no discriminator) and stores
+/// line/column in the free Metadata subclass-data slots. Grouped behind a
+/// \a DILayerLocList on \a DILocation's optional `irlayers` operand.
+///
+/// Uses the SubclassData16 and SubclassData32 Metadata slots.
+class DILayerLoc : public MDNode {
+ friend class LLVMContextImpl;
+ friend class MDNode;
+
+ DILayerLoc(LLVMContext &C, StorageType Storage, unsigned Line,
+ unsigned Column, ArrayRef<Metadata *> Ops)
+ : MDNode(C, DILayerLocKind, Storage, Ops) {
+ assert(Ops.size() == 2 && "Expected {kind, file}");
+ assert(Column < (1u << 16) && "Expected 16-bit column");
+ SubclassData32 = Line;
+ SubclassData16 = Column;
+ }
+ ~DILayerLoc() { dropAllReferences(); }
+
+ static DILayerLoc *getImpl(LLVMContext &Context, MDString *Kind,
+ Metadata *File, unsigned Line, unsigned Column,
+ StorageType Storage, bool ShouldCreate = true);
+
+ TempDILayerLoc cloneImpl() const {
+ return getTemporary(getContext(), getRawKind(), getRawFile(), getLine(),
+ getColumn());
+ }
+
+public:
+ DEFINE_MDNODE_GET(DILayerLoc,
+ (MDString * Kind, Metadata *File, unsigned Line,
+ unsigned Column),
+ (Kind, File, Line, Column))
+
+ TempDILayerLoc clone() const { return cloneImpl(); }
+
+ unsigned getLine() const { return SubclassData32; }
+ unsigned getColumn() const { return SubclassData16; }
+
+ MDString *getRawKind() const {
+ return cast_if_present<MDString>(getOperand(0));
+ }
+ StringRef getKind() const {
+ if (MDString *K = getRawKind())
+ return K->getString();
+ return StringRef();
+ }
+ Metadata *getRawFile() const { return getOperand(1); }
+ DIFile *getFile() const { return cast_if_present<DIFile>(getRawFile()); }
+
+ static bool classof(const Metadata *MD) {
+ return MD->getMetadataID() == DILayerLocKind;
+ }
+};
+
+/// A uniqued list of \a DILayerLoc entries.
+///
+/// The container node behind \a DILocation's optional `irlayers` operand:
+/// essentially an \a MDTuple of \a DILayerLoc refs with typed accessors and its
+/// own metadata kind, so consumers can type-check it with
+/// `isa<DILayerLocList>`. Operand order is preserved and is part of the node's
+/// identity, but LLVM assigns it no meaning.
+class DILayerLocList : public MDNode {
+ friend class LLVMContextImpl;
+ friend class MDNode;
+
+ DILayerLocList(LLVMContext &C, StorageType Storage, unsigned Hash,
+ ArrayRef<Metadata *> Ops)
+ : MDNode(C, DILayerLocListKind, Storage, Ops) {
+ setHash(Hash);
+ }
+ ~DILayerLocList() { dropAllReferences(); }
+
+ void setHash(unsigned Hash) { SubclassData32 = Hash; }
+ void recalculateHash();
+
+ static DILayerLocList *getImpl(LLVMContext &Context,
+ ArrayRef<Metadata *> Layers,
+ StorageType Storage, bool ShouldCreate = true);
+
+ TempDILayerLocList cloneImpl() const {
+ return getTemporary(getContext(), SmallVector<Metadata *, 4>(operands()));
+ }
+
+public:
+ /// Get the operand hash (used by the MDNodeOpsKey uniquing key).
+ unsigned getHash() const { return SubclassData32; }
+
+ DEFINE_MDNODE_GET(DILayerLocList, (ArrayRef<Metadata *> Layers), (Layers))
+
+ TempDILayerLocList clone() const { return cloneImpl(); }
+
+ unsigned getNumLayers() const { return getNumOperands(); }
+ DILayerLoc *getLayer(unsigned I) const {
+ return cast_if_present<DILayerLoc>(getOperand(I));
+ }
+ using layer_iterator = MDNode::op_iterator;
+ iterator_range<layer_iterator> layers() const { return operands(); }
+
+ static bool classof(const Metadata *MD) {
+ return MD->getMetadataID() == DILayerLocListKind;
+ }
+};
+
/// Debug location.
///
/// A debug location in source code, used for debug info and otherwise.
@@ -2668,34 +2776,40 @@ class DISubprogram : public DILocalScope {
class DILocation : public MDNode {
friend class LLVMContextImpl;
friend class MDNode;
- uint64_t AtomGroup : 61;
+ uint64_t AtomGroup : 60;
uint64_t AtomRank : 3;
+ // Disambiguates the two optional trailing operands, layout
+ // [scope, (inlinedAt?), (irlayers?)]: irlayers is always last when present.
+ uint64_t HasIRLayers : 1;
DILocation(LLVMContext &C, StorageType Storage, unsigned Line,
unsigned Column, uint64_t AtomGroup, uint8_t AtomRank,
- ArrayRef<Metadata *> MDs, bool ImplicitCode);
+ bool HasIRLayers, ArrayRef<Metadata *> MDs, bool ImplicitCode);
~DILocation() { dropAllReferences(); }
- LLVM_ABI static DILocation *
- getImpl(LLVMContext &Context, unsigned Line, unsigned Column, Metadata *Scope,
- Metadata *InlinedAt, bool ImplicitCode, uint64_t AtomGroup,
- uint8_t AtomRank, StorageType Storage, bool ShouldCreate = true);
+ LLVM_ABI static DILocation *getImpl(LLVMContext &Context, unsigned Line,
+ unsigned Column, Metadata *Scope,
+ Metadata *InlinedAt, bool ImplicitCode,
+ uint64_t AtomGroup, uint8_t AtomRank,
+ Metadata *IRLayers, StorageType Storage,
+ bool ShouldCreate = true);
static DILocation *getImpl(LLVMContext &Context, unsigned Line,
unsigned Column, DILocalScope *Scope,
DILocation *InlinedAt, bool ImplicitCode,
uint64_t AtomGroup, uint8_t AtomRank,
- StorageType Storage, bool ShouldCreate = true) {
+ Metadata *IRLayers, StorageType Storage,
+ bool ShouldCreate = true) {
return getImpl(Context, Line, Column, static_cast<Metadata *>(Scope),
static_cast<Metadata *>(InlinedAt), ImplicitCode, AtomGroup,
- AtomRank, Storage, ShouldCreate);
+ AtomRank, IRLayers, Storage, ShouldCreate);
}
TempDILocation cloneImpl() const {
- // Get the raw scope/inlinedAt since it is possible to invoke this on
- // a DILocation containing temporary metadata.
+ // Get the raw scope/inlinedAt/irlayers since it is possible to invoke this
+ // on a DILocation containing temporary metadata.
return getTemporary(getContext(), getLine(), getColumn(), getRawScope(),
getRawInlinedAt(), isImplicitCode(), getAtomGroup(),
- getAtomRank());
+ getAtomRank(), getRawIRLayers());
}
public:
@@ -2706,7 +2820,8 @@ class DILocation : public MDNode {
if (!getAtomGroup() && !getAtomRank())
return this;
return get(getContext(), getLine(), getColumn(), getScope(), getInlinedAt(),
- isImplicitCode());
+ isImplicitCode(), /*AtomGroup=*/0, /*AtomRank=*/0,
+ getRawIRLayers());
}
// Disallow replacing operands.
@@ -2715,15 +2830,17 @@ class DILocation : public MDNode {
DEFINE_MDNODE_GET(DILocation,
(unsigned Line, unsigned Column, Metadata *Scope,
Metadata *InlinedAt = nullptr, bool ImplicitCode = false,
- uint64_t AtomGroup = 0, uint8_t AtomRank = 0),
+ uint64_t AtomGroup = 0, uint8_t AtomRank = 0,
+ Metadata *IRLayers = nullptr),
(Line, Column, Scope, InlinedAt, ImplicitCode, AtomGroup,
- AtomRank))
+ AtomRank, IRLayers))
DEFINE_MDNODE_GET(DILocation,
(unsigned Line, unsigned Column, DILocalScope *Scope,
DILocation *InlinedAt = nullptr, bool ImplicitCode = false,
- uint64_t AtomGroup = 0, uint8_t AtomRank = 0),
+ uint64_t AtomGroup = 0, uint8_t AtomRank = 0,
+ Metadata *IRLayers = nullptr),
(Line, Column, Scope, InlinedAt, ImplicitCode, AtomGroup,
- AtomRank))
+ AtomRank, IRLayers))
/// Return a (temporary) clone of this.
TempDILocation clone() const { return cloneImpl(); }
@@ -2945,11 +3062,32 @@ class DILocation : public MDNode {
Metadata *getRawScope() const { return getOperand(0); }
Metadata *getRawInlinedAt() const {
- if (getNumOperands() == 2)
+ // Layout: [scope, (inlinedAt?), (irlayers?)]. irlayers, when present, is
+ // always the last operand; discount it before the inlinedAt count trick.
+ unsigned NonLayerOps = getNumOperands() - (HasIRLayers ? 1 : 0);
+ if (NonLayerOps == 2)
return getOperand(1);
return nullptr;
}
+ /// The optional intermediate-IR layer list (\a DILayerLocList), or null.
+ /// Raw form: returns the operand without casting, so it is safe to call
+ /// before forward-ref resolution (the operand may still be a placeholder).
+ Metadata *getRawIRLayers() const {
+ return HasIRLayers ? getOperand(getNumOperands() - 1) : nullptr;
+ }
+ DILayerLocList *getIRLayers() const {
+ return cast_if_present<DILayerLocList>(getRawIRLayers());
+ }
+ unsigned getNumLayers() const {
+ DILayerLocList *L = getIRLayers();
+ return L ? L->getNumLayers() : 0;
+ }
+ DILayerLoc *getLayer(unsigned I) const {
+ DILayerLocList *L = getIRLayers();
+ return L ? L->getLayer(I) : nullptr;
+ }
+
static bool classof(const Metadata *MD) {
return MD->getMetadataID() == DILocationKind;
}
@@ -3103,7 +3241,7 @@ DILocation::cloneWithDiscriminator(unsigned Discriminator) const {
DILexicalBlockFile::get(getContext(), Scope, getFile(), Discriminator);
return DILocation::get(getContext(), getLine(), getColumn(), NewScope,
getInlinedAt(), isImplicitCode(), getAtomGroup(),
- getAtomRank());
+ getAtomRank(), getRawIRLayers());
}
unsigned DILocation::getBaseDiscriminator() const {
diff --git a/llvm/include/llvm/IR/DebugLoc.h b/llvm/include/llvm/IR/DebugLoc.h
index 34e6aadd83cae..c9a5149c80940 100644
--- a/llvm/include/llvm/IR/DebugLoc.h
+++ b/llvm/include/llvm/IR/DebugLoc.h
@@ -246,13 +246,18 @@ class DebugLoc {
return true;
return ((bool)*this == (bool)Other) && getLine() == Other.getLine() &&
getCol() == Other.getCol() && getScope() == Other.getScope() &&
- getInlinedAt() == Other.getInlinedAt();
+ getInlinedAt() == Other.getInlinedAt() &&
+ getRawIRLayers() == Other.getRawIRLayers();
}
LLVM_ABI unsigned getLine() const;
LLVM_ABI unsigned getCol() const;
LLVM_ABI MDNode *getScope() const;
LLVM_ABI DILocation *getInlinedAt() const;
+ /// The raw intermediate-IR layer list (\a DILayerLocList) of the underlying
+ /// location, or null. Out-of-line so this header need not see DILocation's
+ /// definition.
+ LLVM_ABI MDNode *getRawIRLayers() const;
/// Get the fully inlined-at scope for a DebugLoc.
///
diff --git a/llvm/include/llvm/IR/Metadata.def b/llvm/include/llvm/IR/Metadata.def
index 511bf48707f00..80817892d0d99 100644
--- a/llvm/include/llvm/IR/Metadata.def
+++ b/llvm/include/llvm/IR/Metadata.def
@@ -81,6 +81,8 @@ HANDLE_METADATA_LEAF(DIArgList)
HANDLE_MDNODE_BRANCH(MDNode)
HANDLE_MDNODE_LEAF_UNIQUABLE(MDTuple)
HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DILocation)
+HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DILayerLoc)
+HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DILayerLocList)
HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DIExpression)
HANDLE_SPECIALIZED_MDNODE_LEAF_UNIQUABLE(DIGlobalVariableExpression)
HANDLE_SPECIALIZED_MDNODE_BRANCH(DINode)
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index edff818b3b152..34d583820227f 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -5784,13 +5784,49 @@ bool LLParser::parseDILocation(MDNode *&Result, bool IsDistinct) {
OPTIONAL(inlinedAt, MDField, ); \
OPTIONAL(isImplicitCode, MDBoolField, (false)); \
OPTIONAL(atomGroup, MDUnsignedField, (0, UINT64_MAX)); \
- OPTIONAL(atomRank, MDUnsignedField, (0, UINT8_MAX));
+ OPTIONAL(atomRank, MDUnsignedField, (0, UINT8_MAX)); \
+ OPTIONAL(irlayers, MDField, );
PARSE_MD_FIELDS();
#undef VISIT_MD_FIELDS
- Result = GET_OR_DISTINCT(
- DILocation, (Context, line.Val, column.Val, scope.Val, inlinedAt.Val,
- isImplicitCode.Val, atomGroup.Val, atomRank.Val));
+ Result =
+ GET_OR_DISTINCT(DILocation, (Context, line.Val, column.Val, scope.Val,
+ inlinedAt.Val, isImplicitCode.Val,
+ atomGroup.Val, atomRank.Val, irlayers.Val));
+ return false;
+}
+
+bool LLParser::parseDILayerLoc(MDNode *&Result, bool IsDistinct) {
+#define VISIT_MD_FIELDS(OPTIONAL, REQUIRED) \
+ OPTIONAL(line, LineField, ); \
+ OPTIONAL(column, ColumnField, ); \
+ REQUIRED(file, MDField, (/* AllowNull */ false)); \
+ REQUIRED(kind, MDStringField, );
+ PARSE_MD_FIELDS();
+#undef VISIT_MD_FIELDS
+
+ Result = GET_OR_DISTINCT(DILayerLoc,
+ (Context, kind.Val, file.Val, line.Val, column.Val));
+ return false;
+}
+
+bool LLParser::parseDILayerLocList(MDNode *&Result, bool IsDistinct) {
+ // ::= !DILayerLocList(!a, !b, ...)
+ Lex.Lex(); // eat the '!DILayerLocList' type name
+ if (parseToken(lltok::lparen, "expected '(' here"))
+ return true;
+ SmallVector<Metadata *, 4> Layers;
+ if (!EatIfPresent(lltok::rparen)) {
+ do {
+ Metadata *MD;
+ if (parseMetadata(MD, nullptr))
+ return true;
+ Layers.push_back(MD);
+ } while (EatIfPresent(lltok::comma));
+ if (parseToken(lltok::rparen, "expected ')' here"))
+ return true;
+ }
+ Result = GET_OR_DISTINCT(DILayerLocList, (Context, Layers));
return false;
}
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index ac61ede6395af..b093854488ae0 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -5241,8 +5241,8 @@ Error BitcodeReader::parseFunctionBody(Function *F) {
unsigned Line = Record[0], Col = Record[1];
unsigned ScopeID = Record[2], IAID = Record[3];
bool isImplicitCode = Record.size() >= 5 && Record[4];
- uint64_t AtomGroup = Record.size() == 7 ? Record[5] : 0;
- uint8_t AtomRank = Record.size() == 7 ? Record[6] : 0;
+ uint64_t AtomGroup = Record.size() >= 7 ? Record[5] : 0;
+ uint8_t AtomRank = Record.size() >= 7 ? Record[6] : 0;
MDNode *Scope = nullptr, *IA = nullptr;
if (ScopeID) {
@@ -5257,9 +5257,12 @@ Error BitcodeReader::parseFunctionBody(Function *F) {
if (!IA)
return error("Invalid debug loc record");
}
+ Metadata *IRLayers = nullptr;
+ if (Record.size() >= 8 && Record[7])
+ IRLayers = MDLoader->getMetadataFwdRefOrLoad(Record[7] - 1);
LastLoc = DILocation::get(Scope->getContext(), Line, Col, Scope, IA,
- isImplicitCode, AtomGroup, AtomRank);
+ isImplicitCode, AtomGroup, AtomRank, IRLayers);
I->setDebugLoc(LastLoc);
I = nullptr;
continue;
diff --git a/llvm/lib/Bitcode/Reader/MetadataLoader.cpp b/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
index 8b7beb1a8ff9e..4fa47f7db0e21 100644
--- a/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
+++ b/llvm/lib/Bitcode/Reader/MetadataLoader.cpp
@@ -1472,8 +1472,12 @@ Error MetadataLoader::MetadataLoaderImpl::parseOneMetadata(
break;
}
case bitc::METADATA_LOCATION: {
- // 5: inlinedAt, 6: isImplicit, 8: Key Instructions fields.
- if (Record.size() != 5 && Record.size() != 6 && Record.size() != 8)
+ // Defined shapes: 5 (through inlinedAt), 6 (isImplicit), 8 (adds the Key
+ // Instructions fields), 9 (adds irlayers). Anything longer is a record from
+ // a newer writer and its trailing fields are ignored, which is what makes
+ // this encoding append-only; anything shorter, or a 7 (an atomGroup with no
+ // atomRank), is a shape no writer produces.
+ if (Record.size() < 5 || Record.size() == 7)
return error("Invalid record");
IsDistinct = Record[0];
@@ -1482,15 +1486,47 @@ Error MetadataLoader::MetadataLoaderImpl::parseOneMetadata(
Metadata *Scope = getMD(Record[3]);
Metadata *InlinedAt = getMDOrNull(Record[4]);
bool ImplicitCode = Record.size() >= 6 && Record[5];
- uint64_t AtomGroup = Record.size() == 8 ? Record[6] : 0;
- uint8_t AtomRank = Record.size() == 8 ? Record[7] : 0;
+ uint64_t AtomGroup = Record.size() >= 8 ? Record[6] : 0;
+ uint8_t AtomRank = Record.size() >= 8 ? Record[7] : 0;
+ Metadata *IRLayers = Record.size() >= 9 ? getMDOrNull(Record[8]) : nullptr;
MetadataList.assignValue(
- GET_OR_DISTINCT(DILocation, (Context, Line, Column, Scope, InlinedAt,
- ImplicitCode, AtomGroup, AtomRank)),
+ GET_OR_DISTINCT(DILocation,
+ (Context, Line, Column, Scope, InlinedAt, ImplicitCode,
+ AtomGroup, AtomRank, IRLayers)),
NextMetadataNo);
NextMetadataNo++;
break;
}
+ case bitc::METADATA_LAYERLOC: {
+ if (Record.size() != 5)
+ return error("Invalid record");
+
+ IsDistinct = Record[0];
+ unsigned Line = Record[1];
+ unsigned Column = Record[2];
+ Metadata *File = getMD(Record[3]);
+ // Read the kind opaquely and let the verifier report a bad type, as the
+ // other DI readers do: an unchecked cast would assert on malformed bitcode.
+ MDString *Kind = dyn_cast_if_present<MDString>(getMD(Record[4]));
+ MetadataList.assignValue(
+ GET_OR_DISTINCT(DILayerLoc, (Context, Kind, File, Line, Column)),
+ NextMetadataNo);
+ NextMetadataNo++;
+ break;
+ }
+ case bitc::METADATA_LAYERLOCLIST: {
+ if (Record.empty())
+ return error("Invalid record");
+
+ IsDistinct = Record[0];
+ SmallVector<Metadata *, 4> Elts;
+ for (unsigned I = 1, E = Record.size(); I != E; ++I)
+ Elts.push_back(getMDOrNull(Record[I]));
+ MetadataList.assignValue(GET_OR_DISTINCT(DILayerLocList, (Context, Elts)),
+ NextMetadataNo);
+ NextMetadataNo++;
+ break;
+ }
case bitc::METADATA_GENERIC_DEBUG: {
if (Record.size() < 4)
return error("Invalid record");
diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
index 571336c217797..4edba342fed48 100644
--- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -340,6 +340,10 @@ class ModuleBitcodeWriter : public ModuleBitcodeWriterBase {
unsigned createDILocationAbbrev();
void writeDILocation(const DILocation *N, SmallVectorImpl<uint64_t> &Record,
unsigned &Abbrev);
+ void writeDILayerLoc(const DILayerLoc *N, SmallVectorImpl<uint64_t> &Record,
+ unsigned Abbrev);
+ void writeDILayerLocList(const DILayerLocList *N,
+ SmallVectorImpl<uint64_t> &Record, unsigned Abbrev);
unsigned createGenericDINodeAbbrev();
void writeGenericDINode(const GenericDINode *N,
SmallVectorImpl<uint64_t> &Record, unsigned &Abbrev);
@@ -1898,6 +1902,7 @@ unsigned ModuleBitcodeWriter::createDILocationAbbrev() {
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isImplicitCode
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // atomGroup
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // atomRank
+ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // irlayers (0 = none)
return Stream.EmitAbbrev(std::move(Abbv));
}
@@ -1915,10 +1920,33 @@ void ModuleBitcodeWriter::writeDILocation(const DILocation *N,
Record.push_back(N->isImplicitCode());
Record.push_back(N->getAtomGroup());
Record.push_back(N->getAtomRank());
+ Record.push_back(VE.getMetadataOrNullID(N->getRawIRLayers()));
Stream.EmitRecord(bitc::METADATA_LOCATION, Record, Abbrev);
Record.clear();
}
+void ModuleBitcodeWriter::writeDILayerLoc(const DILayerLoc *N,
+ SmallVectorImpl<uint64_t> &Record,
+ unsigned Abbrev) {
+ Record.push_back(N->isDistinct());
+ Record.push_back(N->getLine());
+ Record.push_back(N->getColumn());
+ Record.push_back(VE.getMetadataID(N->getRawFile()));
+ Record.push_back(VE.getMetadataID(N->getRawKind()));
+ Stream.EmitRecord(bitc::METADATA_LAYERLOC, Record, Abbrev);
+ Record.clear();
+}
+
+void ModuleBitcodeWriter::writeDILayerLocList(const DILayerLocList *N,
+ SmallVectorImpl<uint64_t> &Record,
+ unsigned Abbrev) {
+ Record.push_back(N->isDistinct());
+ for (auto &I : N->operands())
+ Record.push_back(VE.getMetadataOrNullID(I));
+ Stream.EmitRecord(bitc::METADATA_LAYERLOCLIST, Record, Abbrev);
+ Record.clear();
+}
+
unsigned ModuleBitcodeWriter::createGenericDINodeAbbrev() {
// Assume the column is usually under 128, and always output the inlined-at
// location (it's never more expensive than building an array size 1).
@@ -3873,6 +3901,7 @@ void ModuleBitcodeWriter::writeFunction(
Vals.push_back(DL->isImplicitCode());
Vals.push_back(DL->getAtomGroup());
Vals.push_back(DL->getAtomRank());
+ Vals.push_back(VE.getMetadataOrNullID(DL->getRawIRLayers()));
Stream.EmitRecord(bitc::FUNC_CODE_DEBUG_LOC, Vals,
FUNCTION_DEBUG_LOC_ABBREV);
Vals.clear();
@@ -4281,6 +4310,7 @@ void ModuleBitcodeWriter::writeBlockInfo() {
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1));
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // Atom group.
Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 3)); // Atom rank.
+ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // irlayers (0 = none).
if (Stream.EmitBlockInfoAbbrev(bitc::FUNCTION_BLOCK_ID, Abbv) !=
FUNCTION_DEBUG_LOC_ABBREV)
llvm_unreachable("Unexpected abbrev ordering!");
diff --git a/llvm/lib/CodeGen/MIRParser/MIParser.cpp b/llvm/lib/CodeGen/MIRParser/MIParser.cpp
index bb0b87cc042d0..71be0869ef244 100644
--- a/llvm/lib/CodeGen/MIRParser/MIParser.cpp
+++ b/llvm/lib/CodeGen/MIRParser/MIParser.cpp
@@ -2573,6 +2573,7 @@ bool MIParser::parseDILocation(MDNode *&Loc) {
bool ImplicitCode = false;
uint64_t AtomGroup = 0;
uint64_t AtomRank = 0;
+ MDNode *IRLayers = nullptr;
if (expectAndConsume(MIToken::lparen))
return true;
@@ -2670,6 +2671,16 @@ bool MIParser::parseDILocation(MDNode *&Loc) {
lex();
continue;
}
+ if (Token.stringValue() == "irlayers") {
+ lex();
+ if (expectAndConsume(MIToken::colon))
+ return true;
+ if (parseMDNode(IRLayers))
+ return error("expected metadata node");
+ if (!isa<DILayerLocList>(IRLayers))
+ return error("expected DILayerLocList node");
+ continue;
+ }
}
return error(Twine("invalid DILocation argument '") +
Token.stringValue() + "'");
@@ -2685,7 +2696,7 @@ bool MIParser::parseDILocation(MDNode *&Loc) {
return error("DILocation requires a scope");
Loc = DILocation::get(MF.getFunction().getContext(), Line, Column, Scope,
- InlinedAt, ImplicitCode, AtomGroup, AtomRank);
+ InlinedAt, ImplicitCode, AtomGroup, AtomRank, IRLayers);
return false;
}
diff --git a/llvm/lib/IR/AsmWriter.cpp b/llvm/lib/IR/AsmWriter.cpp
index cad4f17b0db91..132a08b7a9dc7 100644
--- a/llvm/lib/IR/AsmWriter.cpp
+++ b/llvm/lib/IR/AsmWriter.cpp
@@ -2124,6 +2124,29 @@ static void writeDILocation(raw_ostream &Out, const DILocation *DL,
/* Default */ false);
Printer.printInt("atomGroup", DL->getAtomGroup());
Printer.printInt<unsigned>("atomRank", DL->getAtomRank());
+ Printer.printMetadata("irlayers", DL->getRawIRLayers());
+ Out << ")";
+}
+
+static void writeDILayerLoc(raw_ostream &Out, const DILayerLoc *N,
+ AsmWriterContext &WriterCtx) {
+ Out << "!DILayerLoc(";
+ MDFieldPrinter Printer(Out, WriterCtx);
+ Printer.printInt("line", N->getLine(), /* ShouldSkipZero */ false);
+ Printer.printInt("column", N->getColumn());
+ Printer.printMetadata("file", N->getRawFile(), /* ShouldSkipNull */ false);
+ Printer.printString("kind", N->getKind(), /* ShouldSkipEmpty */ false);
+ Out << ")";
+}
+
+static void writeDILayerLocList(raw_ostream &Out, const DILayerLocList *N,
+ AsmWriterContext &WriterCtx) {
+ Out << "!DILayerLocList(";
+ ListSeparator FS;
+ for (const MDOperand &Op : N->operands()) {
+ Out << FS;
+ writeMetadataAsOperand(Out, Op, WriterCtx);
+ }
Out << ")";
}
diff --git a/llvm/lib/IR/DebugInfoMetadata.cpp b/llvm/lib/IR/DebugInfoMetadata.cpp
index 5e9e0e4e81c6e..779742f5a02da 100644
--- a/llvm/lib/IR/DebugInfoMetadata.cpp
+++ b/llvm/lib/IR/DebugInfoMetadata.cpp
@@ -13,6 +13,7 @@
#include "llvm/IR/DebugInfoMetadata.h"
#include "LLVMContextImpl.h"
#include "MetadataImpl.h"
+#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/BinaryFormat/Dwarf.h"
@@ -26,6 +27,7 @@
#include <numeric>
#include <optional>
+#include <tuple>
using namespace llvm;
@@ -60,15 +62,17 @@ DebugVariableAggregate::DebugVariableAggregate(const DbgVariableRecord *DVR)
DILocation::DILocation(LLVMContext &C, StorageType Storage, unsigned Line,
unsigned Column, uint64_t AtomGroup, uint8_t AtomRank,
- ArrayRef<Metadata *> MDs, bool ImplicitCode)
+ bool HasIRLayers, ArrayRef<Metadata *> MDs,
+ bool ImplicitCode)
: MDNode(C, DILocationKind, Storage, MDs), AtomGroup(AtomGroup),
- AtomRank(AtomRank) {
+ AtomRank(AtomRank), HasIRLayers(HasIRLayers) {
assert(AtomRank <= 7 && "AtomRank number should fit in 3 bits");
+ assert(AtomGroup < (1ULL << 60) && "AtomGroup number should fit in 60 bits");
if (AtomGroup)
C.updateDILocationAtomGroupWaterline(AtomGroup + 1);
- assert((MDs.size() == 1 || MDs.size() == 2) &&
- "Expected a scope and optional inlined-at");
+ assert(MDs.size() >= 1 && MDs.size() <= 3 &&
+ "Expected a scope and optional inlined-at + irlayers");
// Set line and column.
assert(Column < (1u << 16) && "Expected 16-bit column");
@@ -88,15 +92,20 @@ DILocation *DILocation::getImpl(LLVMContext &Context, unsigned Line,
unsigned Column, Metadata *Scope,
Metadata *InlinedAt, bool ImplicitCode,
uint64_t AtomGroup, uint8_t AtomRank,
- StorageType Storage, bool ShouldCreate) {
+ Metadata *IRLayers, StorageType Storage,
+ bool ShouldCreate) {
// Fixup column.
adjustColumn(Column);
+ // Clamp rather than truncate, which would wrap into a different valid group.
+ if (AtomGroup >= (1ULL << 60))
+ AtomGroup = 0;
+
if (Storage == Uniqued) {
- if (auto *N = getUniqued(Context.pImpl->DILocations,
- DILocationInfo::KeyTy(Line, Column, Scope,
- InlinedAt, ImplicitCode,
- AtomGroup, AtomRank)))
+ if (auto *N = getUniqued(
+ Context.pImpl->DILocations,
+ DILocationInfo::KeyTy(Line, Column, Scope, InlinedAt, ImplicitCode,
+ AtomGroup, AtomRank, IRLayers)))
return N;
if (!ShouldCreate)
return nullptr;
@@ -104,16 +113,62 @@ DILocation *DILocation::getImpl(LLVMContext &Context, unsigned Line,
assert(ShouldCreate && "Expected non-uniqued nodes to always be created");
}
- SmallVector<Metadata *, 2> Ops;
+ SmallVector<Metadata *, 3> Ops;
Ops.push_back(Scope);
if (InlinedAt)
Ops.push_back(InlinedAt);
- return storeImpl(new (Ops.size(), Storage)
- DILocation(Context, Storage, Line, Column, AtomGroup,
- AtomRank, Ops, ImplicitCode),
+ if (IRLayers)
+ Ops.push_back(IRLayers);
+ return storeImpl(new (Ops.size(), Storage) DILocation(
+ Context, Storage, Line, Column, AtomGroup, AtomRank,
+ /*HasIRLayers=*/IRLayers != nullptr, Ops, ImplicitCode),
Storage, Context.pImpl->DILocations);
}
+DILayerLoc *DILayerLoc::getImpl(LLVMContext &Context, MDString *Kind,
+ Metadata *File, unsigned Line, unsigned Column,
+ StorageType Storage, bool ShouldCreate) {
+ // Clamp an out-of-range column to 0 (the 16-bit SubclassData limit), as
+ // DILocation::getImpl does; the ctor otherwise asserts and release builds
+ // truncate.
+ adjustColumn(Column);
+ if (Storage == Uniqued) {
+ if (auto *N = getUniqued(Context.pImpl->DILayerLocs,
+ DILayerLocInfo::KeyTy(Kind, File, Line, Column)))
+ return N;
+ if (!ShouldCreate)
+ return nullptr;
+ }
+ Metadata *Ops[] = {Kind, File};
+ return storeImpl(new (std::size(Ops), Storage)
+ DILayerLoc(Context, Storage, Line, Column, Ops),
+ Storage, Context.pImpl->DILayerLocs);
+}
+
+DILayerLocList *DILayerLocList::getImpl(LLVMContext &Context,
+ ArrayRef<Metadata *> Layers,
+ StorageType Storage,
+ bool ShouldCreate) {
+ unsigned Hash = 0;
+ if (Storage == Uniqued) {
+ DILayerLocListInfo::KeyTy Key(Layers);
+ if (auto *N = getUniqued(Context.pImpl->DILayerLocLists, Key))
+ return N;
+ if (!ShouldCreate)
+ return nullptr;
+ Hash = Key.getHash();
+ } else {
+ assert(ShouldCreate && "Expected non-uniqued nodes to always be created");
+ }
+ return storeImpl(new (Layers.size(), Storage)
+ DILayerLocList(Context, Storage, Hash, Layers),
+ Storage, Context.pImpl->DILayerLocLists);
+}
+
+void DILayerLocList::recalculateHash() {
+ setHash(DILayerLocListInfo::KeyTy::calculateHash(this));
+}
+
DILocation *DILocation::getMergedLocations(ArrayRef<DILocation *> Locs) {
if (Locs.empty())
return nullptr;
@@ -220,6 +275,40 @@ struct ScopeLocationsMatcher {
}
};
+// Returns a uniqued DILayerLocList holding the intersection of LocA's and
+// LocB's layer sets, or null if either has no layers or they share none.
+// Entries keep LocA's relative order: LLVM assigns no meaning to layer order,
+// but it is part of a list's identity and visible to consumers, so preserve
+// rather than sort.
+static Metadata *mergeIRLayers(LLVMContext &C, const DILocation *LocA,
+ const DILocation *LocB) {
+ DILayerLocList *LA = LocA->getIRLayers();
+ DILayerLocList *LB = LocB->getIRLayers();
+ if (!LA || !LB)
+ return nullptr;
+ // Entries match on their fields rather than by pointer: DILayerLocs are
+ // uniqued, but `distinct` ones are legal and must not look disjoint. Keying
+ // on the fields also keeps the intersection linear in the two list lengths.
+ using LayerKey = std::tuple<Metadata *, Metadata *, unsigned, unsigned>;
+ auto keyOf = [](const DILayerLoc *L) {
+ return LayerKey(L->getRawKind(), L->getRawFile(), L->getLine(),
+ L->getColumn());
+ };
+ SmallDenseSet<LayerKey, 2> BLayers;
+ for (unsigned I = 0, E = LB->getNumLayers(); I != E; ++I)
+ if (const DILayerLoc *R = LB->getLayer(I))
+ BLayers.insert(keyOf(R));
+ SmallVector<Metadata *, 2> Keep;
+ for (unsigned I = 0, E = LA->getNumLayers(); I != E; ++I) {
+ DILayerLoc *L = LA->getLayer(I);
+ if (L && BLayers.contains(keyOf(L)))
+ Keep.push_back(L);
+ }
+ if (Keep.empty())
+ return nullptr;
+ return DILayerLocList::get(C, Keep);
+}
+
DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) {
if (LocA == LocB)
return LocA;
@@ -304,13 +393,19 @@ DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) {
if (L1 == L2)
return DILocation::get(C, L1->getLine(), L1->getColumn(), L1->getScope(),
InlinedAt, L1->isImplicitCode(),
- L1->getAtomGroup(), L1->getAtomRank());
+ L1->getAtomGroup(), L1->getAtomRank(),
+ L1->getRawIRLayers());
// If the locations originate from different subprograms we can't produce
// a common location.
if (L1->getScope()->getSubprogram() != L2->getScope()->getSubprogram())
return nullptr;
+ // Each merged frame keeps the intersection of the two frames'
+ // intermediate-IR layer sets, so a layer on any frame (e.g. the shared
+ // outermost kernel frame) survives the merge.
+ Metadata *MergedLayers = mergeIRLayers(C, L1, L2);
+
// Find nearest common scope inside subprogram.
DIScope *Scope = getNearestMatchingScope<EqualScopesMatcher>(L1, L2).first;
assert(Scope && "No common scope in the same subprogram?");
@@ -333,7 +428,9 @@ DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) {
// from CommonLoc. Use it as merged location.
if (Scope->getFile() != L1->getFile() || L1->getFile() != L2->getFile())
return DILocation::get(C, CommonLoc.first, CommonLoc.second,
- CommonLocScope, InlinedAt);
+ CommonLocScope, InlinedAt,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0,
+ /*AtomRank=*/0, MergedLayers);
}
bool SameLine = L1->getLine() == L2->getLine();
@@ -346,7 +443,7 @@ DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) {
// further to do if neither location has an atom number.
if (!SameLine || !(L1->getAtomGroup() || L2->getAtomGroup()))
return DILocation::get(C, Line, Col, Scope, InlinedAt, IsImplicitCode,
- /*AtomGroup*/ 0, /*AtomRank*/ 0);
+ /*AtomGroup*/ 0, /*AtomRank*/ 0, MergedLayers);
uint64_t Group = 0;
uint64_t Rank = 0;
@@ -380,7 +477,7 @@ DILocation *DILocation::getMergedLocation(DILocation *LocA, DILocation *LocB) {
Rank = 1;
}
return DILocation::get(C, Line, Col, Scope, InlinedAt, IsImplicitCode,
- Group, Rank);
+ Group, Rank, MergedLayers);
};
DILocation *Result = ARIt != ALocs.rend() ? (*ARIt)->getInlinedAt() : nullptr;
diff --git a/llvm/lib/IR/DebugLoc.cpp b/llvm/lib/IR/DebugLoc.cpp
index 16a82c2a05208..87bbb91acb97f 100644
--- a/llvm/lib/IR/DebugLoc.cpp
+++ b/llvm/lib/IR/DebugLoc.cpp
@@ -60,6 +60,11 @@ DILocation *DebugLoc::getInlinedAt() const {
return get()->getInlinedAt();
}
+MDNode *DebugLoc::getRawIRLayers() const {
+ DILocation *L = get();
+ return L ? cast_if_present<MDNode>(L->getRawIRLayers()) : nullptr;
+}
+
MDNode *DebugLoc::getInlinedAtScope() const {
return cast<DILocation>(Loc)->getInlinedAtScope();
}
@@ -149,9 +154,14 @@ DebugLoc DebugLoc::appendInlinedAt(const DebugLoc &DL, DILocation *InlinedAt,
// location (then rebuilding the rest of the chain behind it) and update the
// map of already-constructed inlined-at nodes.
// Key Instructions: InlinedAt fields don't need atom info.
+ // Preserve each frame's irlayers -- the intermediate-IR snapshot can
+ // live on any frame (the one outermost at snapshot time), so the chain
+ // rebuild must not drop it.
for (const DILocation *MD : reverse(InlinedAtLocations))
Cache[MD] = Last = DILocation::getDistinct(
- Ctx, MD->getLine(), MD->getColumn(), MD->getScope(), Last);
+ Ctx, MD->getLine(), MD->getColumn(), MD->getScope(), Last,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0,
+ MD->getRawIRLayers());
return Last;
}
diff --git a/llvm/lib/IR/LLVMContextImpl.h b/llvm/lib/IR/LLVMContextImpl.h
index 41c8a92c56eda..b7736dd84a714 100644
--- a/llvm/lib/IR/LLVMContextImpl.h
+++ b/llvm/lib/IR/LLVMContextImpl.h
@@ -277,7 +277,8 @@ template <> struct MDNodeKeyImpl<MDTuple> : MDNodeOpsKey {
template <> struct MDNodeKeyImpl<DILocation> {
Metadata *Scope;
Metadata *InlinedAt;
- uint64_t AtomGroup : 61;
+ Metadata *IRLayers;
+ uint64_t AtomGroup : 60;
uint64_t AtomRank : 3;
unsigned Line;
uint16_t Column;
@@ -285,22 +286,23 @@ template <> struct MDNodeKeyImpl<DILocation> {
MDNodeKeyImpl(unsigned Line, uint16_t Column, Metadata *Scope,
Metadata *InlinedAt, bool ImplicitCode, uint64_t AtomGroup,
- uint8_t AtomRank)
- : Scope(Scope), InlinedAt(InlinedAt), AtomGroup(AtomGroup),
- AtomRank(AtomRank), Line(Line), Column(Column),
+ uint8_t AtomRank, Metadata *IRLayers)
+ : Scope(Scope), InlinedAt(InlinedAt), IRLayers(IRLayers),
+ AtomGroup(AtomGroup), AtomRank(AtomRank), Line(Line), Column(Column),
ImplicitCode(ImplicitCode) {}
MDNodeKeyImpl(const DILocation *L)
: Scope(L->getRawScope()), InlinedAt(L->getRawInlinedAt()),
- AtomGroup(L->getAtomGroup()), AtomRank(L->getAtomRank()),
- Line(L->getLine()), Column(L->getColumn()),
+ IRLayers(L->getRawIRLayers()), AtomGroup(L->getAtomGroup()),
+ AtomRank(L->getAtomRank()), Line(L->getLine()), Column(L->getColumn()),
ImplicitCode(L->isImplicitCode()) {}
bool isKeyOf(const DILocation *RHS) const {
return Line == RHS->getLine() && Column == RHS->getColumn() &&
Scope == RHS->getRawScope() && InlinedAt == RHS->getRawInlinedAt() &&
ImplicitCode == RHS->isImplicitCode() &&
- AtomGroup == RHS->getAtomGroup() && AtomRank == RHS->getAtomRank();
+ AtomGroup == RHS->getAtomGroup() && AtomRank == RHS->getAtomRank() &&
+ IRLayers == RHS->getRawIRLayers();
}
unsigned getHashValue() const {
@@ -313,13 +315,59 @@ template <> struct MDNodeKeyImpl<DILocation> {
// messing with the hash distribution* appear to still be massively
// outweighed by the overall compile time savings by performing this check.
// * (hash_combine(x) != hash_combine(x, 0))
- if (AtomGroup || AtomRank)
+ // irlayers is likewise rare, so it is only mixed in when present to keep
+ // the common no-layers hashes unchanged.
+ if (AtomGroup || AtomRank) {
+ if (IRLayers)
+ return hash_combine(LineColumnAndImplicitCode, Scope, InlinedAt,
+ AtomGroup | (uint64_t(AtomRank) << 61), IRLayers);
return hash_combine(LineColumnAndImplicitCode, Scope, InlinedAt,
AtomGroup | (uint64_t(AtomRank) << 61));
+ }
+ if (IRLayers)
+ return hash_combine(LineColumnAndImplicitCode, Scope, InlinedAt,
+ IRLayers);
return hash_combine(LineColumnAndImplicitCode, Scope, InlinedAt);
}
};
+/// DenseMapInfo for DILayerLoc.
+template <> struct MDNodeKeyImpl<DILayerLoc> {
+ Metadata *Kind;
+ Metadata *File;
+ unsigned Line;
+ uint16_t Column;
+
+ MDNodeKeyImpl(Metadata *Kind, Metadata *File, unsigned Line, uint16_t Column)
+ : Kind(Kind), File(File), Line(Line), Column(Column) {}
+ MDNodeKeyImpl(const DILayerLoc *N)
+ : Kind(N->getRawKind()), File(N->getRawFile()), Line(N->getLine()),
+ Column(N->getColumn()) {}
+
+ bool isKeyOf(const DILayerLoc *RHS) const {
+ return Kind == RHS->getRawKind() && File == RHS->getRawFile() &&
+ Line == RHS->getLine() && Column == RHS->getColumn();
+ }
+
+ unsigned getHashValue() const {
+ return hash_combine(Kind, File, Line, Column);
+ }
+};
+
+/// DenseMapInfo for DILayerLocList.
+template <> struct MDNodeKeyImpl<DILayerLocList> : MDNodeOpsKey {
+ MDNodeKeyImpl(ArrayRef<Metadata *> Ops) : MDNodeOpsKey(Ops) {}
+ MDNodeKeyImpl(const DILayerLocList *N) : MDNodeOpsKey(N) {}
+
+ bool isKeyOf(const DILayerLocList *RHS) const { return compareOps(RHS); }
+
+ unsigned getHashValue() const { return getHash(); }
+
+ static unsigned calculateHash(DILayerLocList *N) {
+ return MDNodeOpsKey::calculateHash(N);
+ }
+};
+
/// DenseMapInfo for GenericDINode.
template <> struct MDNodeKeyImpl<GenericDINode> : MDNodeOpsKey {
unsigned Tag;
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index b813069d035f6..32e501bb6064f 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -1089,6 +1089,22 @@ void Verifier::visitDILocation(const DILocation &N) {
CheckDI(isa<DILocation>(IA), "inlined-at should be a location", &N, IA);
if (auto *SP = dyn_cast<DISubprogram>(N.getRawScope()))
CheckDI(SP->isDefinition(), "scope points into the type hierarchy", &N);
+ if (auto *L = N.getRawIRLayers())
+ CheckDI(isa<DILayerLocList>(L), "irlayers must be a DILayerLocList", &N, L);
+}
+
+void Verifier::visitDILayerLoc(const DILayerLoc &N) {
+ CheckDI(isa_and_nonnull<MDString>(N.getRawKind()),
+ "layer kind must be a non-null MDString", &N, N.getRawKind());
+ CheckDI(isa_and_nonnull<DIFile>(N.getRawFile()),
+ "layer file must be a non-null DIFile", &N, N.getRawFile());
+}
+
+void Verifier::visitDILayerLocList(const DILayerLocList &N) {
+ CheckDI(N.getNumLayers() > 0, "DILayerLocList must be non-empty", &N);
+ for (const MDOperand &Op : N.layers())
+ CheckDI(isa_and_nonnull<DILayerLoc>(Op.get()),
+ "DILayerLocList entry must be a DILayerLoc", &N, Op.get());
}
void Verifier::visitGenericDINode(const GenericDINode &N) {
diff --git a/llvm/lib/Target/DirectX/DXILWriter/DXILBitcodeWriter.cpp b/llvm/lib/Target/DirectX/DXILWriter/DXILBitcodeWriter.cpp
index 6f14f70014b01..6056414a61018 100644
--- a/llvm/lib/Target/DirectX/DXILWriter/DXILBitcodeWriter.cpp
+++ b/llvm/lib/Target/DirectX/DXILWriter/DXILBitcodeWriter.cpp
@@ -299,6 +299,14 @@ class DXILBitcodeWriter {
// See https://github.com/llvm/llvm-project/issues/58989
llvm_unreachable("DXIL cannot contain DIAssignID Nodes");
}
+ void writeDILayerLoc(const DILayerLoc *N, SmallVectorImpl<uint64_t> &Record,
+ unsigned Abbrev) {
+ llvm_unreachable("DXIL cannot contain DILayerLoc Nodes");
+ }
+ void writeDILayerLocList(const DILayerLocList *N,
+ SmallVectorImpl<uint64_t> &Record, unsigned Abbrev) {
+ llvm_unreachable("DXIL cannot contain DILayerLocList Nodes");
+ }
void writeDIModule(const DIModule *N, SmallVectorImpl<uint64_t> &Record,
unsigned Abbrev);
void writeDITemplateTypeParameter(const DITemplateTypeParameter *N,
diff --git a/llvm/test/Bitcode/intermediate-loc-metadata-location.ll b/llvm/test/Bitcode/intermediate-loc-metadata-location.ll
new file mode 100644
index 0000000000000..89d16e12e5875
--- /dev/null
+++ b/llvm/test/Bitcode/intermediate-loc-metadata-location.ll
@@ -0,0 +1,58 @@
+; RUN: llvm-as < %s | llvm-dis | FileCheck %s
+;;
+;; Checks that irlayers survive a bitcode round-trip on a DILocation written as
+;; a METADATA_LOCATION record -- one reachable only as an inlinedAt target, with
+;; a layer list built through forward references.
+
+define dso_local void @test_kernel(ptr noundef %v) #0 !dbg !8 {
+entry:
+ store ptr %v, ptr %v, align 8, !dbg !20
+ store ptr %v, ptr %v, align 8, !dbg !23
+ ret void, !dbg !21
+}
+
+attributes #0 = { noinline optnone }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "test.cu", directory: "/test")
+!2 = !{i32 7, !"Dwarf Version", i32 2}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!8 = distinct !DISubprogram(name: "test_kernel", scope: !1, file: !1, line: 1, type: !9, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DISubroutineType(types: !10)
+!10 = !{null}
+!11 = distinct !DISubprogram(name: "helper", scope: !1, file: !1, line: 20, type: !9, scopeLine: 20, spFlags: DISPFlagDefinition, unit: !0)
+
+!14 = !DIFile(filename: "kernel.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", source: "tile ir text")
+!15 = !DIFile(filename: "kernel.gpuir", directory: ".", checksumkind: CSK_MD5, checksum: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", source: "gpu ir text")
+
+;; !18 precedes its entries, so they parse as forward references and the list is
+;; re-uniqued once they resolve. !22 names the same entries after they resolve,
+;; so it must land on !18's node -- a stale cached hash would miss the uniquing
+;; lookup and produce a second, identical list.
+!18 = !DILayerLocList(!16, !17)
+!16 = !DILayerLoc(line: 100, column: 1, file: !14, kind: "tile ir")
+!17 = !DILayerLoc(line: 7, column: 3, file: !15, kind: "gpu ir")
+!22 = !DILayerLocList(!16, !17)
+
+!19 = distinct !DILocation(line: 30, column: 1, scope: !8, irlayers: !18)
+!24 = distinct !DILocation(line: 40, column: 1, scope: !8, irlayers: !22)
+!20 = !DILocation(line: 21, column: 5, scope: !11, inlinedAt: !19)
+!21 = !DILocation(line: 22, column: 1, scope: !11, inlinedAt: !19)
+!23 = !DILocation(line: 23, column: 5, scope: !11, inlinedAt: !24)
+
+;; The metadata-block location keeps its layers, and the two entries keep their
+;; order.
+; CHECK-DAG: ![[IA:[0-9]+]] = distinct !DILocation(line: 30, column: 1, scope: !{{[0-9]+}}, irlayers: ![[LIST:[0-9]+]])
+; CHECK-DAG: ![[LIST]] = !DILayerLocList(![[TILE:[0-9]+]], ![[GPU:[0-9]+]])
+; CHECK-DAG: ![[TILE]] = !DILayerLoc(line: 100, column: 1, file: !{{[0-9]+}}, kind: "tile ir")
+; CHECK-DAG: ![[GPU]] = !DILayerLoc(line: 7, column: 3, file: !{{[0-9]+}}, kind: "gpu ir")
+
+;; Both layered locations reference that one list node.
+; CHECK-DAG: = distinct !DILocation(line: 40, column: 1, scope: !{{[0-9]+}}, irlayers: ![[LIST]])
+
+;; The instruction locations reference them as inlinedAt and carry no layers of
+;; their own.
+; CHECK-DAG: !DILocation(line: 21, column: 5, scope: !{{[0-9]+}}, inlinedAt: ![[IA]])
diff --git a/llvm/test/Bitcode/intermediate-loc-roundtrip.ll b/llvm/test/Bitcode/intermediate-loc-roundtrip.ll
new file mode 100644
index 0000000000000..89e060e3efbf1
--- /dev/null
+++ b/llvm/test/Bitcode/intermediate-loc-roundtrip.ll
@@ -0,0 +1,57 @@
+; RUN: llvm-as < %s | llvm-dis | FileCheck %s
+; RUN: verify-uselistorder %s
+
+;; Test that intermediate location metadata (used for multi-level line info)
+;; round-trips correctly through bitcode. The layers live on the
+;; DILocation's typed `irlayers` operand (a DILayerLocList of DILayerLoc
+;; entries), so the whole DILocation/DILayerLocList/DILayerLoc/DIFile chain must
+;; survive .ll -> .bc -> .ll.
+
+define dso_local void @test_kernel(ptr noundef %v) !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ store ptr %v, ptr %v.addr, align 8, !dbg !20
+ %0 = load ptr, ptr %v.addr, align 8, !dbg !21
+ ret void, !dbg !22
+}
+
+; CHECK-LABEL: define dso_local void @test_kernel
+; CHECK: store ptr %v, ptr %v.addr, align 8, !dbg ![[DBG1:[0-9]+]]
+; CHECK: load ptr, ptr %v.addr, align 8, !dbg ![[DBG2:[0-9]+]]
+; CHECK: ret void, !dbg ![[DBG3:[0-9]+]]
+
+;; Verify the metadata structure is preserved: each !dbg is a DILocation whose
+;; source line/col comes from the primary location and whose `irlayers` operand
+;; points at a shared (uniqued) DILayerLocList.
+
+; CHECK-DAG: ![[DBG1]] = !DILocation(line: 2, column: 5, scope: ![[SP:[0-9]+]], irlayers: ![[LIST:[0-9]+]])
+; CHECK-DAG: ![[DBG2]] = !DILocation(line: 3, column: 5, scope: ![[SP]], irlayers: ![[LIST]])
+; CHECK-DAG: ![[DBG3]] = !DILocation(line: 4, column: 1, scope: ![[SP]], irlayers: ![[LIST]])
+
+;; The shared layer list holds one DILayerLoc with the kind string and the
+;; intermediate coordinate.
+; CHECK-DAG: ![[LIST]] = !DILayerLocList(![[LAYER:[0-9]+]])
+; CHECK-DAG: ![[LAYER]] = !DILayerLoc(line: 100, column: 10, file: ![[INTFILE:[0-9]+]], kind: "TileIR")
+; CHECK-DAG: ![[INTFILE]] = !DIFile(filename: "intermediate.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "ffffffffffffffffffffffffffffffff")
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "test.cu", directory: "/test")
+!2 = !{i32 7, !"Dwarf Version", i32 2}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!8 = distinct !DISubprogram(name: "test_kernel", scope: !1, file: !1, line: 1, type: !9, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DISubroutineType(types: !10)
+!10 = !{null}
+
+;; Intermediate-IR layer: one DILayerLoc, shared (uniqued) across all layered
+;; instructions via a single DILayerLocList.
+!14 = !DIFile(filename: "intermediate.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "ffffffffffffffffffffffffffffffff")
+!30 = !DILayerLocList(!31)
+!31 = !DILayerLoc(line: 100, column: 10, file: !14, kind: "TileIR")
+
+;; Layered instruction locations: primary source loc + shared irlayers.
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !30)
+!21 = !DILocation(line: 3, column: 5, scope: !8, irlayers: !30)
+!22 = !DILocation(line: 4, column: 1, scope: !8, irlayers: !30)
diff --git a/llvm/test/CodeGen/MIR/X86/instructions-debug-location-irlayers.mir b/llvm/test/CodeGen/MIR/X86/instructions-debug-location-irlayers.mir
new file mode 100644
index 0000000000000..093e9051601b7
--- /dev/null
+++ b/llvm/test/CodeGen/MIR/X86/instructions-debug-location-irlayers.mir
@@ -0,0 +1,64 @@
+# RUN: llc -mtriple=x86_64 -run-pass none -o - %s | FileCheck %s
+# RUN: llc -mtriple=x86_64 -run-pass none -o - %s \
+# RUN: | llc -x mir -mtriple=x86_64 -run-pass none -o - | FileCheck %s
+## Check that the MIR parser accepts `irlayers` on a DILocation and that the
+## printer emits it again. The second RUN line feeds the printed MIR back through
+## llc, so a layer list that prints as something unparsable fails rather than
+## passing quietly. The first location carries irlayers without an inlinedAt, the
+## second carries both.
+
+# CHECK-DAG: ![[LIST:[0-9]+]] = !DILayerLocList(![[LAYER:[0-9]+]])
+# CHECK-DAG: ![[LAYER]] = !DILayerLoc(line: 42, column: 5, file: !{{[0-9]+}}, kind: "tile ir")
+# CHECK-LABEL: name: test
+# CHECK: $eax = COPY %0, debug-location !DILocation(line: 1, scope: !{{[0-9]+}}, irlayers: ![[LIST]])
+# CHECK: RET64 $eax, debug-location !DILocation(line: 2, scope: !{{[0-9]+}}, inlinedAt: !DILocation(line: 1, scope: !{{[0-9]+}}), irlayers: ![[LIST]])
+
+--- |
+
+ define i32 @test(i32 %x) #0 !dbg !4 {
+ entry:
+ %x.addr = alloca i32, align 4
+ store i32 %x, ptr %x.addr, align 4
+ %0 = load i32, ptr %x.addr, align 4, !dbg !15
+ ret i32 %0, !dbg !14
+ }
+
+ attributes #0 = { nounwind "frame-pointer"="none" }
+
+ !llvm.dbg.cu = !{!0}
+ !llvm.module.flags = !{!9, !10}
+
+ !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, retainedTypes: !2, globals: !2, imports: !2)
+ !1 = !DIFile(filename: "test.ll", directory: "")
+ !2 = !{}
+ !4 = distinct !DISubprogram(name: "test", scope: !5, file: !5, line: 4, type: !6, isDefinition: true, scopeLine: 4, flags: DIFlagPrototyped, isOptimized: false, unit: !0, retainedNodes: !2)
+ !5 = !DIFile(filename: "test.c", directory: "")
+ !6 = !DISubroutineType(types: !7)
+ !7 = !{!8, !8}
+ !8 = !DIBasicType(name: "int", size: 32, align: 32, encoding: DW_ATE_signed)
+ !9 = !{i32 2, !"Dwarf Version", i32 4}
+ !10 = !{i32 2, !"Debug Info Version", i32 3}
+ !14 = !DILocation(line: 8, scope: !4)
+ !15 = !DILocation(line: 9, scope: !4, irlayers: !22)
+ !20 = !DIFile(filename: "kernel.tileir", directory: ".")
+ !21 = !DILayerLoc(line: 42, column: 5, file: !20, kind: "tile ir")
+ !22 = !DILayerLocList(!21)
+
+...
+---
+name: test
+tracksRegLiveness: true
+registers:
+ - { id: 0, class: gr32 }
+frameInfo:
+ maxAlignment: 4
+stack:
+ - { id: 0, name: x.addr, size: 4, alignment: 4 }
+body: |
+ bb.0.entry:
+ liveins: $edi
+
+ %0 = COPY $edi
+ $eax = COPY %0, debug-location !DILocation(line: 1, scope: !4, irlayers: !22)
+ RET64 $eax, debug-location !DILocation(line: 2, scope: !4, inlinedAt: !DILocation(line: 1, scope: !4), irlayers: !22)
+...
diff --git a/llvm/test/Verifier/intermediate-layerloc.ll b/llvm/test/Verifier/intermediate-layerloc.ll
new file mode 100644
index 0000000000000..c5409ffc168a7
--- /dev/null
+++ b/llvm/test/Verifier/intermediate-layerloc.ll
@@ -0,0 +1,37 @@
+; RUN: llvm-as -disable-output %s -o - 2>&1 | FileCheck --allow-empty %s
+;;
+;; Verification of DILayerLoc is structural only, so a checksum-less intermediate
+;; file verifies clean -- with or without source.
+
+; CHECK-NOT: requires a checksum
+; CHECK-NOT: ignoring invalid debug info
+
+define void @k(ptr %p) !dbg !5 {
+ store ptr null, ptr %p, align 8, !dbg !20
+ store ptr null, ptr %p, align 8, !dbg !21
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, emissionKind: FullDebug)
+!1 = !DIFile(filename: "test.cu", directory: "/test")
+!2 = !{i32 7, !"Dwarf Version", i32 2}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !{null})
+!5 = distinct !DISubprogram(name: "k", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+
+;; No checksum, no source.
+!10 = !DIFile(filename: "kernel.tileir", directory: ".")
+!11 = !DILayerLoc(line: 42, column: 5, file: !10, kind: "tile ir")
+!12 = !DILayerLocList(!11)
+
+;; Source but still no checksum -- the case NVPTX would otherwise use a digest
+;; for, and the one that used to be rejected outright.
+!13 = !DIFile(filename: "kernel.gpuir", directory: ".", source: "gpu ir source")
+!14 = !DILayerLoc(line: 7, column: 1, file: !13, kind: "gpu ir")
+!15 = !DILayerLocList(!14)
+
+!20 = !DILocation(line: 2, column: 5, scope: !5, irlayers: !12)
+!21 = !DILocation(line: 3, column: 5, scope: !5, irlayers: !15)
diff --git a/llvm/unittests/IR/DebugInfoTest.cpp b/llvm/unittests/IR/DebugInfoTest.cpp
index 4004ab4b080b2..7e23d38f9ad71 100644
--- a/llvm/unittests/IR/DebugInfoTest.cpp
+++ b/llvm/unittests/IR/DebugInfoTest.cpp
@@ -12,6 +12,7 @@
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DebugInfoMetadata.h"
+#include "llvm/IR/DebugLoc.h"
#include "llvm/IR/DebugProgramInstruction.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/IntrinsicInst.h"
@@ -1511,4 +1512,405 @@ TEST(DIBuilder, DynamicOffsetAndSize) {
EXPECT_EQ(Field->getRawSizeInBits(), Len);
}
+// Tests for DebugLoc with intermediate location support.
+
+TEST(DebugLocTest, IntermediateLocBasics) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *F = DIB.createFile("source.cu", "/");
+ DIFile *IntF = DIB.createFile("intermediate.mlir", "/");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, F, "test", false, "", 0);
+ DISubprogram *SP =
+ DIB.createFunction(CU, "foo", "", F, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ // Build a layered DILocation: a source coordinate plus one TileIR layer that
+ // hangs off the DILocation's typed `irlayers` operand.
+ MDString *Kind = MDString::get(Ctx, "TileIR");
+ DILayerLoc *Layer = DILayerLoc::get(Ctx, Kind, IntF, 100, 1);
+ DILayerLocList *Layers = DILayerLocList::get(Ctx, {Layer});
+ DILocation *Loc =
+ DILocation::get(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0,
+ /*IRLayers=*/Layers);
+
+ DebugLoc DL(Loc);
+ EXPECT_TRUE((bool)DL);
+ EXPECT_EQ(DL.get(), Loc);
+ EXPECT_EQ(DL.getLine(), 10u);
+ EXPECT_EQ(DL.getCol(), 5u);
+
+ // The layer list is reachable through both DebugLoc and DILocation.
+ EXPECT_EQ(DL.getRawIRLayers(), Layers);
+ ASSERT_EQ(Loc->getIRLayers(), Layers);
+ ASSERT_EQ(Loc->getNumLayers(), 1u);
+ DILayerLoc *L0 = Loc->getLayer(0);
+ ASSERT_NE(L0, nullptr);
+ EXPECT_EQ(L0->getKind(), "TileIR");
+ EXPECT_EQ(L0->getFile(), IntF);
+ EXPECT_EQ(L0->getLine(), 100u);
+ EXPECT_EQ(L0->getColumn(), 1u);
+}
+
+TEST(DebugLocTest, IntermediateLocWithAndWithout) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *F = DIB.createFile("source.cu", "/");
+ DIFile *IntF = DIB.createFile("intermediate.mlir", "/");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, F, "test", false, "", 0);
+ DISubprogram *SP =
+ DIB.createFunction(CU, "foo", "", F, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ // A source-only DILocation has no layers.
+ DILocation *SourceLoc = DILocation::get(Ctx, 10, 5, SP);
+ DebugLoc DLSourceOnly(SourceLoc);
+ EXPECT_EQ(SourceLoc->getRawIRLayers(), nullptr);
+ EXPECT_EQ(SourceLoc->getIRLayers(), nullptr);
+ EXPECT_EQ(SourceLoc->getNumLayers(), 0u);
+ EXPECT_EQ(DLSourceOnly.getRawIRLayers(), nullptr);
+ EXPECT_EQ(DLSourceOnly.get(), SourceLoc);
+
+ // A layered DILocation returns its list.
+ MDString *Kind = MDString::get(Ctx, "TileIR");
+ DILayerLoc *Layer = DILayerLoc::get(Ctx, Kind, IntF, 100, 1);
+ DILayerLocList *Layers = DILayerLocList::get(Ctx, {Layer});
+ DILocation *LayeredLoc =
+ DILocation::get(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false,
+ /*AtomGroup=*/0, /*AtomRank=*/0,
+ /*IRLayers=*/Layers);
+ DebugLoc DLWithInt(LayeredLoc);
+ EXPECT_EQ(DLWithInt.getRawIRLayers(), Layers);
+ EXPECT_EQ(LayeredLoc->getIRLayers(), Layers);
+ ASSERT_EQ(LayeredLoc->getNumLayers(), 1u);
+ EXPECT_EQ(LayeredLoc->getLayer(0)->getKind(), "TileIR");
+}
+
+TEST(DebugLocTest, IntermediateLocEquality) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *F = DIB.createFile("source.cu", "/");
+ DIFile *IntF = DIB.createFile("intermediate.mlir", "/");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, F, "test", false, "", 0);
+ DISubprogram *SP =
+ DIB.createFunction(CU, "foo", "", F, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ MDString *Kind = MDString::get(Ctx, "TileIR");
+ DILayerLoc *LayerA = DILayerLoc::get(Ctx, Kind, IntF, 100, 1);
+ // Differs from LayerA in a single field (column only) -- a minimal structural
+ // change must still uniquify to a distinct node, so DL1 and DL3 differ.
+ DILayerLoc *LayerB = DILayerLoc::get(Ctx, Kind, IntF, 100, 2);
+ DILayerLocList *ListA = DILayerLocList::get(Ctx, {LayerA});
+ // A structurally-identical list uniques to the same node.
+ DILayerLocList *ListA2 = DILayerLocList::get(Ctx, {LayerA});
+ DILayerLocList *ListB = DILayerLocList::get(Ctx, {LayerB});
+ EXPECT_EQ(ListA, ListA2);
+ EXPECT_NE(ListA, ListB);
+
+ auto Layered = [&](DILayerLocList *L) {
+ return DILocation::get(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0,
+ /*AtomRank=*/0, /*IRLayers=*/L);
+ };
+ DebugLoc DL1(Layered(ListA));
+ // A *distinct* location with the same fields and the same (uniqued) layer
+ // list, so isSameSourceLocation cannot short-circuit on pointer identity and
+ // must actually run the structural getRawIRLayers() comparison.
+ DebugLoc DL2(DILocation::getDistinct(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0,
+ /*AtomRank=*/0, /*IRLayers=*/ListA));
+ DebugLoc DL3(Layered(ListB));
+ DebugLoc DL4(DILocation::get(Ctx, 10, 5, SP)); // no layers
+
+ ASSERT_NE(DL1.get(), DL2.get());
+ // Distinct nodes with same line/col/scope and identical (uniqued) layers ->
+ // same source location (exercises the layer-aware structural compare).
+ EXPECT_TRUE(DL1.isSameSourceLocation(DL2));
+ // Same line/col/scope but different layers -> NOT the same source location.
+ EXPECT_FALSE(DL1.isSameSourceLocation(DL3));
+ // One has layers, the other doesn't -> NOT the same source location.
+ EXPECT_FALSE(DL1.isSameSourceLocation(DL4));
+}
+
+TEST(DebugLocTest, MergedLocationWithIntermediate) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *F = DIB.createFile("source.cu", "/");
+ DIFile *IntF = DIB.createFile("intermediate.mlir", "/");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, F, "test", false, "", 0);
+ DISubprogram *SP =
+ DIB.createFunction(CU, "foo", "", F, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ MDString *Kind = MDString::get(Ctx, "TileIR");
+ // A layer shared by both locations, plus a distinct layer on each so the two
+ // DILocations are different nodes.
+ DILayerLoc *Shared = DILayerLoc::get(Ctx, Kind, IntF, 100, 1);
+ DILayerLoc *ExtraA = DILayerLoc::get(Ctx, Kind, IntF, 111, 1);
+ DILayerLoc *ExtraB = DILayerLoc::get(Ctx, Kind, IntF, 222, 1);
+ DILayerLocList *List1 = DILayerLocList::get(Ctx, {Shared, ExtraA});
+ DILayerLocList *List2 = DILayerLocList::get(Ctx, {Shared, ExtraB});
+
+ auto Layered = [&](DILayerLocList *L) {
+ return DILocation::get(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0,
+ /*AtomRank=*/0, /*IRLayers=*/L);
+ };
+ DILocation *Loc1 = Layered(List1);
+ DILocation *Loc2 = Layered(List2);
+ ASSERT_NE(Loc1, Loc2);
+
+ // Merging two locations that share a DILayerLoc keeps the shared layer only.
+ DILocation *Merged = DILocation::getMergedLocation(Loc1, Loc2);
+ ASSERT_NE(Merged, nullptr);
+ EXPECT_EQ(Merged->getLine(), 10u);
+ ASSERT_NE(Merged->getIRLayers(), nullptr);
+ ASSERT_EQ(Merged->getNumLayers(), 1u);
+ EXPECT_EQ(Merged->getLayer(0), Shared);
+}
+
+TEST(DebugLocTest, MergedLocationPartialIntermediate) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *F = DIB.createFile("source.cu", "/");
+ DIFile *IntF = DIB.createFile("intermediate.mlir", "/");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, F, "test", false, "", 0);
+ DISubprogram *SP =
+ DIB.createFunction(CU, "foo", "", F, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ MDString *Kind = MDString::get(Ctx, "TileIR");
+ DILayerLoc *LayerA = DILayerLoc::get(Ctx, Kind, IntF, 100, 1);
+ DILayerLocList *LayersA = DILayerLocList::get(Ctx, {LayerA});
+ DILayerLoc *LayerB = DILayerLoc::get(Ctx, Kind, IntF, 200, 2);
+ DILayerLocList *LayersB = DILayerLocList::get(Ctx, {LayerB});
+
+ DILocation *Loc1 =
+ DILocation::get(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0,
+ /*IRLayers=*/LayersA);
+ DILocation *Loc2 = DILocation::get(Ctx, 10, 5, SP); // no layers
+ DILocation *Loc3 =
+ DILocation::get(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0,
+ /*IRLayers=*/LayersB); // disjoint layer set
+
+ // One side has no layers at all -> merged keeps none (the !LA || !LB
+ // early-out in mergeIRLayers).
+ DILocation *M12 = DILocation::getMergedLocation(Loc1, Loc2);
+ ASSERT_NE(M12, nullptr);
+ EXPECT_EQ(M12->getLine(), 10u);
+ EXPECT_EQ(M12->getRawIRLayers(), nullptr);
+
+ // Both sides have non-empty but DISJOINT layer sets -> empty intersection ->
+ // merged keeps no layers (exercises mergeIRLayers' Keep.empty() path, which
+ // must not attach an invalid empty DILayerLocList).
+ DILocation *M13 = DILocation::getMergedLocation(Loc1, Loc3);
+ ASSERT_NE(M13, nullptr);
+ EXPECT_EQ(M13->getRawIRLayers(), nullptr);
+}
+
+// Under the outermost-frame model, two instructions inlined into the same
+// kernel share the kernel (outer) frame that carries the tile-IR snapshot,
+// while their heads (inner frames) differ. Merging them must PRESERVE that
+// shared outer-frame layer (MergeLocPair threads per-frame layers), so the
+// merged instruction still resolves to the kernel's tile-IR line.
+TEST(DebugLocTest, MergedLocationOuterFrameLayerPreserved) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *SrcF = DIB.createFile("kernel.py", "/k");
+ DIFile *IntF = DIB.createFile("kernel.tileir", ".");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, SrcF, "tile", false, "", 0);
+ DISubprogram *KernelSP = DIB.createFunction(
+ CU, "kernel", "", SrcF, 10, DIB.createSubroutineType({}), 10,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+ DISubprogram *CalleeSP = DIB.createFunction(
+ CU, "helper", "", SrcF, 5, DIB.createSubroutineType({}), 5,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ MDString *Kind = MDString::get(Ctx, "tile ir");
+ DILayerLoc *Layer = DILayerLoc::get(Ctx, Kind, IntF, 100, 1);
+ DILayerLocList *KernelLayers = DILayerLocList::get(Ctx, {Layer});
+
+ // The kernel (outer) frame carries the snapshot layer.
+ DILocation *KernelFrame =
+ DILocation::get(Ctx, 50, 1, KernelSP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0,
+ /*IRLayers=*/KernelLayers);
+
+ // Two instructions inlined into that kernel frame: same callee, different
+ // head lines, no layer of their own.
+ DILocation *LocA = DILocation::get(Ctx, 10, 3, CalleeSP, KernelFrame);
+ DILocation *LocB = DILocation::get(Ctx, 11, 5, CalleeSP, KernelFrame);
+
+ DILocation *Merged = DILocation::getMergedLocation(LocA, LocB);
+ ASSERT_NE(Merged, nullptr);
+ // The merged head has no layer of its own...
+ EXPECT_EQ(Merged->getRawIRLayers(), nullptr);
+ // ...but the shared outer (kernel) frame, and its snapshot layer, survives so
+ // the emission walk still resolves to the kernel's tile-IR line.
+ DILocation *MergedOuter = Merged->getInlinedAt();
+ ASSERT_NE(MergedOuter, nullptr);
+ EXPECT_EQ(MergedOuter->getScope(), KernelSP);
+ EXPECT_EQ(MergedOuter->getIRLayers(), KernelLayers);
+}
+
+// Rebuilding a location with a new discriminator must carry `irlayers` over:
+// the discriminator says nothing about the intermediate-IR position. This is
+// the path AddDiscriminators, SampleProfile and loop unrolling rebuild through.
+TEST(DebugLocTest, CloneWithDiscriminatorPreservesLayers) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *F = DIB.createFile("source.cu", "/");
+ DIFile *IntF = DIB.createFile("intermediate.tileir", ".");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, F, "test", false, "", 0);
+ DISubprogram *SP =
+ DIB.createFunction(CU, "foo", "", F, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ DILayerLoc *Layer =
+ DILayerLoc::get(Ctx, MDString::get(Ctx, "tile ir"), IntF, 42, 5);
+ DILayerLocList *List = DILayerLocList::get(Ctx, {Layer});
+ DILocation *Loc =
+ DILocation::get(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0,
+ /*IRLayers=*/List);
+ ASSERT_EQ(Loc->getIRLayers(), List);
+
+ std::optional<const DILocation *> Cloned = Loc->cloneWithBaseDiscriminator(7);
+ ASSERT_TRUE(Cloned.has_value());
+ ASSERT_NE(*Cloned, Loc);
+ EXPECT_EQ((*Cloned)->getBaseDiscriminator(), 7u);
+ EXPECT_EQ((*Cloned)->getIRLayers(), List);
+}
+
+// `distinct !DILayerLoc` is valid IR, so two structurally identical layers can
+// be different pointers. Merging must still recognize them as the same layer.
+TEST(DebugLocTest, MergedLocationDistinctLayersCompareStructurally) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *F = DIB.createFile("source.cu", "/");
+ DIFile *IntF = DIB.createFile("intermediate.tileir", ".");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, F, "test", false, "", 0);
+ DISubprogram *SP =
+ DIB.createFunction(CU, "foo", "", F, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ MDString *Kind = MDString::get(Ctx, "tile ir");
+ // Same fields, but each side holds its own `distinct` node, so the two shared
+ // layers are unequal pointers.
+ DILayerLoc *SharedA = DILayerLoc::getDistinct(Ctx, Kind, IntF, 100, 1);
+ DILayerLoc *SharedB = DILayerLoc::getDistinct(Ctx, Kind, IntF, 100, 1);
+ ASSERT_NE(SharedA, SharedB);
+ DILayerLoc *ExtraA = DILayerLoc::get(Ctx, Kind, IntF, 111, 1);
+ DILayerLoc *ExtraB = DILayerLoc::get(Ctx, Kind, IntF, 222, 1);
+
+ auto Layered = [&](ArrayRef<Metadata *> Layers) {
+ return DILocation::get(Ctx, 10, 5, SP, /*InlinedAt=*/nullptr,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0,
+ /*AtomRank=*/0,
+ /*IRLayers=*/DILayerLocList::get(Ctx, Layers));
+ };
+ DILocation *Loc1 = Layered({SharedA, ExtraA});
+ DILocation *Loc2 = Layered({SharedB, ExtraB});
+
+ DILocation *Merged = DILocation::getMergedLocation(Loc1, Loc2);
+ ASSERT_NE(Merged, nullptr);
+ // The structurally equal distinct layer survives; the divergent ones do not.
+ ASSERT_NE(Merged->getIRLayers(), nullptr);
+ ASSERT_EQ(Merged->getNumLayers(), 1u);
+ EXPECT_EQ(Merged->getLayer(0), SharedA);
+}
+
+// The layer nodes behind `irlayers` are uniqued MDNodes: structurally
+// identical DILayerLoc / DILayerLocList values map to the same pointer.
+TEST(DebugLocTest, IntermediateLocLayerUniquing) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *IntF = DIB.createFile("intermediate.mlir", "/");
+ MDString *Kind = MDString::get(Ctx, "TileIR");
+
+ // Two structurally-identical DILayerLoc::get calls return the same node.
+ DILayerLoc *L1 = DILayerLoc::get(Ctx, Kind, IntF, 100, 5);
+ DILayerLoc *L2 = DILayerLoc::get(Ctx, Kind, IntF, 100, 5);
+ EXPECT_EQ(L1, L2);
+
+ // Any differing field yields a distinct node.
+ DILayerLoc *L3 = DILayerLoc::get(Ctx, Kind, IntF, 101, 5);
+ EXPECT_NE(L1, L3);
+
+ // DILayerLocList uniques on its operands as well.
+ DILayerLocList *List1 = DILayerLocList::get(Ctx, {L1});
+ DILayerLocList *List2 = DILayerLocList::get(Ctx, {L2}); // {L2} == {L1}
+ EXPECT_EQ(List1, List2);
+
+ DILayerLocList *List3 = DILayerLocList::get(Ctx, {L1, L3});
+ EXPECT_NE(List1, List3);
+}
+
+TEST(DebugLocTest, PrintIntermediateLocWithInlinedAt) {
+ LLVMContext Ctx;
+ auto M = std::make_unique<Module>("MyModule", Ctx);
+ DIBuilder DIB(*M);
+ DIFile *SrcF = DIB.createFile("caller.py", "/src");
+ DIFile *CalleeF = DIB.createFile("callee.py", "/src");
+ DIFile *IntF = DIB.createFile("callee.tileir", "/ir");
+ DICompileUnit *CU =
+ DIB.createCompileUnit(dwarf::DW_LANG_C, SrcF, "test", false, "", 0);
+ DISubprogram *CallerSP = DIB.createFunction(
+ CU, "caller", "", SrcF, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+ DISubprogram *CalleeSP = DIB.createFunction(
+ CU, "callee", "", CalleeF, 1, DIB.createSubroutineType({}), 1,
+ DINode::FlagZero, DISubprogram::SPFlagDefinition);
+
+ // A DILocation carrying BOTH an inlinedAt chain and an irlayers operand.
+ DILocation *CallSiteLoc = DILocation::get(Ctx, 50, 1, CallerSP);
+ MDString *Kind = MDString::get(Ctx, "TileIR");
+ DILayerLoc *Layer = DILayerLoc::get(Ctx, Kind, IntF, 100, 5);
+ DILayerLocList *Layers = DILayerLocList::get(Ctx, {Layer});
+ DILocation *Loc =
+ DILocation::get(Ctx, 10, 3, CalleeSP, CallSiteLoc,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0,
+ /*IRLayers=*/Layers);
+
+ // Print the DILocation node itself so the typed operands are rendered.
+ std::string Result;
+ raw_string_ostream OS(Result);
+ Loc->print(OS, M.get());
+
+ // The printed node names both trailing operands.
+ EXPECT_NE(Result.find("!DILocation("), std::string::npos) << Result;
+ EXPECT_NE(Result.find("inlinedAt:"), std::string::npos) << Result;
+ EXPECT_NE(Result.find("irlayers:"), std::string::npos) << Result;
+
+ // The human-readable DebugLoc form still prints the source coordinate and its
+ // inlinedAt chain.
+ std::string DLResult;
+ raw_string_ostream DLOS(DLResult);
+ DebugLoc(Loc).print(DLOS);
+ EXPECT_NE(DLResult.find("callee.py:10:3"), std::string::npos) << DLResult;
+ EXPECT_NE(DLResult.find("@["), std::string::npos) << DLResult;
+ EXPECT_NE(DLResult.find("caller.py:50:1"), std::string::npos) << DLResult;
+}
+
} // end namespace
>From 02ce5018f011359656a2d142c4bba70ad5b7b880 Mon Sep 17 00:00:00 2001
From: Alexander Yermolovich <ayermolovich at nvidia.com>
Date: Wed, 12 Aug 2026 18:57:06 +0000
Subject: [PATCH 2/4] [DebugInfo] Keep irlayers when outlining rebuilds a
DILocation chain
DebugLoc::replaceInlinedAtSubprogram rebuilds a location's terminal frame and
each of its inlined-at frames so their scopes point at the outlined function.
Neither rebuild forwarded the new `irlayers` operand, so CodeExtractor -- and
through it hot/cold splitting, partial inlining and loop extraction -- silently
dropped a valid intermediate mapping.
---
llvm/lib/IR/DebugLoc.cpp | 17 +++--
.../transfer-debug-info-irlayers.ll | 74 +++++++++++++++++++
2 files changed, 85 insertions(+), 6 deletions(-)
create mode 100644 llvm/test/Transforms/HotColdSplit/transfer-debug-info-irlayers.ll
diff --git a/llvm/lib/IR/DebugLoc.cpp b/llvm/lib/IR/DebugLoc.cpp
index 87bbb91acb97f..41f60766e09bf 100644
--- a/llvm/lib/IR/DebugLoc.cpp
+++ b/llvm/lib/IR/DebugLoc.cpp
@@ -114,17 +114,22 @@ DebugLoc DebugLoc::replaceInlinedAtSubprogram(
DILocation *LocToUpdate = LocChain.pop_back_val();
DIScope *NewScope = DILocalScope::cloneScopeForSubprogram(
*LocToUpdate->getScope(), NewSP, Ctx, Cache);
- UpdatedLoc = DILocation::get(Ctx, LocToUpdate->getLine(),
- LocToUpdate->getColumn(), NewScope);
+ UpdatedLoc = DILocation::get(
+ Ctx, LocToUpdate->getLine(), LocToUpdate->getColumn(), NewScope,
+ /*InlinedAt=*/nullptr, /*ImplicitCode=*/false, /*AtomGroup=*/0,
+ /*AtomRank=*/0, LocToUpdate->getRawIRLayers());
Cache[LocToUpdate] = UpdatedLoc;
}
// Recreate the location chain, bottom-up, starting at the new scope (or a
- // cached result).
+ // cached result). Each frame keeps its own irlayers, as appendInlinedAt does:
+ // the intermediate-IR snapshot can live on any frame, so outlining must not
+ // drop it.
for (const DILocation *LocToUpdate : reverse(LocChain)) {
- UpdatedLoc =
- DILocation::get(Ctx, LocToUpdate->getLine(), LocToUpdate->getColumn(),
- LocToUpdate->getScope(), UpdatedLoc);
+ UpdatedLoc = DILocation::get(
+ Ctx, LocToUpdate->getLine(), LocToUpdate->getColumn(),
+ LocToUpdate->getScope(), UpdatedLoc, /*ImplicitCode=*/false,
+ /*AtomGroup=*/0, /*AtomRank=*/0, LocToUpdate->getRawIRLayers());
Cache[LocToUpdate] = UpdatedLoc;
}
diff --git a/llvm/test/Transforms/HotColdSplit/transfer-debug-info-irlayers.ll b/llvm/test/Transforms/HotColdSplit/transfer-debug-info-irlayers.ll
new file mode 100644
index 0000000000000..e057c84edbd2b
--- /dev/null
+++ b/llvm/test/Transforms/HotColdSplit/transfer-debug-info-irlayers.ll
@@ -0,0 +1,74 @@
+; RUN: opt -passes=hotcoldsplit -hotcoldsplit-threshold=0 -S < %s | FileCheck %s
+;;
+;; Outlining rebuilds every DILocation in the extracted region so its scope points
+;; at the new cold function: once for the terminal frame, and once per inlined-at
+;; frame. Check that irlayers survive both rebuilds.
+
+target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-macosx10.14.0"
+
+; CHECK-LABEL: define {{.*}}@foo.cold.1
+
+;; A location carrying layers of its own: the terminal frame is rebuilt against
+;; the cold function's scope and keeps its layer list.
+; CHECK: [[ADD:%.*]] = add i32 %{{.*}}, 1, !dbg ![[OWN:[0-9]+]]
+
+;; An inlined location whose inlinedAt frame carries the layers: the chain
+;; rebuild has to keep them on that frame.
+; CHECK: call void @sink(i32 [[ADD]]), !dbg ![[INL:[0-9]+]]
+
+;; Each frame keeps its own coordinate: 100 for the terminal frame, 200 for the
+;; inlined-at frame.
+; CHECK-DAG: ![[OWN]] = !DILocation(line: 1, column: 1, scope: !{{[0-9]+}}, irlayers: ![[OWNLIST:[0-9]+]])
+; CHECK-DAG: ![[OWNLIST]] = !DILayerLocList(![[OWNLAYER:[0-9]+]])
+; CHECK-DAG: ![[OWNLAYER]] = !DILayerLoc(line: 100, column: 1, file: !{{[0-9]+}}, kind: "tile ir")
+
+; CHECK-DAG: ![[INL]] = !DILocation(line: 2, column: 2, scope: !{{[0-9]+}}, inlinedAt: ![[IA:[0-9]+]])
+; CHECK-DAG: ![[IA]] = !DILocation(line: 3, column: 3, scope: !{{[0-9]+}}, irlayers: ![[IALIST:[0-9]+]])
+; CHECK-DAG: ![[IALIST]] = !DILayerLocList(![[IALAYER:[0-9]+]])
+; CHECK-DAG: ![[IALAYER]] = !DILayerLoc(line: 200, column: 1, file: !{{[0-9]+}}, kind: "tile ir")
+
+define void @foo(i32 %arg1, i1 %c) !dbg !6 {
+entry:
+ br i1 %c, label %if.then, label %if.end
+
+if.then:
+ ret void
+
+if.end:
+ %add1 = add i32 %arg1, 1, !dbg !20
+ call void @sink(i32 %add1), !dbg !21
+ ret void
+}
+
+declare void @sink(i32) cold
+
+define void @inline_me() !dbg !12 {
+ ret void
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!5}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "tile", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "<stdin>", directory: "/")
+!2 = !{}
+!5 = !{i32 2, !"Debug Info Version", i32 3}
+!6 = distinct !DISubprogram(name: "foo", linkageName: "foo", scope: null, file: !1, line: 1, type: !7, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
+!7 = !DISubroutineType(types: !2)
+!12 = distinct !DISubprogram(name: "inline_me", linkageName: "inline_me", scope: null, file: !1, line: 1, type: !7, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !2)
+
+;; Two distinct coordinates in one intermediate-IR snapshot, so a frame that ends
+;; up with the other frame's layers fails rather than passing quietly.
+!14 = !DIFile(filename: "kernel.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+!15 = !DILayerLoc(line: 100, column: 1, file: !14, kind: "tile ir")
+!16 = !DILayerLocList(!15)
+!17 = !DILayerLoc(line: 200, column: 1, file: !14, kind: "tile ir")
+!18 = !DILayerLocList(!17)
+
+;; !20 carries layers directly, exercising the terminal-frame rebuild. !21 is
+;; inlined from @inline_me and its inlinedAt frame !22 carries its own, exercising
+;; the chain rebuild.
+!20 = !DILocation(line: 1, column: 1, scope: !6, irlayers: !16)
+!21 = !DILocation(line: 2, column: 2, scope: !12, inlinedAt: !22)
+!22 = !DILocation(line: 3, column: 3, scope: !6, irlayers: !18)
>From e87cfe569ee655885a94c97ebaedb9e156b422ab Mon Sep 17 00:00:00 2001
From: Alexander Yermolovich <ayermolovich at nvidia.com>
Date: Wed, 12 Aug 2026 22:05:34 +0000
Subject: [PATCH 3/4] [DebugInfo] Compare only the primary position in
isSameSourceLocation
isSameSourceLocation folded the new irlayers operand into its comparison, which
changed generic equality semantics for every caller. SimplifyCFG uses it to
decide whether to discard a speculated instruction's location, and DwarfDebug
uses it as the line-table emission gate, so a layer-only difference discarded
debug locations and added redundant line-table rows on targets that ignore
layers.
Compare the primary position only. Callers that need layer equality get an
explicit variant (isSameSourceLocationAndIRLayers, added in the upstream stack).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply at anthropic.com>
---
llvm/include/llvm/IR/DebugLoc.h | 10 ++++++----
llvm/unittests/IR/DebugInfoTest.cpp | 13 +++++++------
2 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/llvm/include/llvm/IR/DebugLoc.h b/llvm/include/llvm/IR/DebugLoc.h
index c9a5149c80940..cb82a7119813b 100644
--- a/llvm/include/llvm/IR/DebugLoc.h
+++ b/llvm/include/llvm/IR/DebugLoc.h
@@ -239,15 +239,17 @@ class DebugLoc {
appendInlinedAt(const DebugLoc &DL, DILocation *InlinedAt, LLVMContext &Ctx,
DenseMap<const MDNode *, MDNode *> &Cache);
- /// Return true if the source locations match, ignoring isImplicitCode and
- /// source atom info.
+ /// Return true if the source locations match, ignoring isImplicitCode,
+ /// source atom info and intermediate-IR layers. Layers are deliberately not
+ /// part of this comparison: two locations at the same source position are the
+ /// same source position regardless of which intermediate IR they came from.
+ /// Callers that must also match layers use isSameSourceLocationAndIRLayers.
bool isSameSourceLocation(const DebugLoc &Other) const {
if (get() == Other.get())
return true;
return ((bool)*this == (bool)Other) && getLine() == Other.getLine() &&
getCol() == Other.getCol() && getScope() == Other.getScope() &&
- getInlinedAt() == Other.getInlinedAt() &&
- getRawIRLayers() == Other.getRawIRLayers();
+ getInlinedAt() == Other.getInlinedAt();
}
LLVM_ABI unsigned getLine() const;
diff --git a/llvm/unittests/IR/DebugInfoTest.cpp b/llvm/unittests/IR/DebugInfoTest.cpp
index 7e23d38f9ad71..b547ced372d9e 100644
--- a/llvm/unittests/IR/DebugInfoTest.cpp
+++ b/llvm/unittests/IR/DebugInfoTest.cpp
@@ -1631,15 +1631,16 @@ TEST(DebugLocTest, IntermediateLocEquality) {
DebugLoc DL4(DILocation::get(Ctx, 10, 5, SP)); // no layers
ASSERT_NE(DL1.get(), DL2.get());
- // Distinct nodes with same line/col/scope and identical (uniqued) layers ->
- // same source location (exercises the layer-aware structural compare).
+ // isSameSourceLocation compares the primary position only, so all three of
+ // these are the same source location: identical layers, differing layers, and
+ // layers versus none. See IntermediateLocEqualityWithLayers for the
+ // layer-aware comparison.
EXPECT_TRUE(DL1.isSameSourceLocation(DL2));
- // Same line/col/scope but different layers -> NOT the same source location.
- EXPECT_FALSE(DL1.isSameSourceLocation(DL3));
- // One has layers, the other doesn't -> NOT the same source location.
- EXPECT_FALSE(DL1.isSameSourceLocation(DL4));
+ EXPECT_TRUE(DL1.isSameSourceLocation(DL3));
+ EXPECT_TRUE(DL1.isSameSourceLocation(DL4));
}
+
TEST(DebugLocTest, MergedLocationWithIntermediate) {
LLVMContext Ctx;
auto M = std::make_unique<Module>("MyModule", Ctx);
>From ce7b99cf23c68383a343334f1005b44b68b6197a Mon Sep 17 00:00:00 2001
From: Alexander Yermolovich <ayermolovich at nvidia.com>
Date: Wed, 12 Aug 2026 23:41:29 +0000
Subject: [PATCH 4/4] [DebugInfo] Drop references to symbols this branch does
not carry
The isSameSourceLocation doc comment pointed at
isSameSourceLocationAndIRLayers, and a unit-test comment pointed at
IntermediateLocEqualityWithLayers. Neither exists here -- both belong to the
layer-aware comparison that lands separately -- so restore the original doc
comment and drop the dangling test reference.
Also removes a stray blank line that clang-format rejects.
---
llvm/include/llvm/IR/DebugLoc.h | 7 ++-----
llvm/unittests/IR/DebugInfoTest.cpp | 4 +---
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/llvm/include/llvm/IR/DebugLoc.h b/llvm/include/llvm/IR/DebugLoc.h
index cb82a7119813b..7d748a4b12375 100644
--- a/llvm/include/llvm/IR/DebugLoc.h
+++ b/llvm/include/llvm/IR/DebugLoc.h
@@ -239,11 +239,8 @@ class DebugLoc {
appendInlinedAt(const DebugLoc &DL, DILocation *InlinedAt, LLVMContext &Ctx,
DenseMap<const MDNode *, MDNode *> &Cache);
- /// Return true if the source locations match, ignoring isImplicitCode,
- /// source atom info and intermediate-IR layers. Layers are deliberately not
- /// part of this comparison: two locations at the same source position are the
- /// same source position regardless of which intermediate IR they came from.
- /// Callers that must also match layers use isSameSourceLocationAndIRLayers.
+ /// Return true if the source locations match, ignoring isImplicitCode and
+ /// source atom info.
bool isSameSourceLocation(const DebugLoc &Other) const {
if (get() == Other.get())
return true;
diff --git a/llvm/unittests/IR/DebugInfoTest.cpp b/llvm/unittests/IR/DebugInfoTest.cpp
index b547ced372d9e..5d3993fe87236 100644
--- a/llvm/unittests/IR/DebugInfoTest.cpp
+++ b/llvm/unittests/IR/DebugInfoTest.cpp
@@ -1633,14 +1633,12 @@ TEST(DebugLocTest, IntermediateLocEquality) {
ASSERT_NE(DL1.get(), DL2.get());
// isSameSourceLocation compares the primary position only, so all three of
// these are the same source location: identical layers, differing layers, and
- // layers versus none. See IntermediateLocEqualityWithLayers for the
- // layer-aware comparison.
+ // layers versus none.
EXPECT_TRUE(DL1.isSameSourceLocation(DL2));
EXPECT_TRUE(DL1.isSameSourceLocation(DL3));
EXPECT_TRUE(DL1.isSameSourceLocation(DL4));
}
-
TEST(DebugLocTest, MergedLocationWithIntermediate) {
LLVMContext Ctx;
auto M = std::make_unique<Module>("MyModule", Ctx);
More information about the llvm-commits
mailing list