Add checked arithmetic

This commit is contained in:
jun 2025-05-11 08:55:03 +02:00
parent c4b68d5ad2
commit 9c417d2aa1
11 changed files with 1772 additions and 127 deletions

View File

@ -15,6 +15,12 @@ typedef struct guf_allocator {
void *ctx; void *ctx;
} guf_allocator; } 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 { typedef enum guf_alloc_fn_type {
GUF_ALLOC_FN_TYPE_ALLOC, GUF_ALLOC_FN_TYPE_ALLOC,
GUF_ALLOC_FN_TYPE_REALLOC, GUF_ALLOC_FN_TYPE_REALLOC,

View File

@ -8,7 +8,8 @@
#include "guf_alloc.h" #include "guf_alloc.h"
typedef struct guf_libc_alloc_ctx { typedef struct guf_libc_alloc_ctx {
int alloc_type_id, thread_id; guf_alloc_meta meta;
bool track_allocs;
bool zero_init; bool zero_init;
} guf_libc_alloc_ctx; } guf_libc_alloc_ctx;

View File

@ -1,14 +1,116 @@
#include "guf_common.h" #include "guf_common.h"
#include "guf_alloc.h" #include "guf_alloc.h"
#include "math.h"
typedef struct guf_alloc_info { typedef struct guf_alloc_tracker {
const char *name; FILE *log, *err_log;
ptrdiff_t allocated_bytes, freed_bytes;
size_t alloc_count, realloc_count, free_count; 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];
// }

View File

@ -22,56 +22,117 @@
static inline uint64_t guf_rotl_u64(uint64_t x, int k) {return (x << k) | (x >> (64 - k));} 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));} 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) // Signed min, max, clamp functions (generated with libguf/tools/min_max_clamp-gen.py)
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)
GUF_DEFINE_MIN_MAX_CLAMP(unsigned char, uchar) static inline char guf_min_char(char a, char b) { return a < b ? a : b; }
GUF_DEFINE_MIN_MAX_CLAMP(unsigned, unsigned) static inline char guf_max_char(char a, char b) { return a > b ? a : b; }
GUF_DEFINE_MIN_MAX_CLAMP(unsigned long, ulong) 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(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)
GUF_DEFINE_MIN_MAX_CLAMP(float, f32) static inline int guf_min_int(int a, int b) { return a < b ? a : b; }
GUF_DEFINE_MIN_MAX_CLAMP(double, f64) 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 long long guf_min_long_long(long long a, long long b) { return a < b ? a : b; }
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 long long guf_max_long_long(long long a, long long b) { return a > b ? a : b; }
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 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 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 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 int8_t guf_min_i8(int8_t a, int8_t b) { return a < b ? a : b; }
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 int8_t guf_max_i8(int8_t a, int8_t b) { return a > b ? a : b; }
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 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 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 int16_t guf_min_i16(int16_t a, int16_t b) { return a < b ? a : b; }
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 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) 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); 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) static inline bool guf_size_calc_safe(ptrdiff_t count, ptrdiff_t sizeof_elem, ptrdiff_t *result)
{ {
if (count < 0 || sizeof_elem <= 0) { 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; return is_safe;
} }
// cf. https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 (last-retrieved 2025-03-19) // 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_u8(uint8_t x) { return x && !(x & (x - 1)); }
static inline bool guf_is_pow2_u16(uint16_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_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 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_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_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_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;} static bool guf_nearly_one_f64(double x, double eps) {return fabs(x - 1) <= eps;}

1203
src/guf_math_ckdint.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -228,6 +228,7 @@ GUF_RAND_KWRDS double guf_rand32_normal_sample_one_f64(guf_rand32_state *state,
#include "guf_common.h" #include "guf_common.h"
#include "guf_assert.h" #include "guf_assert.h"
#include "guf_math.h" #include "guf_math.h"
#include "guf_math_ckdint.h"
#ifdef UINT64_MAX #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); } while (step >= limit);
step = step / bin_size; 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; const int32_t rnd = min + (int32_t)step;
GUF_ASSERT(rnd >= min && rnd <= max); GUF_ASSERT(rnd >= min && rnd <= max);
return rnd; return rnd;
@ -805,7 +807,7 @@ GUF_RAND_KWRDS uint32_t guf_randrange_u32(guf_randstate *state, uint32_t min, ui
} while (step >= limit); } while (step >= limit);
step = step / bin_size; 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; const int64_t rnd = min + (int64_t)step;
GUF_ASSERT(rnd >= min && rnd <= max); GUF_ASSERT(rnd >= min && rnd <= max);
return rnd; return rnd;

View File

@ -238,6 +238,7 @@ GUF_STR_KWRDS bool guf_str_is_valid(const guf_str *str);
#include "guf_common.h" #include "guf_common.h"
#include "guf_math.h" #include "guf_math.h"
#include "guf_math_ckdint.h"
#include <string.h> #include <string.h>
#ifdef GUF_STR_IMPL #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); 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; 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(substr_len >= 0 && substr_len <= len && substr_len <= guf_str_capacity(str));
GUF_ASSERT((size_t)pos + (size_t)(substr_len) <= (size_t)len); // [*] 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}; 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; const ptrdiff_t substr_len = pos_plus_count > str.len ? str.len - pos : count;
GUF_ASSERT(substr_len >= 0); GUF_ASSERT(substr_len >= 0);
GUF_ASSERT(substr_len <= str.len); GUF_ASSERT(substr_len <= str.len);

View File

@ -59,7 +59,7 @@ int main(void)
guf_platform_assert_native_word_bits(); guf_platform_assert_native_word_bits();
guf_allocator test_allocator = guf_allocator_libc; 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; test_allocator.ctx = &test_allocator_ctx;
dict_cstr_int ht; dict_cstr_int ht;

View File

@ -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 - sort: add cpp #ifdef to remove restrict from declaration
- separate impl and headers from tests (for compile perf) - 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? - 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): - 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 - no guf_init.h

253
tools/ckdint-gen.py Normal file
View File

@ -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)

View File

@ -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)