IL2CPP Internals - Garbage collector integration

This is the seventh post in the IL2CPP Internals series. In this post, we will explore a bit about how the IL2CPP runtime integrates with a garbage collector. Specifically, we’ll see how the GC roots in managed code are communicated to the native garbage collector.

As with all of the posts in this series, this post deals with implementation details that can and likely will change in the future. In this post we will specifically look at some internal APIs used by the runtime code to communicate with the garbage collector. These APIs are not publicly supported, and you should not attempt to call them from any code in a real project. But, this is a post about internals, so let’s dig in.

Garbage collection

I won’t discuss general garbage collection techniques in this post, as it is a wide and varied subject, with plenty of existing research and published information. To follow along, just think of a GC as an algorithm that develops a directed graph of object references. If an object Child is used by an object Parent (via a pointer in native code), then the graph looks like this

alt

As the GC scans through the memory for a process, it looks for objects which don’t have parent. If it finds one, then it can reuse the memory for that object on something else.

Of course, most object will have a parent of some sort, so the GC really needs to know which objects are the important parents. I like to think of these as the objects that are actually in use by your program. In GC terminology, these are called the “roots”. Here is an example of a parent without a root.

alt

In this case, Parent 2 does not have a root, so the GC can reuse the memory from Parent 2 and Child 2. Parent 1 and Child 1, however, do have a root, so the GC cannot reuse their memory. The program is still using them for something.

For .NET, there are three kinds of roots:

  • Local variables on the stack of any thread executing managed code
  • Static variables
  • GChandle objects

We’ll see how IL2CPP communicates with the garbage collector about all three of these kinds of roots.

The setup

For this post, I’m using Unity 5.1.0p1 on OSX, and I’m building for the iOS platform. This will allow us to use Xcode to have a look at how IL2CPP interacts with the garbage collector. As with the other posts in this series, I’ll use an example project with a single script:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine;

public class AnyClass {}

public class HelloWorld : MonoBehaviour {
private static AnyClass staticAnyClass = new AnyClass();
void Start () {
var thread = new Thread(AnotherThread);
thread.Start();
thread.Join();
var anyClassForGCHandle = new AnyClass();
var gcHandle = GCHandle.Alloc(anyClassForGCHandle);
}

private static void AnotherThread() {
var anyClassLocal = new AnyClass();
}
}
 

I have enabled the “Development Build” in the Build Settings dialog, and I set the “Run in Xcode as” option to a value of “Debug”. In the generated Xcode project, first search for the string “Start_m”. You should see the generated code for the Start method in the the HelloWorld class named HelloWorld_Start_m3.