Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit d84eba2

Browse files
committed
Fix ARM GCStress hole with byref write barrier helper
When unrolling a STOREOBJ, we can generate multiple consecutive byref helper calls. This helper has a unique calling convention where the dst and src addresses are modified by adding pointer size to their original value (thus allowing consecutive helper calls without reloading the dst/src addresses). So, for liveness purposes, the helper call kills the dst/src values. However, for GC purposes, it does not, as the registers still contain byref pointers. We were, in the ARM case, reporting the r0/r1 registers dead after the first call, so a GC didn't update them, and a second call updated garbage. In fixing this, I cleaned up the helper call kill handling a bit. I also fixed and improved RyuJIT/x86 write barrier kill modeling.
1 parent e275d2d commit d84eba2

File tree

10 files changed

+236
-54
lines changed

10 files changed

+236
-54
lines changed

src/jit/codegen.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ class CodeGen : public CodeGenInterface
269269
void genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize);
270270
#endif
271271

272-
void genGCWriteBarrier(GenTreePtr tree, GCInfo::WriteBarrierForm wbf);
272+
void genGCWriteBarrier(GenTreePtr tgt, GCInfo::WriteBarrierForm wbf);
273273

274274
BasicBlock* genCreateTempLabel();
275275

src/jit/codegenarm.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -873,8 +873,10 @@ void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode)
873873
#endif // DEBUG
874874

875875
// Consume the operands and get them into the right registers.
876-
// They may now contain gc pointers; genConsumeBlockOp will take care of that.
876+
// They may now contain gc pointers (depending on their type; gcMarkRegPtrVal will "do the right thing").
877877
genConsumeBlockOp(cpObjNode, REG_WRITE_BARRIER_DST_BYREF, REG_WRITE_BARRIER_SRC_BYREF, REG_NA);
878+
gcInfo.gcMarkRegPtrVal(REG_WRITE_BARRIER_SRC_BYREF, srcAddrType);
879+
gcInfo.gcMarkRegPtrVal(REG_WRITE_BARRIER_DST_BYREF, dstAddr->TypeGet());
878880

879881
// Temp register used to perform the sequence of loads and stores.
880882
regNumber tmpReg = cpObjNode->ExtractTempReg();

src/jit/codegencommon.cpp

Lines changed: 142 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -600,8 +600,11 @@ void CodeGenInterface::genUpdateRegLife(const LclVarDsc* varDsc, bool isBorn, bo
600600
}
601601
}
602602

