Disable object stack allocation verification under GCStress.
authorEugene Rozenfeld <erozen@microsoft.com>
Mon, 19 Nov 2018 21:25:56 +0000 (13:25 -0800)
committerEugene Rozenfeld <erozen@microsoft.com>
Wed, 21 Nov 2018 19:11:50 +0000 (11:11 -0800)
ObjectStackAllocationTests use GC.GetAllocatedBytesForCurrentThread to
verify object stack allocations. Under GCStress the vm may initiate additional
heap allocations in GCHeap::StressHeap (see the call to 'pGenGCHeap->allocate' below).

This change re-enables ObjectStackAllocationTests and updates then to not verify stack allocations under GCStress.
It's useful to run the tests even without the verification to catch crashes, gc asserts, etc.

```
if (Interlocked::Increment(&OneAtATime) == 0 &&
        !TrackAllocations()) // Messing with object sizes can confuse the profiler (see ICorProfilerInfo::GetObjectSize)
    {
        StringObject* str;

        // If the current string is used up
        if (HndFetchHandle(m_StressObjs[m_CurStressObj]) == 0)
        {
            // Populate handles with strings
            int i = m_CurStressObj;
            while(HndFetchHandle(m_StressObjs[i]) == 0)
            {
                _ASSERTE(m_StressObjs[i] != 0);
                unsigned strLen = (LARGE_OBJECT_SIZE - 32) / sizeof(WCHAR);
                unsigned strSize = PtrAlign(StringObject::GetSize(strLen));

                // update the cached type handle before allocating
                SetTypeHandleOnThreadForAlloc(TypeHandle(g_pStringClass));
                str = (StringObject*) pGenGCHeap->allocate (strSize, acontext);
                if (str)
                {
                    str->SetMethodTable (g_pStringClass);
                    str->SetStringLength (strLen);

                    HndAssignHandle(m_StressObjs[i], ObjectToOBJECTREF(str));
                }
                i = (i + 1) % NUM_HEAP_STRESS_OBJS;
                if (i == m_CurStressObj) break;
            }

            // advance the current handle to the next string
            m_CurStressObj = (m_CurStressObj + 1) % NUM_HEAP_STRESS_OBJS;
        }

        // Get the current string
        str = (StringObject*) OBJECTREFToObject(HndFetchHandle(m_StressObjs[m_CurStressObj]));
        if (str)
        {
            // Chop off the end of the string and form a new object out of it.
            // This will 'free' an object at the begining of the heap, which will
            // force data movement.  Note that we can only do this so many times.
            // before we have to move on to the next string.
            unsigned sizeOfNewObj = (unsigned)Align(min_obj_size * 31);
            if (str->GetStringLength() > sizeOfNewObj / sizeof(WCHAR))
            {
                unsigned sizeToNextObj = (unsigned)Align(size(str));
                uint8_t* freeObj = ((uint8_t*) str) + sizeToNextObj - sizeOfNewObj;
                pGenGCHeap->make_unused_array (freeObj, sizeOfNewObj);
                str->SetStringLength(str->GetStringLength() - (sizeOfNewObj / sizeof(WCHAR)));
            }
            else
            {
                // Let the string itself become garbage.
                // will be realloced next time around
                HndAssignHandle(m_StressObjs[m_CurStressObj], 0);
            }
        }
    }
    Interlocked::Decrement(&OneAtATime);
```

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

src/coreclr/tests/src/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.cs
src/coreclr/tests/src/JIT/opt/ObjectStackAllocation/ObjectStackAllocationTests.csproj

index b016368..fc4fc15 100644 (file)
@@ -69,6 +69,13 @@ namespace ObjectStackAllocation
         public SimpleStruct s;
     }
 
