forked from OSchip/llvm-project
203 lines
7.0 KiB
C++
203 lines
7.0 KiB
C++
//===-- runtime/buffer.h ----------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// External file buffering
|
|
|
|
#ifndef FORTRAN_RUNTIME_BUFFER_H_
|
|
#define FORTRAN_RUNTIME_BUFFER_H_
|
|
|
|
#include "io-error.h"
|
|
#include "memory.h"
|
|
#include <algorithm>
|
|
#include <cinttypes>
|
|
#include <cstring>
|
|
|
|
namespace Fortran::runtime::io {
|
|
|
|
void LeftShiftBufferCircularly(char *, std::size_t bytes, std::size_t shift);
|
|
|
|
// Maintains a view of a contiguous region of a file in a memory buffer.
|
|
// The valid data in the buffer may be circular, but any active frame
|
|
// will also be contiguous in memory. The requirement stems from the need to
|
|
// preserve read data that may be reused by means of Tn/TLn edit descriptors
|
|
// without needing to position the file (which may not always be possible,
|
|
// e.g. a socket) and a general desire to reduce system call counts.
|
|
//
|
|
// Possible scenario with a tiny 32-byte buffer after a ReadFrame or
|
|
// WriteFrame with a file offset of 103 to access "DEF":
|
|
//
|
|
// fileOffset_ 100 --+ +-+ frame of interest (103:105)
|
|
// file: ............ABCDEFGHIJKLMNOPQRSTUVWXYZ....
|
|
// buffer: [NOPQRSTUVWXYZ......ABCDEFGHIJKLM] (size_ == 32)
|
|
// | +-- frame_ == 3
|
|
// +----- start_ == 19, length_ == 26
|
|
//
|
|
// The buffer holds length_ == 26 bytes from file offsets 100:125.
|
|
// Those 26 bytes "wrap around" the end of the circular buffer,
|
|
// so file offsets 100:112 map to buffer offsets 19:31 ("A..M") and
|
|
// file offsets 113:125 map to buffer offsets 0:12 ("N..Z")
|
|
// The 3-byte frame of file offsets 103:105 is contiguous in the buffer
|
|
// at buffer offset (start_ + frame_) == 22 ("DEF").
|
|
|
|
template <typename STORE, std::size_t minBuffer = 65536> class FileFrame {
|
|
public:
|
|
using FileOffset = std::int64_t;
|
|
|
|
~FileFrame() { FreeMemoryAndNullify(buffer_); }
|
|
|
|
// The valid data in the buffer begins at buffer_[start_] and proceeds
|
|
// with possible wrap-around for length_ bytes. The current frame
|
|
// is offset by frame_ bytes into that region and is guaranteed to
|
|
// be contiguous for at least as many bytes as were requested.
|
|
|
|
FileOffset FrameAt() const { return fileOffset_ + frame_; }
|
|
char *Frame() const { return buffer_ + start_ + frame_; }
|
|
std::size_t FrameLength() const {
|
|
return std::min<std::size_t>(length_ - frame_, size_ - (start_ + frame_));
|
|
}
|
|
std::size_t BytesBufferedBeforeFrame() const { return frame_ - start_; }
|
|
|
|
// Returns a short frame at a non-fatal EOF. Can return a long frame as well.
|
|
std::size_t ReadFrame(
|
|
FileOffset at, std::size_t bytes, IoErrorHandler &handler) {
|
|
Flush(handler);
|
|
Reallocate(bytes, handler);
|
|
std::int64_t newFrame{at - fileOffset_};
|
|
if (newFrame < 0 || newFrame > length_) {
|
|
Reset(at);
|
|
} else {
|
|
frame_ = newFrame;
|
|
}
|
|
RUNTIME_CHECK(handler, at == fileOffset_ + frame_);
|
|
if (static_cast<std::int64_t>(start_ + frame_ + bytes) > size_) {
|
|
DiscardLeadingBytes(frame_, handler);
|
|
MakeDataContiguous(handler, bytes);
|
|
RUNTIME_CHECK(handler, at == fileOffset_ + frame_);
|
|
}
|
|
if (FrameLength() < bytes) {
|
|
auto next{start_ + length_};
|
|
RUNTIME_CHECK(handler, next < size_);
|
|
auto minBytes{bytes - FrameLength()};
|
|
auto maxBytes{size_ - next};
|
|
auto got{Store().Read(
|
|
fileOffset_ + length_, buffer_ + next, minBytes, maxBytes, handler)};
|
|
length_ += got;
|
|
RUNTIME_CHECK(handler, length_ <= size_);
|
|
}
|
|
return FrameLength();
|
|
}
|
|
|
|
void WriteFrame(FileOffset at, std::size_t bytes, IoErrorHandler &handler) {
|
|
Reallocate(bytes, handler);
|
|
std::int64_t newFrame{at - fileOffset_};
|
|
if (!dirty_ || newFrame < 0 || newFrame > length_) {
|
|
Flush(handler);
|
|
Reset(at);
|
|
} else if (start_ + newFrame + static_cast<std::int64_t>(bytes) > size_) {
|
|
// Flush leading data before "at", retain from "at" onward
|
|
Flush(handler, length_ - newFrame);
|
|
MakeDataContiguous(handler, bytes);
|
|
} else {
|
|
frame_ = newFrame;
|
|
}
|
|
RUNTIME_CHECK(handler, at == fileOffset_ + frame_);
|
|
dirty_ = true;
|
|
length_ = std::max<std::int64_t>(length_, frame_ + bytes);
|
|
}
|
|
|
|
void Flush(IoErrorHandler &handler, std::int64_t keep = 0) {
|
|
if (dirty_) {
|
|
while (length_ > keep) {
|
|
std::size_t chunk{
|
|
std::min<std::size_t>(length_ - keep, size_ - start_)};
|
|
std::size_t put{
|
|
Store().Write(fileOffset_, buffer_ + start_, chunk, handler)};
|
|
DiscardLeadingBytes(put, handler);
|
|
if (put < chunk) {
|
|
break;
|
|
}
|
|
}
|
|
if (length_ == 0) {
|
|
Reset(fileOffset_);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
STORE &Store() { return static_cast<STORE &>(*this); }
|
|
|
|
void Reallocate(std::int64_t bytes, const Terminator &terminator) {
|
|
if (bytes > size_) {
|
|
char *old{buffer_};
|
|
auto oldSize{size_};
|
|
size_ = std::max<std::int64_t>(bytes, minBuffer);
|
|
buffer_ =
|
|
reinterpret_cast<char *>(AllocateMemoryOrCrash(terminator, size_));
|
|
auto chunk{std::min<std::int64_t>(length_, oldSize - start_)};
|
|
std::memcpy(buffer_, old + start_, chunk);
|
|
start_ = 0;
|
|
std::memcpy(buffer_ + chunk, old, length_ - chunk);
|
|
FreeMemory(old);
|
|
}
|
|
}
|
|
|
|
void Reset(FileOffset at) {
|
|
start_ = length_ = frame_ = 0;
|
|
fileOffset_ = at;
|
|
dirty_ = false;
|
|
}
|
|
|
|
void DiscardLeadingBytes(std::int64_t n, const Terminator &terminator) {
|
|
RUNTIME_CHECK(terminator, length_ >= n);
|
|
length_ -= n;
|
|
if (length_ == 0) {
|
|
start_ = 0;
|
|
} else {
|
|
start_ += n;
|
|
if (start_ >= size_) {
|
|
start_ -= size_;
|
|
}
|
|
}
|
|
if (frame_ >= n) {
|
|
frame_ -= n;
|
|
} else {
|
|
frame_ = 0;
|
|
}
|
|
fileOffset_ += n;
|
|
}
|
|
|
|
void MakeDataContiguous(IoErrorHandler &handler, std::size_t bytes) {
|
|
if (static_cast<std::int64_t>(start_ + bytes) > size_) {
|
|
// Frame would wrap around; shift current data (if any) to force
|
|
// contiguity.
|
|
RUNTIME_CHECK(handler, length_ < size_);
|
|
if (start_ + length_ <= size_) {
|
|
// [......abcde..] -> [abcde........]
|
|
std::memmove(buffer_, buffer_ + start_, length_);
|
|
} else {
|
|
// [cde........ab] -> [abcde........]
|
|
auto n{start_ + length_ - size_}; // 3 for cde
|
|
RUNTIME_CHECK(handler, length_ >= n);
|
|
std::memmove(buffer_ + n, buffer_ + start_, length_ - n); // cdeab
|
|
LeftShiftBufferCircularly(buffer_, length_, n); // abcde
|
|
}
|
|
start_ = 0;
|
|
}
|
|
}
|
|
|
|
char *buffer_{nullptr};
|
|
std::int64_t size_{0}; // current allocated buffer size
|
|
FileOffset fileOffset_{0}; // file offset corresponding to buffer valid data
|
|
std::int64_t start_{0}; // buffer_[] offset of valid data
|
|
std::int64_t length_{0}; // valid data length (can wrap)
|
|
std::int64_t frame_{0}; // offset of current frame in valid data
|
|
bool dirty_{false};
|
|
};
|
|
} // namespace Fortran::runtime::io
|
|
#endif // FORTRAN_RUNTIME_BUFFER_H_
|