Skip to content

Commit 8efa0a2

Browse files
committed
sys: util: timeutil: add timespec manipulation utilities
Add a number of utility functions for manipulating struct timespec. * timespec_add() * timespec_compare() * timespec_equal() * timespec_is_valid() * timespec_negate() * timespec_normalize() * timespec_sub() If the `__builtin_add_overflow()` and `__builtin_sub_overflow()` functions are available, then these functions use are mostly branchless, which should provide decent performance on systems with an instruction cache. Otherwise, manually written alternatives exist that are also perhaps more readable. Signed-off-by: Chris Friedt <[email protected]>
1 parent b30ce11 commit 8efa0a2

File tree

1 file changed

+299
-0
lines changed

1 file changed

+299
-0
lines changed

include/zephyr/sys/timeutil.h

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@
2121
#ifndef ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_
2222
#define ZEPHYR_INCLUDE_SYS_TIMEUTIL_H_
2323

24+
#include <limits.h>
25+
#include <stdbool.h>
26+
#include <stddef.h>
27+
#include <stdint.h>
2428
#include <time.h>
2529

30+
#include <zephyr/sys_clock.h>
31+
#include <zephyr/sys/__assert.h>
32+
#include <zephyr/toolchain.h>
2633
#include <zephyr/types.h>
2734

