From 0ac29ee544efc9c221831425a8346c04f8aad530 Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 00:55:38 +0200
Subject: [PATCH 01/13] Report -1 for GC.GetGeneration(nongc_obj) and same for
 GetObjectGeneration

---
 src/coreclr/gc/gc.cpp                         |  5 +-
 src/coreclr/gc/gcinterface.h                  |  2 +-
 src/coreclr/inc/corerror.xml                  |  5 ++
 src/coreclr/pal/prebuilt/corerror/mscorurt.rc |  5 +-
 src/coreclr/pal/prebuilt/inc/corerror.h       |  1 +
 src/coreclr/vm/proftoeeinterfaceimpl.cpp      |  9 +++
 src/tests/profiler/gc/nongcheap.cs            | 49 ++++++++++++
 src/tests/profiler/gc/nongcheap.csproj        | 21 ++++++
 src/tests/profiler/native/CMakeLists.txt      |  1 +
 src/tests/profiler/native/classfactory.cpp    |  5 ++
 .../profiler/native/nongcheap/nongcheap.cpp   | 74 +++++++++++++++++++
 .../profiler/native/nongcheap/nongcheap.h     | 24 ++++++
 12 files changed, 196 insertions(+), 5 deletions(-)
 create mode 100644 src/tests/profiler/gc/nongcheap.cs
 create mode 100644 src/tests/profiler/gc/nongcheap.csproj
 create mode 100644 src/tests/profiler/native/nongcheap/nongcheap.cpp
 create mode 100644 src/tests/profiler/native/nongcheap/nongcheap.h

diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp
index 474e041974cf9b..e343abeaa9245c 100644
--- a/src/coreclr/gc/gc.cpp
+++ b/src/coreclr/gc/gc.cpp
@@ -45893,9 +45893,10 @@ unsigned int GCHeap::WhichGeneration (Object* object)
 {
     uint8_t* o = (uint8_t*)object;
 #ifdef FEATURE_BASICFREEZE
-    if (!((o < g_gc_highest_address) && (o >= g_gc_lowest_address)))
+    if (GCHeap::IsInFrozenSegment (object))
     {
-        return max_generation;
+        // Report -1 for objects allocated on NonGC heaps (aka frozen segments)
+        return -1;
     }
 #endif //FEATURE_BASICFREEZE
     gc_heap* hp = gc_heap::heap_of (o);
diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h
index d04e1f87cce9a1..3d47943e15a88b 100644
--- a/src/coreclr/gc/gcinterface.h
+++ b/src/coreclr/gc/gcinterface.h
@@ -984,7 +984,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 f9f604c28ae32c..46cd387a09cc29 100644
--- a/src/coreclr/inc/corerror.xml
+++ b/src/coreclr/inc/corerror.xml
@@ -1182,6 +1182,11 @@
     <Comment> The runtime cannot be suspened since a suspension is already in progress. </Comment>
 </HRESULT>
 
+<HRESULT NumericValue="0x80131389">
+    <SymbolicName>CORPROF_E_NOT_GC_OBJECT</SymbolicName>
+    <Comment> This object belongs to a non-gc heap. </Comment>
+</HRESULT>
+
 <HRESULT NumericValue="0x80131416">
 	<SymbolicName>CORSEC_E_POLICY_EXCEPTION</SymbolicName>
 	<Message>"PolicyException thrown."</Message>
diff --git a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc
index 6f808d6f8f84d5..9f5297a657552d 100644
--- a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc
+++ b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc
@@ -18,7 +18,7 @@ BEGIN
 	MSG_FOR_URT_HR(FUSION_E_INVALID_NAME) "The given assembly name was invalid."
 	MSG_FOR_URT_HR(FUSION_E_CACHEFILE_FAILED) "Failed to add file to AppDomain cache."
 	MSG_FOR_URT_HR(FUSION_E_APP_DOMAIN_LOCKED) "The requested assembly version conflicts with what is already bound in the app domain or specified in the manifest."
-	MSG_FOR_URT_HR(COR_E_LOADING_REFERENCE_ASSEMBLY) "Reference assemblies cannot be loaded for execution."
+	MSG_FOR_URT_HR(COR_E_LOADING_REFERENCE_ASSEMBLY) "Reference assemblies cannot not be loaded for execution."
 	MSG_FOR_URT_HR(COR_E_AMBIGUOUSIMPLEMENTATION) "Ambiguous implementation found."
 	MSG_FOR_URT_HR(CLDB_E_FILE_BADREAD) "Error occurred during a read."
 	MSG_FOR_URT_HR(CLDB_E_FILE_BADWRITE) "Error occurred during a write."
@@ -245,7 +245,7 @@ BEGIN
 	MSG_FOR_URT_HR(CORDBG_E_CANNOT_RESOLVE_ASSEMBLY) "We failed to resolve assembly given an AssemblyRef token. Assembly may be not loaded yet or not a valid token."
 	MSG_FOR_URT_HR(CORDBG_E_MUST_BE_IN_LOAD_MODULE) "Must be in context of LoadModule callback to perform requested operation."
 	MSG_FOR_URT_HR(CORDBG_E_CANNOT_BE_ON_ATTACH) "Requested operation cannot be performed during an attach operation."
-	MSG_FOR_URT_HR(CORDBG_E_NGEN_NOT_SUPPORTED) "NGEN is not supported."
+	MSG_FOR_URT_HR(CORDBG_E_NGEN_NOT_SUPPORTED) "NGEN must be supported to perform the requested operation."
 	MSG_FOR_URT_HR(CORDBG_E_ILLEGAL_SHUTDOWN_ORDER) "Trying to shutdown out of order."
 	MSG_FOR_URT_HR(CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS) "Debugging fiber mode managed process is not supported."
 	MSG_FOR_URT_HR(CORDBG_E_MUST_BE_IN_CREATE_PROCESS) "Must be in context of CreateProcess callback to perform requested operation."
@@ -288,6 +288,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 6f0be602583ee1..8b64c945ac9bc8 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 1d78588b328a51..88b32052e029a6 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)-1;
+        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/tests/profiler/gc/nongcheap.cs b/src/tests/profiler/gc/nongcheap.cs
new file mode 100644
index 00000000000000..8bdf447f88506a
--- /dev/null
+++ b/src/tests/profiler/gc/nongcheap.cs
@@ -0,0 +1,49 @@
+// 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");
+        }
+
+        [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 00000000000000..d51dcb692abfe0
--- /dev/null
+++ b/src/tests/profiler/gc/nongcheap.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+    <OutputType>exe</OutputType>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Optimize>true</Optimize>
+    <!-- This test provides no interesting scenarios for GCStress -->
+    <GCStressIncompatible>true</GCStressIncompatible>
+    <!-- The test launches a secondary process and process launch creates
+    an infinite event loop in the SocketAsyncEngine on Linux. Since
+    runincontext loads even framework assemblies into the unloadable
+    context, locals in this loop prevent unloading -->
+    <UnloadabilityIncompatible>true</UnloadabilityIncompatible>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildProjectName).cs" />
+    <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+    <ProjectReference Include="../common/profiler_common.csproj" />
+    <CMakeProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
+  </ItemGroup>
+</Project>
diff --git a/src/tests/profiler/native/CMakeLists.txt b/src/tests/profiler/native/CMakeLists.txt
index a3d2d77902f386..feb446e8846df6 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 418b0b680830b9..7d8aa7942e5d9d 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 00000000000000..efb81e8d545640
--- /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)-1)
+    {
+        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 00000000000000..5594d0f7964088
--- /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<int> _nonGcHeapObjects;
+    std::atomic<int> _failures;
+};

From 71f63b22c966a1fbdeed883efb5672dbc12ab464 Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 01:07:56 +0200
Subject: [PATCH 02/13] Update corerror.xml

---
 src/coreclr/inc/corerror.xml | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/coreclr/inc/corerror.xml b/src/coreclr/inc/corerror.xml
index 46cd387a09cc29..afeaf4f602a4ad 100644
--- a/src/coreclr/inc/corerror.xml
+++ b/src/coreclr/inc/corerror.xml
@@ -198,8 +198,8 @@
 
 <HRESULT NumericValue="0x80131058">
 	<SymbolicName>COR_E_LOADING_REFERENCE_ASSEMBLY</SymbolicName>
