blob: 7807b3f9081adc11b3a0ab16c47b6a44d7aac84f [file]
// 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
// Base operation type for proactor-driven async I/O.
//
// All async operations extend iree_async_operation_t with type-specific
// parameters and result fields. Operations are caller-owned (intrusive — no
// proactor allocation on submit) and the proactor invokes the completion
// callback when the operation finishes.
//
// The ownership rule: the caller owns the operation storage after (and only
// after) the final completion callback fires. During execution the proactor
// may read/write any field. For multishot operations the callback fires
// multiple times; the caller regains ownership only on the final invocation
// (the one without IREE_ASYNC_COMPLETION_FLAG_MORE).
//
// Operation subtypes are defined in iree/async/operations/*.h. Each subtype
// embeds iree_async_operation_t as its first member for safe base-to-subtype
// casts via the type discriminator.
#ifndef IREE_ASYNC_OPERATION_H_
#define IREE_ASYNC_OPERATION_H_
#include <stdint.h>
#include "iree/base/api.h"
#include "iree/base/internal/atomics.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
typedef struct iree_async_operation_t iree_async_operation_t;
typedef struct iree_async_operation_pool_t iree_async_operation_pool_t;
//===----------------------------------------------------------------------===//
// Completion callback
//===----------------------------------------------------------------------===//
// Flags passed to the completion callback alongside the status.
enum iree_async_completion_flag_bits_e {
IREE_ASYNC_COMPLETION_FLAG_NONE = 0u,
// More completions will follow for this operation (multishot).
// The proactor retains ownership of the operation; the caller must not
// modify, resubmit, or release it. When this flag is NOT set the callback
// is final and the caller owns the operation again.
IREE_ASYNC_COMPLETION_FLAG_MORE = 1u << 0,
// Zero-copy was achieved for this send operation.
// Set when the socket has IREE_ASYNC_SOCKET_OPTION_ZERO_COPY AND the kernel
// actually achieved zero-copy (no fallback to copying). This allows callers
// to monitor ZC effectiveness for diagnostics or adaptive behavior.
// Only meaningful for socket send completions; other operations ignore this.
IREE_ASYNC_COMPLETION_FLAG_ZERO_COPY_ACHIEVED = 1u << 1,
};
typedef uint32_t iree_async_completion_flags_t;
// Completion callback invoked by the proactor during poll().
//
// |user_data| is the value set on the operation at submit time.
// |operation| is the completed operation (cast to subtype via operation->type).
// |status| is the result: OK on success, CANCELLED on cancellation, or an
// error status describing the failure. Ownership of the status is transferred
// to the callback; the callback must consume or ignore it.
// |flags| is a bitmask of iree_async_completion_flag_e values.
//
// Callbacks fire from within iree_async_proactor_poll() on the polling thread.
// Heavy work should be deferred to avoid stalling completion dispatch.
typedef void (*iree_async_completion_fn_t)(void* user_data,
iree_async_operation_t* operation,
iree_status_t status,
iree_async_completion_flags_t flags);
//===----------------------------------------------------------------------===//
// Operation type discriminator
//===----------------------------------------------------------------------===//
// Identifies the concrete subtype of an iree_async_operation_t for safe
// downcasting. Each value corresponds to a specific *_operation_t struct
// in iree/async/operations/.
enum iree_async_operation_type_e {
// Scheduling and synchronization.
IREE_ASYNC_OPERATION_TYPE_NOP = 0u,
IREE_ASYNC_OPERATION_TYPE_TIMER,
IREE_ASYNC_OPERATION_TYPE_EVENT_WAIT,
IREE_ASYNC_OPERATION_TYPE_SEMAPHORE_WAIT,
IREE_ASYNC_OPERATION_TYPE_SEMAPHORE_SIGNAL,
IREE_ASYNC_OPERATION_TYPE_SEQUENCE,
// Socket I/O.
IREE_ASYNC_OPERATION_TYPE_SOCKET_ACCEPT,
IREE_ASYNC_OPERATION_TYPE_SOCKET_CONNECT,
IREE_ASYNC_OPERATION_TYPE_SOCKET_RECV,
IREE_ASYNC_OPERATION_TYPE_SOCKET_RECV_POOL,
IREE_ASYNC_OPERATION_TYPE_SOCKET_SEND,
IREE_ASYNC_OPERATION_TYPE_SOCKET_SENDTO,
IREE_ASYNC_OPERATION_TYPE_SOCKET_RECVFROM,
IREE_ASYNC_OPERATION_TYPE_SOCKET_CLOSE,
// File I/O.
IREE_ASYNC_OPERATION_TYPE_FILE_OPEN,
IREE_ASYNC_OPERATION_TYPE_FILE_READ,
IREE_ASYNC_OPERATION_TYPE_FILE_WRITE,
IREE_ASYNC_OPERATION_TYPE_FILE_CLOSE,
// Futex operations (requires
// IREE_ASYNC_PROACTOR_CAPABILITY_FUTEX_OPERATIONS).
IREE_ASYNC_OPERATION_TYPE_FUTEX_WAIT,
IREE_ASYNC_OPERATION_TYPE_FUTEX_WAKE,
// Notification operations (uses futex or eventfd based on capabilities).
IREE_ASYNC_OPERATION_TYPE_NOTIFICATION_WAIT,
IREE_ASYNC_OPERATION_TYPE_NOTIFICATION_SIGNAL,
// Handle poll (one-shot readiness check on a raw system handle).
IREE_ASYNC_OPERATION_TYPE_HANDLE_POLL,
// Cross-proactor messaging (requires
// IREE_ASYNC_PROACTOR_CAPABILITY_PROACTOR_MESSAGING).
IREE_ASYNC_OPERATION_TYPE_MESSAGE,
};
typedef uint8_t iree_async_operation_type_t;
//===----------------------------------------------------------------------===//
// Operation flags
//===----------------------------------------------------------------------===//
// Behavioral flags set by the caller before submitting an operation.
enum iree_async_operation_flag_bits_e {
IREE_ASYNC_OPERATION_FLAG_NONE = 0u,
// Request multishot behavior: the proactor will deliver completions
// repeatedly until explicitly cancelled or the resource is closed.
// Supported on: ACCEPT, RECV.
// Unsupported operations will fail with IREE_STATUS_INVALID_ARGUMENT.
IREE_ASYNC_OPERATION_FLAG_MULTISHOT = 1u << 0,
// Link this operation to the next operation in the submission batch.
// When set, the kernel will not begin the next operation until this one
// completes successfully. If this operation fails or is cancelled, all
// subsequent linked operations receive IREE_STATUS_CANCELLED.
//
// Semantics: "link TO next" - set on all operations EXCEPT the last in a
// chain. The last operation must NOT have this flag set.
//
// Requires IREE_ASYNC_PROACTOR_CAPABILITY_LINKED_OPERATIONS capability.
IREE_ASYNC_OPERATION_FLAG_LINKED = 1u << 1,
};
typedef uint32_t iree_async_operation_flags_t;
//===----------------------------------------------------------------------===//
// Wait mode
//===----------------------------------------------------------------------===//
// Determines when a multi-semaphore wait operation is satisfied.
enum iree_async_wait_mode_e {
// Satisfied when ALL semaphores reach their target values.
IREE_ASYNC_WAIT_MODE_ALL = 0u,
// Satisfied when ANY semaphore reaches its target value.
IREE_ASYNC_WAIT_MODE_ANY = 1u,
};
typedef uint8_t iree_async_wait_mode_t;
// Flags controlling the behavior of a blocking wait operation.
//
// The low two bits select a mutually exclusive wait strategy. ACTIVE takes
// precedence over YIELD if both are set. Upper bits are reserved for future
// orthogonal flags (e.g. interruptibility).
//
// Platforms that cannot distinguish YIELD from ACTIVE may promote YIELD to
// ACTIVE. The flags are hints — the implementation chooses the closest
// supported behavior.
//
// Matches HSA's wait_state (ACTIVE/BLOCKED) and extends it with a middle
// tier that captures the empirically-observed sweet spot (~200-500ns of spin)
// between pure spin (excessive memory bandwidth) and pure block (1-20us of
// wake latency from futex/context switch).
enum iree_async_wait_flag_bits_e {
IREE_ASYNC_WAIT_FLAG_NONE = 0u,
// Brief spin (yield/pause loop) followed by a kernel block. Catches fast
// signals without a context switch while bounding CPU waste. The spin
// duration is platform-tuned (typically 200-500ns).
IREE_ASYNC_WAIT_FLAG_YIELD = 1u << 0,
// Full active spin — the thread never enters a kernel wait. Lowest latency
// at the cost of burning a full CPU core and increased memory bandwidth
// pressure. Use only when the expected wait is very short (sub-microsecond)
// and latency is critical.
IREE_ASYNC_WAIT_FLAG_ACTIVE = 1u << 1,
};
typedef uint32_t iree_async_wait_flags_t;
//===----------------------------------------------------------------------===//
// Internal flags
//===----------------------------------------------------------------------===//
// Proactor-private flags for operation state management during execution.
// These are not part of the public API; callers should not read or write them.
// Flag values are defined per-backend as enums (e.g.,
// iree_async_posix_operation_internal_flags_e in the POSIX proactor).
//
// The value type is used for flag constants; the struct field is atomic because
// cancel (any thread) sets CANCELLED while poll (single thread) reads it.
typedef uint32_t iree_async_operation_internal_flags_t;
//===----------------------------------------------------------------------===//
// Base operation
//===----------------------------------------------------------------------===//
// Base type for all async operations. Extended by subtype structs that embed
// this as their first member.
//
// Fields are written by the caller before submission:
// type, flags, completion_fn, user_data, pool
//
// Fields are managed by the proactor during execution:
// next, internal_flags, linked_next, submit_time_ns (tracing only)
//
// After the final completion callback, all fields are caller-owned again.
typedef struct iree_async_operation_t {
// Intrusive linked list pointer (proactor-internal tracking).
// Must not be touched by the caller while the operation is in flight.
struct iree_async_operation_t* next;
// Discriminates the concrete subtype for safe downcasting.
iree_async_operation_type_t type;
// Proactor-private flags for cancellation, iteration safety, etc.
// Initialized to 0; callers must not access this field.
// Atomic: cancel (any thread) writes CANCELLED, poll thread reads.
iree_atomic_int32_t internal_flags;
// Behavioral flags (MULTISHOT, etc.) set by caller before submit.
iree_async_operation_flags_t flags;
// Callback invoked on completion (from poll context).
// Must not be NULL.
iree_async_completion_fn_t completion_fn;
void* user_data;
// Optional: pool to release this operation to after the final callback.
// If non-NULL, the proactor calls iree_async_operation_pool_release()
// after the final callback returns. The caller must not access the
// operation after the callback in this case.
iree_async_operation_pool_t* pool;
// LINKED chain continuation pointer (proactor-internal).
// When this operation has IREE_ASYNC_OPERATION_FLAG_LINKED set, points to
// the next operation in the chain. On completion, the proactor submits
// continuations (on success) or cancels them (on failure).
// Callers must not access this field.
struct iree_async_operation_t* linked_next;
// Tracing: timestamp of submission for latency measurement.
IREE_TRACE(iree_time_t submit_time_ns;)
#if defined(IREE_SANITIZER_THREAD)
// Atomic bridge for TSAN across kernel-mediated completion dispatch.
// Proactor backends that use kernel-managed shared memory rings (io_uring CQ,
// IOCP completion ports) have a submit→completion ordering gap that TSAN
// cannot observe: the submitter thread writes operation fields, the kernel
// processes the I/O, and the poll thread reads the operation fields from a
// completion event — but TSAN sees no userspace synchronization between the
// write and the read.
//
// This atomic provides a release/acquire pair that TSAN intercepts through
// its compiler instrumentation (atomic operations are TSAN's core tracking
// mechanism). The submitter stores with release ordering after filling the
// operation; the completer loads with acquire ordering before reading
// operation fields. This makes the kernel-provided ordering visible to TSAN.
iree_atomic_int32_t tsan_bridge;
#endif // IREE_SANITIZER_THREAD
} iree_async_operation_t;
//===----------------------------------------------------------------------===//
// Operation list (batch submit)
//===----------------------------------------------------------------------===//
// A list of operations for batch submission.
// Maps to a single io_uring_submit() or kevent() call for efficiency.
// The values pointer and count must remain valid only for the duration of the
// submit call (the proactor copies or enqueues internally).
typedef struct iree_async_operation_list_t {
iree_async_operation_t** values;
iree_host_size_t count;
} iree_async_operation_list_t;
//===----------------------------------------------------------------------===//
// Resource retain/release
//===----------------------------------------------------------------------===//
// Retains resources referenced by an operation entering proactor management.
// Called at submit time to prevent premature destruction while the operation is
// in flight. Each retained resource gets exactly one reference increment.
//
// Close operations (SOCKET_CLOSE, FILE_CLOSE) are intentionally absent: they
// consume the caller's reference rather than retaining a new one. The
// corresponding release in iree_async_operation_release_resources IS the
// consumption, with no prior retain to balance it.
IREE_API_EXPORT void iree_async_operation_retain_resources(
iree_async_operation_t* operation);
// Releases resources retained by iree_async_operation_retain_resources, plus
// consumes caller references for close operations. Called during completion
// dispatch, after linked continuation dispatch but before the user's callback.
// Must happen BEFORE the callback since the callback may free the operation.
IREE_API_EXPORT void iree_async_operation_release_resources(
iree_async_operation_t* operation);
//===----------------------------------------------------------------------===//
// Inline helpers
//===----------------------------------------------------------------------===//
// Zero-initializes an operation subtype struct for reuse.
//
// Unlike raw memset(), this avoids non-atomic writes to the atomic fields in
// the base struct (internal_flags, tsan_bridge). C11 forbids mixing atomic and
// non-atomic accesses to the same object, and TSAN correctly flags violations.
// The base fields are set by iree_async_operation_initialize() below.
//
// Usage:
// iree_async_operation_zero(&send_op->base, sizeof(*send_op));
// iree_async_operation_initialize(&send_op->base, ...);
// send_op->socket = ...; // subtype fields are zeroed, set as needed
static inline void iree_async_operation_zero(iree_async_operation_t* operation,
iree_host_size_t total_size) {
iree_host_size_t base_size = sizeof(iree_async_operation_t);
if (total_size > base_size) {
memset((char*)operation + base_size, 0, total_size - base_size);
}
}
// Initializes the base fields of an operation.
// Caller must still fill subtype-specific fields after this call.
static inline void iree_async_operation_initialize(
iree_async_operation_t* operation, iree_async_operation_type_t type,
iree_async_operation_flags_t flags,
iree_async_completion_fn_t completion_fn, void* user_data) {
operation->next = NULL;
operation->type = type;
iree_atomic_store(&operation->internal_flags, 0, iree_memory_order_relaxed);
operation->flags = flags;
operation->completion_fn = completion_fn;
operation->user_data = user_data;
operation->pool = NULL;
operation->linked_next = NULL;
IREE_TRACE({ operation->submit_time_ns = 0; });
#if defined(IREE_SANITIZER_THREAD)
iree_atomic_store(&operation->tsan_bridge, 0, iree_memory_order_relaxed);
#endif // IREE_SANITIZER_THREAD
}
// Atomically loads the internal flags of an operation (acquire ordering).
static inline iree_async_operation_internal_flags_t
iree_async_operation_load_internal_flags(iree_async_operation_t* operation) {
return (iree_async_operation_internal_flags_t)iree_atomic_load(
&operation->internal_flags, iree_memory_order_acquire);
}
// Atomically sets (ORs) internal flags on an operation (release ordering).
// Used by cancel (any thread) to set CANCELLED, by poll thread to set state.
static inline void iree_async_operation_set_internal_flags(
iree_async_operation_t* operation,
iree_async_operation_internal_flags_t flags_to_set) {
iree_atomic_fetch_or(&operation->internal_flags, (int32_t)flags_to_set,
iree_memory_order_release);
}
// Atomically clears (resets to 0) the internal flags of an operation.
// Used at submission time to prepare an operation for a fresh execution cycle.
static inline void iree_async_operation_clear_internal_flags(
iree_async_operation_t* operation) {
iree_atomic_store(&operation->internal_flags, 0, iree_memory_order_release);
}
// Creates an operation list from a pointer and count.
static inline iree_async_operation_list_t iree_async_operation_list_make(
iree_async_operation_t** values, iree_host_size_t count) {
iree_async_operation_list_t list;
list.values = values;
list.count = count;
return list;
}
// Creates an operation list containing a single operation.
static inline iree_async_operation_list_t iree_async_operation_list_from_one(
iree_async_operation_t* operation) {
iree_async_operation_list_t list;
list.values = &operation;
list.count = 1;
return list;
}
// Returns an empty operation list.
static inline iree_async_operation_list_t iree_async_operation_list_empty(
void) {
iree_async_operation_list_t list;
list.values = NULL;
list.count = 0;
return list;
}
// Returns true if the operation list is empty.
static inline bool iree_async_operation_list_is_empty(
iree_async_operation_list_t list) {
return list.count == 0;
}
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // IREE_ASYNC_OPERATION_H_