mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-17 16:46:39 +00:00

Mostly mechanical changes in preparation of extracting the Flang-RT "subproject" in #110217. This PR intends to only move pre-existing files to the new folder structure, with no behavioral change. Common files (headers, testing, cmake) shared by Flang-RT and Flang remain in `flang/`. Some cosmetic changes and files paths were necessary: * Relative paths to the new path for the source files and `add_subdirectory`. * Add the new location's include directory to `include_directories` * The unittest/Evaluate directory has unitests for flang-rt and Flang. A new `CMakeLists.txt` was introduced for the flang-rt tests. * Change the `#include` paths relative to the include directive * clang-format on the `#include` directives * Since the paths are part if the copyright header and include guards, a script was used to canonicalize those * `test/Runtime` and runtime tests in `test/Driver` are moved, but the lit.cfg.py mechanism to execute the will only be added in #110217.
356 lines
12 KiB
C++
356 lines
12 KiB
C++
//===-- lib/runtime/external-unit.cpp ---------------------------*- C++ -*-===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Implemenation of ExternalFileUnit for RT_USE_PSEUDO_FILE_UNIT=0.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "unit-map.h"
|
|
#include "unit.h"
|
|
#include "flang-rt/runtime/io-error.h"
|
|
#include "flang-rt/runtime/lock.h"
|
|
#include "flang-rt/runtime/tools.h"
|
|
|
|
// NOTE: the header files above may define OpenMP declare target
|
|
// variables, so they have to be included unconditionally
|
|
// so that the offload entries are consistent between host and device.
|
|
#if !defined(RT_USE_PSEUDO_FILE_UNIT)
|
|
|
|
#include <cstdio>
|
|
#include <limits>
|
|
|
|
namespace Fortran::runtime::io {
|
|
|
|
// The per-unit data structures are created on demand so that Fortran I/O
|
|
// should work without a Fortran main program.
|
|
static Lock unitMapLock;
|
|
static Lock createOpenLock;
|
|
static UnitMap *unitMap{nullptr};
|
|
|
|
void FlushOutputOnCrash(const Terminator &terminator) {
|
|
if (!defaultOutput && !errorOutput) {
|
|
return;
|
|
}
|
|
IoErrorHandler handler{terminator};
|
|
handler.HasIoStat(); // prevent nested crash if flush has error
|
|
CriticalSection critical{unitMapLock};
|
|
if (defaultOutput) {
|
|
defaultOutput->FlushOutput(handler);
|
|
}
|
|
if (errorOutput) {
|
|
errorOutput->FlushOutput(handler);
|
|
}
|
|
}
|
|
|
|
ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
|
|
return GetUnitMap().LookUp(unit);
|
|
}
|
|
|
|
ExternalFileUnit *ExternalFileUnit::LookUpOrCreate(
|
|
int unit, const Terminator &terminator, bool &wasExtant) {
|
|
return GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant);
|
|
}
|
|
|
|
ExternalFileUnit *ExternalFileUnit::LookUpOrCreateAnonymous(int unit,
|
|
Direction dir, Fortran::common::optional<bool> isUnformatted,
|
|
IoErrorHandler &handler) {
|
|
// Make sure that the returned anonymous unit has been opened,
|
|
// not just created in the unitMap.
|
|
CriticalSection critical{createOpenLock};
|
|
bool exists{false};
|
|
ExternalFileUnit *result{GetUnitMap().LookUpOrCreate(unit, handler, exists)};
|
|
if (result && !exists) {
|
|
common::optional<Action> action;
|
|
if (dir == Direction::Output) {
|
|
action = Action::ReadWrite;
|
|
}
|
|
if (!result->OpenAnonymousUnit(
|
|
dir == Direction::Input ? OpenStatus::Unknown : OpenStatus::Replace,
|
|
action, Position::Rewind, Convert::Unknown, handler)) {
|
|
// fort.N isn't a writable file
|
|
if (ExternalFileUnit * closed{LookUpForClose(result->unitNumber())}) {
|
|
closed->DestroyClosed();
|
|
}
|
|
result = nullptr;
|
|
} else {
|
|
result->isUnformatted = isUnformatted;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ExternalFileUnit *ExternalFileUnit::LookUp(
|
|
const char *path, std::size_t pathLen) {
|
|
return GetUnitMap().LookUp(path, pathLen);
|
|
}
|
|
|
|
ExternalFileUnit &ExternalFileUnit::CreateNew(
|
|
int unit, const Terminator &terminator) {
|
|
bool wasExtant{false};
|
|
ExternalFileUnit *result{
|
|
GetUnitMap().LookUpOrCreate(unit, terminator, wasExtant)};
|
|
RUNTIME_CHECK(terminator, result && !wasExtant);
|
|
return *result;
|
|
}
|
|
|
|
ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
|
|
return GetUnitMap().LookUpForClose(unit);
|
|
}
|
|
|
|
ExternalFileUnit &ExternalFileUnit::NewUnit(
|
|
const Terminator &terminator, bool forChildIo) {
|
|
ExternalFileUnit &unit{GetUnitMap().NewUnit(terminator)};
|
|
unit.createdForInternalChildIo_ = forChildIo;
|
|
return unit;
|
|
}
|
|
|
|
bool ExternalFileUnit::OpenUnit(Fortran::common::optional<OpenStatus> status,
|
|
Fortran::common::optional<Action> action, Position position,
|
|
OwningPtr<char> &&newPath, std::size_t newPathLength, Convert convert,
|
|
IoErrorHandler &handler) {
|
|
if (convert == Convert::Unknown) {
|
|
convert = executionEnvironment.conversion;
|
|
}
|
|
swapEndianness_ = convert == Convert::Swap ||
|
|
(convert == Convert::LittleEndian && !isHostLittleEndian) ||
|
|
(convert == Convert::BigEndian && isHostLittleEndian);
|
|
bool impliedClose{false};
|
|
if (IsConnected()) {
|
|
bool isSamePath{newPath.get() && path() && pathLength() == newPathLength &&
|
|
std::memcmp(path(), newPath.get(), newPathLength) == 0};
|
|
if (status && *status != OpenStatus::Old && isSamePath) {
|
|
handler.SignalError("OPEN statement for connected unit may not have "
|
|
"explicit STATUS= other than 'OLD'");
|
|
return impliedClose;
|
|
}
|
|
if (!newPath.get() || isSamePath) {
|
|
// OPEN of existing unit, STATUS='OLD' or unspecified, not new FILE=
|
|
newPath.reset();
|
|
return impliedClose;
|
|
}
|
|
// Otherwise, OPEN on open unit with new FILE= implies CLOSE
|
|
DoImpliedEndfile(handler);
|
|
FlushOutput(handler);
|
|
TruncateFrame(0, handler);
|
|
Close(CloseStatus::Keep, handler);
|
|
impliedClose = true;
|
|
}
|
|
if (newPath.get() && newPathLength > 0) {
|
|
if (const auto *already{
|
|
GetUnitMap().LookUp(newPath.get(), newPathLength)}) {
|
|
handler.SignalError(IostatOpenAlreadyConnected,
|
|
"OPEN(UNIT=%d,FILE='%.*s'): file is already connected to unit %d",
|
|
unitNumber_, static_cast<int>(newPathLength), newPath.get(),
|
|
already->unitNumber_);
|
|
return impliedClose;
|
|
}
|
|
}
|
|
set_path(std::move(newPath), newPathLength);
|
|
Open(status.value_or(OpenStatus::Unknown), action, position, handler);
|
|
if (handler.InError()) {
|
|
return impliedClose;
|
|
}
|
|
auto totalBytes{knownSize()};
|
|
if (access == Access::Direct) {
|
|
if (!openRecl) {
|
|
handler.SignalError(IostatOpenBadRecl,
|
|
"OPEN(UNIT=%d,ACCESS='DIRECT'): record length is not known",
|
|
unitNumber());
|
|
} else if (*openRecl <= 0) {
|
|
handler.SignalError(IostatOpenBadRecl,
|
|
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is invalid",
|
|
unitNumber(), static_cast<std::intmax_t>(*openRecl));
|
|
} else if (totalBytes && (*totalBytes % *openRecl != 0)) {
|
|
handler.SignalError(IostatOpenBadRecl,
|
|
"OPEN(UNIT=%d,ACCESS='DIRECT',RECL=%jd): record length is not an "
|
|
"even divisor of the file size %jd",
|
|
unitNumber(), static_cast<std::intmax_t>(*openRecl),
|
|
static_cast<std::intmax_t>(*totalBytes));
|
|
}
|
|
recordLength = openRecl;
|
|
}
|
|
endfileRecordNumber.reset();
|
|
currentRecordNumber = 1;
|
|
if (totalBytes && access == Access::Direct && openRecl.value_or(0) > 0) {
|
|
endfileRecordNumber = 1 + (*totalBytes / *openRecl);
|
|
}
|
|
if (position == Position::Append) {
|
|
if (totalBytes) {
|
|
frameOffsetInFile_ = *totalBytes;
|
|
}
|
|
if (access != Access::Stream) {
|
|
if (!endfileRecordNumber) {
|
|
// Fake it so that we can backspace relative from the end
|
|
endfileRecordNumber = std::numeric_limits<std::int64_t>::max() - 2;
|
|
}
|
|
currentRecordNumber = *endfileRecordNumber;
|
|
}
|
|
}
|
|
return impliedClose;
|
|
}
|
|
|
|
bool ExternalFileUnit::OpenAnonymousUnit(
|
|
Fortran::common::optional<OpenStatus> status,
|
|
Fortran::common::optional<Action> action, Position position,
|
|
Convert convert, IoErrorHandler &handler) {
|
|
// I/O to an unconnected unit reads/creates a local file, e.g. fort.7
|
|
std::size_t pathMaxLen{32};
|
|
auto path{SizedNew<char>{handler}(pathMaxLen)};
|
|
std::snprintf(path.get(), pathMaxLen, "fort.%d", unitNumber_);
|
|
OpenUnit(status, action, position, std::move(path), std::strlen(path.get()),
|
|
convert, handler);
|
|
return IsConnected();
|
|
}
|
|
|
|
void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
|
|
DoImpliedEndfile(handler);
|
|
FlushOutput(handler);
|
|
Close(status, handler);
|
|
}
|
|
|
|
void ExternalFileUnit::DestroyClosed() {
|
|
GetUnitMap().DestroyClosed(*this); // destroys *this
|
|
}
|
|
|
|
Iostat ExternalFileUnit::SetDirection(Direction direction) {
|
|
if (direction == Direction::Input) {
|
|
if (mayRead()) {
|
|
direction_ = Direction::Input;
|
|
return IostatOk;
|
|
} else {
|
|
return IostatReadFromWriteOnly;
|
|
}
|
|
} else {
|
|
if (mayWrite()) {
|
|
if (direction_ == Direction::Input) {
|
|
// Don't retain any input data from previous record, like a
|
|
// variable-length unformatted record footer, in the frame,
|
|
// since we're going start writing frames.
|
|
frameOffsetInFile_ += recordOffsetInFrame_;
|
|
recordOffsetInFrame_ = 0;
|
|
}
|
|
direction_ = Direction::Output;
|
|
return IostatOk;
|
|
} else {
|
|
return IostatWriteToReadOnly;
|
|
}
|
|
}
|
|
}
|
|
|
|
UnitMap &ExternalFileUnit::CreateUnitMap() {
|
|
Terminator terminator{__FILE__, __LINE__};
|
|
IoErrorHandler handler{terminator};
|
|
UnitMap &newUnitMap{*New<UnitMap>{terminator}().release()};
|
|
|
|
bool wasExtant{false};
|
|
ExternalFileUnit &out{*newUnitMap.LookUpOrCreate(
|
|
FORTRAN_DEFAULT_OUTPUT_UNIT, terminator, wasExtant)};
|
|
RUNTIME_CHECK(terminator, !wasExtant);
|
|
out.Predefine(1);
|
|
handler.SignalError(out.SetDirection(Direction::Output));
|
|
out.isUnformatted = false;
|
|
defaultOutput = &out;
|
|
|
|
ExternalFileUnit &in{*newUnitMap.LookUpOrCreate(
|
|
FORTRAN_DEFAULT_INPUT_UNIT, terminator, wasExtant)};
|
|
RUNTIME_CHECK(terminator, !wasExtant);
|
|
in.Predefine(0);
|
|
handler.SignalError(in.SetDirection(Direction::Input));
|
|
in.isUnformatted = false;
|
|
defaultInput = ∈
|
|
|
|
ExternalFileUnit &error{
|
|
*newUnitMap.LookUpOrCreate(FORTRAN_ERROR_UNIT, terminator, wasExtant)};
|
|
RUNTIME_CHECK(terminator, !wasExtant);
|
|
error.Predefine(2);
|
|
handler.SignalError(error.SetDirection(Direction::Output));
|
|
error.isUnformatted = false;
|
|
errorOutput = &error;
|
|
|
|
return newUnitMap;
|
|
}
|
|
|
|
// A back-up atexit() handler for programs that don't terminate with a main
|
|
// program END or a STOP statement or other Fortran-initiated program shutdown,
|
|
// such as programs with a C main() that terminate normally. It flushes all
|
|
// external I/O units. It is registered once the first time that any external
|
|
// I/O is attempted.
|
|
static void CloseAllExternalUnits() {
|
|
IoErrorHandler handler{"Fortran program termination"};
|
|
ExternalFileUnit::CloseAll(handler);
|
|
}
|
|
|
|
UnitMap &ExternalFileUnit::GetUnitMap() {
|
|
if (unitMap) {
|
|
return *unitMap;
|
|
}
|
|
{
|
|
CriticalSection critical{unitMapLock};
|
|
if (unitMap) {
|
|
return *unitMap;
|
|
}
|
|
unitMap = &CreateUnitMap();
|
|
}
|
|
std::atexit(CloseAllExternalUnits);
|
|
return *unitMap;
|
|
}
|
|
|
|
void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {
|
|
CriticalSection critical{unitMapLock};
|
|
if (unitMap) {
|
|
unitMap->CloseAll(handler);
|
|
FreeMemoryAndNullify(unitMap);
|
|
}
|
|
defaultOutput = nullptr;
|
|
defaultInput = nullptr;
|
|
errorOutput = nullptr;
|
|
}
|
|
|
|
void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
|
|
CriticalSection critical{unitMapLock};
|
|
if (unitMap) {
|
|
unitMap->FlushAll(handler);
|
|
}
|
|
}
|
|
|
|
int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) {
|
|
if (!mayAsynchronous()) {
|
|
handler.SignalError(IostatBadAsynchronous);
|
|
return -1;
|
|
} else {
|
|
for (int j{0}; 64 * j < maxAsyncIds; ++j) {
|
|
if (auto least{asyncIdAvailable_[j].LeastElement()}) {
|
|
asyncIdAvailable_[j].reset(*least);
|
|
return 64 * j + static_cast<int>(*least);
|
|
}
|
|
}
|
|
handler.SignalError(IostatTooManyAsyncOps);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bool ExternalFileUnit::Wait(int id) {
|
|
if (static_cast<std::size_t>(id) >= maxAsyncIds ||
|
|
asyncIdAvailable_[id / 64].test(id % 64)) {
|
|
return false;
|
|
} else {
|
|
if (id == 0) { // means "all IDs"
|
|
for (int j{0}; 64 * j < maxAsyncIds; ++j) {
|
|
asyncIdAvailable_[j].set();
|
|
}
|
|
asyncIdAvailable_[0].reset(0);
|
|
} else {
|
|
asyncIdAvailable_[id / 64].set(id % 64);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
} // namespace Fortran::runtime::io
|
|
#endif // !defined(RT_USE_PSEUDO_FILE_UNIT)
|