Beispiel #1
0
 /// <summary>
 /// Handles NullCoalescingInstruction case 1: reference types.
 ///
 /// stloc s(valueInst)
 /// if (comp(ldloc s == ldnull)) {
 ///		stloc s(fallbackInst)
 /// }
 /// =>
 /// stloc s(if.notnull(valueInst, fallbackInst))
 /// </summary>
 bool TransformRefTypes(Block block, int pos, StatementTransformContext context)
 {
     if (!(block.Instructions[pos] is StLoc stloc))
     {
         return(false);
     }
     if (stloc.Variable.Kind != VariableKind.StackSlot)
     {
         return(false);
     }
     if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst))
     {
         return(false);
     }
     if (!(condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull()))
     {
         return(false);
     }
     trueInst = Block.Unwrap(trueInst);
     if (trueInst.MatchStLoc(stloc.Variable, out var fallbackValue))
     {
         context.Step("NullCoalescingTransform: simple (reference types)", stloc);
         stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue);
         block.Instructions.RemoveAt(pos + 1);                 // remove if instruction
         ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context);
         return(true);
     }
     // sometimes the compiler generates:
     // stloc s(valueInst)
     // if (comp(ldloc s == ldnull)) {
     //		stloc v(fallbackInst)
     //      stloc s(ldloc v)
     // }
     // v must be single-assign and single-use.
     if (trueInst is Block trueBlock && trueBlock.Instructions.Count == 2 &&
         trueBlock.Instructions[0].MatchStLoc(out var temporary, out fallbackValue) &&
         temporary.IsSingleDefinition && temporary.LoadCount == 1 &&
         trueBlock.Instructions[1].MatchStLoc(stloc.Variable, out var useOfTemporary) &&
         useOfTemporary.MatchLdLoc(temporary))
     {
         context.Step("NullCoalescingTransform: with temporary variable (reference types)", stloc);
         stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue);
         block.Instructions.RemoveAt(pos + 1);                 // remove if instruction
         ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context);
         return(true);
     }
     return(false);
 }
 internal static void TransformDynamicSetMemberInstruction(DynamicSetMemberInstruction inst, StatementTransformContext context)
 {
     if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment))
     {
         return;
     }
     if (!(inst.Value is DynamicBinaryOperatorInstruction binaryOp))
     {
         return;
     }
     if (!(binaryOp.Left is DynamicGetMemberInstruction dynamicGetMember))
     {
         return;
     }
     if (!dynamicGetMember.Target.Match(inst.Target).Success)
     {
         return;
     }
     if (!SemanticHelper.IsPure(dynamicGetMember.Target.Flags))
     {
         return;
     }
     if (inst.Name != dynamicGetMember.Name || !DynamicCompoundAssign.IsExpressionTypeSupported(binaryOp.Operation))
     {
         return;
     }
     context.Step("dynamic.setmember.compound -> dynamic.compound.op", inst);
     inst.ReplaceWith(new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo));
 }
Beispiel #3
0
        bool LegacyPattern(Block block, int pos, StatementTransformContext context)
        {
            // Legacy csc pattern:
            //   stloc s(lhsInst)
            //   if (logic.not(call op_False(ldloc s))) Block {
            //     stloc s(call op_BitwiseAnd(ldloc s, rhsInst))
            //   }
            // ->
            //   stloc s(user.logic op_BitwiseAnd(lhsInst, rhsInst))
            if (!block.Instructions[pos].MatchStLoc(out var s, out var lhsInst))
            {
                return(false);
            }
            if (!(s.Kind == VariableKind.StackSlot))
            {
                return(false);
            }
            if (!(block.Instructions[pos + 1] is IfInstruction ifInst))
            {
                return(false);
            }
            if (!ifInst.Condition.MatchLogicNot(out var condition))
            {
                return(false);
            }
            if (!(MatchCondition(condition, out var s2, out string conditionMethodName) && s2 == s))
            {
                return(false);
            }
            if (ifInst.FalseInst.OpCode != OpCode.Nop)
            {
                return(false);
            }
            var trueInst = Block.Unwrap(ifInst.TrueInst);

            if (!trueInst.MatchStLoc(s, out var storeValue))
            {
                return(false);
            }
            if (storeValue is Call call)
            {
                if (!MatchBitwiseCall(call, s, conditionMethodName))
                {
                    return(false);
                }
                if (s.IsUsedWithin(call.Arguments[1]))
                {
                    return(false);
                }
                context.Step("User-defined short-circuiting logic operator (legacy pattern)", condition);
                ((StLoc)block.Instructions[pos]).Value = new UserDefinedLogicOperator(call.Method, lhsInst, call.Arguments[1])
                                                         .WithILRange(call);
                block.Instructions.RemoveAt(pos + 1);
                context.RequestRerun();                 // the 'stloc s' may now be eligible for inlining
                return(true);
            }
            return(false);
        }
