Ruby 3.5.0dev (2025-01-10 revision 5fab31b15e32622c4b71d1d347a41937e9f9c212)
hrtime.h (5fab31b15e32622c4b71d1d347a41937e9f9c212)
1#ifndef RB_HRTIME_H
2#define RB_HRTIME_H
3#include "ruby/ruby.h"
4#include <time.h>
5#if defined(HAVE_SYS_TIME_H)
6# include <sys/time.h>
7#endif
8
9#include "internal/compilers.h"
10
11/*
12 * Hi-res monotonic clock. It is currently nsec resolution, which has over
13 * 500 years of range (with an unsigned 64-bit integer). Developers
14 * targeting small systems may try 32-bit and low-resolution (milliseconds).
15 *
16 * TBD: Is nsec even necessary? usec resolution seems enough for userspace
17 * and it'll be suitable for use with devices lasting over 500,000 years
18 * (maybe some devices designed for long-term space travel)
19 *
20 * Current API:
21 *
22 * * rb_hrtime_now - current clock value (monotonic if available)
23 * * rb_hrtime_mul - multiply with overflow check
24 * * rb_hrtime_add - add with overflow check
25 * * rb_timeval2hrtime - convert from timeval
26 * * rb_timespec2hrtime - convert from timespec
27 * * rb_msec2hrtime - convert from millisecond
28 * * rb_sec2hrtime - convert from time_t (seconds)
29 * * rb_hrtime2timeval - convert to timeval
30 * * rb_hrtime2timespec - convert to timespec
31 *
32 * Note: no conversion to milliseconds is provided here because different
33 * functions have different limits (e.g. epoll_wait vs w32_wait_events).
34 * So we provide RB_HRTIME_PER_MSEC and similar macros for implementing
35 * this for each use case.
36 */
37#define RB_HRTIME_PER_USEC ((rb_hrtime_t)1000)
38#define RB_HRTIME_PER_MSEC (RB_HRTIME_PER_USEC * (rb_hrtime_t)1000)
39#define RB_HRTIME_PER_SEC (RB_HRTIME_PER_MSEC * (rb_hrtime_t)1000)
40#define RB_HRTIME_MAX UINT64_MAX
41#define RB_HRTIME_MIN ((rb_hrtime_t)0)
42
43/*
44 * Lets try to support time travelers. Lets assume anybody with a time machine
45 * also has access to a modern gcc or clang with 128-bit int support
46 */
47#ifdef MY_RUBY_BUILD_MAY_TIME_TRAVEL
48typedef int128_t rb_hrtime_t;
49#else
50typedef uint64_t rb_hrtime_t;
51#endif
52
53/* thread.c */
54/* returns the value of the monotonic clock (if available) */
55rb_hrtime_t rb_hrtime_now(void);
56
57/*
58 * multiply @a and @b with overflow check and return the
59 * (clamped to RB_HRTIME_MAX) result.
60 */
61static inline rb_hrtime_t
62rb_hrtime_mul(rb_hrtime_t a, rb_hrtime_t b)
63{
64 rb_hrtime_t c;
65
66#ifdef ckd_mul
67 if (ckd_mul(&c, a, b))
68 return RB_HRTIME_MAX;
69
70#elif __has_builtin(__builtin_mul_overflow)
71 if (__builtin_mul_overflow(a, b, &c))
72 return RB_HRTIME_MAX;
73#else
74 if (b != 0 && a > RB_HRTIME_MAX / b) /* overflow */
75 return RB_HRTIME_MAX;
76 c = a * b;
77#endif
78 return c;
79}
80
81/*
82 * add @a and @b with overflow check and return the
83 * (clamped to RB_HRTIME_MAX) result.
84 */
85static inline rb_hrtime_t
86rb_hrtime_add(rb_hrtime_t a, rb_hrtime_t b)
87{
88 rb_hrtime_t c;
89
90#ifdef ckd_add
91 if (ckd_add(&c, a, b))
92 return RB_HRTIME_MAX;
93
94#elif __has_builtin(__builtin_add_overflow)
95 if (__builtin_add_overflow(a, b, &c))
96 return RB_HRTIME_MAX;
97#else
98 c = a + b;
99 if (c < a) /* overflow */
100 return RB_HRTIME_MAX;
101#endif
102 return c;
103}
104
105static inline rb_hrtime_t
106rb_hrtime_sub(rb_hrtime_t a, rb_hrtime_t b)
107{
108 if (a < b) {
109 return RB_HRTIME_MIN;
110 }
111 return a - b;
112}
113
114/*
115 * convert a timeval struct to rb_hrtime_t, clamping at RB_HRTIME_MAX
116 */
117static inline rb_hrtime_t
118rb_timeval2hrtime(const struct timeval *tv)
119{
120 rb_hrtime_t s = rb_hrtime_mul((rb_hrtime_t)tv->tv_sec, RB_HRTIME_PER_SEC);
121 rb_hrtime_t u = rb_hrtime_mul((rb_hrtime_t)tv->tv_usec, RB_HRTIME_PER_USEC);
122
123 return rb_hrtime_add(s, u);
124}
125
126/*
127 * convert a timespec struct to rb_hrtime_t, clamping at RB_HRTIME_MAX
128 */
129static inline rb_hrtime_t
130rb_timespec2hrtime(const struct timespec *ts)
131{
132 rb_hrtime_t s = rb_hrtime_mul((rb_hrtime_t)ts->tv_sec, RB_HRTIME_PER_SEC);
133
134 return rb_hrtime_add(s, (rb_hrtime_t)ts->tv_nsec);
135}
136
137/*
138 * convert a millisecond value to rb_hrtime_t, clamping at RB_HRTIME_MAX
139 */
140static inline rb_hrtime_t
141rb_msec2hrtime(unsigned long msec)
142{
143 return rb_hrtime_mul((rb_hrtime_t)msec, RB_HRTIME_PER_MSEC);
144}
145
146/*
147 * convert a time_t value to rb_hrtime_t, clamping at RB_HRTIME_MAX
148 * Negative values will be clamped at 0.
149 */
150static inline rb_hrtime_t
151rb_sec2hrtime(time_t sec)
152{
153 if (sec <= 0) return 0;
154
155 return rb_hrtime_mul((rb_hrtime_t)sec, RB_HRTIME_PER_SEC);
156}
157
158/*
159 * convert a rb_hrtime_t value to a timespec, suitable for calling
160 * functions like ppoll(2) or kevent(2)
161 */
162static inline struct timespec *
163rb_hrtime2timespec(struct timespec *ts, const rb_hrtime_t *hrt)
164{
165 if (hrt) {
166 ts->tv_sec = (time_t)(*hrt / RB_HRTIME_PER_SEC);
167 ts->tv_nsec = (int32_t)(*hrt % RB_HRTIME_PER_SEC);
168 return ts;
169 }
170 return 0;
171}
172
173/*
174 * convert a rb_hrtime_t value to a timeval, suitable for calling
175 * functions like select(2)
176 */
177static inline struct timeval *
178rb_hrtime2timeval(struct timeval *tv, const rb_hrtime_t *hrt)
179{
180 if (hrt) {
181 tv->tv_sec = (time_t)(*hrt / RB_HRTIME_PER_SEC);
182 tv->tv_usec = (int32_t)((*hrt % RB_HRTIME_PER_SEC)/RB_HRTIME_PER_USEC);
183
184 return tv;
185 }
186 return 0;
187}
188
189#include "internal/warnings.h"
190#include "internal/time.h"
191
192/*
193 * Back when we used "struct timeval", not all platforms implemented
194 * tv_sec as time_t. Nowadays we use "struct timespec" and tv_sec
195 * seems to be implemented more consistently across platforms.
196 * At least other parts of our code hasn't had to deal with non-time_t
197 * tv_sec in timespec...
198 */
199#define TIMESPEC_SEC_MAX TIMET_MAX
200#define TIMESPEC_SEC_MIN TIMET_MIN
201
202COMPILER_WARNING_PUSH
203#if __has_warning("-Wimplicit-int-float-conversion")
204COMPILER_WARNING_IGNORED(-Wimplicit-int-float-conversion)
205#elif defined(_MSC_VER)
206/* C4305: 'initializing': truncation from '__int64' to 'const double' */
207COMPILER_WARNING_IGNORED(4305)
208#endif
209static const double TIMESPEC_SEC_MAX_as_double = TIMESPEC_SEC_MAX;
210COMPILER_WARNING_POP
211
212static inline rb_hrtime_t *
213double2hrtime(rb_hrtime_t *hrt, double d)
214{
215 /* assume timespec.tv_sec has same signedness as time_t */
216 const double TIMESPEC_SEC_MAX_PLUS_ONE = 2.0 * (TIMESPEC_SEC_MAX_as_double / 2.0 + 1.0);
217
218 if (TIMESPEC_SEC_MAX_PLUS_ONE <= d) {
219 *hrt = RB_HRTIME_MAX;
220 return NULL;
221 }
222 else if (d <= 0) {
223 *hrt = 0;
224 }
225 else {
226 *hrt = (rb_hrtime_t)(d * (double)RB_HRTIME_PER_SEC);
227 }
228 return hrt;
229}
230
231static inline double
232hrtime2double(rb_hrtime_t hrt)
233{
234 return (double)hrt / (double)RB_HRTIME_PER_SEC;
235}
236
237#endif /* RB_HRTIME_H */