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); }
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); }
/* * stloc tuple(call MakeIntIntTuple(ldloc this)) * ---- * stloc myInt(call op_Implicit(ldfld Item2(ldloca tuple))) * stloc a(ldfld Item1(ldloca tuple)) * stloc b(ldloc myInt) * ==> * deconstruct { * init: * <empty> * deconstruct: * match.deconstruct(temp = ldloca tuple) { * match(result0 = deconstruct.result 0(temp)), * match(result1 = deconstruct.result 1(temp)) * } * conversions: { * stloc conv2(call op_Implicit(ldloc result1)) * } * assignments: { * stloc a(ldloc result0) * stloc b(ldloc conv2) * } * } * */ void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { if (!context.Settings.Deconstruction) { return; } try { this.context = context; Reset(); if (TransformDeconstruction(block, pos)) { return; } if (InlineDeconstructionInitializer(block, pos)) { return; } } finally { this.context = null; Reset(); } }
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; } if (DoTransformInlineRuntimeHelpersInitializeArray(block, pos)) { return; } } finally { this.context = null; } }
/// <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); } trueInst = Block.Unwrap(trueInst); if (condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull() && trueInst.MatchStLoc(stloc.Variable, out var fallbackValue) ) { context.Step("NullCoalescingTransform (reference types)", stloc); stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue); block.Instructions.RemoveAt(pos + 1); // remove if instruction ILInlining.InlineOneIfPossible(block, pos, false, context); return(true); } return(false); }
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); }
public void Run(Block block, int pos, StatementTransformContext context) { if (!TransformRefTypes(block, pos, context)) { TransformThrowExpressionValueTypes(block, pos, context); } }
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { this.context = context; try { DoTransform(block, pos); } finally { this.context = null; } }
/// <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, int pos, StatementTransformContext context) { if (!context.Settings.NamedArguments) { return; } var options = ILInlining.OptionsForBlock(block, pos, context); options |= InliningOptions.IntroduceNamedArguments; ILInlining.InlineOneIfPossible(block, pos, options, context: context); }
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, 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--; } } }
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; } }
/// <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); }
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { if (!context.Settings.ArrayInitializers) { return; } this.context = context; try { if (!DoTransform(block, pos)) { DoTransformMultiDim(block, pos); } } finally { this.context = null; } }
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; } }
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); }
/// 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); }
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { if (!context.Settings.StringInterpolation) { return; } int interpolationStart = pos; int interpolationEnd; ILInstruction insertionPoint; // stloc v(newobj DefaultInterpolatedStringHandler..ctor(ldc.i4 literalLength, ldc.i4 formattedCount)) if (block.Instructions[pos] is StLoc { Variable : ILVariable { Kind : VariableKind.Local } v, Value : NewObj { Arguments : { Count : 2 } } newObj } stloc && v.Type.IsKnownType(KnownTypeCode.DefaultInterpolatedStringHandler) && newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.DefaultInterpolatedStringHandler) && newObj.Arguments[0].MatchLdcI4(out _) && newObj.Arguments[1].MatchLdcI4(out _)) { // { call MethodName(ldloca v, ...) } do { pos++; }while (IsKnownCall(block, pos, v)); interpolationEnd = pos; // ... call ToStringAndClear(ldloca v) ... if (!FindToStringAndClear(block, pos, interpolationStart, interpolationEnd, v, out insertionPoint)) { return; } if (!(v.StoreCount == 1 && v.AddressCount == interpolationEnd - interpolationStart && v.LoadCount == 0)) { return; } }
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); }
public void Run(Block block, int pos, StatementTransformContext context) { InlineOneIfPossible(block, pos, OptionsForBlock(block, pos, context), 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)); }
public void Run(Block block, int pos, StatementTransformContext context) { InlineOneIfPossible(block, pos, aggressive: IsCatchWhenBlock(block), context: context); }
public void Run(Block block, int pos, StatementTransformContext context) { TransformRefTypes(block, pos, context); }
/// <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 (compoundStore is StLoc) { // transform local variables only for user-defined operators return(false); } if (!IsMatchingCompoundLoad(binary.Left, compoundStore, out var target, out var targetKind, out var finalizeMatch, forbiddenVariable: storeInSetter?.Variable)) { return(false); } if (!ValidateCompoundAssign(binary, smallIntConv, targetType)) { return(false); } context.Step($"Compound assignment (binary.numeric)", compoundStore); finalizeMatch?.Invoke(context); newInst = new NumericCompoundAssign( binary, target, targetKind, binary.Right, targetType, CompoundEvalMode.EvaluatesToNewValue); } else if (setterValue is Call operatorCall && operatorCall.Method.IsOperator) { if (operatorCall.Arguments.Count == 0) { return(false); } if (!IsMatchingCompoundLoad(operatorCall.Arguments[0], compoundStore, out var target, out var targetKind, out var finalizeMatch, 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); finalizeMatch?.Invoke(context); newInst = new UserDefinedCompoundAssign(operatorCall.Method, CompoundEvalMode.EvaluatesToNewValue, target, targetKind, rhs); }
/// <summary> /// stloc v(value) /// if (logic.not(call get_HasValue(ldloca v))) throw(...) /// ... Call(arg1, arg2, call GetValueOrDefault(ldloca v), arg4) ... /// => /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ... /// </summary> bool TransformThrowExpressionValueTypes(Block block, int pos, StatementTransformContext context) { if (pos + 2 >= block.Instructions.Count) { return(false); } if (!(block.Instructions[pos] is StLoc stloc)) { return(false); } ILVariable v = stloc.Variable; if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2)) { return(false); } if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) { return(false); } if (!(Block.Unwrap(trueInst) is Throw throwInst)) { return(false); } if (!condition.MatchLogicNot(out var arg)) { return(false); } if (!(arg is CallInstruction call && NullableLiftingTransform.MatchHasValueCall(call, v))) { return(false); } var throwInstParent = throwInst.Parent; var throwInstChildIndex = throwInst.ChildIndex; var nullCoalescingWithThrow = new NullCoalescingInstruction( NullCoalescingKind.NullableWithValueFallback, stloc.Value, throwInst); var resultType = NullableType.GetUnderlyingType(call.Method.DeclaringType).GetStackType(); nullCoalescingWithThrow.UnderlyingResultType = resultType; var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], v, nullCoalescingWithThrow, InliningOptions.None); if (result.Type == ILInlining.FindResultType.Found && NullableLiftingTransform.MatchGetValueOrDefault(result.LoadInst.Parent, v)) { context.Step("NullCoalescingTransform (value types + throw expression)", stloc); throwInst.resultType = resultType; result.LoadInst.Parent.ReplaceWith(nullCoalescingWithThrow); block.Instructions.RemoveRange(pos, 2); // remove store(s) and if instruction return(true); } else { // reset the primary position (see remarks on ILInstruction.Parent) stloc.Value = stloc.Value; var children = throwInstParent.Children; children[throwInstChildIndex] = throwInst; return(false); } }