From 55cad42af143cda1c8dc99647a2df67c7904e6c5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 15:24:08 +0000
Subject: [PATCH 1/7] Initial plan
From f1fdc0e495f61cd5a539f8d620bd3b98508e4d49 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 27 Apr 2026 15:36:28 +0000
Subject: [PATCH 2/7] Split FE parameter decl/use and add parent/reference
child links
Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/628c76ba-460c-4a8d-b880-585db5217f72
Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com>
---
.../FlatExpression.cs | 227 ++++++++++++++----
.../LightExpressionTests.cs | 49 ++++
2 files changed, 231 insertions(+), 45 deletions(-)
diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
index 52654ef8..ba8b3a2a 100644
--- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
+++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
@@ -23,6 +23,12 @@ public enum ExprNodeKind : byte
{
/// Represents a regular expression node.
Expression,
+ /// Represents a parameter declaration node.
+ ParameterDeclaration,
+ /// Represents a parameter usage node that points to its declaration node index.
+ ParameterUsage,
+ /// Represents a generic reference to an already-linked node.
+ NodeReference,
/// Represents a switch case payload.
SwitchCase,
/// Represents a catch block payload.
@@ -55,6 +61,8 @@ public struct ExprNode
private const int CountShift = 16;
private const ulong IndexMask = 0xFFFF;
private const ulong KindMask = 0x0F;
+ private const byte NextPointsParentFlag = 0x8;
+ private const ulong TagMask = 0xFFUL << TagShift;
private const ulong NextMask = IndexMask << NextShift;
private const ulong ChildCountMask = IndexMask << CountShift;
private const ulong ChildInfoMask = ChildCountMask | IndexMask;
@@ -83,6 +91,10 @@ public struct ExprNode
/// Gets the next sibling node index in the intrusive child chain.
public int NextIdx => (int)((_data >> NextShift) & IndexMask);
+ internal bool IsParentLink => (Flags & NextPointsParentFlag) != 0;
+
+ internal bool HasNextLink => (_data & NextMask) != 0 || IsParentLink;
+
/// Gets the number of direct children linked from this node.
public int ChildCount => (int)((_data >> CountShift) & IndexMask);
@@ -102,8 +114,20 @@ internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind k
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void SetNextIdx(int nextIdx) =>
+ internal void SetNextSiblingIdx(int nextIdx)
+ {
_data = (_data & KeepWithoutNextMask) | ((ulong)(ushort)nextIdx << NextShift);
+ if (IsParentLink)
+ SetFlags((byte)(Flags & ~NextPointsParentFlag));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void SetParentIdx(int parentIdx)
+ {
+ _data = (_data & KeepWithoutNextMask) | ((ulong)(ushort)parentIdx << NextShift);
+ if (!IsParentLink)
+ SetFlags((byte)(Flags | NextPointsParentFlag));
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void SetChildInfo(int childIdx, int childCount) =>
@@ -115,14 +139,20 @@ internal void SetChildInfo(int childIdx, int childCount) =>
internal bool Is(ExprNodeKind kind) => Kind == kind;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal bool IsExpression() => Kind == ExprNodeKind.Expression;
+ internal bool IsExpression() =>
+ Kind == ExprNodeKind.Expression || Kind == ExprNodeKind.ParameterDeclaration || Kind == ExprNodeKind.ParameterUsage;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool HasFlag(byte flag) => (Flags & flag) != 0;
+ internal byte CopyableFlags => (byte)(Flags & ~NextPointsParentFlag);
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal bool ShouldCloneWhenLinked() =>
- Kind == ExprNodeKind.LabelTarget || NodeType == ExpressionType.Parameter || Kind == ExprNodeKind.ObjectReference || ChildCount == 0;
+ private void SetFlags(byte flags)
+ {
+ var tag = (byte)((flags << FlagsShift) | (byte)Kind);
+ _data = (_data & ~TagMask) | ((ulong)tag << TagShift);
+ }
}
/// Stores an expression tree as a flat node array plus out-of-line closure constants.
@@ -130,6 +160,7 @@ public struct ExprTree
{
private static readonly object ClosureConstantMarker = new();
private const byte ParameterByRefFlag = 1;
+ private const int UnboundParameterPosition = ushort.MaxValue;
private const byte BinaryLiftedToNullFlag = 1;
private const byte LoopHasBreakFlag = 1;
private const byte LoopHasContinueFlag = 2;
@@ -149,8 +180,9 @@ public struct ExprTree
/// Adds a parameter node and returns its index.
public int Parameter(Type type, string name = null)
{
- var id = Nodes.Count + 1;
- return AddRawLeafExpressionNode(type, name, ExpressionType.Parameter, type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: id);
+ var parameterType = type ?? throw new ArgumentNullException(nameof(type));
+ return AddLeafNode(parameterType, name, ExpressionType.Parameter, ExprNodeKind.ParameterDeclaration,
+ parameterType.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: 0, childCount: UnboundParameterPosition);
}
/// Adds a typed parameter node and returns its index.
@@ -310,11 +342,16 @@ public int Block(Type type, IEnumerable variables, params int[] expressions
throw new ArgumentException("Block should contain at least one expression.", nameof(expressions));
ChildList children = default;
+ ChildList variableDeclarations = default;
if (variables != null)
{
ChildList variableChildren = default;
foreach (var variable in variables)
- variableChildren.Add(variable);
+ {
+ var declaration = NormalizeParameterDeclarationIndex(variable);
+ variableChildren.Add(declaration);
+ variableDeclarations.Add(declaration);
+ }
if (variableChildren.Count != 0)
children.Add(AddChildListNode(in variableChildren));
}
@@ -322,7 +359,10 @@ public int Block(Type type, IEnumerable variables, params int[] expressions
for (var i = 0; i < expressions.Length; ++i)
bodyChildren.Add(expressions[i]);
children.Add(AddChildListNode(in bodyChildren));
- return AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, null, ExpressionType.Block, in children);
+ var blockIndex = AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, null, ExpressionType.Block, in children);
+ for (var i = 0; i < variableDeclarations.Count; ++i)
+ BindParameterDeclaration(variableDeclarations[i], blockIndex, i);
+ return blockIndex;
}
/// Adds a typed lambda node.
@@ -330,10 +370,20 @@ public int Lambda(int body, params int[] parameters) where TDelegate
Lambda(typeof(TDelegate), body, parameters);
/// Adds a lambda node.
- public int Lambda(Type delegateType, int body, params int[] parameters) =>
- parameters == null || parameters.Length == 0
- ? AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, 0, body)
- : AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, PrependToChildList(body, parameters));
+ public int Lambda(Type delegateType, int body, params int[] parameters)
+ {
+ if (parameters == null || parameters.Length == 0)
+ return AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, 0, body);
+
+ var declarations = new int[parameters.Length];
+ for (var i = 0; i < parameters.Length; ++i)
+ declarations[i] = NormalizeParameterDeclarationIndex(parameters[i]);
+
+ var lambdaIndex = AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, PrependToChildList(body, declarations));
+ for (var i = 0; i < declarations.Length; ++i)
+ BindParameterDeclaration(declarations[i], lambdaIndex, i);
+ return lambdaIndex;
+ }
/// Adds a member-assignment binding node.
public int Bind(System.Reflection.MemberInfo member, int expression) =>
@@ -545,6 +595,40 @@ public SysExpr ToExpression() =>
[RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)]
public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression());
+ private int NormalizeParameterDeclarationIndex(int parameterIndex)
+ {
+ ref var node = ref Nodes[parameterIndex];
+ if (node.Is(ExprNodeKind.ParameterUsage))
+ return node.ChildIdx;
+ if (node.Is(ExprNodeKind.ParameterDeclaration))
+ return parameterIndex;
+ throw new InvalidOperationException($"Node at index {parameterIndex} is not a parameter declaration or usage.");
+ }
+
+ private void BindParameterDeclaration(int declarationIndex, int ownerIndex, int position)
+ {
+ ref var declaration = ref Nodes[declarationIndex];
+ if (!declaration.Is(ExprNodeKind.ParameterDeclaration))
+ throw new InvalidOperationException($"Node at index {declarationIndex} is not a parameter declaration.");
+ if (declaration.ChildCount != UnboundParameterPosition)
+ throw new InvalidOperationException($"Parameter declaration at index {declarationIndex} is already bound to an owner scope.");
+ declaration.SetChildInfo(ownerIndex, position);
+ }
+
+ private int AddParameterUsageNode(int declarationIndex)
+ {
+ ref var declaration = ref Nodes[declarationIndex];
+ Debug.Assert(declaration.Is(ExprNodeKind.ParameterDeclaration));
+ return AddLeafNode(declaration.Type, declaration.Obj, ExpressionType.Parameter, ExprNodeKind.ParameterUsage,
+ declaration.CopyableFlags, declarationIndex, 0);
+ }
+
+ private int AddNodeReference(int index)
+ {
+ ref var node = ref Nodes[index];
+ return AddLeafNode(node.Type, node.Obj, node.NodeType, ExprNodeKind.NodeReference, node.CopyableFlags, index, 0);
+ }
+
private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) =>
AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child));
@@ -698,34 +782,49 @@ private int AddExpression(SysExpr expression)
case ExpressionType.Parameter:
{
var parameter = (SysParameterExpression)expression;
- return _tree.AddRawLeafExpressionNode(expression.Type, parameter.Name, expression.NodeType,
- parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter));
+ return _tree.AddParameterUsageNode(GetParameterDeclarationIndex(parameter));
}
case ExpressionType.Lambda:
{
var lambda = (System.Linq.Expressions.LambdaExpression)expression;
ChildList children = default;
children.Add(AddExpression(lambda.Body));
+ ChildList declarations = default;
for (var i = 0; i < lambda.Parameters.Count; ++i)
- children.Add(AddExpression(lambda.Parameters[i]));
- return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children);
+ {
+ var declaration = GetParameterDeclarationIndex(lambda.Parameters[i]);
+ declarations.Add(declaration);
+ children.Add(declaration);
+ }
+ var lambdaIndex = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children);
+ for (var i = 0; i < declarations.Count; ++i)
+ _tree.BindParameterDeclaration(declarations[i], lambdaIndex, i);
+ return lambdaIndex;
}
case ExpressionType.Block:
{
var block = (System.Linq.Expressions.BlockExpression)expression;
ChildList children = default;
+ ChildList declarations = default;
if (block.Variables.Count != 0)
{
ChildList variables = default;
for (var i = 0; i < block.Variables.Count; ++i)
- variables.Add(AddExpression(block.Variables[i]));
+ {
+ var declaration = GetParameterDeclarationIndex(block.Variables[i]);
+ declarations.Add(declaration);
+ variables.Add(declaration);
+ }
children.Add(_tree.AddChildListNode(in variables));
}
ChildList expressions = default;
for (var i = 0; i < block.Expressions.Count; ++i)
expressions.Add(AddExpression(block.Expressions[i]));
children.Add(_tree.AddChildListNode(in expressions));
- return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children);
+ var blockIndex = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children);
+ for (var i = 0; i < declarations.Count; ++i)
+ _tree.BindParameterDeclaration(declarations[i], blockIndex, i);
+ return blockIndex;
}
case ExpressionType.MemberAccess:
{
@@ -1005,6 +1104,14 @@ private int AddElementInit(SysElementInit init)
return _tree.AddRawAuxNode(init.AddMethod.DeclaringType, init.AddMethod, ExprNodeKind.ElementInit, children);
}
+ private int GetParameterDeclarationIndex(SysParameterExpression parameter)
+ {
+ ref var declaration = ref _parameterIds.Map.AddOrGetValueRef(parameter, out var found);
+ if (!found)
+ declaration = _tree.Parameter(parameter.Type, parameter.Name);
+ return declaration;
+ }
+
private static int GetId(ref SmallMap16