@@ -79,7 +79,7 @@ public enum CompilerFlags : byte
7979 ThrowOnNotSupportedExpression = 1 << 2,
8080 /// <summary>Will try to evalaute constant, arithmetic, logical, comparison expressions consisting of the former expression types,
8181 /// and emit the result only to the IL instead the whole computation. Minimizes IL and moves optimization to the compilation phase if possible.</summary>
82- TryEvalPureArithmeticAndLogic = 1 << 4
82+ DisableInterpreter = 1 << 4
8383 }
8484
8585 /// <summary>FEC Not Supported exception</summary>
@@ -493,12 +493,12 @@ internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Exp
493493 Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags)
494494 {
495495#endif
496- if ((flags & CompilerFlags.TryEvalPureArithmeticAndLogic) != 0 &
497- returnType == typeof(bool) & closurePlusParamTypes.Length == 1)
498- {
499- if ( Interpreter.TryEvalPureArithmeticAndLogic(out var result, bodyExpr) )
500- return (bool)result ? Interpreter.TrueFunc : Interpreter.FalseFunc;
501- }
496+ // Try to avoid complilation altogether for Func<bool> delegates via Interpreter, see #468
497+ if ((flags & CompilerFlags.DisableInterpreter) == 0 &
498+ returnType == typeof(bool) & closurePlusParamTypes.Length == 1
499+ && Interpreter.IsCandidateForInterpretation( bodyExpr)
500+ && Interpreter.TryInterpretBoolean(out var result, bodyExpr))
501+ return result ? Interpreter.TrueFunc : Interpreter.FalseFunc;
502502
503503 // The method collects the info from the all nested lambdas deep down up-front and de-duplicates the lambdas as well.
504504 var closureInfo = new ClosureInfo(ClosureStatus.ToBeCollected);
@@ -1199,14 +1199,14 @@ public static Result TryCollectInfo(ref ClosureInfo closure, Expression expr,
11991199 {
12001200 case ExpressionType.Constant:
12011201#if LIGHT_EXPRESSION
1202- if (((ConstantExpression) expr).RefField != null )
1202+ if (expr is ConstantRefExpression )
12031203 {
12041204 // Register the constant expression itself in the closure
12051205 closure.AddConstantOrIncrementUsageCount(expr);
12061206 return Result.OK;
12071207 }
12081208
1209- if (expr == NullConstant || expr == FalseConstant || expr == TrueConstant || expr is IntConstantExpression n )
1209+ if (expr == NullConstant | expr == FalseConstant | expr == TrueConstant || expr is IntConstantExpression)
12101210 return r;
12111211#endif
12121212 var constantExpr = (ConstantExpression)expr;
@@ -2087,11 +2087,11 @@ public static bool TryEmit(Expression expr,
20872087 case ExpressionType.LessThanOrEqual:
20882088 case ExpressionType.Equal:
20892089 case ExpressionType.NotEqual:
2090- if ((setup & CompilerFlags.TryEvalPureArithmeticAndLogic) ! = 0 && expr.Type.IsPrimitive &&
2091- Interpreter.TryEvalPureArithmeticAndLogic (out var evalResult , expr))
2090+ if ((setup & CompilerFlags.DisableInterpreter) = = 0 && expr.Type.IsPrimitive &&
2091+ Interpreter.TryInterpretBoolean (out var boolResult , expr))
20922092 {
20932093 if ((parent & ParentFlags.IgnoreResult) == 0)
2094- il.Demit((bool)evalResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
2094+ il.Demit(boolResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
20952095 return true;
20962096 }
20972097 var binaryExpr = (BinaryExpression)expr;
@@ -2111,10 +2111,12 @@ public static bool TryEmit(Expression expr,
21112111 case ExpressionType.ExclusiveOr:
21122112 case ExpressionType.LeftShift:
21132113 case ExpressionType.RightShift:
2114+ // todo: @wip interpreter
21142115 return TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, expr.Type, paramExprs, il, ref closure, setup, parent);
21152116
21162117 case ExpressionType.AndAlso:
21172118 case ExpressionType.OrElse:
2119+ // todo: @wip interpreter
21182120 return TryEmitLogicalOperator((BinaryExpression)expr, nodeType, paramExprs, il, ref closure, setup, parent);
21192121
21202122 case ExpressionType.Coalesce:
@@ -3432,31 +3434,30 @@ public static bool TryEmitConstant(ConstantExpression expr, Type exprType, ILGen
34323434 {
34333435 var ok = false;
34343436#if LIGHT_EXPRESSION
3435- var refField = expr.RefField;
3436- if (refField != null)
3437+ if (expr is ConstantRefExpression cref)
34373438 {
34383439 Debug.Assert(closure.ContainsConstantsOrNestedLambdas());
3439- ok = TryEmitConstant(true, null, null, expr, il, ref closure, byRefIndex, refField );
3440+ ok = TryEmitConstant(true, null, null, expr, il, ref closure, byRefIndex, cref.RefField );
34403441 if (!ok) return false;
34413442 }
34423443 else if (expr == NullConstant)
34433444 {
34443445 il.Demit(OpCodes.Ldnull);
34453446 ok = true;
34463447 }
3447- else if (expr == FalseConstant)
3448+ else if (expr == FalseConstant | expr == ZeroConstant )
34483449 {
34493450 il.Demit(OpCodes.Ldc_I4_0);
34503451 ok = true;
34513452 }
3452- else if (expr == TrueConstant)
3453+ else if (expr == TrueConstant | expr == OneConstant )
34533454 {
34543455 il.Demit(OpCodes.Ldc_I4_1);
34553456 ok = true;
34563457 }
34573458 else if (expr is IntConstantExpression n)
34583459 {
3459- EmitLoadConstantInt(il, n.IntValue );
3460+ EmitLoadConstantInt(il, (int)n.Value );
34603461 ok = true;
34613462 }
34623463#endif
@@ -6402,6 +6403,7 @@ public static class Interpreter
64026403 /// <summary>Single instance of false object</summary>
64036404 public static readonly object FalseObject = false;
64046405
6406+ [MethodImpl(MethodImplOptions.NoInlining)]
64056407 private static T UnreachableCase<T>()
64066408 {
64076409 throw new InvalidCastException("Unreachable switch case reached");
@@ -6435,7 +6437,7 @@ public static bool IsArithmetic(ExpressionType nodeType) =>
64356437 nodeType == ExpressionType.Negate;
64366438
64376439 /// <summary>Eval negate</summary>
6438- public static object EvalNegateOrNull (object operand)
6440+ public static object DoNegateOrNull (object operand)
64396441 {
64406442 return Type.GetTypeCode(operand.GetType()) switch
64416443 {
@@ -6454,9 +6456,9 @@ public static object EvalNegateOrNull(object operand)
64546456 };
64556457 }
64566458
6457- /// <summary>Eval arithmetic. The types of the left and the right operands assumed to be the same.
6459+ /// <summary>Interpret arithmetic. The types of the left and the right operands assumed to be the same.
64586460 /// The Expression.Add, Divide, etc, expects the operands to be of the same type </summary>
6459- public static object EvalArithmeticOrNull (object left, object right, ExpressionType nodeType)
6461+ public static object DoArithmeticOrNull (object left, object right, ExpressionType nodeType)
64606462 {
64616463 Debug.Assert(left != null && right != null, "left and right should not be null");
64626464 Debug.Assert(left.GetType() == right.GetType(), "left and right should be of the same type");
@@ -6545,22 +6547,67 @@ public static object EvalArithmeticOrNull(object left, object right, ExpressionT
65456547 };
65466548 }
65476549
6550+ public static class ZeroDefault<T>
6551+ {
6552+ public static readonly object Instance = default(T);
6553+ }
6554+
6555+ public static object GetZeroDefaultObject(TypeCode typeCode)
6556+ {
6557+ return typeCode switch
6558+ {
6559+ TypeCode.Boolean => FalseObject,
6560+ TypeCode.SByte => ZeroDefault<sbyte>.Instance,
6561+ TypeCode.Byte => ZeroDefault<byte>.Instance,
6562+ TypeCode.Int16 => ZeroDefault<short>.Instance,
6563+ TypeCode.UInt16 => ZeroDefault<ushort>.Instance,
6564+ TypeCode.Int32 => ZeroDefault<int>.Instance,
6565+ TypeCode.UInt32 => ZeroDefault<uint>.Instance,
6566+ TypeCode.Int64 => ZeroDefault<long>.Instance,
6567+ TypeCode.UInt64 => ZeroDefault<ulong>.Instance,
6568+ TypeCode.Single => ZeroDefault<float>.Instance,
6569+ TypeCode.Double => ZeroDefault<double>.Instance,
6570+ TypeCode.Decimal => ZeroDefault<decimal>.Instance,
6571+ _ => null,
6572+ };
6573+ }
6574+
6575+ /// <summary>Fast, mostly negative check to skip or proceed with interpretation.
6576+ /// Depending on the context you may avoid calling it because you know the interpeted expression beforehand,</summary>
6577+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
6578+ public static bool IsCandidateForInterpretation(Expression expr)
6579+ {
6580+ var nodeType = expr.NodeType;
6581+ return
6582+ nodeType == ExpressionType.Constant |
6583+ nodeType == ExpressionType.Default |
6584+ nodeType == ExpressionType.Convert |
6585+ nodeType == ExpressionType.Not |
6586+ nodeType == ExpressionType.Negate |
6587+ expr is BinaryExpression;
6588+ }
6589+
65486590 /// <summary>In case of exception FEC will emit the whole computation to throw expection in the invocation phase</summary>
6549- public static bool TryEvalPureArithmeticAndLogic (out object result, Expression expr)
6591+ public static bool TryInterpretBoolean (out bool result, Expression expr)
65506592 {
65516593 try
65526594 {
6553- return TryEvalPrimitive(out result, expr);
6595+ if (TryInterpretPrimitive(out var resultObj, expr))
6596+ {
6597+ result = resultObj == TrueObject;
6598+ return true;
6599+ }
65546600 }
65556601 catch
65566602 {
6557- result = null;
6558- return false;
6603+ // eat up the expression this time
65596604 }
6605+ result = false;
6606+ return false;
65606607 }
65616608
6562- /// <summary>Tries to eval the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic</summary>
6563- public static bool TryEvalPrimitive (out object result, Expression expr)
6609+ /// <summary>Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic</summary>
6610+ public static bool TryInterpretPrimitive (out object result, Expression expr)
65646611 {
65656612 Debug.Assert(expr.Type.IsPrimitive);
65666613 result = null;
@@ -6571,30 +6618,39 @@ public static bool TryEvalPrimitive(out object result, Expression expr)
65716618#if LIGHT_EXPRESSION
65726619 if (expr is ConstantRefExpression)
65736620 return false;
6621+ // todo: @perf check for LightExpression TrueConstant, FalseConstant, etc. and process them specially
65746622#endif
65756623 result = ((ConstantExpression)expr).Value;
6624+ if (expr.Type == typeof(bool))
6625+ result = (bool)result ? TrueObject : FalseObject;
65766626 return true;
65776627 }
65786628
6579- // todo: @wip handle the DefaultExpression
6629+ if (nodeType == ExpressionType.Default)
6630+ {
6631+ result = GetZeroDefaultObject(Type.GetTypeCode(expr.Type));
6632+ return true;
6633+ }
65806634
65816635 if (nodeType == ExpressionType.Convert)
65826636 {
65836637 var unaryExpr = (UnaryExpression)expr;
65846638 var operand = unaryExpr.Operand;
6585- if (!TryEvalPrimitive (out var val , operand))
6639+ if (!TryInterpretPrimitive (out result , operand))
65866640 return false;
65876641 var exprType = expr.Type;
6588- result = (operand.Type == exprType || exprType.IsAssignableFrom(operand.Type)
6589- ? val
6590- : System.Convert.ChangeType(val, exprType));
6642+ if (operand.Type != exprType && !exprType.IsAssignableFrom(operand.Type))
6643+ {
6644+ var converted = System.Convert.ChangeType(result, exprType);
6645+ result = exprType != typeof(bool) ? converted : (bool)converted ? TrueObject : FalseObject;
6646+ }
65916647 return true;
65926648 }
65936649
65946650 if (nodeType == ExpressionType.Not)
65956651 {
65966652 var unaryExpr = (UnaryExpression)expr;
6597- if (!TryEvalPrimitive (out var boolVal, unaryExpr.Operand))
6653+ if (!TryInterpretPrimitive (out var boolVal, unaryExpr.Operand))
65986654 return false;
65996655 result = boolVal == TrueObject ? FalseObject : TrueObject;
66006656 return true;
@@ -6603,7 +6659,7 @@ public static bool TryEvalPrimitive(out object result, Expression expr)
66036659 if (IsLogical(nodeType))
66046660 {
66056661 var binaryExpr = (BinaryExpression)expr;
6606- if (!TryEvalPrimitive (out var leftVal, binaryExpr.Left))
6662+ if (!TryInterpretPrimitive (out var leftVal, binaryExpr.Left))
66076663 return false;
66086664
66096665 // Short circuit the evalution, because this is an actual logic of these logical operations
@@ -6613,14 +6669,14 @@ public static bool TryEvalPrimitive(out object result, Expression expr)
66136669 result = leftVal;
66146670 return true;
66156671 }
6616- return TryEvalPrimitive (out result, binaryExpr.Right);
6672+ return TryInterpretPrimitive (out result, binaryExpr.Right);
66176673 }
66186674
66196675 if (IsComparison(nodeType))
66206676 {
66216677 var binaryExpr = (BinaryExpression)expr;
6622- if (!TryEvalPrimitive (out var leftVal, binaryExpr.Left) ||
6623- !TryEvalPrimitive (out var rightVal, binaryExpr.Right))
6678+ if (!TryInterpretPrimitive (out var leftVal, binaryExpr.Left) ||
6679+ !TryInterpretPrimitive (out var rightVal, binaryExpr.Right))
66246680 return false;
66256681
66266682 if (nodeType == ExpressionType.Equal | nodeType == ExpressionType.NotEqual)
@@ -6653,20 +6709,20 @@ public static bool TryEvalPrimitive(out object result, Expression expr)
66536709 if (nodeType == ExpressionType.Negate)
66546710 {
66556711 var unaryExpr = (UnaryExpression)expr;
6656- if (!TryEvalPrimitive (out var val, unaryExpr.Operand))
6712+ if (!TryInterpretPrimitive (out var val, unaryExpr.Operand))
66576713 return false;
6658- result = EvalNegateOrNull (val);
6714+ result = DoNegateOrNull (val);
66596715 return result != null;
66606716 }
66616717
66626718 if (IsArithmetic(nodeType))
66636719 {
66646720 var binaryExpr = (BinaryExpression)expr;
6665- if (!TryEvalPrimitive (out var leftVal, binaryExpr.Left) ||
6666- !TryEvalPrimitive (out var rightVal, binaryExpr.Right))
6721+ if (!TryInterpretPrimitive (out var leftVal, binaryExpr.Left) ||
6722+ !TryInterpretPrimitive (out var rightVal, binaryExpr.Right))
66676723 return false;
66686724
6669- result = EvalArithmeticOrNull (leftVal, rightVal, nodeType);
6725+ result = DoArithmeticOrNull (leftVal, rightVal, nodeType);
66706726 return result != null;
66716727 }
66726728
0 commit comments