/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2013 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include "xenia/cpu/ppc/ppc_translator.h"

#include "xenia/base/assert.h"
#include "xenia/base/byte_order.h"
#include "xenia/base/memory.h"
#include "xenia/base/profiling.h"
#include "xenia/base/reset_scope.h"
#include "xenia/cpu/compiler/compiler_passes.h"
#include "xenia/cpu/cpu_flags.h"
#include "xenia/cpu/ppc/ppc_frontend.h"
#include "xenia/cpu/ppc/ppc_hir_builder.h"
#include "xenia/cpu/ppc/ppc_opcode_info.h"
#include "xenia/cpu/ppc/ppc_scanner.h"
#include "xenia/cpu/processor.h"

namespace xe {
namespace cpu {
namespace ppc {

using xe::cpu::backend::Backend;
using xe::cpu::compiler::Compiler;
namespace passes = xe::cpu::compiler::passes;

PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) {
  Backend* backend = frontend->processor()->backend();

  scanner_.reset(new PPCScanner(frontend));
  builder_.reset(new PPCHIRBuilder(frontend));
  compiler_.reset(new Compiler(frontend->processor()));
  assembler_ = backend->CreateAssembler();
  assembler_->Initialize();

  bool validate = cvars::validate_hir;

  // Merge blocks early. This will let us use more context in other passes.
  // The CFG is required for simplification and dirtied by it.
  compiler_->AddPass(std::make_unique<passes::ControlFlowAnalysisPass>());
  compiler_->AddPass(std::make_unique<passes::ControlFlowSimplificationPass>());

  // Passes are executed in the order they are added. Multiple of the same
  // pass type may be used.
  if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
  compiler_->AddPass(std::make_unique<passes::ContextPromotionPass>());
  if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());

  // Grouped simplification + constant propagation.
  // Loops until no changes are made.
  auto sap = std::make_unique<passes::ConditionalGroupPass>();
  sap->AddPass(std::make_unique<passes::SimplificationPass>());
  if (validate) sap->AddPass(std::make_unique<passes::ValidationPass>());
  sap->AddPass(std::make_unique<passes::ConstantPropagationPass>());
  if (validate) sap->AddPass(std::make_unique<passes::ValidationPass>());
  compiler_->AddPass(std::move(sap));

  if (backend->machine_info()->supports_extended_load_store) {
    // Backend supports the advanced LOAD/STORE instructions.
    // These will save us a lot of HIR opcodes.
    compiler_->AddPass(
        std::make_unique<passes::MemorySequenceCombinationPass>());
    if (validate)
      compiler_->AddPass(std::make_unique<passes::ValidationPass>());
  }
  compiler_->AddPass(std::make_unique<passes::SimplificationPass>());
  if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());
  // compiler_->AddPass(std::make_unique<passes::DeadStoreEliminationPass>());
  // if (validate)
  // compiler_->AddPass(std::make_unique<passes::ValidationPass>());
  compiler_->AddPass(std::make_unique<passes::DeadCodeEliminationPass>());
  if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());

  //// Removes all unneeded variables. Try not to add new ones after this.
  // compiler_->AddPass(new passes::ValueReductionPass());
  // if (validate) compiler_->AddPass(new passes::ValidationPass());

  // Register allocation for the target backend.
  // Will modify the HIR to add loads/stores.
  // This should be the last pass before finalization, as after this all
  // registers are assigned and ready to be emitted.
  compiler_->AddPass(std::make_unique<passes::RegisterAllocationPass>(
      backend->machine_info()));
  if (validate) compiler_->AddPass(std::make_unique<passes::ValidationPass>());

  // Must come last. The HIR is not really HIR after this.
  compiler_->AddPass(std::make_unique<passes::FinalizationPass>());
}

PPCTranslator::~PPCTranslator() = default;

