Skip to content
This repository has been archived by the owner on Oct 6, 2023. It is now read-only.

What even is AOT

Kalle Jillheden edited this page Jan 5, 2020 · 4 revisions

But wait, why do I need to know this?

There are some major issues with using Newtonsoft.Json that are related to AOT. It's exactly due these issues this entire project, Newtonsoft.Json-for-Unity, is here to solve. But it cannot do this fully "automagically", we need you to know about these issues too.

The acronym AOT is tossed around a lot in these corners. If you're like me then not knowing how something actually works will bug you to headaches when you use it day-to-day. Here is a brief explanation of AOT and JIT in the context of Unity and Newtonsoft.Json.

Before explaining AOT, we first must clear up two other main concepts: JIT and reflection.

What is JIT?

Just-In-Time (JIT) compilation; a relatively old but very hot technology put into many modern runtimes. The word defines compilation that's invoked at the split second before its outcome is to be executed. The JVM and CLR are proud users of this JIT technology, but the definition also includes scripting languages such as JavaScript and Python.

JIT compilation is used to create new code, both native and intermediate, in effort of at runtime optimize performance or simply dynamically generating code.

The act of doing a JIT compilation is sometimes referred to with the verb "jitting".

Reflection

"In computer science, reflection is the ability of a process to examine, introspect, and modify its own structure and behavior."

From: https://en.wikipedia.org/wiki/Reflection_(computer_programming)

Newtonsoft.Json is not precompiled with every possible JSON schema handling every possible object type. Instead, Newtonsoft.Json contains a lot of clever uses of reflection to figure out the schema of the objects it's working with, but also to create new instances of those types dynamically (i.e. not knowing at compile time which types to instantiate).

This dynamic instantiation sometimes requires jitting.

How does .NET use JIT compilation?

A common occurrence are generic types. Most times the compiler can through inspecting your code see which generic types are to be used, but sometimes (such as the case with reflection) it cannot. When you then at runtime come across a code path where you need a type the compiled product was not prepared for it boots up the JIT compilation, compiling the code needed to handle that type, then returning back to the code execution as if it always existed.

Example where JIT would be required:

void Example() {
    Type listType = Type.GetType("System.Collections.Generic.List`1");
    Type listOfStringType = listType.MakeGenericType(new[] { typeof(string) });
    
    // Equivalent to list = new List<string>()
    object list = listOfStringType.GetConstructor(new Type[0]).Invoke(new object[0]);
    // Equivalent to list.Add("foo")
    listOfStringType.GetMethod("Add").Invoke(list, new object[] { "foo" });
    // Equivalent to first = list[0]
    object first = listOfStringType.GetProperty("Item").GetValue(list, new object[] { 0 });
    
    Console.WriteLine($"First item: {first}"); // outputs: foo
}

It would take a smart compiler to know what generic type to compile from the example above, but even so we could come up with an even be more convoluted variant where the strings being fed through input by the user.

Generation of generic types are fully handled by the CLR upon request. But there are sometimes we want to generate more than just a generic type, such as generating full abstract syntax trees of if-statements and function calls to create a new function out of the blue. That subject is what the oh so dreaded System.Reflection.Emit solves. Nothing we will discuss here, except the fact that Newtonsoft.Json uses it to speed itself up in certain scenarios, which is why we sometimes come across this error message:

NotSupportedException: /Users​/builduser​/buildslave​/unity​/build​/External​/il2cpp​/il2cpp​/libil2cpp​/icalls​/mscorlib​/System.Reflection.Emit​/AssemblyBuilder.cpp(20) : Unsupported internal call for IL2CPP:AssemblyBuilder::basic_init - System.Reflection.Emit is not supported.

What is AOT?

Ahead-Of-Time (AOT) compilation; let's say it's the default. Hitting the "Build" button and letting your easy to read C# source code be turned into easy to execute .DLL's and .EXE's. AOT compilation is completed at the phase often referred to as "compile-time". Some languages and environments only allow AOT for differing reasons, but that is where we have to know everything in advance.

All the C# code in your program be AOT compiled. Classes, methods, const values, interfaces, et.al. Generics are compiled to be used as templates (such as List<>), and known generic types (such as List<int>) are compiled among the rest of the type store. If we want for example a specific generic type to be compiled with the resulting program, we sometimes need to help the compiler out by hinting what we actually want.

There is no common verb for the act of "AOT" compilation as there is with "jitting", except for the ambiguous word "compiling".

The thing about IL2CPP

IL2CPP. What an amazing compiler created by Unity Technologies. It takes intermediate language (IL) code, the input used by the CLR and the output from many different C# compilers such as the Microsoft C# compiler (CSC) or the Mono C# compiler (MCS), and converts it to C++ code, that can later be compiled with tools such as the WebAssembly compiler to effectively run C# in the browser, or iOS compiler to effectively run C# on iPhone!

Nowadays there are many solutions to run C# here and there, but the IL2CPP compiler was one of the first to the market. Groundbreaking!

However, the problem with this is that IL2CPP only supports AOT. The code generated by IL2CPP is not run by the CLR (which handles the generation of generics, remember?) and Unity Technologies have intentionally not implemented System.Reflection.Emit as a design principle.

Why perhaps? It's their shortcut. Run C++ code, which for reference has no JIT capabilities, instead of a full fletched CLR. Compared to the Blazor project, where they run .NET Core code in the browser using WebAssembly by actually loading an entire .NET CLR environment into your browser window, per page. Here with IL2CPP since they AOT compile everything they can be much smarter about what to strip away, such as stripping away the entire classes because they are not used. A royalty that Blazor cannot afford (until they get that promise of full AOT compilation of .NET Core/.NET 5 working).

This means you have to take extra steps when building with IL2CPP to make sure the AOT compilations are as complete as they need to be. If using the scripting backend Mono you have full JIT capabilities at your disposal, however if you're using IL2CPP, you need to take extra precautions. Here's a small table of the supported scripting backends per build target in Unity:

Platform Scripting backends
Standalone Mac OS X Mono, IL2CPP
(Note: Mac IL2CPP player can only be built on Mac)
Standalone Windows Mono, IL2CPP
Standalone Linux Mono
Universal Windows Platform (UWP) IL2CPP
tvOS IL2CPP
iOS IL2CPP
WebGL IL2CPP
PS4 Mono, IL2CPP
Xbox One Mono, IL2CPP
Android Mono, IL2CPP

To deal with all of the errors that can occur on this AOT only compiler IL2CPP we have this wiki page to guide you with handling these problems:

Clone this wiki locally