Skip to content

Commit

Permalink
implemented MethodInfoCompileTimeWeaver to speed up MethodBase.GetCur…
Browse files Browse the repository at this point in the history
…rentMethod() performance #85
  • Loading branch information
dd committed Oct 18, 2021
1 parent 9b02f57 commit cd4bdcd
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 26 deletions.
2 changes: 2 additions & 0 deletions src/MethodBoundaryAspect.Fody.UnitTests.Shared/PeVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ private static string GetPeVerifyPath()
var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
var possiblePeVerifyPaths = new[]
{
$@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\peverify.exe",
$@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.2 Tools\peverify.exe",
$@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.1 Tools\peverify.exe",
$@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7 Tools\peverify.exe",
$@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\peverify.exe",
$@"{folderPath}\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\peverify.exe",
$@"{folderPath}\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\peverify.exe"
};

Expand Down
30 changes: 21 additions & 9 deletions src/MethodBoundaryAspect.Fody/AsyncMethodWeaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,26 @@ namespace MethodBoundaryAspect.Fody
{
public class AsyncMethodWeaver : MethodWeaver
{
MethodDefinition _moveNext;
Instruction _setupPointer;
FieldReference _executionArgsField;
VariableDefinition _stateMachineLocal;
TypeReference _stateMachine;

public AsyncMethodWeaver(ModuleDefinition module, MethodDefinition method, MethodDefinition moveNext, IList<AspectData> aspects) :
base(module, method, aspects)
private readonly MethodDefinition _moveNext;
private readonly MethodInfoCompileTimeWeaver _methodInfoCompileTimeWeaver;

private readonly VariableDefinition _stateMachineLocal;
private readonly TypeReference _stateMachine;

private Instruction _setupPointer;
private FieldReference _executionArgsField;

public AsyncMethodWeaver(
ModuleDefinition module,
MethodDefinition method,
MethodDefinition moveNext,
IList<AspectData> aspects,
MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver) :
base(module, method, aspects, methodInfoCompileTimeWeaver)
{
_moveNext = moveNext;
_methodInfoCompileTimeWeaver = methodInfoCompileTimeWeaver;

var instructions = _ilProcessor.Body.Instructions;
if (instructions.Count < 2)
throw new InvalidOperationException($"Async state machine on {method.FullName} not in expected configuration.");
Expand Down Expand Up @@ -99,7 +109,9 @@ protected override void WeaveMethodExecutionArgs(NamedInstructionBlockChain argu
{
var executionArgs = _creator.CreateMethodExecutionArgsInstance(
arguments,
_aspects[0].Info.AspectAttribute.AttributeType);
_aspects[0].Info.AspectAttribute.AttributeType,
_method,
_methodInfoCompileTimeWeaver);

_executionArgsField = _module.ImportReference(_stateMachine.AddPublicInstanceField(executionArgs.Variable.VariableType));
executionArgs.Add(new InstructionBlock("", Instruction.Create(OpCodes.Ldloc, executionArgs.Variable)));
Expand Down
24 changes: 19 additions & 5 deletions src/MethodBoundaryAspect.Fody/InstructionBlockChainCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ public NamedInstructionBlockChain CreateThisVariable(TypeReference typeReference

public NamedInstructionBlockChain CreateMethodExecutionArgsInstance(
NamedInstructionBlockChain argumentsArrayChain,
TypeReference anyAspectTypeDefinition)
TypeReference anyAspectTypeDefinition,
MethodDefinition method,
MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver)
{
// instance value
var createThisVariableBlock = CreateThisVariable();
Expand Down Expand Up @@ -140,15 +142,27 @@ public NamedInstructionBlockChain CreateMethodExecutionArgsInstance(

var methodBaseTypeRef = _referenceFinder.GetTypeReference(typeof (MethodBase));
var methodBaseVariable = _creator.CreateVariable(methodBaseTypeRef);
var methodBaseGetCurrentMethod = _referenceFinder.GetMethodReference(methodBaseTypeRef,
md => md.Name == "GetCurrentMethod");
var callGetCurrentMethodBlock = _creator.CallStaticMethod(methodBaseGetCurrentMethod, new VariablePersistable(methodBaseVariable));
InstructionBlock callGetCurrentMethodBlock;
var variablePersistable = new VariablePersistable(methodBaseVariable);
if (methodInfoCompileTimeWeaver?.IsEnabled != true)
{
// fallback: slow GetCurrentMethod
var methodBaseGetCurrentMethod = _referenceFinder.GetMethodReference(methodBaseTypeRef,
md => md.Name == "GetCurrentMethod");
callGetCurrentMethodBlock = _creator.CallStaticMethod(methodBaseGetCurrentMethod, variablePersistable);
}
else
{
// fast: precompiled method info token
methodInfoCompileTimeWeaver.AddMethod(method);
callGetCurrentMethodBlock = methodInfoCompileTimeWeaver.PushMethodInfoOnStack(method, variablePersistable);
}

var methodExecutionArgsSetMethodBaseMethodRef =
_referenceFinder.GetMethodReference(methodExecutionArgsTypeRef, md => md.Name == "set_Method");
var callSetMethodBlock = _creator.CallVoidInstanceMethod(methodExecutionArgsSetMethodBaseMethodRef,
new VariablePersistable(methodExecutionArgsVariable),
new VariablePersistable(methodBaseVariable));
variablePersistable);

var newMethodExecutionArgsBlockChain = new NamedInstructionBlockChain(methodExecutionArgsVariable,
methodExecutionArgsTypeRef);
Expand Down
148 changes: 148 additions & 0 deletions src/MethodBoundaryAspect.Fody/MethodInfoCompileTimeWeaver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using TypeAttributes = Mono.Cecil.TypeAttributes;

namespace MethodBoundaryAspect.Fody
{
public class MethodInfoCompileTimeWeaver
{
private readonly ModuleDefinition _mainModule;
private TypeDefinition _methodInfosClass;

private readonly Dictionary<MethodDefinition, FieldDefinition> _fieldsCache
= new Dictionary<MethodDefinition, FieldDefinition>();

private readonly string _cacheNamespace = "OnMethodBoundaryAspectCompile";
private readonly string _cacheClass = "MethodInfos";

public MethodInfoCompileTimeWeaver(ModuleDefinition module)
{
_mainModule = module;
}

public bool IsEnabled { get; set; }

public void EnsureInit()
{
if (_methodInfosClass != null)
return;

var classAttributes = TypeAttributes.Class
| TypeAttributes.Public
| TypeAttributes.Abstract // abstract + sealed => static
| TypeAttributes.Sealed;
_methodInfosClass = new TypeDefinition(_cacheNamespace, _cacheClass, classAttributes)
{
BaseType = GetTypeReference(typeof(object))
};
_mainModule.Types.Add(_methodInfosClass);
}

public void AddMethod(MethodDefinition method)
{
EnsureInit();

var typeReferenceMethodBase = GetTypeReference(typeof(MethodBase));
_mainModule.ImportReference(typeReferenceMethodBase);

var identifier = CreateIdentifier(method);
var fieldAttributes = FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly;
var fd = new FieldDefinition($"_methodInfo_{identifier}", fieldAttributes, typeReferenceMethodBase);
_methodInfosClass.Fields.Add(fd);

_fieldsCache.Add(method, fd);
}

public InstructionBlock PushMethodInfoOnStack(MethodDefinition method, VariablePersistable variablePersistable)
{
var fieldDefinition = _fieldsCache[method];
var instruction = Instruction.Create(OpCodes.Ldsfld, fieldDefinition);
var store = variablePersistable.Store(
new InstructionBlock("", instruction),
variablePersistable.PersistedType);
return new InstructionBlock($"Load method info for '{method.Name}'", store.Instructions);
}

public void Finish()
{
if (_fieldsCache.Any())
CreateStaticCtor();
}

private string CreateIdentifier(MemberReference method)
{
var bytes = Encoding.UTF8.GetBytes(method.FullName);
var sha256 = new SHA256Managed();
var hash = sha256.ComputeHash(bytes);
return BitConverter.ToString(hash).Replace("-", string.Empty);
}

private void CreateStaticCtor()
{
var typeReferenceVoid = GetTypeReference(typeof(void));
var staticConstructorAttributes =
MethodAttributes.Private |
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
MethodAttributes.Static;
var cctor = new MethodDefinition(".cctor", staticConstructorAttributes, typeReferenceVoid);

// taken from https://gist.github.com/jbevain/390902
var getMethodFromHandle = ImportGetMethodFromHandle();
var cctorInstructions = new List<Instruction>();
foreach (var entry in _fieldsCache)
{
cctorInstructions.Add(Instruction.Create(OpCodes.Ldtoken, entry.Key));
cctorInstructions.Add(Instruction.Create(OpCodes.Call, getMethodFromHandle));
cctorInstructions.Add(Instruction.Create(OpCodes.Stsfld, entry.Value));
}

cctorInstructions.Add(Instruction.Create(OpCodes.Ret));

foreach (var methodInstruction in cctorInstructions)
cctor.Body.Instructions.Add(methodInstruction);
cctor.Body.Optimize();

_methodInfosClass.Methods.Add(cctor);
}

private TypeReference GetTypeReference(Type type)
{
return GetTypeReference(type.FullName);
}

private TypeReference GetTypeReference(string name)
{
switch (name)
{
case "System.Object":
return _mainModule.TypeSystem.Object;
case "System.Void":
return _mainModule.TypeSystem.Void;
default:
{
var splitted = name.Split('.');
var namespaceName = string.Join(".", splitted.Take(splitted.Length - 1));
var typeName = splitted.Last();
return new TypeReference(namespaceName, typeName, _mainModule, _mainModule.TypeSystem.CoreLibrary);
}
}
}

private MethodReference ImportGetMethodFromHandle()
{
return _mainModule.ImportReference(typeof(MethodBase)
.GetMethod("GetMethodFromHandle", new[] {typeof(RuntimeMethodHandle)}));
}
}
}
12 changes: 10 additions & 2 deletions src/MethodBoundaryAspect.Fody/MethodWeaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,25 @@ public class MethodWeaver
protected readonly InstructionBlockChainCreator _creator;
protected readonly ILProcessor _ilProcessor;
protected readonly IList<AspectData> _aspects;
private readonly MethodInfoCompileTimeWeaver _methodInfoCompileTimeWeaver;

protected bool HasMultipleAspects => _aspects.Count > 1;
protected IPersistable ExecutionArgs { get; set; }

public int WeaveCounter { get; private set; }

public MethodWeaver(ModuleDefinition module, MethodDefinition method, IList<AspectData> aspects)
public MethodWeaver(
ModuleDefinition module,
MethodDefinition method,
IList<AspectData> aspects,
MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver)
{
_module = module;
_method = method;
_creator = new InstructionBlockChainCreator(method, module);
_ilProcessor = _method.Body.GetILProcessor();
_aspects = aspects;
_methodInfoCompileTimeWeaver = methodInfoCompileTimeWeaver;
}

public void Weave()
Expand Down Expand Up @@ -192,7 +198,9 @@ protected virtual void WeaveMethodExecutionArgs(NamedInstructionBlockChain argum
{
var executionArgs = _creator.CreateMethodExecutionArgsInstance(
arguments,
_aspects[0].Info.AspectAttribute.AttributeType);
_aspects[0].Info.AspectAttribute.AttributeType,
_method,
_methodInfoCompileTimeWeaver);
AddToSetup(executionArgs);
ExecutionArgs = executionArgs;
}
Expand Down
16 changes: 12 additions & 4 deletions src/MethodBoundaryAspect.Fody/MethodWeaverFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ namespace MethodBoundaryAspect.Fody
{
public static class MethodWeaverFactory
{
public static MethodWeaver MakeWeaver(ModuleDefinition module, MethodDefinition method, IEnumerable<AspectInfo> aspects)
public static MethodWeaver MakeWeaver(ModuleDefinition module,
MethodDefinition method,
IEnumerable<AspectInfo> aspects,
MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver)
{
var filteredAspects = from a in aspects
let methods = GetUsedAspectMethods(a.AspectAttribute.AttributeType)
Expand All @@ -19,12 +22,17 @@ public static MethodWeaver MakeWeaver(ModuleDefinition module, MethodDefinition
if (asyncAttribute == null)
{
var aspectList = filteredAspects.Select(a => new AspectData(a.Aspect, a.Methods, method, module)).ToList();
return new MethodWeaver(module, method, aspectList);
return new MethodWeaver(module, method, aspectList, methodInfoCompileTimeWeaver);
}

var moveNextMethod = ((TypeDefinition)asyncAttribute.ConstructorArguments[0].Value).Methods.First(m => m.Name == "MoveNext");
return new AsyncMethodWeaver(module, method, moveNextMethod,
filteredAspects.Select(a => new AspectDataOnAsyncMethod(moveNextMethod, a.Aspect, a.Methods, method, module)).ToList<AspectData>());
var aspectDatas = filteredAspects.Select(a => new AspectDataOnAsyncMethod(moveNextMethod, a.Aspect, a.Methods, method, module)).ToList<AspectData>();
return new AsyncMethodWeaver(
module,
method,
moveNextMethod,
aspectDatas,
methodInfoCompileTimeWeaver);
}

static AspectMethods GetUsedAspectMethods(TypeReference aspectTypeDefinition)
Expand Down
23 changes: 17 additions & 6 deletions src/MethodBoundaryAspect.Fody/ModuleWeaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ namespace MethodBoundaryAspect.Fody
/// </summary>
public class ModuleWeaver : BaseModuleWeaver
{
private MethodInfoCompileTimeWeaver _methodInfoCompileTimeWeaver;

public ModuleWeaver()
{
InitLogging();
}

public bool DisableCompileTimeMethodInfos { get; set; }

public int TotalWeavedTypes { get; private set; }
public int TotalWeavedMethods { get; private set; }
Expand All @@ -48,7 +52,12 @@ public ModuleWeaver()

public override void Execute()
{
_methodInfoCompileTimeWeaver = new MethodInfoCompileTimeWeaver(ModuleDefinition);
_methodInfoCompileTimeWeaver.IsEnabled = !DisableCompileTimeMethodInfos;

Execute(ModuleDefinition);

_methodInfoCompileTimeWeaver.Finish();
}

public override IEnumerable<string> GetAssembliesForScanning()
Expand Down Expand Up @@ -142,7 +151,7 @@ private void Execute(ModuleDefinition module)
{
var assemblyMethodBoundaryAspects = module.Assembly.CustomAttributes;

foreach (var type in module.Types)
foreach (var type in module.Types.ToList())
WeaveTypeAndNestedTypes(module, type, assemblyMethodBoundaryAspects);
}

Expand Down Expand Up @@ -207,31 +216,33 @@ private void WeaveType(
.ToList();
if (aspectInfos.Count == 0)
continue;

foreach (var aspectInfo in aspectInfos)
aspectInfo.InitOrderIndex(assemblyMethodBoundaryAspects, classMethodBoundaryAspects, methodMethodBoundaryAspects);

weavedAtLeastOneMethod = WeaveMethod(
module,
method,
aspectInfos);
aspectInfos,
_methodInfoCompileTimeWeaver);
}

if (weavedAtLeastOneMethod)
TotalWeavedTypes++;
}

private bool WeaveMethod(
ModuleDefinition module,
ModuleDefinition module,
MethodDefinition method,
List<AspectInfo> aspectInfos)
List<AspectInfo> aspectInfos,
MethodInfoCompileTimeWeaver methodInfoCompileTimeWeaver)
{
aspectInfos = AspectOrderer.Order(aspectInfos);
var aspectInfosWithMethods = aspectInfos
.Where(x => !x.SkipProperties || (!method.IsGetter && !method.IsSetter))
.ToList();

var methodWeaver = MethodWeaverFactory.MakeWeaver(module, method, aspectInfosWithMethods);
var methodWeaver = MethodWeaverFactory.MakeWeaver(module, method, aspectInfosWithMethods, methodInfoCompileTimeWeaver);
methodWeaver.Weave();
if (methodWeaver.WeaveCounter == 0)
return false;
Expand Down

0 comments on commit cd4bdcd

Please sign in to comment.