Xamarin.Mac AOT Prototype

ChrisHamonsChrisHamons USXamarin Team Xamurai
edited December 2016 in Xamarin.Mac

One possible feature for C10 is enabling a "Hybrid AOT" to improve launch performance.

You can read about it here: https://medium.com/@donblas/ahead-of-time-compilation-with-xamarin-mac-ceb6fb1d0a3c#.q5vopwz4l

And test it out by downloading this build from master:

https://dl.xamarin.com/xamarin-mac-aot-prototype/xamarin.mac-3.1.0.126.pkg

And adding this additional mmp arguments to your project:

--experimental-xm-aot

Please post feedback and bug reports in this thread.

«1

Posts

  • FelixDeimelFelixDeimel ATMember ✭✭
    edited February 13

    So today I wanted to give this a shot.
    Didn't get very far unfortunately as my build fails with:
    System.ComponentModel.Win32Exception (0x80004005): ApplicationName='/usr/local/bin/mono64', CommandLine='--aot=hybrid /Users/fx/Development/Mono/XamarinMacTest/XamarinMacTest/bin/Debug/XamarinMacTest.app/Contents/MonoBundle/System.Core.dll', CurrentDirectory='', Native error= Cannot find the specified file

    I checked that file it complains about actually exists.
    Reproduction steps are as simple as it gets:

    • New 10.12.3 machine
    • Installed Mono 4.8.0.483
    • Installed Xamarin.Mac 3.1.0.126
    • Installed Xamarin Studio 6.1.5
    • Created new Xamarin.Mac project
    • Added --experimental-xm-aot to mmp arguments
    • Build

    Without --experimental-xm-aot it builds just fine.

  • ChrisHamonsChrisHamons USXamarin Team Xamurai
    edited February 13

    So likely the problem is '/usr/local/bin/mono64'. The way I was handling locating the correct mono was rather wrong.

    Since I posted this prototype, I have drastically improved things. Off the top of my head:

    • Added ability to select which assemblies are compiled, if we should use hybrid or standard AOT
    • Rewritten everything, with full unit test coverage and full tests passes of our test suits AOT'ed
    • Fixed a number of bugs.

    Check out a build from master and let me know how it goes:

    https://jenkins.mono-project.com/view/Xamarin.MaciOS/job/xamarin-macios-builds-master/

    The syntax did change. /Library/Frameworks/Xamarin.Mac.framework/Commands/mmp has the full details but you likely wants

    --aot=all or --aot=core

    Sorry for not updating this post pointing to the newer builds.

  • FelixDeimelFelixDeimel ATMember ✭✭

    Thx! That's much better! So using this build I was able to get the sample project to compile and run.
    It also enabled me to use Instruments and have actual method names show up instead of memory addresses. Great!

    But, a very simple sample project compiled with --aot=all now compiles down to a whopping 233MB! After removing all dSYM files we're looking at 134MB which is still a whole lot for just a sample project.
    Is there any way to (drastically) reduce this?

    Also, is there a way to statically compile the individual libraries and have them linked into the main binary? So that essentially the MonoBundle folder is not required anymore? If that's not possible, is there at least a way to get rid of the .dll files in the app bundle?

  • ChrisHamonsChrisHamons USXamarin Team Xamurai
    edited February 14

    Let me answer each question in turn:

    • Yes, AOT'ed code can be large. We're generating almost every possible code path. And since you are shipping all of Xamarin.Mac / System / mscorlib, which are not small libraries, you get large assemblies.
    • The best way of cutting down application size is to swap to the Mobile target framework and to enable linking. That way instead of shipping say the 740 types in System.dll, with all of that code, you ship what you use. Unfortunately, due to complications with reflection and System.Configuration.dll usage, linking is restricted to Mobile as it is only safe there (I can get into details if you desire).
    • The next best thing you can do is restrict which assemblies you AOT. If you notice, if you use --all, every single assembly you use is AOT'ed. Since we're not on iOS, AOT isn't required so we can pick and choose to get the most bang for your buck. You can do this by passing in different arguments to mmp via --aot. Often Xamarin.Mac.dll/System.dll/mscorlib.dll are enough to get most of the benefit.
    • The syntax for the AOT command is as follows (or if you prefer code):

      • core (Xamarin.Mac.dll, System.dll, mscorlib.dll)
      • all (Everyhing)
      • sdk (All non-user code
      • You then can add in hybrid aot via "|hybrid". Example - --aot=code|hybrid. Hybrid AOT generates slower code but allows you to strip your IL, which is not the common use case.
      • You then can explicitly include/exclude assemblies with a comma delimited list of +Assembly.dll or -Assembly.ddl - Example: --aot=code,+Foo.dll,-System.dll
    • Removing the managed assemblies is not safe. You might get away with it for awhile, but here's the problem. Unless you use --aot=full (which I don't have support for here), our runtime sometimes "looks up and JITs" things from your managed code. The details are fiendishly complicated, but think generics for example. List<int> vs List<object>.

  • DaveHuntDaveHunt USMember ✭✭✭✭✭

    I would think any reflection would break without the dlls as well.

  • FelixDeimelFelixDeimel ATMember ✭✭

    Thx! Lots of useful information there!

    Switching to the mobile target framework is unfortunately not possible for me at the moment. However, since I'm mainly interested in AOTing my own assemblies I can specify --aot=+MyAssembly, save a lot of space and still have my assemblies AOT'ed. Perfect!

    Regarding removing managed assemblies: I'm not really interested in completely getting rid of them but I'd like them to be embedded either into the main executable or into some kind of "fat" binary. Basically more like hiding them from prying eyes than completely throwing them away.

    Two more questions:
    1) Is it possible to call into AOT'ed libraries from libraries dynamically loaded at runtime (plugins) and if so are there any gotchas?
    2) How can I manually AOT a library that is not included in the executable project and will the AOT'ed part be picked up correctly if loaded dynamically at runtime?

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    Right now we do not have any way of hiding the contents of MonoBundle. Beyond rewriting the entire launch process (which I can point you to the code if you really want to try), our code assumes there will be assemblies there.

    If you are rather concerned about it, you could use hybrid and strip the IL from the assemblies. That will make your code slower for sure, and possibly break a few use cases that you likely aren't using, but it could be hammered out if you want to go down that route.

    Let me give you a rough idea how mono deals with AOT in the non-full case (like Xamarin.Mac instead of Xamarin.iOS). It's not 100% accurate, but should give you a rough idea what's going on.

    • You double click your bundle
    • macOS finds the native executable inside your bundle and starts it
    • That launcher does some homework and starts up mono, pointing to your entry point exe
    • Mono starts up, finds the exe, and looks for the AOT'ed code in a dylib next to it.
    • If you AOT'ed it, it loads it and skips JITing it (except for the cases where it JITs things like generic trampolines later as I mentioned)
    • For each assembly dependency, go find it in your MonoBundle and load it. If there is a dylib from AOT, use it.

    Thus:

    • From your perspective, you are loading just normal assemblies. Mono is noticing the AOT'ness and using it if it is there. This is different that "full" AOT, where the JIT is disabled, but that's the Xamarin.iOS use case.
    • If you want to AOT things yourself, you can look at the build output to see the mono invocations or the code is here.
  • FelixDeimelFelixDeimel ATMember ✭✭

    Okay, so I was able to manually AOT a library of mine with mono64 --aot /Path/To/Assembly.dll. Dylib is created and is 64 bit, all good.
    I then went ahead and loaded this assembly at runtime using:

    Assembly pluginAssembly = Assembly.LoadFrom ("/Path/To/XamMacClassLibrary.dll");
    
    string typeName = "XamMacClassLibrary.PluginTest";
    
    object pluginInstance = pluginAssembly.CreateInstance (typeName);
    Type pluginType = pluginAssembly.GetType (typeName);
    MethodInfo doCalculationMethodInfo = pluginType.GetMethod ("DoCalculation");
    
    doCalculationMethodInfo.Invoke (pluginInstance, new object [] { });
    

    This works and the method I invoke is successfully executed.
    However, when looking at the call stack in instruments, I don't get symbolification for the invoked method.
    If I instead reference the plugin library directly in my Xamarin.Mac app I can see the method name in Instruments just fine.
    So this leads me to believe that the AOT'ed part is not loaded when using Assembly.LoadFrom and similar methods.

    Is there any way to get mono to also load the AOT bits when loading assemblies at runtime?

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    Hmm. Sounds like a possible bug. There have been a few of those in the past to my knowledge.

    Could you launch your application from the command line with MONO_LOG_LEVEL=debug set.

    Like

    MONO_LOG_LEVEL=debug Foo.app/Contents/MacOS/Foo

    and gist/pastebin me the trace. I'll have the runtime folks take a look.

  • FelixDeimelFelixDeimel ATMember ✭✭

    Here you go!
    I guess the problem here are these two lines:

    2017-02-14 20:04:45.113 XamarinMacAOTTest[12508:2320322] info: AOT: module /Users/fx/Development/Mono/XamarinMacAOTTest/XamMacClassLibrary/bin/Release/XamMacClassLibrary.dll.dylib is unusable (GUID of dependent assembly mscorlib doesn't match (expected '12E050E5-B3D3-4326-A1B4-E2E7624E75DA', got '4913533B-0843-4F6D-900C-52070E5CCBCD').
    2017-02-14 20:04:45.113 XamarinMacAOTTest[12508:2320322] info: AOT: Module /Users/fx/Development/Mono/XamarinMacAOTTest/XamMacClassLibrary/bin/Release/XamMacClassLibrary.dll is unusable because a dependency is out-of-date.
    

    Like previously mentioned I AOT'ed the dynamically loaded library with mono64 --aot /Path/To/Assembly.dll. Are additional parameters required maybe?

  • FelixDeimelFelixDeimel ATMember ✭✭

    Yep, the issue here appears to be the way I AOT'ed the assembly. If I instead reference it in the app project and include it in the --aot arguments, then copy the generated dylib and remove the reference and use my original dynamic assembly loading code everything works perfectly.

    So the question now is, how do I properly AOT an assembly manually without having it included in an app project?

  • FelixDeimelFelixDeimel ATMember ✭✭

    Additional question: How can I load the AOT bits when using Assembly.Load(byte[])?
    I'm asking because I'm having my own proprietary "fat" binary format for plugins which essentially rules out loading assemblies from disk using Assembly.LoadFrom or Assembly.LoadFile.

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    So:

    2017-02-14 20:04:45.113 XamarinMacAOTTest[12508:2320322] info: AOT: module /Users/fx/Development/Mono/XamarinMacAOTTest/XamMacClassLibrary/bin/Release/XamMacClassLibrary.dll.dylib is unusable (GUID of dependent assembly mscorlib doesn't match (expected '12E050E5-B3D3-4326-A1B4-E2E7624E75DA', got '4913533B-0843-4F6D-900C-52070E5CCBCD').

    tells me what the problem is, which is a step I forgot.

    You need to set MONO_PATH to point to the BCL your application is using.

    Mobile Target Framework - /Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/Xamarin.Mac
    XM 4.5 - /Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/4.5

  • FelixDeimelFelixDeimel ATMember ✭✭

    Yep, setting the MONO_PATH did the trick!

    Any thoughts about my other question regarding loading AOT modules when using Assembly.Load(byte[])?

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    I had a detailed discussion with one of the awesome runtime engineers and have answers for your second question on Assembly.Load(byte[]):

    • What you are suggesting is not done out of the box. How could mono know from the byte [] where to look?
    • However, there is an embedding API mono_aot_register_module (https://github.com/mono/mono/blob/master/mono/mini/mini.h#L2526) that you can use to tell mono - "Hey, add this AOT info to your lookup table".
    • The thing you pass to mono_aot_register_module is a struct (https://github.com/mono/mono/blob/master/mono/mini/mini.h#L208) that you can resolve via dlsym from the dylib generated by AOTing
    • Since we statically link in the runtime to your application, you should be able to resolve mono_aot_register_module via a p/invoke into "__Internal"
    • The runtime guy thinks that calling mono_aot_register_module from running C# code might be ok.
    • You are completely in the land of "I hope this works, but if it breaks you get to keep the pieces" behavior with this.
  • FelixDeimelFelixDeimel ATMember ✭✭

    Alright, so I tried to implement this using this code.
    dlopen succeeds and so does dlsym but when I go ahead and call mono_aot_register_module my app crashes with this crashlog.

    Any ideas?

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    So it turns out there is one missing piece:

    https://bugzilla.xamarin.com/show_bug.cgi?id=52535

    • mono_aot_file_info is not the correct symbol, the one that we are looking for starts with mono_aot_module_
    • However, you won't find that symbol in your dylib
    • We are currently only emitting it when you generate a static lib version of dylib info, like Xamarin.iOS
    • I filed that bug so that the runtime teams can fix that oversight at some point.

    Until then, you won't be able to due AOT of assemblies you load via Load (Byte[]). Sorry.

  • FelixDeimelFelixDeimel ATMember ✭✭

    Any news on this?

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    As the bug in question (https://bugzilla.xamarin.com/show_bug.cgi?id=52535) has not changed there is not much to say.

    I'll double check that it's filed against the right group, etc, but until they you won't be able to take advantage of AOT with dynamically loaded plugins.

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    One of the runtime engineers suggested trying:

    mono --aot=static foo.dll

    Let me know here (or post to the bug) if that changes things.

  • FelixDeimelFelixDeimel ATMember ✭✭

    Not sure what exactly changed but when AOTing one of my libraries with the current stable Xamarin stack I get the following error:
    Failed to load method 0x6000010 from '/PATH/TO/LIBRARY.dll' due to Could not load file or assembly 'Xamarin.Mac, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065' or one of its dependencies. assembly:Xamarin.Mac, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 type:<unknown type> member:<none>. Run with MONO_LOG_LEVEL=debug for more information.

    Doesn't matter if I include =static or not. I also set the MONO_PATH before attempting to run this.
    If I attempt to AOT a library that doesn't reference Xamarin.Mac it completes without error but instead of a .dylib or .a file I get a .o file which I'm not sure what to do with?!

  • FelixDeimelFelixDeimel ATMember ✭✭

    Also just tried using the latest build from master (3.5.0.22) with the same results.

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    You could run your application with:

    MONO_LOG_LEVEL=debug MONO_LOG_MASK=asm Foo.app/Contents/MacOS/Foo

    and see what the loading issue is.

    But in general, AOTing requires all of the dependent libraries in one location. The XM AOT toolchain roughly does:

    MONO_PATH=. mono --aot *.{dll,exe}

    in your MonoBundle folder.

    Could you post the commands you are running, or a small sample?

  • FelixDeimelFelixDeimel ATMember ✭✭

    I'm doing this:
    MONO_PATH=/Library/Frameworks/Xamarin.Mac.framework/Versions/Current/lib/mono/4.5 mono64 --aot=static /Path/To/Library.dll

  • FelixDeimelFelixDeimel ATMember ✭✭

    Putting Xamarin.Mac.dll in the same folder as my Library.dll worked.
    But now I'm still confused as to what to do with the .o file that gets created due to the use of the "static" aot flag?!

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    I believe you are to link them into your launcher application. Try playing with adding additional mmp arguments (in your project settings, mac build) to ask the linker to add them in.

    Something roughly like:

    --link_flags="PATH/Foo.o"

  • FelixDeimelFelixDeimel ATMember ✭✭

    Well, I can't link them into the launcher app since those libraries will be loaded at runtime.

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    Well then link them into a dylib and load that first using dlopen?

  • FelixDeimelFelixDeimel ATMember ✭✭

    I could try that. But would they get picked up by the mono/Xamarin.Mac runtime automatically when dlopening or would I have to call something like mono_aot_register_module after dlopen?

  • ChrisHamonsChrisHamons USXamarin Team Xamurai
    edited April 13

    I'm not sure. ¯\_(ツ)_/¯

    This entire thing is both firmly untested and something I've never tried myself.

    My totally wild guess is that if you have dlopen'ed the native bit it before loading the assembly it might work.

  • FelixDeimelFelixDeimel ATMember ✭✭

    Fair enough! ;) Well, I'll give it a shot in a bit.

  • FelixDeimelFelixDeimel ATMember ✭✭

    So I just gave this a shot.
    Here's what I did:

    • Created the AOT module using mono64 --aot=static /Path/To/Library.dll
    • Linked the resulting .o file into a dylib using Xcode
    • Verified that the AOT bits are indeed there by inspecting the dylib using Hopper
    • Loaded the AOT dylib using dlopen before loading the managed dll
    • Attached Instruments to running application
    • Called one of the methods in the AOTed library

    Unfortunately, the call still doesn't show with its method name in Instruments which leads me to believe that the AOT bits haven't been loaded into the mono runtime.
    I also tried to register the AOT bits using dlsym and mono_aot_register_module but dlsym always returns a zero pointer for mono_aot_file_info.

    Any more pointers?

  • ZoltanVargaZoltanVarga USXamarin Team Xamurai

    Hi,

    You need to link all the .o files produced by mono --aot=static into the native executable, then register the aot modules by calling
    mono_aot_register_module () in your startup code with the symbol name printed by the aot compiler, i.e. when aot-ing hello.exe, the aot compiler will print:

    Linking symbol: '_mono_aot_module_hello_info'.

    Then call mono_aot_register_module () using:

    extern void *mono_aot_module_hello_info;

    mono_aot_register_module (mono_aot_module_hello_info);

  • FelixDeimelFelixDeimel ATMember ✭✭

    @ZoltanVarga
    Like previously mentioned, I can't link the AOT modules into my executable as I'm loading the assemblies I want to AOT at runtime. This is a plugin system I'm building.

    However, I tried your suggestion of dlsym'ing mono_aot_module_hello_info (of course adapted to match my library name) and calling mono_aot_register_module on it after dlopen'ing the dylib I created that just has the AOT .o linked in.
    dlsym works on this symbol but the call to mono_aot_register_module fails with the following error message: error: * Assertion at /Users/builder/data/lanes/4466/a04678c2/source/xamarin-macios/external/mono/mono/mini/aot-runtime.c:2300, condition 'info->version == MONO_AOT_FILE_VERSION' not met

  • ZoltanVargaZoltanVarga USXamarin Team Xamurai

    That usually means the aot image was not created by the same version of mono which is used at runtime.

  • FelixDeimelFelixDeimel ATMember ✭✭

    @ZoltanVarga Well I only have one mono runtime installed.

  • FelixDeimelFelixDeimel ATMember ✭✭

    Just to be sure, I just completely removed /Library/Frameworks/Mono.framework and re-installed all Xamarin related stuff from the beta channel. Then I rebuilt my libraries, the main executable and re-generated the AOT bits and dylib. Same issue.

  • ChrisHamonsChrisHamons USXamarin Team Xamurai

    Well you don't only have one mono installed. Unless you select the "system mono" option in the target framework page (https://developer.xamarin.com/guides/mac/advanced_topics/target-framework/), we do not use system mono.

    What target framework are you using?

  • FelixDeimelFelixDeimel ATMember ✭✭

    My app project uses Xamarin.Mac .NET 4.5 Framework while the library project that I want to AOT uses Mono / .NET 4.5. Note that this library project is not a Xamarin.Mac library but a regular class library.

  • FelixDeimelFelixDeimel ATMember ✭✭

    To make sure it's not related to differing target frameworks I just created a new Xamarin.Mac class library and tested the whole AOT process with this instead. Same result.

«1
Sign In or Register to comment.