1 /**
2     Utility functions for memory management
3 
4     Copyright: © 2012-2013 RejectedSoftware e.K.
5     		   © 2014-2015 Etienne Cimon
6     License: Subject to the terms of the MIT license.
7     Authors: Sönke Ludwig, Etienne Cimon
8 */
9 module memutils.allocators;
10 
11 public import memutils.constants;
12 import core.thread : Fiber;
13 import core.exception : OutOfMemoryError;
14 import core.stdc.stdlib;
15 import core.memory;
16 import core.sync.mutex;
17 import std.conv;
18 import std.traits;
19 import std.algorithm;
20 import std.traits : ReturnType;
21 import memutils.hashmap : HashMap;
22 import memutils.pool;
23 import memutils.memory;
24 import memutils.debugger;
25 import memutils.cryptosafe;
26 import memutils.freelist;
27 import memutils.utils : Malloc;
28 import core.thread : thread_isMainThread;
29 
30 static if (HasDebugAllocations) {
31 	pragma(msg, "Memory debugger enabled");
32 	alias LocklessAllocator = DebugAllocator!(AutoFreeListAllocator!(MallocAllocator));
33 	static if (HasCryptoSafe)
34 		alias CryptoSafeAllocator = DebugAllocator!(SecureAllocator!(AutoFreeListAllocator!(MallocAllocator)));
35 	alias ProxyGCAllocator = DebugAllocator!GCAllocator;
36 
37 }
38 else {
39 	alias LocklessAllocator = AutoFreeListAllocator!(MallocAllocator);
40 	static if (HasCryptoSafe)
41 		alias CryptoSafeAllocator = SecureAllocator!LocklessAllocator;
42 	alias ProxyGCAllocator = GCAllocator;
43 }
44 
45 interface Allocator {
46 	enum size_t alignment = 0x10;
47 
48 	enum size_t alignmentMask = alignment-1;
49 	
50 	void[] alloc(size_t sz)
51 	out {
52 		static if (!HasSecurePool && !HasBotan) assert((cast(size_t)__result.ptr & alignmentMask) == 0, "alloc() returned misaligned data.");
53 	}
54 
55 	void[] realloc(void[] mem, size_t new_sz)
56 	in {
57 		assert(mem.ptr !is null, "realloc() called with null array.");
58 		static if (!HasSecurePool && !HasBotan) assert((cast(size_t)mem.ptr & alignmentMask) == 0, "misaligned pointer passed to realloc().");
59 	}
60 	out { static if (!HasSecurePool && !HasBotan) assert((cast(size_t)__result.ptr & alignmentMask) == 0, "realloc() returned misaligned data."); }
61 
62 	void free(void[] mem)
63 	in {
64 		assert(mem.ptr !is null, "free() called with null array.");
65 		static if (!HasSecurePool && !HasBotan) assert((cast(size_t)mem.ptr & alignmentMask) == 0, "misaligned pointer passed to free().");
66 	}
67 }
68 
69 package:
70 
71 version(TLSGC) { } else {
72 	public import core.sync.mutex : Mutex;
73 	__gshared Mutex mtx;
74 }
75 
76 pragma(inline, true)
77 public auto getAllocator(int ALLOC)(bool is_freeing = false) {
78 	static if (ALLOC == LocklessFreeList) alias R = LocklessAllocator;
79 	else static if (ALLOC == NativeGC) alias R = ProxyGCAllocator;
80 	else static if (HasCryptoSafe && ALLOC == CryptoSafe) alias R = CryptoSafeAllocator;
81 	else static if (ALLOC == Mallocator) alias R = MallocAllocator;
82 	else static assert(false, "Invalid allocator specified");
83 	return getAllocator!R(is_freeing);
84 }
85 
86 R getAllocator(R)(bool is_freeing = false, bool kill_it = false) {
87 	version(TLSGC)
88 		static R alloc;
89 	else static __gshared R alloc;
90 
91 	static bool deinit;
92 	if (kill_it) {alloc.destroy(); deinit = true; alloc = null; return null; }
93 	if (!alloc && !is_freeing) {
94 		alloc = new R;
95 	}
96 
97 	return alloc;
98 }
99 
100 version(TLSGC)
101 static ~this() {
102 	getAllocator!CryptoSafeAllocator(false, true);
103 	getAllocator!LocklessAllocator(false, true);
104 }
105 
106 size_t alignedSize(size_t sz)
107 {
108 	return ((sz + Allocator.alignment - 1) / Allocator.alignment) * Allocator.alignment;
109 }
110 
111 void ensureValidMemory(void[] mem)
112 {
113 	auto bytes = cast(ubyte[])mem;
114 	swap(bytes[0], bytes[$-1]);
115 	swap(bytes[0], bytes[$-1]);
116 }
117 
118 void* extractUnalignedPointer(void* base)
119 {
120 	ubyte misalign = *(cast(const(ubyte)*)base-1);
121 	assert(misalign <= Allocator.alignment);
122 	return base - misalign;
123 }
124 
125 void* adjustPointerAlignment(void* base, ubyte* misalign_ = null)
126 {
127 	ubyte misalign = Allocator.alignment - (cast(size_t)base & Allocator.alignmentMask);
128 	base += misalign;
129 	if (misalign_) *misalign_ = misalign;
130 	else *(cast(ubyte*)base-1) = misalign;
131 	return base;
132 }
133 
134 template AllocSize(T)
135 {
136 	static if (is(T == class)) {
137 		// workaround for a strange bug where AllocSize!SSLStream == 0: TODO: dustmite!
138 		enum dummy = T.stringof ~ __traits(classInstanceSize, T).stringof;
139 		enum AllocSize = __traits(classInstanceSize, T);
140 	} else {
141 		enum AllocSize = T.sizeof;
142 	}
143 }
144 
145 template RefTypeOf(T) {
146 	static if( is(T == class) || __traits(isAbstractClass, T) || is(T == interface) ){
147 		alias RefTypeOf = T;
148 	} else {
149 		alias RefTypeOf = T*;
150 	}
151 }
152 
153 
154 unittest {
155 	void testAlign(void* p, size_t adjustment) {
156 		void* pa = adjustPointerAlignment(p);
157 		assert((cast(size_t)pa & Allocator.alignmentMask) == 0, "Non-aligned pointer.");
158 		assert(*(cast(const(ubyte)*)pa-1) == adjustment, "Invalid adjustment "~to!string(p)~": "~to!string(*(cast(const(ubyte)*)pa-1)));
159 		void* pr = extractUnalignedPointer(pa);
160 		assert(pr == p, "Recovered base != original");
161 	}
162 	void* ptr = .malloc(0x40);
163 	ptr += Allocator.alignment - (cast(size_t)ptr & Allocator.alignmentMask);
164 	testAlign(ptr++, 0x10);
165 	testAlign(ptr++, 0x0F);
166 	testAlign(ptr++, 0x0E);
167 	testAlign(ptr++, 0x0D);
168 	testAlign(ptr++, 0x0C);
169 	testAlign(ptr++, 0x0B);
170 	testAlign(ptr++, 0x0A);
171 	testAlign(ptr++, 0x09);
172 	testAlign(ptr++, 0x08);
173 	testAlign(ptr++, 0x07);
174 	testAlign(ptr++, 0x06);
175 	testAlign(ptr++, 0x05);
176 	testAlign(ptr++, 0x04);
177 	testAlign(ptr++, 0x03);
178 	testAlign(ptr++, 0x02);
179 	testAlign(ptr++, 0x01);
180 	testAlign(ptr++, 0x10);
181 }
182 
183 unittest {
184 	foreach( i; 0 .. 20 ){
185 		auto ia = alignedSize(i);
186 		assert(ia >= i);
187 		assert((ia & Allocator.alignmentMask) == 0);
188 		assert(ia < i+Allocator.alignment);
189 	}
190 }