/// <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);
        }
Beispiel #4
0
        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);
        }