// Copyright 2022 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <stdio.h>

// Low-level IREE VM APIs.
// The higher-level iree/runtime/api.h can be used for more complete ML-like
// programs using the hardware abstraction layer (HAL). This simple sample just
// uses base VM types.
#include "iree/base/api.h"
#include "iree/vm/api.h"
#include "iree/vm/bytecode/module.h"

// HACK: this pokes in to private APIs for IO helpers while we expect
// applications to bring their own IO.
#include "iree/base/internal/file_io.h"

// Custom native module used in the sample.
// Modules may be linked in from native code or other bytecode modules loaded at
// runtime: there's no difference.
#include "module.h"

// NOTE: CHECKs are dangerous but this is a sample; a real application would
// want to handle errors gracefully. We know in this constrained case that
// these won't fail unless something is catastrophically wrong (out of memory,
// solar flares, etc).
int main(int argc, char** argv) {
  if (argc != 3) {
    fprintf(
        stderr,
        "Usage:\n"
        "  custom-module-basic-run - <entry.point> # read from stdin\n"
        "  custom-module-basic-run </path/to/say_hello.vmfb> <entry.point>\n");
    fprintf(stderr, "  (See the README for this sample for details)\n ");
    return -1;
  }

  // Internally IREE does not (in general) use malloc and instead uses the
  // provided allocator to allocate and free memory. Applications can integrate
  // their own allocator as-needed.
  iree_allocator_t allocator = iree_allocator_system();

  // Create the root isolated VM instance that we can create contexts within.
  iree_vm_instance_t* instance = NULL;
  IREE_CHECK_OK(iree_vm_instance_create(IREE_VM_TYPE_CAPACITY_DEFAULT,
                                        allocator, &instance));

  // Create the custom module that can be reused across contexts.
  iree_vm_module_t* custom_module = NULL;
  IREE_CHECK_OK(
      iree_custom_module_basic_create(instance, allocator, &custom_module));

  // Load the module from stdin or a file on disk.
  // Applications can ship and load modules however they want (such as mapping
  // them into memory instead of allocating like this). Modules can also be
  // embedded in the binary but in those cases it makes more sense to use emitc
  // to avoid the bytecode entirely and have a fully static build (see
  // samples/emitc_modules/ for some examples).
  const char* module_path = argv[1];
  iree_file_contents_t* module_contents = NULL;
  if (strcmp(module_path, "-") == 0) {
    IREE_CHECK_OK(iree_stdin_read_contents(allocator, &module_contents));
  } else {
    IREE_CHECK_OK(iree_file_read_contents(
        module_path, IREE_FILE_READ_FLAG_DEFAULT, allocator, &module_contents));
  }

  // Load the bytecode module from the vmfb.
  // This module can be reused across multiple contexts.
  // Note that we let the module retain the file contents for as long as needed.
  iree_vm_module_t* bytecode_module = NULL;
  IREE_CHECK_OK(iree_vm_bytecode_module_create(
      instance, module_contents->const_buffer,
      iree_file_contents_deallocator(module_contents), allocator,
      &bytecode_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));

  // Lookup the function by fully-qualified name (module.func).
  iree_vm_function_t function;
  IREE_CHECK_OK(iree_vm_context_resolve_function(
      context, iree_make_cstring_view(argv[2]), &function));

  fprintf(stdout, "INVOKE BEGIN %s\n", argv[2]);
  fflush(stdout);

  // Synchronously invoke the requested function.
  // We don't pass in/out anything in these simple examples so the I/O lists
  // are not needed.
  IREE_CHECK_OK(iree_vm_invoke(context, function, IREE_VM_INVOCATION_FLAG_NONE,
                               /*policy=*/NULL, /*inputs=*/NULL,
                               /*outputs=*/NULL, allocator));

  fprintf(stdout, "INVOKE END\n");
  fflush(stdout);

  iree_vm_context_release(context);
  iree_vm_module_release(bytecode_module);
  iree_vm_module_release(custom_module);
  iree_vm_instance_release(instance);
  return 0;
}
