Skip to content

Commit

Permalink
Return int.MaxValue in GC.GetGeneration(nongc_obj) and same for profi…
Browse files Browse the repository at this point in the history
…ler's GetObjectGeneration (#85017)
  • Loading branch information
EgorBo authored Apr 23, 2023
1 parent e8c4971 commit 2c9e483
Show file tree
Hide file tree
Showing 16 changed files with 222 additions and 9 deletions.
13 changes: 12 additions & 1 deletion src/coreclr/gc/gc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46118,8 +46118,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);
Expand Down Expand Up @@ -48971,6 +48979,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)
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/gc/gcimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/gc/gcinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
14 changes: 10 additions & 4 deletions src/coreclr/inc/corerror.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down Expand Up @@ -1182,6 +1182,12 @@
<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>
<Message>"This object belongs to a non-gc heap."</Message>
<Comment> This object belongs to a non-gc heap </Comment>
</HRESULT>

<HRESULT NumericValue="0x80131416">
<SymbolicName>CORSEC_E_POLICY_EXCEPTION</SymbolicName>
<Message>"PolicyException thrown."</Message>
Expand Down Expand Up @@ -1788,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">
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/pal/prebuilt/corerror/mscorurt.rc
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -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."
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/pal/prebuilt/inc/corerror.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/vm/proftoeeinterfaceimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/threads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Runtime/tests/System/GCTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
53 changes: 53 additions & 0 deletions src/tests/profiler/gc/nongcheap.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
21 changes: 21 additions & 0 deletions src/tests/profiler/gc/nongcheap.csproj
Original file line number Diff line number Diff line change
@@ -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>
1 change: 1 addition & 0 deletions src/tests/profiler/native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/tests/profiler/native/classfactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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();
Expand Down
74 changes: 74 additions & 0 deletions src/tests/profiler/native/nongcheap/nongcheap.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
24 changes: 24 additions & 0 deletions src/tests/profiler/native/nongcheap/nongcheap.h
Original file line number Diff line number Diff line change
@@ -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;
};

0 comments on commit 2c9e483

Please sign in to comment.