+    enum AllocationKind
+    {
+        Heap,
+        Stack,
+        Undefined
+    }
+
     class Tests
     {
         static volatile int f1 = 5;
@@ -80,32 +87,43 @@ namespace ObjectStackAllocation
 
         public static int Main()
         {
-            bool spcOptimizationEnabled = SPCOptimizationsEnabled();
+            AllocationKind expectedAllocationKind = AllocationKind.Stack;
+            if (GCStressEnabled()) {
+                expectedAllocationKind = AllocationKind.Undefined;
+            }
+            else if (!SPCOptimizationsEnabled()) {
+                expectedAllocationKind = AllocationKind.Heap;
+            }
 
-            CallTestAndVerifyAllocation(AllocateSimpleClassAndAddFields, 12, !spcOptimizationEnabled);
+            CallTestAndVerifyAllocation(AllocateSimpleClassAndAddFields, 12, expectedAllocationKind);
 
-            CallTestAndVerifyAllocation(AllocateSimpleClassesAndEQCompareThem, 0, !spcOptimizationEnabled);
+            CallTestAndVerifyAllocation(AllocateSimpleClassesAndEQCompareThem, 0, expectedAllocationKind);
 
-            CallTestAndVerifyAllocation(AllocateSimpleClassesAndNECompareThem, 1, !spcOptimizationEnabled);
+            CallTestAndVerifyAllocation(AllocateSimpleClassesAndNECompareThem, 1, expectedAllocationKind);
 
-            CallTestAndVerifyAllocation(AllocateSimpleClassAndCheckType, 1, !spcOptimizationEnabled);
+            CallTestAndVerifyAllocation(AllocateSimpleClassAndCheckType, 1, expectedAllocationKind);
 
-            CallTestAndVerifyAllocation(AllocateSimpleClassAndCast, 7, !spcOptimizationEnabled);
+            CallTestAndVerifyAllocation(AllocateSimpleClassAndCast, 7, expectedAllocationKind);
 
-            CallTestAndVerifyAllocation(AllocateSimpleClassAndGetField, 7, !spcOptimizationEnabled);
+            CallTestAndVerifyAllocation(AllocateSimpleClassAndGetField, 7, expectedAllocationKind);
 
-            CallTestAndVerifyAllocation(AllocateClassWithNestedStructAndGetField, 5, !spcOptimizationEnabled);
+            CallTestAndVerifyAllocation(AllocateClassWithNestedStructAndGetField, 5, expectedAllocationKind);
 
-            CallTestAndVerifyAllocation(AllocateClassWithNestedStructAndAddFields, 24, !spcOptimizationEnabled);
+            CallTestAndVerifyAllocation(AllocateClassWithNestedStructAndAddFields, 24, expectedAllocationKind);
+
+            // The remaining tests currently never allocate on the stack
+            if (expectedAllocationKind == AllocationKind.Stack) {
+                expectedAllocationKind = AllocationKind.Heap;
+            }
 
             // Stack allocation of classes with GC fields is currently disabled
-            CallTestAndVerifyAllocation(AllocateSimpleClassWithGCFieldAndAddFields, 12, true);
+            CallTestAndVerifyAllocation(AllocateSimpleClassWithGCFieldAndAddFields, 12, expectedAllocationKind);
 
             // Assigning class ref to a field of another object currently always disables stack allocation
-            CallTestAndVerifyAllocation(AllocateSimpleClassAndAssignRefToAField, 12, true);
+            CallTestAndVerifyAllocation(AllocateSimpleClassAndAssignRefToAField, 12, expectedAllocationKind);
 
             // Stack allocation of boxed structs is currently disabled
-            CallTestAndVerifyAllocation(BoxSimpleStructAndAddFields, 12, true);
+            CallTestAndVerifyAllocation(BoxSimpleStructAndAddFields, 12, expectedAllocationKind);
 
             return methodResult;
         }
@@ -119,8 +137,15 @@ namespace ObjectStackAllocation
             return ((debuggableAttribute == null) || !debuggableAttribute.IsJITOptimizerDisabled);
         }
 
-        static void CallTestAndVerifyAllocation(Test test, int expectedResult, bool expectHeapAllocations)
+        static bool GCStressEnabled()
+        {
+            return Environment.GetEnvironmentVariable("COMPlus_GCStress") != null;
+        }
+
+        static void CallTestAndVerifyAllocation(Test test, int expectedResult, AllocationKind expectedAllocationsKind)
         {
+            // Run the test once to exclude any allocations during jitting, etc.
+            //test();
             long allocatedBytesBefore = GC.GetAllocatedBytesForCurrentThread();
             int testResult = test();
             long allocatedBytesAfter = GC.GetAllocatedBytesForCurrentThread();
@@ -130,11 +155,11 @@ namespace ObjectStackAllocation
                 Console.WriteLine($"FAILURE ({methodName}): expected {expectedResult}, got {testResult}");
                 methodResult = -1;
             }
-            else if (!expectHeapAllocations && (allocatedBytesBefore != allocatedBytesAfter)) {
+            else if ((expectedAllocationsKind == AllocationKind.Stack) && (allocatedBytesBefore != allocatedBytesAfter)) {
                 Console.WriteLine($"FAILURE ({methodName}): unexpected allocation of {allocatedBytesAfter - allocatedBytesBefore} bytes");
                 methodResult = -1;
             }
-            else if (expectHeapAllocations && (allocatedBytesBefore == allocatedBytesAfter)) {
+            else if ((expectedAllocationsKind == AllocationKind.Heap) && (allocatedBytesBefore == allocatedBytesAfter)) {
                 Console.WriteLine($"FAILURE ({methodName}): unexpected stack allocation");
                 methodResult = -1;
             }
index 5fd0c6f..b6efc07 100644 (file)
@@ -10,8 +10,6 @@
     <OutputType>Exe</OutputType>
     <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
     <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
-    <!-- Issue 21057, https://github.com/dotnet/coreclr/issues/21057 -->
-    <GCStressIncompatible>true</GCStressIncompatible>
   </PropertyGroup>
   <!-- Default configurations to help VS understand the configurations -->
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>