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/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 
TruncateFrame(std::int64_t at,IoErrorHandler & handler)131   void TruncateFrame(std::int64_t at, IoErrorHandler &handler) {
132     RUNTIME_CHECK(handler, !dirty_);
133     if (at <= fileOffset_) {
134       Reset(at);
135     } else if (at < fileOffset_ + length_) {
136       length_ = at - fileOffset_;
137     }
138   }
139 
140 private:
Store()141   STORE &Store() { return static_cast<STORE &>(*this); }
142 
Reallocate(std::int64_t bytes,const Terminator & terminator)143   void Reallocate(std::int64_t bytes, const Terminator &terminator) {
144     if (bytes > size_) {
145       char *old{buffer_};
146       auto oldSize{size_};
147       size_ = std::max<std::int64_t>(bytes, size_ + minBuffer);
148       buffer_ =
149           reinterpret_cast<char *>(AllocateMemoryOrCrash(terminator, size_));
150       auto chunk{std::min<std::int64_t>(length_, oldSize - start_)};
151       std::memcpy(buffer_, old + start_, chunk);
152       start_ = 0;
153       std::memcpy(buffer_ + chunk, old, length_ - chunk);
154       FreeMemory(old);
155     }
156   }
157 
Reset(FileOffset at)158   void Reset(FileOffset at) {
159     start_ = length_ = frame_ = 0;
160     fileOffset_ = at;
161     dirty_ = false;
162   }
163 
DiscardLeadingBytes(std::int64_t n,const Terminator & terminator)164   void DiscardLeadingBytes(std::int64_t n, const Terminator &terminator) {
165     RUNTIME_CHECK(terminator, length_ >= n);
166     length_ -= n;
167     if (length_ == 0) {
168       start_ = 0;
169     } else {
170       start_ += n;
171       if (start_ >= size_) {
172         start_ -= size_;
173       }
174     }
175     if (frame_ >= n) {
176       frame_ -= n;
177     } else {
178       frame_ = 0;
179     }
180     fileOffset_ += n;
181   }
182 
MakeDataContiguous(IoErrorHandler & handler,std::size_t bytes)183   void MakeDataContiguous(IoErrorHandler &handler, std::size_t bytes) {
184     if (static_cast<std::int64_t>(start_ + bytes) > size_) {
185       // Frame would wrap around; shift current data (if any) to force
186       // contiguity.
187       RUNTIME_CHECK(handler, length_ < size_);
188       if (start_ + length_ <= size_) {
189         // [......abcde..] -> [abcde........]
190         std::memmove(buffer_, buffer_ + start_, length_);
191       } else {
192         // [cde........ab] -> [abcde........]
193         auto n{start_ + length_ - size_}; // 3 for cde
194         RUNTIME_CHECK(handler, length_ >= n);
195         std::memmove(buffer_ + n, buffer_ + start_, length_ - n); // cdeab
196         LeftShiftBufferCircularly(buffer_, length_, n); // abcde
197       }
198       start_ = 0;
199     }
200   }
201 
202   char *buffer_{nullptr};
203   std::int64_t size_{0}; // current allocated buffer size
204   FileOffset fileOffset_{0}; // file offset corresponding to buffer valid data
205   std::int64_t start_{0}; // buffer_[] offset of valid data
206   std::int64_t length_{0}; // valid data length (can wrap)
207   std::int64_t frame_{0}; // offset of current frame in valid data
208   bool dirty_{false};
209 };
210 } // namespace Fortran::runtime::io
211 #endif // FORTRAN_RUNTIME_BUFFER_H_
212