| // 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 | 
 |  | 
 | // Demonstrates a system linked plugin exporting a single `simple_mul_workgroup` | 
 | // function that also prints to stdout. This is not a great idea but shows how | 
 | // plugins can have side-effecting behavior - even if in most cases a standalone | 
 | // plugin can be used with much smaller code size and portability. | 
 | // | 
 | // The major use-case for a system linked plugin is JITs that may compile | 
 | // imports on-demand. Such plugins could either JIT everything on load time | 
 | // or defer JITting to the first call to a particular import. Performing JIT at | 
 | // load time is strongly preferred as it keeps all of the expensive work in one | 
 | // place before the program starts scheduling execution. Deferring will | 
 | // introduce first-run delays and require warmup steps. Since only the imports | 
 | // used by the program are present and most programs use all imports it's almost | 
 | // always going to be better to do things ahead of time. | 
 | // | 
 | // NOTE: when using the system loader all unsafe behavior is allowed: TLS, | 
 | // threads, mutable globals, syscalls, etc. Doing any of those things will | 
 | // likely break in interesting ways as the import functions are called from | 
 | // arbitrary threads concurrently. Be very careful and prefer standalone plugins | 
 | // instead except when debugging/profiling. | 
 |  | 
 | #include <inttypes.h> | 
 | #include <stdio.h> | 
 |  | 
 | // The only header required from IREE: | 
 | #include "iree/hal/local/executable_plugin.h" | 
 |  | 
 | // Stateful plugin instance. | 
 | // There may be multiple of these in a process at a time, each with its own | 
 | // load/unload pairing. We pass a pointer to this to all import calls via the | 
 | // context argument. | 
 | typedef struct { | 
 |   iree_hal_executable_plugin_allocator_t host_allocator; | 
 |   FILE* file; | 
 | } system_plugin_t; | 
 |  | 
 | // `ret = lhs * rhs` | 
 | // | 
 | // Conforms to ABI: | 
 | // #hal.pipeline.layout<constants = 1, bindings = [ | 
 | //   #hal.pipeline.binding<storage_buffer, ReadOnly>, | 
 | //   #hal.pipeline.binding<storage_buffer, ReadOnly>, | 
 | //   #hal.pipeline.binding<storage_buffer> | 
 | // ]> | 
 | // With a workgroup size of 64x1x1. | 
 | // | 
 | // |context| is whatever was set in out_fn_contexts. This could point to shared | 
 | // state or each import can have its own context (pointer into some JIT lookup | 
 | // table, etc). In this sample we pass the sample plugin pointer to all imports. | 
 | // | 
 | // |params_ptr| points to a packed struct of all results followed by all args | 
 | // using native arch packing/alignment rules. Results should be set before | 
 | // returning. | 
 | // | 
 | // Expects a return of 0 on success and any other value indicates failure. | 
 | // Try not to fail! | 
 | static int simple_mul_workgroup(void* params_ptr, void* context, | 
 |                                 void* reserved) { | 
 |   system_plugin_t* plugin = (system_plugin_t*)context; | 
 |   typedef struct { | 
 |     const float* restrict binding0; | 
 |     size_t binding0_offset; | 
 |     const float* restrict binding1; | 
 |     size_t binding1_offset; | 
 |     float* restrict binding2; | 
 |     size_t binding2_offset; | 
 |     size_t size; | 
 |     size_t tid; | 
 |     uint32_t processor_id; | 
 |     const uint64_t* restrict processor_data; | 
 |   } params_t; | 
 |   const params_t* params = (const params_t*)params_ptr; | 
 |   fprintf(plugin->file, "processor_id=%u\n", params->processor_id); | 
 |   if (params->processor_data) { | 
 |     fprintf(plugin->file, "processor_data[0]=%" PRIX64 "\n", | 
 |             params->processor_data[0]); | 
 |   } | 
 |   // The operation `iree_codegen.ukernel.generic` always operates | 
 |   // on a slice of the inputs to produce a slice of the output, | 
 |   // so the loop here just needs to iterate from `0` to `size`, | 
 |   // where `size` is the size of the slice to be executed by this call. | 
 |   for (size_t i = 0; i < params->size; ++i) { | 
 |     params->binding2[params->binding2_offset + i] = | 
 |         params->binding0[params->binding0_offset + i] * | 
 |         params->binding1[params->binding2_offset + i]; | 
 |     fprintf(plugin->file, "mul[%zu:%zu](%g * %g = %g)\n", params->tid, i, | 
 |             params->binding0[params->binding0_offset + i], | 
 |             params->binding1[params->binding1_offset + i], | 
 |             params->binding2[params->binding2_offset + i]); | 
 |   } | 
 |   return 0; | 
 | } | 
 |  | 
 | // Called once for each plugin load and paired with a future call to unload. | 
 | // Even in standalone mode we could allocate using environment->host_allocator, | 
 | // set an out_self pointer, and parse parameters but here in system mode we can | 
 | // do whatever we want. | 
 | // | 
 | // If any state is required it should be allocated and stored in |out_self|. | 
 | // This self value will be passed to all future calls related to the particular | 
 | // instance. Note that there may be multiple instances of a plugin in any | 
 | // particular process and this must be thread-safe. | 
 | static iree_hal_executable_plugin_status_t system_plugin_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) { | 
 |   // Allocate the plugin state. | 
 |   system_plugin_t* plugin = NULL; | 
 |   iree_hal_executable_plugin_status_t status = | 
 |       iree_hal_executable_plugin_allocator_malloc( | 
 |           environment->host_allocator, sizeof(*plugin), (void**)&plugin); | 
 |   if (status) return status; | 
 |   plugin->host_allocator = environment->host_allocator; | 
 |  | 
 |   // "Open standard out" simulating us doing some syscalls or other expensive | 
 |   // stateful/side-effecting things. | 
 |   plugin->file = stdout; | 
 |  | 
 |   // Pass back the plugin instance that'll be passed to resolve. | 
 |   *out_self = plugin; | 
 |   return iree_hal_executable_plugin_ok_status(); | 
 | } | 
 |  | 
 | // Called to free any plugin state allocated in load. | 
 | static void system_plugin_unload(void* self) { | 
 |   system_plugin_t* plugin = (system_plugin_t*)self; | 
 |   iree_hal_executable_plugin_allocator_t host_allocator = | 
 |       plugin->host_allocator; | 
 |  | 
 |   // "Close standard out" simulating us doing some syscalls and other expensive | 
 |   // stateful/side-effecting things. | 
 |   fflush(plugin->file); | 
 |   plugin->file = NULL; | 
 |  | 
 |   // Free the plugin state using the same allocator it came from. | 
 |   iree_hal_executable_plugin_allocator_free(host_allocator, plugin); | 
 | } | 
 |  | 
 | // Called to resolve one or more imports by symbol name. | 
 | // See the plugin API header for more information. Note that some of the | 
 | // functions may already be resolved and some may be optional. | 
 | static iree_hal_executable_plugin_status_t system_plugin_resolve( | 
 |     void* self, const iree_hal_executable_plugin_resolve_params_v0_t* params, | 
 |     iree_hal_executable_plugin_resolution_t* out_resolution) { | 
 |   system_plugin_t* plugin = (system_plugin_t*)self; | 
 |   *out_resolution = 0; | 
 |   bool any_required_not_found = false; | 
 |   for (size_t i = 0; i < params->count; ++i) { | 
 |     if (params->out_fn_ptrs[i]) continue; | 
 |     const char* symbol_name = params->symbol_names[i]; | 
 |     bool is_optional = | 
 |         iree_hal_executable_plugin_import_is_optional(symbol_name); | 
 |     if (is_optional) ++symbol_name; | 
 |     if (iree_hal_executable_plugin_strcmp(symbol_name, | 
 |                                           "simple_mul_workgroup") == 0) { | 
 |       params->out_fn_ptrs[i] = simple_mul_workgroup; | 
 |       params->out_fn_contexts[i] = | 
 |           plugin;  // passing plugin to each import call | 
 |     } else { | 
 |       if (is_optional) { | 
 |         *out_resolution |= | 
 |             IREE_HAL_EXECUTABLE_PLUGIN_RESOLUTION_MISSING_OPTIONAL; | 
 |       } else { | 
 |         any_required_not_found = true; | 
 |       } | 
 |     } | 
 |   } | 
 |   return any_required_not_found | 
 |              ? iree_hal_executable_plugin_status_from_code( | 
 |                    IREE_HAL_EXECUTABLE_PLUGIN_STATUS_NOT_FOUND) | 
 |              : iree_hal_executable_plugin_ok_status(); | 
 | } | 
 |  | 
 | // Exported on the shared library and used by the runtime to query the plugin | 
 | // interface. When statically linking the plugin this is just a function that | 
 | // can be called and can have any name to allow for multiple plugins. When | 
 | // dynamically linking the exported symbol must be exactly this with no C++ | 
 | // name mangling. | 
 | IREE_HAL_EXECUTABLE_PLUGIN_EXPORT const iree_hal_executable_plugin_header_t** | 
 | iree_hal_executable_plugin_query( | 
 |     iree_hal_executable_plugin_version_t max_version, void* reserved) { | 
 |   static const iree_hal_executable_plugin_header_t header = { | 
 |       // Declares what library version is present: newer runtimes may support | 
 |       // loading older plugins but newer plugins cannot load on older runtimes. | 
 |       .version = IREE_HAL_EXECUTABLE_PLUGIN_VERSION_LATEST, | 
 |       // Name and description are used for tracing/logging/diagnostics. | 
 |       .name = "sample_system", | 
 |       .description = | 
 |           "system plugin sample " | 
 |           "(custom_dispatch/cpu/plugin/system_plugin.c)", | 
 |       .features = 0, | 
 |       // Let the runtime know what sanitizer this plugin was compiled with. | 
 |       .sanitizer = IREE_HAL_EXECUTABLE_PLUGIN_SANITIZER_KIND, | 
 |   }; | 
 |   static const iree_hal_executable_plugin_v0_t plugin = { | 
 |       .header = &header, | 
 |       .load = system_plugin_load, | 
 |       .unload = system_plugin_unload, | 
 |       .resolve = system_plugin_resolve, | 
 |   }; | 
 |   return max_version <= IREE_HAL_EXECUTABLE_PLUGIN_VERSION_LATEST | 
 |              ? (const iree_hal_executable_plugin_header_t**)&plugin | 
 |              : NULL; | 
 | } |