protected internal override void VisitStObj(StObj inst) { base.VisitStObj(inst); if (EarlyExpressionTransforms.StObjToStLoc(inst, context)) { context.RequestRerun(); return; } TransformAssignment.HandleCompoundAssign(inst, context); }
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]) { ILRange = call.ILRange }; block.Instructions.RemoveAt(pos + 1); context.RequestRerun(); // the 'stloc s' may now be eligible for inlining return(true); } return(false); }
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { this.context = context; if (TransformInlineAssignmentStObjOrCall(block, pos) || TransformInlineAssignmentLocal(block, pos)) { // both inline assignments create a top-level stloc which might affect inlining context.RequestRerun(); return; } if (TransformPostIncDecOperatorWithInlineStore(block, pos) || TransformPostIncDecOperator(block, pos) || TransformPostIncDecOperatorLocal(block, pos)) { // again, new top-level stloc might need inlining: context.RequestRerun(); return; } }
/// <summary> /// Transform compound assignments where the return value is not being used, /// or where there's an inlined assignment within the setter call. /// </summary> /// <remarks> /// Called by ExpressionTransforms. /// </remarks> internal static bool HandleCallCompoundAssign(CallInstruction setterCall, StatementTransformContext context) { // 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) var setterValue = setterCall.Arguments.LastOrDefault(); var storeInSetter = setterValue as StLoc; if (storeInSetter != null) { // 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; } setterValue = UnwrapSmallIntegerConv(setterValue, out var conv); if (!(setterValue is BinaryNumericInstruction binary)) { return(false); } var getterCall = binary.Left as CallInstruction; if (!MatchingGetterAndSetterCalls(getterCall, setterCall)) { return(false); } IType targetType = getterCall.Method.ReturnType; if (!ValidateCompoundAssign(binary, conv, targetType)) { return(false); } if (storeInSetter != null && storeInSetter.Variable.Type.IsSmallIntegerType()) { // 'stloc v' implicitly truncates. // Ensure that type of 'v' must match type of the property: if (storeInSetter.Variable.Type.GetSize() != targetType.GetSize()) { return(false); } if (storeInSetter.Variable.Type.GetSign() != targetType.GetSign()) { return(false); } } context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall); ILInstruction newInst = new CompoundAssignmentInstruction( binary, getterCall, binary.Right, getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue); if (storeInSetter != null) { storeInSetter.Value = newInst; newInst = storeInSetter; context.RequestRerun(); // moving stloc to top-level might trigger inlining } setterCall.ReplaceWith(newInst); return(true); }
public void Run(Block block, BlockTransformContext context) { var ctx = new StatementTransformContext(context); int pos = 0; ctx.rerunPosition = block.Instructions.Count - 1; while (pos >= 0) { if (ctx.rerunPosition != null) { Debug.Assert(ctx.rerunPosition >= pos); #if DEBUG for (; pos < ctx.rerunPosition; ++pos) { block.Instructions[pos].ResetDirty(); } #else pos = ctx.rerunPosition.Value; #endif Debug.Assert(pos == ctx.rerunPosition); ctx.rerunPosition = null; } foreach (var transform in children) { transform.Run(block, pos, ctx); #if DEBUG block.Instructions[pos].CheckInvariant(ILPhase.Normal); for (int i = Math.Max(0, pos - 100); i < pos; ++i) { if (block.Instructions[i].IsDirty) { Debug.Fail($"{transform.GetType().Name} modified an instruction before pos"); } } #endif if (ctx.rerunCurrentPosition) { ctx.rerunCurrentPosition = false; ctx.RequestRerun(pos); } if (ctx.rerunPosition != null) { break; } } if (ctx.rerunPosition == null) { pos--; } } // This invariant can be surprisingly expensive to check if the block has thousands // of instructions and is frequently modified by transforms (invalidating the flags each time) // so we'll check this only once at the end of the block. Debug.Assert(block.HasFlag(InstructionFlags.EndPointUnreachable)); }
public void Run(Block block, BlockTransformContext context) { var ctx = new StatementTransformContext(context); int pos = 0; ctx.rerunPosition = block.Instructions.Count - 1; while (pos >= 0) { if (ctx.rerunPosition != null) { Debug.Assert(ctx.rerunPosition >= pos); #if DEBUG for (; pos < ctx.rerunPosition; ++pos) { block.Instructions[pos].ResetDirty(); } #else pos = ctx.rerunPosition.Value; #endif Debug.Assert(pos == ctx.rerunPosition); ctx.rerunPosition = null; } foreach (var transform in children) { Debug.Assert(block.HasFlag(InstructionFlags.EndPointUnreachable)); transform.Run(block, pos, ctx); #if DEBUG block.Instructions[pos].CheckInvariant(ILPhase.Normal); for (int i = Math.Max(0, pos - 100); i < pos; ++i) { if (block.Instructions[i].IsDirty) { Debug.Fail($"{transform.GetType().Name} modified an instruction before pos"); } } #endif if (ctx.rerunCurrentPosition) { ctx.rerunCurrentPosition = false; ctx.RequestRerun(pos); } if (ctx.rerunPosition != null) { break; } } if (ctx.rerunPosition == null) { pos--; } } }
protected internal override void VisitStObj(StObj inst) { base.VisitStObj(inst); if (EarlyExpressionTransforms.StObjToStLoc(inst, context)) { context.RequestRerun(); return; } if (inst.Value is BinaryNumericInstruction binary && binary.Left.MatchLdObj(out ILInstruction target, out IType t) && inst.Target.Match(target).Success && SemanticHelper.IsPure(target.Flags) && CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, t)) { context.Step("compound assignment", inst); // stobj(target, binary.op(ldobj(target), ...)) // => compound.op(target, ...) inst.ReplaceWith(new CompoundAssignmentInstruction( binary, binary.Left, binary.Right, t, CompoundAssignmentType.EvaluatesToNewValue)); } }
/// stloc V_1(dynamic.isevent (target)) /// if (logic.not(ldloc V_1)) Block IL_004a { /// stloc V_2(dynamic.getmember B(target)) /// } /// [stloc copyOfValue(value)] /// if (logic.not(ldloc V_1)) Block IL_0149 { /// dynamic.setmember.compound B(target, dynamic.binary.operator AddAssign(ldloc V_2, value)) /// } else Block IL_0151 { /// dynamic.invokemember.invokespecial.discard add_B(target, value) /// } /// => /// if (logic.not(dynamic.isevent (target))) Block IL_0149 { /// dynamic.setmember.compound B(target, dynamic.binary.operator AddAssign(dynamic.getmember B(target), value)) /// } else Block IL_0151 { /// dynamic.invokemember.invokespecial.discard add_B(target, value) /// } public void Run(Block block, int pos, StatementTransformContext context) { if (!(pos + 3 < block.Instructions.Count && block.Instructions[pos].MatchStLoc(out var flagVar, out var inst) && inst is DynamicIsEventInstruction isEvent)) { return; } if (!(flagVar.IsSingleDefinition && flagVar.LoadCount == 2)) { return; } if (!MatchLhsCacheIfInstruction(block.Instructions[pos + 1], flagVar, out var dynamicGetMemberStore)) { return; } if (!(dynamicGetMemberStore.MatchStLoc(out var getMemberVar, out inst) && inst is DynamicGetMemberInstruction getMemberInst)) { return; } int offset = 2; if (block.Instructions[pos + offset].MatchStLoc(out var valueVariable) && pos + 4 < block.Instructions.Count && valueVariable.IsSingleDefinition && valueVariable.LoadCount == 2 && valueVariable.LoadInstructions.All(ld => ld.Parent is DynamicInstruction)) { offset++; } foreach (var descendant in block.Instructions[pos + offset].Descendants) { if (!MatchIsEventAssignmentIfInstruction(descendant, isEvent, flagVar, getMemberVar, out var setMemberInst, out var getMemberVarUse, out var isEventConditionUse)) { continue; } context.Step("DynamicIsEventAssignmentTransform", block.Instructions[pos]); // Collapse duplicate condition getMemberVarUse.ReplaceWith(getMemberInst); isEventConditionUse.ReplaceWith(isEvent); block.Instructions.RemoveRange(pos, 2); // Reuse ExpressionTransforms ExpressionTransforms.TransformDynamicSetMemberInstruction(setMemberInst, context); context.RequestRerun(); break; } }
/// <summary> /// Transform compound assignments where the return value is not being used, /// or where there's an inlined assignment within the setter call. /// </summary> /// <remarks> /// Called by ExpressionTransforms. /// </remarks> internal static bool HandleCallCompoundAssign(CallInstruction setterCall, StatementTransformContext context) { // callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value)) // ==> compound.op.new(callvirt(callvirt get_Property(ldloc S_1)), value) var setterValue = setterCall.Arguments.LastOrDefault(); var storeInSetter = setterValue as StLoc; if (storeInSetter != null) { // callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value))) // ==> stloc v(compound.op.new(callvirt(callvirt get_Property(ldloc S_1)), value)) setterValue = storeInSetter.Value; } if (!(setterValue is BinaryNumericInstruction binary)) { return(false); } var getterCall = binary.Left as CallInstruction; if (!MatchingGetterAndSetterCalls(getterCall, setterCall)) { return(false); } if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType)) { return(false); } context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall); ILInstruction newInst = new CompoundAssignmentInstruction( binary, getterCall, binary.Right, getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue); if (storeInSetter != null) { storeInSetter.Value = newInst; newInst = storeInSetter; context.RequestRerun(); // moving stloc to top-level might trigger inlining } setterCall.ReplaceWith(newInst); return(true); }