Skip to content

Commit b1ac3f2

Browse files
committed
sys: util: timespec_util: add collection of inlines for timespec
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() builtins are available, then these functions use are mostly branchless, which should provide decent performance on systems with an instruction cache. Otherwise, a manually written alternative exists that is perhaps more readable. Signed-off-by: Chris Friedt <[email protected]>
1 parent 4512cbf commit b1ac3f2

File tree

1 file changed

+315
-0
lines changed

1 file changed

+315
-0
lines changed

include/zephyr/sys/timespec_util.h

+315
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
/*
2+
* Copyright 2025 Tenstorrent AI ULC
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* @file
9+
* @brief Utilities supporting operations on timespec structures.
10+
*
11+
* This file provides utility functions for manipulating timespec structures, including
12+
* normalization, addition, subtraction, comparison, and negation. It also includes functions to
13+
* check the validity of timespec structures and to compare them for equality.
14+
*
15+
* In case the compiler supports `HAS_BUILTIN(__builtin_add_overflow)` and
16+
* `HAS_BUILTIN(__builtin_sub_overflow))`, the majority of this API is branchless and should
17+
* perform relatively fast on systems with instruction caches.
18+
*/
19+
20+
#ifndef ZEPHYR_INCLUDE_SYS_TIMESPEC_UTIL_H_
21+
#define ZEPHYR_INCLUDE_SYS_TIMESPEC_UTIL_H_
22+
23+
#include <limits.h>
24+
#include <stdbool.h>
25+
#include <stddef.h>
26+
#include <stdint.h>
27+
#include <time.h>
28+
29+
#include <zephyr/sys_clock.h>
30+
#include <zephyr/sys/__assert.h>
31+
#include <zephyr/toolchain.h>
32+
33+
#ifdef __cplusplus
34+
extern "C" {
35+
#endif
36+
37+
BUILD_ASSERT(sizeof(time_t) == sizeof(int64_t), "time_t must be 64-bit");
38+
BUILD_ASSERT(sizeof(((struct timespec *)0)->tv_sec) == sizeof(int64_t), "tv_sec must be 64-bit");
39+
40+
/**
41+
* @defgroup timespec_util_apis Timespec Utility APIs
42+
* @ingroup utilities
43+
* @{
44+
*/
45+
46+
/**
47+
* @brief Check if a timespec is valid.
48+
*
49+
* Check if a timespec is valid by ensuring that the `tv_nsec` field is in the range `[0,
50+
* NSEC_PER_SEC-1]`.
51+
*
52+
* @note @p ts must not be `NULL`.
53+
*
54+
* @param ts the timespec to check
55+
*
56+
* @return `true` if the timespec is valid, otherwise `false`.
57+
*/
58+
static inline bool timespec_is_valid(const struct timespec *ts)
59+
{
60+
__ASSERT_NO_MSG(ts != NULL);
61+
62+
return (ts->tv_nsec >= 0) && (ts->tv_nsec < NSEC_PER_SEC);
63+
}
64+
65+
#if (HAS_BUILTIN(__builtin_add_overflow) && HAS_BUILTIN(__builtin_sub_overflow)) || \
66+
defined(__DOXYGEN__)
67+
/**
68+
* @brief Normalize a timespec so that the `tv_nsec` field is in valid range.
69+
*
70+
* Normalize a timespec by adjusting the `tv_sec` and `tv_nsec` fields so that the `tv_nsec` field
71+
* is in the range `[0, NSEC_PER_SEC-1]`. This is achieved by converting nanoseconds to seconds and
72+
* accumulating seconds in either the positive direction when `tv_nsec` > `NSEC_PER_SEC`, or in the
73+
* negative direction when `tv_nsec` < 0.
74+
*
75+
* In pseudocode, normalization can be done as follows:
76+
* ```python
77+
* if ts.tv_nsec >= NSEC_PER_SEC:
78+
* sec = ts.tv_nsec / NSEC_PER_SEC
79+
* ts.tv_sec += sec
80+
* ts.tv_nsec -= sec * NSEC_PER_SEC
81+
* elif ts.tv_nsec < 0:
82+
* # div_round_up(abs(ts->tv_nsec), NSEC_PER_SEC)
83+
* sec = (NSEC_PER_SEC - ts.tv_nsec - 1) / NSEC_PER_SEC
84+
* ts.tv_sec -= sec;
85+
* ts.tv_nsec += sec * NSEC_PER_SEC;
86+
* ```
87+
*
88+
* @note There are two cases where the normalization can result in integer overflow. These can
89+
* be extrapolated to not simply overflowing the `tv_sec` field by one second, but also by any
90+
* realizable multiple of `NSEC_PER_SEC`.
91+
*
92+
* 1. When `tv_nsec` is negative and `tv_sec` is already most negative.
93+
* 2. When `tv_nsec` is greater-or-equal to `NSEC_PER_SEC` and `tv_sec` is already most positive.
94+
*
95+
* @note @p ts must be non-`NULL`.
96+
*
97+
* @param ts the timespec to be normalized
98+
*
99+
* @return `true` if the operation would result in integer overflow, otherwise `false`.
100+
*/
101+
static inline bool timespec_normalize(struct timespec *ts)
102+
{
103+
__ASSERT_NO_MSG(ts != NULL);
104+
105+
int sign = (ts->tv_nsec >= 0) - (ts->tv_nsec < 0);
106+
int64_t sec = (ts->tv_nsec >= NSEC_PER_SEC) * (ts->tv_nsec / NSEC_PER_SEC) +
107+
(ts->tv_nsec < 0) * DIV_ROUND_UP(-ts->tv_nsec, NSEC_PER_SEC);
108+
bool overflow = __builtin_add_overflow(ts->tv_sec, sign * sec, &ts->tv_sec) ||
109+
__builtin_sub_overflow(ts->tv_nsec, (long)(sign * sec * NSEC_PER_SEC),
110+
&ts->tv_nsec);
111+
112+
if (!overflow) {
113+
__ASSERT_NO_MSG(timespec_is_valid(ts));
114+
}
115+
116+
return overflow;
117+
}
118+
119+
/**
120+
* @brief Add one timespec to another
121+
*
122+
* This function sums the two timespecs pointed to by @p a and @p b and stores the result in the
123+
* timespce pointed to by @p a.
124+
*
125+
* @note @p a and @p b must be non-`NULL` and normalized.
126+
*
127+
* @param a the timespec which is added to
128+
* @param b the timespec to be added
129+
*
130+
* @return `true` if the operation would result in integer overflow, otherwise `false`.
131+
*/
132+
static inline bool timespec_add(struct timespec *a, const struct timespec *b)
133+
{
134+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
135+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
136+
137+
return __builtin_add_overflow(a->tv_sec, b->tv_sec, &a->tv_sec) ||
138+
__builtin_add_overflow(a->tv_nsec, b->tv_nsec, &a->tv_nsec) || timespec_normalize(a);
139+
}
140+
141+
/**
142+
* @brief Negate a timespec object
143+
*
144+
* Negate the timespec object pointed to be @p ts and store the result in the same
145+
* memory location.
146+
*
147+
* @param ts The timespec object to negate.
148+
*
149+
* @return `true` of the operation would overflow, otherwise `false`.
150+
*/
151+
static inline bool timespec_negate(struct timespec *ts)
152+
{
153+
__ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts));
154+
155+
return __builtin_sub_overflow(0LL, ts->tv_sec, &ts->tv_sec) ||
156+
__builtin_sub_overflow(0L, ts->tv_nsec, &ts->tv_nsec) || timespec_normalize(ts);
157+
}
158+
#else
159+
160+
static inline bool timespec_normalize(struct timespec *ts)
161+
{
162+
__ASSERT_NO_MSG(ts != NULL);
163+
164+
/* This implementation is unoptimized mainly for readability */
165+
166+
long sec = 0;
167+
168+
if (ts->tv_nsec >= NSEC_PER_SEC) {
169+
sec = ts->tv_nsec / NSEC_PER_SEC;
170+
} else if (ts->tv_nsec < 0) {
171+
sec = DIV_ROUND_UP(-ts->tv_nsec, NSEC_PER_SEC);
172+
}
173+
174+
if (sizeof((ts->tv_sec) == sizeof(int64_t))) {
175+
if ((ts->tv_nsec < 0) && (ts->tv_sec < 0) && (ts->tv_sec - INT64_MIN < sec)) {
176+
/*
177+
* When `tv_nsec` is negative and `tv_sec` is already most negative,
178+
* further subtraction would cause integer overflow.
179+
*/
180+
return true;
181+
}
182+
183+
if ((ts->tv_nsec >= NSEC_PER_SEC) && (ts->tv_sec > 0) &&
184+
(INT64_MAX - ts->tv_sec < sec)) {
185+
/*
186+
* When `tv_nsec` is >= `NSEC_PER_SEC` and `tv_sec` is already most
187+
* positive, further addition would cause integer overflow.
188+
*/
189+
return true;
190+
}
191+
}
192+
193+
if (ts->tv_nsec >= NSEC_PER_SEC) {
194+
ts->tv_sec += sec;
195+
ts->tv_nsec -= sec * NSEC_PER_SEC;
196+
} else if (ts->tv_nsec < 0) {
197+
ts->tv_sec -= sec;
198+
ts->tv_nsec += sec * NSEC_PER_SEC;
199+
}
200+
201+
return false;
202+
}
203+
204+
static inline bool timespec_add(struct timespec *a, const struct timespec *b)
205+
{
206+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
207+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
208+
209+
if ((a->tv_sec < 0) && (b->tv_sec < 0) && (INT64_MIN - a->tv_sec > b->tv_sec)) {
210+
/* negative integer overflow would occur */
211+
return true;
212+
}
213+
214+
if ((a->tv_sec > 0) && (b->tv_sec > 0) && (INT64_MAX - a->tv_sec < b->tv_sec)) {
215+
/* positive integer overflow would occur */
216+
return true;
217+
}
218+
219+
a->tv_sec += b->tv_sec;
220+
a->tv_nsec += b->tv_nsec;
221+
222+
return timespec_normalize(a);
223+
}
224+
225+
static inline bool timespec_negate(struct timespec *ts)
226+
{
227+
__ASSERT_NO_MSG((ts != NULL) && timespec_is_valid(ts));
228+
229+
if (ts->tv_sec == INT64_MIN) {
230+
/* -INT64_MIN > INT64_MAX, so +ve integer overflow would occur */
231+
return true;
232+
}
233+
234+
ts->tv_sec = -ts->tv_sec;
235+
ts->tv_nsec = -ts->tv_nsec;
236+
237+
return timespec_normalize(ts);
238+
}
239+
#endif /* (HAS_BUILTIN(__builtin_add_overflow) && HAS_BUILTIN(__builtin_sub_overflow)) || \
240+
defined(__DOXYGEN__) */
241+
242+
/**
243+
* @brief Subtract one timespec from another
244+
*
245+
* This function subtracts the timespec pointed to by @p b from the timespec pointed to by @p b and
246+
* stores the result in the timespce pointed to by @p a.
247+
*
248+
* @note @p a and @p b must be non-`NULL`.
249+
*
250+
* @param a the timespec which is subtracted from
251+
* @param b the timespec to be subtracted
252+
*
253+
* @return `true` if the operation would result in integer overflow, otherwise `false`.
254+
*/
255+
static inline bool timespec_sub(struct timespec *a, const struct timespec *b)
256+
{
257+
__ASSERT_NO_MSG(a != NULL);
258+
__ASSERT_NO_MSG(b != NULL);
259+
260+
struct timespec neg = *b;
261+
262+
return timespec_negate(&neg) || timespec_add(a, &neg);
263+
}
264+
265+
/**
266+
* @brief Compare two timespec objects
267+
*
268+
* This function compares two timespec objects pointed to by @p a and @p b.
269+
*
270+
* @note @p a and @p b must be non-`NULL` and normalized.
271+
*
272+
* @param a the first timespec to compare
273+
* @param b the second timespec to compare
274+
*
275+
* @return -1, 0, or +1 if @a a is less than, equal to, or greater than @a b, respectively.
276+
*/
277+
static inline int timespec_compare(const struct timespec *a, const struct timespec *b)
278+
{
279+
__ASSERT_NO_MSG((a != NULL) && timespec_is_valid(a));
280+
__ASSERT_NO_MSG((b != NULL) && timespec_is_valid(b));
281+
282+
return +(((a->tv_sec == b->tv_sec) && (a->tv_nsec < b->tv_nsec)) * -1) +
283+
(((a->tv_sec == b->tv_sec) && (a->tv_nsec > b->tv_nsec)) * 1) +
284+
((a->tv_sec < b->tv_sec) * -1) + ((a->tv_sec > b->tv_sec));
285+
}
286+
287+
/**
288+
* @brief Check if two timespec objects are equal
289+
*
290+
* This function checks if the two timespec objects pointed to by @p a and @p b are equal.
291+
*
292+
* @note @p a and @p b must be non-`NULL` are not required to be normalized.
293+
*
294+
* @param a the first timespec to compare
295+
* @param b the second timespec to compare
296+
*
297+
* @return true if the two timespec objects are equal, otherwise false.
298+
*/
299+
static inline bool timespec_equal(const struct timespec *a, const struct timespec *b)
300+
{
301+
__ASSERT_NO_MSG(a != NULL);
302+
__ASSERT_NO_MSG(b != NULL);
303+
304+
return (a->tv_sec == b->tv_sec) && (a->tv_nsec == b->tv_nsec);
305+
}
306+
307+
#ifdef __cplusplus
308+
}
309+
#endif
310+
311+
/**
312+
* @}
313+
*/
314+
315+
#endif /* ZEPHYR_INCLUDE_SYS_TIMESPEC_UTIL_H_ */

0 commit comments

Comments
 (0)