[flang-commits] [flang] [mlir] [mlir][LLVMIR] Avoid duplicate retained local variables (PR #209911)

via flang-commits flang-commits at lists.llvm.org
Thu Jul 16 01:14:58 PDT 2026


https://github.com/qyingwu updated https://github.com/llvm/llvm-project/pull/209911

>From 818ce27934a2b483f0d0864243721b7492a55ddd Mon Sep 17 00:00:00 2001
From: qyingwu <qiyingwu at utexas.edu>
Date: Wed, 15 Jul 2026 14:58:04 -0700
Subject: [PATCH] [mlir][LLVMIR] Avoid duplicate retained local variables

Use recursive DILocalVariableAttr for retained locals that also appear in debug intrinsics. This avoids importing the same LLVM metadata node twice.
---
 .../lib/Optimizer/Transforms/AddDebugInfo.cpp |  5 +-
 .../Transforms/DebugTypeGenerator.cpp         |  2 +-
 .../mlir/Dialect/LLVMIR/LLVMAttrDefs.td       | 26 ++++++++--
 .../Dialect/LLVMIR/LLVMDialectBytecode.td     |  4 +-
 mlir/lib/CAPI/Dialect/LLVM.cpp                |  7 +--
 mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp      | 19 ++++++++
 mlir/lib/Target/LLVMIR/DebugImporter.cpp      | 42 +++++++++++++----
 mlir/lib/Target/LLVMIR/DebugImporter.h        | 13 +++++
 mlir/lib/Target/LLVMIR/DebugTranslation.cpp   | 47 ++++++++++++++++++-
 mlir/lib/Target/LLVMIR/DebugTranslation.h     | 10 ++++
 mlir/lib/Target/LLVMIR/ModuleImport.cpp       | 33 +++++++++++++
 mlir/test/Target/LLVMIR/Import/debug-info.ll  | 33 +++++++++++++
 mlir/test/Target/LLVMIR/llvmir-debug.mlir     | 40 ++++++++++++++++
 13 files changed, 260 insertions(+), 21 deletions(-)

diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 7c0b32e48832e..6004e5509e6e6 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -363,9 +363,8 @@ void AddDebugInfoPass::handleLocalVariable(Op declOp, llvm::StringRef name,
   }
 
   auto localVarAttr = mlir::LLVM::DILocalVariableAttr::get(
-      context, scopeAttr, mlir::StringAttr::get(context, name), fileAttr,
-      getLineFromLoc(declOp.getLoc()), argNo, /* alignInBits*/ 0, tyAttr,
-      mlir::LLVM::DIFlags::Zero);
+      scopeAttr, name, fileAttr, getLineFromLoc(declOp.getLoc()), argNo,
+      /*alignInBits=*/0, tyAttr, mlir::LLVM::DIFlags::Zero);
   declOp->setLoc(builder.getFusedLoc({declOp->getLoc()}, localVarAttr));
 }
 
diff --git a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
index d45aee26470a7..b3f3186288685 100644
--- a/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
+++ b/flang/lib/Optimizer/Transforms/DebugTypeGenerator.cpp
@@ -114,7 +114,7 @@ mlir::LLVM::DILocalVariableAttr DebugTypeGenerator::generateArtificialVariable(
   }
   mlir::LLVM::DITypeAttr Ty = convertType(type, fileAttr, scope, declOp);
   auto lvAttr = mlir::LLVM::DILocalVariableAttr::get(
-      context, scope, name, fileAttr, /*line=*/0, /*argNo=*/0,
+      scope, name.getValue(), fileAttr, /*line=*/0, /*argNo=*/0,
       /*alignInBits=*/0, Ty, mlir::LLVM::DIFlags::Artificial);
   mlir::LLVM::DbgValueOp::create(builder, declOp.getLoc(), val, lvAttr,
                                  nullptr);
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
index 6e2d4628c6173..fb17f1efb7c94 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMAttrDefs.td
@@ -696,9 +696,14 @@ def LLVM_DILexicalBlockFile : LLVM_Attr<"DILexicalBlockFile", "di_lexical_block_
 //===----------------------------------------------------------------------===//
 
 def LLVM_DILocalVariableAttr : LLVM_Attr<"DILocalVariable", "di_local_variable",
