alloc: add aligned::foreign allocator

sometimes we need to ensure memory allocation has a particular alignment
in an _offset_ buffer (which we have no control over, eg renderdoc's
OpenGL buffers).

this applies an offset to various operations that make the
aligned::direct allocator correctly align allocations for buffers that
aren't themselves aligned.
This commit is contained in:
Danny Robson 2018-03-02 12:18:20 +11:00
parent 825ca4a7e7
commit c2265b9ed2
15 changed files with 277 additions and 69 deletions

View File

@ -140,9 +140,12 @@ list (
alloc/allocator.hpp alloc/allocator.hpp
alloc/arena.cpp alloc/arena.cpp
alloc/arena.hpp alloc/arena.hpp
alloc/raw/traits.hpp
alloc/raw/affix.cpp alloc/raw/affix.cpp
alloc/raw/affix.hpp alloc/raw/affix.hpp
alloc/raw/aligned.hpp alloc/raw/aligned/base.hpp
alloc/raw/aligned/direct.hpp
alloc/raw/aligned/foreign.hpp
alloc/raw/dynamic.hpp alloc/raw/dynamic.hpp
alloc/raw/fallback.cpp alloc/raw/fallback.cpp
alloc/raw/fallback.hpp alloc/raw/fallback.hpp
@ -422,7 +425,8 @@ if (TESTS)
APPEND TEST_BIN APPEND TEST_BIN
ascii ascii
algo/sort algo/sort
alloc/aligned alloc/aligned/foreign
alloc/aligned/direct
alloc/arena alloc/arena
alloc/dynamic alloc/dynamic
alloc/linear alloc/linear

View File

@ -29,8 +29,10 @@ namespace util::alloc {
class dynamic; class dynamic;
template <typename AllocT> namespace aligned {
class aligned; template <typename AllocT> class foreign;
template <typename AllocT> class direct;
}
} }

View File

