Skip to content

Commit b2337c2

Browse files
committed
eval eval eval
1 parent 4059378 commit b2337c2

File tree

2 files changed

+84
-31
lines changed

2 files changed

+84
-31
lines changed

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ public enum CompilerFlags : byte
7676
/// <summary>Adds the Expression, ExpressionString, and CSharpString to the delegate closure for the debugging inspection</summary>
7777
EnableDelegateDebugInfo = 1 << 1,
7878
/// <summary>When the flag is set then instead of the returning `null` the specific exception is thrown*346</summary>
79-
ThrowOnNotSupportedExpression = 1 << 2
79+
ThrowOnNotSupportedExpression = 1 << 2,
80+
/// <summary>Will try to evalaute constant, arithmetic, logical, comparison expressions consisting of the former expression types,
81+
/// 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+
EvaluateExpressionIfPossible = 1 << 4
8083
}
8184

8285
/// <summary>FEC Not Supported exception</summary>
@@ -2073,6 +2076,13 @@ public static bool TryEmit(Expression expr,
20732076
case ExpressionType.LessThanOrEqual:
20742077
case ExpressionType.Equal:
20752078
case ExpressionType.NotEqual:
2079+
if ((setup & CompilerFlags.EvaluateExpressionIfPossible) != 0 && expr.Type.IsPrimitive &&
2080+
TryEvalExpressionAndCatchExceptions(out var evalResult, expr))
2081+
{
2082+
if ((parent & ParentFlags.IgnoreResult) == 0)
2083+
il.Demit((bool)evalResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
2084+
return true;
2085+
}
20762086
var binaryExpr = (BinaryExpression)expr;
20772087
return TryEmitComparison(binaryExpr.Left, binaryExpr.Right, expr.Type, nodeType, paramExprs, il, ref closure, setup, parent);
20782088

@@ -5559,12 +5569,25 @@ public static object EvalArithmeticOrNull(object left, object right, ExpressionT
55595569
};
55605570
}
55615571

5562-
// todo: @wip #468
5572+
/// <summary>In case of exception FEC will emit the whole computation to throw expection in the invocation phase</summary>
5573+
internal static bool TryEvalExpressionAndCatchExceptions(out object result, Expression expr)
5574+
{
5575+
try
5576+
{
5577+
return TryEvalExpression(out result, expr);
5578+
}
5579+
catch
5580+
{
5581+
result = null;
5582+
return false;
5583+
}
5584+
}
5585+
55635586
internal static bool TryEvalExpression(out object result, Expression expr)
55645587
{
55655588
Debug.Assert(expr.Type.IsPrimitive);
5566-
55675589
result = false;
5590+
55685591
var nodeType = expr.NodeType;
55695592
if (nodeType == ExpressionType.Constant)
55705593
{
@@ -5576,34 +5599,46 @@ internal static bool TryEvalExpression(out object result, Expression expr)
55765599
return true;
55775600
}
55785601

5579-
var isLogical = IsLogical(nodeType);
5580-
var isComparison = IsComparison(nodeType);
5581-
var isArithmetic = IsArithmetic(nodeType);
5582-
if (!isLogical && !isComparison && !isArithmetic)
5583-
return false;
5602+
var exprType = expr.Type;
5603+
if (nodeType == ExpressionType.Convert)
5604+
{
5605+
var unaryExpr = (UnaryExpression)expr;
5606+
var operand = unaryExpr.Operand;
5607+
if (!TryEvalExpression(out var val, operand))
5608+
return false;
5609+
result = (operand.Type == exprType || exprType.IsAssignableFrom(operand.Type)
5610+
? val
5611+
: System.Convert.ChangeType(val, exprType));
5612+
return true;
5613+
}
55845614

5585-
if (isLogical)
5615+
if (nodeType == ExpressionType.Not)
55865616
{
5587-
if (nodeType == ExpressionType.Not)
5588-
{
5589-
var unaryExpr = (UnaryExpression)expr;
5590-
if (!TryEvalExpression(out var boolVal, unaryExpr.Operand))
5591-
return false;
5592-
result = !(bool)boolVal;
5593-
return true;
5594-
}
5617+
var unaryExpr = (UnaryExpression)expr;
5618+
if (!TryEvalExpression(out var boolVal, unaryExpr.Operand))
5619+
return false;
5620+
result = !(bool)boolVal;
5621+
return true;
5622+
}
55955623

5624+
if (IsLogical(nodeType))
5625+
{
55965626
var binaryExpr = (BinaryExpression)expr;
5597-
if (!TryEvalExpression(out var leftVal, binaryExpr.Left) ||
5598-
!TryEvalExpression(out var rightVal, binaryExpr.Right))
5627+
if (!TryEvalExpression(out var leftVal, binaryExpr.Left))
55995628
return false;
5600-
result = nodeType == ExpressionType.AndAlso
5601-
? (bool)leftVal && (bool)rightVal
5602-
: (bool)leftVal || (bool)rightVal;
5603-
return true;
5629+
5630+
// Short circuit the evalution, because this is an actual logic of these logical operations
5631+
if ((bool)leftVal)
5632+
return nodeType == ExpressionType.OrElse
5633+
|| TryEvalExpression(out result, binaryExpr.Right);
5634+
// left is false
5635+
if (nodeType == ExpressionType.AndAlso)
5636+
result = leftVal; // return the false result
5637+
// otherwise for || evaluate the right result
5638+
return TryEvalExpression(out result, binaryExpr.Right);
56045639
}
56055640

5606-
if (isComparison)
5641+
if (IsComparison(nodeType))
56075642
{
56085643
var binaryExpr = (BinaryExpression)expr;
56095644
if (!TryEvalExpression(out var left, binaryExpr.Left) ||
@@ -5616,6 +5651,7 @@ internal static bool TryEvalExpression(out object result, Expression expr)
56165651
result = !left.Equals(right);
56175652
else
56185653
{
5654+
// Assuming that the both sides are of the same type, we can use only the left one for comparison
56195655
var cmp = left as IComparable;
56205656
if (cmp == null)
56215657
return false;
@@ -5632,6 +5668,26 @@ internal static bool TryEvalExpression(out object result, Expression expr)
56325668
return true;
56335669
}
56345670

5671+
if (nodeType == ExpressionType.Negate)
5672+
{
5673+
var unaryExpr = (UnaryExpression)expr;
5674+
if (!TryEvalExpression(out var val, unaryExpr.Operand))
5675+
return false;
5676+
result = EvalNegateOrNull(val);
5677+
return result != null;
5678+
}
5679+
5680+
if (IsArithmetic(nodeType))
5681+
{
5682+
var binaryExpr = (BinaryExpression)expr;
5683+
if (!TryEvalExpression(out var leftVal, binaryExpr.Left) ||
5684+
!TryEvalExpression(out var rightVal, binaryExpr.Right))
5685+
return false;
5686+
5687+
result = EvalArithmeticOrNull(leftVal, rightVal, nodeType);
5688+
return result != null;
5689+
}
5690+
56355691
result = false;
56365692
return false;
56375693
}
@@ -5668,13 +5724,6 @@ private static bool TryEmitComparison(
56685724
var isEqualityOp = nodeType == ExpressionType.Equal | nodeType == ExpressionType.NotEqual;
56695725
if (isEqualityOp)
56705726
{
5671-
// if (leftType.IsPrimitive &&
5672-
// TryReduceArithmeticsOrComparisonOrLogical(out bool result, nodeType, left, right))
5673-
// {
5674-
// il.Demit((bool)result ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
5675-
// return il.EmitPopIfIgnoreResult(parent);
5676-
// }
5677-
56785727
if (leftIsNullable & rightIsNull)
56795728
{
56805729
if (!TryEmit(left, paramExprs, il, ref closure, setup, operandParent))

test/FastExpressionCompiler.IssueTests/Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public void Original_expression(TestContext t)
6464
var ff = expr.CompileFast(false);
6565
ff.PrintIL();
6666
t.IsTrue(ff());
67+
68+
var ffe = expr.CompileFast(false, CompilerFlags.EvaluateExpressionIfPossible);
69+
ffe.PrintIL();
70+
t.IsTrue(ffe());
6771
}
6872

6973
public void Original_expression_with_closure(TestContext t)

0 commit comments

Comments
 (0)