Adding iree_wait_until and iree_wait_source_delay.
This allows for modeling sleeps such that they can be efficiently handled
via the system multi-wait APIs. Sleeps that do end up requiring a real
sleep are ignored on bare-metal or can be overridden with a user-provided
IREE_WAIT_UNTIL_FN.
diff --git a/docs/website/docs/deployment-configurations/bare-metal.md b/docs/website/docs/deployment-configurations/bare-metal.md
index 2277974..4c81c37 100644
--- a/docs/website/docs/deployment-configurations/bare-metal.md
+++ b/docs/website/docs/deployment-configurations/bare-metal.md
@@ -102,13 +102,16 @@
### Define IREE macros
* `-DIREE_PLATFORM_GENERIC`: Let IREE to build the runtime library without
-targeting a specific platform
+targeting a specific platform.
* `-DIREE_SYNCHRONIZATION_DISABLE_UNSAFE=1`: Disable thread synchronization
-support
-* `-DIREE_FILE_IO_ENABLE=0`: Disable file I/O
+support. Must only be used if there's a single thread.
+* `-DIREE_FILE_IO_ENABLE=0`: Disable file I/O.
* `-DIREE_TIME_NOW_FN`: A function to return the system time. For the bare-metal
system, it can be set as `-DIREE_TIME_NOW_FN=\"\{ return 0;\}\"` as there's no
-asynchronous wait handling
+asynchronous wait handling.
+* `-DIREE_WAIT_UNTIL_FN`: A function to wait until the given time in
+nanoseconds. Must match the signature `bool(uint64_t nanos)` and return
+false if the wait failed.
Examples of how to setup the CMakeLists.txt and .cmake file:
diff --git a/iree/base/internal/wait_handle.c b/iree/base/internal/wait_handle.c
index b756b0f..df17eb1 100644
--- a/iree/base/internal/wait_handle.c
+++ b/iree/base/internal/wait_handle.c
@@ -29,7 +29,7 @@
iree_status_t iree_wait_handle_ctl(iree_wait_source_t wait_source,
iree_wait_source_command_t command,
const void* params, void** inout_ptr) {
- iree_wait_handle_t* wait_handle = (iree_wait_handle_t*)wait_source.storage;
+ iree_wait_handle_t* wait_handle = iree_wait_handle_from_source(&wait_source);
switch (command) {
case IREE_WAIT_SOURCE_COMMAND_QUERY: {
iree_status_code_t* out_wait_status_code = (iree_status_code_t*)inout_ptr;
diff --git a/iree/base/internal/wait_handle.h b/iree/base/internal/wait_handle.h
index 23d78cd..8bac3fa 100644
--- a/iree/base/internal/wait_handle.h
+++ b/iree/base/internal/wait_handle.h
@@ -44,6 +44,13 @@
"iree_wait_handle_t must fit in 16-bytes so it can be stored in "
"other data structures");
+// Returns a wait handle that is immediately resolved.
+static inline iree_wait_handle_t iree_wait_handle_immediate(void) {
+ iree_wait_handle_t wait_handle;
+ memset(&wait_handle, 0, sizeof(wait_handle));
+ return wait_handle;
+}
+
// Returns true if the wait |handle| is resolved immediately (empty).
static inline bool iree_wait_handle_is_immediate(iree_wait_handle_t handle) {
return handle.type == IREE_WAIT_PRIMITIVE_TYPE_NONE;
@@ -70,6 +77,15 @@
iree_wait_source_command_t command,
const void* params, void** inout_ptr);
+// Returns a pointer to the wait handle in |wait_source| if it is using
+// iree_wait_handle_ctl and otherwise NULL.
+static inline iree_wait_handle_t* iree_wait_handle_from_source(
+ iree_wait_source_t* wait_source) {
+ return wait_source->ctl == iree_wait_handle_ctl
+ ? (iree_wait_handle_t*)wait_source->storage
+ : NULL;
+}
+
//===----------------------------------------------------------------------===//
// iree_wait_set_t
//===----------------------------------------------------------------------===//
diff --git a/iree/base/internal/wait_handle_poll.c b/iree/base/internal/wait_handle_poll.c
index 5dd51fc..8aa1e5f 100644
--- a/iree/base/internal/wait_handle_poll.c
+++ b/iree/base/internal/wait_handle_poll.c
@@ -37,12 +37,8 @@
*out_signaled_count = 0;
int rv = -1;
do {
- iree_duration_t timeout_ns =
- iree_absolute_deadline_to_timeout_ns(deadline_ns);
- int timeout_ms = timeout_ns != IREE_TIME_INFINITE_FUTURE
- ? (int)(timeout_ns / 1000000ull)
- : (int)timeout_ns;
- rv = poll(fds, nfds, timeout_ms);
+ uint32_t timeout_ms = iree_absolute_deadline_to_timeout_ms(deadline_ns);
+ rv = poll(fds, nfds, (int)timeout_ms);
} while (rv < 0 && errno == EINTR);
if (rv > 0) {
// One or more events set.
diff --git a/iree/base/internal/wait_handle_posix.c b/iree/base/internal/wait_handle_posix.c
index 9fec7c4..2ad9155 100644
--- a/iree/base/internal/wait_handle_posix.c
+++ b/iree/base/internal/wait_handle_posix.c
@@ -171,6 +171,8 @@
int rv = -1;
switch (handle->type) {
+ case IREE_WAIT_PRIMITIVE_TYPE_NONE:
+ return iree_ok_status(); // no-op
#if defined(IREE_HAVE_WAIT_TYPE_EVENTFD)
case IREE_WAIT_PRIMITIVE_TYPE_EVENT_FD: {
eventfd_t val = 0;
@@ -211,6 +213,8 @@
iree_status_t iree_wait_primitive_write(iree_wait_handle_t* handle) {
int rv = -1;
switch (handle->type) {
+ case IREE_WAIT_PRIMITIVE_TYPE_NONE:
+ return iree_ok_status(); // no-op
#if defined(IREE_HAVE_WAIT_TYPE_EVENTFD)
case IREE_WAIT_PRIMITIVE_TYPE_EVENT_FD: {
IREE_SYSCALL(rv, eventfd_write(handle->value.event.fd, 1ull));
@@ -242,6 +246,9 @@
}
iree_status_t iree_wait_primitive_clear(iree_wait_handle_t* handle) {
+ // No-op for null handles.
+ if (handle->type == IREE_WAIT_PRIMITIVE_TYPE_NONE) return iree_ok_status();
+
// Read in a loop until the read would block.
// Depending on how the user setup the fd the act of reading may reset the
// entire handle (such as with the default eventfd mode) or multiple reads may
diff --git a/iree/base/internal/wait_handle_test.cc b/iree/base/internal/wait_handle_test.cc
index 6fa67a7..637cf8a 100644
--- a/iree/base/internal/wait_handle_test.cc
+++ b/iree/base/internal/wait_handle_test.cc
@@ -94,6 +94,18 @@
iree_event_deinitialize(&event);
}
+// Tests an event that was wrapped from an immediate primitive.
+// These are used to neuter events in lists/sets and should be no-ops.
+TEST(Event, ImmediateEvent) {
+ iree_event_t event;
+ IREE_ASSERT_OK(iree_wait_handle_wrap_primitive(IREE_WAIT_PRIMITIVE_TYPE_NONE,
+ {0}, &event));
+ iree_event_set(&event);
+ IREE_EXPECT_OK(iree_wait_one(&event, IREE_TIME_INFINITE_PAST));
+ iree_event_reset(&event);
+ IREE_EXPECT_OK(iree_wait_one(&event, IREE_TIME_INFINITE_PAST));
+}
+
TEST(Event, SetWait) {
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event));
diff --git a/iree/base/internal/wait_handle_win32.c b/iree/base/internal/wait_handle_win32.c
index 7bad051..4550f2d 100644
--- a/iree/base/internal/wait_handle_win32.c
+++ b/iree/base/internal/wait_handle_win32.c
@@ -292,8 +292,7 @@
// Remap absolute timeout to relative timeout, handling special values as
// needed.
- DWORD timeout_ms =
- (DWORD)(iree_absolute_deadline_to_timeout_ns(deadline_ns) / 1000000ull);
+ DWORD timeout_ms = iree_absolute_deadline_to_timeout_ms(deadline_ns);
// Perform the wait; this is allowed to yield the calling thread even if the
// timeout_ms is 0 to indicate a poll.
@@ -374,8 +373,7 @@
// Remap absolute timeout to relative timeout, handling special values as
// needed.
- DWORD timeout_ms =
- (DWORD)(iree_absolute_deadline_to_timeout_ns(deadline_ns) / 1000000ull);
+ DWORD timeout_ms = iree_absolute_deadline_to_timeout_ms(deadline_ns);
// Perform the wait; this is allowed to yield the calling thread even if the
// timeout_ms is 0 to indicate a poll.
@@ -390,7 +388,7 @@
// here as we don't want to track all that in non-exceptional cases.
status = iree_status_from_code(IREE_STATUS_DEADLINE_EXCEEDED);
} else if (result == WAIT_OBJECT_0) {
- // Handle was signaled sucessfully.
+ // Handle was signaled successfully.
status = iree_ok_status();
} else if (result == WAIT_ABANDONED_0) {
// The mutex handle was abandonded during the wait.
@@ -443,11 +441,13 @@
}
void iree_event_set(iree_event_t* event) {
- SetEvent((HANDLE)event->value.win32.handle);
+ HANDLE handle = (HANDLE)event->value.win32.handle;
+ if (handle) SetEvent(handle);
}
void iree_event_reset(iree_event_t* event) {
- ResetEvent((HANDLE)event->value.win32.handle);
+ HANDLE handle = (HANDLE)event->value.win32.handle;
+ if (handle) ResetEvent(handle);
}
#endif // IREE_WAIT_API == IREE_WAIT_API_WIN32
diff --git a/iree/base/time.c b/iree/base/time.c
index 89889f2..54a5620 100644
--- a/iree/base/time.c
+++ b/iree/base/time.c
@@ -12,6 +12,7 @@
#include <time.h>
#include "iree/base/target_platform.h"
+#include "iree/base/tracing.h"
IREE_API_EXPORT iree_time_t iree_time_now(void) {
#if defined(IREE_TIME_NOW_FN)
@@ -21,20 +22,19 @@
// (such as using std::chrono) if older support is needed.
FILETIME system_time;
GetSystemTimePreciseAsFileTime(&system_time);
-
const int64_t kUnixEpochStartTicks = 116444736000000000i64;
- const int64_t kFtToMicroSec = 10;
+ const int64_t kFtToNanoSec = 100;
LARGE_INTEGER li;
li.LowPart = system_time.dwLowDateTime;
li.HighPart = system_time.dwHighDateTime;
li.QuadPart -= kUnixEpochStartTicks;
- li.QuadPart /= kFtToMicroSec;
+ li.QuadPart *= kFtToNanoSec;
return li.QuadPart;
#elif defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_APPLE) || \
defined(IREE_PLATFORM_LINUX) || defined(IREE_PLATFORM_EMSCRIPTEN)
struct timespec clock_time;
clock_gettime(CLOCK_REALTIME, &clock_time);
- return clock_time.tv_nsec;
+ return clock_time.tv_sec * 1000000000ull + clock_time.tv_nsec;
#else
#error "IREE system clock needs to be set up for your platform"
#endif // IREE_PLATFORM_*
@@ -57,11 +57,126 @@
} else if (deadline_ns == IREE_TIME_INFINITE_FUTURE) {
return IREE_DURATION_INFINITE;
} else {
+ iree_time_t now_ns = iree_time_now();
+ return deadline_ns < now_ns ? IREE_DURATION_ZERO : deadline_ns - now_ns;
+ }
+}
+
+IREE_API_EXPORT uint32_t
+iree_absolute_deadline_to_timeout_ms(iree_time_t deadline_ns) {
+ if (deadline_ns == IREE_TIME_INFINITE_PAST) {
+ return IREE_DURATION_ZERO;
+ } else if (deadline_ns == IREE_TIME_INFINITE_FUTURE) {
+ return UINT32_MAX;
+ } else {
// We have either already passed the deadline (and can turn this into a
// poll) or want to do nanos->millis. We round up so that a deadline of 1ns
// results in 1ms as it should still wait, vs. if it was actually 0ns
// indicating the user intended a poll.
iree_time_t now_ns = iree_time_now();
- return deadline_ns < now_ns ? IREE_DURATION_ZERO : deadline_ns - now_ns;
+ return deadline_ns < now_ns
+ ? IREE_DURATION_ZERO
+ : (deadline_ns - now_ns + 1000000 - 1) / 1000000ull;
}
}
+
+#if defined(IREE_WAIT_UNTIL_FN)
+
+// Define IREE_WAIT_UNTIL_FN to call out to a user-configured function.
+static bool iree_wait_until_impl(iree_time_t deadline_ns) {
+ return IREE_WAIT_UNTIL_FN(deadline_ns);
+}
+
+#elif defined(IREE_PLATFORM_WINDOWS)
+
+// No good sleep APIs on Windows; we need to accumulate low-precision relative
+// waits to reach the absolute time. Lots of slop here, but we primarily use
+// nanoseconds as a uniform time API and don't guarantee that precision. Note
+// that we try to round up to ensure we wait until at least the requested time.
+static bool iree_wait_until_impl(iree_time_t deadline_ns) {
+ iree_time_t now_ns = iree_time_now();
+ while (now_ns < deadline_ns) {
+ iree_time_t delta_ns = deadline_ns - now_ns;
+ uint32_t delta_ms = (uint32_t)((delta_ns + 1000000 - 1) / 1000000ull);
+ if (delta_ms == 0) {
+ // Sleep(0) doesn't actually sleep and instead acts as a yield; instead of
+ // potentially spilling in a tight loop when we get down near the end of
+ // the wait we bail a bit early. We don't guarantee the precision of the
+ // waits so this is fine.
+ break;
+ }
+ Sleep(delta_ms);
+ now_ns = iree_time_now();
+ }
+ return true;
+}
+
+#elif (_POSIX_C_SOURCE >= 200112L) && defined(TIMER_ABSTIME)
+
+// This is widely available on *nix-like systems (linux/bsd/etc) and in
+// most libc implementations (glibc/musl/etc). It's the best as we get to
+// tell the system the exact time we want to sleep until.
+//
+// https://man7.org/linux/man-pages/man2/clock_nanosleep.2.html
+//
+// NOTE: we could save a syscall in many cases if we returned the time upon wake
+// from the API.
+static bool iree_wait_until_impl(iree_time_t deadline_ns) {
+ struct timespec ts = {
+ .tv_sec = 0,
+ .tv_nsec = deadline_ns,
+ };
+ int ret = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, NULL);
+ return ret == 0;
+}
+
+#elif (_POSIX_C_SOURCE >= 199309L) || defined(IREE_PLATFORM_APPLE)
+
+// Apple doesn't have clock_nanosleep. We could use the Mach APIs on darwin to
+// do this but they require initialization and potential updates during
+// execution as clock frequencies change. Instead we use the relative nanosleep
+// and accumulate until the deadline, which is a good fallback for some other
+// platforms as well.
+//
+// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/nanosleep.2.html
+static bool iree_wait_until_impl(iree_time_t deadline_ns) {
+ iree_time_t now_ns = iree_time_now();
+ while (now_ns < deadline_ns) {
+ iree_time_t delta_ns = deadline_ns - now_ns;
+ struct timespec ts = {
+ .tv_sec = 0,
+ .tv_nsec = delta_ns,
+ };
+ int ret = nanosleep(&ts, NULL);
+ if (ret != 0) return false;
+ now_ns = iree_time_now();
+ }
+ return true;
+}
+
+#else
+
+// No waiting available; just pretend like we did. This will cause programs
+// using timers to run as fast as possible but without having a way to delay
+// time there's not much else they could do.
+static bool iree_wait_until_impl(iree_time_t deadline_ns) { return true; }
+
+#endif // (platforms)
+
+bool iree_wait_until(iree_time_t deadline_ns) {
+ // Can't wait forever - or for the past.
+ if (deadline_ns == IREE_TIME_INFINITE_FUTURE) return false;
+ if (deadline_ns == IREE_TIME_INFINITE_PAST) return true;
+
+ IREE_TRACE_ZONE_BEGIN(z0);
+ IREE_TRACE_ZONE_APPEND_VALUE(
+ z0, (uint64_t)iree_absolute_deadline_to_timeout_ns(deadline_ns));
+
+ // NOTE: we want to use sleep APIs with absolute times as that makes retrying
+ // on spurious wakes easier; if we using relative timeouts we need to ensure
+ // we don't drift.
+ bool did_wait = iree_wait_until_impl(deadline_ns);
+
+ IREE_TRACE_ZONE_END(z0);
+ return did_wait;
+}
diff --git a/iree/base/time.h b/iree/base/time.h
index 63eefce..bebceb1 100644
--- a/iree/base/time.h
+++ b/iree/base/time.h
@@ -58,12 +58,18 @@
IREE_API_EXPORT iree_time_t
iree_relative_timeout_to_deadline_ns(iree_duration_t timeout_ns);
-// Converts an absolute deadline time to a relative timeout duration.
+// Converts an absolute deadline time to a relative timeout duration in nanos.
// This handles the special cases of IREE_TIME_INFINITE_PAST and
// IREE_TIME_INFINITE_FUTURE to avoid extraneous time queries.
IREE_API_EXPORT iree_duration_t
iree_absolute_deadline_to_timeout_ns(iree_time_t deadline_ns);
+// Converts an absolute deadline time to a relative timeout duration in millis.
+// This handles the special cases of IREE_TIME_INFINITE_PAST and
+// IREE_TIME_INFINITE_FUTURE to avoid extraneous time queries.
+IREE_API_EXPORT uint32_t
+iree_absolute_deadline_to_timeout_ms(iree_time_t deadline_ns);
+
typedef enum iree_timeout_type_e {
// Timeout is defined by an absolute value `deadline_ns`.
IREE_TIMEOUT_ABSOLUTE = 0,
@@ -165,6 +171,12 @@
return iree_make_deadline(lhs.nanos < rhs.nanos ? lhs.nanos : rhs.nanos);
}
+// Waits until |deadline_ns| (or longer), putting the calling thread to sleep.
+// The precision of this varies across platforms and may have a minimum
+// granularity anywhere between microsecond to milliseconds.
+// Returns true if the sleep completed successfully and false if it was aborted.
+bool iree_wait_until(iree_time_t deadline_ns);
+
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
diff --git a/iree/base/wait_source.c b/iree/base/wait_source.c
index f4ed575..b626a69 100644
--- a/iree/base/wait_source.c
+++ b/iree/base/wait_source.c
@@ -72,3 +72,46 @@
IREE_TRACE_ZONE_END(z0);
return status;
}
+
+//===----------------------------------------------------------------------===//
+// iree_wait_source_delay
+//===----------------------------------------------------------------------===//
+
+IREE_API_EXPORT iree_status_t iree_wait_source_delay_ctl(
+ iree_wait_source_t wait_source, iree_wait_source_command_t command,
+ const void* params, void** inout_ptr) {
+ iree_time_t delay_deadline_ns = (iree_time_t)wait_source.data;
+ switch (command) {
+ case IREE_WAIT_SOURCE_COMMAND_QUERY: {
+ iree_status_code_t* out_wait_status_code = (iree_status_code_t*)inout_ptr;
+ *out_wait_status_code = iree_time_now() >= delay_deadline_ns
+ ? IREE_STATUS_OK
+ : IREE_STATUS_DEFERRED;
+ return iree_ok_status();
+ }
+ case IREE_WAIT_SOURCE_COMMAND_WAIT_ONE: {
+ iree_time_t timeout_deadline_ns = iree_timeout_as_deadline_ns(
+ ((const iree_wait_source_wait_params_t*)params)->timeout);
+ if (timeout_deadline_ns > delay_deadline_ns) {
+ // Delay is before timeout and we can perform a simple sleep.
+ return iree_wait_until(delay_deadline_ns)
+ ? iree_ok_status()
+ : iree_status_from_code(IREE_STATUS_DEFERRED);
+ } else {
+ // Timeout is before deadline, just wait for the deadline. We _may_
+ // wake after the delay deadline but can't be sure.
+ iree_wait_until(timeout_deadline_ns);
+ return iree_time_now() >= delay_deadline_ns
+ ? iree_ok_status()
+ : iree_status_from_code(IREE_STATUS_DEADLINE_EXCEEDED);
+ }
+ return iree_status_from_code(IREE_STATUS_DEFERRED);
+ }
+ case IREE_WAIT_SOURCE_COMMAND_EXPORT:
+ return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
+ "delay wait sources cannot be exported");
+ default:
+ return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
+ "unhandled wait source command");
+ }
+}
diff --git a/iree/base/wait_source.h b/iree/base/wait_source.h
index 97cc24b..56bba51 100644
--- a/iree/base/wait_source.h
+++ b/iree/base/wait_source.h
@@ -146,6 +146,13 @@
return primitive;
}
+// Returns a wait primitive that will resolve immediately if waited on.
+static inline iree_wait_primitive_t iree_wait_primitive_immediate(void) {
+ iree_wait_primitive_value_t dummy_primitive = {0};
+ return iree_make_wait_primitive(IREE_WAIT_PRIMITIVE_TYPE_NONE,
+ dummy_primitive);
+}
+
// Returns true if the |wait_primitive| is resolved immediately (empty).
static inline bool iree_wait_primitive_is_immediate(
iree_wait_primitive_t wait_primitive) {
@@ -237,6 +244,36 @@
return v;
}
+// Returns true if the |wait_source| is immediately resolved.
+// This can be used to neuter waits in lists/sets.
+static inline bool iree_wait_source_is_immediate(
+ iree_wait_source_t wait_source) {
+ return wait_source.ctl == NULL;
+}
+
+// Wait source control function for iree_wait_source_delay.
+IREE_API_EXPORT iree_status_t iree_wait_source_delay_ctl(
+ iree_wait_source_t wait_source, iree_wait_source_command_t command,
+ const void* params, void** inout_ptr);
+
+// Returns a wait source that indicates a delay until a point in time.
+// The source will remain unresolved until the |deadline_ns| is reached or
+// exceeded and afterward return resolved. Export is unavailable.
+static inline iree_wait_source_t iree_wait_source_delay(
+ iree_time_t deadline_ns) {
+ iree_wait_source_t v = {
+ {{NULL, (uint64_t)deadline_ns}},
+ iree_wait_source_delay_ctl,
+ };
+ return v;
+}
+
+// Returns true if the |wait_source| is a timed delay.
+// These are sleeps that can often be handled more intelligently by platforms.
+static inline bool iree_wait_source_is_delay(iree_wait_source_t wait_source) {
+ return wait_source.ctl == iree_wait_source_delay_ctl;
+}
+
// Imports a system |wait_primitive| into a wait source in |out_wait_source|.
// Ownership of the wait handle remains will the caller and it must remain valid
// for the duration the wait source is in use.