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
5 changes: 5 additions & 0 deletions src/Microsoft.Android.Sdk.TrimmableTypeMap/AssemblyInput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Reflection.PortableExecutable;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

public readonly record struct AssemblyInput (string Name, string Path, PEReader Reader);
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ internal void LoadManagedArgument (TrackedInstructionEncoder encoder, TypeRefDat
{
string managedTypeName = managedType.ManagedTypeName;

ThrowIfUnsupportedManagedType (managedTypeName);
ThrowIfUnsupportedManagedType (managedType);

if (TryEmitExportParameterArgument (encoder, exportKind, argumentIndex)) {
return;
Expand Down Expand Up @@ -410,8 +410,10 @@ void ConvertManagedReturnValue (TrackedInstructionEncoder encoder, TypeRefData m
encoder.Call (_context.JniEnvToLocalJniHandleRef, parameterCount: 1, returnsValue: true);
}

void ThrowIfUnsupportedManagedType (string managedTypeName)
void ThrowIfUnsupportedManagedType (TypeRefData managedType)
{
string managedTypeName = managedType.ManagedTypeName;

if (managedTypeName.EndsWith ("&", StringComparison.Ordinal) || managedTypeName.EndsWith ("*", StringComparison.Ordinal)) {
throw new NotSupportedException ($"[Export] methods with by-ref or pointer signature types are not supported: '{managedTypeName}'.");
}
Expand All @@ -421,8 +423,8 @@ void ThrowIfUnsupportedManagedType (string managedTypeName)
nonArrayTypeName = nonArrayTypeName.Substring (0, nonArrayTypeName.Length - 2);
}

if (nonArrayTypeName.StartsWith ("!", StringComparison.Ordinal) || nonArrayTypeName.IndexOf ('<') >= 0) {
throw new NotSupportedException ($"[Export] methods with generic signature types are not supported: '{managedTypeName}'.");
if (nonArrayTypeName.StartsWith ("!", StringComparison.Ordinal) || managedType.GenericArguments.Count > 0 || nonArrayTypeName.IndexOf ('<') >= 0) {
throw new NotSupportedException ($"[Export] methods with generic signature types are not supported: '{managedType.DisplayName}'.");
}
}

Expand Down Expand Up @@ -571,7 +573,7 @@ void EncodeManagedType (SignatureTypeEncoder encoder, TypeRefData managedType)
{
string managedTypeName = managedType.ManagedTypeName;

ThrowIfUnsupportedManagedType (managedTypeName);
ThrowIfUnsupportedManagedType (managedType);
if (managedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
EncodeManagedType (encoder.SZArray (), managedType with {
ManagedTypeName = managedTypeName.Substring (0, managedTypeName.Length - 2),
Expand All @@ -598,7 +600,7 @@ void EncodeManagedType (SignatureTypeEncoder encoder, TypeRefData managedType)
}

var typeHandle = ResolveManagedTypeHandle (managedType);
encoder.Type (typeHandle, isValueType: managedType.IsEnum);
encoder.Type (typeHandle, isValueType: managedType.EncodeAsValueType);
}

void AddUnmanagedCallersOnlyAttribute (MethodDefinitionHandle handle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,24 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data)
}
foreach (var proxy in data.ProxyTypes) {
writer.Write (proxy.TypeName);
writer.Write (proxy.TargetType.ManagedTypeName);
writer.Write (proxy.TargetType.AssemblyName);
writer.WriteTypeRef (proxy.TargetType);
writer.Write ((byte)(proxy.ActivationCtor?.Style ?? 0));
if (proxy.ActivationCtor is not null) {
writer.WriteTypeRef (proxy.ActivationCtor.DeclaringType);
}
writer.Write ((byte)(proxy.InvokerActivationCtorStyle ?? 0));
writer.Write (proxy.UcoMethods.Count);
foreach (var method in proxy.UcoMethods) {
writer.WriteUcoMethod (method);
}
writer.Write (proxy.UcoConstructors.Count);
foreach (var constructor in proxy.UcoConstructors) {
writer.WriteUcoConstructor (constructor);
}
writer.Write (proxy.NativeRegistrations.Count);
foreach (var registration in proxy.NativeRegistrations) {
writer.WriteNativeRegistration (registration);
}
}
foreach (var assoc in data.Associations) {
writer.Write (assoc.SourceTypeReference);
Expand All @@ -50,4 +64,68 @@ public static byte [] ComputeContentFingerprint (TypeMapAssemblyData data)
writer.Flush ();
return sha.ComputeHash (stream.ToArray ());
}

static void WriteTypeRef (this System.IO.BinaryWriter writer, TypeRefData type)
{
writer.Write (type.ManagedTypeName);
writer.Write (type.AssemblyName);
writer.Write (type.IsValueType ? (byte) 1 : (byte) 0);
writer.Write (type.IsEnum ? (byte) 1 : (byte) 0);
writer.Write (type.GenericArguments.Count);
foreach (var argument in type.GenericArguments) {
writer.WriteTypeRef (argument);
}
}

static void WriteUcoMethod (this System.IO.BinaryWriter writer, UcoMethodData method)
{
writer.Write (method.WrapperName);
writer.Write (method.CallbackMethodName);
writer.WriteTypeRef (method.CallbackType);
writer.Write (method.JniSignature);
writer.WriteExportMethodDispatch (method.ExportMethodDispatch);
}

static void WriteExportMethodDispatch (this System.IO.BinaryWriter writer, ExportMethodDispatchData? dispatch)
{
writer.Write (dispatch is not null);
if (dispatch is null) {
return;
}

writer.Write (dispatch.ManagedMethodName);
writer.Write (dispatch.ParameterTypes.Count);
foreach (var parameterType in dispatch.ParameterTypes) {
writer.WriteTypeRef (parameterType);
}
writer.Write (dispatch.ParameterKinds.Count);
foreach (var parameterKind in dispatch.ParameterKinds) {
writer.Write ((int) parameterKind);
}
writer.WriteTypeRef (dispatch.ReturnType);
writer.Write ((int) dispatch.ReturnKind);
writer.Write (dispatch.IsStatic);
}

static void WriteUcoConstructor (this System.IO.BinaryWriter writer, UcoConstructorData constructor)
{
writer.Write (constructor.WrapperName);
writer.WriteTypeRef (constructor.TargetType);
writer.Write (constructor.JniSignature);
writer.Write (constructor.HasMatchingManagedCtor);
writer.Write (constructor.ManagedParameterTypes.Count);
foreach (var parameterType in constructor.ManagedParameterTypes) {
writer.WriteTypeRef (parameterType);
}
}

static void WriteNativeRegistration (this System.IO.BinaryWriter writer, NativeRegistrationData registration)
{
writer.Write (registration.JniMethodName);
writer.Write (registration.JniSignature);
writer.Write (registration.WrapperMethodName);
writer.Write (registration.WrapperTarget.TypeNamespace);
writer.Write (registration.WrapperTarget.TypeName);
writer.Write (registration.WrapperTarget.MethodName);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

Expand Down Expand Up @@ -186,11 +187,75 @@ sealed record TypeRefData
public required string AssemblyName { get; init; }

/// <summary>
/// True if this type — or, for array types, the element type — is an enum.
/// Generic arguments for a constructed generic type. Empty for non-generic
/// types and open generic definitions.
/// </summary>
public IReadOnlyList<TypeRefData> GenericArguments { get; init; } = [];
Comment thread
simonrozsival marked this conversation as resolved.

/// <summary>
/// True if this type — or, for array types, the element type — is a value type.
/// Used by the IL emitter to encode the type as <c>ELEMENT_TYPE_VALUETYPE</c>
/// rather than <c>ELEMENT_TYPE_CLASS</c> in member references and signatures.
/// </summary>
public bool IsValueType { get; init; }

/// <summary>
/// True if this type — or, for array types, the element type — is an enum.
/// Used by JNI signature generation to map enum values to their underlying
/// primitive ABI type.
/// </summary>
public bool IsEnum { get; init; }

public bool EncodeAsValueType => IsValueType || IsEnum;

public string DisplayName {
get {
if (ManagedTypeName.EndsWith ("[]", StringComparison.Ordinal)) {
return $"{(this with { ManagedTypeName = ManagedTypeName.Substring (0, ManagedTypeName.Length - 2) }).DisplayName}[]";
}
return GenericArguments.Count == 0
? ManagedTypeName
: $"{ManagedTypeName}<{string.Join (",", GenericArguments.Select (t => t.DisplayName))}>";
}
}

public bool Equals (TypeRefData? other)
{
if (ReferenceEquals (this, other)) {
return true;
}
if (other is null) {
return false;
}
if (!string.Equals (ManagedTypeName, other.ManagedTypeName, StringComparison.Ordinal) ||
!string.Equals (AssemblyName, other.AssemblyName, StringComparison.Ordinal) ||
IsValueType != other.IsValueType ||
IsEnum != other.IsEnum ||
GenericArguments.Count != other.GenericArguments.Count) {
return false;
}
for (int i = 0; i < GenericArguments.Count; i++) {
if (!GenericArguments [i].Equals (other.GenericArguments [i])) {
return false;
}
}
return true;
}

public override int GetHashCode ()
{
unchecked {
int hash = 17;
hash = hash * 31 + StringComparer.Ordinal.GetHashCode (ManagedTypeName);
hash = hash * 31 + StringComparer.Ordinal.GetHashCode (AssemblyName);
hash = hash * 31 + IsValueType.GetHashCode ();
hash = hash * 31 + IsEnum.GetHashCode ();
foreach (var argument in GenericArguments) {
hash = hash * 31 + argument.GetHashCode ();
}
return hash;
}
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,7 @@ static JavaPeerProxyData BuildProxyType (JavaPeerInfo peer, string jniName, Hash
if (peer.ActivationCtor != null) {
bool isOnLeaf = string.Equals (peer.ActivationCtor.DeclaringTypeName, peer.ManagedTypeName, StringComparison.Ordinal);
proxy.ActivationCtor = new ActivationCtorData {
DeclaringType = new TypeRefData {
ManagedTypeName = peer.ActivationCtor.DeclaringTypeName,
AssemblyName = peer.ActivationCtor.DeclaringAssemblyName,
},
DeclaringType = peer.ActivationCtor.DeclaringType,
IsOnLeafType = isOnLeaf,
Style = peer.ActivationCtor.Style,
};
Expand All @@ -333,7 +330,7 @@ static void BuildUcoMethods (JavaPeerInfo peer, JavaPeerProxyData proxy)
proxy.UcoMethods.Add (new UcoMethodData {
WrapperName = $"n_{mm.JniName}_uco_{ucoIndex}",
CallbackMethodName = mm.NativeCallbackName,
CallbackType = new TypeRefData {
CallbackType = mm.DeclaringType ?? new TypeRefData {
ManagedTypeName = !mm.DeclaringTypeName.IsNullOrEmpty () ? mm.DeclaringTypeName : peer.ManagedTypeName,
AssemblyName = !mm.DeclaringAssemblyName.IsNullOrEmpty () ? mm.DeclaringAssemblyName : peer.AssemblyName,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
Expand All @@ -23,7 +24,7 @@ sealed class PEAssemblyBuilder
static readonly byte [] MonoAndroidPublicKeyToken = { 0x84, 0xe0, 0x4f, 0xf9, 0xcf, 0xb7, 0x90, 0x65 };

readonly Dictionary<string, AssemblyReferenceHandle> _asmRefCache = new (StringComparer.OrdinalIgnoreCase);
readonly Dictionary<(string Assembly, string Type), EntityHandle> _typeRefCache = new ();
readonly Dictionary<string, EntityHandle> _typeRefCache = new ();

// Reusable scratch BlobBuilders — avoids allocating a new one per method body / attribute / member ref.
// Each is Clear()'d before use. Safe because all emission is single-threaded and non-reentrant.
Expand Down Expand Up @@ -148,16 +149,95 @@ public MemberReferenceHandle AddMemberRef (EntityHandle parent, string name, Act
/// </summary>
public EntityHandle ResolveTypeRef (TypeRefData typeRef)
{
var cacheKey = (typeRef.AssemblyName, typeRef.ManagedTypeName);
var cacheKey = GetTypeRefCacheKey (typeRef);
if (_typeRefCache.TryGetValue (cacheKey, out var cached)) {
return cached;
}
var asmRef = FindOrAddAssemblyRef (typeRef.AssemblyName);
var result = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName);
EntityHandle result;
if (typeRef.GenericArguments.Count > 0) {
var openType = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName);
var blob = new BlobBuilder (64);
WriteGenericInstantiationSignature (blob, openType, typeRef);
result = Metadata.AddTypeSpecification (Metadata.GetOrAddBlob (blob));
} else {
result = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName);
}
_typeRefCache [cacheKey] = result;
return result;
}

static string GetTypeRefCacheKey (TypeRefData typeRef)
{
var typeKind = typeRef.EncodeAsValueType ? "valuetype" : "class";
if (typeRef.GenericArguments.Count == 0) {
return $"{typeKind}:{typeRef.AssemblyName}:{typeRef.ManagedTypeName}";
}
return $"{typeKind}:{typeRef.AssemblyName}:{typeRef.ManagedTypeName}<{string.Join (",", typeRef.GenericArguments.Select (GetTypeRefCacheKey))}>";
}

void WriteGenericInstantiationSignature (BlobBuilder blob, EntityHandle openType, TypeRefData typeRef)
{
blob.WriteByte (0x15); // ELEMENT_TYPE_GENERICINST
blob.WriteByte (typeRef.EncodeAsValueType ? (byte) 0x11 : (byte) 0x12); // VALUETYPE or CLASS
blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (openType));
blob.WriteCompressedInteger (typeRef.GenericArguments.Count);
foreach (var argument in typeRef.GenericArguments) {
WriteTypeSignature (blob, argument);
Comment thread
simonrozsival marked this conversation as resolved.
}
}

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.GenericArguments.Count > 0) {
var asmRef = FindOrAddAssemblyRef (typeRef.AssemblyName);
var openType = MakeTypeRefForManagedName (asmRef, typeRef.ManagedTypeName);
WriteGenericInstantiationSignature (blob, openType, typeRef);
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.EncodeAsValueType ? (byte) 0x11 : (byte) 0x12); // VALUETYPE or CLASS
blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeHandle));
}

TypeReferenceHandle MakeTypeRefForManagedName (EntityHandle scope, string managedTypeName)
{
int plusIndex = managedTypeName.IndexOf ('+');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1059,7 +1059,7 @@ MemberReferenceHandle AddManagedCtorRef (EntityHandle declaringTypeRef, IReadOnl
blob.WriteCompressedInteger (parameterTypes.Count);
blob.WriteByte (0x01); // ELEMENT_TYPE_VOID
foreach (var parameterType in parameterTypes) {
WriteManagedTypeSignature (blob, parameterType.ManagedTypeName, parameterType.AssemblyName);
_pe.WriteTypeSignature (blob, parameterType);
}
return _pe.Metadata.AddMemberReference (declaringTypeRef, _pe.Metadata.GetOrAddString (".ctor"), _pe.Metadata.GetOrAddBlob (blob));
}
Expand Down
Loading
Loading