[flang-commits] [flang] [flang][cuda] Record implicit managed attribution in module files (PR #224601)

Kareem Ergawy via flang-commits flang-commits at lists.llvm.org
Tue Sep 22 00:16:13 PDT 2026


https://github.com/ergawy updated https://github.com/llvm/llvm-project/pull/224601

>From 6c50c03d927a495c8cbe1165b1011b0b45e97094 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Fri, 18 Sep 2026 02:35:35 -0700
Subject: [PATCH 1/2] [flang][cuda] Record implicit managed attribution in
 module files

An attribute the compiler applied under -gpu=mem:managed is written into the
module file the same way a user-written one is, so a reader cannot tell them
apart. It then treats the attribute as a user requirement: allocating such a
component in a DEVICE object is rejected, and the memory space the user did
ask for on the object no longer wins.

Spell the distinction in the module file as MANAGED(IMPLICIT), modelled on
INTENT(IN): CUDA-data-attr gains an optional parenthesized qualifier, carried
by a new CUDADataAttrSpec parse-tree node in AttrSpec and ComponentAttrSpec.
ATTRIBUTES(...) keeps the bare attribute, so the qualifier cannot be written
there.

The attribute itself is still written out, so a component keeps the same
memory space no matter which options a consumer is compiled with.

Also stop an implicitly applied attribute from making a module a definer of
CUDA symbols. Without this, adding -gpu=mem:managed to a module's build
rejects its OpenACC-only consumers over an attribute the user never wrote.
---
 flang/include/flang/Parser/dump-parse-tree.h  |  2 +
 flang/include/flang/Parser/parse-tree.h       | 15 +++-
 flang/lib/Parser/Fortran-parsers.cpp          | 12 ++-
 flang/lib/Parser/unparse.cpp                  |  6 ++
 flang/lib/Semantics/mod-file.cpp              | 22 +++++-
 flang/lib/Semantics/resolve-names.cpp         | 38 +++++++---
 flang/test/Parser/cuf-sanity-tree.CUF         |  6 +-
 .../CUDA/cuda-managed-implicit-modfile.cuf    | 76 +++++++++++++++++++
 8 files changed, 159 insertions(+), 18 deletions(-)
 create mode 100644 flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf

diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index 3b4d467f14345..7ca404663b486 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -63,6 +63,8 @@ class ParseTreeDumper {
   NODE(std, int64_t)
   NODE(std, uint64_t)
   NODE_ENUM(common, CUDADataAttr)
+  NODE(parser, CUDADataAttrSpec)
+  NODE(CUDADataAttrSpec, Implicit)
   NODE_ENUM(common, CUDASubprogramAttrs)
   NODE_ENUM(common, ImportKind)
   NODE_ENUM(common, OmpDependenceKind)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 3e9a035d0bfee..207a5543ab775 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -999,10 +999,21 @@ struct ComponentArraySpec {
 EMPTY_CLASS(Allocatable);
 EMPTY_CLASS(Pointer);
 EMPTY_CLASS(Contiguous);
+// CUDA-data-attr [( IMPLICIT )]
+// The (IMPLICIT) qualifier marks an attribute that the compiler applied on the
+// user's behalf (e.g. an unattributed ALLOCATABLE under -gpu=mem:managed)
+// rather than one the user wrote. It exists so that module files can carry
+// that distinction; user code is not expected to spell it.
+struct CUDADataAttrSpec {
+  TUPLE_CLASS_BOILERPLATE(CUDADataAttrSpec);
+  EMPTY_CLASS(Implicit);
+  std::tuple<common::CUDADataAttr, std::optional<Implicit>> t;
+};
+
 struct ComponentAttrSpec {
   UNION_CLASS_BOILERPLATE(ComponentAttrSpec);
   std::variant<AccessSpec, Allocatable, CoarraySpec, Contiguous,
-      ComponentArraySpec, Pointer, common::CUDADataAttr, ErrorRecovery>
+      ComponentArraySpec, Pointer, CUDADataAttrSpec, ErrorRecovery>
       u;
 };
 
@@ -1412,7 +1423,7 @@ struct AttrSpec {
   std::variant<AccessSpec, Allocatable, Asynchronous, CoarraySpec, Contiguous,
       ArraySpec, External, IntentSpec, Intrinsic, LanguageBindingSpec, Optional,
       Parameter, Pointer, Protected, RankClause, Save, Target, Value, Volatile,
-      common::CUDADataAttr>
+      CUDADataAttrSpec>
       u;
 };
 
diff --git a/flang/lib/Parser/Fortran-parsers.cpp b/flang/lib/Parser/Fortran-parsers.cpp
index a20983e095d18..af62a15edd1f5 100644
--- a/flang/lib/Parser/Fortran-parsers.cpp
+++ b/flang/lib/Parser/Fortran-parsers.cpp
@@ -477,7 +477,7 @@ TYPE_PARSER(construct<ComponentAttrSpec>(accessSpec) ||
     construct<ComponentAttrSpec>("DIMENSION" >> componentArraySpec) ||
     construct<ComponentAttrSpec>(pointer) ||
     extension<LanguageFeature::CUDA>(
-        construct<ComponentAttrSpec>(Parser<common::CUDADataAttr>{})) ||
+        construct<ComponentAttrSpec>(Parser<CUDADataAttrSpec>{})) ||
     construct<ComponentAttrSpec>(recovery(
         fail<ErrorRecovery>(
             "type parameter definitions must appear before component declarations"_err_en_US),
@@ -764,7 +764,15 @@ TYPE_PARSER(construct<AttrSpec>(accessSpec) ||
     construct<AttrSpec>(construct<Value>("VALUE"_tok)) ||
     construct<AttrSpec>(construct<Volatile>("VOLATILE"_tok)) ||
     extension<LanguageFeature::CUDA>(
-        construct<AttrSpec>(Parser<common::CUDADataAttr>{})))
+        construct<AttrSpec>(Parser<CUDADataAttrSpec>{})))
+
+// CUDA-data-attr-spec -> CUDA-data-attr [( IMPLICIT )]
+// The parenthesized qualifier marks a compiler-applied attribute; it is
+// emitted into module files so the distinction survives, and is not meant to
+// be written in user code.
+TYPE_PARSER(construct<CUDADataAttrSpec>(Parser<common::CUDADataAttr>{},
+    maybe(parenthesized(
+        construct<CUDADataAttrSpec::Implicit>("IMPLICIT" >> ok)))))
 
 // CUDA-data-attr ->
 //     CONSTANT | DEVICE | MANAGED | PINNED | SHARED | TEXTURE | UNIFIED
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index d075a77e17b43..a046c08e710c6 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -2910,6 +2910,12 @@ class UnparseVisitor {
   WALK_NESTED_ENUM(AccDataModifier, Modifier)
   WALK_NESTED_ENUM(AccessSpec, Kind) // R807
   WALK_NESTED_ENUM(common, TypeParamAttr) // R734
+  void Unparse(const CUDADataAttrSpec &x) { // CUDA
+    Walk(std::get<common::CUDADataAttr>(x.t));
+    if (std::get<std::optional<CUDADataAttrSpec::Implicit>>(x.t)) {
+      Word("(IMPLICIT)");
+    }
+  }
   WALK_NESTED_ENUM(common, CUDADataAttr) // CUDA
   WALK_NESTED_ENUM(common, CUDASubprogramAttrs) // CUDA
   WALK_NESTED_ENUM(common, OmpDependenceKind)
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index e17bfb2ee3f32..a30581b30b892 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -1418,6 +1418,12 @@ void ModFileWriter::PutEntity(llvm::raw_ostream &os, const Symbol &symbol,
   if (const auto *details{symbol.detailsIf<ObjectEntityDetails>()}) {
     if (auto attr{details->cudaDataAttr()}) {
       PutLower(os << ',', common::EnumToString(*attr));
+      // Record that the compiler applied this attribute, so that a reader can
+      // tell it from one the user wrote and let an explicit memory space on an
+      // enclosing object take precedence over it.
+      if (details->cudaDataAttrIsImplicit()) {
+        os << "(implicit)";
+      }
     }
   }
   if (symbol.owner().kind() == Scope::Kind::DerivedType &&
@@ -2010,18 +2016,28 @@ static std::optional<SourceName> GetSubmoduleParent(
   }
 }
 
+// Does this symbol carry a CUDA data attribute the user actually wrote? An
+// attribute the compiler applied on the user's behalf does not make the module
+// a definer of CUDA symbols: the user wrote no CUDA Fortran, so a consumer
+// without CUDA enabled has nothing to object to.
+static bool HasExplicitCUDADataAttr(const Symbol &symbol) {
+  const auto *object{symbol.detailsIf<ObjectEntityDetails>()};
+  return object && object->cudaDataAttr() && !object->cudaDataAttrIsImplicit();
+}
+
 static bool ScopeHasCUDAModuleVariables(const Scope &scope) {
   for (const auto &[_, symbolRef] : scope) {
     const Symbol &symbol{*symbolRef};
     if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
-      if (object->cudaDataAttr()) {
+      if (HasExplicitCUDADataAttr(symbol)) {
         return true;
       }
       const DeclTypeSpec *type{object->type()};
       const DerivedTypeSpec *derived{type ? type->AsDerived() : nullptr};
       if (derived &&
-          FindUltimateComponent(*derived,
-              [](const Symbol &component) { return HasCUDAAttr(component); })) {
+          FindUltimateComponent(*derived, [](const Symbol &component) {
+            return HasExplicitCUDADataAttr(component);
+          })) {
         return true;
       }
     }
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index c5f7fba49fffb..8a2ffc1c20f1b 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -262,6 +262,7 @@ class AttrsVisitor : public virtual BaseVisitor {
   bool BeginAttrs(); // always returns true
   Attrs GetAttrs();
   std::optional<common::CUDADataAttr> cudaDataAttr() { return cudaDataAttr_; }
+  bool cudaDataAttrIsImplicit() const { return cudaDataAttrIsImplicit_; }
   Attrs EndAttrs();
   bool SetPassNameOn(Symbol &);
   void SetBindNameOn(Symbol &);
@@ -304,10 +305,12 @@ class AttrsVisitor : public virtual BaseVisitor {
   HANDLE_ATTR_CLASS(Volatile, VOLATILE)
 #undef HANDLE_ATTR_CLASS
   bool Pre(const common::CUDADataAttr);
+  bool Pre(const parser::CUDADataAttrSpec::Implicit &);
 
 protected:
   std::optional<Attrs> attrs_;
   std::optional<common::CUDADataAttr> cudaDataAttr_;
+  bool cudaDataAttrIsImplicit_{false};
 
   Attr AccessSpecToAttr(const parser::AccessSpec &x) {
     switch (x.v) {
@@ -776,8 +779,8 @@ class ScopeHandler : public ImplicitRulesVisitor {
     symbol.attrs().set(attr);
     symbol.implicitAttrs().set(attr);
   }
-  void SetCUDADataAttr(
-      SourceName, Symbol &, std::optional<common::CUDADataAttr>);
+  void SetCUDADataAttr(SourceName, Symbol &,
+      std::optional<common::CUDADataAttr>, bool isImplicit = false);
 
 protected:
   FuncResultStack &funcResultStack() { return funcResultStack_; }
@@ -2586,6 +2589,7 @@ Attrs AttrsVisitor::EndAttrs() {
   Attrs result{GetAttrs()};
   attrs_.reset();
   cudaDataAttr_.reset();
+  cudaDataAttrIsImplicit_ = false;
   passName_ = std::nullopt;
   bindName_.reset();
   isCDefined_ = false;
@@ -2726,6 +2730,12 @@ bool AttrsVisitor::Pre(const common::CUDADataAttr x) {
   cudaDataAttr_ = x;
   return false;
 }
+bool AttrsVisitor::Pre(const parser::CUDADataAttrSpec::Implicit &) {
+  // The (IMPLICIT) qualifier only appears in module files, marking an
+  // attribute this compiler applied rather than one the user wrote.
+  cudaDataAttrIsImplicit_ = true;
+  return false;
+}
 
 // DeclTypeSpecVisitor implementation
 
@@ -3836,7 +3846,7 @@ bool ScopeHandler::CheckDuplicatedAttrs(
 }
 
 void ScopeHandler::SetCUDADataAttr(SourceName source, Symbol &symbol,
-    std::optional<common::CUDADataAttr> attr) {
+    std::optional<common::CUDADataAttr> attr, bool isImplicit) {
   if (attr) {
     ConvertToObjectEntity(symbol);
     if (auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
@@ -3847,6 +3857,7 @@ void ScopeHandler::SetCUDADataAttr(SourceName source, Symbol &symbol,
             std::string{common::EnumToString(*object->cudaDataAttr())}.c_str());
       } else {
         object->set_cudaDataAttr(attr);
+        object->set_cudaDataAttrIsImplicit(isImplicit);
       }
     } else {
       Say(source,
@@ -5772,7 +5783,8 @@ void SubprogramVisitor::PostEntryStmt(const parser::EntryStmt &stmt) {
   }
   SubprogramDetails &entryDetails{entrySymbol.get<SubprogramDetails>()};
   CHECK(entryDetails.entryScope() == &inclusiveScope);
-  SetCUDADataAttr(name.source, entrySymbol, cudaDataAttr());
+  SetCUDADataAttr(
+      name.source, entrySymbol, cudaDataAttr(), cudaDataAttrIsImplicit());
   entrySymbol.attrs() |= GetAttrs();
   SetBindNameOn(entrySymbol);
   for (const auto &dummyArg : std::get<std::list<parser::DummyArg>>(stmt.t)) {
@@ -6243,7 +6255,8 @@ void DeclarationVisitor::Post(const parser::EntityDecl &x) {
   attrs.set(Attr::INTRINSIC, false); // dealt with in Pre(TypeDeclarationStmt)
   Symbol &symbol{DeclareUnknownEntity(name, attrs)};
   symbol.ReplaceName(name.source);
-  SetCUDADataAttr(name.source, symbol, cudaDataAttr());
+  SetCUDADataAttr(
+      name.source, symbol, cudaDataAttr(), cudaDataAttrIsImplicit());
   if (const auto &init{std::get<std::optional<parser::Initialization>>(x.t)}) {
     ConvertToObjectEntity(symbol) || ConvertToProcEntity(symbol);
     symbol.set(
@@ -6686,6 +6699,8 @@ bool DeclarationVisitor::Pre(const parser::CUDAAttributesStmt &x) {
       if (attr == common::CUDADataAttr::Value) {
         SetExplicitAttr(*symbol, Attr::VALUE);
       } else {
+        // ATTRIBUTES(...) carries a bare CUDA-data-attr, with no place for
+        // the (IMPLICIT) qualifier, so such an attribute is always the user's.
         SetCUDADataAttr(name.source, *symbol, attr);
       }
     }
@@ -7600,7 +7615,8 @@ void DeclarationVisitor::Post(const parser::ComponentDecl &x) {
   }
   if (OkToAddComponent(name)) {
     auto &symbol{DeclareObjectEntity(name, attrs)};
-    SetCUDADataAttr(name.source, symbol, cudaDataAttr());
+    SetCUDADataAttr(
+        name.source, symbol, cudaDataAttr(), cudaDataAttrIsImplicit());
 
     // Implicitely attribute allocatable/pointer components with `managed`
     // memory if CUDA and `-gpu=mem:managed` are enabled.
@@ -7734,7 +7750,8 @@ void DeclarationVisitor::Post(const parser::ProcDecl &x) {
     attrs.set(Attr::PRIVATE);
   }
   Symbol &symbol{DeclareProcEntity(name, attrs, procInterface)};
-  SetCUDADataAttr(name.source, symbol, cudaDataAttr()); // for error
+  SetCUDADataAttr(name.source, symbol, cudaDataAttr(),
+      cudaDataAttrIsImplicit()); // for error
   symbol.ReplaceName(name.source);
   if (dtDetails) {
     dtDetails->add_component(symbol);
@@ -8717,7 +8734,8 @@ Symbol *DeclarationVisitor::MakeTypeSymbol(
       attrs.set(Attr::PRIVATE);
     }
     Symbol &result{MakeSymbol(name, attrs, std::move(details))};
-    SetCUDADataAttr(name, result, cudaDataAttr());
+
+    SetCUDADataAttr(name, result, cudaDataAttr(), cudaDataAttrIsImplicit());
     return &result;
   }
 }
@@ -10992,8 +11010,10 @@ void ResolveNamesVisitor::FinishSpecificationPart(
         if (context().languageFeatures().IsEnabled(
                 common::LanguageFeature::CUDA)) {
           if (context().languageFeatures().IsEnabled(
-                  common::LanguageFeature::CudaManaged))
+                  common::LanguageFeature::CudaManaged)) {
             object->set_cudaDataAttr(common::CUDADataAttr::Managed);
+            object->set_cudaDataAttrIsImplicit();
+          }
           // Implicitly treat allocatable arrays as pinned when feature is
           // enabled.
           else if (IsAllocatable(symbol) &&
diff --git a/flang/test/Parser/cuf-sanity-tree.CUF b/flang/test/Parser/cuf-sanity-tree.CUF
index b4d53f27cf395..5acd8f2b0cc48 100644
--- a/flang/test/Parser/cuf-sanity-tree.CUF
+++ b/flang/test/Parser/cuf-sanity-tree.CUF
@@ -21,7 +21,8 @@ include "cuf-sanity-common"
 !CHECK: | | DeclarationConstruct -> SpecificationConstruct -> TypeDeclarationStmt
 !CHECK: | | | DeclarationTypeSpec -> IntrinsicTypeSpec -> Real
 !CHECK: | | | AttrSpec -> Allocatable
-!CHECK: | | | AttrSpec -> CUDADataAttr = Pinned
+!CHECK: | | | AttrSpec -> CUDADataAttrSpec
+!CHECK: | | | | CUDADataAttr = Pinned
 !CHECK: | | | EntityDecl
 !CHECK: | | | | Name = 'pa'
 !CHECK: | | | | ArraySpec -> DeferredShapeSpecList -> int
@@ -111,7 +112,8 @@ include "cuf-sanity-common"
 !CHECK: | | | | | Name = 'devx1'
 !CHECK: | | | | DeclarationConstruct -> SpecificationConstruct -> TypeDeclarationStmt
 !CHECK: | | | | | DeclarationTypeSpec -> IntrinsicTypeSpec -> Real
-!CHECK: | | | | | AttrSpec -> CUDADataAttr = Device
+!CHECK: | | | | | AttrSpec -> CUDADataAttrSpec
+!CHECK: | | | | | | CUDADataAttr = Device
 !CHECK: | | | | | EntityDecl
 !CHECK: | | | | | | Name = 'devx2'
 !CHECK: | | | ExecutionPart -> Block
diff --git a/flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf b/flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf
new file mode 100644
index 0000000000000..ca5e8037d5e75
--- /dev/null
+++ b/flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf
@@ -0,0 +1,76 @@
+! Under -gpu=managed the compiler attributes unattributed ALLOCATABLE and
+! POINTER entities as managed on the user's behalf. A module file records that
+! it did so, with the (IMPLICIT) qualifier, so that a reader can tell such an
+! attribute from one the user wrote.
+
+! RUN: rm -rf %t && split-file %s %t
+! RUN: cd %t && bbc -emit-hlfir -fcuda -gpu=managed def.cuf -o /dev/null
+! RUN: cat %t/m.mod | FileCheck %s --check-prefix=MODFILE
+
+! The memory space the user asked for on an enclosing object wins over an
+! implicitly attributed component, even across the module file.
+! RUN: cd %t && bbc -emit-hlfir -fcuda -gpu=managed use_device.cuf -o - \
+! RUN:   | FileCheck %s --check-prefix=DEVICE
+
+! With no enclosing object asking for a space, the implicit attribute applies,
+! and it does so whether or not the consumer repeats -gpu=managed.
+! RUN: cd %t && bbc -emit-hlfir -fcuda -gpu=managed use_host.cuf -o - \
+! RUN:   | FileCheck %s --check-prefix=HOST
+! RUN: cd %t && bbc -emit-hlfir -fcuda use_host.cuf -o - \
+! RUN:   | FileCheck %s --check-prefix=HOST
+
+! An attribute the compiler applied does not make the module a definer of CUDA
+! symbols, so a consumer without CUDA Fortran enabled is not rejected.
+! RUN: cd %t && bbc -emit-hlfir -fopenacc use_acc.cuf -o /dev/null
+
+!--- def.cuf
+module m
+  ! Every component here was attributed by the compiler, so an enclosing
+  ! object's own memory space takes precedence over them.
+  type :: t
+    real, allocatable :: implicit_comp(:)
+  end type
+  ! A component the user attributed, kept apart so that it does not affect
+  ! where an object of type t is placed.
+  type :: t_explicit
+    real, allocatable, managed :: explicit_comp(:)
+  end type
+  real, allocatable :: implicit_var(:)
+end module
+
+! The compiler applied the attribute here, and the module file says so.
+! MODFILE: real(4),allocatable,managed(implicit)::implicit_comp(:)
+! The user wrote this one, so it is recorded without the qualifier.
+! MODFILE: real(4),allocatable,managed::explicit_comp(:)
+! The same distinction is kept for an entity in the module's own scope.
+! MODFILE: real(4),allocatable,managed(implicit)::implicit_var(:)
+
+!--- use_device.cuf
+subroutine device_object()
+  use m
+  type(t), device :: d
+  allocate(d%implicit_comp(10))
+  deallocate(d%implicit_comp)
+end subroutine
+
+! DEVICE-LABEL: func.func @_QPdevice_object()
+! DEVICE: cuf.alloc {{.*}} {bindc_name = "d", data_attr = #cuf.cuda<device>
+! DEVICE: fir.embox {{.*}} {allocator_idx = 2 : i32}
+! DEVICE: cuf.allocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+! DEVICE: cuf.deallocate {{.*}} {data_attr = #cuf.cuda<device>} -> i32
+
+!--- use_host.cuf
+subroutine host_object()
+  use m
+  type(t) :: h
+  allocate(h%implicit_comp(10))
+end subroutine
+
+! HOST-LABEL: func.func @_QPhost_object()
+! HOST: fir.embox {{.*}} {allocator_idx = 3 : i32}
+! HOST: cuf.allocate {{.*}} {data_attr = #cuf.cuda<managed>} -> i32
+
+!--- use_acc.cuf
+subroutine acc_only()
+  use m
+end subroutine

>From 2dacc78ca082b315a8db3d558f92d8e355bd68e1 Mon Sep 17 00:00:00 2001
From: ergawy <kareem.ergawy at gmail.com>
Date: Mon, 21 Sep 2026 00:02:21 -0700
Subject: [PATCH 2/2] [flang][cuda] Restrict the MANAGED(IMPLICIT) spelling to
 module files

---
 flang/docs/ModFiles.md                        | 23 ++++++++++++++++++-
 .../include/flang/Support/Fortran-features.h  |  7 +++---
 flang/lib/Parser/Fortran-parsers.cpp          |  5 ++--
 flang/lib/Semantics/mod-file.cpp              |  4 ++++
 flang/lib/Support/Fortran-features.cpp        |  3 +++
 .../CUDA/cuda-managed-implicit-modfile.cuf    | 14 +++++++++++
 6 files changed, 50 insertions(+), 6 deletions(-)

diff --git a/flang/docs/ModFiles.md b/flang/docs/ModFiles.md
index 4485770f7d24d..6cdabcdb11f59 100644
--- a/flang/docs/ModFiles.md
+++ b/flang/docs/ModFiles.md
@@ -43,7 +43,10 @@ this is needed, Flang accepts the option `-module-suffix` to alter the suffix.
 
 Module files are Fortran free form source code.
 (One can, in principle, copy `foo.mod` into `tmp.f90`, recompile it,
-and obtain a matching `foo.mod` file.)
+and obtain a matching `foo.mod` file.  The exception is a module file that
+records an attribute the compiler applied on the user's behalf, described
+under Body below: those spellings are accepted only while a module file is
+being read, so such a module file does not recompile as ordinary source.)
 They include the declarations of all visible locally defined entities along
 with the private entities on which they depend.
 
@@ -78,6 +81,24 @@ appear in the module file as their folded values.
 Any compiler directives (`!omp$`, `!acc$`, &c.) relevant to the declarations
 of names are also included in the module file.
 
+An attribute that the compiler applied on the user's behalf, rather than one
+that appeared in the source, is marked as such so that a reader can tell the
+two apart.  Under `-gpu=mem:managed`, for example, an unattributed
+ALLOCATABLE or POINTER is attributed as managed, and the module file records
+that as `MANAGED(IMPLICIT)`:
+
+```
+real(4),allocatable,managed(implicit)::a(:)   ! applied by the compiler
+real(4),allocatable,managed::b(:)             ! written by the user
+```
+
+The distinction matters because an attribute the user did not ask for does
+not constrain them: a memory space they did request on an enclosing object
+takes precedence over it, and such a module does not count as defining CUDA
+symbols for the purposes of using it from code compiled without CUDA Fortran.
+These parenthesized qualifiers are enabled only while reading a module file
+and cannot be written in user code.
+
 Executable statements are omitted.
 If we ever want to do Fortran-level inline expansion of procedures
 in the future,
diff --git a/flang/include/flang/Support/Fortran-features.h b/flang/include/flang/Support/Fortran-features.h
index 4b2ff0a227f61..6b1aaaac19ec5 100644
--- a/flang/include/flang/Support/Fortran-features.h
+++ b/flang/include/flang/Support/Fortran-features.h
@@ -60,9 +60,10 @@ ENUM_CLASS(LanguageFeature, BackslashEscapes, OldDebugLines,
     DefaultStructConstructorNullPointer, AssumedRankIoItem,
     MultipleProgramUnitsOnSameLine, AllocatedForAssociated,
     OpenMPThreadprivateEquivalence, RelaxedCLocChecks, CudaPinned,
-    OpenAccDefaultNoneScalarsStrict, OpenACCMultipleNamesInRoutine,
-    EnumerationType, CUDAInit, PreferIntrinsicModuleUseAssociation,
-    MultipleCommonBlockInit, OutOfBoundsSubscripts)
+    CUDAImplicitDataAttrSpelling, OpenAccDefaultNoneScalarsStrict,
+    OpenACCMultipleNamesInRoutine, EnumerationType, CUDAInit,
+    PreferIntrinsicModuleUseAssociation, MultipleCommonBlockInit,
+    OutOfBoundsSubscripts)
 
 // Portability and suspicious usage warnings
 ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable,
diff --git a/flang/lib/Parser/Fortran-parsers.cpp b/flang/lib/Parser/Fortran-parsers.cpp
index af62a15edd1f5..263ce9249a8b2 100644
--- a/flang/lib/Parser/Fortran-parsers.cpp
+++ b/flang/lib/Parser/Fortran-parsers.cpp
@@ -771,8 +771,9 @@ TYPE_PARSER(construct<AttrSpec>(accessSpec) ||
 // emitted into module files so the distinction survives, and is not meant to
 // be written in user code.
 TYPE_PARSER(construct<CUDADataAttrSpec>(Parser<common::CUDADataAttr>{},
-    maybe(parenthesized(
-        construct<CUDADataAttrSpec::Implicit>("IMPLICIT" >> ok)))))
+    maybe(
+        extension<LanguageFeature::CUDAImplicitDataAttrSpelling>(parenthesized(
+            construct<CUDADataAttrSpec::Implicit>("IMPLICIT" >> ok))))))
 
 // CUDA-data-attr ->
 //     CONSTANT | DEVICE | MANAGED | PINNED | SHARED | TEXTURE | UNIFIED
diff --git a/flang/lib/Semantics/mod-file.cpp b/flang/lib/Semantics/mod-file.cpp
index a30581b30b892..c2d5b04a915b6 100644
--- a/flang/lib/Semantics/mod-file.cpp
+++ b/flang/lib/Semantics/mod-file.cpp
@@ -1770,6 +1770,10 @@ Scope *ModFileReader::Read(SourceName name, std::optional<bool> isIntrinsic,
   }
   options.features.Enable(common::LanguageFeature::OpenMP);
   options.features.Enable(common::LanguageFeature::CUDA);
+  // Module files may record that the compiler applied a CUDA data attribute
+  // itself, as MANAGED(IMPLICIT). That spelling exists only here.
+  options.features.Enable(
+      common::LanguageFeature::CUDAImplicitDataAttrSpelling);
   if (!isIntrinsic.value_or(false) && !notAModule) {
     // The search for this module file will scan non-intrinsic module
     // directories.  If a directory is in both the intrinsic and non-intrinsic
diff --git a/flang/lib/Support/Fortran-features.cpp b/flang/lib/Support/Fortran-features.cpp
index 3dd1602e29103..4ae05994d98a7 100644
--- a/flang/lib/Support/Fortran-features.cpp
+++ b/flang/lib/Support/Fortran-features.cpp
@@ -135,6 +135,9 @@ LanguageFeatureControl::LanguageFeatureControl() {
   disable_.set(LanguageFeature::CudaManaged);
   disable_.set(LanguageFeature::CudaUnified);
   disable_.set(LanguageFeature::CudaPinned);
+  // Spelled only in module files, by the compiler itself; enabled just for
+  // parsing them, so that user code cannot write it.
+  disable_.set(LanguageFeature::CUDAImplicitDataAttrSpelling);
   disable_.set(LanguageFeature::CUDAInit);
   disable_.set(LanguageFeature::ImplicitNoneTypeNever);
   disable_.set(LanguageFeature::ImplicitNoneTypeAlways);
diff --git a/flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf b/flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf
index ca5e8037d5e75..7564cd7ea397d 100644
--- a/flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf
+++ b/flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf
@@ -23,6 +23,12 @@
 ! symbols, so a consumer without CUDA Fortran enabled is not rejected.
 ! RUN: cd %t && bbc -emit-hlfir -fopenacc use_acc.cuf -o /dev/null
 
+! The qualifier is spelled only in module files: the production is enabled by
+! a language feature that only the module-file reader turns on, so writing it
+! in user code is a syntax error.
+! RUN: cd %t && not bbc -emit-hlfir -fcuda -gpu=managed user_written.cuf -o - 2>&1 \
+! RUN:   | FileCheck %s --check-prefix=NOTFORUSERS
+
 !--- def.cuf
 module m
   ! Every component here was attributed by the compiler, so an enclosing
@@ -74,3 +80,11 @@ end subroutine
 subroutine acc_only()
   use m
 end subroutine
+
+!--- user_written.cuf
+subroutine writes_the_qualifier()
+  real, allocatable, managed(implicit) :: a(:)
+end subroutine
+
+! NOTFORUSERS: user_written.cuf:{{[0-9]+}}:{{[0-9]+}}: error: expected entity declarations
+! NOTFORUSERS-NEXT: real, allocatable, managed(implicit) :: a(:)



More information about the flang-commits mailing list