docs/design/mono/aot.md
The mono Ahead of Time (AOT) compiler enables the compilation of the IL code in a .NET assembly to a native object file. This file is called an AOT image. This AOT image can be used by the runtime to avoid having to JIT the IL code.
The AOT compiler is integrated into the mono runtime executable, and can be run using the --aot command
line argument, i.e.
<mono-executable> --aot HelloWorld.dll
aot-compiler.c: The AOT compileraot-runtime.c: Code used at runtime to load AOT imagesimage-writer.c: Support code for emitting textual assemblydwarfwriter.c: Support code for emitting DWARF debug infoIn this mode, the AOT compiler creates a platform shared object file (.so/.dylib), i.e. HelloWorld.dll.so. During execution, when
an assembly is loaded, the runtime loads the corresponding shared object and uses it to avoid having to AOT the methods in the
assembly.
Emission of the native code is done by first emitting an assembly (.s) file, then compiling and linking it with the system tools
(as/ld, or clang).
In this mode, the AOT compiler creates a platform object file (.o). This file needs to be linked into the application and registered with the runtime.
Static compilation is enabled by using the static aot option, i.e. --aot=static,.... The resulting object file contains a linking
symbol named mono_aot_module_<assembly name>_info. This symbol needs to be passed to the a runtime function before the
runtime is initialized, i.e.:
mono_aot_register_module (mono_aot_module_HelloWorld_info);
In this mode, which can be combined with the other modes, the compiler generates additional code which enables the runtime to function without any code being generated at runtime. This includes 2 types of code:
This is enabled by using full aot option, i.e. --aot=full,.... At runtime, all assemblies need to have a full-aot-ed AOT image
present in order for the app to work. This is used on platforms which don't allow runtime code generation like IOS.
LLVM support can be enabled using the llvm aot option, i.e. --aot=llvm. In this mode, instead of generating native code,
the AOT compiler generates an LLVM bitcode (.bc), file, then compiles it to native code using the opt/llc LLVM tools. The
various AOT data structures are also emitted into the .bc file instead of as assembly.
Since the LLVM backend currently doesn't support all .net methods, a smaller assembly file is still emitted, and linked together
with the opt/llc compiled object file into the final shared object file.
The generated AOT images have a dependency on the exact version input assembly used to generate them and the versions of all the referenced assemblies. This means the GUIDs of the assemblies have to match. If there is a mismatch, the AOT image will fail to load.
The AOT images exports one symbol named mono_aot_module_<assembly name>_info which points to a MonoAotFileInfo structure,
which contains pointers to the tables/structures. The AOT image contains:
The AOT image contains serialized versions of many .NET objects like methods/types etc. This uses ad-hoc binary encodings.
The aot-runtime.c file contains the runtime support for loading AOT images.
When an assembly is loaded, the corresponding AOT images is either loaded using the system dynamic linker (dlopen), or
found among the statically linked AOT images.
Every method in the AOT image is assigned an index. The AOT methods corresponding to 'normal' .NET methods are assigned an index corresponding to their metadata token index, while the 'extra' methods are assigned subsequent indexes. There is a hash table inside the AOT image mapping extra methods to their AOT indexes. Loading a method consists of
The mapping from method index to the code is done in an architecture specific way, designed to minimize the amount of runtime relocations in the AOT image. In some cases, this involves generating an extra table with assembly call instructions to all the methods, then disassembling this table at runtime.
The generated code needs to access data which is only available at runtime. For example, for an ldstr "Hello" instruction, the
"Hello" string is a runtime constant.
These constants are stored in a global table called the GOT which is modelled after the Global Offset Table in ELF images. The GOT table contains pointers to runtime objects. The AOT image contains descriptions of these runtime objects so the AOT runtime can compute them. The entries in the GOT are initialized either when the AOT image is loaded (for frequently used entries), or before the method which uses them is first executed.
Before an AOTed method can be executed, it might need some initialization. This involves:
For methods compiled by the mono JIT, initialization is done when the method is loaded. This means that it's not possible to have direct calls between methods. Instead, calls between methods go through small pieces of generated code called PLT (Program Linkage Table) entries, which transfer control to the runtime which loads the called method before executing it. For methods compiled by LLVM, the method entry contains a call to the runtime which initializes the method.
In full-aot mode, the AOT compiler needs to emit all the trampolines which will be used at runtime. This is done in the following way:
aot argument set
to TRUE, then saves the returned native code into the AOT image, along with some relocation information like the
GOT slots used by the trampolines.The runtime might require an unbounded number of certain trampolines, but the AOT image can only contain a fixed
number of them. To solve this problem, on some platforms (IOS), it's possible to have infinite trampolines. This is
implemented by emitting a different version of these trampolines which reference their corresponding data using
relative addressing. At runtime, a page of these trampolines is mapped using mmap next to a writable page
which contains their corresponding data. The same page of trampolines is mapped multiple times at multiple
addresses.
It's possible to use the AOT compiler to target a platform different than the host. This requires a separate cross compiler
build of the runtime.
The generated code depends on offsets inside runtime structures like MonoClass/MonoVTable etc. which could
differ between the host and the target. This is handled by having a tool called the offsets-tool, which is a python
script which uses the clang python interface to compute and emit a C header file containing these offsets. The header
file is passed as a cmake argument during the runtime build. Inside the runtime code, the MONO_STRUCT_OFFSET
C macro reads the data from the offsets file to produce the offset corresponding to the target platform.