// Copyright 2020 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_TASK_LIST_H_
#define IREE_TASK_LIST_H_

#include <stdbool.h>
#include <stddef.h>

#include "iree/base/api.h"
#include "iree/base/internal/atomic_slist.h"
#include "iree/task/task.h"

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

// iree_atomic_task_slist_t, an atomic approximately LIFO singly-linked list.
// iree_task_list_t should be preferred when working with
// uncontended/thread-local lists as it has no overhead, while the
// iree_atomic_task_slist_t should be used when multiple threads may need to
// share lists of tasks (free lists, mailboxes, etc).
IREE_TYPED_ATOMIC_SLIST_WRAPPER(iree_atomic_task, iree_task_t,
                                offsetof(iree_task_t, next_task));

// Flushes the atomic slist and discards all tasks, resetting intrusive
// pointers and running task-specific cleanup. Use this for failure paths
// where tasks must be properly torn down (not just abandoned).
void iree_atomic_task_slist_flush_and_discard(iree_atomic_task_slist_t* slist);

// A singly-linked list of tasks using the embedded task next_task pointer.
//
// Thread-compatible; designed to be used from a single thread manipulating a
// list for passing to an API that accepts lists.
typedef struct iree_task_list_t {
  iree_task_t* head;
  iree_task_t* tail;
} iree_task_list_t;

// Initializes an empty task list.
void iree_task_list_initialize(iree_task_list_t* out_list);

// Moves |list| into |out_list|, leaving |list| empty.
void iree_task_list_move(iree_task_list_t* list, iree_task_list_t* out_list);

// Discards a task list; should be used for failure cleanup during list
// construction to ensure intrusive pointers are reset. List is immediately
// reusable as if it had been initialized.
void iree_task_list_discard(iree_task_list_t* list);

// Returns true if the list is empty.
bool iree_task_list_is_empty(const iree_task_list_t* list);

// Counts the total number of tasks in the list.
// WARNING: this requires an O(n) walk of the entire list; use this only for
// debugging or when the list is known to be small and hot in cache.
iree_host_size_t iree_task_list_calculate_size(const iree_task_list_t* list);

// Returns the first task in the list, if any.
iree_task_t* iree_task_list_front(iree_task_list_t* list);

// Returns the last task in the list, if any.
iree_task_t* iree_task_list_back(iree_task_list_t* list);

// Pushes a task onto the back of the task list. The task list takes ownership
// of |task|.
void iree_task_list_push_back(iree_task_list_t* list, iree_task_t* task);

// Pushes a task onto the front of the task list. The task list takes ownership
// of |task|.
void iree_task_list_push_front(iree_task_list_t* list, iree_task_t* task);

// Pops a task from the front of the task list or returns NULL if the list is
// empty. Caller takes ownership of the returned task.
iree_task_t* iree_task_list_pop_front(iree_task_list_t* list);

// Erases |task| from the list.
// |prev_task| must point to the task immediately prior to |task| in the list
// or NULL if the task was at the head.
void iree_task_list_erase(iree_task_list_t* list, iree_task_t* prev_task,
                          iree_task_t* task);

// Prepends |prefix| onto the beginning of |list|. |prefix| will be reset.
void iree_task_list_prepend(iree_task_list_t* list, iree_task_list_t* prefix);

// Appends |suffix| onto the end of |list|. |suffix| will be reset.
void iree_task_list_append(iree_task_list_t* list, iree_task_list_t* suffix);

// Flushes the given |slist| and appends all tasks to the list in FIFO order.
void iree_task_list_append_from_fifo_slist(iree_task_list_t* list,
                                           iree_atomic_task_slist_t* slist);

// Reverses the list in-place.
// Requires a full O(n) traversal.
void iree_task_list_reverse(iree_task_list_t* list);

// Splits |head_list| in half (up to |max_tasks|) and retains the first half
// in |head_list| and the second half in |tail_list|.
void iree_task_list_split(iree_task_list_t* head_list,
                          iree_host_size_t max_tasks,
                          iree_task_list_t* out_tail_list);

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

#endif  // IREE_TASK_LIST_H_
