//
using System;
+using SLE = System.Linq.Expressions;
#if STATIC
using IKVM.Reflection.Emit;
MemberFilter.Method ("GetValueOrDefault", 0, ParametersCompiled.EmptyReadOnlyParameters, null), BindingRestriction.None);
}
+ //
+ // Don't use unless really required for correctness, see Unwrap::Emit
+ //
public static MethodSpec GetValue (TypeSpec nullableType)
{
return (MethodSpec) MemberCache.FindMember (nullableType,
{
return ((InflatedTypeSpec) nullableType).TypeArguments[0];
}
+
+ public static TypeSpec GetEnumUnderlyingType (ModuleContainer module, TypeSpec nullableEnum)
+ {
+ return module.PredefinedTypes.Nullable.TypeSpec.MakeGenericType (module,
+ new[] { EnumSpec.GetUnderlyingType (GetUnderlyingType (nullableEnum)) });
+ }
}
public class Unwrap : Expression, IMemoryLocation
Expression expr;
LocalTemporary temp;
+ Expression temp_field;
readonly bool useDefaultValue;
- Unwrap (Expression expr, bool useDefaultValue)
+ public Unwrap (Expression expr, bool useDefaultValue = true)
{
this.expr = expr;
this.loc = expr.Location;
return expr.ContainsEmitWithAwait ();
}
+ // TODO: REMOVE
public static Expression Create (Expression expr)
{
//
return Create (expr, false);
}
+ public static Expression CreateUnwrapped (Expression expr)
+ {
+ //
+ // Avoid unwraping and wraping of same type
+ //
+ Wrap wrap = expr as Wrap;
+ if (wrap != null)
+ return wrap.Child;
+
+ return Create (expr, true);
+ }
+
public static Unwrap Create (Expression expr, bool useDefaultValue)
{
return new Unwrap (expr, useDefaultValue);
var call = new CallEmitter ();
call.InstanceExpression = this;
+ //
+ // Using GetGetValueOrDefault is prefered because JIT can possibly
+ // inline it whereas Value property contains a throw which is very
+ // unlikely to be inlined
+ //
if (useDefaultValue)
call.EmitPredefined (ec, NullableInfo.GetGetValueOrDefault (expr.Type), null);
else
call.EmitPredefined (ec, NullableInfo.GetHasValue (expr.Type), null);
}
+ public override Expression EmitToField (EmitContext ec)
+ {
+ if (temp_field == null)
+ temp_field = this.expr.EmitToField (ec);
+
+ return this;
+ }
+
public override bool Equals (object obj)
{
Unwrap uw = obj as Unwrap;
return uw != null && expr.Equals (uw.expr);
}
+ public override void FlowAnalysis (FlowAnalysisContext fc)
+ {
+ expr.FlowAnalysis (fc);
+ }
+
public Expression Original {
get {
return expr;
}
}
- void Store (EmitContext ec)
+ public void Store (EmitContext ec)
{
- if (temp != null)
+ if (temp != null || temp_field != null)
return;
if (expr is VariableReference)
public void Load (EmitContext ec)
{
- if (expr is VariableReference)
+ if (temp_field != null)
+ temp_field.Emit (ec);
+ else if (expr is VariableReference)
expr.Emit (ec);
else
LocalVariable.Emit (ec);
}
- public override System.Linq.Expressions.Expression MakeExpression (BuilderContext ctx)
+ public override SLE.Expression MakeExpression (BuilderContext ctx)
{
return expr.MakeExpression (ctx);
}
public void AddressOf (EmitContext ec, AddressOp mode)
{
- IMemoryLocation ml = expr as VariableReference;
+ IMemoryLocation ml;
+
+ if (temp_field != null) {
+ ml = temp_field as IMemoryLocation;
+ if (ml == null) {
+ var lt = new LocalTemporary (temp_field.Type);
+ temp_field.Emit (ec);
+ lt.Store (ec);
+ ml = lt;
+ }
+ } else {
+ ml = expr as VariableReference;
+ }
+
if (ml != null)
ml.AddressOf (ec, mode);
else
//
LocalTemporary LocalVariable {
get {
- if (temp == null)
+ if (temp == null && temp_field == null)
temp = new LocalTemporary (expr.Type);
return temp;
}
return child_cast.CreateExpressionTree (ec);
}
+ var user_cast = child as UserCast;
+ if (user_cast != null) {
+ child.Type = type;
+ return user_cast.CreateExpressionTree (ec);
+ }
+
return base.CreateExpressionTree (ec);
}
return new LiftedNull (nullable, loc);
}
- public static Constant CreateFromExpression (ResolveContext ec, Expression e)
+ public static Constant CreateFromExpression (ResolveContext rc, Expression e)
{
- ec.Report.Warning (458, 2, e.Location, "The result of the expression is always `null' of type `{0}'",
- TypeManager.CSharpName (e.Type));
+ if (!rc.HasSet (ResolveContext.Options.ExpressionTreeConversion)) {
+ rc.Report.Warning (458, 2, e.Location, "The result of the expression is always `null' of type `{0}'",
+ e.Type.GetSignatureForError ());
+ }
return ReducedExpression.Create (Create (e.Type, e.Location), e);
}
value_target.AddressOf (ec, AddressOp.Store);
ec.Emit (OpCodes.Initobj, type);
- ((IMemoryLocation) value_target).AddressOf (ec, Mode);
+ value_target.AddressOf (ec, Mode);
}
}
//
// Generic lifting expression, supports all S/S? -> T/T? cases
//
- public class Lifted : Expression, IMemoryLocation
+ public class LiftedConversion : Expression, IMemoryLocation
{
Expression expr, null_value;
Unwrap unwrap;
- public Lifted (Expression expr, Unwrap unwrap, TypeSpec type)
+ public LiftedConversion (Expression expr, Unwrap unwrap, TypeSpec type)
{
this.expr = expr;
this.unwrap = unwrap;
this.type = type;
}
- public Lifted (Expression expr, Expression unwrap, TypeSpec type)
+ public LiftedConversion (Expression expr, Expression unwrap, TypeSpec type)
: this (expr, unwrap as Unwrap, type)
{
}
// Wrap target for T?
if (type.IsNullableType) {
- expr = Wrap.Create (expr, type);
- if (expr == null)
- return null;
+ if (!expr.Type.IsNullableType) {
+ expr = Wrap.Create (expr, type);
+ if (expr == null)
+ return null;
+ }
null_value = LiftedNull.Create (type, loc);
} else if (TypeSpec.IsValueType (type)) {
ec.MarkLabel (is_null_label);
null_value.Emit (ec);
+
ec.MarkLabel (end_label);
}
+ public override void FlowAnalysis (FlowAnalysisContext fc)
+ {
+ expr.FlowAnalysis (fc);
+ }
+
public void AddressOf (EmitContext ec, AddressOp mode)
{
unwrap.AddressOf (ec, mode);
}
}
- public class LiftedBinaryOperator : Binary
+ //
+ // Lifted version of binary operators
+ //
+ class LiftedBinaryOperator : Expression
{
- Unwrap left_unwrap, right_unwrap;
- Expression left_orig, right_orig;
- Expression user_operator;
- MethodSpec wrap_ctor;
-
- public LiftedBinaryOperator (Binary.Operator op, Expression left, Expression right)
- : base (op, left, right)
+ public LiftedBinaryOperator (Binary b)
{
+ this.Binary = b;
+ this.loc = b.Location;
}
- bool IsBitwiseBoolean {
- get {
- return (Oper == Operator.BitwiseAnd || Oper == Operator.BitwiseOr) &&
- ((left_unwrap != null && left_unwrap.Type.BuiltinType == BuiltinTypeSpec.Type.Bool) ||
- (right_unwrap != null && right_unwrap.Type.BuiltinType == BuiltinTypeSpec.Type.Bool));
- }
- }
+ public Binary Binary { get; private set; }
- bool IsLeftNullLifted {
- get {
- return (state & State.LeftNullLifted) != 0;
- }
- }
+ public Expression Left { get; set; }
- bool IsRightNullLifted {
- get {
- return (state & State.RightNullLifted) != 0;
- }
- }
+ public Expression Right { get; set; }
- public override Expression CreateExpressionTree (ResolveContext ec)
- {
- if (user_operator != null)
- return user_operator.CreateExpressionTree (ec);
+ public Unwrap UnwrapLeft { get; set; }
- return base.CreateExpressionTree (ec);
- }
+ public Unwrap UnwrapRight { get; set; }
- //
- // CSC 2 has this behavior, it allows structs to be compared
- // with the null literal *outside* of a generics context and
- // inlines that as true or false.
- //
- Constant CreateNullConstant (ResolveContext ec, Expression expr)
- {
- // FIXME: Handle side effect constants
- Constant c = new BoolConstant (ec.BuiltinTypes, Oper == Operator.Inequality, loc);
+ public MethodSpec UserOperator { get; set; }
- if ((Oper & Operator.EqualityMask) != 0) {
- ec.Report.Warning (472, 2, loc, "The result of comparing value type `{0}' with null is `{1}'",
- TypeManager.CSharpName (expr.Type), c.GetValueAsLiteral ());
- } else {
- ec.Report.Warning (464, 2, loc, "The result of comparing type `{0}' with null is always `{1}'",
- TypeManager.CSharpName (expr.Type), c.GetValueAsLiteral ());
+ bool IsBitwiseBoolean {
+ get {
+ return (Binary.Oper == Binary.Operator.BitwiseAnd || Binary.Oper == Binary.Operator.BitwiseOr) &&
+ ((UnwrapLeft != null && UnwrapLeft.Type.BuiltinType == BuiltinTypeSpec.Type.Bool) ||
+ (UnwrapRight != null && UnwrapRight.Type.BuiltinType == BuiltinTypeSpec.Type.Bool));
}
-
- return ReducedExpression.Create (c, this);
}
- protected override Expression DoResolve (ResolveContext ec)
+ public override bool ContainsEmitWithAwait ()
{
- if ((Oper & Operator.LogicalMask) != 0) {
- Error_OperatorCannotBeApplied (ec, left, right);
- return null;
- }
-
- bool use_default_call = (Oper & (Operator.BitwiseMask | Operator.EqualityMask)) != 0;
- left_orig = left;
- if (left.Type.IsNullableType) {
- left = left_unwrap = Unwrap.Create (left, use_default_call);
- if (left == null)
- return null;
- }
-
- right_orig = right;
- if (right.Type.IsNullableType) {
- right = right_unwrap = Unwrap.Create (right, use_default_call);
- if (right == null)
- return null;
- }
-
- //
- // Some details are in 6.4.2, 7.2.7
- // Arguments can be lifted for equal operators when the return type is bool and both
- // arguments are of same type
- //
- if (left_orig is NullLiteral) {
- left = right;
- state |= State.LeftNullLifted;
- type = ec.BuiltinTypes.Bool;
- }
-
- if (right_orig.IsNull) {
- if ((Oper & Operator.ShiftMask) != 0)
- right = new EmptyExpression (ec.BuiltinTypes.Int);
- else
- right = left;
-
- state |= State.RightNullLifted;
- type = ec.BuiltinTypes.Bool;
- }
-
- eclass = ExprClass.Value;
- return DoResolveCore (ec, left_orig, right_orig);
+ return Left.ContainsEmitWithAwait () || Right.ContainsEmitWithAwait ();
}
- void EmitBitwiseBoolean (EmitContext ec)
+ public override Expression CreateExpressionTree (ResolveContext rc)
{
- Label load_left = ec.DefineLabel ();
- Label load_right = ec.DefineLabel ();
- Label end_label = ec.DefineLabel ();
+ if (UserOperator != null) {
+ Arguments args = new Arguments (2);
+ args.Add (new Argument (Binary.Left));
+ args.Add (new Argument (Binary.Right));
- // null & value, null | value
- if (left_unwrap == null) {
- left_unwrap = right_unwrap;
- right_unwrap = null;
- right = left;
+ var method = new UserOperatorCall (UserOperator, args, Binary.CreateExpressionTree, loc);
+ return method.CreateExpressionTree (rc);
}
- left_unwrap.Emit (ec);
- ec.Emit (OpCodes.Brtrue, load_right);
-
- // value & null, value | null
- if (right_unwrap != null) {
- right_unwrap.Emit (ec);
- ec.Emit (OpCodes.Brtrue_S, load_left);
- }
-
- left_unwrap.EmitCheck (ec);
- ec.Emit (OpCodes.Brfalse_S, load_right);
-
- // load left
- ec.MarkLabel (load_left);
-
- if (Oper == Operator.BitwiseAnd) {
- left_unwrap.Load (ec);
- } else {
- if (right_unwrap == null) {
- right.Emit (ec);
- if (right is EmptyConstantCast || right is EmptyCast)
- ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
- } else {
- right_unwrap.Load (ec);
- right_unwrap = left_unwrap;
- }
- }
- ec.Emit (OpCodes.Br_S, end_label);
-
- // load right
- ec.MarkLabel (load_right);
- if (right_unwrap == null) {
- if (Oper == Operator.BitwiseAnd) {
- right.Emit (ec);
- if (right is EmptyConstantCast || right is EmptyCast)
- ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
- } else {
- left_unwrap.Load (ec);
- }
- } else {
- right_unwrap.Load (ec);
- }
-
- ec.MarkLabel (end_label);
+ return Binary.CreateExpressionTree (rc);
}
- //
- // Emits optimized equality or inequality operator when possible
- //
- void EmitEquality (EmitContext ec)
+ protected override Expression DoResolve (ResolveContext rc)
{
- //
- // Either left or right is null
- //
- if (left_unwrap != null && (IsRightNullLifted || right.IsNull)) {
- left_unwrap.EmitCheck (ec);
- if (Oper == Binary.Operator.Equality) {
- ec.EmitInt (0);
- ec.Emit (OpCodes.Ceq);
- }
- return;
- }
-
- if (right_unwrap != null && (IsLeftNullLifted || left.IsNull)) {
- right_unwrap.EmitCheck (ec);
- if (Oper == Binary.Operator.Equality) {
- ec.EmitInt (0);
- ec.Emit (OpCodes.Ceq);
- }
- return;
- }
-
- Label dissimilar_label = ec.DefineLabel ();
- Label end_label = ec.DefineLabel ();
+ if (rc.IsRuntimeBinder) {
+ if (UnwrapLeft == null && !Left.Type.IsNullableType)
+ Left = LiftOperand (rc, Left);
- if (user_operator != null) {
- user_operator.Emit (ec);
- ec.Emit (Oper == Operator.Equality ? OpCodes.Brfalse_S : OpCodes.Brtrue_S, dissimilar_label);
+ if (UnwrapRight == null && !Right.Type.IsNullableType)
+ Right = LiftOperand (rc, Right);
} else {
- if (ec.HasSet (BuilderContext.Options.AsyncBody) && right.ContainsEmitWithAwait ()) {
- left = left.EmitToField (ec);
- right = right.EmitToField (ec);
+ if (UnwrapLeft == null && Left != null && Left.Type.IsNullableType) {
+ Left = Unwrap.CreateUnwrapped (Left);
+ UnwrapLeft = Left as Unwrap;
}
- left.Emit (ec);
- right.Emit (ec);
-
- ec.Emit (OpCodes.Bne_Un_S, dissimilar_label);
+ if (UnwrapRight == null && Right != null && Right.Type.IsNullableType) {
+ Right = Unwrap.CreateUnwrapped (Right);
+ UnwrapRight = Right as Unwrap;
+ }
}
- if (left_unwrap != null)
- left_unwrap.EmitCheck (ec);
+ type = Binary.Type;
+ eclass = Binary.eclass;
- if (right_unwrap != null)
- right_unwrap.EmitCheck (ec);
+ return this;
+ }
- if (left_unwrap != null && right_unwrap != null) {
- if (Oper == Operator.Inequality)
- ec.Emit (OpCodes.Xor);
- else
- ec.Emit (OpCodes.Ceq);
+ Expression LiftOperand (ResolveContext rc, Expression expr)
+ {
+ TypeSpec type;
+ if (expr.IsNull) {
+ type = Left.IsNull ? Right.Type : Left.Type;
} else {
- if (Oper == Operator.Inequality) {
- ec.EmitInt (0);
- ec.Emit (OpCodes.Ceq);
- }
+ type = expr.Type;
}
- ec.Emit (OpCodes.Br_S, end_label);
-
- ec.MarkLabel (dissimilar_label);
- if (Oper == Operator.Inequality)
- ec.EmitInt (1);
- else
- ec.EmitInt (0);
+ if (!type.IsNullableType)
+ type = rc.Module.PredefinedTypes.Nullable.TypeSpec.MakeGenericType (rc.Module, new[] { type });
- ec.MarkLabel (end_label);
+ return Wrap.Create (expr, type);
}
-
- public override void EmitBranchable (EmitContext ec, Label target, bool onTrue)
- {
- Emit (ec);
- ec.Emit (onTrue ? OpCodes.Brtrue : OpCodes.Brfalse, target);
- }
public override void Emit (EmitContext ec)
{
- //
- // Optimize same expression operation
- //
- if (right_unwrap != null && right.Equals (left))
- right_unwrap = left_unwrap;
-
- if (user_operator == null && IsBitwiseBoolean) {
+ if (IsBitwiseBoolean && UserOperator == null) {
EmitBitwiseBoolean (ec);
return;
}
- if ((Oper & Operator.EqualityMask) != 0) {
+ if ((Binary.Oper & Binary.Operator.EqualityMask) != 0) {
EmitEquality (ec);
return;
}
Label is_null_label = ec.DefineLabel ();
Label end_label = ec.DefineLabel ();
- if (left_unwrap != null) {
- left_unwrap.EmitCheck (ec);
- ec.Emit (OpCodes.Brfalse, is_null_label);
+ if (ec.HasSet (BuilderContext.Options.AsyncBody) && Right.ContainsEmitWithAwait ()) {
+ Left = Left.EmitToField (ec);
+ Right = Right.EmitToField (ec);
+ }
+
+ if (UnwrapLeft != null) {
+ UnwrapLeft.EmitCheck (ec);
}
//
// Don't emit HasValue check when left and right expressions are same
//
- if (right_unwrap != null && !left.Equals (right)) {
- right_unwrap.EmitCheck (ec);
- ec.Emit (OpCodes.Brfalse, is_null_label);
+ if (UnwrapRight != null && !Binary.Left.Equals (Binary.Right)) {
+ UnwrapRight.EmitCheck (ec);
+ if (UnwrapLeft != null) {
+ ec.Emit (OpCodes.And);
+ }
}
- EmitOperator (ec, left.Type);
+ ec.Emit (OpCodes.Brfalse, is_null_label);
+
+ if (UserOperator != null) {
+ var args = new Arguments (2);
+ args.Add (new Argument (Left));
+ args.Add (new Argument (Right));
+
+ var call = new CallEmitter ();
+ call.EmitPredefined (ec, UserOperator, args);
+ } else {
+ Binary.EmitOperator (ec, Left, Right);
+ }
- if (wrap_ctor != null)
- ec.Emit (OpCodes.Newobj, wrap_ctor);
+ //
+ // Wrap the result when the operator return type is nullable type
+ //
+ if (type.IsNullableType)
+ ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
ec.Emit (OpCodes.Br_S, end_label);
ec.MarkLabel (is_null_label);
- if ((Oper & Operator.ComparisonMask) != 0) {
+ if ((Binary.Oper & Binary.Operator.ComparisonMask) != 0) {
ec.EmitInt (0);
} else {
LiftedNull.Create (type, loc).Emit (ec);
ec.MarkLabel (end_label);
}
- protected override void EmitOperator (EmitContext ec, TypeSpec l)
+ void EmitBitwiseBoolean (EmitContext ec)
{
- if (user_operator != null) {
- user_operator.Emit (ec);
- return;
- }
+ Label load_left = ec.DefineLabel ();
+ Label load_right = ec.DefineLabel ();
+ Label end_label = ec.DefineLabel ();
+ Label is_null_label = ec.DefineLabel ();
- if (left.Type.IsNullableType) {
- l = NullableInfo.GetUnderlyingType (left.Type);
- left = EmptyCast.Create (left, l);
- }
+ bool or = Binary.Oper == Binary.Operator.BitwiseOr;
- if (right.Type.IsNullableType) {
- right = EmptyCast.Create (right, NullableInfo.GetUnderlyingType (right.Type));
- }
+ //
+ // Both operands are bool? types
+ //
+ if (UnwrapLeft != null && UnwrapRight != null) {
+ if (ec.HasSet (BuilderContext.Options.AsyncBody) && Binary.Right.ContainsEmitWithAwait ()) {
+ Left = Left.EmitToField (ec);
+ Right = Right.EmitToField (ec);
+ }
- base.EmitOperator (ec, l);
- }
+ Left.Emit (ec);
+ ec.Emit (OpCodes.Brtrue_S, load_right);
- Expression LiftResult (ResolveContext ec, Expression res_expr)
- {
- TypeSpec lifted_type;
+ Right.Emit (ec);
+ ec.Emit (OpCodes.Brtrue_S, load_left);
- //
- // Avoid double conversion
- //
- if (left_unwrap == null || IsLeftNullLifted || left_unwrap.Type != left.Type || (left_unwrap != null && IsRightNullLifted)) {
- lifted_type = new NullableType (left.Type, loc).ResolveAsType (ec);
- if (lifted_type == null)
- return null;
+ UnwrapLeft.EmitCheck (ec);
+ ec.Emit (OpCodes.Brfalse_S, load_right);
- if (left is UserCast || left is EmptyCast || left is OpcodeCast)
- left.Type = lifted_type;
+ // load left
+ ec.MarkLabel (load_left);
+ if (or)
+ UnwrapRight.Load (ec);
else
- left = EmptyCast.Create (left, lifted_type);
- }
+ UnwrapLeft.Load (ec);
- if (left != right && (right_unwrap == null || IsRightNullLifted || right_unwrap.Type != right.Type || (right_unwrap != null && IsLeftNullLifted))) {
- lifted_type = new NullableType (right.Type, loc).ResolveAsType (ec);
- if (lifted_type == null)
- return null;
-
- var r = right;
- if (r is ReducedExpression)
- r = ((ReducedExpression) r).OriginalExpression;
+ ec.Emit (OpCodes.Br_S, end_label);
- if (r is UserCast || r is EmptyCast || r is OpcodeCast)
- r.Type = lifted_type;
+ // load right
+ ec.MarkLabel (load_right);
+ if (or)
+ UnwrapLeft.Load (ec);
else
- right = EmptyCast.Create (right, lifted_type);
+ UnwrapRight.Load (ec);
+
+ ec.MarkLabel (end_label);
+ return;
}
- if ((Oper & Operator.ComparisonMask) == 0) {
- lifted_type = new NullableType (res_expr.Type, loc).ResolveAsType (ec);
- if (lifted_type == null)
- return null;
+ //
+ // Faster version when one operand is bool
+ //
+ if (UnwrapLeft == null) {
+ //
+ // (bool, bool?)
+ //
+ // Optimizes remaining (false & bool?), (true | bool?) which are not easy to handle
+ // in binary expression reduction
+ //
+ var c = Left as BoolConstant;
+ if (c != null) {
+ // Keep evaluation order
+ UnwrapRight.Store (ec);
+
+ ec.EmitInt (or ? 1 : 0);
+ ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
+ } else if (Left.IsNull) {
+ UnwrapRight.Emit (ec);
+ ec.Emit (or ? OpCodes.Brfalse_S : OpCodes.Brtrue_S, is_null_label);
+
+ UnwrapRight.Load (ec);
+ ec.Emit (OpCodes.Br_S, end_label);
+
+ ec.MarkLabel (is_null_label);
+ LiftedNull.Create (type, loc).Emit (ec);
+ } else {
+ Left.Emit (ec);
+ ec.Emit (or ? OpCodes.Brfalse_S : OpCodes.Brtrue_S, load_right);
- wrap_ctor = NullableInfo.GetConstructor (lifted_type);
- type = res_expr.Type = lifted_type;
- }
+ ec.EmitInt (or ? 1 : 0);
+ ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
- if (IsLeftNullLifted) {
- left = LiftedNull.Create (right.Type, left.Location);
+ ec.Emit (OpCodes.Br_S, end_label);
+ ec.MarkLabel (load_right);
+ UnwrapRight.Original.Emit (ec);
+ }
+ } else {
//
- // Special case for bool?, the result depends on both null right side and left side value
+ // (bool?, bool)
//
- if ((Oper == Operator.BitwiseAnd || Oper == Operator.BitwiseOr) && NullableInfo.GetUnderlyingType (type).BuiltinType == BuiltinTypeSpec.Type.Bool) {
- return res_expr;
- }
-
- if ((Oper & (Operator.ArithmeticMask | Operator.ShiftMask | Operator.BitwiseMask)) != 0)
- return LiftedNull.CreateFromExpression (ec, res_expr);
+ // Keep left-right evaluation order
+ UnwrapLeft.Store (ec);
//
- // Value types and null comparison
+ // Optimizes remaining (bool? & false), (bool? | true) which are not easy to handle
+ // in binary expression reduction
//
- if (right_unwrap == null || (Oper & Operator.RelationalMask) != 0)
- return CreateNullConstant (ec, right_orig);
+ var c = Right as BoolConstant;
+ if (c != null) {
+ ec.EmitInt (or ? 1 : 0);
+ ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
+ } else if (Right.IsNull) {
+ UnwrapLeft.Emit (ec);
+ ec.Emit (or ? OpCodes.Brfalse_S : OpCodes.Brtrue_S, is_null_label);
+
+ UnwrapLeft.Load (ec);
+ ec.Emit (OpCodes.Br_S, end_label);
+
+ ec.MarkLabel (is_null_label);
+ LiftedNull.Create (type, loc).Emit (ec);
+ } else {
+ Right.Emit (ec);
+ ec.Emit (or ? OpCodes.Brfalse_S : OpCodes.Brtrue_S, load_right);
+
+ ec.EmitInt (or ? 1 : 0);
+ ec.Emit (OpCodes.Newobj, NullableInfo.GetConstructor (type));
+
+ ec.Emit (OpCodes.Br_S, end_label);
+
+ ec.MarkLabel (load_right);
+
+ UnwrapLeft.Load (ec);
+ }
}
- if (IsRightNullLifted) {
- right = LiftedNull.Create (left.Type, right.Location);
+ ec.MarkLabel (end_label);
+ }
+ //
+ // Emits optimized equality or inequality operator when possible
+ //
+ void EmitEquality (EmitContext ec)
+ {
+ //
+ // Either left or right is null
+ //
+ if (UnwrapLeft != null && Binary.Right.IsNull) { // TODO: Optimize for EmitBranchable
//
- // Special case for bool?, the result depends on both null right side and left side value
+ // left.HasValue == false
//
- if ((Oper == Operator.BitwiseAnd || Oper == Operator.BitwiseOr) && NullableInfo.GetUnderlyingType (type).BuiltinType == BuiltinTypeSpec.Type.Bool) {
- return res_expr;
+ UnwrapLeft.EmitCheck (ec);
+ if (Binary.Oper == Binary.Operator.Equality) {
+ ec.EmitInt (0);
+ ec.Emit (OpCodes.Ceq);
}
+ return;
+ }
- if ((Oper & (Operator.ArithmeticMask | Operator.ShiftMask | Operator.BitwiseMask)) != 0)
- return LiftedNull.CreateFromExpression (ec, res_expr);
-
+ if (UnwrapRight != null && Binary.Left.IsNull) {
//
- // Value types and null comparison
+ // right.HasValue == false
//
- if (left_unwrap == null || (Oper & Operator.RelationalMask) != 0)
- return CreateNullConstant (ec, left_orig);
+ UnwrapRight.EmitCheck (ec);
+ if (Binary.Oper == Binary.Operator.Equality) {
+ ec.EmitInt (0);
+ ec.Emit (OpCodes.Ceq);
+ }
+ return;
}
- return res_expr;
- }
+ Label dissimilar_label = ec.DefineLabel ();
+ Label end_label = ec.DefineLabel ();
- protected override Expression ResolveOperatorPredefined (ResolveContext ec, Binary.PredefinedOperator [] operators, bool primitives_only, TypeSpec enum_type)
- {
- Expression e = base.ResolveOperatorPredefined (ec, operators, primitives_only, enum_type);
+ if (UserOperator != null) {
+ var left = Left;
- if (e == this || enum_type != null)
- return LiftResult (ec, e);
+ if (UnwrapLeft != null) {
+ UnwrapLeft.EmitCheck (ec);
+ } else {
+ // Keep evaluation order same
+ if (!(Left is VariableReference)) {
+ Left.Emit (ec);
+ var lt = new LocalTemporary (Left.Type);
+ lt.Store (ec);
+ left = lt;
+ }
+ }
- //
- // 7.9.9 Equality operators and null
- //
- // The == and != operators permit one operand to be a value of a nullable type and
- // the other to be the null literal, even if no predefined or user-defined operator
- // (in unlifted or lifted form) exists for the operation.
- //
- if (e == null && (Oper & Operator.EqualityMask) != 0) {
- if ((IsLeftNullLifted && right_unwrap != null) || (IsRightNullLifted && left_unwrap != null))
- return LiftResult (ec, this);
- }
+ if (UnwrapRight != null) {
+ UnwrapRight.EmitCheck (ec);
- return e;
- }
+ if (UnwrapLeft != null) {
+ ec.Emit (OpCodes.Bne_Un, dissimilar_label);
- protected override Expression ResolveUserOperator (ResolveContext ec, Expression left, Expression right)
- {
- //
- // Try original types first for exact match without unwrapping
- //
- Expression expr = base.ResolveUserOperator (ec, left_orig, right_orig);
- if (expr != null)
- return expr;
+ Label compare_label = ec.DefineLabel ();
+ UnwrapLeft.EmitCheck (ec);
+ ec.Emit (OpCodes.Brtrue, compare_label);
- State orig_state = state;
+ if (Binary.Oper == Binary.Operator.Equality)
+ ec.EmitInt (1);
+ else
+ ec.EmitInt (0);
- //
- // One side is a nullable type, try to match underlying types
- //
- if (left_unwrap != null || right_unwrap != null || (state & (State.RightNullLifted | State.LeftNullLifted)) != 0) {
- expr = base.ResolveUserOperator (ec, left, right);
+ ec.Emit (OpCodes.Br, end_label);
+
+ ec.MarkLabel (compare_label);
+ } else {
+ ec.Emit (OpCodes.Brfalse, dissimilar_label);
+ }
+ } else {
+ ec.Emit (OpCodes.Brfalse, dissimilar_label);
+ }
+
+ var args = new Arguments (2);
+ args.Add (new Argument (left));
+ args.Add (new Argument (Right));
+
+ var call = new CallEmitter ();
+ call.EmitPredefined (ec, UserOperator, args);
+ } else {
+ if (ec.HasSet (BuilderContext.Options.AsyncBody) && Binary.Right.ContainsEmitWithAwait ()) {
+ Left = Left.EmitToField (ec);
+ Right = Right.EmitToField (ec);
+ }
+
+ //
+ // Emit underlying value comparison first.
+ //
+ // For this code: int? a = 1; bool b = a == 1;
+ //
+ // We emit something similar to this. Expressions with side effects have local
+ // variable created by Unwrap expression
+ //
+ // left.GetValueOrDefault ()
+ // right
+ // bne.un.s dissimilar_label
+ // left.HasValue
+ // br.s end_label
+ // dissimilar_label:
+ // ldc.i4.0
+ // end_label:
+ //
+
+ Left.Emit (ec);
+ Right.Emit (ec);
+
+ ec.Emit (OpCodes.Bne_Un_S, dissimilar_label);
+
+ //
+ // Check both left and right expressions for Unwrap call in which
+ // case we need to run get_HasValue() check because the type is
+ // nullable and could have null value
+ //
+ if (UnwrapLeft != null)
+ UnwrapLeft.EmitCheck (ec);
+
+ if (UnwrapRight != null)
+ UnwrapRight.EmitCheck (ec);
+
+ if (UnwrapLeft != null && UnwrapRight != null) {
+ if (Binary.Oper == Binary.Operator.Inequality)
+ ec.Emit (OpCodes.Xor);
+ else
+ ec.Emit (OpCodes.Ceq);
+ } else {
+ if (Binary.Oper == Binary.Operator.Inequality) {
+ ec.EmitInt (0);
+ ec.Emit (OpCodes.Ceq);
+ }
+ }
}
- if (expr == null)
- return null;
+ ec.Emit (OpCodes.Br_S, end_label);
- //
- // Lift the result in the case it can be null and predefined or user operator
- // result type is of a value type
- //
- if (!TypeSpec.IsValueType (expr.Type))
- return null;
+ ec.MarkLabel (dissimilar_label);
+ if (Binary.Oper == Binary.Operator.Inequality)
+ ec.EmitInt (1);
+ else
+ ec.EmitInt (0);
- if (state != orig_state)
- return expr;
+ ec.MarkLabel (end_label);
+ }
- expr = LiftResult (ec, expr);
- if (expr is Constant)
- return expr;
+ public override void FlowAnalysis (FlowAnalysisContext fc)
+ {
+ Binary.FlowAnalysis (fc);
+ }
- type = expr.Type;
- user_operator = expr;
- return this;
+ public override SLE.Expression MakeExpression (BuilderContext ctx)
+ {
+ return Binary.MakeExpression (ctx, Left, Right);
}
}
}
TypeSpec rtype = right.Type;
- if (!Convert.ImplicitConversionExists (ec, unwrap != null ? unwrap : left, rtype) || right.eclass == ExprClass.MethodGroup)
+ if (!Convert.ImplicitConversionExists (ec, unwrap ?? left, rtype) || right.eclass == ExprClass.MethodGroup)
return null;
//
if (left.IsNull)
return ReducedExpression.Create (right, this).Resolve (ec);
- left = Convert.ImplicitConversion (ec, unwrap != null ? unwrap : left, rtype, loc);
+ left = Convert.ImplicitConversion (ec, unwrap ?? left, rtype, loc);
type = rtype;
return this;
}
ec.MarkLabel (end_label);
}
+ public override void FlowAnalysis (FlowAnalysisContext fc)
+ {
+ left.FlowAnalysis (fc);
+ var left_da = fc.BranchDefiniteAssignment ();
+ right.FlowAnalysis (fc);
+ fc.DefiniteAssignment = left_da;
+ }
+
protected override void CloneTo (CloneContext clonectx, Expression t)
{
NullCoalescingOperator target = (NullCoalescingOperator) t;
call = new CallEmitter ();
call.InstanceExpression = lt;
- call.EmitPredefined (ec, NullableInfo.GetValue (expr.Type), null);
+ call.EmitPredefined (ec, NullableInfo.GetGetValueOrDefault (expr.Type), null);
lt.Release (ec);