diff --git a/.gitmodules b/.gitmodules
index eefd1092b6d4..5a50d28b9224 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -13,7 +13,7 @@
 [submodule "external/Touch.Unit"]
     path = external/Touch.Unit
     url = ../../spouliot/Touch.Unit.git
-    branch = main
+    branch = net8.0
 [submodule "external/Xamarin.MacDev"]
     path = external/Xamarin.MacDev
     url = ../../xamarin/Xamarin.MacDev
@@ -21,7 +21,7 @@
 [submodule "external/MonoTouch.Dialog"]
     path = external/MonoTouch.Dialog
     url = ../../migueldeicaza/MonoTouch.Dialog
-    branch = dotnet
+    branch = net8.0
 [submodule "api-tools"]
     path = external/api-tools
     url = ../../rolfbjarne/api-tools
diff --git a/Make.config b/Make.config
index 4e5873937c81..2c2747c4950e 100644
--- a/Make.config
+++ b/Make.config
@@ -273,6 +273,12 @@ DOTNET_MIN_TVOS_SDK_VERSION=11.0
 DOTNET_MIN_MACCATALYST_SDK_VERSION=13.1
 DOTNET_MIN_MACOS_SDK_VERSION=10.15
 
+# Minimum OS versions when using NativeOAT - these are at least the general min versions above (but may be higher).
+DOTNET_MIN_NATIVEAOT_IOS_SDK_VERSION=12.2
+DOTNET_MIN_NATIVEAOT_TVOS_SDK_VERSION=12.2
+DOTNET_MIN_NATIVEAOT_MACCATALYST_SDK_VERSION=13.1
+DOTNET_MIN_NATIVEAOT_MACOS_SDK_VERSION=10.15
+
 # The min simulator version available in the Xcode we're using
 MIN_IOS_SIMULATOR_VERSION=13.7
 MIN_WATCHOS_SIMULATOR_VERSION=7.0
@@ -682,6 +688,7 @@ DOTNET_PLATFORMS=
 ifdef INCLUDE_IOS
 DOTNET_PLATFORMS+=iOS
 DOTNET_IOS_BITNESSES+=64
+DOTNET_NATIVEAOT_PLATFORMS+=iOS
 
 # 32-bit architectures
 ifdef IOS_SUPPORTS_32BIT_ARCHITECTURES
@@ -705,6 +712,7 @@ endif # INCLUDE_IOS
 ifdef INCLUDE_TVOS
 DOTNET_PLATFORMS+=tvOS
 DOTNET_TVOS_BITNESSES+=64
+DOTNET_NATIVEAOT_PLATFORMS+=tvOS
 ifdef INCLUDE_DEVICE
 DOTNET_TVOS_RUNTIME_IDENTIFIERS=tvos-arm64 tvossimulator-x64 tvossimulator-arm64
 else
@@ -728,6 +736,7 @@ endif
 ifdef INCLUDE_MACCATALYST
 DOTNET_PLATFORMS+=MacCatalyst
 DOTNET_MACCATALYST_BITNESSES+=64
+DOTNET_NATIVEAOT_PLATFORMS+=MacCatalyst
 DOTNET_MACCATALYST_RUNTIME_IDENTIFIERS=maccatalyst-x64 maccatalyst-arm64
 DOTNET_MACCATALYST_RUNTIME_IDENTIFIERS_64+=$(DOTNET_MACCATALYST_RUNTIME_IDENTIFIERS)
 endif
@@ -736,6 +745,7 @@ ifdef INCLUDE_MAC
 DOTNET_PLATFORMS+=macOS
 DOTNET_CORECLR_PLATFORMS+=macOS
 DOTNET_MACOS_BITNESSES+=64
+DOTNET_NATIVEAOT_PLATFORMS+=macOS
 DOTNET_MACOS_RUNTIME_IDENTIFIERS=osx-x64 osx-arm64
 DOTNET_MACOS_RUNTIME_IDENTIFIERS_64=$(DOTNET_MACOS_RUNTIME_IDENTIFIERS)
 endif
diff --git a/dotnet/generate-target-platforms.csharp b/dotnet/generate-target-platforms.csharp
index ddd6a5a0aa1a..9e0a1b82628f 100755
--- a/dotnet/generate-target-platforms.csharp
+++ b/dotnet/generate-target-platforms.csharp
@@ -24,10 +24,16 @@ var doc = new XmlDocument ();
 doc.Load (plistPath);
 var nodes = doc.SelectNodes ($"/plist/dict/key[text()='KnownVersions']/following-sibling::dict[1]/key[text()='{platform}']/following-sibling::array[1]/string");
 
+var allLines = File.ReadAllLines ("../Make.config");
+
 var minSdkVersionName = $"DOTNET_MIN_{platform.ToUpper ()}_SDK_VERSION";
-var minSdkVersionString = File.ReadAllLines ("../Make.config").Single (v => v.StartsWith (minSdkVersionName + "=", StringComparison.Ordinal)).Substring (minSdkVersionName.Length + 1);
+var minSdkVersionString = allLines.Single (v => v.StartsWith (minSdkVersionName + "=", StringComparison.Ordinal)).Substring (minSdkVersionName.Length + 1);
 var minSdkVersion = Version.Parse (minSdkVersionString);
 
+var minNativeAotSdkVersionName = $"DOTNET_MIN_NATIVEAOT_{platform.ToUpper ()}_SDK_VERSION";
+var minNativeAotSdkVersionString = allLines.Single (v => v.StartsWith (minNativeAotSdkVersionName + "=", StringComparison.Ordinal)).Substring (minNativeAotSdkVersionName.Length + 1);
+var minNativeAotSdkVersion = Version.Parse (minNativeAotSdkVersionString);
+
 using (TextWriter writer = new StreamWriter (outputPath)) {
 	writer.WriteLine ($"<!-- This file contains a generated list of the {platform} platform versions that are supported for this SDK -->");
 	writer.WriteLine ($"<!-- Generation script: https://github.com/xamarin/xamarin-macios/blob/main/dotnet/generate-target-platforms.csharp -->");
@@ -36,9 +42,14 @@ using (TextWriter writer = new StreamWriter (outputPath)) {
 
 	foreach (XmlNode n in nodes) {
 		var version = n.InnerText;
-		if (Version.Parse (version) < minSdkVersion)
+		var parsedVersion = Version.Parse (version);
+		if (parsedVersion < minSdkVersion)
 			continue;
-		writer.WriteLine ($"\t\t<{platform}SdkSupportedTargetPlatformVersion Include=\"{n.InnerText}\" />");
+		if (parsedVersion < minNativeAotSdkVersion) {
+			writer.WriteLine ($"\t\t<{platform}SdkSupportedTargetPlatformVersion Include=\"{n.InnerText}\" Condition=\"!('$(PublishAot)' == 'true' And '$(_IsPublishing)' == 'true')\" />");
+		} else {
+			writer.WriteLine ($"\t\t<{platform}SdkSupportedTargetPlatformVersion Include=\"{n.InnerText}\" />");
+		}
 	}
 
 	writer.WriteLine ("\t</ItemGroup>");
@@ -46,7 +57,8 @@ using (TextWriter writer = new StreamWriter (outputPath)) {
 	writer.WriteLine ($"\t\t<SdkSupportedTargetPlatformVersion Condition=\"'$(TargetPlatformIdentifier)' == '{platform}'\" Include=\"@({platform}SdkSupportedTargetPlatformVersion)\" />");
 	writer.WriteLine ("\t</ItemGroup>");
 	writer.WriteLine ("\t<PropertyGroup>");
-	writer.WriteLine ($"\t\t<{platform}MinSupportedOSPlatformVersion>{minSdkVersionString}</{platform}MinSupportedOSPlatformVersion>");
+	writer.WriteLine ($"\t\t<{platform}MinSupportedOSPlatformVersion Condition=\"!('$(PublishAot)' == 'true' And '$(_IsPublishing)' == 'true')\">{minSdkVersionString}</{platform}MinSupportedOSPlatformVersion>");
+	writer.WriteLine ($"\t\t<{platform}MinSupportedOSPlatformVersion Condition=\"'$(PublishAot)' == 'true' And '$(_IsPublishing)' == 'true'\">{minNativeAotSdkVersionString}</{platform}MinSupportedOSPlatformVersion>");
 	writer.WriteLine ("\t</PropertyGroup>");
 	writer.WriteLine ("</Project>");
 }
diff --git a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets
index d100091037f5..c13dc5def78a 100644
--- a/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets
+++ b/dotnet/targets/Xamarin.Shared.Sdk.DefaultItems.targets
@@ -11,8 +11,17 @@
     <EnableDefaultmacOSItems Condition=" '$(_PlatformName)' == 'macOS' And '$(EnableDefaultmacOSItems)' == '' ">$(EnableDefaultItems)</EnableDefaultmacOSItems>
     <EnableDefaultMacCatalystItems Condition=" '$(_PlatformName)' == 'MacCatalyst' And '$(EnableDefaultMacCatalystItems)' == '' ">$(EnableDefaultItems)</EnableDefaultMacCatalystItems>
 
-    <UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' And '$(_PlatformName)' != 'macOS'">true</UseMonoRuntime>
+    <!--
+        PublishAot should only take effect when doing 'dotnet publish', not when doing 'dotnet build'. We distinguish these cases using the '_IsPublishing' property,
+        but it's rather annoying to always have to check both PublishAot and _IsPublishing to see if we're using NativeAOT, so introduce a third property that's
+        only set to true if both PublishAot=true and _IsPublishing=true
+    -->
+    <_UseNativeAot Condition="'$(PublishAot)' == 'true' And '$(_IsPublishing)' == 'true'">true</_UseNativeAot>
+
     <UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' And '$(_PlatformName)' == 'macOS'">false</UseMonoRuntime>
+    <UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' And '$(_UseNativeAot)' == 'true'">false</UseMonoRuntime>
+    <UseMonoRuntime Condition=" '$(UseMonoRuntime)' == ''">true</UseMonoRuntime>
+
   </PropertyGroup>
 
   <ItemGroup>
diff --git a/dotnet/targets/Xamarin.Shared.Sdk.props b/dotnet/targets/Xamarin.Shared.Sdk.props
index 3bff654e6993..050a40c1736d 100644
--- a/dotnet/targets/Xamarin.Shared.Sdk.props
+++ b/dotnet/targets/Xamarin.Shared.Sdk.props
@@ -58,6 +58,13 @@
 
 		<!-- Explicitly export symbols using clang command-line option "-exported_symbols_list export_list" -->
 		<_ExportSymbolsExplicitly Condition="'$(_ExportSymbolsExplicitly)' == ''">true</_ExportSymbolsExplicitly>
+
+		<!--
+			Some runtime libraries feature switches defaults that need to be set early
+			Available feature switches: https://github.com/dotnet/runtime/blob/master/docs/workflow/trimming/feature-switches.md
+		-->
+		<!-- AutoreleasePoolSupport needs to be set earlier than other switches, so that illink doesn't override it - https://github.com/dotnet/runtime/pull/86753 - so it's set here, instead of in Xamarin.Shared.Sdk.targets -->
+		<AutoreleasePoolSupport Condition="'$(AutoreleasePoolSupport)' == ''">true</AutoreleasePoolSupport>
 	</PropertyGroup>
 
 	<!-- Set the default RuntimeIdentifier if not already specified. -->
@@ -95,6 +102,20 @@
 		<SelfContained>true</SelfContained>
 	</PropertyGroup>
 
+	<!--
+
+		SelfContained is automatically enabled if PublishAot is true, and that
+		doesn't work properly (restore fails because RuntimeIdentifier is not
+		set) when doing the outer build of a universal apps (when
+		RuntimeIdentifier=''), so manually disable SelfContained in that case.
+
+		This might not be necessary after: https://github.com/dotnet/sdk/pull/33229
+
+	-->
+	<PropertyGroup Condition="'$(PublishAot)' == 'true' And '$(_IsPublishing)' == 'true' And '$(RuntimeIdentifiers)' != '' And '$(RuntimeIdentifier)' == '' And '$(SelfContained)' == ''">
+		<SelfContained>false</SelfContained>
+	</PropertyGroup>
+
 	<!--
 		Enable LLVM by default for mobile release builds.
 
@@ -105,4 +126,22 @@
 	<PropertyGroup Condition="'$(MtouchUseLlvm)' == '' And '$(Configuration)' == 'Release' And ('$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS')">
 		<MtouchUseLlvm>true</MtouchUseLlvm>
 	</PropertyGroup>
+
+	<!-- Various options when using NativeAOT -->
+	<PropertyGroup Condition="'$(PublishAot)' == 'true' And '$(_IsPublishing)' == 'true'">
+		<!-- Disable our own assembly IL stripping logic, because ILC does that already -->
+		<EnableAssemblyILStripping>false</EnableAssemblyILStripping>
+
+		<!-- We're using our own native main function when using NativeAOT -->
+		<CustomNativeMain>true</CustomNativeMain>
+
+		<!-- We must find the BCL libraries using the runtime pack instead of using the built-in NativeAOT BCL -->
+		<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
+
+		<!-- This turns off some NativeAOT logic we don't want nor need -->
+		<NativeCompilationDuringPublish>false</NativeCompilationDuringPublish>
+
+		<!-- This works around an issue in NativeAOT: https://github.com/dotnet/runtime/issues/86186 -->
+		<IlcKeepManagedDebuggerSupport>true</IlcKeepManagedDebuggerSupport>
+	</PropertyGroup>
 </Project>
diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets
index 496a66b7537b..d62eb17660e3 100644
--- a/dotnet/targets/Xamarin.Shared.Sdk.targets
+++ b/dotnet/targets/Xamarin.Shared.Sdk.targets
@@ -130,7 +130,7 @@
 		<UseSystemResourceKeys Condition="'$(UseSystemResourceKeys)' == '' And '$(_BundlerDebug)' != 'true'">true</UseSystemResourceKeys>
 		<UseSystemResourceKeys Condition="'$(UseSystemResourceKeys)' == '' And '$(_BundlerDebug)' == 'true'">false</UseSystemResourceKeys>
 		<UseNativeHttpHandler Condition="'$(_PlatformName)' != 'macOS' And '$(UseNativeHttpHandler)' == ''">true</UseNativeHttpHandler>
-		<AutoreleasePoolSupport Condition="'$(AutoreleasePoolSupport)' == ''">true</AutoreleasePoolSupport>
+		<!-- AutoreleasePoolSupport needs to be set earlier, so that illink doesn't override it - https://github.com/dotnet/runtime/pull/86753 - so it's set in Xamarin.Shared.Sdk.props -->
 		<_AggressiveAttributeTrimming Condition="'$(_AggressiveAttributeTrimming)' == ''">true</_AggressiveAttributeTrimming>		
 		<NullabilityInfoContextSupport Condition="'$(NullabilityInfoContextSupport)' == ''">false</NullabilityInfoContextSupport>
 		<BuiltInComInteropSupport Condition="'$(BuiltInComInteropSupport)' == ''">false</BuiltInComInteropSupport>
@@ -153,6 +153,11 @@
 	<!-- Default item includes (globs and implicit references) -->
 	<Import Project="Xamarin.Shared.Sdk.DefaultItems.targets" />
 
+	<PropertyGroup Condition="'$(_GlobalizationDataFileLocation)' == ''">
+		<_GlobalizationDataFileLocation Condition="'$(_UseNativeAot)' == 'true'">Resource</_GlobalizationDataFileLocation>
+		<_GlobalizationDataFileLocation Condition="'$(_UseNativeAot)' != 'true'">Assembly</_GlobalizationDataFileLocation>
+	</PropertyGroup>
+
 	<!-- dotnet publish -->
 	<Import Project="Xamarin.Shared.Sdk.Publish.targets" />
 
@@ -424,7 +429,13 @@
 			_ComputeLinkMode;
 			_ComputeFrameworkVariables;
 			_ComputeFrameworkAssemblies;
+		</_ComputeLinkerArgumentsDependsOn>
+		<_ComputeLinkerArgumentsDependsOn Condition="'$(_UseNativeAot)' != 'true'">
+			$(_ComputeLinkerArgumentsDependsOn);
 			ComputeResolvedFilesToPublishList;
+		</_ComputeLinkerArgumentsDependsOn>
+		<_ComputeLinkerArgumentsDependsOn>
+			$(_ComputeLinkerArgumentsDependsOn);
 			_ParseBundlerArguments;
 			_ComputeVariables;
 			_CreateRuntimeConfiguration;
@@ -433,9 +444,50 @@
 			_DetectAppManifest;
 			_ReadAppManifest;
 			_WriteAppManifest;
+			_ComputeLinkerFeatures;
 		</_ComputeLinkerArgumentsDependsOn>
 	</PropertyGroup>
 
+	<PropertyGroup Condition="'$(_UseNativeAot)' == 'true'">
+		<IlcCompileDependsOn>Compile;_ComputeLinkerArguments;_ComputeManagedAssemblyToLink;SetupOSSpecificProps;PrepareForILLink;_XamarinComputeIlcCompileInputs</IlcCompileDependsOn>
+	</PropertyGroup>
+
+	<Target Name="_ComputeLinkerFeatures">
+		<PropertyGroup Condition="'$(_UseNativeAot)' == 'true'">
+			<!-- The one and only registrar is 'managed-static' when using NativeAOT -->
+			<Registrar Condition="'$(Registrar)' == ''">managed-static</Registrar>
+
+			<!-- Yep, we want to run ILLink as well, because we need our custom steps to run (NativeAOT sets this to false, so set it back to true) -->
+			<RunILLink>true</RunILLink>
+		</PropertyGroup>
+
+		<PropertyGroup>
+			<!-- Set linker feature flags for device/simulator builds -->
+			<_IsSimulatorFeature Condition="('$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS') And '$(_SdkIsSimulator)' == 'true'">true</_IsSimulatorFeature>
+			<_IsSimulatorFeature Condition="'$(_IsSimulatorFeature)' == ''">false</_IsSimulatorFeature>
+
+			<!-- Set managed static registrar value -->
+			<_IsManagedStaticRegistrarFeature Condition="'$(Registrar)' == 'managed-static'">true</_IsManagedStaticRegistrarFeature>
+			<_IsManagedStaticRegistrarFeature Condition="'$(Registrar)' != 'managed-static'">false</_IsManagedStaticRegistrarFeature>
+
+			<!-- Set NativeAOT value -->
+			<_IsNativeAOTFeature Condition="'$(_XamarinRuntime)' == 'NativeAOT'">true</_IsNativeAOTFeature>
+			<_IsNativeAOTFeature Condition="'$(_XamarinRuntime)' != 'NativeAOT'">false</_IsNativeAOTFeature>
+
+			<!-- Set the features for ILLink -->
+			<_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.Arch.IsSimulator $(_IsSimulatorFeature)</_ExtraTrimmerArgs>
+			<_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.IsManagedStaticRegistrar $(_IsManagedStaticRegistrarFeature)</_ExtraTrimmerArgs>
+			<_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.IsNativeAOT $(_IsNativeAOTFeature)</_ExtraTrimmerArgs>
+		</PropertyGroup>
+
+		<ItemGroup>
+			<!-- Set the features for ILC -->
+			<IlcArg Include="--feature:ObjCRuntime.Runtime.Arch.IsSimulator=$(_IsSimulatorFeature)" />
+			<IlcArg Include="--feature:ObjCRuntime.Runtime.IsManagedStaticRegistrar=$(_IsManagedStaticRegistrarFeature)" />
+			<IlcArg Include="--feature:ObjCRuntime.Runtime.IsNativeAOT=$(_IsNativeAOTFeature)" />
+		</ItemGroup>
+	</Target>
+
 	<Target Name="_ComputeLinkerArguments" DependsOnTargets="$(_ComputeLinkerArgumentsDependsOn)">
 		<!-- Validate the linker mode -->
 		<Error Text="Invalid link mode: '$(_LinkMode)'. Valid link modes are: 'None', 'SdkOnly' and 'Full'" Condition="'$(_LinkMode)' != 'None' And '$(_LinkMode)' != 'SdkOnly' And '$(_LinkMode)' != 'Full' And '$(_LinkMode)' != 'TrimMode'" />
@@ -443,6 +495,11 @@
 		<!-- it's invariant or it's one of the ICU data files, not both -->
 		<Error Text="Can not set values for both InvariantGlobalization '$(InvariantGlobalization)' and _GlobalizationDataFile '$(_GlobalizationDataFile)'" Condition="'$(_GlobalizationDataFile)' != '' And '$(InvariantGlobalization)' == 'true'" />
 
+		<Error Text="The only valid registrar when using NativeAOT is 'managed-static' (current value: '$(Registrar)'). Please either delete the 'Registrar' property, or change it to be 'managed-static'." Condition="'$(_UseNativeAot)' == 'true' And '$(Registrar)' != '' And '$(Registrar)' != 'managed-static'" />
+
+		<Warning Text="Only the managed static registrar is supported when using NativeAOT." Condition="'$(_UseNativeAot)' == 'true' And '$(Registrar)' != 'managed-static'" />
+		<Warning Text="All assemblies must be processed by the linker when using NativeAOT. Please don't set neither the '$(_LinkModeProperty)' nor the 'TrimMode' property, so that the build can default to linking all assemblies." Condition="'$(_UseNativeAot)' == 'true' And '$(_LinkMode)' != 'Full'" />
+
 		<PropertyGroup>
 			<!-- Pass the custom options to our custom steps -->
 			<_CustomLinkerOptionsFile>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)custom-linker-options.txt'))</_CustomLinkerOptionsFile>
