// Copyright 2019 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "iree/base/time.h"

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "iree/base/internal/time.h"
#include "iree/base/target_platform.h"
#include "iree/base/tracing.h"

IREE_API_EXPORT iree_time_t iree_time_now(void) {
  return iree_platform_time_now();
}

IREE_API_EXPORT iree_time_t
iree_relative_timeout_to_deadline_ns(iree_duration_t timeout_ns) {
  if (timeout_ns == IREE_DURATION_ZERO) {
    return IREE_TIME_INFINITE_PAST;
  } else if (timeout_ns == IREE_DURATION_INFINITE) {
    return IREE_TIME_INFINITE_FUTURE;
  }
  return iree_time_now() + timeout_ns;
}

IREE_API_EXPORT iree_duration_t
iree_absolute_deadline_to_timeout_ns(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 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 + 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 = (time_t)(deadline_ns / 1000000000ull),
      .tv_nsec = (long)(deadline_ns % 1000000000ull),
  };
  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 abs_ts = {
        .tv_sec = (time_t)(delta_ns / 1000000000ull),
        .tv_nsec = (long)(delta_ns % 1000000000ull),
    };
    int ret = nanosleep(&abs_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_I64(
      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;
}
