mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2025-01-31 20:51:52 +01:00
c19db3b1d5
registers. The goal of this patch is to improve the throughput analysis in llvm-mca for the case where instructions perform partial register writes. On x86, partial register writes are quite difficult to model, mainly because different processors tend to implement different register merging schemes in hardware. When the code contains partial register writes, the IPC (instructions per cycles) estimated by llvm-mca tends to diverge quite significantly from the observed IPC (using perf). Modern AMD processors (at least, from Bulldozer onwards) don't rename partial registers. Quoting Agner Fog's microarchitecture.pdf: " The processor always keeps the different parts of an integer register together. For example, AL and AH are not treated as independent by the out-of-order execution mechanism. An instruction that writes to part of a register will therefore have a false dependence on any previous write to the same register or any part of it." This patch is a first important step towards improving the analysis of partial register updates. It changes the semantic of RegisterFile descriptors in tablegen, and teaches llvm-mca how to identify false dependences in the presence of partial register writes (for more details: see the new code comments in include/Target/TargetSchedule.h - class RegisterFile). This patch doesn't address the case where a write to a part of a register is followed by a read from the whole register. On Intel chips, high8 registers (AH/BH/CH/DH)) can be stored in separate physical registers. However, a later (dirty) read of the full register (example: AX/EAX) triggers a merge uOp, which adds extra latency (and potentially affects the pipe usage). This is a very interesting article on the subject with a very informative answer from Peter Cordes: https://stackoverflow.com/questions/45660139/how-exactly-do-partial-registers-on-haswell-skylake-perform-writing-al-seems-to In future, the definition of RegisterFile can be extended with extra information that may be used to identify delays caused by merge opcodes triggered by a dirty read of a partial write. Differential Revision: https://reviews.llvm.org/D49196 llvm-svn: 337123
178 lines
4.8 KiB
C++
178 lines
4.8 KiB
C++
//===--------------------- Instruction.cpp ----------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines abstractions used by the Pipeline to model register reads,
|
|
// register writes and instructions.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Instruction.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
namespace mca {
|
|
|
|
using namespace llvm;
|
|
|
|
void ReadState::writeStartEvent(unsigned Cycles) {
|
|
assert(DependentWrites);
|
|
assert(CyclesLeft == UNKNOWN_CYCLES);
|
|
|
|
// This read may be dependent on more than one write. This typically occurs
|
|
// when a definition is the result of multiple writes where at least one
|
|
// write does a partial register update.
|
|
// The HW is forced to do some extra bookkeeping to track of all the
|
|
// dependent writes, and implement a merging scheme for the partial writes.
|
|
--DependentWrites;
|
|
TotalCycles = std::max(TotalCycles, Cycles);
|
|
|
|
if (!DependentWrites) {
|
|
CyclesLeft = TotalCycles;
|
|
IsReady = !CyclesLeft;
|
|
}
|
|
}
|
|
|
|
void WriteState::onInstructionIssued() {
|
|
assert(CyclesLeft == UNKNOWN_CYCLES);
|
|
// Update the number of cycles left based on the WriteDescriptor info.
|
|
CyclesLeft = getLatency();
|
|
|
|
// Now that the time left before write-back is known, notify
|
|
// all the users.
|
|
for (const std::pair<ReadState *, int> &User : Users) {
|
|
ReadState *RS = User.first;
|
|
unsigned ReadCycles = std::max(0, CyclesLeft - User.second);
|
|
RS->writeStartEvent(ReadCycles);
|
|
}
|
|
}
|
|
|
|
void WriteState::addUser(ReadState *User, int ReadAdvance) {
|
|
// If CyclesLeft is different than -1, then we don't need to
|
|
// update the list of users. We can just notify the user with
|
|
// the actual number of cycles left (which may be zero).
|
|
if (CyclesLeft != UNKNOWN_CYCLES) {
|
|
unsigned ReadCycles = std::max(0, CyclesLeft - ReadAdvance);
|
|
User->writeStartEvent(ReadCycles);
|
|
return;
|
|
}
|
|
|
|
std::pair<ReadState *, int> NewPair(User, ReadAdvance);
|
|
Users.insert(NewPair);
|
|
}
|
|
|
|
void WriteState::cycleEvent() {
|
|
// Note: CyclesLeft can be a negative number. It is an error to
|
|
// make it an unsigned quantity because users of this write may
|
|
// specify a negative ReadAdvance.
|
|
if (CyclesLeft != UNKNOWN_CYCLES)
|
|
CyclesLeft--;
|
|
}
|
|
|
|
void ReadState::cycleEvent() {
|
|
// Update the total number of cycles.
|
|
if (DependentWrites && TotalCycles) {
|
|
--TotalCycles;
|
|
return;
|
|
}
|
|
|
|
// Bail out immediately if we don't know how many cycles are left.
|
|
if (CyclesLeft == UNKNOWN_CYCLES)
|
|
return;
|
|
|
|
if (CyclesLeft) {
|
|
--CyclesLeft;
|
|
IsReady = !CyclesLeft;
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
void WriteState::dump() const {
|
|
dbgs() << "{ OpIdx=" << WD.OpIndex << ", Lat=" << getLatency() << ", RegID "
|
|
<< getRegisterID() << ", Cycles Left=" << getCyclesLeft() << " }";
|
|
}
|
|
|
|
void WriteRef::dump() const {
|
|
dbgs() << "IID=" << getSourceIndex() << ' ';
|
|
if (isValid())
|
|
getWriteState()->dump();
|
|
else
|
|
dbgs() << "(null)";
|
|
}
|
|
#endif
|
|
|
|
void Instruction::dispatch(unsigned RCUToken) {
|
|
assert(Stage == IS_INVALID);
|
|
Stage = IS_AVAILABLE;
|
|
RCUTokenID = RCUToken;
|
|
|
|
// Check if input operands are already available.
|
|
update();
|
|
}
|
|
|
|
void Instruction::execute() {
|
|
assert(Stage == IS_READY);
|
|
Stage = IS_EXECUTING;
|
|
|
|
// Set the cycles left before the write-back stage.
|
|
CyclesLeft = Desc.MaxLatency;
|
|
|
|
for (UniqueDef &Def : Defs)
|
|
Def->onInstructionIssued();
|
|
|
|
// Transition to the "executed" stage if this is a zero-latency instruction.
|
|
if (!CyclesLeft)
|
|
Stage = IS_EXECUTED;
|
|
}
|
|
|
|
void Instruction::update() {
|
|
assert(isDispatched() && "Unexpected instruction stage found!");
|
|
|
|
if (!llvm::all_of(Uses, [](const UniqueUse &Use) { return Use->isReady(); }))
|
|
return;
|
|
|
|
// A partial register write cannot complete before a dependent write.
|
|
auto IsDefReady = [&](const UniqueDef &Def) {
|
|
if (const WriteState *Write = Def->getDependentWrite()) {
|
|
int WriteLatency = Write->getCyclesLeft();
|
|
if (WriteLatency == UNKNOWN_CYCLES)
|
|
return false;
|
|
return static_cast<unsigned>(WriteLatency) < Desc.MaxLatency;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (llvm::all_of(Defs, IsDefReady))
|
|
Stage = IS_READY;
|
|
}
|
|
|
|
void Instruction::cycleEvent() {
|
|
if (isReady())
|
|
return;
|
|
|
|
if (isDispatched()) {
|
|
for (UniqueUse &Use : Uses)
|
|
Use->cycleEvent();
|
|
|
|
update();
|
|
return;
|
|
}
|
|
|
|
assert(isExecuting() && "Instruction not in-flight?");
|
|
assert(CyclesLeft && "Instruction already executed?");
|
|
for (UniqueDef &Def : Defs)
|
|
Def->cycleEvent();
|
|
CyclesLeft--;
|
|
if (!CyclesLeft)
|
|
Stage = IS_EXECUTED;
|
|
}
|
|
|
|
const unsigned WriteRef::INVALID_IID = std::numeric_limits<unsigned>::max();
|
|
|
|
} // namespace mca
|