Skip to content

[Flang][MIF] Allocation of NON-ALLOCATABLE SAVE coarray - #205847

Merged
JDPailleux merged 7 commits into
llvm:mainfrom
SiPearl:mif/coarray-alloc-save
Jul 13, 2026
Merged

[Flang][MIF] Allocation of NON-ALLOCATABLE SAVE coarray#205847
JDPailleux merged 7 commits into
llvm:mainfrom
SiPearl:mif/coarray-alloc-save

Conversation

@JDPailleux

Copy link
Copy Markdown
Contributor

The goal of this PR is to add support for allocating NON-allocatable coarrays that are not in the Main program.
All allocations will be performed in a function called __mif_save_coarrays_allocate. If this function exists, then LLVM::GlobalCtors is created (or modified).
The initialization of the MIF with mif.init is moved too in a GlobalCtors with a priority of 0 to ensure that the initialization takes place before any MIF allocations.

@llvmorg-github-actions llvmorg-github-actions Bot added flang Flang issues not falling into any other category flang:fir-hlfir labels Jun 25, 2026
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-flang-fir-hlfir

Author: Jean-Didier PAILLEUX (JDPailleux)

Changes

The goal of this PR is to add support for allocating NON-allocatable coarrays that are not in the Main program.
All allocations will be performed in a function called __mif_save_coarrays_allocate. If this function exists, then LLVM::GlobalCtors is created (or modified).
The initialization of the MIF with mif.init is moved too in a GlobalCtors with a priority of 0 to ensure that the initialization takes place before any MIF allocations.


Patch is 22.29 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/205847.diff

14 Files Affected:

  • (modified) flang/include/flang/Lower/MultiImageFortran.h (+5)
  • (modified) flang/include/flang/Optimizer/Builder/MIFCommon.h (+9)
  • (modified) flang/include/flang/Optimizer/Transforms/Passes.td (+5)
  • (modified) flang/lib/Lower/Bridge.cpp (-9)
  • (modified) flang/lib/Lower/ConvertVariable.cpp (+6-5)
  • (modified) flang/lib/Lower/MultiImageFortran.cpp (+22)
  • (modified) flang/lib/Optimizer/Builder/MIFCommon.cpp (+36)
  • (modified) flang/lib/Optimizer/Builder/Runtime/Main.cpp (+2-1)
  • (modified) flang/lib/Optimizer/Passes/Pipelines.cpp (+2)
  • (modified) flang/lib/Optimizer/Transforms/CMakeLists.txt (+1)
  • (added) flang/lib/Optimizer/Transforms/EmitMIFGlobalCtors.cpp (+109)
  • (modified) flang/test/Fir/basic-program.fir (+1)
  • (modified) flang/test/Lower/MIF/coarray_allocation4.f90 (+69-2)
  • (modified) flang/test/Lower/MIF/coarray_allocation5.f90 (+23-3)
diff --git a/flang/include/flang/Lower/MultiImageFortran.h b/flang/include/flang/Lower/MultiImageFortran.h
index 002f156c807f2..c9b9e9f17cf39 100644
--- a/flang/include/flang/Lower/MultiImageFortran.h
+++ b/flang/include/flang/Lower/MultiImageFortran.h
@@ -76,6 +76,11 @@ mlir::Value genAllocateCoarray(
         std::nullopt,
     mlir::Value errMsg = {}, bool hasStat = false);
 
+void genAllocateNonAllocatableSaveCoarray(AbstractConverter &converter,
+                                          mlir::Location loc,
+                                          const semantics::Symbol &sym,
+                                          mlir::Value addr);
+
 //===----------------------------------------------------------------------===//
 // COARRAY expressions
 //===----------------------------------------------------------------------===//
diff --git a/flang/include/flang/Optimizer/Builder/MIFCommon.h b/flang/include/flang/Optimizer/Builder/MIFCommon.h
index bde3e6aa80b34..de760b91f7a45 100644
--- a/flang/include/flang/Optimizer/Builder/MIFCommon.h
+++ b/flang/include/flang/Optimizer/Builder/MIFCommon.h
@@ -14,11 +14,20 @@
 #include "mlir/IR/BuiltinOps.h"
 
 static constexpr llvm::StringRef coarrayHandleSuffix = "_coarray_handle";
+static constexpr llvm::StringRef mifInitializationName = "__mif_initialization";
+static constexpr llvm::StringRef mifSaveCoarraysAllocName =
+    "__mif_save_coarrays_allocate";
 
 namespace mif {
 
 std::string getFullUniqName(mlir::Value addr);
 
+mlir::func::FuncOp getOrCreateInitFunc(mlir::OpBuilder &builder,
+                                       mlir::ModuleOp mod,
+                                       llvm::StringRef name);
+
+void genMIFInit(fir::FirOpBuilder &, mlir::Location loc);
+
 } // namespace mif
 
 #endif // FORTRAN_OPTIMIZER_TRANSFORMS_MIFCOMMON_H_
