From fae12a08bdb72b23781e670308bdd866099d14c8 Mon Sep 17 00:00:00 2001 From: peter klausler Date: Thu, 23 Jan 2020 16:10:00 -0800 Subject: [PATCH] [flang] Basic file operation wrapper Asynchronous interfaces and locking Original-commit: flang-compiler/f18@3ba77a0c2035644485737a025cc8912485b56225 Reviewed-on: https://github.com/flang-compiler/f18/pull/949 --- flang/runtime/CMakeLists.txt | 2 + flang/runtime/file.cc | 326 +++++++++++++++++++++++++++++++++++++++++++ flang/runtime/file.h | 77 ++++++++++ flang/runtime/io-api.h | 1 + flang/runtime/io-error.cc | 35 ++--- flang/runtime/io-error.h | 5 +- flang/runtime/lock.h | 47 +++++++ flang/runtime/memory.h | 9 +- flang/runtime/tools.cc | 51 +++++++ flang/runtime/tools.h | 25 ++++ 10 files changed, 550 insertions(+), 28 deletions(-) create mode 100644 flang/runtime/file.cc create mode 100644 flang/runtime/file.h create mode 100644 flang/runtime/lock.h create mode 100644 flang/runtime/tools.cc create mode 100644 flang/runtime/tools.h diff --git a/flang/runtime/CMakeLists.txt b/flang/runtime/CMakeLists.txt index 3253f4b..ce42973 100644 --- a/flang/runtime/CMakeLists.txt +++ b/flang/runtime/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(FortranRuntime ISO_Fortran_binding.cc derived-type.cc descriptor.cc + file.cc format.cc io-api.cc io-error.cc @@ -18,6 +19,7 @@ add_library(FortranRuntime memory.cc stop.cc terminator.cc + tools.cc transformational.cc type-code.cc ) diff --git a/flang/runtime/file.cc b/flang/runtime/file.cc new file mode 100644 index 0000000..c99db09 --- /dev/null +++ b/flang/runtime/file.cc @@ -0,0 +1,326 @@ +//===-- runtime/file.cc -----------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "file.h" +#include "magic-numbers.h" +#include "memory.h" +#include "tools.h" +#include +#include +#include +#include +#include + +namespace Fortran::runtime::io { + +void OpenFile::Open(const char *path, std::size_t pathLength, + const char *status, std::size_t statusLength, const char *action, + std::size_t actionLength, IoErrorHandler &handler) { + CriticalSection criticalSection{lock_}; + RUNTIME_CHECK(handler, fd_ < 0); + int flags{0}; + static const char *actions[]{"READ", "WRITE", "READWRITE", nullptr}; + switch (IdentifyValue(action, actionLength, actions)) { + case 0: flags = O_RDONLY; break; + case 1: flags = O_WRONLY; break; + case 2: flags = O_RDWR; break; + default: + handler.Crash( + "Invalid ACTION='%.*s'", action, static_cast(actionLength)); + } + if (!status) { + status = "UNKNOWN", statusLength = 7; + } + static const char *statuses[]{ + "OLD", "NEW", "SCRATCH", "REPLACE", "UNKNOWN", nullptr}; + switch (IdentifyValue(status, statusLength, statuses)) { + case 0: // STATUS='OLD' + if (!path && fd_ >= 0) { + // TODO: Update OpenFile in situ; can ACTION be changed? + return; + } + break; + case 1: // STATUS='NEW' + flags |= O_CREAT | O_EXCL; + break; + case 2: // STATUS='SCRATCH' + if (path_.get()) { + handler.Crash("FILE= must not appear with STATUS='SCRATCH'"); + path_.reset(); + } + { + char path[]{"/tmp/Fortran-Scratch-XXXXXX"}; + fd_ = ::mkstemp(path); + if (fd_ < 0) { + handler.SignalErrno(); + } + ::unlink(path); + } + return; + case 3: // STATUS='REPLACE' + flags |= O_CREAT | O_TRUNC; + break; + case 4: // STATUS='UNKNOWN' + if (fd_ >= 0) { + return; + } + flags |= O_CREAT; + break; + default: + handler.Crash( + "Invalid STATUS='%.*s'", status, static_cast(statusLength)); + } + // If we reach this point, we're opening a new file + if (fd_ >= 0) { + if (::close(fd_) != 0) { + handler.SignalErrno(); + } + } + path_ = SaveDefaultCharacter(path, pathLength, handler); + if (!path_.get()) { + handler.Crash( + "FILE= is required unless STATUS='OLD' and unit is connected"); + } + fd_ = ::open(path_.get(), flags, 0600); + if (fd_ < 0) { + handler.SignalErrno(); + } + pending_.reset(); + knownSize_.reset(); +} + +void OpenFile::Close( + const char *status, std::size_t statusLength, IoErrorHandler &handler) { + CriticalSection criticalSection{lock_}; + CheckOpen(handler); + pending_.reset(); + knownSize_.reset(); + static const char *statuses[]{"KEEP", "DELETE", nullptr}; + switch (IdentifyValue(status, statusLength, statuses)) { + case 0: break; + case 1: + if (path_.get()) { + ::unlink(path_.get()); + } + break; + default: + if (status) { + handler.Crash( + "Invalid STATUS='%.*s'", status, static_cast(statusLength)); + } + } + path_.reset(); + if (fd_ >= 0) { + if (::close(fd_) != 0) { + handler.SignalErrno(); + } + fd_ = -1; + } +} + +std::size_t OpenFile::Read(Offset at, char *buffer, std::size_t minBytes, + std::size_t maxBytes, IoErrorHandler &handler) { + if (maxBytes == 0) { + return 0; + } + CriticalSection criticalSection{lock_}; + CheckOpen(handler); + if (!Seek(at, handler)) { + return 0; + } + if (maxBytes < minBytes) { + minBytes = maxBytes; + } + std::size_t got{0}; + while (got < minBytes) { + auto chunk{::read(fd_, buffer + got, maxBytes - got)}; + if (chunk == 0) { + handler.SignalEnd(); + break; + } + if (chunk < 0) { + auto err{errno}; + if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { + handler.SignalError(err); + break; + } + } else { + position_ += chunk; + got += chunk; + } + } + return got; +} + +std::size_t OpenFile::Write( + Offset at, const char *buffer, std::size_t bytes, IoErrorHandler &handler) { + if (bytes == 0) { + return 0; + } + CriticalSection criticalSection{lock_}; + CheckOpen(handler); + if (!Seek(at, handler)) { + return 0; + } + std::size_t put{0}; + while (put < bytes) { + auto chunk{::write(fd_, buffer + put, bytes - put)}; + if (chunk >= 0) { + position_ += chunk; + put += chunk; + } else { + auto err{errno}; + if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { + handler.SignalError(err); + break; + } + } + } + if (knownSize_ && position_ > *knownSize_) { + knownSize_ = position_; + } + return put; +} + +void OpenFile::Truncate(Offset at, IoErrorHandler &handler) { + CriticalSection criticalSection{lock_}; + CheckOpen(handler); + if (!knownSize_ || *knownSize_ != at) { + if (::ftruncate(fd_, at) != 0) { + handler.SignalErrno(); + } + knownSize_ = at; + } +} + +// The operation is performed immediately; the results are saved +// to be claimed by a later WAIT statement. +// TODO: True asynchronicity +int OpenFile::ReadAsynchronously( + Offset at, char *buffer, std::size_t bytes, IoErrorHandler &handler) { + CriticalSection criticalSection{lock_}; + CheckOpen(handler); + int iostat{0}; + for (std::size_t got{0}; got < bytes;) { +#if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L + auto chunk{::pread(fd_, buffer + got, bytes - got, at)}; +#else + auto chunk{RawSeek(at) ? ::read(fd_, buffer + got, bytes - got) : -1}; +#endif + if (chunk == 0) { + iostat = FORTRAN_RUNTIME_IOSTAT_END; + break; + } + if (chunk < 0) { + auto err{errno}; + if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { + iostat = err; + break; + } + } else { + at += chunk; + got += chunk; + } + } + return PendingResult(handler, iostat); +} + +// TODO: True asynchronicity +int OpenFile::WriteAsynchronously( + Offset at, const char *buffer, std::size_t bytes, IoErrorHandler &handler) { + CriticalSection criticalSection{lock_}; + CheckOpen(handler); + int iostat{0}; + for (std::size_t put{0}; put < bytes;) { +#if _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200809L + auto chunk{::pwrite(fd_, buffer + put, bytes - put, at)}; +#else + auto chunk{RawSeek(at) ? ::write(fd_, buffer + put, bytes - put) : -1}; +#endif + if (chunk >= 0) { + at += chunk; + put += chunk; + } else { + auto err{errno}; + if (err != EAGAIN && err != EWOULDBLOCK && err != EINTR) { + iostat = err; + break; + } + } + } + return PendingResult(handler, iostat); +} + +void OpenFile::Wait(int id, IoErrorHandler &handler) { + std::optional ioStat; + { + CriticalSection criticalSection{lock_}; + Pending *prev{nullptr}; + for (Pending *p{pending_.get()}; p; p = (prev = p)->next.get()) { + if (p->id == id) { + ioStat = p->ioStat; + if (prev) { + prev->next.reset(p->next.release()); + } else { + pending_.reset(p->next.release()); + } + break; + } + } + } + if (ioStat) { + handler.SignalError(*ioStat); + } +} + +void OpenFile::WaitAll(IoErrorHandler &handler) { + while (true) { + int ioStat; + { + CriticalSection criticalSection{lock_}; + if (pending_) { + ioStat = pending_->ioStat; + pending_.reset(pending_->next.release()); + } else { + return; + } + } + handler.SignalError(ioStat); + } +} + +void OpenFile::CheckOpen(Terminator &terminator) { + RUNTIME_CHECK(terminator, fd_ >= 0); +} + +bool OpenFile::Seek(Offset at, IoErrorHandler &handler) { + if (at == position_) { + return true; + } else if (RawSeek(at)) { + position_ = at; + return true; + } else { + handler.SignalErrno(); + return false; + } +} + +bool OpenFile::RawSeek(Offset at) { +#ifdef _LARGEFILE64_SOURCE + return ::lseek64(fd_, at, SEEK_SET) == 0; +#else + return ::lseek(fd_, at, SEEK_SET) == 0; +#endif +} + +int OpenFile::PendingResult(Terminator &terminator, int iostat) { + int id{nextId_++}; + pending_.reset(&New{}(terminator, id, iostat, std::move(pending_))); + return id; +} +} diff --git a/flang/runtime/file.h b/flang/runtime/file.h new file mode 100644 index 0000000..1c0a10d --- /dev/null +++ b/flang/runtime/file.h @@ -0,0 +1,77 @@ +//===-- runtime/file.h ------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +// Raw system I/O wrappers + +#ifndef FORTRAN_RUNTIME_FILE_H_ +#define FORTRAN_RUNTIME_FILE_H_ + +#include "io-error.h" +#include "lock.h" +#include "memory.h" +#include "terminator.h" +#include +#include + +namespace Fortran::runtime::io { + +class OpenFile { +public: + using Offset = std::uint64_t; + + Offset position() const { return position_; } + + void Open(const char *path, std::size_t pathLength, const char *status, + std::size_t statusLength, const char *action, std::size_t actionLength, + IoErrorHandler &); + void Close(const char *action, std::size_t actionLength, IoErrorHandler &); + + // Reads data into memory; returns amount acquired. Synchronous. + // Partial reads (less than minBytes) signify end-of-file. If the + // buffer is larger than minBytes, and extra returned data will be + // preserved for future consumption, set maxBytes larger than minBytes + // to reduce system calls This routine handles EAGAIN/EWOULDBLOCK and EINTR. + std::size_t Read(Offset, char *, std::size_t minBytes, std::size_t maxBytes, + IoErrorHandler &); + + // Writes data. Synchronous. Partial writes indicate program-handled + // error conditions. + std::size_t Write(Offset, const char *, std::size_t, IoErrorHandler &); + + // Truncates the file + void Truncate(Offset, IoErrorHandler &); + + // Asynchronous transfers + int ReadAsynchronously(Offset, char *, std::size_t, IoErrorHandler &); + int WriteAsynchronously(Offset, const char *, std::size_t, IoErrorHandler &); + void Wait(int id, IoErrorHandler &); + void WaitAll(IoErrorHandler &); + +private: + struct Pending { + int id; + int ioStat{0}; + OwningPtr next; + }; + + // lock_ must be held for these + void CheckOpen(Terminator &); + bool Seek(Offset, IoErrorHandler &); + bool RawSeek(Offset); + int PendingResult(Terminator &, int); + + Lock lock_; + int fd_{-1}; + OwningPtr path_; + Offset position_{0}; + std::optional knownSize_; + int nextId_; + OwningPtr pending_; +}; +} +#endif // FORTRAN_RUNTIME_FILE_H_ diff --git a/flang/runtime/io-api.h b/flang/runtime/io-api.h index 20f0f21..1c1f81e 100644 --- a/flang/runtime/io-api.h +++ b/flang/runtime/io-api.h @@ -127,6 +127,7 @@ AsynchronousId IONAME(BeginAsynchronousOutput)(ExternalUnit, std::int64_t REC, AsynchronousId IONAME(BeginAsynchronousInput)(ExternalUnit, std::int64_t REC, char *, std::size_t, const char *sourceFile = nullptr, int sourceLine = 0); Cookie IONAME(BeginWait)(ExternalUnit, AsynchronousId); +Cookie IONAME(BeginWaitAll)(ExternalUnit); // Other I/O statements Cookie IONAME(BeginClose)( diff --git a/flang/runtime/io-error.cc b/flang/runtime/io-error.cc index 74dcef8..ccf143a 100644 --- a/flang/runtime/io-error.cc +++ b/flang/runtime/io-error.cc @@ -17,16 +17,18 @@ namespace Fortran::runtime::io { void IoErrorHandler::Begin(const char *sourceFileName, int sourceLine) { flags_ = 0; ioStat_ = 0; - hitEnd_ = false; - hitEor_ = false; SetLocation(sourceFileName, sourceLine); } void IoErrorHandler::SignalError(int iostatOrErrno) { - if (iostatOrErrno != 0) { + if (iostatOrErrno == FORTRAN_RUNTIME_IOSTAT_END) { + SignalEnd(); + } else if (iostatOrErrno == FORTRAN_RUNTIME_IOSTAT_EOR) { + SignalEor(); + } else if (iostatOrErrno != 0) { if (flags_ & hasIoStat) { - if (!ioStat_) { - ioStat_ = iostatOrErrno; + if (ioStat_ <= 0) { + ioStat_ = iostatOrErrno; // priority over END=/EOR= } } else if (iostatOrErrno == FORTRAN_RUNTIME_IOSTAT_INQUIRE_INTERNAL_UNIT) { Crash("INQUIRE on internal unit"); @@ -36,9 +38,13 @@ void IoErrorHandler::SignalError(int iostatOrErrno) { } } +void IoErrorHandler::SignalErrno() { SignalError(errno); } + void IoErrorHandler::SignalEnd() { if (flags_ & hasEnd) { - hitEnd_ = true; + if (!ioStat_ || ioStat_ < FORTRAN_RUNTIME_IOSTAT_END) { + ioStat_ = FORTRAN_RUNTIME_IOSTAT_END; + } } else { Crash("End of file"); } @@ -46,22 +52,11 @@ void IoErrorHandler::SignalEnd() { void IoErrorHandler::SignalEor() { if (flags_ & hasEor) { - hitEor_ = true; + if (!ioStat_ || ioStat_ < FORTRAN_RUNTIME_IOSTAT_EOR) { + ioStat_ = FORTRAN_RUNTIME_IOSTAT_EOR; // least priority + } } else { Crash("End of record"); } } - -int IoErrorHandler::GetIoStat() const { - if (ioStat_) { - return ioStat_; - } else if (hitEnd_) { - return FORTRAN_RUNTIME_IOSTAT_END; - } else if (hitEor_) { - return FORTRAN_RUNTIME_IOSTAT_EOR; - } else { - return 0; - } -} - } diff --git a/flang/runtime/io-error.h b/flang/runtime/io-error.h index 08aea4e..6cab725 100644 --- a/flang/runtime/io-error.h +++ b/flang/runtime/io-error.h @@ -28,10 +28,11 @@ public: void HasEorLabel() { flags_ |= hasEor; } void SignalError(int iostatOrErrno); + void SignalErrno(); void SignalEnd(); void SignalEor(); - int GetIoStat() const; + int GetIoStat() const { return ioStat_; } private: enum Flag : std::uint8_t { @@ -41,8 +42,6 @@ private: hasEor = 8, // EOR= }; std::uint8_t flags_{0}; - bool hitEnd_{false}; - bool hitEor_{false}; int ioStat_{0}; }; diff --git a/flang/runtime/lock.h b/flang/runtime/lock.h new file mode 100644 index 0000000..19f0cea --- /dev/null +++ b/flang/runtime/lock.h @@ -0,0 +1,47 @@ +//===-- runtime/lock.h ------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +// Wraps pthread_mutex_t (or whatever) + +#ifndef FORTRAN_RUNTIME_LOCK_H_ +#define FORTRAN_RUNTIME_LOCK_H_ + +#include + +namespace Fortran::runtime { + +class Lock { +public: + Lock() { pthread_mutex_init(&mutex_, nullptr); } + ~Lock() { pthread_mutex_destroy(&mutex_); } + void Take() { pthread_mutex_lock(&mutex_); } + bool Try() { return pthread_mutex_trylock(&mutex_) != 0; } + void Drop() { pthread_mutex_unlock(&mutex_); } + + void CheckLocked(Terminator &terminator) { + if (Try()) { + Drop(); + terminator.Crash("Lock::CheckLocked() failed"); + } + } + +private: + pthread_mutex_t mutex_; +}; + +class CriticalSection { +public: + explicit CriticalSection(Lock &lock) : lock_{lock} { lock_.Take(); } + ~CriticalSection() { lock_.Drop(); } + +private: + Lock &lock_; +}; +} + +#endif // FORTRAN_RUNTIME_LOCK_H_ diff --git a/flang/runtime/memory.h b/flang/runtime/memory.h index f44ceed..3e65a98 100644 --- a/flang/runtime/memory.h +++ b/flang/runtime/memory.h @@ -26,16 +26,15 @@ void FreeMemory(void *); void FreeMemoryAndNullify(void *&); template struct New { - template A &operator()(Terminator &terminator, X&&... x) { - return *new (AllocateMemoryOrCrash(terminator, sizeof(A))) A{std::forward(x)...}; + template A &operator()(Terminator &terminator, X &&... x) { + return *new (AllocateMemoryOrCrash(terminator, sizeof(A))) + A{std::forward(x)...}; } }; -namespace { -template class OwningPtrDeleter { +template struct OwningPtrDeleter { void operator()(A *p) { FreeMemory(p); } }; -} template using OwningPtr = std::unique_ptr>; } diff --git a/flang/runtime/tools.cc b/flang/runtime/tools.cc new file mode 100644 index 0000000..8a9980f --- /dev/null +++ b/flang/runtime/tools.cc @@ -0,0 +1,51 @@ +//===-- runtime/tools.cc ----------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "tools.h" +#include + +namespace Fortran::runtime { + +OwningPtr SaveDefaultCharacter( + const char *s, std::size_t length, Terminator &terminator) { + if (s) { + auto *p{static_cast(AllocateMemoryOrCrash(terminator, length + 1))}; + std::memcpy(p, s, length); + p[length] = '\0'; + return OwningPtr{p}; + } else { + return OwningPtr{}; + } +} + +static bool CaseInsensitiveMatch( + const char *value, std::size_t length, const char *possibility) { + for (; length-- > 0; ++value, ++possibility) { + char ch{*value}; + if (ch >= 'a' && ch <= 'z') { + ch += 'A' - 'a'; + } + if (*possibility == '\0' || ch != *possibility) { + return false; + } + } + return *possibility == '\0'; +} + +int IdentifyValue( + const char *value, std::size_t length, const char *possibilities[]) { + if (value) { + for (int j{0}; possibilities[j]; ++j) { + if (CaseInsensitiveMatch(value, length, possibilities[j])) { + return j; + } + } + } + return -1; +} +} diff --git a/flang/runtime/tools.h b/flang/runtime/tools.h new file mode 100644 index 0000000..184f6af --- /dev/null +++ b/flang/runtime/tools.h @@ -0,0 +1,25 @@ +//===-- runtime/tools.h -----------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef FORTRAN_RUNTIME_TOOLS_H_ +#define FORTRAN_RUNTIME_TOOLS_H_ +#include "memory.h" +namespace Fortran::runtime { + +class Terminator; + +OwningPtr SaveDefaultCharacter(const char *, std::size_t, Terminator &); + +// For validating and recognizing default CHARACTER values in a +// case-insensitive manner. Returns the zero-based index into the +// null-terminated array of upper-case possibilities when the value is valid, +// or -1 when it has no match. +int IdentifyValue( + const char *value, std::size_t length, const char *possibilities[]); +} +#endif // FORTRAN_RUNTIME_TOOLS_H_ -- 2.7.4