xref: /haiku/src/system/boot/loader/heap.cpp (revision 16c83730262f1e4f0fc69d80744bb36dcfbbe3af)
1 /*
2  * Copyright 2003-2013, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <boot/heap.h>
8 
9 #ifdef HEAP_TEST
10 #	include <stdio.h>
11 #endif
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #include <algorithm>
16 
17 #include <boot/platform.h>
18 #include <util/SplayTree.h>
19 
20 
21 //#define TRACE_HEAP
22 #ifdef TRACE_HEAP
23 #	define TRACE(format...)	dprintf(format)
24 #else
25 #	define TRACE(format...)	do { } while (false)
26 #endif
27 
28 
29 /*!	This is a very simple malloc()/free() implementation - it only
30 	manages a free list.
31 	After heap_init() is called, all free memory is contained in one
32 	big chunk, the only entry in the free link list (which is a single
33 	linked list).
34 	When memory is allocated, the smallest free chunk that contains
35 	the requested size is split (or taken as a whole if it can't be
36 	splitted anymore), and it's lower half will be removed from the
37 	free list.
38 	The free list is ordered by size, starting with the smallest
39 	free chunk available. When a chunk is freed, it will be joint
40 	with its predecessor or successor, if possible.
41 	To ease list handling, the list anchor itself is a free chunk with
42 	size 0 that can't be allocated.
43 */
44 
45 #define DEBUG_ALLOCATIONS
46 	// if defined, freed memory is filled with 0xcc
47 #define DEBUG_MAX_HEAP_USAGE
48 	// if defined, the maximum heap usage is determined and printed before
49 	// entering the kernel
50 
51 
52 const static size_t kAlignment = 8;
53 	// all memory chunks will be a multiple of this
54 
55 
56 class Chunk {
57 public:
58 	size_t CompleteSize() const
59 	{
60 		return fSize;
61 	}
62 
63 protected:
64 	union {
65 		size_t	fSize;
66 		char	fAlignment[kAlignment];
67 	};
68 };
69 
70 
71 class FreeChunk;
72 
73 
74 struct FreeChunkData : SplayTreeLink<FreeChunk> {
75 
76 	FreeChunk* Next() const
77 	{
78 		return fNext;
79 	}
80 
81 	FreeChunk** NextLink()
82 	{
83 		return &fNext;
84 	}
85 
86 protected:
87 	FreeChunk*	fNext;
88 };
89 
90 
91 class FreeChunk : public Chunk, public FreeChunkData {
92 public:
93 			void				SetTo(size_t size);
94 
95 			size_t				Size() const;
96 
97 			FreeChunk*			Split(size_t splitSize);
98 			bool				IsTouching(FreeChunk* link);
99 			FreeChunk*			Join(FreeChunk* link);
100 
101 			void*				AllocatedAddress() const;
102 	static	FreeChunk*			SetToAllocated(void* allocated);
103 };
104 
105 
106 struct FreeChunkKey {
107 	FreeChunkKey(size_t size)
108 		:
109 		fSize(size),
110 		fChunk(NULL)
111 	{
112 	}
113 
114 	FreeChunkKey(const FreeChunk* chunk)
115 		:
116 		fSize(chunk->Size()),
117 		fChunk(chunk)
118 	{
119 	}
120 
121 	int Compare(const FreeChunk* chunk) const
122 	{
123 		size_t chunkSize = chunk->Size();
124 		if (chunkSize != fSize)
125 			return fSize < chunkSize ? -1 : 1;
126 
127 		if (fChunk == chunk)
128 			return 0;
129 		return fChunk < chunk ? -1 : 1;
130 	}
131 
132 private:
133 	size_t				fSize;
134 	const FreeChunk*	fChunk;
135 };
136 
137 
138 struct FreeChunkTreeDefinition {
139 	typedef FreeChunkKey	KeyType;
140 	typedef FreeChunk		NodeType;
141 
142 	static FreeChunkKey GetKey(const FreeChunk* node)
143 	{
144 		return FreeChunkKey(node);
145 	}
146 
147 	static SplayTreeLink<FreeChunk>* GetLink(FreeChunk* node)
148 	{
149 		return node;
150 	}
151 
152 	static int Compare(const FreeChunkKey& key, const FreeChunk* node)
153 	{
154 		return key.Compare(node);
155 	}
156 
157 	static FreeChunk** GetListLink(FreeChunk* node)
158 	{
159 		return node->NextLink();
160 	}
161 };
162 
163 
164 typedef IteratableSplayTree<FreeChunkTreeDefinition> FreeChunkTree;
165 
166 
167 static void* sHeapBase;
168 static size_t /*sHeapSize,*/ sMaxHeapSize, sAvailable, sMaxHeapUsage;
169 static FreeChunkTree sFreeChunkTree;
170 
171 
172 static inline size_t
173 align(size_t size)
174 {
175 	return (size + kAlignment - 1) & ~(kAlignment - 1);
176 }
177 
178 
179 void
180 FreeChunk::SetTo(size_t size)
181 {
182 	fSize = size;
183 	fNext = NULL;
184 }
185 
186 
187 /*!	Returns the amount of bytes that can be allocated
188 	in this chunk.
189 */
190 size_t
191 FreeChunk::Size() const
192 {
193 	return (addr_t)this + fSize - (addr_t)AllocatedAddress();
194 }
195 
196 
197 /*!	Splits the upper half at the requested location and returns it. This chunk
198 	will no longer be a valid FreeChunk object; only its fSize will be valid.
199  */
200 FreeChunk*
201 FreeChunk::Split(size_t splitSize)
202 {
203 	splitSize = align(splitSize);
204 
205 	FreeChunk* chunk = (FreeChunk*)((addr_t)AllocatedAddress() + splitSize);
206 	size_t newSize = (addr_t)chunk - (addr_t)this;
207 	chunk->fSize = fSize - newSize;
208 	chunk->fNext = NULL;
209 
210 	fSize = newSize;
211 
212 	return chunk;
213 }
214 
215 
216 /*!	Checks if the specified chunk touches this chunk, so
217 	that they could be joined.
218 */
219 bool
220 FreeChunk::IsTouching(FreeChunk* chunk)
221 {
222 	return chunk
223 		&& (((uint8*)this + fSize == (uint8*)chunk)
224 			|| (uint8*)chunk + chunk->fSize == (uint8*)this);
225 }
226 
227 
228 /*!	Joins the chunk to this chunk and returns the pointer
229 	to the new chunk - which will either be one of the
230 	two chunks.
231 	Note, the chunks must be joinable, or else this method
232 	doesn't work correctly. Use FreeChunk::IsTouching()
233 	to check if this method can be applied.
234 */
235 FreeChunk*
236 FreeChunk::Join(FreeChunk* chunk)
237 {
238 	if (chunk < this) {
239 		chunk->fSize += fSize;
240 		chunk->fNext = fNext;
241 
242 		return chunk;
243 	}
244 
245 	fSize += chunk->fSize;
246 	fNext = chunk->fNext;
247 
248 	return this;
249 }
250 
251 
252 void*
253 FreeChunk::AllocatedAddress() const
254 {
255 	return (void*)static_cast<const FreeChunkData*>(this);
256 }
257 
258 
259 FreeChunk*
260 FreeChunk::SetToAllocated(void* allocated)
261 {
262 	return static_cast<FreeChunk*>((FreeChunkData*)allocated);
263 }
264 
265 
266 //	#pragma mark -
267 
268 
269 void
270 heap_release(stage2_args* args)
271 {
272 	platform_release_heap(args, sHeapBase);
273 }
274 
275 
276 void
277 heap_print_statistics()
278 {
279 #ifdef DEBUG_MAX_HEAP_USAGE
280 	dprintf("maximum boot loader heap usage: %" B_PRIu32 "\n", sMaxHeapUsage);
281 #endif
282 }
283 
284 
285 status_t
286 heap_init(stage2_args* args)
287 {
288 	void* base;
289 	void* top;
290 	if (platform_init_heap(args, &base, &top) < B_OK)
291 		return B_ERROR;
292 
293 	sHeapBase = base;
294 	sMaxHeapSize = (uint8*)top - (uint8*)base;
295 
296 	// declare the whole heap as one chunk, and add it
297 	// to the free list
298 	FreeChunk* chunk = (FreeChunk*)base;
299 	chunk->SetTo(sMaxHeapSize);
300 	sFreeChunkTree.Insert(chunk);
301 
302 	sAvailable = chunk->Size();
303 #ifdef DEBUG_MAX_HEAP_USAGE
304 	sMaxHeapUsage = sMaxHeapSize - sAvailable;
305 #endif
306 
307 	return B_OK;
308 }
309 
310 
311 #if 0
312 char*
313 grow_heap(uint32 bytes)
314 {
315 	char* start;
316 
317 	if (sHeapSize + bytes > sMaxHeapSize)
318 		return NULL;
319 
320 	start = (char*)sHeapBase + sHeapSize;
321 	memset(start, 0, bytes);
322 	sHeapSize += bytes;
323 
324 	return start;
325 }
326 #endif
327 
328 #ifdef HEAP_TEST
329 void
330 dump_chunks(void)
331 {
332 	FreeChunk* chunk = sFreeChunkTree.FindMin();
333 	while (chunk != NULL) {
334 		printf("\t%p: chunk size = %ld, end = %p, next = %p\n", chunk,
335 			chunk->Size(), (uint8*)chunk + chunk->CompleteSize(),
336 			chunk->Next());
337 		chunk = chunk->Next();
338 	}
339 }
340 #endif
341 
342 uint32
343 heap_available(void)
344 {
345 	return (uint32)sAvailable;
346 }
347 
348 
349 void*
350 malloc(size_t size)
351 {
352 	if (sHeapBase == NULL || size == 0)
353 		return NULL;
354 
355 	// align the size requirement to a kAlignment bytes boundary
356 	if (size < sizeof(FreeChunkData))
357 		size = sizeof(FreeChunkData);
358 	size = align(size);
359 
360 	if (size > sAvailable) {
361 		dprintf("malloc(): Out of memory!\n");
362 		return NULL;
363 	}
364 
365 	FreeChunk* chunk = sFreeChunkTree.FindClosest(FreeChunkKey(size), true,
366 		true);
367 
368 	if (chunk == NULL) {
369 		// could not find a free chunk as large as needed
370 		dprintf("malloc(): Out of memory!\n");
371 		return NULL;
372 	}
373 
374 	sFreeChunkTree.Remove(chunk);
375 	sAvailable -= chunk->Size();
376 
377 	void* allocatedAddress = chunk->AllocatedAddress();
378 
379 	// If this chunk is bigger than the requested size and there's enough space
380 	// left over for a new chunk, we split it.
381 	if (chunk->Size() >= size + align(sizeof(FreeChunk))) {
382 		FreeChunk* freeChunk = chunk->Split(size);
383 		sFreeChunkTree.Insert(freeChunk);
384 		sAvailable += freeChunk->Size();
385 	}
386 
387 #ifdef DEBUG_MAX_HEAP_USAGE
388 	sMaxHeapUsage = std::max(sMaxHeapUsage, sMaxHeapSize - sAvailable);
389 #endif
390 
391 	TRACE("malloc(%lu) -> %p\n", size, allocatedAddress);
392 	return allocatedAddress;
393 }
394 
395 
396 void*
397 realloc(void* oldBuffer, size_t newSize)
398 {
399 	if (newSize == 0) {
400 		TRACE("realloc(%p, %lu) -> NULL\n", oldBuffer, newSize);
401 		free(oldBuffer);
402 		return NULL;
403 	}
404 
405 	size_t copySize = newSize;
406 	if (oldBuffer != NULL) {
407 		FreeChunk* oldChunk = FreeChunk::SetToAllocated(oldBuffer);
408 
409 		// Check if the old buffer still fits, and if it makes sense to keep it
410 		if (oldChunk->Size() >= newSize
411 			&& (oldChunk->Size() < 128 || newSize > oldChunk->Size() / 3)) {
412 			TRACE("realloc(%p, %lu) old buffer is large enough\n",
413 				oldBuffer, newSize);
414 			return oldChunk->AllocatedAddress();
415 		}
416 
417 		if (copySize > oldChunk->Size())
418 			copySize = oldChunk->Size();
419 	}
420 
421 	void* newBuffer = malloc(newSize);
422 	if (newBuffer == NULL)
423 		return NULL;
424 
425 	if (oldBuffer != NULL) {
426 		memcpy(newBuffer, oldBuffer, copySize);
427 		free(oldBuffer);
428 	}
429 
430 	TRACE("realloc(%p, %lu) -> %p\n", oldBuffer, newSize, newBuffer);
431 	return newBuffer;
432 }
433 
434 
435 void
436 free(void* allocated)
437 {
438 	if (allocated == NULL)
439 		return;
440 
441 	TRACE("free(%p)\n", allocated);
442 
443 	FreeChunk* freedChunk = FreeChunk::SetToAllocated(allocated);
444 
445 #ifdef DEBUG_ALLOCATIONS
446 	if (freedChunk->Size() > sMaxHeapSize - sAvailable) {
447 		panic("freed chunk %p clobbered (%#zx)!\n", freedChunk,
448 			freedChunk->Size());
449 	}
450 	{
451 		FreeChunk* chunk = sFreeChunkTree.FindMin();
452 		while (chunk) {
453 			if (chunk->Size() > sAvailable || freedChunk == chunk)
454 				panic("invalid chunk in free list (%p (%zu)), or double free\n",
455 					chunk, chunk->Size());
456 			chunk = chunk->Next();
457 		}
458 	}
459 #endif
460 
461 	// try to join the new free chunk with an existing one
462 	// it may be joined with up to two chunks
463 
464 	FreeChunk* chunk = sFreeChunkTree.FindMin();
465 	int32 joinCount = 0;
466 
467 	while (chunk) {
468 		FreeChunk* nextChunk = chunk->Next();
469 
470 		if (chunk->IsTouching(freedChunk)) {
471 			sFreeChunkTree.Remove(chunk);
472 			sAvailable -= chunk->Size();
473 
474 			freedChunk = chunk->Join(freedChunk);
475 
476 			if (++joinCount == 2)
477 				break;
478 		}
479 
480 		chunk = nextChunk;
481 	}
482 
483 	sFreeChunkTree.Insert(freedChunk);
484 	sAvailable += freedChunk->Size();
485 #ifdef DEBUG_MAX_HEAP_USAGE
486 	sMaxHeapUsage = std::max(sMaxHeapUsage, sMaxHeapSize - sAvailable);
487 #endif
488 }
489 
490