mirror of
https://github.com/dwhinham/mt32-pi.git
synced 2026-10-26 22:11:05 +00:00
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:
1 parent
f060199032
commit
5d39db9682
10 files changed
+588
-15
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user