Implement <xml_memory_pool> concept similar to C++17's std::memory_resource. Allows construction of pugi::xml_document objects using preallocated memory buffers. By default serves memory requests via standard memory (de)alocation functions.

This commit is contained in:
Ambal 2019-12-23 19:42:03 +02:00
parent 41b6ff21c4
commit 694659e286
3 changed files with 493 additions and 96 deletions

View File

@ -191,6 +191,8 @@ PUGI__NS_BEGIN
free(ptr);
}
void* aligned_allocate(xml_memory_pool* pool, size_t size);
template <typename T>
struct xml_memory_management_function_storage
{
@ -282,14 +284,56 @@ PUGI__NS_BEGIN
return result;
}
};
template<> struct auto_deleter<void>
{
xml_memory_pool* pool;
void* data;
auto_deleter(xml_memory_pool* pool_, size_t size) : pool(pool_), data(impl::aligned_allocate(pool_, size))
{
}
~auto_deleter()
{
if (data) pool->deallocate(data);
}
void* release()
{
void* result = data;
data = 0;
return result;
}
};
PUGI__NS_END
// standard memory pool using global allocate/deallocate functions
PUGI__NS_BEGIN
class xml_global_memory_pool : public xml_memory_pool
{
public:
virtual void* allocate(size_t size) PUGIXML_OVERRIDE
{
return xml_memory::allocate(size);
}
virtual void deallocate(void* ptr) PUGIXML_OVERRIDE
{
return xml_memory::deallocate(ptr);
}
};
PUGI__NS_END
#ifdef PUGIXML_COMPACT
PUGI__NS_BEGIN
class compact_hash_table
{
public:
compact_hash_table(): _items(0), _capacity(0), _count(0)
explicit compact_hash_table(xml_memory_pool* pool): _pool(pool), _items(0), _capacity(0), _count(0)
{
}
@ -297,7 +341,7 @@ PUGI__NS_BEGIN
{
if (_items)
{
xml_memory::deallocate(_items);
_pool->deallocate(_items);
_items = 0;
_capacity = 0;
_count = 0;
@ -346,6 +390,7 @@ PUGI__NS_BEGIN
void* value;
};
xml_memory_pool* _pool;
item_t* _items;
size_t _capacity;
@ -397,9 +442,9 @@ PUGI__NS_BEGIN
while (count >= capacity - capacity / 4)
capacity *= 2;
compact_hash_table rt;
compact_hash_table rt(_pool);
rt._capacity = capacity;
rt._items = static_cast<item_t*>(xml_memory::allocate(sizeof(item_t) * capacity));
rt._items = static_cast<item_t*>(impl::aligned_allocate(_pool, sizeof(item_t) * capacity));
if (!rt._items)
return false;
@ -411,7 +456,7 @@ PUGI__NS_BEGIN
rt.insert(_items[i].key, _items[i].value);
if (_items)
xml_memory::deallocate(_items);
_pool->deallocate(_items);
_capacity = capacity;
_items = rt._items;
@ -453,6 +498,24 @@ PUGI__NS_BEGIN
#define PUGI__GETPAGE(n) PUGI__GETPAGE_IMPL((n)->header)
#define PUGI__NODETYPE(n) static_cast<xml_node_type>((n)->header & impl::xml_memory_page_type_mask)
static xml_global_memory_pool xml_default_memory_pool;
PUGI__FN xml_memory_pool* get_default_memory_pool() PUGIXML_NOEXCEPT
{
return &xml_default_memory_pool;
}
PUGI__FN size_t xml_calculate_aligned_size(size_t size) PUGIXML_NOEXCEPT
{
// round size up to block alignment boundary
return (size + (xml_memory_block_alignment - 1)) & ~(xml_memory_block_alignment - 1);
}
PUGI__FN void* aligned_allocate(xml_memory_pool* pool, size_t size)
{
return pool->allocate(xml_calculate_aligned_size(size));
}
struct xml_allocator;
struct xml_memory_page
@ -507,7 +570,7 @@ PUGI__NS_BEGIN
struct xml_allocator
{
xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size)
xml_allocator(xml_memory_pool* pool, xml_memory_page* root): _pool(pool), _root(root), _busy_size(root->busy_size)
{
#ifdef PUGIXML_COMPACT
_hash = 0;
@ -519,7 +582,7 @@ PUGI__NS_BEGIN
size_t size = sizeof(xml_memory_page) + data_size;
// allocate block with some alignment, leaving memory for worst-case padding
void* memory = xml_memory::allocate(size);
void* memory = _pool->allocate(size);
if (!memory) return 0;
// prepare page structure
@ -531,9 +594,9 @@ PUGI__NS_BEGIN
return page;
}
static void deallocate_page(xml_memory_page* page)
void deallocate_page(xml_memory_page* page)
{
xml_memory::deallocate(page);
_pool->deallocate(page);
}
void* allocate_memory_oob(size_t size, xml_memory_page*& out_page);
@ -644,7 +707,7 @@ PUGI__NS_BEGIN
size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t);
// round size up to block alignment boundary
size_t full_size = (size + (xml_memory_block_alignment - 1)) & ~(xml_memory_block_alignment - 1);
size_t full_size = xml_calculate_aligned_size(size);
xml_memory_page* page;
xml_memory_string_header* header = static_cast<xml_memory_string_header*>(allocate_memory(full_size, page));
@ -696,6 +759,7 @@ PUGI__NS_BEGIN
#endif
}
xml_memory_pool* _pool;
xml_memory_page* _root;
size_t _busy_size;
@ -1140,10 +1204,22 @@ PUGI__NS_BEGIN
struct xml_document_struct: public xml_node_struct, public xml_allocator
{
xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0)
xml_document_struct(xml_memory_pool* pool, xml_memory_page* page)
: xml_node_struct(page, node_document)
, xml_allocator(pool, page)
, buffer(0)
, extra_buffers(0)
#ifdef PUGIXML_COMPACT
, hash(pool)
#endif
{
}
xml_memory_pool* get_memory_pool() PUGIXML_NOEXCEPT
{
return this->xml_allocator::_pool;
}
const char_t* buffer;
xml_extra_buffer* extra_buffers;
@ -2037,7 +2113,7 @@ PUGI__NS_BEGIN
return guess_buffer_encoding(data, size);
}
PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{
size_t length = size / sizeof(char_t);
@ -2048,7 +2124,7 @@ PUGI__NS_BEGIN
}
else
{
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false;
if (contents)
@ -2072,7 +2148,7 @@ PUGI__NS_BEGIN
(le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be);
}
PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{
const char_t* data = static_cast<const char_t*>(contents);
size_t length = size / sizeof(char_t);
@ -2088,7 +2164,7 @@ PUGI__NS_BEGIN
}
else
{
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false;
convert_wchar_endian_swap(buffer, data, length);
@ -2101,7 +2177,7 @@ PUGI__NS_BEGIN
return true;
}
template <typename D> PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D)
template <typename D> PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, xml_memory_pool* pool, D)
{
const typename D::type* data = static_cast<const typename D::type*>(contents);
size_t data_length = size / sizeof(typename D::type);
@ -2110,7 +2186,7 @@ PUGI__NS_BEGIN
size_t length = D::process(data, data_length, 0, wchar_counter());
// allocate buffer of suitable length
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false;
// second pass: convert utf16 input to wchar_t
@ -2126,22 +2202,22 @@ PUGI__NS_BEGIN
return true;
}
PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable)
PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{
// get native encoding
xml_encoding wchar_encoding = get_wchar_encoding();
// fast path: no conversion required
if (encoding == wchar_encoding)
return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable, pool);
// only endian-swapping is required
if (need_endian_swap_utf(encoding, wchar_encoding))
return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable);
return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable, pool);
// source encoding is utf8
if (encoding == encoding_utf8)
return convert_buffer_generic(out_buffer, out_length, contents, size, utf8_decoder());
return convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf8_decoder());
// source encoding is utf16
if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
@ -2149,8 +2225,8 @@ PUGI__NS_BEGIN
xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
return (native_encoding == encoding) ?
convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_true>());
convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder<opt_true>());
}
// source encoding is utf32
@ -2159,19 +2235,19 @@ PUGI__NS_BEGIN
xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
return (native_encoding == encoding) ?
convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_true>());
convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder<opt_true>());
}
// source encoding is latin1
if (encoding == encoding_latin1)
return convert_buffer_generic(out_buffer, out_length, contents, size, latin1_decoder());
return convert_buffer_generic(out_buffer, out_length, contents, size, pool, latin1_decoder());
assert(false && "Invalid encoding"); // unreachable
return false;
}
#else
template <typename D> PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D)
template <typename D> PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, xml_memory_pool* pool, D)
{
const typename D::type* data = static_cast<const typename D::type*>(contents);
size_t data_length = size / sizeof(typename D::type);
@ -2180,7 +2256,7 @@ PUGI__NS_BEGIN
size_t length = D::process(data, data_length, 0, utf8_counter());
// allocate buffer of suitable length
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false;
// second pass: convert utf16 input to utf8
@ -2205,7 +2281,7 @@ PUGI__NS_BEGIN
return size;
}
PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable)
PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{
const uint8_t* data = static_cast<const uint8_t*>(contents);
size_t data_length = size;
@ -2218,13 +2294,13 @@ PUGI__NS_BEGIN
size_t postfix_length = data_length - prefix_length;
// if no conversion is needed, just return the original buffer
if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable, pool);
// first pass: get length in utf8 units
size_t length = prefix_length + latin1_decoder::process(postfix, postfix_length, 0, utf8_counter());
// allocate buffer of suitable length
char_t* buffer = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
char_t* buffer = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!buffer) return false;
// second pass: convert latin1 input to utf8
@ -2242,11 +2318,11 @@ PUGI__NS_BEGIN
return true;
}
PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable)
PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable, xml_memory_pool* pool)
{
// fast path: no conversion required
if (encoding == encoding_utf8)
return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable);
return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable, pool);
// source encoding is utf16
if (encoding == encoding_utf16_be || encoding == encoding_utf16_le)
@ -2254,8 +2330,8 @@ PUGI__NS_BEGIN
xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be;
return (native_encoding == encoding) ?
convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder<opt_true>());
convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf16_decoder<opt_true>());
}
// source encoding is utf32
@ -2264,13 +2340,13 @@ PUGI__NS_BEGIN
xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be;
return (native_encoding == encoding) ?
convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder<opt_true>());
convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder<opt_false>()) :
convert_buffer_generic(out_buffer, out_length, contents, size, pool, utf32_decoder<opt_true>());
}
// source encoding is latin1
if (encoding == encoding_latin1)
return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable);
return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable, pool);
assert(false && "Invalid encoding"); // unreachable
return false;
@ -4701,10 +4777,11 @@ PUGI__NS_BEGIN
size_t length = 0;
// coverity[var_deref_model]
if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory);
xml_memory_pool* pool = doc->get_memory_pool();
if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable, pool)) return impl::make_parse_result(status_out_of_memory);
// delete original buffer if we performed a conversion
if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents);
if (own && buffer != contents && contents) pool->deallocate(contents);
// grab onto buffer if it's our buffer, user is responsible for deallocating contents himself
if (own || buffer != contents) *out_buffer = buffer;
@ -4798,7 +4875,8 @@ PUGI__NS_BEGIN
size_t max_suffix_size = sizeof(char_t);
// allocate buffer for the whole file
char* contents = static_cast<char*>(xml_memory::allocate(size + max_suffix_size));
xml_memory_pool* pool = doc->get_memory_pool();
char* contents = static_cast<char*>(impl::aligned_allocate(pool, size + max_suffix_size));
if (!contents) return make_parse_result(status_out_of_memory);
// read file in memory
@ -4806,7 +4884,7 @@ PUGI__NS_BEGIN
if (read_size != size)
{
xml_memory::deallocate(contents);
pool->deallocate(contents);
return make_parse_result(status_io_error);
}
@ -4823,12 +4901,12 @@ PUGI__NS_BEGIN
#ifndef PUGIXML_NO_STL
template <typename T> struct xml_stream_chunk
{
static xml_stream_chunk* create()
static xml_stream_chunk* create(xml_memory_pool* p)
{
void* memory = xml_memory::allocate(sizeof(xml_stream_chunk));
void* memory = p->allocate(sizeof(xml_stream_chunk));
if (!memory) return 0;
return new (memory) xml_stream_chunk();
return new (memory) xml_stream_chunk(p);
}
static void destroy(xml_stream_chunk* chunk)
@ -4838,23 +4916,25 @@ PUGI__NS_BEGIN
{
xml_stream_chunk* next_ = chunk->next;
xml_memory::deallocate(chunk);
xml_memory_pool* p = chunk->pool;
p->deallocate(chunk);
chunk = next_;
}
}
xml_stream_chunk(): next(0), size(0)
explicit xml_stream_chunk(xml_memory_pool* p): pool(p), next(0), size(0)
{
}
xml_memory_pool* pool;
xml_stream_chunk* next;
size_t size;
T data[xml_memory_page_size / sizeof(T)];
};
template <typename T> PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size)
template <typename T> PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size, xml_memory_pool* pool)
{
auto_deleter<xml_stream_chunk<T> > chunks(0, xml_stream_chunk<T>::destroy);
@ -4865,7 +4945,7 @@ PUGI__NS_BEGIN
while (!stream.eof())
{
// allocate new chunk
xml_stream_chunk<T>* chunk = xml_stream_chunk<T>::create();
xml_stream_chunk<T>* chunk = xml_stream_chunk<T>::create(pool);
if (!chunk) return status_out_of_memory;
// append chunk to list
@ -4887,7 +4967,7 @@ PUGI__NS_BEGIN
size_t max_suffix_size = sizeof(char_t);
// copy chunk list to a contiguous buffer
char* buffer = static_cast<char*>(xml_memory::allocate(total + max_suffix_size));
char* buffer = static_cast<char*>(impl::aligned_allocate(pool, total + max_suffix_size));
if (!buffer) return status_out_of_memory;
char* write = buffer;
@ -4908,7 +4988,7 @@ PUGI__NS_BEGIN
return status_ok;
}
template <typename T> PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size)
template <typename T> PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream<T>& stream, void** out_buffer, size_t* out_size, xml_memory_pool* pool)
{
// get length of remaining data in stream
typename std::basic_istream<T>::pos_type pos = stream.tellg();
@ -4926,7 +5006,7 @@ PUGI__NS_BEGIN
size_t max_suffix_size = sizeof(char_t);
// read stream data into memory (guard against stream exceptions with buffer holder)
auto_deleter<void> buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate);
auto_deleter<void> buffer(pool, read_length * sizeof(T) + max_suffix_size);
if (!buffer.data) return status_out_of_memory;
stream.read(static_cast<T*>(buffer.data), static_cast<std::streamsize>(read_length));
@ -4957,10 +5037,10 @@ PUGI__NS_BEGIN
if (stream.tellg() < 0)
{
stream.clear(); // clear error flags that could be set by a failing tellg
status = load_stream_data_noseek(stream, &buffer, &size);
status = load_stream_data_noseek(stream, &buffer, &size, doc->get_memory_pool());
}
else
status = load_stream_data_seek(stream, &buffer, &size);
status = load_stream_data_seek(stream, &buffer, &size, doc->get_memory_pool());
if (status != status_ok) return make_parse_result(status);
@ -4985,7 +5065,7 @@ PUGI__NS_BEGIN
size_t size = as_utf8_begin(str, length);
// allocate resulting string
char* result = static_cast<char*>(xml_memory::allocate(size + 1));
char* result = static_cast<char*>(get_default_memory_pool()->allocate(size + 1));
if (!result) return 0;
// second pass: convert to utf8
@ -5011,7 +5091,7 @@ PUGI__NS_BEGIN
FILE* result = fopen(path_utf8, mode_ascii);
// free dummy buffer
xml_memory::deallocate(path_utf8);
get_default_memory_pool()->deallocate(path_utf8);
return result;
}
@ -6912,18 +6992,24 @@ namespace pugi
PUGI__FN xml_document::xml_document(): _buffer(0)
{
_create();
_create(impl::get_default_memory_pool());
}
PUGI__FN xml_document::xml_document(xml_memory_pool& pool): _buffer(0)
{
_create(&pool);
}
PUGI__FN xml_document::~xml_document()
{
_destroy();
(void)_destroy();
}
#ifdef PUGIXML_HAS_MOVE
PUGI__FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(0)
{
_create();
impl::xml_document_struct* other = static_cast<impl::xml_document_struct*>(rhs._root);
_create(other->get_memory_pool());
_move(rhs);
}
@ -6931,8 +7017,8 @@ namespace pugi
{
if (this == &rhs) return *this;
_destroy();
_create();
xml_memory_pool* pool = _destroy();
_create(pool);
_move(rhs);
return *this;
@ -6941,8 +7027,8 @@ namespace pugi
PUGI__FN void xml_document::reset()
{
_destroy();
_create();
xml_memory_pool* pool = _destroy();
_create(pool);
}
PUGI__FN void xml_document::reset(const xml_document& proto)
@ -6953,7 +7039,7 @@ namespace pugi
append_copy(cur);
}
PUGI__FN void xml_document::_create()
PUGI__FN void xml_document::_create(xml_memory_pool* pool)
{
assert(!_root);
@ -6981,7 +7067,7 @@ namespace pugi
#endif
// allocate new root
_root = new (reinterpret_cast<char*>(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(page);
_root = new (reinterpret_cast<char*>(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(pool, page);
_root->prev_sibling_c = _root;
// setup sentinel page
@ -6996,21 +7082,23 @@ namespace pugi
assert(reinterpret_cast<char*>(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory));
}
PUGI__FN void xml_document::_destroy()
PUGI__FN xml_memory_pool* xml_document::_destroy()
{
assert(_root);
impl::xml_document_struct* doc = static_cast<impl::xml_document_struct*>(_root);
xml_memory_pool* pool = doc->get_memory_pool();
// destroy static storage
if (_buffer)
{
impl::xml_memory::deallocate(_buffer);
pool->deallocate(_buffer);
_buffer = 0;
}
// destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator)
for (impl::xml_extra_buffer* extra = static_cast<impl::xml_document_struct*>(_root)->extra_buffers; extra; extra = extra->next)
for (impl::xml_extra_buffer* extra = doc->extra_buffers; extra; extra = extra->next)
{
if (extra->buffer) impl::xml_memory::deallocate(extra->buffer);
if (extra->buffer) pool->deallocate(extra->buffer);
}
// destroy dynamic storage, leave sentinel page (it's in static memory)
@ -7022,7 +7110,7 @@ namespace pugi
{
impl::xml_memory_page* next = page->next;
impl::xml_allocator::deallocate_page(page);
doc->deallocate_page(page);
page = next;
}
@ -7033,6 +7121,8 @@ namespace pugi
#endif
_root = 0;
return pool;
}
#ifdef PUGIXML_HAS_MOVE
@ -7137,7 +7227,7 @@ namespace pugi
}
// reset other document
new (other) impl::xml_document_struct(PUGI__GETPAGE(other));
new (other) impl::xml_document_struct(other->get_memory_pool(), PUGI__GETPAGE(other));
rhs._buffer = 0;
}
#endif
@ -7637,7 +7727,7 @@ PUGI__NS_BEGIN
size_t block_size = block_capacity + offsetof(xpath_memory_block, data);
xpath_memory_block* block = static_cast<xpath_memory_block*>(xml_memory::allocate(block_size));
xpath_memory_block* block = static_cast<xpath_memory_block*>(get_default_memory_pool()->allocate(block_size));
if (!block)
{
if (_error) *_error = true;
@ -7692,7 +7782,7 @@ PUGI__NS_BEGIN
if (next)
{
xml_memory::deallocate(_root->next);
get_default_memory_pool()->deallocate(_root->next);
_root->next = next;
}
}
@ -7710,7 +7800,7 @@ PUGI__NS_BEGIN
{
xpath_memory_block* next = cur->next;
xml_memory::deallocate(cur);
get_default_memory_pool()->deallocate(cur);
cur = next;
}
@ -7729,7 +7819,7 @@ PUGI__NS_BEGIN
{
xpath_memory_block* next = cur->next;
xml_memory::deallocate(cur);
get_default_memory_pool()->deallocate(cur);
cur = next;
}
@ -8397,7 +8487,7 @@ PUGI__NS_BEGIN
#endif
}
PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result)
PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result, xml_memory_pool* pool)
{
size_t length = static_cast<size_t>(end - begin);
char_t* scratch = buffer;
@ -8405,7 +8495,7 @@ PUGI__NS_BEGIN
if (length >= sizeof(buffer) / sizeof(buffer[0]))
{
// need to make dummy on-heap copy
scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
scratch = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!scratch) return false;
}
@ -8416,7 +8506,7 @@ PUGI__NS_BEGIN
*out_result = convert_string_to_number(scratch);
// free dummy buffer
if (scratch != buffer) xml_memory::deallocate(scratch);
if (scratch != buffer) pool->deallocate(scratch);
return true;
}
@ -8658,7 +8748,7 @@ PUGI__NS_BEGIN
~xpath_variable_string()
{
if (value) xml_memory::deallocate(value);
if (value) get_default_memory_pool()->deallocate(value);
}
char_t* value;
@ -8702,7 +8792,7 @@ PUGI__NS_BEGIN
if (length == 0) return 0; // empty variable names are invalid
// $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters
void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t));
void* memory = get_default_memory_pool()->allocate(sizeof(T) + length * sizeof(char_t));
if (!memory) return 0;
T* result = new (memory) T();
@ -8736,7 +8826,7 @@ PUGI__NS_BEGIN
template <typename T> PUGI__FN void delete_xpath_variable(T* var)
{
var->~T();
xml_memory::deallocate(var);
get_default_memory_pool()->deallocate(var);
}
PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var)
@ -8786,7 +8876,7 @@ PUGI__NS_BEGIN
}
}
PUGI__FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result)
PUGI__FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result, xml_memory_pool* pool)
{
size_t length = static_cast<size_t>(end - begin);
char_t* scratch = buffer;
@ -8794,7 +8884,7 @@ PUGI__NS_BEGIN
if (length >= sizeof(buffer) / sizeof(buffer[0]))
{
// need to make dummy on-heap copy
scratch = static_cast<char_t*>(xml_memory::allocate((length + 1) * sizeof(char_t)));
scratch = static_cast<char_t*>(impl::aligned_allocate(pool, (length + 1) * sizeof(char_t)));
if (!scratch) return false;
}
@ -8805,7 +8895,7 @@ PUGI__NS_BEGIN
*out_result = set->get(scratch);
// free dummy buffer
if (scratch != buffer) xml_memory::deallocate(scratch);
if (scratch != buffer) pool->deallocate(scratch);
return true;
}
@ -11444,7 +11534,7 @@ PUGI__NS_BEGIN
return error("Unknown variable: variable set is not provided");
xpath_variable* var = 0;
if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var))
if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var, get_default_memory_pool()))
return error_oom();
if (!var)
@ -11484,7 +11574,7 @@ PUGI__NS_BEGIN
{
double value = 0;
if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value))
if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value, get_default_memory_pool()))
return error_oom();
_lexer.next();
@ -12023,7 +12113,7 @@ PUGI__NS_BEGIN
{
static xpath_query_impl* create()
{
void* memory = xml_memory::allocate(sizeof(xpath_query_impl));
void* memory = get_default_memory_pool()->allocate(sizeof(xpath_query_impl));
if (!memory) return 0;
return new (memory) xpath_query_impl();
@ -12035,7 +12125,7 @@ PUGI__NS_BEGIN
impl->alloc.release();
// free allocator memory (with the first page)
xml_memory::deallocate(impl);
get_default_memory_pool()->deallocate(impl);
}
xpath_query_impl(): root(0), alloc(&block, &oom), oom(false)
@ -12072,6 +12162,18 @@ PUGI__NS_END
namespace pugi
{
PUGI__FN void* xml_monotonic_memory_pool::allocate(size_t size)
{
if (PUGI__UNLIKELY(size > _capacity))
return 0;
void* block = _buffer;
_buffer = reinterpret_cast<char*>(_buffer) + size;
_capacity -= size;
return block;
}
#ifndef PUGIXML_NO_EXCEPTIONS
PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_)
{
@ -12159,7 +12261,7 @@ namespace pugi
size_t size_ = static_cast<size_t>(end_ - begin_);
// use internal buffer for 0 or 1 elements, heap buffer otherwise
xpath_node* storage = (size_ <= 1) ? _storage : static_cast<xpath_node*>(impl::xml_memory::allocate(size_ * sizeof(xpath_node)));
xpath_node* storage = (size_ <= 1) ? _storage : static_cast<xpath_node*>(impl::get_default_memory_pool()->allocate(size_ * sizeof(xpath_node)));
if (!storage)
{
@ -12172,7 +12274,7 @@ namespace pugi
// deallocate old buffer
if (_begin != _storage)
impl::xml_memory::deallocate(_begin);
impl::get_default_memory_pool()->deallocate(_begin);
// size check is necessary because for begin_ = end_ = nullptr, memcpy is UB
if (size_)
@ -12209,7 +12311,7 @@ namespace pugi
PUGI__FN xpath_node_set::~xpath_node_set()
{
if (_begin != _storage)
impl::xml_memory::deallocate(_begin);
impl::get_default_memory_pool()->deallocate(_begin);
}
PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(_storage), _end(_storage)
@ -12237,7 +12339,7 @@ namespace pugi
if (this == &rhs) return *this;
if (_begin != _storage)
impl::xml_memory::deallocate(_begin);
impl::get_default_memory_pool()->deallocate(_begin);
_move(rhs);
@ -12377,13 +12479,13 @@ namespace pugi
// duplicate string
size_t size = (impl::strlength(value) + 1) * sizeof(char_t);
char_t* copy = static_cast<char_t*>(impl::xml_memory::allocate(size));
char_t* copy = static_cast<char_t*>(impl::get_default_memory_pool()->allocate(size));
if (!copy) return false;
memcpy(copy, value, size);
// replace old string
if (var->value) impl::xml_memory::deallocate(var->value);
if (var->value) impl::get_default_memory_pool()->deallocate(var->value);
var->value = copy;
return true;

View File

@ -307,6 +307,31 @@ namespace pugi
It _begin, _end;
};
// Interface for pool allocators
class PUGIXML_CLASS xml_memory_pool
{
public:
virtual ~xml_memory_pool() {}
virtual void* allocate(size_t size) = 0;
virtual void deallocate(void* ptr) = 0;
};
// Non-owning allocate-only fixed size memory pool, deallocation is no-op
// Requires buffer of at least PUGIXML_MEMORY_PAGE_SIZE bytes for xml_document construction
class PUGIXML_CLASS xml_monotonic_memory_pool : public xml_memory_pool
{
public:
xml_monotonic_memory_pool(void* buffer, size_t size) PUGIXML_NOEXCEPT : _buffer(buffer), _capacity(size) {}
virtual void* allocate(size_t size) PUGIXML_OVERRIDE;
virtual void deallocate(void*) PUGIXML_OVERRIDE {}
private:
void* _buffer;
size_t _capacity;
};
// Writer interface for node printing (see xml_node::print)
class PUGIXML_CLASS xml_writer
{
@ -1021,14 +1046,17 @@ namespace pugi
xml_document(const xml_document&);
xml_document& operator=(const xml_document&);
void _create();
void _destroy();
void _create(xml_memory_pool* pool);
xml_memory_pool* _destroy();
void _move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT;
public:
// Default constructor, makes empty document
xml_document();
// Create empty document using supplied memory pool
explicit xml_document(xml_memory_pool& pool);
// Destructor, invalidates all node/attribute handles to this document
~xml_document();

View File

@ -30,6 +30,47 @@ namespace
page_deallocs += is_page(memory_size(ptr));
memory_deallocate(ptr);
}
void* null_allocate(size_t)
{
#ifndef PUGIXML_NO_EXCEPTIONS
throw std::runtime_error("null_allocate was invoked");
#else
return 0;
#endif
}
void null_deallocate(void*)
{
#ifndef PUGIXML_NO_EXCEPTIONS
throw std::runtime_error("null_deallocate was invoked");
#endif
}
class test_memory_pool : public pugi::xml_memory_pool
{
public:
test_memory_pool(pugi::allocation_function alloc, pugi::deallocation_function dealloc) PUGIXML_NOEXCEPT
: _allocate(alloc)
, _deallocate(dealloc)
{
}
void* allocate(size_t size) PUGIXML_OVERRIDE
{
return _allocate(size);
}
void deallocate(void* ptr) PUGIXML_OVERRIDE
{
_deallocate(ptr);
}
private:
pugi::allocation_function _allocate;
pugi::deallocation_function _deallocate;
};
}
TEST(memory_custom_memory_management)
@ -76,6 +117,53 @@ TEST(memory_custom_memory_management)
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_pool_custom_memory_management)
{
page_allocs = page_deallocs = 0;
// remember old functions
allocation_function old_allocate = get_memory_allocation_function();
deallocation_function old_deallocate = get_memory_deallocation_function();
// replace functions with non-allocating ones
set_memory_management_functions(null_allocate, null_deallocate);
{
// create custom memory pool
test_memory_pool pool(allocate, deallocate);
// parse document
xml_document doc(pool);
CHECK(page_allocs == 0 && page_deallocs == 0);
CHECK(doc.load_string(STR("<node />")));
CHECK(page_allocs == 1 && page_deallocs == 0);
// modify document (no new page)
CHECK(doc.first_child().set_name(STR("foobars")));
CHECK(page_allocs == 1 && page_deallocs == 0);
// modify document (new page)
std::basic_string<char_t> s(65536, 'x');
CHECK(doc.first_child().set_name(s.c_str()));
CHECK(page_allocs == 2 && page_deallocs == 0);
// modify document (new page, old one should die)
s += s;
CHECK(doc.first_child().set_name(s.c_str()));
CHECK(page_allocs == 3 && page_deallocs == 1);
}
CHECK(page_allocs == 3 && page_deallocs == 3);
// restore old functions
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_large_allocations)
{
page_allocs = page_deallocs = 0;
@ -140,6 +228,73 @@ TEST(memory_large_allocations)
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_pool_large_allocations)
{
page_allocs = page_deallocs = 0;
// remember old functions
allocation_function old_allocate = get_memory_allocation_function();
deallocation_function old_deallocate = get_memory_deallocation_function();
// replace functions with non-allocating ones
set_memory_management_functions(null_allocate, null_deallocate);
{
// create custom memory pool
test_memory_pool pool(allocate, deallocate);
xml_document doc(pool);
CHECK(page_allocs == 0 && page_deallocs == 0);
// initial fill
for (size_t i = 0; i < 128; ++i)
{
std::basic_string<char_t> s(i * 128, 'x');
CHECK(doc.append_child(node_pcdata).set_value(s.c_str()));
}
CHECK(page_allocs > 0 && page_deallocs == 0);
// grow-prune loop
while (doc.first_child())
{
xml_node node;
// grow
for (node = doc.first_child(); node; node = node.next_sibling())
{
std::basic_string<char_t> s = node.value();
CHECK(node.set_value((s + s).c_str()));
}
// prune
for (node = doc.first_child(); node; )
{
xml_node next = node.next_sibling().next_sibling();
node.parent().remove_child(node);
node = next;
}
}
CHECK(page_allocs == page_deallocs + 1); // only one live page left (it waits for new allocations)
char buffer;
CHECK(doc.load_buffer_inplace(&buffer, 0, parse_fragment, get_native_encoding()));
CHECK(page_allocs == page_deallocs); // no live pages left
}
CHECK(page_allocs == page_deallocs); // everything is freed
// restore old functions
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_page_management)
{
page_allocs = page_deallocs = 0;
@ -197,6 +352,66 @@ TEST(memory_page_management)
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_resource_page_management)
{
page_allocs = page_deallocs = 0;
// remember old functions
allocation_function old_allocate = get_memory_allocation_function();
deallocation_function old_deallocate = get_memory_deallocation_function();
// replace functions with non-allocating ones
set_memory_management_functions(null_allocate, null_deallocate);
{
// create custom memory pool
test_memory_pool pool(allocate, deallocate);
xml_document doc(pool);
CHECK(page_allocs == 0 && page_deallocs == 0);
// initial fill
std::vector<xml_node> nodes;
for (size_t i = 0; i < 4000; ++i)
{
xml_node node = doc.append_child(STR("n"));
CHECK(node);
nodes.push_back(node);
}
CHECK(page_allocs > 0 && page_deallocs == 0);
// grow-prune loop
size_t offset = 0;
size_t prime = 15485863;
while (nodes.size() > 0)
{
offset = (offset + prime) % nodes.size();
doc.remove_child(nodes[offset]);
nodes[offset] = nodes.back();
nodes.pop_back();
}
CHECK(page_allocs == page_deallocs + 1); // only one live page left (it waits for new allocations)
char buffer;
CHECK(doc.load_buffer_inplace(&buffer, 0, parse_fragment, get_native_encoding()));
CHECK(page_allocs == page_deallocs); // no live pages left
}
CHECK(page_allocs == page_deallocs); // everything is freed
// restore old functions
set_memory_management_functions(old_allocate, old_deallocate);
}
TEST(memory_string_allocate_increasing)
{
xml_document doc;
@ -301,3 +516,55 @@ TEST(memory_string_allocate_decreasing_inplace)
CHECK(result == "x");
}
TEST(monotonic_memory_pool_management)
{
page_allocs = page_deallocs = 0;
// remember old functions
allocation_function old_allocate = get_memory_allocation_function();
deallocation_function old_deallocate = get_memory_deallocation_function();
// replace functions with non-allocating ones
set_memory_management_functions(null_allocate, null_deallocate);
const size_t buffer_size = 512 * 1024;
void* buffer = old_allocate(buffer_size);
{
// create custom memory pool
xml_monotonic_memory_pool pool(buffer, buffer_size);
// parse document
xml_document doc(pool);
CHECK(page_allocs == 0 && page_deallocs == 0);
CHECK(doc.load_string(STR("<node />")));
CHECK(page_allocs == 0 && page_deallocs == 0);
// modify document (no new page)
CHECK(doc.first_child().set_name(STR("foobars")));
CHECK(page_allocs == 0 && page_deallocs == 0);
// modify document (new page)
std::basic_string<char_t> s(65536, 'x');
CHECK(doc.first_child().set_name(s.c_str()));
CHECK(page_allocs == 0 && page_deallocs == 0);
// modify document (new page, old one should die)
s += s;
CHECK(doc.first_child().set_name(s.c_str()));
CHECK(page_allocs == 0 && page_deallocs == 0);
}
CHECK(page_allocs == 0 && page_deallocs == 0);
old_deallocate(buffer);
// restore old functions
set_memory_management_functions(old_allocate, old_deallocate);
}