private BoundExpression TrivialLiftedComparisonOperatorOptimizations( CSharpSyntaxNode syntax, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, MethodSymbol method) { Debug.Assert(left != null); Debug.Assert(right != null); // Optimization #1: if both sides are null then the result // is either true (for equality) or false (for everything else.) bool leftAlwaysNull = NullableNeverHasValue(left); bool rightAlwaysNull = NullableNeverHasValue(right); TypeSymbol boolType = _compilation.GetSpecialType(SpecialType.System_Boolean); if (leftAlwaysNull && rightAlwaysNull) { return MakeLiteral(syntax, ConstantValue.Create(kind.Operator() == BinaryOperatorKind.Equal), boolType); } // Optimization #2: If both sides are non-null then we can again eliminate the lifting entirely. BoundExpression leftNonNull = NullableAlwaysHasValue(left); BoundExpression rightNonNull = NullableAlwaysHasValue(right); if (leftNonNull != null && rightNonNull != null) { return MakeBinaryOperator( syntax: syntax, operatorKind: kind.Unlifted(), loweredLeft: leftNonNull, loweredRight: rightNonNull, type: boolType, method: method); } // Optimization #3: If one side is null and the other is definitely not, then we generate the side effects // of the non-null side and result in true (for not-equals) or false (for everything else.) BinaryOperatorKind operatorKind = kind.Operator(); if (leftAlwaysNull && rightNonNull != null || rightAlwaysNull && leftNonNull != null) { BoundExpression result = MakeLiteral(syntax, ConstantValue.Create(operatorKind == BinaryOperatorKind.NotEqual), boolType); BoundExpression nonNull = leftAlwaysNull ? rightNonNull : leftNonNull; if (ReadIsSideeffecting(nonNull)) { result = new BoundSequence( syntax: syntax, locals: ImmutableArray<LocalSymbol>.Empty, sideEffects: ImmutableArray.Create<BoundExpression>(nonNull), value: result, type: boolType); } return result; } // Optimization #4: If one side is null and the other is unknown, then we have three cases: // #4a: If we have x == null then that becomes !x.HasValue. // #4b: If we have x != null then that becomes x.HasValue. // #4c: If we have x OP null then that becomes side effects of x, result in false. if (leftAlwaysNull || rightAlwaysNull) { BoundExpression maybeNull = leftAlwaysNull ? right : left; if (operatorKind == BinaryOperatorKind.Equal || operatorKind == BinaryOperatorKind.NotEqual) { BoundExpression callHasValue = MakeNullableHasValue(syntax, maybeNull); BoundExpression result = operatorKind == BinaryOperatorKind.Equal ? MakeUnaryOperator(UnaryOperatorKind.BoolLogicalNegation, syntax, null, callHasValue, boolType) : callHasValue; return result; } else { BoundExpression falseExpr = MakeBooleanConstant(syntax, operatorKind == BinaryOperatorKind.NotEqual); return _factory.Sequence(maybeNull, falseExpr); } } return null; }
private BoundExpression LowerLiftedUserDefinedComparisonOperator( CSharpSyntaxNode syntax, BinaryOperatorKind kind, BoundExpression loweredLeft, BoundExpression loweredRight, MethodSymbol method) { // If both sides are null, or neither side is null, then we can do some simple optimizations. BoundExpression optimized = TrivialLiftedComparisonOperatorOptimizations(syntax, kind, loweredLeft, loweredRight, method); if (optimized != null) { return optimized; } // Otherwise, the expression // // x == y // // becomes // // tempX = x; // tempY = y; // result = tempX.HasValue == tempY.HasValue ? // (tempX.HasValue ? // tempX.GetValueOrDefault() == tempY.GetValueOrDefault() : // true) : // false; // // // the expression // // x != y // // becomes // // tempX = x; // tempY = y; // result = tempX.HasValue == tempY.HasValue ? // (tempX.HasValue ? // tempX.GetValueOrDefault() != tempY.GetValueOrDefault() : // false) : // true; // // // For the other comparison operators <, <=, >, >=, // // x OP y // // becomes // // tempX = x; // tempY = y; // result = tempX.HasValue & tempY.HasValue ? // tempX.GetValueOrDefault() OP tempY.GetValueOrDefault() : // false; // // We have not yet optimized the case where we have a known-not-null value on one side, // and an unknown value on the other. In those cases we will still generate a temp, but // we will not generate the call to the unnecessary nullable ctor or to GetValueOrDefault. // Rather, we will generate the value's temp instead of a call to GetValueOrDefault, and generate // literal true for HasValue. The tree construction methods we call will use those constants // to eliminate unnecessary branches. BoundExpression xNonNull = NullableAlwaysHasValue(loweredLeft); BoundExpression yNonNull = NullableAlwaysHasValue(loweredRight); // TODO: (This TODO applies throughout this file, not just to this method.) // TODO: We might be storing a constant to this temporary that we could simply inline. // TODO: (There are other expressions that can be safely moved around other than constants // TODO: as well -- for example a boxing conversion of a constant int to object.) // TODO: Build a better temporary-storage management system that decides whether or not // TODO: to store a temporary. BoundAssignmentOperator tempAssignmentX; BoundLocal boundTempX = _factory.StoreToTemp(xNonNull ?? loweredLeft, out tempAssignmentX); BoundAssignmentOperator tempAssignmentY; BoundLocal boundTempY = _factory.StoreToTemp(yNonNull ?? loweredRight, out tempAssignmentY); BoundExpression callX_GetValueOrDefault = MakeOptimizedGetValueOrDefault(syntax, boundTempX); BoundExpression callY_GetValueOrDefault = MakeOptimizedGetValueOrDefault(syntax, boundTempY); BoundExpression callX_HasValue = MakeOptimizedHasValue(syntax, boundTempX); BoundExpression callY_HasValue = MakeOptimizedHasValue(syntax, boundTempY); // tempx.HasValue == tempy.HasValue BinaryOperatorKind conditionOperator; BinaryOperatorKind operatorKind = kind.Operator(); switch (operatorKind) { case BinaryOperatorKind.Equal: case BinaryOperatorKind.NotEqual: conditionOperator = BinaryOperatorKind.BoolEqual; break; default: conditionOperator = BinaryOperatorKind.BoolAnd; break; } TypeSymbol boolType = _compilation.GetSpecialType(SpecialType.System_Boolean); BoundExpression condition = MakeBinaryOperator( syntax: syntax, operatorKind: conditionOperator, loweredLeft: callX_HasValue, loweredRight: callY_HasValue, type: boolType, method: null); // tempX.GetValueOrDefault() OP tempY.GetValueOrDefault() BoundExpression unliftedOp = MakeBinaryOperator( syntax: syntax, operatorKind: kind.Unlifted(), loweredLeft: callX_GetValueOrDefault, loweredRight: callY_GetValueOrDefault, type: boolType, method: method); BoundExpression consequence; if (operatorKind == BinaryOperatorKind.Equal || operatorKind == BinaryOperatorKind.NotEqual) { // tempx.HasValue ? tempX.GetValueOrDefault() == tempY.GetValueOrDefault() : true consequence = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: callX_HasValue, rewrittenConsequence: unliftedOp, rewrittenAlternative: MakeLiteral(syntax, ConstantValue.Create(operatorKind == BinaryOperatorKind.Equal), boolType), constantValueOpt: null, rewrittenType: boolType); } else { // tempX.GetValueOrDefault() OP tempY.GetValueOrDefault() consequence = unliftedOp; } // false BoundExpression alternative = MakeBooleanConstant(syntax, operatorKind == BinaryOperatorKind.NotEqual); BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: condition, rewrittenConsequence: consequence, rewrittenAlternative: alternative, constantValueOpt: null, rewrittenType: boolType); return new BoundSequence( syntax: syntax, locals: ImmutableArray.Create<LocalSymbol>(boundTempX.LocalSymbol, boundTempY.LocalSymbol), sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignmentX, tempAssignmentY), value: conditionalExpression, type: boolType); }
private BoundExpression MakeLiftedBinaryOperatorConsequence( CSharpSyntaxNode syntax, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, TypeSymbol type, MethodSymbol method) { // tempX.GetValueOrDefault() OP tempY.GetValueOrDefault() BoundExpression unliftedOp = MakeBinaryOperator( syntax: syntax, operatorKind: kind.Unlifted(), loweredLeft: left, loweredRight: right, type: type.GetNullableUnderlyingType(), method: method); // new R?(tempX.GetValueOrDefault() OP tempY.GetValueOrDefault) return new BoundObjectCreationExpression( syntax, GetNullableMethod(syntax, type, SpecialMember.System_Nullable_T__ctor), unliftedOp); }