blob: 05eafd4dd724cfea02d67baaf9f3e62f9151ad2f [file] [log] [blame]
// Copyright 2026 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/async/event_pool.h"
#include "iree/async/event.h"
//===----------------------------------------------------------------------===//
// Internal: Pool growth
//===----------------------------------------------------------------------===//
// Allocates a new event and adds it to the pool's all_events list.
// Called under acquire_mutex when both stacks are empty.
static iree_status_t iree_async_event_pool_grow_locked(
iree_async_event_pool_t* pool, iree_async_event_t** out_event) {
IREE_TRACE_ZONE_BEGIN(z0);
// Create a new event via the proactor (eventfd on Linux, pipe on macOS, etc.)
iree_async_event_t* event = NULL;
IREE_RETURN_AND_END_ZONE_IF_ERROR(
z0, iree_async_event_create(pool->proactor, &event));
// Set the event's home pool for release routing.
event->pool = pool;
// Add to all_events list for cleanup during deinitialize.
// Use pool_all_next (not pool_next) to keep this list independent of the
// acquire_stack and return_stack which use pool_next.
event->pool_all_next = pool->all_events_head;
pool->all_events_head = event;
*out_event = event;
IREE_TRACE_ZONE_END(z0);
return iree_ok_status();
}
//===----------------------------------------------------------------------===//
// Event pool
//===----------------------------------------------------------------------===//
IREE_API_EXPORT iree_status_t iree_async_event_pool_initialize(
iree_async_proactor_t* proactor, iree_allocator_t allocator,
iree_host_size_t initial_capacity, iree_async_event_pool_t* out_pool) {
IREE_ASSERT_ARGUMENT(proactor);
IREE_ASSERT_ARGUMENT(out_pool);
IREE_TRACE_ZONE_BEGIN(z0);
memset(out_pool, 0, sizeof(*out_pool));
out_pool->proactor = proactor;
out_pool->allocator = allocator;
iree_atomic_store(&out_pool->return_stack.head, 0, iree_memory_order_relaxed);
iree_slim_mutex_initialize(&out_pool->acquire_mutex);
out_pool->acquire_head = NULL;
out_pool->all_events_head = NULL;
// Pre-create initial_capacity events to amortize eventfd syscall cost.
// Events go directly to acquire_head (no need for migration on first use).
iree_status_t status = iree_ok_status();
for (iree_host_size_t i = 0;
i < initial_capacity && iree_status_is_ok(status); ++i) {
iree_async_event_t* event = NULL;
status = iree_async_event_pool_grow_locked(out_pool, &event);
if (iree_status_is_ok(status)) {
// Add to acquire stack (already in all_events from grow_locked).
event->pool_next = out_pool->acquire_head;
out_pool->acquire_head = event;
}
}
if (!iree_status_is_ok(status)) {
iree_async_event_pool_deinitialize(out_pool);
}
IREE_TRACE_ZONE_END(z0);
return status;
}
IREE_API_EXPORT void iree_async_event_pool_deinitialize(
iree_async_event_pool_t* pool) {
if (!pool) return;
IREE_TRACE_ZONE_BEGIN(z0);
// Destroy all events in the all_events list.
// This list uses pool_all_next linkage (independent of pool_next used by
// acquire_stack and return_stack).
iree_async_event_t* event = pool->all_events_head;
while (event) {
iree_async_event_t* next = event->pool_all_next;
// Clear pool references before destroying to avoid double-free issues.
event->pool = NULL;
event->pool_next = NULL;
event->pool_all_next = NULL;
// Release the event (will call proactor vtable destroy_event).
iree_async_event_release(event);
event = next;
}
pool->all_events_head = NULL;
pool->acquire_head = NULL;
iree_atomic_store(&pool->return_stack.head, 0, iree_memory_order_relaxed);
iree_slim_mutex_deinitialize(&pool->acquire_mutex);
IREE_TRACE_ZONE_END(z0);
}
IREE_API_EXPORT iree_status_t iree_async_event_pool_acquire(
iree_async_event_pool_t* pool, iree_async_event_t** out_event) {
IREE_ASSERT_ARGUMENT(pool);
IREE_ASSERT_ARGUMENT(out_event);
*out_event = NULL;
iree_slim_mutex_lock(&pool->acquire_mutex);
iree_async_event_t* event = NULL;
iree_status_t status = iree_ok_status();
// Fast path: pop from acquire stack.
if (pool->acquire_head) {
event = pool->acquire_head;
pool->acquire_head = event->pool_next;
} else {
// Slow path: migrate from return stack.
iree_async_event_t* return_list = (iree_async_event_t*)iree_atomic_exchange(
&pool->return_stack.head, 0, iree_memory_order_acquire);
if (!return_list) {
// Both stacks empty: grow the pool.
status = iree_async_event_pool_grow_locked(pool, &event);
} else {
// Reverse the list for FIFO fairness (events returned first are acquired
// first). This also ensures good cache locality since recently-used
// events are acquired next.
iree_async_event_t* reversed = NULL;
while (return_list) {
iree_async_event_t* next = return_list->pool_next;
return_list->pool_next = reversed;
reversed = return_list;
return_list = next;
}
// Pop one for the caller, rest becomes acquire_head.
event = reversed;
pool->acquire_head = reversed->pool_next;
}
}
iree_slim_mutex_unlock(&pool->acquire_mutex);
if (iree_status_is_ok(status)) {
// No reset needed: events are one-shot. The proactor's event wait operation
// drains the underlying primitive (eventfd/pipe) as part of completion, so
// events are always clean when returned to the pool.
// Clear pool_next to avoid dangling pointers while event is in use.
event->pool_next = NULL;
*out_event = event;
}
return status;
}
IREE_API_EXPORT void iree_async_event_pool_release(
iree_async_event_pool_t* pool, iree_async_event_t* event) {
IREE_ASSERT_ARGUMENT(pool);
IREE_ASSERT_ARGUMENT(event);
// Lock-free push to return stack (Treiber stack).
// This is the critical path for the polling thread - must be fast.
//
// Use intptr_t for the expected value to avoid strict aliasing violations.
// The CAS atomically updates head if it still equals expected, otherwise
// reloads expected with the current value for retry.
intptr_t expected;
do {
expected =
iree_atomic_load(&pool->return_stack.head, iree_memory_order_relaxed);
event->pool_next = (iree_async_event_t*)expected;
} while (!iree_atomic_compare_exchange_weak(
&pool->return_stack.head, &expected, (intptr_t)event,
iree_memory_order_release, iree_memory_order_relaxed));
}