diff --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index e7a83f64acef9..31d3b22e6e470 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -673,6 +673,11 @@ def MIFOpConversion : Pass<"mif-convert", "mlir::ModuleOp"> {
                            "mlir::cf::ControlFlowDialect", "mlir::DLTIDialect"];
 }
 
+def EmitMIFGlobalCtors : Pass<"emit-mif-global-ctors", "mlir::ModuleOp"> {
+  let summary = "Generate GlobalCtorOp to init MIF and allocate coarrays.";
+  let dependentDialects = ["fir::FIROpsDialect", "mlir::LLVM::LLVMDialect"];
+}
+
 def LoopInvariantCodeMotion : Pass<"flang-licm", "::mlir::func::FuncOp"> {
   let summary = "Hoist invariants from loops";
   let description = [{
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index ab7c141f11905..dd6d2daad5d68 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -6618,15 +6618,6 @@ class FirConverter : public Fortran::lower::AbstractConverter {
         if (sym.name() == "numeric_storage_size" && owner.IsModule() &&
             DEREF(owner.symbol()).name() == "iso_fortran_env")
           continue;
-
-        if (Fortran::evaluate::IsCoarray(sym) &&
-            !Fortran::semantics::IsAllocatable(sym) &&
-            Fortran::semantics::IsSaved(sym)) {
-          mlir::Location loc = toLocation();
-          TODO(
-              loc,
-              "coarray: non-ALLOCATABLE SAVE coarray outside the main program");
-        }
       }
       Fortran::lower::defineModuleVariable(*this, var);
     }
diff --git a/flang/lib/Lower/ConvertVariable.cpp b/flang/lib/Lower/ConvertVariable.cpp
index 2834531dccd96..d777ef900d4e4 100644
--- a/flang/lib/Lower/ConvertVariable.cpp
+++ b/flang/lib/Lower/ConvertVariable.cpp
@@ -2637,11 +2637,12 @@ void Fortran::lower::mapSymbolAttributes(
     assert(!Fortran::semantics::IsAllocatable(sym) &&
            "must be a non-ALLOCATABLE coarray");
     if (Fortran::semantics::IsSaved(sym) &&
-        sym.owner().kind() != Fortran::semantics::Scope::Kind::MainProgram)
-      TODO(loc,
-           "coarray: non-ALLOCATABLE SAVE coarray outside the main program");
-    ;
-    Fortran::lower::genAllocateCoarray(converter, loc, sym, addr);
+        (sym.owner().kind() != Fortran::semantics::Scope::Kind::MainProgram ||
+         var.isModuleOrSubmoduleVariable()))
+      Fortran::lower::genAllocateNonAllocatableSaveCoarray(converter, loc, sym,
+                                                           addr);
+    else
+      Fortran::lower::genAllocateCoarray(converter, loc, sym, addr);
     ::genDeclareSymbol(converter, symMap, sym, addr, len, extents, lbounds,
                        replace);
     return;
diff --git a/flang/lib/Lower/MultiImageFortran.cpp b/flang/lib/Lower/MultiImageFortran.cpp
index dc84a00c29d10..6eb10196f6042 100644
--- a/flang/lib/Lower/MultiImageFortran.cpp
+++ b/flang/lib/Lower/MultiImageFortran.cpp
@@ -20,6 +20,7 @@
 #include "flang/Optimizer/Builder/Todo.h"
 #include "flang/Parser/parse-tree.h"
 #include "flang/Semantics/expression.h"
+#include "mlir/IR/IRMapping.h"
 
 //===----------------------------------------------------------------------===//
 // Synchronization statements
@@ -451,6 +452,27 @@ mlir::Value Fortran::lower::genAllocateCoarray(
   return stat;
 }
 
+void Fortran::lower::genAllocateNonAllocatableSaveCoarray(
+    Fortran::lower::AbstractConverter &converter, mlir::Location loc,
+    const semantics::Symbol &sym, mlir::Value addr) {
+  fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+  mlir::ModuleOp mod = builder.getModule();
+  mlir::IRMapping mapping;
+
+  auto func = mif::getOrCreateInitFunc(builder, mod, mifSaveCoarraysAllocName);
+
+  mlir::Block &entry = func.getBody().front();
+  auto returnOp = entry.getTerminator();
+
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.setInsertionPoint(returnOp);
+
+  mlir::Operation &op = *addr.getDefiningOp();
+  mlir::Operation *localAddrOp = builder.clone(op, mapping);
+  Fortran::lower::genAllocateCoarray(converter, loc, sym,
+                                     localAddrOp->getResult(0));
+}
+
 //===----------------------------------------------------------------------===//
 // COARRAY expressions
 //===----------------------------------------------------------------------===//
diff --git a/flang/lib/Optimizer/Builder/MIFCommon.cpp b/flang/lib/Optimizer/Builder/MIFCommon.cpp
index e92d4629609a0..5b007ea937e86 100644
--- a/flang/lib/Optimizer/Builder/MIFCommon.cpp
+++ b/flang/lib/Optimizer/Builder/MIFCommon.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "flang/Optimizer/Builder/MIFCommon.h"
+#include "flang/Lower/MultiImageFortran.h"
 #include "flang/Optimizer/Builder/FIRBuilder.h"
 #include "flang/Optimizer/Dialect/MIF/MIFOps.h"
 #include "flang/Optimizer/Dialect/Support/KindMapping.h"
@@ -61,3 +62,38 @@ std::string mif::getFullUniqName(mlir::Value addr) {
   }
   return "";
 }
+
+mlir::func::FuncOp mif::getOrCreateInitFunc(mlir::OpBuilder &builder,
+                                            mlir::ModuleOp mod,
+                                            llvm::StringRef name) {
+
+  if (auto func = mod.lookupSymbol<mlir::func::FuncOp>(name))
+    return func;
+
+  auto funcType = builder.getFunctionType({}, {});
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.setInsertionPointToEnd(mod.getBody());
+
+  mlir::Location loc = mod.getLoc();
+  auto func = mlir::func::FuncOp::create(builder, loc, name, funcType);
+  func.setPublic();
+
+  func.addEntryBlock();
+  builder.setInsertionPointToEnd(&func.getBody().front());
+  mlir::func::ReturnOp::create(builder, loc);
+
+  return func;
+}
+
+void mif::genMIFInit(fir::FirOpBuilder &builder, mlir::Location loc) {
+  mlir::ModuleOp mod = builder.getModule();
+  auto func = getOrCreateInitFunc(builder, mod, mifInitializationName);
+
+  mlir::Block &entry = func.getBody().front();
+  auto returnOp = entry.getTerminator();
+
+  mlir::OpBuilder::InsertionGuard guard(builder);
+  builder.setInsertionPoint(returnOp);
+
+  mif::InitOp::create(builder, loc);
+}
diff --git a/flang/lib/Optimizer/Builder/Runtime/Main.cpp b/flang/lib/Optimizer/Builder/Runtime/Main.cpp
index 2b748ded039fd..ead469305bee2 100644
--- a/flang/lib/Optimizer/Builder/Runtime/Main.cpp
+++ b/flang/lib/Optimizer/Builder/Runtime/Main.cpp
@@ -10,6 +10,7 @@
 #include "flang/Lower/EnvironmentDefault.h"
 #include "flang/Optimizer/Builder/BoxValue.h"
 #include "flang/Optimizer/Builder/FIRBuilder.h"
+#include "flang/Optimizer/Builder/MIFCommon.h"
 #include "flang/Optimizer/Builder/Runtime/EnvironmentDefaults.h"
 #include "flang/Optimizer/Builder/Runtime/RTBuilder.h"
 #include "flang/Optimizer/Dialect/FIROps.h"
@@ -71,7 +72,7 @@ void fir::runtime::genMain(
     fir::CallOp::create(builder, loc, initFn);
   }
   if (initCoarrayEnv)
-    mif::InitOp::create(builder, loc);
+    mif::genMIFInit(builder, loc);
 
   fir::CallOp::create(builder, loc, qqMainFn);
 
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index c677962f30199..5d99e4d7586d3 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -10,6 +10,7 @@
 /// common to flang and the test tools.
 
 #include "flang/Optimizer/Passes/Pipelines.h"
+#include "flang/Optimizer/Builder/MIFCommon.h"
 #include "flang/Optimizer/OpenACC/Passes.h"
 #include "mlir/Conversion/Passes.h"
 #include "mlir/Dialect/LLVMIR/Transforms/Passes.h"
@@ -459,6 +460,7 @@ void createDefaultFIRCodeGenPassPipeline(mlir::PassManager &pm,
   }
 
   fir::addFIRToLLVMPass(pm, config);
+  pm.addPass(fir::createEmitMIFGlobalCtors());
 
   // Convert applicable OpenMP stack allocations to shared memory allocations
   // for GPU targets. This pass must run after any alloca-generating passes to
diff --git a/flang/lib/Optimizer/Transforms/CMakeLists.txt b/flang/lib/Optimizer/Transforms/CMakeLists.txt
index 5a3059ebbd97f..4f3e815e57adf 100644
--- a/flang/lib/Optimizer/Transforms/CMakeLists.txt
+++ b/flang/lib/Optimizer/Transforms/CMakeLists.txt
@@ -27,6 +27,7 @@ add_flang_library(FIRTransforms
   ControlFlowConverter.cpp
   ConvertComplexPow.cpp
   DebugTypeGenerator.cpp
+  EmitMIFGlobalCtors.cpp
   ExternalNameConversion.cpp
   FIRToSCF.cpp
   FIRToMemRef.cpp
diff --git a/flang/lib/Optimizer/Transforms/EmitMIFGlobalCtors.cpp b/flang/lib/Optimizer/Transforms/EmitMIFGlobalCtors.cpp
new file mode 100644
index 0000000000000..cadfd42046ff7
--- /dev/null
+++ b/flang/lib/Optimizer/Transforms/EmitMIFGlobalCtors.cpp
@@ -0,0 +1,109 @@
+//===-- EmitMIFGlobalCtors.cpp --------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Optimizer/Builder/MIFCommon.h"
+#include "flang/Optimizer/Builder/Runtime/Inquiry.h"
+#include "flang/Optimizer/Builder/Runtime/RTBuilder.h"
+#include "flang/Optimizer/Dialect/FIRDialect.h"
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/Dialect/MIF/MIFOps.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/Pass/Pass.h"
+
+namespace fir {
+#define GEN_PASS_DEF_EMITMIFGLOBALCTORS
+#include "flang/Optimizer/Transforms/Passes.h.inc"
+} // namespace fir
+
+using namespace mlir;
+using namespace Fortran::runtime;
+
+namespace {
+
+class EmitMIFGlobalCtors
+    : public fir::impl::EmitMIFGlobalCtorsBase<EmitMIFGlobalCtors> {
+public:
+  void runOnOperation() override {
+    mlir::ModuleOp mod = getOperation();
+    mlir::MLIRContext *ctx = mod.getContext();
+    mlir::OpBuilder builder(ctx);
+    builder.setInsertionPointToEnd(mod.getBody());
+    mlir::Location loc = builder.getUnknownLoc();
+
+    llvm::SmallVector<mlir::Attribute> funcs, priorities, data;
+    mlir::Type i32Ty = mlir::IntegerType::get(ctx, 32);
+    mlir::Attribute zeroAttr = mlir::LLVM::ZeroAttr::get(builder.getContext());
+
+    // Setting priority 0 for the initialization of mif.
+    if (mod.lookupSymbol<mlir::LLVM::LLVMFuncOp>(mifInitializationName)) {
+      funcs.push_back(mlir::FlatSymbolRefAttr::get(ctx, mifInitializationName));
+      priorities.push_back(mlir::IntegerAttr::get(i32Ty, 0));
+      data.push_back(zeroAttr);
+    }
+
+    // We set priority 1 because 0 is used for the function that call
+    // `mif.init`, to ensure that it is called after the parallel environment
+    // initialization.
+    if (mod.lookupSymbol<mlir::LLVM::LLVMFuncOp>(mifSaveCoarraysAllocName)) {
+      funcs.push_back(
+          mlir::FlatSymbolRefAttr::get(ctx, mifSaveCoarraysAllocName));
+      priorities.push_back(mlir::IntegerAttr::get(i32Ty, 1));
+      data.push_back(zeroAttr);
+    }
+
+    if (funcs.empty())
+      return;
+
+    // We check whether a GlobalCtorsOp already exists to avoid having
+    // multiple instances.
+    mlir::LLVM::GlobalCtorsOp globalCtors;
+    mod.walk([&](mlir::LLVM::GlobalCtorsOp op) { globalCtors = op; });
+
+    if (globalCtors) {
+      llvm::SmallVector<mlir::Attribute> mergedFuncs(
+          globalCtors.getCtors().begin(), globalCtors.getCtors().end());
+      llvm::SmallVector<mlir::Attribute> mergedPriorities(
+          globalCtors.getPriorities().begin(),
+          globalCtors.getPriorities().end());
+      llvm::SmallVector<mlir::Attribute> mergedData(
+          globalCtors.getData().begin(), globalCtors.getData().end());
+
+      llvm::SmallDenseSet<llvm::StringRef> existingNames;
+      for (auto attr : globalCtors.getCtors())
+        existingNames.insert(
+            llvm::cast<mlir::FlatSymbolRefAttr>(attr).getValue());
+
+      for (auto [func, priority, d] : llvm::zip(funcs, priorities, data)) {
+        auto name = llvm::cast<mlir::FlatSymbolRefAttr>(func).getValue();
+        if (!existingNames.contains(name)) {
+          mergedFuncs.push_back(func);
+          mergedPriorities.push_back(priority);
+          mergedData.push_back(d);
+        }
+      }
+
+      // Deleting the existing GlobalCtorsOp and creating a new one that
+      // contains the merged attributes.
+      builder.setInsertionPoint(globalCtors);
+      mlir::LLVM::GlobalCtorsOp::create(builder, globalCtors.getLoc(),
+                                        builder.getArrayAttr(mergedFuncs),
+                                        builder.getArrayAttr(mergedPriorities),
+                                        builder.getArrayAttr(mergedData));
+      globalCtors.erase();
+    } else {
+      builder.setInsertionPointToEnd(mod.getBody());
+      mlir::LLVM::GlobalCtorsOp::create(
+          builder, loc, builder.getArrayAttr(funcs),
+          builder.getArrayAttr(priorities), builder.getArrayAttr(data));
+    }
+  }
+};
+
+} // namespace
diff --git a/flang/test/Fir/basic-program.fir b/flang/test/Fir/basic-program.fir
index 2502a2a89a539..dd1bc5e83b601 100644
--- a/flang/test/Fir/basic-program.fir
+++ b/flang/test/Fir/basic-program.fir
@@ -184,6 +184,7 @@ func.func @_QQmain() {
 // PASSES-NEXT:  LowerNontemporalPass
 // PASSES-NEXT: FIRToLLVMLowering
 // PASSES-NEXT: ReconcileUnrealizedCasts
+// PASSES-NEXT: EmitMIFGlobalCtors
 // PASSES-NEXT: 'llvm.func' Pipeline
 // PASSES-NEXT:  StackToSharedPass
 // PASSES-NEXT: PrepareForOMPOffloadPrivatizationPass
diff --git a/flang/test/Lower/MIF/coarray_allocation4.f90 b/flang/test/Lower/MIF/coarray_allocation4.f90
index f0278ea2cd534..1f10067d651ba 100644
--- a/flang/test/Lower/MIF/coarray_allocation4.f90
+++ b/flang/test/Lower/MIF/coarray_allocation4.f90
@@ -1,8 +1,75 @@
-! RUN: not %flang_fc1 -emit-hlfir -fcoarray %s -o - 2>&1 | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir -fcoarray %s -o - 2>&1 | FileCheck %s
+! RUN: %flang_fc1 -emit-llvm -fcoarray %s -o - 2>&1 | FileCheck %s --check-prefix=LLVM
 
-!CHECK: not yet implemented: coarray: non-ALLOCATABLE SAVE coarray outside the main program
+! LLVM: llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @__mif_initialization, ptr null }, { i32, ptr, ptr } { i32 1, ptr @__mif_save_coarrays_allocate, ptr null }]
 
 subroutine test_coarray_save()
     implicit none
     real, SAVE :: n[*]
+    real, SAVE :: m[*]
+    real, SAVE :: p[*]
 end subroutine test_coarray_save
+
+program main
+  call test_coarray_save()
+end program
+
+! CHECK-LABEL: func.func @_QPtest_coarray_save()
+! CHECK:   %0 = fir.dummy_scope : !fir.dscope
+! CHECK:   %1 = fir.address_of(@_QFtest_coarray_saveEm) : !fir.ref<f32>
+! CHECK:   %2:2 = hlfir.declare %1 {uniq_name = "_QFtest_coarray_saveEm"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+! CHECK:   %3 = fir.address_of(@_QFtest_coarray_saveEn) : !fir.ref<f32>
+! CHECK:   %4:2 = hlfir.declare %3 {uniq_name = "_QFtest_coarray_saveEn"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+! CHECK:   %5 = fir.address_of(@_QFtest_coarray_saveEp) : !fir.ref<f32>
+! CHECK:   %6:2 = hlfir.declare %5 {uniq_name = "_QFtest_coarray_saveEp"} : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+! CHECK:   return
+
+! CHECK-LABEL: func.func @_QQmain() attributes {fir.bindc_name = "MAIN"}
+! CHECK:   %0 = fir.dummy_scope : !fir.dscope
+! CHECK:   fir.call @_QPtest_coarray_save() fastmath<contract> : () -> ()
+! CHECK:   return
+
+! CHECK: fir.global internal @_QFtest_coarray_saveEm : f32
+
+! CHECK-LABEL: func.func @__mif_save_coarrays_allocate()
+! CHECK:   %[[VAL_0:.*]] = fir.alloca !fir.array<0xi64>
+! CHECK:   %[[VAL_1:.*]] = fir.alloca !fir.array<1xi64>
+! CHECK:   %[[VAL_2:.*]] = fir.alloca !fir.array<0xi64>
+! CHECK:   %[[VAL_3:.*]] = fir.alloca !fir.array<1xi64>
+! CHECK:   %[[VAL_4:.*]] = fir.alloca !fir.array<0xi64>
+! CHECK:   %[[VAL_5:.*]] = fir.alloca !fir.array<1xi64>
+! CHECK:   %[[VAL_6:.*]] = fir.address_of(@_QFtest_coarray_saveEm) : !fir.ref<f32>
+! CHECK:   %[[C1_I64:.*]] = arith.constant 1 : i64
+! CHECK:   %[[C1_I64_0:.*]] = arith.constant 1 : i64
+! CHECK:   %[[C0:.*]] = arith.constant 0 : index
+! CHECK:   %[[VAL_7:.*]] = fir.coordinate_of %[[VAL_5]], %[[C0]] : (!fir.ref<!fir.array<1xi64>>, index) -> !fir.ref<i64>
+! CHECK:   fir.store %[[C1_I64_0]] to %[[VAL_7]] : !fir.ref<i64>
+! CHECK:   %[[VAL_8:.*]] = fir.embox %[[VAL_5]] : (!fir.ref<!fir.array<1xi64>>) -> !fir.box<!fir.array<1xi64>>
+! CHECK:   %[[C1_I64_1:.*]] = arith.constant 1 : i64
+! CHECK:   %[[VAL_9:.*]] = fir.embox %[[VAL_4]] : (!fir.ref<!fir.array<0xi64>>) -> !fir.box<!fir.array<0xi64>>
+! CHECK:   mif.alloc_coarray %[[VAL_6]] lcobounds %[[VAL_8]] ucobounds %[[VAL_9]] {uniq_name = "_QFtest_coarray_saveEm"} : (!fir.ref<f32>, !fir.box<!fir.array<1xi64>>, !fir.box<!fir.array<0xi64>>) -> ()
+! CHECK:   %[[VAL_10:.*]] = fir.address_of(@_QFtest_coarray_saveEn) : !fir.ref<f32>
+! CHECK:   %[[C1_I64_2:.*]] = arith.constant 1 : i64
+! CHECK:   %[[C1_I64_3:.*]] = arith.constant 1 : i64
+! CHECK:   %[[C0_4:.*]] = arith.constant 0 : index
+! CHECK:   %[[VAL_11:.*]] = fir.coordinate_of %[[VAL_3]], %[[C0_4]] : (!fir.ref<!fir.array<1xi64>>, index) -> !fir.ref<i64>
+! CHECK:   fir.store %[[C1_I64_3]] to %[[VAL_11]] : !fir.ref<i64>
+! CHECK:   %[[VAL_12:.*]] = fir.embox %[[VAL_3]] : (!fir.ref<!fir.array<1xi64>>) -> !fir.box<!fir.array<1xi64>>
+! CHECK:   %[[C1_I64_5:.*]] = arith.constant 1 : i64
+! CHECK:   %[[VAL_13:.*]] = fir.embox %[[VAL_2]] : (!fir.ref<!fir.array<0xi64>>) -> !fir.box<!fir.array<0xi64>>
+! CHECK:   mif.alloc_coarray %[[VAL_10]] lcobounds %[[VAL_12]] ucobounds %[[VAL_13]] {uniq_name = "_QFtest_coarray_saveEn"} : (!fir.ref<f32>, !fir.box<!fir.array<1xi64>>, !fir.box<!fir.array<0xi64>>) -> ()
+! CHECK:   %[[VAL_14:.*]] = fir.address_of(@_QFtest_coarray_saveEp) : !fir.ref<f32>
+! CHECK:   %[[C1_I64_6:.*]] = arith.constant 1 : i64
+! CHECK:   %[[C1_I64_7:.*]] = arith.constant 1 : i64
+! CHECK:   %[[C0_8:.*]] = arith.constant 0 : index
+! CHECK:   %[[VAL_15:.*]] = fir.coordinate_of %[[VAL_1]], %[[C0_8]] : (!fir.ref<!fir.array<1xi64>>, index) -> !fir.ref<i64>
+! CHECK:   fir.store %[[C1_I64_7]] to %[[VAL_15]] : !fir.ref<i64>
+! CHECK:   %[[VAL_16:.*]] = fir.embox %[[VAL_1]] : (!fir.ref<!fir.array<1xi64>>) -> !fir.box<!fir.array<1xi64>>
+! CHECK:   %[[VAL_C1_I64_9:.*]] = arith.constant 1 : i64
+! CHECK:   %[[VAL_17:.*]] = fir.embox %[[VAL_0]] : (!fir.ref<!fir.array<0xi64>>) -> !fir.box<!fir.array<0xi64>>
+! CHECK:   mif.alloc_coarray %...
[truncated]

@bonachea bonachea left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this exciting new work!

I have some questions..

Comment thread flang/test/Lower/MIF/coarray_allocation4.f90
Comment on lines +44 to +59
// Setting priority 0 for the initialization of mif.
if (mod.lookupSymbol<mlir::LLVM::LLVMFuncOp>(mifInitializationName)) {
funcs.push_back(mlir::FlatSymbolRefAttr::get(ctx, mifInitializationName));
priorities.push_back(mlir::IntegerAttr::get(i32Ty, 0));
data.push_back(zeroAttr);
}

// We set priority 1 because 0 is used for the function that call
// `mif.init`, to ensure that it is called after the parallel environment
// initialization.
if (mod.lookupSymbol<mlir::LLVM::LLVMFuncOp>(mifSaveCoarraysAllocName)) {
funcs.push_back(
mlir::FlatSymbolRefAttr::get(ctx, mifSaveCoarraysAllocName));
priorities.push_back(mlir::IntegerAttr::get(i32Ty, 1));
data.push_back(zeroAttr);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand the point of registering these as two separate Ctors.

We deliberately designed prif_init to be idempotent, meaning you are permitted to call it multiple times in a process and invocations after the first call are silently ignored. We don't currently say anything either way about whether the mif_InitOp dialect operation provides the same guarantee, but I think we probably need that property to correctly handle separate compilation of different object files that all declare non-allocatable save coarrays (because initialization of the parallel runtime library is a global process property, not per-TU).

Once we specify idempotence for mif_InitOp dialect operation, then I think we don't need two Ctors and the mif_InitOp can just be the first operation inside the allocation Ctor.

I think we may eventually need a second (or third) Ctor to handle the full generality of save coarray initializers, but that's orthogonal to this decision.

@JDPailleux

Copy link
Copy Markdown
Contributor Author

Thanks @bonachea for this initial feedback. That was actually one of my small question. I've updated the PR based on your comments for mif.init.
For the default initialization part, yes it seems that this isn't handled either.

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 4891 tests passed
  • 135 tests skipped

✅ The build succeeded and all tests passed.

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 4453 tests passed
  • 250 tests skipped

✅ The build succeeded and all tests passed.

@bonachea bonachea left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the improvements!

One other cosmetic nitpick, otherwise LGTM!

Comment thread flang/lib/Lower/ConvertVariable.cpp Outdated

@bonachea bonachea left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@bonachea

bonachea commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@jeanPerier @ktras Can we please get your review on this PR?

@ktras

ktras commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Hello @JDPailleux. Thanks for the PR! I have compiled a Fortran program with non-allocatable save coarrays with this branch and linked with Caffeine. The program unfortunately hangs and it appears, from dumping the MLIR, that you may be missing a call to prif_init before the first prif_allocate_coarray, which is prohibited by PRIF.

@ktras ktras left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix the issue mentioned in previous comment.

@bonachea

bonachea commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

I have compiled a Fortran program with non-allocatable save coarrays with this branch and linked with Caffeine. The program unfortunately hangs and it appears, from dumping the MLIR, that you may be missing a call to prif_init before the first prif_allocate_coarray, which is prohibited by PRIF.

@JDPailleux The program that Kate mentions is Caffeine's naive-multi-image test which has just been updated on the tip of caffeine:main to include new sub tests.

Be sure to compile with -DHAVE_SAVE_COARRAY -DHAVE_MODULE_COARRAY to activate the subtests that exercise the logic added in this PR.

@bonachea

bonachea commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

@JDPailleux some more details:
On Linux/x64_64 this PR results in a (confusing) assertion failure inside Caffeine at startup.

Here is the crash stack at startup on a Linux/x64_64 build of this PR's 9a2007d commit, using Caffeine's naive-multi-image test:

#0  assert_m::assert_always (assertion=.FALSE., description=..., file=..., 
    line=<error reading variable: Cannot access memory at address 0x0>) at build/dependencies/assert/src/assert_m.F90:98
#1  0x000055555557dc53 in prif::team_check (team=..., 
    known_active=<error reading variable: Cannot access memory at address 0x0>, 
    cycle_check=<error reading variable: Location address is not set.>)
    at /<redacted>/caffeine/src/caffeine/prif_private_s.F90:581
#2  0x0000555555567f8e in prif::prif_allocate_coarray (lcobounds=..., ucobounds=..., size_in_bytes=<optimized out>, 
    final_proc=<optimized out>, coarray_handle=<error reading variable: Cannot access memory at address 0x2b>, 
    allocated_memory=<error reading variable: Cannot access memory at address 0x0>, stat=<optimized out>, 
    errmsg=<error reading variable: value requires 3573412790848 bytes, which is more than max-value-size>, 
    errmsg_alloc=...) at /<redacted>/caffeine/src/caffeine/allocation_s.F90:46
#3  0x0000555555567972 in __mif_save_coarrays_allocate () at app/native-multi-image.F90:158
#4  0x00007ffff7c2a304 in call_init (env=<optimized out>, argv=0x7fffffffd7d8, argc=1) at ../csu/libc-start.c:145
#5  __libc_start_main_impl (main=0x555555567d00 <main>, argc=1, argv=0x7fffffffd7d8, init=<optimized out>, 
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd7c8) at ../csu/libc-start.c:347
#6  0x0000555555560c75 in _start ()

