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 | |
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 | |
60 | RT_API_ATTRS FileOffset FrameAt() const { return fileOffset_ + frame_; } |
61 | RT_API_ATTRS char *Frame() const { return buffer_ + start_ + frame_; } |
62 | RT_API_ATTRS std::size_t FrameLength() const { |
63 | return std::min<std::size_t>(length_ - frame_, size_ - (start_ + frame_)); |
64 | } |
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. |
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 | |
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 | |
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: |
146 | RT_API_ATTRS STORE &Store() { return static_cast<STORE &>(*this); } |
147 | |
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 | |
169 | RT_API_ATTRS void Reset(FileOffset at) { |
170 | start_ = length_ = frame_ = 0; |
171 | fileOffset_ = at; |
172 | dirty_ = false; |
173 | } |
174 | |
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 | |
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 | |