IL2CPP内部:生成代码之旅

This is the second blog post in the IL2CPP Internals series. In this post, we will investigate the C++ code generated by il2cpp.exe. Along the way, we will see how managed types are represented in native code, take a look at runtime checks used to support the .NET virtual machine, see how loops are generated and more!

这是IL2CPP Internals系列中的第二篇博客文章。 在本文中,我们将研究il2cpp.exe生成的C ++代码。 在此过程中,我们将看到托管类型如何以本机代码表示,看看用于支持.NET虚拟机的运行时检查,了解如何生成循环等等!

We will get into some very version-specific code that is certainly going to change in later versions of Unity. Still, the concepts will remain the same.

我们将涉及一些非常特定于版本的代码,这些代码肯定会在Unity的更高版本中发生变化。 尽管如此,这些概念将保持不变。

Example project

示例项目

I’ll use the latest version of Unity available, 5.0.1p1, for this example. As in the first post in this series, I’ll start with an empty project and add one script file. This time, it has the following contents:

在本示例中,我将使用可用的最新版本的Unity 5.0.1p1。 与本系列的第一篇文章一样,我将从一个空项目开始,并添加一个脚本文件。 这次,它具有以下内容:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using UnityEngine;
public class HelloWorld : MonoBehaviour {
private class Important {
public static int ClassIdentifier = 42;
public int InstanceIdentifier;
}
void Start () {
Debug.Log("Hello, IL2CPP!");
Debug.LogFormat("Static field: {0}", Important.ClassIdentifier);
var importantData = new [] {
new Important { InstanceIdentifier = 0 },
new Important { InstanceIdentifier = 1 } };
Debug.LogFormat("First value: {0}", importantData[0].InstanceIdentifier);
Debug.LogFormat("Second value: {0}", importantData[1].InstanceIdentifier);
try {
throw new InvalidOperationException("Don't panic");
}
catch (InvalidOperationException e) {
Debug.Log(e.Message);
}
for (var i = 0; i < 3; ++i) {
Debug.LogFormat("Loop iteration: {0}", i);
}
}
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using UnityEngine ;
public class HelloWorld : MonoBehaviour {
private class Important {
public static int ClassIdentifier = 42 ;
public int InstanceIdentifier ;
}
void Start ( ) {
Debug . Log ( "Hello, IL2CPP!" ) ;
Debug . LogFormat ( "Static field: {0}" , Important . ClassIdentifier ) ;
var importantData = new [ ] {
new Important { InstanceIdentifier = 0 } ,
new Important { InstanceIdentifier = 1 } } ;
Debug . LogFormat ( "First value: {0}" , importantData [ 0 ] . InstanceIdentifier ) ;
Debug . LogFormat ( "Second value: {0}" , importantData [ 1 ] . InstanceIdentifier ) ;
try {
throw new InvalidOperationException ( "Don't panic" ) ;
}
catch ( InvalidOperationException e ) {
Debug . Log ( e . Message ) ;
}
for ( var i = 0 ; i < 3 ; ++ i ) {
Debug . LogFormat ( "Loop iteration: {0}" , i ) ;
}
}
}

I’ll build this project for WebGL, running the Unity editor on Windows. I’ve selected the Development Player option in the Build Settings, so that we can get relatively nice names in the generated C++ code. I’ve also set the Enable Exceptions option in the WebGL Player Settings to Full.

我将为WebGL构建该项目,并在Windows上运行Unity编辑器。 我在“构建设置”中选择了“开发播放器”选项,以便我们可以在生成的C ++代码中获得相对不错的名称。 我还将WebGL Player设置中的Enable Exceptions选项设置为Full。

Overview of the generated code

生成代码概述

After the WebGL build is complete, the generated C++ code is available in the Temp\StagingArea\Data\il2cppOutput directory in my project directory. Once the editor is closed, this directory will be deleted. As long as the editor is open though, this directory will remain unchanged, so we can inspect it.

WebGL构建完成后,可以在我的项目目录中的Temp \ StagingArea \ Data \ il2cppOutput目录中找到生成的C ++代码。 关闭编辑器后,该目录将被删除。 只要打开编辑器,该目录就将保持不变,因此我们可以对其进行检查。

The il2cpp.exe utility generated a number of files, even for this small project. I see 4625 header files  and 89 C++ source code files. To get a handle on all of this code, I like to use a text editor which works with Exuberant CTags. CTags will usually generate a tags file quickly for this code, which makes it easier to navigate.

il2cpp.exe实用程序生成了许多文件,即使对于这个小项目也是如此。 我看到了4625个头文件和89个C ++源代码文件。 为了掌握所有这些代码,我喜欢使用与Exuberant CTags一起使用的文本编辑器。 CTags通常会为此代码快速生成一个标签文件,这使得导航变得更加容易。

Initially, you can see that many of the generated C++ files are not from the simple script code, but instead are the converted version of the code in the standard libraries, like mscorlib.dll. As mentioned in the first post in this series, the IL2CPP scripting backend uses the same standard library code as the Mono scripting backend. Note that we convert the code in mscorlib.dll and other standard library assemblies each time il2cpp.exe runs. This might seem unnecessary, since that code does not change.

最初,您可以看到许多生成的C ++文件不是来自简单脚本代码,而是来自标准库(如mscorlib.dll)中代码的转换版本。 如本系列第一篇文章所述,IL2CPP脚本后端使用与Mono脚本后端相同的标准库代码。 请注意,每次il2cpp.exe运行时,我们都会转换mscorlib.dll和其他标准库程序集中的代码。 这似乎不必要,因为该代码不会更改。

However, the IL2CPP scripting backend always uses byte code stripping to decrease the executable size. So even small changes in the script code can cause many different parts of the standard library code to be used or not, depending on the situation. Therefore, we need to convert the mscorlib.dll assembly each time. We are researching better ways to do incremental builds, but we don’t have any good solutions yet.

但是,IL2CPP脚本后端始终使用字节代码剥离来减小可执行文件的大小。 因此,根据情况,即使脚本代码中的微小更改也可能导致标准库代码的许多不同部分被使用或不使用。 因此,我们需要每次都转换mscorlib.dll程序集。 我们正在研究进行增量构建的更好方法,但是我们还没有任何好的解决方案。

How managed code maps to generated C++ code

托管代码如何映射到生成的C ++代码

For each type in the managed code, il2cpp.exe will generate one header file for the C++ definition of the type and another header file for the method declarations for the type. For example, let’s look at the contents of the converted UnityEngine.Vector3 type. The header file for the type is named UnityEngine_UnityEngine_Vector3.h. The name is created based on the name of the assembly, UnityEngine.dll followed by the namespace and name of the type. The code looks like this:

对于托管代码中的每种类型,il2cpp.exe将为该类型的C ++定义生成一个头文件,为该类型的方法声明生成另一个头文件。 例如,让我们看一下转换后的UnityEngine.Vector3类型的内容。 该类型的头文件名为UnityEngine_UnityEngine_Vector3.h。 该名称是根据程序集名称UnityEngine.dll以及名称空间和类型名称创建的。 代码如下:

1

2
3
4
5
6
7
8
9
10
// UnityEngine.Vector3
struct Vector3_t78
{
// System.Single UnityEngine.Vector3::x
float ___x_1;
// System.Single UnityEngine.Vector3::y
float ___y_2;
// System.Single UnityEngine.Vector3::z
float ___z_3;
};

1

2
3
4
5
6
7
8
9
10
// UnityEngine.Vector3
struct Vector3_t78
{
// System.Single UnityEngine.Vector3::x
float ___x_1 ;
// System.Single UnityEngine.Vector3::y
float ___y_2 ;
// System.Single UnityEngine.Vector3::z
float ___z_3 ;
} ;

The il2cpp.exe utility has converted each of the three instance fields, and done a little bit of name mangling to avoid conflicts and reserved words. By using leading underscores, we are using some reserved names in C++, but so far we’ve not seen any conflicts with C++ standard library code.

il2cpp.exe实用程序已转换了三个实例字段中的每一个,并做了一些名称修饰,以避免冲突和保留字。 通过使用前划线,我们在C ++中使用了一些保留名称,但是到目前为止,我们还没有看到与C ++标准库代码有任何冲突。

The UnityEngine_UnityEngine_Vector3MethodDeclarations.h file contains the method declarations for all of the methods in Vector3. For example, Vector3 overrides the Object.ToString method:

UnityEngine_UnityEngine_Vector3MethodDeclarations.h文件包含Vector3所有方法的方法声明。 例如, Vector3重写Object.ToString方法:

1

2
// System.String UnityEngine.Vector3::ToString()
extern "C" String_t* Vector3_ToString_m2315 (Vector3_t78 * __this, MethodInfo* method) IL2CPP_METHOD_ATTR

1

2
// System.String UnityEngine.Vector3::ToString()
extern "C" String_t * Vector3_ToString_m2315 ( Vector3_t78 * __this , MethodInfo * method ) IL2CPP_METHOD_ATTR

Note the comment, which indicates the managed method this native declaration represents. I often find it useful to search the files in the output for the name of the managed method in this format, especially for methods with common names, like ToString.

请注意注释,该注释指示此本机声明表示的托管方法。 我经常发现以这种格式搜索输出文件中的托管方法的名称很有用,尤其是对于具有通用名称(例如ToString

Notice a few interesting things about all methods converted by il2cpp.exe:

注意关于il2cpp.exe转换的所有方法的一些有趣的事情:

  • These are not member functions in C++. All methods are free functions, where the first argument is the “this” pointer. For static functions in managed code, IL2CPP always passes a value of NULL for this first argument. By always declaring methods with the “this” pointer as the first argument, we simplify the method generation code in il2cpp.exe and we make invoking methods via other methods (like delegates) simpler for generated code.

    这些不是C ++中的成员函数。 所有方法都是自由函数,其中第一个参数是“ this”指针。 对于托管代码中的静态函数,IL2CPP始终为此第一个参数传递NULL值。 通过始终以“ this”指针作为第一个参数声明方法,我们简化了il2cpp.exe中的方法生成代码,并使通过其他方法(如委托人)进行调用的方法更易于生成代码。

  • Every method has an additional argument of type MethodInfo* which includes the metadata about the method that is used for things like virtual method invocation. The Mono scripting backend uses platform-specific trampolines to pass this metadata. For IL2CPP, we’ve decided to avoid the use of trampolines to aid in portability.

    每个方法都有一个类型为MethodInfo*的附加参数,其中包含有关该方法的元数据,该元数据用于诸如虚拟方法调用之类的事情。 Mono脚本后端使用特定于平台的蹦床传递此元数据。 对于IL2CPP,我们已决定避免使用蹦床来提高便携性。

  • All methods are declared extern “C” so that il2cpp.exe can sometimes lie to the C++ compiler and treat all methods as if they had the same type.

    所有方法都声明为extern “C”因此il2cpp.exe有时可能位于C ++编译器中,并将所有方法视为具有相同类型。

The first two points imply that every method has at least two parameters, the “this” pointer and the MethodInfo pointer. Do these extra parameters cause unnecessary overhead? While they clearly do add overhead, we haven’t seen anything so far which suggests that those extra arguments cause performance problems. Although it may seem that they would, profiling has shown that the difference in performance is not measurable.

前两点暗示每个方法至少有两个参数,“ this”指针和MethodInfo指针。 这些额外的参数会导致不必要的开销吗? 尽管它们显然确实增加了开销,但到目前为止我们还没有看到任何暗示这些额外参数会导致性能问题的信息。 尽管似乎可以做到,但分析表明性能差异是无法测量的。

We can jump to the definition of this ToString method using Ctags. It is in the Bulk_UnityEngine_0.cpp file. The code in that method definition doesn’t look too much like the C# code in the Vector3::ToString() method. However, if you use a tool like ILSpy to reflect the code for the Vector3::ToString() method, you’ll see that the generated C++ code looks very similar to the IL code.

我们可以使用Ctags跳转到此ToString方法的定义。 它在Bulk_UnityEngine_0.cpp文件中。 该方法定义中的代码看起来Vector3::ToString()方法中的C#代码。 但是,如果使用ILSpy之类的工具来反映Vector3::ToString()方法的代码,则会看到生成的C ++代码看起来与IL代码非常相似。

Why doesn’t il2cpp.exe generate a separate C++ file for the method definitions for each type, as it does for the method declarations? This Bulk_UnityEngine_0.cpp file is pretty large, 20,481 lines actually! We found the C++ compilers we were using had trouble with a large number of source files. Compiling four thousand .cpp files took much longer than compiling the same source code in 80 .cpp files. So il2cpp.exe batches the methods definitions for types into groups and generates one C++ file per group.

为什么il2cpp.exe不像每种方法声明那样为每种类型的方法定义生成单独的C ++文件? 这个Bulk_UnityEngine_0.cpp文件非常大,实际上有20481行! 我们发现我们使用的C ++编译器在处理大量源文件时遇到了麻烦。 与在80个.cpp文件中编译相同的源代码相比,编译四千个.cpp文件花费的时间更长。 因此,il2cpp.exe将类型的方法定义分为几批,并且每组生成一个C ++文件。

Now jump back to the method declarations header file and notice this line near the top of the file:

现在跳回到方法声明头文件,并注意文件顶部附近的这一行:

1

#include "codegen/il2cpp-codegen.h"

1

#include "codegen/il2cpp-codegen.h"

The il2cpp-codegen.h file contains the interface which generated code uses to access the libil2cpp runtime services. We’ll discuss some ways that the runtime is used by generated code later.

il2cpp-codegen.h文件包含生成的代码用来访问libil2cpp运行时服务的接口。 稍后,我们将讨论生成的代码使用运行时的一些方式。

Method prologues

方法序言

Let’s take a look at the definition of the Vector3::ToString() method. Specifically, it has a common prologue that is emitted in all methods by il2cpp.exe.

让我们看一下Vector3::ToString()方法的定义。 具体地说,它具有一个通用的序言,该序言由il2cpp.exe在所有方法中发出。

1

2
3
4
5
6
7
StackTraceSentry _stackTraceSentry(&Vector3_ToString_m2315_MethodInfo);
static bool Vector3_ToString_m2315_init;
if (!Vector3_ToString_m2315_init)
{
ObjectU5BU5D_t4_il2cpp_TypeInfo_var = il2cpp_codegen_class_from_type(&ObjectU5BU5D_t4_0_0_0);
Vector3_ToString_m2315_init = true;
}

1

2
3
4
5
6
7
StackTraceSentry _stackTraceSentry ( &Vector3_ToString_m2315_MethodInfo ) ;
static bool Vector3_ToString_m2315_init ;
if ( ! Vector3_ToString_m2315_init )
{
ObjectU5BU5D_t4_il2cpp_TypeInfo_var = il2cpp_codegen_class_from_type ( &ObjectU5BU5D_t4_0_0_0 ) ;
Vector3_ToString_m2315_init = true ;
}

The first line of this prologue creates a local variable of type StackTraceSentry. This variable is used to track the managed call stack, so that IL2CPP can report it in calls like Environment.StackTrace. Code generation of this entry is actually optional, and is enabled in this case by the --enable-stacktrace option passed to il2cpp.exe (since I set Enable Exceptions option in the WebGL Player Settings to Full). For small functions, we found that the overhead of this variable has a negative impact on performance. So for iOS and other platforms where we can use platform-specific stack trace information, we never emit this line into generated code. For WebGL, we don’t have platform-specific stack trace support, so it is necessary to allow managed code exceptions to work properly.

此序言的第一行创建一个类型为StackTraceSentry的局部变量。 此变量用于跟踪托管的调用堆栈,以便IL2CPP可以在诸如Environment.StackTrace之类的调用中报告它。 此项的代码生成实际上是可选的,并且在这种情况下,通过传递给il2cpp.exe的--enable-stacktrace选项启用了此选项(因为我将WebGL Player设置中的Enable Exceptions选项设置为Full)。 对于小型函数,我们发现此变量的开销对性能有负面影响。 因此,对于可以使用特定于平台的堆栈跟踪信息的iOS和其他平台,我们永远不会将这一行发送到生成的代码中。 对于WebGL,我们没有特定于平台的堆栈跟踪支持,因此必须允许托管代码异常正常工作。

The second part of the prologue does lazy initialization of type metadata for any array or generic types used in the method body. So the name ObjectU5BU5D_t4 is the name of the type System.Object[]. This part of the prologue is only executed once and often does nothing if the type was already initialized elsewhere, so we have not seen any adverse performance implications from this generated code.

序言的第二部分对方法主体中使用的任何数组或通用类型进行类型元数据的延迟初始化。 因此,名称ObjectU5BU5D_t4是类型System.Object[]的名称。 序言的这一部分仅执行一次,并且如果该类型已经在其他地方初始化,则通常不执行任何操作,因此我们没有看到此生成的代码有任何不利的性能影响。

Is this code thread safe though? What if two threads call Vector3::ToString() at the same time? Actually, this code is not problematic, since all of the code in the libil2cpp runtime used for type initialization is safe to call from multiple threads. It is possible (maybe even likely) that il2cpp_codegen_class_from_type function will be called more than once, but the actual work it does will only occur once, on one thread. Method execution won’t continue until that initialization is complete. So this method prologue is thread safe.

这个代码线程安全吗? 如果两个线程同时调用Vector3::ToString()怎么办? 实际上,此代码没有问题,因为libil2cpp运行时中用于类型初始化的所有代码都可以从多个线程安全地调用。 il2cpp_codegen_class_from_type函数有可能(甚至有可能)被多次调用,但是它的实际工作只会在一个线程上发生一次。 直到完成初始化,方法执行才会继续。 因此,此方法序言是线程安全的。

Runtime checks

运行时检查

The next part of the method creates an object array, stores the value of the x field of Vector3 in a local, then boxes the local and adds it to the array at index zero. Here is the generated C++ code (with some annotations):

方法的下一部分将创建一个对象数组,将Vector3x字段的值存储在本地,然后将其装箱并将其添加到索引为零的数组。 这是生成的C ++代码(带有一些注释):

1

2
3
4
5
6
7
8
9
10
11
12
13
// Create a new single-dimension, zero-based object array
ObjectU5BU5D_t4* L_0 = ((ObjectU5BU5D_t4*)SZArrayNew(ObjectU5BU5D_t4_il2cpp_TypeInfo_var, 3));
// Store the Vector3::x field in a local
float L_1 = (__this->___x_1);
float L_2 = L_1;
// Box the float instance, since it is a value type.
Object_t * L_3 = Box(InitializedTypeInfo(&Single_t264_il2cpp_TypeInfo), &L_2);
// Here are three important runtime checks
NullCheck(L_0);
IL2CPP_ARRAY_BOUNDS_CHECK(L_0, 0);
ArrayElementTypeCheck (L_0, L_3);
// Store the boxed value in the array at index 0
*((Object_t **)(Object_t **)SZArrayLdElema(L_0, 0)) = (Object_t *)L_3;

1

2
3
4
5
6
7
8
9
10
11
12
13
// Create a new single-dimension, zero-based object array
ObjectU5BU5D_t4 * L_0 = ( ( ObjectU5BU5D_t4 * ) SZArrayNew ( ObjectU5BU5D_t4_il2cpp_TypeInfo_var , 3 ) ) ;
// Store the Vector3::x field in a local
float L_1 = ( __this -> ___x_1 ) ;
float L_2 = L_1 ;
// Box the float instance, since it is a value type.
Object_t * L_3 = Box ( InitializedTypeInfo ( &Single_t264_il2cpp_TypeInfo ) , &L_2 ) ;
// Here are three important runtime checks
NullCheck ( L_0 ) ;
IL2CPP_ARRAY_BOUNDS_CHECK ( L_0 , 0 ) ;
ArrayElementTypeCheck ( L_0 , L_3 ) ;
// Store the boxed value in the array at index 0
* ( ( Object_t * * ) ( Object_t * * ) SZArrayLdElema ( L_0 , 0 ) ) = ( Object_t * ) L_3 ;

The three runtime checks are not present in the IL code, but are instead injected by il2cpp.exe.

IL代码中不存在这三个运行时检查,而是由il2cpp.exe注入。

  • The NullCheck code will throw a NullReferenceException if the value of the array is null.

    如果数组的值为nullNullCheck代码将引发NullReferenceException

  • The IL2CPP_ARRAY_BOUNDS_CHECK code will throw an IndexOutOfRangeException if the array index is not correct.

    如果数组索引不正确,则IL2CPP_ARRAY_BOUNDS_CHECK代码将引发IndexOutOfRangeException

  • The ArrayElementTypeCheck code will thrown an ArrayTypeMismatchException if the type of the element being added to the array is not correct.

    如果要添加到数组中的元素类型不正确,则ArrayElementTypeCheck代码将引发ArrayTypeMismatchException

These three runtime checks are all guarantees provided by the .NET virtual machine. Rather than injecting code, the Mono scripting backend uses platform specific signaling mechanism to handle these same runtime checks. For IL2CPP, we wanted to be more platform agnostic and support platforms like WebGL, where there is no platform-specific signaling mechanism, so il2cpp.exe injects these checks.

这三个运行时检查都是.NET虚拟机提供的所有保证。 Mono脚本后端使用特定于平台的信令机制来处理这些相同的运行时检查,而不是注入代码。 对于IL2CPP,我们希望成为与平台无关的平台,并支持WebGL之类的平台,因为该平台没有特定于平台的信令机制,因此il2cpp.exe会注入这些检查。

Do these runtime checks cause performance problems though? In most cases, we’ve not seen any adverse impact on performance and they provide the benefits and safety which are required by the .NET virtual machine. In a few specific cases though, we are seeing these checks lead to degraded performance, especially in tight loops. We’re working on a way now to allow managed code to be annotated to remove these runtime checks when il2cpp.exe generates C++ code. Stay tuned on this one.

这些运行时检查是否会导致性能问题? 在大多数情况下,我们并未发现对性能有任何不利影响,它们提供了.NET虚拟机所需的好处和安全性。 不过,在某些特定情况下,我们发现这些检查会导致性能下降,尤其是在紧密循环中。 我们正在研究一种方法,允许在il2cpp.exe生成C ++代码时对托管代码进行注释,以删除这些运行时检查。 请继续关注这一点。

Static Fields

静态场

Now that we’ve seen how instance fields look (in the Vector3 type), let’s see how static fields are converted and accessed. Find the definition of the HelloWorld_Start_m3 method, which is in the Bulk_Assembly-CSharp_0.cpp file in my build. From there, jump to the Important_t1 type (in theAssemblyU2DCSharp_HelloWorld_Important.h file):

既然我们已经看到了实例字段的外观(在Vector3类型中),让我们看看如何转换和访问静态字段。 在我的构建的Bulk_Assembly-CSharp_0.cpp文件中找到HelloWorld_Start_m3方法的定义。 从那里跳到Important_t1类型(在AssemblyU2DCSharp_HelloWorld_Important.h文件中):

1

2
3
4
5
6
7
8
9
10
struct Important_t1  : public Object_t
{
// System.Int32 HelloWorld/Important::InstanceIdentifier
int32_t ___InstanceIdentifier_1;
};
struct Important_t1_StaticFields
{
// System.Int32 HelloWorld/Important::ClassIdentifier
int32_t ___ClassIdentifier_0;
};

1

2
3
4
5
6
7
8
9
10
struct Important _ t1  : public Object_t
{
// System.Int32 HelloWorld/Important::InstanceIdentifier
int32_t ___InstanceIdentifier_1 ;
} ;
struct Important_t1_StaticFields
{
// System.Int32 HelloWorld/Important::ClassIdentifier
int32_t ___ClassIdentifier_0 ;
} ;

Notice that il2cpp.exe has generated a separate C++ struct to hold the static field for this type, since the static field is shared between all instances of this type. So at runtime, there will be one instance of the Important_t1_StaticFields type created, and all of the instances of the Important_t1 type will share that instance of the static fields type. In generated code, the static field is accessed like this:

请注意,il2cpp.exe生成了一个单独的C ++结构来保存此类型的静态字段,因为此类型的所有实例之间都共享静态字段。 因此,在运行时,将创建一个Important_t1_StaticFields类型的实例,并且Important_t 1类型的所有实例都将共享该静态字段类型的实例。 在生成的代码中,静态字段的访问方式如下:

1

int32_t L_1 = (((Important_t1_StaticFields*)InitializedTypeInfo(&Important_t1_il2cpp_TypeInfo)->static_fields)->___ClassIdentifier_0);

1

int32_t L_1 = ( ( ( Important_t1_StaticFields * ) InitializedTypeInfo ( &Important_t1_il2cpp_TypeInfo ) -> static_fields ) -> ___ClassIdentifier_0 ) ;

The type metadata for Important_t1 holds a pointer to the single instance of the Important_t1_StaticFields type, and that instance is used to obtain the value of the static field.

Important_t1的类型元数据包含一个指向Important_t1_StaticFields类型的单个实例的指针,该实例用于获取静态字段的值。

Exceptions

例外情况

Managed exceptions are converted by il2cpp.exe to C++ exceptions. We have chosen this path to again avoid platform-specific solutions. When il2cpp.exe needs to emit code to raise a managed exception, it calls the il2cpp_codegen_raise_exception function.

托管异常由il2cpp.exe转换为C ++异常。 我们选择了这种方式来避免使用特定于平台的解决方案。 当il2cpp.exe需要发出代码以引发托管异常时,它将调用il2cpp_codegen_raise_exception函数。

The code in our HelloWorld_Start_m3 method to throw and catch a managed exception looks like this:

HelloWorld_Start_m3方法中用于引发和捕获托管异常的代码如下所示:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try
{ // begin try (depth: 1)
InvalidOperationException_t7 * L_17 = (InvalidOperationException_t7 *)il2cpp_codegen_object_new (InitializedTypeInfo(&InvalidOperationException_t7_il2cpp_TypeInfo));
InvalidOperationException__ctor_m8(L_17, (String_t*) &_stringLiteral5, /*hidden argument*/&InvalidOperationException__ctor_m8_MethodInfo);
il2cpp_codegen_raise_exception(L_17);
// IL_0092: leave IL_00a8
goto IL_00a8;
} // end try (depth: 1)
catch(Il2CppExceptionWrapper& e)
{
__exception_local = (Exception_t8 *)e.ex;
if(il2cpp_codegen_class_is_assignable_from (&InvalidOperationException_t7_il2cpp_TypeInfo, e.ex->object.klass))
goto IL_0097;
throw e;
}
IL_0097:
{ // begin catch(System.InvalidOperationException)
V_1 = ((InvalidOperationException_t7 *)__exception_local);
NullCheck(V_1);
String_t* L_18 = (String_t*)VirtFuncInvoker0< String_t* >::Invoke(&Exception_get_Message_m9_MethodInfo, V_1);
Debug_Log_m6(NULL /*static, unused*/, L_18, /*hidden argument*/&Debug_Log_m6_MethodInfo);
// IL_00a3: leave IL_00a8
goto IL_00a8;
} // end catch (depth: 1)

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try
{ // begin try (depth: 1)
InvalidOperationException_t7 * L_17 = ( InvalidOperationException_t7 * ) il2cpp_codegen_object_new ( InitializedTypeInfo ( &InvalidOperationException_t7_il2cpp_TypeInfo ) ) ;
InvalidOperationException__ctor_m8 ( L_17 , ( String_t * ) &_stringLiteral5 , /*hidden argument*/ &InvalidOperationException__ctor_m8_MethodInfo ) ;
il2cpp_codegen_raise_exception ( L_17 ) ;
// IL_0092: leave IL_00a8
goto IL_00a8 ;
} // end try (depth: 1)
catch ( Il2CppExceptionWrapper & e )
{
__exception_local = ( Exception_t8 * ) e . ex ;
if ( il2cpp_codegen_class_is_assignable_from ( &InvalidOperationException_t7_il2cpp_TypeInfo , e . ex -> object . klass ) )
goto IL_0097 ;
throw e ;
}
IL_0097 :
{ // begin catch(System.InvalidOperationException)
V_1 = ( ( InvalidOperationException_t7 * ) __exception_local ) ;
NullCheck ( V_1 ) ;
String_t * L_18 = ( String_t * ) VirtFuncInvoker0 < String_t * > :: Invoke ( &Exception_get_Message_m9_MethodInfo , V_1 ) ;
Debug_Log_m6 ( NULL /*static, unused*/ , L_18 , /*hidden argument*/ &Debug_Log_m6_MethodInfo ) ;
// IL_00a3: leave IL_00a8
goto IL_00a8 ;
} // end catch (depth: 1)

All managed exceptions are wrapped in the C++ Il2CppExceptionWrapper type. When the generated code catches an exception of that type, it unpacks the C++ representation of the managed exception (which has type Exception_t8). In this case, we’re looking only for a InvalidOperationException, so if we don’t find an exception of that type, a copy of the C++ exception is thrown again. If we do find the correct type, the code jumps to the implementation of the catch handler, and writes out the exception message.

所有托管异常都包装在C ++ Il2CppExceptionWrapper类型中。 当生成的代码捕获到该类型的异常时,它将解压缩托管异常(其类型为Exception_t8 )的C ++表示。 在这种情况下,我们只在寻找InvalidOperationException ,因此,如果找不到该类型的异常,则会再次抛出C ++异常的副本。 如果我们找到正确的类型,代码将跳转到catch处理程序的实现,并写出异常消息。

Goto!?!

去!?!

This code brings up an interesting point. What are those labels and goto statements doing in there? These constructs are not necessary in structured programming! However, IL does not have structured programming concepts like loops and if/then statements. Since it is lower-level, il2cpp.exe follows lower-level concepts in generated code.

这段代码提出了一个有趣的观点。 这些标签和goto语句在那里做什么? 这些结构在结构化编程中不是必需的! 但是,IL没有诸如循环和if / then语句之类的结构化编程概念。 由于它是较低级别的,因此il2cpp.exe在生成的代码中遵循较低级别的概念。

For example, let’s look at the for loop in the HelloWorld_Start_m3 method:

例如,让我们看一下HelloWorld_Start_m3方法中的for循环:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
IL_00a8:
{
V_2 = 0;
goto IL_00cc;
}
IL_00af:
{
ObjectU5BU5D_t4* L_19 = ((ObjectU5BU5D_t4*)SZArrayNew(ObjectU5BU5D_t4_il2cpp_TypeInfo_var, 1));
int32_t L_20 = V_2;
Object_t * L_21 =
Box(InitializedTypeInfo(&Int32_t5_il2cpp_TypeInfo), &L_20);
NullCheck(L_19);
IL2CPP_ARRAY_BOUNDS_CHECK(L_19, 0);
ArrayElementTypeCheck (L_19, L_21);
*((Object_t **)(Object_t **)SZArrayLdElema(L_19, 0)) = (Object_t *)L_21;
Debug_LogFormat_m7(NULL /*static, unused*/, (String_t*) &_stringLiteral6, L_19, /*hidden argument*/&Debug_LogFormat_m7_MethodInfo);
V_2 = ((int32_t)(V_2+1));
}
IL_00cc:
{
if ((((int32_t)V_2) < ((int32_t)3)))
{
goto IL_00af;
}
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
IL_00a8 :
{
V_2 = 0 ;
goto IL_00cc ;
}
IL_00af :
{
ObjectU5BU5D_t4 * L_19 = ( ( ObjectU5BU5D_t4 * ) SZArrayNew ( ObjectU5BU5D_t4_il2cpp_TypeInfo_var , 1 ) ) ;
int32_t L_20 = V_2 ;
Object_t * L_21 =
Box ( InitializedTypeInfo ( &Int32_t5_il2cpp_TypeInfo ) , &L_20 ) ;
NullCheck ( L_19 ) ;
IL2CPP_ARRAY_BOUNDS_CHECK ( L_19 , 0 ) ;
ArrayElementTypeCheck ( L_19 , L_21 ) ;
* ( ( Object_t * * ) ( Object_t * * ) SZArrayLdElema ( L_19 , 0 ) ) = ( Object_t * ) L_21 ;
Debug_LogFormat_m7 ( NULL /*static, unused*/ , ( String_t * ) &_stringLiteral6 , L_19 , /*hidden argument*/ &Debug_LogFormat_m7_MethodInfo ) ;
V_2 = ( ( int32_t ) ( V_2 + 1 ) ) ;
}
IL_00cc :
{
if ( ( ( ( int32_t ) V_2 ) < ( ( int32_t ) 3 ) ) )
{
goto IL_00af ;
}
}

Here the V_2 variable is the loop index. Is starts off with a value of 0, then is incremented at the bottom of the loop in this line:

这里的V_2变量是循环索引。 从0开始,然后在此行的循环底部递增:

1

V_2 = ((int32_t)(V_2+1));

1

V_2 = ( ( int32_t ) ( V_2 + 1 ) ) ;

The ending condition in the loop is then checked here:

然后在此处检查循环中的结束条件:

1

if ((((int32_t)V_2) < ((int32_t)3)))

1

if ( ( ( ( int32_t ) V_2 ) < ( ( int32_t ) 3 ) ) )

As long as V_2 is less than 3, the goto statement jumps to the IL_00af label, which is the top of the loop body. You might be able to guess that il2cpp.exe is currently generating C++ code directly from IL, without using an intermediate abstract syntax tree representation. If you guessed this, you are correct. You may have also noticed in the Runtime checks section above, some of the generated code looks like this:

只要V_2小于3,goto语句就会跳转到循环主体顶部的IL_00af标签。 您可能能够猜测到il2cpp.exe当前是直接从IL生成C ++代码,而不使用中间的抽象语法树表示形式。 如果您猜对了,那是对的。 您可能还在上面的“运行时检查”部分中注意到,一些生成的代码如下所示:

1

2
float L_1 = (__this->___x_1);
float L_2 = L_1;

1

2
float L_1 = ( __this -> ___x_1 ) ;
float L_2 = L_1 ;

Clearly, the L_2 variable is not necessary here. Most C++ compilers can optimize away this additional assignment, but we would like to avoid emitting it at all. We’re currently researching the possibility of using an AST to better understand the IL code and generate better C++ code for cases involving local variables and for loops, among others.

显然,这里不需要L_2变量。 大多数C ++编译器都可以优化此额外的分配,但是我们完全希望避免发出它。 我们目前正在研究使用AST更好地理解IL代码并针对涉及局部变量和循环的情况生成更好的C ++代码的可能性。

Conclusion

结论

We’ve just scratched the surface of the C++ code generated by the IL2CPP scripting backend for a very simple project. If you haven’t done so already, I encourage you dig into the generated code in your project. As you explore, keep in mind that the generated C++ code will look different in future versions of Unity, as we are constantly working to improve the build and runtime performance of the IL2CPP scripting backend.

我们只是简单地介绍了IL2CPP脚本后端生成的C ++代码的表面。 如果您尚未这样做,建议您深入研究项目中生成的代码。 当您进行探索时,请记住,随着我们不断努力改善IL2CPP脚本后端的构建和运行时性能,生成的C ++代码在Unity的未来版本中将看起来有所不同。

By converting IL code to C++, we’ve been able to obtain a nice balance between portable and performant code. We can have many of the nice developer-friendly features of managed code, while still getting the benefits of quality machine code that C++ compiler provides for various platforms.

通过将IL代码转换为C ++,我们已经能够在可移植代码和高性能代码之间取得良好的平衡。 我们可以拥有托管代码的许多友好的,对开发人员友好的功能,同时仍然可以获得C ++编译器为各种平台提供的高质量机器代码的好处。

In future posts, we’ll explore more generated code, including method calls, sharing of method implementations and wrappers for calls to native libraries. But next time we will debug some of the generated code for an iOS 64-bit build using Xcode.

在以后的文章中,我们将探索更多生成的代码,包括方法调用,方法实现的共享以及对本机库的调用的包装。 但是下一次,我们将使用Xcode调试一些用于iOS 64位版本的生成代码。

翻译自: https://blogs.unity3d.com/2015/05/13/il2cpp-internals-a-tour-of-generated-code/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值