Simplified MinOpts GC ref tracking. (dotnet/coreclr#9231)
authorPeter Kukol <pkukol@users.noreply.github.com>
Thu, 23 Feb 2017 16:13:34 +0000 (09:13 -0700)
committerGitHub <noreply@github.com>
Thu, 23 Feb 2017 16:13:34 +0000 (09:13 -0700)
Simplified MinOpts GC ref tracking - when a method is compiled with MinOpts for targets with the 64-bit GC info encoder we mark all GC slots as untracked and we omit encoding call sites with no live tracked GC refs explicitly in the GC tables; this can be controlled using the new COMPlus_JitMinOptsTrackGCrefs environment flag.

Commit migrated from https://github.com/dotnet/coreclr/commit/d7509df9635ca92c1a3cf75dc2516ae51b0e03d6

14 files changed:
src/coreclr/src/jit/codegenarm.cpp
src/coreclr/src/jit/codegenarm64.cpp
src/coreclr/src/jit/codegenlegacy.cpp
src/coreclr/src/jit/codegenxarch.cpp
src/coreclr/src/jit/gcencode.cpp
src/coreclr/src/jit/jitconfigvalues.h
src/coreclr/src/jit/jitgcinfo.h
src/coreclr/src/jit/lclvars.cpp
src/coreclr/src/vm/gcinfodecoder.cpp
src/coreclr/tests/src/GC/API/GC/KeepAlive.cs
src/coreclr/tests/src/GC/Scenarios/LeakGen/leakgen.cs
src/coreclr/tests/src/GC/Scenarios/Samples/gc.cs
src/coreclr/tests/src/GC/Scenarios/WeakReference/weakreffinal.cs
src/coreclr/tests/src/JIT/Methodical/Arrays/misc/arrres.cs

index 580cad5..88f576e 100644 (file)
@@ -1916,12 +1916,14 @@ void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize,
     // Follow the code pattern of the x86 gc info encoder (genCreateAndStoreGCInfoJIT32).
     gcInfo.gcInfoBlockHdrSave(gcInfoEncoder, codeSize, prologSize);
 
+    // We keep the call count for the second call to gcMakeRegPtrTable() below.
+    unsigned callCnt = 0;
     // First we figure out the encoder ID's for the stack slots and registers.
-    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS);
+    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS, &callCnt);
     // Now we've requested all the slots we'll need; "finalize" these (make more compact data structures for them).
     gcInfoEncoder->FinalizeSlotIds();
     // Now we can actually use those slot ID's to declare live ranges.
-    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK);
+    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK, &callCnt);
 
     gcInfoEncoder->Build();
 
index 0fb3fb8..2fac6af 100644 (file)
@@ -6055,14 +6055,17 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize
     // Follow the code pattern of the x86 gc info encoder (genCreateAndStoreGCInfoJIT32).
     gcInfo.gcInfoBlockHdrSave(gcInfoEncoder, codeSize, prologSize);
 
+    // We keep the call count for the second call to gcMakeRegPtrTable() below.
+    unsigned callCnt = 0;
+
     // First we figure out the encoder ID's for the stack slots and registers.
-    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS);
+    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS, &callCnt);
 
     // Now we've requested all the slots we'll need; "finalize" these (make more compact data structures for them).
     gcInfoEncoder->FinalizeSlotIds();
 
     // Now we can actually use those slot ID's to declare live ranges.
-    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK);
+    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK, &callCnt);
 
     if (compiler->opts.compDbgEnC)
     {
index c616546..d61dda5 100644 (file)
@@ -20152,12 +20152,14 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize
     // Follow the code pattern of the x86 gc info encoder (genCreateAndStoreGCInfoJIT32).
     gcInfo.gcInfoBlockHdrSave(gcInfoEncoder, codeSize, prologSize);
 
+    // We keep the call count for the second call to gcMakeRegPtrTable() below.
+    unsigned callCnt = 0;
     // First we figure out the encoder ID's for the stack slots and registers.
-    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS);
+    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS, &callCnt);
     // Now we've requested all the slots we'll need; "finalize" these (make more compact data structures for them).
     gcInfoEncoder->FinalizeSlotIds();
     // Now we can actually use those slot ID's to declare live ranges.
