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)); + } } }