1
0
mirror of https://github.com/adtools/clib2.git synced 2025-12-08 14:59:05 +00:00
Files
amiga-clib2/library/amiga_pools.c
Olaf Barthel f9d1222bd7 Fix for wrong address alignment rounding
The original V39/V40 amiga.lib puddle creation code did not round up, it rounded off.
2023-10-11 09:15:28 +02:00

563 lines
16 KiB
C

/*
* :ts=4
*
* Portable ISO 'C' (1994) runtime library for the Amiga computer
* Copyright (c) 2002-2021 by Olaf Barthel <obarthel (at) gmx.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Neither the name of Olaf Barthel nor the names of contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <exec/memory.h>
#include <exec/alerts.h>
#include <exec/lists.h>
/****************************************************************************/
#include <string.h>
/****************************************************************************/
#include <proto/exec.h>
#include <clib/alib_protos.h>
/****************************************************************************/
#include "debug.h"
/****************************************************************************/
/* This signature identifies a large allocation on the list
* which contains both puddles and such large memory allocations.
*/
#define IS_LARGE_ALLOCATION 0
/****************************************************************************/
/* A private memory pool, as used by this implementation of the
* pool memory management functions.
*/
struct MemoryPool
{
struct MinList mp_PuddleMinList; /* Both puddles and large allocations
* are stored in this list. The puddles
* are stored near the head of the list
* and the large allocations are stored
* near the tail of the list.
*/
ULONG mp_MemoryFlags; /* Memory attributes for allocation,
* which may also include MEMF_CLEAR.
*/
ULONG mp_PuddleSize; /* Largest allocation which will fit
* into a single puddle.
*/
ULONG mp_PuddleSizeThreshold; /* Allocations which exceed this size
* will be allocated separately and not
* make use of the puddles.
*/
};
/****************************************************************************/
/* The management data structure associated with a separate large memory
* allocation. This is distinct from the puddles.
*/
struct LargeAllocation
{
struct MinNode la_MinNode;
ULONG la_Signature; /* Must be set to IS_LARGE_ALLOCATION */
};
/****************************************************************************/
/* This combines a plain Node, a MemHeader and a LargeAllocation. They
* all overlap to some degree. The "puddle" is really a MemHeader from
* which the individual memory allocations are made.
*
* For reference, here is how the individual data structures look like.
* The 'struct MinNode' is used by the 'struct LargeAllocation' and you
* can see that the LargeAllocation.la_Signature overlaps the
* Node.ln_Type and Node.ln_Pri fields. The 'struct MemHeader' begins
* with a 'struct Node' and is initialized so that the Node.ln_Type and
* Node.ln_Pri are never zero. This is why LargeAllocation.la_Signature
* being zero is key to identifying large allocations which along with
* the MemHeaders share the same storage list.
*
* struct MinNode {
* struct MinNode * mln_Succ;
* struct MinNode * mln_Pred;
* };
*
* struct LargeAllocation {
* struct MinNode la_MinNode;
* ULONG la_Signature;
* };
*
* struct Node {
* struct Node * ln_Succ;
* struct Node * ln_Pred;
* UBYTE ln_Type;
* BYTE ln_Pri;
* char * ln_Name;
* };
*
* struct MemHeader {
* struct Node mh_Node;
* UWORD mh_Attributes;
* struct MemChunk * mh_First;
* APTR mh_Lower;
* APTR mh_Upper;
* ULONG mh_Free;
* };
*/
union PuddleUnion
{
struct Node pu_Node;
struct MemHeader pu_MemHeader;
struct LargeAllocation pu_LargeAllocation;
};
/****************************************************************************/
/* Our local Kickstart 1.x compatible implementation of FreeVec(). */
static VOID free_vec(APTR allocation)
{
if (allocation != NULL)
{
ULONG * mem = allocation;
FreeMem(&mem[-1], mem[-1]);
}
}
/****************************************************************************/
/* Our local Kickstart 1.x compatible implementation of AllocVec(). */
static APTR alloc_vec(ULONG size, ULONG flags)
{
ULONG * mem = NULL;
if (size > 0)
{
/* Note: no overflow testing is being performed! */
size += sizeof(*mem);
mem = AllocMem(size, flags);
if (mem != NULL)
(*mem++) = size;
}
return mem;
}
/****************************************************************************/
VOID LibDeletePool(APTR pool)
{
/* If possible, use the operating system implementation
* instead of this reimplementation.
*/
#if ! defined(__amigaos4__)
if (SysBase->LibNode.lib_Version >= 39)
{
DeletePool(pool);
return;
}
#endif /* ! defined(__amigaos4__) */
if (pool != NULL)
{
struct MemoryPool * mp = pool;
struct MinNode * mln_next;
struct MinNode * mln;
for (mln = mp->mp_PuddleMinList.mlh_Head ;
mln->mln_Succ != NULL ;
mln = mln_next)
{
mln_next = mln->mln_Succ;
free_vec(mln);
}
FreeMem(mp, sizeof(*mp));
}
}
/****************************************************************************/
/* Note: puddle size should not be 0 or every single memory allocation
* will result in an AllocVec() call.
*/
APTR LibCreatePool(
ULONG memory_flags,
ULONG puddle_size,
ULONG threshold_size)
{
struct MemoryPool * mp = NULL;
APTR result = NULL;
/* If possible, use the operating system implementation
* instead of this reimplementation.
*/
#if ! defined(__amigaos4__)
if (SysBase->LibNode.lib_Version >= 39)
{
return CreatePool(memory_flags, puddle_size, threshold_size);
}
#endif /* ! defined(__amigaos4__) */
/* The threshold size must be less than or equal
* to the puddle size.
*/
if (threshold_size > puddle_size)
goto out;
/* Round up the puddle size to the size of a
* memory block, as managed by Allocate().
*
* Note: no overflow checking is performed!
*/
puddle_size = (puddle_size + MEM_BLOCKMASK) & ~MEM_BLOCKMASK;
mp = AllocMem(sizeof(*mp), MEMF_ANY);
if (mp == NULL)
goto out;
NewList((struct List *)&mp->mp_PuddleMinList);
mp->mp_MemoryFlags = memory_flags;
mp->mp_PuddleSize = puddle_size;
mp->mp_PuddleSizeThreshold = threshold_size;
result = mp;
mp = NULL;
out:
if (mp != NULL)
LibDeletePool(mp);
return result;
}
/****************************************************************************/
/* Creates a new puddle from which memory allocations up to a certain size
* will be made. Returns the new puddle or NULL for failure.
*
* Note: No overflow testing is performed when allocating the puddle.
*/
static union PuddleUnion * create_new_puddle(struct MemoryPool * mp)
{
ULONG memory_flags = mp->mp_MemoryFlags;
ULONG puddle_size = mp->mp_PuddleSize;
union PuddleUnion * result = NULL;
union PuddleUnion * pu;
struct MemHeader * mh;
struct MemChunk * mc;
/* The extra sizeof(APTR) is needed for aligning the
* allocatable memory chunks within the memory header.
*
* Note: no overflow checking is performed!
*
* Also note that the original memory allocation flags are
* being used, which may include MEMF_CLEAR. This means that
* if MEMF_CLEAR is set, the initial allocation will be
* cleared and then the individual puddle allocations made
* through LibAllocPooled() will be cleared, too.
*/
pu = alloc_vec(sizeof(pu->pu_MemHeader) + sizeof(LONG) + puddle_size, memory_flags);
if (pu == NULL)
goto out;
mh = &pu->pu_MemHeader;
/* The first allocatable memory chunk follows the memory header
* and must be aligned to a 64 bit address. This happens to be
* the smallest allocatable memory unit (MEM_BLOCKSIZE == 8).
*/
mc = (struct MemChunk *)(((ULONG)&mh[1] + sizeof(LONG)) & ~MEM_BLOCKMASK);
mc->mc_Next = NULL;
mc->mc_Bytes = puddle_size;
/* Both ln_Type and ln_Pri must be non-zero! This allows them to
* be identified as small puddles as compared to the large puddles
* which have the la_Signature member set to IS_LARGE_ALLOCATION.
* The 'struct Node' which introduces the 'struct MemHeader'
* overlaps the 'struct LargeAllocation' in the Node.ln_Type/ln_Pri
* fields. This is why the test for la_Signature == IS_LARGE_ALLOCATION
* works.
*/
mh->mh_Node.ln_Type = NT_MEMORY;
mh->mh_Node.ln_Pri = mh->mh_Node.ln_Type;
mh->mh_Node.ln_Name = (char *)&"\0Pool"[1]; /* The memory name must be an odd address. */
mh->mh_Attributes = memory_flags;
mh->mh_First = mc;
mh->mh_Lower = mc;
mh->mh_Upper = &((BYTE *)mc)[puddle_size];
mh->mh_Free = puddle_size;
/* Puddles are always added at the head of the list. */
AddHead((struct List *)&mp->mp_PuddleMinList, &pu->pu_Node);
result = pu;
out:
return result;
}
/****************************************************************************/
/* Note: No overflow testing is performed when making a large allocation. */
APTR LibAllocPooled(APTR pool, ULONG mem_size)
{
struct MemoryPool * mp;
union PuddleUnion * pu;
APTR result = NULL;
/* If possible, use the operating system implementation
* instead of this reimplementation.
*/
#if ! defined(__amigaos4__)
if (SysBase->LibNode.lib_Version >= 39)
{
return AllocPooled(pool, mem_size);
}
#endif /* ! defined(__amigaos4__) */
/* No pool or no memory to allocate? */
if (pool == NULL || mem_size == 0)
goto out;
mp = pool;
/* Requested allocation size is still within the threshold limit? */
if (mem_size <= mp->mp_PuddleSizeThreshold)
{
/* Search for the first puddle from which memory may be
* allocated. If no such puddle exists, it will have
* to be created. Instead of puddles we might end up
* finding a large memory allocation instead. This is
* also an indication that a new puddle will have to
* be created.
*/
pu = (union PuddleUnion *)mp->mp_PuddleMinList.mlh_Head;
while (TRUE)
{
/* We just reached the end of the puddle list or this
* is a large allocation? We can't use an existing puddle to
* allocate memory, so we have to make a new one.
*/
if (pu->pu_Node.ln_Succ == NULL || pu->pu_LargeAllocation.la_Signature == IS_LARGE_ALLOCATION)
{
/* We need to create another puddle, which is added to
* the head of the list. Then the search for allocatable
* memory in the puddle list will resume there.
*/
pu = create_new_puddle(mp);
if (pu == NULL)
goto out;
}
/* Try to allocate memory from this puddle.*/
result = Allocate(&pu->pu_MemHeader, mem_size);
if (result != NULL)
{
/* If the allocation needs to be zeroed, clear
* the entire allocated memory which Allocate()
* returned. It's been rounded up to be a
* multiple of MEM_BLOCKSIZE.
*
* Note that the puddle will have been allocated
* with MEMF_CLEAR already, which does not render
* the following memset() call redundant, but
* making the initial puddle allocation use
* MEMF_CLEAR is definitely redundant.
*/
if ((mp->mp_MemoryFlags & MEMF_CLEAR) != 0)
memset(result, 0, (mem_size + MEM_BLOCKMASK) & ~MEM_BLOCKMASK);
/* And we're good. */
break;
}
/* Try the next puddle on the list. */
pu = (union PuddleUnion *)pu->pu_Node.ln_Succ;
}
}
/* No, we need to allocate this memory chunk separately. */
else
{
struct LargeAllocation * la;
/* Note: no overflow checking is performed! */
pu = alloc_vec(sizeof(pu->pu_LargeAllocation) + mem_size, mp->mp_MemoryFlags);
if (pu == NULL)
goto out;
/* This identifies it as a separate large allocation. */
pu->pu_LargeAllocation.la_Signature = IS_LARGE_ALLOCATION;
/* The separate large allocations are stored near the end
* of the list. The puddles are always stored at the beginning
* of the list.
*/
AddTail((struct List *)&mp->mp_PuddleMinList, &pu->pu_Node);
la = &pu->pu_LargeAllocation;
result = &la[1];
}
out:
return result;
}
/****************************************************************************/
/* Note: The size of the allocation to free must match
* exactly or memory will remain unfreed. Furthermore,
* the wrong allocation size may lead to general
* memory corruption.
*/
VOID LibFreePooled(APTR pool, APTR memory, ULONG size)
{
/* If possible, use the operating system implementation
* instead of this reimplementation.
*/
#if ! defined(__amigaos4__)
if (SysBase->LibNode.lib_Version >= 39)
{
FreePooled(pool, memory, size);
return;
}
#endif /* ! defined(__amigaos4__) */
if (pool != NULL && memory != NULL)
{
struct MemoryPool * mp = pool;
/* This allocation was made from a puddle? */
if (size <= mp->mp_PuddleSizeThreshold)
{
union PuddleUnion * pu_deallocation = NULL;
union PuddleUnion * pu;
/* Try to find the puddle which contains this
* allocation.
*/
for (pu = (union PuddleUnion *)mp->mp_PuddleMinList.mlh_Head ;
pu->pu_Node.ln_Succ != NULL ;
pu = (union PuddleUnion *)pu->pu_Node.ln_Succ)
{
/* Did we reach the end of the puddle list? Then
* the given memory address and/or size may be
* invalid...
*/
if (pu->pu_LargeAllocation.la_Signature == IS_LARGE_ALLOCATION)
break;
/* Is this the memory header which contains the
* memory allocation?
*/
if (pu->pu_MemHeader.mh_Lower <= memory && memory < pu->pu_MemHeader.mh_Upper)
{
/* Free the allocation and remember where it
* came from. We will make use of this below to
* free now unused puddles.
*/
Deallocate(&pu->pu_MemHeader, memory, size);
pu_deallocation = pu;
break;
}
}
/* Did we succeed in releasing this memory allocation? */
if (pu_deallocation != NULL)
{
APTR upper;
/* The more frequently memory is freed from a
* specific puddle, the easier it should become
* to find it on the list again. We try to move it
* closer to the beginning of the list unless it
* is already at the head of the list.
*/
if (mp->mp_PuddleMinList.mlh_Head != (struct MinNode *)pu)
{
/* This puddle precedes the one from which we
* just released an allocation.
*/
struct Node * pred = pu->pu_Node.ln_Pred;
struct Node * before;
/* Put this puddle at the head of the list? */
if (pred == (struct Node *)mp->mp_PuddleMinList.mlh_Head)
before = NULL;
/* No, insert it in front of its predecessor. */
else
before = pred->ln_Pred;
Remove(&pu->pu_Node);
Insert((struct List *)&mp->mp_PuddleMinList, &pu->pu_Node, before);
}
/* Has this puddle been emptied? */
upper = &((BYTE *)pu->pu_MemHeader.mh_Lower)[pu->pu_MemHeader.mh_Free];
if (upper == pu->pu_MemHeader.mh_Upper)
{
/* Then we may remove and free it now. */
Remove(&pu->pu_Node);
free_vec(pu);
}
}
/* Something's wrong :-( */
else
{
Alert(AN_BadFreeAddr);
}
}
/* No, this is a large allocation which must be freed as it is. */
else
{
struct LargeAllocation * la = &((struct LargeAllocation *)memory)[-1];
Remove((struct Node *)&la->la_MinNode);
free_vec(la);
}
}
}