Skip to content

Commit c480c7e

Browse files
Copilotdadhi
andauthored
FlatExpression: track lambda node indices during construction to reduce TryCollectInfo work
Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/9e120fbc-6ec4-46c7-9cc8-46e269e91f1b Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
1 parent 67e5255 commit c480c7e

2 files changed

Lines changed: 128 additions & 4 deletions

File tree

src/FastExpressionCompiler.LightExpression/FlatExpression.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ public struct ExprTree
167167
/// <summary>Gets or sets closure constants that are referenced from constant nodes.</summary>
168168
public SmallList<object, Stack16<object>, NoArrayPool<object>> ClosureConstants;
169169

170+
/// <summary>Gets or sets the indices of all lambda nodes added during construction.
171+
/// The root lambda index is stored in <see cref="RootIndex"/>; all other entries are nested lambdas.
172+
/// Populated automatically by <see cref="Lambda(Type,int,int[])"/> and <see cref="ExprTree.FromExpression"/>,
173+
/// enabling callers to discover nested lambdas without a full tree traversal.</summary>
174+
public SmallList<int, Stack16<int>, NoArrayPool<int>> LambdaNodes;
175+
170176
/// <summary>Adds a parameter node and returns its index.</summary>
171177
public int Parameter(Type type, string name = null)
172178
{
@@ -414,11 +420,17 @@ public int Lambda<TDelegate>(int body, params int[] parameters) where TDelegate
414420
/// out-of-order decl pattern. The Reader resolves identity through a shared id map
415421
/// so that all refs and the single decl resolve to the same
416422
/// <see cref="System.Linq.Expressions.ParameterExpression"/> object.
423+
/// <para>The lambda node index is recorded in <see cref="LambdaNodes"/> so callers can discover
424+
/// nested lambdas (all entries except <see cref="RootIndex"/>) without a full tree traversal.</para>
417425
/// </remarks>
418-
public int Lambda(Type delegateType, int body, params int[] parameters) =>
419-
parameters == null || parameters.Length == 0
426+
public int Lambda(Type delegateType, int body, params int[] parameters)
427+
{
428+
var index = parameters == null || parameters.Length == 0
420429
? AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, 0, body)
421430
: AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, PrependToChildList(body, parameters));
431+
LambdaNodes.Add(index);
432+
return index;
433+
}
422434

423435
/// <summary>Adds a member-assignment binding node.</summary>
424436
public int Bind(System.Reflection.MemberInfo member, int expression) =>
@@ -823,7 +835,9 @@ private int AddExpression(SysExpr expression)
823835
children.Add(AddExpression(lambda.Body));
824836
for (var i = 0; i < lambda.Parameters.Count; ++i)
825837
children.Add(AddExpression(lambda.Parameters[i]));
826-
return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children);
838+
var lambdaIndex = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children);
839+
_tree.LambdaNodes.Add(lambdaIndex);
840+
return lambdaIndex;
827841
}
828842
case ExpressionType.Block:
829843
{

test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ public int Run()
4040
Flat_nested_lambda_captures_outer_parameter_identity();
4141
Flat_out_of_order_decl_block_in_lambda_compiles_correctly();
4242
Flat_enum_constant_stored_inline_roundtrip();
43-
return 23;
43+
Flat_lambda_nodes_tracks_all_lambdas_during_direct_construction();
44+
Flat_lambda_nodes_tracks_deeply_nested_lambdas_during_direct_construction();
45+
Flat_lambda_nodes_tracks_lambdas_from_expression_conversion();
46+
Flat_lambda_nodes_has_single_entry_for_root_only_lambda();
47+
return 27;
4448
}
4549

4650

