feature-2: allocating cells with count = 1; 7 unit tests (all bignums) fail.

This commit is contained in:
Simon Brooke 2026-02-12 10:17:11 +00:00
parent 351ca5bd17
commit 004ff6737c
10 changed files with 209 additions and 114 deletions

View file

@ -19,12 +19,13 @@
#include <wchar.h>
#include <wctype.h>
#include "arith/integer.h"
#include "arith/peano.h"
#include "debug.h"
#include "memory/conspage.h"
#include "memory/consspaceobject.h"
#include "debug.h"
#include "ops/equal.h"
#include "ops/lispops.h"
#include "arith/peano.h"
/**
* hexadecimal digits for printing numbers.
@ -34,19 +35,33 @@ const char *hex_digits = "0123456789ABCDEF";
/*
* Doctrine from here on in is that ALL integers are bignums, it's just
* that integers less than 61 bits are bignums of one cell only.
* that integers less than 61 bits are bignums of one cell only.
* TODO: why do I not have confidence to make this 64 bits?
*/
/*
* A small_int_cache array of pointers to the integers 0...23,
* used only by functions `acquire_integer(int64) => cons_pointer` and
* `release_integer(cons_pointer) => NULL` which, if the value desired is
* in the cache, supplies it from the cache, and, otherwise, calls
* make_integer() and dec_ref() respectively.
*/
#define SMALL_INT_LIMIT 24
bool small_int_cache_initialised = false;
struct cons_pointer small_int_cache[SMALL_INT_LIMIT];
/**
* Low level integer arithmetic, do not use elsewhere.
*
* @param c a pointer to a cell, assumed to be an integer cell;
* @param op a character representing the operation: expectedto be either
* '+' or '*'; behaviour with other values is undefined.
* @param is_first_cell true if this is the first cell in a bignum
* chain, else false.
* \see multiply_integers
* \see add_integers
*/
* Low level integer arithmetic, do not use elsewhere.
*
* @param c a pointer to a cell, assumed to be an integer cell;
* @param op a character representing the operation: expectedto be either
* '+' or '*'; behaviour with other values is undefined.
* @param is_first_cell true if this is the first cell in a bignum
* chain, else false.
* \see multiply_integers
* \see add_integers
*/
__int128_t cell_value( struct cons_pointer c, char op, bool is_first_cell ) {
long int val = nilp( c ) ? 0 : pointer2cell( c ).payload.integer.value;
@ -86,7 +101,6 @@ struct cons_pointer make_integer( int64_t value, struct cons_pointer more ) {
struct cons_space_object *cell = &pointer2cell( result );
cell->payload.integer.value = value;
cell->payload.integer.more = more;
inc_ref(result);
}
debug_print( L"make_integer: returning\n", DEBUG_ALLOC );
@ -95,11 +109,74 @@ struct cons_pointer make_integer( int64_t value, struct cons_pointer more ) {
}
/**
* Overwrite the value field of the integer indicated by `new` with
* @brief Supply small valued integers from the small integer cache, if available.
*
* The pattern here is intended to be that, at least within this file, instead of
* calling make_integer when an integer is required and dec_ref when it's no longer
* required, we call acquire_integer and release_integer respectively, in order to
* reduce allocation churn.
*
* In the initial implementation, acquire_integer supplies the integer from the
* small integer cache if available, else calls make_integer. Later, more
* sophisticated caching of integers which are currently in play may be enabled.
*
* @param value the value of the integer desired.
* @param more if this value is a bignum, the rest (less significant bits) of the
* value.
* @return struct cons_pointer a pointer to the integer acquired.
*/
struct cons_pointer acquire_integer( int64_t value, struct cons_pointer more ) {
struct cons_pointer result;
if ( !nilp( more) || value >= SMALL_INT_LIMIT) {
debug_print( L"acquire_integer passing to make_integer (too large)\n", DEBUG_ALLOC );
result = make_integer( value, more);
} else {
if ( !small_int_cache_initialised) {
for (int64_t i = 0; i < SMALL_INT_LIMIT; i++) {
small_int_cache[i] = make_integer( i, NIL);
pointer2cell(small_int_cache[i]).count = UINT32_MAX; // lock it in so it can't be GC'd
}
small_int_cache_initialised = true;
debug_print( L"small_int_cache initialised.\n", DEBUG_ALLOC );
}
debug_printf( DEBUG_ALLOC, L"acquire_integer: returning %" PRId64 "\n", value);
result = small_int_cache[value];
}
return result;
}
/**
* @brief if the value of p is less than the size of the small integer cache
* (and thus it was presumably supplied from there), suppress dec_ref.
*
* **NOTE THAT** at this stage it's still safe to dec_ref an arbitrary integer,
* because those in the cache are locked and can't be dec_refed.
*
* @param p a pointer, expected to be to an integer.
*/
void release_integer( struct cons_pointer p) {
struct cons_space_object o = pointer2cell( p);
if ( !integerp( p) || // what I've been passed isn't an integer;
!nilp( o.payload.integer.more) || // or it's a bignum;
o.payload.integer.value >= SMALL_INT_LIMIT || // or it's bigger than the small int cache limit;
!eq( p, small_int_cache[ o.payload.integer.value]) // or it's simply not the copy in the cache...
) { dec_ref( p); } else {
debug_printf( DEBUG_ALLOC, L"release_integer: releasing %" PRId64 "\n",
o.payload.integer.value);
}
}
/**
* @brief Overwrite the value field of the integer indicated by `new` with
* the least significant INTEGER_BITS bits of `val`, and return the
* more significant bits (if any) right-shifted by INTEGER_BITS places.
* Destructive, primitive, do not use in any context except primitive
* operations on integers.
* more significant bits (if any) right-shifted by INTEGER_BITS places.
*
* Destructive, primitive, DO NOT USE in any context except primitive
* operations on integers. The value passed as `new` MUST be constructed
* with `make_integer`, NOT acquired with `acquire_integer`.
*
* @param val the value to represent;
* @param less_significant the less significant words of this bignum, if any,
@ -134,25 +211,6 @@ __int128_t int128_to_integer( __int128_t val,
return carry;
}
struct cons_pointer make_integer_128( __int128_t val,
struct cons_pointer less_significant ) {
struct cons_pointer result = NIL;
do {
if ( MAX_INTEGER >= val ) {
result = make_integer( ( long int ) val, less_significant );
} else {
less_significant =
make_integer( ( long int ) val & MAX_INTEGER,
less_significant );
val = val * INT_CELL_BASE;
}
} while ( nilp( result ) );
return result;
}
/**
* Return a pointer to an integer representing the sum of the integers
* pointed to by `a` and `b`. If either isn't an integer, will return nil.
@ -218,28 +276,38 @@ struct cons_pointer base_partial( int depth ) {
struct cons_pointer result = NIL;
for ( int i = 0; i < depth; i++ ) {
result = make_integer( 0, result );
result = acquire_integer( 0, result );
}
return result;
}
/**
* destructively modify this `partial` by appending this `digit`.
* @brief Return a copy of this `partial` with this `digit` appended.
*
* @param partial the more significant bits of a possible bignum.
* @param digit the less significant bits of that possible bignum. NOTE: the
* name `digit` is technically correct but possibly misleading, because the
* numbering system here is base INT_CELL_BASE, currently x0fffffffffffffffL
*/
struct cons_pointer append_digit( struct cons_pointer partial,
struct cons_pointer append_cell( struct cons_pointer partial,
struct cons_pointer digit ) {
struct cons_pointer c = partial;
struct cons_space_object cell = pointer2cell( partial);
// TODO: I should recursively copy the whole bignum chain, because
// we're still destructively modifying the end of it.
struct cons_pointer c = make_integer( cell.payload.integer.value,
cell.payload.integer.more);
struct cons_pointer result = partial;
if ( nilp( partial ) ) {
if ( nilp( partial)) {
result = digit;
} else {
// find the last digit in the chain...
while ( !nilp( pointer2cell( c ).payload.integer.more ) ) {
c = pointer2cell( c ).payload.integer.more;
}
( &pointer2cell( c ) )->payload.integer.more = digit;
( pointer2cell( c ) ).payload.integer.more = digit;
}
return result;
}
@ -259,7 +327,7 @@ struct cons_pointer append_digit( struct cons_pointer partial,
*/
struct cons_pointer multiply_integers( struct cons_pointer a,
struct cons_pointer b ) {
struct cons_pointer result = make_integer( 0, NIL );
struct cons_pointer result = acquire_integer( 0, NIL );
bool neg = is_negative( a ) != is_negative( b );
bool is_first_b = true;
int i = 0;
@ -300,16 +368,18 @@ struct cons_pointer multiply_integers( struct cons_pointer a,
/* if xj exceeds one digit, break it into the digit dj and
* the carry */
carry = xj >> INTEGER_BIT_SHIFT;
struct cons_pointer dj = make_integer( xj & MAX_INTEGER, NIL );
struct cons_pointer dj = acquire_integer( xj & MAX_INTEGER, NIL );
/* destructively modify ri by appending dj */
ri = append_digit( ri, dj );
replace_integer_p( ri, append_cell( ri, dj ));
// struct cons_pointer new_ri = append_cell( ri, dj );
// release_integer( ri);
// ri = new_ri;
} /* end for bj */
/* if carry is not equal to zero, append it as a final digit
/* if carry is not equal to zero, append it as a final cell
* to ri */
if ( carry != 0 ) {
ri = append_digit( ri, make_integer( carry, NIL ) );
replace_integer_i( ri, carry)
}
/* add ri to result */
@ -341,6 +411,9 @@ struct cons_pointer integer_to_string_add_digit( int digit, int digits,
}
/**
* @brief return a string representation of this integer, which may be a
* bignum.
*
* The general principle of printing a bignum is that you print the least
* significant digit in whatever base you're dealing with, divide through
* by the base, print the next, and carry on until you've none left.
@ -350,6 +423,9 @@ struct cons_pointer integer_to_string_add_digit( int digit, int digits,
* object to the next. 64 bit integers don't align with decimal numbers, so
* when we get to the last digit from one integer cell, we have potentially
* to be looking to the next. H'mmmm.
*
* @param int_pointer cons_pointer to the integer to print,
* @param base the base to print it in.
*/
struct cons_pointer integer_to_string( struct cons_pointer int_pointer,
int base ) {