[flang-commits] [flang] [flang] Don't emit needless symbols to hermetic module files (PR #144765)

Peter Klausler via flang-commits flang-commits at lists.llvm.org
Mon Jul 14 08:29:40 PDT 2025


https://github.com/klausler updated https://github.com/llvm/llvm-project/pull/144765

>From de069ae733d7b002a7317f6288bdf49bd6b86267 Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Tue, 17 Jun 2025 11:51:02 -0700
Subject: [PATCH 1/2] [flang] Don't emit needless symbols to hermetic module
 files

When emitting the dependent modules for a hermetic module file,
omit symbols that are not necessary (directly or otherwise) for
the declarations and definitions in the main module.
---
 .../flang/Semantics/symbol-dependence.h       |  36 ++
 flang/include/flang/Semantics/tools.h         |   2 +-
 flang/lib/Semantics/CMakeLists.txt            |   1 +
 flang/lib/Semantics/check-declarations.cpp    |  12 +-
 flang/lib/Semantics/expression.cpp            |   3 +-
 flang/lib/Semantics/mod-file.cpp              | 377 +++++++++++-------
 flang/lib/Semantics/mod-file.h                |  11 +-
 flang/lib/Semantics/resolve-names.cpp         |  22 +-
 flang/lib/Semantics/semantics.cpp             |  12 +-
 flang/lib/Semantics/symbol-dependence.cpp     | 356 +++++++++++++++++
 flang/lib/Semantics/symbol.cpp                |  28 +-
 flang/lib/Semantics/tools.cpp                 |  33 +-
 flang/lib/Semantics/type.cpp                  |  26 +-
 flang/test/Semantics/modfile03.f90            |   8 +-
 flang/test/Semantics/modfile65.f90            |   6 +-
 flang/test/Semantics/modfile78.F90            |   1 -
 16 files changed, 718 insertions(+), 216 deletions(-)
 create mode 100644 flang/include/flang/Semantics/symbol-dependence.h
 create mode 100644 flang/lib/Semantics/symbol-dependence.cpp

diff --git a/flang/include/flang/Semantics/symbol-dependence.h b/flang/include/flang/Semantics/symbol-dependence.h
new file mode 100644
index 0000000000000..9bcff564a4c04
--- /dev/null
+++ b/flang/include/flang/Semantics/symbol-dependence.h
@@ -0,0 +1,36 @@
+//===-- include/flang/Semantics/symbol-dependence.h -------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_SEMANTICS_SYMBOL_DEPENDENCE_H_
+#define FORTRAN_SEMANTICS_SYMBOL_DEPENDENCE_H_
+
+#include "flang/Semantics/symbol.h"
+
+namespace Fortran::semantics {
+
+// For a set or scope of symbols, computes the transitive closure of their
+// dependences due to their types, bounds, specific procedures, interfaces,
+// initialization, storage association, &c. Includes the original symbol
+// or members of the original set.  Does not include dependences from
+// subprogram definitions, only their interfaces.
+enum DependenceCollectionFlags {
+  NoDependenceCollectionFlags = 0,
+  IncludeOriginalSymbols = 1 << 0,
+  FollowUseAssociations = 1 << 1,
+  IncludeSpecificsOfGenerics = 1 << 2,
+  IncludeUsesOfGenerics = 1 << 3,
+  NotJustForOneModule = 1 << 4,
+};
+
+SymbolVector CollectAllDependences(const SymbolVector &,
+    int = NoDependenceCollectionFlags, const Scope * = nullptr);
+SymbolVector CollectAllDependences(
+    const Scope &, int = NoDependenceCollectionFlags);
+
+} // namespace Fortran::semantics
+#endif // FORTRAN_SEMANTICS_SYMBOL_DEPENDENCE_H_
diff --git a/flang/include/flang/Semantics/tools.h b/flang/include/flang/Semantics/tools.h
index fb670528f3ce4..2050d23870657 100644
--- a/flang/include/flang/Semantics/tools.h
+++ b/flang/include/flang/Semantics/tools.h
@@ -43,7 +43,7 @@ const Scope &GetProgramUnitOrBlockConstructContaining(const Symbol &);
 
 const Scope *FindModuleContaining(const Scope &);
 const Scope *FindModuleOrSubmoduleContaining(const Scope &);
-const Scope *FindModuleFileContaining(const Scope &);
+bool IsInModuleFile(const Scope &);
 const Scope *FindPureProcedureContaining(const Scope &);
 const Scope *FindOpenACCConstructContaining(const Scope *);
 
diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt
index 109bc2dbb8569..c711103e793d8 100644
--- a/flang/lib/Semantics/CMakeLists.txt
+++ b/flang/lib/Semantics/CMakeLists.txt
@@ -48,6 +48,7 @@ add_flang_library(FortranSemantics
   runtime-type-info.cpp
   scope.cpp
   semantics.cpp
+  symbol-dependence.cpp
   symbol.cpp
   tools.cpp
   type.cpp
diff --git a/flang/lib/Semantics/check-declarations.cpp b/flang/lib/Semantics/check-declarations.cpp
index f9d64485f1407..36e47f7999538 100644
--- a/flang/lib/Semantics/check-declarations.cpp
+++ b/flang/lib/Semantics/check-declarations.cpp
@@ -124,10 +124,7 @@ class CheckHelper {
     }
     return msg;
   }
