├── .dir-locals.el ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── TODO.md ├── dub.sdl ├── dub.selections.json ├── reggaefile.d ├── source └── automem │ ├── allocator.d │ ├── array.d │ ├── package.d │ ├── ref_counted.d │ ├── traits.d │ ├── unique.d │ ├── utils.d │ └── vector.d ├── tests ├── ut │ ├── issues.d │ ├── package.d │ ├── ref_counted.d │ ├── unique.d │ └── vector.d └── ut_main.d └── travis_install.sh /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((d-mode 5 | (fldd-dub-configuration . "unittest"))) 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: Dub Test 7 | strategy: 8 | matrix: 9 | os: 10 | - ubuntu-24.04 11 | - windows-2022 12 | #- macos-13 13 | dc: 14 | - dmd-2.109.1 15 | - dmd-2.100.0 16 | - ldc-1.40.0 17 | - ldc-1.28.0 18 | 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - name: Install D compiler 24 | uses: dlang-community/setup-dlang@v2 25 | with: 26 | compiler: ${{ matrix.dc }} 27 | 28 | - name: Run tests on Posix 29 | if: runner.os != 'Windows' 30 | run: dub test -q --build=unittest-cov # -c asan 31 | 32 | - name: Run tests on Windows 33 | if: runner.os == 'Windows' 34 | run: dub test -q --build=unittest-cov 35 | 36 | - name: Build binary 37 | run: dub build -q 38 | 39 | - uses: codecov/codecov-action@v5.1.2 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | __test__*__ 7 | bin* 8 | *.a 9 | *.ninja 10 | compile_commands.json 11 | .reggae 12 | .ninja_deps 13 | .ninja_log 14 | *.lst -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | sudo: false 3 | 4 | install: 5 | - ./travis_install.sh 6 | 7 | packages: 8 | - gcc-multilib 9 | 10 | matrix: 11 | include: 12 | - d: dmd-2.092.1 13 | - d: ldc-1.20.1 14 | 15 | script: 16 | - dub test --build=unittest-cov --compiler=${DC} -c asan 17 | - dub build --compiler=${DC} 18 | 19 | after_success: 20 | - bash <(curl -s https://codecov.io/bash) 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017-2019, Atila Neves 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # automem - smart pointers for D 2 | 3 | [![Build Status](https://github.com/atilaneves/automem/workflows/CI/badge.svg)](https://github.com/atilaneves/automem/actions) 4 | [![Coverage](https://codecov.io/gh/atilaneves/automem/branch/master/graph/badge.svg)](https://codecov.io/gh/atilaneves/automem) 5 | [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/P3yCpG) 6 | 7 | ## C++-style automatic memory management smart pointers for D using `std.experimental.allocator`. 8 | 9 | Unlike the C++ variants, the smart pointers themselves allocate the memory for the objects they contain. 10 | That ensures the right allocator is used to dispose of the memory as well. 11 | 12 | Allocators are template arguments instead of using `theAllocator` so 13 | that these smart pointers can be used in `@nogc` code. However, they 14 | will default to `typeof(theAllocator)` for simplicity. The examples 15 | above will be explicit. 16 | 17 | Another reason to have to pass in the type of allocator is to decide how it is to 18 | be stored. Stateless allocators can be "stored" by value and imply zero-cost `Unique` pointers. 19 | Singleton allocators such as Mallocator (that have an `instance` attribute/member function) 20 | don't need to be passed in to the constructor. This is detected at compile-time as an example 21 | of design by instrospection. 22 | 23 | `RefCounted` leverages D's type system by doing atomic reference counting *iff* the type of the contained 24 | object is `shared`. Otherwise it's non-atomic. 25 | 26 | Sample code: 27 | 28 | ```d 29 | @safe unittest { 30 | 31 | import std.algorithm: move; 32 | 33 | static struct Point { 34 | int x; 35 | int y; 36 | } 37 | 38 | // set theAllocator as desired beforehand, e.g. 39 | // theAllocator = allocatorObject(Mallocator.instance) 40 | 41 | { 42 | // must pass arguments to initialise the contained object 43 | auto u1 = Unique!Point(2, 3); 44 | assert(*u1 == Point(2, 3)); 45 | assert(u1.y == 3); 46 | 47 | // auto u2 = u1; // won't compile, can only move 48 | typeof(u1) u2 = () @trusted { return u1.move; }(); 49 | assert(cast(bool)u1 == false); // u1 is now empty 50 | } 51 | // memory freed for the Point structure created in the block 52 | 53 | { 54 | auto s1 = RefCounted!Point(4, 5); 55 | assert(*s1 == Point(4, 5)); 56 | assert(s1.x == 4); 57 | { 58 | auto s2 = s1; // can be copied 59 | } // ref count goes to 1 here 60 | 61 | } // ref count goes to 0 here, memory released 62 | 63 | { 64 | import std.algorithm: map; 65 | import std.range: iota; 66 | 67 | // `vector` is also known as `array` 68 | auto vec = vector(Point(1, 2), Point(3, 4), Point(5, 6)); 69 | assert(equal(vec.range, [Point(1, 2), Point(3, 4), Point(5, 6)])); 70 | 71 | vec.length = 1; 72 | assert(equal(vec.range, [Point(1, 2)])); 73 | 74 | vec ~= Point(7, 8); 75 | assert(equal(vec.range, [Point(1, 2), Point(7, 8)])); 76 | 77 | vec ~= 2.iota.map!(i => Point(i + 10, i + 11)); 78 | assert(equal(vec.range, [Point(1, 2), Point(7, 8), Point(10, 11), Point(11, 12)])); 79 | } // memory for the array released here 80 | } 81 | 82 | 83 | // @nogc test - must explicitly use the allocator for compile-time guarantees 84 | @system @nogc unittest { 85 | import stdx.allocator.mallocator: Mallocator; 86 | 87 | static struct Point { 88 | int x; 89 | int y; 90 | } 91 | 92 | { 93 | // must pass arguments to initialise the contained object 94 | auto u1 = Unique!(Point, Mallocator)(2, 3); 95 | assert(*u1 == Point(2, 3)); 96 | assert(u1.y == 3); 97 | } 98 | // memory freed for the Point structure created in the block 99 | 100 | // similarly for the other types 101 | } 102 | ``` 103 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * UniqueArray with 2 or more dimensions 2 | * Weak references to break cycles 3 | * RefCountedArray 4 | * Use mallocator as well as test allocator for all unit tests 5 | * Benchmark RefCounted vs C++ std::shared_ptr 6 | * deferred_ptr? 7 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "automem" 2 | authors "Atila Neves" 3 | description "Automatic memory management" 4 | license "BSD 3-clause" 5 | targetType "library" 6 | targetPath "bin" 7 | 8 | 9 | configuration "library" { 10 | } 11 | 12 | 13 | configuration "unittest" { 14 | targetType "executable" 15 | targetName "ut" 16 | 17 | importPaths "tests" 18 | sourcePaths "tests" 19 | mainSourceFile "tests/ut_main.d" 20 | 21 | dflags "-preview=dip1000" "-preview=dip1008" 22 | versions "AutomemTesting" 23 | 24 | dependency "unit-threaded" version="*" 25 | dependency "test_allocator" version="*" 26 | } 27 | 28 | 29 | configuration "asan" { 30 | targetType "executable" 31 | targetName "asan" 32 | 33 | importPaths "tests" 34 | sourcePaths "tests" 35 | mainSourceFile "tests/ut_main.d" 36 | 37 | # -preview=dip1008 causes asan issues with malloc 38 | dflags "-preview=dip1000" 39 | dflags "-fsanitize=address" platform="ldc" 40 | 41 | // unit threaded light is necessary for the tests to actually run 42 | versions "AutomemTesting" "AutomemAsan" "unitUnthreaded" "unitThreadedLight" 43 | 44 | dependency "unit-threaded" version="*" 45 | dependency "test_allocator" version="*" 46 | } 47 | 48 | 49 | configuration "utl" { 50 | targetType "executable" 51 | targetName "utl" 52 | 53 | importPaths "tests" 54 | sourcePaths "tests" 55 | 56 | dflags "-preview=dip1000" "-preview=dip1008" 57 | 58 | versions "AutomemTesting" "unitThreadedLight" 59 | 60 | dependency "unit-threaded" version="*" 61 | dependency "test_allocator" version="*" 62 | } 63 | -------------------------------------------------------------------------------- /dub.selections.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileVersion": 1, 3 | "versions": { 4 | "test_allocator": "0.3.4", 5 | "unit-threaded": "2.1.9" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /reggaefile.d: -------------------------------------------------------------------------------- 1 | import reggae; 2 | import std.typecons; 3 | 4 | alias lib = dubBuild!(); 5 | alias ut = dubTest!(CompilationMode.options, Yes.coverage); 6 | alias asan = dubBuild!( 7 | Configuration("asan"), 8 | CompilerFlags("-unittest -cov -fsanitize=address"), 9 | LinkerFlags("-fsanitize=address"), 10 | ); 11 | 12 | 13 | mixin build!(lib, optional!ut, optional!asan); 14 | -------------------------------------------------------------------------------- /source/automem/allocator.d: -------------------------------------------------------------------------------- 1 | /** 2 | Custom versions of std.experimental.allocator functions (unfortunately) 3 | */ 4 | module automem.allocator; 5 | 6 | import automem.utils: destruct; 7 | 8 | /** 9 | 10 | Destroys and then deallocates (using $(D alloc)) the object pointed to by a 11 | pointer, the class object referred to by a $(D class) or $(D interface) 12 | reference, or an entire array. It is assumed the respective entities had been 13 | allocated with the same allocator. 14 | 15 | */ 16 | void dispose(A, T)(auto ref A alloc, T* p) 17 | { 18 | import std.traits: hasElaborateDestructor; 19 | 20 | static if (hasElaborateDestructor!T) 21 | { 22 | destruct(*p); 23 | } 24 | alloc.deallocate((cast(void*) p)[0 .. T.sizeof]); 25 | } 26 | 27 | /// Ditto 28 | void dispose(A, T)(auto ref A alloc, T p) 29 | if (is(T == class) || is(T == interface)) 30 | { 31 | 32 | if (!p) return; 33 | static if (is(T == interface)) 34 | { 35 | version(Windows) 36 | { 37 | import core.sys.windows.unknwn : IUnknown; 38 | static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in " 39 | ~ __PRETTY_FUNCTION__); 40 | } 41 | auto ob = cast(Object) p; 42 | } 43 | else 44 | alias ob = p; 45 | auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length]; 46 | 47 | destruct(p); 48 | 49 | alloc.deallocate(support); 50 | } 51 | 52 | /// Ditto 53 | void dispose(A, T)(auto ref A alloc, T[] array) 54 | { 55 | import std.traits: hasElaborateDestructor; 56 | 57 | static if (hasElaborateDestructor!(typeof(array[0]))) 58 | { 59 | foreach (ref e; array) 60 | { 61 | destruct(e); 62 | } 63 | } 64 | alloc.deallocate(array); 65 | } 66 | -------------------------------------------------------------------------------- /source/automem/array.d: -------------------------------------------------------------------------------- 1 | /** 2 | Aliases for automem.vector 3 | */ 4 | module automem.array; 5 | 6 | public import automem.vector: array = vector; 7 | public import automem.vector: Array = Vector; 8 | -------------------------------------------------------------------------------- /source/automem/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | C++-style automatic memory management smart pointers for D using `std.experimental.allocator`. 3 | 4 | Unlike the C++ variants, the smart pointers themselves allocate the memory for the objects they contain. 5 | That ensures the right allocator is used to dispose of the memory as well. 6 | 7 | Allocators are template arguments instead of using `theAllocator` so 8 | that these smart pointers can be used in `@nogc` code. However, they 9 | will default to `typeof(theAllocator)` for simplicity. 10 | 11 | Another reason to have to pass in the type of allocator is to decide how it is to 12 | be stored. Stateless allocators can be "stored" by value and imply zero-cost `Unique` pointers. 13 | Singleton allocators such as Mallocator (that have an `instance` attribute/member function) 14 | don't need to be passed in to the constructor. This is detected at compile-time as an example 15 | of design by instrospection. 16 | 17 | `RefCounted` leverages D's type system by doing atomic reference counting *iff* the type of the contained 18 | object is `shared`. Otherwise it's non-atomic. 19 | */ 20 | module automem; 21 | 22 | public import automem.unique; 23 | public import automem.ref_counted; 24 | public import automem.vector; 25 | public import automem.array; 26 | 27 | 28 | @safe unittest { 29 | 30 | import std.algorithm: move; 31 | 32 | static struct Point { 33 | int x; 34 | int y; 35 | } 36 | 37 | // set theAllocator as desired beforehand, e.g. 38 | // theAllocator = allocatorObject(Mallocator.instance) 39 | 40 | { 41 | // must pass arguments to initialise the contained object 42 | auto u1 = Unique!Point(2, 3); 43 | assert(*u1 == Point(2, 3)); 44 | assert(u1.y == 3); 45 | 46 | // auto u2 = u1; // won't compile, can only move 47 | typeof(u1) u2 = () @trusted { return u1.move; }(); 48 | assert(cast(bool)u1 == false); // u1 is now empty 49 | } 50 | // memory freed for the Point structure created in the block 51 | 52 | { 53 | auto s1 = RefCounted!Point(4, 5); 54 | assert(*s1 == Point(4, 5)); 55 | assert(s1.x == 4); 56 | { 57 | auto s2 = s1; // can be copied 58 | } // ref count goes to 1 here 59 | 60 | } // ref count goes to 0 here, memory released 61 | 62 | { 63 | import std.algorithm: map, equal; 64 | import std.range: iota; 65 | 66 | // `vector` is also known as `array` 67 | auto vec = vector(Point(1, 2), Point(3, 4), Point(5, 6)); 68 | assert(equal(vec.range, [Point(1, 2), Point(3, 4), Point(5, 6)])); 69 | 70 | // reallocations are @system since old pointers can dangle 71 | () @trusted { 72 | vec.length = 1; 73 | assert(equal(vec.range, [Point(1, 2)])); 74 | 75 | vec ~= Point(7, 8); 76 | assert(equal(vec.range, [Point(1, 2), Point(7, 8)])); 77 | 78 | vec ~= 2.iota.map!(i => Point(i + 10, i + 11)); 79 | assert(equal(vec.range, [Point(1, 2), Point(7, 8), Point(10, 11), Point(11, 12)])); 80 | }(); 81 | } // memory for the array released here 82 | } 83 | 84 | 85 | // @nogc test - must explicitly use the allocator for compile-time guarantees 86 | @safe @nogc unittest { 87 | import std.experimental.allocator.mallocator: Mallocator; 88 | 89 | static struct Point { 90 | int x; 91 | int y; 92 | } 93 | 94 | { 95 | // must pass arguments to initialise the contained object 96 | auto u1 = Unique!(Point, Mallocator)(2, 3); 97 | assert(*u1 == Point(2, 3)); 98 | assert(u1.y == 3); 99 | } 100 | // memory freed for the Point structure created in the block 101 | 102 | // similarly for the other types 103 | } 104 | -------------------------------------------------------------------------------- /source/automem/ref_counted.d: -------------------------------------------------------------------------------- 1 | /** 2 | A reference-counted smart pointer. 3 | */ 4 | module automem.ref_counted; 5 | 6 | import automem.traits: isAllocator; 7 | import automem.unique: Unique; 8 | import std.experimental.allocator: theAllocator, processAllocator; 9 | import std.typecons: Flag; 10 | 11 | 12 | alias RC = RefCounted; 13 | 14 | version (D_BetterC) 15 | enum gcExists = false; 16 | else 17 | enum gcExists = true; 18 | 19 | /** 20 | A reference-counted smart pointer similar to C++'s std::shared_ptr. 21 | */ 22 | struct RefCounted(RefCountedType, 23 | Allocator = typeof(theAllocator), 24 | Flag!"supportGC" supportGC = gcExists ? Flag!"supportGC".yes : Flag!"supportGC".no) 25 | if(isAllocator!Allocator) 26 | { 27 | 28 | import std.traits: hasMember; 29 | 30 | enum isSingleton = hasMember!(Allocator, "instance"); 31 | enum isTheAllocator = is(Allocator == typeof(theAllocator)); 32 | enum isGlobal = isSingleton || isTheAllocator; 33 | 34 | alias Type = RefCountedType; 35 | 36 | static if(isGlobal) 37 | /** 38 | The allocator is a singleton, so no need to pass it in to the 39 | constructor 40 | */ 41 | this(Args...)(auto ref Args args) { 42 | this.makeObject!args(); 43 | } 44 | else 45 | /** 46 | Non-singleton allocator, must be passed in 47 | */ 48 | this(Args...)(Allocator allocator, auto ref Args args) { 49 | _allocator = allocator; 50 | this.makeObject!args(); 51 | } 52 | 53 | static if(isGlobal) 54 | /** 55 | Factory method to enable construction of structs despite 56 | structs not being able to have a constructor with no arguments. 57 | */ 58 | static typeof(this) construct(Args...)(auto ref Args args) { 59 | static if (Args.length != 0) 60 | return typeof(return)(args); 61 | else { 62 | typeof(return) ret; 63 | ret.makeObject!()(); 64 | return ret; 65 | } 66 | } 67 | else 68 | /** 69 | Factory method. Not necessary with non-global allocator 70 | but included for symmetry. 71 | */ 72 | static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) { 73 | return typeof(return)(allocator, args); 74 | } 75 | 76 | /// 77 | this(this) { 78 | if(_impl !is null) inc; 79 | } 80 | 81 | /// 82 | ~this() { 83 | release; 84 | } 85 | 86 | /** 87 | Assign to an lvalue RefCounted 88 | */ 89 | void opAssign(ref RefCounted other) { 90 | 91 | if (_impl == other._impl) return; 92 | 93 | if(_impl !is null) release; 94 | 95 | static if(!isGlobal) 96 | _allocator = other._allocator; 97 | 98 | _impl = other._impl; 99 | 100 | if(_impl !is null) inc; 101 | } 102 | 103 | /** 104 | Assign to an rvalue RefCounted 105 | */ 106 | void opAssign(RefCounted other) { 107 | import std.algorithm: swap; 108 | swap(_impl, other._impl); 109 | static if(!isGlobal) 110 | swap(_allocator, other._allocator); 111 | } 112 | 113 | /** 114 | Dereference the smart pointer and yield a reference 115 | to the contained type. 116 | */ 117 | ref auto opUnary(string s)() inout if (s == "*") { 118 | return _impl._get; 119 | } 120 | 121 | /** 122 | Prevent opSlice and opIndex from being hidden by Impl*. 123 | This comment is deliberately not DDOC. 124 | */ 125 | auto ref opSlice(A...)(auto ref A args) 126 | if (__traits(compiles, Type.init.opSlice(args))) 127 | { 128 | return _impl._get.opSlice(args); 129 | } 130 | /// ditto 131 | auto ref opIndex(A...)(auto ref A args) 132 | if (__traits(compiles, Type.init.opIndex(args))) 133 | { 134 | return _impl._get.opIndex(args); 135 | } 136 | /// ditto 137 | auto ref opIndexAssign(A...)(auto ref A args) 138 | if (__traits(compiles, Type.init.opIndexAssign(args))) 139 | { 140 | return _impl._get.opIndexAssign(args); 141 | } 142 | 143 | alias _impl this; 144 | 145 | private: 146 | 147 | static struct Impl { 148 | 149 | static if(is(Type == shared)) 150 | shared size_t _count; 151 | else 152 | size_t _count; 153 | 154 | static if(is(Type == class)) { 155 | 156 | align ((void*).alignof) 157 | void[__traits(classInstanceSize, Type)] _rawMemory; 158 | 159 | } else 160 | Type _object; 161 | 162 | 163 | static if (is(Type == class)) { 164 | 165 | inout(Type) _get() inout 166 | in(&this !is null) 167 | do 168 | { 169 | return cast(inout(Type)) &_rawMemory[0]; 170 | } 171 | 172 | inout(shared(Type)) _get() inout shared 173 | in(&this !is null) 174 | do 175 | { 176 | return cast(inout(shared(Type))) &_rawMemory[0]; 177 | } 178 | } else { // struct 179 | 180 | ref inout(Type) _get() inout 181 | in(&this !is null) 182 | do 183 | { 184 | return _object; 185 | } 186 | 187 | ref inout(shared(Type)) _get() inout shared 188 | in(&this !is null) 189 | do 190 | { 191 | return _object; 192 | } 193 | } 194 | 195 | alias _get this; 196 | } 197 | 198 | static if(isSingleton) 199 | alias _allocator = Allocator.instance; 200 | else static if(isTheAllocator) { 201 | static if (is(Type == shared)) 202 | // 'processAllocator' should be used for allocating 203 | // memory shared across threads 204 | alias _allocator = processAllocator; 205 | else 206 | alias _allocator = theAllocator; 207 | } 208 | else 209 | Allocator _allocator; 210 | 211 | static if(is(Type == shared)) 212 | alias ImplType = shared Impl; 213 | else 214 | alias ImplType = Impl; 215 | 216 | public ImplType* _impl; // has to be public or alias this doesn't work 217 | 218 | void allocateImpl() { 219 | import std.traits: hasIndirections; 220 | 221 | _impl = cast(typeof(_impl)) _allocator.allocate(Impl.sizeof); 222 | _impl._count = 1; 223 | 224 | static if (is(Type == class)) { 225 | // class representation: 226 | // void* classInfoPtr 227 | // void* monitorPtr 228 | // [] interfaces 229 | // T... members 230 | import core.memory: GC; 231 | 232 | // TypeInfo_Shared has no 233 | static if(is(Type == shared)) { 234 | auto flags() { 235 | return (cast(TypeInfo_Class) typeid(Type).base).m_flags; 236 | } 237 | } else { 238 | auto flags() { 239 | return typeid(Type).m_flags; 240 | } 241 | } 242 | 243 | 244 | if (supportGC && !(flags & TypeInfo_Class.ClassFlags.noPointers)) 245 | // members have pointers: we have to watch the monitor 246 | // and all members; skip the classInfoPtr 247 | GC.addRange(cast(void*) &_impl._rawMemory[(void*).sizeof], 248 | __traits(classInstanceSize, Type) - (void*).sizeof); 249 | else 250 | // representation doesn't have pointers, just watch the 251 | // monitor pointer; skip the classInfoPtr 252 | // need to watch the monitor pointer even if supportGC is false. 253 | GC.addRange(cast(void*) &_impl._rawMemory[(void*).sizeof], (void*).sizeof); 254 | } else static if (supportGC && hasIndirections!Type) { 255 | import core.memory: GC; 256 | GC.addRange(cast(void*) &_impl._object, Type.sizeof); 257 | } 258 | } 259 | 260 | void release() { 261 | import std.traits : hasIndirections; 262 | import core.memory : GC; 263 | import automem.utils : destruct; 264 | 265 | if(_impl is null) return; 266 | assert(_impl._count > 0, "Trying to release a RefCounted but ref count is 0 or less"); 267 | 268 | dec; 269 | 270 | if(_impl._count == 0) { 271 | () @trusted { destruct(_impl._get); }(); 272 | static if (is(Type == class)) { 273 | // need to watch the monitor pointer even if supportGC is false. 274 | () @trusted { GC.removeRange(cast(void*) &_impl._rawMemory[(void*).sizeof]); }(); 275 | } else static if (supportGC && hasIndirections!Type) { 276 | () @trusted { GC.removeRange(cast(void*) &_impl._object); }(); 277 | } 278 | auto memSlice = () @trusted { return (cast(void*) _impl)[0 .. Impl.sizeof]; }(); 279 | () @trusted { _allocator.deallocate(memSlice); }(); 280 | } 281 | } 282 | 283 | void inc() { 284 | static if(is(Type == shared)) { 285 | import core.atomic: atomicOp; 286 | _impl._count.atomicOp!"+="(1); 287 | } else 288 | ++_impl._count; 289 | } 290 | 291 | void dec() { 292 | static if(is(Type == shared)) { 293 | import core.atomic: atomicOp; 294 | _impl._count.atomicOp!"-="(1); 295 | } else 296 | --_impl._count; 297 | } 298 | 299 | } 300 | 301 | private template makeObject(args...) 302 | { 303 | void makeObject(Type, A)(ref RefCounted!(Type, A) rc) @trusted { 304 | import std.conv: emplace; 305 | import std.functional : forward; 306 | import std.traits: Unqual; 307 | 308 | rc.allocateImpl; 309 | 310 | static if(is(Type == class)) 311 | emplace!Type(cast(void[]) rc._impl._rawMemory[], forward!args); 312 | else 313 | emplace(&rc._impl._object, forward!args); 314 | } 315 | } 316 | 317 | 318 | 319 | auto refCounted(Type, Allocator)(Unique!(Type, Allocator) ptr) { 320 | 321 | RefCounted!(Type, Allocator) ret; 322 | 323 | static if(!ptr.isGlobal) 324 | ret._allocator = ptr.allocator; 325 | 326 | ret.allocateImpl; 327 | *ret = *ptr; 328 | 329 | return ret; 330 | } 331 | -------------------------------------------------------------------------------- /source/automem/traits.d: -------------------------------------------------------------------------------- 1 | module automem.traits; 2 | 3 | 4 | void checkAllocator(T)() { 5 | import std.experimental.allocator: make, dispose; 6 | import std.traits: hasMember; 7 | 8 | static if(hasMember!(T, "instance")) 9 | alias allocator = T.instance; 10 | else 11 | T allocator; 12 | 13 | int* i = allocator.make!int; 14 | allocator.dispose(&i); 15 | void[] bytes = allocator.allocate(size_t.init); 16 | allocator.deallocate(bytes); 17 | } 18 | 19 | enum isAllocator(T) = is(typeof(checkAllocator!T)); 20 | 21 | 22 | @("isAllocator") 23 | @safe @nogc pure unittest { 24 | import std.experimental.allocator.mallocator: Mallocator; 25 | import test_allocator: TestAllocator; 26 | 27 | static assert( isAllocator!Mallocator); 28 | static assert( isAllocator!TestAllocator); 29 | static assert(!isAllocator!int); 30 | } 31 | 32 | 33 | template isGlobal(Allocator) { 34 | enum isGlobal = isSingleton!Allocator || isTheAllocator!Allocator; 35 | } 36 | 37 | template isSingleton(Allocator) { 38 | import std.traits: hasMember; 39 | enum isSingleton = hasMember!(Allocator, "instance"); 40 | } 41 | 42 | template isTheAllocator(Allocator) { 43 | import std.experimental.allocator: theAllocator; 44 | enum isTheAllocator = is(Allocator == typeof(theAllocator)); 45 | } 46 | 47 | /** 48 | Determines if a type is Unique. 49 | */ 50 | template isUnique(T) { 51 | import automem.unique: Unique; 52 | import std.traits: TemplateOf; 53 | enum isUnique = __traits(isSame, TemplateOf!T, Unique); 54 | } 55 | 56 | /// 57 | @("isUnique") 58 | @safe unittest { 59 | import automem.unique: Unique; 60 | 61 | static struct Point { 62 | int x; 63 | int y; 64 | } 65 | 66 | auto u = Unique!Point(2, 3); 67 | static assert(isUnique!(typeof(u))); 68 | 69 | auto p = Point(2, 3); 70 | static assert(!isUnique!(typeof(p))); 71 | } 72 | 73 | /** 74 | Determines if a type is RefCounted. 75 | */ 76 | template isRefCounted(T) { 77 | import automem.ref_counted: RefCounted; 78 | import std.traits: TemplateOf; 79 | enum isRefCounted = __traits(isSame, TemplateOf!T, RefCounted); 80 | } 81 | 82 | /// 83 | @("isRefCounted") 84 | @safe unittest { 85 | import automem.ref_counted: RefCounted; 86 | 87 | static struct Point { 88 | int x; 89 | int y; 90 | } 91 | 92 | auto s = RefCounted!Point(2, 3); 93 | static assert(isRefCounted!(typeof(s))); 94 | 95 | auto p = Point(2, 3); 96 | static assert(!isRefCounted!(typeof(p))); 97 | } 98 | 99 | 100 | /** 101 | The target of a `Unique` or `RefCounted` pointer. 102 | */ 103 | template PointerTarget(T) 104 | if (isUnique!T || isRefCounted!T) 105 | { 106 | alias PointerTarget = T.Type; 107 | } 108 | 109 | /// 110 | @("Get the target of a Unique or RefCounter pointer") 111 | @safe unittest { 112 | import automem.unique: Unique; 113 | import automem.ref_counted: RefCounted; 114 | 115 | static struct Point { 116 | int x; 117 | int y; 118 | } 119 | 120 | auto u = Unique!Point(2, 3); 121 | static assert(is(Point == PointerTarget!(typeof(u)))); 122 | 123 | auto s = RefCounted!Point(2, 3); 124 | static assert(is(Point == PointerTarget!(typeof(s)))); 125 | } 126 | 127 | /// 128 | @("Mixing Unique and RefCounted pointers") 129 | unittest { 130 | import std.math : isClose; 131 | import automem.unique: Unique; 132 | import automem.ref_counted: RefCounted; 133 | 134 | static struct Point { 135 | int x; 136 | int y; 137 | } 138 | 139 | static double distance(T, U)(auto ref T p1, auto ref U p2) 140 | if (is(PointerTarget!T == Point) && 141 | is(PointerTarget!U == Point)) 142 | { 143 | import std.conv : to; 144 | import std.math : sqrt, pow; 145 | return((pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)).to!double.sqrt); 146 | } 147 | 148 | int x1 = 2; 149 | int y1 = 3; 150 | int x2 = x1 + 3; 151 | int y2 = y1 + 4; 152 | 153 | auto u_p1 = Unique!Point(x1, y1); 154 | auto u_p2 = Unique!Point(x2, y2); 155 | assert(isClose(distance(u_p1, u_p2), 5.0)); 156 | 157 | auto rc_p1 = RefCounted!Point(x1, y1); 158 | auto rc_p2 = RefCounted!Point(x2, y2); 159 | assert(isClose(distance(rc_p1, rc_p2), 5.0)); 160 | 161 | assert(isClose(distance(u_p1, rc_p2), 5.0)); 162 | assert(isClose(distance(rc_p1, u_p2), 5.0)); 163 | } 164 | -------------------------------------------------------------------------------- /source/automem/unique.d: -------------------------------------------------------------------------------- 1 | /** 2 | A unique pointer. 3 | */ 4 | module automem.unique; 5 | 6 | import automem.traits: isAllocator; 7 | import std.experimental.allocator: theAllocator; 8 | import std.typecons: Flag; 9 | 10 | version(AutomemTesting) { 11 | import ut; 12 | mixin TestUtils; 13 | } 14 | 15 | version (D_BetterC) 16 | enum gcExists = false; 17 | else 18 | enum gcExists = true; 19 | 20 | /** 21 | A unique pointer similar to C++'s std::unique_ptr. 22 | */ 23 | struct Unique( 24 | UniqueType, 25 | Allocator = typeof(theAllocator()), 26 | Flag!"supportGC" supportGC = gcExists ? Flag!"supportGC".yes : Flag!"supportGC".no 27 | ) 28 | if(isAllocator!Allocator) 29 | { 30 | 31 | import std.traits: hasMember; 32 | import std.typecons: Proxy; 33 | 34 | enum isSingleton = hasMember!(Allocator, "instance"); 35 | enum isTheAllocator = is(Allocator == typeof(theAllocator)); 36 | enum isGlobal = isSingleton || isTheAllocator; 37 | 38 | alias Type = UniqueType; 39 | 40 | static if(is(Type == class) || is(Type == interface)) 41 | alias Pointer = Type; 42 | else 43 | alias Pointer = Type*; 44 | 45 | static if(isGlobal) { 46 | 47 | /** 48 | The allocator is global, so no need to pass it in to the constructor 49 | */ 50 | this(Args...)(auto ref Args args) { 51 | this.makeObject!(supportGC, args)(); 52 | } 53 | 54 | } else { 55 | 56 | /** 57 | Non-singleton allocator, must be passed in 58 | */ 59 | this(Args...)(Allocator allocator, auto ref Args args) { 60 | _allocator = allocator; 61 | this.makeObject!(supportGC, args)(); 62 | } 63 | } 64 | 65 | 66 | static if(isGlobal) 67 | /** 68 | Factory method so can construct with zero args. 69 | */ 70 | static typeof(this) construct(Args...)(auto ref Args args) { 71 | static if (Args.length != 0) 72 | return typeof(return)(args); 73 | else { 74 | typeof(return) ret; 75 | ret.makeObject!(supportGC)(); 76 | return ret; 77 | } 78 | } 79 | else 80 | /** 81 | Factory method. Not necessary with non-global allocator 82 | but included for symmetry. 83 | */ 84 | static typeof(this) construct(Args...)(auto ref Allocator allocator, auto ref Args args) { 85 | return typeof(return)(allocator, args); 86 | } 87 | 88 | /// 89 | this(T)(Unique!(T, Allocator) other) if(is(T: Type)) { 90 | moveFrom(other); 91 | } 92 | 93 | /// 94 | @disable this(this); 95 | 96 | /// 97 | ~this() { 98 | deleteObject; 99 | } 100 | 101 | /** 102 | Borrow the owned pointer. 103 | Can be @safe with DIP1000 and if used in a scope fashion. 104 | */ 105 | auto borrow() inout { 106 | return _object; 107 | } 108 | 109 | alias get = borrow; // backwards compatibility 110 | 111 | /** 112 | Releases ownership and transfers it to the returned 113 | Unique object. 114 | */ 115 | Unique unique() { 116 | import std.algorithm: move; 117 | Unique u; 118 | move(this, u); 119 | assert(_object is null); 120 | return u; 121 | } 122 | 123 | /// release ownership 124 | package Pointer release() { 125 | auto ret = _object; 126 | _object = null; 127 | return ret; 128 | } 129 | 130 | /// 131 | package Allocator allocator() { 132 | return _allocator; 133 | } 134 | 135 | /** 136 | "Truthiness" cast 137 | */ 138 | bool opCast(T)() const if(is(T == bool)) { 139 | return _object !is null; 140 | } 141 | 142 | /// Move from another smart pointer 143 | void opAssign(T)(Unique!(T, Allocator) other) if(is(T: Type)) { 144 | deleteObject; 145 | moveFrom(other); 146 | } 147 | 148 | mixin Proxy!_object; 149 | 150 | private: 151 | 152 | Pointer _object; 153 | 154 | static if(isSingleton) 155 | alias _allocator = Allocator.instance; 156 | else static if(isTheAllocator) 157 | alias _allocator = theAllocator; 158 | else 159 | Allocator _allocator; 160 | 161 | void deleteObject() @safe { 162 | import automem.allocator: dispose; 163 | import std.traits: isPointer; 164 | import std.traits : hasIndirections; 165 | import core.memory : GC; 166 | 167 | static if(isPointer!Allocator) 168 | assert(_object is null || _allocator !is null); 169 | 170 | if(_object !is null) () @trusted { _allocator.dispose(_object); }(); 171 | static if (is(Type == class)) { 172 | // need to watch the monitor pointer even if supportGC is false. 173 | () @trusted { 174 | auto repr = (cast(void*)_object)[0..__traits(classInstanceSize, Type)]; 175 | GC.removeRange(&repr[(void*).sizeof]); 176 | }(); 177 | } else static if (supportGC && hasIndirections!Type && !is(Type == interface)) { 178 | () @trusted { 179 | GC.removeRange(_object); 180 | }(); 181 | } 182 | } 183 | 184 | void moveFrom(T)(ref Unique!(T, Allocator) other) if(is(T: Type)) { 185 | _object = other._object; 186 | other._object = null; 187 | 188 | static if(!isGlobal) { 189 | import std.algorithm: move; 190 | _allocator = other._allocator.move; 191 | } 192 | } 193 | } 194 | 195 | 196 | /// 197 | @("Construct Unique using global allocator for struct with zero-args ctor") 198 | @system unittest { 199 | struct S { 200 | private ulong zeroArgsCtorTest = 3; 201 | } 202 | auto s = Unique!S.construct(); 203 | static assert(is(typeof(s) == Unique!S)); 204 | assert(s._object !is null); 205 | assert(s.zeroArgsCtorTest == 3); 206 | } 207 | 208 | 209 | /// 210 | @("release") 211 | @system unittest { 212 | import std.experimental.allocator: dispose; 213 | import core.exception: AssertError; 214 | 215 | try { 216 | auto allocator = TestAllocator(); 217 | auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 42); 218 | ptr.release; 219 | assert(Struct.numStructs == 1); 220 | } catch(AssertError e) { // TestAllocator should throw due to memory leak 221 | version(unitThreadedLight) {} 222 | else 223 | "Memory leak in TestAllocator".should.be in e.msg; 224 | return; 225 | } 226 | 227 | assert(0); // should throw above 228 | } 229 | 230 | 231 | private template makeObject(Flag!"supportGC" supportGC, args...) 232 | { 233 | void makeObject(Type,A)(ref Unique!(Type, A) u) { 234 | import std.experimental.allocator: make; 235 | import std.functional : forward; 236 | import std.traits : hasIndirections; 237 | import core.memory : GC; 238 | 239 | u._object = () @trusted { return u._allocator.make!Type(forward!args); }(); 240 | 241 | static if (is(Type == class) || is(Type == interface)) { 242 | () @trusted { 243 | auto repr = (cast(void*)u._object)[0..__traits(classInstanceSize, Type)]; 244 | if (supportGC && !(typeid(Type).m_flags & TypeInfo_Class.ClassFlags.noPointers)) { 245 | GC.addRange(&repr[(void*).sizeof], 246 | __traits(classInstanceSize, Type) - (void*).sizeof); 247 | } else { 248 | // need to watch the monitor pointer even if supportGC is false. 249 | GC.addRange(&repr[(void*).sizeof], (void*).sizeof); 250 | } 251 | }(); 252 | } else static if (supportGC && hasIndirections!Type) { 253 | () @trusted { 254 | GC.addRange(u._object, Type.sizeof); 255 | }(); 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /source/automem/utils.d: -------------------------------------------------------------------------------- 1 | module automem.utils; 2 | 3 | import std.traits : isStaticArray; 4 | 5 | // This is a destroy() copied and modified from 6 | // druntime, to allow for destruction attribute inference 7 | 8 | void destruct(T)(T obj) if (is(T == class)) { 9 | (cast(_finalizeType!T) &rt_finalize)(() @trusted { return cast(void*) obj; }()); 10 | } 11 | 12 | void destruct(T)(T obj) if (is(T == interface)) { 13 | destruct(cast(Object) obj); 14 | } 15 | 16 | void destruct(T)(ref T obj) if (is(T == struct)) { 17 | static if (__traits(hasMember, T, "__xdtor")) 18 | obj.__xdtor; 19 | } 20 | 21 | void destruct(T : U[n], U, size_t n)(ref T obj) if (!is(T == struct)) { 22 | foreach_reverse (ref e; obj[]) 23 | destruct(e); 24 | } 25 | 26 | void destruct(T)(ref T obj) 27 | if(!is(T == struct) && !is(T == class) && !is(T == interface) && !isStaticArray!T) { 28 | obj = T.init; 29 | } 30 | 31 | @("class dtor inference") 32 | @safe @nogc pure unittest { 33 | class A { ~this() @nogc {} } 34 | class B : A { ~this() {} } 35 | class C : B { ~this() @nogc {} } 36 | 37 | static assert( __traits(compiles, () @nogc { A a; destruct(a); })); 38 | static assert(!__traits(compiles, () @nogc { B a; destruct(b); })); 39 | static assert(!__traits(compiles, () @nogc { C a; destruct(c); })); 40 | } 41 | 42 | @("class dtor inference with struct members") 43 | @system @nogc pure unittest { 44 | import std.traits: functionAttributes, FunctionAttribute; 45 | import std.conv: text; 46 | 47 | struct A { ~this() @nogc {} } 48 | struct B { ~this() { new int; } } 49 | class CA { A a; ~this() @nogc {} } 50 | class CB { B b; ~this() @nogc {} } 51 | 52 | static assert( __traits(compiles, () @nogc { CA a; destruct(a); })); 53 | static assert(!__traits(compiles, () @system @nogc { CB b; destruct(b); })); 54 | } 55 | 56 | private: 57 | 58 | extern(C) void rt_finalize(void* p, bool det = true) @trusted @nogc pure; 59 | 60 | // A slightly better hack than the one presented by 61 | // https://www.auburnsounds.com/blog/2016-11-10_Running-D-without-its-runtime.html 62 | // 63 | // This template infers destruction attributes from the given 64 | // class hierarchy. It actually may be incorrect, as by 65 | // the current language rules derived class can still 66 | // have weaker set of destruction attributes. 67 | extern(C) 68 | template _finalizeType(T) { 69 | import std.traits: Unqual; 70 | static if (is(Unqual!T == Object)) { 71 | alias _finalizeType = typeof(&rt_finalize); 72 | } else { 73 | import std.traits : BaseClassesTuple; 74 | import std.meta : AliasSeq; 75 | alias _finalizeType = typeof(function void(void* p, bool det = true) { 76 | // generate a body that calls all the destructors in the chain, 77 | // compiler should infer the intersection of attributes 78 | foreach (B; AliasSeq!(T, BaseClassesTuple!T)) { 79 | // __dtor, i.e. B.~this 80 | static if (__traits(hasMember, B, "__dtor")) 81 | () { B obj; obj.__dtor; } (); 82 | // __xdtor, i.e. dtors for all RAII members 83 | static if (__traits(hasMember, B, "__xdtor")) 84 | () { B obj; obj.__xdtor; } (); 85 | } 86 | }); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /source/automem/vector.d: -------------------------------------------------------------------------------- 1 | /** 2 | Dynamic arrays with deterministic memory usage 3 | akin to C++'s std::vector or Rust's std::vec::Vec 4 | */ 5 | module automem.vector; 6 | 7 | 8 | import automem.traits: isGlobal; 9 | import std.range.primitives: isInputRange; 10 | import std.experimental.allocator: theAllocator; 11 | import std.experimental.allocator.mallocator: Mallocator; 12 | 13 | 14 | alias String = StringA!(typeof(theAllocator)); 15 | alias StringM = StringA!Mallocator; 16 | 17 | 18 | template StringA(A = typeof(theAllocator)) if(isAllocator!A) { 19 | alias StringA = Vector!(immutable char, A); 20 | } 21 | 22 | /** 23 | Create a vector from a variadic list of elements, inferring the type of 24 | the elements and the allocator 25 | */ 26 | auto vector(A = typeof(theAllocator), E) 27 | (E[] elements...) 28 | if(isAllocator!A && isGlobal!A) 29 | { 30 | return Vector!(E, A)(elements); 31 | } 32 | 33 | /// ditto 34 | auto vector(A = typeof(theAllocator), E) 35 | (A allocator, E[] elements...) 36 | if(isAllocator!A && !isGlobal!A) 37 | { 38 | return Vector!(E, A)(allocator, elements); 39 | } 40 | 41 | /** 42 | Create a vector from an input range, inferring the type of the elements 43 | and the allocator. 44 | */ 45 | auto vector(A = typeof(theAllocator), R) 46 | (R range) 47 | if(isAllocator!A && isGlobal!A && isInputRange!R) 48 | { 49 | import automem.vector: ElementType; 50 | return Vector!(ElementType!R, A)(range); 51 | } 52 | 53 | 54 | /// ditto 55 | auto vector(A = typeof(theAllocator), R) 56 | (A allocator, R range) 57 | if(isAllocator!A && !isGlobal!A && isInputRange!R) 58 | { 59 | import automem.vector: ElementType; 60 | return Vector!(ElementType!R, A)(allocator, range); 61 | } 62 | 63 | /** 64 | A dynamic array with deterministic memory usage 65 | akin to C++'s std::vector or Rust's std::vec::Vec 66 | */ 67 | struct Vector(E, Allocator = typeof(theAllocator)) if(isAllocator!Allocator) { 68 | 69 | import automem.traits: isGlobal, isSingleton, isTheAllocator; 70 | import std.traits: Unqual, isCopyable; 71 | 72 | alias MutE = Unqual!E; 73 | enum isElementMutable = !is(E == immutable) && !is(E == const); 74 | 75 | static if(isGlobal!Allocator) { 76 | 77 | this(E[] elements...) { 78 | fromElements(elements); 79 | } 80 | 81 | this(R)(R range) if(isInputRangeOf!(R, E)) { 82 | // reallocating is ok if only allocating now 83 | () @trusted { this = range; }(); 84 | } 85 | 86 | } else static if(isCopyable!Allocator) { 87 | 88 | this(Allocator allocator, E[] elements...) { 89 | _allocator = allocator; 90 | fromElements(elements); 91 | } 92 | 93 | this(R)(Allocator allocator, R range) if(isInputRangeOf!(R, E)) { 94 | _allocator = allocator; 95 | this = range; 96 | } 97 | } else { 98 | 99 | this(R)(R range) if(isInputRangeOf!(R, E)) { 100 | this = range; 101 | } 102 | 103 | } 104 | 105 | this(this) scope { 106 | 107 | auto oldElements = _elements; 108 | _elements = createVector(_elements.length); 109 | 110 | static if(isElementMutable) 111 | _elements[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT]; 112 | else 113 | () @trusted { 114 | cast(MutE[])(_elements)[0 .. length.toSizeT] = oldElements[0 .. length.toSizeT]; 115 | }(); 116 | } 117 | 118 | ~this() { 119 | free; 120 | } 121 | 122 | /// Frees the memory and returns to .init 123 | void free() scope { 124 | import std.experimental.allocator: dispose; 125 | 126 | // dispose is @system for theAllocator 127 | () @trusted { 128 | static if(is(E == immutable)) 129 | auto elements = cast(MutE[]) _elements; 130 | else 131 | alias elements = _elements; 132 | 133 | _allocator.dispose(elements); 134 | }(); 135 | 136 | clear; 137 | } 138 | 139 | static if(isElementMutable) { 140 | /// Pops the front element off 141 | void popFront() { 142 | foreach(i; 0 .. length - 1) 143 | _elements[i.toSizeT] = _elements[i.toSizeT + 1]; 144 | 145 | popBack; 146 | } 147 | } 148 | 149 | /// Pops the last element off 150 | void popBack() { 151 | --_length; 152 | } 153 | 154 | /// If the vector is empty 155 | bool empty() const { 156 | return length == 0; 157 | } 158 | 159 | /// The current length of the vector 160 | @property long length() const { 161 | return _length; 162 | } 163 | 164 | /// Set the length of the vector 165 | @property void length(long newLength) { 166 | if(capacity < newLength) reserve(newLength); 167 | _length = newLength; 168 | } 169 | 170 | /// The current memory capacity of the vector 171 | long capacity() const { 172 | return _elements.length; 173 | } 174 | 175 | /// Clears the vector, resulting in an empty one 176 | void clear() { 177 | _length = 0; 178 | } 179 | 180 | /// Reserve memory to avoid allocations when appending 181 | void reserve(long newLength) { 182 | expandMemory(newLength); 183 | } 184 | 185 | static if(isElementMutable) { 186 | 187 | /// Shrink to fit the current length. Returns if shrunk. 188 | bool shrink() scope { 189 | return shrink(length); 190 | } 191 | 192 | /** 193 | Shrink to fit the new length given. Returns if shrunk. 194 | Cannot be made @safe due to reallocation causing pointers 195 | to dangle. 196 | */ 197 | bool shrink(long newLength) scope { 198 | import std.experimental.allocator: shrinkArray; 199 | 200 | const delta = capacity - newLength; 201 | const shrunk = _allocator.shrinkArray(_elements, delta.toSizeT); 202 | _length = newLength; 203 | 204 | return shrunk; 205 | } 206 | } 207 | 208 | /// Access the ith element. Can throw RangeError. 209 | ref inout(E) opIndex(long i) return scope inout { 210 | if(i < 0 || i >= length) 211 | mixin(throwBoundsException); 212 | return _elements[i.toSizeT]; 213 | } 214 | 215 | /// Returns a new vector after appending to the given vector. 216 | Vector opBinary(string s, T)(auto ref T other) const 217 | if(s == "~" && is(Unqual!T == Vector)) 218 | { 219 | import std.range: chain; 220 | // opSlice is @system , but it's ok here because we're not 221 | // returning the slice but concatenating. 222 | return Vector(chain(() @trusted { return this[]; }(), 223 | () @trusted { return other[]; }())); 224 | } 225 | 226 | /// Assigns from a range. 227 | void opAssign(R)(R range) scope if(isForwardRangeOf!(R, E)) { 228 | import std.range.primitives: walkLength, save; 229 | 230 | expand(range.save.walkLength); 231 | 232 | long i = 0; 233 | foreach(element; range) 234 | _elements[toSizeT(i++)] = element; 235 | } 236 | 237 | // make it an output range 238 | 239 | 240 | /// Append to the vector 241 | void opOpAssign(string op) 242 | (E other) 243 | scope 244 | if(op == "~") 245 | { 246 | put(other); 247 | } 248 | 249 | void put(E other) { 250 | 251 | expand(length + 1); 252 | 253 | const lastIndex = (length - 1).toSizeT; 254 | 255 | static if(isElementMutable) 256 | _elements[lastIndex] = other; 257 | else { 258 | assert(_elements[lastIndex] == E.init, 259 | "Assigning to non default initialised non mutable member"); 260 | 261 | () @trusted { mutableElements[lastIndex] = other; }(); 262 | } 263 | } 264 | 265 | /// Append to the vector from a range 266 | void opOpAssign(string op, R) 267 | (scope R range) 268 | scope 269 | if(op == "~" && isForwardRangeOf!(R, E)) 270 | { 271 | put(range); 272 | } 273 | 274 | void put(R)(scope R range) if(isLengthRangeOf!(R, E)) { 275 | import std.range.primitives: walkLength, save; 276 | 277 | long index = length; 278 | 279 | static if(hasLength!R) 280 | const rangeLength = range.length; 281 | else 282 | const rangeLength = range.save.walkLength; 283 | 284 | expand(length + rangeLength); 285 | 286 | foreach(element; range) { 287 | const safeIndex = toSizeT(index++); 288 | static if(!isElementMutable) { 289 | assert(_elements[safeIndex] == E.init, 290 | "Assigning to non default initialised non mutable member"); 291 | } 292 | 293 | static if(isElementMutable) 294 | _elements[safeIndex] = element; 295 | else { 296 | assert(_elements[safeIndex] == E.init, 297 | "Assigning to non default initialised non mutable member"); 298 | () @trusted { mutableElements[safeIndex] = element; }(); 299 | } 300 | } 301 | } 302 | 303 | /** 304 | Return a forward range of the vector contents. 305 | Negative `end` values work like in Python. 306 | */ 307 | auto range(this This)(in long start = 0, long end = -1) return scope 308 | in(start >= 0) 309 | in(end <= length) 310 | do 311 | { 312 | import std.range.primitives: isForwardRange; 313 | 314 | static struct Range { 315 | private This* self; 316 | private long index; 317 | private long end; 318 | 319 | Range save() { 320 | return this; 321 | } 322 | 323 | auto front() { 324 | return (*self)[index]; 325 | } 326 | 327 | void popFront() { 328 | ++index; 329 | } 330 | 331 | bool empty() const { 332 | const comp = end < 0 ? length + end + 1 : end; 333 | return index >= comp; 334 | } 335 | 336 | auto length() const { 337 | return self.length; 338 | } 339 | } 340 | 341 | static assert(isForwardRange!Range); 342 | 343 | // FIXME - why isn't &this @safe? 344 | return Range(() @trusted { return &this; }(), 345 | start, 346 | end); 347 | } 348 | 349 | /** 350 | Returns a slice. 351 | @system because the pointer in the slice might dangle. 352 | */ 353 | auto opSlice(this This)() @system scope return { 354 | return _elements[0 .. length.toSizeT]; 355 | } 356 | 357 | /** 358 | Returns a slice. 359 | @system because the pointer in the slice might dangle. 360 | */ 361 | auto opSlice(this This)(long start, long end) @system scope return { 362 | if(start < 0 || start >= length) 363 | mixin(throwBoundsException); 364 | 365 | if(end < 0 || end > length) 366 | mixin(throwBoundsException); 367 | 368 | return _elements[start.toSizeT .. end.toSizeT]; 369 | } 370 | 371 | long opDollar() const { 372 | return length; 373 | } 374 | 375 | static if(isElementMutable) { 376 | /// Assign all elements to the given value 377 | void opSliceAssign(E value) { 378 | _elements[] = value; 379 | } 380 | } 381 | 382 | 383 | static if(isElementMutable) { 384 | /// Assign all elements in the given range to the given value 385 | void opSliceAssign(E value, long start, long end) { 386 | if(start < 0 || start >= length) 387 | mixin(throwBoundsException); 388 | 389 | if(end < 0 || end >= length) 390 | mixin(throwBoundsException); 391 | 392 | _elements[start.toSizeT .. end.toSizeT] = value; 393 | } 394 | } 395 | 396 | static if(isElementMutable) { 397 | /// Assign all elements using the given operation and the given value 398 | void opSliceOpAssign(string op)(E value) scope { 399 | foreach(ref elt; _elements) 400 | mixin(`elt ` ~ op ~ `= value;`); 401 | } 402 | } 403 | 404 | static if(isElementMutable) { 405 | /// Assign all elements in the given range using the given operation and the given value 406 | void opSliceOpAssign(string op)(E value, long start, long end) scope { 407 | if(start < 0 || start >= length) 408 | mixin(throwBoundsException); 409 | 410 | if(end < 0 || end >= length) 411 | mixin(throwBoundsException); 412 | 413 | foreach(ref elt; _elements[start.toSizeT .. end.toSizeT]) 414 | mixin(`elt ` ~ op ~ `= value;`); 415 | } 416 | } 417 | 418 | bool opCast(U)() const scope if(is(U == bool)) { 419 | return length > 0; 420 | } 421 | 422 | static if(is(Unqual!E == char)) { 423 | 424 | /** 425 | Return a null-terminated C string 426 | @system since appending is not @safe. 427 | */ 428 | auto stringz(this This)() return scope @system { 429 | if(capacity == length) reserve(length + 1); 430 | 431 | static if(isElementMutable) { 432 | _elements[length.toSizeT] = 0; 433 | } else { 434 | assert(_elements[length.toSizeT] == E.init || _elements[length.toSizeT] == 0, 435 | "Assigning to non default initialised non mutable member"); 436 | 437 | () @trusted { mutableElements[length.toSizeT] = 0; }(); 438 | } 439 | 440 | return &_elements[0]; 441 | } 442 | } 443 | 444 | auto ptr(this This)() return scope { 445 | return &_elements[0]; 446 | } 447 | 448 | bool opEquals(R)(R range) scope const 449 | if(isInputRangeOf!(R, E)) 450 | { 451 | import std.array: empty, popFront, front; 452 | 453 | if(length == 0 && range.empty) return true; 454 | 455 | foreach(i; 0 .. length) { 456 | if(range.empty) return false; 457 | if(range.front != this[i]) return false; 458 | range.popFront; 459 | } 460 | 461 | return range.empty; 462 | } 463 | 464 | bool opEquals(OtherAllocator)(auto ref scope const(Vector!(E, OtherAllocator)) other) const { 465 | return this == other.range; 466 | } 467 | 468 | private: 469 | 470 | E[] _elements; 471 | long _length; 472 | 473 | static if(isSingleton!Allocator) 474 | alias _allocator = Allocator.instance; 475 | else static if(isTheAllocator!Allocator) 476 | alias _allocator = theAllocator; 477 | else 478 | Allocator _allocator; 479 | 480 | E[] createVector(long length) scope { 481 | import std.experimental.allocator: makeArray; 482 | // theAllocator.makeArray is @system 483 | return () @trusted { return _allocator.makeArray!E(length.toSizeT); }(); 484 | } 485 | 486 | void fromElements(E[] elements) { 487 | 488 | _elements = createVector(elements.length); 489 | 490 | static if(isElementMutable) 491 | _elements[] = elements[]; 492 | else 493 | () @trusted { (cast(MutE[]) _elements)[] = elements[]; }(); 494 | 495 | _length = elements.length; 496 | } 497 | 498 | void expand(long newLength) scope { 499 | expandMemory(newLength); 500 | _length = newLength; 501 | } 502 | 503 | 504 | // @system since reallocating can cause pointers to dangle 505 | void expandMemory(long newLength) scope @system { 506 | import std.experimental.allocator: expandArray; 507 | 508 | if(newLength > capacity) { 509 | if(length == 0) 510 | _elements = createVector(newLength); 511 | else { 512 | const newCapacity = (newLength * 3) / 2; 513 | const delta = newCapacity - capacity; 514 | _allocator.expandArray(mutableElements, delta.toSizeT); 515 | } 516 | } 517 | } 518 | 519 | ref MutE[] mutableElements() scope return @system { 520 | auto ptr = &_elements; 521 | return *(cast(MutE[]*) ptr); 522 | } 523 | } 524 | 525 | 526 | static if (__VERSION__ >= 2082) { // version identifier D_Exceptions was added in 2.082 527 | version (D_Exceptions) 528 | private enum haveExceptions = true; 529 | else 530 | private enum haveExceptions = false; 531 | } else { 532 | version (D_BetterC) 533 | private enum haveExceptions = false; 534 | else 535 | private enum haveExceptions = true; 536 | } 537 | 538 | 539 | static if (haveExceptions) { 540 | private static BoundsException boundsException; 541 | private enum throwBoundsException = q{throw boundsException;}; 542 | class BoundsException: Exception { 543 | import std.exception: basicExceptionCtors; 544 | 545 | mixin basicExceptionCtors; 546 | } 547 | 548 | static this() 549 | { 550 | boundsException = new BoundsException("Out of bounds index"); 551 | } 552 | } else { 553 | private enum throwBoundsException = q{assert(0, "Out of bounds index");}; 554 | } 555 | 556 | 557 | private template isInputRangeOf(R, E) { 558 | import std.range.primitives: isInputRange; 559 | enum isInputRangeOf = isInputRange!R && canAssignFrom!(R, E); 560 | } 561 | 562 | private template isForwardRangeOf(R, E) { 563 | import std.range.primitives: isForwardRange; 564 | enum isForwardRangeOf = isForwardRange!R && canAssignFrom!(R, E); 565 | } 566 | 567 | 568 | private enum hasLength(R) = is(typeof({ 569 | import std.traits: isIntegral; 570 | auto length = R.init.length; 571 | static assert(isIntegral!(typeof(length))); 572 | })); 573 | 574 | 575 | private enum isLengthRangeOf(R, E) = isForwardRangeOf!(R, E) || hasLength!R; 576 | 577 | private template canAssignFrom(R, E) { 578 | enum canAssignFrom = is(typeof({ 579 | import automem.vector: frontNoAutoDecode; 580 | E element = R.init.frontNoAutoDecode; 581 | })); 582 | } 583 | 584 | private size_t toSizeT(long length) @safe @nogc pure nothrow { 585 | static if(size_t.sizeof < long.sizeof) 586 | assert(length < cast(long) size_t.max); 587 | return cast(size_t) length; 588 | } 589 | 590 | // Because autodecoding is fun 591 | private template ElementType(R) { 592 | import std.traits: isSomeString; 593 | 594 | static if(isSomeString!R) { 595 | alias ElementType = typeof(R.init[0]); 596 | } else { 597 | import std.range.primitives: ElementType_ = ElementType; 598 | alias ElementType = ElementType_!R; 599 | } 600 | } 601 | 602 | @("ElementType") 603 | @safe pure unittest { 604 | import automem.vector: ElementType; 605 | static assert(is(ElementType!(int[]) == int)); 606 | static assert(is(ElementType!(char[]) == char)); 607 | static assert(is(ElementType!(wchar[]) == wchar)); 608 | static assert(is(ElementType!(dchar[]) == dchar)); 609 | } 610 | 611 | 612 | // More fun with autodecoding 613 | private auto frontNoAutoDecode(R)(R range) { 614 | import std.traits: isSomeString; 615 | 616 | static if(isSomeString!R) 617 | return range[0]; 618 | else { 619 | import std.range.primitives: front; 620 | return range.front; 621 | } 622 | } 623 | 624 | 625 | void checkAllocator(T)() { 626 | import std.experimental.allocator: dispose, shrinkArray, makeArray, expandArray; 627 | import std.traits: hasMember; 628 | 629 | static if(hasMember!(T, "instance")) 630 | alias allocator = T.instance; 631 | else 632 | T allocator; 633 | 634 | void[] bytes; 635 | allocator.dispose(bytes); 636 | 637 | int[] ints = allocator.makeArray!int(42); 638 | 639 | allocator.shrinkArray(ints, size_t.init); 640 | allocator.expandArray(ints, size_t.init); 641 | } 642 | enum isAllocator(T) = is(typeof(checkAllocator!T)); 643 | 644 | 645 | @("isAllocator") 646 | @safe @nogc pure unittest { 647 | import std.experimental.allocator.mallocator: Mallocator; 648 | import test_allocator: TestAllocator; 649 | 650 | static assert( isAllocator!Mallocator); 651 | static assert( isAllocator!TestAllocator); 652 | static assert(!isAllocator!int); 653 | static assert( isAllocator!(typeof(theAllocator))); 654 | } 655 | -------------------------------------------------------------------------------- /tests/ut/issues.d: -------------------------------------------------------------------------------- 1 | module ut.issues; 2 | 3 | 4 | import ut; 5 | import automem; 6 | 7 | 8 | private typeof(vector(1).range()) gVectorIntRange; 9 | 10 | version(AutomemAsan) {} 11 | else { 12 | 13 | @ShouldFail("https://issues.dlang.org/show_bug.cgi?id=19752") 14 | @("26") 15 | @safe unittest { 16 | static void escape() { 17 | auto vec = vector(1, 2, 3); 18 | gVectorIntRange = vec.range; 19 | } 20 | 21 | static void stackSmash() { 22 | long[4096] arr = 42; 23 | } 24 | 25 | escape; 26 | gVectorIntRange.length.should == 0; 27 | stackSmash; 28 | gVectorIntRange.length.should == 0; 29 | } 30 | } 31 | 32 | 33 | @("27") 34 | @safe unittest { 35 | const str = String("foobar"); 36 | 37 | (str == "foobar").should == true; 38 | (str == "barfoo").should == false; 39 | (str == "quux").should == false; 40 | 41 | (str == String("foobar")).should == true; 42 | (str == String("barfoo")).should == false; 43 | (str == String("quux")).should == false; 44 | } 45 | 46 | 47 | @("37") 48 | @safe unittest { 49 | 50 | static struct S { 51 | int front; 52 | void popFront() { ++front; } 53 | @property bool empty() { return front >= 10; } 54 | } 55 | 56 | auto rc = RefCounted!S(0); 57 | foreach(i; *rc) {} 58 | 59 | auto un = Unique!S(0); 60 | foreach(i; *un) {} 61 | } 62 | 63 | 64 | @("38") 65 | @safe unittest { 66 | 67 | import core.exception: AssertError; 68 | 69 | static struct S { 70 | int front = 0; 71 | } 72 | 73 | auto rc = RefCounted!S(); 74 | rc.front.shouldThrow!AssertError; 75 | } 76 | 77 | 78 | version(unitThreadedLight) {} 79 | else { 80 | @HiddenTest 81 | @("51") 82 | @system unittest { 83 | 84 | import std.experimental.allocator: allocatorObject; 85 | import std.experimental.allocator.mallocator: Mallocator; 86 | 87 | static interface Interface { 88 | void hello(); 89 | string test(); 90 | } 91 | 92 | static class Oops: Interface { 93 | private ubyte[] _buffer; 94 | override void hello() {} 95 | override string test() { return "foo"; } 96 | this() { _buffer.length = 1024 * 1024; } 97 | ~this() {} 98 | 99 | static Oops opCall() { 100 | import std.experimental.allocator: theAllocator, make; 101 | return theAllocator.make!Oops; 102 | } 103 | } 104 | 105 | Vector!Interface interfaces; 106 | foreach(i; 0 .. 12) interfaces ~= Oops(); 107 | } 108 | } 109 | 110 | 111 | int created, destroyed; 112 | 113 | struct Issue156 { 114 | 115 | @disable this(); 116 | @disable this(this); 117 | 118 | this(int val) { 119 | writelnUt(" Issue156(", val, ")"); 120 | this.val = val; 121 | created++; 122 | } 123 | ~this() { 124 | writelnUt("~Issue156(", val, ")"); 125 | destroyed++; 126 | } 127 | int val; 128 | } 129 | 130 | 131 | version(AutomemAsan) {} 132 | else { 133 | @("56") 134 | @ShouldFail 135 | @system unittest { 136 | 137 | import std.range; 138 | 139 | { 140 | RefCounted!Issue156 s = RefCounted!Issue156(1); 141 | writelnUt("Creating r1"); 142 | auto r1 = repeat(s, 2); 143 | writelnUt("Creating r2"); 144 | auto r2 = repeat(s, 2); 145 | writelnUt("iterating"); 146 | foreach(s1, s2; lockstep(r1, r2)) 147 | s1.val.should == s2.val; 148 | } 149 | writelnUt("after lockstep: created ", created, " vs destroyed ", destroyed); 150 | created.should == 1; 151 | destroyed.should == 1; 152 | { 153 | RefCounted!Issue156 s = RefCounted!Issue156(2); 154 | writelnUt("Creating r1"); 155 | auto r1 = repeat(s, 2); 156 | writelnUt("Creating r2"); 157 | auto r2 = repeat(s, 2); 158 | writelnUt("iterating"); 159 | foreach(s1, s2; zip(r1, r2)) 160 | s1.val.should == s2.val; 161 | } 162 | writelnUt("after zip: created ", created, " vs destroyed ", destroyed); 163 | created.should == 2; 164 | destroyed.should == 2; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/ut/package.d: -------------------------------------------------------------------------------- 1 | module ut; 2 | 3 | public import unit_threaded; 4 | public import unit_threaded.should: should; // FIXME 5 | public import test_allocator: TestAllocator; 6 | 7 | mixin template TestUtils() { 8 | import unit_threaded; 9 | import test_allocator; 10 | 11 | /** 12 | Returns an object that, while in scope, replaces whatever 13 | theAllocator was with TestAllocator. 14 | */ 15 | auto theTestAllocator() { 16 | static struct Context { 17 | import std.experimental.allocator: theAllocator; 18 | 19 | TestAllocator testAllocator; 20 | typeof(theAllocator) oldAllocator; 21 | 22 | static auto create() { 23 | import std.experimental.allocator: allocatorObject; 24 | 25 | Context ctx; 26 | 27 | ctx.oldAllocator = theAllocator; 28 | theAllocator = allocatorObject(ctx.testAllocator); 29 | 30 | return ctx; 31 | } 32 | 33 | ~this() { 34 | import std.experimental.allocator: dispose; 35 | 36 | // 2.079.0 changed the API - we check here 37 | static if(__traits(compiles, testAllocator.dispose(theAllocator))) 38 | testAllocator.dispose(theAllocator); 39 | 40 | theAllocator = oldAllocator; 41 | } 42 | } 43 | 44 | return Context.create; 45 | } 46 | 47 | @Setup 48 | void before() { 49 | } 50 | 51 | @Shutdown 52 | void after() { 53 | reset; 54 | } 55 | 56 | void reset() { 57 | Struct.numStructs = 0; 58 | Class.numClasses = 0; 59 | SharedStruct.numStructs = 0; 60 | NoGcStruct.numStructs = 0; 61 | } 62 | 63 | 64 | void _writelnUt(T...)(T args) { 65 | try { 66 | () @trusted { writelnUt(args); }(); 67 | } catch(Exception ex) { 68 | assert(false); 69 | } 70 | } 71 | 72 | private struct Struct { 73 | int i; 74 | static int numStructs = 0; 75 | 76 | this(int i) @safe nothrow { 77 | this.i = i; 78 | 79 | ++numStructs; 80 | _writelnUt("Struct ", &this, " normal ctor, i=", i, ", N=", numStructs); 81 | } 82 | 83 | this(this) @safe nothrow { 84 | ++numStructs; 85 | _writelnUt("Struct ", &this, " postBlit ctor, i=", i, ", N=", numStructs); 86 | } 87 | 88 | ~this() @safe nothrow const { 89 | --numStructs; 90 | _writelnUt("Struct ", &this, " dtor, i=", i, ", N=", numStructs); 91 | } 92 | 93 | int twice() @safe pure const nothrow { 94 | return i * 2; 95 | } 96 | } 97 | 98 | private struct SharedStruct { 99 | int i; 100 | static int numStructs = 0; 101 | 102 | this(int i) @safe nothrow shared { 103 | this.i = i; 104 | 105 | ++numStructs; 106 | try () @trusted { 107 | _writelnUt("Struct normal ctor ", &this, ", i=", i, ", N=", numStructs); 108 | }(); 109 | catch(Exception ex) {} 110 | } 111 | 112 | this(this) @safe nothrow shared { 113 | ++numStructs; 114 | try () @trusted { 115 | _writelnUt("Struct postBlit ctor ", &this, ", i=", i, ", N=", numStructs); 116 | }(); 117 | catch(Exception ex) {} 118 | } 119 | 120 | ~this() @safe nothrow { 121 | --numStructs; 122 | try () @trusted { _writelnUt("Struct dtor ", &this, ", i=", i, ", N=", numStructs); }(); 123 | catch(Exception ex) {} 124 | } 125 | 126 | int twice() @safe pure const nothrow shared { 127 | return i * 2; 128 | } 129 | } 130 | 131 | private class Class { 132 | int i; 133 | static int numClasses = 0; 134 | 135 | this(int i) @safe nothrow { 136 | this.i = i; 137 | ++numClasses; 138 | } 139 | 140 | ~this() @safe nothrow { 141 | --numClasses; 142 | } 143 | 144 | int twice() @safe pure const nothrow { 145 | return i * 2; 146 | } 147 | } 148 | 149 | private class SharedClass { 150 | int i; 151 | static int numClasss = 0; 152 | 153 | this(int i) @safe nothrow shared { 154 | this.i = i; 155 | 156 | ++numClasss; 157 | try () @trusted { 158 | _writelnUt("SharedClass normal ctor ", this, ", i=", i, ", N=", numClasss); 159 | }(); 160 | catch(Exception ex) {} 161 | } 162 | 163 | ~this() @safe nothrow { 164 | --numClasss; 165 | try () @trusted { _writelnUt("SharedClass dtor ", this, ", i=", i, ", N=", numClasss); }(); 166 | catch(Exception ex) {} 167 | } 168 | 169 | int twice() @safe pure const nothrow shared { 170 | return i * 2; 171 | } 172 | } 173 | 174 | 175 | private struct SafeAllocator { 176 | 177 | import std.experimental.allocator.mallocator: Mallocator; 178 | 179 | void[] allocate(this T)(size_t i) @trusted nothrow @nogc { 180 | return Mallocator.instance.allocate(i); 181 | } 182 | 183 | void deallocate(this T)(void[] bytes) @trusted nothrow @nogc { 184 | Mallocator.instance.deallocate(bytes); 185 | } 186 | } 187 | 188 | private struct NoGcStruct { 189 | int i; 190 | 191 | static int numStructs = 0; 192 | 193 | this(int i) @safe @nogc nothrow { 194 | this.i = i; 195 | 196 | ++numStructs; 197 | } 198 | 199 | this(this) @safe @nogc nothrow { 200 | ++numStructs; 201 | } 202 | 203 | ~this() @safe @nogc nothrow { 204 | --numStructs; 205 | } 206 | 207 | } 208 | 209 | private class NoGcClass { 210 | int i; 211 | static int numClasses = 0; 212 | 213 | this(int i) @safe @nogc nothrow { 214 | this.i = i; 215 | ++numClasses; 216 | } 217 | 218 | ~this() @safe @nogc nothrow { 219 | --numClasses; 220 | } 221 | } 222 | 223 | private struct SharedStructWithIndirection { 224 | string s; 225 | this(string s) shared { 226 | this.s = s; 227 | } 228 | } 229 | 230 | private interface NoGcInterface { 231 | int getNumber() @nogc; 232 | } 233 | 234 | private class NoGcClassImplementingNoGcInterface : NoGcInterface{ 235 | 236 | int i; 237 | static int numClasses = 0; 238 | 239 | this(int i) @safe @nogc nothrow { 240 | this.i = i; 241 | ++numClasses; 242 | } 243 | 244 | ~this() @safe @nogc nothrow { 245 | --numClasses; 246 | } 247 | 248 | int getNumber() @nogc { 249 | return i; 250 | } 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /tests/ut/ref_counted.d: -------------------------------------------------------------------------------- 1 | module ut.ref_counted; 2 | 3 | import ut; 4 | import automem.ref_counted; 5 | 6 | mixin TestUtils; 7 | 8 | /// 9 | @("struct test allocator no copies") 10 | @system unittest { 11 | auto allocator = TestAllocator(); 12 | Struct.numStructs.should == 0; 13 | { 14 | auto ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 15 | Struct.numStructs.shouldEqual(1); 16 | } 17 | Struct.numStructs.shouldEqual(0); 18 | } 19 | 20 | @("struct test allocator one lvalue assignment") 21 | @system unittest { 22 | auto allocator = TestAllocator(); 23 | { 24 | auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 25 | Struct.numStructs.shouldEqual(1); 26 | 27 | RefCounted!(Struct, TestAllocator*) ptr2; 28 | ptr2 = ptr1; 29 | Struct.numStructs.shouldEqual(1); 30 | } 31 | Struct.numStructs.shouldEqual(0); 32 | } 33 | 34 | @("struct test allocator one lvalue assignment from T.init") 35 | @system unittest { 36 | 37 | auto allocator = TestAllocator(); 38 | 39 | { 40 | RefCounted!(Struct, TestAllocator*) ptr1; 41 | Struct.numStructs.shouldEqual(0); 42 | 43 | auto ptr2 = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 44 | Struct.numStructs.shouldEqual(1); 45 | 46 | ptr2 = ptr1; 47 | Struct.numStructs.shouldEqual(0); 48 | } 49 | 50 | Struct.numStructs.shouldEqual(0); 51 | } 52 | 53 | @("struct test allocator one lvalue assignment both non-null") 54 | @system unittest { 55 | 56 | auto allocator = TestAllocator(); 57 | 58 | { 59 | auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 60 | Struct.numStructs.shouldEqual(1); 61 | 62 | auto ptr2 = RefCounted!(Struct, TestAllocator*)(&allocator, 7); 63 | Struct.numStructs.shouldEqual(2); 64 | 65 | ptr2 = ptr1; 66 | Struct.numStructs.shouldEqual(1); 67 | } 68 | 69 | Struct.numStructs.shouldEqual(0); 70 | } 71 | 72 | 73 | 74 | @("struct test allocator one rvalue assignment test allocator") 75 | @system unittest { 76 | auto allocator = TestAllocator(); 77 | { 78 | RefCounted!(Struct, TestAllocator*) ptr; 79 | ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 80 | Struct.numStructs.shouldEqual(1); 81 | } 82 | Struct.numStructs.shouldEqual(0); 83 | } 84 | 85 | @("struct test allocator one rvalue assignment mallocator") 86 | @safe unittest { 87 | import std.experimental.allocator.mallocator: Mallocator; 88 | { 89 | RefCounted!(Struct, Mallocator) ptr; 90 | ptr = RefCounted!(Struct, Mallocator)(5); 91 | Struct.numStructs.shouldEqual(1); 92 | } 93 | Struct.numStructs.shouldEqual(0); 94 | } 95 | 96 | 97 | @("struct test allocator one lvalue copy constructor") 98 | @system unittest { 99 | auto allocator = TestAllocator(); 100 | { 101 | auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 102 | Struct.numStructs.shouldEqual(1); 103 | auto ptr2 = ptr1; 104 | Struct.numStructs.shouldEqual(1); 105 | 106 | ptr1.i.shouldEqual(5); 107 | ptr2.i.shouldEqual(5); 108 | } 109 | Struct.numStructs.shouldEqual(0); 110 | } 111 | 112 | @("struct test allocator one rvalue copy constructor") 113 | @system unittest { 114 | auto allocator = TestAllocator(); 115 | { 116 | auto ptr = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 117 | Struct.numStructs.shouldEqual(1); 118 | } 119 | Struct.numStructs.shouldEqual(0); 120 | } 121 | 122 | @("many copies made") 123 | @system unittest { 124 | auto allocator = TestAllocator(); 125 | 126 | // helper function for intrusive testing, in case the implementation 127 | // ever changes 128 | size_t refCount(T)(ref T ptr) { 129 | return ptr._impl._count; 130 | } 131 | 132 | { 133 | auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 5); 134 | Struct.numStructs.shouldEqual(1); 135 | 136 | auto ptr2 = ptr1; 137 | Struct.numStructs.shouldEqual(1); 138 | 139 | { 140 | auto ptr3 = ptr2; 141 | Struct.numStructs.shouldEqual(1); 142 | 143 | refCount(ptr1).shouldEqual(3); 144 | refCount(ptr2).shouldEqual(3); 145 | refCount(ptr3).shouldEqual(3); 146 | } 147 | 148 | Struct.numStructs.shouldEqual(1); 149 | refCount(ptr1).shouldEqual(2); 150 | refCount(ptr2).shouldEqual(2); 151 | 152 | auto produce() { 153 | return RefCounted!(Struct, TestAllocator*)(&allocator, 3); 154 | } 155 | 156 | ptr1 = produce; 157 | Struct.numStructs.shouldEqual(2); 158 | refCount(ptr1).shouldEqual(1); 159 | refCount(ptr2).shouldEqual(1); 160 | 161 | ptr1.twice.shouldEqual(6); 162 | ptr2.twice.shouldEqual(10); 163 | } 164 | 165 | Struct.numStructs.shouldEqual(0); 166 | } 167 | 168 | @("default allocator") 169 | @system unittest { 170 | { 171 | auto ptr = RefCounted!Struct(5); 172 | Struct.numStructs.shouldEqual(1); 173 | } 174 | Struct.numStructs.shouldEqual(0); 175 | } 176 | 177 | @("default.struct.shared") 178 | @system unittest { 179 | { 180 | auto ptr0 = RefCounted!(shared SharedStruct)(5); 181 | SharedStruct.numStructs.shouldEqual(1); 182 | auto ptr2 = ptr0; // copying the pointer ups the ref count but not # of structs 183 | SharedStruct.numStructs.should == 1; 184 | } 185 | SharedStruct.numStructs.shouldEqual(0); 186 | } 187 | 188 | 189 | @("default.class.shared") 190 | @system unittest { 191 | { 192 | auto ptr = RefCounted!(shared SharedClass)(5); 193 | SharedClass.numClasss.shouldEqual(1); 194 | } 195 | SharedClass.numClasss.shouldEqual(0); 196 | } 197 | 198 | 199 | @("deref") 200 | @system unittest { 201 | auto allocator = TestAllocator(); 202 | auto rc1 = RefCounted!(int, TestAllocator*)(&allocator, 5); 203 | 204 | (*rc1).shouldEqual(5); 205 | auto rc2 = rc1; 206 | *rc2 = 42; 207 | (*rc1).shouldEqual(42); 208 | } 209 | 210 | @("swap") 211 | @system unittest { 212 | import std.algorithm: swap; 213 | RefCounted!(int, TestAllocator*) rc1, rc2; 214 | swap(rc1, rc2); 215 | } 216 | 217 | @("phobos bug 6606") 218 | @system unittest { 219 | 220 | union U { 221 | size_t i; 222 | void* p; 223 | } 224 | 225 | struct S { 226 | U u; 227 | } 228 | 229 | alias SRC = RefCounted!(S, TestAllocator*); 230 | } 231 | 232 | @("phobos bug 6436") 233 | @system unittest 234 | { 235 | static struct S { 236 | this(ref int val, string file = __FILE__, size_t line = __LINE__) { 237 | val.shouldEqual(3, file, line); 238 | ++val; 239 | } 240 | } 241 | 242 | auto allocator = TestAllocator(); 243 | int val = 3; 244 | auto s = RefCounted!(S, TestAllocator*)(&allocator, val); 245 | val.shouldEqual(4); 246 | } 247 | 248 | @("assign from T") 249 | @safe unittest { 250 | import std.experimental.allocator.mallocator: Mallocator; 251 | 252 | { 253 | auto a = RefCounted!(Struct, Mallocator)(3); 254 | Struct.numStructs.shouldEqual(1); 255 | 256 | *a = Struct(5); 257 | Struct.numStructs.shouldEqual(1); 258 | (*a).shouldEqual(Struct(5)); 259 | 260 | RefCounted!(Struct, Mallocator) b; 261 | b = a; 262 | (*b).shouldEqual(Struct(5)); 263 | Struct.numStructs.shouldEqual(1); 264 | } 265 | 266 | Struct.numStructs.shouldEqual(0); 267 | } 268 | 269 | @("assign self") 270 | @system unittest { 271 | auto allocator = TestAllocator(); 272 | { 273 | auto a = RefCounted!(Struct, TestAllocator*)(&allocator, 1); 274 | a = a; 275 | Struct.numStructs.shouldEqual(1); 276 | } 277 | Struct.numStructs.shouldEqual(0); 278 | } 279 | 280 | @("SharedStruct") 281 | @system unittest { 282 | auto allocator = TestAllocator(); 283 | { 284 | auto ptr = RefCounted!(shared SharedStruct, TestAllocator*)(&allocator, 5); 285 | SharedStruct.numStructs.shouldEqual(1); 286 | } 287 | SharedStruct.numStructs.shouldEqual(0); 288 | } 289 | 290 | @("@nogc @safe") 291 | @safe @nogc unittest { 292 | 293 | auto allocator = SafeAllocator(); 294 | 295 | { 296 | const ptr = RefCounted!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6); 297 | assert(ptr.i == 6); 298 | assert(NoGcStruct.numStructs == 1); 299 | } 300 | 301 | assert(NoGcStruct.numStructs == 0); 302 | } 303 | 304 | 305 | @("const object") 306 | @system unittest { 307 | auto allocator = TestAllocator(); 308 | auto ptr1 = RefCounted!(const Struct, TestAllocator*)(&allocator, 5); 309 | } 310 | 311 | 312 | @("theAllocator") 313 | @system unittest { 314 | 315 | with(theTestAllocator) { 316 | auto ptr = RefCounted!Struct(42); 317 | (*ptr).shouldEqual(Struct(42)); 318 | Struct.numStructs.shouldEqual(1); 319 | } 320 | 321 | Struct.numStructs.shouldEqual(0); 322 | } 323 | 324 | 325 | @("threads Mallocator") 326 | @system unittest { 327 | import std.experimental.allocator.mallocator: Mallocator; 328 | static assert(__traits(compiles, sendRefCounted!Mallocator(7))); 329 | } 330 | 331 | @("threads SafeAllocator by value") 332 | @system unittest { 333 | // can't even use TestAllocator because it has indirections 334 | // can't pass by pointer since it's an indirection 335 | auto allocator = SafeAllocator(); 336 | static assert(__traits(compiles, sendRefCounted!(SafeAllocator)(allocator, 7))); 337 | } 338 | 339 | @("threads SafeAllocator by shared pointer") 340 | @system unittest { 341 | // can't even use TestAllocator because it has indirections 342 | // can't only pass by pointer if shared 343 | auto allocator = shared SafeAllocator(); 344 | static assert(__traits(compiles, sendRefCounted!(shared SafeAllocator*)(&allocator, 7))); 345 | } 346 | 347 | @("Construct RefCounted from Unique") 348 | @system unittest { 349 | import automem.unique: Unique; 350 | auto allocator = TestAllocator(); 351 | auto ptr = refCounted(Unique!(int, TestAllocator*)(&allocator, 42)); 352 | (*ptr).shouldEqual(42); 353 | } 354 | 355 | @("RefCounted with class") 356 | @system unittest { 357 | auto allocator = TestAllocator(); 358 | { 359 | writelnUt("Creating ptr"); 360 | auto ptr = RefCounted!(Class, TestAllocator*)(&allocator, 33); 361 | (*ptr).i.shouldEqual(33); 362 | Class.numClasses.shouldEqual(1); 363 | } 364 | Class.numClasses.shouldEqual(0); 365 | } 366 | 367 | @("@nogc class destructor") 368 | @nogc unittest { 369 | 370 | import automem: Unique; 371 | 372 | auto allocator = SafeAllocator(); 373 | 374 | { 375 | const ptr = Unique!(NoGcClass, SafeAllocator)(SafeAllocator(), 6); 376 | // shouldEqual isn't @nogc 377 | assert(ptr.i == 6); 378 | assert(NoGcClass.numClasses == 1); 379 | } 380 | 381 | assert(NoGcClass.numClasses == 0); 382 | } 383 | 384 | @("RefCounted opSlice and opIndex") 385 | @system unittest { 386 | import std.mmfile: MmFile; 387 | auto file = RefCounted!MmFile(null, MmFile.Mode.readWriteNew, 120, null); 388 | // The type of file[0] should be ubyte, not Impl. 389 | static assert(is(typeof(file[0]) == typeof(MmFile.init[0]))); 390 | // opSlice should result in void[] not Impl[]. 391 | static assert(is(typeof(file[0 .. size_t.max]) == typeof(MmFile.init[0 .. size_t.max]))); 392 | ubyte[] data = cast(ubyte[]) file[0 .. cast(size_t) file.length]; 393 | immutable ubyte b = file[1]; 394 | file[1] = cast(ubyte) (b + 1); 395 | assert(data[1] == cast(ubyte) (b + 1)); 396 | } 397 | 398 | @("Construct RefCounted using global allocator for struct with zero-args ctor") 399 | @system unittest { 400 | struct S { 401 | private ulong zeroArgsCtorTest = 3; 402 | } 403 | auto s = RefCounted!S.construct(); 404 | static assert(is(typeof(s) == RefCounted!S)); 405 | assert(s._impl !is null); 406 | assert(s.zeroArgsCtorTest == 3); 407 | } 408 | 409 | 410 | 411 | void sendRefCounted(Allocator, Args...)(Args args) { 412 | import std.concurrency: spawn, send; 413 | 414 | auto tid = spawn(&threadFunc); 415 | auto ptr = RefCounted!(shared SharedStruct, Allocator)(args); 416 | 417 | tid.send(ptr); 418 | } 419 | 420 | void threadFunc() { 421 | 422 | } 423 | 424 | @("shared struct with indirection") 425 | @system unittest { 426 | auto s = RefCounted!(shared SharedStructWithIndirection)("foobar"); 427 | } 428 | 429 | 430 | @("copy from T.init") 431 | unittest { 432 | static struct X { 433 | int i; 434 | } 435 | static struct Y { 436 | RefCounted!X x; 437 | } 438 | Y y1; 439 | Y y2; 440 | y2 = y1; 441 | } 442 | 443 | 444 | @("number of allocations") 445 | @safe unittest { 446 | static TestAllocator allocator; 447 | allocator.numAllocations.should == 0; 448 | 449 | auto ptr1 = RefCounted!(Struct, TestAllocator*)(&allocator, 77); 450 | allocator.numAllocations.should == 1; 451 | { 452 | auto ptr2 = ptr1; 453 | allocator.numAllocations.should == 1; 454 | 455 | { 456 | auto ptr = ptr2; 457 | allocator.numAllocations.should == 1; 458 | } 459 | 460 | auto produce(int i) { 461 | return typeof(ptr1)(&allocator, i); 462 | } 463 | 464 | ptr1 = produce(99); 465 | allocator.numAllocations.should == 2; 466 | } 467 | 468 | allocator.numAllocations.should == 2; 469 | } 470 | -------------------------------------------------------------------------------- /tests/ut/unique.d: -------------------------------------------------------------------------------- 1 | module ut.unique; 2 | 3 | 4 | import ut; 5 | import automem.unique; 6 | 7 | 8 | mixin TestUtils; 9 | 10 | 11 | /// 12 | @("with struct and test allocator") 13 | @system unittest { 14 | 15 | auto allocator = TestAllocator(); 16 | { 17 | const foo = Unique!(Struct, TestAllocator*)(&allocator, 5); 18 | foo.twice.shouldEqual(10); 19 | allocator.numAllocations.shouldEqual(1); 20 | Struct.numStructs.shouldEqual(1); 21 | } 22 | 23 | Struct.numStructs.shouldEqual(0); 24 | } 25 | 26 | 27 | @("with class and test allocator") 28 | @system unittest { 29 | 30 | auto allocator = TestAllocator(); 31 | { 32 | const foo = Unique!(Class, TestAllocator*)(&allocator, 5); 33 | foo.twice.shouldEqual(10); 34 | allocator.numAllocations.shouldEqual(1); 35 | Class.numClasses.shouldEqual(1); 36 | } 37 | 38 | Class.numClasses.shouldEqual(0); 39 | } 40 | 41 | /// 42 | @("with struct and mallocator") 43 | @safe unittest { 44 | 45 | import std.experimental.allocator.mallocator: Mallocator; 46 | { 47 | const foo = Unique!(Struct, Mallocator)(5); 48 | foo.twice.shouldEqual(10); 49 | Struct.numStructs.shouldEqual(1); 50 | } 51 | 52 | Struct.numStructs.shouldEqual(0); 53 | } 54 | 55 | @("default constructor") 56 | @system unittest { 57 | auto allocator = TestAllocator(); 58 | 59 | auto ptr = Unique!(Struct, TestAllocator*)(); 60 | (cast(bool)ptr).shouldBeFalse; 61 | ptr.get.shouldBeNull; 62 | 63 | ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 64 | ptr.get.shouldNotBeNull; 65 | ptr.get.twice.shouldEqual(10); 66 | (cast(bool)ptr).shouldBeTrue; 67 | } 68 | 69 | @(".init") 70 | @system unittest { 71 | auto allocator = TestAllocator(); 72 | 73 | Unique!(Struct, TestAllocator*) ptr; 74 | (cast(bool)ptr).shouldBeFalse; 75 | ptr.get.shouldBeNull; 76 | 77 | ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 78 | ptr.get.shouldNotBeNull; 79 | ptr.get.twice.shouldEqual(10); 80 | (cast(bool)ptr).shouldBeTrue; 81 | } 82 | 83 | @("move") 84 | @system unittest { 85 | import std.algorithm: move; 86 | 87 | auto allocator = TestAllocator(); 88 | auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 89 | Unique!(Struct, TestAllocator*) newPtr = oldPtr.move; 90 | oldPtr.shouldBeNull; 91 | newPtr.twice.shouldEqual(10); 92 | Struct.numStructs.shouldEqual(1); 93 | } 94 | 95 | @("copy") 96 | @system unittest { 97 | auto allocator = TestAllocator(); 98 | auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 99 | Unique!(Struct, TestAllocator*) newPtr; 100 | // non-copyable 101 | static assert(!__traits(compiles, newPtr = oldPtr)); 102 | } 103 | 104 | @("construct base class") 105 | @system unittest { 106 | auto allocator = TestAllocator(); 107 | { 108 | Unique!(Object, TestAllocator*) bar = Unique!(Class, TestAllocator*)(&allocator, 5); 109 | Class.numClasses.shouldEqual(1); 110 | } 111 | 112 | Class.numClasses.shouldEqual(0); 113 | } 114 | 115 | @("assign base class") 116 | @system unittest { 117 | auto allocator = TestAllocator(); 118 | { 119 | Unique!(Object, TestAllocator*) bar; 120 | bar = Unique!(Class, TestAllocator*)(&allocator, 5); 121 | Class.numClasses.shouldEqual(1); 122 | } 123 | 124 | Class.numClasses.shouldEqual(0); 125 | } 126 | 127 | @("Return Unique from function") 128 | @system unittest { 129 | auto allocator = TestAllocator(); 130 | 131 | auto produce(int i) { 132 | return Unique!(Struct, TestAllocator*)(&allocator, i); 133 | } 134 | 135 | auto ptr = produce(4); 136 | ptr.twice.shouldEqual(8); 137 | } 138 | 139 | @("unique") 140 | @system unittest { 141 | auto allocator = TestAllocator(); 142 | auto oldPtr = Unique!(Struct, TestAllocator*)(&allocator, 5); 143 | auto newPtr = oldPtr.unique; 144 | newPtr.twice.shouldEqual(10); 145 | oldPtr.shouldBeNull; 146 | } 147 | 148 | @("@nogc") 149 | @safe @nogc unittest { 150 | 151 | import std.experimental.allocator.mallocator: Mallocator; 152 | 153 | { 154 | const ptr = Unique!(NoGcStruct, Mallocator)(5); 155 | // shouldEqual isn't @nogc 156 | assert(ptr.i == 5); 157 | assert(NoGcStruct.numStructs == 1); 158 | } 159 | 160 | assert(NoGcStruct.numStructs == 0); 161 | } 162 | 163 | @("@nogc @safe") 164 | @safe @nogc unittest { 165 | 166 | auto allocator = SafeAllocator(); 167 | 168 | { 169 | const ptr = Unique!(NoGcStruct, SafeAllocator)(SafeAllocator(), 6); 170 | // shouldEqual isn't @nogc 171 | assert(ptr.i == 6); 172 | assert(NoGcStruct.numStructs == 1); 173 | } 174 | 175 | assert(NoGcStruct.numStructs == 0); 176 | } 177 | 178 | @("deref") 179 | @system unittest { 180 | { 181 | auto allocator = TestAllocator(); 182 | auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 183 | *ptr = Struct(13); 184 | ptr.twice.shouldEqual(26); 185 | Struct.numStructs.shouldEqual(1); 186 | } 187 | Struct.numStructs.shouldEqual(0); 188 | } 189 | 190 | @("move from populated other unique") 191 | @system unittest { 192 | 193 | import std.algorithm: move; 194 | 195 | { 196 | auto allocator = TestAllocator(); 197 | 198 | auto ptr1 = Unique!(Struct, TestAllocator*)(&allocator, 5); 199 | Struct.numStructs.shouldEqual(1); 200 | 201 | { 202 | auto ptr2 = Unique!(Struct, TestAllocator*)(&allocator, 10); 203 | Struct.numStructs.shouldEqual(2); 204 | ptr1 = ptr2.move; 205 | Struct.numStructs.shouldEqual(1); 206 | ptr2.shouldBeNull; 207 | ptr1.twice.shouldEqual(20); 208 | } 209 | 210 | } 211 | 212 | Struct.numStructs.shouldEqual(0); 213 | } 214 | 215 | @("assign to rvalue") 216 | @system unittest { 217 | 218 | { 219 | auto allocator = TestAllocator(); 220 | 221 | auto ptr = Unique!(Struct, TestAllocator*)(&allocator, 5); 222 | ptr = Unique!(Struct, TestAllocator*)(&allocator, 7); 223 | 224 | Struct.numStructs.shouldEqual(1); 225 | ptr.twice.shouldEqual(14); 226 | } 227 | 228 | Struct.numStructs.shouldEqual(0); 229 | } 230 | 231 | 232 | @("theAllocator") 233 | @system unittest { 234 | with(theTestAllocator){ 235 | auto ptr = Unique!Struct(42); 236 | (*ptr).shouldEqual(Struct(42)); 237 | Struct.numStructs.shouldEqual(1); 238 | } 239 | 240 | Struct.numStructs.shouldEqual(0); 241 | } 242 | 243 | 244 | @("@nogc class destructor") 245 | @nogc unittest { 246 | 247 | auto allocator = SafeAllocator(); 248 | 249 | { 250 | const ptr = Unique!(NoGcClass, SafeAllocator)(SafeAllocator(), 6); 251 | // shouldEqual isn't @nogc 252 | assert(ptr.i == 6); 253 | assert(NoGcClass.numClasses == 1); 254 | } 255 | 256 | assert(NoGcClass.numClasses == 0); 257 | } 258 | 259 | @("@nogc class implementing @nogc interface ") 260 | @nogc unittest { 261 | import std.experimental.allocator.mallocator: Mallocator; 262 | { 263 | Unique!(NoGcInterface, Mallocator) ptr = Unique!(NoGcClassImplementingNoGcInterface, Mallocator)(42); 264 | assert(ptr.getNumber == 42); 265 | assert(NoGcClassImplementingNoGcInterface.numClasses == 1); 266 | } 267 | assert(NoGcClassImplementingNoGcInterface.numClasses == 0); 268 | } 269 | 270 | version(DIP1000) { 271 | @("borrow") 272 | @safe unittest { 273 | 274 | auto allocator = SafeAllocator(); 275 | 276 | { 277 | const ptr = Unique!(Struct, SafeAllocator)(SafeAllocator(), 6); 278 | scopeFunc(ptr.borrow).shouldEqual(18); 279 | } 280 | } 281 | 282 | private int scopeFunc(scope const(Struct)* s) @safe { 283 | 284 | return s.i * 3; 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /tests/ut/vector.d: -------------------------------------------------------------------------------- 1 | module ut.vector; 2 | 3 | 4 | import ut; 5 | import automem.vector; 6 | import std.experimental.allocator.mallocator: Mallocator; 7 | import test_allocator; 8 | 9 | 10 | @("length") 11 | @safe unittest { 12 | vector("foo", "bar", "baz").length.should == 3; 13 | vector("quux", "toto").length.should == 2; 14 | } 15 | 16 | @("vector.int") 17 | @safe unittest { 18 | vector(1, 2, 3, 4, 5).range.should == [1, 2, 3, 4, 5]; 19 | vector(2, 3, 4).range.should == [2, 3, 4]; 20 | vector(2, 3, 4).range.should == [2, 3, 4]; 21 | } 22 | 23 | @("vector.double") 24 | @safe unittest { 25 | vector(33.3).range.should == [33.3]; 26 | vector(22.2, 77.7).range.should == [22.2, 77.7]; 27 | } 28 | 29 | @("copying") 30 | @safe unittest { 31 | auto vec1 = vector(1, 2, 3); 32 | () @trusted { vec1.reserve(10); }(); 33 | auto vec2 = vec1; 34 | vec1[1] = 7; 35 | 36 | vec1.range.should == [1, 7, 3]; 37 | vec2.range.should == [1, 2, 3]; 38 | } 39 | 40 | @("bounds check") 41 | @safe unittest { 42 | 43 | auto vec = vector(1, 2, 3); 44 | () @trusted { vec.reserve(10); }(); 45 | vec[3].shouldThrow!BoundsException; 46 | vec[-1].shouldThrow!BoundsException; 47 | () @trusted { vec[0 .. 4].shouldThrow!BoundsException; }(); 48 | () @trusted { vec[0.. 3]; }(); // shouldn't throw (see #45) 49 | } 50 | 51 | @("extend") 52 | @system unittest { 53 | import std.algorithm: map; 54 | 55 | auto vec = vector(0, 1, 2, 3); 56 | 57 | vec ~= 4; 58 | vec.range.should == [0, 1, 2, 3, 4]; 59 | 60 | vec ~= [5, 6]; 61 | vec.range.should == [0, 1, 2, 3, 4, 5, 6]; 62 | 63 | vec ~= [1, 2].map!(a => a + 10); 64 | vec.range.should == [0, 1, 2, 3, 4, 5, 6, 11, 12]; 65 | } 66 | 67 | 68 | @("put") 69 | @system unittest { 70 | import std.range: iota; 71 | 72 | auto vec = vector(0, 1, 2, 3); 73 | vec.put(4); 74 | vec.range.should == [0, 1, 2, 3, 4]; 75 | vec.put(2.iota); 76 | vec.range.should == [0, 1, 2, 3, 4, 0, 1]; 77 | } 78 | 79 | @("append") 80 | @system unittest { 81 | auto vec1 = vector(0, 1, 2); 82 | auto vec2 = vector(3, 4); 83 | 84 | auto vec3 = vec1 ~ vec2; 85 | vec3.range.should == [0, 1, 2, 3, 4]; 86 | 87 | vec1[0] = 7; 88 | vec2[0] = 9; 89 | vec3.range.should == [0, 1, 2, 3, 4]; 90 | 91 | 92 | // make sure capacity is larger 93 | vec1 ~= 100; 94 | vec1.capacity.shouldBeGreaterThan(vec1.length); 95 | vec1.range.should == [7, 1, 2, 100]; 96 | 97 | vec2 ~= 200; 98 | vec2.capacity.shouldBeGreaterThan(vec2.length); 99 | vec2.range.should == [9, 4, 200]; 100 | 101 | (vec1 ~ vec2).range.should == [7, 1, 2, 100, 9, 4, 200]; 102 | (vec1 ~ vector(11, 12, 13, 14, 15)).range.should == [7, 1, 2, 100, 11, 12, 13, 14, 15]; 103 | } 104 | 105 | @("slice") 106 | @system unittest { 107 | const vec = vector(0, 1, 2, 3, 4, 5); 108 | vec[].should == [0, 1, 2, 3, 4, 5]; 109 | vec[1 .. 3].should == [1, 2]; 110 | vec[1 .. 4].should == [1, 2, 3]; 111 | vec[2 .. 5].should == [2, 3, 4]; 112 | vec[1 .. $ - 1].should == [1, 2, 3, 4]; 113 | vec[0 .. 6].should == [0, 1, 2, 3, 4, 5]; 114 | } 115 | 116 | @("opDollar") 117 | @system unittest { 118 | auto vec = vector(0, 1, 2, 3, 4); 119 | vec ~= 5; 120 | vec ~= 6; 121 | vec.capacity.shouldBeGreaterThan(vec.length); 122 | 123 | vec[1 .. $ - 1].should == [1, 2, 3, 4, 5]; 124 | } 125 | 126 | @("assign") 127 | @system unittest { 128 | import std.range: iota; 129 | auto vec = vector(10, 11, 12); 130 | vec = 5.iota; 131 | vec.range.should == [0, 1, 2, 3, 4]; 132 | } 133 | 134 | @("construct from range") 135 | @safe unittest { 136 | import std.range: iota; 137 | vector(5.iota).range.should == [0, 1, 2, 3, 4]; 138 | } 139 | 140 | 141 | @("popBack") 142 | @safe unittest { 143 | auto vec = vector(0, 1, 2); 144 | vec.popBack; 145 | vec.range.should == [0, 1]; 146 | } 147 | 148 | @("popFront") 149 | @safe unittest { 150 | auto vec = vector(0, 1, 2, 3, 4); 151 | vec.popFront; 152 | vec.range.should == [1, 2, 3, 4]; 153 | vec.empty.shouldBeFalse; 154 | 155 | foreach(i; 0 .. vec.length) vec.popFront; 156 | vec.empty.shouldBeTrue; 157 | } 158 | 159 | 160 | @("opSliceAssign") 161 | @system unittest { 162 | auto vec = vector("foo", "bar", "quux", "toto"); 163 | 164 | vec[] = "haha"; 165 | vec.range.should == ["haha", "haha", "haha", "haha"]; 166 | 167 | vec[1..3] = "oops"; 168 | vec.range.should == ["haha", "oops", "oops", "haha"]; 169 | } 170 | 171 | @("opSliceOpAssign") 172 | @system unittest { 173 | auto vec = vector("foo", "bar", "quux", "toto"); 174 | vec[] ~= "oops"; 175 | vec.range.should == ["foooops", "baroops", "quuxoops", "totooops"]; 176 | } 177 | 178 | @("opSliceOpAssign range") 179 | @system unittest { 180 | auto vec = vector("foo", "bar", "quux", "toto"); 181 | vec[1..3] ~= "oops"; 182 | vec.range.should == ["foo", "baroops", "quuxoops", "toto"]; 183 | } 184 | 185 | @("clear") 186 | @safe unittest { 187 | auto vec = vector(0, 1, 2, 3); 188 | vec.clear; 189 | int[] empty; 190 | vec.range.should ==(empty); 191 | } 192 | 193 | 194 | @("Mallocator elements") 195 | @safe @nogc unittest { 196 | import std.algorithm: equal; 197 | auto vec = vector!Mallocator(0, 1, 2, 3); 198 | int[4] exp = [0, 1, 2, 3]; 199 | assert(equal(vec.range, exp[])); 200 | } 201 | 202 | @("Mallocator range") 203 | @safe @nogc unittest { 204 | import std.algorithm: equal; 205 | import std.range: iota; 206 | auto vec = vector!Mallocator(iota(5)); 207 | int[5] exp = [0, 1, 2, 3, 4]; 208 | assert(equal(vec.range, exp[])); 209 | } 210 | 211 | 212 | @("theAllocator null") 213 | @safe unittest { 214 | Vector!int vec; 215 | } 216 | 217 | 218 | @("Mallocator null") 219 | @safe @nogc unittest { 220 | Vector!(int, Mallocator) vec; 221 | } 222 | 223 | 224 | @("escape.range") 225 | @safe @nogc unittest { 226 | 227 | alias Ints = typeof(Vector!(int, Mallocator).init.range()); 228 | 229 | Ints ints1; 230 | scope vec = vector!Mallocator(0, 1, 2, 3); 231 | Ints ints2; 232 | 233 | static assert(!__traits(compiles, ints1 = vec.range)); 234 | ints2 = vec.range; // should compile 235 | } 236 | 237 | 238 | @("escape.element") 239 | @safe unittest { 240 | 241 | int i = 1; 242 | int j = 2; 243 | 244 | int** oops; 245 | scope vec = vector(&i, &j); 246 | int** ok; 247 | 248 | // Taking address of `ref` return not allowed in older dmd versions 249 | static if (__VERSION__ >= 2101) 250 | { 251 | static assert(!__traits(compiles, oops = &vec[0])); 252 | ok = &vec[0]; 253 | } 254 | } 255 | 256 | 257 | @("TestAllocator elements capacity") 258 | @system unittest { 259 | static TestAllocator allocator; 260 | 261 | auto vec = vector(&allocator, 0, 1, 2); 262 | vec.range.should == [0, 1, 2]; 263 | 264 | vec ~= 3; 265 | vec ~= 4; 266 | vec ~= 5; 267 | vec ~= 6; 268 | vec ~= 7; 269 | vec ~= 8; 270 | 271 | vec.range.should == [0, 1, 2, 3, 4, 5, 6, 7, 8]; 272 | allocator.numAllocations.shouldBeSmallerThan(4); 273 | } 274 | 275 | @("TestAllocator reserve") 276 | @system unittest { 277 | static TestAllocator allocator; 278 | 279 | auto vec = vector!(TestAllocator*, int)(&allocator); 280 | 281 | vec.reserve(5); 282 | () @trusted { vec.empty.should == true; }(); 283 | 284 | vec ~= 0; 285 | vec ~= 1; 286 | vec ~= 2; 287 | vec ~= 3; 288 | vec ~= 4; 289 | 290 | vec.range.should == [0, 1, 2, 3, 4]; 291 | allocator.numAllocations.should == 1; 292 | 293 | vec ~= 5; 294 | vec.range.should == [0, 1, 2, 3, 4, 5]; 295 | allocator.numAllocations.should == 2; 296 | } 297 | 298 | @("TestAllocator shrink no length") 299 | @system unittest { 300 | static TestAllocator allocator; 301 | 302 | auto vec = vector!(TestAllocator*, int)(&allocator); 303 | vec.reserve(10); 304 | 305 | vec ~= 0; 306 | vec ~= 1; 307 | vec ~= 2; 308 | vec ~= 3; 309 | 310 | vec.length.should == 4; 311 | vec.capacity.should == 10; 312 | 313 | vec.shrink; 314 | vec.length.should == 4; 315 | vec.capacity.should == 4; 316 | } 317 | 318 | @("TestAllocator shrink negative number") 319 | @system unittest { 320 | static TestAllocator allocator; 321 | 322 | auto vec = vector(&allocator, 0); 323 | vec ~= 1; 324 | vec ~= 2; 325 | vec ~= 3; 326 | vec.capacity.shouldBeGreaterThan(vec.length); 327 | const oldCapacity = vec.capacity; 328 | 329 | vec.shrink(-1).shouldBeFalse; 330 | vec.capacity.should == oldCapacity; 331 | } 332 | 333 | @("TestAllocator shrink larger than capacity") 334 | @system unittest { 335 | static TestAllocator allocator; 336 | 337 | auto vec = vector(&allocator, 0); 338 | vec ~= 1; 339 | vec ~= 2; 340 | vec ~= 3; 341 | vec.capacity.shouldBeGreaterThan(vec.length); 342 | const oldCapacity = vec.capacity; 343 | 344 | vec.shrink(oldCapacity * 2).shouldBeFalse; 345 | vec.capacity.should == oldCapacity; 346 | } 347 | 348 | 349 | @("TestAllocator shrink with length") 350 | @system unittest { 351 | static TestAllocator allocator; 352 | 353 | auto vec = vector(&allocator, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 354 | vec.capacity.should == 10; 355 | 356 | vec.shrink(5); 357 | vec.range.should == [0, 1, 2, 3, 4]; 358 | vec.capacity.should == 5; 359 | 360 | vec ~= 5; 361 | vec.range.should == [0, 1, 2, 3, 4, 5]; 362 | allocator.numAllocations.should == 3; 363 | 364 | vec.reserve(10); 365 | vec.length.should == 6; 366 | vec.capacity.shouldBeGreaterThan(6); 367 | } 368 | 369 | @("TestAllocator shrink with length range invalidation") 370 | @safe unittest { 371 | static TestAllocator allocator; 372 | 373 | scope vec = vector(&allocator, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); 374 | vec.capacity.should == 10; 375 | scope rng = vec.range; 376 | 377 | foreach(i; 0 .. 5) 378 | rng.popFront; 379 | rng.empty.should == false; 380 | 381 | () @trusted { vec.shrink(5); }(); 382 | rng.empty.should == true; 383 | } 384 | 385 | @("TestAllocator copy") 386 | @safe unittest { 387 | static TestAllocator allocator; 388 | 389 | auto vec1 = vector(&allocator, "foo", "bar", "baz"); 390 | allocator.numAllocations.should == 1; 391 | 392 | auto vec2 = vec1; 393 | allocator.numAllocations.should == 2; 394 | } 395 | 396 | @("TestAllocator move") 397 | @safe unittest { 398 | static TestAllocator allocator; 399 | 400 | auto vec = vector(&allocator, "foo", "bar", "baz"); 401 | allocator.numAllocations.should == 1; 402 | 403 | consumeVec(vec); 404 | allocator.numAllocations.should == 1; 405 | } 406 | 407 | 408 | private void consumeVec(T)(auto ref T vec) { 409 | 410 | } 411 | 412 | 413 | @("set length") 414 | @system unittest { 415 | Vector!int vec; 416 | vec.length = 3; 417 | vec.range.should == [0, 0, 0]; 418 | } 419 | 420 | 421 | @("foreach") 422 | @safe unittest { 423 | // can't be inline in the foreach otherwise disappears before anything happens 424 | scope v = vector(7, 7, 7); 425 | foreach(e; v.range) { 426 | e.should == 7; 427 | } 428 | } 429 | 430 | 431 | @("equal") 432 | @safe unittest { 433 | import std.range: iota; 434 | import std.algorithm: equal; 435 | 436 | auto v = vector(0, 1, 2, 3); 437 | assert(equal(v.range, 4.iota)); 438 | } 439 | 440 | 441 | @("bool") 442 | @safe unittest { 443 | vector(0, 1, 2).shouldBeTrue; 444 | Vector!int v; 445 | if(v) { 446 | assert(0); 447 | } 448 | } 449 | 450 | @("char") 451 | @system unittest { 452 | { 453 | auto vec = vector('f', 'o', 'o'); 454 | vec.range.should ==("foo"); 455 | vec ~= 'b'; 456 | vec ~= ['a', 'r']; 457 | vec.range.should ==("foobar"); 458 | vec ~= "quux"; 459 | vec.range.should ==("foobarquux"); 460 | } 461 | 462 | { 463 | auto vec = vector("foo"); 464 | vec.range.should ==("foo"); 465 | vec.popBack; 466 | vec.range.should ==("fo"); 467 | } 468 | 469 | { 470 | auto vec = vector("foo"); 471 | vec ~= "bar"; 472 | vec.range.should ==("foobar"); 473 | } 474 | } 475 | 476 | 477 | @("immutable.append") 478 | @system unittest { 479 | Vector!(immutable int) vec; 480 | vec ~= 42; 481 | vec.range.should == [42]; 482 | } 483 | 484 | 485 | @("String") 486 | @safe unittest { 487 | // can't be inline in the foreach otherwise disappears 488 | auto s = String("oooooo"); 489 | foreach(c; s.range) 490 | c.should == 'o'; 491 | } 492 | 493 | @("stringz") 494 | @safe unittest { 495 | import std.string: fromStringz; 496 | auto str = vector("foobar"); 497 | const strz = () @trusted { return str.stringz; }(); 498 | const back = () @trusted { return fromStringz(strz); }(); 499 | back.should == "foobar"; 500 | str.range.should ==("foobar"); 501 | } 502 | 503 | 504 | @("ptr") 505 | @safe unittest { 506 | const vec = vector(0, 1, 2, 3); 507 | takesScopePtr(vec.ptr); 508 | () @trusted { vec.ptr[1].should == 1; }(); 509 | } 510 | 511 | private void takesScopePtr(T)(scope const(T)* ptr) { 512 | 513 | } 514 | 515 | 516 | @("StackFront") 517 | @safe @nogc unittest { 518 | import std.algorithm: equal; 519 | import std.experimental.allocator.showcase: StackFront; 520 | import std.experimental.allocator.mallocator: Mallocator; 521 | 522 | alias Allocator = StackFront!(1024, Mallocator); 523 | 524 | { 525 | Vector!(int, Allocator) v; 526 | () @trusted { v ~= 1; }(); 527 | { 528 | int[1] expected = [1]; 529 | assert(equal(v.range, expected[])); 530 | } 531 | } 532 | 533 | { 534 | static void fun(Allocator)(ref Allocator allocator) { 535 | Vector!(int, Allocator) v; 536 | } 537 | } 538 | } 539 | 540 | 541 | version(Windows) {} 542 | else { 543 | @("mmapRegionList") 544 | @system unittest { 545 | import std.experimental.allocator.showcase: mmapRegionList; 546 | import std.experimental.allocator.mallocator: Mallocator; 547 | import automem.vector: isAllocator; 548 | 549 | auto v = vector(mmapRegionList(1024), 0, 1, 2); 550 | v ~= 3; 551 | } 552 | } 553 | 554 | 555 | 556 | @("2d") 557 | @safe unittest { 558 | auto v = vector(vector(0, 0, 0), vector(1, 1, 1, 1)); 559 | v[0].range.should == [0, 0, 0]; 560 | v[1].range.should == [1, 1, 1, 1]; 561 | } 562 | 563 | 564 | @("toString") 565 | @safe unittest { 566 | import std.conv: text; 567 | auto v = vector(1, 2, 3); 568 | v.range.text.should == `[1, 2, 3]`; 569 | } 570 | 571 | 572 | @("return") 573 | @system unittest { 574 | 575 | static auto fun() { 576 | auto v = vector(1, 2, 3); 577 | v ~= 4; 578 | return v; 579 | } 580 | 581 | auto v = fun; 582 | v ~= 5; 583 | v.range.should == [1, 2, 3, 4, 5]; 584 | } 585 | 586 | 587 | @("noconsume.range") 588 | @safe unittest { 589 | import std.algorithm: equal; 590 | 591 | scope v = vector(1, 2, 3); 592 | 593 | static void fun(R)(R range) { 594 | import std.array: array; 595 | assert(equal(range, [1, 2, 3])); 596 | } 597 | 598 | fun(v.range); 599 | assert(equal(v.range, [1, 2, 3])); 600 | } 601 | 602 | 603 | @("noconsume.foreach") 604 | @safe unittest { 605 | scope v = vector(1, 2, 3); 606 | foreach(e; v.range) {} 607 | v.range.should == [1, 2, 3]; 608 | } 609 | 610 | 611 | @("noconsume.map") 612 | @safe unittest { 613 | import std.algorithm: map; 614 | 615 | scope v = vector(1, 2, 3); 616 | v.range.map!(a => a * 2).should == [2, 4, 6]; 617 | v.range.should == [1, 2, 3]; 618 | } 619 | 620 | 621 | @("reserve") 622 | @safe unittest { 623 | scope vec = vector(1, 2, 3); 624 | vec.range.should == [1, 2, 3]; 625 | () @trusted { vec.reserve(10); }(); 626 | vec.range.should == [1, 2, 3]; 627 | } 628 | 629 | 630 | @("range.reserve") 631 | @safe unittest { 632 | scope vec = vector(1, 2, 3); 633 | scope range = vec.range; 634 | 635 | range.save.should == [1, 2, 3]; 636 | () @trusted { vec.reserve(10); }(); 637 | 638 | range.should == [1, 2, 3]; 639 | } 640 | 641 | 642 | @("range.const") 643 | @safe unittest { 644 | const vec = vector(1, 2, 3); 645 | vec.range.should == [1, 2, 3]; 646 | } 647 | 648 | 649 | @("range.bounds") 650 | @safe unittest { 651 | const vec = vector(1, 2, 3, 4, 5); 652 | vec.range(1, 4).should == [2, 3, 4]; 653 | vec.range(2, vec.length).should == [3, 4, 5]; 654 | vec.range(2, -1).should == [3, 4, 5]; 655 | vec.range(2, -2).should == [3, 4]; 656 | } 657 | 658 | 659 | @("equals") 660 | @safe unittest { 661 | import std.range: iota, only; 662 | 663 | const vec = vector(0, 1, 2); 664 | 665 | (vec == 3.iota).should == true; 666 | (vec == 2.iota).should == false; 667 | (vec == 4.iota).should == false; 668 | (vec == only(0)).should == false; 669 | } 670 | -------------------------------------------------------------------------------- /tests/ut_main.d: -------------------------------------------------------------------------------- 1 | import unit_threaded; 2 | 3 | 4 | mixin runTestsMain!( 5 | "automem", // example tests 6 | "automem.unique", // has some tests that can't be moved out 7 | "automem.traits", // static asserts 8 | "automem.utils", // static asserts 9 | "ut.issues", 10 | "ut.ref_counted", 11 | "ut.unique", 12 | "ut.vector", 13 | ); 14 | 15 | 16 | shared static this() @safe nothrow { 17 | import std.experimental.allocator: theAllocator, allocatorObject; 18 | import std.experimental.allocator.mallocator: Mallocator; 19 | () @trusted { theAllocator = allocatorObject(Mallocator.instance); }(); 20 | } 21 | -------------------------------------------------------------------------------- /travis_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | [ -e bin ] || mkdir bin 6 | --------------------------------------------------------------------------------