Beispiel #4
0
 bool RoslynOptimized(Block block, int pos, StatementTransformContext context)
 {
     // Roslyn, optimized pattern in combination with return statement:
     //   if (logic.not(call op_False(ldloc lhsVar))) leave IL_0000 (call op_BitwiseAnd(ldloc lhsVar, rhsInst))
     //   leave IL_0000(ldloc lhsVar)
     // ->
     //   user.logic op_BitwiseAnd(ldloc lhsVar, rhsInst)
     if (!block.Instructions[pos].MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst))
     {
         return(false);
     }
     if (trueInst.OpCode == OpCode.Nop)
     {
         trueInst = block.Instructions[pos + 1];
     }
     else if (falseInst.OpCode == OpCode.Nop)
     {
         falseInst = block.Instructions[pos + 1];
     }
     else
     {
         return(false);
     }
     if (trueInst.MatchReturn(out var trueValue) && falseInst.MatchReturn(out var falseValue))
     {
         var transformed = Transform(condition, trueValue, falseValue);
         if (transformed == null)
         {
             transformed = TransformDynamic(condition, trueValue, falseValue);
         }
         if (transformed != null)
         {
             context.Step("User-defined short-circuiting logic operator (optimized return)", condition);
             ((Leave)block.Instructions[pos + 1]).Value = transformed;
             block.Instructions.RemoveAt(pos);
             return(true);
         }
     }
     return(false);
 }
        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 (HandleRuntimeHelperInitializeArray(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);
        }
        protected internal override void VisitComp(Comp inst)
        {
            // "logic.not(arg)" is sugar for "comp(arg != ldc.i4 0)"
            if (inst.MatchLogicNot(out var arg))
            {
                VisitLogicNot(inst, arg);
                return;
            }
            else if (inst.Kind == ComparisonKind.Inequality && inst.LiftingKind == ComparisonLiftingKind.None &&
                     inst.Right.MatchLdcI4(0) && (IfInstruction.IsInConditionSlot(inst) || inst.Left is Comp)
                     )
            {
                // if (comp(x != 0)) ==> if (x)
                // comp(comp(...) != 0) => comp(...)
                context.Step("Remove redundant comp(... != 0)", inst);
                inst.Left.AddILRange(inst);
                inst.ReplaceWith(inst.Left);
                inst.Left.AcceptVisitor(this);
                return;
            }

            base.VisitComp(inst);
            if (inst.IsLifted)
            {
                return;
            }
            if (inst.Right.MatchLdNull())
            {
                if (inst.Kind == ComparisonKind.GreaterThan)
                {
                    context.Step("comp(left > ldnull)  => comp(left != ldnull)", inst);
                    inst.Kind = ComparisonKind.Inequality;
                }
                else if (inst.Kind == ComparisonKind.LessThanOrEqual)
                {
                    context.Step("comp(left <= ldnull) => comp(left == ldnull)", inst);
                    inst.Kind = ComparisonKind.Equality;
                }
            }
            else if (inst.Left.MatchLdNull())
            {
                if (inst.Kind == ComparisonKind.LessThan)
                {
                    context.Step("comp(ldnull < right)  => comp(ldnull != right)", inst);
                    inst.Kind = ComparisonKind.Inequality;
                }
                else if (inst.Kind == ComparisonKind.GreaterThanOrEqual)
                {
                    context.Step("comp(ldnull >= right) => comp(ldnull == right)", inst);
                    inst.Kind = ComparisonKind.Equality;
                }
            }

            var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend);

            if (rightWithoutConv.MatchLdcI4(0) &&
                inst.Sign == Sign.Unsigned &&
                (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual))
            {
                if (inst.Kind == ComparisonKind.GreaterThan)
                {
                    context.Step("comp.unsigned(left > ldc.i4 0) => comp(left != ldc.i4 0)", inst);
                    inst.Kind = ComparisonKind.Inequality;
                    VisitComp(inst);
                    return;
                }
                else if (inst.Kind == ComparisonKind.LessThanOrEqual)
                {
                    context.Step("comp.unsigned(left <= ldc.i4 0) => comp(left == ldc.i4 0)", inst);
                    inst.Kind = ComparisonKind.Equality;
                    VisitComp(inst);
                    return;
                }
            }
            else if (rightWithoutConv.MatchLdcI4(0) && inst.Kind.IsEqualityOrInequality())
            {
                if (inst.Left.MatchLdLen(StackType.I, out ILInstruction array))
                {
                    // comp.unsigned(ldlen array == conv i4->i(ldc.i4 0))
                    // => comp(ldlen.i4 array == ldc.i4 0)
                    // This is a special case where the C# compiler doesn't generate conv.i4 after ldlen.
                    context.Step("comp(ldlen.i4 array == ldc.i4 0)", inst);
                    inst.InputType = StackType.I4;
                    inst.Left.ReplaceWith(new LdLen(StackType.I4, array).WithILRange(inst.Left));
                    inst.Right = rightWithoutConv;
                }
                else if (inst.Left is Conv conv && conv.TargetType == PrimitiveType.I && conv.Argument.ResultType == StackType.O)
                {
                    // C++/CLI sometimes uses this weird comparison with null:
                    context.Step("comp(conv o->i (ldloc obj) == conv i4->i <sign extend>(ldc.i4 0))", inst);
                    // -> comp(ldloc obj == ldnull)
                    inst.InputType = StackType.O;
                    inst.Left      = conv.Argument;
                    inst.Right     = new LdNull().WithILRange(inst.Right);
                    inst.Right.AddILRange(rightWithoutConv);
                }
            }

            if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out arg, out var type) && type.Kind == TypeKind.TypeParameter)
            {
                if (inst.Kind == ComparisonKind.Equality)
                {
                    context.Step("comp(box T(..) == ldnull) -> comp(.. == ldnull)", inst);
                    inst.Left = arg;
                }
                if (inst.Kind == ComparisonKind.Inequality)
                {
                    context.Step("comp(box T(..) != ldnull) -> comp(.. != ldnull)", inst);
                    inst.Left = arg;
                }
            }
        }
        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);
        }