2835
#ifdef __cplusplus
@@ -301,6 +308,298 @@ int timeutil_sync_local_from_ref(const struct timeutil_sync_state *tsp,
301308
*/
302309
int32_t timeutil_sync_skew_to_ppb(float skew);
303310

311+
/**
312+
* @}
313+
*/
314+
315+
BUILD_ASSERT(sizeof(time_t) == sizeof(int64_t), "time_t must be 64-bit");
316+
BUILD_ASSERT(sizeof(((struct timespec *)0)->tv_sec) == sizeof(int64_t), "tv_sec must be 64-bit");
317+
318+
/**
319+
* @defgroup timeutil_timespec_apis Timespec Utility APIs
320+
* @ingroup timeutil_apis
321+
* @{
322+
*/
323+
324+
/**
325+
* @brief Check if a timespec is valid.
326+
*
327+
* Check if a timespec is valid (i.e. normalized) by ensuring that the `tv_nsec` field is in the
328+
* range `[0, NSEC_PER_SEC-1]`.
329+
*
330+
* @note @p ts must not be `NULL`.
331+
*
332+
* @param ts the timespec to check
333+
*
334+
* @return `true` if the timespec is valid, otherwise `false`.
335+
*/
336+
static inline bool timespec_is_valid(const struct timespec *ts)
337+
{
338+
__ASSERT_NO_MSG(ts != NULL);
339+
340+
return (ts->tv_nsec >= 0) && (ts->tv_nsec < NSEC_PER_SEC);
341+
}
342+
343+
/** @cond INTERNAL_HIDDEN */
344+
345+
/* this is mainly to work around a clang-format issue */
346+
#define TIMESPEC_HAS_BUILTINS \
347+
(HAS_BUILTIN(__builtin_add_overflow) && HAS_BUILTIN(__builtin_sub_overflow))
348+
349+
/** INTERNAL_HIDDEN @endcond */
350+
351+
#if (defined(CONFIG_SPEED_OPTIMIZATIONS) && TIMESPEC_HAS_BUILTINS) || defined(__DOXYGEN__)
352+
/**
353+
* @brief Normalize a timespec so that the `tv_nsec` field is in valid range.
354+
*
355+
* Normalize a timespec by adjusting the `tv_sec` and `tv_nsec` fields so that the `tv_nsec` field
356+
* is in the range `[0, NSEC_PER_SEC-1]`. This is achieved by converting nanoseconds to seconds and
357+
* accumulating seconds in either the positive direction when `tv_nsec` > `NSEC_PER_SEC`, or in the
358+
* negative direction when `tv_nsec` < 0.
359+
*
360+
* In pseudocode, normalization can be done as follows:
361+
* ```python
362+
* if ts.tv_nsec >= NSEC_PER_SEC:
363+
* sec = ts.tv_nsec / NSEC_PER_SEC
364+
* ts.tv_sec += sec
365+
* ts.tv_nsec -= sec * NSEC_PER_SEC
366+
* elif ts.tv_nsec < 0:
367+
* # div_round_up(abs(ts->tv_nsec), NSEC_PER_SEC)
368+
* sec = (NSEC_PER_SEC - ts.tv_nsec - 1) / NSEC_PER_SEC
369+
* ts.tv_sec -= sec;
370+
* ts.tv_nsec += sec * NSEC_PER_SEC;
371+
* ```
372+
*
373+
* @note There are two cases where the normalization can result in integer overflow. These can
374+
* be extrapolated to not simply overflowing the `tv_sec` field by one second, but also by any
375+
* realizable multiple of `NSEC_PER_SEC`.
376+
*
377+
* 1. When `tv_nsec` is negative and `tv_sec` is already most negative.
378+
* 2. When `tv_nsec` is greater-or-equal to `NSEC_PER_SEC` and `tv_sec` is already most positive.
379+
*
380+
* If the operation would result in integer overflow, return value is `false`.
381+
*
382+
* @note @p ts must be non-`NULL`.
383+
*
384+
* @param ts the timespec to be normalized
385+
*
386+
* @return `true` if the operation completes successfully, otherwise `false`.
387+
*/
388+
static inline bool timespec_normalize(struct timespec *ts)
389+
{
390+
__ASSERT_NO_MSG(ts != NULL);
391+
392+
int sign = (ts->tv_nsec >= 0) - (ts->tv_nsec < 0);
393+
int64_t sec =
394+
(ts->tv_nsec >= (long)NSEC_PER_SEC) * (ts->tv_nsec / (long)NSEC_PER_SEC) +
395+
(ts->tv_nsec < 0) * DIV_ROUND_UP((unsigned long)-ts->tv_nsec, (long)NSEC_PER_SEC);
396+
bool overflow =
397+
__builtin_add_overflow(ts->tv_sec, sign * sec, &ts->tv_sec) ||
398+
__builtin_sub_overflow(ts->tv_nsec, sign * sec * NSEC_PER_SEC, &ts->tv_nsec);
399+
400+
if (!overflow) {
401+
__ASSERT_NO_MSG(timespec_is_valid(ts));
402+
}
403+
404+
return !overflow;
405+
}
406+
407+
/**
408+
* @brief Add one timespec to another
409+
*
410+
* This function sums the two timespecs pointed to by @p a and @p b and stores the result in the
411+
* timespce pointed to by @p a.
412+
*
413+
* If the operation would result in integer overflow, return value is `false`.
414+
*
415+
* @note @p a and @p b must be non-`NULL` and normalized.
416+
*
417+
* @param a the timespec which is added to
418+
* @param b the timespec to be added
419+
*
420+
* @return `true` if the operation was successful, otherwise `false`.
421+
*/
422+
static inline bool timespec_add(struct timespec *a, const struct timespec *b)
423+
{
424+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
425+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
426+
427+
return !__builtin_add_overflow(a->tv_sec, b->tv_sec, &a->tv_sec) &&
428+
!__builtin_add_overflow(a->tv_nsec, b->tv_nsec, &a->tv_nsec) &&
429+
timespec_normalize(a);
430+
}
431+
432+
/**
433+
* @brief Negate a timespec object
434+
*
435+
* Negate the timespec object pointed to by @p ts and store the result in the same
436+
* memory location.
437+
*
438+
* If the operation would result in integer overflow, return value is `false`.
439+
*
440+
* @param ts The timespec object to negate.
441+
*
442+
* @return `true` of the operation is successful, otherwise `false`.
443+
*/
444+
static inline bool timespec_negate(struct timespec *ts)
445+
{
446+
__ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts));
447+
448+
return !__builtin_sub_overflow(0LL, ts->tv_sec, &ts->tv_sec) &&
449+
!__builtin_sub_overflow(0L, ts->tv_nsec, &ts->tv_nsec) && timespec_normalize(ts);
450+
}
451+
#else
452+
453+
static inline bool timespec_normalize(struct timespec *ts)
454+
{
455+
__ASSERT_NO_MSG(ts != NULL);
456+
457+
long sec = 0;
458+
459+
if (ts->tv_nsec >= (long)NSEC_PER_SEC) {
460+
sec = ts->tv_nsec / (long)NSEC_PER_SEC;
461+
} else if (ts->tv_nsec < 0) {
462+
if ((sizeof(ts->tv_nsec) == sizeof(uint32_t)) && (ts->tv_nsec == LONG_MIN)) {
463+
sec = DIV_ROUND_UP(LONG_MAX / NSEC_PER_USEC, USEC_PER_SEC);
464+
} else {
465+
sec = DIV_ROUND_UP((unsigned long)-ts->tv_nsec, NSEC_PER_SEC);
466+
}
467+
}
468+
469+
if ((ts->tv_nsec < 0) && (ts->tv_sec < 0) && (ts->tv_sec - INT64_MIN < sec)) {
470+
/*
471+
* When `tv_nsec` is negative and `tv_sec` is already most negative,
472+
* further subtraction would cause integer overflow.
473+
*/
474+
return false;
475+
}
476+
477+
if ((ts->tv_nsec >= (long)NSEC_PER_SEC) && (ts->tv_sec > 0) &&
478+
(INT64_MAX - ts->tv_sec < sec)) {
479+
/*
480+
* When `tv_nsec` is >= `NSEC_PER_SEC` and `tv_sec` is already most
481+
* positive, further addition would cause integer overflow.
482+
*/
483+
return false;
484+
}
485+
486+
if (ts->tv_nsec >= (long)NSEC_PER_SEC) {
487+
ts->tv_sec += sec;
488+
ts->tv_nsec -= sec * (long)NSEC_PER_SEC;
489+
} else if (ts->tv_nsec < 0) {
490+
ts->tv_sec -= sec;
491+
ts->tv_nsec += sec * (long)NSEC_PER_SEC;
492+
}
493+
494+
__ASSERT_NO_MSG(timespec_is_valid(ts));
495+
496+
return true;
497+
}
498+
499+
static inline bool timespec_add(struct timespec *a, const struct timespec *b)
500+
{
501+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
502+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
503+
504+
if ((a->tv_sec < 0) && (b->tv_sec < 0) && (INT64_MIN - a->tv_sec > b->tv_sec)) {
505+
/* negative integer overflow would occur */
506+
return false;
507+
}
508+
509+
if ((a->tv_sec > 0) && (b->tv_sec > 0) && (INT64_MAX - a->tv_sec < b->tv_sec)) {
510+
/* positive integer overflow would occur */
511+
return false;
512+
}
513+
514+
a->tv_sec += b->tv_sec;
515+
a->tv_nsec += b->tv_nsec;
516+
517+
return timespec_normalize(a);
518+
}
519+
520+
static inline bool timespec_negate(struct timespec *ts)
521+
{
522+
__ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts));
523+
524+
if (ts->tv_sec == INT64_MIN) {
525+
/* -INT64_MIN > INT64_MAX, so +ve integer overflow would occur */
526+
return false;
527+
}
528+
529+
ts->tv_sec = -ts->tv_sec;
530+
ts->tv_nsec = -ts->tv_nsec;
531+
532+
return timespec_normalize(ts);
533+
}
534+
#endif
535+
536+
/**
537+
* @brief Subtract one timespec from another
538+
*
539+
* This function subtracts the timespec pointed to by @p b from the timespec pointed to by @p a and
540+
* stores the result in the timespce pointed to by @p a.
541+
*
542+
* If the operation would result in integer overflow, return value is `false`.
543+
*
544+
* @note @p a and @p b must be non-`NULL`.
545+
*
546+
* @param a the timespec which is subtracted from
547+
* @param b the timespec to be subtracted
548+
*
549+
* @return `true` if the operation is successful, otherwise `false`.
550+
*/
551+
static inline bool timespec_sub(struct timespec *a, const struct timespec *b)
552+
{
553+
__ASSERT_NO_MSG(a != NULL);
554+
__ASSERT_NO_MSG(b != NULL);
555+
556+
struct timespec neg = *b;
557+
558+
return timespec_negate(&neg) && timespec_add(a, &neg);
559+
}
560+
561+
/**
562+
* @brief Compare two timespec objects
563+
*
564+
* This function compares two timespec objects pointed to by @p a and @p b.
565+
*
566+
* @note @p a and @p b must be non-`NULL` and normalized.
567+
*
568+
* @param a the first timespec to compare
569+
* @param b the second timespec to compare
570+
*
571+
* @return -1, 0, or +1 if @a a is less than, equal to, or greater than @a b, respectively.
572+
*/
573+
static inline int timespec_compare(const struct timespec *a, const struct timespec *b)
574+
{
575+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
576+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
577+
578+
return (((a->tv_sec == b->tv_sec) && (a->tv_nsec < b->tv_nsec)) * -1) +
579+
(((a->tv_sec == b->tv_sec) && (a->tv_nsec > b->tv_nsec)) * 1) +
580+
((a->tv_sec < b->tv_sec) * -1) + ((a->tv_sec > b->tv_sec));
581+
}
582+
583+
/**
584+
* @brief Check if two timespec objects are equal
585+
*
586+
* This function checks if the two timespec objects pointed to by @p a and @p b are equal.
587+
*
588+
* @note @p a and @p b must be non-`NULL` are not required to be normalized.
589+
*
590+
* @param a the first timespec to compare
591+
* @param b the second timespec to compare
592+
*
593+
* @return true if the two timespec objects are equal, otherwise false.
594+
*/
595+
static inline bool timespec_equal(const struct timespec *a, const struct timespec *b)
596+
{
597+
__ASSERT_NO_MSG(a != NULL);
598+
__ASSERT_NO_MSG(b != NULL);
599+
600+
return (a->tv_sec == b->tv_sec) && (a->tv_nsec == b->tv_nsec);
601+
}
602+
304603
/**
305604
* @}
306605
*/

0 commit comments

Comments
 (0)