// 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 <stdio.h>

#include "iree/base/api.h"
#include "iree/io/file_contents.h"
#include "iree/schemas/instruments/dispatch.h"

// NOTE: include order matters:
#include "iree/base/internal/flatcc/parsing.h"
#include "iree/schemas/instruments/dispatch_def_reader.h"

typedef struct {
  iree_instruments_DispatchFunctionDef_vec_t functions_def;
  iree_instruments_DispatchSiteDef_vec_t dispatch_sites_def;
} iree_dispatch_metadata_t;

static iree_status_t iree_tooling_dump_dispatch_metadata(
    const uint8_t* flatbuffer_ptr, iree_host_size_t flatbuffer_size,
    iree_dispatch_metadata_t* out_metadata, FILE* stream) {
  memset(out_metadata, 0, sizeof(*out_metadata));

  iree_instruments_DispatchInstrumentDef_table_t instr_def =
      iree_instruments_DispatchInstrumentDef_as_root(flatbuffer_ptr);

  iree_instruments_DispatchFunctionDef_vec_t functions_def =
      iree_instruments_DispatchInstrumentDef_functions(instr_def);
  out_metadata->functions_def = functions_def;
  for (iree_host_size_t i = 0;
       i < iree_instruments_DispatchFunctionDef_vec_len(functions_def); ++i) {
    fprintf(stream, "\n");
    iree_instruments_DispatchFunctionDef_table_t function_def =
        iree_instruments_DispatchFunctionDef_vec_at(functions_def, i);
    flatbuffers_string_t name =
        iree_instruments_DispatchFunctionDef_name(function_def);
    fprintf(stream,
            "//"
            "===---------------------------------------------------------------"
            "-------===//\n");
    fprintf(stream, "// export[%" PRIhsz "]: %s\n", i, name);
    fprintf(stream,
            "//"
            "===---------------------------------------------------------------"
            "-------===//\n");
    flatbuffers_string_t target =
        iree_instruments_DispatchFunctionDef_target(function_def);
    if (target) fprintf(stream, "//  target: %s\n", target);
    flatbuffers_string_t layout =
        iree_instruments_DispatchFunctionDef_layout(function_def);
    if (layout) fprintf(stream, "//  layout: %s\n", layout);
    flatbuffers_string_t source =
        iree_instruments_DispatchFunctionDef_source(function_def);
    if (source) fprintf(stream, "%s\n", source);
    fprintf(stream, "\n");
  }

  fprintf(stream,
          "//"
          "===---------------------------------------------------------------"
          "-------===//\n");
  iree_instruments_DispatchSiteDef_vec_t dispatch_sites_def =
      iree_instruments_DispatchInstrumentDef_sites(instr_def);
  out_metadata->dispatch_sites_def = dispatch_sites_def;
  for (iree_host_size_t i = 0;
       i < iree_instruments_DispatchSiteDef_vec_len(dispatch_sites_def); ++i) {
    iree_instruments_DispatchSiteDef_table_t dispatch_site_def =
        iree_instruments_DispatchSiteDef_vec_at(dispatch_sites_def, i);
    iree_instruments_DispatchFunctionDef_table_t function_def =
        iree_instruments_DispatchFunctionDef_vec_at(
            functions_def,
            iree_instruments_DispatchSiteDef_function(dispatch_site_def));
    flatbuffers_string_t name =
        iree_instruments_DispatchFunctionDef_name(function_def);
    fprintf(stream, "// dispatch site %" PRIhsz ": %s\n", i, name);
  }
  fprintf(stream,
          "//"
          "===---------------------------------------------------------------"
          "-------===//\n\n");

  return iree_ok_status();
}

