Skip to content

Commit 60a4f93

Browse files
Copilotdadhi
andauthored
FlatExpression: also track BlocksWithVariables, GotoNodes, LabelNodes and TryCatchNodes during construction
Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/fbbd4fe6-1e27-488e-b48b-b5ff5dbc8626 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
1 parent c480c7e commit 60a4f93

2 files changed

Lines changed: 232 additions & 24 deletions

File tree

src/FastExpressionCompiler.LightExpression/FlatExpression.cs

Lines changed: 96 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,30 @@ public struct ExprTree
173173
/// enabling callers to discover nested lambdas without a full tree traversal.</summary>
174174
public SmallList<int, Stack16<int>, NoArrayPool<int>> LambdaNodes;
175175

176+
/// <summary>Gets or sets the indices of all block nodes that carry explicit variable declarations.
177+
/// These are the block nodes where <c>children.Count == 2</c> (variable list + expression list).
178+
/// Populated automatically by <see cref="Block(Type,IEnumerable{int},int[])"/> and <see cref="ExprTree.FromExpression"/>,
179+
/// enabling callers to enumerate block-scoped variables without a full tree traversal.</summary>
180+
public SmallList<int, Stack16<int>, NoArrayPool<int>> BlocksWithVariables;
181+
182+
/// <summary>Gets or sets the indices of all <see cref="ExpressionType.Goto"/> nodes
183+
/// (including <c>return</c> and <c>break</c>/<c>continue</c> goto-family nodes).
184+
/// Populated automatically by <see cref="MakeGoto"/> and <see cref="ExprTree.FromExpression"/>,
185+
/// enabling callers to link gotos to their label targets without a full tree traversal.</summary>
186+
public SmallList<int, Stack16<int>, NoArrayPool<int>> GotoNodes;
187+
188+
/// <summary>Gets or sets the indices of all <see cref="ExpressionType.Label"/> expression nodes.
189+
/// Populated automatically by <see cref="Label(int,int?)"/> and <see cref="ExprTree.FromExpression"/>,
190+
/// enabling callers to link label expressions to their targets without a full tree traversal.</summary>
191+
public SmallList<int, Stack16<int>, NoArrayPool<int>> LabelNodes;
192+
193+
/// <summary>Gets or sets the indices of all <see cref="ExpressionType.Try"/> nodes
194+
/// (try/catch, try/finally, try/fault, and combined forms).
195+
/// Populated automatically by <see cref="TryCatch"/>, <see cref="TryFinally"/>,
196+
/// <see cref="TryFault"/>, <see cref="TryCatchFinally"/> and <see cref="ExprTree.FromExpression"/>,
197+
/// enabling callers to locate all try regions without a full tree traversal.</summary>
198+
public SmallList<int, Stack16<int>, NoArrayPool<int>> TryCatchNodes;
199+
176200
/// <summary>Adds a parameter node and returns its index.</summary>
177201
public int Parameter(Type type, string name = null)
178202
{
@@ -380,26 +404,36 @@ public int Block(params int[] expressions) =>
380404
/// Variable parameter nodes share the same id-slot as the refs used inside the body
381405
/// (out-of-order: the variable decl nodes appear in children[0] before the body expressions
382406
/// that reference them in children[1]).
407+
/// <para>When the block has explicit variable declarations its node index is recorded in
408+
/// <see cref="BlocksWithVariables"/>, enabling callers to enumerate block-scoped variables
409+
/// without a full tree traversal.</para>
383410
/// </remarks>
384411
public int Block(Type type, IEnumerable<int> variables, params int[] expressions)
385412
{
386413
if (expressions == null || expressions.Length == 0)
387414
throw new ArgumentException("Block should contain at least one expression.", nameof(expressions));
388415

389416
ChildList children = default;
417+
var hasVariables = false;
390418
if (variables != null)
391419
{
392420
ChildList variableChildren = default;
393421
foreach (var variable in variables)
394422
variableChildren.Add(variable);
395423
if (variableChildren.Count != 0)
424+
{
396425
children.Add(AddChildListNode(in variableChildren));
426+
hasVariables = true;
427+
}
397428
}
398429
ChildList bodyChildren = default;
399430
for (var i = 0; i < expressions.Length; ++i)
400431
bodyChildren.Add(expressions[i]);
401432
children.Add(AddChildListNode(in bodyChildren));
402-
return AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, null, ExpressionType.Block, in children);
433+
var index = AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, null, ExpressionType.Block, in children);
434+
if (hasVariables)
435+
BlocksWithVariables.Add(index);
436+
return index;
403437
}
404438

405439
/// <summary>Adds a typed lambda node.</summary>
@@ -468,18 +502,26 @@ public int Label(Type type = null, string name = null)
468502
}
469503

