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 // 31 // Possible scenario with a tiny 32-byte buffer after a ReadFrame or 32 // WriteFrame with a file offset of 103 to access "DEF": 33 // 34 // fileOffset_ 100 --+ +-+ frame of interest (103:105) 35 // file: ............ABCDEFGHIJKLMNOPQRSTUVWXYZ.... 36 // buffer: [NOPQRSTUVWXYZ......ABCDEFGHIJKLM] (size_ == 32) 37 // | +-- frame_ == 3 38 // +----- start_ == 19, length_ == 26 39 // 40 // The buffer holds length_ == 26 bytes from file offsets 100:125. 41 // Those 26 bytes "wrap around" the end of the circular buffer, 42 // so file offsets 100:112 map to buffer offsets 19:31 ("A..M") and 43 // file offsets 113:125 map to buffer offsets 0:12 ("N..Z") 44 // The 3-byte frame of file offsets 103:105 is contiguous in the buffer 45 // at buffer offset (start_ + frame_) == 22 ("DEF"). 46 47 template <typename STORE, std::size_t minBuffer = 65536> class FileFrame { 48 public: 49 using FileOffset = std::int64_t; 50 ~FileFrame()51 ~FileFrame() { FreeMemoryAndNullify(buffer_); } 52 53 // The valid data in the buffer begins at buffer_[start_] and proceeds 54 // with possible wrap-around for length_ bytes. The current frame 55 // is offset by frame_ bytes into that region and is guaranteed to 56 // be contiguous for at least as many bytes as were requested. 57 FrameAt()58 FileOffset FrameAt() const { return fileOffset_ + frame_; } Frame()59 char *Frame() const { return buffer_ + start_ + frame_; } FrameLength()60 std::size_t FrameLength() const { 61 return std::min<std::size_t>(length_ - frame_, size_ - (start_ + frame_)); 62 } BytesBufferedBeforeFrame()63 std::size_t BytesBufferedBeforeFrame() const { return frame_ - start_; } 64 65 // Returns a short frame at a non-fatal EOF. Can return a long frame as well. ReadFrame(FileOffset at,std::size_t bytes,IoErrorHandler & handler)66 std::size_t ReadFrame( 67 FileOffset at, std::size_t bytes, IoErrorHandler &handler) { 68 Flush(handler); 69 Reallocate(bytes, handler); 70 std::int64_t newFrame{at - fileOffset_}; 71 if (newFrame < 0 || newFrame > length_) { 72 Reset(at); 73 } else { 74 frame_ = newFrame; 75 } 76 RUNTIME_CHECK(handler, at == fileOffset_ + frame_); 77 if (static_cast<std::int64_t>(start_ + frame_ + bytes) > size_) { 78 DiscardLeadingBytes(frame_, handler); 79 MakeDataContiguous(handler, bytes); 80 RUNTIME_CHECK(handler, at == fileOffset_ + frame_); 81 } 82 if (FrameLength() < bytes) { 83 auto next{start_ + length_}; 84 RUNTIME_CHECK(handler, next < size_); 85 auto minBytes{bytes - FrameLength()}; 86 auto maxBytes{size_ - next}; 87 auto got{Store().Read( 88 fileOffset_ + length_, buffer_ + next, minBytes, maxBytes, handler)}; 89 length_ += got; 90 RUNTIME_CHECK(handler, length_ <= size_); 91 } 92 return FrameLength(); 93 } 94 WriteFrame(FileOffset at,std::size_t bytes,IoErrorHandler & handler)95 void WriteFrame(FileOffset at, std::size_t bytes, IoErrorHandler &handler) { 96 Reallocate(bytes, handler); 97 std::int64_t newFrame{at - fileOffset_}; 98 if (!dirty_ || newFrame < 0 || newFrame > length_) { 99 Flush(handler); 100 Reset(at); 101 } else if (start_ + newFrame + static_cast<std::int64_t>(bytes) > size_) { 102 // Flush leading data before "at", retain from "at" onward 103 Flush(handler, length_ - newFrame); 104 MakeDataContiguous(handler, bytes); 105 } else { 106 frame_ = newFrame; 107 } 108 RUNTIME_CHECK(handler, at == fileOffset_ + frame_); 109 dirty_ = true; 110 length_ = std::max<std::int64_t>(length_, frame_ + bytes); 111 } 112 113 void Flush(IoErrorHandler &handler, std::int64_t keep = 0) { 114 if (dirty_) { 115 while (length_ > keep) { 116 std::size_t chunk{ 117 std::min<std::size_t>(length_ - keep, size_ - start_)}; 118 std::size_t put{ 119 Store().Write(fileOffset_, buffer_ + start_, chunk, handler)}; 120 DiscardLeadingBytes(put, handler); 121 if (put < chunk) { 122 break; 123 } 124 } 125 if (length_ == 0) { 126 Reset(fileOffset_); 127 } 128 } 129 } 130 131 private: Store()132 STORE &Store() { return static_cast<STORE &>(*this); } 133 Reallocate(std::int64_t bytes,const Terminator & terminator)134 void Reallocate(std::int64_t bytes, const Terminator &terminator) { 135 if (bytes > size_) { 136 char *old{buffer_}; 137 auto oldSize{size_}; 138 size_ = std::max<std::int64_t>(bytes, minBuffer); 139 buffer_ = 140 reinterpret_cast<char *>(AllocateMemoryOrCrash(terminator, size_)); 141 auto chunk{std::min<std::int64_t>(length_, oldSize - start_)}; 142 std::memcpy(buffer_, old + start_, chunk); 143 start_ = 0; 144 std::memcpy(buffer_ + chunk, old, length_ - chunk); 145 FreeMemory(old); 146 } 147 } 148 Reset(FileOffset at)149 void Reset(FileOffset at) { 150 start_ = length_ = frame_ = 0; 151 fileOffset_ = at; 152 dirty_ = false; 153 } 154 DiscardLeadingBytes(std::int64_t n,const Terminator & terminator)155 void DiscardLeadingBytes(std::int64_t n, const Terminator &terminator) { 156 RUNTIME_CHECK(terminator, length_ >= n); 157 length_ -= n; 158 if (length_ == 0) { 159 start_ = 0; 160 } else { 161 start_ += n; 162 if (start_ >= size_) { 163 start_ -= size_; 164 } 165 } 166 if (frame_ >= n) { 167 frame_ -= n; 168 } else { 169 frame_ = 0; 170 } 171 fileOffset_ += n; 172 } 173 MakeDataContiguous(IoErrorHandler & handler,std::size_t bytes)174 void MakeDataContiguous(IoErrorHandler &handler, std::size_t bytes) { 175 if (static_cast<std::int64_t>(start_ + bytes) > size_) { 176 // Frame would wrap around; shift current data (if any) to force 177 // contiguity. 178 RUNTIME_CHECK(handler, length_ < size_); 179 if (start_ + length_ <= size_) { 180 // [......abcde..] -> [abcde........] 181 std::memmove(buffer_, buffer_ + start_, length_); 182 } else { 183 // [cde........ab] -> [abcde........] 184 auto n{start_ + length_ - size_}; // 3 for cde 185 RUNTIME_CHECK(handler, length_ >= n); 186 std::memmove(buffer_ + n, buffer_ + start_, length_ - n); // cdeab 187 LeftShiftBufferCircularly(buffer_, length_, n); // abcde 188 } 189 start_ = 0; 190 } 191 } 192 193 char *buffer_{nullptr}; 194 std::int64_t size_{0}; // current allocated buffer size 195 FileOffset fileOffset_{0}; // file offset corresponding to buffer valid data 196 std::int64_t start_{0}; // buffer_[] offset of valid data 197 std::int64_t length_{0}; // valid data length (can wrap) 198 std::int64_t frame_{0}; // offset of current frame in valid data 199 bool dirty_{false}; 200 }; 201 } // namespace Fortran::runtime::io 202 #endif // FORTRAN_RUNTIME_BUFFER_H_ 203