bool MatchSwitchVar(ILInstruction inst) { if (switchVar != null) { return(inst.MatchLdLoc(switchVar)); } else { return(inst.MatchLdLoc(out switchVar)); } }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> static bool?FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst) { loadInst = null; if (expr == null) { return(false); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline loadInst = expr; return(true); } else if (expr is Block block && block.Instructions.Count > 0) { // Inlining into inline-blocks? only for some block types, and only into the first instruction. switch (block.Kind) { case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: return(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, out loadInst) ?? false); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. default: return(false); } }
bool IsDisplayClassLoad(ILInstruction target, out ILVariable variable) { if (target.MatchLdLoc(out variable) || target.MatchLdLoca(out variable)) { return(true); } return(false); }
private bool IsDisplayClassLoad(ILInstruction target, out ILVariable variable) { // We cannot use MatchLdLocRef here because local functions use ref parameters if (target.MatchLdLoc(out variable) || target.MatchLdLoca(out variable)) { return(true); } return(false); }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> static bool?FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst) { loadInst = null; if (expr == null) { return(false); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline loadInst = expr; return(true); } else if (expr is Block block && block.Instructions.Count > 0) { // Inlining into inline-blocks? only for some block types, and only into the first instruction. switch (block.Type) { case BlockType.ArrayInitializer: case BlockType.CollectionInitializer: case BlockType.ObjectInitializer: return(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, out loadInst) ?? false); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. default: return(false); } } foreach (var child in expr.Children) { if (!child.SlotInfo.CanInlineInto) { return(false); } // Recursively try to find the load instruction bool?r = FindLoadInNext(child, v, expressionBeingMoved, out loadInst); if (r != null) { return(r); } } if (IsSafeForInlineOver(expr, expressionBeingMoved)) { return(null); // continue searching } else { return(false); // abort, inlining not possible } }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> internal static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, InliningOptions options) { if (expr == null) { return(FindResult.Stop); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline if (expr.SlotInfo == StObj.TargetSlot && !((StObj)expr.Parent).CanInlineIntoTargetSlot(expressionBeingMoved)) { // special case: the StObj.TargetSlot does not accept some kinds of expressions return(FindResult.Stop); } return(FindResult.Found(expr)); } else if (expr is Block block) { // Inlining into inline-blocks? switch (block.Kind) { case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block if (block.Instructions.Count == 0) { return(FindResult.Stop); } return(NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. case BlockKind.CallWithNamedArgs: return(NamedArgumentTransform.CanExtendNamedArgument(block, v, expressionBeingMoved)); default: return(FindResult.Stop); } } else if (expr is BlockContainer container && container.EntryPoint.IncomingEdgeCount == 1) { // Possibly a switch-container, allow inlining into the switch instruction: return(NoContinue(FindLoadInNext(container.EntryPoint.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the blockcontainer. }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> internal static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst) { loadInst = null; if (expr == null) { return(FindResult.Stop); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline loadInst = expr; return(FindResult.Found); } else if (expr is Block block) { // Inlining into inline-blocks? switch (block.Kind) { case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block if (block.Instructions.Count == 0) { return(FindResult.Stop); } return(NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, out loadInst))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. case BlockKind.CallWithNamedArgs: return(NamedArgumentTransform.CanExtendNamedArgument(block, v, expressionBeingMoved, out loadInst)); default: return(FindResult.Stop); } } else if (expr is BlockContainer container && container.EntryPoint.IncomingEdgeCount == 1) { // Possibly a switch-container, allow inlining into the switch instruction: return(NoContinue(FindLoadInNext(container.EntryPoint.Instructions[0], v, expressionBeingMoved, out loadInst))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the blockcontainer. }
/// <summary> /// if (call op_False(ldloc lhsVar)) ldloc lhsVar else call op_BitwiseAnd(ldloc lhsVar, rhsInst) /// -> user.logic op_BitwiseAnd(ldloc lhsVar, rhsInst) /// or /// if (call op_True(ldloc lhsVar)) ldloc lhsVar else call op_BitwiseOr(ldloc lhsVar, rhsInst) /// -> user.logic op_BitwiseOr(ldloc lhsVar, rhsInst) /// </summary> public static ILInstruction Transform(ILInstruction condition, ILInstruction trueInst, ILInstruction falseInst) { if (!MatchCondition(condition, out var lhsVar, out var conditionMethodName)) { return(null); } if (!trueInst.MatchLdLoc(lhsVar)) { return(null); } var call = falseInst as Call; if (!MatchBitwiseCall(call, lhsVar, conditionMethodName)) { return(null); } var result = new UserDefinedLogicOperator(call.Method, call.Arguments[0], call.Arguments[1]); result.AddILRange(condition); result.AddILRange(trueInst); result.AddILRange(call); return(result); }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> static bool?FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, out ILInstruction loadInst) { loadInst = null; if (expr == null) { return(false); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline loadInst = expr; return(true); } foreach (var child in expr.Children) { if (!child.SlotInfo.CanInlineInto) { return(false); } // Recursively try to find the load instruction bool?r = FindLoadInNext(child, v, expressionBeingMoved, out loadInst); if (r != null) { return(r); } } if (IsSafeForInlineOver(expr, expressionBeingMoved)) { return(null); // continue searching } else { return(false); // abort, inlining not possible } }
/// <summary> /// Performs nullable lifting. /// /// Produces a lifted instruction with semantics equivalent to: /// (v1 != null && ... && vn != null) ? trueInst : falseInst, /// where the v1,...,vn are the <c>this.nullableVars</c>. /// If lifting fails, returns <c>null</c>. /// </summary> ILInstruction LiftNormal(ILInstruction trueInst, ILInstruction falseInst, Interval ilrange) { bool isNullCoalescingWithNonNullableFallback = false; if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) { isNullCoalescingWithNonNullableFallback = true; utype = context.TypeSystem.Compilation.FindType(trueInst.ResultType.ToKnownTypeCode()); exprToLift = trueInst; if (nullableVars.Count == 1 && exprToLift.MatchLdLoc(nullableVars[0])) { // v.HasValue ? ldloc v : fallback // => v ?? fallback context.Step("v.HasValue ? v : fallback => v ?? fallback", trueInst); return(new NullCoalescingInstruction(NullCoalescingKind.Nullable, trueInst, falseInst) { UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType(), ILRange = ilrange }); } else if (trueInst is Call call && !call.IsLifted && CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method) && call.Method.Name != "op_Equality" && call.Method.Name != "op_Inequality" && falseInst.MatchLdcI4(0)) { // (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0) var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); if (liftedOperator != null) { var(left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1]); if (left != null && right != null && bits.All(0, nullableVars.Count)) { return(new Call(liftedOperator) { Arguments = { left, right }, ConstrainedTo = call.ConstrainedTo, ILRange = call.ILRange, ILStackWasEmpty = call.ILStackWasEmpty, IsTail = call.IsTail }); } } } } ILInstruction lifted; if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) { // v.HasValue ? call GetValueOrDefault(ldloca v) : fallback // => conv.nop.lifted(ldloc v) ?? fallback // This case is handled separately from DoLift() because // that doesn't introduce nop-conversions. context.Step("v.HasValue ? v.GetValueOrDefault() : fallback => v ?? fallback", trueInst); var inputUType = NullableType.GetUnderlyingType(nullableVars[0].Type); lifted = new LdLoc(nullableVars[0]); if (!inputUType.Equals(utype) && utype.ToPrimitiveType() != PrimitiveType.None) { // While the ILAst allows implicit conversions between short and int // (because both map to I4); it does not allow implicit conversions // between short? and int? (structs of different types). // So use 'conv.nop.lifted' to allow the conversion. lifted = new Conv( lifted, inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), checkForOverflow: false, isLifted: true ) { ILRange = ilrange }; } } else { context.Step("NullableLiftingTransform.DoLift", trueInst); BitSet bits; (lifted, bits) = DoLift(exprToLift); if (lifted == null) { return(null); } if (!bits.All(0, nullableVars.Count)) { // don't lift if a nullableVar doesn't contribute to the result return(null); } Debug.Assert(lifted is ILiftableInstruction liftable && liftable.IsLifted && liftable.UnderlyingResultType == exprToLift.ResultType); } if (isNullCoalescingWithNonNullableFallback) { lifted = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, lifted, falseInst) { UnderlyingResultType = exprToLift.ResultType, ILRange = ilrange }; } else if (!MatchNull(falseInst, utype)) { // Normal lifting, but the falseInst isn't `default(utype?)` // => use the `??` operator to provide the fallback value. lifted = new NullCoalescingInstruction(NullCoalescingKind.Nullable, lifted, falseInst) { UnderlyingResultType = exprToLift.ResultType, ILRange = ilrange }; } return(lifted); }
ILInstruction Lift(IfInstruction ifInst, ILInstruction trueInst, ILInstruction falseInst) { ILInstruction condition = ifInst.Condition; while (condition.MatchLogicNot(out var arg)) { condition = arg; Swap(ref trueInst, ref falseInst); } if (AnalyzeCondition(condition)) { // (v1 != null && ... && vn != null) ? trueInst : falseInst // => normal lifting return(LiftNormal(trueInst, falseInst, ilrange: ifInst.ILRange)); } if (condition is Comp comp && !comp.IsLifted) { // This might be a C#-style lifted comparison // (C# checks the underlying value before checking the HasValue bits) if (comp.Kind.IsEqualityOrInequality()) { // for equality/inequality, the HasValue bits must also compare equal/inequal if (comp.Kind == ComparisonKind.Inequality) { // handle inequality by swapping one last time Swap(ref trueInst, ref falseInst); } if (falseInst.MatchLdcI4(0)) { // (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue == b.HasValue) : false // => a == b return(LiftCSharpEqualityComparison(comp, ComparisonKind.Equality, trueInst)); } else if (falseInst.MatchLdcI4(1)) { // (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue != b.HasValue) : true // => a != b return(LiftCSharpEqualityComparison(comp, ComparisonKind.Inequality, trueInst)); } else if (IsGenericNewPattern(condition, trueInst, falseInst)) { // (default(T) == null) ? Activator.CreateInstance<T>() : default(T) // => Activator.CreateInstance<T>() return(trueInst); } } else { // Not (in)equality, but one of < <= > >=. // Returns false unless all HasValue bits are true. if (falseInst.MatchLdcI4(0) && AnalyzeCondition(trueInst)) { // comp(lhs, rhs) ? (v1 != null && ... && vn != null) : false // => comp.lifted[C#](lhs, rhs) return(LiftCSharpComparison(comp, comp.Kind)); } else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) { // comp(lhs, rhs) ? false : (v1 != null && ... && vn != null) return(LiftCSharpComparison(comp, comp.Kind.Negate())); } } } ILVariable v; if (MatchGetValueOrDefault(condition, out v) && NullableType.GetUnderlyingType(v.Type).IsKnownType(KnownTypeCode.Boolean)) { if (MatchHasValueCall(trueInst, v) && falseInst.MatchLdcI4(0)) { // v.GetValueOrDefault() ? v.HasValue : false // ==> v == true context.Step("NullableLiftingTransform: v == true", ifInst); return(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp, StackType.I4, Sign.None, new LdLoc(v) { ILRange = trueInst.ILRange }, new LdcI4(1) { ILRange = falseInst.ILRange } ) { ILRange = ifInst.ILRange }); } else if (trueInst.MatchLdcI4(0) && MatchHasValueCall(falseInst, v)) { // v.GetValueOrDefault() ? false : v.HasValue // ==> v == false context.Step("NullableLiftingTransform: v == false", ifInst); return(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp, StackType.I4, Sign.None, new LdLoc(v) { ILRange = falseInst.ILRange }, trueInst // LdcI4(0) ) { ILRange = ifInst.ILRange }); } else if (MatchNegatedHasValueCall(trueInst, v) && falseInst.MatchLdcI4(1)) { // v.GetValueOrDefault() ? !v.HasValue : true // ==> v != true context.Step("NullableLiftingTransform: v != true", ifInst); return(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp, StackType.I4, Sign.None, new LdLoc(v) { ILRange = trueInst.ILRange }, falseInst // LdcI4(1) ) { ILRange = ifInst.ILRange }); } else if (trueInst.MatchLdcI4(1) && MatchNegatedHasValueCall(falseInst, v)) { // v.GetValueOrDefault() ? true : !v.HasValue // ==> v != false context.Step("NullableLiftingTransform: v != false", ifInst); return(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp, StackType.I4, Sign.None, new LdLoc(v) { ILRange = falseInst.ILRange }, new LdcI4(0) { ILRange = trueInst.ILRange } ) { ILRange = ifInst.ILRange }); } } if (trueInst.MatchLdLoc(out v)) { if (MatchNullableCtor(falseInst, out var utype, out var arg) && utype.IsKnownType(KnownTypeCode.Boolean) && arg.MatchLdcI4(0)) { // condition ? v : (bool?)false // => condition & v context.Step("NullableLiftingTransform: 3vl.logic.and(bool, bool?)", ifInst); return(new ThreeValuedLogicAnd(condition, trueInst) { ILRange = ifInst.ILRange }); } if (falseInst.MatchLdLoc(out var v2)) { // condition ? v : v2 if (MatchThreeValuedLogicConditionPattern(condition, out var nullable1, out var nullable2)) { // (nullable1.GetValueOrDefault() || (!nullable2.GetValueOrDefault() && !nullable1.HasValue)) ? v : v2 if (v == nullable1 && v2 == nullable2) { context.Step("NullableLiftingTransform: 3vl.logic.or(bool?, bool?)", ifInst); return(new ThreeValuedLogicOr(trueInst, falseInst) { ILRange = ifInst.ILRange }); } else if (v == nullable2 && v2 == nullable1) { context.Step("NullableLiftingTransform: 3vl.logic.and(bool?, bool?)", ifInst); return(new ThreeValuedLogicAnd(falseInst, trueInst) { ILRange = ifInst.ILRange }); } } } } else if (falseInst.MatchLdLoc(out v)) { if (MatchNullableCtor(trueInst, out var utype, out var arg) && utype.IsKnownType(KnownTypeCode.Boolean) && arg.MatchLdcI4(1)) { // condition ? (bool?)true : v // => condition | v context.Step("NullableLiftingTransform: 3vl.logic.or(bool, bool?)", ifInst); return(new ThreeValuedLogicOr(condition, falseInst) { ILRange = ifInst.ILRange }); } } return(null); }
/// <summary> /// Finds the position to inline to. /// </summary> /// <returns>true = found; false = cannot continue search; null = not found</returns> internal static FindResult FindLoadInNext(ILInstruction expr, ILVariable v, ILInstruction expressionBeingMoved, InliningOptions options) { if (expr == null) { return(FindResult.Stop); } if (expr.MatchLdLoc(v) || expr.MatchLdLoca(v)) { // Match found, we can inline if (expr.SlotInfo == StObj.TargetSlot && !((StObj)expr.Parent).CanInlineIntoTargetSlot(expressionBeingMoved)) { if ((options & InliningOptions.AllowChangingOrderOfEvaluationForExceptions) != 0) { // Intentionally change code semantics so that we can avoid a ref local if (expressionBeingMoved is LdFlda ldflda) { ldflda.DelayExceptions = true; } else if (expressionBeingMoved is LdElema ldelema) { ldelema.DelayExceptions = true; } } else { // special case: the StObj.TargetSlot does not accept some kinds of expressions return(FindResult.Stop); } } return(FindResult.Found(expr)); } else if (expr is Block block) { // Inlining into inline-blocks? switch (block.Kind) { case BlockKind.ControlFlow when block.Parent is BlockContainer: case BlockKind.ArrayInitializer: case BlockKind.CollectionInitializer: case BlockKind.ObjectInitializer: case BlockKind.CallInlineAssign: // Allow inlining into the first instruction of the block if (block.Instructions.Count == 0) { return(FindResult.Stop); } return(NoContinue(FindLoadInNext(block.Instructions[0], v, expressionBeingMoved, options))); // If FindLoadInNext() returns null, we still can't continue searching // because we can't inline over the remainder of the block. case BlockKind.CallWithNamedArgs: return(NamedArgumentTransform.CanExtendNamedArgument(block, v, expressionBeingMoved)); default: return(FindResult.Stop); } } else if (options.HasFlag(InliningOptions.FindDeconstruction) && expr is DeconstructInstruction di) { return(FindResult.Deconstruction(di)); } foreach (var child in expr.Children) { if (!expr.CanInlineIntoSlot(child.ChildIndex, expressionBeingMoved)) { return(FindResult.Stop); } // Recursively try to find the load instruction FindResult r = FindLoadInNext(child, v, expressionBeingMoved, options); if (r.Type != FindResultType.Continue) { if (r.Type == FindResultType.Stop && (options & InliningOptions.IntroduceNamedArguments) != 0 && expr is CallInstruction call) { return(NamedArgumentTransform.CanIntroduceNamedArgument(call, child, v, expressionBeingMoved)); } return(r); } } if (IsSafeForInlineOver(expr, expressionBeingMoved)) { return(FindResult.Continue); // continue searching } else { return(FindResult.Stop); // abort, inlining not possible } }
/// <summary> /// Main entry point for lifting; called by both the expression-transform /// and the block transform. /// </summary> ILInstruction Lift(ILInstruction ifInst, ILInstruction condition, ILInstruction trueInst, ILInstruction falseInst) { // ifInst is usually the IfInstruction to which condition belongs; // but can also be a BinaryNumericInstruction. while (condition.MatchLogicNot(out var arg)) { condition = arg; ExtensionMethods.Swap(ref trueInst, ref falseInst); } if (context.Settings.NullPropagation && !NullPropagationTransform.IsProtectedIfInst(ifInst as IfInstruction)) { var nullPropagated = new NullPropagationTransform(context) .Run(condition, trueInst, falseInst)?.WithILRange(ifInst); if (nullPropagated != null) { return(nullPropagated); } } if (!context.Settings.LiftNullables) { return(null); } if (AnalyzeCondition(condition)) { // (v1 != null && ... && vn != null) ? trueInst : falseInst // => normal lifting return(LiftNormal(trueInst, falseInst)?.WithILRange(ifInst)); } if (MatchCompOrDecimal(condition, out var comp)) { // This might be a C#-style lifted comparison // (C# checks the underlying value before checking the HasValue bits) if (comp.Kind.IsEqualityOrInequality()) { // for equality/inequality, the HasValue bits must also compare equal/inequal if (comp.Kind == ComparisonKind.Inequality) { // handle inequality by swapping one last time ExtensionMethods.Swap(ref trueInst, ref falseInst); } if (falseInst.MatchLdcI4(0)) { // (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue == b.HasValue) : false // => a == b return(LiftCSharpEqualityComparison(comp, ComparisonKind.Equality, trueInst) ?? LiftCSharpUserEqualityComparison(comp, ComparisonKind.Equality, trueInst)); } else if (falseInst.MatchLdcI4(1)) { // (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue != b.HasValue) : true // => a != b return(LiftCSharpEqualityComparison(comp, ComparisonKind.Inequality, trueInst) ?? LiftCSharpUserEqualityComparison(comp, ComparisonKind.Inequality, trueInst)); } else if (IsGenericNewPattern(comp.Left, comp.Right, trueInst, falseInst)) { // (default(T) == null) ? Activator.CreateInstance<T>() : default(T) // => Activator.CreateInstance<T>() return(trueInst); } } else { // Not (in)equality, but one of < <= > >=. // Returns false unless all HasValue bits are true. if (falseInst.MatchLdcI4(0) && AnalyzeCondition(trueInst)) { // comp(lhs, rhs) ? (v1 != null && ... && vn != null) : false // => comp.lifted[C#](lhs, rhs) return(LiftCSharpComparison(comp, comp.Kind)); } else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) { // comp(lhs, rhs) ? false : (v1 != null && ... && vn != null) return(LiftCSharpComparison(comp, comp.Kind.Negate())); } } } ILVariable v; // Handle equality comparisons with bool?: if (MatchGetValueOrDefault(condition, out v) && NullableType.GetUnderlyingType(v.Type).IsKnownType(KnownTypeCode.Boolean)) { if (MatchHasValueCall(trueInst, v) && falseInst.MatchLdcI4(0)) { // v.GetValueOrDefault() ? v.HasValue : false // ==> v == true context.Step("NullableLiftingTransform: v == true", ifInst); return(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp, StackType.I4, Sign.None, new LdLoc(v).WithILRange(trueInst), new LdcI4(1).WithILRange(falseInst) ).WithILRange(ifInst)); } else if (trueInst.MatchLdcI4(0) && MatchHasValueCall(falseInst, v)) { // v.GetValueOrDefault() ? false : v.HasValue // ==> v == false context.Step("NullableLiftingTransform: v == false", ifInst); return(new Comp(ComparisonKind.Equality, ComparisonLiftingKind.CSharp, StackType.I4, Sign.None, new LdLoc(v).WithILRange(falseInst), trueInst // LdcI4(0) ).WithILRange(ifInst)); } else if (MatchNegatedHasValueCall(trueInst, v) && falseInst.MatchLdcI4(1)) { // v.GetValueOrDefault() ? !v.HasValue : true // ==> v != true context.Step("NullableLiftingTransform: v != true", ifInst); return(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp, StackType.I4, Sign.None, new LdLoc(v).WithILRange(trueInst), falseInst // LdcI4(1) ).WithILRange(ifInst)); } else if (trueInst.MatchLdcI4(1) && MatchNegatedHasValueCall(falseInst, v)) { // v.GetValueOrDefault() ? true : !v.HasValue // ==> v != false context.Step("NullableLiftingTransform: v != false", ifInst); return(new Comp(ComparisonKind.Inequality, ComparisonLiftingKind.CSharp, StackType.I4, Sign.None, new LdLoc(v).WithILRange(falseInst), new LdcI4(0).WithILRange(trueInst) ).WithILRange(ifInst)); } } // Handle & and | on bool?: if (trueInst.MatchLdLoc(out v)) { if (MatchNullableCtor(falseInst, out var utype, out var arg) && utype.IsKnownType(KnownTypeCode.Boolean) && arg.MatchLdcI4(0)) { // condition ? v : (bool?)false // => condition & v context.Step("NullableLiftingTransform: 3vl.bool.and(bool, bool?)", ifInst); return(new ThreeValuedBoolAnd(condition, trueInst).WithILRange(ifInst)); } if (falseInst.MatchLdLoc(out var v2)) { // condition ? v : v2 if (MatchThreeValuedLogicConditionPattern(condition, out var nullable1, out var nullable2)) { // (nullable1.GetValueOrDefault() || (!nullable2.GetValueOrDefault() && !nullable1.HasValue)) ? v : v2 if (v == nullable1 && v2 == nullable2) { context.Step("NullableLiftingTransform: 3vl.bool.or(bool?, bool?)", ifInst); return(new ThreeValuedBoolOr(trueInst, falseInst).WithILRange(ifInst)); } else if (v == nullable2 && v2 == nullable1) { context.Step("NullableLiftingTransform: 3vl.bool.and(bool?, bool?)", ifInst); return(new ThreeValuedBoolAnd(falseInst, trueInst).WithILRange(ifInst)); } } } } else if (falseInst.MatchLdLoc(out v)) { if (MatchNullableCtor(trueInst, out var utype, out var arg) && utype.IsKnownType(KnownTypeCode.Boolean) && arg.MatchLdcI4(1)) { // condition ? (bool?)true : v // => condition | v context.Step("NullableLiftingTransform: 3vl.logic.or(bool, bool?)", ifInst); return(new ThreeValuedBoolOr(condition, falseInst).WithILRange(ifInst)); } } return(null); }
bool MatchesTargetOrCopyLoad(ILInstruction inst) { return(targetAndCopies.Any(v => inst.MatchLdLoc(v))); }
/// <summary> /// Block entryPoint (incoming: 1) { /// stloc temp(isinst exceptionType(ldloc exceptionVar)) /// if (comp(ldloc temp != ldnull)) br whenConditionBlock /// br falseBlock /// } /// </summary> bool MatchCatchWhenEntryPoint(ILVariable exceptionVar, BlockContainer container, Block entryPoint, out IType exceptionType, out ILInstruction exceptionSlot, out Block whenConditionBlock) { exceptionType = null; exceptionSlot = null; whenConditionBlock = null; if (entryPoint == null || entryPoint.IncomingEdgeCount != 1) { return(false); } if (entryPoint.Instructions.Count == 3) { // stloc temp(isinst exceptionType(ldloc exceptionVar)) // if (comp(ldloc temp != ldnull)) br whenConditionBlock // br falseBlock if (!entryPoint.Instructions[0].MatchStLoc(out var temp, out var isinst) || temp.Kind != VariableKind.StackSlot || !isinst.MatchIsInst(out exceptionSlot, out exceptionType)) { return(false); } if (!exceptionSlot.MatchLdLoc(exceptionVar)) { return(false); } if (!entryPoint.Instructions[1].MatchIfInstruction(out var condition, out var branch)) { return(false); } if (!condition.MatchCompNotEquals(out var left, out var right)) { return(false); } if (!entryPoint.Instructions[2].MatchBranch(out var falseBlock) || !MatchFalseBlock(container, falseBlock, out var returnVar, out var exitBlock)) { return(false); } if ((left.MatchLdNull() && right.MatchLdLoc(temp)) || (right.MatchLdNull() && left.MatchLdLoc(temp))) { return(branch.MatchBranch(out whenConditionBlock)); } } else if (entryPoint.Instructions.Count == 2) { // if (comp(isinst exceptionType(ldloc exceptionVar) != ldnull)) br whenConditionBlock // br falseBlock if (!entryPoint.Instructions[0].MatchIfInstruction(out var condition, out var branch)) { return(false); } if (!condition.MatchCompNotEquals(out var left, out var right)) { return(false); } if (!entryPoint.Instructions[1].MatchBranch(out var falseBlock) || !MatchFalseBlock(container, falseBlock, out var returnVar, out var exitBlock)) { return(false); } if (!left.MatchIsInst(out exceptionSlot, out exceptionType)) { return(false); } if (!exceptionSlot.MatchLdLoc(exceptionVar)) { return(false); } if (right.MatchLdNull()) { return(branch.MatchBranch(out whenConditionBlock)); } } return(false); }
/// <summary> /// Performs nullable lifting. /// /// Produces a lifted instruction with semantics equivalent to: /// (v1 != null && ... && vn != null) ? trueInst : falseInst, /// where the v1,...,vn are the <c>this.nullableVars</c>. /// If lifting fails, returns <c>null</c>. /// </summary> ILInstruction LiftNormal(ILInstruction trueInst, ILInstruction falseInst, Interval ilrange) { bool isNullCoalescingWithNonNullableFallback = false; if (!MatchNullableCtor(trueInst, out var utype, out var exprToLift)) { isNullCoalescingWithNonNullableFallback = true; utype = context.TypeSystem.Compilation.FindType(trueInst.ResultType.ToKnownTypeCode()); exprToLift = trueInst; if (nullableVars.Count == 1 && exprToLift.MatchLdLoc(nullableVars[0])) { // v.HasValue ? ldloc v : fallback // => v ?? fallback context.Step("v.HasValue ? v : fallback => v ?? fallback", trueInst); return(new NullCoalescingInstruction(NullCoalescingKind.Nullable, trueInst, falseInst) { UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType(), ILRange = ilrange }); } } ILInstruction lifted; if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) { // v.HasValue ? call GetValueOrDefault(ldloca v) : fallback // => conv.nop.lifted(ldloc v) ?? fallback // This case is handled separately from DoLift() because // that doesn't introduce nop-conversions. context.Step("v.HasValue ? v.GetValueOrDefault() : fallback => v ?? fallback", trueInst); var inputUType = NullableType.GetUnderlyingType(nullableVars[0].Type); lifted = new LdLoc(nullableVars[0]); if (!inputUType.Equals(utype) && utype.ToPrimitiveType() != PrimitiveType.None) { // While the ILAst allows implicit conversions between short and int // (because both map to I4); it does not allow implicit conversions // between short? and int? (structs of different types). // So use 'conv.nop.lifted' to allow the conversion. lifted = new Conv( lifted, inputUType.GetStackType(), inputUType.GetSign(), utype.ToPrimitiveType(), checkForOverflow: false, isLifted: true ) { ILRange = ilrange }; } } else { context.Step("NullableLiftingTransform.DoLift", trueInst); BitSet bits; (lifted, bits) = DoLift(exprToLift); if (lifted == null) { return(null); } if (!bits.All(0, nullableVars.Count)) { // don't lift if a nullableVar doesn't contribute to the result return(null); } Debug.Assert(lifted is ILiftableInstruction liftable && liftable.IsLifted && liftable.UnderlyingResultType == exprToLift.ResultType); } if (isNullCoalescingWithNonNullableFallback) { lifted = new NullCoalescingInstruction(NullCoalescingKind.NullableWithValueFallback, lifted, falseInst) { UnderlyingResultType = exprToLift.ResultType, ILRange = ilrange }; } else if (!MatchNull(falseInst, utype)) { // Normal lifting, but the falseInst isn't `default(utype?)` // => use the `??` operator to provide the fallback value. lifted = new NullCoalescingInstruction(NullCoalescingKind.Nullable, lifted, falseInst) { UnderlyingResultType = exprToLift.ResultType, ILRange = ilrange }; } return(lifted); }