470504
/// <summary>Adds a label-expression node.</summary>
471-
public int Label(int target, int? defaultValue = null) =>
472-
defaultValue.HasValue
505+
/// <remarks>The node index is recorded in <see cref="LabelNodes"/>.</remarks>
506+
public int Label(int target, int? defaultValue = null)
507+
{
508+
var index = defaultValue.HasValue
473509
? AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target, defaultValue.Value)
474510
: AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target);
511+
LabelNodes.Add(index);
512+
return index;
513+
}
475514

476515
/// <summary>Adds a goto-family node.</summary>
516+
/// <remarks>The node index is recorded in <see cref="GotoNodes"/>.</remarks>
477517
public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type type = null)
478518
{
479519
var resultType = type ?? (value.HasValue ? Nodes[value.Value].Type : typeof(void));
480-
return value.HasValue
520+
var index = value.HasValue
481521
? AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, 0, target, value.Value)
482522
: AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, 0, target);
523+
GotoNodes.Add(index);
524+
return index;
483525
}
484526

485527
/// <summary>Adds a goto node.</summary>
@@ -557,29 +599,48 @@ public int MakeCatchBlock(Type test, int? variable, int body, int? filter)
557599
}
558600

559601
/// <summary>Adds a try/catch node.</summary>
602+
/// <remarks>The node index is recorded in <see cref="TryCatchNodes"/>.</remarks>
560603
public int TryCatch(int body, params int[] handlers)
561604
{
605+
int index;
562606
if (handlers == null || handlers.Length == 0)
563-
return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body);
564-
565-
ChildList handlerChildren = default;
566-
for (var i = 0; i < handlers.Length; ++i)
567-
handlerChildren.Add(handlers[i]);
568-
ChildList children = default;
569-
children.Add(body);
570-
children.Add(AddChildListNode(in handlerChildren));
571-
return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, in children);
607+
{
608+
index = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body);
609+
}
610+
else
611+
{
612+
ChildList handlerChildren = default;
613+
for (var i = 0; i < handlers.Length; ++i)
614+
handlerChildren.Add(handlers[i]);
615+
ChildList children = default;
616+
children.Add(body);
617+
children.Add(AddChildListNode(in handlerChildren));
618+
index = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, in children);
619+
}
620+
TryCatchNodes.Add(index);
621+
return index;
572622
}
573623

574624
/// <summary>Adds a try/finally node.</summary>
575-
public int TryFinally(int body, int @finally) =>
576-
AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body, @finally);
625+
/// <remarks>The node index is recorded in <see cref="TryCatchNodes"/>.</remarks>
626+
public int TryFinally(int body, int @finally)
627+
{
628+
var index = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body, @finally);
629+
TryCatchNodes.Add(index);
630+
return index;
631+
}
577632

578633
/// <summary>Adds a try/fault node.</summary>
579-
public int TryFault(int body, int fault) =>
580-
AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, TryFaultFlag, body, fault);
634+
/// <remarks>The node index is recorded in <see cref="TryCatchNodes"/>.</remarks>
635+
public int TryFault(int body, int fault)
636+
{
637+
var index = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, TryFaultFlag, body, fault);
638+
TryCatchNodes.Add(index);
639+
return index;
640+
}
581641

582642
/// <summary>Adds a try node with optional finally block and catch handlers.</summary>
643+
/// <remarks>The node index is recorded in <see cref="TryCatchNodes"/>.</remarks>
583644
public int TryCatchFinally(int body, int? @finally, params int[] handlers)
584645
{
585646
ChildList children = default;
@@ -593,7 +654,9 @@ public int TryCatchFinally(int body, int? @finally, params int[] handlers)
593654
handlerChildren.Add(handlers[i]);
594655
children.Add(AddChildListNode(in handlerChildren));
595656
}
596-
return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, in children);
657+
var index = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, in children);
658+
TryCatchNodes.Add(index);
659+
return index;
597660
}
598661

