// Copyright 2022 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

#ifndef IREE_BASE_LOOP_H_
#define IREE_BASE_LOOP_H_

#include <inttypes.h>

#include "iree/base/allocator.h"
#include "iree/base/attributes.h"
#include "iree/base/status.h"
#include "iree/base/time.h"
#include "iree/base/wait_source.h"

#ifdef __cplusplus
extern "C" {
#endif  // __cplusplus

//===----------------------------------------------------------------------===//
// iree_loop_t public API
//===----------------------------------------------------------------------===//

typedef struct iree_loop_t iree_loop_t;
typedef uint32_t iree_loop_command_t;

// TODO(benvanik): define prioritization. This is useful for ensuring fast
// coroutine switching by avoiding the current coroutine being set to the back
// of the loop. It's easy to shoot yourself in the foot, though: cooperative
// scheduling can be tricky.
typedef enum iree_loop_priority_e {
  IREE_LOOP_PRIORITY_DEFAULT = 0u,
} iree_loop_priority_t;

// Callback to execute user code used by the loop.
// |user_data| contains the value provided to the callback when enqueuing the
// operation and must remain live until the callback is made.
//
// If the callback is to be executed as normal |status| will be OK.
// A non-fatal error case of IREE_STATUS_DEADLINE_EXCEEDED can occur if the
// operation had a deadline specified and it elapsed prior to the condition
// being met.
//
// |status| otherwise indicates that the operation failed (such as a failed wait
// or a failed workgroup callback).
//
// Callbacks may reentrantly queue work on the |loop| _unless_ the passed
// |status| is IREE_STATUS_ABORTED indicating that the loop is shutting down or
// the operation is being aborted because of a prior failure.
//
// Any non-OK result will be routed to a loop-global error handler (depending on
// implementation) or otherwise ignored; users must set their own exit bits.
typedef iree_status_t(IREE_API_PTR* iree_loop_callback_fn_t)(
    void* user_data, iree_loop_t loop, iree_status_t status);

// Callback to execute a single workgroup in a grid dispatch.
// Each call receives the XYZ location in the grid and may run concurrently with
// any other workgroup call.
//
// Any non-OK result will be routed to the completion callback of the dispatch
// operation but not otherwise trigger loop failure. Other workgroups may
// continue to run up until the completion callback is issued.
typedef iree_status_t(IREE_API_PTR* iree_loop_workgroup_fn_t)(
    void* user_data, iree_loop_t loop, uint32_t workgroup_x,
    uint32_t workgroup_y, uint32_t workgroup_z);

// Function pointer for an iree_loop_t control function.
// |command| provides the operation to perform. Commands may use |params| to
// pass additional operation-specific parameters. |inout_ptr| usage is defined
// by each operation.
typedef iree_status_t(IREE_API_PTR* iree_loop_ctl_fn_t)(
    void* self, iree_loop_command_t command, const void* params,
    void** inout_ptr);

// An event system for executing queued asynchronous work.
// Implementations are allowed to execute operations in any order but generally
// runs FIFO and will only ever execute one operation at a time. The thread used
// for execution may change from operation to operation. Usage that has order
// requirements is required to perform the ordering themselves.
//
// This is a form of cooperative scheduling and the loop _may_ not make forward
// progress if a callback issues a blocking operation. All blocking operations
// should either be done on user-controlled threads or via the loop primitives
// such as iree_loop_wait_one. Callbacks may enqueue zero or more operations
// with 2+ performing a conceptual fork. The iree_loop_dispatch operation allows
// for a constrained style of concurrency matching a GPU grid dispatch and can
// be used as a primitive to implement other kinds of parallel loops.
//
// User data passed to callbacks is unowned and must be kept live by the
// requester. All callbacks are guaranteed to be issued even on failure and
// allocations made when enqueuing operations are safe to free in the callbacks.
//
// The rough behavior of the loop matches that of the web event loop
// dispatching events/promises/timeouts/etc. It's a stackless design where the
// owner of the primary control loop is hidden from the users of the loop. This
// allows implementations to integrate into existing scheduling mechanisms
// (ALooper, libuv, io_uring, the browser main event loop, etc) in a generic
// way. The design of the API here is meant to make it easy to put the
// implementation in external code (python/javascript/rust/java/etc) as only a
// single method with a fixed interface is used to cross the boundaries.
//
// Note that by default this implementation is only intended for host-level
// synchronization and scheduling: fairly coarse events performed fairly
// infrequently. Optimized multi-threaded workloads are intended to execute on
// the iree/task/ system via command buffers.
typedef struct iree_loop_t {
  // Control function data.
  void* self;
  // ioctl-style control function servicing all loop-related commands.
  // See iree_loop_command_t for more information.
  iree_loop_ctl_fn_t ctl;
} iree_loop_t;

// A loop that can do no work. Attempts to enqueue work will fail.
static inline iree_loop_t iree_loop_null() {
  iree_loop_t loop = {NULL, NULL};
  return loop;
}

// Executes |callback| from the loop at some point in the future.
//
// The callback is guaranteed to be issued but in an undefined order.
// |user_data| is not retained and must be live until the callback is issued.
IREE_API_EXPORT iree_status_t iree_loop_call(iree_loop_t loop,
                                             iree_loop_priority_t priority,
                                             iree_loop_callback_fn_t callback,
                                             void* user_data);

// Executes |workgroup_callback| from the loop at some point in the future
// with grid dispatch of |workgroup_count_xyz| workgroups. Each
// |workgroup_callback| will receive its XYZ location in the grid and
// |completion_callback| will be issued upon completion (or failure).
// The dispatched workgroups are not guaranteed to run concurrently and must
// not perform blocking operations.
//
// The completion callback is guaranteed to be issued but in an undefined order.
// The workgroup callback runs serially or concurrently from multiple threads.
// |user_data| is not retained and must be live until the callback is issued.
IREE_API_EXPORT iree_status_t iree_loop_dispatch(
    iree_loop_t loop, const uint32_t workgroup_count_xyz[3],
    iree_loop_workgroup_fn_t workgroup_callback,
    iree_loop_callback_fn_t completion_callback, void* user_data);

// Waits until |timeout| is reached and then issues |callback|.
// There may be a significant latency between |timeout| and when the |callback|
// is executed.
//
// The callback is guaranteed to be issued.
// |user_data| is not retained and must be live until the callback is issued.
IREE_API_EXPORT iree_status_t
iree_loop_wait_until(iree_loop_t loop, iree_timeout_t timeout,
                     iree_loop_callback_fn_t callback, void* user_data);

// Waits until the |wait_source| is satisfied or |timeout| is reached and then
// issues |callback|.
//
// The callback is guaranteed to be issued.
// |user_data| is not retained and must be live until the callback is issued.
IREE_API_EXPORT iree_status_t iree_loop_wait_one(
    iree_loop_t loop, iree_wait_source_t wait_source, iree_timeout_t timeout,
    iree_loop_callback_fn_t callback, void* user_data);

// Waits until one or more of the |wait_sources| is satisfied or |timeout| is
// reached and then issues |callback|.
//
// The callback is guaranteed to be issued.
// |wait_sources| and |user_data| is not retained and must be live until the
// callback is issued.
IREE_API_EXPORT iree_status_t iree_loop_wait_any(
    iree_loop_t loop, iree_host_size_t count, iree_wait_source_t* wait_sources,
    iree_timeout_t timeout, iree_loop_callback_fn_t callback, void* user_data);

// Waits until all of the |wait_sources| is satisfied or |timeout| is reached
// and then issues |callback|.
//
// The callback is guaranteed to be issued.
// |wait_sources| and |user_data| is not retained and must be live until the
// callback is issued.
IREE_API_EXPORT iree_status_t iree_loop_wait_all(
    iree_loop_t loop, iree_host_size_t count, iree_wait_source_t* wait_sources,
    iree_timeout_t timeout, iree_loop_callback_fn_t callback, void* user_data);

// Blocks the caller and waits until the loop is idle or |timeout| is reached.
//
// Not all implementations support this and may return
// IREE_STATUS_DEADLINE_EXCEEDED immediately when work is still pending.
// |user_data| is not retained and must be live until the callback is issued.
IREE_API_EXPORT iree_status_t iree_loop_drain(iree_loop_t loop,
                                              iree_timeout_t timeout);

//===----------------------------------------------------------------------===//
// iree_loop_t implementation details
//===----------------------------------------------------------------------===//
// These are exposed so that user applications can implement their own loops and
// are otherwise private to the API.

// Controls the behavior of an iree_loop_ctl_fn_t callback function.
enum iree_loop_command_e {
  // Issues the callback from the loop at some point in the future.
  // The callback will always be called (including when aborted).
  //
  // iree_loop_ctl_fn_t:
  //   params: iree_loop_call_params_t
  //   inout_ptr: unused
  IREE_LOOP_COMMAND_CALL = 0u,

  // Issues a workgroup callback across a grid and then issues the callback.
  // The completion callback will always be called (including when aborted).
  //
  // iree_loop_ctl_fn_t:
  //   params: iree_loop_dispatch_params_t
  //   inout_ptr: unused
  IREE_LOOP_COMMAND_DISPATCH,

  // TODO(benvanik): open/read/write/close/etc with iovecs.
  // Our iree_byte_span_t matches with `struct iovec` and if we share that we
  // can do scatter/gather I/O with io_uring.
  // Want something with an fd, flags, count, and iree_byte_span_t's.

  // TODO(benvanik): IREE_LOOP_COMMAND_WAIT_IDLE to get idle callbacks.

  // Sleeps until the timeout is reached then issues the callback.
  // The callback will always be called (including when aborted).
  //
  // iree_loop_ctl_fn_t:
  //   params: iree_loop_wait_until_params_t
  //   inout_ptr: unused
  IREE_LOOP_COMMAND_WAIT_UNTIL,

  // Waits until the wait source has resolved then issues the callback.
  // The callback will always be called (including when aborted).
  //
  // iree_loop_ctl_fn_t:
  //   params: iree_loop_wait_one_params_t
  //   inout_ptr: unused
  IREE_LOOP_COMMAND_WAIT_ONE,

  // Waits until one or more wait sources have resolved then issues the
  // callback. The callback will always be called (including when aborted).
  //
  // iree_loop_ctl_fn_t:
  //   params: iree_loop_wait_multi_params_t
  //   inout_ptr: unused
  IREE_LOOP_COMMAND_WAIT_ANY,

  // Waits until all of the wait sources have resolved then issues the
  // callback. The callback will always be called (including when aborted).
  //
  // iree_loop_ctl_fn_t:
  //   params: iree_loop_wait_multi_params_t
  //   inout_ptr: unused
  IREE_LOOP_COMMAND_WAIT_ALL,

  // Waits until the loop has no more pending work.
  // Resolves early with IREE_STATUS_DEADLINE_EXCEEDED if the timeout is reached
  // before the loop is idle or if the platform does not support the operation.
  //
  // iree_loop_ctl_fn_t:
  //   params: iree_loop_drain_params_t
  //   inout_ptr: unused
  IREE_LOOP_COMMAND_DRAIN,

  IREE_LOOP_COMMAND_MAX = IREE_LOOP_COMMAND_DRAIN,
};

typedef struct iree_loop_callback_t {
  // Callback function pointer.
  iree_loop_callback_fn_t fn;
  // User data passed to the callback function. Unowned.
  void* user_data;
} iree_loop_callback_t;

// Parameters for IREE_LOOP_COMMAND_CALL.
typedef struct iree_loop_call_params_t {
  // Callback issued to perform the call.
  iree_loop_callback_t callback;
  // Controls the scheduling of the call.
  iree_loop_priority_t priority;
} iree_loop_call_params_t;

// Parameters for IREE_LOOP_COMMAND_DISPATCH.
typedef struct iree_loop_dispatch_params_t {
  // Callback issued when the call completes (successfully or otherwise).
  iree_loop_callback_t callback;
  // Callback issued for each workgroup.
  iree_loop_workgroup_fn_t workgroup_fn;
  // 3D workgroup count.
  uint32_t workgroup_count_xyz[3];
} iree_loop_dispatch_params_t;

// Parameters for IREE_LOOP_COMMAND_WAIT_UTIL.
typedef struct iree_loop_wait_until_params_t {
  // Callback issued after the deadline has passed.
  iree_loop_callback_t callback;
  // Minimum time to wait before issuing the callback.
  iree_time_t deadline_ns;
} iree_loop_wait_until_params_t;

// Parameters for IREE_LOOP_COMMAND_WAIT_ONE.
typedef struct iree_loop_wait_one_params_t {
  // Callback issued after the wait condition is satisfied.
  iree_loop_callback_t callback;
  // Maximum time to wait before failing the wait with
  // IREE_STATUS_DEADLINE_EXCEEDED.
  iree_time_t deadline_ns;
  // Wait source to wait on.
  iree_wait_source_t wait_source;
} iree_loop_wait_one_params_t;

// Parameters for IREE_LOOP_COMMAND_WAIT_ANY / IREE_LOOP_COMMAND_WAIT_ALL.
typedef struct iree_loop_wait_multi_params_t {
  // Callback issued after any/all wait conditions are satisfied.
  iree_loop_callback_t callback;
  // Maximum time to wait before failing the wait with
  // IREE_STATUS_DEADLINE_EXCEEDED.
  iree_time_t deadline_ns;
  // Total number of wait sources.
  iree_host_size_t count;
  // List of wait source to wait on.
  // Ownership remains with the issuer and must remain live until the callback.
  iree_wait_source_t* wait_sources;
} iree_loop_wait_multi_params_t;

// Parameters for IREE_LOOP_COMMAND_DRAIN.
typedef struct iree_loop_drain_params_t {
  // Time when the wait will abort.
  iree_time_t deadline_ns;
} iree_loop_drain_params_t;

#ifdef __cplusplus
}  // extern "C"
#endif  // __cplusplus

#endif  // IREE_BASE_LOOP_H_
