Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion build-tools/create-packs/Microsoft.Android.Runtime.proj
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ projects that use the Microsoft.Android framework in .NET 6+.
Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Microsoft.Android.Runtime.NativeAOT.dll"
Condition=" '$(AndroidRuntime)' == 'NativeAOT' "
/>
<_AndroidRuntimePackAssemblies
Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\System.IO.Hashing.dll"
Condition=" '$(AndroidRuntime)' == 'NativeAOT' "
NoSymbols="true"
/>
<_AndroidRuntimePackAssemblies Include="$(_MonoAndroidNETOutputRoot)$(AndroidLatestStableApiLevel)\Mono.Android.Export.dll" />
</ItemGroup>

Expand Down Expand Up @@ -77,7 +82,7 @@ projects that use the Microsoft.Android framework in .NET 6+.

<ItemGroup>
<_PackageFiles Include="@(_AndroidRuntimePackAssemblies)" PackagePath="$(_AndroidRuntimePackAssemblyPath)" TargetPath="$(_AndroidRuntimePackAssemblyPath)" />
<_PackageFiles Include="@(_AndroidRuntimePackAssemblies->'%(RelativeDir)%(Filename).pdb')" PackagePath="$(_AndroidRuntimePackAssemblyPath)" />
<_PackageFiles Include="@(_AndroidRuntimePackAssemblies->'%(RelativeDir)%(Filename).pdb')" PackagePath="$(_AndroidRuntimePackAssemblyPath)" Condition=" '%(_AndroidRuntimePackAssemblies.NoSymbols)' != 'true' " />
<_PackageFiles Include="@(_AndroidRuntimePackAssets)" PackagePath="$(_AndroidRuntimePackNativePath)" TargetPath="$(_AndroidRuntimePackNativePath)" IsNative="true" />
</ItemGroup>
</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,8 @@ partial class NativeAotTypeManager : JniRuntime.JniTypeManager {
internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods;
internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;

readonly IDictionary<string, Type> TypeMappings = new Dictionary<string, Type> (StringComparer.Ordinal);

public NativeAotTypeManager ()
{
var startTicks = global::System.Environment.TickCount;
InitializeTypeMappings ();
var endTicks = global::System.Environment.TickCount;
AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"InitializeTypeMappings() took {endTicks - startTicks}ms");
}

void InitializeTypeMappings ()
{
// Should be replaced by src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs
throw new InvalidOperationException ("TypeMappings should be replaced during trimming!");
}

