Skip to content

Commit 7ade793

Browse files
committed
Added TlsfAllocator class and unit tests
1 parent 50c5f36 commit 7ade793

File tree

4 files changed

+935
-407
lines changed

4 files changed

+935
-407
lines changed

engine/utils/TlsfAllocator.cpp

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
//
2+
// Copyright (c) 2020-present Caps Collective & contributors
3+
// Originally authored by Jonathan Moallem (@jonjondev) & Aryeh Zinn (@Raelr)
4+
//
5+
// This code is released under an unmodified zlib license.
6+
// For conditions of distribution and use, please see:
7+
// https://opensource.org/licenses/Zlib
8+
//
9+
10+
#include "TlsfAllocator.h"
11+
#include "Logging.h"
12+
13+
#include <memory>
14+
15+
#define TO_BYTES(val) reinterpret_cast<uint8_t*>(val)
16+
#define TO_HEADER(val) reinterpret_cast<BlockHeader*>(val)
17+
#define TO_FOOTER(val) reinterpret_cast<BlockFooter*>(val)
18+
#define TO_FREE_BLOCK(val) reinterpret_cast<FreeBlockNode*>(val)
19+
20+
#define HEADER_SIZE sizeof(BlockHeader)
21+
#define FOOTER_SIZE sizeof(BlockFooter)
22+
#define FREE_BLOCK_SIZE sizeof(FreeBlockNode)
23+
#define PAD_SIZE(size) size + HEADER_SIZE + FOOTER_SIZE
24+
25+
#define FL_MIN_INDEX 4
26+
#define MAX_SL_BUCKETS 16
27+
#define FL(size) 63 - __builtin_clzll(size)
28+
#define SL(size, fl) (size >> fl) & ((1 << FL_MIN_INDEX) - 1);
29+
#define FLAG_OFFSET 3
30+
#define MIN_ALLOCATION 16
31+
#define INVALID_INDEX UINT64_MAX
32+
33+
namespace Siege
34+
{
35+
36+
TlsfAllocator::TlsfAllocator() {}
37+
38+
TlsfAllocator::TlsfAllocator(const uint64_t size)
39+
: capacity {size}, bytesRemaining {size}
40+
{
41+
if (size == 0 || size < MIN_ALLOCATION || size >= UINT64_MAX)
42+
{
43+
capacity = 0;
44+
bytesRemaining = 0;
45+
return;
46+
}
47+
48+
uint64_t maxBuckets = (FL(size) - FL_MIN_INDEX) + 1;
49+
50+
uint64_t totalSize = PAD_SIZE(size);
51+
uint64_t slBucketSize = sizeof(uint16_t) * maxBuckets;
52+
uint64_t freeListSize = maxBuckets * MAX_SL_BUCKETS * sizeof(FreeBlockNode*);
53+
54+
uint64_t allocSize = totalSize + slBucketSize + freeListSize;
55+
56+
data = TO_BYTES(calloc(1, allocSize));
57+
58+
CC_ASSERT(data, "Allocation returned null. This should not happen and implies an implementation failure.")
59+
60+
freeList = (FreeBlockNode**)(data + totalSize);
61+
slBitmasks = (uint16_t*)(data + totalSize + freeListSize);
62+
63+
BlockHeader* firstHeader = CreateHeader(data, totalSize, FREE);
64+
65+
AddNewBlock(totalSize, firstHeader);
66+
}
67+
68+
TlsfAllocator::BlockHeader* TlsfAllocator::CreateHeader(uint8_t* ptr, const uint64_t size,
69+
HeaderFlags flags)
70+
{
71+
if (!ptr) return nullptr;
72+
BlockHeader* header = TO_HEADER(ptr);
73+
header->sizeAndFlags = (size << FLAG_OFFSET) | flags;
74+
return header;
75+
}
76+
77+
void* TlsfAllocator::Allocate(const uint64_t& size)
78+
{
79+
size_t requiredSize = sizeof(BlockHeader) + size + sizeof(BlockFooter);
80+
if (!data || capacity == 0 ||size == 0 || requiredSize < size || requiredSize > bytesRemaining
81+
|| requiredSize < MIN_ALLOCATION) return nullptr;
82+
83+
FreeBlockNode* block = FindFreeBlock(requiredSize);
84+
85+
if (!block) return nullptr;
86+
87+
BlockHeader* header = TrySplitBlock(block, requiredSize);
88+
header = CreateHeader(TO_BYTES(header), requiredSize, FULL);
89+
BlockFooter* footer = CreateFooter(TO_BYTES(GetFooter(header)) , requiredSize);
90+
91+
uint8_t* ptr = GetBlockData(header);
92+
bytesRemaining -= requiredSize;
93+
return ptr;
94+
}
95+
96+
TlsfAllocator::BlockHeader* TlsfAllocator::TrySplitBlock(TlsfAllocator::FreeBlockNode* node, uint64_t allocatedSize)
97+
{
98+
if (!node) return nullptr;
99+
100+
uint64_t oldSize = GetHeaderSize(GetHeader(node));
101+
102+
BlockHeader* header = GetHeader(node);
103+
104+
if (!RemoveFreeBlock(node)) return nullptr;
105+
106+
if (oldSize <= allocatedSize || ((oldSize - allocatedSize) <
107+
(HEADER_SIZE + FREE_BLOCK_SIZE + FOOTER_SIZE))) return header;
108+
109+
AddNewBlock(oldSize - allocatedSize, TO_HEADER(TO_BYTES(header) + allocatedSize));
110+
111+
return header;
112+
}
113+
114+
void TlsfAllocator::AddNewBlock(const uint64_t size, BlockHeader* header)
115+
{
116+
uint64_t fl = 0,sl = 0, index;
117+
index = CalculateFreeBlockIndices(size, fl, sl);
118+
119+
BlockHeader* newHeader = CreateHeader(TO_BYTES(header), size, FREE);
120+
FreeBlockNode* node = CreateFreeBlock(TO_BYTES(GetFreeBlock(newHeader)), nullptr, freeList[index]);
121+
BlockFooter* footer = CreateFooter(TO_BYTES(GetFooter(newHeader)), size);
122+
123+
if (node->next) node->next->prev = node;
124+
125+
freeList[index] = node;
126+
flBitmask |= (1ULL << fl);
127+
slBitmasks[fl] |= (1 << sl);
128+
}
129+
130+
bool TlsfAllocator::RemoveFreeBlock(TlsfAllocator::FreeBlockNode* node)
131+
{
132+
BlockHeader* header = GetHeader(node);
133+
134+
if (!header) return false;
135+
136+
uint64_t oldSize = GetHeaderSize(header);
137+
138+
uint64_t fl, sl, index;
139+
140+
index = CalculateFreeBlockIndices(oldSize, fl, sl);
141+
142+
if (!node->prev) freeList[index] = node->next;
143+
else node->prev->next = node->next;
144+
145+
if (node->next) node->next->prev = node->prev;
146+
147+
if (!freeList[index]) slBitmasks[fl] &= ~(1 << sl);
148+
if (!slBitmasks[fl]) flBitmask &= ~(1ULL << fl);
149+
150+
node->next = nullptr;
151+
node->prev = nullptr;
152+
153+
return true;
154+
}
155+
156+
TlsfAllocator::FreeBlockNode* TlsfAllocator::FindFreeBlock(const uint64_t& size)
157+
{
158+
uint64_t fl, sl, index;
159+
index = CalculateFreeBlockIndices(size, fl, sl);
160+
161+
if (!IsFree(fl, sl)) index = GetNextFreeSlotIndex(fl, sl);
162+
if (index == INVALID_INDEX) return nullptr;
163+
164+
return freeList[index];
165+
}
166+
167+
const uint64_t TlsfAllocator::GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl)
168+
{
169+
sl = __builtin_ctz(slBitmasks[fl] & ~(((1 << (sl + 1)) - 1)));
170+
171+
if (sl == 32) sl = 0;
172+
if (sl) return fl * MAX_SL_BUCKETS + sl;
173+
174+
fl = flBitmask & ~(((1ULL << (sl + 1)) - 1));
175+
176+
if (!fl) return INVALID_INDEX;
177+
178+
fl = __builtin_ctzll(fl);
179+
CC_ASSERT(slBitmasks[fl] > 0,
180+
"SlBitmasks is returning 0. This should not be happening and indicates an implementation error.")
181+
182+
sl = __builtin_ctz(slBitmasks[fl]);
183+
184+
return fl * MAX_SL_BUCKETS + sl;
185+
}
186+
187+
uint64_t TlsfAllocator::CalculateFreeBlockIndices(uint64_t size, OUT uint64_t & fl, OUT uint64_t & sl)
188+
{
189+
uint64_t rawFl = FL(size);
190+
fl = rawFl - FL_MIN_INDEX;
191+
sl = SL(size, fl);
192+
return fl * MAX_SL_BUCKETS + sl;
193+
}
194+
195+
TlsfAllocator::BlockFooter* TlsfAllocator::CreateFooter(uint8_t* ptr, const uint64_t size)
196+
{
197+
if (!ptr) return nullptr;
198+
BlockFooter* footer = TO_FOOTER(ptr);
199+
footer->totalBlockSize = size;
200+
return footer;
201+
}
202+
203+
TlsfAllocator::BlockHeader* TlsfAllocator::GetHeader(TlsfAllocator::FreeBlockNode* node)
204+
{
205+
if (!node) return nullptr;
206+
return TO_HEADER(TO_BYTES(node) - HEADER_SIZE);
207+
}
208+
209+
TlsfAllocator::BlockHeader* TlsfAllocator::GetPrevHeader(TlsfAllocator::BlockHeader* header)
210+
{
211+
if (!header || TO_BYTES(header) == data) return nullptr;
212+
return TO_HEADER(TO_BYTES(header) - GetPrevFooter(header)->totalBlockSize);
213+
}
214+
215+
TlsfAllocator::BlockHeader* TlsfAllocator::GetNextHeader(TlsfAllocator::BlockHeader* header)
216+
{
217+
if (!header) return nullptr;
218+
return TO_HEADER(TO_BYTES(header) + GetHeaderSize(header));
219+
}
220+
221+
TlsfAllocator::FreeBlockNode* TlsfAllocator::CreateFreeBlock(uint8_t* ptr,
222+
TlsfAllocator::FreeBlockNode* prev,
223+
TlsfAllocator::FreeBlockNode* next)
224+
{
225+
if (!ptr) return nullptr;
226+
FreeBlockNode* node = TO_FREE_BLOCK(ptr);
227+
node->prev = prev;
228+
node->next = next;
229+
return node;
230+
}
231+
232+
TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(BlockHeader* header)
233+
{
234+
if (!IsFree(header)) return nullptr;
235+
return TO_FREE_BLOCK(TO_BYTES(header) + HEADER_SIZE);
236+
}
237+
238+
TlsfAllocator::FreeBlockNode* TlsfAllocator::GetFreeBlock(const uint64_t fl, const uint64_t sl)
239+
{
240+
return freeList[fl * MAX_SL_BUCKETS + sl];
241+
}
242+
243+
TlsfAllocator::BlockFooter* TlsfAllocator::GetFooter(TlsfAllocator::BlockHeader* header)
244+
{
245+
uint64_t size = GetHeaderSize(header);
246+
return TO_FOOTER(TO_BYTES(header) + (size - FOOTER_SIZE));
247+
}
248+
249+
TlsfAllocator::BlockFooter* TlsfAllocator::GetPrevFooter(TlsfAllocator::BlockHeader* header)
250+
{
251+
uint8_t* raw = TO_BYTES(header);
252+
if (raw == data) return nullptr;
253+
return TO_FOOTER(raw - FOOTER_SIZE);
254+
}
255+
256+
uint8_t* TlsfAllocator::GetBlockData(TlsfAllocator::BlockHeader* header)
257+
{
258+
return TO_BYTES(header) + HEADER_SIZE;
259+
}
260+
261+
const uint64_t TlsfAllocator::GetHeaderSize(BlockHeader* header)
262+
{
263+
return header->sizeAndFlags >> FLAG_OFFSET;
264+
}
265+
266+
bool TlsfAllocator::IsFree(BlockHeader* header)
267+
{
268+
return header->sizeAndFlags & FREE;
269+
}
270+
271+
bool TlsfAllocator::IsFree(uint64_t fl, uint64_t sl)
272+
{
273+
return slBitmasks[fl] & (1 << sl);
274+
}
275+
276+
bool TlsfAllocator::PrevBlockIsFree(BlockHeader* header)
277+
{
278+
uint8_t* raw = TO_BYTES(header);
279+
if (raw == data) return false;
280+
return header && IsFree(TO_HEADER(raw - GetPrevFooter(header)->totalBlockSize));
281+
}
282+
283+
} // namespace Siege

