Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions doc/releases/migration-guide-4.5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ Kernel
be used as futex address. The error -EINVAL can no longer happen on futex
operations.

* With GCC, the argument of a time unit conversion macro such as :c:macro:`K_MSEC` or
:c:macro:`k_ms_to_ticks_ceil32` used in a static initializer must be an integer constant
expression. A ``const`` variable, which GCC folded as an extension, is now rejected with
``initializer element is not constant``. Use a macro, an enumerator or, with
:kconfig:option:`CONFIG_STD_C23`, a ``constexpr`` object instead.

Boards
******

Expand Down
7 changes: 7 additions & 0 deletions doc/releases/release-notes-4.5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2094,6 +2094,13 @@ Other notable changes
failure. Use :c:func:`k_thread_cpu_pin` to reassign a thread to a
different CPU.

* With GCC and Clang, the time unit conversion macros (``k_<from>_to_<to>_<round><bits>()``),
:c:macro:`K_MSEC`, :c:macro:`K_USEC`, :c:macro:`K_NSEC`, :c:macro:`K_CYC`,
:c:macro:`K_SECONDS` and :c:macro:`SYS_TIMEOUT_MS` evaluate an argument that is not a
compile-time constant exactly once. An argument such as ``deadline - k_uptime_get()`` was
evaluated up to four times, and reads of a running clock could disagree within one
conversion. Constant arguments still produce integer constant expressions.

* Timer

