Skip to content

Commit 4679132

Browse files
authored
[flang] Lower ASYNCHRONOUS variables and IO statements (#80008)
Finish plugging-in ASYNCHRONOUS IO in lowering (GetAsynchronousId was not used yet). Add a runtime implementation for GetAsynchronousId (only the signature was defined). Always return zero since flang runtime "fakes" asynchronous IO (data transfer are always complete, see flang/docs/IORuntimeInternals.md). Update all runtime integer argument and results for IDs to use the AsynchronousId int alias for consistency. In lowering, asynchronous attribute is added on the hlfir.declare of ASYNCHRONOUS variable, but nothing else is done. This is OK given the synchronous aspects of flang IO, but it would be safer to treat these variable as volatile (prevent code motion of related store/loads) since the asynchronous data change can also be done by C defined user procedure (see 18.10.4 Asynchronous communication). Flang lowering anyway does not give enough info for LLVM to do such code motions (the variables that are passed in a call are not given the noescape attribute, so LLVM will assume any later opaque call may modify the related data and would not move load/stores of such variables before/after calls even if it could from a pure Fortran point of view without ASYNCHRONOUS).
1 parent 70fb96a commit 4679132

File tree

6 files changed

+128
-72
lines changed

6 files changed

+128
-72
lines changed

flang/include/flang/Runtime/io-api.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ std::size_t IONAME(GetIoLength)(Cookie);
332332
void IONAME(GetIoMsg)(Cookie, char *, std::size_t); // IOMSG=
333333

334334
// Defines ID= on READ/WRITE(ASYNCHRONOUS='YES')
335-
int IONAME(GetAsynchronousId)(Cookie);
335+
AsynchronousId IONAME(GetAsynchronousId)(Cookie);
336336

337337
// INQUIRE() specifiers are mostly identified by their NUL-terminated
338338
// case-insensitive names.
@@ -343,7 +343,7 @@ bool IONAME(InquireCharacter)(Cookie, InquiryKeywordHash, char *, std::size_t);
343343
// EXIST, NAMED, OPENED, and PENDING (without ID):
344344
bool IONAME(InquireLogical)(Cookie, InquiryKeywordHash, bool &);
345345
// PENDING with ID
346-
bool IONAME(InquirePendingId)(Cookie, std::int64_t, bool &);
346+
bool IONAME(InquirePendingId)(Cookie, AsynchronousId, bool &);
347347
// NEXTREC, NUMBER, POS, RECL, SIZE
348348
bool IONAME(InquireInteger64)(
349349
Cookie, InquiryKeywordHash, std::int64_t &, int kind = 8);

flang/lib/Lower/CallInterface.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -976,8 +976,11 @@ class Fortran::lower::CallInterfaceImpl {
976976
};
977977
if (obj.attrs.test(Attrs::Optional))
978978
addMLIRAttr(fir::getOptionalAttrName());
979-
if (obj.attrs.test(Attrs::Asynchronous))
980-
TODO(loc, "ASYNCHRONOUS in procedure interface");
979+
// Skipping obj.attrs.test(Attrs::Asynchronous), this does not impact the
980+
// way the argument is passed given flang implement asynch IO synchronously.
981+
// TODO: it would be safer to treat them as volatile because since Fortran
982+
// 2018 asynchronous can also be used for C defined asynchronous user
983+
// processes (see 18.10.4 Asynchronous communication).
981984
if (obj.attrs.test(Attrs::Contiguous))
982985
addMLIRAttr(fir::getContiguousAttrName());
983986
if (obj.attrs.test(Attrs::Value))

flang/lib/Lower/IO.cpp

Lines changed: 48 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,13 @@ static constexpr std::tuple<
9696
mkIOKey(BeginUnformattedInput), mkIOKey(BeginUnformattedOutput),
9797
mkIOKey(BeginWait), mkIOKey(BeginWaitAll),
9898
mkIOKey(CheckUnitNumberInRange64), mkIOKey(CheckUnitNumberInRange128),
99-
mkIOKey(EnableHandlers), mkIOKey(EndIoStatement), mkIOKey(GetIoLength),
100-
mkIOKey(GetIoMsg), mkIOKey(GetNewUnit), mkIOKey(GetSize),
101-
mkIOKey(InputAscii), mkIOKey(InputComplex32), mkIOKey(InputComplex64),
102-
mkIOKey(InputDerivedType), mkIOKey(InputDescriptor), mkIOKey(InputInteger),
103-
mkIOKey(InputLogical), mkIOKey(InputNamelist), mkIOKey(InputReal32),
104-
mkIOKey(InputReal64), mkIOKey(InquireCharacter), mkIOKey(InquireInteger64),
99+
mkIOKey(EnableHandlers), mkIOKey(EndIoStatement),
100+
mkIOKey(GetAsynchronousId), mkIOKey(GetIoLength), mkIOKey(GetIoMsg),
101+
mkIOKey(GetNewUnit), mkIOKey(GetSize), mkIOKey(InputAscii),
102+
mkIOKey(InputComplex32), mkIOKey(InputComplex64), mkIOKey(InputDerivedType),
103+
mkIOKey(InputDescriptor), mkIOKey(InputInteger), mkIOKey(InputLogical),
104+
mkIOKey(InputNamelist), mkIOKey(InputReal32), mkIOKey(InputReal64),
105+
mkIOKey(InquireCharacter), mkIOKey(InquireInteger64),
105106
mkIOKey(InquireLogical), mkIOKey(InquirePendingId), mkIOKey(OutputAscii),
106107
mkIOKey(OutputComplex32), mkIOKey(OutputComplex64),
107108
mkIOKey(OutputDerivedType), mkIOKey(OutputDescriptor),
@@ -1313,13 +1314,6 @@ mlir::Value genIOOption<Fortran::parser::IoControlSpec::Asynchronous>(
13131314
spec.v);
13141315
}
13151316

