diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
index ad07b3fe..f6821ab5 100644
--- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
+++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
@@ -709,6 +709,40 @@ public SysExpr ToExpression() =>
[RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)]
public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression());
+ /// Returns true when all reachable nodes are compacted into a canonical post-order layout.
+ ///
+ /// Canonical post-order means that each child is placed before its parent, the root is the last node,
+ /// and there are no unreachable nodes left in .
+ ///
+ public bool IsInOrder()
+ {
+ if (Nodes.Count == 0)
+ return true;
+ if ((uint)RootIdx >= (uint)Nodes.Count)
+ return false;
+
+ var visitStates = new byte[Nodes.Count];
+ var expectedIdx = 0;
+ if (!TryValidateCanonicalPostOrder(RootIdx, visitStates, ref expectedIdx))
+ return false;
+ return expectedIdx == Nodes.Count;
+ }
+
+ /// Compacts the current tree into the canonical post-order layout and drops unreachable nodes.
+ /// The number of removed unreachable nodes.
+ [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)]
+ public int PutInOrder()
+ {
+ if (Nodes.Count == 0)
+ return 0;
+ if (IsInOrder())
+ return 0;
+
+ var originalNodeCount = Nodes.Count;
+ this = FromExpression(ToExpression());
+ return originalNodeCount - Nodes.Count;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) =>
AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child));
@@ -1408,6 +1442,12 @@ private static Type GetArrayElementType(Type arrayType, int depth)
return elementType ?? typeof(object);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool HasStructuralChildren(in ExprNode node) =>
+ node.ChildCount != 0 &&
+ !ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) &&
+ node.Kind != ExprNodeKind.UInt16Pair;
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int CloneChild(int idx)
{
@@ -1606,6 +1646,39 @@ private static bool Contains(ref SmallList
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ushort ToStoredUShortIdx(int idx) => checked((ushort)idx);
+ private bool TryValidateCanonicalPostOrder(
+ int idx,
+ byte[] visitStates,
+ ref int expectedIdx)
+ {
+ if ((uint)idx >= (uint)Nodes.Count)
+ return false;
+
+ var visitState = visitStates[idx];
+ if (visitState == 2)
+ return true;
+ if (visitState == 1)
+ return false;
+
+ visitStates[idx] = 1;
+
+ ref var node = ref Nodes.GetSurePresentRef(idx);
+ if (HasStructuralChildren(in node))
+ {
+ var childIdx = node.ChildIdx;
+ for (var i = 0; i < node.ChildCount; ++i)
+ {
+ var currentChildIdx = childIdx;
+ if (!TryValidateCanonicalPostOrder(currentChildIdx, visitStates, ref expectedIdx))
+ return false;
+ childIdx = Nodes.GetSurePresentRef(currentChildIdx).NextIdx;
+ }
+ }
+
+ visitStates[idx] = 2;
+ return idx == expectedIdx++;
+ }
+
/// Reconstructs System.Linq nodes from the flat representation while reusing parameter and label identities.
private struct Reader
{
diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs
index 3e3ff694..a7ad89ca 100644
--- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs
+++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs
@@ -55,7 +55,9 @@ public int Run()
Flat_blocks_with_variables_tracked_from_expression_conversion();
Flat_goto_and_label_nodes_tracked_from_expression_conversion();
Flat_try_catch_nodes_tracked_from_expression_conversion();
- return 38;
+ Flat_expression_created_from_conversion_is_in_canonical_order();
+ Flat_expression_put_in_order_rebuilds_direct_helper_tree_with_compact_indexes();
+ return 40;
}
@@ -1023,5 +1025,52 @@ public void Flat_try_catch_nodes_tracked_from_expression_conversion()
Asserts.AreEqual(1, fe.TryCatchNodes.Count);
}
+
+ public void Flat_expression_created_from_conversion_is_in_canonical_order()
+ {
+ var fe = CreateComplexLightExpression("state").ToFlatExpression();
+
+ Asserts.IsTrue(fe.IsInOrder());
+ }
+
+ public void Flat_expression_put_in_order_rebuilds_direct_helper_tree_with_compact_indexes()
+ {
+ var fe = default(ExprTree);
+ var parameter = fe.ParameterOf("p");
+ var one = fe.ConstantInt(1);
+ var add = fe.Add(parameter, one);
+ fe.RootIdx = fe.Lambda>(add, parameter);
+
+ Asserts.AreEqual(0, parameter);
+ Asserts.AreEqual(1, one);
+ Asserts.AreEqual(4, add);
+ Asserts.AreEqual(6, fe.RootIdx);
+ Asserts.AreEqual(7, fe.Nodes.Count);
+ Asserts.IsFalse(fe.IsInOrder());
+
+ var removedNodes = fe.PutInOrder();
+
+ Asserts.AreEqual(2, removedNodes);
+ Asserts.IsTrue(fe.IsInOrder());
+ Asserts.AreEqual(5, fe.Nodes.Count);
+ Asserts.AreEqual(4, fe.RootIdx);
+ Asserts.AreEqual(1, fe.LambdaNodes.Count);
+ Asserts.AreEqual(fe.RootIdx, fe.LambdaNodes[0]);
+ Asserts.AreEqual(0, fe.GotoNodes.Count);
+ Asserts.AreEqual(0, fe.LabelNodes.Count);
+ Asserts.AreEqual(0, fe.TryCatchNodes.Count);
+ Asserts.AreEqual(ExpressionType.Parameter, fe.Nodes[0].NodeType);
+ Asserts.AreEqual(ExpressionType.Constant, fe.Nodes[1].NodeType);
+ Asserts.AreEqual(ExpressionType.Add, fe.Nodes[2].NodeType);
+ Asserts.AreEqual(ExpressionType.Parameter, fe.Nodes[3].NodeType);
+ Asserts.AreEqual(ExpressionType.Lambda, fe.Nodes[4].NodeType);
+ Asserts.AreEqual(0, fe.Nodes[2].ChildIdx);
+ Asserts.AreEqual(2, fe.Nodes[2].ChildCount);
+ Asserts.AreEqual(1, fe.Nodes[0].NextIdx);
+ Asserts.AreEqual(2, fe.Nodes[4].ChildIdx);
+ Asserts.AreEqual(2, fe.Nodes[4].ChildCount);
+ Asserts.AreEqual(3, fe.Nodes[2].NextIdx);
+ Asserts.AreEqual(42, ((LambdaExpression)fe.ToLightExpression()).CompileFast>(true)(41));
+ }
}
}