-	<Message>"Reference assemblies cannot not be loaded for execution."</Message>
-	<Comment> Reference assemblies cannot not be loaded for execution.  </Comment>
+	<Message>"Reference assemblies cannot be loaded for execution."</Message>
+	<Comment> Reference assemblies cannot be loaded for execution </Comment>
 </HRESULT>
 
 <HRESULT NumericValue="0x8013106A">
@@ -1184,7 +1184,8 @@
 
 <HRESULT NumericValue="0x80131389">
     <SymbolicName>CORPROF_E_NOT_GC_OBJECT</SymbolicName>
-    <Comment> This object belongs to a non-gc heap. </Comment>
+    <Message>"This object belongs to a non-gc heap."</Message>
+    <Comment> This object belongs to a non-gc heap </Comment>
 </HRESULT>
 
 <HRESULT NumericValue="0x80131416">
@@ -1793,8 +1794,8 @@
 
 <HRESULT NumericValue="0x80131c14">
 	<SymbolicName>CORDBG_E_NGEN_NOT_SUPPORTED</SymbolicName>
-	<Message>"NGEN must be supported to perform the requested operation."</Message>
-	<Comment> NGEN must be supported to perform the requested operation </Comment>
+	<Message>"NGEN is not supported."</Message>
+	<Comment> NGEN is not supported </Comment>
 </HRESULT>
 
 <HRESULT NumericValue="0x80131c15">

From c3754e0bc0b9e359a5f9c274201cf0f89146c238 Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 01:09:13 +0200
Subject: [PATCH 03/13] revert changes in error codes

---
 src/coreclr/pal/prebuilt/corerror/mscorurt.rc | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc
index 9f5297a657552d..a30a45ef680196 100644
--- a/src/coreclr/pal/prebuilt/corerror/mscorurt.rc
+++ b/src/coreclr/pal/prebuilt/corerror/mscorurt.rc
@@ -18,7 +18,7 @@ BEGIN
 	MSG_FOR_URT_HR(FUSION_E_INVALID_NAME) "The given assembly name was invalid."
 	MSG_FOR_URT_HR(FUSION_E_CACHEFILE_FAILED) "Failed to add file to AppDomain cache."
 	MSG_FOR_URT_HR(FUSION_E_APP_DOMAIN_LOCKED) "The requested assembly version conflicts with what is already bound in the app domain or specified in the manifest."
-	MSG_FOR_URT_HR(COR_E_LOADING_REFERENCE_ASSEMBLY) "Reference assemblies cannot not be loaded for execution."
+	MSG_FOR_URT_HR(COR_E_LOADING_REFERENCE_ASSEMBLY) "Reference assemblies cannot be loaded for execution."
 	MSG_FOR_URT_HR(COR_E_AMBIGUOUSIMPLEMENTATION) "Ambiguous implementation found."
 	MSG_FOR_URT_HR(CLDB_E_FILE_BADREAD) "Error occurred during a read."
 	MSG_FOR_URT_HR(CLDB_E_FILE_BADWRITE) "Error occurred during a write."
@@ -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."
@@ -245,7 +246,7 @@ BEGIN
 	MSG_FOR_URT_HR(CORDBG_E_CANNOT_RESOLVE_ASSEMBLY) "We failed to resolve assembly given an AssemblyRef token. Assembly may be not loaded yet or not a valid token."
 	MSG_FOR_URT_HR(CORDBG_E_MUST_BE_IN_LOAD_MODULE) "Must be in context of LoadModule callback to perform requested operation."
 	MSG_FOR_URT_HR(CORDBG_E_CANNOT_BE_ON_ATTACH) "Requested operation cannot be performed during an attach operation."
-	MSG_FOR_URT_HR(CORDBG_E_NGEN_NOT_SUPPORTED) "NGEN must be supported to perform the requested operation."
+	MSG_FOR_URT_HR(CORDBG_E_NGEN_NOT_SUPPORTED) "NGEN is not supported."
 	MSG_FOR_URT_HR(CORDBG_E_ILLEGAL_SHUTDOWN_ORDER) "Trying to shutdown out of order."
 	MSG_FOR_URT_HR(CORDBG_E_CANNOT_DEBUG_FIBER_PROCESS) "Debugging fiber mode managed process is not supported."
 	MSG_FOR_URT_HR(CORDBG_E_MUST_BE_IN_CREATE_PROCESS) "Must be in context of CreateProcess callback to perform requested operation."

From d1268e578a39202e38f6a2fe2ca605a5348adcda Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 11:34:17 +0200
Subject: [PATCH 04/13] Test commit

---
 src/coreclr/gc/gc.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp
index e343abeaa9245c..11856c4da4bc71 100644
--- a/src/coreclr/gc/gc.cpp
+++ b/src/coreclr/gc/gc.cpp
@@ -45893,9 +45893,8 @@ unsigned int GCHeap::WhichGeneration (Object* object)
 {
     uint8_t* o = (uint8_t*)object;
 #ifdef FEATURE_BASICFREEZE
-    if (GCHeap::IsInFrozenSegment (object))
+    if (!((o < g_gc_highest_address) && (o >= g_gc_lowest_address)) || GCHeap::IsInFrozenSegment (object))
     {
-        // Report -1 for objects allocated on NonGC heaps (aka frozen segments)
         return -1;
     }
 #endif //FEATURE_BASICFREEZE

From d57e1935f9b5b75cee5b99aeddd00bfc263b017c Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 14:09:49 +0200
Subject: [PATCH 05/13] fix CI

---
 src/coreclr/gc/gc.cpp                       | 14 +++++++++++---
 src/coreclr/gc/gcimpl.h                     |  2 +-
 src/coreclr/gc/gcinterface.h                |  2 +-
 src/coreclr/nativeaot/Runtime/GCHelpers.cpp |  2 +-
 src/coreclr/vm/comutilnative.cpp            |  4 ++--
 5 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp
index 3a9076bc697e22..237997957e80b2 100644
--- a/src/coreclr/gc/gc.cpp
+++ b/src/coreclr/gc/gc.cpp
@@ -45798,14 +45798,22 @@ void GCHeap::SetYieldProcessorScalingFactor (float scalingFactor)
     }
 }
 
-unsigned int GCHeap::WhichGeneration (Object* object)
+unsigned int GCHeap::WhichGeneration (Object* object, bool maxGenForNonGcObjects)
 {
     uint8_t* o = (uint8_t*)object;
 #ifdef FEATURE_BASICFREEZE
-    if (!((o < g_gc_highest_address) && (o >= g_gc_lowest_address)) || GCHeap::IsInFrozenSegment (object))
+    if (!((o < g_gc_highest_address) && (o >= g_gc_lowest_address)))
     {
-        return -1;
+        return maxGenForNonGcObjects ? max_generation : -1;
+    }
+#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 maxGenForNonGcObjects ? max_generation : -1;
     }
+#endif
 #endif //FEATURE_BASICFREEZE
     gc_heap* hp = gc_heap::heap_of (o);
     unsigned int g = hp->object_gennum (o);
diff --git a/src/coreclr/gc/gcimpl.h b/src/coreclr/gc/gcimpl.h
index b82f17a78f9597..a5085c29b52685 100644
--- a/src/coreclr/gc/gcimpl.h
+++ b/src/coreclr/gc/gcimpl.h
@@ -151,7 +151,7 @@ class GCHeap : public IGCHeapInternal
     void    SetFinalizationRun (Object* obj);
 
     //returns the generation number of an object (not valid during relocation)
-    unsigned WhichGeneration (Object* object);
+    unsigned WhichGeneration (Object* object, bool maxGenForNonGcObjects = true);
     // returns TRUE is the object is ephemeral
     bool IsEphemeral (Object* object);
     bool IsHeapPointer (void* object, bool small_heap_only = false);
diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h
index 945629d02f712a..d814f68c07e189 100644
--- a/src/coreclr/gc/gcinterface.h
+++ b/src/coreclr/gc/gcinterface.h
@@ -722,7 +722,7 @@ class IGCHeap {
 
     // Returns the generation in which obj is found. Also used by the VM
     // in some places, in particular syncblk code.
-    virtual unsigned WhichGeneration(Object* obj) PURE_VIRTUAL
+    virtual unsigned WhichGeneration(Object* obj, bool maxGenForNonGcObjects = true) PURE_VIRTUAL
 
     // Returns the number of GCs that have transpired in the given generation
     // since the beginning of the life of the process. Also used by the VM
diff --git a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp
index d795d460753ea6..a662d716cd61cf 100644
--- a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp
+++ b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp
@@ -104,7 +104,7 @@ COOP_PINVOKE_HELPER(int32_t, RhGetGcCollectionCount, (int32_t generation, CLR_BO
 
 COOP_PINVOKE_HELPER(int32_t, RhGetGeneration, (OBJECTREF obj))
 {
-    return GCHeapUtilities::GetGCHeap()->WhichGeneration(obj);
+    return GCHeapUtilities::GetGCHeap()->WhichGeneration(obj, false);
 }
 
 COOP_PINVOKE_HELPER(int32_t, RhGetGcLatencyMode, ())
diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp
index 488655ee7a62c9..025b7d7ee0080b 100644
--- a/src/coreclr/vm/comutilnative.cpp
+++ b/src/coreclr/vm/comutilnative.cpp
@@ -726,7 +726,7 @@ FCIMPL1(int, GCInterface::GetGeneration, Object* objUNSAFE)
     if (objUNSAFE == NULL)
         FCThrowArgumentNull(W("obj"));
 
-    int result = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(objUNSAFE);
+    int result = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(objUNSAFE, false);
     FC_GC_POLL_RET();
     return result;
 }
@@ -826,7 +826,7 @@ FCIMPL1(int, GCInterface::GetGenerationWR, LPVOID handle)
     if (temp == NULL)
         COMPlusThrowArgumentNull(W("wo"));
 
-    iRetVal = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(OBJECTREFToObject(temp));
+    iRetVal = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(OBJECTREFToObject(temp), false);
 
     HELPER_METHOD_FRAME_END();
 

From 8720b4ff4186b456e1cfd6975d2551f42cdb852d Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 20:09:11 +0200
Subject: [PATCH 06/13] try INT_MAX

---
 eng/native/configurecompiler.cmake                       | 1 +
 src/coreclr/gc/gc.cpp                                    | 6 +++---
 src/coreclr/gc/gcimpl.h                                  | 2 +-
 src/coreclr/gc/gcinterface.h                             | 2 +-
 src/coreclr/nativeaot/Runtime/GCHelpers.cpp              | 2 +-
 src/coreclr/vm/comutilnative.cpp                         | 4 ++--
 .../SmokeTests/Preinitialization/Preinitialization.cs    | 4 ++--
 src/tests/profiler/gc/nongcheap.cs                       | 9 +++++++++
 src/tests/profiler/native/nongcheap/nongcheap.cpp        | 2 +-
 9 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake
index 0282e29f069eea..f62eaa53fa68a8 100644
--- a/eng/native/configurecompiler.cmake
+++ b/eng/native/configurecompiler.cmake
@@ -87,6 +87,7 @@ if (MSVC)
 
   # Checked build specific flags
   add_linker_flag(/INCREMENTAL:NO CHECKED) # prevent "warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:REF' specification"
+  add_linker_flag(/INCREMENTAL:NO DEBUG) # prevent "warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:REF' specification"
   add_linker_flag(/OPT:REF CHECKED)
   add_linker_flag(/OPT:NOICF CHECKED)
 
diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp
index 237997957e80b2..a944a0541dea54 100644
--- a/src/coreclr/gc/gc.cpp
+++ b/src/coreclr/gc/gc.cpp
@@ -45798,20 +45798,20 @@ void GCHeap::SetYieldProcessorScalingFactor (float scalingFactor)
     }
 }
 
-unsigned int GCHeap::WhichGeneration (Object* object, bool maxGenForNonGcObjects)
+unsigned int GCHeap::WhichGeneration (Object* object)
 {
     uint8_t* o = (uint8_t*)object;
 #ifdef FEATURE_BASICFREEZE
     if (!((o < g_gc_highest_address) && (o >= g_gc_lowest_address)))
     {
-        return maxGenForNonGcObjects ? max_generation : -1;
+        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 maxGenForNonGcObjects ? max_generation : -1;
+        return INT32_MAX;
     }
 #endif
 #endif //FEATURE_BASICFREEZE
diff --git a/src/coreclr/gc/gcimpl.h b/src/coreclr/gc/gcimpl.h
index a5085c29b52685..b82f17a78f9597 100644
--- a/src/coreclr/gc/gcimpl.h
+++ b/src/coreclr/gc/gcimpl.h
@@ -151,7 +151,7 @@ class GCHeap : public IGCHeapInternal
     void    SetFinalizationRun (Object* obj);
 
     //returns the generation number of an object (not valid during relocation)
-    unsigned WhichGeneration (Object* object, bool maxGenForNonGcObjects = true);
+    unsigned WhichGeneration (Object* object);
     // returns TRUE is the object is ephemeral
     bool IsEphemeral (Object* object);
     bool IsHeapPointer (void* object, bool small_heap_only = false);
diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h
index d814f68c07e189..945629d02f712a 100644
--- a/src/coreclr/gc/gcinterface.h
+++ b/src/coreclr/gc/gcinterface.h
@@ -722,7 +722,7 @@ class IGCHeap {
 
     // Returns the generation in which obj is found. Also used by the VM
     // in some places, in particular syncblk code.
-    virtual unsigned WhichGeneration(Object* obj, bool maxGenForNonGcObjects = true) PURE_VIRTUAL
+    virtual unsigned WhichGeneration(Object* obj) PURE_VIRTUAL
 
     // Returns the number of GCs that have transpired in the given generation
     // since the beginning of the life of the process. Also used by the VM
diff --git a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp
index a662d716cd61cf..d795d460753ea6 100644
--- a/src/coreclr/nativeaot/Runtime/GCHelpers.cpp
+++ b/src/coreclr/nativeaot/Runtime/GCHelpers.cpp
@@ -104,7 +104,7 @@ COOP_PINVOKE_HELPER(int32_t, RhGetGcCollectionCount, (int32_t generation, CLR_BO
 
 COOP_PINVOKE_HELPER(int32_t, RhGetGeneration, (OBJECTREF obj))
 {
-    return GCHeapUtilities::GetGCHeap()->WhichGeneration(obj, false);
+    return GCHeapUtilities::GetGCHeap()->WhichGeneration(obj);
 }
 
 COOP_PINVOKE_HELPER(int32_t, RhGetGcLatencyMode, ())
diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp
index 025b7d7ee0080b..488655ee7a62c9 100644
--- a/src/coreclr/vm/comutilnative.cpp
+++ b/src/coreclr/vm/comutilnative.cpp
@@ -726,7 +726,7 @@ FCIMPL1(int, GCInterface::GetGeneration, Object* objUNSAFE)
     if (objUNSAFE == NULL)
         FCThrowArgumentNull(W("obj"));
 
-    int result = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(objUNSAFE, false);
+    int result = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(objUNSAFE);
     FC_GC_POLL_RET();
     return result;
 }
@@ -826,7 +826,7 @@ FCIMPL1(int, GCInterface::GetGenerationWR, LPVOID handle)
     if (temp == NULL)
         COMPlusThrowArgumentNull(W("wo"));
 
-    iRetVal = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(OBJECTREFToObject(temp), false);
+    iRetVal = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(OBJECTREFToObject(temp));
 
     HELPER_METHOD_FRAME_END();
 
diff --git a/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs b/src/tests/nativeaot/SmokeTests/Preinitialization/Preinitialization.cs
index e87d6f44f46735..6f16508f216e03 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<C1>();
-            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
index 8bdf447f88506a..c2d42550dbf3ec 100644
--- a/src/tests/profiler/gc/nongcheap.cs
+++ b/src/tests/profiler/gc/nongcheap.cs
@@ -22,6 +22,15 @@ static void AllocateNonGcHeapObjects()
             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 heaps");
+
+            GC.Collect(gen);
+            GC.Collect(gen, GCCollectionMode.Aggressive);
+            GC.Collect(gen, GCCollectionMode.Optimized, true);
+            GC.Collect(gen, GCCollectionMode.Forced, true, true);
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
diff --git a/src/tests/profiler/native/nongcheap/nongcheap.cpp b/src/tests/profiler/native/nongcheap/nongcheap.cpp
index efb81e8d545640..7689f9dda5f4bf 100644
--- a/src/tests/profiler/native/nongcheap/nongcheap.cpp
+++ b/src/tests/profiler/native/nongcheap/nongcheap.cpp
@@ -32,7 +32,7 @@ HRESULT STDMETHODCALLTYPE NonGcHeapProfiler::ObjectAllocated(ObjectID objectId,
     HRESULT hr = pCorProfilerInfo->GetObjectGeneration(objectId, &gen);
 
     // non-GC objects (same for GC.GetGeneration() API) have generation = -1
-    if (gen.generation == (COR_PRF_GC_GENERATION)-1)
+    if (gen.generation == (COR_PRF_GC_GENERATION)INT32_MAX)
     {
         if (!FAILED(hr))
         {

From 0e422ab9b0c8642cf536ba605494860c81f60860 Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 20:10:10 +0200
Subject: [PATCH 07/13] revert unrelated change

---
 eng/native/configurecompiler.cmake | 1 -
 1 file changed, 1 deletion(-)

diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake
index f62eaa53fa68a8..0282e29f069eea 100644
--- a/eng/native/configurecompiler.cmake
+++ b/eng/native/configurecompiler.cmake
@@ -87,7 +87,6 @@ if (MSVC)
 
   # Checked build specific flags
   add_linker_flag(/INCREMENTAL:NO CHECKED) # prevent "warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:REF' specification"
-  add_linker_flag(/INCREMENTAL:NO DEBUG) # prevent "warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:REF' specification"
   add_linker_flag(/OPT:REF CHECKED)
   add_linker_flag(/OPT:NOICF CHECKED)
 

From 330f22a62e51335b40082258d64c5be74394d105 Mon Sep 17 00:00:00 2001
From: Egor Bogatov <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 22:05:17 +0200
Subject: [PATCH 08/13] oops

---
 src/coreclr/vm/proftoeeinterfaceimpl.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp
index 88b32052e029a6..0e9e39a91fd188 100644
--- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp
+++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp
@@ -9143,7 +9143,7 @@ HRESULT ProfToEEInterfaceImpl::GetObjectGeneration(ObjectID objectId,
 
     if (hp->IsInFrozenSegment((Object*)objectId))
     {
-        range->generation = (COR_PRF_GC_GENERATION)-1;
+        range->generation = (COR_PRF_GC_GENERATION)INT32_MAX;
         range->rangeStart = 0;
         range->rangeLength = 0;
         range->rangeLengthReserved = 0;

From d63b65228df6ff35e6f4dd7598cea2315dc31651 Mon Sep 17 00:00:00 2001
From: Egor Bogatov <egorbo@gmail.com>
Date: Wed, 19 Apr 2023 23:56:39 +0200
Subject: [PATCH 09/13] Update nongcheap.cs

---
 src/tests/profiler/gc/nongcheap.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/tests/profiler/gc/nongcheap.cs b/src/tests/profiler/gc/nongcheap.cs
index c2d42550dbf3ec..b93a4de0da01af 100644
--- a/src/tests/profiler/gc/nongcheap.cs
+++ b/src/tests/profiler/gc/nongcheap.cs
@@ -28,7 +28,6 @@ static void AllocateNonGcHeapObjects()
                 throw new Exception("object is expected to be in a non-gc heaps");
 
             GC.Collect(gen);
-            GC.Collect(gen, GCCollectionMode.Aggressive);
             GC.Collect(gen, GCCollectionMode.Optimized, true);
             GC.Collect(gen, GCCollectionMode.Forced, true, true);
         }

From 2135544ad88b8260912be0cc621b0a9634b5f0a6 Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Fri, 21 Apr 2023 14:16:25 +0200
Subject: [PATCH 10/13] Address feedback

---
 src/libraries/System.Runtime/tests/System/GCTests.cs | 3 +++
 src/tests/profiler/gc/nongcheap.cs                   | 6 +-----
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/libraries/System.Runtime/tests/System/GCTests.cs b/src/libraries/System.Runtime/tests/System/GCTests.cs
index d6d7488c1fc091..e59aea9ccdd1fb 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/profiler/gc/nongcheap.cs b/src/tests/profiler/gc/nongcheap.cs
index b93a4de0da01af..e81925a76e9e7e 100644
--- a/src/tests/profiler/gc/nongcheap.cs
+++ b/src/tests/profiler/gc/nongcheap.cs
@@ -25,11 +25,7 @@ static void AllocateNonGcHeapObjects()
 
             int gen = GC.GetGeneration("string7");
             if (gen != int.MaxValue)
-                throw new Exception("object is expected to be in a non-gc heaps");
-
-            GC.Collect(gen);
-            GC.Collect(gen, GCCollectionMode.Optimized, true);
-            GC.Collect(gen, GCCollectionMode.Forced, true, true);
+                throw new Exception("object is expected to be in a non-gc heap for this test to work");
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]

From b50f36ad6e23566a297735b4c9d691205698c767 Mon Sep 17 00:00:00 2001
From: EgorBo <egorbo@gmail.com>
Date: Fri, 21 Apr 2023 15:17:40 +0200
Subject: [PATCH 11/13] Add asserts

---
 src/coreclr/gc/gc.cpp        | 3 +++
 src/coreclr/gc/gcimpl.h      | 3 ++-
 src/coreclr/gc/gcinterface.h | 1 +
 src/coreclr/vm/threads.cpp   | 1 +
 4 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp
index a944a0541dea54..04263269157147 100644
--- a/src/coreclr/gc/gc.cpp
+++ b/src/coreclr/gc/gc.cpp
@@ -48665,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 b82f17a78f9597..4a4c92cb50ede1 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 945629d02f712a..a6a3177acb090d 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
diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp
index 2dd6b621599728..76b133b0d1248d 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)
             {

From 2ce158898ab99598cee0bc6ee8915d22b14f1f34 Mon Sep 17 00:00:00 2001
From: Egor Bogatov <egorbo@gmail.com>
Date: Sun, 23 Apr 2023 02:38:35 +0200
Subject: [PATCH 12/13] Don't run the test with crossgen

---
 src/tests/profiler/gc/nongcheap.csproj | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/tests/profiler/gc/nongcheap.csproj b/src/tests/profiler/gc/nongcheap.csproj
index d51dcb692abfe0..d59b11eaf4a53e 100644
--- a/src/tests/profiler/gc/nongcheap.csproj
+++ b/src/tests/profiler/gc/nongcheap.csproj
@@ -11,6 +11,8 @@
     runincontext loads even framework assemblies into the unloadable
     context, locals in this loop prevent unloading -->
     <UnloadabilityIncompatible>true</UnloadabilityIncompatible>
+    <!-- Crossgen doesn't allocate string literals on non-gc heaps -->
+    <CrossGenTest>false</CrossGenTest>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="$(MSBuildProjectName).cs" />

From fdcfbdd9e9fb9812e4855d844e3b25e737e00801 Mon Sep 17 00:00:00 2001
From: Egor Bogatov <egorbo@gmail.com>
Date: Sun, 23 Apr 2023 12:00:29 +0200
Subject: [PATCH 13/13] Update nongcheap.csproj

---
 src/tests/profiler/gc/nongcheap.csproj | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/tests/profiler/gc/nongcheap.csproj b/src/tests/profiler/gc/nongcheap.csproj
index d59b11eaf4a53e..d51dcb692abfe0 100644
--- a/src/tests/profiler/gc/nongcheap.csproj
+++ b/src/tests/profiler/gc/nongcheap.csproj
@@ -11,8 +11,6 @@
     runincontext loads even framework assemblies into the unloadable
     context, locals in this loop prevent unloading -->
     <UnloadabilityIncompatible>true</UnloadabilityIncompatible>
-    <!-- Crossgen doesn't allocate string literals on non-gc heaps -->
-    <CrossGenTest>false</CrossGenTest>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="$(MSBuildProjectName).cs" />
