From c2265b9ed2f49c5dab8f2e63b5bd9bf5f3092eab Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Fri, 2 Mar 2018 12:18:20 +1100 Subject: [PATCH] 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. --- CMakeLists.txt | 8 +- alloc/fwd.hpp | 6 +- alloc/raw/{aligned.hpp => aligned/direct.hpp} | 36 ++----- alloc/raw/aligned/foreign.hpp | 99 +++++++++++++++++++ alloc/raw/dynamic.hpp | 53 ++++++++-- alloc/raw/linear.cpp | 15 +-- alloc/raw/linear.hpp | 9 +- alloc/raw/stack.cpp | 6 +- alloc/raw/stack.hpp | 2 +- alloc/raw/traits.hpp | 42 ++++++++ .../alloc/{aligned.cpp => aligned/direct.cpp} | 8 +- test/alloc/aligned/foreign.cpp | 54 ++++++++++ test/alloc/arena.cpp | 4 +- test/alloc/linear.cpp | 2 +- test/alloc/stack.cpp | 2 +- 15 files changed, 277 insertions(+), 69 deletions(-) rename alloc/raw/{aligned.hpp => aligned/direct.hpp} (74%) create mode 100644 alloc/raw/aligned/foreign.hpp create mode 100644 alloc/raw/traits.hpp rename test/alloc/{aligned.cpp => aligned/direct.cpp} (87%) create mode 100644 test/alloc/aligned/foreign.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 684465ea..68d0dcd8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,9 +140,12 @@ list ( alloc/allocator.hpp alloc/arena.cpp alloc/arena.hpp + alloc/raw/traits.hpp alloc/raw/affix.cpp 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/fallback.cpp alloc/raw/fallback.hpp @@ -422,7 +425,8 @@ if (TESTS) APPEND TEST_BIN ascii algo/sort - alloc/aligned + alloc/aligned/foreign + alloc/aligned/direct alloc/arena alloc/dynamic alloc/linear diff --git a/alloc/fwd.hpp b/alloc/fwd.hpp index cb3fd8a5..e6ca3843 100644 --- a/alloc/fwd.hpp +++ b/alloc/fwd.hpp @@ -29,8 +29,10 @@ namespace util::alloc { class dynamic; - template - class aligned; + namespace aligned { + template class foreign; + template class direct; + } } diff --git a/alloc/raw/aligned.hpp b/alloc/raw/aligned/direct.hpp similarity index 74% rename from alloc/raw/aligned.hpp rename to alloc/raw/aligned/direct.hpp index bf96bac3..f3b7534e 100644 --- a/alloc/raw/aligned.hpp +++ b/alloc/raw/aligned/direct.hpp @@ -14,23 +14,23 @@ * Copyright 2016 Danny Robson */ -#ifndef CRUFT_UTIL_ALLOC_RAW_ALIGNED_HPP -#define CRUFT_UTIL_ALLOC_RAW_ALIGNED_HPP +#ifndef CRUFT_UTIL_ALLOC_RAW_ALIGNED_DIRECT_HPP +#define CRUFT_UTIL_ALLOC_RAW_ALIGNED_DIRECT_HPP + +#include "../../../debug.hpp" #include #include -#include "../../debug.hpp" - -namespace util::alloc::raw { +namespace util::alloc::raw::aligned { /// wraps a child allocator and enforces a fixed alignment template - class aligned { + class direct { public: /////////////////////////////////////////////////////////////////////// template - aligned (std::size_t _alignment, Args &&...args): - m_successor (std::forward (args)...), + direct (util::view _data, std::size_t _alignment, Args &&...args): + m_successor (_data, std::forward (args)...), m_alignment (_alignment) { ; } @@ -42,16 +42,6 @@ namespace util::alloc::raw { 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 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; } diff --git a/alloc/raw/aligned/foreign.hpp b/alloc/raw/aligned/foreign.hpp new file mode 100644 index 00000000..4ea054d0 --- /dev/null +++ b/alloc/raw/aligned/foreign.hpp @@ -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 + */ + +#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 +#include + +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 + class foreign { + public: + template + foreign (util::view _data, std::size_t _alignment, Args &&...args): + m_successor ( + view {0, _data.size ()}, + _alignment, + std::forward (args)... + ), + m_offset (m_successor.data () - _data.data ()), + m_alignment (_alignment) + { ; } + + + void* + allocate (std::size_t size) + { + auto ptr= reinterpret_cast ( + m_successor.allocate (size) + ); + return ptr - m_offset; + } + + auto + deallocate (void *ptr, std::size_t size) + { + return m_successor.deallocate ( + reinterpret_cast (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 m_successor; + std::ptrdiff_t m_offset; + std::size_t m_alignment; + }; +} + +#endif diff --git a/alloc/raw/dynamic.hpp b/alloc/raw/dynamic.hpp index f7b30e07..4e54b98a 100644 --- a/alloc/raw/dynamic.hpp +++ b/alloc/raw/dynamic.hpp @@ -11,12 +11,14 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * Copyright 2016 Danny Robson + * Copyright 2016-2018 Danny Robson */ #ifndef CRUFT_UTIL_ALLOC_RAW_DYNAMIC_HPP #define CRUFT_UTIL_ALLOC_RAW_DYNAMIC_HPP +#include "traits.hpp" + #include #include @@ -26,6 +28,8 @@ namespace util::alloc::raw { // allocator interface. class dynamic { public: + struct alignment_unsupported : public std::exception {}; + //--------------------------------------------------------------------- // disable copying, but allow moving (required for calls to 'make') dynamic (const dynamic&) = delete; @@ -49,12 +53,19 @@ namespace util::alloc::raw { } //--------------------------------------------------------------------- - // allocation management - auto allocate (size_t bytes) { return m_child->allocate (bytes); } + // 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, 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, size_t alignment) { return m_child->deallocate (ptr, bytes, alignment); } + auto deallocate (void *ptr, size_t bytes) + { 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 (); } @@ -109,9 +120,11 @@ namespace util::alloc::raw { }; - template + template class child final : public interface { public: + struct _alignment_unsupported : public alignment_unsupported { }; + template child (Args &&...args): interface (), @@ -123,9 +136,21 @@ namespace util::alloc::raw { allocate (size_t bytes) override { 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* allocate (size_t bytes, size_t alignment) override - { return m_target.allocate (bytes, alignment); } + { + if constexpr (has_aligned_allocate_v) { + return m_target.allocate (bytes, alignment); + } else { + (void)bytes; + (void)alignment; + throw _alignment_unsupported (); + } + } void deallocate (void *ptr, size_t bytes) override @@ -133,7 +158,17 @@ namespace util::alloc::raw { void deallocate (void *ptr, size_t bytes, size_t alignment) override - { m_target.deallocate (ptr, bytes, alignment); } + { + if constexpr (has_aligned_allocate_v) { + m_target.deallocate (ptr, bytes, alignment); + } else { + (void)ptr; + (void)bytes; + (void)alignment; + + throw _alignment_unsupported (); + } + } const std::byte* begin (void) const override @@ -164,7 +199,7 @@ namespace util::alloc::raw { size_t remain (void) const override { return m_target.remain (); } private: - T m_target; + ChildT m_target; }; diff --git a/alloc/raw/linear.cpp b/alloc/raw/linear.cpp index 34dc4067..65352239 100644 --- a/alloc/raw/linear.cpp +++ b/alloc/raw/linear.cpp @@ -23,15 +23,11 @@ using util::alloc::raw::linear; /////////////////////////////////////////////////////////////////////////////// -linear::linear (void *begin, void *end): - m_begin (reinterpret_cast (begin)), - m_end (reinterpret_cast (end)), - m_cursor (reinterpret_cast (begin)) -{ - CHECK_NEZ (begin); - CHECK_NEZ (end); - CHECK_LE (begin, end); -} +linear::linear (util::view _data): + m_begin (_data.begin ()), + m_end (_data.end ()), + m_cursor (_data.begin ()) +{ ; } /////////////////////////////////////////////////////////////////////////////// @@ -52,7 +48,6 @@ linear::allocate (size_t bytes, size_t alignment) m_cursor = ptr + bytes; - CHECK_NEZ (ptr); return ptr; } diff --git a/alloc/raw/linear.hpp b/alloc/raw/linear.hpp index fa451ccd..a63f73aa 100644 --- a/alloc/raw/linear.hpp +++ b/alloc/raw/linear.hpp @@ -17,6 +17,8 @@ #ifndef CRUFT_UTIL_ALLOC_RAW_LINEAR_HPP #define CRUFT_UTIL_ALLOC_RAW_LINEAR_HPP +#include "../../view.hpp" + #include #include @@ -30,12 +32,7 @@ namespace util::alloc::raw { linear& operator= (const linear&) = delete; linear& operator= (linear&&) = delete; - linear (void *begin, void *end); - - template - linear (T &&view): - linear (std::begin (view), std::end (view)) - { ; } + linear (util::view _data); void* allocate (size_t bytes); void* allocate (size_t bytes, size_t alignment); diff --git a/alloc/raw/stack.cpp b/alloc/raw/stack.cpp index 7fe5b9f7..cc08a599 100644 --- a/alloc/raw/stack.cpp +++ b/alloc/raw/stack.cpp @@ -24,9 +24,9 @@ using util::alloc::raw::stack; /////////////////////////////////////////////////////////////////////////////// -stack::stack (void *begin, void *end): - m_begin (reinterpret_cast (begin)), - m_end (reinterpret_cast (end)), +stack::stack (util::view _data): + m_begin (_data.begin ()), + m_end (_data.end ()), m_cursor (m_begin) { CHECK_LE (m_begin, m_end); diff --git a/alloc/raw/stack.hpp b/alloc/raw/stack.hpp index feef7fbd..c7414ea7 100644 --- a/alloc/raw/stack.hpp +++ b/alloc/raw/stack.hpp @@ -32,7 +32,7 @@ namespace util::alloc::raw { stack& operator= (const stack&) = delete; stack& operator= (stack&&) = delete; - stack (void *begin, void *end); + stack (util::view _data); void *allocate (size_t bytes, size_t alignment); void *allocate (size_t bytes); diff --git a/alloc/raw/traits.hpp b/alloc/raw/traits.hpp new file mode 100644 index 00000000..46416c94 --- /dev/null +++ b/alloc/raw/traits.hpp @@ -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 + */ + +#ifndef CRUFT_UTIL_ALLOC_RAW_TRAITS_HPP +#define CRUFT_UTIL_ALLOC_RAW_TRAITS_HPP + +#include + + +namespace util::alloc::raw { + template > + struct has_aligned_allocate : std::false_type {}; + + template + struct has_aligned_allocate< + AllocT, + std::void_t< + decltype ( + std::declval ().allocate(16, 16) + ) + > + >: public std::true_type {}; + + + template + constexpr auto has_aligned_allocate_v = has_aligned_allocate::value; +}; + +#endif diff --git a/test/alloc/aligned.cpp b/test/alloc/aligned/direct.cpp similarity index 87% rename from test/alloc/aligned.cpp rename to test/alloc/aligned/direct.cpp index 22faa649..92ab8b64 100644 --- a/test/alloc/aligned.cpp +++ b/test/alloc/aligned/direct.cpp @@ -1,6 +1,6 @@ #include "tap.hpp" -#include "alloc/raw/aligned.hpp" +#include "alloc/raw/aligned/direct.hpp" #include "alloc/raw/linear.hpp" @@ -13,15 +13,15 @@ main (int, char**) // satisfy a sane allocation request during testing, just in case the // underlying code actually decides to do something; we don't be touching // 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 // underlying allocator. if the allocation fulfills this alignment then // we're probably operating correctly. static constexpr std::size_t alignment = 3; - util::alloc::raw::aligned alloc ( - alignment, std::begin (buffer), std::end (buffer) + util::alloc::raw::aligned::direct alloc ( + util::make_view (buffer), alignment ); // allocate a range of values and make sure they all satisfy our alignment. diff --git a/test/alloc/aligned/foreign.cpp b/test/alloc/aligned/foreign.cpp new file mode 100644 index 00000000..81fec629 --- /dev/null +++ b/test/alloc/aligned/foreign.cpp @@ -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 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 (); +}; diff --git a/test/alloc/arena.cpp b/test/alloc/arena.cpp index 732429a8..ae695b4b 100644 --- a/test/alloc/arena.cpp +++ b/test/alloc/arena.cpp @@ -6,7 +6,7 @@ /////////////////////////////////////////////////////////////////////////////// -static char g_backing[1024*1024]; +static std::byte g_backing[1024*1024]; //----------------------------------------------------------------------------- @@ -28,7 +28,7 @@ struct setter { int 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 arena (alloc); util::TAP::logger tap; diff --git a/test/alloc/linear.cpp b/test/alloc/linear.cpp index adb6ae53..6c71dba5 100644 --- a/test/alloc/linear.cpp +++ b/test/alloc/linear.cpp @@ -11,7 +11,7 @@ main (void) constexpr size_t BUFFER_SIZE = 1024; 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.offset (std::begin (memory)), 0u, "base offset is 0"); diff --git a/test/alloc/stack.cpp b/test/alloc/stack.cpp index e698a352..70aa9df5 100644 --- a/test/alloc/stack.cpp +++ b/test/alloc/stack.cpp @@ -32,7 +32,7 @@ main (void) alignas (std::max_align_t) std::byte memory[BUFFER_SIZE]; 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.offset (std::begin (memory)), 0u, "base offset is 0");