Skip to content

Commit 9d890f4

Browse files
[release/7.0] JIT: fix gc hole in peephole optimizations (#78109)
* JIT: fix gc hole in peephole optimizations 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. Closed #77661. * revise per feedback Co-authored-by: Andy Ayers <[email protected]>
1 parent 3721bc8 commit 9d890f4

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
@@ -2179,6 +2179,20 @@ class emitter
21792179

21802180
instrDesc* emitLastIns;
21812181

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

src/coreclr/jit/emitarm64.cpp

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

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

1594515945
if (dst == src)
1594615946
{
@@ -15960,17 +15960,16 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN
1596015960
else if (isGeneralRegisterOrSP(dst) && (size == EA_4BYTE))
1596115961
{
1596215962
// See if the previous instruction already cleared upper 4 bytes for us unintentionally
15963-
if (!isFirstInstrInBlock && (emitLastIns != nullptr) && (emitLastIns->idReg1() == dst) &&
15964-
(emitLastIns->idOpSize() == size) && emitLastIns->idInsIs(INS_ldr, INS_ldrh, INS_ldrb))
15963+
if (canOptimize && (emitLastIns->idReg1() == dst) && (emitLastIns->idOpSize() == size) &&
15964+
emitLastIns->idInsIs(INS_ldr, INS_ldrh, INS_ldrb))
1596515965
{
1596615966
JITDUMP("\n -- suppressing mov because ldr already cleared upper 4 bytes\n");
1596715967
return true;
1596815968
}
1596915969
}
1597015970
}
1597115971

15972-
if (!isFirstInstrInBlock && // Don't optimize if instruction is not the first instruction in IG.
15973-
(emitLastIns != nullptr) &&
15972+
if (canOptimize && // Don't optimize if unsafe.
1597415973
(emitLastIns->idIns() == INS_mov) && // Don't optimize if last instruction was not 'mov'.
1597515974
(emitLastIns->idOpSize() == size)) // Don't optimize if operand size is different than previous instruction.
1597615975
{
@@ -16048,9 +16047,7 @@ bool emitter::IsRedundantMov(instruction ins, emitAttr size, regNumber dst, regN
1604816047
bool emitter::IsRedundantLdStr(
1604916048
instruction ins, regNumber reg1, regNumber reg2, ssize_t imm, emitAttr size, insFormat fmt)
1605016049
{
16051-
bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0);
16052-
16053-
if (((ins != INS_ldr) && (ins != INS_str)) || (isFirstInstrInBlock) || (emitLastIns == nullptr))
16050+
if (((ins != INS_ldr) && (ins != INS_str)) || !emitCanPeepholeLastIns())
1605416051
{
1605516052
return false;
1605616053
}

src/coreclr/jit/emitxarch.cpp

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

315315
bool emitter::AreUpper32BitsZero(regNumber reg)
316316
{
317-
// If there are no instructions in this IG, we can look back at
318-
// the previous IG's instructions if this IG is an extension.
317+
// Only consider if safe
319318
//
320-
if ((emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0))
319+
if (!emitCanPeepholeLastIns())
321320
{
322321
return false;
323322
}
@@ -397,8 +396,9 @@ bool emitter::AreFlagsSetToZeroCmp(regNumber reg, emitAttr opSize, genTreeOps tr
397396
return false;
398397
}
399398

400-
// Don't look back across IG boundaries (possible control flow)
401-
if (emitCurIGinsCnt == 0 && ((emitCurIG->igFlags & IGF_EXTEND) == 0))
399+
// Only consider if safe
400+
//
401+
if (!emitCanPeepholeLastIns())
402402
{
403403
return false;
404404
}
@@ -480,8 +480,9 @@ bool emitter::AreFlagsSetForSignJumpOpt(regNumber reg, emitAttr opSize, GenTree*
480480
return false;
481481
}
482482

483-
// Don't look back across IG boundaries (possible control flow)
484-
if (emitCurIGinsCnt == 0 && ((emitCurIG->igFlags & IGF_EXTEND) == 0))
483+
// Only consider if safe
484+
//
485+
if (!emitCanPeepholeLastIns())
485486
{
486487
return false;
487488
}
@@ -4698,13 +4699,10 @@ bool emitter::IsRedundantMov(
46984699
return true;
46994700
}
47004701

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

4706-
if (isFirstInstrInBlock || // Don't optimize if instruction is the first instruction in IG.
4707-
(emitLastIns == nullptr) || // or if a last instruction doesn't exist
4705+
if (!emitCanPeepholeLastIns() || // Don't optimize if unsafe
47084706
(emitLastIns->idIns() != ins) || // or if the instruction is different from the last instruction
47094707
(emitLastIns->idOpSize() != size) || // or if the operand size is different from the last instruction
47104708
(emitLastIns->idInsFmt() != fmt)) // or if the format is different from the last instruction
@@ -7343,14 +7341,10 @@ bool emitter::IsRedundantStackMov(instruction ins, insFormat fmt, emitAttr size,
73437341
return false;
73447342
}
73457343

7346-
bool hasSideEffect = HasSideEffect(ins, size);
7347-
7348-
bool isFirstInstrInBlock = (emitCurIGinsCnt == 0) && ((emitCurIG->igFlags & IGF_EXTEND) == 0);
73497344
// TODO-XArch-CQ: Certain instructions, such as movaps vs movups, are equivalent in
73507345
// functionality even if their actual identifier differs and we should optimize these
73517346

7352-
if (isFirstInstrInBlock || // Don't optimize if instruction is the first instruction in IG.
7353-
(emitLastIns == nullptr) || // or if a last instruction doesn't exist
7347+
if (!emitCanPeepholeLastIns() || // Don't optimize if unsafe
73547348
(emitLastIns->idIns() != ins) || // or if the instruction is different from the last instruction
73557349
(emitLastIns->idOpSize() != size)) // or if the operand size is different from the last instruction
73567350
{
@@ -7367,6 +7361,8 @@ bool emitter::IsRedundantStackMov(instruction ins, insFormat fmt, emitAttr size,
73677361
int varNum = emitLastIns->idAddr()->iiaLclVar.lvaVarNum();
73687362
int lastOffs = emitLastIns->idAddr()->iiaLclVar.lvaOffset();
73697363

7364+
const bool hasSideEffect = HasSideEffect(ins, size);
7365+
73707366
// Check if the last instruction and current instructions use the same register and local memory.
73717367
if (varNum == varx && lastReg1 == ireg && lastOffs == offs)
73727368
{

0 commit comments

Comments
 (0)