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)