1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-10-21 03:53:04 +02:00
llvm-mirror/tools/llvm-mca/Instruction.cpp
Andrea Di Biagio c19db3b1d5 [llvm-mca][BtVer2] teach how to identify false dependencies on partially written
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
2018-07-15 11:01:38 +00:00

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