/** * memory/pso.c * * Paged space objects. * * Broadly, it should be save to cast any paged space object to a pso2, since * that is the smallest actually used size class. This should work to extract * the tag and size class fields from the header, for example. I'm not * confident enough of my understanding of C to know whether it is similarly * safe to cast something passed to you as a pso2 up to something larger, even * if you know from the size class field that it actually is something larger. * * (c) 2026 Simon Brooke * Licensed under GPL version 2.0, or, at your option, any later version. */ #include #include #include #include #include #include #include "debug.h" #include "memory/destroy.h" #include "memory/header.h" #include "memory/memory.h" #include "memory/node.h" #include "memory/page.h" #include "memory/pointer.h" #include "memory/pso.h" #include "memory/pso4.h" #include "memory/tags.h" #include "ops/truth.h" /** * @brief a means of creating a cons cell without using a stack frame, to * prevent runaway recursion. * * @param car * @param cdr * * return cons */ struct pso_pointer cheaty_make_cons( struct pso_pointer car, struct pso_pointer cdr ) { struct pso_pointer result = allocate( nil, CONSTAG, 2 ); struct pso2 *obj = pointer_to_object( result ); obj->payload.cons.car = car; obj->payload.cons.cdr = cdr; return result; } /** * @brief Allocate an object of this `size_class` with this `tag`. * * All objects that are allocated (after completion of init)) should be linked * onto the `locals` slot of a stack frame. This guarantees * 1. that they do get `inc_ref`ed; and that, * 2. if nothing else hangs onto them they will be reclaimed when that stack * frame is reclaimed. * for some objects (e.g. those cons cells on the locals list) this isn't * possible due to infinite recursion, but those special cases need to be * audited carefully. * * @param frame_pointer pointer to an active stack frame (or * nil, but only during initialisation). * @param tag The tag. Only the first three bytes will be used; * @param size_class The size class for the object to be allocated; * @return struct pso_pointer a pointer to the newly allocated object */ struct pso_pointer allocate( struct pso_pointer frame_pointer, char *tag, uint8_t size_class ) { // todo: issue #21: must have stack frame passed in. #ifdef DEBUG debug_printf( DEBUG_ALLOC, 0, L"Allocating object of size class %d with tag `%s`... ", size_class, tag ); #endif struct pso_pointer result = pop_freelist( size_class ); struct pso4 *frame = pointer_to_pso4( frame_pointer ); if ( !c_nilp( result ) ) { strncpy( ( char * ) ( pointer_to_object( result )->header.tag.bytes. mnemonic ), tag, TAGLENGTH ); debug_printf( DEBUG_ALLOC, 0, L"at page %d, offset %d... ", result.page, result.offset ); if ( stackp( frame_pointer ) ) { // You can't make a stack frame in the middle of making a stack // frame. Infinite recursion. So we have to cheat. struct pso_pointer locals = cheaty_make_cons( result, frame-> payload.stack_frame. locals ); frame->payload.stack_frame.locals = locals; } else if ( memory_initialised ) { fputws( L"WARNING: No stack frame passed to `allocate`.\n", stderr ); } } else { // TODO: throw exception } #ifdef DEBUG debug_print( exceptionp( result ) ? L"fail\n" : L"success\n", DEBUG_ALLOC, 0 ); #endif return result; } int payload_size( struct pso2 *object ) { // TODO: Unit tests DEFINITELY needed! int sc = object->header.tag.bytes.size_class; int hs = sizeof( struct pso_header ) / sizeof( uint64_t ); int p = pow( 2, sc ); int result = abs( p - hs ); return result; } /** * increment the reference count of the object at this cons pointer. * * You can't roll over the reference count. Once it hits the maximum * value you cannot increment further. * * Returns the `pointer`. */ struct pso_pointer inc_ref( struct pso_pointer pointer ) { struct pso2 *object = pointer_to_object( pointer ); if ( object->header.count < MAXREFERENCE ) { object->header.count++; #ifdef DEBUG debug_printf( DEBUG_ALLOC, 0, L"\nIncremented object of type %3.3s at page %u, offset %u to count %u", ( ( char * ) &object->header.tag.bytes.mnemonic[0] ), pointer.page, pointer.offset, object->header.count ); if ( vectorpointp( pointer ) ) { debug_printf( DEBUG_ALLOC, 0, L"; pointer to vector object of type %3.3s.\n", ( ( char * ) &( object->payload.vectorp.tag.bytes[0] ) ) ); } else { debug_println( DEBUG_ALLOC ); } #endif } return pointer; } /** * Decrement the reference count of the object at this cons pointer. * * If a count has reached MAXREFERENCE it cannot be decremented. * If a count is decremented to zero the object should be freed. * * Returns the `pointer`, or, if the object has been freed, a pointer to `nil`. */ struct pso_pointer dec_ref( struct pso_pointer pointer ) { struct pso2 *object = pointer_to_object( pointer ); if ( !c_nilp( pointer ) && object->header.count > 0 && object->header.count != MAXREFERENCE ) { object->header.count--; #ifdef DEBUG debug_printf( DEBUG_ALLOC, 0, L"\nDecremented object of type %3.3s at page %d, offset %d to count %d", ( ( char * ) ( object->header.tag.bytes.mnemonic ) ), pointer.page, pointer.offset, object->header.count ); if ( vectorpointp( pointer ) ) { debug_printf( DEBUG_ALLOC, 0, L"; pointer to vector object of type %3.3s.\n", ( ( char * ) &( object->payload.vectorp.tag.bytes ) ) ); } else { debug_println( DEBUG_ALLOC ); } #endif if ( object->header.count == 0 ) { free_object( pointer ); pointer = nil; } } return pointer; } /** * @brief Prevent an object ever being dereferenced. * * @param pointer pointer to an object to lock. * * @return the `pointer` */ struct pso_pointer lock_object( struct pso_pointer pointer ) { struct pso2 *object = pointer_to_object( pointer ); object->header.count = MAXREFERENCE; return pointer; } /** * @brief decrement all pointers pointed to by the object at this pointer; * clear its memory, and return it to the freelist. */ struct pso_pointer free_object( struct pso_pointer p ) { struct pso_pointer result = nil; struct pso2 *obj = pointer_to_object( p ); uint32_t array_size = ( uint32_t ) payload_size( obj ); uint8_t size_class = ( obj->header.tag.bytes.size_class ); result = destroy( p ); /* will C just let me cheerfully walk off the end of the array I've declared? */ for ( int i = 0; i < array_size; i++ ) { obj->payload.words[i] = 0; } push_freelist( p ); return result; }