From 9a87d4a1949db3e231a8eccab3f96023f97f0212 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:00:01 +0000 Subject: [PATCH 01/15] Initial plan From bf135d2187c3fbc41a18d971de5c6ab034f568bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:13:40 +0000 Subject: [PATCH 02/15] feat: add flat light expression roundtrip support Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/631bfa1a-7ed7-4397-b2cb-ab11dfbcbdee Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../Expression.cs | 56 +- .../FlatExpression.cs | 908 ++++++++++++++++++ .../LightExpressionTests.cs | 89 +- 3 files changed, 1035 insertions(+), 18 deletions(-) create mode 100644 src/FastExpressionCompiler.LightExpression/FlatExpression.cs diff --git a/src/FastExpressionCompiler.LightExpression/Expression.cs b/src/FastExpressionCompiler.LightExpression/Expression.cs index 6be7058d..f67042ae 100644 --- a/src/FastExpressionCompiler.LightExpression/Expression.cs +++ b/src/FastExpressionCompiler.LightExpression/Expression.cs @@ -2773,11 +2773,11 @@ internal static Expression ConvertToLightExpression(SysExpr sysExpr, ref SmallLi var pe = (System.Linq.Expressions.ParameterExpression)sysExpr; return Expression.Parameter(pe.IsByRef ? exprType.MakeByRefType() : exprType, pe.Name); } - case ExpressionType.Lambda: - { - var le = (System.Linq.Expressions.LambdaExpression)sysExpr; - var body = le.Body.ToLightExpression(ref exprsConverted); - var retType = le.ReturnType; + case ExpressionType.Lambda: + { + var le = (System.Linq.Expressions.LambdaExpression)sysExpr; + var body = le.Body.ToLightExpression(ref exprsConverted); + var retType = le.ReturnType; var pars = le.Parameters; var parCount = pars.Count; switch (parCount) @@ -2819,16 +2819,40 @@ internal static Expression ConvertToLightExpression(SysExpr sysExpr, ref SmallLi (ParameterExpression)pars[5].ToLightExpression(ref exprsConverted), retType); default: var pes = new ParameterExpression[parCount]; - for (var i = 0; i < pes.Length; i++) - pes[i] = (ParameterExpression)le.Parameters[i].ToLightExpression(ref exprsConverted); - return Expression.Lambda(exprType, body, pes, retType); - } - } - default: - if (sysExpr is System.Linq.Expressions.UnaryExpression ue) - { - var operand = ue.Operand.ToLightExpression(ref exprsConverted); - return Expression.MakeUnary(nodeType, operand, exprType, ue.Method); + for (var i = 0; i < pes.Length; i++) + pes[i] = (ParameterExpression)le.Parameters[i].ToLightExpression(ref exprsConverted); + return Expression.Lambda(exprType, body, pes, retType); + } + } + case ExpressionType.Dynamic: + { + var de = (System.Linq.Expressions.DynamicExpression)sysExpr; + var sysArgs = de.Arguments; + var args = new Expression[sysArgs.Count]; + for (var i = 0; i < args.Length; ++i) + args[i] = sysArgs[i].ToLightExpression(ref exprsConverted); + return new DynamicExpression(de.DelegateType, de.Binder, args); + } + case ExpressionType.RuntimeVariables: + { + var rve = (System.Linq.Expressions.RuntimeVariablesExpression)sysExpr; + var sysVars = rve.Variables; + var vars = new ParameterExpression[sysVars.Count]; + for (var i = 0; i < vars.Length; ++i) + vars[i] = (ParameterExpression)sysVars[i].ToLightExpression(ref exprsConverted); + return new RuntimeVariablesExpression(vars); + } + case ExpressionType.DebugInfo: + { + var die = (System.Linq.Expressions.DebugInfoExpression)sysExpr; + return Expression.DebugInfo(new SymbolDocumentInfo(die.Document.FileName), + die.StartLine, die.StartColumn, die.EndLine, die.EndColumn); + } + default: + if (sysExpr is System.Linq.Expressions.UnaryExpression ue) + { + var operand = ue.Operand.ToLightExpression(ref exprsConverted); + return Expression.MakeUnary(nodeType, operand, exprType, ue.Method); } if (sysExpr is System.Linq.Expressions.BinaryExpression be) @@ -5924,4 +5948,4 @@ public interface IParameterProvider ParameterExpression GetParameter(int index); } -#nullable restore \ No newline at end of file +#nullable restore diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs new file mode 100644 index 00000000..f1475fa6 --- /dev/null +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -0,0 +1,908 @@ +namespace FastExpressionCompiler.FlatExpression; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using FastExpressionCompiler.LightExpression.ImTools; +using LightExpression = FastExpressionCompiler.LightExpression.Expression; +using SysCatchBlock = System.Linq.Expressions.CatchBlock; +using SysElementInit = System.Linq.Expressions.ElementInit; +using SysExpr = System.Linq.Expressions.Expression; +using SysLabelTarget = System.Linq.Expressions.LabelTarget; +using SysMemberBinding = System.Linq.Expressions.MemberBinding; +using SysParameterExpression = System.Linq.Expressions.ParameterExpression; +using SysSwitchCase = System.Linq.Expressions.SwitchCase; + +public enum ExprNodeKind : byte +{ + Expression, + SwitchCase, + CatchBlock, + LabelTarget, + MemberAssignment, + MemberMemberBinding, + MemberListBinding, + ElementInit, +} + +public struct ExprNode +{ + private const int NodeTypeShift = 56; + private const int KindShift = 48; + private const int NextShift = 32; + private const int CountShift = 16; + private const ulong IndexMask = 0xFFFF; + + public Type Type; + public object Obj; + private ulong _data; + + public ExpressionType NodeType => (ExpressionType)((_data >> NodeTypeShift) & 0xFF); + public ExprNodeKind Kind => (ExprNodeKind)((_data >> KindShift) & 0xFF); + public int NextIdx => (int)((_data >> NextShift) & IndexMask); + public int ChildCount => (int)((_data >> CountShift) & IndexMask); + public int ChildIdx => (int)(_data & IndexMask); + + internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, int childIdx = 0, int childCount = 0, int nextIdx = 0) + { + Type = type; + Obj = obj; + _data = ((ulong)(byte)nodeType << NodeTypeShift) + | ((ulong)(byte)kind << KindShift) + | ((ulong)(ushort)nextIdx << NextShift) + | ((ulong)(ushort)childCount << CountShift) + | (ushort)childIdx; + } + + internal void SetNextIdx(int nextIdx) => + _data = (_data & ~(IndexMask << NextShift)) | ((ulong)(ushort)nextIdx << NextShift); + + internal void SetChildInfo(int childIdx, int childCount) => + _data = (_data & ~((IndexMask << CountShift) | IndexMask)) + | ((ulong)(ushort)childCount << CountShift) + | (ushort)childIdx; +} + +public struct ExprTree +{ + private static readonly object ClosureConstantMarker = new(); + + public int RootIndex; + public SmallList, NoArrayPool> Nodes; + public SmallList, NoArrayPool> ClosureConstants; + + public static ExprTree FromExpression(SysExpr expression) + { + if (expression == null) + throw new ArgumentNullException(nameof(expression)); + + var builder = new Builder(); + return builder.Build(expression); + } + + public static ExprTree FromLightExpression(LightExpression expression) + { + if (expression == null) + throw new ArgumentNullException(nameof(expression)); + + return FromExpression(expression.ToExpression()); + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", + Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] + public SysExpr ToExpression() + { + if (Nodes.Count == 0) + throw new InvalidOperationException("Flat expression tree is empty."); + + return new Reader(this).ReadExpression(RootIndex); + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); + + private sealed class Builder + { + private readonly Dictionary _parameterIds = new(ReferenceEqComparer.Instance); + private readonly Dictionary _labelIds = new(ReferenceEqComparer.Instance); + private ExprTree _tree; + + public ExprTree Build(SysExpr expression) + { + _tree.RootIndex = AddExpression(expression); + return _tree; + } + + private int AddExpression(SysExpr expression) + { + switch (expression.NodeType) + { + case ExpressionType.Constant: + return AddConstant((System.Linq.Expressions.ConstantExpression)expression); + case ExpressionType.Default: + return AddNode(expression.Type, null, expression.NodeType); + case ExpressionType.Parameter: + { + var parameter = (SysParameterExpression)expression; + return AddNode(expression.Type, new ParameterData(GetId(_parameterIds, parameter), parameter.Name, parameter.IsByRef), expression.NodeType); + } + case ExpressionType.Lambda: + { + var lambda = (System.Linq.Expressions.LambdaExpression)expression; + var children = new List(lambda.Parameters.Count + 1) { AddExpression(lambda.Body) }; + for (var i = 0; i < lambda.Parameters.Count; ++i) + children.Add(AddExpression(lambda.Parameters[i])); + return AddNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.Block: + { + var block = (System.Linq.Expressions.BlockExpression)expression; + var children = new List(block.Variables.Count + block.Expressions.Count); + for (var i = 0; i < block.Variables.Count; ++i) + children.Add(AddExpression(block.Variables[i])); + for (var i = 0; i < block.Expressions.Count; ++i) + children.Add(AddExpression(block.Expressions[i])); + return AddNode(expression.Type, new BlockData(block.Variables.Count), expression.NodeType, children); + } + case ExpressionType.MemberAccess: + { + var member = (System.Linq.Expressions.MemberExpression)expression; + return AddNode(expression.Type, member.Member, expression.NodeType, + member.Expression != null ? new List(1) { AddExpression(member.Expression) } : null); + } + case ExpressionType.Call: + { + var call = (System.Linq.Expressions.MethodCallExpression)expression; + var children = new List(call.Arguments.Count + (call.Object != null ? 1 : 0)); + if (call.Object != null) + children.Add(AddExpression(call.Object)); + for (var i = 0; i < call.Arguments.Count; ++i) + children.Add(AddExpression(call.Arguments[i])); + return AddNode(expression.Type, call.Method, expression.NodeType, children); + } + case ExpressionType.New: + { + var @new = (System.Linq.Expressions.NewExpression)expression; + var children = new List(@new.Arguments.Count); + for (var i = 0; i < @new.Arguments.Count; ++i) + children.Add(AddExpression(@new.Arguments[i])); + return AddNode(expression.Type, @new.Constructor, expression.NodeType, children); + } + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + { + var array = (System.Linq.Expressions.NewArrayExpression)expression; + var children = new List(array.Expressions.Count); + for (var i = 0; i < array.Expressions.Count; ++i) + children.Add(AddExpression(array.Expressions[i])); + return AddNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.Invoke: + { + var invoke = (System.Linq.Expressions.InvocationExpression)expression; + var children = new List(invoke.Arguments.Count + 1) { AddExpression(invoke.Expression) }; + for (var i = 0; i < invoke.Arguments.Count; ++i) + children.Add(AddExpression(invoke.Arguments[i])); + return AddNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.Index: + { + var index = (System.Linq.Expressions.IndexExpression)expression; + var children = new List(index.Arguments.Count + (index.Object != null ? 1 : 0)); + if (index.Object != null) + children.Add(AddExpression(index.Object)); + for (var i = 0; i < index.Arguments.Count; ++i) + children.Add(AddExpression(index.Arguments[i])); + return AddNode(expression.Type, index.Indexer, expression.NodeType, children); + } + case ExpressionType.Conditional: + { + var conditional = (System.Linq.Expressions.ConditionalExpression)expression; + return AddNode(expression.Type, null, expression.NodeType, + new List(3) + { + AddExpression(conditional.Test), + AddExpression(conditional.IfTrue), + AddExpression(conditional.IfFalse), + }); + } + case ExpressionType.Loop: + { + var loop = (System.Linq.Expressions.LoopExpression)expression; + var data = new LoopData(loop.BreakLabel != null, loop.ContinueLabel != null); + var children = new List(3) { AddExpression(loop.Body) }; + if (loop.BreakLabel != null) + children.Add(AddLabelTarget(loop.BreakLabel)); + if (loop.ContinueLabel != null) + children.Add(AddLabelTarget(loop.ContinueLabel)); + return AddNode(expression.Type, data, expression.NodeType, children); + } + case ExpressionType.Goto: + { + var @goto = (System.Linq.Expressions.GotoExpression)expression; + var children = new List(2) { AddLabelTarget(@goto.Target) }; + if (@goto.Value != null) + children.Add(AddExpression(@goto.Value)); + return AddNode(expression.Type, @goto.Kind, expression.NodeType, children); + } + case ExpressionType.Label: + { + var label = (System.Linq.Expressions.LabelExpression)expression; + var children = new List(2) { AddLabelTarget(label.Target) }; + if (label.DefaultValue != null) + children.Add(AddExpression(label.DefaultValue)); + return AddNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.Switch: + { + var @switch = (System.Linq.Expressions.SwitchExpression)expression; + var children = new List(@switch.Cases.Count + 2) { AddExpression(@switch.SwitchValue) }; + if (@switch.DefaultBody != null) + children.Add(AddExpression(@switch.DefaultBody)); + for (var i = 0; i < @switch.Cases.Count; ++i) + children.Add(AddSwitchCase(@switch.Cases[i])); + return AddNode(expression.Type, new SwitchData(@switch.DefaultBody != null, @switch.Comparison), expression.NodeType, children); + } + case ExpressionType.Try: + { + var @try = (System.Linq.Expressions.TryExpression)expression; + var children = new List(@try.Handlers.Count + 2) { AddExpression(@try.Body) }; + if (@try.Fault != null) + children.Add(AddExpression(@try.Fault)); + else if (@try.Finally != null) + children.Add(AddExpression(@try.Finally)); + for (var i = 0; i < @try.Handlers.Count; ++i) + children.Add(AddCatchBlock(@try.Handlers[i])); + return AddNode(expression.Type, new TryData(@try.Finally != null, @try.Fault != null), expression.NodeType, children); + } + case ExpressionType.MemberInit: + { + var memberInit = (System.Linq.Expressions.MemberInitExpression)expression; + var children = new List(memberInit.Bindings.Count + 1) { AddExpression(memberInit.NewExpression) }; + for (var i = 0; i < memberInit.Bindings.Count; ++i) + children.Add(AddMemberBinding(memberInit.Bindings[i])); + return AddNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.ListInit: + { + var listInit = (System.Linq.Expressions.ListInitExpression)expression; + var children = new List(listInit.Initializers.Count + 1) { AddExpression(listInit.NewExpression) }; + for (var i = 0; i < listInit.Initializers.Count; ++i) + children.Add(AddElementInit(listInit.Initializers[i])); + return AddNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: + { + var typeBinary = (System.Linq.Expressions.TypeBinaryExpression)expression; + return AddNode(expression.Type, typeBinary.TypeOperand, expression.NodeType, + new List(1) { AddExpression(typeBinary.Expression) }); + } + case ExpressionType.Dynamic: + { + var dynamic = (System.Linq.Expressions.DynamicExpression)expression; + var children = new List(dynamic.Arguments.Count); + for (var i = 0; i < dynamic.Arguments.Count; ++i) + children.Add(AddExpression(dynamic.Arguments[i])); + return AddNode(expression.Type, new DynamicData(dynamic.DelegateType, dynamic.Binder), expression.NodeType, children); + } + case ExpressionType.RuntimeVariables: + { + var runtime = (System.Linq.Expressions.RuntimeVariablesExpression)expression; + var children = new List(runtime.Variables.Count); + for (var i = 0; i < runtime.Variables.Count; ++i) + children.Add(AddExpression(runtime.Variables[i])); + return AddNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.DebugInfo: + { + var debug = (System.Linq.Expressions.DebugInfoExpression)expression; + return AddNode(expression.Type, + new DebugInfoData(debug.Document.FileName, debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn), + expression.NodeType); + } + default: + if (expression is System.Linq.Expressions.UnaryExpression unary) + { + return AddNode(expression.Type, unary.Method, expression.NodeType, + new List(1) { AddExpression(unary.Operand) }); + } + + if (expression is System.Linq.Expressions.BinaryExpression binary) + { + var children = new List(binary.Conversion != null ? 3 : 2) + { + AddExpression(binary.Left), + AddExpression(binary.Right) + }; + if (binary.Conversion != null) + children.Add(AddExpression(binary.Conversion)); + return AddNode(expression.Type, new BinaryData(binary.Method, binary.IsLiftedToNull), expression.NodeType, children); + } + + throw new NotSupportedException($"Flattening of `ExpressionType.{expression.NodeType}` is not supported yet."); + } + } + + private int AddConstant(System.Linq.Expressions.ConstantExpression constant) + { + if (ShouldInlineConstant(constant.Value, constant.Type)) + return AddNode(constant.Type, constant.Value, constant.NodeType); + + var constantIndex = _tree.ClosureConstants.Add(constant.Value); + return AddNode(constant.Type, ClosureConstantMarker, constant.NodeType, ExprNodeKind.Expression, childIdx: constantIndex); + } + + private int AddSwitchCase(SysSwitchCase switchCase) + { + var children = new List(switchCase.TestValues.Count + 1); + for (var i = 0; i < switchCase.TestValues.Count; ++i) + children.Add(AddExpression(switchCase.TestValues[i])); + children.Add(AddExpression(switchCase.Body)); + return AddNode(switchCase.Body.Type, null, ExpressionType.Extension, ExprNodeKind.SwitchCase, children); + } + + private int AddCatchBlock(SysCatchBlock catchBlock) + { + var children = new List(3); + if (catchBlock.Variable != null) + children.Add(AddExpression(catchBlock.Variable)); + children.Add(AddExpression(catchBlock.Body)); + if (catchBlock.Filter != null) + children.Add(AddExpression(catchBlock.Filter)); + return AddNode(catchBlock.Test, new CatchData(catchBlock.Variable != null, catchBlock.Filter != null), + ExpressionType.Extension, ExprNodeKind.CatchBlock, children); + } + + private int AddLabelTarget(SysLabelTarget target) => + AddNode(target.Type, new LabelTargetData(GetId(_labelIds, target), target.Name), ExpressionType.Extension, ExprNodeKind.LabelTarget); + + private int AddMemberBinding(SysMemberBinding binding) + { + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + return AddNode(GetMemberType(binding.Member), binding.Member, ExpressionType.Extension, ExprNodeKind.MemberAssignment, + new List(1) { AddExpression(((System.Linq.Expressions.MemberAssignment)binding).Expression) }); + case MemberBindingType.MemberBinding: + { + var memberBinding = (System.Linq.Expressions.MemberMemberBinding)binding; + var children = new List(memberBinding.Bindings.Count); + for (var i = 0; i < memberBinding.Bindings.Count; ++i) + children.Add(AddMemberBinding(memberBinding.Bindings[i])); + return AddNode(GetMemberType(binding.Member), binding.Member, ExpressionType.Extension, ExprNodeKind.MemberMemberBinding, children); + } + case MemberBindingType.ListBinding: + { + var listBinding = (System.Linq.Expressions.MemberListBinding)binding; + var children = new List(listBinding.Initializers.Count); + for (var i = 0; i < listBinding.Initializers.Count; ++i) + children.Add(AddElementInit(listBinding.Initializers[i])); + return AddNode(GetMemberType(binding.Member), binding.Member, ExpressionType.Extension, ExprNodeKind.MemberListBinding, children); + } + default: + throw new NotSupportedException($"Flattening of member binding `{binding.BindingType}` is not supported yet."); + } + } + + private int AddElementInit(SysElementInit init) + { + var children = new List(init.Arguments.Count); + for (var i = 0; i < init.Arguments.Count; ++i) + children.Add(AddExpression(init.Arguments[i])); + return AddNode(init.AddMethod.DeclaringType, init.AddMethod, ExpressionType.Extension, ExprNodeKind.ElementInit, children); + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, List children = null) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, children); + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, List children = null, int childIdx = 0) + { + var nodeIndex = _tree.Nodes.Add(new ExprNode(type, obj, nodeType, kind, childIdx, 0, 0)); + if (children != null && children.Count != 0) + { + for (var i = 0; i < children.Count - 1; ++i) + { + ref var child = ref _tree.Nodes[children[i]]; + child.SetNextIdx(children[i + 1]); + } + + ref var node = ref _tree.Nodes[nodeIndex]; + node.SetChildInfo(children[0], children.Count); + } + return nodeIndex; + } + + private static bool ShouldInlineConstant(object value, Type type) + { + if (value == null || value is string || value is Type) + return true; + + if (type.IsEnum) + return true; + + return Type.GetTypeCode(type) != TypeCode.Object; + } + + private static int GetId(Dictionary ids, object item) + { + if (ids.TryGetValue(item, out var id)) + return id; + + id = ids.Count + 1; + ids[item] = id; + return id; + } + + private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch + { + System.Reflection.FieldInfo field => field.FieldType, + System.Reflection.PropertyInfo property => property.PropertyType, + _ => typeof(object) + }; + } + + private readonly struct Reader + { + private readonly ExprTree _tree; + private readonly Dictionary _parametersById; + private readonly Dictionary _labelsById; + + public Reader(ExprTree tree) + { + _tree = tree; + _parametersById = new Dictionary(); + _labelsById = new Dictionary(); + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public SysExpr ReadExpression(int index) + { + ref var node = ref _tree.Nodes[index]; + if (node.Kind != ExprNodeKind.Expression) + throw new InvalidOperationException($"Node at index {index} is not an expression node."); + + switch (node.NodeType) + { + case ExpressionType.Constant: + return SysExpr.Constant(ReferenceEquals(node.Obj, ClosureConstantMarker) + ? _tree.ClosureConstants[node.ChildIdx] + : node.Obj, node.Type); + case ExpressionType.Default: + return SysExpr.Default(node.Type); + case ExpressionType.Parameter: + { + var data = (ParameterData)node.Obj; + if (_parametersById.TryGetValue(data.Id, out var parameter)) + return parameter; + + parameter = SysExpr.Parameter(data.IsByRef ? node.Type.MakeByRefType() : node.Type, data.Name); + _parametersById[data.Id] = parameter; + return parameter; + } + case ExpressionType.Lambda: + { + var children = GetChildren(index); + var body = ReadExpression(children[0]); + var parameters = new SysParameterExpression[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + parameters[i - 1] = (SysParameterExpression)ReadExpression(children[i]); + return SysExpr.Lambda(node.Type, body, parameters); + } + case ExpressionType.Block: + { + var data = (BlockData)node.Obj; + var children = GetChildren(index); + var variables = new SysParameterExpression[data.VariableCount]; + for (var i = 0; i < variables.Length; ++i) + variables[i] = (SysParameterExpression)ReadExpression(children[i]); + var expressions = new SysExpr[children.Count - data.VariableCount]; + for (var i = data.VariableCount; i < children.Count; ++i) + expressions[i - data.VariableCount] = ReadExpression(children[i]); + return SysExpr.Block(node.Type, variables, expressions); + } + case ExpressionType.MemberAccess: + { + var children = GetChildren(index); + return SysExpr.MakeMemberAccess(children.Count != 0 ? ReadExpression(children[0]) : null, (System.Reflection.MemberInfo)node.Obj); + } + case ExpressionType.Call: + { + var method = (System.Reflection.MethodInfo)node.Obj; + var children = GetChildren(index); + var hasInstance = !method.IsStatic; + var instance = hasInstance ? ReadExpression(children[0]) : null; + var arguments = new SysExpr[children.Count - (hasInstance ? 1 : 0)]; + for (var i = hasInstance ? 1 : 0; i < children.Count; ++i) + arguments[i - (hasInstance ? 1 : 0)] = ReadExpression(children[i]); + return SysExpr.Call(instance, method, arguments); + } + case ExpressionType.New: + { + var children = GetChildren(index); + var arguments = ReadExpressions(children); + return node.Obj is System.Reflection.ConstructorInfo ctor + ? SysExpr.New(ctor, arguments) + : CreateValueTypeNewExpression(node.Type); + } + case ExpressionType.NewArrayInit: + return SysExpr.NewArrayInit(node.Type.GetElementType(), ReadExpressions(GetChildren(index))); + case ExpressionType.NewArrayBounds: + return SysExpr.NewArrayBounds(node.Type.GetElementType(), ReadExpressions(GetChildren(index))); + case ExpressionType.Invoke: + { + var children = GetChildren(index); + var arguments = new SysExpr[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + arguments[i - 1] = ReadExpression(children[i]); + return SysExpr.Invoke(ReadExpression(children[0]), arguments); + } + case ExpressionType.Index: + { + var children = GetChildren(index); + var property = (System.Reflection.PropertyInfo)node.Obj; + var hasInstance = property != null || children.Count > 1; + var instance = hasInstance ? ReadExpression(children[0]) : null; + var arguments = new SysExpr[children.Count - (hasInstance ? 1 : 0)]; + for (var i = hasInstance ? 1 : 0; i < children.Count; ++i) + arguments[i - (hasInstance ? 1 : 0)] = ReadExpression(children[i]); + return property != null + ? SysExpr.Property(instance, property, arguments) + : SysExpr.ArrayAccess(instance, arguments); + } + case ExpressionType.Conditional: + { + var children = GetChildren(index); + return SysExpr.Condition(ReadExpression(children[0]), ReadExpression(children[1]), ReadExpression(children[2]), node.Type); + } + case ExpressionType.Loop: + { + var data = (LoopData)node.Obj; + var children = GetChildren(index); + var childIndex = 1; + var breakLabel = data.HasBreak ? ReadLabelTarget(children[childIndex++]) : null; + var continueLabel = data.HasContinue ? ReadLabelTarget(children[childIndex]) : null; + return SysExpr.Loop(ReadExpression(children[0]), breakLabel, continueLabel); + } + case ExpressionType.Goto: + { + var children = GetChildren(index); + var value = children.Count > 1 ? ReadExpression(children[1]) : null; + return SysExpr.MakeGoto((GotoExpressionKind)node.Obj, ReadLabelTarget(children[0]), value, node.Type); + } + case ExpressionType.Label: + { + var children = GetChildren(index); + var defaultValue = children.Count > 1 ? ReadExpression(children[1]) : null; + return SysExpr.Label(ReadLabelTarget(children[0]), defaultValue); + } + case ExpressionType.Switch: + { + var data = (SwitchData)node.Obj; + var children = GetChildren(index); + var childIndex = 1; + var defaultBody = data.HasDefault ? ReadExpression(children[childIndex++]) : null; + var cases = new SysSwitchCase[children.Count - childIndex]; + for (var i = childIndex; i < children.Count; ++i) + cases[i - childIndex] = ReadSwitchCase(children[i]); + return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, data.Comparison, cases); + } + case ExpressionType.Try: + { + var data = (TryData)node.Obj; + var children = GetChildren(index); + var childIndex = 1; + if (data.HasFault) + return SysExpr.TryFault(ReadExpression(children[0]), ReadExpression(children[1])); + + var @finally = data.HasFinally ? ReadExpression(children[childIndex++]) : null; + var handlers = new SysCatchBlock[children.Count - childIndex]; + for (var i = childIndex; i < children.Count; ++i) + handlers[i - childIndex] = ReadCatchBlock(children[i]); + return SysExpr.TryCatchFinally(ReadExpression(children[0]), @finally, handlers); + } + case ExpressionType.MemberInit: + { + var children = GetChildren(index); + var bindings = new SysMemberBinding[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + bindings[i - 1] = ReadMemberBinding(children[i]); + return SysExpr.MemberInit((System.Linq.Expressions.NewExpression)ReadExpression(children[0]), bindings); + } + case ExpressionType.ListInit: + { + var children = GetChildren(index); + var initializers = new SysElementInit[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + initializers[i - 1] = ReadElementInit(children[i]); + return SysExpr.ListInit((System.Linq.Expressions.NewExpression)ReadExpression(children[0]), initializers); + } + case ExpressionType.TypeIs: + return SysExpr.TypeIs(ReadExpression(GetChildren(index)[0]), (Type)node.Obj); + case ExpressionType.TypeEqual: + return SysExpr.TypeEqual(ReadExpression(GetChildren(index)[0]), (Type)node.Obj); + case ExpressionType.Dynamic: + { + var data = (DynamicData)node.Obj; + return SysExpr.MakeDynamic(data.DelegateType, data.Binder, ReadExpressions(GetChildren(index))); + } + case ExpressionType.RuntimeVariables: + { + var children = GetChildren(index); + var variables = new SysParameterExpression[children.Count]; + for (var i = 0; i < children.Count; ++i) + variables[i] = (SysParameterExpression)ReadExpression(children[i]); + return SysExpr.RuntimeVariables(variables); + } + case ExpressionType.DebugInfo: + { + var data = (DebugInfoData)node.Obj; + return SysExpr.DebugInfo(SysExpr.SymbolDocument(data.FileName), + data.StartLine, data.StartColumn, data.EndLine, data.EndColumn); + } + default: + if (node.ChildCount == 1) + { + var method = node.Obj as System.Reflection.MethodInfo; + return SysExpr.MakeUnary(node.NodeType, ReadExpression(GetChildren(index)[0]), node.Type, method); + } + + if (node.ChildCount >= 2) + { + var data = node.Obj as BinaryData; + var children = GetChildren(index); + var conversion = children.Count > 2 ? (System.Linq.Expressions.LambdaExpression)ReadExpression(children[2]) : null; + return SysExpr.MakeBinary(node.NodeType, ReadExpression(children[0]), ReadExpression(children[1]), + data != null && data.IsLiftedToNull, data?.Method, conversion); + } + + throw new NotSupportedException($"Reconstruction of `ExpressionType.{node.NodeType}` is not supported yet."); + } + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysSwitchCase ReadSwitchCase(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Kind == ExprNodeKind.SwitchCase); + var children = GetChildren(index); + var testValues = new SysExpr[children.Count - 1]; + for (var i = 0; i < testValues.Length; ++i) + testValues[i] = ReadExpression(children[i]); + return SysExpr.SwitchCase(ReadExpression(children[^1]), testValues); + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysCatchBlock ReadCatchBlock(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Kind == ExprNodeKind.CatchBlock); + var data = (CatchData)node.Obj; + var children = GetChildren(index); + var childIndex = 0; + var variable = data.HasVariable ? (SysParameterExpression)ReadExpression(children[childIndex++]) : null; + var body = ReadExpression(children[childIndex++]); + var filter = data.HasFilter ? ReadExpression(children[childIndex]) : null; + return SysExpr.MakeCatchBlock(node.Type, variable, body, filter); + } + + private SysLabelTarget ReadLabelTarget(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Kind == ExprNodeKind.LabelTarget); + var data = (LabelTargetData)node.Obj; + if (_labelsById.TryGetValue(data.Id, out var label)) + return label; + + label = SysExpr.Label(node.Type, data.Name); + _labelsById[data.Id] = label; + return label; + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysMemberBinding ReadMemberBinding(int index) + { + ref var node = ref _tree.Nodes[index]; + var member = (System.Reflection.MemberInfo)node.Obj; + switch (node.Kind) + { + case ExprNodeKind.MemberAssignment: + return SysExpr.Bind(member, ReadExpression(GetChildren(index)[0])); + case ExprNodeKind.MemberMemberBinding: + { + var childIndexes = GetChildren(index); + var bindings = new SysMemberBinding[childIndexes.Count]; + for (var i = 0; i < childIndexes.Count; ++i) + bindings[i] = ReadMemberBinding(childIndexes[i]); + return SysExpr.MemberBind(member, bindings); + } + case ExprNodeKind.MemberListBinding: + { + var childIndexes = GetChildren(index); + var initializers = new SysElementInit[childIndexes.Count]; + for (var i = 0; i < childIndexes.Count; ++i) + initializers[i] = ReadElementInit(childIndexes[i]); + return SysExpr.ListBind(member, initializers); + } + default: + throw new InvalidOperationException($"Node at index {index} is not a member binding node."); + } + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysElementInit ReadElementInit(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Kind == ExprNodeKind.ElementInit); + return SysExpr.ElementInit((System.Reflection.MethodInfo)node.Obj, ReadExpressions(GetChildren(index))); + } + + private List GetChildren(int index) + { + ref var node = ref _tree.Nodes[index]; + var count = node.ChildCount; + var children = new List(count); + var childIndex = node.ChildIdx; + for (var i = 0; i < count; ++i) + { + children.Add(childIndex); + childIndex = _tree.Nodes[childIndex].NextIdx; + } + return children; + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysExpr[] ReadExpressions(List childIndexes) + { + var expressions = new SysExpr[childIndexes.Count]; + for (var i = 0; i < expressions.Length; ++i) + expressions[i] = ReadExpression(childIndexes[i]); + return expressions; + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", + Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] + private static System.Linq.Expressions.NewExpression CreateValueTypeNewExpression(Type type) => SysExpr.New(type); + } + + private sealed class ParameterData + { + public readonly int Id; + public readonly string Name; + public readonly bool IsByRef; + + public ParameterData(int id, string name, bool isByRef) + { + Id = id; + Name = name; + IsByRef = isByRef; + } + } + + private sealed class LabelTargetData + { + public readonly int Id; + public readonly string Name; + + public LabelTargetData(int id, string name) + { + Id = id; + Name = name; + } + } + + private sealed class BlockData + { + public readonly int VariableCount; + public BlockData(int variableCount) => VariableCount = variableCount; + } + + private sealed class SwitchData + { + public readonly bool HasDefault; + public readonly System.Reflection.MethodInfo Comparison; + + public SwitchData(bool hasDefault, System.Reflection.MethodInfo comparison) + { + HasDefault = hasDefault; + Comparison = comparison; + } + } + + private sealed class TryData + { + public readonly bool HasFinally; + public readonly bool HasFault; + + public TryData(bool hasFinally, bool hasFault) + { + HasFinally = hasFinally; + HasFault = hasFault; + } + } + + private sealed class LoopData + { + public readonly bool HasBreak; + public readonly bool HasContinue; + + public LoopData(bool hasBreak, bool hasContinue) + { + HasBreak = hasBreak; + HasContinue = hasContinue; + } + } + + private sealed class CatchData + { + public readonly bool HasVariable; + public readonly bool HasFilter; + + public CatchData(bool hasVariable, bool hasFilter) + { + HasVariable = hasVariable; + HasFilter = hasFilter; + } + } + + private sealed class BinaryData + { + public readonly System.Reflection.MethodInfo Method; + public readonly bool IsLiftedToNull; + + public BinaryData(System.Reflection.MethodInfo method, bool isLiftedToNull) + { + Method = method; + IsLiftedToNull = isLiftedToNull; + } + } + + private sealed class DynamicData + { + public readonly Type DelegateType; + public readonly CallSiteBinder Binder; + + public DynamicData(Type delegateType, CallSiteBinder binder) + { + DelegateType = delegateType; + Binder = binder; + } + } + + private sealed class DebugInfoData + { + public readonly string FileName; + public readonly int StartLine; + public readonly int StartColumn; + public readonly int EndLine; + public readonly int EndColumn; + + public DebugInfoData(string fileName, int startLine, int startColumn, int endLine, int endColumn) + { + FileName = fileName; + StartLine = startLine; + StartColumn = startColumn; + EndLine = endLine; + EndColumn = endColumn; + } + } + + private sealed class ReferenceEqComparer : IEqualityComparer + { + public static readonly ReferenceEqComparer Instance = new(); + public new bool Equals(object x, object y) => ReferenceEquals(x, y); + public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); + } +} + +public static class FlatExpressionExtensions +{ + public static ExprTree ToFlatExpression(this SysExpr expression) => ExprTree.FromExpression(expression); + + public static ExprTree ToFlatExpression(this LightExpression expression) => ExprTree.FromLightExpression(expression); +} diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 6421f4a4..16e13347 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -1,7 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; +using FastExpressionCompiler.FlatExpression; using static FastExpressionCompiler.LightExpression.Expression; using System.Linq.Expressions; @@ -26,7 +28,10 @@ public int Run() Should_output_the_System_and_LightExpression_to_the_identical_CSharp_syntax(); Expression_produced_by_ToExpressionString_should_compile(); Multiple_methods_in_block_should_be_aligned_when_output_to_csharp(); - return 11; + Can_roundtrip_light_expression_through_flat_expression(); + Flat_expression_preserves_parameter_and_label_identity_and_collects_closure_constants(); + Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expression_and_flat_expression(); + return 14; } @@ -339,6 +344,86 @@ public void Multiple_methods_in_block_should_be_aligned_when_output_to_csharp() public static void SayHi(int i, int j) { } + public void Can_roundtrip_light_expression_through_flat_expression() + { + var expr = CreateComplexLightExpression("state"); + + var flat = expr.ToFlatExpression(); + + Asserts.IsTrue(flat.Nodes.Count > 0); + Asserts.AreEqual(0, flat.ClosureConstants.Count); + + var roundtrip = (LambdaExpression)flat.ToLightExpression(); + var func = roundtrip.CompileFast>(true); + var a = (A)func(new object[12] { null, null, null, null, null, null, null, null, null, null, null, "flat" }); + + Asserts.AreEqual("flat", a.Sop); + Asserts.IsInstanceOf