-    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK);
+    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK, &callCnt);
 
     gcInfoEncoder->Build();
 
index e893da6..8499d3a 100644 (file)
@@ -8354,12 +8354,14 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize
     // Follow the code pattern of the x86 gc info encoder (genCreateAndStoreGCInfoJIT32).
     gcInfo.gcInfoBlockHdrSave(gcInfoEncoder, codeSize, prologSize);
 
+    // We keep the call count for the second call to gcMakeRegPtrTable() below.
+    unsigned callCnt = 0;
     // First we figure out the encoder ID's for the stack slots and registers.
-    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS);
+    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS, &callCnt);
     // Now we've requested all the slots we'll need; "finalize" these (make more compact data structures for them).
     gcInfoEncoder->FinalizeSlotIds();
     // Now we can actually use those slot ID's to declare live ranges.
-    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK);
+    gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK, &callCnt);
 
     if (compiler->opts.compDbgEnC)
     {
index dcca19e..2b9fe37 100644 (file)
@@ -3844,13 +3844,15 @@ struct InterruptibleRangeReporter
     }
 };
 
-void GCInfo::gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder,
-                               unsigned       codeSize,
-                               unsigned       prologSize,
-                               MakeRegPtrMode mode)
+void GCInfo::gcMakeRegPtrTable(
+    GcInfoEncoder* gcInfoEncoder, unsigned codeSize, unsigned prologSize, MakeRegPtrMode mode, unsigned* callCntRef)
 {
     GCENCODER_WITH_LOGGING(gcInfoEncoderWithLog, gcInfoEncoder);
 
+    const bool noTrackedGCSlots =
+        (compiler->opts.MinOpts() && !compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT) &&
+         !JitConfig.JitMinOptsTrackGCrefs());
+
     if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS)
     {
         m_regSlotMap   = new (compiler->getAllocator()) RegSlotMap(compiler->getAllocator());
@@ -3961,14 +3963,25 @@ void GCInfo::gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder,
             {
                 stackSlotBase = GC_FRAMEREG_REL;
             }
-            StackSlotIdKey sskey(varDsc->lvStkOffs, (stackSlotBase == GC_FRAMEREG_REL), flags);
-            GcSlotId       varSlotId;
-            if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS)
+            if (noTrackedGCSlots)
             {
-                if (!m_stackSlotMap->Lookup(sskey, &varSlotId))
+                // No need to hash/lookup untracked GC refs; just grab a new Slot Id.
+                if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS)
                 {
-                    varSlotId = gcInfoEncoderWithLog->GetStackSlotId(varDsc->lvStkOffs, flags, stackSlotBase);
-                    m_stackSlotMap->Set(sskey, varSlotId);
+                    gcInfoEncoderWithLog->GetStackSlotId(varDsc->lvStkOffs, flags, stackSlotBase);
+                }
+            }
+            else
+            {
+                StackSlotIdKey sskey(varDsc->lvStkOffs, (stackSlotBase == GC_FRAMEREG_REL), flags);
+                GcSlotId       varSlotId;
+                if (mode == MAKE_REG_PTR_MODE_ASSIGN_SLOTS)
+                {
+                    if (!m_stackSlotMap->Lookup(sskey, &varSlotId))
+                    {
+                        varSlotId = gcInfoEncoderWithLog->GetStackSlotId(varDsc->lvStkOffs, flags, stackSlotBase);
+                        m_stackSlotMap->Set(sskey, varSlotId);
+                    }
                 }
             }
         }
