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