Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
/// <remarks>
/// <para>The generated assembly looks like this (pseudo-C#):</para>
/// <code>
/// // Assembly-level TypeMap attributes — one per Java peer type:
/// [assembly: TypeMap&lt;Java.Lang.Object&gt;("android/app/Activity", typeof(Activity_Proxy))] // unconditional (ACW)
/// [assembly: TypeMap&lt;Java.Lang.Object&gt;("android/widget/TextView", typeof(TextView_Proxy), typeof(TextView))] // trimmable (MCW)
/// [assembly: TypeMapAssociation&lt;Java.Lang.Object&gt;(typeof(MyTextView), typeof(Android_Widget_TextView_Proxy))] // managed → proxy
/// // Assembly-level TypeMap attributes — one per Java peer type.
/// // The anchor type T is Java.Lang.Object in merged mode (Release) or
/// // a per-assembly __TypeMapAnchor in per-assembly mode (Debug):
/// [assembly: TypeMap&lt;T&gt;("android/app/Activity", typeof(Activity_Proxy))] // unconditional (ACW)
/// [assembly: TypeMap&lt;T&gt;("android/widget/TextView", typeof(TextView_Proxy), typeof(TextView))] // trimmable (MCW)
/// [assembly: TypeMapAssociation&lt;T&gt;(typeof(MyTextView), typeof(Android_Widget_TextView_Proxy))] // managed → proxy
///
/// // One proxy type per Java peer that needs activation or UCO wrappers:
/// public sealed class Activity_Proxy : JavaPeerProxy&lt;Activity&gt;, IAndroidCallableWrapper // IAndroidCallableWrapper for ACWs only
Expand Down Expand Up @@ -107,6 +109,8 @@ sealed class TypeMapAssemblyEmitter
MemberReferenceHandle _jniEnvTypesRegisterNativesRef;
MemberReferenceHandle _readOnlySpanOfJniNativeMethodCtorRef;

EntityHandle _anchorTypeHandle;

/// <summary>
/// Creates a new emitter.
/// </summary>
Expand All @@ -123,7 +127,11 @@ public TypeMapAssemblyEmitter (Version systemRuntimeVersion)
/// <summary>
/// Emits a PE assembly from the given model and writes it to <paramref name="stream"/>.
/// </summary>
public void Emit (TypeMapAssemblyData model, Stream stream)
/// <param name="useSharedTypemapUniverse">
/// When true, uses <c>Java.Lang.Object</c> as the shared anchor type so all assemblies
/// share a single typemap universe. When false, emits a per-assembly <c>__TypeMapAnchor</c>.
/// </param>
public void Emit (TypeMapAssemblyData model, Stream stream, bool useSharedTypemapUniverse = false)
{
if (model is null) {
throw new ArgumentNullException (nameof (model));
Expand All @@ -132,17 +140,26 @@ public void Emit (TypeMapAssemblyData model, Stream stream)
throw new ArgumentNullException (nameof (stream));
}

EmitCore (model);
EmitCore (model, useSharedTypemapUniverse);
_pe.WritePE (stream);
}

void EmitCore (TypeMapAssemblyData model)
void EmitCore (TypeMapAssemblyData model, bool useSharedTypemapUniverse)
{
_pe.EmitPreamble (model.AssemblyName, model.ModuleName, MetadataHelper.ComputeContentFingerprint (model));

_javaInteropRef = _pe.AddAssemblyRef ("Java.Interop", new Version (0, 0, 0, 0));

EmitTypeReferences ();
if (useSharedTypemapUniverse) {
// Use Java.Lang.Object as the shared anchor so all assemblies share a single
// typemap universe that can be merged at startup.
_anchorTypeHandle = _pe.Metadata.AddTypeReference (_pe.MonoAndroidRef,
_pe.Metadata.GetOrAddString ("Java.Lang"),
_pe.Metadata.GetOrAddString ("Object"));
} else {
EmitAnchorType ();
}
EmitMemberReferences ();

// Track wrapper method names → handles for RegisterNatives
Expand Down Expand Up @@ -212,6 +229,26 @@ void EmitTypeReferences ()
_readOnlySpanOfJniNativeMethodSpec = MakeGenericTypeSpec_ValueType (_readOnlySpanOpenRef, _jniNativeMethodRef);
}

