diff --git a/stdx/allocator.d b/stdx/allocator.d
new file mode 100644
index 0000000..fb6db9e
--- /dev/null
+++ b/stdx/allocator.d
@@ -0,0 +1,4602 @@
+// Written in the D programming language.
+
+/**
+Macros:
+WIKI = Phobos/StdAllocator
+MYREF = $1
+TDC =
$(D $1)$(BR)$(SMALL $(I Post:) $(BLUE $(D $+))) |
+TDC2 = $(D $(LREF $0)) |
+RES = $(I result)
+
+Copyright: Andrei Alexandrescu 2013-.
+
+License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Authors: $(WEB erdani.com, Andrei Alexandrescu)
+
+Source: $(PHOBOSSRC std/_allocator.d)
+
+This module implements untyped composable memory allocators. They are $(I
+untyped) because they deal exclusively in $(D void[]) and have no notion of what
+type the memory allocated would be destined for. They are $(I composable)
+because the included allocators are building blocks that can be assembled in
+complex nontrivial allocators.
+
+$(P Unlike the allocators for the C and C++ programming languages, which manage
+the allocated size internally, these allocators require that the client
+maintains (or knows $(I a priori)) the allocation size for each piece of memory
+allocated. Put simply, the client must pass the allocated size upon
+deallocation. Storing the size in the _allocator has significant negative
+performance implications, and is virtually always redundant because client code
+needs knowledge of the allocated size in order to avoid buffer overruns. (See
+more discussion in a $(WEB open-
+std.org/JTC1/SC22/WG21/docs/papers/2013/n3536.html, proposal) for sized
+deallocation in C++.) For this reason, allocators herein traffic in $(D void[])
+as opposed to $(D void*).)
+
+$(P In order to be usable as an _allocator, a type should implement the
+following methods with their respective semantics. Only $(D alignment) and $(D
+allocate) are required. If any of the other methods is missing, the _allocator
+is assumed to not have that capability (for example some allocators do not offer
+manual deallocation of memory).)
+
+$(BOOKTABLE ,
+$(TR $(TH Method name) $(TH Semantics))
+
+$(TR $(TDC uint alignment;, $(RES) > 0) $(TD Returns the minimum alignment of
+all data returned by the allocator. An allocator may implement $(D alignment) as
+a statically-known $(D enum) value only. Applications that need
+dynamically-chosen alignment values should use the $(D alignedAllocate) and $(D
+alignedReallocate) APIs.))
+
+$(TR $(TDC size_t goodAllocSize(size_t n);, $(RES) >= n) $(TD Allocators
+customarily allocate memory in discretely-sized chunks. Therefore, a request for
+$(D n) bytes may result in a larger allocation. The extra memory allocated goes
+unused and adds to the so-called $(WEB goo.gl/YoKffF,internal fragmentation).
+The function $(D goodAllocSize(n)) returns the actual number of bytes that would
+be allocated upon a request for $(D n) bytes. This module defines a default
+implementation that returns $(D n) rounded up to a multiple of the allocator's
+alignment.))
+
+$(TR $(TDC void[] allocate(size_t s);, $(RES) is null || $(RES).length == s)
+$(TD If $(D s == 0), the call may return any empty slice (including $(D
+null)). Otherwise, the call allocates $(D s) bytes of memory and returns the
+allocated block, or $(D null) if the request could not be satisfied.))
+
+$(TR $(TDC void[] alignedAllocate(size_t s, uint a);, $(RES) is null ||
+$(RES).length == s) $(TD Similar to $(D allocate), with the additional guarantee
+that the memory returned is aligned to at least $(D a) bytes. $(D a) must be a
+power of 2 greater than $(D (void*).sizeof).))
+
+$(TR $(TDC void[] allocateAll();, n/a) $(TD This is a special function
+indicating to wrapping allocators that $(D this) is a simple,
+limited-capabilities allocator that invites customization. Fixed-size regions
+fit this characterization. If called, the function allocates all memory
+available to the allocator and returns it.))
+
+$(TR $(TDC bool expand(ref void[] b, size_t delta);, !$(RES) || b.length == $(I
+old)(b).length + delta) $(TD Expands $(D b) by $(D delta) bytes. If $(D b is
+null), the call evaluates $(D b = allocate(delta)) and returns $(D b !is null).
+Otherwise, $(D b) must be a buffer previously allocated with the same allocator.
+If expansion was successful, $(D expand) changes $(D b)'s length to $(D b.length
++ delta) and returns $(D true). Upon failure, the call effects no change upon
+the allocator object, leaves $(D b) unchanged, and returns $(D false).))
+
+$(TR $(TDC bool reallocate(ref void[] b, size_t s);, !$(RES) || b.length == s)
+$(TD Reallocates $(D b) to size $(D s), possibly moving memory around. $(D b)
+must be $(D null) or a buffer allocated with the same allocator. If reallocation
+was successful, $(D reallocate) changes $(D b) appropriately and returns $(D
+true). Upon failure, the call effects no change upon the allocator object,
+leaves $(D b) unchanged, and returns $(D false). An allocator should implement
+$(D reallocate) if it can derive some advantage from doing so; otherwise, this
+module defines a $(D reallocate) free function implemented in terms of $(D
+expand), $(D allocate), and $(D deallocate).))
+
+$(TR $(TDC bool alignedReallocate(ref void[] b, size_t s, uint a);, !$(RES) ||
+b.length == s) $(TD Similar to $(D reallocate), but guarantees the reallocated
+memory is aligned at $(D a) bytes. The buffer must have been originated with a
+call to $(D alignedAllocate). $(D a) must be a power of 2 greater than $(D
+(void*).sizeof).))
+
+$(TR $(TDC bool owns(void[] b);, n/a) $(TD Returns $(D true) if $(D b) has been
+allocated with this allocator. An allocator should define this
+method only if it can decide on ownership precisely and fast (in constant time,
+logarithmic time, or linear time with a low multiplication factor). Traditional
+allocators such as the C heap do not define such functionality. If $(D b is
+null), the allocator should return $(D true) if it may return $(D null) as result of an allocation with $(D size == 0).))
+
+$(TR $(TDC void deallocate(void[] b);, n/a) $(TD If $(D b is null), does
+nothing. Otherwise, deallocates memory previously allocated with this
+allocator.))
+
+$(TR $(TDC void deallocateAll();, n/a) $(TD Deallocates all memory allocated
+with this allocator. If an allocator implements this method, it must specify
+whether its destructor calls it, too.))
+
+$(TR $(TDC static Allocator it;, it $(I is a valid) Allocator $(I object)) $(TD
+Some allocators are $(I monostate), i.e. have only an instance and hold only
+global state. (Notable examples are C's own $(D malloc)-based allocator and D's
+garbage-collected heap.) Such allocators must define a static $(D it) instance
+that serves as the symbolic placeholder for the global instance of the
+allocator. An allocator should not hold state and define $(D it) simultaneously.
+Depending on whether the allocator is thread-safe or not, this instance may be
+$(D shared).))
+
+)
+
+The example below features an allocator modeled after $(WEB goo.gl/m7329l,
+jemalloc), which uses a battery of free-list allocators spaced so as to keep
+internal fragmentation to a minimum. The $(D FList) definitions specify no
+bounds for the freelist because the $(D Segregator) does all size selection in
+advance.
+
+Sizes through 3584 bytes are handled via freelists of staggered sizes. Sizes
+from 3585 bytes through 4072 KB are handled by a $(D HeapBlock) with a
+block size of 4 KB. Sizes above that are passed direct to the $(D Mallocator).
+
+----
+ alias FList = Freelist!(GCAllocator, 0, unbounded);
+ alias A = Segregator!(
+ 8, Freelist!(GCAllocator, 0, 8),
+ 128, Bucketizer!(FList, 1, 128, 16),
+ 256, Bucketizer!(FList, 129, 256, 32),
+ 512, Bucketizer!(FList, 257, 512, 64),
+ 1024, Bucketizer!(FList, 513, 1024, 128),
+ 2048, Bucketizer!(FList, 1025, 2048, 256),
+ 3584, Bucketizer!(FList, 2049, 3584, 512),
+ 4072 * 1024, CascadingAllocator!(
+ () => HeapBlock!(GCAllocator, 4096)(4072 * 1024)),
+ GCAllocator
+ );
+ A tuMalloc;
+ auto b = tuMalloc.allocate(500);
+ assert(b.length == 500);
+ auto c = tuMalloc.allocate(113);
+ assert(c.length == 113);
+ assert(tuMalloc.expand(c, 14));
+ tuMalloc.deallocate(b);
+ tuMalloc.deallocate(c);
+----
+
+$(H2 Allocating memory for sharing across threads)
+
+One allocation pattern used in multithreaded applications is to share memory
+across threads, and to deallocate blocks in a different thread than the one that
+allocated it.
+
+All allocators in this module accept and return $(D void[]) (as opposed to
+$(D shared void[])). This is because at the time of allocation, deallocation, or
+reallocation, the memory is effectively not $(D shared) (if it were, it would
+reveal a bug at the application level).
+
+The issue remains of calling $(D a.deallocate(b)) from a different thread than
+the one that allocated $(D b). It follows that both threads must have access to
+the same instance $(D a) of the respective allocator type. By definition of D,
+this is possible only if $(D a) has the $(D shared) qualifier. It follows that
+the allocator type must implement $(D allocate) and $(D deallocate) as $(D
+shared) methods. That way, the allocator commits to allowing usable $(D shared)
+instances.
+
+Conversely, allocating memory with one non-$(D shared) allocator, passing it
+across threads (by casting the obtained buffer to $(D shared)), and later
+deallocating it in a different thread (either with a different allocator object
+or with the same allocator object after casting it to $(D shared)) is illegal.
+
+$(BOOKTABLE $(BIG Synopsis of predefined _allocator building blocks),
+$(TR $(TH Allocator) $(TH Description))
+
+$(TR $(TDC2 NullAllocator) $(TD Very good at doing absolutely nothing. A good
+starting point for defining other allocators or for studying the API.))
+
+$(TR $(TDC2 GCAllocator) $(TD The system-provided garbage-collector allocator.
+This should be the default fallback allocator tapping into system memory. It
+offers manual $(D free) and dutifully collects litter.))
+
+$(TR $(TDC2 Mallocator) $(TD The C heap _allocator, a.k.a. $(D
+malloc)/$(D realloc)/$(D free). Use sparingly and only for code that is unlikely
+to leak.))
+
+$(TR $(TDC2 AlignedMallocator) $(TD Interface to OS-specific _allocators that
+support specifying alignment:
+$(WEB man7.org/linux/man-pages/man3/posix_memalign.3.html, $(D posix_memalign))
+on Posix and $(WEB msdn.microsoft.com/en-us/library/fs9stz4e(v=vs.80).aspx,
+$(D __aligned_xxx)) on Windows.))
+
+$(TR $(TDC2 AffixAllocator) $(TD Allocator that allows and manages allocating
+extra prefix and/or a suffix bytes for each block allocated.))
+
+$(TR $(TDC2 HeapBlock) $(TD Organizes one contiguous chunk of memory in
+equal-size blocks and tracks allocation status at the cost of one bit per
+block.))
+
+$(TR $(TDC2 FallbackAllocator) $(TD Allocator that combines two other allocators
+ - primary and fallback. Allocation requests are first tried with primary, and
+ upon failure are passed to the fallback. Useful for small and fast allocators
+ fronting general-purpose ones.))
+
+$(TR $(TDC2 Freelist) $(TD Allocator that implements a $(WEB
+wikipedia.org/wiki/Free_list, free list) on top of any other allocator. The
+preferred size, tolerance, and maximum elements are configurable at compile- and
+run time.))
+
+$(TR $(TDC2 SharedFreelist) $(TD Same features as $(D Freelist), but packaged as
+a $(D shared) structure that is accessible to several threads.))
+
+$(TR $(TDC2 Region) $(TD Region allocator organizes a chunk of memory as a
+simple bump-the-pointer allocator.))
+
+$(TR $(TDC2 InSituRegion) $(TD Region holding its own allocation, most often on
+the stack. Has statically-determined size.))
+
+$(TR $(TDC2 AllocatorWithStats) $(TD Collect statistics about any other
+allocator.))
+
+$(TR $(TDC2 CascadingAllocator) $(TD Given an allocator factory, lazily creates as
+many allocators as needed to satisfy allocation requests. The allocators are
+stored in a linked list. Requests for allocation are satisfied by searching the
+list in a linear manner.))
+
+$(TR $(TDC2 Segregator) $(TD Segregates allocation requests by size and
+dispatches them to distinct allocators.))
+
+$(TR $(TDC2 Bucketizer) $(TD Divides allocation sizes in discrete buckets and
+uses an array of allocators, one per bucket, to satisfy requests.))
+
+)
+ */
+
+module stdx.allocator;
+
+// Example in the synopsis above
+unittest
+{
+ alias FList = Freelist!(GCAllocator, 0, unbounded);
+ alias A = Segregator!(
+ 8, Freelist!(GCAllocator, 0, 8),
+ 128, Bucketizer!(FList, 1, 128, 16),
+ 256, Bucketizer!(FList, 129, 256, 32),
+ 512, Bucketizer!(FList, 257, 512, 64),
+ 1024, Bucketizer!(FList, 513, 1024, 128),
+ 2048, Bucketizer!(FList, 1025, 2048, 256),
+ 3584, Bucketizer!(FList, 2049, 3584, 512),
+ 4072 * 1024, CascadingAllocator!(
+ () => HeapBlock!(GCAllocator, 4096)(4072 * 1024)),
+ GCAllocator
+ );
+ A tuMalloc;
+ auto b = tuMalloc.allocate(500);
+ assert(b.length == 500);
+ auto c = tuMalloc.allocate(113);
+ assert(c.length == 113);
+ assert(tuMalloc.expand(c, 14));
+ tuMalloc.deallocate(b);
+ tuMalloc.deallocate(c);
+}
+
+import std.algorithm, std.conv, std.exception, std.range, std.traits,
+ std.typecons, std.typetuple;
+version(unittest) import std.stdio;
+
+/*
+Ternary by Timon Gehr and Andrei Alexandrescu.
+*/
+private struct Ternary
+{
+ private ubyte value = 6;
+ private static Ternary make(ubyte b)
+ {
+ Ternary r = void;
+ r.value = b;
+ return r;
+ }
+
+ enum no = make(0), yes = make(2), unknown = make(6);
+
+ this(bool b) { value = b << 1; }
+
+ void opAssign(bool b) { value = b << 1; }
+
+ Ternary opUnary(string s)() if (s == "~")
+ {
+ return make(386 >> value & 6);
+ }
+
+ Ternary opBinary(string s)(Ternary rhs) if (s == "|")
+ {
+ return make(25512 >> value + rhs.value & 6);
+ }
+
+ Ternary opBinary(string s)(Ternary rhs) if (s == "&")
+ {
+ return make(26144 >> value + rhs.value & 6);
+ }
+
+ Ternary opBinary(string s)(Ternary rhs) if (s == "^")
+ {
+ return make(26504 >> value + rhs.value & 6);
+ }
+}
+
+unittest
+{
+ alias f = Ternary.no, t = Ternary.yes, u = Ternary.unknown;
+ auto truthTableAnd =
+ [
+ t, t, t,
+ t, u, u,
+ t, f, f,
+ u, t, u,
+ u, u, u,
+ u, f, f,
+ f, t, f,
+ f, u, f,
+ f, f, f,
+ ];
+
+ auto truthTableOr =
+ [
+ t, t, t,
+ t, u, t,
+ t, f, t,
+ u, t, t,
+ u, u, u,
+ u, f, u,
+ f, t, t,
+ f, u, u,
+ f, f, f,
+ ];
+
+ auto truthTableXor =
+ [
+ t, t, f,
+ t, u, u,
+ t, f, t,
+ u, t, u,
+ u, u, u,
+ u, f, u,
+ f, t, t,
+ f, u, u,
+ f, f, f,
+ ];
+
+ for (auto i = 0; i != truthTableAnd.length; i += 3)
+ {
+ assert((truthTableAnd[i] & truthTableAnd[i + 1])
+ == truthTableAnd[i + 2]);
+ assert((truthTableOr[i] | truthTableOr[i + 1])
+ == truthTableOr[i + 2]);
+ assert((truthTableXor[i] ^ truthTableXor[i + 1])
+ == truthTableXor[i + 2]);
+ }
+
+ Ternary a;
+ assert(a == Ternary.unknown);
+ static assert(!is(typeof({ if (a) {} })));
+ assert(!is(typeof({ auto b = Ternary(3); })));
+ a = true;
+ assert(a == Ternary.yes);
+ a = false;
+ assert(a == Ternary.no);
+ a = Ternary.unknown;
+ assert(a == Ternary.unknown);
+ Ternary b;
+ b = a;
+ assert(b == a);
+ assert(~Ternary.yes == Ternary.no);
+ assert(~Ternary.no == Ternary.yes);
+ assert(~Ternary.unknown == Ternary.unknown);
+}
+
+/**
+Returns the size in bytes of the state that needs to be allocated to hold an
+object of type $(D T). $(D stateSize!T) is zero for $(D struct)s that are not
+nested and have no nonstatic member variables.
+ */
+private template stateSize(T)
+{
+ static if (is(T == class) || is(T == interface))
+ enum stateSize = __traits(classInstanceSize, T);
+ else static if (is(T == struct) || is(T == union))
+ enum stateSize = FieldTypeTuple!T.length || isNested!T ? T.sizeof : 0;
+ else static if (is(T == void))
+ enum size_t stateSize = 0;
+ else
+ enum stateSize = T.sizeof;
+}
+
+unittest
+{
+ static assert(stateSize!void == 0);
+ struct A {}
+ static assert(stateSize!A == 0);
+ struct B { int x; }
+ static assert(stateSize!B == 4);
+ interface I1 {}
+ static assert(stateSize!I1 == 2 * size_t.sizeof);
+ class C1 {}
+ static assert(stateSize!C1 == 3 * size_t.sizeof);
+ class C2 { char c; }
+ static assert(stateSize!C2 == 4 * size_t.sizeof);
+ static class C3 { char c; }
+ static assert(stateSize!C3 == 2 * size_t.sizeof + char.sizeof);
+}
+
+/**
+$(D chooseAtRuntime) is a compile-time constant of type $(D size_t) that several
+parameterized structures in this module recognize to mean deferral to runtime of
+the exact value. For example, $(D HeapBlock!(Allocator, 4096)) (described in
+detail below) defines a block allocator with block size of 4096 bytes, whereas
+$(D HeapBlock!(Allocator, chooseAtRuntime)) defines a block allocator that has a
+field storing the block size, initialized by the user.
+*/
+enum chooseAtRuntime = size_t.max - 1;
+
+/**
+$(D unbounded) is a compile-time constant of type $(D size_t) that several
+parameterized structures in this module recognize to mean "infinite" bounds for
+the parameter. For example, $(D Freelist) (described in detail below) accepts a
+$(D maxNodes) parameter limiting the number of freelist items. If $(D unbounded)
+is passed for $(D maxNodes), then there is no limit and no checking for the
+number of nodes.
+*/
+enum unbounded = size_t.max;
+
+/**
+The alignment that is guaranteed to accommodate any D object allocation on the
+current platform.
+*/
+enum uint platformAlignment = std.algorithm.max(double.alignof, real.alignof);
+
+/**
+The default good size allocation is deduced as $(D n) rounded up to the
+allocator's alignment.
+*/
+size_t goodAllocSize(A)(auto ref A a, size_t n)
+{
+ return n.roundUpToMultipleOf(a.alignment);
+}
+
+/**
+The default $(D reallocate) function first attempts to use $(D expand). If $(D
+Allocator.expand) is not defined or returns $(D false), $(D reallocate)
+allocates a new block of memory of appropriate size and copies data from the old
+block to the new block. Finally, if $(D Allocator) defines $(D deallocate), $(D
+reallocate) uses it to free the old memory block.
+
+$(D reallocate) does not attempt to use $(D Allocator.reallocate) even if
+defined. This is deliberate so allocators may use it internally within their own
+implementation of $(D reallocate).
+
+*/
+bool reallocate(Allocator)(ref Allocator a, ref void[] b, size_t s)
+{
+ if (b.length == s) return true;
+ static if (hasMember!(Allocator, "expand"))
+ {
+ if (b.length <= s && a.expand(b, s - b.length)) return true;
+ }
+ auto newB = a.allocate(s);
+ if (newB.length <= b.length) newB[] = b[0 .. newB.length];
+ else newB[0 .. b.length] = b[];
+ static if (hasMember!(Allocator, "deallocate"))
+ a.deallocate(b);
+ b = newB;
+ return true;
+}
+
+/*
+ _ _ _ _ _ _ _
+ | \ | | | | | /\ | | | | |
+ | \| |_ _| | | / \ | | | ___ ___ __ _| |_ ___ _ __
+ | . ` | | | | | | / /\ \ | | |/ _ \ / __/ _` | __/ _ \| '__|
+ | |\ | |_| | | |/ ____ \| | | (_) | (_| (_| | || (_) | |
+ |_| \_|\__,_|_|_/_/ \_\_|_|\___/ \___\__,_|\__\___/|_|
+*/
+/**
+$(D NullAllocator) is an emphatically empty implementation of the allocator interface. Although it has no direct use, it is useful as a "terminator" in composite allocators.
+*/
+struct NullAllocator
+{
+ /**
+ $(D NullAllocator) advertises a relatively large _alignment equal to 64 KB.
+ This is because $(D NullAllocator) never actually needs to honor this
+ alignment and because composite allocators using $(D NullAllocator)
+ shouldn't be unnecessarily constrained.
+ */
+ enum uint alignment = 64 * 1024;
+ /// Always returns $(D null).
+ void[] allocate(size_t) shared { return null; }
+ /// Returns $(D b is null).
+ bool owns(void[] b) shared { return b is null; }
+ /**
+ These methods return $(D false).
+ Precondition: $(D b is null). This is because there is no other possible
+ legitimate input.
+ */
+ bool expand(ref void[] b, size_t) shared
+ { assert(b is null); return false; }
+ /// Ditto
+ bool reallocate(ref void[] b, size_t) shared
+ { assert(b is null); return false; }
+ /**
+ No-op.
+ Precondition: $(D b is null)
+ */
+ void deallocate(void[] b) shared { assert(b is null); }
+ /**
+ No-op.
+ */
+ void deallocateAll() shared { }
+ /**
+ Returns the $(D shared) global instance of the $(D NullAllocator).
+ */
+ static shared NullAllocator it;
+}
+
+unittest
+{
+ auto b = NullAllocator.it.allocate(100);
+ assert(b is null);
+ NullAllocator.it.deallocate(b);
+ NullAllocator.it.deallocateAll();
+ assert(NullAllocator.it.owns(null));
+}
+
+/**
+D's built-in garbage-collected allocator.
+ */
+struct GCAllocator
+{
+ private import core.memory;
+
+ /**
+ The alignment is a static constant equal to $(D platformAlignment), which
+ ensures proper alignment for any D data type.
+ */
+ enum uint alignment = platformAlignment;
+
+ /**
+ Standard allocator methods per the semantics defined above. The $(D deallocate) and $(D reallocate) methods are $(D @system) because they may move memory around, leaving dangling pointers in user code.
+ */
+ @trusted void[] allocate(size_t bytes) shared
+ {
+ auto p = GC.malloc(bytes);
+ return p ? p[0 .. bytes] : null;
+ }
+
+ /// Ditto
+ @trusted bool expand(ref void[] b, size_t delta) shared
+ {
+ auto newSize = GC.extend(b.ptr, b.length + delta,
+ b.length + delta);
+ if (newSize == 0)
+ {
+ // expansion unsuccessful
+ return false;
+ }
+ assert(newSize >= b.length + delta);
+ b = b.ptr[0 .. newSize];
+ return true;
+ }
+
+ /// Ditto
+ @system bool reallocate(ref void[] b, size_t newSize) shared
+ {
+ import core.exception : OutOfMemoryError;
+ try
+ {
+ auto p = cast(ubyte*) GC.realloc(b.ptr, newSize);
+ b = p[0 .. newSize];
+ }
+ catch (OutOfMemoryError)
+ {
+ // leave the block in place, tell caller
+ return false;
+ }
+ return true;
+ }
+
+ /// Ditto
+ @system void deallocate(void[] b) shared
+ {
+ GC.free(b.ptr);
+ }
+
+ /**
+ Returns the global instance of this allocator type. The garbage collected allocator is thread-safe, therefore all of its methods and $(D it) itself are $(D shared).
+ */
+ static shared GCAllocator it;
+
+ // Leave it undocummented for now.
+ @trusted void collect() shared
+ {
+ GC.collect();
+ }
+}
+
+///
+unittest
+{
+ auto buffer = GCAllocator.it.allocate(1024 * 1024 * 4);
+ scope(exit) GCAllocator.it.deallocate(buffer); // or leave it to collection
+ //...
+}
+
+unittest
+{
+ auto b = GCAllocator.it.allocate(10000);
+ assert(GCAllocator.it.expand(b, 1));
+}
+
+/**
+ The C heap allocator.
+ */
+struct Mallocator
+{
+ private import core.stdc.stdlib;
+
+ /**
+ The alignment is a static constant equal to $(D platformAlignment), which ensures proper alignment for any D data type.
+ */
+ enum uint alignment = platformAlignment;
+
+ /**
+ Standard allocator methods per the semantics defined above. The $(D deallocate) and $(D reallocate) methods are $(D @system) because they may move memory around, leaving dangling pointers in user code. Somewhat paradoxically, $(D malloc) is $(D @safe) but that's only useful to safe programs that can afford to leak memory allocated.
+ */
+ @trusted void[] allocate(size_t bytes) shared
+ {
+ auto p = malloc(bytes);
+ return p ? p[0 .. bytes] : null;
+ }
+
+ /// Ditto
+ @system void deallocate(void[] b) shared
+ {
+ free(b.ptr);
+ }
+
+ /// Ditto
+ @system bool reallocate(ref void[] b, size_t s) shared
+ {
+ if (!s)
+ {
+ // fuzzy area in the C standard, see http://goo.gl/ZpWeSE
+ // so just deallocate and nullify the pointer
+ deallocate(b);
+ b = null;
+ return true;
+ }
+ auto p = cast(ubyte*) realloc(b.ptr, s);
+ if (!p) return false;
+ b = p[0 .. s];
+ return true;
+ }
+
+ /**
+ Returns the global instance of this allocator type. The C heap allocator is thread-safe, therefore all of its methods and $(D it) itself are $(D shared).
+ */
+ static shared Mallocator it;
+}
+
+///
+unittest
+{
+ auto buffer = Mallocator.it.allocate(1024 * 1024 * 4);
+ scope(exit) Mallocator.it.deallocate(buffer);
+ //...
+}
+
+unittest
+{
+ static void test(A)()
+ {
+ int* p = null;
+ p = new int;
+ *p = 42;
+ assert(*p == 42);
+ }
+
+ test!GCAllocator();
+ test!Mallocator();
+}
+
+unittest
+{
+ static void test(A)()
+ {
+ Object p = null;
+ p = new Object;
+ assert(p !is null);
+ }
+
+ test!GCAllocator();
+ test!Mallocator();
+}
+
+version (Posix) extern(C) int posix_memalign(void**, size_t, size_t);
+version (Windows)
+{
+ extern(C) void* _aligned_malloc(size_t, size_t);
+ extern(C) void _aligned_free(void *memblock);
+ extern(C) void* _aligned_realloc(void *, size_t, size_t);
+}
+
+/**
+ Aligned allocator using OS-specific primitives, under a uniform API.
+ */
+struct AlignedMallocator
+{
+ private import core.stdc.stdlib;
+
+ /**
+ The default alignment is $(D platformAlignment).
+ */
+ enum uint alignment = platformAlignment;
+
+ /**
+ Forwards to $(D alignedAllocate(bytes, platformAlignment)).
+ */
+ @trusted void[] allocate(size_t bytes) shared
+ {
+ return alignedAllocate(bytes, alignment);
+ }
+
+ version (Posix) import core.stdc.errno, core.sys.posix.stdlib;
+
+ /**
+ Uses $(WEB man7.org/linux/man-pages/man3/posix_memalign.3.html,
+ $(D posix_memalign)) on Posix and
+ $(WEB msdn.microsoft.com/en-us/library/8z34s9c6(v=vs.80).aspx,
+ $(D __aligned_malloc)) on Windows.
+ */
+ version(Posix) @trusted
+ void[] alignedAllocate(size_t bytes, uint a) shared
+ {
+ assert(a.isGoodDynamicAlignment);
+ void* result;
+ auto code = posix_memalign(&result, a, bytes);
+ if (code == ENOMEM) return null;
+ enforce(code == 0, text("Invalid alignment requested: ", a));
+ return result[0 .. bytes];
+ }
+ else version(Windows) @trusted
+ void[] alignedAllocate(size_t bytes, uint a) shared
+ {
+ auto result = _aligned_malloc(bytes, a);
+ return result ? result[0 .. bytes] : null;
+ }
+ else static assert(0);
+
+ /**
+ Calls $(D free(b.ptr)) on Posix and
+ $(WEB msdn.microsoft.com/en-US/library/17b5h8td(v=vs.80).aspx,
+ $(D __aligned_free(b.ptr))) on Windows.
+ */
+ version (Posix) @system
+ void deallocate(void[] b) shared
+ {
+ free(b.ptr);
+ }
+ else version (Windows) @system
+ void deallocate(void[] b) shared
+ {
+ _aligned_free(b.ptr);
+ }
+ else static assert(0);
+
+ /**
+ On Posix, forwards to $(D realloc). On Windows, forwards to
+ $(D alignedReallocate(b, newSize, platformAlignment)).
+ */
+ version (Posix) @system bool reallocate(ref void[] b, size_t newSize) shared
+ {
+ return Mallocator.it.reallocate(b, newSize);
+ }
+ version (Windows) @system
+ bool reallocate(ref void[] b, size_t newSize) shared
+ {
+ return alignedReallocate(b, newSize, alignment);
+ }
+
+ /**
+ On Posix, uses $(D alignedAllocate) and copies data around because there is
+ no realloc for aligned memory. On Windows, calls
+ $(WEB msdn.microsoft.com/en-US/library/y69db7sx(v=vs.80).aspx,
+ $(D __aligned_realloc(b.ptr, newSize, a))).
+ */
+ version (Posix) @system
+ bool alignedReallocate(ref void[] b, size_t s, uint a) shared
+ {
+ if (!s)
+ {
+ deallocate(b);
+ b = null;
+ return true;
+ }
+ auto result = alignedAllocate(s, a);
+ if (!result) return false;
+ if (s < b.length) result[] = b[0 .. s];
+ else result[0 .. b.length] = b[];
+ deallocate(b);
+ b = result;
+ return true;
+ }
+ else version (Windows) @system
+ bool alignedReallocate(ref void[] b, size_t s, uint a) shared
+ {
+ if (!s)
+ {
+ deallocate(b);
+ b = null;
+ return true;
+ }
+ auto p = cast(ubyte*) _aligned_realloc(b.ptr, s, a);
+ if (!p) return false;
+ b = p[0 .. s];
+ return true;
+ }
+
+ /**
+ Returns the global instance of this allocator type. The C heap allocator is thread-safe, therefore all of its methods and $(D it) itself are $(D shared).
+ */
+ static shared AlignedMallocator it;
+}
+
+///
+unittest
+{
+ auto buffer = AlignedMallocator.it.alignedAllocate(1024 * 1024 * 4, 128);
+ scope(exit) AlignedMallocator.it.deallocate(buffer);
+ //...
+}
+
+/**
+Returns s rounded up to a multiple of base.
+*/
+private size_t roundUpToMultipleOf(size_t s, uint base)
+{
+ assert(base);
+ auto rem = s % base;
+ return rem ? s + base - rem : s;
+}
+
+unittest
+{
+ assert(10.roundUpToMultipleOf(11) == 11);
+ assert(11.roundUpToMultipleOf(11) == 11);
+ assert(12.roundUpToMultipleOf(11) == 22);
+ assert(118.roundUpToMultipleOf(11) == 121);
+}
+
+/**
+Returns s rounded up to a multiple of base.
+*/
+private void[] roundStartToMultipleOf(void[] s, uint base)
+{
+ assert(base);
+ auto p = cast(void*) roundUpToMultipleOf(
+ cast(size_t) s.ptr, base);
+ auto end = s.ptr + s.length;
+ return p[0 .. end - p];
+}
+
+unittest
+{
+ void[] p;
+ assert(roundStartToMultipleOf(p, 16) is null);
+ p = new ulong[10];
+ assert(roundStartToMultipleOf(p, 16) is p);
+}
+
+/**
+Returns $(D s) rounded up to the nearest power of 2.
+*/
+private size_t roundUpToPowerOf2(size_t s)
+{
+ assert(s <= (size_t.max >> 1) + 1);
+ --s;
+ static if (size_t.sizeof == 4)
+ alias Shifts = TypeTuple!(1, 2, 4, 8, 16);
+ else
+ alias Shifts = TypeTuple!(1, 2, 4, 8, 16, 32);
+ foreach (i; Shifts)
+ {
+ s |= s >> i;
+ }
+ return s + 1;
+}
+
+unittest
+{
+ assert(0.roundUpToPowerOf2 == 0);
+ assert(1.roundUpToPowerOf2 == 1);
+ assert(2.roundUpToPowerOf2 == 2);
+ assert(3.roundUpToPowerOf2 == 4);
+ assert(7.roundUpToPowerOf2 == 8);
+ assert(8.roundUpToPowerOf2 == 8);
+ assert(10.roundUpToPowerOf2 == 16);
+ assert(11.roundUpToPowerOf2 == 16);
+ assert(12.roundUpToPowerOf2 == 16);
+ assert(118.roundUpToPowerOf2 == 128);
+ assert((size_t.max >> 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1);
+ assert(((size_t.max >> 1) + 1).roundUpToPowerOf2 == (size_t.max >> 1) + 1);
+}
+
+/**
+
+Allocator that adds some extra data before (of type $(D Prefix)) and/or after
+(of type $(D Suffix)) any allocation made with its parent allocator. This is
+useful for uses where additional allocation-related information is needed, such
+as mutexes, reference counts, or walls for debugging memory corruption errors.
+
+If $(D Prefix) is not $(D void), $(D Allocator) must guarantee an alignment at
+least as large as $(D Prefix.alignof).
+
+Suffixes are slower to get at because of alignment rounding, so prefixes should
+be preferred. However, small prefixes blunt the alignment so if a large
+alignment with a small affix is needed, suffixes should be chosen.
+
+ */
+struct AffixAllocator(Allocator, Prefix, Suffix = void)
+{
+ static assert(
+ !stateSize!Prefix || Allocator.alignment >= Prefix.alignof,
+ "AffixAllocator does not work with allocators offering a smaller"
+ " alignment than the prefix alignment.");
+ static assert(alignment % Suffix.alignof == 0,
+ "This restriction could be relaxed in the future.");
+
+ /**
+ If $(D Prefix) is $(D void), the alignment is that of the parent. Otherwise, the alignment is the same as the $(D Prefix)'s alignment.
+ */
+ enum uint alignment =
+ stateSize!Prefix ? Allocator.alignment : Prefix.alignof;
+
+ /**
+ If the parent allocator $(D Allocator) is stateful, an instance of it is
+ stored as a member. Otherwise, $(D AffixAllocator) uses $(D Allocator.it).
+ In either case, the name $(D _parent) is uniformly used for accessing the
+ parent allocator.
+ */
+ static if (stateSize!Allocator) Allocator parent;
+ else alias Allocator.it parent;
+
+ template Impl()
+ {
+ size_t goodAllocSize(size_t s)
+ {
+ return parent.goodAllocSize(actualAllocationSize(s));
+ }
+
+ private size_t actualAllocationSize(size_t s) const
+ {
+ static if (!stateSize!Suffix)
+ {
+ return s + stateSize!Prefix;
+ }
+ else
+ {
+ return roundUpToMultipleOf(
+ s + stateSize!Prefix,
+ Suffix.alignof) + stateSize!Suffix;
+ }
+ }
+
+ private void[] actualAllocation(void[] b) const
+ {
+ assert(b !is null);
+ return (b.ptr - stateSize!Prefix)
+ [0 .. actualAllocationSize(b.length)];
+ }
+
+ void[] allocate(size_t bytes)
+ {
+ auto result = parent.allocate(actualAllocationSize(bytes));
+ if (result is null) return null;
+ static if (stateSize!Prefix)
+ emplace!Prefix(cast(Prefix*)result.ptr);
+ static if (stateSize!Suffix)
+ emplace!Suffix(
+ cast(Suffix*)(result.ptr + result.length - Suffix.sizeof));
+ return result[stateSize!Prefix .. stateSize!Prefix + bytes];
+ }
+
+ static if (hasMember!(Allocator, "owns"))
+ bool owns(void[] b)
+ {
+ return b is null ? true : parent.owns(actualAllocation(b));
+ }
+
+ static if (!stateSize!Suffix && hasMember!(Allocator, "expand"))
+ bool expand(ref void[] b, size_t delta)
+ {
+ auto t = actualAllocation(b);
+ auto result = parent.expand(t, delta);
+ if (!result) return false;
+ b = b.ptr[0 .. b.length + delta];
+ return true;
+ }
+
+ static if (hasMember!(Allocator, "reallocate"))
+ bool reallocate(ref void[] b, size_t s)
+ {
+ auto t = actualAllocation(b);
+ auto result = parent.reallocate(t, actualAllocationSize(s));
+ if (!result) return false; // no harm done
+ b = t.ptr[stateSize!Prefix .. stateSize!Prefix + s];
+ return true;
+ }
+
+ static if (hasMember!(Allocator, "deallocate"))
+ void deallocate(void[] b)
+ {
+ auto p = b.ptr - stateSize!Prefix;
+ parent.deallocate(p[0 .. actualAllocationSize(b.length)]);
+ }
+
+ static if (hasMember!(Allocator, "deallocateAll"))
+ void deallocateAll()
+ {
+ parent.deallocateAll();
+ }
+
+ // Extra functions
+ static if (stateSize!Prefix)
+ static ref Prefix prefix(void[] b)
+ {
+ return (cast(Prefix*)b.ptr)[-1];
+ }
+ static if (stateSize!Suffix)
+ ref Suffix suffix(void[] b)
+ {
+ auto p = b.ptr - stateSize!Prefix
+ + actualAllocationSize(b.length);
+ return (cast(Prefix*) p)[-1];
+ }
+ }
+
+ version (StdDdoc)
+ {
+ /**
+ Standard allocator methods. Each is defined if and only if the parent
+ allocator defines the homonym method (except for $(D goodAllocSize),
+ which may use the global default). Also, the methods will be $(D
+ shared) if the parent allocator defines them as such.
+ */
+ size_t goodAllocSize(size_t);
+ /// Ditto
+ void[] allocate(size_t);
+ /// Ditto
+ bool owns(void[]);
+ /// Ditto
+ bool expand(ref void[] b, size_t delta);
+ /// Ditto
+ bool reallocate(ref void[] b, size_t s);
+ /// Ditto
+ void deallocate(void[] b);
+ /// Ditto
+ void deallocateAll();
+
+ /**
+ The $(D it) singleton is defined if and only if the parent allocator has no state and defines its own $(D it) object.
+ */
+ static AffixAllocator it;
+
+ /**
+ Affix access functions offering mutable references to the affixes of a block previously allocated with this allocator. $(D b) may not be null. They are defined if and only if the corresponding affix is not $(D void).
+
+ Precondition: $(D b !is null)
+ */
+ static ref Prefix prefix(void[] b);
+ /// Ditto
+ static ref Suffix suffix(void[] b);
+ }
+ else static if (is(typeof(Allocator.it) == shared))
+ {
+ static shared AffixAllocator it;
+ shared { mixin Impl!(); }
+ }
+ else
+ {
+ mixin Impl!();
+ static if (stateSize!Allocator == 0)
+ static __gshared AffixAllocator it;
+ }
+}
+
+///
+unittest
+{
+ // One word before and after each allocation.
+ alias A = AffixAllocator!(Mallocator, size_t, size_t);
+ auto b = A.it.allocate(11);
+ A.it.prefix(b) = 0xCAFE_BABE;
+ A.it.suffix(b) = 0xDEAD_BEEF;
+ assert(A.it.prefix(b) == 0xCAFE_BABE && A.it.suffix(b) == 0xDEAD_BEEF);
+}
+
+unittest
+{
+ alias AffixAllocator!(Mallocator, size_t) A;
+ auto b = A.it.allocate(10);
+ A.it.prefix(b) = 10;
+ assert(A.it.prefix(b) == 10);
+
+ alias B = AffixAllocator!(NullAllocator, size_t);
+ b = B.it.allocate(100);
+ assert(b is null);
+}
+
+/**
+Returns the number of most significant ones before a zero can be found in $(D x). If $(D x) contains no zeros (i.e. is equal to $(D ulong.max)), returns 64.
+*/
+private uint leadingOnes(ulong x)
+{
+ uint result = 0;
+ while (cast(long) x < 0)
+ {
+ ++result;
+ x <<= 1;
+ }
+ return result;
+}
+
+unittest
+{
+ assert(leadingOnes(0) == 0);
+ assert(leadingOnes(~0UL) == 64);
+ assert(leadingOnes(0xF000_0000_0000_0000) == 4);
+ assert(leadingOnes(0xE400_0000_0000_0000) == 3);
+ assert(leadingOnes(0xC700_0200_0000_0000) == 2);
+ assert(leadingOnes(0x8000_0030_0000_0000) == 1);
+ assert(leadingOnes(0x2000_0000_0000_0000) == 0);
+}
+
+/**
+Finds a run of contiguous ones in $(D x) of length at least $(D n).
+*/
+private uint findContigOnes(ulong x, uint n)
+{
+ while (n > 1)
+ {
+ immutable s = n >> 1;
+ x &= x << s;
+ n -= s;
+ }
+ return leadingOnes(~x);
+}
+
+unittest
+{
+ assert(findContigOnes(0x0000_0000_0000_0300, 2) == 54);
+
+ assert(findContigOnes(~0UL, 1) == 0);
+ assert(findContigOnes(~0UL, 2) == 0);
+ assert(findContigOnes(~0UL, 32) == 0);
+ assert(findContigOnes(~0UL, 64) == 0);
+ assert(findContigOnes(0UL, 1) == 64);
+
+ assert(findContigOnes(0x4000_0000_0000_0000, 1) == 1);
+ assert(findContigOnes(0x0000_0F00_0000_0000, 4) == 20);
+}
+
+/**
+Returns the number of trailing zeros of $(D x).
+*/
+private uint trailingZeros(ulong x)
+{
+ uint result;
+ while (result < 64 && !(x & (1UL << result)))
+ {
+ ++result;
+ }
+ return result;
+}
+
+unittest
+{
+ assert(trailingZeros(0) == 64);
+ assert(trailingZeros(1) == 0);
+ assert(trailingZeros(2) == 1);
+ assert(trailingZeros(3) == 0);
+ assert(trailingZeros(4) == 2);
+}
+
+/*
+Unconditionally sets the bits from lsb through msb in w to zero.
+*/
+private void setBits(ref ulong w, uint lsb, uint msb)
+{
+ assert(lsb <= msb && msb < 64);
+ const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
+ w |= mask;
+}
+
+unittest
+{
+ ulong w;
+ w = 0; setBits(w, 0, 63); assert(w == ulong.max);
+ w = 0; setBits(w, 1, 63); assert(w == ulong.max - 1);
+ w = 6; setBits(w, 0, 1); assert(w == 7);
+ w = 6; setBits(w, 3, 3); assert(w == 14);
+}
+
+/* Are bits from lsb through msb in w zero? If so, make then 1
+and return the resulting w. Otherwise, just return 0.
+*/
+private bool setBitsIfZero(ref ulong w, uint lsb, uint msb)
+{
+ assert(lsb <= msb && msb < 64);
+ const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
+ if (w & mask) return false;
+ w |= mask;
+ return true;
+}
+
+// Assigns bits in w from lsb through msb to zero.
+private void resetBits(ref ulong w, uint lsb, uint msb)
+{
+ assert(lsb <= msb && msb < 64);
+ const mask = (ulong.max << lsb) & (ulong.max >> (63 - msb));
+ w &= ~mask;
+}
+
+/**
+
+$(D HeapBlock) implements a simple heap consisting of one contiguous area
+of memory organized in blocks, each of size $(D theBlockSize). A block is a unit
+of allocation. A bitmap serves as bookkeeping data, more precisely one bit per
+block indicating whether that block is currently allocated or not.
+
+There are advantages to storing bookkeeping data separated from the payload
+(as opposed to e.g. $(D AffixAllocator)). The layout is more compact, searching
+for a free block during allocation enjoys better cache locality, and
+deallocation does not touch memory around the payload being deallocated (which
+is often cold).
+
+Allocation requests are handled on a first-fit basis. Although linear in
+complexity, allocation is in practice fast because of the compact bookkeeping
+representation, use of simple and fast bitwise routines, and memoization of the
+first available block position. A known issue with this general approach is
+fragmentation, partially mitigated by coalescing. Since $(D HeapBlock) does
+not maintain the allocated size, freeing memory implicitly coalesces free blocks
+together. Also, tuning $(D blockSize) has a considerable impact on both internal
+and external fragmentation.
+
+The size of each block can be selected either during compilation or at run
+time. Statically-known block sizes are frequent in practice and yield slightly
+better performance. To choose a block size statically, pass it as the $(D
+blockSize) parameter as in $(D HeapBlock!(Allocator, 4096)). To choose a block
+size parameter, use $(D HeapBlock!(Allocator, chooseAtRuntime)) and pass the
+block size to the constructor.
+
+TODO: implement $(D alignedAllocate) and $(D alignedReallocate).
+
+*/
+struct HeapBlock(Allocator, size_t theBlockSize,
+ size_t theAlignment = platformAlignment)
+{
+ static assert(theBlockSize > 0 && theAlignment.isGoodStaticAlignment);
+
+ /**
+ Parent allocator. If it has no state, $(D parent) is an alias for $(D
+ Allocator.it).
+ */
+ static if (stateSize!Allocator) Allocator parent;
+ else alias parent = Allocator.it;
+
+ /**
+ If $(D blockSize == chooseAtRuntime), $(D HeapBlock) offers a read/write
+ property $(D blockSize). It must be set to a power of two before any use
+ of the allocator. Otherwise, $(D blockSize) is an alias for $(D
+ theBlockSize).
+ */
+ static if (theBlockSize != chooseAtRuntime)
+ {
+ alias blockSize = theBlockSize;
+ }
+ else
+ {
+ @property uint blockSize() { return _blockSize; }
+ @property void blockSize(uint s)
+ {
+ assert(!_control && s % alignment == 0);
+ _blockSize = s;
+ }
+ private uint _blockSize;
+ }
+
+ /**
+ The alignment offered is user-configurable statically through parameter
+ $(D theAlignment), defaulted to $(D platformAlignment).
+ */
+ alias alignment = theAlignment;
+
+ private uint _blocks;
+ private ulong[] _control;
+ private void[] _payload;
+ private size_t _startIdx;
+
+ /**
+ Constructs a block allocator given the total number of blocks. Only one $(D
+ parent.allocate) call will be made, and the layout puts the bitmap at the
+ front followed immediately by the payload. The constructor does not perform the allocation, however; allocation is done lazily upon the first call to
+ $(D allocate).
+ */
+ this(uint blocks)
+ {
+ _blocks = blocks;
+ }
+
+ private void initialize()
+ {
+ assert(_blocks);
+ const controlBytes = ((_blocks + 63) / 64) * 8;
+ const controlBytesRounded = controlBytes.roundUpToMultipleOf(
+ alignment);
+ const payloadBytes = _blocks * blockSize;
+ auto allocatedByUs = parent.allocate(
+ controlBytesRounded // control bits
+ + payloadBytes // payload
+ );
+ auto m = cast(ulong[]) allocatedByUs;
+ _control = m[0 .. controlBytes / 8];
+ _control[] = 0;
+ _payload = m[controlBytesRounded / 8 .. $];
+ assert(_payload.length == _blocks * blockSize,
+ text(_payload.length, " != ", _blocks * blockSize));
+ }
+
+ private void initialize(void[] store)
+ {
+ assert(store.length);
+ // Round store to be ulong-aligned
+ store = store.roundStartToMultipleOf(ulong.alignof);
+ assert(store.length);
+ /* Divide data between control and payload. The equation is (in real
+ numbers, not integers): bs * x + x / 8 = store.length, where x is
+ the number of blocks.
+ */
+ double approxBlocks = (8.0 * store.length) / (8 * blockSize + 1);
+ import std.math;
+ auto blocks = cast(size_t) (approxBlocks + nextDown(1.0));
+ assert(blocks > 0);
+ assert(blockSize);
+ assert(blocks * blockSize + ((blocks + 63) / 64) * 8 >= store.length,
+ text(approxBlocks, " ", blocks, " ", blockSize, " ",
+ store.length));
+ while (blocks * blockSize + ((blocks + 63) / 64) * 8 > store.length)
+ {
+ --blocks;
+ assert(blocks > 0);
+ }
+ auto control = cast(ulong[]) store[0 .. ((blocks + 63) / 64) * 8];
+ store = store[control.length * 8 .. $];
+ // Take into account data alignment necessities
+ store = store.roundStartToMultipleOf(alignment);
+ assert(store.length);
+ while (blocks * blockSize > store.length)
+ {
+ --blocks;
+ }
+ auto payload = store[0 .. blocks * blockSize];
+ initialize(control, payload, blockSize);
+ }
+
+ private void initialize(ulong[] control, void[] payload, size_t blockSize)
+ {
+ enforce(payload.length % blockSize == 0,
+ text(payload.length, " % ", blockSize, " != 0"));
+ assert(payload.length / blockSize <= uint.max);
+ _blocks = cast(uint) (payload.length / blockSize);
+ const controlWords = (_blocks + 63) / 64;
+ enforce(controlWords == control.length);
+ _control = control;
+ assert(control.equal(repeat(0, control.length)));
+ _payload = payload;
+ }
+
+ /*
+ Adjusts the memoized _startIdx to the leftmost control word that has at
+ least one zero bit. Assumes all control words to the left of $(D
+ _control[_startIdx]) are already occupied.
+ */
+ private void adjustStartIdx()
+ {
+ while (_startIdx < _control.length && _control[_startIdx] == ulong.max)
+ {
+ ++_startIdx;
+ }
+ }
+
+ /*
+ Returns the blocks corresponding to the control bits starting at word index
+ wordIdx and bit index msbIdx (MSB=0) for a total of howManyBlocks.
+ */
+ private void[] blocksFor(size_t wordIdx, uint msbIdx, size_t howManyBlocks)
+ {
+ assert(msbIdx <= 63);
+ const start = (wordIdx * 64 + msbIdx) * blockSize;
+ const end = start + blockSize * howManyBlocks;
+ if (end <= _payload.length) return _payload[start .. end];
+ // This could happen if we have more control bits than available memory.
+ // That's possible because the control bits are rounded up to fit in
+ // 64-bit words.
+ return null;
+ }
+
+ /**
+ Standard allocator methods per the semantics defined above. The $(D
+ deallocate) and $(D reallocate) methods are $(D @system) because they may
+ move memory around, leaving dangling pointers in user code.
+
+ BUGS: Neither $(D deallocateAll) nor the destructor free the original memory
+ block. Either user code or the parent allocator should carry that.
+ */
+ @trusted void[] allocate(const size_t s)
+ {
+ if (!_control)
+ {
+ // Lazy initialize
+ if (!_blocks)
+ static if (hasMember!(Allocator, "allocateAll"))
+ initialize(parent.allocateAll);
+ else
+ return null;
+ else
+ initialize();
+ }
+ assert(_blocks && _control && _payload);
+ const blocks = (s + blockSize - 1) / blockSize;
+ void[] result = void;
+
+ switcharoo:
+ switch (blocks)
+ {
+ case 1:
+ // inline code here for speed
+ // find the next available block
+ foreach (i; _startIdx .. _control.length)
+ {
+ const w = _control[i];
+ if (w == ulong.max) continue;
+ uint j = leadingOnes(w);
+ assert(j < 64);
+ assert((_control[i] & ((1UL << 63) >> j)) == 0);
+ _control[i] |= (1UL << 63) >> j;
+ if (i == _startIdx)
+ {
+ adjustStartIdx();
+ }
+ result = blocksFor(i, j, 1);
+ break switcharoo;
+ }
+ goto case 0; // fall through
+ case 0:
+ return null;
+ case 2: .. case 63:
+ result = smallAlloc(cast(uint) blocks);
+ break;
+ default:
+ result = hugeAlloc(blocks);
+ break;
+ }
+ return result ? result.ptr[0 .. s] : null;
+ }
+
+ /// Ditto
+ bool owns(void[] b) const
+ {
+ return b.ptr >= _payload.ptr
+ && b.ptr + b.length <= _payload.ptr + _payload.length
+ || b is null;
+ }
+
+ /*
+ Tries to allocate "blocks" blocks at the exact position indicated by the
+ position wordIdx/msbIdx (msbIdx counts from MSB, i.e. MSB has index 0). If
+ it succeeds, fills "result" with the result and returns tuple(size_t.max,
+ 0). Otherwise, returns a tuple with the next position to search.
+ */
+ private Tuple!(size_t, uint) allocateAt(size_t wordIdx, uint msbIdx,
+ size_t blocks, ref void[] result)
+ {
+ assert(blocks > 0);
+ assert(wordIdx < _control.length);
+ assert(msbIdx <= 63);
+ if (msbIdx + blocks <= 64)
+ {
+ // Allocation should fit this control word
+ if (setBitsIfZero(_control[wordIdx],
+ cast(uint) (64 - msbIdx - blocks), 63 - msbIdx))
+ {
+ // Success
+ result = blocksFor(wordIdx, msbIdx, blocks);
+ return tuple(size_t.max, 0u);
+ }
+ // Can't allocate, make a suggestion
+ return msbIdx + blocks == 64
+ ? tuple(wordIdx + 1, 0u)
+ : tuple(wordIdx, cast(uint) (msbIdx + blocks));
+ }
+ // Allocation spans two control words or more
+ auto mask = ulong.max >> msbIdx;
+ if (_control[wordIdx] & mask)
+ {
+ // We can't allocate the rest of this control word,
+ // return a suggestion.
+ return tuple(wordIdx + 1, 0u);
+ }
+ // We can allocate the rest of this control word, but we first need to
+ // make sure we can allocate the tail.
+ if (wordIdx + 1 == _control.length)
+ {
+ // No more memory
+ return tuple(_control.length, 0u);
+ }
+ auto hint = allocateAt(wordIdx + 1, 0, blocks - 64 + msbIdx, result);
+ if (hint[0] == size_t.max)
+ {
+ // We did it!
+ _control[wordIdx] |= mask;
+ result = blocksFor(wordIdx, msbIdx, blocks);
+ return tuple(size_t.max, 0u);
+ }
+ // Failed, return a suggestion that skips this whole run.
+ return hint;
+ }
+
+ /* Allocates as many blocks as possible at the end of the blocks indicated
+ by wordIdx. Returns the number of blocks allocated. */
+ private uint allocateAtTail(size_t wordIdx)
+ {
+ assert(wordIdx < _control.length);
+ const available = trailingZeros(_control[wordIdx]);
+ _control[wordIdx] |= ulong.max >> available;
+ return available;
+ }
+
+ private void[] smallAlloc(uint blocks)
+ {
+ assert(blocks >= 2 && blocks <= 64, text(blocks));
+ foreach (i; _startIdx .. _control.length)
+ {
+ // Test within the current 64-bit word
+ const v = _control[i];
+ if (v == ulong.max) continue;
+ auto j = findContigOnes(~v, blocks);
+ if (j < 64)
+ {
+ // yay, found stuff
+ setBits(_control[i], 64 - j - blocks, 63 - j);
+ return blocksFor(i, j, blocks);
+ }
+ // Next, try allocations that cross a word
+ auto available = trailingZeros(v);
+ if (available == 0) continue;
+ if (i + 1 >= _control.length) break;
+ assert(available < blocks); // otherwise we should have found it
+ auto needed = blocks - available;
+ assert(needed > 0 && needed < 64);
+ if (allocateAtFront(i + 1, needed))
+ {
+ // yay, found a block crossing two words
+ _control[i] |= (1UL << available) - 1;
+ return blocksFor(i, 64 - available, blocks);
+ }
+ }
+ return null;
+ }
+
+ private void[] hugeAlloc(size_t blocks)
+ {
+ assert(blocks > 64);
+ void[] result;
+ auto pos = tuple(_startIdx, 0);
+ for (;;)
+ {
+ if (pos[0] >= _control.length)
+ {
+ // No more memory
+ return null;
+ }
+ pos = allocateAt(pos[0], pos[1], blocks, result);
+ if (pos[0] == size_t.max)
+ {
+ // Found and allocated
+ return result;
+ }
+ }
+ }
+
+ // Rounds sizeInBytes to a multiple of blockSize.
+ private size_t bytes2blocks(size_t sizeInBytes)
+ {
+ return (sizeInBytes + blockSize - 1) / blockSize;
+ }
+
+ /* Allocates given blocks at the beginning blocks indicated by wordIdx.
+ Returns true if allocation was possible, false otherwise. */
+ private bool allocateAtFront(size_t wordIdx, uint blocks)
+ {
+ assert(wordIdx < _control.length && blocks >= 1 && blocks <= 64);
+ const mask = (1UL << (64 - blocks)) - 1;
+ if (_control[wordIdx] > mask) return false;
+ // yay, works
+ _control[wordIdx] |= ~mask;
+ return true;
+ }
+
+ /// Ditto
+ @trusted bool expand(ref void[] b, size_t delta)
+ {
+ //debug writefln("expand(%s, %s, %s)", b, minDelta, desiredDelta);
+ if (b is null)
+ {
+ b = allocate(delta);
+ return b !is null;
+ }
+
+ const blocksOld = bytes2blocks(b.length);
+ const blocksNew = bytes2blocks(b.length + delta);
+ assert(blocksOld <= blocksNew);
+
+ // Possibly we have enough slack at the end of the block!
+ if (blocksOld == blocksNew)
+ {
+ b = b.ptr[0 .. b.length + delta];
+ return true;
+ }
+
+ assert((b.ptr - _payload.ptr) % blockSize == 0);
+ const blockIdx = (b.ptr - _payload.ptr) / blockSize;
+ const blockIdxAfter = blockIdx + blocksOld;
+ //writefln("blockIdx: %s, blockIdxAfter: %s", blockIdx, blockIdxAfter);
+
+ // Try the maximum
+ const wordIdx = blockIdxAfter / 64,
+ msbIdx = cast(uint) (blockIdxAfter % 64);
+ void[] p;
+ auto hint = allocateAt(wordIdx, msbIdx, blocksNew - blocksOld, p);
+ if (hint[0] != size_t.max)
+ {
+ return false;
+ }
+ // Expansion successful
+ assert(p.ptr == b.ptr + blocksOld * blockSize,
+ text(p.ptr, " != ", b.ptr + blocksOld * blockSize));
+ b = b.ptr[0 .. b.length + delta];
+ return true;
+ }
+
+ /// Ditto
+ @system bool reallocate(ref void[] b, size_t newSize)
+ {
+ if (newSize == 0)
+ {
+ deallocate(b);
+ b = null;
+ return true;
+ }
+ if (newSize < b.length)
+ {
+ // Shrink. Will shrink in place by deallocating the trailing part.
+ auto newCapacity = bytes2blocks(newSize) * blockSize;
+ deallocate(b[newCapacity .. $]);
+ b = b[0 .. newSize];
+ return true;
+ }
+ // Attempt an in-place expansion first
+ const delta = newSize - b.length;
+ if (expand(b, delta)) return true;
+ // Go the slow route
+ return .reallocate(this, b, newSize);
+ }
+
+ /// Ditto
+ void deallocate(void[] b)
+ {
+ // Round up size to multiple of block size
+ auto blocks = (b.length + blockSize - 1) / blockSize;
+ // Locate position
+ auto pos = b.ptr - _payload.ptr;
+ assert(pos % blockSize == 0);
+ auto blockIdx = pos / blockSize;
+ auto wordIdx = blockIdx / 64, msbIdx = cast(uint) (blockIdx % 64);
+ if (_startIdx > wordIdx) _startIdx = wordIdx;
+
+ // Three stages: heading bits, full words, leftover bits
+ if (msbIdx)
+ {
+ if (blocks + msbIdx <= 64)
+ {
+ resetBits(_control[wordIdx], cast(uint) (64 - msbIdx - blocks),
+ 63 - msbIdx);
+ return;
+ }
+ else
+ {
+ _control[wordIdx] &= ulong.max << 64 - msbIdx;
+ blocks -= 64 - msbIdx;
+ ++wordIdx;
+ msbIdx = 0;
+ }
+ }
+
+ // Stage 2: reset one word at a time
+ for (; blocks >= 64; blocks -= 64)
+ {
+ _control[wordIdx++] = 0;
+ }
+
+ // Stage 3: deal with leftover bits, if any
+ assert(wordIdx <= _control.length);
+ if (blocks)
+ {
+ _control[wordIdx] &= ulong.max >> blocks;
+ }
+ }
+
+ /// Ditto
+ void deallocateAll()
+ {
+ static if (false && hasMember!(Allocator, "deallocate"))
+ {
+ parent.deallocate(_allocatedByUs);
+ this = this.init;
+ }
+ else
+ {
+ _control[] = 0;
+ _startIdx = 0;
+ }
+ }
+}
+
+///
+unittest
+{
+ // Create a block allocator on top of a 10KB stack region.
+ HeapBlock!(InSituRegion!(10240, 64), 64, 64) a;
+ static assert(hasMember!(InSituRegion!(10240, 64), "allocateAll"));
+ auto b = a.allocate(100);
+ assert(b.length == 100);
+}
+
+unittest
+{
+ static void testAllocateAll(size_t bs)(uint blocks, uint blocksAtATime)
+ {
+ assert(bs);
+ auto a = HeapBlock!(GCAllocator, bs)(blocks);
+ assert(a._blocks || !blocks);
+
+ // test allocation of 0 bytes
+ auto x = a.allocate(0);
+ assert(x is null);
+ // test allocation of 1 byte
+ x = a.allocate(1);
+ assert(x.length == 1 || blocks == 0, text(x.ptr, " ", x.length, " ", a));
+ a.deallocateAll();
+
+ //writeln("Control words: ", a._control.length);
+ //writeln("Payload bytes: ", a._payload.length);
+ bool twice = true;
+
+ begin:
+ foreach (i; 0 .. blocks / blocksAtATime)
+ {
+ auto b = a.allocate(bs * blocksAtATime);
+ assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+ }
+ assert(a.allocate(bs * blocksAtATime) is null);
+ assert(a.allocate(1) is null);
+
+ // Now deallocate all and do it again!
+ a.deallocateAll();
+
+ // Test deallocation
+
+ auto v = new void[][blocks / blocksAtATime];
+ foreach (i; 0 .. blocks / blocksAtATime)
+ {
+ auto b = a.allocate(bs * blocksAtATime);
+ assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+ v[i] = b;
+ }
+ assert(a.allocate(bs * blocksAtATime) is null);
+ assert(a.allocate(1) is null);
+
+ foreach (i; 0 .. blocks / blocksAtATime)
+ {
+ a.deallocate(v[i]);
+ }
+
+ foreach (i; 0 .. blocks / blocksAtATime)
+ {
+ auto b = a.allocate(bs * blocksAtATime);
+ assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+ v[i] = b;
+ }
+
+ foreach (i; 0 .. v.length)
+ {
+ a.deallocate(v[i]);
+ }
+
+ if (twice)
+ {
+ twice = false;
+ goto begin;
+ }
+
+ a.deallocateAll;
+
+ // test expansion
+ if (blocks >= blocksAtATime)
+ {
+ foreach (i; 0 .. blocks / blocksAtATime - 1)
+ {
+ auto b = a.allocate(bs * blocksAtATime);
+ assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+ (cast(ubyte[]) b)[] = 0xff;
+ a.expand(b, blocksAtATime * bs)
+ || assert(0, text(i));
+ (cast(ubyte[]) b)[] = 0xfe;
+ assert(b.length == bs * blocksAtATime * 2, text(i, ": ", b.length));
+ a.reallocate(b, blocksAtATime * bs) || assert(0);
+ assert(b.length == bs * blocksAtATime, text(i, ": ", b.length));
+ }
+ }
+ }
+
+ testAllocateAll!(1)(0, 1);
+ testAllocateAll!(1)(8, 1);
+ testAllocateAll!(4096)(128, 1);
+
+ testAllocateAll!(1)(0, 2);
+ testAllocateAll!(1)(128, 2);
+ testAllocateAll!(4096)(128, 2);
+
+ testAllocateAll!(1)(0, 4);
+ testAllocateAll!(1)(128, 4);
+ testAllocateAll!(4096)(128, 4);
+
+ testAllocateAll!(1)(0, 3);
+ testAllocateAll!(1)(24, 3);
+ testAllocateAll!(3000)(100, 1);
+ testAllocateAll!(3000)(100, 3);
+
+ testAllocateAll!(1)(0, 128);
+ testAllocateAll!(1)(128 * 1, 128);
+ testAllocateAll!(128 * 20)(13 * 128, 128);
+}
+
+/**
+$(D FallbackAllocator) is the allocator equivalent of an "or" operator in
+algebra. An allocation request is first attempted with the $(D Primary)
+allocator. If that returns $(D null), the request is forwarded to the $(D
+Fallback) allocator. All other requests are dispatched appropriately to one of
+the two allocators.
+
+In order to work, $(D FallbackAllocator) requires that $(D Primary) defines the
+$(D owns) method. This is needed in order to decide which allocator was
+responsible for a given allocation.
+
+$(D FallbackAllocator) is useful for fast, special-purpose allocators backed up
+by general-purpose allocators. The example below features a stack region backed
+up by the $(D GCAllocator).
+*/
+struct FallbackAllocator(Primary, Fallback)
+{
+ /// The primary allocator.
+ static if (stateSize!Primary) Primary primary;
+ else alias primary = Primary.it;
+
+ /// The fallback allocator.
+ static if (stateSize!Fallback) Fallback fallback;
+ else alias fallback = Fallback.it;
+
+ /**
+ If both $(D Primary) and $(D Fallback) are stateless, $(D FallbackAllocator)
+ defines a static instance $(D it).
+ */
+ static if (!stateSize!Primary && !stateSize!Fallback)
+ {
+ static FallbackAllocator it;
+ }
+
+ /**
+ The alignment offered is the minimum of the two allocators' alignment.
+ */
+ enum uint alignment = min(Primary.alignment, Fallback.alignment);
+
+ /**
+ Allocates memory trying the primary allocator first. If it returns $(D
+ null), the fallback allocator is tried.
+ */
+ void[] allocate(size_t s)
+ {
+ auto result = primary.allocate(s);
+ return result ? result : fallback.allocate(s);
+ }
+
+ /**
+
+ $(D expand) is defined if and only if at least one of the allocators
+ defines $(D expand). It works as follows. If $(D primary.owns(b)), then the
+ request is forwarded to $(D primary.expand) if it is defined, or fails
+ (returning $(D false)) otherwise. If $(D primary) does not own $(D b), then
+ the request is forwarded to $(D fallback.expand) if it is defined, or fails
+ (returning $(D false)) otherwise.
+
+ */
+ static if (hasMember!(Primary, "expand") || hasMember!(Fallback, "expand"))
+ bool expand(ref void[] b, size_t delta)
+ {
+ if (primary.owns(b))
+ {
+ static if (hasMember!(Primary, "expand"))
+ return primary.expand(b, delta);
+ else
+ return false;
+ }
+ static if (hasMember!(Fallback, "expand"))
+ return fallback.expand(b, delta);
+ else
+ return false;
+ }
+
+ /**
+
+ $(D reallocate) works as follows. If $(D primary.owns(b)), then $(D
+ primary.reallocate(b, newSize)) is attempted. If it fails, an attempt is
+ made to move the allocation from $(D primary) to $(D fallback).
+
+ If $(D primary) does not own $(D b), then $(D fallback.reallocate(b,
+ newSize)) is attempted. If that fails, an attempt is made to move the
+ allocation from $(D fallback) to $(D primary).
+
+ */
+ bool reallocate(ref void[] b, size_t newSize)
+ {
+ bool crossAllocatorMove(From, To)(ref From from, ref To to)
+ {
+ auto b1 = to.allocate(newSize);
+ if (!b1) return false;
+ if (b.length < newSize) b1[0 .. b.length] = b[];
+ else b1[] = b[0 .. newSize];
+ static if (hasMember!(From, "deallocate"))
+ from.deallocate(b);
+ b = b1;
+ return true;
+ }
+
+ if (primary.owns(b))
+ {
+ if (primary.reallocate(b, newSize)) return true;
+ // Move from primary to fallback
+ return crossAllocatorMove(primary, fallback);
+ }
+ if (fallback.reallocate(b, newSize)) return true;
+ // Interesting. Move from fallback to primary.
+ return crossAllocatorMove(fallback, primary);
+ }
+
+ /**
+ $(D owns) is defined if and only if both allocators define $(D owns).
+ Returns $(D primary.owns(b) || fallback.owns(b)).
+ */
+ static if (hasMember!(Primary, "owns") && hasMember!(Fallback, "owns"))
+ bool owns(void[] p)
+ {
+ return primary.owns(b) || fallback.owns(p);
+ }
+
+ /**
+ $(D deallocate) is defined if and only if at least one of the allocators
+ define $(D deallocate). It works as follows. If $(D primary.owns(b)),
+ then the request is forwarded to $(D primary.deallocate) if it is defined,
+ or is a no-op otherwise. If $(D primary) does not own $(D b), then the
+ request is forwarded to $(D fallback.deallocate) if it is defined, or is a
+ no-op otherwise.
+ */
+ static if (hasMember!(Primary, "deallocate")
+ || hasMember!(Fallback, "deallocate"))
+ void deallocate(void[] b)
+ {
+ if (primary.owns(b))
+ {
+ static if (hasMember!(Primary, "deallocate"))
+ primary.deallocate(b);
+ }
+ else
+ {
+ static if (hasMember!(Fallback, "deallocate"))
+ return fallback.deallocate(b);
+ }
+ }
+}
+
+///
+unittest
+{
+ FallbackAllocator!(InSituRegion!16384, GCAllocator) a;
+ // This allocation uses the stack
+ auto b1 = a.allocate(1024);
+ assert(b1.length == 1024, text(b1.length));
+ assert(a.primary.owns(b1));
+ // This large allocation will go to the Mallocator
+ auto b2 = a.allocate(1024 * 1024);
+ assert(!a.primary.owns(b2));
+ a.deallocate(b1);
+ a.deallocate(b2);
+}
+
+/**
+
+$(WEB en.wikipedia.org/wiki/Free_list, Free list allocator), stackable on top of
+another allocator. Allocation requests between $(D min) and $(D max) bytes are
+rounded up to $(D max) and served from a singly-linked list of buffers
+deallocated in the past. All other allocations are directed to $(D
+ParentAllocator). Due to the simplicity of free list management, allocations
+from the free list are fast.
+
+If a program makes many allocations in the interval $(D [minSize, maxSize]) and
+then frees most of them, the freelist may grow large, thus making memory
+inaccessible to requests of other sizes. To prevent that, the $(D maxNodes)
+parameter allows limiting the size of the free list. Alternatively, $(D
+deallocateAll) cleans the free list.
+
+$(D Freelist) attempts to reduce internal fragmentation and improve cache
+locality by allocating multiple nodes at once, under the control of the $(D
+batchCount) parameter. This makes $(D Freelist) an efficient front for small
+object allocation on top of a large-block allocator. The default value of $(D
+batchCount) is 8, which should amortize freelist management costs to negligible
+in most cases.
+
+One instantiation is of particular interest: $(D Freelist!(0,unbounded)) puts
+every deallocation in the freelist, and subsequently serves any allocation from
+the freelist (if not empty). There is no checking of size matching, which would
+be incorrect for a freestanding allocator but is both correct and fast when an
+owning allocator on top of the free list allocator (such as $(D Segregator)) is
+already in charge of handling size checking.
+
+*/
+struct Freelist(ParentAllocator,
+ size_t minSize, size_t maxSize = minSize,
+ uint batchCount = 8, size_t maxNodes = unbounded)
+{
+ static assert(minSize != unbounded, "Use minSize = 0 for no low bound.");
+ static assert(maxSize >= (void*).sizeof,
+ "Maximum size must accommodate a pointer.");
+
+ static if (minSize != chooseAtRuntime)
+ {
+ alias min = minSize;
+ }
+ else
+ {
+ size_t _min = chooseAtRuntime;
+ @property size_t min() const
+ {
+ assert(_min != chooseAtRuntime);
+ return _min;
+ }
+ @property void min(size_t x)
+ {
+ enforce(x <= _max);
+ _min = x;
+ }
+ static if (maxSize == chooseAtRuntime)
+ {
+ // Both bounds can be set, provide one function for setting both in
+ // one shot.
+ void setBounds(size_t low, size_t high)
+ {
+ enforce(low <= high && high >= (void*).sizeof);
+ _min = low;
+ _max = high;
+ }
+ }
+ }
+
+ private bool tooSmall(size_t n) const
+ {
+ static if (minSize == 0) return false;
+ else return n < min;
+ }
+
+ static if (maxSize != chooseAtRuntime)
+ {
+ alias max = maxSize;
+ }
+ else
+ {
+ size_t _max;
+ @property size_t max() const { return _max; }
+ @property void max(size_t x)
+ {
+ enforce(x >= _min && x >= (void*).sizeof);
+ _max = x;
+ }
+ }
+
+ private bool tooLarge(size_t n) const
+ {
+ static if (maxSize == unbounded) return false;
+ else return n > max;
+ }
+
+ private bool inRange(size_t n) const
+ {
+ static if (minSize == maxSize && minSize != chooseAtRuntime)
+ return n == maxSize;
+ else return !tooSmall(n) && !tooLarge(n);
+ }
+
+ version (StdDdoc)
+ {
+ /**
+ Properties for getting and setting bounds. Setting a bound is only
+ possible if the respective compile-time parameter has been set to $(D
+ chooseAtRuntime). $(D setBounds) is defined only if both $(D minSize)
+ and $(D maxSize) are set to $(D chooseAtRuntime).
+ */
+ @property size_t min();
+ /// Ditto
+ @property void min(size_t newMinSize);
+ /// Ditto
+ @property size_t max();
+ /// Ditto
+ @property void max(size_t newMaxSize);
+ /// Ditto
+ void setBounds(size_t newMin, size_t newMax);
+ ///
+ unittest
+ {
+ Freelist!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
+ // Set the maxSize first so setting the minSize doesn't throw
+ a.max = 128;
+ a.min = 64;
+ a.setBounds(64, 128); // equivalent
+ assert(a.max == 128);
+ assert(a.min == 64);
+ }
+ }
+
+ /**
+ The parent allocator. Depending on whether $(D ParentAllocator) holds state
+ or not, this is a member variable or an alias for $(D ParentAllocator.it).
+ */
+ static if (stateSize!ParentAllocator) ParentAllocator parent;
+ else alias parent = ParentAllocator.it;
+
+ private struct Node { Node* next; }
+ static assert(ParentAllocator.alignment >= Node.alignof);
+ private Node* _root;
+ private uint nodesAtATime = batchCount;
+
+ static if (maxNodes != unbounded)
+ {
+ private size_t nodes;
+ private void incNodes() { ++nodes; }
+ private void decNodes() { assert(nodes); --nodes; }
+ private bool nodesFull() { return nodes >= maxNodes; }
+ }
+ else
+ {
+ private static void incNodes() { }
+ private static void decNodes() { }
+ private enum bool nodesFull = false;
+ }
+
+ /**
+ Alignment is defined as $(D parent.alignment). However, if $(D
+ parent.alignment > maxSize), objects returned from the freelist will have a
+ smaller _alignment, namely $(D maxSize) rounded up to the nearest multiple
+ of 2. This allows $(D Freelist) to minimize internal fragmentation by
+ allocating several small objects within an allocated block. Also, there is
+ no disruption because no object has smaller size than its _alignment.
+ */
+ enum uint alignment = ParentAllocator.alignment;
+
+ /**
+ Returns $(D max) for sizes in the interval $(D [min, max]), and $(D
+ parent.goodAllocSize(bytes)) otherwise.
+ */
+ size_t goodAllocSize(size_t bytes)
+ {
+ if (inRange(bytes)) return maxSize == unbounded ? bytes : max;
+ return parent.goodAllocSize(bytes);
+ }
+
+ /**
+ Allocates memory either off of the free list or from the parent allocator.
+ */
+ void[] allocate(size_t bytes)
+ in
+ {
+ assert (_root !is null);
+ }
+ body
+ {
+ assert(bytes < size_t.max / 2);
+ if (!inRange(bytes)) return parent.allocate(bytes);
+ // Round up allocation to max
+ if (maxSize != unbounded) bytes = max;
+ if (!_root) return allocateFresh(bytes);
+ // Pop off the freelist
+ auto result = (cast(ubyte*) _root)[0 .. bytes];
+ _root = _root.next;
+ decNodes();
+ return result;
+ }
+
+ private void[] allocateFresh(const size_t bytes)
+ {
+ assert(!_root);
+ assert(bytes == max || max == unbounded);
+ if (nodesAtATime == 1)
+ {
+ // Easy case, just get it over with
+ return parent.allocate(bytes);
+ }
+ static if (maxSize != unbounded && maxSize != chooseAtRuntime)
+ {
+ static assert((parent.alignment + max) % Node.alignof == 0,
+ text("(", parent.alignment, " + ", max, ") % ",
+ Node.alignof));
+ }
+ else
+ {
+ assert((parent.alignment + bytes) % Node.alignof == 0,
+ text("(", parent.alignment, " + ", bytes, ") % ",
+ Node.alignof));
+ }
+
+ auto data = parent.allocate(nodesAtATime * bytes);
+ if (!data) return null;
+ auto result = data[0 .. bytes];
+ auto n = data[bytes .. $];
+ _root = cast(Node*) n.ptr;
+ for (;;)
+ {
+ if (n.length < bytes)
+ {
+ (cast(Node*) data.ptr).next = null;
+ break;
+ }
+ (cast(Node*) data.ptr).next = cast(Node*) n.ptr;
+ data = n;
+ n = data[bytes .. $];
+ }
+ return result;
+ }
+
+ /**
+ If $(D b.length) is in the interval $(D [min, max]), returns $(D true).
+ Otherwise, if $(D Parent.owns) is defined, forwards to it. Otherwise,
+ returns $(D false). This semantics is intended to have $(D
+ Freelist) handle deallocations of objects of the appropriate size,
+ even for allocators that don't support $(D owns) (such as $(D Mallocator)).
+ */
+ bool owns(void[] b)
+ {
+ if (inRange(b.length)) return true;
+ static if (hasMember!(ParentAllocator, "owns"))
+ return parent.owns(b);
+ else
+ return false;
+ }
+
+ /**
+ Forwards to $(D parent).
+ */
+ static if (hasMember!(ParentAllocator, "expand"))
+ bool expand(void[] b, size_t s)
+ {
+ return parent.expand(b, s);
+ }
+
+ /// Ditto
+ static if (hasMember!(ParentAllocator, "reallocate"))
+ bool reallocate(void[] b, size_t s)
+ {
+ return parent.reallocate(b, s);
+ }
+
+ /**
+ Intercepts deallocations and caches those of the appropriate size in the
+ freelist. For all others, forwards to $(D parent.deallocate) or does nothing
+ if $(D Parent) does not define $(D deallocate).
+ */
+ void deallocate(void[] block)
+ {
+ if (!nodesFull && inRange(block.length))
+ {
+ auto t = _root;
+ _root = cast(Node*) block.ptr;
+ _root.next = t;
+ incNodes();
+ }
+ else
+ {
+ static if (is(typeof(parent.deallocate(block))))
+ parent.deallocate(block);
+ }
+ }
+
+ /**
+ If $(D ParentAllocator) defines $(D deallocateAll), just forwards to it and
+ reset the freelist. Otherwise, walks the list and frees each object in turn.
+ */
+ void deallocateAll()
+ {
+ static if (hasMember!(ParentAllocator, "deallocateAll"))
+ {
+ parent.deallocateAll();
+ }
+ else static if (hasMember!(ParentAllocator, "deallocate"))
+ {
+ for (auto n = _root; n; n = n.next)
+ {
+ parent.deallocate((cast(ubyte*)n)[0 .. max]);
+ }
+ }
+ _root = null;
+ }
+}
+
+unittest
+{
+ Freelist!(GCAllocator, 0, 8, 1) fl;
+ assert(fl._root is null);
+ auto b1 = fl.allocate(7);
+ //assert(fl._root !is null);
+ auto b2 = fl.allocate(8);
+ assert(fl._root is null);
+ fl.deallocate(b1);
+ assert(fl._root !is null);
+ auto b3 = fl.allocate(8);
+ assert(fl._root is null);
+}
+
+/**
+Freelist shared across threads. Allocation and deallocation are lock-free. The
+parameters have the same semantics as for $(D Freelist).
+*/
+struct SharedFreelist(ParentAllocator,
+ size_t minSize, size_t maxSize = minSize,
+ uint batchCount = 8, size_t maxNodes = unbounded)
+{
+ static assert(minSize != unbounded, "Use minSize = 0 for no low bound.");
+ static assert(maxSize >= (void*).sizeof,
+ "Maximum size must accommodate a pointer.");
+
+ private import core.atomic;
+
+ static if (minSize != chooseAtRuntime)
+ {
+ alias min = minSize;
+ }
+ else
+ {
+ shared size_t _min = chooseAtRuntime;
+ @property size_t min() const shared
+ {
+ assert(_min != chooseAtRuntime);
+ return _min;
+ }
+ @property void min(size_t x) shared
+ {
+ enforce(x <= max);
+ enforce(cas(&_min, chooseAtRuntime, x),
+ "SharedFreelist.min must be initialized exactly once.");
+ }
+ static if (maxSize == chooseAtRuntime)
+ {
+ // Both bounds can be set, provide one function for setting both in
+ // one shot.
+ void setBounds(size_t low, size_t high) shared
+ {
+ enforce(low <= high && high >= (void*).sizeof);
+ enforce(cas(&_min, chooseAtRuntime, low),
+ "SharedFreelist.min must be initialized exactly once.");
+ enforce(cas(&_max, chooseAtRuntime, high),
+ "SharedFreelist.max must be initialized exactly once.");
+ }
+ }
+ }
+
+ private bool tooSmall(size_t n) const shared
+ {
+ static if (minSize == 0) return false;
+ else static if (minSize == chooseAtRuntime) return n < _min;
+ else return n < minSize;
+ }
+
+ static if (maxSize != chooseAtRuntime)
+ {
+ alias max = maxSize;
+ }
+ else
+ {
+ shared size_t _max = chooseAtRuntime;
+ @property size_t max() const shared { return _max; }
+ @property void max(size_t x) shared
+ {
+ enforce(x >= _min && x >= (void*).sizeof);
+ enforce(cas(&_max, chooseAtRuntime, x),
+ "SharedFreelist.max must be initialized exactly once.");
+ }
+ }
+
+ private bool tooLarge(size_t n) const shared
+ {
+ static if (maxSize == unbounded) return false;
+ else static if (maxSize == chooseAtRuntime) return n > _max;
+ else return n > maxSize;
+ }
+
+ private bool inRange(size_t n) const shared
+ {
+ static if (minSize == maxSize && minSize != chooseAtRuntime)
+ return n == maxSize;
+ else return !tooSmall(n) && !tooLarge(n);
+ }
+
+ static if (maxNodes != unbounded)
+ {
+ private shared size_t nodes;
+ private void incNodes() shared
+ {
+ atomicOp!("+=")(nodes, 1);
+ }
+ private void decNodes() shared
+ {
+ assert(nodes);
+ atomicOp!("-=")(nodes, 1);
+ }
+ private bool nodesFull() shared
+ {
+ return nodes >= maxNodes;
+ }
+ }
+ else
+ {
+ private static void incNodes() { }
+ private static void decNodes() { }
+ private enum bool nodesFull = false;
+ }
+
+ version (StdDdoc)
+ {
+ /**
+ Properties for getting (and possibly setting) the bounds. Setting bounds
+ is allowed only once , and before any allocation takes place. Otherwise,
+ the primitives have the same semantics as those of $(D Freelist).
+ */
+ @property size_t min();
+ /// Ditto
+ @property void min(size_t newMinSize);
+ /// Ditto
+ @property size_t max();
+ /// Ditto
+ @property void max(size_t newMaxSize);
+ /// Ditto
+ void setBounds(size_t newMin, size_t newMax);
+ ///
+ unittest
+ {
+ Freelist!(Mallocator, chooseAtRuntime, chooseAtRuntime) a;
+ // Set the maxSize first so setting the minSize doesn't throw
+ a.max = 128;
+ a.min = 64;
+ a.setBounds(64, 128); // equivalent
+ assert(a.max == 128);
+ assert(a.min == 64);
+ }
+ }
+
+ /**
+ The parent allocator. Depending on whether $(D ParentAllocator) holds state
+ or not, this is a member variable or an alias for $(D ParentAllocator.it).
+ */
+ static if (stateSize!ParentAllocator) shared ParentAllocator parent;
+ else alias parent = ParentAllocator.it;
+
+ private struct Node { Node* next; }
+ static assert(ParentAllocator.alignment >= Node.alignof);
+ private Node* _root;
+ private uint nodesAtATime = batchCount;
+
+ /// Standard primitives.
+ enum uint alignment = ParentAllocator.alignment;
+
+ /// Ditto
+ size_t goodAllocSize(size_t bytes) shared
+ {
+ if (inRange(bytes)) return maxSize == unbounded ? bytes : max;
+ return parent.goodAllocSize(bytes);
+ }
+
+ /// Ditto
+ bool owns(void[] b) shared const
+ {
+ if (inRange(b.length)) return true;
+ static if (hasMember!(ParentAllocator, "owns"))
+ return parent.owns(b);
+ else
+ return false;
+ }
+
+ /**
+ Forwards to $(D parent), which must also support $(D shared) primitives.
+ */
+ static if (hasMember!(ParentAllocator, "expand"))
+ bool expand(void[] b, size_t s)
+ {
+ return parent.expand(b, s);
+ }
+
+ /// Ditto
+ static if (hasMember!(ParentAllocator, "reallocate"))
+ bool reallocate(void[] b, size_t s)
+ {
+ return parent.reallocate(b, s);
+ }
+
+ /// Ditto
+ void[] allocate(size_t bytes) shared
+ {
+ assert(bytes < size_t.max / 2);
+ if (!inRange(bytes)) return parent.allocate(bytes);
+ if (maxSize != unbounded) bytes = max;
+ if (!_root) return allocateFresh(bytes);
+ // Pop off the freelist
+ shared Node* oldRoot = void, next = void;
+ do
+ {
+ oldRoot = _root; // atomic load
+ next = oldRoot.next; // atomic load
+ }
+ while (!cas(&_root, oldRoot, next));
+ // great, snatched the root
+ decNodes();
+ return (cast(ubyte*) oldRoot)[0 .. bytes];
+ }
+
+ private void[] allocateFresh(const size_t bytes) shared
+ {
+ assert(bytes == max || max == unbounded);
+ if (nodesAtATime == 1)
+ {
+ // Easy case, just get it over with
+ return parent.allocate(bytes);
+ }
+ static if (maxSize != unbounded && maxSize != chooseAtRuntime)
+ {
+ static assert(
+ (parent.alignment + max) % Node.alignof == 0,
+ text("(", parent.alignment, " + ", max, ") % ",
+ Node.alignof));
+ }
+ else
+ {
+ assert((parent.alignment + bytes) % Node.alignof == 0,
+ text("(", parent.alignment, " + ", bytes, ") % ",
+ Node.alignof));
+ }
+
+ auto data = parent.allocate(nodesAtATime * bytes);
+ if (!data) return null;
+ auto result = data[0 .. bytes];
+ auto n = data[bytes .. $];
+ auto newRoot = cast(shared Node*) n.ptr;
+ shared Node* lastNode;
+ for (;;)
+ {
+ if (n.length < bytes)
+ {
+ lastNode = cast(shared Node*) data.ptr;
+ break;
+ }
+ (cast(Node*) data.ptr).next = cast(Node*) n.ptr;
+ data = n;
+ n = data[bytes .. $];
+ }
+ // Created the list, now wire the new nodes in considering another
+ // thread might have also created some nodes.
+ do
+ {
+ lastNode.next = _root;
+ }
+ while (!cas(&_root, lastNode.next, newRoot));
+ return result;
+ }
+
+ /// Ditto
+ void deallocate(void[] b) shared
+ {
+ if (!nodesFull && inRange(b.length))
+ {
+ auto newRoot = cast(shared Node*) b.ptr;
+ shared Node* oldRoot;
+ do
+ {
+ oldRoot = _root;
+ newRoot.next = oldRoot;
+ }
+ while (!cas(&_root, oldRoot, newRoot));
+ incNodes();
+ }
+ else
+ {
+ static if (is(typeof(parent.deallocate(block))))
+ parent.deallocate(block);
+ }
+ }
+
+ /// Ditto
+ void deallocateAll() shared
+ {
+ static if (hasMember!(ParentAllocator, "deallocateAll"))
+ {
+ parent.deallocateAll();
+ }
+ else static if (hasMember!(ParentAllocator, "deallocate"))
+ {
+ for (auto n = _root; n; n = n.next)
+ {
+ parent.deallocate((cast(ubyte*)n)[0 .. max]);
+ }
+ }
+ _root = null;
+ }
+}
+
+unittest
+{
+ import core.thread, std.concurrency;
+
+ static shared SharedFreelist!(Mallocator, 64, 128, 8, 100) a;
+
+ assert(a.goodAllocSize(1) == platformAlignment);
+
+ auto b = a.allocate(100);
+ a.deallocate(b);
+
+ static void fun(Tid tid, int i)
+ {
+ scope(exit) tid.send(true);
+ auto b = cast(ubyte[]) a.allocate(100);
+ b[] = cast(ubyte) i;
+
+ assert(b.equal(repeat(cast(ubyte) i, b.length)));
+ a.deallocate(b);
+ }
+
+ Tid[] tids;
+ foreach (i; 0 .. 1000)
+ {
+ tids ~= spawn(&fun, thisTid, i);
+ }
+
+ foreach (i; 0 .. 1000)
+ {
+ assert(receiveOnly!bool);
+ }
+}
+
+unittest
+{
+ shared SharedFreelist!(Mallocator, chooseAtRuntime, chooseAtRuntime,
+ 8, 100) a;
+ auto b = a.allocate(64);
+}
+
+/*
+(This type is not public.)
+
+A $(D BasicRegion) allocator allocates memory straight from an externally-
+provided storage as backend. There is no deallocation, and once the region is
+full, allocation requests return $(D null). Therefore, $(D Region)s are often
+used in conjunction with freelists and a fallback general-purpose allocator.
+
+The region only stores two words, corresponding to the current position in the
+store and the available length. One allocation entails rounding up the
+allocation size for alignment purposes, bumping the current pointer, and
+comparing it against the limit.
+
+The $(D minAlign) parameter establishes alignment. If $(D minAlign > 1), the
+sizes of all allocation requests are rounded up to a multiple of $(D minAlign).
+Applications aiming at maximum speed may want to choose $(D minAlign = 1) and
+control alignment externally.
+*/
+private struct BasicRegion(uint minAlign = platformAlignment)
+{
+ static assert(minAlign.isGoodStaticAlignment);
+ private void* _current, _end;
+
+ /**
+ Constructs a region backed by a user-provided store.
+ */
+ this(void[] store)
+ {
+ static if (minAlign > 1)
+ {
+ auto newStore = cast(void*) roundUpToMultipleOf(
+ cast(ulong) store.ptr,
+ alignment);
+ enforce(newStore <= store.ptr + store.length);
+ _current = newStore;
+ }
+ else
+ {
+ _current = store;
+ }
+ _end = store.ptr + store.length;
+ }
+
+ /**
+ The postblit of $(D BasicRegion) is disabled because such objects should not
+ be copied around naively.
+ */
+ //@disable this(this);
+
+ /**
+ Standard allocator primitives.
+ */
+ enum uint alignment = minAlign;
+
+ /// Ditto
+ void[] allocate(size_t bytes)
+ {
+ static if (minAlign > 1)
+ const rounded = bytes.roundUpToMultipleOf(alignment);
+ else
+ alias rounded = bytes;
+ auto newCurrent = _current + rounded;
+ if (newCurrent > _end) return null;
+ auto result = _current[0 .. bytes];
+ _current = newCurrent;
+ assert(cast(ulong) result.ptr % alignment == 0);
+ return result;
+ }
+
+ /// Ditto
+ void[] alignedAllocate(size_t bytes, uint a)
+ {
+ // Just bump the pointer to the next good allocation
+ auto save = _current;
+ _current = cast(void*) roundUpToMultipleOf(
+ cast(ulong) _current, a);
+ if (auto b = allocate(bytes)) return b;
+ // Failed, rollback
+ _current = save;
+ return null;
+ }
+
+ /// Allocates and returns all memory available to this region.
+ void[] allocateAll()
+ {
+ auto result = _current[0 .. available];
+ _current = _end;
+ return result;
+ }
+ /// Nonstandard property that returns bytes available for allocation.
+ size_t available() const
+ {
+ return _end - _current;
+ }
+}
+
+/*
+For implementers' eyes: Region adds more capabilities on top of $(BasicRegion)
+at the cost of one extra word of storage. $(D Region) "remembers" the beginning
+of the region and therefore is able to provide implementations of $(D owns) and
+$(D deallocateAll). For most applications the performance distinction between
+$(D BasicRegion) and $(D Region) is unimportant, so the latter should be the
+default choice.
+*/
+
+/**
+A $(D Region) allocator manages one block of memory provided at construction.
+There is no deallocation, and once the region is full, allocation requests
+return $(D null). Therefore, $(D Region)s are often used in conjunction
+with freelists, a fallback general-purpose allocator, or both.
+
+The region stores three words corresponding to the start of the store, the
+current position in the store, and the end of the store. One allocation entails
+rounding up the allocation size for alignment purposes, bumping the current
+pointer, and comparing it against the limit.
+
+The $(D minAlign) parameter establishes alignment. If $(D minAlign > 1), the
+sizes of all allocation requests are rounded up to a multiple of $(D minAlign).
+Applications aiming at maximum speed may want to choose $(D minAlign = 1) and
+control alignment externally.
+*/
+struct Region(uint minAlign = platformAlignment)
+{
+ static assert(minAlign.isGoodStaticAlignment);
+
+ private BasicRegion!(minAlign) base;
+ private void* _begin;
+
+ /**
+ Constructs a $(D Region) object backed by $(D buffer), which must be aligned
+ to $(D minAlign).
+ */
+ this(void[] buffer)
+ {
+ base = BasicRegion!minAlign(buffer);
+ assert(buffer.ptr !is &this);
+ _begin = base._current;
+ }
+
+ /**
+ Standard primitives.
+ */
+ enum uint alignment = minAlign;
+
+ /// Ditto
+ void[] allocate(size_t bytes)
+ {
+ return base.allocate(bytes);
+ }
+
+ /// Ditto
+ void[] alignedAllocate(size_t bytes, uint a)
+ {
+ return base.alignedAllocate(bytes, a);
+ }
+
+ /// Ditto
+ bool owns(void[] b) const
+ {
+ return b.ptr >= _begin && b.ptr + b.length <= base._end
+ || b is null;
+ }
+
+ /// Ditto
+ void deallocateAll()
+ {
+ base._current = _begin;
+ }
+
+ /**
+ Nonstandard function that gives away the initial buffer used by the range,
+ and makes the range unavailable for further allocations. This is useful for
+ deallocating the memory assigned to the region.
+ */
+ void[] relinquish()
+ {
+ auto result = _begin[0 .. base._end - _begin];
+ base._current = base._end;
+ return result;
+ }
+}
+
+///
+unittest
+{
+ auto reg = Region!()(Mallocator.it.allocate(1024 * 64));
+ scope(exit) Mallocator.it.deallocate(reg.relinquish);
+ auto b = reg.allocate(101);
+ assert(b.length == 101);
+}
+
+/**
+
+$(D InSituRegion) is a convenient region that carries its storage within itself
+(in the form of a statically-sized array).
+
+The first template argument is the size of the region and the second is the
+needed alignment. Depending on the alignment requested and platform details,
+the actual available storage may be smaller than the compile-time parameter. To
+make sure that at least $(D n) bytes are available in the region, use
+$(D InSituRegion!(n + a - 1, a)).
+
+*/
+struct InSituRegion(size_t size, size_t minAlign = platformAlignment)
+{
+ static assert(minAlign.isGoodStaticAlignment);
+ static assert(size >= minAlign);
+
+ // The store will be aligned to double.alignof, regardless of the requested
+ // alignment.
+ union
+ {
+ private ubyte[size] _store = void;
+ private double _forAlignmentOnly = void;
+ }
+ private void* _crt, _end;
+
+ /**
+ An alias for $(D minAlign), which must be a valid alignment (nonzero power
+ of 2). The start of the region and all allocation requests will be rounded
+ up to a multiple of the alignment.
+
+ ----
+ InSituRegion!(4096) a1;
+ assert(a1.alignment == platformAlignment);
+ InSituRegion!(4096, 64) a2;
+ assert(a2.alignment == 64);
+ ----
+ */
+ enum uint alignment = minAlign;
+
+ private void lazyInit()
+ {
+ assert(!_crt);
+ _crt = cast(void*) roundUpToMultipleOf(
+ cast(ulong) _store.ptr, alignment);
+ _end = _store.ptr + _store.length;
+ }
+
+ /**
+ Allocates $(D bytes) and returns them, or $(D null) if the region cannot
+ accommodate the request. For efficiency reasons, if $(D bytes == 0) the
+ function returns an empty non-null slice.
+ */
+ void[] allocate(size_t bytes)
+ {
+ // Oddity: we don't return null for null allocation. Instead, we return
+ // an empty slice with a non-null ptr.
+ const rounded = bytes.roundUpToMultipleOf(alignment);
+ auto newCrt = _crt + rounded;
+ assert(newCrt >= _crt); // big overflow
+ again:
+ if (newCrt <= _end)
+ {
+ assert(_crt); // this relies on null + size > null
+ auto result = _crt[0 .. bytes];
+ _crt = newCrt;
+ return result;
+ }
+ // slow path
+ if (_crt) return null;
+ // Lazy initialize _crt
+ lazyInit();
+ newCrt = _crt + rounded;
+ goto again;
+ }
+
+ /**
+ As above, but the memory allocated is aligned at $(D a) bytes.
+ */
+ void[] alignedAllocate(size_t bytes, uint a)
+ {
+ // Just bump the pointer to the next good allocation
+ auto save = _crt;
+ _crt = cast(void*) roundUpToMultipleOf(
+ cast(ulong) _crt, a);
+ if (auto b = allocate(bytes)) return b;
+ // Failed, rollback
+ _crt = save;
+ return null;
+ }
+
+ /**
+ Returns $(D true) if and only if $(D b) is the result of a successful
+ allocation. For efficiency reasons, if $(D b is null) the function returns
+ $(D false).
+ */
+ bool owns(void[] b) const
+ {
+ // No nullptr
+ return b.ptr >= _store.ptr
+ && b.ptr + b.length <= _store.ptr + _store.length;
+ }
+
+ /**
+ Deallocates all memory allocated with this allocator.
+ */
+ void deallocateAll()
+ {
+ _crt = _store.ptr;
+ }
+
+ /**
+ Allocates all memory available with this allocator.
+ */
+ void[] allocateAll()
+ {
+ auto s = available;
+ auto result = _crt[0 .. s];
+ _crt = _end;
+ return result;
+ }
+
+ /**
+ Nonstandard function that returns the bytes available for allocation.
+ */
+ size_t available()
+ {
+ if (!_crt) lazyInit();
+ return _end - _crt;
+ }
+}
+
+///
+unittest
+{
+ // 128KB region, allocated to x86's cache line
+ InSituRegion!(128 * 1024, 64) r1;
+ auto a1 = r1.allocate(101);
+ assert(a1.length == 101);
+
+ // 128KB region, with fallback to the garbage collector.
+ FallbackAllocator!(InSituRegion!(128 * 1024), GCAllocator) r2;
+ auto a2 = r1.allocate(102);
+ assert(a2.length == 102);
+
+ // Reap with GC fallback.
+ InSituRegion!(128 * 1024) tmp3;
+ FallbackAllocator!(HeapBlock!(InSituRegion!(128 * 1024), 64, 64),
+ GCAllocator) r3;
+ auto a3 = r3.allocate(103);
+ assert(a3.length == 103);
+
+ // Reap/GC with a freelist for small objects up to 16 bytes.
+ InSituRegion!(128 * 1024) tmp4;
+ Freelist!(FallbackAllocator!(
+ HeapBlock!(InSituRegion!(128 * 1024), 64, 64), GCAllocator), 0, 16) r4;
+ auto a4 = r4.allocate(104);
+ assert(a4.length == 104);
+
+ // Same as above, except the freelist only applies to the reap.
+ InSituRegion!(128 * 1024) tmp5;
+ FallbackAllocator!(Freelist!(HeapBlock!(InSituRegion!(128 * 1024), 64, 64), 0, 16), GCAllocator) r5;
+ auto a5 = r5.allocate(105);
+ assert(a5.length == 105);
+}
+
+unittest
+{
+ InSituRegion!(4096) r1;
+ auto a = r1.allocate(2001);
+ assert(a.length == 2001);
+ assert(r1.available == 2080, text(r1.available));
+
+ InSituRegion!(65536, 1024*4) r2;
+ assert(r2.available <= 65536);
+ a = r2.allocate(2001);
+ assert(a.length == 2001);
+}
+
+/**
+_Options for $(D AllocatorWithStats) defined below. Each enables during
+compilation one specific counter, statistic, or other piece of information.
+*/
+enum Options : uint
+{
+ /**
+ Counts the number of calls to $(D owns).
+ */
+ numOwns = 1u << 0,
+ /**
+ Counts the number of calls to $(D allocate). All calls are counted,
+ including requests for zero bytes or failed requests.
+ */
+ numAllocate = 1u << 1,
+ /**
+ Counts the number of calls to $(D allocate) that succeeded, i.e. they were
+ for more than zero bytes and returned a non-null block.
+ */
+ numAllocateOK = 1u << 2,
+ /**
+ Counts the number of calls to $(D expand), regardless of arguments or
+ result.
+ */
+ numExpand = 1u << 3,
+ /**
+ Counts the number of calls to $(D expand) that resulted in a successful
+ expansion.
+ */
+ numExpandOK = 1u << 4,
+ /**
+ Counts the number of calls to $(D reallocate), regardless of arguments or
+ result.
+ */
+ numReallocate = 1u << 5,
+ /**
+ Counts the number of calls to $(D reallocate) that succeeded. (Reallocations
+ to zero bytes count as successful.)
+ */
+ numReallocateOK = 1u << 6,
+ /**
+ Counts the number of calls to $(D reallocate) that resulted in an in-place
+ reallocation (no memory moved). If this number is close to the total number
+ of reallocations, that indicates the allocator finds room at the current
+ block's end in a large fraction of the cases, but also that internal
+ fragmentation may be high (the size of the unit of allocation is large
+ compared to the typical allocation size of the application).
+ */
+ numReallocateInPlace = 1u << 7,
+ /**
+ Counts the number of calls to $(D deallocate).
+ */
+ numDeallocate = 1u << 8,
+ /**
+ Counts the number of calls to $(D deallocateAll).
+ */
+ numDeallocateAll = 1u << 9,
+ /**
+ Chooses all $(D numXxx) flags.
+ */
+ numAll = (1u << 10) - 1,
+ /**
+ Tracks total cumulative bytes allocated by means of $(D allocate),
+ $(D expand), and $(D reallocate) (when resulting in an expansion). This
+ number always grows and indicates allocation traffic. To compute bytes
+ currently allocated, subtract $(D bytesDeallocated) (below) from
+ $(D bytesAllocated).
+ */
+ bytesAllocated = 1u << 10,
+ /**
+ Tracks total cumulative bytes deallocated by means of $(D deallocate) and
+ $(D reallocate) (when resulting in a contraction). This number always grows
+ and indicates deallocation traffic.
+ */
+ bytesDeallocated = 1u << 11,
+ /**
+ Tracks the sum of all $(D delta) values in calls of the form
+ $(D expand(b, delta)) that succeed (return $(D true)).
+ */
+ bytesExpanded = 1u << 12,
+ /**
+ Tracks the sum of all $(D b.length - s) with $(D b.length > s) in calls of
+ the form $(D realloc(b, s)) that succeed (return $(D true)).
+ */
+ bytesContracted = 1u << 13,
+ /**
+ Tracks the sum of all bytes moved as a result of calls to $(D realloc) that
+ were unable to reallocate in place. A large number (relative to $(D
+ bytesAllocated)) indicates that the application should use larger
+ preallocations.
+ */
+ bytesMoved = 1u << 14,
+ /**
+ Measures the sum of extra bytes allocated beyond the bytes requested, i.e.
+ the $(WEB goo.gl/YoKffF, internal fragmentation). This is the current
+ effective number of slack bytes, and it goes up and down with time.
+ */
+ bytesSlack = 1u << 15,
+ /**
+ Measures the maximum bytes allocated over the time. This is useful for
+ dimensioning allocators.
+ */
+ bytesHighTide = 1u << 16,
+ /**
+ Chooses all $(D byteXxx) flags.
+ */
+ bytesAll = ((1u << 17) - 1) & ~numAll,
+ /**
+ Instructs $(D AllocatorWithStats) to store the size asked by the caller for
+ each allocation. All per-allocation data is stored just before the actually
+ allocation (see $(D AffixAllocator)).
+ */
+ callerSize = 1u << 17,
+ /**
+ Instructs $(D AllocatorWithStats) to store the caller module for each
+ allocation.
+ */
+ callerModule = 1u << 18,
+ /**
+ Instructs $(D AllocatorWithStats) to store the caller's file for each
+ allocation.
+ */
+ callerFile = 1u << 19,
+ /**
+ Instructs $(D AllocatorWithStats) to store the caller $(D __FUNCTION__) for
+ each allocation.
+ */
+ callerFunction = 1u << 20,
+ /**
+ Instructs $(D AllocatorWithStats) to store the caller's line for each
+ allocation.
+ */
+ callerLine = 1u << 21,
+ /**
+ Instructs $(D AllocatorWithStats) to store the time of each allocation.
+ */
+ callerTime = 1u << 22,
+ /**
+ Chooses all $(D callerXxx) flags.
+ */
+ callerAll = ((1u << 23) - 1) & ~numAll & ~bytesAll,
+ /**
+ Combines all flags above.
+ */
+ all = (1u << 23) - 1
+}
+
+/**
+
+Allocator that collects extra data about allocations. Since each piece of
+information adds size and time overhead, statistics can be individually enabled
+or disabled through compile-time $(D flags).
+
+All stats of the form $(D numXxx) record counts of events occurring, such as
+calls to functions and specific results. The stats of the form $(D bytesXxx)
+collect cumulative sizes.
+
+In addition, the data $(D callerSize), $(D callerModule), $(D callerFile), $(D
+callerLine), and $(D callerTime) is associated with each specific allocation.
+This data prefixes each allocation.
+
+*/
+struct AllocatorWithStats(Allocator, uint flags = Options.all)
+{
+private:
+ // Per-allocator state
+ mixin(define("ulong",
+ "numOwns",
+ "numAllocate",
+ "numAllocateOK",
+ "numExpand",
+ "numExpandOK",
+ "numReallocate",
+ "numReallocateOK",
+ "numReallocateInPlace",
+ "numDeallocate",
+ "numDeallocateAll",
+ "bytesAllocated",
+ "bytesDeallocated",
+ "bytesExpanded",
+ "bytesContracted",
+ "bytesMoved",
+ "bytesSlack",
+ "bytesHighTide",
+ ));
+
+ static string define(string type, string[] names...)
+ {
+ string result;
+ foreach (v; names)
+ result ~= "static if (flags & Options."~v~") {"
+ "private "~type~" _"~v~";"
+ "public const("~type~") "~v~"() const { return _"~v~"; }"
+ "}";
+ return result;
+ }
+
+ void add(string counter)(Signed!size_t n)
+ {
+ mixin("static if (flags & Options." ~ counter
+ ~ ") _" ~ counter ~ " += n;");
+ }
+
+ void up(string counter)() { add!counter(1); }
+ void down(string counter)() { add!counter(-1); }
+
+ version (StdDdoc)
+ {
+ /**
+ Read-only properties enabled by the homonym $(D flags) chosen by the
+ user.
+
+ Example:
+ ----
+ AllocatorWithStats!(Mallocator,
+ Options.bytesAllocated | Options.bytesDeallocated) a;
+ auto d1 = a.allocate(10);
+ auto d2 = a.allocate(11);
+ a.deallocate(d1);
+ assert(a.bytesAllocated == 21);
+ assert(a.bytesDeallocated == 10);
+ ----
+ */
+ @property ulong numOwns() const;
+ /// Ditto
+ @property ulong numAllocate() const;
+ /// Ditto
+ @property ulong numAllocateOK() const;
+ /// Ditto
+ @property ulong numExpand() const;
+ /// Ditto
+ @property ulong numExpandOK() const;
+ /// Ditto
+ @property ulong numReallocate() const;
+ /// Ditto
+ @property ulong numReallocateOK() const;
+ /// Ditto
+ @property ulong numReallocateInPlace() const;
+ /// Ditto
+ @property ulong numDeallocate() const;
+ /// Ditto
+ @property ulong numDeallocateAll() const;
+ /// Ditto
+ @property ulong bytesAllocated() const;
+ /// Ditto
+ @property ulong bytesDeallocated() const;
+ /// Ditto
+ @property ulong bytesExpanded() const;
+ /// Ditto
+ @property ulong bytesContracted() const;
+ /// Ditto
+ @property ulong bytesMoved() const;
+ /// Ditto
+ @property ulong bytesSlack() const;
+ /// Ditto
+ @property ulong bytesHighTide() const;
+ }
+
+ // Do flags require any per allocation state?
+ enum hasPerAllocationState = flags & (Options.callerTime
+ | Options.callerModule | Options.callerFile | Options.callerLine);
+
+ version (StdDdoc)
+ {
+ /**
+ Per-allocation information that can be iterated upon by using
+ $(D byAllocation). This only tracks live allocations and is useful for
+ e.g. tracking memory leaks.
+
+ Example:
+ ----
+ AllocatorWithStats!(Mallocator, Options.all) a;
+ auto d1 = a.allocate(10);
+ auto d2 = a.allocate(11);
+ a.deallocate(d1);
+ foreach (ref e; a.byAllocation)
+ {
+ writeln("Allocation module: ", e.callerModule);
+ }
+ ----
+ */
+ public struct AllocationInfo
+ {
+ /**
+ Read-only property defined by the corresponding flag chosen in
+ $(D options).
+ */
+ @property size_t callerSize() const;
+ /// Ditto
+ @property string callerModule() const;
+ /// Ditto
+ @property string callerFile() const;
+ /// Ditto
+ @property uint callerLine() const;
+ /// Ditto
+ @property uint callerFunction() const;
+ /// Ditto
+ @property const(SysTime) callerTime() const;
+ }
+ }
+ else static if (hasPerAllocationState)
+ {
+ public struct AllocationInfo
+ {
+ import std.datetime;
+ mixin(define("string", "callerModule", "callerFile",
+ "callerFunction"));
+ mixin(define("uint", "callerLine"));
+ mixin(define("size_t", "callerSize"));
+ mixin(define("SysTime", "callerTime"));
+ private AllocationInfo* _prev, _next;
+ }
+ AllocationInfo* _root;
+ alias MyAllocator = AffixAllocator!(Allocator, AllocationInfo);
+
+ public auto byAllocation()
+ {
+ struct Voldemort
+ {
+ private AllocationInfo* _root;
+ bool empty() { return _root is null; }
+ ref AllocationInfo front() { return *_root; }
+ void popFront() { _root = _root._next; }
+ Voldemort save() { return this; }
+ }
+ return Voldemort(_root);
+ }
+ }
+ else
+ {
+ alias MyAllocator = Allocator;
+ }
+
+public:
+ // Parent allocator (publicly accessible)
+ static if (stateSize!MyAllocator) MyAllocator parent;
+ else alias parent = MyAllocator.it;
+
+ enum uint alignment = Allocator.alignment;
+
+ static if (hasMember!(Allocator, "owns"))
+ bool owns(void[] b)
+ {
+ up!"numOwns";
+ return parent.owns(b);
+ }
+
+ void[] allocate
+ (string m = __MODULE__, string f = __FILE__, ulong n = __LINE__,
+ string fun = __FUNCTION__)
+ (size_t bytes)
+ {
+ up!"numAllocate";
+ auto result = parent.allocate(bytes);
+ add!"bytesAllocated"(result.length);
+ add!"bytesSlack"(this.goodAllocSize(result.length) - result.length);
+ add!"numAllocateOK"(result || !bytes); // allocating 0 bytes is OK
+ static if (flags & Options.bytesHighTide)
+ {
+ const bytesNow = bytesAllocated - bytesDeallocated;
+ if (_bytesHighTide < bytesNow) _bytesHighTide = bytesNow;
+ }
+ static if (hasPerAllocationState)
+ {
+ auto p = &parent.prefix(result);
+ static if (flags & Options.callerSize)
+ p._callerSize = bytes;
+ static if (flags & Options.callerModule)
+ p._callerModule = m;
+ static if (flags & Options.callerFile)
+ p._callerFile = f;
+ static if (flags & Options.callerFunction)
+ p._callerFunction = fun;
+ static if (flags & Options.callerLine)
+ p._callerLine = n;
+ static if (flags & Options.callerTime)
+ {
+ import std.datetime;
+ p._callerTime = Clock.currTime;
+ }
+ // Wire the new info into the list
+ assert(p._prev is null);
+ p._next = _root;
+ if (_root) _root._prev = p;
+ _root = p;
+ }
+ return result;
+ }
+
+ static if (hasMember!(Allocator, "expand"))
+ bool expand(ref void[] b, size_t s)
+ {
+ up!"numExpand";
+ static if (flags & Options.bytesSlack)
+ const bytesSlackB4 = goodAllocSize(b.length) - b.length;
+ auto result = parent.expand(b, s);
+ if (result)
+ {
+ up!"numExpandOK";
+ add!"bytesExpanded"(s);
+ add!"bytesSlack"(goodAllocSize(b.length) - b.length - bytesSlackB4);
+ }
+ return result;
+ }
+
+ bool reallocate(ref void[] b, size_t s)
+ {
+ up!"numReallocate";
+ static if (flags & Options.bytesSlack)
+ const bytesSlackB4 = this.goodAllocSize(b.length) - b.length;
+ static if (flags & Options.numReallocateInPlace)
+ const oldB = b.ptr;
+ static if (flags & Options.bytesMoved)
+ const oldLength = b.length;
+ static if (hasPerAllocationState)
+ const reallocatingRoot = b && _root is &parent.prefix(b);
+ if (!parent.reallocate(b, s)) return false;
+ up!"numReallocateOK";
+ add!"bytesSlack"(this.goodAllocSize(b.length) - b.length
+ - bytesSlackB4);
+ if (oldB == b.ptr)
+ {
+ // This was an in-place reallocation, yay
+ up!"numReallocateInPlace";
+ const Signed!size_t delta = b.length - oldLength;
+ if (delta >= 0)
+ {
+ // Expansion
+ add!"bytesAllocated"(delta);
+ add!"bytesExpanded"(delta);
+ }
+ else
+ {
+ // Contraction
+ add!"bytesDeallocated"(-delta);
+ add!"bytesContracted"(-delta);
+ }
+ }
+ else
+ {
+ // This was a allocate-move-deallocate cycle
+ add!"bytesAllocated"(b.length);
+ add!"bytesMoved"(oldLength);
+ add!"bytesDeallocated"(oldLength);
+ static if (hasPerAllocationState)
+ {
+ // Stitch the pointers again, ho-hum
+ auto p = &parent.prefix(b);
+ if (p._next) p._next._prev = p;
+ if (p._prev) p._prev._next = p;
+ if (reallocatingRoot) _root = p;
+ }
+ }
+ return true;
+ }
+
+ void deallocate(void[] b)
+ {
+ up!"numDeallocate";
+ add!"bytesDeallocated"(b.length);
+ add!"bytesSlack"(-(this.goodAllocSize(b.length) - b.length));
+ // Remove the node from the list
+ static if (hasPerAllocationState)
+ {
+ auto p = &parent.prefix(b);
+ if (p._next) p._next._prev = p._prev;
+ if (p._prev) p._prev._next = p._next;
+ if (_root is p) _root = p._next;
+ }
+ parent.deallocate(b);
+ }
+
+ static if (hasMember!(Allocator, "deallocateAll"))
+ void deallocateAll()
+ {
+ up!"numDeallocateAll";
+ // Must force bytesDeallocated to match bytesAllocated
+ static if ((flags & Options.bytesDeallocated)
+ && (flags & Options.bytesDeallocated))
+ _bytesDeallocated = _bytesAllocated;
+ parent.deallocateAll();
+ static if (hasPerAllocationState) _root = null;
+ }
+}
+
+unittest
+{
+ void test(Allocator)()
+ {
+ Allocator a;
+ auto b1 = a.allocate(100);
+ assert(a.numAllocate == 1);
+ auto b2 = a.allocate(101);
+ assert(a.numAllocate == 2);
+ assert(a.bytesAllocated == 201);
+ auto b3 = a.allocate(202);
+ assert(a.numAllocate == 3);
+ assert(a.bytesAllocated == 403);
+
+ assert(walkLength(a.byAllocation) == 3);
+
+ foreach (ref e; a.byAllocation)
+ {
+ if (false) writeln(e);
+ }
+
+ a.deallocate(b2);
+ assert(a.numDeallocate == 1);
+ a.deallocate(b1);
+ assert(a.numDeallocate == 2);
+ a.deallocate(b3);
+ assert(a.numDeallocate == 3);
+ assert(a.numAllocate == a.numDeallocate);
+ assert(a.bytesDeallocated == 403);
+ }
+
+ test!(AllocatorWithStats!Mallocator)();
+ test!(AllocatorWithStats!(Freelist!(Mallocator, 128)))();
+}
+
+//struct ArrayOfAllocators(alias make)
+//{
+// alias Allocator = typeof(make());
+// private Allocator[] allox;
+
+// void[] allocate(size_t bytes)
+// {
+// void[] result = allocateNoGrow(bytes);
+// if (result) return result;
+// // Everything's full to the brim, create a new allocator.
+// auto newAlloc = make();
+// assert(&newAlloc !is newAlloc.initial);
+// // Move the array to the new allocator
+// assert(Allocator.alignment % Allocator.alignof == 0);
+// const arrayBytes = (allox.length + 1) * Allocator.sizeof;
+// Allocator[] newArray = void;
+// do
+// {
+// if (arrayBytes < bytes)
+// {
+// // There is a chance we can find room in the existing allocator.
+// newArray = cast(Allocator[]) allocateNoGrow(arrayBytes);
+// if (newArray) break;
+// }
+// newArray = cast(Allocator[]) newAlloc.allocate(arrayBytes);
+// writeln(newArray.length);
+// assert(newAlloc.initial !is &newArray[$ - 1]);
+// if (!newArray) return null;
+// } while (false);
+
+// assert(newAlloc.initial !is &newArray[$ - 1]);
+
+// // Move data over to the new position
+// foreach (i, ref e; allox)
+// {
+// writeln(&e, " ", e.base.store_.ptr, " ", e.initial);
+// e.move(newArray[i]);
+// }
+// auto recoveredBytes = allox.length * Allocator.sizeof;
+// static if (hasMember!(Allocator, "deallocate"))
+// deallocate(allox);
+// allox = newArray;
+// assert(&allox[$ - 1] !is newAlloc.initial);
+// newAlloc.move(allox[$ - 1]);
+// assert(&allox[$ - 1] !is allox[$ - 1].initial);
+// if (recoveredBytes >= bytes)
+// {
+// // The new request may be served from the just-freed memory. Recurse
+// // and be bold.
+// return allocateNoGrow(bytes);
+// }
+// // Otherwise, we can't possibly fetch memory from anywhere else but the
+// // fresh new allocator.
+// return allox.back.allocate(bytes);
+// }
+
+// private void[] allocateNoGrow(size_t bytes)
+// {
+// void[] result;
+// foreach (ref a; allox)
+// {
+// result = a.allocate(bytes);
+// if (result) break;
+// }
+// return result;
+// }
+
+// bool owns(void[] b)
+// {
+// foreach (i, ref a; allox)
+// {
+// if (a.owns(b)) return true;
+// }
+// return false;
+// }
+
+// static if (hasMember!(Allocator, "deallocate"))
+// void deallocate(void[] b)
+// {
+// foreach (i, ref a; allox)
+// {
+// if (!a.owns(b)) continue;
+// a.deallocate(b);
+// break;
+// }
+// }
+//}
+//
+//version(none) unittest
+//{
+// ArrayOfAllocators!({ return Region!()(new void[1024 * 4096]); }) a;
+// assert(a.allox.length == 0);
+// auto b1 = a.allocate(1024 * 8192);
+// assert(b1 is null);
+// b1 = a.allocate(1024 * 10);
+// assert(b1.length == 1024 * 10);
+// assert(a.allox.length == 1);
+// auto b2 = a.allocate(1024 * 4095);
+// assert(a.allox.length == 2);
+//}
+
+
+/**
+Given $(D make) as a function that returns fresh allocators, $(D
+CascadingAllocator) creates an allocator that lazily creates as many allocators
+are needed for satisfying client allocation requests.
+
+The management data of the allocators is stored in memory obtained from the
+allocators themselves, in a private linked list.
+*/
+struct CascadingAllocator(alias make)
+{
+ /// Alias for $(D typeof(make)).
+ alias typeof(make()) Allocator;
+ private struct Node
+ {
+ Allocator a;
+ Node* next;
+ bool nextIsInitialized;
+ }
+ private Node* _root;
+
+ /**
+ Standard primitives.
+ */
+ enum uint alignment = Allocator.alignment;
+
+ /// Ditto
+ void[] allocate(size_t s)
+ {
+ auto result = allocateNoGrow(s);
+ if (result) return result;
+ // Must create a new allocator object
+ if (!_root)
+ {
+ // I mean _brand_ new allocator object
+ auto newNodeStack = Node(make());
+ // Weird: store the new node inside its own allocated storage!
+ _root = cast(Node*) newNodeStack.a.allocate(Node.sizeof).ptr;
+ if (!_root)
+ {
+ // Are you serious? Not even the first allocation?
+ return null;
+ }
+ newNodeStack.move(*_root);
+ // Make sure we reserve room for the next next node
+ _root.next = cast(Node*) _root.a.allocate(Node.sizeof).ptr;
+ assert(_root.next);
+ // root is set up, serve from it
+ return allocateNoGrow(s);
+ }
+ // No room left, must append a new allocator
+ auto n = _root;
+ while (n.nextIsInitialized) n = n.next;
+ if (!n.next)
+ {
+ // Resources truly exhausted, not much to do
+ return null;
+ }
+ emplace(n.next, Node(make()));
+ n.nextIsInitialized = true;
+ // Reserve room for the next next allocator
+ n.next.next = cast(Node*) allocateNoGrow(Node.sizeof).ptr;
+ // Rare failure cases leave nextIsInitialized to false
+ if (!n.next.next) n.nextIsInitialized = false;
+ // TODO: would be nice to bring the new allocator to the front.
+ // All done!
+ return allocateNoGrow(s);
+ }
+
+ private void[] allocateNoGrow(size_t bytes)
+ {
+ void[] result;
+ if (!_root) return result;
+ for (auto n = _root; ; n = n.next)
+ {
+ result = n.a.allocate(bytes);
+ if (result) break;
+ if (!n.nextIsInitialized) break;
+ }
+ return result;
+ }
+
+ /// Defined only if $(D Allocator.owns) is defined.
+ static if (hasMember!(Allocator, "owns"))
+ bool owns(void[] b)
+ {
+ if (!_root) return b is null;
+ for (auto n = _root; ; n = n.next)
+ {
+ if (n.a.owns(b)) return true;
+ if (!n.nextIsInitialized) break;
+ }
+ return false;
+ }
+
+ /// Defined only if $(D Allocator.expand) is defined.
+ static if (hasMember!(Allocator, "expand"))
+ bool expand(ref void[] b, size_t delta)
+ {
+ if (!b) return (b = allocate(delta)) !is null;
+ if (!_root) return false;
+ for (auto n = _root; ; n = n.next)
+ {
+ if (n.a.owns(b)) return n.a.expand(b, delta);
+ if (!n.nextIsInitialized) break;
+ }
+ return false;
+ }
+
+ /// Allows moving data from one $(D Allocator) to another.
+ bool reallocate(ref void[] b, size_t s)
+ {
+ if (!b) return (b = allocate(s)) !is null;
+ // First attempt to reallocate within the existing node
+ if (!_root) return false;
+ for (auto n = _root; ; n = n.next)
+ {
+ if (n.a.owns(b) && n.a.reallocate(b, s)) return true;
+ if (!n.nextIsInitialized) break;
+ }
+ // Failed, but we may find new memory in a new node.
+ auto newB = allocate(s);
+ if (!newB) return false;
+ newB[] = b[];
+ static if (hasMember!(Allocator, "deallocate"))
+ deallocate(b);
+ b = newB;
+ return true;
+ }
+
+ /// Defined only if $(D Allocator.deallocate) is defined.
+ static if (hasMember!(Allocator, "deallocate"))
+ void deallocate(void[] b)
+ {
+ if (!_root)
+ {
+ assert(b is null);
+ return;
+ }
+ for (auto n = _root; ; n = n.next)
+ {
+ if (n.a.owns(b)) return n.a.deallocate(b);
+ if (!n.nextIsInitialized) break;
+ }
+ assert(false);
+ }
+
+ /// Defined only if $(D Allocator.deallocateAll) is defined.
+ static if (hasMember!(Allocator, "deallocateAll"))
+ void deallocateAll()
+ {
+ if (!_root) return;
+ // This is tricky because the list of allocators is threaded through the
+ // allocators themselves. Malloc to the rescue!
+ // First compute the number of allocators
+ uint k = 0;
+ for (auto n = _root; ; n = n.next)
+ {
+ ++k;
+ if (!n.nextIsInitialized) break;
+ }
+ auto nodes =
+ cast(Node*[]) Mallocator.it.allocate(k * (Allocator*).sizeof);
+ scope(exit) Mallocator.it.deallocate(nodes);
+ foreach (ref n; nodes)
+ {
+ n = _root;
+ _root = _root.next;
+ }
+ _root = null;
+ // Now we can deallocate in peace
+ foreach (n; nodes)
+ {
+ n.a.deallocateAll();
+ }
+ }
+}
+
+///
+unittest
+{
+ // Create an allocator based upon 4MB regions, fetched from the GC heap.
+ CascadingAllocator!({ return Region!()(new void[1024 * 4096]); }) a;
+ auto b1 = a.allocate(1024 * 8192);
+ assert(b1 is null); // can't allocate more than 4MB at a time
+ b1 = a.allocate(1024 * 10);
+ assert(b1.length == 1024 * 10);
+ a.deallocateAll();
+}
+
+unittest
+{
+ CascadingAllocator!({ return Region!()(new void[1024 * 4096]); }) a;
+ auto b1 = a.allocate(1024 * 8192);
+ assert(b1 is null);
+ assert(!a._root.nextIsInitialized);
+ b1 = a.allocate(1024 * 10);
+ assert(b1.length == 1024 * 10);
+ auto b2 = a.allocate(1024 * 4095);
+ assert(a._root.nextIsInitialized);
+ a.deallocateAll();
+ assert(!a._root);
+}
+
+/**
+Dispatches allocations (and deallocations) between two allocators ($(D
+SmallAllocator) and $(D LargeAllocator)) depending on the size allocated, as
+follows. All allocations smaller than or equal to $(D threshold) will be
+dispatched to $(D SmallAllocator). The others will go to $(D LargeAllocator).
+
+If both allocators are $(D shared), the $(D Segregator) will also offer $(D
+shared) methods.
+*/
+struct Segregator(size_t threshold, SmallAllocator, LargeAllocator)
+{
+ static if (stateSize!SmallAllocator) SmallAllocator _small;
+ else static alias SmallAllocator.it _small;
+ static if (stateSize!LargeAllocator) LargeAllocator _large;
+ else alias LargeAllocator.it _large;
+
+ version (StdDdoc)
+ {
+ /**
+ The alignment offered is the minimum of the two allocators' alignment.
+ */
+ enum uint alignment;
+ /**
+ This method is defined only if at least one of the allocators defines
+ it. The good allocation size is obtained from $(D SmallAllocator) if $(D
+ s <= threshold), or $(D LargeAllocator) otherwise. (If one of the
+ allocators does not define $(D goodAllocSize), the default
+ implementation in this module applies.)
+ */
+ static size_t goodAllocSize(size_t s);
+ /**
+ The memory is obtained from $(D SmallAllocator) if $(D s <= threshold),
+ or $(D LargeAllocator) otherwise.
+ */
+ void[] allocate(size_t);
+ /**
+ This method is defined only if both allocators define it. The call is
+ forwarded to $(D SmallAllocator) if $(D b.length <= threshold), or $(D
+ LargeAllocator) otherwise.
+ */
+ bool owns(void[] b);
+ /**
+ This method is defined only if at least one of the allocators defines
+ it. If $(D SmallAllocator) defines $(D expand) and $(D b.length +
+ delta <= threshold), the call is forwarded to $(D SmallAllocator). If $(
+ LargeAllocator) defines $(D expand) and $(D b.length > threshold), the
+ call is forwarded to $(D LargeAllocator). Otherwise, the call returns
+ $(D false).
+ */
+ bool expand(ref void[] b, size_t delta);
+ /**
+ This method is defined only if at least one of the allocators defines
+ it. If $(D SmallAllocator) defines $(D reallocate) and $(D b.length <=
+ threshold && s <= threshold), the call is forwarded to $(D
+ SmallAllocator). If $(D LargeAllocator) defines $(D expand) and $(D
+ b.length > threshold && s > threshold), the call is forwarded to $(D
+ LargeAllocator). Otherwise, the call returns $(D false).
+ */
+ bool reallocate(ref void[] b, size_t s);
+ /**
+ This function is defined only if both allocators define it, and forwards
+ appropriately depending on $(D b.length).
+ */
+ void deallocate(void[] b);
+ /**
+ This function is defined only if both allocators define it, and calls
+ $(D deallocateAll) for them in turn.
+ */
+ void deallocateAll();
+ }
+
+ /**
+ Composite allocators involving nested instantiations of $(D Segregator) make
+ it difficult to access individual sub-allocators stored within. $(D
+ allocatorForSize) simplifies the task by supplying the allocator nested
+ inside a $(D Segregator) that is responsible for a specific size $(D s).
+
+ Example:
+ ----
+ alias A = Segregator!(300,
+ Segregator!(200, A1, A2),
+ A3);
+ A a;
+ static assert(typeof(a.allocatorForSize!10) == A1);
+ static assert(typeof(a.allocatorForSize!250) == A2);
+ static assert(typeof(a.allocatorForSize!301) == A3);
+ ----
+ */
+ ref auto allocatorForSize(size_t s)()
+ {
+ static if (s <= threshold)
+ static if (is(SmallAllocator == Segregator!(Args), Args...))
+ return _small.allocatorForSize!s;
+ else return _small;
+ else
+ static if (is(LargeAllocator == Segregator!(Args), Args...))
+ return _large.allocatorForSize!s;
+ else return _large;
+ }
+
+ enum uint alignment = min(SmallAllocator.alignment,
+ LargeAllocator.alignment);
+
+ template Impl()
+ {
+ void[] allocate(size_t s)
+ {
+ return s <= threshold ? _small.allocate(s) : _large.allocate(s);
+ }
+
+ static if (hasMember!(SmallAllocator, "deallocate")
+ && hasMember!(LargeAllocator, "deallocate"))
+ void deallocate(void[] data)
+ {
+ data.length <= threshold
+ ? _small.deallocate(data)
+ : _large.deallocate(data);
+ }
+
+ size_t goodAllocSize(size_t s)
+ {
+ return s <= threshold
+ ? _small.goodAllocSize(s)
+ : _large.goodAllocSize(s);
+ }
+
+ static if (hasMember!(SmallAllocator, "owns")
+ && hasMember!(LargeAllocator, "owns"))
+ bool owns(void[] b)
+ {
+ return b.length <= threshold ? _small.owns(b) : _large.owns(b);
+ }
+
+ static if (hasMember!(SmallAllocator, "expand")
+ || hasMember!(LargeAllocator, "expand"))
+ bool expand(ref void[] b, size_t delta)
+ {
+ if (b.length + delta <= threshold)
+ {
+ // Old and new allocations handled by _small
+ static if (hasMember!(SmallAllocator, "expand"))
+ return _small.expand(b, delta);
+ else
+ return false;
+ }
+ if (b.length > threshold)
+ {
+ // Old and new allocations handled by _large
+ static if (hasMember!(LargeAllocator, "expand"))
+ return _large.expand(b, delta);
+ else
+ return false;
+ }
+ // Oops, cross-allocator transgression
+ return false;
+ }
+
+ static if (hasMember!(SmallAllocator, "reallocate")
+ || hasMember!(LargeAllocator, "reallocate"))
+ bool reallocate(ref void[] b, size_t s)
+ {
+ static if (hasMember!(SmallAllocator, "reallocate"))
+ if (b.length <= threshold && s <= threshold)
+ {
+ // Old and new allocations handled by _small
+ return _small.reallocate(b, s);
+ }
+ static if (hasMember!(LargeAllocator, "reallocate"))
+ if (b.length > threshold && s > threshold)
+ {
+ // Old and new allocations handled by _large
+ return _large.reallocate(b, s);
+ }
+ // Cross-allocator transgression
+ return .reallocate(this, b, s);
+ }
+
+ static if (hasMember!(SmallAllocator, "deallocateAll")
+ && hasMember!(LargeAllocator, "deallocateAll"))
+ void deallocateAll()
+ {
+ _small.deallocateAll();
+ _large.deallocateAll();
+ }
+ }
+
+ enum sharedMethods =
+ !stateSize!SmallAllocator
+ && !stateSize!LargeAllocator
+ && is(typeof(SmallAllocator.it) == shared)
+ && is(typeof(LargeAllocator.it) == shared);
+ //pragma(msg, sharedMethods);
+
+ static if (sharedMethods)
+ {
+ static shared Segregator it;
+ shared { mixin Impl!(); }
+ }
+ else
+ {
+ static if (!stateSize!SmallAllocator && !stateSize!LargeAllocator)
+ static __gshared Segregator it;
+ mixin Impl!();
+ }
+}
+
+///
+unittest
+{
+ alias A =
+ Segregator!(
+ 1024 * 4,
+ Segregator!(
+ 128, Freelist!(Mallocator, 0, 128),
+ GCAllocator),
+ Segregator!(
+ 1024 * 1024, Mallocator,
+ GCAllocator)
+ );
+ A a;
+ auto b = a.allocate(200);
+ assert(b.length == 200);
+ a.deallocate(b);
+}
+
+/**
+A $(D Segregator) with more than three arguments expands to a composition of
+elemental $(D Segregator)s, as illustrated by the following example:
+
+----
+alias A =
+ Segregator!(
+ n1, A1,
+ n2, A2,
+ n3, A3,
+ A4
+ );
+----
+
+With this definition, allocation requests for $(D n1) bytes or less are directed
+to $(D A1); requests between $(D n1 + 1) and $(D n2) bytes (inclusive) are
+directed to $(D A2); requests between $(D n2 + 1) and $(D n3) bytes (inclusive)
+are directed to $(D A3); and requests for more than $(D n3) bytes are directed
+to $(D A4). If some particular range should not be handled, $(D NullAllocator)
+may be used appropriately.
+
+*/
+template Segregator(Args...) if (Args.length > 3)
+{
+ // Binary search
+ private enum cutPoint = ((Args.length - 2) / 4) * 2;
+ static if (cutPoint >= 2)
+ {
+ alias Segregator = .Segregator!(
+ Args[cutPoint],
+ .Segregator!(Args[0 .. cutPoint], Args[cutPoint + 1]),
+ .Segregator!(Args[cutPoint + 2 .. $])
+ );
+ }
+ else
+ {
+ // Favor small sizes
+ alias Segregator = .Segregator!(
+ Args[0],
+ Args[1],
+ .Segregator!(Args[2 .. $])
+ );
+ }
+
+ // Linear search
+ //alias Segregator = .Segregator!(
+ // Args[0], Args[1],
+ // .Segregator!(Args[2 .. $])
+ //);
+}
+
+///
+unittest
+{
+ alias A =
+ Segregator!(
+ 128, Freelist!(Mallocator, 0, 128),
+ 1024 * 4, GCAllocator,
+ 1024 * 1024, Mallocator,
+ GCAllocator
+ );
+ A a;
+ auto b = a.allocate(201);
+ assert(b.length == 201);
+ a.deallocate(b);
+}
+
+/**
+
+A $(D Bucketizer) uses distinct allocators for handling allocations of sizes in
+the intervals $(D [min, min + step - 1]), $(D [min + step, min + 2 * step - 1]),
+$(D [min + 2 * step, min + 3 * step - 1]), $(D ...), $(D [max - step + 1, max]).
+
+$(D Bucketizer) holds a fixed-size array of allocators and dispatches calls to
+them appropriately. The size of the array is $(D (max + 1 - min) / step), which
+must be an exact division.
+
+Allocations for sizes smaller than $(D min) or larger than $(D max) are illegal
+for $(D Bucketizer). To handle them separately, $(D Segregator) may be of use.
+
+*/
+struct Bucketizer(Allocator, size_t min, size_t max, size_t step)
+{
+ static assert((max - (min - 1)) % step == 0,
+ "Invalid limits when instantiating " ~ Bucketizer.stringof);
+
+ //static if (min == chooseAtRuntime) size_t _min;
+ //else alias _min = min;
+ //static if (max == chooseAtRuntime) size_t _max;
+ //else alias _max = max;
+ //static if (step == chooseAtRuntime) size_t _step;
+ //else alias _step = step;
+
+ /// The array of allocators is publicly available for e.g. initialization
+ /// and inspection.
+ Allocator buckets[(max - (min - 1)) / step];
+
+ /**
+ The alignment offered is the same as $(D Allocator.alignment).
+ */
+ enum uint alignment = Allocator.alignment;
+
+ /**
+ Rounds up to the maximum size of the bucket in which $(D bytes) falls.
+ */
+ size_t goodAllocSize(size_t bytes) const
+ {
+ // round up bytes such that bytes - min + 1 is a multiple of step
+ assert(bytes >= min);
+ const min_1 = min - 1;
+ return min_1 + roundUpToMultipleOf(bytes - min_1, step);
+ }
+
+ /**
+ Returns $(D b.length >= min && b.length <= max).
+ */
+ bool owns(void[] b) const
+ {
+ return b.length >= min && b.length <= max;
+ }
+
+ /**
+ Directs the call to either one of the $(D buckets) allocators.
+ */
+ void[] allocate(size_t bytes)
+ {
+ // Choose the appropriate allocator
+ const i = (bytes - min) / step;
+ assert(i < buckets.length);
+ const actual = goodAllocSize(bytes);
+ auto result = buckets[i].allocate(actual);
+ return result.ptr[0 .. bytes];
+ }
+
+ /**
+ This method allows expansion within the respective bucket range. It succeeds
+ if both $(D b.length) and $(D b.length + delta) fall in a range of the form
+ $(D [min + k * step, min + (k + 1) * step - 1]).
+ */
+ bool expand(ref void[] b, size_t delta)
+ {
+ assert(b.length >= min && b.length <= max);
+ const available = goodAllocSize(b.length);
+ const desired = b.length + delta;
+ if (available < desired) return false;
+ b = b.ptr[0 .. desired];
+ return true;
+ }
+
+ /**
+ This method is only defined if $(D Allocator) defines $(D deallocate).
+ */
+ static if (hasMember!(Allocator, "deallocate"))
+ void deallocate(void[] b)
+ {
+ const i = (b.length - min) / step;
+ assert(i < buckets.length);
+ const actual = goodAllocSize(b.length);
+ buckets.ptr[i].deallocate(b.ptr[0 .. actual]);
+ }
+
+ /**
+ This method is only defined if all allocators involved define $(D
+ deallocateAll), and calls it for each bucket in turn.
+ */
+ static if (hasMember!(Allocator, "deallocateAll"))
+ void deallocateAll()
+ {
+ foreach (ref a; buckets)
+ {
+ a.deallocateAll();
+ }
+ }
+}
+
+///
+unittest
+{
+ Bucketizer!(Freelist!(Mallocator, 0, unbounded),
+ 65, 512, 64) a;
+ auto b = a.allocate(400);
+ assert(b.length == 400, text(b.length));
+ a.deallocate(b);
+}
+
+/**
+Dynamic version of an allocator. This should be used wherever a uniform type is
+required for encapsulating various allocator implementations.
+
+TODO: add support for $(D shared).
+*/
+class CAllocator
+{
+ /// Returns the alignment offered. By default this method returns $(D
+ /// platformAlignment).
+ @property uint alignment()
+ {
+ return platformAlignment;
+ }
+
+ /**
+ Sets the alignment and returns $(D true) on success, $(D false) if not
+ supported. By default returns $(D false). An allocator implementation could
+ throw an exception if it does allow setting the alignment but an invalid
+ value is passed.
+ */
+ @property bool alignment(uint)
+ {
+ return false;
+ }
+
+ /**
+ Returns the good allocation size that guarantees zero internal
+ fragmentation. By default returns $(D s) rounded up to the nearest multiple
+ of $(D alignment).
+ */
+ size_t goodAllocSize(size_t s)
+ {
+ return s.roundUpToMultipleOf(alignment);
+ }
+
+ /**
+ Allocates memory.
+ */
+ abstract void[] allocate(size_t);
+
+ /**
+ Returns $(D true) if the allocator supports $(D owns). By default returns
+ $(D false).
+ */
+ bool supportsOwns()
+ {
+ return false;
+ }
+
+ /**
+ Returns $(D true) if the allocator owns $(D b). By default issues $(D
+ assert(false)).
+ */
+ bool owns(void[] b)
+ {
+ assert(false);
+ }
+
+ /// Expands a memory block in place.
+ abstract bool expand(ref void[], size_t);
+
+ /// Reallocates a memory block.
+ abstract bool reallocate(ref void[] b, size_t);
+
+ /// Deallocates a memory block. Returns $(D false) if deallocation is not
+ /// supported.
+ abstract bool deallocate(void[]);
+
+ /// Deallocates all memory. Returns $(D false) if not supported.
+ abstract bool deallocateAll();
+
+ /// Returns $(D true) if allocator supports $(D allocateAll). By default
+ /// returns $(D false).
+ bool supportsAllocateAll()
+ {
+ return false;
+ }
+
+ /**
+ Allocates and returns all memory available to this allocator. By default
+ issues $(D assert(false)).
+ */
+ void[] allocateAll()
+ {
+ assert(false);
+ }
+}
+
+/**
+Implementation of $(D CAllocator) using $(D Allocator). This adapts a
+statically-built allocator type to a uniform dynamic interface that is directly
+usable by non-templated code.
+*/
+class CAllocatorImpl(Allocator) : CAllocator
+{
+ /**
+ The implementation is available as a public member.
+ */
+ static if (stateSize!Allocator) Allocator impl;
+ else alias impl = Allocator.it;
+
+ /// Returns $(D impl.alignment).
+ override @property uint alignment()
+ {
+ return impl.alignment;
+ }
+
+ /**
+ If $(D Allocator) supports alignment setting, performs it and returns $(D
+ true). Otherwise, returns $(D false).
+ */
+ override @property bool alignment(uint a)
+ {
+ static if (is(typeof(impl.alignment = a)))
+ {
+ impl.alignment = a;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ Returns $(D impl.goodAllocSize(s)).
+ */
+ override size_t goodAllocSize(size_t s)
+ {
+ return impl.goodAllocSize(s);
+ }
+
+ /**
+ Returns $(D impl.allocate(s)).
+ */
+ override void[] allocate(size_t s)
+ {
+ return impl.allocate(s);
+ }
+
+ /**
+ Returns $(D true) if $(D Allocator) supports $(D owns).
+ */
+ override bool supportsOwns()
+ {
+ return hasMember!(Allocator, "owns");
+ }
+
+ /**
+ Overridden only if $(D Allocator) implements $(D owns). In that case,
+ returns $(D impl.owns(b)).
+ */
+ static if (hasMember!(Allocator, "owns"))
+ override bool owns(void[] b)
+ {
+ return impl.owns(b);
+ }
+
+ /// Returns $(D impl.expand(b, s)) if defined, $(D false) otherwise.
+ override bool expand(ref void[] b, size_t s)
+ {
+ static if (hasMember!(Allocator, "expand"))
+ return impl.expand(b, s);
+ else
+ return false;
+ }
+
+ /// Returns $(D impl.reallocate(b, s)).
+ override bool reallocate(ref void[] b, size_t s)
+ {
+ return impl.reallocate(b, s);
+ }
+
+ /// Calls $(D impl.deallocate(b)) and returns $(D true) if defined,
+ /// otherwise returns $(D false).
+ override bool deallocate(void[] b)
+ {
+ static if (hasMember!(Allocator, "deallocate"))
+ {
+ impl.deallocate(b);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /// Calls $(D impl.deallocateAll()) and returns $(D true) if defined,
+ /// otherwise returns $(D false).
+ override bool deallocateAll()
+ {
+ static if (hasMember!(Allocator, "deallocateAll"))
+ {
+ impl.deallocateAll();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /// Returns $(D true) if allocator supports $(D allocateAll). By default
+ /// returns $(D false).
+ override bool supportsAllocateAll()
+ {
+ return hasMember!(Allocator, "allocateAll");
+ }
+
+ /**
+ Overridden only if $(D Allocator) implements $(D allocateAll). In that case,
+ returns $(D impl.allocateAll()).
+ */
+ static if (hasMember!(Allocator, "allocateAll"))
+ override void[] allocateAll()
+ {
+ return impl.allocateAll();
+ }
+}
+
+///
+unittest
+{
+ /// Define an allocator bound to the built-in GC.
+ CAllocator alloc = new CAllocatorImpl!GCAllocator;
+ auto b = alloc.allocate(42);
+ assert(b.length == 42);
+ assert(alloc.deallocate(b));
+
+ // Define an elaborate allocator and bind it to the class API.
+ // Note that the same variable "alloc" is used.
+ alias FList = Freelist!(GCAllocator, 0, unbounded);
+ alias A = Segregator!(
+ 8, Freelist!(GCAllocator, 0, 8),
+ 128, Bucketizer!(FList, 1, 128, 16),
+ 256, Bucketizer!(FList, 129, 256, 32),
+ 512, Bucketizer!(FList, 257, 512, 64),
+ 1024, Bucketizer!(FList, 513, 1024, 128),
+ 2048, Bucketizer!(FList, 1025, 2048, 256),
+ 3584, Bucketizer!(FList, 2049, 3584, 512),
+ 4072 * 1024, CascadingAllocator!(
+ () => HeapBlock!(GCAllocator, 4096)(4072 * 1024)),
+ GCAllocator
+ );
+
+ alloc = new CAllocatorImpl!A;
+ b = alloc.allocate(101);
+ assert(alloc.deallocate(b));
+}
+
+private bool isPowerOf2(uint x)
+{
+ return (x & (x - 1)) == 0;
+}
+
+private bool isGoodStaticAlignment(uint x)
+{
+ return x.isPowerOf2 && x > 0;
+}
+
+private bool isGoodDynamicAlignment(uint x)
+{
+ return x.isPowerOf2 && x >= (void*).sizeof;
+}
+
+__EOF__
+
+version(none) struct TemplateAllocator
+{
+ enum alignment = platformAlignment;
+ static size_t goodAllocSize(size_t s)
+ {
+ }
+ void[] allocate(size_t)
+ {
+ }
+ bool owns(void[])
+ {
+ }
+ bool expand(ref void[] b, size_t)
+ {
+ }
+ bool reallocate(ref void[] b, size_t)
+ {
+ }
+ void deallocate(void[] b)
+ {
+ }
+ void deallocateAll()
+ {
+ }
+ void[] allocateAll()
+ {
+ }
+ static shared TemplateAllocator it;
+}
diff --git a/stdx/d/parser.d b/stdx/d/parser.d
index a27fa52..07f52b0 100644
--- a/stdx/d/parser.d
+++ b/stdx/d/parser.d
@@ -12,6 +12,7 @@ module stdx.d.parser;
import stdx.d.lexer;
import stdx.d.ast;
+import stdx.allocator;
import std.conv;
import std.algorithm;
import std.array;
@@ -22,6 +23,114 @@ import std.string : format;
// Caution: generates 180 megabytes of logging for std.datetime
//version = std_parser_verbose;
+/**
+ * The parse allocator is designed so that very large number of node instances
+ * allocated by the parser can be deallocated all at once. This saves time by
+ * preventing the GC from having to scan the nodes.
+ */
+class ParseAllocator : CAllocator
+{
+public:
+
+ this(string name = "none")
+ {
+ this.name = name;
+ }
+
+ string name;
+
+ override void[] allocate(size_t size)
+ in
+ {
+ assert (size > 0);
+ assert (size < blockSize);
+ }
+ out (result)
+ {
+ assert (result.length == size);
+ }
+ body
+ {
+ enum size_t mask = ~ (cast(size_t) 7);
+ enum s = ((Node.sizeof - 1) & mask) + 8;
+ Node* current = root;
+ while (true)
+ {
+ while (current !is null)
+ {
+ immutable size_t blockLength = current.block.length;
+ immutable size_t oldUsed = current.used;
+ immutable size_t newUsed = oldUsed + size;
+ if (newUsed > blockLength)
+ current = current.next;
+ else
+ {
+ current.used = ((newUsed - 1) & mask) + 8;
+// assert (current.used >= oldUsed + size);
+// assert (current.block.ptr + blockSize > current.block.ptr + newUsed);
+// assert (newUsed > oldUsed);
+// writefln("Allocating 0x%012x - 0x%012x",
+// cast(size_t) current.block.ptr + oldUsed,
+// cast(size_t) current.block.ptr + newUsed);
+ current.block[oldUsed .. newUsed] = 0;
+ return current.block[oldUsed .. newUsed];
+ }
+ }
+ import core.memory;
+// stderr.writeln("Allocating new block while processing ", name);
+ ubyte* newBlock = cast(ubyte*) GC.malloc(blockSize, GC.BlkAttr.NO_SCAN);
+// assert (newBlock !is null);
+// stderr.writefln("Memory spans from 0x%012x to 0x%012x",
+// cast(size_t) newBlock, cast(size_t) newBlock + blockSize);
+ root = new Node (root, s, newBlock[0 .. blockSize]);
+// assert (root.block.length == blockSize);
+ current = root;
+ }
+ assert (false);
+ }
+
+ /**
+ * Deallocates all memory held by this allocator. All node instances created
+ * by a parser using this allocator instance are invalid after calling this
+ * function.
+ */
+ override bool deallocateAll()
+ {
+ deallocateRecursive(root);
+ root = null;
+ return true;
+ }
+
+ override bool expand(ref void[], size_t) { return false; }
+ override bool reallocate(ref void[], size_t) { return false; }
+ override bool deallocate(void[]) { return false; }
+
+private:
+
+ void deallocateRecursive(Node* node)
+ {
+ import core.memory;
+// import std.c.stdlib;
+// node.block[] = 0;
+// free(node.block.ptr);
+ GC.free(node.block.ptr);
+ if (node.next !is null)
+ deallocateRecursive(node.next);
+ node.next = null;
+ }
+
+ Node* root;
+
+ enum blockSize = 1024 * 1024 * 4;
+
+ struct Node
+ {
+ Node* next;
+ size_t used;
+ ubyte[] block;
+ }
+}
+
/**
* Params:
* tokens = the tokens parsed by std.d.lexer
@@ -32,13 +141,14 @@ import std.string : format;
* means warning).
* Returns: the parsed module
*/
-Module parseModule(const(Token)[] tokens, string fileName,
+Module parseModule(const(Token)[] tokens, string fileName, CAllocator allocator = null,
void function(string, size_t, size_t, string, bool) messageFunction = null)
{
auto parser = new Parser();
parser.fileName = fileName;
parser.tokens = tokens;
parser.messageFunction = messageFunction;
+ parser.allocator = allocator;
auto mod = parser.parseModule();
// writefln("Parsing finished with %d errors and %d warnings.",
// parser.errorCount, parser.warningCount);
@@ -76,7 +186,7 @@ class Parser
AliasDeclaration parseAliasDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new AliasDeclaration;
+ auto node = allocate!AliasDeclaration;
if (expect(tok!"alias") is null) return null;
// 'alias extern(C) void function() f;' => supported in DMD and DScanner.
@@ -92,22 +202,24 @@ class Parser
if (startsWith(tok!"identifier", tok!"="))
{
+ AliasInitializer[] initializers;
do
{
auto initializer = parseAliasInitializer();
if (initializer is null) return null;
- node.initializers ~= initializer;
+ initializers ~= initializer;
if (currentIs(tok!","))
advance();
else
break;
}
while (moreTokens());
+ node.initializers = ownArray(initializers);
}
else
{
- warn("Syntax \"'alias' type identifier ';'\" is deprecated. Please use "
- ~ " \"'alias' identifier '=' type ';'\" instead.");
+ warn("Syntax \"'alias' type identifier ';'\" is deprecated. Please use "
+ ~ " \"'alias' identifier '=' type ';'\" instead.");
if ((node.type = parseType()) is null) return null;
auto ident = expect(tok!"identifier");
if (ident is null)
@@ -143,16 +255,17 @@ alias core.sys.posix.stdio.fileno fileno;
AliasInitializer parseAliasInitializer()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new AliasInitializer;
+ auto node = allocate!AliasInitializer;
auto i = expect(tok!"identifier");
if (i is null) return null;
+ assert (i.text.length < 10_000);
node.name = *i;
if (expect(tok!"=") is null) return null;
node.type = parseType();
return node;
}
- unittest
+ unittest
{
auto sourceCode = q{a = abcde!def};
Parser p = getParserForUnittest(sourceCode, "parseAliasInitializer");
@@ -171,7 +284,7 @@ alias core.sys.posix.stdio.fileno fileno;
AliasThisDeclaration parseAliasThisDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new AliasThisDeclaration;
+ auto node = allocate!AliasThisDeclaration;
if (expect(tok!"alias") is null) return null;
auto ident = expect(tok!"identifier");
if (ident is null) return null;
@@ -200,7 +313,7 @@ alias core.sys.posix.stdio.fileno fileno;
AlignAttribute parseAlignAttribute()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new AlignAttribute;
+ auto node = allocate!AlignAttribute;
expect(tok!"align");
if (currentIs(tok!"("))
{
@@ -277,7 +390,7 @@ alias core.sys.posix.stdio.fileno fileno;
Arguments parseArguments()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Arguments;
+ auto node = allocate!Arguments;
if (expect(tok!"(") is null) return null;
if (!currentIs(tok!")"))
node.argumentList = parseArgumentList();
@@ -296,18 +409,20 @@ alias core.sys.posix.stdio.fileno fileno;
ArrayInitializer parseArrayInitializer()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ArrayInitializer;
+ auto node = allocate!ArrayInitializer;
if (expect(tok!"[") is null) return null;
+ ArrayMemberInitialization[] arrayMemberInitializations;
while (moreTokens())
{
if (currentIs(tok!"]"))
break;
- node.arrayMemberInitializations ~= parseArrayMemberInitialization();
+ arrayMemberInitializations ~= parseArrayMemberInitialization();
if (currentIs(tok!","))
advance();
else
break;
}
+ node.arrayMemberInitializations = ownArray(arrayMemberInitializations);
if (expect(tok!"]") is null) return null;
return node;
}
@@ -322,7 +437,7 @@ alias core.sys.posix.stdio.fileno fileno;
ArrayLiteral parseArrayLiteral()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ArrayLiteral;
+ auto node = allocate!ArrayLiteral;
if (expect(tok!"[") is null) return null;
if (!currentIs(tok!"]"))
node.argumentList = parseArgumentList();
@@ -340,7 +455,7 @@ alias core.sys.posix.stdio.fileno fileno;
ArrayMemberInitialization parseArrayMemberInitialization()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ArrayMemberInitialization;
+ auto node = allocate!ArrayMemberInitialization;
switch (current.type)
{
case tok!"{":
@@ -360,7 +475,7 @@ alias core.sys.posix.stdio.fileno fileno;
}
else
{
- node.nonVoidInitializer = new NonVoidInitializer;
+ node.nonVoidInitializer = allocate!NonVoidInitializer;
node.nonVoidInitializer.assignExpression = assignExpression;
}
}
@@ -390,7 +505,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmAndExp parseAsmAndExp()
{
- auto node = new AsmAndExp;
+ auto node = allocate!AsmAndExp;
assert (false, "asm"); // TODO asm
}
@@ -404,7 +519,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmBrExp parseAsmBrExp()
{
- auto node = new AsmBrExp;
+ auto node = allocate!AsmBrExp;
assert (false, "asm"); // TODO asm
}
@@ -417,7 +532,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmEqualExp parseAsmEqualExp()
{
- auto node = new AsmEqualExp;
+ auto node = allocate!AsmEqualExp;
assert (false, "asm"); // TODO asm
}
@@ -430,7 +545,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmExp parseAsmExp()
{
- auto node = new AsmExp;
+ auto node = allocate!AsmExp;
assert (false, "asm"); // TODO asm
}
@@ -448,7 +563,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmInstruction parseAsmInstruction()
{
- auto node = new AsmInstruction;
+ auto node = allocate!AsmInstruction;
assert (false, "asm"); // TODO asm
}
@@ -461,7 +576,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmLogAndExp parseAsmLogAndExp()
{
- auto node = new AsmLogAndExp;
+ auto node = allocate!AsmLogAndExp;
assert (false, "asm"); // TODO asm
}
@@ -474,7 +589,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmLogOrExp parseAsmLogOrExp()
{
- auto node = new AsmLogOrExp;
+ auto node = allocate!AsmLogOrExp;
assert (false, "asm"); // TODO asm
}
@@ -487,7 +602,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmMulExp parseAsmMulExp()
{
- auto node = new AsmMulExp;
+ auto node = allocate!AsmMulExp;
assert (false, "asm"); // TODO asm
}
@@ -500,7 +615,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmOrExp parseAsmOrExp()
{
- auto node = new AsmOrExp;
+ auto node = allocate!AsmOrExp;
assert (false, "asm"); // TODO asm
}
@@ -517,7 +632,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmPrimaryExp parseAsmPrimaryExp()
{
- auto node = new AsmPrimaryExp;
+ auto node = allocate!AsmPrimaryExp;
assert (false, "asm"); // TODO asm
}
@@ -530,7 +645,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmRelExp parseAsmRelExp()
{
- auto node = new AsmRelExp;
+ auto node = allocate!AsmRelExp;
assert (false, "asm"); // TODO asm
}
@@ -543,7 +658,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmShiftExp parseAsmShiftExp()
{
- auto node = new AsmShiftExp;
+ auto node = allocate!AsmShiftExp;
assert (false, "asm"); // TODO asm
}
@@ -557,7 +672,7 @@ alias core.sys.posix.stdio.fileno fileno;
AsmStatement parseAsmStatement()
{
// TODO asm
- auto node = new AsmStatement;
+ auto node = allocate!AsmStatement;
warn("Skipping assembly statement. Not supported.");
advance();
skipBraces();
@@ -579,7 +694,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmTypePrefix parseAsmTypePrefix()
{
- auto node = new AsmTypePrefix;
+ auto node = allocate!AsmTypePrefix;
assert (false, "asm"); // TODO asm
}
@@ -598,7 +713,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmUnaExp parseAsmUnaExp()
{
- auto node = new AsmUnaExp;
+ auto node = allocate!AsmUnaExp;
assert (false, "asm"); // TODO asm
}
@@ -611,7 +726,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AsmXorExp parseAsmXorExp()
{
- auto node = new AsmXorExp;
+ auto node = allocate!AsmXorExp;
assert (false, "asm"); // TODO asm
}
@@ -625,7 +740,7 @@ alias core.sys.posix.stdio.fileno fileno;
AssertExpression parseAssertExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new AssertExpression;
+ auto node = allocate!AssertExpression;
expect(tok!"assert");
if (expect(tok!"(") is null) return null;
node.assertion = parseAssignExpression();
@@ -664,7 +779,7 @@ alias core.sys.posix.stdio.fileno fileno;
AssignExpression parseAssignExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new AssignExpression;
+ auto node = allocate!AssignExpression;
node.ternaryExpression = parseTernaryExpression();
if (currentIsOneOf(tok!"=", tok!">>>=",
tok!">>=", tok!"<<=",
@@ -703,7 +818,7 @@ alias core.sys.posix.stdio.fileno fileno;
AtAttribute parseAtAttribute()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new AtAttribute;
+ auto node = allocate!AtAttribute;
if (expect(tok!"@") is null) return null;
switch (current.type)
{
@@ -743,7 +858,7 @@ alias core.sys.posix.stdio.fileno fileno;
Attribute parseAttribute()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Attribute;
+ auto node = allocate!Attribute;
switch (current.type)
{
case tok!"extern":
@@ -781,7 +896,7 @@ alias core.sys.posix.stdio.fileno fileno;
*/
AttributeDeclaration parseAttributeDeclaration(Attribute attribute = null)
{
- auto node = new AttributeDeclaration;
+ auto node = allocate!AttributeDeclaration;
node.attribute = attribute is null ? parseAttribute() : attribute;
expect(tok!":");
return node;
@@ -797,21 +912,25 @@ alias core.sys.posix.stdio.fileno fileno;
AutoDeclaration parseAutoDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new AutoDeclaration;
+ auto node = allocate!AutoDeclaration;
+ Token[] identifiers;
+ Initializer[] initializers;
do
{
auto ident = expect(tok!"identifier");
if (ident is null) return null;
- node.identifiers ~= *ident;
+ identifiers ~= *ident;
if (expect(tok!"=") is null) return null;
auto init = parseInitializer();
if (init is null) return null;
- node.initializers ~= init;
+ initializers ~= init;
if (currentIs(tok!","))
advance();
else
break;
} while (moreTokens());
+ node.identifiers = ownArray(identifiers);
+ node.initializers = ownArray(initializers);
if (expect(tok!";") is null) return null;
return node;
}
@@ -826,7 +945,7 @@ alias core.sys.posix.stdio.fileno fileno;
BlockStatement parseBlockStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new BlockStatement();
+ auto node = allocate!BlockStatement;
auto openBrace = expect(tok!"{");
if (openBrace is null) return null;
node.startLocation = openBrace.index;
@@ -865,7 +984,7 @@ alias core.sys.posix.stdio.fileno fileno;
{
mixin(traceEnterAndExit!(__FUNCTION__));
expect(tok!"break");
- auto node = new BreakStatement;
+ auto node = allocate!BreakStatement;
switch (current.type)
{
case tok!"identifier":
@@ -892,7 +1011,7 @@ alias core.sys.posix.stdio.fileno fileno;
BaseClass parseBaseClass()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new BaseClass;
+ auto node = allocate!BaseClass;
if (current.type.isProtection())
{
warn("Use of base class protection is deprecated.");
@@ -967,7 +1086,7 @@ alias core.sys.posix.stdio.fileno fileno;
CaseRangeStatement parseCaseRangeStatement(AssignExpression low = null)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new CaseRangeStatement;
+ auto node = allocate!CaseRangeStatement;
if (low is null)
{
expect(tok!"case");
@@ -994,7 +1113,7 @@ alias core.sys.posix.stdio.fileno fileno;
CaseStatement parseCaseStatement(ArgumentList argumentList = null)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new CaseStatement;
+ auto node = allocate!CaseStatement;
if (argumentList is null)
{
expect(tok!"case");
@@ -1017,7 +1136,7 @@ alias core.sys.posix.stdio.fileno fileno;
CastExpression parseCastExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new CastExpression;
+ auto node = allocate!CastExpression;
expect(tok!"cast");
if (expect(tok!"(") is null) return null;
if (!currentIs(tok!")"))
@@ -1049,7 +1168,7 @@ alias core.sys.posix.stdio.fileno fileno;
CastQualifier parseCastQualifier()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new CastQualifier;
+ auto node = allocate!CastQualifier;
switch (current.type)
{
case tok!"inout":
@@ -1146,7 +1265,7 @@ incorrect;
Catch parseCatch()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Catch;
+ auto node = allocate!Catch;
expect(tok!"catch");
if (expect(tok!"(") is null) return null;
node.type = parseType();
@@ -1168,21 +1287,21 @@ incorrect;
Catches parseCatches()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Catches;
+ auto node = allocate!Catches;
+ Catch[] catches;
while (moreTokens())
{
if (!currentIs(tok!"catch"))
break;
if (peekIs(tok!"("))
- {
- node.catches ~= parseCatch();
- }
+ catches ~= parseCatch();
else
{
node.lastCatch = parseLastCatch();
break;
}
}
+ node.catches = ownArray(catches);
return node;
}
@@ -1196,7 +1315,7 @@ incorrect;
ClassDeclaration parseClassDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ClassDeclaration;
+ auto node = allocate!ClassDeclaration;
expect(tok!"class");
auto ident = expect(tok!"identifier");
if (ident is null) return null;
@@ -1280,7 +1399,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
ExpressionNode parseCmpExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new CmpExpression;
+ auto node = allocate!CmpExpression;
auto shift = parseShiftExpression();
if (!moreTokens())
return shift;
@@ -1335,7 +1454,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
CompileCondition parseCompileCondition()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new CompileCondition;
+ auto node = allocate!CompileCondition;
switch (current.type)
{
case tok!"version":
@@ -1366,20 +1485,23 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
ConditionalDeclaration parseConditionalDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ConditionalDeclaration;
+ auto node = allocate!ConditionalDeclaration;
node.compileCondition = parseCompileCondition();
+ Declaration[] trueDeclarations;
if (currentIs(tok!":"))
{
advance();
while (isDeclaration())
- node.trueDeclarations ~= parseDeclaration();
+ trueDeclarations ~= parseDeclaration();
+ node.trueDeclarations = ownArray(trueDeclarations);
return node;
}
auto dec = parseDeclaration();
if (dec is null) return null;
- node.trueDeclarations ~= dec;
+ trueDeclarations ~= dec;
+ node.trueDeclarations = ownArray(trueDeclarations);
if(currentIs(tok!"else"))
advance();
@@ -1402,7 +1524,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
ConditionalStatement parseConditionalStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ConditionalStatement;
+ auto node = allocate!ConditionalStatement;
node.compileCondition = parseCompileCondition();
node.trueStatement = parseDeclarationOrStatement();
if (currentIs(tok!"else"))
@@ -1423,7 +1545,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
Constraint parseConstraint()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Constraint;
+ auto node = allocate!Constraint;
if (expect(tok!"if") is null) return null;
if (expect(tok!"(") is null) return null;
node.expression = parseExpression();
@@ -1441,7 +1563,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
Constructor parseConstructor()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- Constructor node = new Constructor;
+ Constructor node = allocate!Constructor;
node.comment = comment;
comment = null;
auto t = expect(tok!"this");
@@ -1457,8 +1579,10 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
node.parameters = parseParameters();
if (node.parameters is null) return null;
+ MemberFunctionAttribute[] memberFunctionAttributes;
while(moreTokens() && currentIsMemberFunctionAttribute())
- node.memberFunctionAttributes ~= parseMemberFunctionAttribute();
+ memberFunctionAttributes ~= parseMemberFunctionAttribute();
+ node.memberFunctionAttributes = ownArray(memberFunctionAttributes);
if (isTemplate && currentIs(tok!"if"))
node.constraint = parseConstraint();
@@ -1485,7 +1609,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
{
mixin(traceEnterAndExit!(__FUNCTION__));
if (expect(tok!"continue") is null) return null;
- auto node = new ContinueStatement;
+ auto node = allocate!ContinueStatement;
switch (current.type)
{
case tok!"identifier":
@@ -1512,7 +1636,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
DebugCondition parseDebugCondition()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new DebugCondition;
+ auto node = allocate!DebugCondition;
if (expect(tok!"debug") is null) return null;
if (currentIs(tok!"("))
{
@@ -1539,7 +1663,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
DebugSpecification parseDebugSpecification()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new DebugSpecification;
+ auto node = allocate!DebugSpecification;
if (expect(tok!"debug") is null) return null;
if (expect(tok!"=") is null) return null;
if (currentIsOneOf(tok!"identifier", tok!"intLiteral"))
@@ -1591,8 +1715,9 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
Declaration parseDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Declaration;
+ auto node = allocate!Declaration;
comment = current.comment;
+ Attribute[] attributes;
do
{
if (!isAttribute())
@@ -1609,8 +1734,9 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
return node;
}
else
- node.attributes ~= attr;
+ attributes ~= attr;
} while (moreTokens());
+ node.attributes = ownArray(attributes);
switch (current.type)
{
@@ -1621,12 +1747,14 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
break;
case tok!"{":
advance();
+ Declaration[] declarations;
while (moreTokens() && !currentIs(tok!"}"))
{
auto declaration = parseDeclaration();
if (declaration !is null)
- node.declarations ~= declaration;
+ declarations ~= declaration;
}
+ node.declarations = ownArray(declarations);
if (expect(tok!"}") is null) return null;
break;
case tok!"alias":
@@ -1802,13 +1930,15 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
DeclarationsAndStatements parseDeclarationsAndStatements()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new DeclarationsAndStatements;
+ auto node = allocate!DeclarationsAndStatements;
+ DeclarationOrStatement[] declarationsAndStatements;
while (!currentIsOneOf(tok!"}") && moreTokens())
{
auto dos = parseDeclarationOrStatement();
if (dos !is null)
- node.declarationsAndStatements ~= dos;
+ declarationsAndStatements ~= dos;
}
+ node.declarationsAndStatements = ownArray(declarationsAndStatements);
return node;
}
@@ -1823,7 +1953,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
DeclarationOrStatement parseDeclarationOrStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new DeclarationOrStatement;
+ auto node = allocate!DeclarationOrStatement;
// "Any ambiguities in the grammar between Statements and
// Declarations are resolved by the declarations taking precedence."
if (isDeclaration())
@@ -1855,7 +1985,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
Declarator parseDeclarator()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Declarator;
+ auto node = allocate!Declarator;
auto id = expect(tok!"identifier");
if (id is null) return null;
node.name = *id;
@@ -1882,7 +2012,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
DefaultStatement parseDefaultStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new DefaultStatement;
+ auto node = allocate!DefaultStatement;
if (expect(tok!"default") is null) return null;
if (expect(tok!":") is null) return null;
node.declarationsAndStatements = parseDeclarationsAndStatements();
@@ -1899,9 +2029,9 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
DeleteExpression parseDeleteExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new DeleteExpression;
- node.line = current.line;
- node.column = current.column;
+ auto node = allocate!DeleteExpression;
+ node.line = current.line;
+ node.column = current.column;
if (expect(tok!"delete") is null) return null;
node.unaryExpression = parseUnaryExpression();
return node;
@@ -1917,7 +2047,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
Deprecated parseDeprecated()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Deprecated;
+ auto node = allocate!Deprecated;
if (expect(tok!"deprecated") is null) return null;
if (currentIs(tok!"("))
{
@@ -1938,7 +2068,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
Destructor parseDestructor()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Destructor;
+ auto node = allocate!Destructor;
node.comment = comment;
comment = null;
if (expect(tok!"~") is null) return null;
@@ -1973,7 +2103,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
DoStatement parseDoStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new DoStatement;
+ auto node = allocate!DoStatement;
if (expect(tok!"do") is null) return null;
node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault();
if (expect(tok!"while") is null) return null;
@@ -1995,16 +2125,17 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
EnumBody parseEnumBody()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- EnumBody node = new EnumBody;
+ EnumBody node = allocate!EnumBody;
if (!currentIs(tok!";"))
{
auto open = expect (tok!"{");
if (open is null) goto ret;
node.startLocation = open.index;
+ EnumMember[] enumMembers;
while (moreTokens())
{
if (!currentIsOneOf(tok!",", tok!"}"))
- node.enumMembers ~= parseEnumMember();
+ enumMembers ~= parseEnumMember();
else if (currentIs(tok!","))
{
advance();
@@ -2018,6 +2149,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
goto ret;
}
}
+ node.enumMembers = ownArray(enumMembers);
auto close = expect (tok!"}");
if (close !is null)
node.endLocation = close.index;
@@ -2036,12 +2168,12 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
EnumDeclaration parseEnumDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new EnumDeclaration;
+ auto node = allocate!EnumDeclaration;
if (expect(tok!"enum") is null) return null;
if (currentIs(tok!"identifier"))
node.name = advance();
- else
- node.name.line = tokens[index - 1].line; // preserve line number if anonymous
+ else
+ node.name.line = tokens[index - 1].line; // preserve line number if anonymous
node.comment = comment;
comment = null;
if (currentIs(tok!":"))
@@ -2064,7 +2196,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
EnumMember parseEnumMember()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new EnumMember;
+ auto node = allocate!EnumMember;
node.comment = current.comment;
if (currentIs(tok!"identifier"))
{
@@ -2099,7 +2231,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
EqualExpression parseEqualExpression(ExpressionNode shift = null)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new EqualExpression;
+ auto node = allocate!EqualExpression;
node.left = shift is null ? parseShiftExpression() : shift;
if (currentIsOneOf(tok!"==", tok!"!="))
node.operator = advance().type;
@@ -2130,7 +2262,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
ExpressionStatement parseExpressionStatement(Expression expression = null)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ExpressionStatement;
+ auto node = allocate!ExpressionStatement;
node.expression = expression is null ? parseExpression() : expression;
if (expect(tok!";") is null) return null;
return node;
@@ -2146,7 +2278,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
FinalSwitchStatement parseFinalSwitchStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new FinalSwitchStatement;
+ auto node = allocate!FinalSwitchStatement;
if (expect(tok!"final") is null) return null;
node.switchStatement = parseSwitchStatement();
if (node.switchStatement is null) return null;
@@ -2163,7 +2295,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
Finally parseFinally()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Finally;
+ auto node = allocate!Finally;
if (expect(tok!"finally") is null) return null;
node.declarationOrStatement = parseDeclarationOrStatement();
return node;
@@ -2179,7 +2311,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
ForStatement parseForStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ForStatement;
+ auto node = allocate!ForStatement;
if (expect(tok!"for") is null) return null;
node.startIndex = current().index;
if (expect(tok!"(") is null) return null;
@@ -2219,7 +2351,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
ForeachStatement parseForeachStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- ForeachStatement node = new ForeachStatement;
+ ForeachStatement node = allocate!ForeachStatement;
if (currentIsOneOf(tok!"foreach", tok!"foreach_reverse"))
node.type = advance().type;
else
@@ -2272,7 +2404,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
ForeachType parseForeachType()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ForeachType;
+ auto node = allocate!ForeachType;
if (currentIsOneOf(tok!"ref", tok!"const", tok!"immutable",
tok!"shared", tok!"inout"))
{
@@ -2316,7 +2448,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
*/
FunctionAttribute parseFunctionAttribute(bool validate = true)
{
- auto node = new FunctionAttribute;
+ auto node = allocate!FunctionAttribute;
switch (current.type)
{
case tok!"@":
@@ -2344,7 +2476,7 @@ class ClassFour(A, B) if (someTest()) : Super {}}c;
*/
FunctionBody parseFunctionBody()
{
- auto node = new FunctionBody;
+ auto node = allocate!FunctionBody;
if (currentIs(tok!";"))
{
advance();
@@ -2430,7 +2562,7 @@ body {} // six
FunctionCallExpression parseFunctionCallExpression(UnaryExpression unary = null)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new FunctionCallExpression;
+ auto node = allocate!FunctionCallExpression;
node.unaryExpression = unary is null ? parseUnaryExpression() : unary;
if (currentIs(tok!"!"))
node.templateArguments = parseTemplateArguments();
@@ -2448,7 +2580,7 @@ body {} // six
FunctionCallStatement parseFunctionCallStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new FunctionCallStatement;
+ auto node = allocate!FunctionCallStatement;
node.functionCallExpression = parseFunctionCallExpression();
if (expect(tok!";") is null) return null;
return node;
@@ -2464,15 +2596,16 @@ body {} // six
FunctionDeclaration parseFunctionDeclaration(Type type = null, bool isAuto = false)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new FunctionDeclaration;
+ auto node = allocate!FunctionDeclaration;
node.comment = comment;
comment = null;
+ MemberFunctionAttribute[] memberFunctionAttributes;
if (isAuto)
goto functionName;
while(moreTokens() && currentIsMemberFunctionAttribute())
- node.memberFunctionAttributes ~= parseMemberFunctionAttribute();
+ memberFunctionAttributes ~= parseMemberFunctionAttribute();
node.returnType = type is null ? parseType() : type;
@@ -2499,7 +2632,7 @@ body {} // six
if (node.parameters is null) return null;
while(moreTokens() && currentIsMemberFunctionAttribute())
- node.memberFunctionAttributes ~= parseMemberFunctionAttribute();
+ memberFunctionAttributes ~= parseMemberFunctionAttribute();
if (isTemplate && currentIs(tok!"if"))
node.constraint = parseConstraint();
@@ -2508,7 +2641,7 @@ body {} // six
advance();
else
node.functionBody = parseFunctionBody();
-
+ node.memberFunctionAttributes = ownArray(memberFunctionAttributes);
return node;
}
@@ -2521,7 +2654,7 @@ body {} // six
*/
FunctionLiteralExpression parseFunctionLiteralExpression()
{
- auto node = new FunctionLiteralExpression;
+ auto node = allocate!FunctionLiteralExpression;
if (currentIsOneOf(tok!"function", tok!"delegate"))
{
node.functionOrDelegate = advance().type;
@@ -2536,14 +2669,16 @@ body {} // six
{
node.parameters = parseParameters();
if (node.parameters is null) return null;
+ FunctionAttribute[] functionAttributes;
do
{
auto attr = parseFunctionAttribute(false);
if (attr is null)
break;
else
- node.functionAttributes ~= attr;
+ functionAttributes ~= attr;
} while (moreTokens());
+ node.functionAttributes = ownArray(functionAttributes);
}
node.functionBody = parseFunctionBody();
if (node.functionBody is null) return null;
@@ -2559,7 +2694,7 @@ body {} // six
*/
GotoStatement parseGotoStatement()
{
- auto node = new GotoStatement;
+ auto node = allocate!GotoStatement;
if (expect(tok!"goto") is null) return null;
switch (current.type)
{
@@ -2589,12 +2724,13 @@ body {} // six
*/
IdentifierChain parseIdentifierChain()
{
- auto node = new IdentifierChain;
+ auto node = allocate!IdentifierChain;
+ Token[] identifiers;
while (moreTokens())
{
auto ident = expect(tok!"identifier");
if (ident is null) return null;
- node.identifiers ~= *ident;
+ identifiers ~= *ident;
if (currentIs(tok!"."))
{
advance();
@@ -2603,6 +2739,7 @@ body {} // six
else
break;
}
+ node.identifiers = ownArray(identifiers);
return node;
}
@@ -2615,12 +2752,13 @@ body {} // six
*/
IdentifierList parseIdentifierList()
{
- auto node = new IdentifierList;
+ auto node = allocate!IdentifierList;
+ Token[] identifiers;
do
{
auto ident = expect(tok!"identifier");
if (ident is null) return null;
- node.identifiers ~= *ident;
+ identifiers ~= *ident;
if (currentIs(tok!","))
{
advance();
@@ -2629,6 +2767,7 @@ body {} // six
else
break;
} while (moreTokens());
+ node.identifiers = ownArray(identifiers);
return node;
}
@@ -2641,15 +2780,17 @@ body {} // six
*/
IdentifierOrTemplateChain parseIdentifierOrTemplateChain()
{
- auto node = new IdentifierOrTemplateChain;
+ auto node = allocate!IdentifierOrTemplateChain;
+ IdentifierOrTemplateInstance[] identifiersOrTemplateInstances;
while (moreTokens())
{
- node.identifiersOrTemplateInstances ~= parseIdentifierOrTemplateInstance();
+ identifiersOrTemplateInstances ~= parseIdentifierOrTemplateInstance();
if (!currentIs(tok!"."))
break;
else
advance();
}
+ node.identifiersOrTemplateInstances = ownArray(identifiersOrTemplateInstances);
return node;
}
@@ -2664,7 +2805,7 @@ body {} // six
IdentifierOrTemplateInstance parseIdentifierOrTemplateInstance()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new IdentifierOrTemplateInstance;
+ auto node = allocate!IdentifierOrTemplateInstance;
if (peekIs(tok!"!") && !startsWith(tok!"identifier",
tok!"!", tok!"is")
&& !startsWith(tok!"identifier", tok!"!", tok!"in"))
@@ -2690,7 +2831,7 @@ body {} // six
ExpressionNode parseIdentityExpression(ExpressionNode shift = null)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new IdentityExpression;
+ auto node = allocate!IdentityExpression;
node.left = shift is null ? parseShiftExpression() : shift;
if (currentIs(tok!"!"))
{
@@ -2716,7 +2857,7 @@ body {} // six
IfStatement parseIfStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new IfStatement;
+ auto node = allocate!IfStatement;
if (expect(tok!"if") is null) return null;
node.startIndex = current().index;
if (expect(tok!"(") is null) return null;
@@ -2776,7 +2917,7 @@ body {} // six
*/
ImportBind parseImportBind()
{
- auto node = new ImportBind;
+ auto node = allocate!ImportBind;
auto ident = expect(tok!"identifier");
if (ident is null) return null;
node.left = *ident;
@@ -2799,17 +2940,19 @@ body {} // six
*/
ImportBindings parseImportBindings(SingleImport singleImport)
{
- auto node = new ImportBindings;
+ auto node = allocate!ImportBindings;
node.singleImport = singleImport is null ? parseSingleImport() : singleImport;
if (expect(tok!":") is null) return null;
+ ImportBind[] importBinds;
while (moreTokens())
{
- node.importBinds ~= parseImportBind();
+ importBinds ~= parseImportBind();
if (currentIs(tok!","))
advance();
else
break;
}
+ node.importBinds = ownArray(importBinds);
return node;
}
@@ -2823,14 +2966,15 @@ body {} // six
*/
ImportDeclaration parseImportDeclaration()
{
- auto node = new ImportDeclaration;
+ auto node = allocate!ImportDeclaration;
if (expect(tok!"import") is null) return null;
SingleImport si = parseSingleImport();
if (currentIs(tok!":"))
node.importBindings = parseImportBindings(si);
else
{
- node.singleImports ~= si;
+ SingleImport[] singleImports;
+ singleImports ~= si;
if (currentIs(tok!","))
{
advance();
@@ -2846,7 +2990,7 @@ body {} // six
}
else
{
- node.singleImports ~= single;
+ singleImports ~= single;
if (currentIs(tok!","))
advance();
else
@@ -2854,6 +2998,7 @@ body {} // six
}
}
}
+ node.singleImports = ownArray(singleImports);
}
if (expect(tok!";") is null) return null;
return node;
@@ -2921,7 +3066,7 @@ import core.stdc.stdio, std.string : KeepTerminator;
*/
ImportExpression parseImportExpression()
{
- auto node = new ImportExpression;
+ auto node = allocate!ImportExpression;
if (expect(tok!"import") is null) return null;
if (expect(tok!"(") is null) return null;
node.assignExpression = parseAssignExpression();
@@ -2939,7 +3084,7 @@ import core.stdc.stdio, std.string : KeepTerminator;
IndexExpression parseIndexExpression(UnaryExpression unaryExpression = null)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new IndexExpression;
+ auto node = allocate!IndexExpression;
node.unaryExpression = unaryExpression is null ? parseUnaryExpression() : unaryExpression;
if (expect(tok!"[") is null) return null;
node.argumentList = parseArgumentList();
@@ -2956,7 +3101,7 @@ import core.stdc.stdio, std.string : KeepTerminator;
*/
ExpressionNode parseInExpression(ExpressionNode shift = null)
{
- auto node = new InExpression;
+ auto node = allocate!InExpression;
node.left = shift is null ? parseShiftExpression() : shift;
if (currentIs(tok!"!"))
{
@@ -2977,7 +3122,7 @@ import core.stdc.stdio, std.string : KeepTerminator;
*/
InStatement parseInStatement()
{
- auto node = new InStatement;
+ auto node = allocate!InStatement;
if (expect(tok!"in") is null) return null;
node.blockStatement = parseBlockStatement();
return node;
@@ -2993,7 +3138,7 @@ import core.stdc.stdio, std.string : KeepTerminator;
*/
Initialize parseInitialize()
{
- auto node = new Initialize;
+ auto node = allocate!Initialize;
if (!currentIs(tok!";"))
{
node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault();
@@ -3014,7 +3159,7 @@ import core.stdc.stdio, std.string : KeepTerminator;
*/
Initializer parseInitializer()
{
- auto node = new Initializer;
+ auto node = allocate!Initializer;
if (currentIs(tok!"void"))
advance();
else
@@ -3031,7 +3176,7 @@ import core.stdc.stdio, std.string : KeepTerminator;
*/
InterfaceDeclaration parseInterfaceDeclaration()
{
- auto node = new InterfaceDeclaration;
+ auto node = allocate!InterfaceDeclaration;
if (expect(tok!"interface") is null) return null;
auto ident = expect(tok!"identifier");
if (ident is null) return null;
@@ -3107,7 +3252,7 @@ interface "Four"
*/
Invariant parseInvariant()
{
- auto node = new Invariant;
+ auto node = allocate!Invariant;
if (expect(tok!"invariant") is null) return null;
if (currentIs(tok!"("))
{
@@ -3153,7 +3298,7 @@ invariant() foo();
IsExpression parseIsExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new IsExpression;
+ auto node = allocate!IsExpression;
if (expect(tok!"is") is null) return null;
if (expect(tok!"(") is null) return null;
node.type = parseType();
@@ -3174,14 +3319,14 @@ invariant() foo();
return node;
}
- unittest
+ unittest
{
auto sourceCode = q{is ( x : uybte)}c;
Parser p = getParserForUnittest(sourceCode, "parseIsExpression");
auto isExp1 = p.parseIsExpression();
assert (isExp1 !is null);
assert (p.errorCount == 0);
- stderr.writeln("Unittest for parseIsExpression passed.");
+ stderr.writeln("Unittest for parseIsExpression passed.");
}
/**
@@ -3194,7 +3339,7 @@ invariant() foo();
KeyValuePair parseKeyValuePair()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new KeyValuePair;
+ auto node = allocate!KeyValuePair;
node.key = parseAssignExpression();
if (expect(tok!":") is null) return null;
node.value = parseAssignExpression();
@@ -3211,12 +3356,13 @@ invariant() foo();
KeyValuePairs parseKeyValuePairs()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new KeyValuePairs;
+ auto node = allocate!KeyValuePairs;
+ KeyValuePair[] keyValuePairs;
while (moreTokens())
{
auto kvPair = parseKeyValuePair();
if (kvPair !is null)
- node.keyValuePairs ~= kvPair;
+ keyValuePairs ~= kvPair;
if (currentIs(tok!","))
{
advance();
@@ -3226,6 +3372,7 @@ invariant() foo();
else
break;
}
+ node.keyValuePairs = ownArray(keyValuePairs);
return node;
}
@@ -3239,7 +3386,7 @@ invariant() foo();
LabeledStatement parseLabeledStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new LabeledStatement;
+ auto node = allocate!LabeledStatement;
auto ident = expect(tok!"identifier");
if (ident is null) return null;
node.identifier = *ident;
@@ -3261,7 +3408,7 @@ invariant() foo();
LambdaExpression parseLambdaExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new LambdaExpression;
+ auto node = allocate!LambdaExpression;
if (currentIsOneOf(tok!"function", tok!"delegate"))
{
node.functionType = advance().type;
@@ -3273,14 +3420,16 @@ invariant() foo();
{
lParen:
node.parameters = parseParameters();
+ FunctionAttribute[] functionAttributes;
do
{
auto attribute = parseFunctionAttribute(false);
if (attribute is null)
break;
- node.functionAttributes ~= attribute;
+ functionAttributes ~= attribute;
}
while (moreTokens());
+ node.functionAttributes = ownArray(functionAttributes);
}
else
{
@@ -3306,7 +3455,7 @@ invariant() foo();
LastCatch parseLastCatch()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new LastCatch;
+ auto node = allocate!LastCatch;
if (expect(tok!"catch") is null) return null;
if ((node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault()) is null)
return null;
@@ -3323,7 +3472,7 @@ invariant() foo();
LinkageAttribute parseLinkageAttribute()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new LinkageAttribute;
+ auto node = allocate!LinkageAttribute;
expect(tok!"extern");
expect(tok!"(");
auto ident = expect(tok!"identifier");
@@ -3352,7 +3501,7 @@ invariant() foo();
MemberFunctionAttribute parseMemberFunctionAttribute()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new MemberFunctionAttribute;
+ auto node = allocate!MemberFunctionAttribute;
switch (current.type)
{
case tok!"@":
@@ -3383,7 +3532,7 @@ invariant() foo();
MixinDeclaration parseMixinDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new MixinDeclaration;
+ auto node = allocate!MixinDeclaration;
if (peekIs(tok!"identifier") || peekIs(tok!"typeof"))
node.templateMixinExpression = parseTemplateMixinExpression();
else if (peekIs(tok!"("))
@@ -3407,7 +3556,7 @@ invariant() foo();
MixinExpression parseMixinExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new MixinExpression;
+ auto node = allocate!MixinExpression;
expect(tok!"mixin");
expect(tok!"(");
node.assignExpression = parseAssignExpression();
@@ -3425,7 +3574,7 @@ invariant() foo();
MixinTemplateDeclaration parseMixinTemplateDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new MixinTemplateDeclaration;
+ auto node = allocate!MixinTemplateDeclaration;
if (expect(tok!"mixin") is null) return null;
node.templateDeclaration = parseTemplateDeclaration();
if (node.templateDeclaration is null) return null;
@@ -3443,7 +3592,7 @@ invariant() foo();
MixinTemplateName parseMixinTemplateName()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new MixinTemplateName;
+ auto node = allocate!MixinTemplateName;
if (currentIs(tok!"typeof"))
{
node.typeofExpression = parseTypeofExpression();
@@ -3469,7 +3618,7 @@ invariant() foo();
Module parseModule()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- Module m = new Module;
+ Module m = allocate!Module;
if (currentIs(tok!"scriptLine"))
advance();
if (currentIs(tok!"module"))
@@ -3492,7 +3641,7 @@ invariant() foo();
*/
ModuleDeclaration parseModuleDeclaration()
{
- auto node = new ModuleDeclaration;
+ auto node = allocate!ModuleDeclaration;
expect(tok!"module");
node.moduleName = parseIdentifierChain();
expect(tok!";");
@@ -3522,7 +3671,7 @@ invariant() foo();
*/
NewAnonClassExpression parseNewAnonClassExpression()
{
- auto node = new NewAnonClassExpression;
+ auto node = allocate!NewAnonClassExpression;
expect(tok!"new");
if (currentIs(tok!"("))
node.allocatorArguments = parseArguments();
@@ -3545,7 +3694,7 @@ invariant() foo();
*/
NewExpression parseNewExpression()
{
- auto node = new NewExpression;
+ auto node = allocate!NewExpression;
if (peekIsOneOf(tok!"class", tok!"("))
node.newAnonClassExpression = parseNewAnonClassExpression();
else
@@ -3598,7 +3747,7 @@ invariant() foo();
*/
StatementNoCaseNoDefault parseStatementNoCaseNoDefault()
{
- auto node = new StatementNoCaseNoDefault;
+ auto node = allocate!StatementNoCaseNoDefault;
switch (current.type)
{
case tok!"{":
@@ -3710,7 +3859,7 @@ invariant() foo();
NonVoidInitializer parseNonVoidInitializer()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new NonVoidInitializer;
+ auto node = allocate!NonVoidInitializer;
if (currentIs(tok!"{"))
node.structInitializer = parseStructInitializer();
else if (currentIs(tok!"["))
@@ -3741,7 +3890,7 @@ invariant() foo();
*/
Operands parseOperands()
{
- auto node = new Operands;
+ auto node = allocate!Operands;
assert (false, "asm"); // TODO asm
}
@@ -3784,7 +3933,7 @@ invariant() foo();
*/
OutStatement parseOutStatement()
{
- auto node = new OutStatement;
+ auto node = allocate!OutStatement;
expect(tok!"out");
if (currentIs(tok!"("))
{
@@ -3808,15 +3957,17 @@ invariant() foo();
Parameter parseParameter()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Parameter;
+ auto node = allocate!Parameter;
+ IdType[] parameterAttributes;
while (moreTokens())
{
IdType type = parseParameterAttribute(false);
if (type == tok!"")
break;
else
- node.parameterAttributes ~= type;
+ parameterAttributes ~= type;
}
+ node.parameterAttributes = ownArray(parameterAttributes);
node.type = parseType();
if (node.type is null) return null;
if (currentIs(tok!"identifier"))
@@ -3901,8 +4052,9 @@ invariant() foo();
Parameters parseParameters()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Parameters;
+ auto node = allocate!Parameters;
if (expect(tok!"(") is null) return null;
+ Parameter[] parameters;
if (currentIs(tok!")"))
goto end;
if (currentIs(tok!"..."))
@@ -3924,12 +4076,13 @@ invariant() foo();
auto param = parseParameter();
if (param is null)
return null;
- node.parameters ~= param;
+ parameters ~= param;
if (currentIs(tok!","))
advance();
else
break;
}
+ node.parameters = ownArray(node.parameters);
end:
if (expect(tok!")") is null)
return null;
@@ -3972,7 +4125,7 @@ q{(int a, ...)
*/
Postblit parsePostblit()
{
- auto node = new Postblit;
+ auto node = allocate!Postblit;
expect(tok!"this");
expect(tok!"(");
expect(tok!"this");
@@ -3993,7 +4146,7 @@ q{(int a, ...)
*/
PostIncDecExpression parsePostIncDecExpression(UnaryExpression unary = null)
{
- auto node = new PostIncDecExpression;
+ auto node = allocate!PostIncDecExpression;
node.unaryExpression = unary is null ? parseUnaryExpression() : unary;
node.operator = advance().type;
return node;
@@ -4022,8 +4175,8 @@ q{(int a, ...)
*/
PragmaDeclaration parsePragmaDeclaration()
{
- mixin (traceEnterAndExit!(__FUNCTION__));
- auto node = new PragmaDeclaration;
+ mixin (traceEnterAndExit!(__FUNCTION__));
+ auto node = allocate!PragmaDeclaration;
node.pragmaExpression = parsePragmaExpression();
expect(tok!";");
return node;
@@ -4038,8 +4191,8 @@ q{(int a, ...)
*/
PragmaExpression parsePragmaExpression()
{
- mixin (traceEnterAndExit!(__FUNCTION__));
- auto node = new PragmaExpression;
+ mixin (traceEnterAndExit!(__FUNCTION__));
+ auto node = allocate!PragmaExpression;
expect(tok!"pragma");
expect(tok!"(");
auto ident = expect(tok!"identifier");
@@ -4064,7 +4217,7 @@ q{(int a, ...)
PreIncDecExpression parsePreIncDecExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new PreIncDecExpression;
+ auto node = allocate!PreIncDecExpression;
if (currentIsOneOf(tok!"++", tok!"--"))
advance();
else
@@ -4120,12 +4273,12 @@ q{(int a, ...)
PrimaryExpression parsePrimaryExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new PrimaryExpression;
- if (!moreTokens())
- {
- error("Expected primary statement instead of EOF");
- return null;
- }
+ auto node = allocate!PrimaryExpression;
+ if (!moreTokens())
+ {
+ error("Expected primary statement instead of EOF");
+ return null;
+ }
switch (current.type)
{
case tok!".":
@@ -4263,7 +4416,7 @@ q{(int a, ...)
Register parseRegister()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Register;
+ auto node = allocate!Register;
auto ident = expect(tok!"identifier");
if (ident is null) return null;
node.identifier = *ident;
@@ -4319,7 +4472,7 @@ q{(int a, ...)
ReturnStatement parseReturnStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ReturnStatement;
+ auto node = allocate!ReturnStatement;
if (expect(tok!"return") is null) return null;
if (!currentIs(tok!";"))
node.expression = parseExpression();
@@ -4337,7 +4490,7 @@ q{(int a, ...)
ScopeGuardStatement parseScopeGuardStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ScopeGuardStatement;
+ auto node = allocate!ScopeGuardStatement;
expect(tok!"scope");
expect(tok!"(");
auto ident = expect(tok!"identifier");
@@ -4402,7 +4555,7 @@ q{(int a, ...)
SingleImport parseSingleImport()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new SingleImport;
+ auto node = allocate!SingleImport;
if (startsWith(tok!"identifier", tok!"="))
{
node.rename = advance();
@@ -4425,7 +4578,7 @@ q{(int a, ...)
SliceExpression parseSliceExpression(UnaryExpression unary = null)
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new SliceExpression;
+ auto node = allocate!SliceExpression;
node.unaryExpression = unary is null ? parseUnaryExpression() : unary;
if (expect(tok!"[") is null) return null;
if (!currentIs(tok!"]"))
@@ -4451,12 +4604,12 @@ q{(int a, ...)
Statement parseStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Statement;
- if (!moreTokens())
- {
- error("Expected statement instead of EOF");
- return null;
- }
+ auto node = allocate!Statement;
+ if (!moreTokens())
+ {
+ error("Expected statement instead of EOF");
+ return null;
+ }
switch (current.type)
{
case tok!"case":
@@ -4572,7 +4725,7 @@ q{(int a, ...)
*/
StorageClass parseStorageClass()
{
- auto node = new StorageClass;
+ auto node = allocate!StorageClass;
switch (current.type)
{
case tok!"@":
@@ -4618,15 +4771,17 @@ q{(int a, ...)
StructBody parseStructBody()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new StructBody;
+ auto node = allocate!StructBody;
auto start = expect(tok!"{");
if (start !is null) node.startLocation = start.index;
+ Declaration[] declarations;
while (!currentIs(tok!"}") && moreTokens())
{
auto dec = parseDeclaration();
if (dec !is null)
- node.declarations ~= dec;
+ declarations ~= dec;
}
+ node.declarations = ownArray(declarations);
auto end = expect(tok!"}");
if (end !is null) node.endLocation = end.index;
return node;
@@ -4642,7 +4797,7 @@ q{(int a, ...)
StructDeclaration parseStructDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new StructDeclaration;
+ auto node = allocate!StructDeclaration;
expect(tok!"struct");
if (currentIs(tok!"identifier"))
{
@@ -4682,7 +4837,7 @@ q{(int a, ...)
StructInitializer parseStructInitializer()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new StructInitializer;
+ auto node = allocate!StructInitializer;
expect(tok!"{");
node.structMemberInitializers = parseStructMemberInitializers();
expect(tok!"}");
@@ -4699,7 +4854,7 @@ q{(int a, ...)
StructMemberInitializer parseStructMemberInitializer()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new StructMemberInitializer;
+ auto node = allocate!StructMemberInitializer;
if (startsWith(tok!"identifier", tok!":"))
{
node.identifier = tokens[index++];
@@ -4719,12 +4874,13 @@ q{(int a, ...)
StructMemberInitializers parseStructMemberInitializers()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new StructMemberInitializers;
+ auto node = allocate!StructMemberInitializers;
+ StructMemberInitializer[] structMemberInitializers;
do
{
auto structMemberInitializer = parseStructMemberInitializer();
if (structMemberInitializer !is null)
- node.structMemberInitializers ~= structMemberInitializer;
+ structMemberInitializers ~= structMemberInitializer;
if (currentIs(tok!","))
{
advance();
@@ -4736,6 +4892,7 @@ q{(int a, ...)
else
break;
} while (moreTokens());
+ node.structMemberInitializers = ownArray(structMemberInitializers);
return node;
}
@@ -4749,7 +4906,7 @@ q{(int a, ...)
SwitchStatement parseSwitchStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new SwitchStatement;
+ auto node = allocate!SwitchStatement;
expect(tok!"switch");
expect(tok!"(");
node.expression = parseExpression();
@@ -4768,12 +4925,12 @@ q{(int a, ...)
Symbol parseSymbol()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Symbol;
+ auto node = allocate!Symbol;
if (currentIs(tok!"."))
- {
- node.dot = true;
- advance();
- }
+ {
+ node.dot = true;
+ advance();
+ }
node.identifierOrTemplateChain = parseIdentifierOrTemplateChain();
return node;
}
@@ -4788,7 +4945,7 @@ q{(int a, ...)
SynchronizedStatement parseSynchronizedStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new SynchronizedStatement;
+ auto node = allocate!SynchronizedStatement;
expect(tok!"synchronized");
if (currentIs(tok!"("))
{
@@ -4810,7 +4967,7 @@ q{(int a, ...)
TemplateAliasParameter parseTemplateAliasParameter()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateAliasParameter;
+ auto node = allocate!TemplateAliasParameter;
expect(tok!"alias");
if (currentIs(tok!"identifier"))
{
@@ -4858,7 +5015,7 @@ q{(int a, ...)
TemplateArgument parseTemplateArgument()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateArgument;
+ auto node = allocate!TemplateArgument;
auto b = setBookmark();
auto t = parseType();
if (t !is null && currentIsOneOf(tok!",", tok!")"))
@@ -4897,7 +5054,7 @@ q{(int a, ...)
TemplateArguments parseTemplateArguments()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateArguments;
+ auto node = allocate!TemplateArguments;
expect(tok!"!");
if (currentIs(tok!"("))
{
@@ -4922,7 +5079,7 @@ q{(int a, ...)
TemplateDeclaration parseTemplateDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateDeclaration;
+ auto node = allocate!TemplateDeclaration;
node.comment = comment;
comment = null;
if (currentIs(tok!"enum"))
@@ -4938,12 +5095,14 @@ q{(int a, ...)
if (currentIs(tok!"if"))
node.constraint = parseConstraint();
if (expect(tok!"{") is null) return null;
+ Declaration[] declarations;
while (moreTokens() && !currentIs(tok!"}"))
{
auto decl = parseDeclaration();
if (decl !is null)
- node.declarations ~= decl;
+ declarations ~= decl;
}
+ node.declarations = ownArray(declarations);
expect(tok!"}");
return node;
}
@@ -4958,7 +5117,7 @@ q{(int a, ...)
EponymousTemplateDeclaration parseEponymousTemplateDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new EponymousTemplateDeclaration;
+ auto node = allocate!EponymousTemplateDeclaration;
expect(tok!"enum");
auto ident = expect(tok!"identifier");
if (ident is null) return null;
@@ -4980,7 +5139,7 @@ q{(int a, ...)
TemplateInstance parseTemplateInstance()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateInstance;
+ auto node = allocate!TemplateInstance;
auto ident = expect(tok!"identifier");
if (ident is null) return null;
node.identifier = *ident;
@@ -5000,7 +5159,7 @@ q{(int a, ...)
TemplateMixinExpression parseTemplateMixinExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateMixinExpression;
+ auto node = allocate!TemplateMixinExpression;
if (expect(tok!"mixin") is null) return null;
node.mixinTemplateName = parseMixinTemplateName();
if (currentIs(tok!"!"))
@@ -5024,7 +5183,7 @@ q{(int a, ...)
TemplateParameter parseTemplateParameter()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateParameter;
+ auto node = allocate!TemplateParameter;
switch (current.type)
{
case tok!"alias":
@@ -5071,7 +5230,7 @@ q{(int a, ...)
TemplateParameters parseTemplateParameters()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateParameters;
+ auto node = allocate!TemplateParameters;
if (expect(tok!"(") is null) return null;
if (!currentIs(tok!")"))
node.templateParameterList = parseTemplateParameterList();
@@ -5108,7 +5267,7 @@ q{(int a, ...)
TemplateSingleArgument parseTemplateSingleArgument()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateSingleArgument;
+ auto node = allocate!TemplateSingleArgument;
switch (current.type)
{
case tok!"true":
@@ -5138,7 +5297,7 @@ q{(int a, ...)
TemplateThisParameter parseTemplateThisParameter()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateThisParameter;
+ auto node = allocate!TemplateThisParameter;
expect(tok!"this");
node.templateTypeParameter = parseTemplateTypeParameter();
return node;
@@ -5154,7 +5313,7 @@ q{(int a, ...)
TemplateTupleParameter parseTemplateTupleParameter()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateTupleParameter;
+ auto node = allocate!TemplateTupleParameter;
auto i = expect(tok!"identifier");
if (i is null)
return null;
@@ -5173,7 +5332,7 @@ q{(int a, ...)
TemplateTypeParameter parseTemplateTypeParameter()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateTypeParameter;
+ auto node = allocate!TemplateTypeParameter;
auto ident = expect(tok!"identifier");
if (ident is null) return null;
node.identifier = *ident;
@@ -5200,7 +5359,7 @@ q{(int a, ...)
TemplateValueParameter parseTemplateValueParameter()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateValueParameter;
+ auto node = allocate!TemplateValueParameter;
if ((node.type = parseType()) is null) return null;
auto ident = expect(tok!"identifier");
if (ident is null) return null;
@@ -5228,7 +5387,7 @@ q{(int a, ...)
TemplateValueParameterDefault parseTemplateValueParameterDefault()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TemplateValueParameterDefault;
+ auto node = allocate!TemplateValueParameterDefault;
expect(tok!"=");
switch (current.type)
{
@@ -5256,7 +5415,7 @@ q{(int a, ...)
ExpressionNode parseTernaryExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TernaryExpression;
+ auto node = allocate!TernaryExpression;
node.orOrExpression = parseOrOrExpression();
if (currentIs(tok!"?"))
{
@@ -5278,7 +5437,7 @@ q{(int a, ...)
ThrowStatement parseThrowStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new ThrowStatement;
+ auto node = allocate!ThrowStatement;
expect(tok!"throw");
node.expression = parseExpression();
expect(tok!";");
@@ -5295,7 +5454,7 @@ q{(int a, ...)
TraitsExpression parseTraitsExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TraitsExpression;
+ auto node = allocate!TraitsExpression;
if (expect(tok!"__traits") is null) return null;
if (expect(tok!"(") is null) return null;
auto ident = expect(tok!"identifier");
@@ -5320,7 +5479,7 @@ q{(int a, ...)
TryStatement parseTryStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TryStatement;
+ auto node = allocate!TryStatement;
expect(tok!"try");
node.declarationOrStatement = parseDeclarationOrStatement();
if (currentIs(tok!"catch"))
@@ -5340,7 +5499,7 @@ q{(int a, ...)
Type parseType()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Type;
+ auto node = allocate!Type;
switch(current.type)
{
case tok!"const":
@@ -5359,6 +5518,7 @@ q{(int a, ...)
node.type2 = parseType2();
if (node.type2 is null)
return null;
+ TypeSuffix[] typeSuffixes;
loop: while (moreTokens()) switch (current.type)
{
case tok!"*":
@@ -5367,13 +5527,14 @@ q{(int a, ...)
case tok!"function":
auto suffix = parseTypeSuffix();
if (suffix !is null)
- node.typeSuffixes ~= suffix;
+ typeSuffixes ~= suffix;
else
return null;
break;
default:
break loop;
}
+ node.typeSuffixes = ownArray(typeSuffixes);
return node;
}
@@ -5390,7 +5551,7 @@ q{(int a, ...)
Type2 parseType2()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new Type2;
+ auto node = allocate!Type2;
switch (current.type)
{
case tok!"identifier":
@@ -5510,7 +5671,7 @@ q{(int a, ...)
TypeSpecialization parseTypeSpecialization()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TypeSpecialization;
+ auto node = allocate!TypeSpecialization;
switch (current.type)
{
case tok!"struct":
@@ -5554,7 +5715,7 @@ q{(int a, ...)
TypeSuffix parseTypeSuffix()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TypeSuffix;
+ auto node = allocate!TypeSuffix;
switch(current.type)
{
case tok!"*":
@@ -5595,8 +5756,10 @@ q{(int a, ...)
case tok!"function":
node.delegateOrFunction = advance();
node.parameters = parseParameters();
+ MemberFunctionAttribute[] memberFunctionAttributes;
while (currentIsMemberFunctionAttribute())
- node.memberFunctionAttributes ~= parseMemberFunctionAttribute();
+ memberFunctionAttributes ~= parseMemberFunctionAttribute();
+ node.memberFunctionAttributes = ownArray(memberFunctionAttributes);
return node;
default:
error(`"*", "[", "delegate", or "function" expected.`);
@@ -5614,7 +5777,7 @@ q{(int a, ...)
TypeidExpression parseTypeidExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TypeidExpression;
+ auto node = allocate!TypeidExpression;
expect(tok!"typeid");
expect(tok!"(");
auto b = setBookmark();
@@ -5645,7 +5808,7 @@ q{(int a, ...)
TypeofExpression parseTypeofExpression()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new TypeofExpression;
+ auto node = allocate!TypeofExpression;
expect(tok!"typeof");
expect(tok!"(");
if (currentIs(tok!"return"))
@@ -5687,7 +5850,7 @@ q{(int a, ...)
mixin(traceEnterAndExit!(__FUNCTION__));
if (!moreTokens())
return null;
- auto node = new UnaryExpression;
+ auto node = allocate!UnaryExpression;
switch (current.type)
{
case tok!"&":
@@ -5760,19 +5923,19 @@ q{(int a, ...)
break loop;
case tok!"(":
- auto newUnary = new UnaryExpression();
+ auto newUnary = allocate!UnaryExpression();
newUnary.functionCallExpression = parseFunctionCallExpression(node);
node = newUnary;
break;
case tok!"++":
case tok!"--":
- auto n = new UnaryExpression();
+ auto n = allocate!UnaryExpression();
n.unaryExpression = node;
n.suffix = advance();
node = n;
break;
case tok!"[":
- auto n = new UnaryExpression;
+ auto n = allocate!UnaryExpression;
if (isSliceExpression())
n.sliceExpression = parseSliceExpression(node);
else
@@ -5781,7 +5944,7 @@ q{(int a, ...)
break;
case tok!".":
advance();
- auto n = new UnaryExpression();
+ auto n = allocate!UnaryExpression();
n.unaryExpression = node;
n.identifierOrTemplateInstance = parseIdentifierOrTemplateInstance();
node = n;
@@ -5815,7 +5978,7 @@ q{doStuff(5)}c;
UnionDeclaration parseUnionDeclaration()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new UnionDeclaration;
+ auto node = allocate!UnionDeclaration;
// grab line number even if it's anonymous
auto l = expect(tok!"union").line;
bool templated = false;
@@ -5869,7 +6032,7 @@ q{doStuff(5)}c;
VariableDeclaration parseVariableDeclaration(Type type = null, bool isAuto = false)
{
mixin (traceEnterAndExit!(__FUNCTION__));
- auto node = new VariableDeclaration;
+ auto node = allocate!VariableDeclaration;
if (isAuto)
{
node.autoDeclaration = parseAutoDeclaration();
@@ -5878,16 +6041,18 @@ q{doStuff(5)}c;
node.type = type is null ? parseType() : type;
+ Declarator[] declarators;
while(true)
{
auto declarator = parseDeclarator();
if (declarator is null) return null;
- node.declarators ~= declarator;
+ declarators ~= declarator;
if (moreTokens() && currentIs(tok!","))
advance();
else
break;
}
+ node.declarators = ownArray(declarators);
expect(tok!";");
return node;
}
@@ -5915,7 +6080,7 @@ q{doStuff(5)}c;
VersionCondition parseVersionCondition()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new VersionCondition;
+ auto node = allocate!VersionCondition;
mixin (expectSequence!(tok!"version", tok!"("));
if (currentIsOneOf(tok!"intLiteral", tok!"identifier",
tok!"unittest", tok!"assert"))
@@ -5941,7 +6106,7 @@ q{doStuff(5)}c;
VersionSpecification parseVersionSpecification()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new VersionSpecification;
+ auto node = allocate!VersionSpecification;
mixin (expectSequence!(tok!"version", tok!"="));
if (!currentIsOneOf(tok!"identifier", tok!"intLiteral"))
{
@@ -5963,7 +6128,7 @@ q{doStuff(5)}c;
WhileStatement parseWhileStatement()
{
mixin(traceEnterAndExit!(__FUNCTION__));
- auto node = new WhileStatement;
+ auto node = allocate!WhileStatement;
expect(tok!"while");
node.startIndex = current().index;
expect(tok!"(");
@@ -6023,6 +6188,11 @@ q{doStuff(5)}c;
*/
string fileName;
+ /**
+ * Allocator used for creating AST nodes
+ */
+ CAllocator allocator;
+
/**
* Function that is called when a warning or error is encountered.
* The parameters are the file name, line number, column number,
@@ -6051,9 +6221,29 @@ q{doStuff(5)}c;
return index < tokens.length;
}
-
protected:
+ T[] ownArray(T)(T[] from)
+ {
+ if (allocator is null)
+ return from;
+ T[] to = cast(T[]) allocator.allocate(T.sizeof * from.length);
+ to[] = from[];
+ return to;
+ }
+
+ T allocate(T, Args...)(auto ref Args args)
+ {
+ import std.stdio;
+ if (allocator is null)
+ return new T(args);
+ enum numBytes = __traits(classInstanceSize, T);
+ void[] mem = allocator.allocate(numBytes);
+ T t = emplace!T(mem, args);
+ assert (cast(void*) t == mem.ptr, "%x, %x".format(cast(void*) t, mem.ptr));
+ return t;
+ }
+
bool isCastQualifier() const
{
switch (current.type)
@@ -6294,13 +6484,13 @@ protected:
mixin ("node = part is null ? parse" ~ ExpressionPartType.stringof ~ "() : part;");
while (currentIsOneOf(Operators))
{
- auto n = new ExpressionType;
+ auto n = allocate!ExpressionType;
static if (__traits(hasMember, ExpressionType, "operator"))
- {
- n.line = current.line;
- n.column = current.column;
+ {
+ n.line = current.line;
+ n.column = current.column;
n.operator = advance().type;
- }
+ }
else
advance();
n.left = node;
@@ -6312,10 +6502,11 @@ protected:
ListType parseCommaSeparatedRule(alias ListType, alias ItemType)(bool allowTrailingComma = false)
{
- auto node = new ListType;
+ auto node = allocate!ListType;
+ ItemType[] items;
while (moreTokens())
{
- mixin ("node.items ~= parse" ~ ItemType.stringof ~ "();");
+ mixin ("items ~= parse" ~ ItemType.stringof ~ "();");
if (currentIs(tok!","))
{
advance();
@@ -6330,6 +6521,7 @@ protected:
else
break;
}
+ node.items = ownArray(items);
return node;
}
@@ -6565,7 +6757,7 @@ protected:
string testName)
{
auto r = byToken(cast(ubyte[]) sourceCode);
- Parser p = new Parser;
+ Parser p = allocate!Parser;
p.messageFunction = &doNothingErrorFunction;
p.fileName = testName ~ ".d";
p.tokens = r.array();
@@ -6575,13 +6767,13 @@ protected:
template simpleParse(NodeType, parts ...)
{
static if (__traits(hasMember, NodeType, "comment"))
- enum simpleParse = "auto node = new " ~ NodeType.stringof ~ ";\n"
+ enum simpleParse = "auto node = allocate!" ~ NodeType.stringof ~ ";\n"
~ "node.comment = comment;\n"
~ "comment = null;\n"
~ simpleParseItems!(parts)
~ "\nreturn node;\n";
else
- enum simpleParse = "auto node = new " ~ NodeType.stringof ~ ";\n"
+ enum simpleParse = "auto node = allocate!" ~ NodeType.stringof ~ ";\n"
~ simpleParseItems!(parts)
~ "\nreturn node;\n";
}
@@ -6627,7 +6819,7 @@ protected:
{
void trace(lazy string message)
{
- auto depth = format("%4d ", _traceDepth);
+ auto depth = format("%4d ", _traceDepth);
if (suppressMessages > 0)
return;
if (index < tokens.length)