Exemple #1
0
        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)
            {
                return(false);
            }
            IType instType;
            var   blockKind    = BlockKind.CollectionInitializer;
            var   insertionPos = initInst.ChildIndex;
            var   siblings     = initInst.Parent.Children;

            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 delegate construction.
                // DelegateConstruction transform cannot deal with this.
                if (DelegateConstruction.MatchDelegateConstruction(newObjInst, out _, out _, out _) ||
                    TransformDisplayClassUsage.IsPotentialClosure(context, newObjInst))
                {
                    return(false);
                }
                // Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression
                // 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;

            case Call c when c.Method.FullNameIs("System.Activator", "CreateInstance") && c.Method.TypeArguments.Count == 1:
                instType = c.Method.TypeArguments[0];

                blockKind = BlockKind.ObjectInitializer;
                break;

            case CallInstruction ci when context.Settings.WithExpressions && IsRecordCloneMethodCall(ci):
                instType  = ci.Method.DeclaringType;
                blockKind = BlockKind.WithInitializer;
                initInst  = ci.Arguments.Single();
                break;

            default:
                var typeDef = v.Type.GetDefinition();
                if (context.Settings.WithExpressions && typeDef?.IsReferenceType == false && typeDef.IsRecord)
                {
                    instType  = v.Type;
                    blockKind = BlockKind.WithInitializer;
                    break;
                }
                return(false);
            }
            int initializerItemsCount = 0;

            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, instType);

            initializerBlock.FinalInstruction = new LdLoc(finalSlot);
            initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst));
            // 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;
                }
            }
            body.Instructions.RemoveRange(pos + 1, initializerItemsCount);
            siblings[insertionPos] = initializerBlock;
            ILInlining.InlineIfPossible(body, pos, context);
            return(true);
        }
        bool DoTransform(ILFunction function, Block body, int pos)
        {
            if (pos >= body.Instructions.Count - 2)
            {
                return(false);
            }
            ILInstruction inst = body.Instructions[pos];

            if (inst.MatchStLoc(out var v, out var newarrExpr) && MatchNewArr(newarrExpr, out var elementType, out var arrayLength))
            {
                if (HandleRuntimeHelpersInitializeArray(body, pos + 1, v, elementType, arrayLength, out var values, out var initArrayPos))
                {
                    context.Step("HandleRuntimeHelperInitializeArray: single-dim", inst);
                    var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                    var block     = BlockFromInitializer(tempStore, elementType, arrayLength, values);
                    body.Instructions[pos] = new StLoc(v, block);
                    body.Instructions.RemoveAt(initArrayPos);
                    ILInlining.InlineIfPossible(body, pos, context);
                    return(true);
                }
                if (arrayLength.Length == 1)
                {
                    if (HandleSimpleArrayInitializer(function, body, pos + 1, v, elementType, arrayLength, out var arrayValues, out var instructionsToRemove))
                    {
                        context.Step("HandleSimpleArrayInitializer: single-dim", inst);
                        var block     = new Block(BlockKind.ArrayInitializer);
                        var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                        block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
                        block.Instructions.AddRange(arrayValues.Select(
                                                        t => {
                            var(indices, value) = t;
                            if (value == null)
                            {
                                value = GetNullExpression(elementType);
                            }
                            return(StElem(new LdLoc(tempStore), indices, value, elementType));
                        }
                                                        ));
                        block.FinalInstruction = new LdLoc(tempStore);
                        body.Instructions[pos] = new StLoc(v, block);
                        body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
                        ILInlining.InlineIfPossible(body, pos, context);
                        return(true);
                    }
                    if (HandleJaggedArrayInitializer(body, pos + 1, v, elementType, arrayLength[0], out ILVariable finalStore, out values, out instructionsToRemove))
                    {
                        context.Step("HandleJaggedArrayInitializer: single-dim", inst);
                        var block     = new Block(BlockKind.ArrayInitializer);
                        var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
                        block.Instructions.Add(new StLoc(tempStore, new NewArr(elementType, arrayLength.Select(l => new LdcI4(l)).ToArray())));
                        block.Instructions.AddRange(values.SelectWithIndex((i, value) => StElem(new LdLoc(tempStore), new[] { new LdcI4(i) }, value, elementType)));
                        block.FinalInstruction = new LdLoc(tempStore);
                        body.Instructions[pos] = new StLoc(finalStore, block);
                        body.Instructions.RemoveRange(pos + 1, instructionsToRemove);
                        ILInlining.InlineIfPossible(body, pos, context);
                        return(true);
                    }
                }
            }
            return(false);
        }