/// <summary>
/// Emits an internal <c>__TypeMapAnchor</c> class used as the group type parameter
/// for <c>TypeMap&lt;T&gt;</c> and <c>TypeMapAssociation&lt;T&gt;</c>. Each per-assembly
/// typemap DLL gets its own anchor, creating an isolated typemap universe.
/// </summary>
void EmitAnchorType ()
{
var metadata = _pe.Metadata;
var objectRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Object"));

_anchorTypeHandle = metadata.AddTypeDefinition (
TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class,
default,
metadata.GetOrAddString ("__TypeMapAnchor"),
objectRef,
MetadataTokens.FieldDefinitionHandle (metadata.GetRowCount (TableIndex.Field) + 1),
MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1));
}

void EmitMemberReferences ()
{
_getTypeFromHandleRef = _pe.AddMemberRef (_systemTypeRef, "GetTypeFromHandle",
Expand Down Expand Up @@ -306,10 +343,8 @@ void EmitTypeMapAttributeCtorRef ()
var typeMapAttrOpenRef = metadata.AddTypeReference (_pe.SystemRuntimeInteropServicesRef,
metadata.GetOrAddString ("System.Runtime.InteropServices"),
metadata.GetOrAddString ("TypeMapAttribute`1"));
var javaLangObjectRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object"));

var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (typeMapAttrOpenRef, javaLangObjectRef);
var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (typeMapAttrOpenRef, _anchorTypeHandle);

// 2-arg: TypeMap(string jniName, Type proxyType) — unconditional
_typeMapAttrCtorRef2Arg = _pe.AddMemberRef (closedAttrTypeSpec, ".ctor",
Expand Down Expand Up @@ -337,9 +372,7 @@ void EmitTypeMapAssociationAttributeCtorRef ()
var typeMapAssociationAttrOpenRef = metadata.AddTypeReference (_pe.SystemRuntimeInteropServicesRef,
metadata.GetOrAddString ("System.Runtime.InteropServices"),
metadata.GetOrAddString ("TypeMapAssociationAttribute`1"));
var javaLangObjectRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object"));
var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (typeMapAssociationAttrOpenRef, javaLangObjectRef);
var closedAttrTypeSpec = _pe.MakeGenericTypeSpec (typeMapAssociationAttrOpenRef, _anchorTypeHandle);

_typeMapAssociationAttrCtorRef = _pe.AddMemberRef (closedAttrTypeSpec, ".ctor",
sig => sig.MethodSignature (isInstanceMethod: true).Parameters (2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ public TypeMapAssemblyGenerator (Version systemRuntimeVersion)
/// <param name="peers">Scanned Java peer types.</param>
/// <param name="stream">Stream to write the output PE assembly to.</param>
/// <param name="assemblyName">Assembly name for the generated assembly.</param>
public void Generate (IReadOnlyList<JavaPeerInfo> peers, Stream stream, string assemblyName)
/// <param name="useSharedTypemapUniverse">
/// When true, uses <c>Java.Lang.Object</c> as the shared anchor type. When false, emits a per-assembly anchor.
/// </param>
public void Generate (IReadOnlyList<JavaPeerInfo> peers, Stream stream, string assemblyName, bool useSharedTypemapUniverse = false)
{
var model = ModelBuilder.Build (peers, assemblyName + ".dll", assemblyName);
var emitter = new TypeMapAssemblyEmitter (_systemRuntimeVersion);
emitter.Emit (model, stream);
emitter.Emit (model, stream, useSharedTypemapUniverse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public TrimmableTypeMapResult Execute (
IReadOnlyList<(string Name, PEReader Reader)> assemblies,
Version systemRuntimeVersion,
HashSet<string> frameworkAssemblyNames,
bool useSharedTypemapUniverse = false,
ManifestConfig? manifestConfig = null,
XDocument? manifestTemplate = null)
{
Expand All @@ -41,7 +42,7 @@ public TrimmableTypeMapResult Execute (
RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig));
PropagateDeferredRegistrationToBaseClasses (allPeers);

var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion);
var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse);
var jcwPeers = allPeers.Where (p =>
!frameworkAssemblyNames.Contains (p.AssemblyName)
|| p.JavaName.StartsWith ("mono/", StringComparison.Ordinal)).ToList ();
Expand Down Expand Up @@ -112,7 +113,7 @@ GeneratedManifest GenerateManifest (List<JavaPeerInfo> allPeers, AssemblyManifes
return (peers, manifestInfo);
}

List<GeneratedAssembly> GenerateTypeMapAssemblies (List<JavaPeerInfo> allPeers, Version systemRuntimeVersion)
List<GeneratedAssembly> GenerateTypeMapAssemblies (List<JavaPeerInfo> allPeers, Version systemRuntimeVersion, bool useSharedTypemapUniverse)
{
var peersByAssembly = allPeers.GroupBy (p => p.AssemblyName, StringComparer.Ordinal).OrderBy (g => g.Key, StringComparer.Ordinal);
var generatedAssemblies = new List<GeneratedAssembly> ();
Expand All @@ -123,14 +124,14 @@ List<GeneratedAssembly> GenerateTypeMapAssemblies (List<JavaPeerInfo> allPeers,
perAssemblyNames.Add (assemblyName);
var peers = group.ToList ();
var stream = new MemoryStream ();
generator.Generate (peers, stream, assemblyName);
generator.Generate (peers, stream, assemblyName, useSharedTypemapUniverse);
stream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly (assemblyName, stream));
logger.LogGeneratedTypeMapAssemblyInfo (assemblyName, peers.Count);
}
var rootStream = new MemoryStream ();
var rootGenerator = new RootTypeMapAssemblyGenerator (systemRuntimeVersion);
rootGenerator.Generate (perAssemblyNames, rootStream);
rootGenerator.Generate (perAssemblyNames, useSharedTypemapUniverse, rootStream);
rootStream.Position = 0;
generatedAssemblies.Add (new GeneratedAssembly ("_Microsoft.Android.TypeMaps", rootStream));
logger.LogGeneratedRootTypeMapInfo (perAssemblyNames.Count);
Expand Down
4 changes: 0 additions & 4 deletions src/Mono.Android/Android.Runtime/JNIEnvInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,6 @@ internal static unsafe void Initialize (JnienvInitializeArgs* args)
);
JniRuntime.SetCurrent (androidRuntime);

if (RuntimeFeature.TrimmableTypeMap) {
TrimmableTypeMap.Initialize ();
}

grefIGCUserPeer_class = args->grefIGCUserPeer;
grefGCUserPeerable_class = args->grefGCUserPeerable;

Expand Down
44 changes: 44 additions & 0 deletions src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Android.Runtime;

/// <summary>
/// Wraps N <see cref="SingleUniverseTypeMap"/> instances and flattens
/// results across all universes. Debug-only — each assembly has its own
/// universe with an isolated <c>TypeMapLazyDictionary</c>.
/// </summary>
sealed class AggregateTypeMap : ITypeMapWithAliasing
{
readonly SingleUniverseTypeMap[] _universes;

public AggregateTypeMap (SingleUniverseTypeMap[] universes)
{
ArgumentNullException.ThrowIfNull (universes);
_universes = universes;
}

public IEnumerable<Type> GetTypes (string jniName)
{
foreach (var universe in _universes) {
foreach (var type in universe.GetTypes (jniName)) {
yield return type;
}
}
}

public bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? proxyType)
{
// First-wins: each managed type exists in exactly one assembly
foreach (var universe in _universes) {
if (universe.TryGetProxyType (managedType, out proxyType)) {
return true;
}
}
proxyType = null;
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Android.Runtime;

/// <summary>
/// Abstraction over the typemap dictionary that handles alias resolution.
/// Both Debug (per-assembly universes) and Release (single merged universe)
/// go through this interface, so <see cref="TrimmableTypeMap"/> doesn't
/// need to know about aliasing mechanics.
/// </summary>
interface ITypeMapWithAliasing
{
/// <summary>
/// Returns all types mapped to a JNI name, resolving alias holders.
/// For non-alias entries this yields a single type. For alias groups
/// it follows each alias key and yields the surviving target types.
/// </summary>
IEnumerable<Type> GetTypes (string jniName);

/// <summary>
/// Resolves a managed type to its proxy type (the generated type that
/// carries the <see cref="JavaPeerProxy"/> attribute).
/// </summary>
bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? proxyType);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Java.Interop;

namespace Microsoft.Android.Runtime;

/// <summary>
/// Wraps a single <see cref="IReadOnlyDictionary{String, Type}"/> universe
/// and its proxy type map. Handles <see cref="JavaPeerAliasesAttribute"/>
/// alias resolution within that universe.
/// Used in both Debug (one per assembly) and Release (single merged).
/// </summary>
sealed class SingleUniverseTypeMap : ITypeMapWithAliasing
{
readonly IReadOnlyDictionary<string, Type> _typeMap;
readonly IReadOnlyDictionary<Type, Type> _proxyTypeMap;

public SingleUniverseTypeMap (IReadOnlyDictionary<string, Type> typeMap, IReadOnlyDictionary<Type, Type> proxyTypeMap)
{
ArgumentNullException.ThrowIfNull (typeMap);
ArgumentNullException.ThrowIfNull (proxyTypeMap);
_typeMap = typeMap;
_proxyTypeMap = proxyTypeMap;
}

public IEnumerable<Type> GetTypes (string jniName)
{
if (!_typeMap.TryGetValue (jniName, out var mappedType)) {
yield break;
}

// Fast path: non-alias entry
if (mappedType.GetCustomAttribute<JavaPeerProxy> (inherit: false) is not null) {
yield return mappedType;
yield break;
}

// Slow path: alias holder — follow each alias key
var aliases = mappedType.GetCustomAttribute<JavaPeerAliasesAttribute> (inherit: false);
if (aliases is null) {
yield break;
}

foreach (var key in aliases.Aliases) {
if (_typeMap.TryGetValue (key, out var aliasEntryType) &&
aliasEntryType.GetCustomAttribute<JavaPeerProxy> (inherit: false) is not null) {
yield return aliasEntryType;
}
}
}

public bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? proxyType)
{
if (!_proxyTypeMap.TryGetValue (managedType, out var mappedProxyType)) {
proxyType = null;
return false;
}

// Fast path: direct proxy
if (mappedProxyType.GetCustomAttribute<JavaPeerProxy> (inherit: false) is not null) {
proxyType = mappedProxyType;
return true;
}

// Slow path: alias holder — find the alias whose target type matches
var aliases = mappedProxyType.GetCustomAttribute<JavaPeerAliasesAttribute> (inherit: false);
if (aliases is not null) {
foreach (var key in aliases.Aliases) {
if (_typeMap.TryGetValue (key, out var aliasProxyType)) {
var aliasProxy = aliasProxyType.GetCustomAttribute<JavaPeerProxy> (inherit: false);
if (aliasProxy is not null && TrimmableTypeMap.TargetTypeMatches (managedType, aliasProxy.TargetType)) {
proxyType = aliasProxyType;
return true;
}
}
}
}

proxyType = null;
return false;
}
}
Loading