[llvm] [Debug] Add support for multi-level line support (PR #211390)
Alexander Yermolovich via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 16:51:52 PDT 2026
https://github.com/ayermolo updated https://github.com/llvm/llvm-project/pull/211390
>From 389b47ae520fe8411edbb1872c74a0a973ed6b39 Mon Sep 17 00:00:00 2001
From: Alexander Yermolovich <ayermolovich at nvidia.com>
Date: Wed, 12 Aug 2026 22:03:39 +0000
Subject: [PATCH 1/3] [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, and the two
DILocation chain rebuilds in DebugLoc carry it over so inlining and outlining
do not drop it.
isSameSourceLocation compares the primary source position only, not the
irlayers operand. Two locations at the same source position are the same
source position regardless of which intermediate IR they came from. Callers
that need layer equality use isSameSourceLocationAndIRLayers (added with the
SimplifyCFG guard in a follow-on commit).
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply at anthropic.com>
---
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 | 4 +
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 | 29 +-
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 +++
.../transfer-debug-info-irlayers.ll | 74 ++++
llvm/test/Verifier/intermediate-layerloc.ll | 37 ++
llvm/unittests/IR/DebugInfoTest.cpp | 403 ++++++++++++++++++
24 files changed, 1278 insertions(+), 65 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/Transforms/HotColdSplit/transfer-debug-info-irlayers.ll
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..7d748a4b12375 100644
--- a/llvm/include/llvm/IR/DebugLoc.h
+++ b/llvm/include/llvm/IR/DebugLoc.h
@@ -253,6 +253,10 @@ class DebugLoc {
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..41f60766e09bf 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();
}
@@ -109,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;
}
@@ -149,9 +159,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/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)
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..b547ced372d9e 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,406 @@ 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());
+ // 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));
+ EXPECT_TRUE(DL1.isSameSourceLocation(DL3));
+ EXPECT_TRUE(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 3ca30cbfebdfa3a44456945abe6a2a3c3242fd43 Mon Sep 17 00:00:00 2001
From: Alexander Yermolovich <ayermolovich at nvidia.com>
Date: Wed, 12 Aug 2026 22:03:59 +0000
Subject: [PATCH 2/3] [DebugInfo] Preserve DILocation irlayers across inlining
and remapping
Several places rebuild a DILocation field by field and so dropped the new
operand: the inliner, ValueMapper, cloneWithDiscriminator, and
strip-nonlinetable-debuginfo. Carry it over in each.
An intermediate layer is line-table data, so it survives the downgrade to
line-tables-only rather than being stripped along with the rest of -g.
Also add isSameSourceLocationAndIRLayers for callers that replace a location
wholesale and must match layers too. SimplifyCFG uses it in the
foldBranchToCommonDest atom-transfer guard: the predecessor terminator carries
the whole location of the folded branch, so a layer-only difference would
silently relocate its intermediate coordinate.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply at anthropic.com>
---
llvm/include/llvm/IR/DebugLoc.h | 15 ++++-
llvm/lib/IR/DebugInfo.cpp | 23 +++++--
llvm/lib/Transforms/Utils/InlineFunction.cpp | 9 ++-
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 2 +-
llvm/lib/Transforms/Utils/ValueMapper.cpp | 7 +-
.../Inline/intermediate-loc-inline.ll | 65 ++++++++++++++++++
.../SimplifyCFG/irlayers-atom-transfer.ll | 67 +++++++++++++++++++
.../strip-nonlinetable-debuginfo-irlayers.ll | 34 ++++++++++
llvm/unittests/IR/DebugInfoTest.cpp | 38 +++++++++++
9 files changed, 248 insertions(+), 12 deletions(-)
create mode 100644 llvm/test/Transforms/Inline/intermediate-loc-inline.ll
create mode 100644 llvm/test/Transforms/SimplifyCFG/irlayers-atom-transfer.ll
create mode 100644 llvm/test/Transforms/Util/strip-nonlinetable-debuginfo-irlayers.ll
diff --git a/llvm/include/llvm/IR/DebugLoc.h b/llvm/include/llvm/IR/DebugLoc.h
index 7d748a4b12375..db6000751bf1d 100644
--- a/llvm/include/llvm/IR/DebugLoc.h
+++ b/llvm/include/llvm/IR/DebugLoc.h
@@ -239,8 +239,11 @@ 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;
@@ -249,6 +252,14 @@ class DebugLoc {
getInlinedAt() == Other.getInlinedAt();
}
+ /// As isSameSourceLocation, and additionally requires the intermediate-IR
+ /// layers to match. For callers that replace a location wholesale, or that
+ /// emit something derived from the layers.
+ bool isSameSourceLocationAndIRLayers(const DebugLoc &Other) const {
+ return isSameSourceLocation(Other) &&
+ getRawIRLayers() == Other.getRawIRLayers();
+ }
+
LLVM_ABI unsigned getLine() const;
LLVM_ABI unsigned getCol() const;
LLVM_ABI MDNode *getScope() const;
diff --git a/llvm/lib/IR/DebugInfo.cpp b/llvm/lib/IR/DebugInfo.cpp
index e164ec54ead60..540d7a43a3fee 100644
--- a/llvm/lib/IR/DebugInfo.cpp
+++ b/llvm/lib/IR/DebugInfo.cpp
@@ -851,11 +851,16 @@ class DebugTypeInfoRemoval {
DILocation *getReplacementMDLocation(DILocation *MLD) {
auto *Scope = map(MLD->getScope());
auto *InlinedAt = map(MLD->getInlinedAt());
+ // Intermediate-IR layers are line-table data (a line/column in a DIFile),
+ // so they survive the downgrade to line-tables-only.
+ Metadata *IRLayers = map(MLD->getRawIRLayers());
if (MLD->isDistinct())
- return DILocation::getDistinct(MLD->getContext(), MLD->getLine(),
- MLD->getColumn(), Scope, InlinedAt);
+ return DILocation::getDistinct(
+ MLD->getContext(), MLD->getLine(), MLD->getColumn(), Scope, InlinedAt,
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0, IRLayers);
return DILocation::get(MLD->getContext(), MLD->getLine(), MLD->getColumn(),
- Scope, InlinedAt);
+ Scope, InlinedAt, /*ImplicitCode=*/false,
+ /*AtomGroup=*/0, /*AtomRank=*/0, IRLayers);
}
/// Create a new generic MDNode, to replace the one given
@@ -887,6 +892,12 @@ class DebugTypeInfoRemoval {
return getReplacementCU(CU);
if (isa<DIFile>(N))
return N;
+ // Intermediate-IR layer nodes hold only a kind string and a DIFile, so
+ // none of their content is part of the type hierarchy being stripped. The
+ // generic fallback below would rebuild them as plain MDTuples, which
+ // DILocation::getIRLayers() cannot cast.
+ if (isa<DILayerLoc>(N) || isa<DILayerLocList>(N))
+ return N;
if (auto *MDLB = dyn_cast<DILexicalBlockBase>(N))
// Remap to our referenced scope (recursively).
return mapNode(MDLB->getScope());
@@ -990,8 +1001,12 @@ bool llvm::stripNonLineTableDebugInfo(Module &M) {
MDNode *InlinedAt = DL.getInlinedAt();
Scope = remap(Scope);
InlinedAt = remap(InlinedAt);
+ // Layers need no remapping (Mapper maps them to themselves), but they
+ // do need carrying over -- they are line-table data.
return DILocation::get(M.getContext(), DL.getLine(), DL.getCol(),
- Scope, InlinedAt);
+ Scope, InlinedAt, /*ImplicitCode=*/false,
+ /*AtomGroup=*/0, /*AtomRank=*/0,
+ DL.getRawIRLayers());
};
if (I.getDebugLoc() != DebugLoc())
diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index 45454002a78e2..b3ab81505df85 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -1938,7 +1938,8 @@ static DebugLoc inlineDebugLoc(DebugLoc OrigDL, DILocation *InlinedAt,
auto IA = DebugLoc::appendInlinedAt(OrigDL, InlinedAt, Ctx, IANodes);
return DILocation::get(Ctx, OrigDL.getLine(), OrigDL.getCol(),
OrigDL.getScope(), IA, OrigDL.isImplicitCode(),
- OrigDL->getAtomGroup(), OrigDL->getAtomRank());
+ OrigDL->getAtomGroup(), OrigDL->getAtomRank(),
+ OrigDL.getRawIRLayers());
}
/// Update inlined instructions' line numbers to
@@ -1960,9 +1961,13 @@ static void fixupLineNumbers(Function *Fn, Function::iterator FI,
// Create a unique call site, not to be confused with any other call from the
// same location.
+ // Preserve the call site's irlayers so the (outermost) inlined-at
+ // frame keeps its intermediate-IR snapshot instead of being stripped.
InlinedAtNode = DILocation::getDistinct(
Ctx, InlinedAtNode->getLine(), InlinedAtNode->getColumn(),
- InlinedAtNode->getScope(), InlinedAtNode->getInlinedAt());
+ InlinedAtNode->getScope(), InlinedAtNode->getInlinedAt(),
+ /*ImplicitCode=*/false, /*AtomGroup=*/0, /*AtomRank=*/0,
+ InlinedAtNode->getRawIRLayers());
// Cache the inlined-at nodes as they're built so they are reused, without
// this every instruction's inlined-at chain would become distinct from each
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 089892dc573f3..2c5000d0d96df 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1240,7 +1240,7 @@ static void cloneInstructionsIntoPredecessorBlockAndUpdateSSAUses(
if (auto &PredDL = PTI->getDebugLoc()) {
auto &DL = BB->getTerminator()->getDebugLoc();
if (!PredDL->getAtomGroup() && DL && DL->getAtomGroup() &&
- PredDL.isSameSourceLocation(DL)) {
+ PredDL.isSameSourceLocationAndIRLayers(DL)) {
PTI->setDebugLoc(DL);
RemapSourceAtom(PTI, VMap);
}
diff --git a/llvm/lib/Transforms/Utils/ValueMapper.cpp b/llvm/lib/Transforms/Utils/ValueMapper.cpp
index 22230c352bf01..ee506afe06f12 100644
--- a/llvm/lib/Transforms/Utils/ValueMapper.cpp
+++ b/llvm/lib/Transforms/Utils/ValueMapper.cpp
@@ -1332,8 +1332,9 @@ void llvm::RemapSourceAtom(Instruction *I, ValueToValueMapTy &VM) {
AtomGroup = R->second;
// Remap the atom group and copy all other fields.
- DILocation *New = DILocation::get(
- I->getContext(), DL.getLine(), DL.getCol(), DL.getScope(),
- DL.getInlinedAt(), DL.isImplicitCode(), AtomGroup, DL->getAtomRank());
+ DILocation *New =
+ DILocation::get(I->getContext(), DL.getLine(), DL.getCol(), DL.getScope(),
+ DL.getInlinedAt(), DL.isImplicitCode(), AtomGroup,
+ DL->getAtomRank(), DL.getRawIRLayers());
I->setDebugLoc(New);
}
diff --git a/llvm/test/Transforms/Inline/intermediate-loc-inline.ll b/llvm/test/Transforms/Inline/intermediate-loc-inline.ll
new file mode 100644
index 0000000000000..228e1d4a725b5
--- /dev/null
+++ b/llvm/test/Transforms/Inline/intermediate-loc-inline.ll
@@ -0,0 +1,65 @@
+; RUN: opt %s -passes='always-inline' -S | FileCheck %s
+
+;; This is the shape the tile compiler emits: the front end inlined `helper`
+;; into `kernel`, THEN captured the tile-IR snapshot, so the snapshot (irlayers)
+;; sits on the OUTER `kernel` frame -- the instruction head (`helper`) carries
+;; no layer of its own. The layer lives on whichever DILocation
+;; frame was outermost when the snapshot was taken (here, `kernel`).
+;;
+;; `kernel` is then inlined into `caller` by the LLVM inliner -- an inline AFTER
+;; the snapshot. The `kernel` frame becomes an INNER frame of the deeper chain,
+;; and its irlayers must be PRESERVED, not stripped (appendInlinedAt /
+;; InlinedAtNode). The NVPTX backend later finds this layer by
+;; walking head -> outward to the first layer-bearing frame.
+
+define void @kernel(ptr %p) alwaysinline !dbg !6 {
+ store ptr null, ptr %p, align 8, !dbg !9
+ ret void, !dbg !8
+}
+
+define void @caller(ptr %p) !dbg !12 {
+ call void @kernel(ptr %p), !dbg !15
+ ret void, !dbg !16
+}
+
+; CHECK-LABEL: define void @caller
+; CHECK: store ptr null, ptr %p,{{.*}} !dbg ![[INST:[0-9]+]]
+
+;; The inlined store keeps the helper (head) source loc; its inlinedAt now points
+;; at the kernel frame.
+; CHECK-DAG: ![[INST]] = !DILocation(line: 6, column: 1, scope: ![[HELPER:[0-9]+]], inlinedAt: ![[KFRAME:[0-9]+]])
+;; The kernel frame is an INNER frame now (inlinedAt the caller call site) and
+;; RETAINS its snapshot layer -- the whole point of the outermost-frame model.
+; CHECK-DAG: ![[KFRAME]] = distinct !DILocation(line: 11, column: 1, scope: ![[KERNEL:[0-9]+]], inlinedAt: ![[CS:[0-9]+]], irlayers: ![[LIST:[0-9]+]])
+;; The appended outermost frame is the caller's call site, with no layer of its own.
+; CHECK-DAG: ![[CS]] = distinct !DILocation(line: 15, column: 1, scope: ![[CALLER:[0-9]+]])
+; CHECK-DAG: ![[LIST]] = !DILayerLocList(![[LAYER:[0-9]+]])
+; CHECK-DAG: ![[LAYER]] = !DILayerLoc(line: 100, column: 1, file: {{![0-9]+}}, kind: "tile ir")
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "tile", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "kernel.py", directory: "/k")
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !5)
+!5 = !{null}
+
+;; kernel (outer-frame scope) and the front-end-inlined helper (head scope).
+!6 = distinct !DISubprogram(name: "kernel", scope: !1, file: !1, line: 10, type: !4, spFlags: DISPFlagDefinition, unit: !0)
+!7 = distinct !DISubprogram(name: "helper", scope: !1, file: !1, line: 5, type: !4, spFlags: DISPFlagDefinition, unit: !0)
+!8 = !DILocation(line: 10, column: 1, scope: !6)
+
+;; tile-IR snapshot file + the snapshot layer.
+!18 = !DIFile(filename: "kernel.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
+!19 = !DILayerLoc(line: 100, column: 1, file: !18, kind: "tile ir")
+!17 = !DILayerLocList(!19)
+
+;; The kernel (outer) frame carries the snapshot; the helper (head) does not.
+!10 = !DILocation(line: 11, column: 1, scope: !6, irlayers: !17)
+!9 = !DILocation(line: 6, column: 1, scope: !7, inlinedAt: !10)
+
+;; caller.
+!12 = distinct !DISubprogram(name: "caller", scope: !1, file: !1, line: 20, type: !4, spFlags: DISPFlagDefinition, unit: !0)
+!15 = !DILocation(line: 15, column: 1, scope: !12)
+!16 = !DILocation(line: 16, column: 1, scope: !12)
diff --git a/llvm/test/Transforms/SimplifyCFG/irlayers-atom-transfer.ll b/llvm/test/Transforms/SimplifyCFG/irlayers-atom-transfer.ll
new file mode 100644
index 0000000000000..83ec4b31a2cc0
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/irlayers-atom-transfer.ll
@@ -0,0 +1,67 @@
+; RUN: opt -S -passes=simplifycfg -simplifycfg-require-and-preserve-domtree=1 < %s | FileCheck %s
+
+;; foldBranchToCommonDest: the Key Instructions atom-group transfer from BB's
+;; terminator to the predecessor terminator (PTI) is guarded by
+;; isSameSourceLocationAndIRLayers. When PTI and BB's terminator share the
+;; same primary source position but carry DIFFERENT irlayers, the transfer must
+;; be skipped and PTI keeps its own location (irlayers line 100) rather than
+;; receiving BB's location wholesale (irlayers line 200, atomGroup: 1).
+;;
+;; Non-vacuity: with the guard reverted to isSameSourceLocation the two
+;; locations would compare equal (primary position matches) and the transfer
+;; would fire, giving %or.cond a location with atomGroup: 1 and irlayers
+;; pointing to line 200. The CHECK below then fails against that output.
+
+define i32 @test_no_transfer(i32 %x, i1 %c1) !dbg !4 {
+entry:
+ br i1 %c1, label %merge, label %bb, !dbg !13
+
+bb:
+ %cond = icmp ne i32 %x, 0
+ br i1 %cond, label %merge, label %other, !dbg !14
+
+merge:
+ ret i32 1
+
+other:
+ ret i32 0
+}
+
+;; The folded select must carry the pred location (irlayers line 100, no
+;; atomGroup). If the transfer had fired it would carry atomGroup: 1 and
+;; irlayers pointing to line 200.
+; CHECK-LABEL: define {{.*}}@test_no_transfer
+; CHECK: %or.cond = select {{.*}}, !dbg ![[PRED:[0-9]+]]
+; CHECK: ![[PRED]] = !DILocation(line: 10, column: 5, scope: !{{[0-9]+}}, irlayers: ![[LIST:[0-9]+]])
+; CHECK: ![[LIST]] = !DILayerLocList(![[LAYER:[0-9]+]])
+; CHECK: ![[LAYER]] = !DILayerLoc(line: 100,
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "test.c", directory: "/tmp")
+!2 = !DISubroutineType(types: !{})
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = distinct !DISubprogram(name: "test_no_transfer", scope: !1, file: !1,
+ line: 1, type: !2, scopeLine: 1,
+ spFlags: DISPFlagDefinition, unit: !0,
+ keyInstructions: true)
+
+!8 = !DIFile(filename: "test.tile.ir", directory: "/tmp",
+ checksumkind: CSK_MD5,
+ checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
+
+;; Two distinct intermediate layers at the same primary source position. Using
+;; different layer coordinates means the test can tell apart "kept pred's layers
+;; (line 100)" from "got bb's layers (line 200)".
+!9 = !DILayerLoc(line: 100, column: 1, file: !8, kind: "tile ir")
+!10 = !DILayerLocList(!9)
+!11 = !DILayerLoc(line: 200, column: 1, file: !8, kind: "tile ir")
+!12 = !DILayerLocList(!11)
+
+;; Pred terminator: same primary position as bb, different irlayers, no atomGroup.
+!13 = !DILocation(line: 10, column: 5, scope: !4, irlayers: !10)
+;; BB terminator: same primary position, different irlayers, has atomGroup.
+!14 = !DILocation(line: 10, column: 5, scope: !4, irlayers: !12,
+ atomGroup: 1, atomRank: 1)
diff --git a/llvm/test/Transforms/Util/strip-nonlinetable-debuginfo-irlayers.ll b/llvm/test/Transforms/Util/strip-nonlinetable-debuginfo-irlayers.ll
new file mode 100644
index 0000000000000..15771e8db0cc3
--- /dev/null
+++ b/llvm/test/Transforms/Util/strip-nonlinetable-debuginfo-irlayers.ll
@@ -0,0 +1,34 @@
+; RUN: opt -passes=strip-nonlinetable-debuginfo -S %s | FileCheck %s
+;;
+;; strip-nonlinetable-debuginfo downgrades -g metadata to line-tables-only. An
+;; intermediate-IR layer is line-table data -- a line/column in a DIFile -- so it
+;; must survive, and DILayerLoc/DILayerLocList must keep their types rather than
+;; being rebuilt as generic MDTuples by the pass's fallback remapping.
+
+define void @f(ptr %p) !dbg !5 {
+ store ptr null, ptr %p, align 8, !dbg !20
+ ret void, !dbg !21
+}
+
+; CHECK: store ptr null, ptr %p, align 8, !dbg ![[DBG:[0-9]+]]
+; CHECK-DAG: ![[DBG]] = !DILocation(line: 2, column: 5, scope: !{{[0-9]+}}, irlayers: ![[LIST:[0-9]+]])
+; CHECK-DAG: ![[LIST]] = !DILayerLocList(![[LAYER:[0-9]+]])
+; CHECK-DAG: ![[LAYER]] = !DILayerLoc(line: 42, column: 5, file: ![[INTF:[0-9]+]], kind: "tile ir")
+; CHECK-DAG: ![[INTF]] = !DIFile(filename: "kernel.tileir", directory: ".")
+
+!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: "f", scope: !1, file: !1, line: 1, type: !4, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+
+!10 = !DIFile(filename: "kernel.tileir", directory: ".")
+!11 = !DILayerLoc(line: 42, column: 5, file: !10, kind: "tile ir")
+!12 = !DILayerLocList(!11)
+
+!20 = !DILocation(line: 2, column: 5, scope: !5, irlayers: !12)
+!21 = !DILocation(line: 3, column: 1, scope: !5)
diff --git a/llvm/unittests/IR/DebugInfoTest.cpp b/llvm/unittests/IR/DebugInfoTest.cpp
index b547ced372d9e..679f11585dbbc 100644
--- a/llvm/unittests/IR/DebugInfoTest.cpp
+++ b/llvm/unittests/IR/DebugInfoTest.cpp
@@ -1640,6 +1640,44 @@ TEST(DebugLocTest, IntermediateLocEquality) {
EXPECT_TRUE(DL1.isSameSourceLocation(DL4));
}
+TEST(DebugLocTest, IntermediateLocEqualityWithLayers) {
+ 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);
+ DILayerLoc *LayerB = DILayerLoc::get(Ctx, Kind, IntF, 100, 2);
+ DILayerLocList *ListA = DILayerLocList::get(Ctx, {LayerA});
+ DILayerLocList *ListB = DILayerLocList::get(Ctx, {LayerB});
+
+ 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));
+ 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());
+ // Identical layers (distinct nodes, same layer list) -> same.
+ EXPECT_TRUE(DL1.isSameSourceLocationAndIRLayers(DL2));
+ // Same source coordinate but different layers -> NOT the same.
+ EXPECT_FALSE(DL1.isSameSourceLocationAndIRLayers(DL3));
+ // One has layers, the other doesn't -> NOT the same.
+ EXPECT_FALSE(DL1.isSameSourceLocationAndIRLayers(DL4));
+}
TEST(DebugLocTest, MergedLocationWithIntermediate) {
LLVMContext Ctx;
>From f2dce70c12fc585538ad773bb77e4f12ffe7dad7 Mon Sep 17 00:00:00 2001
From: Alexander Yermolovich <ayermolovich at nvidia.com>
Date: Wed, 12 Aug 2026 22:04:18 +0000
Subject: [PATCH 3/3] [NVPTX] Emit .loc_intermediate and
.nv_intermediate_source_section
Lower DILocation's `irlayers` into PTX so a debugger can step through the
intermediate IR a kernel was compiled from. Each layer emits a secondary
.loc_intermediate alongside the primary .loc, and the intermediate source text
carried on DIFile.source is emitted once per file in a new section.
MCStreamer gains an optional parameter so a target can select the .loc
mnemonic; it affects assembly output only.
DwarfDebug gains a recordTargetSameSourceLine hook. When the primary source
position is unchanged from the previous instruction the line table needs no
new row, but NVPTX may still need to emit a .loc_intermediate for a changed
irlayers operand. NVPTXDwarfDebug implements the hook with a PrevIRLayers
guard so it only emits when the layer list actually changed.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply at anthropic.com>
---
llvm/docs/NVPTXUsage.md | 57 +++++++++++
llvm/include/llvm/MC/MCObjectStreamer.h | 3 +-
llvm/include/llvm/MC/MCStreamer.h | 10 +-
llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp | 4 +
llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h | 6 ++
llvm/lib/MC/MCAsmStreamer.cpp | 9 +-
llvm/lib/MC/MCObjectStreamer.cpp | 10 +-
llvm/lib/MC/MCStreamer.cpp | 3 +-
llvm/lib/Target/NVPTX/NVPTXAsmPrinter.cpp | 25 +++++
llvm/lib/Target/NVPTX/NVPTXDwarfDebug.cpp | 94 +++++++++++++++++++
llvm/lib/Target/NVPTX/NVPTXDwarfDebug.h | 41 ++++++++
.../intermediate-checksum-secondary-file.ll | 43 +++++++++
.../intermediate-loc-crossmodule-shadow.ll | 78 +++++++++++++++
.../NVPTX/intermediate-loc-dedup-gate.ll | 70 ++++++++++++++
.../NVPTX/intermediate-loc-inline-shadow.ll | 85 +++++++++++++++++
.../NVPTX/intermediate-loc-multi-layer.ll | 62 ++++++++++++
llvm/test/DebugInfo/NVPTX/intermediate-loc.ll | 76 +++++++++++++++
...intermediate-source-section-determinism.ll | 64 +++++++++++++
.../intermediate-source-section-empty.ll | 45 +++++++++
.../intermediate-source-section-mixed.ll | 62 ++++++++++++
...intermediate-source-section-no-checksum.ll | 51 ++++++++++
...ermediate-source-section-shared-filenum.ll | 58 ++++++++++++
.../NVPTX/intermediate-source-section.ll | 81 ++++++++++++++++
23 files changed, 1023 insertions(+), 14 deletions(-)
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-checksum-secondary-file.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-loc-crossmodule-shadow.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-loc-dedup-gate.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-loc-inline-shadow.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-loc-multi-layer.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-loc.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-source-section-determinism.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-source-section-empty.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-source-section-mixed.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-source-section-no-checksum.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-source-section-shared-filenum.ll
create mode 100644 llvm/test/DebugInfo/NVPTX/intermediate-source-section.ll
diff --git a/llvm/docs/NVPTXUsage.md b/llvm/docs/NVPTXUsage.md
index 2541d504c6e10..502fd02aa46d6 100644
--- a/llvm/docs/NVPTXUsage.md
+++ b/llvm/docs/NVPTXUsage.md
@@ -154,6 +154,63 @@ Example: 32-bit PTX for CUDA Driver API: `nvptx-nvidia-cuda`
Example: 64-bit PTX for CUDA Driver API: `nvptx64-nvidia-cuda`
+### Multi-Level Line Tables
+
+A GPU program is often lowered through one or more intermediate IRs before
+reaching LLVM IR, and a tool may want to map an address back to a position in
+each of them, not just to the original source. A `DILocation` can carry those
+extra positions in its `irlayers:` field, which points at a `DILayerLocList` of
+`DILayerLoc` entries — see
+[LangRef](LangRef.md#dilayerloc). NVPTX is currently the only
+consumer; other targets ignore the field.
+
+For each layer on an instruction's location, the backend emits a secondary
+directive immediately after the primary `.loc`:
+
+```text
+.loc 1 2 5 // primary source position
+.loc_intermediate 2 100 10 // position in the intermediate IR
+```
+
+The layer's `DIFile` becomes an ordinary `.file` entry, so `.loc_intermediate`
+refers to it by file number exactly as `.loc` does.
+
+The layer's intermediate IR text travels with it, in a
+`.nv_intermediate_source_section`, so a consumer can show that IR without needing
+the file on disk:
+
+```text
+.nv_intermediate_source_section {
+ .code_block {
+ .ir_name: "tile ir"
+ .sourceFileName: 2
+ .source_begin
+...intermediate IR text...
+.source_end
+ }
+}
+```
+
+`.ir_name` comes from `DILayerLoc`'s `kind:` and `.sourceFileName` is the file
+number the `.code_block` describes. `ptxas` stores that text keyed by the
+secondary file's *name*, so the name must be unique per file rather than
+human-readable: the backend uses the `DIFile`'s checksum digest when it has one
+and a hash of the file's path otherwise.
+
+The text comes from the layer `DIFile`'s `source:` field, and a layer is only
+emitted when that field is present. A `.loc_intermediate` pointing at a file with
+no `.code_block` is not accepted, so rather than emit a reference that cannot be
+resolved, a layer whose `DIFile` has no `source:` is dropped entirely — it
+contributes no `.loc_intermediate`, no `.file` entry, and no `.code_block`. A
+producer that wants intermediate positions in the output must therefore also
+carry the intermediate text.
+
+Layer positions are preserved across inlining and location merging. A layer
+attaches to the inline frame it originated from, and emission resolves an
+instruction to the nearest enclosing frame that carries one, so code inlined into
+a layered region is attributed to the enclosing layer position rather than
+losing its intermediate attribution.
+
(nvptx-arch-hierarchy)=
## NVPTX Architecture Hierarchy and Ordering
diff --git a/llvm/include/llvm/MC/MCObjectStreamer.h b/llvm/include/llvm/MC/MCObjectStreamer.h
index 7f5875c90d215..7497c17e3b03e 100644
--- a/llvm/include/llvm/MC/MCObjectStreamer.h
+++ b/llvm/include/llvm/MC/MCObjectStreamer.h
@@ -153,7 +153,8 @@ class LLVM_ABI MCObjectStreamer : public MCStreamer {
void emitDwarfLocDirective(unsigned FileNo, unsigned Line, unsigned Column,
unsigned Flags, unsigned Isa,
unsigned Discriminator, StringRef FileName,
- StringRef Comment = {}) override;
+ StringRef Comment = {},
+ StringRef LocOpcode = {}) override;
void emitDwarfAdvanceLineAddr(int64_t LineDelta, const MCSymbol *LastLabel,
const MCSymbol *Label,
unsigned PointerSize) override;
diff --git a/llvm/include/llvm/MC/MCStreamer.h b/llvm/include/llvm/MC/MCStreamer.h
index 614b8d79c5da4..78ced7b50b4f0 100644
--- a/llvm/include/llvm/MC/MCStreamer.h
+++ b/llvm/include/llvm/MC/MCStreamer.h
@@ -918,11 +918,17 @@ class LLVM_ABI MCStreamer {
/// This implements the DWARF2 '.loc fileno lineno ...' assembler
/// directive.
+ ///
+ /// \p LocOpcode selects the directive mnemonic, allowing a target to emit a
+ /// variant such as NVPTX's `.loc_intermediate`. It only affects textual
+ /// output: MCAsmStreamer prints it, while this implementation and
+ /// MCObjectStreamer record the location in the line table and ignore it, so a
+ /// non-default mnemonic is silently dropped when not emitting assembly.
virtual void emitDwarfLocDirective(unsigned FileNo, unsigned Line,
unsigned Column, unsigned Flags,
unsigned Isa, unsigned Discriminator,
- StringRef FileName,
- StringRef Comment = {});
+ StringRef FileName, StringRef Comment = {},
+ StringRef LocOpcode = ".loc");
/// This is same as emitDwarfLocDirective, except it has the capability to
/// add inlined_at information.
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
index 88e75e3c96106..f0ceb29b984b7 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
@@ -2245,6 +2245,10 @@ void DwarfDebug::beginInstruction(const MachineInstr *MI) {
// Reinstate the source location but not marked as a statement.
RecordSourceLine(DL, Flags);
}
+ // No new line-table row is needed for an unchanged primary location, but
+ // a target may still have to emit directives derived from operands the
+ // line table ignores.
+ recordTargetSameSourceLine(DL, Flags);
return;
}
}
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h
index 73285711d0aa9..f8065fef7edaa 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.h
@@ -734,6 +734,12 @@ class DwarfDebug : public DebugHandlerBase {
/// Target-specific source line recording.
virtual void recordTargetSourceLine(const DebugLoc &DL, unsigned Flags);
+ /// Target-specific recording for an instruction whose primary source location
+ /// matches the previous instruction's, so the line table needs no new row. A
+ /// target that emits directives derived from location operands the line table
+ /// ignores can still emit them here.
+ virtual void recordTargetSameSourceLine(const DebugLoc &DL, unsigned Flags) {}
+
/// Target-specific compile unit attribute finalization.
virtual void finishTargetUnitAttributes(const DICompileUnit &DIUnit,
DwarfCompileUnit &NewCU) {}
diff --git a/llvm/lib/MC/MCAsmStreamer.cpp b/llvm/lib/MC/MCAsmStreamer.cpp
index 2dd964edb738a..9631cc636cad8 100644
--- a/llvm/lib/MC/MCAsmStreamer.cpp
+++ b/llvm/lib/MC/MCAsmStreamer.cpp
@@ -409,7 +409,8 @@ class MCAsmStreamer final : public MCAsmBaseStreamer {
void emitDwarfLocDirective(unsigned FileNo, unsigned Line, unsigned Column,
unsigned Flags, unsigned Isa,
unsigned Discriminator, StringRef FileName,
- StringRef Location = {}) override;
+ StringRef Location = {},
+ StringRef LocOpcode = ".loc") override;
void emitDwarfLocLabelDirective(SMLoc Loc, StringRef Name) override;
/// This is same as emitDwarfLocDirective, except also emits inlined function
@@ -1894,8 +1895,8 @@ void MCAsmStreamer::emitDwarfLocDirectiveSuffix(unsigned FileNo, unsigned Line,
void MCAsmStreamer::emitDwarfLocDirective(unsigned FileNo, unsigned Line,
unsigned Column, unsigned Flags,
unsigned Isa, unsigned Discriminator,
- StringRef FileName,
- StringRef Comment) {
+ StringRef FileName, StringRef Comment,
+ StringRef LocOpcode) {
// If target doesn't support .loc/.file directive, we need to record the lines
// same way like we do in object mode.
if (MAI->isAIX()) {
@@ -1908,7 +1909,7 @@ void MCAsmStreamer::emitDwarfLocDirective(unsigned FileNo, unsigned Line,
}
// Emit the basic .loc directive.
- OS << "\t.loc\t" << FileNo << " " << Line << " " << Column;
+ OS << "\t" << LocOpcode << "\t" << FileNo << " " << Line << " " << Column;
// Emit common suffix (flags, comment, EOL, parent call).
emitDwarfLocDirectiveSuffix(FileNo, Line, Column, Flags, Isa, Discriminator,
diff --git a/llvm/lib/MC/MCObjectStreamer.cpp b/llvm/lib/MC/MCObjectStreamer.cpp
index 7a64fbbd66712..35f7e98869bf9 100644
--- a/llvm/lib/MC/MCObjectStreamer.cpp
+++ b/llvm/lib/MC/MCObjectStreamer.cpp
@@ -514,12 +514,10 @@ void MCObjectStreamer::emitInstToFragment(const MCInst &Inst,
newFragment();
}
-void MCObjectStreamer::emitDwarfLocDirective(unsigned FileNo, unsigned Line,
- unsigned Column, unsigned Flags,
- unsigned Isa,
- unsigned Discriminator,
- StringRef FileName,
- StringRef Comment) {
+void MCObjectStreamer::emitDwarfLocDirective(
+ unsigned FileNo, unsigned Line, unsigned Column, unsigned Flags,
+ unsigned Isa, unsigned Discriminator, StringRef FileName, StringRef Comment,
+ StringRef LocOpcode) {
// In case we see two .loc directives in a row, make sure the
// first one gets a line entry.
MCDwarfLineEntry::make(this, getCurrentSectionOnly());
diff --git a/llvm/lib/MC/MCStreamer.cpp b/llvm/lib/MC/MCStreamer.cpp
index 1d51fbc46a43c..5551b0491ad20 100644
--- a/llvm/lib/MC/MCStreamer.cpp
+++ b/llvm/lib/MC/MCStreamer.cpp
@@ -235,7 +235,8 @@ void MCStreamer::emitCFIMTETaggedFrame() {
void MCStreamer::emitDwarfLocDirective(unsigned FileNo, unsigned Line,
unsigned Column, unsigned Flags,
unsigned Isa, unsigned Discriminator,
- StringRef FileName, StringRef Comment) {
+ StringRef FileName, StringRef Comment,
+ StringRef LocOpcode) {
getContext().setCurrentDwarfLoc(FileNo, Line, Column, Flags, Isa,
Discriminator);
}
diff --git a/llvm/lib/Target/NVPTX/NVPTXAsmPrinter.cpp b/llvm/lib/Target/NVPTX/NVPTXAsmPrinter.cpp
index 9f8051ebc2ed8..006062e88fc2d 100644
--- a/llvm/lib/Target/NVPTX/NVPTXAsmPrinter.cpp
+++ b/llvm/lib/Target/NVPTX/NVPTXAsmPrinter.cpp
@@ -1356,6 +1356,14 @@ bool NVPTXAsmPrinter::doFinalization(Module &M) {
GlobalsEmitted = true;
}
+ // Take the intermediate files while the DwarfDebug is still alive
+ // (AsmPrinter::doFinalization destroys it). Only the borrowed descriptors
+ // move; the source text stays in metadata and is streamed below, after the
+ // .file directives, so ptxas can resolve .sourceFileName references.
+ NVPTXDwarfDebug::IntermediateFileVec IntermediateFiles;
+ if (auto *NVDD = static_cast<NVPTXDwarfDebug *>(getDwarfDebug()))
+ IntermediateFiles = NVDD->takeIntermediateFiles();
+
// call doFinalization
bool ret = AsmPrinter::doFinalization(M);
@@ -1373,6 +1381,23 @@ bool NVPTXAsmPrinter::doFinalization(Module &M) {
// Output last DWARF .file directives, if any.
TS->outputDwarfFileDirectives();
+ // Every recorded file has source text (recordIntermediateLoc drops the
+ // others), so each entry produces a .code_block. Each block streams straight
+ // from metadata rather than through an owning copy of the whole payload.
+ if (!IntermediateFiles.empty()) {
+ OutStreamer->emitRawText(".nv_intermediate_source_section {");
+ for (const auto &[FileNo, Info] : IntermediateFiles) {
+ OutStreamer->emitRawText(" .code_block {");
+ OutStreamer->emitRawText(Twine(" .ir_name: \"") + Info.Kind + "\"");
+ OutStreamer->emitRawText(Twine(" .sourceFileName: ") + Twine(FileNo));
+ OutStreamer->emitRawText(" .source_begin");
+ OutStreamer->emitRawText(Info.Source);
+ OutStreamer->emitRawText(".source_end");
+ OutStreamer->emitRawText(" }");
+ }
+ OutStreamer->emitRawText("}");
+ }
+
return ret;
}
diff --git a/llvm/lib/Target/NVPTX/NVPTXDwarfDebug.cpp b/llvm/lib/Target/NVPTX/NVPTXDwarfDebug.cpp
index 283641d1bf516..bd5ab4c53b85f 100644
--- a/llvm/lib/Target/NVPTX/NVPTXDwarfDebug.cpp
+++ b/llvm/lib/Target/NVPTX/NVPTXDwarfDebug.cpp
@@ -23,7 +23,9 @@
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/MD5.h"
#include "llvm/Support/NVPTXAddrSpace.h"
+#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetMachine.h"
using namespace llvm;
@@ -188,11 +190,103 @@ void NVPTXDwarfDebug::recordTargetSourceLine(const DebugLoc &DL,
if (EnhancedLineinfo)
EmittedInlinedAtLocs.insert(Current);
}
+
+ recordIntermediateLoc(DL, Flags);
+}
+
+void NVPTXDwarfDebug::recordTargetSameSourceLine(const DebugLoc &DL,
+ unsigned Flags) {
+ recordIntermediateLoc(DL, Flags, /*SkipIfUnchanged=*/true);
+}
+
+void NVPTXDwarfDebug::recordIntermediateLoc(const DebugLoc &DL, unsigned Flags,
+ bool SkipIfUnchanged) {
+ // Intermediate-IR layers live on the frame that was outermost when the tile
+ // snapshot was taken; after later inlining that frame can sit anywhere in the
+ // inlined-at chain. Walk outward from the head and use the first (innermost)
+ // frame that carries layers: it is the most specific intermediate origin for
+ // this PC -- the instruction actually being executed -- and for layer-less
+ // inlined code it is the nearest enclosing frame (continuous coverage).
+ const DILocation *Loc = DL.get();
+ while (Loc && !Loc->getRawIRLayers())
+ Loc = Loc->getInlinedAt();
+ if (!Loc) {
+ // Nothing is current any more, so the next layered instruction re-emits
+ // even if it repeats an earlier list.
+ PrevIRLayers = nullptr;
+ return;
+ }
+ DILayerLocList *Layers = Loc->getIRLayers();
+ // The comparison has to use the resolved list rather than DL's own operand:
+ // the walk above may have taken the layers from an enclosing frame, and two
+ // instructions with different head locations can share one list.
+ if (SkipIfUnchanged && Layers == PrevIRLayers)
+ return;
+ PrevIRLayers = Layers;
+ // Emit a secondary .loc_intermediate DWARF directive for each intermediate-IR
+ // layer. Each DILayerLoc carries its file/line/column directly (no scope and
+ // no discriminator).
+ const unsigned CUID = Asm->OutStreamer->getContext().getDwarfCompileUnitID();
+ for (unsigned I = 0, E = Layers->getNumLayers(); I != E; ++I) {
+ // The verifier guarantees each entry is a non-null DILayerLoc whose file is
+ // a non-null DIFile, so no null checks are needed here.
+ const DILayerLoc *L = Layers->getLayer(I);
+ const unsigned Line = L->getLine();
+ const DIFile *OrigFile = L->getFile();
+ // Skip the DWARF "no line" sentinel (line 0 is valid IR, not a verifier
+ // invariant).
+ if (!Line)
+ continue;
+ // A .loc_intermediate is only meaningful alongside the intermediate source
+ // it points into: the consumer rejects a reference to a file that has no
+ // .code_block. So a layer whose file carries no source text is dropped here
+ // rather than emitting a dangling .loc_intermediate and .file entry.
+ std::optional<StringRef> Source = OrigFile->getSource();
+ if (!Source)
+ continue;
+ auto [CacheIt, Inserted] = IntermediateFileNums.try_emplace(OrigFile);
+ if (Inserted) {
+ // Secondary .file name. ptxas keys the .source text it stores in the
+ // cubin by this name, so it must be a unique hash rather than a readable
+ // path: the file's checksum digest if it has one, else a path hash.
+ SmallString<32> HashedName;
+ StringRef SecondaryFilename;
+ if (const auto Checksum = OrigFile->getChecksum()) {
+ SecondaryFilename = Checksum->Value;
+ } else {
+ MD5 Hash;
+ Hash.update(OrigFile->getDirectory());
+ Hash.update(OrigFile->getFilename());
+ MD5::MD5Result Result;
+ Hash.final(Result);
+ HashedName = Result.digest();
+ SecondaryFilename = HashedName;
+ }
+ DIFile *SecondaryFile = DIFile::get(Loc->getContext(), SecondaryFilename,
+ OrigFile->getDirectory());
+ CacheIt->second = static_cast<DwarfCompileUnit &>(*getUnits()[CUID])
+ .getOrCreateSourceID(SecondaryFile);
+ }
+ const unsigned FileNo = CacheIt->second;
+ const unsigned Col = L->getColumn();
+ Asm->OutStreamer->emitDwarfLocDirective(
+ FileNo, Line, Col, Flags, 0, /*Discriminator=*/0,
+ OrigFile->getFilename(), "", ".loc_intermediate");
+ // First encounter wins. DIFiles that collapse onto one number share content
+ // by checksum, so their source agrees; a differing kind spelling (possible
+ // across LTO-merged producers, not a verifier error) just takes the first.
+ IntermediateFiles.insert({FileNo, {*Source, L->getKind()}});
+ }
+}
+
+NVPTXDwarfDebug::IntermediateFileVec NVPTXDwarfDebug::takeIntermediateFiles() {
+ return IntermediateFiles.takeVector();
}
/// NVPTX-specific debug info initialization.
void NVPTXDwarfDebug::initializeTargetDebugInfo(const MachineFunction &MF) {
EmittedInlinedAtLocs.clear();
+ PrevIRLayers = nullptr;
}
// PTX does not support subtracting labels from the code section in the
diff --git a/llvm/lib/Target/NVPTX/NVPTXDwarfDebug.h b/llvm/lib/Target/NVPTX/NVPTXDwarfDebug.h
index 46b9a7e7a5206..b9b1df5629f20 100644
--- a/llvm/lib/Target/NVPTX/NVPTXDwarfDebug.h
+++ b/llvm/lib/Target/NVPTX/NVPTXDwarfDebug.h
@@ -17,9 +17,14 @@
#define LLVM_LIB_TARGET_NVPTX_NVPTXDWARFDEBUG_H
#include "../../CodeGen/AsmPrinter/DwarfCompileUnit.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include <utility>
namespace llvm {
+class DIFile;
/// NVPTX-specific DwarfDebug implementation.
///
@@ -58,8 +63,44 @@ class NVPTXDwarfDebug : public DwarfDebug {
DwarfCompileUnit &NewCU) override;
void initializeTargetDebugInfo(const MachineFunction &MF) override;
void recordTargetSourceLine(const DebugLoc &DL, unsigned Flags) override;
+ void recordTargetSameSourceLine(const DebugLoc &DL, unsigned Flags) override;
bool shouldAttachCompileUnitRanges() const override;
bool shouldEmitDwarfPubSections() const override { return false; }
+
+public:
+ /// Source code and IR kind, per intermediate .file number, for emission into
+ /// .code_block. Both strings are borrowed from metadata, which the
+ /// LLVMContext owns, so they stay valid after this DwarfDebug is destroyed.
+ struct IntermediateFileInfo {
+ StringRef Source;
+ StringRef Kind;
+ };
+
+ /// Hand off the intermediate files collected during line emission. The
+ /// caller emits them after the .file directives have been flushed, which is
+ /// past the point where AsmPrinter::doFinalization destroys this object --
+ /// hence the handoff rather than emitting from here.
+ using IntermediateFileVec =
+ SmallVector<std::pair<unsigned, IntermediateFileInfo>, 0>;
+ [[nodiscard]] IntermediateFileVec takeIntermediateFiles();
+
+private:
+ /// Emit secondary .loc_intermediate directives for the intermediate-IR
+ /// layers carried on a DebugLoc's irlayers operand. On the deduplicated path
+ /// the primary .loc was not re-emitted, so SkipIfUnchanged suppresses
+ /// directives for a layer list that is already current.
+ void recordIntermediateLoc(const DebugLoc &DL, unsigned Flags,
+ bool SkipIfUnchanged = false);
+
+ /// Layer list of the last emitted .loc_intermediate run.
+ const DILayerLocList *PrevIRLayers = nullptr;
+
+ MapVector<unsigned, IntermediateFileInfo> IntermediateFiles;
+
+ /// Secondary .file number for each intermediate DIFile already seen, so a
+ /// repeated layer skips rebuilding the derived DIFile and re-querying the
+ /// DWARF file table.
+ DenseMap<const DIFile *, unsigned> IntermediateFileNums;
};
} // end namespace llvm
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-checksum-secondary-file.ll b/llvm/test/DebugInfo/NVPTX/intermediate-checksum-secondary-file.ll
new file mode 100644
index 0000000000000..8d32ce5b68062
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-checksum-secondary-file.ll
@@ -0,0 +1,43 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;;
+;; For an intermediate-IR file that carries source, the secondary .file directive
+;; must use the DIFile's *carried* checksum digest as its name: NVPTX emit reads
+;; DIFile.getChecksum()->Value directly rather than recomputing a hash of the
+;; filename. The distinctive checksum "cafebabe..." below is deliberately NOT
+;; MD5("kernel.tileir"), so if emit ever went back to hashing the filename this
+;; test would fail.
+
+define dso_local void @test_kernel(ptr noundef %v) #0 !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ store ptr %v, ptr %v.addr, align 8, !dbg !20
+ ret void, !dbg !21
+}
+
+attributes #0 = { noinline optnone }
+
+;; The intermediate .loc references a file number...
+; CHECK: .loc_intermediate [[INTFILE:[0-9]+]] 42 5
+;; ... whose .file NAME is exactly the carried checksum value, prefixed by the
+;; intermediate DIFile's directory (".").
+; CHECK: .file [[INTFILE]] ".{{/|\\\\}}cafebabecafebabecafebabecafebabe"
+
+!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 file WITH source and a distinctive checksum that is NOT a hash of
+;; its name -- so the secondary .file name must be the carried checksum.
+!14 = !DIFile(filename: "kernel.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "cafebabecafebabecafebabecafebabe", source: "tile ir source")
+!30 = !DILayerLocList(!31)
+!31 = !DILayerLoc(line: 42, column: 5, file: !14, kind: "tile ir")
+
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !30)
+!21 = !DILocation(line: 3, column: 1, scope: !8)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-loc-crossmodule-shadow.ll b/llvm/test/DebugInfo/NVPTX/intermediate-loc-crossmodule-shadow.ll
new file mode 100644
index 0000000000000..5f4c7d541e5bd
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-loc-crossmodule-shadow.ll
@@ -0,0 +1,78 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda \
+; RUN: | FileCheck %s --implicit-check-not=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
+; RUN: --implicit-check-not=kernelA-snapshot-text
+;;
+;; Cross-module variant of the shadowing test: a
+;; separately-snapshotted kernelB is inlined POST-snapshot into a
+;; separately-snapshotted kernelA, so the two layer-bearing frames reference
+;; DIFFERENT intermediate DIFiles:
+;;
+;; kernelB : 60 (head) -- irlayers: B.tileir @ 200 (INNER, wins)
+;; -> kernelA : 20 -- irlayers: A.tileir @ 50 (OUTER, shadowed)
+;;
+;; the NVPTX backend stops at the first layer-bearing frame (the head here),
+;; so B.tileir is recorded/emitted and A.tileir is never touched -- proving the
+;; shadowed frame's whole DIFile (its .file entry AND its source) is absent, not
+;; just its line. Only the inlined op is present, so A.tileir appears solely on
+;; the shadowed frame; a native kernelA op would legitimately emit A.tileir, but
+;; that is out of scope for isolating the shadow.
+
+;; Primary .loc = head (kernelB, line 60); intermediate = kernelB's B.tileir layer.
+; CHECK: .loc [[SRC:[0-9]+]] 60 5
+; CHECK-NEXT: .loc_intermediate [[BINT:[0-9]+]] 200 3
+;; Only one .loc_intermediate: the shadowed kernelA A.tileir layer is not emitted.
+; CHECK-NOT: .loc_intermediate
+
+; CHECK: .file [[SRC]] "/k{{/|\\\\}}kernel.py"
+;; Only B.tileir's checksum-named .file appears; A.tileir's (aaaa...) is shadowed
+;; out entirely (see the --implicit-check-not patterns on the RUN line).
+; CHECK: .file [[BINT]] ".{{/|\\\\}}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+
+;; One code_block, for B.tileir only.
+; CHECK: .nv_intermediate_source_section {
+; CHECK-NEXT: .code_block {
+; CHECK-NEXT: .ir_name: "tile ir"
+; CHECK-NEXT: .sourceFileName: [[BINT]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: kernelB-snapshot-text
+; CHECK-NEXT: .source_end
+; CHECK-NEXT: }
+; CHECK-NEXT: }
+
+define dso_local ptx_kernel void @test_kernel(ptr noundef %v) #0 !dbg !6 {
+entry:
+ store ptr null, ptr %v, align 8, !dbg !9
+ ret void, !dbg !8
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "tile", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "kernel.py", directory: "/k")
+!2 = !{i32 7, !"Dwarf Version", i32 2}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !5)
+!5 = !{null}
+
+;; kernelA (the function) and kernelB (inlined into it post-snapshot).
+!6 = distinct !DISubprogram(name: "kernelA", scope: !1, file: !1, line: 10, type: !4, spFlags: DISPFlagDefinition, unit: !0)
+!7 = distinct !DISubprogram(name: "kernelB", scope: !1, file: !1, line: 40, type: !4, spFlags: DISPFlagDefinition, unit: !0)
+
+;; kernelB's OWN module snapshot (B.tileir) -- the INNER layer that wins.
+!14 = !DIFile(filename: "B.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", source: "kernelB-snapshot-text")
+!15 = !DILayerLoc(line: 200, column: 3, file: !14, kind: "tile ir")
+!16 = !DILayerLocList(!15)
+
+;; kernelA's OWN module snapshot (A.tileir) -- the OUTER layer, shadowed.
+!24 = !DIFile(filename: "A.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", source: "kernelA-snapshot-text")
+!25 = !DILayerLoc(line: 50, column: 1, file: !24, kind: "tile ir")
+!26 = !DILayerLocList(!25)
+
+;; Chain: kernelB op (head, B.tileir layer) inlinedAt kernelA call site
+;; (A.tileir layer, shadowed / outermost).
+!9 = !DILocation(line: 60, column: 5, scope: !7, inlinedAt: !10, irlayers: !16)
+!10 = !DILocation(line: 20, column: 1, scope: !6, irlayers: !26)
+!8 = !DILocation(line: 10, column: 1, scope: !6)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-loc-dedup-gate.ll b/llvm/test/DebugInfo/NVPTX/intermediate-loc-dedup-gate.ll
new file mode 100644
index 0000000000000..29ce9971be92f
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-loc-dedup-gate.ll
@@ -0,0 +1,70 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda -mcpu=sm_70 -mattr=+ptx72 \
+; RUN: | FileCheck %s
+
+;; Regression test for the DwarfDebug::beginInstruction dedup gate plus the
+;; recordTargetSameSourceLine hook.
+;;
+;; Two instructions share the same primary source coordinate (line 10, col 5,
+;; same scope) but carry DIFFERENT irlayers. With the old code,
+;; isSameSourceLocation compared getRawIRLayers(), so the dedup gate did NOT
+;; fire for the second instruction: the primary .loc was re-emitted and the
+;; second .loc_intermediate followed it.
+;;
+;; After this change isSameSourceLocation ignores irlayers. For the second
+;; instruction the dedup gate fires (same primary position), so the primary
+;; .loc is NOT re-emitted. The recordTargetSameSourceLine hook fires instead,
+;; giving NVPTX a chance to emit .loc_intermediate even though no new line-
+;; table row was needed. The third instruction (ret) shares the coordinate and
+;; has no layers; the hook fires, the walk finds nothing, and nothing is emitted.
+;;
+;; Expected shape:
+;; .loc 1 10 5 <- first instruction (add), primary
+;; .loc_intermediate … 100 <- first instruction, layer
+;; [no second .loc 1 10] <- dedup gate fires for mul
+;; .loc_intermediate … 200 <- second instruction, layer via hook
+
+target triple = "nvptx64-nvidia-cuda"
+
+define i32 @dedup_gate_demo(i32 %a, i32 %b) !dbg !5 {
+ %1 = add i32 %a, %b, !dbg !20
+ %2 = mul i32 %1, 3, !dbg !21
+ ret i32 %2, !dbg !100
+}
+
+; CHECK-LABEL: dedup_gate_demo
+;; First instruction (add): primary .loc plus its intermediate @ line 100.
+; CHECK: .loc 1 10 5
+; CHECK-NEXT: .loc_intermediate {{[0-9]+}} 100 1
+;; Second instruction (mul) shares the source coordinate, so the primary .loc is
+;; NOT repeated -- but its layer differs, so it still gets its own
+;; .loc_intermediate.
+; CHECK-NOT: .loc 1 10
+; CHECK: .loc_intermediate {{[0-9]+}} 200 1
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!0, !1}
+
+!0 = !{i32 2, !"Dwarf Version", i32 2}
+!1 = !{i32 2, !"Debug Info Version", i32 3}
+!2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3,
+ emissionKind: DebugDirectivesOnly)
+!3 = !DIFile(filename: "demo.c", directory: "/tmp")
+!4 = !DISubroutineType(types: !{})
+!5 = distinct !DISubprogram(name: "dedup_gate_demo", scope: !3, file: !3,
+ line: 1, type: !4, scopeLine: 1,
+ spFlags: DISPFlagDefinition, unit: !2)
+
+!10 = !DIFile(filename: "demo.tile.ir", directory: "/tmp", checksumkind: CSK_MD5, checksum: "dddddddddddddddddddddddddddddddd", source: "demo tile ir text")
+
+;; Two distinct intermediate layers on the shared source coordinate.
+!110 = !DILayerLoc(line: 100, column: 1, file: !10, kind: "tile ir")
+!111 = !DILayerLoc(line: 200, column: 1, file: !10, kind: "tile ir")
+!120 = !DILayerLocList(!110)
+!121 = !DILayerLocList(!111)
+
+;; Same source (line 10, col 5, scope !5) but different layer lists.
+!20 = !DILocation(line: 10, column: 5, scope: !5, irlayers: !120)
+!21 = !DILocation(line: 10, column: 5, scope: !5, irlayers: !121)
+
+;; Bare source location (no layers).
+!100 = !DILocation(line: 10, column: 5, scope: !5)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-loc-inline-shadow.ll b/llvm/test/DebugInfo/NVPTX/intermediate-loc-inline-shadow.ll
new file mode 100644
index 0000000000000..6deb80714e01c
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-loc-inline-shadow.ll
@@ -0,0 +1,85 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;;
+;; Two stacked layer-bearing frames in ONE inlined-at chain. The front end
+;; inlined funcB into funcA (snapshot on the funcA frame), and funcA was then
+;; inlined into kernelA -- which ALSO carried its own snapshot layer. So the
+;; store's chain is:
+;;
+;; funcB : 52 (head) -- no irlayers
+;; -> funcA : 38 -- irlayers: tile ir @ 45 (INNER, wins)
+;; -> kernelA : 15 -- irlayers: tile ir @ 12 (OUTER, shadowed)
+;;
+;; The producer disassembles the whole module into ONE frozen snapshot text, so both the
+;; funcA op (line 45) and the kernelA call op (line 12) index into the SAME
+;; intermediate DIFile -- they differ only by (line, col), not by file. The
+;; shadowing is therefore line-level: the NVPTX backend walks head -> outward
+;; to the FIRST (innermost) layer-bearing frame and stops, so the funcA line (45)
+;; is emitted and the kernelA line (12) never is. (Distinct intermediate DIFiles
+;; only arise across separately-snapshotted modules.)
+
+;; Primary .loc = the head (funcB, line 52); the winning intermediate layer is
+;; funcA's (line 45), read off a middle (non-head, non-outermost) frame.
+; CHECK: .loc [[SRC:[0-9]+]] 52 3
+; CHECK-NEXT: .loc_intermediate [[INT:[0-9]+]] 45 7
+;; Exactly one .loc_intermediate: the shadowed kernelA line (12) is not emitted.
+;; (A wrong outermost-frame walk would print `12 1` here; emitting both frames
+;; would add a second .loc_intermediate -- either way this fails.)
+; CHECK-NOT: .loc_intermediate
+
+; CHECK: .file [[SRC]] "/k{{/|\\\\}}kernel.py"
+;; A single intermediate .file (the shared module snapshot), named by its carried
+;; checksum digest (not MD5(filename)).
+; CHECK: .file [[INT]] ".{{/|\\\\}}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+
+;; One code_block for the one shared snapshot file.
+; CHECK: .nv_intermediate_source_section {
+; CHECK-NEXT: .code_block {
+; CHECK-NEXT: .ir_name: "tile ir"
+; CHECK-NEXT: .sourceFileName: [[INT]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: whole-module-tile-ir-snapshot
+; CHECK-NEXT: .source_end
+; CHECK-NEXT: }
+; CHECK-NEXT: }
+
+define dso_local ptx_kernel void @test_kernel(ptr noundef %v) #0 !dbg !6 {
+entry:
+ store ptr null, ptr %v, align 8, !dbg !9
+ ret void, !dbg !8
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "tile", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "kernel.py", directory: "/k")
+!2 = !{i32 7, !"Dwarf Version", i32 2}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !DISubroutineType(types: !5)
+!5 = !{null}
+
+;; Three source scopes: kernelA (the function), funcA, funcB.
+!6 = distinct !DISubprogram(name: "kernelA", scope: !1, file: !1, line: 10, type: !4, spFlags: DISPFlagDefinition, unit: !0)
+!7 = distinct !DISubprogram(name: "funcA", scope: !1, file: !1, line: 30, type: !4, spFlags: DISPFlagDefinition, unit: !0)
+!12 = distinct !DISubprogram(name: "funcB", scope: !1, file: !1, line: 50, type: !4, spFlags: DISPFlagDefinition, unit: !0)
+
+;; ONE whole-module tile-IR snapshot DIFile. Both frames' layers reference it,
+;; at different (line, col): funcA's op at 45:7, kernelA's call op at 12:1.
+!14 = !DIFile(filename: "module.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", source: "whole-module-tile-ir-snapshot")
+
+;; funcA's layer (the INNER layer that wins).
+!15 = !DILayerLoc(line: 45, column: 7, file: !14, kind: "tile ir")
+!16 = !DILayerLocList(!15)
+
+;; kernelA's layer (the OUTER layer that is SHADOWED) -- SAME file, different line.
+!25 = !DILayerLoc(line: 12, column: 1, file: !14, kind: "tile ir")
+!26 = !DILayerLocList(!25)
+
+;; The inlined-at chain: funcB head (no layer) -> funcA frame (layer 45) ->
+;; kernelA frame (layer 12, shadowed / outermost).
+!9 = !DILocation(line: 52, column: 3, scope: !12, inlinedAt: !10)
+!10 = !DILocation(line: 38, column: 7, scope: !7, inlinedAt: !11, irlayers: !16)
+!11 = !DILocation(line: 15, column: 1, scope: !6, irlayers: !26)
+!8 = !DILocation(line: 10, column: 1, scope: !6)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-loc-multi-layer.ll b/llvm/test/DebugInfo/NVPTX/intermediate-loc-multi-layer.ll
new file mode 100644
index 0000000000000..03e352c65504c
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-loc-multi-layer.ll
@@ -0,0 +1,62 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;;
+;; A DILocation may carry more than one layer, one per intermediate IR level the
+;; program was lowered through. Check that each entry emits its own
+;; .loc_intermediate, in list order and right after the primary .loc, and that
+;; each contributes its own .code_block.
+
+; CHECK: .loc [[SRC:[0-9]+]] 2 5
+; CHECK-NEXT: .loc_intermediate [[TILE:[0-9]+]] 100 1
+; CHECK-NEXT: .loc_intermediate [[GPU:[0-9]+]] 7 3
+
+;; Each layer file gets its own secondary .file, named by its checksum digest.
+; CHECK-DAG: .file [[TILE]] ".{{/|\\\\}}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+; CHECK-DAG: .file [[GPU]] ".{{/|\\\\}}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+
+;; Both levels appear in the source section, in first-reference order.
+; CHECK: .nv_intermediate_source_section {
+; CHECK-NEXT: .code_block {
+; CHECK-NEXT: .ir_name: "tile ir"
+; CHECK-NEXT: .sourceFileName: [[TILE]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: tile ir text
+; CHECK-NEXT: .source_end
+; CHECK-NEXT: }
+; CHECK-NEXT: .code_block {
+; CHECK-NEXT: .ir_name: "gpu ir"
+; CHECK-NEXT: .sourceFileName: [[GPU]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: gpu ir text
+; CHECK-NEXT: .source_end
+; CHECK-NEXT: }
+; CHECK-NEXT: }
+
+define dso_local ptx_kernel void @test_kernel(ptr noundef %v) #0 !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ store ptr %v, ptr %v.addr, align 8, !dbg !20
+ ret void, !dbg !21
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+!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}
+
+;; Two intermediate levels: tile IR lowered to GPU IR.
+!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")
+!16 = !DILayerLoc(line: 100, column: 1, file: !14, kind: "tile ir")
+!17 = !DILayerLoc(line: 7, column: 3, file: !15, kind: "gpu ir")
+!18 = !DILayerLocList(!16, !17)
+
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !18)
+!21 = !DILocation(line: 3, column: 1, scope: !8)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-loc.ll b/llvm/test/DebugInfo/NVPTX/intermediate-loc.ll
new file mode 100644
index 0000000000000..1a155fc94dfdd
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-loc.ll
@@ -0,0 +1,76 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;; [TODO] Re-enable once ptxas changes have landed.
+;; RUN-TODO: %if ptxas %{ llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | %ptxas-verify %}
+;;
+;; Test that DILocation.irlayers (multi-level line info) generates
+;; .loc_intermediate directives in PTX output: the primary source location is
+;; the DILocation, and each DILayerLoc entry emits a secondary directive.
+;; Instructions with only a source DILocation (no irlayers) do not emit one.
+
+; CHECK: .target sm_{{[0-9]+}}, debug
+
+; CHECK: .visible .func test_kernel
+
+;; First instruction (store ptr) is layered.
+; CHECK: .loc [[SRCFILE:[0-9]+]] 2 5
+; CHECK-NEXT: .loc_intermediate [[INTFILE:[0-9]+]] 100 10
+
+;; Second instruction (load ptr) has only a source DILocation.
+; CHECK: .loc [[SRCFILE]] 3 5
+; CHECK-NOT: .loc_intermediate
+
+;; Third instruction (load i32) is layered.
+; CHECK: .loc [[SRCFILE]] 5 5
+; CHECK-NEXT: .loc_intermediate [[INTFILE]] 100 10
+
+;; Fourth instruction (store i32) has only a source DILocation.
+; CHECK: .loc [[SRCFILE]] 6 5
+; CHECK-NOT: .loc_intermediate
+
+;; Fifth instruction (ret) is layered.
+; CHECK: .loc [[SRCFILE]] 4 1
+; CHECK-NEXT: .loc_intermediate [[INTFILE]] 100 10
+
+;; The .file declarations come after the function body.
+; CHECK: .file [[SRCFILE]] "/test{{/|\\\\}}test.cu"
+;; The secondary .file is named by the intermediate DIFile's checksum digest.
+; CHECK: .file [[INTFILE]] ".{{/|\\\\}}0123456789abcdef0123456789abcdef"
+
+define dso_local void @test_kernel(ptr noundef %v) #0 !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ %val = alloca i32, align 4
+ store ptr %v, ptr %v.addr, align 8, !dbg !20
+ %0 = load ptr, ptr %v.addr, align 8, !dbg !12
+ %1 = load i32, ptr %0, align 4, !dbg !23
+ store i32 %1, ptr %val, align 4, !dbg !19
+ ret void, !dbg !22
+}
+
+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}
+
+;; Intermediate-IR layer: one DILayerLoc, shared (uniqued) across all layered
+;; instructions via a single DILayerLocList.
+!14 = !DIFile(filename: "intermediate.ptx", directory: ".", checksumkind: CSK_MD5, checksum: "0123456789abcdef0123456789abcdef", source: "intermediate ptx text")
+!30 = !DILayerLocList(!31)
+!31 = !DILayerLoc(line: 100, column: 10, file: !14, kind: "TileIR")
+
+;; Source-only instruction locations (no irlayers).
+!12 = !DILocation(line: 3, column: 5, scope: !8)
+!19 = !DILocation(line: 6, column: 5, scope: !8)
+
+;; Layered instruction locations: primary source loc + irlayers.
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !30)
+!22 = !DILocation(line: 4, column: 1, scope: !8, irlayers: !30)
+!23 = !DILocation(line: 5, column: 5, scope: !8, irlayers: !30)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-source-section-determinism.ll b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-determinism.ll
new file mode 100644
index 0000000000000..4174a3163c241
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-determinism.ll
@@ -0,0 +1,64 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;;
+;; The .code_blocks in .nv_intermediate_source_section are emitted in a
+;; deterministic order: first-reference (insertion) order. The test pins that
+;; the ordering key is reference order, not the filename -- "bbb.tileir" is
+;; referenced first and "aaa.tileir" second, so bbb's code_block is emitted
+;; first even though "aaa" sorts earlier alphabetically.
+
+define dso_local ptx_kernel void @test_kernel(ptr noundef %v) #0 !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ store ptr %v, ptr %v.addr, align 8, !dbg !20
+ store ptr null, ptr %v.addr, align 8, !dbg !21
+ ret void, !dbg !22
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+;; bbb is referenced first, so it receives the lower .file number.
+; CHECK-DAG: .file [[FBBB:[0-9]+]] "{{.*}}22222222222222222222222222222222"
+; CHECK-DAG: .file [[FAAA:[0-9]+]] "{{.*}}11111111111111111111111111111111"
+
+; CHECK: .nv_intermediate_source_section {
+;; "bbb.tileir" is referenced first -> lower file number -> emitted first.
+; CHECK: .code_block {
+; CHECK-NEXT: .ir_name: "tile ir"
+; CHECK-NEXT: .sourceFileName: [[FBBB]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: bbb source line
+; CHECK-NEXT: .source_end
+;; "aaa.tileir" second (higher file number), despite sorting earlier by name.
+; CHECK: .code_block {
+; CHECK-NEXT: .ir_name: "tile ir"
+; CHECK-NEXT: .sourceFileName: [[FAAA]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: aaa source line
+; CHECK-NEXT: .source_end
+; CHECK: }
+
+!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}
+
+;; aaa.tileir: sorts first by filename; carries a distinct checksum + source.
+!14 = !DIFile(filename: "aaa.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "11111111111111111111111111111111", source: "aaa source line")
+!15 = !DILayerLoc(line: 10, column: 1, file: !14, kind: "tile ir")
+!16 = !DILayerLocList(!15)
+
+;; bbb.tileir: sorts second by filename.
+!24 = !DIFile(filename: "bbb.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "22222222222222222222222222222222", source: "bbb source line")
+!25 = !DILayerLoc(line: 20, column: 1, file: !24, kind: "tile ir")
+!26 = !DILayerLocList(!25)
+
+;; First instruction references bbb, second references aaa (reverse of sort).
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !26)
+!21 = !DILocation(line: 3, column: 5, scope: !8, irlayers: !16)
+!22 = !DILocation(line: 4, column: 1, scope: !8)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-source-section-empty.ll b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-empty.ll
new file mode 100644
index 0000000000000..361e08d7dd9cd
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-empty.ll
@@ -0,0 +1,45 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;;
+;; An instruction's DILayerLoc references an intermediate DIFile with NO source:
+;; text. A .loc_intermediate is only meaningful with the source it points into,
+;; so such a layer is dropped entirely rather than emitting a reference the
+;; consumer cannot resolve. With the only layer gone there is nothing to put in
+;; the source section either, so no section is emitted -- not even an empty stub.
+
+;; A non-empty PTX is still produced...
+; CHECK: .target sm_{{[0-9]+}}
+;; ... but the layer leaves no trace: no secondary location, no secondary .file,
+;; and no source section.
+; CHECK-NOT: .loc_intermediate
+; CHECK-NOT: cccccccccccccccccccccccccccccccc
+; CHECK-NOT: .nv_intermediate_source_section
+
+define dso_local void @no_intermediate_source(ptr noundef %v) #0 !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ store ptr %v, ptr %v.addr, align 8, !dbg !11
+ ret void, !dbg !12
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+!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: "no_intermediate_source", scope: !1, file: !1, line: 1, type: !9, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+!9 = !DISubroutineType(types: !10)
+!10 = !{null}
+
+;; Intermediate file: checksum present but NO source: text, so no code_block is
+;; built for it.
+!4 = !DIFile(filename: "tileIR_source.unused", directory: ".", checksumkind: CSK_MD5, checksum: "cccccccccccccccccccccccccccccccc")
+!5 = !DILayerLoc(line: 100, column: 10, file: !4, kind: "TileIR")
+!6 = !DILayerLocList(!5)
+
+;; The store references the source-less intermediate layer; the ret is source-only.
+!11 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !6)
+!12 = !DILocation(line: 4, column: 1, scope: !8)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-source-section-mixed.ll b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-mixed.ll
new file mode 100644
index 0000000000000..497819918fc7d
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-mixed.ll
@@ -0,0 +1,62 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;;
+;; Mixed intermediate files: one carries DIFile.source, the other only a checksum
+;; (no source). A .loc_intermediate is only meaningful with the source it points
+;; into -- the consumer rejects a reference to a file with no .code_block -- so
+;; the source-less layer is dropped ENTIRELY: no .loc_intermediate, no .file
+;; entry, no .code_block. The sourced layer is emitted as usual, named by its
+;; checksum digest. Contrast intermediate-source-section-empty.ll, where EVERY
+;; file is source-less and the section disappears with them.
+
+; CHECK: .loc_intermediate [[FA:[0-9]+]] 100 10
+; CHECK-NOT: .loc_intermediate
+; CHECK: .file [[FA]] ".{{/|\\\\}}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+; CHECK-NOT: .file
+
+;; The section holds EXACTLY ONE code_block -- the sourced file's. It closes
+;; right after it.
+; CHECK: .nv_intermediate_source_section {
+; CHECK-NEXT: .code_block {
+; CHECK-NEXT: .ir_name: "tile ir"
+; CHECK-NEXT: .sourceFileName: [[FA]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: aaa source line
+; CHECK-NEXT: .source_end
+; CHECK-NEXT: }
+; CHECK-NEXT: }
+
+define dso_local ptx_kernel void @test_kernel(ptr noundef %v) #0 !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ store ptr %v, ptr %v.addr, align 8, !dbg !20
+ store ptr null, ptr %v.addr, align 8, !dbg !21
+ ret void, !dbg !22
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+!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}
+
+;; Sourced intermediate file (referenced first -> lower .file number).
+!14 = !DIFile(filename: "aaa.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", source: "aaa source line")
+!15 = !DILayerLoc(line: 100, column: 10, file: !14, kind: "tile ir")
+!16 = !DILayerLocList(!15)
+
+;; Source-less intermediate file (checksum only, no source:).
+!24 = !DIFile(filename: "bbb.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
+!25 = !DILayerLoc(line: 200, column: 20, file: !24, kind: "tile ir")
+!26 = !DILayerLocList(!25)
+
+;; First instruction references the sourced file; second the source-less file.
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !16)
+!21 = !DILocation(line: 3, column: 5, scope: !8, irlayers: !26)
+!22 = !DILocation(line: 4, column: 1, scope: !8)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-source-section-no-checksum.ll b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-no-checksum.ll
new file mode 100644
index 0000000000000..7600cce432c87
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-no-checksum.ll
@@ -0,0 +1,51 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;;
+;; An intermediate file that carries source but NO checksum still needs a unique
+;; hash for its secondary .file name -- ptxas stores the .code_block's .source in
+;; the cubin keyed by that name -- so emission hashes the DIFile's directory and
+;; filename instead of using the path verbatim. Contrast
+;; intermediate-checksum-secondary-file.ll, where a checksum is present and that
+;; content-addressed digest is used instead.
+
+;; MD5(".kernel.tileir") == directory "." concatenated with filename.
+; CHECK-DAG: .loc_intermediate [[F:[0-9]+]] 42 5
+; CHECK-DAG: .file [[F]] ".{{/|\\\\}}f3c6d19eaf8d63898bcec70cb38e2482"
+
+;; ...and the source still reaches the section.
+; CHECK: .nv_intermediate_source_section {
+; CHECK-NEXT: .code_block {
+; CHECK-NEXT: .ir_name: "tile ir"
+; CHECK-NEXT: .sourceFileName: [[F]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: tile ir source
+; CHECK-NEXT: .source_end
+; CHECK-NEXT: }
+; CHECK-NEXT: }
+
+define dso_local ptx_kernel void @test_kernel(ptr noundef %v) #0 !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ store ptr %v, ptr %v.addr, align 8, !dbg !20
+ ret void, !dbg !21
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+!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}
+
+;; Source present, checksum absent.
+!14 = !DIFile(filename: "kernel.tileir", directory: ".", source: "tile ir source")
+!30 = !DILayerLocList(!31)
+!31 = !DILayerLoc(line: 42, column: 5, file: !14, kind: "tile ir")
+
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !30)
+!21 = !DILocation(line: 3, column: 1, scope: !8)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-source-section-shared-filenum.ll b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-shared-filenum.ll
new file mode 100644
index 0000000000000..4b1b5eb31e4dc
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-source-section-shared-filenum.ll
@@ -0,0 +1,58 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;;
+;; Two intermediate DIFiles with identical content under different filenames
+;; collapse onto one emitted .file number (a sourced file is named by its
+;; checksum digest). Check the source section emits exactly one .code_block
+;; for that number.
+
+;; Both layers resolve to the same .file number, so both .loc_intermediate
+;; directives reference it, and only one .file directive is emitted for it.
+; CHECK-DAG: .loc_intermediate [[F:[0-9]+]] 100 10
+; CHECK-DAG: .loc_intermediate [[F]] 200 20
+; CHECK-DAG: .file [[F]] ".{{/|\\\\}}cccccccccccccccccccccccccccccccc"
+
+;; EXACTLY ONE code_block: the section closes immediately after it.
+; CHECK: .nv_intermediate_source_section {
+; CHECK-NEXT: .code_block {
+; CHECK-NEXT: .ir_name: "tile ir"
+; CHECK-NEXT: .sourceFileName: [[F]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: shared source line
+; CHECK-NEXT: .source_end
+; CHECK-NEXT: }
+; CHECK-NEXT: }
+
+define dso_local ptx_kernel void @test_kernel(ptr noundef %v) #0 !dbg !8 {
+entry:
+ %v.addr = alloca ptr, align 8
+ store ptr %v, ptr %v.addr, align 8, !dbg !20
+ store ptr null, ptr %v.addr, align 8, !dbg !21
+ ret void, !dbg !22
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+!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}
+
+;; Two DIFile nodes differing ONLY in filename -- same directory, same checksum,
+;; same source -- so they are distinct metadata but name the same content.
+!14 = !DIFile(filename: "a.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "cccccccccccccccccccccccccccccccc", source: "shared source line")
+!15 = !DILayerLoc(line: 100, column: 10, file: !14, kind: "tile ir")
+!16 = !DILayerLocList(!15)
+
+!24 = !DIFile(filename: "b.tileir", directory: ".", checksumkind: CSK_MD5, checksum: "cccccccccccccccccccccccccccccccc", source: "shared source line")
+!25 = !DILayerLoc(line: 200, column: 20, file: !24, kind: "tile ir")
+!26 = !DILayerLocList(!25)
+
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !16)
+!21 = !DILocation(line: 3, column: 5, scope: !8, irlayers: !26)
+!22 = !DILocation(line: 4, column: 1, scope: !8)
diff --git a/llvm/test/DebugInfo/NVPTX/intermediate-source-section.ll b/llvm/test/DebugInfo/NVPTX/intermediate-source-section.ll
new file mode 100644
index 0000000000000..4a657801f0b1c
--- /dev/null
+++ b/llvm/test/DebugInfo/NVPTX/intermediate-source-section.ll
@@ -0,0 +1,81 @@
+; RUN: llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+;; TODO: Enable once PTXAS changes land.
+;; RUN-TODO: %if ptxas %{ llc -O0 < %s -mtriple=nvptx64-nvidia-cuda | %ptxas-verify %}
+;;
+;; Test that intermediate DIFile.source text generates the
+;; .nv_intermediate_source_section in PTX output. This section contains the
+;; high-level source code from intermediate representations like TileIR.
+;;
+;; The layers live on the DILocation's `irlayers` operand: each
+;; instruction's DILocation carries a DILayerLocList of DILayerLoc entries that
+;; reference the intermediate DIFile. The code_block for a declared source file
+;; is emitted only when some instruction's layer references that file (see
+;; intermediate-source-section-empty.ll for the skip case).
+
+;; Check that .file directives are emitted for the intermediate source files.
+;; The secondary .file name is the intermediate DIFile's carried checksum digest.
+; CHECK-DAG: .file [[FILE123:[0-9]+]] "{{.*}}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+; CHECK-DAG: .file [[FILE456:[0-9]+]] "{{.*}}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
+
+;; Check that the intermediate source section is emitted
+; CHECK: .nv_intermediate_source_section {
+
+;; First code block with TileIR - sourceFileName matches the file number
+; CHECK: .code_block {
+; CHECK-NEXT: .ir_name: "TileIR"
+; CHECK-NEXT: .sourceFileName: [[FILE123]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: %0 = memref.load %arg0[] : memref<f32>
+; CHECK-NEXT: .source_end
+
+;; Second code block with TileIR - sourceFileName matches the file number
+; CHECK: .code_block {
+; CHECK-NEXT: .ir_name: "TileIR"
+; CHECK-NEXT: .sourceFileName: [[FILE456]]
+; CHECK-NEXT: .source_begin
+; CHECK-NEXT: memref.store %0, %arg1[] : memref<f32>
+; CHECK-NEXT: .source_end
+
+;; Close the second code_block, then the section itself.
+; CHECK-NEXT: }
+; CHECK-NEXT: }
+
+define dso_local ptx_kernel void @test_kernel(ptr noundef %v) #0 !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
+ store ptr %0, ptr %v.addr, align 8, !dbg !22
+ ret void, !dbg !23
+}
+
+attributes #0 = { noinline optnone "target-cpu"="sm_75" }
+
+!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}
+
+;; High-level source file reference for tileIR_source.123, with its shared layer.
+!15 = !DIFile(filename: "tileIR_source.123", directory: ".", checksumkind: CSK_MD5, checksum: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", source: "%0 = memref.load %arg0[] : memref<f32>")
+!17 = !DILayerLoc(line: 100, column: 10, file: !15, kind: "TileIR")
+!18 = !DILayerLocList(!17)
+
+;; High-level source file reference for tileIR_source.456, with its shared layer.
+!24 = !DIFile(filename: "tileIR_source.456", directory: ".", checksumkind: CSK_MD5, checksum: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", source: "memref.store %0, %arg1[] : memref<f32>")
+!26 = !DILayerLoc(line: 200, column: 15, file: !24, kind: "TileIR")
+!27 = !DILayerLocList(!26)
+
+;; Instruction locations: source DILocation + irlayers.
+;; First two instructions reference tileIR_source.123
+!20 = !DILocation(line: 2, column: 5, scope: !8, irlayers: !18)
+!21 = !DILocation(line: 3, column: 5, scope: !8, irlayers: !18)
+;; Last two instructions reference tileIR_source.456
+!22 = !DILocation(line: 4, column: 5, scope: !8, irlayers: !27)
+!23 = !DILocation(line: 5, column: 1, scope: !8, irlayers: !27)
More information about the llvm-commits
mailing list