-  bool InModuleFile() const {
-    return FindModuleFileContaining(context_.FindScope(messages_.at())) !=
-        nullptr;
-  }
+  bool InModuleFile() const { return context_.IsInModuleFile(messages_.at()); }
   template <typename FeatureOrUsageWarning, typename... A>
   parser::Message *Warn(FeatureOrUsageWarning warning, A &&...x) {
     if (!context_.ShouldWarn(warning) || InModuleFile()) {
@@ -139,8 +136,7 @@ class CheckHelper {
   template <typename FeatureOrUsageWarning, typename... A>
   parser::Message *Warn(
       FeatureOrUsageWarning warning, parser::CharBlock source, A &&...x) {
-    if (!context_.ShouldWarn(warning) ||
-        FindModuleFileContaining(context_.FindScope(source))) {
+    if (!context_.ShouldWarn(warning) || context_.IsInModuleFile(source)) {
       return nullptr;
     } else {
       return messages_.Say(warning, source, std::forward<A>(x)...);
@@ -4050,7 +4046,7 @@ void DistinguishabilityHelper::Add(const Symbol &generic, GenericKind kind,
 }
 
 void DistinguishabilityHelper::Check(const Scope &scope) {
-  if (FindModuleFileContaining(scope)) {
+  if (IsInModuleFile(scope)) {
     // Distinguishability was checked when the module was created;
     // don't let optional warnings then become errors now.
     return;
@@ -4109,7 +4105,7 @@ void DistinguishabilityHelper::SayNotDistinguishable(const Scope &scope,
   if (isWarning &&
       (!context_.ShouldWarn(
            common::LanguageFeature::IndistinguishableSpecifics) ||
-          FindModuleFileContaining(scope))) {
+          IsInModuleFile(scope))) {
     return;
   }
   std::string name1{proc1.name().ToString()};
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index f4af738284ed7..50c6d51abebdf 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -1852,8 +1852,7 @@ void ArrayConstructorContext::Push(MaybeExpr &&x) {
     } else {
       if (!(messageDisplayedSet_ & 2)) {
         exprAnalyzer_.Say(
-            "Values in array constructor must have the same declared type "
-            "when no explicit type appears"_err_en_US); // C7110
+            "Values in array constructor must have the same declared type when no explicit type appears"_err_en_US); // C7110
         messageDisplayedSet_ |= 2;
       }
     }
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index 82c8536902eb2..1a27449e5a786 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -15,6 +15,7 @@
 #include "flang/Parser/unparse.h"
 #include "flang/Semantics/scope.h"
 #include "flang/Semantics/semantics.h"
+#include "flang/Semantics/symbol-dependence.h"
 #include "flang/Semantics/symbol.h"
 #include "flang/Semantics/tools.h"
 #include "llvm/Support/FileSystem.h"
@@ -130,7 +131,7 @@ static std::string ModFileName(const SourceName &name,
   return ancestorName.empty() ? result : ancestorName + '-' + result;
 }
 
-// Write the module file for symbol, which must be a module or submodule.
+// Writes the module file for symbol, which must be a module or submodule.
 void ModFileWriter::Write(const Symbol &symbol) {
   const auto &module{symbol.get<ModuleDetails>()};
   if (symbol.test(Symbol::Flag::ModFile) || module.moduleFileHash()) {
@@ -142,26 +143,15 @@ void ModFileWriter::Write(const Symbol &symbol) {
   std::string path{context_.moduleDirectory() + '/' +
       ModFileName(symbol.name(), ancestorName, context_.moduleFileSuffix())};
 
-  std::set<std::string> hermeticModuleNames;
-  hermeticModuleNames.insert(symbol.name().ToString());
-  UnorderedSymbolSet additionalModules;
-  PutSymbols(DEREF(symbol.scope()),
-      hermeticModuleFileOutput_ ? &additionalModules : nullptr);
-  auto asStr{GetAsString(symbol)};
-  while (!additionalModules.empty()) {
-    UnorderedSymbolSet nextPass{std::move(additionalModules)};
-    additionalModules.clear();
-    for (const Symbol &modSym : nextPass) {
-      if (!modSym.owner().IsIntrinsicModules() &&
-          hermeticModuleNames.find(modSym.name().ToString()) ==
-              hermeticModuleNames.end()) {
-        hermeticModuleNames.insert(modSym.name().ToString());
-        PutSymbols(DEREF(modSym.scope()), &additionalModules);
-        asStr += GetAsString(modSym);
-      }
-    }
+  SymbolVector dependenceClosure;
+  if (hermeticModuleFileOutput_ && !isSubmodule_) {
+    dependenceClosure = CollectAllDependences(DEREF(symbol.scope()),
+        FollowUseAssociations | IncludeUsesOfGenerics |
+            IncludeSpecificsOfGenerics | NotJustForOneModule);
   }
-
+  PutSymbols(DEREF(symbol.scope()), hermeticModuleFileOutput_);
+  auto asStr{GetAsString(&symbol, symbol.name().ToString())};
+  asStr += PutDependencyModules(symbol.name().ToString(), dependenceClosure);
   ModuleCheckSumType checkSum;
   if (std::error_code error{
           WriteFile(path, asStr, checkSum, context_.debugModuleWriter())}) {
@@ -177,9 +167,9 @@ void ModFileWriter::WriteClosure(llvm::raw_ostream &out, const Symbol &symbol,
       !nonIntrinsicModulesWritten.insert(symbol).second) {
     return;
   }
-  PutSymbols(DEREF(symbol.scope()), /*hermeticModules=*/nullptr);
+  PutSymbols(DEREF(symbol.scope()), /*omitModules=*/false);
   needsBuf_.clear(); // omit module checksums
-  auto str{GetAsString(symbol)};
+  auto str{GetAsString(&symbol, symbol.name().ToString())};
   for (auto depRef : std::move(usedNonIntrinsicModules_)) {
     WriteClosure(out, *depRef, nonIntrinsicModulesWritten);
   }
@@ -188,22 +178,23 @@ void ModFileWriter::WriteClosure(llvm::raw_ostream &out, const Symbol &symbol,
 
 // Return the entire body of the module file
 // and clear saved uses, decls, and contains.
-std::string ModFileWriter::GetAsString(const Symbol &symbol) {
+std::string ModFileWriter::GetAsString(const Symbol *symbol, std::string name) {
   std::string buf;
   llvm::raw_string_ostream all{buf};
   all << needs_.str();
   needs_.str().clear();
-  auto &details{symbol.get<ModuleDetails>()};
-  if (!details.isSubmodule()) {
-    all << "module " << symbol.name();
+  const ModuleDetails *details{
+      symbol ? &symbol->get<ModuleDetails>() : nullptr};
+  if (!details || !details->isSubmodule()) {
+    all << "module " << name;
   } else {
-    auto *parent{details.parent()->symbol()};
-    auto *ancestor{details.ancestor()->symbol()};
+    auto *parent{details->parent()->symbol()};
+    auto *ancestor{details->ancestor()->symbol()};
     all << "submodule(" << ancestor->name();
     if (parent != ancestor) {
       all << ':' << parent->name();
     }
-    all << ") " << symbol.name();
+    all << ") " << name;
   }
   all << '\n' << uses_.str();
   uses_.str().clear();
@@ -223,79 +214,22 @@ std::string ModFileWriter::GetAsString(const Symbol &symbol) {
 // Collect symbols from constant and specification expressions that are being
 // referenced directly from other modules; they may require new USE
 // associations.
-static void HarvestSymbolsNeededFromOtherModules(
-    SourceOrderedSymbolSet &, const Scope &);
-static void HarvestSymbolsNeededFromOtherModules(
-    SourceOrderedSymbolSet &set, const Symbol &symbol, const Scope &scope) {
-  auto HarvestBound{[&](const Bound &bound) {
-    if (const auto &expr{bound.GetExplicit()}) {
-      for (SymbolRef ref : evaluate::CollectSymbols(*expr)) {
-        set.emplace(*ref);
-      }
-    }
-  }};
-  auto HarvestShapeSpec{[&](const ShapeSpec &shapeSpec) {
-    HarvestBound(shapeSpec.lbound());
-    HarvestBound(shapeSpec.ubound());
-  }};
-  auto HarvestArraySpec{[&](const ArraySpec &arraySpec) {
-    for (const auto &shapeSpec : arraySpec) {
-      HarvestShapeSpec(shapeSpec);
-    }
-  }};
-
-  if (symbol.has<DerivedTypeDetails>()) {
-    if (symbol.scope()) {
-      HarvestSymbolsNeededFromOtherModules(set, *symbol.scope());
-    }
-  } else if (const auto &generic{symbol.detailsIf<GenericDetails>()};
-             generic && generic->derivedType()) {
-    const Symbol &dtSym{*generic->derivedType()};
-    if (dtSym.has<DerivedTypeDetails>()) {
-      if (dtSym.scope()) {
-        HarvestSymbolsNeededFromOtherModules(set, *dtSym.scope());
-      }
-    } else {
-      CHECK(dtSym.has<UseDetails>() || dtSym.has<UseErrorDetails>());
-    }
-  } else if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
-    HarvestArraySpec(object->shape());
-    HarvestArraySpec(object->coshape());
-    if (IsNamedConstant(symbol) || scope.IsDerivedType()) {
-      if (object->init()) {
-        for (SymbolRef ref : evaluate::CollectSymbols(*object->init())) {
-          set.emplace(*ref);
-        }
-      }
-    }
-  } else if (const auto *proc{symbol.detailsIf<ProcEntityDetails>()}) {
-    if (proc->init() && *proc->init() && scope.IsDerivedType()) {
-      set.emplace(**proc->init());
-    }
-  } else if (const auto *subp{symbol.detailsIf<SubprogramDetails>()}) {
-    for (const Symbol *dummy : subp->dummyArgs()) {
-      if (dummy) {
-        HarvestSymbolsNeededFromOtherModules(set, *dummy, scope);
-      }
-    }
-    if (subp->isFunction()) {
-      HarvestSymbolsNeededFromOtherModules(set, subp->result(), scope);
-    }
-  }
-}
-
-static void HarvestSymbolsNeededFromOtherModules(
-    SourceOrderedSymbolSet &set, const Scope &scope) {
-  for (const auto &[_, symbol] : scope) {
-    HarvestSymbolsNeededFromOtherModules(set, *symbol, scope);
+static SourceOrderedSymbolSet HarvestSymbolsNeededFromOtherModules(
+    const Scope &scope) {
+  SourceOrderedSymbolSet set;
+  for (const Symbol &symbol : CollectAllDependences(scope)) {
+    set.insert(symbol);
   }
+  return set;
 }
 
-void ModFileWriter::PrepareRenamings(const Scope &scope) {
-  // Identify use-associated symbols already in scope under some name
-  std::map<const Symbol *, const Symbol *> useMap;
-  for (const auto &[name, symbolRef] : scope) {
-    const Symbol *symbol{&*symbolRef};
+// Identifies use-associated symbols already in scope under some name
+using UseRenameMap = std::map<const Symbol *, const Symbol *>;
+template <typename SYMBOLS>
+UseRenameMap GetUseRenamings(const SYMBOLS &symbols) {
+  UseRenameMap useMap;
+  for (const Symbol &sym : symbols) {
+    const Symbol *symbol{&sym};
     while (const auto *hostAssoc{symbol->detailsIf<HostAssocDetails>()}) {
       symbol = &hostAssoc->symbol();
     }
@@ -303,65 +237,80 @@ void ModFileWriter::PrepareRenamings(const Scope &scope) {
       useMap.emplace(&use->symbol(), symbol);
     }
   }
+  return useMap;
+}
+
+void ModFileWriter::PrepareRenamings(const Scope &scope) {
   // Collect symbols needed from other modules
-  SourceOrderedSymbolSet symbolsNeeded;
-  HarvestSymbolsNeededFromOtherModules(symbolsNeeded, scope);
+  SourceOrderedSymbolSet symbolsNeeded{
+      HarvestSymbolsNeededFromOtherModules(scope)};
   // Establish any necessary renamings of symbols in other modules
   // to their names in this scope, creating those new names when needed.
+  UseRenameMap useMap{GetUseRenamings(symbolsNeeded)};
   auto &renamings{context_.moduleFileOutputRenamings()};
-  for (SymbolRef s : symbolsNeeded) {
-    if (s->owner().kind() != Scope::Kind::Module) {
-      // Not a USE'able name from a module's top scope;
-      // component, binding, dummy argument, &c.
-      continue;
-    }
-    const Scope *sMod{FindModuleContaining(s->owner())};
-    if (!sMod || sMod == &scope) {
-      continue;
-    }
-    if (auto iter{useMap.find(&*s)}; iter != useMap.end()) {
-      renamings.emplace(&*s, iter->second->name());
-      continue;
-    }
-    SourceName rename{s->name()};
-    if (const Symbol * found{scope.FindSymbol(s->name())}) {
-      if (found == &*s) {
-        continue; // available in scope
-      }
-      if (const auto *generic{found->detailsIf<GenericDetails>()}) {
-        if (generic->derivedType() == &*s || generic->specific() == &*s) {
-          continue;
-        }
-      } else if (found->has<UseDetails>()) {
-        if (&found->GetUltimate() == &*s) {
-          continue; // already use-associated with same name
-        }
+  for (const Symbol &sym : symbolsNeeded) {
+    if (auto iter{useMap.find(&sym)}; iter != useMap.end()) {
+      renamings.emplace(&sym, iter->second->name());
+    } else {
+      PutRenamedSymbolUse(scope, sym);
+    }
+  }
+}
+
+std::optional<SourceName> ModFileWriter::GetUseName(
+    const Scope &scope, const Symbol &sym, const Scope &symMod) {
+  if (sym.owner().kind() != Scope::Kind::Module) {
+    // Not a USE'able name from a module's top scope;
+    // component, binding, dummy argument, &c.
+    return std::nullopt;
+  }
+  if (&symMod == &scope) {
+    return std::nullopt; // already here
+  }
+  if (const Symbol *found{scope.FindSymbol(sym.name())}) {
+    if (found == &sym) {
+      return std::nullopt; // available in scope
+    }
+    if (const auto *generic{found->detailsIf<GenericDetails>()}) {
+      if (generic->derivedType() == &sym || generic->specific() == &sym) {
+        return std::nullopt;
       }
-      if (&s->owner() != &found->owner()) { // Symbol needs renaming
-        rename = scope.context().SaveTempName(
-            DEREF(sMod->symbol()).name().ToString() + "$" +
-            s->name().ToString());
+    } else if (found->has<UseDetails>()) {
+      if (&found->GetUltimate() == &sym) {
+        return std::nullopt; // already use-associated with same name
       }
     }
-    // Symbol is used in this scope but not visible under its name
-    if (sMod->parent().IsIntrinsicModules()) {
-      uses_ << "use,intrinsic::";
-    } else {
-      uses_ << "use ";
+    if (&sym.owner() != &found->owner()) { // Symbol needs renaming
+      return context_.SaveTempName(DEREF(symMod.symbol()).name().ToString() +
+          "$" + sym.name().ToString());
     }
-    uses_ << DEREF(sMod->symbol()).name() << ",only:";
-    if (rename != s->name()) {
-      uses_ << rename << "=>";
-      renamings.emplace(&s->GetUltimate(), rename);
+  }
+  return sym.name(); // needs USE, no renaming
+}
+
+void ModFileWriter::PutRenamedSymbolUse(const Scope &scope, const Symbol &sym) {
+  if (const Scope *symMod{FindModuleContaining(sym.owner())}) {
+    if (auto rename{GetUseName(scope, sym, *symMod)}) {
+      // Symbol is used in this scope but not visible under its name
+      if (symMod->parent().IsIntrinsicModules()) {
+        uses_ << "use,intrinsic::";
+      } else {
+        uses_ << "use ";
+      }
+      uses_ << DEREF(symMod->symbol()).name() << ",only:";
+      if (*rename != sym.name()) {
+        uses_ << *rename << "=>";
+        context_.moduleFileOutputRenamings().emplace(
+            &sym.GetUltimate(), *rename);
+      }
+      uses_ << sym.name() << '\n';
+      useExtraAttrs_ << "private::" << *rename << '\n';
     }
-    uses_ << s->name() << '\n';
-    useExtraAttrs_ << "private::" << rename << '\n';
   }
 }
 
 // Put out the visible symbols from scope.
-void ModFileWriter::PutSymbols(
-    const Scope &scope, UnorderedSymbolSet *hermeticModules) {
+void ModFileWriter::PutSymbols(const Scope &scope, bool omitModules) {
   SymbolVector sorted;
   SymbolVector uses;
   auto &renamings{context_.moduleFileOutputRenamings()};
@@ -369,12 +318,10 @@ void ModFileWriter::PutSymbols(
   PrepareRenamings(scope);
   SourceOrderedSymbolSet modules;
   CollectSymbols(scope, sorted, uses, modules);
-  // Write module files for dependencies first so that their
+  // Write module files for compiled dependency modules first so that their
   // hashes are known.
-  for (const Symbol &mod : modules) {
-    if (hermeticModules) {
-      hermeticModules->insert(mod);
-    } else {
+  if (!omitModules) {
+    for (const Symbol &mod : modules) {
       Write(mod);
       // It's possible that the module's file already existed and
       // without its own hash due to being embedded in a hermetic
@@ -412,6 +359,127 @@ void ModFileWriter::PutSymbols(
   renamings = std::move(previousRenamings);
 }
 
+// Combines duplicate top-level symbols, which can arise when
+// merging dependency modules from hermetic module files.
+static SymbolVector CombineDuplicateSymbols(const SymbolVector &originals) {
+  std::map<SourceName, const Symbol *> distinct;
+  SymbolVector symbols;
+  symbols.reserve(originals.size());
+  for (const Symbol &symbol : originals) {
+    if (symbol.owner().IsModule()) {
+      if (auto pair{distinct.emplace(symbol.name(), &symbol)}; !pair.second) {
+        // name already present
+        if (symbol.has<GenericDetails>()) {
+          pair.first->second = &symbol;
+        } else {
+          continue;
+        }
+      }
+    }
+    symbols.emplace_back(symbol);
+  }
+  return symbols;
+}
+
+std::string ModFileWriter::PutDependencyModules(
+    std::string originalModuleName, const SymbolVector &order) {
+  // Partition symbols by module name.
+  // Ignore symbols from intrinsic modules and the original module.
+  std::map<std::string, SymbolVector> perModuleName;
+  for (const Symbol &symbol : order) {
+    if (const Scope *module{FindModuleContaining(symbol.owner())}) {
+      if (!module->parent().IsIntrinsicModules()) {
+        if (auto name{module->GetName()}) {
+          if (*name != originalModuleName) {
+            perModuleName[name->ToString()].emplace_back(symbol);
+          }
+        }
+      }
+    }
+  }
+  std::string result;
+  for (const auto &[moduleName, symbols] : perModuleName) {
+    SymbolVector distinct{CombineDuplicateSymbols(symbols)};
+    result += PutDependencyModule(moduleName, distinct);
+  }
+  return result;
+}
+
+std::string ModFileWriter::PutDependencyModule(
+    const std::string &moduleName, const SymbolVector &needed) {
+  const Scope &scope{DEREF(FindModuleContaining(needed.front()->owner()))};
+  CHECK(scope.IsModule());
+  // The needed symbols for this module may depend on use-associated
+  // symbols from other dependency modules, so collect them.
+  SymbolVector symbols{CollectAllDependences(
+      needed, IncludeOriginalSymbols | IncludeUsesOfGenerics, &scope)};
+  SymbolVector order, namelists, generics;
+  std::set<std::string> names, commonNames, genericNames;
+  order.reserve(symbols.size());
+  for (const Symbol &symbol : symbols) {
+    std::string symbolName{symbol.name().ToString()};
+    if (symbol.test(Symbol::Flag::ParentComp) ||
+        symbol.test(Symbol::Flag::CompilerCreated) ||
+        !symbol.owner().IsModule()) {
+    } else if (symbol.has<CommonBlockDetails>()) {
+      if (commonNames.find(symbolName) == commonNames.end()) {
+        order.push_back(symbol);
+        commonNames.insert(symbolName);
+      }
+    } else if (const auto *generic{symbol.detailsIf<GenericDetails>()}) {
+      if (names.find(symbolName) == names.end()) {
+        if (generic->specific() &&
+            &generic->specific()->owner() == &symbol.owner()) {
+          order.push_back(*generic->specific());
+          names.insert(symbolName);
+        } else if (generic->derivedType() &&
+            &generic->derivedType()->owner() == &symbol.owner()) {
+          order.push_back(*generic->derivedType());
+          names.insert(symbolName);
+        }
+      }
+      if (genericNames.find(symbolName) == genericNames.end()) {
+        generics.push_back(symbol);
+        genericNames.insert(symbolName);
+      }
+    } else if (names.find(symbolName) != names.end()) {
+      if (const auto *use{symbol.detailsIf<UseDetails>()}) {
+        if (use->symbol().GetUltimate().has<GenericDetails>()) {
+          order.push_back(symbol); // pmk duplicates?
+        }
+      }
+    } else if (symbol.has<NamelistDetails>()) {
+      namelists.push_back(symbol);
+      names.insert(symbolName);
+    } else {
+      order.push_back(symbol);
+      names.insert(symbolName);
+    }
+  }
+  order.insert(order.end(), generics.begin(), generics.end());
+  order.insert(order.end(), namelists.begin(), namelists.end());
+  // Emit the symbols
+  std::string buf;
+  llvm::raw_string_ostream typeBindings{buf};
+  auto &renamings{context_.moduleFileOutputRenamings()};
+  auto previousRenamings{std::move(renamings)};
+  UseRenameMap useMap{GetUseRenamings(order)};
+  for (const Symbol &sym : order) {
+    if (auto iter{useMap.find(&sym)}; iter != useMap.end()) {
+      renamings.emplace(&sym, iter->second->name());
+    } else if (const Scope *from{FindModuleContaining(sym.owner())};
+        from && from->GetName().value().ToString() != moduleName) {
+      PutRenamedSymbolUse(scope, sym);
+    } else {
+      PutSymbol(typeBindings, sym);
+    }
+  }
+  // pmk TODO: equivalence sets
+  CHECK(typeBindings.str().empty());
+  renamings = std::move(previousRenamings);
+  return GetAsString(nullptr, moduleName);
+}
+
 // Emit components in order
 bool ModFileWriter::PutComponents(const Symbol &typeSymbol) {
   const auto &scope{DEREF(typeSymbol.scope())};
@@ -1578,14 +1646,15 @@ Scope *ModFileReader::Read(SourceName name, std::optional<bool> isIntrinsic,
   // their own nested scope that will be visible only to USE statements
   // within the module file.
   Scope *previousHermetic{context_.currentHermeticModuleFileScope()};
+  Scope *hermeticScope{nullptr};
   if (parseTree.v.size() > 1) {
     parser::Program hermeticModules{std::move(parseTree.v)};
     parseTree.v.emplace_back(std::move(hermeticModules.v.front()));
     hermeticModules.v.pop_front();
-    Scope &hermeticScope{topScope.MakeScope(Scope::Kind::Global)};
-    context_.set_currentHermeticModuleFileScope(&hermeticScope);
-    ResolveNames(context_, hermeticModules, hermeticScope);
-    for (auto &[_, ref] : hermeticScope) {
+    hermeticScope = &topScope.MakeScope(Scope::Kind::Global);
+    context_.set_currentHermeticModuleFileScope(hermeticScope);
+    ResolveNames(context_, hermeticModules, *hermeticScope);
+    for (auto &[_, ref] : *hermeticScope) {
       CHECK(ref->has<ModuleDetails>());
       ref->set(Symbol::Flag::ModFile);
     }
diff --git a/flang/lib/Semantics/mod-file.h b/flang/lib/Semantics/mod-file.h
index 9e5724089b3c5..309c23f78f44e 100644
--- a/flang/lib/Semantics/mod-file.h
+++ b/flang/lib/Semantics/mod-file.h
@@ -66,9 +66,16 @@ class ModFileWriter {
   void WriteAll(const Scope &);
   void WriteOne(const Scope &);
   void Write(const Symbol &);
-  std::string GetAsString(const Symbol &);
+  std::string GetAsString(const Symbol *, std::string);
   void PrepareRenamings(const Scope &);
-  void PutSymbols(const Scope &, UnorderedSymbolSet *hermetic);
+  std::optional<SourceName> GetUseName(
+      const Scope &, const Symbol &, const Scope &symMod);
+  void PutRenamedSymbolUse(const Scope &, const Symbol &);
+  void PutSymbols(const Scope &, bool omitModules);
+  std::string PutDependencyModules(
+      std::string originalModuleName, const SymbolVector &);
+  std::string PutDependencyModule(
+      const std::string &modName, const SymbolVector &);
   // Returns true if a derived type with bindings and "contains" was emitted
   bool PutComponents(const Symbol &);
   void PutSymbol(llvm::raw_ostream &, const Symbol &);
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index d0336c9cb661d..b3bbd682bde60 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -3504,8 +3504,7 @@ ModuleVisitor::SymbolRename ModuleVisitor::AddUse(
         useModuleScope_->GetName().value());
     return {};
   }
-  if (useSymbol->attrs().test(Attr::PRIVATE) &&
-      !FindModuleFileContaining(currScope())) {
+  if (useSymbol->attrs().test(Attr::PRIVATE) && !IsInModuleFile(currScope())) {
     // Privacy is not enforced in module files so that generic interfaces
     // can be resolved to private specific procedures in specification
     // expressions.
@@ -3617,10 +3616,11 @@ static bool CheckCompatibleDistinctUltimates(SemanticsContext &context,
     if (const auto *useObject{useUltimate.detailsIf<ObjectEntityDetails>()}) {
       auto localType{evaluate::DynamicType::From(localUltimate)};
       auto useType{evaluate::DynamicType::From(useUltimate)};
-      if (localUltimate.size() != useUltimate.size() ||
-          (localType &&
-              (!useType || !localType->IsTkLenCompatibleWith(*useType) ||
-                  !useType->IsTkLenCompatibleWith(*localType))) ||
+      if (localUltimate.size() != useUltimate.size()) {
+        isError = true;
+      } else if ((localType &&
+                     (!useType || !localType->IsTkLenCompatibleWith(*useType) ||
+                         !useType->IsTkLenCompatibleWith(*localType))) ||
           (!localType && useType)) {
         isError = true;
       } else if (IsNamedConstant(localUltimate)) {
@@ -7424,7 +7424,8 @@ void DeclarationVisitor::SetType(
 std::optional<DerivedTypeSpec> DeclarationVisitor::ResolveDerivedType(
     const parser::Name &name) {
   Scope &outer{NonDerivedTypeScope()};
-  Symbol *symbol{FindSymbol(outer, name)};
+  Symbol *original{FindSymbol(outer, name)};
+  Symbol *symbol{original};
   Symbol *ultimate{symbol ? &symbol->GetUltimate() : nullptr};
   auto *generic{ultimate ? ultimate->detailsIf<GenericDetails>() : nullptr};
   if (generic) {
@@ -7437,11 +7438,12 @@ std::optional<DerivedTypeSpec> DeclarationVisitor::ResolveDerivedType(
       (generic && &ultimate->owner() == &outer)) {
     if (allowForwardReferenceToDerivedType()) {
       if (!symbol) {
-        symbol = &MakeSymbol(outer, name.source, Attrs{});
+        symbol = original = &MakeSymbol(outer, name.source, Attrs{});
         Resolve(name, *symbol);
       } else if (generic) {
         // forward ref to type with later homonymous generic
-        symbol = &outer.MakeSymbol(name.source, Attrs{}, UnknownDetails{});
+        symbol = original =
+            &outer.MakeSymbol(name.source, Attrs{}, UnknownDetails{});
         generic->set_derivedType(*symbol);
         name.symbol = symbol;
       }
@@ -7461,7 +7463,7 @@ std::optional<DerivedTypeSpec> DeclarationVisitor::ResolveDerivedType(
   if (CheckUseError(name)) {
     return std::nullopt;
   } else if (symbol->GetUltimate().has<DerivedTypeDetails>()) {
-    return DerivedTypeSpec{name.source, *symbol};
+    return DerivedTypeSpec{name.source, *original};
   } else {
     Say(name, "'%s' is not a derived type"_err_en_US);
     return std::nullopt;
diff --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp
index ab78605d01f4c..ad901e75820f4 100644
--- a/flang/lib/Semantics/semantics.cpp
+++ b/flang/lib/Semantics/semantics.cpp
@@ -42,6 +42,7 @@
 #include "flang/Semantics/expression.h"
 #include "flang/Semantics/scope.h"
 #include "flang/Semantics/symbol.h"
+#include "flang/Semantics/tools.h"
 #include "flang/Support/default-kinds.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TargetParser/Host.h"
@@ -456,13 +457,7 @@ void SemanticsContext::UpdateScopeIndex(
 }
 
 bool SemanticsContext::IsInModuleFile(parser::CharBlock source) const {
-  for (const Scope *scope{&FindScope(source)}; !scope->IsGlobal();
-       scope = &scope->parent()) {
-    if (scope->IsModuleFile()) {
-      return true;
-    }
-  }
-  return false;
+  return semantics::IsInModuleFile(FindScope(source));
 }
 
 void SemanticsContext::PopConstruct() {
@@ -648,7 +643,8 @@ bool Semantics::Perform() {
       PerformStatementSemantics(context_, program_) &&
       CanonicalizeDirectives(context_.messages(), program_) &&
       ModFileWriter{context_}
-          .set_hermeticModuleFileOutput(hermeticModuleFileOutput_)
+          .set_hermeticModuleFileOutput(
+              hermeticModuleFileOutput_ || getenv("PMK_HERMETIC"))
           .WriteAll();
 }
 
diff --git a/flang/lib/Semantics/symbol-dependence.cpp b/flang/lib/Semantics/symbol-dependence.cpp
new file mode 100644
index 0000000000000..2591f609f3d00
--- /dev/null
+++ b/flang/lib/Semantics/symbol-dependence.cpp
@@ -0,0 +1,356 @@
+//===-- lib/Semantics/symbol-dependence.cpp -------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Semantics/symbol-dependence.h"
+#include "flang/Common/idioms.h"
+#include "flang/Common/restorer.h"
+#include "flang/Common/visit.h"
+#include <queue>
+
+static constexpr bool EnableDebugging{false};
+
+namespace Fortran::semantics {
+
+// Helper class that collects all of the symbol dependences for a
+// given symbol.
+class Collector {
+public:
+  explicit Collector(int flags) : flags_{flags} {}
+
+  void CollectSymbolDependences(const Symbol &);
+  UnorderedSymbolSet MustFollowDependences() { return std::move(dependences_); }
+  SymbolVector AllDependences() { return std::move(mentions_); }
+
+private:
+  // This symbol is depended upon and its declaration must precede
+  // the symbol of interest.
+  void MustFollow(const Symbol &x) {
+    if (!possibleImports_ || !DoesScopeContain(possibleImports_, x)) {
+      dependences_.insert(x);
+    }
+  }
+  // This symbol is depended upon, but is not necessarily a dependence
+  // that must precede the symbol of interest in the output of the
+  // topological sort.
+  void Need(const Symbol &x) {
+    if (mentioned_.insert(x).second) {
+      mentions_.emplace_back(x);
+    }
+  }
+  void Need(const Symbol *x) {
+    if (x) {
+      Need(*x);
+    }
+  }
+
+  // These overloads of Collect() are mutally recursive, so they're
+  // packaged as member functions of a class.
+  void Collect(const Symbol &x) {
+    Need(x);
+    const auto *subp{x.detailsIf<SubprogramDetails>()};
+    if ((subp && subp->isInterface()) || IsDummy(x) ||
+        x.has<CommonBlockDetails>() || x.has<NamelistDetails>()) {
+      // can be forward-referenced
+    } else {
+      MustFollow(x);
+    }
+  }
+  void Collect(SymbolRef x) { Collect(*x); }
+  template <typename A> void Collect(const std::optional<A> &x) {
+    if (x) {
+      Collect(*x);
+    }
+  }
+  template <typename A> void Collect(const A *x) {
+    if (x) {
+      Collect(*x);
+    }
+  }
+  void Collect(const UnorderedSymbolSet &x) {
+    for (const Symbol &symbol : x) {
+      Collect(symbol);
+    }
+  }
+  void Collect(const SourceOrderedSymbolSet &x) {
+    for (const Symbol &symbol : x) {
+      Collect(symbol);
+    }
+  }
+  void Collect(const SymbolVector &x) {
+    for (const Symbol &symbol : x) {
+      Collect(symbol);
+    }
+  }
+  void Collect(const Scope &x) { Collect(x.GetSymbols()); }
+  template <typename T> void Collect(const evaluate::Expr<T> &x) {
+    UnorderedSymbolSet exprSyms{evaluate::CollectSymbols(x)};
+    for (const Symbol &sym : exprSyms) {
+      if (!sym.owner().IsDerivedType()) {
+        Collect(sym);
+      }
+    }
+  }
+  void Collect(const DeclTypeSpec &type) {
+    if (type.category() == DeclTypeSpec::Category::Character) {
+      Collect(type.characterTypeSpec().length());
+    } else {
+      Collect(type.AsDerived());
+    }
+  }
+  void Collect(const DerivedTypeSpec &type) {
+    const Symbol &typeSym{type.originalTypeSymbol()};
+    if (!derivedTypeReferenceCanBeForward_ || !type.parameters().empty()) {
+      MustFollow(typeSym);
+    }
+    Need(typeSym);
+    for (const auto &[_, value] : type.parameters()) {
+      Collect(value);
+    }
+  }
+  void Collect(const ParamValue &x) { Collect(x.GetExplicit()); }
+  void Collect(const Bound &x) { Collect(x.GetExplicit()); }
+  void Collect(const ShapeSpec &x) {
+    Collect(x.lbound());
+    Collect(x.ubound());
+  }
+  void Collect(const ArraySpec &x) {
+    for (const ShapeSpec &shapeSpec : x) {
+      Collect(shapeSpec);
+    }
+  }
+
+  UnorderedSymbolSet mentioned_, dependences_;
+  SymbolVector mentions_;
+  int flags_{NoDependenceCollectionFlags};
+  bool derivedTypeReferenceCanBeForward_{false};
+  const Scope *possibleImports_{nullptr};
+};
+
+void Collector::CollectSymbolDependences(const Symbol &symbol) {
+  if (symbol.has<ProcBindingDetails>() || symbol.has<SubprogramDetails>()) {
+    // type will be picked up later for the function result, if any
+  } else if (symbol.has<UseDetails>() || symbol.has<UseErrorDetails>() ||
+      symbol.has<HostAssocDetails>()) {
+  } else if (IsAllocatableOrPointer(symbol) && symbol.owner().IsDerivedType()) {
+    bool saveCanBeForward{derivedTypeReferenceCanBeForward_};
+    derivedTypeReferenceCanBeForward_ = true;
+    Collect(symbol.GetType());
+    derivedTypeReferenceCanBeForward_ = saveCanBeForward;
+  } else {
+    Collect(symbol.GetType());
+  }
+  common::visit(
+      common::visitors{
+          [this, &symbol](const ObjectEntityDetails &x) {
+            Collect(x.shape());
+            Collect(x.coshape());
+            if (IsNamedConstant(symbol) || symbol.owner().IsDerivedType()) {
+              Collect(x.init());
+            }
+            Need(x.commonBlock());
+            if (const auto *set{FindEquivalenceSet(symbol)}) {
+              for (const EquivalenceObject &equivObject : *set) {
+                Need(equivObject.symbol);
+              }
+            }
+          },
+          [this, &symbol](const ProcEntityDetails &x) {
+            Collect(x.rawProcInterface());
+            if (symbol.owner().IsDerivedType()) {
+              Collect(x.init());
+            }
+          },
+          [this](const ProcBindingDetails &x) { Need(x.symbol()); },
+          [this, &symbol](const SubprogramDetails &x) {
+            // Note dummy arguments & result symbol without dependence, unless
+            // the subprogram is an interface block that might need to IMPORT
+            // a type.
+            bool needImports{x.isInterface()};
+            auto restorer{common::ScopedSet(
+                possibleImports_, needImports ? symbol.scope() : nullptr)};
+            for (const Symbol *dummy : x.dummyArgs()) {
+              if (dummy) {
+                Need(*dummy);
+                if (needImports) {
+                  CollectSymbolDependences(*dummy);
+                }
+              }
+            }
+            if (x.isFunction()) {
+              Need(x.result());
+              if (needImports) {
+                CollectSymbolDependences(x.result());
+              }
+            }
+          },
+          [this, &symbol](const DerivedTypeDetails &x) {
+            Collect(symbol.scope());
+            for (const auto &[_, symbolRef] : x.finals()) {
+              Need(*symbolRef);
+            }
+          },
+          [this](const GenericDetails &x) {
+            Collect(x.derivedType());
+            Collect(x.specific());
+            if (flags_ & IncludeUsesOfGenerics) {
+              for (const Symbol &use : x.uses()) {
+                Collect(use);
+              }
+            }
+            if (flags_ & IncludeSpecificsOfGenerics) {
+              for (const Symbol &specific : x.specificProcs()) {
+                Collect(specific);
+              }
+            }
+          },
+          [this](const NamelistDetails &x) {
+            for (const Symbol &symbol : x.objects()) {
+              Collect(symbol);
+            }
+          },
+          [this](const CommonBlockDetails &x) {
+            for (auto ref : x.objects()) {
+              Collect(*ref);
+            }
+          },
+          [this](const UseDetails &x) {
+            if (flags_ & FollowUseAssociations) {
+              Need(x.symbol());
+            }
+          },
+          [this](const HostAssocDetails &x) { Need(x.symbol()); },
+          [](const auto &) {},
+      },
+      symbol.details());
+}
+
+SymbolVector CollectAllDependences(const Scope &scope, int flags) {
+  SymbolVector basis{scope.GetSymbols()};
+  return CollectAllDependences(basis, flags, &scope);
+}
+
+// Returns a vector of symbols, topologically sorted by dependence
+SymbolVector CollectAllDependences(
+    const SymbolVector &original, int flags, const Scope *forScope) {
+  std::queue<const Symbol *> work;
+  UnorderedSymbolSet enqueued;
+  for (const Symbol &symbol : original) {
+    if (!symbol.test(Symbol::Flag::CompilerCreated)) {
+      work.push(&symbol);
+      enqueued.insert(symbol);
+    }
+  }
+  // For each symbol, collect its dependences into "topology".
+  // The "visited" vector and "enqueued" set hold all of the
+  // symbols considered.
+  std::map<const Symbol *, UnorderedSymbolSet> topology;
+  std::vector<const Symbol *> visited;
+  visited.reserve(2 * original.size());
+  std::optional<SourceName> forModuleName;
+  if (forScope && !(flags & NotJustForOneModule)) {
+    if (const Scope *forModule{FindModuleContaining(*forScope)}) {
+      forModuleName = forModule->GetName();
+    }
+  }
+  while (!work.empty()) {
+    const Symbol &symbol{*work.front()};
+    work.pop();
+    visited.push_back(&symbol);
+    Collector collector{flags};
+    bool doCollection{true};
+    if (forModuleName) {
+      if (const Scope *symModule{FindModuleContaining(symbol.owner())}) {
+        if (auto symModName{symModule->GetName()}) {
+          doCollection = *forModuleName == *symModName;
+        }
+      }
+    }
+    if (doCollection) {
+      collector.CollectSymbolDependences(symbol);
+    }
+    auto dependences{collector.MustFollowDependences()};
+    auto mentions{collector.AllDependences()};
+    if constexpr (EnableDebugging) {
+      for (const Symbol &need : dependences) {
+        llvm::errs() << "symbol " << symbol << " must follow " << need << '\n';
+      }
+      for (const Symbol &need : mentions) {
+        llvm::errs() << "symbol " << symbol << " needs " << need << '\n';
+      }
+    }
+    CHECK(topology.find(&symbol) == topology.end());
+    topology.emplace(&symbol, std::move(dependences));
+    for (const Symbol &symbol : mentions) {
+      if (!symbol.test(Symbol::Flag::CompilerCreated)) {
+        if (enqueued.insert(symbol).second) {
+          work.push(&symbol);
+        }
+      }
+    }
+  }
+  CHECK(enqueued.size() == visited.size());
+  // Topological sorting
+  // Subtle: This inverted topology map uses a SymbolVector, not a set
+  // of symbols, so that the order of symbols in the final output remains
+  // deterministic.
+  std::map<const Symbol *, SymbolVector> invertedTopology;
+  for (const Symbol *symbol : visited) {
+    invertedTopology[symbol] = SymbolVector{};
+  }
+  std::map<const Symbol *, std::size_t> numWaitingFor;
+  for (const Symbol *symbol : visited) {
+    auto topoIter{topology.find(symbol)};
+    CHECK(topoIter != topology.end());
+    const auto &needs{topoIter->second};
+    if (needs.empty()) {
+      work.push(symbol);
+    } else {
+      numWaitingFor[symbol] = needs.size();
+      for (const Symbol &need : needs) {
+        invertedTopology[&need].push_back(*symbol);
+      }
+    }
+  }
+  CHECK(visited.size() == work.size() + numWaitingFor.size());
+  SymbolVector resultVector;
+  while (!work.empty()) {
+    const Symbol &symbol{*work.front()};
+    work.pop();
+    resultVector.push_back(symbol);
+    auto enqueuedIter{enqueued.find(symbol)};
+    CHECK(enqueuedIter != enqueued.end());
+    enqueued.erase(enqueuedIter);
+    if (auto invertedIter{invertedTopology.find(&symbol)};
+        invertedIter != invertedTopology.end()) {
+      for (const Symbol &neededBy : invertedIter->second) {
+        std::size_t stillAwaiting{numWaitingFor[&neededBy] - 1};
+        if (stillAwaiting == 0) {
+          work.push(&neededBy);
+        } else {
+          numWaitingFor[&neededBy] = stillAwaiting;
+        }
+      }
+    }
+  }
+  if constexpr (EnableDebugging) {
+    llvm::errs() << "Topological sort failed in CollectAllDependences\n";
+    for (const Symbol &remnant : enqueued) {
+      auto topoIter{topology.find(&remnant)};
+      CHECK(topoIter != topology.end());
+      llvm::errs() << "  remnant symbol " << remnant << " needs:\n";
+      for (const Symbol &n : topoIter->second) {
+        llvm::errs() << "   " << n << '\n';
+      }
+    }
+  }
+  CHECK(enqueued.empty());
+  CHECK(resultVector.size() == visited.size());
+  return resultVector;
+}
+
+} // namespace Fortran::semantics
diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp
index 0380207927ad3..23bb16ad30106 100644
--- a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -277,13 +277,27 @@ void GenericDetails::CopyFrom(const GenericDetails &from) {
     CHECK(!derivedType_ || derivedType_ == from.derivedType_);
     derivedType_ = from.derivedType_;
   }
-  for (std::size_t i{0}; i < from.specificProcs_.size(); ++i) {
-    if (llvm::none_of(specificProcs_, [&](const Symbol &mySymbol) {
-          return &mySymbol.GetUltimate() ==
-              &from.specificProcs_[i]->GetUltimate();
-        })) {
-      specificProcs_.push_back(from.specificProcs_[i]);
-      bindingNames_.push_back(from.bindingNames_[i]);
+  for (std::size_t j{0}; j < from.specificProcs_.size(); ++j) {
+    auto fromSpecific{from.specificProcs_[j]};
+    SourceName fromBinding{from.bindingNames_[j]};
+    const Symbol &fromUltimate{fromSpecific->GetUltimate()};
+    const Scope *fromModule{FindModuleContaining(fromUltimate.owner())};
+    auto fromModuleName{fromModule ? fromModule->GetName() : std::nullopt};
+    bool addit{true};
+    for (std::size_t k{0}; addit && k < specificProcs_.size(); ++k) {
+      const Symbol &ultimate{specificProcs_[k]->GetUltimate()};
+      if (&fromUltimate == &ultimate) {
+        addit = false;
+      } else if (fromBinding == bindingNames_[k]) {
+        const Scope *module{FindModuleContaining(ultimate.owner())};
+        auto moduleName{module ? module->GetName() : std::nullopt};
+        addit =
+            !(fromModuleName && moduleName && *fromModuleName == *moduleName);
+      }
+    }
+    if (addit) {
+      specificProcs_.push_back(fromSpecific);
+      bindingNames_.push_back(fromBinding);
     }
   }
 }
diff --git a/flang/lib/Semantics/tools.cpp b/flang/lib/Semantics/tools.cpp
index d27d250b3f11e..17a76becc1306 100644
--- a/flang/lib/Semantics/tools.cpp
+++ b/flang/lib/Semantics/tools.cpp
@@ -6,15 +6,15 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "flang/Parser/tools.h"
+#include "flang/Semantics/tools.h"
 #include "flang/Common/indirection.h"
 #include "flang/Parser/dump-parse-tree.h"
 #include "flang/Parser/message.h"
 #include "flang/Parser/parse-tree.h"
+#include "flang/Parser/tools.h"
 #include "flang/Semantics/scope.h"
 #include "flang/Semantics/semantics.h"
 #include "flang/Semantics/symbol.h"
-#include "flang/Semantics/tools.h"
 #include "flang/Semantics/type.h"
 #include "flang/Support/Fortran.h"
 #include "llvm/Support/raw_ostream.h"
@@ -58,9 +58,16 @@ const Scope *FindModuleOrSubmoduleContaining(const Scope &start) {
   });
 }
 
-const Scope *FindModuleFileContaining(const Scope &start) {
-  return FindScopeContaining(
-      start, [](const Scope &scope) { return scope.IsModuleFile(); });
+bool IsInModuleFile(const Scope &start) {
+  for (const Scope *scope{&start};; scope = &scope->parent()) {
+    if (scope->IsModuleFile() ||
+        scope == scope->context().currentHermeticModuleFileScope()) {
+      return true;
+    } else if (scope->IsTopLevel()) {
+      break;
+    }
+  }
+  return false;
 }
 
 const Scope &GetProgramUnitContaining(const Scope &start) {
@@ -1158,7 +1165,7 @@ std::optional<parser::MessageFormattedText> CheckAccessibleSymbol(
     const Scope &scope, const Symbol &symbol) {
   if (IsAccessible(symbol, scope)) {
     return std::nullopt;
-  } else if (FindModuleFileContaining(scope)) {
+  } else if (IsInModuleFile(scope)) {
     // Don't enforce component accessibility checks in module files;
     // there may be forward-substituted named constants of derived type
     // whose structure constructors reference private components.
@@ -1845,4 +1852,18 @@ bool HadUseError(
   }
 }
 
+bool CheckForSymbolMatch(const SomeExpr *lhs, const SomeExpr *rhs) {
+  if (lhs && rhs) {
+    if (SymbolVector lhsSymbols{evaluate::GetSymbolVector(*lhs)};
+        !lhsSymbols.empty()) {
+      const Symbol &first{*lhsSymbols.front()};
+      for (const Symbol &symbol : evaluate::GetSymbolVector(*rhs)) {
+        if (first == symbol) {
+          return true;
+        }
+      }
+    }
+  }
+  return false;
+}
 } // namespace Fortran::semantics
diff --git a/flang/lib/Semantics/type.cpp b/flang/lib/Semantics/type.cpp
index 964a37e1c822b..b8f35c9bf4507 100644
--- a/flang/lib/Semantics/type.cpp
+++ b/flang/lib/Semantics/type.cpp
@@ -22,9 +22,19 @@
 
 namespace Fortran::semantics {
 
+static const Symbol &ResolveOriginalTypeSymbol(const Symbol *symbol) {
+  symbol = &symbol->GetUltimate();
+  if (const auto *generic{symbol->detailsIf<GenericDetails>()}) {
+    CHECK(generic->derivedType() != nullptr);
+    return generic->derivedType()->GetUltimate();
+  } else {
+    return *symbol;
+  }
+}
+
 DerivedTypeSpec::DerivedTypeSpec(SourceName name, const Symbol &typeSymbol)
     : name_{name}, originalTypeSymbol_{typeSymbol},
-      typeSymbol_{typeSymbol.GetUltimate()} {
+      typeSymbol_{ResolveOriginalTypeSymbol(&typeSymbol)} {
   CHECK(typeSymbol_.has<DerivedTypeDetails>());
 }
 DerivedTypeSpec::DerivedTypeSpec(const DerivedTypeSpec &that) = default;
@@ -252,16 +262,12 @@ static bool MatchKindParams(const Symbol &typeSymbol,
 }
 
 bool DerivedTypeSpec::MatchesOrExtends(const DerivedTypeSpec &that) const {
-  const Symbol *typeSymbol{&typeSymbol_};
-  while (typeSymbol != &that.typeSymbol_) {
-    if (const DerivedTypeSpec *
-        parent{typeSymbol->GetParentTypeSpec(typeSymbol->scope())}) {
-      typeSymbol = &parent->typeSymbol_;
-    } else {
-      return false;
-    }
+  const DerivedTypeSpec *type{this};
+  while (type &&
+      !evaluate::AreSameDerivedTypeIgnoringTypeParameters(*type, that)) {
+    type = type->typeSymbol_.GetParentTypeSpec(type->typeSymbol_.scope());
   }
-  return MatchKindParams(*typeSymbol, *this, that);
+  return type && MatchKindParams(type->typeSymbol_, *this, that);
 }
 
 class InstantiateHelper {
diff --git a/flang/test/Semantics/modfile03.f90 b/flang/test/Semantics/modfile03.f90
index eb3136f0aa8bc..399547ca41958 100644
--- a/flang/test/Semantics/modfile03.f90
+++ b/flang/test/Semantics/modfile03.f90
@@ -69,7 +69,7 @@ pure integer function f1(i)
 module m5b
   use m5a, only: k2 => k1, l2 => l1, f2 => f1
   interface
-    subroutine s(x, y)
+    subroutine s5b(x, y)
       import f2, l2
       character(l2, k2) :: x
       character(f2(l2)) :: y
@@ -82,7 +82,7 @@ subroutine s(x, y)
 ! use m5a,only:l2=>l1
 ! use m5a,only:f2=>f1
 ! interface
-!  subroutine s(x,y)
+!  subroutine s5b(x,y)
 !   import::f2
 !   import::l2
 !   character(l2,4)::x
@@ -142,7 +142,7 @@ module m6d
 module m6e
   use m6a, only: t2 => t1
   interface
-    subroutine s(x)
+    subroutine s6e(x)
       import t2
       type(t2) :: x
     end subroutine
@@ -152,7 +152,7 @@ subroutine s(x)
 !module m6e
 ! use m6a,only:t2=>t1
 ! interface
-!  subroutine s(x)
+!  subroutine s6e(x)
 !   import::t2
 !   type(t2)::x
 !  end
diff --git a/flang/test/Semantics/modfile65.f90 b/flang/test/Semantics/modfile65.f90
index 249255e02129f..3eabf47d4d55e 100644
--- a/flang/test/Semantics/modfile65.f90
+++ b/flang/test/Semantics/modfile65.f90
@@ -42,12 +42,12 @@ module m4
 !use m2,only:n
 !use m3,only:m
 !end
+!module m1
+!integer(4),parameter::n=123_4
+!end
 !module m2
 !use m1,only:n
 !end
 !module m3
 !use m1,only:m=>n
 !end
-!module m1
-!integer(4),parameter::n=123_4
-!end
diff --git a/flang/test/Semantics/modfile78.F90 b/flang/test/Semantics/modfile78.F90
index 19b9ac39de934..f612c2fa30d64 100644
--- a/flang/test/Semantics/modfile78.F90
+++ b/flang/test/Semantics/modfile78.F90
@@ -27,7 +27,6 @@ module modfile78c
 !CHECK: integer(4)::global_variable
 !CHECK: end
 !CHECK: module modfile78b
-!CHECK: use modfile78a,only:global_variable
 !CHECK: contains
 !CHECK: subroutine test()
 !CHECK: end

>From 28d4df0ce012af443c05a7e9b754123dc99db58f Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Mon, 7 Jul 2025 08:29:55 -0700
Subject: [PATCH 2/2] more

---
 flang/lib/Semantics/mod-file.cpp          | 49 +++++++++++++++++++----
 flang/lib/Semantics/mod-file.h            |  3 +-
 flang/lib/Semantics/symbol-dependence.cpp |  7 ++++
 3 files changed, 50 insertions(+), 9 deletions(-)

diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index 1a27449e5a786..f9b0c61fe0696 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -28,6 +28,8 @@
 #include <variant>
 #include <vector>
 
+static constexpr bool pruneHermeticModuleFiles{true};
+
 namespace Fortran::semantics {
 
 using namespace parser::literals;
@@ -144,14 +146,40 @@ void ModFileWriter::Write(const Symbol &symbol) {
       ModFileName(symbol.name(), ancestorName, context_.moduleFileSuffix())};
 
   SymbolVector dependenceClosure;
-  if (hermeticModuleFileOutput_ && !isSubmodule_) {
+  if (hermeticModuleFileOutput_ && !isSubmodule_ && pruneHermeticModuleFiles) {
     dependenceClosure = CollectAllDependences(DEREF(symbol.scope()),
         FollowUseAssociations | IncludeUsesOfGenerics |
             IncludeSpecificsOfGenerics | NotJustForOneModule);
   }
-  PutSymbols(DEREF(symbol.scope()), hermeticModuleFileOutput_);
+  UnorderedSymbolSet fullHermeticModules;
+  PutSymbols(DEREF(symbol.scope()), hermeticModuleFileOutput_,
+      hermeticModuleFileOutput_ && !pruneHermeticModuleFiles
+          ? &fullHermeticModules
+          : nullptr);
   auto asStr{GetAsString(&symbol, symbol.name().ToString())};
-  asStr += PutDependencyModules(symbol.name().ToString(), dependenceClosure);
+  if (!dependenceClosure.empty()) {
+    // Emit minimal modules on which this module depends, if emitting a
+    // hermetic module file
+    asStr += PutDependencyModules(symbol.name().ToString(), dependenceClosure);
+  } else if (!fullHermeticModules.empty()) {
+    // Emit full (complete) modules on which this module depends
+    std::set<std::string> hermeticModuleNames;
+    hermeticModuleNames.insert(symbol.name().ToString());
+    while (!fullHermeticModules.empty()) {
+      UnorderedSymbolSet nextPass{std::move(fullHermeticModules)};
+      fullHermeticModules.clear();
+      for (const Symbol &modSym : nextPass) {
+        if (!modSym.owner().IsIntrinsicModules() &&
+            hermeticModuleNames.find(modSym.name().ToString()) ==
+                hermeticModuleNames.end()) {
+          hermeticModuleNames.insert(modSym.name().ToString());
+          PutSymbols(DEREF(modSym.scope()), /*omitModules=*/false,
+              &fullHermeticModules);
+          asStr += GetAsString(&modSym, modSym.name().ToString());
+        }
+      }
+    }
+  }
   ModuleCheckSumType checkSum;
   if (std::error_code error{
           WriteFile(path, asStr, checkSum, context_.debugModuleWriter())}) {
@@ -167,7 +195,8 @@ void ModFileWriter::WriteClosure(llvm::raw_ostream &out, const Symbol &symbol,
       !nonIntrinsicModulesWritten.insert(symbol).second) {
     return;
   }
-  PutSymbols(DEREF(symbol.scope()), /*omitModules=*/false);
+  PutSymbols(DEREF(symbol.scope()), /*omitModules=*/false,
+      /*fullHermeticModules=*/nullptr);
   needsBuf_.clear(); // omit module checksums
   auto str{GetAsString(&symbol, symbol.name().ToString())};
   for (auto depRef : std::move(usedNonIntrinsicModules_)) {
@@ -310,7 +339,8 @@ void ModFileWriter::PutRenamedSymbolUse(const Scope &scope, const Symbol &sym) {
 }
 
 // Put out the visible symbols from scope.
-void ModFileWriter::PutSymbols(const Scope &scope, bool omitModules) {
+void ModFileWriter::PutSymbols(const Scope &scope, bool omitModules,
+    UnorderedSymbolSet *fullHermeticModules) {
   SymbolVector sorted;
   SymbolVector uses;
   auto &renamings{context_.moduleFileOutputRenamings()};
@@ -320,7 +350,11 @@ void ModFileWriter::PutSymbols(const Scope &scope, bool omitModules) {
   CollectSymbols(scope, sorted, uses, modules);
   // Write module files for compiled dependency modules first so that their
   // hashes are known.
-  if (!omitModules) {
+  if (fullHermeticModules) {
+    for (const Symbol &mod : modules) {
+      fullHermeticModules->insert(mod);
+    }
+  } else if (!omitModules) {
     for (const Symbol &mod : modules) {
       Write(mod);
       // It's possible that the module's file already existed and
@@ -445,7 +479,7 @@ std::string ModFileWriter::PutDependencyModule(
     } else if (names.find(symbolName) != names.end()) {
       if (const auto *use{symbol.detailsIf<UseDetails>()}) {
         if (use->symbol().GetUltimate().has<GenericDetails>()) {
-          order.push_back(symbol); // pmk duplicates?
+          order.push_back(symbol);
         }
       }
     } else if (symbol.has<NamelistDetails>()) {
@@ -474,7 +508,6 @@ std::string ModFileWriter::PutDependencyModule(
       PutSymbol(typeBindings, sym);
     }
   }
-  // pmk TODO: equivalence sets
   CHECK(typeBindings.str().empty());
   renamings = std::move(previousRenamings);
   return GetAsString(nullptr, moduleName);
diff --git a/flang/lib/Semantics/mod-file.h b/flang/lib/Semantics/mod-file.h
index 309c23f78f44e..6b25542c138ca 100644
--- a/flang/lib/Semantics/mod-file.h
+++ b/flang/lib/Semantics/mod-file.h
@@ -71,7 +71,8 @@ class ModFileWriter {
   std::optional<SourceName> GetUseName(
       const Scope &, const Symbol &, const Scope &symMod);
   void PutRenamedSymbolUse(const Scope &, const Symbol &);
-  void PutSymbols(const Scope &, bool omitModules);
+  void PutSymbols(
+      const Scope &, bool omitModules, UnorderedSymbolSet *fullHermeticModules);
   std::string PutDependencyModules(
       std::string originalModuleName, const SymbolVector &);
   std::string PutDependencyModule(
diff --git a/flang/lib/Semantics/symbol-dependence.cpp b/flang/lib/Semantics/symbol-dependence.cpp
index 2591f609f3d00..83ed2991203ea 100644
--- a/flang/lib/Semantics/symbol-dependence.cpp
+++ b/flang/lib/Semantics/symbol-dependence.cpp
@@ -158,6 +158,13 @@ void Collector::CollectSymbolDependences(const Symbol &symbol) {
                 Need(equivObject.symbol);
               }
             }
+            if (symbol.owner().IsModule()) {
+              if (const EquivalenceSet *equiv{FindEquivalenceSet(symbol)}) {
+                for (const EquivalenceObject &eqObj : *equiv) {
+                  Need(eqObj.symbol);
+                }
+              }
+            }
           },
           [this, &symbol](const ProcEntityDetails &x) {
             Collect(x.rawProcInterface());



More information about the flang-commits mailing list