Ahead of Time Compilation in Mono

by Miguel de Icaza

A few years ago we modified the Mono code generation engine from being merely a Just-in-Time compiler for managed code to be also a batch compiler that could be used to batch compile code in advance.

We call this batch compilation process "Ahead of Time Compilation" or AOT.

There are many reasons for supporting AOT compilation: reduced startup time, reduced overall memory usage and the possibility of running better optimized code.

This batch compilation is typically done by invoking the mono command with the --aot flag on a managed assembly, like this:

 $ mono --aot hello.exe
 $ ls -l hello.exe.*
 -rwxr-xr-x 1 miguel users  3584 2006-08-15 12:38 hello.exe
 -rwxr-xr-x 1 miguel users 10769 2006-08-17 00:05 hello.exe.so


The .exe file in the above listing contains the managed code in CIL format, while the hello.exe.so is an ELF file that contains the pre-compiled code.

Today, the .so file only contains the pre-compiled code but does not contain any of the additional metadata that is present on the .exe file. To run the application it is necessary to have the original assembly (.exe or .dll) around as it contains information that is not replicated on the executable.

To execute the code, you must still invoke Mono with the original parameters, for instance:

 $ mono hello.exe


The runtime will take care of probing whether there is a native pre-compiled image, and if the image is valid, it will use the pre-compiled code from the shared object instead of JITing the code as it goes.

As I mentioned before, there were an array of motivations for implementing AOT in Mono. Initially we hoped to reduced application startup time as the JIT had less work to do. In practice, the Mono JIT was fast enough that the JIT time was never much of an issue, even with the last batch of optimizations that were turned on in Mono 1.1.16, the startup speed is not even noticeable. In addition, there is a slight memory gain by not executing code in the JIT in the first place, so there was a small memory improvement as well.

Heavier optimizations that exist today, and heavier optimizations that we are developing will make the use of AOT more important, as those code generation optimizations are slower to have enabled by default on the Just-in-Time compilation stage.

This is what the original design of the AOT file format was designed to cope with: startup time.

In the last few months Zoltan has been working on adapting AOT for another task: overall memory reduction.

As developers start to run a handful of Mono applications on a desktop, we want to minimize the duplicated code in memory. To do this, we had to address a number of problems with the original AOT design.

The original AOT design had poor multi-process page sharing capabilities, as the JIT had to fix-up the mapped AOT code in memory. The native code was shared across multiple processes by using mmap() to map the native code into each process, but as the fix-ups were applied to the mapped code, the numbers of pages that could be shared across processes decreased.

The new design by Zoltan for the AOT generated code is very similar to the ELF shared libraries file format. It uses a couple of tables for data and code (Global Offset Table a slightly modified Program Linkage Table tuned for Mono compilation) which are the only pieces that are modified.

 $ mono --aot /mono/lib/mono/1.0/mscorlib.dll
 Mono Ahead of Time compiler - compiling assembly /mono/lib/mono/1.0/mscorlib.dll
 Code: 1632832 Info: 105844 Ex Info: 46601 Class Info: 39399 PLT: 4127 GOT: 55756
 Executing the native assembler: as /tmp/mono_aot_jiqD57 -o /tmp/mono_aot_jiqD57.o
 Executing the native linker: ld -shared -o /mono/lib/mono/1.0/mscorlib.dll.so 
 Compiled 11050 out of 11051 methods (99%)
 1 methods contain absolute addresses (0%)
 0 methods contain wrapper references (0%)
 0 methods contain lmf pointers (0%)
 0 methods have other problems (0%)
 Methods without GOT slots: 7322 (66%)
 Direct calls: 11350 (53%)
 GOT slot distribution:
         methodconst: 25
         switch: 167
         class: 2176
         field: 165
         vtable: 3241
         sflda: 2356
         ldstr: 3003
         ldtoken: 14
         type_from_handle: 546
         iid: 414
         adjusted_iid: 1831

By reducing the pieces that have to be patched to a small set of pages, the rest of the code can be shared across multiple Mono processes.

Now, with the same set of optimization flags, AOT code is slightly slower than JITed code because it has to be position independent and has to go through a few extra indirections that JIT code does not have to do (to maximize the sharing across applications).

The big news is that the new file format design has finally reached the point where using AOT code on day to day operations is faster than using JIT code. Building our core class libraries with our C# compiler with AOT code now takes less time than using the JIT-tuned version.

The mscorlib compilation consists of 262,261 lines of C# source code, the results are:

JIT time: 4.34 seconds

AOT time: 4.21 seconds

Today AOT is only available on the x86 and x86-64 ports of Mono, this seems to be good enough and where most of the desktop deployments will be.

Now, one common question we get is: if we already generate native code, how come you still need Mono to run your applications; or the variant of the question: can I produce statically generated executables that only contain native code?

It is currently not possible to generate merely a native code file because Mono still needs to extract the metadata from the original assembly file (the .dll or the .exe file), information that we do not encode in the AOT file format. In addition the generated image still requires the Mono runtime (for GC, Just-in-Time compilation services, reflection and a handful of extra features).

Mono does support a special mode that turns an assembly into a static binary, but it does not use native code, it still uses the JIT engine. This is done using the mkbundle command.

It is still possible to turn off the use of AOT files in a per-run basis, this can be done by passing the -O=-aot command line option to the runtime. In the upcoming version of Mono, all of our wrapper scripts will also support the MONO_OPTIONS variable to control this.

We are hoping to ship a script that will AOT all the libraries and executables that ship as part of Mono, or to disable all the AOT executables on demand in a future release of Mono.

Don asked me at the Lang.NET Symposium how we handled generics, and I have no idea.

Posted on 17 Aug 2006