mirror of
https://github.com/gcc-mirror/gcc.git
synced 2024-11-21 13:40:47 +00:00
383 lines
11 KiB
C
383 lines
11 KiB
C
/* Copyright (C) 2023-2024 Free Software Foundation, Inc.
|
|
|
|
This file is part of the GNU Offloading and Multi Processing Library
|
|
(libgomp).
|
|
|
|
Libgomp 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, or (at your option)
|
|
any later version.
|
|
|
|
Libgomp 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.
|
|
|
|
Under Section 7 of GPL version 3, you are granted additional
|
|
permissions described in the GCC Runtime Library Exception, version
|
|
3.1, as published by the Free Software Foundation.
|
|
|
|
You should have received a copy of the GNU General Public License and
|
|
a copy of the GCC Runtime Library Exception along with this program;
|
|
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
/* This is a basic "malloc" implementation intended for use with small,
|
|
low-latency memories.
|
|
|
|
To use this template, define BASIC_ALLOC_PREFIX, and then #include the
|
|
source file. The other configuration macros are optional.
|
|
|
|
The root heap descriptor is stored in the first bytes of the heap, and each
|
|
free chunk contains a similar descriptor for the next free chunk in the
|
|
chain.
|
|
|
|
The descriptor is two values: offset and size, which describe the
|
|
location of a chunk of memory available for allocation. The offset is
|
|
relative to the base of the heap. The special offset value 0xffffffff
|
|
indicates that the heap (free chain) is locked. The offset and size are
|
|
32-bit values so the base alignment can be 8-bytes.
|
|
|
|
Memory is allocated to the first free chunk that fits. The free chain
|
|
is always stored in order of the offset to assist coalescing adjacent
|
|
chunks. */
|
|
|
|
#include "libgomp.h"
|
|
|
|
#ifndef BASIC_ALLOC_PREFIX
|
|
#error "BASIC_ALLOC_PREFIX not defined."
|
|
#endif
|
|
|
|
#ifndef BASIC_ALLOC_YIELD
|
|
#define BASIC_ALLOC_YIELD
|
|
#endif
|
|
|
|
#define ALIGN(VAR) (((VAR) + 7) & ~7) /* 8-byte granularity. */
|
|
|
|
#define fn1(prefix, name) prefix ## _ ## name
|
|
#define fn(prefix, name) fn1 (prefix, name)
|
|
#define basic_alloc_init fn(BASIC_ALLOC_PREFIX,init)
|
|
#define basic_alloc_alloc fn(BASIC_ALLOC_PREFIX,alloc)
|
|
#define basic_alloc_calloc fn(BASIC_ALLOC_PREFIX,calloc)
|
|
#define basic_alloc_free fn(BASIC_ALLOC_PREFIX,free)
|
|
#define basic_alloc_realloc fn(BASIC_ALLOC_PREFIX,realloc)
|
|
|
|
typedef struct {
|
|
uint32_t offset;
|
|
uint32_t size;
|
|
} heapdesc;
|
|
|
|
void
|
|
basic_alloc_init (char *heap, size_t limit)
|
|
{
|
|
if (heap == NULL)
|
|
return;
|
|
|
|
/* Initialize the head of the free chain. */
|
|
heapdesc *root = (heapdesc *) heap;
|
|
root->offset = ALIGN(1);
|
|
root->size = limit - root->offset;
|
|
|
|
/* And terminate the chain. */
|
|
heapdesc *next = (heapdesc *) (heap + root->offset);
|
|
next->offset = 0;
|
|
next->size = 0;
|
|
}
|
|
|
|
static void *
|
|
basic_alloc_alloc (char *heap, size_t size)
|
|
{
|
|
if (heap == NULL)
|
|
return NULL;
|
|
|
|
/* Memory is allocated in N-byte granularity. */
|
|
size = ALIGN (size);
|
|
|
|
/* Acquire a lock on the low-latency heap. */
|
|
heapdesc root, *root_ptr = (heapdesc *) heap;
|
|
do
|
|
{
|
|
root.offset = __atomic_exchange_n (&root_ptr->offset, 0xffffffff,
|
|
MEMMODEL_ACQUIRE);
|
|
if (root.offset != 0xffffffff)
|
|
{
|
|
root.size = root_ptr->size;
|
|
break;
|
|
}
|
|
/* Spin. */
|
|
BASIC_ALLOC_YIELD;
|
|
}
|
|
while (1);
|
|
|
|
/* Walk the free chain. */
|
|
heapdesc chunk = root;
|
|
heapdesc *prev_chunkptr = NULL;
|
|
heapdesc *chunkptr = (heapdesc *) (heap + chunk.offset);
|
|
heapdesc onward_chain = *chunkptr;
|
|
while (chunk.size != 0 && (uint32_t) size > chunk.size)
|
|
{
|
|
chunk = onward_chain;
|
|
prev_chunkptr = chunkptr;
|
|
chunkptr = (heapdesc *) (heap + chunk.offset);
|
|
onward_chain = *chunkptr;
|
|
}
|
|
|
|
void *result = NULL;
|
|
if (chunk.size != 0)
|
|
{
|
|
/* Allocation successful. */
|
|
result = chunkptr;
|
|
|
|
/* Update the free chain. */
|
|
heapdesc stillfree = chunk;
|
|
stillfree.offset += size;
|
|
stillfree.size -= size;
|
|
heapdesc *stillfreeptr = (heapdesc *) (heap + stillfree.offset);
|
|
|
|
if (stillfree.size == 0)
|
|
/* The whole chunk was used. */
|
|
stillfree = onward_chain;
|
|
else
|
|
/* The chunk was split, so restore the onward chain. */
|
|
*stillfreeptr = onward_chain;
|
|
|
|
/* The previous free slot or root now points to stillfree. */
|
|
if (prev_chunkptr)
|
|
*prev_chunkptr = stillfree;
|
|
else
|
|
root = stillfree;
|
|
}
|
|
|
|
/* Update the free chain root and release the lock. */
|
|
root_ptr->size = root.size;
|
|
__atomic_store_n (&root_ptr->offset, root.offset, MEMMODEL_RELEASE);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void *
|
|
basic_alloc_calloc (char *heap, size_t size)
|
|
{
|
|
/* Memory is allocated in N-byte granularity. */
|
|
size = ALIGN (size);
|
|
|
|
uint64_t *result = basic_alloc_alloc (heap, size);
|
|
if (result)
|
|
/* Inline memset in which we know size is a multiple of 8. */
|
|
for (unsigned i = 0; i < (unsigned) size / 8; i++)
|
|
result[i] = 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
basic_alloc_free (char *heap, void *addr, size_t size)
|
|
{
|
|
/* Memory is allocated in N-byte granularity. */
|
|
size = ALIGN (size);
|
|
|
|
/* Acquire a lock on the low-latency heap. */
|
|
heapdesc root, *root_ptr = (heapdesc *) heap;
|
|
do
|
|
{
|
|
root.offset = __atomic_exchange_n (&root_ptr->offset, 0xffffffff,
|
|
MEMMODEL_ACQUIRE);
|
|
if (root.offset != 0xffffffff)
|
|
{
|
|
root.size = root_ptr->size;
|
|
break;
|
|
}
|
|
/* Spin. */
|
|
BASIC_ALLOC_YIELD;
|
|
}
|
|
while (1);
|
|
|
|
/* Walk the free chain to find where to insert a new entry. */
|
|
heapdesc chunk = root, prev_chunk = {0};
|
|
heapdesc *prev_chunkptr = NULL, *prevprev_chunkptr = NULL;
|
|
heapdesc *chunkptr = (heapdesc *) (heap + chunk.offset);
|
|
heapdesc onward_chain = *chunkptr;
|
|
while (chunk.size != 0 && addr > (void *) chunkptr)
|
|
{
|
|
prev_chunk = chunk;
|
|
chunk = onward_chain;
|
|
prevprev_chunkptr = prev_chunkptr;
|
|
prev_chunkptr = chunkptr;
|
|
chunkptr = (heapdesc *) (heap + chunk.offset);
|
|
onward_chain = *chunkptr;
|
|
}
|
|
|
|
/* Create the new chunk descriptor. */
|
|
heapdesc newfreechunk;
|
|
newfreechunk.offset = (uint32_t) ((uintptr_t) addr - (uintptr_t) heap);
|
|
newfreechunk.size = (uint32_t) size;
|
|
|
|
/* Coalesce adjacent free chunks. */
|
|
if (newfreechunk.offset + size == chunk.offset)
|
|
{
|
|
/* Free chunk follows. */
|
|
newfreechunk.size += chunk.size;
|
|
chunk = onward_chain;
|
|
}
|
|
if (prev_chunkptr)
|
|
{
|
|
if (prev_chunk.offset + prev_chunk.size
|
|
== newfreechunk.offset)
|
|
{
|
|
/* Free chunk precedes. */
|
|
newfreechunk.offset = prev_chunk.offset;
|
|
newfreechunk.size += prev_chunk.size;
|
|
addr = heap + prev_chunk.offset;
|
|
prev_chunkptr = prevprev_chunkptr;
|
|
}
|
|
}
|
|
|
|
/* Update the free chain in the new and previous chunks. */
|
|
*(heapdesc *) addr = chunk;
|
|
if (prev_chunkptr)
|
|
*prev_chunkptr = newfreechunk;
|
|
else
|
|
root = newfreechunk;
|
|
|
|
/* Update the free chain root and release the lock. */
|
|
root_ptr->size = root.size;
|
|
__atomic_store_n (&root_ptr->offset, root.offset, MEMMODEL_RELEASE);
|
|
|
|
}
|
|
|
|
static void *
|
|
basic_alloc_realloc (char *heap, void *addr, size_t oldsize,
|
|
size_t size)
|
|
{
|
|
/* Memory is allocated in N-byte granularity. */
|
|
oldsize = ALIGN (oldsize);
|
|
size = ALIGN (size);
|
|
|
|
if (oldsize == size)
|
|
return addr;
|
|
|
|
/* Acquire a lock on the low-latency heap. */
|
|
heapdesc root, *root_ptr = (heapdesc *) heap;
|
|
do
|
|
{
|
|
root.offset = __atomic_exchange_n (&root_ptr->offset, 0xffffffff,
|
|
MEMMODEL_ACQUIRE);
|
|
if (root.offset != 0xffffffff)
|
|
{
|
|
root.size = root_ptr->size;
|
|
break;
|
|
}
|
|
/* Spin. */
|
|
BASIC_ALLOC_YIELD;
|
|
}
|
|
while (1);
|
|
|
|
/* Walk the free chain. */
|
|
heapdesc chunk = root;
|
|
heapdesc *prev_chunkptr = NULL;
|
|
heapdesc *chunkptr = (heapdesc *) (heap + chunk.offset);
|
|
heapdesc onward_chain = *chunkptr;
|
|
while (chunk.size != 0 && (void *) chunkptr < addr)
|
|
{
|
|
chunk = onward_chain;
|
|
prev_chunkptr = chunkptr;
|
|
chunkptr = (heapdesc *) (heap + chunk.offset);
|
|
onward_chain = *chunkptr;
|
|
}
|
|
|
|
void *result = NULL;
|
|
if (size < oldsize)
|
|
{
|
|
/* The new allocation is smaller than the old; we can always
|
|
shrink an allocation in place. */
|
|
result = addr;
|
|
|
|
heapdesc *nowfreeptr = (heapdesc *) (addr + size);
|
|
|
|
/* Update the free chain. */
|
|
heapdesc nowfree;
|
|
nowfree.offset = (char *) nowfreeptr - heap;
|
|
nowfree.size = oldsize - size;
|
|
|
|
if (nowfree.offset + size == chunk.offset)
|
|
{
|
|
/* Coalesce following free chunk. */
|
|
nowfree.size += chunk.size;
|
|
*nowfreeptr = onward_chain;
|
|
}
|
|
else
|
|
*nowfreeptr = chunk;
|
|
|
|
/* The previous free slot or root now points to nowfree. */
|
|
if (prev_chunkptr)
|
|
*prev_chunkptr = nowfree;
|
|
else
|
|
root = nowfree;
|
|
}
|
|
else if (chunk.size != 0
|
|
&& (char *) addr + oldsize == (char *) chunkptr
|
|
&& chunk.size >= size-oldsize)
|
|
{
|
|
/* The new allocation is larger than the old, and we found a
|
|
large enough free block right after the existing block,
|
|
so we extend into that space. */
|
|
result = addr;
|
|
|
|
uint32_t delta = size-oldsize;
|
|
|
|
/* Update the free chain. */
|
|
heapdesc stillfree = chunk;
|
|
stillfree.offset += delta;
|
|
stillfree.size -= delta;
|
|
heapdesc *stillfreeptr = (heapdesc *) (heap + stillfree.offset);
|
|
|
|
if (stillfree.size == 0)
|
|
/* The whole chunk was used. */
|
|
stillfree = onward_chain;
|
|
else
|
|
/* The chunk was split, so restore the onward chain. */
|
|
*stillfreeptr = onward_chain;
|
|
|
|
/* The previous free slot or root now points to stillfree. */
|
|
if (prev_chunkptr)
|
|
*prev_chunkptr = stillfree;
|
|
else
|
|
root = stillfree;
|
|
}
|
|
/* Else realloc in-place has failed and result remains NULL. */
|
|
|
|
/* Update the free chain root and release the lock. */
|
|
root_ptr->size = root.size;
|
|
__atomic_store_n (&root_ptr->offset, root.offset, MEMMODEL_RELEASE);
|
|
|
|
if (result == NULL)
|
|
{
|
|
/* The allocation could not be extended in place, so we simply
|
|
allocate fresh memory and move the data. If we can't allocate
|
|
from low-latency memory then we leave the original alloaction
|
|
intact and return NULL.
|
|
We could do a fall-back to main memory, but we don't know what
|
|
the fall-back trait said to do. */
|
|
result = basic_alloc_alloc (heap, size);
|
|
if (result != NULL)
|
|
{
|
|
/* Inline memcpy in which we know oldsize is a multiple of 8. */
|
|
uint64_t *from = addr, *to = result;
|
|
for (unsigned i = 0; i < (unsigned) oldsize / 8; i++)
|
|
to[i] = from[i];
|
|
|
|
basic_alloc_free (heap, addr, oldsize);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#undef ALIGN
|
|
#undef fn1
|
|
#undef fn
|
|
#undef basic_alloc_init
|
|
#undef basic_alloc_alloc
|
|
#undef basic_alloc_free
|
|
#undef basic_alloc_realloc
|