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)" } } break; } }
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; } }
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; } }
static bool ValidateCompoundAssign(BinaryNumericInstruction binary, Conv conv, IType targetType) { if (!NumericCompoundAssign.IsBinaryCompatibleWithType(binary, targetType)) { return(false); } if (conv != null && !(conv.TargetType == targetType.ToPrimitiveType() && conv.CheckForOverflow == binary.CheckForOverflow)) { return(false); // conv does not match binary operation } return(true); }
/// <code> /// stloc s(binary(callvirt(getter), value)) /// callvirt (setter, ldloc s) /// (followed by single usage of s in next instruction) /// --> /// stloc s(compound.op.new(callvirt(getter), value)) /// </code> bool TransformInlineCompoundAssignmentCall(Block block, int i) { var mainStLoc = block.Instructions[i] as StLoc; // in some cases it can be a compiler-generated local if (mainStLoc == null || (mainStLoc.Variable.Kind != VariableKind.StackSlot && mainStLoc.Variable.Kind != VariableKind.Local)) { return(false); } BinaryNumericInstruction binary = mainStLoc.Value as BinaryNumericInstruction; ILVariable localVariable = mainStLoc.Variable; if (!localVariable.IsSingleDefinition) { return(false); } if (localVariable.LoadCount != 2) { return(false); } var getterCall = binary?.Left as CallInstruction; var setterCall = block.Instructions.ElementAtOrDefault(i + 1) as CallInstruction; if (!MatchingGetterAndSetterCalls(getterCall, setterCall)) { return(false); } if (!setterCall.Arguments.Last().MatchLdLoc(localVariable)) { return(false); } var next = block.Instructions.ElementAtOrDefault(i + 2); if (next == null) { return(false); } if (next.Descendants.Where(d => d.MatchLdLoc(localVariable)).Count() != 1) { return(false); } if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType)) { return(false); } context.Step($"Inline compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall); block.Instructions.RemoveAt(i + 1); // remove setter call binary.ReplaceWith(new CompoundAssignmentInstruction( binary, getterCall, binary.Right, getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue)); return(true); }
/// <summary> /// VS2017.8 / Roslyn 2.9 started optimizing some cases of /// "a.GetValueOrDefault() == b.GetValueOrDefault() && (a.HasValue & b.HasValue)" /// to /// "(a.GetValueOrDefault() == b.GetValueOrDefault()) & (a.HasValue & b.HasValue)" /// so this secondary entry point analyses logic.and as-if it was a short-circuiting &&. /// </summary> public bool Run(BinaryNumericInstruction bni) { Debug.Assert(!bni.IsLifted && bni.Operator == BinaryNumericOperator.BitAnd); // caller ensures that bni.Left/bni.Right are booleans var lifted = Lift(bni, bni.Left, bni.Right, new LdcI4(0)); if (lifted != null) { bni.ReplaceWith(lifted); return(true); } return(false); }
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; } }
/// <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 BitNot bitnot) { var(arg, bits) = DoLift(bitnot.Argument); if (arg != null) { var newInst = new BitNot(arg, isLifted: true, stackType: bitnot.ResultType) { ILRange = bitnot.ILRange }; return(newInst, bits); } } else if (inst is BinaryNumericInstruction binary) { var(left, right, bits) = DoLiftBinary(binary.Left, binary.Right); if (left != null && right != null) { 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); } } else if (inst is Comp comp && !comp.IsLifted && comp.Kind == ComparisonKind.Equality && MatchGetValueOrDefault(comp.Left, out ILVariable v) && NullableType.GetUnderlyingType(v.Type).IsKnownType(KnownTypeCode.Boolean) && comp.Right.MatchLdcI4(0) ) { // C# doesn't support ComparisonLiftingKind.ThreeValuedLogic, // except for operator! on bool?. var(arg, bits) = DoLift(comp.Left); Debug.Assert(arg != null); var newInst = new Comp(comp.Kind, ComparisonLiftingKind.ThreeValuedLogic, comp.InputType, comp.Sign, arg, comp.Right.Clone()) { ILRange = comp.ILRange }; return(newInst, bits); }
/// <code> /// stloc s(ldflda) /// stloc s2(ldobj(ldflda(ldloc s))) /// stloc l(ldloc s2) /// stobj (ldflda(ldloc s), binary.add(ldloc s2, ldc.i4 1)) /// --> /// stloc l(compound.op.old(ldobj(ldflda(ldflda)), ldc.i4 1)) /// </code> bool TransformCSharp4PostIncDecOperatorOnAddress(Block block, int i) { var baseFieldAddress = block.Instructions[i] as StLoc; var fieldValue = block.Instructions.ElementAtOrDefault(i + 1) as StLoc; var fieldValueCopyToLocal = block.Instructions.ElementAtOrDefault(i + 2) as StLoc; var stobj = block.Instructions.ElementAtOrDefault(i + 3) as StObj; if (baseFieldAddress == null || fieldValue == null || fieldValueCopyToLocal == null || stobj == null) { return(false); } if (baseFieldAddress.Variable.Kind != VariableKind.StackSlot || fieldValue.Variable.Kind != VariableKind.StackSlot || fieldValueCopyToLocal.Variable.Kind != VariableKind.Local) { return(false); } IType t; IField targetField; ILInstruction targetFieldLoad, baseFieldAddressLoad2; if (!fieldValue.Value.MatchLdObj(out targetFieldLoad, out t)) { return(false); } ILInstruction baseAddress; if (baseFieldAddress.Value is LdFlda) { IField targetField2; ILInstruction baseFieldAddressLoad3; if (!targetFieldLoad.MatchLdFlda(out baseFieldAddressLoad2, out targetField) || !baseFieldAddressLoad2.MatchLdLoc(baseFieldAddress.Variable)) { return(false); } if (!stobj.Target.MatchLdFlda(out baseFieldAddressLoad3, out targetField2) || !baseFieldAddressLoad3.MatchLdLoc(baseFieldAddress.Variable) || !IsSameMember(targetField, targetField2)) { return(false); } baseAddress = new LdFlda(baseFieldAddress.Value, targetField); } else if (baseFieldAddress.Value is LdElema) { if (!targetFieldLoad.MatchLdLoc(baseFieldAddress.Variable) || !stobj.Target.MatchLdLoc(baseFieldAddress.Variable)) { return(false); } baseAddress = baseFieldAddress.Value; } else { return(false); } BinaryNumericInstruction binary = stobj.Value as BinaryNumericInstruction; if (binary == null || !binary.Left.MatchLdLoc(fieldValue.Variable) || !binary.Right.MatchLdcI4(1) || (binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub)) { return(false); } context.Step($"TransformCSharp4PostIncDecOperatorOnAddress", baseFieldAddress); var assignment = new CompoundAssignmentInstruction(binary, new LdObj(baseAddress, t), binary.Right, t, CompoundAssignmentType.EvaluatesToOldValue); stobj.ReplaceWith(new StLoc(fieldValueCopyToLocal.Variable, assignment)); block.Instructions.RemoveAt(i + 2); block.Instructions.RemoveAt(i + 1); 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); }