================
@@ -83,6 +86,293 @@ struct UnwrapStmt<parser::UnlabeledStatement<A>> {
 void dumpScope(const semantics::Scope *scope, int depth = -1);
 #endif
 
+/// Collect direct storage uses while retaining complex expressions on the
+/// existing storage path. Skipped nodes do not seed selection from their
+/// children.
+struct MainSymbolVisitor
+    : public evaluate::AllTraverse<MainSymbolVisitor, true, false> {
+  using Base = evaluate::AllTraverse<MainSymbolVisitor, true, false>;
+  /// Borrow the callback for one synchronous expression walk.
+  explicit MainSymbolVisitor(
+      llvm::function_ref<void(const semantics::Symbol &)> callback)
+      : Base{*this}, callback{callback} {}
+  using Base::operator();
+  /// Stream each direct symbol occurrence without building subtree sets.
+  bool operator()(const semantics::Symbol &symbol) const {
+    LLVM_DEBUG(llvm::dbgs() << "main-global symbol\n");
+    callback(symbol);
+    return true;
+  }
+  /// TODO: Collect runtime dependencies of calls, constructors, PDT parameters
+  /// and coarray references, excluding metadata and folded payloads and
+  /// avoiding copies of selector expressions.
+  bool operator()(const semantics::DerivedTypeSpec &) const { return true; }
+  /// Computed derived constants cannot add direct storage uses.
+  bool operator()(const evaluate::Constant<evaluate::SomeDerived> &) const {
+    return true;
+  }
+  bool operator()(const evaluate::CoarrayRef &) const { return true; }
+  bool operator()(const evaluate::ProcedureRef &) const { return true; }
+  /// Defined assignment is a call; ordinary assignment retains both operands.
+  bool operator()(const evaluate::Assignment &assignment) const {
+    if (std::holds_alternative<evaluate::ProcedureRef>(assignment.u))
+      return true;
+    return Base::operator()(assignment);
+  }
+  bool operator()(const evaluate::StructureConstructor &) const { return true; 
}
+  template <typename A>
+  bool operator()(const evaluate::ArrayConstructor<A> &) const {
+    return true;
+  }
+  /// Avoid syntax collection after deliberately skipping resolved calls.
+  bool Pre(const parser::CallStmt &) { return false; }
+  /// SHARED names are metadata rather than accesses to object storage.
+  bool Pre(const parser::LocalitySpec::Shared &) { return false; }
+  template <typename A>
+  bool Pre(const A &node) {
+    if constexpr (parser::HasTypedExpr<A>::value) {
+      if (const auto *expr = semantics::GetExpr(nullptr, node)) {
+        collect(*expr);
+        return false;
+      }
+    } else if constexpr (parser::HasTypedAssignment<A>::value) {
+      if (node.typedAssignment && node.typedAssignment->v) {
+        collect(*node.typedAssignment->v);
+        return false;
+      }
+    }
+    return true;
+  }
+  bool Pre(const parser::Name &name) {
+    if (name.symbol)
+      callback(*name.symbol);
+    return false;
+  }
+  template <typename A>
+  void Post(const A &) {}
+
+private:
+  /// Traverse the outer typed expression once before returning to parser Walk.
+  template <typename A>
+  void collect(const A &expr) {
+    LLVM_DEBUG(llvm::dbgs() << "main-global expression\n");
+    (*this)(expr);
+  }
+  llvm::function_ref<void(const semantics::Symbol &)> callback;
+};
+
+/// Select small MAIN scalars directly used inside explicit host PARALLEL
+/// regions during PFT construction, before host associations are classified.
+/// Unsupported contexts retain the existing storage policy for the whole MAIN.
+class MainGlobalAnalysis {
+public:
+  /// Disabled analyses do not walk statements or semantic scopes.
+  explicit MainGlobalAnalysis(bool enabled) : enabled{enabled} {}
+  /// Determine membership once per procedure, including serially called
+  /// internal procedures that contain their own PARALLEL regions.
+  void enterFunction(const lower::pft::FunctionLikeUnit &unit) {
+    if (!enabled)
+      return;
+    const auto &scope = unit.getScope();
+    relevant = unit.isMainProgram();
+    if (relevant) {
+      mainScope = &scope;
+      // Bare MAIN SAVE already gives every eligible object global storage.
+      if (scope.hasSAVE()) {
+        enabled = false;
+        relevant = false;
+        return;
+      }
+    }
+    // Internal subprograms cannot themselves contain internal subprograms.
+    // Check only the immediate PFT parent, without semantic submodule 
ancestry.
+    if (!relevant && mainScope)
+      if (const auto *host =
+              unit.parent.getIf<lower::pft::FunctionLikeUnit>()) {
+        LLVM_DEBUG(llvm::dbgs() << "main-global function parent\n");
+        relevant = host->isMainProgram();
+      }
+    if (relevant && scope.symbol()) {
+      const auto &symbol = *scope.symbol();
+      if (symbol.test(semantics::Symbol::Flag::OmpDeclareTarget))
+        unsupported = true;
+    }
+  }
+  /// Observe directive headers before their bodies for the whole-MAIN 
fallback.
+  /// TARGET leaves of combined constructs are checked by enterOpenMP().
+  void directive(llvm::omp::Directive id) {
+    if (!relevant || unsupported)
+      return;
+    switch (id) {
+    case llvm::omp::Directive::OMPD_metadirective:
+    case llvm::omp::Directive::OMPD_declare_target:
+    case llvm::omp::Directive::OMPD_target_data:
+    case llvm::omp::Directive::OMPD_target_enter_data:
+    case llvm::omp::Directive::OMPD_target_exit_data:
+    case llvm::omp::Directive::OMPD_target_update:
+      unsupported = true;
+      return;
+    default:
+      return;
+    }
+  }
+  /// Track nesting independently of fallback state, keeping entry and exit
+  /// balanced when an unsupported context is encountered inside PARALLEL.
+  void enterOpenMP(const parser::OpenMPConstruct &construct) {
+    if (!relevant)
+      return;
+    bool parallel = false;
+    for (auto leaf : llvm::omp::getLeafConstructsOrSelf(
+             parser::omp::GetOmpDirectiveName(construct).v)) {
+      parallel |= leaf == llvm::omp::Directive::OMPD_parallel;
+      unsupported |= leaf == llvm::omp::Directive::OMPD_target;
+    }
+    parallels.push_back(parallel);
+    parallelDepth += parallel;
+  }
+  /// Restore the enclosing PARALLEL nesting after its PFT construct.
+  void exitOpenMP() {
+    if (relevant) {
+      parallelDepth -= parallels.back();
+      parallels.pop_back();
+    }
+  }
+  /// Visit executable leaves in PARALLEL, including WHERE/FORALL statements
+  /// without PFT evaluations. Clauses do not seed selection; an IF action has
+  /// its own evaluation and must not be visited a second time.
+  template <typename A>
+  void collect(const A &node) {
+    if (!relevant || unsupported || !parallelDepth)
+      return;
+    auto collectSymbol = [&](const semantics::Symbol &symbol) {
+      const auto *original = root(symbol);
+      if (&original->owner() == mainScope)
+        objects.insert(original);
+    };
+    MainSymbolVisitor visitor{collectSymbol};
+    if constexpr (std::is_same_v<A, lower::pft::Evaluation>) {
+      node.visit([&](const auto &stmt) {
+        if constexpr (std::is_same_v<std::decay_t<decltype(stmt)>,
+                                     parser::IfStmt>)
+          parser::Walk(std::get<parser::ScalarLogicalExpr>(stmt.t), visitor);
+        else if constexpr (lower::pft::isActionStmt<
+                               std::decay_t<decltype(stmt)>> ||
+                           lower::pft::isConstructStmt<
+                               std::decay_t<decltype(stmt)>>)
+          parser::Walk(stmt, visitor);
+      });
+    } else {
+      parser::Walk(node, visitor);
+    }
+  }
+  /// Filter whole original objects, then set storage flags. Set iteration
+  /// order cannot affect these independent decisions or generated 
declarations.
+  void finalize() {
+    if (unsupported || objects.empty())
+      return;
+    objects.remove_if(
+        [](const semantics::Symbol *symbol) { return !canUseGlobal(*symbol); 
});
+    if (objects.empty())
+      return;
+    for (const auto &set : mainScope->equivalenceSets())
+      for (const auto &object : set)
+        if (objects.erase(&object.symbol) && objects.empty())
+          return;
+    // Retain legacy storage for the privatization/locality flags below and for
+    // NAMELIST members throughout MAIN, including unused internal procedures.
+    // This deliberately avoids analyzing individual copy bindings.
+    // LocalityReduce uses the original accumulator, so it remains eligible.
+    const semantics::Symbol::Flags excluded{
+        semantics::Symbol::Flag::OmpPrivate,
+        semantics::Symbol::Flag::OmpFirstPrivate,
+        semantics::Symbol::Flag::OmpLastPrivate,
+        semantics::Symbol::Flag::OmpLinear,
+        semantics::Symbol::Flag::OmpReduction,
+        semantics::Symbol::Flag::OmpInReduction,
+        semantics::Symbol::Flag::OmpGroupPrivate,
+        semantics::Symbol::Flag::OmpCopyPrivate,
+        semantics::Symbol::Flag::OmpPreDetermined,
+        semantics::Symbol::Flag::LocalityLocal,
+        semantics::Symbol::Flag::LocalityLocalInit,
+        semantics::Symbol::Flag::InNamelist};
+    llvm::SmallVector<const semantics::Scope *> scopes{mainScope};
+    while (!scopes.empty()) {
+      const auto *scope = scopes.pop_back_val();
+      LLVM_DEBUG(llvm::dbgs() << "main-global scope\n");
+      // This sweep only removes candidates; stop resolving after exhaustion.
+      for (const auto &[name, symbol] : *scope)
+        if ((symbol->flags() & excluded).any() &&
+            objects.erase(root(*symbol)) && objects.empty())
+          return;
+      for (const auto &child : scope->children())
+        scopes.push_back(&child);
+    }
+    for (const auto *symbol : objects)
+      const_cast<semantics::Symbol *>(symbol)->set(
+          semantics::Symbol::Flag::OmpMainGlobal);
+  }
+
+private:
+  /// Memoize every USE/host association tail across collection and filtering.
+  /// ASSOCIATE entities deliberately retain their own identity.
+  const semantics::Symbol *root(const semantics::Symbol &symbol) {
+    const auto *original = &symbol;
+    llvm::SmallVector<const semantics::Symbol *> path;
+    while (true) {
+      if (auto it = roots.find(original); it != roots.end()) {
+        original = it->second;
+        break;
+      }
+      path.push_back(original);
+      LLVM_DEBUG(llvm::dbgs() << "main-global association\n");
+      if (const auto *details =
+              original->detailsIf<semantics::HostAssocDetails>())
+        original = &details->symbol();
+      else if (const auto *details =
+                   original->detailsIf<semantics::UseDetails>())
+        original = &details->symbol();
+      else
+        break;
+    }
+    for (const auto *entry : path)
+      roots.try_emplace(entry, original);
+    LLVM_DEBUG(llvm::dbgs() << "main-global reference: "
+                            << toStringRef(original->name()) << '\n');
+    return original;
+  }
+  /// Leave arrays, descriptors, derived types, automatic objects and special
+  /// runtime/device storage on their existing lowering paths.
+  static bool canUseGlobal(const semantics::Symbol &symbol) {
+    // Privatizers and MIF coarray handles append these fragments to object
+    // names. Leave potential collisions on their existing local-storage path.
+    const auto name = toStringRef(symbol.name());
+    if (name.contains("_private_") || name.contains("_firstprivate_") ||
----------------
SergeyShch01 wrote:

Thanks for catching this. I've added _linear_ to the exclusions and a 
regression test. The name filter is a workaround for a broader naming issue 
that also affects existing globals. I've filed 
[#236705](https://github.com/llvm/llvm-project/issues/236705) and added a FIXME 
linking to it.

https://github.com/llvm/llvm-project/pull/236384
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to