Switch FluidSynth to use a custom memory allocator

Add a Doom-style "zone" memory allocator that can be used instead of
malloc()/realloc()/free() for FluidSynth.

This removes the need to reconfigure the Circle heap and could reduce
fragmentation caused by repeated loading/unloading of SoundFonts.
This commit is contained in:
Dale Whinham committed 2021-01-02 22:42:17 +00:00
1 parent f060199032
commit 5d39db9682
10 files changed
+588 -15

No files matched your search

+3
View File
@@ -33,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Switching MT-32 ROM sets whilst MIDI was playing could cause a crash.
- Switching SoundFonts too often (especially large ones, and especially while MIDI is playing) could result in a crash due to an out-of-memory condition.
* This has been solved with some upstream fixes by [the FluidSynth team](https://github.com/FluidSynth/fluidsynth/issues/727) combined with a custom memory allocator that should prevent fragmentation. Many thanks to @derselbst and @jjceresa for their help!
- Power saving mode would never be entered whilst in SoundFont mode if the user had switched SoundFonts.
## [0.7.1] - 2020-11-26
+2 -1
View File
@@ -24,7 +24,8 @@ OBJS := src/config.o \
src/rommanager.o \
src/soundfontmanager.o \
src/synth/mt32synth.o \
src/synth/soundfontsynth.o
src/synth/soundfontsynth.o \
src/zoneallocator.o
EXTRACLEAN += src/*.d src/*.o \
src/control/*.d src/control/*.o \
-5
View File
@@ -23,11 +23,6 @@ $(CIRCLE_STDLIB_CONFIG) $(CIRCLE_CONFIG)&:
# Enable multi-core
echo "DEFINE += -DARM_ALLOW_MULTI_CORE" >> $(CIRCLE_CONFIG)
# Heap configuration
echo "DEFINE += -DHEAP_DEFAULT_NEW=HEAP_ANY" >> $(CIRCLE_CONFIG)
echo "DEFINE += -DHEAP_DEFAULT_MALLOC=HEAP_ANY" >> $(CIRCLE_CONFIG)
echo "DEFINE += -DHEAP_BLOCK_BUCKET_SIZES=0x40,0x400,0x1000,0x4000,0x10000,0x40000,0x80000,0x100000,0x800000,0x1000000,0x2000000,0x4000000,0x8000000,0x10000000,0x14000000,0x18000000,0x1C000000,0x20000000" >> $(CIRCLE_CONFIG)
# Disable delay loop calibration (boot speed improvement)
echo "DEFINE += -DNO_CALIBRATE_DELAY" >> $(CIRCLE_CONFIG)
-2
View File
@@ -388,8 +388,6 @@ F0 7D { command } { parameters } F7
**A:** Your keyboard is probably sending note data on **channel 1**, but by default, on power-up the MT-32 is set to receive on MIDI **channels 2-10**. Check your keyboard's documentation to see if you can change the transmit channel. If this isn't possible, [see this wiki page](https://github.com/dwhinham/mt32-pi/wiki/MIDI-channel-assignment) for more information on how to reassign the emulated MT-32's channels.
- **Q:** I'm getting sound distortion and/or the power LED on my Raspberry Pi is blinking - why?
**A:** It's possible that the Pi is not receiving enough power. Try swapping the USB cable being used to power the Pi, or try another power supply. Check the [Raspberry Pi power supply page] for recommended power supply specifications.
- **Q:** I'm trying to switch between very large (>200MB) SoundFonts and received an error message/crash - what's wrong?
**A:** This is probably due to memory fragmentation. When using a Raspberry Pi with a lower amount of RAM such as the Pi 3A+ (512MB), the memory allocator may struggle to find enough free contiguous space after unloading a large SoundFont and attempting to load another one. This is a difficult problem to solve, but may be improved in the future. For now, stick to smaller SoundFonts, avoid switching between large ones, or choose a Pi with more RAM.
- **Q:** Why don't I see any video output on my HDMI-connected monitor or television? Has the Pi has failed to boot?
**A:** This is completely normal - `mt32-pi` is designed to run headless and therefore there is no video output. For troubleshooting purposes, it's possible to compile `mt32-pi` with HDMI debug logs enabled, but these builds will hang on a Raspberry Pi 4 if **no** HDMI display is attached due to a quirk of the Pi 4 and Circle. Hence, for regular use, video output is disabled.
- **Q:** What happened to the old `mt32-pi` project that was based on a minimal Linux distro built with Buildroot?
+2
View File
@@ -31,6 +31,7 @@
#include "config.h"
#include "mt32pi.h"
#include "zoneallocator.h"
class CKernel : public CStdlibApp
{
@@ -57,6 +58,7 @@ protected:
CGPIOManager m_GPIOManager;
private:
CZoneAllocator m_Allocator;
CConfig m_Config;
CMT32Pi m_MT32Pi;
};
+86
View File
@@ -0,0 +1,86 @@
//
// zoneallocator.h
//
// mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi
// Copyright (C) 2020 Dale Whinham <daleyo@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef _zoneallocator_h
#define _zoneallocator_h
#include <circle/types.h>
// Block allocation tags
enum TZoneTag : u32
{
Free = 0,
Uncategorized = 1,
FluidSynth
};
class CZoneAllocator
{
public:
CZoneAllocator();
~CZoneAllocator();
// Allocator interface
bool Initialize();
void* Alloc(size_t nSize, TZoneTag Tag);
void* Realloc(void* pPtr, size_t nSize, TZoneTag Tag);
void Free(void* pPtr);
size_t GetAllocCount() const { return m_nAllocCount; }
void FreeTag(u32 nTag);
void Clear();
void Dump() const;
static CZoneAllocator* Get() { return s_pThis; }
private:
// Memory block header/linked list
struct TBlock
{
size_t nSize; // 32bit: 4 bytes | 64bit: 8 bytes
TBlock* pNext; // 8 bytes | 16 bytes
TBlock* pPrevious; // 12 bytes | 24 bytes
TZoneTag Tag; // 16 bytes | 28 bytes
u32 nMagic; // 20 bytes | 32 bytes
#if AARCH == 32
u8 Padding[12]; // 32 bytes |
#endif
};
// Constants
static constexpr u32 BlockMagic = 0xDA1EDEAD;
static constexpr size_t MinFragmentSize = 16;
inline u32& GetEndMagic(TBlock* pBlock) const
{
return *reinterpret_cast<u32*>(reinterpret_cast<u8*>(pBlock) + pBlock->nSize - sizeof(BlockMagic));
}
void* m_pHeap;
size_t m_nHeapSize;
TBlock m_MainBlock;
TBlock* m_pCurrentBlock;
size_t m_nAllocCount;
static CZoneAllocator* s_pThis;
};
#endif
+39 -7
View File
@@ -160,7 +160,7 @@ index 75248f42..9cd8390f 100644
static int
diff --git a/src/utils/fluid_sys.c b/src/utils/fluid_sys.c
index 3f2687a9..e6886dcb 100644
index 3f2687a9..36913627 100644
--- a/src/utils/fluid_sys.c
+++ b/src/utils/fluid_sys.c
@@ -50,6 +50,7 @@
@@ -179,7 +179,23 @@ index 3f2687a9..e6886dcb 100644
static fluid_log_function_t fluid_log_function[LAST_LOG_LEVEL] =
{
@@ -304,6 +306,7 @@ char *fluid_strtok(char **str, char *delim)
@@ -194,6 +196,7 @@ fluid_log(int level, const char *fmt, ...)
return FLUID_FAILED;
}
+#if 0
void* fluid_alloc(size_t len)
{
void* ptr = malloc(len);
@@ -227,6 +230,7 @@ void fluid_free(void* ptr)
{
free(ptr);
}
+#endif
/**
* An improved strtok, still trashes the input string, but is portable and
@@ -304,6 +308,7 @@ char *fluid_strtok(char **str, char *delim)
return token;
}
@@ -187,7 +203,7 @@ index 3f2687a9..e6886dcb 100644
/**
* Suspend the execution of the current thread for the specified amount of time.
* @param milliseconds to wait.
@@ -312,6 +315,7 @@ void fluid_msleep(unsigned int msecs)
@@ -312,6 +317,7 @@ void fluid_msleep(unsigned int msecs)
{
g_usleep(msecs * 1000);
}
@@ -195,7 +211,7 @@ index 3f2687a9..e6886dcb 100644
/**
* Get time in milliseconds to be used in relative timing operations.
@@ -332,6 +336,7 @@ unsigned int fluid_curtime(void)
@@ -332,6 +338,7 @@ unsigned int fluid_curtime(void)
return (unsigned int)((now - initial_time) / 1000.0f);
}
@@ -203,7 +219,7 @@ index 3f2687a9..e6886dcb 100644
/**
* Get time in microseconds to be used in relative timing operations.
* @return time in microseconds.
@@ -1670,3 +1675,4 @@ FILE* fluid_file_open(const char* path, const char** errMsg)
@@ -1670,3 +1677,4 @@ FILE* fluid_file_open(const char* path, const char** errMsg)
return handle;
}
@@ -369,7 +385,7 @@ index c1644035..925fa176 100644
FILE* fluid_file_open(const char* filename, const char** errMsg);
diff --git a/src/utils/fluidsynth_priv.h b/src/utils/fluidsynth_priv.h
index 0626bace..0398ed77 100644
index 0626bace..57402f27 100644
--- a/src/utils/fluidsynth_priv.h
+++ b/src/utils/fluidsynth_priv.h
@@ -29,7 +29,9 @@
@@ -382,7 +398,23 @@ index 0626bace..0398ed77 100644
#include "config.h"
@@ -281,8 +283,12 @@ do { strncpy(_dst,_src,_n); \
@@ -182,13 +184,14 @@ typedef void (*fluid_rvoice_function_t)(void *obj, const fluid_rvoice_param_t pa
/* Memory allocation */
#define FLUID_MALLOC(_n) fluid_alloc(_n)
-#define FLUID_REALLOC(_p,_n) realloc(_p,_n)
+#define FLUID_REALLOC(_p,_n) fluid_realloc(_p,_n)
#define FLUID_FREE(_p) fluid_free(_p)
#define FLUID_NEW(_t) (_t*)FLUID_MALLOC(sizeof(_t))
#define FLUID_ARRAY_ALIGNED(_t,_n,_a) (_t*)FLUID_MALLOC((_n)*sizeof(_t) + ((unsigned int)_a - 1u))
#define FLUID_ARRAY(_t,_n) FLUID_ARRAY_ALIGNED(_t,_n,1u)
void* fluid_alloc(size_t len);
+void* fluid_realloc(void* ptr, size_t len);
/* File access */
#define FLUID_FOPEN(_f,_m) fopen(_f,_m)
@@ -281,8 +284,12 @@ do { strncpy(_dst,_src,_n); \
#define FLUID_ASSERT(a)
#endif
+4
View File
@@ -104,6 +104,10 @@ bool CKernel::Initialize(void)
if (!m_GPIOManager.Initialize())
return false;
// Init custom memory allocator
if (!m_Allocator.Initialize())
return false;
if (!m_MT32Pi.Initialize(bSerialMIDIEnabled))
return false;
+16
View File
@@ -26,6 +26,7 @@
#include "synth/sc55sysex.h"
#include "synth/soundfontsynth.h"
#include "utility.h"
#include "zoneallocator.h"
const char SoundFontSynthName[] = "soundfontsynth";
const char SoundFontPath[] = "soundfonts";
@@ -33,6 +34,21 @@ const char SoundFontPath[] = "soundfonts";
extern "C"
{
// Replacements for fluid_sys.c functions
void* fluid_alloc(size_t len)
{
return CZoneAllocator::Get()->Alloc(len, TZoneTag::FluidSynth);
}
void* fluid_realloc(void* ptr, size_t len)
{
return CZoneAllocator::Get()->Realloc(ptr, len, TZoneTag::FluidSynth);
}
void fluid_free(void* ptr)
{
CZoneAllocator::Get()->Free(ptr);
}
FILE* fluid_file_open(const char* path, const char** errMsg)
{
FILE* pFile = fopen(path, "rb");
+436
View File
@@ -0,0 +1,436 @@
//
// zoneallocator.cpp
//
// mt32-pi - A bare-metal Roland MT-32 emulator for Raspberry Pi
// Copyright (C) 2020 Dale Whinham <daleyo@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include <circle/alloc.h>
#include <circle/logger.h>
#include <circle/memory.h>
#include "utility.h"
#include "zoneallocator.h"
const char ZoneAllocatorName[] = "zoneallocator";
// #define ZONE_ALLOCATOR_DEBUG
// #define ZONE_ALLOCATOR_TRACE
CZoneAllocator* CZoneAllocator::s_pThis = nullptr;
CZoneAllocator::CZoneAllocator()
: m_pHeap(nullptr),
m_nHeapSize(0),
m_pCurrentBlock(nullptr),
m_nAllocCount(0)
{
assert(s_pThis == nullptr);
s_pThis = this;
}
CZoneAllocator::~CZoneAllocator()
{
// Release the entire heap
free(m_pHeap);
}
bool CZoneAllocator::Initialize()
{
CLogger* pLogger = CLogger::Get();
#if RASPPI >= 4
// Allocate all of the remaining HIGH region
m_nHeapSize = CMemorySystem::Get()->GetHeapFreeSpace(HEAP_HIGH);
m_pHeap = CMemorySystem::Get()->HeapAllocate(m_nHeapSize, HEAP_HIGH);
#else
// Allocate the majority of the remaining LOW region
m_nHeapSize = CMemorySystem::Get()->GetHeapFreeSpace(HEAP_LOW) - 32 * MEGABYTE;
m_pHeap = CMemorySystem::Get()->HeapAllocate(m_nHeapSize, HEAP_LOW);
#endif
if (!m_pHeap)
{
if (m_nHeapSize >= MEGABYTE)
pLogger->Write(ZoneAllocatorName, LogError, "Couldn't allocate a %d megabyte heap", m_nHeapSize / MEGABYTE);
else
pLogger->Write(ZoneAllocatorName, LogError, "Couldn't allocate a %d byte heap", m_nHeapSize);
return false;
}
if (m_nHeapSize >= MEGABYTE)
pLogger->Write(ZoneAllocatorName, LogNotice, "Allocated a %d megabyte heap at %p", m_nHeapSize / MEGABYTE, m_pHeap);
else
pLogger->Write(ZoneAllocatorName, LogNotice, "Allocated a %d byte heap at %p", m_nHeapSize, m_pHeap);
#ifdef ZONE_ALLOCATOR_DEBUG
if ((reinterpret_cast<uintptr>(m_pHeap) & 15) == 0)
pLogger->Write(ZoneAllocatorName, LogDebug, "Heap is 16-byte aligned");
else
pLogger->Write(ZoneAllocatorName, LogDebug, "Heap is NOT 16-byte aligned");
pLogger->Write(ZoneAllocatorName, LogDebug, "Size of block header: %d", sizeof(TBlock));
#endif
// Initialize the heap with an empty block
Clear();
return true;
}
void* CZoneAllocator::Alloc(size_t nSize, TZoneTag Tag)
{
if (!nSize)
return nullptr;
if (Tag == TZoneTag::Free)
{
CLogger::Get()->Write(ZoneAllocatorName, LogError, "Zone allocation failed: tag value of 0 was used");
return nullptr;
}
// Account for size of block header and magic number at end of zone (for corruption detection), padded to 16 bytes
nSize = (nSize + sizeof(TBlock) + sizeof(BlockMagic) + 0xF) & ~0xF;
TBlock* pNextBlock = m_pCurrentBlock;
TBlock* pCandidateBlock = m_pCurrentBlock;
TBlock* pStartBlock = m_pCurrentBlock->pPrevious;
do
{
// We've been through the whole linked list and couldn't find a free block
if (pNextBlock == pStartBlock)
{
CLogger::Get()->Write(ZoneAllocatorName, LogError, "Zone allocation failed: couldn't allocate %d bytes", nSize);
return nullptr;
}
// This block is in use; look at the next one
if (pNextBlock->Tag != TZoneTag::Free)
pCandidateBlock = pNextBlock->pNext;
pNextBlock = pNextBlock->pNext;
} while (pCandidateBlock->Tag || pCandidateBlock->nSize < nSize);
// Create a new block for any remaining free space
const size_t nRemaining = pCandidateBlock->nSize - nSize;
if (nRemaining > MinFragmentSize)
{
TBlock* pNewBlock = reinterpret_cast<TBlock*>(reinterpret_cast<u8*>(pCandidateBlock) + nSize);
pNewBlock->nSize = nRemaining;
pNewBlock->pNext = pCandidateBlock->pNext;
pNewBlock->pPrevious = pCandidateBlock;
pNewBlock->Tag = TZoneTag::Free;
pNewBlock->nMagic = BlockMagic;
#if AARCH == 32
memset(pNewBlock->Padding, 0xEB, Utility::ArraySize(pNewBlock->Padding));
#endif
// Set the next block's previous to look at the new block
pNewBlock->pNext->pPrevious = pNewBlock;
pCandidateBlock->nSize = nSize;
pCandidateBlock->pNext = pNewBlock;
}
// Mark block used
pCandidateBlock->Tag = Tag;
pCandidateBlock->nMagic = BlockMagic;
// Mark end of memory with magic number
GetEndMagic(pCandidateBlock) = BlockMagic;
// Next allocation will start looking at this block
m_pCurrentBlock = pCandidateBlock->pNext;
#ifdef ZONE_ALLOCATOR_TRACE
CLogger::Get()->Write(ZoneAllocatorName, LogDebug, "Allocated %d bytes for tag %x", nSize, Tag);
#endif
// Increment alloc counter
++m_nAllocCount;
return pCandidateBlock + 1;
}
void* CZoneAllocator::Realloc(void* pPtr, size_t nSize, TZoneTag Tag)
{
// If passed a null pointer, perform a new allocation
if (!pPtr)
return Alloc(nSize, Tag);
if (!nSize)
return nullptr;
// Account for size of block header and magic number at end of zone (for corruption detection), padded to 16 bytes
const size_t nNewSize = (nSize + sizeof(TBlock) + sizeof(BlockMagic) + 0xF) & ~0xF;
TBlock* pBlock = reinterpret_cast<TBlock*>(pPtr) - 1;
if (Tag == TZoneTag::Free)
{
CLogger::Get()->Write(ZoneAllocatorName, LogError, "Zone reallocation failed: tag value of 0 was used");
return nullptr;
}
if (pBlock->Tag == TZoneTag::Free)
{
CLogger::Get()->Write(ZoneAllocatorName, LogError, "Attempted to reallocate a freed block");
return nullptr;
}
// Expand block
if (nNewSize > pBlock->nSize)
{
const size_t nSizeDiff = nNewSize - pBlock->nSize;
// Expand in-place if next block is free and large enough
if (pBlock->pNext->Tag == TZoneTag::Free && pBlock->pNext->nSize >= nSizeDiff)
{
TBlock* pNewBlock = reinterpret_cast<TBlock*>(reinterpret_cast<u8*>(pBlock) + nNewSize);
pNewBlock->nSize = pBlock->pNext->nSize - nSizeDiff;
pNewBlock->pNext = pBlock->pNext->pNext;
pNewBlock->pNext->pPrevious = pNewBlock;
pNewBlock->pPrevious = pBlock;
pNewBlock->Tag = TZoneTag::Free;
pNewBlock->nMagic = BlockMagic;
#if AARCH == 32
memset(pNewBlock->Padding, 0xEB, Utility::ArraySize(pNewBlock->Padding));
#endif
GetEndMagic(pNewBlock) = BlockMagic;
// Next allocations search from this new merged free block
if (pBlock->pNext == m_pCurrentBlock)
m_pCurrentBlock = pNewBlock;
pBlock->nSize = nNewSize;
pBlock->pNext = pNewBlock;
pBlock->Tag = Tag;
GetEndMagic(pBlock) = BlockMagic;
#ifdef ZONE_ALLOCATOR_TRACE
CLogger::Get()->Write(ZoneAllocatorName, LogDebug, "Expanded block at %p in-place", pPtr);
#endif
return pBlock + 1;
}
// Allocate a new block and move contents
else
{
const size_t nSrcSize = pBlock->nSize - sizeof(TBlock) - sizeof(BlockMagic);
void* pDest = Alloc(nSize, Tag);
if (!pDest)
{
CLogger::Get()->Write(ZoneAllocatorName, LogError, "Zone reallocation failed");
return nullptr;
}
memcpy(pDest, pPtr, nSrcSize);
Free(pPtr);
#ifdef ZONE_ALLOCATOR_TRACE
CLogger::Get()->Write(ZoneAllocatorName, LogDebug, "Expanded block at %p by allocating new block", pPtr);
#endif
return pDest;
}
}
// Shrink in-place
if (nNewSize < pBlock->nSize)
{
const size_t nRemain = pBlock->nSize - nNewSize;
if (nRemain > MinFragmentSize)
{
TBlock* pNewBlock = reinterpret_cast<TBlock*>(reinterpret_cast<u8*>(pBlock) + nNewSize);
if (pBlock->pNext->Tag == TZoneTag::Free)
{
// Merge free space with next block if it is also free
*pNewBlock = *pBlock->pNext;
pNewBlock->nSize += nRemain;
#ifdef ZONE_ALLOCATOR_TRACE
CLogger::Get()->Write(ZoneAllocatorName, LogDebug, "Shrunk block at %p in-place; adjacent free space expanded", pPtr);
#endif
}
else
{
// Create a new block for any remaining free space
pNewBlock->nSize = nRemain;
pNewBlock->pNext = pBlock->pNext;
pNewBlock->pPrevious = pBlock;
pNewBlock->Tag = TZoneTag::Free;
pNewBlock->nMagic = BlockMagic;
#if AARCH == 32
memset(pNewBlock->Padding, 0xEB, Utility::ArraySize(pNewBlock->Padding));
#endif
GetEndMagic(pNewBlock) = BlockMagic;
#ifdef ZONE_ALLOCATOR_TRACE
CLogger::Get()->Write(ZoneAllocatorName, LogDebug, "Shrunk block at %p in-place; new free block inserted after", pPtr);
#endif
}
// Next allocations search from this new merged free block
if (pBlock->pNext == m_pCurrentBlock)
m_pCurrentBlock = pNewBlock;
// Set the next block's previous to look at the new block
pNewBlock->pNext->pPrevious = pNewBlock;
pBlock->pNext = pNewBlock;
}
pBlock->nSize = nNewSize;
pBlock->Tag = Tag;
// Mark end of memory with magic number
GetEndMagic(pBlock) = BlockMagic;
return pBlock + 1;
}
// Size is the same, just update tag
pBlock->Tag = Tag;
return pPtr;
}
void CZoneAllocator::Free(void* pPtr)
{
if (!pPtr)
return;
TBlock* pBlock = reinterpret_cast<TBlock*>(pPtr) - 1;
if (pBlock->Tag == TZoneTag::Free)
{
CLogger::Get()->Write(ZoneAllocatorName, LogError, "Attempted to free an already-freed block");
return;
}
if (pBlock->nMagic != BlockMagic)
{
CLogger::Get()->Write(ZoneAllocatorName, LogError, "Attempted to free a block with a bad magic number (heap corruption?)");
return;
}
// Mark this block as free
pBlock->Tag = TZoneTag::Free;
// Join with previous block if previous block is also free
TBlock* pAdjacentBlock = pBlock->pPrevious;
if (pAdjacentBlock->Tag == TZoneTag::Free)
{
pAdjacentBlock->nSize += pBlock->nSize;
pAdjacentBlock->pNext = pBlock->pNext;
pAdjacentBlock->pNext->pPrevious = pAdjacentBlock;
// Next allocations search from this new merged free block
if (pBlock == m_pCurrentBlock)
m_pCurrentBlock = pAdjacentBlock;
#ifdef ZONE_ALLOCATOR_TRACE
CLogger::Get()->Write(ZoneAllocatorName, LogDebug, "Merged freed block at %p with previous block at %p", pPtr, pAdjacentBlock);
#endif
pBlock = pAdjacentBlock;
}
// Join with next block if next block is also free
pAdjacentBlock = pBlock->pNext;
if (pAdjacentBlock->Tag == TZoneTag::Free)
{
pBlock->nSize += pAdjacentBlock->nSize;
pBlock->pNext = pAdjacentBlock->pNext;
pBlock->pNext->pPrevious = pBlock;
if (pAdjacentBlock == m_pCurrentBlock)
m_pCurrentBlock = pBlock;
#ifdef ZONE_ALLOCATOR_TRACE
CLogger::Get()->Write(ZoneAllocatorName, LogDebug, "Merged freed block at %p with next block at %p", pPtr, pAdjacentBlock);
#endif
}
// Decrement allocation counter
--m_nAllocCount;
}
void CZoneAllocator::Clear()
{
TBlock* pFirstBlock = static_cast<TBlock*>(m_pHeap);
// The main block is a special block which acts as an end marker for the linked list of blocks
m_MainBlock.nSize = 0;
m_MainBlock.pNext = pFirstBlock;
m_MainBlock.pPrevious = pFirstBlock;
m_MainBlock.Tag = TZoneTag::Uncategorized;
m_MainBlock.nMagic = 0;
#if AARCH == 32
// 0xEB - "extra byte"; useful for memory view when debugging
memset(m_MainBlock.Padding, 0xEB, Utility::ArraySize(m_MainBlock.Padding));
#endif
pFirstBlock->nSize = m_nHeapSize;
pFirstBlock->pNext = &m_MainBlock;
pFirstBlock->pPrevious = &m_MainBlock;
pFirstBlock->Tag = TZoneTag::Free;
pFirstBlock->nMagic = BlockMagic;
#if AARCH == 32
memset(pFirstBlock->Padding, 0xEB, Utility::ArraySize(pFirstBlock->Padding));
#endif
m_pCurrentBlock = pFirstBlock;
}
void CZoneAllocator::FreeTag(u32 Tag)
{
if (Tag == TZoneTag::Free)
{
CLogger::Get()->Write(ZoneAllocatorName, LogError, "Attempted to free an invalid tag");
return;
}
TBlock* pBlock = m_MainBlock.pNext;
TBlock* pNextBlock;
do
{
// Grab the next block before freeing this one
pNextBlock = pBlock->pNext;
if (pBlock->Tag == Tag)
Free(reinterpret_cast<u8*>(pBlock) + sizeof(TBlock));
pBlock = pNextBlock;
} while (pBlock != &m_MainBlock);
}
void CZoneAllocator::Dump() const
{
CLogger* pLogger = CLogger::Get();
pLogger->Write(ZoneAllocatorName, LogNotice, "Allocation diagnostics:");
TBlock* pBlock = m_MainBlock.pNext;
do
{
pLogger->Write(ZoneAllocatorName, LogNotice, "Block address %p (%s):", pBlock, pBlock->Tag ? "IN-USE" : "FREE");
// If the block is free, it doesn't need a valid tail magic
const bool bMagicOK = (pBlock->nMagic == BlockMagic) && (!pBlock->Tag || GetEndMagic(pBlock) == BlockMagic);
if (!bMagicOK)
CLogger::Get()->Write(ZoneAllocatorName, LogWarning, "WARNING: This memory block is probably corrupt!");
CLogger::Get()->Write(ZoneAllocatorName, LogNotice, "\tSize: %d bytes", pBlock->nSize);
CLogger::Get()->Write(ZoneAllocatorName, LogNotice, "\tTag: 0x%x", pBlock->Tag);
CLogger::Get()->Write(ZoneAllocatorName, LogNotice, "\tMagic: %s", bMagicOK ? "OK" : "BAD");
pBlock = pBlock->pNext;
} while (pBlock != &m_MainBlock);
}