@@ -239,8 +239,21 @@ class ShadowStackAllocator
239239 }
240240 };
241241
242+ // We perform queries on whether a given value is GC-exposed in the context of a 'current' node.
243+ // If the current value is derived from one already spilled and still live on the shadow stack,
244+ // we can skip re-spilling it. To pass the context of whether that latter condition is true, we
245+ // use this enum. In general, we can only ascertain with precision 'active'-ness of candidate
246+ // locals and so treat the rest quite conservatively.
247+ //
248+ enum class DefStatus
249+ {
250+ Unknown,// The stack slot (if any) associated with the def may have been overwritten.
251+ Active// The stack slot (if any) associated with the def contains its value.
252+ };
253+
242254static const unsigned GC_EXPOSED_NO =0 ;
243- static const unsigned GC_EXPOSED_YES =1 ;
255+ static const unsigned GC_EXPOSED_SPILL =1 ;
256+ static const unsigned GC_EXPOSED_YES =2 ;
244257static const unsigned GC_EXPOSED_UNKNOWN = ValueNumStore::NoVN;
245258static const unsigned LAST_ACTIVE_USE_IS_LAST_USE_BIT =1 <<31 ;
246259
@@ -353,15 +366,11 @@ class ShadowStackAllocator
353366SpillSdsuValue (blockRange, retBufNode, &spillLclNum);
354367 m_liveSdsuGcDefs.AddOrUpdate (retBufNode, spillLclNum);
355368 }
356- else if (retBufNode-> OperIsLocalRead ( ))
369+ else if (varTypeIsGC ( retBufNode) && IsCandidateLocalNode (retBufNode ))
357370 {
358371unsigned lclNum = retBufNode->AsLclVarCommon ()->GetLclNum ();
359- LclVarDsc* varDsc = m_compiler->lvaGetDesc (lclNum);
360- if (IsCandidateLocal (varDsc))
361- {
362- SpillLocalValue (lclNum, retBufNode->AsLclVarCommon ()->GetSsaNum ()
363- DEBUGARG (" is used as a return buffer" ));
364- }
372+ unsigned ssaNum = retBufNode->AsLclVarCommon ()->GetSsaNum ();
373+ SpillLocalValue (lclNum, ssaNumDEBUGARG (" is used as a return buffer" ));
365374 }
366375 }
367376
@@ -578,7 +587,7 @@ class ShadowStackAllocator
578587 GenTreeLclVarCommon* defNode = ssaDsc->GetDefNode ();
579588
580589INDEBUG (const char * reason);
581- if (!IsGcExposedLocalValue (ssaDsc DEBUGARG (&reason)))
590+ if (!IsGcExposedLocalValue (varDsc, ssaNum, DefStatus::Active DEBUGARG (&reason)))
582591 {
583592JITDUMP (" V%02u/%d %s, but did not insert a spill: %s\n " , lclNum, ssaNum, spillReason, reason);
584593return ;
@@ -620,51 +629,73 @@ class ShadowStackAllocator
620629MarkLocalSpilled (varDsc, ssaDsc);
621630 }
622631
623- bool IsGcExposedValue (GenTree* node)
632+ bool IsGcExposedValue (GenTree* node, DefStatus defStatus )
624633 {
625634// TODO-LLVM-LSSA-CQ: add handling for PHIs here. Take note to handle cycles properly.
626635assert (node->IsValue ());
627- if (node->OperIsLocalRead ())
636+ LclVarDsc* varDsc;
637+ if (IsCandidateLocalNode (node, &varDsc))
628638 {
629- LclVarDsc* varDsc = m_compiler->lvaGetDesc (node->AsLclVarCommon ());
630- if (IsCandidateLocal (varDsc))
631- {
632- LclSsaVarDsc* ssaDsc = varDsc->GetPerSsaData (node->AsLclVarCommon ()->GetSsaNum ());
633- return IsGcExposedLocalValue (ssaDsc);
634- }
635-
636- // Non-candidate locals are always on the shadow stack and so not exposed.
637- return false ;
639+ return IsGcExposedLocalValue (varDsc, node->AsLclVarCommon ()->GetSsaNum (), defStatus);
638640 }
639641
640- return IsGcExposedSdsuValue (node);
642+ return IsGcExposedSdsuValue (node, defStatus );
641643 }
642644
643- bool IsGcExposedSdsuValue (GenTree* node)
645+ bool IsGcExposedSdsuValue (GenTree* node, DefStatus defStatus )
644646 {
645- assert (node->IsValue () && !node-> OperIsLocal ( ));
647+ assert (node->IsValue () && !IsCandidateLocalNode (node ));
646648
647649// Addition and subtraction of byrefs follows convenient rules that allow us to consider only
648650// whether the "base" is exposed or not. Namely, byref arithmetic must stay within the bounds
649651// of the underlying object, or be performed on pinned values only.
650652if (node->OperIs (GT_ADD, GT_SUB) && node->gtGetOp2 ()->TypeIs (TYP_I_IMPL))
651653 {
652- return IsGcExposedValue (node->gtGetOp1 ());
654+ return IsGcExposedValue (node->gtGetOp1 (), DefStatus::Unknown);
655+ }
656+
657+ if (node->OperIsLocalRead ())
658+ {
659+ // For non-candidate locals, all definitions are spilled to the shadow stack. Thus, the only
660+ // way for a value to get exposed is if something modifies the backing location. This can
661+ // happen while the SDSU is still live. E. g.:
662+ // t1 = LCL_VAR V00
663+ // CALL(&V00) ; A safe point, does V00 = null
664+ // USE(t1)
665+ // Thanks to IR invariants, the above can only occur with address-exposed locals. However,
666+ // even a 'normal' untracked local may be redefined via a local store; we handle this via
667+ // the check on 'defStatus'.
668+ //
669+ if ((defStatus == DefStatus::Unknown) ||
670+ m_compiler->lvaGetDesc (node->AsLclVarCommon ())->IsAddressExposed ())
671+ {
672+ return true ;
673+ }
674+
675+ return false ;
653676 }
654677
655678// Local address nodes always point to the stack (native or shadow). Constant handles will
656- // only point to immortal and immovable (frozen) objects.
657- return !node->OperIs (GT_LCL_ADDR) && !node->IsIconHandle () && !node->IsIntegralConst (0 );
679+ // only point to immortal and immovable (frozen) objects. TODO-LLVM-LSSA-CQ: add LCLHEAP here.
680+ if (node->OperIs (GT_LCL_ADDR) || node->IsIconHandle () || node->IsIntegralConst (0 ))
681+ {
682+ return false ;
683+ }
684+
685+ return true ;
658686 }
659687
660- bool IsGcExposedLocalValue (LclSsaVarDsc* ssaDscDEBUGARG (const char ** pReason =nullptr ))
688+ bool IsGcExposedLocalValue (
689+ LclVarDsc* varDsc,unsigned ssaNum, DefStatus defStatusDEBUGARG (const char ** pReason =nullptr ))
661690 {
691+ LclSsaVarDsc* ssaDsc = varDsc->GetPerSsaData (ssaNum);
662692unsigned status =GetGcExposedStatus (ssaDscDEBUGARG (pReason));
663693if (status == GC_EXPOSED_UNKNOWN)
664694 {
665695INDEBUG (const char * reason =nullptr );
666696 GenTreeLclVarCommon* defNode = ssaDsc->GetDefNode ();
667- if ((defNode !=nullptr ) && defNode->OperIs (GT_STORE_LCL_VAR) && !IsGcExposedValue (defNode->Data ()))
697+ if ((defNode !=nullptr ) && defNode->OperIs (GT_STORE_LCL_VAR) &&
698+ !IsGcExposedValue (defNode->Data (), DefStatus::Unknown))
668699 {
669700INDEBUG (reason =" value not exposed" );
670701 status = GC_EXPOSED_NO;
@@ -678,6 +709,26 @@ class ShadowStackAllocator
678709SetGcExposedStatus (ssaDsc, statusDEBUGARG (reason));
679710DBEXEC (pReason !=nullptr , *pReason = reason);
680711 }
712+ // Take care not to depend on potentially stale information. Consider:
713+ //
714+ // V00/1 = X
715+ // SAFE_POINT(...) ; V00/1 is spilled
716+ // V01/1 = V00/1
717+ //
718+ // V00/2 = Y
719+ // SAFE_POINT(...) ; V00/2 is also spilled, to the same stack slot.
720+ //
721+ // USE(V01/1) ; We have to spill V01/1, since its equal to 'X', not 'Y'
722+ // that is the active value of the spilled V00.
723+ //
724+ // TODO-LLVM-LSSA-CQ: we can be more intelligent here be allocating separate
725+ // shadow stack slots to distinct SSA definitions of the same local.
726+ //
727+ else if ((status == GC_EXPOSED_SPILL) && (defStatus == DefStatus::Unknown) &&
728+ (m_activeDefs.Top (varDsc->lvVarIndex ) != ssaNum))
729+ {
730+ status = GC_EXPOSED_YES;
731+ }
681732
682733assert (status != GC_EXPOSED_UNKNOWN);
683734return status == GC_EXPOSED_YES;
@@ -686,7 +737,7 @@ class ShadowStackAllocator
686737void SetGcExposedStatus (LclSsaVarDsc* ssaDsc,unsigned statusDEBUGARG (const char * reason))
687738 {
688739unsigned * pStatus =GetRawGcExposedStatusRef (ssaDsc);
689- assert ((*pStatus == GC_EXPOSED_UNKNOWN) || ((*pStatus == GC_EXPOSED_YES) && (status ==GC_EXPOSED_NO )));
740+ assert ((*pStatus == GC_EXPOSED_UNKNOWN) || ((*pStatus == GC_EXPOSED_YES) && (status ==GC_EXPOSED_SPILL )));
690741assert (status != GC_EXPOSED_UNKNOWN);
691742
692743DBEXEC (reason !=nullptr , m_gcExposedStatusReasonMap.Set (ssaDsc, reason));
@@ -708,38 +759,42 @@ class ShadowStackAllocator
708759void MarkLocalSpilled (LclVarDsc* varDsc, LclSsaVarDsc* ssaDsc)
709760 {
710761 varDsc->SetRegNum (REG_STK_CANDIDATE_COMMITED);
711- SetGcExposedStatus (ssaDsc,GC_EXPOSED_NO DEBUGARG (" already spilled" ));
762+ SetGcExposedStatus (ssaDsc,GC_EXPOSED_SPILL DEBUGARG (" already spilled" ));
712763 }
713764
714765void ProcessDef (BasicBlock* block, GenTree* node)
715766 {
716- if (node->OperIsLocal ( ))
767+ if (node->OperIs (GT_PHI, GT_PHI_ARG ))
717768 {
718- LclVarDsc* varDsc = m_compiler->lvaGetDesc (node->AsLclVarCommon ());
719- if (IsCandidateLocal (varDsc))
720- {
721- // We depend here on the conservativeness of GC in not reloading after a safepoint.
722- assert (m_lssa->IsGcConservative ());
723- node->SetRegNum (REG_LLVM);
769+ // These are never exposed and do not participate in our use counting.
770+ return ;
771+ }
724772
725- // If this is a definition, add it to the stack of currently active ones and update liveness.
726- unsigned ssaNum = node->AsLclVarCommon ()->GetSsaNum ();
727- if (node->OperIsLocalStore ())
728- {
729- PushActiveLocalDef (block, varDsc, ssaNum);
730- UpdateLiveLocalDefs (varDsc, node->AsLclVarCommon ()->HasLastUse (),/* isBorn*/ true );
731- }
732- // Increment the "active" use count used for accurate last use detection. Note that skipping
733- // unused locals here means that we could technically extend their live ranges unnecessarily
734- // (if this was the last 'use'), but all such cases should have been DCEd by this point, and
735- // it's not a correctness problem to skip them.
736- else if (!node->OperIs (GT_PHI_ARG) && !node->IsUnusedValue ())
737- {
738- IncrementActiveUseCount (varDsc->GetPerSsaData (ssaNum));
739- }
773+ LclVarDsc* varDsc;
774+ if (IsCandidateLocalNode (node, &varDsc))
775+ {
776+ // We depend here on the conservativeness of GC in not reloading after a safepoint.
777+ assert (m_lssa->IsGcConservative ());
778+ node->SetRegNum (REG_LLVM);
779+
780+ // If this is a definition, add it to the stack of currently active ones and update liveness.
781+ unsigned ssaNum = node->AsLclVarCommon ()->GetSsaNum ();
782+ if (node->OperIsLocalStore ())
783+ {
784+ PushActiveLocalDef (block, varDsc, ssaNum);
785+ UpdateLiveLocalDefs (varDsc, node->AsLclVarCommon ()->HasLastUse (),/* isBorn*/ true );
786+ }
787+ // Increment the "active" use count used for accurate last use detection. Note that skipping
788+ // unused locals here means that we could technically extend their live ranges unnecessarily
789+ // (if this was the last 'use'), but all such cases should have been DCEd by this point, and
790+ // it's not a correctness problem to skip them.
791+ else if (!node->IsUnusedValue ())
792+ {
793+ IncrementActiveUseCount (varDsc->GetPerSsaData (ssaNum));
740794 }
741795 }
742- else if (node->IsValue () && !node->IsUnusedValue () &&IsGcExposedType (node) &&IsGcExposedSdsuValue (node))
796+ else if (node->IsValue () && !node->IsUnusedValue () &&IsGcExposedType (node) &&
797+ IsGcExposedSdsuValue (node, DefStatus::Active))
743798 {
744799 node->gtLIRFlags |= LIR::Flags::Mark;
745800 m_liveSdsuGcDefs.AddOrUpdate (node, BAD_VAR_NUM);
@@ -754,7 +809,8 @@ class ShadowStackAllocator
754809 }
755810if (node->TypeIs (TYP_STRUCT))
756811 {
757- if (node->OperIs (GT_IND, GT_PHI))
812+ // TODO-LLVM: delete this once we're up to date with upstream deleting "STORE_DYN_BLK".
813+ if (node->OperIs (GT_IND))
758814 {
759815return false ;
760816 }
@@ -837,6 +893,24 @@ class ShadowStackAllocator
837893 (varDsc->GetRegNum () == REG_STK_CANDIDATE_COMMITED);
838894 }
839895
896+ bool IsCandidateLocalNode (GenTree* node, LclVarDsc** pVarDsc =nullptr )
897+ {
898+ if (node->OperIsLocal ())
899+ {
900+ LclVarDsc* varDsc = m_compiler->lvaGetDesc (node->AsLclVarCommon ());
901+ if (IsCandidateLocal (varDsc))
902+ {
903+ if (pVarDsc !=nullptr )
904+ {
905+ *pVarDsc = varDsc;
906+ }
907+ return true ;
908+ }
909+ }
910+
911+ return false ;
912+ }
913+
840914#ifdef DEBUG
841915void VerifyActiveUseCounts ()
842916 {