Integrating .NET GC in your C++ application

kant20021 pts1 comments

Integrating .NET GC in your C++ application | Андрій-Ка

When you read programming language holy wars, what irritates me is that when .NET compares to Go then magically arguments about GC start floating around, that some GC is magically better then others. I found that super irrational to that degree so I decide to do at least something.

My problem with GC comparison is that they obviously vague, cannot be proven and usually assume that GC is always come with runtime. And at this point, I don’t think it’s fair call that GC, and no CLR/JVM/Go runtime. So in order to fuel pointless debates, and because it’s fun, I think I can rip .NET GC out of dotnet/runtime repository and package it in the separate application which can be used for illustrative purposes.

As basis for this I would take hidden gem in the src/coreclr/gc/samples subfolder. Tehcnically you can just grab dotnet repo, clone it and build using ./build.cmd -s clr then you will have somewhere in the artifacts folder file gcsample.exe. That’s probably easy way, but not really convinient if some hothead decide that it’s inspirational enough and want more of GC. So I decide to unbundle build of GC sample from whole .NET build. Curent work in the repository on Codeberg. BTW, move your projects from GitHub to other forge.

Setup

Setup for building GC sample is have my repo folder, and dotnet/runtime in the runtime folder alongside with gcsample folder where actual work happens. I decide that this is easier for me since, I don’t like to have multiple cloned variants of runtime laying around. You may easiely tweak this to use submodules if you into that kind of things.

How to integrate GC into your application

GC by itself do not run in isolation, and it needs some basic runtime support for it’s work. Basically it have two important abstractions PAL/OS interface and runtime (or execution engine) support. PAL/OS interface is gratefully provided by CoreCLR itself. But the interface to EE we should provide by ourselves. For that we should implement following classes

GCToEEInterface this is an interface to execution engine

Thread this is abstraction to thread. This is not nescessary OS thread, it can be green thread if runtime will implement it.

We also implement helper class ThreadStore which will be abstraction over working with threads in the runtime.

GC does not ask us much out of API for Thread class. We would like to store allocation context and that would be enough to have minimal GC in our process. The more feature we add to our runtime, the more things we will add to this class and other plumbing. Note: The Thread here by design is virual thread which not nescessary should correspond 1-on-1 to OS thread abstraction.

class Thread<br>uintptr_t m_alloc_context[16]; // Reserve enough space to fix allocation context

friend class ThreadStore;<br>Thread * m_pNext;

public:<br>Thread()

alloc_context* GetAllocContext()<br>return (alloc_context *)&m_alloc_context;<br>};

ThreadStore would be also really simple.

class ThreadStore<br>public:<br>// Get thread list starting from current thread.<br>static Thread * GetThreadList(Thread * pThread);

// Enlist current OS thread to be used for GC on it.<br>static void AttachCurrentThread();<br>};

We don’t really need much out of EE interface. We definitely need interface for threads. Let’s look at the API which cover absolute minimum required

// Get Thread abstraction for the current thread.<br>Thread* GCToEEInterface::GetThread();

The GC uses Thread class later to get access to allocation context and do all magic.

Additional thread related functions.

// Get allocation conext for the curent thread.<br>gc_alloc_context * GCToEEInterface::GetAllocContext();

// Execution fn on the all allocation contexts with given parameter.<br>void GCToEEInterface::GcEnumAllocContexts (enum_alloc_context_func* fn, void* param);

// Get Id of the OS thread on which virtual thread running.<br>uint64_t GCToEEInterface::GetThreadOSThreadId(Thread* thread)

// Start background GC thread<br>bool GCToEEInterface::CreateThread(void (*threadStart)(void*), void* arg, bool is_suspendable, const char* name)

Currently GCToEEInterface::GetAllocContext is mostly GCToEEInterface::GetThread()->GetAllocContext() and only used in DAC from what I see. Maybe that can be removed, or it’s here for standalone interface compatibility. I don’t know.

There other important parts of the GC to execution engine interface is suspension/resuming of EE.

// Ask execution engin to suspend it's operation for specific reason.<br>void GCToEEInterface::SuspendEE(SUSPEND_REASON reason);

// Request execution engine to start operation.<br>void GCToEEInterface::RestartEE(bool /*_bFinishedGC*/);

there two values for SUSPEND_REASON

SUSPEND_FOR_GC which is for actual GC collection

SUSPEND_FOR_GC_PREP is for starting GC when background collection started, or when count of heaps changing. in the background GC.

I also comment out bFinishedGC parameter, since it’s not actually used anywhere in current GC.

Our...

thread runtime gctoeeinterface interface void class

Related Articles