Skip to content

Commit af494f4

Browse files
authored
JIT: fix gc hole in peephole optimizations (#78074)
We cannot safely peephole instructions that straddle a gc enable boundary. Detecting when this might happen is a bit subtle; currently we rely on `emitForceNewIG` to be set. Add a new utility 'emitCanPeepholeLastIns` to centralize the logic that decides whether basing current emission on `emitLastIns` is safe. Closes #77661.
1 parent 3bcdb34 commit af494f4

File tree

3 files changed

+31
-24
lines changed

3 files changed

+31
-24
lines changed

src/coreclr/jit/emit.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,6 +2180,20 @@ class emitter
21802180

21812181
instrDesc* emitLastIns;
21822182

2183+
// Check if a peephole optimization involving emitLastIns is safe.
2184+
//
2185+
// We must have a lastInstr to consult.
2186+
// The emitForceNewIG check here prevents peepholes from crossing nogc boundaries.
2187+
// The final check prevents looking across an IG boundary unless we're in an extension IG.
2188+
bool emitCanPeepholeLastIns()
2189+
{
2190+
return (emitLastIns != nullptr) && // there is an emitLastInstr
2191+
!emitForceNewIG && // and we're not about to start a new IG
2192+
((emitCurIGinsCnt > 0) || // and we're not at the start of a new IG
2193+
((emitCurIG->igFlags & IGF_EXTEND) != 0)); // or we are at the start of a new IG,
2194+
// and it's an extension IG
2195+
}
2196+
21832197
#ifdef TARGET_ARMARCH
21842198
instrDesc* emitLastMemBarrier;
21852199
#endif

src/coreclr/jit/emitarm64.cpp

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15957,7 +15957,7 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN
1595715957
return false;
1595815958
}
1595915959

15960-
const bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0);
15960+
const bool canOptimize = emitCanPeepholeLastIns();
1596115961

1596215962
if (dst == src)
1596315963
{
@@ -15977,17 +15977,16 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN
1597715977
else if (isGeneralRegisterOrSP(dst) && (size == EA_4BYTE))
1597815978
{
1597915979
// See if the previous instruction already cleared upper 4 bytes for us unintentionally
15980-
if (!isFirstInstrInBlock && (emitLastIns != nullptr) && (emitLastIns->idReg1() == dst) &&
15981-
(emitLastIns->idOpSize() == size) && emitLastIns->idInsIs(INS_ldr, INS_ldrh, INS_ldrb))
15980+
if (canOptimize && (emitLastIns->idReg1() == dst) && (emitLastIns->idOpSize() == size) &&
15981+
emitLastIns->idInsIs(INS_ldr, INS_ldrh, INS_ldrb))
1598215982
{
1598315983
JITDUMP("\n -- suppressing mov because ldr already cleared upper 4 bytes\n");
1598415984
return true;
1598515985
}
1598615986
}
1598715987
}
1598815988

