Skip to content

Commit 98de027

Browse files
authored
JIT: Model GS cookie check in LSRA (#120192)
Also switch the register used when generating the check if there is a tailcall in the block on x64. This allows us to enable fast tailcalls out of methods with GS cookie checks on x64.
1 parent 18ece9e commit 98de027

19 files changed

+111
-303
lines changed

src/coreclr/jit/codegen.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1097,7 +1097,7 @@ class CodeGen final : public CodeGenInterface
10971097
#if defined(FEATURE_SIMD) || defined(FEATURE_HW_INTRINSICS)
10981098
void genConsumeMultiOpOperands(GenTreeMultiOp* tree);
10991099
#endif
1100-
void genEmitGSCookieCheck(bool pushReg);
1100+
void genEmitGSCookieCheck(bool tailCall);
11011101
void genCodeForShift(GenTree* tree);
11021102

11031103
#if defined(TARGET_X86) || defined(TARGET_ARM)

src/coreclr/jit/codegenarmarch.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,10 @@ void CodeGen::genSetGSSecurityCookie(regNumber initReg, bool* pInitRegZeroed)
613613
// genEmitGSCookieCheck: Generate code to check that the GS cookie
614614
// wasn't thrashed by a buffer overrun.
615615
//
616-
void CodeGen::genEmitGSCookieCheck(bool pushReg)
616+
// Parameters:
617+
// tailCall - Whether or not this is being emitted for a tail call
618+
//
619+
void CodeGen::genEmitGSCookieCheck(bool tailCall)
617620
{
618621
noway_assert(compiler->gsGlobalSecurityCookieAddr || compiler->gsGlobalSecurityCookieVal);
619622

@@ -623,8 +626,9 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
623626
// We don't have any IR node representing this check, so LSRA can't communicate registers
624627
// for us to use.
625628

626-
regNumber regGSConst = REG_GSCOOKIE_TMP_0;
627-
regNumber regGSValue = REG_GSCOOKIE_TMP_1;
629+
regMaskTP tmpRegs = genGetGSCookieTempRegs(tailCall);
630+
regNumber regGSConst = genFirstRegNumFromMaskAndToggle(tmpRegs);
631+
regNumber regGSValue = genFirstRegNumFromMaskAndToggle(tmpRegs);
628632

629633
if (compiler->gsGlobalSecurityCookieAddr == nullptr)
630634
{
@@ -3266,12 +3270,12 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
32663270
{
32673271
regMaskTP trashedByEpilog = RBM_CALLEE_SAVED;
32683272

3269-
// The epilog may use and trash REG_GSCOOKIE_TMP_0/1. Make sure we have no
3270-
// non-standard args that may be trash if this is a tailcall.
3273+
// The epilog may use and trash some registers for the GS cookie check.
3274+
// Make sure we have no non-standard args that may be trash if this is
3275+
// a tailcall.
32713276
if (compiler->getNeedsGSSecurityCookie())
32723277
{
3273-
trashedByEpilog |= genRegMask(REG_GSCOOKIE_TMP_0);
3274-
trashedByEpilog |= genRegMask(REG_GSCOOKIE_TMP_1);
3278+
trashedByEpilog |= genGetGSCookieTempRegs(/* tailCall */ true);
32753279
}
32763280

32773281
for (CallArg& arg : call->gtArgs.Args())

src/coreclr/jit/codegencommon.cpp

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1552,14 +1552,11 @@ void CodeGen::genExitCode(BasicBlock* block)
15521552
Note that this may result in a duplicate IPmapping entry, and
15531553
that this is ok */
15541554

1555-
// For non-optimized debuggable code, there is only one epilog.
15561555
genIPmappingAdd(IPmappingDscKind::Epilog, DebugInfo(), true);
15571556

1558-
bool jmpEpilog = block->HasFlag(BBF_HAS_JMP);
1559-
15601557
#ifdef DEBUG
15611558
// For returnining epilogs do some validation that the GC info looks right.
1562-
if (!jmpEpilog)
1559+
if (!block->HasFlag(BBF_HAS_JMP))
15631560
{
15641561
if (compiler->compMethodReturnsRetBufAddr())
15651562
{
@@ -1583,7 +1580,7 @@ void CodeGen::genExitCode(BasicBlock* block)
15831580

15841581
if (compiler->getNeedsGSSecurityCookie())
15851582
{
1586-
genEmitGSCookieCheck(jmpEpilog);
1583+
genEmitGSCookieCheck(block->HasFlag(BBF_HAS_JMP));
15871584
}
15881585

15891586
genReserveEpilog(block);
@@ -2508,6 +2505,47 @@ CorInfoHelpFunc CodeGenInterface::genWriteBarrierHelperForWriteBarrierForm(GCInf
25082505
}
25092506
}
25102507

2508+
// -----------------------------------------------------------------------------
2509+
// genGetGSCookieTempRegs:
2510+
// Get a mask of registers to use for the GS cookie check generated in a
2511+
// block.
2512+
//
2513+
// Parameters:
2514+
// tailCall - Whether the block is a tailcall
2515+
//
2516+
// Returns:
2517+
// Mask of all the registers that can be used. Some targets may need more
2518+
// than one register.
2519+
//
2520+
regMaskTP CodeGenInterface::genGetGSCookieTempRegs(bool tailCall)
2521+
{
2522+
#ifdef TARGET_XARCH
2523+
if (tailCall)
2524+
{
2525+
// If we are tailcalling then return registers are available for use
2526+
return RBM_RAX;
2527+
}
2528+
2529+
#ifdef TARGET_AMD64
2530+
// Otherwise on x64 (win-x64 and SysV) r8 is never used for return values
2531+
return RBM_R8;
2532+
#else
2533+
// On x86 it's more difficult: we have only eax, ecx and edx available as volatile
2534+
// registers, and all of them may be used for return values (longs + async continuation).
2535+
if (compiler->compIsAsync())
2536+
{
2537+
// Just use a callee save for this rare async + gs cookie check case.
2538+
return RBM_ESI;
2539+
}
2540+
2541+
// Outside async ecx is not used for returns.
2542+
return RBM_ECX;
2543+
#endif
2544+
#else
2545+
return RBM_GSCOOKIE_TMP;
2546+
#endif
2547+
}
2548+
25112549
//----------------------------------------------------------------------
25122550
// genGCWriteBarrier: Generate a write barrier for a node.
25132551
//

src/coreclr/jit/codegeninterface.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ class CodeGenInterface
217217
bool genWriteBarrierUsed;
218218
#endif
219219

220+
regMaskTP genGetGSCookieTempRegs(bool tailCall);
221+
220222
// The following property indicates whether the current method sets up
221223
// an explicit stack frame or not.
222224
private:

src/coreclr/jit/codegenloongarch64.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4464,7 +4464,10 @@ void CodeGen::genSetGSSecurityCookie(regNumber initReg, bool* pInitRegZeroed)
44644464
// genEmitGSCookieCheck: Generate code to check that the GS cookie
44654465
// wasn't thrashed by a buffer overrun.
44664466
//
4467-
void CodeGen::genEmitGSCookieCheck(bool pushReg)
4467+
// Parameters:
4468+
// tailCall - Whether or not this is being emitted for a tail call
4469+
//
4470+
void CodeGen::genEmitGSCookieCheck(bool tailCall)
44684471
{
44694472
noway_assert(compiler->gsGlobalSecurityCookieAddr || compiler->gsGlobalSecurityCookieVal);
44704473

@@ -4474,8 +4477,9 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
44744477
// We don't have any IR node representing this check, so LSRA can't communicate registers
44754478
// for us to use.
44764479

4477-
regNumber regGSConst = REG_GSCOOKIE_TMP_0;
4478-
regNumber regGSValue = REG_GSCOOKIE_TMP_1;
4480+
regMaskTP tmpRegs = genGetGSCookieTempRegs(tailCall);
4481+
regNumber regGSConst = genFirstRegNumFromMaskAndToggle(tmpRegs);
4482+
regNumber regGSValue = genFirstRegNumFromMaskAndToggle(tmpRegs);
44794483

44804484
if (compiler->gsGlobalSecurityCookieAddr == nullptr)
44814485
{
@@ -5691,12 +5695,11 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
56915695
{
56925696
regMaskTP trashedByEpilog = RBM_CALLEE_SAVED;
56935697

5694-
// The epilog may use and trash REG_GSCOOKIE_TMP_0/1. Make sure we have no
5698+
// The epilog may use and trash REG_GSCOOKIE_TMP. Make sure we have no
56955699
// non-standard args that may be trash if this is a tailcall.
56965700
if (compiler->getNeedsGSSecurityCookie())
56975701
{
5698-
trashedByEpilog |= genRegMask(REG_GSCOOKIE_TMP_0);
5699-
trashedByEpilog |= genRegMask(REG_GSCOOKIE_TMP_1);
5702+
trashedByEpilog |= genGetGSCookieTempRegs(/* tailCall */ true);
57005703
}
57015704

57025705
for (CallArg& arg : call->gtArgs.Args())

src/coreclr/jit/codegenriscv64.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4473,7 +4473,10 @@ void CodeGen::genSetGSSecurityCookie(regNumber initReg, bool* pInitRegZeroed)
44734473
// genEmitGSCookieCheck: Generate code to check that the GS cookie
44744474
// wasn't thrashed by a buffer overrun.
44754475
//
4476-
void CodeGen::genEmitGSCookieCheck(bool pushReg)
4476+
// Parameters:
4477+
// tailCall - Whether or not this is being emitted for a tail call
4478+
//
4479+
void CodeGen::genEmitGSCookieCheck(bool tailCall)
44774480
{
44784481
noway_assert(compiler->gsGlobalSecurityCookieAddr || compiler->gsGlobalSecurityCookieVal);
44794482

@@ -4483,8 +4486,9 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
44834486
// We don't have any IR node representing this check, so LSRA can't communicate registers
44844487
// for us to use.
44854488

4486-
regNumber regGSConst = REG_GSCOOKIE_TMP_0;
4487-
regNumber regGSValue = REG_GSCOOKIE_TMP_1;
4489+
regMaskTP tmpRegs = genGetGSCookieTempRegs(tailCall);
4490+
regNumber regGSConst = genFirstRegNumFromMaskAndToggle(tmpRegs);
4491+
regNumber regGSValue = genFirstRegNumFromMaskAndToggle(tmpRegs);
44884492

44894493
if (compiler->gsGlobalSecurityCookieAddr == nullptr)
44904494
{
@@ -5748,12 +5752,12 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
57485752
{
57495753
regMaskTP trashedByEpilog = RBM_CALLEE_SAVED;
57505754

5751-
// The epilog may use and trash REG_GSCOOKIE_TMP_0/1. Make sure we have no
5752-
// non-standard args that may be trash if this is a tailcall.
5755+
// The epilog may use and trash some registers for the GS cookie check.
5756+
// Make sure we have no non-standard args that may be trash if this is
5757+
// a tailcall.
57535758
if (compiler->getNeedsGSSecurityCookie())
57545759
{
5755-
trashedByEpilog |= genRegMask(REG_GSCOOKIE_TMP_0);
5756-
trashedByEpilog |= genRegMask(REG_GSCOOKIE_TMP_1);
5760+
trashedByEpilog |= genGetGSCookieTempRegs(/* tailCall */ true);
57575761
}
57585762

57595763
for (CallArg& arg : call->gtArgs.Args())

src/coreclr/jit/codegenxarch.cpp

Lines changed: 11 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -84,64 +84,20 @@ void CodeGen::genSetGSSecurityCookie(regNumber initReg, bool* pInitRegZeroed)
8484
}
8585
}
8686

87-
/*****************************************************************************
88-
*
89-
* Generate code to check that the GS cookie wasn't thrashed by a buffer
90-
* overrun. If pushReg is true, preserve all registers around code sequence.
91-
* Otherwise ECX could be modified.
92-
*
93-
* Implementation Note: pushReg = true, in case of tail calls.
94-
*/
95-
void CodeGen::genEmitGSCookieCheck(bool pushReg)
87+
//---------------------------------------------------------------------
88+
// genEmitGSCookieCheck:
89+
// Emit the check that the GS cookie has its original value.
90+
//
91+
// Parameters:
92+
// tailCall - Whether or not this is being emitted for a tail call
93+
//
94+
void CodeGen::genEmitGSCookieCheck(bool tailCall)
9695
{
9796
noway_assert(compiler->gsGlobalSecurityCookieAddr || compiler->gsGlobalSecurityCookieVal);
9897

99-
regNumber regGSCheck;
100-
regMaskTP regMaskGSCheck = RBM_NONE;
101-
102-
if (!pushReg)
103-
{
104-
// Non-tail call: we can use any callee trash register that is not
105-
// a return register or contain 'this' pointer (keep alive this), since
106-
// we are generating GS cookie check after a GT_RETURN block.
107-
// Note: On Amd64 System V RDX is an arg register - REG_ARG_2 - as well
108-
// as return register for two-register-returned structs.
109-
#ifdef TARGET_X86
110-
// Note: ARG_0 can be REG_ASYNC_CONTINUATION_RET
111-
// we will check for that later if we end up saving/restoring this.
112-
regGSCheck = REG_ARG_0;
113-
regNumber regGSCheckAlternative = REG_ARG_1;
114-
#else
115-
// these cannot be a part of any kind of return
116-
regGSCheck = REG_R8;
117-
regNumber regGSCheckAlternative = REG_R9;
118-
#endif
119-
120-
if (compiler->lvaKeepAliveAndReportThis() && compiler->lvaGetDesc(compiler->info.compThisArg)->lvIsInReg() &&
121-
(compiler->lvaGetDesc(compiler->info.compThisArg)->GetRegNum() == regGSCheck))
122-
{
123-
regGSCheck = regGSCheckAlternative;
124-
}
125-
}
126-
else
127-
{
128-
#ifdef TARGET_X86
129-
// It doesn't matter which register we pick, since we're going to save and restore it
130-
// around the check.
131-
// TODO-CQ: Can we optimize the choice of register to avoid doing the push/pop sometimes?
132-
regGSCheck = REG_EAX;
133-
regMaskGSCheck = RBM_EAX;
134-
#else // !TARGET_X86
135-
// Jmp calls: specify method handle using which JIT queries VM for its entry point
136-
// address and hence it can neither be a VSD call nor PInvoke calli with cookie
137-
// parameter. Therefore, in case of jmp calls it is safe to use R11.
138-
regGSCheck = REG_R11;
139-
#endif // !TARGET_X86
140-
}
141-
142-
regMaskTP byrefPushedRegs = RBM_NONE;
143-
regMaskTP norefPushedRegs = RBM_NONE;
144-
regMaskTP pushedRegs = RBM_NONE;
98+
regMaskTP tempRegs = genGetGSCookieTempRegs(tailCall);
99+
assert(tempRegs != RBM_NONE);
100+
regNumber regGSCheck = genFirstRegNumFromMask(tempRegs);
145101

146102
if (compiler->gsGlobalSecurityCookieAddr == nullptr)
147103
{
@@ -163,18 +119,6 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
163119
}
164120
else
165121
{
166-
// AOT case - GS cookie value needs to be accessed through an indirection.
167-
168-
// if we use the continuation reg, the pop/push requires no-GC
169-
// this can happen only when AOT supports async on x86
170-
if (compiler->compIsAsync() && (regGSCheck == REG_ASYNC_CONTINUATION_RET))
171-
{
172-
regMaskGSCheck = RBM_ASYNC_CONTINUATION_RET;
173-
GetEmitter()->emitDisableGC();
174-
}
175-
176-
pushedRegs = genPushRegs(regMaskGSCheck, &byrefPushedRegs, &norefPushedRegs);
177-
178122
instGen_Set_Reg_To_Imm(EA_HANDLE_CNS_RELOC, regGSCheck, (ssize_t)compiler->gsGlobalSecurityCookieAddr);
179123
GetEmitter()->emitIns_R_AR(ins_Load(TYP_I_IMPL), EA_PTRSIZE, regGSCheck, regGSCheck, 0);
180124
GetEmitter()->emitIns_S_R(INS_cmp, EA_PTRSIZE, regGSCheck, compiler->lvaGSSecurityCookie, 0);
@@ -184,8 +128,6 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)
184128
inst_JMP(EJ_je, gsCheckBlk);
185129
genEmitHelperCall(CORINFO_HELP_FAIL_FAST, 0, EA_UNKNOWN);
186130
genDefineTempLabel(gsCheckBlk);
187-
188-
genPopRegs(pushedRegs, byrefPushedRegs, norefPushedRegs);
189131
}
190132

191133
BasicBlock* CodeGen::genCallFinally(BasicBlock* block)
@@ -6043,24 +5985,6 @@ void CodeGen::genCall(GenTreeCall* call)
60435985
// all virtuals should have been expanded into a control expression
60445986
assert(!call->IsVirtual() || call->gtControlExpr || call->gtCallAddr);
60455987

6046-
// Insert a GS check if necessary
6047-
if (call->IsTailCallViaJitHelper())
6048-
{
6049-
if (compiler->getNeedsGSSecurityCookie())
6050-
{
6051-
#if FEATURE_FIXED_OUT_ARGS
6052-
// If either of the conditions below is true, we will need a temporary register in order to perform the GS
6053-
// cookie check. When FEATURE_FIXED_OUT_ARGS is disabled, we save and restore the temporary register using
6054-
// push/pop. When FEATURE_FIXED_OUT_ARGS is enabled, however, we need an alternative solution. For now,
6055-
// though, the tail prefix is ignored on all platforms that use fixed out args, so we should never hit this
6056-
// case.
6057-
assert(compiler->gsGlobalSecurityCookieAddr == nullptr);
6058-
assert((int)compiler->gsGlobalSecurityCookieVal == (ssize_t)compiler->gsGlobalSecurityCookieVal);
6059-
#endif
6060-
genEmitGSCookieCheck(true);
6061-
}
6062-
}
6063-
60645988
genCallPlaceRegArgs(call);
60655989

60665990
#if defined(TARGET_X86)

src/coreclr/jit/lower.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3177,10 +3177,6 @@ void Lowering::LowerFastTailCall(GenTreeCall* call)
31773177
assert(!call->IsUnmanaged()); // tail calls to unamanaged methods
31783178
assert(!comp->compLocallocUsed); // tail call from methods that also do localloc
31793179

3180-
#ifdef TARGET_AMD64
3181-
assert(!comp->getNeedsGSSecurityCookie()); // jit64 compat: tail calls from methods that need GS check
3182-
#endif // TARGET_AMD64
3183-
31843180
// We expect to see a call that meets the following conditions
31853181
assert(call->IsFastTailCall());
31863182

src/coreclr/jit/lsraarmarch.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,7 @@ int LinearScan::BuildCall(GenTreeCall* call)
174174
ctrlExprCandidates = allRegs(TYP_INT) & RBM_INT_CALLEE_TRASH.GetIntRegSet() & ~SRBM_LR;
175175
if (compiler->getNeedsGSSecurityCookie())
176176
{
177-
ctrlExprCandidates &=
178-
~(genSingleTypeRegMask(REG_GSCOOKIE_TMP_0) | genSingleTypeRegMask(REG_GSCOOKIE_TMP_1));
177+
ctrlExprCandidates &= ~compiler->codeGen->genGetGSCookieTempRegs(/* tailCall */ true).GetIntRegSet();
179178
}
180179
assert(ctrlExprCandidates != RBM_NONE);
181180
}

src/coreclr/jit/lsrabuild.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2535,6 +2535,16 @@ void LinearScan::buildIntervals()
25352535
currentLoc += 2;
25362536
}
25372537

2538+
if (compiler->getNeedsGSSecurityCookie() && block->KindIs(BBJ_RETURN))
2539+
{
2540+
// The cookie check will kill some registers that it is using.
2541+
// Model this to ensure values that are kept live throughout the
2542+
// method are properly made available.
2543+
bool isTailCall = block->HasFlag(BBF_HAS_JMP);
2544+
addKillForRegs(compiler->codeGen->genGetGSCookieTempRegs(isTailCall), currentLoc + 1);
2545+
currentLoc += 2;
2546+
}
2547+
25382548
if (localVarsEnregistered)
25392549
{
25402550
#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE

0 commit comments

Comments
 (0)