diff options
Diffstat (limited to 'hrtime.h')
| -rw-r--r-- | hrtime.h | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/hrtime.h b/hrtime.h new file mode 100644 index 0000000000..2ca7b0c088 --- /dev/null +++ b/hrtime.h @@ -0,0 +1,237 @@ +#ifndef RB_HRTIME_H +#define RB_HRTIME_H +#include "ruby/ruby.h" +#include <time.h> +#if defined(HAVE_SYS_TIME_H) +# include <sys/time.h> +#endif + +#include "internal/compilers.h" + +/* + * Hi-res monotonic clock. It is currently nsec resolution, which has over + * 500 years of range (with an unsigned 64-bit integer). Developers + * targeting small systems may try 32-bit and low-resolution (milliseconds). + * + * TBD: Is nsec even necessary? usec resolution seems enough for userspace + * and it'll be suitable for use with devices lasting over 500,000 years + * (maybe some devices designed for long-term space travel) + * + * Current API: + * + * * rb_hrtime_now - current clock value (monotonic if available) + * * rb_hrtime_mul - multiply with overflow check + * * rb_hrtime_add - add with overflow check + * * rb_timeval2hrtime - convert from timeval + * * rb_timespec2hrtime - convert from timespec + * * rb_msec2hrtime - convert from millisecond + * * rb_sec2hrtime - convert from time_t (seconds) + * * rb_hrtime2timeval - convert to timeval + * * rb_hrtime2timespec - convert to timespec + * + * Note: no conversion to milliseconds is provided here because different + * functions have different limits (e.g. epoll_wait vs w32_wait_events). + * So we provide RB_HRTIME_PER_MSEC and similar macros for implementing + * this for each use case. + */ +#define RB_HRTIME_PER_USEC ((rb_hrtime_t)1000) +#define RB_HRTIME_PER_MSEC (RB_HRTIME_PER_USEC * (rb_hrtime_t)1000) +#define RB_HRTIME_PER_SEC (RB_HRTIME_PER_MSEC * (rb_hrtime_t)1000) +#define RB_HRTIME_MAX UINT64_MAX +#define RB_HRTIME_MIN ((rb_hrtime_t)0) + +/* + * Lets try to support time travelers. Lets assume anybody with a time machine + * also has access to a modern gcc or clang with 128-bit int support + */ +#ifdef MY_RUBY_BUILD_MAY_TIME_TRAVEL +typedef int128_t rb_hrtime_t; +#else +typedef uint64_t rb_hrtime_t; +#endif + +/* thread.c */ +/* returns the value of the monotonic clock (if available) */ +rb_hrtime_t rb_hrtime_now(void); + +/* + * multiply @a and @b with overflow check and return the + * (clamped to RB_HRTIME_MAX) result. + */ +static inline rb_hrtime_t +rb_hrtime_mul(rb_hrtime_t a, rb_hrtime_t b) +{ + rb_hrtime_t c; + +#ifdef ckd_mul + if (ckd_mul(&c, a, b)) + return RB_HRTIME_MAX; + +#elif __has_builtin(__builtin_mul_overflow) + if (__builtin_mul_overflow(a, b, &c)) + return RB_HRTIME_MAX; +#else + if (b != 0 && a > RB_HRTIME_MAX / b) /* overflow */ + return RB_HRTIME_MAX; + c = a * b; +#endif + return c; +} + +/* + * add @a and @b with overflow check and return the + * (clamped to RB_HRTIME_MAX) result. + */ +static inline rb_hrtime_t +rb_hrtime_add(rb_hrtime_t a, rb_hrtime_t b) +{ + rb_hrtime_t c; + +#ifdef ckd_add + if (ckd_add(&c, a, b)) + return RB_HRTIME_MAX; + +#elif __has_builtin(__builtin_add_overflow) + if (__builtin_add_overflow(a, b, &c)) + return RB_HRTIME_MAX; +#else + c = a + b; + if (c < a) /* overflow */ + return RB_HRTIME_MAX; +#endif + return c; +} + +static inline rb_hrtime_t +rb_hrtime_sub(rb_hrtime_t a, rb_hrtime_t b) +{ + if (a < b) { + return RB_HRTIME_MIN; + } + return a - b; +} + +/* + * convert a timeval struct to rb_hrtime_t, clamping at RB_HRTIME_MAX + */ +static inline rb_hrtime_t +rb_timeval2hrtime(const struct timeval *tv) +{ + rb_hrtime_t s = rb_hrtime_mul((rb_hrtime_t)tv->tv_sec, RB_HRTIME_PER_SEC); + rb_hrtime_t u = rb_hrtime_mul((rb_hrtime_t)tv->tv_usec, RB_HRTIME_PER_USEC); + + return rb_hrtime_add(s, u); +} + +/* + * convert a timespec struct to rb_hrtime_t, clamping at RB_HRTIME_MAX + */ +static inline rb_hrtime_t +rb_timespec2hrtime(const struct timespec *ts) +{ + rb_hrtime_t s = rb_hrtime_mul((rb_hrtime_t)ts->tv_sec, RB_HRTIME_PER_SEC); + + return rb_hrtime_add(s, (rb_hrtime_t)ts->tv_nsec); +} + +/* + * convert a millisecond value to rb_hrtime_t, clamping at RB_HRTIME_MAX + */ +static inline rb_hrtime_t +rb_msec2hrtime(unsigned long msec) +{ + return rb_hrtime_mul((rb_hrtime_t)msec, RB_HRTIME_PER_MSEC); +} + +/* + * convert a time_t value to rb_hrtime_t, clamping at RB_HRTIME_MAX + * Negative values will be clamped at 0. + */ +static inline rb_hrtime_t +rb_sec2hrtime(time_t sec) +{ + if (sec <= 0) return 0; + + return rb_hrtime_mul((rb_hrtime_t)sec, RB_HRTIME_PER_SEC); +} + +/* + * convert a rb_hrtime_t value to a timespec, suitable for calling + * functions like ppoll(2) or kevent(2) + */ +static inline struct timespec * +rb_hrtime2timespec(struct timespec *ts, const rb_hrtime_t *hrt) +{ + if (hrt) { + ts->tv_sec = (time_t)(*hrt / RB_HRTIME_PER_SEC); + ts->tv_nsec = (int32_t)(*hrt % RB_HRTIME_PER_SEC); + return ts; + } + return 0; +} + +/* + * convert a rb_hrtime_t value to a timeval, suitable for calling + * functions like select(2) + */ +static inline struct timeval * +rb_hrtime2timeval(struct timeval *tv, const rb_hrtime_t *hrt) +{ + if (hrt) { + tv->tv_sec = (time_t)(*hrt / RB_HRTIME_PER_SEC); + tv->tv_usec = (int32_t)((*hrt % RB_HRTIME_PER_SEC)/RB_HRTIME_PER_USEC); + + return tv; + } + return 0; +} + +#include "internal/warnings.h" +#include "internal/time.h" + +/* + * Back when we used "struct timeval", not all platforms implemented + * tv_sec as time_t. Nowadays we use "struct timespec" and tv_sec + * seems to be implemented more consistently across platforms. + * At least other parts of our code hasn't had to deal with non-time_t + * tv_sec in timespec... + */ +#define TIMESPEC_SEC_MAX TIMET_MAX +#define TIMESPEC_SEC_MIN TIMET_MIN + +COMPILER_WARNING_PUSH +#if __has_warning("-Wimplicit-int-float-conversion") +COMPILER_WARNING_IGNORED(-Wimplicit-int-float-conversion) +#elif defined(_MSC_VER) +/* C4305: 'initializing': truncation from '__int64' to 'const double' */ +COMPILER_WARNING_IGNORED(4305) +#endif +static const double TIMESPEC_SEC_MAX_as_double = TIMESPEC_SEC_MAX; +COMPILER_WARNING_POP + +static inline rb_hrtime_t * +double2hrtime(rb_hrtime_t *hrt, double d) +{ + /* assume timespec.tv_sec has same signedness as time_t */ + const double TIMESPEC_SEC_MAX_PLUS_ONE = 2.0 * (TIMESPEC_SEC_MAX_as_double / 2.0 + 1.0); + + if (TIMESPEC_SEC_MAX_PLUS_ONE <= d) { + *hrt = RB_HRTIME_MAX; + return NULL; + } + else if (d <= 0) { + *hrt = 0; + } + else { + *hrt = (rb_hrtime_t)(d * (double)RB_HRTIME_PER_SEC); + } + return hrt; +} + +static inline double +hrtime2double(rb_hrtime_t hrt) +{ + return (double)hrt / (double)RB_HRTIME_PER_SEC; +} + +#endif /* RB_HRTIME_H */ |
