From 9c417d2aa180f3ff0ff3ff6d4bdd8d996560203e Mon Sep 17 00:00:00 2001 From: jun <83899451+zeichensystem@users.noreply.github.com> Date: Sun, 11 May 2025 08:55:03 +0200 Subject: [PATCH] Add checked arithmetic --- src/guf_alloc.h | 6 + src/guf_alloc_libc.h | 3 +- src/guf_alloc_tracker.c | 118 +++- src/guf_math.h | 198 +++--- src/guf_math_ckdint.h | 1203 ++++++++++++++++++++++++++++++++++++ src/guf_rand.h | 6 +- src/guf_str.h | 7 +- src/test/example.c | 2 +- todo.txt | 26 +- tools/ckdint-gen.py | 253 ++++++++ tools/min_max_clamp-gen.py | 77 +++ 11 files changed, 1772 insertions(+), 127 deletions(-) create mode 100644 src/guf_math_ckdint.h create mode 100644 tools/ckdint-gen.py create mode 100644 tools/min_max_clamp-gen.py diff --git a/src/guf_alloc.h b/src/guf_alloc.h index 345f965..2d865a5 100644 --- a/src/guf_alloc.h +++ b/src/guf_alloc.h @@ -15,6 +15,12 @@ typedef struct guf_allocator { void *ctx; } guf_allocator; +typedef struct guf_alloc_meta { + size_t alloc_count, realloc_count, free_count; + ptrdiff_t allocated_bytes; + uint32_t alloc_id; +} guf_alloc_meta; + typedef enum guf_alloc_fn_type { GUF_ALLOC_FN_TYPE_ALLOC, GUF_ALLOC_FN_TYPE_REALLOC, diff --git a/src/guf_alloc_libc.h b/src/guf_alloc_libc.h index 3023dbd..c64917b 100644 --- a/src/guf_alloc_libc.h +++ b/src/guf_alloc_libc.h @@ -8,7 +8,8 @@ #include "guf_alloc.h" typedef struct guf_libc_alloc_ctx { - int alloc_type_id, thread_id; + guf_alloc_meta meta; + bool track_allocs; bool zero_init; } guf_libc_alloc_ctx; diff --git a/src/guf_alloc_tracker.c b/src/guf_alloc_tracker.c index 66729a4..86db727 100644 --- a/src/guf_alloc_tracker.c +++ b/src/guf_alloc_tracker.c @@ -1,14 +1,116 @@ + #include "guf_common.h" #include "guf_alloc.h" +#include "math.h" -typedef struct guf_alloc_info { - const char *name; - ptrdiff_t allocated_bytes, freed_bytes; +typedef struct guf_alloc_tracker { + FILE *log, *err_log; size_t alloc_count, realloc_count, free_count; -} guf_alloc_info; + ptrdiff_t allocated_bytes; + uint32_t id; +} guf_alloc_tracker; + +static bool guf_track_alloc(guf_alloc_tracker *t, ptrdiff_t size) +{ + GUF_ASSERT(t); + GUF_ASSERT(size >= 0); + bool success = true; + if (t->err_log && t->alloc_count == SIZE_MAX) { + fprintf(t->err_log, "guf_alloc_track (id %" PRIu32 ") WARNING: alloc_count overflow\n", t->id); + //success = false; + } + t->alloc_count = guf_add_saturated_size_t(t->alloc_count, 1); + + if (guf_add_is_overflow_ptrdiff(t->allocated_bytes, size)) { + if (t->err_log) { + fprintf(t->err_log, "guf_alloc_track (id %" PRIu32 ") ERROR: allocated_byte overflow\n", t->id); + } + success = false; + } + t->allocated_bytes = guf_add_saturated_ptrdiff(t->allocated_bytes, size); + if (t->allocated_bytes < 0) { + if (t->err_log) { + fprintf(t->err_log, "guf_alloc_track (id %" PRIu32 ") ERROR: allocated_bytes < 0\n", t->id); + } + success = false; + } + + if (t->log) { + fprintf(t->log, "guf_alloc_track (id %" PRIu32 "): alloc (%td bytes) %s\n", t->id, size, success ? "SUCCESS" : "FAILURE"); + } + return success; +} + + +static bool guf_track_realloc(guf_alloc_tracker *t, ptrdiff_t old_size, ptrdiff_t new_size) +{ + GUF_ASSERT(t); + GUF_ASSERT(old_size >= 0 && new_size >= 0); + bool success = true; + if (t->err_log && t->realloc_count == SIZE_MAX) { + fprintf(t->err_log, "guf_realloc_track (id %" PRIu32 ") WARNING: realloc_count overflow\n"); + //success = false; + } + t->realloc_count = guf_add_saturated_size_t(t->realloc_count, 1); + + if (old_size < 0 || new_size < 0) { + if (t->err_log) { + fprintf(t->err_log, "guf_realloc_track (id %" PRIu32 ") ERROR: old_size < 0 or new_size < 0\n"); + } + success = false; + } + + t->allocated_bytes = guf_sub_saturated_ptrdiff(t->allocated_bytes, old_size); + if (t->allocated_bytes < 0) { + success = false; + fprintf(t->err_log, "guf_realloc_track (id %" PRIu32 ") ERROR: allocated_bytes < 0 after subtracting old_size\n", t->id); + } + if (guf_add_is_overflow_ptrdiff(t->allocated_bytes, new_size)) { + success = false; + if (t->err_log) { + fprintf(t->err_log, "guf_realloc_track (id %" PRIu32 ") ERROR: allocated_bytes overflow \n"); + } + } + t->allocated_bytes = guf_add_saturated_ptrdiff(t->allocated_bytes, new_size); + + if (t->allocated_bytes < 0) { + success = false; + fprintf(t->err_log, "guf_realloc_track (id %" PRIu32 ") ERROR: allocated_bytes < 0 after adding new_size\n", t->id); + } + + if (t->log) { + fprintf(t->log, "guf_realloc_track (id %" PRIu32 "): realloc (from %td to %td bytes) %s", t->id, old_size, new_size, (success ? "SUCCESS" : "FAILURE")); + } + + return success; +} + +static bool guf_track_free(guf_alloc_tracker *t, ptrdiff_t size) +{ + GUF_ASSERT(t); + GUF_ASSERT(size >= 0); + + if (t->err_log && t->free_count == SIZE_MAX) { + fprintf(t->err_log, "guf_track_free (id %" PRIu32 ") WARNING: free_count overflow\n"); + } + bool success = true; + + if (size < 0) { + success = false; + if (t->err_log) { + fprintf(t->err_log, "guf_track_free (id %" PRIu32 ") ERROR: size < 0\n"); + } + } + + if (t->allocated_bytes < size) { + success = false; + if (t->err_log) { + fprintf(t->err_log, "guf_track_free (id %" PRIu32 ") ERROR: freed more bytes than allocated\n"); + } + } + t->allocated_bytes = guf_sub_saturated_ptrdiff(t->allocated_bytes, size); + + return success; +} -// void guf_alloc_track(int type_id, int thread_id, ptrdiff_t size) -// { -// static guf_alloc_info guf_alloc_info[GUF_ALLOC_THREAD_ID_MAX][GUF_ALLOC_TYPE_ID_MAX]; -// } diff --git a/src/guf_math.h b/src/guf_math.h index 66c1a9b..8ee87e0 100644 --- a/src/guf_math.h +++ b/src/guf_math.h @@ -22,56 +22,117 @@ static inline uint64_t guf_rotl_u64(uint64_t x, int k) {return (x << k) | (x >> (64 - k));} static inline uint32_t guf_rotl_u32(uint32_t x, int k) {return (x << k) | (x >> (32 - k));} -#define GUF_DEFINE_MIN_MAX_CLAMP(int_type, int_type_name)\ - static inline int_type GUF_CAT(guf_min_, int_type_name)(int_type a, int_type b) {return a < b ? a : b;}\ - static inline int_type GUF_CAT(guf_max_, int_type_name)(int_type a, int_type b) {return a > b ? a : b;}\ - static inline int_type GUF_CAT(guf_clamp_, int_type_name)(int_type x, int_type min, int_type max) {if (x < min) {return min;} if (x > max) {return max;} return x;} -GUF_DEFINE_MIN_MAX_CLAMP(char, char) -GUF_DEFINE_MIN_MAX_CLAMP(int, int) -GUF_DEFINE_MIN_MAX_CLAMP(long, long) -GUF_DEFINE_MIN_MAX_CLAMP(long long, long_long) -GUF_DEFINE_MIN_MAX_CLAMP(int8_t, i8) -GUF_DEFINE_MIN_MAX_CLAMP(int16_t, i16) -GUF_DEFINE_MIN_MAX_CLAMP(int32_t, i32) -GUF_DEFINE_MIN_MAX_CLAMP(int64_t, i64) -GUF_DEFINE_MIN_MAX_CLAMP(ptrdiff_t, ptrdiff_t) +// Signed min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py) -GUF_DEFINE_MIN_MAX_CLAMP(unsigned char, uchar) -GUF_DEFINE_MIN_MAX_CLAMP(unsigned, unsigned) -GUF_DEFINE_MIN_MAX_CLAMP(unsigned long, ulong) -GUF_DEFINE_MIN_MAX_CLAMP(unsigned long long, ulong_long) -GUF_DEFINE_MIN_MAX_CLAMP(uint8_t, u8) -GUF_DEFINE_MIN_MAX_CLAMP(uint16_t, u16) -GUF_DEFINE_MIN_MAX_CLAMP(uint32_t, u32) -GUF_DEFINE_MIN_MAX_CLAMP(uint64_t, u64) -GUF_DEFINE_MIN_MAX_CLAMP(size_t, size_t) +static inline char guf_min_char(char a, char b) { return a < b ? a : b; } +static inline char guf_max_char(char a, char b) { return a > b ? a : b; } +static inline char guf_clamp_char(char x, char min, char max) { if (x < min) {return min;} if (x > max) {return max;} return x; } -GUF_DEFINE_MIN_MAX_CLAMP(float, f32) -GUF_DEFINE_MIN_MAX_CLAMP(double, f64) +static inline int guf_min_int(int a, int b) { return a < b ? a : b; } +static inline int guf_max_int(int a, int b) { return a > b ? a : b; } +static inline int guf_clamp_int(int x, int min, int max) { if (x < min) {return min;} if (x > max) {return max;} return x; } -#undef GUF_DEFINE_MIN_MAX_CLAMP +static inline long guf_min_long(long a, long b) { return a < b ? a : b; } +static inline long guf_max_long(long a, long b) { return a > b ? a : b; } +static inline long guf_clamp_long(long x, long min, long max) { if (x < min) {return min;} if (x > max) {return max;} return x; } -static inline int guf_abs_int(int x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > INT_MIN); return -x;} // I would not drink that... -static inline int8_t guf_abs_i8 (int8_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > INT8_MIN); return -x;} -static inline int16_t guf_abs_i16(int16_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > INT16_MIN); return -x;} -static inline int32_t guf_abs_i32(int32_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > INT32_MIN); return -x;} -static inline int64_t guf_abs_i64(int64_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > INT64_MIN); return -x;} -static inline ptrdiff_t guf_abs_ptrdiff(ptrdiff_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(x > PTRDIFF_MIN); return -x;} +static inline long long guf_min_long_long(long long a, long long b) { return a < b ? a : b; } +static inline long long guf_max_long_long(long long a, long long b) { return a > b ? a : b; } +static inline long long guf_clamp_long_long(long long x, long long min, long long max) { if (x < min) {return min;} if (x > max) {return max;} return x; } -static inline unsigned char guf_uabs_char(char x) {if (x >= 0) {return x;} else if (x == CHAR_MIN) {return (unsigned char)CHAR_MAX + 1;} else {return -x;}} -static inline unsigned guf_uabs_int(int x) {if (x >= 0) {return x;} else if (x == INT_MIN) {return (unsigned)INT_MAX + 1;} else {return -x;}} -static inline uint8_t guf_uabs_i8(int8_t x) {if (x >= 0) {return x;} else if (x == INT8_MIN) {return (uint8_t)INT8_MAX + 1;} else {return -x;}} -static inline uint16_t guf_uabs_i16(int16_t x) {if (x >= 0) {return x;} else if (x == INT16_MIN) {return (uint16_t)INT16_MAX + 1;} else {return -x;}} -static inline uint32_t guf_uabs_i32(int32_t x) {if (x >= 0) {return x;} else if (x == INT32_MIN) {return (uint32_t)INT32_MAX + 1;} else {return -x;}} -static inline uint64_t guf_uabs_i64(int64_t x) {if (x >= 0) {return x;} else if (x == INT64_MIN) {return (uint64_t)INT64_MAX + 1;} else {return -x;}} +static inline int8_t guf_min_i8(int8_t a, int8_t b) { return a < b ? a : b; } +static inline int8_t guf_max_i8(int8_t a, int8_t b) { return a > b ? a : b; } +static inline int8_t guf_clamp_i8(int8_t x, int8_t min, int8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline int16_t guf_min_i16(int16_t a, int16_t b) { return a < b ? a : b; } +static inline int16_t guf_max_i16(int16_t a, int16_t b) { return a > b ? a : b; } +static inline int16_t guf_clamp_i16(int16_t x, int16_t min, int16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline int32_t guf_min_i32(int32_t a, int32_t b) { return a < b ? a : b; } +static inline int32_t guf_max_i32(int32_t a, int32_t b) { return a > b ? a : b; } +static inline int32_t guf_clamp_i32(int32_t x, int32_t min, int32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline int64_t guf_min_i64(int64_t a, int64_t b) { return a < b ? a : b; } +static inline int64_t guf_max_i64(int64_t a, int64_t b) { return a > b ? a : b; } +static inline int64_t guf_clamp_i64(int64_t x, int64_t min, int64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline ptrdiff_t guf_min_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { return a < b ? a : b; } +static inline ptrdiff_t guf_max_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { return a > b ? a : b; } +static inline ptrdiff_t guf_clamp_ptrdiff_t(ptrdiff_t x, ptrdiff_t min, ptrdiff_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline float guf_min_f32(float a, float b) { return a < b ? a : b; } +static inline float guf_max_f32(float a, float b) { return a > b ? a : b; } +static inline float guf_clamp_f32(float x, float min, float max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline double guf_min_f64(double a, double b) { return a < b ? a : b; } +static inline double guf_max_f64(double a, double b) { return a > b ? a : b; } +static inline double guf_clamp_f64(double x, double min, double max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +// Unsigned min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py) + +static inline unsigned char guf_min_uchar(unsigned char a, unsigned char b) { return a < b ? a : b; } +static inline unsigned char guf_max_uchar(unsigned char a, unsigned char b) { return a > b ? a : b; } +static inline unsigned char guf_clamp_uchar(unsigned char x, unsigned char min, unsigned char max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline unsigned guf_min_unsigned(unsigned a, unsigned b) { return a < b ? a : b; } +static inline unsigned guf_max_unsigned(unsigned a, unsigned b) { return a > b ? a : b; } +static inline unsigned guf_clamp_unsigned(unsigned x, unsigned min, unsigned max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline unsigned long guf_min_ulong(unsigned long a, unsigned long b) { return a < b ? a : b; } +static inline unsigned long guf_max_ulong(unsigned long a, unsigned long b) { return a > b ? a : b; } +static inline unsigned long guf_clamp_ulong(unsigned long x, unsigned long min, unsigned long max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline unsigned long long guf_min_ulong_long(unsigned long long a, unsigned long long b) { return a < b ? a : b; } +static inline unsigned long long guf_max_ulong_long(unsigned long long a, unsigned long long b) { return a > b ? a : b; } +static inline unsigned long long guf_clamp_ulong_long(unsigned long long x, unsigned long long min, unsigned long long max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline uint8_t guf_min_u8(uint8_t a, uint8_t b) { return a < b ? a : b; } +static inline uint8_t guf_max_u8(uint8_t a, uint8_t b) { return a > b ? a : b; } +static inline uint8_t guf_clamp_u8(uint8_t x, uint8_t min, uint8_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline uint16_t guf_min_u16(uint16_t a, uint16_t b) { return a < b ? a : b; } +static inline uint16_t guf_max_u16(uint16_t a, uint16_t b) { return a > b ? a : b; } +static inline uint16_t guf_clamp_u16(uint16_t x, uint16_t min, uint16_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline uint32_t guf_min_u32(uint32_t a, uint32_t b) { return a < b ? a : b; } +static inline uint32_t guf_max_u32(uint32_t a, uint32_t b) { return a > b ? a : b; } +static inline uint32_t guf_clamp_u32(uint32_t x, uint32_t min, uint32_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline uint64_t guf_min_u64(uint64_t a, uint64_t b) { return a < b ? a : b; } +static inline uint64_t guf_max_u64(uint64_t a, uint64_t b) { return a > b ? a : b; } +static inline uint64_t guf_clamp_u64(uint64_t x, uint64_t min, uint64_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + +static inline size_t guf_min_size_t(size_t a, size_t b) { return a < b ? a : b; } +static inline size_t guf_max_size_t(size_t a, size_t b) { return a > b ? a : b; } +static inline size_t guf_clamp_size_t(size_t x, size_t min, size_t max) { if (x < min) {return min;} if (x > max) {return max;} return x; } + + +// abs functions with signed result (can fail/panic for abs(INT_TYPE_MIN) for platforms with two's complement signed ints; C2X for example guarantees two's complement) +static inline int guf_abs_int(int x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT_MAX == INT_MIN || x > INT_MIN); return -x;} // I would not drink that... +static inline int8_t guf_abs_i8 (int8_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT8_MAX == INT8_MIN || x > INT8_MIN); return -x;} +static inline int16_t guf_abs_i16(int16_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT16_MAX == INT16_MIN || x > INT16_MIN); return -x;} +static inline int32_t guf_abs_i32(int32_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT32_MAX == INT32_MIN || x > INT32_MIN); return -x;} +static inline int64_t guf_abs_i64(int64_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-INT64_MAX == INT64_MIN || x > INT64_MIN); return -x;} +static inline ptrdiff_t guf_abs_ptrdiff(ptrdiff_t x) {if (x >= 0) {return x;} GUF_ASSERT_RELEASE(-PTRDIFF_MAX == PTRDIFF_MIN || x > PTRDIFF_MIN); return -x;} + +// abs functions with unsigned result functions (cannot fail) +static inline unsigned guf_uabs_int(int x) {if (x >= 0) {return x;} else if (x == INT_MIN && -INT_MAX != INT_MIN) {return (unsigned)INT_MAX + 1;} else {return -x;}} +static inline uint8_t guf_uabs_i8(int8_t x) {if (x >= 0) {return x;} else if (x == INT8_MIN && -INT8_MAX != INT8_MIN) {return (uint8_t)INT8_MAX + 1;} else {return -x;}} +static inline uint16_t guf_uabs_i16(int16_t x) {if (x >= 0) {return x;} else if (x == INT16_MIN && -INT16_MAX != INT16_MIN) {return (uint16_t)INT16_MAX + 1;} else {return -x;}} +static inline uint32_t guf_uabs_i32(int32_t x) {if (x >= 0) {return x;} else if (x == INT32_MIN && -INT32_MAX != INT32_MIN) {return (uint32_t)INT32_MAX + 1;} else {return -x;}} +static inline uint64_t guf_uabs_i64(int64_t x) {if (x >= 0) {return x;} else if (x == INT64_MIN && -INT64_MAX != INT64_MIN) {return (uint64_t)INT64_MAX + 1;} else {return -x;}} +static inline size_t guf_uabs_ptrdiff_t(ptrdiff_t x) {if (x >= 0) {return x;} else if (x == PTRDIFF_MIN && -PTRDIFF_MAX != PTRDIFF_MIN) {return (size_t)PTRDIFF_MAX + 1;} else {return -x;}} + +// absdiff functions with unsigned result (cannot fail) +static inline unsigned char guf_absdiff_char(char a, char b) {return a > b ? (unsigned char)a - (unsigned char)b : (unsigned char)b - (unsigned char)a;} +static inline unsigned guf_absdiff_int(int a, int b) {return a > b ? (unsigned)a - (unsigned)b : (unsigned)b - (unsigned)a;} +static inline uint8_t guf_absdiff_i8(int8_t a, int8_t b) {return a > b ? (uint8_t)a - (uint8_t)b : (uint8_t)b - (uint8_t)a;} +static inline uint16_t guf_absdiff_i16(int16_t a, int16_t b) {return a > b ? (uint16_t)a - (uint16_t)b : (uint16_t)b - (uint16_t)a;} +static inline uint32_t guf_absdiff_i32(int32_t a, int32_t b) {return a > b ? (uint32_t)a - (uint32_t)b : (uint32_t)b - (uint32_t)a;} +static inline uint64_t guf_absdiff_i64(int64_t a, int64_t b) {return a > b ? (uint64_t)a - (uint64_t)b : (uint64_t)b - (uint64_t)a;} +static inline size_t guf_absdiff_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) {return a > b ? (size_t)a - (size_t)b : (size_t)b - (size_t)a;} -static inline unsigned char guf_absdiff_char(char a, char b) {return a > b ? (unsigned char)a - (unsigned char)b : (unsigned char)b - (unsigned char)a;} -static inline unsigned guf_absdiff_int(int a, int b) {return a > b ? (unsigned)a - (unsigned)b : (unsigned)b - (unsigned)a;} -static inline uint8_t guf_absdiff_i8(int8_t a, int8_t b) {return a > b ? (uint8_t)a - (uint8_t)b : (uint8_t)b - (uint8_t)a;} -static inline uint16_t guf_absdiff_i16(int16_t a, int16_t b) {return a > b ? (uint16_t)a - (uint16_t)b : (uint16_t)b - (uint16_t)a;} -static inline uint32_t guf_absdiff_i32(int32_t a, int32_t b) {return a > b ? (uint32_t)a - (uint32_t)b : (uint32_t)b - (uint32_t)a;} -static inline uint64_t guf_absdiff_i64(int64_t a, int64_t b) {return a > b ? (uint64_t)a - (uint64_t)b : (uint64_t)b - (uint64_t)a;} static inline bool guf_add_is_overflow_size_t(size_t a, size_t b) { @@ -87,54 +148,6 @@ static inline bool guf_mul_is_overflow_size_t(size_t a, size_t b) return a != 0 && ((c / a) != b); } -static inline bool guf_add_is_overflow_u32(uint32_t a, uint32_t b) -{ - return (a + b) < a; -} -static inline bool guf_sub_is_overflow_u32(uint32_t a, uint32_t b) -{ - return (a - b) > a; -} -static inline bool guf_mul_is_overflow_u32(uint32_t a, uint32_t b) -{ - const uint32_t c = a * b; - return a != 0 && ((c / a) != b); -} - -// cf. https://stackoverflow.com/questions/199333/how-do-i-detect-unsigned-integer-overflow (last-retrieved 2025-03-17) -static inline bool guf_add_is_overflow_ptrdiff(ptrdiff_t a, ptrdiff_t b) -{ - return (b > 0 && a > PTRDIFF_MAX - b) || // a + b overflow - (b < 0 && a < PTRDIFF_MIN - b); // a + b underflow -} -static inline bool guf_sub_is_overflow_ptrdiff(ptrdiff_t a, ptrdiff_t b) -{ - return (b < 0 && a > PTRDIFF_MAX + b) || // a - b overflow - (b > 0 && a < PTRDIFF_MIN + b); // a - b underflow -} - -static inline bool guf_add_is_overflow_i32(int32_t a, int32_t b) -{ - return (b > 0 && a > INT32_MAX - b) || // a + b overflow - (b < 0 && a < INT32_MIN - b); // a + b underflow -} -static inline bool guf_sub_is_overflow_i32(int32_t a, int32_t b) -{ - return (b < 0 && a > INT32_MAX + b) || // a - b overflow - (b > 0 && a < INT32_MIN + b); // a - b underflow -} - -static inline bool guf_add_is_overflow_i64(int64_t a, int64_t b) -{ - return (b > 0 && a > INT64_MAX - b) || // a + b overflow - (b < 0 && a < INT64_MIN - b); // a + b underflow -} -static inline bool guf_sub_is_overflow_i64(int64_t a, int64_t b) -{ - return (b < 0 && a > INT64_MAX + b) || // a - b overflow - (b > 0 && a < INT64_MIN + b); // a - b underflow -} - static inline bool guf_size_calc_safe(ptrdiff_t count, ptrdiff_t sizeof_elem, ptrdiff_t *result) { if (count < 0 || sizeof_elem <= 0) { @@ -152,6 +165,7 @@ static inline bool guf_size_calc_safe(ptrdiff_t count, ptrdiff_t sizeof_elem, pt return is_safe; } + // cf. https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 (last-retrieved 2025-03-19) static inline bool guf_is_pow2_u8(uint8_t x) { return x && !(x & (x - 1)); } static inline bool guf_is_pow2_u16(uint16_t x) { return x && !(x & (x - 1)); } @@ -159,9 +173,9 @@ static inline bool guf_is_pow2_u32(uint32_t x) { return x && !(x & (x - 1)); } static inline bool guf_is_pow2_u64(uint64_t x) { return x && !(x & (x - 1)); } static inline bool guf_is_pow2_size_t(size_t x) { return x && !(x & (x - 1)); } + static bool guf_nearly_zero_f32(float x, float eps) {return fabsf(x) <= eps;} static bool guf_nearly_one_f32(float x, float eps) {return fabsf(x - 1) <= eps;} - static bool guf_nearly_zero_f64(double x, double eps) {return fabs(x) <= eps;} static bool guf_nearly_one_f64(double x, double eps) {return fabs(x - 1) <= eps;} diff --git a/src/guf_math_ckdint.h b/src/guf_math_ckdint.h new file mode 100644 index 0000000..22f867c --- /dev/null +++ b/src/guf_math_ckdint.h @@ -0,0 +1,1203 @@ +#ifndef GUF_MATH_CKDINT_H +#define GUF_MATH_CKDINT_H +#include "guf_common.h" +#include "guf_assert.h" + +typedef enum guf_math_ckd_result {GUF_MATH_CKD_SUCCESS = 0, GUF_MATH_CKD_OVERFLOW, GUF_MATH_CKD_UNDERFLOW} guf_math_ckd_result; + +/* + // Functions for safely checking for over- and underflow of arithmetic operations + + guf_math_ckd_result guf_ckd_add_TYPE(TYPE a, TYPE b); + - if a + b doesn't over/underflow TYPE: return GUF_MATH_CKD_SUCCESS (falsy) + - if a + b overflows TYPE: return GUF_MATH_CKD_OVERFLOW (truthy) + - if a + b underflows TYPE: return GUF_MATH_CKD_UNDERFLOW (truthy) + + guf_math_ckd_result guf_ckd_sub_TYPE(TYPE a, TYPE b); + - if a - b doesn't over/underflow TYPE: return GUF_MATH_CKD_SUCCESS (falsy) + - if a - b overflows TYPE: return GUF_MATH_CKD_OVERFLOW (truthy) + - if a - b underflows TYPE: return GUF_MATH_CKD_UNDERFLOW (truthy) + + + // Functions for safely computing arithmetic operations with saturating over- and underflow semantics + // (cf. https://doc.rust-lang.org/std/intrinsics/fn.saturating_add.html (last-retrieved 2025-05-10)) + + guf_math_ckd_result guf_saturating_add_TYPE(TYPE a, TYPE b, TYPE *result); + - if a + b does not over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a + b) + - if a + b overflows TYPE: return GUF_MATH_CKD_OVERFLOW (if result is not NULL, set result to TYPE_MAX, i.e. use saturating overflow semantics). + - if a + b underflows TYPE: return GUF_MATH_CKD_UNDERFLOW (if result is not NULL, set result to TYPE_MIN, i.e. use saturating underflow semantics). + + guf_math_ckd_result guf_saturating_sub_TYPE(TYPE a, TYPE b, TYPE *result); + - if a - b does not over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a - b) + - if a - b overflows TYPE: return GUF_MATH_CKD_OVERFLOW (if result is not NULL, set result to TYPE_MAX, i.e. use saturating overflow semantics). + - if a - b underflows TYPE: return GUF_MATH_CKD_UNDERFLOW (if result is not NULL, set result to TYPE_MIN, i.e. use saturating underflow semantics). + + + // Functions for safely computing arithmetic operations with wrapping over- and underflow semantics + // (cf. https://doc.rust-lang.org/std/intrinsics/fn.wrapping_add.html (last-retrieved 2025-05-10)) + + guf_math_ckd_result guf_wrapping_add_TYPE(TYPE a, TYPE b, TYPE *result); + - if a + b does not over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a + b) + - if a + b overflows TYPE: return GUF_MATH_CKD_OVERFLOW (if result is not NULL, set result using two's complement wrap-around overflow semantics). + - if a + b underflows TYPE: return GUF_MATH_CKD_UNDERFLOW (if result is not NULL, set result using two's complement wrap-around underflow semantics). + + guf_math_ckd_result guf_wrapping_sub_TYPE(TYPE a, TYPE b, TYPE *result); + - if a - b doesn't over-/underflow TYPE: return GUF_MATH_CKD_SUCCESS (if result is not NULL, set result to a - b) + - if a - b overflows TYPE: return GUF_MATH_CKD_OVERFLOW (if result is not NULL, set result using two's complement wrap-around overflow semantics). + - if a - b underflows TYPE: return GUF_MATH_CKD_UNDERFLOW (if result is not NULL, set result using two's complement wrap-around underflow semantics). + + cf. https://stackoverflow.com/questions/199333/how-do-i-detect-unsigned-integer-overflow (last-retrieved 2025-03-17) + cf. https://stackoverflow.com/questions/59307930/how-to-implement-wrapping-signed-int-addition-in-c (last-retrieved 2025-05-10) + +*/ + + +// Signed integer add/sub checks (generated with libguf/tools/ckdint-gen.py) + +static inline guf_math_ckd_result guf_ckd_add_int(int a, int b) +{ + if (b > 0 && a > INT_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b < 0 && a < INT_MIN - b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_int(int a, int b) +{ + if (b < 0 && a > INT_MAX + b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b > 0 && a < INT_MIN + b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_i8(int8_t a, int8_t b) +{ + if (b > 0 && a > INT8_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b < 0 && a < INT8_MIN - b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_i8(int8_t a, int8_t b) +{ + if (b < 0 && a > INT8_MAX + b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b > 0 && a < INT8_MIN + b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_i16(int16_t a, int16_t b) +{ + if (b > 0 && a > INT16_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b < 0 && a < INT16_MIN - b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_i16(int16_t a, int16_t b) +{ + if (b < 0 && a > INT16_MAX + b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b > 0 && a < INT16_MIN + b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_i32(int32_t a, int32_t b) +{ + if (b > 0 && a > INT32_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b < 0 && a < INT32_MIN - b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_i32(int32_t a, int32_t b) +{ + if (b < 0 && a > INT32_MAX + b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b > 0 && a < INT32_MIN + b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_i64(int64_t a, int64_t b) +{ + if (b > 0 && a > INT64_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b < 0 && a < INT64_MIN - b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_i64(int64_t a, int64_t b) +{ + if (b < 0 && a > INT64_MAX + b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b > 0 && a < INT64_MIN + b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) +{ + if (b > 0 && a > PTRDIFF_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b < 0 && a < PTRDIFF_MIN - b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) +{ + if (b < 0 && a > PTRDIFF_MAX + b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (b > 0 && a < PTRDIFF_MIN + b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +// Unsigned integer add/sub checks (generated with libguf/tools/ckdint-gen.py) + +static inline guf_math_ckd_result guf_ckd_add_uchar(unsigned char a, unsigned char b) +{ + if (b > 0 && a > UCHAR_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_uchar(unsigned char a, unsigned char b) +{ + if (b > a) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_unsigned(unsigned a, unsigned b) +{ + if (b > 0 && a > UINT_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_unsigned(unsigned a, unsigned b) +{ + if (b > a) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_u8(uint8_t a, uint8_t b) +{ + if (b > 0 && a > UINT8_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_u8(uint8_t a, uint8_t b) +{ + if (b > a) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_u16(uint16_t a, uint16_t b) +{ + if (b > 0 && a > UINT16_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_u16(uint16_t a, uint16_t b) +{ + if (b > a) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_u32(uint32_t a, uint32_t b) +{ + if (b > 0 && a > UINT32_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_u32(uint32_t a, uint32_t b) +{ + if (b > a) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_u64(uint64_t a, uint64_t b) +{ + if (b > 0 && a > UINT64_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_u64(uint64_t a, uint64_t b) +{ + if (b > a) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + +static inline guf_math_ckd_result guf_ckd_add_size_t(size_t a, size_t b) +{ + if (b > 0 && a > SIZE_MAX - b) { + return GUF_MATH_CKD_OVERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} +static inline guf_math_ckd_result guf_ckd_sub_size_t(size_t a, size_t b) +{ + if (b > a) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } +} + + +// Signed saturating/wrapping add/sub (generated with libguf/tools/ckdint-gen.py) + +static inline guf_math_ckd_result guf_saturating_add_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_add_int(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_int(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_add_int(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = (a + INT_MIN) + (b + INT_MIN); + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = (a - INT_MIN) + (b - INT_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_int(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + GUF_ASSERT(b < 0); + *result = (a + INT_MIN) - (b - INT_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_UNDERFLOW: + GUF_ASSERT(b > 0); + *result = (a - INT_MIN) - (b + INT_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT8_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT8_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT8_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT8_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = (a + INT8_MIN) + (b + INT8_MIN); + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = (a - INT8_MIN) + (b - INT8_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + GUF_ASSERT(b < 0); + *result = (a + INT8_MIN) - (b - INT8_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_UNDERFLOW: + GUF_ASSERT(b > 0); + *result = (a - INT8_MIN) - (b + INT8_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT16_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT16_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT16_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT16_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = (a + INT16_MIN) + (b + INT16_MIN); + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = (a - INT16_MIN) + (b - INT16_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + GUF_ASSERT(b < 0); + *result = (a + INT16_MIN) - (b - INT16_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_UNDERFLOW: + GUF_ASSERT(b > 0); + *result = (a - INT16_MIN) - (b + INT16_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT32_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT32_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT32_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT32_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = (a + INT32_MIN) + (b + INT32_MIN); + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = (a - INT32_MIN) + (b - INT32_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + GUF_ASSERT(b < 0); + *result = (a + INT32_MIN) - (b - INT32_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_UNDERFLOW: + GUF_ASSERT(b > 0); + *result = (a - INT32_MIN) - (b + INT32_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT64_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT64_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = INT64_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = INT64_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = (a + INT64_MIN) + (b + INT64_MIN); + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = (a - INT64_MIN) + (b - INT64_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + GUF_ASSERT(b < 0); + *result = (a + INT64_MIN) - (b - INT64_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_UNDERFLOW: + GUF_ASSERT(b > 0); + *result = (a - INT64_MIN) - (b + INT64_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = PTRDIFF_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = PTRDIFF_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = PTRDIFF_MAX; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = PTRDIFF_MIN; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = (a + PTRDIFF_MIN) + (b + PTRDIFF_MIN); + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = (a - PTRDIFF_MIN) + (b - PTRDIFF_MIN); + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + GUF_ASSERT(b < 0); + *result = (a + PTRDIFF_MIN) - (b - PTRDIFF_MIN); // TODO: not sure + break; + case GUF_MATH_CKD_UNDERFLOW: + GUF_ASSERT(b > 0); + *result = (a - PTRDIFF_MIN) - (b + PTRDIFF_MIN); // TODO: not sure + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + + +// Unsigned saturating/wrapping add/sub (generated with libguf/tools/ckdint-gen.py) + +static inline guf_math_ckd_result guf_saturating_add_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_add_uchar(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = UCHAR_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_uchar(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_UNDERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = 0; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_add_uchar(a, b); + if (result) { + *result = a + b; + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_uchar(a, b); + if (result) { + *result = a - b; + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_add_unsigned(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = UINT_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_unsigned(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_UNDERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = 0; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_add_unsigned(a, b); + if (result) { + *result = a + b; + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_unsigned(a, b); + if (result) { + *result = a - b; + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u8(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = UINT8_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u8(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_UNDERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = 0; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u8(a, b); + if (result) { + *result = a + b; + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u8(a, b); + if (result) { + *result = a - b; + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u16(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = UINT16_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u16(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_UNDERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = 0; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u16(a, b); + if (result) { + *result = a + b; + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u16(a, b); + if (result) { + *result = a - b; + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u32(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = UINT32_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u32(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_UNDERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = 0; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u32(a, b); + if (result) { + *result = a + b; + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u32(a, b); + if (result) { + *result = a - b; + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u64(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = UINT64_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u64(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_UNDERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = 0; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_u64(a, b); + if (result) { + *result = a + b; + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_u64(a, b); + if (result) { + *result = a - b; + } + return check; +} + + +static inline guf_math_ckd_result guf_saturating_add_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_size_t(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = SIZE_MAX; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} +static inline guf_math_ckd_result guf_saturating_sub_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_size_t(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_UNDERFLOW); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = 0; + break; + default: + GUF_ASSERT(false); + } + } + return check; +} + +static inline guf_math_ckd_result guf_wrapping_add_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_add_size_t(a, b); + if (result) { + *result = a + b; + } + return check; +} +static inline guf_math_ckd_result guf_wrapping_sub_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_sub_size_t(a, b); + if (result) { + *result = a - b; + } + return check; +} + +#endif diff --git a/src/guf_rand.h b/src/guf_rand.h index 0c16313..453819a 100644 --- a/src/guf_rand.h +++ b/src/guf_rand.h @@ -228,6 +228,7 @@ GUF_RAND_KWRDS double guf_rand32_normal_sample_one_f64(guf_rand32_state *state, #include "guf_common.h" #include "guf_assert.h" #include "guf_math.h" +#include "guf_math_ckdint.h" #ifdef UINT64_MAX /* @@ -711,7 +712,8 @@ GUF_RAND_KWRDS int32_t guf_rand32_range_i32(guf_rand32_state *state, int32_t min } while (step >= limit); step = step / bin_size; - GUF_ASSERT(!guf_add_is_overflow_i32(min, step)); + + GUF_ASSERT(guf_ckd_add_i32(min, step) == GUF_MATH_CKD_SUCCESS); const int32_t rnd = min + (int32_t)step; GUF_ASSERT(rnd >= min && rnd <= max); return rnd; @@ -805,7 +807,7 @@ GUF_RAND_KWRDS uint32_t guf_randrange_u32(guf_randstate *state, uint32_t min, ui } while (step >= limit); step = step / bin_size; - GUF_ASSERT(!guf_add_is_overflow_i64(min, step)); + GUF_ASSERT(guf_ckd_add_i64(min, step) == GUF_MATH_CKD_SUCCESS); const int64_t rnd = min + (int64_t)step; GUF_ASSERT(rnd >= min && rnd <= max); return rnd; diff --git a/src/guf_str.h b/src/guf_str.h index 0d88743..bb79b0d 100644 --- a/src/guf_str.h +++ b/src/guf_str.h @@ -238,6 +238,7 @@ GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str); #include "guf_common.h" #include "guf_math.h" +#include "guf_math_ckdint.h" #include #ifdef GUF_STR_IMPL @@ -1131,7 +1132,8 @@ GUF_STR_KWRDS guf_str *guf_str_try_substr(guf_str *str, ptrdiff_t pos, ptrdiff_t } GUF_ASSERT(c_str); - const ptrdiff_t pos_plus_count = guf_add_is_overflow_ptrdiff(pos, count) ? PTRDIFF_MAX : pos + count; + ptrdiff_t pos_plus_count = 0; + guf_saturating_add_ptrdiff_t(pos, count, &pos_plus_count); const ptrdiff_t substr_len = pos_plus_count > len ? len - pos : count; GUF_ASSERT(substr_len >= 0 && substr_len <= len && substr_len <= guf_str_capacity(str)); GUF_ASSERT((size_t)pos + (size_t)(substr_len) <= (size_t)len); // [*] @@ -1419,7 +1421,8 @@ GUF_STR_KWRDS guf_str_view guf_str_view_substr(guf_str_view str, ptrdiff_t pos, return (guf_str_view){.str = str.str, .len = 0}; } - const ptrdiff_t pos_plus_count = guf_add_is_overflow_ptrdiff(pos, count) ? PTRDIFF_MAX : pos + count; + ptrdiff_t pos_plus_count = 0; + guf_saturating_add_ptrdiff_t(pos, count, &pos_plus_count); const ptrdiff_t substr_len = pos_plus_count > str.len ? str.len - pos : count; GUF_ASSERT(substr_len >= 0); GUF_ASSERT(substr_len <= str.len); diff --git a/src/test/example.c b/src/test/example.c index 41aad68..5f17615 100644 --- a/src/test/example.c +++ b/src/test/example.c @@ -59,7 +59,7 @@ int main(void) guf_platform_assert_native_word_bits(); guf_allocator test_allocator = guf_allocator_libc; - guf_libc_alloc_ctx test_allocator_ctx = {.alloc_type_id = 0, .thread_id = 0, .zero_init = true}; + guf_libc_alloc_ctx test_allocator_ctx = {.zero_init = true, .track_allocs = false}; test_allocator.ctx = &test_allocator_ctx; dict_cstr_int ht; diff --git a/todo.txt b/todo.txt index 67731ab..74001b8 100644 --- a/todo.txt +++ b/todo.txt @@ -1,3 +1,8 @@ +- abs functions with signed result assume two's complement (x) +- abs functions with unsigned result assume two's complement (x) + +- header guards for optional int64_t types maybe... + - sort: add cpp #ifdef to remove restrict from declaration - separate impl and headers from tests (for compile perf) @@ -15,27 +20,6 @@ - each thread needs its own alloc and alloc_ctx; don't track granular, give each allocator it's unique id maybe? - potential idea for alloc: instead of using a pointer (8 or 4 bytes), use a 2 byte id to save space (and avoid dangling pointers): - typedef guf_allocator_id uint16_t; // allows for up to (UINT16_MAX - 1) allocators (and 0 for NULL) - guf_allocator_dbuf guf_global_allocators; - // cf. https://github.com/erincatto/box2d/blob/main/src/id_pool.c - typedef struct guf_allocator_id_pool { // Implement generically with macro "templates" - guf_allocator_id_dbuf free_ids; - guf_allocator_id next_free_id; - } guf_allocator_id_pool; - - guf_allocator_id_pool guf_global_allocator_id_pool; - - guf_allocator_id guf_register_allocator(guf_allocator allocator) { - guf_allocator_id id = get_free_id(&guf_global_allocator_id_pool); - if (!id) { - return 0; - } - guf_global_allocators[id] = allocator; - } - - guf_allocator *guf_get_allocator(guf_allocator_id id) { - return (id == 0 || id >= UINT16_MAX) ? NULL : (guf_global_allocators + id); - } - no guf_init.h diff --git a/tools/ckdint-gen.py b/tools/ckdint-gen.py new file mode 100644 index 0000000..2d592dd --- /dev/null +++ b/tools/ckdint-gen.py @@ -0,0 +1,253 @@ +""" + Generate typesafe checked arithmetic functions for libguf/src/guf_math_ckdint.h +""" + +from dataclasses import dataclass +import textwrap + +@dataclass +class IntType: + INT_TYPE: str + INT_TYPE_ABBR: str + INT_MIN: str + INT_MAX: str + +@dataclass +class UintType: + INT_TYPE: str + INT_TYPE_ABBR: str + INT_MIN: str + INT_MAX: str + +def generate_ckdint_functions(int_types: list, uint_types: list) -> str: + + ckd_add_sub_uint = textwrap.dedent(""" + static inline guf_math_ckd_result guf_ckd_add_{type_abbr}({type} a, {type} b) + {{ + if (b > 0 && a > {int_max} - b) {{ + return GUF_MATH_CKD_OVERFLOW; + }} else {{ + return GUF_MATH_CKD_SUCCESS; + }} + }} + static inline guf_math_ckd_result guf_ckd_sub_{type_abbr}({type} a, {type} b) + {{ + if (b > a) {{ + return GUF_MATH_CKD_UNDERFLOW; + }} else {{ + return GUF_MATH_CKD_SUCCESS; + }} + }} + """) + + ckd_add_sub_int = textwrap.dedent(""" + static inline guf_math_ckd_result guf_ckd_add_{type_abbr}({type} a, {type} b) + {{ + if (b > 0 && a > {int_max} - b) {{ + return GUF_MATH_CKD_OVERFLOW; + }} else if (b < 0 && a < {int_min} - b) {{ + return GUF_MATH_CKD_UNDERFLOW; + }} else {{ + return GUF_MATH_CKD_SUCCESS; + }} + }} + static inline guf_math_ckd_result guf_ckd_sub_{type_abbr}({type} a, {type} b) + {{ + if (b < 0 && a > {int_max} + b) {{ + return GUF_MATH_CKD_OVERFLOW; + }} else if (b > 0 && a < {int_min} + b) {{ + return GUF_MATH_CKD_UNDERFLOW; + }} else {{ + return GUF_MATH_CKD_SUCCESS; + }} + }} + """) + + saturating_wrapping_int = textwrap.dedent(""" + static inline guf_math_ckd_result guf_saturating_add_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b); + if (result) {{ + switch (check) {{ + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = {int_max}; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = {int_min}; + break; + default: + GUF_ASSERT(false); + }} + }} + return check; + }} + static inline guf_math_ckd_result guf_saturating_sub_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b); + if (result) {{ + switch (check) {{ + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = {int_max}; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = {int_min}; + break; + default: + GUF_ASSERT(false); + }} + }} + return check; + }} + + static inline guf_math_ckd_result guf_wrapping_add_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b); + if (result) {{ + switch (check) {{ + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = (a + {int_min}) + (b + {int_min}); + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = (a - {int_min}) + (b - {int_min}); + break; + default: + GUF_ASSERT(false); + }} + }} + return check; + }} + static inline guf_math_ckd_result guf_wrapping_sub_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b); + if (result) {{ + switch (check) {{ + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_OVERFLOW: + GUF_ASSERT(b < 0); + *result = (a + {int_min}) - (b - {int_min}); // TODO: not sure + break; + case GUF_MATH_CKD_UNDERFLOW: + GUF_ASSERT(b > 0); + *result = (a - {int_min}) - (b + {int_min}); // TODO: not sure + break; + default: + GUF_ASSERT(false); + }} + }} + return check; + }} + """) + + saturating_wrapping_uint = textwrap.dedent(""" + static inline guf_math_ckd_result guf_saturating_add_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_OVERFLOW); + if (result) {{ + switch (check) {{ + case GUF_MATH_CKD_SUCCESS: + *result = a + b; + break; + case GUF_MATH_CKD_OVERFLOW: + *result = {int_max}; + break; + default: + GUF_ASSERT(false); + }} + }} + return check; + }} + static inline guf_math_ckd_result guf_saturating_sub_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b); + GUF_ASSERT(check == GUF_MATH_CKD_SUCCESS || check == GUF_MATH_CKD_UNDERFLOW); + if (result) {{ + switch (check) {{ + case GUF_MATH_CKD_SUCCESS: + *result = a - b; + break; + case GUF_MATH_CKD_UNDERFLOW: + *result = 0; + break; + default: + GUF_ASSERT(false); + }} + }} + return check; + }} + + static inline guf_math_ckd_result guf_wrapping_add_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_add_{type_abbr}(a, b); + if (result) {{ + *result = a + b; + }} + return check; + }} + static inline guf_math_ckd_result guf_wrapping_sub_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_sub_{type_abbr}(a, b); + if (result) {{ + *result = a - b; + }} + return check; + }} + """) + + text_result = "// Signed integer add/sub checks (generated with libguf/tools/ckdint-gen.py)\n" + for type in int_types: + text_result += ckd_add_sub_int.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX) + + text_result += "\n// Unsigned integer add/sub checks (generated with libguf/tools/ckdint-gen.py) \n" + for type in uint_types: + text_result += ckd_add_sub_uint.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX) + + + text_result += "\n\n// Signed saturating/wrapping add/sub (generated with libguf/tools/ckdint-gen.py)\n" + for type in int_types: + text_result += saturating_wrapping_int.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX) + "\n" + + text_result += "\n// Unsigned saturating/wrapping add/sub (generated with libguf/tools/ckdint-gen.py)\n" + for type in uint_types: + text_result += saturating_wrapping_uint.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX) + "\n" + + return text_result + + + +if __name__ == "__main__": + + int_types = [ + IntType(INT_TYPE = "int", INT_TYPE_ABBR = "int", INT_MIN = "INT_MIN", INT_MAX = "INT_MAX"), + IntType(INT_TYPE = "int8_t", INT_TYPE_ABBR = "i8", INT_MIN = "INT8_MIN", INT_MAX = "INT8_MAX"), + IntType(INT_TYPE = "int16_t", INT_TYPE_ABBR = "i16", INT_MIN = "INT16_MIN", INT_MAX = "INT16_MAX"), + IntType(INT_TYPE = "int32_t", INT_TYPE_ABBR = "i32", INT_MIN = "INT32_MIN", INT_MAX = "INT32_MAX"), + IntType(INT_TYPE = "int64_t", INT_TYPE_ABBR = "i64", INT_MIN = "INT64_MIN", INT_MAX = "INT64_MAX"), + IntType(INT_TYPE = "ptrdiff_t", INT_TYPE_ABBR = "ptrdiff_t", INT_MIN = "PTRDIFF_MIN", INT_MAX = "PTRDIFF_MAX"), + ] + + uint_types = [ + UintType(INT_TYPE = "unsigned char", INT_TYPE_ABBR = "uchar", INT_MIN = "0", INT_MAX = "UCHAR_MAX"), + UintType(INT_TYPE = "unsigned", INT_TYPE_ABBR = "unsigned", INT_MIN = "0", INT_MAX = "UINT_MAX"), + UintType(INT_TYPE = "uint8_t", INT_TYPE_ABBR = "u8", INT_MIN = "0", INT_MAX = "UINT8_MAX"), + UintType(INT_TYPE = "uint16_t", INT_TYPE_ABBR = "u16", INT_MIN = "0", INT_MAX = "UINT16_MAX"), + UintType(INT_TYPE = "uint32_t", INT_TYPE_ABBR = "u32", INT_MIN = "0", INT_MAX = "UINT32_MAX"), + UintType(INT_TYPE = "uint64_t", INT_TYPE_ABBR = "u64", INT_MIN = "0", INT_MAX = "UINT64_MAX"), + UintType(INT_TYPE = "size_t", INT_TYPE_ABBR = "size_t", INT_MIN = "0", INT_MAX = "SIZE_MAX"), + ] + + ckd_fun_code = generate_ckdint_functions(int_types = int_types, uint_types= uint_types) + + print(ckd_fun_code) + diff --git a/tools/min_max_clamp-gen.py b/tools/min_max_clamp-gen.py new file mode 100644 index 0000000..ecfa11a --- /dev/null +++ b/tools/min_max_clamp-gen.py @@ -0,0 +1,77 @@ +""" + Generate typesafe min, max and clamp functions for libguf/src/guf_math.h +""" + +from dataclasses import dataclass +import textwrap + +@dataclass +class IntType: + INT_TYPE: str + INT_TYPE_ABBR: str + INT_MIN: str + INT_MAX: str + +@dataclass +class UintType: + INT_TYPE: str + INT_TYPE_ABBR: str + INT_MIN: str + INT_MAX: str + + +def gen_min_max_clamp(int_types: list, uint_types: list) -> str: + template = textwrap.dedent(""" + static inline {type} guf_min_{type_abbr}({type} a, {type} b) {{ return a < b ? a : b; }} + static inline {type} guf_max_{type_abbr}({type} a, {type} b) {{ return a > b ? a : b; }} + static inline {type} guf_clamp_{type_abbr}({type} x, {type} min, {type} max) {{ if (x < min) {{return min;}} if (x > max) {{return max;}} return x; }} + """) + + result = "\n// Signed min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)\n" + for t in int_types: + result += template.format(type = t.INT_TYPE, type_abbr = t.INT_TYPE_ABBR) + + result += "\n// Unsigned min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)\n" + for t in uint_types: + result += template.format(type = t.INT_TYPE, type_abbr = t.INT_TYPE_ABBR) + + return result + + +if __name__ == "__main__": + + int_types = [ + IntType(INT_TYPE = "char", INT_TYPE_ABBR = "char", INT_MIN = "CHAR_MIN", INT_MAX = "CHAR_MAX"), + IntType(INT_TYPE = "int", INT_TYPE_ABBR = "int", INT_MIN = "INT_MIN", INT_MAX = "INT_MAX"), + IntType(INT_TYPE = "long", INT_TYPE_ABBR = "long", INT_MIN = "LONG_MIN", INT_MAX = "LONG_MAX"), + IntType(INT_TYPE = "long long", INT_TYPE_ABBR = "long_long", INT_MIN = "LLONG_MIN", INT_MAX = "LLONG_MAX"), + + IntType(INT_TYPE = "int8_t", INT_TYPE_ABBR = "i8", INT_MIN = "INT8_MIN", INT_MAX = "INT8_MAX"), + IntType(INT_TYPE = "int16_t", INT_TYPE_ABBR = "i16", INT_MIN = "INT16_MIN", INT_MAX = "INT16_MAX"), + IntType(INT_TYPE = "int32_t", INT_TYPE_ABBR = "i32", INT_MIN = "INT32_MIN", INT_MAX = "INT32_MAX"), + IntType(INT_TYPE = "int64_t", INT_TYPE_ABBR = "i64", INT_MIN = "INT64_MIN", INT_MAX = "INT64_MAX"), + IntType(INT_TYPE = "ptrdiff_t", INT_TYPE_ABBR = "ptrdiff_t", INT_MIN = "PTRDIFF_MIN", INT_MAX = "PTRDIFF_MAX"), + + IntType(INT_TYPE = "float", INT_TYPE_ABBR = "f32", INT_MIN = "-FLT_MAX", INT_MAX = "FLT_MAX"), + IntType(INT_TYPE = "double", INT_TYPE_ABBR = "f64", INT_MIN = "-DBL_MAX", INT_MAX = "DBL_MAX"), + ] + + uint_types = [ + UintType(INT_TYPE = "unsigned char", INT_TYPE_ABBR = "uchar", INT_MIN = "0", INT_MAX = "UCHAR_MAX"), + UintType(INT_TYPE = "unsigned", INT_TYPE_ABBR = "unsigned", INT_MIN = "0", INT_MAX = "UINT_MAX"), + UintType(INT_TYPE = "unsigned long", INT_TYPE_ABBR = "ulong", INT_MIN = "0", INT_MAX = "ULONG_MAX"), + UintType(INT_TYPE = "unsigned long long", INT_TYPE_ABBR = "ulong_long", INT_MIN = "0", INT_MAX = "ULLONG_MAX"), + + UintType(INT_TYPE = "uint8_t", INT_TYPE_ABBR = "u8", INT_MIN = "0", INT_MAX = "UINT8_MAX"), + UintType(INT_TYPE = "uint16_t", INT_TYPE_ABBR = "u16", INT_MIN = "0", INT_MAX = "UINT16_MAX"), + UintType(INT_TYPE = "uint32_t", INT_TYPE_ABBR = "u32", INT_MIN = "0", INT_MAX = "UINT32_MAX"), + UintType(INT_TYPE = "uint64_t", INT_TYPE_ABBR = "u64", INT_MIN = "0", INT_MAX = "UINT64_MAX"), + UintType(INT_TYPE = "size_t", INT_TYPE_ABBR = "size_t", INT_MIN = "0", INT_MAX = "SIZE_MAX"), + ] + + + code = gen_min_max_clamp(int_types=int_types, uint_types=uint_types) + print(code) + + +