@@ -456,9 +513,6 @@
 			<_LinkerCacheDirectory>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)linker-cache'))</_LinkerCacheDirectory>
 			<_LinkerCacheDirectory Condition="'$(BuildSessionId)' != ''">$(IntermediateOutputPath)linker-cache</_LinkerCacheDirectory>
 
-			<_XamarinRuntime Condition="'$(UseMonoRuntime)' == 'true'">MonoVM</_XamarinRuntime>
-			<_XamarinRuntime Condition="'$(UseMonoRuntime)' != 'true'">CoreCLR</_XamarinRuntime>
-
 			<!-- TrimMode specifies what the linker will do with framework assemblies -->
 			<TrimMode Condition="'$(_LinkMode)' == 'TrimMode'">$(TrimMode)</TrimMode>
 			<TrimMode Condition="'$(_LinkMode)' == 'None'">copy</TrimMode>
@@ -562,14 +616,6 @@
 			<!-- If a release build and the app is not extensible (no interpreter or JIT/code-loading for macOS) then the sealer optimization can be used -->
 			<_ExtraTrimmerArgs Condition="'$(_BundlerDebug)' != 'true' And '$(MtouchInterpreter)' == '' And '$(_RunAotCompiler)' == 'true' And '$(_PlatformName)' != 'macOS'">$(_ExtraTrimmerArgs) --enable-opt sealer</_ExtraTrimmerArgs>
 
-			<!-- Set linker feature flags for device/simulator builds -->
-			<_ExtraTrimmerArgs Condition="('$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS') And '$(_SdkIsSimulator)' == 'true'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.Arch.IsSimulator true</_ExtraTrimmerArgs>
-			<_ExtraTrimmerArgs Condition="('$(_PlatformName)' == 'iOS' Or '$(_PlatformName)' == 'tvOS') And '$(_SdkIsSimulator)' != 'true'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.Arch.IsSimulator false</_ExtraTrimmerArgs>
-
-			<!-- Set managed static registrar value -->
-			<_ExtraTrimmerArgs Condition="'$(Registrar)' == 'managed-static'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.IsManagedStaticRegistrar true</_ExtraTrimmerArgs>
-			<_ExtraTrimmerArgs Condition="'$(Registrar)' != 'managed-static'">$(_ExtraTrimmerArgs) --feature ObjCRuntime.Runtime.IsManagedStaticRegistrar false</_ExtraTrimmerArgs>
-
 			<!-- Enable serialization discovery. Ref: https://github.com/xamarin/xamarin-macios/issues/15676 -->
 			<_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --enable-serialization-discovery</_ExtraTrimmerArgs>
 
@@ -853,7 +899,12 @@
 
 	<Target Name="_ComputeFrameworkVariables" DependsOnTargets="ResolveRuntimePackAssets;ResolveFrameworkReferences">
 		<PropertyGroup>
-			<_PackageIdInfix Condition="'$(UseMonoRuntime)' == 'true'">Mono.</_PackageIdInfix>
+			<_XamarinRuntime Condition="'$(UseMonoRuntime)' == 'true'">MonoVM</_XamarinRuntime>
+			<_XamarinRuntime Condition="'$(UseMonoRuntime)' != 'true' And '$(_UseNativeAot)' != 'true'">CoreCLR</_XamarinRuntime>
+			<_XamarinRuntime Condition="'$(UseMonoRuntime)' != 'true' And '$(_UseNativeAot)' == 'true'">NativeAOT</_XamarinRuntime>
+
+			<_PackageIdInfix Condition="'$(_XamarinRuntime)' == 'MonoVM'">Mono.</_PackageIdInfix>
+			<_PackageIdInfix Condition="'$(_XamarinRuntime)' == 'NativeAOT'">NativeAOT.</_PackageIdInfix>
 
 			<_MonoNugetPackageId>Microsoft.NETCore.App.Runtime.$(_PackageIdInfix)$(RuntimeIdentifier)</_MonoNugetPackageId>
 			<_XamarinNugetPackageId>Microsoft.$(_PlatformName).Runtime.$(RuntimeIdentifier)</_XamarinNugetPackageId>
@@ -871,8 +922,9 @@
 			<_XamarinRefAssemblyDirectory>$(_XamarinRefPackageDirectory)/ref/net8.0/</_XamarinRefAssemblyDirectory>
 			<_XamarinRefAssemblyPath>$(_XamarinRefAssemblyDirectory)$(_PlatformAssemblyName).dll</_XamarinRefAssemblyPath>
 
-			<_LibPartialStaticRegistrar Condition="'$(UseMonoRuntime)' == 'true'">$(_XamarinNativeLibraryDirectory)/Microsoft.$(_PlatformName).registrar.a</_LibPartialStaticRegistrar>
-			<_LibPartialStaticRegistrar Condition="'$(UseMonoRuntime)' != 'true'">$(_XamarinNativeLibraryDirectory)/Microsoft.$(_PlatformName).registrar.coreclr.a</_LibPartialStaticRegistrar>
+			<_LibPartialStaticRegistrar Condition="'$(_XamarinRuntime)' == 'MonoVM'">$(_XamarinNativeLibraryDirectory)/Microsoft.$(_PlatformName).registrar.a</_LibPartialStaticRegistrar>
+			<_LibPartialStaticRegistrar Condition="'$(_XamarinRuntime)' == 'CoreCLR'">$(_XamarinNativeLibraryDirectory)/Microsoft.$(_PlatformName).registrar.coreclr.a</_LibPartialStaticRegistrar>
+			<_LibPartialStaticRegistrar Condition="'$(_XamarinRuntime)' == 'NativeAOT'">$(_XamarinNativeLibraryDirectory)/Microsoft.$(_PlatformName).registrar.nativeaot.a</_LibPartialStaticRegistrar>
 
 			<_MonoRuntimePackPath>%(_MonoFrameworkReference.RuntimePackPath)/runtimes/$(RuntimeIdentifier)/</_MonoRuntimePackPath>
 		</PropertyGroup>
@@ -901,13 +953,16 @@
 			publish (except the merged app bundle, which we'll handle
 			ourselves).
 		-->