engine/utils/TlsfAllocator.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//
2+
// Copyright (c) 2020-present Caps Collective & contributors
3+
// Originally authored by Jonathan Moallem (@jonjondev) & Aryeh Zinn (@Raelr)
4+
//
5+
// This code is released under an unmodified zlib license.
6+
// For conditions of distribution and use, please see:
7+
// https://opensource.org/licenses/Zlib
8+
//
9+
10+
#ifndef SIEGE_ENGINE_TLSFALLOCATOR_H
11+
#define SIEGE_ENGINE_TLSFALLOCATOR_H
12+
13+
#include <cstdint>
14+
#include "Macros.h"
15+
16+
namespace Siege
17+
{
18+
19+
class TlsfAllocator
20+
{
21+
public:
22+
enum HeaderFlags
23+
{
24+
FULL = 0,
25+
FREE = 1,
26+
PREV_IS_FREE = 2
27+
};
28+
29+
struct FreeBlockNode
30+
{
31+
FreeBlockNode* next {nullptr};
32+
FreeBlockNode* prev {nullptr};
33+
};
34+
35+
struct BlockHeader {
36+
uint32_t sizeAndFlags {0};
37+
};
38+
39+
struct BlockFooter
40+
{
41+
uint32_t totalBlockSize {0};
42+
};
43+
44+
// S'tructors
45+
46+
TlsfAllocator();
47+
TlsfAllocator(const uint64_t size);
48+
49+
// Deleted copy and move constructors
50+
51+
TlsfAllocator(const TlsfAllocator& other) = delete;
52+
TlsfAllocator(const TlsfAllocator&& other) = delete;
53+
54+
// Other functions
55+
56+
BlockHeader* CreateHeader(uint8_t* ptr, const uint64_t size, HeaderFlags flags);
57+
BlockFooter* CreateFooter(uint8_t* ptr, const uint64_t size);
58+
FreeBlockNode* CreateFreeBlock(uint8_t* ptr, FreeBlockNode* prev, FreeBlockNode* next);
59+
FreeBlockNode* FindFreeBlock(const uint64_t& size);
60+
61+
const uint64_t GetNextFreeSlotIndex(uint64_t& fl, uint64_t& sl);
62+
bool IsFree(BlockHeader* header);
63+
bool IsFree(uint64_t fl, uint64_t sl);
64+
bool PrevBlockIsFree(BlockHeader* header);
65+
uint64_t CalculateFreeBlockIndices(uint64_t size, OUT uint64_t & fl, OUT uint64_t & sl);
66+
67+
// Buffer manipulation Functions
68+
69+
FreeBlockNode* GetFreeBlock(BlockHeader* header);
70+
FreeBlockNode* GetFreeBlock(const uint64_t fl, const uint64_t sl);
71+
uint8_t* GetBlockData(BlockHeader* header);
72+
BlockFooter* GetFooter(BlockHeader* header);
73+
BlockFooter* GetPrevFooter(BlockHeader* header);
74+
BlockHeader* GetHeader(FreeBlockNode* node);
75+
BlockHeader* GetPrevHeader(BlockHeader* header);
76+
BlockHeader* GetNextHeader(BlockHeader* header);
77+
78+
// Allocate/Deallocate
79+
80+
void* Allocate(const uint64_t& size);
81+
BlockHeader* TrySplitBlock(FreeBlockNode* node, uint64_t allocatedSize);
82+
bool RemoveFreeBlock(FreeBlockNode* node);
83+
void AddNewBlock(const uint64_t size, BlockHeader* currentNode);
84+
85+
// Getters
86+
87+
const uint64_t GetHeaderSize(BlockHeader* header);
88+
const uint64_t Capacity() { return capacity; }
89+
const uint64_t BytesRemaining() { return bytesRemaining; }
90+
const uint8_t* Data() { return data; }
91+
const uint64_t FlBitmask() { return flBitmask; }
92+
const uint16_t& SlBitmask(const uint64_t fl) { return slBitmasks[fl]; }
93+
private:
94+
uint64_t capacity {0};
95+
uint64_t bytesRemaining {0};
96+
97+
uint8_t* data {nullptr};
98+
FreeBlockNode** freeList {nullptr};
99+
100+
// bitmasks
101+
uint64_t flBitmask {0};
102+
uint16_t* slBitmasks {nullptr};
103+
};
104+
105+
} // namespace Siege
106+
107+
#endif // SIEGE_ENGINE_TLSFALLOCATOR_H

0 commit comments

Comments
 (0)