| //===-- runtime/buffer.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 |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // External file buffering |
| |
| #ifndef FORTRAN_RUNTIME_BUFFER_H_ |
| #define FORTRAN_RUNTIME_BUFFER_H_ |
| |
| #include "io-error.h" |
| #include "flang/Runtime/memory.h" |
| #include <algorithm> |
| #include <cinttypes> |
| #include <cstring> |
| |
| namespace Fortran::runtime::io { |
| |
| void LeftShiftBufferCircularly(char *, std::size_t bytes, std::size_t shift); |
| |
| // Maintains a view of a contiguous region of a file in a memory buffer. |
| // The valid data in the buffer may be circular, but any active frame |
| // will also be contiguous in memory. The requirement stems from the need to |
| // preserve read data that may be reused by means of Tn/TLn edit descriptors |
| // without needing to position the file (which may not always be possible, |
| // e.g. a socket) and a general desire to reduce system call counts. |
| // |
| // Possible scenario with a tiny 32-byte buffer after a ReadFrame or |
| // WriteFrame with a file offset of 103 to access "DEF": |
| // |
| // fileOffset_ 100 --+ +-+ frame of interest (103:105) |
| // file: ............ABCDEFGHIJKLMNOPQRSTUVWXYZ.... |
| // buffer: [NOPQRSTUVWXYZ......ABCDEFGHIJKLM] (size_ == 32) |
| // | +-- frame_ == 3 |
| // +----- start_ == 19, length_ == 26 |
| // |
| // The buffer holds length_ == 26 bytes from file offsets 100:125. |
| // Those 26 bytes "wrap around" the end of the circular buffer, |
| // so file offsets 100:112 map to buffer offsets 19:31 ("A..M") and |
| // file offsets 113:125 map to buffer offsets 0:12 ("N..Z") |
| // The 3-byte frame of file offsets 103:105 is contiguous in the buffer |
| // at buffer offset (start_ + frame_) == 22 ("DEF"). |
| |
| template <typename STORE, std::size_t minBuffer = 65536> class FileFrame { |
| public: |
| using FileOffset = std::int64_t; |
| |
| ~FileFrame() { FreeMemoryAndNullify(buffer_); } |
| |
| // The valid data in the buffer begins at buffer_[start_] and proceeds |
| // with possible wrap-around for length_ bytes. The current frame |
| // is offset by frame_ bytes into that region and is guaranteed to |
| // be contiguous for at least as many bytes as were requested. |
| |
| FileOffset FrameAt() const { return fileOffset_ + frame_; } |
| char *Frame() const { return buffer_ + start_ + frame_; } |
| std::size_t FrameLength() const { |
| return std::min<std::size_t>(length_ - frame_, size_ - (start_ + frame_)); |
| } |
| std::size_t BytesBufferedBeforeFrame() const { return frame_ - start_; } |
| |
| // Returns a short frame at a non-fatal EOF. Can return a long frame as well. |
| std::size_t ReadFrame( |
| FileOffset at, std::size_t bytes, IoErrorHandler &handler) { |
| Flush(handler); |
| Reallocate(bytes, handler); |
| std::int64_t newFrame{at - fileOffset_}; |
| if (newFrame < 0 || newFrame > length_) { |
| Reset(at); |
| } else { |
| frame_ = newFrame; |
| } |
| RUNTIME_CHECK(handler, at == fileOffset_ + frame_); |
| if (static_cast<std::int64_t>(start_ + frame_ + bytes) > size_) { |
| DiscardLeadingBytes(frame_, handler); |
| MakeDataContiguous(handler, bytes); |
| RUNTIME_CHECK(handler, at == fileOffset_ + frame_); |
| } |
| if (FrameLength() < bytes) { |
| auto next{start_ + length_}; |
| RUNTIME_CHECK(handler, next < size_); |
| auto minBytes{bytes - FrameLength()}; |
| auto maxBytes{size_ - next}; |
| auto got{Store().Read( |
| fileOffset_ + length_, buffer_ + next, minBytes, maxBytes, handler)}; |
| length_ += got; |
| RUNTIME_CHECK(handler, length_ <= size_); |
| } |
| return FrameLength(); |
| } |
| |
| void WriteFrame(FileOffset at, std::size_t bytes, IoErrorHandler &handler) { |
| Reallocate(bytes, handler); |
| std::int64_t newFrame{at - fileOffset_}; |
| if (!dirty_ || newFrame < 0 || newFrame > length_) { |
| Flush(handler); |
| Reset(at); |
| } else if (start_ + newFrame + static_cast<std::int64_t>(bytes) > size_) { |
| // Flush leading data before "at", retain from "at" onward |
| Flush(handler, length_ - newFrame); |
| MakeDataContiguous(handler, bytes); |
| } else { |
| frame_ = newFrame; |
| } |
| RUNTIME_CHECK(handler, at == fileOffset_ + frame_); |
| dirty_ = true; |
| length_ = std::max<std::int64_t>(length_, frame_ + bytes); |
| } |
| |
| void Flush(IoErrorHandler &handler, std::int64_t keep = 0) { |
| if (dirty_) { |
| while (length_ > keep) { |
| std::size_t chunk{ |
| std::min<std::size_t>(length_ - keep, size_ - start_)}; |
| std::size_t put{ |
| Store().Write(fileOffset_, buffer_ + start_, chunk, handler)}; |
| DiscardLeadingBytes(put, handler); |
| if (put < chunk) { |
| break; |
| } |
| } |
| if (length_ == 0) { |
| Reset(fileOffset_); |
| } |
| } |
| } |
| |
| private: |
| STORE &Store() { return static_cast<STORE &>(*this); } |
| |
| void Reallocate(std::int64_t bytes, const Terminator &terminator) { |
| if (bytes > size_) { |
| char *old{buffer_}; |
| auto oldSize{size_}; |
| size_ = std::max<std::int64_t>(bytes, minBuffer); |
| buffer_ = |
| reinterpret_cast<char *>(AllocateMemoryOrCrash(terminator, size_)); |
| auto chunk{std::min<std::int64_t>(length_, oldSize - start_)}; |
| std::memcpy(buffer_, old + start_, chunk); |
| start_ = 0; |
| std::memcpy(buffer_ + chunk, old, length_ - chunk); |
| FreeMemory(old); |
| } |
| } |
| |
| void Reset(FileOffset at) { |
| start_ = length_ = frame_ = 0; |
| fileOffset_ = at; |
| dirty_ = false; |
| } |
| |
| void DiscardLeadingBytes(std::int64_t n, const Terminator &terminator) { |
| RUNTIME_CHECK(terminator, length_ >= n); |
| length_ -= n; |
| if (length_ == 0) { |
| start_ = 0; |
| } else { |
| start_ += n; |
| if (start_ >= size_) { |
| start_ -= size_; |
| } |
| } |
| if (frame_ >= n) { |
| frame_ -= n; |
| } else { |
| frame_ = 0; |
| } |
| fileOffset_ += n; |
| } |
| |
| void MakeDataContiguous(IoErrorHandler &handler, std::size_t bytes) { |
| if (static_cast<std::int64_t>(start_ + bytes) > size_) { |
| // Frame would wrap around; shift current data (if any) to force |
| // contiguity. |
| RUNTIME_CHECK(handler, length_ < size_); |
| if (start_ + length_ <= size_) { |
| // [......abcde..] -> [abcde........] |
| std::memmove(buffer_, buffer_ + start_, length_); |
| } else { |
| // [cde........ab] -> [abcde........] |
| auto n{start_ + length_ - size_}; // 3 for cde |
| RUNTIME_CHECK(handler, length_ >= n); |
| std::memmove(buffer_ + n, buffer_ + start_, length_ - n); // cdeab |
| LeftShiftBufferCircularly(buffer_, length_, n); // abcde |
| } |
| start_ = 0; |
| } |
| } |
| |
| char *buffer_{nullptr}; |
| std::int64_t size_{0}; // current allocated buffer size |
| FileOffset fileOffset_{0}; // file offset corresponding to buffer valid data |
| std::int64_t start_{0}; // buffer_[] offset of valid data |
| std::int64_t length_{0}; // valid data length (can wrap) |
| std::int64_t frame_{0}; // offset of current frame in valid data |
| bool dirty_{false}; |
| }; |
| } // namespace Fortran::runtime::io |
| #endif // FORTRAN_RUNTIME_BUFFER_H_ |