-		<_ComputeVariablesDependsOn Condition="'$(RuntimeIdentifiers)' == ''">$(_ComputeVariablesDependsOn);ComputeResolvedFilesToPublishList</_ComputeVariablesDependsOn>
+		<_ComputeVariablesDependsOn Condition="'$(RuntimeIdentifiers)' == '' And '$(_UseNativeAot)' != 'true'">$(_ComputeVariablesDependsOn);ComputeResolvedFilesToPublishList</_ComputeVariablesDependsOn>
 	</PropertyGroup>
 
 	<Target Name="_ComputeVariables" DependsOnTargets="$(_ComputeVariablesDependsOn)">
 		<PropertyGroup Condition="'$(_RunAotCompiler)' == ''">
 			<!-- Don't run the AOT compiler by default -->
 			<_RunAotCompiler>false</_RunAotCompiler>
+		</PropertyGroup>
+		<!-- We don't run Mono's AOT compiler if we're using NativeAOT -->
+		<PropertyGroup Condition="'$(_UseNativeAot)' != 'true'" >
 			<!-- We need it for device builds for mobile platforms -->
 			<_RunAotCompiler Condition="'$(_SdkIsSimulator)' != 'true' And '$(_PlatformName)' != 'macOS' And '$(_PlatformName)' != 'MacCatalyst'">true</_RunAotCompiler>
 			<!-- We need it if the interpreter is enabled, no matter where -->
@@ -952,7 +1007,9 @@
 			<_LibXamarinLinkMode Condition="'$(_LibXamarinLinkMode)' == ''">static</_LibXamarinLinkMode>
 			<_LibXamarinExtension Condition="'$(_LibXamarinLinkMode)' == 'dylib'">dylib</_LibXamarinExtension>
 			<_LibXamarinExtension Condition="'$(_LibXamarinLinkMode)' == 'static'">a</_LibXamarinExtension>
-			<_LibXamarinRuntime Condition="'$(UseMonoRuntime)' != 'true'">-coreclr</_LibXamarinRuntime>
+			<_LibXamarinRuntime Condition="'$(_XamarinRuntime)' == 'MonoVM'"></_LibXamarinRuntime>
+			<_LibXamarinRuntime Condition="'$(_XamarinRuntime)' == 'CoreCLR'">-coreclr</_LibXamarinRuntime>
+			<_LibXamarinRuntime Condition="'$(_XamarinRuntime)' == 'NativeAOT'">-nativeaot</_LibXamarinRuntime>
 			<_LibXamarinDebug Condition="'$(_BundlerDebug)' == 'true'">-debug</_LibXamarinDebug>
 			<_LibXamarinName Condition="'$(_LibXamarinName)' == ''">libxamarin-dotnet$(_LibXamarinRuntime)$(_LibXamarinDebug).$(_LibXamarinExtension)</_LibXamarinName>
 
@@ -1121,6 +1178,117 @@
 		</ItemGroup>
 	</Target>
 
+	<Target Name="_XamarinComputeIlcCompileInputs">
+		<PropertyGroup>
+			<!-- Ask ILC to produce a static library -->
+			<NativeLib>static</NativeLib>
+		</PropertyGroup>
+
+		<!-- Remove libSystem.Net.Security.Native.a from tvOS when using NativeAOT, it's not shipped on tvOS -->
+		<ItemGroup>
+			<DirectPInvoke Remove="libSystem.Net.Security.Native" />
+			<NetCoreAppNativeLibrary Remove="System.Net.Security.Native" />
+			<NativeLibrary Remove="@(NativeLibrary)" Condition="'%(Filename)' == 'libSystem.Net.Security.Native'" />
+			<LinkerArg Remove="@(LinkerArg)" Condition="'%(Filename)' == 'libSystem.Net.Security.Native'" />
+		</ItemGroup>
+
+		<ItemGroup>
+			<!-- We need to mark all __Internal P/Invokes as direct, so that the native linker doesn't remove them -->
+			<DirectPInvoke Include="__Internal" />
+
+			<!-- Give ILLink's output to ILC -->
+			<_UpdatedManagedAssemblyToLink Include="@(ManagedAssemblyToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" Condition="Exists('$(IntermediateLinkDir)%(Filename)%(Extension)')" />
+			<ManagedAssemblyToLink Remove="@(ManagedAssemblyToLink)" />
+			<ManagedAssemblyToLink Include="@(PrivateSdkAssemblies);@(_UpdatedManagedAssemblyToLink)" />
+			<ManagedBinary Include="$(IntermediateLinkDir)$(TargetName)$(TargetExt)" />
+			<IlcCompileInput Include="@(ManagedBinary)" />
+			<IlcReference Include="@(ManagedAssemblyToLink)" Exclude="@(ManagedBinary)" />
+
+			<!-- Process UnmanagedCallersOnly attributes from every assembly -->
+			<UnmanagedEntryPointsAssembly Remove="System.Private.CoreLib" />
+			<UnmanagedEntryPointsAssembly Include="@(_UpdatedManagedAssemblyToLink->'%(Filename)')" />
+
+			<!-- Disable trimming by default (assemblies aren't trimmed unless they're marked IsTrimmable) -->
+			<IlcArg Include="--defaultrooting" />
+
+			<!-- Link in the output from ILC -->
+			<_NativeExecutableObjectFiles Include="$(NativeObject)" />
+		</ItemGroup>
+
+		<ItemGroup>
+			<!--
+
+				We need to adjust TrimmerRootAssembly, because ILLink sets it
+				to @(IntermediateAssembly), but for ILC it must be the output
+				from ILLink and not its input.
+
+				Here we first create the intersection of TrimmerRootAssembly
+				and IntermediateAssembly (so that we only add the fixed
+				versions of IntermediateAssembly to TrimmerRootAssembly if
+				it's already there in the first place), then we remove it from
+				TrimmerRootAssembly, before adding the fixed version back.
+
+				Note that we only need the filename in TrimmerRootAssembly,
+				not the full path.
+
+			-->
+			<_TrimmerRootAssemblyButOnlyIntermediateAssembly Include="@(TrimmerRootAssembly)" Condition="'@(TrimmerRootAssembly)' == '@(IntermediateAssembly)' And '%(Identity)' != ''" />
+			<TrimmerRootAssembly Remove="@(_TrimmerRootAssemblyButOnlyIntermediateAssembly)" />
+			<TrimmerRootAssembly Include="@(_TrimmerRootAssemblyButOnlyIntermediateAssembly->'%(Filename)')" />
+		</ItemGroup>
+
+		<!--
+
+			Collect all the native linker args ILC would have used and use
+			them ourselves when we link natively.
+
+			Unfortunately there's a major difference between LinkerArgs (as
+			used by ILC) and our _CustomLinkerFlags: ILC splits on space in
+			LinkerArg, while we quote (when necessary) the items in
+			_CustomLinkerFlags.
+
+			Example:
+
+				<LinkerArg Include="-framework UIKit" />
+
+					ILC will pass that as two separate arguments to the native
+					compiler
+
+				<_CustomLinkFlags Include="-framework UIKit" />
+
+					We'll pass that as a single argument ('-framework UIKit')
+					to the native compiler (which the native compiler doesn't
+					like)
+
+			So we need to convert the list of space-separated items in
+			LinkerArg to space-preserved items in _CustomLinkFlags.
+
+			How to do this in MSBuild is somewhat non-obvious: first we
+			convert the LinkerArg item group to a property where every item is
+			separated by a semi-colon, and where we've also replaced spaces by
+			semi-colons. This means we have a property where every flag we
+			want is separated by a semi-colon. Then we split on the property
+			string on a semi-colon and add those items to the _CustomLinkFlags
+			item group.
+
+			Note: this means ILC doesn't support linker flags with spaces in them.
+
+		-->
+		<PropertyGroup>
+			<_LinkerArgsSplitBySemiColon>@(LinkerArg->Replace(' ',';'))</_LinkerArgsSplitBySemiColon>
+		</PropertyGroup>
+		<ItemGroup>
+			<_CustomLinkFlags Include="$(_LinkerArgsSplitBySemiColon.Split(';'))" />
+		</ItemGroup>
+
+		<ItemGroup>
+			<IlcSatelliteAssembly Include="@(ResourceCopyLocalItems)" Condition="'%(ResourceCopyLocalItems.Culture)' != '' And '%(ResourceCopyLocalItems.Culture)' != 'neutral'" />
+			<IlcSatelliteAssembly Include="@(IntermediateSatelliteAssembliesWithTargetPath)" Condition="'%(IntermediateSatelliteAssembliesWithTargetPath.Culture)' != '' And '%(IntermediateSatelliteAssembliesWithTargetPath.Culture)' != 'neutral'" />
+			<!-- Culture is not set on ReferenceSatellitePaths items, so use DestinationSubDirectory instead -->
+			<IlcSatelliteAssembly Include="@(ReferenceSatellitePaths)" Condition="'%(ReferenceSatellitePaths.DestinationSubDirectory)' != ''" />
+		</ItemGroup>
+	</Target>
+
 	<PropertyGroup>
 		<_GenerateBindingsDependsOn>
 			_ComputeBindingVariables;
@@ -1165,6 +1333,11 @@
 			_ComputeNativeExecutableInputs;
 			_AOTCompile;
 		</_CompileNativeExecutableDependsOn>
+
+		<_CompileNativeExecutableDependsOn Condition="'$(_UseNativeAot)' == 'true'">
+			$(_CompileNativeExecutableDependsOn);
+			IlcCompile;
+		</_CompileNativeExecutableDependsOn>
 	</PropertyGroup>
 
 	<Target Name="_CompileNativeExecutable"
@@ -1378,11 +1551,12 @@
 			<_DefaultLinkMode>TrimMode</_DefaultLinkMode>
 		</PropertyGroup>
 		<PropertyGroup Condition="'$(TrimMode)' == ''">
-			<_DefaultLinkMode Condition="'$(_PlatformName)' == 'macOS'">None</_DefaultLinkMode> <!-- Linking is off by default for macOS apps -->
-			<_DefaultLinkMode Condition="'$(_PlatformName)' == 'MacCatalyst' And '$(Configuration)' == 'Release'">SdkOnly</_DefaultLinkMode> <!-- Default linking is on for release builds for Mac Catalyst apps -->
-			<_DefaultLinkMode Condition="'$(_PlatformName)' == 'MacCatalyst' And '$(Configuration)' != 'Release'">None</_DefaultLinkMode> <!-- Default linking is off for non-release builds for Mac Catalyst apps -->
-			<_DefaultLinkMode Condition="'$(_PlatformName)' != 'macOS' And '$(_PlatformName)' != 'MacCatalyst' And '$(_SdkIsSimulator)' == 'true'">None</_DefaultLinkMode> <!-- Linking is off by default in the simulator -->
-			<_DefaultLinkMode Condition="'$(_PlatformName)' != 'macOS' And '$(_PlatformName)' != 'MacCatalyst' And '$(_SdkIsSimulator)' != 'true'">SdkOnly</_DefaultLinkMode> <!-- Linking is SdkOnly for iOS/tvOS/watchOS apps on device -->
+			<_DefaultLinkMode Condition="'$(_UseNativeAot)' != 'true'">Full</_DefaultLinkMode> <!-- Linking is always on for all assemblies when using NativeAOT - this is because we need to modify all assemblies in the linker for them to be compatible with NativeAOT -->
+			<_DefaultLinkMode Condition="'$(_UseNativeAot)' != 'true' And '$(_PlatformName)' == 'macOS'">None</_DefaultLinkMode> <!-- Linking is off by default for macOS apps -->
+			<_DefaultLinkMode Condition="'$(_UseNativeAot)' != 'true' And '$(_PlatformName)' == 'MacCatalyst' And '$(Configuration)' == 'Release'">SdkOnly</_DefaultLinkMode> <!-- Default linking is on for release builds for Mac Catalyst apps -->
+			<_DefaultLinkMode Condition="'$(_UseNativeAot)' != 'true' And '$(_PlatformName)' == 'MacCatalyst' And '$(Configuration)' != 'Release'">None</_DefaultLinkMode> <!-- Default linking is off for non-release builds for Mac Catalyst apps -->
+			<_DefaultLinkMode Condition="'$(_UseNativeAot)' != 'true' And '$(_PlatformName)' != 'macOS' And '$(_PlatformName)' != 'MacCatalyst' And '$(_SdkIsSimulator)' == 'true'">None</_DefaultLinkMode> <!-- Linking is off by default in the simulator -->
+			<_DefaultLinkMode Condition="'$(_UseNativeAot)' != 'true' And '$(_PlatformName)' != 'macOS' And '$(_PlatformName)' != 'MacCatalyst' And '$(_SdkIsSimulator)' != 'true'">SdkOnly</_DefaultLinkMode> <!-- Linking is SdkOnly for iOS/tvOS/watchOS apps on device -->
 		</PropertyGroup>
 	</Target>
 	<PropertyGroup>
@@ -1488,7 +1662,7 @@
 				            '%(ResolvedFileToPublish.Filename)%(ResolvedFileToPublish.Extension)' == '$(_GlobalizationDataFile)' And
 				            '%(ResolvedFileToPublish.NuGetPackageId)' == '$(_MonoNugetPackageId)'
 				            "
-				PublishFolderType="Assembly"
+				PublishFolderType="$(_GlobalizationDataFileLocation)"
 			/>
 
 			<!-- Remove the libxamarin-*.dylib files we don't want -->
@@ -1528,7 +1702,7 @@
 			<!-- Ref: https://github.com/xamarin/xamarin-macios/issues/11432 -->
 			<_CreateDumpExecutable
 				Include="@(ResolvedFileToPublish)"
