Skip to content
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

Merged
merged 3 commits into from
Mar 21, 2024

Conversation

vzakhari
Copy link
Contributor

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.
@vzakhari vzakhari requested a review from klausler March 21, 2024 15:31
@llvmbot llvmbot added flang:runtime flang Flang issues not falling into any other category labels Mar 21, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Mar 21, 2024

@llvm/pr-subscribers-flang-runtime

Author: Slava Zakharin (vzakhari)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/86134.diff

5 Files Affected:

  • (modified) flang/runtime/io-stmt.cpp (+11)
  • (modified) flang/runtime/lock.h (+15-2)
  • (modified) flang/runtime/tools.h (+21)
  • (modified) flang/runtime/unit.cpp (+135)
  • (modified) flang/runtime/unit.h (+53-2)
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};
 

Copy link
Contributor

@klausler klausler left a 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?

#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_
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@vzakhari
Copy link
Contributor Author

Could this code be structured so that unit.cpp isn't even compiled for the device?

Yes, I can extract the common parts (e.g. lines 367-1070) into a separate file, e.g. unit-common.cpp, and split the methods that have different implementations into unit.cpp and unit-pseudo.cpp. Does it sound okay?

@klausler
Copy link
Contributor

Could this code be structured so that unit.cpp isn't even compiled for the device?

Yes, I can extract the common parts (e.g. lines 367-1070) into a separate file, e.g. unit-common.cpp, and split the methods that have different implementations into unit.cpp and unit-pseudo.cpp. Does it sound okay?

Or move unit.cpp to external-unit.cpp, since the runtime already has internal-unit.cpp.

@vzakhari vzakhari requested a review from klausler March 21, 2024 19:33
@@ -0,0 +1,332 @@
//===-- runtime/unit.cpp --------------------------------------------------===//
Copy link
Contributor

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"
Copy link
Contributor

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.


namespace Fortran::runtime::io {

void FlushOutputOnCrash(const Terminator &terminator) {}
Copy link
Contributor

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.

@vzakhari vzakhari merged commit 00f3454 into llvm:main Mar 21, 2024
3 of 4 checks passed
chencha3 pushed a commit to chencha3/llvm-project that referenced this pull request Mar 23, 2024
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flang:runtime flang Flang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants