| # Basic custom module sample | 
 |  | 
 | This sample shows how to | 
 |  | 
 | 1. Create a custom module in C++ that can be used with the IREE runtime | 
 | 2. Author an MLIR input that uses a custom module including a custom type | 
 | 3. Compile that program to an IREE VM bytecode module | 
 | 4. Load the compiled program using a low-level VM interface | 
 | 5. Call exported functions on the loaded program to exercise the custom module | 
 |  | 
 | The custom module is declared in [`module.h`](./module.h), implemented using a | 
 | C++ module wrapper layer in [`module.cc`](./module.cc), and called by example in | 
 | [`main.c`](./main.c). | 
 |  | 
 | This document uses terminology that can be found in the documentation of | 
 | [IREE's execution model](https://iree.dev/developers/design-docs/invocation-execution-model/). | 
 | See [IREE's extensibility mechanisms](https://iree.dev/reference/extensions/) | 
 | documentation for more information specific to extenting IREE and | 
 | alternative approaches to doing so. | 
 |  | 
 | ## Background | 
 |  | 
 | IREE's VM is used to dynamically link modules of various types together at | 
 | runtime (C, C++, IREE's VM bytecode, etc). Via this mechanism any number of | 
 | modules containing exported functions and types that can be used across modules | 
 | can extend IREE's base functionality. In most IREE programs the HAL module is | 
 | used to provide a hardware abstraction layer for execution and both the HAL | 
 | module itself and the types it exposes (`!hal.buffer`, `!hal.executable`, etc) | 
 | are implemented using this mechanism. | 
 |  | 
 | ## Instructions | 
 |  | 
 | 1. Build or install the `iree-compile` binary: | 
 |  | 
 |     ``` | 
 |     python -m pip install iree-compiler | 
 |     ``` | 
 |  | 
 |     [See here](https://iree.dev/reference/bindings/python/) | 
 |     for general instructions on installing the compiler. | 
 |  | 
 | 3. Compile the [example module](./test/example.mlir) to a .vmfb file: | 
 |  | 
 |     ``` | 
 |     # This simple sample doesn't use tensors and can be compiled in host-only | 
 |     # mode to avoid the need for the HAL. | 
 |     iree-compile --iree-execution-model=host-only samples/custom_module/basic/test/example.mlir -o=/tmp/example.vmfb | 
 |     ``` | 
 |  | 
 | 3. Build the `iree_samples_custom_module_run` CMake target : | 
 |  | 
 |     ``` | 
 |     cmake -B ../iree-build/ -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo . \ | 
 |         -DCMAKE_C_FLAGS=-DIREE_VM_EXECUTION_TRACING_FORCE_ENABLE=1 | 
 |     cmake --build ../iree-build/ --target iree_samples_custom_module_basic_run | 
 |     ``` | 
 |     (here we force runtime execution tracing for demonstration purposes) | 
 |  | 
 |     [See here](https://iree.dev/building-from-source/getting-started/) | 
 |     for general instructions on building using CMake. | 
 |  | 
 | 4. Run the example program to call the main function: | 
 |  | 
 |    ``` | 
 |    ../iree-build/samples/custom_module/basic/custom-module-basic-run \ | 
 |        /tmp/example.vmfb example.main | 
 |    ``` | 
 |  | 
 | ## Defining Custom Modules in C++ | 
 |  | 
 | Modules are exposed to applications and the IREE VM via the `iree_vm_module_t` | 
 | interface. IREE canonically uses C headers to expose module and type functions | 
 | but the implementation of the module can be anything the user is able to work | 
 | with (C, C++, rust, etc). | 
 |  | 
 | A C++ wrapper is provided to ease implementation when minimal code size and overhead is not a focus and provides easy definition of exports and marshaling | 
 | of types. Utilities such as `iree::Status` and `iree::vm::ref<T>` add safety for | 
 | managing reference counted resources and can be used within the modules. | 
 |  | 
 | General flow: | 
 |  | 
 | 1. Expose module via a C API ([`module.h`](./module.h)): | 
 |  | 
 | ```c | 
 | // Ideally all allocations performed by the module should use |allocator|. | 
 | // The returned module in |out_module| should have a ref count of 1 to transfer | 
 | // ownership to the caller. | 
 | iree_status_t iree_table_module_create(iree_allocator_t allocator, | 
 |                                        iree_vm_module_t** out_module); | 
 | ``` | 
 |  | 
 | 2. Implement the module using C/C++/etc ([`module.cc`](./module.cc)): | 
 |  | 
 | Modules have two parts: a shared module and instantiated state. | 
 |  | 
 | The `iree::vm::NativeModule` helper is used to handle the shared module | 
 | declaration and acts as a factory for per-context instantiated state and the | 
 | methods exported by the module: | 
 |  | 
 | ```c++ | 
 | // Any mutable state stored on the module may be accessed from multiple threads | 
 | // if the module is instantiated in multiple contexts and must be thread-safe. | 
 | struct TableModule final : public vm::NativeModule<TableModuleState> { | 
 |   // Each time the module is instantiated this will be called to allocate the | 
 |   // context-specific state. The returned state must only be thread-compatible | 
 |   // as invocations within a context will not be made from multiple threads but | 
 |   // the thread on which they are made may change over time; this means no TLS! | 
 |   StatusOr<std::unique_ptr<TableModuleState>> CreateState( | 
 |       iree_allocator_t allocator) override; | 
 | }; | 
 | ``` | 
 |  | 
 | The module implementation is done on the state object so that methods may use | 
 | `this` to access context-local state: | 
 |  | 
 | ```c++ | 
 | struct TableModuleState final { | 
 |   // Local to the context the module was instantiated in and thread-compatible. | 
 |   std::unordered_map<std::string, std::string> mutable_state; | 
 |  | 
 |   // Exported functions must return Status or StatusOr. Failures will result in | 
 |   // program termination and will be propagated up to the top-level invoker. | 
 |   // If a module wants to provide non-fatal errors it can return results to the | 
 |   // program: here we return a 0/1 indicating whether the key was found as well | 
 |   // as the result or null. | 
 |   // | 
 |   // MLIR declaration: | 
 |   //   func.func private @table.lookup(!util.buffer) -> (i1, !util.buffer) | 
 |   StatusOr<std::tuple<int32_t, vm::ref<iree_vm_buffer_t>>> Lookup( | 
 |       const vm::ref<iree_vm_buffer_t> key); | 
 | }; | 
 | ``` | 
 |  | 
 | Finally the exported methods are registered and marshaling code is expanded: | 
 |  | 
 | ```c++ | 
 | static const vm::NativeFunction<TableModuleState> kTableModuleFunctions[] = { | 
 |     vm::MakeNativeFunction("lookup", &TableModuleState::Lookup), | 
 | }; | 
 | extern "C" iree_status_t iree_table_module_create( | 
 |     iree_allocator_t allocator, iree_vm_module_t** out_module) { | 
 |   auto module = std::make_unique<TableModule>( | 
 |       "table", /*version=*/0, allocator, | 
 |       iree::span<const vm::NativeFunction<CustomModuleState>> | 
 |       (kTableModuleFunctions)); | 
 |   *out_module = module.release()->interface(); | 
 |   return iree_ok_status(); | 
 | } | 
 | ``` | 
 |  | 
 | ## Registering Custom Modules at Runtime | 
 |  | 
 | Once a custom module is defined it needs to be provided to any context that it | 
 | is going to be used in. Each context may have its own unique mix of modules and | 
 | it's the hosting application's responsibility to inject the available modules. | 
 | See [`main.c`](./main.c) for an example showing the entire end-to-end lifetime | 
 | of loading a compiled bytecode module and providing a custom module for runtime | 
 | dynamic linking. | 
 |  | 
 | Since modules themselves can be reused across contexts it can be a way of | 
 | creating shared caches (requires thread-safety!) that span contexts while the | 
 | module state is context specific and isolated. | 
 |  | 
 | Import resolution happens in reverse registration order: the most recently | 
 | registered modules override previous ones. This combined with optional imports | 
 | allows overriding behavior and version compatibility shims (though there is | 
 | still some trickiness involved). | 
 |  | 
 | ```c | 
 | // Ensure custom types are registered before loading modules that use them. | 
 | // This only needs to be done once per instance. | 
 | IREE_CHECK_OK(iree_basic_custom_module_register_types(instance)); | 
 |  | 
 | // Create the custom module that can be reused across contexts. | 
 | iree_vm_module_t* custom_module = NULL; | 
 | IREE_CHECK_OK(iree_basic_custom_module_create(instance, allocator, | 
 |                                               &custom_module)); | 
 |  | 
 | // Create the context for this invocation reusing the loaded modules. | 
 | // Contexts hold isolated state and can be reused for multiple calls. | 
 | // Note that the module order matters: the input user module is dependent on | 
 | // the custom module. | 
 | iree_vm_module_t* modules[] = {custom_module, bytecode_module}; | 
 | iree_vm_context_t* context = NULL; | 
 | IREE_CHECK_OK(iree_vm_context_create_with_modules( | 
 |     instance, IREE_VM_CONTEXT_FLAG_NONE, IREE_ARRAYSIZE(modules), modules, | 
 |     allocator, &context)); | 
 | ``` | 
 |  | 
 | ## Calling Custom Modules from Compiled Programs | 
 |  | 
 | The IREE compiler allows for external functions that are resolved at runtime | 
 | using the [MLIR `func` dialect](https://mlir.llvm.org/docs/Dialects/Func/). Some | 
 | optional attributes are used to allow for customization where required but in | 
 | many cases no additional IREE-specific work is required in the compiled program. | 
 | A few advanced features of the VM FFI are not currently exposed via this | 
 | mechanism such as variadic arguments and tuples but the advantage is that users | 
 | need not customize the IREE compiler in order to use their modules. | 
 |  | 
 | Prior to passing input programs to the IREE compiler users can insert the | 
 | imported functions as external | 
 | [`func.func`](https://mlir.llvm.org/docs/Dialects/Func/#funcfunc-mlirfuncfuncop) | 
 | ops and calls to those functions using | 
 | [`func.call`](https://mlir.llvm.org/docs/Dialects/Func/#funccall-mlirfunccallop): | 
 |  | 
 | ```mlir | 
 | // An external function declaration. | 
 | // `custom` is the runtime module and `string.create` is the exported method. | 
 | // This call uses both IREE types (`!util.buffer`) and custom ones not known to | 
 | // the compiler but available at runtime (`!custom.string`). | 
 | func.func private @custom.string.create(!util.buffer) -> !custom.string | 
 | ``` | 
 |  | 
 | ```mlir | 
 | // Call the imported function. | 
 | %buffer = util.buffer.constant : !util.buffer = "hello world!" | 
 | %result = func.call @custom.string.create(%buffer) : (!util.buffer) -> !custom.string | 
 | ``` | 
 |  | 
 | Users with custom dialects and ops can use | 
 | [MLIR's dialect conversion](https://mlir.llvm.org/docs/DialectConversion/) | 
 | framework to rewrite their custom ops to this form and perform additional | 
 | marshaling logic. For example, the above could have started as this program | 
 | before the user ran their dialect conversion and passed it in to `iree-compile`: | 
 |  | 
 | ```mlir | 
 | %result = custom.string.create "hello world!" : !custom.string | 
 | ``` | 
 |  | 
 | See this samples [`example.mlir`](./test/example.mlir) for examples of features | 
 | such as signature specification and optional import fallback support. |