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

236 lines
7.5 KiB
C

/**
* 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 <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#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;
}