Browse Source

C#: Initial NativeAOT support

This commit adds initial support for games exported as NativeAOT shared
libraries.

At this moment, the NativeAOT runtime is experimental. Additionally,
Godot is not trim-safe as it still makes some use of reflection.
For the time being, a rd.xml file is needed to prevent code triming:

```
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
  <Application>
    <Assembly Name="GodotSharp" Dynamic="Required All" />
    <Assembly Name="GAME_ASSEMBLY" Dynamic="Required All" />
  </Application>
</Directives>
```

These are the csproj changes for publishing:

```
  <PropertyGroup>
    <NativeLib>Shared</NativeLib>
  </PropertyGroup>
  <ItemGroup>
    <RdXmlFile Include="rd.xml" />
    <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="7.0.0-*" />
  </ItemGroup>
```

More info:
- https://github.com/dotnet/runtimelab/blob/feature/NativeAOT/docs/using-nativeaot/compiling.md
- https://github.com/dotnet/runtimelab/tree/feature/NativeAOT/samples/NativeLibrary
- https://github.com/dotnet/runtimelab/blob/feature/NativeAOT/docs/using-nativeaot/rd-xml-format.md
Ignacio Roldán Etcheverry 3 years ago
parent
commit
4b90d16250

+ 4 - 0
modules/mono/csharp_script.cpp

@@ -2179,7 +2179,9 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
 	if (exports_invalidated)
 	if (exports_invalidated)
 #endif
 #endif
 	{
 	{
+#ifdef TOOLS_ENABLED
 		exports_invalidated = false;
 		exports_invalidated = false;
+#endif
 
 
 		changed = true;
 		changed = true;
 
 
@@ -2222,6 +2224,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
 						}
 						}
 					});
 					});
 
 
+#ifdef TOOLS_ENABLED
 			GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyDefaultValues(this,
 			GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyDefaultValues(this,
 					[](CSharpScript *p_script, GDMonoCache::godotsharp_property_def_val_pair *p_def_vals, int32_t p_count) {
 					[](CSharpScript *p_script, GDMonoCache::godotsharp_property_def_val_pair *p_def_vals, int32_t p_count) {
 						for (int i = 0; i < p_count; i++) {
 						for (int i = 0; i < p_count; i++) {
@@ -2233,6 +2236,7 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
 							p_script->exported_members_defval_cache[name] = value;
 							p_script->exported_members_defval_cache[name] = value;
 						}
 						}
 					});
 					});
+#endif
 		}
 		}
 	}
 	}
 
 

+ 5 - 3
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs

@@ -26,14 +26,16 @@ namespace GodotPlugins.Game
 {
 {
     internal static partial class Main
     internal static partial class Main
     {
     {
-        [UnmanagedCallersOnly]
-        private static godot_bool InitializeFromGameProject(IntPtr outManagedCallbacks)
+        [UnmanagedCallersOnly(EntryPoint = ""godotsharp_game_main_init"")]
+        private static godot_bool InitializeFromGameProject(IntPtr godotDllHandle, IntPtr outManagedCallbacks)
         {
         {
             try
             try
             {
             {
+                DllImportResolver dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport;
+
                 var coreApiAssembly = typeof(Godot.Object).Assembly;
                 var coreApiAssembly = typeof(Godot.Object).Assembly;
 
 
-                NativeLibrary.SetDllImportResolver(coreApiAssembly, GodotDllImportResolver.OnResolveDllImport);
+                NativeLibrary.SetDllImportResolver(coreApiAssembly, dllImportResolver);
 
 
                 ManagedCallbacks.Create(outManagedCallbacks);
                 ManagedCallbacks.Create(outManagedCallbacks);
 
 

+ 0 - 4
modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs

@@ -197,10 +197,6 @@ namespace GodotTools.Build
             // Logger
             // Logger
             AddLoggerArgument(buildInfo, arguments);
             AddLoggerArgument(buildInfo, arguments);
 
 
-            // Trimming is not supported for dynamically loaded assemblies, as is our case with self hosting:
-            // https://github.com/dotnet/runtime/blob/main/docs/design/features/native-hosting.md#incompatible-with-trimming
-            arguments.Add("-p:PublishTrimmed=false");
-
             // Custom properties
             // Custom properties
             foreach (string customProperty in buildInfo.CustomProperties)
             foreach (string customProperty in buildInfo.CustomProperties)
             {
             {

+ 10 - 1
modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs

@@ -134,7 +134,16 @@ namespace GodotTools.Export
                 throw new Exception("Failed to build project");
                 throw new Exception("Failed to build project");
             }
             }
 
 
-            if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpEditor.ProjectAssemblyName}.dll")))
+            string soExt = ridOS switch
+            {
+                OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll",
+                OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib",
+                _ => "so"
+            };
+
+            if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpEditor.ProjectAssemblyName}.dll"))
+                // NativeAOT shared library output
+                && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpEditor.ProjectAssemblyName}.{soExt}")))
             {
             {
                 throw new NotSupportedException(
                 throw new NotSupportedException(
                     "Publish succeeded but project assembly not found in the output directory");
                     "Publish succeeded but project assembly not found in the output directory");

+ 1 - 1
modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs

@@ -44,7 +44,7 @@ namespace GodotTools.Utils
             public const string HTML5 = "javascript";
             public const string HTML5 = "javascript";
         }
         }
 
 
-        private static class DotNetOS
+        public static class DotNetOS
         {
         {
             public const string Win = "win";
             public const string Win = "win";
             public const string OSX = "osx";
             public const string OSX = "osx";

+ 8 - 4
modules/mono/glue/GodotSharp/GodotPlugins/Main.cs

@@ -20,22 +20,26 @@ namespace GodotPlugins
             AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ??
             AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ??
             AssemblyLoadContext.Default;
             AssemblyLoadContext.Default;
 
 
+        private static DllImportResolver? _dllImportResolver;
+
         // Right now we do it this way for simplicity as hot-reload is disabled. It will need to be changed later.
         // Right now we do it this way for simplicity as hot-reload is disabled. It will need to be changed later.
         [UnmanagedCallersOnly]
         [UnmanagedCallersOnly]
         // ReSharper disable once UnusedMember.Local
         // ReSharper disable once UnusedMember.Local
-        private static unsafe godot_bool InitializeFromEngine(godot_bool editorHint,
+        private static unsafe godot_bool InitializeFromEngine(IntPtr godotDllHandle, godot_bool editorHint,
             PluginsCallbacks* pluginsCallbacks, ManagedCallbacks* managedCallbacks)
             PluginsCallbacks* pluginsCallbacks, ManagedCallbacks* managedCallbacks)
         {
         {
             try
             try
             {
             {
+                _dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport;
+
                 SharedAssemblies.Add(CoreApiAssembly.GetName());
                 SharedAssemblies.Add(CoreApiAssembly.GetName());
-                NativeLibrary.SetDllImportResolver(CoreApiAssembly, GodotDllImportResolver.OnResolveDllImport);
+                NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver);
 
 
                 if (editorHint.ToBool())
                 if (editorHint.ToBool())
                 {
                 {
                     _editorApiAssembly = Assembly.Load("GodotSharpEditor");
                     _editorApiAssembly = Assembly.Load("GodotSharpEditor");
                     SharedAssemblies.Add(_editorApiAssembly.GetName());
                     SharedAssemblies.Add(_editorApiAssembly.GetName());
-                    NativeLibrary.SetDllImportResolver(_editorApiAssembly, GodotDllImportResolver.OnResolveDllImport);
+                    NativeLibrary.SetDllImportResolver(_editorApiAssembly, _dllImportResolver);
                 }
                 }
 
 
                 *pluginsCallbacks = new()
                 *pluginsCallbacks = new()
@@ -97,7 +101,7 @@ namespace GodotPlugins
 
 
                 var assembly = LoadPlugin(assemblyPath);
                 var assembly = LoadPlugin(assemblyPath);
 
 
-                NativeLibrary.SetDllImportResolver(assembly, GodotDllImportResolver.OnResolveDllImport);
+                NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!);
 
 
                 var method = assembly.GetType("GodotTools.GodotSharpEditor")?
                 var method = assembly.GetType("GodotTools.GodotSharpEditor")?
                     .GetMethod("InternalCreateInstance",
                     .GetMethod("InternalCreateInstance",

+ 8 - 6
modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs

@@ -39,7 +39,12 @@ namespace Godot.Bridge
 
 
             try
             try
             {
             {
-                Type nativeType = TypeGetProxyClass(nativeTypeName);
+                using var stringName = StringName.CreateTakingOwnershipOfDisposableValue(
+                    NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName)));
+                string nativeTypeNameStr = stringName.ToString();
+
+                Type nativeType = TypeGetProxyClass(nativeTypeNameStr) ?? throw new InvalidOperationException(
+                    "Wrapper class not found for type: " + nativeTypeNameStr);
                 var obj = (Object)FormatterServices.GetUninitializedObject(nativeType);
                 var obj = (Object)FormatterServices.GetUninitializedObject(nativeType);
 
 
                 var ctor = nativeType.GetConstructor(
                 var ctor = nativeType.GetConstructor(
@@ -171,12 +176,9 @@ namespace Godot.Bridge
             }
             }
         }
         }
 
 
-        private static unsafe Type TypeGetProxyClass(godot_string_name* nativeTypeName)
+        private static Type TypeGetProxyClass(string nativeTypeNameStr)
         {
         {
             // Performance is not critical here as this will be replaced with a generated dictionary.
             // Performance is not critical here as this will be replaced with a generated dictionary.
-            using var stringName = StringName.CreateTakingOwnershipOfDisposableValue(
-                NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName)));
-            string nativeTypeNameStr = stringName.ToString();
 
 
             if (nativeTypeNameStr[0] == '_')
             if (nativeTypeNameStr[0] == '_')
                 nativeTypeNameStr = nativeTypeNameStr.Substring(1);
                 nativeTypeNameStr = nativeTypeNameStr.Substring(1);
@@ -186,7 +188,7 @@ namespace Godot.Bridge
             if (wrapperType == null)
             if (wrapperType == null)
             {
             {
                 wrapperType = AppDomain.CurrentDomain.GetAssemblies()
                 wrapperType = AppDomain.CurrentDomain.GetAssemblies()
-                    .First(a => a.GetName().Name == "GodotSharpEditor")
+                    .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor")?
                     .GetType("Godot." + nativeTypeNameStr);
                     .GetType("Godot." + nativeTypeNameStr);
             }
             }
 
 

+ 10 - 15
modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs

@@ -6,9 +6,16 @@ using System.Runtime.InteropServices;
 
 
 namespace Godot.NativeInterop
 namespace Godot.NativeInterop
 {
 {
-    public static class GodotDllImportResolver
+    public class GodotDllImportResolver
     {
     {
-        public static IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
+        private IntPtr _internalHandle;
+
+        public GodotDllImportResolver(IntPtr internalHandle)
+        {
+            _internalHandle = internalHandle;
+        }
+
+        public IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
         {
         {
             if (libraryName == "__Internal")
             if (libraryName == "__Internal")
             {
             {
@@ -18,7 +25,7 @@ namespace Godot.NativeInterop
                 }
                 }
                 else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                 else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                 {
                 {
-                    return Linux.dlopen(IntPtr.Zero, Linux.RTLD_LAZY);
+                    return _internalHandle;
                 }
                 }
                 else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                 else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                 {
                 {
@@ -40,18 +47,6 @@ namespace Godot.NativeInterop
             public static extern IntPtr dlopen(IntPtr path, int mode);
             public static extern IntPtr dlopen(IntPtr path, int mode);
         }
         }
 
 
-        private static class Linux
-        {
-            // libdl.so was resulting in DllNotFoundException, for some reason...
-            // libcoreclr.so should work with both CoreCLR and the .NET Core version of Mono.
-            private const string SystemLibrary = "libcoreclr.so";
-
-            public const int RTLD_LAZY = 1;
-
-            [DllImport(SystemLibrary)]
-            public static extern IntPtr dlopen(IntPtr path, int mode);
-        }
-
         private static class Win32
         private static class Win32
         {
         {
             private const string SystemLibrary = "Kernel32.dll";
             private const string SystemLibrary = "Kernel32.dll";

+ 86 - 20
modules/mono/mono_gd/gd_mono.cpp

@@ -48,6 +48,9 @@
 
 
 #include <coreclr_delegates.h>
 #include <coreclr_delegates.h>
 #include <hostfxr.h>
 #include <hostfxr.h>
+#ifdef UNIX_ENABLED
+#include <dlfcn.h>
+#endif
 
 
 // TODO mobile
 // TODO mobile
 #if 0
 #if 0
@@ -168,18 +171,24 @@ String find_hostfxr() {
 #else
 #else
 
 
 #if defined(WINDOWS_ENABLED)
 #if defined(WINDOWS_ENABLED)
-	return GodotSharpDirs::get_api_assemblies_dir()
-			.plus_file("hostfxr.dll");
+	String probe_path = GodotSharpDirs::get_api_assemblies_dir()
+								.plus_file("hostfxr.dll");
 #elif defined(MACOS_ENABLED)
 #elif defined(MACOS_ENABLED)
-	return GodotSharpDirs::get_api_assemblies_dir()
-			.plus_file("libhostfxr.dylib");
+	String probe_path = GodotSharpDirs::get_api_assemblies_dir()
+								.plus_file("libhostfxr.dylib");
 #elif defined(UNIX_ENABLED)
 #elif defined(UNIX_ENABLED)
-	return GodotSharpDirs::get_api_assemblies_dir()
-			.plus_file("libhostfxr.so");
+	String probe_path = GodotSharpDirs::get_api_assemblies_dir()
+								.plus_file("libhostfxr.so");
 #else
 #else
 #error "Platform not supported (yet?)"
 #error "Platform not supported (yet?)"
 #endif
 #endif
 
 
+	if (FileAccess::exists(probe_path)) {
+		return probe_path;
+	}
+
+	return String();
+
 #endif
 #endif
 }
 }
 
 
@@ -285,11 +294,21 @@ load_assembly_and_get_function_pointer_fn initialize_hostfxr_self_contained(
 #endif
 #endif
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
-using godot_plugins_initialize_fn = bool (*)(bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *);
+using godot_plugins_initialize_fn = bool (*)(void *, bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *);
 #else
 #else
-using godot_plugins_initialize_fn = bool (*)(GDMonoCache::ManagedCallbacks *);
+using godot_plugins_initialize_fn = bool (*)(void *, GDMonoCache::ManagedCallbacks *);
 #endif
 #endif
 
 
+static String get_assembly_name() {
+	String appname = ProjectSettings::get_singleton()->get("application/config/name");
+	String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
+	if (appname_safe.is_empty()) {
+		appname_safe = "UnnamedProject";
+	}
+
+	return appname_safe;
+}
+
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
 godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
 	godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
 	godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
@@ -320,15 +339,9 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime
 }
 }
 #else
 #else
 godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
 godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) {
-	String appname = ProjectSettings::get_singleton()->get("application/config/name");
-	String appname_safe = OS::get_singleton()->get_safe_dir_name(appname);
-	if (appname_safe.is_empty()) {
-		appname_safe = "UnnamedProject";
-	}
-
 	godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
 	godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
 
 
-	String assembly_name = appname_safe;
+	String assembly_name = get_assembly_name();
 
 
 	HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir()
 	HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir()
 															 .plus_file(assembly_name + ".dll"));
 															 .plus_file(assembly_name + ".dll"));
@@ -351,6 +364,38 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime
 
 
 	return godot_plugins_initialize;
 	return godot_plugins_initialize;
 }
 }
+
+godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) {
+	String assembly_name = get_assembly_name();
+
+#if defined(WINDOWS_ENABLED)
+	String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".dll");
+#elif defined(MACOS_ENABLED)
+	String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".dylib");
+#elif defined(UNIX_ENABLED)
+	String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".so");
+#else
+#error "Platform not supported (yet?)"
+#endif
+
+	if (FileAccess::exists(native_aot_so_path)) {
+		Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle);
+
+		if (err != OK) {
+			return nullptr;
+		}
+
+		void *lib = r_aot_dll_handle;
+
+		void *symbol = nullptr;
+
+		err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol);
+		ERR_FAIL_COND_V(err != OK, nullptr);
+		return (godot_plugins_initialize_fn)symbol;
+	}
+
+	return nullptr;
+}
 #endif
 #endif
 
 
 } // namespace
 } // namespace
@@ -377,25 +422,46 @@ void GDMono::initialize() {
 
 
 	_init_godot_api_hashes();
 	_init_godot_api_hashes();
 
 
+	godot_plugins_initialize_fn godot_plugins_initialize = nullptr;
+
 	if (!load_hostfxr(hostfxr_dll_handle)) {
 	if (!load_hostfxr(hostfxr_dll_handle)) {
+#if !defined(TOOLS_ENABLED)
+		godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle);
+
+		if (godot_plugins_initialize != nullptr) {
+			is_native_aot = true;
+		} else {
+			ERR_FAIL_MSG(".NET: Failed to load hostfxr");
+		}
+#else
 		ERR_FAIL_MSG(".NET: Failed to load hostfxr");
 		ERR_FAIL_MSG(".NET: Failed to load hostfxr");
+#endif
 	}
 	}
 
 
-	godot_plugins_initialize_fn godot_plugins_initialize =
-			initialize_hostfxr_and_godot_plugins(runtime_initialized);
-	ERR_FAIL_NULL(godot_plugins_initialize);
+	if (!is_native_aot) {
+		godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized);
+		ERR_FAIL_NULL(godot_plugins_initialize);
+	}
 
 
 	GDMonoCache::ManagedCallbacks managed_callbacks;
 	GDMonoCache::ManagedCallbacks managed_callbacks;
 
 
+	void *godot_dll_handle = nullptr;
+
+#if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(IOS_ENABLED)
+	// Managed code can access it on its own on other platforms
+	godot_dll_handle = dlopen(nullptr, RTLD_NOW);
+#endif
+
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	gdmono::PluginCallbacks plugin_callbacks_res;
 	gdmono::PluginCallbacks plugin_callbacks_res;
-	bool init_ok = godot_plugins_initialize(Engine::get_singleton()->is_editor_hint(),
+	bool init_ok = godot_plugins_initialize(godot_dll_handle,
+			Engine::get_singleton()->is_editor_hint(),
 			&plugin_callbacks_res, &managed_callbacks);
 			&plugin_callbacks_res, &managed_callbacks);
 	ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
 	ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
 
 
 	plugin_callbacks = plugin_callbacks_res;
 	plugin_callbacks = plugin_callbacks_res;
 #else
 #else
-	bool init_ok = godot_plugins_initialize(&managed_callbacks);
+	bool init_ok = godot_plugins_initialize(godot_dll_handle, &managed_callbacks);
 	ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
 	ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed");
 #endif
 #endif
 
 

+ 1 - 0
modules/mono/mono_gd/gd_mono.h

@@ -61,6 +61,7 @@ class GDMono {
 	bool finalizing_scripts_domain;
 	bool finalizing_scripts_domain;
 
 
 	void *hostfxr_dll_handle = nullptr;
 	void *hostfxr_dll_handle = nullptr;
+	bool is_native_aot = false;
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	bool _load_project_assembly();
 	bool _load_project_assembly();