static void iree_tooling_dump_print_value(
    iree_instrument_dispatch_value_type_t type, uint64_t raw_value,
    FILE* stream) {
  union {
    int8_t i8;
    int16_t i16;
    int32_t i32;
    int64_t i64;
    float f32;
    double f64;
    uint8_t value_storage[sizeof(uint64_t)];
  } value = {.i64 = raw_value};
  switch (type) {
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_SINT_8:
      fprintf(stream, "%" PRId8, value.i8);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_UINT_8:
      fprintf(stream, "%" PRIu8, value.i8);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_SINT_16:
      fprintf(stream, "%" PRId16, value.i16);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_UINT_16:
      fprintf(stream, "%" PRIu16, value.i16);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_SINT_32:
      fprintf(stream, "%" PRId32, value.i32);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_UINT_32:
      fprintf(stream, "%" PRIu32, value.i32);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_SINT_64:
      fprintf(stream, "%" PRId64, value.i64);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_UINT_64:
      fprintf(stream, "%" PRIu64, value.i64);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_POINTER:
      fprintf(stream, "%16" PRIX64, value.i64);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_FLOAT_16:
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_BFLOAT_16:
      fprintf(stream, "%4" PRIX16, value.i16);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_FLOAT_32:
      fprintf(stream, "%e %f", value.f32, value.f32);
      break;
    case IREE_INSTRUMENT_DISPATCH_VALUE_TYPE_FLOAT_64:
      fprintf(stream, "%e %f", value.f64, value.f64);
      break;
    default:
      fprintf(stream, "<<unknown type: %02X>>", (uint32_t)type);
      break;
  }
}

static iree_status_t iree_tooling_dump_dispatch_ringbuffer(
    const uint8_t* data_ptr, iree_host_size_t data_size,
    const iree_dispatch_metadata_t* metadata, FILE* stream) {
  const uint64_t ring_size = data_size - IREE_INSTRUMENT_DISPATCH_PADDING;
  const uint8_t* ring_data = data_ptr;
  const uint64_t ring_head = *(const uint64_t*)(ring_data + data_size - 8);
  const uint64_t ring_range = iree_min(ring_head, ring_size);

  for (iree_host_size_t i = 0; i < ring_range;) {
    const iree_instrument_dispatch_header_t* header =
        (const iree_instrument_dispatch_header_t*)(ring_data + i);
    switch (header->tag) {
      case IREE_INSTRUMENT_DISPATCH_TYPE_WORKGROUP: {
        const iree_instrument_dispatch_workgroup_t* workgroup =
            (const iree_instrument_dispatch_workgroup_t*)header;
        iree_instruments_DispatchSiteDef_table_t dispatch_site_def =
            iree_instruments_DispatchSiteDef_vec_at(
                metadata->dispatch_sites_def, workgroup->dispatch_id);
        iree_instruments_DispatchFunctionDef_table_t function_def =
            iree_instruments_DispatchFunctionDef_vec_at(
                metadata->functions_def,
                iree_instruments_DispatchSiteDef_function(dispatch_site_def));
        flatbuffers_string_t name_def =
            iree_instruments_DispatchFunctionDef_name(function_def);
        fprintf(stream,
                "%016" PRIX64
                " | WORKGROUP dispatch(%u %s %ux%ux%u) %u,%u,%u pid:%u\n",
                (uint64_t)i, workgroup->dispatch_id, name_def,
                workgroup->workgroup_count_x, workgroup->workgroup_count_y,
                workgroup->workgroup_count_z, workgroup->workgroup_id_x,
                workgroup->workgroup_id_y, workgroup->workgroup_id_z,
                workgroup->processor_id);
        i += sizeof(*workgroup);
        break;
      }
      case IREE_INSTRUMENT_DISPATCH_TYPE_PRINT: {
        const iree_instrument_dispatch_print_t* print =
            (const iree_instrument_dispatch_print_t*)header;
        fprintf(stream, "%016" PRIX64 " | PRINT %.*s\n",
                (uint64_t)print->workgroup_offset, (int)print->length,
                print->data);
        i += iree_host_align(sizeof(*print) + print->length, 16);
        break;
      }
      case IREE_INSTRUMENT_DISPATCH_TYPE_VALUE: {
        const iree_instrument_dispatch_value_t* value =
            (const iree_instrument_dispatch_value_t*)header;
        fprintf(stream, "%016" PRIX64 " | VALUE %04u = ",
                (uint64_t)value->workgroup_offset, (uint32_t)value->ordinal);
        iree_tooling_dump_print_value(value->type, value->bits, stream);
        fputc('\n', stream);
        i += sizeof(*value);
        break;
      }
      case IREE_INSTRUMENT_DISPATCH_TYPE_MEMORY_LOAD: {
        const iree_instrument_dispatch_memory_op_t* op =
            (const iree_instrument_dispatch_memory_op_t*)header;
        fprintf(stream, "%016" PRIX64 " | LOAD  %016" PRIX64 " %u\n",
                (uint64_t)op->workgroup_offset, op->address, (int)op->length);
        i += sizeof(*op);
        break;
      }
      case IREE_INSTRUMENT_DISPATCH_TYPE_MEMORY_STORE: {
        const iree_instrument_dispatch_memory_op_t* op =
            (const iree_instrument_dispatch_memory_op_t*)header;
        fprintf(stream, "%016" PRIX64 " | STORE %016" PRIX64 " %u\n",
                (uint64_t)op->workgroup_offset, op->address, (int)op->length);
        i += sizeof(*op);
        break;
      }
      default:
        return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                "unimplemented dispatch instr type: %u",
                                (uint32_t)header->tag);
    }
  }

  return iree_ok_status();
}

