| // Copyright 2023 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/local/executable_plugin_manager.h" |
| |
| #include "iree/base/threading/mutex.h" |
| |
| //===----------------------------------------------------------------------===// |
| // Plugin API compatibility checks |
| //===----------------------------------------------------------------------===// |
| |
| // Ensures that the copies/shims we have in the plugin API match what's in the |
| // compiler. If there are compilation failures here it means we've changed one |
| // side and not the other. Note that additions in the runtime are fine so long |
| // as they don't disturb the existing values exposed to the plugin. |
| |
| #define STATIC_ASSERT_EQ(a, b) \ |
| static_assert((int)(a) == (int)(b), "plugin/runtime API mismatch") |
| |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_OK, IREE_STATUS_OK); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_CANCELLED, |
| IREE_STATUS_CANCELLED); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_UNKNOWN, |
| IREE_STATUS_UNKNOWN); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_INVALID_ARGUMENT, |
| IREE_STATUS_INVALID_ARGUMENT); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_DEADLINE_EXCEEDED, |
| IREE_STATUS_DEADLINE_EXCEEDED); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_NOT_FOUND, |
| IREE_STATUS_NOT_FOUND); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_ALREADY_EXISTS, |
| IREE_STATUS_ALREADY_EXISTS); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_PERMISSION_DENIED, |
| IREE_STATUS_PERMISSION_DENIED); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_RESOURCE_EXHAUSTED, |
| IREE_STATUS_RESOURCE_EXHAUSTED); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_FAILED_PRECONDITION, |
| IREE_STATUS_FAILED_PRECONDITION); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_ABORTED, |
| IREE_STATUS_ABORTED); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_OUT_OF_RANGE, |
| IREE_STATUS_OUT_OF_RANGE); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_UNIMPLEMENTED, |
| IREE_STATUS_UNIMPLEMENTED); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_INTERNAL, |
| IREE_STATUS_INTERNAL); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_UNAVAILABLE, |
| IREE_STATUS_UNAVAILABLE); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_DATA_LOSS, |
| IREE_STATUS_DATA_LOSS); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_UNAUTHENTICATED, |
| IREE_STATUS_UNAUTHENTICATED); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_DEFERRED, |
| IREE_STATUS_DEFERRED); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_STATUS_CODE_MASK, |
| IREE_STATUS_CODE_MASK); |
| |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_MALLOC, |
| IREE_ALLOCATOR_COMMAND_MALLOC); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_CALLOC, |
| IREE_ALLOCATOR_COMMAND_CALLOC); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_REALLOC, |
| IREE_ALLOCATOR_COMMAND_REALLOC); |
| STATIC_ASSERT_EQ(IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_FREE, |
| IREE_ALLOCATOR_COMMAND_FREE); |
| |
| STATIC_ASSERT_EQ(sizeof(iree_hal_executable_plugin_allocator_alloc_params_t), |
| sizeof(iree_allocator_alloc_params_t)); |
| STATIC_ASSERT_EQ(sizeof(iree_hal_executable_plugin_allocator_t), |
| sizeof(iree_allocator_t)); |
| |
| STATIC_ASSERT_EQ(sizeof(iree_hal_executable_plugin_string_view_t), |
| sizeof(iree_string_view_t)); |
| STATIC_ASSERT_EQ(sizeof(iree_hal_executable_plugin_string_pair_t), |
| sizeof(iree_string_pair_t)); |
| |
| #undef STATIC_ASSERT_EQ |
| |
| //===----------------------------------------------------------------------===// |
| // iree_hal_executable_plugin_t |
| //===----------------------------------------------------------------------===// |
| |
| static iree_status_t iree_hal_executable_plugin_load( |
| const iree_hal_executable_plugin_header_t** header_ptr, |
| iree_host_size_t param_count, const iree_string_pair_t* params, |
| iree_allocator_t host_allocator, iree_hal_executable_plugin_t* plugin) { |
| // The header may be NULL if the plugin API used isn't compatible. |
| if (!header_ptr) { |
| return iree_make_status( |
| IREE_STATUS_FAILED_PRECONDITION, |
| "plugin does not support this version of the runtime (%08X)", |
| IREE_HAL_EXECUTABLE_PLUGIN_VERSION_LATEST); |
| } |
| plugin->library.header = header_ptr; |
| const iree_hal_executable_plugin_header_t* header = *plugin->library.header; |
| |
| plugin->identifier = iree_make_cstring_view(header->name); |
| |
| // Ensure features declared by the plugin are available/allowed. |
| #if !IREE_STATUS_MODE |
| if (iree_all_bits_set(header->features, |
| IREE_HAL_EXECUTABLE_PLUGIN_FEATURE_FULL_STATUS)) { |
| return iree_make_status( |
| IREE_STATUS_FAILED_PRECONDITION, |
| "plugin `%.*s` is compiled with the full iree_status_t but the runtime " |
| "is not; IREE_STATUS_MODE must be > 0", |
| (int)plugin->identifier.size, plugin->identifier.data); |
| } |
| #endif // !IREE_STATUS_MODE |
| |
| // Ensure that if the plugin is built for a particular sanitizer that we also |
| // were compiled with that sanitizer enabled. |
| switch (header->sanitizer) { |
| case IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_NONE: |
| // Always safe even if the host has a sanitizer enabled; it just means |
| // that we won't be able to catch anything from within the plugin, |
| // however checks outside will (often) still trigger when guard pages are |
| // dirtied/etc. |
| break; |
| #if defined(IREE_SANITIZER_ADDRESS) |
| case IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_ADDRESS: |
| // ASAN is compiled into the host and we can load this library. |
| break; |
| #else |
| case IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_ADDRESS: |
| return iree_make_status( |
| IREE_STATUS_UNAVAILABLE, |
| "plugin `%.*s` is compiled with ASAN support but the host " |
| "runtime is not compiled with it enabled; add -fsanitize=address to " |
| "the runtime compilation options", |
| (int)plugin->identifier.size, plugin->identifier.data); |
| #endif // IREE_SANITIZER_ADDRESS |
| #if defined(IREE_SANITIZER_THREAD) |
| case IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_THREAD: |
| // TSAN is compiled into the host and we can load this library. |
| break; |
| #else |
| case IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_THREAD: |
| return iree_make_status( |
| IREE_STATUS_UNAVAILABLE, |
| "plugin `%.*s` is compiled with TSAN support but the host " |
| "runtime is not compiled with it enabled; add -fsanitize=thread to " |
| "the runtime compilation options", |
| (int)plugin->identifier.size, plugin->identifier.data); |
| #endif // IREE_SANITIZER_THREAD |
| default: |
| return iree_make_status( |
| IREE_STATUS_UNAVAILABLE, |
| "plugin `%.*s` requires a sanitizer the host runtime is not " |
| "compiled to enable/understand: %u", |
| (int)plugin->identifier.size, plugin->identifier.data, |
| (uint32_t)header->sanitizer); |
| } |
| |
| // Everything a standalone plugin needs should be packed in here. |
| iree_hal_executable_plugin_environment_v0_t environment = { |
| .host_allocator = |
| { |
| .self = host_allocator.self, |
| .ctl = (iree_hal_executable_plugin_allocator_ctl_fn_t) |
| host_allocator.ctl, |
| }, |
| }; |
| |
| // Plugin is probably good - let's try loading it! It could fail for any |
| // reason and the caller will clean up. |
| return (iree_status_t)plugin->library.v0->load( |
| &environment, (size_t)param_count, |
| (const iree_hal_executable_plugin_string_pair_t*)params, &plugin->self); |
| } |
| |
| iree_status_t iree_hal_executable_plugin_initialize( |
| const void* vtable, iree_hal_executable_plugin_features_t required_features, |
| const iree_hal_executable_plugin_header_t** header_ptr, |
| iree_host_size_t param_count, const iree_string_pair_t* params, |
| iree_hal_executable_plugin_resolve_thunk_t resolve_thunk, |
| iree_allocator_t host_allocator, |
| iree_hal_executable_plugin_t* out_base_plugin) { |
| IREE_TRACE_ZONE_BEGIN(z0); |
| |
| // NOTE: if we fail below the caller will release us and will need these to be |
| // properly initialized. |
| iree_atomic_ref_count_init(&out_base_plugin->ref_count); |
| out_base_plugin->vtable = vtable; |
| memset(&out_base_plugin->library, 0, sizeof(out_base_plugin->library)); |
| out_base_plugin->self = NULL; |
| out_base_plugin->resolve_thunk = resolve_thunk; |
| |
| // Try to load the plugin; this may fail if the plugin is not supported |
| // (version, features, etc) or the plugin decides it doesn't like Tuesdays. |
| iree_status_t status = iree_hal_executable_plugin_load( |
| header_ptr, param_count, params, host_allocator, out_base_plugin); |
| if (iree_status_is_ok(status)) { |
| IREE_TRACE({ |
| const iree_hal_executable_plugin_header_t* header = |
| out_base_plugin->library.v0->header; |
| IREE_TRACE_ZONE_APPEND_TEXT(z0, header->name); |
| IREE_TRACE_ZONE_APPEND_TEXT(z0, header->description); |
| }); |
| } |
| |
| IREE_TRACE_ZONE_END(z0); |
| return status; |
| } |
| |
| void iree_hal_executable_plugin_destroy(iree_hal_executable_plugin_t* plugin) { |
| IREE_ASSERT_ARGUMENT(plugin); |
| IREE_TRACE_ZONE_BEGIN(z0); |
| IREE_TRACE_ZONE_APPEND_TEXT(z0, plugin->identifier.data, |
| plugin->identifier.size); |
| |
| // Unload the plugin, if it has an unload method. |
| if (plugin->library.v0 && plugin->library.v0->unload) { |
| plugin->library.v0->unload(plugin->self); |
| } |
| memset(&plugin->library, 0, sizeof(plugin->library)); |
| plugin->self = NULL; |
| |
| plugin->vtable->destroy(plugin); |
| |
| IREE_TRACE_ZONE_END(z0); |
| } |
| |
| void iree_hal_executable_plugin_retain(iree_hal_executable_plugin_t* plugin) { |
| if (IREE_LIKELY(plugin)) { |
| iree_atomic_ref_count_inc(&plugin->ref_count); |
| } |
| } |
| |
| void iree_hal_executable_plugin_release(iree_hal_executable_plugin_t* plugin) { |
| if (IREE_LIKELY(plugin) && |
| iree_atomic_ref_count_dec(&plugin->ref_count) == 1) { |
| iree_hal_executable_plugin_destroy(plugin); |
| } |
| } |
| |
| // NOTE: must match iree_hal_executable_import_provider_t.resolve. |
| static iree_status_t iree_hal_executable_plugin_resolve( |
| void* self, iree_host_size_t count, const char* const* symbol_names, |
| void** out_fn_ptrs, void** out_fn_contexts, |
| iree_hal_executable_import_resolution_t* out_resolution) { |
| IREE_ASSERT_ARGUMENT(self); |
| IREE_ASSERT_ARGUMENT(out_resolution); |
| *out_resolution = 0; |
| if (!count) return iree_ok_status(); |
| IREE_TRACE_ZONE_BEGIN(z0); |
| iree_hal_executable_plugin_t* plugin = (iree_hal_executable_plugin_t*)self; |
| IREE_TRACE_ZONE_APPEND_TEXT(z0, plugin->identifier.data, |
| plugin->identifier.size); |
| |
| const iree_hal_executable_plugin_resolve_params_v0_t params = { |
| .count = (size_t)count, |
| .symbol_names = symbol_names, |
| .out_fn_ptrs = out_fn_ptrs, |
| .out_fn_contexts = out_fn_contexts, |
| }; |
| iree_hal_executable_plugin_resolution_t resolution = 0; |
| iree_status_t status = |
| plugin->resolve_thunk |
| ? plugin->resolve_thunk(plugin->library.v0->resolve, plugin->self, |
| ¶ms, &resolution) |
| : (iree_status_t)plugin->library.v0->resolve(plugin->self, ¶ms, |
| &resolution); |
| *out_resolution = (iree_hal_executable_import_resolution_t)resolution; |
| |
| IREE_TRACE_ZONE_END(z0); |
| return status; |
| } |
| |
| iree_hal_executable_import_provider_t iree_hal_executable_plugin_provider( |
| iree_hal_executable_plugin_t* plugin) { |
| IREE_ASSERT_ARGUMENT(plugin); |
| return (iree_hal_executable_import_provider_t){ |
| .self = plugin, |
| .resolve = iree_hal_executable_plugin_resolve, |
| }; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Default registration hook |
| //===----------------------------------------------------------------------===// |
| |
| #if defined(IREE_HAL_EXECUTABLE_PLUGIN_REGISTRATION_FN) |
| |
| // Defined by the user and linked in to the binary: |
| extern iree_status_t IREE_HAL_EXECUTABLE_PLUGIN_REGISTRATION_FN( |
| iree_hal_executable_plugin_manager_t* manager, |
| iree_allocator_t host_allocator); |
| |
| static iree_status_t iree_hal_executable_plugin_manager_register_external( |
| iree_hal_executable_plugin_manager_t* manager, |
| iree_allocator_t host_allocator) { |
| IREE_TRACE_ZONE_BEGIN(z0); |
| iree_status_t status = |
| IREE_HAL_EXECUTABLE_PLUGIN_REGISTRATION_FN(manager, host_allocator); |
| IREE_TRACE_ZONE_END(z0); |
| return status; |
| } |
| |
| #else |
| |
| static iree_status_t iree_hal_executable_plugin_manager_register_external( |
| iree_hal_executable_plugin_manager_t* manager, |
| iree_allocator_t host_allocator) { |
| return iree_ok_status(); |
| } |
| |
| #endif // IREE_HAL_EXECUTABLE_PLUGIN_REGISTRATION_FN |
| |
| //===----------------------------------------------------------------------===// |
| // iree_hal_executable_plugin_manager_t |
| //===----------------------------------------------------------------------===// |
| |
| struct iree_hal_executable_plugin_manager_t { |
| iree_atomic_ref_count_t ref_count; |
| iree_allocator_t host_allocator; |
| |
| // Total number of available provider and plugin slots. |
| int32_t capacity; |
| |
| // Guards plugin/provider list mutation. |
| // We always hold a mutex around mutation but still allow provider reads. This |
| // is safe because we only ever append to the provider list and if a thread |
| // comes in and reads an old count while a new provider is being added it will |
| // just miss the provider being added. We go through this trouble because we |
| // can't hold a manager lock during resolution as if the import plugins are |
| // expensive (JITing code, etc) they'd block the whole process. |
| iree_slim_mutex_t mutex; |
| |
| // List of registered plugins. This may be a subset of the providers as not |
| // all providers need a plugin backing them. We only use this to keep the |
| // plugins live for the lifetime of the manager. |
| int32_t plugin_count; |
| iree_hal_executable_plugin_t** plugins; |
| |
| // List of available providers used during resolution. |
| // Providers are scanned in reverse registration order and include both |
| // unowned/external providers and ones backed by plugins owned by the |
| // manager. Note that threads may read this while the provider list is being |
| // appended. |
| iree_atomic_int32_t provider_count; |
| iree_hal_executable_import_provider_t providers[]; |
| }; |
| |
| iree_status_t iree_hal_executable_plugin_manager_create( |
| iree_host_size_t capacity, iree_allocator_t host_allocator, |
| iree_hal_executable_plugin_manager_t** out_manager) { |
| IREE_ASSERT_ARGUMENT(out_manager); |
| *out_manager = NULL; |
| IREE_TRACE_ZONE_BEGIN(z0); |
| |
| iree_hal_executable_plugin_manager_t* manager = NULL; |
| iree_host_size_t plugins_offset = iree_host_align( |
| sizeof(*manager) + capacity * sizeof(manager->providers[0]), |
| iree_max_align_t); |
| iree_host_size_t total_size = |
| plugins_offset + capacity * sizeof(*manager->plugins); |
| IREE_RETURN_AND_END_ZONE_IF_ERROR( |
| z0, iree_allocator_malloc(host_allocator, total_size, (void**)&manager)); |
| iree_atomic_ref_count_init(&manager->ref_count); |
| manager->host_allocator = host_allocator; |
| manager->capacity = capacity; |
| iree_slim_mutex_initialize(&manager->mutex); |
| manager->plugin_count = 0; |
| manager->plugins = |
| (iree_hal_executable_plugin_t**)((uintptr_t)manager + plugins_offset); |
| |
| // Register any externally-defined plugins by default. Dynamically registered |
| // plugins can override these if they are registered later on. |
| iree_status_t status = iree_hal_executable_plugin_manager_register_external( |
| manager, host_allocator); |
| |
| if (iree_status_is_ok(status)) { |
| *out_manager = manager; |
| } else { |
| iree_hal_executable_plugin_manager_release(manager); |
| } |
| IREE_TRACE_ZONE_END(z0); |
| return status; |
| } |
| |
| static void iree_hal_executable_plugin_manager_destroy( |
| iree_hal_executable_plugin_manager_t* manager) { |
| iree_allocator_t host_allocator = manager->host_allocator; |
| IREE_TRACE_ZONE_BEGIN(z0); |
| |
| // Release in reverse registration order; likely not required but makes for |
| // easier debugging of stateful providers (JITs and such that may have lots of |
| // memory/etc we want to track). |
| for (int32_t i = manager->plugin_count - 1; i >= 0; --i) { |
| iree_hal_executable_plugin_release(manager->plugins[i]); |
| } |
| |
| iree_slim_mutex_deinitialize(&manager->mutex); |
| iree_allocator_free(host_allocator, manager); |
| |
| IREE_TRACE_ZONE_END(z0); |
| } |
| |
| void iree_hal_executable_plugin_manager_retain( |
| iree_hal_executable_plugin_manager_t* manager) { |
| if (IREE_LIKELY(manager)) { |
| iree_atomic_ref_count_inc(&manager->ref_count); |
| } |
| } |
| |
| void iree_hal_executable_plugin_manager_release( |
| iree_hal_executable_plugin_manager_t* manager) { |
| if (IREE_LIKELY(manager) && |
| iree_atomic_ref_count_dec(&manager->ref_count) == 1) { |
| iree_hal_executable_plugin_manager_destroy(manager); |
| } |
| } |
| |
| // Registers a |provider| and optional |plugin| as an atomic operation. |
| static iree_status_t iree_hal_executable_plugin_manager_register( |
| iree_hal_executable_plugin_manager_t* manager, |
| iree_hal_executable_import_provider_t provider, |
| iree_hal_executable_plugin_t* plugin) { |
| IREE_ASSERT_ARGUMENT(manager); |
| if (provider.resolve == NULL) { |
| // No-op provider; may happen on accident. |
| return iree_ok_status(); |
| } |
| |
| // Hold the mutex to block other writers - readers are fine, though, as |
| // they'll just get stale data. |
| iree_slim_mutex_lock(&manager->mutex); |
| |
| // Get the next provider slot. Note that we don't yet increment it as we need |
| // to put the provider in there first. |
| int32_t slot = |
| iree_atomic_load(&manager->provider_count, iree_memory_order_acquire); |
| if (slot >= manager->capacity) { |
| iree_slim_mutex_unlock(&manager->mutex); |
| return iree_make_status(IREE_STATUS_RESOURCE_EXHAUSTED, |
| "import manager capacity of %d reached", |
| manager->capacity); |
| } |
| |
| // Stash the provider and the plugin, if any. |
| manager->providers[slot] = provider; |
| if (plugin) { |
| iree_hal_executable_plugin_retain(plugin); |
| manager->plugins[manager->plugin_count++] = plugin; |
| } |
| |
| // Mark the slot as valid now that the provider is in it. |
| iree_atomic_fetch_add(&manager->provider_count, 1, iree_memory_order_release); |
| |
| iree_slim_mutex_unlock(&manager->mutex); |
| return iree_ok_status(); |
| } |
| |
| iree_status_t iree_hal_executable_plugin_manager_register_provider( |
| iree_hal_executable_plugin_manager_t* manager, |
| iree_hal_executable_import_provider_t provider) { |
| return iree_hal_executable_plugin_manager_register(manager, provider, NULL); |
| } |
| |
| iree_status_t iree_hal_executable_plugin_manager_register_plugin( |
| iree_hal_executable_plugin_manager_t* manager, |
| iree_hal_executable_plugin_t* plugin) { |
| IREE_ASSERT_ARGUMENT(plugin); |
| return iree_hal_executable_plugin_manager_register( |
| manager, iree_hal_executable_plugin_provider(plugin), plugin); |
| } |
| |
| // Resolves |count| imports given |symbol_names| and stores pointers to their |
| // implementation in |out_fn_ptrs| and optional contexts in |out_fn_contexts|. |
| // |
| // A symbol name starting with `?` indicates that the symbol is optional and is |
| // allowed to be resolved to NULL. Such cases will always return OK but set the |
| // IREE_HAL_EXECUTABLE_IMPORT_RESOLUTION_MISSING_OPTIONAL resolution bit. |
| // |
| // Any already resolved function pointers will be skipped and left unmodified. |
| // When there's only partial availability of required imports any available |
| // ones will still be populated and NOT_FOUND will is returned. This allows for |
| // looping over multiple providers to populate what they can and only fails out |
| // if all providers return NOT_FOUND for a required import. |
| // |
| // Symbol names must be sorted alphabetically so if we cared we could use this |
| // information to more efficiently resolve the symbols from providers (O(n) |
| // walk vs potential O(nlogn)/O(n^2)). |
| // |
| // NOTE: this matches the iree_hal_executable_import_provider_t.resolve |
| // function signature so that it can be directly used as a provider. |
| static iree_status_t iree_hal_executable_plugin_manager_resolve( |
| iree_hal_executable_plugin_manager_t* manager, iree_host_size_t count, |
| const char* const* symbol_names, void** out_fn_ptrs, void** out_fn_contexts, |
| iree_hal_executable_import_resolution_t* out_resolution) { |
| if (!count) return iree_ok_status(); |
| IREE_ASSERT_ARGUMENT(manager); |
| IREE_ASSERT_ARGUMENT(out_fn_ptrs); |
| IREE_ASSERT_ARGUMENT(out_fn_contexts); |
| if (out_resolution) *out_resolution = 0; |
| IREE_TRACE_ZONE_BEGIN(z0); |
| IREE_TRACE_ZONE_APPEND_VALUE_I64(z0, count); |
| |
| // Fetch the valid provider count. |
| // This may end up missing providers that get registered during/after we scan |
| // but that's ok: multithreaded registration/resolution is non-deterministic |
| // by nature. Not holding the lock here means we allow multiple threads to |
| // resolve imports at the same time. |
| int32_t provider_count = |
| iree_atomic_load(&manager->provider_count, iree_memory_order_acquire); |
| |
| // Scan in reverse registration order so that more recently registered |
| // providers get queried first. try_resolve will populate any function |
| // pointers it can and ignore already populated ones (or be mean and overwrite |
| // them, but please don't!). After resolving if a provider can't resolve a |
| // symbol it will return NOT_FOUND but we only really care on the final |
| // provider in the scan. |
| bool all_required_resolved = false; |
| iree_hal_executable_import_resolution_t resolution = 0; |
| for (int32_t i = provider_count - 1; i >= 0; --i) { |
| iree_hal_executable_import_provider_t provider = manager->providers[i]; |
| IREE_ASSERT(provider.resolve); // checked on registration |
| iree_status_t provider_status = |
| provider.resolve(provider.self, count, symbol_names, out_fn_ptrs, |
| out_fn_contexts, &resolution); |
| if (iree_status_is_ok(provider_status)) { |
| // Found all required but may be missing some optional imports. If so |
| // we'll need to continue scanning. |
| all_required_resolved = true; |
| if (iree_all_bits_set( |
| resolution, |
| IREE_HAL_EXECUTABLE_IMPORT_RESOLUTION_MISSING_OPTIONAL)) { |
| continue; |
| } |
| // All required + optional found, end the scan early. |
| break; |
| } else if (iree_status_is_not_found(provider_status)) { |
| // One or more required symbols not found, keep scanning. |
| iree_status_ignore(provider_status); |
| continue; |
| } else { |
| // Other failure we need to propagate (may be JIT issues or something). |
| return provider_status; |
| } |
| } |
| |
| // If any required imports are missing we'll fail now with a listing. |
| // Note that some optional imports may not be resolved (check |resolution|). |
| iree_status_t status = iree_ok_status(); |
| if (IREE_UNLIKELY(!all_required_resolved)) { |
| #if IREE_STATUS_MODE |
| iree_host_size_t missing_count = 0; |
| iree_string_builder_t builder; |
| iree_string_builder_initialize(manager->host_allocator, &builder); |
| for (iree_host_size_t i = 0; i < count; ++i) { |
| if (out_fn_ptrs[i] != NULL) continue; |
| if (iree_hal_executable_import_is_optional(symbol_names[i])) continue; |
| if (missing_count > 0) { |
| IREE_IGNORE_ERROR( |
| iree_string_builder_append_string(&builder, IREE_SV(", "))); |
| } |
| IREE_IGNORE_ERROR( |
| iree_string_builder_append_cstring(&builder, symbol_names[i])); |
| ++missing_count; |
| } |
| status = iree_make_status( |
| IREE_STATUS_NOT_FOUND, |
| "missing %" PRIhsz " required executable imports: [%.*s]", |
| missing_count, (int)iree_string_builder_size(&builder), |
| iree_string_builder_buffer(&builder)); |
| iree_string_builder_deinitialize(&builder); |
| #else |
| status = iree_status_from_code(IREE_STATUS_NOT_FOUND); |
| #endif // IREE_STATUS_MODE |
| } |
| |
| if (out_resolution) *out_resolution = resolution; |
| IREE_TRACE_ZONE_END(z0); |
| return status; |
| } |
| |
| iree_hal_executable_import_provider_t |
| iree_hal_executable_plugin_manager_provider( |
| iree_hal_executable_plugin_manager_t* manager) { |
| return (iree_hal_executable_import_provider_t){ |
| .self = manager, |
| .resolve = manager ? (iree_hal_executable_import_provider_resolve_fn_t) |
| iree_hal_executable_plugin_manager_resolve |
| : NULL, |
| }; |
| } |