// Copyright 2009 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <cstring>

#include "Common/CommonFuncs.h"
#include "Core/HW/Memmap.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/PowerPC/PPCCache.h"
#include "Core/PowerPC/PowerPC.h"

namespace PowerPC
{
static const u32 s_plru_mask[8] = {11, 11, 19, 19, 37, 37, 69, 69};
static const u32 s_plru_value[8] = {11, 3, 17, 1, 36, 4, 64, 0};

InstructionCache::InstructionCache()
{
  for (u32 m = 0; m < 0xff; m++)
  {
    u32 w = 0;
    while (m & (1 << w))
      w++;
    way_from_valid[m] = w;
  }

  for (u32 m = 0; m < 128; m++)
  {
    u32 b[7];
    for (int i = 0; i < 7; i++)
      b[i] = m & (1 << i);
    u32 w;
    if (b[0])
      if (b[2])
        if (b[6])
          w = 7;
        else
          w = 6;
      else if (b[5])
        w = 5;
      else
        w = 4;
    else if (b[1])
      if (b[4])
        w = 3;
      else
        w = 2;
    else if (b[3])
      w = 1;
    else
      w = 0;
    way_from_plru[m] = w;
  }
}

void InstructionCache::Reset()
{
  memset(valid, 0, sizeof(valid));
  memset(plru, 0, sizeof(plru));
  memset(lookup_table, 0xff, sizeof(lookup_table));
  memset(lookup_table_ex, 0xff, sizeof(lookup_table_ex));
  memset(lookup_table_vmem, 0xff, sizeof(lookup_table_vmem));
  JitInterface::ClearSafe();
}

void InstructionCache::Init()
{
  memset(data, 0, sizeof(data));
  memset(tags, 0, sizeof(tags));

  Reset();
}

void InstructionCache::Invalidate(u32 addr)
{
  if (!HID0.ICE)
    return;
  // invalidates the whole set
  u32 set = (addr >> 5) & 0x7f;
  for (int i = 0; i < 8; i++)
    if (valid[set] & (1 << i))
    {
      if (tags[set][i] & (ICACHE_VMEM_BIT >> 12))
        lookup_table_vmem[((tags[set][i] << 7) | set) & 0xfffff] = 0xff;
      else if (tags[set][i] & (ICACHE_EXRAM_BIT >> 12))
        lookup_table_ex[((tags[set][i] << 7) | set) & 0x1fffff] = 0xff;
      else
        lookup_table[((tags[set][i] << 7) | set) & 0xfffff] = 0xff;
    }
  valid[set] = 0;
  JitInterface::InvalidateICache(addr & ~0x1f, 32, false);
}

u32 InstructionCache::ReadInstruction(u32 addr)
{
  if (!HID0.ICE)  // instruction cache is disabled
    return Memory::Read_U32(addr);
  u32 set = (addr >> 5) & 0x7f;
  u32 tag = addr >> 12;

  u32 t;
  if (addr & ICACHE_VMEM_BIT)
  {
    t = lookup_table_vmem[(addr >> 5) & 0xfffff];
  }
  else if (addr & ICACHE_EXRAM_BIT)
  {
    t = lookup_table_ex[(addr >> 5) & 0x1fffff];
  }
  else
  {
    t = lookup_table[(addr >> 5) & 0xfffff];
  }

  if (t == 0xff)  // load to the cache
  {
    if (HID0.ILOCK)  // instruction cache is locked
      return Memory::Read_U32(addr);
    // select a way
    if (valid[set] != 0xff)
      t = way_from_valid[valid[set]];
    else
      t = way_from_plru[plru[set]];
    // load
    Memory::CopyFromEmu((u8*)data[set][t], (addr & ~0x1f), 32);
    if (valid[set] & (1 << t))
    {
      if (tags[set][t] & (ICACHE_VMEM_BIT >> 12))
        lookup_table_vmem[((tags[set][t] << 7) | set) & 0xfffff] = 0xff;
      else if (tags[set][t] & (ICACHE_EXRAM_BIT >> 12))
        lookup_table_ex[((tags[set][t] << 7) | set) & 0x1fffff] = 0xff;
      else
        lookup_table[((tags[set][t] << 7) | set) & 0xfffff] = 0xff;
    }

    if (addr & ICACHE_VMEM_BIT)
      lookup_table_vmem[(addr >> 5) & 0xfffff] = t;
    else if (addr & ICACHE_EXRAM_BIT)
      lookup_table_ex[(addr >> 5) & 0x1fffff] = t;
    else
      lookup_table[(addr >> 5) & 0xfffff] = t;
    tags[set][t] = tag;
    valid[set] |= (1 << t);
  }
  // update plru
  plru[set] = (plru[set] & ~s_plru_mask[t]) | s_plru_value[t];
  u32 res = Common::swap32(data[set][t][(addr >> 2) & 7]);
  return res;
}
}
