├── .clang-format ├── .editorconfig ├── .github └── workflows │ └── windows.yaml ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE.txt ├── README.md ├── benchmarks ├── CMakeLists.txt ├── bench-utils.hpp ├── push_back.cpp ├── push_back_copy.cpp └── push_back_copy_and_alloc.cpp ├── lib ├── CMakeLists.txt ├── include │ └── vmcontainer │ │ ├── detail.hpp │ │ ├── pinned_vector.hpp │ │ └── vm.hpp └── src │ └── vmcontainer │ └── vm.cpp ├── tests ├── CMakeLists.txt ├── allocator_mocks.hpp ├── detail │ ├── algorithms.cpp │ ├── detail.cpp │ └── value_init_when_moved_from.cpp ├── instantiations.cpp ├── main.cpp ├── pinned_vector │ ├── access.cpp │ ├── assign.cpp │ ├── capacity.cpp │ ├── clear.cpp │ ├── contiguous.cpp │ ├── emplace_back.cpp │ ├── iterators.cpp │ ├── push_back.cpp │ └── special.cpp ├── pinned_vector_test.hpp └── vm │ ├── page_stack.cpp │ └── reservation.cpp └── vcpkg.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: false 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Inline 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: false 19 | AlwaysBreakTemplateDeclarations: true 20 | BinPackArguments: false 21 | BinPackParameters: false 22 | BreakBeforeBinaryOperators: NonAssignment 23 | BreakBeforeBraces: Custom 24 | BraceWrapping: 25 | AfterClass: true 26 | AfterControlStatement: true 27 | AfterEnum: true 28 | AfterFunction: true 29 | AfterNamespace: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | SplitEmptyFunction: false 36 | SplitEmptyRecord: false 37 | SplitEmptyNamespace: false 38 | BreakBeforeInheritanceComma: true 39 | BreakBeforeTernaryOperators: true 40 | BreakInheritanceList: BeforeComma 41 | BreakConstructorInitializers: BeforeComma 42 | ColumnLimit: 120 43 | CompactNamespaces: false 44 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 45 | ConstructorInitializerIndentWidth: 2 46 | ContinuationIndentWidth: 2 47 | Cpp11BracedListStyle: true 48 | DerivePointerAlignment: false 49 | FixNamespaceComments: false 50 | IncludeBlocks: Preserve 51 | IndentCaseLabels: true 52 | IndentPPDirectives: AfterHash 53 | IndentWidth: 2 54 | IndentWrappedFunctionNames: true 55 | KeepEmptyLinesAtTheStartOfBlocks: false 56 | MaxEmptyLinesToKeep: 1 57 | NamespaceIndentation: All 58 | PointerAlignment: Left 59 | ReflowComments: true 60 | SortIncludes: true 61 | SortUsingDeclarations: true 62 | SpaceAfterCStyleCast: false 63 | SpaceAfterTemplateKeyword: false 64 | SpaceBeforeAssignmentOperators: true 65 | SpaceBeforeCpp11BracedList: false 66 | SpaceBeforeCtorInitializerColon: true 67 | SpaceBeforeInheritanceColon: true 68 | SpaceBeforeParens: Never 69 | SpaceBeforeRangeBasedForLoopColon: false 70 | SpaceInEmptyParentheses: false 71 | SpacesBeforeTrailingComments: 1 72 | SpacesInAngles: false 73 | SpacesInCStyleCastParentheses: false 74 | SpacesInContainerLiterals: false 75 | SpacesInParentheses: false 76 | SpacesInSquareBrackets: false 77 | Standard: Cpp11 78 | TabWidth: 2 79 | UseTab: Never 80 | ... 81 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | insert_final_newline = true 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.github/workflows/windows.yaml: -------------------------------------------------------------------------------- 1 | name: windows 2 | on: [push, pull_request] 3 | 4 | defaults: 5 | run: 6 | shell: pwsh 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.image }} 11 | strategy: 12 | matrix: 13 | arch: [x64] 14 | image: [windows-2019] 15 | compiler: [cl] 16 | std: [14] 17 | config: [debug, release] 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Checkout vcpkg 24 | uses: actions/checkout@v2 25 | with: 26 | repository: microsoft/vcpkg 27 | path: vcpkg 28 | 29 | - name: Install ninja 30 | run: choco install ninja -y 31 | 32 | - name: Setup MSVC Environment 33 | if: runner.os == 'Windows' 34 | run: | 35 | $vcvarsall = "C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvarsall.bat" 36 | cmd /c "`"$vcvarsall`" ${{ matrix.arch }} > nul 2>&1 && set" >> $Env:GITHUB_ENV 37 | exit $LASTEXITCODE 38 | 39 | - name: Configure 40 | env: 41 | VCPKG_ROOT: ${{ github.workspace }}/vcpkg 42 | run: > 43 | cmake 44 | --preset ${{ matrix.arch }}-windows-${{ matrix.compiler }}-std${{ matrix.std }}-${{ matrix.config }} 45 | -B out/build 46 | . 47 | 48 | - name: Build 49 | run: cmake --build out/build 50 | 51 | - name: Test 52 | working-directory: out/build 53 | run: ctest --verbose --output-on-failure 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | .vscode/ 3 | /out/ 4 | 5 | CMakeUserPresets.json 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(vmcontainer CXX) 4 | 5 | option(VMCONTAINER_DEV_MODE "Enables all the dev option by default") 6 | option(VMCONTAINER_BUILD_TESTS "Enable the vmcontainer test suite" ${VMCONTAINER_DEV_MODE}) 7 | option(VMCONTAINER_BUILD_BENCHMARKS "Enable the vmcontainer benchmark suite" ${VMCONTAINER_DEV_MODE}) 8 | 9 | add_subdirectory(lib) 10 | 11 | if(VMCONTAINER_BUILD_BENCHMARKS) 12 | add_subdirectory(benchmarks) 13 | endif() 14 | 15 | if(VMCONTAINER_BUILD_TESTS) 16 | enable_testing() 17 | add_subdirectory(tests) 18 | endif() 19 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 20, 6 | "patch": 5 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "base", 11 | "hidden": true, 12 | "binaryDir": "${sourceDir}/out/build/${presetName}", 13 | "cacheVariables": { 14 | "CMAKE_TOOLCHAIN_FILE": { 15 | "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", 16 | "type": "FILEPATH" 17 | }, 18 | "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}", 19 | "VMCONTAINER_DEV_MODE": "ON" 20 | } 21 | }, 22 | { 23 | "name": "debug", 24 | "hidden": true, 25 | "cacheVariables": { 26 | "CMAKE_BUILD_TYPE": "Debug" 27 | } 28 | }, 29 | { 30 | "name": "release", 31 | "hidden": true, 32 | "cacheVariables": { 33 | "CMAKE_BUILD_TYPE": "Release" 34 | } 35 | }, 36 | { 37 | "name": "std14", 38 | "hidden": true, 39 | "cacheVariables": { 40 | "CMAKE_CXX_STANDARD": "14" 41 | } 42 | }, 43 | { 44 | "name": "windows-base", 45 | "inherits": "base", 46 | "hidden": true, 47 | "generator": "Ninja", 48 | "toolset": { 49 | "value": "host=x64", 50 | "strategy": "external" 51 | }, 52 | "architecture": { 53 | "value": "x64", 54 | "strategy": "external" 55 | }, 56 | "cacheVariables": { 57 | "VCPKG_TARGET_TRIPLET": "x64-windows" 58 | }, 59 | "vendor": { 60 | "microsoft.com/VisualStudioSettings/CMake/1.0": { 61 | "hostOS": [ "Windows" ] 62 | } 63 | } 64 | }, 65 | { 66 | "name": "x64-windows-cl-std14-debug", 67 | "inherits": [ "windows-base", "debug", "std14" ], 68 | "displayName": "Windows x64 MSVC C++14 Debug", 69 | "cacheVariables": { 70 | "CMAKE_CXX_COMPILER": "cl" 71 | } 72 | }, 73 | { 74 | "name": "x64-windows-cl-std14-release", 75 | "inherits": [ "windows-base", "release", "std14" ], 76 | "displayName": "Windows x64 MSVC C++14 Release", 77 | "cacheVariables": { 78 | "CMAKE_CXX_COMPILER": "cl" 79 | } 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vmcontainer 2 | Virtual memory based containers 3 | 4 | This repository will be filled with the implementation of the container presented in the Meeting C++ 2018 talk "pinned_vector" by Jakob Schweißhelm and Miro Knejp. 5 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(benchmark CONFIG REQUIRED) 2 | 3 | add_executable( 4 | vmcontainer.bench.push_back 5 | 6 | bench-utils.hpp 7 | push_back.cpp 8 | ) 9 | 10 | add_library(vmcontainer.bench.dependencies INTERFACE) 11 | target_link_libraries( 12 | vmcontainer.bench.dependencies 13 | INTERFACE 14 | vmcontainer::vmcontainer 15 | benchmark::benchmark 16 | benchmark::benchmark_main 17 | ) 18 | 19 | target_link_libraries(vmcontainer.bench.push_back PRIVATE vmcontainer.bench.dependencies) 20 | 21 | add_executable( 22 | vmcontainer.bench.push_back_copy 23 | 24 | bench-utils.hpp 25 | push_back_copy.cpp 26 | ) 27 | target_link_libraries(vmcontainer.bench.push_back_copy PRIVATE vmcontainer.bench.dependencies) 28 | 29 | add_executable( 30 | vmcontainer.bench.push_back_copy_and_alloc 31 | 32 | bench-utils.hpp 33 | push_back_copy_and_alloc.cpp 34 | ) 35 | target_link_libraries(vmcontainer.bench.push_back_copy_and_alloc PRIVATE vmcontainer.bench.dependencies) 36 | -------------------------------------------------------------------------------- /benchmarks/bench-utils.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | namespace bench_utils 17 | { 18 | template 19 | struct tag 20 | {}; 21 | 22 | struct bigval 23 | { 24 | double a, b, c, d, e, f, g, h, i, j; 25 | }; 26 | 27 | struct bumb_allocator_data 28 | { 29 | void* p; 30 | std::size_t available; 31 | std::vector buffer; 32 | }; 33 | 34 | template 35 | class bumb_allocator 36 | { 37 | public: 38 | using value_type = T; 39 | 40 | explicit bumb_allocator(std::size_t size) : _data(new bumb_allocator_data{}) 41 | { 42 | _data->buffer.resize(size); 43 | reset(); 44 | } 45 | 46 | template 47 | explicit bumb_allocator(bumb_allocator const& other) : _data(other.data()) 48 | {} 49 | 50 | T* allocate(std::size_t n) 51 | { 52 | if(std::align(alignof(T), sizeof(T) * n, _data->p, _data->available)) 53 | { 54 | auto const p = static_cast(_data->p); 55 | _data->p = static_cast(_data->p) + sizeof(T) * n; 56 | _data->available -= sizeof(T) * n; 57 | return p; 58 | } 59 | else 60 | { 61 | throw std::bad_alloc(); 62 | } 63 | } 64 | 65 | void deallocate(T*, std::size_t) {} 66 | 67 | template 68 | bool operator==(bumb_allocator const& other) const 69 | { 70 | return _data == other._data; 71 | } 72 | template 73 | bool operator!=(bumb_allocator const& other) const 74 | { 75 | return !(*this == other); 76 | } 77 | 78 | auto data() const { return _data; } 79 | 80 | void reset() 81 | { 82 | _data->p = _data->buffer.data(); 83 | _data->available = _data->buffer.size(); 84 | } 85 | 86 | private: 87 | std::shared_ptr _data; 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /benchmarks/push_back.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "bench-utils.hpp" 8 | 9 | #include "vmcontainer/pinned_vector.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace mknejp::vmcontainer; 16 | using namespace bench_utils; 17 | 18 | namespace 19 | { 20 | template 21 | auto init_vector(std::size_t max_size, tag>) -> std::vector 22 | { 23 | return std::vector(); 24 | } 25 | 26 | template 27 | auto init_vector(std::size_t max_size, tag>) -> pinned_vector 28 | { 29 | return pinned_vector(max_elements(max_size)); 30 | } 31 | 32 | auto const max_bytes_tests = { 33 | std::int64_t(64), 34 | std::int64_t(128), 35 | std::int64_t(256), 36 | std::int64_t(512), 37 | std::int64_t(1) * 1024, 38 | std::int64_t(4) * 1024, 39 | std::int64_t(16) * 1024, 40 | std::int64_t(64) * 1024, 41 | std::int64_t(128) * 1024, 42 | std::int64_t(512) * 1024, 43 | std::int64_t(1) * 1024 * 1024, 44 | std::int64_t(4) * 1024 * 1024, 45 | std::int64_t(16) * 1024 * 1024, 46 | std::int64_t(64) * 1024 * 1024, 47 | std::int64_t(128) * 1024 * 1024, 48 | std::int64_t(512) * 1024 * 1024, 49 | std::int64_t(1) * 1024 * 1024 * 1024, 50 | std::int64_t(2) * 1024 * 1024 * 1024, 51 | std::int64_t(4) * 1024 * 1024 * 1024, 52 | }; 53 | 54 | template 55 | auto configure_runs(benchmark::internal::Benchmark* b) 56 | { 57 | b->UseManualTime(); 58 | b->Unit(benchmark::kNanosecond); 59 | for(auto max_bytes: max_bytes_tests) 60 | { 61 | if(max_bytes / sizeof(T) > 0) 62 | { 63 | b->Arg(max_bytes); 64 | } 65 | } 66 | } 67 | } 68 | 69 | /////////////////////////////////////////////////////////////////////////////// 70 | // push_back_basline 71 | // 72 | 73 | // Establish a test baseline by only doing push_back without any allocations 74 | template 75 | static auto baseline_push_back(benchmark::State& state, tag, T x) 76 | { 77 | auto const max_size = static_cast(state.range(0)) / sizeof(T); 78 | auto v = init_vector(max_size, tag()); 79 | v.reserve(max_size); 80 | benchmark::DoNotOptimize(v.data()); 81 | 82 | for(auto _: state) 83 | { 84 | (void)_; 85 | // Do not count reserve + destructor 86 | auto start = std::chrono::high_resolution_clock::now(); 87 | std::fill_n(std::back_inserter(v), max_size, x); 88 | benchmark::ClobberMemory(); 89 | auto end = std::chrono::high_resolution_clock::now(); 90 | 91 | state.SetIterationTime(std::chrono::duration_cast>(end - start).count()); 92 | v.clear(); 93 | } 94 | } 95 | 96 | // trivially copyable types 97 | BENCHMARK_CAPTURE(baseline_push_back, std::vector, tag>(), 12345)->Apply(configure_runs); 98 | BENCHMARK_CAPTURE(baseline_push_back, pinned_vector, tag>(), 12345)->Apply(configure_runs); 99 | 100 | BENCHMARK_CAPTURE(baseline_push_back, 101 | std::vector, 102 | tag>(), 103 | bigval{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}) 104 | ->Apply(configure_runs); 105 | BENCHMARK_CAPTURE(baseline_push_back, 106 | pinned_vector, 107 | tag>(), 108 | bigval{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}) 109 | ->Apply(configure_runs); 110 | 111 | // std::string with small string optimization 112 | BENCHMARK_CAPTURE(baseline_push_back, std::vector, tag>(), std::string("abcd")) 113 | ->Apply(configure_runs); 114 | BENCHMARK_CAPTURE(baseline_push_back, 115 | pinned_vector, 116 | tag>(), 117 | std::string("abcd")) 118 | ->Apply(configure_runs); 119 | 120 | /////////////////////////////////////////////////////////////////////////////// 121 | // push_back 122 | // 123 | 124 | template 125 | static auto push_back(benchmark::State& state, tag, T x) 126 | { 127 | auto const max_size = static_cast(state.range(0)) / sizeof(T); 128 | for(auto _: state) 129 | { 130 | (void)_; 131 | auto v = init_vector(max_size, tag()); 132 | 133 | // Do not count destructor 134 | auto start = std::chrono::high_resolution_clock::now(); 135 | std::fill_n(std::back_inserter(v), max_size, x); 136 | benchmark::DoNotOptimize(v.data()); 137 | benchmark::ClobberMemory(); 138 | auto end = std::chrono::high_resolution_clock::now(); 139 | 140 | state.SetIterationTime(std::chrono::duration_cast>(end - start).count()); 141 | } 142 | } 143 | 144 | // trivially copyable types 145 | BENCHMARK_CAPTURE(push_back, std::vector, tag>(), 12345)->Apply(configure_runs); 146 | BENCHMARK_CAPTURE(push_back, pinned_vector, tag>(), 12345)->Apply(configure_runs); 147 | 148 | BENCHMARK_CAPTURE(push_back, std::vector, tag>(), bigval{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}) 149 | ->Apply(configure_runs); 150 | BENCHMARK_CAPTURE(push_back, pinned_vector, tag>(), bigval{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}) 151 | ->Apply(configure_runs); 152 | 153 | // std::string with small string optimization 154 | BENCHMARK_CAPTURE(push_back, std::vector, tag>(), std::string("abcd")) 155 | ->Apply(configure_runs); 156 | BENCHMARK_CAPTURE(push_back, pinned_vector, tag>(), std::string("abcd")) 157 | ->Apply(configure_runs); 158 | -------------------------------------------------------------------------------- /benchmarks/push_back_copy.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "bench-utils.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace bench_utils; 15 | 16 | namespace 17 | { 18 | auto alloc = bumb_allocator(std::size_t(9) * 1024 * 1024 * 1024); 19 | 20 | auto const max_bytes_tests = { 21 | std::int64_t(64), 22 | std::int64_t(128), 23 | std::int64_t(256), 24 | std::int64_t(512), 25 | std::int64_t(1) * 1024, 26 | std::int64_t(4) * 1024, 27 | std::int64_t(16) * 1024, 28 | std::int64_t(64) * 1024, 29 | std::int64_t(128) * 1024, 30 | std::int64_t(512) * 1024, 31 | std::int64_t(1) * 1024 * 1024, 32 | std::int64_t(4) * 1024 * 1024, 33 | std::int64_t(16) * 1024 * 1024, 34 | std::int64_t(64) * 1024 * 1024, 35 | std::int64_t(128) * 1024 * 1024, 36 | std::int64_t(512) * 1024 * 1024, 37 | std::int64_t(1) * 1024 * 1024 * 1024, 38 | std::int64_t(2) * 1024 * 1024 * 1024, 39 | // std::int64_t(4) * 1024 * 1024 * 1024, 40 | }; 41 | 42 | template 43 | auto configure_runs(benchmark::internal::Benchmark* b) 44 | { 45 | b->UseManualTime(); 46 | b->Unit(benchmark::kNanosecond); 47 | for(auto max_bytes: max_bytes_tests) 48 | { 49 | if(max_bytes / sizeof(T) > 0) 50 | { 51 | b->Arg(max_bytes); 52 | } 53 | } 54 | } 55 | } 56 | 57 | template 58 | static auto push_back_copy(benchmark::State& state, T x) 59 | { 60 | auto const max_size = static_cast(state.range(0)); 61 | auto const n = max_size / sizeof(T); 62 | 63 | alloc.reset(); 64 | 65 | for(auto _: state) 66 | { 67 | (void)_; 68 | auto v = std::vector>(bumb_allocator(alloc)); 69 | 70 | // Do not count destructor 71 | auto start = std::chrono::high_resolution_clock::now(); 72 | std::fill_n(std::back_inserter(v), n, x); 73 | benchmark::DoNotOptimize(v.data()); 74 | benchmark::ClobberMemory(); 75 | auto end = std::chrono::high_resolution_clock::now(); 76 | 77 | state.SetIterationTime(std::chrono::duration_cast>(end - start).count()); 78 | alloc.reset(); 79 | } 80 | } 81 | 82 | // trivially copyable types 83 | BENCHMARK_CAPTURE(push_back_copy, int, 12345)->Apply(configure_runs); 84 | 85 | BENCHMARK_CAPTURE(push_back_copy, bigval, bigval{1, 2, 3, 4, 5, 6, 7, 8, 9, 0})->Apply(configure_runs); 86 | 87 | // std::string with small string optimization 88 | BENCHMARK_CAPTURE(push_back_copy, small string, std::string("abcd"))->Apply(configure_runs); 89 | -------------------------------------------------------------------------------- /benchmarks/push_back_copy_and_alloc.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "bench-utils.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace bench_utils; 15 | 16 | namespace 17 | { 18 | auto const max_bytes_tests = { 19 | std::int64_t(64), 20 | std::int64_t(128), 21 | std::int64_t(256), 22 | std::int64_t(512), 23 | std::int64_t(1) * 1024, 24 | std::int64_t(4) * 1024, 25 | std::int64_t(16) * 1024, 26 | std::int64_t(64) * 1024, 27 | std::int64_t(128) * 1024, 28 | std::int64_t(512) * 1024, 29 | std::int64_t(1) * 1024 * 1024, 30 | std::int64_t(4) * 1024 * 1024, 31 | std::int64_t(16) * 1024 * 1024, 32 | std::int64_t(64) * 1024 * 1024, 33 | std::int64_t(128) * 1024 * 1024, 34 | std::int64_t(512) * 1024 * 1024, 35 | std::int64_t(1) * 1024 * 1024 * 1024, 36 | std::int64_t(2) * 1024 * 1024 * 1024, 37 | // std::int64_t(4) * 1024 * 1024 * 1024, 38 | }; 39 | 40 | template 41 | auto configure_runs(benchmark::internal::Benchmark* b) 42 | { 43 | b->UseManualTime(); 44 | b->Unit(benchmark::kNanosecond); 45 | for(auto max_bytes: max_bytes_tests) 46 | { 47 | if(max_bytes / sizeof(T) > 0) 48 | { 49 | b->Arg(max_bytes); 50 | } 51 | } 52 | } 53 | } 54 | 55 | template 56 | static auto push_back_copy_and_alloc(benchmark::State& state, T x) 57 | { 58 | auto const max_size = static_cast(state.range(0)); 59 | auto const n = max_size / sizeof(T); 60 | 61 | for(auto _: state) 62 | { 63 | (void)_; 64 | auto v = std::vector(); 65 | 66 | // Do not count destructor 67 | auto start = std::chrono::high_resolution_clock::now(); 68 | std::fill_n(std::back_inserter(v), n, x); 69 | benchmark::DoNotOptimize(v.data()); 70 | benchmark::ClobberMemory(); 71 | auto end = std::chrono::high_resolution_clock::now(); 72 | 73 | state.SetIterationTime(std::chrono::duration_cast>(end - start).count()); 74 | } 75 | } 76 | 77 | // trivially copyable types 78 | BENCHMARK_CAPTURE(push_back_copy_and_alloc, int, 12345)->Apply(configure_runs); 79 | 80 | BENCHMARK_CAPTURE(push_back_copy_and_alloc, bigval, bigval{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}) 81 | ->Apply(configure_runs); 82 | 83 | // std::string with small string optimization 84 | BENCHMARK_CAPTURE(push_back_copy_and_alloc, small string, std::string("abcd"))->Apply(configure_runs); 85 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(vmcontainer CXX) 4 | 5 | file(GLOB_RECURSE vmcontainer-sources CONFIGURE_DEPENDS *.cpp) 6 | add_library(vmcontainer ${vmcontainer-sources}) 7 | add_library(vmcontainer::vmcontainer ALIAS vmcontainer) 8 | 9 | # For CMAKE_INSTALL_ 10 | include(GNUInstallDirs) 11 | 12 | target_include_directories(vmcontainer PUBLIC 13 | $ 14 | $ 15 | ) 16 | 17 | target_compile_features(vmcontainer PUBLIC cxx_std_14) 18 | set_target_properties(vmcontainer PROPERTIES DEBUG_POSTFIX -d) 19 | 20 | install( 21 | DIRECTORY include/vmcontainer 22 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.hpp" 23 | ) 24 | 25 | install(TARGETS vmcontainer 26 | EXPORT vmcontainer-config 27 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 28 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 29 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 30 | ) 31 | 32 | # Since there is two projects, we need to export into the parent directory 33 | export( 34 | TARGETS vmcontainer 35 | NAMESPACE vmcontainer:: 36 | FILE "${PROJECT_BINARY_DIR}/../vmcontainer-config.cmake" 37 | ) 38 | 39 | install(EXPORT vmcontainer-config 40 | DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/vmcontainer" 41 | NAMESPACE vmcontainer:: 42 | ) 43 | -------------------------------------------------------------------------------- /lib/include/vmcontainer/detail.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #pragma once 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace mknejp 17 | { 18 | namespace vmcontainer 19 | { 20 | class max_size_t; 21 | class reservation_size_t; 22 | 23 | constexpr auto max_elements(std::size_t n) noexcept -> max_size_t; 24 | constexpr auto max_bytes(std::size_t n) noexcept -> max_size_t; 25 | constexpr auto max_pages(std::size_t n) noexcept -> max_size_t; 26 | 27 | constexpr auto num_bytes(std::size_t n) noexcept -> reservation_size_t; 28 | constexpr auto num_pages(std::size_t n) noexcept -> reservation_size_t; 29 | 30 | namespace detail 31 | { 32 | // simple utility type that stores a value of type T and when moved from assigns to it a value-initialized object. 33 | template 34 | struct value_init_when_moved_from; 35 | 36 | constexpr auto round_up(std::size_t num_bytes, std::size_t page_size) noexcept -> std::size_t; 37 | 38 | template 39 | auto construct_at(T* p, Args&&... args) -> T*; 40 | // C++17 algorithms 41 | template 42 | auto destroy_at(T* p) -> void; 43 | template 44 | auto destroy(ForwardIt first, ForwardIt last) -> void; 45 | template 46 | auto uninitialized_move(InputIt first, InputIt last, ForwardIt d_first) -> ForwardIt; 47 | template 48 | auto uninitialized_move_n(InputIt first, std::size_t count, ForwardIt d_first) -> std::pair; 49 | template 50 | auto uninitialized_value_construct_n(ForwardIt first, std::size_t count) -> ForwardIt; 51 | } 52 | } 53 | } 54 | 55 | /////////////////////////////////////////////////////////////////////////////// 56 | // reservation_size_t 57 | // 58 | 59 | class mknejp::vmcontainer::reservation_size_t 60 | { 61 | public: 62 | constexpr auto num_bytes(std::size_t page_size) const noexcept 63 | { 64 | switch(_unit) 65 | { 66 | case unit::bytes: 67 | return _count; 68 | case unit::pages: 69 | return _count * page_size; 70 | } 71 | // TODO: mark as unreachable 72 | // assert(false); 73 | return std::size_t(0); 74 | } 75 | 76 | private: 77 | friend constexpr auto num_bytes(std::size_t n) noexcept -> reservation_size_t; 78 | friend constexpr auto num_pages(std::size_t n) noexcept -> reservation_size_t; 79 | 80 | enum class unit 81 | { 82 | pages, 83 | bytes, 84 | }; 85 | 86 | constexpr reservation_size_t(unit unit, std::size_t count) noexcept : _unit(unit), _count(count) {} 87 | 88 | unit _unit = unit::bytes; 89 | std::size_t _count = 0; 90 | }; 91 | 92 | constexpr auto mknejp::vmcontainer::num_bytes(std::size_t n) noexcept -> reservation_size_t 93 | { 94 | return {reservation_size_t::unit::bytes, n}; 95 | } 96 | constexpr auto mknejp::vmcontainer::num_pages(std::size_t n) noexcept -> reservation_size_t 97 | { 98 | return {reservation_size_t::unit::pages, n}; 99 | } 100 | 101 | /////////////////////////////////////////////////////////////////////////////// 102 | // max_size_t 103 | // 104 | 105 | class mknejp::vmcontainer::max_size_t 106 | { 107 | public: 108 | template 109 | constexpr auto scaled_for_type() const noexcept -> reservation_size_t 110 | { 111 | switch(_unit) 112 | { 113 | case unit::elements: 114 | return num_bytes(sizeof(T) * _count); 115 | case unit::bytes: 116 | return num_bytes(_count); 117 | case unit::pages: 118 | return num_pages(_count); 119 | } 120 | // TODO: mark as unreachable 121 | // assert(false); 122 | return num_pages(0); 123 | } 124 | 125 | private: 126 | friend constexpr auto max_elements(std::size_t n) noexcept -> max_size_t; 127 | friend constexpr auto max_bytes(std::size_t n) noexcept -> max_size_t; 128 | friend constexpr auto max_pages(std::size_t n) noexcept -> max_size_t; 129 | 130 | enum class unit 131 | { 132 | elements, 133 | pages, 134 | bytes, 135 | }; 136 | 137 | constexpr max_size_t(unit unit, std::size_t count) noexcept : _unit(unit), _count(count) {} 138 | 139 | unit _unit = unit::bytes; 140 | std::size_t _count = 0; 141 | }; 142 | 143 | constexpr auto mknejp::vmcontainer::max_elements(std::size_t n) noexcept -> max_size_t 144 | { 145 | return {max_size_t::unit::elements, n}; 146 | } 147 | constexpr auto mknejp::vmcontainer::max_bytes(std::size_t n) noexcept -> max_size_t 148 | { 149 | return {max_size_t::unit::bytes, n}; 150 | } 151 | constexpr auto mknejp::vmcontainer::max_pages(std::size_t n) noexcept -> max_size_t 152 | { 153 | return {max_size_t::unit::pages, n}; 154 | } 155 | 156 | /////////////////////////////////////////////////////////////////////////////// 157 | // value_init_when_moved_from 158 | // 159 | 160 | template 161 | struct mknejp::vmcontainer::detail::value_init_when_moved_from 162 | { 163 | static_assert(std::is_trivial::value, ""); 164 | 165 | using value_type = T; 166 | 167 | value_init_when_moved_from() = default; 168 | /*implicit*/ value_init_when_moved_from(T value) noexcept : value(value) {} 169 | value_init_when_moved_from(value_init_when_moved_from const& other) = default; 170 | value_init_when_moved_from(value_init_when_moved_from&& other) noexcept : value(other.value) { other.value = T{}; } 171 | value_init_when_moved_from& operator=(value_init_when_moved_from const& other) & = default; 172 | value_init_when_moved_from& operator=(value_init_when_moved_from&& other) & noexcept 173 | { 174 | auto temp = other.value; 175 | other.value = T{}; 176 | value = temp; 177 | return *this; 178 | } 179 | T value = {}; 180 | 181 | /*implicit*/ operator T&() & noexcept { return value; } 182 | /*implicit*/ operator T const&() const& noexcept { return value; } 183 | /*implicit*/ operator T &&() && noexcept { return value; } 184 | }; 185 | 186 | /////////////////////////////////////////////////////////////////////////////// 187 | // algorithms 188 | // 189 | 190 | constexpr auto mknejp::vmcontainer::detail::round_up(std::size_t num_bytes, std::size_t page_size) noexcept 191 | -> std::size_t 192 | { 193 | return ((num_bytes + page_size - 1) / page_size) * page_size; 194 | } 195 | 196 | template 197 | auto mknejp::vmcontainer::detail::construct_at(T* p, Args&&... args) -> T* 198 | { 199 | return ::new(static_cast(p)) T(std::forward(args)...); 200 | } 201 | 202 | template 203 | auto mknejp::vmcontainer::detail::destroy_at(T* p) -> void 204 | { 205 | p->~T(); 206 | } 207 | 208 | template 209 | auto mknejp::vmcontainer::detail::destroy(ForwardIt first, ForwardIt last) -> void 210 | { 211 | std::for_each(first, last, [](auto& x) { destroy_at(std::addressof(x)); }); 212 | } 213 | 214 | template 215 | auto mknejp::vmcontainer::detail::uninitialized_move(InputIt first, InputIt last, ForwardIt d_first) -> ForwardIt 216 | { 217 | return std::uninitialized_copy(std::make_move_iterator(first), std::make_move_iterator(last), d_first); 218 | } 219 | 220 | template 221 | auto mknejp::vmcontainer::detail::uninitialized_move_n(InputIt first, std::size_t count, ForwardIt d_first) 222 | -> std::pair 223 | { 224 | auto current = d_first; 225 | try 226 | { 227 | for(std::size_t i = 0; i < count; ++first, (void)++current, ++i) 228 | { 229 | construct_at(std::addressof(*current), std::move(*first)); 230 | } 231 | } 232 | catch(...) 233 | { 234 | destroy(d_first, current); 235 | throw; 236 | } 237 | return {first, current}; 238 | } 239 | 240 | template 241 | auto mknejp::vmcontainer::detail::uninitialized_value_construct_n(ForwardIt first, std::size_t count) -> ForwardIt 242 | { 243 | auto current = first; 244 | try 245 | { 246 | for(std::size_t i = 0; i < count; ++i, (void)++current) 247 | { 248 | construct_at(std::addressof(*current)); 249 | } 250 | } 251 | catch(...) 252 | { 253 | destroy(first, current); 254 | throw; 255 | } 256 | return current; 257 | } 258 | -------------------------------------------------------------------------------- /lib/include/vmcontainer/pinned_vector.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #pragma once 8 | #include "vmcontainer/detail.hpp" 9 | #include "vmcontainer/vm.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace mknejp 20 | { 21 | namespace vmcontainer 22 | { 23 | struct pinned_vector_traits; 24 | 25 | template 26 | class pinned_vector; 27 | } 28 | } 29 | 30 | /////////////////////////////////////////////////////////////////////////////// 31 | // pinned_vector_traits 32 | // 33 | 34 | struct mknejp::vmcontainer::pinned_vector_traits 35 | { 36 | using storage_type = vm::page_stack; 37 | using growth_factor = std::ratio<2, 1>; 38 | }; 39 | 40 | /////////////////////////////////////////////////////////////////////////////// 41 | // pinned_vector 42 | // 43 | 44 | template 45 | class mknejp::vmcontainer::pinned_vector 46 | { 47 | public: 48 | static_assert(std::is_destructible::value, "value_type must satisfy Destructible concept"); 49 | static_assert(std::ratio_greater>::value, 50 | "growth factor must be greater than 1"); 51 | 52 | using value_type = T; 53 | using size_type = std::size_t; 54 | using difference_type = std::ptrdiff_t; 55 | using reference = T&; 56 | using const_reference = T const&; 57 | using pointer = T*; 58 | using const_pointer = T const*; 59 | 60 | using iterator = T*; 61 | using const_iterator = T const*; 62 | using reverse_iterator = std::reverse_iterator; 63 | using const_reverse_iterator = std::reverse_iterator; 64 | 65 | using traits_type = Traits; 66 | using storage_type = typename Traits::storage_type; 67 | 68 | // constructors 69 | pinned_vector() = default; 70 | explicit pinned_vector(max_size_t max_size) : _storage(max_size.scaled_for_type()) {} 71 | 72 | pinned_vector(max_size_t max_size, std::initializer_list init) : pinned_vector(max_size) { insert(end(), init); } 73 | 74 | template::iterator_category>::value>::type> 78 | // requires InputIterator 79 | pinned_vector(max_size_t max_size, InputIter first, InputIter last) : pinned_vector(max_size) 80 | { 81 | insert(end(), first, last); 82 | } 83 | 84 | template::value>::type> 85 | pinned_vector(max_size_t max_size, size_type count, T const& value) : pinned_vector(max_size) 86 | { 87 | insert(end(), count, value); 88 | } 89 | 90 | template::value>::type> 91 | pinned_vector(max_size_t max_size, size_type count) : pinned_vector(max_size) 92 | { 93 | reserve(count); 94 | _end = detail::uninitialized_value_construct_n(data(), count); 95 | } 96 | 97 | // Special members 98 | pinned_vector(pinned_vector const& other) : _storage(num_bytes(other._storage.reserved_bytes())) 99 | { 100 | _storage.resize(other.size() * sizeof(T)); 101 | _end = std::uninitialized_copy(other.cbegin(), other.cend(), data()); 102 | } 103 | pinned_vector(pinned_vector&& other) = default; 104 | pinned_vector& operator=(pinned_vector const& other) & 105 | { 106 | if(this != std::addressof(other)) 107 | { 108 | *this = pinned_vector(other); 109 | } 110 | return *this; 111 | } 112 | pinned_vector& operator=(pinned_vector&& other) & noexcept 113 | { 114 | auto temp = std::move(other); 115 | swap(temp); 116 | return *this; 117 | } 118 | pinned_vector& operator=(std::initializer_list init) & 119 | { 120 | assign(init); 121 | return *this; 122 | } 123 | 124 | ~pinned_vector() { clear(); } 125 | 126 | // Assign 127 | auto assign(std::size_t count, T const& value) -> void 128 | { 129 | clear(); 130 | insert(end(), count, value); 131 | } 132 | auto assign(std::initializer_list init) -> void { assign(init.begin(), init.end()); } 133 | template 134 | // requires InputIterator 135 | auto assign(InputIter first, InputIter last) -> typename std::enable_if< 136 | std::is_base_of::iterator_category>::value, 137 | void>::type 138 | { 139 | clear(); 140 | insert(end(), first, last, typename std::iterator_traits::iterator_category()); 141 | } 142 | 143 | // Element access 144 | auto at(size_type pos) -> T& 145 | { 146 | if(pos >= size()) 147 | { 148 | throw std::out_of_range("index out of range"); 149 | } 150 | return data()[pos]; 151 | } 152 | auto at(size_type pos) const -> T const& 153 | { 154 | if(pos >= size()) 155 | { 156 | throw std::out_of_range("index out of range"); 157 | } 158 | return data()[pos]; 159 | } 160 | 161 | auto operator[](size_type pos) -> T& 162 | { 163 | assert(pos < size()); 164 | return data()[pos]; 165 | } 166 | auto operator[](size_type pos) const -> T const& 167 | { 168 | assert(pos < size()); 169 | return data()[pos]; 170 | } 171 | 172 | auto front() -> T& 173 | { 174 | assert(size() > 0); 175 | return (*this)[0]; 176 | } 177 | auto front() const -> T const& 178 | { 179 | assert(size() > 0); 180 | return (*this)[0]; 181 | } 182 | 183 | auto back() -> T& 184 | { 185 | assert(size() > 0); 186 | return (*this)[size() - 1]; 187 | } 188 | auto back() const -> T const& 189 | { 190 | assert(size() > 0); 191 | return (*this)[size() - 1]; 192 | } 193 | 194 | auto data() noexcept -> T* { return static_cast(_storage.base()); } 195 | auto data() const noexcept -> T const* { return static_cast(_storage.base()); } 196 | 197 | // Iterators 198 | 199 | auto begin() noexcept -> iterator { return iterator(data()); } 200 | auto end() noexcept -> iterator { return iterator(_end); } 201 | 202 | auto begin() const noexcept -> const_iterator { return cbegin(); } 203 | auto end() const noexcept -> const_iterator { return cend(); } 204 | 205 | auto cbegin() const noexcept -> const_iterator { return const_iterator(data()); } 206 | auto cend() const noexcept -> const_iterator { return const_iterator(_end); } 207 | 208 | auto rbegin() noexcept -> reverse_iterator { return reverse_iterator(end()); } 209 | auto rend() noexcept -> reverse_iterator { return reverse_iterator(begin()); } 210 | 211 | auto rbegin() const noexcept -> const_reverse_iterator { return const_reverse_iterator(cend()); } 212 | auto rend() const noexcept -> const_reverse_iterator { return const_reverse_iterator(cbegin()); } 213 | 214 | auto crbegin() const noexcept -> const_reverse_iterator { return const_reverse_iterator(cend()); } 215 | auto crend() const noexcept -> const_reverse_iterator { return const_reverse_iterator(cbegin()); } 216 | 217 | // Capacity 218 | 219 | auto empty() const noexcept -> bool { return begin() == end(); } 220 | auto size() const noexcept -> size_type { return static_cast(_end - data()); } 221 | auto max_size() const noexcept -> size_type { return _storage.reserved_bytes() / sizeof(T); } 222 | auto reserve(size_type new_cap) -> void 223 | { 224 | assert(new_cap <= max_size()); 225 | if(new_cap > capacity()) 226 | { 227 | _storage.resize(new_cap * sizeof(T)); 228 | } 229 | } 230 | auto capacity() const noexcept -> size_type { return _storage.committed_bytes() / sizeof(T); } 231 | auto shrink_to_fit() -> void 232 | { 233 | if(capacity() > size()) 234 | { 235 | _storage.resize(size() * sizeof(T)); 236 | } 237 | } 238 | auto page_size() const noexcept -> std::size_t { return _storage.page_size(); } 239 | 240 | // Modifiers 241 | 242 | auto clear() noexcept -> void 243 | { 244 | detail::destroy(begin(), end()); 245 | _end = data(); 246 | } 247 | auto insert(const_iterator pos, T const& value) -> 248 | typename std::enable_if::value, iterator>::type 249 | { 250 | assert(is_valid_last_iterator(pos)); 251 | return iterator(std::addressof(emplace(pos, value))); 252 | } 253 | template 254 | auto insert(const_iterator pos, T&& value) -> 255 | typename std::enable_if::value, iterator>::type 256 | { 257 | assert(is_valid_last_iterator(pos)); 258 | return iterator(std::addressof(emplace(pos, std::move(value)))); 259 | } 260 | auto insert(const_iterator pos, size_type count, const T& value) -> iterator 261 | { 262 | assert(is_valid_last_iterator(pos)); 263 | return range_insert_impl(pos, count, [&value](T* first, T* last) { std::fill(first, last, value); }); 264 | } 265 | 266 | template 267 | auto insert(const_iterator pos, InputIter first, InputIter last) -> typename std::enable_if< 268 | std::is_base_of::iterator_category>::value, 269 | iterator>::type 270 | { 271 | assert(is_valid_last_iterator(pos)); 272 | return insert(pos, first, last, typename std::iterator_traits::iterator_category()); 273 | } 274 | 275 | private: 276 | template 277 | auto insert(const_iterator pos, InputIter first, InputIter last, std::input_iterator_tag) -> iterator 278 | { 279 | for(auto it = pos; first != last; ++first, ++it) 280 | { 281 | insert(it, *first); 282 | } 283 | return to_iterator(pos); 284 | } 285 | 286 | template 287 | auto insert(const_iterator pos, InputIter first, InputIter last, std::forward_iterator_tag) -> iterator 288 | { 289 | return range_insert_impl(pos, static_cast(std::distance(first, last)), [&](T* d_first, T* d_last) { 290 | std::copy(first, last, d_first); 291 | }); 292 | } 293 | 294 | template 295 | auto range_insert_impl(const_iterator pos, size_type count, F fill) -> iterator 296 | { 297 | auto* const p = to_pointer(pos); 298 | if(count > 0) 299 | { 300 | grow_if_necessary(count); 301 | if(p != _end) 302 | { 303 | detail::uninitialized_move(_end - count, _end.value, _end.value); 304 | auto const rest = _end - p - count; 305 | std::move_backward(p, p + rest, _end - rest); 306 | } 307 | fill(p, p + count); 308 | _end += count; 309 | } 310 | return iterator(p); 311 | } 312 | 313 | public: 314 | auto insert(const_iterator pos, std::initializer_list ilist) -> iterator 315 | { 316 | assert(is_valid_last_iterator(pos)); 317 | return range_insert_impl( 318 | pos, ilist.size(), [&](T* d_first, T* d_last) { std::copy(ilist.begin(), ilist.end(), d_first); }); 319 | } 320 | template 321 | auto emplace(const_iterator pos, Args&&... args) -> 322 | typename std::enable_if::value && std::is_move_constructible::value 323 | && std::is_move_assignable::value, 324 | T&>::type 325 | { 326 | assert(is_valid_last_iterator(pos)); 327 | grow_if_necessary(1); 328 | auto* p = to_pointer(pos); 329 | if(p != _end) 330 | { 331 | detail::uninitialized_move(_end - 1, _end.value, _end.value); 332 | std::move_backward(p, _end - 1, p + 1); 333 | detail::destroy_at(p); 334 | } 335 | auto* x = detail::construct_at(p, std::forward(args)...); 336 | ++_end; 337 | return *x; 338 | } 339 | auto erase(const_iterator pos) -> iterator 340 | { 341 | assert(is_valid_iterator(pos)); 342 | std::move(to_iterator(pos) + 1, end(), to_iterator(pos)); 343 | detail::destroy_at(--_end); 344 | return pos; 345 | } 346 | auto erase(const_iterator first, const_iterator last) -> iterator 347 | { 348 | assert(is_valid_last_iterator(last)); 349 | assert(first <= last); 350 | std::move(to_iterator(last), end(), to_iterator(first)); 351 | detail::destroy(to_iterator(last), end()); 352 | _end = to_pointer(last); 353 | return last; 354 | } 355 | template 356 | auto push_back(T const& value) -> typename std::enable_if::value, T&>::type 357 | { 358 | return emplace_back(value); 359 | } 360 | template 361 | auto push_back(T&& value) -> typename std::enable_if::value, T&>::type 362 | { 363 | return emplace_back(std::move(value)); 364 | } 365 | template 366 | auto emplace_back(Args&&... args) -> typename std::enable_if::value, T&>::type 367 | { 368 | grow_if_necessary(1); 369 | detail::construct_at(_end.value, std::forward(args)...); 370 | return *_end++; 371 | } 372 | auto pop_back() -> void 373 | { 374 | assert(!empty()); 375 | detail::destroy_at(--_end); 376 | } 377 | template::value>::type> 378 | auto resize(size_type count) -> void 379 | { 380 | if(count > size()) 381 | { 382 | reserve(count); 383 | _end = detail::uninitialized_value_construct_n(_end.value, count - size()); 384 | } 385 | else if(count < size()) 386 | { 387 | auto const delta = size() - count; 388 | detail::destroy(_end - delta, _end.value); 389 | _end -= delta; 390 | shrink_to_fit(); 391 | } 392 | } 393 | auto resize(size_type count, T const& value) -> void 394 | { 395 | auto const old_size = size(); 396 | if(count > old_size) 397 | { 398 | reserve(count); 399 | _end = std::uninitialized_fill_n(_end.value, count - old_size, value); 400 | } 401 | else if(count < old_size) 402 | { 403 | auto const delta = old_size - count; 404 | detail::destroy(_end - delta, _end.value); 405 | _end -= delta; 406 | shrink_to_fit(); 407 | } 408 | } 409 | auto swap(pinned_vector& other) noexcept -> void 410 | { 411 | using std::swap; 412 | swap(_storage, other._storage); 413 | swap(_end, other._end); 414 | } 415 | 416 | private: 417 | auto grow_if_necessary(std::size_t n) -> void 418 | { 419 | assert(max_size() - size() >= n); 420 | auto const new_size = size() + n; 421 | if(new_size > capacity()) 422 | { 423 | auto const new_cap = capacity() * Traits::growth_factor::num / Traits::growth_factor::den; 424 | reserve(std::min(max_size(), std::max(new_cap, new_size))); 425 | } 426 | } 427 | 428 | auto is_valid_iterator(const_iterator it) const noexcept -> bool 429 | { 430 | return to_pointer(it) >= data() && to_pointer(it) < _end; 431 | } 432 | auto is_valid_last_iterator(const_iterator it) const noexcept -> bool 433 | { 434 | return to_pointer(it) >= data() && to_pointer(it) <= _end; 435 | } 436 | 437 | auto to_iterator(const_iterator it) noexcept -> iterator { return iterator(to_pointer(it)); } 438 | auto to_pointer(const_iterator it) const noexcept -> T const* { return data() + (it - cbegin()); } 439 | auto to_pointer(const_iterator it) noexcept -> T* { return data() + (it - cbegin()); } 440 | 441 | storage_type _storage; 442 | detail::value_init_when_moved_from _end = data(); 443 | }; 444 | 445 | namespace mknejp 446 | { 447 | namespace vmcontainer 448 | { 449 | template 450 | auto swap(pinned_vector& lhs, pinned_vector& rhs) noexcept -> void 451 | { 452 | lhs.swap(rhs); 453 | } 454 | 455 | template() == std::declval())> 456 | auto operator==(pinned_vector const& lhs, pinned_vector const& rhs) -> bool 457 | { 458 | return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); 459 | } 460 | template() == std::declval())> 461 | auto operator!=(pinned_vector const& lhs, pinned_vector const& rhs) -> bool 462 | { 463 | return !(lhs == rhs); 464 | } 465 | template() == std::declval())> 466 | auto operator<(pinned_vector const& lhs, pinned_vector const& rhs) -> bool 467 | { 468 | return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); 469 | } 470 | template() == std::declval())> 471 | auto operator>(pinned_vector const& lhs, pinned_vector const& rhs) -> bool 472 | { 473 | return rhs < lhs; 474 | } 475 | template() == std::declval())> 476 | auto operator<=(pinned_vector const& lhs, pinned_vector const& rhs) -> bool 477 | { 478 | return !(rhs < lhs); 479 | } 480 | template() == std::declval())> 481 | auto operator>=(pinned_vector const& lhs, pinned_vector const& rhs) -> bool 482 | { 483 | return !(lhs < rhs); 484 | } 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /lib/include/vmcontainer/vm.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #pragma once 8 | #include "vmcontainer/detail.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace mknejp 15 | { 16 | namespace vmcontainer 17 | { 18 | namespace vm 19 | { 20 | struct system_default; 21 | 22 | class reservation; 23 | template 24 | class reservation_base; 25 | 26 | class page_stack; 27 | template 28 | class page_stack_base; 29 | } 30 | } 31 | } 32 | 33 | /////////////////////////////////////////////////////////////////////////////// 34 | // default_vm_traits 35 | // 36 | 37 | struct mknejp::vmcontainer::vm::system_default 38 | { 39 | static auto reserve(std::size_t num_bytes) -> void*; 40 | static auto free(void* offset, std::size_t num_bytes) -> void; 41 | static auto commit(void* offset, std::size_t num_bytes) -> void; 42 | static auto decommit(void* offset, std::size_t num_bytes) -> void; 43 | 44 | static auto page_size() noexcept -> std::size_t { return _page_size; } 45 | 46 | private: 47 | static std::size_t const _page_size; 48 | }; 49 | 50 | /////////////////////////////////////////////////////////////////////////////// 51 | // reservation 52 | // 53 | 54 | template 55 | class mknejp::vmcontainer::vm::reservation_base 56 | { 57 | public: 58 | reservation_base() = default; 59 | explicit reservation_base(reservation_size_t reserved_bytes) 60 | { 61 | auto num_bytes = reserved_bytes.num_bytes(VirtualMemorySystem::page_size()); 62 | if(num_bytes > 0) 63 | { 64 | num_bytes = detail::round_up(num_bytes, VirtualMemorySystem::page_size()); 65 | _reservation.reset(VirtualMemorySystem::reserve(num_bytes)); 66 | _reservation.get_deleter().reserved_bytes = num_bytes; 67 | } 68 | } 69 | 70 | auto base() const noexcept -> void* { return _reservation.get(); } 71 | auto reserved_bytes() const noexcept -> std::size_t { return _reservation.get_deleter().reserved_bytes; } 72 | 73 | private: 74 | struct deleter 75 | { 76 | auto operator()(void* p) const -> void { VirtualMemorySystem::free(p, reserved_bytes); } 77 | detail::value_init_when_moved_from reserved_bytes = 0; 78 | }; 79 | 80 | std::unique_ptr _reservation; 81 | }; 82 | 83 | class mknejp::vmcontainer::vm::reservation final : public reservation_base 84 | { 85 | using reservation_base::reservation_base; 86 | }; 87 | 88 | /////////////////////////////////////////////////////////////////////////////// 89 | // page_stack 90 | // 91 | 92 | template 93 | class mknejp::vmcontainer::vm::page_stack_base 94 | { 95 | public: 96 | page_stack_base() = default; 97 | explicit page_stack_base(reservation_size_t reserved_bytes) : _reservation(reserved_bytes) {} 98 | explicit page_stack_base(reservation_base reservation) : _reservation(std::move(reservation)) {} 99 | 100 | auto resize(std::size_t new_bytes) -> std::size_t 101 | { 102 | new_bytes = detail::round_up(new_bytes, page_size()); 103 | if(new_bytes > committed_bytes()) 104 | { 105 | VirtualMemorySystem::commit(static_cast(base()) + committed_bytes(), new_bytes - committed_bytes()); 106 | } 107 | else if(new_bytes < committed_bytes()) 108 | { 109 | VirtualMemorySystem::decommit(static_cast(base()) + new_bytes, committed_bytes() - new_bytes); 110 | } 111 | _committed_bytes = new_bytes; 112 | return committed_bytes(); 113 | } 114 | 115 | auto base() const noexcept -> void* { return _reservation.base(); } 116 | auto committed_bytes() const noexcept -> std::size_t { return _committed_bytes; } 117 | auto reserved_bytes() const noexcept -> std::size_t { return _reservation.reserved_bytes(); } 118 | auto page_size() const noexcept -> std::size_t { return VirtualMemorySystem::page_size(); } 119 | 120 | private: 121 | reservation_base _reservation; 122 | detail::value_init_when_moved_from _committed_bytes = 0; 123 | }; 124 | 125 | class mknejp::vmcontainer::vm::page_stack final : public page_stack_base 126 | { 127 | using page_stack_base::page_stack_base; 128 | }; 129 | -------------------------------------------------------------------------------- /lib/src/vmcontainer/vm.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/vm.hpp" 8 | 9 | #ifdef WIN32 10 | # ifndef NOMINMAX 11 | # define NOMINMAX 12 | # endif 13 | # ifndef WIN32_LEAN_AND_MEAN 14 | # define WIN32_LEAN_AND_MEAN 15 | # endif 16 | # include 17 | #else 18 | # include 19 | # include 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | auto mknejp::vmcontainer::vm::system_default::reserve(std::size_t num_bytes) -> void* 27 | { 28 | assert(num_bytes > 0); 29 | 30 | #ifdef WIN32 31 | auto const offset = ::VirtualAlloc(nullptr, num_bytes, MEM_RESERVE, PAGE_NOACCESS); 32 | if(offset == nullptr) 33 | { 34 | auto const err = ::GetLastError(); 35 | throw std::system_error(std::error_code(err, std::system_category()), "virtual memory reservation failed"); 36 | } 37 | return offset; 38 | #else 39 | auto const offset = ::mmap(nullptr, num_bytes, PROT_NONE, MAP_ANON | MAP_PRIVATE, 0, 0); 40 | if(offset == MAP_FAILED) 41 | { 42 | throw std::system_error(std::error_code(errno, std::system_category()), "virtual memory reservation failed"); 43 | } 44 | return offset; 45 | #endif 46 | } 47 | 48 | auto mknejp::vmcontainer::vm::system_default::free(void* offset, std::size_t num_bytes) -> void 49 | { 50 | #ifdef WIN32 51 | auto const result = ::VirtualFree(offset, 0, MEM_RELEASE); 52 | (void)result; 53 | assert(result != 0); 54 | #else 55 | auto const result = ::munmap(offset, num_bytes); 56 | (void)result; 57 | assert(result == 0); 58 | #endif 59 | } 60 | 61 | auto mknejp::vmcontainer::vm::system_default::commit(void* offset, std::size_t num_bytes) -> void 62 | { 63 | assert(num_bytes > 0); 64 | 65 | #ifdef WIN32 66 | auto const result = ::VirtualAlloc(offset, num_bytes, MEM_COMMIT, PAGE_READWRITE); 67 | if(result == nullptr) 68 | { 69 | throw std::bad_alloc(); 70 | } 71 | #else 72 | auto const result = ::mprotect(offset, num_bytes, PROT_READ | PROT_WRITE); 73 | if(result != 0) 74 | { 75 | throw std::bad_alloc(); 76 | } 77 | #endif 78 | } 79 | 80 | auto mknejp::vmcontainer::vm::system_default::decommit(void* offset, std::size_t num_bytes) -> void 81 | { 82 | #ifdef WIN32 83 | auto const result = ::VirtualFree(offset, num_bytes, MEM_DECOMMIT); 84 | (void)result; 85 | assert(result != 0); 86 | #else 87 | auto const result1 = ::madvise(offset, num_bytes, MADV_DONTNEED); 88 | (void)result1; 89 | assert(result1 == 0); 90 | auto const result2 = ::mprotect(offset, num_bytes, PROT_NONE); 91 | (void)result2; 92 | assert(result2 == 0); 93 | #endif 94 | } 95 | 96 | std::size_t const mknejp::vmcontainer::vm::system_default::_page_size = 97 | #ifdef WIN32 98 | []() { 99 | auto info = SYSTEM_INFO{}; 100 | ::GetSystemInfo(&info); 101 | return static_cast(info.dwPageSize); 102 | }(); 103 | #else 104 | static_cast(::getpagesize()); 105 | #endif 106 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Catch2 CONFIG REQUIRED) 2 | 3 | file(GLOB_RECURSE vmcontainer.test-sources CONFIGURE_DEPENDS *.cpp) 4 | add_executable(vmcontainer.test ${vmcontainer.test-sources}) 5 | 6 | target_link_libraries(vmcontainer.test PRIVATE vmcontainer::vmcontainer Catch2::Catch2) 7 | target_include_directories(vmcontainer.test PRIVATE .) 8 | 9 | add_test(NAME vmcontainer.test COMMAND vmcontainer.test) 10 | -------------------------------------------------------------------------------- /tests/allocator_mocks.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #pragma once 8 | 9 | #include "catch.hpp" 10 | 11 | #include 12 | #include 13 | 14 | namespace vmcontainer_test 15 | { 16 | template 17 | struct virtual_memory_system_stub 18 | { 19 | static std::functionvoid*> reserve; 20 | static std::functionvoid> free; 21 | static std::functionvoid> commit; 22 | static std::functionvoid> decommit; 23 | static std::functionsize_t> page_size; 24 | 25 | static auto reset() -> void 26 | { 27 | reserve = [](std::size_t) -> void* { 28 | FAIL("virtual_memory_system_stub::reserve() called without setup"); 29 | return nullptr; 30 | }; 31 | free = [](void*, std::size_t) { FAIL("virtual_memory_system_stub::free() called without setup"); }; 32 | commit = [](void*, std::size_t) { FAIL("virtual_memory_system_stub::commit() called without setup"); }; 33 | decommit = [](void*, std::size_t) { FAIL("virtual_memory_system_stub::decommit() called without setup"); }; 34 | page_size = []() -> std::size_t { 35 | FAIL("virtual_memory_system_stub::page_size() called without setup"); 36 | return 0; 37 | }; 38 | } 39 | }; 40 | 41 | template 42 | std::functionvoid*> virtual_memory_system_stub::reserve; 43 | template 44 | std::functionvoid> virtual_memory_system_stub::free; 45 | template 46 | std::functionvoid> virtual_memory_system_stub::commit; 47 | template 48 | std::functionvoid> virtual_memory_system_stub::decommit; 49 | template 50 | std::functionsize_t> virtual_memory_system_stub::page_size; 51 | 52 | template 53 | class tracking_allocator 54 | { 55 | public: 56 | using vm_stub = virtual_memory_system_stub; 57 | 58 | tracking_allocator() { vm_stub::reset(); } 59 | 60 | auto expect_reserve(void* block, std::size_t expected_size) -> void 61 | { 62 | vm_stub::reserve = [this, block, expected_size](std::size_t num_bytes) { 63 | REQUIRE(num_bytes == expected_size); 64 | auto result = _reservations.insert(std::make_pair(block, num_bytes)); 65 | REQUIRE(result.second == true); 66 | ++_reserve_calls; 67 | return block; 68 | }; 69 | }; 70 | 71 | auto expect_free(void* block) -> void 72 | { 73 | vm_stub::free = [this, block](void* p, std::size_t num_bytes) { 74 | REQUIRE(block == p); 75 | auto it = _reservations.find(p); 76 | REQUIRE(it != _reservations.end()); 77 | REQUIRE(it->second == num_bytes); 78 | _reservations.erase(it); 79 | ++_free_calls; 80 | }; 81 | }; 82 | 83 | auto expect_commit(void* offset, std::size_t expected_size) -> void 84 | { 85 | vm_stub::commit = [this, offset, expected_size](void* p, std::size_t num_bytes) { 86 | REQUIRE(num_bytes == expected_size); 87 | REQUIRE(offset == p); 88 | ++_commit_calls; 89 | }; 90 | }; 91 | 92 | auto expect_commit_and_fail(void* offset, std::size_t expected_size) -> void 93 | { 94 | vm_stub::commit = [this, offset, expected_size](void* p, std::size_t num_bytes) { 95 | REQUIRE(num_bytes == expected_size); 96 | REQUIRE(offset == p); 97 | ++_commit_calls; 98 | throw std::bad_alloc(); 99 | }; 100 | }; 101 | 102 | auto expect_decommit(void* offset, std::size_t expected_size) -> void 103 | { 104 | vm_stub::decommit = [this, offset, expected_size](void* p, std::size_t num_bytes) { 105 | REQUIRE(num_bytes == expected_size); 106 | REQUIRE(offset == p); 107 | ++_decommit_calls; 108 | }; 109 | }; 110 | 111 | auto set_page_size(std::size_t n) 112 | { 113 | vm_stub::page_size = [n] { return n; }; 114 | } 115 | 116 | auto reservations() const noexcept -> std::size_t { return _reservations.size(); } 117 | auto reserve_calls() const noexcept -> int { return _reserve_calls; } 118 | auto free_calls() const noexcept -> int { return _free_calls; } 119 | auto commit_calls() const noexcept -> int { return _commit_calls; } 120 | auto decommit_calls() const noexcept -> int { return _decommit_calls; } 121 | 122 | private: 123 | std::map _reservations; 124 | 125 | int _reserve_calls = 0; 126 | int _free_calls = 0; 127 | int _commit_calls = 0; 128 | int _decommit_calls = 0; 129 | }; 130 | } 131 | -------------------------------------------------------------------------------- /tests/detail/algorithms.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/detail.hpp" 8 | 9 | #include 10 | 11 | #include 12 | 13 | using namespace mknejp::vmcontainer::detail; 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // algorithms 17 | // 18 | -------------------------------------------------------------------------------- /tests/detail/detail.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/detail.hpp" 8 | 9 | #include 10 | 11 | #include 12 | 13 | using namespace mknejp::vmcontainer; 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // reservation_size_t 17 | // 18 | 19 | static_assert(num_bytes(5).num_bytes(1000) == 5, ""); 20 | static_assert(num_pages(5).num_bytes(1000) == 5 * 1000, ""); 21 | 22 | /////////////////////////////////////////////////////////////////////////////// 23 | // max_size_t 24 | // 25 | 26 | static_assert(max_elements(5).scaled_for_type().num_bytes(1000) == 5 * sizeof(int), ""); 27 | static_assert(max_bytes(5).scaled_for_type().num_bytes(1000) == 5, ""); 28 | static_assert(max_pages(5).scaled_for_type().num_bytes(1000) == 5 * 1000, ""); 29 | -------------------------------------------------------------------------------- /tests/detail/value_init_when_moved_from.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/detail.hpp" 8 | 9 | #include 10 | 11 | #include 12 | 13 | using namespace mknejp::vmcontainer::detail; 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // value_init_when_moved_from 17 | // 18 | 19 | TEST_CASE("detail::value_init_when_moved_from") 20 | { 21 | static_assert(std::is_nothrow_default_constructible>::value, ""); 22 | static_assert(std::is_nothrow_move_constructible>::value, ""); 23 | static_assert(std::is_nothrow_move_assignable>::value, ""); 24 | 25 | GIVEN("a default constructed value_init_when_moved_from") 26 | { 27 | auto x = value_init_when_moved_from(); 28 | using pointer = int*; 29 | auto p = value_init_when_moved_from(); 30 | THEN("its value compares equal to a value-initialized value_type") 31 | { 32 | REQUIRE(x.value == int{}); 33 | REQUIRE(p.value == pointer{}); 34 | REQUIRE(p.value == nullptr); 35 | } 36 | } 37 | 38 | GIVEN("a value_init_when_moved_from object initialized with a value") 39 | { 40 | auto x = value_init_when_moved_from(5); 41 | 42 | THEN("its value compares equal to the same value") { REQUIRE(x.value == 5); } 43 | 44 | THEN("it implicitly converts to its value_type") 45 | { 46 | int i = x; 47 | REQUIRE(i == 5); 48 | } 49 | 50 | THEN("it can be implicitly assigned from its value_type") 51 | { 52 | x = 10; 53 | REQUIRE(x.value == 10); 54 | } 55 | 56 | WHEN("moved from via move-construction") 57 | { 58 | auto y = std::move(x); 59 | 60 | THEN("its value compares equal to a value-initialized value_type") { REQUIRE(x.value == int{}); } 61 | THEN("the newly constructed object contains the original value") { REQUIRE(y.value == 5); } 62 | } 63 | 64 | WHEN("moved from via move-assignment") 65 | { 66 | auto y = value_init_when_moved_from(); 67 | y = std::move(x); 68 | 69 | THEN("its value compares equal to a value-initialized value_type") { REQUIRE(x.value == int{}); } 70 | THEN("the assigned-to object contains the original value") { REQUIRE(y.value == 5); } 71 | } 72 | 73 | WHEN("copied via copy-construction") 74 | { 75 | auto y = x; 76 | 77 | THEN("its value doesn't change") { REQUIRE(x.value == 5); } 78 | THEN("the newly constructed object contains an equivalent value") { REQUIRE(y.value == 5); } 79 | THEN("the two objects compare equal") { REQUIRE(x == y); } 80 | } 81 | 82 | WHEN("copied from via copy-assignment") 83 | { 84 | auto y = value_init_when_moved_from(); 85 | y = x; 86 | 87 | THEN("its value doesn't change") { REQUIRE(x.value == 5); } 88 | THEN("the newly constructed object contains an equivalent value") { REQUIRE(y.value == 5); } 89 | THEN("the two objects compare equal") { REQUIRE(x == y); } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/instantiations.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/pinned_vector.hpp" 8 | 9 | #include 10 | 11 | using namespace mknejp::vmcontainer; 12 | 13 | namespace 14 | { 15 | struct regular 16 | { 17 | public: 18 | regular() = default; 19 | regular(regular const&) = default; 20 | regular(regular&&) = default; 21 | auto operator=(regular const&) -> regular& = default; 22 | auto operator=(regular &&) -> regular& = default; 23 | }; 24 | 25 | struct movable_only 26 | { 27 | public: 28 | movable_only() = default; 29 | movable_only(movable_only const&) = delete; 30 | movable_only(movable_only&&) = default; 31 | auto operator=(movable_only const&) -> movable_only& = delete; 32 | auto operator=(movable_only &&) -> movable_only& = default; 33 | }; 34 | 35 | struct immovable 36 | { 37 | public: 38 | immovable() = default; 39 | immovable(immovable const&) = delete; 40 | immovable(immovable&&) = delete; 41 | auto operator=(immovable const&) -> immovable& = delete; 42 | auto operator=(immovable &&) -> immovable& = delete; 43 | }; 44 | } 45 | 46 | template 47 | static void required_functions() 48 | { 49 | // ctor 50 | auto a = pinned_vector(max_bytes(100)); 51 | // move ctor 52 | auto b = std::move(a); 53 | // move assign 54 | a = std::move(b); 55 | 56 | // member functions which are always required otherwise the container is pointless 57 | a.clear(); 58 | 59 | // swap 60 | swap(a, b); // ADL 61 | a.swap(b); 62 | 63 | static_assert(std::is_same::iterator>::value, ""); 64 | static_assert(std::is_same::const_iterator>::value, ""); 65 | static_assert(std::is_same::reverse_iterator>::value, ""); 66 | static_assert(std::is_same::const_reverse_iterator>::value, ""); 67 | 68 | static_assert(std::is_same::iterator>::value, ""); 69 | static_assert(std::is_same::const_iterator>::value, ""); 70 | static_assert(std::is_same::reverse_iterator>::value, ""); 71 | static_assert(std::is_same::const_reverse_iterator>::value, ""); 72 | 73 | static_assert(std::is_same::value, ""); 74 | 75 | static_assert(std::is_same::value, ""); 76 | static_assert(std::is_same::value, ""); 77 | 78 | static_assert(std::is_same::value, ""); 79 | static_assert(std::is_same::value, ""); 80 | 81 | static_assert(std::is_same::value, ""); 82 | 83 | a.pop_back(); 84 | 85 | a.reserve(typename pinned_vector::size_type(1000)); 86 | a.resize(typename pinned_vector::size_type(2000)); 87 | 88 | static_assert(std::is_same::value, ""); 89 | static_assert(std::is_same::size_type>::value, ""); 90 | static_assert(std::is_same::size_type>::value, ""); 91 | static_assert(std::is_same::size_type>::value, ""); 92 | static_assert(std::is_same::size_type>::value, ""); 93 | 94 | const auto c = std::move(a); 95 | 96 | static_assert(std::is_same::const_iterator>::value, ""); 97 | static_assert(std::is_same::const_iterator>::value, ""); 98 | static_assert(std::is_same::const_reverse_iterator>::value, ""); 99 | static_assert(std::is_same::const_reverse_iterator>::value, ""); 100 | 101 | static_assert(std::is_same::const_iterator>::value, ""); 102 | static_assert(std::is_same::const_iterator>::value, ""); 103 | static_assert(std::is_same::const_reverse_iterator>::value, ""); 104 | static_assert(std::is_same::const_reverse_iterator>::value, ""); 105 | 106 | static_assert(std::is_same::value, ""); 107 | static_assert(std::is_same::value, ""); 108 | 109 | static_assert(std::is_same::value, ""); 110 | static_assert(std::is_same::value, ""); 111 | 112 | static_assert(std::is_same::value, ""); 113 | } 114 | 115 | template 116 | static void required_for_copyable() 117 | { 118 | pinned_vector a(max_bytes(100)); 119 | // copy ctor 120 | auto b = a; 121 | // copy assign 122 | a = b; 123 | } 124 | 125 | template void required_functions(); 126 | template void required_functions(); 127 | template void required_functions(); 128 | 129 | template void required_for_copyable(); 130 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #define CATCH_CONFIG_MAIN 8 | #include "catch.hpp" 9 | -------------------------------------------------------------------------------- /tests/pinned_vector/access.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/pinned_vector.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | #include 12 | 13 | using namespace mknejp::vmcontainer; 14 | 15 | TEST_CASE("pinned_vector::at()", "[pinned_vector][access]") 16 | { 17 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 18 | auto const& cv = v; 19 | 20 | GIVEN("an in-range index") 21 | { 22 | auto i = 5; 23 | THEN("the returned reference refers to the element at that index") 24 | { 25 | auto& r = v.at(i); 26 | auto& cr = cv.at(i); 27 | 28 | CHECK(std::addressof(r) == v.data() + i); 29 | CHECK(std::addressof(cr) == v.data() + i); 30 | 31 | static_assert(std::is_same::value, ""); 32 | static_assert(std::is_same::value, ""); 33 | } 34 | } 35 | 36 | GIVEN("an out-of-range index") 37 | { 38 | auto i = v.size(); 39 | THEN("an exception of type std::out_of_range is thrown") 40 | { 41 | CHECK_THROWS_AS(v.at(i), std::out_of_range); 42 | CHECK_THROWS_AS(cv.at(i), std::out_of_range); 43 | } 44 | } 45 | } 46 | 47 | TEST_CASE("pinned_vector::operator[]()", "[pinned_vector][access]") 48 | { 49 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 50 | auto const& cv = v; 51 | 52 | GIVEN("an in-range index") 53 | { 54 | auto i = 5; 55 | THEN("the returned reference refers to the element at that index") 56 | { 57 | auto& r = v[i]; 58 | auto& cr = cv[i]; 59 | 60 | CHECK(std::addressof(r) == v.data() + i); 61 | CHECK(std::addressof(cr) == v.data() + i); 62 | 63 | static_assert(std::is_same::value, ""); 64 | static_assert(std::is_same::value, ""); 65 | } 66 | } 67 | } 68 | 69 | TEST_CASE("pinned_vector::front()", "[pinned_vector][access]") 70 | { 71 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 72 | auto const& cv = v; 73 | 74 | auto& r = v.front(); 75 | auto& cr = cv.front(); 76 | 77 | CHECK(std::addressof(r) == v.data()); 78 | CHECK(std::addressof(cr) == v.data()); 79 | 80 | static_assert(std::is_same::value, ""); 81 | static_assert(std::is_same::value, ""); 82 | } 83 | 84 | TEST_CASE("pinned_vector::back()", "[pinned_vector][access]") 85 | { 86 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 87 | auto const& cv = v; 88 | 89 | auto& r = v.back(); 90 | auto& cr = cv.back(); 91 | 92 | CHECK(std::addressof(r) == v.data() + 9); 93 | CHECK(std::addressof(cr) == v.data() + 9); 94 | 95 | static_assert(std::is_same::value, ""); 96 | static_assert(std::is_same::value, ""); 97 | } 98 | 99 | TEST_CASE("pinned_vector::data()", "[pinned_vector][access]") 100 | { 101 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 102 | auto const& cv = v; 103 | 104 | auto p = v.data(); 105 | auto cp = cv.data(); 106 | 107 | static_assert(std::is_same::value, ""); 108 | static_assert(std::is_same::value, ""); 109 | 110 | CHECK(p != nullptr); 111 | CHECK(cp != nullptr); 112 | CHECK(p == cp); 113 | } 114 | -------------------------------------------------------------------------------- /tests/pinned_vector/assign.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/pinned_vector.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace mknejp::vmcontainer; 17 | 18 | TEST_CASE("pinned_vector::assign() with an iterator pair", "[pinned_vector][cons]") 19 | { 20 | auto test = [](auto first, auto last, auto expected) { 21 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 22 | 23 | v.assign(first, last); 24 | CHECK(v.size() == expected.size()); 25 | CHECK(v.empty() == false); 26 | CHECK(std::equal(v.begin(), v.end(), begin(expected), end(expected))); 27 | }; 28 | auto const expected = {10, 11, 12, 13, 14}; 29 | SECTION("input iterator") 30 | { 31 | auto init = std::istringstream("10 11 12 13 14"); 32 | test(std::istream_iterator(init), std::istream_iterator(), expected); 33 | } 34 | SECTION("forward iterator") 35 | { 36 | auto init = std::forward_list{10, 11, 12, 13, 14}; 37 | test(begin(init), end(init), expected); 38 | } 39 | SECTION("bidirectional iterator") 40 | { 41 | auto init = std::list{10, 11, 12, 13, 14}; 42 | test(begin(init), end(init), expected); 43 | } 44 | SECTION("random access iterator") 45 | { 46 | auto init = {10, 11, 12, 13, 14}; 47 | test(begin(init), end(init), expected); 48 | } 49 | } 50 | 51 | TEST_CASE("pinned_vector::assign() with a count and value", "[pinned_vector][cons]") 52 | { 53 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 54 | 55 | v.assign(5, 6); 56 | CHECK(v.size() == 5); 57 | CHECK(v.empty() == false); 58 | CHECK(std::distance(v.begin(), v.end()) == 5); 59 | CHECK(std::all_of(v.begin(), v.end(), [](int x) { return x == 6; })); 60 | } 61 | 62 | TEST_CASE("pinned_vector::assign() with an initializer_list", "[pinned_vector][cons]") 63 | { 64 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 65 | 66 | auto replacement = {10, 11, 12, 13, 14}; 67 | v.assign(replacement); 68 | 69 | CHECK(v.size() == 5); 70 | CHECK(v.empty() == false); 71 | CHECK(std::equal(v.begin(), v.end(), begin(replacement), end(replacement))); 72 | } 73 | -------------------------------------------------------------------------------- /tests/pinned_vector/capacity.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "pinned_vector_test.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | using namespace mknejp::vmcontainer; 12 | using namespace vmcontainer_test; 13 | 14 | static auto round_up(std::size_t bytes, std::size_t page_size) 15 | { 16 | return ((bytes + page_size - 1) / page_size) * page_size; 17 | } 18 | 19 | TEST_CASE("pinned_vector::page_size() returns the system page size", "[pinned_vector][capacity]") 20 | { 21 | auto v = pinned_vector(); 22 | CHECK(v.page_size() == vm::system_default::page_size()); 23 | } 24 | 25 | TEST_CASE("pinned_vector::reserve() grows the capacity", "[pinned_vector][capacity]") 26 | { 27 | auto v = pinned_vector(max_pages(10)); 28 | auto page_size = v.page_size(); 29 | REQUIRE(page_size > 0); 30 | CAPTURE(page_size); 31 | 32 | auto test = [&v, page_size](std::size_t n) { 33 | CAPTURE(n); 34 | auto new_cap = n * page_size / sizeof(int); 35 | v.reserve(new_cap); 36 | CHECK(v.capacity() == new_cap); 37 | }; 38 | 39 | test(1); 40 | test(2); 41 | test(3); 42 | test(4); 43 | } 44 | 45 | TEST_CASE("pinned_vector::reserve() grows in multiples of page size", "[pinned_vector][capacity]") 46 | { 47 | auto v = pinned_vector(max_pages(10)); 48 | auto page_size = v.page_size(); 49 | REQUIRE(page_size > 0); 50 | CAPTURE(page_size); 51 | 52 | auto test = [&v, page_size](std::size_t n) { 53 | CAPTURE(n); 54 | v.reserve(n); 55 | auto expected = round_up(n * sizeof(int), page_size) / sizeof(int); 56 | CHECK(v.capacity() == expected); 57 | }; 58 | test(1); 59 | test(page_size / sizeof(int) + 1); 60 | } 61 | 62 | TEST_CASE("pinned_vector::reserve() does not reduce capacity", "[pinned_vector][capacity]") 63 | { 64 | auto v = pinned_vector(max_pages(2)); 65 | auto page_size = v.page_size(); 66 | REQUIRE(page_size > 0); 67 | CAPTURE(page_size); 68 | 69 | v.reserve(2 * page_size / sizeof(int)); 70 | auto old_capacity = v.capacity(); 71 | 72 | v.reserve(1); 73 | CHECK(v.capacity() == old_capacity); 74 | } 75 | 76 | TEST_CASE("pinned_vector::reserve() has strong exception guarantee if committing new memory fails", 77 | "[pinned_vector][capacity]") 78 | { 79 | struct tag 80 | {}; 81 | auto alloc = tracking_allocator(); 82 | using traits = pinned_vector_test_traits; 83 | 84 | char page[4 * sizeof(int)]; 85 | alloc.set_page_size(sizeof(page)); 86 | alloc.expect_reserve(page, 2 * sizeof(page)); 87 | alloc.expect_commit(page, sizeof(page)); 88 | alloc.expect_free(page); 89 | 90 | auto v = pinned_vector(max_pages(2)); 91 | REQUIRE(v.max_size() == 8); 92 | REQUIRE(v.size() == 0); 93 | 94 | v.push_back(1); 95 | v.push_back(2); 96 | REQUIRE(v.size() == 2); 97 | REQUIRE(v.capacity() == 4); 98 | 99 | alloc.expect_commit_and_fail(&page[0] + sizeof(page), sizeof(page)); 100 | auto state = capture_value_state(v); 101 | REQUIRE_THROWS_AS(v.reserve(5), std::bad_alloc); 102 | REQUIRE(v.capacity() == 4); 103 | REQUIRE(capture_value_state(v) == state); 104 | } 105 | 106 | TEST_CASE("pinned_vector::shrink_to_fit() reduces capacity to current size rounded up to page size", 107 | "[pinned_vector][capacity]") 108 | { 109 | auto v = pinned_vector(max_pages(2), {1}); 110 | auto page_size = v.page_size(); 111 | REQUIRE(page_size > 0); 112 | CAPTURE(page_size); 113 | 114 | v.reserve(2 * page_size / sizeof(int)); 115 | CHECK(v.capacity() == 2 * page_size / sizeof(int)); 116 | 117 | v.shrink_to_fit(); 118 | CHECK(v.capacity() == 1 * page_size / sizeof(int)); 119 | 120 | v.clear(); 121 | v.shrink_to_fit(); 122 | CHECK(v.capacity() == 0); 123 | } 124 | 125 | TEST_CASE("pinned_vector::shrink_to_fit() does not change iterators", "[pinned_vector][capacity]") 126 | { 127 | auto v = pinned_vector(max_pages(2), {1}); 128 | auto page_size = v.page_size(); 129 | REQUIRE(page_size > 0); 130 | CAPTURE(page_size); 131 | 132 | auto state = capture_value_state(v); 133 | v.reserve(2 * page_size / sizeof(int)); 134 | REQUIRE(capture_value_state(v) == state); 135 | 136 | state = capture_value_state(v); 137 | v.shrink_to_fit(); 138 | REQUIRE(capture_value_state(v) == state); 139 | } 140 | 141 | TEST_CASE("pinned_vector::empty()", "[pinned_vector][capacity]") 142 | { 143 | auto v = pinned_vector(max_elements(10)); 144 | REQUIRE(v.empty() == true); 145 | v.push_back(1); 146 | REQUIRE(v.empty() == false); 147 | v.clear(); 148 | REQUIRE(v.empty() == true); 149 | } 150 | 151 | TEST_CASE("pinned_vector capacity grows geometrically according to growth_factor", "[pinned_vector][capacity]") 152 | { 153 | auto test = [](auto factor) { 154 | struct traits 155 | { 156 | using storage_type = pinned_vector_traits::storage_type; 157 | using growth_factor = decltype(factor); 158 | }; 159 | 160 | auto v = pinned_vector(max_pages(100)); 161 | auto page_size = v.page_size(); 162 | REQUIRE(page_size > 0); 163 | CAPTURE(page_size); 164 | 165 | auto next_cap = [page_size](auto old_cap) { 166 | return (old_cap == 0 167 | ? page_size 168 | : round_up(old_cap * sizeof(int) * traits::growth_factor::num / traits::growth_factor::den, page_size)) 169 | / sizeof(int); 170 | }; 171 | 172 | auto old_cap = v.capacity(); 173 | std::fill_n(std::back_inserter(v), page_size / sizeof(int), 1); 174 | REQUIRE(v.capacity() == next_cap(old_cap)); 175 | 176 | for(int i = 0; i < 5; ++i) 177 | { 178 | old_cap = v.capacity(); 179 | std::fill_n(std::back_inserter(v), v.capacity() - v.size() + 1, 1); 180 | REQUIRE(v.capacity() == next_cap(old_cap)); 181 | } 182 | }; 183 | SECTION("default growth factor 2x") 184 | { 185 | static_assert(std::is_same>::value, 186 | "default growth factor is not 2x"); 187 | test(pinned_vector_traits::growth_factor()); 188 | } 189 | SECTION("custom growth factor 1.5x") { test(std::ratio<3, 2>()); } 190 | } 191 | 192 | TEST_CASE("pinned_vector::resize() without a default value", "[pinned_vector][capacity]") 193 | { 194 | auto v = pinned_vector(max_elements(12345)); 195 | REQUIRE(v.size() == 0); 196 | 197 | v.resize(10); 198 | REQUIRE(v.size() == 10); 199 | REQUIRE(v.capacity() >= 10); 200 | REQUIRE(std::all_of(v.begin(), v.end(), [](int x) { return x == 0; })); 201 | 202 | v.resize(20); 203 | REQUIRE(v.size() == 20); 204 | REQUIRE(v.capacity() >= 20); 205 | REQUIRE(std::all_of(v.begin(), v.end(), [](int x) { return x == 0; })); 206 | 207 | v.resize(15); 208 | REQUIRE(v.size() == 15); 209 | REQUIRE(v.capacity() >= 20); 210 | REQUIRE(std::all_of(v.begin(), v.end(), [](int x) { return x == 0; })); 211 | } 212 | 213 | TEST_CASE("pinned_vector::resize() with a default value", "[pinned_vector][capacity]") 214 | { 215 | auto v = pinned_vector(max_elements(12345)); 216 | REQUIRE(v.size() == 0); 217 | 218 | v.resize(10); 219 | REQUIRE(v.size() == 10); 220 | REQUIRE(v.capacity() >= 10); 221 | REQUIRE(std::all_of(v.begin(), v.end(), [](int x) { return x == 0; })); 222 | 223 | v.resize(20, 1); 224 | REQUIRE(v.size() == 20); 225 | REQUIRE(v.capacity() >= 20); 226 | REQUIRE(std::all_of(v.begin(), v.begin() + 10, [](int x) { return x == 0; })); 227 | REQUIRE(std::all_of(v.begin() + 10, v.begin() + 20, [](int x) { return x == 1; })); 228 | 229 | v.resize(15, 2); 230 | REQUIRE(v.size() == 15); 231 | REQUIRE(v.capacity() >= 15); 232 | REQUIRE(std::all_of(v.begin(), v.begin() + 10, [](int x) { return x == 0; })); 233 | REQUIRE(std::all_of(v.begin() + 10, v.begin() + 15, [](int x) { return x == 1; })); 234 | 235 | v.resize(30, 3); 236 | REQUIRE(v.size() == 30); 237 | REQUIRE(v.capacity() >= 30); 238 | REQUIRE(std::all_of(v.begin(), v.begin() + 10, [](int x) { return x == 0; })); 239 | REQUIRE(std::all_of(v.begin() + 10, v.begin() + 15, [](int x) { return x == 1; })); 240 | REQUIRE(std::all_of(v.begin() + 15, v.begin() + 30, [](int x) { return x == 3; })); 241 | } 242 | 243 | TEST_CASE("pinned_vector::resize() does not move the memory buffer", "[pinned_vector][capacity]") 244 | { 245 | auto v = pinned_vector(max_elements(12345)); 246 | auto data = v.data(); 247 | auto begin = v.begin(); 248 | 249 | v.resize(10); 250 | REQUIRE(v.data() == data); 251 | REQUIRE(v.begin() == begin); 252 | 253 | v.resize(20); 254 | REQUIRE(v.data() == data); 255 | REQUIRE(v.begin() == begin); 256 | 257 | v.resize(15, 3); 258 | REQUIRE(v.data() == data); 259 | REQUIRE(v.begin() == begin); 260 | 261 | v.resize(30); 262 | REQUIRE(v.data() == data); 263 | REQUIRE(v.begin() == begin); 264 | } 265 | 266 | TEST_CASE("pinned_vector::resize() has strong exception guarantee if committing new memory fails", 267 | "[pinned_vector][capacity]") 268 | { 269 | struct tag 270 | {}; 271 | auto alloc = tracking_allocator(); 272 | using traits = pinned_vector_test_traits; 273 | 274 | char page[4 * sizeof(int)]; 275 | alloc.set_page_size(sizeof(page)); 276 | alloc.expect_reserve(page, 2 * sizeof(page)); 277 | alloc.expect_commit(page, sizeof(page)); 278 | alloc.expect_free(page); 279 | 280 | auto v = pinned_vector(max_pages(2)); 281 | REQUIRE(v.max_size() == 8); 282 | REQUIRE(v.size() == 0); 283 | 284 | v.resize(1); 285 | REQUIRE(v.size() == 1); 286 | REQUIRE(v.capacity() == 4); 287 | 288 | alloc.expect_commit_and_fail(&page[0] + sizeof(page), sizeof(page)); 289 | auto state = capture_value_state(v); 290 | REQUIRE_THROWS_AS(v.resize(5), std::bad_alloc); 291 | REQUIRE(v.capacity() == 4); 292 | REQUIRE(capture_value_state(v) == state); 293 | } 294 | -------------------------------------------------------------------------------- /tests/pinned_vector/clear.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "pinned_vector_test.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | #include 12 | #include 13 | 14 | using namespace mknejp::vmcontainer; 15 | using namespace vmcontainer_test; 16 | 17 | TEST_CASE("pinned_vector::clear() empties the container", "[pinned_vector][clear]") 18 | { 19 | auto vec = pinned_vector(max_elements(10), {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}); 20 | 21 | REQUIRE(vec.size() == 10); 22 | 23 | static_assert(noexcept(vec.clear()), ""); 24 | vec.clear(); 25 | 26 | REQUIRE(vec.size() == 0); 27 | REQUIRE(vec.empty() == true); 28 | REQUIRE(vec.begin() == vec.end()); 29 | } 30 | 31 | TEST_CASE("pinned_vector::clear() does not change capacity", "[pinned_vector][clear]") 32 | { 33 | auto vec = pinned_vector(max_elements(10), {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}); 34 | 35 | auto const capacity = vec.capacity(); 36 | 37 | vec.clear(); 38 | 39 | REQUIRE(vec.capacity() == capacity); 40 | } 41 | 42 | TEST_CASE("pinned_vector::clear() destroys its elements", "[pinned_vector][clear]") 43 | { 44 | static std::vector constructed; 45 | static std::vector destroyed; 46 | 47 | struct tracker 48 | { 49 | tracker() { constructed.push_back(reinterpret_cast(this)); } 50 | ~tracker() { destroyed.push_back(reinterpret_cast(this)); } 51 | }; 52 | 53 | auto vec = pinned_vector(max_elements(10), 10); 54 | 55 | REQUIRE(vec.size() == 10); 56 | REQUIRE(constructed.size() == 10); 57 | 58 | vec.clear(); 59 | REQUIRE(std::equal(constructed.begin(), constructed.end(), destroyed.begin(), destroyed.end())); 60 | } 61 | -------------------------------------------------------------------------------- /tests/pinned_vector/contiguous.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/pinned_vector.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace mknejp::vmcontainer; 16 | 17 | SCENARIO("pinned_vector is a contiguous container", "[pinned_vector]") 18 | { 19 | auto check = [](auto const& container) { 20 | THEN("their elements are laid out in contiguous memory") 21 | { 22 | using container_t = std::decay_t; 23 | for(int i = 0; i < container.size(); ++i) 24 | { 25 | CAPTURE(i); 26 | auto const& x = *(std::addressof(*container.begin()) + i); 27 | REQUIRE(x == *(container.begin() + typename container_t::difference_type(i))); 28 | REQUIRE(x == *(container.data() + i)); 29 | } 30 | } 31 | }; 32 | 33 | GIVEN("an empty container") { check(pinned_vector()); } 34 | GIVEN("a small container") { check(pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); } 35 | GIVEN("a large container spanning multiple pages") 36 | { 37 | auto v = pinned_vector(max_pages(5)); 38 | std::generate_n(std::back_inserter(v), v.max_size(), [i = 0]() mutable { return i++; }); 39 | check(v); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/pinned_vector/emplace_back.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "pinned_vector_test.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | using namespace mknejp::vmcontainer; 12 | using namespace vmcontainer_test; 13 | 14 | TEST_CASE("pinned_vector::emplace_back()", "[pinned_vector][emplace_back]") 15 | { 16 | auto v = pinned_vector(max_elements(10)); 17 | 18 | int& a = v.emplace_back(1); 19 | REQUIRE(v.size() == 1); 20 | REQUIRE(std::addressof(a) == std::addressof(v.back())); 21 | REQUIRE(v[0] == 1); 22 | REQUIRE(v.end() - v.begin() == 1); 23 | 24 | int& b = v.emplace_back(2); 25 | REQUIRE(v.size() == 2); 26 | REQUIRE(std::addressof(b) == std::addressof(v.back())); 27 | REQUIRE(v[1] == 2); 28 | REQUIRE(v.end() - v.begin() == 2); 29 | 30 | int& c = v.emplace_back(3); 31 | REQUIRE(v.size() == 3); 32 | REQUIRE(std::addressof(c) == std::addressof(v.back())); 33 | REQUIRE(v[2] == 3); 34 | REQUIRE(v.end() - v.begin() == 3); 35 | } 36 | 37 | TEST_CASE("pinned_vector::emplace_back() with CopyConstructible", "[pinned_vector][emplace_back]") 38 | { 39 | struct CopyConstructible 40 | { 41 | explicit CopyConstructible(int x) : x(x) {} 42 | int x; 43 | }; 44 | 45 | auto v = pinned_vector(max_elements(10)); 46 | 47 | auto x = CopyConstructible(1); 48 | 49 | CopyConstructible& a = v.emplace_back(x); 50 | REQUIRE(v.size() == 1); 51 | REQUIRE(std::addressof(a) == std::addressof(v.back())); 52 | REQUIRE(v[0].x == 1); 53 | REQUIRE(v.end() - v.begin() == 1); 54 | 55 | x.x = 2; 56 | CopyConstructible& b = v.emplace_back(x); 57 | REQUIRE(v.size() == 2); 58 | REQUIRE(std::addressof(b) == std::addressof(v.back())); 59 | REQUIRE(v[1].x == 2); 60 | REQUIRE(v.end() - v.begin() == 2); 61 | 62 | x.x = 3; 63 | CopyConstructible& c = v.emplace_back(x); 64 | REQUIRE(v.size() == 3); 65 | REQUIRE(std::addressof(c) == std::addressof(v.back())); 66 | REQUIRE(v[2].x == 3); 67 | REQUIRE(v.end() - v.begin() == 3); 68 | } 69 | 70 | TEST_CASE("pinned_vector::emplace_back() with MoveConstructible", "[pinned_vector][emplace_back]") 71 | { 72 | struct MoveConstructible 73 | { 74 | explicit MoveConstructible(int x) : x(x) {} 75 | MoveConstructible(MoveConstructible const&) = delete; 76 | MoveConstructible(MoveConstructible&&) = default; 77 | int x; 78 | }; 79 | 80 | auto v = pinned_vector(max_elements(10)); 81 | 82 | auto x = MoveConstructible(1); 83 | 84 | MoveConstructible& a = v.emplace_back(std::move(x)); 85 | REQUIRE(v.size() == 1); 86 | REQUIRE(std::addressof(a) == std::addressof(v.back())); 87 | REQUIRE(v[0].x == 1); 88 | REQUIRE(v.end() - v.begin() == 1); 89 | 90 | x.x = 2; 91 | MoveConstructible& b = v.emplace_back(std::move(x)); 92 | REQUIRE(v.size() == 2); 93 | REQUIRE(std::addressof(b) == std::addressof(v.back())); 94 | REQUIRE(v[1].x == 2); 95 | REQUIRE(v.end() - v.begin() == 2); 96 | 97 | x.x = 3; 98 | MoveConstructible& c = v.emplace_back(std::move(x)); 99 | REQUIRE(v.size() == 3); 100 | REQUIRE(std::addressof(c) == std::addressof(v.back())); 101 | REQUIRE(v[2].x == 3); 102 | REQUIRE(v.end() - v.begin() == 3); 103 | } 104 | 105 | TEST_CASE("pinned_vector::emplace_back() with DefaultConstructible", "[pinned_vector][emplace_back]") 106 | { 107 | static int counter = 1; 108 | 109 | struct DefaultConstructible 110 | { 111 | int x = counter++; 112 | }; 113 | 114 | auto v = pinned_vector(max_elements(10)); 115 | 116 | DefaultConstructible& a = v.emplace_back(); 117 | REQUIRE(v.size() == 1); 118 | REQUIRE(std::addressof(a) == std::addressof(v.back())); 119 | REQUIRE(v[0].x == 1); 120 | REQUIRE(v.end() - v.begin() == 1); 121 | 122 | DefaultConstructible& b = v.emplace_back(); 123 | REQUIRE(v.size() == 2); 124 | REQUIRE(std::addressof(b) == std::addressof(v.back())); 125 | REQUIRE(v[1].x == 2); 126 | REQUIRE(v.end() - v.begin() == 2); 127 | 128 | DefaultConstructible& c = v.emplace_back(); 129 | REQUIRE(v.size() == 3); 130 | REQUIRE(std::addressof(c) == std::addressof(v.back())); 131 | REQUIRE(v[2].x == 3); 132 | REQUIRE(v.end() - v.begin() == 3); 133 | } 134 | 135 | namespace 136 | { 137 | struct check_emplace_arg_forwarding 138 | { 139 | template 140 | check_emplace_arg_forwarding(L&& l, R&& r, CL&& cl, CR&& cr, VL&& vl, VR&& vr, CVL&& cvl, CVR&& cvr) 141 | { 142 | static_assert(std::is_same::value, ""); 143 | static_assert(std::is_same::value, ""); 144 | static_assert(std::is_same::value, ""); 145 | static_assert(std::is_same::value, ""); 146 | static_assert(std::is_same::value, ""); 147 | static_assert(std::is_same::value, ""); 148 | static_assert(std::is_same::value, ""); 149 | static_assert(std::is_same::value, ""); 150 | REQUIRE(l == 1); 151 | REQUIRE(r == 2); 152 | REQUIRE(cl == 3); 153 | REQUIRE(cr == 4); 154 | REQUIRE(vl == 5); 155 | REQUIRE(vr == 6); 156 | REQUIRE(cvl == 7); 157 | REQUIRE(cvr == 8); 158 | } 159 | }; 160 | } 161 | 162 | TEST_CASE("pinned_vector::emplace_back() forwards arguments to value constructor", "[pinned_vector][emplace_back]") 163 | { 164 | auto v = pinned_vector(max_elements(10)); 165 | 166 | auto a = 1; 167 | auto b = 2; 168 | auto const c = 3; 169 | auto const d = 4; 170 | auto volatile e = 5; 171 | auto volatile f = 6; 172 | auto const volatile g = 7; 173 | auto const volatile h = 8; 174 | v.emplace_back(a, std::move(b), c, std::move(d), e, std::move(f), g, std::move(h)); 175 | } 176 | 177 | TEST_CASE("pinned_vector::emplace_back() with NotMoveConstructibleNotCopyConstructible", 178 | "[pinned_vector][emplace_back]") 179 | { 180 | struct NotMoveConstructibleNotCopyConstructible 181 | { 182 | explicit NotMoveConstructibleNotCopyConstructible(int x) : x(x) {} 183 | NotMoveConstructibleNotCopyConstructible(NotMoveConstructibleNotCopyConstructible const&) = delete; 184 | NotMoveConstructibleNotCopyConstructible(NotMoveConstructibleNotCopyConstructible&&) = delete; 185 | int x; 186 | }; 187 | 188 | auto v = pinned_vector(max_elements(10)); 189 | 190 | NotMoveConstructibleNotCopyConstructible& a = v.emplace_back(1); 191 | REQUIRE(v.size() == 1); 192 | REQUIRE(std::addressof(a) == std::addressof(v.back())); 193 | REQUIRE(v[0].x == 1); 194 | REQUIRE(v.end() - v.begin() == 1); 195 | 196 | NotMoveConstructibleNotCopyConstructible& b = v.emplace_back(2); 197 | REQUIRE(v.size() == 2); 198 | REQUIRE(std::addressof(b) == std::addressof(v.back())); 199 | REQUIRE(v[1].x == 2); 200 | REQUIRE(v.end() - v.begin() == 2); 201 | 202 | NotMoveConstructibleNotCopyConstructible& c = v.emplace_back(3); 203 | REQUIRE(v.size() == 3); 204 | REQUIRE(std::addressof(c) == std::addressof(v.back())); 205 | REQUIRE(v[2].x == 3); 206 | REQUIRE(v.end() - v.begin() == 3); 207 | } 208 | 209 | TEST_CASE("pinned_vector::emplace_back() has strong exception guarantee on throwing copy construction", 210 | "[pinned_vector][emplace_back]") 211 | { 212 | struct may_throw_when_copied 213 | { 214 | may_throw_when_copied() = default; 215 | may_throw_when_copied(may_throw_when_copied const& other) 216 | { 217 | if(other.should_throw) 218 | { 219 | throw 1; 220 | } 221 | } 222 | bool should_throw = false; 223 | }; 224 | 225 | auto v = pinned_vector(max_elements(10)); 226 | 227 | auto x = may_throw_when_copied(); 228 | 229 | v.emplace_back(x); 230 | v.emplace_back(x); 231 | REQUIRE(v.size() == 2); 232 | 233 | auto state = capture_value_state(v); 234 | x.should_throw = true; 235 | REQUIRE_THROWS_AS(v.emplace_back(x), int); 236 | 237 | REQUIRE(capture_value_state(v) == state); 238 | } 239 | 240 | TEST_CASE("pinned_vector::emplace_back() has strong exception guarantee on throwing move construction", 241 | "[pinned_vector][emplace_back]") 242 | { 243 | struct may_throw_when_copied 244 | { 245 | may_throw_when_copied() = default; 246 | may_throw_when_copied(may_throw_when_copied&& other) 247 | { 248 | if(other.should_throw) 249 | { 250 | throw 1; 251 | } 252 | } 253 | bool should_throw = false; 254 | }; 255 | 256 | auto v = pinned_vector(max_elements(10)); 257 | 258 | auto x = may_throw_when_copied(); 259 | 260 | v.emplace_back(std::move(x)); 261 | v.emplace_back(std::move(x)); 262 | REQUIRE(v.size() == 2); 263 | 264 | auto state = capture_value_state(v); 265 | x.should_throw = true; 266 | REQUIRE_THROWS_AS(v.emplace_back(std::move(x)), int); 267 | 268 | REQUIRE(capture_value_state(v) == state); 269 | } 270 | 271 | TEST_CASE("pinned_vector::emplace_back() has strong exception guarantee on throwing construction", 272 | "[pinned_vector][emplace_back]") 273 | { 274 | static bool should_throw = false; 275 | 276 | struct may_throw_when_constructed 277 | { 278 | may_throw_when_constructed() 279 | { 280 | if(should_throw) 281 | { 282 | throw 1; 283 | } 284 | } 285 | }; 286 | 287 | auto v = pinned_vector(max_elements(10)); 288 | 289 | v.emplace_back(); 290 | v.emplace_back(); 291 | REQUIRE(v.size() == 2); 292 | 293 | auto state = capture_value_state(v); 294 | should_throw = true; 295 | REQUIRE_THROWS_AS(v.emplace_back(), int); 296 | 297 | REQUIRE(capture_value_state(v) == state); 298 | } 299 | 300 | TEST_CASE("pinned_vector::emplace_back() has strong exception guarantee if committing new memory fails", 301 | "[pinned_vector][emplace_back]") 302 | { 303 | struct tag 304 | {}; 305 | auto alloc = tracking_allocator(); 306 | using traits = pinned_vector_test_traits; 307 | 308 | char page[4 * sizeof(int)]; 309 | alloc.set_page_size(sizeof(page)); 310 | alloc.expect_reserve(page, 2 * sizeof(page)); 311 | alloc.expect_commit(page, sizeof(page)); 312 | alloc.expect_free(page); 313 | 314 | auto v = pinned_vector(max_pages(2)); 315 | REQUIRE(v.max_size() == 8); 316 | 317 | v.emplace_back(1); 318 | v.emplace_back(2); 319 | v.emplace_back(3); 320 | v.emplace_back(4); 321 | REQUIRE(v.size() == 4); 322 | REQUIRE(v.capacity() == 4); 323 | 324 | alloc.expect_commit_and_fail(&page[0] + sizeof(page), sizeof(page)); 325 | auto state = capture_value_state(v); 326 | REQUIRE_THROWS_AS(v.emplace_back(5), std::bad_alloc); 327 | 328 | REQUIRE(v.capacity() == 4); 329 | REQUIRE(capture_value_state(v) == state); 330 | } 331 | -------------------------------------------------------------------------------- /tests/pinned_vector/iterators.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/pinned_vector.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | #include 12 | 13 | using namespace mknejp::vmcontainer; 14 | 15 | TEST_CASE("pinned_vector iterators compare equal for empty containers", "[pinned_vector][iterator]") 16 | { 17 | auto test = [](pinned_vector& v) { 18 | THEN("begin() and end() compare equal") 19 | { 20 | auto begin = v.begin(); 21 | auto end = v.end(); 22 | 23 | REQUIRE(begin == end); 24 | REQUIRE(std::distance(begin, end) == 0); 25 | } 26 | THEN("rbegin() and rend() compare equal") 27 | { 28 | auto begin = v.rbegin(); 29 | auto end = v.rend(); 30 | 31 | REQUIRE(begin == end); 32 | REQUIRE(std::distance(begin, end) == 0); 33 | } 34 | THEN("mutable and const iterators compare equal") 35 | { 36 | auto const& cv = v; 37 | 38 | REQUIRE(v.begin() == v.cbegin()); 39 | REQUIRE(v.end() == v.cend()); 40 | REQUIRE(v.rbegin() == v.crbegin()); 41 | REQUIRE(v.rend() == v.crend()); 42 | 43 | REQUIRE(cv.begin() == v.begin()); 44 | REQUIRE(cv.end() == v.end()); 45 | REQUIRE(cv.rbegin() == v.rbegin()); 46 | REQUIRE(cv.rend() == v.rend()); 47 | 48 | REQUIRE(cv.begin() == v.cbegin()); 49 | REQUIRE(cv.end() == v.cend()); 50 | REQUIRE(cv.rbegin() == v.crbegin()); 51 | REQUIRE(cv.rend() == v.crend()); 52 | 53 | REQUIRE(cv.begin() == cv.cbegin()); 54 | REQUIRE(cv.end() == cv.cend()); 55 | REQUIRE(cv.rbegin() == cv.crbegin()); 56 | REQUIRE(cv.rend() == cv.crend()); 57 | } 58 | }; 59 | 60 | GIVEN("an empty container") 61 | { 62 | auto v = pinned_vector(max_elements(1)); 63 | test(v); 64 | } 65 | GIVEN("a default-constructed container") 66 | { 67 | auto v = pinned_vector(); 68 | test(v); 69 | } 70 | GIVEN("a moved-from container") 71 | { 72 | auto v = pinned_vector(max_elements(1)); 73 | auto _ = std::move(v); 74 | test(v); 75 | } 76 | } 77 | 78 | TEST_CASE("pinned_vector container iteration", "[pinned_vector][iterator]") 79 | { 80 | GIVEN("a non-empty container") 81 | { 82 | auto init = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 83 | auto v = pinned_vector(max_elements(10), init); 84 | auto const& cv = v; 85 | 86 | THEN("begin() and end() iterate through all values") 87 | { 88 | CHECK(std::equal(v.begin(), v.end(), begin(init), end(init))); 89 | CHECK(std::equal(v.cbegin(), v.cend(), begin(init), end(init))); 90 | CHECK(std::equal(cv.begin(), cv.end(), begin(init), end(init))); 91 | CHECK(std::equal(cv.cbegin(), cv.cend(), begin(init), end(init))); 92 | } 93 | 94 | THEN("rbegin() and rend() iterate through all values in reverse order") 95 | { 96 | CHECK(std::equal(v.rbegin(), v.rend(), rbegin(init), rend(init))); 97 | CHECK(std::equal(v.crbegin(), v.crend(), rbegin(init), rend(init))); 98 | CHECK(std::equal(cv.rbegin(), cv.rend(), rbegin(init), rend(init))); 99 | CHECK(std::equal(cv.crbegin(), cv.crend(), rbegin(init), rend(init))); 100 | } 101 | } 102 | } 103 | 104 | TEST_CASE("pinned_vector singular iterators compare equal", "[pinned_vector][iterator]") 105 | { 106 | GIVEN("default constructed iterators") 107 | { 108 | auto i1 = pinned_vector::iterator(); 109 | auto i2 = pinned_vector::iterator(); 110 | auto i3 = i2; 111 | auto c1 = pinned_vector::const_iterator(); 112 | 113 | THEN("they compare equal") 114 | { 115 | REQUIRE(i1 == i2); 116 | REQUIRE(i2 == i3); 117 | REQUIRE(i1 == c1); 118 | REQUIRE(c1 == i1); 119 | 120 | REQUIRE(!(i1 != i2)); 121 | REQUIRE(!(i1 != c1)); 122 | REQUIRE(!(c1 != i1)); 123 | 124 | REQUIRE(!(i1 < i2)); 125 | REQUIRE(!(i1 < c1)); 126 | REQUIRE(!(c1 < i1)); 127 | 128 | REQUIRE(!(i1 > i2)); 129 | REQUIRE(!(i1 > c1)); 130 | REQUIRE(!(c1 > i1)); 131 | 132 | REQUIRE(i1 <= i2); 133 | REQUIRE(i1 <= c1); 134 | REQUIRE(c1 <= i1); 135 | 136 | REQUIRE(i1 >= i2); 137 | REQUIRE(i1 >= c1); 138 | REQUIRE(c1 >= i1); 139 | 140 | REQUIRE((i1 - i2) == 0); 141 | REQUIRE((i1 - c1) == 0); 142 | REQUIRE((c1 - i1) == 0); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /tests/pinned_vector/push_back.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "pinned_vector_test.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | using namespace mknejp::vmcontainer; 12 | using namespace vmcontainer_test; 13 | 14 | TEST_CASE("pinned_vector::push_back()", "[pinned_vector][push_back]") 15 | { 16 | auto v = pinned_vector(max_elements(10)); 17 | 18 | int& a = v.push_back(1); 19 | REQUIRE(v.size() == 1); 20 | REQUIRE(std::addressof(a) == std::addressof(v.back())); 21 | REQUIRE(v[0] == 1); 22 | REQUIRE(v.end() - v.begin() == 1); 23 | 24 | int& b = v.push_back(2); 25 | REQUIRE(v.size() == 2); 26 | REQUIRE(std::addressof(b) == std::addressof(v.back())); 27 | REQUIRE(v[1] == 2); 28 | REQUIRE(v.end() - v.begin() == 2); 29 | 30 | int& c = v.push_back(3); 31 | REQUIRE(v.size() == 3); 32 | REQUIRE(std::addressof(c) == std::addressof(v.back())); 33 | REQUIRE(v[2] == 3); 34 | REQUIRE(v.end() - v.begin() == 3); 35 | } 36 | 37 | TEST_CASE("pinned_vector::push_back() with CopyConstructible", "[pinned_vector][push_back]") 38 | { 39 | struct CopyConstructible 40 | { 41 | explicit CopyConstructible(int x) : x(x) {} 42 | int x; 43 | }; 44 | 45 | auto v = pinned_vector(max_elements(10)); 46 | 47 | auto x = CopyConstructible(1); 48 | 49 | CopyConstructible& a = v.push_back(x); 50 | REQUIRE(v.size() == 1); 51 | REQUIRE(std::addressof(a) == std::addressof(v.back())); 52 | REQUIRE(v[0].x == 1); 53 | REQUIRE(v.end() - v.begin() == 1); 54 | 55 | x.x = 2; 56 | CopyConstructible& b = v.push_back(x); 57 | REQUIRE(v.size() == 2); 58 | REQUIRE(std::addressof(b) == std::addressof(v.back())); 59 | REQUIRE(v[1].x == 2); 60 | REQUIRE(v.end() - v.begin() == 2); 61 | 62 | x.x = 3; 63 | CopyConstructible& c = v.push_back(x); 64 | REQUIRE(v.size() == 3); 65 | REQUIRE(std::addressof(c) == std::addressof(v.back())); 66 | REQUIRE(v[2].x == 3); 67 | REQUIRE(v.end() - v.begin() == 3); 68 | } 69 | 70 | TEST_CASE("pinned_vector::push_back() with MoveConstructible", "[pinned_vector][push_back]") 71 | { 72 | struct MoveConstructible 73 | { 74 | explicit MoveConstructible(int x) : x(x) {} 75 | MoveConstructible(MoveConstructible const&) = delete; 76 | MoveConstructible(MoveConstructible&&) = default; 77 | int x; 78 | }; 79 | 80 | auto v = pinned_vector(max_elements(10)); 81 | 82 | auto x = MoveConstructible(1); 83 | 84 | MoveConstructible& a = v.push_back(std::move(x)); 85 | REQUIRE(v.size() == 1); 86 | REQUIRE(std::addressof(a) == std::addressof(v.back())); 87 | REQUIRE(v[0].x == 1); 88 | REQUIRE(v.end() - v.begin() == 1); 89 | 90 | x.x = 2; 91 | MoveConstructible& b = v.push_back(std::move(x)); 92 | REQUIRE(v.size() == 2); 93 | REQUIRE(std::addressof(b) == std::addressof(v.back())); 94 | REQUIRE(v[1].x == 2); 95 | REQUIRE(v.end() - v.begin() == 2); 96 | 97 | x.x = 3; 98 | MoveConstructible& c = v.push_back(std::move(x)); 99 | REQUIRE(v.size() == 3); 100 | REQUIRE(std::addressof(c) == std::addressof(v.back())); 101 | REQUIRE(v[2].x == 3); 102 | REQUIRE(v.end() - v.begin() == 3); 103 | } 104 | 105 | TEST_CASE("pinned_vector::push_back() has strong exception guarantee on throwing copy construction", 106 | "[pinned_vector][push_back]") 107 | { 108 | struct may_throw_when_copied 109 | { 110 | may_throw_when_copied() = default; 111 | may_throw_when_copied(may_throw_when_copied const& other) 112 | { 113 | if(other.should_throw) 114 | { 115 | throw 1; 116 | } 117 | } 118 | bool should_throw = false; 119 | }; 120 | 121 | auto v = pinned_vector(max_elements(10)); 122 | 123 | auto x = may_throw_when_copied(); 124 | 125 | v.push_back(x); 126 | v.push_back(x); 127 | REQUIRE(v.size() == 2); 128 | 129 | auto state = capture_value_state(v); 130 | x.should_throw = true; 131 | REQUIRE_THROWS_AS(v.push_back(x), int); 132 | 133 | REQUIRE(capture_value_state(v) == state); 134 | } 135 | 136 | TEST_CASE("pinned_vector::push_back() has strong exception guarantee on throwing move construction", 137 | "[pinned_vector][push_back]") 138 | { 139 | struct may_throw_when_copied 140 | { 141 | may_throw_when_copied() = default; 142 | may_throw_when_copied(may_throw_when_copied&& other) 143 | { 144 | if(other.should_throw) 145 | { 146 | throw 1; 147 | } 148 | } 149 | bool should_throw = false; 150 | }; 151 | 152 | auto v = pinned_vector(max_elements(10)); 153 | 154 | auto x = may_throw_when_copied(); 155 | 156 | v.push_back(std::move(x)); 157 | v.push_back(std::move(x)); 158 | REQUIRE(v.size() == 2); 159 | 160 | auto state = capture_value_state(v); 161 | x.should_throw = true; 162 | REQUIRE_THROWS_AS(v.push_back(std::move(x)), int); 163 | 164 | REQUIRE(capture_value_state(v) == state); 165 | } 166 | 167 | TEST_CASE("pinned_vector::push_back() has strong exception guarantee if committing new memory fails", 168 | "[pinned_vector][push_back]") 169 | { 170 | struct tag 171 | {}; 172 | auto alloc = tracking_allocator(); 173 | using traits = pinned_vector_test_traits; 174 | 175 | char page[4 * sizeof(int)]; 176 | alloc.set_page_size(sizeof(page)); 177 | alloc.expect_reserve(page, 2 * sizeof(page)); 178 | alloc.expect_commit(page, sizeof(page)); 179 | alloc.expect_free(page); 180 | 181 | auto v = pinned_vector(max_pages(2)); 182 | REQUIRE(v.max_size() == 8); 183 | 184 | v.push_back(1); 185 | v.push_back(2); 186 | v.push_back(3); 187 | v.push_back(4); 188 | REQUIRE(v.size() == 4); 189 | REQUIRE(v.capacity() == 4); 190 | 191 | alloc.expect_commit_and_fail(&page[0] + sizeof(page), sizeof(page)); 192 | auto state = capture_value_state(v); 193 | REQUIRE_THROWS_AS(v.push_back(5), std::bad_alloc); 194 | 195 | REQUIRE(v.capacity() == 4); 196 | REQUIRE(capture_value_state(v) == state); 197 | } 198 | -------------------------------------------------------------------------------- /tests/pinned_vector/special.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "pinned_vector_test.hpp" 8 | 9 | #include "catch.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace mknejp::vmcontainer; 19 | using namespace vmcontainer_test; 20 | 21 | static auto round_up(std::size_t bytes, std::size_t page_size) 22 | { 23 | return ((bytes + page_size - 1) / page_size) * page_size; 24 | } 25 | 26 | static_assert(std::is_nothrow_default_constructible>::value, ""); 27 | static_assert(std::is_nothrow_move_constructible>::value, ""); 28 | static_assert(std::is_nothrow_move_assignable>::value, ""); 29 | 30 | TEST_CASE("a default constructed pinned_vector is empty", "[pinned_vector][special]") 31 | { 32 | auto v = pinned_vector(); 33 | CHECK(v.empty() == true); 34 | CHECK(v.size() == 0); 35 | CHECK(v.max_size() == 0); 36 | CHECK(v.capacity() == 0); 37 | } 38 | 39 | TEST_CASE("pinned_vector construction creates appropriate max_size", "[pinned_vector][special]") 40 | { 41 | SECTION("num_elements") 42 | { 43 | auto v = pinned_vector(max_elements(12345)); 44 | 45 | auto page_size = vm::system_default::page_size(); 46 | // rounded up to page size 47 | REQUIRE(page_size > 0); 48 | CAPTURE(page_size); 49 | auto max_size = round_up(12345 * sizeof(int), page_size) / sizeof(int); 50 | 51 | CHECK(v.max_size() == max_size); 52 | } 53 | SECTION("num_bytes") 54 | { 55 | auto v = pinned_vector(max_bytes(12345)); 56 | 57 | auto page_size = vm::system_default::page_size(); 58 | // rounded up to page size 59 | REQUIRE(page_size > 0); 60 | CAPTURE(page_size); 61 | auto max_size = round_up(12345, page_size) / sizeof(int); 62 | 63 | CHECK(v.max_size() == max_size); 64 | } 65 | SECTION("num_pages") 66 | { 67 | auto v = pinned_vector(max_pages(10)); 68 | 69 | auto page_size = vm::system_default::page_size(); 70 | REQUIRE(page_size > 0); 71 | CAPTURE(page_size); 72 | auto max_size = 10 * page_size / sizeof(int); 73 | 74 | CHECK(v.max_size() == max_size); 75 | } 76 | } 77 | 78 | TEST_CASE("pinned_vector construction from an initializer_list", "[pinned_vector][special]") 79 | { 80 | auto init = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 81 | auto v = pinned_vector(max_elements(init.size()), init); 82 | 83 | CHECK(v.size() == init.size()); 84 | CHECK(v.empty() == false); 85 | CHECK(std::equal(v.begin(), v.end(), init.begin(), init.end())); 86 | } 87 | 88 | TEST_CASE("pinned_vector construction from an iterator pair", "[pinned_vector][special]") 89 | { 90 | auto test = [](auto first, auto last, auto expected) { 91 | auto v = pinned_vector(max_elements(expected.size()), first, last); 92 | CHECK(v.size() == expected.size()); 93 | CHECK(v.empty() == false); 94 | CHECK(std::equal(v.begin(), v.end(), begin(expected), end(expected))); 95 | }; 96 | auto const expected = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 97 | SECTION("input iterator") 98 | { 99 | auto init = std::istringstream("0 1 2 3 4 5 6 7 8 9"); 100 | test(std::istream_iterator(init), std::istream_iterator(), expected); 101 | } 102 | SECTION("forward iterator") 103 | { 104 | auto init = std::forward_list{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 105 | test(begin(init), end(init), expected); 106 | } 107 | SECTION("bidirectional iterator") 108 | { 109 | auto init = std::list{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 110 | test(begin(init), end(init), expected); 111 | } 112 | SECTION("random access iterator") 113 | { 114 | auto init = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 115 | test(begin(init), end(init), expected); 116 | } 117 | } 118 | 119 | TEST_CASE("pinned_vector construction from a count and value", "[pinned_vector][special]") 120 | { 121 | auto v = pinned_vector(max_elements(10), 10, 5); 122 | 123 | CHECK(v.size() == 10); 124 | CHECK(v.empty() == false); 125 | CHECK(std::distance(v.begin(), v.end()) == 10); 126 | CHECK(std::all_of(v.begin(), v.end(), [](int x) { return x == 5; })); 127 | } 128 | 129 | TEST_CASE("pinned_vector construction from a count uses only default constructor", "[pinned_vector][special]") 130 | { 131 | static std::vector constructed; 132 | 133 | struct default_constructible 134 | { 135 | default_constructible() { constructed.push_back(this); } 136 | default_constructible(default_constructible const&) = delete; 137 | default_constructible& operator=(default_constructible const&) = delete; 138 | }; 139 | 140 | auto v = pinned_vector(max_elements(10), 10); 141 | 142 | CHECK(constructed.size() == 10); 143 | CHECK(v.size() == 10); 144 | CHECK(v.empty() == false); 145 | 146 | for(std::size_t i = 0; i < v.size(); ++i) 147 | { 148 | CAPTURE(i); 149 | REQUIRE(constructed[i] == &v[i]); 150 | } 151 | } 152 | 153 | TEST_CASE("pinned_vector constructed with elements has capacity rounded up to page size", "[pinned_vector][special]") 154 | { 155 | SECTION("count and value") 156 | { 157 | auto v = pinned_vector(max_elements(12345), 50, 1); 158 | CAPTURE(v.page_size()); 159 | CHECK(v.capacity() == round_up(50 * sizeof(int), v.page_size()) / sizeof(int)); 160 | } 161 | SECTION("count") 162 | { 163 | auto v = pinned_vector(max_elements(12345), 1234); 164 | CAPTURE(v.page_size()); 165 | CHECK(v.capacity() == round_up(1234 * sizeof(int), v.page_size()) / sizeof(int)); 166 | } 167 | SECTION("initializer_list") 168 | { 169 | auto v = pinned_vector(max_elements(12345), {1, 2, 3, 4, 5, 6}); 170 | CAPTURE(v.page_size()); 171 | CHECK(v.capacity() == round_up(6 * sizeof(int), v.page_size()) / sizeof(int)); 172 | } 173 | SECTION("iterator pair") 174 | { 175 | auto init = {1, 2, 3}; 176 | auto v = pinned_vector(max_elements(12345), begin(init), end(init)); 177 | CAPTURE(v.page_size()); 178 | CHECK(v.capacity() == round_up(3 * sizeof(int), v.page_size()) / sizeof(int)); 179 | } 180 | } 181 | 182 | TEST_CASE("pinned_vector copy construction", "[pinned_vector][special]") 183 | { 184 | auto a = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 185 | auto b = a; 186 | 187 | CHECK(a.size() == b.size()); 188 | CHECK(a.empty() == b.empty()); 189 | CHECK(std::equal(a.begin(), a.end(), b.begin(), b.end())); 190 | } 191 | 192 | TEST_CASE("pinned_vector copy assignment", "[pinned_vector][special]") 193 | { 194 | auto a = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 195 | auto b = pinned_vector(); 196 | b = a; 197 | 198 | CHECK(a.size() == b.size()); 199 | CHECK(a.empty() == b.empty()); 200 | CHECK(std::equal(a.begin(), a.end(), b.begin(), b.end())); 201 | } 202 | 203 | TEST_CASE("pinned_vector copy assignment destroys old elements", "[pinned_vector][special]") 204 | { 205 | static std::vector constructed; 206 | static std::vector destroyed; 207 | 208 | struct tracker 209 | { 210 | tracker() { constructed.push_back(reinterpret_cast(this)); } 211 | ~tracker() { destroyed.push_back(reinterpret_cast(this)); } 212 | }; 213 | 214 | auto a = pinned_vector(max_elements(10), 10); 215 | auto b = pinned_vector(max_elements(10), 10); 216 | 217 | REQUIRE(constructed.size() == 20); 218 | b = a; 219 | REQUIRE(destroyed.size() == 10); 220 | 221 | REQUIRE(std::equal(constructed.begin() + 10, constructed.begin() + 20, destroyed.begin(), destroyed.end())); 222 | } 223 | 224 | TEST_CASE("pinned_vector move construction", "[pinned_vector][special]") 225 | { 226 | auto a = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 227 | auto a_state = capture_value_state(a); 228 | 229 | auto b = std::move(a); 230 | 231 | REQUIRE(a.size() == 0); 232 | REQUIRE(a.empty() == true); 233 | REQUIRE(capture_value_state(b) == a_state); 234 | } 235 | 236 | TEST_CASE("pinned_vector move assignment", "[pinned_vector][special]") 237 | { 238 | auto a = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 239 | auto a_state = capture_value_state(a); 240 | 241 | auto b = pinned_vector(); 242 | b = std::move(a); 243 | 244 | REQUIRE(a.size() == 0); 245 | REQUIRE(a.empty() == true); 246 | REQUIRE(capture_value_state(b) == a_state); 247 | } 248 | 249 | TEST_CASE("pinned_vector move assignment destroys old elements", "[pinned_vector][special]") 250 | { 251 | static std::vector constructed; 252 | static std::vector destroyed; 253 | 254 | struct tracker 255 | { 256 | tracker() { constructed.push_back(reinterpret_cast(this)); } 257 | ~tracker() { destroyed.push_back(reinterpret_cast(this)); } 258 | }; 259 | 260 | auto a = pinned_vector(max_elements(10), 10); 261 | auto b = pinned_vector(max_elements(10), 10); 262 | 263 | REQUIRE(constructed.size() == 20); 264 | b = std::move(a); 265 | REQUIRE(destroyed.size() == 10); 266 | 267 | REQUIRE(std::equal(constructed.begin() + 10, constructed.begin() + 20, destroyed.begin(), destroyed.end())); 268 | } 269 | 270 | TEST_CASE("pinned_vector destructor destroys its elements", "[pinned_vector][special]") 271 | { 272 | static std::vector constructed; 273 | static std::vector destroyed; 274 | 275 | struct tracker 276 | { 277 | tracker() { constructed.push_back(reinterpret_cast(this)); } 278 | ~tracker() { destroyed.push_back(reinterpret_cast(this)); } 279 | }; 280 | 281 | { 282 | auto vec = pinned_vector(max_elements(10), 10); 283 | 284 | REQUIRE(vec.size() == 10); 285 | REQUIRE(constructed.size() == 10); 286 | } 287 | REQUIRE(std::equal(constructed.begin(), constructed.end(), destroyed.begin(), destroyed.end())); 288 | } 289 | 290 | TEST_CASE("pinned_vector assignment operator with initializer_list", "[pinned_vector][special]") 291 | { 292 | auto v = pinned_vector(max_elements(10), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); 293 | 294 | v = {10, 11, 12, 13, 14}; 295 | 296 | CHECK(v.size() == 5); 297 | CHECK(v.empty() == false); 298 | CHECK(v[0] == 10); 299 | CHECK(v[1] == 11); 300 | CHECK(v[2] == 12); 301 | CHECK(v[3] == 13); 302 | CHECK(v[4] == 14); 303 | } 304 | 305 | TEST_CASE("pinned_vector::swap()", "[pinned_vector][special]") 306 | { 307 | auto init_a = {1, 2, 3, 4, 5}; 308 | auto init_b = {6, 7, 8, 9}; 309 | 310 | auto a = pinned_vector(max_elements(5), init_a); 311 | auto b = pinned_vector(max_elements(4), init_b); 312 | 313 | auto a_state = capture_value_state(a); 314 | auto b_state = capture_value_state(b); 315 | 316 | REQUIRE(a.size() == 5); 317 | REQUIRE(b.size() == 4); 318 | 319 | SECTION("free swap()") 320 | { 321 | swap(a, b); 322 | static_assert(noexcept(swap(a, b)), "pinned_vector swap() is noexcept"); 323 | } 324 | SECTION("member swap()") 325 | { 326 | a.swap(b); 327 | static_assert(noexcept(a.swap(b)), "pinned_vector swap() is noexcept"); 328 | } 329 | 330 | REQUIRE(capture_value_state(a) == b_state); 331 | REQUIRE(capture_value_state(b) == a_state); 332 | 333 | REQUIRE(std::equal(a.begin(), a.end(), begin(init_b), end(init_b))); 334 | REQUIRE(std::equal(b.begin(), b.end(), begin(init_a), end(init_a))); 335 | } 336 | -------------------------------------------------------------------------------- /tests/pinned_vector_test.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/pinned_vector.hpp" 8 | 9 | #include "allocator_mocks.hpp" 10 | 11 | namespace vmcontainer_test 12 | { 13 | template 14 | struct pinned_vector_test_traits 15 | { 16 | using storage_type = mknejp::vmcontainer::vm::page_stack_base; 17 | using growth_factor = typename mknejp::vmcontainer::pinned_vector_traits::growth_factor; 18 | }; 19 | 20 | template 21 | struct pinned_vector_value_state 22 | { 23 | using container = typename mknejp::vmcontainer::pinned_vector; 24 | 25 | pinned_vector_value_state(container const& c) 26 | : begin(c.begin()), end(c.end()), data(c.data()), size(c.size()), max_size(c.max_size()), empty(c.empty()) 27 | {} 28 | 29 | typename container::const_iterator begin; 30 | typename container::const_iterator end; 31 | T const* data; 32 | typename container::size_type size; 33 | typename container::size_type max_size; 34 | bool empty; 35 | 36 | auto operator==(pinned_vector_value_state const& other) const -> bool 37 | { 38 | return begin == other.begin && end == other.end && data == other.data && size == other.size 39 | && max_size == other.max_size && empty == other.empty; 40 | } 41 | }; 42 | 43 | template 44 | auto capture_value_state(typename mknejp::vmcontainer::pinned_vector const& c) 45 | { 46 | return pinned_vector_value_state(c); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/vm/page_stack.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/vm.hpp" 8 | 9 | #include "allocator_mocks.hpp" 10 | 11 | #include "catch.hpp" 12 | 13 | #include 14 | 15 | using namespace mknejp::vmcontainer; 16 | 17 | static_assert(std::is_base_of, vm::page_stack>(), ""); 18 | 19 | static_assert(std::is_nothrow_default_constructible::value, ""); 20 | static_assert(std::is_nothrow_move_constructible::value, ""); 21 | static_assert(std::is_nothrow_move_assignable::value, ""); 22 | 23 | TEST_CASE("vm::page_stack", "[page_stack]") 24 | { 25 | struct Tag 26 | {}; 27 | using virtual_memory_system_stub = vmcontainer_test::virtual_memory_system_stub; 28 | auto alloc = vmcontainer_test::tracking_allocator(); 29 | 30 | virtual_memory_system_stub::page_size = [] { return 100; }; 31 | 32 | using page_stack = vm::page_stack_base; 33 | 34 | SECTION("default constructed has no reservation") 35 | { 36 | { 37 | auto vmps = page_stack(); 38 | CHECK(vmps.base() == nullptr); 39 | CHECK(vmps.reserved_bytes() == 0); 40 | CHECK(vmps.committed_bytes() == 0); 41 | CHECK(vmps.page_size() == 100); 42 | } 43 | CHECK(alloc.reservations() == 0); 44 | CHECK(alloc.reserve_calls() == 0); 45 | CHECK(alloc.free_calls() == 0); 46 | CHECK(alloc.commit_calls() == 0); 47 | CHECK(alloc.decommit_calls() == 0); 48 | } 49 | 50 | SECTION("ctor/dtor reserve/free virtual memory") 51 | { 52 | { 53 | char block[1000]; 54 | alloc.expect_reserve(block, 1000); 55 | auto vmps = page_stack(num_bytes(1000)); 56 | CHECK(alloc.reservations() == 1); 57 | CHECK(alloc.reserve_calls() == 1); 58 | CHECK(alloc.free_calls() == 0); 59 | CHECK(vmps.base() == block); 60 | CHECK(vmps.reserved_bytes() == 1000); 61 | CHECK(vmps.committed_bytes() == 0); 62 | CHECK(vmps.page_size() == 100); 63 | alloc.expect_free(block); 64 | } 65 | CHECK(alloc.reservations() == 0); 66 | CHECK(alloc.reserve_calls() == 1); 67 | CHECK(alloc.free_calls() == 1); 68 | CHECK(alloc.commit_calls() == 0); 69 | CHECK(alloc.decommit_calls() == 0); 70 | } 71 | 72 | SECTION("ctor taking num_pages reserves the equivalent number of bytes") 73 | { 74 | { 75 | char block[1000]; 76 | alloc.expect_reserve(block, 1000); 77 | auto vmps = page_stack(num_pages(10)); 78 | CHECK(alloc.reservations() == 1); 79 | CHECK(alloc.reserve_calls() == 1); 80 | CHECK(alloc.free_calls() == 0); 81 | CHECK(vmps.base() == block); 82 | CHECK(vmps.reserved_bytes() == 1000); 83 | CHECK(vmps.committed_bytes() == 0); 84 | CHECK(vmps.page_size() == 100); 85 | alloc.expect_free(block); 86 | } 87 | CHECK(alloc.reservations() == 0); 88 | CHECK(alloc.reserve_calls() == 1); 89 | CHECK(alloc.free_calls() == 1); 90 | CHECK(alloc.commit_calls() == 0); 91 | CHECK(alloc.decommit_calls() == 0); 92 | } 93 | 94 | SECTION("move construction") 95 | { 96 | { 97 | char block[1000]; 98 | alloc.expect_reserve(block, 1000); 99 | auto vmps1 = page_stack(num_bytes(1000)); 100 | alloc.expect_commit(block, 400); 101 | vmps1.resize(400); 102 | 103 | auto vmps2 = std::move(vmps1); 104 | CHECK(alloc.reservations() == 1); 105 | CHECK(alloc.reserve_calls() == 1); 106 | CHECK(alloc.free_calls() == 0); 107 | CHECK(alloc.commit_calls() == 1); 108 | CHECK(alloc.decommit_calls() == 0); 109 | CHECK(vmps1.base() == nullptr); 110 | CHECK(vmps2.base() == block); 111 | CHECK(vmps1.reserved_bytes() == 0); 112 | CHECK(vmps2.reserved_bytes() == 1000); 113 | CHECK(vmps1.committed_bytes() == 0); 114 | CHECK(vmps2.committed_bytes() == 400); 115 | alloc.expect_free(block); 116 | } 117 | CHECK(alloc.reservations() == 0); 118 | CHECK(alloc.reserve_calls() == 1); 119 | CHECK(alloc.free_calls() == 1); 120 | CHECK(alloc.commit_calls() == 1); 121 | CHECK(alloc.decommit_calls() == 0); 122 | } 123 | 124 | SECTION("move assignment") 125 | { 126 | { 127 | char block1[1000]; 128 | char block2[2000]; 129 | alloc.expect_reserve(block1, 1000); 130 | auto vmps1 = page_stack(num_bytes(1000)); 131 | alloc.expect_commit(block1, 400); 132 | vmps1.resize(400); 133 | 134 | { 135 | alloc.expect_reserve(block2, 2000); 136 | auto vmps2 = page_stack(num_bytes(2000)); 137 | alloc.expect_commit(block2, 300); 138 | vmps2.resize(300); 139 | 140 | alloc.expect_free(block1); 141 | vmps1 = std::move(vmps2); 142 | 143 | CHECK(alloc.reservations() == 1); 144 | CHECK(alloc.reserve_calls() == 2); 145 | CHECK(alloc.free_calls() == 1); 146 | CHECK(alloc.commit_calls() == 2); 147 | CHECK(alloc.decommit_calls() == 0); 148 | CHECK(vmps1.base() == block2); 149 | CHECK(vmps2.base() == nullptr); 150 | CHECK(vmps1.reserved_bytes() == 2000); 151 | CHECK(vmps2.reserved_bytes() == 0); 152 | CHECK(vmps1.committed_bytes() == 300); 153 | CHECK(vmps2.committed_bytes() == 0); 154 | } 155 | alloc.expect_free(block2); 156 | } 157 | CHECK(alloc.reservations() == 0); 158 | CHECK(alloc.reserve_calls() == 2); 159 | CHECK(alloc.free_calls() == 2); 160 | CHECK(alloc.commit_calls() == 2); 161 | CHECK(alloc.decommit_calls() == 0); 162 | } 163 | 164 | SECTION("self move assignment is a no-op") 165 | { 166 | char block[1000]; 167 | alloc.expect_reserve(block, 1000); 168 | auto vmps = page_stack(num_bytes(1000)); 169 | alloc.expect_commit(block, 400); 170 | vmps.resize(400); 171 | 172 | #ifdef __clang__ 173 | # pragma clang diagnostic push 174 | # pragma clang diagnostic ignored "-Wself-move" 175 | #endif 176 | vmps = std::move(vmps); 177 | #ifdef __clang__ 178 | # pragma clang diagnostic pop 179 | #endif 180 | 181 | CHECK(vmps.base() == block); 182 | CHECK(vmps.reserved_bytes() == 1000); 183 | CHECK(vmps.committed_bytes() == 400); 184 | 185 | CHECK(alloc.reservations() == 1); 186 | CHECK(alloc.reserve_calls() == 1); 187 | CHECK(alloc.free_calls() == 0); 188 | CHECK(alloc.commit_calls() == 1); 189 | CHECK(alloc.decommit_calls() == 0); 190 | alloc.expect_free(block); 191 | } 192 | 193 | SECTION("single resize() and inverse resize()") 194 | { 195 | { 196 | char block[1000]; 197 | alloc.expect_reserve(block, 1000); 198 | auto vmps = page_stack(num_bytes(1000)); 199 | alloc.expect_commit(block, 100); 200 | vmps.resize(100); 201 | CHECK(vmps.committed_bytes() == 100); 202 | CHECK(alloc.commit_calls() == 1); 203 | CHECK(alloc.decommit_calls() == 0); 204 | 205 | alloc.expect_decommit(block, 100); 206 | vmps.resize(0); 207 | CHECK(vmps.committed_bytes() == 0); 208 | CHECK(alloc.commit_calls() == 1); 209 | CHECK(alloc.decommit_calls() == 1); 210 | 211 | alloc.expect_free(block); 212 | } 213 | CHECK(alloc.reservations() == 0); 214 | CHECK(alloc.reserve_calls() == 1); 215 | CHECK(alloc.free_calls() == 1); 216 | CHECK(alloc.commit_calls() == 1); 217 | CHECK(alloc.decommit_calls() == 1); 218 | } 219 | SECTION("multiple growing and shrinking resize()") 220 | { 221 | { 222 | char block[1000]; 223 | alloc.expect_reserve(block, 1000); 224 | auto vmps = page_stack(num_bytes(1000)); 225 | alloc.expect_commit(block, 100); 226 | vmps.resize(100); 227 | CHECK(vmps.committed_bytes() == 100); 228 | CHECK(alloc.commit_calls() == 1); 229 | CHECK(alloc.decommit_calls() == 0); 230 | 231 | alloc.expect_commit(block + 100, 200); 232 | vmps.resize(300); 233 | CHECK(vmps.committed_bytes() == 300); 234 | CHECK(alloc.commit_calls() == 2); 235 | CHECK(alloc.decommit_calls() == 0); 236 | 237 | alloc.expect_decommit(block + 200, 100); 238 | vmps.resize(200); 239 | CHECK(vmps.committed_bytes() == 200); 240 | CHECK(alloc.commit_calls() == 2); 241 | CHECK(alloc.decommit_calls() == 1); 242 | 243 | alloc.expect_commit(block + 200, 200); 244 | vmps.resize(400); 245 | CHECK(vmps.committed_bytes() == 400); 246 | CHECK(alloc.commit_calls() == 3); 247 | CHECK(alloc.decommit_calls() == 1); 248 | 249 | alloc.expect_decommit(block + 300, 100); 250 | vmps.resize(300); 251 | CHECK(vmps.committed_bytes() == 300); 252 | CHECK(alloc.commit_calls() == 3); 253 | CHECK(alloc.decommit_calls() == 2); 254 | 255 | alloc.expect_free(block); 256 | } 257 | CHECK(alloc.reservations() == 0); 258 | CHECK(alloc.reserve_calls() == 1); 259 | CHECK(alloc.free_calls() == 1); 260 | CHECK(alloc.commit_calls() == 3); 261 | CHECK(alloc.decommit_calls() == 2); 262 | } 263 | SECTION("resize() amount is rounded up to page size") 264 | { 265 | { 266 | char block[1000]; 267 | alloc.expect_reserve(block, 1000); 268 | auto vmps = page_stack(num_bytes(1000)); 269 | vmps.resize(0); 270 | CHECK(vmps.committed_bytes() == 0); 271 | CHECK(alloc.commit_calls() == 0); 272 | CHECK(alloc.decommit_calls() == 0); 273 | 274 | alloc.expect_commit(block, 100); 275 | vmps.resize(1); 276 | CHECK(vmps.committed_bytes() == 100); 277 | CHECK(alloc.commit_calls() == 1); 278 | CHECK(alloc.decommit_calls() == 0); 279 | 280 | alloc.expect_commit(block + 100, 100); 281 | vmps.resize(199); 282 | CHECK(vmps.committed_bytes() == 200); 283 | CHECK(alloc.commit_calls() == 2); 284 | CHECK(alloc.decommit_calls() == 0); 285 | 286 | alloc.expect_commit(block + 200, 200); 287 | vmps.resize(301); 288 | CHECK(vmps.committed_bytes() == 400); 289 | CHECK(alloc.commit_calls() == 3); 290 | CHECK(alloc.decommit_calls() == 0); 291 | 292 | vmps.resize(399); 293 | CHECK(vmps.committed_bytes() == 400); 294 | CHECK(alloc.commit_calls() == 3); 295 | CHECK(alloc.decommit_calls() == 0); 296 | 297 | vmps.resize(305); 298 | CHECK(vmps.committed_bytes() == 400); 299 | CHECK(alloc.commit_calls() == 3); 300 | CHECK(alloc.decommit_calls() == 0); 301 | 302 | alloc.expect_free(block); 303 | } 304 | CHECK(alloc.reservations() == 0); 305 | CHECK(alloc.reserve_calls() == 1); 306 | CHECK(alloc.free_calls() == 1); 307 | CHECK(alloc.commit_calls() == 3); 308 | CHECK(alloc.decommit_calls() == 0); 309 | } 310 | 311 | SECTION("swap") 312 | { 313 | { 314 | char block1[1000]; 315 | char block2[2000]; 316 | alloc.expect_reserve(block1, 1000); 317 | auto vmps1 = page_stack(num_bytes(1000)); 318 | alloc.expect_commit(block1, 400); 319 | vmps1.resize(400); 320 | 321 | { 322 | alloc.expect_reserve(block2, 2000); 323 | auto vmps2 = page_stack(num_bytes(2000)); 324 | alloc.expect_commit(block2, 300); 325 | vmps2.resize(300); 326 | 327 | using std::swap; 328 | static_assert(noexcept(swap(vmps1, vmps2)), "page_stack::swap() is not noexcept"); 329 | 330 | swap(vmps1, vmps2); 331 | CHECK(alloc.reservations() == 2); 332 | CHECK(alloc.reserve_calls() == 2); 333 | CHECK(alloc.free_calls() == 0); 334 | CHECK(alloc.commit_calls() == 2); 335 | CHECK(alloc.decommit_calls() == 0); 336 | CHECK(vmps1.base() == block2); 337 | CHECK(vmps2.base() == block1); 338 | CHECK(vmps1.reserved_bytes() == 2000); 339 | CHECK(vmps2.reserved_bytes() == 1000); 340 | CHECK(vmps1.committed_bytes() == 300); 341 | CHECK(vmps2.committed_bytes() == 400); 342 | 343 | alloc.expect_free(block1); 344 | } 345 | alloc.expect_free(block2); 346 | } 347 | CHECK(alloc.reservations() == 0); 348 | CHECK(alloc.reserve_calls() == 2); 349 | CHECK(alloc.free_calls() == 2); 350 | CHECK(alloc.commit_calls() == 2); 351 | CHECK(alloc.decommit_calls() == 0); 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /tests/vm/reservation.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright Miro Knejp 2021. 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE.txt or copy at https://www.boost.org/LICENSE_1_0.txt) 5 | // 6 | 7 | #include "vmcontainer/vm.hpp" 8 | 9 | #include "allocator_mocks.hpp" 10 | 11 | #include "catch.hpp" 12 | 13 | #include 14 | 15 | using namespace mknejp::vmcontainer; 16 | 17 | static_assert(std::is_base_of, vm::reservation>(), ""); 18 | 19 | static_assert(std::is_nothrow_default_constructible::value, ""); 20 | static_assert(std::is_nothrow_move_constructible::value, ""); 21 | static_assert(std::is_nothrow_move_assignable::value, ""); 22 | 23 | TEST_CASE("vm::reservation", "[reservation]") 24 | { 25 | struct Tag 26 | {}; 27 | using virtual_memory_system_stub = vmcontainer_test::virtual_memory_system_stub; 28 | auto alloc = vmcontainer_test::tracking_allocator(); 29 | 30 | virtual_memory_system_stub::page_size = [] { return 100; }; 31 | 32 | using reservation = vm::reservation_base; 33 | 34 | SECTION("default constructed has no reservation") 35 | { 36 | { 37 | char block[100]; 38 | alloc.expect_reserve(block, 100); 39 | auto vmr = reservation(); 40 | CHECK(vmr.base() == nullptr); 41 | CHECK(vmr.reserved_bytes() == 0); 42 | alloc.expect_free(block); 43 | } 44 | CHECK(alloc.reservations() == 0); 45 | CHECK(alloc.reserve_calls() == 0); 46 | CHECK(alloc.free_calls() == 0); 47 | } 48 | 49 | SECTION("ctor/dtor reserve/free virtual memory") 50 | { 51 | { 52 | char block[100]; 53 | alloc.expect_reserve(block, 100); 54 | auto vmr = reservation(num_bytes(100)); 55 | CHECK(alloc.reservations() == 1); 56 | CHECK(alloc.reserve_calls() == 1); 57 | CHECK(alloc.free_calls() == 0); 58 | CHECK(vmr.base() == block); 59 | CHECK(vmr.reserved_bytes() == 100); 60 | alloc.expect_free(block); 61 | } 62 | CHECK(alloc.reservations() == 0); 63 | CHECK(alloc.reserve_calls() == 1); 64 | CHECK(alloc.free_calls() == 1); 65 | } 66 | 67 | SECTION("round up reservation to page size") 68 | { 69 | char block[100]; 70 | alloc.expect_reserve(block, 100); 71 | auto vmr = reservation(num_bytes(1)); 72 | CHECK(vmr.base() == block); 73 | CHECK(vmr.reserved_bytes() == 100); 74 | alloc.expect_free(block); 75 | } 76 | 77 | SECTION("move construction") 78 | { 79 | { 80 | char block[100]; 81 | alloc.expect_reserve(block, 100); 82 | auto vmr1 = reservation(num_bytes(100)); 83 | 84 | auto vmr2 = std::move(vmr1); 85 | CHECK(alloc.reservations() == 1); 86 | CHECK(alloc.reserve_calls() == 1); 87 | CHECK(alloc.free_calls() == 0); 88 | CHECK(vmr1.base() == nullptr); 89 | CHECK(vmr1.reserved_bytes() == 0); 90 | CHECK(vmr2.base() == block); 91 | CHECK(vmr2.reserved_bytes() == 100); 92 | alloc.expect_free(block); 93 | } 94 | CHECK(alloc.reservations() == 0); 95 | CHECK(alloc.reserve_calls() == 1); 96 | CHECK(alloc.free_calls() == 1); 97 | } 98 | 99 | SECTION("move assignment") 100 | { 101 | { 102 | char block1[100]; 103 | char block2[200]; 104 | alloc.expect_reserve(block1, 100); 105 | auto vmr1 = reservation(num_bytes(100)); 106 | 107 | alloc.expect_free(block1); 108 | alloc.expect_reserve(block2, 200); 109 | auto vmr2 = reservation(num_bytes(200)); 110 | 111 | vmr1 = std::move(vmr2); 112 | CHECK(alloc.reservations() == 1); 113 | CHECK(alloc.reserve_calls() == 2); 114 | CHECK(alloc.free_calls() == 1); 115 | CHECK(vmr1.base() == block2); 116 | CHECK(vmr1.reserved_bytes() == 200); 117 | CHECK(vmr2.base() == nullptr); 118 | CHECK(vmr2.reserved_bytes() == 0); 119 | alloc.expect_free(block2); 120 | } 121 | CHECK(alloc.reservations() == 0); 122 | CHECK(alloc.reserve_calls() == 2); 123 | CHECK(alloc.free_calls() == 2); 124 | } 125 | 126 | SECTION("self move assignment is a no-op") 127 | { 128 | char block[100]; 129 | alloc.expect_reserve(block, 100); 130 | auto vmr1 = reservation(num_bytes(100)); 131 | 132 | #ifdef __clang__ 133 | # pragma clang diagnostic push 134 | # pragma clang diagnostic ignored "-Wself-move" 135 | #endif 136 | vmr1 = std::move(vmr1); 137 | #ifdef __clang__ 138 | # pragma clang diagnostic pop 139 | #endif 140 | 141 | CHECK(vmr1.base() == block); 142 | CHECK(vmr1.reserved_bytes() == 100); 143 | 144 | CHECK(alloc.reservations() == 1); 145 | CHECK(alloc.reserve_calls() == 1); 146 | CHECK(alloc.free_calls() == 0); 147 | alloc.expect_free(block); 148 | } 149 | 150 | SECTION("swap") 151 | { 152 | { 153 | char block1[100]; 154 | char block2[200]; 155 | alloc.expect_reserve(block1, 100); 156 | auto vmr1 = reservation(num_bytes(100)); 157 | { 158 | alloc.expect_reserve(block2, 200); 159 | auto vmr2 = reservation(num_bytes(200)); 160 | 161 | using std::swap; 162 | static_assert(noexcept(swap(vmr1, vmr2)), "reservation::swap() is not noexcept"); 163 | 164 | swap(vmr1, vmr2); 165 | CHECK(alloc.reservations() == 2); 166 | CHECK(alloc.reserve_calls() == 2); 167 | CHECK(alloc.free_calls() == 0); 168 | CHECK(vmr1.base() == block2); 169 | CHECK(vmr2.base() == block1); 170 | CHECK(vmr1.reserved_bytes() == 200); 171 | CHECK(vmr2.reserved_bytes() == 100); 172 | alloc.expect_free(block1); 173 | } 174 | alloc.expect_free(block2); 175 | } 176 | CHECK(alloc.reservations() == 0); 177 | CHECK(alloc.reserve_calls() == 2); 178 | CHECK(alloc.free_calls() == 2); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vmcontainer", 3 | "version-string": "alpha", 4 | "dependencies": [ 5 | "benchmark", 6 | "catch2" 7 | ] 8 | } 9 | --------------------------------------------------------------------------------