599662
/// <summary>Adds a type-test node.</summary>
@@ -847,7 +910,8 @@ private int AddExpression(SysExpr expression)
847910
// children.Count == 2 is the canonical test for the presence of variables.
848911
var block = (System.Linq.Expressions.BlockExpression)expression;
849912
ChildList children = default;
850-
if (block.Variables.Count != 0)
913+
var hasVariables = block.Variables.Count != 0;
914+
if (hasVariables)
851915
{
852916
ChildList variables = default;
853917
for (var i = 0; i < block.Variables.Count; ++i)
@@ -858,7 +922,10 @@ private int AddExpression(SysExpr expression)
858922
for (var i = 0; i < block.Expressions.Count; ++i)
859923
expressions.Add(AddExpression(block.Expressions[i]));
860924
children.Add(_tree.AddChildListNode(in expressions));
861-
return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children);
925+
var blockIndex = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children);
926+
if (hasVariables)
927+
_tree.BlocksWithVariables.Add(blockIndex);
928+
return blockIndex;
862929
}
863930
case ExpressionType.MemberAccess:
864931
{
@@ -944,7 +1011,9 @@ private int AddExpression(SysExpr expression)
9441011
children.Add(AddLabelTarget(@goto.Target));
9451012
if (@goto.Value != null)
9461013
children.Add(AddExpression(@goto.Value));
947-
return _tree.AddRawExpressionNode(expression.Type, @goto.Kind, expression.NodeType, children);
1014+
var gotoIndex = _tree.AddRawExpressionNode(expression.Type, @goto.Kind, expression.NodeType, children);
1015+
_tree.GotoNodes.Add(gotoIndex);
1016+
return gotoIndex;
9481017
}
9491018
case ExpressionType.Label:
9501019
{
@@ -953,7 +1022,9 @@ private int AddExpression(SysExpr expression)
9531022
children.Add(AddLabelTarget(label.Target));
9541023
if (label.DefaultValue != null)
9551024
children.Add(AddExpression(label.DefaultValue));
956-
return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children);
1025+
var labelIndex = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children);
1026+
_tree.LabelNodes.Add(labelIndex);
1027+
return labelIndex;
9571028
}
9581029
case ExpressionType.Switch:
9591030
{
@@ -991,7 +1062,9 @@ private int AddExpression(SysExpr expression)
9911062
handlers.Add(AddCatchBlock(@try.Handlers[i]));
9921063
children.Add(_tree.AddChildListNode(in handlers));
9931064
}
994-
return _tree.AddNode(expression.Type, null, expression.NodeType, ExprNodeKind.Expression, flags, in children);
1065+
var tryIndex = _tree.AddNode(expression.Type, null, expression.NodeType, ExprNodeKind.Expression, flags, in children);
1066+
_tree.TryCatchNodes.Add(tryIndex);
1067+
return tryIndex;
9951068
}
9961069
case ExpressionType.MemberInit:
9971070
{

test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ public int Run()
4444
Flat_lambda_nodes_tracks_deeply_nested_lambdas_during_direct_construction();
4545
Flat_lambda_nodes_tracks_lambdas_from_expression_conversion();
4646
Flat_lambda_nodes_has_single_entry_for_root_only_lambda();
47-
return 27;
47+
Flat_blocks_with_variables_tracked_during_direct_construction();
48+
Flat_goto_and_label_nodes_tracked_during_direct_construction();
49+
Flat_try_catch_nodes_tracked_during_direct_construction();
50+
Flat_blocks_with_variables_tracked_from_expression_conversion();
51+
Flat_goto_and_label_nodes_tracked_from_expression_conversion();
52+
Flat_try_catch_nodes_tracked_from_expression_conversion();
53+
return 33;
4854
}
4955

5056

