|
| 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 | +} |
0 commit comments