From 8ae2d111c5c55990204cab8e4c527a3cf2bd266c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 10:08:01 +0000 Subject: [PATCH 01/15] Initial plan From 176af12bd97265839c423d152c76058cf5a77128 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 10:16:19 +0000 Subject: [PATCH 02/15] feat: add structural equality for flat expressions Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/8bcf787b-adee-4401-8721-4587fe7cb997 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 377 +++++++++++++++++- .../LightExpressionTests.cs | 57 ++- 2 files changed, 432 insertions(+), 2 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index ad07b3fe..df0f6aa4 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -173,7 +173,7 @@ public LambdaClosureParameterUsage(ushort lambdaIdx, ushort parameterIdx, ushort } /// Stores an expression tree as flat nodes plus separate closure constants. -public struct ExprTree +public struct ExprTree : IEquatable { private static readonly object ClosureConstantMarker = new(); private const byte ParameterByRefFlag = 1; @@ -709,6 +709,27 @@ public SysExpr ToExpression() => [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); + /// Structurally compares two flat expression trees. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(ExprTree other) => + new StructuralComparer().Eq(this, other); + + /// Structurally compares this tree with another object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool Equals(object obj) => + obj is ExprTree other && Equals(other); + + /// Computes a content-addressable hash for the flat expression tree. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => + new StructuralComparer().Hash(this); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(ExprTree left, ExprTree right) => left.Equals(right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(ExprTree left, ExprTree right) => !left.Equals(right); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child)); @@ -1606,6 +1627,360 @@ private static bool Contains(ref SmallList [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort ToStoredUShortIdx(int idx) => checked((ushort)idx); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static object ReadInlineConstantValue(Type type, uint data) + { + if (type.IsEnum) + return Enum.ToObject(type, Type.GetTypeCode(Enum.GetUnderlyingType(type)) switch + { + TypeCode.Byte => (object)(byte)data, + TypeCode.SByte => (object)(sbyte)(byte)data, + TypeCode.Char => (object)(char)(ushort)data, + TypeCode.Int16 => (object)(short)(ushort)data, + TypeCode.UInt16 => (object)(ushort)data, + TypeCode.Int32 => (object)(int)data, + TypeCode.UInt32 => (object)data, + var tc => FlatExpressionThrow.UnsupportedInlineConstantType(type, tc) + }); + return Type.GetTypeCode(type) switch + { + TypeCode.Boolean => (object)(data != 0), + TypeCode.Byte => (object)(byte)data, + TypeCode.SByte => (object)(sbyte)(byte)data, + TypeCode.Char => (object)(char)(ushort)data, + TypeCode.Int16 => (object)(short)(ushort)data, + TypeCode.UInt16 => (object)(ushort)data, + TypeCode.Int32 => (object)(int)data, + TypeCode.UInt32 => (object)data, + TypeCode.Single => (object)FloatBits.ToFloat(data), + _ => FlatExpressionThrow.UnsupportedInlineConstantType(type) + }; + } + + private struct StructuralComparer + { + private SmallList, NoArrayPool> _xParameterIds, _yParameterIds; + private SmallList, NoArrayPool> _xLabelIds, _yLabelIds; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Eq(ExprTree xTree, ExprTree yTree) + { + if (xTree.Nodes.Count == 0 || yTree.Nodes.Count == 0) + return xTree.Nodes.Count == yTree.Nodes.Count; + + return EqNode(xTree, xTree.RootIdx, yTree, yTree.RootIdx); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Hash(ExprTree tree) => + tree.Nodes.Count == 0 ? 0 : HashNode(tree, tree.RootIdx); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Combine(int h1, int h2) => + unchecked(h1 ^ (h2 + (int)0x9e3779b9 + (h1 << 6) + (h1 >> 2))); + + private bool EqNode(ExprTree xTree, int xIdx, ExprTree yTree, int yIdx) + { + ref var x = ref xTree.Nodes.GetSurePresentRef(xIdx); + ref var y = ref yTree.Nodes.GetSurePresentRef(yIdx); + if (x.Kind != y.Kind || x.NodeType != y.NodeType || x.Type != y.Type || x.Flags != y.Flags) + return false; + + if (x.Kind == ExprNodeKind.LabelTarget) + return EqLabelTarget(ref x, ref y); + + if (x.Kind == ExprNodeKind.CatchBlock) + return EqCatchBlock(xTree, xIdx, yTree, yIdx); + + if (x.Kind == ExprNodeKind.UInt16Pair) + return x.ChildIdx == y.ChildIdx && x.ChildCount == y.ChildCount; + + switch (x.NodeType) + { + case ExpressionType.Parameter: + return EqParameter(ref x, ref y); + + case ExpressionType.Constant: + return Equals(GetConstantValue(xTree, ref x), GetConstantValue(yTree, ref y)); + + case ExpressionType.Lambda: + return EqLambda(xTree, xIdx, yTree, yIdx); + + case ExpressionType.Block: + return EqBlock(xTree, xIdx, yTree, yIdx); + } + + if (!EqObj(xTree, ref x, yTree, ref y)) + return false; + + return EqChildren(xTree.GetChildren(xIdx), xTree, yTree.GetChildren(yIdx), yTree); + } + + private bool EqLambda(ExprTree xTree, int xIdx, ExprTree yTree, int yIdx) + { + var xChildren = xTree.GetChildren(xIdx); + var yChildren = yTree.GetChildren(yIdx); + if (xChildren.Count != yChildren.Count || xChildren.Count == 0) + return false; + + var scopeCount = _xParameterIds.Count; + for (var i = 1; i < xChildren.Count; ++i) + { + ref var xp = ref xTree.Nodes.GetSurePresentRef(xChildren[i]); + ref var yp = ref yTree.Nodes.GetSurePresentRef(yChildren[i]); + if (xp.NodeType != ExpressionType.Parameter || yp.NodeType != ExpressionType.Parameter || + xp.Kind != ExprNodeKind.Expression || yp.Kind != ExprNodeKind.Expression || + xp.Type != yp.Type || xp.HasFlag(ParameterByRefFlag) != yp.HasFlag(ParameterByRefFlag)) + return false; + + _xParameterIds.Add(ToStoredUShortIdx(xp.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yp.ChildIdx)); + } + + var eq = EqNode(xTree, xChildren[0], yTree, yChildren[0]); + _xParameterIds.Count = scopeCount; + _yParameterIds.Count = scopeCount; + return eq; + } + + private bool EqBlock(ExprTree xTree, int xIdx, ExprTree yTree, int yIdx) + { + var xChildren = xTree.GetChildren(xIdx); + var yChildren = yTree.GetChildren(yIdx); + if (xChildren.Count != yChildren.Count || xChildren.Count == 0) + return false; + + var hasVariables = xChildren.Count == 2; + if (hasVariables != (yChildren.Count == 2)) + return false; + + var scopeCount = _xParameterIds.Count; + if (hasVariables) + { + var xVariables = xTree.GetChildren(xChildren[0]); + var yVariables = yTree.GetChildren(yChildren[0]); + if (xVariables.Count != yVariables.Count) + return false; + + for (var i = 0; i < xVariables.Count; ++i) + { + ref var xv = ref xTree.Nodes.GetSurePresentRef(xVariables[i]); + ref var yv = ref yTree.Nodes.GetSurePresentRef(yVariables[i]); + if (xv.NodeType != ExpressionType.Parameter || yv.NodeType != ExpressionType.Parameter || + xv.Kind != ExprNodeKind.Expression || yv.Kind != ExprNodeKind.Expression || + xv.Type != yv.Type || xv.HasFlag(ParameterByRefFlag) != yv.HasFlag(ParameterByRefFlag)) + return false; + + _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + } + } + + var eq = EqNode(xTree, xChildren[xChildren.Count - 1], yTree, yChildren[yChildren.Count - 1]); + _xParameterIds.Count = scopeCount; + _yParameterIds.Count = scopeCount; + return eq; + } + + private bool EqCatchBlock(ExprTree xTree, int xIdx, ExprTree yTree, int yIdx) + { + var xChildren = xTree.GetChildren(xIdx); + var yChildren = yTree.GetChildren(yIdx); + if (xChildren.Count != yChildren.Count) + return false; + + var scopeCount = _xParameterIds.Count; + var childIdx = 0; + if (xTree.Nodes[xIdx].HasFlag(CatchHasVariableFlag)) + { + ref var xv = ref xTree.Nodes.GetSurePresentRef(xChildren[childIdx]); + ref var yv = ref yTree.Nodes.GetSurePresentRef(yChildren[childIdx]); + if (xv.NodeType != ExpressionType.Parameter || yv.NodeType != ExpressionType.Parameter || + xv.Type != yv.Type || xv.HasFlag(ParameterByRefFlag) != yv.HasFlag(ParameterByRefFlag)) + return false; + + _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + childIdx++; + } + + var eq = EqNode(xTree, xChildren[childIdx], yTree, yChildren[childIdx]); + childIdx++; + if (eq && xTree.Nodes[xIdx].HasFlag(CatchHasFilterFlag)) + eq = EqNode(xTree, xChildren[childIdx], yTree, yChildren[childIdx]); + + _xParameterIds.Count = scopeCount; + _yParameterIds.Count = scopeCount; + return eq; + } + + private bool EqChildren(ChildList xChildren, ExprTree xTree, ChildList yChildren, ExprTree yTree) + { + if (xChildren.Count != yChildren.Count) + return false; + + for (var i = 0; i < xChildren.Count; ++i) + if (!EqNode(xTree, xChildren[i], yTree, yChildren[i])) + return false; + + return true; + } + + private bool EqParameter(ref ExprNode x, ref ExprNode y) + { + var xId = ToStoredUShortIdx(x.ChildIdx); + for (var i = 0; i < _xParameterIds.Count; ++i) + if (_xParameterIds[i] == xId) + return _yParameterIds[i] == ToStoredUShortIdx(y.ChildIdx); + + return x.HasFlag(ParameterByRefFlag) == y.HasFlag(ParameterByRefFlag) && + Equals(x.Obj, y.Obj); + } + + private bool EqLabelTarget(ref ExprNode x, ref ExprNode y) + { + var xId = ToStoredUShortIdx(x.ChildIdx); + for (var i = 0; i < _xLabelIds.Count; ++i) + if (_xLabelIds[i] == xId) + return _yLabelIds[i] == ToStoredUShortIdx(y.ChildIdx); + + _xLabelIds.Add(xId); + _yLabelIds.Add(ToStoredUShortIdx(y.ChildIdx)); + return Equals(x.Obj, y.Obj); + } + + private static bool EqObj(ExprTree xTree, ref ExprNode x, ExprTree yTree, ref ExprNode y) + { + if (ReferenceEquals(x.Obj, ExprNode.InlineValueMarker) || ReferenceEquals(y.Obj, ExprNode.InlineValueMarker)) + return ReferenceEquals(x.Obj, ExprNode.InlineValueMarker) && + ReferenceEquals(y.Obj, ExprNode.InlineValueMarker) && + x.InlineValue == y.InlineValue; + + if (ReferenceEquals(x.Obj, ClosureConstantMarker) || ReferenceEquals(y.Obj, ClosureConstantMarker)) + return Equals(GetConstantValue(xTree, ref x), GetConstantValue(yTree, ref y)); + + return ReferenceEquals(x.Obj, y.Obj) || Equals(x.Obj, y.Obj); + } + + private int HashNode(ExprTree tree, int idx) + { + ref var node = ref tree.Nodes.GetSurePresentRef(idx); + if (node.Kind == ExprNodeKind.LabelTarget) + return Combine(Combine((int)node.Kind, node.Type?.GetHashCode() ?? 0), node.Obj?.GetHashCode() ?? 0); + + if (node.Kind == ExprNodeKind.CatchBlock) + return HashCatchBlock(tree, idx, ref node); + + if (node.Kind == ExprNodeKind.UInt16Pair) + return Combine(Combine((int)node.Kind, node.ChildIdx), node.ChildCount); + + var h = Combine(Combine((int)node.Kind, (int)node.NodeType), node.Type?.GetHashCode() ?? 0); + h = Combine(h, node.Flags); + + switch (node.NodeType) + { + case ExpressionType.Parameter: + { + var id = ToStoredUShortIdx(node.ChildIdx); + for (var i = 0; i < _xParameterIds.Count; ++i) + if (_xParameterIds[i] == id) + return Combine(h, i); + return Combine(h, node.Obj?.GetHashCode() ?? 0); + } + + case ExpressionType.Constant: + return Combine(h, GetConstantValue(tree, ref node)?.GetHashCode() ?? 0); + + case ExpressionType.Lambda: + return HashLambda(tree, idx, h); + + case ExpressionType.Block: + return HashBlock(tree, idx, h); + } + + h = Combine(h, GetObjHashCode(tree, ref node)); + var children = tree.GetChildren(idx); + for (var i = 0; i < children.Count; ++i) + h = Combine(h, HashNode(tree, children[i])); + return h; + } + + private int HashLambda(ExprTree tree, int idx, int h) + { + var children = tree.GetChildren(idx); + var scopeCount = _xParameterIds.Count; + for (var i = 1; i < children.Count; ++i) + { + ref var parameter = ref tree.Nodes.GetSurePresentRef(children[i]); + _xParameterIds.Add(ToStoredUShortIdx(parameter.ChildIdx)); + h = Combine(h, Combine(parameter.Type?.GetHashCode() ?? 0, parameter.HasFlag(ParameterByRefFlag) ? 1 : 0)); + } + + h = Combine(h, HashNode(tree, children[0])); + _xParameterIds.Count = scopeCount; + return h; + } + + private int HashBlock(ExprTree tree, int idx, int h) + { + var children = tree.GetChildren(idx); + var scopeCount = _xParameterIds.Count; + if (children.Count == 2) + { + var variables = tree.GetChildren(children[0]); + for (var i = 0; i < variables.Count; ++i) + { + ref var variable = ref tree.Nodes.GetSurePresentRef(variables[i]); + _xParameterIds.Add(ToStoredUShortIdx(variable.ChildIdx)); + h = Combine(h, Combine(variable.Type?.GetHashCode() ?? 0, variable.HasFlag(ParameterByRefFlag) ? 1 : 0)); + } + } + + h = Combine(h, HashNode(tree, children[children.Count - 1])); + _xParameterIds.Count = scopeCount; + return h; + } + + private int HashCatchBlock(ExprTree tree, int idx, ref ExprNode node) + { + var h = Combine(Combine((int)node.Kind, node.Type?.GetHashCode() ?? 0), node.Flags); + var children = tree.GetChildren(idx); + var scopeCount = _xParameterIds.Count; + var childIdx = 0; + if (node.HasFlag(CatchHasVariableFlag)) + { + ref var variable = ref tree.Nodes.GetSurePresentRef(children[childIdx++]); + _xParameterIds.Add(ToStoredUShortIdx(variable.ChildIdx)); + h = Combine(h, Combine(variable.Type?.GetHashCode() ?? 0, variable.HasFlag(ParameterByRefFlag) ? 1 : 0)); + } + + h = Combine(h, HashNode(tree, children[childIdx++])); + if (node.HasFlag(CatchHasFilterFlag)) + h = Combine(h, HashNode(tree, children[childIdx])); + + _xParameterIds.Count = scopeCount; + return h; + } + + private static int GetObjHashCode(ExprTree tree, ref ExprNode node) + { + if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker)) + return GetConstantValue(tree, ref node)?.GetHashCode() ?? 0; + if (ReferenceEquals(node.Obj, ClosureConstantMarker)) + return GetConstantValue(tree, ref node)?.GetHashCode() ?? 0; + return node.Obj?.GetHashCode() ?? 0; + } + + private static object GetConstantValue(ExprTree tree, ref ExprNode node) + { + if (ReferenceEquals(node.Obj, ClosureConstantMarker)) + return tree.ClosureConstants[node.ChildIdx]; + if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker)) + return ReadInlineConstantValue(node.Type, node.InlineValue); + return node.Obj; + } + } + /// Reconstructs System.Linq nodes from the flat representation while reusing parameter and label identities. private struct Reader { diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 3e3ff694..98084bd8 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -55,7 +55,11 @@ public int Run() Flat_blocks_with_variables_tracked_from_expression_conversion(); Flat_goto_and_label_nodes_tracked_from_expression_conversion(); Flat_try_catch_nodes_tracked_from_expression_conversion(); - return 38; + Flat_equal_lambdas_with_different_parameter_names_are_structurally_equal_and_hash_equal(); + Flat_equal_nested_lambdas_with_captures_are_structurally_equal_and_hash_equal(); + Flat_standalone_parameters_use_name_in_structural_equality(); + Flat_structural_hash_supports_dictionary_lookup(); + return 42; } @@ -1023,5 +1027,56 @@ public void Flat_try_catch_nodes_tracked_from_expression_conversion() Asserts.AreEqual(1, fe.TryCatchNodes.Count); } + + public void Flat_equal_lambdas_with_different_parameter_names_are_structurally_equal_and_hash_equal() + { + var x = Parameter(typeof(int), "x"); + var left = Lambda>(Add(x, Constant(1)), x).ToFlatExpression(); + + var y = Parameter(typeof(int), "y"); + var right = Lambda>(Add(y, Constant(1)), y).ToFlatExpression(); + + Asserts.IsTrue(left.Equals(right)); + Asserts.IsTrue(left == right); + Asserts.AreEqual(left.GetHashCode(), right.GetHashCode()); + } + + public void Flat_equal_nested_lambdas_with_captures_are_structurally_equal_and_hash_equal() + { + var x = Parameter(typeof(int), "x"); + var left = Lambda>>( + Lambda>(Add(x, Constant(1))), + x).ToFlatExpression(); + + var y = Parameter(typeof(int), "value"); + var right = Lambda>>( + Lambda>(Add(y, Constant(1))), + y).ToFlatExpression(); + + Asserts.IsTrue(left.Equals(right)); + Asserts.AreEqual(left.GetHashCode(), right.GetHashCode()); + } + + public void Flat_standalone_parameters_use_name_in_structural_equality() + { + var left = Parameter(typeof(int), "x").ToFlatExpression(); + var right = Parameter(typeof(int), "y").ToFlatExpression(); + + Asserts.IsFalse(left.Equals(right)); + } + + public void Flat_structural_hash_supports_dictionary_lookup() + { + var x = Parameter(typeof(int), "x"); + var key = Lambda>(Add(x, Constant(1)), x).ToFlatExpression(); + var dict = new Dictionary { [key] = "found" }; + + var lookup = default(ExprTree); + var y = lookup.ParameterOf("arg"); + lookup.RootIdx = lookup.Lambda>(lookup.Add(y, lookup.ConstantInt(1)), y); + + Asserts.IsTrue(dict.TryGetValue(lookup, out var value)); + Asserts.AreEqual("found", value); + } } } From 8fc6aff9fb0f26a3ea8fe66c67a8216ad0718820 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 10:18:46 +0000 Subject: [PATCH 03/15] docs: clarify flat inline constant decoding Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/8bcf787b-adee-4401-8721-4587fe7cb997 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- src/FastExpressionCompiler.LightExpression/FlatExpression.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index df0f6aa4..8ee83753 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -1627,6 +1627,7 @@ private static bool Contains(ref SmallList [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort ToStoredUShortIdx(int idx) => checked((ushort)idx); + /// Reconstructs the boxed constant value from the node's inline 32-bit payload. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static object ReadInlineConstantValue(Type type, uint data) { From f933b56b00545c1c59769cc9a23e2ceeaa036694 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 10:49:53 +0000 Subject: [PATCH 04/15] refactor: use loop-based flat expression equality Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/340a2774-3537-47bb-b6cb-038b55aec114 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 597 +++++++++++------- 1 file changed, 377 insertions(+), 220 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 8ee83753..43a0778f 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -712,7 +712,7 @@ public SysExpr ToExpression() => /// Structurally compares two flat expression trees. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ExprTree other) => - new StructuralComparer().Eq(this, other); + new StructuralComparer().Eq(ref this, ref other); /// Structurally compares this tree with another object. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -722,7 +722,7 @@ public override bool Equals(object obj) => /// Computes a content-addressable hash for the flat expression tree. [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() => - new StructuralComparer().Hash(this); + new StructuralComparer().Hash(ref this); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(ExprTree left, ExprTree right) => left.Equals(right); @@ -1627,205 +1627,198 @@ private static bool Contains(ref SmallList [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort ToStoredUShortIdx(int idx) => checked((ushort)idx); - /// Reconstructs the boxed constant value from the node's inline 32-bit payload. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static object ReadInlineConstantValue(Type type, uint data) - { - if (type.IsEnum) - return Enum.ToObject(type, Type.GetTypeCode(Enum.GetUnderlyingType(type)) switch - { - TypeCode.Byte => (object)(byte)data, - TypeCode.SByte => (object)(sbyte)(byte)data, - TypeCode.Char => (object)(char)(ushort)data, - TypeCode.Int16 => (object)(short)(ushort)data, - TypeCode.UInt16 => (object)(ushort)data, - TypeCode.Int32 => (object)(int)data, - TypeCode.UInt32 => (object)data, - var tc => FlatExpressionThrow.UnsupportedInlineConstantType(type, tc) - }); - return Type.GetTypeCode(type) switch - { - TypeCode.Boolean => (object)(data != 0), - TypeCode.Byte => (object)(byte)data, - TypeCode.SByte => (object)(sbyte)(byte)data, - TypeCode.Char => (object)(char)(ushort)data, - TypeCode.Int16 => (object)(short)(ushort)data, - TypeCode.UInt16 => (object)(ushort)data, - TypeCode.Int32 => (object)(int)data, - TypeCode.UInt32 => (object)data, - TypeCode.Single => (object)FloatBits.ToFloat(data), - _ => FlatExpressionThrow.UnsupportedInlineConstantType(type) - }; - } - private struct StructuralComparer { private SmallList, NoArrayPool> _xParameterIds, _yParameterIds; private SmallList, NoArrayPool> _xLabelIds, _yLabelIds; + private SmallList, NoArrayPool> _eqFrames; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Eq(ExprTree xTree, ExprTree yTree) + public bool Eq(ref ExprTree xTree, ref ExprTree yTree) { if (xTree.Nodes.Count == 0 || yTree.Nodes.Count == 0) return xTree.Nodes.Count == yTree.Nodes.Count; - return EqNode(xTree, xTree.RootIdx, yTree, yTree.RootIdx); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Hash(ExprTree tree) => - tree.Nodes.Count == 0 ? 0 : HashNode(tree, tree.RootIdx); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int Combine(int h1, int h2) => - unchecked(h1 ^ (h2 + (int)0x9e3779b9 + (h1 << 6) + (h1 >> 2))); - - private bool EqNode(ExprTree xTree, int xIdx, ExprTree yTree, int yIdx) - { - ref var x = ref xTree.Nodes.GetSurePresentRef(xIdx); - ref var y = ref yTree.Nodes.GetSurePresentRef(yIdx); - if (x.Kind != y.Kind || x.NodeType != y.NodeType || x.Type != y.Type || x.Flags != y.Flags) - return false; - - if (x.Kind == ExprNodeKind.LabelTarget) - return EqLabelTarget(ref x, ref y); - - if (x.Kind == ExprNodeKind.CatchBlock) - return EqCatchBlock(xTree, xIdx, yTree, yIdx); - - if (x.Kind == ExprNodeKind.UInt16Pair) - return x.ChildIdx == y.ChildIdx && x.ChildCount == y.ChildCount; - - switch (x.NodeType) - { - case ExpressionType.Parameter: - return EqParameter(ref x, ref y); - - case ExpressionType.Constant: - return Equals(GetConstantValue(xTree, ref x), GetConstantValue(yTree, ref y)); - - case ExpressionType.Lambda: - return EqLambda(xTree, xIdx, yTree, yIdx); - - case ExpressionType.Block: - return EqBlock(xTree, xIdx, yTree, yIdx); - } - - if (!EqObj(xTree, ref x, yTree, ref y)) - return false; - - return EqChildren(xTree.GetChildren(xIdx), xTree, yTree.GetChildren(yIdx), yTree); - } - - private bool EqLambda(ExprTree xTree, int xIdx, ExprTree yTree, int yIdx) - { - var xChildren = xTree.GetChildren(xIdx); - var yChildren = yTree.GetChildren(yIdx); - if (xChildren.Count != yChildren.Count || xChildren.Count == 0) - return false; - - var scopeCount = _xParameterIds.Count; - for (var i = 1; i < xChildren.Count; ++i) + var xIdx = xTree.RootIdx; + var yIdx = yTree.RootIdx; + var remainingSiblings = 0; + while (true) { - ref var xp = ref xTree.Nodes.GetSurePresentRef(xChildren[i]); - ref var yp = ref yTree.Nodes.GetSurePresentRef(yChildren[i]); - if (xp.NodeType != ExpressionType.Parameter || yp.NodeType != ExpressionType.Parameter || - xp.Kind != ExprNodeKind.Expression || yp.Kind != ExprNodeKind.Expression || - xp.Type != yp.Type || xp.HasFlag(ParameterByRefFlag) != yp.HasFlag(ParameterByRefFlag)) + ref var x = ref xTree.Nodes.GetSurePresentRef(xIdx); + ref var y = ref yTree.Nodes.GetSurePresentRef(yIdx); + if (x.Kind != y.Kind || x.NodeType != y.NodeType || x.Type != y.Type || x.Flags != y.Flags) return false; - _xParameterIds.Add(ToStoredUShortIdx(xp.ChildIdx)); - _yParameterIds.Add(ToStoredUShortIdx(yp.ChildIdx)); - } - - var eq = EqNode(xTree, xChildren[0], yTree, yChildren[0]); - _xParameterIds.Count = scopeCount; - _yParameterIds.Count = scopeCount; - return eq; - } - - private bool EqBlock(ExprTree xTree, int xIdx, ExprTree yTree, int yIdx) - { - var xChildren = xTree.GetChildren(xIdx); - var yChildren = yTree.GetChildren(yIdx); - if (xChildren.Count != yChildren.Count || xChildren.Count == 0) - return false; - - var hasVariables = xChildren.Count == 2; - if (hasVariables != (yChildren.Count == 2)) - return false; - - var scopeCount = _xParameterIds.Count; - if (hasVariables) - { - var xVariables = xTree.GetChildren(xChildren[0]); - var yVariables = yTree.GetChildren(yChildren[0]); - if (xVariables.Count != yVariables.Count) - return false; + var descendX = 0; + var descendY = 0; + var descendChildCount = 0; + var restoreXParameterCount = -1; + var restoreYParameterCount = -1; - for (var i = 0; i < xVariables.Count; ++i) + if (x.Kind == ExprNodeKind.LabelTarget) { - ref var xv = ref xTree.Nodes.GetSurePresentRef(xVariables[i]); - ref var yv = ref yTree.Nodes.GetSurePresentRef(yVariables[i]); - if (xv.NodeType != ExpressionType.Parameter || yv.NodeType != ExpressionType.Parameter || - xv.Kind != ExprNodeKind.Expression || yv.Kind != ExprNodeKind.Expression || - xv.Type != yv.Type || xv.HasFlag(ParameterByRefFlag) != yv.HasFlag(ParameterByRefFlag)) + if (!EqLabelTarget(ref x, ref y)) + return false; + } + else if (x.Kind == ExprNodeKind.UInt16Pair) + { + if (x.ChildIdx != y.ChildIdx || x.ChildCount != y.ChildCount) + return false; + } + else if (x.Kind == ExprNodeKind.CatchBlock) + { + if (x.ChildCount != y.ChildCount) return false; - _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); - _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = x.ChildCount - (x.HasFlag(CatchHasVariableFlag) ? 1 : 0); + if (x.HasFlag(CatchHasVariableFlag)) + { + ref var xv = ref xTree.Nodes.GetSurePresentRef(descendX); + ref var yv = ref yTree.Nodes.GetSurePresentRef(descendY); + if (!AreEquivalentParameterDeclarations(ref xv, ref yv)) + return false; + _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + descendX = xv.NextIdx; + descendY = yv.NextIdx; + } } - } + else + { + switch (x.NodeType) + { + case ExpressionType.Parameter: + if (!EqParameter(ref x, ref y)) + return false; + break; + + case ExpressionType.Constant: + if (!ConstantEquals(ref xTree, ref x, ref yTree, ref y)) + return false; + break; + + case ExpressionType.Lambda: + if (x.ChildCount != y.ChildCount || x.ChildCount == 0) + return false; + + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = 1; + var xParameterIdx = xTree.Nodes.GetSurePresentRef(descendX).NextIdx; + var yParameterIdx = yTree.Nodes.GetSurePresentRef(descendY).NextIdx; + for (var i = 1; i < x.ChildCount; ++i) + { + ref var xp = ref xTree.Nodes.GetSurePresentRef(xParameterIdx); + ref var yp = ref yTree.Nodes.GetSurePresentRef(yParameterIdx); + if (!AreEquivalentParameterDeclarations(ref xp, ref yp)) + return false; + _xParameterIds.Add(ToStoredUShortIdx(xp.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yp.ChildIdx)); + xParameterIdx = xp.NextIdx; + yParameterIdx = yp.NextIdx; + } + break; + + case ExpressionType.Block: + if (x.ChildCount != y.ChildCount || x.ChildCount == 0) + return false; + + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = 1; + if (x.ChildCount == 2) + { + ref var xVariables = ref xTree.Nodes.GetSurePresentRef(descendX); + ref var yVariables = ref yTree.Nodes.GetSurePresentRef(descendY); + if (xVariables.Kind != ExprNodeKind.ChildList || yVariables.Kind != ExprNodeKind.ChildList || + xVariables.ChildCount != yVariables.ChildCount) + return false; + + var xVariableIdx = xVariables.ChildIdx; + var yVariableIdx = yVariables.ChildIdx; + for (var i = 0; i < xVariables.ChildCount; ++i) + { + ref var xv = ref xTree.Nodes.GetSurePresentRef(xVariableIdx); + ref var yv = ref yTree.Nodes.GetSurePresentRef(yVariableIdx); + if (!AreEquivalentParameterDeclarations(ref xv, ref yv)) + return false; + _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + xVariableIdx = xv.NextIdx; + yVariableIdx = yv.NextIdx; + } + + descendX = xVariables.NextIdx; + descendY = yVariables.NextIdx; + } + break; - var eq = EqNode(xTree, xChildren[xChildren.Count - 1], yTree, yChildren[yChildren.Count - 1]); - _xParameterIds.Count = scopeCount; - _yParameterIds.Count = scopeCount; - return eq; - } + default: + if (x.ChildCount != y.ChildCount || !EqObj(ref x, ref y)) + return false; + if (x.ChildCount != 0) + { + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = x.ChildCount; + } + break; + } + } - private bool EqCatchBlock(ExprTree xTree, int xIdx, ExprTree yTree, int yIdx) - { - var xChildren = xTree.GetChildren(xIdx); - var yChildren = yTree.GetChildren(yIdx); - if (xChildren.Count != yChildren.Count) - return false; + if (descendChildCount != 0) + { + _eqFrames.Add(new TraversalFrame(x.NextIdx, y.NextIdx, remainingSiblings, restoreXParameterCount, restoreYParameterCount)); + xIdx = descendX; + yIdx = descendY; + remainingSiblings = descendChildCount - 1; + continue; + } - var scopeCount = _xParameterIds.Count; - var childIdx = 0; - if (xTree.Nodes[xIdx].HasFlag(CatchHasVariableFlag)) - { - ref var xv = ref xTree.Nodes.GetSurePresentRef(xChildren[childIdx]); - ref var yv = ref yTree.Nodes.GetSurePresentRef(yChildren[childIdx]); - if (xv.NodeType != ExpressionType.Parameter || yv.NodeType != ExpressionType.Parameter || - xv.Type != yv.Type || xv.HasFlag(ParameterByRefFlag) != yv.HasFlag(ParameterByRefFlag)) - return false; + while (true) + { + if (remainingSiblings != 0) + { + xIdx = x.NextIdx; + yIdx = y.NextIdx; + remainingSiblings--; + goto ContinueTraversal; + } - _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); - _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); - childIdx++; - } + if (_eqFrames.Count == 0) + return true; - var eq = EqNode(xTree, xChildren[childIdx], yTree, yChildren[childIdx]); - childIdx++; - if (eq && xTree.Nodes[xIdx].HasFlag(CatchHasFilterFlag)) - eq = EqNode(xTree, xChildren[childIdx], yTree, yChildren[childIdx]); + var frame = _eqFrames[_eqFrames.Count - 1]; + _eqFrames.Count--; + RestoreParameterScope(frame.XParameterCount, frame.YParameterCount); + if (frame.RemainingSiblingsAfterNode != 0) + { + xIdx = frame.XNextIdx; + yIdx = frame.YNextIdx; + remainingSiblings = frame.RemainingSiblingsAfterNode - 1; + goto ContinueTraversal; + } + } - _xParameterIds.Count = scopeCount; - _yParameterIds.Count = scopeCount; - return eq; + ContinueTraversal:; + } } - private bool EqChildren(ChildList xChildren, ExprTree xTree, ChildList yChildren, ExprTree yTree) - { - if (xChildren.Count != yChildren.Count) - return false; - - for (var i = 0; i < xChildren.Count; ++i) - if (!EqNode(xTree, xChildren[i], yTree, yChildren[i])) - return false; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Hash(ref ExprTree tree) => + tree.Nodes.Count == 0 ? 0 : HashNode(ref tree, tree.RootIdx); - return true; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int Combine(int h1, int h2) => + unchecked(h1 ^ (h2 + (int)0x9e3779b9 + (h1 << 6) + (h1 >> 2))); private bool EqParameter(ref ExprNode x, ref ExprNode y) { @@ -1850,27 +1843,34 @@ private bool EqLabelTarget(ref ExprNode x, ref ExprNode y) return Equals(x.Obj, y.Obj); } - private static bool EqObj(ExprTree xTree, ref ExprNode x, ExprTree yTree, ref ExprNode y) - { - if (ReferenceEquals(x.Obj, ExprNode.InlineValueMarker) || ReferenceEquals(y.Obj, ExprNode.InlineValueMarker)) - return ReferenceEquals(x.Obj, ExprNode.InlineValueMarker) && - ReferenceEquals(y.Obj, ExprNode.InlineValueMarker) && - x.InlineValue == y.InlineValue; - - if (ReferenceEquals(x.Obj, ClosureConstantMarker) || ReferenceEquals(y.Obj, ClosureConstantMarker)) - return Equals(GetConstantValue(xTree, ref x), GetConstantValue(yTree, ref y)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool AreEquivalentParameterDeclarations(ref ExprNode x, ref ExprNode y) => + x.NodeType == ExpressionType.Parameter && y.NodeType == ExpressionType.Parameter && + x.Kind == ExprNodeKind.Expression && y.Kind == ExprNodeKind.Expression && + x.Type == y.Type && x.HasFlag(ParameterByRefFlag) == y.HasFlag(ParameterByRefFlag); + private static bool EqObj(ref ExprNode x, ref ExprNode y) + { return ReferenceEquals(x.Obj, y.Obj) || Equals(x.Obj, y.Obj); } - private int HashNode(ExprTree tree, int idx) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RestoreParameterScope(int xParameterCount, int yParameterCount) + { + if (xParameterCount >= 0) + _xParameterIds.Count = xParameterCount; + if (yParameterCount >= 0) + _yParameterIds.Count = yParameterCount; + } + + private int HashNode(ref ExprTree tree, int idx) { ref var node = ref tree.Nodes.GetSurePresentRef(idx); if (node.Kind == ExprNodeKind.LabelTarget) return Combine(Combine((int)node.Kind, node.Type?.GetHashCode() ?? 0), node.Obj?.GetHashCode() ?? 0); if (node.Kind == ExprNodeKind.CatchBlock) - return HashCatchBlock(tree, idx, ref node); + return HashCatchBlock(ref tree, idx, ref node); if (node.Kind == ExprNodeKind.UInt16Pair) return Combine(Combine((int)node.Kind, node.ChildIdx), node.ChildCount); @@ -1890,95 +1890,252 @@ private int HashNode(ExprTree tree, int idx) } case ExpressionType.Constant: - return Combine(h, GetConstantValue(tree, ref node)?.GetHashCode() ?? 0); + return Combine(h, GetConstantHashCode(ref tree, ref node)); case ExpressionType.Lambda: - return HashLambda(tree, idx, h); + return HashLambda(ref tree, idx, h); case ExpressionType.Block: - return HashBlock(tree, idx, h); + return HashBlock(ref tree, idx, h); } - h = Combine(h, GetObjHashCode(tree, ref node)); - var children = tree.GetChildren(idx); - for (var i = 0; i < children.Count; ++i) - h = Combine(h, HashNode(tree, children[i])); + h = Combine(h, node.Obj?.GetHashCode() ?? 0); + var childIdx = node.ChildIdx; + for (var i = 0; i < node.ChildCount; ++i) + { + h = Combine(h, HashNode(ref tree, childIdx)); + childIdx = tree.Nodes.GetSurePresentRef(childIdx).NextIdx; + } return h; } - private int HashLambda(ExprTree tree, int idx, int h) + private int HashLambda(ref ExprTree tree, int idx, int h) { - var children = tree.GetChildren(idx); var scopeCount = _xParameterIds.Count; - for (var i = 1; i < children.Count; ++i) + ref var node = ref tree.Nodes.GetSurePresentRef(idx); + var bodyIdx = node.ChildIdx; + var parameterIdx = tree.Nodes.GetSurePresentRef(bodyIdx).NextIdx; + for (var i = 1; i < node.ChildCount; ++i) { - ref var parameter = ref tree.Nodes.GetSurePresentRef(children[i]); + ref var parameter = ref tree.Nodes.GetSurePresentRef(parameterIdx); _xParameterIds.Add(ToStoredUShortIdx(parameter.ChildIdx)); h = Combine(h, Combine(parameter.Type?.GetHashCode() ?? 0, parameter.HasFlag(ParameterByRefFlag) ? 1 : 0)); + parameterIdx = parameter.NextIdx; } - h = Combine(h, HashNode(tree, children[0])); + h = Combine(h, HashNode(ref tree, bodyIdx)); _xParameterIds.Count = scopeCount; return h; } - private int HashBlock(ExprTree tree, int idx, int h) + private int HashBlock(ref ExprTree tree, int idx, int h) { - var children = tree.GetChildren(idx); var scopeCount = _xParameterIds.Count; - if (children.Count == 2) + ref var node = ref tree.Nodes.GetSurePresentRef(idx); + var bodyListIdx = node.ChildIdx; + if (node.ChildCount == 2) { - var variables = tree.GetChildren(children[0]); - for (var i = 0; i < variables.Count; ++i) + ref var variables = ref tree.Nodes.GetSurePresentRef(bodyListIdx); + var variableIdx = variables.ChildIdx; + for (var i = 0; i < variables.ChildCount; ++i) { - ref var variable = ref tree.Nodes.GetSurePresentRef(variables[i]); + ref var variable = ref tree.Nodes.GetSurePresentRef(variableIdx); _xParameterIds.Add(ToStoredUShortIdx(variable.ChildIdx)); h = Combine(h, Combine(variable.Type?.GetHashCode() ?? 0, variable.HasFlag(ParameterByRefFlag) ? 1 : 0)); + variableIdx = variable.NextIdx; } + bodyListIdx = variables.NextIdx; } - h = Combine(h, HashNode(tree, children[children.Count - 1])); + h = Combine(h, HashNode(ref tree, bodyListIdx)); _xParameterIds.Count = scopeCount; return h; } - private int HashCatchBlock(ExprTree tree, int idx, ref ExprNode node) + private int HashCatchBlock(ref ExprTree tree, int idx, ref ExprNode node) { var h = Combine(Combine((int)node.Kind, node.Type?.GetHashCode() ?? 0), node.Flags); - var children = tree.GetChildren(idx); var scopeCount = _xParameterIds.Count; var childIdx = 0; + var catchChildIdx = node.ChildIdx; if (node.HasFlag(CatchHasVariableFlag)) { - ref var variable = ref tree.Nodes.GetSurePresentRef(children[childIdx++]); + ref var variable = ref tree.Nodes.GetSurePresentRef(catchChildIdx); _xParameterIds.Add(ToStoredUShortIdx(variable.ChildIdx)); h = Combine(h, Combine(variable.Type?.GetHashCode() ?? 0, variable.HasFlag(ParameterByRefFlag) ? 1 : 0)); + catchChildIdx = variable.NextIdx; + childIdx++; } - h = Combine(h, HashNode(tree, children[childIdx++])); + h = Combine(h, HashNode(ref tree, catchChildIdx)); + catchChildIdx = tree.Nodes.GetSurePresentRef(catchChildIdx).NextIdx; + childIdx++; if (node.HasFlag(CatchHasFilterFlag)) - h = Combine(h, HashNode(tree, children[childIdx])); + h = Combine(h, HashNode(ref tree, catchChildIdx)); _xParameterIds.Count = scopeCount; return h; } - private static int GetObjHashCode(ExprTree tree, ref ExprNode node) + private static int GetConstantHashCode(ref ExprTree tree, ref ExprNode node) { if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker)) - return GetConstantValue(tree, ref node)?.GetHashCode() ?? 0; - if (ReferenceEquals(node.Obj, ClosureConstantMarker)) - return GetConstantValue(tree, ref node)?.GetHashCode() ?? 0; - return node.Obj?.GetHashCode() ?? 0; + return GetInlineConstantHashCode(node.Type, node.InlineValue); + return GetStoredConstantValue(ref tree, ref node)?.GetHashCode() ?? 0; } - private static object GetConstantValue(ExprTree tree, ref ExprNode node) + private static bool ConstantEquals(ref ExprTree xTree, ref ExprNode x, ref ExprTree yTree, ref ExprNode y) { - if (ReferenceEquals(node.Obj, ClosureConstantMarker)) - return tree.ClosureConstants[node.ChildIdx]; - if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker)) - return ReadInlineConstantValue(node.Type, node.InlineValue); - return node.Obj; + var xObj = GetStoredConstantValue(ref xTree, ref x); + var yObj = GetStoredConstantValue(ref yTree, ref y); + if (!ReferenceEquals(x.Obj, ExprNode.InlineValueMarker) && !ReferenceEquals(y.Obj, ExprNode.InlineValueMarker)) + return ReferenceEquals(xObj, yObj) || Equals(xObj, yObj); + + if (x.Type.IsEnum) + { + if (ReferenceEquals(x.Obj, ExprNode.InlineValueMarker) && ReferenceEquals(y.Obj, ExprNode.InlineValueMarker)) + return x.InlineValue == y.InlineValue; + return Type.GetTypeCode(Enum.GetUnderlyingType(x.Type)) switch + { + TypeCode.Byte => GetInlineOrConvertedByte(ref xTree, ref x) == GetInlineOrConvertedByte(ref yTree, ref y), + TypeCode.SByte => GetInlineOrConvertedSByte(ref xTree, ref x) == GetInlineOrConvertedSByte(ref yTree, ref y), + TypeCode.Char => GetInlineOrConvertedChar(ref xTree, ref x) == GetInlineOrConvertedChar(ref yTree, ref y), + TypeCode.Int16 => GetInlineOrConvertedInt16(ref xTree, ref x) == GetInlineOrConvertedInt16(ref yTree, ref y), + TypeCode.UInt16 => GetInlineOrConvertedUInt16(ref xTree, ref x) == GetInlineOrConvertedUInt16(ref yTree, ref y), + TypeCode.Int32 => GetInlineOrConvertedInt32(ref xTree, ref x) == GetInlineOrConvertedInt32(ref yTree, ref y), + TypeCode.UInt32 => GetInlineOrConvertedUInt32(ref xTree, ref x) == GetInlineOrConvertedUInt32(ref yTree, ref y), + var tc => FlatExpressionThrow.UnsupportedInlineConstantType(x.Type, tc) + }; + } + + return Type.GetTypeCode(x.Type) switch + { + TypeCode.Boolean => GetInlineOrStoredBoolean(ref xTree, ref x) == GetInlineOrStoredBoolean(ref yTree, ref y), + TypeCode.Byte => GetInlineOrStoredByte(ref xTree, ref x) == GetInlineOrStoredByte(ref yTree, ref y), + TypeCode.SByte => GetInlineOrStoredSByte(ref xTree, ref x) == GetInlineOrStoredSByte(ref yTree, ref y), + TypeCode.Char => GetInlineOrStoredChar(ref xTree, ref x) == GetInlineOrStoredChar(ref yTree, ref y), + TypeCode.Int16 => GetInlineOrStoredInt16(ref xTree, ref x) == GetInlineOrStoredInt16(ref yTree, ref y), + TypeCode.UInt16 => GetInlineOrStoredUInt16(ref xTree, ref x) == GetInlineOrStoredUInt16(ref yTree, ref y), + TypeCode.Int32 => GetInlineOrStoredInt32(ref xTree, ref x) == GetInlineOrStoredInt32(ref yTree, ref y), + TypeCode.UInt32 => GetInlineOrStoredUInt32(ref xTree, ref x) == GetInlineOrStoredUInt32(ref yTree, ref y), + TypeCode.Single => GetInlineOrStoredSingle(ref xTree, ref x).Equals(GetInlineOrStoredSingle(ref yTree, ref y)), + _ => ReferenceEquals(xObj, yObj) || Equals(xObj, yObj) + }; + } + + private static object GetStoredConstantValue(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ClosureConstantMarker) ? tree.ClosureConstants[node.ChildIdx] : node.Obj; + + private static int GetInlineConstantHashCode(Type type, uint data) + { + if (type.IsEnum) + return Type.GetTypeCode(Enum.GetUnderlyingType(type)) switch + { + TypeCode.Byte => ((byte)data).GetHashCode(), + TypeCode.SByte => ((sbyte)(byte)data).GetHashCode(), + TypeCode.Char => ((char)(ushort)data).GetHashCode(), + TypeCode.Int16 => ((short)(ushort)data).GetHashCode(), + TypeCode.UInt16 => ((ushort)data).GetHashCode(), + TypeCode.Int32 => ((int)data).GetHashCode(), + TypeCode.UInt32 => data.GetHashCode(), + var tc => FlatExpressionThrow.UnsupportedInlineConstantType(type, tc) + }; + + return Type.GetTypeCode(type) switch + { + TypeCode.Boolean => (data != 0).GetHashCode(), + TypeCode.Byte => ((byte)data).GetHashCode(), + TypeCode.SByte => ((sbyte)(byte)data).GetHashCode(), + TypeCode.Char => ((char)(ushort)data).GetHashCode(), + TypeCode.Int16 => ((short)(ushort)data).GetHashCode(), + TypeCode.UInt16 => ((ushort)data).GetHashCode(), + TypeCode.Int32 => ((int)data).GetHashCode(), + TypeCode.UInt32 => data.GetHashCode(), + TypeCode.Single => FloatBits.ToFloat(data).GetHashCode(), + _ => FlatExpressionThrow.UnsupportedInlineConstantType(type) + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool GetInlineOrStoredBoolean(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? node.InlineValue != 0 : (bool)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetInlineOrStoredByte(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (byte)node.InlineValue : (byte)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static sbyte GetInlineOrStoredSByte(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (sbyte)(byte)node.InlineValue : (sbyte)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static char GetInlineOrStoredChar(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (char)(ushort)node.InlineValue : (char)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short GetInlineOrStoredInt16(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (short)(ushort)node.InlineValue : (short)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort GetInlineOrStoredUInt16(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (ushort)node.InlineValue : (ushort)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetInlineOrStoredInt32(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (int)node.InlineValue : (int)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint GetInlineOrStoredUInt32(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? node.InlineValue : (uint)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float GetInlineOrStoredSingle(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? FloatBits.ToFloat(node.InlineValue) : (float)GetStoredConstantValue(ref tree, ref node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte GetInlineOrConvertedByte(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (byte)node.InlineValue : System.Convert.ToByte(GetStoredConstantValue(ref tree, ref node)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static sbyte GetInlineOrConvertedSByte(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (sbyte)(byte)node.InlineValue : System.Convert.ToSByte(GetStoredConstantValue(ref tree, ref node)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static char GetInlineOrConvertedChar(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (char)(ushort)node.InlineValue : System.Convert.ToChar(GetStoredConstantValue(ref tree, ref node)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static short GetInlineOrConvertedInt16(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (short)(ushort)node.InlineValue : System.Convert.ToInt16(GetStoredConstantValue(ref tree, ref node)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort GetInlineOrConvertedUInt16(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (ushort)node.InlineValue : System.Convert.ToUInt16(GetStoredConstantValue(ref tree, ref node)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetInlineOrConvertedInt32(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (int)node.InlineValue : System.Convert.ToInt32(GetStoredConstantValue(ref tree, ref node)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint GetInlineOrConvertedUInt32(ref ExprTree tree, ref ExprNode node) => + ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? node.InlineValue : System.Convert.ToUInt32(GetStoredConstantValue(ref tree, ref node)); + + private struct TraversalFrame + { + public readonly int XNextIdx; + public readonly int YNextIdx; + public readonly int RemainingSiblingsAfterNode; + public readonly int XParameterCount; + public readonly int YParameterCount; + + public TraversalFrame(int xNextIdx, int yNextIdx, int remainingSiblingsAfterNode, int xParameterCount, int yParameterCount) + { + XNextIdx = xNextIdx; + YNextIdx = yNextIdx; + RemainingSiblingsAfterNode = remainingSiblingsAfterNode; + XParameterCount = xParameterCount; + YParameterCount = yParameterCount; + } } } From 50deedb0af0590fa74b58cc20a1b60a178fe75bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 10:53:02 +0000 Subject: [PATCH 05/15] refactor: simplify flat equality loop control Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/340a2774-3537-47bb-b6cb-038b55aec114 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 43a0778f..f5a64e4d 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -1783,6 +1783,7 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) continue; } + var advanced = false; while (true) { if (remainingSiblings != 0) @@ -1790,7 +1791,8 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) xIdx = x.NextIdx; yIdx = y.NextIdx; remainingSiblings--; - goto ContinueTraversal; + advanced = true; + break; } if (_eqFrames.Count == 0) @@ -1804,11 +1806,12 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) xIdx = frame.XNextIdx; yIdx = frame.YNextIdx; remainingSiblings = frame.RemainingSiblingsAfterNode - 1; - goto ContinueTraversal; + advanced = true; + break; } } - - ContinueTraversal:; + if (advanced) + continue; } } From a1f952f153c7d14c9e9a0e88de4332cea1b7ccb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 10:54:57 +0000 Subject: [PATCH 06/15] refactor: rename flat constant comparison helper Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/340a2774-3537-47bb-b6cb-038b55aec114 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- src/FastExpressionCompiler.LightExpression/FlatExpression.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index f5a64e4d..b351d25f 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -1697,7 +1697,7 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) break; case ExpressionType.Constant: - if (!ConstantEquals(ref xTree, ref x, ref yTree, ref y)) + if (!AreConstantsEqual(ref xTree, ref x, ref yTree, ref y)) return false; break; @@ -1987,7 +1987,7 @@ private static int GetConstantHashCode(ref ExprTree tree, ref ExprNode node) return GetStoredConstantValue(ref tree, ref node)?.GetHashCode() ?? 0; } - private static bool ConstantEquals(ref ExprTree xTree, ref ExprNode x, ref ExprTree yTree, ref ExprNode y) + private static bool AreConstantsEqual(ref ExprTree xTree, ref ExprNode x, ref ExprTree yTree, ref ExprNode y) { var xObj = GetStoredConstantValue(ref xTree, ref x); var yObj = GetStoredConstantValue(ref yTree, ref y); From 023bb11d475bab393e25c1fce6f99f415e1c59b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 6 May 2026 11:16:06 +0000 Subject: [PATCH 07/15] refactor: simplify expr node equality precheck Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/5b87e13e-991a-4ea4-aab8-266df9dda7fd Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- .../FlatExpression.cs | 223 ++++++++++-------- 1 file changed, 121 insertions(+), 102 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index b351d25f..bd8bc961 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -61,6 +61,7 @@ public struct ExprNode private const uint MetaKeepWithoutNext = 0xFFFF0000u; // _data layout: bits [31:16]=ChildCount | [15:0]=ChildIdx (or full uint for inline constants) private const int DataCountShift = 16; + private const uint DataKeepWithoutChildIdx = 0xFFFF0000u; private const uint DataIdxMask = 0xFFFFu; private const int FlagsShift = 4; private const uint KindMask = 0x0Fu; @@ -139,6 +140,19 @@ internal void SetChildInfo(int childIdx, int childCount) => [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool HasFlag(byte flag) => (Flags & flag) != 0; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool HasSameHeaderExceptNext(ref ExprNode other) => + Type == other.Type && (_meta & MetaKeepWithoutNext) == (other._meta & MetaKeepWithoutNext); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool HasSameShapeExceptLinks(ref ExprNode other) => + HasSameHeaderExceptNext(ref other) && + (_data & DataKeepWithoutChildIdx) == (other._data & DataKeepWithoutChildIdx); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool HasSameShapeExceptNext(ref ExprNode other) => + HasSameHeaderExceptNext(ref other) && _data == other._data; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool ShouldCloneWhenLinked() => ReferenceEquals(Obj, InlineValueMarker) || @@ -1646,7 +1660,17 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) { ref var x = ref xTree.Nodes.GetSurePresentRef(xIdx); ref var y = ref yTree.Nodes.GetSurePresentRef(yIdx); - if (x.Kind != y.Kind || x.NodeType != y.NodeType || x.Type != y.Type || x.Flags != y.Flags) + if (x.Kind == ExprNodeKind.UInt16Pair) + { + if (!x.HasSameShapeExceptNext(ref y)) + return false; + } + else if (x.NodeType == ExpressionType.Constant) + { + if (!x.HasSameHeaderExceptNext(ref y)) + return false; + } + else if (!x.HasSameShapeExceptLinks(ref y)) return false; var descendX = 0; @@ -1655,122 +1679,117 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) var restoreXParameterCount = -1; var restoreYParameterCount = -1; - if (x.Kind == ExprNodeKind.LabelTarget) + if (x.Kind != ExprNodeKind.UInt16Pair) { - if (!EqLabelTarget(ref x, ref y)) - return false; - } - else if (x.Kind == ExprNodeKind.UInt16Pair) - { - if (x.ChildIdx != y.ChildIdx || x.ChildCount != y.ChildCount) - return false; - } - else if (x.Kind == ExprNodeKind.CatchBlock) - { - if (x.ChildCount != y.ChildCount) - return false; - - restoreXParameterCount = _xParameterIds.Count; - restoreYParameterCount = _yParameterIds.Count; - descendX = x.ChildIdx; - descendY = y.ChildIdx; - descendChildCount = x.ChildCount - (x.HasFlag(CatchHasVariableFlag) ? 1 : 0); - if (x.HasFlag(CatchHasVariableFlag)) + if (x.Kind == ExprNodeKind.LabelTarget) { - ref var xv = ref xTree.Nodes.GetSurePresentRef(descendX); - ref var yv = ref yTree.Nodes.GetSurePresentRef(descendY); - if (!AreEquivalentParameterDeclarations(ref xv, ref yv)) + if (!EqLabelTarget(ref x, ref y)) return false; - _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); - _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); - descendX = xv.NextIdx; - descendY = yv.NextIdx; } - } - else - { - switch (x.NodeType) + else if (x.Kind == ExprNodeKind.CatchBlock) { - case ExpressionType.Parameter: - if (!EqParameter(ref x, ref y)) - return false; - break; - - case ExpressionType.Constant: - if (!AreConstantsEqual(ref xTree, ref x, ref yTree, ref y)) - return false; - break; - - case ExpressionType.Lambda: - if (x.ChildCount != y.ChildCount || x.ChildCount == 0) + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = x.ChildCount - (x.HasFlag(CatchHasVariableFlag) ? 1 : 0); + if (x.HasFlag(CatchHasVariableFlag)) + { + ref var xv = ref xTree.Nodes.GetSurePresentRef(descendX); + ref var yv = ref yTree.Nodes.GetSurePresentRef(descendY); + if (!AreEquivalentParameterDeclarations(ref xv, ref yv)) return false; - - restoreXParameterCount = _xParameterIds.Count; - restoreYParameterCount = _yParameterIds.Count; - descendX = x.ChildIdx; - descendY = y.ChildIdx; - descendChildCount = 1; - var xParameterIdx = xTree.Nodes.GetSurePresentRef(descendX).NextIdx; - var yParameterIdx = yTree.Nodes.GetSurePresentRef(descendY).NextIdx; - for (var i = 1; i < x.ChildCount; ++i) - { - ref var xp = ref xTree.Nodes.GetSurePresentRef(xParameterIdx); - ref var yp = ref yTree.Nodes.GetSurePresentRef(yParameterIdx); - if (!AreEquivalentParameterDeclarations(ref xp, ref yp)) + _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + descendX = xv.NextIdx; + descendY = yv.NextIdx; + } + } + else + { + switch (x.NodeType) + { + case ExpressionType.Parameter: + if (!EqParameter(ref x, ref y)) return false; - _xParameterIds.Add(ToStoredUShortIdx(xp.ChildIdx)); - _yParameterIds.Add(ToStoredUShortIdx(yp.ChildIdx)); - xParameterIdx = xp.NextIdx; - yParameterIdx = yp.NextIdx; - } - break; + break; - case ExpressionType.Block: - if (x.ChildCount != y.ChildCount || x.ChildCount == 0) - return false; + case ExpressionType.Constant: + if (!AreConstantsEqual(ref xTree, ref x, ref yTree, ref y)) + return false; + break; - restoreXParameterCount = _xParameterIds.Count; - restoreYParameterCount = _yParameterIds.Count; - descendX = x.ChildIdx; - descendY = y.ChildIdx; - descendChildCount = 1; - if (x.ChildCount == 2) - { - ref var xVariables = ref xTree.Nodes.GetSurePresentRef(descendX); - ref var yVariables = ref yTree.Nodes.GetSurePresentRef(descendY); - if (xVariables.Kind != ExprNodeKind.ChildList || yVariables.Kind != ExprNodeKind.ChildList || - xVariables.ChildCount != yVariables.ChildCount) + case ExpressionType.Lambda: + if (x.ChildCount == 0) return false; - var xVariableIdx = xVariables.ChildIdx; - var yVariableIdx = yVariables.ChildIdx; - for (var i = 0; i < xVariables.ChildCount; ++i) + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = 1; + var xParameterIdx = xTree.Nodes.GetSurePresentRef(descendX).NextIdx; + var yParameterIdx = yTree.Nodes.GetSurePresentRef(descendY).NextIdx; + for (var i = 1; i < x.ChildCount; ++i) { - ref var xv = ref xTree.Nodes.GetSurePresentRef(xVariableIdx); - ref var yv = ref yTree.Nodes.GetSurePresentRef(yVariableIdx); - if (!AreEquivalentParameterDeclarations(ref xv, ref yv)) + ref var xp = ref xTree.Nodes.GetSurePresentRef(xParameterIdx); + ref var yp = ref yTree.Nodes.GetSurePresentRef(yParameterIdx); + if (!AreEquivalentParameterDeclarations(ref xp, ref yp)) return false; - _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); - _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); - xVariableIdx = xv.NextIdx; - yVariableIdx = yv.NextIdx; + _xParameterIds.Add(ToStoredUShortIdx(xp.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yp.ChildIdx)); + xParameterIdx = xp.NextIdx; + yParameterIdx = yp.NextIdx; } + break; - descendX = xVariables.NextIdx; - descendY = yVariables.NextIdx; - } - break; + case ExpressionType.Block: + if (x.ChildCount == 0) + return false; - default: - if (x.ChildCount != y.ChildCount || !EqObj(ref x, ref y)) - return false; - if (x.ChildCount != 0) - { + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; descendX = x.ChildIdx; descendY = y.ChildIdx; - descendChildCount = x.ChildCount; - } - break; + descendChildCount = 1; + if (x.ChildCount == 2) + { + ref var xVariables = ref xTree.Nodes.GetSurePresentRef(descendX); + ref var yVariables = ref yTree.Nodes.GetSurePresentRef(descendY); + if (xVariables.Kind != ExprNodeKind.ChildList || yVariables.Kind != ExprNodeKind.ChildList || + xVariables.ChildCount != yVariables.ChildCount) + return false; + + var xVariableIdx = xVariables.ChildIdx; + var yVariableIdx = yVariables.ChildIdx; + for (var i = 0; i < xVariables.ChildCount; ++i) + { + ref var xv = ref xTree.Nodes.GetSurePresentRef(xVariableIdx); + ref var yv = ref yTree.Nodes.GetSurePresentRef(yVariableIdx); + if (!AreEquivalentParameterDeclarations(ref xv, ref yv)) + return false; + _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + xVariableIdx = xv.NextIdx; + yVariableIdx = yv.NextIdx; + } + + descendX = xVariables.NextIdx; + descendY = yVariables.NextIdx; + } + break; + + default: + if (!EqObj(ref x, ref y)) + return false; + if (x.ChildCount != 0) + { + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = x.ChildCount; + } + break; + } } } @@ -1848,9 +1867,9 @@ private bool EqLabelTarget(ref ExprNode x, ref ExprNode y) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool AreEquivalentParameterDeclarations(ref ExprNode x, ref ExprNode y) => - x.NodeType == ExpressionType.Parameter && y.NodeType == ExpressionType.Parameter && - x.Kind == ExprNodeKind.Expression && y.Kind == ExprNodeKind.Expression && - x.Type == y.Type && x.HasFlag(ParameterByRefFlag) == y.HasFlag(ParameterByRefFlag); + x.NodeType == ExpressionType.Parameter && + y.NodeType == ExpressionType.Parameter && + x.HasSameShapeExceptLinks(ref y); private static bool EqObj(ref ExprNode x, ref ExprNode y) { From 14f0c05b8463e826052bc91090157d68bf846205 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 10:11:22 +0000 Subject: [PATCH 08/15] docs: add xml comments for expr tree operators Agent-Logs-Url: https://github.com/dadhi/FastExpressionCompiler/sessions/306086af-4c82-4f97-9286-a3fe568d3f9a Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- src/FastExpressionCompiler.LightExpression/FlatExpression.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index bd8bc961..253e68a8 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -738,9 +738,11 @@ public override bool Equals(object obj) => public override int GetHashCode() => new StructuralComparer().Hash(ref this); + /// Determines whether two flat expression trees are structurally equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(ExprTree left, ExprTree right) => left.Equals(right); + /// Determines whether two flat expression trees are not structurally equal. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(ExprTree left, ExprTree right) => !left.Equals(right); From b1874d833a31316de7efd52dd9a0a26e486ee9d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 21:51:47 +0000 Subject: [PATCH 09/15] refactor: inline small flat constants --- .../FlatExpression.cs | 93 +++++-------------- 1 file changed, 23 insertions(+), 70 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 253e68a8..6a78edd3 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -288,7 +288,7 @@ public int Constant(object value, Type type) /// Adds an constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantInt(int value) => AddRawExpressionNode(typeof(int), value, ExpressionType.Constant); + public int ConstantInt(int value) => AddInlineConstantNode(typeof(int), unchecked((uint)value)); /// Adds a typed constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -2012,37 +2012,27 @@ private static bool AreConstantsEqual(ref ExprTree xTree, ref ExprNode x, ref Ex { var xObj = GetStoredConstantValue(ref xTree, ref x); var yObj = GetStoredConstantValue(ref yTree, ref y); - if (!ReferenceEquals(x.Obj, ExprNode.InlineValueMarker) && !ReferenceEquals(y.Obj, ExprNode.InlineValueMarker)) + var xInline = ReferenceEquals(x.Obj, ExprNode.InlineValueMarker); + var yInline = ReferenceEquals(y.Obj, ExprNode.InlineValueMarker); + if (!(xInline && yInline)) return ReferenceEquals(xObj, yObj) || Equals(xObj, yObj); if (x.Type.IsEnum) { - if (ReferenceEquals(x.Obj, ExprNode.InlineValueMarker) && ReferenceEquals(y.Obj, ExprNode.InlineValueMarker)) - return x.InlineValue == y.InlineValue; - return Type.GetTypeCode(Enum.GetUnderlyingType(x.Type)) switch - { - TypeCode.Byte => GetInlineOrConvertedByte(ref xTree, ref x) == GetInlineOrConvertedByte(ref yTree, ref y), - TypeCode.SByte => GetInlineOrConvertedSByte(ref xTree, ref x) == GetInlineOrConvertedSByte(ref yTree, ref y), - TypeCode.Char => GetInlineOrConvertedChar(ref xTree, ref x) == GetInlineOrConvertedChar(ref yTree, ref y), - TypeCode.Int16 => GetInlineOrConvertedInt16(ref xTree, ref x) == GetInlineOrConvertedInt16(ref yTree, ref y), - TypeCode.UInt16 => GetInlineOrConvertedUInt16(ref xTree, ref x) == GetInlineOrConvertedUInt16(ref yTree, ref y), - TypeCode.Int32 => GetInlineOrConvertedInt32(ref xTree, ref x) == GetInlineOrConvertedInt32(ref yTree, ref y), - TypeCode.UInt32 => GetInlineOrConvertedUInt32(ref xTree, ref x) == GetInlineOrConvertedUInt32(ref yTree, ref y), - var tc => FlatExpressionThrow.UnsupportedInlineConstantType(x.Type, tc) - }; + return x.InlineValue == y.InlineValue; } return Type.GetTypeCode(x.Type) switch { - TypeCode.Boolean => GetInlineOrStoredBoolean(ref xTree, ref x) == GetInlineOrStoredBoolean(ref yTree, ref y), - TypeCode.Byte => GetInlineOrStoredByte(ref xTree, ref x) == GetInlineOrStoredByte(ref yTree, ref y), - TypeCode.SByte => GetInlineOrStoredSByte(ref xTree, ref x) == GetInlineOrStoredSByte(ref yTree, ref y), - TypeCode.Char => GetInlineOrStoredChar(ref xTree, ref x) == GetInlineOrStoredChar(ref yTree, ref y), - TypeCode.Int16 => GetInlineOrStoredInt16(ref xTree, ref x) == GetInlineOrStoredInt16(ref yTree, ref y), - TypeCode.UInt16 => GetInlineOrStoredUInt16(ref xTree, ref x) == GetInlineOrStoredUInt16(ref yTree, ref y), - TypeCode.Int32 => GetInlineOrStoredInt32(ref xTree, ref x) == GetInlineOrStoredInt32(ref yTree, ref y), - TypeCode.UInt32 => GetInlineOrStoredUInt32(ref xTree, ref x) == GetInlineOrStoredUInt32(ref yTree, ref y), - TypeCode.Single => GetInlineOrStoredSingle(ref xTree, ref x).Equals(GetInlineOrStoredSingle(ref yTree, ref y)), + TypeCode.Boolean => GetInlineBoolean(ref x) == GetInlineBoolean(ref y), + TypeCode.Byte => GetInlineByte(ref x) == GetInlineByte(ref y), + TypeCode.SByte => GetInlineSByte(ref x) == GetInlineSByte(ref y), + TypeCode.Char => GetInlineChar(ref x) == GetInlineChar(ref y), + TypeCode.Int16 => GetInlineInt16(ref x) == GetInlineInt16(ref y), + TypeCode.UInt16 => GetInlineUInt16(ref x) == GetInlineUInt16(ref y), + TypeCode.Int32 => GetInlineInt32(ref x) == GetInlineInt32(ref y), + TypeCode.UInt32 => GetInlineUInt32(ref x) == GetInlineUInt32(ref y), + TypeCode.Single => GetInlineSingle(ref x).Equals(GetInlineSingle(ref y)), _ => ReferenceEquals(xObj, yObj) || Equals(xObj, yObj) }; } @@ -2081,68 +2071,31 @@ private static int GetInlineConstantHashCode(Type type, uint data) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetInlineOrStoredBoolean(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? node.InlineValue != 0 : (bool)GetStoredConstantValue(ref tree, ref node); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetInlineOrStoredByte(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (byte)node.InlineValue : (byte)GetStoredConstantValue(ref tree, ref node); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static sbyte GetInlineOrStoredSByte(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (sbyte)(byte)node.InlineValue : (sbyte)GetStoredConstantValue(ref tree, ref node); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static char GetInlineOrStoredChar(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (char)(ushort)node.InlineValue : (char)GetStoredConstantValue(ref tree, ref node); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static short GetInlineOrStoredInt16(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (short)(ushort)node.InlineValue : (short)GetStoredConstantValue(ref tree, ref node); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort GetInlineOrStoredUInt16(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (ushort)node.InlineValue : (ushort)GetStoredConstantValue(ref tree, ref node); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetInlineOrStoredInt32(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (int)node.InlineValue : (int)GetStoredConstantValue(ref tree, ref node); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint GetInlineOrStoredUInt32(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? node.InlineValue : (uint)GetStoredConstantValue(ref tree, ref node); + private static bool GetInlineBoolean(ref ExprNode node) => node.InlineValue != 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetInlineOrStoredSingle(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? FloatBits.ToFloat(node.InlineValue) : (float)GetStoredConstantValue(ref tree, ref node); + private static byte GetInlineByte(ref ExprNode node) => (byte)node.InlineValue; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetInlineOrConvertedByte(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (byte)node.InlineValue : System.Convert.ToByte(GetStoredConstantValue(ref tree, ref node)); + private static sbyte GetInlineSByte(ref ExprNode node) => (sbyte)(byte)node.InlineValue; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static sbyte GetInlineOrConvertedSByte(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (sbyte)(byte)node.InlineValue : System.Convert.ToSByte(GetStoredConstantValue(ref tree, ref node)); + private static char GetInlineChar(ref ExprNode node) => (char)(ushort)node.InlineValue; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static char GetInlineOrConvertedChar(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (char)(ushort)node.InlineValue : System.Convert.ToChar(GetStoredConstantValue(ref tree, ref node)); + private static short GetInlineInt16(ref ExprNode node) => (short)(ushort)node.InlineValue; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static short GetInlineOrConvertedInt16(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (short)(ushort)node.InlineValue : System.Convert.ToInt16(GetStoredConstantValue(ref tree, ref node)); + private static ushort GetInlineUInt16(ref ExprNode node) => (ushort)node.InlineValue; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort GetInlineOrConvertedUInt16(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (ushort)node.InlineValue : System.Convert.ToUInt16(GetStoredConstantValue(ref tree, ref node)); + private static int GetInlineInt32(ref ExprNode node) => (int)node.InlineValue; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetInlineOrConvertedInt32(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? (int)node.InlineValue : System.Convert.ToInt32(GetStoredConstantValue(ref tree, ref node)); + private static uint GetInlineUInt32(ref ExprNode node) => node.InlineValue; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint GetInlineOrConvertedUInt32(ref ExprTree tree, ref ExprNode node) => - ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) ? node.InlineValue : System.Convert.ToUInt32(GetStoredConstantValue(ref tree, ref node)); + private static float GetInlineSingle(ref ExprNode node) => FloatBits.ToFloat(node.InlineValue); private struct TraversalFrame { From 005b4bf2febe77af5510d60133d30fa969c12c2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 10:30:19 +0000 Subject: [PATCH 10/15] refactor: assert normalized flat constants --- .../FlatExpression.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 6a78edd3..2ff56db5 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -107,6 +107,7 @@ public struct ExprNode internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0, int nextIdx = 0) { + Debug.Assert(!RequiresInlineConstantStorage(type, obj, nodeType)); Type = type; Obj = obj; var tag = (byte)((flags << FlagsShift) | (byte)kind); @@ -137,6 +138,23 @@ internal void SetChildInfo(int childIdx, int childCount) => [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool IsExpression() => Kind == ExprNodeKind.Expression; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool RequiresInlineConstantStorage(Type type, object obj, ExpressionType nodeType) + { + if (nodeType != ExpressionType.Constant || obj == null || ReferenceEquals(obj, InlineValueMarker)) + return false; + + return type.IsEnum + ? IsSmallPrimitive(Type.GetTypeCode(Enum.GetUnderlyingType(type))) + : type.IsPrimitive && IsSmallPrimitive(Type.GetTypeCode(type)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSmallPrimitive(TypeCode tc) => + tc == TypeCode.Boolean || tc == TypeCode.Byte || tc == TypeCode.SByte || + tc == TypeCode.Char || tc == TypeCode.Int16 || tc == TypeCode.UInt16 || + tc == TypeCode.Int32 || tc == TypeCode.UInt32 || tc == TypeCode.Single; + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool HasFlag(byte flag) => (Flags & flag) != 0; @@ -2005,22 +2023,29 @@ private static int GetConstantHashCode(ref ExprTree tree, ref ExprNode node) { if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker)) return GetInlineConstantHashCode(node.Type, node.InlineValue); + + Debug.Assert(!ExprNode.RequiresInlineConstantStorage(node.Type, node.Obj, node.NodeType)); return GetStoredConstantValue(ref tree, ref node)?.GetHashCode() ?? 0; } private static bool AreConstantsEqual(ref ExprTree xTree, ref ExprNode x, ref ExprTree yTree, ref ExprNode y) { - var xObj = GetStoredConstantValue(ref xTree, ref x); - var yObj = GetStoredConstantValue(ref yTree, ref y); var xInline = ReferenceEquals(x.Obj, ExprNode.InlineValueMarker); var yInline = ReferenceEquals(y.Obj, ExprNode.InlineValueMarker); - if (!(xInline && yInline)) + Debug.Assert(xInline == yInline); + if (xInline != yInline) + return false; + + if (!xInline) + { + Debug.Assert(!ExprNode.RequiresInlineConstantStorage(x.Type, x.Obj, x.NodeType)); + var xObj = GetStoredConstantValue(ref xTree, ref x); + var yObj = GetStoredConstantValue(ref yTree, ref y); return ReferenceEquals(xObj, yObj) || Equals(xObj, yObj); + } if (x.Type.IsEnum) - { return x.InlineValue == y.InlineValue; - } return Type.GetTypeCode(x.Type) switch { @@ -2033,7 +2058,7 @@ private static bool AreConstantsEqual(ref ExprTree xTree, ref ExprNode x, ref Ex TypeCode.Int32 => GetInlineInt32(ref x) == GetInlineInt32(ref y), TypeCode.UInt32 => GetInlineUInt32(ref x) == GetInlineUInt32(ref y), TypeCode.Single => GetInlineSingle(ref x).Equals(GetInlineSingle(ref y)), - _ => ReferenceEquals(xObj, yObj) || Equals(xObj, yObj) + _ => FlatExpressionThrow.UnsupportedInlineConstantType(x.Type) }; } From 0c059a4ba2bbef1b52a673f180a8a2bccaae7e25 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 12:33:30 +0000 Subject: [PATCH 11/15] refactor: simplify inline constant comparison --- .../FlatExpression.cs | 45 ++++--------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 2ff56db5..c281d993 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -2049,15 +2049,15 @@ private static bool AreConstantsEqual(ref ExprTree xTree, ref ExprNode x, ref Ex return Type.GetTypeCode(x.Type) switch { - TypeCode.Boolean => GetInlineBoolean(ref x) == GetInlineBoolean(ref y), - TypeCode.Byte => GetInlineByte(ref x) == GetInlineByte(ref y), - TypeCode.SByte => GetInlineSByte(ref x) == GetInlineSByte(ref y), - TypeCode.Char => GetInlineChar(ref x) == GetInlineChar(ref y), - TypeCode.Int16 => GetInlineInt16(ref x) == GetInlineInt16(ref y), - TypeCode.UInt16 => GetInlineUInt16(ref x) == GetInlineUInt16(ref y), - TypeCode.Int32 => GetInlineInt32(ref x) == GetInlineInt32(ref y), - TypeCode.UInt32 => GetInlineUInt32(ref x) == GetInlineUInt32(ref y), - TypeCode.Single => GetInlineSingle(ref x).Equals(GetInlineSingle(ref y)), + TypeCode.Boolean => x.InlineValue == y.InlineValue, + TypeCode.Byte => x.InlineValue == y.InlineValue, + TypeCode.SByte => x.InlineValue == y.InlineValue, + TypeCode.Char => x.InlineValue == y.InlineValue, + TypeCode.Int16 => x.InlineValue == y.InlineValue, + TypeCode.UInt16 => x.InlineValue == y.InlineValue, + TypeCode.Int32 => x.InlineValue == y.InlineValue, + TypeCode.UInt32 => x.InlineValue == y.InlineValue, + TypeCode.Single => FloatBits.ToFloat(x.InlineValue).Equals(FloatBits.ToFloat(y.InlineValue)), _ => FlatExpressionThrow.UnsupportedInlineConstantType(x.Type) }; } @@ -2095,33 +2095,6 @@ private static int GetInlineConstantHashCode(Type type, uint data) }; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool GetInlineBoolean(ref ExprNode node) => node.InlineValue != 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte GetInlineByte(ref ExprNode node) => (byte)node.InlineValue; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static sbyte GetInlineSByte(ref ExprNode node) => (sbyte)(byte)node.InlineValue; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static char GetInlineChar(ref ExprNode node) => (char)(ushort)node.InlineValue; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static short GetInlineInt16(ref ExprNode node) => (short)(ushort)node.InlineValue; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ushort GetInlineUInt16(ref ExprNode node) => (ushort)node.InlineValue; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetInlineInt32(ref ExprNode node) => (int)node.InlineValue; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint GetInlineUInt32(ref ExprNode node) => node.InlineValue; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float GetInlineSingle(ref ExprNode node) => FloatBits.ToFloat(node.InlineValue); - private struct TraversalFrame { public readonly int XNextIdx; From baad46f9fb55654e363708ef7a236ef31d5e9f19 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 15:59:56 +0000 Subject: [PATCH 12/15] refactor: simplify flat constant structural comparison --- .../FlatExpression.cs | 82 ++++++------------- 1 file changed, 25 insertions(+), 57 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index c281d993..35c8b643 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -1839,7 +1839,10 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) var frame = _eqFrames[_eqFrames.Count - 1]; _eqFrames.Count--; - RestoreParameterScope(frame.XParameterCount, frame.YParameterCount); + if (frame.XParameterCount >= 0) + _xParameterIds.Count = frame.XParameterCount; + if (frame.YParameterCount >= 0) + _yParameterIds.Count = frame.YParameterCount; if (frame.RemainingSiblingsAfterNode != 0) { xIdx = frame.XNextIdx; @@ -1891,19 +1894,8 @@ private static bool AreEquivalentParameterDeclarations(ref ExprNode x, ref ExprN y.NodeType == ExpressionType.Parameter && x.HasSameShapeExceptLinks(ref y); - private static bool EqObj(ref ExprNode x, ref ExprNode y) - { - return ReferenceEquals(x.Obj, y.Obj) || Equals(x.Obj, y.Obj); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void RestoreParameterScope(int xParameterCount, int yParameterCount) - { - if (xParameterCount >= 0) - _xParameterIds.Count = xParameterCount; - if (yParameterCount >= 0) - _yParameterIds.Count = yParameterCount; - } + private static bool EqObj(ref ExprNode x, ref ExprNode y) => + ReferenceEquals(x.Obj, y.Obj) || Equals(x.Obj, y.Obj); private int HashNode(ref ExprTree tree, int idx) { @@ -2041,25 +2033,17 @@ private static bool AreConstantsEqual(ref ExprTree xTree, ref ExprNode x, ref Ex Debug.Assert(!ExprNode.RequiresInlineConstantStorage(x.Type, x.Obj, x.NodeType)); var xObj = GetStoredConstantValue(ref xTree, ref x); var yObj = GetStoredConstantValue(ref yTree, ref y); - return ReferenceEquals(xObj, yObj) || Equals(xObj, yObj); + return xObj?.Equals(yObj) ?? yObj == null; } if (x.Type.IsEnum) return x.InlineValue == y.InlineValue; - return Type.GetTypeCode(x.Type) switch - { - TypeCode.Boolean => x.InlineValue == y.InlineValue, - TypeCode.Byte => x.InlineValue == y.InlineValue, - TypeCode.SByte => x.InlineValue == y.InlineValue, - TypeCode.Char => x.InlineValue == y.InlineValue, - TypeCode.Int16 => x.InlineValue == y.InlineValue, - TypeCode.UInt16 => x.InlineValue == y.InlineValue, - TypeCode.Int32 => x.InlineValue == y.InlineValue, - TypeCode.UInt32 => x.InlineValue == y.InlineValue, - TypeCode.Single => FloatBits.ToFloat(x.InlineValue).Equals(FloatBits.ToFloat(y.InlineValue)), - _ => FlatExpressionThrow.UnsupportedInlineConstantType(x.Type) - }; + var typeCode = Type.GetTypeCode(x.Type); + Debug.Assert(IsSmallPrimitive(typeCode)); + return typeCode != TypeCode.Single + ? x.InlineValue == y.InlineValue + : FloatBits.ToFloat(x.InlineValue).Equals(FloatBits.ToFloat(y.InlineValue)); } private static object GetStoredConstantValue(ref ExprTree tree, ref ExprNode node) => @@ -2067,49 +2051,33 @@ private static object GetStoredConstantValue(ref ExprTree tree, ref ExprNode nod private static int GetInlineConstantHashCode(Type type, uint data) { - if (type.IsEnum) - return Type.GetTypeCode(Enum.GetUnderlyingType(type)) switch - { - TypeCode.Byte => ((byte)data).GetHashCode(), - TypeCode.SByte => ((sbyte)(byte)data).GetHashCode(), - TypeCode.Char => ((char)(ushort)data).GetHashCode(), - TypeCode.Int16 => ((short)(ushort)data).GetHashCode(), - TypeCode.UInt16 => ((ushort)data).GetHashCode(), - TypeCode.Int32 => ((int)data).GetHashCode(), - TypeCode.UInt32 => data.GetHashCode(), - var tc => FlatExpressionThrow.UnsupportedInlineConstantType(type, tc) - }; - - return Type.GetTypeCode(type) switch + if (!type.IsEnum) { - TypeCode.Boolean => (data != 0).GetHashCode(), - TypeCode.Byte => ((byte)data).GetHashCode(), - TypeCode.SByte => ((sbyte)(byte)data).GetHashCode(), - TypeCode.Char => ((char)(ushort)data).GetHashCode(), - TypeCode.Int16 => ((short)(ushort)data).GetHashCode(), - TypeCode.UInt16 => ((ushort)data).GetHashCode(), - TypeCode.Int32 => ((int)data).GetHashCode(), - TypeCode.UInt32 => data.GetHashCode(), - TypeCode.Single => FloatBits.ToFloat(data).GetHashCode(), - _ => FlatExpressionThrow.UnsupportedInlineConstantType(type) - }; + var typeCode = Type.GetTypeCode(type); + Debug.Assert(IsSmallPrimitive(typeCode)); + if (typeCode == TypeCode.Single) + return FloatBits.ToFloat(data).GetHashCode(); + } + + return data.GetHashCode(); } + [StructLayout(LayoutKind.Sequential)] private struct TraversalFrame { - public readonly int XNextIdx; - public readonly int YNextIdx; public readonly int RemainingSiblingsAfterNode; public readonly int XParameterCount; public readonly int YParameterCount; + public readonly ushort XNextIdx; + public readonly ushort YNextIdx; public TraversalFrame(int xNextIdx, int yNextIdx, int remainingSiblingsAfterNode, int xParameterCount, int yParameterCount) { - XNextIdx = xNextIdx; - YNextIdx = yNextIdx; RemainingSiblingsAfterNode = remainingSiblingsAfterNode; XParameterCount = xParameterCount; YParameterCount = yParameterCount; + XNextIdx = checked((ushort)xNextIdx); + YNextIdx = checked((ushort)yNextIdx); } } } From c1962732c4a21c5d32598731e6da2256f46ac910 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:07:26 +0000 Subject: [PATCH 13/15] Apply remaining changes --- .../FlatExpression.cs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 35c8b643..fe1364b3 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -139,15 +139,11 @@ internal void SetChildInfo(int childIdx, int childCount) => internal bool IsExpression() => Kind == ExprNodeKind.Expression; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool RequiresInlineConstantStorage(Type type, object obj, ExpressionType nodeType) - { - if (nodeType != ExpressionType.Constant || obj == null || ReferenceEquals(obj, InlineValueMarker)) - return false; - - return type.IsEnum + internal static bool RequiresInlineConstantStorage(Type type, object obj, ExpressionType nodeType) => + nodeType == ExpressionType.Constant && obj != null && !ReferenceEquals(obj, InlineValueMarker) && + (type.IsEnum ? IsSmallPrimitive(Type.GetTypeCode(Enum.GetUnderlyingType(type))) - : type.IsPrimitive && IsSmallPrimitive(Type.GetTypeCode(type)); - } + : type.IsPrimitive && IsSmallPrimitive(Type.GetTypeCode(type))); [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool IsSmallPrimitive(TypeCode tc) => @@ -155,8 +151,16 @@ private static bool IsSmallPrimitive(TypeCode tc) => tc == TypeCode.Char || tc == TypeCode.Int16 || tc == TypeCode.UInt16 || tc == TypeCode.Int32 || tc == TypeCode.UInt32 || tc == TypeCode.Single; + [Flags] + private enum NodeFlags : byte { None = 0 } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool HasFlag(byte flag) => (Flags & flag) != 0; + internal bool HasFlag(byte flag) => +#if NET6_0_OR_GREATER + ((NodeFlags)Flags).HasFlag((NodeFlags)flag); +#else + (Flags & flag) != 0; +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] internal bool HasSameHeaderExceptNext(ref ExprNode other) => @@ -1712,8 +1716,9 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) restoreYParameterCount = _yParameterIds.Count; descendX = x.ChildIdx; descendY = y.ChildIdx; - descendChildCount = x.ChildCount - (x.HasFlag(CatchHasVariableFlag) ? 1 : 0); - if (x.HasFlag(CatchHasVariableFlag)) + var hasVariable = x.Flags & CatchHasVariableFlag; + descendChildCount = x.ChildCount - hasVariable; + if (hasVariable != 0) { ref var xv = ref xTree.Nodes.GetSurePresentRef(descendX); ref var yv = ref yTree.Nodes.GetSurePresentRef(descendY); @@ -1838,7 +1843,7 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) return true; var frame = _eqFrames[_eqFrames.Count - 1]; - _eqFrames.Count--; + _eqFrames.Count -= 1; if (frame.XParameterCount >= 0) _xParameterIds.Count = frame.XParameterCount; if (frame.YParameterCount >= 0) @@ -2065,11 +2070,11 @@ private static int GetInlineConstantHashCode(Type type, uint data) [StructLayout(LayoutKind.Sequential)] private struct TraversalFrame { - public readonly int RemainingSiblingsAfterNode; - public readonly int XParameterCount; - public readonly int YParameterCount; - public readonly ushort XNextIdx; - public readonly ushort YNextIdx; + public int RemainingSiblingsAfterNode; + public int XParameterCount; + public int YParameterCount; + public ushort XNextIdx; + public ushort YNextIdx; public TraversalFrame(int xNextIdx, int yNextIdx, int remainingSiblingsAfterNode, int xParameterCount, int yParameterCount) { From 09a0ac1c76654544005f8454e5b6fefaf4a6c96d Mon Sep 17 00:00:00 2001 From: dadhi Date: Sun, 21 Jun 2026 00:20:59 +0200 Subject: [PATCH 14/15] inlining --- .vscode/settings.json | 1 + .../FlatExpression.cs | 61 +++++++------------ .../LightExpressionTests.cs | 2 - 3 files changed, 23 insertions(+), 41 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 98afea1c..bd22d1da 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "Funcs", "gotos", "Hasher", + "idxs", "iface", "ifaces", "ifthen", diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index fe1364b3..edaed427 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -22,7 +22,7 @@ namespace FastExpressionCompiler.FlatExpression; public enum ExprNodeKind : byte { /// Represents a regular expression node. - Expression, + Expression = 0, /// Represents a switch case payload. SwitchCase, /// Represents a catch block payload. @@ -252,11 +252,8 @@ public struct ExprTree : IEquatable public SmallList, NoArrayPool> LambdaClosureParameterUsages; /// Adds a parameter node and returns its idx. - public int Parameter(Type type, string name = null) - { - var id = Nodes.Count + 1; - return AddRawLeafExpressionNode(type, name, ExpressionType.Parameter, type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: id); - } + public int Parameter(Type type, string name = null) => + AddLeafNode(type, name, ExpressionType.Parameter, flags: type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: Nodes.Count + 1); /// Adds a typed parameter node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -268,7 +265,7 @@ public int Parameter(Type type, string name = null) /// Adds a default-value node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Default(Type type) => AddRawExpressionNode(type, null, ExpressionType.Default); + public int Default(Type type) => AddLeafNode(type, null, ExpressionType.Default); /// Adds a constant node using the runtime type of the supplied value. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -279,34 +276,28 @@ public int Constant(object value) => public int Constant(object value, Type type) { if (value == null || value is string || value is Type || value is decimal) - return AddRawExpressionNode(type, value, ExpressionType.Constant); + return AddLeafNode(type, value, ExpressionType.Constant); if (type.IsEnum) - { - var underlyingTc = Type.GetTypeCode(Enum.GetUnderlyingType(type)); - if (IsSmallPrimitive(underlyingTc)) - return AddInlineConstantNode(type, (uint)System.Convert.ToInt64(value)); - // long/ulong-backed enum (extremely rare): store boxed in Obj - return AddRawExpressionNode(type, value, ExpressionType.Constant); - } + return IsSmallPrimitive(Type.GetTypeCode(Enum.GetUnderlyingType(type))) + ? AddInlineConstantNode(type, (uint)System.Convert.ToInt64(value)) + : AddLeafNode(type, value, ExpressionType.Constant); // long/ulong-backed enum (extremely rare): store boxed in Obj if (type.IsPrimitive) { var tc = Type.GetTypeCode(type); - if (IsSmallPrimitive(tc)) - return AddInlineConstantNode(type, ToInlineValue(value, tc)); - // long, ulong, double: primitive but too wide for _data, store boxed in Obj - return AddRawExpressionNode(type, value, ExpressionType.Constant); + return IsSmallPrimitive(tc) + ? AddInlineConstantNode(type, ToInlineValue(value, tc)) + : AddLeafNode(type, value, ExpressionType.Constant); // long, ulong, double: primitive but too wide for _data, store boxed in Obj } // Delegate, array types, and user-defined reference/value types go to ClosureConstants - var constantIdx = ClosureConstants.Add(value); - return AddRawLeafExpressionNode(type, ClosureConstantMarker, ExpressionType.Constant, childIdx: constantIdx); + return AddLeafNode(type, ClosureConstantMarker, ExpressionType.Constant, childIdx: ClosureConstants.Add(value)); } /// Adds a null constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantNull(Type type = null) => AddRawExpressionNode(type ?? typeof(object), null, ExpressionType.Constant); + public int ConstantNull(Type type = null) => AddLeafNode(type ?? typeof(object), null, ExpressionType.Constant); /// Adds an constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -321,7 +312,7 @@ public int Constant(object value, Type type) public int New(Type type) { if (type.IsValueType) - return AddRawExpressionNode(type, null, ExpressionType.New); + return AddLeafNode(type, null, ExpressionType.New); foreach (var ctor in type.GetConstructors()) if (ctor.GetParameters().Length == 0) @@ -362,7 +353,7 @@ public int Call(int instance, System.Reflection.MethodInfo method, params int[] public int MakeMemberAccess(int? instance, System.Reflection.MemberInfo member) => instance.HasValue ? AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, instance.Value) - : AddRawExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess); + : AddLeafNode(GetMemberType(member), member, ExpressionType.MemberAccess); /// Adds a field-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -832,10 +823,6 @@ private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeT return AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, in cloned); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType) => - AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, 0, 0, 0); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in children); @@ -848,10 +835,6 @@ private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, 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); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child) => AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child)); @@ -939,12 +922,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.AddLeafNode(expression.Type, null, expression.NodeType); case ExpressionType.Parameter: { var parameter = (SysParameterExpression)expression; - return _tree.AddRawLeafExpressionNode(expression.Type, parameter.Name, expression.NodeType, - parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); + return _tree.AddLeafNode(expression.Type, parameter.Name, expression.NodeType, + flags: parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); } case ExpressionType.Lambda: { @@ -957,7 +940,7 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(lambda.Body)); for (var i = 0; i < lambda.Parameters.Count; ++i) children.Add(AddExpression(lambda.Parameters[i])); - var lambdaIdx = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + var lambdaIdx = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children); _tree.LambdaNodes.Add(lambdaIdx); _tree.CollectLambdaClosureParameterUsages(lambdaIdx); return lambdaIdx; @@ -1048,8 +1031,7 @@ private int AddExpression(SysExpr expression) children.Add(AddExpression(conditional.Test)); children.Add(AddExpression(conditional.IfTrue)); children.Add(AddExpression(conditional.IfFalse)); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, - children[0], children[1], children[2]); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children[0], children[1], children[2]); } case ExpressionType.Loop: { @@ -1281,7 +1263,8 @@ private static int GetId(ref SmallMap16> ids, object } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddLeafNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int childIdx, int childCount) + private int AddLeafNode(Type type, object obj, ExpressionType nodeType, + ExprNodeKind kind = ExprNodeKind.Expression, byte flags = default, int childIdx = default, int childCount = default) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 98084bd8..df384290 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -11,7 +11,6 @@ namespace FastExpressionCompiler.LightExpression.UnitTests { - public partial class LightExpressionTests : ITest { public int Run() @@ -62,7 +61,6 @@ public int Run() return 42; } - public void Can_compile_lambda_without_converting_to_expression() { var funcExpr = Lambda( From 9624f3a137b2345df5c5870cabda8f211e29148b Mon Sep 17 00:00:00 2001 From: dadhi Date: Mon, 22 Jun 2026 08:25:07 +0200 Subject: [PATCH 15/15] @wip starting simplification --- .../FlatExpression.cs | 494 ++++++++---------- src/FastExpressionCompiler/ImTools.cs | 4 + 2 files changed, 219 insertions(+), 279 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index edaed427..d4265395 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -5,11 +5,12 @@ namespace FastExpressionCompiler.FlatExpression; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; +using System.Reflection; 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; +using ChildList = LightExpression.ImTools.SmallList, LightExpression.ImTools.NoArrayPool>; +using LightExpression = LightExpression.Expression; using SysCatchBlock = System.Linq.Expressions.CatchBlock; using SysElementInit = System.Linq.Expressions.ElementInit; using SysExpr = System.Linq.Expressions.Expression; @@ -46,25 +47,16 @@ public enum ExprNodeKind : byte } /// Stores one flat expression node and its child-link metadata in 24 bytes on 64-bit runtimes. -/// -/// Layout (64-bit): Type(8) | Obj(8) | _meta(4) | _data(4) = 24 bytes. -/// _meta bits: NodeType(8)|Tag(8)|NextIdx(16). -/// _data bits: ChildCount(16)|ChildIdx(16) for regular nodes, -/// or the raw 32-bit value for inline primitive constants (when == ). -/// [StructLayout(LayoutKind.Explicit, Size = 24)] public struct ExprNode { // _meta layout: bits [31:24]=NodeType | [23:20]=Flags | [19:16]=Kind | [15:0]=NextIdx - private const int MetaNodeTypeShift = 24; private const int MetaTagShift = 16; - private const uint MetaKeepWithoutNext = 0xFFFF0000u; // _data layout: bits [31:16]=ChildCount | [15:0]=ChildIdx (or full uint for inline constants) - private const int DataCountShift = 16; - private const uint DataKeepWithoutChildIdx = 0xFFFF0000u; - private const uint DataIdxMask = 0xFFFFu; + private const int ChildCountShift = 16; + private const uint ChildCountMask = 0xFFFF0000u; + private const uint ChildIdxMask = 0xFFFFu; private const int FlagsShift = 4; - private const uint KindMask = 0x0Fu; /// Sentinel placed in to indicate the node holds a small primitive constant in . internal static readonly object InlineValueMarker = new(); @@ -77,67 +69,71 @@ public struct ExprNode [FieldOffset(8)] public object Obj; - /// NodeType(8b) | Tag=(Flags:4b|Kind:4b)(8b) | NextIdx(16b) + /// ChildCount(16b) | ChildIdx(16b) or raw 32-bit inline constant value. [FieldOffset(16)] - private uint _meta; + private uint _child; - /// ChildCount(16b) | ChildIdx(16b) or raw 32-bit inline constant value. + /// Index of the next sibling node if any. [FieldOffset(20)] - private uint _data; + public ushort NextIdx; + + [FieldOffset(22)] + private byte _nodeType; + + /// 4bits:Flags|4bits:Kind + [FieldOffset(23)] + public byte FlagsAndKind; /// Gets the expression kind encoded for this node. - public ExpressionType NodeType => (ExpressionType)(_meta >> MetaNodeTypeShift); + public ExpressionType NodeType => (ExpressionType)_nodeType; /// Gets the payload classification for this node. - public ExprNodeKind Kind => (ExprNodeKind)((_meta >> MetaTagShift) & KindMask); - - internal byte Flags => (byte)((_meta >> (MetaTagShift + FlagsShift)) & 0xFu); + public ExprNodeKind Kind => (ExprNodeKind)(FlagsAndKind & 0b1111); - /// Gets the next sibling node idx. - public int NextIdx => (int)(_meta & 0xFFFFu); + internal byte Flags => (byte)(FlagsAndKind >> 4); /// Gets the number of direct children linked from this node. - public int ChildCount => (int)(_data >> DataCountShift); + public ushort ChildCount => (ushort)(_child >> ChildCountShift); /// Gets the first child idx or an auxiliary payload idx. - public int ChildIdx => (int)(_data & DataIdxMask); + public ushort ChildIdx => (ushort)(_child & ChildIdxMask); + + public void SetChild(ushort childCount, ushort childIdx) => _child = ((uint)childCount << ChildCountShift) | childIdx; /// Gets the raw 32-bit value for inline primitive constants. Only valid when == . - internal uint InlineValue => _data; + internal uint InlineValue => _child; + + internal ExprNode(ExpressionType nodeType, Type type, object obj = null) + { + Type = type; + Obj = obj; + _nodeType = (byte)nodeType; + } - internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0, int nextIdx = 0) + internal ExprNode(ExpressionType nodeType, Type type, object obj, + ExprNodeKind kind = default, byte flags = default, + ushort childIdx = 0, ushort childCount = 0, ushort nextIdx = 0) { - Debug.Assert(!RequiresInlineConstantStorage(type, obj, nodeType)); Type = type; Obj = obj; - var tag = (byte)((flags << FlagsShift) | (byte)kind); - _meta = ((uint)(byte)nodeType << MetaNodeTypeShift) | ((uint)tag << MetaTagShift) | checked((ushort)nextIdx); - _data = ((uint)checked((ushort)childCount) << DataCountShift) | checked((ushort)childIdx); + _child = ((uint)childCount << ChildCountShift) | childIdx; + NextIdx = nextIdx; + _nodeType = (byte)nodeType; + FlagsAndKind = (byte)((flags << 4) | ((byte)kind & 0b1111)); } - /// Constructs an inline primitive constant node; is set to . + /// Constructs an inline primitive constant node, is set to . internal ExprNode(Type type, uint inlineValue) { Type = type; Obj = InlineValueMarker; - _meta = (uint)(byte)ExpressionType.Constant << MetaNodeTypeShift; - _data = inlineValue; + _nodeType = (byte)ExpressionType.Constant; + _child = inlineValue; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetNextIdx(int nextIdx) => - _meta = (_meta & MetaKeepWithoutNext) | checked((ushort)nextIdx); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetChildInfo(int childIdx, int childCount) => - _data = ((uint)checked((ushort)childCount) << DataCountShift) | checked((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 static bool RequiresInlineConstantStorage(Type type, object obj, ExpressionType nodeType) => nodeType == ExpressionType.Constant && obj != null && !ReferenceEquals(obj, InlineValueMarker) && @@ -151,35 +147,18 @@ private static bool IsSmallPrimitive(TypeCode tc) => tc == TypeCode.Char || tc == TypeCode.Int16 || tc == TypeCode.UInt16 || tc == TypeCode.Int32 || tc == TypeCode.UInt32 || tc == TypeCode.Single; - [Flags] - private enum NodeFlags : byte { None = 0 } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool HasFlag(byte flag) => -#if NET6_0_OR_GREATER - ((NodeFlags)Flags).HasFlag((NodeFlags)flag); -#else - (Flags & flag) != 0; -#endif + internal bool HasFlag(byte flag) => (Flags & flag) != 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool HasSameHeaderExceptNext(ref ExprNode other) => - Type == other.Type && (_meta & MetaKeepWithoutNext) == (other._meta & MetaKeepWithoutNext); + internal bool HasSameShapeExceptChildIdx(ref ExprNode other) => + Type == other.Type && NodeType == other.NodeType && FlagsAndKind == other.FlagsAndKind && + (_child & ChildCountMask) == (other._child & ChildCountMask); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool HasSameShapeExceptLinks(ref ExprNode other) => - HasSameHeaderExceptNext(ref other) && - (_data & DataKeepWithoutChildIdx) == (other._data & DataKeepWithoutChildIdx); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool HasSameShapeExceptNext(ref ExprNode other) => - HasSameHeaderExceptNext(ref other) && _data == other._data; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool ShouldCloneWhenLinked() => - ReferenceEquals(Obj, InlineValueMarker) || - Kind == ExprNodeKind.LabelTarget || NodeType == ExpressionType.Parameter || - Kind == ExprNodeKind.ObjectReference || ChildCount == 0; + internal bool HasSameShape(ref ExprNode other) => + Type == other.Type && NodeType == other.NodeType && FlagsAndKind == other.FlagsAndKind && + _child == other._child; } /// Maps a lambda node to a captured outer parameter or variable. @@ -252,8 +231,9 @@ public struct ExprTree : IEquatable public SmallList, NoArrayPool> LambdaClosureParameterUsages; /// Adds a parameter node and returns its idx. - public int Parameter(Type type, string name = null) => - AddLeafNode(type, name, ExpressionType.Parameter, flags: type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: Nodes.Count + 1); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Parameter(Type type, string name = null) => + Nodes.Add(new(ExpressionType.Parameter, type, name, flags: type.IsByRef ? ParameterByRefFlag : (byte)0)); /// Adds a typed parameter node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -265,54 +245,33 @@ public int Parameter(Type type, string name = null) => /// Adds a default-value node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Default(Type type) => AddLeafNode(type, null, ExpressionType.Default); - - /// Adds a constant node using the runtime type of the supplied value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Constant(object value) => - Constant(value, value?.GetType() ?? typeof(object)); + public int Default(Type type) => Nodes.Add(new(ExpressionType.Default, type, null)); /// Adds a constant node with an explicit constant type. - public int Constant(object value, Type type) - { - if (value == null || value is string || value is Type || value is decimal) - return AddLeafNode(type, value, ExpressionType.Constant); - - if (type.IsEnum) - return IsSmallPrimitive(Type.GetTypeCode(Enum.GetUnderlyingType(type))) - ? AddInlineConstantNode(type, (uint)System.Convert.ToInt64(value)) - : AddLeafNode(type, value, ExpressionType.Constant); // long/ulong-backed enum (extremely rare): store boxed in Obj - - if (type.IsPrimitive) - { - var tc = Type.GetTypeCode(type); - return IsSmallPrimitive(tc) - ? AddInlineConstantNode(type, ToInlineValue(value, tc)) - : AddLeafNode(type, value, ExpressionType.Constant); // long, ulong, double: primitive but too wide for _data, store boxed in Obj - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Constant(object value, Type type) => Nodes.Add(new(ExpressionType.Constant, type, value)); - // Delegate, array types, and user-defined reference/value types go to ClosureConstants - return AddLeafNode(type, ClosureConstantMarker, ExpressionType.Constant, childIdx: ClosureConstants.Add(value)); - } + /// Adds a constant node using the runtime type of the supplied value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Constant(object value) => Constant(value, value?.GetType() ?? typeof(object)); /// Adds a null constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantNull(Type type = null) => AddLeafNode(type ?? typeof(object), null, ExpressionType.Constant); + public int ConstantNull(Type type = null) => Nodes.Add(new(ExpressionType.Constant, type ?? typeof(object), null)); /// Adds an constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantInt(int value) => AddInlineConstantNode(typeof(int), unchecked((uint)value)); + public int ConstantInt(int value) => Nodes.Add(new(typeof(int), unchecked((uint)value))); - /// Adds a typed constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantOf(T value) => Constant(value, typeof(T)); + public int New(ConstructorInfo ctor) => Nodes.Add(new(ExpressionType.New, ctor.DeclaringType, ctor)); /// Adds a parameterless new node for the specified type. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public int New(Type type) { if (type.IsValueType) - return AddLeafNode(type, null, ExpressionType.New); + return Nodes.Add(new(ExpressionType.New, type, null)); foreach (var ctor in type.GetConstructors()) if (ctor.GetParameters().Length == 0) @@ -322,8 +281,29 @@ public int New(Type type) } /// Adds a constructor call node. - public int New(System.Reflection.ConstructorInfo constructor, params int[] arguments) => - AddFactoryExpressionNode(constructor.DeclaringType, constructor, ExpressionType.New, arguments); + public int New(ConstructorInfo ctor, params int[] args) + { + var newNode = new ExprNode(ExpressionType.New, ctor.DeclaringType, ctor); + if (args == null || args.Length == 0) + return Nodes.Add(in newNode); + + ushort argIdx = (ushort)args.GetSurePresent(0); + ref var arg = ref Nodes.GetSurePresentRef(argIdx); + argIdx = arg.NextIdx == 0 ? argIdx : (ushort)Nodes.AddCopy(arg); + newNode.SetChild((ushort)args.Length, argIdx); + + if (args.Length > 1) + for (var i = 1; i < args.Length; ++i) + { + argIdx = (ushort)args.GetSurePresent(i); + ref var nextArg = ref Nodes.GetSurePresentRef(argIdx); + arg.NextIdx = nextArg.NextIdx == 0 ? argIdx : (ushort)Nodes.AddCopy(nextArg); + arg = ref nextArg; + } + + // do not forget to set last arg.NextIdx to its parent index to navigate upwards + return arg.NextIdx = (ushort)Nodes.Add(in newNode); + } /// Adds an array initialization node. public int NewArrayInit(Type elementType, params int[] expressions) => @@ -340,35 +320,35 @@ public int Invoke(int expression, params int[] arguments) => : 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) => + public int Call(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) => + public int Call(int instance, MethodInfo method, params int[] 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) => + public int MakeMemberAccess(int? instance, MemberInfo member) => instance.HasValue ? AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, instance.Value) : AddLeafNode(GetMemberType(member), member, ExpressionType.MemberAccess); /// Adds a field-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Field(int instance, System.Reflection.FieldInfo field) => MakeMemberAccess(instance, field); + public int Field(int instance, FieldInfo field) => MakeMemberAccess(instance, field); /// Adds a property-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Property(int instance, System.Reflection.PropertyInfo property) => MakeMemberAccess(instance, property); + public int Property(int instance, PropertyInfo property) => MakeMemberAccess(instance, property); /// Adds a static property-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Property(System.Reflection.PropertyInfo property) => MakeMemberAccess(null, property); + public int Property(PropertyInfo property) => MakeMemberAccess(null, property); /// Adds an indexed property-access node. - public int Property(int instance, System.Reflection.PropertyInfo property, params int[] arguments) => + public int Property(int instance, PropertyInfo property, params int[] arguments) => arguments == null || arguments.Length == 0 ? Property(instance, property) : AddFactoryExpressionNode(property.PropertyType, property, ExpressionType.Index, PrependToChildList(instance, arguments)); @@ -385,7 +365,7 @@ public int ArrayAccess(int array, params int[] idxs) => /// Adds a conversion node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Convert(int operand, Type type, System.Reflection.MethodInfo method = null) => + public int Convert(int operand, Type type, MethodInfo method = null) => AddFactoryExpressionNode(type, method, ExpressionType.Convert, operand); /// Adds a type-as node. @@ -395,16 +375,16 @@ public int TypeAs(int operand, Type type) => /// Adds a numeric negation node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Negate(int operand, System.Reflection.MethodInfo method = null) => + public int Negate(int operand, MethodInfo method = null) => MakeUnary(ExpressionType.Negate, operand, method: method); /// Adds a logical or bitwise not node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Not(int operand, System.Reflection.MethodInfo method = null) => + public int Not(int operand, 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) => + public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, MethodInfo method = null) => AddFactoryExpressionNode(type ?? GetUnaryResultType(nodeType, Nodes[operand].Type, method), method, nodeType, operand); /// Adds an assignment node. @@ -413,15 +393,15 @@ public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, Sys /// Adds an addition node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Add(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Add, left, right, method: method); + public int Add(int left, int right, MethodInfo method = null) => MakeBinary(ExpressionType.Add, left, right, method: method); /// Adds an equality node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Equal(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Equal, left, right, method: method); + public int Equal(int left, int right, 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) + MethodInfo method = null, int? conversion = null, Type type = null) => conversion.HasValue ? AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), method, nodeType, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, left, right, conversion.Value) @@ -494,19 +474,19 @@ public int Lambda(Type delegateType, int body, params int[] parameters) } /// Adds a member-assignment binding node. - public int Bind(System.Reflection.MemberInfo member, int expression) => + public int Bind(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) => + public int MemberBind(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) => + public int ElementInit(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) => + public int ListBind(MemberInfo member, params int[] initializers) => AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberListBinding, initializers); /// Adds a member-init node. @@ -588,7 +568,7 @@ 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) + public int Switch(Type type, int switchValue, int? defaultBody, MethodInfo comparison, params int[] cases) { ChildList children = default; children.Add(switchValue); @@ -760,36 +740,36 @@ public override int GetHashCode() => public static bool operator !=(ExprTree left, ExprTree right) => !left.Equals(right); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child)); + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, ushort c0) => + Nodes.Add(new(nodeType, type, obj, childIdx: MayBeCloneChild(c0), childCount: 1)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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 c0) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0)); [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1)); [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2)); [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2), MayBeCloneChild(c3)); [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2), MayBeCloneChild(c3), MayBeCloneChild(c4)); [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2), MayBeCloneChild(c3), MayBeCloneChild(c4), MayBeCloneChild(c5)); [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2), MayBeCloneChild(c3), MayBeCloneChild(c4), MayBeCloneChild(c5), MayBeCloneChild(c6)); private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) { @@ -806,7 +786,7 @@ private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeT } var cloned = CloneChildren(children); - return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); + return Nodes.Add(new(nodeType, type, obj, ExprNodeKind.Expression, 0, in cloned)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -837,7 +817,7 @@ private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child) => - AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child)); + AddNode(type, obj, ExpressionType.Extension, kind, flags, MayBeCloneChild(child)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int child) => @@ -845,7 +825,7 @@ private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int chil [MethodImpl(MethodImplOptions.AggressiveInlining)] 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)); + AddNode(type, obj, ExpressionType.Extension, kind, flags, MayBeCloneChild(child0), MayBeCloneChild(child1)); private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int[] children) { @@ -920,7 +900,7 @@ private int AddExpression(SysExpr expression) switch (expression.NodeType) { case ExpressionType.Constant: - return AddConstant((System.Linq.Expressions.ConstantExpression)expression); + return AddConstant((ConstantExpression)expression); case ExpressionType.Default: return _tree.AddLeafNode(expression.Type, null, expression.NodeType); case ExpressionType.Parameter: @@ -935,7 +915,7 @@ private int AddExpression(SysExpr expression) // Body is stored before parameters so that the Reader encounters parameter // refs in the body before their decl nodes (out-of-order decl); identity // is preserved via the shared _parametersById id-map. - var lambda = (System.Linq.Expressions.LambdaExpression)expression; + var lambda = (LambdaExpression)expression; ChildList children = default; children.Add(AddExpression(lambda.Body)); for (var i = 0; i < lambda.Parameters.Count; ++i) @@ -950,7 +930,7 @@ private int AddExpression(SysExpr expression) // With variables: children[0] is the variable list and children[1] is the expression list. // Without variables: children[0] is the expression list. // children.Count == 2 means the block has explicit variables. - var block = (System.Linq.Expressions.BlockExpression)expression; + var block = (BlockExpression)expression; ChildList children = default; var hasVariables = block.Variables.Count != 0; if (hasVariables) @@ -971,7 +951,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.MemberAccess: { - var member = (System.Linq.Expressions.MemberExpression)expression; + var member = (MemberExpression)expression; ChildList children = default; if (member.Expression != null) children.Add(AddExpression(member.Expression)); @@ -980,7 +960,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Call: { - var call = (System.Linq.Expressions.MethodCallExpression)expression; + var call = (MethodCallExpression)expression; ChildList children = default; if (call.Object != null) children.Add(AddExpression(call.Object)); @@ -990,7 +970,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.New: { - var @new = (System.Linq.Expressions.NewExpression)expression; + var @new = (NewExpression)expression; ChildList children = default; for (var i = 0; i < @new.Arguments.Count; ++i) children.Add(AddExpression(@new.Arguments[i])); @@ -999,7 +979,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.NewArrayInit: case ExpressionType.NewArrayBounds: { - var array = (System.Linq.Expressions.NewArrayExpression)expression; + var array = (NewArrayExpression)expression; ChildList children = default; for (var i = 0; i < array.Expressions.Count; ++i) children.Add(AddExpression(array.Expressions[i])); @@ -1007,7 +987,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Invoke: { - var invoke = (System.Linq.Expressions.InvocationExpression)expression; + var invoke = (InvocationExpression)expression; ChildList children = default; children.Add(AddExpression(invoke.Expression)); for (var i = 0; i < invoke.Arguments.Count; ++i) @@ -1016,7 +996,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Index: { - var indexExpr = (System.Linq.Expressions.IndexExpression)expression; + var indexExpr = (IndexExpression)expression; ChildList children = default; if (indexExpr.Object != null) children.Add(AddExpression(indexExpr.Object)); @@ -1026,7 +1006,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Conditional: { - var conditional = (System.Linq.Expressions.ConditionalExpression)expression; + var conditional = (ConditionalExpression)expression; ChildList children = default; children.Add(AddExpression(conditional.Test)); children.Add(AddExpression(conditional.IfTrue)); @@ -1035,7 +1015,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Loop: { - var loop = (System.Linq.Expressions.LoopExpression)expression; + var loop = (LoopExpression)expression; ChildList children = default; children.Add(AddExpression(loop.Body)); if (loop.BreakLabel != null) @@ -1047,7 +1027,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Goto: { - var @goto = (System.Linq.Expressions.GotoExpression)expression; + var @goto = (GotoExpression)expression; ChildList children = default; children.Add(AddLabelTarget(@goto.Target)); if (@goto.Value != null) @@ -1058,7 +1038,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Label: { - var label = (System.Linq.Expressions.LabelExpression)expression; + var label = (LabelExpression)expression; ChildList children = default; children.Add(AddLabelTarget(label.Target)); if (label.DefaultValue != null) @@ -1069,7 +1049,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Switch: { - var @switch = (System.Linq.Expressions.SwitchExpression)expression; + var @switch = (SwitchExpression)expression; ChildList children = default; children.Add(AddExpression(@switch.SwitchValue)); if (@switch.DefaultBody != null) @@ -1085,7 +1065,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Try: { - var @try = (System.Linq.Expressions.TryExpression)expression; + var @try = (TryExpression)expression; ChildList children = default; children.Add(AddExpression(@try.Body)); var flags = (byte)0; @@ -1109,7 +1089,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.MemberInit: { - var memberInit = (System.Linq.Expressions.MemberInitExpression)expression; + var memberInit = (MemberInitExpression)expression; ChildList children = default; children.Add(AddExpression(memberInit.NewExpression)); for (var i = 0; i < memberInit.Bindings.Count; ++i) @@ -1118,7 +1098,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.ListInit: { - var listInit = (System.Linq.Expressions.ListInitExpression)expression; + var listInit = (ListInitExpression)expression; ChildList children = default; children.Add(AddExpression(listInit.NewExpression)); for (var i = 0; i < listInit.Initializers.Count; ++i) @@ -1128,7 +1108,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.TypeIs: case ExpressionType.TypeEqual: { - var typeBinary = (System.Linq.Expressions.TypeBinaryExpression)expression; + var typeBinary = (TypeBinaryExpression)expression; ChildList children = default; children.Add(AddExpression(typeBinary.Expression)); return _tree.AddRawExpressionNode(expression.Type, typeBinary.TypeOperand, expression.NodeType, @@ -1136,7 +1116,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Dynamic: { - var dynamic = (System.Linq.Expressions.DynamicExpression)expression; + var dynamic = (DynamicExpression)expression; ChildList children = default; children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); for (var i = 0; i < dynamic.Arguments.Count; ++i) @@ -1145,7 +1125,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.RuntimeVariables: { - var runtime = (System.Linq.Expressions.RuntimeVariablesExpression)expression; + var runtime = (RuntimeVariablesExpression)expression; ChildList children = default; for (var i = 0; i < runtime.Variables.Count; ++i) children.Add(AddExpression(runtime.Variables[i])); @@ -1153,12 +1133,12 @@ private int AddExpression(SysExpr expression) } case ExpressionType.DebugInfo: { - var debug = (System.Linq.Expressions.DebugInfoExpression)expression; + var debug = (DebugInfoExpression)expression; 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) + if (expression is UnaryExpression unary) { ChildList children = default; children.Add(AddExpression(unary.Operand)); @@ -1166,7 +1146,7 @@ private int AddExpression(SysExpr expression) children); } - if (expression is System.Linq.Expressions.BinaryExpression binary) + if (expression is BinaryExpression binary) { ChildList children = default; children.Add(AddExpression(binary.Left)); @@ -1181,7 +1161,7 @@ private int AddExpression(SysExpr expression) } } - private int AddConstant(System.Linq.Expressions.ConstantExpression constant) => + private int AddConstant(ConstantExpression constant) => _tree.Constant(constant.Value, constant.Type); private int AddSwitchCase(SysSwitchCase switchCase) @@ -1214,12 +1194,12 @@ private int AddMemberBinding(SysMemberBinding binding) { case MemberBindingType.Assignment: ChildList assignmentChildren = default; - assignmentChildren.Add(AddExpression(((System.Linq.Expressions.MemberAssignment)binding).Expression)); + assignmentChildren.Add(AddExpression(((MemberAssignment)binding).Expression)); return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, assignmentChildren); case MemberBindingType.MemberBinding: { - var memberBinding = (System.Linq.Expressions.MemberMemberBinding)binding; + var memberBinding = (MemberMemberBinding)binding; ChildList children = default; for (var i = 0; i < memberBinding.Bindings.Count; ++i) children.Add(AddMemberBinding(memberBinding.Bindings[i])); @@ -1227,7 +1207,7 @@ private int AddMemberBinding(SysMemberBinding binding) } case MemberBindingType.ListBinding: { - var listBinding = (System.Linq.Expressions.MemberListBinding)binding; + var listBinding = (MemberListBinding)binding; ChildList children = default; for (var i = 0; i < listBinding.Initializers.Count; ++i) children.Add(AddElementInit(listBinding.Initializers[i])); @@ -1254,32 +1234,21 @@ private static int GetId(ref SmallMap16> ids, object return id; } - private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch + private static Type GetMemberType(MemberInfo member) => member switch { - System.Reflection.FieldInfo field => field.FieldType, - System.Reflection.PropertyInfo property => property.PropertyType, + FieldInfo field => field.FieldType, + PropertyInfo property => property.PropertyType, _ => typeof(object) }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddLeafNode(Type type, object obj, ExpressionType nodeType, - ExprNodeKind kind = ExprNodeKind.Expression, byte flags = default, int childIdx = default, int childCount = default) - { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, childIdx, childCount); - return nodeIdx; - } + private int AddLeafNode(Type type, object obj, ExpressionType nodeType, + ExprNodeKind kind = ExprNodeKind.Expression, byte flags = default, ushort childIdx = default, ushort childCount = default) => + Nodes.Add(new(nodeType, type, obj, kind, flags, childIdx, childCount)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddInlineConstantNode(Type type, uint inlineValue) - { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, inlineValue); - return nodeIdx; - } + private int AddInlineConstantNode(Type type, uint inlineValue) => Nodes.Add(new(type, inlineValue)); private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags) { @@ -1289,84 +1258,79 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int child0) - { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, child0, 1); - return nodeIdx; - } + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0) => + Nodes.Add(new(nodeType, type, obj, kind, flags, c0, 1)); - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 2); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); + Nodes.GetSurePresentRef(c0).NextIdx = c1; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 3); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 4); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); - Nodes.GetSurePresentRef(c2).SetNextIdx(c3); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; + Nodes.GetSurePresentRef(c2).NextIdx = c3; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3, ushort c4) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 5); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); - Nodes.GetSurePresentRef(c2).SetNextIdx(c3); - Nodes.GetSurePresentRef(c3).SetNextIdx(c4); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; + Nodes.GetSurePresentRef(c2).NextIdx = c3; + Nodes.GetSurePresentRef(c3).NextIdx = c4; return nodeIdx; } - 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) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3, ushort c4, ushort c5) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 6); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); - Nodes.GetSurePresentRef(c2).SetNextIdx(c3); - Nodes.GetSurePresentRef(c3).SetNextIdx(c4); - Nodes.GetSurePresentRef(c4).SetNextIdx(c5); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; + Nodes.GetSurePresentRef(c2).NextIdx = c3; + Nodes.GetSurePresentRef(c3).NextIdx = c4; + Nodes.GetSurePresentRef(c4).NextIdx = c5; return nodeIdx; } - 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) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3, ushort c4, ushort c5, ushort c6) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 7); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); - Nodes.GetSurePresentRef(c2).SetNextIdx(c3); - Nodes.GetSurePresentRef(c3).SetNextIdx(c4); - Nodes.GetSurePresentRef(c4).SetNextIdx(c5); - Nodes.GetSurePresentRef(c5).SetNextIdx(c6); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; + Nodes.GetSurePresentRef(c2).NextIdx = c3; + Nodes.GetSurePresentRef(c3).NextIdx = c4; + Nodes.GetSurePresentRef(c4).NextIdx = c5; + Nodes.GetSurePresentRef(c5).NextIdx = c6; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int[] children) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort[] children) { if (children == null || children.Length == 0) return AddNode(type, obj, nodeType, kind, flags); @@ -1375,7 +1339,7 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, children[0], children.Length); for (var i = 1; i < children.Length; ++i) - Nodes.GetSurePresentRef(children[i - 1]).SetNextIdx(children[i]); + Nodes.GetSurePresentRef(children[i - 1]).NextIdx = children[i]; return nodeIdx; } @@ -1388,12 +1352,12 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind 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) - Nodes.GetSurePresentRef(children[i - 1]).SetNextIdx(children[i]); + Nodes.GetSurePresentRef(children[i - 1]).NextIdx = children[i]; return nodeIdx; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSmallPrimitive(TypeCode tc) => + private static bool In32BitRange(TypeCode tc) => tc == TypeCode.Boolean || tc == TypeCode.Byte || tc == TypeCode.SByte || tc == TypeCode.Char || tc == TypeCode.Int16 || tc == TypeCode.UInt16 || tc == TypeCode.Int32 || tc == TypeCode.UInt32 || tc == TypeCode.Single; @@ -1413,21 +1377,21 @@ private static bool IsSmallPrimitive(TypeCode tc) => _ => FlatExpressionThrow.UnsupportedInlineConstantType(value, tc) }; - private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch + private static Type GetMemberType(MemberInfo member) => member switch { - System.Reflection.FieldInfo field => field.FieldType, - System.Reflection.PropertyInfo property => property.PropertyType, + FieldInfo field => field.FieldType, + PropertyInfo property => property.PropertyType, _ => typeof(object) }; - private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType, System.Reflection.MethodInfo method) => + private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType, 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) + private static Type GetBinaryResultType(ExpressionType nodeType, Type leftType, Type rightType, MethodInfo method) { if (method != null) return method.ReturnType; @@ -1450,35 +1414,6 @@ private static Type GetArrayElementType(Type arrayType, int depth) return elementType ?? typeof(object); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CloneChild(int idx) - { - ref var node = ref Nodes[idx]; - if (!node.ShouldCloneWhenLinked()) return idx; - if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker)) - return AddInlineConstantNode(node.Type, node.InlineValue); - return AddLeafNode(node.Type, node.Obj, node.NodeType, node.Kind, node.Flags, node.ChildIdx, node.ChildCount); - } - - private ChildList CloneChildren(int[] children) - { - ChildList cloned = default; - if (children == null) - return cloned; - - for (var i = 0; i < children.Length; ++i) - cloned.Add(CloneChild(children[i])); - return 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 void CollectLambdaClosureParameterUsages(int lambdaIdx) { var children = GetChildren(lambdaIdx); @@ -1489,7 +1424,7 @@ private void CollectLambdaClosureParameterUsages(int lambdaIdx) for (var i = 1; i < children.Count; ++i) lambdaParameterIds.Add(ToStoredUShortIdx(Nodes[children[i]].ChildIdx)); - SmallList, NoArrayPool> localParameterIds = default; + ChildList localParameterIds = default; SmallList, NoArrayPool> captures = default; CollectClosureParameterUsages(children[0], ToStoredUShortIdx(lambdaIdx), ref lambdaParameterIds, ref localParameterIds, ref captures); @@ -1501,7 +1436,7 @@ private void CollectClosureParameterUsages( int idx, ushort lambdaIdx, ref SmallList, NoArrayPool> lambdaParameterIds, - ref SmallList, NoArrayPool> localParameterIds, + ref ChildList localParameterIds, ref SmallList, NoArrayPool> captures) { ref var node = ref Nodes.GetSurePresentRef(idx); @@ -1569,7 +1504,7 @@ private void CollectCatchBlockClosureParameterUsages( int idx, ushort lambdaIdx, ref SmallList, NoArrayPool> lambdaParameterIds, - ref SmallList, NoArrayPool> localParameterIds, + ref ChildList localParameterIds, ref SmallList, NoArrayPool> captures) { ref var node = ref Nodes.GetSurePresentRef(idx); @@ -1592,7 +1527,7 @@ private void PropagateNestedLambdaClosureParameterUsages( ushort nestedLambdaIdx, ushort lambdaIdx, ref SmallList, NoArrayPool> lambdaParameterIds, - ref SmallList, NoArrayPool> localParameterIds, + ref ChildList localParameterIds, ref SmallList, NoArrayPool> captures) { for (var i = 0; i < LambdaClosureParameterUsages.Count; ++i) @@ -1650,7 +1585,7 @@ private static bool Contains(ref SmallList private struct StructuralComparer { - private SmallList, NoArrayPool> _xParameterIds, _yParameterIds; + private ChildList _xParameterIds, _yParameterIds; private SmallList, NoArrayPool> _xLabelIds, _yLabelIds; private SmallList, NoArrayPool> _eqFrames; @@ -1669,15 +1604,16 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) ref var y = ref yTree.Nodes.GetSurePresentRef(yIdx); if (x.Kind == ExprNodeKind.UInt16Pair) { - if (!x.HasSameShapeExceptNext(ref y)) + if (!x.HasSameShape(ref y)) return false; } else if (x.NodeType == ExpressionType.Constant) { - if (!x.HasSameHeaderExceptNext(ref y)) + + if (x.Type != y.Type || x.NodeType != y.NodeType || x.FlagsAndKind != y.FlagsAndKind) return false; } - else if (!x.HasSameShapeExceptLinks(ref y)) + else if (!x.HasSameShapeExceptChildIdx(ref y)) return false; var descendX = 0; @@ -1880,7 +1816,7 @@ private bool EqLabelTarget(ref ExprNode x, ref ExprNode y) private static bool AreEquivalentParameterDeclarations(ref ExprNode x, ref ExprNode y) => x.NodeType == ExpressionType.Parameter && y.NodeType == ExpressionType.Parameter && - x.HasSameShapeExceptLinks(ref y); + x.HasSameShapeExceptChildIdx(ref y); private static bool EqObj(ref ExprNode x, ref ExprNode y) => ReferenceEquals(x.Obj, y.Obj) || Equals(x.Obj, y.Obj); @@ -2028,7 +1964,7 @@ private static bool AreConstantsEqual(ref ExprTree xTree, ref ExprNode x, ref Ex return x.InlineValue == y.InlineValue; var typeCode = Type.GetTypeCode(x.Type); - Debug.Assert(IsSmallPrimitive(typeCode)); + Debug.Assert(In32BitRange(typeCode)); return typeCode != TypeCode.Single ? x.InlineValue == y.InlineValue : FloatBits.ToFloat(x.InlineValue).Equals(FloatBits.ToFloat(y.InlineValue)); @@ -2042,7 +1978,7 @@ private static int GetInlineConstantHashCode(Type type, uint data) if (!type.IsEnum) { var typeCode = Type.GetTypeCode(type); - Debug.Assert(IsSmallPrimitive(typeCode)); + Debug.Assert(In32BitRange(typeCode)); if (typeCode == TypeCode.Single) return FloatBits.ToFloat(data).GetHashCode(); } @@ -2088,7 +2024,7 @@ public Reader(ExprTree tree) public SysExpr ReadExpression(int idx) { ref var node = ref _tree.Nodes[idx]; - if (!node.IsExpression()) + if (node.Kind != ExprNodeKind.Expression) throw new InvalidOperationException($"Node at idx {idx} is not an expression node."); switch (node.NodeType) @@ -2145,11 +2081,11 @@ public SysExpr ReadExpression(int idx) case ExpressionType.MemberAccess: { var children = GetChildren(idx); - return SysExpr.MakeMemberAccess(children.Count != 0 ? ReadExpression(children[0]) : null, (System.Reflection.MemberInfo)node.Obj); + return SysExpr.MakeMemberAccess(children.Count != 0 ? ReadExpression(children[0]) : null, (MemberInfo)node.Obj); } case ExpressionType.Call: { - var method = (System.Reflection.MethodInfo)node.Obj; + var method = (MethodInfo)node.Obj; var children = GetChildren(idx); var hasInstance = !method.IsStatic; var instance = hasInstance ? ReadExpression(children[0]) : null; @@ -2162,7 +2098,7 @@ public SysExpr ReadExpression(int idx) { var children = GetChildren(idx); var arguments = ReadExpressions(children); - return node.Obj is System.Reflection.ConstructorInfo ctor + return node.Obj is ConstructorInfo ctor ? SysExpr.New(ctor, arguments) : CreateValueTypeNewExpression(node.Type); } @@ -2181,7 +2117,7 @@ public SysExpr ReadExpression(int idx) case ExpressionType.Index: { var children = GetChildren(idx); - var property = (System.Reflection.PropertyInfo)node.Obj; + var property = (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)]; @@ -2236,7 +2172,7 @@ public SysExpr ReadExpression(int idx) var cases = new SysSwitchCase[caseIdxs.Count]; for (var i = 0; i < cases.Length; ++i) cases[i] = ReadSwitchCase(caseIdxs[i]); - return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, (System.Reflection.MethodInfo)node.Obj, cases); + return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, (MethodInfo)node.Obj, cases); } case ExpressionType.Try: { @@ -2267,7 +2203,7 @@ public SysExpr ReadExpression(int idx) 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); + return SysExpr.MemberInit((NewExpression)ReadExpression(children[0]), bindings); } case ExpressionType.ListInit: { @@ -2275,7 +2211,7 @@ public SysExpr ReadExpression(int idx) 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); + return SysExpr.ListInit((NewExpression)ReadExpression(children[0]), initializers); } case ExpressionType.TypeIs: return SysExpr.TypeIs(ReadExpression(GetChildren(idx)[0]), (Type)node.Obj); @@ -2308,16 +2244,16 @@ public SysExpr ReadExpression(int idx) default: if (node.ChildCount == 1) { - var method = node.Obj as System.Reflection.MethodInfo; + var method = node.Obj as MethodInfo; return SysExpr.MakeUnary(node.NodeType, ReadExpression(GetChildren(idx)[0]), node.Type, method); } if (node.ChildCount >= 2) { var children = GetChildren(idx); - var conversion = children.Count > 2 ? (System.Linq.Expressions.LambdaExpression)ReadExpression(children[2]) : null; + var conversion = children.Count > 2 ? (LambdaExpression)ReadExpression(children[2]) : null; return SysExpr.MakeBinary(node.NodeType, ReadExpression(children[0]), ReadExpression(children[1]), - node.HasFlag(BinaryLiftedToNullFlag), (System.Reflection.MethodInfo)node.Obj, conversion); + node.HasFlag(BinaryLiftedToNullFlag), (MethodInfo)node.Obj, conversion); } throw new NotSupportedException($"Reconstruction of `ExpressionType.{node.NodeType}` is not supported yet."); @@ -2379,7 +2315,7 @@ private void ReadUInt16Pair(int idx, out int first, out int second) private SysMemberBinding ReadMemberBinding(int idx) { ref var node = ref _tree.Nodes[idx]; - var member = (System.Reflection.MemberInfo)node.Obj; + var member = (MemberInfo)node.Obj; switch (node.Kind) { case ExprNodeKind.MemberAssignment: @@ -2410,7 +2346,7 @@ private SysElementInit ReadElementInit(int idx) { ref var node = ref _tree.Nodes[idx]; Debug.Assert(node.Is(ExprNodeKind.ElementInit)); - return SysExpr.ElementInit((System.Reflection.MethodInfo)node.Obj, ReadExpressions(GetChildren(idx))); + return SysExpr.ElementInit((MethodInfo)node.Obj, ReadExpressions(GetChildren(idx))); } private ChildList GetChildren(int idx) @@ -2469,7 +2405,7 @@ private SysExpr[] ReadExpressions(in ChildList childIdxs) [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 static NewExpression CreateValueTypeNewExpression(Type type) => SysExpr.New(type); } } diff --git a/src/FastExpressionCompiler/ImTools.cs b/src/FastExpressionCompiler/ImTools.cs index 99e76db9..a84fa62e 100644 --- a/src/FastExpressionCompiler/ImTools.cs +++ b/src/FastExpressionCompiler/ImTools.cs @@ -851,6 +851,10 @@ public int Add(in T item) return index; } + /// Adds the item copy to the end of the list aka the Stack.Push. Returns the index of the added item. + [MethodImpl((MethodImplOptions)256)] + public int AddCopy(T item) => Add(in item); + /// Looks for the item in the list and return its index if found or -1 for the absent item [MethodImpl((MethodImplOptions)256)] public int TryGetIndex(in T item, TEq eq = default) where TEq : struct, IEq