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

#ifndef IREE_HAL_LOCAL_EXECUTABLE_PLUGIN_H_
#define IREE_HAL_LOCAL_EXECUTABLE_PLUGIN_H_

//===----------------------------------------------------------------------===//
//                                                                            //
//    ██╗░░░██╗███╗░░██╗░██████╗████████╗░█████╗░██████╗░██╗░░░░░███████╗     //
//    ██║░░░██║████╗░██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║░░░░░██╔════╝     //
//    ██║░░░██║██╔██╗██║╚█████╗░░░░██║░░░███████║██████╦╝██║░░░░░█████╗░░     //
//    ██║░░░██║██║╚████║░╚═══██╗░░░██║░░░██╔══██║██╔══██╗██║░░░░░██╔══╝░░     //
//    ╚██████╔╝██║░╚███║██████╔╝░░░██║░░░██║░░██║██████╦╝███████╗███████╗     //
//    ░╚═════╝░╚═╝░░╚══╝╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝╚═════╝░╚══════╝╚══════╝     //
//                                                                            //
//===----------------------------------------------------------------------===//

//===----------------------------------------------------------------------===//
// iree_hal_executable_plugin_*_t
//===----------------------------------------------------------------------===//
// An interface providing lifetime management and import resolution for
// externally-defined executable library imports. Plugins can either be
// statically or dynamically linked into the runtime consuming them.
//
// Plugins only need to be used when lifetime management is required and
// otherwise users can register a much simpler import provider using
// iree_hal_executable_plugin_manager_register_provider. For plugins loaded
// from dynamic libraries, conditionally enabled, or needing lifetime management
// using this plugin API and iree_hal_executable_plugin_manager_register_plugin
// will ensure that import resolution is consistent and safe.
//
// Import resolution is intended to scale from function pointer lookup in .rdata
// to ahead-of-time JITs that may compile functions during resolution or return
// stubs that are JITed on-demand. See iree_hal_executable_plugin_v0_t::resolve
// for more information about the resolution process and how multiple plugins
// and import providers can resolve symbols.

// NOTE: this file is designed to be a standalone header: it is intended to be
// used by external projects that may build without the IREE source code.
// Include nothing but headers always available on all platforms/toolchains.
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif  // __cplusplus

//===----------------------------------------------------------------------===//
// Runtime feature support metadata
//===----------------------------------------------------------------------===//

// Defines a bitfield of features that the plugin requires or supports.
enum iree_hal_executable_plugin_feature_bits_t {
  IREE_HAL_EXECUTABLE_PLUGIN_FEATURE_NONE = 0u,

  // Plugin is built standalone and does not require any system facilities
  // beyond those provided by the plugin API.
  //
  // Nearly all plugins should be of this form in order to allow for portable
  // deployment and robust loading/execution; if a plugin is just a list of
  // pure functions there's very few ways it can fail at runtime.
  IREE_HAL_EXECUTABLE_PLUGIN_FEATURE_STANDALONE = 1u << 0,

  // Plugin is built against the IREE runtime with IREE_STATUS_MODE > 0 and
  // iree_hal_executable_plugin_status_t will pass iree_status_t objects instead
  // of just status codes. The hosting runtime loading the plugin must also be
  // compiled with IREE_STATUS_MODE > 0.
  IREE_HAL_EXECUTABLE_PLUGIN_FEATURE_FULL_STATUS = 1u << 1,
};
typedef uint32_t iree_hal_executable_plugin_features_t;