* With :kconfig:option:`CONFIG_SYSTEM_CLOCK_SLOPPY_IDLE` enabled, a driver may no
Expand Down
33 changes: 23 additions & 10 deletions include/zephyr/sys/clock.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,33 @@ typedef struct {
#define Z_TIMEOUT_TICKS(t) ((k_timeout_t)Z_TIMEOUT_TICKS_INIT(t))
#define Z_FOREVER Z_TIMEOUT_TICKS(K_TICKS_FOREVER)

static ALWAYS_INLINE int64_t z_timeout_clamp_once(int64_t t)
{
return MAX(t, 0);
}

#define Z_TIMEOUT_CLAMP(t) z_tmcvt_choose(z_tmcvt_const_arg(t), MAX(t, 0), z_timeout_clamp_once(t))

#ifdef CONFIG_TIMEOUT_64BIT
#define Z_TIMEOUT_MS(t) Z_TIMEOUT_TICKS((k_ticks_t)k_ms_to_ticks_ceil64(MAX(t, 0)))
#define Z_TIMEOUT_US(t) Z_TIMEOUT_TICKS((k_ticks_t)k_us_to_ticks_ceil64(MAX(t, 0)))
#define Z_TIMEOUT_NS(t) Z_TIMEOUT_TICKS((k_ticks_t)k_ns_to_ticks_ceil64(MAX(t, 0)))
#define Z_TIMEOUT_CYC(t) Z_TIMEOUT_TICKS((k_ticks_t)k_cyc_to_ticks_ceil64(MAX(t, 0)))
#define Z_TIMEOUT_MS_TICKS(t) ((k_ticks_t)k_ms_to_ticks_ceil64(MAX(t, 0)))
#define Z_TIMEOUT_MS(t) Z_TIMEOUT_TICKS((k_ticks_t)k_ms_to_ticks_ceil64(Z_TIMEOUT_CLAMP(t)))
#define Z_TIMEOUT_US(t) Z_TIMEOUT_TICKS((k_ticks_t)k_us_to_ticks_ceil64(Z_TIMEOUT_CLAMP(t)))
#define Z_TIMEOUT_NS(t) Z_TIMEOUT_TICKS((k_ticks_t)k_ns_to_ticks_ceil64(Z_TIMEOUT_CLAMP(t)))
#define Z_TIMEOUT_CYC(t) Z_TIMEOUT_TICKS((k_ticks_t)k_cyc_to_ticks_ceil64(Z_TIMEOUT_CLAMP(t)))
#define Z_TIMEOUT_MS_TICKS(t) ((k_ticks_t)k_ms_to_ticks_ceil64(Z_TIMEOUT_CLAMP(t)))
#else
#define Z_TIMEOUT_MS(t) Z_TIMEOUT_TICKS((k_ticks_t)k_ms_to_ticks_ceil32(MAX(t, 0)))
#define Z_TIMEOUT_US(t) Z_TIMEOUT_TICKS((k_ticks_t)k_us_to_ticks_ceil32(MAX(t, 0)))
#define Z_TIMEOUT_NS(t) Z_TIMEOUT_TICKS((k_ticks_t)k_ns_to_ticks_ceil32(MAX(t, 0)))
#define Z_TIMEOUT_CYC(t) Z_TIMEOUT_TICKS((k_ticks_t)k_cyc_to_ticks_ceil32(MAX(t, 0)))
#define Z_TIMEOUT_MS_TICKS(t) ((k_ticks_t)k_ms_to_ticks_ceil32(MAX(t, 0)))
#define Z_TIMEOUT_MS(t) Z_TIMEOUT_TICKS((k_ticks_t)k_ms_to_ticks_ceil32(Z_TIMEOUT_CLAMP(t)))
#define Z_TIMEOUT_US(t) Z_TIMEOUT_TICKS((k_ticks_t)k_us_to_ticks_ceil32(Z_TIMEOUT_CLAMP(t)))
#define Z_TIMEOUT_NS(t) Z_TIMEOUT_TICKS((k_ticks_t)k_ns_to_ticks_ceil32(Z_TIMEOUT_CLAMP(t)))
#define Z_TIMEOUT_CYC(t) Z_TIMEOUT_TICKS((k_ticks_t)k_cyc_to_ticks_ceil32(Z_TIMEOUT_CLAMP(t)))
#define Z_TIMEOUT_MS_TICKS(t) ((k_ticks_t)k_ms_to_ticks_ceil32(Z_TIMEOUT_CLAMP(t)))
#endif

/* forever_ms is SYS_FOREVER_MS converted to the type of the SYS_TIMEOUT_MS() argument */
static ALWAYS_INLINE k_ticks_t z_sys_timeout_ms_ticks_once(int64_t ms, int64_t forever_ms)
{
return (ms == forever_ms) ? K_TICKS_FOREVER : Z_TIMEOUT_MS_TICKS(ms);
}

/* Converts between absolute timeout expiration values (packed into
* the negative space below K_TICKS_FOREVER) and (non-negative) delta
* timeout values. If the result of Z_TICK_ABS(t) is >= 0, then the
Expand Down
80 changes: 64 additions & 16 deletions include/zephyr/sys/time_units.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ extern "C" {
* converted from milliseconds.
*/
#define SYS_TIMEOUT_MS_INIT(ms) \
Z_TIMEOUT_TICKS_INIT((ms) == SYS_FOREVER_MS ? \
K_TICKS_FOREVER : Z_TIMEOUT_MS_TICKS(ms))
Z_TIMEOUT_TICKS_INIT(z_tmcvt_choose(z_tmcvt_const_arg(ms), \
((ms) == SYS_FOREVER_MS ? K_TICKS_FOREVER : Z_TIMEOUT_MS_TICKS(ms)), \
z_sys_timeout_ms_ticks_once(ms, 0 ? (ms) : SYS_FOREVER_MS)))

/** @brief System-wide macro to convert milliseconds to kernel timeouts
*/
Expand Down Expand Up @@ -130,6 +131,25 @@ static inline unsigned int z_impl_sys_clock_hw_cycles_per_sec_runtime_get(void)
* the results may be used to initialize global variables.
*/

/* Constant operands take the expression form, which stays an integer constant
* expression; others are evaluated once as function arguments.
*/
#if defined(__cplusplus) && HAS_BUILTIN(__builtin_constant_p)
#define z_tmcvt_const_arg(__x) __builtin_constant_p(__x)
#define z_tmcvt_choose(__is_const, __expr, __once) ((__is_const) ? (__expr) : (__once))
#elif !defined(__cplusplus) && HAS_BUILTIN(__builtin_constant_p) && \
HAS_BUILTIN(__builtin_choose_expr)
#define z_tmcvt_const_arg(__x) __builtin_constant_p(__x)
#define z_tmcvt_choose(__is_const, __expr, __once) __builtin_choose_expr(__is_const, __expr, __once)
#else
#define z_tmcvt_const_arg(__x) true
#define z_tmcvt_choose(__is_const, __expr, __once) (__expr)
#endif

#define z_tmcvt_is_const(__t, __from_hz, __to_hz) \
(z_tmcvt_const_arg(__t) && z_tmcvt_const_arg(__from_hz) && \
z_tmcvt_const_arg(__to_hz))

/* true if the conversion is the identity */
#define z_tmcvt_is_identity(__from_hz, __to_hz) \
((__to_hz) == (__from_hz))
Expand Down Expand Up @@ -182,29 +202,45 @@ static inline unsigned int z_impl_sys_clock_hw_cycles_per_sec_runtime_get(void)
0)

