[Mlir-commits] [mlir] [MLIR][DebugInfo] Add ArtificialLoc: a built-in location for compiler-generated instructions (PR #215898)
Laxman Sole
llvmlistbot at llvm.org
Wed Aug 12 14:05:40 PDT 2026
https://github.com/laxmansole created https://github.com/llvm/llvm-project/pull/215898
This change adds the `ArtificialLoc` built-in MLIR location attribute for compiler-generated instructions.
When translated to LLVM IR, it produces a `DILocation(line: 0, scope: <enclosing>)`.
If no enclosing scope is available, the instruction carries no debug metadata, mirroring `UnknownLoc`.
Implementation follows the `UnknownLoc` implementation pattern throughout.
Co-authored with Claude code.
>From 2cdfe06bcb45319a2b66028613c5242d4bb6e4ce Mon Sep 17 00:00:00 2001
From: Laxman Sole <lsole at nvidia.com>
Date: Wed, 12 Aug 2026 10:25:46 -0700
Subject: [PATCH] [MLIR] Add ArtificialLoc: a built-in location for
compiler-generated instructions (DWARF line=0)
---
.../mlir/IR/BuiltinLocationAttributes.td | 36 +++++++++++++++
mlir/lib/AsmParser/LocationParser.cpp | 7 +++
mlir/lib/IR/AsmPrinter.cpp | 6 +++
mlir/lib/IR/MLIRContext.cpp | 7 +++
mlir/lib/Target/LLVMIR/DebugTranslation.cpp | 9 ++++
mlir/test/IR/artificial-loc.mlir | 20 +++++++++
mlir/test/Target/LLVMIR/artificial-loc.mlir | 44 +++++++++++++++++++
7 files changed, 129 insertions(+)
create mode 100644 mlir/test/IR/artificial-loc.mlir
create mode 100644 mlir/test/Target/LLVMIR/artificial-loc.mlir
diff --git a/mlir/include/mlir/IR/BuiltinLocationAttributes.td b/mlir/include/mlir/IR/BuiltinLocationAttributes.td
index fe4e61100872f..85b6675770c91 100644
--- a/mlir/include/mlir/IR/BuiltinLocationAttributes.td
+++ b/mlir/include/mlir/IR/BuiltinLocationAttributes.td
@@ -308,6 +308,42 @@ def OpaqueLoc : Builtin_LocationAttr<"OpaqueLoc"> {
let attrName = "builtin.opaque_loc";
}
+//===----------------------------------------------------------------------===//
+// ArtificialLoc
+//===----------------------------------------------------------------------===//
+
+def ArtificialLoc : Builtin_LocationAttr<"ArtificialLoc"> {
+ let summary = "A location for compiler-generated instructions with no source correspondence";
+ let description = [{
+ Syntax:
+
+ ```
+ artificial-location ::= `artificial`
+ ```
+
+ Represents an instruction that is compiler-generated and cannot be
+ attributed to any position in the original source code. The DWARF
+ specification allows the compiler to use the special line number 0 to
+ indicate code that cannot be attributed to any source location. When
+ translated to LLVM IR this produces a `DILocation` with `line=0, column=0`
+ and the enclosing scope. If no scope is available the instruction carries
+ no debug location.
+
+ `ArtificialLoc` expresses intentional absence of source attribution, in
+ contrast to `UnknownLoc` which expresses that the source location is
+ ambiguous or has not yet been determined.
+
+ Example:
+ ```mlir
+ loc(artificial)
+ ```
+ }];
+ let extraClassDeclaration = [{
+ static ArtificialLoc get(MLIRContext *context);
+ }];
+ let attrName = "builtin.artificial_loc";
+}
+
//===----------------------------------------------------------------------===//
// UnknownLoc
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/AsmParser/LocationParser.cpp b/mlir/lib/AsmParser/LocationParser.cpp
index fb0999bed201d..af33c219f244b 100644
--- a/mlir/lib/AsmParser/LocationParser.cpp
+++ b/mlir/lib/AsmParser/LocationParser.cpp
@@ -226,6 +226,13 @@ ParseResult Parser::parseLocationInstance(LocationAttr &loc) {
if (getToken().getSpelling() == "fused")
return parseFusedLocation(loc);
+ // Check for 'artificial' - compiler-generated, no source correspondence.
+ if (getToken().getSpelling() == "artificial") {
+ consumeToken(Token::bare_identifier);
+ loc = ArtificialLoc::get(getContext());
+ return success();
+ }
+
// Check for a 'unknown' for an unknown location.
if (getToken().getSpelling() == "unknown") {
consumeToken(Token::bare_identifier);
diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp
index b95ab00bd5fdd..c75069519f9e6 100644
--- a/mlir/lib/IR/AsmPrinter.cpp
+++ b/mlir/lib/IR/AsmPrinter.cpp
@@ -2174,6 +2174,12 @@ void AsmPrinter::Impl::printLocationInternal(LocationAttr loc, bool pretty,
.Case([&](OpaqueLoc loc) {
printLocationInternal(loc.getFallbackLocation(), pretty);
})
+ .Case([&](ArtificialLoc loc) {
+ if (pretty)
+ os << "[artificial]";
+ else
+ os << "artificial";
+ })
.Case([&](UnknownLoc loc) {
if (pretty)
os << "[unknown]";
diff --git a/mlir/lib/IR/MLIRContext.cpp b/mlir/lib/IR/MLIRContext.cpp
index da891a7e6e014..d72ca32523b43 100644
--- a/mlir/lib/IR/MLIRContext.cpp
+++ b/mlir/lib/IR/MLIRContext.cpp
@@ -253,6 +253,7 @@ class MLIRContextImpl {
/// Cached Attribute Instances.
BoolAttr falseAttr, trueAttr;
UnitAttr unitAttr;
+ ArtificialLoc artificialLocAttr;
UnknownLoc unknownLocAttr;
DictionaryAttr emptyDictionaryAttr;
StringAttr emptyStringAttr;
@@ -335,6 +336,8 @@ MLIRContext::MLIRContext(const DialectRegistry ®istry, Threading setting)
//// Attributes.
//// Note: These must be registered after the types as they may generate one
//// of the above types internally.
+ /// Artificial Location Attribute (compiler-generated, DWARF line=0).
+ impl->artificialLocAttr = AttributeUniquer::get<ArtificialLoc>(this);
/// Unknown Location Attribute.
impl->unknownLocAttr = AttributeUniquer::get<UnknownLoc>(this);
/// Bool Attributes.
@@ -1152,6 +1155,10 @@ UnitAttr UnitAttr::get(MLIRContext *context) {
return context->getImpl().unitAttr;
}
+ArtificialLoc ArtificialLoc::get(MLIRContext *context) {
+ return context->getImpl().artificialLocAttr;
+}
+
UnknownLoc UnknownLoc::get(MLIRContext *context) {
return context->getImpl().unknownLocAttr;
}
diff --git a/mlir/lib/Target/LLVMIR/DebugTranslation.cpp b/mlir/lib/Target/LLVMIR/DebugTranslation.cpp
index 4dc9e91b4e1c2..536bcd083e147 100644
--- a/mlir/lib/Target/LLVMIR/DebugTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/DebugTranslation.cpp
@@ -602,6 +602,15 @@ llvm::DILocation *DebugTranslation::translateLoc(Location loc,
if (isa<UnknownLoc>(loc))
return nullptr;
+ // Compiler-generated instructions with no source position carry line number
+ // 0.
+ if (isa<ArtificialLoc>(loc)) {
+ if (!scope)
+ return nullptr;
+ return llvm::DILocation::get(llvmCtx, /*Line=*/0, /*Col=*/0, scope,
+ const_cast<llvm::DILocation *>(inlinedAt));
+ }
+
// Check for a cached instance.
auto existingIt = locationToLoc.find(std::make_tuple(loc, scope, inlinedAt));
if (existingIt != locationToLoc.end())
diff --git a/mlir/test/IR/artificial-loc.mlir b/mlir/test/IR/artificial-loc.mlir
new file mode 100644
index 0000000000000..b829bc8fcdb3d
--- /dev/null
+++ b/mlir/test/IR/artificial-loc.mlir
@@ -0,0 +1,20 @@
+// RUN: mlir-opt -allow-unregistered-dialect %s -mlir-print-debuginfo | FileCheck %s
+// RUN: mlir-opt -allow-unregistered-dialect %s -mlir-print-debuginfo | mlir-opt -allow-unregistered-dialect -mlir-print-debuginfo | FileCheck %s
+// Tests that ArtificialLoc round-trips correctly through the MLIR parser/printer.
+// Locations shared across multiple ops are printed as aliases; verify the alias
+// definition contains the expected keyword.
+
+// CHECK-DAG: = loc(artificial)
+// CHECK-DAG: = loc(unknown)
+
+func.func @artificial_loc_on_ops() -> i32 {
+ %0 = "test.op"() : () -> i32 loc(artificial)
+ return %0 : i32 loc(artificial)
+} loc(artificial)
+
+func.func @mix_with_unknown() {
+ // ArtificialLoc and UnknownLoc are distinct types.
+ "test.op"() : () -> () loc(artificial)
+ "test.op"() : () -> () loc(unknown)
+ return
+}
diff --git a/mlir/test/Target/LLVMIR/artificial-loc.mlir b/mlir/test/Target/LLVMIR/artificial-loc.mlir
new file mode 100644
index 0000000000000..930e127c7023f
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/artificial-loc.mlir
@@ -0,0 +1,44 @@
+// RUN: mlir-translate -mlir-to-llvmir --split-input-file %s | FileCheck %s
+
+// Verify that ArtificialLoc translates to DILocation(line: 0, column: 0).
+// The DWARF specification allows the compiler to use the special line number 0
+// to indicate code that cannot be attributed to any source location.
+
+// When a valid scope is available, ArtificialLoc produces a DILocation with
+// line=0 attached to the instruction, preserving the enclosing scope.
+//
+// CHECK-LABEL: define void @func_artificial_in_debug_scope
+// CHECK-SAME: !dbg ![[SP:[0-9]+]]
+// CHECK: call void @callee(), !dbg ![[ARTLOC:[0-9]+]]
+// CHECK-DAG: ![[ARTLOC]] = !DILocation(line: 0, scope: ![[SP]])
+
+#file = #llvm.di_file<"test.mlir" in "/test/">
+#cu = #llvm.di_compile_unit<
+ id = distinct[0]<>, sourceLanguage = DW_LANG_C, file = #file,
+ producer = "MLIR", isOptimized = false, emissionKind = Full>
+#spTy = #llvm.di_subroutine_type<callingConvention = DW_CC_normal>
+#sp = #llvm.di_subprogram<
+ id = distinct[1]<>, compileUnit = #cu, scope = #file,
+ name = "func_artificial_in_debug_scope", file = #file,
+ subprogramFlags = "Definition", type = #spTy>
+
+llvm.func @callee() {
+ llvm.return
+}
+
+llvm.func @func_artificial_in_debug_scope() {
+ llvm.call @callee() : () -> () loc(artificial)
+ llvm.return
+} loc(fused<#sp>["test.mlir":1:1])
+
+// -----
+
+// When no enclosing scope exists, ArtificialLoc produces no !dbg metadata,
+// consistent with the behaviour of loc(unknown).
+//
+// CHECK-LABEL: define void @func_artificial_no_scope()
+// CHECK-NOT: !dbg
+
+llvm.func @func_artificial_no_scope() {
+ llvm.return loc(artificial)
+} loc(artificial)
More information about the Mlir-commits
mailing list