// Defines a set of supported sanitizers that plugins may be compiled with.
// The runtime uses this declaration to check as to whether the plugin is
// compatible with the hosting environment for cases where the sanitizer
// requires host support.
typedef enum iree_hal_executable_plugin_sanitizer_kind_e {
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_NONE = 0,
  // Indicates the plugin is compiled to use AddressSanitizer:
  // https://clang.llvm.org/docs/AddressSanitizer.html
  // Equivalent compiler flag: -fsanitize=address
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_ADDRESS = 1,
  // Indicates the plugin is compiled to use MemorySanitizer:
  // https://clang.llvm.org/docs/MemorySanitizer.html
  // Equivalent compiler flag: -fsanitize=memory
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_MEMORY = 2,
  // Indicates the plugin is compiled to use ThreadSanitizer:
  // https://clang.llvm.org/docs/ThreadSanitizer.html
  // Equivalent compiler flag: -fsanitize=thread
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_THREAD = 3,
  // Indicates the plugin is compiled to use UndefinedBehaviorSanitizer:
  // https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
  // Equivalent compiler flag: -fsanitize=undefined
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_UNDEFINED = 4,

  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_MAX_ENUM = INT32_MAX,
} iree_hal_executable_plugin_sanitizer_kind_t;

// The sanitizer kind currently enabled in the compilation unit. Can be used
// in plugins to initialize the sanitizer header field.
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
#define IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_KIND \
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_ADDRESS
#endif  // __has_feature(address_sanitizer)
#if __has_feature(memory_sanitizer)
#define IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_KIND \
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_MEMORY
#endif  // __has_feature(memory_sanitizer)
#if __has_feature(thread_sanitizer)
#define IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_KIND \
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_THREAD
#endif  // __has_feature(thread_sanitizer)
#endif  // defined(__has_feature)
#if !defined(IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_KIND)
#define IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_KIND \
  IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_NONE
#endif  // !IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_KIND

#if !defined(IREE_HOST_SIZE_T)
#define IREE_HOST_SIZE_T size_t
#endif  // !IREE_HOST_SIZE_T

// Size, in bytes, of a buffer on the local host.
typedef IREE_HOST_SIZE_T iree_host_size_t;

//===----------------------------------------------------------------------===//
// Versioning and interface querying
//===----------------------------------------------------------------------===//

// Version code indicating the minimum required runtime structures.
// Runtimes cannot load plugins with newer versions but may be able to load
// older versions if backward compatibility is enabled.
//
// NOTE: until we hit v1 the versioning scheme here is not set in stone.
// We may want to make this major release number, date codes (0x20220307),
// or some semantic versioning we track in whatever spec we end up having.
typedef uint32_t iree_hal_executable_plugin_version_t;

#define IREE_HAL_EXECUTABLE_PLUGIN_VERSION_0_1 0x00000001u

// The latest version of the plugin API; can be used to populate the
// iree_hal_executable_plugin_header_t::version when building plugins.
#define IREE_HAL_EXECUTABLE_PLUGIN_VERSION_LATEST \
  IREE_HAL_EXECUTABLE_PLUGIN_VERSION_0_1

// A header present at the top of all versions of the plugin API used by the
// runtime to ensure version compatibility.
typedef struct iree_hal_executable_plugin_header_t {
  // Version of the API this plugin was built with, which was likely the value
  // of IREE_HAL_EXECUTABLE_PLUGIN_VERSION_LATEST.
  iree_hal_executable_plugin_version_t version;

  // Name used for logging/diagnostics - should be C literal-like.
  const char* name;
  // Human-readable description used for logging/diagnostics.
  // Could contain configuration information (debug/release, features, etc),
  // implementation version, git commit sha of the build, etc.
  const char* description;

  // Bitfield of features required/supported by this plugin.
  iree_hal_executable_plugin_features_t features;

  // Which sanitizer the plugin is compiled to use, if any.
  // Plugins meant for use with a particular sanitizer will are only usable
  // with hosting code that is using the same sanitizer.
  iree_hal_executable_plugin_sanitizer_kind_t sanitizer;

  // Reserved for future use.
  uint64_t reserved[8];
} iree_hal_executable_plugin_header_t;

// Exported function from dynamic libraries for querying plugin information.
// This should be implemented as pure as possible and may be called many times
// while in process. This is only used to query for support, allow the plugin
// to perform version switching, and return the vtables the runtime needs to
// create and manage the plugin.
//
// The provided |max_version| is the maximum version the caller supports;
// callees must return NULL if their lowest available version is greater
// than the max version supported by the caller.
typedef const iree_hal_executable_plugin_header_t** (
    *iree_hal_executable_plugin_query_fn_t)(
    iree_hal_executable_plugin_version_t max_version, void* reserved);

