-
Notifications
You must be signed in to change notification settings - Fork 10.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[flang][runtime] Added pseudo file unit for simplified PRINT. #86134
Conversation
A file unit is emulated via a temporary buffer that accumulates the output, which is printed out via std::printf at the end of the IO statement. This implementation will be used for the offload devices.
@llvm/pr-subscribers-flang-runtime Author: Slava Zakharin (vzakhari) ChangesA file unit is emulated via a temporary buffer that accumulates Full diff: https://github.com/llvm/llvm-project/pull/86134.diff 5 Files Affected:
diff --git a/flang/runtime/io-stmt.cpp b/flang/runtime/io-stmt.cpp
index 075d7b5ae518a4..7746775f657444 100644
--- a/flang/runtime/io-stmt.cpp
+++ b/flang/runtime/io-stmt.cpp
@@ -227,7 +227,18 @@ ConnectionState &ExternalIoStatementBase::GetConnectionState() { return unit_; }
int ExternalIoStatementBase::EndIoStatement() {
CompleteOperation();
auto result{IoStatementBase::EndIoStatement()};
+#if defined(RT_USE_PSEUDO_FILE_UNIT)
+ // Fetch the unit pointer before *this disappears.
+ ExternalFileUnit *unitPtr{&unit_};
+#endif
unit_.EndIoStatement(); // annihilates *this in unit_.u_
+#if defined(RT_USE_PSEUDO_FILE_UNIT)
+ // The pseudo file units are dynamically allocated
+ // and are not tracked in the unit map.
+ // They have to be destructed and deallocated here.
+ unitPtr->~ExternalFileUnit();
+ FreeMemory(unitPtr);
+#endif
return result;
}
diff --git a/flang/runtime/lock.h b/flang/runtime/lock.h
index 5fdcf4745c21c2..61b06a62ff7c88 100644
--- a/flang/runtime/lock.h
+++ b/flang/runtime/lock.h
@@ -12,6 +12,7 @@
#define FORTRAN_RUNTIME_LOCK_H_
#include "terminator.h"
+#include "tools.h"
// Avoid <mutex> if possible to avoid introduction of C++ runtime
// library dependence.
@@ -35,7 +36,17 @@ namespace Fortran::runtime {
class Lock {
public:
-#if USE_PTHREADS
+#if RT_USE_PSEUDO_LOCK
+ // No lock implementation, e.g. for using together
+ // with RT_USE_PSEUDO_FILE_UNIT.
+ // The users of Lock class may use it under
+ // USE_PTHREADS and otherwise, so it has to provide
+ // all the interfaces.
+ void Take() {}
+ bool Try() { return true; }
+ void Drop() {}
+ bool TakeIfNoDeadlock() { return true; }
+#elif USE_PTHREADS
Lock() { pthread_mutex_init(&mutex_, nullptr); }
~Lock() { pthread_mutex_destroy(&mutex_); }
void Take() {
@@ -79,7 +90,9 @@ class Lock {
}
private:
-#if USE_PTHREADS
+#if RT_USE_PSEUDO_FILE_UNIT
+ // No state.
+#elif USE_PTHREADS
pthread_mutex_t mutex_{};
volatile bool isBusy_{false};
volatile pthread_t holder_;
diff --git a/flang/runtime/tools.h b/flang/runtime/tools.h
index df25eb8882335b..c70a1b438e3329 100644
--- a/flang/runtime/tools.h
+++ b/flang/runtime/tools.h
@@ -21,6 +21,27 @@
#include <map>
#include <type_traits>
+/// \macro RT_PRETTY_FUNCTION
+/// Gets a user-friendly looking function signature for the current scope
+/// using the best available method on each platform. The exact format of the
+/// resulting string is implementation specific and non-portable, so this should
+/// only be used, for example, for logging or diagnostics.
+/// Copy of LLVM_PRETTY_FUNCTION
+#if defined(_MSC_VER)
+#define RT_PRETTY_FUNCTION __FUNCSIG__
+#elif defined(__GNUC__) || defined(__clang__)
+#define RT_PRETTY_FUNCTION __PRETTY_FUNCTION__
+#else
+#define RT_PRETTY_FUNCTION __func__
+#endif
+
+#if defined(RT_DEVICE_COMPILATION)
+// Use the pseudo lock and pseudo file unit implementations
+// for the device.
+#define RT_USE_PSEUDO_LOCK 1
+#define RT_USE_PSEUDO_FILE_UNIT 1
+#endif
+
namespace Fortran::runtime {
class Terminator;
diff --git a/flang/runtime/unit.cpp b/flang/runtime/unit.cpp
index 82f0e68cc20a26..8d09502d3bfe94 100644
--- a/flang/runtime/unit.cpp
+++ b/flang/runtime/unit.cpp
@@ -18,15 +18,18 @@
namespace Fortran::runtime::io {
+#if !defined(RT_USE_PSEUDO_FILE_UNIT)
// 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};
+#endif // !defined(RT_USE_PSEUDO_FILE_UNIT)
static ExternalFileUnit *defaultInput{nullptr}; // unit 5
static ExternalFileUnit *defaultOutput{nullptr}; // unit 6
static ExternalFileUnit *errorOutput{nullptr}; // unit 0 extension
+#if !defined(RT_USE_PSEUDO_FILE_UNIT)
void FlushOutputOnCrash(const Terminator &terminator) {
if (!defaultOutput && !errorOutput) {
return;
@@ -41,7 +44,11 @@ void FlushOutputOnCrash(const Terminator &terminator) {
errorOutput->FlushOutput(handler);
}
}
+#else // defined(RT_USE_PSEUDO_FILE_UNIT)
+void FlushOutputOnCrash(const Terminator &terminator) {}
+#endif // defined(RT_USE_PSEUDO_FILE_UNIT)
+#if !defined(RT_USE_PSEUDO_FILE_UNIT)
ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
return GetUnitMap().LookUp(unit);
}
@@ -292,6 +299,70 @@ void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {
unitMap->FlushAll(handler);
}
}
+#else // defined(RT_USE_PSEUDO_FILE_UNIT)
+ExternalFileUnit *ExternalFileUnit::LookUp(int unit) {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+ExternalFileUnit *ExternalFileUnit::LookUpOrCreate(
+ int unit, const Terminator &, bool &wasExtant) {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+ExternalFileUnit *ExternalFileUnit::LookUpOrCreateAnonymous(int unit,
+ Direction direction, Fortran::common::optional<bool> isUnformatted,
+ const Terminator &terminator) {
+ if (direction != Direction::Output) {
+ terminator.Crash("ExternalFileUnit only supports output IO");
+ }
+ return New<ExternalFileUnit>{terminator}(unit).release();
+}
+ExternalFileUnit *ExternalFileUnit::LookUp(
+ const char *path, std::size_t pathLen) {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+ExternalFileUnit &ExternalFileUnit::CreateNew(int unit, const Terminator &) {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+ExternalFileUnit *ExternalFileUnit::LookUpForClose(int unit) {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+ExternalFileUnit &ExternalFileUnit::NewUnit(
+ const Terminator &, bool forChildIo) {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+
+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) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+
+void ExternalFileUnit::OpenAnonymousUnit(
+ Fortran::common::optional<OpenStatus> status,
+ Fortran::common::optional<Action> action, Position position,
+ Convert convert, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+
+void ExternalFileUnit::CloseUnit(CloseStatus status, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+
+void ExternalFileUnit::DestroyClosed() {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+
+Iostat ExternalFileUnit::SetDirection(Direction direction) {
+ if (direction != Direction::Output) {
+ return IostatReadFromWriteOnly;
+ }
+ direction_ = direction;
+ return IostatOk;
+}
+
+void ExternalFileUnit::CloseAll(IoErrorHandler &handler) {}
+void ExternalFileUnit::FlushAll(IoErrorHandler &handler) {}
+#endif // defined(RT_USE_PSEUDO_FILE_UNIT)
static inline void SwapEndianness(
char *data, std::size_t bytes, std::size_t elementBytes) {
@@ -999,6 +1070,7 @@ void ExternalFileUnit::PopChildIo(ChildIo &child) {
child_.reset(child.AcquirePrevious().release()); // deletes top child
}
+#if !defined(RT_USE_PSEUDO_FILE_UNIT)
int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) {
if (!mayAsynchronous()) {
handler.SignalError(IostatBadAsynchronous);
@@ -1031,6 +1103,14 @@ bool ExternalFileUnit::Wait(int id) {
return true;
}
}
+#else // defined(RT_USE_PSEUDO_FILE_UNIT)
+int ExternalFileUnit::GetAsynchronousId(IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+bool ExternalFileUnit::Wait(int id) {
+ Terminator{__FILE__, __LINE__}.Crash("unsupported");
+}
+#endif // defined(RT_USE_PSEUDO_FILE_UNIT)
std::int32_t ExternalFileUnit::ReadHeaderOrFooter(std::int64_t frameOffset) {
std::int32_t word;
@@ -1066,5 +1146,60 @@ Iostat ChildIo::CheckFormattingAndDirection(
return IostatOk;
}
}
+#if defined(RT_USE_PSEUDO_FILE_UNIT)
+void PseudoOpenFile::set_mayAsynchronous(bool yes) {
+ if (yes) {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+ }
+}
+Fortran::common::optional<PseudoOpenFile::FileOffset>
+PseudoOpenFile::knownSize() const {
+ Terminator{__FILE__, __LINE__}.Crash("unsupported");
+}
+void PseudoOpenFile::Open(OpenStatus, Fortran::common::optional<Action>,
+ Position, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+void PseudoOpenFile::Close(CloseStatus, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+std::size_t PseudoOpenFile::Read(FileOffset, char *, std::size_t minBytes,
+ std::size_t maxBytes, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+std::size_t PseudoOpenFile::Write(FileOffset at, const char *buffer,
+ std::size_t bytes, IoErrorHandler &handler) {
+ if (at) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+ }
+ // TODO: use persistent string buffer that can be reallocated
+ // as needed, and only freed at destruction of *this.
+ auto string{SizedNew<char>{handler}(bytes + 1)};
+ std::memcpy(string.get(), buffer, bytes);
+ string.get()[bytes] = '\0';
+ std::printf("%s", string.get());
+ return bytes;
+}
+void PseudoOpenFile::Truncate(FileOffset, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+int PseudoOpenFile::ReadAsynchronously(
+ FileOffset, char *, std::size_t, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+int PseudoOpenFile::WriteAsynchronously(
+ FileOffset, const char *, std::size_t, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+void PseudoOpenFile::Wait(int id, IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+void PseudoOpenFile::WaitAll(IoErrorHandler &handler) {
+ handler.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+Position PseudoOpenFile::InquirePosition() const {
+ Terminator{__FILE__, __LINE__}.Crash("%s: unsupported", RT_PRETTY_FUNCTION);
+}
+#endif // defined(RT_USE_PSEUDO_FILE_UNIT)
} // namespace Fortran::runtime::io
diff --git a/flang/runtime/unit.h b/flang/runtime/unit.h
index fc5bead7e1d930..7086197fe35b6b 100644
--- a/flang/runtime/unit.h
+++ b/flang/runtime/unit.h
@@ -32,9 +32,60 @@ namespace Fortran::runtime::io {
class UnitMap;
class ChildIo;
+#if defined(RT_USE_PSEUDO_FILE_UNIT)
+// A flavor of OpenFile class that pretends to be a terminal,
+// and only provides basic buffering of the output
+// in an internal buffer, and Write's the output
+// using std::printf(). Since it does not rely on file system
+// APIs, it can be used to implement external output
+// for offload devices.
+class PseudoOpenFile {
+public:
+ using FileOffset = std::int64_t;
+
+ const char *path() const { return nullptr; }
+ std::size_t pathLength() const { return 0; }
+ void set_path(OwningPtr<char> &&, std::size_t bytes) {}
+ bool mayRead() const { return false; }
+ bool mayWrite() const { return true; }
+ bool mayPosition() const { return false; }
+ bool mayAsynchronous() const { return false; }
+ void set_mayAsynchronous(bool yes);
+ // Pretend to be a terminal to force the output
+ // at the end of IO statement.
+ bool isTerminal() const { return true; }
+ bool isWindowsTextFile() const { return false; }
+ Fortran::common::optional<FileOffset> knownSize() const;
+ bool IsConnected() const { return false; }
+ void Open(OpenStatus, Fortran::common::optional<Action>, Position,
+ IoErrorHandler &);
+ void Predefine(int fd) {}
+ void Close(CloseStatus, IoErrorHandler &);
+ std::size_t Read(FileOffset, char *, std::size_t minBytes,
+ std::size_t maxBytes, IoErrorHandler &);
+ std::size_t Write(FileOffset, const char *, std::size_t, IoErrorHandler &);
+ void Truncate(FileOffset, IoErrorHandler &);
+ int ReadAsynchronously(FileOffset, char *, std::size_t, IoErrorHandler &);
+ int WriteAsynchronously(
+ FileOffset, const char *, std::size_t, IoErrorHandler &);
+ void Wait(int id, IoErrorHandler &);
+ void WaitAll(IoErrorHandler &);
+ Position InquirePosition() const;
+};
+#endif // defined(RT_USE_PSEUDO_FILE_UNIT)
+
+#if !defined(RT_USE_PSEUDO_FILE_UNIT)
+using OpenFileClass = OpenFile;
+using FileFrameClass = FileFrame<ExternalFileUnit>;
+#else // defined(RT_USE_PSEUDO_FILE_UNIT)
+using OpenFileClass = PseudoOpenFile;
+// Use not so big buffer for the pseudo file unit frame.
+using FileFrameClass = FileFrame<ExternalFileUnit, 1024>;
+#endif // defined(RT_USE_PSEUDO_FILE_UNIT)
+
class ExternalFileUnit : public ConnectionState,
- public OpenFile,
- public FileFrame<ExternalFileUnit> {
+ public OpenFileClass,
+ public FileFrameClass {
public:
static constexpr int maxAsyncIds{64 * 16};
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this code be structured so that unit.cpp isn't even compiled for the device?
flang/runtime/io-stmt.cpp
Outdated
#if defined(RT_USE_PSEUDO_FILE_UNIT) | ||
// Fetch the unit pointer before *this disappears. | ||
ExternalFileUnit *unitPtr{&unit_}; | ||
#endif | ||
unit_.EndIoStatement(); // annihilates *this in unit_.u_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment on this statement isn't relevant on the device. Maybe it would be more clear to have one #ifdef / #else / #endif
construct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I will change it.
Yes, I can extract the common parts (e.g. lines 367-1070) into a separate file, e.g. |
Or move |
flang/runtime/external-unit.cpp
Outdated
@@ -0,0 +1,332 @@ | |||
//===-- runtime/unit.cpp --------------------------------------------------===// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to update the source file name in this block comment.
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include "io-error.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The includes can be in the conditional block.
flang/runtime/pseudo-unit.cpp
Outdated
|
||
namespace Fortran::runtime::io { | ||
|
||
void FlushOutputOnCrash(const Terminator &terminator) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some C++ compilers will complain about unused formal arguments; I suggest removing their names, here and below.
…6134) A file unit is emulated via a temporary buffer that accumulates the output, which is printed out via std::printf at the end of the IO statement. This implementation will be used for the offload devices.
A file unit is emulated via a temporary buffer that accumulates
the output, which is printed out via std::printf at the end
of the IO statement. This implementation will be used for the offload devices.