Beispiel #8
0
        /// <code>
        /// stloc s(value)
        /// stloc l(ldloc s)
        /// stobj(..., ldloc s)
        ///   where ... is pure and does not use s or l,
        ///   and where neither the 'stloc s' nor the 'stobj' truncates
        /// -->
        /// stloc l(stobj (..., value))
        /// </code>
        /// e.g. used for inline assignment to instance field
        ///
        /// -or-
        ///
        /// <code>
        /// stloc s(value)
        /// stobj (..., ldloc s)
        ///   where ... is pure and does not use s, and where the 'stobj' does not truncate
        /// -->
        /// stloc s(stobj (..., value))
        /// </code>
        /// e.g. used for inline assignment to static field
        ///
        /// -or-
        ///
        /// <code>
        /// stloc s(value)
        /// call set_Property(..., ldloc s)
        ///   where the '...' arguments are pure and not using 's'
        /// -->
        /// stloc s(Block InlineAssign { call set_Property(..., stloc i(value)); final: ldloc i })
        ///   new temporary 'i' has type of the property; transform only valid if 'stloc i' doesn't truncate
        /// </code>
        bool TransformInlineAssignmentStObjOrCall(Block block, int pos)
        {
            var inst = block.Instructions[pos] as StLoc;

            // in some cases it can be a compiler-generated local
            if (inst == null || (inst.Variable.Kind != VariableKind.StackSlot && inst.Variable.Kind != VariableKind.Local))
            {
                return(false);
            }
            if (IsImplicitTruncation(inst.Value, inst.Variable.Type, context.TypeSystem))
            {
                // 'stloc s' is implicitly truncating the value
                return(false);
            }
            ILVariable local;
            int        nextPos;

            if (block.Instructions[pos + 1] is StLoc localStore)               // with extra local
            {
                if (localStore.Variable.Kind != VariableKind.Local || !localStore.Value.MatchLdLoc(inst.Variable))
                {
                    return(false);
                }
                // if we're using an extra local, we'll delete "s", so check that that doesn't have any additional uses
                if (!(inst.Variable.IsSingleDefinition && inst.Variable.LoadCount == 2))
                {
                    return(false);
                }
                local   = localStore.Variable;
                nextPos = pos + 2;
            }
            else
            {
                local      = inst.Variable;
                localStore = null;
                nextPos    = pos + 1;
            }
            if (block.Instructions[nextPos] is StObj stobj)
            {
                if (!stobj.Value.MatchLdLoc(inst.Variable))
                {
                    return(false);
                }
                if (!SemanticHelper.IsPure(stobj.Target.Flags) || inst.Variable.IsUsedWithin(stobj.Target))
                {
                    return(false);
                }
                var   pointerType = stobj.Target.InferType(context.TypeSystem);
                IType newType     = stobj.Type;
                if (TypeUtils.IsCompatiblePointerTypeForMemoryAccess(pointerType, stobj.Type))
                {
                    if (pointerType is ByReferenceType byref)
                    {
                        newType = byref.ElementType;
                    }
                    else if (pointerType is PointerType pointer)
                    {
                        newType = pointer.ElementType;
                    }
                }
                if (IsImplicitTruncation(inst.Value, newType, context.TypeSystem))
                {
                    // 'stobj' is implicitly truncating the value
                    return(false);
                }
                context.Step("Inline assignment stobj", stobj);
                stobj.Type = newType;
                block.Instructions.Remove(localStore);
                block.Instructions.Remove(stobj);
                stobj.Value = inst.Value;
                inst.ReplaceWith(new StLoc(local, stobj));
                // note: our caller will trigger a re-run, which will call HandleStObjCompoundAssign if applicable
                return(true);
            }
            else if (block.Instructions[nextPos] is CallInstruction call)
            {
                // call must be a setter call:
                if (!(call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt))
                {
                    return(false);
                }
                if (call.ResultType != StackType.Void || call.Arguments.Count == 0)
                {
                    return(false);
                }
                IProperty property = call.Method.AccessorOwner as IProperty;
                if (property == null)
                {
                    return(false);
                }
                if (!call.Method.Equals(property.Setter))
                {
                    return(false);
                }
                if (!(property.IsIndexer || property.Setter.Parameters.Count == 1))
                {
                    // this is a parameterized property, which cannot be expressed as C# code.
                    // setter calls are not valid in expression context, if property syntax cannot be used.
                    return(false);
                }
                if (!call.Arguments.Last().MatchLdLoc(inst.Variable))
                {
                    return(false);
                }
                foreach (var arg in call.Arguments.SkipLast(1))
                {
                    if (!SemanticHelper.IsPure(arg.Flags) || inst.Variable.IsUsedWithin(arg))
                    {
                        return(false);
                    }
                }
                if (IsImplicitTruncation(inst.Value, call.Method.Parameters.Last().Type, context.TypeSystem))
                {
                    // setter call is implicitly truncating the value
                    return(false);
                }
                // stloc s(Block InlineAssign { call set_Property(..., stloc i(value)); final: ldloc i })
                context.Step("Inline assignment call", call);
                block.Instructions.Remove(localStore);
                block.Instructions.Remove(call);
                var newVar = context.Function.RegisterVariable(VariableKind.StackSlot, call.Method.Parameters.Last().Type);
                call.Arguments[call.Arguments.Count - 1] = new StLoc(newVar, inst.Value);
                var inlineBlock = new Block(BlockKind.CallInlineAssign)
                {
                    Instructions     = { call },
                    FinalInstruction = new LdLoc(newVar)
                };
                inst.ReplaceWith(new StLoc(local, inlineBlock));
                // because the ExpressionTransforms don't look into inline blocks, manually trigger HandleCallCompoundAssign
                if (HandleCompoundAssign(call, context))
                {
                    // if we did construct a compound assignment, it should have made our inline block redundant:
                    if (inlineBlock.Instructions.Single().MatchStLoc(newVar, out var compoundAssign))
                    {
                        Debug.Assert(newVar.IsSingleDefinition && newVar.LoadCount == 1);
                        inlineBlock.ReplaceWith(compoundAssign);
                    }
                }
                return(true);
            }
            else
            {
                return(false);
            }
        }