// Function name exported from dynamic libraries (pass to dlsym).
#define IREE_HAL_EXECUTABLE_PLUGIN_EXPORT_NAME \
  "iree_hal_executable_plugin_query"

#if defined(_WIN32) || defined(__CYGWIN__)
#define IREE_HAL_EXECUTABLE_PLUGIN_EXPORT __declspec(dllexport)
#else
#define IREE_HAL_EXECUTABLE_PLUGIN_EXPORT __attribute__((visibility("default")))
#endif

//===----------------------------------------------------------------------===//
// iree_hal_executable_plugin_status_t
//===----------------------------------------------------------------------===//
//
// A lightweight shim of the minimal iree/base/status.h interface.
// This allows us to keep this header standalone for easy out-of-tree builds and
// version things separately in the future.
//
// Most simple plugins will likely be fine with numeric status codes but more
// complex ones may want the full status functionality in order to give back
// file/line, error messages, and annotations. If the plugin links against the
// IREE runtime it is allowed to return full iree_status_t objects across the
// boundary so long as the hosting runtime was compiled with IREE_STATUS_MODE >
// 0 (otherwise the runtime won't know how to deal with them). Plugins using
// this functionality must declare the feature bit
// IREE_HAL_EXECUTABLE_PLUGIN_FEATURE_FULL_STATUS so that the runtime can
// verify it is compiled in a compatible mode.

// Well-known status codes matching iree_status_code_t.
// Note that any code within IREE_HAL_EXECUTABLE_PLUGIN_STATUS_CODE_MASK is
// valid even if not enumerated here. Always check for unhandled errors/have
// default conditions.
typedef enum iree_hal_executable_plugin_status_code_e {
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_OK = 0,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_CANCELLED = 1,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_UNKNOWN = 2,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_INVALID_ARGUMENT = 3,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_DEADLINE_EXCEEDED = 4,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_NOT_FOUND = 5,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_ALREADY_EXISTS = 6,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_PERMISSION_DENIED = 7,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_RESOURCE_EXHAUSTED = 8,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_FAILED_PRECONDITION = 9,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_ABORTED = 10,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_OUT_OF_RANGE = 11,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_UNIMPLEMENTED = 12,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_INTERNAL = 13,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_UNAVAILABLE = 14,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_DATA_LOSS = 15,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_UNAUTHENTICATED = 16,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_DEFERRED = 17,
  IREE_HAL_EXECUTABLE_PLUGIN_STATUS_CODE_MASK = 0x1Fu,
} iree_hal_executable_plugin_status_code_t;

typedef struct iree_status_handle_t* iree_hal_executable_plugin_status_t;

#define iree_hal_executable_plugin_status_from_code(code)                                       \
  ((iree_hal_executable_plugin_status_t)((uintptr_t)((                                          \
                                             iree_hal_executable_plugin_status_code_t)(code)) & \
                                         IREE_HAL_EXECUTABLE_PLUGIN_STATUS_CODE_MASK))

#define iree_hal_executable_plugin_status_code(value)           \
  ((iree_hal_executable_plugin_status_t)(((uintptr_t)(value)) & \
                                         IREE_HAL_EXECUTABLE_PLUGIN_STATUS_CODE_MASK))

#define iree_hal_executable_plugin_ok_status() \
  iree_hal_executable_plugin_status_from_code( \
      IREE_HAL_EXECUTABLE_PLUGIN_STATUS_OK)

#define iree_hal_executable_plugin_status_is_ok(value) \
  ((uintptr_t)(value) == IREE_HAL_EXECUTABLE_PLUGIN_STATUS_OK)

