/** * memory/memory.c * * The memory management subsystem. * * (c) 2026 Simon Brooke * Licensed under GPL version 2.0, or, at your option, any later version. */ #include #include #include #include #include #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] ); }