1 //===-- runtime/buffer.h ----------------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
9 // External file buffering
11 #ifndef FORTRAN_RUNTIME_BUFFER_H_
12 #define FORTRAN_RUNTIME_BUFFER_H_
15 #include "flang/Runtime/freestanding-tools.h"
16 #include "flang/Runtime/memory.h"
21 namespace Fortran::runtime::io
{
23 RT_API_ATTRS
void LeftShiftBufferCircularly(
24 char *, std::size_t bytes
, std::size_t shift
);
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.
33 // Possible scenario with a tiny 32-byte buffer after a ReadFrame or
34 // WriteFrame with a file offset of 103 to access "DEF":
36 // fileOffset_ 100 --+ +-+ frame of interest (103:105)
37 // file: ............ABCDEFGHIJKLMNOPQRSTUVWXYZ....
38 // buffer: [NOPQRSTUVWXYZ......ABCDEFGHIJKLM] (size_ == 32)
40 // +----- start_ == 19, length_ == 26
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").
49 template <typename STORE
, std::size_t minBuffer
= 65536> class FileFrame
{
51 using FileOffset
= std::int64_t;
53 RT_API_ATTRS
~FileFrame() { FreeMemoryAndNullify(buffer_
); }
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.
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_
));
65 RT_API_ATTRS
std::size_t BytesBufferedBeforeFrame() const {
66 return frame_
- start_
;
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
) {
73 Reallocate(bytes
, handler
);
74 std::int64_t newFrame
{at
- fileOffset_
};
75 if (newFrame
< 0 || newFrame
> length_
) {
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_
);
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
)};
94 RUNTIME_CHECK(handler
, length_
<= size_
);
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_
) {
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
);
113 RUNTIME_CHECK(handler
, at
== fileOffset_
+ frame_
);
115 length_
= std::max
<std::int64_t>(length_
, frame_
+ bytes
);
118 RT_API_ATTRS
void Flush(IoErrorHandler
&handler
, std::int64_t keep
= 0) {
120 while (length_
> keep
) {
122 std::min
<std::size_t>(length_
- keep
, size_
- start_
)};
124 Store().Write(fileOffset_
, buffer_
+ start_
, chunk
, handler
)};
125 DiscardLeadingBytes(put
, handler
);
136 RT_API_ATTRS
void TruncateFrame(std::int64_t at
, IoErrorHandler
&handler
) {
137 RUNTIME_CHECK(handler
, !dirty_
);
138 if (at
<= fileOffset_
) {
140 } else if (at
< fileOffset_
+ length_
) {
141 length_
= at
- fileOffset_
;
146 RT_API_ATTRS STORE
&Store() { return static_cast<STORE
&>(*this); }
148 RT_API_ATTRS
void Reallocate(
149 std::int64_t bytes
, const Terminator
&terminator
) {
153 size_
= std::max
<std::int64_t>(bytes
, size_
+ minBuffer
);
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
160 if (old
!= nullptr) {
161 std::memcpy(buffer_
, old
+ start_
, chunk
);
162 std::memcpy(buffer_
+ chunk
, old
, length_
- chunk
);
169 RT_API_ATTRS
void Reset(FileOffset at
) {
170 start_
= length_
= frame_
= 0;
175 RT_API_ATTRS
void DiscardLeadingBytes(
176 std::int64_t n
, const Terminator
&terminator
) {
177 RUNTIME_CHECK(terminator
, length_
>= n
);
183 if (start_
>= size_
) {
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
200 RUNTIME_CHECK(handler
, length_
< size_
);
201 if (start_
+ length_
<= size_
) {
202 // [......abcde..] -> [abcde........]
203 runtime::memmove(buffer_
, buffer_
+ start_
, length_
);
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
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
223 } // namespace Fortran::runtime::io
224 #endif // FORTRAN_RUNTIME_BUFFER_H_