summaryrefslogtreecommitdiff
path: root/hrtime.h
diff options
context:
space:
mode:
Diffstat (limited to 'hrtime.h')
-rw-r--r--hrtime.h237
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 */