blob: 930ae620a255c8e173fd11544f35d2dad93947e4 [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
#ifndef IREE_HAL_POOL_H_
#define IREE_HAL_POOL_H_
#include <stdbool.h>
#include <stdint.h>
#include "iree/async/frontier.h"
#include "iree/base/api.h"
#include "iree/hal/buffer.h"
#include "iree/hal/resource.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
typedef struct iree_async_notification_t iree_async_notification_t;
//===----------------------------------------------------------------------===//
// Types and Enums
//===----------------------------------------------------------------------===//
// Result of a pool reservation acquisition operation. This is a result enum,
// NOT an iree_status_t, because acquire_reservation() is a hot-path operation
// that regularly returns EXHAUSTED or OVER_BUDGET during normal operation
// (pool growth, steady-state pipelining where releases lag reservations by one
// epoch).
// Using iree_make_status() for these cases would capture backtraces on every
// transient exhaustion; expensive and misleading.
//
// The iree_status_t return from acquire_reservation() is reserved for
// infrastructure failures: invalid arguments, internal corruption, slab
// provider errors. Those are exceptional and warrant backtraces.
typedef uint32_t iree_hal_pool_acquire_result_t;
enum iree_hal_pool_acquire_result_e {
// Block reserved successfully. The death frontier from the recycled block
// was dominated by the requester's frontier; zero-sync reuse. The memory
// is safe for immediate use without any device synchronization.
IREE_HAL_POOL_ACQUIRE_OK = 0,
// Block reserved from previously unused offset space (first use of this
// region, or the block was freed with a NULL frontier). No death frontier
// to check. Equivalent to OK for callers; no synchronization needed.
IREE_HAL_POOL_ACQUIRE_OK_FRESH = 1,
// Block reserved, but the death frontier was NOT dominated by the
// requester's frontier. The block IS reserved (offset assigned), but the
// queue scheduler must add a hidden wait on the death frontier's axes before
// the reservation's bytes are used. The block's death frontier is returned
// via |out_info->wait_frontier| from
// iree_hal_pool_acquire_reservation(), and generic
// metadata about that dependency is returned via |out_info->flags|.
//
// This occurs when try-before-fence fails: prior work on this block may
// still be executing, and the requester's frontier does not transitively
// cover it. Queue implementations decide how to represent that hidden wait
// in their own scheduler state; user-facing queue_alloca APIs must not
// surface this as a transient caller-visible branch.
IREE_HAL_POOL_ACQUIRE_OK_NEEDS_WAIT = 2,
// No blocks available. All blocks are allocated, or all free blocks have
// non-dominated death frontiers and the pool chose not to hand one out
// as NEEDS_WAIT.
//
// The caller should observe the pool's notification, retry the reservation,
// and wait on the observed token only if the pool remains exhausted. No
// reservation was made; |out_reservation| is not modified.
IREE_HAL_POOL_ACQUIRE_EXHAUSTED = 3,
// The pool's budget limit would be exceeded by this reservation. The
// request is valid and blocks may be physically available, but the budget
// policy prevents the allocation.
//
// The caller should free other reservations from this pool, adjust the
// budget, or use a different pool. No reservation was made;
// |out_reservation| is not modified.
IREE_HAL_POOL_ACQUIRE_OVER_BUDGET = 4,
};
// A reservation from a pool. Returned by
// iree_hal_pool_acquire_reservation() and passed to
// iree_hal_pool_release_reservation().
//
// This is a pure value type (32 bytes, no ownership). It lives on the stack
// during queue submission or is stored in the buffer that wraps it. The pool
// knows how to interpret its fields; callers treat it as opaque.
typedef struct iree_hal_pool_reservation_t {
// Offset within the pool's managed range.
iree_device_size_t offset;
// Actual allocated length in bytes. May exceed the requested length due to
// alignment rounding or because the block could not be split (the remainder
// would have been smaller than the minimum block size).
iree_device_size_t length;
// Pool-internal opaque handle for returning the block on release.
// Interpretation is strategy-specific: a TLSF release-node pointer,
// fixed-block block index, pass-through reservation-state pointer, etc.
// 64-bit to accommodate pointer-sized handles on all platforms.
uint64_t block_handle;
// Which slab within the pool (for multi-slab pools in slab mode).
// 0 for single-slab or VMM pools.
uint16_t slab_index;
// Reserved for future expansion. Must be zero.
uint16_t reserved[3];
} iree_hal_pool_reservation_t;
// Flags controlling pool reservation acquisition.
typedef uint32_t iree_hal_pool_reserve_flags_t;
enum iree_hal_pool_reserve_flag_bits_e {
IREE_HAL_POOL_RESERVE_FLAG_NONE = 0u,
// Allows the pool to return IREE_HAL_POOL_ACQUIRE_OK_NEEDS_WAIT when a
// recycled block is available but its death frontier is not dominated by the
// requester frontier. Callers setting this flag must either insert an
// internal dependency on out_info->wait_frontier before the bytes are used or
// release the reservation with out_info->wait_frontier to preserve the
// block's dependency metadata.
//
// Callers that cannot model queue-owned hidden memory dependencies must omit
// this flag. Such calls should receive only immediately-usable reservations
// or transient EXHAUSTED/OVER_BUDGET results from well-behaved pools.
IREE_HAL_POOL_RESERVE_FLAG_ALLOW_WAIT_FRONTIER = 1u << 0,
// Prevents growable pools from acquiring additional backing storage during
// this reservation attempt. Pools that could satisfy the request by growing
// should return IREE_HAL_POOL_ACQUIRE_EXHAUSTED with
// IREE_HAL_POOL_ACQUIRE_FLAG_GROWTH_REQUIRED instead of calling into their
// slab provider.
//
// Queue implementations use this inside critical sections so unbounded
// platform memory allocation is routed through an explicit cold path.
IREE_HAL_POOL_RESERVE_FLAG_DISALLOW_GROWTH = 1u << 1,
};
// Generic metadata flags returned by a pool reservation acquisition.
typedef uint32_t iree_hal_pool_acquire_flags_t;
enum iree_hal_pool_acquire_flag_bits_e {
IREE_HAL_POOL_ACQUIRE_FLAG_NONE = 0u,
// The returned wait frontier is tainted: at least one writer freed the block
// with no death frontier or an invalid frontier, so zero-sync reuse was
// intentionally disabled for safety. Queue implementations should treat this
// as a conservative dependency edge, not proof of precise happens-before.
IREE_HAL_POOL_ACQUIRE_FLAG_WAIT_FRONTIER_TAINTED = 1u << 0,
// The pool did not make a reservation because the caller prohibited growth
// and the request could only be satisfied by acquiring more backing storage.
IREE_HAL_POOL_ACQUIRE_FLAG_GROWTH_REQUIRED = 1u << 1,
};
// Generic metadata returned by a pool reservation acquisition.
//
// |wait_frontier| is a borrowed pointer to the selected block's death frontier
// when |out_result| is IREE_HAL_POOL_ACQUIRE_OK_NEEDS_WAIT. The pointer remains
// valid until the matching reservation is released. It is NULL for
// IREE_HAL_POOL_ACQUIRE_OK and IREE_HAL_POOL_ACQUIRE_OK_FRESH.
//
// If a caller declines an IREE_HAL_POOL_ACQUIRE_OK_NEEDS_WAIT reservation and
// immediately releases it, passing |wait_frontier| back to
// iree_hal_pool_release_reservation() must preserve the block's dependency
// metadata. Concrete pools must therefore tolerate |death_frontier| aliasing
// the reservation's own pool-owned frontier storage in that path.
typedef struct iree_hal_pool_acquire_info_t {
// Borrowed dependency frontier for IREE_HAL_POOL_ACQUIRE_OK_NEEDS_WAIT.
// NULL for success paths that require no wait.
const iree_async_frontier_t* wait_frontier;
// Generic metadata bits describing the selected reservation.
iree_hal_pool_acquire_flags_t flags;
uint32_t reserved;
} iree_hal_pool_acquire_info_t;
// Controls how a concrete buffer object/view is materialized for a reservation.
typedef uint32_t iree_hal_pool_materialize_flags_t;
enum iree_hal_pool_materialize_flag_bits_e {
IREE_HAL_POOL_MATERIALIZE_FLAG_NONE = 0u,
// Transfers reservation ownership to the returned buffer. When that buffer
// is destroyed its release callback must return |reservation| to |pool|
// with a NULL death frontier.
//
// Without this flag, the returned buffer is only a borrowed view of the
// reserved bytes and the caller remains responsible for calling
// iree_hal_pool_release_reservation() exactly once.
IREE_HAL_POOL_MATERIALIZE_FLAG_TRANSFER_RESERVATION_OWNERSHIP = 1u << 0,
};
// Describes the memory capabilities of a pool. Computed at pool creation time
// from the slab provider's properties and the pool's strategy constraints.
// Used by iree_hal_pool_set_t for routing allocation requests to compatible
// pools.
typedef struct iree_hal_pool_capabilities_t {
// Memory type properties provided by this pool's slab provider. Checked
// against the required bits in iree_hal_buffer_params_t.type.
iree_hal_memory_type_t memory_type;
// Buffer usages this pool supports. A pool backed by DEVICE_LOCAL memory
// that isn't host-visible can't serve MAPPING usage.
iree_hal_buffer_usage_t supported_usage;
// Minimum allocation size in bytes. Fixed-block pools use their block size.
// Suballocating pools may round internally and report 0 or 1 when they have
// no practical lower bound.
iree_device_size_t min_allocation_size;
// Strategy-specific maximum single reservation in bytes. Fixed-block pools
// use their block size, TLSF pools use their slab size, and pass-through
// pools report 0 for no strategy limit. Budgets are reported separately and
// enforced by acquire_reservation().
iree_device_size_t max_allocation_size;
} iree_hal_pool_capabilities_t;
// Running statistics for a pool. All values are atomic snapshots; they may
// be momentarily inconsistent under concurrent modifications. Querying stats
// is O(1) with no internal walks or locks.
typedef struct iree_hal_pool_stats_t {
// Total bytes currently occupied by live reservations.
iree_device_size_t bytes_reserved;
// Total bytes in free blocks or available for reservation.
iree_device_size_t bytes_free;
// Total physical memory committed (slabs or VMM pages).
iree_device_size_t bytes_committed;
// Budget limit in bytes (0 = unlimited).
iree_device_size_t budget_limit;
// Number of currently live reservations.
uint32_t reservation_count;
// Number of slabs from the slab provider.
uint32_t slab_count;
// Total successful acquire_reservation() calls.
uint64_t reserve_count;
// Total release_reservation() calls.
uint64_t release_count;
// Reserves that hit frontier-dominated reuse.
uint64_t reuse_count;
// Reserves where the dominance check failed.
uint64_t reuse_miss_count;
// Reserves from fresh, never-used offset space.
uint64_t fresh_count;
// Reserves that returned EXHAUSTED.
uint64_t exhausted_count;
// Reserves that returned OVER_BUDGET.
uint64_t over_budget_count;
// Reserves that returned NEEDS_WAIT.
uint64_t wait_count;
} iree_hal_pool_stats_t;
// Callback for try-before-fence epoch queries. When a death-frontier dominance
// check fails in acquire_reservation(), the pool calls this to check whether
// the timeline has actually advanced past the death frontier's epoch on a
// specific axis, even though the requester's frontier hasn't imported the
// update yet.
//
// Returns true if |axis| has reached at least |epoch| (the work has
// completed). This is a host-side read of the semaphore's current value;
// no device interaction, no blocking. The pool uses this to avoid
// unnecessarily skipping reusable blocks when completion notifications are
// batched by the proactor.
//
// Set at pool creation time. Pools created with |epoch_query.fn| == NULL skip
// the try-before-fence optimization; non-dominated blocks are treated as
// genuinely unavailable for zero-sync reuse.
typedef bool(IREE_API_PTR* iree_hal_pool_epoch_query_fn_t)(
void* user_data, iree_async_axis_t axis, uint64_t epoch);
// Bound epoch query callback and user data.
typedef struct iree_hal_pool_epoch_query_t {
// Callback used to query whether an axis has reached an epoch.
iree_hal_pool_epoch_query_fn_t fn;
// User data passed to |fn|.
void* user_data;
} iree_hal_pool_epoch_query_t;
// Returns a null epoch query callback.
static inline iree_hal_pool_epoch_query_t iree_hal_pool_epoch_query_null(void) {
iree_hal_pool_epoch_query_t query = {NULL, NULL};
return query;
}
//===----------------------------------------------------------------------===//
// iree_hal_pool_t
//===----------------------------------------------------------------------===//
// A memory pool that manages a region of offset space backed by one or more
// slabs of physical memory. Pools are the primary allocation interface in IREE:
// both synchronous (allocate_buffer) and asynchronous (queue_alloca/dealloca)
// allocation paths go through pools.
//
// Pools are ref-counted HAL resources, but allocations/wrapped buffers borrow
// their source pool instead of retaining it. Pool owners must ensure a pool
// outlives every reservation and buffer allocated from it. That keeps
// queue_alloca/dealloca hot paths free of per-allocation pool refcount traffic
// and matches the intended "application-scoped allocation policy" lifetime
// model.
//
// Pools can be shared across queues and threads (concurrency is handled
// internally per pool type).
//
// The pool base type is abstract; concrete pools are created by type-specific
// factory functions (iree_hal_tlsf_pool_create,
// iree_hal_fixed_block_pool_create, etc.) and used through this common
// interface.
//
// ## Allocation protocol
//
// Asynchronous (queue-ordered) allocation:
//
// submit queue_alloca use bytes submit queue_dealloca
// ┌─────────────────────┐ ┌────────────────┐ ┌──────────────────────┐
// │ acquire_reservation │─────▶│ borrowed view │─────▶│ release_reservation │
// │ checks death edge │ │ no pool retain │ │ records death edge │
// └─────────────────────┘ └────────────────┘ └──────────────────────┘
//
// 1. acquire_reservation() at submit time: finds a free block, checks death
// frontier dominance, returns a reservation with offset and length.
// 2. materialize_reservation() without ownership transfer: creates a
// backing buffer view whose lifetime is independent from the reservation.
// 3. release_reservation() at dealloca submit time: returns the block to
// the pool's free list, tagged with a death frontier.
//
// Synchronous allocation:
// iree_hal_pool_allocate_buffer(): calls acquire_reservation() +
// materialize_reservation(TRANSFER_RESERVATION_OWNERSHIP) in a loop, waiting
// on the pool's notification if exhausted. This is a shared utility, not a
// vtable method.
//
// ## Death frontier integration
//
// Free blocks carry death frontiers: causal snapshots from when they were
// last freed. Reserve checks whether the requester's frontier dominates the
// death frontier for zero-sync reuse. This enables buffer recycling without
// any device synchronization in steady-state pipelined workloads.
typedef struct iree_hal_pool_t iree_hal_pool_t;
// Retains the given |pool| for the caller.
IREE_API_EXPORT void iree_hal_pool_retain(iree_hal_pool_t* pool);
// Releases the given |pool| from the caller.
IREE_API_EXPORT void iree_hal_pool_release(iree_hal_pool_t* pool);
// Acquires a reservation from the pool for a future allocation.
//
// |size| is the minimum number of bytes needed. |alignment| is the required
// alignment for the returned offset (must be a power of two and > 0). The
// actual allocated length may exceed |size| due to alignment rounding or
// block splitting constraints.
//
// |requester_frontier| is the queue scheduler's current causal position, used
// for death frontier dominance checking. Pass NULL to skip dominance checking
// (appropriate for synchronous allocations that don't participate in
// queue-ordered frontier tracking).
//
// |flags| controls whether the caller can accept queue-owned dependency work
// as part of the reservation. In particular, callers must set
// IREE_HAL_POOL_RESERVE_FLAG_ALLOW_WAIT_FRONTIER before a pool may return
// IREE_HAL_POOL_ACQUIRE_OK_NEEDS_WAIT.
//
// On success (iree_ok_status()), |out_result| indicates the specific outcome:
// OK / OK_FRESH: reservation succeeded, memory is safe for immediate use.
// OK_NEEDS_WAIT: reservation succeeded, but the queue scheduler must add a
// hidden wait on |out_info->wait_frontier| before the reservation's bytes
// are used.
// EXHAUSTED: no reservation made, pool has no suitable blocks.
// OVER_BUDGET: no reservation made, budget limit would be exceeded.
//
// |out_info| is always initialized on success. For EXHAUSTED/OVER_BUDGET it is
// set to an empty record. For OK_NEEDS_WAIT, |out_info->wait_frontier| is
// borrowed pool storage owned by the reservation and remains valid until the
// reservation is released.
//
// Returns an error status (with backtrace) only for infrastructure failures:
// invalid arguments (size 0, non-power-of-two alignment), internal
// corruption, or slab provider errors. These are exceptional.
IREE_API_EXPORT iree_status_t iree_hal_pool_acquire_reservation(
iree_hal_pool_t* pool, iree_device_size_t size,
iree_device_size_t alignment,
const iree_async_frontier_t* requester_frontier,
iree_hal_pool_reserve_flags_t flags,
iree_hal_pool_reservation_t* out_reservation,
iree_hal_pool_acquire_info_t* out_info,
iree_hal_pool_acquire_result_t* out_result);
// Releases a reservation back to the pool's free list.
//
// |reservation| is the reservation returned by a prior
// iree_hal_pool_acquire_reservation() call on this pool. |death_frontier| is
// the causal snapshot to attach to the freed block; typically the queue's
// frontier at dealloca submit time. Pass NULL for an empty frontier (the block
// is immediately available for zero-sync reuse by any requester).
//
// The reservation's offset is returned to the pool's free list immediately.
// The memory is available for future acquire_reservation() calls from that
// point forward, even though the GPU may still be executing prior work;
// death frontier dominance checking gates actual reuse safety.
//
// Publishes the pool's notification epoch. Releases that occur with no known
// waiter may skip platform wake work; waiters use an observe-check-wait
// protocol so this cannot lose wakeups.
IREE_API_EXPORT void iree_hal_pool_release_reservation(
iree_hal_pool_t* pool, const iree_hal_pool_reservation_t* reservation,
const iree_async_frontier_t* death_frontier);
// Materializes a concrete buffer object/view for a reservation from this pool.
//
// |params| describes the buffer's usage, access, and memory type properties.
// |reservation| is the reservation returned by
// iree_hal_pool_acquire_reservation().
//
// If |flags| includes
// IREE_HAL_POOL_MATERIALIZE_FLAG_TRANSFER_RESERVATION_OWNERSHIP, the returned
// buffer stores the reservation and a borrowed pointer to |pool| and releases
// that reservation with a NULL death frontier when destroyed.
//
// Otherwise the returned buffer is only a borrowed view and the caller keeps
// ownership of |reservation|. This is the queue-allocation path's backing
// materialization primitive: a transient wrapper can commit/decommit the
// borrowed view while releasing the reservation independently at a
// queue-ordered dealloca point.
//
// |pool| must outlive both the reservation and the returned buffer.
//
// The concrete pool owns reservation bookkeeping and release callbacks, but
// provider-specific buffer materialization must flow through that pool's slab
// provider. Generic pools must not dereference slab payload fields directly;
// they pass a slab plus offset range to iree_hal_slab_provider_wrap_buffer().
IREE_API_EXPORT iree_status_t iree_hal_pool_materialize_reservation(
iree_hal_pool_t* pool, iree_hal_buffer_params_t params,
const iree_hal_pool_reservation_t* reservation,
iree_hal_pool_materialize_flags_t flags, iree_hal_buffer_t** out_buffer);
// Queries the memory capabilities of the pool. O(1); reads cached fields
// computed at pool creation time.
IREE_API_EXPORT void iree_hal_pool_query_capabilities(
const iree_hal_pool_t* pool,
iree_hal_pool_capabilities_t* out_capabilities);
// Queries the pool's running statistics. O(1); atomic snapshots of
// incrementally maintained counters. Values may be momentarily inconsistent
// under concurrent modifications.
IREE_API_EXPORT void iree_hal_pool_query_stats(
const iree_hal_pool_t* pool, iree_hal_pool_stats_t* out_stats);
// Releases unused physical memory back to the slab provider.
// VMM mode: decommit pages with no live reservations.
// Slab mode: free slabs with no live reservations.
// The pool remains valid after trimming; it can grow again on demand.
IREE_API_EXPORT iree_status_t iree_hal_pool_trim(iree_hal_pool_t* pool);
// Returns the pool's notification. Callers waiting for blocks to become
// available can use this to sleep efficiently instead of polling.
//
// The notification is advisory over pool state. Callers must observe the
// notification epoch before checking the pool state, and then wait on that
// token only if the checked state still requires a wakeup. Releases may skip
// platform wake work when no wait observer exists.
IREE_API_EXPORT iree_async_notification_t* iree_hal_pool_notification(
iree_hal_pool_t* pool);
// Allocates a buffer from the pool synchronously.
//
// This is a shared utility (NOT a vtable method) that calls
// acquire_reservation() + materialize_reservation() in a loop. If
// acquire_reservation() returns EXHAUSTED or OVER_BUDGET, the function waits
// on the pool's notification for |timeout| and retries.
//
// |requester_frontier| is passed to acquire_reservation() for dominance
// checking. Pass NULL to skip dominance checking (appropriate for persistent
// buffers that aren't queue-ordered).
//
// This helper is synchronous-only. Queue implementations must not call it for
// queue_alloca, because queue-owned memory-frontier waits and pool-notification
// retries are scheduler state, not host-thread blocking in this helper.
//
// |timeout| controls how long to wait for a free block. Converted to an
// absolute deadline internally so retries after spurious wakes use a
// consistent cutoff:
// iree_make_timeout_ms(0): try once, fail immediately if exhausted.
// iree_infinite_timeout(): block until a block becomes available.
// iree_make_timeout_ms(N): wait up to N milliseconds.
//
// Returns IREE_STATUS_DEADLINE_EXCEEDED if |timeout| is reached before an
// immediately-usable reservation can be acquired. This uses iree_make_status()
// because it represents a terminal failure visible to the application, not a
// transient hot-path condition.
IREE_API_EXPORT iree_status_t iree_hal_pool_allocate_buffer(
iree_hal_pool_t* pool, iree_hal_buffer_params_t params,
iree_device_size_t allocation_size,
const iree_async_frontier_t* requester_frontier, iree_timeout_t timeout,
iree_hal_buffer_t** out_buffer);
//===----------------------------------------------------------------------===//
// iree_hal_pool_t implementation details
//===----------------------------------------------------------------------===//
typedef struct iree_hal_pool_vtable_t {
// Destroys a concrete pool implementation.
void(IREE_API_PTR* destroy)(iree_hal_pool_t* pool);
// Acquires reservation metadata from the concrete pool implementation.
iree_status_t(IREE_API_PTR* acquire_reservation)(
iree_hal_pool_t* pool, iree_device_size_t size,
iree_device_size_t alignment,
const iree_async_frontier_t* requester_frontier,
iree_hal_pool_reserve_flags_t flags,
iree_hal_pool_reservation_t* out_reservation,
iree_hal_pool_acquire_info_t* out_info,
iree_hal_pool_acquire_result_t* out_result);
// Releases reservation metadata back to the concrete pool implementation.
void(IREE_API_PTR* release_reservation)(
iree_hal_pool_t* pool, const iree_hal_pool_reservation_t* reservation,
const iree_async_frontier_t* death_frontier);
// Materializes a concrete buffer object or view for a reservation.
iree_status_t(IREE_API_PTR* materialize_reservation)(
iree_hal_pool_t* pool, iree_hal_buffer_params_t params,
const iree_hal_pool_reservation_t* reservation,
iree_hal_pool_materialize_flags_t flags, iree_hal_buffer_t** out_buffer);
// Queries cached memory capabilities for routing.
void(IREE_API_PTR* query_capabilities)(
const iree_hal_pool_t* pool,
iree_hal_pool_capabilities_t* out_capabilities);
// Queries running pool statistics.
void(IREE_API_PTR* query_stats)(const iree_hal_pool_t* pool,
iree_hal_pool_stats_t* out_stats);
// Releases unused physical memory back to the concrete provider.
iree_status_t(IREE_API_PTR* trim)(iree_hal_pool_t* pool);
// Returns the notification used for pool availability changes.
iree_async_notification_t*(IREE_API_PTR* notification)(iree_hal_pool_t* pool);
} iree_hal_pool_vtable_t;
IREE_HAL_ASSERT_VTABLE_LAYOUT(iree_hal_pool_vtable_t);
IREE_API_EXPORT void iree_hal_pool_destroy(iree_hal_pool_t* pool);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // IREE_HAL_POOL_H_