-                                         /*traits=*/[], "DINodeAttr"> {
+                                         [LLVM_DIRecursiveTypeAttrInterface],
+                                         "DINodeAttr"> {
   let parameters = (ins
-    "DIScopeAttr":$scope,
+    // DIRecursiveTypeAttrInterface specific parameters.
+    OptionalParameter<"DistinctAttr">:$recId,
+    OptionalParameter<"bool">:$isRecSelf,
+    // DILocalVariableAttr specific parameters.
+    OptionalParameter<"DIScopeAttr">:$scope,
     OptionalParameter<"StringAttr">:$name,
     OptionalParameter<"DIFileAttr">:$file,
     OptionalParameter<"unsigned">:$line,
@@ -714,11 +719,24 @@ def LLVM_DILocalVariableAttr : LLVM_Attr<"DILocalVariable", "di_local_variable",
       "DITypeAttr":$type, "DIFlags":$flags
     ), [{
       MLIRContext *ctx = scope.getContext();
-      return $_get(ctx, scope, StringAttr::get(ctx, name), file, line,
-                   arg, alignInBits, type, flags);
+      return $_get(ctx, /*recId=*/nullptr, /*isRecSelf=*/false, scope,
+                   StringAttr::get(ctx, name), file, line, arg, alignInBits,
+                   type, flags);
     }]>
   ];
   let assemblyFormat = "`<` struct(params) `>`";
+  let extraClassDeclaration = [{
+    /// Requirements of DIRecursiveTypeAttrInterface.
+    /// @{
+
+    /// Get a copy of this attr but with the recursive ID set to `recId`.
+    DIRecursiveTypeAttrInterface withRecId(DistinctAttr recId);
+
+    /// Build a rec-self instance using the provided `recId`.
+    static DIRecursiveTypeAttrInterface getRecSelf(DistinctAttr recId);
+
+    /// @}
+  }];
 
   // Generate mnemonic alias for the attribute.
   let genMnemonicAlias = 1;
diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMDialectBytecode.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMDialectBytecode.td
index b25a8c8a9829e..ef7df3c204841 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMDialectBytecode.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMDialectBytecode.td
@@ -99,7 +99,9 @@ def DIFileAttr : DialectAttribute<(attr
 //===----------------------------------------------------------------------===//
 
 def DILocalVariableAttr : DialectAttribute<(attr
-  Attr<"DIScopeAttr">:$scope,
+  OptionalAttribute<"DistinctAttr">:$recId,
+  Bool:$isRecSelf,
+  OptionalAttribute<"DIScopeAttr">:$scope,
   OptionalAttribute<"StringAttr">:$name,
   OptionalAttribute<"DIFileAttr">:$file,
   VarInt:$line,
diff --git a/mlir/lib/CAPI/Dialect/LLVM.cpp b/mlir/lib/CAPI/Dialect/LLVM.cpp
index 8082de122e74e..d59fb3c86d903 100644
--- a/mlir/lib/CAPI/Dialect/LLVM.cpp
+++ b/mlir/lib/CAPI/Dialect/LLVM.cpp
@@ -414,9 +414,10 @@ MlirAttribute mlirLLVMDILocalVariableAttrGet(
     MlirAttribute diFile, unsigned int line, unsigned int arg,
     unsigned int alignInBits, MlirAttribute diType, int64_t flags) {
   return wrap(DILocalVariableAttr::get(
-      unwrap(ctx), cast<DIScopeAttr>(unwrap(scope)),
-      cast<StringAttr>(unwrap(name)), cast<DIFileAttr>(unwrap(diFile)), line,
-      arg, alignInBits, cast<DITypeAttr>(unwrap(diType)), DIFlags(flags)));
+      unwrap(ctx), /*recId=*/DistinctAttr{}, /*isRecSelf=*/false,
+      cast<DIScopeAttr>(unwrap(scope)), cast<StringAttr>(unwrap(name)),
+      cast<DIFileAttr>(unwrap(diFile)), line, arg, alignInBits,
+      cast<DITypeAttr>(unwrap(diType)), DIFlags(flags)));
 }
 
 MlirStringRef mlirLLVMDILocalVariableAttrGetName(void) {
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
index 266faeca4553b..7917ac8b1ff10 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMAttrs.cpp
@@ -325,6 +325,25 @@ void printExpressionArg(AsmPrinter &printer, uint64_t opcode,
   });
 }
 
