From 873cdf20b1ea10f360cfd42fc2a9711924f68a27 Mon Sep 17 00:00:00 2001 From: jun <83899451+zeichensystem@users.noreply.github.com> Date: Tue, 13 May 2025 12:42:59 +0200 Subject: [PATCH] Add checked mul arithmetic --- src/guf_math_ckdint.h | 610 +++++++++++++++++++++++++++++++++++++++++- todo.txt | 3 +- tools/ckdint-gen.py | 108 +++++++- 3 files changed, 711 insertions(+), 10 deletions(-) diff --git a/src/guf_math_ckdint.h b/src/guf_math_ckdint.h index 22f867c..30d21d7 100644 --- a/src/guf_math_ckdint.h +++ b/src/guf_math_ckdint.h @@ -18,6 +18,11 @@ typedef enum guf_math_ckd_result {GUF_MATH_CKD_SUCCESS = 0, GUF_MATH_CKD_OVERFLO - 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_mul_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)) @@ -32,6 +37,11 @@ typedef enum guf_math_ckd_result {GUF_MATH_CKD_SUCCESS = 0, GUF_MATH_CKD_OVERFLO - 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_mul_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)) @@ -46,9 +56,18 @@ typedef enum guf_math_ckd_result {GUF_MATH_CKD_SUCCESS = 0, GUF_MATH_CKD_OVERFLO - 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_mul_TYPE(TYPE a, TYPE b, TYPE *result); + // NOTE/TODO: guf_wrapping_mul_TYPE relies on implementation-defined unsigned-to-signed two's complement conversion on over/underflow + // cf. https://stackoverflow.com/questions/76900522/can-you-ensure-overflow-wrapping-behavior-for-signed-integer-arithmetic-in-c (last-retrieved 2025-05-13) + - 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) - + cf. https://stackoverflow.com/questions/54318815/integer-overflow-w-multiplication-in-c (last-retrieved 2025-05-11) + cf. https://stackoverflow.com/questions/29808397/how-to-portably-find-out-minint-max-absint-min (last-retrieved 2025-05-11) */ @@ -74,6 +93,30 @@ static inline guf_math_ckd_result guf_ckd_sub_int(int a, int b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_int(int a, int b) +{ + if (b > 0) { + if (a > INT_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a < INT_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT_MIN != -INT_MAX && b == -1) { // Prevent potential (INT_MIN / b) overflow for b == -1 + return a == INT_MIN ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; + } else if (a < INT_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a > INT_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} static inline guf_math_ckd_result guf_ckd_add_i8(int8_t a, int8_t b) { @@ -95,6 +138,30 @@ static inline guf_math_ckd_result guf_ckd_sub_i8(int8_t a, int8_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_i8(int8_t a, int8_t b) +{ + if (b > 0) { + if (a > INT8_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a < INT8_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT8_MIN != -INT8_MAX && b == -1) { // Prevent potential (INT8_MIN / b) overflow for b == -1 + return a == INT8_MIN ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; + } else if (a < INT8_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a > INT8_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} static inline guf_math_ckd_result guf_ckd_add_i16(int16_t a, int16_t b) { @@ -116,6 +183,30 @@ static inline guf_math_ckd_result guf_ckd_sub_i16(int16_t a, int16_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_i16(int16_t a, int16_t b) +{ + if (b > 0) { + if (a > INT16_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a < INT16_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT16_MIN != -INT16_MAX && b == -1) { // Prevent potential (INT16_MIN / b) overflow for b == -1 + return a == INT16_MIN ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; + } else if (a < INT16_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a > INT16_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} static inline guf_math_ckd_result guf_ckd_add_i32(int32_t a, int32_t b) { @@ -137,6 +228,30 @@ static inline guf_math_ckd_result guf_ckd_sub_i32(int32_t a, int32_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_i32(int32_t a, int32_t b) +{ + if (b > 0) { + if (a > INT32_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a < INT32_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT32_MIN != -INT32_MAX && b == -1) { // Prevent potential (INT32_MIN / b) overflow for b == -1 + return a == INT32_MIN ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; + } else if (a < INT32_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a > INT32_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} static inline guf_math_ckd_result guf_ckd_add_i64(int64_t a, int64_t b) { @@ -158,6 +273,30 @@ static inline guf_math_ckd_result guf_ckd_sub_i64(int64_t a, int64_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_i64(int64_t a, int64_t b) +{ + if (b > 0) { + if (a > INT64_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a < INT64_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (INT64_MIN != -INT64_MAX && b == -1) { // Prevent potential (INT64_MIN / b) overflow for b == -1 + return a == INT64_MIN ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; + } else if (a < INT64_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a > INT64_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} static inline guf_math_ckd_result guf_ckd_add_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) { @@ -179,6 +318,30 @@ static inline guf_math_ckd_result guf_ckd_sub_ptrdiff_t(ptrdiff_t a, ptrdiff_t b return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_ptrdiff_t(ptrdiff_t a, ptrdiff_t b) +{ + if (b > 0) { + if (a > PTRDIFF_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a < PTRDIFF_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else if (b < 0) { + if (PTRDIFF_MIN != -PTRDIFF_MAX && b == -1) { // Prevent potential (PTRDIFF_MIN / b) overflow for b == -1 + return a == PTRDIFF_MIN ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; + } else if (a < PTRDIFF_MAX / b) { + return GUF_MATH_CKD_OVERFLOW; + } else if (a > PTRDIFF_MIN / b) { + return GUF_MATH_CKD_UNDERFLOW; + } else { + return GUF_MATH_CKD_SUCCESS; + } + } else { + return GUF_MATH_CKD_SUCCESS; + } +} // Unsigned integer add/sub checks (generated with libguf/tools/ckdint-gen.py) @@ -198,6 +361,11 @@ static inline guf_math_ckd_result guf_ckd_sub_uchar(unsigned char a, unsigned ch return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_uchar(unsigned char a, unsigned char b) +{ + const unsigned char c = a * b; + return a != 0 && ((c / a) != b) ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; +} static inline guf_math_ckd_result guf_ckd_add_unsigned(unsigned a, unsigned b) { @@ -215,6 +383,11 @@ static inline guf_math_ckd_result guf_ckd_sub_unsigned(unsigned a, unsigned b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_unsigned(unsigned a, unsigned b) +{ + const unsigned c = a * b; + return a != 0 && ((c / a) != b) ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; +} static inline guf_math_ckd_result guf_ckd_add_u8(uint8_t a, uint8_t b) { @@ -232,6 +405,11 @@ static inline guf_math_ckd_result guf_ckd_sub_u8(uint8_t a, uint8_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_u8(uint8_t a, uint8_t b) +{ + const uint8_t c = a * b; + return a != 0 && ((c / a) != b) ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; +} static inline guf_math_ckd_result guf_ckd_add_u16(uint16_t a, uint16_t b) { @@ -249,6 +427,11 @@ static inline guf_math_ckd_result guf_ckd_sub_u16(uint16_t a, uint16_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_u16(uint16_t a, uint16_t b) +{ + const uint16_t c = a * b; + return a != 0 && ((c / a) != b) ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; +} static inline guf_math_ckd_result guf_ckd_add_u32(uint32_t a, uint32_t b) { @@ -266,6 +449,11 @@ static inline guf_math_ckd_result guf_ckd_sub_u32(uint32_t a, uint32_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_u32(uint32_t a, uint32_t b) +{ + const uint32_t c = a * b; + return a != 0 && ((c / a) != b) ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; +} static inline guf_math_ckd_result guf_ckd_add_u64(uint64_t a, uint64_t b) { @@ -283,6 +471,11 @@ static inline guf_math_ckd_result guf_ckd_sub_u64(uint64_t a, uint64_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_u64(uint64_t a, uint64_t b) +{ + const uint64_t c = a * b; + return a != 0 && ((c / a) != b) ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; +} static inline guf_math_ckd_result guf_ckd_add_size_t(size_t a, size_t b) { @@ -300,6 +493,11 @@ static inline guf_math_ckd_result guf_ckd_sub_size_t(size_t a, size_t b) return GUF_MATH_CKD_SUCCESS; } } +static inline guf_math_ckd_result guf_ckd_mul_size_t(size_t a, size_t b) +{ + const size_t c = a * b; + return a != 0 && ((c / a) != b) ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; +} // Signed saturating/wrapping add/sub (generated with libguf/tools/ckdint-gen.py) @@ -344,6 +542,26 @@ static inline guf_math_ckd_result guf_saturating_sub_int(int a, int b, int *resu } return check; } +static inline guf_math_ckd_result guf_saturating_mul_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) { @@ -387,6 +605,24 @@ static inline guf_math_ckd_result guf_wrapping_sub_int(int a, int b, int *result } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_int(int a, int b, int *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_int(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW: + case GUF_MATH_CKD_UNDERFLOW: + *result = (int)((unsigned)a * (unsigned)b); // TODO: Implementation defined... + 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) @@ -429,6 +665,26 @@ static inline guf_math_ckd_result guf_saturating_sub_i8(int8_t a, int8_t b, int8 } return check; } +static inline guf_math_ckd_result guf_saturating_mul_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) { @@ -472,6 +728,24 @@ static inline guf_math_ckd_result guf_wrapping_sub_i8(int8_t a, int8_t b, int8_t } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_i8(int8_t a, int8_t b, int8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i8(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW: + case GUF_MATH_CKD_UNDERFLOW: + *result = (int8_t)((uint8_t)a * (uint8_t)b); // TODO: Implementation defined... + 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) @@ -514,6 +788,26 @@ static inline guf_math_ckd_result guf_saturating_sub_i16(int16_t a, int16_t b, i } return check; } +static inline guf_math_ckd_result guf_saturating_mul_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) { @@ -557,6 +851,24 @@ static inline guf_math_ckd_result guf_wrapping_sub_i16(int16_t a, int16_t b, int } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_i16(int16_t a, int16_t b, int16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i16(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW: + case GUF_MATH_CKD_UNDERFLOW: + *result = (int16_t)((uint16_t)a * (uint16_t)b); // TODO: Implementation defined... + 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) @@ -599,6 +911,26 @@ static inline guf_math_ckd_result guf_saturating_sub_i32(int32_t a, int32_t b, i } return check; } +static inline guf_math_ckd_result guf_saturating_mul_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) { @@ -642,6 +974,24 @@ static inline guf_math_ckd_result guf_wrapping_sub_i32(int32_t a, int32_t b, int } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_i32(int32_t a, int32_t b, int32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i32(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW: + case GUF_MATH_CKD_UNDERFLOW: + *result = (int32_t)((uint32_t)a * (uint32_t)b); // TODO: Implementation defined... + 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) @@ -684,6 +1034,26 @@ static inline guf_math_ckd_result guf_saturating_sub_i64(int64_t a, int64_t b, i } return check; } +static inline guf_math_ckd_result guf_saturating_mul_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) { @@ -727,6 +1097,24 @@ static inline guf_math_ckd_result guf_wrapping_sub_i64(int64_t a, int64_t b, int } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_i64(int64_t a, int64_t b, int64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_i64(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW: + case GUF_MATH_CKD_UNDERFLOW: + *result = (int64_t)((uint64_t)a * (uint64_t)b); // TODO: Implementation defined... + 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) @@ -769,6 +1157,26 @@ static inline guf_math_ckd_result guf_saturating_sub_ptrdiff_t(ptrdiff_t a, ptrd } return check; } +static inline guf_math_ckd_result guf_saturating_mul_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) { @@ -812,6 +1220,24 @@ static inline guf_math_ckd_result guf_wrapping_sub_ptrdiff_t(ptrdiff_t a, ptrdif } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_ptrdiff_t(ptrdiff_t a, ptrdiff_t b, ptrdiff_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_ptrdiff_t(a, b); + if (result) { + switch (check) { + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW: + case GUF_MATH_CKD_UNDERFLOW: + *result = (ptrdiff_t)((size_t)a * (size_t)b); // TODO: Implementation defined... + break; + default: + GUF_ASSERT(false); + } + } + return check; +} // Unsigned saturating/wrapping add/sub (generated with libguf/tools/ckdint-gen.py) @@ -852,6 +1278,24 @@ static inline guf_math_ckd_result guf_saturating_sub_uchar(unsigned char a, unsi } return check; } +static inline guf_math_ckd_result guf_saturating_mul_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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_wrapping_add_uchar(unsigned char a, unsigned char b, unsigned char *result) { @@ -869,6 +1313,14 @@ static inline guf_math_ckd_result guf_wrapping_sub_uchar(unsigned char a, unsign } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_uchar(unsigned char a, unsigned char b, unsigned char *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) @@ -907,6 +1359,24 @@ static inline guf_math_ckd_result guf_saturating_sub_unsigned(unsigned a, unsign } return check; } +static inline guf_math_ckd_result guf_saturating_mul_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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_wrapping_add_unsigned(unsigned a, unsigned b, unsigned *result) { @@ -924,6 +1394,14 @@ static inline guf_math_ckd_result guf_wrapping_sub_unsigned(unsigned a, unsigned } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_unsigned(unsigned a, unsigned b, unsigned *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) @@ -962,6 +1440,24 @@ static inline guf_math_ckd_result guf_saturating_sub_u8(uint8_t a, uint8_t b, ui } return check; } +static inline guf_math_ckd_result guf_saturating_mul_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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_wrapping_add_u8(uint8_t a, uint8_t b, uint8_t *result) { @@ -979,6 +1475,14 @@ static inline guf_math_ckd_result guf_wrapping_sub_u8(uint8_t a, uint8_t b, uint } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_u8(uint8_t a, uint8_t b, uint8_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) @@ -1017,6 +1521,24 @@ static inline guf_math_ckd_result guf_saturating_sub_u16(uint16_t a, uint16_t b, } return check; } +static inline guf_math_ckd_result guf_saturating_mul_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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_wrapping_add_u16(uint16_t a, uint16_t b, uint16_t *result) { @@ -1034,6 +1556,14 @@ static inline guf_math_ckd_result guf_wrapping_sub_u16(uint16_t a, uint16_t b, u } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_u16(uint16_t a, uint16_t b, uint16_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) @@ -1072,6 +1602,24 @@ static inline guf_math_ckd_result guf_saturating_sub_u32(uint32_t a, uint32_t b, } return check; } +static inline guf_math_ckd_result guf_saturating_mul_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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_wrapping_add_u32(uint32_t a, uint32_t b, uint32_t *result) { @@ -1089,6 +1637,14 @@ static inline guf_math_ckd_result guf_wrapping_sub_u32(uint32_t a, uint32_t b, u } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_u32(uint32_t a, uint32_t b, uint32_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) @@ -1127,6 +1683,24 @@ static inline guf_math_ckd_result guf_saturating_sub_u64(uint64_t a, uint64_t b, } return check; } +static inline guf_math_ckd_result guf_saturating_mul_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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_wrapping_add_u64(uint64_t a, uint64_t b, uint64_t *result) { @@ -1144,6 +1718,14 @@ static inline guf_math_ckd_result guf_wrapping_sub_u64(uint64_t a, uint64_t b, u } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_u64(uint64_t a, uint64_t b, uint64_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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) @@ -1182,6 +1764,24 @@ static inline guf_math_ckd_result guf_saturating_sub_size_t(size_t a, size_t b, } return check; } +static inline guf_math_ckd_result guf_saturating_mul_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_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_wrapping_add_size_t(size_t a, size_t b, size_t *result) { @@ -1199,5 +1799,13 @@ static inline guf_math_ckd_result guf_wrapping_sub_size_t(size_t a, size_t b, si } return check; } +static inline guf_math_ckd_result guf_wrapping_mul_size_t(size_t a, size_t b, size_t *result) +{ + const guf_math_ckd_result check = guf_ckd_mul_size_t(a, b); + if (result) { + *result = a * b; + } + return check; +} #endif diff --git a/todo.txt b/todo.txt index 74001b8..5c7b423 100644 --- a/todo.txt +++ b/todo.txt @@ -1,5 +1,4 @@ -- abs functions with signed result assume two's complement (x) -- abs functions with unsigned result assume two's complement (x) +- guf_wrapping_mul_TYPE: Don't rely on implementation defined behaviour - header guards for optional int64_t types maybe... diff --git a/tools/ckdint-gen.py b/tools/ckdint-gen.py index 2d592dd..49bf71a 100644 --- a/tools/ckdint-gen.py +++ b/tools/ckdint-gen.py @@ -11,6 +11,7 @@ class IntType: INT_TYPE_ABBR: str INT_MIN: str INT_MAX: str + UINT_TYPE: str = "size_t" @dataclass class UintType: @@ -38,6 +39,11 @@ def generate_ckdint_functions(int_types: list, uint_types: list) -> str: return GUF_MATH_CKD_SUCCESS; }} }} + static inline guf_math_ckd_result guf_ckd_mul_{type_abbr}({type} a, {type} b) + {{ + const {type} c = a * b; + return a != 0 && ((c / a) != b) ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; + }} """) ckd_add_sub_int = textwrap.dedent(""" @@ -61,6 +67,30 @@ def generate_ckdint_functions(int_types: list, uint_types: list) -> str: return GUF_MATH_CKD_SUCCESS; }} }} + static inline guf_math_ckd_result guf_ckd_mul_{type_abbr}({type} a, {type} b) + {{ + if (b > 0) {{ + if (a > {int_max} / b) {{ + return GUF_MATH_CKD_OVERFLOW; + }} else if (a < {int_min} / b) {{ + return GUF_MATH_CKD_UNDERFLOW; + }} else {{ + return GUF_MATH_CKD_SUCCESS; + }} + }} else if (b < 0) {{ + if ({int_min} != -{int_max} && b == -1) {{ // Prevent potential ({int_min} / b) overflow for b == -1 + return a == {int_min} ? GUF_MATH_CKD_OVERFLOW : GUF_MATH_CKD_SUCCESS; + }} else if (a < {int_max} / b) {{ + return GUF_MATH_CKD_OVERFLOW; + }} else if (a > {int_min} / b) {{ + return GUF_MATH_CKD_UNDERFLOW; + }} else {{ + return GUF_MATH_CKD_SUCCESS; + }} + }} else {{ + return GUF_MATH_CKD_SUCCESS; + }} + }} """) saturating_wrapping_int = textwrap.dedent(""" @@ -104,6 +134,26 @@ def generate_ckdint_functions(int_types: list, uint_types: list) -> str: }} return check; }} + static inline guf_math_ckd_result guf_saturating_mul_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_mul_{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) {{ @@ -147,6 +197,24 @@ def generate_ckdint_functions(int_types: list, uint_types: list) -> str: }} return check; }} + static inline guf_math_ckd_result guf_wrapping_mul_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_mul_{type_abbr}(a, b); + if (result) {{ + switch (check) {{ + case GUF_MATH_CKD_SUCCESS: + *result = a * b; + break; + case GUF_MATH_CKD_OVERFLOW: + case GUF_MATH_CKD_UNDERFLOW: + *result = ({type})(({uint_type})a * ({uint_type})b); // TODO: Implementation defined... + break; + default: + GUF_ASSERT(false); + }} + }} + return check; + }} """) saturating_wrapping_uint = textwrap.dedent(""" @@ -186,6 +254,24 @@ def generate_ckdint_functions(int_types: list, uint_types: list) -> str: }} return check; }} + static inline guf_math_ckd_result guf_saturating_mul_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_mul_{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_wrapping_add_{type_abbr}({type} a, {type} b, {type} *result) {{ @@ -203,6 +289,14 @@ def generate_ckdint_functions(int_types: list, uint_types: list) -> str: }} return check; }} + static inline guf_math_ckd_result guf_wrapping_mul_{type_abbr}({type} a, {type} b, {type} *result) + {{ + const guf_math_ckd_result check = guf_ckd_mul_{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" @@ -216,7 +310,7 @@ def generate_ckdint_functions(int_types: list, uint_types: list) -> str: 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 += saturating_wrapping_int.format(type = type.INT_TYPE, type_abbr = type.INT_TYPE_ABBR, int_min = type.INT_MIN, int_max = type.INT_MAX, uint_type = type.UINT_TYPE) + "\n" text_result += "\n// Unsigned saturating/wrapping add/sub (generated with libguf/tools/ckdint-gen.py)\n" for type in uint_types: @@ -229,12 +323,12 @@ def generate_ckdint_functions(int_types: list, uint_types: list) -> str: 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"), + IntType(INT_TYPE = "int", INT_TYPE_ABBR = "int", INT_MIN = "INT_MIN", INT_MAX = "INT_MAX", UINT_TYPE = "unsigned"), + IntType(INT_TYPE = "int8_t", INT_TYPE_ABBR = "i8", INT_MIN = "INT8_MIN", INT_MAX = "INT8_MAX", UINT_TYPE = "uint8_t"), + IntType(INT_TYPE = "int16_t", INT_TYPE_ABBR = "i16", INT_MIN = "INT16_MIN", INT_MAX = "INT16_MAX", UINT_TYPE = "uint16_t"), + IntType(INT_TYPE = "int32_t", INT_TYPE_ABBR = "i32", INT_MIN = "INT32_MIN", INT_MAX = "INT32_MAX", UINT_TYPE = "uint32_t"), + IntType(INT_TYPE = "int64_t", INT_TYPE_ABBR = "i64", INT_MIN = "INT64_MIN", INT_MAX = "INT64_MAX", UINT_TYPE = "uint64_t"), + IntType(INT_TYPE = "ptrdiff_t", INT_TYPE_ABBR = "ptrdiff_t", INT_MIN = "PTRDIFF_MIN", INT_MAX = "PTRDIFF_MAX", UINT_TYPE = "size_t"), # TODO: size_t is not necessarily the unsigned ptrdiff_t equivalent ] uint_types = [