I've independently confirmed (using debugger breakpoints) that prif_init was never invoked before this point (which includes a call to prif::prif_allocate_coarray).

In order to help catch such defects in the future, I've just merged a PR to caffeine:main that adds assertions that directly diagnose this violation of the PRIF specification. Running the reproducer compiled with this PR branch and -DASSERTIONS -DHAVE_SAVE_COARRAY against the latest Caffeine on Linux/x86_64 now immediately produces the expected failure:

Fortran ERROR STOP: Assertion failure on image 0 at ././src/caffeine/allocation_s.F90:46: call_assert(prif_init_called_previously)

@bonachea bonachea left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to resolve the missing prif_init defect

@jeanPerier jeanPerier left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @JDPailleux, the strategy looks good to me. Minor nit in inline comment.
LGTM provided you address the functional issues raised by other reviewers.

Comment thread flang/lib/Optimizer/Builder/MIFCommon.cpp Outdated
@JDPailleux

Copy link
Copy Markdown
Contributor Author

Ok thanks for your reviews. I'll fix your functionnal issue !

@JDPailleux

Copy link
Copy Markdown
Contributor Author

@bonachea @ktras I add the missing mif.init call in @__mif_save_coarrays_allocate. Now this is works for me with you exemple in Caffeine (with -DASSERTIONS and others variables that you give me).

