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(&params);
+
+  // 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, &params, /*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