Skip to content
Permalink
Browse files

misc: Portable math_extras.h implementations.

Add a <misc/math_extras.h> header file with portable implementations of
a number of commonly used math and bit counting operations that are not
available in the C programming language.

The math_extras functions fall into two groups:

1. Unsigned integer arithmetic with overflow detection. Addition and
   multiplication are provides for the u32_t, u64_t, and size_t types.
   This commit doesn't include subtraction and division, and there are
   no signed operations. These could be added later if there is a need.

2. Bit-counting operations like clz, ctz, and ffs. These are provided
   for u32_t and u64_t only. I don't see a need for size_t operations,
   but they could be added if needed.

A follow-on commit will add more efficient implementations using
compiler builtins for those compilers that support it.

Another commit will replace other uses of "naked" compiler builtins with
calls to these functions.

Signed-off-by: Jakob Olesen <jolesen@fb.com>
  • Loading branch information...
stoklund authored and galak committed May 7, 2019
1 parent aaf91f0 commit e66c0da0c197664ae8acdd389edc76b31e0a5985
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2019 Facebook.
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @file
* @brief Extra arithmetic and bitmanipulation functions.
*
* @details This header file provides portable wrapper functions for a number of
* arithmetic and bit-counting functions that are often provided by compiler
* builtins. If the compiler does not have an appropriate builtin, a portable C
* implementation is used instead.
*/

#ifndef ZEPHYR_INCLUDE_MISC_MATH_EXTRAS_H_
#define ZEPHYR_INCLUDE_MISC_MATH_EXTRAS_H_

#include <zephyr/types.h>
#include <stdbool.h>
#include <stddef.h>

/**
* @name Unsigned integer addition with overflow detection.
*
* These functions compute `a + b` and store the result in `*result`, returning
* true if the operation overflowed.
*/
/**@{*/
static bool u32_add_overflow(u32_t a, u32_t b, u32_t *result);
static bool u64_add_overflow(u64_t a, u64_t b, u64_t *result);
static bool size_add_overflow(size_t a, size_t b, size_t *result);
/**@}*/

/**
* @name Unsigned integer multiplication with overflow detection.
*
* These functions compute `a * b` and store the result in `*result`, returning
* true if the operation overflowed.
*/
/**@{*/
static bool u32_mul_overflow(u32_t a, u32_t b, u32_t *result);
static bool u64_mul_overflow(u64_t a, u64_t b, u64_t *result);
static bool size_mul_overflow(size_t a, size_t b, size_t *result);
/**@}*/

/**
* @name Count leading zeros.
*
* Count the number of leading zero bits in the bitwise representation of `x`.
* When `x = 0`, this is the size of `x` in bits.
*/
/**@{*/
static int u32_count_leading_zeros(u32_t x);
static int u64_count_leading_zeros(u64_t x);
/**@}*/

/**
* @name Count trailing zeros.
*
* Count the number of trailing zero bits in the bitwise representation of `x`.
* When `x = 0`, this is the size of `x` in bits.
*/
/**@{*/
static int u32_count_trailing_zeros(u32_t x);
static int u64_count_trailing_zeros(u64_t x);
/**@}*/

#include <misc/math_extras_impl.h>

#endif /* ZEPHYR_INCLUDE_MISC_MATH_EXTRAS_H_ */
@@ -0,0 +1,108 @@
/*
* Copyright (c) 2019 Facebook.
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @file
* @brief Inline implementation of functions declared in math_extras.h.
*/

#ifndef ZEPHYR_INCLUDE_MISC_MATH_EXTRAS_H_
#error "please include <misc/math_extras.h> instead of this file"
#endif

static inline bool u32_add_overflow(u32_t a, u32_t b, u32_t *result)
{
u32_t c = a + b;

*result = c;

return c < a;
}

static inline bool u64_add_overflow(u64_t a, u64_t b, u64_t *result)
{
u64_t c = a + b;

*result = c;

return c < a;
}

static inline bool size_add_overflow(size_t a, size_t b, size_t *result)
{
size_t c = a + b;

*result = c;

return c < a;
}

static inline bool u32_mul_overflow(u32_t a, u32_t b, u32_t *result)
{
u32_t c = a * b;

*result = c;

return a != 0 && (c / a) != b;
}

static inline bool u64_mul_overflow(u64_t a, u64_t b, u64_t *result)
{
u64_t c = a * b;

*result = c;

return a != 0 && (c / a) != b;
}

static inline bool size_mul_overflow(size_t a, size_t b, size_t *result)
{
size_t c = a * b;

*result = c;

return a != 0 && (c / a) != b;
}

static inline int u32_count_leading_zeros(u32_t x)
{
int b;

for (b = 0; b < 32 && (x >> 31) == 0; b++) {
x <<= 1;
}

return b;
}

