xref: /llvm-project/flang/runtime/buffer.h (revision 3b337242ee165554f0017b00671381ec5b1ba855)
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