IL2CPP: Garbage Collector Integration
- Transfer

Previous topic materials:
IL2CPP: P / Invoke
wrappers for types and methods IL2CPP: method calls
In this article, as in previous publications of the series, we will reveal implementation details of a separate component of IL2CPP, which may be changed in the future. Consider some of the internal APIs used by the runtime code to interact with the garbage collector. These APIs are not public, so you should not try to call them from the code of any real project.
Garbage collection
I will not give here general information about garbage collection, since this is a rather broad topic, which is devoted to a lot of research and publications. For brevity, imagine a garbage collector in the form of an algorithm that is engaged in the construction of directed graphs from links to objects. If the Child object is used by the Parent object through a pointer in the native code, then the graph will look like this:

The garbage collector looks for objects without a parent object in the memory used by the process. If such an object is found, then the memory it occupies can be freed and reused for another purpose.
Of course, most objects must have some kind of parent object. Therefore, the garbage collector needs to be able to distinguish between special parent objects. I prefer to think of the latter as objects used by the program. In the terminology of the garbage collector, they are called “roots”. The following is an example of a parent without root.

In this case, the Parent 2 object has no root, so the garbage collector can reuse the memory occupied by the Parent 2 and Child 2 objects. In turn, Parent 1 and Child 1 have a root - which means they are used by the program, and the garbage collector will not be reused use their memory, since the program still uses them for a specific purpose.
.NET uses three types of roots:
- local variables on the stack of any thread that executes managed code;
- static variables
- GCHandle objects .
We will examine the relationship of IL2CPP with the garbage collector when working with the roots of all the above types.
Preparation for work
I work in Unity version 5.1.0p1 on OSX, and will build for the iOS platform. This will allow us to use Xcode to monitor the interaction of IL2CPP with the garbage collector. As in the previous examples, we will use a project containing one script:
using System;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine;
publicclassAnyClass {}
publicclassHelloWorld : MonoBehaviour {
privatestatic AnyClass staticAnyClass = new AnyClass();
voidStart () {
var thread = new Thread(AnotherThread);
thread.Start();
thread.Join();
var anyClassForGCHandle = new AnyClass();
var gcHandle = GCHandle.Alloc(anyClassForGCHandle);
}
privatestaticvoidAnotherThread() {
var anyClassLocal = new AnyClass();
}
}I checked the Development Build option in the Build Settings window, and chose Debug next to Run in Xcode as. In the generated Xcode project, first find the line Start_m. You should see the generated code for the Start method of the HelloWorld class called HelloWorld_Start_m3.
Add stream local variables as roots
Add a breakpoint in the HelloWorld_Start_m3 function on the line where Thread_Start_m9 is called. This method creates a new managed thread that will be added as root to the garbage collector. This process can be tracked in the libil2cpp header files shipped with Unity. In the Unity installation directory, open the file Contents / Frameworks / il2cpp / libil2cpp / gc / gc-internal.h. It contains a number of methods prefixed with il2cpp_gc_ and is part of the API between the libil2cpp runtime and the garbage collector. But remember that this API is publicly available, therefore these methods should not be called from the code of a real project. In addition, they are subject to change in the new version without notice.
Add a breakpoint in the il2cpp_gc_register_thread function in Xcode. To do this, select Debug> Breakpoints> Create Symbolic Breakpoint.

This point is reached almost instantly after starting the project in Xcode. In this case, we do not see the source code, since it is built in the static library of the libil2cpp environment, however, it is clear from the call stack that this stream is created in the InitializeScriptingBackend method, which is executed at startup.

We will see that this point will be reached several times as managed flows are created for internal use. For now, you can turn it off in Xcode and continue the project without it. We must reach the breakpoint that was added earlier in the HelloWorld_Start_m3 method.
Now I'm going to start the managed thread created by our script code, so I need to turn on the breakpoint on il2cpp_gc_register_thread again. Upon reaching it, we will see that the first thread is waiting to join the created thread, but the call stack for the created thread shows that we are only starting it:

When a new thread contacts the garbage collector, the latter interprets all objects in the local stack of this thread as roots. Take a look at the generated code for the HelloWorld_AnotherThread_m4 method:
AnyClass_t1 * L_0 = (AnyClass_t1 *)il2cpp_codegen_object_new (AnyClass_t1_il2cpp_TypeInfo_var);
AnyClass__ctor_m0(L_0, /*hidden argument*/NULL);
V_0 = L_0;We see one local variable L_0, which the garbage collector should interpret as root. For the short time that this stream exists, this instance of the AnyClass object and any other objects that it refers to cannot be reused by the garbage collector. Variables defined in the stack are the most common root type, since objects in a program primarily start with a local variable in a method executed in a controlled thread.
When the thread completes, the il2cpp_gc_unregister_thread function is called, which tells the garbage collector to no longer interpret the objects in the stream stack as roots. After that, the garbage collector will be able to reuse the memory occupied by the object of the AnyClass class, which is represented in the native code as L_0.
Static variables
Some variables are independent of call flow stacks. These are static variables, and they must also be interpreted as root by the garbage collector.
When IL2CPP creates a native class mapping, all static fields are grouped into a C ++ structure other than field instances in the class. Let's move on to the definition of the HelloWorld_t2 class in Xcode:
struct  HelloWorld_t2  : public MonoBehaviour_t3
{
};
structHelloWorld_t2_StaticFields{// AnyClass HelloWorld::staticAnyClass
AnyClass_t1 * ___staticAnyClass_2;
};Please note that IL2CPP technology does not use the C ++ static keyword, since it must constantly monitor the placement of static fields, as well as the allocation of memory for them, in order to properly interact with the garbage collector. When a particular type is first used at runtime, libil2cpp code will initialize the type. This initialization involves allocating memory for the HelloWorld_t2_StaticFields structure. Memory is allocated using a special call to the garbage collector: il2cpp_gc_alloc_fixed (you can see it in the gc-internal.h file).
After this call, the garbage collector will take the allocated memory as root until the end of the process. You can set a breakpoint on the il2cpp_gc_alloc_fixed function in Xcode, but it is called quite often (even in a simple project like ours), therefore it will not be very useful.
GCHandle Objects
In some cases, it is undesirable to use static variables, but it is necessary to control exactly when the garbage collector can reuse the memory allocated for the object. For example, you need to pass a pointer to a managed entity from managed code to native. If the native code is able to dispose of this object, we need to inform the garbage collector that the native code is now the root in its object graph. For this, a special managed object GCHandle is used.
When creating a GCHandle object, the processing environment code begins to interpret the selected managed object as the root in the garbage collector to prevent reuse of the memory of this object or any other to which it refers. In IL2CPP, we see how the low-level API does this in the file Contents / Frameworks / il2cpp / libil2cpp / gc / GCHandle.h. Again, I remind you that this API is not public. Add a breakpoint to the GCHandle :: New function. If we continue the project, the following call stack should appear:

The generated code for the Start method calls the GCHandle_Alloc_m11 method, which ultimately creates a GCHandle object and notifies the garbage collector of the new root object.
Conclusion
The topic of garbage collection integration in IL2CPP is still far from exhausted. I highly recommend readers to learn more about the interaction of IL2CPP and the garbage collector on their own.