static iree_status_t iree_tooling_dump_instrument_file(
    iree_const_byte_span_t file_contents, FILE* stream) {
  const uint8_t* file_ptr = file_contents.data;
  iree_host_size_t file_size = file_contents.data_length;

  iree_dispatch_metadata_t dispatch_metadata = {0};
  for (iree_host_size_t file_offset = 0; file_offset < file_size;) {
    const iree_idbts_chunk_header_t* header =
        (const iree_idbts_chunk_header_t*)(file_ptr + file_offset);
    const uint8_t* payload = file_ptr + file_offset + sizeof(*header);
    switch (header->type) {
      case IREE_IDBTS_CHUNK_TYPE_DISPATCH_METADATA: {
        IREE_RETURN_IF_ERROR(iree_tooling_dump_dispatch_metadata(
            payload, header->content_length, &dispatch_metadata, stream));
        break;
      }
      case IREE_IDBTS_CHUNK_TYPE_DISPATCH_RINGBUFFER: {
        IREE_RETURN_IF_ERROR(iree_tooling_dump_dispatch_ringbuffer(
            payload, header->content_length, &dispatch_metadata, stream));
        break;
      }
      default:
        return iree_make_status(IREE_STATUS_UNIMPLEMENTED,
                                "unimplemented chunk type: %u",
                                (uint32_t)header->type);
    }
    file_offset +=
        sizeof(*header) + iree_host_align(header->content_length, 16);
  }

  return iree_ok_status();
}

int main(int argc, char** argv) {
  IREE_TRACE_APP_ENTER();

  if (argc < 2) {
    fprintf(stderr,
            "Syntax: iree-dump-instruments instruments.bin > instruments.txt\n"
            "Example usage:\n"
            "  $ iree-compile \\n"
            "        --iree-hal-target-device=local \\n"
            "        --iree-hal-local-target-device-backends=llvm-cpu \\n"
            "        --iree-hal-instrument-dispatches=16mib \\n"
            "        --iree-llvmcpu-instrument-memory-accesses=false \\n"
            "        runtime/src/iree/runtime/demo/simple_mul.mlir \\n"
            "        -o=simple_mul_instr.vmfb\n"
            "  $ iree-run-module \\n"
            "        --device=local-sync \\n"
            "        --module=simple_mul_instr.vmfb \\n"
            "        --function=simple_mul \\n"
            "        --input=4xf32=2 \\n"
            "        --input=4xf32=4 \\n"
            "        --instrument_file=instrument.bin\n"
            "  $ iree-dump-instruments instrument.bin\n"
            "\n");
    IREE_TRACE_APP_EXIT(EXIT_FAILURE);
    return EXIT_FAILURE;
  }

  iree_io_file_contents_t* file_contents = NULL;
  iree_status_t status = iree_io_file_contents_map(
      iree_make_cstring_view(argv[1]), IREE_IO_FILE_ACCESS_READ,
      iree_allocator_system(), &file_contents);
  if (iree_status_is_ok(status)) {
    status =
        iree_tooling_dump_instrument_file(file_contents->const_buffer, stdout);
  }
  iree_io_file_contents_free(file_contents);

  if (!iree_status_is_ok(status)) {
    iree_status_fprint(stderr, status);
    iree_status_free(status);
    IREE_TRACE_APP_EXIT(EXIT_FAILURE);
    return EXIT_FAILURE;
  }
  IREE_TRACE_APP_EXIT(EXIT_SUCCESS);
  return EXIT_SUCCESS;
}
