# Statically-linked custom module sample for IREE tooling

This sample derives from the [basic](./samples/custom_module/basic/) sample to
show how to build a custom C++ module that can be statically linked into the
IREE command line tools. This is only useful for debugging/developing/profiling
custom modules and is not intended as a deployment mechanism. Once a custom
module has been written it can be statically linked into the user's hosting
runtime application or library or dynamically linked and loaded as with the
[dynamic](./samples/custom_module/dynamic/) sample.

The custom module is implemented using a C++ module wrapper layer in
[`module.cc`](./module.cc) and loaded by the `iree-run-module` tool
automatically whenever any user module depends on it.

## 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. The IREE tooling (`iree-run-module`,
`iree-benchmark-module`, etc) is _not_ an ML runtime and not intended to be
deployed but it can be useful to test custom modules using them in order to
benchmark, profile, or debug outside of user applications. This sample
demonstrates how to take a generic custom module that's effectively identical to
both [basic](./samples/custom_module/basic/) and
[dynamic](./samples/custom_module/dynamic/) samples and link that into the IREE
tools for those purposes. Note that this is mostly about CMake configuration and
matching some tooling-specific function signatures and it's possible to share
the same module code between all various modes with minor differences.

## Instructions

1. Build or install the `iree-base-compiler` binary:

    ```sh
    python -m pip install iree-base-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:

    ```sh
    # 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-hal-target-device=local \
        --iree-hal-local-target-device-backends=vmvx \
        samples/custom_module/static/test/example.mlir \
        -o=/tmp/example.vmfb
    ```

3. Configure the IREE tools to include the custom module:

    ```sh
    cmake -B ../iree-build/ -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo . \
        -DCMAKE_C_FLAGS=-DIREE_VM_EXECUTION_TRACING_FORCE_ENABLE=1 \
        -DIREE_EXTERNAL_TOOLING_MODULES=static_sample \
        -DIREE_EXTERNAL_TOOLING_MODULE_STATIC_SAMPLE_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/samples/custom_module/static \
        -DIREE_EXTERNAL_TOOLING_MODULE_STATIC_SAMPLE_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/samples/custom_module/static \
        -DIREE_EXTERNAL_TOOLING_MODULE_STATIC_SAMPLE_TARGET=iree_samples_custom_module_static_module \
        -DIREE_EXTERNAL_TOOLING_MODULE_STATIC_SAMPLE_NAME=custom \
        -DIREE_EXTERNAL_TOOLING_MODULE_STATIC_SAMPLE_REGISTER_TYPES=register_sample_module_types \
        -DIREE_EXTERNAL_TOOLING_MODULE_STATIC_SAMPLE_CREATE=create_sample_module
    cmake --build ../iree-build/ --target iree-run-module
    ```

    (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 using the main `iree-run-module` tool:

   ```sh
   ../iree-build/tools/iree-run-module \
      --module=/tmp/example.vmfb \
      --function=main
   ```

### Type registration

Custom types defined by the dynamic module must be registered with the
`iree_vm_instance_t` provided to the creation function.

Any externally defined types such as the builtin VM types
(`!vm.list`/`iree_vm_list_t`) or the HAL types
(`!hal.buffer_view`/`iree_hal_buffer_view_t`) must be resolved from the instance
prior to use. `iree_vm_resolve_builtin_types` and
`iree_hal_module_resolve_all_types` (or one of the more restricted sets like
`iree_hal_module_resolve_common_types`) can be used to perform the resolution.

The tooling has a quirk around dynamic module resolution that requires all types
be registered prior to loading modules. Here that is implemented with the
`register_sample_module_types` function that is provided to CMake via
`-DIREE_EXTERNAL_TOOLING_MODULE_STATIC_SAMPLE_REGISTER_TYPES=` to ensure that
the sample types get registered first. In a real application that manually
creates modules this would not be required as modules are expected to manage
type registration alongside their lifetime.
