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
21namespace Fortran::runtime::io {
22
23RT_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
49template <typename STORE, std::size_t minBuffer = 65536> class FileFrame {
50public:
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
145private:
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

source code of flang/runtime/buffer.h