/* Integer division 32-bit conversion */
#define z_tmcvt_int_div_32_expr(__t, __off, __divisor) \
((uint64_t) (__t) <= 0xffffffffU - (__off) ? \
(uint32_t) (((__t) + (__off)) / (__divisor)) : \
(uint32_t) (((uint64_t) (__t) + (__off)) / (__divisor)))

static ALWAYS_INLINE uint32_t z_tmcvt_int_div_32_once(uint64_t t, uint32_t off, uint32_t divisor)
{
return z_tmcvt_int_div_32_expr(t, off, divisor);
}

#define z_tmcvt_int_div_32(__t, __from_hz, __to_hz, __round_up, __round_off) \
((uint64_t) (__t) <= 0xffffffffU - \
z_tmcvt_off_div(__from_hz, __to_hz, __round_up, __round_off) ? \
((uint32_t)(((__t) + \
z_tmcvt_off_div(__from_hz, __to_hz, \
__round_up, __round_off)) / \
z_tmcvt_divisor(__from_hz, __to_hz))) \
: \
(uint32_t) (((uint64_t) (__t) + \
z_tmcvt_off_div(__from_hz, __to_hz, \
__round_up, __round_off)) / \
z_tmcvt_divisor(__from_hz, __to_hz)) \
)
z_tmcvt_choose(z_tmcvt_const_arg(__t), \
z_tmcvt_int_div_32_expr(__t, \
z_tmcvt_off_div(__from_hz, __to_hz, __round_up, __round_off), \
z_tmcvt_divisor(__from_hz, __to_hz)), \
z_tmcvt_int_div_32_once(__t, \
z_tmcvt_off_div(__from_hz, __to_hz, __round_up, __round_off), \
z_tmcvt_divisor(__from_hz, __to_hz)))

/* Integer multiplication 32-bit conversion */
#define z_tmcvt_int_mul_32(__t, __from_hz, __to_hz) \
((uint32_t) ((__t)*((__to_hz) / (__from_hz))))

/* General 32-bit conversion */
#define z_tmcvt_gen_32(__t, __from_hz, __to_hz, __round_up, __round_off) \
#define z_tmcvt_gen_32_expr(__t, __from_hz, __to_hz, __round_up, __round_off) \
((uint32_t) (((uint64_t) (__t)*(__to_hz) + \
z_tmcvt_off_gen(__from_hz, __to_hz, __round_up, __round_off)) / (__from_hz)))

static ALWAYS_INLINE uint32_t z_tmcvt_gen_32_once(uint64_t t, uint32_t from_hz, uint32_t to_hz,
bool round_up, bool round_off)
{
return z_tmcvt_gen_32_expr(t, from_hz, to_hz, round_up, round_off);
}

