[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 &registry, 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