@bonachea bonachea left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @JDPailleux - Thanks for the followup on this!

I've retested your branch at a8ac82f and confirmed that it still compiles Caffeine's native-multi-image test with -DASSERTIONS -DHAVE_SAVE_COARRAY enabled and now executes as expected, without assertion failures.

LGTM!

@ktras ktras left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @bonachea did, I have retested and can confirm that your latest pushes to this branch fix the bug that was discovered when testing earlier.

LGTM.

@JDPailleux
JDPailleux force-pushed the mif/coarray-alloc-save branch from a8ac82f to 443ca47 Compare July 13, 2026 10:50
@JDPailleux

Copy link
Copy Markdown
Contributor Author

Conflicts has been solved, and if @jeanPerier agrees, I can merge this PR

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@jeanPerier jeanPerier left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM

@JDPailleux
JDPailleux merged commit 5595710 into llvm:main Jul 13, 2026
11 checks passed
ktras pushed a commit that referenced this pull request Jul 13, 2026
Update `flang/docs/FortranStandardsSupport.md` to reflect the current
state of multi-image (coarray) feature support.

The previous list of multi-image features was incomplete, and several
multi-image features added in F2008 were inaccurately listed in the
F2018 section.

Recent PRs adding functionality now reflected in this document include:
* #205847
* #192944
* #182110

