| // 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_ |