// Copyright 2023 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 "iree/tooling/instrument_util.h"

#include <memory.h>
#include <stdio.h>
#include <string.h>

#include "iree/base/internal/flags.h"
#include "iree/modules/hal/types.h"

//===----------------------------------------------------------------------===//
// Instrument data management
//===----------------------------------------------------------------------===//

IREE_FLAG(string, instrument_file, "",
          "File to populate with instrument data from the program.");

static iree_status_t iree_tooling_write_iovec(iree_vm_ref_t iovec, FILE* file) {
  IREE_TRACE_ZONE_BEGIN(z0);
  bool write_ok = false;
  if (iree_vm_buffer_isa(iovec)) {
    iree_vm_buffer_t* buffer = iree_vm_buffer_deref(iovec);
    IREE_TRACE_ZONE_APPEND_VALUE_I64(z0,
                                     (int64_t)iree_vm_buffer_length(buffer));
    write_ok =
        fwrite(iree_vm_buffer_data(buffer), 1, iree_vm_buffer_length(buffer),
               file) == iree_vm_buffer_length(buffer);
  } else if (iree_hal_buffer_view_isa(iovec)) {
    iree_hal_buffer_view_t* buffer_view = iree_hal_buffer_view_deref(iovec);
    IREE_TRACE_ZONE_APPEND_VALUE_I64(
        z0, (int64_t)iree_hal_buffer_view_byte_length(buffer_view));
    iree_hal_buffer_mapping_t mapping;
    IREE_RETURN_AND_END_ZONE_IF_ERROR(
        z0, iree_hal_buffer_map_range(iree_hal_buffer_view_buffer(buffer_view),
                                      IREE_HAL_MAPPING_MODE_SCOPED,
                                      IREE_HAL_MEMORY_ACCESS_READ, 0,
                                      IREE_WHOLE_BUFFER, &mapping));
    write_ok = fwrite(mapping.contents.data, 1, mapping.contents.data_length,
                      file) == mapping.contents.data_length;
    IREE_IGNORE_ERROR(iree_hal_buffer_unmap_range(&mapping));
  }
  IREE_TRACE_ZONE_END(z0);
  return write_ok ? iree_ok_status()
                  : iree_make_status(iree_status_code_from_errno(errno),
                                     "failed to write iovec to file");
}

iree_status_t iree_tooling_process_instrument_data(
    iree_vm_context_t* context, iree_allocator_t host_allocator) {
  // If no flag was specified we ignore instrument data.
  if (strlen(FLAG_instrument_file) == 0) return iree_ok_status();

  IREE_TRACE_ZONE_BEGIN(z0);
  IREE_TRACE_ZONE_APPEND_TEXT(z0, FLAG_instrument_file);

  // Open the file for overwriting. We do this even if there is no instrument
  // data in the program as we'd rather have the user end up with a 0-byte file
  // when they explicitly ask for it instead of stale data from previous runs.
  FILE* file = fopen(FLAG_instrument_file, "wb");
  if (!file) {
    IREE_TRACE_ZONE_END(z0);
    return iree_make_status(iree_status_code_from_errno(errno),
                            "failed to open instrument file '%s' for writing",
                            FLAG_instrument_file);
  }

  // Each query function pushes iovecs on to a list we provide; we create one
  // list and use that across all of them.
  iree_vm_list_t* iovec_list = NULL;
  IREE_RETURN_AND_END_ZONE_IF_ERROR(
      z0, iree_vm_list_create(iree_vm_make_undefined_type_def(), 8,
                              host_allocator, &iovec_list));

  iree_vm_list_t* input_list = NULL;
  iree_status_t status = iree_vm_list_create(iree_vm_make_undefined_type_def(),
                                             8, host_allocator, &input_list);
  if (iree_status_is_ok(status)) {
    iree_vm_ref_t iovec_list_ref = iree_vm_list_retain_ref(iovec_list);
    status = iree_vm_list_push_ref_move(input_list, &iovec_list_ref);
  }

  // Process instrument data from all modules in the context.
  if (iree_status_is_ok(status)) {
    for (iree_host_size_t i = 0; i < iree_vm_context_module_count(context);
         ++i) {
      iree_vm_module_t* module = iree_vm_context_module_at(context, i);
      if (!module) continue;

      // Find the query function, if present.
      iree_vm_function_t query_func;
      iree_status_t lookup_status = iree_vm_module_lookup_function_by_name(
          module, IREE_VM_FUNCTION_LINKAGE_EXPORT_OPTIONAL,
          IREE_SV("__query_instruments"), &query_func);
      if (!iree_status_is_ok(lookup_status)) {
        // Skip missing/invalid query function.
        iree_status_ignore(lookup_status);
        continue;
      }

      IREE_TRACE_ZONE_BEGIN(z1);
      IREE_TRACE_ZONE_APPEND_TEXT(z1, iree_vm_module_name(module).data,
                                  iree_vm_module_name(module).size);
      status = iree_vm_invoke(context, query_func, IREE_VM_INVOCATION_FLAG_NONE,
                              NULL, input_list, NULL, host_allocator);
      IREE_TRACE_ZONE_END(z1);
      if (!iree_status_is_ok(status)) break;
    }
  }

  if (iree_status_is_ok(status)) {
    for (iree_host_size_t i = 0; i < iree_vm_list_size(iovec_list); ++i) {
      iree_vm_ref_t iovec = iree_vm_ref_null();
      status = iree_vm_list_get_ref_assign(iovec_list, i, &iovec);
      if (!iree_status_is_ok(status)) break;
      status = iree_tooling_write_iovec(iovec, file);
      if (!iree_status_is_ok(status)) break;
    }
  }

  iree_vm_list_release(input_list);
  iree_vm_list_release(iovec_list);
  fclose(file);
  IREE_TRACE_ZONE_END(z0);
  return status;
}
