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