Skip to content

Commit 344fa12

Browse files
authored
Merge pull request #500 from dadhi/copilot/fix-invalidprogramexception-sorting
Add sorting and comparison regression tests as dedicated Issue499 file
2 parents e41b6f4 + d6c559a commit 344fa12

4 files changed

Lines changed: 167 additions & 1 deletion

File tree

src/FastExpressionCompiler.LightExpression/Expression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5160,7 +5160,7 @@ internal TypedManyVariablesBlockExpression(Type type, IReadOnlyList<ParameterExp
51605160
public sealed class LoopExpression : Expression
51615161
{
51625162
public override ExpressionType NodeType => ExpressionType.Loop;
5163-
public override Type Type => typeof(void);
5163+
public override Type Type => BreakLabel?.Type ?? typeof(void);
51645164
public readonly Expression Body;
51655165
public readonly LabelTarget BreakLabel;
51665166
public readonly LabelTarget ContinueLabel;
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using System;
2+
3+
#if LIGHT_EXPRESSION
4+
using FastExpressionCompiler.LightExpression.ImTools;
5+
using static FastExpressionCompiler.LightExpression.Expression;
6+
namespace FastExpressionCompiler.LightExpression.IssueTests;
7+
#else
8+
using System.Linq.Expressions;
9+
using static System.Linq.Expressions.Expression;
10+
namespace FastExpressionCompiler.IssueTests;
11+
#endif
12+
13+
public struct Issue499_InvalidProgramException_for_Sorting_and_comparison_function : ITestX
14+
{
15+
public void Run(TestRun t)
16+
{
17+
Quicksort_partition_with_nested_loops(t);
18+
Comparison_function_with_goto_labels(t);
19+
}
20+
21+
// Reproduces the sorting use-case from https://github.com/dadhi/FastExpressionCompiler/issues/499
22+
// using the same nested-loop pattern as SortCompiler.Compile but minimised to a plain int[].
23+
// The outer loop drives a quicksort-style partition; two inner loops advance the i/j cursors.
24+
public void Quicksort_partition_with_nested_loops(TestContext t)
25+
{
26+
var arr = Parameter(typeof(int[]), "arr");
27+
var low = Parameter(typeof(int), "low");
28+
var high = Parameter(typeof(int), "high");
29+
30+
var i = Variable(typeof(int), "i");
31+
var j = Variable(typeof(int), "j");
32+
var pivot = Variable(typeof(int), "pivot");
33+
var temp = Variable(typeof(int), "temp");
34+
var compareResult = Variable(typeof(int), "compareResult");
35+
36+
var endMain = Label(typeof(void), "endMain");
37+
var endSub1 = Label(typeof(void), "endSub1");
38+
var endSub2 = Label(typeof(void), "endSub2");
39+
var continue1 = Label(typeof(void), "continue1");
40+
var continue2 = Label(typeof(void), "continue2");
41+
42+
var compareToMethod = typeof(int).GetMethod("CompareTo", new[] { typeof(int) });
43+
44+
// Inner loop 1: while arr[i] < pivot { i++ }
45+
// Matches the makeCondition(rowNumbers[i], pivot, Break(endSub1), Block(i++, Continue(continue1))) pattern.
46+
var innerLoop1 = Loop(
47+
Block(
48+
Assign(compareResult, Call(ArrayAccess(arr, i), compareToMethod, pivot)),
49+
IfThen(Equal(compareResult, Constant(-1)),
50+
Block(PostIncrementAssign(i), Continue(continue1))),
51+
Break(endSub1)
52+
),
53+
endSub1, continue1);
54+
55+
// Inner loop 2: while arr[j] > pivot { j-- }
56+
// Matches the makeCondition(pivot, rowNumbers[j], Break(endSub2), Block(j--, Continue(continue2))) pattern.
57+
var innerLoop2 = Loop(
58+
Block(
59+
Assign(compareResult, Call(pivot, compareToMethod, ArrayAccess(arr, j))),
60+
IfThen(Equal(compareResult, Constant(-1)),
61+
Block(PostDecrementAssign(j), Continue(continue2))),
62+
Break(endSub2)
63+
),
64+
endSub2, continue2);
65+
66+
var body = Block(
67+
new[] { i, j, pivot, temp, compareResult },
68+
Assign(i, low),
69+
Assign(j, high),
70+
Assign(pivot, ArrayAccess(arr, Divide(Add(i, j), Constant(2)))),
71+
Loop(
72+
Block(
73+
IfThen(GreaterThan(i, j), Break(endMain)),
74+
innerLoop1,
75+
innerLoop2,
76+
IfThen(LessThanOrEqual(i, j),
77+
Block(
78+
Assign(temp, ArrayAccess(arr, i)),
79+
Assign(ArrayAccess(arr, i), ArrayAccess(arr, j)),
80+
Assign(ArrayAccess(arr, j), temp),
81+
PostIncrementAssign(i),
82+
PostDecrementAssign(j)))
83+
),
84+
endMain));
85+
86+
var expr = Lambda<Action<int[], int, int>>(body, arr, low, high);
87+
expr.PrintCSharp();
88+
89+
int[] data1 = new[] { 3, 1, 4, 1, 5 };
90+
int[] data2 = new[] { 3, 1, 4, 1, 5 };
91+
92+
var fs = expr.CompileSys();
93+
fs.PrintIL();
94+
fs(data1, 0, data1.Length - 1);
95+
96+
var ff = expr.CompileFast(ifFastFailedReturnNull: true);
97+
t.IsNotNull(ff);
98+
ff.PrintIL();
99+
ff(data2, 0, data2.Length - 1);
100+
101+
t.AreEqual(data1, data2);
102+
}
103+
104+
// Reproduces the comparison-function use-case from https://github.com/dadhi/FastExpressionCompiler/issues/499
105+
// using the same Return(label, value) / goto pattern as SortCompiler.CompileComparisonFunction
106+
// but minimised to a plain int comparison (DESC order, like the original failing test).
107+
public void Comparison_function_with_goto_labels(TestContext t)
108+
{
109+
var left = Parameter(typeof(int), "left");
110+
var right = Parameter(typeof(int), "right");
111+
var compareResult = Variable(typeof(int), "compareResult");
112+
113+
var endMain = Label(typeof(int), "endMain");
114+
115+
var compareToMethod = typeof(int).GetMethod("CompareTo", new[] { typeof(int) });
116+
117+
// Mirrors makeCondition(leftIndex, rightIndex, breakExp: Return(endMain, 1), continueExp: Return(endMain, -1))
118+
// for INT DESC without null-handling overhead.
119+
//
120+
// The SortCompiler convention is: -1 = "continueExp" (advance cursor, left value is smaller in sort order),
121+
// 1 = "breakExp" (stop advancing, left value is larger in sort order).
122+
// For DESC order the natural comparison result is negated so that:
123+
// left=3, right=1 → CompareTo=1, negated=-1 → return -1 (3 sorts before 1 in DESC)
124+
// left=1, right=3 → CompareTo=-1, negated=1 → return 1 (1 sorts after 3 in DESC)
125+
//
126+
// allNotNil block:
127+
// compareResult = left.CompareTo(right)
128+
// compareResult = -compareResult (DESC: negate)
129+
// if compareResult == -1: return -1 (continueExp: left comes first)
130+
// (fall through to breakExp)
131+
// breakExp (fallthrough): return 1
132+
// Label: endMain default -1
133+
var body = Block(
134+
new[] { compareResult },
135+
Assign(compareResult, Call(left, compareToMethod, right)),
136+
Assign(compareResult, Negate(compareResult)),
137+
IfThen(Equal(compareResult, Constant(-1)), Return(endMain, Constant(-1))),
138+
Return(endMain, Constant(1)),
139+
Label(endMain, Constant(-1)));
140+
141+
var expr = Lambda<Func<int, int, int>>(body, left, right);
142+
expr.PrintCSharp();
143+
144+
var fs = expr.CompileSys();
145+
fs.PrintIL();
146+
t.AreEqual(-1, fs(3, 1)); // 3 > 1 in DESC → left(3) comes first → -1
147+
t.AreEqual(1, fs(1, 3)); // 1 < 3 in DESC → left(1) comes after → +1
148+
149+
var ff = expr.CompileFast(ifFastFailedReturnNull: true);
150+
t.IsNotNull(ff);
151+
ff.PrintIL();
152+
t.AreEqual(-1, ff(3, 1));
153+
t.AreEqual(1, ff(1, 3));
154+
}
155+
}

test/FastExpressionCompiler.TestsRunner.Net472/Program.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,13 @@ static void RunLightExpressionTests(object state)
3535

3636
var t = (LightExpression.TestRun)state;
3737
t.Run(new LightExpression.IssueTests.Issue183_NullableDecimal());
38+
t.Run(new LightExpression.IssueTests.Issue398_Optimize_Switch_with_OpCodes_Switch());
3839
t.Run(new LightExpression.IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET());
3940
t.Run(new LightExpression.IssueTests.Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation());
4041
t.Run(new LightExpression.IssueTests.Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression());
42+
t.Run(new LightExpression.IssueTests.Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary());
43+
t.Run(new LightExpression.IssueTests.Issue498_InvalidProgramException_when_using_loop());
44+
t.Run(new LightExpression.IssueTests.Issue499_InvalidProgramException_for_Sorting_and_comparison_function());
4145

4246
Console.WriteLine($"Just LightExpression tests are passing in {justLightTestsStopwatch.ElapsedMilliseconds} ms.");
4347
}
@@ -52,6 +56,11 @@ static void RunLightExpressionTests(object state)
5256
fecTests.Run(new Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET());
5357
fecTests.Run(new Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation());
5458
fecTests.Run(new Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression());
59+
fecTests.Run(new Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary());
60+
fecTests.Run(new Issue480_CLR_detected_an_invalid_program_exception());
61+
fecTests.Run(new Issue495_Incomplete_pattern_detection_for_NotSupported_1007_Return_goto_from_TryCatch_with_Assign_generates_invalid_IL());
62+
fecTests.Run(new Issue498_InvalidProgramException_when_using_loop());
63+
fecTests.Run(new Issue499_InvalidProgramException_for_Sorting_and_comparison_function());
5564

5665

5766
Console.WriteLine($"FEC tests are passing in {fecTestsStopwatch.ElapsedMilliseconds} ms.");

test/FastExpressionCompiler.TestsRunner/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public static void Main()
5151

5252
var st = new TestRun(TestFlags.RethrowException);
5353

54+
st.Run(new Issue499_InvalidProgramException_for_Sorting_and_comparison_function());
5455
st.Run(new Issue498_InvalidProgramException_when_using_loop());
5556
st.Run(new Issue495_Incomplete_pattern_detection_for_NotSupported_1007_Return_goto_from_TryCatch_with_Assign_generates_invalid_IL());
5657
st.Run(new Issue480_CLR_detected_an_invalid_program_exception());
@@ -70,6 +71,7 @@ public static void Main()
7071
lt.Run(new LightExpression.IssueTests.Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation());
7172
lt.Run(new LightExpression.IssueTests.Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression());
7273
lt.Run(new LightExpression.IssueTests.Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary());
74+
lt.Run(new LightExpression.IssueTests.Issue499_InvalidProgramException_for_Sorting_and_comparison_function());
7375

7476
RunAllTests();
7577
}

0 commit comments

Comments
 (0)