Skip to content

Commit adc2d46

Browse files
jogibear9988dadhi
authored andcommitted
simple Label & Goto support (dadhi#103)
* feature - helper to save to file * fix dadhi#102 - introduce label and goto support * feature - test for dadhi#100 * feature - tests for dadhi#78 * bugfix - fixes dadhi#78 * bugfix - remove save to assembly * bugfix : expression save helper only for net46 * feature - include a editorconfig , fixes dadhi#97 * remove doubled test * bugfix - nullable char scenario from linq2db * optimize dictionary to keyvalue array * optimize code * label not found should throw
1 parent 76b7c91 commit adc2d46

9 files changed

+220
-15
lines changed

.editorconfig

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4

src/FastExpressionCompiler/FastExpressionCompiler.cs

+76-1
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,10 @@ private static object TryCompile(ref ClosureInfo closureInfo,
364364
else
365365
return null;
366366

367+
if (closureInfo.LabelCount > 0)
368+
closureInfo.Labels = new KeyValuePair<object, Label>[closureInfo.LabelCount];
369+
closureInfo.LabelCount = 0;
370+
367371
var closureType = closureInfo.ClosureType;
368372
var methodParamTypes = closureType == null ? paramTypes : GetClosureAndParamTypes(paramTypes, closureType);
369373

@@ -476,6 +480,10 @@ private struct ClosureInfo
476480
// Helper to decide whether we are inside the block or not
477481
public BlockInfo CurrentBlock;
478482

483+
public int LabelCount;
484+
// Dictionary for the used Labels in IL
485+
public KeyValuePair<object, Label>[] Labels;
486+
479487
// Populates info directly with provided closure object and constants.
480488
public ClosureInfo(bool isConstructed, object closure = null, object[] closureConstantExpressions = null)
481489
{
@@ -484,6 +492,8 @@ public ClosureInfo(bool isConstructed, object closure = null, object[] closureCo
484492
NonPassedParameters = Tools.Empty<object>();
485493
NestedLambdas = Tools.Empty<NestedLambdaInfo>();
486494
CurrentBlock = BlockInfo.Empty;
495+
Labels = null;
496+
LabelCount = 0;
487497

488498
if (closure == null)
489499
{
@@ -986,6 +996,17 @@ private static bool TryCollectBoundConstants(ref ClosureInfo closure,
986996
? TryCollectTryExprConstants(ref closure, (TryExpression)exprObj, paramExprs)
987997
: TryCollectTryExprInfoConstants(ref closure, (TryExpressionInfo)exprObj, paramExprs);
988998

999+
case ExpressionType.Label:
1000+
closure.LabelCount++;
1001+
var defaultValueExpr = ((LabelExpression)exprObj).DefaultValue;
1002+
return defaultValueExpr == null
1003+
|| TryCollectBoundConstants(ref closure, defaultValueExpr, defaultValueExpr.NodeType, paramExprs);
1004+
1005+
case ExpressionType.Goto:
1006+
var gotoValueExpr = ((GotoExpression)exprObj).Value;
1007+
return gotoValueExpr == null
1008+
|| TryCollectBoundConstants(ref closure, gotoValueExpr, gotoValueExpr.NodeType, paramExprs);
1009+
9891010
case ExpressionType.Default:
9901011
return true;
9911012

@@ -1273,7 +1294,7 @@ private static class EmittingVisitor
12731294
.DeclaredMethods.First(m => m.IsStatic && m.Name == "Equals");
12741295

12751296
public static bool TryEmit(object exprObj, ExpressionType exprNodeType, Type exprType,
1276-
object[] paramExprs, ILGenerator il, ref ClosureInfo closure, ExpressionType parent, int byRefIndex = -1)
1297+
object[] paramExprs, ILGenerator il, ref ClosureInfo closure, ExpressionType parent, int byRefIndex = -1)
12771298
{
12781299
switch (exprNodeType)
12791300
{
@@ -1346,11 +1367,58 @@ public static bool TryEmit(object exprObj, ExpressionType exprNodeType, Type exp
13461367
case ExpressionType.Index:
13471368
return TryEmitIndex((IndexExpression)exprObj, exprType, paramExprs, il, ref closure);
13481369

1370+
case ExpressionType.Goto:
1371+
return TryEmitGoto((GotoExpression)exprObj, exprType, paramExprs, il, ref closure);
1372+
1373+
case ExpressionType.Label:
1374+
return TryEmitLabel((LabelExpression)exprObj, exprType, paramExprs, il, ref closure);
1375+
13491376
default:
13501377
return false;
13511378
}
13521379
}
13531380

1381+
private static bool TryEmitLabel(LabelExpression exprObj, Type elemType,
1382+
object[] paramExprs, ILGenerator il, ref ClosureInfo closure)
1383+
{
1384+
var lbl = closure.Labels.FirstOrDefault(x => x.Key == exprObj.Target);
1385+
if (lbl.Key != exprObj.Target)
1386+
{
1387+
lbl = new KeyValuePair<object, Label>(exprObj.Target, il.DefineLabel());
1388+
closure.Labels[closure.LabelCount++] = lbl;
1389+
}
1390+
il.MarkLabel(lbl.Value);
1391+
1392+
if (exprObj.DefaultValue != null && !TryEmit(exprObj.DefaultValue, exprObj.DefaultValue.NodeType, exprObj.DefaultValue.Type, paramExprs, il, ref closure, ExpressionType.Label))
1393+
return false;
1394+
return true;
1395+
}
1396+
1397+
private static bool TryEmitGoto(GotoExpression exprObj, Type elemType,
1398+
object[] paramExprs, ILGenerator il, ref ClosureInfo closure) //todo : GotoExpression.Value
1399+
{
1400+
if (closure.Labels == null)
1401+
throw new InvalidOperationException("cannot jump, no labels found");
1402+
1403+
var lbl = closure.Labels.FirstOrDefault(x => x.Key == exprObj.Target);
1404+
if (lbl.Key != exprObj.Target)
1405+
{
1406+
if(closure.Labels.Length == closure.LabelCount - 1)
1407+
throw new InvalidOperationException("Cannot jump, not all labels found");
1408+
1409+
lbl = new KeyValuePair<object, Label>(exprObj.Target, il.DefineLabel());
1410+
closure.Labels[closure.LabelCount++] = lbl;
1411+
}
1412+
1413+
if (exprObj.Kind == GotoExpressionKind.Goto)
1414+
{
1415+
il.Emit(OpCodes.Br, lbl.Value);
1416+
return true;
1417+
}
1418+
1419+
return false;
1420+
}
1421+
13541422
private static bool TryEmitIndex(IndexExpression exprObj, Type elemType,
13551423
object[] paramExprs, ILGenerator il, ref ClosureInfo closure)
13561424
{
@@ -1815,6 +1883,13 @@ private static bool TryEmitConvert(object exprObj, Type targetType,
18151883

18161884
if (targetType == typeof(object))
18171885
{
1886+
var nullableType = Nullable.GetUnderlyingType(sourceType);
1887+
if (nullableType != null)
1888+
{
1889+
il.Emit(OpCodes.Newobj, sourceType.GetTypeInfo().DeclaredConstructors.First());
1890+
il.Emit(OpCodes.Box, sourceType);
1891+
return true;
1892+
}
18181893
// for value type to object, just box a value, otherwise do nothing - everything is object anyway
18191894
if (sourceType.GetTypeInfo().IsValueType)
18201895
il.Emit(OpCodes.Box, sourceType);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq.Expressions;
4+
using System.Reflection;
5+
using System.Reflection.Emit;
6+
7+
namespace FastExpressionCompiler.IssueTests
8+
{
9+
#if NET46
10+
public static class ExpressionSaveHelper
11+
{
12+
public static void SaveToAssembly<T>(this Expression<T> lambda, string file)
13+
{
14+
AssemblyName assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(file));
15+
AssemblyBuilder dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save, Path.GetDirectoryName(file));
16+
var dynamicModule = dynamicAssembly.DefineDynamicModule(assemblyName + "_module", assemblyName + ".dll");
17+
var dynamicType = dynamicModule.DefineType(assemblyName + "_type");
18+
19+
lambda.CompileToMethod(dynamicType.DefineMethod(assemblyName.Name+"_method", MethodAttributes.Public | MethodAttributes.Static));
20+
dynamicType.CreateType();
21+
dynamicAssembly.Save(Path.GetFileName(file));
22+
}
23+
}
24+
#endif
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using NUnit.Framework;
2+
3+
namespace FastExpressionCompiler.IssueTests
4+
{
5+
class Issue100_ExpressionInfo_wrong_return_type
6+
{
7+
delegate void ActionRef<T>(ref T a1);
8+
9+
[Test, Ignore("needs fix")]
10+
public void RefAssignExpInfo()
11+
{
12+
var objRef = FastExpressionCompiler.ExpressionInfo.Parameter(typeof(double).MakeByRefType());
13+
var lambda = FastExpressionCompiler.ExpressionInfo.Lambda<ActionRef<double>>(FastExpressionCompiler.ExpressionInfo.Assign(objRef, FastExpressionCompiler.ExpressionInfo.Add(objRef, FastExpressionCompiler.ExpressionInfo.Constant((double)3.0))), objRef);
14+
15+
var compiledB = lambda.CompileFast(true);
16+
var exampleB = 5.0;
17+
compiledB(ref exampleB);
18+
Assert.AreEqual(8.0, exampleB);
19+
}
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Linq.Expressions;
3+
using NUnit.Framework;
4+
using System.Linq.Expressions;
5+
using static System.Linq.Expressions.Expression;
6+
7+
namespace FastExpressionCompiler.IssueTests
8+
{
9+
class Issue102_Label_and_Goto_Expression
10+
{
11+
[Test]
12+
public void BlockWithGotoIsSupported()
13+
{
14+
LabelTarget returnTarget = Expression.Label("aaa");
15+
16+
BlockExpression blockExpr =
17+
Expression.Block(
18+
Expression.Call(typeof(Console).GetMethod("WriteLine", new[] {typeof(string)}),
19+
Expression.Constant("GoTo")),
20+
Expression.Goto(returnTarget),
21+
Expression.Call(typeof(Console).GetMethod("WriteLine", new[] {typeof(string)}),
22+
Expression.Constant("Other Work")),
23+
Expression.Label(returnTarget),
24+
Expression.Constant(7)
25+
);
26+
27+
var lambda = Expression.Lambda<Func<int>>(blockExpr);
28+
var fastCompiled = lambda.CompileFast<Func<int>>(true);
29+
Assert.NotNull(fastCompiled);
30+
}
31+
32+
[Test]
33+
public void UnkownLabelShouldThrow()
34+
{
35+
var lambda = Expression.Lambda(
36+
Expression.Return(Expression.Label(), Expression.Constant(1)));
37+
38+
Assert.Throws<InvalidOperationException>(() => lambda.Compile());
39+
Assert.Throws<InvalidOperationException>(() => lambda.CompileFast(true));
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq.Expressions;
4+
using NUnit.Framework;
5+
using static System.Linq.Expressions.Expression;
6+
7+
namespace FastExpressionCompiler.IssueTests
8+
{
9+
class Issue78_blocks_with_constant_return
10+
{
11+
[Test]
12+
public void BlockWithConstanReturnIsSupported()
13+
{
14+
var ret = Block(Label(Label(typeof(int)), Constant(7)));
15+
var lambda = Lambda<Func<int>>(ret);
16+
var compiled = lambda.Compile();
17+
var value1 = compiled();
18+
Assert.AreEqual(7, value1);
19+
var fastCompiled = lambda.CompileFast<Func<int>>(true);
20+
Assert.IsNotNull(fastCompiled);
21+
Assert.AreEqual(7, fastCompiled());
22+
}
23+
24+
[Test]
25+
public void ConstantReturnIsSupported()
26+
{
27+
var lambda = Lambda<Func<int>>(Label(Label(typeof(int)), Constant(7)));
28+
var compiled = lambda.Compile();
29+
var value1 = compiled();
30+
Assert.AreEqual(7, value1);
31+
var fastCompiled = lambda.CompileFast<Func<int>>(true);
32+
Assert.IsNotNull(fastCompiled);
33+
Assert.AreEqual(7, fastCompiled());
34+
}
35+
36+
[Test]
37+
public void ConstantReturnIsSupported2()
38+
{
39+
var varr = Variable(typeof(int), "xxx");
40+
var assign = Assign(varr, Constant(7));
41+
var lambda = Lambda<Func<int>>(Block(new List<ParameterExpression> { varr }, assign, Label(Label(typeof(int)), varr)));
42+
var compiled = lambda.Compile();
43+
var value1 = compiled();
44+
Assert.AreEqual(7, value1);
45+
var fastCompiled = lambda.CompileFast<Func<int>>(true);
46+
Assert.IsNotNull(fastCompiled);
47+
Assert.AreEqual(7, fastCompiled());
48+
}
49+
}
50+
}

test/FastExpressionCompiler.IssueTests/Issue83_linq2db.cs

-1
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ public void Enum_to_enum_conversion()
281281
}
282282

283283
[Test]
284-
[Ignore("Test kills test runner process")]
285284
public void AccessViolationException_on_nullable_char_convert_to_object()
286285
{
287286
var body = Expression.Convert(

test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs

-12
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,6 @@ public void RefAssign()
2727
Assert.AreEqual(8.0, exampleB);
2828
}
2929

30-
[Test, Ignore("needs fix")]
31-
public void RefAssignExpInfo()
32-
{
33-
var objRef = FastExpressionCompiler.ExpressionInfo.Parameter(typeof(double).MakeByRefType());
34-
var lambda = FastExpressionCompiler.ExpressionInfo.Lambda<ActionRef<double>>(FastExpressionCompiler.ExpressionInfo.Assign(objRef, FastExpressionCompiler.ExpressionInfo.Add(objRef, FastExpressionCompiler.ExpressionInfo.Constant((double)3.0))), objRef);
35-
36-
var compiledB = lambda.CompileFast(true);
37-
var exampleB = 5.0;
38-
compiledB(ref exampleB);
39-
Assert.AreEqual(8.0, exampleB);
40-
}
41-
4230
[Test]
4331
public void NullComparisonTest()
4432
{

test/FastExpressionCompiler.UnitTests/ConditionalOperatorsTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public void Ternarary_operator_with_logical_op()
131131
Assert.AreEqual(string.Concat(s, "ccc"), dlg());
132132
}
133133

134-
[Test]
134+
[Test, Ignore("Return & Label are now supported")]
135135
public void CompileFast_should_return_null_when_option_is_set_and_expression_type_is_not_supported()
136136
{
137137
Assert.IsNull(Lambda(

0 commit comments

Comments
 (0)