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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] @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 From 6a1ce071b0c8a5540da138248c6cda5b8d2b9b41 Mon Sep 17 00:00:00 2001 From: dadhi Date: Tue, 4 Aug 2026 22:29:54 +0200 Subject: [PATCH 16/18] @wip @notcompiled using ushort indexes and simplifying To and From --- .../FlatExpression.cs | 2096 ++++++++--------- .../LightExpressionTests.cs | 30 +- 2 files changed, 970 insertions(+), 1156 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index d4265395..8498fe09 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -9,8 +9,6 @@ namespace FastExpressionCompiler.FlatExpression; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using FastExpressionCompiler.LightExpression.ImTools; -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; @@ -18,14 +16,19 @@ namespace FastExpressionCompiler.FlatExpression; using SysMemberBinding = System.Linq.Expressions.MemberBinding; using SysParameterExpression = System.Linq.Expressions.ParameterExpression; using SysSwitchCase = System.Linq.Expressions.SwitchCase; +using ChildIdxs = LightExpression.ImTools.SmallList, LightExpression.ImTools.NoArrayPool>; -/// Classifies the stored flat node payload. +// todo: @wip using Idx = ushort + +/// Kind if the node payload. public enum ExprNodeKind : byte { /// Represents a regular expression node. Expression = 0, - /// Represents a switch case payload. + /// Represents a switch case sub-node. SwitchCase, + /// Represents a switch cases sub-node. + SwitchCases, /// Represents a catch block payload. CatchBlock, /// Represents a label target payload. @@ -40,8 +43,8 @@ public enum ExprNodeKind : byte ElementInit, /// Represents an internal object-reference metadata node. ObjectReference, - /// Represents an internal child-list metadata node. - ChildList, + /// Expressions in Block as a separate sub-node Block child node, it has the same ExpressionType.Block but this kind. + BlockExprs, /// Represents an internal pair of UInt16 values. UInt16Pair, } @@ -55,7 +58,7 @@ public struct ExprNode // _data layout: bits [31:16]=ChildCount | [15:0]=ChildIdx (or full uint for inline constants) private const int ChildCountShift = 16; private const uint ChildCountMask = 0xFFFF0000u; - private const uint ChildIdxMask = 0xFFFFu; + private const uint FirstChildIdxMask = 0xFFFFu; private const int FlagsShift = 4; /// Sentinel placed in to indicate the node holds a small primitive constant in . @@ -96,9 +99,9 @@ public struct ExprNode public ushort ChildCount => (ushort)(_child >> ChildCountShift); /// Gets the first child idx or an auxiliary payload idx. - public ushort ChildIdx => (ushort)(_child & ChildIdxMask); + public ushort FirstChildIdx => (ushort)(_child & FirstChildIdxMask); - public void SetChild(ushort childCount, ushort childIdx) => _child = ((uint)childCount << ChildCountShift) | childIdx; + public void SetChildrenInfo(ushort childCount, ushort firstChildIdx) => _child = ((uint)childCount << ChildCountShift) | firstChildIdx; /// Gets the raw 32-bit value for inline primitive constants. Only valid when == . internal uint InlineValue => _child; @@ -110,8 +113,7 @@ internal ExprNode(ExpressionType nodeType, Type type, object obj = null) _nodeType = (byte)nodeType; } - internal ExprNode(ExpressionType nodeType, Type type, object obj, - ExprNodeKind kind = default, byte flags = default, + internal ExprNode(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort childIdx = 0, ushort childCount = 0, ushort nextIdx = 0) { Type = type; @@ -230,48 +232,55 @@ public struct ExprTree : IEquatable /// The stored idxs use the same 16-bit range as and . public SmallList, NoArrayPool> LambdaClosureParameterUsages; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort AddNode(ExpressionType nodType, Type type, object obj = null, byte flags = 0, ExprNodeKind kind = default) + { + var node = new ExprNode(nodType, type, obj, flags, kind); + return (ushort)Nodes.Add(in node); + } + /// Adds a parameter node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Parameter(Type type, string name = null) => - Nodes.Add(new(ExpressionType.Parameter, type, name, flags: type.IsByRef ? ParameterByRefFlag : (byte)0)); + public ushort Parameter(Type type, string name = null) => + AddNode(ExpressionType.Parameter, type, name, type.IsByRef ? ParameterByRefFlag : (byte)0); /// Adds a typed parameter node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ParameterOf(string name = null) => Parameter(typeof(T), name); + public ushort ParameterOf(string name = null) => Parameter(typeof(T), name); /// Adds a variable node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Variable(Type type, string name = null) => Parameter(type, name); + public ushort Variable(Type type, string name = null) => Parameter(type, name); /// Adds a default-value node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Default(Type type) => Nodes.Add(new(ExpressionType.Default, type, null)); + public ushort Default(Type type) => AddNode(ExpressionType.Default, type); /// Adds a constant node with an explicit constant type. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Constant(object value, Type type) => Nodes.Add(new(ExpressionType.Constant, type, value)); + public ushort Constant(object value, Type type) => AddNode(ExpressionType.Constant, type, 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)); + public ushort Constant(object value) => Constant(value, value?.GetType() ?? typeof(object)); /// Adds a null constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantNull(Type type = null) => Nodes.Add(new(ExpressionType.Constant, type ?? typeof(object), null)); + public ushort ConstantNull(Type type = null) => AddNode(ExpressionType.Constant, type ?? typeof(object)); /// Adds an constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantInt(int value) => Nodes.Add(new(typeof(int), unchecked((uint)value))); + public ushort ConstantInt(int value) => (ushort)Nodes.Add(new(typeof(int), unchecked((uint)value))); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int New(ConstructorInfo ctor) => Nodes.Add(new(ExpressionType.New, ctor.DeclaringType, ctor)); + public ushort New(ConstructorInfo ctor) => AddNode(ExpressionType.New, ctor.DeclaringType, ctor); /// Adds a parameterless new node for the specified type. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] - public int New(Type type) + public ushort New(Type type) { if (type.IsValueType) - return Nodes.Add(new(ExpressionType.New, type, null)); + return AddNode(ExpressionType.New, type); foreach (var ctor in type.GetConstructors()) if (ctor.GetParameters().Length == 0) @@ -280,1082 +289,1046 @@ public int New(Type type) throw new ArgumentException($"The type {type} is missing the default constructor"); } - /// Adds a constructor call node. - public int New(ConstructorInfo ctor, params int[] args) + [UnscopedRef] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref ExprNode CloneIfHasNext(ref ushort idx) + { + ref var childRef = ref Nodes.GetSurePresentRef(idx); + if (idx != 0 && childRef.NextIdx != 0) + idx = (ushort)Nodes.AddCopy(childRef); + return ref childRef; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort WithOneChild(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0) { - var newNode = new ExprNode(ExpressionType.New, ctor.DeclaringType, ctor); - if (args == null || args.Length == 0) - return Nodes.Add(in newNode); + var owner = new ExprNode(nodeType, type, obj, flags, kind); + ref var child = ref CloneIfHasNext(ref ch0); + owner.SetChildrenInfo(ch0 != 0 ? (ushort)1 : (ushort)0, ch0); + return child.NextIdx = (ushort)Nodes.Add(in owner); // do not forget to set last arg.NextIdx to its parent index to navigate upwards + } - 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); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort WithTwoChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1) + { + var owner = new ExprNode(nodeType, type, obj, flags, kind); + ushort childCount = 0; + ref var child = ref CloneIfHasNext(ref ch0); + childCount += (ushort)(ch0 * 1); + + if (ch1 != 0) + { + ref var nextRef = ref CloneIfHasNext(ref ch1); + child.NextIdx = ch1; + child = ref nextRef; + + if (ch0 == 0) ch0 = ch1; + ++childCount; + } + + owner.SetChildrenInfo(childCount, ch0); + return child.NextIdx = (ushort)Nodes.Add(in owner); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort WithThreeChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1, ushort ch2) + { + var owner = new ExprNode(nodeType, type, obj, flags, kind); + ushort childCount = 0; + ref var child = ref CloneIfHasNext(ref ch0); + childCount += (ushort)(ch0 * 1); - if (args.Length > 1) - for (var i = 1; i < args.Length; ++i) + if (ch1 != 0) + { + ref var nextRef = ref CloneIfHasNext(ref ch1); + child.NextIdx = ch1; + child = ref nextRef; + + if (ch0 == 0) ch0 = ch1; + ++childCount; + } + + if (ch2 != 0) + { + ref var nextRef = ref CloneIfHasNext(ref ch2); + child.NextIdx = ch2; + child = ref nextRef; + + if (ch0 == 0) ch0 = ch2; + ++childCount; + } + + owner.SetChildrenInfo(childCount, ch0); + return child.NextIdx = (ushort)Nodes.Add(in owner); + } + + private ushort WithTwoOrMoreChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1, ushort[] more) + { + var owner = new ExprNode(nodeType, type, obj, flags, kind); + ushort childCount = 0; + ref var childRef = ref CloneIfHasNext(ref ch0); + childCount += (ushort)(ch0 * 1); + + if (ch1 != 0) + { + ref var nextRef = ref CloneIfHasNext(ref ch1); + childRef.NextIdx = ch1; + childRef = ref nextRef; + + if (ch0 == 0) ch0 = ch1; + ++childCount; + } + + ushort ch = 0; + if (more != null) + { + for (ushort i = 0; i < more.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; + ch = more.GetSurePresentRef(i); + if (ch == 0) continue; + ref var nextRef = ref CloneIfHasNext(ref ch); + childRef.NextIdx = ch; + childRef = ref nextRef; + + if (ch0 == 0) ch0 = ch; + ++childCount; } + } - // do not forget to set last arg.NextIdx to its parent index to navigate upwards - return arg.NextIdx = (ushort)Nodes.Add(in newNode); + owner.SetChildrenInfo(childCount, ch0); // adding 0 and 0 is ubiquitous + return childRef.NextIdx = (ushort)Nodes.Add(in owner); } + private ushort WithOneOrMoreChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort[] more) + { + var owner = new ExprNode(nodeType, type, obj, flags, kind); + + ushort childCount = 0; + ref var childRef = ref CloneIfHasNext(ref ch0); + childCount += (ushort)(ch0 * 1); + + ushort ch = 0; + if (more != null) + { + for (ushort i = 0; i < more.Length; ++i) + { + ch = more.GetSurePresentRef(i); + if (ch == 0) continue; + ref var nextRef = ref CloneIfHasNext(ref ch); + childRef.NextIdx = ch; + childRef = ref nextRef; + + if (ch0 == 0) ch0 = ch; + ++childCount; + } + } + + owner.SetChildrenInfo(childCount, ch0); // adding 0 and 0 is ubiquitous + return childRef.NextIdx = (ushort)Nodes.Add(in owner); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort WithChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, params ushort[] children) => + WithOneOrMoreChildren(nodeType, type, obj, flags, kind, 0, children); + + /// Adds a constructor call node. + public ushort New(ConstructorInfo ctor, params ushort[] args) => + WithChildren(ExpressionType.New, ctor.DeclaringType, ctor, default, default, args); + /// Adds an array initialization node. - public int NewArrayInit(Type elementType, params int[] expressions) => - AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayInit, expressions); + public ushort NewArrayInit(Type elementType, params ushort[] expressions) => + WithChildren(ExpressionType.NewArrayInit, elementType.MakeArrayType(), null, default, default, expressions); /// Adds an array-bounds node. - public int NewArrayBounds(Type elementType, params int[] bounds) => - AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayBounds, bounds); + public ushort NewArrayBounds(Type elementType, params ushort[] bounds) => + WithChildren(ExpressionType.NewArrayBounds, elementType.MakeArrayType(), null, default, default, bounds); /// Adds an invocation node. - public int Invoke(int expression, params int[] arguments) => - arguments == null || arguments.Length == 0 - ? AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, expression) - : AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, PrependToChildList(expression, arguments)); + public ushort Invoke(ushort expr, params ushort[] args) => + WithOneOrMoreChildren(ExpressionType.Invoke, Nodes[expr].Type, null, default, default, expr, args); /// Adds a static-call node. - public int Call(MethodInfo method, params int[] arguments) => - AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, arguments); + public ushort Call(MethodInfo method, params ushort[] args) => + WithChildren(ExpressionType.Call, method.ReturnType, method, default, default, args); /// Adds an instance-call node. - 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)); + public ushort Call(ushort instance, MethodInfo method, params ushort[] args) => + WithOneOrMoreChildren(ExpressionType.Call, method.ReturnType, method, default, default, instance, args); /// Adds a field or property access node. - public int MakeMemberAccess(int? instance, MemberInfo member) => - instance.HasValue - ? AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, instance.Value) - : AddLeafNode(GetMemberType(member), member, ExpressionType.MemberAccess); + public ushort MakeMemberAccess(MemberInfo member) => + AddNode(ExpressionType.MemberAccess, GetMemberType(member), member); + + public ushort MakeMemberAccess(ushort instance, MemberInfo member) => + WithOneChild(ExpressionType.MemberAccess, GetMemberType(member), member, default, default, instance); /// Adds a field-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Field(int instance, FieldInfo field) => MakeMemberAccess(instance, field); + public ushort Field(FieldInfo field) => MakeMemberAccess(field); - /// Adds a property-access node. + /// Adds a field-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Property(int instance, PropertyInfo property) => MakeMemberAccess(instance, property); + public ushort Field(ushort instance, FieldInfo field) => MakeMemberAccess(instance, field); /// Adds a static property-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Property(PropertyInfo property) => MakeMemberAccess(null, property); + public ushort Property(PropertyInfo prop) => MakeMemberAccess(prop); + + /// Adds a property-access node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort Property(ushort instance, PropertyInfo prop) => MakeMemberAccess(instance, prop); /// Adds an indexed property-access node. - 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)); + public ushort Property(ushort instance, PropertyInfo prop, params ushort[] args) => + args == null || args.Length == 0 + ? Property(instance, prop) + : WithOneOrMoreChildren(ExpressionType.Index, prop.PropertyType, prop, default, default, instance, args); + + /// Adds a binary node of the specified kind. + public ushort MakeBinary(ExpressionType nodeType, ushort left, ushort right, bool isLiftedToNull = false, + MethodInfo method = null, ushort conversion = 0, Type type = null) + => WithThreeChildren( + nodeType, type ?? GetBinaryResultType(nodeType, Nodes[left].Type, method), method, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, default, + left, right, conversion); /// Adds a one-dimensional array index node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ArrayIndex(int array, int idx) => MakeBinary(ExpressionType.ArrayIndex, array, idx); + public ushort ArrayIndex(ushort array, ushort idx) => MakeBinary(ExpressionType.ArrayIndex, array, idx); /// Adds an array access node. - public int ArrayAccess(int array, params int[] idxs) => + public ushort ArrayAccess(ushort array, params ushort[] idxs) => idxs != null && idxs.Length == 1 ? ArrayIndex(array, idxs[0]) - : AddFactoryExpressionNode(GetArrayElementType(Nodes[array].Type, idxs?.Length ?? 0), null, ExpressionType.Index, PrependToChildList(array, idxs)); + : WithOneOrMoreChildren(ExpressionType.Index, GetArrayElementType(Nodes[array].Type, idxs?.Length ?? 0), null, default, default, array, idxs); /// Adds a conversion node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Convert(int operand, Type type, MethodInfo method = null) => - AddFactoryExpressionNode(type, method, ExpressionType.Convert, operand); + public ushort Convert(ushort operand, Type type, MethodInfo method = null) => + WithOneChild(ExpressionType.Convert, type, method, default, default, operand); /// Adds a type-as node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int TypeAs(int operand, Type type) => - AddFactoryExpressionNode(type, null, ExpressionType.TypeAs, operand); + public ushort TypeAs(ushort operand, Type type) => + WithOneChild(ExpressionType.TypeAs, type, null, default, default, operand); /// Adds a numeric negation node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Negate(int operand, MethodInfo method = null) => + public ushort Negate(ushort 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, MethodInfo method = null) => + public ushort Not(ushort 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, MethodInfo method = null) => - AddFactoryExpressionNode(type ?? GetUnaryResultType(nodeType, Nodes[operand].Type, method), method, nodeType, operand); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort MakeUnary(ExpressionType nodeType, ushort operand, Type type = null, MethodInfo method = null) => + WithOneChild(nodeType, type ?? GetUnaryResultType(nodeType, Nodes[operand].Type, method), method, default, default, operand); /// Adds an assignment node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Assign(int left, int right) => MakeBinary(ExpressionType.Assign, left, right); + public ushort Assign(ushort left, ushort right) => MakeBinary(ExpressionType.Assign, left, right); /// Adds an addition node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Add(int left, int right, MethodInfo method = null) => MakeBinary(ExpressionType.Add, left, right, method: method); + public ushort Add(ushort left, ushort 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, 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, - 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) - : AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), - method, nodeType, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, left, right); + public ushort Equal(ushort left, ushort right, MethodInfo method = null) => MakeBinary(ExpressionType.Equal, left, right, method: method); /// Adds a conditional node. - public int Condition(int test, int ifTrue, int ifFalse, Type type = null) => - AddFactoryExpressionNode(type ?? Nodes[ifTrue].Type, null, ExpressionType.Conditional, 0, test, ifTrue, ifFalse); - - /// Adds a block node without explicit variables. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Block(params int[] expressions) => - Block(null, null, expressions); - - /// Adds a block node with optional explicit result type and variables. - /// - /// Blocks with variables use two child lists: variables first, then expressions. - /// Blocks without variables use one child list for expressions. - /// Variable declarations share the same id slot as their later references so identity is preserved. - /// When variables are present, the block idx is recorded in . - /// - public int Block(Type type, IEnumerable variables, params int[] expressions) + public ushort Condition(ushort test, ushort ifTrue, ushort ifFalse, Type type = null) => + WithThreeChildren(ExpressionType.Conditional, type ?? Nodes[ifTrue].Type, null, default, default, test, ifTrue, ifFalse); + + /// BlockNode.ChildIdx -> ExprsNode; ExprsNode.NextIdx -> VarsNode|BlockNode + public ushort Block(Type type, ushort[] vars, params ushort[] exprs) { - if (expressions == null || expressions.Length == 0) - throw new ArgumentException("Block should contain at least one expression.", nameof(expressions)); + if (exprs == null || exprs.Length == 0) + throw new ArgumentException("Block should contain at least one expression.", nameof(exprs)); - ChildList children = default; - var hasVariables = false; - if (variables != null) - { - ChildList variableChildren = default; - foreach (var variable in variables) - variableChildren.Add(variable); - if (variableChildren.Count != 0) - { - children.Add(AddChildListNode(in variableChildren)); - hasVariables = true; - } - } - ChildList bodyChildren = default; - for (var i = 0; i < expressions.Length; ++i) - bodyChildren.Add(expressions[i]); - children.Add(AddChildListNode(in bodyChildren)); - var idx = AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, null, ExpressionType.Block, in children); - if (hasVariables) - BlocksWithVariables.Add(idx); - return idx; + var exprsSubNode = WithChildren(ExpressionType.Block, null, null, default, ExprNodeKind.BlockExprs, exprs); + + type ??= Nodes[exprs[^1]].Type; + return WithOneOrMoreChildren(ExpressionType.Block, type, null, default, default, exprsSubNode, vars); } - /// Adds a typed lambda node. + /// Adds a block node without explicit variables. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Lambda(int body, params int[] parameters) where TDelegate : Delegate => - Lambda(typeof(TDelegate), body, parameters); + public ushort Block(params ushort[] exprs) => + Block(null, null, exprs); /// Adds a lambda node. - /// - /// Lambda children store the body first and parameter declarations after it. - /// That keeps parameter identity stable even when the body refers to a parameter before its declaration node is read. - /// The lambda idx is also recorded in . - /// - public int Lambda(Type delegateType, int body, params int[] parameters) - { - var idx = parameters == null || parameters.Length == 0 - ? AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, 0, body) - : AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, PrependToChildList(body, parameters)); - LambdaNodes.Add(idx); - CollectLambdaClosureParameterUsages(idx); - return idx; - } + public ushort Lambda(Type delegateType, ushort bodyIdx, params ushort[] pars) => + WithOneOrMoreChildren(ExpressionType.Lambda, delegateType, null, default, default, bodyIdx, pars); + + /// Adds a typed lambda node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Lambda(ushort bodyIdx, params ushort[] parameters) where TDelegate : Delegate => + Lambda(typeof(TDelegate), bodyIdx, parameters); /// Adds a member-assignment binding node. - public int Bind(MemberInfo member, int expression) => - AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberAssignment, expression); + public ushort Bind(MemberInfo member, ushort expr) => + WithOneChild(default, GetMemberType(member), member, default, ExprNodeKind.MemberAssignment, expr); /// Adds a nested member-binding node. - public int MemberBind(MemberInfo member, params int[] bindings) => - AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberMemberBinding, bindings); + public ushort MemberBind(MemberInfo member, params ushort[] bindings) => + WithChildren(default, GetMemberType(member), member, default, ExprNodeKind.MemberMemberBinding, bindings); /// Adds an element-initializer node. - public int ElementInit(MethodInfo addMethod, params int[] arguments) => - AddFactoryAuxNode(addMethod.DeclaringType, addMethod, ExprNodeKind.ElementInit, arguments); + public ushort ElementInit(MethodInfo addMethod, params ushort[] args) => + WithChildren(default, addMethod.DeclaringType, addMethod, default, ExprNodeKind.ElementInit, args); /// Adds a list-binding node. - public int ListBind(MemberInfo member, params int[] initializers) => - AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberListBinding, initializers); + public ushort ListBind(MemberInfo member, params ushort[] initializers) => + WithChildren(default, GetMemberType(member), member, default, ExprNodeKind.MemberListBinding, initializers); /// Adds a member-init node. - public int MemberInit(int @new, params int[] bindings) => - bindings == null || bindings.Length == 0 - ? AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, @new) - : AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, PrependToChildList(@new, bindings)); + public ushort MemberInit(ushort expr, params ushort[] bindings) => + WithOneOrMoreChildren(ExpressionType.MemberInit, Nodes[expr].Type, null, default, default, expr, bindings); /// Adds a list-init node. - public int ListInit(int @new, params int[] initializers) => - initializers == null || initializers.Length == 0 - ? AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, @new) - : AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, PrependToChildList(@new, initializers)); + public ushort ListInit(ushort @new, params ushort[] initializers) => + WithOneOrMoreChildren(ExpressionType.ListInit, Nodes[@new].Type, null, default, default, @new, initializers); /// Adds a label-target node. - public int Label(Type type = null, string name = null) - { - var id = Nodes.Count + 1; - return AddRawLeafAuxNode(type ?? typeof(void), name, ExprNodeKind.LabelTarget, childIdx: id); - } + public ushort Label(Type type = null, string name = null) => + WithOneChild(default, type ?? typeof(void), name, default, ExprNodeKind.LabelTarget, 0); /// Adds a label-expression node. - /// The node idx is recorded in . - public int Label(int target, int? defaultValue = null) - { - var idx = defaultValue.HasValue - ? AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target, defaultValue.Value) - : AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target); - LabelNodes.Add(idx); - return idx; - } + public ushort Label(ushort target, ushort defaultValue = 0) => + WithOneChild(ExpressionType.Label, Nodes[target].Type, null, default, default, target); /// Adds a goto-family node. - /// The node idx is recorded in . - public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type type = null) + public ushort MakeGoto(GotoExpressionKind gotoKind, ushort target, ushort value = 0, Type type = null) { - var resultType = type ?? (value.HasValue ? Nodes[value.Value].Type : typeof(void)); - var idx = value.HasValue - ? AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, 0, target, value.Value) - : AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, 0, target); - GotoNodes.Add(idx); - return idx; + var resultType = type ?? (value != 0 ? Nodes[value].Type : typeof(void)); + return WithTwoChildren(ExpressionType.Goto, resultType, null, (byte)gotoKind, default, target, value); } /// Adds a goto node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Goto(int target, int? value = null, Type type = null) => MakeGoto(GotoExpressionKind.Goto, target, value, type); + public ushort Goto(ushort target, ushort value = 0, Type type = null) => MakeGoto(GotoExpressionKind.Goto, target, value, type); /// Adds a return node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Return(int target, int value) => MakeGoto(GotoExpressionKind.Return, target, value, Nodes[value].Type); + public ushort Return(ushort target, ushort value) => MakeGoto(GotoExpressionKind.Return, target, value, Nodes[value].Type); /// Adds a loop node. - public int Loop(int body, int? @break = null, int? @continue = null) - { - ChildList children = default; - children.Add(body); - if (@break.HasValue) - children.Add(@break.Value); - if (@continue.HasValue) - children.Add(@continue.Value); - return AddFactoryExpressionNode(typeof(void), null, ExpressionType.Loop, - (byte)((@break.HasValue ? LoopHasBreakFlag : 0) | (@continue.HasValue ? LoopHasContinueFlag : 0)), in children); - } + public ushort Loop(ushort body, ushort @break = 0, ushort @continue = 0) => + WithThreeChildren(ExpressionType.Loop, typeof(void), null, default, default, body, @break, @continue); /// Adds a switch-case node. - public int SwitchCase(int body, params int[] testValues) - { - ChildList children = default; - if (testValues != null && testValues.Length != 0) - for (var i = 0; i < testValues.Length; ++i) - children.Add(testValues[i]); - children.Add(body); - return AddFactoryAuxNode(Nodes[body].Type, null, ExprNodeKind.SwitchCase, children); - } - - /// Adds a switch node without an explicit default case or comparer. - public int Switch(int switchValue, params int[] cases) => - Switch(Nodes[switchValue].Type, switchValue, null, null, cases); + public ushort SwitchCase(ushort body, params ushort[] testValues) => + WithOneOrMoreChildren(ExpressionType.Switch, null, null, default, ExprNodeKind.SwitchCase, body, testValues); /// Adds a switch node. - public int Switch(Type type, int switchValue, int? defaultBody, MethodInfo comparison, params int[] cases) + public ushort Switch(Type type, ushort switchValue, ushort defaultBody, MethodInfo comparison, params ushort[] cases) { - ChildList children = default; - children.Add(switchValue); - if (defaultBody.HasValue) - children.Add(defaultBody.Value); - if (cases != null && cases.Length != 0) - { - ChildList caseChildren = default; - for (var i = 0; i < cases.Length; ++i) - caseChildren.Add(cases[i]); - children.Add(AddChildListNode(in caseChildren)); - } - return AddFactoryExpressionNode(type, comparison, ExpressionType.Switch, in children); + var casesIdx = cases == null || cases.Length == 0 + ? (ushort)0 + : WithChildren(ExpressionType.Switch, type, null, default, ExprNodeKind.SwitchCases, cases); + return WithThreeChildren(ExpressionType.Switch, type, comparison, default, default, casesIdx, switchValue, defaultBody); } + /// Adds a switch node without an explicit default case or comparer. + public ushort Switch(ushort switchValue, params ushort[] cases) => + WithChildren(ExpressionType.Switch, Nodes[switchValue].Type, null, default, default, cases); + /// Adds a catch block with an exception variable. - public int Catch(int variable, int body) => - AddFactoryAuxNode(Nodes[variable].Type, null, ExprNodeKind.CatchBlock, CatchHasVariableFlag, variable, body); + public ushort Catch(ushort variable, ushort body) => + WithTwoChildren(default, Nodes[variable].Type, null, default, ExprNodeKind.CatchBlock, body, variable); /// Adds a catch block without an exception variable. - public int Catch(Type test, int body) => - AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, 0, body); + public ushort Catch(Type test, ushort body) => + WithOneChild(default, test, null, default, ExprNodeKind.CatchBlock, body); /// Adds a catch block with optional exception variable and filter. - public int MakeCatchBlock(Type test, int? variable, int body, int? filter) - { - ChildList children = default; - if (variable.HasValue) - children.Add(variable.Value); - children.Add(body); - if (filter.HasValue) - children.Add(filter.Value); - return AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, - (byte)((variable.HasValue ? CatchHasVariableFlag : 0) | (filter.HasValue ? CatchHasFilterFlag : 0)), in children); - } + public ushort MakeCatchBlock(Type test, ushort variable, ushort body, ushort filter = 0) => + WithThreeChildren(default, test, null, default, ExprNodeKind.CatchBlock, body, variable, filter); /// Adds a try/catch node. - /// The node idx is recorded in . - public int TryCatch(int body, params int[] handlers) - { - int idx; - if (handlers == null || handlers.Length == 0) - { - idx = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body); - } - else - { - ChildList handlerChildren = default; - for (var i = 0; i < handlers.Length; ++i) - handlerChildren.Add(handlers[i]); - ChildList children = default; - children.Add(body); - children.Add(AddChildListNode(in handlerChildren)); - idx = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, in children); - } - TryCatchNodes.Add(idx); - return idx; - } - - /// Adds a try/finally node. - /// The node idx is recorded in . - public int TryFinally(int body, int @finally) - { - var idx = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body, @finally); - TryCatchNodes.Add(idx); - return idx; - } - - /// Adds a try/fault node. - /// The node idx is recorded in . - public int TryFault(int body, int fault) - { - var idx = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, TryFaultFlag, body, fault); - TryCatchNodes.Add(idx); - return idx; - } - - /// Adds a try node with optional finally block and catch handlers. - /// The node idx is recorded in . - public int TryCatchFinally(int body, int? @finally, params int[] handlers) - { - ChildList children = default; - children.Add(body); - if (@finally.HasValue) - children.Add(@finally.Value); - if (handlers != null && handlers.Length != 0) - { - ChildList handlerChildren = default; - for (var i = 0; i < handlers.Length; ++i) - handlerChildren.Add(handlers[i]); - children.Add(AddChildListNode(in handlerChildren)); - } - var idx = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, in children); - TryCatchNodes.Add(idx); - return idx; - } - - /// Adds a type-test node. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int TypeIs(int expression, Type type) => - AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeIs, expression); - - /// Adds a type-equality test node. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int TypeEqual(int expression, Type type) => - AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeEqual, expression); - - /// Adds a dynamic-expression node. - public int Dynamic(Type delegateType, CallSiteBinder binder, params int[] arguments) - { - ChildList children = default; - children.Add(AddObjectReferenceNode(typeof(Type), delegateType)); - if (arguments != null && arguments.Length != 0) - for (var i = 0; i < arguments.Length; ++i) - children.Add(arguments[i]); - return AddFactoryExpressionNode(typeof(object), binder, ExpressionType.Dynamic, children); - } - - /// Adds a runtime-variables node. - public int RuntimeVariables(params int[] variables) => - AddFactoryExpressionNode(typeof(IRuntimeVariables), null, ExpressionType.RuntimeVariables, variables); - - /// Adds a debug-info node. - public int DebugInfo(string fileName, int startLine, int startColumn, int endLine, int endColumn) => - AddFactoryExpressionNode(typeof(void), fileName, ExpressionType.DebugInfo, CreateDebugInfoChildren(startLine, startColumn, endLine, endColumn)); - - /// Flattens a System.Linq expression tree. - public static ExprTree FromExpression(SysExpr expression) => - new Builder().Build(expression ?? throw new ArgumentNullException(nameof(expression))); - - /// Flattens a LightExpression tree. - public static ExprTree FromLightExpression(LightExpression expression) => - FromExpression((expression ?? throw new ArgumentNullException(nameof(expression))).ToExpression()); - - /// Reconstructs the flat tree as a System.Linq expression tree. - [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", - Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] - public SysExpr ToExpression() => - Nodes.Count != 0 - ? new Reader(this).ReadExpression(RootIdx) - : throw new InvalidOperationException("Flat expression tree is empty."); - - /// Reconstructs the flat tree as a LightExpression tree. - [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] - public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); - - /// Structurally compares two flat expression trees. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(ExprTree other) => - new StructuralComparer().Eq(ref this, ref 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(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); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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 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, 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, 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, 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, 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, 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, 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) - { - if (children != null) - switch (children.Length) - { - case 1: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0]); - case 2: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1]); - case 3: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2]); - case 4: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3]); - case 5: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4]); - case 6: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4], children[5]); - case 7: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4], children[5], children[6]); - } - - var cloned = CloneChildren(children); - return Nodes.Add(new(nodeType, type, obj, ExprNodeKind.Expression, 0, in cloned)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) - { - var cloned = CloneChildren(children); - return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, in ChildList children) - { - var cloned = CloneChildren(children); - return AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, in cloned); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in children); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, children); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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 AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child) => - AddNode(type, obj, ExpressionType.Extension, kind, flags, MayBeCloneChild(child)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int child) => - AddFactoryAuxNode(type, obj, kind, 0, child); - - [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, MayBeCloneChild(child0), MayBeCloneChild(child1)); - - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int[] children) - { - var cloned = CloneChildren(children); - return AddNode(type, obj, ExpressionType.Extension, kind, 0, in cloned); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, in ChildList children) - { - var cloned = CloneChildren(children); - return AddNode(type, obj, ExpressionType.Extension, kind, flags, in cloned); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => - AddFactoryAuxNode(type, obj, kind, 0, in children); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => - AddNode(type, obj, ExpressionType.Extension, kind, 0, in children); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddRawLeafAuxNode(Type type, object obj, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0) => - AddLeafNode(type, obj, ExpressionType.Extension, kind, flags, childIdx, childCount); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddObjectReferenceNode(Type type, object obj) => - AddRawLeafAuxNode(type, obj, ExprNodeKind.ObjectReference); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddChildListNode(in ChildList children) => - AddRawAuxNode(null, null, ExprNodeKind.ChildList, in children); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddUInt16PairNode(int first, int second) => - AddRawLeafAuxNode(null, null, ExprNodeKind.UInt16Pair, childIdx: checked((ushort)first), childCount: checked((ushort)second)); - - private ChildList CreateDebugInfoChildren(int startLine, int startColumn, int endLine, int endColumn) - { - ChildList children = default; - children.Add(AddUInt16PairNode(startLine, startColumn)); - children.Add(AddUInt16PairNode(endLine, endColumn)); - return children; - } - - private static ChildList PrependToChildList(int first, int[] rest) - { - ChildList children = default; - children.Add(first); - if (rest != null) - for (var i = 0; i < rest.Length; ++i) - children.Add(rest[i]); - return children; - } - - /// Builds the flat representation while preserving parameter and label identity. - private struct Builder - { - private SmallMap16> _parameterIds; - private SmallMap16> _labelIds; - private ExprTree _tree; - - public ExprTree Build(SysExpr expression) - { - _tree.RootIdx = AddExpression(expression); - return _tree; - } - - private int AddExpression(SysExpr expression) - { - switch (expression.NodeType) - { - case ExpressionType.Constant: - return AddConstant((ConstantExpression)expression); - case ExpressionType.Default: - return _tree.AddLeafNode(expression.Type, null, expression.NodeType); - case ExpressionType.Parameter: - { - var parameter = (SysParameterExpression)expression; - return _tree.AddLeafNode(expression.Type, parameter.Name, expression.NodeType, - flags: parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); - } - case ExpressionType.Lambda: - { - // Layout: children[0] = body, children[1..n] = parameter decl nodes. - // 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 = (LambdaExpression)expression; - ChildList children = default; - 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, in children); - _tree.LambdaNodes.Add(lambdaIdx); - _tree.CollectLambdaClosureParameterUsages(lambdaIdx); - return lambdaIdx; - } - case ExpressionType.Block: - { - // 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 = (BlockExpression)expression; - ChildList children = default; - var hasVariables = block.Variables.Count != 0; - if (hasVariables) - { - ChildList variables = default; - for (var i = 0; i < block.Variables.Count; ++i) - variables.Add(AddExpression(block.Variables[i])); - children.Add(_tree.AddChildListNode(in variables)); - } - ChildList expressions = default; - for (var i = 0; i < block.Expressions.Count; ++i) - expressions.Add(AddExpression(block.Expressions[i])); - children.Add(_tree.AddChildListNode(in expressions)); - var blockIdx = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children); - if (hasVariables) - _tree.BlocksWithVariables.Add(blockIdx); - return blockIdx; - } - case ExpressionType.MemberAccess: - { - var member = (MemberExpression)expression; - ChildList children = default; - if (member.Expression != null) - children.Add(AddExpression(member.Expression)); - return _tree.AddRawExpressionNode(expression.Type, member.Member, expression.NodeType, - children); - } - case ExpressionType.Call: - { - var call = (MethodCallExpression)expression; - ChildList children = default; - if (call.Object != null) - children.Add(AddExpression(call.Object)); - for (var i = 0; i < call.Arguments.Count; ++i) - children.Add(AddExpression(call.Arguments[i])); - return _tree.AddRawExpressionNode(expression.Type, call.Method, expression.NodeType, children); - } - case ExpressionType.New: - { - var @new = (NewExpression)expression; - ChildList children = default; - for (var i = 0; i < @new.Arguments.Count; ++i) - children.Add(AddExpression(@new.Arguments[i])); - return _tree.AddRawExpressionNode(expression.Type, @new.Constructor, expression.NodeType, children); - } - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - { - var array = (NewArrayExpression)expression; - ChildList children = default; - for (var i = 0; i < array.Expressions.Count; ++i) - children.Add(AddExpression(array.Expressions[i])); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); - } - case ExpressionType.Invoke: - { - var invoke = (InvocationExpression)expression; - ChildList children = default; - children.Add(AddExpression(invoke.Expression)); - for (var i = 0; i < invoke.Arguments.Count; ++i) - children.Add(AddExpression(invoke.Arguments[i])); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); - } - case ExpressionType.Index: - { - var indexExpr = (IndexExpression)expression; - ChildList children = default; - if (indexExpr.Object != null) - children.Add(AddExpression(indexExpr.Object)); - for (var i = 0; i < indexExpr.Arguments.Count; ++i) - children.Add(AddExpression(indexExpr.Arguments[i])); - return _tree.AddRawExpressionNode(expression.Type, indexExpr.Indexer, expression.NodeType, children); - } - case ExpressionType.Conditional: - { - var conditional = (ConditionalExpression)expression; - ChildList children = default; - children.Add(AddExpression(conditional.Test)); - children.Add(AddExpression(conditional.IfTrue)); - children.Add(AddExpression(conditional.IfFalse)); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children[0], children[1], children[2]); - } - case ExpressionType.Loop: - { - var loop = (LoopExpression)expression; - ChildList children = default; - children.Add(AddExpression(loop.Body)); - if (loop.BreakLabel != null) - children.Add(AddLabelTarget(loop.BreakLabel)); - if (loop.ContinueLabel != null) - children.Add(AddLabelTarget(loop.ContinueLabel)); - return _tree.AddNode(expression.Type, null, expression.NodeType, ExprNodeKind.Expression, - (byte)((loop.BreakLabel != null ? LoopHasBreakFlag : 0) | (loop.ContinueLabel != null ? LoopHasContinueFlag : 0)), in children); - } - case ExpressionType.Goto: - { - var @goto = (GotoExpression)expression; - ChildList children = default; - children.Add(AddLabelTarget(@goto.Target)); - if (@goto.Value != null) - children.Add(AddExpression(@goto.Value)); - var gotoIdx = _tree.AddRawExpressionNode(expression.Type, @goto.Kind, expression.NodeType, children); - _tree.GotoNodes.Add(gotoIdx); - return gotoIdx; - } - case ExpressionType.Label: - { - var label = (LabelExpression)expression; - ChildList children = default; - children.Add(AddLabelTarget(label.Target)); - if (label.DefaultValue != null) - children.Add(AddExpression(label.DefaultValue)); - var labelIdx = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); - _tree.LabelNodes.Add(labelIdx); - return labelIdx; - } - case ExpressionType.Switch: - { - var @switch = (SwitchExpression)expression; - ChildList children = default; - children.Add(AddExpression(@switch.SwitchValue)); - if (@switch.DefaultBody != null) - children.Add(AddExpression(@switch.DefaultBody)); - if (@switch.Cases.Count != 0) - { - ChildList cases = default; - for (var i = 0; i < @switch.Cases.Count; ++i) - cases.Add(AddSwitchCase(@switch.Cases[i])); - children.Add(_tree.AddChildListNode(in cases)); - } - return _tree.AddRawExpressionNode(expression.Type, @switch.Comparison, expression.NodeType, in children); - } - case ExpressionType.Try: - { - var @try = (TryExpression)expression; - ChildList children = default; - children.Add(AddExpression(@try.Body)); - var flags = (byte)0; - if (@try.Fault != null) - { - flags = TryFaultFlag; - children.Add(AddExpression(@try.Fault)); - } - else if (@try.Finally != null) - children.Add(AddExpression(@try.Finally)); - if (@try.Handlers.Count != 0) - { - ChildList handlers = default; - for (var i = 0; i < @try.Handlers.Count; ++i) - handlers.Add(AddCatchBlock(@try.Handlers[i])); - children.Add(_tree.AddChildListNode(in handlers)); - } - var tryIdx = _tree.AddNode(expression.Type, null, expression.NodeType, ExprNodeKind.Expression, flags, in children); - _tree.TryCatchNodes.Add(tryIdx); - return tryIdx; - } - case ExpressionType.MemberInit: - { - var memberInit = (MemberInitExpression)expression; - ChildList children = default; - children.Add(AddExpression(memberInit.NewExpression)); - for (var i = 0; i < memberInit.Bindings.Count; ++i) - children.Add(AddMemberBinding(memberInit.Bindings[i])); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); - } - case ExpressionType.ListInit: - { - var listInit = (ListInitExpression)expression; - ChildList children = default; - children.Add(AddExpression(listInit.NewExpression)); - for (var i = 0; i < listInit.Initializers.Count; ++i) - children.Add(AddElementInit(listInit.Initializers[i])); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); - } - case ExpressionType.TypeIs: - case ExpressionType.TypeEqual: - { - var typeBinary = (TypeBinaryExpression)expression; - ChildList children = default; - children.Add(AddExpression(typeBinary.Expression)); - return _tree.AddRawExpressionNode(expression.Type, typeBinary.TypeOperand, expression.NodeType, - children); - } - case ExpressionType.Dynamic: - { - var dynamic = (DynamicExpression)expression; - ChildList children = default; - children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); - for (var i = 0; i < dynamic.Arguments.Count; ++i) - children.Add(AddExpression(dynamic.Arguments[i])); - return _tree.AddRawExpressionNode(expression.Type, dynamic.Binder, expression.NodeType, children); - } - case ExpressionType.RuntimeVariables: - { - var runtime = (RuntimeVariablesExpression)expression; - ChildList children = default; - for (var i = 0; i < runtime.Variables.Count; ++i) - children.Add(AddExpression(runtime.Variables[i])); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); - } - case ExpressionType.DebugInfo: - { - 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 UnaryExpression unary) - { - ChildList children = default; - children.Add(AddExpression(unary.Operand)); - return _tree.AddRawExpressionNode(expression.Type, unary.Method, expression.NodeType, - children); - } - - if (expression is BinaryExpression binary) - { - ChildList children = default; - children.Add(AddExpression(binary.Left)); - children.Add(AddExpression(binary.Right)); - if (binary.Conversion != null) - children.Add(AddExpression(binary.Conversion)); - return _tree.AddNode(expression.Type, binary.Method, expression.NodeType, ExprNodeKind.Expression, - binary.IsLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, in children); - } - - throw new NotSupportedException($"Flattening of `ExpressionType.{expression.NodeType}` is not supported yet."); - } - } - - private int AddConstant(ConstantExpression constant) => - _tree.Constant(constant.Value, constant.Type); - - private int AddSwitchCase(SysSwitchCase switchCase) - { - ChildList children = default; - for (var i = 0; i < switchCase.TestValues.Count; ++i) - children.Add(AddExpression(switchCase.TestValues[i])); - children.Add(AddExpression(switchCase.Body)); - return _tree.AddRawAuxNode(switchCase.Body.Type, null, ExprNodeKind.SwitchCase, children); - } - - private int AddCatchBlock(SysCatchBlock catchBlock) - { - ChildList children = default; - if (catchBlock.Variable != null) - children.Add(AddExpression(catchBlock.Variable)); - children.Add(AddExpression(catchBlock.Body)); - if (catchBlock.Filter != null) - children.Add(AddExpression(catchBlock.Filter)); - return _tree.AddNode(catchBlock.Test, null, ExpressionType.Extension, ExprNodeKind.CatchBlock, - (byte)((catchBlock.Variable != null ? CatchHasVariableFlag : 0) | (catchBlock.Filter != null ? CatchHasFilterFlag : 0)), in children); - } - - private int AddLabelTarget(SysLabelTarget target) => - _tree.AddRawLeafAuxNode(target.Type, target.Name, ExprNodeKind.LabelTarget, childIdx: GetId(ref _labelIds, target)); - - private int AddMemberBinding(SysMemberBinding binding) - { - switch (binding.BindingType) - { - case MemberBindingType.Assignment: - ChildList assignmentChildren = default; - assignmentChildren.Add(AddExpression(((MemberAssignment)binding).Expression)); - return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, - assignmentChildren); - case MemberBindingType.MemberBinding: - { - var memberBinding = (MemberMemberBinding)binding; - ChildList children = default; - for (var i = 0; i < memberBinding.Bindings.Count; ++i) - children.Add(AddMemberBinding(memberBinding.Bindings[i])); - return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberMemberBinding, children); - } - case MemberBindingType.ListBinding: - { - var listBinding = (MemberListBinding)binding; - ChildList children = default; - for (var i = 0; i < listBinding.Initializers.Count; ++i) - children.Add(AddElementInit(listBinding.Initializers[i])); - return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberListBinding, children); - } - default: - throw new NotSupportedException($"Flattening of member binding `{binding.BindingType}` is not supported yet."); - } - } - - private int AddElementInit(SysElementInit init) - { - ChildList children = default; - for (var i = 0; i < init.Arguments.Count; ++i) - children.Add(AddExpression(init.Arguments[i])); - return _tree.AddRawAuxNode(init.AddMethod.DeclaringType, init.AddMethod, ExprNodeKind.ElementInit, children); - } + public ushort TryCatch(ushort body, params ushort[] handlers) => + WithOneOrMoreChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, handlers); - private static int GetId(ref SmallMap16> ids, object item) - { - ref var id = ref ids.Map.AddOrGetValueRef(item, out var found); - if (!found) - id = ids.Map.Count; - return id; - } + /// Adds a try/finally node. + public ushort TryFinally(ushort body, ushort @finally) => + WithTwoChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, @finally); - private static Type GetMemberType(MemberInfo member) => member switch - { - FieldInfo field => field.FieldType, - PropertyInfo property => property.PropertyType, - _ => typeof(object) - }; - } + /// Adds a try/fault node. + public ushort TryFault(ushort body, ushort fault) => + WithTwoChildren(ExpressionType.Try, Nodes[body].Type, null, TryFaultFlag, default, body, fault); + + /// Adds a try node with optional finally block and catch handlers. + public ushort TryCatchFinally(ushort body, ushort @finally, params ushort[] handlers) => + WithTwoOrMoreChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, @finally, handlers); + /// Adds a type-test node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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)); + public ushort TypeIs(ushort expr, Type type) => + WithOneChild(ExpressionType.TypeIs, typeof(bool), null, default, default, expr); + /// Adds a type-equality test node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddInlineConstantNode(Type type, uint inlineValue) => Nodes.Add(new(type, inlineValue)); + public ushort TypeEqual(ushort expr, Type type) => + WithOneChild(ExpressionType.TypeEqual, typeof(bool), type, default, default, expr); - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags) - { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags); - return nodeIdx; - } + /// Adds a dynamic-expression node. + public ushort Dynamic(Type delegateType, CallSiteBinder binder, params ushort[] args) => + WithChildren(ExpressionType.Dynamic, typeof(object), binder, default, default, args); - 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)); + /// Adds a runtime-variables node. + public ushort RuntimeVariables(params ushort[] vars) => + WithChildren(ExpressionType.RuntimeVariables, typeof(IRuntimeVariables), null, default, default, vars); - 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).NextIdx = c1; - return nodeIdx; - } + /// Adds a debug-info node. + public ushort DebugInfo(string fileName, ushort startLine, ushort startColumn, ushort endLine, ushort endColumn) => + WithChildren(ExpressionType.DebugInfo, typeof(void), fileName, default, default, startLine, startColumn, endLine, endColumn); - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2) + private int AddSwitchCase(SysSwitchCase switchCase) { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 3); - Nodes.GetSurePresentRef(c0).NextIdx = c1; - Nodes.GetSurePresentRef(c1).NextIdx = c2; - return nodeIdx; + ChildIdxs children = default; + for (var i = 0; i < switchCase.TestValues.Count; ++i) + children.Add(AddExpression(switchCase.TestValues[i])); + children.Add(AddExpression(switchCase.Body)); + return _tree.AddRawAuxNode(switchCase.Body.Type, null, ExprNodeKind.SwitchCase, children); } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3) + private int AddCatchBlock(SysCatchBlock catchBlock) { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 4); - Nodes.GetSurePresentRef(c0).NextIdx = c1; - Nodes.GetSurePresentRef(c1).NextIdx = c2; - Nodes.GetSurePresentRef(c2).NextIdx = c3; - return nodeIdx; + ChildIdxs children = default; + if (catchBlock.Variable != null) + children.Add(AddExpression(catchBlock.Variable)); + children.Add(AddExpression(catchBlock.Body)); + if (catchBlock.Filter != null) + children.Add(AddExpression(catchBlock.Filter)); + return _tree.AddNode(catchBlock.Test, null, ExpressionType.Extension, ExprNodeKind.CatchBlock, + (byte)((catchBlock.Variable != null ? CatchHasVariableFlag : 0) | (catchBlock.Filter != null ? CatchHasFilterFlag : 0)), in children); } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3, ushort c4) + private int AddLabelTarget(SysLabelTarget target) => + _tree.AddRawLeafAuxNode(target.Type, target.Name, ExprNodeKind.LabelTarget, childIdx: GetId(ref _labelIds, target)); + + private int AddMemberBinding(SysMemberBinding binding) { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 5); - Nodes.GetSurePresentRef(c0).NextIdx = c1; - Nodes.GetSurePresentRef(c1).NextIdx = c2; - Nodes.GetSurePresentRef(c2).NextIdx = c3; - Nodes.GetSurePresentRef(c3).NextIdx = c4; - return nodeIdx; + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + ChildIdxs assignmentChildren = default; + assignmentChildren.Add(AddExpression(((MemberAssignment)binding).Expression)); + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, + assignmentChildren); + case MemberBindingType.MemberBinding: + { + var memberBinding = (MemberMemberBinding)binding; + ChildIdxs children = default; + for (var i = 0; i < memberBinding.Bindings.Count; ++i) + children.Add(AddMemberBinding(memberBinding.Bindings[i])); + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberMemberBinding, children); + } + case MemberBindingType.ListBinding: + { + var listBinding = (MemberListBinding)binding; + ChildIdxs children = default; + for (var i = 0; i < listBinding.Initializers.Count; ++i) + children.Add(AddElementInit(listBinding.Initializers[i])); + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberListBinding, children); + } + default: + throw new NotSupportedException($"Flattening of member binding `{binding.BindingType}` is not supported yet."); + } } - 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) + private int AddElementInit(SysElementInit init) { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 6); - 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; + ChildIdxs children = default; + for (var i = 0; i < init.Arguments.Count; ++i) + children.Add(AddExpression(init.Arguments[i])); + return _tree.AddRawAuxNode(init.AddMethod.DeclaringType, init.AddMethod, ExprNodeKind.ElementInit, children); } - 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) + private static int GetId(ref SmallMap16> ids, object item) { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 7); - 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; + ref var id = ref ids.Map.AddOrGetValueRef(item, out var found); + if (!found) + id = ids.Map.Count; + return id; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort[] children) + public ushort FromSysExpr(SysExpr expr) { - if (children == null || children.Length == 0) - return AddNode(type, obj, nodeType, kind, flags); - - var nodeIdx = Nodes.Count; - 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]).NextIdx = children[i]; - return nodeIdx; + switch (expr.NodeType) + { + case ExpressionType.Constant: + return Constant(((ConstantExpression)expr).Value, expr.Type); + + case ExpressionType.Default: + return Default(expr.Type); + + case ExpressionType.Parameter: + { + var parameter = (SysParameterExpression)expr; + return _tree.AddLeafNode(expr.Type, parameter.Name, expr.NodeType, + flags: parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); + } + case ExpressionType.Lambda: + { + // Layout: children[0] = body, children[1..n] = parameter decl nodes. + // 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 = (LambdaExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(lambda.Body)); + for (var i = 0; i < lambda.Parameters.Count; ++i) + children.Add(AddExpression(lambda.Parameters[i])); + var lambdaIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, in children); + _tree.LambdaNodes.Add(lambdaIdx); + _tree.CollectLambdaClosureParameterUsages(lambdaIdx); + return lambdaIdx; + } + case ExpressionType.Block: + { + // 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 = (BlockExpression)expr; + ChildIdxs children = default; + var hasVariables = block.Variables.Count != 0; + if (hasVariables) + { + ChildIdxs variables = default; + for (var i = 0; i < block.Variables.Count; ++i) + variables.Add(AddExpression(block.Variables[i])); + children.Add(_tree.AddChildListNode(in variables)); + } + ChildIdxs expressions = default; + for (var i = 0; i < block.Expressions.Count; ++i) + expressions.Add(AddExpression(block.Expressions[i])); + children.Add(_tree.AddChildListNode(in expressions)); + var blockIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, in children); + if (hasVariables) + _tree.BlocksWithVariables.Add(blockIdx); + return blockIdx; + } + case ExpressionType.MemberAccess: + { + var member = (MemberExpression)expr; + ChildIdxs children = default; + if (member.Expression != null) + children.Add(AddExpression(member.Expression)); + return _tree.AddRawExpressionNode(expr.Type, member.Member, expr.NodeType, + children); + } + case ExpressionType.Call: + { + var call = (MethodCallExpression)expr; + ChildIdxs children = default; + if (call.Object != null) + children.Add(AddExpression(call.Object)); + for (var i = 0; i < call.Arguments.Count; ++i) + children.Add(AddExpression(call.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, call.Method, expr.NodeType, children); + } + case ExpressionType.New: + { + var @new = (NewExpression)expr; + ChildIdxs children = default; + for (var i = 0; i < @new.Arguments.Count; ++i) + children.Add(AddExpression(@new.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, @new.Constructor, expr.NodeType, children); + } + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + { + var array = (NewArrayExpression)expr; + ChildIdxs children = default; + for (var i = 0; i < array.Expressions.Count; ++i) + children.Add(AddExpression(array.Expressions[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.Invoke: + { + var invoke = (InvocationExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(invoke.Expression)); + for (var i = 0; i < invoke.Arguments.Count; ++i) + children.Add(AddExpression(invoke.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.Index: + { + var indexExpr = (IndexExpression)expr; + ChildIdxs children = default; + if (indexExpr.Object != null) + children.Add(AddExpression(indexExpr.Object)); + for (var i = 0; i < indexExpr.Arguments.Count; ++i) + children.Add(AddExpression(indexExpr.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, indexExpr.Indexer, expr.NodeType, children); + } + case ExpressionType.Conditional: + { + var conditional = (ConditionalExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(conditional.Test)); + children.Add(AddExpression(conditional.IfTrue)); + children.Add(AddExpression(conditional.IfFalse)); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children[0], children[1], children[2]); + } + case ExpressionType.Loop: + { + var loop = (LoopExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(loop.Body)); + if (loop.BreakLabel != null) + children.Add(AddLabelTarget(loop.BreakLabel)); + if (loop.ContinueLabel != null) + children.Add(AddLabelTarget(loop.ContinueLabel)); + return _tree.AddNode(expr.Type, null, expr.NodeType, ExprNodeKind.Expression, + (byte)((loop.BreakLabel != null ? LoopHasBreakFlag : 0) | (loop.ContinueLabel != null ? LoopHasContinueFlag : 0)), in children); + } + case ExpressionType.Goto: + { + var @goto = (GotoExpression)expr; + ChildIdxs children = default; + children.Add(AddLabelTarget(@goto.Target)); + if (@goto.Value != null) + children.Add(AddExpression(@goto.Value)); + var gotoIdx = _tree.AddRawExpressionNode(expr.Type, @goto.Kind, expr.NodeType, children); + _tree.GotoNodes.Add(gotoIdx); + return gotoIdx; + } + case ExpressionType.Label: + { + var label = (LabelExpression)expr; + ChildIdxs children = default; + children.Add(AddLabelTarget(label.Target)); + if (label.DefaultValue != null) + children.Add(AddExpression(label.DefaultValue)); + var labelIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + _tree.LabelNodes.Add(labelIdx); + return labelIdx; + } + case ExpressionType.Switch: + { + var @switch = (SwitchExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(@switch.SwitchValue)); + if (@switch.DefaultBody != null) + children.Add(AddExpression(@switch.DefaultBody)); + if (@switch.Cases.Count != 0) + { + ChildIdxs cases = default; + for (var i = 0; i < @switch.Cases.Count; ++i) + cases.Add(AddSwitchCase(@switch.Cases[i])); + children.Add(_tree.AddChildListNode(in cases)); + } + return _tree.AddRawExpressionNode(expr.Type, @switch.Comparison, expr.NodeType, in children); + } + case ExpressionType.Try: + { + var @try = (TryExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(@try.Body)); + var flags = (byte)0; + if (@try.Fault != null) + { + flags = TryFaultFlag; + children.Add(AddExpression(@try.Fault)); + } + else if (@try.Finally != null) + children.Add(AddExpression(@try.Finally)); + if (@try.Handlers.Count != 0) + { + ChildIdxs handlers = default; + for (var i = 0; i < @try.Handlers.Count; ++i) + handlers.Add(AddCatchBlock(@try.Handlers[i])); + children.Add(_tree.AddChildListNode(in handlers)); + } + var tryIdx = _tree.AddNode(expr.Type, null, expr.NodeType, ExprNodeKind.Expression, flags, in children); + _tree.TryCatchNodes.Add(tryIdx); + return tryIdx; + } + case ExpressionType.MemberInit: + { + var memberInit = (MemberInitExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(memberInit.NewExpression)); + for (var i = 0; i < memberInit.Bindings.Count; ++i) + children.Add(AddMemberBinding(memberInit.Bindings[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.ListInit: + { + var listInit = (ListInitExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(listInit.NewExpression)); + for (var i = 0; i < listInit.Initializers.Count; ++i) + children.Add(AddElementInit(listInit.Initializers[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: + { + var typeBinary = (TypeBinaryExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(typeBinary.Expression)); + return _tree.AddRawExpressionNode(expr.Type, typeBinary.TypeOperand, expr.NodeType, + children); + } + case ExpressionType.Dynamic: + { + var dynamic = (DynamicExpression)expr; + ChildIdxs children = default; + children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); + for (var i = 0; i < dynamic.Arguments.Count; ++i) + children.Add(AddExpression(dynamic.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, dynamic.Binder, expr.NodeType, children); + } + case ExpressionType.RuntimeVariables: + { + var runtime = (RuntimeVariablesExpression)expr; + ChildIdxs children = default; + for (var i = 0; i < runtime.Variables.Count; ++i) + children.Add(AddExpression(runtime.Variables[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.DebugInfo: + { + var debug = (DebugInfoExpression)expr; + return _tree.AddFactoryExpressionNode(expr.Type, debug.Document.FileName, expr.NodeType, + _tree.CreateDebugInfoChildren(debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn)); + } + default: + if (expr is UnaryExpression unary) + { + ChildIdxs children = default; + children.Add(AddExpression(unary.Operand)); + return _tree.AddRawExpressionNode(expr.Type, unary.Method, expr.NodeType, + children); + } + + if (expr is BinaryExpression binary) + { + ChildIdxs children = default; + children.Add(AddExpression(binary.Left)); + children.Add(AddExpression(binary.Right)); + if (binary.Conversion != null) + children.Add(AddExpression(binary.Conversion)); + return _tree.AddNode(expr.Type, binary.Method, expr.NodeType, ExprNodeKind.Expression, + binary.IsLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, in children); + } + + throw new NotSupportedException($"Flattening of `ExpressionType.{expr.NodeType}` is not supported yet."); + } } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, in ChildList children) + public ushort FromLightExpr(LightExpression.Expression expr) { - if (children.Count == 0) - return AddNode(type, obj, nodeType, kind, flags); - - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, children[0], children.Count); - for (var i = 1; i < children.Count; ++i) - Nodes.GetSurePresentRef(children[i - 1]).NextIdx = children[i]; - return nodeIdx; + switch (expr.NodeType) + { + case ExpressionType.Constant: + return Constant(((LightExpression.ConstantExpression)expr).Value, expr.Type); + + case ExpressionType.Default: + return _tree.AddLeafNode(expr.Type, null, expr.NodeType); + + case ExpressionType.Parameter: + { + var parameter = (SysParameterExpression)expr; + return _tree.AddLeafNode(expr.Type, parameter.Name, expr.NodeType, + flags: parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); + } + case ExpressionType.Lambda: + { + // Layout: children[0] = body, children[1..n] = parameter decl nodes. + // 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 = (LambdaExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(lambda.Body)); + for (var i = 0; i < lambda.Parameters.Count; ++i) + children.Add(AddExpression(lambda.Parameters[i])); + var lambdaIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, in children); + _tree.LambdaNodes.Add(lambdaIdx); + _tree.CollectLambdaClosureParameterUsages(lambdaIdx); + return lambdaIdx; + } + case ExpressionType.Block: + { + // 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 = (BlockExpression)expr; + ChildIdxs children = default; + var hasVariables = block.Variables.Count != 0; + if (hasVariables) + { + ChildIdxs variables = default; + for (var i = 0; i < block.Variables.Count; ++i) + variables.Add(AddExpression(block.Variables[i])); + children.Add(_tree.AddChildListNode(in variables)); + } + ChildIdxs expressions = default; + for (var i = 0; i < block.Expressions.Count; ++i) + expressions.Add(AddExpression(block.Expressions[i])); + children.Add(_tree.AddChildListNode(in expressions)); + var blockIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, in children); + if (hasVariables) + _tree.BlocksWithVariables.Add(blockIdx); + return blockIdx; + } + case ExpressionType.MemberAccess: + { + var member = (MemberExpression)expr; + ChildIdxs children = default; + if (member.Expression != null) + children.Add(AddExpression(member.Expression)); + return _tree.AddRawExpressionNode(expr.Type, member.Member, expr.NodeType, + children); + } + case ExpressionType.Call: + { + var call = (MethodCallExpression)expr; + ChildIdxs children = default; + if (call.Object != null) + children.Add(AddExpression(call.Object)); + for (var i = 0; i < call.Arguments.Count; ++i) + children.Add(AddExpression(call.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, call.Method, expr.NodeType, children); + } + case ExpressionType.New: + { + var @new = (NewExpression)expr; + ChildIdxs children = default; + for (var i = 0; i < @new.Arguments.Count; ++i) + children.Add(AddExpression(@new.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, @new.Constructor, expr.NodeType, children); + } + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + { + var array = (NewArrayExpression)expr; + ChildIdxs children = default; + for (var i = 0; i < array.Expressions.Count; ++i) + children.Add(AddExpression(array.Expressions[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.Invoke: + { + var invoke = (InvocationExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(invoke.Expression)); + for (var i = 0; i < invoke.Arguments.Count; ++i) + children.Add(AddExpression(invoke.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.Index: + { + var indexExpr = (IndexExpression)expr; + ChildIdxs children = default; + if (indexExpr.Object != null) + children.Add(AddExpression(indexExpr.Object)); + for (var i = 0; i < indexExpr.Arguments.Count; ++i) + children.Add(AddExpression(indexExpr.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, indexExpr.Indexer, expr.NodeType, children); + } + case ExpressionType.Conditional: + { + var conditional = (ConditionalExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(conditional.Test)); + children.Add(AddExpression(conditional.IfTrue)); + children.Add(AddExpression(conditional.IfFalse)); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children[0], children[1], children[2]); + } + case ExpressionType.Loop: + { + var loop = (LoopExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(loop.Body)); + if (loop.BreakLabel != null) + children.Add(AddLabelTarget(loop.BreakLabel)); + if (loop.ContinueLabel != null) + children.Add(AddLabelTarget(loop.ContinueLabel)); + return _tree.AddNode(expr.Type, null, expr.NodeType, ExprNodeKind.Expression, + (byte)((loop.BreakLabel != null ? LoopHasBreakFlag : 0) | (loop.ContinueLabel != null ? LoopHasContinueFlag : 0)), in children); + } + case ExpressionType.Goto: + { + var @goto = (GotoExpression)expr; + ChildIdxs children = default; + children.Add(AddLabelTarget(@goto.Target)); + if (@goto.Value != null) + children.Add(AddExpression(@goto.Value)); + var gotoIdx = _tree.AddRawExpressionNode(expr.Type, @goto.Kind, expr.NodeType, children); + _tree.GotoNodes.Add(gotoIdx); + return gotoIdx; + } + case ExpressionType.Label: + { + var label = (LabelExpression)expr; + ChildIdxs children = default; + children.Add(AddLabelTarget(label.Target)); + if (label.DefaultValue != null) + children.Add(AddExpression(label.DefaultValue)); + var labelIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + _tree.LabelNodes.Add(labelIdx); + return labelIdx; + } + case ExpressionType.Switch: + { + var @switch = (SwitchExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(@switch.SwitchValue)); + if (@switch.DefaultBody != null) + children.Add(AddExpression(@switch.DefaultBody)); + if (@switch.Cases.Count != 0) + { + ChildIdxs cases = default; + for (var i = 0; i < @switch.Cases.Count; ++i) + cases.Add(AddSwitchCase(@switch.Cases[i])); + children.Add(_tree.AddChildListNode(in cases)); + } + return _tree.AddRawExpressionNode(expr.Type, @switch.Comparison, expr.NodeType, in children); + } + case ExpressionType.Try: + { + var @try = (TryExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(@try.Body)); + var flags = (byte)0; + if (@try.Fault != null) + { + flags = TryFaultFlag; + children.Add(AddExpression(@try.Fault)); + } + else if (@try.Finally != null) + children.Add(AddExpression(@try.Finally)); + if (@try.Handlers.Count != 0) + { + ChildIdxs handlers = default; + for (var i = 0; i < @try.Handlers.Count; ++i) + handlers.Add(AddCatchBlock(@try.Handlers[i])); + children.Add(_tree.AddChildListNode(in handlers)); + } + var tryIdx = _tree.AddNode(expr.Type, null, expr.NodeType, ExprNodeKind.Expression, flags, in children); + _tree.TryCatchNodes.Add(tryIdx); + return tryIdx; + } + case ExpressionType.MemberInit: + { + var memberInit = (MemberInitExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(memberInit.NewExpression)); + for (var i = 0; i < memberInit.Bindings.Count; ++i) + children.Add(AddMemberBinding(memberInit.Bindings[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.ListInit: + { + var listInit = (ListInitExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(listInit.NewExpression)); + for (var i = 0; i < listInit.Initializers.Count; ++i) + children.Add(AddElementInit(listInit.Initializers[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: + { + var typeBinary = (TypeBinaryExpression)expr; + ChildIdxs children = default; + children.Add(AddExpression(typeBinary.Expression)); + return _tree.AddRawExpressionNode(expr.Type, typeBinary.TypeOperand, expr.NodeType, + children); + } + case ExpressionType.Dynamic: + { + var dynamic = (DynamicExpression)expr; + ChildIdxs children = default; + children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); + for (var i = 0; i < dynamic.Arguments.Count; ++i) + children.Add(AddExpression(dynamic.Arguments[i])); + return _tree.AddRawExpressionNode(expr.Type, dynamic.Binder, expr.NodeType, children); + } + case ExpressionType.RuntimeVariables: + { + var runtime = (RuntimeVariablesExpression)expr; + ChildIdxs children = default; + for (var i = 0; i < runtime.Variables.Count; ++i) + children.Add(AddExpression(runtime.Variables[i])); + return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + } + case ExpressionType.DebugInfo: + { + var debug = (DebugInfoExpression)expr; + return _tree.AddFactoryExpressionNode(expr.Type, debug.Document.FileName, expr.NodeType, + _tree.CreateDebugInfoChildren(debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn)); + } + default: + if (expr is UnaryExpression unary) + { + ChildIdxs children = default; + children.Add(AddExpression(unary.Operand)); + return _tree.AddRawExpressionNode(expr.Type, unary.Method, expr.NodeType, + children); + } + + if (expr is BinaryExpression binary) + { + ChildIdxs children = default; + children.Add(AddExpression(binary.Left)); + children.Add(AddExpression(binary.Right)); + if (binary.Conversion != null) + children.Add(AddExpression(binary.Conversion)); + return _tree.AddNode(expr.Type, binary.Method, expr.NodeType, ExprNodeKind.Expression, + binary.IsLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, in children); + } + + throw new NotSupportedException($"Flattening of `ExpressionType.{expr.NodeType}` is not supported yet."); + } } + /// Reconstructs the flat tree as a System.Linq expression tree. + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", + Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] + public SysExpr ToExpression() => + Nodes.Count != 0 + ? new SysExprBuilder(this).ReadExpression(RootIdx) + : throw new InvalidOperationException("Flat expression tree is empty."); + + /// Reconstructs the flat tree as a LightExpression tree. + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); + + /// Structurally compares two flat expression trees. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(ExprTree other) => + new StructuralComparer().Eq(ref this, ref 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(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); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool In32BitRange(TypeCode tc) => tc == TypeCode.Boolean || tc == TypeCode.Byte || tc == TypeCode.SByte || @@ -1377,6 +1350,7 @@ private static bool In32BitRange(TypeCode tc) => _ => FlatExpressionThrow.UnsupportedInlineConstantType(value, tc) }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Type GetMemberType(MemberInfo member) => member switch { FieldInfo field => field.FieldType, @@ -1384,6 +1358,7 @@ private static bool In32BitRange(TypeCode tc) => _ => typeof(object) }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType, MethodInfo method) => nodeType switch { @@ -1391,12 +1366,9 @@ private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType _ => method?.ReturnType ?? operandType }; - private static Type GetBinaryResultType(ExpressionType nodeType, Type leftType, Type rightType, MethodInfo method) - { - if (method != null) - return method.ReturnType; - - return nodeType switch + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Type GetBinaryResultType(ExpressionType nodeType, Type leftType, MethodInfo method) => + method != null ? method.ReturnType : nodeType switch { ExpressionType.Equal or ExpressionType.NotEqual or ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual or ExpressionType.LessThan or ExpressionType.LessThanOrEqual or ExpressionType.AndAlso or ExpressionType.OrElse => typeof(bool), @@ -1404,8 +1376,8 @@ ExpressionType.Equal or ExpressionType.NotEqual or ExpressionType.GreaterThan or ExpressionType.Assign => leftType, _ => leftType }; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Type GetArrayElementType(Type arrayType, int depth) { var elementType = arrayType; @@ -1414,162 +1386,6 @@ private static Type GetArrayElementType(Type arrayType, int depth) return elementType ?? typeof(object); } - private void CollectLambdaClosureParameterUsages(int lambdaIdx) - { - var children = GetChildren(lambdaIdx); - if (children.Count == 0) - return; - - SmallList, NoArrayPool> lambdaParameterIds = default; - for (var i = 1; i < children.Count; ++i) - lambdaParameterIds.Add(ToStoredUShortIdx(Nodes[children[i]].ChildIdx)); - - ChildList localParameterIds = default; - SmallList, NoArrayPool> captures = default; - CollectClosureParameterUsages(children[0], ToStoredUShortIdx(lambdaIdx), ref lambdaParameterIds, ref localParameterIds, ref captures); - - for (var i = 0; i < captures.Count; ++i) - LambdaClosureParameterUsages.Add(captures[i]); - } - - private void CollectClosureParameterUsages( - int idx, - ushort lambdaIdx, - ref SmallList, NoArrayPool> lambdaParameterIds, - ref ChildList localParameterIds, - ref SmallList, NoArrayPool> captures) - { - ref var node = ref Nodes.GetSurePresentRef(idx); - switch (node.NodeType) - { - case ExpressionType.Parameter: - { - var parameterId = ToStoredUShortIdx(node.ChildIdx); - if (!Contains(ref lambdaParameterIds, parameterId) && - !Contains(ref localParameterIds, parameterId)) - AddClosureParameterUsage(lambdaIdx, ToStoredUShortIdx(idx), parameterId, ref captures); - return; - } - case ExpressionType.Lambda: - PropagateNestedLambdaClosureParameterUsages(ToStoredUShortIdx(idx), lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); - return; - case ExpressionType.Block: - { - var children = GetChildren(idx); - var localCount = localParameterIds.Count; - var hasVariables = children.Count == 2; - if (hasVariables) - { - var variableIdxs = GetChildren(children[0]); - for (var i = 0; i < variableIdxs.Count; ++i) - localParameterIds.Add(ToStoredUShortIdx(Nodes[variableIdxs[i]].ChildIdx)); - } - - var expressionIdxs = GetChildren(children[children.Count - 1]); - for (var i = 0; i < expressionIdxs.Count; ++i) - CollectClosureParameterUsages(expressionIdxs[i], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); - - localParameterIds.Count = localCount; - return; - } - case ExpressionType.Try: - { - var children = GetChildren(idx); - CollectClosureParameterUsages(children[0], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); - - var lastChildIdx = children.Count - 1; - if (lastChildIdx > 0 && Nodes[children[lastChildIdx]].Is(ExprNodeKind.ChildList)) - { - var handlerIdxs = GetChildren(children[lastChildIdx]); - for (var i = 0; i < handlerIdxs.Count; ++i) - CollectCatchBlockClosureParameterUsages(handlerIdxs[i], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); - lastChildIdx--; - } - - for (var i = 1; i <= lastChildIdx; ++i) - CollectClosureParameterUsages(children[i], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); - return; - } - } - - if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) || node.ChildCount == 0) - return; - - var childIdxs = GetChildren(idx); - for (var i = 0; i < childIdxs.Count; ++i) - CollectClosureParameterUsages(childIdxs[i], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); - } - - private void CollectCatchBlockClosureParameterUsages( - int idx, - ushort lambdaIdx, - ref SmallList, NoArrayPool> lambdaParameterIds, - ref ChildList localParameterIds, - ref SmallList, NoArrayPool> captures) - { - ref var node = ref Nodes.GetSurePresentRef(idx); - Debug.Assert(node.Is(ExprNodeKind.CatchBlock)); - - var children = GetChildren(idx); - var localCount = localParameterIds.Count; - var childIdx = 0; - if (node.HasFlag(CatchHasVariableFlag)) - localParameterIds.Add(ToStoredUShortIdx(Nodes[children[childIdx++]].ChildIdx)); - - var bodyIdx = children[childIdx++]; - if (node.HasFlag(CatchHasFilterFlag)) - CollectClosureParameterUsages(children[childIdx], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); - CollectClosureParameterUsages(bodyIdx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); - localParameterIds.Count = localCount; - } - - private void PropagateNestedLambdaClosureParameterUsages( - ushort nestedLambdaIdx, - ushort lambdaIdx, - ref SmallList, NoArrayPool> lambdaParameterIds, - ref ChildList localParameterIds, - ref SmallList, NoArrayPool> captures) - { - for (var i = 0; i < LambdaClosureParameterUsages.Count; ++i) - { - ref var usage = ref LambdaClosureParameterUsages[i]; - if (usage.LambdaIdx != nestedLambdaIdx) - continue; - if (Contains(ref lambdaParameterIds, usage.ParameterId) || - Contains(ref localParameterIds, usage.ParameterId)) - continue; - AddClosureParameterUsage(lambdaIdx, usage.ParameterIdx, usage.ParameterId, ref captures); - } - } - - private static void AddClosureParameterUsage( - ushort lambdaIdx, - ushort parameterIdx, - ushort parameterId, - ref SmallList, NoArrayPool> captures) - { - for (var i = 0; i < captures.Count; ++i) - if (captures[i].ParameterId == parameterId) - return; - captures.Add(new LambdaClosureParameterUsage(lambdaIdx, parameterIdx, parameterId)); - } - - private ChildList GetChildren(int idx) - { - ref var node = ref Nodes.GetSurePresentRef(idx); - if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) || node.ChildCount == 0) - return default; - var count = node.ChildCount; - ChildList children = default; - var childIdx = node.ChildIdx; - for (var i = 0; i < count; ++i) - { - children.Add(childIdx); - childIdx = Nodes.GetSurePresentRef(childIdx).NextIdx; - } - return children; - } - private static bool Contains(ref SmallList ids, ushort value) where TStack : struct, IStack where TPool : struct, ISmallArrayPool @@ -1585,7 +1401,7 @@ private static bool Contains(ref SmallList private struct StructuralComparer { - private ChildList _xParameterIds, _yParameterIds; + private ChildIdxs _xParameterIds, _yParameterIds; private SmallList, NoArrayPool> _xLabelIds, _yLabelIds; private SmallList, NoArrayPool> _eqFrames; @@ -1609,7 +1425,7 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) } else if (x.NodeType == ExpressionType.Constant) { - + if (x.Type != y.Type || x.NodeType != y.NodeType || x.FlagsAndKind != y.FlagsAndKind) return false; } @@ -1700,7 +1516,7 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) { 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 || + if (xVariables.Kind != ExprNodeKind.BlockExprs || yVariables.Kind != ExprNodeKind.BlockExprs || xVariables.ChildCount != yVariables.ChildCount) return false; @@ -2007,13 +1823,13 @@ public TraversalFrame(int xNextIdx, int yNextIdx, int remainingSiblingsAfterNode } /// Reconstructs System.Linq nodes from the flat representation while reusing parameter and label identities. - private struct Reader + private struct SysExprBuilder { private readonly ExprTree _tree; private SmallMap16 _parametersById; private SmallMap16 _labelsById; - public Reader(ExprTree tree) + public SysExprBuilder(ExprTree tree) { _tree = tree; _parametersById = default; @@ -2156,11 +1972,11 @@ public SysExpr ReadExpression(int idx) { var children = GetChildren(idx); var defaultBody = default(SysExpr); - ChildList caseIdxs = default; + ChildIdxs caseIdxs = default; if (children.Count > 1) { ref var lastChild = ref _tree.Nodes[children[children.Count - 1]]; - if (lastChild.Is(ExprNodeKind.ChildList)) + if (lastChild.Is(ExprNodeKind.BlockExprs)) { caseIdxs = GetChildren(children[children.Count - 1]); if (children.Count == 3) @@ -2181,7 +1997,7 @@ public SysExpr ReadExpression(int idx) return SysExpr.TryFault(ReadExpression(children[0]), ReadExpression(children[1])); var handlers = default(SysCatchBlock[]); - var lastChildIsHandlerList = children.Count > 1 && _tree.Nodes[children[children.Count - 1]].Is(ExprNodeKind.ChildList); + var lastChildIsHandlerList = children.Count > 1 && _tree.Nodes[children[children.Count - 1]].Is(ExprNodeKind.BlockExprs); if (lastChildIsHandlerList) { var handlerIdxs = GetChildren(children[children.Count - 1]); @@ -2349,11 +2165,11 @@ private SysElementInit ReadElementInit(int idx) return SysExpr.ElementInit((MethodInfo)node.Obj, ReadExpressions(GetChildren(idx))); } - private ChildList GetChildren(int idx) + private ChildIdxs GetChildren(int idx) { ref var node = ref _tree.Nodes.GetSurePresentRef(idx); var count = node.ChildCount; - ChildList children = default; + ChildIdxs children = default; var childIdx = node.ChildIdx; for (var i = 0; i < count; ++i) { @@ -2394,7 +2210,7 @@ private static object ReadInlineValue(Type type, uint data) } [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] - private SysExpr[] ReadExpressions(in ChildList childIdxs) + private SysExpr[] ReadExpressions(in ChildIdxs childIdxs) { var expressions = new SysExpr[childIdxs.Count]; for (var i = 0; i < expressions.Length; ++i) @@ -2407,7 +2223,20 @@ private SysExpr[] ReadExpressions(in ChildList childIdxs) Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] private static NewExpression CreateValueTypeNewExpression(Type type) => SysExpr.New(type); } +} + +/// Builds the flat representation while preserving parameter and label identity. +public static class FlatExprBuilder +{ + private SmallMap16> _parameterIds; + private SmallMap16> _labelIds; + private ExprTree _tree; + public ExprTree Build(SysExpr sysExpr) + { + _tree.RootIdx = AddExpression(sysExpr); + return _tree; + } } /// Union struct for reinterpreting float bits as uint without unsafe code. @@ -2444,8 +2273,9 @@ internal static T UnsupportedInlineConstantType(object value, TypeCode tc) => public static class FlatExpressionExtensions { /// Flattens a System.Linq expression tree. - public static ExprTree ToFlatExpression(this SysExpr expression) => ExprTree.FromExpression(expression); - - /// Flattens a LightExpression tree. - public static ExprTree ToFlatExpression(this LightExpression expression) => ExprTree.FromLightExpression(expression); + public static ref ExprTree ToFlatExpression(this SysExpr expression, ref ExprTree exprTree) + { + exprTree.FromSysExpr(expression); + return ref exprTree; + } } diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index df384290..9931082b 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -33,6 +33,7 @@ public int Run() Can_build_flat_expression_directly_with_light_expression_like_api(); Can_build_flat_expression_control_flow_directly(); Can_property_test_generated_flat_expression_roundtrip_structurally(); + Flat_lambda_parameter_ref_before_decl_preserves_identity(); Flat_lambda_multiple_parameter_refs_all_yield_same_identity(); Flat_block_variables_and_refs_yield_same_identity(); @@ -58,6 +59,7 @@ public int Run() 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; } @@ -282,7 +284,6 @@ public static ExprTree CreateComplexFlatExpression(string parameterName = null) return fe; } - public void Can_compile_complex_expr_with_Arrays_and_Casts() { var expr = CreateComplexLightExpression(); @@ -394,9 +395,10 @@ public static void SayHi(int i, int j) { } public void Can_roundtrip_light_expression_through_flat_expression() { - var expr = CreateComplexLightExpression("state"); + var expr = CreateComplexExpression("state"); - var flat = expr.ToFlatExpression(); + ExprTree flat = default; + flat.FromSysExpr(expr); Asserts.IsTrue(flat.Nodes.Count > 0); Asserts.AreEqual(0, flat.ClosureConstants.Count); @@ -417,30 +419,12 @@ public void Flat_expression_preserves_parameter_and_label_identity_and_collects_ var valueHolder = new S(); var valueField = typeof(S).GetField(nameof(S.Value)); var constExpr = Lambda>(Field(Constant(valueHolder), valueField)); - var constFlat = constExpr.ToFlatExpression(); + ExprTree constFlat = default; + constFlat.FromLightExpr(constExpr); Asserts.AreEqual(1, constFlat.ClosureConstants.Count); Asserts.AreSame(valueHolder, constFlat.ClosureConstants[0]); Asserts.AreEqual(null, ((LambdaExpression)constFlat.ToLightExpression()).CompileFast>(true)()); - - var p = SysExpr.Parameter(typeof(int), "p"); - var target = SysExpr.Label(typeof(int), "done"); - var sysLambda = SysExpr.Lambda>( - SysExpr.Block( - SysExpr.Goto(target, p, typeof(int)), - SysExpr.Label(target, SysExpr.Constant(0))), - p); - - var sysRoundtrip = (System.Linq.Expressions.LambdaExpression)sysLambda - .ToFlatExpression() - .ToExpression(); - - var block = (System.Linq.Expressions.BlockExpression)sysRoundtrip.Body; - var @goto = (System.Linq.Expressions.GotoExpression)block.Expressions[0]; - var label = (System.Linq.Expressions.LabelExpression)block.Expressions[1]; - - Asserts.AreSame(sysRoundtrip.Parameters[0], @goto.Value); - Asserts.AreSame(@goto.Target, label.Target); } public void Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expression_and_flat_expression() From 14f1f5011bbb5eabe6e9954b55fa214f299c4918 Mon Sep 17 00:00:00 2001 From: dadhi Date: Wed, 12 Aug 2026 19:04:50 +0200 Subject: [PATCH 17/18] FlatExpression tests passing; ISize1Plus --- .../FlatExpression.cs | 1426 +++++++++-------- src/FastExpressionCompiler/ImTools.cs | 108 +- .../LightExpressionPropertyTests.cs | 10 +- .../LightExpressionTests.cs | 16 +- 4 files changed, 858 insertions(+), 702 deletions(-) diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index 8498fe09..c79bfea5 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -98,21 +98,16 @@ public struct ExprNode /// Gets the number of direct children linked from this node. public ushort ChildCount => (ushort)(_child >> ChildCountShift); - /// Gets the first child idx or an auxiliary payload idx. - public ushort FirstChildIdx => (ushort)(_child & FirstChildIdxMask); + /// Gets the first child idx or an auxiliary payload idx (parameter/label id, closure constant idx). + public ushort ChildIdx => (ushort)(_child & FirstChildIdxMask); - public void SetChildrenInfo(ushort childCount, ushort firstChildIdx) => _child = ((uint)childCount << ChildCountShift) | firstChildIdx; + /// Sets the child-link metadata for the node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetChildrenInfo(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 => _child; - internal ExprNode(ExpressionType nodeType, Type type, object obj = null) - { - Type = type; - Obj = obj; - _nodeType = (byte)nodeType; - } - internal ExprNode(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort childIdx = 0, ushort childCount = 0, ushort nextIdx = 0) { @@ -212,37 +207,56 @@ public struct ExprTree : IEquatable /// Gets or sets all lambda node idxs added during construction. /// The root lambda idx is stored in ; other entries are nested lambdas. - public SmallList, NoArrayPool> LambdaNodes; + public SmallList, NoArrayPool> LambdaNodes; /// Gets or sets all block node idxs that carry explicit variable declarations. /// A tracked block uses children.Count == 2: one child list for variables and one for expressions. - public SmallList, NoArrayPool> BlocksWithVariables; + public SmallList, NoArrayPool> BlocksWithVariables; /// Gets or sets all node idxs, /// including return, break, and continue. - public SmallList, NoArrayPool> GotoNodes; + public SmallList, NoArrayPool> GotoNodes; /// Gets or sets all expression node idxs. - public SmallList, NoArrayPool> LabelNodes; + public SmallList, NoArrayPool> LabelNodes; /// Gets or sets all node idxs for try/catch, try/finally, try/fault, and combined forms. - public SmallList, NoArrayPool> TryCatchNodes; + public SmallList, NoArrayPool> TryCatchNodes; /// Gets or sets captured outer parameter and variable usages for lambdas. /// The stored idxs use the same 16-bit range as and . public SmallList, NoArrayPool> LambdaClosureParameterUsages; + // Import-only identity maps (valid during FromSysExpr / FromLightExpr). + private SmallMap16> _parameterIds; + private SmallMap16> _labelIds; + + // todo: @perf how can we initialize Count to 1 to avoid the call? + /// Index 0 is reserved as the absent-child sentinel used by With* helpers. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void EnsureIndexZeroSentinel() + { + if (Nodes.Count == 0) Nodes.Count = 1; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ushort AddNode(ExpressionType nodType, Type type, object obj = null, byte flags = 0, ExprNodeKind kind = default) + private ushort AddNode(ExpressionType nodType, Type type, object obj = null, byte flags = 0, ExprNodeKind kind = default, + ushort childIdx = 0, ushort childCount = 0) { - var node = new ExprNode(nodType, type, obj, flags, kind); - return (ushort)Nodes.Add(in node); + EnsureIndexZeroSentinel(); + var node = new ExprNode(nodType, type, obj, flags, kind, childIdx, childCount); + return checked((ushort)Nodes.Add(in node)); } /// Adds a parameter node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort Parameter(Type type, string name = null) => - AddNode(ExpressionType.Parameter, type, name, type.IsByRef ? ParameterByRefFlag : (byte)0); + ParameterWithId(type, name, checked((ushort)(Nodes.Count + 1))); + + /// id is the index of the parameter declaration node set as its child index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort ParameterWithId(Type type, string name, ushort id) => + AddNode(ExpressionType.Parameter, type, name, type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: id); /// Adds a typed parameter node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -257,8 +271,31 @@ public ushort Parameter(Type type, string name = null) => public ushort Default(Type type) => AddNode(ExpressionType.Default, type); /// Adds a constant node with an explicit constant type. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort Constant(object value, Type type) => AddNode(ExpressionType.Constant, type, value); + public ushort Constant(object value, Type type) + { + if (value == null || value is string || value is Type || value is decimal) + return AddNode(ExpressionType.Constant, type, value); + + if (type.IsEnum) + { + if (!In32BitRange(Type.GetTypeCode(Enum.GetUnderlyingType(type)))) + return AddNode(ExpressionType.Constant, type, value); + EnsureIndexZeroSentinel(); + return checked((ushort)Nodes.Add(new ExprNode(type, unchecked((uint)System.Convert.ToInt64(value))))); + } + + if (type.IsPrimitive) + { + var tc = Type.GetTypeCode(type); + if (!In32BitRange(tc)) + return AddNode(ExpressionType.Constant, type, value); + EnsureIndexZeroSentinel(); + return checked((ushort)Nodes.Add(new ExprNode(type, ToInlineValue(value, tc)))); + } + + return AddNode(ExpressionType.Constant, type, ClosureConstantMarker, + childIdx: checked((ushort)ClosureConstants.Add(value))); + } /// Adds a constant node using the runtime type of the supplied value. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -270,8 +307,19 @@ public ushort Parameter(Type type, string name = null) => /// Adds an constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ushort ConstantInt(int value) => (ushort)Nodes.Add(new(typeof(int), unchecked((uint)value))); + public ushort ConstantInt(int value) + { + EnsureIndexZeroSentinel(); + return checked((ushort)Nodes.Add(new ExprNode(typeof(int), unchecked((uint)value)))); + } + /// Adds a typed constant node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort ConstantOf(T value) => Constant(value, typeof(T)); + + /// Adds a constructor-call node for the specified constructor. + /// The constructor to represent. + /// The node index of the added constructor-call node. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort New(ConstructorInfo ctor) => AddNode(ExpressionType.New, ctor.DeclaringType, ctor); @@ -289,145 +337,141 @@ public ushort New(Type type) throw new ArgumentException($"The type {type} is missing the default constructor"); } - [UnscopedRef] + /// Prepares a non-zero child for the given owner under the parent-first protocol: + /// clone when already linked ( != 0) or when the node is a + /// (each attach is a distinct usage/def slot), + /// then mark the accepted child with immediately. + /// In-progress same-parent duplicates are detected because earlier siblings already carry + /// a non-zero NextIdx from this mark. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ref ExprNode CloneIfHasNext(ref ushort idx) + private ushort MayBeCloneChildForOwner(ushort childIdx, ushort ownerIdx) { - ref var childRef = ref Nodes.GetSurePresentRef(idx); - if (idx != 0 && childRef.NextIdx != 0) - idx = (ushort)Nodes.AddCopy(childRef); - return ref childRef; + Debug.Assert(childIdx != 0); + ref var childRef = ref Nodes.GetSurePresentRef(childIdx); + if (childRef.NextIdx == 0 && childRef.NodeType != ExpressionType.Parameter) + childRef.NextIdx = ownerIdx; + else + { + // Clone the child node that is already in some child chain (NextIdx != 0) or is parameter (we always split parameter definition - original and usage - clone). + ExprNode childCopy = childRef; + childCopy.NextIdx = ownerIdx; + childIdx = checked((ushort)Nodes.Add(in childCopy)); + } + return childIdx; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ushort WithOneChild(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0) + private ushort ReserveOwner(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind) { + EnsureIndexZeroSentinel(); var owner = new ExprNode(nodeType, type, obj, flags, kind); - ref var child = ref CloneIfHasNext(ref ch0); - owner.SetChildrenInfo(ch0 != 0 ? (ushort)1 : (ushort)0, ch0); - return child.NextIdx = (ushort)Nodes.Add(in owner); // do not forget to set last arg.NextIdx to its parent index to navigate upwards + return checked((ushort)Nodes.Add(in owner)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ushort WithTwoChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1) + private void AppendPreparedChild(ushort childIdx, ref ushort firstChildIdx, ref ushort prevChildIdx, ref ushort childCount) { - var owner = new ExprNode(nodeType, type, obj, flags, kind); - ushort childCount = 0; - ref var child = ref CloneIfHasNext(ref ch0); - childCount += (ushort)(ch0 * 1); + if (childCount == 0) + firstChildIdx = childIdx; + else + Nodes.GetSurePresentRef(prevChildIdx).NextIdx = childIdx; + prevChildIdx = childIdx; + ++childCount; + } - if (ch1 != 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort WithOneChild(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0) + { + var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + ushort first = 0; + ushort count = 0; + if (ch0 != 0) { - ref var nextRef = ref CloneIfHasNext(ref ch1); - child.NextIdx = ch1; - child = ref nextRef; - - if (ch0 == 0) ch0 = ch1; - ++childCount; + first = MayBeCloneChildForOwner(ch0, ownerIdx); + count = 1; } - - owner.SetChildrenInfo(childCount, ch0); - return child.NextIdx = (ushort)Nodes.Add(in owner); + Nodes.GetSurePresentRef(ownerIdx).SetChildrenInfo(count, first); + return ownerIdx; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ushort WithThreeChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1, ushort ch2) + private ushort WithTwoChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1) { - var owner = new ExprNode(nodeType, type, obj, flags, kind); - ushort childCount = 0; - ref var child = ref CloneIfHasNext(ref ch0); - childCount += (ushort)(ch0 * 1); + var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + ushort first = 0, prev = 0, count = 0; + if (ch0 != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch0, ownerIdx), ref first, ref prev, ref count); if (ch1 != 0) - { - ref var nextRef = ref CloneIfHasNext(ref ch1); - child.NextIdx = ch1; - child = ref nextRef; + AppendPreparedChild(MayBeCloneChildForOwner(ch1, ownerIdx), ref first, ref prev, ref count); - if (ch0 == 0) ch0 = ch1; - ++childCount; - } + Nodes.GetSurePresentRef(ownerIdx).SetChildrenInfo(count, first); + return ownerIdx; + } - if (ch2 != 0) - { - ref var nextRef = ref CloneIfHasNext(ref ch2); - child.NextIdx = ch2; - child = ref nextRef; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort WithThreeChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1, ushort ch2) + { + var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + ushort first = 0, prev = 0, count = 0; - if (ch0 == 0) ch0 = ch2; - ++childCount; - } + if (ch0 != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch0, ownerIdx), ref first, ref prev, ref count); + if (ch1 != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch1, ownerIdx), ref first, ref prev, ref count); + if (ch2 != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch2, ownerIdx), ref first, ref prev, ref count); - owner.SetChildrenInfo(childCount, ch0); - return child.NextIdx = (ushort)Nodes.Add(in owner); + Nodes.GetSurePresentRef(ownerIdx).SetChildrenInfo(count, first); + return ownerIdx; } private ushort WithTwoOrMoreChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1, ushort[] more) { - var owner = new ExprNode(nodeType, type, obj, flags, kind); - ushort childCount = 0; - ref var childRef = ref CloneIfHasNext(ref ch0); - childCount += (ushort)(ch0 * 1); + var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + ushort first = 0, prev = 0, count = 0; + if (ch0 != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch0, ownerIdx), ref first, ref prev, ref count); if (ch1 != 0) - { - ref var nextRef = ref CloneIfHasNext(ref ch1); - childRef.NextIdx = ch1; - childRef = ref nextRef; - - if (ch0 == 0) ch0 = ch1; - ++childCount; - } + AppendPreparedChild(MayBeCloneChildForOwner(ch1, ownerIdx), ref first, ref prev, ref count); - ushort ch = 0; if (more != null) { - for (ushort i = 0; i < more.Length; ++i) + for (var i = 0; i < more.Length; ++i) { - ch = more.GetSurePresentRef(i); + var ch = more[i]; if (ch == 0) continue; - ref var nextRef = ref CloneIfHasNext(ref ch); - childRef.NextIdx = ch; - childRef = ref nextRef; - - if (ch0 == 0) ch0 = ch; - ++childCount; + AppendPreparedChild(MayBeCloneChildForOwner(ch, ownerIdx), ref first, ref prev, ref count); } } - owner.SetChildrenInfo(childCount, ch0); // adding 0 and 0 is ubiquitous - return childRef.NextIdx = (ushort)Nodes.Add(in owner); + Nodes.GetSurePresentRef(ownerIdx).SetChildrenInfo(count, first); + return ownerIdx; } private ushort WithOneOrMoreChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort[] more) { - var owner = new ExprNode(nodeType, type, obj, flags, kind); + var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + ushort first = 0, prev = 0, count = 0; - ushort childCount = 0; - ref var childRef = ref CloneIfHasNext(ref ch0); - childCount += (ushort)(ch0 * 1); + if (ch0 != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch0, ownerIdx), ref first, ref prev, ref count); - ushort ch = 0; if (more != null) { - for (ushort i = 0; i < more.Length; ++i) + for (var i = 0; i < more.Length; ++i) { - ch = more.GetSurePresentRef(i); + var ch = more[i]; if (ch == 0) continue; - ref var nextRef = ref CloneIfHasNext(ref ch); - childRef.NextIdx = ch; - childRef = ref nextRef; - - if (ch0 == 0) ch0 = ch; - ++childCount; + AppendPreparedChild(MayBeCloneChildForOwner(ch, ownerIdx), ref first, ref prev, ref count); } } - owner.SetChildrenInfo(childCount, ch0); // adding 0 and 0 is ubiquitous - return childRef.NextIdx = (ushort)Nodes.Add(in owner); + Nodes.GetSurePresentRef(ownerIdx).SetChildrenInfo(count, first); + return ownerIdx; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort WithChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, params ushort[] children) => WithOneOrMoreChildren(nodeType, type, obj, flags, kind, 0, children); @@ -459,6 +503,10 @@ public ushort Call(ushort instance, MethodInfo method, params ushort[] args) => public ushort MakeMemberAccess(MemberInfo member) => AddNode(ExpressionType.MemberAccess, GetMemberType(member), member); + /// Adds a member-access node for the specified member on a supplied instance target. + /// The node index representing the instance target. + /// The member to access. + /// The node index of the added member-access node. public ushort MakeMemberAccess(ushort instance, MemberInfo member) => WithOneChild(ExpressionType.MemberAccess, GetMemberType(member), member, default, default, instance); @@ -486,8 +534,8 @@ public ushort Property(ushort instance, PropertyInfo prop, params ushort[] args) /// Adds a binary node of the specified kind. public ushort MakeBinary(ExpressionType nodeType, ushort left, ushort right, bool isLiftedToNull = false, - MethodInfo method = null, ushort conversion = 0, Type type = null) - => WithThreeChildren( + MethodInfo method = null, ushort conversion = 0, Type type = null) => + WithThreeChildren( nodeType, type ?? GetBinaryResultType(nodeType, Nodes[left].Type, method), method, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, default, left, right, conversion); @@ -543,7 +591,8 @@ public ushort MakeUnary(ExpressionType nodeType, ushort operand, Type type = nul public ushort Condition(ushort test, ushort ifTrue, ushort ifFalse, Type type = null) => WithThreeChildren(ExpressionType.Conditional, type ?? Nodes[ifTrue].Type, null, default, default, test, ifTrue, ifFalse); - /// BlockNode.ChildIdx -> ExprsNode; ExprsNode.NextIdx -> VarsNode|BlockNode + /// Block layout: first child = BlockExprs sub-node (expression list); + /// optional following siblings = variable declarations. Tracked in when vars present. public ushort Block(Type type, ushort[] vars, params ushort[] exprs) { if (exprs == null || exprs.Length == 0) @@ -551,8 +600,11 @@ public ushort Block(Type type, ushort[] vars, params ushort[] exprs) var exprsSubNode = WithChildren(ExpressionType.Block, null, null, default, ExprNodeKind.BlockExprs, exprs); - type ??= Nodes[exprs[^1]].Type; - return WithOneOrMoreChildren(ExpressionType.Block, type, null, default, default, exprsSubNode, vars); + type ??= Nodes[exprs[exprs.Length - 1]].Type; + var blockIdx = WithOneOrMoreChildren(ExpressionType.Block, type, null, default, default, exprsSubNode, vars); + if (vars != null && vars.Length != 0) + BlocksWithVariables.Add(blockIdx); + return blockIdx; } /// Adds a block node without explicit variables. @@ -560,13 +612,18 @@ public ushort Block(Type type, ushort[] vars, params ushort[] exprs) public ushort Block(params ushort[] exprs) => Block(null, null, exprs); - /// Adds a lambda node. - public ushort Lambda(Type delegateType, ushort bodyIdx, params ushort[] pars) => - WithOneOrMoreChildren(ExpressionType.Lambda, delegateType, null, default, default, bodyIdx, pars); + /// Adds a lambda node. Layout: body then parameters. Tracks and captures. + public ushort Lambda(Type delegateType, ushort bodyIdx, params ushort[] pars) + { + var idx = WithOneOrMoreChildren(ExpressionType.Lambda, delegateType, null, default, default, bodyIdx, pars); + LambdaNodes.Add(idx); + CollectLambdaClosureParameterUsages(idx); + return idx; + } /// Adds a typed lambda node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Lambda(ushort bodyIdx, params ushort[] parameters) where TDelegate : Delegate => + public ushort Lambda(ushort bodyIdx, params ushort[] parameters) where TDelegate : Delegate => Lambda(typeof(TDelegate), bodyIdx, parameters); /// Adds a member-assignment binding node. @@ -593,19 +650,31 @@ public ushort MemberInit(ushort expr, params ushort[] bindings) => public ushort ListInit(ushort @new, params ushort[] initializers) => WithOneOrMoreChildren(ExpressionType.ListInit, Nodes[@new].Type, null, default, default, @new, initializers); - /// Adds a label-target node. + /// Adds a label-target node with a stable identity in . public ushort Label(Type type = null, string name = null) => - WithOneChild(default, type ?? typeof(void), name, default, ExprNodeKind.LabelTarget, 0); + LabelTargetWithId(type ?? typeof(void), name, checked((ushort)(Nodes.Count + 1))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort LabelTargetWithId(Type type, string name, ushort id) => + AddNode(ExpressionType.Extension, type, name, 0, ExprNodeKind.LabelTarget, childIdx: id); /// Adds a label-expression node. - public ushort Label(ushort target, ushort defaultValue = 0) => - WithOneChild(ExpressionType.Label, Nodes[target].Type, null, default, default, target); + public ushort Label(ushort target, ushort defaultValue = 0) + { + var idx = defaultValue == 0 + ? WithOneChild(ExpressionType.Label, Nodes[target].Type, null, default, default, target) + : WithTwoChildren(ExpressionType.Label, Nodes[target].Type, null, default, default, target, defaultValue); + LabelNodes.Add(idx); + return idx; + } - /// Adds a goto-family node. + /// Adds a goto-family node. Kind is stored in flags. public ushort MakeGoto(GotoExpressionKind gotoKind, ushort target, ushort value = 0, Type type = null) { var resultType = type ?? (value != 0 ? Nodes[value].Type : typeof(void)); - return WithTwoChildren(ExpressionType.Goto, resultType, null, (byte)gotoKind, default, target, value); + var idx = WithTwoChildren(ExpressionType.Goto, resultType, null, (byte)gotoKind, default, target, value); + GotoNodes.Add(idx); + return idx; } /// Adds a goto node. @@ -617,147 +686,158 @@ public ushort MakeGoto(GotoExpressionKind gotoKind, ushort target, ushort value public ushort Return(ushort target, ushort value) => MakeGoto(GotoExpressionKind.Return, target, value, Nodes[value].Type); /// Adds a loop node. - public ushort Loop(ushort body, ushort @break = 0, ushort @continue = 0) => - WithThreeChildren(ExpressionType.Loop, typeof(void), null, default, default, body, @break, @continue); + public ushort Loop(ushort body, ushort @break = 0, ushort @continue = 0) + { + byte flags = 0; + if (@break != 0) flags |= LoopHasBreakFlag; + if (@continue != 0) flags |= LoopHasContinueFlag; + return WithThreeChildren(ExpressionType.Loop, typeof(void), null, flags, default, body, @break, @continue); + } - /// Adds a switch-case node. + /// Adds a switch-case node. Layout: test values then body. public ushort SwitchCase(ushort body, params ushort[] testValues) => - WithOneOrMoreChildren(ExpressionType.Switch, null, null, default, ExprNodeKind.SwitchCase, body, testValues); + WithOneOrMoreChildren(default, null, null, default, ExprNodeKind.SwitchCase, 0, + AppendUShort(testValues, body)); - /// Adds a switch node. + /// Adds a switch node. Layout: switchValue, optional defaultBody, optional SwitchCases group. public ushort Switch(Type type, ushort switchValue, ushort defaultBody, MethodInfo comparison, params ushort[] cases) { var casesIdx = cases == null || cases.Length == 0 ? (ushort)0 : WithChildren(ExpressionType.Switch, type, null, default, ExprNodeKind.SwitchCases, cases); - return WithThreeChildren(ExpressionType.Switch, type, comparison, default, default, casesIdx, switchValue, defaultBody); + return WithThreeChildren(ExpressionType.Switch, type, comparison, default, default, switchValue, defaultBody, casesIdx); } /// Adds a switch node without an explicit default case or comparer. - public ushort Switch(ushort switchValue, params ushort[] cases) => - WithChildren(ExpressionType.Switch, Nodes[switchValue].Type, null, default, default, cases); + public ushort Switch(ushort switchValue, params ushort[] cases) + { + var type = Nodes[switchValue].Type; + var casesIdx = cases == null || cases.Length == 0 + ? (ushort)0 + : WithChildren(ExpressionType.Switch, type, null, default, ExprNodeKind.SwitchCases, cases); + return casesIdx == 0 + ? WithOneChild(ExpressionType.Switch, type, null, default, default, switchValue) + : WithTwoChildren(ExpressionType.Switch, type, null, default, default, switchValue, casesIdx); + } - /// Adds a catch block with an exception variable. + /// Adds a catch block with an exception variable. Layout: variable, body [, filter]. public ushort Catch(ushort variable, ushort body) => - WithTwoChildren(default, Nodes[variable].Type, null, default, ExprNodeKind.CatchBlock, body, variable); + WithTwoChildren(default, Nodes[variable].Type, null, CatchHasVariableFlag, ExprNodeKind.CatchBlock, variable, body); /// Adds a catch block without an exception variable. public ushort Catch(Type test, ushort body) => WithOneChild(default, test, null, default, ExprNodeKind.CatchBlock, body); - /// Adds a catch block with optional exception variable and filter. - public ushort MakeCatchBlock(Type test, ushort variable, ushort body, ushort filter = 0) => - WithThreeChildren(default, test, null, default, ExprNodeKind.CatchBlock, body, variable, filter); + /// Adds a catch block with optional exception variable and filter. Layout: [variable,] body [, filter]. + public ushort MakeCatchBlock(Type test, ushort variable, ushort body, ushort filter = 0) + { + byte flags = 0; + if (variable != 0) flags |= CatchHasVariableFlag; + if (filter != 0) flags |= CatchHasFilterFlag; + if (variable != 0) + return filter != 0 + ? WithThreeChildren(default, test, null, flags, ExprNodeKind.CatchBlock, variable, body, filter) + : WithTwoChildren(default, test, null, flags, ExprNodeKind.CatchBlock, variable, body); + return filter != 0 + ? WithTwoChildren(default, test, null, flags, ExprNodeKind.CatchBlock, body, filter) + : WithOneChild(default, test, null, flags, ExprNodeKind.CatchBlock, body); + } - /// Adds a try/catch node. - public ushort TryCatch(ushort body, params ushort[] handlers) => - WithOneOrMoreChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, handlers); + /// Adds a try/catch node. Layout: body, handlers… + public ushort TryCatch(ushort body, params ushort[] handlers) + { + var idx = WithOneOrMoreChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, handlers); + TryCatchNodes.Add(idx); + return idx; + } /// Adds a try/finally node. - public ushort TryFinally(ushort body, ushort @finally) => - WithTwoChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, @finally); + public ushort TryFinally(ushort body, ushort @finally) + { + var idx = WithTwoChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, @finally); + TryCatchNodes.Add(idx); + return idx; + } /// Adds a try/fault node. - public ushort TryFault(ushort body, ushort fault) => - WithTwoChildren(ExpressionType.Try, Nodes[body].Type, null, TryFaultFlag, default, body, fault); + public ushort TryFault(ushort body, ushort fault) + { + var idx = WithTwoChildren(ExpressionType.Try, Nodes[body].Type, null, TryFaultFlag, default, body, fault); + TryCatchNodes.Add(idx); + return idx; + } - /// Adds a try node with optional finally block and catch handlers. - public ushort TryCatchFinally(ushort body, ushort @finally, params ushort[] handlers) => - WithTwoOrMoreChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, @finally, handlers); + /// Adds a try node with optional finally block and catch handlers. Layout: body, finally, handlers… + public ushort TryCatchFinally(ushort body, ushort @finally, params ushort[] handlers) + { + var idx = WithTwoOrMoreChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, @finally, handlers); + TryCatchNodes.Add(idx); + return idx; + } /// Adds a type-test node. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort TypeIs(ushort expr, Type type) => - WithOneChild(ExpressionType.TypeIs, typeof(bool), null, default, default, expr); + WithOneChild(ExpressionType.TypeIs, typeof(bool), type, default, default, expr); /// Adds a type-equality test node. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort TypeEqual(ushort expr, Type type) => WithOneChild(ExpressionType.TypeEqual, typeof(bool), type, default, default, expr); - /// Adds a dynamic-expression node. - public ushort Dynamic(Type delegateType, CallSiteBinder binder, params ushort[] args) => - WithChildren(ExpressionType.Dynamic, typeof(object), binder, default, default, args); + /// Adds a dynamic-expression node. Delegate type stored as ObjectReference first child. + public ushort Dynamic(Type delegateType, CallSiteBinder binder, params ushort[] args) + { + var delRef = AddNode(ExpressionType.Extension, typeof(Type), delegateType, 0, ExprNodeKind.ObjectReference); + return WithOneOrMoreChildren(ExpressionType.Dynamic, typeof(object), binder, default, default, delRef, args); + } /// Adds a runtime-variables node. public ushort RuntimeVariables(params ushort[] vars) => WithChildren(ExpressionType.RuntimeVariables, typeof(IRuntimeVariables), null, default, default, vars); - /// Adds a debug-info node. - public ushort DebugInfo(string fileName, ushort startLine, ushort startColumn, ushort endLine, ushort endColumn) => - WithChildren(ExpressionType.DebugInfo, typeof(void), fileName, default, default, startLine, startColumn, endLine, endColumn); - - private int AddSwitchCase(SysSwitchCase switchCase) + /// Adds a debug-info node. Line/column pairs stored as UInt16Pair children. + public ushort DebugInfo(string fileName, int startLine, int startColumn, int endLine, int endColumn) { - ChildIdxs children = default; - for (var i = 0; i < switchCase.TestValues.Count; ++i) - children.Add(AddExpression(switchCase.TestValues[i])); - children.Add(AddExpression(switchCase.Body)); - return _tree.AddRawAuxNode(switchCase.Body.Type, null, ExprNodeKind.SwitchCase, children); + var start = AddNode(ExpressionType.Extension, null, null, 0, ExprNodeKind.UInt16Pair, + childIdx: checked((ushort)startLine), childCount: checked((ushort)startColumn)); + var end = AddNode(ExpressionType.Extension, null, null, 0, ExprNodeKind.UInt16Pair, + childIdx: checked((ushort)endLine), childCount: checked((ushort)endColumn)); + return WithTwoChildren(ExpressionType.DebugInfo, typeof(void), fileName, default, default, start, end); } - private int AddCatchBlock(SysCatchBlock catchBlock) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ushort[] AppendUShort(ushort[] prefix, ushort last) { - ChildIdxs children = default; - if (catchBlock.Variable != null) - children.Add(AddExpression(catchBlock.Variable)); - children.Add(AddExpression(catchBlock.Body)); - if (catchBlock.Filter != null) - children.Add(AddExpression(catchBlock.Filter)); - return _tree.AddNode(catchBlock.Test, null, ExpressionType.Extension, ExprNodeKind.CatchBlock, - (byte)((catchBlock.Variable != null ? CatchHasVariableFlag : 0) | (catchBlock.Filter != null ? CatchHasFilterFlag : 0)), in children); + if (prefix == null || prefix.Length == 0) + return new[] { last }; + var result = new ushort[prefix.Length + 1]; + Array.Copy(prefix, result, prefix.Length); + result[prefix.Length] = last; + return result; } - private int AddLabelTarget(SysLabelTarget target) => - _tree.AddRawLeafAuxNode(target.Type, target.Name, ExprNodeKind.LabelTarget, childIdx: GetId(ref _labelIds, target)); - - private int AddMemberBinding(SysMemberBinding binding) - { - switch (binding.BindingType) - { - case MemberBindingType.Assignment: - ChildIdxs assignmentChildren = default; - assignmentChildren.Add(AddExpression(((MemberAssignment)binding).Expression)); - return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, - assignmentChildren); - case MemberBindingType.MemberBinding: - { - var memberBinding = (MemberMemberBinding)binding; - ChildIdxs children = default; - for (var i = 0; i < memberBinding.Bindings.Count; ++i) - children.Add(AddMemberBinding(memberBinding.Bindings[i])); - return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberMemberBinding, children); - } - case MemberBindingType.ListBinding: - { - var listBinding = (MemberListBinding)binding; - ChildIdxs children = default; - for (var i = 0; i < listBinding.Initializers.Count; ++i) - children.Add(AddElementInit(listBinding.Initializers[i])); - return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberListBinding, children); - } - default: - throw new NotSupportedException($"Flattening of member binding `{binding.BindingType}` is not supported yet."); - } - } - - private int AddElementInit(SysElementInit init) + /// Flattens a System.Linq expression into this tree and sets . + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public ushort FromSysExpr(SysExpr expr) { - ChildIdxs children = default; - for (var i = 0; i < init.Arguments.Count; ++i) - children.Add(AddExpression(init.Arguments[i])); - return _tree.AddRawAuxNode(init.AddMethod.DeclaringType, init.AddMethod, ExprNodeKind.ElementInit, children); + _parameterIds = default; + _labelIds = default; + RootIdx = AddSysExpression(expr); + return (ushort)RootIdx; } - private static int GetId(ref SmallMap16> ids, object item) + // @perf remove Light -> System -> Flat round trip => make it Light -> Flat. + /// Flattens a LightExpression into this tree and sets . + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public ushort FromLightExpr(FastExpressionCompiler.LightExpression.Expression expr) { - ref var id = ref ids.Map.AddOrGetValueRef(item, out var found); - if (!found) - id = ids.Map.Count; - return id; + // Prefer Sys round-trip for a single From core; Light→Sys preserves identity via ToExpression. + return FromSysExpr(expr.ToExpression()); } - public ushort FromSysExpr(SysExpr expr) + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private ushort AddSysExpression(SysExpr expr) { switch (expr.NodeType) { @@ -770,529 +850,491 @@ public ushort FromSysExpr(SysExpr expr) case ExpressionType.Parameter: { var parameter = (SysParameterExpression)expr; - return _tree.AddLeafNode(expr.Type, parameter.Name, expr.NodeType, - flags: parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); + var id = checked((ushort)GetId(ref _parameterIds, parameter)); + return ParameterWithId(parameter.IsByRef ? parameter.Type : expr.Type, parameter.Name, id); } case ExpressionType.Lambda: { - // Layout: children[0] = body, children[1..n] = parameter decl nodes. - // 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 = (LambdaExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(lambda.Body)); - for (var i = 0; i < lambda.Parameters.Count; ++i) - children.Add(AddExpression(lambda.Parameters[i])); - var lambdaIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, in children); - _tree.LambdaNodes.Add(lambdaIdx); - _tree.CollectLambdaClosureParameterUsages(lambdaIdx); - return lambdaIdx; + var pars = new ushort[lambda.Parameters.Count]; + for (var i = 0; i < pars.Length; ++i) + pars[i] = AddSysExpression(lambda.Parameters[i]); + return Lambda(expr.Type, AddSysExpression(lambda.Body), pars); } case ExpressionType.Block: { - // 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 = (BlockExpression)expr; - ChildIdxs children = default; - var hasVariables = block.Variables.Count != 0; - if (hasVariables) + ushort[] vars = null; + if (block.Variables.Count != 0) { - ChildIdxs variables = default; - for (var i = 0; i < block.Variables.Count; ++i) - variables.Add(AddExpression(block.Variables[i])); - children.Add(_tree.AddChildListNode(in variables)); + vars = new ushort[block.Variables.Count]; + for (var i = 0; i < vars.Length; ++i) + vars[i] = AddSysExpression(block.Variables[i]); } - ChildIdxs expressions = default; - for (var i = 0; i < block.Expressions.Count; ++i) - expressions.Add(AddExpression(block.Expressions[i])); - children.Add(_tree.AddChildListNode(in expressions)); - var blockIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, in children); - if (hasVariables) - _tree.BlocksWithVariables.Add(blockIdx); - return blockIdx; + var exprs = new ushort[block.Expressions.Count]; + for (var i = 0; i < exprs.Length; ++i) + exprs[i] = AddSysExpression(block.Expressions[i]); + return Block(expr.Type, vars, exprs); } case ExpressionType.MemberAccess: { var member = (MemberExpression)expr; - ChildIdxs children = default; - if (member.Expression != null) - children.Add(AddExpression(member.Expression)); - return _tree.AddRawExpressionNode(expr.Type, member.Member, expr.NodeType, - children); + return member.Expression != null + ? MakeMemberAccess(AddSysExpression(member.Expression), member.Member) + : MakeMemberAccess(member.Member); } case ExpressionType.Call: { var call = (MethodCallExpression)expr; - ChildIdxs children = default; - if (call.Object != null) - children.Add(AddExpression(call.Object)); - for (var i = 0; i < call.Arguments.Count; ++i) - children.Add(AddExpression(call.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, call.Method, expr.NodeType, children); + var args = new ushort[call.Arguments.Count]; + for (var i = 0; i < args.Length; ++i) + args[i] = AddSysExpression(call.Arguments[i]); + return call.Object != null + ? Call(AddSysExpression(call.Object), call.Method, args) + : Call(call.Method, args); } case ExpressionType.New: { var @new = (NewExpression)expr; - ChildIdxs children = default; - for (var i = 0; i < @new.Arguments.Count; ++i) - children.Add(AddExpression(@new.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, @new.Constructor, expr.NodeType, children); + if (@new.Constructor == null) + return New(expr.Type); + if (@new.Arguments.Count == 0) + return New(@new.Constructor); + var args = new ushort[@new.Arguments.Count]; + for (var i = 0; i < args.Length; ++i) + args[i] = AddSysExpression(@new.Arguments[i]); + return New(@new.Constructor, args); } case ExpressionType.NewArrayInit: case ExpressionType.NewArrayBounds: { var array = (NewArrayExpression)expr; - ChildIdxs children = default; - for (var i = 0; i < array.Expressions.Count; ++i) - children.Add(AddExpression(array.Expressions[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + var items = new ushort[array.Expressions.Count]; + for (var i = 0; i < items.Length; ++i) + items[i] = AddSysExpression(array.Expressions[i]); + return expr.NodeType == ExpressionType.NewArrayInit + ? NewArrayInit(expr.Type.GetElementType(), items) + : NewArrayBounds(expr.Type.GetElementType(), items); } case ExpressionType.Invoke: { var invoke = (InvocationExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(invoke.Expression)); - for (var i = 0; i < invoke.Arguments.Count; ++i) - children.Add(AddExpression(invoke.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + var args = new ushort[invoke.Arguments.Count]; + for (var i = 0; i < args.Length; ++i) + args[i] = AddSysExpression(invoke.Arguments[i]); + return Invoke(AddSysExpression(invoke.Expression), args); } case ExpressionType.Index: { var indexExpr = (IndexExpression)expr; - ChildIdxs children = default; - if (indexExpr.Object != null) - children.Add(AddExpression(indexExpr.Object)); - for (var i = 0; i < indexExpr.Arguments.Count; ++i) - children.Add(AddExpression(indexExpr.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, indexExpr.Indexer, expr.NodeType, children); + var args = new ushort[indexExpr.Arguments.Count]; + for (var i = 0; i < args.Length; ++i) + args[i] = AddSysExpression(indexExpr.Arguments[i]); + var instance = indexExpr.Object != null ? AddSysExpression(indexExpr.Object) : (ushort)0; + return indexExpr.Indexer != null + ? Property(instance, indexExpr.Indexer, args) + : ArrayAccess(instance, args); } case ExpressionType.Conditional: { var conditional = (ConditionalExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(conditional.Test)); - children.Add(AddExpression(conditional.IfTrue)); - children.Add(AddExpression(conditional.IfFalse)); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children[0], children[1], children[2]); + return Condition( + AddSysExpression(conditional.Test), + AddSysExpression(conditional.IfTrue), + AddSysExpression(conditional.IfFalse), + expr.Type); } case ExpressionType.Loop: { var loop = (LoopExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(loop.Body)); - if (loop.BreakLabel != null) - children.Add(AddLabelTarget(loop.BreakLabel)); - if (loop.ContinueLabel != null) - children.Add(AddLabelTarget(loop.ContinueLabel)); - return _tree.AddNode(expr.Type, null, expr.NodeType, ExprNodeKind.Expression, - (byte)((loop.BreakLabel != null ? LoopHasBreakFlag : 0) | (loop.ContinueLabel != null ? LoopHasContinueFlag : 0)), in children); + return Loop( + AddSysExpression(loop.Body), + loop.BreakLabel != null ? AddSysLabelTarget(loop.BreakLabel) : (ushort)0, + loop.ContinueLabel != null ? AddSysLabelTarget(loop.ContinueLabel) : (ushort)0); } case ExpressionType.Goto: { var @goto = (GotoExpression)expr; - ChildIdxs children = default; - children.Add(AddLabelTarget(@goto.Target)); - if (@goto.Value != null) - children.Add(AddExpression(@goto.Value)); - var gotoIdx = _tree.AddRawExpressionNode(expr.Type, @goto.Kind, expr.NodeType, children); - _tree.GotoNodes.Add(gotoIdx); - return gotoIdx; + return MakeGoto(@goto.Kind, AddSysLabelTarget(@goto.Target), + @goto.Value != null ? AddSysExpression(@goto.Value) : (ushort)0, expr.Type); } case ExpressionType.Label: { var label = (LabelExpression)expr; - ChildIdxs children = default; - children.Add(AddLabelTarget(label.Target)); - if (label.DefaultValue != null) - children.Add(AddExpression(label.DefaultValue)); - var labelIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); - _tree.LabelNodes.Add(labelIdx); - return labelIdx; + return Label(AddSysLabelTarget(label.Target), + label.DefaultValue != null ? AddSysExpression(label.DefaultValue) : (ushort)0); } case ExpressionType.Switch: { var @switch = (SwitchExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(@switch.SwitchValue)); - if (@switch.DefaultBody != null) - children.Add(AddExpression(@switch.DefaultBody)); - if (@switch.Cases.Count != 0) + var cases = new ushort[@switch.Cases.Count]; + for (var i = 0; i < cases.Length; ++i) { - ChildIdxs cases = default; - for (var i = 0; i < @switch.Cases.Count; ++i) - cases.Add(AddSwitchCase(@switch.Cases[i])); - children.Add(_tree.AddChildListNode(in cases)); + var sc = @switch.Cases[i]; + var tests = new ushort[sc.TestValues.Count]; + for (var t = 0; t < tests.Length; ++t) + tests[t] = AddSysExpression(sc.TestValues[t]); + cases[i] = SwitchCase(AddSysExpression(sc.Body), tests); } - return _tree.AddRawExpressionNode(expr.Type, @switch.Comparison, expr.NodeType, in children); + return Switch(expr.Type, AddSysExpression(@switch.SwitchValue), + @switch.DefaultBody != null ? AddSysExpression(@switch.DefaultBody) : (ushort)0, + @switch.Comparison, cases); } case ExpressionType.Try: { var @try = (TryExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(@try.Body)); - var flags = (byte)0; if (@try.Fault != null) + return TryFault(AddSysExpression(@try.Body), AddSysExpression(@try.Fault)); + + var handlers = new ushort[@try.Handlers.Count]; + for (var i = 0; i < handlers.Length; ++i) { - flags = TryFaultFlag; - children.Add(AddExpression(@try.Fault)); - } - else if (@try.Finally != null) - children.Add(AddExpression(@try.Finally)); - if (@try.Handlers.Count != 0) - { - ChildIdxs handlers = default; - for (var i = 0; i < @try.Handlers.Count; ++i) - handlers.Add(AddCatchBlock(@try.Handlers[i])); - children.Add(_tree.AddChildListNode(in handlers)); + var h = @try.Handlers[i]; + var variable = h.Variable != null ? AddSysExpression(h.Variable) : (ushort)0; + var filter = h.Filter != null ? AddSysExpression(h.Filter) : (ushort)0; + handlers[i] = MakeCatchBlock(h.Test, variable, AddSysExpression(h.Body), filter); } - var tryIdx = _tree.AddNode(expr.Type, null, expr.NodeType, ExprNodeKind.Expression, flags, in children); - _tree.TryCatchNodes.Add(tryIdx); - return tryIdx; + + if (@try.Finally != null) + return handlers.Length != 0 + ? TryCatchFinally(AddSysExpression(@try.Body), AddSysExpression(@try.Finally), handlers) + : TryFinally(AddSysExpression(@try.Body), AddSysExpression(@try.Finally)); + + return TryCatch(AddSysExpression(@try.Body), handlers); } case ExpressionType.MemberInit: { var memberInit = (MemberInitExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(memberInit.NewExpression)); - for (var i = 0; i < memberInit.Bindings.Count; ++i) - children.Add(AddMemberBinding(memberInit.Bindings[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + var bindings = new ushort[memberInit.Bindings.Count]; + for (var i = 0; i < bindings.Length; ++i) + bindings[i] = AddSysMemberBinding(memberInit.Bindings[i]); + return MemberInit(AddSysExpression(memberInit.NewExpression), bindings); } case ExpressionType.ListInit: { var listInit = (ListInitExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(listInit.NewExpression)); - for (var i = 0; i < listInit.Initializers.Count; ++i) - children.Add(AddElementInit(listInit.Initializers[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + var inits = new ushort[listInit.Initializers.Count]; + for (var i = 0; i < inits.Length; ++i) + inits[i] = AddSysElementInit(listInit.Initializers[i]); + return ListInit(AddSysExpression(listInit.NewExpression), inits); } case ExpressionType.TypeIs: + { + var typeBinary = (TypeBinaryExpression)expr; + return TypeIs(AddSysExpression(typeBinary.Expression), typeBinary.TypeOperand); + } case ExpressionType.TypeEqual: { var typeBinary = (TypeBinaryExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(typeBinary.Expression)); - return _tree.AddRawExpressionNode(expr.Type, typeBinary.TypeOperand, expr.NodeType, - children); + return TypeEqual(AddSysExpression(typeBinary.Expression), typeBinary.TypeOperand); } case ExpressionType.Dynamic: { var dynamic = (DynamicExpression)expr; - ChildIdxs children = default; - children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); - for (var i = 0; i < dynamic.Arguments.Count; ++i) - children.Add(AddExpression(dynamic.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, dynamic.Binder, expr.NodeType, children); + var args = new ushort[dynamic.Arguments.Count]; + for (var i = 0; i < args.Length; ++i) + args[i] = AddSysExpression(dynamic.Arguments[i]); + return Dynamic(dynamic.DelegateType, dynamic.Binder, args); } case ExpressionType.RuntimeVariables: { var runtime = (RuntimeVariablesExpression)expr; - ChildIdxs children = default; - for (var i = 0; i < runtime.Variables.Count; ++i) - children.Add(AddExpression(runtime.Variables[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); + var vars = new ushort[runtime.Variables.Count]; + for (var i = 0; i < vars.Length; ++i) + vars[i] = AddSysExpression(runtime.Variables[i]); + return RuntimeVariables(vars); } case ExpressionType.DebugInfo: { var debug = (DebugInfoExpression)expr; - return _tree.AddFactoryExpressionNode(expr.Type, debug.Document.FileName, expr.NodeType, - _tree.CreateDebugInfoChildren(debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn)); + return DebugInfo(debug.Document.FileName, debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn); } default: if (expr is UnaryExpression unary) - { - ChildIdxs children = default; - children.Add(AddExpression(unary.Operand)); - return _tree.AddRawExpressionNode(expr.Type, unary.Method, expr.NodeType, - children); - } + return MakeUnary(expr.NodeType, AddSysExpression(unary.Operand), expr.Type, unary.Method); if (expr is BinaryExpression binary) - { - ChildIdxs children = default; - children.Add(AddExpression(binary.Left)); - children.Add(AddExpression(binary.Right)); - if (binary.Conversion != null) - children.Add(AddExpression(binary.Conversion)); - return _tree.AddNode(expr.Type, binary.Method, expr.NodeType, ExprNodeKind.Expression, - binary.IsLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, in children); - } + return MakeBinary(expr.NodeType, + AddSysExpression(binary.Left), + AddSysExpression(binary.Right), + binary.IsLiftedToNull, + binary.Method, + binary.Conversion != null ? AddSysExpression(binary.Conversion) : (ushort)0, + expr.Type); throw new NotSupportedException($"Flattening of `ExpressionType.{expr.NodeType}` is not supported yet."); } } - public ushort FromLightExpr(LightExpression.Expression expr) + private ushort AddSysLabelTarget(SysLabelTarget target) { - switch (expr.NodeType) + var id = checked((ushort)GetId(ref _labelIds, target)); + // Reuse existing label-target node with the same id when the same SysLabelTarget is seen again. + for (var i = 0; i < Nodes.Count; ++i) { - case ExpressionType.Constant: - return Constant(((LightExpression.ConstantExpression)expr).Value, expr.Type); - - case ExpressionType.Default: - return _tree.AddLeafNode(expr.Type, null, expr.NodeType); + ref var n = ref Nodes.GetSurePresentRef(i); + if (n.Is(ExprNodeKind.LabelTarget) && n.ChildIdx == id) + return checked((ushort)i); + } + return LabelTargetWithId(target.Type, target.Name, id); + } - case ExpressionType.Parameter: - { - var parameter = (SysParameterExpression)expr; - return _tree.AddLeafNode(expr.Type, parameter.Name, expr.NodeType, - flags: parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); - } - case ExpressionType.Lambda: - { - // Layout: children[0] = body, children[1..n] = parameter decl nodes. - // 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 = (LambdaExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(lambda.Body)); - for (var i = 0; i < lambda.Parameters.Count; ++i) - children.Add(AddExpression(lambda.Parameters[i])); - var lambdaIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, in children); - _tree.LambdaNodes.Add(lambdaIdx); - _tree.CollectLambdaClosureParameterUsages(lambdaIdx); - return lambdaIdx; - } - case ExpressionType.Block: - { - // 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 = (BlockExpression)expr; - ChildIdxs children = default; - var hasVariables = block.Variables.Count != 0; - if (hasVariables) - { - ChildIdxs variables = default; - for (var i = 0; i < block.Variables.Count; ++i) - variables.Add(AddExpression(block.Variables[i])); - children.Add(_tree.AddChildListNode(in variables)); - } - ChildIdxs expressions = default; - for (var i = 0; i < block.Expressions.Count; ++i) - expressions.Add(AddExpression(block.Expressions[i])); - children.Add(_tree.AddChildListNode(in expressions)); - var blockIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, in children); - if (hasVariables) - _tree.BlocksWithVariables.Add(blockIdx); - return blockIdx; - } - case ExpressionType.MemberAccess: - { - var member = (MemberExpression)expr; - ChildIdxs children = default; - if (member.Expression != null) - children.Add(AddExpression(member.Expression)); - return _tree.AddRawExpressionNode(expr.Type, member.Member, expr.NodeType, - children); - } - case ExpressionType.Call: - { - var call = (MethodCallExpression)expr; - ChildIdxs children = default; - if (call.Object != null) - children.Add(AddExpression(call.Object)); - for (var i = 0; i < call.Arguments.Count; ++i) - children.Add(AddExpression(call.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, call.Method, expr.NodeType, children); - } - case ExpressionType.New: - { - var @new = (NewExpression)expr; - ChildIdxs children = default; - for (var i = 0; i < @new.Arguments.Count; ++i) - children.Add(AddExpression(@new.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, @new.Constructor, expr.NodeType, children); - } - case ExpressionType.NewArrayInit: - case ExpressionType.NewArrayBounds: - { - var array = (NewArrayExpression)expr; - ChildIdxs children = default; - for (var i = 0; i < array.Expressions.Count; ++i) - children.Add(AddExpression(array.Expressions[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); - } - case ExpressionType.Invoke: - { - var invoke = (InvocationExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(invoke.Expression)); - for (var i = 0; i < invoke.Arguments.Count; ++i) - children.Add(AddExpression(invoke.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); - } - case ExpressionType.Index: - { - var indexExpr = (IndexExpression)expr; - ChildIdxs children = default; - if (indexExpr.Object != null) - children.Add(AddExpression(indexExpr.Object)); - for (var i = 0; i < indexExpr.Arguments.Count; ++i) - children.Add(AddExpression(indexExpr.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, indexExpr.Indexer, expr.NodeType, children); - } - case ExpressionType.Conditional: - { - var conditional = (ConditionalExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(conditional.Test)); - children.Add(AddExpression(conditional.IfTrue)); - children.Add(AddExpression(conditional.IfFalse)); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children[0], children[1], children[2]); - } - case ExpressionType.Loop: + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private ushort AddSysMemberBinding(SysMemberBinding binding) + { + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + return Bind(binding.Member, AddSysExpression(((MemberAssignment)binding).Expression)); + case MemberBindingType.MemberBinding: { - var loop = (LoopExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(loop.Body)); - if (loop.BreakLabel != null) - children.Add(AddLabelTarget(loop.BreakLabel)); - if (loop.ContinueLabel != null) - children.Add(AddLabelTarget(loop.ContinueLabel)); - return _tree.AddNode(expr.Type, null, expr.NodeType, ExprNodeKind.Expression, - (byte)((loop.BreakLabel != null ? LoopHasBreakFlag : 0) | (loop.ContinueLabel != null ? LoopHasContinueFlag : 0)), in children); + var memberBinding = (MemberMemberBinding)binding; + var bindings = new ushort[memberBinding.Bindings.Count]; + for (var i = 0; i < bindings.Length; ++i) + bindings[i] = AddSysMemberBinding(memberBinding.Bindings[i]); + return MemberBind(binding.Member, bindings); } - case ExpressionType.Goto: + case MemberBindingType.ListBinding: { - var @goto = (GotoExpression)expr; - ChildIdxs children = default; - children.Add(AddLabelTarget(@goto.Target)); - if (@goto.Value != null) - children.Add(AddExpression(@goto.Value)); - var gotoIdx = _tree.AddRawExpressionNode(expr.Type, @goto.Kind, expr.NodeType, children); - _tree.GotoNodes.Add(gotoIdx); - return gotoIdx; + var listBinding = (MemberListBinding)binding; + var inits = new ushort[listBinding.Initializers.Count]; + for (var i = 0; i < inits.Length; ++i) + inits[i] = AddSysElementInit(listBinding.Initializers[i]); + return ListBind(binding.Member, inits); } - case ExpressionType.Label: + default: + throw new NotSupportedException($"Flattening of member binding `{binding.BindingType}` is not supported yet."); + } + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private ushort AddSysElementInit(SysElementInit init) + { + var args = new ushort[init.Arguments.Count]; + for (var i = 0; i < args.Length; ++i) + args[i] = AddSysExpression(init.Arguments[i]); + return ElementInit(init.AddMethod, args); + } + + private static int GetId(ref SmallMap16> ids, object item) + { + ref var id = ref ids.Map.AddOrGetValueRef(item, out var found); + if (!found) + id = ids.Map.Count; + return id; + } + + private void CollectLambdaClosureParameterUsages(ushort lambdaIdx) + { + var children = GetChildren(lambdaIdx); + if (children.Count == 0) + return; + + SmallList, NoArrayPool> lambdaParameterIds = default; + for (var i = 1; i < children.Count; ++i) + lambdaParameterIds.Add(ToStoredUShortIdx(Nodes[children[i]].ChildIdx)); + + SmallList, NoArrayPool> localParameterIds = default; + SmallList, NoArrayPool> captures = default; + CollectClosureParameterUsages(children[0], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + + for (var i = 0; i < captures.Count; ++i) + LambdaClosureParameterUsages.Add(captures[i]); + } + + private void CollectClosureParameterUsages( + ushort idx, + ushort lambdaIdx, + ref SmallList, NoArrayPool> lambdaParameterIds, + ref SmallList, NoArrayPool> localParameterIds, + ref SmallList, NoArrayPool> captures) + { + if (idx == 0) + return; + + ref var node = ref Nodes.GetSurePresentRef(idx); + + // Group / metadata kinds share some ExpressionType values with real expressions. + // Always walk their children as a plain sibling list — never apply Block/Lambda layouts. + if (node.Kind == ExprNodeKind.BlockExprs || + node.Kind == ExprNodeKind.SwitchCases || + node.Kind == ExprNodeKind.SwitchCase || + node.Kind == ExprNodeKind.CatchBlock || + node.Kind == ExprNodeKind.ObjectReference || + node.Kind == ExprNodeKind.UInt16Pair || + node.Kind == ExprNodeKind.LabelTarget || + node.Kind == ExprNodeKind.MemberAssignment || + node.Kind == ExprNodeKind.MemberMemberBinding || + node.Kind == ExprNodeKind.MemberListBinding || + node.Kind == ExprNodeKind.ElementInit) + { + if (node.Kind == ExprNodeKind.CatchBlock) + { + CollectCatchBlockClosureParameterUsages(idx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + return; + } + + WalkClosureChildren(idx, ref node, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + return; + } + + if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) || node.ChildCount == 0) + { + if (node.NodeType == ExpressionType.Parameter) + { + var parameterId = ToStoredUShortIdx(node.ChildIdx); + if (!Contains(ref lambdaParameterIds, parameterId) && + !Contains(ref localParameterIds, parameterId)) + AddClosureParameterUsage(lambdaIdx, idx, parameterId, ref captures); + } + return; + } + + switch (node.NodeType) + { + case ExpressionType.Parameter: { - var label = (LabelExpression)expr; - ChildIdxs children = default; - children.Add(AddLabelTarget(label.Target)); - if (label.DefaultValue != null) - children.Add(AddExpression(label.DefaultValue)); - var labelIdx = _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); - _tree.LabelNodes.Add(labelIdx); - return labelIdx; + var parameterId = ToStoredUShortIdx(node.ChildIdx); + if (!Contains(ref lambdaParameterIds, parameterId) && + !Contains(ref localParameterIds, parameterId)) + AddClosureParameterUsage(lambdaIdx, idx, parameterId, ref captures); + return; } - case ExpressionType.Switch: + case ExpressionType.Lambda: + PropagateNestedLambdaClosureParameterUsages(idx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + return; + case ExpressionType.Block: { - var @switch = (SwitchExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(@switch.SwitchValue)); - if (@switch.DefaultBody != null) - children.Add(AddExpression(@switch.DefaultBody)); - if (@switch.Cases.Count != 0) + // Layout: first child = BlockExprs (expression list); remaining siblings = variables. + var localCount = localParameterIds.Count; + var exprListIdx = node.ChildIdx; + if (exprListIdx == 0) + return; + + ref var exprList = ref Nodes.GetSurePresentRef(exprListIdx); + + var varIdx = exprList.NextIdx; + for (var i = 1; i < node.ChildCount && varIdx != 0 && varIdx != idx; ++i) { - ChildIdxs cases = default; - for (var i = 0; i < @switch.Cases.Count; ++i) - cases.Add(AddSwitchCase(@switch.Cases[i])); - children.Add(_tree.AddChildListNode(in cases)); + ref var v = ref Nodes.GetSurePresentRef(varIdx); + localParameterIds.Add(ToStoredUShortIdx(v.ChildIdx)); + varIdx = v.NextIdx; } - return _tree.AddRawExpressionNode(expr.Type, @switch.Comparison, expr.NodeType, in children); + + if (exprList.Is(ExprNodeKind.BlockExprs)) + WalkClosureChildren(exprListIdx, ref exprList, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + else + CollectClosureParameterUsages(exprListIdx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + + localParameterIds.Count = localCount; + return; } case ExpressionType.Try: { - var @try = (TryExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(@try.Body)); - var flags = (byte)0; - if (@try.Fault != null) - { - flags = TryFaultFlag; - children.Add(AddExpression(@try.Fault)); - } - else if (@try.Finally != null) - children.Add(AddExpression(@try.Finally)); - if (@try.Handlers.Count != 0) - { - ChildIdxs handlers = default; - for (var i = 0; i < @try.Handlers.Count; ++i) - handlers.Add(AddCatchBlock(@try.Handlers[i])); - children.Add(_tree.AddChildListNode(in handlers)); - } - var tryIdx = _tree.AddNode(expr.Type, null, expr.NodeType, ExprNodeKind.Expression, flags, in children); - _tree.TryCatchNodes.Add(tryIdx); - return tryIdx; - } - case ExpressionType.MemberInit: - { - var memberInit = (MemberInitExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(memberInit.NewExpression)); - for (var i = 0; i < memberInit.Bindings.Count; ++i) - children.Add(AddMemberBinding(memberInit.Bindings[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); - } - case ExpressionType.ListInit: - { - var listInit = (ListInitExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(listInit.NewExpression)); - for (var i = 0; i < listInit.Initializers.Count; ++i) - children.Add(AddElementInit(listInit.Initializers[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); - } - case ExpressionType.TypeIs: - case ExpressionType.TypeEqual: - { - var typeBinary = (TypeBinaryExpression)expr; - ChildIdxs children = default; - children.Add(AddExpression(typeBinary.Expression)); - return _tree.AddRawExpressionNode(expr.Type, typeBinary.TypeOperand, expr.NodeType, - children); - } - case ExpressionType.Dynamic: - { - var dynamic = (DynamicExpression)expr; - ChildIdxs children = default; - children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); - for (var i = 0; i < dynamic.Arguments.Count; ++i) - children.Add(AddExpression(dynamic.Arguments[i])); - return _tree.AddRawExpressionNode(expr.Type, dynamic.Binder, expr.NodeType, children); - } - case ExpressionType.RuntimeVariables: - { - var runtime = (RuntimeVariablesExpression)expr; - ChildIdxs children = default; - for (var i = 0; i < runtime.Variables.Count; ++i) - children.Add(AddExpression(runtime.Variables[i])); - return _tree.AddRawExpressionNode(expr.Type, null, expr.NodeType, children); - } - case ExpressionType.DebugInfo: - { - var debug = (DebugInfoExpression)expr; - return _tree.AddFactoryExpressionNode(expr.Type, debug.Document.FileName, expr.NodeType, - _tree.CreateDebugInfoChildren(debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn)); - } - default: - if (expr is UnaryExpression unary) - { - ChildIdxs children = default; - children.Add(AddExpression(unary.Operand)); - return _tree.AddRawExpressionNode(expr.Type, unary.Method, expr.NodeType, - children); + WalkClosureChildren(idx, ref node, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + return; } + } - if (expr is BinaryExpression binary) - { - ChildIdxs children = default; - children.Add(AddExpression(binary.Left)); - children.Add(AddExpression(binary.Right)); - if (binary.Conversion != null) - children.Add(AddExpression(binary.Conversion)); - return _tree.AddNode(expr.Type, binary.Method, expr.NodeType, ExprNodeKind.Expression, - binary.IsLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, in children); - } + WalkClosureChildren(idx, ref node, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + } - throw new NotSupportedException($"Flattening of `ExpressionType.{expr.NodeType}` is not supported yet."); + private void WalkClosureChildren( + ushort idx, + ref ExprNode node, + ushort lambdaIdx, + ref SmallList, NoArrayPool> lambdaParameterIds, + ref SmallList, NoArrayPool> localParameterIds, + ref SmallList, NoArrayPool> captures) + { + if (node.ChildCount == 0 || node.ChildIdx == 0) + return; + + // Stop after ChildCount. Also stop if the sibling chain hits 0 or loops back to this + // owner (last-child.NextIdx is the parent up-link). + var cIdx = node.ChildIdx; + for (var i = 0; i < node.ChildCount && cIdx != 0 && cIdx != idx; ++i) + { + var next = Nodes.GetSurePresentRef(cIdx).NextIdx; + CollectClosureParameterUsages(cIdx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + cIdx = next; } } + private void CollectCatchBlockClosureParameterUsages( + ushort idx, + ushort lambdaIdx, + ref SmallList, NoArrayPool> lambdaParameterIds, + ref SmallList, NoArrayPool> localParameterIds, + ref SmallList, NoArrayPool> captures) + { + ref var node = ref Nodes.GetSurePresentRef(idx); + Debug.Assert(node.Is(ExprNodeKind.CatchBlock)); + + var localCount = localParameterIds.Count; + var childIdx = node.ChildIdx; + if (node.HasFlag(CatchHasVariableFlag)) + { + localParameterIds.Add(ToStoredUShortIdx(Nodes.GetSurePresentRef(childIdx).ChildIdx)); + childIdx = Nodes.GetSurePresentRef(childIdx).NextIdx; + } + + var bodyIdx = childIdx; + childIdx = Nodes.GetSurePresentRef(childIdx).NextIdx; + if (node.HasFlag(CatchHasFilterFlag)) + CollectClosureParameterUsages(childIdx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + CollectClosureParameterUsages(bodyIdx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + localParameterIds.Count = localCount; + } + + private void PropagateNestedLambdaClosureParameterUsages( + ushort nestedLambdaIdx, + ushort lambdaIdx, + ref SmallList, NoArrayPool> lambdaParameterIds, + ref SmallList, NoArrayPool> localParameterIds, + ref SmallList, NoArrayPool> captures) + { + for (var i = 0; i < LambdaClosureParameterUsages.Count; ++i) + { + ref var usage = ref LambdaClosureParameterUsages[i]; + if (usage.LambdaIdx != nestedLambdaIdx) + continue; + if (Contains(ref lambdaParameterIds, usage.ParameterId) || + Contains(ref localParameterIds, usage.ParameterId)) + continue; + AddClosureParameterUsage(lambdaIdx, usage.ParameterIdx, usage.ParameterId, ref captures); + } + } + + private static void AddClosureParameterUsage( + ushort lambdaIdx, + ushort parameterIdx, + ushort parameterId, + ref SmallList, NoArrayPool> captures) + { + for (var i = 0; i < captures.Count; ++i) + if (captures[i].ParameterId == parameterId) + return; + captures.Add(new LambdaClosureParameterUsage(lambdaIdx, parameterIdx, parameterId)); + } + + private ChildIdxs GetChildren(int idx) + { + ref var node = ref Nodes.GetSurePresentRef(idx); + if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) || node.ChildCount == 0 || node.ChildIdx == 0) + return default; + var count = node.ChildCount; + ChildIdxs children = default; + // Stop after ChildCount. Also stop if the sibling chain hits 0 or loops back to this + // owner (last-child.NextIdx is the parent up-link). + var childIdx = node.ChildIdx; + for (var i = 0; i < count && childIdx != 0 && childIdx != idx; ++i) + { + children.Add(childIdx); + childIdx = Nodes.GetSurePresentRef(childIdx).NextIdx; + } + return children; + } /// Reconstructs the flat tree as a System.Linq expression tree. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", @@ -1304,7 +1346,8 @@ public SysExpr ToExpression() => /// Reconstructs the flat tree as a LightExpression tree. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] - public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); + public FastExpressionCompiler.LightExpression.Expression ToLightExpression() => + FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); /// Structurally compares two flat expression trees. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1504,6 +1547,7 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) break; case ExpressionType.Block: + // Layout: first child = BlockExprs; remaining siblings = variables. if (x.ChildCount == 0) return false; @@ -1512,30 +1556,25 @@ public bool Eq(ref ExprTree xTree, ref ExprTree yTree) descendX = x.ChildIdx; descendY = y.ChildIdx; descendChildCount = 1; - if (x.ChildCount == 2) + + ref var xExprList = ref xTree.Nodes.GetSurePresentRef(descendX); + ref var yExprList = ref yTree.Nodes.GetSurePresentRef(descendY); + if (xExprList.Kind != ExprNodeKind.BlockExprs || yExprList.Kind != ExprNodeKind.BlockExprs || + x.ChildCount != y.ChildCount) + return false; + + var xVariableIdx = xExprList.NextIdx; + var yVariableIdx = yExprList.NextIdx; + for (var i = 1; i < x.ChildCount; ++i) { - ref var xVariables = ref xTree.Nodes.GetSurePresentRef(descendX); - ref var yVariables = ref yTree.Nodes.GetSurePresentRef(descendY); - if (xVariables.Kind != ExprNodeKind.BlockExprs || yVariables.Kind != ExprNodeKind.BlockExprs || - xVariables.ChildCount != yVariables.ChildCount) + ref var xv = ref xTree.Nodes.GetSurePresentRef(xVariableIdx); + ref var yv = ref yTree.Nodes.GetSurePresentRef(yVariableIdx); + if (!AreEquivalentParameterDeclarations(ref xv, ref yv)) 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; + _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + xVariableIdx = xv.NextIdx; + yVariableIdx = yv.NextIdx; } break; @@ -1704,21 +1743,18 @@ private int HashLambda(ref ExprTree tree, int idx, int h) private int HashBlock(ref ExprTree tree, int idx, int h) { + // Layout: first child = BlockExprs; remaining siblings = variables. var scopeCount = _xParameterIds.Count; ref var node = ref tree.Nodes.GetSurePresentRef(idx); var bodyListIdx = node.ChildIdx; - if (node.ChildCount == 2) + ref var exprList = ref tree.Nodes.GetSurePresentRef(bodyListIdx); + var variableIdx = exprList.NextIdx; + for (var i = 1; i < node.ChildCount; ++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(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; + 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; } h = Combine(h, HashNode(ref tree, bodyListIdx)); @@ -1879,16 +1915,13 @@ public SysExpr ReadExpression(int idx) // 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. - // Variable decl nodes in children[0] are registered in _parametersById before - // the body expressions in children[1] are read, so refs in the body resolve - // to the same SysParameterExpression object as the decl (normal order here). + // Layout: children[0] = BlockExprs; children[1..] = variable decls. + // Register variables first so body refs resolve to the same parameter objects. var children = GetChildren(idx); - var hasVariables = children.Count == 2; - var variableIdxs = hasVariables ? GetChildren(children[0]) : default; - var expressionIdxs = GetChildren(children[children.Count - 1]); - var variables = new SysParameterExpression[variableIdxs.Count]; - for (var i = 0; i < variables.Length; ++i) - variables[i] = (SysParameterExpression)ReadExpression(variableIdxs[i]); + var variables = new SysParameterExpression[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + variables[i - 1] = (SysParameterExpression)ReadExpression(children[i]); + var expressionIdxs = GetChildren(children[0]); var expressions = new SysExpr[expressionIdxs.Count]; for (var i = 0; i < expressions.Length; ++i) expressions[i] = ReadExpression(expressionIdxs[i]); @@ -1960,7 +1993,7 @@ public SysExpr ReadExpression(int idx) { var children = GetChildren(idx); var value = children.Count > 1 ? ReadExpression(children[1]) : null; - return SysExpr.MakeGoto((GotoExpressionKind)node.Obj, ReadLabelTarget(children[0]), value, node.Type); + return SysExpr.MakeGoto((GotoExpressionKind)node.Flags, ReadLabelTarget(children[0]), value, node.Type); } case ExpressionType.Label: { @@ -1970,13 +2003,14 @@ public SysExpr ReadExpression(int idx) } case ExpressionType.Switch: { + // Layout: switchValue, optional defaultBody, optional SwitchCases group. var children = GetChildren(idx); var defaultBody = default(SysExpr); ChildIdxs caseIdxs = default; if (children.Count > 1) { ref var lastChild = ref _tree.Nodes[children[children.Count - 1]]; - if (lastChild.Is(ExprNodeKind.BlockExprs)) + if (lastChild.Is(ExprNodeKind.SwitchCases)) { caseIdxs = GetChildren(children[children.Count - 1]); if (children.Count == 3) @@ -1992,25 +2026,22 @@ public SysExpr ReadExpression(int idx) } case ExpressionType.Try: { + // Layout: body, optional finally/fault, then CatchBlock handlers as direct children. var children = GetChildren(idx); if (node.HasFlag(TryFaultFlag)) return SysExpr.TryFault(ReadExpression(children[0]), ReadExpression(children[1])); - var handlers = default(SysCatchBlock[]); - var lastChildIsHandlerList = children.Count > 1 && _tree.Nodes[children[children.Count - 1]].Is(ExprNodeKind.BlockExprs); - if (lastChildIsHandlerList) + var handlerStart = 1; + var @finally = default(SysExpr); + if (children.Count > 1 && !_tree.Nodes[children[1]].Is(ExprNodeKind.CatchBlock)) { - var handlerIdxs = GetChildren(children[children.Count - 1]); - handlers = new SysCatchBlock[handlerIdxs.Count]; - for (var i = 0; i < handlers.Length; ++i) - handlers[i] = ReadCatchBlock(handlerIdxs[i]); + @finally = ReadExpression(children[1]); + handlerStart = 2; } - else - handlers = Array.Empty(); - var @finally = children.Count > 1 && (!lastChildIsHandlerList || children.Count == 3) - ? ReadExpression(children[1]) - : null; + var handlers = new SysCatchBlock[children.Count - handlerStart]; + for (var i = 0; i < handlers.Length; ++i) + handlers[i] = ReadCatchBlock(children[handlerStart + i]); return SysExpr.TryCatchFinally(ReadExpression(children[0]), @finally, handlers); } case ExpressionType.MemberInit: @@ -2168,10 +2199,14 @@ private SysElementInit ReadElementInit(int idx) private ChildIdxs GetChildren(int idx) { ref var node = ref _tree.Nodes.GetSurePresentRef(idx); + if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) || node.ChildCount == 0 || node.ChildIdx == 0) + return default; var count = node.ChildCount; ChildIdxs children = default; + // Stop after ChildCount. Also stop if the sibling chain hits 0 or loops back to this + // owner (last-child.NextIdx is the parent up-link). var childIdx = node.ChildIdx; - for (var i = 0; i < count; ++i) + for (var i = 0; i < count && childIdx != 0 && childIdx != idx; ++i) { children.Add(childIdx); childIdx = _tree.Nodes.GetSurePresentRef(childIdx).NextIdx; @@ -2225,20 +2260,6 @@ private SysExpr[] ReadExpressions(in ChildIdxs childIdxs) } } -/// Builds the flat representation while preserving parameter and label identity. -public static class FlatExprBuilder -{ - private SmallMap16> _parameterIds; - private SmallMap16> _labelIds; - private ExprTree _tree; - - public ExprTree Build(SysExpr sysExpr) - { - _tree.RootIdx = AddExpression(sysExpr); - return _tree; - } -} - /// Union struct for reinterpreting float bits as uint without unsafe code. [StructLayout(LayoutKind.Explicit)] internal struct FloatBits @@ -2272,10 +2293,37 @@ internal static T UnsupportedInlineConstantType(object value, TypeCode tc) => /// Provides conversions from System and LightExpression trees to . public static class FlatExpressionExtensions { - /// Flattens a System.Linq expression tree. + /// Flattens a System.Linq expression tree into a new . + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public static ExprTree ToFlatExpression(this SysExpr expression) + { + ExprTree tree = default; + tree.FromSysExpr(expression); + return tree; + } + + /// Flattens a System.Linq expression tree into the supplied . + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public static ref ExprTree ToFlatExpression(this SysExpr expression, ref ExprTree exprTree) { exprTree.FromSysExpr(expression); return ref exprTree; } + + /// Flattens a LightExpression tree into a new . + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public static ExprTree ToFlatExpression(this FastExpressionCompiler.LightExpression.Expression expression) + { + ExprTree tree = default; + tree.FromLightExpr(expression); + return tree; + } + + /// Flattens a LightExpression tree into the supplied . + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public static ref ExprTree ToFlatExpression(this FastExpressionCompiler.LightExpression.Expression expression, ref ExprTree exprTree) + { + exprTree.FromLightExpr(expression); + return ref exprTree; + } } diff --git a/src/FastExpressionCompiler/ImTools.cs b/src/FastExpressionCompiler/ImTools.cs index a84fa62e..8b52600e 100644 --- a/src/FastExpressionCompiler/ImTools.cs +++ b/src/FastExpressionCompiler/ImTools.cs @@ -326,14 +326,18 @@ public interface ISize /// Returns the size of the collection or container int Size { get; } } -/// Marker for collection or container holding 2 or items -public interface ISize2Plus : ISize { } +/// Marker for collection or container holding 1 or more items +public interface ISize1Plus : ISize { } +/// Marker for collection or container holding 2 or more items +public interface ISize2Plus : ISize1Plus { } /// Marker for collection or container holding 4 or more items public interface ISize4Plus : ISize2Plus { } /// Marker for collection or container holding 8 or more items public interface ISize8Plus : ISize4Plus { } /// Marker for collection or container holding 16 or more items public interface ISize16Plus : ISize8Plus { } +/// Marker for collection or container holding 32 or more items +public interface ISize32Plus : ISize16Plus { } /// Marker for collection or container holding 0 items public struct Size0 : ISize @@ -342,7 +346,14 @@ public struct Size0 : ISize public int Size => 0; } -/// Marker for collection or container holding 4 items +/// Marker for collection or container holding 1 item +public struct Size1 : ISize1Plus +{ + /// + public int Size => 1; +} + +/// Marker for collection or container holding 2 items public struct Size2 : ISize2Plus { /// @@ -366,6 +377,13 @@ public struct Size16 : ISize16Plus /// public int Size => 16; } +/// Marker for collection or container holding 32 items +public struct Size32 : ISize32Plus +{ + /// + public int Size => 32; +} + /// Implementation of `IStack` for 2 items on stack [StructLayout(LayoutKind.Sequential, Pack = 1)] @@ -580,6 +598,86 @@ public ref T this[int index] #endif } +// todo: @perf create variant with Stack32 +/// Implementation of `IStack` for 16 items on stack +[StructLayout(LayoutKind.Sequential, Pack = 1)] +public struct Stack32 : IStack> +{ + /// + public int Capacity => 32; + int IIndexed.Count => Capacity; + + internal T _it0, _it1, _it2, _it3, _it4, _it5, _it6, _it7; + internal T _it8, _it9, _it10, _it11, _it12, _it13, _it14, _it15; + internal T _it16, _it17, _it18, _it19, _it20, _it21, _it22, _it23; + internal T _it24, _it25, _it26, _it27, _it28, _it29, _it30, _it31; + + /// + [UnscopedRef] + [MethodImpl((MethodImplOptions)256)] + public ref T GetSurePresentRef(int index) + { +#if SUPPORTS_UNSAFE + return ref Unsafe.Add(ref _it0, index); +#else + switch (index) + { + case 0: return ref _it0; + case 1: return ref _it1; + case 2: return ref _it2; + case 3: return ref _it3; + case 4: return ref _it4; + case 5: return ref _it5; + case 6: return ref _it6; + case 7: return ref _it7; + case 8: return ref _it8; + case 9: return ref _it9; + case 10: return ref _it10; + case 11: return ref _it11; + case 12: return ref _it12; + case 13: return ref _it13; + case 14: return ref _it14; + case 15: return ref _it15; + case 16: return ref _it16; + case 17: return ref _it17; + case 18: return ref _it18; + case 19: return ref _it19; + case 20: return ref _it20; + case 21: return ref _it21; + case 22: return ref _it22; + case 23: return ref _it23; + case 24: return ref _it24; + case 25: return ref _it25; + case 26: return ref _it26; + case 27: return ref _it27; + case 28: return ref _it28; + case 29: return ref _it29; + case 30: return ref _it30; + default: return ref _it31; + } +#endif + } + + /// + [UnscopedRef] + public ref T this[int index] + { + [MethodImpl((MethodImplOptions)256)] + get + { + if (index >= 0 & index < Capacity) + return ref GetSurePresentRef(index); + return ref Stack.ThrowIndexOutOfBounds(index, Capacity); + } + } + +#if SUPPORTS_CREATE_SPAN + /// + [MethodImpl((MethodImplOptions)256)] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _it0, Capacity); +#endif +} + /// Abstraction over the small array pool to rent and return the arrays of small sizes, from 1 to N public interface ISmallArrayPool { @@ -851,10 +949,6 @@ 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 diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs index a55d9bb5..7fa152f4 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs @@ -56,7 +56,7 @@ private static FastExpressionCompiler.LightExpression.Expression BuildLightInt(I _ => throw new NotSupportedException(spec.GetType().Name) }; - private static int BuildFlatInt(ref ExprTree fe, IntSpec spec, int[] ints) => + private static ushort BuildFlatInt(ref ExprTree fe, IntSpec spec, ushort[] ints) => spec switch { IntSpec.ParameterRef parameter => ints[parameter.Index], @@ -86,7 +86,7 @@ private static FastExpressionCompiler.LightExpression.Expression BuildLightBool( _ => throw new NotSupportedException(spec.GetType().Name) }; - private static int BuildFlatBool(ref ExprTree fe, BoolSpec spec, int[] ints) => + private static ushort BuildFlatBool(ref ExprTree fe, BoolSpec spec, ushort[] ints) => spec switch { BoolSpec.Constant constant => fe.ConstantOf(constant.Value), @@ -115,10 +115,10 @@ private static FastExpressionCompiler.LightExpression.Expression BuildLightBlock return Block(locals, expressions); } - private static int BuildFlatBlock(ref ExprTree fe, IntSpec.LetMany letMany, int[] ints) + private static ushort BuildFlatBlock(ref ExprTree fe, IntSpec.LetMany letMany, ushort[] ints) { - var locals = new int[letMany.Values.Length]; - var expressions = new int[letMany.Values.Length + 1]; + var locals = new ushort[letMany.Values.Length]; + var expressions = new ushort[letMany.Values.Length + 1]; for (var i = 0; i < locals.Length; ++i) { locals[i] = fe.Variable(typeof(int), $"v{i}"); diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 9931082b..c1715d04 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -570,6 +570,19 @@ public void Flat_lambda_multiple_parameter_refs_all_yield_same_identity() Asserts.AreSame(paramDecl, add.Right); } + public void Flat_repeated_child_references_do_not_create_self_cycle() + { + var fe = default(ExprTree); + var p = fe.ParameterOf("p"); + fe.RootIdx = fe.Lambda>(fe.Add(p, p), p); + + var sysLambda = (System.Linq.Expressions.LambdaExpression)fe.ToExpression(); + var add = (System.Linq.Expressions.BinaryExpression)sysLambda.Body; + + Asserts.AreSame(sysLambda.Parameters[0], add.Left); + Asserts.AreSame(sysLambda.Parameters[0], add.Right); + } + /// /// Block variables are read before body expressions (normal order), /// but each variable idx is cloned whenever it appears as a child. @@ -768,7 +781,8 @@ public void Flat_metadata_uses_16_bit_idx_storage() Asserts.AreEqual(6, typeof(LambdaClosureParameterUsage).StructLayoutAttribute.Size); var fe = default(ExprTree); - for (var i = 0; i < ushort.MaxValue; ++i) + // Index 0 is reserved as the absent-child sentinel, so MaxValue-1 real nodes fill storage. + for (var i = 1; i < ushort.MaxValue; ++i) fe.Default(typeof(int)); Asserts.AreEqual(ushort.MaxValue, fe.Nodes.Count); From b8fc8d661d45795d3015ffb6727dd2caf1979fc4 Mon Sep 17 00:00:00 2001 From: dadhi Date: Fri, 14 Aug 2026 13:08:26 +0200 Subject: [PATCH 18/18] properly adding net10.0 - no alloc ComplexExpr but 3x slower creation that Ligh --- .vscode/settings.json | 1 + bm.bat | 2 +- bt.bat | 2 +- build.bat | 2 +- .../FlatExpression.cs | 91 +++++++++++++------ src/FastExpressionCompiler/ImTools.cs | 15 ++- .../FastExpressionCompiler.Benchmarks.csproj | 8 +- .../LightExprVsFlatExpr_Create_ComplexExpr.cs | 11 +++ .../Program.cs | 4 +- ...Compiler.LightExpression.IssueTests.csproj | 4 +- ...nCompiler.LightExpression.UnitTests.csproj | 30 +++--- .../FastExpressionCompiler.UnitTests.csproj | 4 +- 12 files changed, 116 insertions(+), 58 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bd22d1da..3f73ce4b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,6 +7,7 @@ "Castclass", "Conv", "cref", + "Diagnoser", "Dmark", "dont", "Funcs", diff --git a/bm.bat b/bm.bat index 678c7f6b..3ed83135 100644 --- a/bm.bat +++ b/bm.bat @@ -1,2 +1,2 @@ @echo off -dotnet run -c:Release -f:net9.0 --project test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj \ No newline at end of file +dotnet run -c:Release -f:net10.0 --project test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj \ No newline at end of file diff --git a/bt.bat b/bt.bat index f5fe81d3..fce184bd 100644 --- a/bt.bat +++ b/bt.bat @@ -3,7 +3,7 @@ echo: echo:## Running TESTS on the Latest Supported .NET... echo: -dotnet run -p:DevMode=true -f:net9.0 -c:Release --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj +dotnet run -p:DevMode=true -f:net10.0 -c:Release --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj if %ERRORLEVEL% neq 0 goto :error echo: diff --git a/build.bat b/build.bat index a46b739f..e4aa6c4f 100644 --- a/build.bat +++ b/build.bat @@ -21,7 +21,7 @@ dotnet run --no-build -f:net10.0 -c:Release --project test/FastExpressionCompile if %ERRORLEVEL% neq 0 goto :error echo: -echo:running on .NET 9.0 (Latest) +echo:running on .NET 9.0 dotnet run --no-build -f:net9.0 -c:Release --project test/FastExpressionCompiler.TestsRunner if %ERRORLEVEL% neq 0 goto :error diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index c79bfea5..e5448a42 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -200,7 +200,7 @@ public struct ExprTree : IEquatable public int RootIdx; /// Gets or sets the flat node storage. - public SmallList, NoArrayPool> Nodes; + public SmallList, NoArrayPool> Nodes; /// Gets or sets closure constants that are referenced from constant nodes. public SmallList, NoArrayPool> ClosureConstants; @@ -215,10 +215,10 @@ public struct ExprTree : IEquatable /// Gets or sets all node idxs, /// including return, break, and continue. - public SmallList, NoArrayPool> GotoNodes; + public SmallList, NoArrayPool> GotoNodes; /// Gets or sets all expression node idxs. - public SmallList, NoArrayPool> LabelNodes; + public SmallList, NoArrayPool> LabelNodes; /// Gets or sets all node idxs for try/catch, try/finally, try/fault, and combined forms. public SmallList, NoArrayPool> TryCatchNodes; @@ -360,14 +360,6 @@ private ushort MayBeCloneChildForOwner(ushort childIdx, ushort ownerIdx) return childIdx; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private ushort ReserveOwner(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind) - { - EnsureIndexZeroSentinel(); - var owner = new ExprNode(nodeType, type, obj, flags, kind); - return checked((ushort)Nodes.Add(in owner)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AppendPreparedChild(ushort childIdx, ref ushort firstChildIdx, ref ushort prevChildIdx, ref ushort childCount) { @@ -382,7 +374,7 @@ private void AppendPreparedChild(ushort childIdx, ref ushort firstChildIdx, ref [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort WithOneChild(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0) { - var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + var ownerIdx = AddNode(nodeType, type, obj, flags, kind); ushort first = 0; ushort count = 0; if (ch0 != 0) @@ -397,7 +389,7 @@ private ushort WithOneChild(ExpressionType nodeType, Type type, object obj, byte [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort WithTwoChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1) { - var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + var ownerIdx = AddNode(nodeType, type, obj, flags, kind); ushort first = 0, prev = 0, count = 0; if (ch0 != 0) @@ -412,7 +404,7 @@ private ushort WithTwoChildren(ExpressionType nodeType, Type type, object obj, b [MethodImpl(MethodImplOptions.AggressiveInlining)] private ushort WithThreeChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1, ushort ch2) { - var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + var ownerIdx = AddNode(nodeType, type, obj, flags, kind); ushort first = 0, prev = 0, count = 0; if (ch0 != 0) @@ -428,7 +420,7 @@ private ushort WithThreeChildren(ExpressionType nodeType, Type type, object obj, private ushort WithTwoOrMoreChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1, ushort[] more) { - var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + var ownerIdx = AddNode(nodeType, type, obj, flags, kind); ushort first = 0, prev = 0, count = 0; if (ch0 != 0) @@ -450,56 +442,75 @@ private ushort WithTwoOrMoreChildren(ExpressionType nodeType, Type type, object return ownerIdx; } +#if NET10_0_OR_GREATER + private ushort WithOneOrMoreChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ReadOnlySpan more) + { +#else private ushort WithOneOrMoreChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort[] more) { - var ownerIdx = ReserveOwner(nodeType, type, obj, flags, kind); + more ??= Array.Empty(); +#endif + var ownerIdx = AddNode(nodeType, type, obj, flags, kind); ushort first = 0, prev = 0, count = 0; if (ch0 != 0) - AppendPreparedChild(MayBeCloneChildForOwner(ch0, ownerIdx), ref first, ref prev, ref count); - - if (more != null) { - for (var i = 0; i < more.Length; ++i) - { - var ch = more[i]; - if (ch == 0) continue; - AppendPreparedChild(MayBeCloneChildForOwner(ch, ownerIdx), ref first, ref prev, ref count); - } + prev = first = MayBeCloneChildForOwner(ch0, ownerIdx); + ++count; } + foreach (var ch in more) + if (ch != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch, ownerIdx), ref first, ref prev, ref count); + Nodes.GetSurePresentRef(ownerIdx).SetChildrenInfo(count, first); return ownerIdx; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET10_0_OR_GREATER + private ushort WithChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, params ReadOnlySpan children) => +#else private ushort WithChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, params ushort[] children) => +#endif WithOneOrMoreChildren(nodeType, type, obj, flags, kind, 0, children); /// Adds a constructor call node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET10_0_OR_GREATER + public ushort New(ConstructorInfo ctor, params ReadOnlySpan args) => +#else public ushort New(ConstructorInfo ctor, params ushort[] args) => +#endif WithChildren(ExpressionType.New, ctor.DeclaringType, ctor, default, default, args); /// Adds an array initialization node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort NewArrayInit(Type elementType, params ushort[] expressions) => WithChildren(ExpressionType.NewArrayInit, elementType.MakeArrayType(), null, default, default, expressions); /// Adds an array-bounds node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort NewArrayBounds(Type elementType, params ushort[] bounds) => WithChildren(ExpressionType.NewArrayBounds, elementType.MakeArrayType(), null, default, default, bounds); /// Adds an invocation node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort Invoke(ushort expr, params ushort[] args) => WithOneOrMoreChildren(ExpressionType.Invoke, Nodes[expr].Type, null, default, default, expr, args); /// Adds a static-call node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort Call(MethodInfo method, params ushort[] args) => WithChildren(ExpressionType.Call, method.ReturnType, method, default, default, args); /// Adds an instance-call node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort Call(ushort instance, MethodInfo method, params ushort[] args) => WithOneOrMoreChildren(ExpressionType.Call, method.ReturnType, method, default, default, instance, args); /// Adds a field or property access node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort MakeMemberAccess(MemberInfo member) => AddNode(ExpressionType.MemberAccess, GetMemberType(member), member); @@ -507,6 +518,7 @@ public ushort MakeMemberAccess(MemberInfo member) => /// The node index representing the instance target. /// The member to access. /// The node index of the added member-access node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort MakeMemberAccess(ushort instance, MemberInfo member) => WithOneChild(ExpressionType.MemberAccess, GetMemberType(member), member, default, default, instance); @@ -533,8 +545,9 @@ public ushort Property(ushort instance, PropertyInfo prop, params ushort[] args) : WithOneOrMoreChildren(ExpressionType.Index, prop.PropertyType, prop, default, default, instance, args); /// Adds a binary node of the specified kind. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort MakeBinary(ExpressionType nodeType, ushort left, ushort right, bool isLiftedToNull = false, - MethodInfo method = null, ushort conversion = 0, Type type = null) => + MethodInfo method = null, ushort conversion = 0, Type type = null) => WithThreeChildren( nodeType, type ?? GetBinaryResultType(nodeType, Nodes[left].Type, method), method, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, default, left, right, conversion); @@ -603,7 +616,7 @@ public ushort Block(Type type, ushort[] vars, params ushort[] exprs) type ??= Nodes[exprs[exprs.Length - 1]].Type; var blockIdx = WithOneOrMoreChildren(ExpressionType.Block, type, null, default, default, exprsSubNode, vars); if (vars != null && vars.Length != 0) - BlocksWithVariables.Add(blockIdx); + BlocksWithVariables.Add(blockIdx); return blockIdx; } @@ -613,7 +626,12 @@ public ushort Block(params ushort[] exprs) => Block(null, null, exprs); /// Adds a lambda node. Layout: body then parameters. Tracks and captures. + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET10_0_OR_GREATER + public ushort Lambda(Type delegateType, ushort bodyIdx, params ReadOnlySpan pars) +#else public ushort Lambda(Type delegateType, ushort bodyIdx, params ushort[] pars) +#endif { var idx = WithOneOrMoreChildren(ExpressionType.Lambda, delegateType, null, default, default, bodyIdx, pars); LambdaNodes.Add(idx); @@ -623,18 +641,25 @@ public ushort Lambda(Type delegateType, ushort bodyIdx, params ushort[] pars) /// Adds a typed lambda node. [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET10_0_OR_GREATER + public ushort Lambda(ushort bodyIdx, params ReadOnlySpan parameters) where TDelegate : Delegate => +#else public ushort Lambda(ushort bodyIdx, params ushort[] parameters) where TDelegate : Delegate => +#endif Lambda(typeof(TDelegate), bodyIdx, parameters); /// Adds a member-assignment binding node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort Bind(MemberInfo member, ushort expr) => WithOneChild(default, GetMemberType(member), member, default, ExprNodeKind.MemberAssignment, expr); /// Adds a nested member-binding node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort MemberBind(MemberInfo member, params ushort[] bindings) => WithChildren(default, GetMemberType(member), member, default, ExprNodeKind.MemberMemberBinding, bindings); /// Adds an element-initializer node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort ElementInit(MethodInfo addMethod, params ushort[] args) => WithChildren(default, addMethod.DeclaringType, addMethod, default, ExprNodeKind.ElementInit, args); @@ -643,14 +668,21 @@ public ushort ListBind(MemberInfo member, params ushort[] initializers) => WithChildren(default, GetMemberType(member), member, default, ExprNodeKind.MemberListBinding, initializers); /// Adds a member-init node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NET10_0_OR_GREATER + public ushort MemberInit(ushort expr, params ReadOnlySpan bindings) => +#else public ushort MemberInit(ushort expr, params ushort[] bindings) => +#endif WithOneOrMoreChildren(ExpressionType.MemberInit, Nodes[expr].Type, null, default, default, expr, bindings); /// Adds a list-init node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort ListInit(ushort @new, params ushort[] initializers) => WithOneOrMoreChildren(ExpressionType.ListInit, Nodes[@new].Type, null, default, default, @new, initializers); /// Adds a label-target node with a stable identity in . + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort Label(Type type = null, string name = null) => LabelTargetWithId(type ?? typeof(void), name, checked((ushort)(Nodes.Count + 1))); @@ -659,6 +691,7 @@ private ushort LabelTargetWithId(Type type, string name, ushort id) => AddNode(ExpressionType.Extension, type, name, 0, ExprNodeKind.LabelTarget, childIdx: id); /// Adds a label-expression node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort Label(ushort target, ushort defaultValue = 0) { var idx = defaultValue == 0 @@ -669,6 +702,7 @@ public ushort Label(ushort target, ushort defaultValue = 0) } /// Adds a goto-family node. Kind is stored in flags. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort MakeGoto(GotoExpressionKind gotoKind, ushort target, ushort value = 0, Type type = null) { var resultType = type ?? (value != 0 ? Nodes[value].Type : typeof(void)); @@ -686,6 +720,7 @@ public ushort MakeGoto(GotoExpressionKind gotoKind, ushort target, ushort value public ushort Return(ushort target, ushort value) => MakeGoto(GotoExpressionKind.Return, target, value, Nodes[value].Type); /// Adds a loop node. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort Loop(ushort body, ushort @break = 0, ushort @continue = 0) { byte flags = 0; @@ -694,7 +729,9 @@ public ushort Loop(ushort body, ushort @break = 0, ushort @continue = 0) return WithThreeChildren(ExpressionType.Loop, typeof(void), null, flags, default, body, @break, @continue); } + // @perf use params ReadOnlySpan /// Adds a switch-case node. Layout: test values then body. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ushort SwitchCase(ushort body, params ushort[] testValues) => WithOneOrMoreChildren(default, null, null, default, ExprNodeKind.SwitchCase, 0, AppendUShort(testValues, body)); diff --git a/src/FastExpressionCompiler/ImTools.cs b/src/FastExpressionCompiler/ImTools.cs index 8b52600e..41168b5b 100644 --- a/src/FastExpressionCompiler/ImTools.cs +++ b/src/FastExpressionCompiler/ImTools.cs @@ -897,12 +897,11 @@ public ref T GetSurePresentRef(int index) Debug.Assert(_count != 0, "List should not be empty"); Debug.Assert(index >= 0 & index < _count, $"Index {index} should be less than Count {_count}"); - var stackCap = Stack.Capacity; - if (index < stackCap) + if (index < Stack.Capacity) return ref Stack.GetSurePresentRef(index); Debug.Assert(Rest != null); - return ref Rest.GetSurePresentRef(index - stackCap); + return ref Rest.GetSurePresentRef(index - Stack.Capacity); } /// Returns surely present item by ref @@ -936,6 +935,16 @@ public ref T AddDefaultAndGetRef() return ref SmallList.AddDefaultAndGetRef(ref Rest, ref Pool, index - stackCap); } + /// Adds the item to the end of the list aka the Stack.Push. Returns the ref to the added item. + [UnscopedRef] + [MethodImpl((MethodImplOptions)256)] + public ref T AddAndGetRef(in T item) + { + ref T r = ref AddDefaultAndGetRef(); + r = item; + return ref r; + } + /// Adds the item to the end of the list aka the Stack.Push. Returns the index of the added item. [MethodImpl((MethodImplOptions)256)] public int Add(in T item) diff --git a/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj b/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj index 10a5283e..98d93d40 100644 --- a/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj +++ b/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj @@ -1,8 +1,8 @@  - net9.0;net8.0 - net9.0 + net10.0;net9.0;net8.0 + net10.0 Exe false @@ -17,8 +17,8 @@ - - + + diff --git a/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs b/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs index 525ad531..62abe1dd 100644 --- a/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs +++ b/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs @@ -4,6 +4,17 @@ namespace FastExpressionCompiler.Benchmarks { + /* +.NET SDK 10.0.400 + [Host] : .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v3 + DefaultJob : .NET 10.0.11 (10.0.11, 10.0.1126.37416), X64 RyuJIT x86-64-v3 + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +|----------------------- |---------:|---------:|---------:|------:|--------:|-----:|-------:|----------:|------------:| +| Create_LightExpression | 175.0 ns | 3.29 ns | 4.17 ns | 1.00 | 0.03 | 1 | 0.0827 | 520 B | 1.00 | +| Create_FlatExpression | 525.5 ns | 10.47 ns | 12.86 ns | 3.00 | 0.10 | 2 | - | - | 0.00 | + + */ [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class LightExprVsFlatExpr_Create_ComplexExpr { diff --git a/test/FastExpressionCompiler.Benchmarks/Program.cs b/test/FastExpressionCompiler.Benchmarks/Program.cs index b49cb798..0f395dbb 100644 --- a/test/FastExpressionCompiler.Benchmarks/Program.cs +++ b/test/FastExpressionCompiler.Benchmarks/Program.cs @@ -21,7 +21,7 @@ public static void Main() // BenchmarkRunner.Run(); // not included in README.md, may be it needs to // BenchmarkRunner.Run(); - // BenchmarkRunner.Run(); + BenchmarkRunner.Run(); // BenchmarkRunner.Run(); //-------------------------------------------- @@ -54,7 +54,7 @@ public static void Main() // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); - BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); diff --git a/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj b/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj index d10a3535..5b1467c2 100644 --- a/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj @@ -1,7 +1,7 @@  - net472;net6.0;net8.0;net9.0 - net472;net9.0 + net472;net6.0;net8.0;net9.0;net10.0 + net472;net9.0;net10.0 LIGHT_EXPRESSION diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj index 42ebb322..28e1c129 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj @@ -1,16 +1,16 @@ - - - net472;net6.0;net8.0;net9.0 - net472;net9.0 - - LIGHT_EXPRESSION - - - - - - - + + + net472;net6.0;net8.0;net9.0;net10.0 + net472;net9.0;net10.0 + + LIGHT_EXPRESSION + + + + + + + @@ -19,8 +19,8 @@ - - + + diff --git a/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj b/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj index 5826f08b..280277dd 100644 --- a/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj +++ b/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj @@ -1,7 +1,7 @@  - net472;net6.0;net8.0;net9.0 - net472;net9.0 + net472;net6.0;net8.0;net9.0;net10.0 + net472;net9.0;net10.0