-
Notifications
You must be signed in to change notification settings - Fork 7.4k
sys: timeutil: add utility functions for struct timespec #90060
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
cc @trembel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since timespec is specific to posix, should it be moved to include/zephyr/posix/
where
struct` timespec {
time_t tv_sec;
long tv_nsec;
};
is also defined? include/zephyr/sys
I believe is primarily for zephyr native libraries, though there obviously are a few overlaps there like fdtable. Not sure how or where we draw such lines :)
include/zephyr/sys/timespec_util.h
Outdated
* | ||
* @return `true` if the operation would result in integer overflow, otherwise `false`. | ||
*/ | ||
static inline bool timespec_add(struct timespec *a, const struct timespec *b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if timespec
itself is not nameclashing, the header should probably just be named timespec.h
given the namespace timespec_util
is not used for any function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just merged it directly into timeutil.h
include/zephyr/sys/timespec_util.h
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the structure of the documentation, it might be better just to add the contents of this file directly to include/zephyr/sys/timeutil.h
and adjust doxygen groups accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few comments:
First, the bool return value. It feels to me like it should be the other
way around i.e. true=OK and false=problem. Currently you have things like:
return timespec_negate(&neg) || timespec_add(a, &neg);
This just looks semantically wrong. It should be like: if we can negate
and add then return OK e.g.:
return timespec_negate(&neg) && timespec_add(a, &neg);
Another example:
return __builtin_add_overflow(a->tv_sec, b->tv_sec, &a->tv_sec) ||
__builtin_add_overflow(a->tv_nsec, b->tv_nsec, &a->tv_nsec) ||
timespec_normalize(a);
IMHO this would be more logically readable as:
return !__builtin_add_overflow(a->tv_sec, b->tv_sec, &a->tv_sec) &&
!__builtin_add_overflow(a->tv_nsec, b->tv_nsec, &a->tv_nsec) &&
timespec_normalize(a);
Translation: if no overflow on the first add and no overflow on the second
add and normalization worked then return OK (true). Yes,
__builtin_add_overflow()
returns true on error but that's in the name.
Here we have timespec_add()
and the name doesn't imply an error.
Users would have more readable code if:
if (timespec_add(a, b) {
/* true: the addition was possible */
} else {
/* false: addition was _not_ possible */
}
Next, I'm a little concerned by the optimization. This:
int64_t sec = (ts->tv_nsec >= NSEC_PER_SEC) * (ts->tv_nsec / NSEC_PER_SEC) +
+ (ts->tv_nsec < 0) * DIV_ROUND_UP(-ts->tv_nsec, NSEC_PER_SEC);
That's potentially two divisions just for the purpose of avoiding a branch.
On many architectures (if not most) a branch is still cheaper than a
division, even more so if the division ends up in a library call given
a 64-bit dividend.
And finally, you should add more values to your test case. In particular,
tests with tv_nsec being -NSEC_PER_SEC - 1
, -2*NSEC_PER_SEC + 1
,
-2*NSEC_PER_SEC - 1
, etc. would provide more coverage.
@bjarki-andreasen - mentioned in these docs as well as the C standard, while |
Fair enough :) I have no strong opinion, just noticed that we do define the timespec struct in the posix folder. Anyway PR looks good, definate improvement! |
From my perspective, that is worse - there are 5 logical operations instead of 3.
I'm sort of thinking I would prefer to just append
Yeah, I could definitely rename that to https://github.com/zephyrproject-rtos/zephyr/tree/main/include/zephyr/sys/math_extras.h
Agreed. What I initially had is only cheap on processors that have e.g. icache, dcache, HW divide, and only cheap compared to potential cache misses. I would be happy to use
Absolutely - thanks for the feedback @npitre. |
Yeah, that's true - we should probably eliminate duplicate definitions especially with #30105 One kind of pervasive thing in POSIX (not sure if it's like this in the c spec as much) is that types can be originally defined in several header files. E.g. |
e72e78a
to
7aeb687
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO this would be more logically readable as:
return !__builtin_add_overflow(a->tv_sec, b->tv_sec, &a->tv_sec) && !__builtin_add_overflow(a->tv_nsec, b->tv_nsec, &a->tv_nsec) && timespec_normalize(a);
From my perspective, that is worse - there are 5 logical operations
instead of 3.
Absolutely not. There are no more operations. The compiler is happy
to flip the logic around as it see fit and it won't care one way or the
other.. OTOH this is better semantics for humans reading the code.
I'm sort of thinking I would prefer to just append
_overflow
. To me,
it's less useful to add multiple extra logical operations.
Please don't! This is an abomination!
Adding _overflow
everywhere instead of !
in a few places is a really
a bad tradeoff. Again, the compiler doesn't care either ways (the code won't
be any bigger or any less efficient).
8816044
to
2118238
Compare
Hehe.. sure, if you insist. I feel less strongly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
I might take a crack at fixing #90029 here as well. It would be nice to be able to put the tests in the |
da9e55d
to
7a7a195
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are doing:
.tv_sec = (timeout.ticks == K_TICKS_FOREVER) * INT64_MAX +
(timeout.ticks != K_TICKS_FOREVER) * (ns / NSEC_PER_SEC),
.tv_nsec = (timeout.ticks == K_TICKS_FOREVER) * (NSEC_PER_SEC - 1) +
(timeout.ticks != K_TICKS_FOREVER) *
(ns - (ns / NSEC_PER_SEC) * NSEC_PER_SEC),
Instead of (ns - (ns / NSEC_PER_SEC) * NSEC_PER_SEC)
you could simply do
(ns % NSEC_PER_SEC)
. When computing (ns / NSEC_PER_SEC)
earlier, the
modulus often comes for free as a byproduct of the division and the compiler
would just use it given the opportunity. Granted, a smart compiler will
conclude that the above code can be substituted by a modulus already,
meaning that in the end it doesn't make much difference to the resulting
binary, therefore it is best to just use the simpler code.
No need to respin the PR just for this hence my +1, but if you need to
re-push then this change should be considered.
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() * timespec_from_timeout() * timespec_to_timeout() If the `__builtin_add_overflow()` function is available, then the API is mostly branchless, which should provide decent performance on systems with an instruction cache and branch prediction. Otherwise, manually written alternatives exist that are also perhaps more readable. The two functions at the end convert time durations between representation as `struct timespec` and `k_timeout_t`. Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
Additional Testing Done: twister -i -p native_sim/native/64 -s buildsystem.cmake.board.extend_two
INFO - Using 'zephyr' toolchain.
INFO - Building initial testsuite list...
INFO - Writing JSON report /home/cfriedt/zephyrproject/zephyr/twister-out/testplan.json
INFO - JOBS: 32
INFO - Adding tasks to the queue...
INFO - Added initial list of jobs to queue
INFO - Total complete: 1/ 1 100% built (not run): 0, filtered: 0, failed: 0, error: 0
INFO - 1 test scenarios (1 configurations) selected, 0 configurations filtered (0 by static filter, 0 at runtime).
INFO - 1 of 1 executed test configurations passed (100.00%), 0 built (not run), 0 failed, 0 errored, with no warnings in 10.56 seconds.
INFO - 1 of 1 executed test cases passed (100.00%) on 1 out of total 1058 platforms (0.09%).
INFO - 1 test configurations executed on platforms, 0 test configurations were only built.
INFO - Saving reports...
INFO - Writing JSON report /home/cfriedt/zephyrproject/zephyr/twister-out/twister.json
INFO - Writing xunit report /home/cfriedt/zephyrproject/zephyr/twister-out/twister.xml...
INFO - Writing xunit report /home/cfriedt/zephyrproject/zephyr/twister-out/twister_report.xml...
INFO - Run completed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still missing one modulo in case you repush.
Add a timespec util testsuite. This should have reasonably high enough coverage to be useful. I would have preferred to add this as an architecture-independent unit test (for the unit_testing platform) under tests/unit/timeutil but there is an inconsistency about the size of time_t on the unit_testing and native_sim/native platforms. On every other platform supported by Zephyr, time_t is 64-bits. However, on those platforms, time_t is only 32-bits. Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
Add documentation in the timeutil group for recently added functions for validating, comparing, and manipulating `struct timespec` objects as well as functions for converting between representation of time durations as either `struct timespec` or `k_timeout_t`. Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
Use the newly added timespec util functions to manipulate and compare timespec structures with overflow detection. Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
@npitre - jeez - this is like Where's Waldo 😆 Can't sneak anything past you (not that I was trying, it was just hard for me to see). |
|
Add copyright for changes that were included with zephyrproject-rtos#90060. Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
@andyross, @peter-mitsis - please (re-)review when you have a moment. One of you must approve as assignee. |
@cfriedt clang is unhappy, could you please have a look? |
It's actually also failing with gcc https://github.com/zephyrproject-rtos/zephyr/actions/runs/15197606643 |
Add copyright for changes that were included with zephyrproject-rtos#90060. Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
Add copyright for changes that were included with #90060. Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
return (k_timeout_t){ | ||
.ticks = ((ts->tv_sec == INT64_MAX) && (ts->tv_nsec == NSEC_PER_SEC - 1)) * | ||
K_TICKS_FOREVER + | ||
((ts->tv_sec != INT64_MAX) && (ts->tv_sec >= 0)) * |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm seeing compile errors on this code.
/mnt/host/source/src/third_party/zephyr/main/include/zephyr/sys/timeutil.h:680:18: error: result of comparison of constant 9223372036854775807 with expression of type 'const __time_t' (aka 'const long') is
always true [-Werror,-Wtautological-constant-out-of-range-compare]
680 | ((ts->tv_sec != INT64_MAX) && (ts->tv_sec >= 0)) *
| ~~~~~~~~~~ ^ ~~~~~~~~~
/mnt/host/source/src/third_party/zephyr/main/include/zephyr/sys/timeutil.h:678:25: error: result of comparison of constant 9223372036854775807 with expression of type 'const __time_t' (aka 'const long') is
always false [-Werror,-Wtautological-constant-out-of-range-compare]
678 | .ticks = ((ts->tv_sec == INT64_MAX) && (ts->tv_nsec == NSEC_PER_SEC - 1)) *
| ~~~~~~~~~~ ^ ~~~~~~~~~
2 errors generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
west twister -p native_sim/native -s rollback/rollback.secure_clear_os
with clang
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add copyright for changes that were included with zephyrproject-rtos#90060. Signed-off-by: Chris Friedt <cfriedt@tenstorrent.com>
Add a number of utility functions for manipulating struct timespec.
If the
__builtin_add_overflow()
function is available, then these functions use are mostly branchless, which should provide decent performance on systems with caches and branch prediction (gated byCONFIG_SPEED_OPTIMIZATIONS
). Otherwise, manually written alternatives exist that are also perhaps more readable.The two functions at the end convert time durations between representation as
struct timespec
andk_timeout_t
.Fixes #88115
Doc Preview
Testing Done: