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;
+};