1 //===-- runtime/buffer.h ----------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 // External file buffering 10 11 #ifndef FORTRAN_RUNTIME_BUFFER_H_ 12 #define FORTRAN_RUNTIME_BUFFER_H_ 13 14 #include "io-error.h" 15 #include "memory.h" 16 #include <algorithm> 17 #include <cinttypes> 18 #include <cstring> 19 20 namespace Fortran::runtime::io { 21 22 void LeftShiftBufferCircularly(char *, std::size_t bytes, std::size_t shift); 23 24 // Maintains a view of a contiguous region of a file in a memory buffer. 25 // The valid data in the buffer may be circular, but any active frame 26 // will also be contiguous in memory. The requirement stems from the need to 27 // preserve read data that may be reused by means of Tn/TLn edit descriptors 28 // without needing to position the file (which may not always be possible, 29 // e.g. a socket) and a general desire to reduce system call counts. 30 template <typename STORE> class FileFrame { 31 public: 32 using FileOffset = std::int64_t; 33 34 ~FileFrame() { FreeMemoryAndNullify(buffer_); } 35 36 // The valid data in the buffer begins at buffer_[start_] and proceeds 37 // with possible wrap-around for length_ bytes. The current frame 38 // is offset by frame_ bytes into that region and is guaranteed to 39 // be contiguous for at least as many bytes as were requested. 40 41 FileOffset FrameAt() const { return fileOffset_ + frame_; } 42 char *Frame() const { return buffer_ + start_ + frame_; } 43 std::size_t FrameLength() const { 44 return std::min<std::size_t>(length_ - frame_, size_ - (start_ + frame_)); 45 } 46 std::size_t BytesBufferedBeforeFrame() const { return frame_ - start_; } 47 48 // Returns a short frame at a non-fatal EOF. Can return a long frame as well. 49 std::size_t ReadFrame( 50 FileOffset at, std::size_t bytes, IoErrorHandler &handler) { 51 Flush(handler); 52 Reallocate(bytes, handler); 53 if (at < fileOffset_ || at > fileOffset_ + length_) { 54 Reset(at); 55 } 56 frame_ = at - fileOffset_; 57 if (static_cast<std::int64_t>(start_ + frame_ + bytes) > size_) { 58 DiscardLeadingBytes(frame_, handler); 59 if (static_cast<std::int64_t>(start_ + bytes) > size_) { 60 // Frame would wrap around; shift current data (if any) to force 61 // contiguity. 62 RUNTIME_CHECK(handler, length_ < size_); 63 if (start_ + length_ <= size_) { 64 // [......abcde..] -> [abcde........] 65 std::memmove(buffer_, buffer_ + start_, length_); 66 } else { 67 // [cde........ab] -> [abcde........] 68 auto n{start_ + length_ - size_}; // 3 for cde 69 RUNTIME_CHECK(handler, length_ >= n); 70 std::memmove(buffer_ + n, buffer_ + start_, length_ - n); // cdeab 71 LeftShiftBufferCircularly(buffer_, length_, n); // abcde 72 } 73 start_ = 0; 74 } 75 } 76 while (FrameLength() < bytes) { 77 auto next{start_ + length_}; 78 RUNTIME_CHECK(handler, next < size_); 79 auto minBytes{bytes - FrameLength()}; 80 auto maxBytes{size_ - next}; 81 auto got{Store().Read( 82 fileOffset_ + length_, buffer_ + next, minBytes, maxBytes, handler)}; 83 length_ += got; 84 RUNTIME_CHECK(handler, length_ < size_); 85 if (got < minBytes) { 86 break; // error or EOF & program can handle it 87 } 88 } 89 return FrameLength(); 90 } 91 92 void WriteFrame(FileOffset at, std::size_t bytes, IoErrorHandler &handler) { 93 if (!dirty_ || at < fileOffset_ || at > fileOffset_ + length_ || 94 start_ + (at - fileOffset_) + static_cast<std::int64_t>(bytes) > 95 size_) { 96 Flush(handler); 97 fileOffset_ = at; 98 Reallocate(bytes, handler); 99 } 100 dirty_ = true; 101 frame_ = at - fileOffset_; 102 length_ = std::max<std::int64_t>(length_, frame_ + bytes); 103 } 104 105 void Flush(IoErrorHandler &handler) { 106 if (dirty_) { 107 while (length_ > 0) { 108 std::size_t chunk{std::min<std::size_t>(length_, size_ - start_)}; 109 std::size_t put{ 110 Store().Write(fileOffset_, buffer_ + start_, chunk, handler)}; 111 length_ -= put; 112 start_ += put; 113 fileOffset_ += put; 114 if (put < chunk) { 115 break; 116 } 117 } 118 Reset(fileOffset_); 119 } 120 } 121 122 private: 123 STORE &Store() { return static_cast<STORE &>(*this); } 124 125 void Reallocate(std::int64_t bytes, const Terminator &terminator) { 126 if (bytes > size_) { 127 char *old{buffer_}; 128 auto oldSize{size_}; 129 size_ = std::max<std::int64_t>(bytes, minBuffer); 130 buffer_ = 131 reinterpret_cast<char *>(AllocateMemoryOrCrash(terminator, size_)); 132 auto chunk{std::min<std::int64_t>(length_, oldSize - start_)}; 133 std::memcpy(buffer_, old + start_, chunk); 134 start_ = 0; 135 std::memcpy(buffer_ + chunk, old, length_ - chunk); 136 FreeMemory(old); 137 } 138 } 139 140 void Reset(FileOffset at) { 141 start_ = length_ = frame_ = 0; 142 fileOffset_ = at; 143 dirty_ = false; 144 } 145 146 void DiscardLeadingBytes(std::int64_t n, const Terminator &terminator) { 147 RUNTIME_CHECK(terminator, length_ >= n); 148 length_ -= n; 149 if (length_ == 0) { 150 start_ = 0; 151 } else { 152 start_ += n; 153 if (start_ >= size_) { 154 start_ -= size_; 155 } 156 } 157 if (frame_ >= n) { 158 frame_ -= n; 159 } else { 160 frame_ = 0; 161 } 162 fileOffset_ += n; 163 } 164 165 static constexpr std::size_t minBuffer{64 << 10}; 166 167 char *buffer_{nullptr}; 168 std::int64_t size_{0}; // current allocated buffer size 169 FileOffset fileOffset_{0}; // file offset corresponding to buffer valid data 170 std::int64_t start_{0}; // buffer_[] offset of valid data 171 std::int64_t length_{0}; // valid data length (can wrap) 172 std::int64_t frame_{0}; // offset of current frame in valid data 173 bool dirty_{false}; 174 }; 175 } // namespace Fortran::runtime::io 176 #endif // FORTRAN_RUNTIME_BUFFER_H_ 177