@@ -4204,9 +4217,24 @@ void GCInfo::gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder,
         {
             if (gcCallDescList != nullptr)
             {
-                for (CallDsc* call = gcCallDescList; call != nullptr; call = call->cdNext)
+                if (noTrackedGCSlots)
                 {
-                    numCallSites++;
+                    // We have the call count from the previous run.
+                    numCallSites = *callCntRef;
+
+                    // If there are no calls, tell the world and bail.
+                    if (numCallSites == 0)
+                    {
+                        gcInfoEncoderWithLog->DefineCallSites(nullptr, nullptr, 0);
+                        return;
+                    }
+                }
+                else
+                {
+                    for (CallDsc* call = gcCallDescList; call != nullptr; call = call->cdNext)
+                    {
+                        numCallSites++;
+                    }
                 }
                 pCallSites     = new (compiler, CMK_GC) unsigned[numCallSites];
                 pCallSiteSizes = new (compiler, CMK_GC) BYTE[numCallSites];
@@ -4216,17 +4244,8 @@ void GCInfo::gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder,
         // Now consider every call.
         for (CallDsc* call = gcCallDescList; call != nullptr; call = call->cdNext)
         {
-            if (mode == MAKE_REG_PTR_MODE_DO_WORK)
-            {
-                pCallSites[callSiteNum]     = call->cdOffs - call->cdCallInstrSize;
-                pCallSiteSizes[callSiteNum] = call->cdCallInstrSize;
-                callSiteNum++;
-            }
-
-            unsigned nextOffset;
-
             // Figure out the code offset of this entry.
-            nextOffset = call->cdOffs;
+            unsigned nextOffset = call->cdOffs;
 
             // As far as I (DLD, 2010) can determine by asking around, the "call->u1.cdArgMask"
             // and "cdArgCnt" cases are to handle x86 situations in which a call expression is nested as an
@@ -4251,13 +4270,36 @@ void GCInfo::gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder,
             assert(call->cdOffs >= call->cdCallInstrSize);
             // call->cdOffs is actually the offset of the instruction *following* the call, so subtract
             // the call instruction size to get the offset of the actual call instruction...
-            unsigned callOffset = call->cdOffs - call->cdCallInstrSize;
-            // Record that these registers are live before the call...
-            gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, callOffset, regMask, GC_SLOT_LIVE, byrefRegMask, nullptr);
-            // ...and dead after.
-            gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, call->cdOffs, regMask, GC_SLOT_DEAD, byrefRegMask,
-                                         nullptr);
+            unsigned callOffset = nextOffset - call->cdCallInstrSize;
+
+            if (noTrackedGCSlots && regMask == 0)
+            {
+                // No live GC refs in regs at the call -> don't record the call.
+            }
+            else
+            {
+                // Append an entry for the call if doing the real thing.
+                if (mode == MAKE_REG_PTR_MODE_DO_WORK)
+                {
+                    pCallSites[callSiteNum]     = callOffset;
+                    pCallSiteSizes[callSiteNum] = call->cdCallInstrSize;
+                }
+                callSiteNum++;
+
+                // Record that these registers are live before the call...
+                gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, callOffset, regMask, GC_SLOT_LIVE, byrefRegMask,
+                                             nullptr);
+                // ...and dead after.
+                gcInfoRecordGCRegStateChange(gcInfoEncoder, mode, nextOffset, regMask, GC_SLOT_DEAD, byrefRegMask,
+                                             nullptr);
+            }
         }
