Skip to content

Commit 774cb32

Browse files
committed
utils: add functions for uint/int and sign conversions
Add functions that do not rely on undefined behavior for converting unsigned to signed negative integers (and viceversa), for checking if conversion overflows and for conditionally negate. Start using newly introduced utilities in both intn and externalterm (an old macro is removed). Signed-off-by: Davide Bettio <davide@uninstall.it>
1 parent 5bc0a65 commit 774cb32

File tree

4 files changed

+102
-57
lines changed

4 files changed

+102
-57
lines changed

src/libAtomVM/externalterm.c

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,6 @@
5959
#define MAP_EXT_BASE_SIZE 5
6060
#define SMALL_ATOM_EXT_BASE_SIZE 2
6161

62-
// Assuming two's-complement implementation of signed integers
63-
#define REMOVE_SIGN(val, unsigned_type) \
64-
((val) < 0 ? ~((unsigned_type) (val)) + 1 : (unsigned_type) (val))
65-
6662
// MAINTENANCE NOTE. Range checking on the external term buffer is only performed in
6763
// the calculate_heap_usage function, which will fail with an invalid term if there is
6864
// insufficient space in the external term buffer (preventing reading off the end of the
@@ -250,12 +246,13 @@ static int serialize_term(uint8_t *buf, term t, GlobalContext *glb)
250246
}
251247
return INTEGER_EXT_SIZE;
252248
} else {
253-
avm_uint64_t unsigned_val = REMOVE_SIGN(val, avm_uint64_t);
249+
bool is_negative;
250+
avm_uint64_t unsigned_val = int64_safe_unsigned_abs_set_flag(val, &is_negative);
254251
uint8_t num_bytes = get_num_bytes(unsigned_val);
255252
if (buf != NULL) {
256253
buf[0] = SMALL_BIG_EXT;
257254
buf[1] = num_bytes;
258-
buf[2] = val < 0 ? 0x01 : 0x00;
255+
buf[2] = is_negative ? 0x01 : 0x00;
259256
write_bytes(buf + 3, unsigned_val);
260257
}
261258
return SMALL_BIG_EXT_BASE_SIZE + num_bytes;
@@ -439,13 +436,9 @@ static term parse_external_terms(const uint8_t *external_term_buf, size_t *eterm
439436
// NB due to call to calculate_heap_usage, there is no loss of precision:
440437
// 1. 0 <= unsigned_value <= INT64_MAX if sign is 0
441438
// 2. 0 <= unsigned_value <= INT64_MAX + 1 if sign is not 0
442-
avm_int64_t value = 0;
443-
if (sign != 0x00) {
444-
value = -((avm_int64_t) unsigned_value);
445-
} else {
446-
value = (avm_int64_t) unsigned_value;
447-
}
439+
avm_int64_t value = int64_cond_neg_unsigned(sign != 0x00, unsigned_value);
448440
*eterm_size = SMALL_BIG_EXT_BASE_SIZE + num_bytes;
441+
449442
return term_make_maybe_boxed_int64(value, heap);
450443
}
451444

@@ -700,12 +693,7 @@ static int calculate_heap_usage(const uint8_t *external_term_buf, size_t remaini
700693
}
701694
// Compute the size with the sign as -2^27 or -2^59 can be encoded
702695
// on 1 term while 2^27 and 2^59 respectively (32/64 bits) cannot.
703-
avm_int64_t value = 0;
704-
if (sign != 0x00) {
705-
value = -((avm_int64_t) unsigned_value);
706-
} else {
707-
value = (avm_int64_t) unsigned_value;
708-
}
696+
avm_int64_t value = int64_cond_neg_unsigned(sign != 0x00, unsigned_value);
709697
return term_boxed_integer_size(value);
710698
}
711699

src/libAtomVM/intn.h

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,16 +77,8 @@ static inline void intn_copy(
7777
memset(out + num_len, 0, (extend_to - num_len) * sizeof(intn_digit_t));
7878
}
7979

