Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ errors:
- EXPRESSION_NOT_WRITABLE_NUMBERED
- EXPRESSION_NOT_WRITABLE_SELF
- EXPRESSION_NOT_WRITABLE_TRUE
- FLOAT_PARSE
- FOR_COLLECTION
- FOR_IN
- FOR_INDEX
Expand Down
4 changes: 2 additions & 2 deletions docs/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ Only nodes that own comments have an attachment, and there are none unless the `
| # bytes | field |
| --- | --- |
| `1` | `1` if the integer is negative, `0` if the integer is positive |
| varuint | the number of words in this integer |
| varuint+ | the words of the integer, least-significant to most-significant |
| varuint | the number of words in this integer, `0` if the integer is zero |
| varuint* | the words of the integer, least-significant to most-significant |

## Structure

Expand Down
66 changes: 52 additions & 14 deletions include/prism/integer.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,74 @@
#ifndef PRISM_INTEGER_H
#define PRISM_INTEGER_H

#include "prism/compiler/inline.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/**
* A structure represents an arbitrary-sized integer.
* A structure represents an arbitrary-sized integer. The magnitude is stored as
* 32-bit words, least significant first.
*/
typedef struct {
/**
* The number of allocated values. length is set to 0 if the integer fits
* into uint32_t.
* The number of words in the magnitude, with no leading zero words. Zero
* has no words, so a zeroed pm_integer_t is always positive zero.
*/
size_t length;
uint32_t length;

/**
* List of 32-bit integers. Set to NULL if the integer fits into uint32_t.
* Whether or not the integer is negative.
*/
uint32_t *values;
bool negative;

/**
* Embedded value for small integer. This value is set to 0 if the value
* does not fit into uint32_t.
* The words of the magnitude. Read them through pm_integer_words or
* pm_integer_uint64_value rather than directly.
*/
uint32_t value;
union {
/**
* The words of an integer whose length is at most 2. Words past the
* length are zero.
*/
uint32_t value[2];

/**
* Whether or not the integer is negative. It is stored this way so that a
* zeroed pm_integer_t is always positive zero.
*/
bool negative;
/**
* The words of an integer whose length is greater than 2.
*/
uint32_t *values;
} words;
} pm_integer_t;

/**
* Return the words of an integer, least significant first.
*
* @param integer The integer to read.
* @param length Set to the number of words returned, which is 0 for zero.
* @returns A pointer to the words, valid for as long as the integer is.
*/
static PRISM_INLINE const uint32_t *
pm_integer_words(const pm_integer_t *integer, uint32_t *length) {
*length = integer->length;

if (integer->length <= 2) {
return integer->words.value;
} else {
return integer->words.values;
}
}

/**
* Return the magnitude of an integer whose length is at most 2, which always
* fits in a uint64_t.
*
* @param integer The integer to read, whose length must be at most 2.
* @returns The magnitude of the integer.
*/
static PRISM_INLINE uint64_t
pm_integer_uint64_value(const pm_integer_t *integer) {
return ((uint64_t) integer->words.value[1] << 32) | integer->words.value[0];
}

#endif
28 changes: 28 additions & 0 deletions include/prism/internal/arena.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,32 @@ pm_arena_memdup(pm_arena_t *arena, const void *src, size_t size, size_t alignmen
return dst;
}

/*
* Resize an allocation from the arena from old_size to new_size bytes and
* return where it now lives. The sizes are in the same order as xrealloc_sized.
* The most recent allocation in the current block ends exactly where the
* block's used bytes do, so it is resized in place when the block has room. Any
* other allocation stays where it is when it shrinks, and is copied into a new
* allocation when it grows, leaving the old one behind in the arena.
*/
static PRISM_INLINE void *
pm_arena_realloc(pm_arena_t *arena, void *ptr, size_t new_size, size_t old_size, size_t alignment) {
pm_arena_block_t *block = arena->current;

if (block != NULL && (char *) ptr + old_size == block->data + block->used) {
size_t start = block->used - old_size;

if (new_size <= block->capacity - start) {
block->used = start + new_size;
return ptr;
}
}

if (new_size <= old_size) return ptr;

void *result = pm_arena_alloc(arena, new_size, alignment);
memcpy(result, ptr, old_size);
return result;
}

#endif
239 changes: 239 additions & 0 deletions include/prism/internal/bignum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
* Arithmetic on arbitrary-precision unsigned integers stored as arrays of
* 32-bit words.
*/
#ifndef PRISM_INTERNAL_BIGNUM_H
#define PRISM_INTERNAL_BIGNUM_H

#include "prism/compiler/inline.h"

#include "prism/internal/bit.h"

#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

/*
* The base of the words of a number.
*/
#define PM_BIGNUM_BINARY_BASE ((uint64_t) 1 << 32)

/*
* The base of the words of a decimal number, each of which is nine digits.
*/
#define PM_BIGNUM_DECIMAL_BASE 1000000000

/*
* An arbitrary-precision unsigned integer.
*/
typedef struct {
/* The number of words in use. */
uint32_t length;

/* The words, least significant first. */
uint32_t *words;
} pm_bignum_t;

/*
* Multiply the number by multiplier and add addend, in place. A word times a
* word plus a word is at most 2**64 - 2**32, so each intermediate product fits
* in a uint64_t. A nonzero carry out of the top word becomes a new top word, so
* the storage must have room for it.
*/
static PRISM_INLINE void
pm_bignum_mul_add(pm_bignum_t *bignum, uint32_t multiplier, uint32_t addend) {
assert(multiplier != 0);
uint64_t carry = addend;

for (uint32_t index = 0; index < bignum->length; index++) {
uint64_t product = (uint64_t) bignum->words[index] * multiplier + carry;
bignum->words[index] = (uint32_t) product;
carry = product >> 32;
}

if (carry != 0) bignum->words[bignum->length++] = (uint32_t) carry;
}

/*
* Divide the number by divisor, in place, working down from the top word.
* Returns the remainder. The remainder is always below the divisor, so shifting
* it above the next word fits in a uint64_t, and since the divisor is a single
* word the quotient is at most one word shorter. When inlined with a constant
* divisor, the compiler replaces the division with a multiplication.
*/
static PRISM_INLINE uint32_t
pm_bignum_div(pm_bignum_t *bignum, uint32_t divisor) {
assert(divisor != 0);
uint64_t remainder = 0;

for (uint32_t index = bignum->length; index > 0; index--) {
uint64_t current = (remainder << 32) | bignum->words[index - 1];
bignum->words[index - 1] = (uint32_t) (current / divisor);
remainder = current % divisor;
}

if (bignum->length > 0 && bignum->words[bignum->length - 1] == 0) bignum->length--;
return (uint32_t) remainder;
}

/*
* Divide the number by divisor if it divides evenly, and return whether it did.
* Otherwise the number is left unchanged: multiplying the quotient back and
* adding the remainder restores it exactly.
*/
static PRISM_INLINE bool
pm_bignum_div_exact(pm_bignum_t *bignum, uint32_t divisor) {
uint32_t remainder = pm_bignum_div(bignum, divisor);
if (remainder == 0) return true;

pm_bignum_mul_add(bignum, divisor, remainder);
return false;
}

/*
* Shift the number left by count bits, in place. The words grow by count / 32
* plus possibly one more, so the storage must have room for them.
*/
static PRISM_INLINE void
pm_bignum_shift_left(pm_bignum_t *bignum, size_t count) {
if (bignum->length == 0) return;

uint32_t *words = bignum->words;
uint32_t length = bignum->length;
uint32_t word_shift = (uint32_t) (count / 32);
uint32_t bit_shift = (uint32_t) (count % 32);

/* Work down from the top word, so each word is read before the word that
* replaces it is written. */
uint32_t carry = bit_shift == 0 ? 0 : words[length - 1] >> (32 - bit_shift);
for (uint32_t index = length; index > 0; index--) {
uint32_t below = (bit_shift == 0 || index == 1) ? 0 : words[index - 2] >> (32 - bit_shift);
words[index - 1 + word_shift] = (words[index - 1] << bit_shift) | below;
}

memset(words, 0, sizeof(uint32_t) * word_shift);
bignum->length = length + word_shift;
if (carry != 0) words[bignum->length++] = carry;
}

/*
* Shift the number right by count bits, in place, dropping the bits that are
* shifted out.
*/
static PRISM_INLINE void
pm_bignum_shift_right(pm_bignum_t *bignum, size_t count) {
uint32_t *words = bignum->words;
uint32_t word_shift = (uint32_t) (count / 32);
uint32_t bit_shift = (uint32_t) (count % 32);

if (word_shift >= bignum->length) {
bignum->length = 0;
return;
}

/* Work up from the bottom word, so each word is read before the word that
* replaces it is written. */
uint32_t length = bignum->length - word_shift;
for (uint32_t index = 0; index < length; index++) {
uint32_t above = (bit_shift == 0 || index + 1 == length) ? 0 : words[index + word_shift + 1] << (32 - bit_shift);
words[index] = (words[index + word_shift] >> bit_shift) | above;
}

bignum->length = length;
if (words[length - 1] == 0) bignum->length--;
}

/*
* Return the number of trailing zero bits in a nonzero number. Its top word is
* nonzero, so the scan for the first nonzero word stops within its length.
*/
static PRISM_INLINE uint64_t
pm_bignum_trailing_zeros(const pm_bignum_t *bignum) {
assert(bignum->length > 0 && bignum->words[bignum->length - 1] != 0);

uint32_t index = 0;
while (bignum->words[index] == 0) index++;
return (uint64_t) index * 32 + pm_ctzll(bignum->words[index]);
}

/*
* Compare two numbers. Returns -1 if left is less than right, 0 if they are
* equal, and 1 if left is greater than right.
*/
static PRISM_INLINE int
pm_bignum_compare(const pm_bignum_t *left, const pm_bignum_t *right) {
if (left->length != right->length) return left->length < right->length ? -1 : 1;

for (uint32_t index = left->length; index > 0; index--) {
uint32_t left_word = left->words[index - 1];
uint32_t right_word = right->words[index - 1];
if (left_word != right_word) return left_word < right_word ? -1 : 1;
}

return 0;
}

/*
* Return the value of a number of at most two words. Words past the length may
* hold stale values after an in-place operation, so only the words in use are
* read.
*/
static PRISM_INLINE uint64_t
pm_bignum_uint64(const pm_bignum_t *bignum) {
assert(bignum->length <= 2);
return (bignum->length > 0 ? (uint64_t) bignum->words[0] : 0) | (bignum->length > 1 ? (uint64_t) bignum->words[1] << 32 : 0);
}

/*
* Temporary words for the operations that need them, taken and given back in
* stack order: an operation saves the cursor, takes what it needs, and
* restores the cursor before it returns.
*/
typedef struct {
/* The next free word. */
uint32_t *cursor;

/* One past the last word. */
uint32_t *end;
} pm_bignum_scratch_t;

/*
* Take count words from the scratch space.
*/
static PRISM_INLINE uint32_t *
pm_bignum_scratch_take(pm_bignum_scratch_t *scratch, uint32_t count) {
assert((size_t) (scratch->end - scratch->cursor) >= count);
uint32_t *words = scratch->cursor;
scratch->cursor += count;
return words;
}

/*
* Add right into left, in place. The storage must have room for the sum, which
* is at most one word longer than the longer of the two.
*/
void pm_bignum_add(pm_bignum_t *left, const pm_bignum_t *right);

/*
* Multiply left by right into result, whose storage has room for
* left->length + right->length words and overlaps neither. Long numbers use
* Karatsuba's method, which takes its temporaries from scratch.
*/
void pm_bignum_multiply(pm_bignum_t *result, const pm_bignum_t *left, const pm_bignum_t *right, pm_bignum_scratch_t *scratch);

/*
* Add right into left, in place, in base 10**9. The storage must have room for
* the sum, which is at most one word longer than the longer of the two.
*/
void pm_bignum_decimal_add(pm_bignum_t *left, const pm_bignum_t *right);

/*
* Multiply left by right into result, in base 10**9, whose storage has room
* for left->length + right->length words and overlaps neither. Long numbers
* use Karatsuba's method, which takes its temporaries from scratch.
*/
void pm_bignum_decimal_multiply(pm_bignum_t *result, const pm_bignum_t *left, const pm_bignum_t *right, pm_bignum_scratch_t *scratch);

#endif
Loading
Loading