gem5/src/mem/mem_checker.hh
Brandon Potter a928a438b8 style: [patch 3/22] reduce include dependencies in some headers
Used cppclean to help identify useless includes and removed them. This
involved erroneously included headers, but also cases where forward
declarations could have been used rather than a full include.
2016-11-09 14:27:40 -06:00

570 lines
20 KiB
C++

/*
* Copyright (c) 2014 ARM Limited
* All rights reserved.
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* 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.
*
* Authors: Rune Holm
* Marco Elver
*/
#ifndef __MEM_MEM_CHECKER_HH__
#define __MEM_MEM_CHECKER_HH__
#include <list>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
#include "base/misc.hh"
#include "base/trace.hh"
#include "base/types.hh"
#include "debug/MemChecker.hh"
#include "params/MemChecker.hh"
#include "sim/core.hh"
#include "sim/sim_object.hh"
/**
* MemChecker. Verifies that reads observe the values from permissible writes.
* As memory operations have a start and completion time, we consider them as
* transactions which have a start and end time. Because of this, the lifetimes
* of transactions of memory operations may be overlapping -- we assume that if
* there is overlap between writes, they could be reordered by the memory
* subsystem, and a read could any of these. For more detail, see comments of
* inExpectedData().
*
* For simplicity, the permissible values a read can observe are only dependent
* on the particular location, and we do not consider the effect of multi-byte
* reads or writes. This precludes us from discovering single-copy atomicity
* violations.
*/
class MemChecker : public SimObject
{
public:
/**
* The Serial type is used to be able to uniquely identify a transaction as
* it passes through the system. It's value is independent of any other
* system counters.
*/
typedef uint64_t Serial;
static const Serial SERIAL_INITIAL = 0; //!< Initial serial
/**
* The initial tick the system starts with. Must not be larger than the
* minimum value that curTick() could return at any time in the system's
* execution.
*/
static const Tick TICK_INITIAL = 0;
/**
* The maximum value that curTick() could ever return.
*/
static const Tick TICK_FUTURE = MaxTick;
/**
* Initial data value. No requirements.
*/
static const uint8_t DATA_INITIAL = 0x00;
/**
* The Transaction class captures the lifetimes of read and write
* operations, and the values they consumed or produced respectively.
*/
class Transaction
{
public:
Transaction(Serial _serial,
Tick _start, Tick _complete,
uint8_t _data = DATA_INITIAL)
: serial(_serial),
start(_start), complete(_complete),
data(_data)
{}
public:
Serial serial; //!< Unique identifying serial
Tick start; //!< Start tick
Tick complete; //!< Completion tick
/**
* Depending on the memory operation, the data value either represents:
* for writes, the value written upon start; for reads, the value read
* upon completion.
*/
uint8_t data;
/**
* Orders Transactions for use with std::map.
*/
bool operator<(const Transaction& rhs) const
{ return serial < rhs.serial; }
};
/**
* The WriteCluster class captures sets of writes where all writes are
* overlapping with at least one other write. Capturing writes in this way
* simplifies pruning of writes.
*/
class WriteCluster
{
public:
WriteCluster()
: start(TICK_FUTURE), complete(TICK_FUTURE),
completeMax(TICK_INITIAL), numIncomplete(0)
{}
/**
* Starts a write transaction.
*
* @param serial Unique identifier of the write.
* @param _start When the write was sent off to the memory subsystem.
* @param data The data that this write passed to the memory
* subsystem.
*/
void startWrite(Serial serial, Tick _start, uint8_t data);
/**
* Completes a write transaction.
*
* @param serial Unique identifier of a write *previously started*.
* @param _complete When the write was sent off to the memory
* subsystem.
*/
void completeWrite(Serial serial, Tick _complete);
/**
* Aborts a write transaction.
*
* @param serial Unique identifier of a write *previously started*.
*/
void abortWrite(Serial serial);
/**
* @return true if this cluster's write all completed, false otherwise.
*/
bool isComplete() const { return complete != TICK_FUTURE; }
public:
Tick start; //!< Start of earliest write in cluster
Tick complete; //!< Completion of last write in cluster
/**
* Map of Serial --> Transaction of all writes in cluster; contains
* all, in-flight or already completed.
*/
std::unordered_map<Serial, Transaction> writes;
private:
Tick completeMax;
size_t numIncomplete;
};
typedef std::list<Transaction> TransactionList;
typedef std::list<WriteCluster> WriteClusterList;
/**
* The ByteTracker keeps track of transactions for the *same byte* -- all
* outstanding reads, the completed reads (and what they observed) and write
* clusters (see WriteCluster).
*/
class ByteTracker : public Named
{
public:
ByteTracker(Addr addr = 0, const MemChecker *parent = NULL)
: Named((parent != NULL ? parent->name() : "") +
csprintf(".ByteTracker@%#llx", addr))
{
// The initial transaction has start == complete == TICK_INITIAL,
// indicating that there has been no real write to this location;
// therefore, upon checking, we do not expect any particular value.
readObservations.emplace_back(
Transaction(SERIAL_INITIAL, TICK_INITIAL, TICK_INITIAL,
DATA_INITIAL));
}
/**
* Starts a read transaction.
*
* @param serial Unique identifier for the read.
* @param start When the read was sent off to the memory subsystem.
*/
void startRead(Serial serial, Tick start);
/**
* Given a start and end time (of any read transaction), this function
* iterates through all data that such a read is expected to see. The
* data parameter is the actual value that we observed, and the
* function immediately returns true when a match is found, false
* otherwise.
*
* The set of expected data are:
*
* 1. The last value observed by a read with a completion time before
* this start time (if any).
*
* 2. The data produced by write transactions with a completion after
* the last observed read start time. Only data produced in the
* closest overlapping / earlier write cluster relative to this check
* request is considered, as writes in separate clusters are not
* reordered.
*
* @param start Start time of transaction to validate.
* @param complete End time of transaction to validate.
* @param data The value that we have actually seen.
*
* @return True if a match is found, false otherwise.
*/
bool inExpectedData(Tick start, Tick complete, uint8_t data);
/**
* Completes a read transaction that is still outstanding.
*
* @param serial Unique identifier of a read *previously started*.
* @param complete When the read got a response.
* @param data The data returned by the memory subsystem.
*/
bool completeRead(Serial serial, Tick complete, uint8_t data);
/**
* Starts a write transaction. Wrapper to startWrite of WriteCluster
* instance.
*
* @param serial Unique identifier of the write.
* @param start When the write was sent off to the memory subsystem.
* @param data The data that this write passed to the memory
* subsystem.
*/
void startWrite(Serial serial, Tick start, uint8_t data);
/**
* Completes a write transaction. Wrapper to startWrite of WriteCluster
* instance.
*
* @param serial Unique identifier of a write *previously started*.
* @param complete When the write was sent off to the memory subsystem.
*/
void completeWrite(Serial serial, Tick complete);
/**
* Aborts a write transaction. Wrapper to abortWrite of WriteCluster
* instance.
*
* @param serial Unique identifier of a write *previously started*.
*/
void abortWrite(Serial serial);
/**
* This function returns the expected data that inExpectedData iterated
* through in the last call. If inExpectedData last returned true, the
* set may be incomplete; if inExpectedData last returned false, the
* vector will contain the full set.
*
* @return Reference to internally maintained vector maintaining last
* expected data that inExpectedData iterated through.
*/
const std::vector<uint8_t>& lastExpectedData() const
{ return _lastExpectedData; }
private:
/**
* Convenience function to return the most recent incomplete write
* cluster. Instantiates new write cluster if the most recent one has
* been completed.
*
* @return The most recent incomplete write cluster.
*/
WriteCluster* getIncompleteWriteCluster();
/**
* Helper function to return an iterator to the entry of a container of
* Transaction compatible classes, before a certain tick.
*
* @param before Tick value which should be greater than the
* completion tick of the returned element.
*
* @return Iterator into container.
*/
template <class TList>
typename TList::iterator lastCompletedTransaction(TList *l, Tick before)
{
assert(!l->empty());
// Scanning backwards increases the chances of getting a match
// quicker.
auto it = l->end();
for (--it; it != l->begin() && it->complete >= before; --it);
return it;
}
/**
* Prunes no longer needed transactions. We only keep up to the last /
* most recent of each, readObservations and writeClusters, before the
* first outstanding read.
*
* It depends on the contention / overlap between memory operations to
* the same location of a particular workload how large each of them
* would grow.
*/
void pruneTransactions();
private:
/**
* Maintains a map of Serial -> Transaction for all outstanding reads.
*
* Use an ordered map here, as this makes pruneTransactions() more
* efficient (find first outstanding read).
*/
std::map<Serial, Transaction> outstandingReads;
/**
* List of completed reads, i.e. observations of reads.
*/
TransactionList readObservations;
/**
* List of write clusters for this address.
*/
WriteClusterList writeClusters;
/**
* See lastExpectedData().
*/
std::vector<uint8_t> _lastExpectedData;
};
public:
MemChecker(const MemCheckerParams *p)
: SimObject(p),
nextSerial(SERIAL_INITIAL)
{}
virtual ~MemChecker() {}
/**
* Starts a read transaction.
*
* @param start Tick this read was sent to the memory subsystem.
* @param addr Address for read.
* @param size Size of data expected.
*
* @return Serial representing the unique identifier for this transaction.
*/
Serial startRead(Tick start, Addr addr, size_t size);
/**
* Starts a write transaction.
*
* @param start Tick when this write was sent to the memory subsystem.
* @param addr Address for write.
* @param size Size of data to be written.
* @param data Pointer to size bytes, containing data to be written.
*
* @return Serial representing the unique identifier for this transaction.
*/
Serial startWrite(Tick start, Addr addr, size_t size, const uint8_t *data);
/**
* Completes a previously started read transaction.
*
* @param serial A serial of a read that was previously started and
* matches the address of the previously started read.
* @param complete Tick we received the response from the memory subsystem.
* @param addr Address for read.
* @param size Size of data received.
* @param data Pointer to size bytes, containing data received.
*
* @return True if the data we received is in the expected set, false
* otherwise.
*/
bool completeRead(Serial serial, Tick complete,
Addr addr, size_t size, uint8_t *data);
/**
* Completes a previously started write transaction.
*
* @param serial A serial of a write that was previously started and
* matches the address of the previously started write.
* @param complete Tick we received acknowledgment of completion from the
* memory subsystem.
* @param addr Address for write.
* @param size The size of the data written.
*/
void completeWrite(Serial serial, Tick complete, Addr addr, size_t size);
/**
* Aborts a previously started write transaction.
*
* @param serial A serial of a write that was previously started and
* matches the address of the previously started write.
* @param addr Address for write.
* @param size The size of the data written.
*/
void abortWrite(Serial serial, Addr addr, size_t size);
/**
* Resets the entire checker. Note that if there are transactions
* in-flight, this will cause a warning to be issued if these are completed
* after the reset. This does not reset nextSerial to avoid such a race
* condition: where a transaction started before a reset with serial S,
* then reset() was called, followed by a start of a transaction with the
* same serial S and then receive a completion of the transaction before
* the reset with serial S.
*/
void reset()
{ byte_trackers.clear(); }
/**
* Resets an address-range. This may be useful in case other unmonitored
* parts of the system caused modification to this memory, but we cannot
* track their written values.
*
* @param addr Address base.
* @param size Size of range to be invalidated.
*/
void reset(Addr addr, size_t size);
/**
* In completeRead, if an error is encountered, this does not print nor
* cause an error, but instead should be handled by the caller. However, to
* record information about the cause of an error, completeRead creates an
* errorMessage. This function returns the last error that was detected in
* completeRead.
*
* @return Reference to string of error message.
*/
const std::string& getErrorMessage() const { return errorMessage; }
private:
/**
* Returns the instance of ByteTracker for the requested location.
*/
ByteTracker* getByteTracker(Addr addr)
{
auto it = byte_trackers.find(addr);
if (it == byte_trackers.end()) {
it = byte_trackers.insert(
std::make_pair(addr, ByteTracker(addr, this))).first;
}
return &it->second;
};
private:
/**
* Detailed error message of the last violation in completeRead.
*/
std::string errorMessage;
/**
* Next distinct serial to be assigned to the next transaction to be
* started.
*/
Serial nextSerial;
/**
* Maintain a map of address --> byte-tracker. Per-byte entries are
* initialized as needed.
*
* The required space for this obviously grows with the number of distinct
* addresses used for a particular workload. The used size is independent on
* the number of nodes in the system, those may affect the size of per-byte
* tracking information.
*
* Access via getByteTracker()!
*/
std::unordered_map<Addr, ByteTracker> byte_trackers;
};
inline MemChecker::Serial
MemChecker::startRead(Tick start, Addr addr, size_t size)
{
DPRINTF(MemChecker,
"starting read: serial = %d, start = %d, addr = %#llx, "
"size = %d\n", nextSerial, start, addr , size);
for (size_t i = 0; i < size; ++i) {
getByteTracker(addr + i)->startRead(nextSerial, start);
}
return nextSerial++;
}
inline MemChecker::Serial
MemChecker::startWrite(Tick start, Addr addr, size_t size, const uint8_t *data)
{
DPRINTF(MemChecker,
"starting write: serial = %d, start = %d, addr = %#llx, "
"size = %d\n", nextSerial, start, addr, size);
for (size_t i = 0; i < size; ++i) {
getByteTracker(addr + i)->startWrite(nextSerial, start, data[i]);
}
return nextSerial++;
}
inline void
MemChecker::completeWrite(MemChecker::Serial serial, Tick complete,
Addr addr, size_t size)
{
DPRINTF(MemChecker,
"completing write: serial = %d, complete = %d, "
"addr = %#llx, size = %d\n", serial, complete, addr, size);
for (size_t i = 0; i < size; ++i) {
getByteTracker(addr + i)->completeWrite(serial, complete);
}
}
inline void
MemChecker::abortWrite(MemChecker::Serial serial, Addr addr, size_t size)
{
DPRINTF(MemChecker,
"aborting write: serial = %d, addr = %#llx, size = %d\n",
serial, addr, size);
for (size_t i = 0; i < size; ++i) {
getByteTracker(addr + i)->abortWrite(serial);
}
}
#endif // __MEM_MEM_CHECKER_HH__