Files
mazegame/engine/src/memory.c
T

347 lines
9.9 KiB
C

// ____________________________
// ██▀▀█▀▀██▀▀▀▀▀▀▀█▀▀█ │ ▄ ▄
// ██ ▀ █▄ ▀██▄ ▀ ▄█ ▄▀▀ █ │ ██▀█ ▄███ ▄█▄█ ▄█▀▄ ██▄▀ █ ██
// █ █ █ ▀▀ ▄█ █ █ ▀▄█ █▄ │ ██ █ ▀█▄▄ ██ █ ▀█▄▀ ██ ▀██
// ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀────────┘ ▀▀
// by Guillaume 'Aoineko' Blanchard under CC BY-SA license
//─────────────────────────────────────────────────────────────────────────────
// Module to handle memory allocation and copy/fill operations
//─────────────────────────────────────────────────────────────────────────────
//=============================================================================
// INCLUDES
//=============================================================================
#include "memory.h"
//=============================================================================
// VARIABLES
//=============================================================================
u16 g_StackAddress;
//=============================================================================
// FUNCTIONS
//=============================================================================
//-----------------------------------------------------------------------------
// Get the current address of the stack top (lower address).
u16 Mem_GetStackAddress() __NAKED
{
__asm
ld (_g_StackAddress), sp
ld de, (_g_StackAddress)
ret
__endasm;
}
//-----------------------------------------------------------------------------
// Allocate a part of the heap
void* Mem_HeapAlloc(u16 size)
{
u16 addr = g_HeapStartAddress;
g_HeapStartAddress += size;
return (void*)addr;
}
//-----------------------------------------------------------------------------
// Free the last allocated area of the heap
void Mem_HeapFree(u16 size)
{
g_HeapStartAddress -= size;
}
#if (!MEM_USE_BUILTIN)
//-----------------------------------------------------------------------------
// Copy a memory block from a source address to an other
void Mem_Copy(const void* src, void* dest, u16 size) __NAKED // Stack: 4 bytes
{
src; // HL
dest; // DE
size; // SP[2]
__asm
// Get parameters
pop iy // 16 cc (return address)
pop bc // 11 cc (retrieve size) | stack adjusted
#if (MEM_USE_VALIDATOR)
// Skip if size == 0
ld a, b
or a, c
jp z, mem_copy_end
#endif
// Do copy
ldir // 23/18 cc
mem_copy_end:
jp (iy) // 10 cc
__endasm;
}
#endif // (!MEM_USE_BUILTIN)
#if (MEM_USE_FASTCOPY)
//-----------------------------------------------------------------------------
// Fast copy a memory block from a source address to a destination
// This function use a unrolled LDI loop (18,75% faster ; break-even at 6 loops for multiple of 16 size or at 31 loops otherwise)
void Mem_FastCopy(const void* src, void* dest, u16 size) __NAKED // Stack: 4 bytes
{
src; // HL
dest; // DE
size; // SP[2]
__asm
// Get parameters
pop iy // 16 cc (return address)
pop bc // 11 cc (retrieve size) | stack adjusted
#if (MEM_USE_VALIDATOR)
// Skip if size == 0
ld a, b
or a, c
jp z, mem_fastcopy_end
#endif
mem_fastcopy_setup:
// Setup fast LDIR loop
xor a // 5 cc
sub c // 5 cc
and #15 // 8 cc
jp z, mem_fastcopy_loop // 11 cc - total 29 cc (break-even at 16 loops)
add a // 5 cc
exx // 5 cc
add a, #mem_fastcopy_loop // 8 cc
ld l, a // 5 cc
ld a, #0 // 8 cc
adc a, #mem_fastcopy_loop >> 8 // 8 cc
ld h, a // 5 cc
push hl // 12 cc
exx // 5 cc
ret // 11 cc - total 101 cc (break-even at 25 loops)
mem_fastcopy_loop:
// Fast LDIR (with 16x unrolled LDI)
.rept 16
ldi // 18 cc
.endm
jp pe, mem_fastcopy_loop // 11 cc (0,6875 cc per ldi)
mem_fastcopy_end:
jp (iy) // 10 cc
__endasm;
}
#endif // (MEM_USE_FASTCOPY)
#if (!MEM_USE_BUILTIN)
//-----------------------------------------------------------------------------
// Fill a memory block with a given value (minimal size: 2)
void Mem_Set(u8 val, void* dest, u16 size) __NAKED // Stack: 4 bytes
{
val; // A
dest; // DE
size; // SP[2]
__asm
push de
pop hl
ld (hl), a
inc de
// Get parameters
pop iy // 16 cc (return address)
pop bc // 11 cc (retrieve size) | stack adjusted
#if (MEM_USE_VALIDATOR)
// Skip if size < 2
ld a, b
or a, c
jp z, mem_fill_end
dec bc
jp z, mem_fill_end
#else
dec bc
#endif
// Do fill
ldir // 23/18 cc
mem_fill_end:
jp (iy) // 10 cc
__endasm;
}
#endif // (!MEM_USE_BUILTIN)
//-----------------------------------------------------------------------------
// Fill a memory block with a given 16-bits value (minimal size of 2 bytes).
void Mem_Set_16b(u16 val, void* dest, u16 size)
{
val; // HL
dest; // DE
size; // SP[2]
__asm
push de
ex de, hl
ld (hl), d
inc hl
ld (hl), e
inc hl
ex de, hl
pop hl
// Get parameters
pop iy // 16 cc (return address)
pop bc // 11 cc (retrieve size) | stack adjusted
#if 0//(MEM_USE_VALIDATOR)
// Skip if size < 2
ld a, b
or a, c
jp z, mem_fill16_end
dec bc
jp z, mem_fill16_end
dec bc
jp z, mem_fill16_end
#else
dec bc
dec bc
#endif
// Do fill
ldir // 23/18 cc
mem_fill16_end:
jp (iy) // 10 cc
__endasm;
}
#if (MEM_USE_FASTSET)
//-----------------------------------------------------------------------------
// Fast fill a memory block with a given value (minimal size of 2 bytes).
void Mem_FastSet(u8 val, void* dest, u16 size) __NAKED // Stack: 4 bytes
{
val; // A
dest; // DE
size; // SP[2]
__asm
push de
pop hl
ld (hl), a
inc de
// Get parameters
pop iy // 16 cc (return address)
pop bc // 11 cc (retrieve size) | stack adjusted
#if (MEM_USE_VALIDATOR)
// Skip if size < 2
ld a, b
or a, c
jp z, mem_fill_end
dec bc
jp z, mem_fill_end
#else
dec bc
#endif
// Note: could jump from here to 'mem_fastcopy_setup' label and removing the following code
mem_fastfill_setup:
// Setup fast LDIR loop
xor a // 5 cc
sub c // 5 cc
and #15 // 8 cc
jp z, mem_fastfill_loop // 11 cc - total 29 cc (break-even at 16 loops)
add a // 5 cc
exx // 5 cc
add a, #mem_fastfill_loop // 8 cc
ld l, a // 5 cc
ld a, #0 // 8 cc
adc a, #mem_fastfill_loop >> 8 // 8 cc
ld h, a // 5 cc
push hl // 12 cc
exx // 5 cc
ret // 11 cc - total 101 cc (break-even at 25 loops)
mem_fastfill_loop:
// Fast LDIR (with 16x unrolled LDI)
.rept 16
ldi // 18 cc
.endm
jp pe, mem_fastfill_loop // 11 cc (0,6875 cc per ldi)
mem_fastfill_end:
jp (iy) // 10 cc
__endasm;
}
#endif // MEM_USE_FASTSET
#if (MEM_USE_DYNAMIC)
//=============================================================================
// DYNAMIC MEMORY ALLOCATOR
//=============================================================================
// Dynamic memory chunk flag
#define MEM_CHUNK_FREE 0x8000
// Dynamic memory chunk root
MemChunkHeader* g_MemChunkRoot = NULL;
//-----------------------------------------------------------------------------
// Allocates a static memory block which can then be used to allocate chunks dynamically.
void Mem_DynamicInitialize(void* base, u16 size)
{
g_MemChunkRoot = (MemChunkHeader*)base;
g_MemChunkRoot->Size = (size - sizeof(MemChunkHeader)) | MEM_CHUNK_FREE;
g_MemChunkRoot->Next = NULL;
}
//-----------------------------------------------------------------------------
// Allocate a memory chunk from the dynamic memory buffer
void* Mem_DynamicAlloc(u16 size)
{
#if (MEM_USE_VALIDATOR)
if (size == 0)
return NULL;
#endif
MemChunkHeader* chunk = g_MemChunkRoot;
while (chunk)
{
u16 chunkSize = chunk->Size;
if (chunkSize & MEM_CHUNK_FREE) // Free chunk
{
chunkSize &= ~MEM_CHUNK_FREE;
if (chunkSize == size) // Re-use chunk
{
chunk->Size &= ~MEM_CHUNK_FREE;
return (void*)((u16)chunk + sizeof(MemChunkHeader));
}
u16 needSize = size + sizeof(MemChunkHeader);
if (chunkSize > needSize) // Create new sub-chunk
{
MemChunkHeader* newChunk = (MemChunkHeader*)((u16)chunk + needSize); // New free sub-chunk
newChunk->Size = (chunkSize - needSize) | MEM_CHUNK_FREE;
newChunk->Next = chunk->Next;
chunk->Size = size; // Allocated sub-chunk
chunk->Next = newChunk;
return (void*)((u16)chunk + sizeof(MemChunkHeader));
}
}
chunk = chunk->Next;
}
return NULL;
}
//-----------------------------------------------------------------------------
// Merge contiguous empty memory blocks
void Mem_DynamicMerge()
{
MemChunkHeader* chunk = g_MemChunkRoot;
while (chunk)
{
MemChunkHeader* nextChunk = chunk->Next;
if ((nextChunk != NULL) && (chunk->Size & MEM_CHUNK_FREE) && (nextChunk->Size & MEM_CHUNK_FREE))
{
chunk->Size += nextChunk->Size + sizeof(MemChunkHeader);
chunk->Next = nextChunk->Next;
}
chunk = chunk->Next;
}
}
//-----------------------------------------------------------------------------
// Free a memory chunk from the dynamic memory buffer
void Mem_DynamicFree(void* ptr)
{
MemChunkHeader* chunk = (MemChunkHeader*)((u16)ptr - sizeof(MemChunkHeader));
chunk->Size |= MEM_CHUNK_FREE;
Mem_DynamicMerge();
}
#endif // (MEM_USE_DYNAMIC)