80-
static inline void int64_to_intn_2(int64_t i64, uint32_t out[], intn_integer_sign_t *out_sign)
80+
static inline void intn_u64_to_digits(uint64_t absu64, uint32_t out[])
8181
{
82-
uint64_t absu64;
83-
if (i64 < 0) {
84-
absu64 = -i64;
85-
*out_sign = IntNNegativeInteger;
86-
} else {
87-
absu64 = i64;
88-
*out_sign = IntNPositiveInteger;
89-
}
9082
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
9183
memcpy(out, &absu64, sizeof(absu64));
9284
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
@@ -98,24 +90,39 @@ static inline void int64_to_intn_2(int64_t i64, uint32_t out[], intn_integer_sig
9890
#endif
9991
}
10092

93+
static inline void int64_to_intn_2(int64_t i64, uint32_t out[], intn_integer_sign_t *out_sign)
94+
{
95+
bool is_negative;
96+
uint64_t absu64 = int64_safe_unsigned_abs_set_flag(i64, &is_negative);
97+
*out_sign = is_negative ? IntNNegativeInteger : IntNPositiveInteger;
98+
intn_u64_to_digits(absu64, out);
99+
}
100+
101+
static inline uint64_t intn_digits_to_u64(const intn_digit_t num[])
102+
{
103+
uint64_t utmp;
104+
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
105+
memcpy(&utmp, num, sizeof(uint64_t));
106+
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
107+
utmp = (((uint64_t) num[1] << 32) | (uint64_t) num[0]);
108+
#else
109+
#error "Unsupported endianess"
110+
#endif
111+
112+
return utmp;
113+
}
114+
101115
static inline int64_t intn_2_digits_to_int64(
102116
const intn_digit_t num[], size_t len, intn_integer_sign_t sign)
103117
{
104118
switch (len) {
105119
case 0:
106120
return 0;
107121
case 1:
108-
return (sign == IntNPositiveInteger) ? (int32_t) num[0] : -((int32_t) num[0]);
122+
return int32_cond_neg_unsigned(sign == IntNNegativeInteger, num[0]);
109123
case 2: {
110-
int64_t ret;
111-
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
112-
memcpy(&ret, num, sizeof(int64_t));
113-
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
114-
ret = (((uint64_t) num[1] << 32) | (uint64_t) num[0]);
115-
#else
116-
#error "Unsupported endianess"
117-
#endif
118-
return (sign == IntNPositiveInteger) ? ret : -ret;
124+
uint64_t utmp = intn_digits_to_u64(num);
125+
return int64_cond_neg_unsigned(sign == IntNNegativeInteger, utmp);
119126
}
120127
default:
121128
UNREACHABLE();
@@ -127,12 +134,8 @@ static inline bool intn_fits_int64(const intn_digit_t num[], size_t len, intn_in
127134
if (len < INTN_INT64_LEN) {
128135
return true;
129136
} else if (len == INTN_INT64_LEN) {
130-
uint64_t u64 = (((uint64_t) num[1]) << 32) | (num[0]);
131-
if (sign == IntNPositiveInteger) {
132-
return u64 <= ((uint64_t) INT64_MAX);
133-
} else {
134-
return u64 <= ((uint64_t) INT64_MAX) + 1;
135-
}
137+
uint64_t u64 = intn_digits_to_u64(num);
138+
return !uint64_does_overflow_int64(u64, sign == IntNNegativeInteger);
136139
}
137140
return false;
138141
}

src/libAtomVM/utils.c

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -207,17 +207,6 @@ size_t int64_write_to_ascii_buf(int64_t n, unsigned int base, char *out_end)
207207

208208
#endif
209209

210-
static inline int64_t int64_safe_neg_unsigned(uint64_t u64)
211-
{
212-
return (-((int64_t) (u64 - 1)) - 1);
213-
}
214-
215-
static inline int64_t uint64_does_overflow_int64(uint64_t val, bool is_negative)
216-
{
217-
return ((is_negative && (val > ((uint64_t) INT64_MAX) + 1))
218-
|| (!is_negative && (val > ((uint64_t) INT64_MAX))));
219-
}
220-
221210
static inline bool is_base_10_digit(char c)
222211
{
223212
return (c >= '0') && (c <= '9');
@@ -265,7 +254,7 @@ static int buf10_to_int64(
265254
utmp /= 10;
266255
pos--;
267256
}
268-
*out = is_negative ? int64_safe_neg_unsigned(utmp) : (int64_t) utmp;
257+
*out = int64_cond_neg_unsigned(is_negative, utmp);
269258
return pos;
270259

271260
#elif INTPTR_MAX == INT32_MAX
@@ -379,7 +368,7 @@ static int buf16_to_int64(
379368
utmp >>= 4;
380369
pos--;
381370
}
382-
*out = is_negative ? int64_safe_neg_unsigned(utmp) : (int64_t) utmp;
371+
*out = int64_cond_neg_unsigned(is_negative, utmp);
383372
return pos;
384373

385374
#elif INTPTR_MAX == INT32_MAX
@@ -407,7 +396,7 @@ static int buf16_to_int64(
407396
pos--;
408397
}
409398
// this trick is useful to avoid any intermediate undefined/overflow
410-
*out = is_negative ? int64_safe_neg_unsigned(combined) : (int64_t) combined;
399+
*out = int64_cond_neg_unsigned(is_negative, combined);
411400

412401
return pos;
413402
#else

src/libAtomVM/utils.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#define _UTILS_H_
3030

3131
#include <inttypes.h>
32+
#include <stdbool.h>
3233
#include <stddef.h>
3334
#include <stdio.h>
3435
#include <stdlib.h>
@@ -348,6 +349,70 @@ static inline __attribute__((always_inline)) func_ptr_t cast_void_to_func_ptr(vo
348349
#define ASSUME(...)
349350
#endif
350351

352+
static inline int32_t int32_neg_unsigned(uint32_t u32)
353+
{
354+
return (UINT32_C(0) - u32);
355+
}
356+
357+
static inline int64_t int64_neg_unsigned(uint64_t u64)
358+
{
359+
return (UINT64_C(0) - u64);
360+
}
361+
362+
static inline int32_t int32_cond_neg_unsigned(bool negative, uint32_t u32)
363+
{
364+
return negative ? int32_neg_unsigned(u32) : (int32_t) u32;
365+
}
366+
367+
static inline int64_t int64_cond_neg_unsigned(bool negative, uint64_t u64)
368+
{
369+
return negative ? int64_neg_unsigned(u64) : (int64_t) u64;
370+
}
371+
372+
static inline bool uint32_does_overflow_int32(uint32_t u32, bool is_negative)
373+
{
374+
return ((is_negative && (u32 > ((uint32_t) INT32_MAX) + 1))
375+
|| (!is_negative && (u32 > ((uint32_t) INT32_MAX))));
376+
}
377+
378+
static inline bool uint64_does_overflow_int64(uint64_t u64, bool is_negative)
379+
{
380+
return ((is_negative && (u64 > ((uint64_t) INT64_MAX) + 1))
381+
|| (!is_negative && (u64 > ((uint64_t) INT64_MAX))));
382+
}
383+
384+
static inline uint32_t int32_safe_unsigned_abs(int32_t i32)
385+
{
386+
return (i32 < 0) ? ((uint32_t) - (i32 + 1)) + 1 : (uint32_t) i32;
387+
}
388+
389+
static inline uint64_t int64_safe_unsigned_abs(int64_t i64)
390+
{
391+
return (i64 < 0) ? ((uint64_t) - (i64 + 1)) + 1 : (uint64_t) i64;
392+
}
393+
394+
static inline bool int32_is_negative(int32_t i32)
395+
{
396+
return ((uint32_t) i32) >> 31;
397+
}
398+
399+
static inline bool int64_is_negative(int64_t i64)
400+
{
401+
return ((uint64_t) i64) >> 63;
402+
}
403+
404+
static inline uint32_t int32_safe_unsigned_abs_set_flag(int32_t i32, bool *is_negative)
405+
{
406+
*is_negative = int32_is_negative(i32);
407+
return int32_safe_unsigned_abs(i32);
408+
}
409+
410+
static inline uint64_t int64_safe_unsigned_abs_set_flag(int64_t i64, bool *is_negative)
411+
{
412+
*is_negative = int64_is_negative(i64);
413+
return int64_safe_unsigned_abs(i64);
414+
}
415+
351416
#if INTPTR_MAX <= INT32_MAX
352417
#define INTPTR_WRITE_TO_ASCII_BUF_LEN (32 + 1)
353418
#elif INTPTR_MAX <= INT64_MAX

0 commit comments

Comments
 (0)