[mono] Add iOS sample (AOT, arm64) (#33633)
authorEgor Bogatov <egorbo@gmail.com>
Wed, 18 Mar 2020 20:51:47 +0000 (23:51 +0300)
committerGitHub <noreply@github.com>
Wed, 18 Mar 2020 20:51:47 +0000 (23:51 +0300)
* Add iOS sample

src/mono/netcore/sample/iOS/.gitignore [new file with mode: 0644]
src/mono/netcore/sample/iOS/CMakeLists.txt [new file with mode: 0644]
src/mono/netcore/sample/iOS/Makefile [new file with mode: 0644]
src/mono/netcore/sample/iOS/Plist.in [new file with mode: 0644]
src/mono/netcore/sample/iOS/Program.cs [new file with mode: 0644]
src/mono/netcore/sample/iOS/Program.csproj [new file with mode: 0644]
src/mono/netcore/sample/iOS/main.m [new file with mode: 0644]
src/mono/netcore/sample/iOS/runtime.h [new file with mode: 0644]
src/mono/netcore/sample/iOS/runtime.m [new file with mode: 0644]

diff --git a/src/mono/netcore/sample/iOS/.gitignore b/src/mono/netcore/sample/iOS/.gitignore
new file mode 100644 (file)
index 0000000..8a7ad91
--- /dev/null
@@ -0,0 +1,4 @@
+bin/
+xcode/
+*.o
+*.dll
\ No newline at end of file
diff --git a/src/mono/netcore/sample/iOS/CMakeLists.txt b/src/mono/netcore/sample/iOS/CMakeLists.txt
new file mode 100644 (file)
index 0000000..5a6aaef
--- /dev/null
@@ -0,0 +1,47 @@
+cmake_minimum_required(VERSION 3.14.5)
+
+project(HelloiOS)
+
+# make sure "make all" is executed first
+file(GLOB DLLS *.dll)
+file(GLOB DLLS_AOT *.dll.o)
+
+set(APP_RESOURCES
+    ${DLLS}
+)
+
+# add the executable
+add_executable(
+    HelloiOS
+    main.m
+    runtime.h
+    runtime.m
+    ${APP_RESOURCES}
+)
+
+include_directories("../../../../../artifacts/bin/mono/iOS.${MONO_ARCH}.${MONO_CONFIG}/include/mono-2.0")
+
+set_target_properties(HelloiOS PROPERTIES
+    MACOSX_BUNDLE TRUE
+    MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Plist.in
+    XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
+    XCODE_ATTRIBUTE_DEAD_CODE_STRIPPING "NO"
+    RESOURCE "${APP_RESOURCES}"
+)
+
+# FIXME: `XCODE_ATTRIBUTE_DEAD_CODE_STRIPPING` should not be NO
+# investigate why `PALEXPORT` doesn't preserve symbols
+
+target_link_libraries(
+    HelloiOS
+    "-framework Foundation"
+    "-framework UIKit"
+    "-framework GSS"
+    "-lz"
+    "-liconv"
+    "-force_load ../../../../../artifacts/bin/mono/iOS.${MONO_ARCH}.${MONO_CONFIG}/libmono.a"
+    "-force_load ../../../../../artifacts/bin/native/netcoreapp5.0-iOS-${MONO_CONFIG}-${MONO_ARCH}/libSystem.IO.Compression.Native.a"
+    "-force_load ../../../../../artifacts/bin/native/netcoreapp5.0-iOS-${MONO_CONFIG}-${MONO_ARCH}/libSystem.Native.a"
+    "-force_load ../../../../../artifacts/bin/native/netcoreapp5.0-iOS-${MONO_CONFIG}-${MONO_ARCH}/libSystem.Security.Cryptography.Native.Apple.a"
+    ${DLLS_AOT}
+)
\ No newline at end of file
diff --git a/src/mono/netcore/sample/iOS/Makefile b/src/mono/netcore/sample/iOS/Makefile
new file mode 100644 (file)
index 0000000..4113101
--- /dev/null
@@ -0,0 +1,65 @@
+MONO_CONFIG=Debug
+ARTIFACTS_BIN=../../../../../artifacts/bin/
+ARTIFACTS_BCL=$(ARTIFACTS_BIN)runtime/netcoreapp5.0-iOS-$(MONO_CONFIG)-arm64
+ARTIFACTS_MONO=$(ARTIFACTS_BIN)/mono/iOS.arm64.$(MONO_CONFIG)
+
+DOTNET := $(shell cd ../../ && bash init-tools.sh | tail -1)
+SYSROOT := $(shell xcrun --sdk iphoneos --show-sdk-path)
+
+# once a new library is added here (e.g. System.Console.dll) it should also be 
+# added in mono_ios_register_modules() (runtime.m) and both lib.dll and lib.dll.o 
+# should be added to xcodeproj 
+all: prepare
+       make aot-lib LIB=$(ARTIFACTS_MONO)/System.Private.CoreLib.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Runtime.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Runtime.Extensions.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Collections.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Core.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Threading.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Threading.Tasks.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Linq.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Memory.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Runtime.InteropServices.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Text.Encoding.Extensions.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/Microsoft.Win32.Primitives.dll
+       make aot-lib LIB=$(ARTIFACTS_BCL)/System.Console.dll
+       make Program.dll.o
+
+# recompile Program.cs AOT
+Program.dll.o: bin/Program.dll Makefile
+       make aot-lib LIB=bin/Program.dll
+
+# we need to copy some BCL libs to ARTIFACTS_MONO
+# to be able to aot other bcl libs
+prepare:
+       cp $(ARTIFACTS_BCL)/System.Memory.dll $(ARTIFACTS_MONO)
+       cp $(ARTIFACTS_BCL)/System.Collections.dll $(ARTIFACTS_MONO)
+       cp $(ARTIFACTS_BCL)/System.Threading.dll $(ARTIFACTS_MONO)
+       cp $(ARTIFACTS_BCL)/System.Threading.Thread.dll $(ARTIFACTS_MONO)
+       cp $(ARTIFACTS_BCL)/System.Runtime.dll $(ARTIFACTS_MONO)
+       cp $(ARTIFACTS_BCL)/System.Runtime.InteropServices.dll $(ARTIFACTS_MONO)
+       cp $(ARTIFACTS_BCL)/System.Text.Encoding.Extensions.dll $(ARTIFACTS_MONO)
+       cp $(ARTIFACTS_BCL)/Microsoft.Win32.Primitives.dll $(ARTIFACTS_MONO)
+       cp $(ARTIFACTS_BCL)/System.Console.dll $(ARTIFACTS_MONO)
+
+bin/Program.dll: Program.cs
+       $(DOTNET) build -c Debug Program.csproj
+
+aot-lib:
+       DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 MONO_PATH=$(ARTIFACTS_MONO) \
+       $(ARTIFACTS_MONO)/cross/./mono-aot-cross -O=gsharedvt,float32 --nollvm --debug \
+       --aot=mtriple=arm64-ios,static,asmonly,direct-icalls,no-direct-calls,dwarfdebug,full $(LIB) && \
+       clang -isysroot $(SYSROOT) -miphoneos-version-min=10.1 -arch arm64 -c $(LIB).s
+       cp $(LIB) $(notdir $(LIB))
+
+# generate an xcode project
+xcode: all
+       cmake -S. -BXcode -GXcode \
+       -DCMAKE_SYSTEM_NAME=iOS \
+       -DCMAKE_OSX_ARCHITECTURES=arm64 \
+       -DCMAKE_OSX_DEPLOYMENT_TARGET=10.1 \
+       -DCMAKE_INSTALL_PREFIX=`pwd`/_install \
+       -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=YES \
+       -DCMAKE_IOS_INSTALL_COMBINED=YES \
+       -DMONO_CONFIG=$(MONO_CONFIG) \
+       -DMONO_ARCH=arm64
\ No newline at end of file
diff --git a/src/mono/netcore/sample/iOS/Plist.in b/src/mono/netcore/sample/iOS/Plist.in
new file mode 100644 (file)
index 0000000..8934e53
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>en-US</string>
+       <key>CFBundleExecutable</key>
+       <string>HelloiOS</string>
+       <key>CFBundleIdentifier</key>
+       <string>net.dot.HelloiOS</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundleName</key>
+       <string>HelloiOS</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleShortVersionString</key>
+       <string>1.0</string>
+       <key>CFBundleVersion</key>
+       <string>1</string>
+       <key>LSRequiresIPhoneOS</key>
+       <true/>
+       <key>UIApplicationSceneManifest</key>
+       <dict>
+               <key>UIApplicationSupportsMultipleScenes</key>
+               <false/>
+       </dict>
+       <key>UIRequiredDeviceCapabilities</key>
+       <array>
+               <string>arm64</string>
+       </array>
+       <key>UISupportedInterfaceOrientations</key>
+       <array>
+               <string>UIInterfaceOrientationPortrait</string>
+       </array>
+       <key>UISupportedInterfaceOrientations~ipad</key>
+       <array>
+               <string>UIInterfaceOrientationPortrait</string>
+       </array>
+</dict>
+</plist>
diff --git a/src/mono/netcore/sample/iOS/Program.cs b/src/mono/netcore/sample/iOS/Program.cs
new file mode 100644 (file)
index 0000000..6e3571d
--- /dev/null
@@ -0,0 +1,50 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+
+// it's not part of the BCL but runtime needs it for native-to-managed callbacks in AOT
+// To be replaced with NativeCallableAttribute
+public class MonoPInvokeCallbackAttribute : Attribute
+{
+    public MonoPInvokeCallbackAttribute(Type delegateType) { }
+}
+
+public static class Program
+{
+    // Defined in main.m
+    [DllImport("__Internal")]
+    private extern static void ios_set_text(string value);
+
+    [DllImport("__Internal")]
+    private extern static void ios_register_button_click(Action action);
+
+    private static Action buttonClickHandler = null;
+
+    private static int counter = 0;
+
+    // Called by native code, see main.m
+    [MonoPInvokeCallback(typeof(Action))]
+    private static async void OnButtonClick()
+    {
+        ios_set_text("OnButtonClick! #" + counter++);
+    }
+
+    public static async Task Main(string[] args)
+    {
+        // Register a managed callback (will be called by UIButton, see main.m)
+        // Also, keep the handler alive so GC won't collect it.
+        ios_register_button_click(buttonClickHandler = OnButtonClick);
+
+        const string msg = "Hello World!\n.NET 5.0";
+        for (int i = 0; i < msg.Length; i++)
+        {
+            // a kind of an animation
+            ios_set_text(msg.Substring(0, i + 1));
+            await Task.Delay(100);
+        }
+
+        // TODO: https://github.com/dotnet/runtime/issues/33667
+        Console.WriteLine("Done!");
+    }
+}
\ No newline at end of file
diff --git a/src/mono/netcore/sample/iOS/Program.csproj b/src/mono/netcore/sample/iOS/Program.csproj
new file mode 100644 (file)
index 0000000..0b65c42
--- /dev/null
@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <OutputPath>bin</OutputPath>
+    <TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
+  </PropertyGroup>
+
+</Project>
diff --git a/src/mono/netcore/sample/iOS/main.m b/src/mono/netcore/sample/iOS/main.m
new file mode 100644 (file)
index 0000000..d3c6881
--- /dev/null
@@ -0,0 +1,77 @@
+#import <UIKit/UIKit.h>
+#import "runtime.h"
+
+@interface ViewController : UIViewController
+@end
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate>
+@property (strong, nonatomic) UIWindow *window;
+@property (strong, nonatomic) ViewController *controller;
+@end
+
+@implementation AppDelegate
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+    self.controller = [[ViewController alloc] initWithNibName:nil bundle:nil];
+    self.window.rootViewController = self.controller;
+    [self.window makeKeyAndVisible];
+    return YES;
+}
+@end
+
+UILabel *label;
+void (*clickHandlerPtr)(void);
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    
+    label = [[UILabel alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+    label.textColor = [UIColor greenColor];
+    label.font = [UIFont boldSystemFontOfSize: 30];
+    label.numberOfLines = 2;
+    label.textAlignment = NSTextAlignmentCenter;
+    [self.view addSubview:label];
+    
+    UIButton *button = [UIButton buttonWithType:UIButtonTypeInfoDark];
+    [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
+    [button setFrame:CGRectMake(50, 300, 200, 50)];
+    [button setTitle:@"Click me" forState:UIControlStateNormal];
+    [button setExclusiveTouch:YES];
+    [self.view addSubview:button];
+
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+        mono_ios_runtime_init ();
+    });
+}
+-(void) buttonClicked:(UIButton*)sender
+{
+    if (clickHandlerPtr)
+        clickHandlerPtr();
+}
+
+@end
+
+// called from C# sample
+void
+ios_register_button_click (void* ptr)
+{
+    clickHandlerPtr = ptr;
+}
+
+// called from C# sample
+void
+ios_set_text (const char* value)
+{
+    NSString* nsstr = [NSString stringWithUTF8String:strdup(value)];
+    dispatch_async(dispatch_get_main_queue(), ^{
+        label.text = nsstr;
+    });
+}
+
+int main(int argc, char * argv[]) {
+    @autoreleasepool {
+        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+    }
+}
diff --git a/src/mono/netcore/sample/iOS/runtime.h b/src/mono/netcore/sample/iOS/runtime.h
new file mode 100644 (file)
index 0000000..ddddd96
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef runtime_h
+#define runtime_h
+
+void mono_ios_runtime_init (void);
+
+#endif /* runtime_h */
diff --git a/src/mono/netcore/sample/iOS/runtime.m b/src/mono/netcore/sample/iOS/runtime.m
new file mode 100644 (file)
index 0000000..79fbd4f
--- /dev/null
@@ -0,0 +1,287 @@
+#import <Foundation/Foundation.h>
+#include <mono/utils/mono-publib.h>
+#include <mono/utils/mono-logger.h>
+#include <mono/metadata/assembly.h>
+#include <mono/metadata/mono-debug.h>
+#include <mono/metadata/exception.h>
+#include <mono/jit/jit.h>
+
+#import <os/log.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+static os_log_t stdout_log;
+
+/* These are not in public headers */
+typedef unsigned char* (*MonoLoadAotDataFunc) (MonoAssembly *assembly, int size, void *user_data, void **out_handle);
+typedef void  (*MonoFreeAotDataFunc) (MonoAssembly *assembly, int size, void *user_data, void *handle);
+void mono_install_load_aot_data_hook (MonoLoadAotDataFunc load_func, MonoFreeAotDataFunc free_func, void *user_data);
+void mono_trace_init (void);
+void mono_gc_init_finalizer_thread (void);
+
+static char *bundle_path;
+
+const char *
+get_bundle_path (void)
+{
+    if (bundle_path)
+        return bundle_path;
+
+    NSBundle* main_bundle = [NSBundle mainBundle];
+    NSString* path = [main_bundle bundlePath];
+    bundle_path = strdup ([path UTF8String]);
+
+    return bundle_path;
+}
+
+static unsigned char *
+load_aot_data (MonoAssembly *assembly, int size, void *user_data, void **out_handle)
+{
+    *out_handle = NULL;
+
+    char path [1024];
+    int res;
+
+    MonoAssemblyName *assembly_name = mono_assembly_get_name (assembly);
+    const char *aname = mono_assembly_name_get_name (assembly_name);
+    const char *bundle = get_bundle_path ();
+
+    os_log_info (OS_LOG_DEFAULT, "Looking for aot data for assembly '%s'.", aname);
+    res = snprintf (path, sizeof (path) - 1, "%s/%s.aotdata", bundle, aname);
+    assert (res > 0);
+
+    int fd = open (path, O_RDONLY);
+    if (fd < 0) {
+        os_log_info (OS_LOG_DEFAULT, "Could not load the aot data for %s from %s: %s\n", aname, path, strerror (errno));
+        return NULL;
+    }
+
+    void *ptr = mmap (NULL, size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
+    if (ptr == MAP_FAILED) {
+        os_log_info (OS_LOG_DEFAULT, "Could not map the aot file for %s: %s\n", aname, strerror (errno));
+        close (fd);
+        return NULL;
+    }
+
+    close (fd);
+    os_log_info (OS_LOG_DEFAULT, "Loaded aot data for %s.\n", aname);
+    *out_handle = ptr;
+    return (unsigned char *) ptr;
+}
+
+static void
+free_aot_data (MonoAssembly *assembly, int size, void *user_data, void *handle)
+{
+    munmap (handle, size);
+}
+
+static MonoAssembly*
+load_assembly (const char *name, const char *culture)
+{
+    const char *bundle = get_bundle_path ();
+    char filename [1024];
+    char path [1024];
+    int res;
+
+    os_log_info (OS_LOG_DEFAULT, "assembly_preload_hook: %{public}s %{public}s %{public}s\n", name, culture, bundle);
+
+    int len = strlen (name);
+    int has_extension = len > 3 && name [len - 4] == '.' && (!strcmp ("exe", name + (len - 3)) || !strcmp ("dll", name + (len - 3)));
+
+    // add extensions if required.
+    strlcpy (filename, name, sizeof (filename));
+    if (!has_extension) {
+        strlcat (filename, ".dll", sizeof (filename));
+    }
+
+    if (culture && strcmp (culture, ""))
+        res = snprintf (path, sizeof (path) - 1, "%s/%s/%s", bundle, culture, filename);
+    else
+        res = snprintf (path, sizeof (path) - 1, "%s/%s", bundle, filename);
+    assert (res > 0);
+    
+    struct stat buffer;
+    if (stat (path, &buffer) == 0) {
+        MonoAssembly *assembly = mono_assembly_open (path, NULL);
+        assert (assembly);
+        return assembly;
+    }
+    return NULL;
+}
+
+static MonoAssembly*
+assembly_preload_hook (MonoAssemblyName *aname, char **assemblies_path, void* user_data)
+{
+    const char *name = mono_assembly_name_get_name (aname);
+    const char *culture = mono_assembly_name_get_culture (aname);
+    return load_assembly (name, culture);
+}
+
+char *
+strdup_printf (const char *msg, ...)
+{
+    va_list args;
+    char *formatted = NULL;
+    va_start (args, msg);
+    vasprintf (&formatted, msg, args);
+    va_end (args);
+    return formatted;
+}
+
+static MonoObject *
+fetch_exception_property (MonoObject *obj, const char *name, bool is_virtual)
+{
+    MonoMethod *get = NULL;
+    MonoMethod *get_virt = NULL;
+    MonoObject *exc = NULL;
+
+    get = mono_class_get_method_from_name (mono_get_exception_class (), name, 0);
+    if (get) {
+        if (is_virtual) {
+            get_virt = mono_object_get_virtual_method (obj, get);
+            if (get_virt)
+                get = get_virt;
+        }
+
+        return (MonoObject *) mono_runtime_invoke (get, obj, NULL, &exc);
+    } else {
+        printf ("Could not find the property System.Exception.%s", name);
+    }
+
+    return NULL;
+}
+
+static char *
+fetch_exception_property_string (MonoObject *obj, const char *name, bool is_virtual)
+{
+    MonoString *str = (MonoString *) fetch_exception_property (obj, name, is_virtual);
+    return str ? mono_string_to_utf8 (str) : NULL;
+}
+
+void
+unhandled_exception_handler (MonoObject *exc, void *user_data)
+{
+    NSMutableString *msg = [[NSMutableString alloc] init];
+
+    MonoClass *type = mono_object_get_class (exc);
+    char *type_name = strdup_printf ("%s.%s", mono_class_get_namespace (type), mono_class_get_name (type));
+    char *trace = fetch_exception_property_string (exc, "get_StackTrace", true);
+    char *message = fetch_exception_property_string (exc, "get_Message", true);
+
+    [msg appendString:@"Unhandled managed exceptions:\n"];
+    [msg appendFormat: @"%s (%s)\n%s\n", message, type_name, trace ? trace : ""];
+
+    free (trace);
+    free (message);
+    free (type_name);
+
+    os_log_info (OS_LOG_DEFAULT, "%@", msg);
+    os_log_info (OS_LOG_DEFAULT, "Exit code: %d.", 1);
+    exit (1);
+}
+
+void
+log_callback (const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data)
+{
+    os_log_info (OS_LOG_DEFAULT, "(%s %s) %s", log_domain, log_level, message);
+    if (fatal) {
+        os_log_info (OS_LOG_DEFAULT, "Exit code: %d.", 1);
+        exit (1);
+    }
+}
+
+static void
+register_dllmap (void)
+{
+    mono_dllmap_insert (NULL, "libSystem.Native", NULL, "__Internal", NULL);
+    mono_dllmap_insert (NULL, "libSystem.IO.Compression.Native", NULL, "__Internal", NULL);
+    mono_dllmap_insert (NULL, "libSystem.Security.Cryptography.Native.Apple", NULL, "__Internal", NULL);
+}
+
+void mono_jit_set_aot_mode (MonoAotMode mode);
+
+extern void *mono_aot_module_Program_info;
+extern void *mono_aot_module_System_Private_CoreLib_info;
+extern void *mono_aot_module_System_Runtime_info;
+extern void *mono_aot_module_System_Runtime_Extensions_info;
+extern void *mono_aot_module_System_Collections_info;
+extern void *mono_aot_module_System_Core_info;
+extern void *mono_aot_module_System_Threading_info;
+extern void *mono_aot_module_System_Threading_Tasks_info;
+extern void *mono_aot_module_System_Linq_info;
+extern void *mono_aot_module_System_Memory_info;
+extern void *mono_aot_module_System_Runtime_InteropServices_info;
+extern void *mono_aot_module_System_Text_Encoding_Extensions_info;
+extern void *mono_aot_module_Microsoft_Win32_Primitives_info;
+extern void *mono_aot_module_System_Console_info;
+extern void *mono_aot_module_Program_info;
+
+void mono_ios_register_modules (void)
+{
+    mono_aot_register_module (mono_aot_module_Program_info);
+    mono_aot_register_module (mono_aot_module_System_Private_CoreLib_info);
+    mono_aot_register_module (mono_aot_module_System_Runtime_info);
+    mono_aot_register_module (mono_aot_module_System_Runtime_Extensions_info);
+    mono_aot_register_module (mono_aot_module_System_Collections_info);
+    mono_aot_register_module (mono_aot_module_System_Core_info);
+    mono_aot_register_module (mono_aot_module_System_Threading_info);
+    mono_aot_register_module (mono_aot_module_System_Threading_Tasks_info);
+    mono_aot_register_module (mono_aot_module_System_Linq_info);
+    mono_aot_register_module (mono_aot_module_System_Memory_info);
+    mono_aot_register_module (mono_aot_module_System_Runtime_InteropServices_info);
+    mono_aot_register_module (mono_aot_module_System_Text_Encoding_Extensions_info);
+    mono_aot_register_module (mono_aot_module_Microsoft_Win32_Primitives_info);
+    mono_aot_register_module (mono_aot_module_System_Console_info);
+    mono_aot_register_module (mono_aot_module_Program_info);
+}
+
+void mono_ios_setup_execution_mode (void)
+{
+    mono_jit_set_aot_mode (MONO_AOT_MODE_FULL);
+}
+
+void
+mono_ios_runtime_init (void)
+{
+    // for now, only Invariant Mode is supported (FIXME: integrate ICU)
+    setenv ("DOTNET_SYSTEM_GLOBALIZATION_INVARIANT", "1", TRUE);
+
+    stdout_log = os_log_create ("net.dot.mono", "stdout");
+
+    bool wait_for_debugger = FALSE;
+    char* executable = "Program.dll";
+
+    const char* bundle = get_bundle_path ();
+    chdir (bundle);
+
+    register_dllmap ();
+
+    // register modules
+    mono_ios_register_modules ();
+    mono_ios_setup_execution_mode ();
+    
+    mono_debug_init (MONO_DEBUG_FORMAT_MONO);
+    mono_install_assembly_preload_hook (assembly_preload_hook, NULL);
+    mono_install_load_aot_data_hook (load_aot_data, free_aot_data, NULL);
+    mono_install_unhandled_exception_hook (unhandled_exception_handler, NULL);
+    mono_trace_init ();
+    mono_trace_set_log_handler (log_callback, NULL);
+    mono_set_signal_chaining (TRUE);
+    mono_set_crash_chaining (TRUE);
+
+    if (wait_for_debugger) {
+        char* options[] = { "--debugger-agent=transport=dt_socket,server=y,address=0.0.0.0:55555" };
+        mono_jit_parse_options (1, options);
+    }
+    mono_jit_init_version ("dotnet.ios", "mobile");
+    mono_gc_init_finalizer_thread ();
+
+    MonoAssembly *assembly = load_assembly (executable, NULL);
+    assert (assembly);
+    os_log_info (OS_LOG_DEFAULT, "Executable: %{public}s", executable);
+
+    int res = mono_jit_exec (mono_domain_get (), assembly, 1, &executable);
+    // Print this so apps parsing logs can detect when we exited
+    os_log_info (OS_LOG_DEFAULT, "Exit code: %d.", res);
+}
+