mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 21:34:08 -07:00
cpu.cpp: avoided the need for wrapper functions by calling the OS-specific function directly (declared in central header, implemented in the platform's cpp file) avoid the need for init in cpu and ia32 via if(!init) Init() pattern. optimized memcpy now requires SSE support remove error-prone CAS macro; replace with cpu_CAS config: no longer require inline asm for float->int conversions lib_error: remove special-case in CHECK_ERR for windows (no longer needed) This was SVN commit r5365.
576 lines
10 KiB
C++
576 lines
10 KiB
C++
/**
|
|
* =========================================================================
|
|
* File : lf_alloc.cpp
|
|
* Project : 0 A.D.
|
|
* Description : lock-free memory allocation.
|
|
* =========================================================================
|
|
*/
|
|
|
|
// license: GPL; see lib/license.txt
|
|
|
|
#include "precompiled.h"
|
|
|
|
#if 0
|
|
|
|
#include <algorithm>
|
|
#include <limits.h>
|
|
|
|
#include "posix.h"
|
|
#include "lib/sysdep/cpu.h"
|
|
#include "lockfree.h"
|
|
#include "timer.h"
|
|
|
|
|
|
// superblock descriptor structure
|
|
// one machine word
|
|
struct Anchor
|
|
{
|
|
uint avail : 10;
|
|
uint count : 10;
|
|
uint tag : 10;
|
|
uint state : 2;
|
|
|
|
// convert to uintptr_t for cpu_CAS
|
|
operator uintptr_t() const
|
|
{
|
|
return *(uintptr_t*)this;
|
|
}
|
|
};
|
|
|
|
cassert(sizeof(Anchor) == sizeof(uintptr_t));
|
|
|
|
enum State
|
|
{
|
|
ACTIVE = 0,
|
|
FULL = 1,
|
|
PARTIAL = 2,
|
|
EMPTY = 3
|
|
};
|
|
|
|
|
|
/*typedef void* DescList;
|
|
|
|
struct SizeClass
|
|
{
|
|
DescList partial; // initially empty
|
|
size_t sz; // block size
|
|
size_t sb_size; // superblock's size
|
|
};
|
|
|
|
struct Descriptor;
|
|
|
|
static const uint PTR_BITS = sizeof(void*) * CHAR_BIT;
|
|
|
|
struct Active
|
|
{
|
|
uint pdesc : PTR_BITS-6;
|
|
uint credits : 6;
|
|
|
|
Active()
|
|
{
|
|
}
|
|
|
|
// convert to uintptr_t for cpu_CAS
|
|
operator uintptr_t() const
|
|
{
|
|
return *(uintptr_t*)this;
|
|
}
|
|
|
|
|
|
//
|
|
// allow Active to be used as Descriptor*
|
|
//
|
|
|
|
Active& operator=(Descriptor* desc)
|
|
{
|
|
*(Descriptor**)this = desc;
|
|
debug_assert(credits == 0); // make sure ptr is aligned
|
|
return *this;
|
|
}
|
|
|
|
Active(Descriptor* desc)
|
|
{
|
|
*this = desc;
|
|
}
|
|
|
|
// disambiguate (could otherwise be either uintptr_t or Descriptor*)
|
|
bool operator!() const
|
|
{
|
|
return (uintptr_t)*this != 0;
|
|
}
|
|
|
|
operator Descriptor*() const
|
|
{
|
|
return *(Descriptor**)this;
|
|
}
|
|
|
|
};
|
|
|
|
static const uint MAX_CREDITS = 64; // = 2 ** num_credit_bits
|
|
|
|
struct ProcHeap
|
|
{
|
|
Active active; // initially 0; points to Descriptor
|
|
Descriptor* partial; // initially 0
|
|
SizeClass* sc; // parent
|
|
};
|
|
|
|
// POD; must be MAX_CREDITS-aligned!
|
|
struct Descriptor
|
|
{
|
|
Anchor anchor;
|
|
Descriptor* next;
|
|
u8* sb; // superblock
|
|
ProcHeap* heap; // -> owner procheap
|
|
size_t sz; // block size
|
|
uint maxcount; // superblock size/sz
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static u8* AllocNewSB(size_t sb_size)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void FreeSB(u8* sb)
|
|
{
|
|
}
|
|
|
|
|
|
static Descriptor* DescAvail = 0;
|
|
|
|
static const size_t DESCSBSIZE = 128;
|
|
|
|
static Descriptor* DescAlloc()
|
|
{
|
|
Descriptor* desc;
|
|
for(;;)
|
|
{
|
|
desc = DescAvail;
|
|
if(desc)
|
|
{
|
|
Descriptor* next = desc->next;
|
|
if(cpu_CAS(&DescAvail, desc, next))
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
desc = (Descriptor*)AllocNewSB(DESCSBSIZE);
|
|
// organize descriptors in a linked list
|
|
cpu_MemoryFence();
|
|
if(cpu_CAS(&DescAvail, 0, desc->next))
|
|
break;
|
|
FreeSB((u8*)desc);
|
|
}
|
|
}
|
|
return desc;
|
|
}
|
|
|
|
static void DescRetire(Descriptor* desc)
|
|
{
|
|
Descriptor* old_head;
|
|
do
|
|
{
|
|
old_head = DescAvail;
|
|
desc->next = old_head;
|
|
cpu_MemoryFence();
|
|
}
|
|
while(!cpu_CAS(&DescAvail, old_head, desc));
|
|
}
|
|
|
|
static Descriptor* ListGetPartial(SizeClass* sc)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void ListPutPartial(Descriptor* desc)
|
|
{
|
|
}
|
|
|
|
static void ListRemoveEmptyDesc(SizeClass* sc)
|
|
{
|
|
}
|
|
|
|
static ProcHeap* find_heap(SizeClass* sc)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
static Descriptor* HeapGetPartial(ProcHeap* heap)
|
|
{
|
|
Descriptor* desc;
|
|
do
|
|
{
|
|
desc = heap->partial;
|
|
if(!desc)
|
|
return ListGetPartial(heap->sc);
|
|
}
|
|
while(!cpu_CAS(&heap->partial, desc, 0));
|
|
return desc;
|
|
}
|
|
|
|
|
|
static void HeapPutPartial(Descriptor* desc)
|
|
{
|
|
Descriptor* prev;
|
|
do
|
|
prev = desc->heap->partial;
|
|
while(!cpu_CAS(&desc->heap->partial, prev, desc));
|
|
if(prev)
|
|
ListPutPartial(prev);
|
|
}
|
|
|
|
|
|
static void UpdateActive(ProcHeap* heap, Descriptor* desc, uint more_credits)
|
|
{
|
|
Active new_active = desc;
|
|
new_active.credits = more_credits-1;
|
|
if(cpu_CAS(&heap->active, 0, new_active))
|
|
return;
|
|
|
|
// someone installed another active sb
|
|
// return credits to sb and make it partial
|
|
Anchor old_anchor, new_anchor;
|
|
do
|
|
{
|
|
new_anchor = old_anchor = desc->anchor;
|
|
new_anchor.count += more_credits;
|
|
new_anchor.state = PARTIAL;
|
|
}
|
|
while(!cpu_CAS(&desc->anchor, old_anchor, new_anchor));
|
|
HeapPutPartial(desc);
|
|
}
|
|
|
|
|
|
|
|
static void RemoveEmptyDesc(ProcHeap* heap, Descriptor* desc)
|
|
{
|
|
if(cpu_CAS(&heap->partial, desc, 0))
|
|
DescRetire(desc);
|
|
else
|
|
ListRemoveEmptyDesc(heap->sc);
|
|
}
|
|
|
|
|
|
static void* MallocFromActive(ProcHeap* heap)
|
|
{
|
|
// reserve block
|
|
Active old_active, new_active;
|
|
do
|
|
{
|
|
new_active = old_active = heap->active;
|
|
// no active superblock - will try Partial and then NewSB
|
|
if(!old_active)
|
|
return 0;
|
|
// none left - mark as no longer active
|
|
if(old_active.credits == 0)
|
|
new_active = 0;
|
|
// expected case - reserve
|
|
else
|
|
new_active.credits--;
|
|
}
|
|
while(!cpu_CAS(&heap->active, old_active, new_active));
|
|
|
|
u8* p;
|
|
|
|
// pop block
|
|
Anchor old_anchor, new_anchor;
|
|
Descriptor* desc = old_active;
|
|
uint more_credits;
|
|
do
|
|
{
|
|
new_anchor = old_anchor = desc->anchor;
|
|
p = desc->sb + old_anchor.avail*desc->sz;
|
|
new_anchor.avail = *(uint*)p;
|
|
new_anchor.tag++;
|
|
if(old_active.credits == 0)
|
|
{
|
|
// state must be ACTIVE
|
|
if(old_anchor.count == 0)
|
|
new_anchor.state = FULL;
|
|
else
|
|
{
|
|
more_credits = std::min(old_anchor.count, MAX_CREDITS);
|
|
new_anchor.count -= more_credits;
|
|
}
|
|
}
|
|
}
|
|
while(!cpu_CAS(&desc->anchor, old_anchor, new_anchor));
|
|
if(old_active.credits == 0 && old_anchor.count > 0)
|
|
UpdateActive(heap, desc, more_credits);
|
|
|
|
*(Descriptor**)p = desc;
|
|
return p+sizeof(void*);
|
|
|
|
}
|
|
|
|
|
|
static void* MallocFromPartial(ProcHeap* heap)
|
|
{
|
|
retry:
|
|
Descriptor* desc = HeapGetPartial(heap);
|
|
if(!desc)
|
|
return 0;
|
|
desc->heap = heap;
|
|
|
|
// reserve blocks
|
|
uint more_credits;
|
|
Anchor old_anchor, new_anchor;
|
|
do
|
|
{
|
|
new_anchor = old_anchor = desc->anchor;
|
|
if(old_anchor.state == EMPTY)
|
|
{
|
|
DescRetire(desc);
|
|
goto retry;
|
|
}
|
|
// old_anchor state must be PARTIAL
|
|
// old_anchor count must be > 0
|
|
more_credits = std::min(old_anchor.count-1, MAX_CREDITS);
|
|
new_anchor.count -= more_credits+1;
|
|
new_anchor.state = (more_credits > 0)? ACTIVE : FULL;
|
|
}
|
|
while(!cpu_CAS(&desc->anchor, old_anchor, new_anchor));
|
|
|
|
u8* p;
|
|
|
|
// pop reserved block
|
|
do
|
|
{
|
|
new_anchor = old_anchor = desc->anchor;
|
|
p = desc->sb + old_anchor.avail*desc->sz;
|
|
new_anchor.avail = *(uint*)p;
|
|
new_anchor.tag++;
|
|
}
|
|
while(!cpu_CAS(&desc->anchor, old_anchor, new_anchor));
|
|
|
|
if(more_credits > 0)
|
|
UpdateActive(heap, desc, more_credits);
|
|
|
|
*(Descriptor**)p = desc;
|
|
return p+sizeof(void*);
|
|
}
|
|
|
|
|
|
static void* MallocFromNewSB(ProcHeap* heap)
|
|
{
|
|
Descriptor* desc = DescAlloc();
|
|
desc->sb = AllocNewSB(heap->sc->sb_size);
|
|
|
|
//organize blocks in a linked list starting with index 0
|
|
|
|
desc->heap = heap;
|
|
desc->anchor.avail = 1;
|
|
desc->sz = heap->sc->sz;
|
|
desc->maxcount = (uint)(heap->sc->sb_size/desc->sz);
|
|
Active new_active = (Active)desc;
|
|
new_active.credits = std::min(desc->maxcount-1, MAX_CREDITS)-1;
|
|
desc->anchor.count = (desc->maxcount-1)-(new_active.credits+1);
|
|
desc->anchor.state = ACTIVE;
|
|
cpu_MemoryFence();
|
|
if(!cpu_CAS(&heap->active, 0, new_active))
|
|
{
|
|
FreeSB(desc->sb);
|
|
return 0;
|
|
}
|
|
|
|
u8* p = desc->sb;
|
|
|
|
*(Descriptor**)p = desc;
|
|
return p+sizeof(void*);
|
|
}
|
|
|
|
|
|
void* lf_malloc(size_t sz)
|
|
{
|
|
void* p;
|
|
|
|
// use sz and thread id to find heap
|
|
ProcHeap* heap = find_heap(0); // TODO: pass SizeClass
|
|
// large block - allocate directly
|
|
if(!heap)
|
|
{
|
|
p = malloc(sz);
|
|
if(p)
|
|
*(size_t*)p = sz|1;
|
|
return p;
|
|
}
|
|
|
|
retry:
|
|
p = MallocFromActive(heap);
|
|
if(p)
|
|
return p;
|
|
p = MallocFromPartial(heap);
|
|
if(p)
|
|
return p;
|
|
p = MallocFromNewSB(heap);
|
|
if(p)
|
|
return p;
|
|
goto retry;
|
|
}
|
|
|
|
|
|
void lf_free(void* p_)
|
|
{
|
|
if(!p_)
|
|
return;
|
|
u8* p = (u8*)p_;
|
|
|
|
// get block header
|
|
p -= sizeof(void*);
|
|
uintptr_t hdr = *(uintptr_t*)p;
|
|
|
|
// large block - free directly
|
|
if(hdr & 1)
|
|
{
|
|
free(p);
|
|
return;
|
|
}
|
|
|
|
Descriptor* desc = (Descriptor*)hdr;
|
|
u8* sb = desc->sb;
|
|
Anchor old_anchor, new_anchor;
|
|
ProcHeap* heap;
|
|
do
|
|
{
|
|
new_anchor = old_anchor = desc->anchor;
|
|
*(size_t*)p = old_anchor.avail;
|
|
new_anchor.avail = (uint)((p-sb) / desc->sz);
|
|
if(old_anchor.state == FULL)
|
|
new_anchor.state = PARTIAL;
|
|
if(old_anchor.count == desc->maxcount-1)
|
|
{
|
|
heap = desc->heap;
|
|
serialize();
|
|
new_anchor.state = EMPTY;
|
|
}
|
|
else
|
|
new_anchor.count++;
|
|
cpu_MemoryFence();
|
|
}
|
|
while(!cpu_CAS(&desc->anchor, old_anchor, new_anchor));
|
|
if(new_anchor.state == EMPTY)
|
|
{
|
|
FreeSB(sb);
|
|
RemoveEmptyDesc(heap, desc);
|
|
}
|
|
else if(old_anchor.state == FULL)
|
|
HeapPutPartial(desc);
|
|
}
|
|
|
|
/*
|
|
static const int MAX_POOLS = 8;
|
|
|
|
// split out of pools[] for more efficient lookup
|
|
static size_t pool_element_sizes[MAX_POOLS];
|
|
|
|
struct Pool
|
|
{
|
|
u8* bucket_pos;
|
|
u8* freelist;
|
|
}
|
|
pools[MAX_POOLS];
|
|
|
|
static const int num_pools = 0;
|
|
|
|
|
|
const size_t BUCKET_SIZE = 8*KiB;
|
|
|
|
static u8* bucket_pos;
|
|
|
|
|
|
// return the pool responsible for <size>, or 0 if not yet set up and
|
|
// there are already too many pools.
|
|
static Pool* responsible_pool(size_t size)
|
|
{
|
|
Pool* pool = pools;
|
|
for(int i = 0; i < MAX_POOLS; i++, pool++)
|
|
if(pool->element_size == size)
|
|
return pool;
|
|
|
|
// need to set up a new pool
|
|
// .. but there are too many
|
|
debug_assert(0 <= num_pools && num_pools <= MAX_POOLS);
|
|
if(num_pools >= MAX_POOLS)
|
|
{
|
|
debug_warn("increase MAX_POOLS");
|
|
return 0;
|
|
}
|
|
|
|
pool = &pools[num_pools++];
|
|
pool->element_size = size;
|
|
return pool;
|
|
}
|
|
|
|
void* sbh_alloc(size_t size)
|
|
{
|
|
// when this allocation is freed, there must be enough room for
|
|
// our freelist pointer. also ensures alignment.
|
|
size = round_up(size, 8);
|
|
|
|
// would overflow a bucket
|
|
if(size > BUCKET_SIZE-sizeof(u8*))
|
|
{
|
|
debug_warn("sbh_alloc: size doesn't fit in a bucket");
|
|
return 0;
|
|
}
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
}
|
|
|
|
|
|
TNode* node_alloc(size_t size)
|
|
{
|
|
// would overflow a bucket
|
|
if(size > BUCKET_SIZE-sizeof(u8*))
|
|
{
|
|
debug_warn("node_alloc: size doesn't fit in a bucket");
|
|
return 0;
|
|
}
|
|
|
|
size = round_up(size, 8);
|
|
// ensure alignment, since size includes a string
|
|
const uintptr_t addr = (uintptr_t)bucket_pos;
|
|
const size_t bytes_used = addr % BUCKET_SIZE;
|
|
// addr = 0 on first call (no bucket yet allocated)
|
|
// bytes_used == 0 if a node fit exactly into a bucket
|
|
if(addr == 0 || bytes_used == 0 || bytes_used+size > BUCKET_SIZE)
|
|
{
|
|
u8* const prev_bucket = (u8*)addr - bytes_used;
|
|
u8* bucket = (u8*)mem_alloc(BUCKET_SIZE, BUCKET_SIZE);
|
|
if(!bucket)
|
|
return 0;
|
|
*(u8**)bucket = prev_bucket;
|
|
bucket_pos = bucket+round_up(sizeof(u8*), 8);
|
|
}
|
|
|
|
TNode* node = (TNode*)bucket_pos;
|
|
bucket_pos = (u8*)node+size;
|
|
return node;
|
|
}
|
|
|
|
|
|
static void node_free_all()
|
|
{
|
|
const uintptr_t addr = (uintptr_t)bucket_pos;
|
|
u8* bucket = bucket_pos - (addr % BUCKET_SIZE);
|
|
|
|
// covers bucket_pos == 0 case
|
|
while(bucket)
|
|
{
|
|
u8* prev_bucket = *(u8**)bucket;
|
|
mem_free(bucket);
|
|
bucket = prev_bucket;
|
|
}
|
|
}
|
|
*/
|
|
#endif
|