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 "flang/Runtime/freestanding-tools.h" 16 #include "flang/Runtime/memory.h" 17 #include <algorithm> 18 #include <cinttypes> 19 #include <cstring> 20 21 namespace Fortran::runtime::io { 22 23 RT_API_ATTRS void LeftShiftBufferCircularly( 24 char *, std::size_t bytes, std::size_t shift); 25 26 // Maintains a view of a contiguous region of a file in a memory buffer. 27 // The valid data in the buffer may be circular, but any active frame 28 // will also be contiguous in memory. The requirement stems from the need to 29 // preserve read data that may be reused by means of Tn/TLn edit descriptors 30 // without needing to position the file (which may not always be possible, 31 // e.g. a socket) and a general desire to reduce system call counts. 32 // 33 // Possible scenario with a tiny 32-byte buffer after a ReadFrame or 34 // WriteFrame with a file offset of 103 to access "DEF": 35 // 36 // fileOffset_ 100 --+ +-+ frame of interest (103:105) 37 // file: ............ABCDEFGHIJKLMNOPQRSTUVWXYZ.... 38 // buffer: [NOPQRSTUVWXYZ......ABCDEFGHIJKLM] (size_ == 32) 39 // | +-- frame_ == 3 40 // +----- start_ == 19, length_ == 26 41 // 42 // The buffer holds length_ == 26 bytes from file offsets 100:125. 43 // Those 26 bytes "wrap around" the end of the circular buffer, 44 // so file offsets 100:112 map to buffer offsets 19:31 ("A..M") and 45 // file offsets 113:125 map to buffer offsets 0:12 ("N..Z") 46 // The 3-byte frame of file offsets 103:105 is contiguous in the buffer 47 // at buffer offset (start_ + frame_) == 22 ("DEF"). 48 49 template <typename STORE, std::size_t minBuffer = 65536> class FileFrame { 50 public: 51 using FileOffset = std::int64_t; 52 ~FileFrame()53 RT_API_ATTRS ~FileFrame() { FreeMemoryAndNullify(buffer_); } 54 55 // The valid data in the buffer begins at buffer_[start_] and proceeds 56 // with possible wrap-around for length_ bytes. The current frame 57 // is offset by frame_ bytes into that region and is guaranteed to 58 // be contiguous for at least as many bytes as were requested. 59 FrameAt()60 RT_API_ATTRS FileOffset FrameAt() const { return fileOffset_ + frame_; } Frame()61 RT_API_ATTRS char *Frame() const { return buffer_ + start_ + frame_; } FrameLength()62 RT_API_ATTRS std::size_t FrameLength() const { 63 return std::min<std::size_t>(length_ - frame_, size_ - (start_ + frame_)); 64 } BytesBufferedBeforeFrame()65 RT_API_ATTRS std::size_t BytesBufferedBeforeFrame() const { 66 return frame_ - start_; 67 } 68 69 // 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)70 RT_API_ATTRS std::size_t ReadFrame( 71 FileOffset at, std::size_t bytes, IoErrorHandler &handler) { 72 Flush(handler); 73 Reallocate(bytes, handler); 74 std::int64_t newFrame{at - fileOffset_}; 75 if (newFrame < 0 || newFrame > length_) { 76 Reset(at); 77 } else { 78 frame_ = newFrame; 79 } 80 RUNTIME_CHECK(handler, at == fileOffset_ + frame_); 81 if (static_cast<std::int64_t>(start_ + frame_ + bytes) > size_) { 82 DiscardLeadingBytes(frame_, handler); 83 MakeDataContiguous(handler, bytes); 84 RUNTIME_CHECK(handler, at == fileOffset_ + frame_); 85 } 86 if (FrameLength() < bytes) { 87 auto next{start_ + length_}; 88 RUNTIME_CHECK(handler, next < size_); 89 auto minBytes{bytes - FrameLength()}; 90 auto maxBytes{size_ - next}; 91 auto got{Store().Read( 92 fileOffset_ + length_, buffer_ + next, minBytes, maxBytes, handler)}; 93 length_ += got; 94 RUNTIME_CHECK(handler, length_ <= size_); 95 } 96 return FrameLength(); 97 } 98 WriteFrame(FileOffset at,std::size_t bytes,IoErrorHandler & handler)99 RT_API_ATTRS void WriteFrame( 100 FileOffset at, std::size_t bytes, IoErrorHandler &handler) { 101 Reallocate(bytes, handler); 102 std::int64_t newFrame{at - fileOffset_}; 103 if (!dirty_ || newFrame < 0 || newFrame > length_) { 104 Flush(handler); 105 Reset(at); 106 } else if (start_ + newFrame + static_cast<std::int64_t>(bytes) > size_) { 107 // Flush leading data before "at", retain from "at" onward 108 Flush(handler, length_ - newFrame); 109 MakeDataContiguous(handler, bytes); 110 } else { 111 frame_ = newFrame; 112 } 113 RUNTIME_CHECK(handler, at == fileOffset_ + frame_); 114 dirty_ = true; 115 length_ = std::max<std::int64_t>(length_, frame_ + bytes); 116 } 117 118 RT_API_ATTRS void Flush(IoErrorHandler &handler, std::int64_t keep = 0) { 119 if (dirty_) { 120 while (length_ > keep) { 121 std::size_t chunk{ 122 std::min<std::size_t>(length_ - keep, size_ - start_)}; 123 std::size_t put{ 124 Store().Write(fileOffset_, buffer_ + start_, chunk, handler)}; 125 DiscardLeadingBytes(put, handler); 126 if (put < chunk) { 127 break; 128 } 129 } 130 if (length_ == 0) { 131 Reset(fileOffset_); 132 } 133 } 134 } 135 TruncateFrame(std::int64_t at,IoErrorHandler & handler)136 RT_API_ATTRS void TruncateFrame(std::int64_t at, IoErrorHandler &handler) { 137 RUNTIME_CHECK(handler, !dirty_); 138 if (at <= fileOffset_) { 139 Reset(at); 140 } else if (at < fileOffset_ + length_) { 141 length_ = at - fileOffset_; 142 } 143 } 144 145 private: Store()146 RT_API_ATTRS STORE &Store() { return static_cast<STORE &>(*this); } 147 Reallocate(std::int64_t bytes,const Terminator & terminator)148 RT_API_ATTRS void Reallocate( 149 std::int64_t bytes, const Terminator &terminator) { 150 if (bytes > size_) { 151 char *old{buffer_}; 152 auto oldSize{size_}; 153 size_ = std::max<std::int64_t>(bytes, size_ + minBuffer); 154 buffer_ = 155 reinterpret_cast<char *>(AllocateMemoryOrCrash(terminator, size_)); 156 auto chunk{std::min<std::int64_t>(length_, oldSize - start_)}; 157 // "memcpy" in glibc has a "nonnull" attribute on the source pointer. 158 // Avoid passing a null pointer, since it would result in an undefined 159 // behavior. 160 if (old != nullptr) { 161 std::memcpy(buffer_, old + start_, chunk); 162 std::memcpy(buffer_ + chunk, old, length_ - chunk); 163 FreeMemory(old); 164 } 165 start_ = 0; 166 } 167 } 168 Reset(FileOffset at)169 RT_API_ATTRS void Reset(FileOffset at) { 170 start_ = length_ = frame_ = 0; 171 fileOffset_ = at; 172 dirty_ = false; 173 } 174 DiscardLeadingBytes(std::int64_t n,const Terminator & terminator)175 RT_API_ATTRS void DiscardLeadingBytes( 176 std::int64_t n, const Terminator &terminator) { 177 RUNTIME_CHECK(terminator, length_ >= n); 178 length_ -= n; 179 if (length_ == 0) { 180 start_ = 0; 181 } else { 182 start_ += n; 183 if (start_ >= size_) { 184 start_ -= size_; 185 } 186 } 187 if (frame_ >= n) { 188 frame_ -= n; 189 } else { 190 frame_ = 0; 191 } 192 fileOffset_ += n; 193 } 194 MakeDataContiguous(IoErrorHandler & handler,std::size_t bytes)195 RT_API_ATTRS void MakeDataContiguous( 196 IoErrorHandler &handler, std::size_t bytes) { 197 if (static_cast<std::int64_t>(start_ + bytes) > size_) { 198 // Frame would wrap around; shift current data (if any) to force 199 // contiguity. 200 RUNTIME_CHECK(handler, length_ < size_); 201 if (start_ + length_ <= size_) { 202 // [......abcde..] -> [abcde........] 203 runtime::memmove(buffer_, buffer_ + start_, length_); 204 } else { 205 // [cde........ab] -> [abcde........] 206 auto n{start_ + length_ - size_}; // 3 for cde 207 RUNTIME_CHECK(handler, length_ >= n); 208 runtime::memmove(buffer_ + n, buffer_ + start_, length_ - n); // cdeab 209 LeftShiftBufferCircularly(buffer_, length_, n); // abcde 210 } 211 start_ = 0; 212 } 213 } 214 215 char *buffer_{nullptr}; 216 std::int64_t size_{0}; // current allocated buffer size 217 FileOffset fileOffset_{0}; // file offset corresponding to buffer valid data 218 std::int64_t start_{0}; // buffer_[] offset of valid data 219 std::int64_t length_{0}; // valid data length (can wrap) 220 std::int64_t frame_{0}; // offset of current frame in valid data 221 bool dirty_{false}; 222 }; 223 } // namespace Fortran::runtime::io 224 #endif // FORTRAN_RUNTIME_BUFFER_H_ 225