Skip to content

Commit

Permalink
core/assert: add magic_assert() and magic_static_assert()
Browse files Browse the repository at this point in the history
`magic_static_assert()` abuses the optimizer and linker to provide the
functionality of `static_assert()` for expressions that are formally
nowhere near to constant integer expressions, but can still be fully
evaluated at compile time. This can become a simple wrapper around
`constexpr` and `static_assert()` once C gains support for `constexpr`.

`magic_assert()` will do that same as `magic_static_assert()` if the
argument can be evaluated at compile time, but will fall back to a
standard `assert()` (hence, a run time check) where this is not
possible. This is immensely useful when e.g. depending on the used board
an expression can be evaluated at compile time or not. (E.g. some boards
detect the CPU frequency at run-time, while most know it at compile
time.) Unlike `assert()`, `magic_assert()` will break at compile-time
when the assertion is known to always be false.

Note: Unlike `assert(0)` a `magic_assert(0)` is never sensible. The
former can be placed at code paths that must be unreachable, the latter
will (unless the code path is eliminated as dead branch at compile time)
fail to compile. Additionally, `magic_assert()` will not be fully
disabled with `NDEBUG`, only the run-time checks will be disabled at
compile time. As a result, expressions with side-effects as arguments
`magic_assert()` will be still evaluated with `NDEBUG`.
  • Loading branch information
maribu committed Dec 13, 2021
1 parent ef2913d commit 944e253
Showing 1 changed file with 57 additions and 0 deletions.
57 changes: 57 additions & 0 deletions core/include/assert.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#ifndef ASSERT_H
#define ASSERT_H

#include "kernel_defines.h"
#include "panic.h"

#ifdef __cplusplus
Expand Down Expand Up @@ -125,6 +126,62 @@ NORETURN void _assert_failure(const char *file, unsigned line);
#endif
#endif

/**
* @brief Enforce that the compile time constant expression @p expr evaluates
* to `true`
*
* @note This abuses a linker failure rather than printing a more decent
* message. A linker failure due to missing symbol
* `magic_static_assert_expr_not_constant` signals that `expr` was not
* constant, while missing symbol
* `magic_static_assert_assertion_failed` signals a failed assertion.
*/
static inline __attribute__((always_inline)) void magic_static_assert(int expr)
{
if (!IS_CT_CONSTANT(expr)) {
extern void magic_static_assert_expr_not_constant(void);
magic_static_assert_expr_not_constant();
}
else if (!(expr)) {
extern void magic_static_assert_assertion_failed(void);
magic_static_assert_assertion_failed();
}
}

/**
* @brief If constant folding is possible for @p expr the assertion is checked
* at compile time, otherwise a regular @ref assert is emitted to
* perform a run-time check instead.
*
* @note This abuses a linker failure rather than printing a more decent
* message. A linker failure due to missing symbol
* `magic_assert_assertion_failed` signals a failed assertion when
* @p expr was a compile-time constant
*
* @warning It is a common pattern to place an `assert(0)` in code paths that
* should be unreachable. Placing `magic_assert(0)` in code paths that
* are not eliminated as dead branches during optimization will result
* in the assertion to blow, since the constant expression `0` is
* known to be false at compile time.
*
* @warning Defining `NDEBUG` will only disable run-time checks and @p expr
* will remain evaluated (e.g. function calls as argument to
* @ref magic_assert will still be emitted, very much unlike
* @ref assert would do.
*/
static inline __attribute__((always_inline)) void magic_assert(int expr)
{
if (IS_CT_CONSTANT(expr)) {
if (!(expr)) {
extern void magic_assert_assertion_failed(void);
magic_assert_assertion_failed();
}
}
else {
assert(expr);
}
}

#ifdef __cplusplus
}
#endif
Expand Down

0 comments on commit 944e253

Please sign in to comment.