//===----------------------------------------------------------------------===//
// iree_hal_executable_plugin_allocator_t
//===----------------------------------------------------------------------===//
//
// A lightweight shim of the iree/base/allocator.h interface.
// This allows us to keep this header standalone for easy out-of-tree builds and
// version things separately in the future.
//
// Plugins are expected to use the allocator for all requests such that the
// hosting runtime can track the allocations. Plugins built in systems that
// don't support custom allocators can do as they want but must not report
// IREE_HAL_EXECUTABLE_PLUGIN_FEATURE_STANDALONE as the plugin would be making
// syscalls in order to allocate/free memory from the underlying system.
//
// The iree_hal_executable_plugin_allocator_t struct is compatible with
// iree_allocator_t and can be used interchangeably.

typedef enum iree_hal_executable_plugin_allocator_command_e {
  IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_MALLOC = 0,
  IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_CALLOC = 1,
  IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_REALLOC = 2,
  IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_FREE = 3,
} iree_hal_executable_plugin_allocator_command_t;

typedef struct iree_hal_executable_plugin_allocator_alloc_params_t {
  iree_host_size_t byte_length;
} iree_hal_executable_plugin_allocator_alloc_params_t;

typedef iree_hal_executable_plugin_status_t (
    *iree_hal_executable_plugin_allocator_ctl_fn_t)(
    void* self, iree_hal_executable_plugin_allocator_command_t command,
    const void* params, void** inout_ptr);

typedef struct iree_hal_executable_plugin_allocator_t {
  void* self;
  iree_hal_executable_plugin_allocator_ctl_fn_t ctl;
} iree_hal_executable_plugin_allocator_t;

static inline iree_hal_executable_plugin_status_t
iree_hal_executable_plugin_allocator_malloc(
    iree_hal_executable_plugin_allocator_t allocator,
    iree_host_size_t byte_length, void** inout_ptr) {
  iree_hal_executable_plugin_allocator_alloc_params_t params = {byte_length};
  return allocator.ctl(allocator.self,
                       IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_MALLOC,
                       &params, inout_ptr);
}

static inline iree_hal_executable_plugin_status_t
iree_hal_executable_plugin_allocator_free(
    iree_hal_executable_plugin_allocator_t allocator, void* ptr) {
  return allocator.ctl(allocator.self,
                       IREE_HAL_EXECUTABLE_PLUGIN_ALLOCATOR_COMMAND_FREE,
                       /*params=*/NULL, &ptr);
}

//===----------------------------------------------------------------------===//
// iree_hal_executable_plugin_string_view_t
//===----------------------------------------------------------------------===//

// iree_string_view_t-compatible type.
// The string data may not be NUL terminated and the provided size (in
// characters) must be used.
typedef struct iree_hal_executable_plugin_string_view_t {
  const char* data;
  iree_host_size_t size;
} iree_hal_executable_plugin_string_view_t;

// iree_string_pair_t-compatible type.
typedef struct iree_hal_executable_plugin_string_pair_t {
  union {
    iree_hal_executable_plugin_string_view_t first;
    iree_hal_executable_plugin_string_view_t key;
  };
  union {
    iree_hal_executable_plugin_string_view_t second;
    iree_hal_executable_plugin_string_view_t value;
  };
} iree_hal_executable_plugin_string_pair_t;

//===----------------------------------------------------------------------===//
// Common utilities
//===----------------------------------------------------------------------===//

// Matches iree_hal_executable_import_resolution_bits_e.
// New bits may be added over time but the existing bits will not change.
enum iree_hal_executable_plugin_resolution_bits_e {
  // One or more missing optional symbols.
  IREE_HAL_EXECUTABLE_PLUGIN_RESOLUTION_MISSING_OPTIONAL = 1u << 0,
};
typedef uint32_t iree_hal_executable_plugin_resolution_t;

// Returns true if the import |symbol_name| is optional.
static inline bool iree_hal_executable_plugin_import_is_optional(
    const char* symbol_name) {
  // A `?` prefix indicates the symbol is optional and can be NULL.
  // Since the strings are NUL terminated we know there's always 1 char and
  // we can just test that for the prefix.
  return symbol_name ? (symbol_name[0] == '?') : false;
}

