diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..23c438e --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,80 @@ +name: CI +on: + pull_request: + push: + branches: + - master + +jobs: + Test: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-20.04 + - windows-2019 + - macos-10.15 + d: + - "ldc-1.27.1" + - "dmd-2.097.2" + meson: + - 0.59.1 + ninja: + - 1.10.2 + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + # Cache + - name: Cache + uses: actions/cache@v2 + with: + path: | + ~/.dub + ~/AppData/Local/dub + ~/.cache/pip + ~/AppData/Local/pip/cache + key: "containers-cache-OS:${{ matrix.os }}-D:${{ matrix.d }}-${{ matrix.meson }}-${{ matrix.ninja }}-deps:${{ hashFiles('./meson.build') }}-${{ hashFiles('./dub.sdl') }}" + + # Setup compilers and tools + + - name: Setup D + uses: dlang-community/setup-dlang@v1 + with: + compiler: ${{ matrix.d }} + + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Setup Meson + run: pip install meson==${{ matrix.meson }} + + - name: Setup Ninja + uses: aminya/install-cmake@new-versions-and-arch + with: + cmake: false + ninja: ${{ matrix.ninja }} + + # Build and Test + + - name: Build + run: | + meson setup ./build + meson compile -C ./build + + - name: Install gcc-multilib + if: contains(matrix.os, 'ubuntu') + run: sudo apt-get install -y gcc-multilib + + - name: Make Test + run: | + make -B -C test/ + + # TODO it fails to run the UnrolledList test on ldc + - name: Dub Test + run: | + dub test diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index ebca4a1..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "stdx-allocator"] - path = stdx-allocator - url = https://github.com/dlang-community/stdx-allocator diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fa6cfd2..0000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: d -sudo: false -dist: bionic - -addons: - apt: - packages: - - pkg-config - - gcc-multilib - - ninja-build - - python3-pip - - python3-setuptools - -branches: - only: - - master - -install: - - pip3 install 'meson==0.53.1' - -before_script: - export PATH=$PATH:$PWD/.ntmp - -script: - - meson build && ninja -j8 -C build - - ninja -j8 -C build test -v - - if [ $? -ne 0 ]; then cat ./build/meson-logs/testlog.txt; fi - - git submodule update --init --recursive - - make -B -C test/ - - dub test diff --git a/README.md b/README.md index 3491af9..44ca39b 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,6 @@ Containers [![CI status](https://travis-ci.org/dlang-community/containers.svg?br Containers backed by std.experimental.allocator -# Dependencies -Run `git submodule update --init --recursive` after cloning this repository. - # Documentation Documentation is available at http://dlang-community.github.io/containers/index.html diff --git a/dub.sdl b/dub.sdl index 4e24c48..b0f430d 100644 --- a/dub.sdl +++ b/dub.sdl @@ -8,4 +8,3 @@ buildType "unittest" { versions "emsi_containers_unittest" buildOptions "debugMode" "debugInfo" "unittests" } -dependency "stdx-allocator" version="~>2.77.5" diff --git a/meson.build b/meson.build index 2465359..49b03ba 100644 --- a/meson.build +++ b/meson.build @@ -6,9 +6,6 @@ project('dcontainers', 'd', project_soversion = '0' -pkgc = import('pkgconfig') -allocator_dep = dependency('stdx-allocator', version: '>= 2.77', fallback: ['stdx-allocator', 'allocator_dep']) - # # Sources # @@ -38,19 +35,16 @@ src_dir = include_directories('src/') # # Targets # -dcontainers_lib = library('dcontainers', +dcontainers_lib = static_library('dcontainers', [dcontainers_src], include_directories: [src_dir], install: true, - version: meson.project_version(), - soversion: project_soversion, - dependencies: [allocator_dep] ) +pkgc = import('pkgconfig') pkgc.generate(name: 'dcontainers', libraries: [dcontainers_lib], subdirs: 'd/containers', - requires: ['stdx-allocator'], version: meson.project_version(), description: 'Containers backed by std.experimental.allocator.' ) @@ -59,24 +53,18 @@ pkgc.generate(name: 'dcontainers', dcontainers_dep = declare_dependency( link_with: [dcontainers_lib], include_directories: [src_dir], - dependencies: [allocator_dep] ) # # Tests # -if meson.get_compiler('d').get_id() == 'llvm' - extra_args = ['-main', '-link-defaultlib-shared'] -else - extra_args = ['-main'] -endif +extra_args = ['-main'] dcontainers_test_exe = executable('test_dcontainers', [dcontainers_src, 'test/compile_test.d', 'test/external_allocator_test.d'], include_directories: [src_dir], - dependencies: [allocator_dep], d_unittest: true, link_args: extra_args ) diff --git a/src/containers/cyclicbuffer.d b/src/containers/cyclicbuffer.d index 765ab61..8a317f7 100644 --- a/src/containers/cyclicbuffer.d +++ b/src/containers/cyclicbuffer.d @@ -8,7 +8,7 @@ module containers.cyclicbuffer; private import core.exception : onRangeError; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; private import std.range.primitives : empty, front, back, popFront, popBack; private import containers.internal.node : shouldAddGCRange; @@ -26,7 +26,7 @@ struct CyclicBuffer(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange @disable this(this); private import std.conv : emplace; - private import stdx.allocator.common : stateSize; + private import std.experimental.allocator.common : stateSize; private import std.traits : isImplicitlyConvertible, hasElaborateDestructor; static if (stateSize!Allocator != 0) @@ -449,8 +449,8 @@ private: version(emsi_containers_unittest) private { import std.algorithm.comparison : equal; - import stdx.allocator.gc_allocator : GCAllocator; - import stdx.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.gc_allocator : GCAllocator; + import std.experimental.allocator.building_blocks.free_list : FreeList; import std.range : iota, lockstep, StoppingPolicy; struct S @@ -549,27 +549,27 @@ version(emsi_containers_unittest) unittest version(emsi_containers_unittest) unittest { - int a = 0; + int* a = new int; { CyclicBuffer!S b; { - S s = { &a }; + S s = { a }; foreach (i; 0 .. 5) b.insertBack(s); - assert(a == 5); + assert(*a == 5); foreach (i; 0 .. 5) - b.insertBack(S(&a)); - assert(a == 10); + b.insertBack(S(a)); + assert(*a == 10); foreach (i; 0 .. 5) { b.removeBack(); b.removeFront(); } - assert(a == 20); + assert(*a == 20); } - assert(a == 21); + assert(*a == 21); } - assert(a == 21); + assert(*a == 21); } version(emsi_containers_unittest) unittest @@ -678,26 +678,26 @@ version(emsi_containers_unittest) unittest version(emsi_containers_unittest) unittest { - int a = 0; + int* a = new int; { CyclicBuffer!S b; foreach (i; 0 .. 5) - b.insertBack(S(&a)); - assert(a == 5); + b.insertBack(S(a)); + assert(*a == 5); } - assert(a == 10); - a = 0; + assert(*a == 10); + *a = 0; { CyclicBuffer!S b; foreach (i; 0 .. 4) - b.insertBack(S(&a)); - assert(a == 4); + b.insertBack(S(a)); + assert(*a == 4); b.removeFront(); - assert(a == 5); - b.insertBack(S(&a)); - assert(a == 6); + assert(*a == 5); + b.insertBack(S(a)); + assert(*a == 6); } - assert(a == 10); + assert(*a == 10); } version(emsi_containers_unittest) unittest diff --git a/src/containers/dynamicarray.d b/src/containers/dynamicarray.d index a76774e..da14bdb 100644 --- a/src/containers/dynamicarray.d +++ b/src/containers/dynamicarray.d @@ -7,8 +7,10 @@ module containers.dynamicarray; +private import core.lifetime : move, moveEmplace, copyEmplace, emplace; +private import std.traits : isCopyable; private import containers.internal.node : shouldAddGCRange; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; /** * Array that is able to grow itself when items are appended to it. Uses @@ -24,7 +26,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange { this(this) @disable; - private import stdx.allocator.common : stateSize; + private import std.experimental.allocator.common : stateSize; static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $]))) { @@ -61,7 +63,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange ~this() { - import stdx.allocator.mallocator : Mallocator; + import std.experimental.allocator.mallocator : Mallocator; import containers.internal.node : shouldAddGCRange; if (arr is null) @@ -100,7 +102,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange /// Index operator overload pragma(inline, true) - auto opIndex(this This)(size_t i) @nogc + ref auto opIndex(this This)(size_t i) @nogc { return opSlice!(This)(i, i + 1)[0]; } @@ -110,7 +112,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange */ void insertBack(T value) { - import stdx.allocator.mallocator : Mallocator; + import std.experimental.allocator.mallocator : Mallocator; import containers.internal.node : shouldAddGCRange; if (arr.length == 0) @@ -128,6 +130,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange static if (useGC) void* oldPtr = arr.ptr; void[] a = cast(void[]) arr; + import std.experimental.allocator.common : reallocate; allocator.reallocate(a, c * T.sizeof); arr = cast(typeof(arr)) a; static if (useGC) @@ -137,20 +140,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange GC.addRange(arr.ptr, arr.length * T.sizeof); } } - import std.traits: hasElaborateAssign, hasElaborateDestructor; - static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) - { - // If a destructor is run before blit or assignment involves - // more than just a blit, ensure that arr[l] is in a valid - // state before assigning to it. - import core.stdc.string : memcpy, memset; - const init = typeid(T).initializer(); - if (init.ptr is null) // null pointer means initialize to 0s - (() @trusted => memset(arr.ptr + l, 0, T.sizeof))(); - else - (() @trusted => memcpy(arr.ptr + l, init.ptr, T.sizeof))(); - } - emplace(arr[l++], value); + moveEmplace(*cast(ContainerStorageType!T*)&value, arr[l++]); } /// ditto @@ -192,7 +182,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) { foreach (ref value; rhs) - emplace(arr[l++], value); + copyEmplace(value, arr[l++]); } else { @@ -258,6 +248,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange static if (useGC) void* oldPtr = arr.ptr; void[] a = cast(void[]) arr; + import std.experimental.allocator.common : reallocate; allocator.reallocate(a, c * T.sizeof); arr = cast(typeof(arr)) a; static if (useGC) @@ -269,37 +260,53 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange } } - static if (is(typeof({T value;}))) // default construction is allowed + /** + * Change the array length. + * When growing, initialize new elements to the default value. + */ + static if (is(typeof({static T value;}))) // default construction is allowed + void resize(size_t n) { - /** - * Change the array length. - * When growing, initialize new elements to the default value. - */ - void resize(size_t n) + import std.traits: hasElaborateAssign, hasElaborateDestructor; + auto toFill = resizeStorage(n); + static if (is(T == struct) && hasElaborateDestructor!T) { - resize(n, T.init); + foreach (ref target; toFill) + emplace(&target); } + else + toFill[] = T.init; } /** * Change the array length. * When growing, initialize new elements to the given value. */ + static if (isCopyable!T) void resize(size_t n, T value) { + import std.traits: hasElaborateAssign, hasElaborateDestructor; + auto toFill = resizeStorage(n); + static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) + { + foreach (ref target; toFill) + copyEmplace(value, target); + } + else + toFill[] = value; + } + + // Resizes storage only, and returns slice of new memory to fill. + private ContainerStorageType!T[] resizeStorage(size_t n) + { + ContainerStorageType!T[] toFill = null; + if (arr.length < n) reserve(n); if (l < n) // Growing? { - import std.traits: hasElaborateAssign, hasElaborateDestructor; - static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) - { - foreach (i; l..n) - emplace(arr[i], value); - } - else - arr[l..n] = value; + toFill = arr[l..n]; } else { @@ -312,6 +319,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange } l = n; + return toFill; } /** @@ -324,7 +332,7 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange auto next = i + 1; while (next < this.l) { - arr[next - 1] = arr[next]; + move(arr[next], arr[next - 1]); ++next; } @@ -353,16 +361,18 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange /// Index assignment support void opIndexAssign(T value, size_t i) @nogc { - arr[i] = value; + arr[i] = move(*cast(ContainerStorageType!T*)&value); } /// Slice assignment support + static if (isCopyable!T) void opSliceAssign(T value) @nogc { arr[0 .. l] = value; } /// ditto + static if (isCopyable!T) void opSliceAssign(T value, size_t i, size_t j) @nogc { arr[i .. j] = value; @@ -403,13 +413,6 @@ struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange private: - static void emplace(ref ContainerStorageType!T target, ref AppendT source) - { - (cast(void[])((&target)[0..1]))[] = cast(void[])((&source)[0..1]); - static if (__traits(hasMember, T, "__xpostblit")) - target.__xpostblit(); - } - import containers.internal.storage_type : ContainerStorageType; import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; @@ -470,12 +473,12 @@ version(emsi_containers_unittest) version(emsi_containers_unittest) unittest { - int a = 0; + int* a = new int; { DynamicArray!(Cls) arr; - arr.insert(new Cls( & a)); + arr.insert(new Cls(a)); } - assert(a == 0); // Destructor not called. + assert(*a == 0); // Destructor not called. } version(emsi_containers_unittest) unittest @@ -522,12 +525,12 @@ version(emsi_containers_unittest) unittest version(emsi_containers_unittest) unittest { - int a = 0; + int* a = new int; DynamicArray!(Cls, Mallocator, true) arr; - arr.insert(new Cls(&a)); + arr.insert(new Cls(a)); arr.remove(0); - assert(a == 0); // Destructor not called. + assert(*a == 0); // Destructor not called. } version(emsi_containers_unittest) unittest @@ -593,12 +596,14 @@ version(emsi_containers_unittest) unittest ++(*a); } } - int a = 0; - DynamicArray!S arr; - // This next line may segfault if destructors are called - // on structs in invalid states. - arr.insert(S(&a)); - assert(a == 1); + int* a = new int; + { + DynamicArray!S arr; + // This next line may segfault if destructors are called + // on structs in invalid states. + arr.insert(S(a)); + } + assert(*a == 1); } version(emsi_containers_unittest) @nogc unittest @@ -624,16 +629,19 @@ version(emsi_containers_unittest) @nogc unittest version(emsi_containers_unittest) unittest { + enum initialValue = 0x69FF5705DAD1AB6CUL; + enum payloadValue = 0x495343303356D18CUL; + static struct S { - bool initialized; + ulong value = initialValue; @nogc: @disable this(); - this(int) { initialized = true; } - ~this() { assert(initialized); } + this(ulong value) { this.value = value; } + ~this() { assert(value == initialValue || value == payloadValue); } } - auto s = S(0); + auto s = S(payloadValue); DynamicArray!S arr; arr.insertBack(s); @@ -668,3 +676,23 @@ version(emsi_containers_unittest) @nogc unittest b.resize(3, Counter(0)); assert(Counter.count == 3); } + +version(emsi_containers_unittest) @nogc unittest +{ + struct S { int i = 42; @disable this(this); } + DynamicArray!S a; + a.resize(1); + assert(a[0].i == 42); +} + +version(emsi_containers_unittest) unittest +{ + import std.experimental.allocator.building_blocks.region : Region; + auto region = Region!Mallocator(1024); + + auto arr = DynamicArray!(int, Region!(Mallocator)*, true)(®ion); + // reserve and insert back call the common form of reallocate + arr.reserve(10); + arr.insertBack(1); + assert(arr[0] == 1); +} diff --git a/src/containers/hashmap.d b/src/containers/hashmap.d index 25d7a02..2fa7f6d 100644 --- a/src/containers/hashmap.d +++ b/src/containers/hashmap.d @@ -7,9 +7,10 @@ module containers.hashmap; +private import core.lifetime : move; private import containers.internal.hash; private import containers.internal.node : shouldAddGCRange; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; private import std.traits : isBasicType, Unqual; /** @@ -28,7 +29,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K { this(this) @disable; - private import stdx.allocator.common : stateSize; + private import std.experimental.allocator.common : stateSize; static if (stateSize!Allocator != 0) { @@ -96,7 +97,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K */ void clear() { - import stdx.allocator : dispose; + import std.experimental.allocator : dispose; // always remove ranges from GC first before disposing of buckets, to // prevent segfaults when the GC collects at an unfortunate time @@ -181,7 +182,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K */ void opIndexAssign(V value, const K key) { - insert(key, value); + insert(key, move(mutable(value))); } /** @@ -257,7 +258,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K auto app = appender!(K[])(); foreach (ref const bucket; buckets) { - foreach (item; bucket) + foreach (ref item; bucket) app.put(cast(K) item.key); } return app.data; @@ -354,7 +355,7 @@ struct HashMap(K, V, Allocator = Mallocator, alias hashFunction = generateHash!K private: - import stdx.allocator : make, makeArray; + import std.experimental.allocator : make, makeArray; import containers.unrolledlist : UnrolledList; import containers.internal.storage_type : ContainerStorageType; import containers.internal.element_type : ContainerElementType; @@ -364,6 +365,8 @@ private: enum bool useGC = supportGC && (shouldAddGCRange!K || shouldAddGCRange!V); alias Hash = typeof({ K k = void; return hashFunction(k); }()); + static ref ContainerStorageType!T mutable(T)(ref T value) { return *cast(ContainerStorageType!T*)&value; } + enum IterType: ubyte { key, value, both @@ -472,7 +475,7 @@ private: Node* insert(const K key, V value) { - return insert(key, value, hashFunction(key)); + return insert(key, move(mutable(value)), hashFunction(key)); } Node* insert(const K key, V value, const Hash hash, const bool modifyLength = true) @@ -484,15 +487,15 @@ private: { if (item.hash == hash && item.key == key) { - item.value = value; + item.value = move(mutable(value)); return &item; } } static if (storeHash) - Node node = Node(hash, cast(ContainerStorageType!K) key, value); + Node node = Node(hash, cast(ContainerStorageType!K) key, move(mutable(value))); else - Node node = Node(cast(ContainerStorageType!K) key, value); - Node* n = buckets[index].insertAnywhere(node); + Node node = Node(cast(ContainerStorageType!K) key, move(mutable(value))); + Node* n = buckets[index].insertAnywhere(move(node)); if (modifyLength) _length++; if (shouldRehash()) @@ -533,7 +536,8 @@ private: buckets = cast(Bucket[]) allocator.allocate(newSize); static if (useGC) GC.addRange(buckets.ptr, buckets.length * Bucket.sizeof); - assert (buckets); + if (newLength) + assert (buckets); assert (buckets.length == newLength); foreach (ref bucket; buckets) { @@ -545,8 +549,8 @@ private: foreach (ref bucket; oldBuckets) { - foreach (node; bucket) - insert(cast(K) node.key, node.value, node.hash, false); + foreach (ref node; bucket) + insert(cast(K) node.key, move(node.value), node.hash, false); typeid(typeof(bucket)).destroy(&bucket); } static if (useGC) @@ -780,3 +784,15 @@ version(emsi_containers_unittest) unittest foreach (k, ref v; hm) { v++; } assert(hm["a"] == 2); } + +version(emsi_containers_unittest) unittest +{ + static struct S { @disable this(this); } + alias HM = HashMap!(int, S); +} + +version(emsi_containers_unittest) unittest +{ + struct S { int* a; } + alias HM = HashMap!(S, int); +} diff --git a/src/containers/hashset.d b/src/containers/hashset.d index 74c4e7c..3619659 100644 --- a/src/containers/hashset.d +++ b/src/containers/hashset.d @@ -9,7 +9,7 @@ module containers.hashset; private import containers.internal.hash : generateHash, hashToIndex; private import containers.internal.node : shouldAddGCRange; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; private import std.traits : isBasicType; /** @@ -27,7 +27,7 @@ struct HashSet(T, Allocator = Mallocator, alias hashFunction = generateHash!T, { this(this) @disable; - private import stdx.allocator.common : stateSize; + private import std.experimental.allocator.common : stateSize; static if (stateSize!Allocator != 0) { @@ -82,7 +82,7 @@ struct HashSet(T, Allocator = Mallocator, alias hashFunction = generateHash!T, ~this() { - import stdx.allocator : dispose; + import std.experimental.allocator : dispose; import core.memory : GC; static if (useGC) GC.removeRange(buckets.ptr); @@ -218,7 +218,7 @@ private: void initialize(size_t bucketCount) { import core.memory : GC; - import stdx.allocator : makeArray; + import std.experimental.allocator : makeArray; makeBuckets(bucketCount); static if (useGC) @@ -289,7 +289,7 @@ private: void makeBuckets(size_t bucketCount) { - import stdx.allocator : makeArray; + import std.experimental.allocator : makeArray; static if (stateSize!Allocator == 0) buckets = allocator.makeArray!Bucket(bucketCount); @@ -311,7 +311,7 @@ private: void rehash() @trusted { - import stdx.allocator : makeArray, dispose; + import std.experimental.allocator : makeArray, dispose; import core.memory : GC; immutable size_t newLength = buckets.length << 1; @@ -363,7 +363,7 @@ private: ~this() { import core.memory : GC; - import stdx.allocator : dispose; + import std.experimental.allocator : dispose; BucketNode* current = root; BucketNode* previous; @@ -462,7 +462,7 @@ private: bool insert(ItemNode n) { import core.memory : GC; - import stdx.allocator : make; + import std.experimental.allocator : make; BucketNode* hasSpace = null; for (BucketNode* current = root; current !is null; current = current.next) @@ -489,7 +489,7 @@ private: bool remove(ItemNode n) { import core.memory : GC; - import stdx.allocator : dispose; + import std.experimental.allocator : dispose; BucketNode* current = root; BucketNode* previous; @@ -747,7 +747,7 @@ version(emsi_containers_unittest) unittest version(emsi_containers_unittest) unittest { - import stdx.allocator.showcase; + import std.experimental.allocator.showcase; auto allocator = mmapRegionList(1024); auto set = HashSet!(ulong, typeof(&allocator))(0x1000, &allocator); set.insert(124); diff --git a/src/containers/immutablehashset.d b/src/containers/immutablehashset.d index 31289a4..f17a8ea 100644 --- a/src/containers/immutablehashset.d +++ b/src/containers/immutablehashset.d @@ -153,7 +153,7 @@ struct ImmutableHashSet(T, alias hashFunction) private: - import stdx.allocator.mallocator : Mallocator; + import std.experimental.allocator.mallocator : Mallocator; import std.traits : isBasicType, hasMember; import containers.internal.node : shouldAddGCRange; import core.memory : GC; diff --git a/src/containers/internal/hash.d b/src/containers/internal/hash.d index 09fc4fc..983e038 100644 --- a/src/containers/internal/hash.d +++ b/src/containers/internal/hash.d @@ -15,7 +15,7 @@ static if (hash_t.sizeof == 4) } else { - hash_t generateHash(T)(T value) if (!is(T == string)) + hash_t generateHash(T)(const T value) if (!is(T == string)) { return hashOf(value); } diff --git a/src/containers/openhashset.d b/src/containers/openhashset.d index f4ea4ee..feabe51 100644 --- a/src/containers/openhashset.d +++ b/src/containers/openhashset.d @@ -8,8 +8,8 @@ module containers.openhashset; private import containers.internal.hash; private import containers.internal.node : shouldAddGCRange; -private import stdx.allocator.common : stateSize; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.common : stateSize; +private import std.experimental.allocator.mallocator : Mallocator; /** * Simple open-addressed hash set that uses linear probing to resolve sollisions. diff --git a/src/containers/simdset.d b/src/containers/simdset.d index 934d573..07e1860 100644 --- a/src/containers/simdset.d +++ b/src/containers/simdset.d @@ -6,7 +6,7 @@ */ module containers.simdset; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; /** * Set implementation that is well suited for small sets and simple items. @@ -26,7 +26,7 @@ version (D_InlineAsm_X86_64) struct SimdSet(T, Allocator = Mallocator) { this(this) @disable; - private import stdx.allocator.common : stateSize; + private import std.experimental.allocator.common : stateSize; static if (stateSize!Allocator != 0) { diff --git a/src/containers/slist.d b/src/containers/slist.d index 0c327e6..32bcdb5 100644 --- a/src/containers/slist.d +++ b/src/containers/slist.d @@ -8,7 +8,7 @@ module containers.slist; private import containers.internal.node : shouldAddGCRange; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; /** * Single-linked allocator-backed list. @@ -23,7 +23,7 @@ struct SList(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) /// Disable copying. this(this) @disable; - private import stdx.allocator.common : stateSize; + private import std.experimental.allocator.common : stateSize; static if (stateSize!Allocator != 0) { @@ -261,7 +261,7 @@ struct SList(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) private: - import stdx.allocator : make, dispose; + import std.experimental.allocator : make, dispose; import containers.internal.node : shouldAddGCRange; import containers.internal.element_type : ContainerElementType; import containers.internal.mixins : AllocatorState; diff --git a/src/containers/treemap.d b/src/containers/treemap.d index 15158fb..804ba80 100644 --- a/src/containers/treemap.d +++ b/src/containers/treemap.d @@ -8,7 +8,7 @@ module containers.treemap; private import containers.internal.node : shouldAddGCRange; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; /** * A key→value mapping where the keys are guaranteed to be sorted. @@ -25,7 +25,7 @@ struct TreeMap(K, V, Allocator = Mallocator, alias less = "a < b", { this(this) @disable; - private import stdx.allocator.common : stateSize; + private import std.experimental.allocator.common : stateSize; auto allocator() { @@ -285,10 +285,10 @@ version(emsi_containers_unittest) unittest { import std.range.primitives : walkLength; import std.stdio : stdout; - import stdx.allocator.building_blocks.allocator_list : AllocatorList; - import stdx.allocator.building_blocks.free_list : FreeList; - import stdx.allocator.building_blocks.region : Region; - import stdx.allocator.building_blocks.stats_collector : StatsCollector; + import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.stats_collector : StatsCollector; StatsCollector!(FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024)), 64)) allocator; diff --git a/src/containers/ttree.d b/src/containers/ttree.d index bba4cda..6b213a6 100644 --- a/src/containers/ttree.d +++ b/src/containers/ttree.d @@ -9,7 +9,7 @@ module containers.ttree; private import containers.internal.node : shouldAddGCRange; private import containers.internal.mixins : AllocatorState; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; /** * Implements a binary search tree with multiple items per tree node. @@ -481,7 +481,7 @@ private: import std.functional: binaryFun; import std.range : ElementType, isInputRange; import std.traits: isPointer, PointerTarget; - import stdx.allocator.common : stateSize; + import std.experimental.allocator.common : stateSize; alias N = FatNodeInfo!(T.sizeof, 3, cacheLineSize, ulong.sizeof); alias Value = ContainerStorageType!T; @@ -533,7 +533,7 @@ private: do { import core.memory : GC; - import stdx.allocator : make; + import std.experimental.allocator : make; static if (stateSize!Allocator == 0) Node* n = make!Node(Allocator.instance); @@ -554,7 +554,7 @@ private: } do { - import stdx.allocator : dispose; + import std.experimental.allocator : dispose; import core.memory : GC; if (n.left !is null) @@ -1299,10 +1299,10 @@ version(emsi_containers_unittest) unittest } { - import stdx.allocator.building_blocks.free_list : FreeList; - import stdx.allocator.building_blocks.allocator_list : AllocatorList; - import stdx.allocator.building_blocks.region : Region; - import stdx.allocator.building_blocks.stats_collector : StatsCollector; + import std.experimental.allocator.building_blocks.free_list : FreeList; + import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; + import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.stats_collector : StatsCollector; import std.stdio : stdout; StatsCollector!(FreeList!(AllocatorList!(a => Region!(Mallocator)(1024 * 1024)), diff --git a/src/containers/unrolledlist.d b/src/containers/unrolledlist.d index 1212e58..bd9ddae 100644 --- a/src/containers/unrolledlist.d +++ b/src/containers/unrolledlist.d @@ -7,8 +7,9 @@ module containers.unrolledlist; +private import core.lifetime : move; private import containers.internal.node : shouldAddGCRange; -private import stdx.allocator.mallocator : Mallocator; +private import std.experimental.allocator.mallocator : Mallocator; version (X86_64) version (LDC) @@ -33,7 +34,7 @@ struct UnrolledList(T, Allocator = Mallocator, { this(this) @disable; - private import stdx.allocator.common : stateSize; + private import std.experimental.allocator.common : stateSize; static if (stateSize!Allocator != 0) { @@ -71,9 +72,6 @@ struct UnrolledList(T, Allocator = Mallocator, { previous = current; current = current.next; - static if (!(is(T == class) || is(T == interface))) - foreach (ref item; previous.items) - typeid(T).destroy(&item); static if (useGC) { @@ -98,7 +96,7 @@ struct UnrolledList(T, Allocator = Mallocator, if (_back is null) { assert (_front is null); - _back = allocateNode(item); + _back = allocateNode(move(mutable(item))); _front = _back; result = &_back.items[0]; } @@ -107,7 +105,7 @@ struct UnrolledList(T, Allocator = Mallocator, size_t index = _back.nextAvailableIndex(); if (index >= nodeCapacity) { - Node* n = allocateNode(item); + Node* n = allocateNode(move(mutable(item))); n.prev = _back; _back.next = n; _back = n; @@ -116,7 +114,7 @@ struct UnrolledList(T, Allocator = Mallocator, } else { - _back.items[index] = item; + _back.items[index] = move(mutable(item)); _back.markUsed(index); result = &_back.items[index]; } @@ -170,15 +168,13 @@ struct UnrolledList(T, Allocator = Mallocator, n = n.next; continue; } - n.items[i] = item; + n.items[i] = move(mutable(item)); n.markUsed(i); _length++; assert (n.registry <= fullBitPattern); return cast(T*) &n.items[i]; } - n = allocateNode(item); - n.items[0] = item; - n.markUsed(0); + n = allocateNode(move(mutable(item))); _length++; auto retVal = cast(T*) &n.items[0]; if (_front is null) @@ -213,7 +209,7 @@ struct UnrolledList(T, Allocator = Mallocator, * * Returns: true if something was removed. */ - bool remove(T item) + bool remove(ref const T item) { if (_front is null) return false; @@ -239,6 +235,7 @@ struct UnrolledList(T, Allocator = Mallocator, } return retVal; } + bool remove(const T item) { return remove(item); } /// Pops the front item off of the list void popFront() @@ -266,7 +263,7 @@ struct UnrolledList(T, Allocator = Mallocator, import containers.internal.backwards : bsf; size_t index = bsf(_front.registry); } - T r = _front.items[index]; + T r = move(_front.items[index]); _front.markUnused(index); _length--; if (_front.registry == 0) @@ -371,7 +368,7 @@ struct UnrolledList(T, Allocator = Mallocator, i--; } assert (!_back.isFree(i)); - T item = _back.items[i]; + T item = move(_back.items[i]); _back.markUnused(i); _length--; if (_back.registry == 0) @@ -466,7 +463,7 @@ struct UnrolledList(T, Allocator = Mallocator, private: - import stdx.allocator: make, dispose; + import std.experimental.allocator: make, dispose; import containers.internal.node : FatNodeInfo, shouldAddGCRange, fullBits, shouldNullSlot; import containers.internal.storage_type : ContainerStorageType; @@ -479,6 +476,8 @@ private: enum fullBitPattern = fullBits!(BookkeepingType, nodeCapacity); enum bool useGC = supportGC && shouldAddGCRange!T; + static ref ContainerStorageType!T mutable(ref T value) { return *cast(ContainerStorageType!T*)&value; } + Node* _back; Node* _front; size_t _length; @@ -492,7 +491,7 @@ private: import core.memory: GC; GC.addRange(n, Node.sizeof); } - n.items[0] = item; + n.items[0] = move(mutable(item)); n.markUsed(0); return n; } @@ -550,11 +549,12 @@ private: ContainerStorageType!T[nodeCapacity] temp; foreach (j; 0 .. nodeCapacity) if (!first.isFree(j)) - temp[i++] = first.items[j]; + temp[i++] = move(first.items[j]); foreach (j; 0 .. nodeCapacity) if (!second.isFree(j)) - temp[i++] = second.items[j]; - first.items[0 .. i] = temp[0 .. i]; + temp[i++] = move(second.items[j]); + foreach (j; 0 .. i) + first.items[j] = move(temp[j]); first.registry = 0; foreach (k; 0 .. i) first.markUsed(k); @@ -569,7 +569,7 @@ private: static if (BookkeepingType.sizeof < uint.sizeof) immutable uint notReg = ~(cast(uint) registry); else - immutable uint notReg = cast(uint) (~registry); + immutable auto notReg = ~registry; version (LDC_64) { import ldc.intrinsics : llvm_cttz; @@ -736,3 +736,31 @@ version(emsi_containers_unittest) unittest assert(ints.front == 1); assert(ints.back == 11); } + +// Issue #168 +version(emsi_containers_unittest) unittest +{ + import std.typecons : RefCounted; + alias E = RefCounted!int; + + E e = E(12); + UnrolledList!E ints; + ints.insertBack(e); + ints.clear(); + // crucial: no assert failure + assert (e == 12); +} + +// Issue #170 +version(emsi_containers_unittest) unittest +{ + static struct S { @disable this(this); } + UnrolledList!S list; + + list.insert(S()); + list.remove(S()); + + list.insert(S()); + S s; + list.remove(s); +} diff --git a/stdx-allocator b/stdx-allocator deleted file mode 160000 index d6e6ce4..0000000 --- a/stdx-allocator +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d6e6ce4a838e0dad43ef13f050f96627339cdccd diff --git a/subprojects/stdx-allocator.wrap b/subprojects/stdx-allocator.wrap deleted file mode 100644 index a9e29c7..0000000 --- a/subprojects/stdx-allocator.wrap +++ /dev/null @@ -1,4 +0,0 @@ -[wrap-git] -directory = stdx-allocator -url = https://github.com/dlang-community/stdx-allocator.git -revision = v2.77.5 diff --git a/test/external_allocator_test.d b/test/external_allocator_test.d index 610cb13..f3ed50d 100644 --- a/test/external_allocator_test.d +++ b/test/external_allocator_test.d @@ -14,11 +14,11 @@ import containers.unrolledlist; import std.meta : AliasSeq; import std.range.primitives : walkLength; import std.stdio : stdout; -import stdx.allocator.building_blocks.allocator_list : AllocatorList; -import stdx.allocator.building_blocks.free_list : FreeList; -import stdx.allocator.building_blocks.region : Region; -import stdx.allocator.building_blocks.stats_collector : StatsCollector; -import stdx.allocator.mallocator : Mallocator; +import std.experimental.allocator.building_blocks.allocator_list : AllocatorList; +import std.experimental.allocator.building_blocks.free_list : FreeList; +import std.experimental.allocator.building_blocks.region : Region; +import std.experimental.allocator.building_blocks.stats_collector : StatsCollector; +import std.experimental.allocator.mallocator : Mallocator; // Chosen for a very important and completely undocumented reason private enum VERY_SPECIFIC_NUMBER = 371; diff --git a/test/makefile b/test/makefile index 2050440..fcc98a1 100644 --- a/test/makefile +++ b/test/makefile @@ -1,12 +1,32 @@ .PHONY: test graphs clean DC?=dmd -SRC:=$(shell find ../src/ -name "*.d") \ - $(shell find ../stdx-allocator/source -name "*.d") -FLAGS:=-unittest -main -g -cov -I../src/ -I../stdx-allocator/source -debug -wi +SRC:=$(shell find ../src/ -name "*.d") + +ifeq ($(DC), ldc2) + FLAGS:=-unittest -main -g -cov -I../src/ -d-debug -wi + hashmap_gc_test_FLAGS:=-g -O3 -d-debug -wi + looptest_FLAGS:=-O3 +else + FLAGS:=-unittest -main -g -cov -I../src/ -debug -wi + hashmap_gc_test_FLAGS:=-g -inline -O -release -debug -wi + looptest_FLAGS:=-inline -O -release +endif FLAGS32:=$(FLAGS) -m32 -all: all_32 all_64 +all_arch:= +ifeq ($(OS), Windows_NT) + all_arch += all_32 all_64 +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S), Linux) + all_arch += all_32 all_64 + else + all_arch += all_64 + endif +endif + +all: $(all_arch) all_64: test compile_test external_allocator_test hashmap_gc_test ./tests @@ -35,15 +55,15 @@ external_allocator_test_32: external_allocator_test.d $(SRC) $(DC) $(FLAGS32) external_allocator_test.d $(SRC) -ofexternal_allocator_test_32 looptest: looptest.d - $(DC) looptest.d -inline -O -release \ + $(DC) looptest.d $(looptest_FLAGS) \ ../src/memory/allocators.d \ ../src/containers/ttree.d \ ../src/containers/internal/node.d \ -I../src/ \ hashmap_gc_test: hashmap_gc_test.d $(SRC) - $(DC) -g -inline -O -release \ - -I../src/ -I../stdx-allocator/source -debug -wi \ + $(DC) $(hashmap_gc_test_FLAGS) \ + -I../src/ \ $(SRC) \ hashmap_gc_test.d \ -ofhashmap_gc_test