Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions flang/include/flang/Optimizer/Builder/IntrinsicCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ struct IntrinsicLibrary {
template <mlir::arith::CmpIPredicate pred>
fir::ExtendedValue genCPtrCompare(mlir::Type,
llvm::ArrayRef<fir::ExtendedValue>);
void genCoBroadcast(llvm::ArrayRef<fir::ExtendedValue>);
void genCoMax(llvm::ArrayRef<fir::ExtendedValue>);
void genCoMin(llvm::ArrayRef<fir::ExtendedValue>);
void genCoSum(llvm::ArrayRef<fir::ExtendedValue>);
mlir::Value genCosd(mlir::Type, llvm::ArrayRef<mlir::Value>);
mlir::Value genCospi(mlir::Type, llvm::ArrayRef<mlir::Value>);
void genDateAndTime(llvm::ArrayRef<fir::ExtendedValue>);
Expand Down
22 changes: 22 additions & 0 deletions flang/include/flang/Optimizer/Builder/Runtime/Coarray.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ namespace fir::runtime {
return fir::NameUniquer::doProcedure({"prif"}, {}, oss.str()); \
}()

#define PRIF_STAT_TYPE builder.getRefType(builder.getI32Type())
#define PRIF_ERRMSG_TYPE \
fir::BoxType::get(fir::CharacterType::get(builder.getContext(), 1, \
fir::CharacterType::unknownLen()))

/// Generate Call to runtime prif_init
mlir::Value genInitCoarray(fir::FirOpBuilder &builder, mlir::Location loc);

Expand All @@ -49,5 +54,22 @@ mlir::Value getNumImagesWithTeam(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Value getThisImage(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Value team = {});

/// Generate call to runtime subroutine prif_co_broadcast
void genCoBroadcast(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Value A, mlir::Value sourceImage, mlir::Value stat,
mlir::Value errmsg);

/// Generate call to runtime subroutine prif_co_max and prif_co_max_character
void genCoMax(fir::FirOpBuilder &builder, mlir::Location loc, mlir::Value A,
mlir::Value resultImage, mlir::Value stat, mlir::Value errmsg);

/// Generate call to runtime subroutine prif_co_min or prif_co_min_character
void genCoMin(fir::FirOpBuilder &builder, mlir::Location loc, mlir::Value A,
mlir::Value resultImage, mlir::Value stat, mlir::Value errmsg);

/// Generate call to runtime subroutine prif_co_sum
void genCoSum(fir::FirOpBuilder &builder, mlir::Location loc, mlir::Value A,
mlir::Value resultImage, mlir::Value stat, mlir::Value errmsg);

} // namespace fir::runtime
#endif // FORTRAN_OPTIMIZER_BUILDER_RUNTIME_COARRAY_H
108 changes: 108 additions & 0 deletions flang/lib/Optimizer/Builder/IntrinsicCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,34 @@ static constexpr IntrinsicHandler handlers[]{
{"cmplx",
&I::genCmplx,
{{{"x", asValue}, {"y", asValue, handleDynamicOptional}}}},
{"co_broadcast",
&I::genCoBroadcast,
{{{"a", asBox},
{"source_image", asAddr},
{"stat", asAddr, handleDynamicOptional},
{"errmsg", asBox, handleDynamicOptional}}},
/*isElemental*/ false},
{"co_max",
&I::genCoMax,
{{{"a", asBox},
{"result_image", asAddr, handleDynamicOptional},
{"stat", asAddr, handleDynamicOptional},
{"errmsg", asBox, handleDynamicOptional}}},
/*isElemental*/ false},
{"co_min",
&I::genCoMin,
{{{"a", asBox},
{"result_image", asAddr, handleDynamicOptional},
{"stat", asAddr, handleDynamicOptional},
{"errmsg", asBox, handleDynamicOptional}}},
/*isElemental*/ false},
{"co_sum",
&I::genCoSum,
{{{"a", asBox},
{"result_image", asAddr, handleDynamicOptional},
{"stat", asAddr, handleDynamicOptional},
{"errmsg", asBox, handleDynamicOptional}}},
/*isElemental*/ false},
{"command_argument_count", &I::genCommandArgumentCount},
{"conjg", &I::genConjg},
{"cosd", &I::genCosd},
Expand Down Expand Up @@ -3649,6 +3677,86 @@ mlir::Value IntrinsicLibrary::genCmplx(mlir::Type resultType,
imag);
}

// CO_BROADCAST
void IntrinsicLibrary::genCoBroadcast(llvm::ArrayRef<fir::ExtendedValue> args) {
checkCoarrayEnabled();
assert(args.size() == 4);
mlir::Value sourceImage = fir::getBase(args[1]);
mlir::Value status =
isStaticallyAbsent(args[2])
? builder
.create<fir::AbsentOp>(loc,
builder.getRefType(builder.getI32Type()))
.getResult()
: fir::getBase(args[2]);
mlir::Value errmsg =
isStaticallyAbsent(args[3])
? builder.create<fir::AbsentOp>(loc, PRIF_ERRMSG_TYPE).getResult()
: fir::getBase(args[3]);
fir::runtime::genCoBroadcast(builder, loc, fir::getBase(args[0]), sourceImage,
status, errmsg);
}

// CO_MAX
void IntrinsicLibrary::genCoMax(llvm::ArrayRef<fir::ExtendedValue> args) {
checkCoarrayEnabled();
assert(args.size() == 4);
mlir::Value refNone =
builder
.create<fir::AbsentOp>(loc, builder.getRefType(builder.getI32Type()))
.getResult();
mlir::Value resultImage =
isStaticallyAbsent(args[1]) ? refNone : fir::getBase(args[1]);
mlir::Value status =
isStaticallyAbsent(args[2]) ? refNone : fir::getBase(args[2]);
mlir::Value errmsg =
isStaticallyAbsent(args[3])
? builder.create<fir::AbsentOp>(loc, PRIF_ERRMSG_TYPE).getResult()
: fir::getBase(args[3]);
fir::runtime::genCoMax(builder, loc, fir::getBase(args[0]), resultImage,
status, errmsg);
}

// CO_MIN
void IntrinsicLibrary::genCoMin(llvm::ArrayRef<fir::ExtendedValue> args) {
checkCoarrayEnabled();
assert(args.size() == 4);
mlir::Value refNone =
builder
.create<fir::AbsentOp>(loc, builder.getRefType(builder.getI32Type()))
.getResult();
mlir::Value resultImage =
isStaticallyAbsent(args[1]) ? refNone : fir::getBase(args[1]);
mlir::Value status =
isStaticallyAbsent(args[2]) ? refNone : fir::getBase(args[2]);
mlir::Value errmsg =
isStaticallyAbsent(args[3])
? builder.create<fir::AbsentOp>(loc, PRIF_ERRMSG_TYPE).getResult()
: fir::getBase(args[3]);
fir::runtime::genCoMin(builder, loc, fir::getBase(args[0]), resultImage,
status, errmsg);
}

// CO_SUM
void IntrinsicLibrary::genCoSum(llvm::ArrayRef<fir::ExtendedValue> args) {
checkCoarrayEnabled();
assert(args.size() == 4);
mlir::Value absentInt =
builder
.create<fir::AbsentOp>(loc, builder.getRefType(builder.getI32Type()))
.getResult();
mlir::Value resultImage =
isStaticallyAbsent(args[1]) ? absentInt : fir::getBase(args[1]);
mlir::Value status =
isStaticallyAbsent(args[2]) ? absentInt : fir::getBase(args[2]);
mlir::Value errmsg =
isStaticallyAbsent(args[3])
? builder.create<fir::AbsentOp>(loc, PRIF_ERRMSG_TYPE).getResult()
: fir::getBase(args[3]);
fir::runtime::genCoSum(builder, loc, fir::getBase(args[0]), resultImage,
status, errmsg);
}

// COMMAND_ARGUMENT_COUNT
fir::ExtendedValue IntrinsicLibrary::genCommandArgumentCount(
mlir::Type resultType, llvm::ArrayRef<fir::ExtendedValue> args) {
Expand Down
81 changes: 81 additions & 0 deletions flang/lib/Optimizer/Builder/Runtime/Coarray.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@
using namespace Fortran::runtime;
using namespace Fortran::semantics;

// Most PRIF functions take `errmsg` and `errmsg_alloc` as two optional
// arguments of intent (out). One is allocatable, the other is not.
// It is the responsibility of the compiler to ensure that the appropriate
// optional argument is passed, and at most one must be provided in a given
// call.
// Depending on the type of `errmsg`, this function will return the pair
// corresponding to (`errmsg`, `errmsg_alloc`).
static std::pair<mlir::Value, mlir::Value>
genErrmsgPRIF(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Value errmsg) {
bool isAllocatableErrmsg = fir::isAllocatableType(errmsg.getType());

mlir::Value absent = builder.create<fir::AbsentOp>(loc, PRIF_ERRMSG_TYPE);
mlir::Value errMsg = isAllocatableErrmsg ? absent : errmsg;
mlir::Value errMsgAlloc = isAllocatableErrmsg ? errmsg : absent;
return {errMsg, errMsgAlloc};
}

/// Generate Call to runtime prif_init
mlir::Value fir::runtime::genInitCoarray(fir::FirOpBuilder &builder,
mlir::Location loc) {
Expand Down Expand Up @@ -84,3 +102,66 @@ mlir::Value fir::runtime::getThisImage(fir::FirOpBuilder &builder,
builder.create<fir::CallOp>(loc, funcOp, args);
return builder.create<fir::LoadOp>(loc, result);
}

/// Generate call to collective subroutines except co_reduce
/// A must be lowered as a box
void genCollectiveSubroutine(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Value A, mlir::Value rootImage,
mlir::Value stat, mlir::Value errmsg,
std::string coName) {
mlir::Type boxTy = fir::BoxType::get(builder.getNoneType());
mlir::FunctionType ftype =
PRIF_FUNCTYPE(boxTy, builder.getRefType(builder.getI32Type()),
PRIF_STAT_TYPE, PRIF_ERRMSG_TYPE, PRIF_ERRMSG_TYPE);
mlir::func::FuncOp funcOp = builder.createFunction(loc, coName, ftype);

auto [errmsgArg, errmsgAllocArg] = genErrmsgPRIF(builder, loc, errmsg);
llvm::SmallVector<mlir::Value> args = fir::runtime::createArguments(
builder, loc, ftype, A, rootImage, stat, errmsgArg, errmsgAllocArg);
fir::CallOp::create(builder, loc, funcOp, args);
}

/// Generate call to runtime subroutine prif_co_broadcast
void fir::runtime::genCoBroadcast(fir::FirOpBuilder &builder,
mlir::Location loc, mlir::Value A,
mlir::Value sourceImage, mlir::Value stat,
mlir::Value errmsg) {
genCollectiveSubroutine(builder, loc, A, sourceImage, stat, errmsg,
PRIFNAME_SUB("co_broadcast"));
}

/// Generate call to runtime subroutine prif_co_max or prif_co_max_character
void fir::runtime::genCoMax(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Value A, mlir::Value resultImage,
mlir::Value stat, mlir::Value errmsg) {
mlir::Type argTy =
fir::unwrapSequenceType(fir::unwrapPassByRefType(A.getType()));
if (mlir::isa<fir::CharacterType>(argTy))
genCollectiveSubroutine(builder, loc, A, resultImage, stat, errmsg,
PRIFNAME_SUB("co_max_character"));
else
genCollectiveSubroutine(builder, loc, A, resultImage, stat, errmsg,
PRIFNAME_SUB("co_max"));
}

/// Generate call to runtime subroutine prif_co_min or prif_co_min_character
void fir::runtime::genCoMin(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Value A, mlir::Value resultImage,
mlir::Value stat, mlir::Value errmsg) {
mlir::Type argTy =
fir::unwrapSequenceType(fir::unwrapPassByRefType(A.getType()));
if (mlir::isa<fir::CharacterType>(argTy))
genCollectiveSubroutine(builder, loc, A, resultImage, stat, errmsg,
PRIFNAME_SUB("co_min_character"));
else
genCollectiveSubroutine(builder, loc, A, resultImage, stat, errmsg,
PRIFNAME_SUB("co_min"));
}

/// Generate call to runtime subroutine prif_co_sum
void fir::runtime::genCoSum(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Value A, mlir::Value resultImage,
mlir::Value stat, mlir::Value errmsg) {
genCollectiveSubroutine(builder, loc, A, resultImage, stat, errmsg,
PRIFNAME_SUB("co_sum"));
}
92 changes: 92 additions & 0 deletions flang/test/Lower/Coarray/co_broadcast.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
! RUN: %flang_fc1 -emit-hlfir -fcoarray %s -o - | FileCheck %s

program test_co_broadcast
integer :: i, array_i(2), status
real :: r, array_r(2)
double precision :: d, array_d(2)
complex :: c, array_c(2)
character(len=1) :: message

! CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
! CHECK: %[[V1:.*]] = fir.embox %[[VAR_I:.*]]#0 : (!fir.ref<i32>) -> !fir.box<i32>
! CHECK: fir.store %[[C1_i32]] to %[[IMAGE_RESULT:.*]] : !fir.ref<i32>
! CHECK: %[[V2:.*]] = fir.absent !fir.ref<i32>
! CHECK: %[[V3:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V4:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V5:.*]] = fir.convert %[[V1]] : (!fir.box<i32>) -> !fir.box<none>
! CHECK: fir.call @_QMprifPprif_co_broadcast(%[[V5]], %[[IMAGE_RESULT]], %[[V2]], %[[V3]], %[[V4]]) fastmath<contract> : (!fir.box<none>, !fir.ref<i32>, !fir.ref<i32>, !fir.box<!fir.char<1,?>>, !fir.box<!fir.char<1,?>>) -> ()
call co_broadcast(i, source_image=1)

! CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
! CHECK: %[[V1:.*]] = fir.embox %[[VAR_C:.*]]#0 : (!fir.ref<complex<f32>>) -> !fir.box<complex<f32>>
! CHECK: fir.store %[[C1_i32]] to %[[IMAGE_RESULT:.*]] : !fir.ref<i32>
! CHECK: %[[V2:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V3:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V4:.*]] = fir.convert %[[V1]] : (!fir.box<complex<f32>>) -> !fir.box<none>
! CHECK: fir.call @_QMprifPprif_co_broadcast(%[[V4]], %[[IMAGE_RESULT]], %[[STATUS:.*]], %[[V2]], %[[V3]]) fastmath<contract> : (!fir.box<none>, !fir.ref<i32>, !fir.ref<i32>, !fir.box<!fir.char<1,?>>, !fir.box<!fir.char<1,?>>) -> ()
call co_broadcast(c, source_image=1, stat=status)

! CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
! CHECK: %[[V1:.*]] = fir.embox %[[VAR_D:.*]]#0 : (!fir.ref<f64>) -> !fir.box<f64>
! CHECK: fir.store %[[C1_i32]] to %[[IMAGE_RESULT:.*]] : !fir.ref<i32>
! CHECK: %[[V2:.*]] = fir.embox %[[MESSAGE:.*]]#0 : (!fir.ref<!fir.char<1>>) -> !fir.box<!fir.char<1>>
! CHECK: %[[V3:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V4:.*]] = fir.convert %[[V1]] : (!fir.box<f64>) -> !fir.box<none>
! CHECK: %[[V5:.*]] = fir.convert %[[V2]] : (!fir.box<!fir.char<1>>) -> !fir.box<!fir.char<1,?>>
! CHECK: fir.call @_QMprifPprif_co_broadcast(%[[V4]], %[[IMAGE_RESULT]], %[[STATUS]], %[[V5]], %[[V3]]) fastmath<contract> : (!fir.box<none>, !fir.ref<i32>, !fir.ref<i32>, !fir.box<!fir.char<1,?>>, !fir.box<!fir.char<1,?>>) -> ()
call co_broadcast(d, source_image=1, stat=status, errmsg=message)

! CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
! CHECK: %[[V1:.*]] = fir.embox %[[VAR_R:.*]]#0 : (!fir.ref<f32>) -> !fir.box<f32>
! CHECK: fir.store %[[C1_i32]] to %[[IMAGE_RESULT:.*]] : !fir.ref<i32>
! CHECK: %[[V2:.*]] = fir.embox %[[MESSAGE]]#0 : (!fir.ref<!fir.char<1>>) -> !fir.box<!fir.char<1>>
! CHECK: %[[V3:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V4:.*]] = fir.convert %[[V1]] : (!fir.box<f32>) -> !fir.box<none>
! CHECK: %[[V5:.*]] = fir.convert %[[V2]] : (!fir.box<!fir.char<1>>) -> !fir.box<!fir.char<1,?>>
! CHECK: fir.call @_QMprifPprif_co_broadcast(%[[V4]], %[[IMAGE_RESULT]], %[[STATUS]], %[[V5]], %[[V3]]) fastmath<contract> : (!fir.box<none>, !fir.ref<i32>, !fir.ref<i32>, !fir.box<!fir.char<1,?>>, !fir.box<!fir.char<1,?>>) -> ()
call co_broadcast(r, source_image=1, stat=status, errmsg=message)

! CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
! CHECK: %[[SHAPE_2:.*]] = fir.shape %[[C2_2:.*]] : (index) -> !fir.shape<1>
! CHECK: %[[V1:.*]] = fir.embox %[[ARRAY_I:.*]]#0(%[[SHAPE_2]]) : (!fir.ref<!fir.array<2xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<2xi32>>
! CHECK: fir.store %[[C1_i32]] to %[[IMAGE_RESULT:.*]] : !fir.ref<i32>
! CHECK: %[[V2:.*]] = fir.absent !fir.ref<i32>
! CHECK: %[[V3:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V4:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V5:.*]] = fir.convert %[[V1]] : (!fir.box<!fir.array<2xi32>>) -> !fir.box<none>
! CHECK: fir.call @_QMprifPprif_co_broadcast(%[[V5]], %[[IMAGE_RESULT]], %[[V2]], %[[V3]], %[[V4]]) fastmath<contract> : (!fir.box<none>, !fir.ref<i32>, !fir.ref<i32>, !fir.box<!fir.char<1,?>>, !fir.box<!fir.char<1,?>>) -> ()
call co_broadcast(array_i, source_image=1)

! CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
! CHECK: %[[SHAPE_2:.*]] = fir.shape %[[C2_2:.*]] : (index) -> !fir.shape<1>
! CHECK: %[[V1:.*]] = fir.embox %[[ARRAY_C:.*]]#0(%[[SHAPE_2]]) : (!fir.ref<!fir.array<2xcomplex<f32>>>, !fir.shape<1>) -> !fir.box<!fir.array<2xcomplex<f32>>>
! CHECK: fir.store %[[C1_i32]] to %[[IMAGE_RESULT:.*]] : !fir.ref<i32>
! CHECK: %[[V2:.*]] = fir.absent !fir.ref<i32>
! CHECK: %[[V3:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V4:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V5:.*]] = fir.convert %[[V1]] : (!fir.box<!fir.array<2xcomplex<f32>>>) -> !fir.box<none>
! CHECK: fir.call @_QMprifPprif_co_broadcast(%[[V5]], %[[IMAGE_RESULT]], %[[V2]], %[[V3]], %[[V4]]) fastmath<contract> : (!fir.box<none>, !fir.ref<i32>, !fir.ref<i32>, !fir.box<!fir.char<1,?>>, !fir.box<!fir.char<1,?>>) -> ()
call co_broadcast(array_c, source_image=1)

! CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
! CHECK: %[[SHAPE_2:.*]] = fir.shape %[[C2_2:.*]] : (index) -> !fir.shape<1>
! CHECK: %[[V1:.*]] = fir.embox %[[ARRAY_D:.*]]#0(%[[SHAPE_2]]) : (!fir.ref<!fir.array<2xf64>>, !fir.shape<1>) -> !fir.box<!fir.array<2xf64>>
! CHECK: fir.store %[[C1_i32]] to %[[IMAGE_RESULT:.*]] : !fir.ref<i32>
! CHECK: %[[V2:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V3:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V4:.*]] = fir.convert %[[V1]] : (!fir.box<!fir.array<2xf64>>) -> !fir.box<none>
! CHECK: fir.call @_QMprifPprif_co_broadcast(%[[V4]], %[[IMAGE_RESULT]], %[[STATUS]], %[[V2]], %[[V3]]) fastmath<contract> : (!fir.box<none>, !fir.ref<i32>, !fir.ref<i32>, !fir.box<!fir.char<1,?>>, !fir.box<!fir.char<1,?>>) -> ()
call co_broadcast(array_d, source_image=1, stat=status)

! CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
! CHECK: %[[SHAPE_2:.*]] = fir.shape %[[C2_2:.*]] : (index) -> !fir.shape<1>
! CHECK: %[[V1:.*]] = fir.embox %[[ARRAY_C:.*]]#0(%[[SHAPE_2]]) : (!fir.ref<!fir.array<2xf32>>, !fir.shape<1>) -> !fir.box<!fir.array<2xf32>>
! CHECK: fir.store %[[C1_i32]] to %[[IMAGE_RESULT:.*]] : !fir.ref<i32>
! CHECK: %[[V2:.*]] = fir.embox %[[MESSAGE]]#0 : (!fir.ref<!fir.char<1>>) -> !fir.box<!fir.char<1>>
! CHECK: %[[V3:.*]] = fir.absent !fir.box<!fir.char<1,?>>
! CHECK: %[[V4:.*]] = fir.convert %[[V1]] : (!fir.box<!fir.array<2xf32>>) -> !fir.box<none>
! CHECK: %[[V5:.*]] = fir.convert %[[V2]] : (!fir.box<!fir.char<1>>) -> !fir.box<!fir.char<1,?>>
! CHECK: fir.call @_QMprifPprif_co_broadcast(%[[V4]], %[[IMAGE_RESULT]], %[[STATUS]], %[[V5]], %[[V3]]) fastmath<contract> : (!fir.box<none>, !fir.ref<i32>, !fir.ref<i32>, !fir.box<!fir.char<1,?>>, !fir.box<!fir.char<1,?>>) -> ()
call co_broadcast(array_r, source_image=1, stat= status, errmsg=message)

end program
Loading