[flang-commits] [flang] [flang][debug] Do not scope device subprograms at the host procedure. (PR #225121)
Abid Qadeer via flang-commits
flang-commits at lists.llvm.org
Mon Sep 21 08:50:10 PDT 2026
https://github.com/abidh created https://github.com/llvm/llvm-project/pull/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
>From 41f4ca996c6fa0ae439ccfc36cf1304fae54d9cb Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Fri, 18 Sep 2026 18:23:37 +0100
Subject: [PATCH] [flang][debug] Do not scope device subprograms at the host
procedure.
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.
Co-authored-by: Cursor <cursoragent at cursor.com>
---
.../lib/Optimizer/Transforms/AddDebugInfo.cpp | 13 ++++-
.../debug-omp-target-internal-proc.fir | 56 +++++++++++++++++++
2 files changed, 68 insertions(+), 1 deletion(-)
create mode 100644 flang/test/Transforms/debug-omp-target-internal-proc.fir
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 differ 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