Skip to content

Commit 869b0e0

Browse files
authored
[native] NativeAOT runtime (#10461)
Fixes: #10455 Implements a minimal Android host for NativeAOT applications. The goal is to create a runtime environment that is as compatible with Xamarin.Android / .NET for Android applications using MonoVM or CoreCLR as possible. This PR implements the very basics: * Support for pre-loading of JNI libraries * Support for setting of the application environment variables (via `<AndroidEnvironment>`) * Support for the CoreCLR GC bridge An important element that's currently missing is linking `libc++` statically into the runtime, instead we include the shared `libc++` from the NDK. The reason for this is that NativeAOT build task references a compatibility C++ library which produces conflicts with the static `libc++` one when attempting to link. This issue will be fixed in a separate PR.
1 parent a85247e commit 869b0e0

File tree

56 files changed

+1764
-496
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1764
-496
lines changed

Configuration.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@
233233
<PropertyGroup>
234234
<_MonoRuntimeFlavorDirName>mono</_MonoRuntimeFlavorDirName>
235235
<_CLRRuntimeFlavorDirName>clr</_CLRRuntimeFlavorDirName>
236+
<_NativeAotRuntimeFlavorDirName>nativeaot</_NativeAotRuntimeFlavorDirName>
236237
<_RuntimeRedistDirName>redist</_RuntimeRedistDirName>
237238
</PropertyGroup>
238239

Xamarin.Android.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "native-mono", "src\native\n
4949
EndProject
5050
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "native-clr", "src\native\native-clr.csproj", "{9B42A5BB-74CB-46E2-BCE0-AF763A792551}"
5151
EndProject
52+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "native-nativeaot", "src\native\native-nativeaot.csproj", "{645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}"
53+
EndProject
5254
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483}"
5355
EndProject
5456
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.ProjectTools", "src\Xamarin.Android.Build.Tasks\Tests\Xamarin.ProjectTools\Xamarin.ProjectTools.csproj", "{2DD1EE75-6D8D-4653-A800-0A24367F7F38}"
@@ -211,6 +213,10 @@ Global
211213
{9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Debug|AnyCPU.Build.0 = Debug|Any CPU
212214
{9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|AnyCPU.ActiveCfg = Release|Any CPU
213215
{9B42A5BB-74CB-46E2-BCE0-AF763A792551}.Release|AnyCPU.Build.0 = Release|Any CPU
216+
{645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU
217+
{645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Debug|AnyCPU.Build.0 = Debug|Any CPU
218+
{645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|AnyCPU.ActiveCfg = Release|Any CPU
219+
{645E1718-C8C4-4C23-8A49-5A37E4ECF7ED}.Release|AnyCPU.Build.0 = Release|Any CPU
214220
{2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU
215221
{2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Debug|AnyCPU.Build.0 = Debug|Any CPU
216222
{2DD1EE75-6D8D-4653-A800-0A24367F7F38}.Release|AnyCPU.ActiveCfg = Release|Any CPU
@@ -383,6 +389,7 @@ Global
383389
{D48EE8D0-0A0A-4493-AEF5-DAF5F8CF86AD} = {864062D3-A415-4A6F-9324-5820237BA058}
384390
{53EE4C57-1C03-405A-8243-8DA539546C88} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
385391
{9B42A5BB-74CB-46E2-BCE0-AF763A792551} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
392+
{645E1718-C8C4-4C23-8A49-5A37E4ECF7ED} = {04E3E11E-B47D-4599-8AFC-50515A95E715}
386393
{2DD1EE75-6D8D-4653-A800-0A24367F7F38} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483}
387394
{53E4ABF0-1085-45F9-B964-DCAE4B819998} = {CAB438D8-B0F5-4AF0-BEBD-9E2ADBD7B483}
388395
{38C762AB-8FD1-44DE-9855-26AAE7129DC3} = {864062D3-A415-4A6F-9324-5820237BA058}

build-tools/create-packs/Microsoft.Android.Runtime.proj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+.
4646
BeforeTargets="GetFilesToPackage" >
4747
<PropertyGroup>
4848
<_RuntimeFlavorDirName Condition=" '$(AndroidRuntime)' == 'CoreCLR' ">$(_CLRRuntimeFlavorDirName)</_RuntimeFlavorDirName>
49+
<_RuntimeFlavorDirName Condition=" '$(AndroidRuntime)' == 'NativeAOT' ">$(_NativeAotRuntimeFlavorDirName)</_RuntimeFlavorDirName>
4950
<_RuntimeFlavorDirName Condition=" '$(AndroidRuntime)' == 'Mono' Or '$(AndroidRuntime)' == '' ">$(_MonoRuntimeFlavorDirName)</_RuntimeFlavorDirName>
5051
<_ClangArch Condition=" '$(AndroidRID)' == 'android-arm64' ">aarch64</_ClangArch>
5152
<_ClangArch Condition=" '$(AndroidRID)' == 'android-arm' ">arm</_ClangArch>
@@ -102,6 +103,8 @@ projects that use the Microsoft.Android.Runtimes framework in .NET 6+.
102103
</ItemGroup>
103104

104105
<ItemGroup Condition=" '$(AndroidRuntime)' == 'NativeAOT' ">
106+
<NativeRuntimeAsset Condition=" Exists('$(NativeRuntimeOutputRootDir)$(_RuntimeFlavorDirName)\$(AndroidRID)\libnaot-android.debug-static-debug.a') " Include="$(NativeRuntimeOutputRootDir)$(_RuntimeFlavorDirName)\$(AndroidRID)\libnaot-android.debug-static-debug.a" />
107+
<NativeRuntimeAsset Condition=" Exists('$(NativeRuntimeOutputRootDir)$(_RuntimeFlavorDirName)\$(AndroidRID)\libnaot-android.release-static-release.a') " Include="$(NativeRuntimeOutputRootDir)$(_RuntimeFlavorDirName)\$(AndroidRID)\libnaot-android.release-static-release.a" />
105108
<_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Microsoft.Android.Runtime.NativeAOT.dll" />
106109
</ItemGroup>
107110

samples/NativeAOT/MainActivity.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using Android.Util;
44
using System.Reflection;
55
using System.Runtime.InteropServices;
6+
using System.Security.Cryptography;
7+
using System.Text;
68

79
namespace NativeAOT;
810

@@ -27,5 +29,9 @@ protected override void OnCreate(Bundle? savedInstanceState)
2729
if (t) {
2830
throw new InvalidOperationException ("What happened?");
2931
}
32+
33+
// This tests loading and initializing JNI shared libraries
34+
var hashData = new byte[32];
35+
SHA256.HashData(Encoding.ASCII.GetBytes("Hello World"), hashData.AsSpan());
3036
}
3137
}

samples/NativeAOT/run.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash -e
2+
CONFIG=Release
3+
APK="bin/${CONFIG}/net10.0-android/net.dot.hellonativeaot-Signed.apk"
4+
PACKAGE="net.dot.hellonativeaot"
5+
ACTIVITY="my.MainActivity"
6+
7+
FAILED=no
8+
rm -rf bin obj
9+
../../dotnet-local.sh build -c ${CONFIG} -bl || FAILED=yes
10+
../../dotnet-local.sh msbuild -v:diag msbuild.binlog > msbuild.txt
11+
if [ "${FAILED}" == "yes" ]; then
12+
echo Build failed
13+
exit 1
14+
fi
15+
16+
if [ -n "${1}" ]; then
17+
exit 0
18+
fi
19+
20+
adb uninstall "${PACKAGE}" || true
21+
adb install -r -d --no-streaming --no-fastdeploy "${APK}"
22+
#adb shell setprop debug.mono.log default,assembly,timing=bare
23+
adb logcat -G 128M
24+
adb logcat -c
25+
adb shell am start -S --user "0" -a "android.intent.action.MAIN" -c "android.intent.category.LAUNCHER" -n "${PACKAGE}/${ACTIVITY}" -W
26+
echo "Waiting for the app to start..."
27+
sleep 5
28+
adb logcat -d > log.txt

src/Microsoft.Android.Runtime.NativeAOT/Android.Runtime.NativeAOT/JavaInteropRuntime.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ static partial class JavaInteropRuntime
88
{
99
static JniRuntime? runtime;
1010

11+
[DllImport("xa-internal-api")]
12+
static extern int XA_Host_NativeAOT_JNI_OnLoad (IntPtr vm, IntPtr reserved);
13+
1114
[UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")]
1215
static int JNI_OnLoad (IntPtr vm, IntPtr reserved)
1316
{
1417
try {
1518
AndroidLog.Print (AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnLoad()");
19+
XA_Host_NativeAOT_JNI_OnLoad (vm, reserved);
1620
LogcatTextWriter.Init ();
1721
return (int) JniVersion.v1_6;
1822
}
@@ -29,6 +33,9 @@ static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
2933
runtime?.Dispose ();
3034
}
3135

36+
[DllImport("xa-internal-api")]
37+
static extern void XA_Host_NativeAOT_OnInit ();
38+
3239
// symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h`
3340
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_nativeaot_JavaInteropRuntime_init")]
3441
static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader)
@@ -38,12 +45,11 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader)
3845
var settings = new DiagnosticSettings ();
3946
settings.AddDebugDotnetLog ();
4047

41-
var typeManager = new ManagedTypeManager ();
4248
var options = new NativeAotRuntimeOptions {
4349
EnvironmentPointer = jnienv,
44-
ClassLoader = new JniObjectReference (classLoader),
45-
TypeManager = typeManager,
46-
ValueManager = new SimpleValueManager (),
50+
ClassLoader = new JniObjectReference (classLoader, JniObjectReferenceType.Global),
51+
TypeManager = new ManagedTypeManager (),
52+
ValueManager = ManagedValueManager.GetOrCreateInstance (),
4753
UseMarshalMemberBuilder = false,
4854
JniGlobalReferenceLogWriter = settings.GrefLog,
4955
JniLocalReferenceLogWriter = settings.LrefLog,
@@ -52,6 +58,7 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader)
5258

5359
// Entry point into Mono.Android.dll
5460
JNIEnvInit.InitializeJniRuntime (runtime);
61+
XA_Host_NativeAOT_OnInit ();
5562

5663
transition = new JniTransition (jnienv);
5764

@@ -64,4 +71,4 @@ static void init (IntPtr jnienv, IntPtr klass, IntPtr classLoader)
6471
}
6572
transition.Dispose ();
6673
}
67-
}
74+
}

src/Microsoft.Android.Runtime.NativeAOT/Java.Interop/JreRuntime.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ static NativeAotRuntimeOptions CreateJreVM (NativeAotRuntimeOptions builder)
6161
builder.TypeManager ??= new ManagedTypeManager ();
6262
#endif // NET
6363

64-
builder.ValueManager ??= new SimpleValueManager ();
65-
builder.ObjectReferenceManager ??= new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter);
64+
builder.ValueManager ??= ManagedValueManager.GetOrCreateInstance();
65+
builder.ObjectReferenceManager ??= new Android.Runtime.AndroidObjectReferenceManager ();
6666

