blob: 2025829b0932985cc856c6cef99bb319c7cdb01e [file]
// 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_HAL_UTILS_SEMAPHORE_BASE_H_
#define IREE_HAL_UTILS_SEMAPHORE_BASE_H_
#include <stdbool.h>
#include <stdint.h>
#include "iree/base/api.h"
#include "iree/base/internal/synchronization.h"
#include "iree/hal/api.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Callback handler for semaphore timepoints.
// Handlers receive the semaphore, the current value, and the status code.
//
// The |value| is only valid if |status_code| is IREE_STATUS_OK.
// In error cases handlers can query the status of the |semaphore| to receive
// the full status if desired.
//
// Handlers run outside the semaphore lock but under the timepoint list lock
// and may re-entrantly use the semaphore to query but not manage timepoints.
typedef iree_status_t(IREE_API_PTR* iree_hal_semaphore_callback_fn_t)(
void* user_data, iree_hal_semaphore_t* semaphore, uint64_t value,
iree_status_code_t status_code);
typedef struct iree_hal_semaphore_callback_t {
// Callback function pointer.
iree_hal_semaphore_callback_fn_t fn;
// User data passed to the callback function. Unowned.
void* user_data;
} iree_hal_semaphore_callback_t;
// Storage for a semaphore timepoint.
// Each semaphore manages a list of active timepoints and issues their specified
// callback when the semaphore is signaled to or beyond a given value.
typedef struct iree_hal_semaphore_timepoint_t {
// Intrusive doubly-linked list next entry pointer.
// Guarded by the semaphore mutex.
struct iree_hal_semaphore_timepoint_t* next;
// Intrusive doubly-linked list previous entry pointer.
// Guarded by the semaphore mutex.
struct iree_hal_semaphore_timepoint_t* prev;
// Retained semaphore; this ensures the semaphore remains valid for the
// lifetime of the timepoint. The semaphore must be released by the underlying
// implementation or by the user with iree_hal_semaphore_release_timepoint.
struct iree_hal_semaphore_t* semaphore;
// Target value the semaphore must reach or exceed to trigger the timepoint.
uint64_t minimum_value;
// Absolute deadline after which the timepoint will expire if the semaphore
// has not reached the target value.
iree_time_t deadline_ns;
// Callback to issue when the timepoint is reached, the deadline is exceeded,
// or the semaphore fails.
iree_hal_semaphore_callback_t callback;
} iree_hal_semaphore_timepoint_t;
// A doubly-linked FIFO list of timepoints.
// The order of the timepoints does *not* match increasing payload values but
// instead the order they were added to the list.
//
// Note that the timepoints are not owned by the list - this just nicely
// stitches together timepoints for easier management.
typedef struct iree_hal_semaphore_timepoint_list_t {
iree_hal_semaphore_timepoint_t* head;
iree_hal_semaphore_timepoint_t* tail;
} iree_hal_semaphore_timepoint_list_t;
// Abstract base implementation of semaphores that perform timepoint tracking.
//
// Device implementations can acquire timepoints that provide low-latency
// directed notification of when a semaphore timeline reaches a certain point
// (or fails). The storage for the timepoints is managed by the requester and
// can be allocation-free making the timepoint operations safe to perform from
// driver threads/callbacks.
//
// Semaphore implementations need to notify the tracking semaphore of signal and
// failure events using the iree_hal_semaphore_notify method. Any satisfied
// timepoints will have their callback made immediately from the notifying
// thread.
struct iree_hal_semaphore_t {
iree_hal_resource_t resource; // must be at 0
// Non-recursive mutex guarding access to the timepoint list.
iree_slim_mutex_t timepoint_mutex;
// Timepoint list in insertion order.
// There are probably better orderings we could use here that allow us to
// walk the entire list less frequently, though target payload value is not
// enough as deadlines still require the scan. We could sort by non-infinite
// deadlines first and then infinite ones last but given the common timepoint
// counts (0..1) it's not worth the complexity today.
iree_hal_semaphore_timepoint_list_t timepoint_list
IREE_GUARDED_BY(timepoint_mutex);
};
// Initializes the base |out_semaphore| resource.
IREE_API_EXPORT void iree_hal_semaphore_initialize(
const iree_hal_semaphore_vtable_t* vtable,
iree_hal_semaphore_t* out_semaphore);
// Deinitializes the |semaphore|.
// Because timepoints retain their semaphore the timepoint list is known empty.
IREE_API_EXPORT void iree_hal_semaphore_deinitialize(
iree_hal_semaphore_t* semaphore);
// Acquires a timepoint on the semaphore timeline that issues the given
// |callback| when the semaphore payload reaches or exceeds |minimum_value|. The
// callback may be made from a random external thread and must avoid recursive
// locks (such as managing timepoints).
//
// The caller provides storage in |out_timepoint| and it must remain valid until
// either the callback is made or the timepoint is cancelled via
// iree_hal_semaphore_cancel_timepoint.
//
// If the timepoint has already been reached the callback _may_ be issued prior
// to the function returning. If the |timeout| has already been reached then the
// callback _may_ be issued with IREE_STATUS_DEADLINE_EXCEEDED.
//
// NOTE: this behavior is due to racy multi-threaded behavior and not a
// guarantee of the API: if there's only ever a single thread then acquiring a
// timepoint that has been satisfied will *NOT* callback here.
// iree_hal_semaphore_poll can be used to force a flush of any resolved
// timepoints on-demand.
//
// Must not be called from a timepoint callback.
IREE_API_EXPORT void iree_hal_semaphore_acquire_timepoint(
iree_hal_semaphore_t* semaphore, uint64_t minimum_value,
iree_timeout_t timeout, iree_hal_semaphore_callback_t callback,
iree_hal_semaphore_timepoint_t* out_timepoint);
// Cancels a |timepoint| and prevents any future callbacks.
// The timepoint is only considered cancelled once execution returns
// to the caller; due to races it's possible for a callback to be made with
// a different status code while releasing.
//
// Only the owner of the timepoint (whatever is listening for the callback)
// should use this as otherwise the program may become desynchronized.
//
// Must not be called from a timepoint callback.
IREE_API_EXPORT void iree_hal_semaphore_cancel_timepoint(
iree_hal_semaphore_t* semaphore, iree_hal_semaphore_timepoint_t* timepoint);
// Used by implementations to notify when a new timepoint is reached.
// Implementations must call this when they observe changes.
// Calling this incorrectly will result in undefined behavior.
//
// Must not be called from a timepoint callback.
// Must not be called with a semaphore lock held as notifications may
// re-entrantly use the semaphore.
IREE_API_EXPORT void iree_hal_semaphore_notify(
iree_hal_semaphore_t* semaphore, uint64_t new_value,
iree_status_code_t new_status_code);
// Polls timepoints and issues callbacks for those already resolved.
// This polling is performed internally on user calls such as signal and wait
// but can be made more frequently to reduce latency in cases where users are
// not making calls frequently enough. Implementations should always prefer to
// notify directly with iree_hal_semaphore_notify to avoid additional
// synchronization overheads.
//
// Must not be called from a timepoint callback.
IREE_API_EXPORT void iree_hal_semaphore_poll(iree_hal_semaphore_t* semaphore);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // IREE_HAL_UTILS_SEMAPHORE_BASE_H_