+//===----------------------------------------------------------------------===//
+// DILocalVariableAttr
+//===----------------------------------------------------------------------===//
+
+DIRecursiveTypeAttrInterface
+DILocalVariableAttr::withRecId(DistinctAttr recId) {
+  return DILocalVariableAttr::get(
+      getContext(), recId, getIsRecSelf(), getScope(), getName(), getFile(),
+      getLine(), getArg(), getAlignInBits(), getType(), getFlags());
+}
+
+DIRecursiveTypeAttrInterface
+DILocalVariableAttr::getRecSelf(DistinctAttr recId) {
+  return DILocalVariableAttr::get(
+      recId.getContext(), recId, /*isRecSelf=*/true, /*scope=*/{},
+      /*name=*/{}, /*file=*/{}, /*line=*/0, /*arg=*/0, /*alignInBits=*/0,
+      /*type=*/{}, DIFlags::Zero);
+}
+
 //===----------------------------------------------------------------------===//
 // DICompositeTypeAttr
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/DebugImporter.cpp b/mlir/lib/Target/LLVMIR/DebugImporter.cpp
index 85df880d2712a..f8ccbc5bb6637 100644
--- a/mlir/lib/Target/LLVMIR/DebugImporter.cpp
+++ b/mlir/lib/Target/LLVMIR/DebugImporter.cpp
@@ -17,6 +17,7 @@
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/Metadata.h"
+#include "llvm/Support/SaveAndRestore.h"
 
 using namespace mlir;
 using namespace mlir::LLVM;