@@ -686,5 +690,111 @@ void Check<TEnum>(TEnum enumValue) where TEnum : Enum
686690
Check(UIntEnum.A);
687691
Check(UIntEnum.B);
688692
}
693+
694+
/// <summary>
695+
/// When building a flat expression directly, calling Lambda() for a nested lambda
696+
/// and then for the root lambda should result in both indices recorded in LambdaNodes.
697+
/// The root is identified by RootIndex; all others are nested.
698+
/// </summary>
699+
public void Flat_lambda_nodes_tracks_all_lambdas_during_direct_construction()
700+
{
701+
var fe = default(ExprTree);
702+
var x = fe.ParameterOf<int>("x");
703+
704+
// Build: outer: x => () => x
705+
var inner = fe.Lambda<Func<int>>(x);
706+
fe.RootIndex = fe.Lambda<Func<int, Func<int>>>(inner, x);
707+
708+
// Both the root and nested lambda indices should be recorded
709+
Asserts.AreEqual(2, fe.LambdaNodes.Count);
710+
711+
// Check that inner and root are both in LambdaNodes
712+
var foundInner = false;
713+
var foundRoot = false;
714+
for (var i = 0; i < fe.LambdaNodes.Count; i++)
715+
{
716+
if (fe.LambdaNodes[i] == inner) foundInner = true;
717+
if (fe.LambdaNodes[i] == fe.RootIndex) foundRoot = true;
718+
}
719+
Asserts.IsTrue(foundInner);
720+
Asserts.IsTrue(foundRoot);
721+
722+
// Nested lambdas are all LambdaNodes entries that are not the root
723+
var nestedCount = 0;
724+
for (var i = 0; i < fe.LambdaNodes.Count; i++)
725+
if (fe.LambdaNodes[i] != fe.RootIndex)
726+
++nestedCount;
727+
Asserts.AreEqual(1, nestedCount);
728+
}
729+
730+
/// <summary>
731+
/// When building a flat expression with multiple levels of nesting,
732+
/// all lambda node indices are captured in LambdaNodes.
733+
/// </summary>
734+
public void Flat_lambda_nodes_tracks_deeply_nested_lambdas_during_direct_construction()
735+
{
736+
var fe = default(ExprTree);
737+
var x = fe.ParameterOf<int>("x");
738+
739+
// Build: outer: x => (() => (() => x))
740+
var innermost = fe.Lambda<Func<int>>(x);
741+
var middle = fe.Lambda<Func<Func<int>>>(innermost);
742+
fe.RootIndex = fe.Lambda<Func<int, Func<Func<int>>>>(middle, x);
743+
744+
// All three lambda nodes should be recorded
745+
Asserts.AreEqual(3, fe.LambdaNodes.Count);
746+
747+
// Count nested (non-root) lambdas
748+
var nestedCount = 0;
749+
for (var i = 0; i < fe.LambdaNodes.Count; i++)
750+
if (fe.LambdaNodes[i] != fe.RootIndex)
751+
++nestedCount;
752+
Asserts.AreEqual(2, nestedCount);
753+
}
754+
755+
/// <summary>
756+
/// When converting a System.Linq expression tree with nested lambdas via FromExpression,
757+
/// the resulting ExprTree should have all lambda indices populated in LambdaNodes.
758+
/// </summary>
759+
public void Flat_lambda_nodes_tracks_lambdas_from_expression_conversion()
760+
{
761+
var p = SysExpr.Parameter(typeof(int), "p");
762+
// Build: p => () => p using System.Linq.Expressions
763+
var sysLambda = SysExpr.Lambda<Func<int, Func<int>>>(
764+
SysExpr.Lambda<Func<int>>(p),
765+
p);
766+
767+
var fe = sysLambda.ToFlatExpression();
768+
769+
// Both root and nested lambda indices should be recorded
770+
Asserts.AreEqual(2, fe.LambdaNodes.Count);
771+
772+
// The root lambda must be in the list
773+
var foundRoot = false;
774+
for (var i = 0; i < fe.LambdaNodes.Count; i++)
775+
if (fe.LambdaNodes[i] == fe.RootIndex) { foundRoot = true; break; }
776+
Asserts.IsTrue(foundRoot);
777+
778+
// Exactly one nested lambda
779+
var nestedCount = 0;
780+
for (var i = 0; i < fe.LambdaNodes.Count; i++)
781+
if (fe.LambdaNodes[i] != fe.RootIndex)
782+
++nestedCount;
783+
Asserts.AreEqual(1, nestedCount);
784+
}
785+
786+
/// <summary>
787+
/// A flat expression with no nested lambdas (root-only) should have exactly one
788+
/// entry in LambdaNodes (the root itself).
789+
/// </summary>
790+
public void Flat_lambda_nodes_has_single_entry_for_root_only_lambda()
791+
{
792+
var fe = default(ExprTree);
793+
var p = fe.ParameterOf<int>("p");
794+
fe.RootIndex = fe.Lambda<Func<int, int>>(fe.Add(p, fe.ConstantInt(1)), p);
795+
796+
Asserts.AreEqual(1, fe.LambdaNodes.Count);
797+
Asserts.AreEqual(fe.RootIndex, fe.LambdaNodes[0]);
798+
}
689799
}
690800
}

0 commit comments

Comments
 (0)