// 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,
                                  &params, &resolution)
          : (iree_status_t)plugin->library.v0->resolve(plugin->self, &params,
                                                       &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,
  };
}