#define z_tmcvt_gen_32(__t, __from_hz, __to_hz, __round_up, __round_off) \
z_tmcvt_choose(z_tmcvt_is_const(__t, __from_hz, __to_hz), \
z_tmcvt_gen_32_expr(__t, __from_hz, __to_hz, __round_up, __round_off), \
z_tmcvt_gen_32_once(__t, __from_hz, __to_hz, __round_up, __round_off))

/* Integer division 64-bit conversion */
#define z_tmcvt_int_div_64(__t, __from_hz, __to_hz, __round_up, __round_off) \
(((uint64_t) (__t) + z_tmcvt_off_div(__from_hz, __to_hz, \
Expand All @@ -221,11 +257,23 @@ static inline unsigned int z_impl_sys_clock_hw_cycles_per_sec_runtime_get(void)
z_tmcvt_off_gen(__from_hz, __to_hz, __round_up, __round_off)) / (__from_hz))

/* Slow 64-bit conversion. This avoids overflowing the multiply */
#define z_tmcvt_gen_64_slow(__t, __from_hz, __to_hz, __round_up, __round_off) \
#define z_tmcvt_gen_64_slow_expr(__t, __from_hz, __to_hz, __round_up, __round_off) \
((((uint64_t) (__t) / (__from_hz))*(__to_hz)) + \
(((((uint64_t) (__t) % (__from_hz))*(__to_hz)) + \
z_tmcvt_off_gen(__from_hz, __to_hz, __round_up, __round_off)) / (__from_hz)))

static ALWAYS_INLINE uint64_t z_tmcvt_gen_64_slow_once(uint64_t t, uint32_t from_hz,
uint32_t to_hz, bool round_up,
bool round_off)
{
return z_tmcvt_gen_64_slow_expr(t, from_hz, to_hz, round_up, round_off);
}

#define z_tmcvt_gen_64_slow(__t, __from_hz, __to_hz, __round_up, __round_off) \
z_tmcvt_choose(z_tmcvt_is_const(__t, __from_hz, __to_hz), \
z_tmcvt_gen_64_slow_expr(__t, __from_hz, __to_hz, __round_up, __round_off), \
z_tmcvt_gen_64_slow_once(__t, __from_hz, __to_hz, __round_up, __round_off))

/* General 64-bit conversion. Uses one of the two above macros */
#define z_tmcvt_gen_64(__t, __from_hz, __to_hz, __round_up, __round_off) \
(z_tmcvt_use_fast_algo(__from_hz, __to_hz) ? \
Expand Down
205 changes: 205 additions & 0 deletions tests/unit/time_units/single_eval.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/*
* Copyright The Zephyr Project Contributors
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdbool.h>
#include <stdint.h>

#include <zephyr/sys/clock.h>
#include <zephyr/sys/time_units.h>
#include <zephyr/sys/util.h>
#include <zephyr/ztest.h>

BUILD_ASSERT(k_ms_to_ticks_ceil32(1) == DIV_ROUND_UP(CONFIG_SYS_CLOCK_TICKS_PER_SEC, 1000));
BUILD_ASSERT(k_ticks_to_ns_floor64(CONFIG_SYS_CLOCK_TICKS_PER_SEC) == NSEC_PER_SEC);

static const k_timeout_t static_timeout = Z_TIMEOUT_MS(100);
static const k_timeout_t static_forever = SYS_TIMEOUT_MS(SYS_FOREVER_MS);
static const uint32_t static_ticks = z_tmcvt_32(32768, 32768, 1000, true, true, false);
static uint8_t static_array[k_ms_to_ticks_ceil32(1)];

static int evaluations;

static uint64_t counted(uint64_t value)
{
evaluations++;
return value;
}

static int64_t counted_signed(int64_t value)
{
evaluations++;
return value;
}

static uint32_t counted_hz(uint32_t hz)
{
evaluations++;
return hz;
}

#define ASSERT_ONCE(convert, value) \
do { \
evaluations = 0; \
const uint64_t runtime = convert(counted(value)); \
\
zassert_equal(evaluations, 1, #convert "(" #value ") evaluated %d times", \
evaluations); \
zassert_equal(runtime, convert(value), #convert "(" #value ")"); \
} while (false)

#define DIV_32_FLOOR(t) z_tmcvt_32(t, 10000, 1000, true, false, false)
#define DIV_32_NEAR(t) z_tmcvt_32(t, 10000, 1000, true, false, true)
#define DIV_32_CEIL(t) z_tmcvt_32(t, 10000, 1000, true, true, false)
#define MUL_32(t) z_tmcvt_32(t, 1000, 10000, true, true, false)
#define GEN_32(t) z_tmcvt_32(t, 32768, 1000, true, true, false)
#define DIV_64_CEIL(t) z_tmcvt_64(t, 10000, 1000, true, true, false)
#define MUL_64(t) z_tmcvt_64(t, 1000, 10000, true, true, false)
#define FAST_64(t) z_tmcvt_64(t, 32768, 1000, true, true, false)
#define SLOW_64(t) z_tmcvt_64(t, 32768, 1000000000, true, false, false)
#define RUNTIME_64(t) z_tmcvt_64(t, 32768, 1000000000, false, false, true)

#define ASSERT_ONCE_ALL_PATHS(value) \
ASSERT_ONCE(DIV_32_FLOOR, value); \
ASSERT_ONCE(DIV_32_NEAR, value); \
ASSERT_ONCE(DIV_32_CEIL, value); \
ASSERT_ONCE(MUL_32, value); \
ASSERT_ONCE(GEN_32, value); \
ASSERT_ONCE(DIV_64_CEIL, value); \
ASSERT_ONCE(MUL_64, value); \
ASSERT_ONCE(FAST_64, value); \
ASSERT_ONCE(SLOW_64, value); \
ASSERT_ONCE(RUNTIME_64, value)

ZTEST(time_units, test_constant_arguments_stay_constant_expressions)
{
zassert_equal(static_timeout.ticks, k_ms_to_ticks_ceil64(100));
zassert_equal(static_forever.ticks, K_TICKS_FOREVER);
zassert_equal(static_ticks, 1000);
zassert_equal(sizeof(static_array), k_ms_to_ticks_ceil32(1));
}

ZTEST(time_units, test_conversion_paths_evaluate_argument_once)
{
ASSERT_ONCE_ALL_PATHS(0);
ASSERT_ONCE_ALL_PATHS(1);
ASSERT_ONCE_ALL_PATHS(32767);
ASSERT_ONCE_ALL_PATHS(32768);
ASSERT_ONCE_ALL_PATHS(0xfffffff6);
ASSERT_ONCE_ALL_PATHS(0xfffffff7);
ASSERT_ONCE_ALL_PATHS(0xffffffff);
ASSERT_ONCE_ALL_PATHS(0x100000000);
}

ZTEST(time_units, test_runtime_frequency_evaluated_once)
{
evaluations = 0;
const uint64_t ns =
z_tmcvt_64(counted(98304), counted_hz(32768), 1000000000, false, false, true);

zassert_equal(evaluations, 2);
zassert_equal(ns, 3000000000);

evaluations = 0;
const uint32_t ms = z_tmcvt_32(counted(98304), counted_hz(32768), 1000, false, true, false);

zassert_equal(evaluations, 2);
zassert_equal(ms, 3000);
}

ZTEST(time_units, test_timeout_macros_evaluate_argument_once)
{
evaluations = 0;
const k_timeout_t ms = Z_TIMEOUT_MS(counted_signed(250));

zassert_equal(evaluations, 1);
zassert_equal(ms.ticks, Z_TIMEOUT_MS(250).ticks);

evaluations = 0;
const k_timeout_t us = Z_TIMEOUT_US(counted_signed(250));

zassert_equal(evaluations, 1);
zassert_equal(us.ticks, Z_TIMEOUT_US(250).ticks);

evaluations = 0;
const k_timeout_t ns = Z_TIMEOUT_NS(counted_signed(250));

zassert_equal(evaluations, 1);
zassert_equal(ns.ticks, Z_TIMEOUT_NS(250).ticks);

evaluations = 0;
const k_timeout_t negative = Z_TIMEOUT_MS(counted_signed(-5));

zassert_equal(evaluations, 1);
zassert_equal(negative.ticks, 0);
}

ZTEST(time_units, test_sys_timeout_ms_evaluates_argument_once)
{
evaluations = 0;
const k_timeout_t ms = SYS_TIMEOUT_MS(counted_signed(250));

zassert_equal(evaluations, 1);
zassert_equal(ms.ticks, SYS_TIMEOUT_MS(250).ticks);

evaluations = 0;
const k_timeout_t forever = SYS_TIMEOUT_MS(counted_signed(SYS_FOREVER_MS));

zassert_equal(evaluations, 1);
zassert_equal(forever.ticks, K_TICKS_FOREVER);
}

ZTEST(time_units, test_sys_timeout_ms_forever_in_argument_type)
{
zassert_equal(SYS_TIMEOUT_MS((int32_t)counted_signed(-1)).ticks, K_TICKS_FOREVER);
zassert_equal(SYS_TIMEOUT_MS((uint32_t)counted(UINT32_MAX)).ticks, K_TICKS_FOREVER);
zassert_equal(SYS_TIMEOUT_MS(counted_signed(-1)).ticks, K_TICKS_FOREVER);
zassert_equal(SYS_TIMEOUT_MS(counted(UINT64_MAX)).ticks, K_TICKS_FOREVER);
zassert_equal(SYS_TIMEOUT_MS((uint16_t)counted(UINT16_MAX)).ticks,
SYS_TIMEOUT_MS((uint16_t)UINT16_MAX).ticks);
zassert_equal(SYS_TIMEOUT_MS((uint32_t)counted(UINT32_MAX - 1)).ticks,
SYS_TIMEOUT_MS((uint32_t)(UINT32_MAX - 1)).ticks);
}

static int reads;
static int64_t read_sequence[2];

static int64_t read_clock(void)
{
return read_sequence[reads++ % ARRAY_SIZE(read_sequence)];
}

/* A deadline that passes between two reads of the clock must not bypass the
* clamp; at 1000 Hz a -1 ms argument is K_TICKS_FOREVER.
*/
ZTEST(time_units, test_timeout_clamp_uses_a_single_clock_read)
{
const int64_t now = 1000000;
const int64_t deadline = now + 1;

reads = 0;
read_sequence[0] = now;
read_sequence[1] = deadline + 1;
const k_timeout_t timeout = Z_TIMEOUT_MS(deadline - read_clock());

zassert_equal(reads, 1);
zassert_equal(timeout.ticks, Z_TIMEOUT_MS(1).ticks);
}

/* Quotient and remainder of the slow 64-bit path come from one read, so reads
* that straddle a second boundary cannot lose a whole second.
*/
ZTEST(time_units, test_slow_path_uses_a_single_clock_read)
{
const int64_t hz = CONFIG_SYS_CLOCK_TICKS_PER_SEC;

reads = 0;
read_sequence[0] = 1000 * hz - 1;
read_sequence[1] = 1000 * hz;
const uint64_t ns = k_ticks_to_ns_floor64(read_clock());

zassert_equal(reads, 1);
zassert_equal(ns, k_ticks_to_ns_floor64(1000 * CONFIG_SYS_CLOCK_TICKS_PER_SEC - 1));
}
Loading