Skip to content

Commit 7d74cd0

Browse files
author
maximv
committed
fixed: dadhi#255 Nested Lambda Invoke does not work in Block
1 parent b0ac51a commit 7d74cd0

File tree

3 files changed

+111
-48
lines changed

3 files changed

+111
-48
lines changed

src/FastExpressionCompiler/FastExpressionCompiler.cs

+14-14
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ public bool IsTryReturnLabel(int index)
744744
public static ConstructorInfo ArrayClosureWithNonPassedParamsConstructorWithoutConstants = _nonPassedParamsArrayClosureCtors[1];
745745
public class ArrayClosure
746746
{
747-
public readonly object[] ConstantsAndNestedLambdas; // todo: @incomplete split into two to reduce copying
747+
public readonly object[] ConstantsAndNestedLambdas; // todo: @feature split into two to reduce copying - it mostly need to set up nested lamdbas and constants externally without closure collecting phase
748748
public ArrayClosure(object[] constantsAndNestedLambdas) => ConstantsAndNestedLambdas = constantsAndNestedLambdas;
749749
}
750750

@@ -2611,12 +2611,13 @@ private static bool TryEmitNotNullConstant(
26112611
if (constIndex == -1)
26122612
return false;
26132613

2614+
// todo: @incomplete - are we still using the usage to decide for the vars?
26142615
var varIndex = closure.ConstantUsageThenVarIndex.Items[constIndex] - 1;
26152616
if (varIndex > 0)
26162617
EmitLoadLocalVariable(il, varIndex);
26172618
else
26182619
{
2619-
il.Emit(OpCodes.Ldloc_0); // load constants array from variable
2620+
il.Emit(OpCodes.Ldloc_0); // load constants array from the 0 variable // todo: @incomplete until we optimize for a single constant case - then we need a check here for number of constants
26202621
EmitLoadConstantInt(il, constIndex);
26212622
il.Emit(OpCodes.Ldelem_Ref);
26222623
if (exprType.IsValueType())
@@ -2657,7 +2658,7 @@ private static bool TryEmitNotNullConstant(
26572658
return true;
26582659
}
26592660

2660-
// todo: can we do something about boxing?
2661+
// todo: @perf can we do something about boxing?
26612662
private static bool TryEmitNumberConstant(ILGenerator il, object constantValue, Type constValueType)
26622663
{
26632664
if (constValueType == typeof(int))
@@ -2768,6 +2769,7 @@ internal static bool TryEmitNumberOne(ILGenerator il, Type type)
27682769

27692770
internal static void EmitLoadConstantsAndNestedLambdasIntoVars(ILGenerator il, ref ClosureInfo closure)
27702771
{
2772+
// todo: @perf load the field to `var` only if the constants are more than 1
27712773
// Load constants array field from Closure and store it into the variable
27722774
il.Emit(OpCodes.Ldarg_0);
27732775
il.Emit(OpCodes.Ldfld, ArrayClosureArrayField);
@@ -2780,7 +2782,7 @@ internal static void EmitLoadConstantsAndNestedLambdasIntoVars(ILGenerator il, r
27802782
int varIndex;
27812783
for (var i = 0; i < constCount; i++)
27822784
{
2783-
if (constUsage[i] > 1)
2785+
if (constUsage[i] > 1) // todo: @incomplete should we proceed to do this or simplify an remove the usages for the closure info?
27842786
{
27852787
il.Emit(OpCodes.Ldloc_0);// SHOULD BE always at 0 locaton; load array field variable on the stack
27862788
EmitLoadConstantInt(il, i);
@@ -3241,8 +3243,8 @@ private static bool TryEmitAssign(BinaryExpression expr,
32413243
// if parameter isn't passed, then it is passed into some outer lambda or it is a local variable,
32423244
// so it should be loaded from closure or from the locals. Then the closure is null will be an invalid state.
32433245
// if it's a local variable, then store the right value in it
3244-
var localVariableIdx = closure.GetDefinedLocalVarOrDefault(leftParamExpr); // todo: @bug ? does the var index takes into account loading constants into vars?
3245-
if (localVariableIdx != -1)
3246+
var localVarIndex = closure.GetDefinedLocalVarOrDefault(leftParamExpr);
3247+
if (localVarIndex != -1)
32463248
{
32473249
if (!TryEmit(right, paramExprs, il, ref closure, flags))
32483250
return false;
@@ -3253,7 +3255,7 @@ private static bool TryEmitAssign(BinaryExpression expr,
32533255
if ((parent & ParentFlags.IgnoreResult) == 0) // if we have to push the result back, duplicate the right value
32543256
il.Emit(OpCodes.Dup);
32553257

3256-
EmitStoreLocalVariable(il, localVariableIdx);
3258+
EmitStoreLocalVariable(il, localVarIndex);
32573259
return true;
32583260
}
32593261

@@ -3638,10 +3640,6 @@ private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr,
36383640
EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex); // load the variable for the second time
36393641
il.Emit(OpCodes.Ldfld, NestedLambdaWithConstantsAndNestedLambdas.ConstantsAndNestedLambdasField);
36403642
}
3641-
else
3642-
{
3643-
// we are already loaded the nested lambda, because it is not wrapped in NestedLambdaWithConstantsAndNestedLambdas
3644-
}
36453643

36463644
// - create `NonPassedParameters` array
36473645
EmitLoadConstantInt(il, nestedNonPassedParams.Length); // load the length of array
@@ -3680,10 +3678,12 @@ private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr,
36803678
if (outerNonPassedParams.Length == 0)
36813679
return false; // impossible, better to throw?
36823680

3683-
var variableIdx = closure.GetDefinedLocalVarOrDefault(nestedParam); // rename to paramVarIndex
3684-
if (variableIdx != -1) // it's a local variable
3681+
var outerLocalVarIndex = closure.GetDefinedLocalVarOrDefault(nestedParam);
3682+
if (outerLocalVarIndex != -1) // it's a local variable
36853683
{
3686-
EmitLoadLocalVariable(il, variableIdx);
3684+
EmitLoadLocalVariable(il, outerLocalVarIndex);
3685+
if (nestedParam.Type.IsValueType()) // don't forget to box the value type when we store it into object array, (fixes #255)
3686+
il.Emit(OpCodes.Box, nestedParam.Type);
36873687
}
36883688
else // it's a parameter from the outer closure
36893689
{

test/FastExpressionCompiler.TestsRunner/Program.cs

-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ public class Program
1010
public static void Main()
1111
{
1212
RunAllTests();
13-
14-
//new FastExpressionCompiler.LightExpression.UnitTests.BlockTests().Run();
1513
}
1614

1715
public static void RunAllTests()
@@ -85,7 +83,6 @@ void Run(Func<int> run, string name = null)
8583
Run(new FastExpressionCompiler.LightExpression.UnitTests.NestedLambdasSharedToExpressionCodeStringTest().Run);
8684
});
8785

88-
8986
var issueTests = Task.Run(() =>
9087
{
9188
Run(new Issue14_String_constant_comparisons_fail().Run);

test/FastExpressionCompiler.UnitTests/BlockTests.cs

+97-31
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ public int Run()
2727
Block_local_variable_assignment_with_member_init();
2828
Block_local_variable_assignment_with_member_init_array_closure();
2929
Block_local_variable_assignment_with_lambda_invoke();
30-
31-
// todo: @fix #255 Nested Lambda Invoke does not work in Block
32-
// Block_local_variable_assignment_returning_the_nested_lambda_which_reassigns_the_variable();
33-
34-
// Block_local_variable_assignment_with_lambda_invoke_plus_external_assignment();
35-
// Block_local_variable_assignment_with_lambda_invoke_array_closure();
36-
// Block_local_variable_assignment_with_lambda_invoke_with_param_override();
37-
// Block_local_variable_assignment_with_lambda_invoke_with_param_override_array_closure();
38-
39-
return 13;
30+
Block_returning_the_nested_lambda_assigning_the_outer_parameter();
31+
Block_calling_non_void_method_returning_the_nested_lambda_assigning_the_outer_parameter();
32+
Block_assigning_local_variable_then_returning_the_nested_lambda_which_reassigns_the_variable();
33+
Block_assigning_local_ValueType_variable_then_returning_the_nested_lambda_which_reassigns_the_variable();
34+
Block_local_variable_assignment_with_lambda_invoke_plus_external_assignment();
35+
Block_local_variable_assignment_with_lambda_invoke_array_closure();
36+
Block_local_variable_assignment_with_lambda_invoke_with_param_override();
37+
Block_local_variable_assignment_with_lambda_invoke_with_param_override_array_closure();
38+
39+
return 17;
4040
}
4141

4242
[Test]
@@ -98,43 +98,72 @@ public void Block_local_variable_assignment_with_lambda_invoke()
9898
Assert.AreEqual(6, f());
9999
}
100100

101-
// todo: @fix Invoke CLR crash
101+
public static int Inc(int i) => i + 1;
102+
102103
[Test]
103-
public void Block_local_variable_assignment_returning_the_nested_lambda_which_reassigns_the_variable()
104+
public void Block_calling_non_void_method_returning_the_nested_lambda_assigning_the_outer_parameter()
104105
{
105-
var variable = Variable(typeof(int));
106-
107-
var block = Block(new[] { variable },
108-
Assign(variable, Constant(35)),
109-
Lambda(Assign(variable, Constant(42))));
106+
var p = Parameter(typeof(int), "p");
110107

111-
var lambda = Lambda<Func<Func<int>>>(block);
108+
var lambda = Lambda<Func<int, Func<int>>>(
109+
Block(
110+
Call(null, GetType().GetTypeInfo().GetDeclaredMethod(nameof(Inc)), p),
111+
Lambda(Assign(p, Constant(42)))
112+
),
113+
p);
112114

113115
#if LIGHT_EXPRESSION
114116
lambda.PrintCSharpString();
115117
#endif
116-
117118
var s = lambda.CompileSys();
118119
s.PrintIL("system il");
119120

120-
var f = lambda.CompileFast<Func<Func<int>>>(true);
121+
var f = lambda.CompileFast<Func<int, Func<int>>>(true);
121122
f.PrintIL("fec il");
122123

123124
Assert.NotNull(f);
124-
Assert.AreEqual(6, f()());
125+
var ff = f(17);
126+
Assert.IsInstanceOf<Func<int>>(ff);
127+
128+
Assert.AreEqual(42, ff());
125129
}
126130

127-
// todo: @fix Invoke CLR crash
128131
[Test]
129-
public void Block_local_variable_assignment_with_lambda_invoke_plus_external_assignment()
132+
public void Block_returning_the_nested_lambda_assigning_the_outer_parameter()
130133
{
131-
var variable = Variable(typeof(int));
134+
var p = Parameter(typeof(int), "p");
132135

133-
var block = Block(new[] { variable },
134-
Assign(variable, Constant(5)),
135-
Invoke(Lambda(Assign(variable, Constant(6)))));
136+
var lambda = Lambda<Func<int, Func<int>>>(
137+
Lambda(Assign(p, Constant(42))),
138+
p);
136139

137-
var lambda = Lambda<Func<int>>(block);
140+
#if LIGHT_EXPRESSION
141+
lambda.PrintCSharpString();
142+
#endif
143+
var s = lambda.CompileSys();
144+
s.PrintIL("system il");
145+
146+
var f = lambda.CompileFast<Func<int, Func<int>>>(true);
147+
f.PrintIL("fec il");
148+
149+
Assert.NotNull(f);
150+
var ff = f(17);
151+
Assert.IsInstanceOf<Func<int>>(ff);
152+
153+
Assert.AreEqual(42, ff());
154+
}
155+
156+
[Test]
157+
public void Block_assigning_local_variable_then_returning_the_nested_lambda_which_reassigns_the_variable()
158+
{
159+
var variable = Variable(typeof(string));
160+
161+
var lambda = Lambda<Func<Func<string>>>(
162+
Block(new[] { variable },
163+
Assign(variable, Constant("35")),
164+
Lambda(Assign(variable, Constant("42")))
165+
)
166+
);
138167

139168
#if LIGHT_EXPRESSION
140169
lambda.PrintCSharpString();
@@ -143,11 +172,48 @@ public void Block_local_variable_assignment_with_lambda_invoke_plus_external_ass
143172
var s = lambda.CompileSys();
144173
s.PrintIL("system il");
145174

146-
var f = lambda.CompileFast<Func<int>>(true);
175+
var f = lambda.CompileFast(true);
147176
f.PrintIL("fec il");
148177

149-
// Assert.NotNull(fastCompiled);
150-
// Assert.AreEqual(6, fastCompiled());
178+
Assert.NotNull(f);
179+
var ff = f();
180+
Assert.IsInstanceOf<Func<string>>(ff);
181+
Assert.AreEqual("42", ff());
182+
}
183+
184+
[Test]
185+
public void Block_assigning_local_ValueType_variable_then_returning_the_nested_lambda_which_reassigns_the_variable()
186+
{
187+
var variable = Variable(typeof(int));
188+
189+
var lambda = Lambda<Func<Func<int>>>(
190+
Block(new[] { variable },
191+
Assign(variable, Constant(35)),
192+
Lambda(Assign(variable, Constant(42)))
193+
)
194+
);
195+
196+
var f = lambda.CompileFast(true);
197+
Assert.NotNull(f);
198+
199+
var ff = f();
200+
Assert.IsInstanceOf<Func<int>>(ff);
201+
Assert.AreEqual(42, ff());
202+
}
203+
204+
[Test]
205+
public void Block_local_variable_assignment_with_lambda_invoke_plus_external_assignment()
206+
{
207+
var variable = Variable(typeof(int));
208+
209+
var block = Block(new[] { variable },
210+
Assign(variable, Constant(5)),
211+
Invoke(Lambda(Assign(variable, Constant(6)))));
212+
213+
var lambda = Lambda<Func<int>>(block);
214+
215+
var f = lambda.CompileFast<Func<int>>(true);
216+
Assert.AreEqual(6, f());
151217
}
152218

153219
[Test]

0 commit comments

Comments
 (0)