blob: 0f500d89230e8604f90732fcb5ca4b6a28ef224f [file] [log] [blame]
/*
* Copyright (c) 2010-2011, 2021 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.
*
* Copyright (c) 2004-2005 The Regents of The University of Michigan
* 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.
*/
#include "cpu/o3/dyn_inst.hh"
#include <algorithm>
#include "base/intmath.hh"
#include "debug/DynInst.hh"
#include "debug/IQ.hh"
#include "debug/O3PipeView.hh"
namespace gem5
{
namespace o3
{
DynInst::DynInst(const Arrays &arrays, const StaticInstPtr &static_inst,
const StaticInstPtr &_macroop, InstSeqNum seq_num, CPU *_cpu)
: seqNum(seq_num), staticInst(static_inst), cpu(_cpu),
_numSrcs(arrays.numSrcs), _numDests(arrays.numDests),
_flatDestIdx(arrays.flatDestIdx), _destIdx(arrays.destIdx),
_prevDestIdx(arrays.prevDestIdx), _srcIdx(arrays.srcIdx),
_readySrcIdx(arrays.readySrcIdx), macroop(_macroop)
{
std::fill(_readySrcIdx, _readySrcIdx + (numSrcs() + 7) / 8, 0);
status.reset();
instFlags.reset();
instFlags[RecordResult] = true;
instFlags[Predicate] = true;
instFlags[MemAccPredicate] = true;
#ifndef NDEBUG
++cpu->instcount;
if (cpu->instcount > 1500) {
#ifdef GEM5_DEBUG
cpu->dumpInsts();
dumpSNList();
#endif
assert(cpu->instcount <= 1500);
}
DPRINTF(DynInst,
"DynInst: [sn:%lli] Instruction created. Instcount for %s = %i\n",
seqNum, cpu->name(), cpu->instcount);
#endif
#ifdef GEM5_DEBUG
cpu->snList.insert(seqNum);
#endif
}
DynInst::DynInst(const Arrays &arrays, const StaticInstPtr &static_inst,
const StaticInstPtr &_macroop, const PCStateBase &_pc,
const PCStateBase &pred_pc, InstSeqNum seq_num, CPU *_cpu)
: DynInst(arrays, static_inst, _macroop, seq_num, _cpu)
{
set(pc, _pc);
set(predPC, pred_pc);
}
DynInst::DynInst(const Arrays &arrays, const StaticInstPtr &_staticInst,
const StaticInstPtr &_macroop)
: DynInst(arrays, _staticInst, _macroop, 0, nullptr)
{}
/*
* This custom "new" operator uses the default "new" operator to allocate space
* for a DynInst, but also pads out the number of bytes to make room for some
* extra structures the DynInst needs. We save time and improve performance by
* only going to the heap once to get space for all these structures.
*
* When a DynInst is allocated with new, the compiler will call this "new"
* operator with "count" set to the number of bytes it needs to store the
* DynInst. We ultimately call into the default new operator to get those
* bytes, but before we do, we pad out "count" so that there will be extra
* space for some structures the DynInst needs. We take into account both the
* absolute size of these structures, and also what alignment they need.
*
* Once we've gotten a buffer large enough to hold the DynInst itself and these
* extra structures, we construct the extra bits using placement new. This
* constructs the structures in place in the space we created for them.
*
* Next, we return the buffer as the result of our operator. The compiler takes
* that buffer and constructs the DynInst in the beginning of it using the
* DynInst constructor.
*
* To avoid having to calculate where these extra structures are twice, once
* when making room for them and initializing them, and then once again in the
* DynInst constructor, we also pass in a structure called "arrays" which holds
* pointers to them. The fields of "arrays" are initialized in this operator,
* and are then consumed in the DynInst constructor.
*/
void *
DynInst::operator new(size_t count, Arrays &arrays)
{
// Convenience variables for brevity.
const auto num_dests = arrays.numDests;
const auto num_srcs = arrays.numSrcs;
// Figure out where everything will go.
uintptr_t inst = 0;
size_t inst_size = count;
uintptr_t flat_dest_idx = roundUp(inst + inst_size, alignof(RegId));
size_t flat_dest_idx_size = sizeof(*arrays.flatDestIdx) * num_dests;
uintptr_t dest_idx =
roundUp(flat_dest_idx + flat_dest_idx_size, alignof(PhysRegIdPtr));
size_t dest_idx_size = sizeof(*arrays.destIdx) * num_dests;
uintptr_t prev_dest_idx =
roundUp(dest_idx + dest_idx_size, alignof(PhysRegIdPtr));
size_t prev_dest_idx_size = sizeof(*arrays.prevDestIdx) * num_dests;
uintptr_t src_idx =
roundUp(prev_dest_idx + prev_dest_idx_size, alignof(PhysRegIdPtr));
size_t src_idx_size = sizeof(*arrays.srcIdx) * num_srcs;
uintptr_t ready_src_idx =
roundUp(src_idx + src_idx_size, alignof(uint8_t));
size_t ready_src_idx_size =
sizeof(*arrays.readySrcIdx) * ((num_srcs + 7) / 8);
// Figure out how much space we need in total.
size_t total_size = ready_src_idx + ready_src_idx_size;
// Actually allocate it.
uint8_t *buf = (uint8_t *)::operator new(total_size);
// Fill in "arrays" with pointers to all the arrays.
arrays.flatDestIdx = (RegId *)(buf + flat_dest_idx);
arrays.destIdx = (PhysRegIdPtr *)(buf + dest_idx);
arrays.prevDestIdx = (PhysRegIdPtr *)(buf + prev_dest_idx);
arrays.srcIdx = (PhysRegIdPtr *)(buf + src_idx);
arrays.readySrcIdx = (uint8_t *)(buf + ready_src_idx);
// Initialize all the extra components.
new (arrays.flatDestIdx) RegId[num_dests];
new (arrays.destIdx) PhysRegIdPtr[num_dests];
new (arrays.prevDestIdx) PhysRegIdPtr[num_dests];
new (arrays.srcIdx) PhysRegIdPtr[num_srcs];
new (arrays.readySrcIdx) uint8_t[num_srcs];
return buf;
}
// Because of the custom "new" operator that allocates more bytes than the
// size of the DynInst object, AddressSanitizer throw new-delete-type-mismatch.
// Adding a custom delete function is enough to shut down this false positive
void
DynInst::operator delete(void *ptr)
{
::operator delete(ptr);
}
DynInst::~DynInst()
{
/*
* The buffer this DynInst occupies also holds some of the structures it
* points to. We need to call their destructors manually to make sure that
* they're cleaned up appropriately, but we don't need to free their memory
* explicitly since that's part of the DynInst's buffer and is already
* going to be freed as part of deleting the DynInst.
*/
for (int i = 0; i < _numDests; i++) {
_flatDestIdx[i].~RegId();
_destIdx[i].~PhysRegIdPtr();
_prevDestIdx[i].~PhysRegIdPtr();
}
for (int i = 0; i < _numSrcs; i++)
_srcIdx[i].~PhysRegIdPtr();
for (int i = 0; i < ((_numSrcs + 7) / 8); i++)
_readySrcIdx[i].~uint8_t();
#if TRACING_ON
if (debug::O3PipeView) {
Tick fetch = fetchTick;
// fetchTick can be -1 if the instruction fetched outside the trace
// window.
if (fetch != -1) {
Tick val;
// Print info needed by the pipeline activity viewer.
DPRINTFR(O3PipeView, "O3PipeView:fetch:%llu:0x%08llx:%d:%llu:%s\n",
fetch,
pcState().instAddr(),
pcState().microPC(),
seqNum,
staticInst->disassemble(pcState().instAddr()));
val = (decodeTick == -1) ? 0 : fetch + decodeTick;
DPRINTFR(O3PipeView, "O3PipeView:decode:%llu\n", val);
val = (renameTick == -1) ? 0 : fetch + renameTick;
DPRINTFR(O3PipeView, "O3PipeView:rename:%llu\n", val);
val = (dispatchTick == -1) ? 0 : fetch + dispatchTick;
DPRINTFR(O3PipeView, "O3PipeView:dispatch:%llu\n", val);
val = (issueTick == -1) ? 0 : fetch + issueTick;
DPRINTFR(O3PipeView, "O3PipeView:issue:%llu\n", val);
val = (completeTick == -1) ? 0 : fetch + completeTick;
DPRINTFR(O3PipeView, "O3PipeView:complete:%llu\n", val);
val = (commitTick == -1) ? 0 : fetch + commitTick;
Tick valS = (storeTick == -1) ? 0 : fetch + storeTick;
DPRINTFR(O3PipeView, "O3PipeView:retire:%llu:store:%llu\n",
val, valS);
}
}
#endif
delete [] memData;
delete traceData;
fault = NoFault;
#ifndef NDEBUG
--cpu->instcount;
DPRINTF(DynInst,
"DynInst: [sn:%lli] Instruction destroyed. Instcount for %s = %i\n",
seqNum, cpu->name(), cpu->instcount);
#endif
#ifdef GEM5_DEBUG
cpu->snList.erase(seqNum);
#endif
};
#ifdef GEM5_DEBUG
void
DynInst::dumpSNList()
{
std::set<InstSeqNum>::iterator sn_it = cpu->snList.begin();
int count = 0;
while (sn_it != cpu->snList.end()) {
cprintf("%i: [sn:%lli] not destroyed\n", count, (*sn_it));
count++;
sn_it++;
}
}
#endif
void
DynInst::dump()
{
cprintf("T%d : %#08d `", threadNumber, pc->instAddr());
std::cout << staticInst->disassemble(pc->instAddr());
cprintf("'\n");
}
void
DynInst::dump(std::string &outstring)
{
std::ostringstream s;
s << "T" << threadNumber << " : 0x" << pc->instAddr() << " "
<< staticInst->disassemble(pc->instAddr());
outstring = s.str();
}
void
DynInst::markSrcRegReady()
{
DPRINTF(IQ, "[sn:%lli] has %d ready out of %d sources. RTI %d)\n",
seqNum, readyRegs+1, numSrcRegs(), readyToIssue());
if (++readyRegs == numSrcRegs()) {
setCanIssue();
}
}
void
DynInst::markSrcRegReady(RegIndex src_idx)
{
readySrcIdx(src_idx, true);
markSrcRegReady();
}
void
DynInst::setSquashed()
{
status.set(Squashed);
if (!isPinnedRegsRenamed() || isPinnedRegsSquashDone())
return;
// This inst has been renamed already so it may go through rename
// again (e.g. if the squash is due to memory access order violation).
// Reset the write counters for all pinned destination register to ensure
// that they are in a consistent state for a possible re-rename. This also
// ensures that dest regs will be pinned to the same phys register if
// re-rename happens.
for (int idx = 0; idx < numDestRegs(); idx++) {
PhysRegIdPtr phys_dest_reg = renamedDestIdx(idx);
if (phys_dest_reg->isPinned()) {
phys_dest_reg->incrNumPinnedWrites();
if (isPinnedRegsWritten())
phys_dest_reg->incrNumPinnedWritesToComplete();
}
}
setPinnedRegsSquashDone();
}
Fault
DynInst::execute()
{
// @todo: Pretty convoluted way to avoid squashing from happening
// when using the TC during an instruction's execution
// (specifically for instructions that have side-effects that use
// the TC). Fix this.
bool no_squash_from_TC = thread->noSquashFromTC;
thread->noSquashFromTC = true;
fault = staticInst->execute(this, traceData);
thread->noSquashFromTC = no_squash_from_TC;
return fault;
}
Fault
DynInst::initiateAcc()
{
// @todo: Pretty convoluted way to avoid squashing from happening
// when using the TC during an instruction's execution
// (specifically for instructions that have side-effects that use
// the TC). Fix this.
bool no_squash_from_TC = thread->noSquashFromTC;
thread->noSquashFromTC = true;
fault = staticInst->initiateAcc(this, traceData);
thread->noSquashFromTC = no_squash_from_TC;
return fault;
}
Fault
DynInst::completeAcc(PacketPtr pkt)
{
// @todo: Pretty convoluted way to avoid squashing from happening
// when using the TC during an instruction's execution
// (specifically for instructions that have side-effects that use
// the TC). Fix this.
bool no_squash_from_TC = thread->noSquashFromTC;
thread->noSquashFromTC = true;
if (cpu->checker) {
if (isStoreConditional()) {
reqToVerify->setExtraData(pkt->req->getExtraData());
}
}
fault = staticInst->completeAcc(pkt, this, traceData);
thread->noSquashFromTC = no_squash_from_TC;
return fault;
}
void
DynInst::trap(const Fault &fault)
{
cpu->trap(fault, threadNumber, staticInst);
}
Fault
DynInst::initiateMemRead(Addr addr, unsigned size, Request::Flags flags,
const std::vector<bool> &byte_enable)
{
assert(byte_enable.size() == size);
return cpu->pushRequest(
dynamic_cast<DynInstPtr::PtrType>(this),
/* ld */ true, nullptr, size, addr, flags, nullptr, nullptr,
byte_enable);
}
Fault
DynInst::initiateMemMgmtCmd(Request::Flags flags)
{
const unsigned int size = 8;
return cpu->pushRequest(
dynamic_cast<DynInstPtr::PtrType>(this),
/* ld */ true, nullptr, size, 0x0ul, flags, nullptr, nullptr,
std::vector<bool>(size, true));
}
Fault
DynInst::writeMem(uint8_t *data, unsigned size, Addr addr,
Request::Flags flags, uint64_t *res,
const std::vector<bool> &byte_enable)
{
assert(byte_enable.size() == size);
return cpu->pushRequest(
dynamic_cast<DynInstPtr::PtrType>(this),
/* st */ false, data, size, addr, flags, res, nullptr,
byte_enable);
}
Fault
DynInst::initiateMemAMO(Addr addr, unsigned size, Request::Flags flags,
AtomicOpFunctorPtr amo_op)
{
// atomic memory instructions do not have data to be written to memory yet
// since the atomic operations will be executed directly in cache/memory.
// Therefore, its `data` field is nullptr.
// Atomic memory requests need to carry their `amo_op` fields to cache/
// memory
return cpu->pushRequest(
dynamic_cast<DynInstPtr::PtrType>(this),
/* atomic */ false, nullptr, size, addr, flags, nullptr,
std::move(amo_op), std::vector<bool>(size, true));
}
} // namespace o3
} // namespace gem5