Skip to content

Commit

Permalink
Optimise CallManagedFunctionAction type checks
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam Byass committed Oct 18, 2021
1 parent 8df2dca commit cdf15d0
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ public CallManagedFunctionAction(MethodAnalysis<Instruction> context, Instructio
if (!MethodUtils.CheckParameters(instruction, locatedMethod, context, locatedMethod.HasThis, out Arguments, InstanceBeingCalledOn?.Type, failOnLeftoverArgs: false))
AddComment("parameters do not match, but concrete method was resolved from a constant in a register.");

if (locatedMethod.HasThis && InstanceBeingCalledOn?.Type != null && !Utils.IsManagedTypeAnInstanceOfCppOne(LibCpp2ILUtils.WrapType(locatedMethod.Resolve().AsUnmanaged().DeclaringType!), InstanceBeingCalledOn.Type))
if (locatedMethod.HasThis && InstanceBeingCalledOn?.Type != null && !locatedMethod.DeclaringType.Resolve().IsAssignableFrom(InstanceBeingCalledOn.Type))
AddComment($"This is an instance method, but the type of the 'this' parameter is mismatched. Expecting {locatedMethod.Resolve()?.DeclaringType.Name}, actually {InstanceBeingCalledOn.Type.FullName}");
else if (locatedMethod.HasThis && InstanceBeingCalledOn?.Type != null)
{
//Matching type, but is it us or a base type?
IsCallToSuperclassMethod = !Utils.AreManagedAndCppTypesEqual(LibCpp2ILUtils.WrapType(locatedMethod.Resolve().AsUnmanaged().DeclaringType!), InstanceBeingCalledOn.Type);
IsCallToSuperclassMethod = locatedMethod.DeclaringType.Resolve() != InstanceBeingCalledOn.Type?.Resolve();
}

ManagedMethodBeingCalled = locatedMethod;
Expand Down Expand Up @@ -90,12 +90,12 @@ public CallManagedFunctionAction(MethodAnalysis<Instruction> context, Instructio
if (!MethodUtils.CheckParameters(instruction, possibleTarget, context, !possibleTarget.IsStatic, out Arguments, InstanceBeingCalledOn, failOnLeftoverArgs: false))
AddComment("parameters do not match, but there is only one function at this address.");

if (!possibleTarget.IsStatic && InstanceBeingCalledOn?.Type != null && !Utils.IsManagedTypeAnInstanceOfCppOne(LibCpp2ILUtils.WrapType(possibleTarget.DeclaringType!), InstanceBeingCalledOn.Type))
if (!possibleTarget.IsStatic && InstanceBeingCalledOn?.Type != null && !possibleTarget.DeclaringType!.AsManaged().IsAssignableFrom(InstanceBeingCalledOn.Type))
AddComment($"This is an instance method, but the type of the 'this' parameter is mismatched. Expecting {possibleTarget.DeclaringType.Name}, actually {InstanceBeingCalledOn.Type.FullName}");
else if (!possibleTarget.IsStatic && InstanceBeingCalledOn?.Type != null)
{
//Matching type, but is it us or a base type?
IsCallToSuperclassMethod = !Utils.AreManagedAndCppTypesEqual(LibCpp2ILUtils.WrapType(possibleTarget.DeclaringType!), InstanceBeingCalledOn.Type);
IsCallToSuperclassMethod = possibleTarget.DeclaringType.AsManaged() != InstanceBeingCalledOn.Type?.Resolve();
}
}
else
Expand All @@ -119,7 +119,7 @@ public CallManagedFunctionAction(MethodAnalysis<Instruction> context, Instructio
if (m.IsStatic) continue; //Only checking instance methods here.

//Check defining type matches instance, and check params.
if (Utils.AreManagedAndCppTypesEqual(LibCpp2ILUtils.WrapType(m.DeclaringType!), InstanceBeingCalledOn.Type))
if (m.DeclaringType!.AsManaged() == InstanceBeingCalledOn.Type.Resolve())
{
possibleTarget = m;

Expand All @@ -140,7 +140,7 @@ public CallManagedFunctionAction(MethodAnalysis<Instruction> context, Instructio
if (possibleTarget == null)
{
//Methods which are non-static and for which the declaring type is some form of supertype of the object we're calling on.
var baseClassMethods = listOfCallableMethods.Where(m => !m.IsStatic && Utils.IsManagedTypeAnInstanceOfCppOne(LibCpp2ILUtils.WrapType(m.DeclaringType!), InstanceBeingCalledOn.Type)).ToList();
var baseClassMethods = listOfCallableMethods.Where(m => !m.IsStatic && m.DeclaringType!.AsManaged().IsAssignableFrom(InstanceBeingCalledOn.Type)).ToList();

if (baseClassMethods.Count == 0 && toPushBackIfNeeded != null)
{
Expand Down
40 changes: 33 additions & 7 deletions Cpp2IL.Core/CecilExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;

namespace Cpp2IL.Core
{
internal static class CecilExtensions
{
internal static readonly ConcurrentDictionary<TypeDefinition, ConcurrentDictionary<TypeReference, bool>> AssignabilityCache = new();

/// <summary>
/// Is childTypeDef a subclass of parentTypeDef. Does not test interface inheritance
/// </summary>
Expand Down Expand Up @@ -65,12 +68,35 @@ public static bool DoesSpecificInterfaceImplementInterface(TypeReference iface0,
/// <param name="potentialSubclass"></param>
/// <returns></returns>
public static bool IsAssignableFrom(this TypeDefinition? instanceOrBaseClass, TypeReference? potentialSubclass)
=> instanceOrBaseClass != null && potentialSubclass != null && (instanceOrBaseClass == potentialSubclass
|| instanceOrBaseClass.MetadataToken == potentialSubclass.Resolve()?.MetadataToken
|| potentialSubclass.IsSubclassOf(instanceOrBaseClass)
|| instanceOrBaseClass.IsInterface && potentialSubclass.DoesAnySuperTypeImplementInterface(instanceOrBaseClass)
|| instanceOrBaseClass.IsEnumerableLikeAndSoIs(potentialSubclass)
);
{
//Do the quick checks first, they don't need to be cached.
if (instanceOrBaseClass is null || potentialSubclass is null)
return false;

if (instanceOrBaseClass == potentialSubclass)
return true;

if (instanceOrBaseClass.MetadataToken == potentialSubclass.Resolve()?.MetadataToken)
return true;

//Slow checks are cached
if (!AssignabilityCache.TryGetValue(instanceOrBaseClass, out var subclassCache))
{
subclassCache = new();
AssignabilityCache.TryAdd(instanceOrBaseClass, subclassCache);
}

if (subclassCache.TryGetValue(potentialSubclass, out var ret))
return ret;

ret = potentialSubclass.IsSubclassOf(instanceOrBaseClass)
|| instanceOrBaseClass.IsInterface && potentialSubclass.DoesAnySuperTypeImplementInterface(instanceOrBaseClass)
|| instanceOrBaseClass.IsEnumerableLikeAndSoIs(potentialSubclass);

subclassCache.TryAdd(potentialSubclass, ret);

return ret;
}

/// <summary>
/// Enumerate the current type, it's parent and all the way to the top type
Expand Down
26 changes: 22 additions & 4 deletions Cpp2IL.Core/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,16 @@ public static void MethodSignatureFullName(this IMethodSignature self, StringBui

return SharedState.UnmanagedToManagedMethods[unmanaged];
}


[return: NotNullIfNotNull("managed")]
public static Il2CppMethodDefinition? AsUnmanaged(this MethodDefinition? managed)
{
if (managed == null)
return null;

return SharedState.ManagedToUnmanagedMethods[managed];
}

[return: NotNullIfNotNull("managed")]
public static Il2CppFieldDefinition? AsUnmanaged(this FieldDefinition? managed)
{
Expand All @@ -133,7 +142,7 @@ public static void MethodSignatureFullName(this IMethodSignature self, StringBui

return SharedState.ManagedToUnmanagedFields[managed];
}

[return: NotNullIfNotNull("unmanaged")]
public static FieldDefinition? AsManaged(this Il2CppFieldDefinition? unmanaged)
{
Expand All @@ -144,12 +153,21 @@ public static void MethodSignatureFullName(this IMethodSignature self, StringBui
}

[return: NotNullIfNotNull("managed")]
public static Il2CppMethodDefinition? AsUnmanaged(this MethodDefinition? managed)
public static Il2CppTypeDefinition? AsUnmanaged(this TypeDefinition? managed)
{
if (managed == null)
return null;

return SharedState.ManagedToUnmanagedMethods[managed];
return SharedState.ManagedToUnmanagedTypes[managed];
}

[return: NotNullIfNotNull("unmanaged")]
public static TypeDefinition? AsManaged(this Il2CppTypeDefinition? unmanaged)
{
if (unmanaged == null)
return null;

return SharedState.UnmanagedToManagedTypes[unmanaged];
}

public static T? GetValueSafely<T>(this Collection<T> arr, int i) where T : class
Expand Down
19 changes: 2 additions & 17 deletions Cpp2IL.Core/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public static class Utils

private static Dictionary<string, TypeDefinition> primitiveTypeMappings = new Dictionary<string, TypeDefinition>();
private static readonly Dictionary<string, Tuple<TypeDefinition?, string[]>> _cachedTypeDefsByName = new Dictionary<string, Tuple<TypeDefinition?, string[]>>();
private static readonly Dictionary<(TypeDefinition, TypeReference), bool> _assignableCache = new Dictionary<(TypeDefinition, TypeReference), bool>();

private static readonly Dictionary<string, ulong> PrimitiveSizes = new Dictionary<string, ulong>(14)
{
Expand All @@ -66,7 +65,7 @@ internal static void Reset()
{
primitiveTypeMappings.Clear();
_cachedTypeDefsByName.Clear();
_assignableCache.Clear();
CecilExtensions.AssignabilityCache.Clear();
}

public static void BuildPrimitiveMappings()
Expand Down Expand Up @@ -109,28 +108,14 @@ public static bool IsManagedTypeAnInstanceOfCppOne(Il2CppTypeReflectionData cppT
{
var managedBaseType = SharedState.UnmanagedToManagedTypes[cppType.baseType!];

return CheckAssignability(managedBaseType, managedType);
return managedBaseType.IsAssignableFrom(managedType);
}

//todo generics etc.

return false;
}

private static bool CheckAssignability(TypeDefinition baseType, TypeReference potentialChild)
{
var key = (baseType, potentialChild);
lock (_assignableCache)
{
if (!_assignableCache.ContainsKey(key))
{
_assignableCache[key] = baseType.IsAssignableFrom(potentialChild);
}

return _assignableCache[key];
}
}

public static bool AreManagedAndCppTypesEqual(Il2CppTypeReflectionData cppType, TypeReference managedType)
{
if (!cppType.isType && !cppType.isArray && !cppType.isGenericType) return false;
Expand Down

0 comments on commit cdf15d0

Please sign in to comment.