static inline int iree_hal_executable_plugin_strcmp(const char* lhs,
                                                    const char* rhs) {
  unsigned char lhsc = 0;
  unsigned char rhsc = 0;
  do {
    lhsc = (unsigned char)*lhs++;
    rhsc = (unsigned char)*rhs++;
    if (lhsc == '\0') return lhsc - rhsc;
  } while (lhsc == rhsc);
  return lhsc - rhsc;
}

//===----------------------------------------------------------------------===//
// IREE_HAL_EXECUTABLE_PLUGIN_VERSION_0_*
//===----------------------------------------------------------------------===//

// Environment provided to the plugin on load.
typedef struct iree_hal_executable_plugin_environment_v0_t {
  // Allocator to be used for all plugin allocations for the lifetime of the
  // plugin (such as those needed during resolution). The allocator will be
  // valid until the plugin is unloaded.
  iree_hal_executable_plugin_allocator_t host_allocator;
} iree_hal_executable_plugin_environment_v0_t;

typedef struct iree_hal_executable_plugin_resolve_params_v0_t {
  size_t count;
  const char* const* symbol_names;
  void** out_fn_ptrs;
  void** out_fn_contexts;
} iree_hal_executable_plugin_resolve_params_v0_t;

typedef iree_hal_executable_plugin_status_t (
    *iree_hal_executable_plugin_resolve_fn_v0_t)(
    void* self, const iree_hal_executable_plugin_resolve_params_v0_t* params,
    iree_hal_executable_plugin_resolution_t* out_resolution);

// Structure used for v0 plugin interfaces.
// The entire structure is designed to be read-only and able to live embedded in
// the binary .rdata section.
//
// Thread-safe: the plugin must be safe to load and resolve from multiple
// threads simultaneously. No unloads will be performed with active resolves.
typedef struct iree_hal_executable_plugin_v0_t {
  // Version/metadata header.
  // Will have a version of IREE_HAL_EXECUTABLE_PLUGIN_VERSION_*.
  const iree_hal_executable_plugin_header_t* header;

  // Loads a plugin by possibly allocating state, loading dependencies, and
  // preparing for resolution.
  //
  // The parameter strings are only available during the load and if the plugin
  // needs to retain any of them they must be cloned.
  //
  // The value specified in |out_self| will be passed to subsequent plugin
  // interface calls and may be NULL if the plugin has no state. As the same
  // plugin may be instantiated multiple times global state should be avoided
  // unless the
  iree_hal_executable_plugin_status_t (*load)(
      const iree_hal_executable_plugin_environment_v0_t* environment,
      size_t param_count,
      const iree_hal_executable_plugin_string_pair_t* params, void** out_self);

  // Unloads a plugin represented by |self|.
  // The plugin should free all resources as if in a shared library the hosting
  // runtime may immediately unload the library (dlclose/etc).
  void (*unload)(void* self);

  // Resolves |count| imports given |symbol_names| and stores pointers to their
  // implementation in |out_fn_ptrs| and optional contexts in |out_fn_contexts|.
  // The function contexts can be NULL, a pointer to shared state passed to each
  // import, unique state per function, or anything else. This allows JITs for
  // example to export a single function pointer and pack the information about
  // what to JIT in the context - or pass unique thunk functions for each import
  // and the shared JIT engine state for all contexts so that the thunk can
  // access it.
  //
  // 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_PLUGIN_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.
  //
  // The returned function pointers are the direct storage in the runtime.
  // Implementations are allowed to update their own entries (and _only_ their
  // entries) at runtime so long as they observe the thread-safety guarantees.
  // For example, a JIT may default all exports to JIT thunk functions and then
  // atomically swap them out for the translated function pointers as they are
  // available.
  //
  // 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)). For example if JITing many similar
  // ukernel variants (matmul_Ma_Na_Ka, matmul_Mb_Nb_Kb, etc) a resolver can
  // avoid big switch tables.
  iree_hal_executable_plugin_resolve_fn_v0_t resolve;
} iree_hal_executable_plugin_v0_t;

#ifdef __cplusplus
}  // extern "C"
#endif  // __cplusplus

#endif  // IREE_HAL_LOCAL_EXECUTABLE_PLUGIN_H_
