static bool MatchSpanTCtorWithPointerAndSize(NewObj newObj, StatementTransformContext context, out IType elementType, out FieldDefinition field, out int size) { field = default; size = default; elementType = null; IType type = newObj.Method.DeclaringType; if (!type.IsKnownType(KnownTypeCode.SpanOfT) && !type.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) { return(false); } if (newObj.Arguments.Count != 2 || type.TypeArguments.Count != 1) { return(false); } elementType = type.TypeArguments[0]; if (!newObj.Arguments[0].UnwrapConv(ConversionKind.StopGCTracking).MatchLdsFlda(out var member)) { return(false); } if (member.MetadataToken.IsNil) { return(false); } if (!newObj.Arguments[1].MatchLdcI4(out size)) { return(false); } field = context.PEFile.Metadata.GetFieldDefinition((FieldDefinitionHandle)member.MetadataToken); return(true); }
public void Run(Block block, int pos, StatementTransformContext context) { this.context = context; context.StepStartGroup($"ExpressionTransforms ({block.Label}:{pos})", block.Instructions[pos]); block.Instructions[pos].AcceptVisitor(this); context.StepEndGroup(keepIfEmpty: true); }
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); }
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { if (LegacyPattern(block, pos, context)) { return; } if (RoslynOptimized(block, pos, context)) { return; } }
public void Run(Block block, int pos, StatementTransformContext context) { if (!context.Settings.NamedArguments) { return; } var options = ILInlining.OptionsForBlock(block, pos); options |= InliningOptions.IntroduceNamedArguments; ILInlining.InlineOneIfPossible(block, pos, options, context: context); }
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { if (!context.Settings.ObjectOrCollectionInitializers) { return; } this.context = context; try { DoTransform(block, pos); } finally { this.context = null; } }
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--; } } }
/// <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 bool TransformSpanTArrayInitialization(NewObj inst, StatementTransformContext context, out Block block) { block = null; if (MatchSpanTCtorWithPointerAndSize(inst, context, out var elementType, out var field, out var size)) { if (field.HasFlag(System.Reflection.FieldAttributes.HasFieldRVA)) { var valuesList = new List <ILInstruction>(); var initialValue = field.GetInitialValue(context.PEFile.Reader, context.TypeSystem); if (DecodeArrayInitializer(elementType, initialValue, new[] { size }, valuesList)) { var tempStore = context.Function.RegisterVariable(VariableKind.InitializerTarget, new ArrayType(context.TypeSystem, elementType)); block = BlockFromInitializer(tempStore, elementType, new[] { size }, valuesList.ToArray()); return(true); } } } return(false); }
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); }
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { if (!context.Settings.ArrayInitializers) { return; } this.context = context; try { if (DoTransform(context.Function, block, pos)) { return; } if (DoTransformMultiDim(context.Function, block, pos)) { return; } if (context.Settings.StackAllocInitializers && DoTransformStackAllocInitializer(block, pos)) { return; } } finally { this.context = null; } }
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { this.context = context; if (context.Settings.MakeAssignmentExpressions) { if (TransformInlineAssignmentStObjOrCall(block, pos) || TransformInlineAssignmentLocal(block, pos)) { // both inline assignments create a top-level stloc which might affect inlining context.RequestRerun(); return; } } if (context.Settings.IntroduceIncrementAndDecrement) { if (TransformPostIncDecOperatorWithInlineStore(block, pos) || TransformPostIncDecOperator(block, pos) || TransformPostIncDecOperatorLocal(block, pos)) { // again, new top-level stloc might need inlining: context.RequestRerun(); return; } } }
public void Run(Block block, int pos, StatementTransformContext context) { TransformRefTypes(block, pos, context); }
public void Run(Block block, int pos, StatementTransformContext context) { InlineOneIfPossible(block, pos, OptionsForBlock(block, pos), context: context); }
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)); }
/// <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); }