cachelib/navy/common/Buffer.h (142 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <folly/logging/xlog.h>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <ostream>
#include <stdexcept>
#include <string>
#include "cachelib/navy/common/Utils.h"
namespace facebook {
namespace cachelib {
namespace navy {
// View into a buffer. Doesn't own data. Caller must ensure buffer
// lifetime. We offer two versions:
// 1. read-only - BufferView
// 2. mutable - MutableBufferView
template <typename T>
class BufferViewT {
public:
using Type = T;
constexpr BufferViewT() : BufferViewT{0, nullptr} {}
// @param size Size of data in bytes
// @param data Pointer to the data this view will encapsulate
constexpr BufferViewT(size_t size, Type* data) : size_{size}, data_{data} {}
BufferViewT(const BufferViewT&) = default;
BufferViewT& operator=(const BufferViewT&) = default;
// Return true if data is nullptr
bool isNull() const { return data_ == nullptr; }
// Return byte at specified index. This view must NOT be null. Caller is
// responsible for ensuring the index is within bounds.
uint8_t byteAt(size_t idx) const {
XDCHECK(data_);
XDCHECK_LT(idx, size_);
return data_[idx];
}
// Return beginning of the data
Type* data() const { return data_; }
// Return end of the data
Type* dataEnd() const { return data_ + size_; }
// Return size of the view in bytes
size_t size() const { return size_; }
// @param dst Destination buffer into which to copy data from this view
void copyTo(void* dst) const {
if (data_ != nullptr) {
XDCHECK_NE(dst, nullptr);
std::memcpy(dst, data_, size_);
}
}
// Return a new view from part of this current view. This view must NOT be
// null.
// @param offset start of the data to encapsulate
// @param size size of bytes for the new view
BufferViewT slice(size_t offset, size_t size) const {
// Use - instead of + to avoid potential overflow problem
XDCHECK_LE(offset, size_);
XDCHECK_LE(size, size_ - offset);
return BufferViewT{size, data_ + offset};
}
// Two views are equal if their contents are identical
bool operator==(BufferViewT other) const {
return size_ == other.size_ &&
(size_ == 0 || std::memcmp(other.data_, data_, size_) == 0);
}
bool operator!=(BufferViewT other) const { return !(*this == other); }
private:
size_t size_{};
Type* data_{};
};
using BufferView = BufferViewT<const uint8_t>;
using MutableBufferView = BufferViewT<uint8_t>;
struct BufferDeleter {
void operator()(void* ptr) const { std::free(ptr); }
};
// Byte buffer. Manages buffer lifetime.
class Buffer {
public:
Buffer() = default;
// Copy data from a view
// @param view source of data to be copied from. Cannot be null.
explicit Buffer(BufferView view) : Buffer{view.size()} {
view.copyTo(data());
}
// Copy data from a view into an aligned buffer
// @param view source of data to be copied from. Cannot be null.
// @param alignment alignment of the buffer data.
Buffer(BufferView view, size_t alignment) : Buffer{view.size(), alignment} {
view.copyTo(data());
}
// Create a new, empty buffer
// @param size size of bytes of the buffer
explicit Buffer(size_t size) : size_{size}, data_{allocate(size)} {}
// Create a new, empty, and aligned buffer
// @param size size of bytes of the buffer, it must be a
// multiple of the alignment
// @param alignment alignment of the buffer
Buffer(size_t size, size_t alignment)
: size_{size}, data_{allocate(size, alignment)} {}
// Use @copy instead to make it explicit and visible
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
Buffer(Buffer&&) noexcept = default;
Buffer& operator=(Buffer&&) noexcept = default;
// Return a read-only view
BufferView view() const { return BufferView{size_, data()}; }
// Return a mutable view
MutableBufferView mutableView() { return MutableBufferView{size_, data()}; }
// Return true if data is nullptr
bool isNull() const { return data_ == nullptr; }
// Return read-only start of the data
const uint8_t* data() const { return data_.get() + dataStartOffset_; }
// Return mutable start of the data
uint8_t* data() { return data_.get() + dataStartOffset_; }
// Return size in bytes for the data
size_t size() const { return size_; }
// Copy copies size_ number of bytes from dataOffsetStart_ to a new buffer
// and returns the new buffer
// @param alignment alignment of the new buffer
Buffer copy(size_t alignment = 0) const {
return (alignment == 0) ? copyInternal(Buffer{size_})
: copyInternal(Buffer{size_, alignment});
}
// This buffer must NOT be null and it must have sufficient capacity
// to copy the data from the source view.
// @param offset copy from the offset for the view until the end
// @param view source of the data to be copied from
void copyFrom(size_t offset, BufferView view) {
XDCHECK_LE(offset + view.size(), size_);
XDCHECK_NE(data_, nullptr);
if (view.data() != nullptr) {
std::memcpy(data() + offset, view.data(), view.size());
}
}
// Adjust the data start offset forwards to include less valid data
// This moves the data pointer forwards so that the first amount bytes are no
// longer considered valid data. The caller is responsible for ensuring that
// amount is less than or equal to the actual data length.
//
// This does not modify any actual data in the buffer.
void trimStart(size_t amount) {
XDCHECK_LE(amount, size_);
dataStartOffset_ += amount;
size_ -= amount;
}
// Shrink buffer logical size (doesn't reallocate)
void shrink(size_t size) {
XDCHECK_LE(size, size_);
size_ = size;
}
// Clear the buffer
void reset() {
size_ = 0;
data_.reset();
}
private:
Buffer copyInternal(Buffer buf) const {
if (data_) {
XDCHECK_NE(buf.data_, nullptr);
std::memcpy(buf.data(), data(), size_);
}
return buf;
}
static uint8_t* allocate(size_t size) {
auto ptr = reinterpret_cast<uint8_t*>(std::malloc(size));
if (!ptr) {
throw std::bad_alloc();
}
return ptr;
}
static uint8_t* allocate(size_t size, size_t alignment) {
XDCHECK(folly::isPowTwo(alignment)); // Also ensures @alignment > 0
XDCHECK_EQ(size % alignment, 0u);
auto ptr = reinterpret_cast<uint8_t*>(::aligned_alloc(alignment, size));
if (!ptr) {
throw std::bad_alloc();
}
return ptr;
}
// size_ represents the size of valid data in the data_, i.e., "size_" number
// of bytes from startOffset in data_ are considered valid in the Buffer
size_t size_{};
// dataStartOffset_ is the offset in data_ where the actual(user-interested)
// data starts. This helps in skipping past unnecessary data in the buffer
// without having to copy it. There could be unnecessary data in the buffer
// due to read/write from/to a block-aligned address when the actual data
// starts somewhere in the middle(ie not at the block aligned address).
size_t dataStartOffset_{0};
std::unique_ptr<uint8_t[], BufferDeleter> data_{};
};
inline BufferView toView(MutableBufferView mutableView) {
return {mutableView.size(), mutableView.data()};
}
// Trailing 0 is not included
inline BufferView makeView(const char* cstr) {
return {std::strlen(cstr), reinterpret_cast<const uint8_t*>(cstr)};
}
// Convert to string suitable for debug prints the best
std::string toString(BufferView view, bool compact = true);
// For better interaction with gtest
inline std::ostream& operator<<(std::ostream& os, BufferView view) {
return os << toString(view);
}
} // namespace navy
} // namespace cachelib
} // namespace facebook