blob: 738f0ad079371b5d490059b2bc14188aef8f0947 [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
#include "iree/hal/semaphore.h"
#include <stddef.h>
#include "iree/hal/detail.h"
#include "iree/hal/device.h"
//===----------------------------------------------------------------------===//
// String utils
//===----------------------------------------------------------------------===//
static const iree_bitfield_string_mapping_t
iree_hal_semaphore_compatibility_mappings[] = {
{IREE_HAL_SEMAPHORE_COMPATIBILITY_ALL, IREE_SVL("ALL")},
{IREE_HAL_SEMAPHORE_COMPATIBILITY_HOST_ONLY, IREE_SVL("HOST_ONLY")},
{IREE_HAL_SEMAPHORE_COMPATIBILITY_DEVICE_ONLY, IREE_SVL("DEVICE_ONLY")},
{IREE_HAL_SEMAPHORE_COMPATIBILITY_HOST_WAIT, IREE_SVL("HOST_WAIT")},
{IREE_HAL_SEMAPHORE_COMPATIBILITY_DEVICE_WAIT, IREE_SVL("DEVICE_WAIT")},
{IREE_HAL_SEMAPHORE_COMPATIBILITY_HOST_SIGNAL, IREE_SVL("HOST_SIGNAL")},
{IREE_HAL_SEMAPHORE_COMPATIBILITY_DEVICE_SIGNAL,
IREE_SVL("DEVICE_SIGNAL")},
};
IREE_API_EXPORT iree_status_t iree_hal_semaphore_compatibility_parse(
iree_string_view_t value, iree_hal_semaphore_compatibility_t* out_value) {
return iree_bitfield_parse(
value, IREE_ARRAYSIZE(iree_hal_semaphore_compatibility_mappings),
iree_hal_semaphore_compatibility_mappings, out_value);
}
IREE_API_EXPORT iree_string_view_t iree_hal_semaphore_compatibility_format(
iree_hal_semaphore_compatibility_t value,
iree_bitfield_string_temp_t* out_temp) {
return iree_bitfield_format_inline(
value, IREE_ARRAYSIZE(iree_hal_semaphore_compatibility_mappings),
iree_hal_semaphore_compatibility_mappings, out_temp);
}
//===----------------------------------------------------------------------===//
// iree_hal_semaphore_t
//===----------------------------------------------------------------------===//
// Helper to get the typed vtable from a HAL semaphore.
static inline const iree_hal_semaphore_vtable_t* iree_hal_semaphore_vtable(
iree_hal_semaphore_t* semaphore) {
return (const iree_hal_semaphore_vtable_t*)((const iree_hal_resource_t*)
semaphore)
->vtable;
}
// Hand-written retain/release/destroy because the standard
// IREE_HAL_API_RETAIN_RELEASE macro reads ->destroy by member name, but
// with the embedded async vtable the destroy method is at ->async.destroy.
IREE_API_EXPORT void iree_hal_semaphore_destroy(
iree_hal_semaphore_t* semaphore) {
if (IREE_LIKELY(semaphore)) {
iree_hal_semaphore_vtable(semaphore)->async.destroy(
(iree_async_semaphore_t*)semaphore);
}
}
IREE_API_EXPORT void iree_hal_semaphore_retain(
iree_hal_semaphore_t* semaphore) {
if (IREE_LIKELY(semaphore)) {
iree_atomic_ref_count_inc(&((iree_hal_resource_t*)(semaphore))->ref_count);
}
}
IREE_API_EXPORT void iree_hal_semaphore_release(
iree_hal_semaphore_t* semaphore) {
if (IREE_LIKELY(semaphore) &&
iree_atomic_ref_count_dec(
&((iree_hal_resource_t*)(semaphore))->ref_count) == 1) {
iree_hal_semaphore_destroy(semaphore);
}
}
IREE_API_EXPORT iree_status_t iree_hal_semaphore_create(
iree_hal_device_t* device, iree_hal_queue_affinity_t queue_affinity,
uint64_t initial_value, iree_hal_semaphore_flags_t flags,
iree_hal_semaphore_t** out_semaphore) {
IREE_ASSERT_ARGUMENT(device);
IREE_ASSERT_ARGUMENT(out_semaphore);
*out_semaphore = NULL;
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, initial_value);
iree_status_t status =
IREE_HAL_VTABLE_DISPATCH(device, iree_hal_device, create_semaphore)(
device, queue_affinity, initial_value, flags, out_semaphore);
IREE_TRACE_ZONE_END(z0);
return status;
}
IREE_API_EXPORT iree_status_t
iree_hal_semaphore_query(iree_hal_semaphore_t* semaphore, uint64_t* out_value) {
IREE_ASSERT_ARGUMENT(semaphore);
IREE_ASSERT_ARGUMENT(out_value);
*out_value = 0;
IREE_TRACE_ZONE_BEGIN(z0);
// The async vtable query returns uint64_t directly with the failure sentinel
// value indicating error. We adapt this to the HAL status + out_value API.
uint64_t value = iree_hal_semaphore_vtable(semaphore)->async.query(
(iree_async_semaphore_t*)semaphore);
iree_status_t status = iree_ok_status();
if (IREE_UNLIKELY(value >= IREE_HAL_SEMAPHORE_FAILURE_VALUE)) {
status = iree_hal_semaphore_failure_as_status(value);
*out_value = IREE_HAL_SEMAPHORE_FAILURE_VALUE;
} else {
*out_value = value;
}
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, *out_value);
IREE_TRACE_ZONE_END(z0);
return status;
}
IREE_API_EXPORT iree_status_t
iree_hal_semaphore_signal(iree_hal_semaphore_t* semaphore, uint64_t new_value,
const iree_async_frontier_t* frontier) {
IREE_ASSERT_ARGUMENT(semaphore);
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, new_value);
iree_status_t status = iree_hal_semaphore_vtable(semaphore)->async.signal(
(iree_async_semaphore_t*)semaphore, new_value, frontier);
IREE_TRACE_ZONE_END(z0);
return status;
}
IREE_API_EXPORT void iree_hal_semaphore_fail(iree_hal_semaphore_t* semaphore,
iree_status_t status) {
IREE_ASSERT_ARGUMENT(semaphore);
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, iree_status_code(status));
iree_async_semaphore_fail((iree_async_semaphore_t*)semaphore, status);
IREE_TRACE_ZONE_END(z0);
}
IREE_API_EXPORT iree_status_t
iree_hal_semaphore_wait(iree_hal_semaphore_t* semaphore, uint64_t value,
iree_timeout_t timeout, iree_async_wait_flags_t flags) {
IREE_ASSERT_ARGUMENT(semaphore);
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, value);
iree_status_t status = iree_hal_semaphore_vtable(semaphore)->wait(
semaphore, value, timeout, flags);
IREE_TRACE_ZONE_END(z0);
return status;
}
static iree_status_t iree_hal_semaphore_wait_source_resolve(
iree_wait_source_t wait_source, iree_timeout_t timeout,
iree_wait_source_resolve_callback_t callback, void* user_data) {
iree_hal_semaphore_t* semaphore = (iree_hal_semaphore_t*)wait_source.self;
uint64_t target_value = wait_source.data;
if (callback) {
// Async: register timepoint via the async semaphore (toll-free bridge).
return iree_async_semaphore_resolve((iree_async_semaphore_t*)semaphore,
target_value, timeout, callback,
user_data);
} else {
// Sync: blocking wait through the driver vtable.
return iree_hal_semaphore_wait(semaphore, target_value, timeout,
IREE_ASYNC_WAIT_FLAG_NONE);
}
}
IREE_API_EXPORT iree_wait_source_t
iree_hal_semaphore_await(iree_hal_semaphore_t* semaphore, uint64_t value) {
IREE_ASSERT_ARGUMENT(semaphore);
return (iree_wait_source_t){
.self = semaphore,
.data = value,
.resolve = iree_hal_semaphore_wait_source_resolve,
};
}
IREE_API_EXPORT iree_status_t iree_hal_semaphore_import_timepoint(
iree_hal_semaphore_t* semaphore, uint64_t value,
iree_hal_queue_affinity_t queue_affinity,
iree_hal_external_timepoint_t external_timepoint) {
IREE_ASSERT_ARGUMENT(semaphore);
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, value);
iree_status_t status = iree_ok_status();
if (external_timepoint.type ==
IREE_HAL_EXTERNAL_TIMEPOINT_TYPE_ASYNC_PRIMITIVE) {
// Handled in the base layer via the semaphore's proactor.
// The proactor monitors the primitive handle and signals the semaphore
// timeline when the handle becomes ready.
iree_async_semaphore_t* async_semaphore =
(iree_async_semaphore_t*)semaphore;
status = iree_async_semaphore_import_fence(
async_semaphore->proactor, external_timepoint.handle.async_primitive,
async_semaphore, value);
} else {
// Driver-specific types (CUDA_EVENT, HIP_EVENT, etc.) dispatch through the
// vtable for driver-native handling.
status = iree_hal_semaphore_vtable(semaphore)->import_timepoint(
semaphore, value, queue_affinity, external_timepoint);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
IREE_API_EXPORT iree_status_t iree_hal_semaphore_export_timepoint(
iree_hal_semaphore_t* semaphore, uint64_t value,
iree_hal_queue_affinity_t queue_affinity,
iree_hal_external_timepoint_type_t requested_type,
iree_hal_external_timepoint_flags_t requested_flags,
iree_hal_external_timepoint_t* IREE_RESTRICT out_external_timepoint) {
IREE_ASSERT_ARGUMENT(semaphore);
IREE_ASSERT_ARGUMENT(out_external_timepoint);
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, value);
memset(out_external_timepoint, 0, sizeof(*out_external_timepoint));
iree_status_t status = iree_ok_status();
if (requested_type == IREE_HAL_EXTERNAL_TIMEPOINT_TYPE_ASYNC_PRIMITIVE) {
// Handled in the base layer via the semaphore's proactor.
// Creates a platform handle (eventfd/HANDLE/etc) that signals when the
// semaphore timeline reaches the requested value.
iree_async_semaphore_t* async_semaphore =
(iree_async_semaphore_t*)semaphore;
iree_async_primitive_t primitive = iree_async_primitive_none();
status = iree_async_semaphore_export_fence(
async_semaphore->proactor, async_semaphore, value, &primitive);
if (iree_status_is_ok(status)) {
out_external_timepoint->type =
IREE_HAL_EXTERNAL_TIMEPOINT_TYPE_ASYNC_PRIMITIVE;
out_external_timepoint->flags = requested_flags;
out_external_timepoint->compatibility =
IREE_HAL_SEMAPHORE_COMPATIBILITY_HOST_WAIT |
IREE_HAL_SEMAPHORE_COMPATIBILITY_HOST_SIGNAL;
out_external_timepoint->handle.async_primitive = primitive;
}
} else {
// Driver-specific types (CUDA_EVENT, HIP_EVENT, etc.) dispatch through the
// vtable for driver-native handling.
status = iree_hal_semaphore_vtable(semaphore)->export_timepoint(
semaphore, value, queue_affinity, requested_type, requested_flags,
out_external_timepoint);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
//===----------------------------------------------------------------------===//
// iree_hal_semaphore_list_t
//===----------------------------------------------------------------------===//
IREE_API_EXPORT void iree_hal_semaphore_list_retain(
iree_hal_semaphore_list_t semaphore_list) {
for (iree_host_size_t i = 0; i < semaphore_list.count; ++i) {
iree_hal_semaphore_retain(semaphore_list.semaphores[i]);
}
}
IREE_API_EXPORT void iree_hal_semaphore_list_release(
iree_hal_semaphore_list_t semaphore_list) {
for (iree_host_size_t i = 0; i < semaphore_list.count; ++i) {
iree_hal_semaphore_release(semaphore_list.semaphores[i]);
}
}
IREE_API_EXPORT iree_status_t iree_hal_semaphore_list_clone(
const iree_hal_semaphore_list_t* source_list,
iree_allocator_t host_allocator, iree_hal_semaphore_list_t* out_list) {
IREE_ASSERT_ARGUMENT(out_list);
*out_list = iree_hal_semaphore_list_empty();
if (iree_hal_semaphore_list_is_empty(*source_list)) return iree_ok_status();
// Single allocation for both arrays.
iree_host_size_t semaphores_size =
source_list->count * sizeof(iree_hal_semaphore_t*);
iree_host_size_t values_size = source_list->count * sizeof(uint64_t);
uint8_t* buffer = NULL;
IREE_RETURN_IF_ERROR(iree_allocator_malloc(
host_allocator, semaphores_size + values_size, (void**)&buffer));
out_list->count = source_list->count;
out_list->semaphores = (iree_hal_semaphore_t**)buffer;
out_list->payload_values = (uint64_t*)(buffer + semaphores_size);
for (iree_host_size_t i = 0; i < source_list->count; ++i) {
out_list->semaphores[i] = source_list->semaphores[i];
iree_hal_semaphore_retain(out_list->semaphores[i]);
out_list->payload_values[i] = source_list->payload_values[i];
}
return iree_ok_status();
}
IREE_API_EXPORT void iree_hal_semaphore_list_free(
iree_hal_semaphore_list_t list, iree_allocator_t host_allocator) {
iree_hal_semaphore_list_release(list);
// Semaphores pointer is the base of the combined allocation.
iree_allocator_free(host_allocator, list.semaphores);
}
IREE_API_EXPORT bool iree_hal_semaphore_list_poll(
iree_hal_semaphore_list_t semaphore_list) {
for (iree_host_size_t i = 0; i < semaphore_list.count; ++i) {
// NOTE: this is unfortunately expensive in failure cases as it'll return
// a clone (or maybe the original!) status. We rely on failures being
// exceptional to make this acceptable.
uint64_t current_value = 0;
iree_status_t status =
iree_hal_semaphore_query(semaphore_list.semaphores[i], &current_value);
if (!iree_status_is_ok(status)) {
iree_status_ignore(status);
return false;
} else if (current_value < semaphore_list.payload_values[i]) {
return false; // not yet reached
}
}
return true;
}
IREE_API_EXPORT iree_status_t
iree_hal_semaphore_list_signal(iree_hal_semaphore_list_t semaphore_list,
const iree_async_frontier_t* frontier) {
IREE_TRACE_ZONE_BEGIN(z0);
iree_status_t status = iree_ok_status();
for (iree_host_size_t i = 0; i < semaphore_list.count; ++i) {
status =
iree_hal_semaphore_signal(semaphore_list.semaphores[i],
semaphore_list.payload_values[i], frontier);
if (!iree_status_is_ok(status)) break;
}
IREE_TRACE_ZONE_END(z0);
return status;
}
IREE_API_EXPORT void iree_hal_semaphore_list_fail(
iree_hal_semaphore_list_t semaphore_list, iree_status_t signal_status) {
IREE_TRACE_ZONE_BEGIN(z0);
IREE_TRACE_ZONE_APPEND_TEXT(
z0, iree_status_code_string(iree_status_code(signal_status)));
// This handles cases of empty lists by dropping signal_status if not
// consumed. Otherwise it clones the signal_status for each semaphore except
// the last, which in the common case of a single timepoint fence means no
// expensive clones.
for (iree_host_size_t i = 0; i < semaphore_list.count; ++i) {
const bool is_last = i == semaphore_list.count - 1;
iree_status_t semaphore_status;
if (is_last) {
// Can transfer ownership of the signal status.
semaphore_status = signal_status;
signal_status = iree_ok_status();
} else {
// Clone status for this particular signal.
semaphore_status = iree_status_clone(signal_status);
}
iree_hal_semaphore_fail(semaphore_list.semaphores[i], semaphore_status);
}
iree_status_ignore(signal_status);
IREE_TRACE_ZONE_END(z0);
}
IREE_API_EXPORT iree_status_t iree_hal_semaphore_list_wait(
iree_hal_semaphore_list_t semaphore_list, iree_timeout_t timeout,
iree_async_wait_flags_t flags) {
if (!semaphore_list.count) return iree_ok_status();
// HAL semaphores embed async semaphores at offset 0 (toll-free bridge).
return iree_async_semaphore_multi_wait(
IREE_ASYNC_WAIT_MODE_ALL,
(iree_async_semaphore_t**)semaphore_list.semaphores,
semaphore_list.payload_values, semaphore_list.count, timeout, flags,
iree_allocator_system());
}
static void iree_hal_semaphore_list_swap_elements(
iree_hal_semaphore_list_t* semaphore_list, iree_host_size_t i,
iree_host_size_t j) {
IREE_ASSERT(i >= 0 && i < semaphore_list->count);
IREE_ASSERT(j >= 0 && j < semaphore_list->count);
if (IREE_UNLIKELY(i == j)) {
return;
}
iree_hal_semaphore_t* tmp_semaphore = semaphore_list->semaphores[i];
uint64_t tmp_payload_value = semaphore_list->payload_values[i];
semaphore_list->semaphores[i] = semaphore_list->semaphores[j];
semaphore_list->payload_values[i] = semaphore_list->payload_values[j];
semaphore_list->semaphores[j] = tmp_semaphore;
semaphore_list->payload_values[j] = tmp_payload_value;
}
IREE_API_EXPORT void iree_hal_semaphore_list_erase(
iree_hal_semaphore_list_t* semaphore_list, iree_host_size_t i) {
IREE_ASSERT(semaphore_list->count > 0);
iree_hal_semaphore_list_swap_elements(semaphore_list, i,
semaphore_list->count - 1);
--semaphore_list->count;
}