static inline int u64_count_leading_zeros(u64_t x)
{
if (x == (u32_t)x) {
return 32 + u32_count_leading_zeros((u32_t)x);
} else {
return u32_count_leading_zeros(x >> 32);
}
}

static inline int u32_count_trailing_zeros(u32_t x)
{
int b;

for (b = 0; b < 32 && (x & 1) == 0; b++) {
x >>= 1;
}

return b;
}

static inline int u64_count_trailing_zeros(u64_t x)
{
if ((u32_t)x) {
return u32_count_trailing_zeros((u32_t)x);
} else {
return 32 + u32_count_trailing_zeros(x >> 32);
}
}
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: Apache-2.0

project(math_extras)
include($ENV{ZEPHYR_BASE}/subsys/testsuite/unittest.cmake)
@@ -0,0 +1,199 @@
/*
* Copyright (c) 2019 Facebook
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <ztest.h>
#include <misc/math_extras.h>
#include <inttypes.h>

static void test_u32_add(void)
{
u32_t result = 42;

zassert_false(u32_add_overflow(2, 3, &result), NULL);
zassert_equal(result, 5, NULL);

zassert_false(u32_add_overflow(2, 0, &result), NULL);
zassert_equal(result, 2, NULL);

zassert_false(u32_add_overflow(0, 3, &result), NULL);
zassert_equal(result, 3, NULL);

zassert_false(u32_add_overflow(0, UINT32_MAX, &result), NULL);
zassert_equal(result, UINT32_MAX, NULL);
zassert_true(u32_add_overflow(1, UINT32_MAX, &result), NULL);
zassert_equal(result, 0, NULL);

zassert_false(u32_add_overflow(UINT32_MAX, 0, &result), NULL);
zassert_equal(result, UINT32_MAX, NULL);
zassert_true(u32_add_overflow(UINT32_MAX, 2, &result), NULL);
zassert_equal(result, 1, NULL);
}

static void test_u32_mul(void)
{
u32_t result = 42;

zassert_false(u32_mul_overflow(2, 3, &result), NULL);
zassert_equal(result, 6, NULL);

zassert_false(u32_mul_overflow(UINT32_MAX, 1, &result), NULL);
zassert_equal(result, UINT32_MAX, NULL);
zassert_true(u32_mul_overflow(UINT32_MAX, 2, &result), NULL);
zassert_equal(result, UINT32_MAX * 2, NULL);

zassert_false(u32_mul_overflow(1, UINT32_MAX, &result), NULL);
zassert_equal(result, UINT32_MAX, NULL);
zassert_true(u32_mul_overflow(2, UINT32_MAX, &result), NULL);
zassert_equal(result, UINT32_MAX * 2, NULL);
}

static void test_u64_add(void)
{
u64_t result = 42;

zassert_false(u64_add_overflow(2, 3, &result), NULL);
zassert_equal(result, 5, NULL);

zassert_false(u64_add_overflow(2, 0, &result), NULL);
zassert_equal(result, 2, NULL);

zassert_false(u64_add_overflow(0, 3, &result), NULL);
zassert_equal(result, 3, NULL);

zassert_false(u64_add_overflow(0, UINT64_MAX, &result), NULL);
zassert_equal(result, UINT64_MAX, NULL);
zassert_true(u64_add_overflow(1, UINT64_MAX, &result), NULL);
zassert_equal(result, 0, NULL);

zassert_false(u64_add_overflow(UINT64_MAX, 0, &result), NULL);
zassert_equal(result, UINT64_MAX, NULL);
zassert_true(u64_add_overflow(UINT64_MAX, 2, &result), NULL);
zassert_equal(result, 1, NULL);
}

static void test_u64_mul(void)
{
u64_t result = 42;

zassert_false(u64_mul_overflow(2, 3, &result), NULL);
zassert_equal(result, 6, NULL);

zassert_false(u64_mul_overflow(UINT64_MAX, 1, &result), NULL);
zassert_equal(result, UINT64_MAX, NULL);
zassert_true(u64_mul_overflow(UINT64_MAX, 2, &result), NULL);
zassert_equal(result, UINT64_MAX * 2, NULL);

zassert_false(u64_mul_overflow(1, UINT64_MAX, &result), NULL);
zassert_equal(result, UINT64_MAX, NULL);
zassert_true(u64_mul_overflow(2, UINT64_MAX, &result), NULL);
zassert_equal(result, UINT64_MAX * 2, NULL);
}

static void test_size_add(void)
{
size_t result = 42;

zassert_false(size_add_overflow(2, 3, &result), NULL);
zassert_equal(result, 5, NULL);

zassert_false(size_add_overflow(2, 0, &result), NULL);
zassert_equal(result, 2, NULL);

zassert_false(size_add_overflow(0, 3, &result), NULL);
zassert_equal(result, 3, NULL);

zassert_false(size_add_overflow(0, SIZE_MAX, &result), NULL);
zassert_equal(result, SIZE_MAX, NULL);
zassert_true(size_add_overflow(1, SIZE_MAX, &result), NULL);
zassert_equal(result, 0, NULL);

zassert_false(size_add_overflow(SIZE_MAX, 0, &result), NULL);
zassert_equal(result, SIZE_MAX, NULL);
zassert_true(size_add_overflow(SIZE_MAX, 2, &result), NULL);
zassert_equal(result, 1, NULL);
}

static void test_size_mul(void)
{
size_t result = 42;

zassert_false(size_mul_overflow(2, 3, &result), NULL);
zassert_equal(result, 6, NULL);

zassert_false(size_mul_overflow(SIZE_MAX, 1, &result), NULL);
zassert_equal(result, SIZE_MAX, NULL);
zassert_true(size_mul_overflow(SIZE_MAX, 2, &result), NULL);
zassert_equal(result, SIZE_MAX * 2, NULL);

zassert_false(size_mul_overflow(1, SIZE_MAX, &result), NULL);
zassert_equal(result, SIZE_MAX, NULL);
zassert_true(size_mul_overflow(2, SIZE_MAX, &result), NULL);
zassert_equal(result, SIZE_MAX * 2, NULL);
}

static void test_u32_clz(void)
{
zassert_equal(u32_count_leading_zeros(0), 32, NULL);
zassert_equal(u32_count_leading_zeros(1), 31, NULL);
zassert_equal(u32_count_leading_zeros(0xf00f), 16, NULL);
zassert_equal(u32_count_leading_zeros(0xf00ff00f), 0, NULL);
zassert_equal(u32_count_leading_zeros(0xffffffff), 0, NULL);
}

static void test_u64_clz(void)
{
zassert_equal(u64_count_leading_zeros(0), 64, NULL);
zassert_equal(u64_count_leading_zeros(1), 63, NULL);
zassert_equal(u64_count_leading_zeros(0xf00f), 48, NULL);
zassert_equal(u64_count_leading_zeros(0xf00ff00f), 32, NULL);
zassert_equal(u64_count_leading_zeros(0xffffffff), 32, NULL);
zassert_equal(u64_count_leading_zeros(0xf00f00000000ull), 16, NULL);
zassert_equal(u64_count_leading_zeros(0xf00ff00f00000000ull), 0, NULL);
zassert_equal(u64_count_leading_zeros(0xffffffff00000000ull), 0, NULL);
}

static void test_u32_ctz(void)
{
zassert_equal(u32_count_trailing_zeros(0), 32, NULL);
zassert_equal(u32_count_trailing_zeros(1), 0, NULL);
zassert_equal(u32_count_trailing_zeros(6), 1, NULL);
zassert_equal(u32_count_trailing_zeros(0x00f00f00), 8, NULL);
zassert_equal(u32_count_trailing_zeros(0xf00ffc00), 10, NULL);
zassert_equal(u32_count_trailing_zeros(0xffffffff), 0, NULL);
zassert_equal(u32_count_trailing_zeros(0x80000000), 31, NULL);
}

static void test_u64_ctz(void)
{
zassert_equal(u64_count_trailing_zeros(0), 64, NULL);
zassert_equal(u64_count_trailing_zeros(1), 0, NULL);
zassert_equal(u64_count_trailing_zeros(6), 1, NULL);
zassert_equal(u64_count_trailing_zeros(0x00f00f00), 8, NULL);
zassert_equal(u64_count_trailing_zeros(0xf00ffc00), 10, NULL);
zassert_equal(u64_count_trailing_zeros(0xffffffffffffffffull), 0, NULL);
zassert_equal(u64_count_trailing_zeros(0x8000000080000000ull), 31,
NULL);
zassert_equal(u64_count_trailing_zeros(0xc000000000000000ull), 62,
NULL);
}

void test_main(void)
{
/* clang-format off */
ztest_test_suite(test_math_extras,
ztest_unit_test(test_u32_add),
ztest_unit_test(test_u32_mul),
ztest_unit_test(test_u64_add),
ztest_unit_test(test_u64_mul),
ztest_unit_test(test_size_add),
ztest_unit_test(test_size_mul),
ztest_unit_test(test_u32_clz),
ztest_unit_test(test_u64_clz),
ztest_unit_test(test_u32_ctz),
ztest_unit_test(test_u64_ctz));
ztest_run_test_suite(test_math_extras);
/* clang-format on */
}
@@ -0,0 +1,4 @@
tests:
misc.math_extras:
timeout: 5
type: unit

0 comments on commit e66c0da

Please sign in to comment.
You can’t perform that action at this time.