blob: de9d5e90d22efb23b6f8bf91609d0bbbc46dec52 [file]
// 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_POOL_H_
#define IREE_TASK_POOL_H_
#include <stddef.h>
#include <stdint.h>
#include "iree/base/api.h"
#include "iree/task/list.h"
#include "iree/task/task.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// An allocation of tasks in a task pool containing multiple tasks.
// This struct is at the head of all task allocations made from the allocator.
// It is used to form a linked list of all allocations made so that they can be
// easily freed during pool teardown.
typedef struct iree_task_allocation_header_t {
// Next allocation in the linked list of allocations.
iree_atomic_slist_intrusive_ptr_t* next;
} iree_task_allocation_header_t;
// An atomic approximately LIFO singly-linked list.
IREE_TYPED_ATOMIC_SLIST_WRAPPER(iree_atomic_task_allocation,
iree_task_allocation_header_t,
offsetof(iree_task_allocation_header_t, next));
// Shared thread-safe pool of iree_task_t structures of a particular size.
// This can be used to quickly allocate blocks of tasks to be initialized by
// task producers, enqueued, and then eventually recycled back to the pool.
//
// The lifetime of all tasks must be less than the pool they were acquired
// from. Tasks acquired from one pool must not be released to another pool or
// via any other mechanism.
//
// Pools can either be fixed-size with a maximum number of available tasks that
// can be outstanding at any time or growable to allow the pool to be grown
// unbounded after initialization.
typedef struct iree_task_pool_t {
// Allocator used for allocating/freeing each allocation block.
iree_allocator_t allocator;
// Task size, in bytes.
iree_host_size_t task_size;
// NOTE: we don't track current usage count as that would introduce additional
// contention as tasks are acquired/released. If we end up finding a lot of
// memory idling here we can add a threshold over which we reclaim it, but the
// easiest (and most efficient) solution is to force the user to synchronize
// with the executor on a low memory event and use iree_task_pool_trim.
// Head of a linked list of all allocations made by the pool.
iree_atomic_task_allocation_slist_t allocations_slist;
// Linked list of free tasks used as a stack (LIFO).
// This is not a great structure for this as over time the tasks will get out
// of order and walking the linked list will incur cache misses. We offset
// that cost a bit by knowing that the time between walking the list to
// acquire tasks and when we initialize the tasks is short and that we would
// have triggered a cache miss anyway. In the future we can explore other
// approaches (such as small chunked linear lists) that better exploit spatial
// locality, if needed.
iree_atomic_task_slist_t available_slist;
} iree_task_pool_t;
// Initializes a task pool and optionally performs an initial task allocation.
iree_status_t iree_task_pool_initialize(iree_allocator_t allocator,
iree_host_size_t task_size,
iree_host_size_t initial_capacity,
iree_task_pool_t* out_pool);
// Deinitializes a task pool and releases all task allocations back to the
// allocator specified during initialization. All tasks must have already been
// released back to the pool.
void iree_task_pool_deinitialize(iree_task_pool_t* pool);
// Attempts to trim unused allocations from the task pool.
// Must not be called while any tasks that were acquired from this pool are
// still live; callers must synchronize with the executor and ensure they aren't
// pushing any more work during the trim operation.
void iree_task_pool_trim(iree_task_pool_t* pool);
// Acquires a task from the task pool. The returned task will have undefined
// contents and must be initialized by the caller.
iree_status_t iree_task_pool_acquire(iree_task_pool_t* pool,
iree_task_t** out_task);
// Acquires a set of tasks from the task pool. The returned tasks will have
// undefined contents besides their intrusive next pointers and must be
// intialized by the caller.
//
// WARNING: this may cause growth during races if multiple threads are trying to
// acquire at the same time. Our usage patterns here are such that this is never
// the case, though, as all acquisition from the internal executor pools happens
// with the coordination lock held.
iree_status_t iree_task_pool_acquire_many(iree_task_pool_t* pool,
iree_host_size_t count,
iree_task_list_t* out_list);
// Releases a task to the task pool.
// Callers must ensure the task is no longer in use.
void iree_task_pool_release(iree_task_pool_t* pool, iree_task_t* task);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // IREE_TASK_POOL_H_