From cdf15d0d9c209567f6a4e038343bb6bc52c690b9 Mon Sep 17 00:00:00 2001 From: Sam Byass Date: Mon, 18 Oct 2021 11:43:54 +0100 Subject: [PATCH] Optimise CallManagedFunctionAction type checks --- .../Important/CallManagedFunctionAction.cs | 12 +++--- Cpp2IL.Core/CecilExtensions.cs | 40 +++++++++++++++---- Cpp2IL.Core/Extensions.cs | 26 ++++++++++-- Cpp2IL.Core/Utils.cs | 19 +-------- 4 files changed, 63 insertions(+), 34 deletions(-) diff --git a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionAction.cs b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionAction.cs index a826afc2..e4753a72 100644 --- a/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionAction.cs +++ b/Cpp2IL.Core/Analysis/Actions/x86/Important/CallManagedFunctionAction.cs @@ -52,12 +52,12 @@ public CallManagedFunctionAction(MethodAnalysis 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; @@ -90,12 +90,12 @@ public CallManagedFunctionAction(MethodAnalysis 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 @@ -119,7 +119,7 @@ public CallManagedFunctionAction(MethodAnalysis 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; @@ -140,7 +140,7 @@ public CallManagedFunctionAction(MethodAnalysis 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) { diff --git a/Cpp2IL.Core/CecilExtensions.cs b/Cpp2IL.Core/CecilExtensions.cs index ab92b8d3..b5fcb325 100644 --- a/Cpp2IL.Core/CecilExtensions.cs +++ b/Cpp2IL.Core/CecilExtensions.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using Mono.Cecil; @@ -6,6 +7,8 @@ namespace Cpp2IL.Core { internal static class CecilExtensions { + internal static readonly ConcurrentDictionary> AssignabilityCache = new(); + /// /// Is childTypeDef a subclass of parentTypeDef. Does not test interface inheritance /// @@ -65,12 +68,35 @@ public static bool DoesSpecificInterfaceImplementInterface(TypeReference iface0, /// /// 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; + } /// /// Enumerate the current type, it's parent and all the way to the top type diff --git a/Cpp2IL.Core/Extensions.cs b/Cpp2IL.Core/Extensions.cs index 196e0173..659ce23d 100644 --- a/Cpp2IL.Core/Extensions.cs +++ b/Cpp2IL.Core/Extensions.cs @@ -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) { @@ -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) { @@ -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(this Collection arr, int i) where T : class diff --git a/Cpp2IL.Core/Utils.cs b/Cpp2IL.Core/Utils.cs index 699f9a8c..ac941c4e 100644 --- a/Cpp2IL.Core/Utils.cs +++ b/Cpp2IL.Core/Utils.cs @@ -42,7 +42,6 @@ public static class Utils private static Dictionary primitiveTypeMappings = new Dictionary(); private static readonly Dictionary> _cachedTypeDefsByName = new Dictionary>(); - private static readonly Dictionary<(TypeDefinition, TypeReference), bool> _assignableCache = new Dictionary<(TypeDefinition, TypeReference), bool>(); private static readonly Dictionary PrimitiveSizes = new Dictionary(14) { @@ -66,7 +65,7 @@ internal static void Reset() { primitiveTypeMappings.Clear(); _cachedTypeDefsByName.Clear(); - _assignableCache.Clear(); + CecilExtensions.AssignabilityCache.Clear(); } public static void BuildPrimitiveMappings() @@ -109,7 +108,7 @@ public static bool IsManagedTypeAnInstanceOfCppOne(Il2CppTypeReflectionData cppT { var managedBaseType = SharedState.UnmanagedToManagedTypes[cppType.baseType!]; - return CheckAssignability(managedBaseType, managedType); + return managedBaseType.IsAssignableFrom(managedType); } //todo generics etc. @@ -117,20 +116,6 @@ public static bool IsManagedTypeAnInstanceOfCppOne(Il2CppTypeReflectionData cppT 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;