[return: DynamicallyAccessedMembers (Constructors)]
Expand Down Expand Up @@ -139,7 +127,7 @@ public override void RegisterNativeMembers (

protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
{
if (TypeMappings.TryGetValue (jniSimpleReference, out var target)) {
if (TypeMapping.TryGetType (jniSimpleReference, out var target)) {
yield return target;
}
foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) {
Expand All @@ -149,15 +137,12 @@ protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpl

protected override IEnumerable<string> GetSimpleReferences (Type type)
{
return base.GetSimpleReferences (type)
.Concat (CreateSimpleReferencesEnumerator (type));
}
foreach (var r in base.GetSimpleReferences (type)) {
yield return r;
}

IEnumerable<string> CreateSimpleReferencesEnumerator (Type type)
{
foreach (var e in TypeMappings) {
if (e.Value == type)
yield return e.Key;
if (TypeMapping.TryGetJavaClassName (type, out var javaClassName)) {
yield return javaClassName;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.Buffers.Binary;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO.Hashing;
using System.Runtime.InteropServices;
using System.Text;
using Android.Runtime;

namespace Microsoft.Android.Runtime;

internal static class TypeMapping
{
internal static bool TryGetType (string javaClassName, [NotNullWhen (true)] out Type? type)
{
ulong hash = Hash (javaClassName);

// the hashes array is sorted and all the hashes are unique
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you going to check the uniqueness of the hashcodes at build time to guarantee this?

An alternative way to deal with this would be to allow non-unique hashcodes and check all candidates in the map. If you do that, you can change the hashcodes to 32-bit that makes the map 2x smaller and dealing with hashcodes faster, at the cost of a rare hash collision.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you going to check the uniqueness of the hashcodes at build time to guarantee this?

Yes, the check is here: https://github.com/dotnet/android/pull/9856/files#diff-8e22a99ebc58c9417a48013888fc90d72fa6b638aff5d2d449cbf2c78bd0a8fbR149-R161

If you do that, you can change the hashcodes to 32-bit that makes the map 2x smaller and dealing with hashcodes faster, at the cost of a rare hash collision.

That's definitely worth trying. Assuming even the XxHash32 collisions are very rare, in the typical case there should be just 1 string comparison to verify that the hash belongs to the expected result.

int typeIndex = MemoryExtensions.BinarySearch (JavaClassNameHashes, hash);
if (typeIndex < 0) {
type = null;
return false;
}

type = GetTypeByIndex (typeIndex);
if (type is null) {
throw new InvalidOperationException ($"Type with hash {hash} not found.");
}

// ensure this is not a hash collision
var resolvedJavaClassName = GetJavaClassNameByIndex (TypeIndexToJavaClassNameIndex [typeIndex]);
if (resolvedJavaClassName != javaClassName) {
type = null;
return false;
}

return true;
}

internal static bool TryGetJavaClassName (Type type, [NotNullWhen (true)] out string? className)
{
string? fullName = type.FullName;
if (fullName is null) {
className = null;
return false;
}

ulong hash = Hash (fullName);

// the hashes array is sorted and all the hashes are unique
int javaClassNameIndex = MemoryExtensions.BinarySearch (TypeNameHashes, hash);
if (javaClassNameIndex < 0) {
className = null;
return false;
}

className = GetJavaClassNameByIndex (javaClassNameIndex);
if (className is null) {
throw new InvalidOperationException ($"Java class name with hash {hash} not found.");
}

// ensure this is not a hash collision
var resolvedType = GetTypeByIndex (JavaClassNameIndexToTypeIndex [javaClassNameIndex]);
if (resolvedType?.FullName != type.FullName) {
className = null;
return false;
}

return true;
}

private static ulong Hash (string javaClassName)
{
ReadOnlySpan<byte> bytes = MemoryMarshal.AsBytes (javaClassName.AsSpan ());
ulong hash = XxHash3.HashToUInt64 (bytes);

// The bytes in the hashes array are stored as little endian. If the target platform is big endian,
// we need to reverse the endianness of the hash.
if (!BitConverter.IsLittleEndian) {
hash = BinaryPrimitives.ReverseEndianness (hash);
}

return hash;
}

// Replaced by src/Microsoft.Android.Sdk.ILLink/TypeMappingStep.cs
private static ReadOnlySpan<ulong> JavaClassNameHashes => throw new NotImplementedException ();
private static ReadOnlySpan<ulong> TypeNameHashes => throw new NotImplementedException ();
private static ReadOnlySpan<int> JavaClassNameIndexToTypeIndex => throw new NotImplementedException ();
private static ReadOnlySpan<int> TypeIndexToJavaClassNameIndex => throw new NotImplementedException ();
private static Type? GetTypeByIndex (int index) => throw new NotImplementedException ();
private static string? GetJavaClassNameByIndex (int index) => throw new NotImplementedException ();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
<ProjectReference Include="..\Mono.Android\Mono.Android.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" />
</ItemGroup>

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

<!-- Copy runtime assemblies to bin/$(Configuration)/dotnet/packs folder -->
Expand All @@ -34,6 +38,7 @@
<Target Name="_CopyToPackDirs">
<ItemGroup>
<_RuntimePackFiles Include="$(OutputPath)Microsoft.Android.Runtime.NativeAOT.dll" AndroidRID="%(AndroidAbiAndRuntimeFlavor.AndroidRID)" AndroidRuntime="%(AndroidAbiAndRuntimeFlavor.AndroidRuntime)" />
<_RuntimePackFiles Include="$(OutputPath)System.IO.Hashing.dll" AndroidRID="%(AndroidAbiAndRuntimeFlavor.AndroidRID)" AndroidRuntime="%(AndroidAbiAndRuntimeFlavor.AndroidRuntime)" />
</ItemGroup>
<Message Importance="high" Text="$(TargetPath) %(AndroidAbiAndRuntimeFlavor.AndroidRID)" />
<Copy
Expand Down
Loading
Loading