blob: 86ac02f300a19bf9d3970fd292ec8a4e7816e82b [file] [log] [blame]
// 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;
}