-				Condition=" '$(UseMonoRuntime)' == 'false' And
+				Condition=" '$(_XamarinRuntime)' == 'CoreCLR' And
 				            '%(ResolvedFileToPublish.Filename)' == 'createdump' And
 				            '%(ResolvedFileToPublish.Extension)' == '' And
 				            '%(ResolvedFileToPublish.AssetType)' == 'native' And
diff --git a/external/MonoTouch.Dialog b/external/MonoTouch.Dialog
index 94359c74ef48..f500f9a48dbf 160000
--- a/external/MonoTouch.Dialog
+++ b/external/MonoTouch.Dialog
@@ -1 +1 @@
-Subproject commit 94359c74ef481ce7b5daa28b890f35a6e77d94c8
+Subproject commit f500f9a48dbf856c693b255078e1507cf1e8edde
diff --git a/external/Touch.Unit b/external/Touch.Unit
index 8d80e1f10414..5739adefc261 160000
--- a/external/Touch.Unit
+++ b/external/Touch.Unit
@@ -1 +1 @@
-Subproject commit 8d80e1f10414ec85afa14c8c45a05334eb37c6dd
+Subproject commit 5739adefc261b7bfeb70ff718c7b2d97bdfa42ee
diff --git a/mk/rules.mk b/mk/rules.mk
index 2d309e3389fa..6910cdda651e 100644
--- a/mk/rules.mk
+++ b/mk/rules.mk
@@ -297,6 +297,8 @@ $(eval $(call NativeCompilationTemplate,-dotnet,-O2 -DDOTNET))
 $(eval $(call NativeCompilationTemplate,-dotnet-debug,-DDEBUG -DDOTNET))
 $(eval $(call NativeCompilationTemplate,-dotnet-coreclr,-O2 -DCORECLR_RUNTIME -DDOTNET))
 $(eval $(call NativeCompilationTemplate,-dotnet-coreclr-debug,-DDEBUG -DCORECLR_RUNTIME -DDOTNET))
+$(eval $(call NativeCompilationTemplate,-dotnet-nativeaot,-O2 -DCORECLR_RUNTIME -DDOTNET -DNATIVEAOT))
+$(eval $(call NativeCompilationTemplate,-dotnet-nativeaot-debug,-DDEBUG -DCORECLR_RUNTIME -DDOTNET -DNATIVEAOT))
 
 .libs/iphoneos .libs/iphonesimulator .libs/watchos .libs/watchsimulator .libs/tvos .libs/tvsimulator .libs/maccatalyst .libs/mac:
 	$(Q) mkdir -p $@
diff --git a/runtime/Makefile b/runtime/Makefile
index 2db40b5d7b0f..ea15e991e25c 100644
--- a/runtime/Makefile
+++ b/runtime/Makefile
@@ -22,7 +22,7 @@ SHIPPED_HEADERS +=         \
 	xamarin/monovm-bridge.h \
 	xamarin/coreclr-bridge.h \
 
-SHARED_SOURCES += mono-runtime.m bindings.m bindings-generated.m shared.m runtime.m trampolines.m trampolines-invoke.m xamarin-support.m nsstring-localization.m trampolines-varargs.m monovm-bridge.m coreclr-bridge.m
+SHARED_SOURCES += mono-runtime.m bindings.m bindings-generated.m shared.m runtime.m trampolines.m trampolines-invoke.m xamarin-support.m nsstring-localization.m trampolines-varargs.m monovm-bridge.m coreclr-bridge.m nativeaot-bridge.m
 SHARED_I386_SOURCES += trampolines-i386.m trampolines-i386-asm.s trampolines-i386-objc_msgSend.s trampolines-i386-objc_msgSendSuper.s trampolines-i386-objc_msgSend_stret.s trampolines-i386-objc_msgSendSuper_stret.s
 SHARED_X86_64_SOURCES += trampolines-x86_64.m trampolines-x86_64-asm.s trampolines-x86_64-objc_msgSend.s trampolines-x86_64-objc_msgSendSuper.s trampolines-x86_64-objc_msgSend_stret.s trampolines-x86_64-objc_msgSendSuper_stret.s
 SHARED_ARM64_SOURCES += trampolines-arm64.m trampolines-arm64-asm.s trampolines-arm64-objc_msgSend.s trampolines-arm64-objc_msgSendSuper.s
@@ -454,6 +454,11 @@ MAC_LIBS =                   \
 #    This is used when using the CoreCLR runtime instead of Mono.
 #    CORECLR_RUNTIME is defined for these versions of libxamarin.
 #
+# libxamarin-nativeaot.a:
+#    This is used when using NativeAOT.
+#    CORECLR_RUNTIME is defined for these versions of libxamarin (because the nativeaot bridge shares *a lot* of code with the coreclr bridge)
+#    NATIVEAOT is defined for these versions of libxamarin
+#
 
 ifdef INCLUDE_XAMARIN_LEGACY
 all-local:: $(TARGETS)
@@ -614,6 +619,15 @@ endef
 
 $(foreach platform,$(DOTNET_CORECLR_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(eval $(call DotNetCoreClrLibTemplate,$(platform),$(rid)))))
 
+define DotNetNativeAotLibTemplate
+DOTNET_TARGETS += \
+	$(DOTNET_DESTDIR)/Microsoft.$(1).Runtime.$(2)/runtimes/$(2)/native/libxamarin-dotnet-nativeaot.a \
+	$(DOTNET_DESTDIR)/Microsoft.$(1).Runtime.$(2)/runtimes/$(2)/native/libxamarin-dotnet-nativeaot-debug.a \
+
+endef
+
+$(foreach platform,$(DOTNET_NATIVEAOT_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(eval $(call DotNetNativeAotLibTemplate,$(platform),$(rid)))))
+
 # a few lookup tables, because the data we have is not always in the format we need it
 DOTNET_iphonesimulator_DYLIB_FLAGS=-lmonosgen-2.0 -licudata -licui18n -licuuc -framework UIKit
 DOTNET_iphoneos_DYLIB_FLAGS=-lmonosgen-2.0 -licudata -licui18n -licuuc -framework UIKit
@@ -704,6 +718,8 @@ $(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIM
 $(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(foreach arch,$(DOTNET_$(rid)_ARCHITECTURES),$(eval $(call DotNetLibXamarinTemplate,$(DOTNET_$(rid)_SDK_PLATFORM),$(rid),$(arch),_DEBUG,-dotnet-debug,,.mono)))))
 $(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(foreach arch,$(DOTNET_$(rid)_ARCHITECTURES),$(eval $(call DotNetLibXamarinTemplate,$(DOTNET_$(rid)_SDK_PLATFORM),$(rid),$(arch),_CORECLR,-dotnet-coreclr,_CORECLR)))))
 $(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(foreach arch,$(DOTNET_$(rid)_ARCHITECTURES),$(eval $(call DotNetLibXamarinTemplate,$(DOTNET_$(rid)_SDK_PLATFORM),$(rid),$(arch),_CORECLR_DEBUG,-dotnet-coreclr-debug,_CORECLR)))))
+$(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(foreach arch,$(DOTNET_$(rid)_ARCHITECTURES),$(eval $(call DotNetLibXamarinTemplate,$(DOTNET_$(rid)_SDK_PLATFORM),$(rid),$(arch),_NATIVEAOT,-dotnet-nativeaot,_NATIVEAOT,.mono)))))
+$(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(foreach arch,$(DOTNET_$(rid)_ARCHITECTURES),$(eval $(call DotNetLibXamarinTemplate,$(DOTNET_$(rid)_SDK_PLATFORM),$(rid),$(arch),_NATIVEAOT_DEBUG,-dotnet-nativeaot-debug,_NATIVEAOT,.mono)))))
 
 #
 # DotNetLibExtensionTemplate builds lib[tv]extension.a
@@ -731,6 +747,7 @@ endef
 
 $(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(foreach arch,$(DOTNET_$(rid)_ARCHITECTURES),$(eval $(call DotNetLibExtensionTemplate,$(DOTNET_$(rid)_SDK_PLATFORM),$(rid),$(arch),,-dotnet,,)))))
 $(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(foreach arch,$(DOTNET_$(rid)_ARCHITECTURES),$(eval $(call DotNetLibExtensionTemplate,$(DOTNET_$(rid)_SDK_PLATFORM),$(rid),$(arch),_CORECLR,-dotnet-coreclr,_CORECLR)))))
+$(foreach platform,$(DOTNET_PLATFORMS),$(foreach rid,$(DOTNET_$(platform)_RUNTIME_IDENTIFIERS),$(foreach arch,$(DOTNET_$(rid)_ARCHITECTURES),$(eval $(call DotNetLibExtensionTemplate,$(DOTNET_$(rid)_SDK_PLATFORM),$(rid),$(arch),_NATOVEAOT,-dotnet-nativeaot,_NATOVEAOT)))))
 
 dotnet: $(DOTNET_TARGETS)
 
diff --git a/runtime/coreclr-bridge.m b/runtime/coreclr-bridge.m
index 287c3f963534..ba7ec923e6fc 100644
--- a/runtime/coreclr-bridge.m
+++ b/runtime/coreclr-bridge.m
@@ -11,6 +11,7 @@
 #include <sys/stat.h>
 #include <inttypes.h>
 #include <pthread.h>
+#include <sys/mman.h>
 
 #include "product.h"
 #include "runtime-internal.h"
@@ -477,6 +478,7 @@
 		munmap ((void *) buf, fd_len);
 }
 
+#if !defined (NATIVEAOT)
 bool
 xamarin_bridge_vm_initialize (int propertyCount, const char **propertyKeys, const char **propertyValues)
 {
@@ -510,6 +512,7 @@
 
 	return rv == 0;
 }
+#endif // !defined (NATIVEAOT)
 
 void
 xamarin_install_nsautoreleasepool_hooks ()
@@ -537,18 +540,20 @@
 	xamarin_assertion_message ("%s threw an exception: %p = %s", method, gchandle, [xamarin_print_all_exceptions (gchandle) UTF8String]);
 }
 
-typedef void (*xamarin_runtime_initialize_decl)(struct InitializationOptions* options);
+#if !defined (NATIVEAOT)
+typedef void (*xamarin_runtime_initialize_decl)(struct InitializationOptions* options, GCHandle* exception_gchandle);
 void
 xamarin_bridge_call_runtime_initialize (struct InitializationOptions* options, GCHandle* exception_gchandle)
 {
 	void *del = NULL;
-	int rv = coreclr_create_delegate (coreclr_handle, coreclr_domainId, PRODUCT ", Version=0.0.0.0", "ObjCRuntime.Runtime", "Initialize", &del);
+	int rv = coreclr_create_delegate (coreclr_handle, coreclr_domainId, PRODUCT ", Version=0.0.0.0", "ObjCRuntime.Runtime", "SafeInitialize", &del);
 	if (rv != 0)
 		xamarin_assertion_message ("xamarin_bridge_call_runtime_initialize: failed to create delegate: %i\n", rv);
 
 	xamarin_runtime_initialize_decl runtime_initialize = (xamarin_runtime_initialize_decl) del;
-	runtime_initialize (options);
+	runtime_initialize (options, exception_gchandle);
 }
+#endif // !defined (NATIVEAOT)
 
 void
 xamarin_bridge_register_product_assembly (GCHandle* exception_gchandle)
@@ -737,6 +742,7 @@
 	return rv;
 }
 
+#if !defined (NATIVEAOT)
 int
 mono_jit_exec (MonoDomain * domain, MonoAssembly * assembly, int argc, const char** argv)
 {
@@ -766,6 +772,7 @@
 
 	return (int) exitCode;
 }
+#endif // !defined (NATIVEAOT)
 
 MonoGHashTable *
 mono_g_hash_table_new_type (GHashFunc hash_func, GEqualFunc key_equal_func, MonoGHashGCType type)
diff --git a/runtime/monotouch-main.m b/runtime/monotouch-main.m
index 4f9646b6bd47..c2f8cc0ed30f 100644
--- a/runtime/monotouch-main.m
+++ b/runtime/monotouch-main.m
@@ -454,12 +454,14 @@ - (void) memoryWarning: (NSNotification *) sender
 		xamarin_process_fatal_exception_gchandle (exception_gchandle, "An exception occurred while opening an assembly");
 	}
 
+#if SUPPORTS_DYNAMIC_REGISTRATION
 	if (xamarin_supports_dynamic_registration) {
 		MonoReflectionAssembly *rassembly = mono_assembly_get_object (mono_domain_get (), assembly);
 		xamarin_register_entry_assembly (rassembly, &exception_gchandle);
 		xamarin_mono_object_release (&rassembly);
 		xamarin_process_fatal_exception_gchandle (exception_gchandle, "An exception occurred while opening the entry assembly");
 	}
+#endif // SUPPORTS_DYNAMIC_REGISTRATION
 
 	DEBUG_LAUNCH_TIME_PRINT ("\tAssembly register time");
 
diff --git a/runtime/nativeaot-bridge.m b/runtime/nativeaot-bridge.m
new file mode 100644
index 000000000000..74587bb31399
--- /dev/null
+++ b/runtime/nativeaot-bridge.m
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+*  Authors: Rolf Bjarne Kvinge
+*
+*  Copyright (C) 2023 Microsoft Corp.
+*
+*/
+
+#if defined (NATIVEAOT)
+
+#include <sys/stat.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <dlfcn.h>
+
+#include "product.h"
+#include "runtime-internal.h"
+#include "slinked-list.h"
+#include "xamarin/xamarin.h"
+#include "xamarin/coreclr-bridge.h"
+#include "xamarin/nativeaot-bridge.h"
+
+void
+xamarin_bridge_call_runtime_initialize (struct InitializationOptions* options, GCHandle* exception_gchandle)
+{
+	xamarin_objcruntime_runtime_nativeaotinitialize (options, exception_gchandle);
+}
+
+bool
+xamarin_bridge_vm_initialize (int propertyCount, const char **propertyKeys, const char **propertyValues)
+{
+	return true;
+}
+
+int
+mono_jit_exec (MonoDomain * domain, MonoAssembly * assembly, int argc, const char** argv)
+{
+	return __managed__Main (argc, argv);
+}
+
+#endif // NATIVEAOT
diff --git a/runtime/runtime.m b/runtime/runtime.m
index b38ff6661f8a..dfb9123876d3 100644
--- a/runtime/runtime.m
+++ b/runtime/runtime.m
@@ -82,7 +82,9 @@
 enum MarshalManagedExceptionMode xamarin_marshal_managed_exception_mode = MarshalManagedExceptionModeDefault;
 enum XamarinTriState xamarin_log_exceptions = XamarinTriStateNone;
 enum XamarinLaunchMode xamarin_launch_mode = XamarinLaunchModeApp;