(a.Prop); + Asserts.AreEqual(2, a.Dop.Count()); + } + + public void Flat_expression_preserves_parameter_and_label_identity_and_collects_closure_constants() + { + var valueHolder = new S(); + var valueField = typeof(S).GetField(nameof(S.Value)); + var constExpr = Lambda>(Field(Constant(valueHolder), valueField)); + var constFlat = constExpr.ToFlatExpression(); + + Asserts.AreEqual(1, constFlat.ClosureConstants.Count); + Asserts.AreSame(valueHolder, constFlat.ClosureConstants[0]); + Asserts.AreEqual(null, ((LambdaExpression)constFlat.ToLightExpression()).CompileFast>(true)()); + + var p = SysExpr.Parameter(typeof(int), "p"); + var target = SysExpr.Label(typeof(int), "done"); + var sysLambda = SysExpr.Lambda>( + SysExpr.Block( + SysExpr.Goto(target, p, typeof(int)), + SysExpr.Label(target, SysExpr.Constant(0))), + p); + + var sysRoundtrip = (System.Linq.Expressions.LambdaExpression)sysLambda + .ToFlatExpression() + .ToExpression(); + + var block = (System.Linq.Expressions.BlockExpression)sysRoundtrip.Body; + var @goto = (System.Linq.Expressions.GotoExpression)block.Expressions[0]; + var label = (System.Linq.Expressions.LabelExpression)block.Expressions[1]; + + Asserts.AreSame(sysRoundtrip.Parameters[0], @goto.Value); + Asserts.AreSame(@goto.Target, label.Target); + } + + public void Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expression_and_flat_expression() + { + var runtimeParameter = SysExpr.Parameter(typeof(int), "runtime"); + var runtimeVariables = SysExpr.RuntimeVariables(runtimeParameter); + var runtimeVariablesLight = runtimeVariables.ToLightExpression(); + var runtimeVariablesRoundtrip = runtimeVariablesLight.ToFlatExpression().ToLightExpression(); + + Asserts.AreEqual(ExpressionType.RuntimeVariables, runtimeVariablesLight.NodeType); + Asserts.AreEqual(ExpressionType.RuntimeVariables, runtimeVariablesRoundtrip.NodeType); + + var document = SysExpr.SymbolDocument("flat-expression.cs"); + var debugInfo = SysExpr.DebugInfo(document, 1, 1, 1, 10); + var debugInfoLight = debugInfo.ToLightExpression(); + var debugInfoRoundtrip = debugInfoLight.ToFlatExpression().ToLightExpression(); + + Asserts.AreEqual(ExpressionType.DebugInfo, debugInfoLight.NodeType); + Asserts.AreEqual(ExpressionType.DebugInfo, debugInfoRoundtrip.NodeType); + + var dynamicArgument = SysExpr.Parameter(typeof(object), "arg"); + var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags.None, "Length", typeof(LightExpressionTests), + new[] { Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags.None, null) }); + var dynamicExpression = SysExpr.MakeDynamic(typeof(Func), binder, new[] { dynamicArgument }); + + var dynamicLight = dynamicExpression.ToLightExpression(); + var dynamicRoundtrip = dynamicLight.ToFlatExpression().ToLightExpression(); + + Asserts.AreEqual(ExpressionType.Dynamic, dynamicLight.NodeType); + Asserts.AreEqual(ExpressionType.Dynamic, dynamicRoundtrip.NodeType); + Asserts.AreEqual(ExpressionType.Dynamic, dynamicLight.ToFlatExpression().ToExpression().NodeType); + } + public class A { public P Prop { get; set; } From 330fc9e2f8ea304117447caa38ef99c1f8671007 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:18:20 +0000 Subject: [PATCH 03/15] fix: finalize flat expression roundtrip review feedback Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/631bfa1a-7ed7-4397-b2cb-ab11dfbcbdee Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- src/FastExpressionCompiler.LightExpression/FlatExpression.cs | 5 +++-- .../LightExpressionTests.cs | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index f1475fa6..dbc68896 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -480,7 +480,8 @@ public SysExpr ReadExpression(int index) if (_parametersById.TryGetValue(data.Id, out var parameter)) return parameter; - parameter = SysExpr.Parameter(data.IsByRef ? node.Type.MakeByRefType() : node.Type, data.Name); + var parameterType = data.IsByRef && !node.Type.IsByRef ? node.Type.MakeByRefType() : node.Type; + parameter = SysExpr.Parameter(parameterType, data.Name); _parametersById[data.Id] = parameter; return parameter; } @@ -673,7 +674,7 @@ private SysSwitchCase ReadSwitchCase(int index) var testValues = new SysExpr[children.Count - 1]; for (var i = 0; i < testValues.Length; ++i) testValues[i] = ReadExpression(children[i]); - return SysExpr.SwitchCase(ReadExpression(children[^1]), testValues); + return SysExpr.SwitchCase(ReadExpression(children[children.Count - 1]), testValues); } [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 16e13347..12c50904 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -355,7 +355,9 @@ public void Can_roundtrip_light_expression_through_flat_expression() var roundtrip = (LambdaExpression)flat.ToLightExpression(); var func = roundtrip.CompileFast>(true); - var a = (A)func(new object[12] { null, null, null, null, null, null, null, null, null, null, null, "flat" }); + var state = new object[12]; + state[11] = "flat"; + var a = (A)func(state); Asserts.AreEqual("flat", a.Sop); Asserts.IsInstanceOf

(a.Prop); From b6a8952385bc4291ee299d43371d627ae74aaeff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:50:45 +0000 Subject: [PATCH 04/15] feat: add direct flat expression builder api Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/15dd11ed-8bf0-4fd4-b514-3d2ff4e4e346 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 510 +++++++++++++++--- .../LightExpressionTests.cs | 56 +- 2 files changed, 500 insertions(+), 66 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index dbc68896..dc2687e1 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -74,6 +74,272 @@ public struct ExprTree public SmallList, NoArrayPool> Nodes; public SmallList, NoArrayPool> ClosureConstants; + public int Parameter(Type type, string name = null) + { + var id = Nodes.Count + 1; + return AddRawExpressionNode(type, new ParameterData(id, name, type.IsByRef), ExpressionType.Parameter); + } + + public int ParameterOf(string name = null) => Parameter(typeof(T), name); + + public int Variable(Type type, string name = null) => Parameter(type, name); + + public int Default(Type type) => AddRawExpressionNode(type, null, ExpressionType.Default); + + public int Constant(object value) => + Constant(value, value?.GetType() ?? typeof(object)); + + public int Constant(object value, Type type) + { + if (ShouldInlineConstant(value, type)) + return AddRawExpressionNode(type, value, ExpressionType.Constant); + + var constantIndex = ClosureConstants.Add(value); + return AddRawExpressionNodeWithChildIndex(type, ClosureConstantMarker, ExpressionType.Constant, constantIndex); + } + + public int ConstantNull(Type type = null) => AddRawExpressionNode(type ?? typeof(object), null, ExpressionType.Constant); + + public int ConstantInt(int value) => AddRawExpressionNode(typeof(int), value, ExpressionType.Constant); + + public int ConstantOf(T value) => Constant(value, typeof(T)); + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public int New(Type type) + { + if (type.IsValueType) + return AddRawExpressionNode(type, null, ExpressionType.New); + + foreach (var ctor in type.GetConstructors()) + if (ctor.GetParameters().Length == 0) + return New(ctor); + + throw new ArgumentException($"The type {type} is missing the default constructor"); + } + + public int New(System.Reflection.ConstructorInfo constructor, params int[] arguments) => + AddFactoryExpressionNode(constructor.DeclaringType, constructor, ExpressionType.New, arguments); + + public int NewArrayInit(Type elementType, params int[] expressions) => + AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayInit, expressions); + + public int NewArrayBounds(Type elementType, params int[] bounds) => + AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayBounds, bounds); + + public int Invoke(int expression, params int[] arguments) => + AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, Prepend(expression, arguments)); + + public int Call(System.Reflection.MethodInfo method, params int[] arguments) => + AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, arguments); + + public int Call(int instance, System.Reflection.MethodInfo method, params int[] arguments) => + AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, Prepend(instance, arguments)); + + public int MakeMemberAccess(int? instance, System.Reflection.MemberInfo member) => + AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, + instance.HasValue ? Single(instance.Value) : null); + + public int Field(int instance, System.Reflection.FieldInfo field) => MakeMemberAccess(instance, field); + + public int Property(int instance, System.Reflection.PropertyInfo property) => MakeMemberAccess(instance, property); + + public int Property(System.Reflection.PropertyInfo property) => MakeMemberAccess(null, property); + + public int Property(int instance, System.Reflection.PropertyInfo property, params int[] arguments) => + arguments == null || arguments.Length == 0 + ? Property(instance, property) + : AddFactoryExpressionNode(property.PropertyType, property, ExpressionType.Index, Prepend(instance, arguments)); + + public int ArrayIndex(int array, int index) => MakeBinary(ExpressionType.ArrayIndex, array, index); + + public int ArrayAccess(int array, params int[] indexes) => + indexes != null && indexes.Length == 1 + ? ArrayIndex(array, indexes[0]) + : AddFactoryExpressionNode(GetArrayElementType(Nodes[array].Type, indexes?.Length ?? 0), null, ExpressionType.Index, Prepend(array, indexes)); + + public int Convert(int operand, Type type, System.Reflection.MethodInfo method = null) => + AddFactoryExpressionNode(type, method, ExpressionType.Convert, operand); + + public int TypeAs(int operand, Type type) => + AddFactoryExpressionNode(type, null, ExpressionType.TypeAs, operand); + + public int Negate(int operand, System.Reflection.MethodInfo method = null) => + MakeUnary(ExpressionType.Negate, operand, method: method); + + public int Not(int operand, System.Reflection.MethodInfo method = null) => + MakeUnary(ExpressionType.Not, operand, method: method); + + public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, System.Reflection.MethodInfo method = null) => + AddFactoryExpressionNode(type ?? GetUnaryResultType(nodeType, Nodes[operand].Type, method), method, nodeType, operand); + + public int Assign(int left, int right) => MakeBinary(ExpressionType.Assign, left, right); + + public int Add(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Add, left, right, method: method); + + public int Equal(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Equal, left, right, method: method); + + public int MakeBinary(ExpressionType nodeType, int left, int right, bool isLiftedToNull = false, + System.Reflection.MethodInfo method = null, int? conversion = null, Type type = null) + { + var children = conversion.HasValue ? new[] { left, right, conversion.Value } : new[] { left, right }; + return AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), + new BinaryData(method, isLiftedToNull), nodeType, children); + } + + public int Condition(int test, int ifTrue, int ifFalse, Type type = null) => + AddFactoryExpressionNode(type ?? Nodes[ifTrue].Type, null, ExpressionType.Conditional, new[] { test, ifTrue, ifFalse }); + + public int Block(params int[] expressions) => + Block(null, null, expressions); + + public int Block(Type type, IEnumerable variables, params int[] expressions) + { + if (expressions == null || expressions.Length == 0) + throw new ArgumentException("Block should contain at least one expression.", nameof(expressions)); + + var children = new List(); + var variableCount = 0; + if (variables != null) + { + foreach (var variable in variables) + { + children.Add(variable); + ++variableCount; + } + } + children.AddRange(expressions); + return AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, + new BlockData(variableCount), ExpressionType.Block, children); + } + + public int Lambda(int body, params int[] parameters) where TDelegate : Delegate => + Lambda(typeof(TDelegate), body, parameters); + + public int Lambda(Type delegateType, int body, params int[] parameters) => + AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, Prepend(body, parameters)); + + public int Bind(System.Reflection.MemberInfo member, int expression) => + AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberAssignment, expression); + + public int MemberBind(System.Reflection.MemberInfo member, params int[] bindings) => + AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberMemberBinding, bindings); + + public int ElementInit(System.Reflection.MethodInfo addMethod, params int[] arguments) => + AddFactoryAuxNode(addMethod.DeclaringType, addMethod, ExprNodeKind.ElementInit, arguments); + + public int ListBind(System.Reflection.MemberInfo member, params int[] initializers) => + AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberListBinding, initializers); + + public int MemberInit(int @new, params int[] bindings) => + AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, Prepend(@new, bindings)); + + public int ListInit(int @new, params int[] initializers) => + AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, Prepend(@new, initializers)); + + public int Label(Type type = null, string name = null) + { + var id = Nodes.Count + 1; + return AddRawAuxNode(type ?? typeof(void), new LabelTargetData(id, name), ExprNodeKind.LabelTarget); + } + + public int Label(int target, int? defaultValue = null) => + AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, + defaultValue.HasValue ? new[] { target, defaultValue.Value } : new[] { target }); + + public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type type = null) + { + var resultType = type ?? (value.HasValue ? Nodes[value.Value].Type : typeof(void)); + return AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, + value.HasValue ? new[] { target, value.Value } : new[] { target }); + } + + public int Goto(int target, int? value = null, Type type = null) => MakeGoto(GotoExpressionKind.Goto, target, value, type); + + public int Return(int target, int value) => MakeGoto(GotoExpressionKind.Return, target, value, Nodes[value].Type); + + public int Loop(int body, int? @break = null, int? @continue = null) + { + var children = new List { body }; + if (@break.HasValue) + children.Add(@break.Value); + if (@continue.HasValue) + children.Add(@continue.Value); + return AddFactoryExpressionNode(typeof(void), new LoopData(@break.HasValue, @continue.HasValue), ExpressionType.Loop, children); + } + + public int SwitchCase(int body, params int[] testValues) + { + var children = new List(testValues?.Length + 1 ?? 1); + if (testValues != null && testValues.Length != 0) + children.AddRange(testValues); + children.Add(body); + return AddFactoryAuxNode(Nodes[body].Type, null, ExprNodeKind.SwitchCase, children); + } + + public int Switch(int switchValue, params int[] cases) => + Switch(Nodes[switchValue].Type, switchValue, null, null, cases); + + public int Switch(Type type, int switchValue, int? defaultBody, System.Reflection.MethodInfo comparison, params int[] cases) + { + var children = new List(cases?.Length + 2 ?? 1) { switchValue }; + if (defaultBody.HasValue) + children.Add(defaultBody.Value); + if (cases != null && cases.Length != 0) + children.AddRange(cases); + return AddFactoryExpressionNode(type, new SwitchData(defaultBody.HasValue, comparison), ExpressionType.Switch, children); + } + + public int Catch(int variable, int body) => + AddFactoryAuxNode(Nodes[variable].Type, new CatchData(true, false), ExprNodeKind.CatchBlock, new[] { variable, body }); + + public int Catch(Type test, int body) => + AddFactoryAuxNode(test, new CatchData(false, false), ExprNodeKind.CatchBlock, new[] { body }); + + public int MakeCatchBlock(Type test, int? variable, int body, int? filter) + { + var children = new List(3); + if (variable.HasValue) + children.Add(variable.Value); + children.Add(body); + if (filter.HasValue) + children.Add(filter.Value); + return AddFactoryAuxNode(test, new CatchData(variable.HasValue, filter.HasValue), ExprNodeKind.CatchBlock, children); + } + + public int TryCatch(int body, params int[] handlers) => + AddFactoryExpressionNode(Nodes[body].Type, new TryData(false, false), ExpressionType.Try, Prepend(body, handlers)); + + public int TryFinally(int body, int @finally) => + AddFactoryExpressionNode(Nodes[body].Type, new TryData(true, false), ExpressionType.Try, new[] { body, @finally }); + + public int TryFault(int body, int fault) => + AddFactoryExpressionNode(Nodes[body].Type, new TryData(false, true), ExpressionType.Try, new[] { body, fault }); + + public int TryCatchFinally(int body, int? @finally, params int[] handlers) + { + var children = new List { body }; + if (@finally.HasValue) + children.Add(@finally.Value); + if (handlers != null && handlers.Length != 0) + children.AddRange(handlers); + return AddFactoryExpressionNode(Nodes[body].Type, new TryData(@finally.HasValue, false), ExpressionType.Try, children); + } + + public int TypeIs(int expression, Type type) => + AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeIs, expression); + + public int TypeEqual(int expression, Type type) => + AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeEqual, expression); + + public int Dynamic(Type delegateType, CallSiteBinder binder, params int[] arguments) => + AddFactoryExpressionNode(typeof(object), new DynamicData(delegateType, binder), ExpressionType.Dynamic, arguments); + + public int RuntimeVariables(params int[] variables) => + AddFactoryExpressionNode(typeof(IRuntimeVariables), null, ExpressionType.RuntimeVariables, variables); + + public int DebugInfo(string fileName, int startLine, int startColumn, int endLine, int endColumn) => + AddRawExpressionNode(typeof(void), new DebugInfoData(fileName, startLine, startColumn, endLine, endColumn), ExpressionType.DebugInfo); + public static ExprTree FromExpression(SysExpr expression) { if (expression == null) @@ -105,6 +371,42 @@ public SysExpr ToExpression() [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => + AddRawExpressionNode(type, obj, nodeType, CloneChild(child)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, params int[] children) => + AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children) => + AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children)); + + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => + AddRawExpressionNode(type, obj, nodeType, new[] { child }); + + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, params int[] children) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0); + + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0); + + private int AddRawExpressionNodeWithChildIndex(Type type, object obj, ExpressionType nodeType, int childIdx) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, null, childIdx); + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int child) => + AddRawAuxNode(type, obj, kind, CloneChild(child)); + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, params int[] children) => + AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children)); + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children) => + AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children)); + + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, params int[] children) => + AddNode(type, obj, ExpressionType.Extension, kind, children, 0); + + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children) => + AddNode(type, obj, ExpressionType.Extension, kind, children, 0); + private sealed class Builder { private readonly Dictionary _parameterIds = new(ReferenceEqComparer.Instance); @@ -124,11 +426,11 @@ private int AddExpression(SysExpr expression) case ExpressionType.Constant: return AddConstant((System.Linq.Expressions.ConstantExpression)expression); case ExpressionType.Default: - return AddNode(expression.Type, null, expression.NodeType); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType); case ExpressionType.Parameter: { var parameter = (SysParameterExpression)expression; - return AddNode(expression.Type, new ParameterData(GetId(_parameterIds, parameter), parameter.Name, parameter.IsByRef), expression.NodeType); + return _tree.AddRawExpressionNode(expression.Type, new ParameterData(GetId(_parameterIds, parameter), parameter.Name, parameter.IsByRef), expression.NodeType); } case ExpressionType.Lambda: { @@ -136,7 +438,7 @@ private int AddExpression(SysExpr expression) var children = new List(lambda.Parameters.Count + 1) { AddExpression(lambda.Body) }; for (var i = 0; i < lambda.Parameters.Count; ++i) children.Add(AddExpression(lambda.Parameters[i])); - return AddNode(expression.Type, null, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); } case ExpressionType.Block: { @@ -146,12 +448,12 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(block.Variables[i])); for (var i = 0; i < block.Expressions.Count; ++i) children.Add(AddExpression(block.Expressions[i])); - return AddNode(expression.Type, new BlockData(block.Variables.Count), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, new BlockData(block.Variables.Count), expression.NodeType, children); } case ExpressionType.MemberAccess: { var member = (System.Linq.Expressions.MemberExpression)expression; - return AddNode(expression.Type, member.Member, expression.NodeType, + return _tree.AddRawExpressionNode(expression.Type, member.Member, expression.NodeType, member.Expression != null ? new List(1) { AddExpression(member.Expression) } : null); } case ExpressionType.Call: @@ -162,7 +464,7 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(call.Object)); for (var i = 0; i < call.Arguments.Count; ++i) children.Add(AddExpression(call.Arguments[i])); - return AddNode(expression.Type, call.Method, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, call.Method, expression.NodeType, children); } case ExpressionType.New: { @@ -170,7 +472,7 @@ private int AddExpression(SysExpr expression) var children = new List(@new.Arguments.Count); for (var i = 0; i < @new.Arguments.Count; ++i) children.Add(AddExpression(@new.Arguments[i])); - return AddNode(expression.Type, @new.Constructor, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, @new.Constructor, expression.NodeType, children); } case ExpressionType.NewArrayInit: case ExpressionType.NewArrayBounds: @@ -179,7 +481,7 @@ private int AddExpression(SysExpr expression) var children = new List(array.Expressions.Count); for (var i = 0; i < array.Expressions.Count; ++i) children.Add(AddExpression(array.Expressions[i])); - return AddNode(expression.Type, null, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); } case ExpressionType.Invoke: { @@ -187,7 +489,7 @@ private int AddExpression(SysExpr expression) var children = new List(invoke.Arguments.Count + 1) { AddExpression(invoke.Expression) }; for (var i = 0; i < invoke.Arguments.Count; ++i) children.Add(AddExpression(invoke.Arguments[i])); - return AddNode(expression.Type, null, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); } case ExpressionType.Index: { @@ -197,12 +499,12 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(index.Object)); for (var i = 0; i < index.Arguments.Count; ++i) children.Add(AddExpression(index.Arguments[i])); - return AddNode(expression.Type, index.Indexer, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, index.Indexer, expression.NodeType, children); } case ExpressionType.Conditional: { var conditional = (System.Linq.Expressions.ConditionalExpression)expression; - return AddNode(expression.Type, null, expression.NodeType, + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, new List(3) { AddExpression(conditional.Test), @@ -219,7 +521,7 @@ private int AddExpression(SysExpr expression) children.Add(AddLabelTarget(loop.BreakLabel)); if (loop.ContinueLabel != null) children.Add(AddLabelTarget(loop.ContinueLabel)); - return AddNode(expression.Type, data, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, data, expression.NodeType, children); } case ExpressionType.Goto: { @@ -227,7 +529,7 @@ private int AddExpression(SysExpr expression) var children = new List(2) { AddLabelTarget(@goto.Target) }; if (@goto.Value != null) children.Add(AddExpression(@goto.Value)); - return AddNode(expression.Type, @goto.Kind, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, @goto.Kind, expression.NodeType, children); } case ExpressionType.Label: { @@ -235,7 +537,7 @@ private int AddExpression(SysExpr expression) var children = new List(2) { AddLabelTarget(label.Target) }; if (label.DefaultValue != null) children.Add(AddExpression(label.DefaultValue)); - return AddNode(expression.Type, null, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); } case ExpressionType.Switch: { @@ -245,7 +547,7 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(@switch.DefaultBody)); for (var i = 0; i < @switch.Cases.Count; ++i) children.Add(AddSwitchCase(@switch.Cases[i])); - return AddNode(expression.Type, new SwitchData(@switch.DefaultBody != null, @switch.Comparison), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, new SwitchData(@switch.DefaultBody != null, @switch.Comparison), expression.NodeType, children); } case ExpressionType.Try: { @@ -257,7 +559,7 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(@try.Finally)); for (var i = 0; i < @try.Handlers.Count; ++i) children.Add(AddCatchBlock(@try.Handlers[i])); - return AddNode(expression.Type, new TryData(@try.Finally != null, @try.Fault != null), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, new TryData(@try.Finally != null, @try.Fault != null), expression.NodeType, children); } case ExpressionType.MemberInit: { @@ -265,7 +567,7 @@ private int AddExpression(SysExpr expression) var children = new List(memberInit.Bindings.Count + 1) { AddExpression(memberInit.NewExpression) }; for (var i = 0; i < memberInit.Bindings.Count; ++i) children.Add(AddMemberBinding(memberInit.Bindings[i])); - return AddNode(expression.Type, null, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); } case ExpressionType.ListInit: { @@ -273,13 +575,13 @@ private int AddExpression(SysExpr expression) var children = new List(listInit.Initializers.Count + 1) { AddExpression(listInit.NewExpression) }; for (var i = 0; i < listInit.Initializers.Count; ++i) children.Add(AddElementInit(listInit.Initializers[i])); - return AddNode(expression.Type, null, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); } case ExpressionType.TypeIs: case ExpressionType.TypeEqual: { var typeBinary = (System.Linq.Expressions.TypeBinaryExpression)expression; - return AddNode(expression.Type, typeBinary.TypeOperand, expression.NodeType, + return _tree.AddRawExpressionNode(expression.Type, typeBinary.TypeOperand, expression.NodeType, new List(1) { AddExpression(typeBinary.Expression) }); } case ExpressionType.Dynamic: @@ -288,7 +590,7 @@ private int AddExpression(SysExpr expression) var children = new List(dynamic.Arguments.Count); for (var i = 0; i < dynamic.Arguments.Count; ++i) children.Add(AddExpression(dynamic.Arguments[i])); - return AddNode(expression.Type, new DynamicData(dynamic.DelegateType, dynamic.Binder), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, new DynamicData(dynamic.DelegateType, dynamic.Binder), expression.NodeType, children); } case ExpressionType.RuntimeVariables: { @@ -296,19 +598,19 @@ private int AddExpression(SysExpr expression) var children = new List(runtime.Variables.Count); for (var i = 0; i < runtime.Variables.Count; ++i) children.Add(AddExpression(runtime.Variables[i])); - return AddNode(expression.Type, null, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); } case ExpressionType.DebugInfo: { var debug = (System.Linq.Expressions.DebugInfoExpression)expression; - return AddNode(expression.Type, + return _tree.AddRawExpressionNode(expression.Type, new DebugInfoData(debug.Document.FileName, debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn), expression.NodeType); } default: if (expression is System.Linq.Expressions.UnaryExpression unary) { - return AddNode(expression.Type, unary.Method, expression.NodeType, + return _tree.AddRawExpressionNode(expression.Type, unary.Method, expression.NodeType, new List(1) { AddExpression(unary.Operand) }); } @@ -321,7 +623,7 @@ private int AddExpression(SysExpr expression) }; if (binary.Conversion != null) children.Add(AddExpression(binary.Conversion)); - return AddNode(expression.Type, new BinaryData(binary.Method, binary.IsLiftedToNull), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, new BinaryData(binary.Method, binary.IsLiftedToNull), expression.NodeType, children); } throw new NotSupportedException($"Flattening of `ExpressionType.{expression.NodeType}` is not supported yet."); @@ -331,10 +633,10 @@ private int AddExpression(SysExpr expression) private int AddConstant(System.Linq.Expressions.ConstantExpression constant) { if (ShouldInlineConstant(constant.Value, constant.Type)) - return AddNode(constant.Type, constant.Value, constant.NodeType); + return _tree.AddRawExpressionNode(constant.Type, constant.Value, constant.NodeType); var constantIndex = _tree.ClosureConstants.Add(constant.Value); - return AddNode(constant.Type, ClosureConstantMarker, constant.NodeType, ExprNodeKind.Expression, childIdx: constantIndex); + return _tree.AddRawExpressionNodeWithChildIndex(constant.Type, ClosureConstantMarker, constant.NodeType, constantIndex); } private int AddSwitchCase(SysSwitchCase switchCase) @@ -343,7 +645,7 @@ private int AddSwitchCase(SysSwitchCase switchCase) for (var i = 0; i < switchCase.TestValues.Count; ++i) children.Add(AddExpression(switchCase.TestValues[i])); children.Add(AddExpression(switchCase.Body)); - return AddNode(switchCase.Body.Type, null, ExpressionType.Extension, ExprNodeKind.SwitchCase, children); + return _tree.AddRawAuxNode(switchCase.Body.Type, null, ExprNodeKind.SwitchCase, children); } private int AddCatchBlock(SysCatchBlock catchBlock) @@ -354,19 +656,19 @@ private int AddCatchBlock(SysCatchBlock catchBlock) children.Add(AddExpression(catchBlock.Body)); if (catchBlock.Filter != null) children.Add(AddExpression(catchBlock.Filter)); - return AddNode(catchBlock.Test, new CatchData(catchBlock.Variable != null, catchBlock.Filter != null), - ExpressionType.Extension, ExprNodeKind.CatchBlock, children); + return _tree.AddRawAuxNode(catchBlock.Test, new CatchData(catchBlock.Variable != null, catchBlock.Filter != null), + ExprNodeKind.CatchBlock, children); } private int AddLabelTarget(SysLabelTarget target) => - AddNode(target.Type, new LabelTargetData(GetId(_labelIds, target), target.Name), ExpressionType.Extension, ExprNodeKind.LabelTarget); + _tree.AddRawAuxNode(target.Type, new LabelTargetData(GetId(_labelIds, target), target.Name), ExprNodeKind.LabelTarget); private int AddMemberBinding(SysMemberBinding binding) { switch (binding.BindingType) { case MemberBindingType.Assignment: - return AddNode(GetMemberType(binding.Member), binding.Member, ExpressionType.Extension, ExprNodeKind.MemberAssignment, + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, new List(1) { AddExpression(((System.Linq.Expressions.MemberAssignment)binding).Expression) }); case MemberBindingType.MemberBinding: { @@ -374,7 +676,7 @@ private int AddMemberBinding(SysMemberBinding binding) var children = new List(memberBinding.Bindings.Count); for (var i = 0; i < memberBinding.Bindings.Count; ++i) children.Add(AddMemberBinding(memberBinding.Bindings[i])); - return AddNode(GetMemberType(binding.Member), binding.Member, ExpressionType.Extension, ExprNodeKind.MemberMemberBinding, children); + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberMemberBinding, children); } case MemberBindingType.ListBinding: { @@ -382,7 +684,7 @@ private int AddMemberBinding(SysMemberBinding binding) var children = new List(listBinding.Initializers.Count); for (var i = 0; i < listBinding.Initializers.Count; ++i) children.Add(AddElementInit(listBinding.Initializers[i])); - return AddNode(GetMemberType(binding.Member), binding.Member, ExpressionType.Extension, ExprNodeKind.MemberListBinding, children); + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberListBinding, children); } default: throw new NotSupportedException($"Flattening of member binding `{binding.BindingType}` is not supported yet."); @@ -394,38 +696,7 @@ private int AddElementInit(SysElementInit init) var children = new List(init.Arguments.Count); for (var i = 0; i < init.Arguments.Count; ++i) children.Add(AddExpression(init.Arguments[i])); - return AddNode(init.AddMethod.DeclaringType, init.AddMethod, ExpressionType.Extension, ExprNodeKind.ElementInit, children); - } - - private int AddNode(Type type, object obj, ExpressionType nodeType, List children = null) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, children); - - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, List children = null, int childIdx = 0) - { - var nodeIndex = _tree.Nodes.Add(new ExprNode(type, obj, nodeType, kind, childIdx, 0, 0)); - if (children != null && children.Count != 0) - { - for (var i = 0; i < children.Count - 1; ++i) - { - ref var child = ref _tree.Nodes[children[i]]; - child.SetNextIdx(children[i + 1]); - } - - ref var node = ref _tree.Nodes[nodeIndex]; - node.SetChildInfo(children[0], children.Count); - } - return nodeIndex; - } - - private static bool ShouldInlineConstant(object value, Type type) - { - if (value == null || value is string || value is Type) - return true; - - if (type.IsEnum) - return true; - - return Type.GetTypeCode(type) != TypeCode.Object; + return _tree.AddRawAuxNode(init.AddMethod.DeclaringType, init.AddMethod, ExprNodeKind.ElementInit, children); } private static int GetId(Dictionary ids, object item) @@ -446,6 +717,115 @@ private static int GetId(Dictionary ids, object item) }; } + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, IEnumerable children, int childIdx) + { + var nodeIndex = Nodes.Add(new ExprNode(type, obj, nodeType, kind, childIdx, 0, 0)); + if (children == null) + return nodeIndex; + + using var enumerator = children.GetEnumerator(); + if (!enumerator.MoveNext()) + return nodeIndex; + + var firstChildIndex = enumerator.Current; + var previousChildIndex = firstChildIndex; + var childCount = 1; + while (enumerator.MoveNext()) + { + ref var child = ref Nodes[previousChildIndex]; + child.SetNextIdx(enumerator.Current); + previousChildIndex = enumerator.Current; + ++childCount; + } + + ref var node = ref Nodes[nodeIndex]; + node.SetChildInfo(firstChildIndex, childCount); + return nodeIndex; + } + + private static bool ShouldInlineConstant(object value, Type type) + { + if (value == null || value is string || value is Type) + return true; + + if (type.IsEnum) + return true; + + return Type.GetTypeCode(type) != TypeCode.Object; + } + + private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch + { + System.Reflection.FieldInfo field => field.FieldType, + System.Reflection.PropertyInfo property => property.PropertyType, + _ => typeof(object) + }; + + private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType, System.Reflection.MethodInfo method) => + nodeType switch + { + ExpressionType.IsFalse or ExpressionType.IsTrue or ExpressionType.TypeIs or ExpressionType.TypeEqual => typeof(bool), + _ => method?.ReturnType ?? operandType + }; + + private static Type GetBinaryResultType(ExpressionType nodeType, Type leftType, Type rightType, System.Reflection.MethodInfo method) + { + if (method != null) + return method.ReturnType; + + return nodeType switch + { + ExpressionType.Equal or ExpressionType.NotEqual or ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual + or ExpressionType.LessThan or ExpressionType.LessThanOrEqual or ExpressionType.AndAlso or ExpressionType.OrElse => typeof(bool), + ExpressionType.ArrayIndex => leftType.GetElementType(), + ExpressionType.Assign => leftType, + _ => leftType + }; + } + + private static Type GetArrayElementType(Type arrayType, int rank) + { + var elementType = arrayType; + for (var i = 0; i < rank; ++i) + elementType = elementType.GetElementType(); + return elementType ?? typeof(object); + } + + private int CloneChild(int index) + { + ref var node = ref Nodes[index]; + return node.ChildCount == 0 + ? AddNode(node.Type, node.Obj, node.NodeType, node.Kind, null, node.ChildIdx) + : index; + } + + private int[] CloneChildrenToArray(IEnumerable children) + { + if (children == null) + return Array.Empty(); + + var cloned = new List(); + foreach (var child in children) + cloned.Add(CloneChild(child)); + return cloned.ToArray(); + } + + private static IEnumerable Single(int item) + { + yield return item; + } + + private static int[] Prepend(int first, int[] rest) + { + if (rest == null || rest.Length == 0) + return new[] { first }; + + var items = new int[rest.Length + 1]; + items[0] = first; + Array.Copy(rest, 0, items, 1, rest.Length); + return items; + } + private readonly struct Reader { private readonly ExprTree _tree; diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 12c50904..2ac22689 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -31,7 +31,9 @@ public int Run() Can_roundtrip_light_expression_through_flat_expression(); Flat_expression_preserves_parameter_and_label_identity_and_collects_closure_constants(); Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expression_and_flat_expression(); - return 14; + Can_build_flat_expression_directly_with_light_expression_like_api(); + Can_build_flat_expression_control_flow_directly(); + return 16; } @@ -426,6 +428,58 @@ public void Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expres Asserts.AreEqual(ExpressionType.Dynamic, dynamicLight.ToFlatExpression().ToExpression().NodeType); } + public void Can_build_flat_expression_directly_with_light_expression_like_api() + { + var fe = default(ExprTree); + var state = fe.ParameterOf("state"); + var body = fe.MemberInit( + fe.New(_ctorOfA, + fe.New(_ctorOfB), + fe.Convert( + fe.ArrayIndex(state, fe.ConstantInt(11)), + typeof(string)), + fe.NewArrayInit(typeof(ID), + fe.New(_ctorOfD1), + fe.New(_ctorOfD2))), + fe.Bind(_propAProp, + fe.New(_ctorOfP, + fe.New(_ctorOfB))), + fe.Bind(_fieldABop, + fe.New(_ctorOfB))); + fe.RootIndex = fe.Lambda>(body, state); + + var lambda = (LambdaExpression)fe.ToLightExpression(); + var func = lambda.CompileFast>(true); + var runtimeState = new object[12]; + runtimeState[11] = "direct"; + + var a = (A)func(runtimeState); + + Asserts.AreEqual("direct", a.Sop); + Asserts.IsInstanceOf

(a.Prop); + Asserts.AreEqual(2, a.Dop.Count()); + } + + public void Can_build_flat_expression_control_flow_directly() + { + var fe = default(ExprTree); + var p = fe.Parameter(typeof(int), "p"); + var target = fe.Label(typeof(int), "done"); + fe.RootIndex = fe.Lambda>( + fe.Block( + fe.Goto(target, p, typeof(int)), + fe.Label(target, fe.ConstantInt(0))), + p); + + var sysLambda = (System.Linq.Expressions.LambdaExpression)fe.ToExpression(); + var block = (System.Linq.Expressions.BlockExpression)sysLambda.Body; + var @goto = (System.Linq.Expressions.GotoExpression)block.Expressions[0]; + var label = (System.Linq.Expressions.LabelExpression)block.Expressions[1]; + + Asserts.AreSame(sysLambda.Parameters[0], @goto.Value); + Asserts.AreSame(@goto.Target, label.Target); + } + public class A { public P Prop { get; set; } From 150bdd66e442e47dfe01b0dd6b8b4dc7bdad27e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 06:56:04 +0000 Subject: [PATCH 05/15] fix: finalize flat expression direct builder review notes Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/15dd11ed-8bf0-4fd4-b514-3d2ff4e4e346 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 14 +++++++++++--- .../LightExpressionTests.cs | 6 +++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index dc2687e1..0831e7cd 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -783,10 +783,10 @@ ExpressionType.Equal or ExpressionType.NotEqual or ExpressionType.GreaterThan or }; } - private static Type GetArrayElementType(Type arrayType, int rank) + private static Type GetArrayElementType(Type arrayType, int depth) { var elementType = arrayType; - for (var i = 0; i < rank; ++i) + for (var i = 0; i < depth; ++i) elementType = elementType.GetElementType(); return elementType ?? typeof(object); } @@ -794,7 +794,7 @@ private static Type GetArrayElementType(Type arrayType, int rank) private int CloneChild(int index) { ref var node = ref Nodes[index]; - return node.ChildCount == 0 + return ShouldCloneWhenLinking(node) ? AddNode(node.Type, node.Obj, node.NodeType, node.Kind, null, node.ChildIdx) : index; } @@ -810,6 +810,14 @@ private int[] CloneChildrenToArray(IEnumerable children) return cloned.ToArray(); } + // Any leaf reused in more than one parent would have its intrusive sibling link (`NextIdx`) overwritten. + // Clone the leaf before linking so the direct builder API may safely reuse returned indexes for + // parameters, labels, constants, default values and parameterless `new` expressions. + private static bool ShouldCloneWhenLinking(in ExprNode node) => + node.Kind == ExprNodeKind.LabelTarget || + node.NodeType == ExpressionType.Parameter || + node.ChildCount == 0; + private static IEnumerable Single(int item) { yield return item; diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 2ac22689..37bf7ea6 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -473,11 +473,11 @@ public void Can_build_flat_expression_control_flow_directly() var sysLambda = (System.Linq.Expressions.LambdaExpression)fe.ToExpression(); var block = (System.Linq.Expressions.BlockExpression)sysLambda.Body; - var @goto = (System.Linq.Expressions.GotoExpression)block.Expressions[0]; + var gotoExpr = (System.Linq.Expressions.GotoExpression)block.Expressions[0]; var label = (System.Linq.Expressions.LabelExpression)block.Expressions[1]; - Asserts.AreSame(sysLambda.Parameters[0], @goto.Value); - Asserts.AreSame(@goto.Target, label.Target); + Asserts.AreSame(sysLambda.Parameters[0], gotoExpr.Value); + Asserts.AreSame(gotoExpr.Target, label.Target); } public class A From cc19f39119698c529b05b52e9a48ebe6734272b3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 07:54:14 +0000 Subject: [PATCH 06/15] fix: add xml docs for flat expression api Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/0ccd5cdb-3248-46a4-b6df-54762eed9357 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 99 +++++++++++++++++++ ...nCompiler.LightExpression.UnitTests.csproj | 14 ++- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 0831e7cd..abdd6452 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -16,18 +16,28 @@ namespace FastExpressionCompiler.FlatExpression; using SysParameterExpression = System.Linq.Expressions.ParameterExpression; using SysSwitchCase = System.Linq.Expressions.SwitchCase; +///