Older (pre-22) PRs adding functionality that was not previously
mentioned include:
* #154081
* #154166
pedroMVicente pushed a commit to pedroMVicente/llvm-project that referenced this pull request Jul 15, 2026
The goal of this PR is to add support for allocating NON-allocatable
coarrays that are not in the `Main` program.
All allocations will be performed in a function called
`__mif_save_coarrays_allocate`. If this function exists, then
LLVM::GlobalCtors is created (or modified).
The initialization of the MIF with `mif.init` is moved too in a
GlobalCtors with a priority of 0 to ensure that the initialization takes
place before any MIF allocations.

---------

Co-authored-by: Dan Bonachea <dobonachea@lbl.gov>
pedroMVicente pushed a commit to pedroMVicente/llvm-project that referenced this pull request Jul 15, 2026
Update `flang/docs/FortranStandardsSupport.md` to reflect the current
state of multi-image (coarray) feature support.

The previous list of multi-image features was incomplete, and several
multi-image features added in F2008 were inaccurately listed in the
F2018 section.

Recent PRs adding functionality now reflected in this document include:
* llvm#205847
* llvm#192944
* llvm#182110

Older (pre-22) PRs adding functionality that was not previously
mentioned include:
* llvm#154081
* llvm#154166
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:fir-hlfir flang Flang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants