/*
 * Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*------------------------------------------------------------------------*/
/* Includes                                                                             */
/*------------------------------------------------------------------------*/

#include <map>

#include "mem/ruby/storebuffer/hfa.hh"
#include "mem/ruby/storebuffer/storebuffer.hh"
#include "mem/ruby/common/Global.hh"

#if RUBY_TSO_CHECKER
#include "TsoChecker.hh"
#endif

#define SYSTEM_EXIT ASSERT(0)


// global map of request id_s to map them back to storebuffer pointers
map <uint64_t, StoreBuffer *> request_map;

#if RUBY_TSO_CHECKER
Tso::TsoChecker * g_tsoChecker;
#endif

void hit(int64_t id) {
  if (request_map.find(id) == request_map.end()) {
    ERROR_OUT("Request ID not found in the map");
    DEBUG_EXPR(STOREBUFFER_COMP, MedPrio, id);
    ASSERT(0);
  }
  else {
    request_map[id]->complete(id);
    request_map.erase(id);
  }
}


//*****************************************************************************************
StoreBuffer::StoreBuffer(uint32 id, uint32 block_bits, int storebuffer_size) {
#if RUBY_TSO_CHECKER
  if (id == 0) {
    g_tsoChecker = new Tso::TsoChecker();
    g_tsoChecker->init(64);
  }
#endif
  iseq = 0;
  tso_iseq = 0;
  char name [] = "Sequencer_";
  char port_name [13];
  sprintf(port_name, "%s%d", name, id);
  m_port = libruby_get_port(port_name, hit);
  m_hit_callback = NULL;
  ASSERT(storebuffer_size >= 0);
  m_storebuffer_size = storebuffer_size;
  m_id = id;
  m_block_size = 1 << block_bits;
  m_block_mask = ~(m_block_size - 1);
  m_buffer_size = 0;
  m_use_storebuffer = false;
  m_storebuffer_full = false;
  m_storebuffer_flushing = false;
  m_stalled_issue = true;
  if(m_storebuffer_size > 0){
    m_use_storebuffer = true;
  }

  #ifdef DEBUG_WRITE_BUFFER
      DEBUG_OUT("*******storebuffer_t::Using Write Buffer? %d\n",m_use_storebuffer);
  #endif
}

//******************************************************************************************
StoreBuffer::~StoreBuffer(){
#if RUBY_TSO_CHECKER
  if (m_id == 0) {
    delete g_tsoChecker;
  }
#endif
}

//*****************************************************************************************************
void StoreBuffer::registerHitCallback(void (*hit_callback)(int64_t request_id)) {
  assert(m_hit_callback == NULL); // can't assign hit_callback twice
  m_hit_callback = hit_callback;
}


//*****************************************************************************************************
void StoreBuffer::addToStoreBuffer(struct RubyRequest request){
  if(m_use_storebuffer){
    #ifdef DEBUG_WRITE_BUFFER
       DEBUG_OUT("\n***StoreBuffer: addToStoreBuffer BEGIN, contents:\n");
       DEBUG_OUT("\n");
    #endif

    #ifdef DEBUG_WRITE_BUFFER
       DEBUG_OUT("\t INSERTING new request\n");
    #endif


    buffer.push_front(SBEntry(request, NULL));

    m_buffer_size++;

    if (m_buffer_size >= m_storebuffer_size) {
      m_storebuffer_full = true;
    }
    else if (m_stalled_issue) {
      m_stalled_issue = false;
      issueNextStore();
    }

    iseq++;

     #ifdef DEBUG_WRITE_BUFFER
       DEBUG_OUT("***StoreBuffer: addToStoreBuffer END, contents:\n");
       DEBUG_OUT("\n");
     #endif
  }  //end if(m_use_storebuffer)
  else {
    // make request to libruby
    uint64_t id = libruby_issue_request(m_port, request);
    if (request_map.find(id) != request_map.end()) {
      ERROR_OUT("Request ID is already in the map");
      DEBUG_EXPR(STOREBUFFER_COMP, MedPrio, id);
      ASSERT(0);
    }
    else {
      request_map.insert(make_pair(id, this));
      outstanding_requests.insert(make_pair(id, request));
    }
  }
}


//*****************************************************************************************************
// Return value of -2 indicates that the load request was satisfied by the store buffer
// Return value of -3 indicates a partial match, so the load has to retry until NO_MATCH
// Alternatively we could satisfy the partial match, but tso gets complicated and more races
//*****************************************************************************************************
int64_t StoreBuffer::handleLoad(struct RubyRequest request) {
  if (m_use_storebuffer) {
    load_match match = checkForLoadHit(request);
    if (match == FULL_MATCH) {
      // fill data
      returnMatchedData(request);
      iseq++;
      return -2;
    }
    else if (match == NO_MATCH) {
      // make request to libruby and return the id
      uint64_t id = libruby_issue_request(m_port, request);
      if (request_map.find(id) != request_map.end()) {
        ERROR_OUT("Request ID is already in the map");
        DEBUG_EXPR(STOREBUFFER_COMP, MedPrio, id);
        ASSERT(0);
      }
      else {
        request_map.insert(make_pair(id, this));
        outstanding_requests.insert(make_pair(id, request));
      }
      iseq++;
      return id;
    }
    else { // partial match
      return -3;
    }
  }
  else {
    // make a request to ruby
    return libruby_issue_request(m_port, request);
  }
}


//*****************************************************************************************************
// This function will fill the data array if any match is found
//*****************************************************************************************************
load_match StoreBuffer::checkForLoadHit(struct RubyRequest request) {
  if (m_use_storebuffer) {
    physical_address_t physical_address = request.paddr;
    int len = request.len;

    uint8_t * data = new uint8_t[64];
    memset(data, 0, 64);
    for (int i = physical_address%64; i < len; i++) {
      data[i] = 1;
    }

    bool found = false;
    physical_address_t lineaddr = physical_address & m_block_mask;

    // iterate over the buffer looking for hits
    for (deque<struct SBEntry>::iterator it = buffer.begin(); it != buffer.end(); it++) {
      if ((it->m_request.paddr & m_block_mask) == lineaddr) {
        found = true;
        for (int i = it->m_request.paddr%64; i < it->m_request.len; i++) {
          data[i] = 0;
        }
      }
    }

    // if any matching entry is found, determine if all the requested bytes have been matched
    if (found) {
      ASSERT(m_buffer_size > 0);
      int unmatched_bytes = 0;
      for (int i = physical_address%64; i < len; i++) {
        unmatched_bytes = unmatched_bytes + data[i];
      }
      if (unmatched_bytes == 0) {
        delete data;
        return FULL_MATCH;
      }
      else {
        delete data;
        return PARTIAL_MATCH;
      }
    }
    else {
      delete data;
      return NO_MATCH;
    }
  } // end of if (m_use_storebuffer)
  else {
    // this function should never be called if we are not using a store buffer
    ERROR_OUT("checkForLoadHit called while write buffer is not in use");
    ASSERT(0);
  }
}


//***************************************************************************************************
void StoreBuffer::returnMatchedData(struct RubyRequest request) {
  if (m_use_storebuffer) {

    uint8_t * data = new uint8_t[64];
    memset(data, 0, 64);
    uint8_t * written = new uint8_t[64];
    memset(written, 0, 64);

    physical_address_t physical_address = request.paddr;
    int len = request.len;

    ASSERT(checkForLoadHit(request) != NO_MATCH);
    physical_address_t lineaddr = physical_address & m_block_mask;
    bool found = false;
#if RUBY_TSO_CHECKER
    Tso::TsoCheckerCmd * cmd;
#endif
    deque<struct SBEntry>::iterator satisfying_store;
    for (deque<struct SBEntry>::iterator it = buffer.begin(); it != buffer.end(); it++) {
      if ((it->m_request.paddr & m_block_mask) == lineaddr) {
        if (!found) {
          found = true;
#if RUBY_TSO_CHECKER
          satisfying_store = it;
          cmd = new Tso::TsoCheckerCmd(m_id, // this thread id
                            iseq, // instruction sequence
                            ITYPE_LOAD, // is a store
                            MEM_LOAD_DATA, // commit
                            request.paddr, // the address
                            NULL, // and data
                            request.len,  // and len
                            DSRC_STB, // shouldn't matter
                            libruby_get_time(), // macc: for store macc and time are the same and it
                            0, // gobs
                            0);
#endif
        }
        uint8_t * dataPtr = it->m_request.data;
        int offset = it->m_request.paddr%64;
        for (int i = offset; i < it->m_request.len; i++) {
          if (!written[i]) { // don't overwrite data with earlier data
            data[i] = dataPtr[i-offset];
            written[i] = 1;
          }
        }
      }
    }

    int i = physical_address%64;
    for (int j = 0; (i < physical_address%64 + len) && (j < len); i++, j++) {
      if (written[i]) {
        request.data[j] = data[i];
      }
    }

#if RUBY_TSO_CHECKER
    uint64_t tso_data = 0;
    memcpy(&tso_data, request.data, request.len);
    cmd->setData(tso_data);

    Tso::TsoCheckerCmd * adjust_cmd = satisfying_store->m_next_ptr;
    if (adjust_cmd == NULL) {
      adjust_cmd = cmd;
    }
    else {
      while (adjust_cmd->getNext() != NULL) {
        adjust_cmd = adjust_cmd->getNext();
      }
      adjust_cmd->setNext(cmd);
    }
#endif

    delete data;
    delete written;
  }
  else {
    ERROR_OUT("returnMatchedData called while write buffer is not in use");
    ASSERT(0);
  }
}


//******************************************************************************************
void StoreBuffer::flushStoreBuffer(){
  if (m_use_storebuffer) {
     #ifdef DEBUG_WRITE_BUFFER
         DEBUG_OUT("\n***StoreBuffer: flushStoreBuffer BEGIN, contents:\n");
         DEBUG_OUT("\n");
     #endif

     if(m_buffer_size > 0) {
       m_storebuffer_flushing = true; // indicate that we are flushing
     }
     else {
       m_storebuffer_flushing = false;
       return;
     }
  }
  else {
    // do nothing
    return;
  }
}

//****************************************************************************************
void StoreBuffer::issueNextStore() {
  SBEntry request = buffer.back();
  uint64_t id = libruby_issue_request(m_port, request.m_request);
  if (request_map.find(id) != request_map.end()) {
    assert(0);
  }
  else {
    request_map.insert(make_pair(id, this));
    outstanding_requests.insert(make_pair(id, request.m_request));
  }
}

//****************************************************************************************
void StoreBuffer::complete(uint64_t id) {
  if (m_use_storebuffer) {
    ASSERT(outstanding_requests.find(id) != outstanding_requests.end());
    physical_address_t physical_address = outstanding_requests.find(id)->second.paddr;
    RubyRequestType type = outstanding_requests.find(id)->second.type;
#ifdef DEBUG_WRITE_BUFFER
    DEBUG_OUT("\n***StoreBuffer: complete BEGIN, contents:\n");
    DEBUG_OUT("\n");
#endif

    if (type == RubyRequestType_ST) {
      physical_address_t lineaddr = physical_address & m_block_mask;

      //Note fastpath hits are handled like regular requests - they must remove the WB entry!
      if ( lineaddr != physical_address ) {
        ERROR_OUT("error: StoreBuffer: ruby returns pa 0x%0llx which is not a cache line: 0x%0llx\n", physical_address, lineaddr );
      }

      SBEntry from_buffer = buffer.back();
      if (((from_buffer.m_request.paddr & m_block_mask) == lineaddr) && (from_buffer.m_request.type == type)) {
        buffer.pop_back();
        m_buffer_size--;
        ASSERT(m_buffer_size >= 0);

#if RUBY_TSO_CHECKER
        int len = outstanding_requests.find(id)->second.len;
        uint64_t data = 0;
        memcpy(&data, from_buffer.m_request.data, 4);

        cerr << m_id << " INSERTING STORE" << endl << flush;
        // add to the tsoChecker
        g_tsoChecker->input(m_id, // this thread id
                          (id & ISEQ_MASK), // instruction sequence
                          ITYPE_STORE, // is a store
                          MEM_STORE_COMMIT, // commit
                          physical_address, // the address
                          data, // and data
                          len,  // and len
                          DSRC_STB, // shouldn't matter
                          libruby_get_time(), // macc
                          libruby_get_time(), // gobs
                          libruby_get_time()); // time
        tso_iseq++;

        // also add the loads that are satisfied by this store
        if (from_buffer.m_next_ptr != NULL) {
          from_buffer.m_next_ptr->setGobs(libruby_get_time());
          g_tsoChecker->input(*(from_buffer.m_next_ptr));
          cerr << m_id << " INSERTING LOAD for STORE: " << from_buffer.m_next_ptr->getIseq()  << endl << flush;
          tso_iseq++;
          Tso::TsoCheckerCmd * to_input = from_buffer.m_next_ptr->getNext();
          while (to_input != NULL) {
            if (to_input->getGobs() == 0) {
              to_input->setGobs(libruby_get_time());
            }
            cerr << m_id << " INSERTING LOAD iseq for STORE: " << to_input->getIseq() << endl << flush;
            g_tsoChecker->input(*to_input);
            tso_iseq++;
            to_input = to_input->getNext();
          }
        }
#endif
        // schedule the next request
        if (m_buffer_size > 0) {
          issueNextStore();
        }
        else if (m_buffer_size == 0) {
          m_storebuffer_flushing = false;
          m_stalled_issue = true;
        }

        m_storebuffer_full = false;

      }
      else {
        ERROR_OUT("[%d] error: StoreBuffer: at complete, address 0x%0llx not found.\n", m_id, lineaddr);
        ERROR_OUT("StoreBuffer:: complete FAILS\n");
        ASSERT(0);
      }

#ifdef DEBUG_WRITE_BUFFER
      DEBUG_OUT("***StoreBuffer: complete END, contents:\n");
      DEBUG_OUT("\n");
#endif
    } // end if (type == ST)
    else if (type == RubyRequestType_LD) {
#if RUBY_TSO_CHECKER
      RubyRequest request = outstanding_requests.find(id)->second;
      uint64_t data = 0;
      memcpy(&data, request.data, request.len);

      // add to the tsoChecker if in order, otherwise, find a place to put ourselves
      if ((id & ISEQ_MASK) == tso_iseq) {
        tso_iseq++;
        cerr << m_id << " INSERTING LOAD" << endl << flush;
        g_tsoChecker->input(m_id, // this thread id
                          (id & ISEQ_MASK), // instruction sequence
                          ITYPE_LOAD, // is a store
                          MEM_LOAD_DATA, // commit
                          request.paddr, // the address
                          data, // and data
                          request.len,  // and len
                          DSRC_L2_MEMORY, // shouldn't matter  DSRC_L1
                          libruby_get_time(), // macc: for store macc and time are the same and it
                          libruby_get_time(), // macc
                          libruby_get_time()); // time
      }
      else {
        Tso::TsoCheckerCmd * cmd;
        cmd = new Tso::TsoCheckerCmd(m_id, // this thread id
                          (id & ISEQ_MASK), // instruction sequence
                          ITYPE_LOAD, // is a store
                          MEM_LOAD_DATA, // commit
                          request.paddr, // the address
                          data, // and data
                          request.len,  // and len
                          DSRC_L2_MEMORY, // shouldn't matter  DSRC_L1
                          libruby_get_time(), // macc: for store macc and time are the same and it
                          libruby_get_time(), // macc
                          libruby_get_time()); // time
        insertTsoLL(cmd);
      }
#endif
      m_hit_callback(id);
    }

    // LD, ST or FETCH hit callback
    outstanding_requests.erase(id);

  } // end if(m_use_storebuffer)
  else {
    m_hit_callback(id);
  }
}

