Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/FastExpressionCompiler.LightExpression/FlatExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,40 @@ public SysExpr ToExpression() =>
[RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)]
public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression());

/// <summary>Returns <c>true</c> when all reachable nodes are compacted into a canonical post-order layout.</summary>
/// <remarks>
/// 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 <see cref="Nodes"/>.
/// </remarks>
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;
}

/// <summary>Compacts the current tree into the canonical post-order layout and drops unreachable nodes.</summary>
/// <returns>The number of removed unreachable nodes.</returns>
[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));
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -1606,6 +1646,39 @@ private static bool Contains<TStack, TPool>(ref SmallList<ushort, TStack, TPool>
[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++;
}

/// <summary>Reconstructs System.Linq nodes from the flat representation while reusing parameter and label identities.</summary>
private struct Reader
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}


Expand Down Expand Up @@ -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<int>("p");
var one = fe.ConstantInt(1);
var add = fe.Add(parameter, one);
fe.RootIdx = fe.Lambda<Func<int, int>>(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<Func<int, int>>(true)(41));
}
}
}
Loading