[flang-commits] [flang] [flang] Support nested hermetic modules in hermetic module files (PR #142658)
Peter Klausler via flang-commits
flang-commits at lists.llvm.org
Tue Jun 3 14:08:18 PDT 2025
https://github.com/klausler updated https://github.com/llvm/llvm-project/pull/142658
>From d10882bcf0abb44f112ddb7fcb1b4d657a87d35b Mon Sep 17 00:00:00 2001
From: Peter Klausler <pklausler at nvidia.com>
Date: Mon, 2 Jun 2025 13:54:55 -0700
Subject: [PATCH] [flang] Support nested hermetic modules in hermetic module
files
When a module is built with -fhermetic-module-files, the non-intrinsic
modules on which it depends are copied into its module file so
that they don't have to be available on any search path when the
module file is parsed. When one of these nested modules was itself
built hermetically, its dependencies also need to be copied.
This can lead to problems with duplication. This patch adds
compiler directives to the module file to preserve the nesting
structure.
---
flang/docs/ModFiles.md | 5 +
flang/include/flang/Parser/dump-parse-tree.h | 2 +
flang/include/flang/Parser/parse-tree.h | 9 +-
flang/include/flang/Semantics/symbol.h | 3 +
flang/lib/Parser/Fortran-parsers.cpp | 31 ++--
flang/lib/Parser/unparse.cpp | 6 +
flang/lib/Semantics/mod-file.cpp | 175 +++++++++++++++----
flang/lib/Semantics/mod-file.h | 1 +
flang/lib/Semantics/symbol.cpp | 3 +
flang/test/Semantics/modfile76.F90 | 26 +++
10 files changed, 214 insertions(+), 47 deletions(-)
create mode 100644 flang/test/Semantics/modfile76.F90
diff --git a/flang/docs/ModFiles.md b/flang/docs/ModFiles.md
index fc05c2677fc26..b9047431c60ba 100644
--- a/flang/docs/ModFiles.md
+++ b/flang/docs/ModFiles.md
@@ -172,6 +172,11 @@ When the compiler reads a hermetic module file, the copies of the dependent
modules are read into their own scope, and will not conflict with other modules
of the same name that client code might `USE`.
+The copies of the module files can be copies of hermetic modules as well,
+in which case they and their dependencies are surrounded by compiler directives
+(`!DIR$ BEGIN_NESTED_HERMETIC_MODULE` and `!DIR$ END_NESTED_HERMETIC_MODULE`)
+to represent the nesting.
+
One can use the `-fhermetic-module-files` option when building the top-level
module files of a library for which not all of the implementation modules
will (or can) be shipped.
diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index df9278697346f..24f3b42a1c5c8 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -214,6 +214,8 @@ class ParseTreeDumper {
NODE(CompilerDirective, NoVector)
NODE(CompilerDirective, NoUnroll)
NODE(CompilerDirective, NoUnrollAndJam)
+ NODE(CompilerDirective, BeginNestedHermeticModule)
+ NODE(CompilerDirective, EndNestedHermeticModule)
NODE(parser, ComplexLiteralConstant)
NODE(parser, ComplexPart)
NODE(parser, ComponentArraySpec)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index c99006f0c1c22..0459ad46bab82 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -3354,6 +3354,8 @@ struct StmtFunctionStmt {
// !DIR$ NOVECTOR
// !DIR$ NOUNROLL
// !DIR$ NOUNROLL_AND_JAM
+// !DIR$ BEGIN_NESTED_HERMETIC_MODULE
+// !DIR$ END_NESTED_HERMETIC_MODULE
// !DIR$ <anything else>
struct CompilerDirective {
UNION_CLASS_BOILERPLATE(CompilerDirective);
@@ -3382,11 +3384,14 @@ struct CompilerDirective {
EMPTY_CLASS(NoVector);
EMPTY_CLASS(NoUnroll);
EMPTY_CLASS(NoUnrollAndJam);
+ EMPTY_CLASS(BeginNestedHermeticModule);
+ EMPTY_CLASS(EndNestedHermeticModule);
EMPTY_CLASS(Unrecognized);
CharBlock source;
std::variant<std::list<IgnoreTKR>, LoopCount, std::list<AssumeAligned>,
- VectorAlways, std::list<NameValue>, Unroll, UnrollAndJam, Unrecognized,
- NoVector, NoUnroll, NoUnrollAndJam>
+ VectorAlways, std::list<NameValue>, Unroll, UnrollAndJam, NoVector,
+ NoUnroll, NoUnrollAndJam, BeginNestedHermeticModule,
+ EndNestedHermeticModule, Unrecognized>
u;
};
diff --git a/flang/include/flang/Semantics/symbol.h b/flang/include/flang/Semantics/symbol.h
index 4cded64d170cd..434aebb0ca542 100644
--- a/flang/include/flang/Semantics/symbol.h
+++ b/flang/include/flang/Semantics/symbol.h
@@ -94,6 +94,8 @@ class ModuleDetails : public WithOmpDeclarative {
void set_moduleFileHash(ModuleCheckSumType x) { moduleFileHash_ = x; }
const Symbol *previous() const { return previous_; }
void set_previous(const Symbol *p) { previous_ = p; }
+ bool isHermetic() const { return isHermetic_; }
+ void set_isHermetic(bool yes = true) { isHermetic_ = yes; }
private:
bool isSubmodule_;
@@ -101,6 +103,7 @@ class ModuleDetails : public WithOmpDeclarative {
const Scope *scope_{nullptr};
std::optional<ModuleCheckSumType> moduleFileHash_;
const Symbol *previous_{nullptr}; // same name, different module file hash
+ bool isHermetic_{false};
};
class MainProgramDetails : public WithOmpDeclarative {
diff --git a/flang/lib/Parser/Fortran-parsers.cpp b/flang/lib/Parser/Fortran-parsers.cpp
index fbe629ab52935..d4c2fe8284b37 100644
--- a/flang/lib/Parser/Fortran-parsers.cpp
+++ b/flang/lib/Parser/Fortran-parsers.cpp
@@ -1294,6 +1294,8 @@ TYPE_PARSER(construct<StatOrErrmsg>("STAT =" >> statVariable) ||
// !DIR$ LOOP COUNT (n1[, n2]...)
// !DIR$ name[=value] [, name[=value]]...
// !DIR$ UNROLL [n]
+// !DIR$ BEGIN_NESTED_HERMETIC_MODULE
+// !DIR$ END_NESTED_HERMETIC_MODULE
// !DIR$ <anything else>
constexpr auto ignore_tkr{
"IGNORE_TKR" >> optionalList(construct<CompilerDirective::IgnoreTKR>(
@@ -1315,18 +1317,23 @@ constexpr auto nounroll{"NOUNROLL" >> construct<CompilerDirective::NoUnroll>()};
constexpr auto nounrollAndJam{
"NOUNROLL_AND_JAM" >> construct<CompilerDirective::NoUnrollAndJam>()};
TYPE_PARSER(beginDirective >> "DIR$ "_tok >>
- sourced((construct<CompilerDirective>(ignore_tkr) ||
- construct<CompilerDirective>(loopCount) ||
- construct<CompilerDirective>(assumeAligned) ||
- construct<CompilerDirective>(vectorAlways) ||
- construct<CompilerDirective>(unrollAndJam) ||
- construct<CompilerDirective>(unroll) ||
- construct<CompilerDirective>(novector) ||
- construct<CompilerDirective>(nounrollAndJam) ||
- construct<CompilerDirective>(nounroll) ||
- construct<CompilerDirective>(
- many(construct<CompilerDirective::NameValue>(
- name, maybe(("="_tok || ":"_tok) >> digitString64))))) /
+ sourced(
+ (construct<CompilerDirective>(ignore_tkr) ||
+ construct<CompilerDirective>(loopCount) ||
+ construct<CompilerDirective>(assumeAligned) ||
+ construct<CompilerDirective>(vectorAlways) ||
+ construct<CompilerDirective>(unrollAndJam) ||
+ construct<CompilerDirective>(unroll) ||
+ construct<CompilerDirective>(novector) ||
+ construct<CompilerDirective>(nounrollAndJam) ||
+ construct<CompilerDirective>(nounroll) ||
+ construct<CompilerDirective>("BEGIN_NESTED_HERMETIC_MODULE" >>
+ construct<CompilerDirective::BeginNestedHermeticModule>()) ||
+ construct<CompilerDirective>("End_NESTED_HERMETIC_MODULE" >>
+ construct<CompilerDirective::EndNestedHermeticModule>()) ||
+ construct<CompilerDirective>(
+ many(construct<CompilerDirective::NameValue>(
+ name, maybe(("="_tok || ":"_tok) >> digitString64))))) /
endOfStmt ||
construct<CompilerDirective>(pure<CompilerDirective::Unrecognized>()) /
SkipTo<'\n'>{}))
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 0784a6703bbde..146f212b1027e 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -1867,6 +1867,12 @@ class UnparseVisitor {
[&](const CompilerDirective::NoUnrollAndJam &) {
Word("!DIR$ NOUNROLL_AND_JAM");
},
+ [&](const CompilerDirective::BeginNestedHermeticModule &) {
+ Word("!DIR$ BEGIN_NESTED_HERMETIC_MODULE");
+ },
+ [&](const CompilerDirective::EndNestedHermeticModule &) {
+ Word("!DIR$ END_NESTED_HERMETIC_MODULE");
+ },
[&](const CompilerDirective::Unrecognized &) {
Word("!DIR$ ");
Word(x.source.ToString());
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index a1ec956562204..eb9c24c62adcd 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -142,23 +142,7 @@ void ModFileWriter::Write(const Symbol &symbol) {
auto ancestorName{ancestor ? ancestor->GetName().value().ToString() : ""s};
std::string path{context_.moduleDirectory() + '/' +
ModFileName(symbol.name(), ancestorName, context_.moduleFileSuffix())};
-
- UnorderedSymbolSet hermeticModules;
- hermeticModules.insert(symbol);
- UnorderedSymbolSet additionalModules;
- PutSymbols(DEREF(symbol.scope()),
- hermeticModuleFileOutput_ ? &additionalModules : nullptr);
- auto asStr{GetAsString(symbol)};
- while (!additionalModules.empty()) {
- for (auto ref : UnorderedSymbolSet{std::move(additionalModules)}) {
- if (hermeticModules.insert(*ref).second &&
- !ref->owner().IsIntrinsicModules()) {
- PutSymbols(DEREF(ref->scope()), &additionalModules);
- asStr += GetAsString(*ref);
- }
- }
- }
-
+ std::string asStr{WriteModuleAndDependents(symbol)};
ModuleCheckSumType checkSum;
if (std::error_code error{
WriteFile(path, asStr, checkSum, context_.debugModuleWriter())}) {
@@ -168,6 +152,31 @@ void ModFileWriter::Write(const Symbol &symbol) {
const_cast<ModuleDetails &>(module).set_moduleFileHash(checkSum);
}
+std::string ModFileWriter::WriteModuleAndDependents(const Symbol &symbol) {
+ UnorderedSymbolSet done, more;
+ done.insert(symbol);
+ PutSymbols(
+ DEREF(symbol.scope()), hermeticModuleFileOutput_ ? &more : nullptr);
+ auto asStr{GetAsString(symbol)};
+ while (!more.empty()) {
+ UnorderedSymbolSet toProcess{std::move(more)};
+ more.clear();
+ for (auto ref : toProcess) {
+ if (done.insert(*ref).second && !ref->owner().IsIntrinsicModules()) {
+ if (ref->get<ModuleDetails>().isHermetic()) {
+ asStr += "!dir$ begin_nested_hermetic_module\n";
+ asStr += WriteModuleAndDependents(*ref);
+ asStr += "!dir$ end_nested_hermetic_module\n";
+ } else {
+ PutSymbols(DEREF(ref->scope()), &more);
+ asStr += GetAsString(*ref);
+ }
+ }
+ }
+ }
+ return asStr;
+}
+
void ModFileWriter::WriteClosure(llvm::raw_ostream &out, const Symbol &symbol,
UnorderedSymbolSet &nonIntrinsicModulesWritten) {
if (!symbol.has<ModuleDetails>() || symbol.owner().IsIntrinsicModules() ||
@@ -1342,6 +1351,114 @@ static void GetModuleDependences(
}
}
+// Given a list of program units (modules or compiler directives) parsed from
+// a module file, read the first module and the dependent modules that were
+// packaged with it. The dependent modules can themselves be hermetic,
+// so this routine is recursive. The list of program units is broken apart
+// and later stitched back together to make these recursive calls possible.
+static void ReadHermeticModule(SemanticsContext &context, Scope &scope,
+ std::list<parser::ProgramUnit> &progUnits) {
+ // Extract the first module into its own Program.
+ std::list<parser::ProgramUnit> justFirst;
+ CHECK(!progUnits.empty() &&
+ std::holds_alternative<common::Indirection<parser::Module>>(
+ progUnits.front().u));
+ justFirst.emplace_back(std::move(progUnits.front()));
+ progUnits.pop_front();
+ // The following modules are read into a new scope that's visible to name
+ // resolution only via a pointer in the SemanticsContext.
+ Scope &hermeticScope{scope.MakeScope(Scope::Kind::Global)};
+ // Handle nested hermetic modules recursively first; put non-hermetic nested
+ // modules on a "normals" list to be handled later.
+ std::list<parser::ProgramUnit> normals, hermetics;
+ while (!progUnits.empty()) {
+ if (const auto *dir{
+ std::get_if<common::Indirection<parser::CompilerDirective>>(
+ &progUnits.front().u)};
+ dir &&
+ std::holds_alternative<
+ parser::CompilerDirective::BeginNestedHermeticModule>(
+ dir->value().u)) {
+ // There's a nested hermetic module sequence delimited by directives.
+ // !DIR$ BEGIN_NESTED_HERMETIC_MODULE
+ // module nested
+ // use dependency1
+ // use dependency2
+ // end
+ // module dependency1; end
+ // module dependency2; end
+ // !DIR$ NESTED_NESTED_HERMETIC_MODULE
+ int nesting{1};
+ hermetics.emplace_back(std::move(progUnits.front())); // !DIR$ BEGIN
+ progUnits.pop_front();
+ std::list<parser::ProgramUnit> nested;
+ while (!progUnits.empty()) {
+ if (auto *dir{
+ std::get_if<common::Indirection<parser::CompilerDirective>>(
+ &progUnits.front().u)}) {
+ if (std::holds_alternative<
+ parser::CompilerDirective::BeginNestedHermeticModule>(
+ dir->value().u)) {
+ ++nesting;
+ } else if (std::holds_alternative<
+ parser::CompilerDirective::EndNestedHermeticModule>(
+ dir->value().u)) {
+ CHECK(nesting > 0);
+ if (nesting-- == 1) {
+ // "nested" contains the nested hermetic module and its
+ // dependences, which may also be nested.
+ CHECK(!nested.empty());
+ ReadHermeticModule(context, hermeticScope, nested);
+ for (; !nested.empty(); nested.pop_front()) {
+ hermetics.emplace_back(std::move(nested.front()));
+ }
+ hermetics.emplace_back(std::move(progUnits.front())); // !DIR$ END
+ progUnits.pop_front();
+ break;
+ }
+ }
+ }
+ nested.emplace_back(std::move(progUnits.front()));
+ progUnits.pop_front();
+ }
+ CHECK(nesting == 0);
+ CHECK(nested.empty());
+ } else {
+ normals.emplace_back(std::move(progUnits.front()));
+ progUnits.pop_front();
+ }
+ }
+ // Mark the nested hermetic modules as being such.
+ for (auto &[_, ref] : hermeticScope) {
+ ref->get<ModuleDetails>().set_isHermetic(true);
+ }
+ // Handle non-hermetic nested modules now
+ Scope *previousHermeticScope{context.currentHermeticModuleFileScope()};
+ context.set_currentHermeticModuleFileScope(&hermeticScope);
+ if (!normals.empty()) {
+ parser::Program program{std::move(normals)};
+ ResolveNames(context, program, hermeticScope);
+ normals = std::move(program.v);
+ }
+ for (auto &[_, ref] : hermeticScope) {
+ CHECK(ref->has<ModuleDetails>());
+ ref->set(Symbol::Flag::ModFile);
+ }
+ // Now finally process the first module in the original list.
+ parser::Program firstModuleOnly{std::move(justFirst)};
+ ResolveNames(context, firstModuleOnly, scope);
+ context.set_currentHermeticModuleFileScope(previousHermeticScope);
+ // Reconstruct the progUnits list so parse tree dumps don't look weird.
+ progUnits.clear();
+ progUnits.emplace_back(std::move(firstModuleOnly.v.front()));
+ for (; !hermetics.empty(); hermetics.pop_front()) {
+ progUnits.emplace_back(std::move(hermetics.front()));
+ }
+ for (; !normals.empty(); normals.pop_front()) {
+ progUnits.emplace_back(std::move(normals.front()));
+ }
+}
+
Scope *ModFileReader::Read(SourceName name, std::optional<bool> isIntrinsic,
Scope *ancestor, bool silent) {
std::string ancestorName; // empty for module
@@ -1548,23 +1665,14 @@ Scope *ModFileReader::Read(SourceName name, std::optional<bool> isIntrinsic,
// created under -fhermetic-module-files? If so, process them first in
// their own nested scope that will be visible only to USE statements
// within the module file.
- Scope *previousHermetic{context_.currentHermeticModuleFileScope()};
- 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) {
- CHECK(ref->has<ModuleDetails>());
- ref->set(Symbol::Flag::ModFile);
- }
- }
- GetModuleDependences(context_.moduleDependences(), sourceFile->content());
- ResolveNames(context_, parseTree, topScope);
+ bool isHermetic{parseTree.v.size() > 1};
+ if (isHermetic) {
+ ReadHermeticModule(context_, topScope, parseTree.v);
+ } else {
+ GetModuleDependences(context_.moduleDependences(), sourceFile->content());
+ ResolveNames(context_, parseTree, topScope);
+ }
context_.foldingContext().set_moduleFileName(wasModuleFileName);
- context_.set_currentHermeticModuleFileScope(previousHermetic);
if (!moduleSymbol) {
// Submodule symbols' storage are owned by their parents' scopes,
// but their names are not in their parents' dictionaries -- we
@@ -1582,6 +1690,7 @@ Scope *ModFileReader::Read(SourceName name, std::optional<bool> isIntrinsic,
auto &details{moduleSymbol->get<ModuleDetails>()};
details.set_moduleFileHash(checkSum.value());
details.set_previous(previousModuleSymbol);
+ details.set_isHermetic(isHermetic);
if (isIntrinsic.value_or(false)) {
moduleSymbol->attrs().set(Attr::INTRINSIC);
}
diff --git a/flang/lib/Semantics/mod-file.h b/flang/lib/Semantics/mod-file.h
index 82538fb510873..6258fea4d1fc0 100644
--- a/flang/lib/Semantics/mod-file.h
+++ b/flang/lib/Semantics/mod-file.h
@@ -66,6 +66,7 @@ class ModFileWriter {
void WriteAll(const Scope &);
void WriteOne(const Scope &);
void Write(const Symbol &);
+ std::string WriteModuleAndDependents(const Symbol &);
std::string GetAsString(const Symbol &);
void PrepareRenamings(const Scope &);
void PutSymbols(const Scope &, UnorderedSymbolSet *hermetic);
diff --git a/flang/lib/Semantics/symbol.cpp b/flang/lib/Semantics/symbol.cpp
index 2118970a7bf25..86c0066e7fae0 100644
--- a/flang/lib/Semantics/symbol.cpp
+++ b/flang/lib/Semantics/symbol.cpp
@@ -593,6 +593,9 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Details &details) {
if (x.isDefaultPrivate()) {
os << " isDefaultPrivate";
}
+ if (x.isHermetic()) {
+ os << " isHermetic";
+ }
},
[&](const SubprogramNameDetails &x) {
os << ' ' << EnumToString(x.kind());
diff --git a/flang/test/Semantics/modfile76.F90 b/flang/test/Semantics/modfile76.F90
new file mode 100644
index 0000000000000..80267b9326b01
--- /dev/null
+++ b/flang/test/Semantics/modfile76.F90
@@ -0,0 +1,26 @@
+!RUN: %flang -c -fhermetic-module-files -DWHICH=1 %s && %flang -c -fhermetic-module-files -DWHICH=2 %s && %flang -c -fhermetic-module-files %s && cat modfile76c.mod | FileCheck %s
+
+#if WHICH == 1
+module modfile76a
+ integer :: global_variable = 0
+end
+#elif WHICH == 2
+module modfile76b
+ use modfile76a
+ contains
+ subroutine test
+ end
+end
+#else
+module modfile76c
+ use modfile76a
+ use modfile76b
+end
+#endif
+
+!CHECK: module modfile76c
+!CHECK: module modfile76a
+!CHECK: !dir$ begin_nested_hermetic_module
+!CHECK: module modfile76b
+!CHECK: module modfile76a
+!CHECK: !dir$ end_nested_hermetic_module
More information about the flang-commits
mailing list