@@ -796,5 +802,134 @@ public void Flat_lambda_nodes_has_single_entry_for_root_only_lambda()
796802
Asserts.AreEqual(1, fe.LambdaNodes.Count);
797803
Asserts.AreEqual(fe.RootIndex, fe.LambdaNodes[0]);
798804
}
805+
806+
/// <summary>
807+
/// Block nodes with explicit variable declarations are recorded in BlocksWithVariables;
808+
/// blocks without variables produce no entry.
809+
/// </summary>
810+
public void Flat_blocks_with_variables_tracked_during_direct_construction()
811+
{
812+
var fe = default(ExprTree);
813+
var p = fe.ParameterOf<int>("p");
814+
var v = fe.Variable(typeof(int), "v");
815+
816+
// Block with one variable: should be tracked
817+
var blockWithVar = fe.Block(typeof(int), new[] { v }, fe.Assign(v, p), v);
818+
// Block without variables: should NOT be tracked
819+
var blockNoVar = fe.Block(fe.Add(p, fe.ConstantInt(1)));
820+
821+
fe.RootIndex = fe.Lambda<Func<int, int>>(fe.Block(blockWithVar, blockNoVar), p);
822+
823+
Asserts.AreEqual(1, fe.BlocksWithVariables.Count);
824+
Asserts.AreEqual(blockWithVar, fe.BlocksWithVariables[0]);
825+
}
826+
827+
/// <summary>
828+
/// Goto and label expression nodes are recorded in GotoNodes and LabelNodes respectively.
829+
/// </summary>
830+
public void Flat_goto_and_label_nodes_tracked_during_direct_construction()
831+
{
832+
var fe = default(ExprTree);
833+
var p = fe.ParameterOf<int>("p");
834+
var target = fe.Label(typeof(int), "done");
835+
836+
var gotoNode = fe.Goto(target, p, typeof(int));
837+
var labelNode = fe.Label(target, fe.ConstantInt(0));
838+
839+
fe.RootIndex = fe.Lambda<Func<int, int>>(fe.Block(gotoNode, labelNode), p);
840+
841+
Asserts.AreEqual(1, fe.GotoNodes.Count);
842+
Asserts.AreEqual(gotoNode, fe.GotoNodes[0]);
843+
844+
Asserts.AreEqual(1, fe.LabelNodes.Count);
845+
Asserts.AreEqual(labelNode, fe.LabelNodes[0]);
846+
}
847+
848+
/// <summary>
849+
/// Try/catch, try/finally and try/fault node indices are all recorded in TryCatchNodes.
850+
/// </summary>
851+
public void Flat_try_catch_nodes_tracked_during_direct_construction()
852+
{
853+
var fe = default(ExprTree);
854+
var p = fe.ParameterOf<int>("p");
855+
856+
var tryCatchNode = fe.TryCatch(
857+
fe.Add(p, fe.ConstantInt(1)),
858+
fe.Catch(typeof(Exception), fe.ConstantInt(-1)));
859+
860+
var tryFinallyNode = fe.TryFinally(
861+
fe.Add(p, fe.ConstantInt(2)),
862+
fe.Default(typeof(void)));
863+
864+
fe.RootIndex = fe.Lambda<Func<int, int>>(
865+
fe.Block(tryCatchNode, tryFinallyNode), p);
866+
867+
Asserts.AreEqual(2, fe.TryCatchNodes.Count);
868+
869+
var foundTryCatch = false;
870+
var foundTryFinally = false;
871+
for (var i = 0; i < fe.TryCatchNodes.Count; i++)
872+
{
873+
if (fe.TryCatchNodes[i] == tryCatchNode) foundTryCatch = true;
874+
if (fe.TryCatchNodes[i] == tryFinallyNode) foundTryFinally = true;
875+
}
876+
Asserts.IsTrue(foundTryCatch);
877+
Asserts.IsTrue(foundTryFinally);
878+
}
879+
880+
/// <summary>
881+
/// When converting a System.Linq expression tree, blocks with variables are
882+
/// recorded in BlocksWithVariables; plain blocks are not.
883+
/// </summary>
884+
public void Flat_blocks_with_variables_tracked_from_expression_conversion()
885+
{
886+
var p = SysExpr.Parameter(typeof(int), "p");
887+
var v = SysExpr.Variable(typeof(int), "v");
888+
// block with variable
889+
var sysBlock = SysExpr.Block(new[] { v }, SysExpr.Assign(v, p), v);
890+
var sysLambda = SysExpr.Lambda<Func<int, int>>(sysBlock, p);
891+
892+
var fe = sysLambda.ToFlatExpression();
893+
894+
Asserts.AreEqual(1, fe.BlocksWithVariables.Count);
895+
}
896+
897+
/// <summary>
898+
/// When converting a System.Linq expression tree with goto/label, both
899+
/// GotoNodes and LabelNodes are populated.
900+
/// </summary>
901+
public void Flat_goto_and_label_nodes_tracked_from_expression_conversion()
902+
{
903+
var p = SysExpr.Parameter(typeof(int), "p");
904+
var target = SysExpr.Label(typeof(int), "done");
905+
var sysLambda = SysExpr.Lambda<Func<int, int>>(
906+
SysExpr.Block(
907+
SysExpr.Goto(target, p, typeof(int)),
908+
SysExpr.Label(target, SysExpr.Constant(0))),
909+
p);
910+
911+
var fe = sysLambda.ToFlatExpression();
912+
913+
Asserts.AreEqual(1, fe.GotoNodes.Count);
914+
Asserts.AreEqual(1, fe.LabelNodes.Count);
915+
}
916+
917+
/// <summary>
918+
/// When converting a System.Linq expression tree with a try/catch,
919+
/// TryCatchNodes is populated.
920+
/// </summary>
921+
public void Flat_try_catch_nodes_tracked_from_expression_conversion()
922+
{
923+
var p = SysExpr.Parameter(typeof(int), "p");
924+
var sysLambda = SysExpr.Lambda<Func<int, int>>(
925+
SysExpr.TryCatch(
926+
SysExpr.Add(p, SysExpr.Constant(1)),
927+
SysExpr.Catch(typeof(Exception), SysExpr.Constant(-1))),
928+
p);
929+
930+
var fe = sysLambda.ToFlatExpression();
931+
932+
Asserts.AreEqual(1, fe.TryCatchNodes.Count);
933+
}
799934
}
800935
}

0 commit comments

Comments
 (0)