diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs
index 46c556bb1e6..5d5f7434d49 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/RootTypeMapAssemblyGenerator.cs
@@ -10,7 +10,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
/// Generates the root _Microsoft.Android.TypeMaps.dll assembly that:
///
-/// - References all per-assembly typemap assemblies via [assembly: TypeMapAssemblyTargetAttribute<__TypeMapAnchor>("name")].
+/// - References all per-assembly typemap assemblies via [assembly: TypeMapAssemblyTargetAttribute<T>("name")].
/// - Emits a TypeMapLoader class whose Initialize() method calls
/// with the appropriate
/// type mapping dictionaries.
@@ -36,7 +36,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
/// TrimmableTypeMap.Initialize(
/// TypeMapping.GetOrCreateExternalTypeMapping<Java.Lang.Object>(),
/// TypeMapping.GetOrCreateProxyTypeMapping<Java.Lang.Object>(),
-/// arrayMapsByRank);
+/// arrayMapsByAssemblyAndRank);
///
/// // Option B: Per-assembly universes (aggregated)
/// var typeMaps = new IReadOnlyDictionary<string, Type>[] {
@@ -47,7 +47,7 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
/// TypeMapping.GetOrCreateProxyTypeMapping<_Mono_Android_TypeMap.__TypeMapAnchor>(),
/// TypeMapping.GetOrCreateProxyTypeMapping<_MyApp_TypeMap.__TypeMapAnchor>(),
/// };
-/// TrimmableTypeMap.Initialize(typeMaps, proxyMaps, arrayMapsByRank);
+/// TrimmableTypeMap.Initialize(typeMaps, proxyMaps, arrayMapsByAssemblyAndRank);
/// }
/// }
/// }
@@ -76,7 +76,7 @@ public RootTypeMapAssemblyGenerator (Version systemRuntimeVersion)
///
/// Maximum array rank for which per-assembly typemaps emitted __ArrayMapRank{N}
/// sentinels. Must match the value passed to the per-assembly generators. 0 means
- /// no array sentinels were emitted; the loader passes null for arrayMapsByRank.
+ /// no array sentinels were emitted; the loader passes null for array maps.
///
public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSharedTypemapUniverse, Stream stream, string? assemblyName = null, string? moduleName = null, int maxArrayRank = 0)
{
@@ -121,13 +121,13 @@ public void Generate (IReadOnlyList perAssemblyTypeMapNames, bool useSha
} else {
EmitPerAssemblyUniverseAssemblyTargetAttributes (pe, perAssemblyTypeMapNames);
}
+ EmitArrayAssemblyTargetAttributes (pe, perAssemblyTypeMapNames, maxArrayRank);
// Emit [assembly: IgnoresAccessChecksTo("...")] so TypeMapLoader.Initialize() can access
- // internal types (TrimmableTypeMap and friends in Mono.Android, and __TypeMapAnchor
- // in each per-assembly typemap DLL when in aggregate mode). Shared rank anchors
- // (__ArrayMapRank{N}) live in Mono.Android already.
+ // internal types (TrimmableTypeMap and friends in Mono.Android, and private anchors
+ // in each per-assembly typemap DLL when aggregate universes or array maps are used).
var accessTargets = new List { "Mono.Android" };
- if (!useSharedTypemapUniverse) {
+ if (!useSharedTypemapUniverse || maxArrayRank > 0) {
accessTargets.AddRange (perAssemblyTypeMapNames);
}
pe.EmitIgnoresAccessChecksToAttribute (accessTargets);
@@ -159,6 +159,24 @@ static void EmitPerAssemblyUniverseAssemblyTargetAttributes (PEAssemblyBuilder p
}
}
+ static void EmitArrayAssemblyTargetAttributes (PEAssemblyBuilder pe, IReadOnlyList perAssemblyTypeMapNames, int maxArrayRank)
+ {
+ if (maxArrayRank == 0) {
+ return;
+ }
+
+ var openAttrRef = GetTypeMapAssemblyTargetAttributeRef (pe);
+ foreach (var name in perAssemblyTypeMapNames) {
+ var asmRef = pe.FindOrAddAssemblyRef (name);
+ for (int rank = 1; rank <= maxArrayRank; rank++) {
+ var rankAnchorRef = pe.Metadata.AddTypeReference (asmRef,
+ default, pe.Metadata.GetOrAddString ($"__ArrayMapRank{rank}"));
+ var ctorRef = GetTypeMapAssemblyTargetAttributeCtorRef (pe, openAttrRef, rankAnchorRef);
+ EmitAssemblyTargetAttribute (pe, ctorRef, name);
+ }
+ }
+ }
+
static TypeReferenceHandle GetTypeMapAssemblyTargetAttributeRef (PEAssemblyBuilder pe)
{
return pe.Metadata.AddTypeReference (pe.SystemRuntimeInteropServicesRef,
@@ -217,29 +235,31 @@ static void EmitTypeMapLoader (PEAssemblyBuilder pe, EntityHandle anchorTypeHand
MetadataTokens.MethodDefinitionHandle (metadata.GetRowCount (TableIndex.MethodDef) + 1));
var externalDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
+ var externalDictArrayTypeSpec = MakeIReadOnlyDictArrayTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
if (useSharedTypemapUniverse) {
var initializeRef = AddInitializeSingleWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef);
EmitInitializeWithSingleTypeMap (pe, anchorTypeHandle, getExternalMemberRef, getProxyMemberRef,
- initializeRef, externalDictTypeSpec, maxArrayRank);
+ initializeRef, externalDictTypeSpec, externalDictArrayTypeSpec, perAssemblyTypeMapNames, maxArrayRank);
} else {
var initializeRef = AddInitializeAggregateWithArraysRef (pe, trimmableTypeMapRef, iReadOnlyDictOpenRef, systemTypeRef);
var proxyDictTypeSpec = MakeIReadOnlyDictTypeSpec (pe, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false);
EmitInitializeWithAggregateTypeMap (pe, perAssemblyTypeMapNames, getExternalMemberRef, getProxyMemberRef,
- initializeRef, externalDictTypeSpec, proxyDictTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, maxArrayRank);
+ initializeRef, externalDictTypeSpec, proxyDictTypeSpec, externalDictArrayTypeSpec, iReadOnlyDictOpenRef, systemTypeRef, maxArrayRank);
}
}
///
- /// Aggregate IL emit. Builds typeMaps[N], proxyMaps[N], and either a
- /// flat arrayMapsByRank[maxArrayRank] from shared __ArrayMapRank{N}
- /// anchors or null when is 0.
+ /// Aggregate IL emit. Builds typeMaps[N], proxyMaps[N], and either
+ /// arrayMapsByAssemblyAndRank[N][maxArrayRank] from per-assembly
+ /// __ArrayMapRank{N} anchors or null when is 0.
///
static void EmitInitializeWithAggregateTypeMap (PEAssemblyBuilder pe,
IReadOnlyList perAssemblyTypeMapNames,
MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef,
MemberReferenceHandle initializeRef,
TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle proxyDictTypeSpec,
+ TypeSpecificationHandle externalDictArrayTypeSpec,
TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef,
int maxArrayRank)
{
@@ -267,10 +287,10 @@ static void EmitInitializeWithAggregateTypeMap (PEAssemblyBuilder pe,
EmitNewArrayLocal (encoder, count, proxyDictTypeSpec, slot: 1);
EmitFillArrayLocal (encoder, count, getProxySpecs, slot: 1);
- // TrimmableTypeMap.Initialize(typeMaps, proxyMaps, arrayMapsByRank-or-null)
+ // TrimmableTypeMap.Initialize(typeMaps, proxyMaps, arrayMapsByAssemblyAndRank-or-null)
encoder.LoadLocal (0);
encoder.LoadLocal (1);
- EmitArrayMapsByRankOrNull (pe, encoder, getExternalMemberRef, externalDictTypeSpec, maxArrayRank);
+ EmitArrayMapsByAssemblyAndRankOrNull (pe, encoder, perAssemblyTypeMapNames, getExternalMemberRef, externalDictTypeSpec, externalDictArrayTypeSpec, maxArrayRank);
encoder.OpCode (ILOpCode.Call);
encoder.Token (initializeRef);
encoder.OpCode (ILOpCode.Ret);
@@ -306,7 +326,7 @@ static void EmitFillArrayLocal (InstructionEncoder encoder, int count, EntityHan
}
}
- /// MemberRef for TrimmableTypeMap.Initialize(typeMaps[], proxyMaps[], arrayMapsByRank[]).
+ /// MemberRef for TrimmableTypeMap.Initialize(typeMaps[], proxyMaps[], arrayMapsByAssemblyAndRank[][]).
static MemberReferenceHandle AddInitializeAggregateWithArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef,
TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef)
{
@@ -320,7 +340,8 @@ static MemberReferenceHandle AddInitializeAggregateWithArraysRef (PEAssemblyBuil
// Param 2: IReadOnlyDictionary[]
blob.WriteByte (0x1D);
EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false);
- // Param 3: IReadOnlyDictionary?[]
+ // Param 3: IReadOnlyDictionary?[][]
+ blob.WriteByte (0x1D);
blob.WriteByte (0x1D);
EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
return pe.Metadata.AddMemberReference (trimmableTypeMapRef,
@@ -329,13 +350,14 @@ static MemberReferenceHandle AddInitializeAggregateWithArraysRef (PEAssemblyBuil
///
/// Shared-universe IL emit. Single merged main map (anchored on Java.Lang.Object)
- /// plus either a flat arrayMapsByRank[maxArrayRank] from shared
+ /// plus either arrayMapsByAssemblyAndRank[N][maxArrayRank] from per-assembly
/// __ArrayMapRank{N} anchors or null when is 0.
///
static void EmitInitializeWithSingleTypeMap (PEAssemblyBuilder pe, EntityHandle anchorTypeHandle,
MemberReferenceHandle getExternalMemberRef, MemberReferenceHandle getProxyMemberRef,
MemberReferenceHandle initializeRef,
- TypeSpecificationHandle externalDictTypeSpec,
+ TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle externalDictArrayTypeSpec,
+ IReadOnlyList perAssemblyTypeMapNames,
int maxArrayRank)
{
var getExternalSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, anchorTypeHandle);
@@ -345,19 +367,19 @@ static void EmitInitializeWithSingleTypeMap (PEAssemblyBuilder pe, EntityHandle
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { }),
encoder => {
- // TrimmableTypeMap.Initialize(GetExternal(), GetProxy(), arrayMapsByRank-or-null)
+ // TrimmableTypeMap.Initialize(GetExternal(), GetProxy(), arrayMapsByAssemblyAndRank-or-null)
encoder.OpCode (ILOpCode.Call);
encoder.Token (getExternalSpec);
encoder.OpCode (ILOpCode.Call);
encoder.Token (getProxySpec);
- EmitArrayMapsByRankOrNull (pe, encoder, getExternalMemberRef, externalDictTypeSpec, maxArrayRank);
+ EmitArrayMapsByAssemblyAndRankOrNull (pe, encoder, perAssemblyTypeMapNames, getExternalMemberRef, externalDictTypeSpec, externalDictArrayTypeSpec, maxArrayRank);
encoder.OpCode (ILOpCode.Call);
encoder.Token (initializeRef);
encoder.OpCode (ILOpCode.Ret);
});
}
- /// MemberRef for TrimmableTypeMap.Initialize(typeMap, proxyMap, arrayMapsByRank[]).
+ /// MemberRef for TrimmableTypeMap.Initialize(typeMap, proxyMap, arrayMapsByAssemblyAndRank[][]).
static MemberReferenceHandle AddInitializeSingleWithArraysRef (PEAssemblyBuilder pe, TypeReferenceHandle trimmableTypeMapRef,
TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef)
{
@@ -368,29 +390,49 @@ static MemberReferenceHandle AddInitializeSingleWithArraysRef (PEAssemblyBuilder
EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: false);
blob.WriteByte (0x1D);
+ blob.WriteByte (0x1D);
EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString: true);
return pe.Metadata.AddMemberReference (trimmableTypeMapRef,
pe.Metadata.GetOrAddString ("Initialize"), pe.Metadata.GetOrAddBlob (blob));
}
///
- /// Emits IL that pushes either a fresh IReadOnlyDictionary<string, Type>?[maxArrayRank]
+ /// Emits IL that pushes either a fresh
+ /// IReadOnlyDictionary<string, Type>?[assemblyCount][maxArrayRank]
/// (when > 0) or ldnull.
///
- static void EmitArrayMapsByRankOrNull (PEAssemblyBuilder pe, InstructionEncoder encoder,
- MemberReferenceHandle getExternalMemberRef, TypeSpecificationHandle externalDictTypeSpec, int maxArrayRank)
+ static void EmitArrayMapsByAssemblyAndRankOrNull (PEAssemblyBuilder pe, InstructionEncoder encoder,
+ IReadOnlyList perAssemblyTypeMapNames,
+ MemberReferenceHandle getExternalMemberRef,
+ TypeSpecificationHandle externalDictTypeSpec, TypeSpecificationHandle externalDictArrayTypeSpec,
+ int maxArrayRank)
{
if (maxArrayRank == 0) {
encoder.OpCode (ILOpCode.Ldnull);
return;
}
- var monoAndroidRuntimeNs = pe.Metadata.GetOrAddString ("Microsoft.Android.Runtime");
+ encoder.LoadConstantI4 (perAssemblyTypeMapNames.Count);
+ encoder.OpCode (ILOpCode.Newarr);
+ encoder.Token (externalDictArrayTypeSpec);
+ for (int i = 0; i < perAssemblyTypeMapNames.Count; i++) {
+ var asmRef = pe.FindOrAddAssemblyRef (perAssemblyTypeMapNames [i]);
+ encoder.OpCode (ILOpCode.Dup);
+ encoder.LoadConstantI4 (i);
+ EmitArrayMapsByRank (pe, encoder, asmRef, getExternalMemberRef, externalDictTypeSpec, maxArrayRank);
+ encoder.OpCode (ILOpCode.Stelem_ref);
+ }
+ }
+
+ static void EmitArrayMapsByRank (PEAssemblyBuilder pe, InstructionEncoder encoder,
+ AssemblyReferenceHandle assemblyRef,
+ MemberReferenceHandle getExternalMemberRef, TypeSpecificationHandle externalDictTypeSpec, int maxArrayRank)
+ {
encoder.LoadConstantI4 (maxArrayRank);
encoder.OpCode (ILOpCode.Newarr);
encoder.Token (externalDictTypeSpec);
for (int r = 0; r < maxArrayRank; r++) {
- var rankRef = pe.Metadata.AddTypeReference (pe.MonoAndroidRef, monoAndroidRuntimeNs,
+ var rankRef = pe.Metadata.AddTypeReference (assemblyRef, default,
pe.Metadata.GetOrAddString ($"__ArrayMapRank{r + 1}"));
var rankSpec = MakeGenericMethodSpec (pe, getExternalMemberRef, rankRef);
encoder.OpCode (ILOpCode.Dup);
@@ -443,6 +485,18 @@ static TypeSpecificationHandle MakeIReadOnlyDictTypeSpec (PEAssemblyBuilder pe,
return pe.Metadata.AddTypeSpecification (pe.Metadata.GetOrAddBlob (blob));
}
+ ///
+ /// Creates a TypeSpec for IReadOnlyDictionary<K, V>[] (for jagged-array newarr).
+ ///
+ static TypeSpecificationHandle MakeIReadOnlyDictArrayTypeSpec (PEAssemblyBuilder pe,
+ TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, bool keyIsString)
+ {
+ var blob = new BlobBuilder (32);
+ blob.WriteByte (0x1D);
+ EncodeIReadOnlyDictType (blob, iReadOnlyDictOpenRef, systemTypeRef, keyIsString);
+ return pe.Metadata.AddTypeSpecification (pe.Metadata.GetOrAddBlob (blob));
+ }
+
static void EncodeIReadOnlyDictType (BlobBuilder blob, TypeReferenceHandle iReadOnlyDictOpenRef, TypeReferenceHandle systemTypeRef, bool keyIsString)
{
blob.WriteByte (0x15); // ELEMENT_TYPE_GENERICINST
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
index af36173550f..4e74b6e3e54 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs
@@ -292,10 +292,9 @@ void EmitAnchorType ()
}
///
- /// Populates _rankAnchorHandles with TypeRefs to the shared
- /// Microsoft.Android.Runtime.__ArrayMapRank{N} types in Mono.Android. All per-asm
- /// typemap DLLs reference the same anchors so each rank's entries merge into one dict
- /// at runtime via TypeMapping.GetOrCreateExternalTypeMapping<__ArrayMapRank{N}>().
+ /// Emits private __ArrayMapRank{N} classes used as group type parameters
+ /// for array TypeMap<T> entries. Each per-assembly typemap owns its
+ /// rank anchors so array maps stay scoped to the generated assembly.
///
void EmitRankSentinels (TypeMapAssemblyData model)
{
@@ -304,11 +303,16 @@ void EmitRankSentinels (TypeMapAssemblyData model)
}
_rankAnchorHandles = new EntityHandle [model.MaxArrayRank];
- var ns = _pe.Metadata.GetOrAddString ("Microsoft.Android.Runtime");
+ var objectRef = _pe.Metadata.AddTypeReference (_pe.SystemRuntimeRef,
+ _pe.Metadata.GetOrAddString ("System"), _pe.Metadata.GetOrAddString ("Object"));
for (int i = 0; i < model.MaxArrayRank; i++) {
- _rankAnchorHandles [i] = _pe.Metadata.AddTypeReference (
- _pe.MonoAndroidRef, ns,
- _pe.Metadata.GetOrAddString ($"__ArrayMapRank{i + 1}"));
+ _rankAnchorHandles [i] = _pe.Metadata.AddTypeDefinition (
+ TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class,
+ default,
+ _pe.Metadata.GetOrAddString ($"__ArrayMapRank{i + 1}"),
+ objectRef,
+ MetadataTokens.FieldDefinitionHandle (_pe.Metadata.GetRowCount (TableIndex.Field) + 1),
+ MetadataTokens.MethodDefinitionHandle (_pe.Metadata.GetRowCount (TableIndex.MethodDef) + 1));
}
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
index 090075d73bf..e25293ff982 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
@@ -9,12 +9,6 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
public class TrimmableTypeMapGenerator
{
- ///
- /// Runtime-supported maximum array rank — must match the number of
- /// __ArrayMapRank{N} types pre-defined in Mono.Android.
- ///
- public const int MaxSupportedArrayRank = 8;
-
readonly ITrimmableTypeMapLogger logger;
static readonly HashSet RequiredFrameworkDeferredRegistrationTypes = new (StringComparer.Ordinal) {
@@ -47,12 +41,6 @@ public TrimmableTypeMapResult Execute (
if (maxArrayRank < 0) {
throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank, "Must be >= 0.");
}
- if (maxArrayRank > MaxSupportedArrayRank) {
- throw new ArgumentOutOfRangeException (nameof (maxArrayRank), maxArrayRank,
- $"_AndroidTrimmableTypeMapMaxArrayRank={maxArrayRank} exceeds the runtime's supported maximum ({MaxSupportedArrayRank}). " +
- $"To raise the limit, add additional __ArrayMapRank{{N}} types to Mono.Android.");
- }
-
var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies);
if (allPeers.Count == 0) {
logger.LogNoJavaPeerTypesFound ();
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs
index 3a997d0bca6..0374570f12a 100644
--- a/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs
+++ b/src/Mono.Android/Microsoft.Android.Runtime/AggregateTypeMap.cs
@@ -11,7 +11,7 @@ namespace Microsoft.Android.Runtime;
/// results across all universes. Debug-only — each assembly has its own
/// universe with an isolated TypeMapLazyDictionary.
///
-sealed class AggregateTypeMap : ITypeMapWithAliasing
+sealed class AggregateTypeMap : ITypeMap
{
readonly SingleUniverseTypeMap[] _universes;
@@ -41,4 +41,16 @@ public bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? pr
proxyType = null;
return false;
}
+
+ public bool TryGetArrayType (string jniName, int rankIndex, [NotNullWhen (true)] out Type? arrayType)
+ {
+ foreach (var universe in _universes) {
+ if (universe.TryGetArrayType (jniName, rankIndex, out arrayType)) {
+ return true;
+ }
+ }
+
+ arrayType = null;
+ return false;
+ }
}
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ArrayMapAnchors.cs b/src/Mono.Android/Microsoft.Android.Runtime/ArrayMapAnchors.cs
deleted file mode 100644
index 51b1ddb9cd5..00000000000
--- a/src/Mono.Android/Microsoft.Android.Runtime/ArrayMapAnchors.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-#nullable enable
-
-namespace Microsoft.Android.Runtime;
-
-// Shared TypeMap group anchors for per-rank array entries. Per-assembly typemap DLLs
-// reference these so all rank-N entries across all assemblies merge into a single
-// dictionary at runtime via TypeMapping.GetOrCreateExternalTypeMapping<__ArrayMapRankN>().
-//
-// To support a higher MaxArrayRank, add additional types here and bump
-// TrimmableTypeMapGenerator.MaxSupportedArrayRank.
-
-internal sealed class __ArrayMapRank1 { }
-internal sealed class __ArrayMapRank2 { }
-internal sealed class __ArrayMapRank3 { }
-internal sealed class __ArrayMapRank4 { }
-internal sealed class __ArrayMapRank5 { }
-internal sealed class __ArrayMapRank6 { }
-internal sealed class __ArrayMapRank7 { }
-internal sealed class __ArrayMapRank8 { }
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/ITypeMapWithAliasing.cs b/src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs
similarity index 66%
rename from src/Mono.Android/Microsoft.Android.Runtime/ITypeMapWithAliasing.cs
rename to src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs
index df77c07041d..8aa230d273f 100644
--- a/src/Mono.Android/Microsoft.Android.Runtime/ITypeMapWithAliasing.cs
+++ b/src/Mono.Android/Microsoft.Android.Runtime/ITypeMap.cs
@@ -7,12 +7,12 @@
namespace Microsoft.Android.Runtime;
///
-/// Abstraction over the typemap dictionary that handles alias resolution.
+/// Abstraction over a generated typemap universe.
/// Both Debug (per-assembly universes) and Release (single merged universe)
-/// go through this interface, so doesn't
-/// need to know about aliasing mechanics.
+/// go through this interface, so doesn't need
+/// to know about aliasing mechanics or per-rank array map storage.
///
-interface ITypeMapWithAliasing
+interface ITypeMap
{
///
/// Returns all proxy types mapped to a JNI name, resolving alias holders.
@@ -24,4 +24,9 @@ interface ITypeMapWithAliasing
/// carries the attribute).
///
bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? proxyType);
+
+ ///
+ /// Resolves a JNI leaf name and 0-based array rank index to a managed array type.
+ ///
+ bool TryGetArrayType (string jniName, int rankIndex, [NotNullWhen (true)] out Type? arrayType);
}
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs
index d1094a2a283..a6afdba2bab 100644
--- a/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs
+++ b/src/Mono.Android/Microsoft.Android.Runtime/SingleUniverseTypeMap.cs
@@ -14,17 +14,35 @@ namespace Microsoft.Android.Runtime;
/// alias resolution within that universe.
/// Used in both Debug (one per assembly) and Release (single merged).
///
-sealed class SingleUniverseTypeMap : ITypeMapWithAliasing
+sealed class SingleUniverseTypeMap : ITypeMap
{
readonly IReadOnlyDictionary _typeMap;
readonly IReadOnlyDictionary _proxyTypeMap;
+ readonly IReadOnlyDictionary?[][] _arrayMapsByUniverseAndRank;
public SingleUniverseTypeMap (IReadOnlyDictionary typeMap, IReadOnlyDictionary proxyTypeMap)
+ : this (typeMap, proxyTypeMap, arrayMapsByRank: null)
+ {
+ }
+
+ public SingleUniverseTypeMap (
+ IReadOnlyDictionary typeMap,
+ IReadOnlyDictionary proxyTypeMap,
+ IReadOnlyDictionary?[]? arrayMapsByRank)
+ : this (typeMap, proxyTypeMap, arrayMapsByRank is null ? null : [arrayMapsByRank])
+ {
+ }
+
+ public SingleUniverseTypeMap (
+ IReadOnlyDictionary typeMap,
+ IReadOnlyDictionary proxyTypeMap,
+ IReadOnlyDictionary?[][]? arrayMapsByUniverseAndRank)
{
ArgumentNullException.ThrowIfNull (typeMap);
ArgumentNullException.ThrowIfNull (proxyTypeMap);
_typeMap = typeMap;
_proxyTypeMap = proxyTypeMap;
+ _arrayMapsByUniverseAndRank = arrayMapsByUniverseAndRank ?? [];
}
public IEnumerable GetProxyTypes (string jniName)
@@ -83,4 +101,18 @@ public bool TryGetProxyType (Type managedType, [NotNullWhen (true)] out Type? pr
proxyType = null;
return false;
}
+
+ public bool TryGetArrayType (string jniName, int rankIndex, [NotNullWhen (true)] out Type? arrayType)
+ {
+ foreach (var arrayMapsByRank in _arrayMapsByUniverseAndRank) {
+ if ((uint)rankIndex < (uint)arrayMapsByRank.Length &&
+ arrayMapsByRank [rankIndex] is { } dict &&
+ dict.TryGetValue (jniName, out arrayType)) {
+ return true;
+ }
+ }
+
+ arrayType = null;
+ return false;
+ }
}
diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
index d4dd2b77919..58455d8ea79 100644
--- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
+++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs
@@ -13,7 +13,7 @@
namespace Microsoft.Android.Runtime;
///
-/// Central type map for the trimmable typemap path. Owns the ITypeMapWithAliasing
+/// Central type map for the trimmable typemap path. Owns the ITypeMap
/// and provides peer creation, invoker resolution, container factories, and native
/// method registration. All proxy attribute access is encapsulated here.
///
@@ -28,18 +28,13 @@ public class TrimmableTypeMap
s_instance ?? throw new InvalidOperationException (
"TrimmableTypeMap has not been initialized. Ensure RuntimeFeature.TrimmableTypeMap is enabled and the JNI runtime is initialized.");
- readonly ITypeMapWithAliasing _typeMap;
- // Per-rank array dictionaries, 0-indexed by (rank - 1). Sourced from the shared
- // __ArrayMapRank{N} TypeMap groups, so each rank is one merged dict spanning every
- // per-asm typemap DLL. Empty when no array entries were emitted (CoreCLR builds).
- readonly IReadOnlyDictionary?[] _arrayMapsByRank;
+ readonly ITypeMap _typeMap;
readonly ConcurrentDictionary _proxyCache = new ();
readonly ConcurrentDictionary _jniProxyCache = new (StringComparer.Ordinal);
- TrimmableTypeMap (ITypeMapWithAliasing typeMap, IReadOnlyDictionary?[]? arrayMapsByRank)
+ TrimmableTypeMap (ITypeMap typeMap)
{
_typeMap = typeMap;
- _arrayMapsByRank = arrayMapsByRank ?? [];
}
///
@@ -65,33 +60,42 @@ public static void Initialize (
{
ArgumentNullException.ThrowIfNull (typeMap);
ArgumentNullException.ThrowIfNull (proxyMap);
- InitializeCore (new SingleUniverseTypeMap (typeMap, proxyMap), arrayMapsByRank);
+ InitializeCore (new SingleUniverseTypeMap (typeMap, proxyMap, arrayMapsByRank));
+ }
+
+ ///
+ /// Initializes the singleton with a single merged typemap universe and per-assembly array maps.
+ ///
+ public static void Initialize (
+ IReadOnlyDictionary typeMap,
+ IReadOnlyDictionary proxyMap,
+ IReadOnlyDictionary?[][]? arrayMapsByUniverseAndRank)
+ {
+ ArgumentNullException.ThrowIfNull (typeMap);
+ ArgumentNullException.ThrowIfNull (proxyMap);
+ InitializeCore (new SingleUniverseTypeMap (typeMap, proxyMap, arrayMapsByUniverseAndRank));
}
///
/// Initializes the singleton with multiple per-assembly typemap universes and optional
- /// merged per-rank array dictionaries.
+ /// per-universe array dictionaries.
///
public static void Initialize (
IReadOnlyDictionary[] typeMaps,
IReadOnlyDictionary[] proxyMaps)
{
- Initialize (typeMaps, proxyMaps, arrayMapsByRank: null);
+ Initialize (typeMaps, proxyMaps, arrayMapsByUniverseAndRank: null);
}
///
/// Initializes the singleton with multiple per-assembly typemap universes and optional
- /// merged per-rank array dictionaries.
+ /// per-universe array dictionaries.
///
- ///
- /// 0-indexed by (rank - 1); null when no array entries were emitted. Same shape as
- /// the single-universe overload — array entries live in the shared
- /// __ArrayMapRank{N} TypeMap groups regardless of merge mode.
- ///
+ /// Array maps indexed by universe, then by 0-based rank.
public static void Initialize (
IReadOnlyDictionary[] typeMaps,
IReadOnlyDictionary[] proxyMaps,
- IReadOnlyDictionary?[]? arrayMapsByRank)
+ IReadOnlyDictionary?[][]? arrayMapsByUniverseAndRank)
{
ArgumentNullException.ThrowIfNull (typeMaps);
ArgumentNullException.ThrowIfNull (proxyMaps);
@@ -101,22 +105,25 @@ public static void Initialize (
if (typeMaps.Length != proxyMaps.Length) {
throw new ArgumentException ($"typeMaps.Length ({typeMaps.Length}) must equal proxyMaps.Length ({proxyMaps.Length}).", nameof (proxyMaps));
}
+ if (arrayMapsByUniverseAndRank is not null && arrayMapsByUniverseAndRank.Length != typeMaps.Length) {
+ throw new ArgumentException ($"arrayMapsByUniverseAndRank.Length ({arrayMapsByUniverseAndRank.Length}) must equal typeMaps.Length ({typeMaps.Length}).", nameof (arrayMapsByUniverseAndRank));
+ }
var universes = new SingleUniverseTypeMap [typeMaps.Length];
for (int i = 0; i < typeMaps.Length; i++) {
- universes [i] = new SingleUniverseTypeMap (typeMaps [i], proxyMaps [i]);
+ universes [i] = new SingleUniverseTypeMap (typeMaps [i], proxyMaps [i], arrayMapsByUniverseAndRank? [i]);
}
- InitializeCore (new AggregateTypeMap (universes), arrayMapsByRank);
+ InitializeCore (new AggregateTypeMap (universes));
}
- static void InitializeCore (ITypeMapWithAliasing typeMap, IReadOnlyDictionary?[]? arrayMapsByRank)
+ static void InitializeCore (ITypeMap typeMap)
{
lock (s_initLock) {
if (s_instance is not null) {
throw new InvalidOperationException ("TrimmableTypeMap has already been initialized.");
}
- s_instance = new TrimmableTypeMap (typeMap, arrayMapsByRank);
+ s_instance = new TrimmableTypeMap (typeMap);
}
}
@@ -388,10 +395,7 @@ internal bool TryGetArrayType (Type elementType, [NotNullWhen (true)] out Type?
? TryGetPrimitiveJniName (leaf, out var p) ? p : null
: TryGetJniNameForManagedType (leaf, out var jni) ? jni : null;
- if (leafJniName is not null &&
- (uint)rankIndex < (uint)_arrayMapsByRank.Length &&
- _arrayMapsByRank [rankIndex] is { } dict &&
- dict.TryGetValue (leafJniName, out arrayType)) {
+ if (leafJniName is not null && _typeMap.TryGetArrayType (leafJniName, rankIndex, out arrayType)) {
return true;
}
diff --git a/src/Mono.Android/Mono.Android.csproj b/src/Mono.Android/Mono.Android.csproj
index 89bf52ac3b1..ea0c64a2ace 100644
--- a/src/Mono.Android/Mono.Android.csproj
+++ b/src/Mono.Android/Mono.Android.csproj
@@ -361,7 +361,7 @@
-
+
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs
index 0d76ff22453..7de6aec0679 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/RootTypeMapAssemblyGeneratorTests.cs
@@ -135,6 +135,25 @@ public void Generate_MergedMode_TargetAttributesUseSharedAnchor ()
}, targetAttributes);
}
+ [Theory]
+ [InlineData (true)]
+ [InlineData (false)]
+ public void Generate_WithArrays_TargetAttributesUsePerAssemblyRankAnchors (bool useSharedTypemapUniverse)
+ {
+ var targets = new [] { "_App.TypeMap", "_Mono.Android.TypeMap" };
+ using var stream = GenerateRootAssembly (targets, useSharedTypemapUniverse, maxArrayRank: 2);
+ using var pe = new PEReader (stream);
+ var reader = pe.GetMetadataReader ();
+
+ var targetAttributes = GetTypeMapAssemblyTargetAttributeTargetTypes (reader);
+
+ Assert.Contains (("_App.TypeMap", "_App.TypeMap", "__ArrayMapRank1"), targetAttributes);
+ Assert.Contains (("_App.TypeMap", "_App.TypeMap", "__ArrayMapRank2"), targetAttributes);
+ Assert.Contains (("_Mono.Android.TypeMap", "_Mono.Android.TypeMap", "__ArrayMapRank1"), targetAttributes);
+ Assert.Contains (("_Mono.Android.TypeMap", "_Mono.Android.TypeMap", "__ArrayMapRank2"), targetAttributes);
+ Assert.DoesNotContain (targetAttributes, t => t.GenericArgumentTypeName == "__ArrayMapRank3");
+ }
+
static List GetTypeMapAssemblyTargetAttributes (MetadataReader reader)
{
var result = new List ();
@@ -170,6 +189,25 @@ static List GetTypeMapAssemblyTargetAttributes (MetadataReader
return result;
}
+ static List<(string TargetName, string GenericArgumentScope, string GenericArgumentTypeName)> GetTypeMapAssemblyTargetAttributeTargetTypes (MetadataReader reader)
+ {
+ var result = new List<(string TargetName, string GenericArgumentScope, string GenericArgumentTypeName)> ();
+ foreach (var attr in GetTypeMapAssemblyTargetAttributes (reader)) {
+ var targetName = GetTypeMapAssemblyTargetName (reader, attr);
+ var memberRef = reader.GetMemberReference ((MemberReferenceHandle)attr.Constructor);
+ var typeSpec = reader.GetTypeSpecification ((TypeSpecificationHandle)memberRef.Parent);
+ var blob = reader.GetBlobReader (typeSpec.Signature);
+ Assert.Equal (0x15, blob.ReadByte ()); // ELEMENT_TYPE_GENERICINST
+ Assert.Equal (0x12, blob.ReadByte ()); // ELEMENT_TYPE_CLASS
+ blob.ReadCompressedInteger (); // TypeMapAssemblyTargetAttribute`1 type
+ Assert.Equal (1, blob.ReadCompressedInteger ());
+ Assert.Equal (0x12, blob.ReadByte ()); // ELEMENT_TYPE_CLASS
+ var targetType = DecodeTypeDefOrRefOrSpec (blob.ReadCompressedInteger ());
+ result.Add ((targetName, GetResolutionScopeName (reader, targetType), GetTypeName (reader, targetType)));
+ }
+ return result;
+ }
+
static string GetTypeMapAssemblyTargetName (MetadataReader reader, CustomAttribute attr)
{
var blob = reader.GetBlobReader (attr.Value);
@@ -211,6 +249,17 @@ static string GetResolutionScopeName (MetadataReader reader, EntityHandle handle
};
}
+ static string GetTypeName (MetadataReader reader, EntityHandle handle)
+ {
+ if (handle.Kind == HandleKind.TypeDefinition) {
+ return reader.GetString (reader.GetTypeDefinition ((TypeDefinitionHandle)handle).Name);
+ }
+ if (handle.Kind != HandleKind.TypeReference) {
+ throw new InvalidOperationException ($"Unexpected type handle kind: {handle.Kind}");
+ }
+ return reader.GetString (reader.GetTypeReference ((TypeReferenceHandle)handle).Name);
+ }
+
[Theory]
[InlineData (true)]
[InlineData (false)]
@@ -332,24 +381,32 @@ public void Generate_MergedMode_WithArrays_ProducesValidPEAssembly ()
Assert.True (pe.HasMetadata);
}
- [Fact]
- public void Generate_MergedMode_WithArrays_ReferencesPerAsmRankSentinels ()
+ [Theory]
+ [InlineData (true)]
+ [InlineData (false)]
+ public void Generate_WithArrays_ReferencesPerAsmRankSentinels (bool useSharedTypemapUniverse)
{
using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"],
- useSharedTypemapUniverse: true, maxArrayRank: 2);
+ useSharedTypemapUniverse: useSharedTypemapUniverse, maxArrayRank: 2);
using var pe = new PEReader (stream);
var reader = pe.GetMetadataReader ();
- var typeRefNames = reader.TypeReferences
- .Select (h => reader.GetString (reader.GetTypeReference (h).Name))
+ var rankRefsByAssembly = reader.TypeReferences
+ .Select (h => reader.GetTypeReference (h))
+ .Where (t => reader.GetString (t.Name).StartsWith ("__ArrayMapRank", StringComparison.Ordinal))
+ .Select (t => (
+ Name: reader.GetString (t.Name),
+ Assembly: reader.GetString (reader.GetAssemblyReference ((AssemblyReferenceHandle) t.ResolutionScope).Name)))
.ToList ();
- Assert.Contains ("__ArrayMapRank1", typeRefNames);
- Assert.Contains ("__ArrayMapRank2", typeRefNames);
- Assert.DoesNotContain ("__ArrayMapRank3", typeRefNames);
+ Assert.Contains (("__ArrayMapRank1", "_App.TypeMap"), rankRefsByAssembly);
+ Assert.Contains (("__ArrayMapRank2", "_App.TypeMap"), rankRefsByAssembly);
+ Assert.Contains (("__ArrayMapRank1", "_Mono.Android.TypeMap"), rankRefsByAssembly);
+ Assert.Contains (("__ArrayMapRank2", "_Mono.Android.TypeMap"), rankRefsByAssembly);
+ Assert.DoesNotContain (rankRefsByAssembly, r => r.Name == "__ArrayMapRank3");
}
[Fact]
- public void Generate_MergedMode_WithArrays_NoPerAsmAccessNeeded ()
+ public void Generate_MergedMode_WithArrays_HasIgnoresAccessChecksToArrayAssemblies ()
{
using var stream = GenerateRootAssembly (["_App.TypeMap", "_Mono.Android.TypeMap"],
useSharedTypemapUniverse: true, maxArrayRank: 3);
@@ -358,7 +415,7 @@ public void Generate_MergedMode_WithArrays_NoPerAsmAccessNeeded ()
var accessAttrs = GetIgnoresAccessChecksToValues (reader);
Assert.Contains ("Mono.Android", accessAttrs);
- // Shared-mode root never needs per-asm internal access — rank anchors live in Mono.Android.
- Assert.DoesNotContain ("_App.TypeMap", accessAttrs);
+ Assert.Contains ("_App.TypeMap", accessAttrs);
+ Assert.Contains ("_Mono.Android.TypeMap", accessAttrs);
}
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
index 049bc74a654..ee29afa9466 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
@@ -1022,7 +1022,7 @@ public void Build_EmitArrayEntries_MultiplePeers_GetIndependentTrios ()
public class ArrayEntriesPeBlob
{
[Fact]
- public void FullPipeline_ArrayEntries_ReferencesSharedRankAnchors ()
+ public void FullPipeline_ArrayEntries_DefinesPrivateRankAnchors ()
{
var peer = MakeMcwPeer ("foo/Bar", "Foo.Bar", "App");
var outputPath = Path.Combine (Path.GetTempPath (), "ArrSentinels.dll");
@@ -1030,21 +1030,19 @@ public void FullPipeline_ArrayEntries_ReferencesSharedRankAnchors ()
Assert.Equal (3, model.MaxArrayRank);
EmitAndVerify (model, "ArrSentinels", (pe, reader) => {
- // Per-asm DLLs no longer define their own __ArrayMapRank{N}; they reference
- // the shared anchors in Mono.Android.
var typeDefNames = reader.TypeDefinitions
.Select (h => reader.GetString (reader.GetTypeDefinition (h).Name))
.ToHashSet (StringComparer.Ordinal);
- Assert.DoesNotContain ("__ArrayMapRank1", typeDefNames);
+ Assert.Contains ("__ArrayMapRank1", typeDefNames);
+ Assert.Contains ("__ArrayMapRank2", typeDefNames);
+ Assert.Contains ("__ArrayMapRank3", typeDefNames);
- var rankRefsToMonoAndroid = reader.TypeReferences
+ var rankTypeRefs = reader.TypeReferences
.Select (h => reader.GetTypeReference (h))
.Where (t => reader.GetString (t.Name).StartsWith ("__ArrayMapRank", StringComparison.Ordinal))
.Select (t => reader.GetString (t.Name))
.ToHashSet (StringComparer.Ordinal);
- Assert.Contains ("__ArrayMapRank1", rankRefsToMonoAndroid);
- Assert.Contains ("__ArrayMapRank2", rankRefsToMonoAndroid);
- Assert.Contains ("__ArrayMapRank3", rankRefsToMonoAndroid);
+ Assert.Empty (rankTypeRefs);
});
}
@@ -1057,6 +1055,13 @@ public void FullPipeline_NoArrayEntries_DoesNotReferenceRankAnchors ()
Assert.Equal (0, model.MaxArrayRank);
EmitAndVerify (model, "NoArrSentinels", (pe, reader) => {
+ var typeDefNames = reader.TypeDefinitions
+ .Select (h => reader.GetString (reader.GetTypeDefinition (h).Name))
+ .ToHashSet (StringComparer.Ordinal);
+ Assert.DoesNotContain ("__ArrayMapRank1", typeDefNames);
+ Assert.DoesNotContain ("__ArrayMapRank2", typeDefNames);
+ Assert.DoesNotContain ("__ArrayMapRank3", typeDefNames);
+
var typeRefNames = reader.TypeReferences
.Select (h => reader.GetString (reader.GetTypeReference (h).Name))
.ToHashSet (StringComparer.Ordinal);