private void RemoveDeadStackStores(Block block, bool aggressive) { // Previously copy propagation did this; // ideally the ILReader would already do this, // for now do this here (even though it's not control-flow related). for (int i = block.Instructions.Count - 1; i >= 0; i--) { if (block.Instructions[i] is StLoc stloc && stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 0 && stloc.Variable.Kind == VariableKind.StackSlot) { if (aggressive ? SemanticHelper.IsPure(stloc.Value.Flags) : IsSimple(stloc.Value)) { Debug.Assert(SemanticHelper.IsPure(stloc.Value.Flags)); block.Instructions.RemoveAt(i++); } else { stloc.Value.AddILRange(stloc); stloc.ReplaceWith(stloc.Value); } } } bool IsSimple(ILInstruction inst) { switch (inst.OpCode) { case OpCode.LdLoc: case OpCode.LdStr: // C# 1.0 compiler sometimes emits redundant ldstr in switch-on-string pattern return(true); default: return(false); } } }
public void Run(Block block, BlockTransformContext context) { for (int i = 0; i < block.Instructions.Count; i++) { ILVariable v; ILInstruction copiedExpr; if (block.Instructions[i].MatchStLoc(out v, out copiedExpr)) { if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) { // dead store to stack if (SemanticHelper.IsPure(copiedExpr.Flags)) { // no-op -> delete context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]); block.Instructions.RemoveAt(i--); } else { // evaluate the value for its side-effects context.Step("remove dead store to stack: evaluate the value for its side-effects", block.Instructions[i]); copiedExpr.AddILRange(block.Instructions[i].ILRange); block.Instructions[i] = copiedExpr; } } else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr)) { DoPropagate(v, copiedExpr, block, ref i, context); } } } }
/// <summary> /// Lift a C# comparison. /// /// The output instructions should evaluate to <c>false</c> when any of the <c>nullableVars</c> is <c>null</c>. /// Otherwise, the output instruction should evaluate to the same value as the input instruction. /// The output instruction should have the same side-effects (incl. exceptions being thrown) as the input instruction. /// This means unlike LiftNormal(), we cannot rely on the input instruction not being evaluated if /// a variable is <c>null</c>. /// </summary> Comp LiftCSharpComparison(Comp comp, ComparisonKind newComparisonKind) { var(left, leftBits) = DoLift(comp.Left); var(right, rightBits) = DoLift(comp.Right); if (left != null && right == null && SemanticHelper.IsPure(comp.Right.Flags)) { // Embed non-nullable pure expression in lifted expression. right = comp.Right.Clone(); } if (left == null && right != null && SemanticHelper.IsPure(comp.Left.Flags)) { // Embed non-nullable pure expression in lifted expression. left = comp.Left.Clone(); } // due to the restrictions on side effects, we only allow instructions that are pure after lifting. // (we can't check this before lifting due to the calls to GetValueOrDefault()) if (left != null && right != null && SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags)) { var bits = leftBits ?? rightBits; if (rightBits != null) { bits.UnionWith(rightBits); } if (!bits.All(0, nullableVars.Count)) { // don't lift if a nullableVar doesn't contribute to the result return(null); } context.Step("NullableLiftingTransform: C# comparison", comp); return(new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, comp.InputType, comp.Sign, left, right)); } return(null); }
/// <summary> /// Unwraps a nested BlockContainer, if container contains only a single block, /// and that single block contains only a BlockContainer followed by a Leave instruction. /// If the leave instruction is a return that carries a value, the container is unwrapped only /// if the value has no side-effects. /// Otherwise returns the unmodified container. /// </summary> /// <param name="optionalReturnInst">If the leave is a return and has no side-effects, we can move the return out of the using-block and put it after the loop, otherwise returns null.</param> BlockContainer UnwrapNestedContainerIfPossible(BlockContainer container, out Leave optionalReturnInst) { optionalReturnInst = null; // Check block structure: if (container.Blocks.Count != 1) { return(container); } var nestedBlock = container.Blocks[0]; if (nestedBlock.Instructions.Count != 2 || !(nestedBlock.Instructions[0] is BlockContainer nestedContainer) || !(nestedBlock.Instructions[1] is Leave leave)) { return(container); } // If the leave has no value, just unwrap the BlockContainer. if (leave.MatchLeave(container)) { return(nestedContainer); } // If the leave is a return, we can move the return out of the using-block and put it after the loop // (but only if the value doesn't have side-effects) if (leave.IsLeavingFunction && SemanticHelper.IsPure(leave.Value.Flags)) { optionalReturnInst = leave; return(nestedContainer); } return(container); }
/// <summary> /// Gets whether 'inst' is a possible store for use as a compound store. /// </summary> bool IsCompoundStore(ILInstruction inst, out IType storeType, out ILInstruction value) { value = null; storeType = null; if (inst is StObj stobj) { storeType = stobj.Type; value = stobj.Value; return(SemanticHelper.IsPure(stobj.Target.Flags)); } else if (inst is CallInstruction call && (call.OpCode == OpCode.Call || call.OpCode == OpCode.CallVirt)) { if (call.Method.Parameters.Count == 0) { return(false); } foreach (var arg in call.Arguments.SkipLast(1)) { if (!SemanticHelper.IsPure(arg.Flags)) { return(false); } } storeType = call.Method.Parameters.Last().Type; value = call.Arguments.Last(); return(IsSameMember(call.Method, (call.Method.AccessorOwner as IProperty)?.Setter)); }
/// <summary> /// newobj Delegate..ctor(target, ldvirtftn TargetMethod(target)) /// => /// ldvirtdelegate System.Delegate TargetMethod(target) /// </summary> bool TransformDelegateCtorLdVirtFtnToLdVirtDelegate(NewObj inst, out LdVirtDelegate ldVirtDelegate) { ldVirtDelegate = null; if (inst.Method.DeclaringType.Kind != TypeKind.Delegate) { return(false); } if (inst.Arguments.Count != 2) { return(false); } if (!(inst.Arguments[1] is LdVirtFtn ldVirtFtn)) { return(false); } if (!SemanticHelper.IsPure(inst.Arguments[0].Flags)) { return(false); } if (!inst.Arguments[0].Match(ldVirtFtn.Argument).Success) { return(false); } ldVirtDelegate = new LdVirtDelegate(inst.Arguments[0], inst.Method.DeclaringType, ldVirtFtn.Method) .WithILRange(inst).WithILRange(ldVirtFtn).WithILRange(ldVirtFtn.Argument); return(true); }
static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall) { if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner)) { return(false); } var owner = getterCall.Method.AccessorOwner as IProperty; if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter)) { return(false); } if (setterCall.Arguments.Count != getterCall.Arguments.Count + 1) { return(false); } // Ensure that same arguments are passed to getterCall and setterCall: for (int j = 0; j < getterCall.Arguments.Count; j++) { if (!SemanticHelper.IsPure(getterCall.Arguments[j].Flags)) { return(false); } if (!getterCall.Arguments[j].Match(setterCall.Arguments[j]).Success) { return(false); } } return(true); }
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(ILFunction function, ILTransformContext context) { foreach (var container in function.Descendants.OfType <BlockContainer>()) { context.CancellationToken.ThrowIfCancellationRequested(); SplitBlocksAtWritesToPinnedLocals(container); DetectNullSafeArrayToPointer(container); foreach (var block in container.Blocks) { CreatePinnedRegion(block); } container.Blocks.RemoveAll(b => b.Instructions.Count == 0); // remove dummy blocks } // Sometimes there's leftover writes to the original pinned locals foreach (var block in function.Descendants.OfType <Block>()) { context.CancellationToken.ThrowIfCancellationRequested(); for (int i = 0; i < block.Instructions.Count; i++) { var stloc = block.Instructions[i] as StLoc; if (stloc != null && stloc.Variable.Kind == VariableKind.PinnedLocal && stloc.Variable.LoadCount == 0 && stloc.Variable.AddressCount == 0) { if (SemanticHelper.IsPure(stloc.Value.Flags)) { block.Instructions.RemoveAt(i--); } else { stloc.ReplaceWith(stloc.Value); } } } } }
protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst) { base.VisitBinaryNumericInstruction(inst); switch (inst.Operator) { case BinaryNumericOperator.ShiftLeft: case BinaryNumericOperator.ShiftRight: if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs) && rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31)) { // a << (b & 31) => a << b context.Step("Combine bit.and into shift", inst); inst.Right = lhs; } break; case BinaryNumericOperator.BitAnd: if (inst.Left.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean) && inst.Right.InferType(context.TypeSystem).IsKnownType(KnownTypeCode.Boolean)) { if (new NullableLiftingTransform(context).Run(inst)) { // e.g. "(a.GetValueOrDefault() == b.GetValueOrDefault()) & (a.HasValue & b.HasValue)" } else if (SemanticHelper.IsPure(inst.Right.Flags)) { context.Step("Replace bit.and with logic.and", inst); var expr = IfInstruction.LogicAnd(inst.Left, inst.Right); inst.ReplaceWith(expr); expr.AcceptVisitor(this); } } break; } }
static void RunOnBlock(Block block, ILTransformContext context, HashSet <ILVariable> splitVariables = null) { for (int i = 0; i < block.Instructions.Count; i++) { if (block.Instructions[i].MatchStLoc(out ILVariable v, out ILInstruction copiedExpr)) { if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) { // dead store to stack if (SemanticHelper.IsPure(copiedExpr.Flags)) { // no-op -> delete context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]); block.Instructions.RemoveAt(i); // This can open up new inlining opportunities: int c = ILInlining.InlineInto(block, i, InliningOptions.None, context: context); i -= c + 1; } else { // evaluate the value for its side-effects context.Step("remove dead store to stack: evaluate the value for its side-effects", block.Instructions[i]); copiedExpr.AddILRange(block.Instructions[i]); block.Instructions[i] = copiedExpr; } } else if (v.IsSingleDefinition && CanPerformCopyPropagation(v, copiedExpr, splitVariables)) { DoPropagate(v, copiedExpr, block, ref i, context); } } } }
void IILTransform.Run(ILFunction function, ILTransformContext context) { var instructionsToFix = new List <IsInst>(); foreach (var isInst in function.Descendants.OfType <IsInst>()) { if (isInst.Type.IsReferenceType == true) { continue; // reference-type isinst is always supported } if (SemanticHelper.IsPure(isInst.Argument.Flags)) { continue; // emulated via "expr is T ? (T)expr : null" } if (isInst.Parent is UnboxAny unboxAny && ExpressionBuilder.IsUnboxAnyWithIsInst(unboxAny, isInst)) { continue; // supported pattern "expr as T?" } if (isInst.Parent.MatchCompEqualsNull(out _) || isInst.Parent.MatchCompNotEqualsNull(out _)) { continue; // supported pattern "expr is T" } if (isInst.Parent is Block { Kind: BlockKind.ControlFlow })
protected internal override void VisitBinaryNumericInstruction(BinaryNumericInstruction inst) { base.VisitBinaryNumericInstruction(inst); switch (inst.Operator) { case BinaryNumericOperator.ShiftLeft: case BinaryNumericOperator.ShiftRight: if (inst.Right.MatchBinaryNumericInstruction(BinaryNumericOperator.BitAnd, out var lhs, out var rhs) && rhs.MatchLdcI4(inst.ResultType == StackType.I8 ? 63 : 31)) { // a << (b & 31) => a << b context.Step("Combine bit.and into shift", inst); inst.Right = lhs; } break; case BinaryNumericOperator.BitAnd: if (IsBoolean(inst.Left) && IsBoolean(inst.Right) && SemanticHelper.IsPure(inst.Right.Flags)) { context.Step("Replace bit.and with logic.and", inst); var expr = IfInstruction.LogicAnd(inst.Left, inst.Right); inst.ReplaceWith(expr); expr.AcceptVisitor(this); } break; } }
Comp LiftCSharpEqualityComparison(Comp valueComp, ComparisonKind newComparisonKind, ILInstruction hasValueTest) { Debug.Assert(newComparisonKind.IsEqualityOrInequality()); bool hasValueTestNegated = false; while (hasValueTest.MatchLogicNot(out var arg)) { hasValueTest = arg; hasValueTestNegated = !hasValueTestNegated; } // The HasValue comparison must be the same operator as the Value comparison. if (hasValueTest is Comp hasValueComp) { // Comparing two nullables: HasValue comparison must be the same operator as the Value comparison if ((hasValueTestNegated ? hasValueComp.Kind.Negate() : hasValueComp.Kind) != newComparisonKind) { return(null); } if (!MatchHasValueCall(hasValueComp.Left, out var leftVar)) { return(null); } if (!MatchHasValueCall(hasValueComp.Right, out var rightVar)) { return(null); } nullableVars = new List <ILVariable> { leftVar }; var(left, leftBits) = DoLift(valueComp.Left); nullableVars[0] = rightVar; var(right, rightBits) = DoLift(valueComp.Right); if (left != null && right != null && leftBits[0] && rightBits[0] && SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags) ) { context.Step("NullableLiftingTransform: C# (in)equality comparison", valueComp); return(new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, valueComp.InputType, valueComp.Sign, left, right)); } } else if (newComparisonKind == ComparisonKind.Equality && !hasValueTestNegated && MatchHasValueCall(hasValueTest, out var v)) { // Comparing nullable with non-nullable -> we can fall back to the normal comparison code. nullableVars = new List <ILVariable> { v }; return(LiftCSharpComparison(valueComp, newComparisonKind)); } else if (newComparisonKind == ComparisonKind.Inequality && hasValueTestNegated && MatchHasValueCall(hasValueTest, out v)) { // Comparing nullable with non-nullable -> we can fall back to the normal comparison code. nullableVars = new List <ILVariable> { v }; return(LiftCSharpComparison(valueComp, newComparisonKind)); } return(null); }
public void Run(ILFunction function, ILTransformContext context) { var visitor = new DefiniteAssignmentVisitor(function, context.CancellationToken); function.Body.AcceptVisitor(visitor); foreach (var v in function.Variables) { if (v.Kind != VariableKind.Parameter && !visitor.IsPotentiallyUsedUninitialized(v)) { v.HasInitialValue = false; } } // Remove dead stores to variables that are never read from. // If the stored value has some side-effect, the value is unwrapped. // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler. // In yield return + async, the C# compiler tends to store null/default(T) to variables // when the variable goes out of scope. if (function.IsAsync || function.IsIterator || context.Settings.RemoveDeadCode) { var variableQueue = new Queue <ILVariable>(function.Variables); while (variableQueue.Count > 0) { var v = variableQueue.Dequeue(); if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot) { continue; } if (v.LoadCount != 0 || v.AddressCount != 0) { continue; } foreach (var stloc in v.StoreInstructions.OfType <StLoc>().ToArray()) { if (stloc.Parent is Block block) { if (SemanticHelper.IsPure(stloc.Value.Flags)) { block.Instructions.Remove(stloc); } else { stloc.ReplaceWith(stloc.Value); } if (stloc.Value is LdLoc ldloc) { variableQueue.Enqueue(ldloc.Variable); } } } } } }
/// <summary> Drops the result value. The result expression is still executed, though. </summary> public StatementBlock AsVoid() { if (IsVoid) { return(this); } else if (SemanticHelper.IsPure(Instruction.Flags)) { return(new StatementBlock(Statements)); } else { return(new StatementBlock(Statements.Add(new ExpressionStatement(Instr())))); } }
/// <summary> /// Lift a C# comparison. /// This method cannot be used for (in)equality comparisons where both sides are nullable /// (these special cases are handled in LiftCSharpEqualityComparison instead). /// /// The output instructions should evaluate to <c>false</c> when any of the <c>nullableVars</c> is <c>null</c> /// (except for newComparisonKind==Inequality, where this case should evaluate to <c>true</c> instead). /// Otherwise, the output instruction should evaluate to the same value as the input instruction. /// The output instruction should have the same side-effects (incl. exceptions being thrown) as the input instruction. /// This means unlike LiftNormal(), we cannot rely on the input instruction not being evaluated if /// a variable is <c>null</c>. /// </summary> Comp LiftCSharpComparison(Comp comp, ComparisonKind newComparisonKind) { var(left, right, bits) = DoLiftBinary(comp.Left, comp.Right); // due to the restrictions on side effects, we only allow instructions that are pure after lifting. // (we can't check this before lifting due to the calls to GetValueOrDefault()) if (left != null && right != null && SemanticHelper.IsPure(left.Flags) && SemanticHelper.IsPure(right.Flags)) { if (!bits.All(0, nullableVars.Count)) { // don't lift if a nullableVar doesn't contribute to the result return(null); } context.Step("NullableLiftingTransform: C# comparison", comp); return(new Comp(newComparisonKind, ComparisonLiftingKind.CSharp, comp.InputType, comp.Sign, left, right)); } return(null); }
/// <summary> /// stobj(target, binary.op(ldobj(target), ...)) /// where target is pure /// => compound.op(target, ...) /// </summary> /// <remarks> /// Called by ExpressionTransforms. /// </remarks> internal static bool HandleStObjCompoundAssign(StObj inst, ILTransformContext context) { if (!(UnwrapSmallIntegerConv(inst.Value, out var conv) is BinaryNumericInstruction binary)) { return(false); } if (!(binary.Left is LdObj ldobj)) { return(false); } if (!inst.Target.Match(ldobj.Target).Success) { return(false); } if (!SemanticHelper.IsPure(ldobj.Target.Flags)) { return(false); } // ldobj.Type may just be 'int' (due to ldind.i4) when we're actually operating on a 'ref MyEnum'. // Try to determine the real type of the object we're modifying: IType targetType = ldobj.Target.InferType(); if (targetType.Kind == TypeKind.Pointer || targetType.Kind == TypeKind.ByReference) { targetType = ((TypeWithElementType)targetType).ElementType; if (targetType.Kind == TypeKind.Unknown || targetType.GetSize() != ldobj.Type.GetSize()) { targetType = ldobj.Type; } } else { targetType = ldobj.Type; } if (!ValidateCompoundAssign(binary, conv, targetType)) { return(false); } context.Step("compound assignment", inst); inst.ReplaceWith(new CompoundAssignmentInstruction( binary, binary.Left, binary.Right, targetType, CompoundAssignmentType.EvaluatesToNewValue)); return(true); }
/// <summary> /// dynamic.setindex.compound(target, index, dynamic.binary.operator op(dynamic.getindex(target, index), value)) /// => /// dynamic.compound.op (dynamic.getindex(target, index), value) /// </summary> protected internal override void VisitDynamicSetIndexInstruction(DynamicSetIndexInstruction inst) { base.VisitDynamicSetIndexInstruction(inst); if (!inst.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment)) { return; } if (!(inst.Arguments.LastOrDefault() is DynamicBinaryOperatorInstruction binaryOp)) { return; } if (!(binaryOp.Left is DynamicGetIndexInstruction dynamicGetIndex)) { return; } if (inst.Arguments.Count != dynamicGetIndex.Arguments.Count + 1) { return; } // Ensure that same arguments are passed to dynamicGetIndex and inst: for (int j = 0; j < dynamicGetIndex.Arguments.Count; j++) { if (!SemanticHelper.IsPure(dynamicGetIndex.Arguments[j].Flags)) { return; } if (!dynamicGetIndex.Arguments[j].Match(inst.Arguments[j]).Success) { return; } } if (!DynamicCompoundAssign.IsExpressionTypeSupported(binaryOp.Operation)) { return; } context.Step("dynamic.setindex.compound -> dynamic.compound.op", inst); inst.ReplaceWith(new DynamicCompoundAssign(binaryOp.Operation, binaryOp.BinderFlags, binaryOp.Left, binaryOp.LeftArgumentInfo, binaryOp.Right, binaryOp.RightArgumentInfo)); }
/// <summary> /// Inlines the stloc instruction at block.Instructions[pos] into the next instruction. /// /// Note that this method does not check whether 'v' has only one use; /// the caller is expected to validate whether inlining 'v' has any effects on other uses of 'v'. /// </summary> public static bool InlineOne(StLoc stloc, InliningOptions options, ILTransformContext context) { ILVariable v = stloc.Variable; Block block = (Block)stloc.Parent; int pos = stloc.ChildIndex; if (DoInline(v, stloc.Value, block.Instructions.ElementAtOrDefault(pos + 1), options, context)) { // Assign the ranges of the stloc instruction: stloc.Value.AddILRange(stloc); // Remove the stloc instruction: Debug.Assert(block.Instructions[pos] == stloc); block.Instructions.RemoveAt(pos); return(true); } else if (v.LoadCount == 0 && v.AddressCount == 0) { // The variable is never loaded if (SemanticHelper.IsPure(stloc.Value.Flags)) { // Remove completely if the instruction has no effects // (except for reading locals) context.Step("Remove dead store without side effects", stloc); block.Instructions.RemoveAt(pos); return(true); } else if (v.Kind == VariableKind.StackSlot) { context.Step("Remove dead store, but keep expression", stloc); // Assign the ranges of the stloc instruction: stloc.Value.AddILRange(stloc); // Remove the stloc, but keep the inner expression stloc.ReplaceWith(stloc.Value); return(true); } } return(false); }
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)); } }
static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall, out Action <ILTransformContext> finalizeMatch) { finalizeMatch = null; if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner)) { return(false); } if (setterCall.OpCode != getterCall.OpCode) { return(false); } var owner = getterCall.Method.AccessorOwner as IProperty; if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter)) { return(false); } if (setterCall.Arguments.Count != getterCall.Arguments.Count + 1) { return(false); } // Ensure that same arguments are passed to getterCall and setterCall: for (int j = 0; j < getterCall.Arguments.Count; j++) { if (setterCall.Arguments[j].MatchStLoc(out var v) && v.IsSingleDefinition && v.LoadCount == 1) { if (getterCall.Arguments[j].MatchLdLoc(v)) { // OK, setter call argument is saved in temporary that is re-used for getter call if (finalizeMatch == null) { finalizeMatch = AdjustArguments; } continue; } } if (!SemanticHelper.IsPure(getterCall.Arguments[j].Flags)) { return(false); } if (!getterCall.Arguments[j].Match(setterCall.Arguments[j]).Success) { return(false); } } return(true); void AdjustArguments(ILTransformContext context) { Debug.Assert(setterCall.Arguments.Count == getterCall.Arguments.Count + 1); for (int j = 0; j < getterCall.Arguments.Count; j++) { if (setterCall.Arguments[j].MatchStLoc(out var v, out var value)) { Debug.Assert(v.IsSingleDefinition && v.LoadCount == 1); Debug.Assert(getterCall.Arguments[j].MatchLdLoc(v)); getterCall.Arguments[j] = value; } } } }
/// <summary> /// Matches Roslyn C# switch on nullable. /// </summary> bool MatchRoslynSwitchOnNullable(InstructionCollection <ILInstruction> instructions, int i, out SwitchInstruction newSwitch) { newSwitch = null; // match first block: // if (logic.not(call get_HasValue(target))) br nullCaseBlock // br switchBlock if (!instructions[i].MatchIfInstruction(out var condition, out var trueInst)) { return(false); } if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock)) { return(false); } if (!condition.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction target) || !SemanticHelper.IsPure(target.Flags)) { return(false); } // match second block: switchBlock // note: I have seen cases where switchVar is inlined into the switch. // stloc switchVar(call GetValueOrDefault(ldloca tmp)) // switch (ldloc switchVar) { // case [0..1): br caseBlock1 // ... more cases ... // case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock // } if (switchBlock.IncomingEdgeCount != 1) { return(false); } SwitchInstruction switchInst; switch (switchBlock.Instructions.Count) { case 2: { // this is the normal case described by the pattern above if (!switchBlock.Instructions[0].MatchStLoc(out var switchVar, out var getValueOrDefault)) { return(false); } if (!switchVar.IsSingleDefinition || switchVar.LoadCount != 1) { return(false); } if (!(NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, out ILInstruction target2) && target2.Match(target).Success)) { return(false); } if (!(switchBlock.Instructions[1] is SwitchInstruction si)) { return(false); } switchInst = si; break; } case 1: { // this is the special case where `call GetValueOrDefault(ldloca tmp)` is inlined into the switch. if (!(switchBlock.Instructions[0] is SwitchInstruction si)) { return(false); } if (!(NullableLiftingTransform.MatchGetValueOrDefault(si.Value, out ILInstruction target2) && target2.Match(target).Success)) { return(false); } switchInst = si; break; } default: { return(false); } } ILInstruction switchValue; if (target.MatchLdLoca(out var v)) { switchValue = new LdLoc(v).WithILRange(target); } else { switchValue = new LdObj(target, ((CallInstruction)getHasValue).Method.DeclaringType); } newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, switchValue); return(true); }
public void Run(ILFunction function, ILTransformContext context) { ResetHasInitialValueFlag(function, context); // Remove dead stores to variables that are never read from. // If the stored value has some side-effect, the value is unwrapped. // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler. // In yield return + async, the C# compiler tends to store null/default(T) to variables // when the variable goes out of scope. if (function.IsAsync || function.IsIterator || context.Settings.RemoveDeadStores) { var variableQueue = new Queue <ILVariable>(function.Variables); while (variableQueue.Count > 0) { var v = variableQueue.Dequeue(); if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot) { continue; } // Skip variables that are captured in a mcs yield state-machine // loads of these will only be visible after DelegateConstruction step. if (function.StateMachineCompiledWithMono && v.StateMachineField != null) { continue; } if (v.LoadCount != 0 || v.AddressCount != 0) { continue; } foreach (var stloc in v.StoreInstructions.OfType <StLoc>().ToArray()) { if (stloc.Parent is Block block) { if (SemanticHelper.IsPure(stloc.Value.Flags)) { block.Instructions.Remove(stloc); } else { stloc.ReplaceWith(stloc.Value); } if (stloc.Value is LdLoc ldloc) { variableQueue.Enqueue(ldloc.Variable); } } } } } // Try to infer IType of stack slots that are of StackType.Ref: foreach (var v in function.Variables) { if (v.Kind == VariableKind.StackSlot && v.StackType == StackType.Ref && v.AddressCount == 0) { IType newType = null; // Multiple store are possible in case of (c ? ref a : ref b) += 1, for example. foreach (var stloc in v.StoreInstructions.OfType <StLoc>()) { var inferredType = stloc.Value.InferType(context.TypeSystem); // cancel, if types of values do not match exactly if (newType != null && !newType.Equals(inferredType)) { newType = SpecialType.UnknownType; break; } newType = inferredType; } // Only overwrite existing type, if a "better" type was found. if (newType != null && newType != SpecialType.UnknownType) { v.Type = newType; } } } }
/// <summary> /// op is either add or remove/subtract: /// if (dynamic.isevent (target)) { /// dynamic.invokemember.invokespecial.discard op_Name(target, value) /// } else { /// dynamic.compound.op (dynamic.getmember Name(target), value) /// } /// => /// dynamic.compound.op (dynamic.getmember Name(target), value) /// </summary> bool TransformDynamicAddAssignOrRemoveAssign(IfInstruction inst) { if (!inst.MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst)) { return(false); } if (!(condition is DynamicIsEventInstruction isEvent)) { return(false); } trueInst = Block.Unwrap(trueInst); falseInst = Block.Unwrap(falseInst); if (!(falseInst is DynamicCompoundAssign dynamicCompoundAssign)) { return(false); } if (!(dynamicCompoundAssign.Target is DynamicGetMemberInstruction getMember)) { return(false); } if (!SemanticHelper.IsPure(isEvent.Argument.Flags)) { return(false); } if (!isEvent.Argument.Match(getMember.Target).Success) { return(false); } if (!(trueInst is DynamicInvokeMemberInstruction invokeMember)) { return(false); } if (!(invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.InvokeSpecialName) && invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.ResultDiscarded))) { return(false); } switch (dynamicCompoundAssign.Operation) { case ExpressionType.AddAssign: if (invokeMember.Name != "add_" + getMember.Name) { return(false); } break; case ExpressionType.SubtractAssign: if (invokeMember.Name != "remove_" + getMember.Name) { return(false); } break; default: return(false); } if (!dynamicCompoundAssign.Value.Match(invokeMember.Arguments[1]).Success) { return(false); } if (!invokeMember.Arguments[0].Match(getMember.Target).Success) { return(false); } context.Step("+= / -= dynamic.isevent pattern -> dynamic.compound.op", inst); inst.ReplaceWith(dynamicCompoundAssign); return(true); }
/// <summary> /// Recursive function that lifts the specified instruction. /// The input instruction is expected to a subexpression of the trueInst /// (so that all nullableVars are guaranteed non-null within this expression). /// /// Creates a new lifted instruction without modifying the input instruction. /// On success, returns (new lifted instruction, bitset). /// If lifting fails, returns (null, null). /// /// The returned bitset specifies which nullableVars were considered "relevant" for this instruction. /// bitSet[i] == true means nullableVars[i] was relevant. /// /// The new lifted instruction will have equivalent semantics to the input instruction /// if all relevant variables are non-null [except that the result will be wrapped in a Nullable{T} struct]. /// If any relevant variable is null, the new instruction is guaranteed to evaluate to <c>null</c> /// without having any other effect. /// </summary> (ILInstruction, BitSet) DoLift(ILInstruction inst) { if (MatchGetValueOrDefault(inst, out ILVariable inputVar)) { // n.GetValueOrDefault() lifted => n. BitSet foundIndices = new BitSet(nullableVars.Count); for (int i = 0; i < nullableVars.Count; i++) { if (nullableVars[i] == inputVar) { foundIndices[i] = true; } } if (foundIndices.Any()) { return(new LdLoc(inputVar) { ILRange = inst.ILRange }, foundIndices); } else { return(null, null); } } else if (inst is Conv conv) { var(arg, bits) = DoLift(conv.Argument); if (arg != null) { if (conv.HasFlag(InstructionFlags.MayThrow) && !bits.All(0, nullableVars.Count)) { // Cannot execute potentially-throwing instruction unless all // the nullableVars are arguments to the instruction // (thus causing it not to throw when any of them is null). return(null, null); } var newInst = new Conv(arg, conv.InputType, conv.InputSign, conv.TargetType, conv.CheckForOverflow, isLifted: true) { ILRange = conv.ILRange }; return(newInst, bits); } } else if (inst is BinaryNumericInstruction binary) { var(left, leftBits) = DoLift(binary.Left); var(right, rightBits) = DoLift(binary.Right); if (left != null && right == null && SemanticHelper.IsPure(binary.Right.Flags)) { // Embed non-nullable pure expression in lifted expression. right = binary.Right.Clone(); } if (left == null && right != null && SemanticHelper.IsPure(binary.Left.Flags)) { // Embed non-nullable pure expression in lifted expression. left = binary.Left.Clone(); } if (left != null && right != null) { var bits = leftBits ?? rightBits; if (rightBits != null) { bits.UnionWith(rightBits); } if (binary.HasFlag(InstructionFlags.MayThrow) && !bits.All(0, nullableVars.Count)) { // Cannot execute potentially-throwing instruction unless all // the nullableVars are arguments to the instruction // (thus causing it not to throw when any of them is null). return(null, null); } var newInst = new BinaryNumericInstruction( binary.Operator, left, right, binary.LeftInputType, binary.RightInputType, binary.CheckForOverflow, binary.Sign, isLifted: true ) { ILRange = binary.ILRange }; return(newInst, bits); } } return(null, null); }
/// <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: Debug.Assert(!inlineBlock.IsConnected); } return(true); } else { return(false); } }