private bool ProcessDelegateAllocation(AssemblyProcessorContext context, MethodDefinition method, Instruction delegateAllocationInstruction) { // The instruction must be a delegate allocation // If not, this might be a static delegate, or some unsupported construct if (delegateAllocationInstruction.OpCode != OpCodes.Newobj) return false; var delegateInstanceConstructor = (MethodReference)delegateAllocationInstruction.Operand; var delegateInstanceType = delegateInstanceConstructor.DeclaringType; // The previous instruction pushes the delegate method onto the stack var functionPointerInstruction = delegateAllocationInstruction.Previous; if (functionPointerInstruction.OpCode != OpCodes.Ldftn) return false; var delegateMethod = (MethodReference)functionPointerInstruction.Operand; // The previous instruction pushes the target onto the stack // If it's the this-parameter, we can create an instance field, and reuse the same delegate var loadClosureInstruction = functionPointerInstruction.Previous; if (loadClosureInstruction.OpCode == OpCodes.Ldarg_0 && !method.IsStatic) { // TODO: Handle generic methods/delegates // TODO: Handle multiple constructors propertly var constructor = method.DeclaringType.Methods.FirstOrDefault(x => x.Name == ".ctor" && !x.HasParameters); var retInstruction3 = constructor?.Body.Instructions.FirstOrDefault(x => x.OpCode == OpCodes.Ret); if (retInstruction3 == null) return false; // Create an instance field for the shared delegate var sharedDelegateField = new FieldDefinition($"<delegate>{delegateMethod.Name}", FieldAttributes.Private, delegateInstanceType); method.DeclaringType.Fields.Add(sharedDelegateField); // Create and store the delegate in constructor var ilProcessor5 = constructor.Body.GetILProcessor(); ilProcessor5.InsertBefore(retInstruction3, ilProcessor5.Create(OpCodes.Ldarg_0)); ilProcessor5.InsertBefore(retInstruction3, ilProcessor5.Create(OpCodes.Ldarg_0)); ilProcessor5.InsertBefore(retInstruction3, ilProcessor5.Create(OpCodes.Ldftn, delegateMethod)); ilProcessor5.InsertBefore(retInstruction3, ilProcessor5.Create(OpCodes.Newobj, delegateInstanceConstructor)); ilProcessor5.InsertBefore(retInstruction3, ilProcessor5.Create(OpCodes.Stfld, sharedDelegateField)); // Load from field instead of allocating var ilProcessor4 = method.Body.GetILProcessor(); ilProcessor4.Remove(functionPointerInstruction); ilProcessor4.Replace(delegateAllocationInstruction, ilProcessor4.Create(OpCodes.Ldfld, sharedDelegateField)); return true; } // If the target is a compiler generated closure, we only handle local variable load instructions int variableIndex; OpCode storeOpCode; if (!TryGetStoreOpcode(loadClosureInstruction, out storeOpCode, out variableIndex)) return false; // Find the instruction that stores the closure variable var storeClosureInstruction = loadClosureInstruction.Previous; VariableReference closureVarible = null; while (storeClosureInstruction != null) { closureVarible = storeClosureInstruction.Operand as VariableReference; if (storeClosureInstruction.OpCode == storeOpCode && (closureVarible == null || variableIndex == closureVarible.Index)) break; storeClosureInstruction = storeClosureInstruction.Previous; } if (storeClosureInstruction == null) return false; var closureInstanceType = method.Body.Variables[variableIndex].VariableType; var closureType = closureInstanceType.Resolve(); var genericParameters = closureType.GenericParameters.Cast<TypeReference>().ToArray(); // Patch closure var closure = ProcessClosure(context, closureType, genericParameters); // Create delegate field var delegateFieldType = ChangeGenericArguments(context, delegateInstanceType, closureInstanceType); var delegateField = new FieldDefinition($"<delegate>{delegateMethod.Name}", FieldAttributes.Public, delegateFieldType); closureType.Fields.Add(delegateField); var localDelegateFieldInstance = delegateField.MakeGeneric(genericParameters); // Initialize delegate field (the closure instance (local 0) is already on the stack) var delegateConstructorInstance = (MethodReference)delegateAllocationInstruction.Operand; var delegateGenericArguments = (delegateFieldType as GenericInstanceType)?.GenericArguments.ToArray() ?? new TypeReference[0]; var genericDelegateConstructor = context.Assembly.MainModule.ImportReference(delegateConstructorInstance.Resolve()).MakeGeneric(delegateGenericArguments); var methodInstance = (MethodReference)functionPointerInstruction.Operand; var genericMethod = methodInstance.Resolve().MakeGeneric(closureType.GenericParameters.ToArray()); if (methodInstance is GenericInstanceMethod) throw new NotImplementedException(); var ilProcessor3 = closure.FactoryMethod.Body.GetILProcessor(); var returnInstruction = ilProcessor3.Body.Instructions.FirstOrDefault(x => x.OpCode == OpCodes.Ret); ilProcessor3.InsertBefore(returnInstruction, ilProcessor3.Create(OpCodes.Ldloc_0)); ilProcessor3.InsertBefore(returnInstruction, ilProcessor3.Create(OpCodes.Ldftn, genericMethod)); ilProcessor3.InsertBefore(returnInstruction, ilProcessor3.Create(OpCodes.Newobj, genericDelegateConstructor)); ilProcessor3.InsertBefore(returnInstruction, ilProcessor3.Create(OpCodes.Stfld, localDelegateFieldInstance)); ilProcessor3.InsertBefore(returnInstruction, ilProcessor3.Create(OpCodes.Ldloc_0)); var ilProcessor = method.Body.GetILProcessor(); // Retrieve from pool var closureGenericArguments = (closureInstanceType as GenericInstanceType)?.GenericArguments.ToArray() ?? new TypeReference[0]; var closureAllocation = storeClosureInstruction.Previous; if (closureAllocation.OpCode == OpCodes.Newobj) { // Retrieve closure from pool, instead of allocating var acquireClosure = ilProcessor.Create(OpCodes.Callvirt, poolAcquireMethod.MakeGeneric(closureInstanceType)); ilProcessor.InsertAfter(closureAllocation, acquireClosure); ilProcessor.InsertAfter(closureAllocation, ilProcessor.Create(OpCodes.Ldsfld, closure.PoolField.MakeGeneric(closureGenericArguments))); closureAllocation.OpCode = OpCodes.Nop; // Change to Nop instead of removing it, as this instruction might be reference somewhere? closureAllocation.Operand = null; // Add a reference ilProcessor.InsertAfter(storeClosureInstruction, ilProcessor.Create(OpCodes.Callvirt, closure.AddReferenceMethod.MakeGeneric(closureGenericArguments))); ilProcessor.InsertAfter(storeClosureInstruction, closureVarible == null ? ilProcessor.Create(loadClosureInstruction.OpCode) : ilProcessor.Create(loadClosureInstruction.OpCode, closureVarible.Resolve())); // TODO: Multiple returns + try/finally // Release reference var retInstructions = method.Body.Instructions.Where(x => x.OpCode == OpCodes.Ret).ToArray(); Instruction beforeReturn = closureVarible == null ? ilProcessor.Create(loadClosureInstruction.OpCode) : ilProcessor.Create(loadClosureInstruction.OpCode, closureVarible.Resolve()); Instruction newReturnInstruction = ilProcessor.Create(OpCodes.Ret); ilProcessor.Append(beforeReturn); ilProcessor.Append(ilProcessor.Create(OpCodes.Ldnull)); ilProcessor.Append(ilProcessor.Create(OpCodes.Beq, newReturnInstruction)); ilProcessor.Append(closureVarible == null ? ilProcessor.Create(loadClosureInstruction.OpCode) : ilProcessor.Create(loadClosureInstruction.OpCode, closureVarible.Resolve())); ilProcessor.Append(ilProcessor.Create(OpCodes.Callvirt, closure.ReleaseMethod.MakeGeneric(closureGenericArguments))); ilProcessor.Append(newReturnInstruction); foreach (var retInstruction2 in retInstructions) { retInstruction2.OpCode = OpCodes.Br; retInstruction2.Operand = beforeReturn; } } // Get delegate from closure, instead of allocating ilProcessor.Remove(functionPointerInstruction); ilProcessor.Replace(delegateAllocationInstruction, ilProcessor.Create(OpCodes.Ldfld, delegateField.MakeGeneric(closureGenericArguments))); // Closure object is already on the stack return true; }
private ClosureInfo ProcessClosure(AssemblyProcessorContext context, TypeDefinition closureType, TypeReference[] genericParameters) { ClosureInfo closure; if (closures.TryGetValue(closureType, out closure)) return closure; var closureTypeConstructor = closureType.Methods.FirstOrDefault(x => x.Name == ".ctor"); var closureGenericType = closureType.MakeGenericType(genericParameters); // Create factory method for pool items var factoryMethod = new MethodDefinition("<Factory>", MethodAttributes.HideBySig | MethodAttributes.Private | MethodAttributes.Static, closureGenericType); closureType.Methods.Add(factoryMethod); factoryMethod.Body.InitLocals = true; factoryMethod.Body.Variables.Add(new VariableDefinition(closureGenericType)); var factoryMethodProcessor = factoryMethod.Body.GetILProcessor(); // Create and store closure factoryMethodProcessor.Emit(OpCodes.Newobj, closureTypeConstructor.MakeGeneric(genericParameters)); factoryMethodProcessor.Emit(OpCodes.Stloc_0); //// Return closure factoryMethodProcessor.Emit(OpCodes.Ldloc_0); factoryMethodProcessor.Emit(OpCodes.Ret); // Create pool field var poolField = new FieldDefinition("<pool>", FieldAttributes.Public | FieldAttributes.Static, poolType.MakeGenericType(closureGenericType)); closureType.Fields.Add(poolField); var poolFieldReference = poolField.MakeGeneric(genericParameters); // Initialize pool var cctor = GetOrCreateClassConstructor(closureType); var ilProcessor2 = cctor.Body.GetILProcessor(); var retInstruction = cctor.Body.Instructions.FirstOrDefault(x => x.OpCode == OpCodes.Ret); ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Ldnull)); ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Ldftn, factoryMethod.MakeGeneric(genericParameters))); ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Newobj, funcConstructor.MakeGeneric(closureGenericType))); ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Newobj, poolConstructor.MakeGeneric(closureGenericType))); ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Stsfld, poolFieldReference)); // Implement IPooledClosure closureType.Interfaces.Add(pooledClosureType); // Create reference count field var countField = new FieldDefinition("<referenceCount>", FieldAttributes.Public, context.Assembly.MainModule.TypeSystem.Int32); closureType.Fields.Add(countField); var oountFieldReference = countField.MakeGeneric(genericParameters); // Create AddReference method var addReferenceMethod = new MethodDefinition("AddReference", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot, context.Assembly.MainModule.TypeSystem.Void); var ilProcessor4 = addReferenceMethod.Body.GetILProcessor(); ilProcessor4.Emit(OpCodes.Ldarg_0); ilProcessor4.Emit(OpCodes.Ldflda, oountFieldReference); ilProcessor4.Emit(OpCodes.Call, interlockedIncrementMethod); ilProcessor4.Emit(OpCodes.Pop); ilProcessor4.Emit(OpCodes.Ret); closureType.Methods.Add(addReferenceMethod); // Create Release method var releaseMethod = new MethodDefinition("Release", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot, context.Assembly.MainModule.TypeSystem.Void); ilProcessor4 = releaseMethod.Body.GetILProcessor(); retInstruction = ilProcessor4.Create(OpCodes.Ret); // Check decremented reference count ilProcessor4.Emit(OpCodes.Ldarg_0); ilProcessor4.Emit(OpCodes.Ldflda, oountFieldReference); ilProcessor4.Emit(OpCodes.Call, interlockedDecrementMethod); ilProcessor4.Emit(OpCodes.Ldc_I4_0); ilProcessor4.Emit(OpCodes.Ceq); ilProcessor4.Emit(OpCodes.Brfalse_S, retInstruction); // Release this to pool ilProcessor4.Emit(OpCodes.Ldsfld, poolFieldReference); ilProcessor4.Emit(OpCodes.Ldarg_0); ilProcessor4.Emit(OpCodes.Callvirt, poolReleaseMethod.MakeGeneric(closureGenericType)); ilProcessor4.Append(retInstruction); closureType.Methods.Add(releaseMethod); closures.Add(closureType, closure = new ClosureInfo { FactoryMethod = factoryMethod, AddReferenceMethod = addReferenceMethod, ReleaseMethod = releaseMethod, PoolField = poolField }); return closure; }
public bool Process(AssemblyProcessorContext context) { var assembly = context.Assembly; var mscorlibAssembly = CecilExtensions.FindCorlibAssembly(assembly); if (mscorlibAssembly == null) throw new InvalidOperationException("Missing mscorlib.dll from assembly"); // For now, use import, but this can cause mixed framework versions when processing an assembly with a different framework version. voidType = assembly.MainModule.TypeSystem.Void; stringType = assembly.MainModule.TypeSystem.String; objectType = assembly.MainModule.TypeSystem.Object; var propertyInfoType = assembly.MainModule.ImportReference(mscorlibAssembly.MainModule.GetTypeResolved(typeof(PropertyInfo).FullName)); var typeType = mscorlibAssembly.MainModule.GetTypeResolved(typeof(Type).FullName); TypeDefinition propertyChangedExtendedEventArgsType; AssemblyDefinition siliconStudioCoreAssembly; try { siliconStudioCoreAssembly = assembly.Name.Name == "SiliconStudio.Core" ? assembly : context.AssemblyResolver.Resolve("SiliconStudio.Core"); } catch (Exception) { return true; } propertyChangedExtendedEventArgsType = siliconStudioCoreAssembly.MainModule.GetTypes().First(x => x.Name == "PropertyChangedExtendedEventArgs").Resolve(); var typeTokenInfoEx = mscorlibAssembly.MainModule.GetType("System.Reflection.TypeInfo").Resolve(); var getPropertyMethod = typeTokenInfoEx.Methods.First(x => x.Name == "GetDeclaredProperty" && x.Parameters.Count == 1); var getTypeFromHandleMethod = typeType.Methods.First(x => x.Name == "GetTypeFromHandle"); var getTokenInfoExMethod = mscorlibAssembly.MainModule.GetType("System.Reflection.IntrospectionExtensions").Resolve().Methods.First(x => x.Name == "GetTypeInfo"); var propertyChangedExtendedEventArgsConstructor = assembly.MainModule.ImportReference(propertyChangedExtendedEventArgsType.Methods.First(x => x.IsConstructor)); bool modified = false; foreach (var type in assembly.MainModule.GetTypes()) { MethodReference getPropertyChangedMethod; getPropertyChangedMethod = GetGetPropertyChangedMethod(assembly, type); //var propertyChangedField = GetPropertyChangedField(type); //if (propertyChangedField == null) // continue; var propertyChangedField = GetPropertyChangedField(type); if (getPropertyChangedMethod == null && propertyChangedField == null) continue; TypeReference propertyChangedFieldType; if (getPropertyChangedMethod == null) { modified = true; getPropertyChangedMethod = GetOrCreateGetPropertyChangedMethod(assembly, type, propertyChangedField); } if (propertyChangedField != null) { propertyChangedField = assembly.MainModule.ImportReference(propertyChangedField); propertyChangedFieldType = propertyChangedField.FieldType; } else { propertyChangedFieldType = getPropertyChangedMethod.ReturnType; } // Add generic to declaring type if (getPropertyChangedMethod.DeclaringType.HasGenericParameters) getPropertyChangedMethod = getPropertyChangedMethod.MakeGeneric(getPropertyChangedMethod.DeclaringType.GenericParameters.ToArray()); var propertyChangedInvoke = assembly.MainModule.ImportReference(propertyChangedFieldType.Resolve().Methods.First(x => x.Name == "Invoke")); foreach (var property in type.Properties) { if (property.SetMethod == null || !property.HasThis) continue; MethodReference propertyGetMethod = property.GetMethod; // Only patch properties that have a public Getter var methodDefinition = propertyGetMethod.Resolve(); if ((methodDefinition.Attributes & MethodAttributes.Public) != MethodAttributes.Public) { continue; } // Add generic to declaring type if (propertyGetMethod.DeclaringType.HasGenericParameters) propertyGetMethod = propertyGetMethod.MakeGeneric(propertyGetMethod.DeclaringType.GenericParameters.ToArray()); //var versionableAttribute = property.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == typeof(VersionableAttribute).FullName); //if (versionableAttribute == null) // continue; modified = true; FieldReference staticField = new FieldDefinition(property.Name + "PropertyInfo", FieldAttributes.Static | FieldAttributes.Private | FieldAttributes.InitOnly, propertyInfoType); type.Fields.Add((FieldDefinition)staticField); // Add generic to declaring type if (staticField.DeclaringType.HasGenericParameters) staticField = staticField.MakeGeneric(staticField.DeclaringType.GenericParameters.ToArray()); // In static constructor, find PropertyInfo and store it in static field { var staticConstructor = type.GetStaticConstructor(); if (staticConstructor == null) { staticConstructor = new MethodDefinition(".cctor", MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, voidType); staticConstructor.Body.GetILProcessor().Append(Instruction.Create(OpCodes.Ret)); type.Methods.Add(staticConstructor); } VariableReference localTokenEx = null; int localTokenExIndex = 0; for (int i = 0; i < staticConstructor.Body.Variables.Count; i++) { var localVar = staticConstructor.Body.Variables[i]; if (localVar.VariableType.FullName == typeTokenInfoEx.FullName) { localTokenEx = localVar; localTokenExIndex = i; break; } } if (localTokenEx == null) { localTokenEx = new VariableDefinition(assembly.MainModule.ImportReference(typeTokenInfoEx)); staticConstructor.Body.Variables.Add((VariableDefinition)localTokenEx); localTokenExIndex = staticConstructor.Body.Variables.Count - 1; } var ilProcessor = staticConstructor.Body.GetILProcessor(); var returnInstruction = staticConstructor.Body.Instructions.Last(); var newReturnInstruction = Instruction.Create(returnInstruction.OpCode); newReturnInstruction.Operand = returnInstruction.Operand; returnInstruction.OpCode = OpCodes.Nop; returnInstruction.Operand = null; // Find PropertyInfo and store it in static field ilProcessor.Append(Instruction.Create(OpCodes.Ldtoken, type)); ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getTypeFromHandleMethod))); ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getTokenInfoExMethod))); ilProcessor.Append(LocationToStloc(ilProcessor, localTokenExIndex)); ilProcessor.Append(ilProcessor.Create(OpCodes.Ldloca_S, (byte)localTokenExIndex)); ilProcessor.Append(Instruction.Create(OpCodes.Ldstr, property.Name)); ilProcessor.Append(Instruction.Create(OpCodes.Call, assembly.MainModule.ImportReference(getPropertyMethod))); ilProcessor.Append(Instruction.Create(OpCodes.Stsfld, staticField)); ilProcessor.Append(newReturnInstruction); } { var ilProcessor = property.SetMethod.Body.GetILProcessor(); var returnInstruction = property.SetMethod.Body.Instructions.Last(); var firstInstruction = property.SetMethod.Body.Instructions[0]; if (property.SetMethod.Body.Instructions[0].OpCode != OpCodes.Nop) ilProcessor.InsertBefore(property.SetMethod.Body.Instructions[0], Instruction.Create(OpCodes.Nop)); var newReturnInstruction = Instruction.Create(returnInstruction.OpCode); newReturnInstruction.Operand = returnInstruction.Operand; returnInstruction.OpCode = OpCodes.Nop; returnInstruction.Operand = null; var propertyChangedVariable = new VariableDefinition("propertyChanged", assembly.MainModule.ImportReference(propertyChangedFieldType)); property.SetMethod.Body.Variables.Add(propertyChangedVariable); var oldValueVariable = new VariableDefinition("oldValue", objectType); property.SetMethod.Body.Variables.Add(oldValueVariable); Instruction jump1, jump2; // Prepend: // var propertyChanged = GetPropertyChanged(); // var oldValue = propertyChanged != null ? (object)Property : null; property.SetMethod.Body.SimplifyMacros(); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldarg_0)); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Call, getPropertyChangedMethod)); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Stloc, propertyChangedVariable)); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldloc, propertyChangedVariable)); ilProcessor.InsertBefore(firstInstruction, jump1 = Instruction.Create(OpCodes.Brtrue, Instruction.Create(OpCodes.Nop))); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Ldnull)); ilProcessor.InsertBefore(firstInstruction, jump2 = Instruction.Create(OpCodes.Br, Instruction.Create(OpCodes.Nop))); ilProcessor.InsertBefore(firstInstruction, (Instruction)(jump1.Operand = Instruction.Create(OpCodes.Ldarg_0))); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Call, propertyGetMethod)); if (property.PropertyType.IsValueType) ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Box, property.PropertyType)); ilProcessor.InsertBefore(firstInstruction, (Instruction)(jump2.Operand = Instruction.Create(OpCodes.Nop))); ilProcessor.InsertBefore(firstInstruction, Instruction.Create(OpCodes.Stloc, oldValueVariable)); // Append: // if (propertyChanged != null) // propertyChanged(this, new PropertyChangedExtendedEventArgs("Property", oldValue, Property)); ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, propertyChangedVariable)); ilProcessor.Append(Instruction.Create(OpCodes.Ldnull)); ilProcessor.Append(Instruction.Create(OpCodes.Ceq)); ilProcessor.Append(Instruction.Create(OpCodes.Brtrue, newReturnInstruction)); ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, propertyChangedVariable)); ilProcessor.Append(Instruction.Create(OpCodes.Ldarg_0)); ilProcessor.Append(Instruction.Create(OpCodes.Ldsfld, staticField)); ilProcessor.Append(Instruction.Create(OpCodes.Ldloc, oldValueVariable)); ilProcessor.Append(Instruction.Create(OpCodes.Ldarg_0)); ilProcessor.Append(Instruction.Create(OpCodes.Call, propertyGetMethod)); if (property.PropertyType.IsValueType) ilProcessor.Append(Instruction.Create(OpCodes.Box, property.PropertyType)); ilProcessor.Append(Instruction.Create(OpCodes.Newobj, propertyChangedExtendedEventArgsConstructor)); ilProcessor.Append(Instruction.Create(OpCodes.Callvirt, propertyChangedInvoke)); ilProcessor.Append(Instruction.Create(OpCodes.Nop)); ilProcessor.Append(newReturnInstruction); property.SetMethod.Body.OptimizeMacros(); } } } return modified; }