603-
// Gets a register mask that represent the kill set for a helper call since
604-
// not all JIT Helper calls follow the standard ABI on the target architecture.
603+
//----------------------------------------------------------------------
604+
// compNoGCHelperCallKillSet:
605+
//
606+
// Gets a register mask that represents the kill set for a helper call.
607+
// Not all JIT Helper calls follow the standard ABI on the target architecture.
605608
//
606609
// TODO-CQ: Currently this list is incomplete (not all helpers calls are
607610
// enumerated) and not 100% accurate (some killsets are bigger than
@@ -624,19 +627,24 @@ void CodeGenInterface::genUpdateRegLife(const LclVarDsc* varDsc, bool isBorn, bo
624627
//
625628
// The interim solution is to only add known helper calls that don't
626629
// follow the AMD64 ABI and actually trash registers that are supposed to be non-volatile.
630+
//
631+
// Arguments:
632+
// helper - The helper being inquired about
633+
//
634+
// Return Value:
635+
// Mask of register kills -- registers whose value is no longer guaranteed to be the same.
636+
//
627637
regMaskTP Compiler::compHelperCallKillSet(CorInfoHelpFunc helper)
628638
{
629639
switch (helper)
630640
{
631641
case CORINFO_HELP_ASSIGN_BYREF:
632642
#if defined(_TARGET_AMD64_)
633-
return RBM_RSI | RBM_RDI | RBM_CALLEE_TRASH;
634-
#elif defined(_TARGET_ARM64_)
643+
return RBM_RSI | RBM_RDI | RBM_CALLEE_TRASH_NOGC;
644+
#elif defined(_TARGET_ARMARCH_)
635645
return RBM_WRITE_BARRIER_SRC_BYREF | RBM_WRITE_BARRIER_DST_BYREF | RBM_CALLEE_TRASH_NOGC;
636646
#elif defined(_TARGET_X86_)
637647
return RBM_ESI | RBM_EDI | RBM_ECX;
638-
#elif defined(_TARGET_ARM_)
639-
return RBM_ARG_1 | RBM_ARG_0 | RBM_CALLEE_TRASH_NOGC;
640648
#else
641649
NYI("Model kill set for CORINFO_HELP_ASSIGN_BYREF on target arch");
642650
return RBM_CALLEE_TRASH;
@@ -663,6 +671,29 @@ regMaskTP Compiler::compHelperCallKillSet(CorInfoHelpFunc helper)
663671
NYI("Model kill set for CORINFO_HELP_PROF_FCN_TAILCALL on target arch");
664672
#endif
665673

674+
#ifdef _TARGET_X86_
675+
case CORINFO_HELP_ASSIGN_REF_EAX:
676+
case CORINFO_HELP_ASSIGN_REF_ECX:
677+
case CORINFO_HELP_ASSIGN_REF_EBX:
678+
case CORINFO_HELP_ASSIGN_REF_EBP:
679+
case CORINFO_HELP_ASSIGN_REF_ESI:
680+
case CORINFO_HELP_ASSIGN_REF_EDI:
681+
682+
case CORINFO_HELP_CHECKED_ASSIGN_REF_EAX:
683+
case CORINFO_HELP_CHECKED_ASSIGN_REF_ECX:
684+
case CORINFO_HELP_CHECKED_ASSIGN_REF_EBX:
685+
case CORINFO_HELP_CHECKED_ASSIGN_REF_EBP:
686+
case CORINFO_HELP_CHECKED_ASSIGN_REF_ESI:
687+
case CORINFO_HELP_CHECKED_ASSIGN_REF_EDI:
688+
return RBM_EDX;
689+
690+
#ifdef FEATURE_USE_ASM_GC_WRITE_BARRIERS
691+
case CORINFO_HELP_ASSIGN_REF:
692+
case CORINFO_HELP_CHECKED_ASSIGN_REF:
693+
return RBM_EAX | RBM_EDX;
694+
#endif // FEATURE_USE_ASM_GC_WRITE_BARRIERS
695+
#endif
696+
666697
case CORINFO_HELP_STOP_FOR_GC:
667698
return RBM_STOP_FOR_GC_TRASH;
668699

@@ -674,19 +705,30 @@ regMaskTP Compiler::compHelperCallKillSet(CorInfoHelpFunc helper)
674705
}
675706
}
676707

708+
//----------------------------------------------------------------------
709+
// compNoGCHelperCallKillSet: Gets a register mask that represents the set of registers that no longer
710+
// contain GC or byref pointers, for "NO GC" helper calls. This is used by the emitter when determining
711+
// what registers to remove from the current live GC/byref sets (and thus what to report as dead in the
712+
// GC info). Note that for the CORINFO_HELP_ASSIGN_BYREF helper, in particular, the kill set reported by
713+
// compHelperCallKillSet() doesn't match this kill set. compHelperCallKillSet() reports the dst/src
714+
// address registers as killed for liveness purposes, since their values change. However, they still are
715+
// valid byref pointers after the call, so the dst/src address registers are NOT reported as killed here.
716+
//
717+
// Note: This list may not be complete and defaults to the default RBM_CALLEE_TRASH_NOGC registers.
677718
//
678-
// Gets a register mask that represents the kill set for "NO GC" helper calls since
679-
// not all JIT Helper calls follow the standard ABI on the target architecture.
719+
// Arguments:
720+
// helper - The helper being inquired about
680721
//
681-
// Note: This list may not be complete and defaults to the default NOGC registers.
722+
// Return Value:
723+
// Mask of GC register kills
682724
//
683725
regMaskTP Compiler::compNoGCHelperCallKillSet(CorInfoHelpFunc helper)
684726
{
685727
assert(emitter::emitNoGChelper(helper));
686728

687729
switch (helper)
688730
{
689-
#if defined(_TARGET_AMD64_) || defined(_TARGET_X86_)
731+
#if defined(_TARGET_XARCH_)
690732
case CORINFO_HELP_PROF_FCN_ENTER:
691733
return RBM_PROFILER_ENTER_TRASH;
692734

@@ -695,18 +737,15 @@ regMaskTP Compiler::compNoGCHelperCallKillSet(CorInfoHelpFunc helper)
695737

696738
case CORINFO_HELP_PROF_FCN_TAILCALL:
697739
return RBM_PROFILER_TAILCALL_TRASH;
698-
#endif // defined(_TARGET_AMD64_) || defined(_TARGET_X86_)
740+
#endif // defined(_TARGET_XARCH_)
699741

700742
case CORINFO_HELP_ASSIGN_BYREF:
701743
#if defined(_TARGET_AMD64_)
702-
// this helper doesn't trash RSI and RDI
703-
return RBM_CALLEE_TRASH_NOGC & ~(RBM_RSI | RBM_RDI);
744+
return RBM_CALLEE_TRASH_NOGC;
704745
#elif defined(_TARGET_X86_)
705746
// This helper only trashes ECX.
706747
return RBM_ECX;
707-
#elif defined(_TARGET_ARM64_)
708-
return RBM_CALLEE_TRASH_NOGC & ~(RBM_WRITE_BARRIER_SRC_BYREF | RBM_WRITE_BARRIER_DST_BYREF);
709-
#else
748+
#elif defined(_TARGET_ARMARCH_)
710749
return RBM_CALLEE_TRASH_NOGC;
711750
#endif // defined(_TARGET_AMD64_)
712751

@@ -3912,16 +3951,86 @@ void CodeGen::genReportEH()
39123951
assert(XTnum == EHCount);
39133952
}
39143953

3915-
void CodeGen::genGCWriteBarrier(GenTreePtr tgt, GCInfo::WriteBarrierForm wbf)
3954+
//----------------------------------------------------------------------
3955+
// genUseOptimizedWriteBarriers: Determine if an optimized write barrier
3956+
// helper should be used.
3957+
//
3958+
// Arguments:
3959+
// wbf - The WriteBarrierForm of the write (GT_STOREIND) that is happening.
3960+
//
3961+
// Return Value:
3962+
// true if an optimized write barrier helper should be used, false otherwise.
3963+
// Note: only x86 implements (register-specific source) optimized write
3964+
// barriers currently).
3965+
//
3966+
bool CodeGenInterface::genUseOptimizedWriteBarriers(GCInfo::WriteBarrierForm wbf)
3967+
{
3968+
#if defined(_TARGET_X86_) && NOGC_WRITE_BARRIERS
3969+
#ifdef DEBUG
3970+
return (wbf != GCInfo::WBF_NoBarrier_CheckNotHeapInDebug); // This one is always a call to a C++ method.
3971+
#else // !DEBUG
3972+
return true;
3973+
#endif // !DEBUG
3974+
#else // !defined(_TARGET_X86_) || !NOGC_WRITE_BARRIERS
3975+
return false;
3976+
#endif !defined(_TARGET_X86_) || !NOGC_WRITE_BARRIERS
3977+
}
3978+
3979+
//----------------------------------------------------------------------
3980+
// genUseOptimizedWriteBarriers: Determine if an optimized write barrier
3981+
// helper should be used.
3982+
//
3983+
// This has the same functionality as the version of
3984+
// genUseOptimizedWriteBarriers that takes a WriteBarrierForm, but avoids
3985+
// determining what the required write barrier form is, if possible.
3986+
//
3987+
// Arguments:
3988+
// tgt - target tree of write (e.g., GT_STOREIND)
3989+
// assignVal - tree with value to write
3990+
//
3991+
// Return Value:
3992+
// true if an optimized write barrier helper should be used, false otherwise.
3993+
// Note: only x86 implements (register-specific source) optimized write
3994+
// barriers currently).
3995+
//
3996+
bool CodeGenInterface::genUseOptimizedWriteBarriers(GenTree* tgt, GenTree* assignVal)
3997+
{
3998+
#if defined(_TARGET_X86_) && NOGC_WRITE_BARRIERS
3999+
#ifdef DEBUG
4000+
GCInfo::WriteBarrierForm wbf = compiler->codeGen->gcInfo.gcIsWriteBarrierCandidate(tgt, assignVal);
4001+
return (wbf != GCInfo::WBF_NoBarrier_CheckNotHeapInDebug); // This one is always a call to a C++ method.
4002+
#else // !DEBUG
4003+
return true;
4004+
#endif // !DEBUG
4005+
#else
4006+
return false;
4007+
#endif
4008+
}
4009+
4010+
//----------------------------------------------------------------------
4011+
// genWriteBarrierHelperForWriteBarrierForm: Given a write node requiring a write
4012+
// barrier, and the write barrier form required, determine the helper to call.
4013+
//
4014+
// Arguments:
4015+
// tgt - target tree of write (e.g., GT_STOREIND)
4016+
// wbf - already computed write barrier form to use
4017+
//
4018+
// Return Value:
4019+
// Write barrier helper to use.
4020+
//
4021+
// Note: do not call this function to get an optimized write barrier helper (e.g.,
4022+
// for x86).
4023+
//
4024+
CorInfoHelpFunc CodeGenInterface::genWriteBarrierHelperForWriteBarrierForm(GenTree* tgt, GCInfo::WriteBarrierForm wbf)
39164025
{
39174026
#ifndef LEGACY_BACKEND
39184027
noway_assert(tgt->gtOper == GT_STOREIND);
39194028
#else // LEGACY_BACKEND
39204029
noway_assert(tgt->gtOper == GT_IND || tgt->gtOper == GT_CLS_VAR); // enforced by gcIsWriteBarrierCandidate
39214030
#endif // LEGACY_BACKEND
39224031

3923-
/* Call the proper vm helper */
3924-
int helper = CORINFO_HELP_ASSIGN_REF;
4032+
CorInfoHelpFunc helper = CORINFO_HELP_ASSIGN_REF;
4033+
39254034
#ifdef DEBUG
39264035
if (wbf == GCInfo::WBF_NoBarrier_CheckNotHeapInDebug)
39274036
{
@@ -3949,6 +4058,20 @@ void CodeGen::genGCWriteBarrier(GenTreePtr tgt, GCInfo::WriteBarrierForm wbf)
39494058
((helper == CORINFO_HELP_ASSIGN_REF) &&
39504059
(wbf == GCInfo::WBF_BarrierUnchecked || wbf == GCInfo::WBF_BarrierUnknown)));
39514060

4061+
return helper;
4062+
}
4063+
4064+
//----------------------------------------------------------------------
4065+
// genGCWriteBarrier: Generate a write barrier for a node.
4066+
//
4067+
// Arguments:
4068+
// tgt - target tree of write (e.g., GT_STOREIND)
4069+
// wbf - already computed write barrier form to use
4070+
//
4071+
void CodeGen::genGCWriteBarrier(GenTreePtr tgt, GCInfo::WriteBarrierForm wbf)
4072+
{
4073+
CorInfoHelpFunc helper = genWriteBarrierHelperForWriteBarrierForm(tgt, wbf);
4074+
39524075
#ifdef FEATURE_COUNT_GC_WRITE_BARRIERS
39534076
// We classify the "tgt" trees as follows:
39544077
// If "tgt" is of the form (where [ x ] indicates an optional x, and { x1, ..., xn } means "one of the x_i forms"):

src/jit/codegeninterface.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ class CodeGenInterface
151151
regMaskTP genLiveMask(VARSET_VALARG_TP liveSet);
152152
#endif
153153

154+
public:
155+
bool genUseOptimizedWriteBarriers(GCInfo::WriteBarrierForm wbf);
156+
bool genUseOptimizedWriteBarriers(GenTree* tgt, GenTree* assignVal);
157+
CorInfoHelpFunc genWriteBarrierHelperForWriteBarrierForm(GenTree* tgt, GCInfo::WriteBarrierForm wbf);
158+
154159
// The following property indicates whether the current method sets up
155160
// an explicit stack frame or not.
156161
private:

src/jit/codegenxarch.cpp

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4987,30 +4987,34 @@ bool CodeGen::genEmitOptimizedGCWriteBarrier(GCInfo::WriteBarrierForm writeBarri
49874987
assert(writeBarrierForm != GCInfo::WBF_NoBarrier);
49884988

49894989
#if defined(_TARGET_X86_) && NOGC_WRITE_BARRIERS
4990-
bool useOptimizedWriteBarriers = true;
4991-
4992-
#ifdef DEBUG
4993-
useOptimizedWriteBarriers =
4994-
(writeBarrierForm != GCInfo::WBF_NoBarrier_CheckNotHeapInDebug); // This one is always a call to a C++ method.
4995-
#endif
4996-
4997-
if (!useOptimizedWriteBarriers)
4990+
if (!genUseOptimizedWriteBarriers(writeBarrierForm))
49984991
{
49994992
return false;
50004993
}
50014994

50024995
const static int regToHelper[2][8] = {
50034996
// If the target is known to be in managed memory
50044997
{
5005-
CORINFO_HELP_ASSIGN_REF_EAX, CORINFO_HELP_ASSIGN_REF_ECX, -1, CORINFO_HELP_ASSIGN_REF_EBX, -1,
5006-
CORINFO_HELP_ASSIGN_REF_EBP, CORINFO_HELP_ASSIGN_REF_ESI, CORINFO_HELP_ASSIGN_REF_EDI,
4998+
CORINFO_HELP_ASSIGN_REF_EAX, // EAX
4999+
CORINFO_HELP_ASSIGN_REF_ECX, // ECX
5000+
-1, // EDX (always the target address)
5001+
CORINFO_HELP_ASSIGN_REF_EBX, // EBX
5002+
-1, // ESP
5003+
CORINFO_HELP_ASSIGN_REF_EBP, // EBP
5004+
CORINFO_HELP_ASSIGN_REF_ESI, // ESI
5005+
CORINFO_HELP_ASSIGN_REF_EDI, // EDI
50075006
},
50085007

50095008
// Don't know if the target is in managed memory
50105009
{
5011-
CORINFO_HELP_CHECKED_ASSIGN_REF_EAX, CORINFO_HELP_CHECKED_ASSIGN_REF_ECX, -1,
5012-
CORINFO_HELP_CHECKED_ASSIGN_REF_EBX, -1, CORINFO_HELP_CHECKED_ASSIGN_REF_EBP,
5013-
CORINFO_HELP_CHECKED_ASSIGN_REF_ESI, CORINFO_HELP_CHECKED_ASSIGN_REF_EDI,
5010+
CORINFO_HELP_CHECKED_ASSIGN_REF_EAX, // EAX
5011+
CORINFO_HELP_CHECKED_ASSIGN_REF_ECX, // ECX
5012+
-1, // EDX (always the target address)
5013+
CORINFO_HELP_CHECKED_ASSIGN_REF_EBX, // EBX
5014+
-1, // ESP
5015+
CORINFO_HELP_CHECKED_ASSIGN_REF_EBP, // EBP
5016+
CORINFO_HELP_CHECKED_ASSIGN_REF_ESI, // ESI
5017+
CORINFO_HELP_CHECKED_ASSIGN_REF_EDI, // EDI
50145018
},
50155019
};
50165020

src/jit/emitarm.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4476,6 +4476,16 @@ void emitter::emitIns_Call(EmitCallType callType,
44764476
{
44774477
savedSet |= RBM_PROFILER_RET_SCRATCH;
44784478
}
4479+
4480+
#ifdef DEBUG
4481+
if (emitComp->verbose)
4482+
{
4483+
printf("NOGC Call: savedSet=");
4484+
printRegMaskInt(savedSet);
4485+
emitDispRegSet(savedSet);
4486+
printf("\n");
4487+
}
4488+
#endif
44794489
}
44804490
else
44814491
{

src/jit/lsra.cpp

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2786,6 +2786,47 @@ void LinearScan::addRefsForPhysRegMask(regMaskTP mask, LsraLocation currentLoc,
27862786
}
27872787
}
27882788

2789+
//------------------------------------------------------------------------
2790+
// getKillSetForStoreInd: Determine the liveness kill set for a GT_STOREIND node.
2791+
// If the GT_STOREIND will generate a write barrier, determine the specific kill
2792+
// set required by the case-specific, platform-specific write barrier. If no
2793+
// write barrier is required, the kill set will be RBM_NONE.
2794+
//
2795+
// Arguments:
2796+
// tree - the GT_STOREIND node
2797+
//
2798+
// Return Value: a register mask of the registers killed
2799+
//
2800+
regMaskTP LinearScan::getKillSetForStoreInd(GenTreeStoreInd* tree)
2801+
{
2802+
assert(tree->OperIs(GT_STOREIND));
2803+
2804+
regMaskTP killMask = RBM_NONE;
2805+
2806+
GenTree* data = tree->Data();
2807+
2808+
GCInfo::WriteBarrierForm writeBarrierForm = compiler->codeGen->gcInfo.gcIsWriteBarrierCandidate(tree, data);
2809+
if (writeBarrierForm != GCInfo::WBF_NoBarrier)
2810+
{
2811+
if (compiler->codeGen->genUseOptimizedWriteBarriers(writeBarrierForm))
2812+
{
2813+
// We can't determine the exact helper to be used at this point, because it depends on
2814+
// the allocated register for the `data` operand. However, all the (x86) optimized
2815+
// helpers have the same kill set: EDX.
2816+
killMask = RBM_CALLEE_TRASH_NOGC;
2817+
}
2818+
else
2819+
{
2820+
// Figure out which helper we're going to use, and then get the kill set for that helper.
2821+
CorInfoHelpFunc helper =
2822+
compiler->codeGen->genWriteBarrierHelperForWriteBarrierForm(tree, writeBarrierForm);
2823+
killMask = compiler->compHelperCallKillSet(helper);
2824+
}
2825+
}
2826+
2827+
return killMask;
2828+
}
2829+
27892830
//------------------------------------------------------------------------
27902831
// getKillSetForNode: Return the registers killed by the given tree node.
27912832
//
@@ -2936,10 +2977,7 @@ regMaskTP LinearScan::getKillSetForNode(GenTree* tree)
29362977
}
29372978
break;
29382979
case GT_STOREIND:
2939-
if (compiler->codeGen->gcInfo.gcIsWriteBarrierAsgNode(tree))
2940-
{
2941-
killMask = RBM_CALLEE_TRASH_NOGC;
2942-
}
2980+
killMask = getKillSetForStoreInd(tree->AsStoreInd());
29432981
break;
29442982

29452983
#if defined(PROFILING_SUPPORTED)

0 commit comments

Comments
 (0)