6767
if (builder.InvocationPointer != IntPtr.Zero || builder.EnvironmentPointer != IntPtr.Zero)
6868
return builder;

src/Mono.Android/Android.Runtime/AndroidRuntime.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ public AndroidRuntimeOptions (IntPtr jnienv,
102102
}
103103
}
104104

105-
class AndroidObjectReferenceManager : JniRuntime.JniObjectReferenceManager {
105+
// TODO: this shouldn't be public
106+
public class AndroidObjectReferenceManager : JniRuntime.JniObjectReferenceManager {
106107
public override int GlobalReferenceCount {
107108
get {return RuntimeNativeMethods._monodroid_gref_get ();}
108109
}
@@ -186,7 +187,7 @@ public override JniObjectReference CreateGlobalReference (JniObjectReference val
186187
var from = log ? new StringBuilder (new StackTrace (true).ToString ()) : null;
187188
int gc = RuntimeNativeMethods._monodroid_gref_log_new (value.Handle, ctype, r.Handle, ntype, tname, tid, from, 1);
188189
if (gc >= JNIEnvInit.gref_gc_threshold) {
189-
Logger.Log (LogLevel.Info, "monodroid-gc", gc + " outstanding GREFs. Performing a full GC!");
190+
Logger.Log (LogLevel.Debug, "monodroid-gc", gc + " outstanding GREFs. Performing a full GC!");
190191
System.GC.Collect ();
191192
}
192193

src/Mono.Android/Android.Runtime/JNIEnvInit.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ static Type TypeGetType (string typeName) =>
8282
internal static void InitializeJniRuntime (JniRuntime runtime)
8383
{
8484
androidRuntime = runtime;
85+
gref_gc_threshold = RuntimeNativeMethods._monodroid_max_gref_get ();
86+
if (gref_gc_threshold != int.MaxValue) {
87+
gref_gc_threshold = checked((gref_gc_threshold * 9) / 10);
88+
}
8589
SetSynchronizationContext ();
8690
}
8791

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
88
<Project>
99

1010
<UsingTask TaskName="Xamarin.Android.Tasks.SetNdkPathForIlc" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
11+
<UsingTask TaskName="Xamarin.Android.Tasks.GenerateNativeAotLibraryLoadAssemblerSources" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
12+
<UsingTask TaskName="Xamarin.Android.Tasks.GenerateNativeAotEnvironmentAssemblerSources" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />
1113

1214
<!-- Default property values for NativeAOT -->
1315
<PropertyGroup>
@@ -93,6 +95,9 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
9395
<ItemGroup>
9496
<!-- Android needs a proper soname property or it will refuse to load the library -->
9597
<LinkerArg Include="&quot;-Wl,-soname,lib$(TargetName)$(NativeBinaryExt)&quot;" />
98+
<LinkerArg Include="-Wl,--error-unresolved-symbols" />
99+
<LinkerArg Include="-Wl,--no-undefined" />
100+
96101
<!-- Required for [UnmanagedCallersOnly] to work inside this assembly -->
97102
<UnmanagedEntryPointsAssembly Include="Microsoft.Android.Runtime.NativeAOT" />
98103
<!-- Give ILLink's output to ILC -->
@@ -110,7 +115,85 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
110115
<ResolvedFileToPublish Include="$(_NdkSysrootDir)libc++_shared.so" RuntimeIdentifier="$(RuntimeIdentifier)" />
111116
<!-- Satellite assemblies -->
112117
<IlcSatelliteAssembly Include="$(_OuterIntermediateSatelliteAssembliesWithTargetPath)" />
118+
119+
<LinkerArg Include="@(RuntimePackAsset->WithMetadataValue('Filename', 'libnaot-android.$(Configuration.ToLower())-static-$(Configuration.ToLower())'))" />
120+
<LinkerArg Include="$(_NdkSysrootDir)libc++_shared.so" />
121+
122+
<!-- Every p/invoke using the `xa-internal-api` "library" will be called directly -->
123+
<DirectPInvoke Include="xa-internal-api" />
124+
</ItemGroup>
125+
</Target>
126+
127+
<Target Name="_PrepareNativeAotAndroidAppInputs">
128+
<ItemGroup>
129+
<_PrivateBuildTargetAbi Condition=" '$(RuntimeIdentifier)' == 'android-arm64' " Include="arm64-v8a" />
130+
<_PrivateBuildTargetAbi Condition=" '$(RuntimeIdentifier)' == 'android-x64' " Include="x86_64" />
113131
</ItemGroup>
132+
133+
<PrepareAbiItems
134+
BuildTargetAbis="@(_PrivateBuildTargetAbi)"
135+
NativeSourcesDir="$(_NativeAssemblySourceDir)"
136+
Debug="$(AndroidIncludeDebugSymbols)"
137+
Mode="jni_init">
138+
<Output TaskParameter="AssemblySources" ItemName="_PrivateJniInitFuncsAssemblySource" />
139+
</PrepareAbiItems>
140+
141+
<PrepareAbiItems
142+
BuildTargetAbis="@(_PrivateBuildTargetAbi)"
143+
NativeSourcesDir="$(_NativeAssemblySourceDir)"
144+
Debug="$(AndroidIncludeDebugSymbols)"
145+
Mode="environment">
146+
<Output TaskParameter="AssemblySources" ItemName="_PrivateEnvironmentAssemblySource" />
147+
</PrepareAbiItems>
148+
149+
<ItemGroup>
150+
<_PrivateJniInitFuncsNativeObjectFile Include="@(_PrivateJniInitFuncsAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
151+
<abi>%(_PrivateJniInitFuncsAssemblySource.abi)</abi>
152+
</_PrivateJniInitFuncsNativeObjectFile>
153+
154+
<_PrivateEnvironmentNativeObjectFile Include="@(_PrivateEnvironmentAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
155+
<abi>%(_PrivateEnvironmentAssemblySource.abi)</abi>
156+
</_PrivateEnvironmentNativeObjectFile>
157+
158+
<_PrivateAndroidNaotResolvedAssemblyFiles Include="@(ResolvedFileToPublish->Distinct())" Condition=" '%(ResolvedFileToPublish.Extension)' == '.dll' " />
159+
</ItemGroup>
160+
</Target>
161+
162+
<Target
163+
Name="_GenerateNativeAotAndroidAppAssemblerSources"
164+
DependsOnTargets="_GenerateEnvironmentFiles;_PrepareNativeAotAndroidAppInputs"
165+
Inputs="@(_PrivateAndroidNaotResolvedAssemblyFiles);@(AndroidEnvironment);@(LibraryEnvironments)"
166+
Outputs="@(_PrivateJniInitFuncsAssemblySource);@(_PrivateJniInitFuncsNativeObjectFile);@(_PrivateEnvironmentAssemblySource);@(_PrivateEnvironmentNativeObjectFile)">
167+
<GenerateNativeAotLibraryLoadAssemblerSources
168+
ResolvedAssemblies="@(_PrivateAndroidNaotResolvedAssemblyFiles)"
169+
CustomJniInitFunctions="@(AndroidStaticJniInitFunction)"
170+
OutputSources="@(_PrivateJniInitFuncsAssemblySource)" />
171+
172+
<GenerateNativeAotEnvironmentAssemblerSources
173+
Debug="$(AndroidIncludeDebugSymbols)"
174+
Environments="@(AndroidEnvironment);@(LibraryEnvironments)"
175+
HttpClientHandlerType="$(AndroidHttpClientHandlerType)"
176+
OutputSources="@(_PrivateEnvironmentAssemblySource)"
177+
RID="$(RuntimeIdentifier)" />
178+
179+
<CompileNativeAssembly
180+
Sources="@(_PrivateJniInitFuncsAssemblySource);@(_PrivateEnvironmentAssemblySource)"
181+
DebugBuild="$(AndroidIncludeDebugSymbols)"
182+
WorkingDirectory="$(_NativeAssemblySourceDir)"
183+
AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)" />
184+
185+
<ItemGroup>
186+
<LinkerArg Include="@(_PrivateJniInitFuncsNativeObjectFile)" />
187+
<LinkerArg Include="@(_PrivateEnvironmentNativeObjectFile)" />
188+
</ItemGroup>
189+
</Target>
190+
191+
<!-- Make sure we have a chance to generate our application-specific static library before linking
192+
takes place. We must run after `IlcCompile`, which is a dependency of `LinkNative` -->
193+
<Target
194+
Name="_GenerateNativeAotAndroidAppLibrary"
195+
AfterTargets="IlcCompile"
196+
DependsOnTargets="_GenerateNativeAotAndroidAppAssemblerSources">
114197
</Target>
115198

116199
<Target Name="_AndroidFixNativeLibraryFileName" AfterTargets="ComputeFilesToPublish">

0 commit comments

Comments
 (0)