@ -14,23 +14,23 @@
* Copyright 2016 Danny Robson <danny@nerdcruft.net> * Copyright 2016 Danny Robson <danny@nerdcruft.net>
*/ */
#ifndef CRUFT_UTIL_ALLOC_RAW_ALIGNED_HPP #ifndef CRUFT_UTIL_ALLOC_RAW_ALIGNED_DIRECT_HPP
#define CRUFT_UTIL_ALLOC_RAW_ALIGNED_HPP #define CRUFT_UTIL_ALLOC_RAW_ALIGNED_DIRECT_HPP
#include "../../../debug.hpp"
#include <cstddef> #include <cstddef>
#include <utility> #include <utility>
#include "../../debug.hpp" namespace util::alloc::raw::aligned {
namespace util::alloc::raw {
/// wraps a child allocator and enforces a fixed alignment /// wraps a child allocator and enforces a fixed alignment
template <typename ChildT> template <typename ChildT>
class aligned { class direct {
public: public:
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
template <typename ...Args> template <typename ...Args>
aligned (std::size_t _alignment, Args &&...args): direct (util::view<std::byte*> _data, std::size_t _alignment, Args &&...args):
m_successor (std::forward<Args> (args)...), m_successor (_data, std::forward<Args> (args)...),
m_alignment (_alignment) m_alignment (_alignment)
{ ; } { ; }
@ -42,16 +42,6 @@ namespace util::alloc::raw {
return m_successor.allocate (bytes, m_alignment); return m_successor.allocate (bytes, m_alignment);
} }
//---------------------------------------------------------------------
auto
allocate (std::size_t bytes, std::size_t alignment)
{
(void)alignment;
CHECK_EQ (alignment, m_alignment);
return m_successor.allocate (bytes, m_alignment);
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
auto auto
deallocate (void *ptr, std::size_t bytes) deallocate (void *ptr, std::size_t bytes)
@ -60,16 +50,6 @@ namespace util::alloc::raw {
} }
//---------------------------------------------------------------------
auto
deallocate (void *ptr, std::size_t bytes, std::size_t alignment)
{
(void)alignment;
CHECK_EQ (alignment, m_alignment);
return m_successor.deallocate (ptr, bytes, m_alignment);
}
/////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////
constexpr auto alignment (void) const noexcept { return m_alignment; } constexpr auto alignment (void) const noexcept { return m_alignment; }

View File

@ -0,0 +1,99 @@
/*
* 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.
*
* Copyright 2016 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_ALLOC_RAW_ALIGNED_OFFSET_HPP
#define CRUFT_UTIL_ALLOC_RAW_ALIGNED_OFFSET_HPP
#include "direct.hpp"
#include "../../../pointer.hpp"
#include "../../../debug.hpp"
#include <cstddef>
#include <utility>
namespace util::alloc::raw::aligned {
/// wraps a child allocator and enforces a fixed alignment that is
/// independant of the alignment of the provided source buffer.
///
/// we use the approach of:
/// * shifting the source buffer's alignment to satisfy the requested
/// alignment
/// * using a direct alignment child to service the requests
/// * then applying the reverse offset to values as we return the values
template <typename ChildT>
class foreign {
public:
template <typename ...Args>
foreign (util::view<std::byte*> _data, std::size_t _alignment, Args &&...args):
m_successor (
view<std::byte*> {0, _data.size ()},
_alignment,
std::forward<Args> (args)...
),
m_offset (m_successor.data () - _data.data ()),
m_alignment (_alignment)
{ ; }
void*
allocate (std::size_t size)
{
auto ptr= reinterpret_cast<std::byte*> (
m_successor.allocate (size)
);
return ptr - m_offset;
}
auto
deallocate (void *ptr, std::size_t size)
{
return m_successor.deallocate (
reinterpret_cast<std::byte*> (ptr) + m_offset, size
);
}
constexpr auto alignment (void) const noexcept { return m_alignment; }
auto offset (const void *ptr) const
{
return m_successor.offset (ptr);
}
auto data (void) { return m_successor.data () - m_offset; }
auto data (void) const { return m_successor.data () - m_offset; }
auto begin (void) { return m_successor.begin () - m_offset; }
auto begin (void) const { return m_successor.begin () - m_offset; }
auto end (void) { return m_successor.end () - m_offset; }
auto end (void) const { return m_successor.end () - m_offset; }
auto reset (void) { return m_successor.reset (); }
auto capacity (void) const { return m_successor.capacity (); }
auto used (void) const { return m_successor.used (); }
auto remain (void) const { return m_successor.remain (); }
private:
direct<ChildT> m_successor;
std::ptrdiff_t m_offset;
std::size_t m_alignment;
};
}
#endif

View File

@ -11,12 +11,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
* *
* Copyright 2016 Danny Robson <danny@nerdcruft.net> * Copyright 2016-2018 Danny Robson <danny@nerdcruft.net>
*/ */
#ifndef CRUFT_UTIL_ALLOC_RAW_DYNAMIC_HPP #ifndef CRUFT_UTIL_ALLOC_RAW_DYNAMIC_HPP
#define CRUFT_UTIL_ALLOC_RAW_DYNAMIC_HPP #define CRUFT_UTIL_ALLOC_RAW_DYNAMIC_HPP
#include "traits.hpp"
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
@ -26,6 +28,8 @@ namespace util::alloc::raw {
// allocator interface. // allocator interface.
class dynamic { class dynamic {
public: public:
struct alignment_unsupported : public std::exception {};
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// disable copying, but allow moving (required for calls to 'make') // disable copying, but allow moving (required for calls to 'make')
dynamic (const dynamic&) = delete; dynamic (const dynamic&) = delete;
@ -49,12 +53,19 @@ namespace util::alloc::raw {
} }
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// allocation management // if aligned allocation is not exposed by the child then we will
// unconditionally throw if it is ever called. unfortunately we can't
// dynamically eliminate the function altogether given run-time
// dynamic dispatch needs the common calls exposed to the clients, and
// aligned allocate is stupid useful.
auto allocate (size_t bytes) { return m_child->allocate (bytes); } auto allocate (size_t bytes) { return m_child->allocate (bytes); }
auto allocate (size_t bytes, size_t alignment) { return m_child->allocate (bytes, alignment); } auto allocate (size_t bytes, size_t alignment) { return m_child->allocate (bytes, alignment); }
auto deallocate (void *ptr, size_t bytes) { return m_child->deallocate (ptr, bytes); } auto deallocate (void *ptr, size_t bytes)
auto deallocate (void *ptr, size_t bytes, size_t alignment) { return m_child->deallocate (ptr, bytes, alignment); } { return m_child->deallocate (ptr, bytes); }
auto deallocate (void *ptr, size_t bytes, size_t alignment)
{ return m_child->deallocate (ptr, bytes, alignment); }
//--------------------------------------------------------------------- //---------------------------------------------------------------------
auto begin (void) { return m_child->begin (); } auto begin (void) { return m_child->begin (); }
@ -109,9 +120,11 @@ namespace util::alloc::raw {
}; };
template <typename T> template <typename ChildT>
class child final : public interface { class child final : public interface {
public: public:
struct _alignment_unsupported : public alignment_unsupported { };
template <typename ...Args> template <typename ...Args>
child (Args &&...args): child (Args &&...args):
interface (), interface (),
@ -123,9 +136,21 @@ namespace util::alloc::raw {
allocate (size_t bytes) override allocate (size_t bytes) override
{ return m_target.allocate (bytes); } { return m_target.allocate (bytes); }
// we can't totally eliminate this call given the point is to
// expose the common API area, but we will throw if the operation
// is unsupported in the child.
void* void*
allocate (size_t bytes, size_t alignment) override allocate (size_t bytes, size_t alignment) override
{ return m_target.allocate (bytes, alignment); } {
if constexpr (has_aligned_allocate_v<ChildT>) {
return m_target.allocate (bytes, alignment);
} else {
(void)bytes;
(void)alignment;
throw _alignment_unsupported ();
}
}
void void
deallocate (void *ptr, size_t bytes) override deallocate (void *ptr, size_t bytes) override
@ -133,7 +158,17 @@ namespace util::alloc::raw {
void void
deallocate (void *ptr, size_t bytes, size_t alignment) override deallocate (void *ptr, size_t bytes, size_t alignment) override
{ m_target.deallocate (ptr, bytes, alignment); } {
if constexpr (has_aligned_allocate_v<ChildT>) {
m_target.deallocate (ptr, bytes, alignment);
} else {
(void)ptr;
(void)bytes;
(void)alignment;
throw _alignment_unsupported ();
}
}
const std::byte* const std::byte*
begin (void) const override begin (void) const override
@ -164,7 +199,7 @@ namespace util::alloc::raw {
size_t remain (void) const override { return m_target.remain (); } size_t remain (void) const override { return m_target.remain (); }
private: private:
T m_target; ChildT m_target;
}; };

View File

@ -23,15 +23,11 @@ using util::alloc::raw::linear;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
linear::linear (void *begin, void *end): linear::linear (util::view<std::byte*> _data):
m_begin (reinterpret_cast<std::byte*> (begin)), m_begin (_data.begin ()),
m_end (reinterpret_cast<std::byte*> (end)), m_end (_data.end ()),
m_cursor (reinterpret_cast<std::byte*> (begin)) m_cursor (_data.begin ())
{ { ; }
CHECK_NEZ (begin);
CHECK_NEZ (end);
CHECK_LE (begin, end);
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -52,7 +48,6 @@ linear::allocate (size_t bytes, size_t alignment)
m_cursor = ptr + bytes; m_cursor = ptr + bytes;
CHECK_NEZ (ptr);
return ptr; return ptr;
} }

View File

@ -17,6 +17,8 @@
#ifndef CRUFT_UTIL_ALLOC_RAW_LINEAR_HPP #ifndef CRUFT_UTIL_ALLOC_RAW_LINEAR_HPP
#define CRUFT_UTIL_ALLOC_RAW_LINEAR_HPP #define CRUFT_UTIL_ALLOC_RAW_LINEAR_HPP
#include "../../view.hpp"
#include <cstddef> #include <cstddef>
#include <iterator> #include <iterator>
@ -30,12 +32,7 @@ namespace util::alloc::raw {
linear& operator= (const linear&) = delete; linear& operator= (const linear&) = delete;
linear& operator= (linear&&) = delete; linear& operator= (linear&&) = delete;
linear (void *begin, void *end); linear (util::view<std::byte*> _data);
template <typename T>
linear (T &&view):
linear (std::begin (view), std::end (view))
{ ; }
void* allocate (size_t bytes); void* allocate (size_t bytes);
void* allocate (size_t bytes, size_t alignment); void* allocate (size_t bytes, size_t alignment);

View File

@ -24,9 +24,9 @@ using util::alloc::raw::stack;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
stack::stack (void *begin, void *end): stack::stack (util::view<std::byte*> _data):
m_begin (reinterpret_cast<std::byte*> (begin)), m_begin (_data.begin ()),
m_end (reinterpret_cast<std::byte*> (end)), m_end (_data.end ()),
m_cursor (m_begin) m_cursor (m_begin)
{ {
CHECK_LE (m_begin, m_end); CHECK_LE (m_begin, m_end);

View File

@ -32,7 +32,7 @@ namespace util::alloc::raw {
stack& operator= (const stack&) = delete; stack& operator= (const stack&) = delete;
stack& operator= (stack&&) = delete; stack& operator= (stack&&) = delete;
stack (void *begin, void *end); stack (util::view<std::byte*> _data);
void *allocate (size_t bytes, size_t alignment); void *allocate (size_t bytes, size_t alignment);
void *allocate (size_t bytes); void *allocate (size_t bytes);

42
alloc/raw/traits.hpp Normal file
View File

@ -0,0 +1,42 @@
/*
* 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.
*
* Copyright 2018 Danny Robson <danny@nerdcruft.net>
*/
#ifndef CRUFT_UTIL_ALLOC_RAW_TRAITS_HPP
#define CRUFT_UTIL_ALLOC_RAW_TRAITS_HPP
#include <type_traits>
namespace util::alloc::raw {
template <typename AllocT, typename = std::void_t<>>
struct has_aligned_allocate : std::false_type {};
template <typename AllocT>
struct has_aligned_allocate<
AllocT,
std::void_t<
decltype (
std::declval<AllocT> ().allocate(16, 16)
)
>
>: public std::true_type {};
template <typename AllocT>
constexpr auto has_aligned_allocate_v = has_aligned_allocate<AllocT>::value;
};
#endif

View File

@ -1,6 +1,6 @@
#include "tap.hpp" #include "tap.hpp"
#include "alloc/raw/aligned.hpp" #include "alloc/raw/aligned/direct.hpp"
#include "alloc/raw/linear.hpp" #include "alloc/raw/linear.hpp"
@ -13,15 +13,15 @@ main (int, char**)
// satisfy a sane allocation request during testing, just in case the // satisfy a sane allocation request during testing, just in case the
// underlying code actually decides to do something; we don't be touching // underlying code actually decides to do something; we don't be touching
// it ourselves. // it ourselves.
static char buffer[1024*1024]; static std::byte buffer[1024*1024];
// pick an alignment that isn't likely to be satisfied by any likely // pick an alignment that isn't likely to be satisfied by any likely
// underlying allocator. if the allocation fulfills this alignment then // underlying allocator. if the allocation fulfills this alignment then
// we're probably operating correctly. // we're probably operating correctly.
static constexpr std::size_t alignment = 3; static constexpr std::size_t alignment = 3;
util::alloc::raw::aligned<util::alloc::raw::linear> alloc ( util::alloc::raw::aligned::direct<util::alloc::raw::linear> alloc (
alignment, std::begin (buffer), std::end (buffer) util::make_view (buffer), alignment
); );
// allocate a range of values and make sure they all satisfy our alignment. // allocate a range of values and make sure they all satisfy our alignment.

View File

@ -0,0 +1,54 @@
#include "alloc/raw/aligned/foreign.hpp"
#include "alloc/raw/linear.hpp"
#include "pointer.hpp"
#include "tap.hpp"
int
main ()
{
static std::byte buffer[1024*1024];
static constexpr std::size_t alignment = 3;
static constexpr std::size_t increment = 1;
// ensure we have an base pointer that's off-by-one to a likely natural
// system alignment
std::byte* base = util::align (
std::data (buffer),
alignof (std::max_align_t)
) + increment;
util::alloc::raw::aligned::foreign<util::alloc::raw::linear> alloc (
util::view(base,std::end(buffer)),
alignment
);
util::TAP::logger tap;
// ensure the first element allocated falls at the base address
tap.expect_eq (base, alloc.data (), "allocator base address is the supplied base address");
tap.expect_eq (base, alloc.allocate (8), "first allocation is the supplied base address");
// allocate a range of values and make sure they all satisfy our alignment.
// don't choose values which are likely to combine with the testing
// alignment to produce a likely system alignment. eg, 3 + 5 == 8 which is
// a power-of-2.
static const struct {
size_t size;
const char *message;
} TESTS[] = {
{ 9, "just over a power of two" },
{ 1, "a single byte" },
{ 64, "a cache line" },
{ 250, "multiple cache lines, but not a power of two" },
};
for (const auto &t: TESTS) {
auto ptr = (uintptr_t)alloc.allocate (t.size);
auto offset = ptr - (uintptr_t)base;
tap.expect_mod (offset, alignment, "%s", t.message);
}
return tap.status ();
};

View File

@ -6,7 +6,7 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
static char g_backing[1024*1024]; static std::byte g_backing[1024*1024];
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -28,7 +28,7 @@ struct setter {
int int
main (void) main (void)
{ {
util::alloc::raw::linear alloc (std::begin (g_backing), std::end (g_backing)); util::alloc::raw::linear alloc (util::make_view (g_backing));
util::alloc::arena<util::alloc::raw::linear> arena (alloc); util::alloc::arena<util::alloc::raw::linear> arena (alloc);
util::TAP::logger tap; util::TAP::logger tap;

View File

@ -11,7 +11,7 @@ main (void)
constexpr size_t BUFFER_SIZE = 1024; constexpr size_t BUFFER_SIZE = 1024;
alignas (std::max_align_t) std::byte memory[BUFFER_SIZE]; alignas (std::max_align_t) std::byte memory[BUFFER_SIZE];
util::alloc::raw::linear store (std::begin (memory), std::end (memory)); util::alloc::raw::linear store (util::make_view (memory));
tap.expect_eq (store.begin (), std::begin (memory), "base pointers match"); tap.expect_eq (store.begin (), std::begin (memory), "base pointers match");
tap.expect_eq (store.offset (std::begin (memory)), 0u, "base offset is 0"); tap.expect_eq (store.offset (std::begin (memory)), 0u, "base offset is 0");

View File

@ -32,7 +32,7 @@ main (void)
alignas (std::max_align_t) std::byte memory[BUFFER_SIZE]; alignas (std::max_align_t) std::byte memory[BUFFER_SIZE];
std::fill (std::begin (memory), std::end (memory), std::byte{0}); std::fill (std::begin (memory), std::end (memory), std::byte{0});
util::alloc::raw::stack store (memory, memory + BUFFER_AVAILABLE); util::alloc::raw::stack store (util::make_view(memory, memory + BUFFER_AVAILABLE));
tap.expect_eq (store.begin (), std::begin (memory), "base pointers match"); tap.expect_eq (store.begin (), std::begin (memory), "base pointers match");
tap.expect_eq (store.offset (std::begin (memory)), 0u, "base offset is 0"); tap.expect_eq (store.offset (std::begin (memory)), 0u, "base offset is 0");