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

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Sep 18 04:03:34 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-parser

@llvm/pr-subscribers-flang-semantics

Author: Kareem Ergawy (ergawy)

<details>
<summary>Changes</summary>

An implicit 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.

---
Full diff: https://github.com/llvm/llvm-project/pull/224601.diff


8 Files Affected:

- (modified) flang/include/flang/Parser/dump-parse-tree.h (+2) 
- (modified) flang/include/flang/Parser/parse-tree.h (+13-2) 
- (modified) flang/lib/Parser/Fortran-parsers.cpp (+10-2) 
- (modified) flang/lib/Parser/unparse.cpp (+6) 
- (modified) flang/lib/Semantics/mod-file.cpp (+19-3) 
- (modified) flang/lib/Semantics/resolve-names.cpp (+29-8) 
- (modified) flang/test/Parser/cuf-sanity-tree.CUF (+4-2) 
- (added) flang/test/Semantics/CUDA/cuda-managed-implicit-modfile.cuf (+76) 


``````````diff
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..4bdeb52f1cad8 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) {
@@ -777,7 +780,8 @@ class ScopeHandler : public ImplicitRulesVisitor {
     symbol.implicitAttrs().set(attr);
   }
   void SetCUDADataAttr(
-      SourceName, Symbol &, std::optional<common::CUDADataAttr>);
+      SourceName, Symbol &, std::optional<common::CUDADataAttr>,
+      bool isImplicit = false);
 
 protected:
   FuncResultStack &funcResultStack() { return funcResultStack_; }
@@ -2586,6 +2590,7 @@ Attrs AttrsVisitor::EndAttrs() {
   Attrs result{GetAttrs()};
   attrs_.reset();
   cudaDataAttr_.reset();
+  cudaDataAttrIsImplicit_ = false;
   passName_ = std::nullopt;
   bindName_.reset();
   isCDefined_ = false;
@@ -2726,6 +2731,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 +3847,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 +3858,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 +5784,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 +6256,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 +6700,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 +7616,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 +7751,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 +8735,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 +11011,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

``````````

</details>


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


More information about the llvm-branch-commits mailing list