15989-
if (!isFirstInstrInBlock && // Don't optimize if instruction is not the first instruction in IG.
15990-
(emitLastIns != nullptr) &&
15989+
if (canOptimize && // Don't optimize if unsafe.
1599115990
(emitLastIns->idIns() == INS_mov) && // Don't optimize if last instruction was not 'mov'.
1599215991
(emitLastIns->idOpSize() == size)) // Don't optimize if operand size is different than previous instruction.
1599315992
{
@@ -16065,9 +16064,7 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN
1606516064
bool emitter::IsRedundantLdStr(
1606616065
instruction ins, regNumber reg1, regNumber reg2, ssize_t imm, emitAttr size, insFormat fmt)
1606716066
{
16068-
bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0);
16069-
16070-
if (((ins != INS_ldr) && (ins != INS_str)) || (isFirstInstrInBlock) || (emitLastIns == nullptr))
16067+
if (((ins != INS_ldr) && (ins != INS_str)) || !emitCanPeepholeLastIns())
1607116068
{
1607216069
return false;
1607316070
}

src/coreclr/jit/emitxarch.cpp

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,9 @@ bool emitter::IsFlagsAlwaysModified(instrDesc* id)
486486

487487
bool emitter::AreUpper32BitsZero(regNumber reg)
488488
{
489-
// If there are no instructions in this IG, we can look back at
490-
// the previous IG's instructions if this IG is an extension.
489+
// Only consider if safe
491490
//
492-
if ((emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0))
491+
if (!emitCanPeepholeLastIns())
493492
{
494493
return false;
495494
}
@@ -569,8 +568,9 @@ bool emitter::AreFlagsSetToZeroCmp(regNumber reg, emitAttr opSize, genTreeOps tr
569568
return false;
570569
}
571570

572-
// Don't look back across IG boundaries (possible control flow)
573-
if (emitCurIGinsCnt == 0 && ((emitCurIG->igFlags & IGF_EXTEND) == 0))
571+
// Only consider if safe
572+
//
573+
if (!emitCanPeepholeLastIns())
574574
{
575575
return false;
576576
}
@@ -652,8 +652,9 @@ bool emitter::AreFlagsSetForSignJumpOpt(regNumber reg, emitAttr opSize, GenTree*
652652
return false;
653653
}
654654

655-
// Don't look back across IG boundaries (possible control flow)
656-
if (emitCurIGinsCnt == 0 && ((emitCurIG->igFlags & IGF_EXTEND) == 0))
655+
// Only consider if safe
656+
//
657+
if (!emitCanPeepholeLastIns())
657658
{
658659
return false;
659660
}
@@ -5717,13 +5718,10 @@ bool emitter::IsRedundantMov(
57175718
return true;
57185719
}
57195720

5720-
bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0);
5721-
57225721
// TODO-XArch-CQ: Certain instructions, such as movaps vs movups, are equivalent in
57235722
// functionality even if their actual identifier differs and we should optimize these
57245723

5725-
if (isFirstInstrInBlock || // Don't optimize if instruction is the first instruction in IG.
5726-
(emitLastIns == nullptr) || // or if a last instruction doesn't exist
5724+
if (!emitCanPeepholeLastIns() || // Don't optimize if unsafe
57275725
(emitLastIns->idIns() != ins) || // or if the instruction is different from the last instruction
57285726
(emitLastIns->idOpSize() != size) || // or if the operand size is different from the last instruction
57295727
(emitLastIns->idInsFmt() != fmt)) // or if the format is different from the last instruction
@@ -8410,14 +8408,10 @@ bool emitter::IsRedundantStackMov(instruction ins, insFormat fmt, emitAttr size,
84108408
return false;
84118409
}
84128410

8413-
bool hasSideEffect = HasSideEffect(ins, size);
8414-
8415-
bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0);
84168411
// TODO-XArch-CQ: Certain instructions, such as movaps vs movups, are equivalent in
84178412
// functionality even if their actual identifier differs and we should optimize these
84188413

8419-
if (isFirstInstrInBlock || // Don't optimize if instruction is the first instruction in IG.
8420-
(emitLastIns == nullptr) || // or if a last instruction doesn't exist
8414+
if (!emitCanPeepholeLastIns() || // Don't optimize if unsafe
84218415
(emitLastIns->idIns() != ins) || // or if the instruction is different from the last instruction
84228416
(emitLastIns->idOpSize() != size)) // or if the operand size is different from the last instruction
84238417
{
@@ -8434,6 +8428,8 @@ bool emitter::IsRedundantStackMov(instruction ins, insFormat fmt, emitAttr size,
84348428
int varNum = emitLastIns->idAddr()->iiaLclVar.lvaVarNum();
84358429
int lastOffs = emitLastIns->idAddr()->iiaLclVar.lvaOffset();
84368430

8431+
const bool hasSideEffect = HasSideEffect(ins, size);
8432+
84378433
// Check if the last instruction and current instructions use the same register and local memory.
84388434
if (varNum == varx && lastReg1 == ireg && lastOffs == offs)
84398435
{

0 commit comments

Comments
 (0)