[all-commits] [llvm/llvm-project] dc744d: [flang][debug] Describe submodules in the debug i...
Abid Qadeer via All-commits
all-commits at lists.llvm.org
Fri Sep 25 03:05:37 PDT 2026
Branch: refs/heads/main
Home: https://github.com/llvm/llvm-project
Commit: dc744d9839ca01185a9d19790f0b6bc18361d667
https://github.com/llvm/llvm-project/commit/dc744d9839ca01185a9d19790f0b6bc18361d667
Author: Abid Qadeer <haqadeer at amd.com>
Date: 2026-09-25 (Fri, 25 Sep 2026)
Changed paths:
M flang/include/flang/Optimizer/Dialect/FIROps.td
M flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
M flang/lib/Lower/Bridge.cpp
M flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
M flang/test/Fir/fir-ops.fir
A flang/test/Integration/debug-submodule-ancestor.F90
A flang/test/Integration/debug-submodule-nested.F90
M flang/test/Integration/debug-submodule-procedure.F90
A flang/test/Lower/debug-submodule.f90
M flang/test/Transforms/debug-module-submodule-procedure.fir
A flang/test/Transforms/debug-submodule.fir
Log Message:
-----------
[flang][debug] Describe submodules in the debug info (#225852)
A submodule had no presence of its own in the debug info. Everything it
defined was attributed to the module at the root of its ancestry, so a
debugger showed one module per source file, every one of them carrying
the
name and the line of that ancestor, and it listed the variables of a
submodule as if the ancestor had declared them.
This PR describes a submodule in its own right. There are two
independent strands to it, and they meet only in that both decide which
module an entity is scoped in.
## Thread A: describing the submodule
### Recording the ancestry
A submodule is not a first class entity in FIR. It is not mangled into
the name of anything it declares in a way that distinguishes it from a
module, and its own name is unqualified, so nothing downstream could
tell `impl` of
`shapes` from a module called `impl`. Lowering now records the ancestry
on `fir.module_debug_imports`, which it already emits once for every
module and submodule it compiles:
```
fir.module_debug_imports "shapes" { }
fir.module_debug_imports "impl" in "shapes" parent "shapes" { }
fir.module_debug_imports "deep" in "shapes" parent "impl" { }
```
`ancestor_module` is the module at the root, and `parent_module` is the
one directly above, which is that same module for a first level
submodule and a submodule of it otherwise. Both are needed and neither
can be derived from
the other in a unit that compiles the submodule alone. The division
mirrors semantics, which keeps `ModuleDetails::ancestor()` and
`ModuleDetails::parent()` apart for the same reason.
### Naming
DWARF has no way to say that a module entry is a submodule, so the name
has to carry it. The submodule is named after the module at the root of
its ancestry and itself, joined with a `.`, which is what gfortran does:
```
DW_TAG_module DW_AT_name ("shapes.impl")
DW_TAG_module DW_AT_name ("shapes.deep")
```
The standard makes the pair unique among the submodules of a program,
and a `.` cannot appear in a Fortran identifier, so the result can never
be confused with a module that is really called that. A nested submodule
is named after the root and itself, not after the submodule containing
it, so `deep` of `shapes:impl` is `shapes.deep`. That too follows
gfortran, and it keeps the name independent of how deep the nesting
happens to be.
### Importing the parent
Scoping the entities of a submodule at the submodule loses something: a
submodule also has access to everything declared above it, and nothing
in the name of a module entry says so. Each submodule therefore imports
the one
directly above it:
```
DW_TAG_module DW_AT_name ("shapes.impl")
DW_TAG_imported_module DW_AT_import ("shapes")
DW_TAG_module DW_AT_name ("shapes.deep")
DW_TAG_imported_module DW_AT_import ("shapes.impl")
```
A debugger walks that chain up from wherever it is stopped, so an entity
declared anywhere above a submodule is still found by name. This is
again what gfortran emits, down to importing the immediate parent rather
than the root.
The entries hang off the compile unit rather than off any one procedure,
so that a submodule needs a single one however many procedures it holds.
That makes the compile unit name a module that names the compile unit
back, so it
is built as a recursive attribute, the same way a subprogram holding
imported entities already is. A unit that compiles no submodule is
unchanged.
## Thread B: separate module procedures
A separate module procedure is mangled with the module that declares its
interface rather than the submodule that defines it, because that is the
name its callers use:
```fortran
module shapes
interface
module function square(n) result(r) ! declared here
```
```fortran
submodule (shapes) impl
contains
module function square(n) result(r) ! defined here
```
```
DW_AT_linkage_name ("_QMshapesPsquare") ! names shapes, not impl
```
Every other entity of a submodule carries the whole chain in its uniqued
name: `_QMshapesSimplSdeepEdeep_var` names `shapes`, `impl` and `deep`
in turn. A separate module procedure names only the ancestor, so the
submodule defining it is recorded nowhere, and the procedure was
reported as belonging to the ancestor. Lowering now notes that submodule
on the function as `fir.defining_submodule`.
With that, nothing is scoped at the ancestor of a submodule any more,
and `AddDebugInfoPass::markSubmoduleAncestorsDefined` goes away. It
marked the ancestor as defined wherever a submodule was compiled, so
that the entities hanging off it could reach a compile unit; its comment
said it should go once submodules were described in their own right.
## What this looks like in a debugger
Given a module, a submodule defining its procedure, and a program
calling it:
```fortran
module subpar
interface
module subroutine hello()
end subroutine
end interface
end module subpar
```
```fortran
submodule (subpar) subkid
contains
module subroutine hello()
print *, 'hello from submodule'
end subroutine hello
end submodule subkid
```
A breakpoint on the bare name works before and after, but it now reports
where
the procedure really is, and it can be qualified that way too:
| `gdb -batch -ex 'break ...'` | before | after |
| --- | --- | --- |
| `hello` | `in subpar::hello at kid.f90:4` | `in subpar.subkid::hello
at kid.f90:4` |
| `subpar.subkid::hello` | `Function ... not defined.` | `in
subpar.subkid::hello at kid.f90:4` |
| `subpar::hello` | `in subpar::hello at kid.f90:4` | `Function ... not
defined.` |
The last row is the one to note: `break subpar::hello` stops working,
since the procedure is no longer described as belonging to `subpar`
alone. That is a deliberate change rather than a step backwards and
gfortran behaves exactly as this patch does on all three spellings, so
this brings flang into line with it rather than away from it.
The import is what keeps name lookup working from inside a submodule.
Stopped in a procedure of `submodule (par3) kid3`, which uses `pv`, `pk`
and `ptbl` from `par3` and its own `sq`:
```
(gdb) print pv $1 = 1
(gdb) print pk $1 = 5
(gdb) print ptbl $1 = (7, 8, 9)
(gdb) print sq $1 = (9, 8, 7)
```
Fixes https://github.com/llvm/llvm-project/issues/215811
Assisted By: Cursor
---------
Co-authored-by: Cursor <cursoragent at cursor.com>
To unsubscribe from these emails, change your notification settings at https://github.com/llvm/llvm-project/settings/notifications
More information about the All-commits
mailing list