1// Written in the D programming language.
2/**
3Source: $(PHOBOSSRC std/experimental/allocator/building_blocks/scoped_allocator.d)
4*/
5module std.experimental.allocator.building_blocks.scoped_allocator;
6
7import std.experimental.allocator.common;
8
9/**
10
11`ScopedAllocator` delegates all allocation requests to `ParentAllocator`.
12When destroyed, the `ScopedAllocator` object automatically calls $(D
13deallocate) for all memory allocated through its lifetime. (The $(D
14deallocateAll) function is also implemented with the same semantics.)
15
16`deallocate` is also supported, which is where most implementation effort
17and overhead of `ScopedAllocator` go. If `deallocate` is not needed, a
18simpler design combining `AllocatorList` with `Region` is recommended.
19
20*/
21struct ScopedAllocator(ParentAllocator)
22{
23    static if (!stateSize!ParentAllocator)
24    {
25        // This test is available only for stateless allocators
26        version (StdUnittest)
27        @system unittest
28        {
29            testAllocator!(() => ScopedAllocator());
30        }
31    }
32
33    import std.experimental.allocator.building_blocks.affix_allocator
34        : AffixAllocator;
35    import std.traits : hasMember;
36    import std.typecons : Ternary;
37
38    private struct Node
39    {
40        Node* prev;
41        Node* next;
42        size_t length;
43    }
44
45    alias Allocator = AffixAllocator!(ParentAllocator, Node);
46
47    // state
48    /**
49    If `ParentAllocator` is stateful, `parent` is a property giving access
50    to an `AffixAllocator!ParentAllocator`. Otherwise, `parent` is an alias for `AffixAllocator!ParentAllocator.instance`.
51    */
52    static if (stateSize!ParentAllocator)
53    {
54        Allocator parent;
55    }
56    else
57    {
58        alias parent = Allocator.instance;
59    }
60    private Node* root;
61
62    /**
63    `ScopedAllocator` is not copyable.
64    */
65    @disable this(this);
66
67    /**
68    `ScopedAllocator`'s destructor releases all memory allocated during its
69    lifetime.
70    */
71    ~this()
72    {
73        deallocateAll;
74    }
75
76    /// Alignment offered
77    enum alignment = Allocator.alignment;
78
79    /**
80    Forwards to `parent.goodAllocSize` (which accounts for the management
81    overhead).
82    */
83    size_t goodAllocSize(size_t n)
84    {
85        return parent.goodAllocSize(n);
86    }
87
88    // Common code shared between allocate and allocateZeroed.
89    private enum _processAndReturnAllocateResult =
90    q{
91       if (!b.ptr) return b;
92        Node* toInsert = & parent.prefix(b);
93        toInsert.prev = null;
94        toInsert.next = root;
95        toInsert.length = n;
96        assert(!root || !root.prev);
97        if (root) root.prev = toInsert;
98        root = toInsert;
99        return b;
100    };
101
102    /**
103    Allocates memory. For management it actually allocates extra memory from
104    the parent.
105    */
106    void[] allocate(size_t n)
107    {
108        auto b = parent.allocate(n);
109        mixin(_processAndReturnAllocateResult);
110    }
111
112    static if (hasMember!(Allocator, "allocateZeroed"))
113    package(std) void[] allocateZeroed()(size_t n)
114    {
115        auto b = parent.allocateZeroed(n);
116        mixin(_processAndReturnAllocateResult);
117    }
118
119    /**
120    Forwards to $(D parent.expand(b, delta)).
121    */
122    static if (hasMember!(Allocator, "expand"))
123    bool expand(ref void[] b, size_t delta)
124    {
125        auto result = parent.expand(b, delta);
126        if (result && b)
127        {
128            () @trusted { parent.prefix(b).length = b.length; }();
129        }
130        return result;
131    }
132
133    /**
134    Reallocates `b` to new size `s`.
135    */
136    bool reallocate(ref void[] b, size_t s)
137    {
138        // Remove from list
139        if (b.ptr)
140        {
141            Node* n = & parent.prefix(b);
142            if (n.prev) n.prev.next = n.next;
143            else root = n.next;
144            if (n.next) n.next.prev = n.prev;
145        }
146        auto result = parent.reallocate(b, s);
147        // Add back to list
148        if (b.ptr)
149        {
150            Node* n = & parent.prefix(b);
151            n.prev = null;
152            n.next = root;
153            n.length = s;
154            if (root) root.prev = n;
155            root = n;
156        }
157        return result;
158    }
159
160    /**
161    Forwards to `parent.owns(b)`.
162    */
163    static if (hasMember!(Allocator, "owns"))
164    Ternary owns(void[] b)
165    {
166        return parent.owns(b);
167    }
168
169    /**
170    Deallocates `b`.
171    */
172    static if (hasMember!(Allocator, "deallocate"))
173    bool deallocate(void[] b)
174    {
175        // Remove from list
176        if (b.ptr)
177        {
178            Node* n = & parent.prefix(b);
179            if (n.prev) n.prev.next = n.next;
180            else root = n.next;
181            if (n.next) n.next.prev = n.prev;
182        }
183        return parent.deallocate(b);
184    }
185
186    /**
187    Deallocates all memory allocated.
188    */
189    bool deallocateAll()
190    {
191        bool result = true;
192        for (auto n = root; n; )
193        {
194            void* p = n + 1;
195            auto length = n.length;
196            n = n.next;
197            if (!parent.deallocate(p[0 .. length]))
198                result = false;
199        }
200        root = null;
201        return result;
202    }
203
204    /**
205    Returns `Ternary.yes` if this allocator is not responsible for any memory,
206    `Ternary.no` otherwise. (Never returns `Ternary.unknown`.)
207    */
208    pure nothrow @safe @nogc
209    Ternary empty() const
210    {
211        return Ternary(root is null);
212    }
213}
214
215///
216@system unittest
217{
218    import std.experimental.allocator.mallocator : Mallocator;
219    import std.typecons : Ternary;
220    ScopedAllocator!Mallocator alloc;
221    assert(alloc.empty == Ternary.yes);
222    const b = alloc.allocate(10);
223    assert(b.length == 10);
224    assert(alloc.empty == Ternary.no);
225}
226
227version (StdUnittest)
228@system unittest
229{
230    import std.experimental.allocator.gc_allocator : GCAllocator;
231    testAllocator!(() => ScopedAllocator!GCAllocator());
232}
233
234@system unittest // https://issues.dlang.org/show_bug.cgi?id=16046
235{
236    import std.exception;
237    import std.experimental.allocator;
238    import std.experimental.allocator.mallocator;
239    ScopedAllocator!Mallocator alloc;
240    auto foo = alloc.make!int(1).enforce;
241    auto bar = alloc.make!int(2).enforce;
242    alloc.dispose(foo);
243    alloc.dispose(bar); // segfault here
244}
245
246@system unittest
247{
248    import std.experimental.allocator.gc_allocator : GCAllocator;
249    ScopedAllocator!GCAllocator a;
250
251    assert(__traits(compiles, (() nothrow @safe @nogc => a.goodAllocSize(0))()));
252
253    // Ensure deallocate inherits from parent allocators
254    auto b = a.allocate(42);
255    assert(b.length == 42);
256    () nothrow @nogc { a.deallocate(b); }();
257}
258
259// Test that deallocateAll infers from parent
260@system unittest
261{
262    import std.experimental.allocator.building_blocks.region : Region;
263
264    ScopedAllocator!(Region!()) a;
265    a.parent.parent = Region!()(new ubyte[1024 * 64]);
266    auto b = a.allocate(42);
267    assert(b.length == 42);
268    assert((() pure nothrow @safe @nogc => a.expand(b, 22))());
269    assert(b.length == 64);
270    assert((() nothrow @nogc => a.reallocate(b, 100))());
271    assert(b.length == 100);
272    assert((() nothrow @nogc => a.deallocateAll())());
273}
274
275@system unittest
276{
277    import std.experimental.allocator.building_blocks.region : Region;
278    import std.experimental.allocator.mallocator : Mallocator;
279    import std.typecons : Ternary;
280
281    auto a = Region!(Mallocator)(1024 * 64);
282    auto b = a.allocate(42);
283    assert(b.length == 42);
284    assert((() pure nothrow @safe @nogc => a.expand(b, 22))());
285    assert(b.length == 64);
286    assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes);
287    assert((() nothrow @nogc => a.reallocate(b, 100))());
288    assert(b.length == 100);
289    assert((() pure nothrow @safe @nogc => a.owns(b))() == Ternary.yes);
290    assert((() pure nothrow @safe @nogc => a.owns(null))() == Ternary.no);
291}
292
293// Test empty
294@system unittest
295{
296    import std.experimental.allocator.mallocator : Mallocator;
297    import std.typecons : Ternary;
298    ScopedAllocator!Mallocator alloc;
299
300    assert((() pure nothrow @safe @nogc => alloc.empty)() == Ternary.yes);
301    const b = alloc.allocate(10);
302    assert((() pure nothrow @safe @nogc => alloc.empty)() == Ternary.no);
303}
304