Added work on making namespaces threadsafe.

This commit is contained in:
Simon Brooke 2026-03-28 11:56:36 +00:00
parent 154cda8da3
commit 1afb1b9fad
38 changed files with 1074 additions and 517 deletions

View file

@ -778,7 +778,7 @@ WARN_FORMAT = "$file:$line: $text"
# messages should be written. If left blank the output is written to standard # messages should be written. If left blank the output is written to standard
# error (stderr). # error (stderr).
WARN_LOGFILE = doxy.log WARN_LOGFILE = tmp/doxy.log
#--------------------------------------------------------------------------- #---------------------------------------------------------------------------
# Configuration options related to the input files # Configuration options related to the input files

View file

@ -0,0 +1,141 @@
# Nodes, threads, locks and links
## The problem
Up to now, I've been building a single threaded Lisp. I haven't had to worry about who is mutating memory while I'm trying to read it. The idea that this is a mostly immutable Lisp has encouraged me to be blasé about this. But actually, it isn't entirely immutable, and that matters.
Whenever *any* new datum is created, the freelist pointers have to mutate; whenever any new value is written to any namespace, the namespace has to mutate. The freelist pointers also mutate when objects are allocated and when objects are freed.
Earlier in the design, I had the idea that in the hypercube system, each node would have a two core processor, one core doing execution — actually evaluating Lisp functions — the other handling inter-node communication. I had at one stage the idea that the memory on the node would be partitioned into fixed areas:
| Partition | Contents | Core written by |
| --------- | -------- | --------------- |
| Local cons space | Small objects curated locally | Execution |
| Local vector space | Large objects curated locally | Excecution |
| Cache cons space | Copies of small objects curated elsewhere | Communications |
| Cache vector space | Copies of large objects curated elsewhere | Communications |
So, the execution thread is chuntering merrily along, and it encounters a data item it needs to get from another node. This is intended to happen all the time: every time a function of more than one argument is evaluated, the node will seek to farm out some of the arguments to idle neighbours for evaluation. So the results will often be curated by them. My original vague idea was that the execution node would choose the argument which seemed most costly to evaluate to evaluate locally, pass off the others to neighbours, evaluate the hard one, and by the time that was done probably all the farmed out results would already be back.
The move from cons space objects to the more flexible [paged space objects](Paged-space-objects.md) doesn't really change this, in principle. There will still be a need for some objects which do not fit into pages, and will thus have to lurk in the outer darkness of vector space. Paged space should make the allocation of objects more efficient, but it doesn't change the fundamental issue
But there's an inevitable overhead to copying objects over inter-node links. Even if we have 64 bit (plus housekeeping) wide links, copying a four word object still takes four clock ticks. Of course, in the best case, we could be receiving six four word objects over the six links in those four clock ticks, but
1. The best case only applies to the node initiating a computation;
2. This ignores contention on the communication mesh consequent on hoppity-hop communications between more distant nodes.
So, even if the execution core correctly chose the most expensive argument to evaluate locally, it's quite likely that when it returns to the stack frame, some results from other nodes have still not arrived. What does it do then? Twiddle its thumbs?
It could start another thread, declare itself idle, accept a work request from a neighbour, execute that, and return to the frame to see whether its original task was ready to continue. One of the benefits of having the stack in managed space is that a single stack frame can have arbitrarily many 'next' frames, in arbitrarily many threads. This is exactly how [Interlisp](https://dl.acm.org/doi/10.1145/362375.362379) manages multitasking, after all.
If we do it like that I think we're still safe, because it can't have left any data item in a half-modified state when it switched contexts.
But nevertheless, we still have the issue of contention between the execution process and the communications process. They both need to be able to mutate freelist pointers; and they both need to be able to mutate explicitly mutable objects, which for the present is just namespaces but this will change.
We can work around the freelist problem by assigning separate freelists for each size of paged-space objects to each processor, that's just sixteen more words. But if a foreign node wants to change a value in a local namespace, then the communications process needs to be able to make that change.
Which means we have to be able to lock objects. Which is something I didn't want to have to do.
## Mutexes
It's part of the underlying philosophy of the post scarcity project that one person can't be expert in every part of the stack. I don't fully understand the subtleties of thread safe locking. In my initial draft of this essay, I was planning to reserve one bit in the tag of an object as a thread lock.
There is a well respected standard thread locking library, [`pthreads`](https://www.cs.cmu.edu/afs/cs/academic/class/15492-f07/www/pthreads.html), part of the [POSIX](https://en.wikipedia.org/wiki/POSIX) standard, which implements thread locks. The lock object it implements is called a `mutex` ('mutual exclusion'), and the size of a `mutex` is... complicated. It is declared as a union:
```c
typedef union
{
struct __pthread_mutex_s __data;
char __size[__SIZEOF_PTHREAD_MUTEX_T];
long int __align;
} pthread_mutex_t;
```
I guessed that the `long int __align` member was intended as a contract that this would be *no bigger* than a `long int`, but `long int` may mean 32 or 64 bits depending on context. The payload is clearly `__pthread_mutex_s`; so how big is that? Answer: it varies, dependent on the hardware architecture. But `__SIZEOF_PTHREAD_MUTEX_T` also varies dependent on architecture, and is defined as 40 *bytes* on 64 bit Intel machines:
```c
#ifdef __x86_64__
# if __WORDSIZE == 64
# define __SIZEOF_PTHREAD_MUTEX_T 40
...
```
The header file I have access to declares that for 32 bit Intel machines it's 32 bytes and for all non-Intel machines the size is only 24 bytes, but
1. the machines I'm working on are actually AMD, but x86 64 bit Intel architecture; and
2. I don't currently have a 64 bit ARM version of this library, and ARM is quite likely to be the architecture I would use for a hardware implementation;
So let's be cautious.
Let's also be realistic: what I'm building now is the 0.1.0 prototype, which is not planned to run on even a simulated hypercube, so it doesn't need to have locks at all. I am crossing a bridge I do not yet strictly need to cross.
## Where to put the lock?
Currently, we have namespaces implemented as hashtables (or hashmaps, if you prefer, but I appreciate that it's old fashioned). We have hashtables implemented as an array of buckets. We have buckets implemented, currently, as association lists (lists of dotted pairs), although they could later be implemented as further hashtables. We can always cons a new `(key . value)` pair onto the front of an association list; the fact that there may be a different binding of the same key further down the association list doesn't matter, except in so far as it slows further searches down that association list.
Changing the pointer to the bucket happens in one clock tick: we're writing one 64 bit word to memory over a 64 bit wide address bus. The replacement bucket can — must! — be prepared in advance. So changing the bucket is pretty much an atomic operation.
But the size of a mutex is uncertain, and **must** fit within the footprint of the namespace object.
Forty bytes is (on a 64 bit machine) five words; but, more relevantly, our `pso_pointer` object is 64 bits irrespective of hardware architecture, so forty bytes is the size of five (pointers to) buckets. This means that namespaces are no longer 'the same' as hashtables; hashtables can accommodate (at least) five more buckets within a given [paged space object](Paged-space-objects.md) size. But obviously we can — the whole paged space objects architecture is predicated on ensuring that we can — accommodate any moderately sized fixed size datum into a paged space object, so we can accommodate a mutex into the footprint of a namespace object.
Oh, but wait.
Oh, but wait, here's a more beautiful idea.
### First class mutexes
We can make the mutex a first class object in paged space in its own right.
This has a number of advantages:
1. the space we need to reserve in the namespace object is just a pointer like any other pointer, and is not implementation dependent;
2. we can change the implementation of the mutex object, if we need to do so when changing architecture, without changing the implementation of anything which relies on a mutex;
3. mutexes then become available as ordinary objects in the Lisp system, to be used by any Lisp functions which need to do thread-safe locking.
So we need a new Lisp function,
```lisp
(with-lock mutex forms...)
```
which, when called
1. waits until it can lock the specified mutex;
2. evaluates each of the forms sequentially in the context of that locked mutex;
3. if evaluation of any of the forms results in the throwing of an exception, catches the exception, unlocks the mutex, and then re-throws the exception;
4. on successful completion of the evaluation of the forms, unlocks the mutex and returns the value of the last form.
This means that I *could* write the bootstrap layer namespace handling code non-thread-safe, and then reimplement it for the user layer in Lisp, thread-safe. But it also means that users could write thread safe handlers for any new types of mutable object they need to define.
### Other types
We don't currently have any other mutable objects, but in future at least lazy objects will be mutable; we may have other things that are mutable. It doesn't seem silly to have a single consistent way to store locks, even if it will only be used in the case of a small minority of objects.
## Procedure for using the lock
### Reading namespaces
Secondly, reading from a namespace does not happen in a single clock tick, it takes quite a long time. So it's no good setting a lock bit on the namespace object itself and then immediately assuming that it's now mutable. A reading process could already have started, and be proceeding.
So what I think is, that we have a single top level function, `(::substrate:search-store key store return-key?)` (which we already sort of have in the 0.0.6 prototype, [here](https://www.journeyman.cc/post-scarcity/doc/html/intern_8c.html#a2189c0ab60e57a70adeb32aca99dbc43)). This searches a store (hashmap, namespace, association list, or hybrid association list) to find a binding for a key, and, having found that binding, then, if there is a namespace on the search path, checks whether the lock on the any namespace on the search path is set, and it it is, aborts the search and tries again; but otherwise returns either the key found (if `return-key?` is non-`nil`), or the value found otherwise.
This function implements the user-level Lisp functions `assoc`, `interned`, and `interned?`. It also implements *hashmap-in-function-position* and *keyword-in-function-position*, in so far as both of these are treated as calls to `assoc`.
### Writing namespaces
When writing to a namespace, top level function [`(::substrate:set key value store)`](https://www.journeyman.cc/post-scarcity/doc/html/intern_8c.html#af8e370c233928d41c268874a6aa5d9e2), we first try to acquire the lock on the namespace. If it is not available, we pause a short time, and try again. It it is clear, we lock it, then identify the right bucket, then cons the new `(key . value)` pair onto the front of the bucket[^1], then update the bucket pointer, and finally unlock the lock.
This function implements the user-level Lisp functions `set` and `set!`.
### Allocating/deallocating objects
When allocating a new object from a freelist... Actually, a lock on the tag of the `car` of the freelist doesn't work here. The lock has to be somewhere else. We could have a single lock for all freelists; that feels like a bad idea because it means e.g. that you can't allocate stack frames while allocating cons cells, and you're bound to get in a mess there. But actually, allocating and deallocating objects of size class 2 — cons cells, integers, other numbers, links in strings, many other small things — is going to be happening all the time, so I'm not sure that it makes much difference. Most of the contention is going to be in size class 2. Nevertheless, one lock per size class is probably not a bad idea, and doesn't take up much space.
So: one lock per freelist.
When allocating *or deallocating* objects, we first try to obtain the lock for the freelist. If it is already locked, wait and try again. If it is clear, lock it, make the necessary change to the freelist, then unlock it.
[^1]: We probably remove any older bindings of the same key from the bucket at this point, too, because it will speed later searches, but this is not critical.

View file

@ -14,3 +14,115 @@
int verbosity = 0; int verbosity = 0;
/**
* @brief print this debug `message` to stderr, if `verbosity` matches `level`.
*
* `verbosity` is a set of flags, see debug_print.h; so you can
* turn debugging on for only one part of the system.
*
* NOTE THAT: contrary to behaviour in the 0.0.X prototypes, a line feed is
* always printed before a debug_print message. Hopefully this will result
* in clearer formatting.
*
* @param message The message to be printed, in *wide* (32 bit) characters.
* @param level a mask for `verbosity`. If a bitwise and of `verbosity` and
* `level` is non-zero, print this `message`, else don't.
* @param indent print `indent` spaces before the message.
*/
void debug_print( wchar_t *message, int level, int indent ) {
#ifdef DEBUG
if ( level & verbosity ) {
fwide( stderr, 1 );
fputws( L"\n", stderr );
for ( int i = 0; i < indent; i++ ) {
fputws( L" ", stderr );
}
fputws( message, stderr );
}
#endif
}
/**
* @brief print a 128 bit integer value to stderr, if `verbosity` matches `level`.
*
* `verbosity` is a set of flags, see debug_print.h; so you can
* turn debugging on for only one part of the system.
*
* stolen from https://stackoverflow.com/questions/11656241/how-to-print-uint128-t-number-using-gcc
*
* @param n the large integer to print.
* @param level a mask for `verbosity`. If a bitwise and of `verbosity` and
* `level` is non-zero, print this `message`, else don't.
*/
void debug_print_128bit( __int128_t n, int level ) {
#ifdef DEBUG
if ( level & verbosity ) {
if ( n == 0 ) {
fwprintf( stderr, L"0" );
} else {
char str[40] = { 0 }; // log10(1 << 128) + '\0'
char *s = str + sizeof( str ) - 1; // start at the end
while ( n != 0 ) {
if ( s == str )
return; // never happens
*--s = "0123456789"[n % 10]; // save last digit
n /= 10; // drop it
}
fwprintf( stderr, L"%s", s );
}
}
#endif
}
/**
* @brief print a line feed to stderr, if `verbosity` matches `level`.
*
* `verbosity` is a set of flags, see debug_print.h; so you can
* turn debugging on for only one part of the system.
*
* @param level a mask for `verbosity`. If a bitwise and of `verbosity` and
* `level` is non-zero, print this `message`, else don't.
*/
void debug_println( int level ) {
#ifdef DEBUG
if ( level & verbosity ) {
fwide( stderr, 1 );
fputws( L"\n", stderr );
}
#endif
}
/**
* @brief `wprintf` adapted for the debug logging system.
*
* Print to stderr only if `verbosity` matches `level`. All other arguments
* as for `wprintf`.
*
* @param level a mask for `verbosity`. If a bitwise and of `verbosity` and
* `level` is non-zero, print this `message`, else don't.
* @param indent print `indent` spaces before the message.
* @param format Format string in *wide characters*, but otherwise as used by
* `printf` and friends.
*
* Remaining arguments should match the slots in the format string.
*/
void debug_printf( int level, int indent, wchar_t *format, ... ) {
#ifdef DEBUG
if ( level & verbosity ) {
fwide( stderr, 1 );
fputws( L"\n", stderr );
for ( int i = 0; i < indent; i++ ) {
fputws( L" ", stderr );
}
va_list( args );
va_start( args, format );
vfwprintf( stderr, format, args );
}
#endif
}
// debug_dump_object, debug_print_binding, debug_print_exception, debug_print_object,
// not yet implemented but probably will be.

View file

@ -93,4 +93,12 @@
*/ */
extern int verbosity; extern int verbosity;
void debug_print( wchar_t *message, int level, int indent );
void debug_print_128bit( __int128_t n, int level );
void debug_println( int level );
void debug_printf( int level, int indent, wchar_t *format, ... );
#endif #endif

View file

@ -10,7 +10,7 @@
#ifndef __psse_memory_header_h #ifndef __psse_memory_header_h
#define __psse_memory_header_h #define __psse_memory_header_h
#include <stdint.h> #include <bits/stdint-uintn.h>
#define TAGLENGTH 3 #define TAGLENGTH 3
@ -25,8 +25,8 @@ struct pso_header {
struct { struct {
/** mnemonic for this type; */ /** mnemonic for this type; */
char mnemonic[TAGLENGTH]; char mnemonic[TAGLENGTH];
/** sizetag for this object */ /** size class for this object */
uint8_t sizetag; uint8_t size_class;
} tag; } tag;
/** the tag considered as a number */ /** the tag considered as a number */
uint32_t value; uint32_t value;

View file

@ -9,11 +9,19 @@
#include <stdio.h> #include <stdio.h>
#include "memory/pointer.h" /**
* @brief Freelists for each size class.
*
* TODO: I don't know if that +1 is needed, my mind gets confused by arrays
* indexed from zero. But it does little harm.
*/
struct pso_pointer freelists[MAX_SIZE_CLASS + 1];
int initialise_memory( int node ) { int initialise_memory( int node ) {
fprintf( stderr, "TODO: Implement initialise_memory()" ); fprintf( stderr, "TODO: Implement initialise_memory()" );
for (uint8_t i = 0; i <= MAX_SIZE_CLASS; i++) {
freelists[i] = nil;S
}
} }

View file

@ -10,10 +10,19 @@
#ifndef __psse_memory_memory_h #ifndef __psse_memory_memory_h
#define __psse_memory_memory_h #define __psse_memory_memory_h
#include "memory/pointer.h" /**
* @brief Maximum size class
*
* Size classes are poweres of 2, in words; so an object of size class 2
* has an allocation size of four words; of size class 3, of eight words,
* and so on. Size classes of 0 and 1 do not work for managed objects,
* since managed objects require a two word header; it's unlikely that
* these undersized size classes will be used at all.
*/
#define MAX_SIZE_CLASS = 0xf
int initialise_memory( ); int initialise_memory( );
extern struct pso_pointer out_of_memory_exception; extern struct pso_pointer out_of_memory_exception;
extern struct pso_pointer freelists[];
#endif #endif

View file

@ -8,8 +8,13 @@
* Licensed under GPL version 2.0, or, at your option, any later version. * Licensed under GPL version 2.0, or, at your option, any later version.
*/ */
#include "memory/memory.h" #include "node.h"
#include "memory/pointer.h"
#include <bits/stdint-uintn.h>
#include "ops/equal.h"
#include "memory.h"
#include "pointer.h"
/** /**
* @brief Flag to prevent the node being initialised more than once. * @brief Flag to prevent the node being initialised more than once.
@ -20,6 +25,9 @@ bool node_initialised = false;
/** /**
* @brief The index of this node in the hypercube. * @brief The index of this node in the hypercube.
* *
* TODO: once we have a hypercube, this must be set to the correct value
* IMMEDIATELY on startup, before starting to initalise any other part of
* the Lisp system.
*/ */
uint32_t node_index = 0; uint32_t node_index = 0;
@ -36,7 +44,7 @@ struct pso_pointer nil = struct pso_pointer{ 0, 0, 0};
struct pso_pointer t = struct pso_pointer { 0, 0, 1 }; struct pso_pointer t = struct pso_pointer { 0, 0, 1 };
/** /**
* @brief Set up the basic informetion about this node * @brief Set up the basic informetion about this node.
* *
* @param index * @param index
* @return struct pso_pointer * @return struct pso_pointer
@ -45,10 +53,11 @@ struct pso_pointer initialise_node( uint32_t index) {
node_index = index; node_index = index;
nil = pso_pointer { index, 0, 0}; nil = pso_pointer { index, 0, 0};
t = pso_pointer( index, 0, 1 ); t = pso_pointer( index, 0, 1 );
pso_pointer result = initialise_memory( index ); pso_pointer result = initialise_memory( index );
if ( eq( result, t ) ) { if ( eq( result, t ) ) {
result = initialise_environment(); result = initialise_environment( index );
} }
return result; return result;

View file

@ -7,8 +7,11 @@
* Licensed under GPL version 2.0, or, at your option, any later version. * Licensed under GPL version 2.0, or, at your option, any later version.
*/ */
#include <math.h>
#include <stdint.h> #include <stdint.h>
#include "memory/memory.h"
#include "memory/node.h"
#include "memory/page.h" #include "memory/page.h"
#include "memory/pso2.h" #include "memory/pso2.h"
#include "memory/pso3.h" #include "memory/pso3.h"
@ -24,12 +27,103 @@
#include "memory/psod.h" #include "memory/psod.h"
#include "memory/psoe.h" #include "memory/psoe.h"
#include "memory/psof.h" #include "memory/psof.h"
#include "payloads/free.h"
void * malloc_page( uint8_t size_class) { /**
void * result = malloc( sizeof( page)); * @brief The pages which have so far been initialised.
*
* TODO: This is temporary. We cannot afford to allocate an array big enough
* to hold the number of pages we *might* create at start up time. We need a
* way to grow the number of pages, while keeping access to them cheap.
*/
struct page * pages[NPAGES];
if (result != NULL) { /**
* @brief the number of pages which have thus far been allocated.
*
*/
uint32_t npages_allocated = 0
struct cons_pointer initialise_page( struct page * result, uint16_t page_index, uint8_t size_class, pso_pointer freelist) {
struct cons_pointer result = freelist;
int obj_size = pow(2, size_class);
int obj_bytes = obj_size * sizeof(uint64_t);
int objs_in_page = PAGE_BYTES/obj_bytes;
for (int i = objs_in_page - 1; i >= 0; i--) {
// it should be safe to cast any pso object to a pso2
struct pso2* object = (pso2 *)(result + (i * obj_bytes));
object->header.tag.size_class = size_class;
strncpy( (char *)(object->header.tag.mnemonic), FREETAG, TAGLENGTH);
object->payload.free.next = result;
result = make_pointer( node_index, page_index, (uint16_t)( i * obj_size));
}
return result;
}
/**
* @brief Allocate a page for objects of this size class, initialise it, and
* link the objects in it into the freelist for this size class.
*
* Because we can't return an exception at this low level, and because there
* are multiple possible causes of failure, for the present this function will
* print errors to stderr. We cast the error stream to wide, since we've
* probably (but not certainly) already cast it to wide, and we can't reliably
* cast it back.
*
* @param size_class an integer in the range 0...MAX_SIZE_CLASS.
* @return a pointer to the page, or NULL if an error occurred.
*/
void *allocate_page( uint8_t size_class ) {
void *result = NULL;
if ( npages_allocated == 0) {
for (int i = 0; i < NPAGES; i++) {
pages[i] = NULL;
}
debug_print( L"Pages array zeroed.\n", DEBUG_ALLOC, 0);
}
if ( npages_allocated < NPAGES) {
if ( size_class >= 2 && size_class <= MAX_SIZE_CLASS ) {
result = malloc( sizeof( page ) );
if ( result != NULL ) {
memset( result, 0, sizeof( page ) );
pages[ npages_allocated] = result;
debug_printf( DEBUG_ALLOC, 0,
L"Allocated page %d for objects of size class %x.\n",
npages_allocated, size_class);
freelists[size_class] =
initialise_page( result, npages_allocated, size_class, freelists[size_class] );
debug_printf( DEBUG_ALLOC, 0,
L"Initialised page %d; freelist for size class %x updated.\n",
npages_allocated,
size_class);
npages_allocated ++;
} else {
fwide( stderr, 1 );
fwprintf( stderr,
L"\nCannot allocate page: heap exhausted,\n",
size_class, MAX_SIZE_CLASS );
}
} else {
fwide( stderr, 1 );
fwprintf( stderr,
L"\nCannot allocate page for size class %x, min is 2 max is %x.\n",
size_class, MAX_SIZE_CLASS );
}
} else {
fwide( stderr, 1 );
fwprintf( stderr,
L"\nCannot allocate page: page space exhausted.\n",
size_class, MAX_SIZE_CLASS );
} }
return result; return result;

View file

@ -25,7 +25,20 @@
#include "memory/psoe.h" #include "memory/psoe.h"
#include "memory/psof.h" #include "memory/psof.h"
#define PAGE_SIZE 1048576 /**
* the size of a page, **in bytes**.
*/
#define PAGE_BYTES 1048576
/**
* the number of pages we will initially allow for. For
* convenience we'll set up an array of cons pages this big; however,
* TODO: later we will want a mechanism for this to be able to grow
* dynamically to the maximum we can allow.
*/
#define NPAGES 64
extern struct page *pages[NPAGES];
/** /**
* @brief A page is a megabyte of memory which contains objects all of which * @brief A page is a megabyte of memory which contains objects all of which
@ -40,22 +53,22 @@
* collection they will be returned to that freelist. * collection they will be returned to that freelist.
*/ */
union page { union page {
uint8_t[PAGE_SIZE] bytes; uint8_t[PAGE_BYTES] bytes;
uint64_t[PAGE_SIZE / 8] words; uint64_t[PAGE_BYTES / 8] words;
struct pso2[PAGE_SIZE / 32] pso2s; struct pso2[PAGE_BYTES / 32] pso2s;
struct pso3[PAGE_SIZE / 64] pso3s; struct pso3[PAGE_BYTES / 64] pso3s;
struct pso4[PAGE_SIZE / 128] pso4s; struct pso4[PAGE_BYTES / 128] pso4s;
struct pso5[PAGE_SIZE / 256] pso5s; struct pso5[PAGE_BYTES / 256] pso5s;
struct pso6[PAGE_SIZE / 512] pso6s; struct pso6[PAGE_BYTES / 512] pso6s;
struct pso7[PAGE_SIZE / 1024] pso7s; struct pso7[PAGE_BYTES / 1024] pso7s;
struct pso8[PAGE_SIZE / 2048] pso8s; struct pso8[PAGE_BYTES / 2048] pso8s;
struct pso9[PAGE_SIZE / 4096] pso9s; struct pso9[PAGE_BYTES / 4096] pso9s;
struct psoa[PAGE_SIZE / 8192] psoas; struct psoa[PAGE_BYTES / 8192] psoas;
struct psob[PAGE_SIZE / 16384] psobs; struct psob[PAGE_BYTES / 16384] psobs;
struct psoc[PAGE_SIZE / 32768] psocs; struct psoc[PAGE_BYTES / 32768] psocs;
struct psod[PAGE_SIZE / 65536] psods; struct psod[PAGE_BYTES / 65536] psods;
struct psoe[PAGE_SIZE / 131072] psoes; struct psoe[PAGE_BYTES / 131072] psoes;
struct psof[PAGE_SIZE / 262144] psofs; struct psof[PAGE_BYTES / 262144] psofs;
}; };
#endif #endif

47
src/c/memory/pointer.c Normal file
View file

@ -0,0 +1,47 @@
/**
* memory/pointer.h
*
* A pointer to a paged space object.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#include "memory/node.h"
#include "memory/pointer.h"
#include "memory/pso.h"
/**
* @brief Make a pointer to a paged-space object.
*
* @param node The index of the node on which the object is curated;
* @param page The memory page in which the object resides;
* @param offset The offset, in words, within that page, of the object.
* @return struct pso_pointer a pointer referencing the specified object.
*/
struct pso_pointer make_pointer( uint32_t node, uint16_t page, uint16_t offset) {
return struct pso_pointer{ node, page, pointer};
}
/**
* @brief returns the in-memory address of the object indicated by this
* pointer. TODO: Yhe reason I'm doing it this way is because I'm not
* certain reference counter updates work right it we work with 'the object'
* rather than 'the address of the object'. I really ought to have a
* conversation with someone who understands this bloody language.
*
* @param pointer a pso_pointer which references an object.
* @return struct pso2* the actual address in memory of that object.
*/
struct pso2* pointer_to_object( struct pso_pointer pointer) {
struct pso2* result = NULL;
if ( pointer.node == node_index) {
result = (struct pso2*) &(pages[pointer.node] + (pointer.offset * sizeof( uint64_t)));
}
// TODO: else if we have a copy of the object in cache, return that;
// else request a copy of the object from the node which curates it.
return result;
}

View file

@ -38,4 +38,6 @@ struct pso_pointer {
uint16_t offset; uint16_t offset;
}; };
struct pso_pointer make_pointer( uint32_t node, uint16_t page, uint16_t offset);
#endif #endif

47
src/c/memory/pso.c Normal file
View file

@ -0,0 +1,47 @@
/**
* 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.
*/
struct cons_pointer allocate( char* tag, uint8_t size_class) {
struct cons_pointer result = nil;
if (size_class <= MAX_SIZE_CLASS) {
if ( not( freelists[size_class] ) ) {
result = freelists[size_class];
struct pso2* object = pointer_to_object( result);
freelists[size_class] = object->payload.free.next;
strncpy( (char *)(object->header.tag.mnemonic), tag, TAGLENGTH);
/* 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.size_class != size_class) {
// TODO: return an exception instead? Or warn, set it, and continue?
}
/* 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) {
// TODO: return an exception instead? Or warn, set it, and continue?
}
}
} // TODO: else throw exception
return result;
}
struct cons_pointer get_tag_value( struct cons_pointer pointer) {
result =
}

240
src/c/memory/pso.h Normal file
View file

@ -0,0 +1,240 @@
/**
* memory/pso.h
*
* Paged space objects.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso_h
#define __psse_memory_pso_h
#include <stdint.h>
#include "memory/header.h"
#include "payloads/cons.h"
#include "payloads/free.h"
#include "payloads/function.h"
#include "payloads/integer.h"
#include "payloads/ketwod.h"
#include "payloads/lambda.h"
#include "payloads/mutex.h"
#include "payloads/nlambda.h"
#include "payloads/read_stream.h"
#include "payloads/special.h"
#include "payloads/stack.h"
#include "payloads/string.h"
#include "payloads/symbol.h"
#include "payloads/time.h"
#include "payloads/vector_pointer.h"
#include "payloads/write_stream.h"
/**
* @brief A paged space object of size class 2, four words total, two words
* payload.
*
*/
struct pso2 {
struct pso_header header;
union {
char[16] bytes;
uint64_t[2] words;
struct cons_payload cons;
struct free_payload free;
struct function_payload function;
struct integer_payload integer;
struct lambda_payload lambda;
struct special_payload special;
struct stream_payload stream;
struct time_payload time;
struct vectorp_payload vectorp;
} payload;
};
/**
* @brief A paged space object of size class 3, 8 words total, 6 words
* payload.
*
*/
struct pso3 {
struct pso_header header;
union {
char[48] bytes;
uint64_t[6] words;
struct exception_payload exception;
struct free_payload free;
struct mutex_payload mutex;
} payload;
};
/**
* @brief A paged space object of size class 4, 16 words total, 14 words
* payload.
*
*/
struct pso4 {
struct pso_header header;
union {
char[112] bytes;
uint64_t[14] words;
struct free_payload free;
struct stack_frame_payload stack_frame;
} payload;
};
/**
* @brief A paged space object of size class 5, 32 words total, 30 words
* payload.
*
*/
struct pso5 {
struct pso_header header;
union {
char[240] bytes;
uint64_t[30] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class 6, 64 words total, 62 words
* payload.
*
*/
struct pso6 {
struct pso_header header;
union {
char[496] bytes;
uint64_t[62] words;
struct free_payload free;
struct hashtable_payload hashtable;
struct namespace_payload namespace;
} payload;
};
/**
* @brief A paged space object of size class 7, 128 words total, 126 words
* payload.
*
*/
struct pso7 {
struct pso_header header;
union {
char[1008] bytes;
uint64_t[126] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class 8, 256 words total, 254 words
* payload.
*
*/
struct pso8 {
struct pso_header header;
union {
char[2032] bytes;
uint64_t[254] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class 9, 512 words total, 510 words
* payload.
*
*/
struct pso9 {
struct pso_header header;
union {
char[4080] bytes;
uint64_t[510] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class a, 1024 words total, 1022 words
* payload.
*
*/
struct psoa {
struct pso_header header;
union {
char[8176] bytes;
uint64_t[1022] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class b, 2048 words total, 2046 words
* payload.
*
*/
struct psob {
struct pso_header header;
union {
char[16368] bytes;
uint64_t[2046] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class c, 4096 words total, 4094 words
* payload.
*
*/
struct psoc {
struct pso_header header;
union {
char[32752] bytes;
uint64_t[4094] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class d, 8192 words total, 8190 words
* payload.
*
*/
struct psod {
struct pso_header header;
union {
char[65520] bytes;
uint64_t[8190] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class e, 16384 words total, 16382 words
* payload.
*
*/
struct psoe {
struct pso_header header;
union {
char[131056] bytes;
uint64_t[16382] words;
struct free_payload free;
} payload;
};
/**
* @brief A paged space object of size class f, 32768 words total, 32766 words
* payload.
*
*/
struct psof {
struct pso_header header;
union {
char[262128] bytes;
uint64_t[32766] words;
struct free_payload free;
} payload;
};

View file

@ -1,53 +0,0 @@
/**
* memory/pso2.h
*
* Paged space object of size class 2, four words total, two words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso2_h
#define __psse_memory_pso2_h
#include <stdint.h>
#include "memory/header.h"
#include "payloads/cons.h"
#include "payloads/free.h"
#include "payloads/function.h"
#include "payloads/integer.h"
#include "payloads/ketwod.h"
#include "payloads/lambda.h"
#include "payloads/nlambda.h"
#include "payloads/read_stream.h"
#include "payloads/special.h"
#include "payloads/string.h"
#include "payloads/symbol.h"
#include "payloads/time.h"
#include "payloads/vector_pointer.h"
#include "payloads/write_stream.h"
/**
* @brief A paged space object of size class 2, four words total, two words
* payload.
*
*/
struct pso2 {
struct pso_header header;
union {
char[16] bytes;
uint64_t[2] words;
struct cons_payload cons;
struct free_payload free;
struct function_payload function;
struct integer_payload integer;
struct lambda_payload lambda;
struct special_payload special;
struct stream_payload stream;
struct time_payload time;
struct vectorp_payload vectorp;
} payload;
};
#endif

View file

@ -1,35 +0,0 @@
/**
* memory/pso3.h
*
* Paged space object of size class 3, 8 words total, 6 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso3_h
#define __psse_memory_pso3_h
#include <stdint.h>
#include "memory/header.h"
#include "payloads/exception.h"
#include "payloads/free.h"
/**
* @brief A paged space object of size class 3, 8 words total, 6 words
* payload.
*
*/
struct pso3 {
struct pso_header header;
union {
char[48] bytes;
uint64_t[6] words;
struct exception_payload exception;
struct free_payload free;
} payload;
};
#endif

View file

@ -1,32 +0,0 @@
/**
* memory/pso4.h
*
* Paged space object of size class 4, 16 words total, 14 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso4_h
#define __psse_memory_pso4_h
#include <stdint.h>
#include "memory/header.h"
#include "memory/stack.h"
/**
* @brief A paged space object of size class 4, 16 words total, 14 words
* payload.
*
*/
struct pso4 {
struct pso_header header;
union {
char[112] bytes;
uint64_t[14] words;
struct stack_frame_payload stack_frame;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/pso5.h
*
* Paged space object of size class 5, 32 words total, 30 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso5_h
#define __psse_memory_pso5_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class 5, 32 words total, 30 words
* payload.
*
*/
struct pso5 {
struct pso_header header;
union {
char[240] bytes;
uint64_t[30] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/pso6.h
*
* Paged space object of size class 6, 64 words total, 62 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso6_h
#define __psse_memory_pso6_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class 6, 64 words total, 62 words
* payload.
*
*/
struct pso6 {
struct pso_header header;
union {
char[496] bytes;
uint64_t[62] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/pso7.h
*
* Paged space object of size class 7, 128 words total, 126 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso7_h
#define __psse_memory_pso7_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class 7, 128 words total, 126 words
* payload.
*
*/
struct pso7 {
struct pso_header header;
union {
char[1008] bytes;
uint64_t[126] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/pso8.h
*
* Paged space object of size class 8, 256 words total, 254 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso8_h
#define __psse_memory_pso8_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class 8, 256 words total, 254 words
* payload.
*
*/
struct pso8 {
struct pso_header header;
union {
char[2032] bytes;
uint64_t[254] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/pso9.h
*
* Paged space object of size class 9, 512 words total, 510 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_pso9_h
#define __psse_memory_pso9_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class 9, 512 words total, 510 words
* payload.
*
*/
struct pso9 {
struct pso_header header;
union {
char[4080] bytes;
uint64_t[510] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/psoa.h
*
* Paged space object of size class a, 1024 words total, 1022 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_psoa_h
#define __psse_memory_psoa_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class a, 1024 words total, 1022 words
* payload.
*
*/
struct psoa {
struct pso_header header;
union {
char[8176] bytes;
uint64_t[1022] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/psob.h
*
* Paged space object of size class b, 2048 words total, 2046 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_psob_h
#define __psse_memory_psob_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class b, 2048 words total, 2046 words
* payload.
*
*/
struct psob {
struct pso_header header;
union {
char[16368] bytes;
uint64_t[2046] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/psoc.h
*
* Paged space object of size class c, 4096 words total, 4094 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_psoc_h
#define __psse_memory_psoc_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class c, 4096 words total, 4094 words
* payload.
*
*/
struct psoc {
struct pso_header header;
union {
char[32752] bytes;
uint64_t[4094] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/psod.h
*
* Paged space object of size class d, 8192 words total, 8190 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_psod_h
#define __psse_memory_psod_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class d, 8192 words total, 8190 words
* payload.
*
*/
struct psod {
struct pso_header header;
union {
char[65520] bytes;
uint64_t[8190] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/psoe.h
*
* Paged space object of size class e, 16384 words total, 16382 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_psoe_h
#define __psse_memory_psoe_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class e, 16384 words total, 16382 words
* payload.
*
*/
struct psoe {
struct pso_header header;
union {
char[131056] bytes;
uint64_t[16382] words;
} payload;
};
#endif

View file

@ -1,30 +0,0 @@
/**
* memory/psof.h
*
* Paged space object of size class f, 32768 words total, 32766 words payload.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_memory_psof_h
#define __psse_memory_psof_h
#include <stdint.h>
#include "memory/header.h"
/**
* @brief A paged space object of size class f, 32768 words total, 32766 words
* payload.
*
*/
struct psof {
struct pso_header header;
union {
char[262128] bytes;
uint64_t[32766] words;
} payload;
};
#endif

View file

@ -26,7 +26,8 @@
* @param env the evaluation environment. * @param env the evaluation environment.
* @return struct pso_pointer * @return struct pso_pointer
*/ */
struct pso_pointer eval_despatch( struct stack_frame *frame, struct pso_pointer frame_pointer, struct pso_pointer eval_despatch( struct stack_frame *frame,
struct pso_pointer frame_pointer,
struct pso_pointer env ) { struct pso_pointer env ) {
struct pso_pointer result = frame->arg[0]; struct pso_pointer result = frame->arg[0];
@ -52,7 +53,8 @@ struct pso_pointer eval_despatch( struct stack_frame *frame, struct pso_pointer
return result; return result;
} }
struct pso_pointer lisp_eval( struct stack_frame *frame, struct pso_pointer frame_pointer, struct pso_pointer lisp_eval( struct stack_frame *frame,
struct pso_pointer frame_pointer,
struct pso_pointer env ) { struct pso_pointer env ) {
struct pso_pointer result = eval_despatch( frame, frame_pointer, env ); struct pso_pointer result = eval_despatch( frame, frame_pointer, env );

94
src/c/ops/truth.c Normal file
View file

@ -0,0 +1,94 @@
/**
* ops/truth.c
*
* Post Scarcity Software Environment: nil? true? not.
*
* Functions associated with truthiness.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
/**
* @brief true if `p` points to `nil`, else false.
*
* Note that every node has its own copy of `t` and `nil`, and each instance of
* each is considered equivalent. So we don't check the node when considering
* whether `nil` really is `nil`, or `t` really is `t`.
*
* @param p a pointer
* @return true if `p` points to `nil`.
* @return false otherwise.
*/
bool nilp( struct pso_pointer p) {
return (p.page == 0 && p.offset = 0);
}
/**
* @brief Return `true` if `p` points to `nil`, else `false`.
*
* @param p a pointer
* @return true if `p` points to `nil`;
* @return false otherwise.
*/
bool not( struct pso_pointer p) {
return !nilp( p);
}
/**
* @brief `true` if `p` points to `t`, else `false`.
*
* Note that every node has its own copy of `t` and `nil`, and each instance of
* each is considered equivalent. So we don't check the node when considering
* whether `nil` really is `nil`, or `t` really is `t`.
*
* @param p a pointer
* @return true if `p` points to `t`.
* @return false otherwise.
*/
bool truep( struct pso_pointer p) {
return (p.page == 0 && p.offset = 1);
}
/**
* @brief return `t` if the first argument in this frame is `nil`, else `t`.
*
* @param frame The current stack frame;
* @param frame_pointer A pointer to the current stack frame;
* @param env the evaluation environment.
* @return `t` if the first argument in this frame is `nil`, else `t`
*/
pso_pointer lisp_nilp( struct stack_frame *frame,
struct pso_pointer frame_pointer,
struct pso_pointer env ){
return (nilp(frame->arg[0]) ? t : nil);
}
/**
* @brief return `t` if the first argument in this frame is `t`, else `nil`.
*
* @param frame The current stack frame;
* @param frame_pointer A pointer to the current stack frame;
* @param env the evaluation environment.
* @return `t` if the first argument in this frame is `t`, else `nil`.
*/
pso_pointer lisp_truep( struct stack_frame *frame,
struct pso_pointer frame_pointer,
struct pso_pointer env ){
return (truep(frame->arg[0]) ? t : nil);
}
/**
* @brief return `t` if the first argument in this frame is not `nil`, else
* `t`.
*
* @param frame The current stack frame;
* @param frame_pointer A pointer to the current stack frame;
* @param env the evaluation environment.
* @return `t` if the first argument in this frame is not `nil`, else `t`.
*/
pso_pointer lisp_not( struct stack_frame *frame,
struct pso_pointer frame_pointer,
struct pso_pointer env ){
return (not(frame->arg[0]) ? t : nil);
}

33
src/c/ops/truth.h Normal file
View file

@ -0,0 +1,33 @@
/**
* ops/truth.h
*
* Post Scarcity Software Environment: truth functions.
*
* Tests for truth.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_ops_truth_h
#define __psse_ops_truth_h
bool nilp( struct pso_pointer a, struct pso_pointer b );
struct pso_pointer lisp_nilp( struct stack_frame *frame,
struct pso_pointer frame_pointer,
struct pso_pointer env );
bool not( struct pso_pointer a, struct pso_pointer b );
struct pso_pointer lisp_not( struct stack_frame *frame,
struct pso_pointer frame_pointer,
struct pso_pointer env );
bool truep( struct pso_pointer a, struct pso_pointer b );
struct pso_pointer lisp_truep( struct stack_frame *frame,
struct pso_pointer frame_pointer,
struct pso_pointer env );
#endif

View file

@ -0,0 +1,40 @@
/**
* payloads/hashtable.h
*
* an ordinary Lisp hashtable - one whose contents are immutable.
*
* Can sensibly sit in any pso from size class 6 upwards.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_payloads_hashtable_h
#define __psse_payloads_hashtable_h
#include "memory/pointer.h"
/**
* @brief Tag for an ordinary Lisp hashtable - one whose contents are immutable.
* \see NAMESPACETAG for mutable hashtables.
*/
#define HASHTABLETAG "HTB"
/**
* The payload of a hashtable. The number of buckets is assigned at run-time,
* and is stored in n_buckets. Each bucket is something ASSOC can consume:
* i.e. either an assoc list or a further hashtable.
*/
struct hashtable_payload {
struct cons_pointer hash_fn; /* function for hashing values in this hashtable, or `NIL` to use
the default hashing function */
struct cons_pointer write_acl; /* it seems to me that it is likely that the
* principal difference between a hashtable and a
* namespace is that a hashtable has a write ACL
* of `NIL`, meaning not writeable by anyone */
uint32_t n_buckets; /* number of hash buckets */
uint32_t unused; /* for word alignment and possible later expansion */
struct cons_pointer buckets[]; /* actual hash buckets, which should be `NIL`
* or assoc lists or (possibly) further hashtables. */
};
#endif

66
src/c/payloads/mutex.h Normal file
View file

@ -0,0 +1,66 @@
/**
* payloads/mutex.h
*
* A mutex (mutual exclusion lock) cell. Requires a size class 3 object.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_payloads_mutex_h
#define __psse_payloads_mutex_h
#include <pthread.h>
#include "memory/pointer.h"
/**
* @brief Tag for mutex cell. mutexes are thread-safe locks, required by
* mutable objects.
* \see FUNCTIONTAG.
*/
#define MUTEXTAG "MTX"
/**
* @brief payload for mutex objects.
*
* NOTE that the size of `pthread_mutex_t` is variable dependent on hardware
* architecture, but the largest known size is 40 bytes (five words).
*/
struct mutex_payload {
pthread_mutex_t mutex;
}
struct pso_pointer make_mutex();
/**
* @brief evaluates these forms within the context of a thread-safe lock.
*
* 1. wait until the specified mutex can be locked;
* 2. evaluate each of the forms sequentially in the context of that locked
* mutex;
* 3. if evaluation of any of the forms results in the throwing of an
* exception, catch the exception, unlock the mutex, and then re-throw the
* exception;
* 4. on successful completion of the evaluation of the forms, unlock the mutex
* and return the value of the last form.
*
* @param lock the lock: a mutex (MTX) object;
* @param forms a list of arbitrary Lisp forms.
* @return struct pso_pointer the result.
*/
struct pso_pointer with_lock( struct pso_pointer lock, struct pso_pointer forms);
/**
* @brief as with_lock, q.v. but attempts to obtain a lock and returns an
* exception on failure
*
* 1. attempt to lock the specified mutex;
* 2. if successful, proceed as `with_lock`;
* 3. otherwise, return a specific exception which can be trapped for.
*
* @param lock the lock: a mutex (MTX) object;
* @param forms a list of arbitrary Lisp forms.
* @return struct pso_pointer the result.
*/
struct pso_pointer attempt_with_lock( struct pso_pointer lock, struct pso_pointer forms);

View file

@ -0,0 +1,42 @@
/**
* payloads/namespace.h
*
* a Lisp namespace - a hashtable whose contents are mutable.
*
* Can sensibly sit in any pso from size class 6 upwards.
*
* (c) 2026 Simon Brooke <simon@journeyman.cc>
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
#ifndef __psse_payloads_namespace_h
#define __psse_payloads_namespace_h
#include "memory/pointer.h"
/**
* @brief Tag for a Lisp namespace - a hashtable whose contents are mutable.
* \see HASHTABLETAG for mutable hashtables.
*/
#define NAMESPACETAG "NSP"
/**
* The payload of a namespace. The number of buckets is assigned at run-time,
* and is stored in n_buckets. Each bucket is something ASSOC can consume:
* i.e. either an assoc list or a further namespace.
*/
struct namespace_payload {
struct cons_pointer hash_fn; /* function for hashing values in this namespace, or
* `NIL` to use the default hashing function */
struct cons_pointer write_acl; /* it seems to me that it is likely that the
* principal difference between a hashtable and a
* namespace is that a hashtable has a write ACL
* of `NIL`, meaning not writeable by anyone */
struct cons_pointer mutex; /* the mutex to lock when modifying this namespace.*/
uint32_t n_buckets; /* number of hash buckets */
uint32_t unused; /* for word alignment and possible later expansion */
struct cons_pointer buckets[]; /* actual hash buckets, which should be `NIL`
* or assoc lists or (possibly) further hashtables. */
};
#endif