/// <summary> /// if (comp(ldloc v == ldnull)) { /// stloc v(DelegateConstruction) /// } /// => /// stloc v(DelegateConstruction) /// </summary> bool CachedDelegateInitializationWithLocal(IfInstruction inst) { Block trueInst = inst.TrueInst as Block; if (trueInst == null || (trueInst.Instructions.Count != 1) || !inst.FalseInst.MatchNop()) { return(false); } if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdLoc(out ILVariable v) || !right.MatchLdNull()) { return(false); } var storeInst = trueInst.Instructions.Last(); if (!storeInst.MatchStLoc(v, out ILInstruction value)) { return(false); } if (!DelegateConstruction.IsDelegateConstruction(value as NewObj, true)) { return(false); } // do not transform if there are other stores/loads of this variable if (v.StoreCount != 2 || v.StoreInstructions.Count != 2 || v.LoadCount != 2 || v.AddressCount != 0) { return(false); } // do not transform if the first assignment is not assigning null: var otherStore = v.StoreInstructions.OfType <StLoc>().SingleOrDefault(store => store != storeInst); if (otherStore == null || !otherStore.Value.MatchLdNull() || !(otherStore.Parent is Block)) { return(false); } // do not transform if there is no usage directly afterwards var nextInstruction = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1); if (nextInstruction == null) { return(false); } var usages = nextInstruction.Descendants.Where(i => i.MatchLdLoc(v)).ToArray(); if (usages.Length != 1) { return(false); } context.Step("CachedDelegateInitializationWithLocal", inst); ((Block)otherStore.Parent).Instructions.Remove(otherStore); inst.ReplaceWith(storeInst); return(true); }
/// <summary> /// stloc s(ldobj(ldflda(CachedAnonMethodDelegate)) /// if (comp(ldloc s == null)) { /// stloc s(stobj(ldflda(CachedAnonMethodDelegate), DelegateConstruction)) /// } /// => /// stloc s(DelegateConstruction) /// </summary> bool CachedDelegateInitializationRoslynWithLocal(IfInstruction inst) { Block trueInst = inst.TrueInst as Block; if (trueInst == null || (trueInst.Instructions.Count != 1) || !inst.FalseInst.MatchNop()) { return(false); } if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdLoc(out ILVariable s) || !right.MatchLdNull()) { return(false); } var storeInst = trueInst.Instructions.Last() as StLoc; var storeBeforeIf = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex - 1) as StLoc; if (storeBeforeIf == null || storeInst == null || storeBeforeIf.Variable != s || storeInst.Variable != s) { return(false); } if (!(storeInst.Value is StObj stobj) || !(storeBeforeIf.Value is LdObj ldobj)) { return(false); } if (!(stobj.Value is NewObj)) { return(false); } if (!stobj.Target.MatchLdFlda(out var _, out var field1) || !ldobj.Target.MatchLdFlda(out var __, out var field2) || !field1.Equals(field2)) { return(false); } if (!DelegateConstruction.IsDelegateConstruction((NewObj)stobj.Value, true)) { return(false); } context.Step("CachedDelegateInitializationRoslynWithLocal", inst); storeBeforeIf.Value = stobj.Value; return(true); }
/// <summary> /// if (comp(ldsfld CachedAnonMethodDelegate == ldnull)) { /// stsfld CachedAnonMethodDelegate(DelegateConstruction) /// } /// ... one usage of CachedAnonMethodDelegate ... /// => /// ... one usage of DelegateConstruction ... /// </summary> bool CachedDelegateInitializationWithField(IfInstruction inst) { Block trueInst = inst.TrueInst as Block; if (trueInst == null || trueInst.Instructions.Count != 1 || !inst.FalseInst.MatchNop()) { return(false); } var storeInst = trueInst.Instructions[0]; if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdsFld(out IField field) || !right.MatchLdNull()) { return(false); } if (!storeInst.MatchStsFld(out IField field2, out ILInstruction value) || !field.Equals(field2) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) { return(false); } if (!DelegateConstruction.IsDelegateConstruction(value as NewObj, true)) { return(false); } var nextInstruction = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1); if (nextInstruction == null) { return(false); } var usages = nextInstruction.Descendants.Where(i => i.MatchLdsFld(field)).ToArray(); if (usages.Length != 1) { return(false); } context.Step("CachedDelegateInitializationWithField", inst); usages[0].ReplaceWith(value); return(true); }
void Run(CallInstruction inst, ILTransformContext context) { if (inst.Method.IsStatic) { return; } if (inst.Method.MetadataToken.IsNil || inst.Method.MetadataToken.Kind != HandleKind.MethodDefinition) { return; } var handle = (MethodDefinitionHandle)inst.Method.MetadataToken; if (!IsDefinedInCurrentOrOuterClass(inst.Method, context.Function.Method.DeclaringTypeDefinition)) { return; } if (!inst.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) { return; } var metadata = context.PEFile.Metadata; MethodDefinition methodDef = metadata.GetMethodDefinition((MethodDefinitionHandle)inst.Method.MetadataToken); if (!methodDef.HasBody()) { return; } var genericContext = DelegateConstruction.GenericContextFromTypeArguments(inst.Method.Substitution); if (genericContext == null) { return; } // partially copied from CSharpDecompiler var ilReader = context.CreateILReader(); var body = context.PEFile.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); var proxyFunction = ilReader.ReadIL(handle, body, genericContext.Value, context.CancellationToken); var transformContext = new ILTransformContext(context, proxyFunction); proxyFunction.RunTransforms(CSharp.CSharpDecompiler.EarlyILTransforms(), transformContext); if (!(proxyFunction.Body is BlockContainer blockContainer)) { return; } if (blockContainer.Blocks.Count != 1) { return; } var block = blockContainer.Blocks[0]; Call call; ILInstruction returnValue; switch (block.Instructions.Count) { case 1: // leave IL_0000 (call Test(ldloc this, ldloc A_1)) if (!block.Instructions[0].MatchLeave(blockContainer, out returnValue)) { return; } call = returnValue as Call; break; case 2: // call Test(ldloc this, ldloc A_1) // leave IL_0000(nop) call = block.Instructions[0] as Call; if (!block.Instructions[1].MatchLeave(blockContainer, out returnValue)) { return; } if (!returnValue.MatchNop()) { return; } break; default: return; } if (call == null || call.Method.IsConstructor) { return; } if (call.Method.IsStatic || call.Method.Parameters.Count != inst.Method.Parameters.Count) { return; } // check if original arguments are only correct ldloc calls for (int i = 0; i < call.Arguments.Count; i++) { var originalArg = call.Arguments[i]; if (!originalArg.MatchLdLoc(out ILVariable var) || var.Kind != VariableKind.Parameter || var.Index != i - 1) { return; } } context.Step("Replace proxy: " + inst.Method.Name + " with " + call.Method.Name, inst); Call newInst = (Call)call.Clone(); newInst.Arguments.ReplaceList(inst.Arguments); inst.ReplaceWith(newInst); }
bool DoTransform(Block body, int pos) { ILInstruction inst = body.Instructions[pos]; // Match stloc(v, newobj) if (inst.MatchStLoc(out var v, out var initInst) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot)) { IType instType; switch (initInst) { case NewObj newObjInst: if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor && !context.Function.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) { // on statement level (no other expressions on IL stack), // prefer to keep local variables (but not stack slots), // unless we are in a constructor (where inlining object initializers might be critical // for the base ctor call) or a compiler-generated delegate method, which might be used in a query expression. return(false); } // Do not try to transform display class usages or delegate construction. // DelegateConstruction transform cannot deal with this. if (DelegateConstruction.IsSimpleDisplayClass(newObjInst.Method.DeclaringType)) { return(false); } if (DelegateConstruction.IsDelegateConstruction(newObjInst) || DelegateConstruction.IsPotentialClosure(context, newObjInst)) { return(false); } // Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression:s // anon = new { A = 5 } { 3,4,5 } is invalid syntax. if (newObjInst.Method.DeclaringType.ContainsAnonymousType()) { return(false); } instType = newObjInst.Method.DeclaringType; break; case DefaultValue defaultVal: if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor) { // on statement level (no other expressions on IL stack), // prefer to keep local variables (but not stack slots), // unless we are in a constructor (where inlining object initializers might be critical // for the base ctor call) return(false); } instType = defaultVal.Type; break; default: return(false); } int initializerItemsCount = 0; var blockKind = BlockKind.CollectionInitializer; possibleIndexVariables = new Dictionary <ILVariable, (int Index, ILInstruction Value)>(); currentPath = new List <AccessPathElement>(); isCollection = false; pathStack = new Stack <HashSet <AccessPathElement> >(); pathStack.Push(new HashSet <AccessPathElement>()); // Detect initializer type by scanning the following statements // each must be a callvirt with ldloc v as first argument // if the method is a setter we're dealing with an object initializer // if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer while (pos + initializerItemsCount + 1 < body.Instructions.Count && IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockKind)) { initializerItemsCount++; } // Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable // directly after the possible initializer. if (IsMethodCallOnVariable(body.Instructions[pos + initializerItemsCount + 1], v)) { return(false); } // Calculate the correct number of statements inside the initializer: // All index variables that were used in the initializer have Index set to -1. // We fetch the first unused variable from the list and remove all instructions after its // first usage (i.e. the init store) from the initializer. var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index); if (index != null) { initializerItemsCount = index.Value - pos - 1; } // The initializer would be empty, there's nothing to do here. if (initializerItemsCount <= 0) { return(false); } context.Step("CollectionOrObjectInitializer", inst); // Create a new block and final slot (initializer target variable) var initializerBlock = new Block(blockKind); ILVariable finalSlot = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); initializerBlock.FinalInstruction = new LdLoc(finalSlot); initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone())); // Move all instructions to the initializer block. for (int i = 1; i <= initializerItemsCount; i++) { switch (body.Instructions[i + pos]) { case CallInstruction call: if (!(call is CallVirt || call is Call)) { continue; } var newCall = call; var newTarget = newCall.Arguments[0]; foreach (var load in newTarget.Descendants.OfType <IInstructionWithVariableOperand>()) { if ((load is LdLoc || load is LdLoca) && load.Variable == v) { load.Variable = finalSlot; } } initializerBlock.Instructions.Add(newCall); break; case StObj stObj: var newStObj = stObj; foreach (var load in newStObj.Target.Descendants.OfType <IInstructionWithVariableOperand>()) { if ((load is LdLoc || load is LdLoca) && load.Variable == v) { load.Variable = finalSlot; } } initializerBlock.Instructions.Add(newStObj); break; case StLoc stLoc: var newStLoc = stLoc; initializerBlock.Instructions.Add(newStLoc); break; } } initInst.ReplaceWith(initializerBlock); body.Instructions.RemoveRange(pos + 1, initializerItemsCount); ILInlining.InlineIfPossible(body, pos, context); } return(true); }