[flang-commits] [flang] Reland [flang][debug] Emit debug info for named constants- #213974 (PR #215369)
Eugene Epshteyn via flang-commits
flang-commits at lists.llvm.org
Wed Aug 19 12:01:53 PDT 2026
================
@@ -927,6 +966,43 @@ void AddDebugInfoPass::buildModuleDebugImportsMap(mlir::ModuleOp module) {
});
}
+// Work out which modules this compilation unit defines. It has to be settled
+// before any DIModuleAttr is built, because the attribute is immutable and the
+// first member to mention a module fixes it for all the others.
+//
+// Lowering emits one fir.module_debug_imports for every module and submodule it
+// compiles, which is what says the module is defined here. No individual member
+// can say it: an array named constant from an intrinsic module, for one, is
+// materialized locally as a linkonce_odr definition in every unit that uses it,
+// although the module is defined in no object file at all.
+void AddDebugInfoPass::buildDefinedModuleNames(mlir::ModuleOp module) {
+ definedModuleNames.clear();
+ for (auto &entry : moduleDebugImportsByName)
+ definedModuleNames.insert(entry.getKey());
+
+ // We do not describe submodules yet: a submodule gets no DIModuleAttr of its
+ // own and the entities it defines hang off the DIModuleAttr of its ancestor
+ // module. So the ancestor has to be a definition in a unit that compiles the
+ // submodule. Were it a declaration, it would carry no scope, and those
+ // entities would not be able to reach a compile unit and would be dropped
+ // from the debug information entirely. The mangled name of a module level
+ // global carries its whole module chain, so mark the ancestor as defined
+ // whenever a submodule below it is compiled here. This keeps the current
+ // behavior until submodules are described in their own right.
+ for (auto globalOp : module.getOps<fir::GlobalOp>()) {
----------------
eugeneepshteyn wrote:
(This comment was generated by AI.)
This walk only visits `fir::GlobalOp`s, so a submodule TU that defines **only procedures** — which is what submodules are for — never marks its ancestor as defined. The ancestor's `DW_TAG_module` then becomes a declaration, and gdb (15.1) can no longer set function-name breakpoints on any procedure of that TU:
```fortran
! par.f90
module subpar
implicit none
interface
module subroutine hello()
end subroutine
end interface
end module subpar
```
```fortran
! kid.f90
submodule (subpar) subkid
contains
module subroutine hello()
print *, 'hello from submodule'
end subroutine hello
end submodule subkid
```
```fortran
! main.f90
program p
use subpar
call hello()
end program p
```
```console
$ flang -g -O0 par.f90 kid.f90 main.f90 -o a.out
$ gdb -batch -ex 'break hello' ./a.out
Function "hello" not defined.
$ gdb -batch -ex 'break subpar::hello' ./a.out
Function "subpar::hello" not defined.
```
With this PR's parent commit (d04f1727) and with current main, the module DIE in kid.o is a definition and `break hello` stops at `subpar::hello` — so this is a regression introduced by the definedness commit. The DWARF itself is well-formed (`llvm-dwarfdump --verify` is clean, the `DW_TAG_subprogram` is present with name/linkage name/pc range, and `tbreak kid.f90:4` still works); the loss is specifically name-based breakpoints.
Two data points that pin the mechanism and should guide the fix:
1. Taking the exact `kid.f90` LLVM IR from this PR's flang and flipping **only** `isDecl: true` to `false` on the `!DIModule` node (leaving `scope: null` and no file/line) restores `break hello`; conversely `!DIModule(scope: ..., file: ..., line: 8, isDecl: true)` still fails. So `DW_AT_declaration(true)` is the single causal bit — supplying scope/coords to declaration modules would *not* fix this; only definedness will. (gdb-side: `maint expand-symtabs` recovers the breakpoint, so it's the cooked index skipping subprogram children of declaration modules — perhaps also worth a gdb report, but users run stock gdb, and main works today.)
2. The failure is data-dependent in a way users will find baffling: adding one unrelated constant to the submodule restores name breakpoints, because its mangled name trips this GlobalOp walk —
```fortran
submodule (subpar) subkid
integer, parameter :: rescue_k = 7 ! this line toggles 'break hello' on
contains
module subroutine hello()
print *, 'hello from submodule'
end subroutine hello
end submodule subkid
```
Suggested fix: also walk **body-defining** (non-external) `func.func` ops here — take the name the same way `handleFuncOp` does (the `fir.internal_name` attribute when present, else the symbol name; probably worth a shared helper), deconstruct it, and insert `modules.front()` whenever the module chain is non-empty. Two things to watch: external/body-less `func.func` declarations must be excluded (a purely *using* TU carries `func.func private @_QM<mod>P<proc>()` declarations, and including those would mark used modules as defined and reintroduce the issue-214777 class), and this loop's `drop_front()` logic can't be reused as-is (`_QMsubparPhello` deconstructs to a module chain of length 1 — the rule should mirror `handleFuncOp`'s own `modules[0]` lookup). I haven't compiled this; please verify.
Could you also add a lit test pinning a *definition* `DIModule` for a procedures-only submodule TU, so this shape stays covered?
https://github.com/llvm/llvm-project/pull/215369
More information about the flang-commits
mailing list