Skip to content

Commit

Permalink
[JIT] X64 - Extend emitter peephole optimization of eliminating unnec…
Browse files Browse the repository at this point in the history
…essary `mov` instructions (#79381)
  • Loading branch information
TIHan authored Feb 25, 2023
1 parent c8688f7 commit cac06a5
Show file tree
Hide file tree
Showing 10 changed files with 429 additions and 112 deletions.
32 changes: 17 additions & 15 deletions src/coreclr/jit/emit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1072,8 +1072,8 @@ insGroup* emitter::emitSavIG(bool emitAdd)
// being adding causes a new EXTEND IG to be created. Also, emitLastIns might not be in this IG
// at all if this IG is empty.

assert((emitLastIns == nullptr) == (emitLastInsIG == nullptr));
if ((emitLastIns != nullptr) && (sz != 0))
assert(emitHasLastIns() == (emitLastInsIG != nullptr));
if (emitHasLastIns() && (sz != 0))
{
// If we get here, emitLastIns must be in the current IG we are saving.
assert(emitLastInsIG == emitCurIG);
Expand Down Expand Up @@ -1499,8 +1499,6 @@ size_t emitter::emitGenEpilogLst(size_t (*fp)(void*, unsigned), void* cp)

void* emitter::emitAllocAnyInstr(size_t sz, emitAttr opsz)
{
instrDesc* id;

#ifdef DEBUG
// Under STRESS_EMITTER, put every instruction in its own instruction group.
// We can't do this for a prolog, epilog, funclet prolog, or funclet epilog,
Expand Down Expand Up @@ -1557,18 +1555,18 @@ void* emitter::emitAllocAnyInstr(size_t sz, emitAttr opsz)
assert(IsCodeAligned(emitCurIGsize));

// Make sure we have enough space for the new instruction.
// `igInsCnt` is currently a byte, so we can't have more than 255 instructions in a single insGroup.

size_t fullSize = sz + m_debugInfoSize;

if ((emitCurIGfreeNext + fullSize >= emitCurIGfreeEndp) || emitForceNewIG || (emitCurIGinsCnt >= 255))
if ((emitCurIGfreeNext + fullSize >= emitCurIGfreeEndp) || emitForceNewIG ||
(emitCurIGinsCnt >= (EMIT_MAX_IG_INS_COUNT - 1)))
{
emitNxtIG(true);
}

/* Grab the space for the instruction */

emitLastIns = id = (instrDesc*)(emitCurIGfreeNext + m_debugInfoSize);
instrDesc* id = emitLastIns = (instrDesc*)(emitCurIGfreeNext + m_debugInfoSize);

#if EMIT_BACKWARDS_NAVIGATION
emitCurIG->igLastIns = id;
Expand Down Expand Up @@ -1606,14 +1604,18 @@ void* emitter::emitAllocAnyInstr(size_t sz, emitAttr opsz)
if (m_debugInfoSize > 0)
{
instrDescDebugInfo* info = (instrDescDebugInfo*)emitGetMem(sizeof(*info));
info->idNum = emitInsCount;
info->idSize = sz;
info->idVarRefOffs = 0;
info->idMemCookie = 0;
info->idFlags = GTF_EMPTY;
info->idFinallyCall = false;
info->idCatchRet = false;
info->idCallSig = nullptr;
memset(info, 0, sizeof(instrDescDebugInfo));

// These fields should have been zero-ed by the above
assert(info->idVarRefOffs == 0);
assert(info->idMemCookie == 0);
assert(info->idFlags == GTF_EMPTY);
assert(info->idFinallyCall == false);
assert(info->idCatchRet == false);
assert(info->idCallSig == nullptr);

info->idNum = emitInsCount;
info->idSize = sz;
id->idDebugOnlyInfo(info);
}

Expand Down
110 changes: 97 additions & 13 deletions src/coreclr/jit/emit.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@
#define EMIT_INSTLIST_VERBOSE (emitComp->verbose)
#endif

#define EMIT_BACKWARDS_NAVIGATION 0 // If 1, enable backwards navigation code for MIR (insGroup/instrDesc).
#ifdef TARGET_XARCH
#define EMIT_BACKWARDS_NAVIGATION 1 // If 1, enable backwards navigation code for MIR (insGroup/instrDesc).
#else
#define EMIT_BACKWARDS_NAVIGATION 0
#endif

/*****************************************************************************/

Expand Down Expand Up @@ -2292,31 +2296,111 @@ class emitter
return (emitCurIG && emitCurIGfreeNext > emitCurIGfreeBase);
}

#define EMIT_MAX_IG_INS_COUNT 256

#if EMIT_BACKWARDS_NAVIGATION
#define EMIT_MAX_PEEPHOLE_INS_COUNT 32 // The max number of previous instructions to navigate through for peepholes.
#endif // EMIT_BACKWARDS_NAVIGATION

instrDesc* emitLastIns;
insGroup* emitLastInsIG;

#if EMIT_BACKWARDS_NAVIGATION
unsigned emitLastInsFullSize;
#endif // EMIT_BACKWARDS_NAVIGATION

// Check to see if the last instruction is available.
inline bool emitHasLastIns() const
{
return (emitLastIns != nullptr);
}

// Checks to see if we can cross between the two given IG boundaries.
//
// We have the following checks:
// 1. Looking backwards across an IG boundary can only be done if we're in an extension IG.
// 2. The IG of the previous instruction must have the same GC interrupt status as the current IG.
inline bool isInsIGSafeForPeepholeOptimization(insGroup* prevInsIG, insGroup* curInsIG) const
{
if (prevInsIG == curInsIG)
{
return true;
}
else
{
return (curInsIG->igFlags & IGF_EXTEND) &&
((prevInsIG->igFlags & IGF_NOGCINTERRUPT) == (curInsIG->igFlags & IGF_NOGCINTERRUPT));
}
}

// Check if a peephole optimization involving emitLastIns is safe.
//
// We have the following checks:
// 1. There must be a non-null emitLastIns to consult (thus, we have a known "last" instruction).
// 2. `emitForceNewIG` is not set: this prevents peepholes from crossing nogc boundaries where
// the next instruction is forced to create a new IG.
// 3. Looking backwards across an IG boundary can only be done if we're in an extension IG.
// 4. The IG of the previous instruction must have the same GC interrupt status as the current IG.
// This is related to #2; it disallows peephole when the previous IG is GC and the current is NOGC.
bool emitCanPeepholeLastIns()
{
assert((emitLastIns == nullptr) == (emitLastInsIG == nullptr));

return (emitLastIns != nullptr) && !emitForceNewIG &&
((emitCurIGinsCnt > 0) || // we're not at the start of a new IG
((emitCurIG->igFlags & IGF_EXTEND) != 0)) && // or we are at the start of a new IG,
// and it's an extension IG
((emitLastInsIG->igFlags & IGF_NOGCINTERRUPT) == (emitCurIG->igFlags & IGF_NOGCINTERRUPT));
bool emitCanPeepholeLastIns() const
{
assert(emitHasLastIns() == (emitLastInsIG != nullptr));

return emitHasLastIns() && // there is an emitLastInstr
!emitForceNewIG && // and we're not about to start a new IG.
isInsIGSafeForPeepholeOptimization(emitLastInsIG, emitCurIG);
}

enum emitPeepholeResult
{
PEEPHOLE_CONTINUE,
PEEPHOLE_ABORT
};

// Visits the last emitted instructions.
// Must be safe to do - use emitCanPeepholeLastIns for checking.
// Action is a function type: instrDesc* -> emitPeepholeResult
template <typename Action>
void emitPeepholeIterateLastInstrs(Action action)
{
assert(emitCanPeepholeLastIns());

#if EMIT_BACKWARDS_NAVIGATION
insGroup* curInsIG;
instrDesc* id;

if (!emitGetLastIns(&curInsIG, &id))
return;

for (unsigned i = 0; i < EMIT_MAX_PEEPHOLE_INS_COUNT; i++)
{
assert(id != nullptr);

switch (action(id))
{
case PEEPHOLE_ABORT:
return;
case PEEPHOLE_CONTINUE:
{
insGroup* savedInsIG = curInsIG;
if (emitPrevID(curInsIG, id))
{
if (isInsIGSafeForPeepholeOptimization(curInsIG, savedInsIG))
{
continue;
}
else
{
return;
}
}
return;
}

default:
unreached();
}
}
#else // EMIT_BACKWARDS_NAVIGATION
action(emitLastIns);
#endif // !EMIT_BACKWARDS_NAVIGATION
}

#ifdef TARGET_ARMARCH
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/jit/emitarm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16030,7 +16030,8 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN

if (canOptimize && // Don't optimize if unsafe.
(emitLastIns->idIns() == INS_mov) && // Don't optimize if last instruction was not 'mov'.
(emitLastIns->idOpSize() == size)) // Don't optimize if operand size is different than previous instruction.
(emitLastIns->idOpSize() == size)) // Don't optimize if operand size is different than previous
// instruction.
{
// Check if we did same move in prev instruction except dst/src were switched.
regNumber prevDst = emitLastIns->idReg1();
Expand Down
Loading

0 comments on commit cac06a5

Please sign in to comment.