Skip to content

Commit 8f5cbb6

Browse files
committed
trying #472
1 parent eb0ceb1 commit 8f5cbb6

File tree

4 files changed

+104
-52
lines changed

4 files changed

+104
-52
lines changed

src/FastExpressionCompiler.LightExpression/Expression.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,6 @@ public static ConstantExpression ConstantNull(Type type = null) =>
221221
public static ConstantExpression ConstantOf<T>(T value) =>
222222
value == null ? ConstantNull<T>() : new ValueConstantExpression<T>(value);
223223

224-
[MethodImpl((MethodImplOptions)256)]
225-
public static int TryGetIntConstantValue(Expression e) => ((IntConstantExpression)e).IntValue;
226-
227224
[RequiresUnreferencedCode(Trimming.Message)]
228225
public static NewExpression New(Type type)
229226
{
@@ -3914,9 +3911,8 @@ public sealed class TypedValueConstantExpression : ConstantExpression
39143911
public sealed class IntConstantExpression : ConstantExpression
39153912
{
39163913
public override Type Type => typeof(int);
3917-
public override object Value => IntValue;
3918-
public readonly int IntValue;
3919-
internal IntConstantExpression(int value) => IntValue = value;
3914+
public override object Value { get; }
3915+
internal IntConstantExpression(int value) => Value = value;
39203916
}
39213917

39223918
public class NewExpression : Expression, IArgumentProvider

src/FastExpressionCompiler/FastExpressionCompiler.cs

Lines changed: 98 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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

test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void Setup()
9999
var expr = IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.CreateExpression();
100100
_compiled = expr.CompileSys();
101101
_compiledFast = expr.CompileFast();
102-
_compiledFastWithEvalFlag = expr.CompileFast(flags: CompilerFlags.TryEvalPureArithmeticAndLogic);
102+
_compiledFastWithEvalFlag = expr.CompileFast(flags: CompilerFlags.DisableInterpreter);
103103
}
104104

105105
[Benchmark(Baseline = true)]
@@ -191,7 +191,7 @@ public object CompiledFast()
191191
[Benchmark(Baseline = true)]
192192
public object CompiledFast_WithEvalFlag()
193193
{
194-
return _expr.CompileFast(flags: CompilerFlags.TryEvalPureArithmeticAndLogic);
194+
return _expr.CompileFast(flags: CompilerFlags.DisableInterpreter);
195195
}
196196
}
197197

@@ -217,6 +217,6 @@ public void Setup()
217217
[Benchmark]
218218
public object Optimized()
219219
{
220-
return ExpressionCompiler.Interpreter.TryEvalPrimitive(out var result, _expr) ? result : null;
220+
return ExpressionCompiler.Interpreter.TryInterpretPrimitive(out var result, _expr) ? result : null;
221221
}
222222
}

test/FastExpressionCompiler.IssueTests/Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public void Original_expression(TestContext t)
6565
ff.PrintIL();
6666
t.IsTrue(ff());
6767

68-
var ffe = expr.CompileFast(false, CompilerFlags.TryEvalPureArithmeticAndLogic);
68+
var ffe = expr.CompileFast(false, CompilerFlags.DisableInterpreter);
6969
ffe.PrintIL();
7070
t.IsTrue(ffe());
7171
}

0 commit comments

Comments
 (0)