Beispiel #9
0
        /// <summary>
        /// Transform compound assignments where the return value is not being used,
        /// or where there's an inlined assignment within the setter call.
        ///
        /// Patterns handled:
        /// 1.
        ///   callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value))
        ///   ==> compound.op.new(callvirt get_Property(ldloc S_1), value)
        /// 2.
        ///   callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
        ///   ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value))
        /// 3.
        ///   stobj(target, binary.op(ldobj(target), ...))
        ///     where target is pure
        ///   => compound.op(target, ...)
        /// </summary>
        /// <remarks>
        /// Called by ExpressionTransforms, or after the inline-assignment transform for setters.
        /// </remarks>
        internal static bool HandleCompoundAssign(ILInstruction compoundStore, StatementTransformContext context)
        {
            if (!context.Settings.MakeAssignmentExpressions || !context.Settings.IntroduceIncrementAndDecrement)
            {
                return(false);
            }
            if (compoundStore is CallInstruction && compoundStore.SlotInfo != Block.InstructionSlot)
            {
                // replacing 'call set_Property' with a compound assignment instruction
                // changes the return value of the expression, so this is only valid on block-level.
                return(false);
            }
            if (!IsCompoundStore(compoundStore, out var targetType, out var setterValue, context.TypeSystem))
            {
                return(false);
            }
            // targetType = The type of the property/field/etc. being stored to.
            // setterValue = The value being stored.
            var storeInSetter = setterValue as StLoc;

            if (storeInSetter != null)
            {
                // We'll move the stloc to top-level:
                // callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
                // ==> stloc v(compound.op.new(callvirt get_Property(ldloc S_1), value))
                setterValue = storeInSetter.Value;
                if (storeInSetter.Variable.Type.IsSmallIntegerType())
                {
                    // 'stloc v' implicitly truncates the value.
                    // Ensure that type of 'v' matches the type of the property:
                    if (storeInSetter.Variable.Type.GetSize() != targetType.GetSize())
                    {
                        return(false);
                    }
                    if (storeInSetter.Variable.Type.GetSign() != targetType.GetSign())
                    {
                        return(false);
                    }
                }
            }
            ILInstruction newInst;

            if (UnwrapSmallIntegerConv(setterValue, out var smallIntConv) is BinaryNumericInstruction binary)
            {
                if (!IsMatchingCompoundLoad(binary.Left, compoundStore, forbiddenVariable: storeInSetter?.Variable))
                {
                    return(false);
                }
                if (!ValidateCompoundAssign(binary, smallIntConv, targetType))
                {
                    return(false);
                }
                context.Step($"Compound assignment (binary.numeric)", compoundStore);
                newInst = new NumericCompoundAssign(
                    binary, binary.Left, binary.Right,
                    targetType, CompoundAssignmentType.EvaluatesToNewValue);
            }
            else if (setterValue is Call operatorCall && operatorCall.Method.IsOperator)
            {
                if (operatorCall.Arguments.Count == 0)
                {
                    return(false);
                }
                if (!IsMatchingCompoundLoad(operatorCall.Arguments[0], compoundStore, forbiddenVariable: storeInSetter?.Variable))
                {
                    return(false);
                }
                ILInstruction rhs;
                if (operatorCall.Arguments.Count == 2)
                {
                    if (CSharp.ExpressionBuilder.GetAssignmentOperatorTypeFromMetadataName(operatorCall.Method.Name) == null)
                    {
                        return(false);
                    }
                    rhs = operatorCall.Arguments[1];
                }
                else if (operatorCall.Arguments.Count == 1)
                {
                    if (!(operatorCall.Method.Name == "op_Increment" || operatorCall.Method.Name == "op_Decrement"))
                    {
                        return(false);
                    }
                    // use a dummy node so that we don't need a dedicated instruction for user-defined unary operator calls
                    rhs = new LdcI4(1);
                }
                else
                {
                    return(false);
                }
                if (operatorCall.IsLifted)
                {
                    return(false);                    // TODO: add tests and think about whether nullables need special considerations
                }
                context.Step($"Compound assignment (user-defined binary)", compoundStore);
                newInst = new UserDefinedCompoundAssign(operatorCall.Method, CompoundAssignmentType.EvaluatesToNewValue,
                                                        operatorCall.Arguments[0], rhs);
            }