+#if SUPPORTS_DYNAMIC_REGISTRATION
 bool xamarin_supports_dynamic_registration = true;
+#endif
 const char *xamarin_runtime_configuration_name = NULL;
 
 #if DOTNET
@@ -141,6 +143,7 @@
 	/* unused									= 0x08,*/
 	InitializationFlagsIsSimulator				= 0x10,
 	InitializationFlagsIsCoreCLR                = 0x20,
+	InitializationFlagsIsNativeAOT              = 0x40,
 };
 
 struct InitializationOptions {
@@ -966,7 +969,9 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags;
 xamarin_register_monoassembly (MonoAssembly *assembly, GCHandle *exception_gchandle)
 {
 	// COOP: this is a function executed only at startup, I believe the mode here doesn't matter.
+#if SUPPORTS_DYNAMIC_REGISTRATION
 	if (!xamarin_supports_dynamic_registration) {
+#endif
 #if defined (CORECLR_RUNTIME)
 		if (xamarin_log_level > 0) {
 			MonoReflectionAssembly *rassembly = mono_assembly_get_object (mono_domain_get (), assembly);
@@ -983,11 +988,16 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags;
 		LOG (PRODUCT ": Skipping assembly registration for %s since it's not needed (dynamic registration is not supported)", mono_assembly_name_get_name (mono_assembly_get_name (assembly)));
 #endif
 		return true;
+#if SUPPORTS_DYNAMIC_REGISTRATION
 	}
+#endif
+
+#if SUPPORTS_DYNAMIC_REGISTRATION
 	MonoReflectionAssembly *rassembly = mono_assembly_get_object (mono_domain_get (), assembly);
 	xamarin_register_assembly (rassembly, exception_gchandle);
 	xamarin_mono_object_release (&rassembly);
 	return *exception_gchandle == INVALID_GCHANDLE;
+#endif // SUPPORTS_DYNAMIC_REGISTRATION
 }
 
 // Returns a retained MonoObject. Caller must release.
@@ -1293,6 +1303,9 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags;
 #if defined (CORECLR_RUNTIME)
 	options.flags = (enum InitializationFlags) (options.flags | InitializationFlagsIsCoreCLR);
 #endif
+#if defined (NATIVEAOT)
+	options.flags = (enum InitializationFlags) (options.flags | InitializationFlagsIsNativeAOT);
+#endif
 
 	options.Delegates = &delegates;
 	options.Trampolines = &trampolines;
@@ -1304,12 +1317,14 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags;
 #endif
 
 #if defined (CORECLR_RUNTIME)
+#if !defined(__arm__) // the dynamic trampolines haven't been implemented in 32-bit ARM assembly.
 	options.xamarin_objc_msgsend = (void *) xamarin_dyn_objc_msgSend;
 	options.xamarin_objc_msgsend_super = (void *) xamarin_dyn_objc_msgSendSuper;
 #if !defined(__aarch64__)
 	options.xamarin_objc_msgsend_stret = (void *) xamarin_dyn_objc_msgSend_stret;
 	options.xamarin_objc_msgsend_super_stret = (void *) xamarin_dyn_objc_msgSendSuper_stret;
 #endif // !defined(__aarch64__)
+#endif // !defined(__arm__)
 	options.unhandled_exception_handler = (void *) &xamarin_coreclr_unhandled_exception_handler;
 	options.reference_tracking_begin_end_callback = (void *) &xamarin_coreclr_reference_tracking_begin_end_callback;
 	options.reference_tracking_is_referenced_callback = (void *) &xamarin_coreclr_reference_tracking_is_referenced_callback;
@@ -2574,12 +2589,14 @@ -(void) xamarinSetFlags: (enum XamarinGCHandleFlags) flags;
 	int subtractPropertyCount = 0;
 
 	if (xamarin_icu_dat_file_name != NULL && *xamarin_icu_dat_file_name != 0) {
+#if !defined (NATIVEAOT)
 		char path [1024];
 		if (!xamarin_locate_app_resource (xamarin_icu_dat_file_name, path, sizeof (path))) {
 			LOG (PRODUCT ": Could not locate the ICU data file '%s' in the app bundle.\n", xamarin_icu_dat_file_name);
 		} else {
 			icu_dat_file_path = strdup (path);
 		}
+#endif // !defined (NATIVEAOT)
 	} else {
 		subtractPropertyCount++;
 	}
diff --git a/runtime/xamarin/main.h b/runtime/xamarin/main.h
index 201a09a24d82..a3b51d7745e5 100644
--- a/runtime/xamarin/main.h
+++ b/runtime/xamarin/main.h
@@ -99,6 +99,12 @@ enum XamarinTriState : int {
 
 extern bool mono_use_llvm; // this is defined inside mono
 
+#if defined (NATIVEAOT)
+#define SUPPORTS_DYNAMIC_REGISTRATION 0
+#else
+#define SUPPORTS_DYNAMIC_REGISTRATION 1
+#endif
+
 #if DEBUG
 extern bool xamarin_gc_pump;
 #endif
@@ -121,7 +127,9 @@ extern bool xamarin_is_gc_coop;
 extern enum MarshalObjectiveCExceptionMode xamarin_marshal_objectivec_exception_mode;
 extern enum MarshalManagedExceptionMode xamarin_marshal_managed_exception_mode;
 extern enum XamarinLaunchMode xamarin_launch_mode;
+#if SUPPORTS_DYNAMIC_REGISTRATION
 extern bool xamarin_supports_dynamic_registration;
+#endif
 extern const char *xamarin_runtime_configuration_name;
 extern enum XamarinNativeLinkMode xamarin_libmono_native_link_mode;
 extern const char** xamarin_runtime_libraries;
diff --git a/runtime/xamarin/nativeaot-bridge.h b/runtime/xamarin/nativeaot-bridge.h
new file mode 100644
index 000000000000..6de5ad88117b
--- /dev/null
+++ b/runtime/xamarin/nativeaot-bridge.h
@@ -0,0 +1,26 @@
+
+/* Support for using NativeAOT */
+
+#if defined (NATIVEAOT)
+
+#ifndef __NATIVEAOT_BRIDGE__
+#define __NATIVEAOT_BRIDGE__
+
+#include <stdatomic.h>
+
+#include "runtime.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void xamarin_objcruntime_runtime_nativeaotinitialize (struct InitializationOptions* options, GCHandle* exception_gchandle);
+int __managed__Main (int argc, const char** argv);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __NATIVEAOT_BRIDGE__ */
+
+#endif // NATIVEAOT
diff --git a/src/Foundation/NSObject2.cs b/src/Foundation/NSObject2.cs
index f88f1a0492c2..1f645d3044ba 100644
--- a/src/Foundation/NSObject2.cs
+++ b/src/Foundation/NSObject2.cs
@@ -305,6 +305,12 @@ static void RegisterToggleReference (NSObject obj, IntPtr handle, bool isCustomT
 		{
 #if NET && __MACOS__
 			Runtime.RegisterToggleReferenceCoreCLR (obj, handle, isCustomType);
+#elif NET
+			if (Runtime.IsCoreCLR) {
+				Runtime.RegisterToggleReferenceCoreCLR (obj, handle, isCustomType);
+			} else {
+				RegisterToggleRef (obj, handle, isCustomType);
+			}
 #else
 			RegisterToggleRef (obj, handle, isCustomType);
 #endif
diff --git a/src/ILLink.Substitutions.MacCatalyst.xml b/src/ILLink.Substitutions.MacCatalyst.xml
index 82dfcf97ae5c..21704812349f 100644
--- a/src/ILLink.Substitutions.MacCatalyst.xml
+++ b/src/ILLink.Substitutions.MacCatalyst.xml
@@ -4,7 +4,10 @@
       <method signature="System.Void WarnOnce()" body="stub" feature="System.Diagnostics.Debugger.IsSupported" featurevalue="false" />
     </type>
     <type fullname="ObjCRuntime.Runtime">
-      <method signature="System.Boolean get_IsCoreCLR()" body="stub" value="false" />
+      <method signature="System.Boolean get_IsCoreCLR()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="false" value="false" />
+      <method signature="System.Boolean get_IsCoreCLR()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="true" value="true" />
+      <method signature="System.Boolean get_IsNativeAOT()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="false" value="false" />
+      <method signature="System.Boolean get_IsNativeAOT()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="true" value="true" />
       <method signature="System.Boolean get_IsManagedStaticRegistrar()" body="stub" feature="ObjCRuntime.Runtime.IsManagedStaticRegistrar" featurevalue="false" value="false" />
       <method signature="System.Boolean get_IsManagedStaticRegistrar()" body="stub" feature="ObjCRuntime.Runtime.IsManagedStaticRegistrar" featurevalue="true" value="true" />
     </type>
diff --git a/src/ILLink.Substitutions.ios.xml b/src/ILLink.Substitutions.ios.xml
index 00e9e50e50e6..c4d2a0a4228e 100644
--- a/src/ILLink.Substitutions.ios.xml
+++ b/src/ILLink.Substitutions.ios.xml
@@ -4,7 +4,10 @@
       <method signature="System.Void WarnOnce()" body="stub" feature="System.Diagnostics.Debugger.IsSupported" featurevalue="false" />
     </type>
     <type fullname="ObjCRuntime.Runtime">
-      <method signature="System.Boolean get_IsCoreCLR()" body="stub" value="false" />
+      <method signature="System.Boolean get_IsCoreCLR()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="false" value="false" />
+      <method signature="System.Boolean get_IsCoreCLR()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="true" value="true" />
+      <method signature="System.Boolean get_IsNativeAOT()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="false" value="false" />
+      <method signature="System.Boolean get_IsNativeAOT()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="true" value="true" />
       <method signature="System.Int32 GetRuntimeArch()" body="stub" feature="ObjCRuntime.Runtime.Arch.IsSimulator" featurevalue="false" value="0" />
       <method signature="System.Int32 GetRuntimeArch()" body="stub" feature="ObjCRuntime.Runtime.Arch.IsSimulator" featurevalue="true" value="1" />
       <method signature="System.Boolean get_IsManagedStaticRegistrar()" body="stub" feature="ObjCRuntime.Runtime.IsManagedStaticRegistrar" featurevalue="false" value="false" />
diff --git a/src/ILLink.Substitutions.macOS.xml b/src/ILLink.Substitutions.macOS.xml
index a0fd78280ac9..e993b4cc124c 100644
--- a/src/ILLink.Substitutions.macOS.xml
+++ b/src/ILLink.Substitutions.macOS.xml
@@ -5,6 +5,8 @@
     </type>
     <type fullname="ObjCRuntime.Runtime">
       <method signature="System.Boolean get_IsCoreCLR()" body="stub" value="true" />
+      <method signature="System.Boolean get_IsNativeAOT()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="false" value="false" />
+      <method signature="System.Boolean get_IsNativeAOT()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="true" value="true" />
       <method signature="System.Boolean get_IsManagedStaticRegistrar()" body="stub" feature="ObjCRuntime.Runtime.IsManagedStaticRegistrar" featurevalue="false" value="false" />
       <method signature="System.Boolean get_IsManagedStaticRegistrar()" body="stub" feature="ObjCRuntime.Runtime.IsManagedStaticRegistrar" featurevalue="true" value="true" />
     </type>
diff --git a/src/ILLink.Substitutions.tvos.xml b/src/ILLink.Substitutions.tvos.xml
index e03cc08a2fba..d057a9395ad0 100644
--- a/src/ILLink.Substitutions.tvos.xml
+++ b/src/ILLink.Substitutions.tvos.xml
@@ -4,7 +4,10 @@
       <method signature="System.Void WarnOnce()" body="stub" feature="System.Diagnostics.Debugger.IsSupported" featurevalue="false" />
     </type>
     <type fullname="ObjCRuntime.Runtime">
-      <method signature="System.Boolean get_IsCoreCLR()" body="stub" value="false" />
+      <method signature="System.Boolean get_IsCoreCLR()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="false" value="false" />
+      <method signature="System.Boolean get_IsCoreCLR()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="true" value="true" />
+      <method signature="System.Boolean get_IsNativeAOT()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="false" value="false" />
+      <method signature="System.Boolean get_IsNativeAOT()" body="stub" feature="ObjCRuntime.Runtime.IsNativeAOT" featurevalue="true" value="true" />
       <method signature="System.Int32 GetRuntimeArch()" body="stub" feature="ObjCRuntime.Runtime.Arch.IsSimulator" featurevalue="false" value="0" />
       <method signature="System.Int32 GetRuntimeArch()" body="stub" feature="ObjCRuntime.Runtime.Arch.IsSimulator" featurevalue="true" value="1" />
       <method signature="System.Boolean get_IsManagedStaticRegistrar()" body="stub" feature="ObjCRuntime.Runtime.IsManagedStaticRegistrar" featurevalue="false" value="false" />
diff --git a/src/ObjCRuntime/Runtime.CoreCLR.cs b/src/ObjCRuntime/Runtime.CoreCLR.cs
index b9f3622fda0e..5dfe7e77734e 100644
--- a/src/ObjCRuntime/Runtime.CoreCLR.cs
+++ b/src/ObjCRuntime/Runtime.CoreCLR.cs
@@ -506,7 +506,16 @@ static IntPtr GetAssemblyName (IntPtr gchandle)
 		static IntPtr GetAssemblyLocation (IntPtr gchandle)
 		{
 			var asm = (Assembly?) GetGCHandleTarget (gchandle);
-			return Marshal.StringToHGlobalAuto (asm?.Location);
+			if (asm is null)
+				return IntPtr.Zero;
+
+			string location;
+			if (IsNativeAOT) {
+				location = Path.Combine (System.AppContext.BaseDirectory, asm.GetName ().Name + ".dll");
+			} else {
+				location = asm.Location;
+			}
+			return Marshal.StringToHGlobalAuto (location);
 		}
 
 		static void SetFlagsForNSObject (IntPtr gchandle, byte flags)
diff --git a/src/ObjCRuntime/Runtime.cs b/src/ObjCRuntime/Runtime.cs
index f411a851b93e..f86fc65711d0 100644
--- a/src/ObjCRuntime/Runtime.cs
+++ b/src/ObjCRuntime/Runtime.cs
@@ -159,6 +159,7 @@ internal enum InitializationFlags : int {
 			IsSimulator = 0x10,
 #if NET
 			IsCoreCLR				= 0x20,
+			IsNativeAOT				= 0x40,
 #endif
 		}
 
@@ -222,6 +223,14 @@ internal unsafe static bool IsCoreCLR {
 				return (options->Flags.HasFlag (InitializationFlags.IsCoreCLR));
 			}
 		}
+
+		[BindingImpl (BindingImplOptions.Optimizable)]
+		internal unsafe static bool IsNativeAOT {
+			get {
+				// The linker may turn calls to this property into a constant
+				return (options->Flags.HasFlag (InitializationFlags.IsNativeAOT));
+			}
+		}
 #endif
 
 		[BindingImpl (BindingImplOptions.Optimizable)]
@@ -249,6 +258,20 @@ internal static bool Initialized {
 		static extern int _NSGetExecutablePath (byte[] buf, ref int bufsize);
 #endif
 
+#if NET
+		[Preserve] // called from native - nativeaot-bridge.m and coreclr-bridge.m.
+		[UnmanagedCallersOnly (EntryPoint = "xamarin_objcruntime_runtime_nativeaotinitialize")]
+		unsafe static void SafeInitialize (InitializationOptions* options, IntPtr* exception_gchandle)
+		{
+			*exception_gchandle = IntPtr.Zero;
+			try {
+				Initialize (options);
+			} catch (Exception e) {
+				*exception_gchandle = AllocGCHandle (e);
+			}
+		}
+#endif
+
 		[Preserve] // called from native - runtime.m.
 		[BindingImpl (BindingImplOptions.Optimizable)] // To inline the Runtime.DynamicRegistrationSupported code if possible.
 		unsafe static void Initialize (InitializationOptions* options)
diff --git a/tests/ComputeRegistrarConstant.targets b/tests/ComputeRegistrarConstant.targets
index 6e6c743e890e..9d5d2e9dcaf5 100644
--- a/tests/ComputeRegistrarConstant.targets
+++ b/tests/ComputeRegistrarConstant.targets
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <Project>
 	<Target Name="ComputeRegistrarConstant" BeforeTargets="BeforeBuild">
-		<PropertyGroup Condition="'$(_PlatformName)' == 'iOS' Or $(TargetFramework.EndsWith('-ios')) Or '$(_PlatformName)' == 'tvOS' Or $(TargetFramework.EndsWith('-tvos'))">
+		<PropertyGroup Condition="('$(_PlatformName)' == 'iOS' Or $(TargetFramework.EndsWith('-ios')) Or '$(_PlatformName)' == 'tvOS' Or $(TargetFramework.EndsWith('-tvos'))) And '$(_UseNativeAot)' != 'true'">
 			<IsDynamicRegistrar Condition="'$(ComputedPlatform)' == 'iPhoneSimulator' And '$(Registrar)' == ''">true</IsDynamicRegistrar>
 			<IsDynamicRegistrar Condition="'$(Registrar)' == 'dynamic'">true</IsDynamicRegistrar>
 		</PropertyGroup>
 
-		<PropertyGroup Condition="'$(_PlatformName)' == 'macOS' Or $(TargetFramework.EndsWith('-macos')) Or '$(_PlatformName)' == 'MacCatalyst' Or $(TargetFramework.EndsWith('-maccatalyst'))">
+		<PropertyGroup Condition="('$(_PlatformName)' == 'macOS' Or $(TargetFramework.EndsWith('-macos')) Or '$(_PlatformName)' == 'MacCatalyst' Or $(TargetFramework.EndsWith('-maccatalyst'))) And '$(_UseNativeAot)' != 'true'">
 			<IsDynamicRegistrar Condition="'$(Configuration)' == 'Debug' And '$(Registrar)' == ''">true</IsDynamicRegistrar>
 			<IsDynamicRegistrar Condition="'$(Registrar)' == 'dynamic'">true</IsDynamicRegistrar>
 		</PropertyGroup>
diff --git a/tests/bindings-test/ProtocolTest.cs b/tests/bindings-test/ProtocolTest.cs
index ba1d57be1f92..d1fa363842a0 100644
--- a/tests/bindings-test/ProtocolTest.cs
+++ b/tests/bindings-test/ProtocolTest.cs
@@ -164,50 +164,50 @@ public void ProtocolMembers ()
 			methods = protocol_copyMethodDescriptionList (protocol, true, true);
 			CleanupSignatures (methods);
 			Assert.AreEqual (4, methods.Length, "Required Instance Methods: Count");
-			Assert.That (methods, Contains.Item (new objc_method_description ("requiredInstanceMethod", "v@:")), "Required Instance Methods: requiredInstanceMethod");
-			Assert.That (methods, Contains.Item (new objc_method_description ("requiredInstanceProperty", "@@:")), "Required Instance Methods: requiredInstanceProperty");
-			Assert.That (methods, Contains.Item (new objc_method_description ("setRequiredInstanceProperty:", "v@:@")), "Required Instance Methods: setRequiredInstanceProperty");
-			Assert.That (methods, Contains.Item (new objc_method_description ("requiredReadonlyProperty", "@@:")), "Required Instance Methods: requiredReadonlyProperty:");
+			AssertContains (methods, new objc_method_description ("requiredInstanceMethod", "v@:"), "Required Instance Methods: requiredInstanceMethod");
+			AssertContains (methods, new objc_method_description ("requiredInstanceProperty", "@@:"), "Required Instance Methods: requiredInstanceProperty");
+			AssertContains (methods, new objc_method_description ("setRequiredInstanceProperty:", "v@:@"), "Required Instance Methods: setRequiredInstanceProperty");
+			AssertContains (methods, new objc_method_description ("requiredReadonlyProperty", "@@:"), "Required Instance Methods: requiredReadonlyProperty:");
 
 			// Required static methods
 			methods = protocol_copyMethodDescriptionList (protocol, true, false);
 			CleanupSignatures (methods);
 			Assert.AreEqual (3, methods.Length, "Required Static Methods: Count");
-			Assert.That (methods, Contains.Item (new objc_method_description ("requiredStaticMethod", "v@:")), "Required Static Methods: requiredStaticMethod");
-			Assert.That (methods, Contains.Item (new objc_method_description ("setRequiredStaticProperty:", "v@:@")), "Required Static Methods: setRequiredStaticProperty:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("requiredStaticProperty", "@@:")), "Required Static Methods: requiredStaticProperty");
+			AssertContains (methods, new objc_method_description ("requiredStaticMethod", "v@:"), "Required Static Methods: requiredStaticMethod");
+			AssertContains (methods, new objc_method_description ("setRequiredStaticProperty:", "v@:@"), "Required Static Methods: setRequiredStaticProperty:");
+			AssertContains (methods, new objc_method_description ("requiredStaticProperty", "@@:"), "Required Static Methods: requiredStaticProperty");
 
 			// Optional instance methods
 			methods = protocol_copyMethodDescriptionList (protocol, false, true);
 			CleanupSignatures (methods);
 			Assert.AreEqual (19, methods.Length, "Optional Instance Methods: Count");
-			Assert.That (methods, Contains.Item (new objc_method_description ("variadicMethod:", "v@:^v")), "Optional Instance Methods: variadicMethod:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("methodWithReturnType", "@@:")), "Optional Instance Methods: methodWithReturnType");
-			Assert.That (methods, Contains.Item (new objc_method_description ("methodWithParameter:", "v@:i")), "Optional Instance Methods: methodWithParameter:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("methodWithParameters:second:third:fourth:", "v@:iiii")), "Optional Instance Methods: methodWithParameters:second:third:fourth:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("optionalInstanceMethod", "v@:")), "Optional Instance Methods: optionalInstanceMethod");
-			Assert.That (methods, Contains.Item (new objc_method_description ("methodWithRefParameters:second:third:fourth:", "v@:i^i^ii")), "Optional Instance Methods: methodWithRefParameters:second:third:fourth:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("optionalInstanceProperty", "@@:")), "Optional Instance Methods: optionalInstanceProperty");
-			Assert.That (methods, Contains.Item (new objc_method_description ("setOptionalInstanceProperty:", "v@:@")), "Optional Instance Methods: setOptionalInstanceProperty:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("get_propertyWithCustomAccessors", "@@:")), "Optional Instance Methods: get_propertyWithCustomAccessors");
-			Assert.That (methods, Contains.Item (new objc_method_description ("set_propertyWithCustomAccessors:", "v@:@")), "Optional Instance Methods: set_propertyWithCustomAccessors:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("propertyWithArgumentSemanticNone", "@@:")), "Optional Instance Methods: propertyWithArgumentSemanticNone");
-			Assert.That (methods, Contains.Item (new objc_method_description ("setPropertyWithArgumentSemanticNone:", "v@:@")), "Optional Instance Methods: setPropertyWithArgumentSemanticNone:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("propertyWithArgumentSemanticCopy", "@@:")), "Optional Instance Methods: propertyWithArgumentSemanticCopy");
-			Assert.That (methods, Contains.Item (new objc_method_description ("setPropertyWithArgumentSemanticCopy:", "v@:@")), "Optional Instance Methods: setPropertyWithArgumentSemanticCopy:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("propertyWithArgumentSemanticAssign", "@@:")), "Optional Instance Methods: propertyWithArgumentSemanticAssign");
-			Assert.That (methods, Contains.Item (new objc_method_description ("setPropertyWithArgumentSemanticAssign:", "v@:@")), "Optional Instance Methods: setPropertyWithArgumentSemanticAssign:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("readonlyProperty", "@@:")), "Optional Instance Methods: readonlyProperty:");
-			Assert.That (methods, Contains.Item (new objc_method_description ("propertyWithArgumentSemanticRetain", "@@:")), "Optional Instance Methods: propertyWithArgumentSemanticRetain");
-			Assert.That (methods, Contains.Item (new objc_method_description ("setPropertyWithArgumentSemanticRetain:", "v@:@")), "Optional Instance Methods: setPropertyWithArgumentSemanticRetain:");
+			AssertContains (methods, new objc_method_description ("variadicMethod:", "v@:^v"), "Optional Instance Methods: variadicMethod:");
+			AssertContains (methods, new objc_method_description ("methodWithReturnType", "@@:"), "Optional Instance Methods: methodWithReturnType");
+			AssertContains (methods, new objc_method_description ("methodWithParameter:", "v@:i"), "Optional Instance Methods: methodWithParameter:");
+			AssertContains (methods, new objc_method_description ("methodWithParameters:second:third:fourth:", "v@:iiii"), "Optional Instance Methods: methodWithParameters:second:third:fourth:");
+			AssertContains (methods, new objc_method_description ("optionalInstanceMethod", "v@:"), "Optional Instance Methods: optionalInstanceMethod");
+			AssertContains (methods, new objc_method_description ("methodWithRefParameters:second:third:fourth:", "v@:i^i^ii"), "Optional Instance Methods: methodWithRefParameters:second:third:fourth:");
+			AssertContains (methods, new objc_method_description ("optionalInstanceProperty", "@@:"), "Optional Instance Methods: optionalInstanceProperty");
+			AssertContains (methods, new objc_method_description ("setOptionalInstanceProperty:", "v@:@"), "Optional Instance Methods: setOptionalInstanceProperty:");
+			AssertContains (methods, new objc_method_description ("get_propertyWithCustomAccessors", "@@:"), "Optional Instance Methods: get_propertyWithCustomAccessors");
+			AssertContains (methods, new objc_method_description ("set_propertyWithCustomAccessors:", "v@:@"), "Optional Instance Methods: set_propertyWithCustomAccessors:");
+			AssertContains (methods, new objc_method_description ("propertyWithArgumentSemanticNone", "@@:"), "Optional Instance Methods: propertyWithArgumentSemanticNone");
+			AssertContains (methods, new objc_method_description ("setPropertyWithArgumentSemanticNone:", "v@:@"), "Optional Instance Methods: setPropertyWithArgumentSemanticNone:");
+			AssertContains (methods, new objc_method_description ("propertyWithArgumentSemanticCopy", "@@:"), "Optional Instance Methods: propertyWithArgumentSemanticCopy");
+			AssertContains (methods, new objc_method_description ("setPropertyWithArgumentSemanticCopy:", "v@:@"), "Optional Instance Methods: setPropertyWithArgumentSemanticCopy:");
+			AssertContains (methods, new objc_method_description ("propertyWithArgumentSemanticAssign", "@@:"), "Optional Instance Methods: propertyWithArgumentSemanticAssign");
+			AssertContains (methods, new objc_method_description ("setPropertyWithArgumentSemanticAssign:", "v@:@"), "Optional Instance Methods: setPropertyWithArgumentSemanticAssign:");
+			AssertContains (methods, new objc_method_description ("readonlyProperty", "@@:"), "Optional Instance Methods: readonlyProperty:");
+			AssertContains (methods, new objc_method_description ("propertyWithArgumentSemanticRetain", "@@:"), "Optional Instance Methods: propertyWithArgumentSemanticRetain");
+			AssertContains (methods, new objc_method_description ("setPropertyWithArgumentSemanticRetain:", "v@:@"), "Optional Instance Methods: setPropertyWithArgumentSemanticRetain:");
 
 			// Optional static methods
 			methods = protocol_copyMethodDescriptionList (protocol, false, false);
 			CleanupSignatures (methods);
 			Assert.AreEqual (3, methods.Length, "Optional Static Methods: Count");
-			Assert.That (methods, Contains.Item (new objc_method_description ("optionalStaticMethod", "v@:")), "Optional Static Methods: optionalStaticMethod");
-			Assert.That (methods, Contains.Item (new objc_method_description ("optionalStaticProperty", "@@:")), "Optional Static Methods: optionalStaticProperty");
-			Assert.That (methods, Contains.Item (new objc_method_description ("setOptionalStaticProperty:", "v@:@")), "Optional Static Methods: setOptionalStaticProperty:");
+			AssertContains (methods, new objc_method_description ("optionalStaticMethod", "v@:"), "Optional Static Methods: optionalStaticMethod");
+			AssertContains (methods, new objc_method_description ("optionalStaticProperty", "@@:"), "Optional Static Methods: optionalStaticProperty");
+			AssertContains (methods, new objc_method_description ("setOptionalStaticProperty:", "v@:@"), "Optional Static Methods: setOptionalStaticProperty:");
 
 			objc_property [] properties;
 			properties = protocol_copyPropertyList (protocol);
@@ -221,60 +221,75 @@ public void ProtocolMembers ()
 				Assert.AreEqual (2, properties.Length, "Properties: Count");
 			}
 
-			Assert.That (properties, Contains.Item (new objc_property ("requiredInstanceProperty", "T@\"NSString\",N", new objc_property_attribute [] {
+			AssertContains (properties, new objc_property ("requiredInstanceProperty", "T@\"NSString\",N", new objc_property_attribute [] {
 				new objc_property_attribute ("T", "@\"NSString\""),
 				new objc_property_attribute ("N", "")
-			})), "Properties: requiredInstanceProperty");
+			}), "Properties: requiredInstanceProperty");
 
-			Assert.That (properties, Contains.Item (new objc_property ("requiredReadonlyProperty", "T@\"NSString\",R,N", new objc_property_attribute [] {
+			AssertContains (properties, new objc_property ("requiredReadonlyProperty", "T@\"NSString\",R,N", new objc_property_attribute [] {
 				new objc_property_attribute ("T", "@\"NSString\""),
 				new objc_property_attribute ("R", ""),
 				new objc_property_attribute ("N", "")
-			})), "Properties: requiredReadonlyProperty");
+			}), "Properties: requiredReadonlyProperty");
 
 			if (XamarinTests.ObjCRuntime.Registrar.IsStaticRegistrar) {
-				Assert.That (properties, Contains.Item (new objc_property ("optionalInstanceProperty", "T@\"NSString\",N", new objc_property_attribute [] {
+				AssertContains (properties, new objc_property ("optionalInstanceProperty", "T@\"NSString\",N", new objc_property_attribute [] {
 					new objc_property_attribute ("T", "@\"NSString\""),
 					new objc_property_attribute ("N", "")
-				})), "Properties: optionalInstanceProperty");
+				}), "Properties: optionalInstanceProperty");
 
-				Assert.That (properties, Contains.Item (new objc_property ("propertyWithCustomAccessors", "T@\"NSString\",N,Gget_propertyWithCustomAccessors,Sset_propertyWithCustomAccessors:", new objc_property_attribute [] {
+				AssertContains (properties, new objc_property ("propertyWithCustomAccessors", "T@\"NSString\",N,Gget_propertyWithCustomAccessors,Sset_propertyWithCustomAccessors:", new objc_property_attribute [] {
 					new objc_property_attribute ("T", "@\"NSString\""),
 					new objc_property_attribute ("N", ""),
 					new objc_property_attribute ("G", "get_propertyWithCustomAccessors"),
 					new objc_property_attribute ("S", "set_propertyWithCustomAccessors:")
-				})), "Properties: propertyWithCustomAccessors");
+				}), "Properties: propertyWithCustomAccessors");
 
-				Assert.That (properties, Contains.Item (new objc_property ("propertyWithArgumentSemanticNone", "T@\"NSString\",N", new objc_property_attribute [] {
+				AssertContains (properties, new objc_property ("propertyWithArgumentSemanticNone", "T@\"NSString\",N", new objc_property_attribute [] {
 					new objc_property_attribute ("T", "@\"NSString\""),
 					new objc_property_attribute ("N", "")
-				})), "Properties: propertyWithArgumentSemanticNone");
+				}), "Properties: propertyWithArgumentSemanticNone");
 
-				Assert.That (properties, Contains.Item (new objc_property ("propertyWithArgumentSemanticCopy", "T@\"NSString\",C,N", new objc_property_attribute [] {
+				AssertContains (properties, new objc_property ("propertyWithArgumentSemanticCopy", "T@\"NSString\",C,N", new objc_property_attribute [] {
 					new objc_property_attribute ("T", "@\"NSString\""),
 					new objc_property_attribute ("N", ""),
 					new objc_property_attribute ("C", "")
-				})), "Properties: propertyWithArgumentSemanticCopy");
+				}), "Properties: propertyWithArgumentSemanticCopy");
 
-				Assert.That (properties, Contains.Item (new objc_property ("propertyWithArgumentSemanticAssign", "T@\"NSString\",N", new objc_property_attribute [] {
+				AssertContains (properties, new objc_property ("propertyWithArgumentSemanticAssign", "T@\"NSString\",N", new objc_property_attribute [] {
 					new objc_property_attribute ("T", "@\"NSString\""),
 					new objc_property_attribute ("N", "")
-				})), "Properties: propertyWithArgumentSemanticAssign");
+				}), "Properties: propertyWithArgumentSemanticAssign");
 
-				Assert.That (properties, Contains.Item (new objc_property ("propertyWithArgumentSemanticRetain", "T@\"NSString\",&,N", new objc_property_attribute [] {
+				AssertContains (properties, new objc_property ("propertyWithArgumentSemanticRetain", "T@\"NSString\",&,N", new objc_property_attribute [] {
 					new objc_property_attribute ("T", "@\"NSString\""),
 					new objc_property_attribute ("&", ""),
 					new objc_property_attribute ("N", "")
-				})), "Properties: propertyWithArgumentSemanticRetain");
+				}), "Properties: propertyWithArgumentSemanticRetain");
 
-				Assert.That (properties, Contains.Item (new objc_property ("readonlyProperty", "T@\"NSString\",R,N", new objc_property_attribute [] {
+				AssertContains (properties, new objc_property ("readonlyProperty", "T@\"NSString\",R,N", new objc_property_attribute [] {
 					new objc_property_attribute ("T", "@\"NSString\""),
 					new objc_property_attribute ("R", ""),
 					new objc_property_attribute ("N", "")
-				})), "Properties: readonlyProperty");
+				}), "Properties: readonlyProperty");
 			}
 		}
 
+		static void AssertContains<T> (T [] array, T item, string message) where T : IEquatable<T>
+		{
+			for (var i = 0; i < array.Length; i++) {
+				var element = array [i];
+				if (element is null && item is null)
+					return;
+				if (element is null || item is null)
+					continue;
+				if (element.Equals (item))
+					return;
+			}
+
+			throw new Exception ($"Collection {array} does not contain item {item}: {message}");
+		}
+
 		[DllImport ("/usr/lib/libobjc.dylib")]
 		internal extern static IntPtr objc_getProtocol (string name);
 
diff --git a/tests/dotnet/Net7_0SimpleApp/shared.csproj b/tests/dotnet/Net7_0SimpleApp/shared.csproj
index cda1f92953a6..ccb12ab98835 100644
--- a/tests/dotnet/Net7_0SimpleApp/shared.csproj
+++ b/tests/dotnet/Net7_0SimpleApp/shared.csproj
@@ -6,6 +6,9 @@
 		<ApplicationTitle>MySimpleApp</ApplicationTitle>
 		<ApplicationId>com.xamarin.mysimpleapp</ApplicationId>
 		<ApplicationVersion>7.0</ApplicationVersion>
+
+		<ExcludeNUnitLiteReference>true</ExcludeNUnitLiteReference>
+		<ExcludeTouchUnitReference>true</ExcludeTouchUnitReference>
 	</PropertyGroup>
 
 	<Import Project="../../common/shared-dotnet.csproj" />
diff --git a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs
index c9e7eb495195..dd17244c45c2 100644
--- a/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs
+++ b/tests/monotouch-test/ObjCRuntime/RegistrarTest.cs
@@ -111,7 +111,7 @@ public void RegistrarRemoval ()
 			// It's not safe to remove the dynamic registrar in monotouch-test (by design; some of the tested API makes it unsafe, and the linker correctly detects this),
 			// so the dynamic registrar will only be removed if manually requested.
 			// Also removal of the dynamic registrar is not supported in XM
-#if OPTIMIZEALL && !__MACOS__
+#if (OPTIMIZEALL && !__MACOS__) || NATIVEAOT
 			var shouldBeRemoved = true;
 #else
 			var shouldBeRemoved = false;
diff --git a/tests/monotouch-test/mono/ConfigTest.cs b/tests/monotouch-test/mono/ConfigTest.cs
index 36a554f1151d..32e7fd682ee8 100644
--- a/tests/monotouch-test/mono/ConfigTest.cs
+++ b/tests/monotouch-test/mono/ConfigTest.cs
@@ -13,8 +13,17 @@ public partial class ConfigTest {
 		[Test]
 		public void Existence ()
 		{
+#if NATIVEAOT
+#if __MACCATALYST__ || __MACOS__
+			var config_dir = Path.Combine (Path.GetDirectoryName (AppContext.BaseDirectory.TrimEnd ('/')), "MonoBundle");
+#else
+			var config_dir = AppContext.BaseDirectory;
+#endif
+			var config_file = Path.Combine (config_dir, Assembly.GetExecutingAssembly ().GetName ().Name + ".dll.config");
+#else
 			var config_file = Assembly.GetExecutingAssembly ().Location + ".config";
-			Assert.True (File.Exists (config_file), "existence");
+#endif
+			Assert.That (config_file, Does.Exist, "existence");
 			Assert.That (File.ReadAllText (config_file), Contains.Substring ("<secretMessage>Xamarin rocks</secretMessage>"), "content");
 		}
 	}
diff --git a/tests/monotouch-test/mono/Symbols.cs b/tests/monotouch-test/mono/Symbols.cs
index cb24398bbd5f..61aea05ea396 100644
--- a/tests/monotouch-test/mono/Symbols.cs
+++ b/tests/monotouch-test/mono/Symbols.cs
@@ -20,6 +20,7 @@ public void FunctionNames ()
 
 			Collect ();
 			bool aot = symbols [1].Contains ("MonoTouchFixtures_Symbols_Collect");
+			bool nativeaot = symbols [1].Contains ("MonoTouchFixtures_Symbols__Collect");
 			bool llvmonly = symbols [1].Contains ("mono_llvmonly_runtime_invoke"); // LLVM inlines the Collect function, so 'Collect' doesn't show up in the stack trace :/
 			bool interp = false;
 
@@ -32,7 +33,7 @@ public void FunctionNames ()
 				}
 			}
 
-			Assert.IsTrue (aot || interp || llvmonly, $"#1\n\t{string.Join ("\n\t", symbols)}");
+			Assert.IsTrue (aot || interp || llvmonly || nativeaot, $"#1\n\t{string.Join ("\n\t", symbols)}");
 		}
 
 		void Collect ()
diff --git a/tests/xharness/Jenkins/TestData.cs b/tests/xharness/Jenkins/TestData.cs
index 0fcfe71e46da..62e33f6f21de 100644
--- a/tests/xharness/Jenkins/TestData.cs
+++ b/tests/xharness/Jenkins/TestData.cs
@@ -23,5 +23,6 @@ class TestData {
 		public string XamMacArch;
 		public string RuntimeIdentifier;
 		public string Registrar;
+		public bool PublishAot; // NativeAOT
 	}
 }
diff --git a/tests/xharness/Jenkins/TestVariationsFactory.cs b/tests/xharness/Jenkins/TestVariationsFactory.cs
index 3af7db146ba6..3ef390bdf80f 100644
--- a/tests/xharness/Jenkins/TestVariationsFactory.cs
+++ b/tests/xharness/Jenkins/TestVariationsFactory.cs
@@ -101,6 +101,8 @@ IEnumerable<TestData> GetTestData (RunTestTask test)
 						yield return new TestData { Variation = "Debug (managed static registrar)", Registrar = "managed-static", Debug = true, Profiling = false, Ignored = ignore };
 						yield return new TestData { Variation = "Release (managed static registrar, all optimizations)", BundlerArguments = "--optimize:all", Registrar = "managed-static", Debug = false, Profiling = false, LinkMode = "Full", Defines = "OPTIMIZEALL", Ignored = ignore };
 					}
+					if (test.TestProject.IsDotNetProject)
+						yield return new TestData { Variation = "Release (NativeAOT)", Debug = false, PublishAot = true, Ignored = ignore, Defines = "NATIVEAOT", LinkMode = "Full" };
 					break;
 				case string name when name.StartsWith ("mscorlib", StringComparison.Ordinal):
 					if (supports_debug)
@@ -127,11 +129,14 @@ IEnumerable<TestData> GetTestData (RunTestTask test)
 					yield return new TestData { Variation = "Release (all optimizations)", BundlerArguments = "--optimize:all", Registrar = "static", Debug = false, Profiling = false, LinkMode = "Full", Defines = "OPTIMIZEALL", Ignored = ignore };
 					yield return new TestData { Variation = "Debug (all optimizations)", BundlerArguments = "--optimize:all,-remove-uithread-checks", Registrar = "static", Debug = true, Profiling = false, LinkMode = "Full", Defines = "OPTIMIZEALL", Ignored = ignore ?? !jenkins.TestSelection.IsEnabled (TestLabel.All) };
 
-					if (test.TestProject.IsDotNetProject && mac_supports_arm64)
+					if (test.TestProject.IsDotNetProject && mac_supports_arm64) {
 						yield return new TestData { Variation = "Debug (ARM64)", Debug = true, Profiling = false, Ignored = !mac_supports_arm64 ? true : ignore, RuntimeIdentifier = arm64_sim_runtime_identifier, };
+						yield return new TestData { Variation = "Release (NativeAOT, ARM64)", Debug = false, PublishAot = true, Ignored = ignore, Defines = "NATIVEAOT", RuntimeIdentifier = arm64_sim_runtime_identifier, LinkMode = "Full" };
+					}
 					if (test.TestProject.IsDotNetProject) {
 						yield return new TestData { Variation = "Debug (managed static registrar)", Registrar = "managed-static", Debug = true, Profiling = false, Ignored = ignore };
 						yield return new TestData { Variation = "Release (managed static registrar, all optimizations)", BundlerArguments = "--optimize:all", Registrar = "managed-static", Debug = false, Profiling = false, LinkMode = "Full", Defines = "OPTIMIZEALL", Ignored = ignore };
+						yield return new TestData { Variation = "Release (NativeAOT)", Debug = false, PublishAot = true, Ignored = ignore, Defines = "NATIVEAOT", LinkMode = "Full" };
 					}
 					break;
 				case "introspection":
@@ -157,10 +162,21 @@ IEnumerable<TestData> GetTestData (RunTestTask test)
 						if (test.Platform != TestPlatform.MacCatalyst) {
 							yield return new TestData { Variation = "Debug (static registrar)", Registrar = "static", Debug = true, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.Mac), };
 							yield return new TestData { Variation = "Debug (static registrar, ARM64)", Registrar = "static", Debug = true, Profiling = false, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.Mac) || !mac_supports_arm64, RuntimeIdentifier = arm64_runtime_identifier, };
+							// Pending: We need the NativeAOT's runtime bits to ship using runtime packs for macOS (https://github.com/dotnet/runtime/issues/87060) before we can enable this
+							// yield return new TestData { Variation = "Release (NativeAOT)", Debug = false, PublishAot = true, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.Mac), Defines = "NATIVEAOT", LinkMode = "Full" };
+							// yield return new TestData { Variation = "Release (NativeAOT, ARM64)", Debug = false, PublishAot = true, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.Mac) || !mac_supports_arm64, Defines = "NATIVEAOT", RuntimeIdentifier = arm64_runtime_identifier, LinkMode = "Full" };
 						}
 						if (test.Platform == TestPlatform.MacCatalyst) {
 							yield return new TestData { Variation = "Release (ARM64, LLVM)", Debug = false, UseLlvm = true, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.MacCatalyst) || !mac_supports_arm64, RuntimeIdentifier = arm64_runtime_identifier };
 							yield return new TestData { Variation = "Release", Debug = false, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.MacCatalyst) };
+							// Pending: We need to skip this consistency check to support universal apps:
+							// https://github.com/dotnet/sdk/blob/f90e558092ac61038ffa1017fe3a5aca1af5ae7e/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.RuntimeIdentifierInference.targets#L224-L226
+							// because the outer build has PublishAot=true and no RuntimeIdentifier set.
+							// Filed as https://github.com/dotnet/sdk/issues/33414.
+							// yield return new TestData { Variation = "Release (NativeAOT)", Debug = false, PublishAot = true, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.MacCatalyst), Defines = "NATIVEAOT", LinkMode = "Full" };
+
+							yield return new TestData { Variation = "Release (NativeAOT, ARM64)", Debug = false, PublishAot = true, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.MacCatalyst) || !mac_supports_arm64, Defines = "NATIVEAOT", RuntimeIdentifier = arm64_runtime_identifier, LinkMode = "Full" };
+							yield return new TestData { Variation = "Release (NativeAOT, x64)", Debug = false, PublishAot = true, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.MacCatalyst), Defines = "NATIVEAOT", LinkMode = "Full", RuntimeIdentifier = "maccatalyst-x64" };
 						}
 						if (test.Platform == TestPlatform.Mac) {
 							yield return new TestData { Variation = "Release", Debug = false, Ignored = !jenkins.TestSelection.IsEnabled (TestLabel.Monotouch) || !jenkins.TestSelection.IsEnabled (PlatformLabel.Mac) };
@@ -220,6 +236,7 @@ public IEnumerable<T> CreateTestVariations<T> (IEnumerable<T> tests, Func<MSBuil
 					var runtime_identifer = test_data.RuntimeIdentifier;
 					var use_llvm = test_data.UseLlvm;
 					var registrar = test_data.Registrar;
+					var publishaot = test_data.PublishAot;
 
 					if (task.TestProject.IsDotNetProject)
 						variation += " [dotnet]";
@@ -285,6 +302,10 @@ public IEnumerable<T> CreateTestVariations<T> (IEnumerable<T> tests, Func<MSBuil
 							clone.Xml.SetProperty ("RuntimeIdentifier", runtime_identifer);
 						if (!string.IsNullOrEmpty (registrar))
 							clone.Xml.SetProperty ("Registrar", registrar);
+						if (publishaot) {
+							clone.Xml.SetProperty ("PublishAot", "true");
+							clone.Xml.SetProperty ("_IsPublishing", "true"); // quack like "dotnet publish", otherwise PublishAot=true has no effect.
+						}
 						clone.Xml.Save (clone.Path);
 					});
 
diff --git a/tools/common/Optimizations.cs b/tools/common/Optimizations.cs
index ca6a7af6119d..16e40bdf4c4d 100644
--- a/tools/common/Optimizations.cs
+++ b/tools/common/Optimizations.cs
@@ -176,6 +176,13 @@ public void Initialize (Application app, out List<ProductException> messages)
 				if (!values [i].HasValue)
 					continue;
 
+				// The remove-dynamic-registrar optimization is required when using NativeAOT
+				if (app.XamarinRuntime == XamarinRuntime.NativeAOT && (Opt) i == Opt.RemoveDynamicRegistrar && values [i] == false) {
+					messages.Add (ErrorHelper.CreateWarning (2016, Errors.MX2016 /* Keeping the dynamic registrar (by passing '--optimize=-remove-dynamic-registrar') is not possible, because the dynamic registrar is not supported when using NativeAOT. Support for dynamic registration will still be removed. */));
+					values [i] = true;
+					continue;
+				}
+
 				// The remove-dynamic-registrar optimization is a bit if a special case on macOS:
 				// it only works in very specific circumstances, so we don't add it to valid_platforms.
 				// This means it won't be listed in --help, and it won't be enabled if all optimizations
@@ -275,7 +282,9 @@ public void Initialize (Application app, out List<ProductException> messages)
 				StaticBlockToDelegateLookup = true;
 
 			if (!RemoveDynamicRegistrar.HasValue) {
-				if (InlineDynamicRegistrationSupported != true) {
+				if (app.XamarinRuntime == XamarinRuntime.NativeAOT) {
+					RemoveDynamicRegistrar = true;
+				} else if (InlineDynamicRegistrationSupported != true) {
 					// Can't remove the dynamic registrar unless also inlining Runtime.DynamicRegistrationSupported
 					RemoveDynamicRegistrar = false;
 				} else if (StaticBlockToDelegateLookup != true) {
diff --git a/tools/common/Target.cs b/tools/common/Target.cs
index c952d93fed1f..5cc2a4e90ad0 100644
--- a/tools/common/Target.cs
+++ b/tools/common/Target.cs
@@ -687,20 +687,22 @@ void GenerateIOSMain (StringWriter sw, Abi abi)
 			var assembly_location_count = 0;
 			var enable_llvm = (abi & Abi.LLVM) != 0;
 
-			register_assemblies.AppendLine ("\tGCHandle exception_gchandle = INVALID_GCHANDLE;");
-			foreach (var s in assemblies) {
-				if (!s.IsAOTCompiled)
-					continue;
+			if (app.XamarinRuntime != XamarinRuntime.NativeAOT) {
+				register_assemblies.AppendLine ("\tGCHandle exception_gchandle = INVALID_GCHANDLE;");
+				foreach (var s in assemblies) {
+					if (!s.IsAOTCompiled)
+						continue;
 
-				var info = s.AssemblyDefinition.Name.Name;
-				info = EncodeAotSymbol (info);
-				assembly_externs.Append ("extern void *mono_aot_module_").Append (info).AppendLine ("_info;");
-				assembly_aot_modules.Append ("\tmono_aot_register_module (mono_aot_module_").Append (info).AppendLine ("_info);");
+					var info = s.AssemblyDefinition.Name.Name;
+					info = EncodeAotSymbol (info);
+					assembly_externs.Append ("extern void *mono_aot_module_").Append (info).AppendLine ("_info;");
+					assembly_aot_modules.Append ("\tmono_aot_register_module (mono_aot_module_").Append (info).AppendLine ("_info);");
 
-				string sname = s.FileName;
-				if (assembly_name != sname && IsBoundAssembly (s)) {
-					register_assemblies.Append ("\txamarin_open_and_register (\"").Append (sname).Append ("\", &exception_gchandle);").AppendLine ();
-					register_assemblies.AppendLine ("\txamarin_process_managed_exception_gchandle (exception_gchandle);");
+					string sname = s.FileName;
+					if (assembly_name != sname && IsBoundAssembly (s)) {
+						register_assemblies.Append ("\txamarin_open_and_register (\"").Append (sname).Append ("\", &exception_gchandle);").AppendLine ();
+						register_assemblies.AppendLine ("\txamarin_process_managed_exception_gchandle (exception_gchandle);");
+					}
 				}
 			}
 
@@ -797,6 +799,8 @@ void GenerateIOSMain (StringWriter sw, Abi abi)
 				} else {
 					sw.WriteLine ("\tmono_jit_set_aot_mode (MONO_AOT_MODE_INTERP);");
 				}
+			} else if (app.XamarinRuntime == XamarinRuntime.NativeAOT) {
+				// don't call mono_jit_set_aot_mode
 			} else if (app.IsDeviceBuild) {
 				sw.WriteLine ("\tmono_jit_set_aot_mode (MONO_AOT_MODE_FULL);");
 			} else if (app.Platform == ApplePlatform.MacCatalyst && ((abi & Abi.ARM64) == Abi.ARM64)) {
@@ -852,7 +856,8 @@ void GenerateIOSMain (StringWriter sw, Abi abi)
 			// Do this last, so that the app developer can override any other environment variable we set.
 			foreach (var kvp in app.EnvironmentVariables)
 				sw.WriteLine ("\tsetenv (\"{0}\", \"{1}\", 1);", kvp.Key.Replace ("\"", "\\\""), kvp.Value.Replace ("\"", "\\\""));
-			sw.WriteLine ("\txamarin_supports_dynamic_registration = {0};", app.DynamicRegistrationSupported ? "TRUE" : "FALSE");
+			if (app.XamarinRuntime != XamarinRuntime.NativeAOT)
+				sw.WriteLine ("\txamarin_supports_dynamic_registration = {0};", app.DynamicRegistrationSupported ? "TRUE" : "FALSE");
 #if NET
 			sw.WriteLine ("\txamarin_runtime_configuration_name = {0};", string.IsNullOrEmpty (app.RuntimeConfigurationFile) ? "NULL" : $"\"{app.RuntimeConfigurationFile}\"");
 #endif
diff --git a/tools/common/XamarinRuntime.cs b/tools/common/XamarinRuntime.cs
index af78ec9c2e78..ecab1567fe08 100644
--- a/tools/common/XamarinRuntime.cs
+++ b/tools/common/XamarinRuntime.cs
@@ -2,5 +2,6 @@ namespace Xamarin.Bundler {
 	public enum XamarinRuntime {
 		MonoVM,
 		CoreCLR,
+		NativeAOT,
 	}
 }
diff --git a/tools/mtouch/Errors.designer.cs b/tools/mtouch/Errors.designer.cs
index d770fa6bddc0..9c382bd3f3b8 100644
--- a/tools/mtouch/Errors.designer.cs
+++ b/tools/mtouch/Errors.designer.cs
@@ -3901,6 +3901,15 @@ public static string MX2005 {
             }
         }
         
+        /// <summary>
+        ///   Looks up a localized string similar to Keeping the dynamic registrar (by passing &apos;--optimize=-remove-dynamic-registrar&apos;) is not possible, because the dynamic registrar is not supported when using NativeAOT. Support for dynamic registration will still be removed..
+        /// </summary>
+        public static string MX2016 {
+            get {
+                return ResourceManager.GetString("MX2016", resourceCulture);
+            }
+        }
+        
         /// <summary>
         ///   Looks up a localized string similar to Could not process XML description: {0}
         ///		.
diff --git a/tools/mtouch/Errors.resx b/tools/mtouch/Errors.resx
index 8ebe9f75b579..76e6fc7c5b1b 100644
--- a/tools/mtouch/Errors.resx
+++ b/tools/mtouch/Errors.resx
@@ -1190,6 +1190,10 @@
 		</value>
 	</data>
 
+	<data name="MX2016" xml:space="preserve">
+		<value>Keeping the dynamic registrar (by passing '--optimize=-remove-dynamic-registrar') is not possible, because the dynamic registrar is not supported when using NativeAOT. Support for dynamic registration will still be removed.</value>
+	</data>
+
 	<data name="MX2017" xml:space="preserve">
 		<value>Could not process XML description: {0}
 		</value>
