Add samples to build/run vector multiplication with RVV instructions

Add both int32 and float examples. mhlo.dot MLIR (i8,i8)->i32
(int8 dot product to int32 out) doesn't support RVV codegen
at clang trunk.

The binary can run successfully on qemu-system. renode will hit illegal
instuction for the float example.

Change-Id: I6c87211542fea485eda4d3de7da8250ec60e2a01
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 62a54f3..4b7e783 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -37,4 +37,15 @@
   add_definitions(-DIREE_USER_CONFIG_H="${SPRINGBOK_CONFIG_HEADER}")
 endif()
 
+# Build IREE runtime libraries.
 add_subdirectory($ENV{ROOTDIR}/toolchain/iree iree)
+
+# Apply IREE's CMake variables and build options so we can use IREE build
+# functions properly in this project.
+include($ENV{ROOTDIR}/toolchain/iree/build_tools/cmake/iree_copts.cmake)
+
+# Include springbok log functions.
+include_directories(BEFORE SYSTEM "$ENV{ROOTDIR}/sw/vec/springbok/include")
+
+# Add the included directory here.
+add_subdirectory(samples)
diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt
new file mode 100644
index 0000000..0e9c88b
--- /dev/null
+++ b/samples/CMakeLists.txt
@@ -0,0 +1 @@
+iree_add_all_subdirs()
diff --git a/samples/simple_vec_mul/CMakeLists.txt b/samples/simple_vec_mul/CMakeLists.txt
new file mode 100644
index 0000000..647effc
--- /dev/null
+++ b/samples/simple_vec_mul/CMakeLists.txt
@@ -0,0 +1,102 @@
+#-------------------------------------------------------------------------------
+# Build the mlir bytecode modules with iree-translate. Note the last two flags
+# and "+experimental-v" are for RVV support.
+# https://github.com/llvm/llvm-project/blob/f4ec30d/llvm/lib/Target/RISCV/RISCVSubtarget.cpp#L30-L46
+#-------------------------------------------------------------------------------
+
+iree_bytecode_module(
+  NAME
+    simple_float_mul_bytecode_module_dylib
+  SRC
+    "simple_float_mul.mlir"
+  C_IDENTIFIER
+    "samples_simple_vec_mul_simple_float_mul_bytecode_module_dylib"
+  FLAGS
+    "-iree-input-type=mhlo"
+    "-iree-mlir-to-vm-bytecode-module"
+    "-iree-hal-target-backends=dylib-llvm-aot"
+    "-iree-llvm-target-triple=riscv32-pc-linux-elf"
+    "-iree-llvm-target-cpu=generic-rv32"
+    "-iree-llvm-target-cpu-features=+m,+f,+experimental-v"
+    "-iree-llvm-target-abi=ilp32"
+    "-iree-llvm-link-embedded=true"
+    "-iree-llvm-debug-symbols=false"
+    "-riscv-v-vector-bits-min=512"
+    "-riscv-v-fixed-length-vector-lmul-max=8"
+  PUBLIC
+)
+
+# Temporarily disable RVV for the int32 version so the binary can be run on
+# renode.
+# TODO(b/195201585): Re-enable RVV.
+iree_bytecode_module(
+  NAME
+    simple_int_mul_bytecode_module_dylib
+  SRC
+    "simple_int_mul.mlir"
+  C_IDENTIFIER
+    "samples_simple_vec_mul_simple_int_mul_bytecode_module_dylib"
+  FLAGS
+    "-iree-input-type=mhlo"
+    "-iree-mlir-to-vm-bytecode-module"
+    "-iree-hal-target-backends=dylib-llvm-aot"
+    "-iree-llvm-target-triple=riscv32-pc-linux-elf"
+    "-iree-llvm-target-cpu=generic-rv32"
+    "-iree-llvm-target-cpu-features=+m,+f,+experimental-v"
+    "-iree-llvm-target-abi=ilp32"
+    "-iree-llvm-link-embedded=true"
+    "-iree-llvm-debug-symbols=false"
+    "-riscv-v-vector-bits-min=512"
+    "-riscv-v-fixed-length-vector-lmul-max=8"
+  PUBLIC
+)
+
+#-------------------------------------------------------------------------------
+# Binaries to execute the MLIR bytecode modules
+#-------------------------------------------------------------------------------
+
+
+# If the program requires a larger stack size, add
+#
+#   LINKOPTS
+#     "LINKER:--defsym=__stack_size__=<new stack size>"
+#
+# to increase it.
+
+iree_cc_binary(
+  NAME
+    simple_float_vec_mul_embedded_sync
+  SRCS
+    "device_embedded_sync.c"
+    "float_vec.c"
+    "simple_vec_mul.c"
+  DEPS
+    ::simple_float_mul_bytecode_module_dylib_c
+    iree::base
+    iree::hal
+    iree::hal::local
+    iree::hal::local::loaders::embedded_library_loader
+    iree::hal::local::sync_driver
+    iree::modules::hal
+    iree::vm
+    iree::vm::bytecode_module
+)
+
+iree_cc_binary(
+  NAME
+    simple_int_vec_mul_embedded_sync
+  SRCS
+    "device_embedded_sync.c"
+    "int_vec.c"
+    "simple_vec_mul.c"
+  DEPS
+    ::simple_int_mul_bytecode_module_dylib_c
+    iree::base
+    iree::hal
+    iree::hal::local
+    iree::hal::local::loaders::embedded_library_loader
+    iree::hal::local::sync_driver
+    iree::modules::hal
+    iree::vm
+    iree::vm::bytecode_module
+)
diff --git a/samples/simple_vec_mul/device_embedded_sync.c b/samples/simple_vec_mul/device_embedded_sync.c
new file mode 100644
index 0000000..2f10e6b
--- /dev/null
+++ b/samples/simple_vec_mul/device_embedded_sync.c
@@ -0,0 +1,29 @@
+// An example of setting up the embedded-sync driver.
+
+#include <stddef.h>
+
+#include "iree/base/api.h"
+#include "iree/hal/api.h"
+#include "iree/hal/local/executable_loader.h"
+#include "iree/hal/local/loaders/embedded_library_loader.h"
+#include "iree/hal/local/sync_device.h"
+
+iree_status_t create_sample_device(iree_hal_device_t **device) {
+  // Set parameters for the device created in the next step.
+  iree_hal_sync_device_params_t params;
+  iree_hal_sync_device_params_initialize(&params);
+
+  iree_hal_executable_loader_t *loader = NULL;
+  IREE_RETURN_IF_ERROR(iree_hal_embedded_library_loader_create(
+      iree_hal_executable_import_provider_null(), iree_allocator_system(),
+      &loader));
+
+  iree_string_view_t identifier = iree_make_cstring_view("dylib");
+
+  // Create the synchronous device and release the loader afterwards.
+  iree_status_t status =
+      iree_hal_sync_device_create(identifier, &params, /*loader_count=*/1,
+                                  &loader, iree_allocator_system(), device);
+  iree_hal_executable_loader_release(loader);
+  return status;
+}
diff --git a/samples/simple_vec_mul/float_vec.c b/samples/simple_vec_mul/float_vec.c
new file mode 100644
index 0000000..eaa41e1
--- /dev/null
+++ b/samples/simple_vec_mul/float_vec.c
@@ -0,0 +1,77 @@
+// Float simple_mul bytecode loading and input/output processes
+
+#include "iree/base/api.h"
+#include "iree/hal/api.h"
+
+// Compiled module embedded here to avoid file IO:
+#include "samples/simple_vec_mul/simple_float_mul_bytecode_module_dylib_c.h"
+
+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);
+}
+
+iree_status_t prepare_input_hal_buffer_views(
+    iree_hal_device_t *device, const int buffer_length, void **arg0_buffer,
+    void **arg1_buffer, iree_hal_buffer_view_t **arg0_buffer_view,
+    iree_hal_buffer_view_t **arg1_buffer_view) {
+  iree_status_t result = iree_ok_status();
+  *arg0_buffer =
+      iree_aligned_alloc(sizeof(uint32_t), sizeof(float) * buffer_length);
+  if (*arg0_buffer == NULL) {
+    result = iree_make_status(IREE_STATUS_RESOURCE_EXHAUSTED);
+  }
+  if (iree_status_is_ok(result)) {
+    *arg1_buffer =
+        iree_aligned_alloc(sizeof(uint32_t), sizeof(float) * buffer_length);
+    if (*arg1_buffer == NULL) {
+      result = iree_make_status(IREE_STATUS_RESOURCE_EXHAUSTED);
+    }
+  }
+
+  // Populate initial values
+  // arg0 = 0, 1/4, 1/2, 3/4... 1023/4
+  // arg1 = 0, 1/2, 1, 3/2... 1023/2
+  for (int i = 0; i < buffer_length; ++i) {
+    ((float *)*arg0_buffer)[i] = i / 4.0f;
+    ((float *)*arg1_buffer)[i] = i / 2.0f;
+  }
+  // Wrap buffers in shaped buffer views.
+  // The buffers can be mapped on the CPU and that can also be used
+  // on the device. Not all devices support this, but the ones we have now do.
+  iree_hal_dim_t shape[1] = {buffer_length};
+
+  iree_hal_memory_type_t input_memory_type =
+      IREE_HAL_MEMORY_TYPE_HOST_LOCAL | IREE_HAL_MEMORY_TYPE_DEVICE_VISIBLE;
+
+  result = iree_hal_buffer_view_wrap_or_clone_heap_buffer(
+      iree_hal_device_allocator(device), shape, IREE_ARRAYSIZE(shape),
+      IREE_HAL_ELEMENT_TYPE_FLOAT_32, input_memory_type,
+      IREE_HAL_MEMORY_ACCESS_READ, IREE_HAL_BUFFER_USAGE_ALL,
+      iree_make_byte_span(*arg0_buffer, sizeof(int32_t) * buffer_length),
+      iree_allocator_null(), arg0_buffer_view);
+
+  if (iree_status_is_ok(result)) {
+    result = iree_hal_buffer_view_wrap_or_clone_heap_buffer(
+        iree_hal_device_allocator(device), shape, IREE_ARRAYSIZE(shape),
+        IREE_HAL_ELEMENT_TYPE_FLOAT_32, input_memory_type,
+        IREE_HAL_MEMORY_ACCESS_READ, IREE_HAL_BUFFER_USAGE_ALL,
+        iree_make_byte_span(*arg1_buffer, sizeof(int32_t) * buffer_length),
+        iree_allocator_null(), arg1_buffer_view);
+  }
+  return result;
+}
+
+iree_status_t check_output_data(iree_hal_buffer_mapping_t *mapped_memory) {
+  iree_status_t result = iree_ok_status();
+  for (int i = 0; i < mapped_memory->contents.data_length / sizeof(float);
+       ++i) {
+    if (((const float *)mapped_memory->contents.data)[i] != i * i / 8.0f) {
+      result = iree_make_status(IREE_STATUS_UNKNOWN, "result mismatches");
+      break;
+    }
+  }
+  return result;
+}
diff --git a/samples/simple_vec_mul/int_vec.c b/samples/simple_vec_mul/int_vec.c
new file mode 100644
index 0000000..946aa63
--- /dev/null
+++ b/samples/simple_vec_mul/int_vec.c
@@ -0,0 +1,77 @@
+// Integer simple_mul bytecode loading and input/output processes
+
+#include "iree/base/api.h"
+#include "iree/hal/api.h"
+
+// Compiled module embedded here to avoid file IO:
+#include "samples/simple_vec_mul/simple_int_mul_bytecode_module_dylib_c.h"
+
+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);
+}
+
+iree_status_t prepare_input_hal_buffer_views(
+    iree_hal_device_t *device, const int buffer_length, void **arg0_buffer,
+    void **arg1_buffer, iree_hal_buffer_view_t **arg0_buffer_view,
+    iree_hal_buffer_view_t **arg1_buffer_view) {
+  iree_status_t result = iree_ok_status();
+  *arg0_buffer =
+      iree_aligned_alloc(sizeof(uint32_t), sizeof(int32_t) * buffer_length);
+  if (*arg0_buffer == NULL) {
+    result = iree_make_status(IREE_STATUS_RESOURCE_EXHAUSTED);
+  }
+  if (iree_status_is_ok(result)) {
+    *arg1_buffer =
+        iree_aligned_alloc(sizeof(uint32_t), sizeof(int32_t) * buffer_length);
+    if (*arg1_buffer == NULL) {
+      result = iree_make_status(IREE_STATUS_RESOURCE_EXHAUSTED);
+    }
+  }
+
+  // Populate initial values
+  // arg0 = 0, 0, 1, 1,..., 511
+  // arg1 = 0, 1, 2, 3,..., 1023
+  for (int i = 0; i < buffer_length; ++i) {
+    ((int32_t *)*arg0_buffer)[i] = i >> 1;
+    ((int32_t *)*arg1_buffer)[i] = i;
+  }
+  // Wrap buffers in shaped buffer views.
+  // The buffers can be mapped on the CPU and that can also be used
+  // on the device. Not all devices support this, but the ones we have now do.
+  iree_hal_dim_t shape[1] = {buffer_length};
+
+  iree_hal_memory_type_t input_memory_type =
+      IREE_HAL_MEMORY_TYPE_HOST_LOCAL | IREE_HAL_MEMORY_TYPE_DEVICE_VISIBLE;
+
+  result = iree_hal_buffer_view_wrap_or_clone_heap_buffer(
+      iree_hal_device_allocator(device), shape, IREE_ARRAYSIZE(shape),
+      IREE_HAL_ELEMENT_TYPE_SINT_32, input_memory_type,
+      IREE_HAL_MEMORY_ACCESS_READ, IREE_HAL_BUFFER_USAGE_ALL,
+      iree_make_byte_span(*arg0_buffer, sizeof(int32_t) * buffer_length),
+      iree_allocator_null(), arg0_buffer_view);
+
+  if (iree_status_is_ok(result)) {
+    result = iree_hal_buffer_view_wrap_or_clone_heap_buffer(
+        iree_hal_device_allocator(device), shape, IREE_ARRAYSIZE(shape),
+        IREE_HAL_ELEMENT_TYPE_SINT_32, input_memory_type,
+        IREE_HAL_MEMORY_ACCESS_READ, IREE_HAL_BUFFER_USAGE_ALL,
+        iree_make_byte_span(*arg1_buffer, sizeof(int32_t) * buffer_length),
+        iree_allocator_null(), arg1_buffer_view);
+  }
+  return result;
+}
+
+iree_status_t check_output_data(iree_hal_buffer_mapping_t *mapped_memory) {
+  iree_status_t result = iree_ok_status();
+  for (int i = 0; i < mapped_memory->contents.data_length / sizeof(int32_t);
+       ++i) {
+    if (((const int32_t *)mapped_memory->contents.data)[i] != (i >> 1) * i) {
+      result = iree_make_status(IREE_STATUS_UNKNOWN, "result mismatches");
+      break;
+    }
+  }
+  return result;
+}
diff --git a/samples/simple_vec_mul/simple_float_mul.mlir b/samples/simple_vec_mul/simple_float_mul.mlir
new file mode 100644
index 0000000..7a928ff
--- /dev/null
+++ b/samples/simple_vec_mul/simple_float_mul.mlir
@@ -0,0 +1,5 @@
+func @simple_mul(%arg0: tensor<1024xf32>, %arg1: tensor<1024xf32>) -> tensor<1024xf32>
+{
+  %0 = "mhlo.multiply"(%arg0, %arg1) : (tensor<1024xf32>, tensor<1024xf32>) -> tensor<1024xf32>
+  return %0 : tensor<1024xf32>
+}
diff --git a/samples/simple_vec_mul/simple_int_mul.mlir b/samples/simple_vec_mul/simple_int_mul.mlir
new file mode 100644
index 0000000..9aa9a22
--- /dev/null
+++ b/samples/simple_vec_mul/simple_int_mul.mlir
@@ -0,0 +1,5 @@
+func @simple_mul(%arg0: tensor<1024xi32>, %arg1: tensor<1024xi32>) -> tensor<1024xi32>
+{
+  %0 = "mhlo.multiply"(%arg0, %arg1) : (tensor<1024xi32>, tensor<1024xi32>) -> tensor<1024xi32>
+  return %0 : tensor<1024xi32>
+}
diff --git a/samples/simple_vec_mul/simple_vec_mul.c b/samples/simple_vec_mul/simple_vec_mul.c
new file mode 100644
index 0000000..a2951ea
--- /dev/null
+++ b/samples/simple_vec_mul/simple_vec_mul.c
@@ -0,0 +1,169 @@
+// An example based on iree/samples/simple_embedding. Test a 1024-element int32
+// multiplication with vector extension ISA.
+
+#include <springbok.h>
+#include <stdio.h>
+
+#include "iree/base/api.h"
+#include "iree/hal/api.h"
+#include "iree/modules/hal/module.h"
+#include "iree/vm/api.h"
+#include "iree/vm/bytecode_module.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.
+extern iree_status_t create_sample_device(iree_hal_device_t **device);
+
+extern const iree_const_byte_span_t load_bytecode_module_data();
+
+// Prepare the input buffers and buffer_views based on the data type. They must
+// be released by the caller.
+extern iree_status_t prepare_input_hal_buffer_views(
+    iree_hal_device_t *device, const int buffer_length, void **arg0_buffer,
+    void **arg1_buffer, iree_hal_buffer_view_t **arg0_buffer_view,
+    iree_hal_buffer_view_t **arg1_buffer_view);
+
+extern iree_status_t check_output_data(
+    iree_hal_buffer_mapping_t *mapped_memory);
+
+iree_status_t run() {
+  IREE_RETURN_IF_ERROR(iree_hal_module_register_types());
+
+  iree_vm_instance_t *instance = NULL;
+  iree_status_t result =
+      iree_vm_instance_create(iree_allocator_system(), &instance);
+
+  iree_hal_device_t *device = NULL;
+  if (iree_status_is_ok(result)) {
+    result = create_sample_device(&device);
+  }
+  iree_vm_module_t *hal_module = NULL;
+  if (iree_status_is_ok(result)) {
+    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;
+  if (iree_status_is_ok(result)) {
+    result = iree_vm_bytecode_module_create(module_data, iree_allocator_null(),
+                                            iree_allocator_system(),
+                                            &bytecode_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};
+  if (iree_status_is_ok(result)) {
+    result = iree_vm_context_create_with_modules(
+        instance, &modules[0], IREE_ARRAYSIZE(modules), iree_allocator_system(),
+        &context);
+  }
+  iree_vm_module_release(hal_module);
+  iree_vm_module_release(bytecode_module);
+
+  // Lookup the entry point function.
+  // Note that we use the synchronous variant which operates on pure type/shape
+  // erased buffers.
+  const char kMainFunctionName[] = "module.simple_mul";
+  iree_vm_function_t main_function;
+  if (iree_status_is_ok(result)) {
+    result = (iree_vm_context_resolve_function(
+        context, iree_make_cstring_view(kMainFunctionName), &main_function));
+  }
+
+  // Prepare the input buffers.
+  void *arg0_buffer = NULL;
+  void *arg1_buffer = NULL;
+  iree_hal_buffer_view_t *arg0_buffer_view = NULL;
+  iree_hal_buffer_view_t *arg1_buffer_view = NULL;
+  const int kElementCount = 1024;
+  if (iree_status_is_ok(result)) {
+    result = prepare_input_hal_buffer_views(device, kElementCount, &arg0_buffer,
+                                            &arg1_buffer, &arg0_buffer_view,
+                                            &arg1_buffer_view);
+  }
+
+  // Setup call inputs with our buffers.
+  iree_vm_list_t *inputs = NULL;
+  if (iree_status_is_ok(result)) {
+    result = iree_vm_list_create(
+        /*element_type=*/NULL,
+        /*capacity=*/2, iree_allocator_system(), &inputs);
+  }
+  iree_vm_ref_t arg0_buffer_view_ref =
+      iree_hal_buffer_view_move_ref(arg0_buffer_view);
+  iree_vm_ref_t arg1_buffer_view_ref =
+      iree_hal_buffer_view_move_ref(arg1_buffer_view);
+  if (iree_status_is_ok(result)) {
+    result = iree_vm_list_push_ref_move(inputs, &arg0_buffer_view_ref);
+  }
+  if (iree_status_is_ok(result)) {
+    result = iree_vm_list_push_ref_move(inputs, &arg1_buffer_view_ref);
+  }
+
+  // Prepare outputs list to accept the results from the invocation.
+  // The output vm list is allocated statically.
+  iree_vm_list_t *outputs = NULL;
+  if (iree_status_is_ok(result)) {
+    result = iree_vm_list_create(
+        /*element_type=*/NULL,
+        /*capacity=*/1, iree_allocator_system(), &outputs);
+  }
+
+  // Invoke the function.
+  if (iree_status_is_ok(result)) {
+    result = iree_vm_invoke(context, main_function,
+                            /*policy=*/NULL, inputs, outputs,
+                            iree_allocator_system());
+  }
+
+  // Get the result buffers from the invocation.
+  iree_hal_buffer_view_t *ret_buffer_view =
+      (iree_hal_buffer_view_t *)iree_vm_list_get_ref_deref(
+          outputs, 0, iree_hal_buffer_view_get_descriptor());
+  if (ret_buffer_view == NULL) {
+    result = iree_make_status(IREE_STATUS_NOT_FOUND,
+                              "can't find return buffer view");
+  }
+
+  // Read back the results and ensure we got the right values.
+  iree_hal_buffer_mapping_t mapped_memory;
+  if (iree_status_is_ok(result)) {
+    result = iree_hal_buffer_map_range(
+        iree_hal_buffer_view_buffer(ret_buffer_view),
+        IREE_HAL_MEMORY_ACCESS_READ, 0, IREE_WHOLE_BUFFER, &mapped_memory);
+  }
+  if (iree_status_is_ok(result)) {
+    result = check_output_data(&mapped_memory);
+  }
+  iree_hal_buffer_unmap_range(&mapped_memory);
+
+  iree_vm_list_release(inputs);
+  iree_vm_list_release(outputs);
+  iree_aligned_free(arg0_buffer);
+  iree_aligned_free(arg1_buffer);
+  iree_hal_device_release(device);
+  iree_vm_context_release(context);
+  iree_vm_instance_release(instance);
+  return result;
+}
+
+int main() {
+  const iree_status_t result = run();
+  int ret = (int)iree_status_code(result);
+  if (!iree_status_is_ok(result)) {
+    char *status_buffer = NULL;
+    iree_host_size_t status_buffer_length = 0;
+    iree_status_to_string(result, &status_buffer, &status_buffer_length);
+    LOG_ERROR("%.*s", (int)status_buffer_length, status_buffer);
+    free(status_buffer);
+    iree_status_free(result);
+  } else {
+    LOG_INFO("simple_vec_mul finished successfully");
+  }
+
+  return ret;
+}