@@ -193,13 +194,21 @@ DebugImporter::translateImpl(llvm::DIGlobalVariable *node) {
 
 DILocalVariableAttr DebugImporter::translateImpl(llvm::DILocalVariable *node) {
   // Return nullptr if the scope or type is a cyclic dependency.
-  DIScopeAttr scope = translate(node->getScope());
+  DIScopeAttr scope;
+  {
+    bool preferLocalVariableCycleBreakForNode =
+        static_cast<bool>(nodeToRecId.lookup(node));
+    llvm::SaveAndRestore preferLocalVariableCycleBreakGuard(
+        preferLocalVariableCycleBreak, preferLocalVariableCycleBreakForNode);
+    scope = translate(node->getScope());
+  }
   if (node->getScope() && !scope)
     return nullptr;
   return DILocalVariableAttr::get(
-      context, scope, getStringAttrOrNull(node->getRawName()),
-      translate(node->getFile()), node->getLine(), node->getArg(),
-      node->getAlignInBits(), translate(node->getType()),
+      context, /*recId=*/DistinctAttr{}, /*isRecSelf=*/false, scope,
+      getStringAttrOrNull(node->getRawName()), translate(node->getFile()),
+      node->getLine(), node->getArg(), node->getAlignInBits(),
+      translate(node->getType()),
       symbolizeDIFlags(node->getFlags()).value_or(DIFlags::Zero));
 }
 
@@ -261,6 +270,11 @@ DISubprogramAttr DebugImporter::translateImpl(llvm::DISubprogram *node) {
   // Convert the retained nodes but drop all of them if one of them is invalid.
   SmallVector<Attribute> retainedNodes;
   auto add = [this, &retainedNodes](llvm::DINode *retainedNode) {
+    if (auto *local = dyn_cast<llvm::DILocalVariable>(retainedNode);
+        local && nodeToRecId.contains(local)) {
+      retainedNodes.push_back(*createRecSelf(local));
+      return;
+    }
     retainedNodes.push_back(translate(retainedNode));
   };
   auto addGVE = [](llvm::DIGlobalVariableExpression *GVE) {
@@ -446,6 +460,9 @@ DINodeAttr DebugImporter::translate(llvm::DINode *node) {
       DistinctAttr recId = nodeToRecId.lookup(node);
       auto recType = cast<DIRecursiveTypeAttrInterface>(attr);
       attr = cast<DINodeAttr>(recType.withRecId(recId));
+    } else if (DistinctAttr recId = nodeToRecId.lookup(node)) {
+      auto recType = cast<DIRecursiveTypeAttrInterface>(attr);
+      attr = cast<DINodeAttr>(recType.withRecId(recId));
     }
     cacheEntry.resolve(attr);
     return attr;
@@ -454,22 +471,31 @@ DINodeAttr DebugImporter::translate(llvm::DINode *node) {
   return nullptr;
 }
 
+void DebugImporter::markLocalVariableAsRecursive(llvm::DILocalVariable *node) {
+  if (!nodeToRecId.contains(node))
+    nodeToRecId[node] = DistinctAttr::create(UnitAttr::get(context));
+}
+
 /// Get the `getRecSelf` constructor for the translated node if it participates
-/// in CyclicReplacerCache cycle breaking (recursive composite types,
-/// subprograms, or compile units).
-static function_ref<DIRecursiveTypeAttrInterface(DistinctAttr)>
-getRecSelfConstructor(llvm::DINode *node) {
+/// in CyclicReplacerCache cycle breaking.
+function_ref<DIRecursiveTypeAttrInterface(DistinctAttr)>
+DebugImporter::getRecSelfConstructor(llvm::DINode *node) {
   using CtorType = function_ref<DIRecursiveTypeAttrInterface(DistinctAttr)>;
   return TypeSwitch<llvm::DINode *, CtorType>(node)
       .Case([&](llvm::DICompositeType *) {
         return CtorType(DICompositeTypeAttr::getRecSelf);
       })
       .Case([&](llvm::DISubprogram *) {
+        if (preferLocalVariableCycleBreak)
+          return CtorType();
         return CtorType(DISubprogramAttr::getRecSelf);
       })
       .Case([&](llvm::DICompileUnit *) {
         return CtorType(DICompileUnitAttr::getRecSelf);
       })
+      .Case([&](llvm::DILocalVariable *) {
+        return CtorType(DILocalVariableAttr::getRecSelf);
+      })
       .Default(CtorType());
 }
 
diff --git a/mlir/lib/Target/LLVMIR/DebugImporter.h b/mlir/lib/Target/LLVMIR/DebugImporter.h
index b224ad3376be7..40bf0b113e28f 100644
--- a/mlir/lib/Target/LLVMIR/DebugImporter.h
+++ b/mlir/lib/Target/LLVMIR/DebugImporter.h
@@ -47,6 +47,9 @@ class DebugImporter {
   /// Returns UnknownLoc if `func` has no debug information attached to it.
   Location translateFuncLocation(llvm::Function *func);
 
+  /// Marks the local variable as requiring a recursive representation.
+  void markLocalVariableAsRecursive(llvm::DILocalVariable *node);
+
   /// Translates the given LLVM debug metadata to MLIR.
   DINodeAttr translate(llvm::DINode *node);
 
@@ -92,6 +95,11 @@ class DebugImporter {
   /// for it, or create a new one if not.
   DistinctAttr getOrCreateDistinctID(llvm::DINode *node);
 
+  /// Get the `getRecSelf` constructor for the translated node if it
+  /// participates in CyclicReplacerCache cycle breaking.
+  function_ref<DIRecursiveTypeAttrInterface(DistinctAttr)>
+  getRecSelfConstructor(llvm::DINode *node);
+
   std::optional<DINodeAttr> createRecSelf(llvm::DINode *node);
 
   /// A mapping between distinct LLVM debug metadata nodes and the corresponding
@@ -103,6 +111,11 @@ class DebugImporter {
   /// same ID and be deduplicated.
   DenseMap<llvm::DINode *, DistinctAttr> nodeToRecId;
 
+  /// True while translating the scope of a DILocalVariable. This lets a
+  /// local-variable/subprogram cycle be broken at the local variable instead of
+  /// at the subprogram.
+  bool preferLocalVariableCycleBreak = false;
+
   CyclicReplacerCache<llvm::DINode *, DINodeAttr> cache;
 
   MLIRContext *context;
diff --git a/mlir/lib/Target/LLVMIR/DebugTranslation.cpp b/mlir/lib/Target/LLVMIR/DebugTranslation.cpp
index 442f5b6b137e6..473935fcf38d3 100644
--- a/mlir/lib/Target/LLVMIR/DebugTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/DebugTranslation.cpp
@@ -16,6 +16,7 @@
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Path.h"
+#include "llvm/Support/SaveAndRestore.h"
 
 using namespace mlir;
 using namespace mlir::LLVM;
@@ -329,6 +330,20 @@ DebugTranslation::translateImpl(DILocalVariableAttr attr) {
       /*Annotations=*/nullptr);
 }
 
+llvm::TempDILocalVariable
+DebugTranslation::translateTemporaryImpl(DILocalVariableAttr attr) {
+  llvm::DILocalScope *scope =
+      attr.getScope() ? cast<llvm::DILocalScope>(translate(attr.getScope()))
+                      : localVariablePlaceholderScope;
+  assert(scope && "expected local variable placeholder scope");
+  return llvm::DILocalVariable::getTemporary(
+      llvmCtx, scope, /*Name=*/"", /*File=*/nullptr, attr.getLine(),
+      /*Type=*/static_cast<llvm::DIType *>(nullptr), attr.getArg(),
+      static_cast<llvm::DINode::DIFlags>(attr.getFlags()),
+      attr.getAlignInBits(),
+      /*Annotations=*/nullptr);
+}
+
 llvm::DIGlobalVariable *
 DebugTranslation::translateImpl(DIGlobalVariableAttr attr) {
   return llvm::DIGlobalVariable::getDistinct(
@@ -345,6 +360,24 @@ DebugTranslation::translateRecursive(DIRecursiveTypeAttrInterface attr) {
       iter != recursiveNodeMap.end()) {
     return iter->second;
   }
+
+  if (auto localVariable = dyn_cast<DILocalVariableAttr>(attr)) {
+    if (localVariable.getIsRecSelf()) {
+      auto iter =
+          forwardLocalVariablePlaceholders
+              .try_emplace(recursiveId, translateTemporaryImpl(localVariable))
+              .first;
+      return iter->second.get();
+    }
+    if (auto iter = forwardLocalVariablePlaceholders.find(recursiveId);
+        iter != forwardLocalVariablePlaceholders.end()) {
+      llvm::DILocalVariable *concrete = translateImpl(localVariable);
+      iter->second->replaceAllUsesWith(concrete);
+      forwardLocalVariablePlaceholders.erase(iter);
+      return concrete;
+    }
+  }
+
   assert(!attr.getIsRecSelf() && "unbound DI recursive self reference");
 
   auto setRecursivePlaceholder = [&](llvm::DINode *placeholder) {
@@ -377,6 +410,13 @@ DebugTranslation::translateRecursive(DIRecursiveTypeAttrInterface attr) {
             auto *concrete = translateImpl(attr);
             temporary->replaceAllUsesWith(concrete);
             return concrete;
+          })
+          .Case([&](DILocalVariableAttr attr) {
+            auto temporary = translateTemporaryImpl(attr);
+            setRecursivePlaceholder(temporary.get());
+            auto *concrete = translateImpl(attr);
+            temporary->replaceAllUsesWith(concrete);
+            return concrete;
           });
 
   assert(recursiveNodeMap.back().first == recursiveId &&
@@ -417,10 +457,15 @@ llvm::DISubprogram *DebugTranslation::translateImpl(DISubprogramAttr attr) {
       /*ThisAdjustment=*/0, llvm::DINode::FlagZero,
       static_cast<llvm::DISubprogram::DISPFlags>(attr.getSubprogramFlags()),
       compileUnit, /*TemplateParams=*/nullptr, /*Declaration=*/nullptr,
-      getRetainedNodesOrNull(attr.getRetainedNodes()), nullptr,
+      /*RetainedNodes=*/nullptr, /*ThrownTypes=*/nullptr,
       getMDTupleOrNull(attr.getAnnotations()));
   if (attr.getId())
     distinctAttrToNode.try_emplace(attr.getId(), node);
+  if (!attr.getRetainedNodes().empty()) {
+    llvm::SaveAndRestore guard(localVariablePlaceholderScope,
+                               cast<llvm::DILocalScope>(node));
+    node->replaceRetainedNodes(getRetainedNodesOrNull(attr.getRetainedNodes()));
+  }
   return node;
 }
 
diff --git a/mlir/lib/Target/LLVMIR/DebugTranslation.h b/mlir/lib/Target/LLVMIR/DebugTranslation.h
index c1fdbd89087da..2db7f401f576d 100644
--- a/mlir/lib/Target/LLVMIR/DebugTranslation.h
+++ b/mlir/lib/Target/LLVMIR/DebugTranslation.h
@@ -105,6 +105,7 @@ class DebugTranslation {
   llvm::TempDICompositeType translateTemporaryImpl(DICompositeTypeAttr attr);
   llvm::TempDISubprogram translateTemporaryImpl(DISubprogramAttr attr);
   llvm::TempDICompileUnit translateTemporaryImpl(DICompileUnitAttr attr);
+  llvm::TempDILocalVariable translateTemporaryImpl(DILocalVariableAttr attr);
 
   /// Constructs a string metadata node from the string attribute. Returns
   /// nullptr if `stringAttr` is null or contains and empty string.
@@ -135,6 +136,15 @@ class DebugTranslation {
   /// A mapping between recursive ID and the translated DINode.
   llvm::MapVector<DistinctAttr, llvm::DINode *> recursiveNodeMap;
 
+  /// A mapping between recursive IDs and local-variable placeholders created
+  /// from forward self references in retainedNodes.
+  DenseMap<DistinctAttr, llvm::TempDILocalVariable>
+      forwardLocalVariablePlaceholders;
+
+  /// Scope to use for forward local-variable placeholders while translating a
+  /// DISubprogram's retainedNodes.
+  llvm::DILocalScope *localVariablePlaceholderScope = nullptr;
+
   /// A mapping between a distinct ID and the translated LLVM metadata node.
   /// This helps identify attrs that should translate into the same LLVM debug
   /// node.
diff --git a/mlir/lib/Target/LLVMIR/ModuleImport.cpp b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
index bb55678f91005..fc4ad29d2d747 100644
--- a/mlir/lib/Target/LLVMIR/ModuleImport.cpp
+++ b/mlir/lib/Target/LLVMIR/ModuleImport.cpp
@@ -35,6 +35,7 @@
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/IR/Comdat.h"
 #include "llvm/IR/Constants.h"
+#include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/DebugProgramInstruction.h"
 #include "llvm/IR/InlineAsm.h"
 #include "llvm/IR/InstIterator.h"
@@ -246,6 +247,38 @@ ModuleImport::ModuleImport(ModuleOp mlirModule,
       emitExpensiveWarnings(emitExpensiveWarnings),
       preferUnregisteredIntrinsics(preferUnregisteredIntrinsics) {
   builder.setInsertionPointToStart(mlirModule.getBody());
+
+  // Variables that are both retained and used by a debug intrinsic need to
+  // break cycles at the variable itself to avoid creating duplicate attrs with
+  // different subprogram scopes.
+  llvm::SmallPtrSet<llvm::DILocalVariable *, 8> retainedLocalVariables;
+  for (llvm::Function &func : *this->llvmModule) {
+    llvm::DISubprogram *subprogram = func.getSubprogram();
+    if (!subprogram)
+      continue;
+    auto addLocalVariable = [&](llvm::DILocalVariable *localVariable) {
+      retainedLocalVariables.insert(localVariable);
+    };
+    auto skip = [](auto *) {};
+    subprogram->forEachRetainedNode(addLocalVariable, skip, skip, skip, skip);
+  }
+
+  auto markIfRetained = [&](llvm::DILocalVariable *localVariable) {
+    if (localVariable && retainedLocalVariables.contains(localVariable))
+      debugImporter->markLocalVariableAsRecursive(localVariable);
+  };
+  for (llvm::Function &func : *this->llvmModule) {
+    for (llvm::Instruction &inst : llvm::instructions(func)) {
+      if (auto *dbgIntrinsic = dyn_cast<llvm::DbgVariableIntrinsic>(&inst))
+        markIfRetained(dbgIntrinsic->getVariable());
+      if (!inst.DebugMarker)
+        continue;
+      for (llvm::DbgRecord &dbgRecord : inst.DebugMarker->getDbgRecordRange())
+        if (auto *dbgVariableRecord =
+                dyn_cast<llvm::DbgVariableRecord>(&dbgRecord))
+          markIfRetained(dbgVariableRecord->getVariable());
+    }
+  }
 }
 
 ComdatOp ModuleImport::getGlobalComdatOp() {
diff --git a/mlir/test/Target/LLVMIR/Import/debug-info.ll b/mlir/test/Target/LLVMIR/Import/debug-info.ll
index 28c25809002cd..38b250f00b958 100644
--- a/mlir/test/Target/LLVMIR/Import/debug-info.ll
+++ b/mlir/test/Target/LLVMIR/Import/debug-info.ll
@@ -1046,3 +1046,36 @@ define void @fn_with_static_local() !dbg !3 {
 !12 = !DILocalVariable(name: "unused_local", scope: !3, file: !4, line: 122, type: !9)
 !13 = !DISubroutineType(types: !14)
 !14 = !{null}
+
+; // -----
+
+; Test that a DILocalVariable listed in DISubprogram's retainedNodes and used by
+; a debug intrinsic does not get duplicated after import.
+
+; CHECK-DAG: #[[LVAR_SELF:.+]] = #llvm.di_local_variable<recId = [[REC_ID:distinct.+<>]], isRecSelf = true>
+; CHECK-DAG: #[[SP:.+]] = #llvm.di_subprogram<{{.*}}name = "fn_with_retained_local"{{.*}}retainedNodes = [#[[LVAR_SELF]]]>
+; CHECK-DAG: #[[LVAR:.+]] = #llvm.di_local_variable<recId = [[REC_ID]], scope = #[[SP]], name = "n"{{.*}}>
+; CHECK-DAG-COUNT-2: {{#llvm\.di_local_variable<}}
+; CHECK: llvm.intr.dbg.value #[[LVAR]] = %{{.*}} : i32
+
+define void @fn_with_retained_local(i32 %n) !dbg !3 {
+entry:
+  call void @llvm.dbg.value(metadata i32 %n, metadata !12, metadata !DIExpression()), !dbg !15
+  ret void
+}
+
+declare void @llvm.dbg.value(metadata, metadata, metadata)
+
+!llvm.dbg.cu = !{!7}
+!llvm.module.flags = !{!10}
+
+!3 = distinct !DISubprogram(name: "fn_with_retained_local", scope: !4, file: !4, line: 1, type: !13, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !7, retainedNodes: !11)
+!4 = !DIFile(filename: "test.c", directory: "")
+!7 = distinct !DICompileUnit(language: DW_LANG_C, file: !4)
+!9 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !{i32 2, !"Debug Info Version", i32 3}
+!11 = !{!12}
+!12 = !DILocalVariable(name: "n", arg: 1, scope: !3, file: !4, line: 1, type: !9)
+!13 = !DISubroutineType(types: !14)
+!14 = !{null, !9}
+!15 = !DILocation(line: 1, column: 1, scope: !3)
diff --git a/mlir/test/Target/LLVMIR/llvmir-debug.mlir b/mlir/test/Target/LLVMIR/llvmir-debug.mlir
index 72dadb2602c52..77eade68ae9e3 100644
--- a/mlir/test/Target/LLVMIR/llvmir-debug.mlir
+++ b/mlir/test/Target/LLVMIR/llvmir-debug.mlir
@@ -421,6 +421,46 @@ llvm.func @imp_fn() {
 
 // -----
 
+// Test that a recursive local variable retained by a subprogram is exported
+// without duplicating the DILocalVariable metadata node.
+
+#file = #llvm.di_file<"test.c" in "">
+#cu = #llvm.di_compile_unit<
+  id = distinct[0]<>, sourceLanguage = DW_LANG_C, file = #file,
+  isOptimized = false, emissionKind = Full
+>
+#void = #llvm.di_null_type
+#void_type = #llvm.di_subroutine_type<types = #void>
+#int = #llvm.di_basic_type<
+  tag = DW_TAG_base_type, name = "int",
+  sizeInBits = 32, encoding = DW_ATE_signed
+>
+#local_self = #llvm.di_local_variable<recId = distinct[1]<>, isRecSelf = true>
+#sp = #llvm.di_subprogram<
+  id = distinct[2]<>, compileUnit = #cu, scope = #file,
+  name = "fn_with_retained_local", file = #file,
+  subprogramFlags = Definition, type = #void_type,
+  retainedNodes = [#local_self]
+>
+#local = #llvm.di_local_variable<
+  recId = distinct[1]<>, scope = #sp, name = "n", file = #file,
+  line = 1, arg = 1, type = #int
+>
+
+// CHECK-LABEL: define void @fn_with_retained_local(
+// CHECK-SAME: i32 %[[ARG:.*]]) !dbg ![[SP:[0-9]+]]
+llvm.func @fn_with_retained_local(%arg0: i32) {
+  // RECORDS: #dbg_value(i32 %[[ARG]], ![[LOCAL:[0-9]+]], !DIExpression(), !{{.*}})
+  llvm.intr.dbg.value #local = %arg0 : i32 loc(fused<#sp>["test.c":1:1])
+  llvm.return
+} loc(fused<#sp>["test.c":1:1])
+
+// CHECK-DAG: ![[SP]] = distinct !DISubprogram(name: "fn_with_retained_local"{{.*}}retainedNodes: ![[NODES:[0-9]+]])
+// CHECK-DAG: ![[LOCAL]] = !DILocalVariable(name: "n", arg: 1, scope: ![[SP]], file: !{{.*}}, line: 1, type: !{{.*}})
+// CHECK-DAG: ![[NODES]] = !{![[LOCAL]]}
+
+// -----
+
 // Nameless and scopeless global constant.
 
 // CHECK-LABEL: @.str.1 = external constant [10 x i8]



More information about the flang-commits mailing list