post-scarcity/src/c/memory/memory.c

144 lines
4.5 KiB
C

/**
* memory/memory.c
*
* The memory management subsystem.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "debug.h"
#include "memory/memory.h"
#include "memory/node.h"
#include "memory/page.h"
#include "memory/pointer.h"
#include "memory/pso.h"
#include "memory/pso2.h"
#include "memory/tags.h"
#include "ops/truth.h"
#include "payloads/exception.h"
#include "ops/bind.h"
#include "ops/string_ops.h"
/**
* @brief Freelists for each size class.
*/
struct pso_pointer freelists[MAX_SIZE_CLASS];
/**
* Mutices to lock the freelists during access.
*/
pthread_mutex_t freelists_mutices[MAX_SIZE_CLASS];
/**
* @brief Flag to prevent re-initialisation.
*/
bool memory_initialised = false;
/**
* @brief Initialise the memory allocation system.
*
* Essentially, just set up the freelists; allocating pages will then happen
* automatically as objects are requested.
*
* @param node the index number of the node we are initialising.
* @return int
*/
struct pso_pointer initialise_memory( uint32_t node ) {
struct pso_pointer result = nil;
if ( memory_initialised ) {
result =
make_exception( make_frame( 1, nil, c_string_to_lisp_string
( nil,
L"Attenpt to reinitialise memory." ) ) );
} else {
for ( uint8_t i = 0; i <= MAX_SIZE_CLASS; i++ ) {
freelists[i] = nil;
}
#ifdef DEBUG
debug_print( L"Memory initialised", DEBUG_BOOTSTRAP, 0 );
#endif
memory_initialised = true;
}
return t;
}
/**
* @brief Pop an object off the freelist for the specified `size_class`.
*
* There is no conventional way this function can signal an error. Any pointer
* it returns is potentially valid. However, every valid object must have an
* even numbered offset, so possibly {:node 0, :page 0, :offset 1} could be
* used as a magic marker to indicate total exhaustion of store for this size
* class. TODO: think about this.
*/
struct pso_pointer pop_freelist( uint8_t size_class ) {
struct pso_pointer result = t;
if ( size_class <= MAX_SIZE_CLASS ) {
if ( c_nilp( freelists[size_class] ) ) {
result = allocate_page( size_class );
}
if ( c_nilp( result ) ) {
fputws( L"FATAL: Page space exhausted\n", stderr );
exit( 1 ); // TODO: we don't want to do this! Somehow, we need to
// recover a workable environment, ideally by throwing a pre-made
// exception.
}
if ( !exceptionp( result ) && !c_nilp( result ) ) {
pthread_mutex_lock( &freelists_mutices[size_class] );
result = freelists[size_class];
struct pso2 *object = pointer_to_object( result );
freelists[size_class] = object->payload.free.next;
pthread_mutex_unlock( &freelists_mutices[size_class] );
/* the object ought already to have the right size class in its tag
* because it was popped off the freelist for that size class. */
if ( object->header.tag.bytes.size_class != size_class ) {
fwprintf( stderr,
L"WARNING: Unexpected size class %x. on free list for class %x while allocating.\n",
object->header.tag.bytes.size_class, size_class );
}
/* the objext ought to have a reference count ot zero, because it's
* on the freelist, but again we should sanity check. */
if ( object->header.count != 0 ) {
fwprintf( stderr,
L"\nWARNING: Count of %u in newly allocated object at %u, %u, should be 0\n",
object->header.count, result.page, result.offset );
object->header.count = 0;
}
}
} // TODO: else throw exception
return result;
}
void push_freelist( struct pso_pointer p ) {
struct pso2 *obj = pointer_to_object( p );
uint8_t size_class = ( obj->header.tag.bytes.size_class );
strncpy( ( char * ) ( obj->header.tag.bytes.mnemonic ), FREETAG,
TAGLENGTH );
pthread_mutex_lock( &freelists_mutices[size_class] );
if ( size_class <= MAX_SIZE_CLASS ) {
obj->payload.free.next = freelists[size_class];
freelists[size_class] = p;
}
pthread_mutex_unlock( &freelists_mutices[size_class] );
}