+
+        // Make sure we've recorded the expected number of calls
+        assert(mode != MAKE_REG_PTR_MODE_DO_WORK || numCallSites == callSiteNum);
+        // Return the actual recorded call count to the caller
+        *callCntRef = callSiteNum;
+
         // OK, define the call sites.
         if (mode == MAKE_REG_PTR_MODE_DO_WORK)
         {
index a3e30de..264b661 100644 (file)
@@ -214,6 +214,15 @@ CONFIG_INTEGER(JitEnableNoWayAssert, W("JitEnableNoWayAssert"), 0)
 CONFIG_INTEGER(JitEnableNoWayAssert, W("JitEnableNoWayAssert"), 1)
 #endif // !defined(DEBUG) && !defined(_DEBUG)
 
+#if !defined(JIT32_GCENCODER)
+#if defined(_TARGET_AMD64_) && defined(FEATURE_CORECLR)
+#define JitMinOptsTrackGCrefs_Default 0 // Not tracking GC refs in MinOpts is new behavior
+#else
+#define JitMinOptsTrackGCrefs_Default 1
+#endif
+CONFIG_INTEGER(JitMinOptsTrackGCrefs, W("JitMinOptsTrackGCrefs"), JitMinOptsTrackGCrefs_Default) // Track GC roots
+#endif // !defined(JIT32_GCENCODER)
+
 // The following should be wrapped inside "#if MEASURE_MEM_ALLOC / #endif", but
 // some files include this one without bringing in the definitions from "jit.h"
 // so we don't always know what the "true" value of that flag should be. For now
index 3f8d8af..7b17b84 100644 (file)
@@ -295,7 +295,11 @@ public:
     // references, building up mappings from tuples of <reg/offset X byref/pinning> to the corresponding
     // slot id (in the two member fields declared above).  In the "do work" mode, we use these slot ids to
     // actually declare live ranges to the encoder.
-    void gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder, unsigned codeSize, unsigned prologSize, MakeRegPtrMode mode);
+    void gcMakeRegPtrTable(GcInfoEncoder* gcInfoEncoder,
+                           unsigned       codeSize,
+                           unsigned       prologSize,
+                           MakeRegPtrMode mode,
+                           unsigned*      callCntRef);
 #endif
 
 #ifdef JIT32_GCENCODER
index fa1ba82..f0d6903 100644 (file)
@@ -2965,6 +2965,10 @@ void Compiler::lvaSortByRefCount()
             lvaSetVarDoNotEnregister(lclNum DEBUGARG(DNER_PinningRef));
 #endif
         }
+        else if (opts.MinOpts() && !JitConfig.JitMinOptsTrackGCrefs() && varTypeIsGC(varDsc->TypeGet()))
+        {
+            varDsc->lvTracked = 0;
+        }
 
         //  Are we not optimizing and we have exception handlers?
         //   if so mark all args and locals "do not enregister".