bool PPCTranslator::Translate(GuestFunction* function,
                              uint32_t debug_info_flags) {
  SCOPE_profile_cpu_f("cpu");

  // Reset() all caching when we leave.
  xe::make_reset_scope(builder_);
  xe::make_reset_scope(compiler_);
  xe::make_reset_scope(assembler_);
  xe::make_reset_scope(&string_buffer_);

  // NOTE: we only want to do this when required, as it's expensive to build.
  if (cvars::disassemble_functions) {
    debug_info_flags |= DebugInfoFlags::kDebugInfoAllDisasm;
  }
  if (cvars::trace_functions) {
    debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctions;
  }
  if (cvars::trace_function_coverage) {
    debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctionCoverage;
  }
  if (cvars::trace_function_references) {
    debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctionReferences;
  }
  if (cvars::trace_function_data) {
    debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctionData;
  }
  std::unique_ptr<FunctionDebugInfo> debug_info;
  if (debug_info_flags) {
    debug_info.reset(new FunctionDebugInfo());
  }

  // Scan the function to find its extents and gather debug data.
  if (!scanner_->Scan(function, debug_info.get())) {
    return false;
  }

  // Setup trace data, if needed.
  if (debug_info_flags & DebugInfoFlags::kDebugInfoTraceFunctions) {
    // Base trace data.
    size_t trace_data_size = FunctionTraceData::SizeOfHeader();
    if (debug_info_flags & DebugInfoFlags::kDebugInfoTraceFunctionCoverage) {
      // Additional space for instruction coverage counts.
      trace_data_size += FunctionTraceData::SizeOfInstructionCounts(
          function->address(), function->end_address());
    }
    uint8_t* trace_data =
        frontend_->processor()->AllocateFunctionTraceData(trace_data_size);
    if (trace_data) {
      function->trace_data().Reset(trace_data, trace_data_size,
                                   function->address(),
                                   function->end_address());
    } else {
      debug_info_flags &= ~(DebugInfoFlags::kDebugInfoTraceFunctions |
                            DebugInfoFlags::kDebugInfoTraceFunctionCoverage);
    }
  }

  // Stash source.
  if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmSource) {
    DumpSource(function, &string_buffer_);
    debug_info->set_source_disasm(strdup(string_buffer_.buffer()));
    string_buffer_.Reset();
  }

  // Emit function.
  uint32_t emit_flags = 0;
  if (debug_info) {
    emit_flags |= PPCHIRBuilder::EMIT_DEBUG_COMMENTS;
  }
  if (!builder_->Emit(function, emit_flags)) {
    return false;
  }

  // Stash raw HIR.
  if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmRawHir) {
    builder_->Dump(&string_buffer_);
    debug_info->set_raw_hir_disasm(strdup(string_buffer_.buffer()));
    string_buffer_.Reset();
  }

  // Compile/optimize/etc.
  if (!compiler_->Compile(builder_.get())) {
    return false;
  }

  // Stash optimized HIR.
  if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmHir) {
    builder_->Dump(&string_buffer_);
    debug_info->set_hir_disasm(strdup(string_buffer_.buffer()));
    string_buffer_.Reset();
  }

  // Assemble to backend machine code.
  if (!assembler_->Assemble(function, builder_.get(), debug_info_flags,
                            std::move(debug_info))) {
    return false;
  }

  return true;
}

void PPCTranslator::DumpSource(GuestFunction* function,
                               StringBuffer* string_buffer) {
  Memory* memory = frontend_->memory();

  string_buffer->AppendFormat(
      "{} fn {:08X}-{:08X} {}\n", function->module()->name().c_str(),
      function->address(), function->end_address(), function->name().c_str());

  auto blocks = scanner_->FindBlocks(function);

  uint32_t start_address = function->address();
  uint32_t end_address = function->end_address();
  auto block_it = blocks.begin();
  for (uint32_t address = start_address, offset = 0; address <= end_address;
       address += 4, offset++) {
    uint32_t code =
        xe::load_and_swap<uint32_t>(memory->TranslateVirtual(address));

    // Check labels.
    if (block_it != blocks.end() && block_it->start_address == address) {
      string_buffer->AppendFormat("{:08X}          loc_{:08X}:\n", address,
                                  address);
      ++block_it;
    }

    string_buffer->AppendFormat("{:08X} {:08X}   ", address, code);
    DisasmPPC(address, code, string_buffer);
    string_buffer->Append('\n');
  }
}

}  // namespace ppc
}  // namespace cpu
}  // namespace xe