1316-
template <>
1317-
mlir::Value genIOOption<Fortran::parser::IdVariable>(
1318-
Fortran::lower::AbstractConverter &converter, mlir::Location loc,
1319-
mlir::Value cookie, const Fortran::parser::IdVariable &spec) {
1320-
TODO(loc, "asynchronous ID not implemented");
1321-
}
1322-
13231317
template <>
13241318
mlir::Value genIOOption<Fortran::parser::IoControlSpec::Pos>(
13251319
Fortran::lower::AbstractConverter &converter, mlir::Location loc,
@@ -1334,35 +1328,21 @@ mlir::Value genIOOption<Fortran::parser::IoControlSpec::Rec>(
13341328
return genIntIOOption<mkIOKey(SetRec)>(converter, loc, cookie, spec);
13351329
}
13361330

1337-
/// Generate runtime call to query the read size after an input statement if
1338-
/// the statement has SIZE control-spec.
1339-
template <typename A>
1340-
static void genIOReadSize(Fortran::lower::AbstractConverter &converter,
1341-
mlir::Location loc, mlir::Value cookie,
1342-
const A &specList, bool checkResult) {
1343-
// This call is not conditional on the current IO status (ok) because the size
1344-
// needs to be filled even if some error condition (end-of-file...) was met
1345-
// during the input statement (in which case the runtime may return zero for
1346-
// the size read).
1347-
for (const auto &spec : specList)
1348-
if (const auto *size =
1349-
std::get_if<Fortran::parser::IoControlSpec::Size>(&spec.u)) {
1350-
1351-
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
1352-
mlir::func::FuncOp ioFunc =
1353-
getIORuntimeFunc<mkIOKey(GetSize)>(loc, builder);
1354-
auto sizeValue =
1355-
builder.create<fir::CallOp>(loc, ioFunc, mlir::ValueRange{cookie})
1356-
.getResult(0);
1357-
Fortran::lower::StatementContext localStatementCtx;
1358-
fir::ExtendedValue var = converter.genExprAddr(
1359-
loc, Fortran::semantics::GetExpr(size->v), localStatementCtx);
1360-
mlir::Value varAddr = fir::getBase(var);
1361-
mlir::Type varType = fir::unwrapPassByRefType(varAddr.getType());
1362-
mlir::Value sizeCast = builder.createConvert(loc, varType, sizeValue);
1363-
builder.create<fir::StoreOp>(loc, sizeCast, varAddr);
1364-
break;
1365-
}
1331+
/// Generate runtime call to set some control variable.
1332+
/// Generates "VAR = IoRuntimeKey(cookie)".
1333+
template <typename IoRuntimeKey, typename VAR>
1334+
static void genIOGetVar(Fortran::lower::AbstractConverter &converter,
1335+
mlir::Location loc, mlir::Value cookie,
1336+
const VAR &parserVar) {
1337+
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
1338+
mlir::func::FuncOp ioFunc = getIORuntimeFunc<IoRuntimeKey>(loc, builder);
1339+
mlir::Value value =
1340+
builder.create<fir::CallOp>(loc, ioFunc, mlir::ValueRange{cookie})
1341+
.getResult(0);
1342+
Fortran::lower::StatementContext localStatementCtx;
1343+
fir::ExtendedValue var = converter.genExprAddr(
1344+
loc, Fortran::semantics::GetExpr(parserVar.v), localStatementCtx);
1345+
builder.createStoreWithConvert(loc, value, fir::getBase(var));
13661346
}
13671347

13681348
//===----------------------------------------------------------------------===//
@@ -1412,6 +1392,12 @@ static void threadSpecs(Fortran::lower::AbstractConverter &converter,
14121392
// there is an error.
14131393
return ok;
14141394
},
1395+
[&](const Fortran::parser::IdVariable &) -> mlir::Value {
1396+
// ID is queried after the transfer so that ASYNCHROUNOUS= has
1397+
// been processed and also to set it to zero if the transfer is
1398+
// already finished.
1399+
return ok;
1400+
},
14151401
[&](const auto &x) {
14161402
return genIOOption(converter, loc, cookie, x);
14171403
}},
@@ -1602,21 +1588,6 @@ maybeGetInternalIODescriptor<Fortran::parser::PrintStmt>(
16021588
return std::nullopt;
16031589
}
16041590

1605-
template <typename A>
1606-
static bool isDataTransferAsynchronous(mlir::Location loc, const A &stmt) {
1607-
if (auto *asynch =
1608-
getIOControl<Fortran::parser::IoControlSpec::Asynchronous>(stmt)) {
1609-
// FIXME: should contain a string of YES or NO
1610-
TODO(loc, "asynchronous transfers not implemented in runtime");
1611-
}
1612-
return false;
1613-
}
1614-
template <>
1615-
bool isDataTransferAsynchronous<Fortran::parser::PrintStmt>(
1616-
mlir::Location, const Fortran::parser::PrintStmt &) {
1617-
return false;
1618-
}
1619-
16201591
template <typename A>
16211592
static bool isDataTransferNamelist(const A &stmt) {
16221593
if (stmt.format)
@@ -2043,7 +2014,7 @@ template <bool isInput>
20432014
mlir::func::FuncOp
20442015
getBeginDataTransferFunc(mlir::Location loc, fir::FirOpBuilder &builder,
20452016
bool isFormatted, bool isListOrNml, bool isInternal,
2046-
bool isInternalWithDesc, bool isAsync) {
2017+
bool isInternalWithDesc) {
20472018
if constexpr (isInput) {
20482019
if (isFormatted || isListOrNml) {
20492020
if (isInternal) {
@@ -2098,7 +2069,6 @@ void genBeginDataTransferCallArgs(
20982069
Fortran::lower::AbstractConverter &converter, mlir::Location loc,
20992070
const A &stmt, mlir::FunctionType ioFuncTy, bool isFormatted,
21002071
bool isListOrNml, [[maybe_unused]] bool isInternal,
2101-
[[maybe_unused]] bool isAsync,
21022072
const std::optional<fir::ExtendedValue> &descRef, ConditionSpecInfo &csi,
21032073
Fortran::lower::StatementContext &stmtCtx) {
21042074
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
@@ -2146,8 +2116,6 @@ void genBeginDataTransferCallArgs(
21462116
ioArgs.push_back( // buffer length
21472117
getDefaultScratchLen(builder, loc, ioFuncTy.getInput(ioArgs.size())));
21482118
} else { // external IO - maybe explicit format; unit
2149-
if (isAsync)
2150-
TODO(loc, "asynchronous");
21512119
maybeGetFormatArgs();
21522120
ioArgs.push_back(getIOUnit(converter, loc, stmt,
21532121
ioFuncTy.getInput(ioArgs.size()), csi, stmtCtx,
@@ -2180,8 +2148,12 @@ genDataTransferStmt(Fortran::lower::AbstractConverter &converter,
21802148
isInternal ? maybeGetInternalIODescriptor(converter, loc, stmt, stmtCtx)
21812149
: std::nullopt;
21822150
const bool isInternalWithDesc = descRef.has_value();
2183-
const bool isAsync = isDataTransferAsynchronous(loc, stmt);
21842151
const bool isNml = isDataTransferNamelist(stmt);
2152+
// Flang runtime currently implement asynchronous IO synchronously, so
2153+
// asynchronous IO statements are lowered as regular IO statements
2154+
// (except that GetAsynchronousId may be called to set the ID variable
2155+
// and SetAsynchronous will be call to tell the runtime that this is supposed
2156+
// to be (or not) an asynchronous IO statements).
21852157

21862158
// Generate an EnableHandlers call and remaining specifier calls.
21872159
ConditionSpecInfo csi;
@@ -2192,13 +2164,13 @@ genDataTransferStmt(Fortran::lower::AbstractConverter &converter,
21922164
// Generate the begin data transfer function call.
21932165
mlir::func::FuncOp ioFunc = getBeginDataTransferFunc<isInput>(
21942166
loc, builder, isFormatted, isList || isNml, isInternal,
2195-
isInternalWithDesc, isAsync);
2167+
isInternalWithDesc);
21962168
llvm::SmallVector<mlir::Value> ioArgs;
21972169
genBeginDataTransferCallArgs<
21982170
hasIOCtrl, isInput ? Fortran::runtime::io::DefaultInputUnit
21992171
: Fortran::runtime::io::DefaultOutputUnit>(
22002172
ioArgs, converter, loc, stmt, ioFunc.getFunctionType(), isFormatted,
2201-
isList || isNml, isInternal, isAsync, descRef, csi, stmtCtx);
2173+
isList || isNml, isInternal, descRef, csi, stmtCtx);
22022174
mlir::Value cookie =
22032175
builder.create<fir::CallOp>(loc, ioFunc, ioArgs).getResult(0);
22042176

@@ -2238,8 +2210,18 @@ genDataTransferStmt(Fortran::lower::AbstractConverter &converter,
22382210

22392211
builder.restoreInsertionPoint(insertPt);
22402212
if constexpr (hasIOCtrl) {
2241-
genIOReadSize(converter, loc, cookie, stmt.controls,
2242-
csi.hasErrorConditionSpec());
2213+
for (const auto &spec : stmt.controls)
2214+
if (const auto *size =
2215+
std::get_if<Fortran::parser::IoControlSpec::Size>(&spec.u)) {
2216+
// This call is not conditional on the current IO status (ok) because
2217+
// the size needs to be filled even if some error condition
2218+
// (end-of-file...) was met during the input statement (in which case
2219+
// the runtime may return zero for the size read).
2220+
genIOGetVar<mkIOKey(GetSize)>(converter, loc, cookie, *size);
2221+
} else if (const auto *idVar =
2222+
std::get_if<Fortran::parser::IdVariable>(&spec.u)) {
2223+
genIOGetVar<mkIOKey(GetAsynchronousId)>(converter, loc, cookie, *idVar);
2224+
}
22432225
}
22442226
// Generate end statement call/s.
22452227
mlir::Value result = genEndIO(converter, loc, cookie, csi, stmtCtx);

flang/runtime/io-api.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,19 @@ void IONAME(GetIoMsg)(Cookie cookie, char *msg, std::size_t length) {
14011401
}
14021402
}
14031403

1404+
AsynchronousId IONAME(GetAsynchronousId)(Cookie cookie) {
1405+
IoStatementState &io{*cookie};
1406+
IoErrorHandler &handler{io.GetIoErrorHandler()};
1407+
if (auto *ext{io.get_if<ExternalIoStatementBase>()}) {
1408+
return ext->asynchronousID();
1409+
} else if (!io.get_if<NoopStatementState>() &&
1410+
!io.get_if<ErroneousIoStatementState>()) {
1411+
handler.Crash(
1412+
"GetAsynchronousId() called when not in an external I/O statement");
1413+
}
1414+
return 0;
1415+
}
1416+
14041417
bool IONAME(InquireCharacter)(Cookie cookie, InquiryKeywordHash inquiry,
14051418
char *result, std::size_t length) {
14061419
IoStatementState &io{*cookie};
@@ -1413,7 +1426,7 @@ bool IONAME(InquireLogical)(
14131426
return io.Inquire(inquiry, result);
14141427
}
14151428

1416-
bool IONAME(InquirePendingId)(Cookie cookie, std::int64_t id, bool &result) {
1429+
bool IONAME(InquirePendingId)(Cookie cookie, AsynchronousId id, bool &result) {
14171430
IoStatementState &io{*cookie};
14181431
return io.Inquire(HashInquiryKeyword("PENDING"), id, result);
14191432
}

flang/test/Lower/io-asynchronous.f90

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
! Test lowering of ASYNCHRONOUS variables and IO statements.
2+
! RUN: bbc -emit-hlfir -o - %s | FileCheck %s
3+
4+
module test_async
5+
contains
6+
subroutine test(x, iounit, idvar, pending)
7+
real, asynchronous :: x(10)
8+
integer :: idvar, iounit
9+
logical :: pending
10+
! CHECK-LABEL: func.func @_QMtest_asyncPtest(
11+
! CHECK: %[[VAL_4:.*]]:2 = hlfir.declare %{{.*}}idvar
12+
! CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %{{.*}}iounit
13+
! CHECK: %[[VAL_6:.*]]:2 = hlfir.declare %{{.*}}pending
14+
! CHECK: hlfir.declare %{{.*}}fir.var_attrs<asynchronous>{{.*}}x
15+
16+
open(unit=iounit, asynchronous='yes')
17+
! CHECK: %[[VAL_10:.*]] = fir.load %[[VAL_5]]#0 : !fir.ref<i32>
18+
! CHECK: %[[VAL_14:.*]] = fir.call @_FortranAioBeginOpenUnit(%[[VAL_10]]
19+
! CHECK: %[[VAL_20:.*]] = fir.call @_FortranAioSetAsynchronous(%[[VAL_14]]
20+
! CHECK: %[[VAL_21:.*]] = fir.call @_FortranAioEndIoStatement(%[[VAL_14]])
21+
22+
write(unit=iounit,id=idvar, asynchronous='yes', fmt=*) x
23+
! CHECK: %[[VAL_22:.*]] = fir.load %[[VAL_5]]#0 : !fir.ref<i32>
24+
! CHECK: %[[VAL_26:.*]] = fir.call @_FortranAioBeginExternalListOutput(%[[VAL_22]],
25+
! CHECK: %[[VAL_32:.*]] = fir.call @_FortranAioSetAsynchronous(%[[VAL_26]],
26+
! CHECK: %[[VAL_36:.*]] = fir.call @_FortranAioOutputDescriptor(%[[VAL_26]],
27+
! CHECK: %[[VAL_37:.*]] = fir.call @_FortranAioGetAsynchronousId(%[[VAL_26]])
28+
! CHECK: fir.store %[[VAL_37]] to %[[VAL_4]]#1 : !fir.ref<i32>
29+
! CHECK: %[[VAL_38:.*]] = fir.call @_FortranAioEndIoStatement(%[[VAL_26]])
30+
31+
inquire(unit=iounit, id=idvar, pending=pending)
32+
! CHECK: %[[VAL_39:.*]] = fir.load %[[VAL_5]]#0 : !fir.ref<i32>
33+
! CHECK: %[[VAL_43:.*]] = fir.call @_FortranAioBeginInquireUnit(%[[VAL_39]],
34+
! CHECK: %[[VAL_44:.*]] = fir.load %[[VAL_4]]#0 : !fir.ref<i32>
35+
! CHECK: %[[VAL_46:.*]] = fir.convert %[[VAL_6]]#1 : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i1>
36+
! CHECK: %[[VAL_47:.*]] = fir.call @_FortranAioInquirePendingId(%[[VAL_43]], %[[VAL_44]], %[[VAL_46]])
37+
! CHECK: %[[VAL_48:.*]] = fir.convert %[[VAL_6]]#1 : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i1>
38+
! CHECK: %[[VAL_49:.*]] = fir.load %[[VAL_48]] : !fir.ref<i1>
39+
! CHECK: %[[VAL_50:.*]] = fir.convert %[[VAL_49]] : (i1) -> !fir.logical<4>
40+
! CHECK: fir.store %[[VAL_50]] to %[[VAL_6]]#1 : !fir.ref<!fir.logical<4>>
41+
! CHECK: %[[VAL_51:.*]] = fir.call @_FortranAioEndIoStatement(%[[VAL_43]])
42+
43+
wait(unit=iounit, id=idvar)
44+
! CHECK: %[[VAL_52:.*]] = fir.load %[[VAL_5]]#0 : !fir.ref<i32>
45+
! CHECK: %[[VAL_53:.*]] = fir.load %[[VAL_4]]#0 : !fir.ref<i32>
46+
! CHECK: %[[VAL_57:.*]] = fir.call @_FortranAioBeginWait(%[[VAL_52]], %[[VAL_53]]
47+
! CHECK: %[[VAL_58:.*]] = fir.call @_FortranAioEndIoStatement(%[[VAL_57]])
48+
end subroutine
49+
end module
50+
51+
use test_async
52+
real :: x(10) = 1.
53+
integer :: iounit = 100
54+
integer :: idvar
55+
logical :: pending = .true.
56+
call test(x, iounit, idvar, pending)
57+
print *, idvar, pending
58+
end

flang/test/Lower/io-statement-1.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ subroutine inquire_test(ch, i, b)
109109
! PENDING with ID
110110
! CHECK-DAG: %[[chip:.*]] = fir.call {{.*}}BeginInquireUnit
111111
! CHECK-DAG: fir.call @_QPid_func
112-
! CHECK: call @_FortranAioInquirePendingId(%[[chip]], %{{.*}}, %{{.*}}) {{.*}}: (!fir.ref<i8>, i64, !fir.ref<i1>) -> i1
112+
! CHECK: call @_FortranAioInquirePendingId(%[[chip]], %{{.*}}, %{{.*}}) {{.*}}: (!fir.ref<i8>, i32, !fir.ref<i1>) -> i1
113113
! CHECK: call {{.*}}EndIoStatement
114114
inquire(91, id=id_func(), pending=b)
115115
end subroutine inquire_test

0 commit comments

Comments
 (0)