index 89f4704..5dd52b9 100644 (file)
@@ -628,6 +628,8 @@ bool GcInfoDecoder::EnumerateLiveSlots(
 
 
 #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED
+    bool noTrackedRefs = false;
+
     if(m_SafePointIndex < m_NumSafePoints && !executionAborted)
     {
         // Skip interruptibility information
@@ -648,33 +650,40 @@ bool GcInfoDecoder::EnumerateLiveSlots(
         //
         if(!executionAborted)
         {
-            _ASSERTE(m_NumInterruptibleRanges);
+            if(m_NumInterruptibleRanges == 0)
+            {
+                // No ranges and no explicit safepoint - must be MinOpts with untracked refs.
+                noTrackedRefs = true;
+            }
         }
 
-        int countIntersections = 0;
-        UINT32 lastNormStop = 0;
-        for(UINT32 i=0; i<m_NumInterruptibleRanges; i++)
+        if(m_NumInterruptibleRanges != 0)
         {
-            UINT32 normStartDelta = (UINT32) m_Reader.DecodeVarLengthUnsigned( INTERRUPTIBLE_RANGE_DELTA1_ENCBASE );
-            UINT32 normStopDelta = (UINT32) m_Reader.DecodeVarLengthUnsigned( INTERRUPTIBLE_RANGE_DELTA2_ENCBASE ) + 1;
+            int countIntersections = 0;
+            UINT32 lastNormStop = 0;
+            for(UINT32 i=0; i<m_NumInterruptibleRanges; i++)
+            {
+                UINT32 normStartDelta = (UINT32) m_Reader.DecodeVarLengthUnsigned( INTERRUPTIBLE_RANGE_DELTA1_ENCBASE );
+                UINT32 normStopDelta = (UINT32) m_Reader.DecodeVarLengthUnsigned( INTERRUPTIBLE_RANGE_DELTA2_ENCBASE ) + 1;
 
-            UINT32 normStart = lastNormStop + normStartDelta;
-            UINT32 normStop = normStart + normStopDelta;
-            if(normBreakOffset >= normStart && normBreakOffset < normStop)
+                UINT32 normStart = lastNormStop + normStartDelta;
+                UINT32 normStop = normStart + normStopDelta;
+                if(normBreakOffset >= normStart && normBreakOffset < normStop)
+                {
+                    _ASSERTE(pseudoBreakOffset == 0);
+                    countIntersections++;
+                    pseudoBreakOffset = numInterruptibleLength + normBreakOffset - normStart;
+                }
+                numInterruptibleLength += normStopDelta;
+                lastNormStop = normStop;
+            }        
+            _ASSERTE(countIntersections <= 1);
+            if(countIntersections == 0)
             {
-                _ASSERTE(pseudoBreakOffset == 0);
-                countIntersections++;
-                pseudoBreakOffset = numInterruptibleLength + normBreakOffset - normStart;
+                _ASSERTE(executionAborted);
+                LOG((LF_GCROOTS, LL_INFO100000, "Not reporting this frame because it is aborted and not fully interruptible.\n"));
+                goto ExitSuccess;
             }
-            numInterruptibleLength += normStopDelta;
-            lastNormStop = normStop;
-        }        
-        _ASSERTE(countIntersections <= 1);
-        if(countIntersections == 0)
-        {
-            _ASSERTE(executionAborted);
-            LOG((LF_GCROOTS, LL_INFO100000, "Not reporting this frame because it is aborted and not fully interruptible.\n"));
-            goto ExitSuccess;
         }
     }        
 #else   // !PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED
@@ -718,7 +727,7 @@ bool GcInfoDecoder::EnumerateLiveSlots(
 
         if(executionAborted)
         {
-            _ASSERTE(m_NumSafePoints == 0);
+            // Skip over safepoint info (if any is present).
             m_Reader.Skip(m_NumSafePoints * numSlots);
         }
         else if( m_SafePointIndex != m_NumSafePoints )
@@ -787,6 +796,8 @@ bool GcInfoDecoder::EnumerateLiveSlots(
         else
         {
             m_Reader.Skip(m_NumSafePoints * numSlots);
+            if(noTrackedRefs)
+                goto ReportUntracked;
         }
 #endif // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED
         
index df3d9d2..3174f89 100644 (file)
@@ -47,16 +47,23 @@ public class Test
     }
 
 
+    public static void RunTest2()
+    {
+        Dummy2 obj2 = new Dummy2();
+        obj2 = null;
+    }
+
+
     public static void RunTest()
     {
         Dummy obj = new Dummy();
-        Dummy2 obj2 = new Dummy2();
+
+        RunTest2();
 
         // *uncomment the for loop to make test fail with complus_jitminops set
         // by design as per briansul
 
         //for (int i=0; i<5; i++) {
-        obj2 = null;
         GC.Collect();
         GC.WaitForPendingFinalizers();
         //}
index 9235256..8ec6531 100644 (file)
@@ -62,6 +62,10 @@ namespace LGen {
                 MakeLeak(iObj);
             }
 
+            GC.Collect();
+            GC.WaitForPendingFinalizers();
+            GC.Collect();
+
             Console.WriteLine("~LeakObject() was called {0} times.", LeakObject.icFinal);
             return (LeakObject.icFinal == iObj*iRep);
         }
@@ -80,12 +84,6 @@ namespace LGen {
                 mem[mem.Length-1] = 1;
             }
 
-            Mv_Obj = null;
-
-            GC.Collect();
-            GC.WaitForPendingFinalizers();
-            GC.Collect();
-
         }
 
 
index a6c1cfe..7ed11bc 100644 (file)
@@ -273,16 +273,21 @@ class Application {
     static public ResurrectObj ResObjHolder;    // Defaults to null
 
 
-    // This method demonstrates how the GC supports resurrection.
+    // These methods demonstrate how the GC supports resurrection.
     // NOTE: Resurrection is discouraged.
-    private static void ResurrectionDemo() {
-        Display(0, "\n\nDemo start: Object Resurrection.", +1);
-
+    private static void ResurrectionInit() {
         // Create a ResurrectionObj
         ResurrectObj obj = new ResurrectObj("Resurrection");
 
         // Destroy all strong references to the new ResurrectionObj
         obj = null;
+    }
+
+    private static void ResurrectionDemo() {
+        Display(0, "\n\nDemo start: Object Resurrection.", +1);
+
+        // Create a ResurrectionObj and drop it on the floor.
+        ResurrectionInit();
 
         // Force the GC to determine that the object is unreachable.
         Collect();
index a356cec..754e88d 100644 (file)
@@ -22,6 +22,11 @@ namespace DefaultNamespace {
         public bool RunTest(int iObj,int iSwitch)
         {
             DeleteObj(iObj,iSwitch);
+
+            GC.Collect();
+            GC.WaitForPendingFinalizers();
+            GC.Collect();
+
             bool result = CheckResult(iObj,iSwitch);
             return result;
         }
@@ -48,11 +53,6 @@ namespace DefaultNamespace {
             {
                 rgNode[i] = null;
             }
-
-            GC.Collect();
-            GC.WaitForPendingFinalizers();
-            GC.Collect();
-
         }
 
         public bool CheckResult(int iObj,int iSwitch)
index 7956cc6..e021a59 100644 (file)
@@ -45,15 +45,20 @@ namespace GCTest
             return 100;
         }
         [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
-        private static void Test1()
+        private static void CollectAndFinalize()
         {
-            for (int i = 0; i < 50; i++)
-                s_arr[i] = new Test(i);
             GC.Collect();
             GC.WaitForPendingFinalizers();
             GC.Collect();
         }
         [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
+        private static void Test1()
+        {
+            for (int i = 0; i < 50; i++)
+                s_arr[i] = new Test(i);
+            CollectAndFinalize();
+        }
+        [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
         private static void Test2()
         {
             for (int i = 0; i < 50; i++)
@@ -62,52 +67,44 @@ namespace GCTest
                 s_arr[i].CheckValid();
                 s_arr[i] = null;
             }
-            GC.Collect();
-            GC.WaitForPendingFinalizers();
-            GC.Collect();
         }
         [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
         private static void Test3()
         {
+            CollectAndFinalize();
             for (int i = 0; i < 50; i++)
             {
                 if (s_arr[i] == null) throw new Exception();
                 s_arr[i].CheckValid();
                 s_arr[i] = null;
             }
-            GC.Collect();
-            GC.WaitForPendingFinalizers();
-            GC.Collect();
         }
         [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
         private static void Test4()
         {
+            CollectAndFinalize();
             for (int i = 0; i < 50; i++)
             {
                 if (s_arr[i] == null) throw new Exception();
                 s_arr[i].CheckValid();
                 s_arr[i] = null;
             }
-            GC.Collect();
-            GC.WaitForPendingFinalizers();
-            GC.Collect();
         }
         [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
         private static void Test5()
         {
+            CollectAndFinalize();
             for (int i = 0; i < 50; i++)
             {
                 if (s_arr[i] == null) throw new Exception();
                 s_arr[i].CheckValid();
                 s_arr[i] = null;
             }
-            GC.Collect();
-            GC.WaitForPendingFinalizers();
-            GC.Collect();
         }
         [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
         private static void Test6()
         {
+            CollectAndFinalize();
             for (int i = 0; i < 50; i++)
             {
                 if (s_arr[i] == null) throw new Exception();