Add support for emitc static library This change is for adding support for emitc static library. Status: - simple_vec_mul works properly. - For more complicated float/quant models, C code and static library can be succesufully generated. However, there is compilation error when building the final executable: undefined reference to `call_0riirii_r_import' and `call_0rriCiD_v_import", caused by upstream bug (https://github.com/google/iree/issues/7842). The targets are thus not being built at this moment. Change-Id: Iacfd0c5c0d7c494ae6f870cef2fac0d36f8fbf49
diff --git a/CMakeLists.txt b/CMakeLists.txt index cb26763..fd8264a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -86,6 +86,8 @@ include(${IREE_SOURCE_DIR}/build_tools/cmake/iree_copts.cmake) include(springbok_bytecode_module) +include(springbok_c_module) +include(springbok_modules) include(iree_model_input) # Add the included directory here. add_subdirectory(samples)
diff --git a/cmake/springbok_c_module.cmake b/cmake/springbok_c_module.cmake new file mode 100644 index 0000000..929f4ad --- /dev/null +++ b/cmake/springbok_c_module.cmake
@@ -0,0 +1,136 @@ +include(CMakeParseArguments) + +# springbok_c_module() +# +# A wrapper for the iree_bytecode_module to apply common iree-translate flags +# Parameters: +# NAME: Name of target. +# SRC: Source file to compile into an emitC module. Support relative path. +# FLAGS: Flags to pass to the translation tool (list of strings). +# C_IDENTIFIER: Identifier to use for generate c embed code. +# If omitted then no C embed code will be generated. +# +# Examples: +# springbok_c_module( +# NAME +# dare_devel_c_module_emitc_static +# SRC +# "daredevil_quant.tflite" +# FLAGS +# "-iree-input-type=tosa" +# ) +# +# springbok_c_module( +# NAME +# simple_float_mul_c_module_emitc_static +# SRC +# "simple_float_mul.mlir" +# FLAGS +# "-iree-input-type=mhlo" +# PUBLIC +# ) +# +function(springbok_c_module) + cmake_parse_arguments( + _RULE + "PUBLIC" + "NAME;SRC" + "FLAGS" + ${ARGN} + ) + + set(_MLIR_SRC "${_RULE_SRC}") + string(FIND "${_RULE_SRC}" ".tflite" _IS_TFLITE REVERSE) + if(${_IS_TFLITE} GREATER 0) + find_program(IREE_IMPORT_TFLITE_TOOL "iree-import-tflite" REQUIRED) + set(_MLIR_SRC "${CMAKE_CURRENT_BINARY_DIR}/${_RULE_NAME}.mlir") + get_filename_component(_SRC_PATH "${_RULE_SRC}" REALPATH) + set(_ARGS "${_SRC_PATH}") + list(APPEND _ARGS "-o") + list(APPEND _ARGS "${_RULE_NAME}.mlir") + # Only add the custom_command here. The output is passed to + # iree_bytecode_module as the source. + add_custom_command( + OUTPUT + "${_RULE_NAME}.mlir" + COMMAND + ${IREE_IMPORT_TFLITE_TOOL} + ${_ARGS} + DEPENDS + ${IREE_IMPORT_TFLITE_TOOL} + ) + endif() + + get_filename_component(_MLIR_SRC "${_MLIR_SRC}" REALPATH) + iree_get_executable_path(_TRANSLATE_TOOL_EXECUTABLE "iree-translate") + iree_get_executable_path(_LINKER_TOOL_EXECUTABLE "lld") + + # Replace dependencies passed by ::name with iree::package::name + iree_package_ns(_PACKAGE_NS) + list(TRANSFORM _RULE_DEPS REPLACE "^::" "${_PACKAGE_NS}::") + + # Prefix the library with the package name, so we get: iree_package_name. + iree_package_name(_PACKAGE_NAME) + + set(_RULE_C_NAME "${_RULE_NAME}_c") + set(_LIB_NAME "${_PACKAGE_NAME}_${_RULE_C_NAME}") + set(_GEN_TARGET "${_LIB_NAME}_gen") + set(_O_FILE_NAME ${_RULE_C_NAME}.o) + set(_H_FILE_NAME ${_RULE_C_NAME}.h) + set(_EMITC_FILE_NAME ${_RULE_NAME}_emitc.h) + + ## Example with VM C module. + # Setup args for iree-translate. + set(_TRANSLATE_ARGS ${_RULE_FLAGS}) + list(APPEND _TRANSLATE_ARGS "-iree-mlir-to-vm-c-module") + list(APPEND _TRANSLATE_ARGS "-iree-hal-target-backends=dylib-llvm-aot") + list(APPEND _TRANSLATE_ARGS "-iree-llvm-target-triple=riscv32-pc-linux-elf") + list(APPEND _TRANSLATE_ARGS "-iree-llvm-target-cpu=generic-rv32") + list(APPEND _TRANSLATE_ARGS "-iree-llvm-target-cpu-features=+m,+f,+experimental-v") + list(APPEND _TRANSLATE_ARGS "-iree-llvm-target-abi=ilp32") + list(APPEND _TRANSLATE_ARGS "-iree-llvm-link-embedded=false") + list(APPEND _TRANSLATE_ARGS "-iree-llvm-link-static") + list(APPEND _TRANSLATE_ARGS "-iree-llvm-system-linker-path=\"${_LINKER_TOOL_EXECUTABLE}\"") + list(APPEND _TRANSLATE_ARGS "-iree-llvm-static-library-output-path=${_O_FILE_NAME}") + list(APPEND _TRANSLATE_ARGS "${_MLIR_SRC}") + list(APPEND _TRANSLATE_ARGS "-o") + list(APPEND _TRANSLATE_ARGS "${_EMITC_FILE_NAME}") + + # Custom command for iree-translate to generate static library and C module. + add_custom_command( + OUTPUT + ${_H_FILE_NAME} + ${_O_FILE_NAME} + ${_EMITC_FILE_NAME} + COMMAND ${_TRANSLATE_TOOL_EXECUTABLE} ${_TRANSLATE_ARGS} + DEPENDS ${_TRANSLATE_TOOL_EXECUTABLE} ${_MLIR_SRC} ${_LINKER_TOOL_EXECUTABLE} + ) + + add_custom_target( + ${_GEN_TARGET} + DEPENDS + "${_EMITC_FILE_NAME}" + ) + + add_library(${_LIB_NAME} + STATIC + ${_O_FILE_NAME} + ) + add_dependencies(${_LIB_NAME} ${_GEN_TARGET}) + + SET_TARGET_PROPERTIES( + ${_LIB_NAME} + PROPERTIES + LINKER_LANGUAGE C + ) + + # Alias the iree_package_name library to iree::package::name. + # This lets us more clearly map to Bazel and makes it possible to + # disambiguate the underscores in paths vs. the separators. + add_library(${_PACKAGE_NS}::${_RULE_C_NAME} ALIAS ${_LIB_NAME}) + iree_package_dir(_PACKAGE_DIR) + if(${_RULE_C_NAME} STREQUAL ${_PACKAGE_DIR}) + add_library(${_PACKAGE_NS} ALIAS ${_LIB_NAME}) + endif() + +endfunction()
diff --git a/cmake/springbok_modules.cmake b/cmake/springbok_modules.cmake new file mode 100644 index 0000000..eee7e94 --- /dev/null +++ b/cmake/springbok_modules.cmake
@@ -0,0 +1,69 @@ +include(CMakeParseArguments) + +# springbok_modules() +# +# A wrapper for the springbok_bytecode_module and springbok_c_module to apply common iree-translate flags +# Parameters: +# NAME: Name of target. +# SRC: Source file to compile into a bytecode module. Support relative path. +# FLAGS: Flags to pass to the translation tool (list of strings). +# C_IDENTIFIER: Identifier to use for generate c embed code. +# If omitted then no C embed code will be generated. +# +# Examples: +# springbok_modules( +# NAME +# dare_devel +# SRC +# "daredevil_quant.tflite" +# C_IDENTIFIER +# "daredevil_bytecode_module_dylib" +# FLAGS +# "-iree-input-type=tosa" +# ) +# +# springbok_modules( +# NAME +# simple_float_mul +# SRC +# "simple_float_mul.mlir" +# C_IDENTIFIER +# "simple_float_mul" +# FLAGS +# "-iree-input-type=mhlo" +# PUBLIC +# ) +# + +function(springbok_modules) + cmake_parse_arguments( + _RULE + "PUBLIC" + "NAME;SRC;C_IDENTIFIER" + "FLAGS" + ${ARGN} + ) + + springbok_bytecode_module( + NAME + "${_RULE_NAME}_bytecode_module_dylib" + SRC + "${_RULE_SRC}" + C_IDENTIFIER + "${_RULE_C_IDENTIFIER}_bytecode_module_dylib" + FLAGS + "${_RULE_FLAGS}" + PUBLIC + ) + + springbok_c_module( + NAME + "${_RULE_NAME}_c_module_static" + SRC + "${_RULE_SRC}" + FLAGS + "${_RULE_FLAGS}" + PUBLIC + ) + +endfunction()
diff --git a/samples/device/CMakeLists.txt b/samples/device/CMakeLists.txt index 460b738..cec04b0 100644 --- a/samples/device/CMakeLists.txt +++ b/samples/device/CMakeLists.txt
@@ -12,3 +12,18 @@ iree::hal::local::loaders::embedded_library_loader iree::hal::local::sync_driver ) + +iree_cc_library( + NAME + device_static_loader + HDRS + "device.h" + SRCS + "device_static_loader.c" + DEPS + iree::base + iree::hal + iree::hal::local + iree::hal::local::loaders::static_library_loader + iree::hal::local::sync_driver +)
diff --git a/samples/device/device_static_loader.c b/samples/device/device_static_loader.c new file mode 100644 index 0000000..225712c --- /dev/null +++ b/samples/device/device_static_loader.c
@@ -0,0 +1,51 @@ +// Static library loading in IREE. + +#include "iree/hal/local/loaders/static_library_loader.h" +#include "iree/hal/local/sync_device.h" +#include "iree/modules/hal/module.h" +#include "samples/device/device.h" +#include "samples/util/model_api.h" + +// A function to create the HAL device from the different backend targets. +// The HAL device is returned based on the implementation, and it must be +// released by the caller. +iree_status_t create_sample_device(iree_allocator_t host_allocator, + iree_hal_device_t** out_device) { + iree_status_t status = iree_ok_status(); + + // Set paramters for the device created in the next step. + iree_hal_sync_device_params_t params; + iree_hal_sync_device_params_initialize(¶ms); + + // Load the statically embedded library + const iree_hal_executable_library_header_t** static_library = library_query( + IREE_HAL_EXECUTABLE_LIBRARY_LATEST_VERSION, /*reserved=*/NULL); + const iree_hal_executable_library_header_t** libraries[1] = {static_library}; + + iree_hal_executable_loader_t* library_loader = NULL; + if (iree_status_is_ok(status)) { + status = iree_hal_static_library_loader_create( + IREE_ARRAYSIZE(libraries), libraries, + iree_hal_executable_import_provider_null(), host_allocator, + &library_loader); + } + + // Use the default host allocator for buffer allocations. + iree_string_view_t identifier = iree_make_cstring_view("sync"); + iree_hal_allocator_t* device_allocator = NULL; + if (iree_status_is_ok(status)) { + status = iree_hal_allocator_create_heap(identifier, host_allocator, + host_allocator, &device_allocator); + } + + // Create the device and release the executor and loader afterwards. + if (iree_status_is_ok(status)) { + status = iree_hal_sync_device_create( + identifier, ¶ms, /*loader_count=*/1, &library_loader, + device_allocator, host_allocator, out_device); + } + + iree_hal_allocator_release(device_allocator); + iree_hal_executable_loader_release(library_loader); + return status; +}
diff --git a/samples/simple_vec_mul/CMakeLists.txt b/samples/simple_vec_mul/CMakeLists.txt index 7fc33fc..f545988 100644 --- a/samples/simple_vec_mul/CMakeLists.txt +++ b/samples/simple_vec_mul/CMakeLists.txt
@@ -4,13 +4,13 @@ # https://github.com/llvm/llvm-project/blob/0eeab8b/llvm/lib/Target/RISCV/RISCVSubtarget.cpp#L30-L51 #------------------------------------------------------------------------------- -springbok_bytecode_module( +springbok_modules( NAME - simple_float_mul_bytecode_module_dylib + simple_float_mul SRC "simple_float_mul.mlir" C_IDENTIFIER - "samples_simple_vec_mul_simple_float_mul_bytecode_module_dylib" + "samples_simple_vec_mul_simple_float_mul" FLAGS "-iree-input-type=mhlo" "-riscv-v-vector-bits-min=512" @@ -19,13 +19,13 @@ PUBLIC ) -springbok_bytecode_module( +springbok_modules( NAME - simple_int_mul_bytecode_module_dylib + simple_int_mul SRC "simple_int_mul.mlir" C_IDENTIFIER - "samples_simple_vec_mul_simple_int_mul_bytecode_module_dylib" + "samples_simple_vec_mul_simple_int_mul" FLAGS "-iree-input-type=mhlo" "-riscv-v-vector-bits-min=512" @@ -58,6 +58,18 @@ iree_cc_binary( NAME + simple_float_vec_mul_emitc_static + SRCS + "float_vec.c" + DEPS + ::simple_float_mul_c_module_static_c + samples::util::util_emitc + COPTS + "-DBUILD_EMITC_STATIC" +) + +iree_cc_binary( + NAME simple_int_vec_mul_embedded_sync SRCS "int_vec.c" @@ -65,3 +77,15 @@ ::simple_int_mul_bytecode_module_dylib_c samples::util::util ) + +iree_cc_binary( + NAME + simple_int_vec_mul_emitc_static + SRCS + "int_vec.c" + DEPS + ::simple_int_mul_c_module_static_c + samples::util::util_emitc + COPTS + "-DBUILD_EMITC_STATIC" +)
diff --git a/samples/simple_vec_mul/float_vec.c b/samples/simple_vec_mul/float_vec.c index 3195547..197684f 100644 --- a/samples/simple_vec_mul/float_vec.c +++ b/samples/simple_vec_mul/float_vec.c
@@ -5,7 +5,12 @@ #include "samples/util/util.h" // Compiled module embedded here to avoid file IO: +#if !defined(BUILD_EMITC_STATIC) #include "samples/simple_vec_mul/simple_float_mul_bytecode_module_dylib_c.h" +#else +#include "samples/simple_vec_mul/simple_float_mul_c_module_static_c.h" +#include "samples/simple_vec_mul/simple_float_mul_c_module_static_emitc.h" +#endif const MlModel kModel = { .num_input = 2, @@ -21,12 +26,25 @@ .model_name = "simple_float_vec_mul", }; +#if !defined(BUILD_EMITC_STATIC) const iree_const_byte_span_t load_bytecode_module_data() { const struct iree_file_toc_t *module_file_toc = samples_simple_vec_mul_simple_float_mul_bytecode_module_dylib_create(); return iree_make_const_byte_span(module_file_toc->data, module_file_toc->size); } +#else +// Function to create the C module. +iree_status_t create_c_module(iree_vm_module_t **module) { + return module_create(iree_allocator_system(), module); +} + +const iree_hal_executable_library_header_t **library_query( + iree_hal_executable_library_version_t max_version, void *reserved) { + return simple_mul_dispatch_0_library_query(max_version, + /*reserved=*/reserved); +} +#endif iree_status_t load_input_data(const MlModel *model, void **buffer) { iree_status_t result = alloc_input_buffer(model, buffer);
diff --git a/samples/simple_vec_mul/int_vec.c b/samples/simple_vec_mul/int_vec.c index 70385a2..824a031 100644 --- a/samples/simple_vec_mul/int_vec.c +++ b/samples/simple_vec_mul/int_vec.c
@@ -5,7 +5,12 @@ #include "samples/util/util.h" // Compiled module embedded here to avoid file IO: +#if !defined(BUILD_EMITC_STATIC) #include "samples/simple_vec_mul/simple_int_mul_bytecode_module_dylib_c.h" +#else +#include "samples/simple_vec_mul/simple_int_mul_c_module_static_c.h" +#include "samples/simple_vec_mul/simple_int_mul_c_module_static_emitc.h" +#endif const MlModel kModel = { .num_input = 2, @@ -21,12 +26,25 @@ .model_name = "simple_int_vec_mul", }; +#if !defined(BUILD_EMITC_STATIC) const iree_const_byte_span_t load_bytecode_module_data() { const struct iree_file_toc_t *module_file_toc = samples_simple_vec_mul_simple_int_mul_bytecode_module_dylib_create(); return iree_make_const_byte_span(module_file_toc->data, module_file_toc->size); } +#else +// Function to create the C module. +iree_status_t create_c_module(iree_vm_module_t **module) { + return module_create(iree_allocator_system(), module); +} + +const iree_hal_executable_library_header_t **library_query( + iree_hal_executable_library_version_t max_version, void *reserved) { + return simple_mul_dispatch_0_library_query(max_version, + /*reserved=*/reserved); +} +#endif iree_status_t load_input_data(const MlModel *model, void **buffer) { iree_status_t result = alloc_input_buffer(model, buffer);
diff --git a/samples/util/CMakeLists.txt b/samples/util/CMakeLists.txt index 6023a18..14d2170 100644 --- a/samples/util/CMakeLists.txt +++ b/samples/util/CMakeLists.txt
@@ -20,6 +20,28 @@ iree_cc_library( NAME + util_emitc + HDRS + "util.h" + SRCS + "util.c" + DEPS + ::alloc + iree::base + iree::hal + iree::hal::local + iree::hal::local::loaders::static_library_loader + iree::hal::local::sync_driver + iree::modules::hal + iree::vm + iree::vm::shims_emitc + samples::device::device_static_loader + COPTS + "-DBUILD_EMITC_STATIC" +) + +iree_cc_library( + NAME alloc HDRS "alloc.h"
diff --git a/samples/util/model_api.h b/samples/util/model_api.h index a409019..e4a4f23 100644 --- a/samples/util/model_api.h +++ b/samples/util/model_api.h
@@ -5,6 +5,7 @@ #include "iree/base/api.h" #include "iree/hal/api.h" +#include "iree/hal/local/executable_library.h" #include "iree/modules/hal/module.h" #include "iree/vm/api.h" #include "iree/vm/bytecode_module.h" @@ -31,6 +32,13 @@ // Load the VM bytecode module from the embedded c library into memory. const iree_const_byte_span_t load_bytecode_module_data(); +// Load the statically embedded library +const iree_hal_executable_library_header_t **library_query( + iree_hal_executable_library_version_t max_version, void *reserved); + +// Function to create the C module. +iree_status_t create_c_module(iree_vm_module_t **module); + // For each ML workload, based on the model configuration, allocate the buffer // and prepare the data. It can be loaded from a embedded image binary, a // randomly generated stream, or a pointer from the sensor/ISP output.
diff --git a/samples/util/util.c b/samples/util/util.c index b0b514a..2a0bec5 100644 --- a/samples/util/util.c +++ b/samples/util/util.c
@@ -14,6 +14,17 @@ extern const MlModel kModel; +// Function to create bytecode or C module. +static iree_status_t create_module(iree_vm_module_t **module) { +#if !defined(BUILD_EMITC_STATIC) + const iree_const_byte_span_t module_data = load_bytecode_module_data(); + return iree_vm_bytecode_module_create(module_data, iree_allocator_null(), + iree_allocator_system(), module); +#else + return create_c_module(module); +#endif +} + // Prepare the input buffers and buffer_views based on the data type. They must // be released by the caller. static iree_status_t prepare_input_hal_buffer_views( @@ -62,26 +73,22 @@ result = iree_hal_module_create(device, iree_allocator_system(), &hal_module); } - // Load bytecode module from the embedded data. - const iree_const_byte_span_t module_data = load_bytecode_module_data(); - - iree_vm_module_t *bytecode_module = NULL; + // Load bytecode or C module. + iree_vm_module_t *module = NULL; if (iree_status_is_ok(result)) { - result = iree_vm_bytecode_module_create(module_data, iree_allocator_null(), - iree_allocator_system(), - &bytecode_module); + result = create_module(&module); } // Allocate a context that will hold the module state across invocations. iree_vm_context_t *context = NULL; - iree_vm_module_t *modules[] = {hal_module, bytecode_module}; + iree_vm_module_t *modules[] = {hal_module, module}; if (iree_status_is_ok(result)) { result = iree_vm_context_create_with_modules( instance, IREE_VM_CONTEXT_FLAG_NONE, &modules[0], IREE_ARRAYSIZE(modules), iree_allocator_system(), &context); } iree_vm_module_release(hal_module); - iree_vm_module_release(bytecode_module); + iree_vm_module_release(module); // Lookup the entry point function. // Note that we use the synchronous variant which operates on pure type/shape