CGDoom/cgdoom/z_zone.c

535 lines
11 KiB
C

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// DESCRIPTION:
// Zone Memory Allocation. Neat.
//
//-----------------------------------------------------------------------------
#include "z_zone.h"
#include "i_system.h"
#include "doomdef.h"
#include "cgdoom.h"
//
// ZONE MEMORY ALLOCATION
//
// There is never any space between memblocks,
// and there will never be two contiguous free memblocks.
// The rover can be left pointing at a non-empty block.
//
// It is of no value to free a cachable block,
// because it will get overwritten automatically if needed.
//
#define ZONEID 0x1d4a11
typedef struct
{
// total bytes malloced, including header
int size;
// start / end cap for linked list
memblock_t blocklist;
memblock_t* rover;
} memzone_t;
/* CGDoom: In order to increase available memory, we need to support multiple
zones. The code below is adapted to do that:
* Z_ClearZone is already parameterized, nothing to do (it's never called)
* Z_Init is mostly replaced by a new function Z_AddZone()
* Z_Free is changed to detect which zone contains the pointer
* Z_Malloc is changed to try all zones
* Z_FreeTags is changed to iterate on all zones
* Z_CheckHeap is changed to iterate on all zones
* Z_ChangeTag2 operates on a single block and needs no change
* Z_FreeMemory is changed to iterate on one or all zones
In addition to that, the next-fit strategy with a rover also runs over zones
since starting at the first zone when it's full loses *a lot* of time.
However, the next-fit strategy tends to produce *a lot* of fragmentation,
resulting in early allocation failures due to the lack of large spaces. For
this reason, the last region is not part of the zone rover rotation. */
static memzone_t *zones[ZONE_MAX]; /* static memzone_t *mainzone; */
static int zone_count;
static int zone_rover = 0;
//
// Z_ClearZone
//
void Z_ClearZone (memzone_t* zone)
{
memblock_t* block;
// set the entire zone to one free block
zone->blocklist.next = zone->blocklist.prev = block = (memblock_t *)((byte *)zone + sizeof(memzone_t));
zone->blocklist.user = (void **)zone;
zone->blocklist.tag = PU_STATIC;
zone->rover = block;
block->prev = block->next = &zone->blocklist;
// NULL indicates a free block.
block->user = NULL;
block->size = zone->size - sizeof(memzone_t);
}
//
// Z_Init
//
void Z_Init(void)
{
/* All work is left to Z_AddZone() */
zone_count = 0;
}
void Z_AddZone (void *buf, int size)
{
memblock_t* block;
if (zone_count >= ZONE_MAX)
return;
memzone_t* zone = (memzone_t *)buf;
zones[zone_count++] = zone;
zone->size = size;
// set the entire zone to one free block
zone->blocklist.next = zone->blocklist.prev = block = (memblock_t *)( (byte *)zone + sizeof(memzone_t) );
zone->blocklist.user = (void **)zone;
zone->blocklist.tag = PU_STATIC;
zone->rover = block;
block->prev = block->next = &zone->blocklist;
// NULL indicates a free block.
block->user = NULL;
block->size = zone->size - sizeof(memzone_t);
}
//
// Z_Free
//
void Z_Free (const void* ptr)
{
memblock_t* block;
memblock_t* other;
if (PTR_TO_FLASH(ptr) || ptr == NULL)
return;
memzone_t* zone = NULL;
for (int i = 0; i < zone_count; i++)
{
if (ptr >= (void *)zones[i] && ptr < (void *)zones[i] + zones[i]->size)
{
zone = zones[i];
break;
}
}
if (zone == NULL)
{
I_Error ("Z_Free: %p is out of zone", ptr);
return;
}
prof_enter(CGD_Perf.DynamicAllocation);
block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
if (block->id != ZONEID)
{
I_Error ("Z_Free: freed a pointer without ZONEID: %p", ptr);
}
if (block->user > (void **)0x100)
{
// smaller values are not pointers
// Note: OS-dependend?
// clear the user's mark
*block->user = 0;
}
// mark as free
block->user = NULL;
block->tag = 0;
block->id = 0;
other = block->prev;
if (!other->user)
{
// merge with previous free block
other->size += block->size;
other->next = block->next;
other->next->prev = other;
if (block == zone->rover)
{
zone->rover = other;
}
block = other;
}
other = block->next;
if (!other->user)
{
// merge the next free block onto the end
block->size += other->size;
block->next = other->next;
block->next->prev = block;
if (other == zone->rover)
{
zone->rover = block;
}
}
prof_leave(CGD_Perf.DynamicAllocation);
}
//
// Z_Malloc
// You can pass a NULL user if the tag is < PU_PURGELEVEL.
//
#define MINFRAGMENT 64
void* Z_Malloc( int size, int tag, void* user )
{
prof_enter(CGD_Perf.DynamicAllocation);
CGD_Stats.MemoryAllocated += size;
int extra;
memblock_t* start;
memblock_t* rover;
memblock_t* newblock;
memblock_t* base;
size = (size + (sizeof(void *)-1)) & ~(sizeof(void *)-1);
// scan through the block list,
// looking for the first free block
// of sufficient size,
// throwing out any purgable blocks along the way.
// account for size of block header
size += sizeof(memblock_t);
int zone_start = zone_rover;
int zone_no = zone_rover;
memzone_t *zone = zones[zone_no];
for (;;)
{
// if there is a free block behind the rover,
// back up over them
base = zone->rover;
if (!base->prev->user)
{
base = base->prev;
}
rover = base;
start = base->prev;
int failed = 0;
do
{
if (rover == start)
{
// scanned all the way around the list
failed = 1;
break;
}
if (rover->user)
{
if (rover->tag < PU_PURGELEVEL)
{
// hit a block that can't be purged,
// so move base past it
base = rover = rover->next;
}
else
{
// free the rover block (adding the size to base)
// the rover can be the base block
base = base->prev;
Z_Free ((byte *)rover+sizeof(memblock_t));
base = base->next;
rover = base->next;
}
}
else
rover = rover->next;
} while (base->user || base->size < size);
if (failed)
{
if (zone_no == zone_count - 1)
{
I_Error ("Z_Malloc(%d) failure", size);
prof_leave(CGD_Perf.DynamicAllocation);
return NULL;
}
/* When rotating, perform a full cycle before defaulting to the last
zone; this preserves the last zone to avoid fragmentation and absorb
occasional large chunks */
zone_no++;
if(zone_no == zone_count - 1)
zone_no = 0;
if(zone_no == zone_start)
zone_no = zone_count - 1;
zone = zones[zone_no];
}
else break;
}
// found a block big enough
extra = base->size - size;
if (extra > MINFRAGMENT)
{
// there will be a free fragment after the allocated block
newblock = (memblock_t *) ((byte *)base + size );
newblock->size = extra;
// NULL indicates free block.
newblock->user = NULL;
newblock->tag = 0;
newblock->prev = base;
newblock->next = base->next;
newblock->next->prev = newblock;
base->next = newblock;
base->size = size;
}
if (user)
{
// mark as an in use block
base->user = (void **)user;
*(void **)user = (void *) ((byte *)base + sizeof(memblock_t));
}
else
{
if (tag >= PU_PURGELEVEL)
{
I_Error ("Z_Malloc: an owner is required for purgable blocks");
}
// mark as in use, but unowned
base->user = (void **)2;
}
base->tag = tag;
// next allocation will start looking here
zone->rover = base->next;
if (zone_no != zone_count - 1)
zone_rover = zone_no;
base->id = ZONEID;
prof_leave(CGD_Perf.DynamicAllocation);
return (void *) ((byte *)base + sizeof(memblock_t));
}
//
// Z_FreeTags
//
void Z_FreeTags(int lowtag,int hightag )
{
memblock_t* block;
memblock_t* next;
for (int i = 0; i < zone_count; i++)
{
memzone_t* mainzone = zones[i];
for(block = mainzone->blocklist.next;block != &mainzone->blocklist;block = next)
{
// get link before freeing
next = block->next;
// free block?
if (!block->user)
{
continue;
}
if (block->tag >= lowtag && block->tag <= hightag)
{
Z_Free ( (byte *)block+sizeof(memblock_t));
}
}
}
}
//
// Z_CheckHeap
//
void Z_CheckHeap (void)
{
memblock_t* block;
for (int i = 0; i < zone_count; i++)
{
memzone_t* mainzone = zones[i];
for (block = mainzone->blocklist.next ; ; block = block->next)
{
if (block->next == &mainzone->blocklist)
{
// all blocks have been hit
break;
}
if((byte *)block + block->size != (byte *)block->next)
{
I_Error("Z_CheckHeap: block size does not touch the next block\n");
}
if(block->next->prev != block)
{
I_Error("Z_CheckHeap: next block doesn't have proper back link\n");
}
if(!block->user && !block->next->user)
{
I_Error("Z_CheckHeap: two consecutive free blocks\n");
}
}
}
}
//
// Z_ChangeTag
//
void Z_ChangeTag2(const void* ptr,int tag )
{
memblock_t* block;
if(PTR_TO_FLASH(ptr) || ptr == NULL)
return;
block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t));
if (block->id != ZONEID)
{
I_Error ("Z_ChangeTag: freed a pointer without ZONEID: %p", ptr);
}
if (tag >= PU_PURGELEVEL && (uintptr_t)block->user < 0x100)
{
I_Error ("Z_ChangeTag: an owner is required for purgable blocks");
}
block->tag = tag;
}
//
// Z_FreeMemory
//
int Z_FreeMemory (int zone)
{
if (zone < 0)
{
int free = 0;
for (int i = 0; i < zone_count; i++)
free += Z_FreeMemory (i);
return free;
}
memblock_t* block;
int free = 0;
memzone_t* mainzone = zones[zone];
for(block = mainzone->blocklist.next;block != &mainzone->blocklist;block = block->next)
{
if (!block->user || block->tag >= PU_PURGELEVEL)
{
free += block->size;
}
}
return free;
}
//
// Z_LargestFreeBlock
//
int Z_LargestFreeBlock (int zone)
{
if (zone < 0)
{
int maxsize = 0;
for (int i = 0; i < zone_count; i++) {
int n = Z_LargestFreeBlock(i);
if (n > maxsize)
maxsize = n;
}
return maxsize;
}
memblock_t* block;
int maxsize = 0;
memzone_t* mainzone = zones[zone];
for(block = mainzone->blocklist.next;block != &mainzone->blocklist;block = block->next)
{
if (!block->user || block->tag >= PU_PURGELEVEL)
{
if (block->size > maxsize)
maxsize = block->size;
}
}
return maxsize;
}