[flang-commits] [flang] 750b967 - [flang][debug] Do not scope device subprograms at the host procedure. (#225121)

via flang-commits flang-commits at lists.llvm.org
Tue Sep 22 04:10:08 PDT 2026


Author: Abid Qadeer
Date: 2026-09-22T12:10:02+01:00
New Revision: 750b967f7bf80e08306a42c5d1cfd26d5d1ba56a

URL: https://github.com/llvm/llvm-project/commit/750b967f7bf80e08306a42c5d1cfd26d5d1ba56a
DIFF: https://github.com/llvm/llvm-project/commit/750b967f7bf80e08306a42c5d1cfd26d5d1ba56a.diff

LOG: [flang][debug] Do not scope device subprograms at the host procedure. (#225121)

A target device module holds the internal procedures and the outlined
target regions, but not the host procedure they are contained in, which
only the host runs. Scoping them at it leaves a DW_TAG_subprogram with
no code as their parent, and a debugger does not look inside such a
subtree, so the variables of a device internal procedure, and of a
kernel outlined from one, cannot be read even though they are in the
DWARF.

Scope them at the file instead when compiling for the device. Nothing on
the device reads a variable through the host procedure's scope. An
omp.target region is IsolatedFromAbove, so a variable of the host that
the region uses is brought in as one of the region's own arguments
rather than through host association, and a declare target internal
procedure that does need the host association tuple cannot be called
from a target region at all, because passing the tuple in would be the
same violation. Only the name qualification is lost: a frame prints
"add" rather than "test::add", and the full nesting is still in the
linkage name.

Fixes https://github.com/llvm/llvm-project/issues/225101

Co-authored-by: Cursor <cursoragent at cursor.com>

Added: 
    flang/test/Transforms/debug-omp-target-internal-proc.fir

Modified: 
    flang/lib/Optimizer/Transforms/AddDebugInfo.cpp

Removed: 
    


################################################################################
diff  --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index 045caafa3e5f9..b8e89722fb954 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -70,6 +70,8 @@ class AddDebugInfoPass : public fir::impl::AddDebugInfoBase<AddDebugInfoPass> {
 private:
   llvm::StringMap<mlir::LLVM::DIModuleAttr> moduleMap;
   llvm::StringMap<mlir::LLVM::DICommonBlockAttr> commonBlockMap;
+  /// Whether this module is being compiled for an OpenMP target device.
+  bool isTargetDevice = false;
   // List of GlobalVariableExpressionAttr that are attached to a given global
   // that represents the storage for common block.
   llvm::DenseMap<fir::GlobalOp, llvm::SmallVector<mlir::Attribute>>
@@ -744,7 +746,13 @@ void AddDebugInfoPass::handleFuncOp(mlir::func::FuncOp funcOp,
         subprogramFlags | mlir::LLVM::DISubprogramFlags::Recursive;
 
   unsigned line = fir::getLineFromLoc(l);
-  if (fir::isInternalProcedure(funcOp)) {
+  // A target device module holds the internal procedures and the outlined
+  // target regions, but not the host procedure they are contained in, which
+  // only the host runs. Scoping them at it would leave a DW_TAG_subprogram
+  // with no code as their parent, and a debugger does not look inside such a
+  // subtree, so scope them at the file instead. Nothing on the device reads a
+  // variable through that scope, so only the name qualification is lost.
+  if (fir::isInternalProcedure(funcOp) && !isTargetDevice) {
     // For contained functions, the scope is the parent subroutine.
     mlir::SymbolRefAttr sym = mlir::cast<mlir::SymbolRefAttr>(
         funcOp->getAttr(fir::getHostSymbolAttrName()));
@@ -1131,6 +1139,9 @@ void AddDebugInfoPass::runOnOperation() {
   mlir::ModuleOp module = getOperation();
   mlir::MLIRContext *context = &getContext();
   mlir::SymbolTable symbolTable(module);
+  if (auto offloadMod =
+          mlir::dyn_cast<mlir::omp::OffloadModuleInterface>(*module))
+    isTargetDevice = offloadMod.getIsTargetDevice();
   buildModuleDebugImportsMap(module);
   buildDefinedModuleNames(module);
   llvm::StringRef fileName;

diff  --git a/flang/test/Transforms/debug-omp-target-internal-proc.fir b/flang/test/Transforms/debug-omp-target-internal-proc.fir
new file mode 100644
index 0000000000000..61d06cfddcaa2
--- /dev/null
+++ b/flang/test/Transforms/debug-omp-target-internal-proc.fir
@@ -0,0 +1,56 @@
+// Test the scope of an internal procedure, and of a target region it contains,
+// when compiling for a target device. Such a module does not hold the host
+// procedure they are contained in, so it must not be used as their scope
+// there. The two inputs below only 
diff er in omp.is_target_device.
+
+// RUN: split-file %s %t
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %t/host.fir | FileCheck %s --check-prefix=HOST
+// RUN: fir-opt --add-debug-info --mlir-print-debuginfo %t/device.fir | FileCheck %s --check-prefix=DEVICE
+
+//--- host.fir
+module attributes {dlti.dl_spec = #dlti.dl_spec<>} {
+  func.func @_QQmain() attributes {fir.bindc_name = "test"} {
+    return
+  } loc(#loc1)
+  func.func private @_QFPtar(%arg0: !fir.ref<i32>) attributes {fir.host_symbol = @_QQmain, llvm.linkage = #llvm.linkage<internal>} {
+    %0 = omp.map.info var_ptr(%arg0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) name("n") -> !fir.ref<i32>
+    omp.target kernel_type(generic) map_entries(%0 -> %arg1 : !fir.ref<i32>) {
+      %1 = fircg.ext_declare %arg1 {uniq_name = "_QFEn"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc3)
+      omp.terminator
+    } loc(#loc4)
+    return
+  } loc(#loc2)
+}
+#loc1 = loc("test.f90":1:1)
+#loc2 = loc("test.f90":13:1)
+#loc3 = loc("test.f90":15:1)
+#loc4 = loc("test.f90":14:1)
+
+// The host runs the host procedure, so it is the scope of both.
+// HOST: #[[SP:.*]] = #llvm.di_subprogram<{{.*}}name = "test"{{.*}}>
+// HOST-DAG: #llvm.di_subprogram<{{.*}}scope = #[[SP]], name = "tar"{{.*}}>
+// HOST-DAG: #llvm.di_subprogram<{{.*}}scope = #[[SP]], name = "__omp_offloading_{{.*}}_QFPtar_l14"{{.*}}>
+
+//--- device.fir
+module attributes {dlti.dl_spec = #dlti.dl_spec<>, omp.is_gpu = true, omp.is_target_device = true} {
+  func.func @_QQmain() attributes {fir.bindc_name = "test"} {
+    return
+  } loc(#loc1)
+  func.func private @_QFPtar(%arg0: !fir.ref<i32>) attributes {fir.host_symbol = @_QQmain, llvm.linkage = #llvm.linkage<internal>} {
+    %0 = omp.map.info var_ptr(%arg0 : !fir.ref<i32>, i32) map_clauses(tofrom) capture(ByRef) name("n") -> !fir.ref<i32>
+    omp.target kernel_type(generic) map_entries(%0 -> %arg1 : !fir.ref<i32>) {
+      %1 = fircg.ext_declare %arg1 {uniq_name = "_QFEn"} : (!fir.ref<i32>) -> !fir.ref<i32> loc(#loc3)
+      omp.terminator
+    } loc(#loc4)
+    return
+  } loc(#loc2)
+}
+#loc1 = loc("test.f90":1:1)
+#loc2 = loc("test.f90":13:1)
+#loc3 = loc("test.f90":15:1)
+#loc4 = loc("test.f90":14:1)
+
+// The device does not run the host procedure, so both are scoped at the file.
+// DEVICE: #[[FILE:.*]] = #llvm.di_file<"device.fir"{{.*}}>
+// DEVICE-DAG: #llvm.di_subprogram<{{.*}}scope = #[[FILE]], name = "tar"{{.*}}>
+// DEVICE-DAG: #llvm.di_subprogram<{{.*}}scope = #[[FILE]], name = "__omp_offloading_{{.*}}_QFPtar_l14"{{.*}}>


        


More information about the flang-commits mailing list