[flang-commits] [flang] [flang] only instantiate required symbols from parent modules (PR #193689)

via flang-commits flang-commits at lists.llvm.org
Fri Apr 24 00:56:26 PDT 2026


https://github.com/jeanPerier updated https://github.com/llvm/llvm-project/pull/193689

>From 34c8db41f36537ceed2b69ba0de8a183248e69a4 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Wed, 22 Apr 2026 08:12:17 -0700
Subject: [PATCH 1/2] [flang] only instantiate required symbols from parent
 modules

---
 flang/include/flang/Lower/PFTBuilder.h        |  7 ++
 flang/include/flang/Semantics/tools.h         |  3 +
 flang/lib/Lower/Bridge.cpp                    | 27 ++++----
 flang/lib/Lower/PFTBuilder.cpp                | 69 +++++++++++++++++++
 flang/lib/Semantics/tools.cpp                 |  2 +-
 .../test/Lower/OpenMP/target-map-complex.f90  |  2 +-
 flang/test/Lower/c-interoperability.f90       |  2 -
 .../host_module_variable_instantiation.f90    | 19 +++++
 .../Lower/proc_pointer_hidden_by_generic.f90  | 33 +++++++++
 9 files changed, 148 insertions(+), 16 deletions(-)
 create mode 100644 flang/test/Lower/host_module_variable_instantiation.f90
 create mode 100644 flang/test/Lower/proc_pointer_hidden_by_generic.f90

diff --git a/flang/include/flang/Lower/PFTBuilder.h b/flang/include/flang/Lower/PFTBuilder.h
index 55a755acaaeb7..8da101a11e47e 100644
--- a/flang/include/flang/Lower/PFTBuilder.h
+++ b/flang/include/flang/Lower/PFTBuilder.h
@@ -608,6 +608,13 @@ VariableList getScopeVariableList(const Fortran::semantics::Scope &scope);
 /// depends on. \p symbol itself will be the last variable in the list.
 VariableList getDependentVariableList(const Fortran::semantics::Symbol &);
 
+struct FunctionLikeUnit;
+/// Create an ordered list of equivalence sets and variables from host
+/// [sub]module scopes of \p funit that are referenced in \p funit. This is
+/// used by lowering of module procedures (and their internal subprograms) to
+/// only instantiate referenced host module variables rather than all of them.
+VariableList getHostModuleVariableList(const FunctionLikeUnit &funit);
+
 void dump(VariableList &, std::string s = {}); // `s` is an optional dump label
 
 /// Function-like units may contain evaluations (executable statements),
diff --git a/flang/include/flang/Semantics/tools.h b/flang/include/flang/Semantics/tools.h
index 9f77d0ec5da2e..53248a2b358d6 100644
--- a/flang/include/flang/Semantics/tools.h
+++ b/flang/include/flang/Semantics/tools.h
@@ -127,6 +127,9 @@ bool IsSeparateModuleProcedureInterface(const Symbol *);
 bool HasAlternateReturns(const Symbol &);
 bool IsAutomaticallyDestroyed(const Symbol &);
 
+// Follow association until the first symbol without HostAssocDetails.
+const Symbol &FollowHostAssoc(const Symbol &);
+
 // Return an ultimate component of type that matches predicate, or nullptr.
 const Symbol *FindUltimateComponent(const DerivedTypeSpec &type,
     const std::function<bool(const Symbol &)> &predicate);
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 655edc8ffa7b3..4594a2f73426e 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -6592,18 +6592,21 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
     Fortran::lower::AggregateStoreMap storeMap;
 
-    // Map all containing submodule and module equivalences and variables, in
-    // case they are referenced. It might be better to limit this to variables
-    // that are actually referenced, although that is more complicated when
-    // there are equivalenced variables.
-    auto &scopeVariableListMap =
-        Fortran::lower::pft::getScopeVariableListMap(funit);
-    for (auto *scp = &scope.parent(); !scp->IsGlobal(); scp = &scp->parent())
-      if (scp->kind() == Fortran::semantics::Scope::Kind::Module)
-        for (const auto &var : Fortran::lower::pft::getScopeVariableList(
-                 *scp, scopeVariableListMap))
-          if (!var.isRuntimeTypeInfoData())
-            instantiateVar(var, storeMap);
+    // Map containing [sub]module equivalences and variables that are
+    // referenced in this function-like unit. Only referenced host module
+    // variables are instantiated to avoid generating IR for unused module
+    // variables. Equivalenced variables are handled via the aggregate store
+    // analysis performed on the host [sub]module scopes.
+    const bool hasHostModuleScope = [&]() {
+      for (auto *scp = &scope.parent(); !scp->IsGlobal(); scp = &scp->parent())
+        if (scp->kind() == Fortran::semantics::Scope::Kind::Module)
+          return true;
+      return false;
+    }();
+    if (hasHostModuleScope)
+      for (const auto &var :
+           Fortran::lower::pft::getHostModuleVariableList(funit))
+        instantiateVar(var, storeMap);
 
     // Map function equivalences and variables.
     mlir::Value primaryFuncResultStorage;
diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index 437e70265de94..d1b689edbb6a5 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -1622,6 +1622,20 @@ struct SymbolDependenceAnalysis {
     analyze(symbol);
     finalize();
   }
+  /// Analyze the dependencies of a set of module variables that are host
+  /// associated (or use associated in host module scopes).
+  SymbolDependenceAnalysis(
+      const llvm::SetVector<const semantics::Symbol *> &moduleVariables) {
+    for (const semantics::Symbol *sym : moduleVariables)
+      analyzeLocalEquivalenceSets(sym->owner());
+    // Add all aggregate stores to the front of the variable list.
+    adjustSize(1);
+    for (auto st : stores)
+      layeredVarList[0].emplace_back(std::move(st));
+    for (const semantics::Symbol *sym : moduleVariables)
+      analyze(*sym);
+    finalize();
+  }
   Fortran::lower::pft::VariableList getVariableList() {
     return std::move(layeredVarList[0]);
   }
@@ -2131,6 +2145,61 @@ lower::pft::getDependentVariableList(const semantics::Symbol &symbol) {
   return sda.getVariableList();
 }
 
+static bool
+isGenericHiddingProcedurePointer(const Fortran::semantics::Symbol &sym) {
+  if (const auto *generic = sym.detailsIf<Fortran::semantics::GenericDetails>())
+    if (const Fortran::semantics::Symbol *specific = generic->specific())
+      return Fortran::semantics::IsProcedurePointer(*specific);
+  return false;
+}
+
+/// Collect the canonical list of host [sub]module variables referenced in \p
+/// funit. A symbol is considered a host module variable if it is an
+/// ObjectEntityDetails, a ProcedurePointer, or a NamelistDetails symbol whose
+/// ultimate owning scope is a [sub]module scope containing \p funit's scope.
+/// Namelist groups are expanded to the set of their objects.
+static void collectHostAssociatedModuleVariables(
+    const Fortran::lower::pft::FunctionLikeUnit &funit,
+    llvm::SetVector<const Fortran::semantics::Symbol *> &moduleVariables) {
+  auto addIfHostModuleVariable = [&](const Fortran::semantics::Symbol &sym) {
+    const Fortran::semantics::Symbol &ultimate = sym.GetUltimate();
+    const auto *namelistDetails =
+        ultimate.detailsIf<Fortran::semantics::NamelistDetails>();
+    if (!ultimate.has<Fortran::semantics::ObjectEntityDetails>() &&
+        !Fortran::semantics::IsProcedurePointer(ultimate) &&
+        !isGenericHiddingProcedurePointer(ultimate) && !namelistDetails)
+      return;
+    const Fortran::semantics::Scope &symbolScope =
+        Fortran::semantics::FollowHostAssoc(sym).owner();
+    if (symbolScope.kind() != Fortran::semantics::Scope::Kind::Module)
+      return;
+    if (namelistDetails) {
+      // Namelist symbols are processed on the fly in IO lowering, which
+      // needs to be able to access each of their objects. Capture the
+      // objects rather than the namelist symbol itself.
+      for (const auto &namelistObject : namelistDetails->objects())
+        moduleVariables.insert(&namelistObject->GetUltimate());
+    } else {
+      moduleVariables.insert(&ultimate);
+    }
+  };
+  Fortran::lower::pft::visitAllSymbols(funit, addIfHostModuleVariable);
+}
+
+/// Create an ordered list of equivalences and variables from host
+/// [sub]modules of \p funit that are referenced in \p funit. The result is
+/// not cached.
+lower::pft::VariableList
+lower::pft::getHostModuleVariableList(const FunctionLikeUnit &funit) {
+  LLVM_DEBUG(llvm::dbgs() << "\ngetHostModuleVariableList of funit scope <"
+                          << &funit.getScope() << "> "
+                          << funit.getScope().GetName() << "\n");
+  llvm::SetVector<const semantics::Symbol *> moduleVariables;
+  collectHostAssociatedModuleVariables(funit, moduleVariables);
+  SymbolDependenceAnalysis sda(moduleVariables);
+  return sda.getVariableList();
+}
+
 namespace {
 /// Helper class to find all the symbols referenced in a FunctionLikeUnit.
 /// It defines a parse tree visitor doing a deep visit in all nodes with
diff --git a/flang/lib/Semantics/tools.cpp b/flang/lib/Semantics/tools.cpp
index e0e8727251ed8..86f0f59adc6f4 100644
--- a/flang/lib/Semantics/tools.cpp
+++ b/flang/lib/Semantics/tools.cpp
@@ -262,7 +262,7 @@ bool DoesScopeContain(const Scope *maybeAncestor, const Symbol &symbol) {
   return DoesScopeContain(maybeAncestor, symbol.owner());
 }
 
-static const Symbol &FollowHostAssoc(const Symbol &symbol) {
+const Symbol &FollowHostAssoc(const Symbol &symbol) {
   for (const Symbol *s{&symbol};;) {
     const auto *details{s->detailsIf<HostAssocDetails>()};
     if (!details) {
diff --git a/flang/test/Lower/OpenMP/target-map-complex.f90 b/flang/test/Lower/OpenMP/target-map-complex.f90
index fc01bdafe51ed..2325aec79e65b 100644
--- a/flang/test/Lower/OpenMP/target-map-complex.f90
+++ b/flang/test/Lower/OpenMP/target-map-complex.f90
@@ -8,8 +8,8 @@
 !CHECK-FPRIV: omp.private {type = firstprivate} @[[PRIV_32:.*]] : complex<f32> copy {
 
 !CHECK-LABEL: func.func @_QMmPbar()
-!CHECK:  %[[V0:[0-9]+]]:2 = hlfir.declare {{.*}} (!fir.ref<complex<f64>>) -> (!fir.ref<complex<f64>>, !fir.ref<complex<f64>>)
 !CHECK:  %[[V1:[0-9]+]]:2 = hlfir.declare {{.*}} (!fir.ref<complex<f32>>) -> (!fir.ref<complex<f32>>, !fir.ref<complex<f32>>)
+!CHECK:  %[[V0:[0-9]+]]:2 = hlfir.declare {{.*}} (!fir.ref<complex<f64>>) -> (!fir.ref<complex<f64>>, !fir.ref<complex<f64>>)
 !CHECK-FPRIV:  %[[V2:[0-9]+]] = omp.map.info var_ptr(%[[V1]]#0 : !fir.ref<complex<f32>>, complex<f32>) {{.*}} capture(ByCopy)
 !CHECK-FPRIV:  %[[V3:[0-9]+]] = omp.map.info var_ptr(%[[V0]]#0 : !fir.ref<complex<f64>>, complex<f64>) {{.*}} capture(ByRef)
 !CHECK-FPRIV:  omp.target map_entries(%[[V2]] -> {{.*}}, %[[V3]] -> {{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f64>>) private(@[[PRIV_32]] %[[V1]]#0 -> %{{.*}} [map_idx=0], @[[PRIV_64]] %[[V0]]#0 -> %{{.*}} [map_idx=1] : !fir.ref<complex<f32>>, !fir.ref<complex<f64>>) {
diff --git a/flang/test/Lower/c-interoperability.f90 b/flang/test/Lower/c-interoperability.f90
index 724763027763a..73886f74c69ef 100644
--- a/flang/test/Lower/c-interoperability.f90
+++ b/flang/test/Lower/c-interoperability.f90
@@ -2,8 +2,6 @@
 
 ! CHECK-LABEL: func @_QMc_interoperability_testPget_a_thing() -> !fir.type<_QMc_interoperability_testTthing_with_pointer{cptr:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}> {
 ! CHECK:         %[[VAL_0:.*]] = fir.dummy_scope : !fir.dscope
-! CHECK:         %[[VAL_1:.*]] = fir.address_of(@_QM__fortran_builtinsEC__builtin_c_null_ptr) : !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>
-! CHECK:         %[[VAL_2:.*]]:2 = hlfir.declare %[[VAL_1]] {fortran_attrs = #fir.var_attrs<parameter>, uniq_name = "_QM__fortran_builtinsEC__builtin_c_null_ptr"} : (!fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) -> (!fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>, !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>)
 ! CHECK:         %[[VAL_3:.*]] = fir.address_of(@_QMc_interoperability_testEthis_thing) : !fir.ref<!fir.type<_QMc_interoperability_testTthing_with_pointer{cptr:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>
 ! CHECK:         %[[VAL_4:.*]]:2 = hlfir.declare %[[VAL_3]] {uniq_name = "_QMc_interoperability_testEthis_thing"} : (!fir.ref<!fir.type<_QMc_interoperability_testTthing_with_pointer{cptr:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>) -> (!fir.ref<!fir.type<_QMc_interoperability_testTthing_with_pointer{cptr:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>, !fir.ref<!fir.type<_QMc_interoperability_testTthing_with_pointer{cptr:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}>>)
 ! CHECK:         %[[VAL_5:.*]] = fir.alloca !fir.type<_QMc_interoperability_testTthing_with_pointer{cptr:!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>}> {bindc_name = "get_a_thing", uniq_name = "_QMc_interoperability_testFget_a_thingEget_a_thing"}
diff --git a/flang/test/Lower/host_module_variable_instantiation.f90 b/flang/test/Lower/host_module_variable_instantiation.f90
new file mode 100644
index 0000000000000..029b7b9329589
--- /dev/null
+++ b/flang/test/Lower/host_module_variable_instantiation.f90
@@ -0,0 +1,19 @@
+! Test that host module variables are not instantiated inside
+! module procedure when they are not needed.
+
+! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
+
+module test_host_module_instantiation
+  integer :: var1, var2
+contains
+subroutine foo()
+  call bar(var1)
+end subroutine
+end module
+
+! CHECK-LABEL:  func.func @_QMtest_host_module_instantiationPfoo(
+! CHECK-NOT: fir.address_of
+! CHECK: %[[ADDR1:.*]] = fir.address_of(@_QMtest_host_module_instantiationEvar1) : !fir.ref<i32>
+! CHECK: hlfir.declare %[[ADDR1]]
+! CHECK-NOT: fir.address_of
+! CHECK: return
diff --git a/flang/test/Lower/proc_pointer_hidden_by_generic.f90 b/flang/test/Lower/proc_pointer_hidden_by_generic.f90
new file mode 100644
index 0000000000000..f8efb027fd0d2
--- /dev/null
+++ b/flang/test/Lower/proc_pointer_hidden_by_generic.f90
@@ -0,0 +1,33 @@
+! Test instantiation of procedure pointer hidden behind
+! generic interface host associated from a module.
+
+! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
+
+module m_proc_pointer_hidden_by_generic
+procedure (proc2),pointer :: p
+interface p
+  procedure proc1, p
+end interface
+contains
+subroutine proc1(i)
+  integer :: i
+end subroutine
+subroutine proc2(x)
+  real :: x
+end subroutine
+
+subroutine test()
+p=>proc2
+end subroutine
+end
+
+! CHECK-LABEL:   func.func @_QMm_proc_pointer_hidden_by_genericPtest(
+! CHECK:           %[[DUMMY_SCOPE_0:.*]] = fir.dummy_scope : !fir.dscope
+! CHECK:           %[[ADDRESS_OF_0:.*]] = fir.address_of(@_QMm_proc_pointer_hidden_by_genericEp) : !fir.ref<!fir.boxproc<(!fir.ref<f32>) -> ()>>
+! CHECK:           %[[DECLARE_0:.*]]:2 = hlfir.declare %[[ADDRESS_OF_0]] {fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QMm_proc_pointer_hidden_by_genericEp"} : (!fir.ref<!fir.boxproc<(!fir.ref<f32>) -> ()>>) -> (!fir.ref<!fir.boxproc<(!fir.ref<f32>) -> ()>>, !fir.ref<!fir.boxproc<(!fir.ref<f32>) -> ()>>)
+! CHECK:           %[[ADDRESS_OF_1:.*]] = fir.address_of(@_QMm_proc_pointer_hidden_by_genericPproc2) : (!fir.ref<f32>) -> ()
+! CHECK:           %[[EMBOXPROC_0:.*]] = fir.emboxproc %[[ADDRESS_OF_1]] : ((!fir.ref<f32>) -> ()) -> !fir.boxproc<() -> ()>
+! CHECK:           %[[CONVERT_0:.*]] = fir.convert %[[EMBOXPROC_0]] : (!fir.boxproc<() -> ()>) -> !fir.boxproc<(!fir.ref<f32>) -> ()>
+! CHECK:           fir.store %[[CONVERT_0]] to %[[DECLARE_0]]#0 : !fir.ref<!fir.boxproc<(!fir.ref<f32>) -> ()>>
+! CHECK:           return
+! CHECK:         }

>From 5313e9b6f8fbe748618b7c81e59cffd202fbe95c Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Fri, 24 Apr 2026 00:55:56 -0700
Subject: [PATCH 2/2] address comments

---
 flang/lib/Lower/PFTBuilder.cpp                  |  6 +++---
 .../host_module_variable_instantiation.f90      | 17 +++++++++++++++++
 2 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/flang/lib/Lower/PFTBuilder.cpp b/flang/lib/Lower/PFTBuilder.cpp
index d1b689edbb6a5..3208b5ce921e8 100644
--- a/flang/lib/Lower/PFTBuilder.cpp
+++ b/flang/lib/Lower/PFTBuilder.cpp
@@ -1624,7 +1624,7 @@ struct SymbolDependenceAnalysis {
   }
   /// Analyze the dependencies of a set of module variables that are host
   /// associated (or use associated in host module scopes).
-  SymbolDependenceAnalysis(
+  explicit SymbolDependenceAnalysis(
       const llvm::SetVector<const semantics::Symbol *> &moduleVariables) {
     for (const semantics::Symbol *sym : moduleVariables)
       analyzeLocalEquivalenceSets(sym->owner());
@@ -2146,7 +2146,7 @@ lower::pft::getDependentVariableList(const semantics::Symbol &symbol) {
 }
 
 static bool
-isGenericHiddingProcedurePointer(const Fortran::semantics::Symbol &sym) {
+isGenericHidingProcedurePointer(const Fortran::semantics::Symbol &sym) {
   if (const auto *generic = sym.detailsIf<Fortran::semantics::GenericDetails>())
     if (const Fortran::semantics::Symbol *specific = generic->specific())
       return Fortran::semantics::IsProcedurePointer(*specific);
@@ -2167,7 +2167,7 @@ static void collectHostAssociatedModuleVariables(
         ultimate.detailsIf<Fortran::semantics::NamelistDetails>();
     if (!ultimate.has<Fortran::semantics::ObjectEntityDetails>() &&
         !Fortran::semantics::IsProcedurePointer(ultimate) &&
-        !isGenericHiddingProcedurePointer(ultimate) && !namelistDetails)
+        !isGenericHidingProcedurePointer(ultimate) && !namelistDetails)
       return;
     const Fortran::semantics::Scope &symbolScope =
         Fortran::semantics::FollowHostAssoc(sym).owner();
diff --git a/flang/test/Lower/host_module_variable_instantiation.f90 b/flang/test/Lower/host_module_variable_instantiation.f90
index 029b7b9329589..7c1460bb122b4 100644
--- a/flang/test/Lower/host_module_variable_instantiation.f90
+++ b/flang/test/Lower/host_module_variable_instantiation.f90
@@ -5,10 +5,16 @@
 
 module test_host_module_instantiation
   integer :: var1, var2
+  integer :: a, b, c
+  equivalence (a, b)
 contains
 subroutine foo()
   call bar(var1)
 end subroutine
+
+subroutine foo_equiv
+  b = 1
+end subroutine
 end module
 
 ! CHECK-LABEL:  func.func @_QMtest_host_module_instantiationPfoo(
@@ -17,3 +23,14 @@ subroutine foo()
 ! CHECK: hlfir.declare %[[ADDR1]]
 ! CHECK-NOT: fir.address_of
 ! CHECK: return
+
+
+! CHECK-LABEL:   func.func @_QMtest_host_module_instantiationPfoo_equiv(
+! CHECK-NOT:       hlfir.declare
+! CHECK:           %[[ADDRESS_OF_0:.*]] = fir.address_of(@_QMtest_host_module_instantiationEa) : !fir.ref<!fir.array<4xi8>>
+! CHECK-NEXT:      %[[CONSTANT_0:.*]] = arith.constant 0 : index
+! CHECK-NEXT:      %[[COORDINATE_OF_0:.*]] = fir.coordinate_of %[[ADDRESS_OF_0]], %[[CONSTANT_0]] : (!fir.ref<!fir.array<4xi8>>, index) -> !fir.ref<i8>
+! CHECK-NEXT:      %[[CONVERT_0:.*]] = fir.convert %[[COORDINATE_OF_0]] : (!fir.ref<i8>) -> !fir.ptr<i32>
+! CHECK-NEXT:      %[[DECLARE_0:.*]]:2 = hlfir.declare %[[CONVERT_0]] storage(%[[ADDRESS_OF_0]][0]) {uniq_name = "_QMtest_host_module_instantiationEb"} : (!fir.ptr<i32>, !fir.ref<!fir.array<4xi8>>) -> (!fir.ptr<i32>, !fir.ptr<i32>)
+! CHECK-NOT:       hlfir.declare
+! CHECK:           return



More information about the flang-commits mailing list