Files
FlaxEngine/Source/Engine/Core/Memory/Allocation.h
Ari Vuollet 91c0ba1986 Enforce pointer alignment for InlinedAllocation
AssetReferences stored in inlined allocation needs to be aligned to
pointer sized boundary due to atomic operations having strict
requirements for such. Unaligned access seems to only crash on
Windows on ARM systems when trying to allocate TextRender draw chunks.
2025-10-19 22:14:28 +03:00

306 lines
7.8 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#pragma once
#include "Memory.h"
#include "Engine/Core/Core.h"
namespace AllocationUtils
{
// Rounds up the input value to the next power of 2 to be used as bigger memory allocation block. Handles overflow.
inline int32 RoundUpToPowerOf2(int32 capacity)
{
// Reference: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
capacity--;
capacity |= capacity >> 1;
capacity |= capacity >> 2;
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
uint64 capacity64 = (uint64)(capacity + 1) * 2;
if (capacity64 > MAX_int32)
capacity64 = MAX_int32;
return (int32)capacity64;
}
// Aligns the input value to the next power of 2 to be used as bigger memory allocation block.
inline int32 AlignToPowerOf2(int32 capacity)
{
// Reference: http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
capacity--;
capacity |= capacity >> 1;
capacity |= capacity >> 2;
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
capacity++;
return capacity;
}
inline int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity)
{
if (capacity < minCapacity)
capacity = minCapacity;
if (capacity < 8)
capacity = 8;
else
capacity = RoundUpToPowerOf2(capacity);
return capacity;
}
}
/// <summary>
/// The memory allocation policy that uses inlined memory of the fixed size (no resize support, does not use heap allocations at all).
/// </summary>
template<int Capacity>
class FixedAllocation
{
public:
enum { HasSwap = false };
typedef void* Tag;
template<typename T>
class alignas(sizeof(void*)) Data
{
private:
byte _data[Capacity * sizeof(T)];
public:
FORCE_INLINE Data()
{
}
FORCE_INLINE Data(Tag tag)
{
}
FORCE_INLINE ~Data()
{
}
FORCE_INLINE T* Get()
{
return reinterpret_cast<T*>(_data);
}
FORCE_INLINE const T* Get() const
{
return reinterpret_cast<const T*>(_data);
}
FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, const int32 minCapacity) const
{
ASSERT(minCapacity <= Capacity);
return Capacity;
}
FORCE_INLINE void Allocate(const int32 capacity)
{
ASSERT_LOW_LAYER(capacity <= Capacity);
}
FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount)
{
ASSERT_LOW_LAYER(capacity <= Capacity);
}
FORCE_INLINE void Free()
{
}
void Swap(Data& other)
{
// Not supported
}
};
};
/// <summary>
/// The memory allocation policy that uses default heap allocation.
/// </summary>
class HeapAllocation
{
public:
enum { HasSwap = true };
typedef void* Tag;
template<typename T>
class Data
{
private:
T* _data = nullptr;
public:
FORCE_INLINE Data()
{
}
FORCE_INLINE Data(Tag tag)
{
}
FORCE_INLINE ~Data()
{
Allocator::Free(_data);
}
FORCE_INLINE T* Get()
{
return _data;
}
FORCE_INLINE const T* Get() const
{
return _data;
}
FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, const int32 minCapacity) const
{
return AllocationUtils::CalculateCapacityGrow(capacity, minCapacity);
}
FORCE_INLINE void Allocate(const int32 capacity)
{
ASSERT_LOW_LAYER(!_data);
_data = static_cast<T*>(Allocator::Allocate(capacity * sizeof(T)));
}
FORCE_INLINE void Relocate(const int32 capacity, int32 oldCount, int32 newCount)
{
T* newData = capacity != 0 ? static_cast<T*>(Allocator::Allocate(capacity * sizeof(T))) : nullptr;
if (oldCount)
{
if (newCount > 0)
Memory::MoveItems(newData, _data, newCount);
Memory::DestructItems(_data, oldCount);
}
Allocator::Free(_data);
_data = newData;
}
FORCE_INLINE void Free()
{
Allocator::Free(_data);
_data = nullptr;
}
FORCE_INLINE void Swap(Data& other)
{
::Swap(_data, other._data);
}
};
};
/// <summary>
/// The memory allocation policy that uses inlined memory of the fixed size and supports using additional allocation to increase its capacity (eg. via heap allocation).
/// </summary>
template<int Capacity, typename FallbackAllocation = HeapAllocation>
class InlinedAllocation
{
public:
enum { HasSwap = false };
typedef void* Tag;
template<typename T>
class alignas(sizeof(void*)) Data
{
private:
typedef typename FallbackAllocation::template Data<T> FallbackData;
bool _useFallback = false;
alignas(sizeof(void*)) byte _data[Capacity * sizeof(T)];
FallbackData _fallback;
public:
FORCE_INLINE Data()
{
}
FORCE_INLINE Data(Tag tag)
{
}
FORCE_INLINE ~Data()
{
}
FORCE_INLINE T* Get()
{
return _useFallback ? _fallback.Get() : reinterpret_cast<T*>(_data);
}
FORCE_INLINE const T* Get() const
{
return _useFallback ? _fallback.Get() : reinterpret_cast<const T*>(_data);
}
FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity) const
{
return minCapacity <= Capacity ? Capacity : _fallback.CalculateCapacityGrow(capacity, minCapacity);
}
FORCE_INLINE void Allocate(int32 capacity)
{
if (capacity > Capacity)
{
_useFallback = true;
_fallback.Allocate(capacity);
}
}
FORCE_INLINE void Relocate(int32 capacity, int32 oldCount, int32 newCount)
{
T* data = reinterpret_cast<T*>(_data);
// Check if the new allocation will fit into inlined storage
if (capacity <= Capacity)
{
if (_useFallback)
{
// Move the items from other allocation to the inlined storage
Memory::MoveItems(data, _fallback.Get(), newCount);
// Free the other allocation
Memory::DestructItems(_fallback.Get(), oldCount);
_fallback.Free();
_useFallback = false;
}
}
else
{
if (_useFallback)
{
// Resize other allocation
_fallback.Relocate(capacity, oldCount, newCount);
}
else
{
// Allocate other allocation
_fallback.Allocate(capacity);
_useFallback = true;
// Move the items from the inlined storage to the other allocation
Memory::MoveItems(_fallback.Get(), data, newCount);
Memory::DestructItems(data, oldCount);
}
}
}
FORCE_INLINE void Free()
{
if (_useFallback)
{
_useFallback = false;
_fallback.Free();
}
}
void Swap(Data& other)
{
// Not supported
}
};
};
using DefaultAllocation = HeapAllocation;