diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 8857f6528f90b..0426326915714 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -45804,8 +45804,16 @@ unsigned int GCHeap::WhichGeneration (Object* object) #ifdef FEATURE_BASICFREEZE if (!((o < g_gc_highest_address) && (o >= g_gc_lowest_address))) { - return max_generation; + return INT32_MAX; + } +#ifndef USE_REGIONS + if (GCHeap::IsInFrozenSegment (object)) + { + // in case if the object belongs to an in-range frozen segment + // For regions those are never in-range. + return INT32_MAX; } +#endif #endif //FEATURE_BASICFREEZE gc_heap* hp = gc_heap::heap_of (o); unsigned int g = hp->object_gennum (o); @@ -48657,6 +48665,9 @@ CFinalize::UpdatePromotedGenerations (int gen, BOOL gen_0_empty_p) int new_gen = g_theGCHeap->WhichGeneration (*po); if (new_gen != i) { + // We never promote objects to a non-GC heap + assert (new_gen <= max_generation); + dprintf (3, ("Moving object %p->%p from gen %d to gen %d", po, *po, i, new_gen)); if (new_gen > i) diff --git a/src/coreclr/gc/gcimpl.h b/src/coreclr/gc/gcimpl.h index b82f17a78f959..4a4c92cb50ede 100644 --- a/src/coreclr/gc/gcimpl.h +++ b/src/coreclr/gc/gcimpl.h @@ -150,7 +150,8 @@ class GCHeap : public IGCHeapInternal //Unregister an object for finalization void SetFinalizationRun (Object* obj); - //returns the generation number of an object (not valid during relocation) + // returns the generation number of an object (not valid during relocation) or + // INT32_MAX if the object belongs to a non-GC heap. unsigned WhichGeneration (Object* object); // returns TRUE is the object is ephemeral bool IsEphemeral (Object* object); diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index 86fa8470fbcb9..a6a3177acb090 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -722,6 +722,7 @@ class IGCHeap { // Returns the generation in which obj is found. Also used by the VM // in some places, in particular syncblk code. + // Returns INT32_MAX if obj belongs to a non-GC heap. virtual unsigned WhichGeneration(Object* obj) PURE_VIRTUAL // Returns the number of GCs that have transpired in the given generation @@ -991,7 +992,7 @@ void updateGCShadow(Object** ptr, Object* val); #define GC_CALL_INTERIOR 0x1 #define GC_CALL_PINNED 0x2 -// keep in sync with GC_ALLOC_FLAGS in GC.cs +// keep in sync with GC_ALLOC_FLAGS in GC.CoreCLR.cs enum GC_ALLOC_FLAGS { GC_ALLOC_NO_FLAGS = 0, diff --git a/src/coreclr/inc/corerror.xml b/src/coreclr/inc/corerror.xml index f9f604c28ae32..afeaf4f602a4a 100644 --- a/src/coreclr/inc/corerror.xml +++ b/src/coreclr/inc/corerror.xml @@ -198,8 +198,8 @@ COR_E_LOADING_REFERENCE_ASSEMBLY - "Reference assemblies cannot not be loaded for execution." - Reference assemblies cannot not be loaded for execution. + "Reference assemblies cannot be loaded for execution." + Reference assemblies cannot be loaded for execution @@ -1182,6 +1182,12 @@ The runtime cannot be suspened since a suspension is already in progress. + + CORPROF_E_NOT_GC_OBJECT + "This object belongs to a non-gc heap." + This object belongs to a non-gc heap + + CORSEC_E_POLICY_EXCEPTION "PolicyException thrown." @@ -1788,8 +1794,8 @@ CORDBG_E_NGEN_NOT_SUPPORTED - "NGEN must be supported to perform the requested operation." - NGEN must be supported to perform the requested operation + "NGEN is not supported." + NGEN is not supported diff --git a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc index 6f808d6f8f84d..a30a45ef68019 100644 --- a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc +++ b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc @@ -147,6 +147,7 @@ BEGIN MSG_FOR_URT_HR(CORPROF_E_NOT_YET_AVAILABLE) "Requested information is not yet available." MSG_FOR_URT_HR(CORPROF_E_TYPE_IS_PARAMETERIZED) "The given type is a generic and cannot be used with this method." MSG_FOR_URT_HR(CORPROF_E_FUNCTION_IS_PARAMETERIZED) "The given function is a generic and cannot be used with this method." + MSG_FOR_URT_HR(CORPROF_E_NOT_GC_OBJECT) "This object belongs to a non-gc heap." MSG_FOR_URT_HR(CORSEC_E_POLICY_EXCEPTION) "PolicyException thrown." MSG_FOR_URT_HR(CORSEC_E_MIN_GRANT_FAIL) "Failed to grant minimum permission requests." MSG_FOR_URT_HR(CORSEC_E_NO_EXEC_PERM) "Failed to grant permission to execute." @@ -288,6 +289,7 @@ BEGIN MSG_FOR_URT_HR(CORDBG_E_MISSING_DEBUGGER_EXPORTS) "The debuggee memory space does not have the expected debugging export table." MSG_FOR_URT_HR(CORDBG_E_DATA_TARGET_ERROR) "Failure when calling a data target method." MSG_FOR_URT_HR(CORDBG_E_UNSUPPORTED_DELEGATE) "The delegate contains a delegate currently not supported by the API." + MSG_FOR_URT_HR(CORDBG_E_ASSEMBLY_UPDATES_APPLIED) "The operation is not supported because assembly updates have been applied." MSG_FOR_URT_HR(PEFMT_E_64BIT) "File is PE32+." MSG_FOR_URT_HR(PEFMT_E_32BIT) "File is PE32" MSG_FOR_URT_HR(CLR_E_BIND_ASSEMBLY_VERSION_TOO_LOW) "The bound assembly has a version that is lower than that of the request." diff --git a/src/coreclr/pal/prebuilt/inc/corerror.h b/src/coreclr/pal/prebuilt/inc/corerror.h index 6f0be602583ee..8b64c945ac9bc 100644 --- a/src/coreclr/pal/prebuilt/inc/corerror.h +++ b/src/coreclr/pal/prebuilt/inc/corerror.h @@ -214,6 +214,7 @@ #define CORDIAGIPC_E_UNKNOWN_MAGIC EMAKEHR(0x1386) #define CORDIAGIPC_E_UNKNOWN_ERROR EMAKEHR(0x1387) #define CORPROF_E_SUSPENSION_IN_PROGRESS EMAKEHR(0x1388) +#define CORPROF_E_NOT_GC_OBJECT EMAKEHR(0x1389) #define CORSEC_E_POLICY_EXCEPTION EMAKEHR(0x1416) #define CORSEC_E_MIN_GRANT_FAIL EMAKEHR(0x1417) #define CORSEC_E_NO_EXEC_PERM EMAKEHR(0x1418) diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp index 1d78588b328a5..0e9e39a91fd18 100644 --- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp @@ -9141,6 +9141,15 @@ HRESULT ProfToEEInterfaceImpl::GetObjectGeneration(ObjectID objectId, IGCHeap *hp = GCHeapUtilities::GetGCHeap(); + if (hp->IsInFrozenSegment((Object*)objectId)) + { + range->generation = (COR_PRF_GC_GENERATION)INT32_MAX; + range->rangeStart = 0; + range->rangeLength = 0; + range->rangeLengthReserved = 0; + return CORPROF_E_NOT_GC_OBJECT; + } + uint8_t* pStart; uint8_t* pAllocated; uint8_t* pReserved; diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 2dd6b62159972..76b133b0d1248 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -5533,6 +5533,7 @@ void ThreadStore::TriggerGCForDeadThreadsIfNecessary() } unsigned exposedObjectGeneration = gcHeap->WhichGeneration(exposedObject); + _ASSERTE(exposedObjectGeneration != INT32_MAX); SIZE_T newDeadThreadGenerationCount = ++s_DeadThreadGenerationCounts[exposedObjectGeneration]; if (exposedObjectGeneration > gcGenerationToTrigger && newDeadThreadGenerationCount >= generationCountThreshold) { diff --git a/src/libraries/System.Runtime/tests/System/GCTests.cs b/src/libraries/System.Runtime/tests/System/GCTests.cs index d6d7488c1fc09..e59aea9ccdd1f 100644 --- a/src/libraries/System.Runtime/tests/System/GCTests.cs +++ b/src/libraries/System.Runtime/tests/System/GCTests.cs @@ -35,6 +35,9 @@ public static void Collect_Int() { GC.Collect(i); } + // Also, expect GC.Collect(int.MaxValue) to work without exception since int.MaxValue represents + // a nongc heap generation (that is exactly what GC.GetGeneration returns for a non-gc heap object) + GC.Collect(int.MaxValue); } [Fact] diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs index e87d6f44f4673..6f16508f216e0 100644 --- a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs +++ b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs @@ -986,9 +986,9 @@ public static void Run() } { - // Expecting this to be a frozen array, and reported as Gen2 by the GC + // Expecting this to be a frozen array, and reported as int.MaxValue by the GC object val = AccessArray(); - Assert.AreEqual(2, GC.GetGeneration(val)); + Assert.AreEqual(int.MaxValue, GC.GetGeneration(val)); val = typeof(ClassWithTemplate<>).MakeGenericType(typeof(C4)).GetField("Array").GetValue(null); Assert.AreEqual(0, GC.GetGeneration(val)); diff --git a/src/tests/profiler/gc/nongcheap.cs b/src/tests/profiler/gc/nongcheap.cs new file mode 100644 index 0000000000000..e81925a76e9e7 --- /dev/null +++ b/src/tests/profiler/gc/nongcheap.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace Profiler.Tests +{ + class NonGCHeapTests + { + static readonly Guid GcAllocateEventsProfilerGuid = new Guid("EF0D191C-3FC7-4311-88AF-E474CBEB2859"); + + [MethodImpl(MethodImplOptions.NoInlining)] + static void AllocateNonGcHeapObjects() + { + // When this method is invoked, JIT is expected to trigger allocations for these + // string literals and they're expected to end up in a nongc segment (also known as frozen) + Consume("string1"); + Consume("string2"); + Consume("string3"); + Consume("string4"); + Consume("string5"); + Consume("string6"); + + int gen = GC.GetGeneration("string7"); + if (gen != int.MaxValue) + throw new Exception("object is expected to be in a non-gc heap for this test to work"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void Consume(object o) {} + + public static int RunTest(String[] args) + { + AllocateNonGcHeapObjects(); + Console.WriteLine("Test Passed"); + return 100; + } + + public static int Main(string[] args) + { + if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase)) + { + return RunTest(args); + } + + return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location, + testName: "NonGCHeapAllocate", + profilerClsid: GcAllocateEventsProfilerGuid); + } + } +} diff --git a/src/tests/profiler/gc/nongcheap.csproj b/src/tests/profiler/gc/nongcheap.csproj new file mode 100644 index 0000000000000..d51dcb692abfe --- /dev/null +++ b/src/tests/profiler/gc/nongcheap.csproj @@ -0,0 +1,21 @@ + + + .NETCoreApp + exe + true + true + + true + + true + + + + + + + + diff --git a/src/tests/profiler/native/CMakeLists.txt b/src/tests/profiler/native/CMakeLists.txt index a3d2d77902f38..feb446e8846df 100644 --- a/src/tests/profiler/native/CMakeLists.txt +++ b/src/tests/profiler/native/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES eventpipeprofiler/eventpipewritingprofiler.cpp eventpipeprofiler/eventpipemetadatareader.cpp gcallocateprofiler/gcallocateprofiler.cpp + nongcheap/nongcheap.cpp gcbasicprofiler/gcbasicprofiler.cpp gcprofiler/gcprofiler.cpp getappdomainstaticaddress/getappdomainstaticaddress.cpp diff --git a/src/tests/profiler/native/classfactory.cpp b/src/tests/profiler/native/classfactory.cpp index 418b0b680830b..7d8aa7942e5d9 100644 --- a/src/tests/profiler/native/classfactory.cpp +++ b/src/tests/profiler/native/classfactory.cpp @@ -7,6 +7,7 @@ #include "eventpipeprofiler/eventpipewritingprofiler.h" #include "getappdomainstaticaddress/getappdomainstaticaddress.h" #include "gcallocateprofiler/gcallocateprofiler.h" +#include "nongcheap/nongcheap.h" #include "gcbasicprofiler/gcbasicprofiler.h" #include "gcprofiler/gcprofiler.h" #include "handlesprofiler/handlesprofiler.h" @@ -69,6 +70,10 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI { profiler = new GCAllocateProfiler(); } + else if (clsid == NonGcHeapProfiler::GetClsid()) + { + profiler = new NonGcHeapProfiler(); + } else if (clsid == GCBasicProfiler::GetClsid()) { profiler = new GCBasicProfiler(); diff --git a/src/tests/profiler/native/nongcheap/nongcheap.cpp b/src/tests/profiler/native/nongcheap/nongcheap.cpp new file mode 100644 index 0000000000000..7689f9dda5f4b --- /dev/null +++ b/src/tests/profiler/native/nongcheap/nongcheap.cpp @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "nongcheap.h" + +GUID NonGcHeapProfiler::GetClsid() +{ + // {EF0D191C-3FC7-4311-88AF-E474CBEB2859} + GUID clsid = { 0xef0d191c, 0x3fc7, 0x4311, { 0x88, 0xaf, 0xe4, 0x74, 0xcb, 0xeb, 0x28, 0x59 } }; + return clsid; +} + +HRESULT NonGcHeapProfiler::Initialize(IUnknown* pICorProfilerInfoUnk) +{ + Profiler::Initialize(pICorProfilerInfoUnk); + + HRESULT hr = S_OK; + if (FAILED(hr = pCorProfilerInfo->SetEventMask2( + COR_PRF_ENABLE_OBJECT_ALLOCATED | COR_PRF_MONITOR_OBJECT_ALLOCATED, + COR_PRF_HIGH_BASIC_GC))) + { + printf("FAIL: ICorProfilerInfo::SetEventMask2() failed hr=0x%x", hr); + return hr; + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE NonGcHeapProfiler::ObjectAllocated(ObjectID objectId, ClassID classId) +{ + COR_PRF_GC_GENERATION_RANGE gen; + HRESULT hr = pCorProfilerInfo->GetObjectGeneration(objectId, &gen); + + // non-GC objects (same for GC.GetGeneration() API) have generation = -1 + if (gen.generation == (COR_PRF_GC_GENERATION)INT32_MAX) + { + if (!FAILED(hr)) + { + // We expect GetObjectGeneration to return an error (CORPROF_E_NOT_GC_OBJECT) + // for non-GC objects. + _failures++; + } + _nonGcHeapObjects++; + if (gen.rangeLength != 0 || gen.rangeLengthReserved != 0 || gen.rangeStart != 0) + { + _failures++; + } + } + else if (FAILED(hr)) + { + _failures++; + } + return S_OK; +} + +HRESULT NonGcHeapProfiler::Shutdown() +{ + if (_failures > 0) + { + printf("PROFILER TEST FAILS\n"); + } + else if (_nonGcHeapObjects == 0) + { + printf("PROFILER TEST FAILS: non-GC heap objects were not allocated\n"); + } + else + { + printf("PROFILER TEST PASSES\n"); + } + printf("Non-GC objects allocated: %d\n", (int)_nonGcHeapObjects); + printf("PROFILER TEST PASSES\n"); + fflush(stdout); + return S_OK; +} diff --git a/src/tests/profiler/native/nongcheap/nongcheap.h b/src/tests/profiler/native/nongcheap/nongcheap.h new file mode 100644 index 0000000000000..5594d0f796408 --- /dev/null +++ b/src/tests/profiler/native/nongcheap/nongcheap.h @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "../profiler.h" + +class NonGcHeapProfiler : public Profiler +{ +public: + NonGcHeapProfiler() : Profiler(), + _nonGcHeapObjects(0), + _failures(0) + {} + + static GUID GetClsid(); + virtual HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pICorProfilerInfoUnk); + virtual HRESULT STDMETHODCALLTYPE ObjectAllocated(ObjectID objectId, ClassID classId); + virtual HRESULT STDMETHODCALLTYPE Shutdown(); + +private: + std::atomic _nonGcHeapObjects; + std::atomic _failures; +};