1 /* Copyright (C) 2015 Wildfire Games.
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #ifndef INCLUDED_ALLOCATORS_UNIQUE_RANGE
24 #define INCLUDED_ALLOCATORS_UNIQUE_RANGE
25 
26 #include "lib/lib_api.h"
27 #include "lib/alignment.h"	// allocationAlignment
28 #include "lib/sysdep/vm.h"
29 
30 // we usually don't hold multiple references to allocations, so unique_ptr
31 // can be used instead of the more complex (ICC generated incorrect code on
32 // 2 occasions) and expensive shared_ptr.
33 // a custom deleter is required because allocators such as ReserveAddressSpace need to
34 // pass the size to their deleter. we want to mix pointers from various allocators, but
35 // unique_ptr's deleter is fixed at compile-time, so it would need to be general enough
36 // to handle all allocators.
37 // storing the size and a function pointer would be one such solution, with the added
38 // bonus of no longer requiring a complete type at the invocation of ~unique_ptr.
39 // however, this inflates the pointer size to 3 words. if only a few allocator types
40 // are needed, we can replace the function pointer with an index stashed into the
41 // lower bits of the pointer (safe because all allocations' addresses are multiples
42 // of allocationAlignment).
43 typedef intptr_t IdxDeleter;
44 
45 // no-op deleter (use when returning part of an existing allocation)
46 static const IdxDeleter idxDeleterNone = 0;
47 
48 typedef void (*UniqueRangeDeleter)(void* pointer, size_t size);
49 
50 /**
51  * register a deleter, returning its index within the table.
52  *
53  * @param deleter function pointer. must be uniquely associated with
54  *   the idxDeleter storage location.
55  * @param idxDeleter location where to store the next available index.
56  *   if it is already non-zero, skip the call to this function to
57  *   avoid overhead.
58  *
59  * thread-safe. idxDeleter is used for mutual exclusion between
60  * multiple callers for the same deleter. concurrent registration of
61  * different deleters is also safe due to atomic increments.
62  *
63  * halts the program if more than allocationAlignment deleters are
64  * to be registered.
65  **/
66 LIB_API void RegisterUniqueRangeDeleter(UniqueRangeDeleter deleter, volatile IdxDeleter* idxDeleter);
67 
68 LIB_API NOTHROW_DECLARE void CallUniqueRangeDeleter(void* pointer, size_t size, IdxDeleter idxDeleter);
69 
70 
71 // unfortunately, unique_ptr allows constructing without a custom deleter. to ensure callers can
72 // rely upon pointers being associated with a size, we introduce a `UniqueRange' replacement.
73 // its interface is identical to unique_ptr except for the constructors, the addition of
74 // size() and the removal of operator bool (which avoids implicit casts to int).
75 class UniqueRange
76 {
77 public:
78 	typedef void* pointer;
79 	typedef void element_type;
80 
UniqueRange()81 	UniqueRange()
82 	{
83 		Clear();
84 	}
85 
UniqueRange(pointer p,size_t size,IdxDeleter deleter)86 	UniqueRange(pointer p, size_t size, IdxDeleter deleter)
87 	{
88 		Set(p, size, deleter);
89 	}
90 
UniqueRange(UniqueRange && rvalue)91 	UniqueRange(UniqueRange&& rvalue)
92 	{
93 		Pilfer(rvalue);
94 	}
95 
96 	UniqueRange& operator=(UniqueRange&& rvalue)
97 	{
98 		UniqueRange& lvalue = rvalue;
99 		if(this != &lvalue)
100 		{
101 			Delete();
102 			Pilfer(lvalue);
103 		}
104 		return *this;
105 	}
106 
~UniqueRange()107 	~UniqueRange()
108 	{
109 		Delete();
110 	}
111 
get()112 	pointer get() const
113 	{
114 		return pointer(address_ & ~(allocationAlignment-1));
115 	}
116 
get_deleter()117 	IdxDeleter get_deleter() const
118 	{
119 		return IdxDeleter(address_ % allocationAlignment);
120 	}
121 
size()122 	size_t size() const
123 	{
124 		return size_;
125 	}
126 
127 	// side effect: subsequent get_deleter will return idxDeleterNone
release()128 	pointer release()	// relinquish ownership
129 	{
130 		pointer ret = get();
131 		Clear();
132 		return ret;
133 	}
134 
reset()135 	void reset()
136 	{
137 		Delete();
138 		Clear();
139 	}
140 
reset(pointer p,size_t size,IdxDeleter deleter)141 	void reset(pointer p, size_t size, IdxDeleter deleter)
142 	{
143 		Delete();
144 		Set(p, size, deleter);
145 	}
146 
swap(UniqueRange & rhs)147 	void swap(UniqueRange& rhs)
148 	{
149 		std::swap(address_, rhs.address_);
150 		std::swap(size_, rhs.size_);
151 	}
152 
153 	// don't define construction and assignment from lvalue,
154 	// but the declarations must be accessible
155 	UniqueRange(const UniqueRange&);
156 	UniqueRange& operator=(const UniqueRange&);
157 
158 private:
Set(pointer p,size_t size,IdxDeleter deleter)159 	void Set(pointer p, size_t size, IdxDeleter deleter)
160 	{
161 		ASSERT((uintptr_t(p) % allocationAlignment) == 0);
162 		ASSERT(size_t(deleter) < allocationAlignment);
163 
164 		address_ = uintptr_t(p) | deleter;
165 		size_ = size;
166 
167 		ASSERT(get() == p);
168 		ASSERT(get_deleter() == deleter);
169 		ASSERT(this->size() == size);
170 	}
171 
Clear()172 	void Clear()
173 	{
174 		Set(0, 0, idxDeleterNone);
175 	}
176 
Pilfer(UniqueRange & victim)177 	void Pilfer(UniqueRange& victim)
178 	{
179 		const size_t size = victim.size();
180 		const IdxDeleter idxDeleter = victim.get_deleter();
181 		pointer p = victim.release();
182 		Set(p, size, idxDeleter);
183 		victim.Clear();
184 	}
185 
Delete()186 	void Delete()
187 	{
188 		CallUniqueRangeDeleter(get(), size(), get_deleter());
189 	}
190 
191 	// (IdxDeleter is stored in the lower bits of address since size might not even be a multiple of 4.)
192 	uintptr_t address_;
193 	size_t size_;
194 };
195 
196 namespace std {
197 
swap(UniqueRange & p1,UniqueRange & p2)198 static inline void swap(UniqueRange& p1, UniqueRange& p2)
199 {
200 	p1.swap(p2);
201 }
202 
swap(UniqueRange && p1,UniqueRange & p2)203 static inline void swap(UniqueRange&& p1, UniqueRange& p2)
204 {
205 	p2.swap(p1);
206 }
207 
swap(UniqueRange & p1,UniqueRange && p2)208 static inline void swap(UniqueRange& p1, UniqueRange&& p2)
209 {
210 	p1.swap(p2);
211 }
212 
213 }
214 
215 LIB_API UniqueRange AllocateAligned(size_t size, size_t alignment);
216 
217 LIB_API UniqueRange AllocateVM(size_t size, vm::PageType pageSize = vm::kDefault, int prot = PROT_READ|PROT_WRITE);
218 
219 
220 #endif	// #ifndef INCLUDED_ALLOCATORS_UNIQUE_RANGE
221