Skip to content

Commit dd6af19

Browse files
committed
sys: util: timeutil: add timespec manipulation utilities
Add a number of utility functions for manipulating struct timespec. * timespec_add_overflow() * timespec_compare() * timespec_equal() * timespec_is_valid() * timespec_negate_overflow() * timespec_normalize_overflow() * timespec_sub_overflow() 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 dd6af19

File tree

1 file changed

+279
-0
lines changed

1 file changed

+279
-0
lines changed

include/zephyr/sys/timeutil.h

Lines changed: 279 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,278 @@ 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+
#if (HAS_BUILTIN(__builtin_add_overflow) && HAS_BUILTIN(__builtin_sub_overflow)) || \
344+
defined(__DOXYGEN__)
345+
/**
346+
* @brief Normalize a timespec so that the `tv_nsec` field is in valid range.
347+
*
348+
* Normalize a timespec by adjusting the `tv_sec` and `tv_nsec` fields so that the `tv_nsec` field
349+
* is in the range `[0, NSEC_PER_SEC-1]`. This is achieved by converting nanoseconds to seconds and
350+
* accumulating seconds in either the positive direction when `tv_nsec` > `NSEC_PER_SEC`, or in the
351+
* negative direction when `tv_nsec` < 0.
352+
*
353+
* In pseudocode, normalization can be done as follows:
354+
* ```python
355+
* if ts.tv_nsec >= NSEC_PER_SEC:
356+
* sec = ts.tv_nsec / NSEC_PER_SEC
357+
* ts.tv_sec += sec
358+
* ts.tv_nsec -= sec * NSEC_PER_SEC
359+
* elif ts.tv_nsec < 0:
360+
* # div_round_up(abs(ts->tv_nsec), NSEC_PER_SEC)
361+
* sec = (NSEC_PER_SEC - ts.tv_nsec - 1) / NSEC_PER_SEC
362+
* ts.tv_sec -= sec;
363+
* ts.tv_nsec += sec * NSEC_PER_SEC;
364+
* ```
365+
*
366+
* @note There are two cases where the normalization can result in integer overflow. These can
367+
* be extrapolated to not simply overflowing the `tv_sec` field by one second, but also by any
368+
* realizable multiple of `NSEC_PER_SEC`.
369+
*
370+
* 1. When `tv_nsec` is negative and `tv_sec` is already most negative.
371+
* 2. When `tv_nsec` is greater-or-equal to `NSEC_PER_SEC` and `tv_sec` is already most positive.
372+
*
373+
* @note @p ts must be non-`NULL`.
374+
*
375+
* @param ts the timespec to be normalized
376+
*
377+
* @return `true` if the operation would result in integer overflow, otherwise `false`.
378+
*/
379+
static inline bool timespec_normalize_overflow(struct timespec *ts)
380+
{
381+
__ASSERT_NO_MSG(ts != NULL);
382+
383+
int sign = (ts->tv_nsec >= 0) - (ts->tv_nsec < 0);
384+
int64_t sec = (ts->tv_nsec >= NSEC_PER_SEC) * (ts->tv_nsec / NSEC_PER_SEC) +
385+
(ts->tv_nsec < 0) * DIV_ROUND_UP(-ts->tv_nsec, NSEC_PER_SEC);
386+
bool overflow = __builtin_add_overflow(ts->tv_sec, sign * sec, &ts->tv_sec) ||
387+
__builtin_sub_overflow(ts->tv_nsec, (long)(sign * sec * NSEC_PER_SEC),
388+
&ts->tv_nsec);
389+
390+
if (!overflow) {
391+
__ASSERT_NO_MSG(timespec_is_valid(ts));
392+
}
393+
394+
return overflow;
395+
}
396+
397+
/**
398+
* @brief Add one timespec to another
399+
*
400+
* This function sums the two timespecs pointed to by @p a and @p b and stores the result in the
401+
* timespce pointed to by @p a.
402+
*
403+
* @note @p a and @p b must be non-`NULL` and normalized.
404+
*
405+
* @param a the timespec which is added to
406+
* @param b the timespec to be added
407+
*
408+
* @return `true` if the operation would result in integer overflow, otherwise `false`.
409+
*/
410+
static inline bool timespec_add_overflow(struct timespec *a, const struct timespec *b)
411+
{
412+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
413+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
414+
415+
return __builtin_add_overflow(a->tv_sec, b->tv_sec, &a->tv_sec) ||
416+
__builtin_add_overflow(a->tv_nsec, b->tv_nsec, &a->tv_nsec) ||
417+
timespec_normalize_overflow(a);
418+
}
419+
420+
/**
421+
* @brief Negate a timespec object
422+
*
423+
* Negate the timespec object pointed to by @p ts and store the result in the same
424+
* memory location.
425+
*
426+
* @param ts The timespec object to negate.
427+
*
428+
* @return `true` of the operation would overflow, otherwise `false`.
429+
*/
430+
static inline bool timespec_negate_overflow(struct timespec *ts)
431+
{
432+
__ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts));
433+
434+
return __builtin_sub_overflow(0LL, ts->tv_sec, &ts->tv_sec) ||
435+
__builtin_sub_overflow(0L, ts->tv_nsec, &ts->tv_nsec) ||
436+
timespec_normalize_overflow(ts);
437+
}
438+
#else
439+
440+
static inline bool timespec_normalize_overflow(struct timespec *ts)
441+
{
442+
__ASSERT_NO_MSG(ts != NULL);
443+
444+
/* This implementation is unoptimized mainly for readability */
445+
446+
long sec = 0;
447+
448+
if (ts->tv_nsec >= NSEC_PER_SEC) {
449+
sec = ts->tv_nsec / NSEC_PER_SEC;
450+
} else if (ts->tv_nsec < 0) {
451+
sec = DIV_ROUND_UP(-ts->tv_nsec, NSEC_PER_SEC);
452+
}
453+
454+
if ((ts->tv_nsec < 0) && (ts->tv_sec < 0) && (ts->tv_sec - INT64_MIN < sec)) {
455+
/*
456+
* When `tv_nsec` is negative and `tv_sec` is already most negative,
457+
* further subtraction would cause integer overflow.
458+
*/
459+
return true;
460+
}
461+
462+
if ((ts->tv_nsec >= NSEC_PER_SEC) && (ts->tv_sec > 0) && (INT64_MAX - ts->tv_sec < sec)) {
463+
/*
464+
* When `tv_nsec` is >= `NSEC_PER_SEC` and `tv_sec` is already most
465+
* positive, further addition would cause integer overflow.
466+
*/
467+
return true;
468+
}
469+
470+
if (ts->tv_nsec >= NSEC_PER_SEC) {
471+
ts->tv_sec += sec;
472+
ts->tv_nsec -= sec * NSEC_PER_SEC;
473+
} else if (ts->tv_nsec < 0) {
474+
ts->tv_sec -= sec;
475+
ts->tv_nsec += sec * NSEC_PER_SEC;
476+
}
477+
478+
return false;
479+
}
480+
481+
static inline bool timespec_add_overflow(struct timespec *a, const struct timespec *b)
482+
{
483+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
484+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
485+
486+
if ((a->tv_sec < 0) && (b->tv_sec < 0) && (INT64_MIN - a->tv_sec > b->tv_sec)) {
487+
/* negative integer overflow would occur */
488+
return true;
489+
}
490+
491+
if ((a->tv_sec > 0) && (b->tv_sec > 0) && (INT64_MAX - a->tv_sec < b->tv_sec)) {
492+
/* positive integer overflow would occur */
493+
return true;
494+
}
495+
496+
a->tv_sec += b->tv_sec;
497+
a->tv_nsec += b->tv_nsec;
498+
499+
return timespec_normalize_overflow(a);
500+
}
501+
502+
static inline bool timespec_negate_overflow(struct timespec *ts)
503+
{
504+
__ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts));
505+
506+
if (ts->tv_sec == INT64_MIN) {
507+
/* -INT64_MIN > INT64_MAX, so +ve integer overflow would occur */
508+
return true;
509+
}
510+
511+
ts->tv_sec = -ts->tv_sec;
512+
ts->tv_nsec = -ts->tv_nsec;
513+
514+
return timespec_normalize_overflow(ts);
515+
}
516+
#endif
517+
518+
/**
519+
* @brief Subtract one timespec from another
520+
*
521+
* This function subtracts the timespec pointed to by @p b from the timespec pointed to by @p b and
522+
* stores the result in the timespce pointed to by @p a.
523+
*
524+
* @note @p a and @p b must be non-`NULL`.
525+
*
526+
* @param a the timespec which is subtracted from
527+
* @param b the timespec to be subtracted
528+
*
529+
* @return `true` if the operation would result in integer overflow, otherwise `false`.
530+
*/
531+
static inline bool timespec_sub_overflow(struct timespec *a, const struct timespec *b)
532+
{
533+
__ASSERT_NO_MSG(a != NULL);
534+
__ASSERT_NO_MSG(b != NULL);
535+
536+
struct timespec neg = *b;
537+
538+
return timespec_negate_overflow(&neg) || timespec_add_overflow(a, &neg);
539+
}
540+
541+
/**
542+
* @brief Compare two timespec objects
543+
*
544+
* This function compares two timespec objects pointed to by @p a and @p b.
545+
*
546+
* @note @p a and @p b must be non-`NULL` and normalized.
547+
*
548+
* @param a the first timespec to compare
549+
* @param b the second timespec to compare
550+
*
551+
* @return -1, 0, or +1 if @a a is less than, equal to, or greater than @a b, respectively.
552+
*/
553+
static inline int timespec_compare(const struct timespec *a, const struct timespec *b)
554+
{
555+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
556+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
557+
558+
return (((a->tv_sec == b->tv_sec) && (a->tv_nsec < b->tv_nsec)) * -1) +
559+
(((a->tv_sec == b->tv_sec) && (a->tv_nsec > b->tv_nsec)) * 1) +
560+
((a->tv_sec < b->tv_sec) * -1) + ((a->tv_sec > b->tv_sec));
561+
}
562+
563+
/**
564+
* @brief Check if two timespec objects are equal
565+
*
566+
* This function checks if the two timespec objects pointed to by @p a and @p b are equal.
567+
*
568+
* @note @p a and @p b must be non-`NULL` are not required to be normalized.
569+
*
570+
* @param a the first timespec to compare
571+
* @param b the second timespec to compare
572+
*
573+
* @return true if the two timespec objects are equal, otherwise false.
574+
*/
575+
static inline bool timespec_equal(const struct timespec *a, const struct timespec *b)
576+
{
577+
__ASSERT_NO_MSG(a != NULL);
578+
__ASSERT_NO_MSG(b != NULL);
579+
580+
return (a->tv_sec == b->tv_sec) && (a->tv_nsec == b->tv_nsec);
581+
}
582+
304583
/**
305584
* @}
306585
*/

0 commit comments

Comments
 (0)