#if RUBY_TSO_CHECKER
void StoreBuffer::insertTsoLL(Tso::TsoCheckerCmd * cmd) {
  uint64_t count = cmd->getIseq();
  Tso::TsoCheckerCmd * current = NULL;
  Tso::TsoCheckerCmd * previous = NULL;
  deque<struct SBEntry>::reverse_iterator iter;
  bool found = false;
  for (iter = buffer.rbegin(); iter != buffer.rend(); ++ iter) {
    if (iter->m_next_ptr != NULL) {
      current = iter->m_next_ptr->getNext(); // initalize both to the beginning of the linked list
      previous = current;
      while (current != NULL) {
        if (current->getIseq() > count) {
          found = true;
          break;
        }
        previous = current;
        current = current->getNext();
      }
    }
    // break out if found a match, iterator should still point to the right SBEntry
    if (found) {
      break;
    }
  }

  // will insert at the end if not found
  if (!found) {
    buffer.front().m_next_ptr = cmd;
  }
  else if (current == previous) {
    cerr << "INSERTING " << count << " BEFORE: " << iter->m_next_ptr->getIseq();
    Tso::TsoCheckerCmd * temp = iter->m_next_ptr;
    iter->m_next_ptr = cmd;
    cmd->setNext(temp);
  }
  else {
    cerr << "INSERTING " << count << " BETWEEN: " << previous->getIseq() << " AND " << current->getIseq();
    cmd->setNext(current);
    previous->setNext(cmd);
  }
}
#endif

//***************************************************************************************************
void StoreBuffer::print( void )
{
  DEBUG_OUT("[%d] StoreBuffer: Total entries: %d Outstanding: %d\n", m_id, m_buffer_size);

  if(m_use_storebuffer){
  }
  else{
    DEBUG_OUT("\t WRITE BUFFER NOT USED\n");
  }
}