Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data)
using var stream = new System.IO.MemoryStream ();
using var writer = new System.IO.BinaryWriter (stream, Encoding.UTF8);
foreach (var entry in data.Entries) {
writer.Write (entry.JniName);
writer.Write (entry.MapKey);
writer.Write (entry.ProxyTypeReference);
writer.Write (entry.TargetTypeReference ?? "");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;
Expand Down Expand Up @@ -40,6 +39,11 @@ sealed class TypeMapAssemblyData
/// </summary>
public List<AliasHolderData> AliasHolders { get; } = new ();

/// <summary>
/// Array proxy types to emit — one per JNI element name and rank.
/// </summary>
public List<ArrayProxyData> ArrayProxyTypes { get; } = new ();

/// <summary>
/// Maximum array rank for which the generator emits per-rank <c>__ArrayMapRank{N}</c>
/// sentinel TypeDefs and <c>TypeMap</c> entries. 0 disables.
Expand All @@ -62,9 +66,10 @@ sealed class TypeMapAssemblyData
sealed record TypeMapAttributeData
{
/// <summary>
/// JNI type name, e.g., "android/app/Activity".
/// Type map key, e.g., "android/app/Activity" for peer entries or
/// "Android.App.Activity, Mono.Android" for array proxy entries.
/// </summary>
public required string JniName { get; init; }
public required string MapKey { get; init; }

/// <summary>
/// Assembly-qualified proxy type reference string.
Expand All @@ -91,6 +96,30 @@ sealed record TypeMapAttributeData
public int? AnchorRank { get; init; }
}

/// <summary>
/// A generated array proxy type used by per-rank array TypeMap entries.
/// </summary>
sealed record ArrayProxyData
{
public required string TypeName { get; init; }

public string Namespace { get; init; } = "_TypeMap.ArrayProxies";

public required TypeRefData ElementType { get; init; }

public required int Rank { get; init; }

public PrimitiveArrayProxyData? Primitive { get; init; }
}

/// <summary>
/// Additional primitive array metadata for <see cref="ArrayProxyData"/>.
/// </summary>
sealed record PrimitiveArrayProxyData
{
public required TypeRefData ConcreteArrayType { get; init; }
}

/// <summary>
/// A proxy type to generate in the TypeMap assembly (subclass of JavaPeerProxy).
/// </summary>
Expand Down Expand Up @@ -390,7 +419,7 @@ sealed record ActivationCtorData

/// <summary>
/// One [assembly: TypeMapAssociation(typeof(Source), typeof(AliasProxy))] entry.
/// Links a managed type to the alias holder that owns the alias group.
/// Links a managed type to an alias holder, generated proxy, or generated array proxy.
/// </summary>
sealed record TypeMapAssociationData
{
Expand All @@ -403,6 +432,12 @@ sealed record TypeMapAssociationData
/// Assembly-qualified proxy type reference (the alias holder).
/// </summary>
public required string AliasProxyTypeReference { get; init; }

/// <summary>
/// 1-based array rank when this association should use a <c>__ArrayMapRank{value}</c>
/// sentinel as its <c>TGroup</c> instead of the default model anchor.
/// </summary>
public int? AnchorRank { get; init; }
}

/// <summary>
Expand Down
180 changes: 165 additions & 15 deletions src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ static class ModelBuilder
{
const string ProxyTypeSuffix = "_Proxy";

static readonly PrimitiveArrayProxyInfo [] PrimitiveArrayProxies = [
new ("Z", "Boolean", "System.Boolean", "Java.Interop.JavaBooleanArray"),
new ("B", "SByte", "System.SByte", "Java.Interop.JavaSByteArray"),
new ("C", "Char", "System.Char", "Java.Interop.JavaCharArray"),
new ("S", "Int16", "System.Int16", "Java.Interop.JavaInt16Array"),
new ("I", "Int32", "System.Int32", "Java.Interop.JavaInt32Array"),
new ("J", "Int64", "System.Int64", "Java.Interop.JavaInt64Array"),
new ("F", "Single", "System.Single", "Java.Interop.JavaSingleArray"),
new ("D", "Double", "System.Double", "Java.Interop.JavaDoubleArray"),
];

static readonly HashSet<string> EssentialRuntimeTypes = new (StringComparer.Ordinal) {
"java/lang/Object",
"java/lang/Class",
Expand Down Expand Up @@ -99,6 +110,10 @@ public static TypeMapAssemblyData Build (IReadOnlyList<JavaPeerInfo> peers, stri
}
}

if (maxArrayRank > 0 && string.Equals (assemblyName, "_Java.Interop.TypeMap", StringComparison.Ordinal)) {
EmitPrimitiveArrayEntries (model, maxArrayRank);
}

BuildNativeRegistrations (model);

// Compute IgnoresAccessChecksTo from cross-assembly references
Expand Down Expand Up @@ -190,7 +205,7 @@ static void EmitPeers (TypeMapAssemblyData model, string jniName,
bool aliasBaseUnconditional = EssentialRuntimeTypes.Contains (jniName)
|| peersForName.Any (IsUnconditionalEntry);
model.Entries.Add (new TypeMapAttributeData {
JniName = jniName,
MapKey = jniName,
ProxyTypeReference = holderRef,
TargetTypeReference = aliasBaseUnconditional ? null : holderRef,
});
Expand Down Expand Up @@ -253,13 +268,26 @@ static void AddIfCrossAssembly (SortedSet<string> set, string? asmName, string o
static string ManagedTypeNameToProxyTypeName (string managedTypeName)
{
var builder = new StringBuilder (managedTypeName.Length + ProxyTypeSuffix.Length);
AppendSafeManagedTypeName (builder, managedTypeName);
builder.Append (ProxyTypeSuffix);
return builder.ToString ();
}

static string ManagedTypeNameToArrayProxyTypeName (string managedTypeName, int rank)
{
var builder = new StringBuilder (managedTypeName.Length + 20);
AppendSafeManagedTypeName (builder, managedTypeName);
builder.Append ("_ArrayProxy");
builder.Append (rank);
return builder.ToString ();
}

static void AppendSafeManagedTypeName (StringBuilder builder, string managedTypeName)
{
for (int i = 0; i < managedTypeName.Length; i++) {
char c = managedTypeName [i];
builder.Append (c == '.' || c == '+' || c == '`' ? '_' : c);
}

builder.Append (ProxyTypeSuffix);
return builder.ToString ();
}

static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, HashSet<string> usedProxyNames, bool isAcw)
Expand Down Expand Up @@ -493,7 +521,7 @@ static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? pr
}

return new TypeMapAttributeData {
JniName = jniName,
MapKey = jniName,
ProxyTypeReference = proxyRef,
TargetTypeReference = targetRef,
};
Expand All @@ -502,17 +530,88 @@ static TypeMapAttributeData BuildEntry (JavaPeerInfo peer, JavaPeerProxyData? pr
static string AssemblyQualify (string typeName, string assemblyName)
=> $"{typeName}, {assemblyName}";

static string AddArrayRank (string typeReference, int rank)
{
if (rank == 0) {
return typeReference;
}

int assemblySeparator = typeReference.LastIndexOf (", ", StringComparison.Ordinal);
if (assemblySeparator < 0) {
throw new InvalidOperationException ($"Assembly-qualified type reference '{typeReference}' does not contain an assembly name.");
}

return typeReference.Substring (0, assemblySeparator) + Brackets (rank) + typeReference.Substring (assemblySeparator);
}

static string MakeGenericTypeReference (string openTypeName, string openTypeAssembly, string argumentTypeReference)
=> $"{openTypeName}[[{argumentTypeReference}]], {openTypeAssembly}";

static string MakeNestedJavaObjectArrayTypeReference (string elementTypeReference, int rank)
{
var result = elementTypeReference;
for (int i = 0; i < rank; i++) {
result = MakeGenericTypeReference ("Java.Interop.JavaObjectArray`1", "Java.Interop", result);
}
return result;
}

static IReadOnlyList<string> GetArrayTypeReferences (ArrayProxyData proxy)
{
var elementType = AssemblyQualify (proxy.ElementType.ManagedTypeName, proxy.ElementType.AssemblyName);
if (proxy.Primitive is null) {
var rankOneTypes = new [] {
MakeGenericTypeReference ("Java.Interop.JavaObjectArray`1", "Java.Interop", elementType),
MakeGenericTypeReference ("Java.Interop.JavaArray`1", "Java.Interop", elementType),
AddArrayRank (elementType, 1),
};
return ExpandRankOneTypes (rankOneTypes, proxy.Rank);
}

var rankOnePrimitiveTypes = new [] {
AddArrayRank (elementType, 1),
MakeGenericTypeReference ("Java.Interop.JavaArray`1", "Java.Interop", elementType),
MakeGenericTypeReference ("Java.Interop.JavaPrimitiveArray`1", "Java.Interop", elementType),
AssemblyQualify (proxy.Primitive.ConcreteArrayType.ManagedTypeName, proxy.Primitive.ConcreteArrayType.AssemblyName),
};
return ExpandRankOneTypes (rankOnePrimitiveTypes, proxy.Rank);
}

static IReadOnlyList<string> ExpandRankOneTypes (IReadOnlyList<string> rankOneTypes, int rank)
{
if (rank == 1) {
return rankOneTypes;
}

var result = new List<string> (rankOneTypes.Count * 2);
foreach (var type in rankOneTypes) {
result.Add (MakeNestedJavaObjectArrayTypeReference (type, rank - 1));
result.Add (AddArrayRank (type, rank - 1));
}
return result;
}

static void AddArrayProxyAssociations (TypeMapAssemblyData model, ArrayProxyData proxy, string proxyReference)
{
foreach (var typeReference in GetArrayTypeReferences (proxy)) {
model.Associations.Add (new TypeMapAssociationData {
SourceTypeReference = typeReference,
AliasProxyTypeReference = proxyReference,
AnchorRank = proxy.Rank,
});
}
}

static string GetArrayProxyMapKey (TypeRefData elementType)
=> AssemblyQualify (elementType.ManagedTypeName, elementType.AssemblyName);

/// <summary>
/// Emits per-rank array TypeMap entries for one peer, anchored to the per-assembly
/// <c>__ArrayMapRank{N}</c> sentinels. Keys are bare element JNI names (rank is encoded
/// by the sentinel anchor, not by JNI array prefixes). Skips open generics, primitive JNI
/// keyword keys (handled by the legacy primitive-array path), and alias groups.
/// <c>__ArrayMapRank{N}</c> sentinels. Keys are managed element type names (rank is encoded
/// by the sentinel anchor, not by JNI array prefixes). Skips open generics and alias groups.
/// </summary>
static void EmitArrayEntries (TypeMapAssemblyData model, string jniName, List<JavaPeerInfo> peersForName, int maxArrayRank)
{
if (jniName.Length == 1 && IsJniPrimitiveKeyword (jniName [0])) {
return;
}
if (peersForName.Count != 1) {
return;
}
Expand All @@ -524,15 +623,60 @@ static void EmitArrayEntries (TypeMapAssemblyData model, string jniName, List<Ja
if (peer.IsGenericDefinition) {
return;
}
if (jniName.Length == 1 && IsJniPrimitiveKeyword (jniName [0])) {
return;
}

for (int rank = 1; rank <= maxArrayRank; rank++) {
string arrayTypeRef = AssemblyQualify (peer.ManagedTypeName + Brackets (rank), peer.AssemblyName);
var proxy = new ArrayProxyData {
TypeName = ManagedTypeNameToArrayProxyTypeName (peer.ManagedTypeName, rank),
ElementType = new TypeRefData {
ManagedTypeName = peer.ManagedTypeName,
AssemblyName = peer.AssemblyName,
},
Rank = rank,
};
model.ArrayProxyTypes.Add (proxy);

var proxyReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", model.AssemblyName);
model.Entries.Add (new TypeMapAttributeData {
JniName = jniName,
ProxyTypeReference = arrayTypeRef,
TargetTypeReference = arrayTypeRef,
MapKey = GetArrayProxyMapKey (proxy.ElementType),
ProxyTypeReference = proxyReference,
TargetTypeReference = proxyReference,
AnchorRank = rank,
});
AddArrayProxyAssociations (model, proxy, proxyReference);
}
}

static void EmitPrimitiveArrayEntries (TypeMapAssemblyData model, int maxArrayRank)
{
foreach (var primitive in PrimitiveArrayProxies) {
for (int rank = 1; rank <= maxArrayRank; rank++) {
var proxy = new ArrayProxyData {
TypeName = $"Primitive_{primitive.Name}_ArrayProxy{rank}",
ElementType = new TypeRefData {
ManagedTypeName = primitive.ManagedTypeName,
AssemblyName = "System.Runtime",
},
Rank = rank,
Primitive = new PrimitiveArrayProxyData {
ConcreteArrayType = new TypeRefData {
ManagedTypeName = primitive.ConcreteArrayTypeName,
AssemblyName = "Java.Interop",
},
},
};
model.ArrayProxyTypes.Add (proxy);
var proxyReference = AssemblyQualify ($"{proxy.Namespace}.{proxy.TypeName}", model.AssemblyName);
model.Entries.Add (new TypeMapAttributeData {
MapKey = GetArrayProxyMapKey (proxy.ElementType),
ProxyTypeReference = proxyReference,
TargetTypeReference = proxyReference,
AnchorRank = rank,
});
AddArrayProxyAssociations (model, proxy, proxyReference);
}
}
}

Expand All @@ -555,4 +699,10 @@ static string BuildBrackets (int rank)
static bool IsJniPrimitiveKeyword (char c)
=> c == 'Z' || c == 'B' || c == 'C' || c == 'S' || c == 'I'
|| c == 'J' || c == 'F' || c == 'D' || c == 'V';

readonly record struct PrimitiveArrayProxyInfo (
string JniName,
string Name,
string ManagedTypeName,
string ConcreteArrayTypeName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,50 @@ public EntityHandle ResolveTypeRef (TypeRefData typeRef)
return result;
}

public void WriteTypeSignature (BlobBuilder blob, TypeRefData typeRef)
{
if (typeRef.ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY
WriteTypeSignature (blob, typeRef with {
ManagedTypeName = typeRef.ManagedTypeName.Substring (0, typeRef.ManagedTypeName.Length - 2),
});
return;
}

switch (typeRef.ManagedTypeName) {
case "System.Boolean": blob.WriteByte (0x02); return;
case "System.Char": blob.WriteByte (0x03); return;
case "System.SByte": blob.WriteByte (0x04); return;
case "System.Byte": blob.WriteByte (0x05); return;
case "System.Int16": blob.WriteByte (0x06); return;
case "System.UInt16": blob.WriteByte (0x07); return;
case "System.Int32": blob.WriteByte (0x08); return;
case "System.UInt32": blob.WriteByte (0x09); return;
case "System.Int64": blob.WriteByte (0x0A); return;
case "System.UInt64": blob.WriteByte (0x0B); return;
case "System.Single": blob.WriteByte (0x0C); return;
case "System.Double": blob.WriteByte (0x0D); return;
case "System.String": blob.WriteByte (0x0E); return;
case "System.Object": blob.WriteByte (0x1C); return;
case "System.IntPtr": blob.WriteByte (0x18); return;
}

if (typeRef.ManagedTypeName.StartsWith ("!!", StringComparison.Ordinal)) {
blob.WriteByte (0x1E); // ELEMENT_TYPE_MVAR
blob.WriteCompressedInteger (int.Parse (typeRef.ManagedTypeName.Substring (2), System.Globalization.CultureInfo.InvariantCulture));
return;
}
if (typeRef.ManagedTypeName.StartsWith ("!", StringComparison.Ordinal)) {
blob.WriteByte (0x13); // ELEMENT_TYPE_VAR
blob.WriteCompressedInteger (int.Parse (typeRef.ManagedTypeName.Substring (1), System.Globalization.CultureInfo.InvariantCulture));
return;
}

var typeHandle = ResolveTypeRef (typeRef);
blob.WriteByte (typeRef.IsEnum ? (byte) 0x11 : (byte) 0x12); // VALUETYPE or CLASS
blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeHandle));
}

TypeReferenceHandle MakeTypeRefForManagedName (EntityHandle scope, string managedTypeName)
{
int plusIndex = managedTypeName.IndexOf ('+');
Expand Down
Loading
Loading