IL2CPP Internals: Method calls

This is the fourth blog post in the IL2CPP Internals series. In this post, we will look at how il2cpp.exe generates C++ code for method calls in managed code. Specifically, we will investigate six different types of method calls:

  • Direct calls on instance and static methods
  • Calls via a compile-time delegate
  • Calls via a virtual method
  • Calls via an interface method
  • Calls via a run-time delegate
  • Calls via reflection

In each case, we will focus on what the generated C++ code is doing and, specifically, on how much those instructions will cost.

As with all of the posts in this series, we will be exploring code that is subject to change and, in fact, is likely to change in a newer version of Unity. However, the concepts should remain the same. Please take everything discussed in this series as implementation details. We like to expose and discuss details like this when it is possible though!

Setup

I’ll be using Unity version 5.0.1p4. I’ll run the editor on Windows, and build for the WebGL platform. I’m building with the “Development Player” option enabled, and the “Enable Exceptions” option set to a value of “Full”.

I’ll build with a single script file, modified from the last post so that we can see the different types of method calls. The script starts with an interface and class definition:

                
interface Interface {
int MethodOnInterface(string question);
}

class Important : Interface {
public int Method(string question) { return 42; }
public int MethodOnInterface(string question) { return 42; }
public static int StaticMethod(string question) { return 42; }
}                
            

Then we have a constant field and a delegate type, both used later in the code:

                
private const string question = "What is the answer to the ultimate question of life, the universe, and everything?";

private delegate int ImportantMethodDelegate(string question);
                
            

Finally, these are the methods we are interested in exploring (plus the obligatory Start method, which has no content here):

                
private void CallDirectly() {
var important = ImportantFactory();
important.Method(question);
}

private void CallStaticMethodDirectly() {
Important.StaticMethod(question);
}

private void CallViaDelegate() {
var important = ImportantFactory();
ImportantMethodDelegate indirect = important.Method;
indirect(question);
}

private void CallViaRuntimeDelegate() {
var important = ImportantFactory();
var runtimeDelegate = Delegate.CreateDelegate(typeof (ImportantMethodDelegate), important, "Method");
runtimeDelegate.DynamicInvoke(question);
}

private void CallViaInterface() {
Interface importantViaInterface = new Important();
importantViaInterface.MethodOnInterface(question);
}

private void CallViaReflection() {
var important = ImportantFactory();
var methodInfo = typeof(Important).GetMethod("Method");
methodInfo.Invoke(important, new object[] {question});
}

private static Important ImportantFactory() {
var important = new Important();
return important;
}

void Start () {}                
            

With all that defined, let’s get started. Recall that the generated C++ code will be located in the Temp\StagingArea\Data\il2cppOutput directory in the project (as long as the editor remains open). And don’t forget to generate Ctags on the generated code, to help navigate it.