Classifies the stored flat node payload. public enum ExprNodeKind : byte { + /// Represents a regular expression node. Expression, + /// Represents a switch case payload. SwitchCase, + /// Represents a catch block payload. CatchBlock, + /// Represents a label target payload. LabelTarget, + /// Represents a member-assignment binding payload. MemberAssignment, + /// Represents a nested member-binding payload. MemberMemberBinding, + /// Represents a list-binding payload. MemberListBinding, + /// Represents an element initializer payload. ElementInit, } +/// Stores one flat expression node plus its intrusive child-link metadata. public struct ExprNode { private const int NodeTypeShift = 56; @@ -36,14 +46,26 @@ public struct ExprNode private const int CountShift = 16; private const ulong IndexMask = 0xFFFF; + /// Gets or sets the runtime type of the represented node. public Type Type; + + /// Gets or sets the runtime payload associated with the node. public object Obj; private ulong _data; + /// Gets the expression kind encoded for this node. public ExpressionType NodeType => (ExpressionType)((_data >> NodeTypeShift) & 0xFF); + + /// Gets the payload classification for this node. public ExprNodeKind Kind => (ExprNodeKind)((_data >> KindShift) & 0xFF); + + /// Gets the next sibling node index in the intrusive child chain. public int NextIdx => (int)((_data >> NextShift) & IndexMask); + + /// Gets the number of direct children linked from this node. public int ChildCount => (int)((_data >> CountShift) & IndexMask); + + /// Gets the first child index or an auxiliary payload index. public int ChildIdx => (int)(_data & IndexMask); internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, int childIdx = 0, int childCount = 0, int nextIdx = 0) @@ -66,29 +88,41 @@ internal void SetChildInfo(int childIdx, int childCount) => | (ushort)childIdx; } +/// Stores an expression tree as a flat node array plus out-of-line closure constants. public struct ExprTree { private static readonly object ClosureConstantMarker = new(); + /// Gets or sets the root node index. public int RootIndex; + + /// Gets or sets the flat node storage. public SmallList, NoArrayPool> Nodes; + + /// Gets or sets closure constants that are referenced from constant nodes. public SmallList, NoArrayPool> ClosureConstants; + /// Adds a parameter node and returns its index. public int Parameter(Type type, string name = null) { var id = Nodes.Count + 1; return AddRawExpressionNode(type, new ParameterData(id, name, type.IsByRef), ExpressionType.Parameter); } + /// Adds a typed parameter node and returns its index. public int ParameterOf(string name = null) => Parameter(typeof(T), name); + /// Adds a variable node and returns its index. public int Variable(Type type, string name = null) => Parameter(type, name); + /// Adds a default-value node and returns its index. public int Default(Type type) => AddRawExpressionNode(type, null, ExpressionType.Default); + /// Adds a constant node using the runtime type of the supplied value. public int Constant(object value) => Constant(value, value?.GetType() ?? typeof(object)); + /// Adds a constant node with an explicit constant type. public int Constant(object value, Type type) { if (ShouldInlineConstant(value, type)) @@ -98,12 +132,16 @@ public int Constant(object value, Type type) return AddRawExpressionNodeWithChildIndex(type, ClosureConstantMarker, ExpressionType.Constant, constantIndex); } + /// Adds a null constant node. public int ConstantNull(Type type = null) => AddRawExpressionNode(type ?? typeof(object), null, ExpressionType.Constant); + /// Adds an constant node. public int ConstantInt(int value) => AddRawExpressionNode(typeof(int), value, ExpressionType.Constant); + /// Adds a typed constant node. public int ConstantOf(T value) => Constant(value, typeof(T)); + /// Adds a parameterless new node for the specified type. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public int New(Type type) { @@ -117,67 +155,89 @@ public int New(Type type) throw new ArgumentException($"The type {type} is missing the default constructor"); } + /// Adds a constructor call node. public int New(System.Reflection.ConstructorInfo constructor, params int[] arguments) => AddFactoryExpressionNode(constructor.DeclaringType, constructor, ExpressionType.New, arguments); + /// Adds an array initialization node. public int NewArrayInit(Type elementType, params int[] expressions) => AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayInit, expressions); + /// Adds an array-bounds node. public int NewArrayBounds(Type elementType, params int[] bounds) => AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayBounds, bounds); + /// Adds an invocation node. public int Invoke(int expression, params int[] arguments) => AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, Prepend(expression, arguments)); + /// Adds a static-call node. public int Call(System.Reflection.MethodInfo method, params int[] arguments) => AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, arguments); + /// Adds an instance-call node. public int Call(int instance, System.Reflection.MethodInfo method, params int[] arguments) => AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, Prepend(instance, arguments)); + /// Adds a field or property access node. public int MakeMemberAccess(int? instance, System.Reflection.MemberInfo member) => AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, instance.HasValue ? Single(instance.Value) : null); + /// Adds a field-access node. public int Field(int instance, System.Reflection.FieldInfo field) => MakeMemberAccess(instance, field); + /// Adds a property-access node. public int Property(int instance, System.Reflection.PropertyInfo property) => MakeMemberAccess(instance, property); + /// Adds a static property-access node. public int Property(System.Reflection.PropertyInfo property) => MakeMemberAccess(null, property); + /// Adds an indexed property-access node. public int Property(int instance, System.Reflection.PropertyInfo property, params int[] arguments) => arguments == null || arguments.Length == 0 ? Property(instance, property) : AddFactoryExpressionNode(property.PropertyType, property, ExpressionType.Index, Prepend(instance, arguments)); + /// Adds a one-dimensional array index node. public int ArrayIndex(int array, int index) => MakeBinary(ExpressionType.ArrayIndex, array, index); + /// Adds an array access node. public int ArrayAccess(int array, params int[] indexes) => indexes != null && indexes.Length == 1 ? ArrayIndex(array, indexes[0]) : AddFactoryExpressionNode(GetArrayElementType(Nodes[array].Type, indexes?.Length ?? 0), null, ExpressionType.Index, Prepend(array, indexes)); + /// Adds a conversion node. public int Convert(int operand, Type type, System.Reflection.MethodInfo method = null) => AddFactoryExpressionNode(type, method, ExpressionType.Convert, operand); + /// Adds a type-as node. public int TypeAs(int operand, Type type) => AddFactoryExpressionNode(type, null, ExpressionType.TypeAs, operand); + /// Adds a numeric negation node. public int Negate(int operand, System.Reflection.MethodInfo method = null) => MakeUnary(ExpressionType.Negate, operand, method: method); + /// Adds a logical or bitwise not node. public int Not(int operand, System.Reflection.MethodInfo method = null) => MakeUnary(ExpressionType.Not, operand, method: method); + /// Adds a unary node of the specified kind. public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, System.Reflection.MethodInfo method = null) => AddFactoryExpressionNode(type ?? GetUnaryResultType(nodeType, Nodes[operand].Type, method), method, nodeType, operand); + /// Adds an assignment node. public int Assign(int left, int right) => MakeBinary(ExpressionType.Assign, left, right); + /// Adds an addition node. public int Add(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Add, left, right, method: method); + /// Adds an equality node. public int Equal(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Equal, left, right, method: method); + /// Adds a binary node of the specified kind. public int MakeBinary(ExpressionType nodeType, int left, int right, bool isLiftedToNull = false, System.Reflection.MethodInfo method = null, int? conversion = null, Type type = null) { @@ -186,12 +246,15 @@ public int MakeBinary(ExpressionType nodeType, int left, int right, bool isLifte new BinaryData(method, isLiftedToNull), nodeType, children); } + /// Adds a conditional node. public int Condition(int test, int ifTrue, int ifFalse, Type type = null) => AddFactoryExpressionNode(type ?? Nodes[ifTrue].Type, null, ExpressionType.Conditional, new[] { test, ifTrue, ifFalse }); + /// Adds a block node without explicit variables. public int Block(params int[] expressions) => Block(null, null, expressions); + /// Adds a block node with optional explicit result type and variables. public int Block(Type type, IEnumerable variables, params int[] expressions) { if (expressions == null || expressions.Length == 0) @@ -212,40 +275,51 @@ public int Block(Type type, IEnumerable variables, params int[] expressions new BlockData(variableCount), ExpressionType.Block, children); } + /// Adds a typed lambda node. public int Lambda(int body, params int[] parameters) where TDelegate : Delegate => Lambda(typeof(TDelegate), body, parameters); + /// Adds a lambda node. public int Lambda(Type delegateType, int body, params int[] parameters) => AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, Prepend(body, parameters)); + /// Adds a member-assignment binding node. public int Bind(System.Reflection.MemberInfo member, int expression) => AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberAssignment, expression); + /// Adds a nested member-binding node. public int MemberBind(System.Reflection.MemberInfo member, params int[] bindings) => AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberMemberBinding, bindings); + /// Adds an element-initializer node. public int ElementInit(System.Reflection.MethodInfo addMethod, params int[] arguments) => AddFactoryAuxNode(addMethod.DeclaringType, addMethod, ExprNodeKind.ElementInit, arguments); + /// Adds a list-binding node. public int ListBind(System.Reflection.MemberInfo member, params int[] initializers) => AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberListBinding, initializers); + /// Adds a member-init node. public int MemberInit(int @new, params int[] bindings) => AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, Prepend(@new, bindings)); + /// Adds a list-init node. public int ListInit(int @new, params int[] initializers) => AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, Prepend(@new, initializers)); + /// Adds a label-target node. public int Label(Type type = null, string name = null) { var id = Nodes.Count + 1; return AddRawAuxNode(type ?? typeof(void), new LabelTargetData(id, name), ExprNodeKind.LabelTarget); } + /// Adds a label-expression node. public int Label(int target, int? defaultValue = null) => AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, defaultValue.HasValue ? new[] { target, defaultValue.Value } : new[] { target }); + /// Adds a goto-family node. public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type type = null) { var resultType = type ?? (value.HasValue ? Nodes[value.Value].Type : typeof(void)); @@ -253,10 +327,13 @@ public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type value.HasValue ? new[] { target, value.Value } : new[] { target }); } + /// Adds a goto node. public int Goto(int target, int? value = null, Type type = null) => MakeGoto(GotoExpressionKind.Goto, target, value, type); + /// Adds a return node. public int Return(int target, int value) => MakeGoto(GotoExpressionKind.Return, target, value, Nodes[value].Type); + /// Adds a loop node. public int Loop(int body, int? @break = null, int? @continue = null) { var children = new List { body }; @@ -267,6 +344,7 @@ public int Loop(int body, int? @break = null, int? @continue = null) return AddFactoryExpressionNode(typeof(void), new LoopData(@break.HasValue, @continue.HasValue), ExpressionType.Loop, children); } + /// Adds a switch-case node. public int SwitchCase(int body, params int[] testValues) { var children = new List(testValues?.Length + 1 ?? 1); @@ -276,9 +354,11 @@ public int SwitchCase(int body, params int[] testValues) return AddFactoryAuxNode(Nodes[body].Type, null, ExprNodeKind.SwitchCase, children); } + /// Adds a switch node without an explicit default case or comparer. public int Switch(int switchValue, params int[] cases) => Switch(Nodes[switchValue].Type, switchValue, null, null, cases); + /// Adds a switch node. public int Switch(Type type, int switchValue, int? defaultBody, System.Reflection.MethodInfo comparison, params int[] cases) { var children = new List(cases?.Length + 2 ?? 1) { switchValue }; @@ -289,12 +369,15 @@ public int Switch(Type type, int switchValue, int? defaultBody, System.Reflectio return AddFactoryExpressionNode(type, new SwitchData(defaultBody.HasValue, comparison), ExpressionType.Switch, children); } + /// Adds a catch block with an exception variable. public int Catch(int variable, int body) => AddFactoryAuxNode(Nodes[variable].Type, new CatchData(true, false), ExprNodeKind.CatchBlock, new[] { variable, body }); + /// Adds a catch block without an exception variable. public int Catch(Type test, int body) => AddFactoryAuxNode(test, new CatchData(false, false), ExprNodeKind.CatchBlock, new[] { body }); + /// Adds a catch block with optional exception variable and filter. public int MakeCatchBlock(Type test, int? variable, int body, int? filter) { var children = new List(3); @@ -306,15 +389,19 @@ public int MakeCatchBlock(Type test, int? variable, int body, int? filter) return AddFactoryAuxNode(test, new CatchData(variable.HasValue, filter.HasValue), ExprNodeKind.CatchBlock, children); } + /// Adds a try/catch node. public int TryCatch(int body, params int[] handlers) => AddFactoryExpressionNode(Nodes[body].Type, new TryData(false, false), ExpressionType.Try, Prepend(body, handlers)); + /// Adds a try/finally node. public int TryFinally(int body, int @finally) => AddFactoryExpressionNode(Nodes[body].Type, new TryData(true, false), ExpressionType.Try, new[] { body, @finally }); + /// Adds a try/fault node. public int TryFault(int body, int fault) => AddFactoryExpressionNode(Nodes[body].Type, new TryData(false, true), ExpressionType.Try, new[] { body, fault }); + /// Adds a try node with optional finally block and catch handlers. public int TryCatchFinally(int body, int? @finally, params int[] handlers) { var children = new List { body }; @@ -325,21 +412,27 @@ public int TryCatchFinally(int body, int? @finally, params int[] handlers) return AddFactoryExpressionNode(Nodes[body].Type, new TryData(@finally.HasValue, false), ExpressionType.Try, children); } + /// Adds a type-test node. public int TypeIs(int expression, Type type) => AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeIs, expression); + /// Adds a type-equality test node. public int TypeEqual(int expression, Type type) => AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeEqual, expression); + /// Adds a dynamic-expression node. public int Dynamic(Type delegateType, CallSiteBinder binder, params int[] arguments) => AddFactoryExpressionNode(typeof(object), new DynamicData(delegateType, binder), ExpressionType.Dynamic, arguments); + /// Adds a runtime-variables node. public int RuntimeVariables(params int[] variables) => AddFactoryExpressionNode(typeof(IRuntimeVariables), null, ExpressionType.RuntimeVariables, variables); + /// Adds a debug-info node. public int DebugInfo(string fileName, int startLine, int startColumn, int endLine, int endColumn) => AddRawExpressionNode(typeof(void), new DebugInfoData(fileName, startLine, startColumn, endLine, endColumn), ExpressionType.DebugInfo); + /// Flattens a System.Linq expression tree. public static ExprTree FromExpression(SysExpr expression) { if (expression == null) @@ -349,6 +442,7 @@ public static ExprTree FromExpression(SysExpr expression) return builder.Build(expression); } + /// Flattens a LightExpression tree. public static ExprTree FromLightExpression(LightExpression expression) { if (expression == null) @@ -357,6 +451,7 @@ public static ExprTree FromLightExpression(LightExpression expression) return FromExpression(expression.ToExpression()); } + /// Reconstructs the flat tree as a System.Linq expression tree. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] @@ -368,6 +463,7 @@ public SysExpr ToExpression() return new Reader(this).ReadExpression(RootIndex); } + /// Reconstructs the flat tree as a LightExpression tree. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); @@ -1289,9 +1385,12 @@ private sealed class ReferenceEqComparer : IEqualityComparer } } +/// Provides conversions from System and LightExpression trees to . public static class FlatExpressionExtensions { + /// Flattens a System.Linq expression tree. public static ExprTree ToFlatExpression(this SysExpr expression) => ExprTree.FromExpression(expression); + /// Flattens a LightExpression tree. public static ExprTree ToFlatExpression(this LightExpression expression) => ExprTree.FromLightExpression(expression); } diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj index 057d6c12..fe1279f1 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj @@ -11,8 +11,12 @@ - - - - - + + + + + + + + + From 7958d825a208759e350c48e55e3e3253097ff79b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 08:52:52 +0000 Subject: [PATCH 07/15] perf: reduce flat expression builder allocations Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/a101d2c7-fa90-45b2-b621-f056a99693eb Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 193 +++++++++++++----- 1 file changed, 139 insertions(+), 54 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index abdd6452..e757232a 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -7,6 +7,7 @@ namespace FastExpressionCompiler.FlatExpression; using System.Linq.Expressions; using System.Runtime.CompilerServices; using FastExpressionCompiler.LightExpression.ImTools; +using ChildList = FastExpressionCompiler.LightExpression.ImTools.SmallList, FastExpressionCompiler.LightExpression.ImTools.NoArrayPool>; using LightExpression = FastExpressionCompiler.LightExpression.Expression; using SysCatchBlock = System.Linq.Expressions.CatchBlock; using SysElementInit = System.Linq.Expressions.ElementInit; @@ -260,7 +261,7 @@ public int Block(Type type, IEnumerable variables, params int[] expressions if (expressions == null || expressions.Length == 0) throw new ArgumentException("Block should contain at least one expression.", nameof(expressions)); - var children = new List(); + ChildList children = default; var variableCount = 0; if (variables != null) { @@ -270,7 +271,8 @@ public int Block(Type type, IEnumerable variables, params int[] expressions ++variableCount; } } - children.AddRange(expressions); + for (var i = 0; i < expressions.Length; ++i) + children.Add(expressions[i]); return AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, new BlockData(variableCount), ExpressionType.Block, children); } @@ -336,7 +338,8 @@ public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type /// Adds a loop node. public int Loop(int body, int? @break = null, int? @continue = null) { - var children = new List { body }; + ChildList children = default; + children.Add(body); if (@break.HasValue) children.Add(@break.Value); if (@continue.HasValue) @@ -347,9 +350,10 @@ public int Loop(int body, int? @break = null, int? @continue = null) /// Adds a switch-case node. public int SwitchCase(int body, params int[] testValues) { - var children = new List(testValues?.Length + 1 ?? 1); + ChildList children = default; if (testValues != null && testValues.Length != 0) - children.AddRange(testValues); + for (var i = 0; i < testValues.Length; ++i) + children.Add(testValues[i]); children.Add(body); return AddFactoryAuxNode(Nodes[body].Type, null, ExprNodeKind.SwitchCase, children); } @@ -361,11 +365,13 @@ public int Switch(int switchValue, params int[] cases) => /// Adds a switch node. public int Switch(Type type, int switchValue, int? defaultBody, System.Reflection.MethodInfo comparison, params int[] cases) { - var children = new List(cases?.Length + 2 ?? 1) { switchValue }; + ChildList children = default; + children.Add(switchValue); if (defaultBody.HasValue) children.Add(defaultBody.Value); if (cases != null && cases.Length != 0) - children.AddRange(cases); + for (var i = 0; i < cases.Length; ++i) + children.Add(cases[i]); return AddFactoryExpressionNode(type, new SwitchData(defaultBody.HasValue, comparison), ExpressionType.Switch, children); } @@ -380,7 +386,7 @@ public int Catch(Type test, int body) => /// Adds a catch block with optional exception variable and filter. public int MakeCatchBlock(Type test, int? variable, int body, int? filter) { - var children = new List(3); + ChildList children = default; if (variable.HasValue) children.Add(variable.Value); children.Add(body); @@ -404,11 +410,13 @@ public int TryFault(int body, int fault) => /// Adds a try node with optional finally block and catch handlers. public int TryCatchFinally(int body, int? @finally, params int[] handlers) { - var children = new List { body }; + ChildList children = default; + children.Add(body); if (@finally.HasValue) children.Add(@finally.Value); if (handlers != null && handlers.Length != 0) - children.AddRange(handlers); + for (var i = 0; i < handlers.Length; ++i) + children.Add(handlers[i]); return AddFactoryExpressionNode(Nodes[body].Type, new TryData(@finally.HasValue, false), ExpressionType.Try, children); } @@ -473,6 +481,12 @@ private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeT private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, params int[] children) => AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children)); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) + { + var cloned = CloneChildren(children); + return AddRawExpressionNode(type, obj, nodeType, in cloned); + } + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children) => AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children)); @@ -482,6 +496,9 @@ private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, params int[] children) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0); + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, in children, 0); + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0); @@ -494,12 +511,21 @@ private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int chil private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, params int[] children) => AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children)); + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) + { + var cloned = CloneChildren(children); + return AddRawAuxNode(type, obj, kind, in cloned); + } + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children) => AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children)); private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, params int[] children) => AddNode(type, obj, ExpressionType.Extension, kind, children, 0); + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => + AddNode(type, obj, ExpressionType.Extension, kind, in children, 0); + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children) => AddNode(type, obj, ExpressionType.Extension, kind, children, 0); @@ -531,7 +557,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.Lambda: { var lambda = (System.Linq.Expressions.LambdaExpression)expression; - var children = new List(lambda.Parameters.Count + 1) { AddExpression(lambda.Body) }; + ChildList children = default; + children.Add(AddExpression(lambda.Body)); for (var i = 0; i < lambda.Parameters.Count; ++i) children.Add(AddExpression(lambda.Parameters[i])); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); @@ -539,7 +566,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.Block: { var block = (System.Linq.Expressions.BlockExpression)expression; - var children = new List(block.Variables.Count + block.Expressions.Count); + ChildList children = default; for (var i = 0; i < block.Variables.Count; ++i) children.Add(AddExpression(block.Variables[i])); for (var i = 0; i < block.Expressions.Count; ++i) @@ -549,13 +576,16 @@ private int AddExpression(SysExpr expression) case ExpressionType.MemberAccess: { var member = (System.Linq.Expressions.MemberExpression)expression; + ChildList children = default; + if (member.Expression != null) + children.Add(AddExpression(member.Expression)); return _tree.AddRawExpressionNode(expression.Type, member.Member, expression.NodeType, - member.Expression != null ? new List(1) { AddExpression(member.Expression) } : null); + children); } case ExpressionType.Call: { var call = (System.Linq.Expressions.MethodCallExpression)expression; - var children = new List(call.Arguments.Count + (call.Object != null ? 1 : 0)); + ChildList children = default; if (call.Object != null) children.Add(AddExpression(call.Object)); for (var i = 0; i < call.Arguments.Count; ++i) @@ -565,7 +595,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.New: { var @new = (System.Linq.Expressions.NewExpression)expression; - var children = new List(@new.Arguments.Count); + ChildList children = default; for (var i = 0; i < @new.Arguments.Count; ++i) children.Add(AddExpression(@new.Arguments[i])); return _tree.AddRawExpressionNode(expression.Type, @new.Constructor, expression.NodeType, children); @@ -574,7 +604,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.NewArrayBounds: { var array = (System.Linq.Expressions.NewArrayExpression)expression; - var children = new List(array.Expressions.Count); + ChildList children = default; for (var i = 0; i < array.Expressions.Count; ++i) children.Add(AddExpression(array.Expressions[i])); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); @@ -582,7 +612,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.Invoke: { var invoke = (System.Linq.Expressions.InvocationExpression)expression; - var children = new List(invoke.Arguments.Count + 1) { AddExpression(invoke.Expression) }; + ChildList children = default; + children.Add(AddExpression(invoke.Expression)); for (var i = 0; i < invoke.Arguments.Count; ++i) children.Add(AddExpression(invoke.Arguments[i])); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); @@ -590,7 +621,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.Index: { var index = (System.Linq.Expressions.IndexExpression)expression; - var children = new List(index.Arguments.Count + (index.Object != null ? 1 : 0)); + ChildList children = default; if (index.Object != null) children.Add(AddExpression(index.Object)); for (var i = 0; i < index.Arguments.Count; ++i) @@ -600,19 +631,19 @@ private int AddExpression(SysExpr expression) case ExpressionType.Conditional: { var conditional = (System.Linq.Expressions.ConditionalExpression)expression; + ChildList children = default; + children.Add(AddExpression(conditional.Test)); + children.Add(AddExpression(conditional.IfTrue)); + children.Add(AddExpression(conditional.IfFalse)); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, - new List(3) - { - AddExpression(conditional.Test), - AddExpression(conditional.IfTrue), - AddExpression(conditional.IfFalse), - }); + children); } case ExpressionType.Loop: { var loop = (System.Linq.Expressions.LoopExpression)expression; var data = new LoopData(loop.BreakLabel != null, loop.ContinueLabel != null); - var children = new List(3) { AddExpression(loop.Body) }; + ChildList children = default; + children.Add(AddExpression(loop.Body)); if (loop.BreakLabel != null) children.Add(AddLabelTarget(loop.BreakLabel)); if (loop.ContinueLabel != null) @@ -622,7 +653,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.Goto: { var @goto = (System.Linq.Expressions.GotoExpression)expression; - var children = new List(2) { AddLabelTarget(@goto.Target) }; + ChildList children = default; + children.Add(AddLabelTarget(@goto.Target)); if (@goto.Value != null) children.Add(AddExpression(@goto.Value)); return _tree.AddRawExpressionNode(expression.Type, @goto.Kind, expression.NodeType, children); @@ -630,7 +662,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.Label: { var label = (System.Linq.Expressions.LabelExpression)expression; - var children = new List(2) { AddLabelTarget(label.Target) }; + ChildList children = default; + children.Add(AddLabelTarget(label.Target)); if (label.DefaultValue != null) children.Add(AddExpression(label.DefaultValue)); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); @@ -638,7 +671,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.Switch: { var @switch = (System.Linq.Expressions.SwitchExpression)expression; - var children = new List(@switch.Cases.Count + 2) { AddExpression(@switch.SwitchValue) }; + ChildList children = default; + children.Add(AddExpression(@switch.SwitchValue)); if (@switch.DefaultBody != null) children.Add(AddExpression(@switch.DefaultBody)); for (var i = 0; i < @switch.Cases.Count; ++i) @@ -648,7 +682,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.Try: { var @try = (System.Linq.Expressions.TryExpression)expression; - var children = new List(@try.Handlers.Count + 2) { AddExpression(@try.Body) }; + ChildList children = default; + children.Add(AddExpression(@try.Body)); if (@try.Fault != null) children.Add(AddExpression(@try.Fault)); else if (@try.Finally != null) @@ -660,7 +695,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.MemberInit: { var memberInit = (System.Linq.Expressions.MemberInitExpression)expression; - var children = new List(memberInit.Bindings.Count + 1) { AddExpression(memberInit.NewExpression) }; + ChildList children = default; + children.Add(AddExpression(memberInit.NewExpression)); for (var i = 0; i < memberInit.Bindings.Count; ++i) children.Add(AddMemberBinding(memberInit.Bindings[i])); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); @@ -668,7 +704,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.ListInit: { var listInit = (System.Linq.Expressions.ListInitExpression)expression; - var children = new List(listInit.Initializers.Count + 1) { AddExpression(listInit.NewExpression) }; + ChildList children = default; + children.Add(AddExpression(listInit.NewExpression)); for (var i = 0; i < listInit.Initializers.Count; ++i) children.Add(AddElementInit(listInit.Initializers[i])); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); @@ -677,13 +714,15 @@ private int AddExpression(SysExpr expression) case ExpressionType.TypeEqual: { var typeBinary = (System.Linq.Expressions.TypeBinaryExpression)expression; + ChildList children = default; + children.Add(AddExpression(typeBinary.Expression)); return _tree.AddRawExpressionNode(expression.Type, typeBinary.TypeOperand, expression.NodeType, - new List(1) { AddExpression(typeBinary.Expression) }); + children); } case ExpressionType.Dynamic: { var dynamic = (System.Linq.Expressions.DynamicExpression)expression; - var children = new List(dynamic.Arguments.Count); + ChildList children = default; for (var i = 0; i < dynamic.Arguments.Count; ++i) children.Add(AddExpression(dynamic.Arguments[i])); return _tree.AddRawExpressionNode(expression.Type, new DynamicData(dynamic.DelegateType, dynamic.Binder), expression.NodeType, children); @@ -691,7 +730,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.RuntimeVariables: { var runtime = (System.Linq.Expressions.RuntimeVariablesExpression)expression; - var children = new List(runtime.Variables.Count); + ChildList children = default; for (var i = 0; i < runtime.Variables.Count; ++i) children.Add(AddExpression(runtime.Variables[i])); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); @@ -706,17 +745,17 @@ private int AddExpression(SysExpr expression) default: if (expression is System.Linq.Expressions.UnaryExpression unary) { + ChildList children = default; + children.Add(AddExpression(unary.Operand)); return _tree.AddRawExpressionNode(expression.Type, unary.Method, expression.NodeType, - new List(1) { AddExpression(unary.Operand) }); + children); } if (expression is System.Linq.Expressions.BinaryExpression binary) { - var children = new List(binary.Conversion != null ? 3 : 2) - { - AddExpression(binary.Left), - AddExpression(binary.Right) - }; + ChildList children = default; + children.Add(AddExpression(binary.Left)); + children.Add(AddExpression(binary.Right)); if (binary.Conversion != null) children.Add(AddExpression(binary.Conversion)); return _tree.AddRawExpressionNode(expression.Type, new BinaryData(binary.Method, binary.IsLiftedToNull), expression.NodeType, children); @@ -737,7 +776,7 @@ private int AddConstant(System.Linq.Expressions.ConstantExpression constant) private int AddSwitchCase(SysSwitchCase switchCase) { - var children = new List(switchCase.TestValues.Count + 1); + ChildList children = default; for (var i = 0; i < switchCase.TestValues.Count; ++i) children.Add(AddExpression(switchCase.TestValues[i])); children.Add(AddExpression(switchCase.Body)); @@ -746,7 +785,7 @@ private int AddSwitchCase(SysSwitchCase switchCase) private int AddCatchBlock(SysCatchBlock catchBlock) { - var children = new List(3); + ChildList children = default; if (catchBlock.Variable != null) children.Add(AddExpression(catchBlock.Variable)); children.Add(AddExpression(catchBlock.Body)); @@ -764,12 +803,14 @@ private int AddMemberBinding(SysMemberBinding binding) switch (binding.BindingType) { case MemberBindingType.Assignment: + ChildList assignmentChildren = default; + assignmentChildren.Add(AddExpression(((System.Linq.Expressions.MemberAssignment)binding).Expression)); return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, - new List(1) { AddExpression(((System.Linq.Expressions.MemberAssignment)binding).Expression) }); + assignmentChildren); case MemberBindingType.MemberBinding: { var memberBinding = (System.Linq.Expressions.MemberMemberBinding)binding; - var children = new List(memberBinding.Bindings.Count); + ChildList children = default; for (var i = 0; i < memberBinding.Bindings.Count; ++i) children.Add(AddMemberBinding(memberBinding.Bindings[i])); return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberMemberBinding, children); @@ -777,7 +818,7 @@ private int AddMemberBinding(SysMemberBinding binding) case MemberBindingType.ListBinding: { var listBinding = (System.Linq.Expressions.MemberListBinding)binding; - var children = new List(listBinding.Initializers.Count); + ChildList children = default; for (var i = 0; i < listBinding.Initializers.Count; ++i) children.Add(AddElementInit(listBinding.Initializers[i])); return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberListBinding, children); @@ -789,7 +830,7 @@ private int AddMemberBinding(SysMemberBinding binding) private int AddElementInit(SysElementInit init) { - var children = new List(init.Arguments.Count); + ChildList children = default; for (var i = 0; i < init.Arguments.Count; ++i) children.Add(AddExpression(init.Arguments[i])); return _tree.AddRawAuxNode(init.AddMethod.DeclaringType, init.AddMethod, ExprNodeKind.ElementInit, children); @@ -815,7 +856,9 @@ private static int GetId(Dictionary ids, object item) private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, IEnumerable children, int childIdx) { - var nodeIndex = Nodes.Add(new ExprNode(type, obj, nodeType, kind, childIdx, 0, 0)); + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, childIdx, 0, 0); if (children == null) return nodeIndex; @@ -834,8 +877,30 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind ++childCount; } - ref var node = ref Nodes[nodeIndex]; - node.SetChildInfo(firstChildIndex, childCount); + ref var storedNode = ref Nodes[nodeIndex]; + storedNode.SetChildInfo(firstChildIndex, childCount); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, in ChildList children, int childIdx) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, childIdx, 0, 0); + if (children.Count == 0) + return nodeIndex; + + var firstChildIndex = children[0]; + var previousChildIndex = firstChildIndex; + for (var i = 1; i < children.Count; ++i) + { + ref var child = ref Nodes[previousChildIndex]; + child.SetNextIdx(children[i]); + previousChildIndex = children[i]; + } + + ref var storedNode = ref Nodes[nodeIndex]; + storedNode.SetChildInfo(firstChildIndex, children.Count); return nodeIndex; } @@ -900,10 +965,30 @@ private int[] CloneChildrenToArray(IEnumerable children) if (children == null) return Array.Empty(); - var cloned = new List(); + ChildList cloned = default; foreach (var child in children) cloned.Add(CloneChild(child)); - return cloned.ToArray(); + + return CopyChildrenToArray(cloned); + } + + private ChildList CloneChildren(in ChildList children) + { + ChildList cloned = default; + for (var i = 0; i < children.Count; ++i) + cloned.Add(CloneChild(children[i])); + return cloned; + } + + private static int[] CopyChildrenToArray(in ChildList children) + { + if (children.Count == 0) + return Array.Empty(); + + var array = new int[children.Count]; + for (var i = 0; i < children.Count; ++i) + array[i] = children[i]; + return array; } // Any leaf reused in more than one parent would have its intrusive sibling link (`NextIdx`) overwritten. @@ -1226,11 +1311,11 @@ private SysElementInit ReadElementInit(int index) return SysExpr.ElementInit((System.Reflection.MethodInfo)node.Obj, ReadExpressions(GetChildren(index))); } - private List GetChildren(int index) + private ChildList GetChildren(int index) { ref var node = ref _tree.Nodes[index]; var count = node.ChildCount; - var children = new List(count); + ChildList children = default; var childIndex = node.ChildIdx; for (var i = 0; i < count; ++i) { @@ -1241,7 +1326,7 @@ private List GetChildren(int index) } [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] - private SysExpr[] ReadExpressions(List childIndexes) + private SysExpr[] ReadExpressions(in ChildList childIndexes) { var expressions = new SysExpr[childIndexes.Count]; for (var i = 0; i < expressions.Length; ++i) From 7f3a5b6e77deb292dd2c1719244da40be5bbbb1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 09:31:17 +0000 Subject: [PATCH 08/15] perf: flatten flat expression node payloads Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/f813306b-4b7a-486b-b668-7c5ed51028ba Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 341 ++++++++---------- 1 file changed, 145 insertions(+), 196 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index e757232a..4a87a62c 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -36,6 +36,8 @@ public enum ExprNodeKind : byte MemberListBinding, /// Represents an element initializer payload. ElementInit, + /// Represents an internal object-reference metadata node. + ObjectReference, } /// Stores one flat expression node plus its intrusive child-link metadata. @@ -52,6 +54,8 @@ public struct ExprNode /// Gets or sets the runtime payload associated with the node. public object Obj; + internal int Data0; + internal int Data1; private ulong _data; /// Gets the expression kind encoded for this node. @@ -69,10 +73,13 @@ public struct ExprNode /// Gets the first child index or an auxiliary payload index. public int ChildIdx => (int)(_data & IndexMask); - internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, int childIdx = 0, int childCount = 0, int nextIdx = 0) + internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, int childIdx = 0, int childCount = 0, int nextIdx = 0, + int data0 = 0, int data1 = 0) { Type = type; Obj = obj; + Data0 = data0; + Data1 = data1; _data = ((ulong)(byte)nodeType << NodeTypeShift) | ((ulong)(byte)kind << KindShift) | ((ulong)(ushort)nextIdx << NextShift) @@ -107,7 +114,7 @@ public struct ExprTree public int Parameter(Type type, string name = null) { var id = Nodes.Count + 1; - return AddRawExpressionNode(type, new ParameterData(id, name, type.IsByRef), ExpressionType.Parameter); + return AddRawLeafExpressionNode(type, name, ExpressionType.Parameter, data0: id, data1: type.IsByRef ? 1 : 0); } /// Adds a typed parameter node and returns its index. @@ -244,7 +251,7 @@ public int MakeBinary(ExpressionType nodeType, int left, int right, bool isLifte { var children = conversion.HasValue ? new[] { left, right, conversion.Value } : new[] { left, right }; return AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), - new BinaryData(method, isLiftedToNull), nodeType, children); + method, nodeType, children, isLiftedToNull ? 1 : 0); } /// Adds a conditional node. @@ -274,7 +281,7 @@ public int Block(Type type, IEnumerable variables, params int[] expressions for (var i = 0; i < expressions.Length; ++i) children.Add(expressions[i]); return AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, - new BlockData(variableCount), ExpressionType.Block, children); + null, ExpressionType.Block, children, variableCount); } /// Adds a typed lambda node. @@ -313,7 +320,7 @@ public int ListInit(int @new, params int[] initializers) => public int Label(Type type = null, string name = null) { var id = Nodes.Count + 1; - return AddRawAuxNode(type ?? typeof(void), new LabelTargetData(id, name), ExprNodeKind.LabelTarget); + return AddRawLeafAuxNode(type ?? typeof(void), name, ExprNodeKind.LabelTarget, data0: id); } /// Adds a label-expression node. @@ -344,7 +351,8 @@ public int Loop(int body, int? @break = null, int? @continue = null) children.Add(@break.Value); if (@continue.HasValue) children.Add(@continue.Value); - return AddFactoryExpressionNode(typeof(void), new LoopData(@break.HasValue, @continue.HasValue), ExpressionType.Loop, children); + return AddFactoryExpressionNode(typeof(void), null, ExpressionType.Loop, children, + (@break.HasValue ? 1 : 0) | (@continue.HasValue ? 2 : 0)); } /// Adds a switch-case node. @@ -372,16 +380,16 @@ public int Switch(Type type, int switchValue, int? defaultBody, System.Reflectio if (cases != null && cases.Length != 0) for (var i = 0; i < cases.Length; ++i) children.Add(cases[i]); - return AddFactoryExpressionNode(type, new SwitchData(defaultBody.HasValue, comparison), ExpressionType.Switch, children); + return AddFactoryExpressionNode(type, comparison, ExpressionType.Switch, children, defaultBody.HasValue ? 1 : 0); } /// Adds a catch block with an exception variable. public int Catch(int variable, int body) => - AddFactoryAuxNode(Nodes[variable].Type, new CatchData(true, false), ExprNodeKind.CatchBlock, new[] { variable, body }); + AddFactoryAuxNode(Nodes[variable].Type, null, ExprNodeKind.CatchBlock, new[] { variable, body }, 1); /// Adds a catch block without an exception variable. public int Catch(Type test, int body) => - AddFactoryAuxNode(test, new CatchData(false, false), ExprNodeKind.CatchBlock, new[] { body }); + AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, new[] { body }); /// Adds a catch block with optional exception variable and filter. public int MakeCatchBlock(Type test, int? variable, int body, int? filter) @@ -392,20 +400,21 @@ public int MakeCatchBlock(Type test, int? variable, int body, int? filter) children.Add(body); if (filter.HasValue) children.Add(filter.Value); - return AddFactoryAuxNode(test, new CatchData(variable.HasValue, filter.HasValue), ExprNodeKind.CatchBlock, children); + return AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, children, + (variable.HasValue ? 1 : 0) | (filter.HasValue ? 2 : 0)); } /// Adds a try/catch node. public int TryCatch(int body, params int[] handlers) => - AddFactoryExpressionNode(Nodes[body].Type, new TryData(false, false), ExpressionType.Try, Prepend(body, handlers)); + AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, Prepend(body, handlers)); /// Adds a try/finally node. public int TryFinally(int body, int @finally) => - AddFactoryExpressionNode(Nodes[body].Type, new TryData(true, false), ExpressionType.Try, new[] { body, @finally }); + AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, new[] { body, @finally }, 1); /// Adds a try/fault node. public int TryFault(int body, int fault) => - AddFactoryExpressionNode(Nodes[body].Type, new TryData(false, true), ExpressionType.Try, new[] { body, fault }); + AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, new[] { body, fault }, 2); /// Adds a try node with optional finally block and catch handlers. public int TryCatchFinally(int body, int? @finally, params int[] handlers) @@ -417,7 +426,7 @@ public int TryCatchFinally(int body, int? @finally, params int[] handlers) if (handlers != null && handlers.Length != 0) for (var i = 0; i < handlers.Length; ++i) children.Add(handlers[i]); - return AddFactoryExpressionNode(Nodes[body].Type, new TryData(@finally.HasValue, false), ExpressionType.Try, children); + return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, children, @finally.HasValue ? 1 : 0); } /// Adds a type-test node. @@ -429,8 +438,15 @@ public int TypeEqual(int expression, Type type) => AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeEqual, expression); /// Adds a dynamic-expression node. - public int Dynamic(Type delegateType, CallSiteBinder binder, params int[] arguments) => - AddFactoryExpressionNode(typeof(object), new DynamicData(delegateType, binder), ExpressionType.Dynamic, arguments); + public int Dynamic(Type delegateType, CallSiteBinder binder, params int[] arguments) + { + ChildList children = default; + children.Add(AddObjectReferenceNode(typeof(Type), delegateType)); + if (arguments != null && arguments.Length != 0) + for (var i = 0; i < arguments.Length; ++i) + children.Add(arguments[i]); + return AddFactoryExpressionNode(typeof(object), binder, ExpressionType.Dynamic, children); + } /// Adds a runtime-variables node. public int RuntimeVariables(params int[] variables) => @@ -438,7 +454,8 @@ public int RuntimeVariables(params int[] variables) => /// Adds a debug-info node. public int DebugInfo(string fileName, int startLine, int startColumn, int endLine, int endColumn) => - AddRawExpressionNode(typeof(void), new DebugInfoData(fileName, startLine, startColumn, endLine, endColumn), ExpressionType.DebugInfo); + AddRawLeafExpressionNode(typeof(void), fileName, ExpressionType.DebugInfo, + childIdx: checked((ushort)startColumn), childCount: checked((ushort)endColumn), data0: startLine, data1: endLine); /// Flattens a System.Linq expression tree. public static ExprTree FromExpression(SysExpr expression) @@ -481,12 +498,21 @@ private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeT private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, params int[] children) => AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children)); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children, int data0, int data1 = 0) => + AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children), data0, data1); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) { var cloned = CloneChildren(children); return AddRawExpressionNode(type, obj, nodeType, in cloned); } + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children, int data0, int data1 = 0) + { + var cloned = CloneChildren(children); + return AddRawExpressionNode(type, obj, nodeType, in cloned, data0, data1); + } + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children) => AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children)); @@ -496,14 +522,24 @@ private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, params int[] children) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0); + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children, int data0, int data1 = 0) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0, 0, 0, data0, data1); + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, in children, 0); + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children, int data0, int data1 = 0) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, in children, 0, 0, 0, data0, data1); + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0); private int AddRawExpressionNodeWithChildIndex(Type type, object obj, ExpressionType nodeType, int childIdx) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, null, childIdx); + AddRawLeafExpressionNode(type, obj, nodeType, childIdx: childIdx); + + private int AddRawLeafExpressionNode(Type type, object obj, ExpressionType nodeType, int childIdx = 0, int childCount = 0, + int data0 = 0, int data1 = 0) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, null, childIdx, childCount, 0, data0, data1); private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int child) => AddRawAuxNode(type, obj, kind, CloneChild(child)); @@ -511,24 +547,46 @@ private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int chil private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, params int[] children) => AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children)); + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children, int data0, int data1 = 0) => + AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children), data0, data1); + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) { var cloned = CloneChildren(children); return AddRawAuxNode(type, obj, kind, in cloned); } + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children, int data0, int data1 = 0) + { + var cloned = CloneChildren(children); + return AddRawAuxNode(type, obj, kind, in cloned, data0, data1); + } + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children) => AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children)); private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, params int[] children) => AddNode(type, obj, ExpressionType.Extension, kind, children, 0); + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, int[] children, int data0, int data1 = 0) => + AddNode(type, obj, ExpressionType.Extension, kind, children, 0, 0, 0, data0, data1); + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => AddNode(type, obj, ExpressionType.Extension, kind, in children, 0); + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children, int data0, int data1 = 0) => + AddNode(type, obj, ExpressionType.Extension, kind, in children, 0, 0, 0, data0, data1); + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children) => AddNode(type, obj, ExpressionType.Extension, kind, children, 0); + private int AddRawLeafAuxNode(Type type, object obj, ExprNodeKind kind, int childIdx = 0, int childCount = 0, + int data0 = 0, int data1 = 0) => + AddNode(type, obj, ExpressionType.Extension, kind, null, childIdx, childCount, 0, data0, data1); + + private int AddObjectReferenceNode(Type type, object obj) => + AddRawLeafAuxNode(type, obj, ExprNodeKind.ObjectReference); + private sealed class Builder { private readonly Dictionary _parameterIds = new(ReferenceEqComparer.Instance); @@ -552,7 +610,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.Parameter: { var parameter = (SysParameterExpression)expression; - return _tree.AddRawExpressionNode(expression.Type, new ParameterData(GetId(_parameterIds, parameter), parameter.Name, parameter.IsByRef), expression.NodeType); + return _tree.AddRawLeafExpressionNode(expression.Type, parameter.Name, expression.NodeType, + data0: GetId(_parameterIds, parameter), data1: parameter.IsByRef ? 1 : 0); } case ExpressionType.Lambda: { @@ -571,7 +630,7 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(block.Variables[i])); for (var i = 0; i < block.Expressions.Count; ++i) children.Add(AddExpression(block.Expressions[i])); - return _tree.AddRawExpressionNode(expression.Type, new BlockData(block.Variables.Count), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children, block.Variables.Count); } case ExpressionType.MemberAccess: { @@ -641,14 +700,14 @@ private int AddExpression(SysExpr expression) case ExpressionType.Loop: { var loop = (System.Linq.Expressions.LoopExpression)expression; - var data = new LoopData(loop.BreakLabel != null, loop.ContinueLabel != null); ChildList children = default; children.Add(AddExpression(loop.Body)); if (loop.BreakLabel != null) children.Add(AddLabelTarget(loop.BreakLabel)); if (loop.ContinueLabel != null) children.Add(AddLabelTarget(loop.ContinueLabel)); - return _tree.AddRawExpressionNode(expression.Type, data, expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children, + (loop.BreakLabel != null ? 1 : 0) | (loop.ContinueLabel != null ? 2 : 0)); } case ExpressionType.Goto: { @@ -677,7 +736,8 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(@switch.DefaultBody)); for (var i = 0; i < @switch.Cases.Count; ++i) children.Add(AddSwitchCase(@switch.Cases[i])); - return _tree.AddRawExpressionNode(expression.Type, new SwitchData(@switch.DefaultBody != null, @switch.Comparison), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, @switch.Comparison, expression.NodeType, children, + @switch.DefaultBody != null ? 1 : 0); } case ExpressionType.Try: { @@ -690,7 +750,8 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(@try.Finally)); for (var i = 0; i < @try.Handlers.Count; ++i) children.Add(AddCatchBlock(@try.Handlers[i])); - return _tree.AddRawExpressionNode(expression.Type, new TryData(@try.Finally != null, @try.Fault != null), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children, + (@try.Finally != null ? 1 : 0) | (@try.Fault != null ? 2 : 0)); } case ExpressionType.MemberInit: { @@ -723,9 +784,10 @@ private int AddExpression(SysExpr expression) { var dynamic = (System.Linq.Expressions.DynamicExpression)expression; ChildList children = default; + children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); for (var i = 0; i < dynamic.Arguments.Count; ++i) children.Add(AddExpression(dynamic.Arguments[i])); - return _tree.AddRawExpressionNode(expression.Type, new DynamicData(dynamic.DelegateType, dynamic.Binder), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, dynamic.Binder, expression.NodeType, children); } case ExpressionType.RuntimeVariables: { @@ -738,9 +800,9 @@ private int AddExpression(SysExpr expression) case ExpressionType.DebugInfo: { var debug = (System.Linq.Expressions.DebugInfoExpression)expression; - return _tree.AddRawExpressionNode(expression.Type, - new DebugInfoData(debug.Document.FileName, debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn), - expression.NodeType); + return _tree.AddRawLeafExpressionNode(expression.Type, debug.Document.FileName, expression.NodeType, + childIdx: checked((ushort)debug.StartColumn), childCount: checked((ushort)debug.EndColumn), + data0: debug.StartLine, data1: debug.EndLine); } default: if (expression is System.Linq.Expressions.UnaryExpression unary) @@ -758,7 +820,8 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(binary.Right)); if (binary.Conversion != null) children.Add(AddExpression(binary.Conversion)); - return _tree.AddRawExpressionNode(expression.Type, new BinaryData(binary.Method, binary.IsLiftedToNull), expression.NodeType, children); + return _tree.AddRawExpressionNode(expression.Type, binary.Method, expression.NodeType, children, + binary.IsLiftedToNull ? 1 : 0); } throw new NotSupportedException($"Flattening of `ExpressionType.{expression.NodeType}` is not supported yet."); @@ -791,12 +854,12 @@ private int AddCatchBlock(SysCatchBlock catchBlock) children.Add(AddExpression(catchBlock.Body)); if (catchBlock.Filter != null) children.Add(AddExpression(catchBlock.Filter)); - return _tree.AddRawAuxNode(catchBlock.Test, new CatchData(catchBlock.Variable != null, catchBlock.Filter != null), - ExprNodeKind.CatchBlock, children); + return _tree.AddRawAuxNode(catchBlock.Test, null, ExprNodeKind.CatchBlock, children, + (catchBlock.Variable != null ? 1 : 0) | (catchBlock.Filter != null ? 2 : 0)); } private int AddLabelTarget(SysLabelTarget target) => - _tree.AddRawAuxNode(target.Type, new LabelTargetData(GetId(_labelIds, target), target.Name), ExprNodeKind.LabelTarget); + _tree.AddRawLeafAuxNode(target.Type, target.Name, ExprNodeKind.LabelTarget, data0: GetId(_labelIds, target)); private int AddMemberBinding(SysMemberBinding binding) { @@ -854,11 +917,12 @@ private static int GetId(Dictionary ids, object item) }; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, IEnumerable children, int childIdx) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, IEnumerable children, int childIdx, + int childCount = 0, int nextIdx = 0, int data0 = 0, int data1 = 0) { var nodeIndex = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, childIdx, 0, 0); + newNode = new ExprNode(type, obj, nodeType, kind, childIdx, childCount, nextIdx, data0, data1); if (children == null) return nodeIndex; @@ -868,25 +932,26 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind var firstChildIndex = enumerator.Current; var previousChildIndex = firstChildIndex; - var childCount = 1; + var linkedChildCount = 1; while (enumerator.MoveNext()) { ref var child = ref Nodes[previousChildIndex]; child.SetNextIdx(enumerator.Current); previousChildIndex = enumerator.Current; - ++childCount; + ++linkedChildCount; } ref var storedNode = ref Nodes[nodeIndex]; - storedNode.SetChildInfo(firstChildIndex, childCount); + storedNode.SetChildInfo(firstChildIndex, linkedChildCount); return nodeIndex; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, in ChildList children, int childIdx) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, in ChildList children, int childIdx, + int childCount = 0, int nextIdx = 0, int data0 = 0, int data1 = 0) { var nodeIndex = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, childIdx, 0, 0); + newNode = new ExprNode(type, obj, nodeType, kind, childIdx, childCount, nextIdx, data0, data1); if (children.Count == 0) return nodeIndex; @@ -956,7 +1021,7 @@ private int CloneChild(int index) { ref var node = ref Nodes[index]; return ShouldCloneWhenLinking(node) - ? AddNode(node.Type, node.Obj, node.NodeType, node.Kind, null, node.ChildIdx) + ? AddNode(node.Type, node.Obj, node.NodeType, node.Kind, null, node.ChildIdx, node.ChildCount, 0, node.Data0, node.Data1) : index; } @@ -997,7 +1062,11 @@ private static int[] CopyChildrenToArray(in ChildList children) private static bool ShouldCloneWhenLinking(in ExprNode node) => node.Kind == ExprNodeKind.LabelTarget || node.NodeType == ExpressionType.Parameter || - node.ChildCount == 0; + node.Kind == ExprNodeKind.ObjectReference || + !HasLinkedChildren(node); + + private static bool HasLinkedChildren(in ExprNode node) => + node.NodeType != ExpressionType.DebugInfo && node.ChildCount != 0; private static IEnumerable Single(int item) { @@ -1045,13 +1114,12 @@ public SysExpr ReadExpression(int index) return SysExpr.Default(node.Type); case ExpressionType.Parameter: { - var data = (ParameterData)node.Obj; - if (_parametersById.TryGetValue(data.Id, out var parameter)) + if (_parametersById.TryGetValue(node.Data0, out var parameter)) return parameter; - var parameterType = data.IsByRef && !node.Type.IsByRef ? node.Type.MakeByRefType() : node.Type; - parameter = SysExpr.Parameter(parameterType, data.Name); - _parametersById[data.Id] = parameter; + var parameterType = node.Data1 != 0 && !node.Type.IsByRef ? node.Type.MakeByRefType() : node.Type; + parameter = SysExpr.Parameter(parameterType, (string)node.Obj); + _parametersById[node.Data0] = parameter; return parameter; } case ExpressionType.Lambda: @@ -1065,14 +1133,13 @@ public SysExpr ReadExpression(int index) } case ExpressionType.Block: { - var data = (BlockData)node.Obj; var children = GetChildren(index); - var variables = new SysParameterExpression[data.VariableCount]; + var variables = new SysParameterExpression[node.Data0]; for (var i = 0; i < variables.Length; ++i) variables[i] = (SysParameterExpression)ReadExpression(children[i]); - var expressions = new SysExpr[children.Count - data.VariableCount]; - for (var i = data.VariableCount; i < children.Count; ++i) - expressions[i - data.VariableCount] = ReadExpression(children[i]); + var expressions = new SysExpr[children.Count - node.Data0]; + for (var i = node.Data0; i < children.Count; ++i) + expressions[i - node.Data0] = ReadExpression(children[i]); return SysExpr.Block(node.Type, variables, expressions); } case ExpressionType.MemberAccess: @@ -1131,11 +1198,10 @@ public SysExpr ReadExpression(int index) } case ExpressionType.Loop: { - var data = (LoopData)node.Obj; var children = GetChildren(index); var childIndex = 1; - var breakLabel = data.HasBreak ? ReadLabelTarget(children[childIndex++]) : null; - var continueLabel = data.HasContinue ? ReadLabelTarget(children[childIndex]) : null; + var breakLabel = (node.Data0 & 1) != 0 ? ReadLabelTarget(children[childIndex++]) : null; + var continueLabel = (node.Data0 & 2) != 0 ? ReadLabelTarget(children[childIndex]) : null; return SysExpr.Loop(ReadExpression(children[0]), breakLabel, continueLabel); } case ExpressionType.Goto: @@ -1152,24 +1218,22 @@ public SysExpr ReadExpression(int index) } case ExpressionType.Switch: { - var data = (SwitchData)node.Obj; var children = GetChildren(index); var childIndex = 1; - var defaultBody = data.HasDefault ? ReadExpression(children[childIndex++]) : null; + var defaultBody = (node.Data0 & 1) != 0 ? ReadExpression(children[childIndex++]) : null; var cases = new SysSwitchCase[children.Count - childIndex]; for (var i = childIndex; i < children.Count; ++i) cases[i - childIndex] = ReadSwitchCase(children[i]); - return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, data.Comparison, cases); + return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, (System.Reflection.MethodInfo)node.Obj, cases); } case ExpressionType.Try: { - var data = (TryData)node.Obj; var children = GetChildren(index); var childIndex = 1; - if (data.HasFault) + if ((node.Data0 & 2) != 0) return SysExpr.TryFault(ReadExpression(children[0]), ReadExpression(children[1])); - var @finally = data.HasFinally ? ReadExpression(children[childIndex++]) : null; + var @finally = (node.Data0 & 1) != 0 ? ReadExpression(children[childIndex++]) : null; var handlers = new SysCatchBlock[children.Count - childIndex]; for (var i = childIndex; i < children.Count; ++i) handlers[i - childIndex] = ReadCatchBlock(children[i]); @@ -1197,8 +1261,12 @@ public SysExpr ReadExpression(int index) return SysExpr.TypeEqual(ReadExpression(GetChildren(index)[0]), (Type)node.Obj); case ExpressionType.Dynamic: { - var data = (DynamicData)node.Obj; - return SysExpr.MakeDynamic(data.DelegateType, data.Binder, ReadExpressions(GetChildren(index))); + var children = GetChildren(index); + var delegateType = (Type)ReadObjectReference(children[0]); + var arguments = new SysExpr[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + arguments[i - 1] = ReadExpression(children[i]); + return SysExpr.MakeDynamic(delegateType, (CallSiteBinder)node.Obj, arguments); } case ExpressionType.RuntimeVariables: { @@ -1210,9 +1278,8 @@ public SysExpr ReadExpression(int index) } case ExpressionType.DebugInfo: { - var data = (DebugInfoData)node.Obj; - return SysExpr.DebugInfo(SysExpr.SymbolDocument(data.FileName), - data.StartLine, data.StartColumn, data.EndLine, data.EndColumn); + return SysExpr.DebugInfo(SysExpr.SymbolDocument((string)node.Obj), + node.Data0, node.ChildIdx, node.Data1, node.ChildCount); } default: if (node.ChildCount == 1) @@ -1223,11 +1290,10 @@ public SysExpr ReadExpression(int index) if (node.ChildCount >= 2) { - var data = node.Obj as BinaryData; var children = GetChildren(index); var conversion = children.Count > 2 ? (System.Linq.Expressions.LambdaExpression)ReadExpression(children[2]) : null; return SysExpr.MakeBinary(node.NodeType, ReadExpression(children[0]), ReadExpression(children[1]), - data != null && data.IsLiftedToNull, data?.Method, conversion); + node.Data0 != 0, (System.Reflection.MethodInfo)node.Obj, conversion); } throw new NotSupportedException($"Reconstruction of `ExpressionType.{node.NodeType}` is not supported yet."); @@ -1251,12 +1317,11 @@ private SysCatchBlock ReadCatchBlock(int index) { ref var node = ref _tree.Nodes[index]; Debug.Assert(node.Kind == ExprNodeKind.CatchBlock); - var data = (CatchData)node.Obj; var children = GetChildren(index); var childIndex = 0; - var variable = data.HasVariable ? (SysParameterExpression)ReadExpression(children[childIndex++]) : null; + var variable = (node.Data0 & 1) != 0 ? (SysParameterExpression)ReadExpression(children[childIndex++]) : null; var body = ReadExpression(children[childIndex++]); - var filter = data.HasFilter ? ReadExpression(children[childIndex]) : null; + var filter = (node.Data0 & 2) != 0 ? ReadExpression(children[childIndex]) : null; return SysExpr.MakeCatchBlock(node.Type, variable, body, filter); } @@ -1264,15 +1329,21 @@ private SysLabelTarget ReadLabelTarget(int index) { ref var node = ref _tree.Nodes[index]; Debug.Assert(node.Kind == ExprNodeKind.LabelTarget); - var data = (LabelTargetData)node.Obj; - if (_labelsById.TryGetValue(data.Id, out var label)) + if (_labelsById.TryGetValue(node.Data0, out var label)) return label; - label = SysExpr.Label(node.Type, data.Name); - _labelsById[data.Id] = label; + label = SysExpr.Label(node.Type, (string)node.Obj); + _labelsById[node.Data0] = label; return label; } + private object ReadObjectReference(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Kind == ExprNodeKind.ObjectReference); + return node.Obj; + } + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] private SysMemberBinding ReadMemberBinding(int index) { @@ -1340,128 +1411,6 @@ private SysExpr[] ReadExpressions(in ChildList childIndexes) private static System.Linq.Expressions.NewExpression CreateValueTypeNewExpression(Type type) => SysExpr.New(type); } - private sealed class ParameterData - { - public readonly int Id; - public readonly string Name; - public readonly bool IsByRef; - - public ParameterData(int id, string name, bool isByRef) - { - Id = id; - Name = name; - IsByRef = isByRef; - } - } - - private sealed class LabelTargetData - { - public readonly int Id; - public readonly string Name; - - public LabelTargetData(int id, string name) - { - Id = id; - Name = name; - } - } - - private sealed class BlockData - { - public readonly int VariableCount; - public BlockData(int variableCount) => VariableCount = variableCount; - } - - private sealed class SwitchData - { - public readonly bool HasDefault; - public readonly System.Reflection.MethodInfo Comparison; - - public SwitchData(bool hasDefault, System.Reflection.MethodInfo comparison) - { - HasDefault = hasDefault; - Comparison = comparison; - } - } - - private sealed class TryData - { - public readonly bool HasFinally; - public readonly bool HasFault; - - public TryData(bool hasFinally, bool hasFault) - { - HasFinally = hasFinally; - HasFault = hasFault; - } - } - - private sealed class LoopData - { - public readonly bool HasBreak; - public readonly bool HasContinue; - - public LoopData(bool hasBreak, bool hasContinue) - { - HasBreak = hasBreak; - HasContinue = hasContinue; - } - } - - private sealed class CatchData - { - public readonly bool HasVariable; - public readonly bool HasFilter; - - public CatchData(bool hasVariable, bool hasFilter) - { - HasVariable = hasVariable; - HasFilter = hasFilter; - } - } - - private sealed class BinaryData - { - public readonly System.Reflection.MethodInfo Method; - public readonly bool IsLiftedToNull; - - public BinaryData(System.Reflection.MethodInfo method, bool isLiftedToNull) - { - Method = method; - IsLiftedToNull = isLiftedToNull; - } - } - - private sealed class DynamicData - { - public readonly Type DelegateType; - public readonly CallSiteBinder Binder; - - public DynamicData(Type delegateType, CallSiteBinder binder) - { - DelegateType = delegateType; - Binder = binder; - } - } - - private sealed class DebugInfoData - { - public readonly string FileName; - public readonly int StartLine; - public readonly int StartColumn; - public readonly int EndLine; - public readonly int EndColumn; - - public DebugInfoData(string fileName, int startLine, int startColumn, int endLine, int endColumn) - { - FileName = fileName; - StartLine = startLine; - StartColumn = startColumn; - EndLine = endLine; - EndColumn = endColumn; - } - } - private sealed class ReferenceEqComparer : IEqualityComparer { public static readonly ReferenceEqComparer Instance = new(); From 689458f70bc142dcfd80691a2667a363f05f2184 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:31:22 +0000 Subject: [PATCH 09/15] perf: compact flat expression node layout Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/f2a1578b-aaf8-4465-9e1f-55f5199c16e4 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 632 ++++++++++-------- 1 file changed, 353 insertions(+), 279 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 4a87a62c..91a890d0 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -6,6 +6,7 @@ namespace FastExpressionCompiler.FlatExpression; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using FastExpressionCompiler.LightExpression.ImTools; using ChildList = FastExpressionCompiler.LightExpression.ImTools.SmallList, FastExpressionCompiler.LightExpression.ImTools.NoArrayPool>; using LightExpression = FastExpressionCompiler.LightExpression.Expression; @@ -38,31 +39,41 @@ public enum ExprNodeKind : byte ElementInit, /// Represents an internal object-reference metadata node. ObjectReference, + /// Represents an internal child-list metadata node. + ChildList, + /// Represents an internal pair of ushort values. + UInt16Pair, } /// Stores one flat expression node plus its intrusive child-link metadata. +[StructLayout(LayoutKind.Explicit, Size = 24)] public struct ExprNode { private const int NodeTypeShift = 56; - private const int KindShift = 48; + private const int TagShift = 48; private const int NextShift = 32; private const int CountShift = 16; private const ulong IndexMask = 0xFFFF; + private const ulong KindMask = 0x0F; + private const int FlagsShift = 4; /// Gets or sets the runtime type of the represented node. + [FieldOffset(0)] public Type Type; /// Gets or sets the runtime payload associated with the node. + [FieldOffset(8)] public object Obj; - internal int Data0; - internal int Data1; + [FieldOffset(16)] private ulong _data; /// Gets the expression kind encoded for this node. public ExpressionType NodeType => (ExpressionType)((_data >> NodeTypeShift) & 0xFF); /// Gets the payload classification for this node. - public ExprNodeKind Kind => (ExprNodeKind)((_data >> KindShift) & 0xFF); + public ExprNodeKind Kind => (ExprNodeKind)((_data >> TagShift) & KindMask); + + internal byte Flags => (byte)(((byte)(_data >> TagShift)) >> FlagsShift); /// Gets the next sibling node index in the intrusive child chain. public int NextIdx => (int)((_data >> NextShift) & IndexMask); @@ -73,15 +84,13 @@ public struct ExprNode /// Gets the first child index or an auxiliary payload index. public int ChildIdx => (int)(_data & IndexMask); - internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, int childIdx = 0, int childCount = 0, int nextIdx = 0, - int data0 = 0, int data1 = 0) + internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0, int nextIdx = 0) { Type = type; Obj = obj; - Data0 = data0; - Data1 = data1; + var tag = (byte)((flags << FlagsShift) | (byte)kind); _data = ((ulong)(byte)nodeType << NodeTypeShift) - | ((ulong)(byte)kind << KindShift) + | ((ulong)tag << TagShift) | ((ulong)(ushort)nextIdx << NextShift) | ((ulong)(ushort)childCount << CountShift) | (ushort)childIdx; @@ -100,6 +109,13 @@ internal void SetChildInfo(int childIdx, int childCount) => public struct ExprTree { private static readonly object ClosureConstantMarker = new(); + private const byte ParameterByRefFlag = 1; + private const byte BinaryLiftedToNullFlag = 1; + private const byte LoopHasBreakFlag = 1; + private const byte LoopHasContinueFlag = 2; + private const byte CatchHasVariableFlag = 1; + private const byte CatchHasFilterFlag = 2; + private const byte TryFaultFlag = 1; /// Gets or sets the root node index. public int RootIndex; @@ -114,7 +130,7 @@ public struct ExprTree public int Parameter(Type type, string name = null) { var id = Nodes.Count + 1; - return AddRawLeafExpressionNode(type, name, ExpressionType.Parameter, data0: id, data1: type.IsByRef ? 1 : 0); + return AddRawLeafExpressionNode(type, name, ExpressionType.Parameter, type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: id); } /// Adds a typed parameter node and returns its index. @@ -177,7 +193,9 @@ public int NewArrayBounds(Type elementType, params int[] bounds) => /// Adds an invocation node. public int Invoke(int expression, params int[] arguments) => - AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, Prepend(expression, arguments)); + arguments == null || arguments.Length == 0 + ? AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, expression) + : AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, PrependToChildList(expression, arguments)); /// Adds a static-call node. public int Call(System.Reflection.MethodInfo method, params int[] arguments) => @@ -185,12 +203,15 @@ public int Call(System.Reflection.MethodInfo method, params int[] arguments) => /// Adds an instance-call node. public int Call(int instance, System.Reflection.MethodInfo method, params int[] arguments) => - AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, Prepend(instance, arguments)); + arguments == null || arguments.Length == 0 + ? AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, instance) + : AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, PrependToChildList(instance, arguments)); /// Adds a field or property access node. public int MakeMemberAccess(int? instance, System.Reflection.MemberInfo member) => - AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, - instance.HasValue ? Single(instance.Value) : null); + instance.HasValue + ? AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, instance.Value) + : AddRawExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess); /// Adds a field-access node. public int Field(int instance, System.Reflection.FieldInfo field) => MakeMemberAccess(instance, field); @@ -205,7 +226,7 @@ public int MakeMemberAccess(int? instance, System.Reflection.MemberInfo member) public int Property(int instance, System.Reflection.PropertyInfo property, params int[] arguments) => arguments == null || arguments.Length == 0 ? Property(instance, property) - : AddFactoryExpressionNode(property.PropertyType, property, ExpressionType.Index, Prepend(instance, arguments)); + : AddFactoryExpressionNode(property.PropertyType, property, ExpressionType.Index, PrependToChildList(instance, arguments)); /// Adds a one-dimensional array index node. public int ArrayIndex(int array, int index) => MakeBinary(ExpressionType.ArrayIndex, array, index); @@ -214,7 +235,7 @@ public int Property(int instance, System.Reflection.PropertyInfo property, param public int ArrayAccess(int array, params int[] indexes) => indexes != null && indexes.Length == 1 ? ArrayIndex(array, indexes[0]) - : AddFactoryExpressionNode(GetArrayElementType(Nodes[array].Type, indexes?.Length ?? 0), null, ExpressionType.Index, Prepend(array, indexes)); + : AddFactoryExpressionNode(GetArrayElementType(Nodes[array].Type, indexes?.Length ?? 0), null, ExpressionType.Index, PrependToChildList(array, indexes)); /// Adds a conversion node. public int Convert(int operand, Type type, System.Reflection.MethodInfo method = null) => @@ -248,15 +269,15 @@ public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, Sys /// Adds a binary node of the specified kind. public int MakeBinary(ExpressionType nodeType, int left, int right, bool isLiftedToNull = false, System.Reflection.MethodInfo method = null, int? conversion = null, Type type = null) - { - var children = conversion.HasValue ? new[] { left, right, conversion.Value } : new[] { left, right }; - return AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), - method, nodeType, children, isLiftedToNull ? 1 : 0); - } + => conversion.HasValue + ? AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), + method, nodeType, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, left, right, conversion.Value) + : AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), + method, nodeType, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, left, right); /// Adds a conditional node. public int Condition(int test, int ifTrue, int ifFalse, Type type = null) => - AddFactoryExpressionNode(type ?? Nodes[ifTrue].Type, null, ExpressionType.Conditional, new[] { test, ifTrue, ifFalse }); + AddFactoryExpressionNode(type ?? Nodes[ifTrue].Type, null, ExpressionType.Conditional, 0, test, ifTrue, ifFalse); /// Adds a block node without explicit variables. public int Block(params int[] expressions) => @@ -269,19 +290,19 @@ 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; - var variableCount = 0; if (variables != null) { + ChildList variableChildren = default; foreach (var variable in variables) - { - children.Add(variable); - ++variableCount; - } + variableChildren.Add(variable); + if (variableChildren.Count != 0) + children.Add(AddChildListNode(in variableChildren)); } + ChildList bodyChildren = default; for (var i = 0; i < expressions.Length; ++i) - children.Add(expressions[i]); - return AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, - null, ExpressionType.Block, children, variableCount); + bodyChildren.Add(expressions[i]); + children.Add(AddChildListNode(in bodyChildren)); + return AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, null, ExpressionType.Block, in children); } /// Adds a typed lambda node. @@ -290,7 +311,9 @@ public int Lambda(int body, params int[] parameters) where TDelegate /// Adds a lambda node. public int Lambda(Type delegateType, int body, params int[] parameters) => - AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, Prepend(body, parameters)); + parameters == null || parameters.Length == 0 + ? AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, 0, body) + : AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, PrependToChildList(body, parameters)); /// Adds a member-assignment binding node. public int Bind(System.Reflection.MemberInfo member, int expression) => @@ -310,30 +333,36 @@ public int ListBind(System.Reflection.MemberInfo member, params int[] initialize /// Adds a member-init node. public int MemberInit(int @new, params int[] bindings) => - AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, Prepend(@new, bindings)); + bindings == null || bindings.Length == 0 + ? AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, @new) + : AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, PrependToChildList(@new, bindings)); /// Adds a list-init node. public int ListInit(int @new, params int[] initializers) => - AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, Prepend(@new, initializers)); + initializers == null || initializers.Length == 0 + ? AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, @new) + : AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, PrependToChildList(@new, initializers)); /// Adds a label-target node. public int Label(Type type = null, string name = null) { var id = Nodes.Count + 1; - return AddRawLeafAuxNode(type ?? typeof(void), name, ExprNodeKind.LabelTarget, data0: id); + return AddRawLeafAuxNode(type ?? typeof(void), name, ExprNodeKind.LabelTarget, childIdx: id); } /// Adds a label-expression node. public int Label(int target, int? defaultValue = null) => - AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, - defaultValue.HasValue ? new[] { target, defaultValue.Value } : new[] { target }); + defaultValue.HasValue + ? AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target, defaultValue.Value) + : AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target); /// Adds a goto-family node. public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type type = null) { var resultType = type ?? (value.HasValue ? Nodes[value.Value].Type : typeof(void)); - return AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, - value.HasValue ? new[] { target, value.Value } : new[] { target }); + return value.HasValue + ? AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, 0, target, value.Value) + : AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, 0, target); } /// Adds a goto node. @@ -351,8 +380,8 @@ public int Loop(int body, int? @break = null, int? @continue = null) children.Add(@break.Value); if (@continue.HasValue) children.Add(@continue.Value); - return AddFactoryExpressionNode(typeof(void), null, ExpressionType.Loop, children, - (@break.HasValue ? 1 : 0) | (@continue.HasValue ? 2 : 0)); + return AddFactoryExpressionNode(typeof(void), null, ExpressionType.Loop, + (byte)((@break.HasValue ? LoopHasBreakFlag : 0) | (@continue.HasValue ? LoopHasContinueFlag : 0)), in children); } /// Adds a switch-case node. @@ -378,18 +407,22 @@ public int Switch(Type type, int switchValue, int? defaultBody, System.Reflectio if (defaultBody.HasValue) children.Add(defaultBody.Value); if (cases != null && cases.Length != 0) + { + ChildList caseChildren = default; for (var i = 0; i < cases.Length; ++i) - children.Add(cases[i]); - return AddFactoryExpressionNode(type, comparison, ExpressionType.Switch, children, defaultBody.HasValue ? 1 : 0); + caseChildren.Add(cases[i]); + children.Add(AddChildListNode(in caseChildren)); + } + return AddFactoryExpressionNode(type, comparison, ExpressionType.Switch, in children); } /// Adds a catch block with an exception variable. public int Catch(int variable, int body) => - AddFactoryAuxNode(Nodes[variable].Type, null, ExprNodeKind.CatchBlock, new[] { variable, body }, 1); + AddFactoryAuxNode(Nodes[variable].Type, null, ExprNodeKind.CatchBlock, CatchHasVariableFlag, variable, body); /// Adds a catch block without an exception variable. public int Catch(Type test, int body) => - AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, new[] { body }); + AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, 0, body); /// Adds a catch block with optional exception variable and filter. public int MakeCatchBlock(Type test, int? variable, int body, int? filter) @@ -400,21 +433,32 @@ public int MakeCatchBlock(Type test, int? variable, int body, int? filter) children.Add(body); if (filter.HasValue) children.Add(filter.Value); - return AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, children, - (variable.HasValue ? 1 : 0) | (filter.HasValue ? 2 : 0)); + return AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, + (byte)((variable.HasValue ? CatchHasVariableFlag : 0) | (filter.HasValue ? CatchHasFilterFlag : 0)), in children); } /// Adds a try/catch node. - public int TryCatch(int body, params int[] handlers) => - AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, Prepend(body, handlers)); + public int TryCatch(int body, params int[] handlers) + { + if (handlers == null || handlers.Length == 0) + return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body); + + ChildList handlerChildren = default; + for (var i = 0; i < handlers.Length; ++i) + handlerChildren.Add(handlers[i]); + ChildList children = default; + children.Add(body); + children.Add(AddChildListNode(in handlerChildren)); + return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, in children); + } /// Adds a try/finally node. public int TryFinally(int body, int @finally) => - AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, new[] { body, @finally }, 1); + AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body, @finally); /// Adds a try/fault node. public int TryFault(int body, int fault) => - AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, new[] { body, fault }, 2); + AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, TryFaultFlag, body, fault); /// Adds a try node with optional finally block and catch handlers. public int TryCatchFinally(int body, int? @finally, params int[] handlers) @@ -424,9 +468,13 @@ public int TryCatchFinally(int body, int? @finally, params int[] handlers) if (@finally.HasValue) children.Add(@finally.Value); if (handlers != null && handlers.Length != 0) + { + ChildList handlerChildren = default; for (var i = 0; i < handlers.Length; ++i) - children.Add(handlers[i]); - return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, children, @finally.HasValue ? 1 : 0); + handlerChildren.Add(handlers[i]); + children.Add(AddChildListNode(in handlerChildren)); + } + return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, in children); } /// Adds a type-test node. @@ -454,143 +502,138 @@ public int RuntimeVariables(params int[] variables) => /// Adds a debug-info node. public int DebugInfo(string fileName, int startLine, int startColumn, int endLine, int endColumn) => - AddRawLeafExpressionNode(typeof(void), fileName, ExpressionType.DebugInfo, - childIdx: checked((ushort)startColumn), childCount: checked((ushort)endColumn), data0: startLine, data1: endLine); + AddFactoryExpressionNode(typeof(void), fileName, ExpressionType.DebugInfo, CreateDebugInfoChildren(startLine, startColumn, endLine, endColumn)); /// Flattens a System.Linq expression tree. - public static ExprTree FromExpression(SysExpr expression) - { - if (expression == null) - throw new ArgumentNullException(nameof(expression)); - - var builder = new Builder(); - return builder.Build(expression); - } + public static ExprTree FromExpression(SysExpr expression) => + new Builder().Build(expression ?? throw new ArgumentNullException(nameof(expression))); /// Flattens a LightExpression tree. - public static ExprTree FromLightExpression(LightExpression expression) - { - if (expression == null) - throw new ArgumentNullException(nameof(expression)); - - return FromExpression(expression.ToExpression()); - } + public static ExprTree FromLightExpression(LightExpression expression) => + FromExpression((expression ?? throw new ArgumentNullException(nameof(expression))).ToExpression()); /// Reconstructs the flat tree as a System.Linq expression tree. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] - public SysExpr ToExpression() - { - if (Nodes.Count == 0) - throw new InvalidOperationException("Flat expression tree is empty."); - - return new Reader(this).ReadExpression(RootIndex); - } + public SysExpr ToExpression() => + Nodes.Count != 0 + ? new Reader(this).ReadExpression(RootIndex) + : throw new InvalidOperationException("Flat expression tree is empty."); /// Reconstructs the flat tree as a LightExpression tree. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => - AddRawExpressionNode(type, obj, nodeType, CloneChild(child)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child)); - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, params int[] children) => - AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children)); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int child) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(child)); - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children, int data0, int data1 = 0) => - AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children), data0, data1); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int child0, int child1) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(child0), CloneChild(child1)); - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int child0, int child1, int child2) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(child0), CloneChild(child1), CloneChild(child2)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) { var cloned = CloneChildren(children); - return AddRawExpressionNode(type, obj, nodeType, in cloned); + return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); } - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children, int data0, int data1 = 0) + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) { var cloned = CloneChildren(children); - return AddRawExpressionNode(type, obj, nodeType, in cloned, data0, data1); + return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); } - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children) => - AddRawExpressionNode(type, obj, nodeType, CloneChildrenToArray(children)); - - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => - AddRawExpressionNode(type, obj, nodeType, new[] { child }); - - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, params int[] children) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, in ChildList children) + { + var cloned = CloneChildren(children); + return AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, in cloned); + } - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children, int data0, int data1 = 0) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0, 0, 0, data0, data1); + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType) => + AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, 0, 0, 0); private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, in children, 0); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in children); + + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, children); - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children, int data0, int data1 = 0) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, in children, 0, 0, 0, data0, data1); + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int child0, int child1, int child2) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, child0, child1, child2); - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, IEnumerable children) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, children, 0); + private int AddRawLeafExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags = 0, int childIdx = 0, int childCount = 0) => + AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, flags, childIdx, childCount); private int AddRawExpressionNodeWithChildIndex(Type type, object obj, ExpressionType nodeType, int childIdx) => AddRawLeafExpressionNode(type, obj, nodeType, childIdx: childIdx); - private int AddRawLeafExpressionNode(Type type, object obj, ExpressionType nodeType, int childIdx = 0, int childCount = 0, - int data0 = 0, int data1 = 0) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, null, childIdx, childCount, 0, data0, data1); + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child) => + AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child)); private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int child) => - AddRawAuxNode(type, obj, kind, CloneChild(child)); - - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, params int[] children) => - AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children)); + AddFactoryAuxNode(type, obj, kind, 0, child); - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children, int data0, int data1 = 0) => - AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children), data0, data1); + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child0, int child1) => + AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child0), CloneChild(child1)); - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int[] children) { var cloned = CloneChildren(children); - return AddRawAuxNode(type, obj, kind, in cloned); + return AddNode(type, obj, ExpressionType.Extension, kind, 0, in cloned); } - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children, int data0, int data1 = 0) + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, in ChildList children) { var cloned = CloneChildren(children); - return AddRawAuxNode(type, obj, kind, in cloned, data0, data1); + return AddNode(type, obj, ExpressionType.Extension, kind, flags, in cloned); } - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children) => - AddRawAuxNode(type, obj, kind, CloneChildrenToArray(children)); + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => + AddFactoryAuxNode(type, obj, kind, 0, in children); - private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, params int[] children) => - AddNode(type, obj, ExpressionType.Extension, kind, children, 0); + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => + AddNode(type, obj, ExpressionType.Extension, kind, 0, in children); - private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, int[] children, int data0, int data1 = 0) => - AddNode(type, obj, ExpressionType.Extension, kind, children, 0, 0, 0, data0, data1); + private int AddRawLeafAuxNode(Type type, object obj, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0) => + AddLeafNode(type, obj, ExpressionType.Extension, kind, flags, childIdx, childCount); - private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => - AddNode(type, obj, ExpressionType.Extension, kind, in children, 0); + private int AddObjectReferenceNode(Type type, object obj) => + AddRawLeafAuxNode(type, obj, ExprNodeKind.ObjectReference); - private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children, int data0, int data1 = 0) => - AddNode(type, obj, ExpressionType.Extension, kind, in children, 0, 0, 0, data0, data1); + private int AddChildListNode(in ChildList children) => + AddRawAuxNode(null, null, ExprNodeKind.ChildList, in children); - private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, IEnumerable children) => - AddNode(type, obj, ExpressionType.Extension, kind, children, 0); + private int AddUInt16PairNode(int first, int second) => + AddRawLeafAuxNode(null, null, ExprNodeKind.UInt16Pair, childIdx: checked((ushort)first), childCount: checked((ushort)second)); - private int AddRawLeafAuxNode(Type type, object obj, ExprNodeKind kind, int childIdx = 0, int childCount = 0, - int data0 = 0, int data1 = 0) => - AddNode(type, obj, ExpressionType.Extension, kind, null, childIdx, childCount, 0, data0, data1); + private ChildList CreateDebugInfoChildren(int startLine, int startColumn, int endLine, int endColumn) + { + ChildList children = default; + children.Add(AddUInt16PairNode(startLine, startColumn)); + children.Add(AddUInt16PairNode(endLine, endColumn)); + return children; + } - private int AddObjectReferenceNode(Type type, object obj) => - AddRawLeafAuxNode(type, obj, ExprNodeKind.ObjectReference); + private static ChildList PrependToChildList(int first, int[] rest) + { + ChildList children = default; + children.Add(first); + if (rest != null) + for (var i = 0; i < rest.Length; ++i) + children.Add(rest[i]); + return children; + } - private sealed class Builder + private struct Builder { - private readonly Dictionary _parameterIds = new(ReferenceEqComparer.Instance); - private readonly Dictionary _labelIds = new(ReferenceEqComparer.Instance); + private SmallMap16> _parameterIds; + private SmallMap16> _labelIds; private ExprTree _tree; public ExprTree Build(SysExpr expression) @@ -606,12 +649,12 @@ private int AddExpression(SysExpr expression) case ExpressionType.Constant: return AddConstant((System.Linq.Expressions.ConstantExpression)expression); case ExpressionType.Default: - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType); case ExpressionType.Parameter: { var parameter = (SysParameterExpression)expression; return _tree.AddRawLeafExpressionNode(expression.Type, parameter.Name, expression.NodeType, - data0: GetId(_parameterIds, parameter), data1: parameter.IsByRef ? 1 : 0); + parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); } case ExpressionType.Lambda: { @@ -626,11 +669,18 @@ private int AddExpression(SysExpr expression) { var block = (System.Linq.Expressions.BlockExpression)expression; ChildList children = default; - for (var i = 0; i < block.Variables.Count; ++i) - children.Add(AddExpression(block.Variables[i])); + if (block.Variables.Count != 0) + { + ChildList variables = default; + for (var i = 0; i < block.Variables.Count; ++i) + variables.Add(AddExpression(block.Variables[i])); + children.Add(_tree.AddChildListNode(in variables)); + } + ChildList expressions = default; for (var i = 0; i < block.Expressions.Count; ++i) - children.Add(AddExpression(block.Expressions[i])); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children, block.Variables.Count); + expressions.Add(AddExpression(block.Expressions[i])); + children.Add(_tree.AddChildListNode(in expressions)); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children); } case ExpressionType.MemberAccess: { @@ -695,7 +745,7 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(conditional.IfTrue)); children.Add(AddExpression(conditional.IfFalse)); return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, - children); + children[0], children[1], children[2]); } case ExpressionType.Loop: { @@ -706,8 +756,8 @@ private int AddExpression(SysExpr expression) children.Add(AddLabelTarget(loop.BreakLabel)); if (loop.ContinueLabel != null) children.Add(AddLabelTarget(loop.ContinueLabel)); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children, - (loop.BreakLabel != null ? 1 : 0) | (loop.ContinueLabel != null ? 2 : 0)); + return _tree.AddNode(expression.Type, null, expression.NodeType, ExprNodeKind.Expression, + (byte)((loop.BreakLabel != null ? LoopHasBreakFlag : 0) | (loop.ContinueLabel != null ? LoopHasContinueFlag : 0)), in children); } case ExpressionType.Goto: { @@ -734,24 +784,36 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(@switch.SwitchValue)); if (@switch.DefaultBody != null) children.Add(AddExpression(@switch.DefaultBody)); - for (var i = 0; i < @switch.Cases.Count; ++i) - children.Add(AddSwitchCase(@switch.Cases[i])); - return _tree.AddRawExpressionNode(expression.Type, @switch.Comparison, expression.NodeType, children, - @switch.DefaultBody != null ? 1 : 0); + if (@switch.Cases.Count != 0) + { + ChildList cases = default; + for (var i = 0; i < @switch.Cases.Count; ++i) + cases.Add(AddSwitchCase(@switch.Cases[i])); + children.Add(_tree.AddChildListNode(in cases)); + } + return _tree.AddRawExpressionNode(expression.Type, @switch.Comparison, expression.NodeType, in children); } case ExpressionType.Try: { var @try = (System.Linq.Expressions.TryExpression)expression; ChildList children = default; children.Add(AddExpression(@try.Body)); + var flags = (byte)0; if (@try.Fault != null) + { + flags = TryFaultFlag; children.Add(AddExpression(@try.Fault)); + } else if (@try.Finally != null) children.Add(AddExpression(@try.Finally)); - for (var i = 0; i < @try.Handlers.Count; ++i) - children.Add(AddCatchBlock(@try.Handlers[i])); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children, - (@try.Finally != null ? 1 : 0) | (@try.Fault != null ? 2 : 0)); + if (@try.Handlers.Count != 0) + { + ChildList handlers = default; + for (var i = 0; i < @try.Handlers.Count; ++i) + handlers.Add(AddCatchBlock(@try.Handlers[i])); + children.Add(_tree.AddChildListNode(in handlers)); + } + return _tree.AddNode(expression.Type, null, expression.NodeType, ExprNodeKind.Expression, flags, in children); } case ExpressionType.MemberInit: { @@ -800,9 +862,8 @@ private int AddExpression(SysExpr expression) case ExpressionType.DebugInfo: { var debug = (System.Linq.Expressions.DebugInfoExpression)expression; - return _tree.AddRawLeafExpressionNode(expression.Type, debug.Document.FileName, expression.NodeType, - childIdx: checked((ushort)debug.StartColumn), childCount: checked((ushort)debug.EndColumn), - data0: debug.StartLine, data1: debug.EndLine); + return _tree.AddFactoryExpressionNode(expression.Type, debug.Document.FileName, expression.NodeType, + _tree.CreateDebugInfoChildren(debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn)); } default: if (expression is System.Linq.Expressions.UnaryExpression unary) @@ -820,8 +881,8 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(binary.Right)); if (binary.Conversion != null) children.Add(AddExpression(binary.Conversion)); - return _tree.AddRawExpressionNode(expression.Type, binary.Method, expression.NodeType, children, - binary.IsLiftedToNull ? 1 : 0); + return _tree.AddNode(expression.Type, binary.Method, expression.NodeType, ExprNodeKind.Expression, + binary.IsLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, in children); } throw new NotSupportedException($"Flattening of `ExpressionType.{expression.NodeType}` is not supported yet."); @@ -854,12 +915,12 @@ private int AddCatchBlock(SysCatchBlock catchBlock) children.Add(AddExpression(catchBlock.Body)); if (catchBlock.Filter != null) children.Add(AddExpression(catchBlock.Filter)); - return _tree.AddRawAuxNode(catchBlock.Test, null, ExprNodeKind.CatchBlock, children, - (catchBlock.Variable != null ? 1 : 0) | (catchBlock.Filter != null ? 2 : 0)); + return _tree.AddNode(catchBlock.Test, null, ExpressionType.Extension, ExprNodeKind.CatchBlock, + (byte)((catchBlock.Variable != null ? CatchHasVariableFlag : 0) | (catchBlock.Filter != null ? CatchHasFilterFlag : 0)), in children); } private int AddLabelTarget(SysLabelTarget target) => - _tree.AddRawLeafAuxNode(target.Type, target.Name, ExprNodeKind.LabelTarget, data0: GetId(_labelIds, target)); + _tree.AddRawLeafAuxNode(target.Type, target.Name, ExprNodeKind.LabelTarget, childIdx: GetId(ref _labelIds, target)); private int AddMemberBinding(SysMemberBinding binding) { @@ -899,13 +960,11 @@ private int AddElementInit(SysElementInit init) return _tree.AddRawAuxNode(init.AddMethod.DeclaringType, init.AddMethod, ExprNodeKind.ElementInit, children); } - private static int GetId(Dictionary ids, object item) + private static int GetId(ref SmallMap16> ids, object item) { - if (ids.TryGetValue(item, out var id)) - return id; - - id = ids.Count + 1; - ids[item] = id; + ref var id = ref ids.Map.AddOrGetValueRef(item, out var found); + if (!found) + id = ids.Map.Count; return id; } @@ -917,55 +976,72 @@ private static int GetId(Dictionary ids, object item) }; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, IEnumerable children, int childIdx, - int childCount = 0, int nextIdx = 0, int data0 = 0, int data1 = 0) + private int AddLeafNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int childIdx, int childCount) { var nodeIndex = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, childIdx, childCount, nextIdx, data0, data1); - if (children == null) - return nodeIndex; + newNode = new ExprNode(type, obj, nodeType, kind, flags, childIdx, childCount); + return nodeIndex; + } - using var enumerator = children.GetEnumerator(); - if (!enumerator.MoveNext()) - return nodeIndex; + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags); + return nodeIndex; + } - var firstChildIndex = enumerator.Current; - var previousChildIndex = firstChildIndex; - var linkedChildCount = 1; - while (enumerator.MoveNext()) - { - ref var child = ref Nodes[previousChildIndex]; - child.SetNextIdx(enumerator.Current); - previousChildIndex = enumerator.Current; - ++linkedChildCount; - } + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int child0) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, child0, 1); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int child0, int child1) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, child0, 2); + Nodes[child0].SetNextIdx(child1); + return nodeIndex; + } - ref var storedNode = ref Nodes[nodeIndex]; - storedNode.SetChildInfo(firstChildIndex, linkedChildCount); + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int child0, int child1, int child2) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, child0, 3); + Nodes[child0].SetNextIdx(child1); + Nodes[child1].SetNextIdx(child2); return nodeIndex; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, in ChildList children, int childIdx, - int childCount = 0, int nextIdx = 0, int data0 = 0, int data1 = 0) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int[] children) { + if (children == null || children.Length == 0) + return AddNode(type, obj, nodeType, kind, flags); + var nodeIndex = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, childIdx, childCount, nextIdx, data0, data1); + newNode = new ExprNode(type, obj, nodeType, kind, flags, children[0], children.Length); + for (var i = 1; i < children.Length; ++i) + Nodes[children[i - 1]].SetNextIdx(children[i]); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, in ChildList children) + { if (children.Count == 0) - return nodeIndex; + return AddNode(type, obj, nodeType, kind, flags); - var firstChildIndex = children[0]; - var previousChildIndex = firstChildIndex; + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, children[0], children.Count); for (var i = 1; i < children.Count; ++i) - { - ref var child = ref Nodes[previousChildIndex]; - child.SetNextIdx(children[i]); - previousChildIndex = children[i]; - } - - ref var storedNode = ref Nodes[nodeIndex]; - storedNode.SetChildInfo(firstChildIndex, children.Count); + Nodes[children[i - 1]].SetNextIdx(children[i]); return nodeIndex; } @@ -1021,20 +1097,19 @@ private int CloneChild(int index) { ref var node = ref Nodes[index]; return ShouldCloneWhenLinking(node) - ? AddNode(node.Type, node.Obj, node.NodeType, node.Kind, null, node.ChildIdx, node.ChildCount, 0, node.Data0, node.Data1) + ? AddLeafNode(node.Type, node.Obj, node.NodeType, node.Kind, node.Flags, node.ChildIdx, node.ChildCount) : index; } - private int[] CloneChildrenToArray(IEnumerable children) + private ChildList CloneChildren(int[] children) { - if (children == null) - return Array.Empty(); - ChildList cloned = default; - foreach (var child in children) - cloned.Add(CloneChild(child)); + if (children == null) + return cloned; - return CopyChildrenToArray(cloned); + for (var i = 0; i < children.Length; ++i) + cloned.Add(CloneChild(children[i])); + return cloned; } private ChildList CloneChildren(in ChildList children) @@ -1045,17 +1120,6 @@ private ChildList CloneChildren(in ChildList children) return cloned; } - private static int[] CopyChildrenToArray(in ChildList children) - { - if (children.Count == 0) - return Array.Empty(); - - var array = new int[children.Count]; - for (var i = 0; i < children.Count; ++i) - array[i] = children[i]; - return array; - } - // Any leaf reused in more than one parent would have its intrusive sibling link (`NextIdx`) overwritten. // Clone the leaf before linking so the direct builder API may safely reuse returned indexes for // parameters, labels, constants, default values and parameterless `new` expressions. @@ -1063,38 +1127,21 @@ private static bool ShouldCloneWhenLinking(in ExprNode node) => node.Kind == ExprNodeKind.LabelTarget || node.NodeType == ExpressionType.Parameter || node.Kind == ExprNodeKind.ObjectReference || - !HasLinkedChildren(node); + node.ChildCount == 0; - private static bool HasLinkedChildren(in ExprNode node) => - node.NodeType != ExpressionType.DebugInfo && node.ChildCount != 0; + private static bool HasFlag(in ExprNode node, byte flag) => (node.Flags & flag) != 0; - private static IEnumerable Single(int item) - { - yield return item; - } - - private static int[] Prepend(int first, int[] rest) - { - if (rest == null || rest.Length == 0) - return new[] { first }; - - var items = new int[rest.Length + 1]; - items[0] = first; - Array.Copy(rest, 0, items, 1, rest.Length); - return items; - } - - private readonly struct Reader + private struct Reader { private readonly ExprTree _tree; - private readonly Dictionary _parametersById; - private readonly Dictionary _labelsById; + private SmallMap16 _parametersById; + private SmallMap16 _labelsById; public Reader(ExprTree tree) { _tree = tree; - _parametersById = new Dictionary(); - _labelsById = new Dictionary(); + _parametersById = default; + _labelsById = default; } [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] @@ -1114,13 +1161,12 @@ public SysExpr ReadExpression(int index) return SysExpr.Default(node.Type); case ExpressionType.Parameter: { - if (_parametersById.TryGetValue(node.Data0, out var parameter)) + ref var parameter = ref _parametersById.Map.AddOrGetValueRef(node.ChildIdx, out var found); + if (found) return parameter; - var parameterType = node.Data1 != 0 && !node.Type.IsByRef ? node.Type.MakeByRefType() : node.Type; - parameter = SysExpr.Parameter(parameterType, (string)node.Obj); - _parametersById[node.Data0] = parameter; - return parameter; + var parameterType = HasFlag(node, ParameterByRefFlag) && !node.Type.IsByRef ? node.Type.MakeByRefType() : node.Type; + return parameter = SysExpr.Parameter(parameterType, (string)node.Obj); } case ExpressionType.Lambda: { @@ -1134,12 +1180,15 @@ public SysExpr ReadExpression(int index) case ExpressionType.Block: { var children = GetChildren(index); - var variables = new SysParameterExpression[node.Data0]; + var hasVariables = children.Count == 2; + var variableIndexes = hasVariables ? GetChildren(children[0]) : default; + var expressionIndexes = GetChildren(children[children.Count - 1]); + var variables = new SysParameterExpression[variableIndexes.Count]; for (var i = 0; i < variables.Length; ++i) - variables[i] = (SysParameterExpression)ReadExpression(children[i]); - var expressions = new SysExpr[children.Count - node.Data0]; - for (var i = node.Data0; i < children.Count; ++i) - expressions[i - node.Data0] = ReadExpression(children[i]); + variables[i] = (SysParameterExpression)ReadExpression(variableIndexes[i]); + var expressions = new SysExpr[expressionIndexes.Count]; + for (var i = 0; i < expressions.Length; ++i) + expressions[i] = ReadExpression(expressionIndexes[i]); return SysExpr.Block(node.Type, variables, expressions); } case ExpressionType.MemberAccess: @@ -1200,8 +1249,8 @@ public SysExpr ReadExpression(int index) { var children = GetChildren(index); var childIndex = 1; - var breakLabel = (node.Data0 & 1) != 0 ? ReadLabelTarget(children[childIndex++]) : null; - var continueLabel = (node.Data0 & 2) != 0 ? ReadLabelTarget(children[childIndex]) : null; + var breakLabel = HasFlag(node, LoopHasBreakFlag) ? ReadLabelTarget(children[childIndex++]) : null; + var continueLabel = HasFlag(node, LoopHasContinueFlag) ? ReadLabelTarget(children[childIndex]) : null; return SysExpr.Loop(ReadExpression(children[0]), breakLabel, continueLabel); } case ExpressionType.Goto: @@ -1219,24 +1268,46 @@ public SysExpr ReadExpression(int index) case ExpressionType.Switch: { var children = GetChildren(index); - var childIndex = 1; - var defaultBody = (node.Data0 & 1) != 0 ? ReadExpression(children[childIndex++]) : null; - var cases = new SysSwitchCase[children.Count - childIndex]; - for (var i = childIndex; i < children.Count; ++i) - cases[i - childIndex] = ReadSwitchCase(children[i]); + var defaultBody = default(SysExpr); + ChildList caseIndexes = default; + if (children.Count > 1) + { + ref var lastChild = ref _tree.Nodes[children[children.Count - 1]]; + if (lastChild.Kind == ExprNodeKind.ChildList) + { + caseIndexes = GetChildren(children[children.Count - 1]); + if (children.Count == 3) + defaultBody = ReadExpression(children[1]); + } + else + defaultBody = ReadExpression(children[1]); + } + var cases = new SysSwitchCase[caseIndexes.Count]; + for (var i = 0; i < cases.Length; ++i) + cases[i] = ReadSwitchCase(caseIndexes[i]); return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, (System.Reflection.MethodInfo)node.Obj, cases); } case ExpressionType.Try: { var children = GetChildren(index); - var childIndex = 1; - if ((node.Data0 & 2) != 0) + if (HasFlag(node, TryFaultFlag)) return SysExpr.TryFault(ReadExpression(children[0]), ReadExpression(children[1])); - var @finally = (node.Data0 & 1) != 0 ? ReadExpression(children[childIndex++]) : null; - var handlers = new SysCatchBlock[children.Count - childIndex]; - for (var i = childIndex; i < children.Count; ++i) - handlers[i - childIndex] = ReadCatchBlock(children[i]); + var handlers = default(SysCatchBlock[]); + var lastChildIsHandlerList = children.Count > 1 && _tree.Nodes[children[children.Count - 1]].Kind == ExprNodeKind.ChildList; + if (lastChildIsHandlerList) + { + var handlerIndexes = GetChildren(children[children.Count - 1]); + handlers = new SysCatchBlock[handlerIndexes.Count]; + for (var i = 0; i < handlers.Length; ++i) + handlers[i] = ReadCatchBlock(handlerIndexes[i]); + } + else + handlers = Array.Empty(); + + var @finally = children.Count > 1 && (!lastChildIsHandlerList || children.Count == 3) + ? ReadExpression(children[1]) + : null; return SysExpr.TryCatchFinally(ReadExpression(children[0]), @finally, handlers); } case ExpressionType.MemberInit: @@ -1278,8 +1349,10 @@ public SysExpr ReadExpression(int index) } case ExpressionType.DebugInfo: { - return SysExpr.DebugInfo(SysExpr.SymbolDocument((string)node.Obj), - node.Data0, node.ChildIdx, node.Data1, node.ChildCount); + var children = GetChildren(index); + ReadUInt16Pair(children[0], out var startLine, out var startColumn); + ReadUInt16Pair(children[1], out var endLine, out var endColumn); + return SysExpr.DebugInfo(SysExpr.SymbolDocument((string)node.Obj), startLine, startColumn, endLine, endColumn); } default: if (node.ChildCount == 1) @@ -1293,7 +1366,7 @@ public SysExpr ReadExpression(int index) var children = GetChildren(index); var conversion = children.Count > 2 ? (System.Linq.Expressions.LambdaExpression)ReadExpression(children[2]) : null; return SysExpr.MakeBinary(node.NodeType, ReadExpression(children[0]), ReadExpression(children[1]), - node.Data0 != 0, (System.Reflection.MethodInfo)node.Obj, conversion); + HasFlag(node, BinaryLiftedToNullFlag), (System.Reflection.MethodInfo)node.Obj, conversion); } throw new NotSupportedException($"Reconstruction of `ExpressionType.{node.NodeType}` is not supported yet."); @@ -1319,9 +1392,9 @@ private SysCatchBlock ReadCatchBlock(int index) Debug.Assert(node.Kind == ExprNodeKind.CatchBlock); var children = GetChildren(index); var childIndex = 0; - var variable = (node.Data0 & 1) != 0 ? (SysParameterExpression)ReadExpression(children[childIndex++]) : null; + var variable = HasFlag(node, CatchHasVariableFlag) ? (SysParameterExpression)ReadExpression(children[childIndex++]) : null; var body = ReadExpression(children[childIndex++]); - var filter = (node.Data0 & 2) != 0 ? ReadExpression(children[childIndex]) : null; + var filter = HasFlag(node, CatchHasFilterFlag) ? ReadExpression(children[childIndex]) : null; return SysExpr.MakeCatchBlock(node.Type, variable, body, filter); } @@ -1329,12 +1402,11 @@ private SysLabelTarget ReadLabelTarget(int index) { ref var node = ref _tree.Nodes[index]; Debug.Assert(node.Kind == ExprNodeKind.LabelTarget); - if (_labelsById.TryGetValue(node.Data0, out var label)) + ref var label = ref _labelsById.Map.AddOrGetValueRef(node.ChildIdx, out var found); + if (found) return label; - label = SysExpr.Label(node.Type, (string)node.Obj); - _labelsById[node.Data0] = label; - return label; + return label = SysExpr.Label(node.Type, (string)node.Obj); } private object ReadObjectReference(int index) @@ -1344,6 +1416,14 @@ private object ReadObjectReference(int index) return node.Obj; } + private void ReadUInt16Pair(int index, out int first, out int second) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Kind == ExprNodeKind.UInt16Pair); + first = node.ChildIdx; + second = node.ChildCount; + } + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] private SysMemberBinding ReadMemberBinding(int index) { @@ -1411,12 +1491,6 @@ private SysExpr[] ReadExpressions(in ChildList childIndexes) private static System.Linq.Expressions.NewExpression CreateValueTypeNewExpression(Type type) => SysExpr.New(type); } - private sealed class ReferenceEqComparer : IEqualityComparer - { - public static readonly ReferenceEqComparer Instance = new(); - public new bool Equals(object x, object y) => ReferenceEquals(x, y); - public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); - } } /// Provides conversions from System and LightExpression trees to . From 5f645b2758ea9599b59658dce536ecc7e0066b5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:34:17 +0000 Subject: [PATCH 10/15] docs: clarify flat expression node layout helpers Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/f2a1578b-aaf8-4465-9e1f-55f5199c16e4 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 91a890d0..9ff382e4 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -41,11 +41,11 @@ public enum ExprNodeKind : byte ObjectReference, /// Represents an internal child-list metadata node. ChildList, - /// Represents an internal pair of ushort values. + /// Represents an internal pair of UInt16 values. UInt16Pair, } -/// Stores one flat expression node plus its intrusive child-link metadata. +/// Stores one flat expression node plus its intrusive child-link metadata in 24 bytes on 64-bit runtimes. [StructLayout(LayoutKind.Explicit, Size = 24)] public struct ExprNode { @@ -630,6 +630,7 @@ private static ChildList PrependToChildList(int first, int[] rest) return children; } + /// Builds the flat representation while preserving parameter and label identity with stack-friendly maps. private struct Builder { private SmallMap16> _parameterIds; @@ -1131,6 +1132,7 @@ private static bool ShouldCloneWhenLinking(in ExprNode node) => private static bool HasFlag(in ExprNode node, byte flag) => (node.Flags & flag) != 0; + /// Reconstructs System.Linq nodes from the flat representation while reusing parameter and label identities. private struct Reader { private readonly ExprTree _tree; From 88fc048cb0a14c261bf85179cfc61ffa16987ad3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:25:31 +0000 Subject: [PATCH 11/15] perf: tighten flat expression node helpers Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/d56dd094-3b5e-41e0-a3c3-80c72ae88fdd Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 178 +++++++++++++----- 1 file changed, 127 insertions(+), 51 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 9ff382e4..52654ef8 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -55,6 +55,11 @@ public struct ExprNode private const int CountShift = 16; private const ulong IndexMask = 0xFFFF; private const ulong KindMask = 0x0F; + private const ulong NextMask = IndexMask << NextShift; + private const ulong ChildCountMask = IndexMask << CountShift; + private const ulong ChildInfoMask = ChildCountMask | IndexMask; + private const ulong KeepWithoutNextMask = ~NextMask; + private const ulong KeepWithoutChildInfoMask = ~ChildInfoMask; private const int FlagsShift = 4; /// Gets or sets the runtime type of the represented node. @@ -96,13 +101,28 @@ internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind k | (ushort)childIdx; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetNextIdx(int nextIdx) => - _data = (_data & ~(IndexMask << NextShift)) | ((ulong)(ushort)nextIdx << NextShift); + _data = (_data & KeepWithoutNextMask) | ((ulong)(ushort)nextIdx << NextShift); + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetChildInfo(int childIdx, int childCount) => - _data = (_data & ~((IndexMask << CountShift) | IndexMask)) + _data = (_data & KeepWithoutChildInfoMask) | ((ulong)(ushort)childCount << CountShift) | (ushort)childIdx; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool Is(ExprNodeKind kind) => Kind == kind; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool IsExpression() => Kind == ExprNodeKind.Expression; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool HasFlag(byte flag) => (Flags & flag) != 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool ShouldCloneWhenLinked() => + Kind == ExprNodeKind.LabelTarget || NodeType == ExpressionType.Parameter || Kind == ExprNodeKind.ObjectReference || ChildCount == 0; } /// Stores an expression tree as a flat node array plus out-of-line closure constants. @@ -531,14 +551,38 @@ private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeT private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int child) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(child)); - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int child0, int child1) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(child0), CloneChild(child1)); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3)); - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int child0, int child1, int child2) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(child0), CloneChild(child1), CloneChild(child2)); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4, int c5) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4), CloneChild(c5)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4, int c5, int c6) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4), CloneChild(c5), CloneChild(c6)); private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) { + if (children != null) + switch (children.Length) + { + case 1: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0]); + case 2: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1]); + case 3: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2]); + case 4: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3]); + case 5: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4]); + case 6: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4], children[5]); + case 7: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4], children[5], children[6]); + } + var cloned = CloneChildren(children); return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); } @@ -1001,22 +1045,72 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind return nodeIndex; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int child0, int child1) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1) { var nodeIndex = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, child0, 2); - Nodes[child0].SetNextIdx(child1); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 2); + Nodes[c0].SetNextIdx(c1); return nodeIndex; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int child0, int child1, int child2) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2) { var nodeIndex = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, child0, 3); - Nodes[child0].SetNextIdx(child1); - Nodes[child1].SetNextIdx(child2); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 3); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 4); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + Nodes[c2].SetNextIdx(c3); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 5); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + Nodes[c2].SetNextIdx(c3); + Nodes[c3].SetNextIdx(c4); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4, int c5) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 6); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + Nodes[c2].SetNextIdx(c3); + Nodes[c3].SetNextIdx(c4); + Nodes[c4].SetNextIdx(c5); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4, int c5, int c6) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 7); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + Nodes[c2].SetNextIdx(c3); + Nodes[c3].SetNextIdx(c4); + Nodes[c4].SetNextIdx(c5); + Nodes[c5].SetNextIdx(c6); return nodeIndex; } @@ -1046,16 +1140,9 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind return nodeIndex; } - private static bool ShouldInlineConstant(object value, Type type) - { - if (value == null || value is string || value is Type) - return true; - - if (type.IsEnum) - return true; - - return Type.GetTypeCode(type) != TypeCode.Object; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ShouldInlineConstant(object value, Type type) => + value == null || value is string || value is Type || type.IsEnum || Type.GetTypeCode(type) != TypeCode.Object; private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch { @@ -1097,7 +1184,7 @@ private static Type GetArrayElementType(Type arrayType, int depth) private int CloneChild(int index) { ref var node = ref Nodes[index]; - return ShouldCloneWhenLinking(node) + return node.ShouldCloneWhenLinked() ? AddLeafNode(node.Type, node.Obj, node.NodeType, node.Kind, node.Flags, node.ChildIdx, node.ChildCount) : index; } @@ -1121,17 +1208,6 @@ private ChildList CloneChildren(in ChildList children) return cloned; } - // Any leaf reused in more than one parent would have its intrusive sibling link (`NextIdx`) overwritten. - // Clone the leaf before linking so the direct builder API may safely reuse returned indexes for - // parameters, labels, constants, default values and parameterless `new` expressions. - private static bool ShouldCloneWhenLinking(in ExprNode node) => - node.Kind == ExprNodeKind.LabelTarget || - node.NodeType == ExpressionType.Parameter || - node.Kind == ExprNodeKind.ObjectReference || - node.ChildCount == 0; - - private static bool HasFlag(in ExprNode node, byte flag) => (node.Flags & flag) != 0; - /// Reconstructs System.Linq nodes from the flat representation while reusing parameter and label identities. private struct Reader { @@ -1150,7 +1226,7 @@ public Reader(ExprTree tree) public SysExpr ReadExpression(int index) { ref var node = ref _tree.Nodes[index]; - if (node.Kind != ExprNodeKind.Expression) + if (!node.IsExpression()) throw new InvalidOperationException($"Node at index {index} is not an expression node."); switch (node.NodeType) @@ -1167,7 +1243,7 @@ public SysExpr ReadExpression(int index) if (found) return parameter; - var parameterType = HasFlag(node, ParameterByRefFlag) && !node.Type.IsByRef ? node.Type.MakeByRefType() : node.Type; + var parameterType = node.HasFlag(ParameterByRefFlag) && !node.Type.IsByRef ? node.Type.MakeByRefType() : node.Type; return parameter = SysExpr.Parameter(parameterType, (string)node.Obj); } case ExpressionType.Lambda: @@ -1251,8 +1327,8 @@ public SysExpr ReadExpression(int index) { var children = GetChildren(index); var childIndex = 1; - var breakLabel = HasFlag(node, LoopHasBreakFlag) ? ReadLabelTarget(children[childIndex++]) : null; - var continueLabel = HasFlag(node, LoopHasContinueFlag) ? ReadLabelTarget(children[childIndex]) : null; + var breakLabel = node.HasFlag(LoopHasBreakFlag) ? ReadLabelTarget(children[childIndex++]) : null; + var continueLabel = node.HasFlag(LoopHasContinueFlag) ? ReadLabelTarget(children[childIndex]) : null; return SysExpr.Loop(ReadExpression(children[0]), breakLabel, continueLabel); } case ExpressionType.Goto: @@ -1275,7 +1351,7 @@ public SysExpr ReadExpression(int index) if (children.Count > 1) { ref var lastChild = ref _tree.Nodes[children[children.Count - 1]]; - if (lastChild.Kind == ExprNodeKind.ChildList) + if (lastChild.Is(ExprNodeKind.ChildList)) { caseIndexes = GetChildren(children[children.Count - 1]); if (children.Count == 3) @@ -1292,11 +1368,11 @@ public SysExpr ReadExpression(int index) case ExpressionType.Try: { var children = GetChildren(index); - if (HasFlag(node, TryFaultFlag)) + if (node.HasFlag(TryFaultFlag)) return SysExpr.TryFault(ReadExpression(children[0]), ReadExpression(children[1])); var handlers = default(SysCatchBlock[]); - var lastChildIsHandlerList = children.Count > 1 && _tree.Nodes[children[children.Count - 1]].Kind == ExprNodeKind.ChildList; + var lastChildIsHandlerList = children.Count > 1 && _tree.Nodes[children[children.Count - 1]].Is(ExprNodeKind.ChildList); if (lastChildIsHandlerList) { var handlerIndexes = GetChildren(children[children.Count - 1]); @@ -1368,7 +1444,7 @@ public SysExpr ReadExpression(int index) var children = GetChildren(index); var conversion = children.Count > 2 ? (System.Linq.Expressions.LambdaExpression)ReadExpression(children[2]) : null; return SysExpr.MakeBinary(node.NodeType, ReadExpression(children[0]), ReadExpression(children[1]), - HasFlag(node, BinaryLiftedToNullFlag), (System.Reflection.MethodInfo)node.Obj, conversion); + node.HasFlag(BinaryLiftedToNullFlag), (System.Reflection.MethodInfo)node.Obj, conversion); } throw new NotSupportedException($"Reconstruction of `ExpressionType.{node.NodeType}` is not supported yet."); @@ -1379,7 +1455,7 @@ public SysExpr ReadExpression(int index) private SysSwitchCase ReadSwitchCase(int index) { ref var node = ref _tree.Nodes[index]; - Debug.Assert(node.Kind == ExprNodeKind.SwitchCase); + Debug.Assert(node.Is(ExprNodeKind.SwitchCase)); var children = GetChildren(index); var testValues = new SysExpr[children.Count - 1]; for (var i = 0; i < testValues.Length; ++i) @@ -1391,19 +1467,19 @@ private SysSwitchCase ReadSwitchCase(int index) private SysCatchBlock ReadCatchBlock(int index) { ref var node = ref _tree.Nodes[index]; - Debug.Assert(node.Kind == ExprNodeKind.CatchBlock); + Debug.Assert(node.Is(ExprNodeKind.CatchBlock)); var children = GetChildren(index); var childIndex = 0; - var variable = HasFlag(node, CatchHasVariableFlag) ? (SysParameterExpression)ReadExpression(children[childIndex++]) : null; + var variable = node.HasFlag(CatchHasVariableFlag) ? (SysParameterExpression)ReadExpression(children[childIndex++]) : null; var body = ReadExpression(children[childIndex++]); - var filter = HasFlag(node, CatchHasFilterFlag) ? ReadExpression(children[childIndex]) : null; + var filter = node.HasFlag(CatchHasFilterFlag) ? ReadExpression(children[childIndex]) : null; return SysExpr.MakeCatchBlock(node.Type, variable, body, filter); } private SysLabelTarget ReadLabelTarget(int index) { ref var node = ref _tree.Nodes[index]; - Debug.Assert(node.Kind == ExprNodeKind.LabelTarget); + Debug.Assert(node.Is(ExprNodeKind.LabelTarget)); ref var label = ref _labelsById.Map.AddOrGetValueRef(node.ChildIdx, out var found); if (found) return label; @@ -1414,14 +1490,14 @@ private SysLabelTarget ReadLabelTarget(int index) private object ReadObjectReference(int index) { ref var node = ref _tree.Nodes[index]; - Debug.Assert(node.Kind == ExprNodeKind.ObjectReference); + Debug.Assert(node.Is(ExprNodeKind.ObjectReference)); return node.Obj; } private void ReadUInt16Pair(int index, out int first, out int second) { ref var node = ref _tree.Nodes[index]; - Debug.Assert(node.Kind == ExprNodeKind.UInt16Pair); + Debug.Assert(node.Is(ExprNodeKind.UInt16Pair)); first = node.ChildIdx; second = node.ChildCount; } @@ -1460,7 +1536,7 @@ private SysMemberBinding ReadMemberBinding(int index) private SysElementInit ReadElementInit(int index) { ref var node = ref _tree.Nodes[index]; - Debug.Assert(node.Kind == ExprNodeKind.ElementInit); + Debug.Assert(node.Is(ExprNodeKind.ElementInit)); return SysExpr.ElementInit((System.Reflection.MethodInfo)node.Obj, ReadExpressions(GetChildren(index))); } From 0b9fa7e4d94622270111a3c0e7adec85e4c2fd75 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:02:49 +0000 Subject: [PATCH 12/15] test: add light vs flat creation benchmark Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/ed01d5a9-8337-4a93-a276-4c52a481fc65 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../LightExprVsFlatExpr_Create_ComplexExpr.cs | 21 ++++++++++ .../Program.cs | 1 + .../LightExpressionTests.cs | 41 +++++++++++-------- 3 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs diff --git a/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs b/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs new file mode 100644 index 00000000..26283414 --- /dev/null +++ b/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs @@ -0,0 +1,21 @@ +using BenchmarkDotNet.Attributes; +using FastExpressionCompiler.FlatExpression; +using FastExpressionCompiler.LightExpression.UnitTests; + +namespace FastExpressionCompiler.Benchmarks +{ + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] + public class LightExprVsFlatExpr_Create_ComplexExpr + { + private FastExpressionCompiler.LightExpression.Expression> _lightExpr; + private ExprTree _flatExpr; + + [Benchmark(Baseline = true)] + public void Create_LightExpression() => + _lightExpr = LightExpressionTests.CreateComplexLightExpression(); + + [Benchmark] + public void Create_FlatExpression() => + _flatExpr = LightExpressionTests.CreateComplexFlatExpression(); + } +} diff --git a/test/FastExpressionCompiler.Benchmarks/Program.cs b/test/FastExpressionCompiler.Benchmarks/Program.cs index b00d5c60..d6b8cc09 100644 --- a/test/FastExpressionCompiler.Benchmarks/Program.cs +++ b/test/FastExpressionCompiler.Benchmarks/Program.cs @@ -21,6 +21,7 @@ public static void Main() // BenchmarkRunner.Run(); // not included in README.md, may be it needs to // BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); //-------------------------------------------- diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 37bf7ea6..f56429eb 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -236,6 +236,28 @@ public static Expression> CreateComplexLightExpression_wi return expr; } + public static ExprTree CreateComplexFlatExpression(string p = null) + { + var fe = default(ExprTree); + var stateParamExpr = fe.ParameterOf(p); + var body = fe.MemberInit( + fe.New(_ctorOfA, + fe.New(_ctorOfB), + fe.Convert( + fe.ArrayIndex(stateParamExpr, fe.ConstantInt(11)), + typeof(string)), + fe.NewArrayInit(typeof(ID), + fe.New(_ctorOfD1), + fe.New(_ctorOfD2))), + fe.Bind(_propAProp, + fe.New(_ctorOfP, + fe.New(_ctorOfB))), + fe.Bind(_fieldABop, + fe.New(_ctorOfB))); + fe.RootIndex = fe.Lambda>(body, stateParamExpr); + return fe; + } + public void Can_compile_complex_expr_with_Arrays_and_Casts() { @@ -430,24 +452,7 @@ public void Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expres public void Can_build_flat_expression_directly_with_light_expression_like_api() { - var fe = default(ExprTree); - var state = fe.ParameterOf("state"); - var body = fe.MemberInit( - fe.New(_ctorOfA, - fe.New(_ctorOfB), - fe.Convert( - fe.ArrayIndex(state, fe.ConstantInt(11)), - typeof(string)), - fe.NewArrayInit(typeof(ID), - fe.New(_ctorOfD1), - fe.New(_ctorOfD2))), - fe.Bind(_propAProp, - fe.New(_ctorOfP, - fe.New(_ctorOfB))), - fe.Bind(_fieldABop, - fe.New(_ctorOfB))); - fe.RootIndex = fe.Lambda>(body, state); - + var fe = CreateComplexFlatExpression("state"); var lambda = (LambdaExpression)fe.ToLightExpression(); var func = lambda.CompileFast>(true); var runtimeState = new object[12]; From f4bd9e906c77209349dc0db0bb2b68bc42128156 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:07:58 +0000 Subject: [PATCH 13/15] test: polish flat creation benchmark helper Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/ed01d5a9-8337-4a93-a276-4c52a481fc65 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../LightExprVsFlatExpr_Create_ComplexExpr.cs | 1 + .../LightExpressionTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs b/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs index 26283414..525ad531 100644 --- a/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs +++ b/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs @@ -7,6 +7,7 @@ namespace FastExpressionCompiler.Benchmarks [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class LightExprVsFlatExpr_Create_ComplexExpr { + // Keep the created values reachable so the construction work is not elided. private FastExpressionCompiler.LightExpression.Expression> _lightExpr; private ExprTree _flatExpr; diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index f56429eb..5144e029 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -236,10 +236,10 @@ public static Expression> CreateComplexLightExpression_wi return expr; } - public static ExprTree CreateComplexFlatExpression(string p = null) + public static ExprTree CreateComplexFlatExpression(string parameterName = null) { var fe = default(ExprTree); - var stateParamExpr = fe.ParameterOf(p); + var stateParamExpr = fe.ParameterOf(parameterName); var body = fe.MemberInit( fe.New(_ctorOfA, fe.New(_ctorOfB), From 1ebd6e2670cf2d9e54863f9d78e8fbe3885522c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:27:32 +0000 Subject: [PATCH 14/15] test: add deterministic flat expression property checks Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/b03ffb96-068c-4255-bd58-3ca2f3ef1298 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- ...nCompiler.LightExpression.UnitTests.csproj | 4 + .../LightExpressionPropertyTests.cs | 314 ++++++++++++++++++ .../LightExpressionTests.cs | 5 +- 3 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj index fe1279f1..42ebb322 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj @@ -19,4 +19,8 @@ + + + + diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs new file mode 100644 index 00000000..9432a6b6 --- /dev/null +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using FastExpressionCompiler.FlatExpression; +using static FastExpressionCompiler.LightExpression.Expression; +#if NET8_0_OR_GREATER +using CsCheck; +#endif + +namespace FastExpressionCompiler.LightExpression.UnitTests; + +public partial class LightExpressionTests +{ +#if NET8_0_OR_GREATER + public void Can_property_test_generated_flat_expression_roundtrip_structurally() + { + Gen.Int[0, int.MaxValue] + .Select(seed => new GeneratedCase(seed, GeneratedIntSpecFactory.Create(seed, maxDepth: 3, maxBreadth: 3))) + .Sample(testCase => + GeneratedExpressionComparer.AreEqual( + CreateGeneratedLightExpression(testCase.Spec), + CreateGeneratedFlatExpression(testCase.Spec).ToLightExpression()), + iter: 100, + threads: 1, + seed: "0N0XIzNsQ0O2", + print: testCase => $"{testCase.Seed}: {testCase.Spec}"); + } + + private static FastExpressionCompiler.LightExpression.Expression> CreateGeneratedLightExpression(IntSpec spec) + { + var parameter = ParameterOf("p"); + return Lambda>(BuildLightInt(spec, [parameter]), parameter); + } + + private static ExprTree CreateGeneratedFlatExpression(IntSpec spec) + { + var fe = default(ExprTree); + var parameter = fe.ParameterOf("p"); + fe.RootIndex = fe.Lambda>(BuildFlatInt(ref fe, spec, [parameter]), parameter); + return fe; + } + + private static FastExpressionCompiler.LightExpression.Expression BuildLightInt(IntSpec spec, FastExpressionCompiler.LightExpression.ParameterExpression[] ints) => + spec switch + { + IntSpec.ParameterRef parameter => ints[parameter.Index], + IntSpec.Constant constant => Constant(constant.Value), + IntSpec.Add add => Add(BuildLightInt(add.Left, ints), BuildLightInt(add.Right, ints)), + IntSpec.Subtract subtract => Subtract(BuildLightInt(subtract.Left, ints), BuildLightInt(subtract.Right, ints)), + IntSpec.Multiply multiply => Multiply(BuildLightInt(multiply.Left, ints), BuildLightInt(multiply.Right, ints)), + IntSpec.Conditional conditional => Condition( + BuildLightBool(conditional.Test, ints), + BuildLightInt(conditional.IfTrue, ints), + BuildLightInt(conditional.IfFalse, ints)), + IntSpec.LetMany letMany => BuildLightBlock(letMany, ints), + _ => throw new NotSupportedException(spec.GetType().Name) + }; + + private static int BuildFlatInt(ref ExprTree fe, IntSpec spec, int[] ints) => + spec switch + { + IntSpec.ParameterRef parameter => ints[parameter.Index], + IntSpec.Constant constant => fe.ConstantInt(constant.Value), + IntSpec.Add add => fe.Add(BuildFlatInt(ref fe, add.Left, ints), BuildFlatInt(ref fe, add.Right, ints)), + IntSpec.Subtract subtract => fe.MakeBinary(ExpressionType.Subtract, + BuildFlatInt(ref fe, subtract.Left, ints), BuildFlatInt(ref fe, subtract.Right, ints)), + IntSpec.Multiply multiply => fe.MakeBinary(ExpressionType.Multiply, + BuildFlatInt(ref fe, multiply.Left, ints), BuildFlatInt(ref fe, multiply.Right, ints)), + IntSpec.Conditional conditional => fe.Condition( + BuildFlatBool(ref fe, conditional.Test, ints), + BuildFlatInt(ref fe, conditional.IfTrue, ints), + BuildFlatInt(ref fe, conditional.IfFalse, ints)), + IntSpec.LetMany letMany => BuildFlatBlock(ref fe, letMany, ints), + _ => throw new NotSupportedException(spec.GetType().Name) + }; + + private static FastExpressionCompiler.LightExpression.Expression BuildLightBool(BoolSpec spec, FastExpressionCompiler.LightExpression.ParameterExpression[] ints) => + spec switch + { + BoolSpec.Constant constant => Constant(constant.Value), + BoolSpec.Not not => Not(BuildLightBool(not.Operand, ints)), + BoolSpec.Equal equal => Equal(BuildLightInt(equal.Left, ints), BuildLightInt(equal.Right, ints)), + BoolSpec.GreaterThan greaterThan => GreaterThan(BuildLightInt(greaterThan.Left, ints), BuildLightInt(greaterThan.Right, ints)), + BoolSpec.AndAlso andAlso => AndAlso(BuildLightBool(andAlso.Left, ints), BuildLightBool(andAlso.Right, ints)), + BoolSpec.OrElse orElse => OrElse(BuildLightBool(orElse.Left, ints), BuildLightBool(orElse.Right, ints)), + _ => throw new NotSupportedException(spec.GetType().Name) + }; + + private static int BuildFlatBool(ref ExprTree fe, BoolSpec spec, int[] ints) => + spec switch + { + BoolSpec.Constant constant => fe.ConstantOf(constant.Value), + BoolSpec.Not not => fe.Not(BuildFlatBool(ref fe, not.Operand, ints)), + BoolSpec.Equal equal => fe.Equal(BuildFlatInt(ref fe, equal.Left, ints), BuildFlatInt(ref fe, equal.Right, ints)), + BoolSpec.GreaterThan greaterThan => fe.MakeBinary(ExpressionType.GreaterThan, + BuildFlatInt(ref fe, greaterThan.Left, ints), BuildFlatInt(ref fe, greaterThan.Right, ints)), + BoolSpec.AndAlso andAlso => fe.MakeBinary(ExpressionType.AndAlso, + BuildFlatBool(ref fe, andAlso.Left, ints), BuildFlatBool(ref fe, andAlso.Right, ints)), + BoolSpec.OrElse orElse => fe.MakeBinary(ExpressionType.OrElse, + BuildFlatBool(ref fe, orElse.Left, ints), BuildFlatBool(ref fe, orElse.Right, ints)), + _ => throw new NotSupportedException(spec.GetType().Name) + }; + + private static FastExpressionCompiler.LightExpression.Expression BuildLightBlock(IntSpec.LetMany letMany, FastExpressionCompiler.LightExpression.ParameterExpression[] ints) + { + var locals = new FastExpressionCompiler.LightExpression.ParameterExpression[letMany.Values.Length]; + var expressions = new FastExpressionCompiler.LightExpression.Expression[letMany.Values.Length + 1]; + for (var i = 0; i < locals.Length; ++i) + { + locals[i] = Variable(typeof(int), "v" + i); + expressions[i] = Assign(locals[i], BuildLightInt(letMany.Values[i], ints)); + } + + expressions[locals.Length] = BuildLightInt(letMany.Body, Append(ints, locals)); + return Block(locals, expressions); + } + + private static int BuildFlatBlock(ref ExprTree fe, IntSpec.LetMany letMany, int[] ints) + { + var locals = new int[letMany.Values.Length]; + var expressions = new int[letMany.Values.Length + 1]; + for (var i = 0; i < locals.Length; ++i) + { + locals[i] = fe.Variable(typeof(int), "v" + i); + expressions[i] = fe.Assign(locals[i], BuildFlatInt(ref fe, letMany.Values[i], ints)); + } + + expressions[locals.Length] = BuildFlatInt(ref fe, letMany.Body, Append(ints, locals)); + return fe.Block(typeof(int), locals, expressions); + } + + private static T[] Append(T[] source, T[] append) + { + var result = new T[source.Length + append.Length]; + Array.Copy(source, result, source.Length); + Array.Copy(append, 0, result, source.Length, append.Length); + return result; + } + + private readonly record struct GeneratedCase(int Seed, IntSpec Spec); + + private abstract record IntSpec + { + public sealed record ParameterRef(int Index) : IntSpec; + public sealed record Constant(int Value) : IntSpec; + public sealed record Add(IntSpec Left, IntSpec Right) : IntSpec; + public sealed record Subtract(IntSpec Left, IntSpec Right) : IntSpec; + public sealed record Multiply(IntSpec Left, IntSpec Right) : IntSpec; + public sealed record Conditional(BoolSpec Test, IntSpec IfTrue, IntSpec IfFalse) : IntSpec; + public sealed record LetMany(IntSpec[] Values, IntSpec Body) : IntSpec; + } + + private abstract record BoolSpec + { + public sealed record Constant(bool Value) : BoolSpec; + public sealed record Not(BoolSpec Operand) : BoolSpec; + public sealed record Equal(IntSpec Left, IntSpec Right) : BoolSpec; + public sealed record GreaterThan(IntSpec Left, IntSpec Right) : BoolSpec; + public sealed record AndAlso(BoolSpec Left, BoolSpec Right) : BoolSpec; + public sealed record OrElse(BoolSpec Left, BoolSpec Right) : BoolSpec; + } + + private static class GeneratedIntSpecFactory + { + public static IntSpec Create(int seed, int maxDepth, int maxBreadth) => + NextInt(new Random(seed), maxDepth, envIntCount: 1, maxBreadth); + + private static IntSpec NextInt(Random random, int depth, int envIntCount, int maxBreadth) + { + if (depth <= 0) + return NextIntLeaf(random, envIntCount); + + switch (random.Next(7)) + { + case 0: return NextIntLeaf(random, envIntCount); + case 1: return new IntSpec.Add(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 2: return new IntSpec.Subtract(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 3: return new IntSpec.Multiply(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 4: return new IntSpec.Conditional( + NextBool(random, depth - 1, envIntCount, maxBreadth), + NextInt(random, depth - 1, envIntCount, maxBreadth), + NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 5: return NextLetMany(random, depth - 1, envIntCount, maxBreadth); + default: return new IntSpec.Constant(random.Next(-8, 9)); + } + } + + private static IntSpec NextIntLeaf(Random random, int envIntCount) => + random.Next(3) == 0 + ? new IntSpec.Constant(random.Next(-8, 9)) + : new IntSpec.ParameterRef(random.Next(envIntCount)); + + private static IntSpec NextLetMany(Random random, int depth, int envIntCount, int maxBreadth) + { + var count = random.Next(1, maxBreadth + 1); + var values = new IntSpec[count]; + for (var i = 0; i < count; ++i) + values[i] = NextInt(random, depth, envIntCount, maxBreadth); + return new IntSpec.LetMany(values, NextInt(random, depth, envIntCount + count, maxBreadth)); + } + + private static BoolSpec NextBool(Random random, int depth, int envIntCount, int maxBreadth) + { + if (depth <= 0) + return NextBoolLeaf(random, envIntCount); + + switch (random.Next(6)) + { + case 0: return NextBoolLeaf(random, envIntCount); + case 1: return new BoolSpec.Not(NextBool(random, depth - 1, envIntCount, maxBreadth)); + case 2: return new BoolSpec.Equal(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 3: return new BoolSpec.GreaterThan(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 4: return new BoolSpec.AndAlso(NextBool(random, depth - 1, envIntCount, maxBreadth), NextBool(random, depth - 1, envIntCount, maxBreadth)); + default: return new BoolSpec.OrElse(NextBool(random, depth - 1, envIntCount, maxBreadth), NextBool(random, depth - 1, envIntCount, maxBreadth)); + } + } + + private static BoolSpec NextBoolLeaf(Random random, int envIntCount) => + random.Next(2) == 0 + ? new BoolSpec.Constant(random.Next(2) == 0) + : new BoolSpec.Equal(NextIntLeaf(random, envIntCount), NextIntLeaf(random, envIntCount)); + } + + private sealed class GeneratedExpressionComparer + { + private readonly List _xs = new(); + private readonly List _ys = new(); + + public static bool AreEqual(FastExpressionCompiler.LightExpression.Expression x, FastExpressionCompiler.LightExpression.Expression y) => new GeneratedExpressionComparer().Eq(x, y); + + private bool Eq(FastExpressionCompiler.LightExpression.Expression x, FastExpressionCompiler.LightExpression.Expression y) + { + if (ReferenceEquals(x, y)) + return true; + if (x == null || y == null || x.NodeType != y.NodeType || x.Type != y.Type) + return false; + + return x.NodeType switch + { + ExpressionType.Lambda => EqLambda((FastExpressionCompiler.LightExpression.LambdaExpression)x, (FastExpressionCompiler.LightExpression.LambdaExpression)y), + ExpressionType.Parameter => EqParameter((FastExpressionCompiler.LightExpression.ParameterExpression)x, (FastExpressionCompiler.LightExpression.ParameterExpression)y), + ExpressionType.Constant => Equals(((FastExpressionCompiler.LightExpression.ConstantExpression)x).Value, ((FastExpressionCompiler.LightExpression.ConstantExpression)y).Value), + ExpressionType.Not => Eq(((FastExpressionCompiler.LightExpression.UnaryExpression)x).Operand, ((FastExpressionCompiler.LightExpression.UnaryExpression)y).Operand), + ExpressionType.Add or ExpressionType.Subtract or ExpressionType.Multiply or ExpressionType.Assign + or ExpressionType.Equal or ExpressionType.GreaterThan or ExpressionType.AndAlso or ExpressionType.OrElse + => EqBinary((FastExpressionCompiler.LightExpression.BinaryExpression)x, (FastExpressionCompiler.LightExpression.BinaryExpression)y), + ExpressionType.Conditional => EqConditional((FastExpressionCompiler.LightExpression.ConditionalExpression)x, (FastExpressionCompiler.LightExpression.ConditionalExpression)y), + ExpressionType.Block => EqBlock((FastExpressionCompiler.LightExpression.BlockExpression)x, (FastExpressionCompiler.LightExpression.BlockExpression)y), + _ => throw new NotSupportedException(x.NodeType.ToString()) + }; + } + + private bool EqLambda(FastExpressionCompiler.LightExpression.LambdaExpression x, FastExpressionCompiler.LightExpression.LambdaExpression y) + { + if (x.Parameters.Count != y.Parameters.Count) + return false; + + var start = _xs.Count; + for (var i = 0; i < x.Parameters.Count; ++i) + { + _xs.Add(x.Parameters[i]); + _ys.Add(y.Parameters[i]); + } + + var equal = Eq(x.Body, y.Body); + _xs.RemoveRange(start, _xs.Count - start); + _ys.RemoveRange(start, _ys.Count - start); + return equal; + } + + private bool EqParameter(FastExpressionCompiler.LightExpression.ParameterExpression x, FastExpressionCompiler.LightExpression.ParameterExpression y) + { + for (var i = _xs.Count - 1; i >= 0; --i) + { + var xMatches = ReferenceEquals(_xs[i], x); + var yMatches = ReferenceEquals(_ys[i], y); + if (xMatches || yMatches) + return xMatches && yMatches; + } + + return x.Name == y.Name; + } + + private bool EqBinary(FastExpressionCompiler.LightExpression.BinaryExpression x, FastExpressionCompiler.LightExpression.BinaryExpression y) => + x.Method == y.Method && Eq(x.Left, y.Left) && Eq(x.Right, y.Right); + + private bool EqConditional(FastExpressionCompiler.LightExpression.ConditionalExpression x, FastExpressionCompiler.LightExpression.ConditionalExpression y) => + Eq(x.Test, y.Test) && Eq(x.IfTrue, y.IfTrue) && Eq(x.IfFalse, y.IfFalse); + + private bool EqBlock(FastExpressionCompiler.LightExpression.BlockExpression x, FastExpressionCompiler.LightExpression.BlockExpression y) + { + if (x.Variables.Count != y.Variables.Count || x.Expressions.Count != y.Expressions.Count) + return false; + + var start = _xs.Count; + for (var i = 0; i < x.Variables.Count; ++i) + { + _xs.Add(x.Variables[i]); + _ys.Add(y.Variables[i]); + } + + var equal = true; + for (var i = 0; equal && i < x.Expressions.Count; ++i) + equal = Eq(x.Expressions[i], y.Expressions[i]); + + _xs.RemoveRange(start, _xs.Count - start); + _ys.RemoveRange(start, _ys.Count - start); + return equal; + } + } +#else + public void Can_property_test_generated_flat_expression_roundtrip_structurally() { } +#endif +} diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 5144e029..b91c23c0 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -12,7 +12,7 @@ namespace FastExpressionCompiler.LightExpression.UnitTests { - public class LightExpressionTests : ITest + public partial class LightExpressionTests : ITest { public int Run() { @@ -33,7 +33,8 @@ public int Run() Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expression_and_flat_expression(); Can_build_flat_expression_directly_with_light_expression_like_api(); Can_build_flat_expression_control_flow_directly(); - return 16; + Can_property_test_generated_flat_expression_roundtrip_structurally(); + return 17; } From 46072b590439e50adfcc7949340be6055e9ecd85 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:30:02 +0000 Subject: [PATCH 15/15] test: polish generated property test seeds Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/b03ffb96-068c-4255-bd58-3ca2f3ef1298 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../LightExpressionPropertyTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs index 9432a6b6..a9e24e4e 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs @@ -14,7 +14,7 @@ public partial class LightExpressionTests #if NET8_0_OR_GREATER public void Can_property_test_generated_flat_expression_roundtrip_structurally() { - Gen.Int[0, int.MaxValue] + Gen.Int[0, int.MaxValue / 2] .Select(seed => new GeneratedCase(seed, GeneratedIntSpecFactory.Create(seed, maxDepth: 3, maxBreadth: 3))) .Sample(testCase => GeneratedExpressionComparer.AreEqual( @@ -107,7 +107,7 @@ private static FastExpressionCompiler.LightExpression.Expression BuildLightBlock var expressions = new FastExpressionCompiler.LightExpression.Expression[letMany.Values.Length + 1]; for (var i = 0; i < locals.Length; ++i) { - locals[i] = Variable(typeof(int), "v" + i); + locals[i] = Variable(typeof(int), $"v{i}"); expressions[i] = Assign(locals[i], BuildLightInt(letMany.Values[i], ints)); } @@ -121,7 +121,7 @@ private static int BuildFlatBlock(ref ExprTree fe, IntSpec.LetMany letMany, int[ var expressions = new int[letMany.Values.Length + 1]; for (var i = 0; i < locals.Length; ++i) { - locals[i] = fe.Variable(typeof(int), "v" + i); + locals[i] = fe.Variable(typeof(int), $"v{i}"); expressions[i] = fe.Assign(locals[i], BuildFlatInt(ref fe, letMany.Values[i], ints)); }