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.