[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
Sun Sep 20 22:36:52 PDT 2026


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

>From b4d17d2634480da7d8bd424ab4de2580c7e461a4 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] [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



More information about the flang-commits mailing list