/// <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); } }
/// <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); }
/// <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); }