You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

607 lines
19 KiB

/*
* Copyright 2002-2019 Intel Corporation.
*
* This software and the related documents are Intel copyrighted materials, and your
* use of them is governed by the express license under which they were provided to
* you ("License"). Unless the License provides otherwise, you may not use, modify,
* copy, publish, distribute, disclose or transmit this software or the related
* documents without Intel's prior written permission.
*
* This software and the related documents are provided as is, with no express or
* implied warranties, other than those that are expressly stated in the License.
*/
// <COMPONENT>: util
// <FILE-TYPE>: component public header
#ifndef UTIL_DATA_HPP
#define UTIL_DATA_HPP
#include <string>
#include <cstring>
#include "types.h"
namespace UTIL {
/*!
* Utility that holds a raw data buffer. The internal implementation uses reference
* counting, so the various copy and "slice" operations are fast.
*
* None of the operations are thread safe, so the caller must provide any necessary
* synchronization. Since the implementation uses reference counting, two distinct
* DATA objects may actually share a buffer and require mutual synchronization. To
* avoid this, use DATA::MakeUnique() if necessary.
*/
class /*<UTILITY>*/ DATA
{
public:
/*!
* This is used when constructing a DATA that is a copy of some sort of input buffer.
*/
enum COPY
{
/*!
* The input buffer is copied immediately.
*/
COPY_EAGER,
/*!
* The input buffer is copied only if the DATA is modified. However, the input
* buffer must remain valid and must not be modified until one of the following
* occurs:
*
* - The DATA is destroyed.
* - The Assign() method or "=" (the assignment operator) reconstructs the DATA.
* - A writable buffer is obtained via GetWritableBuf().
*/
COPY_LAZY
};
/*!
* Tells what to do when a DATA buffer is allocated and no initial value is specified.
*/
enum FILL
{
FILL_UNSPECIFIED, ///< Leave the buffer unspecified.
FILL_ZERO ///< Zero fill the buffer.
};
public:
/*!
* Construct a new empty buffer.
*/
DATA() : _sbuf(0), _start(0), _size(0) {}
/*!
* Construct a new buffer.
*
* @param[in] size Size (bytes) of the buffer.
* @param[in] fill Tells whether the initial contents are zero-filled or left unspecified.
*/
DATA(size_t size, FILL fill = FILL_UNSPECIFIED)
:
_sbuf(new SHARED_BUF(size)),
_start(_sbuf->_buf),
_size(size)
{
if (fill == FILL_ZERO)
std::memset(_start, 0, _size);
}
/*!
* Construct a new buffer that is a copy of some existing data.
*
* @param[in] buf Points to the data to copy.
* @param[in] size Size (bytes) of data in \a buf.
* @param[in] copy Tells whether \a buf is copied eagerly or lazily.
*/
DATA(const void *buf, size_t size, COPY copy = COPY_EAGER)
{
CopyFromBuffer(buf, size, copy);
}
/*!
* Construct a new buffer that is a copy of a C string (not including its terminating NUL).
*
* @param[in] str NUL-terminated string.
* @param[in] copy Tells whether \a str is copied eagerly or lazily.
*/
DATA(const char *str, COPY copy = COPY_EAGER)
{
CopyFromBuffer(str, std::strlen(str), copy);
}
/*!
* Construct a new buffer that is a copy of an C++ string.
*
* @param[in] str The string.
* @param[in] copy Tells whether \a str is copied eagerly or lazily.
*/
DATA(const std::string &str, COPY copy = COPY_EAGER)
{
CopyFromBuffer(str.c_str(), str.size(), copy);
}
/*!
* Construct a new buffer that is a copy of a subrange of an existing buffer.
*
* @param[in] other The new buffer is a copy of the contents of \a other.
* @param[in] off The new buffer starts at \a off bytes from the start of \a other.
* If \a off is larger than \a other, the new buffer is empty.
*/
DATA(const DATA &other, size_t off=0)
{
CopyFromData(other, off);
}
/*!
* Construct a new buffer that is a copy of a subrange of an existing buffer.
*
* @param[in] other The new buffer is a copy of the contents of \a other.
* @param[in] off The new buffer starts at \a off bytes from the start of \a other.
* If \a off is larger than \a other, the new buffer is empty.
* @param[in] len The new buffer is at most \a len bytes long. If \a off + \a len
* is greater than the length of \a other, the new buffer is a copy
* of the data up to the end of \a other.
*/
DATA(const DATA &other, size_t off, size_t len)
{
CopyFromDataWithLen(other, off, len);
}
~DATA() {DetachBuf();}
/*!
* Reconstruct the buffer to be a copy of another buffer.
*
* @param[in] other The contents of this buffer are copied.
*
* @return Reference to the new data buffer.
*/
DATA &operator =(const DATA &other)
{
Assign(other);
return *this;
}
/*!
* Reconstruct the buffer to a new size.
*
* @param[in] size Size (bytes) of the buffer.
* @param[in] fill Tells whether the initial contents are zero-filled or left unspecified.
*/
void Assign(size_t size, FILL fill = FILL_UNSPECIFIED)
{
DetachBuf();
_sbuf = new SHARED_BUF(size);
_start = _sbuf->_buf;
_size = size;
if (fill == FILL_ZERO)
std::memset(_start, 0, _size);
}
/*!
* Reconstruct the buffer to be a copy of some existing data.
*
* @param[in] buf Points to the data to copy.
* @param[in] size Size (bytes) of data in \a buf.
* @param[in] copy Tells whether \a buf is copied eagerly or lazily.
*/
void Assign(const void *buf, size_t size, COPY copy = COPY_EAGER)
{
DetachBuf();
CopyFromBuffer(buf, size, copy);
}
/*!
* Reconstruct the buffer to be a copy of a C string (not including its terminating NUL).
*
* @param[in] str NUL-terminated string.
* @param[in] copy Tells whether \a str is copied eagerly or lazily.
*/
void Assign(const char *str, COPY copy = COPY_EAGER)
{
DetachBuf();
CopyFromBuffer(str, std::strlen(str), copy);
}
/*!
* Reconstruct the buffer to be a copy of an C++ string.
*
* @param[in] str The string.
* @param[in] copy Tells whether \a str is copied eagerly or lazily.
*/
void Assign(const std::string &str, COPY copy = COPY_EAGER)
{
DetachBuf();
CopyFromBuffer(str.c_str(), str.size(), copy);
}
/*!
* Reconstruct the buffer to be a copy of a subrange of an existing buffer.
*
* @param[in] other The contents of this buffer are copied.
* @param[in] off This buffer starts at \a off bytes from the start of \a other.
* If \a off is larger than \a other, the new buffer is empty.
*/
void Assign(const DATA &other, size_t off=0)
{
if (this != &other)
{
DetachBuf();
CopyFromData(other, off);
}
else
{
PopFront(off);
}
}
/*!
* Reconstruct the buffer to be a copy of a subrange of an existing buffer.
*
* @param[in] other The contents of this buffer are a copy of the contents of \a other.
* @param[in] off This buffer starts at \a off bytes from the start of \a other.
* If \a off is larger than \a other, the new buffer is empty.
* @param[in] len This buffer is at most \a len bytes long. If \a off + \a len
* is greater than the length of \a other, the new buffer is a copy
* of the data up to the end of \a other.
*/
void Assign(const DATA &other, size_t off, size_t len)
{
if (this != &other)
{
DetachBuf();
CopyFromDataWithLen(other, off, len);
}
else
{
PopFront(off);
if (len < _size)
_size = len;
}
}
/*!
* Clear the content of the buffer, making it empty.
*/
void Clear()
{
DetachBuf();
_sbuf = 0;
_start = 0;
_size = 0;
}
/*!
* Remove initial bytes from the start of the buffer, making it shorter.
*
* @param[in] num This many bytes are removed from the buffer. If \a num
* is larger than the length of the buffer, the buffer becomes empty.
*/
void PopFront(size_t num)
{
if (num >= _size)
{
DetachBuf();
_sbuf = 0;
_start = 0;
_size = 0;
return;
}
_start = static_cast<UINT8 *>(_start) + num;
_size -= num;
}
/*!
* Remove trailing bytes from the end of the buffer, making it shorter.
*
* @param[in] num This many bytes are removed from the buffer. If \a num
* is larger than the length of the buffer, the buffer becomes empty.
*/
void PopBack(size_t num)
{
if (num >= _size)
{
DetachBuf();
_sbuf = 0;
_start = 0;
_size = 0;
return;
}
_size -= num;
}
/*!
* Change the size of the buffer, retaining it's current content. If the new size is
* smaller than the previous size, trailing bytes in the buffer are lost. If the new
* size is greater than the previous size, the \a fill parameter tells whether the new
* buffer space is left unspecified or is zero-filled.
*
* @param[in] newSize The new buffer size (bytes).
* @param[in] fill Tells what to do with the new buffer space if \a newSize is
* greater than the existing buffer size.
*/
void Resize(size_t newSize, FILL fill = FILL_UNSPECIFIED)
{
if (newSize <= _size)
{
if (!newSize)
{
DetachBuf();
_sbuf = 0;
_start = 0;
_size = 0;
return;
}
_size = newSize;
}
else
{
// If we grow, we must re-allocate the input buffer, so we may as well copy
// any "lazy" buffer too. Note that we do NOT want to share the copied buffer
// with any other DATA because doing so would invalidate any GetBuf() pointers
// in the other DATA. Therefore, any other DATA's continue to use the old buffer.
//
SHARED_BUF *sbuf = new SHARED_BUF(newSize);
std::memcpy(sbuf->_buf, _start, _size);
if (fill == FILL_ZERO)
std::memset(static_cast<UINT8 *>(sbuf->_buf) + _size, 0, newSize - _size);
DetachBuf();
_sbuf = sbuf;
_start = sbuf->_buf;
_size = newSize;
}
}
/*!
* Calling this function ensures that the buffer does not share any data with any other
* DATA object, copying any shared buffer if necessary. This could be useful, for example,
* to ensure that two DATA objects can be safely used by different threads.
*/
void MakeUnique()
{
// If no other DATA's share our buffer, there's nothing to do.
//
if (!_sbuf || _sbuf->_refCount == 1 || _sbuf->_refCount == EXCLUSIVE)
return;
// Since we need to copy the buffer anyways, this also copies any "lazy" buffer.
//
_sbuf->_refCount--;
_sbuf = new SHARED_BUF(_size);
std::memcpy(_sbuf->_buf, _start, _size);
_start = _sbuf->_buf;
}
/*!
* @return Size (bytes) of the buffer.
*/
size_t GetSize() const {return _size;}
/*!
* @return A pointer to the buffer's data. The returned pointer remains valid only until
* the next operation that modifies the DATA.
*/
template<typename T> const T *GetBuf() const
{
return static_cast<const T *>(_start);
}
/*!
* Get a writable pointer to the buffer's data. This is considered a modifying operation, so
* pointers returned by previous calls to GetBuf() are no longer valid. When you are
* finished with the pointer, call ReleaseWritableBuf() to release it.
*
* @return A pointer to the buffer's data. The returned pointer remains valid only until
* the next operation that modifies the DATA or until ReleaseWritableBuf() is
* called.
*/
template<typename T> T *GetWritableBuf()
{
// Since the caller could use this pointer to change the contents of the buffer,
// we must make sure that doing so won't change the contents of any other DATA's
// buffer.
//
if (_sbuf && _sbuf->_refCount != EXCLUSIVE)
{
// We must make a private copy if there are other DATA's sharing this buffer.
// This also copies any "lazy" buffer.
//
if (_sbuf->_refCount > 1)
{
_sbuf->_refCount--;
_sbuf = new SHARED_BUF(_size);
std::memcpy(_sbuf->_buf, _start, _size);
_start = _sbuf->_buf;
}
// Even if we're the only DATA that references this buffer, it can't be
// a "lazy" buffer.
//
else if (_sbuf->_isLazy)
{
UINT8 *buf = new UINT8[_size];
std::memcpy(buf, _start, _size);
_sbuf->_buf = buf;
_sbuf->_size = _size;
_sbuf->_isLazy = 0;
_start = buf;
}
// Mark the buffer as exclusively ours. This prevents any future DATA from
// sharing our buffer while there is a writable pointer to it.
//
_sbuf->_refCount = EXCLUSIVE;
}
return static_cast<T *>(_start);
}
/*!
* Release all previously obtained writable pointers obtained via GetWritableBuf().
*/
void ReleaseWritableBuf()
{
if (_sbuf && _sbuf->_refCount == EXCLUSIVE)
_sbuf->_refCount = 1;
}
private:
/*!
* Construct this DATA by copying an external buffer.
*
* @param[in] buf Points to the data to copy.
* @param[in] size Size (bytes) of data in \a buf.
* @param[in] copy Tells whether \a buf is copied eagerly or lazily.
*/
void CopyFromBuffer(const void *buf, size_t size, COPY copy)
{
if (copy == COPY_EAGER)
{
_sbuf = new SHARED_BUF(size);
std::memcpy(_sbuf->_buf, buf, size);
}
else
{
_sbuf = new SHARED_BUF(buf, size);
}
_start = _sbuf->_buf;
_size = size;
}
/*!
* Construct this DATA by copying a slice of another DATA.
*
* @param[in] other The new buffer is a copy of the contents of \a other.
* @param[in] off The new buffer starts at \a off bytes from the start of \a other.
* If \a off is larger than \a other, the new buffer is empty.
*/
void CopyFromData(const DATA &other, size_t off)
{
// Check for a zero-length buffer.
//
if (off >= other._size)
{
_sbuf = 0;
_start = 0;
_size = 0;
return;
}
// If 'other' has an outstanding writable pointer, we can't share its buffer because
// we don't want changes to 'other' to affect us.
//
if (other._sbuf->_refCount == EXCLUSIVE)
{
_size = other._size - off;
_sbuf = new SHARED_BUF(_size);
std::memcpy(_sbuf->_buf, static_cast<UINT8 *>(other._start) + off, _size);
_start = _sbuf->_buf;
return;
}
// Otherwise, we can just share the buffer.
//
_sbuf = other._sbuf;
_sbuf->_refCount++;
_start = static_cast<UINT8 *>(other._start) + off;
_size = other._size - off;
}
/*!
* Construct this DATA by copying a slice of another DATA.
*
* @param[in] other The new buffer is a copy of the contents of \a other.
* @param[in] off The new buffer starts at \a off bytes from the start of \a other.
* If \a off is larger than \a other, the new buffer is empty.
* @param[in] len The new buffer is at most \a len bytes long. If \a off + \a len
* is greater than the length of \a other, the new buffer is a copy
* of the data up to the end of \a other.
*/
void CopyFromDataWithLen(const DATA &other, size_t off, size_t len)
{
// Check for a zero-length buffer.
//
if (off >= other._size)
{
_sbuf = 0;
_start = 0;
_size = 0;
return;
}
if (len > other._size - off)
_size = other._size - off;
else
_size = len;
// If 'other' has an outstanding writable pointer, we can't share its buffer because
// we don't want changes to 'other' to affect us.
//
if (other._sbuf->_refCount == EXCLUSIVE)
{
_sbuf = new SHARED_BUF(_size);
std::memcpy(_sbuf->_buf, static_cast<UINT8 *>(other._start) + off, _size);
_start = _sbuf->_buf;
return;
}
// Otherwise, we can just share the buffer.
//
_sbuf = other._sbuf;
_sbuf->_refCount++;
_start = static_cast<UINT8 *>(other._start) + off;
}
/*!
* If there is an attached SHARED_BUF, detach it and remove it if there are no other
* owners.
*/
void DetachBuf()
{
if (_sbuf && ((_sbuf->_refCount == EXCLUSIVE) || (--(_sbuf->_refCount) == 0)))
{
if (_sbuf->_isLazy == 0)
delete [] static_cast<UINT8 *>(_sbuf->_buf);
delete _sbuf;
}
}
private:
static const UINT32 EXCLUSIVE = UINT32(0x7fffffff);
// This is potentially shared by many DATA instances.
//
struct SHARED_BUF
{
// Constructor for an allocated buffer.
//
SHARED_BUF(size_t sz) : _refCount(1), _isLazy(0), _size(sz), _buf(new UINT8[_size]) {}
// Constructor for an lazy-copied buffer.
//
SHARED_BUF(const void *buf, size_t sz) : _refCount(1), _isLazy(1), _size(sz), _buf(const_cast<void *>(buf)) {}
UINT32 _refCount:31; // Number of DATA's pointing to this SHARED_BUF, or EXCLUSIVE.
UINT32 _isLazy:1; // Tells if '_buf' is lazy-copied or allocated.
size_t _size; // Size of '_buf'.
// If '_isLazy' is TRUE, this points to a lazy-copied input buffer and must be treated as "const".
// If '_isLazy' is FALSE, this points to a "new[]" allocated buffer.
//
void *_buf;
};
SHARED_BUF *_sbuf;
void *_start; // Start of my instance's data in _buf (treated as "const" if '_isLazy' is TRUE).
size_t _size; // Size of my instance's data
};
} // namespace
#endif // file guard