diff --git a/.vscode/settings.json b/.vscode/settings.json index 98afea1c..3f73ce4b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,11 +7,13 @@ "Castclass", "Conv", "cref", + "Diagnoser", "Dmark", "dont", "Funcs", "gotos", "Hasher", + "idxs", "iface", "ifaces", "ifthen", 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 ad07b3fe..e5448a42 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -5,11 +5,10 @@ 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 SysCatchBlock = System.Linq.Expressions.CatchBlock; using SysElementInit = System.Linq.Expressions.ElementInit; using SysExpr = System.Linq.Expressions.Expression; @@ -17,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, - /// Represents a switch case payload. + Expression = 0, + /// 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. @@ -39,31 +43,23 @@ 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, } /// 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 DataIdxMask = 0xFFFFu; + private const int ChildCountShift = 16; + private const uint ChildCountMask = 0xFFFF0000u; + private const uint FirstChildIdxMask = 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(); @@ -76,74 +72,90 @@ 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); + /// Gets the first child idx or an auxiliary payload idx (parameter/label id, closure constant idx). + public ushort ChildIdx => (ushort)(_child & FirstChildIdxMask); + + /// 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 => _data; + internal uint InlineValue => _child; - 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, byte flags, ExprNodeKind kind, + ushort childIdx = 0, ushort childCount = 0, ushort nextIdx = 0) { 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); + internal bool Is(ExprNodeKind kind) => Kind == kind; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SetChildInfo(int childIdx, int childCount) => - _data = ((uint)checked((ushort)childCount) << DataCountShift) | checked((ushort)childIdx); + 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))); [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool Is(ExprNodeKind kind) => Kind == kind; + 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 IsExpression() => Kind == ExprNodeKind.Expression; + internal bool HasFlag(byte flag) => (Flags & flag) != 0; [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool HasFlag(byte flag) => (Flags & flag) != 0; + 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 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. @@ -173,7 +185,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; @@ -188,104 +200,135 @@ public struct ExprTree 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; /// 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; - /// Adds a parameter node and returns its idx. - public int Parameter(Type type, string name = null) + // 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, + ushort childIdx = 0, ushort childCount = 0) { - var id = Nodes.Count + 1; - return AddRawLeafExpressionNode(type, name, ExpressionType.Parameter, type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: id); + 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) => + 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)] - 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) => AddRawExpressionNode(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 ushort Default(Type type) => AddNode(ExpressionType.Default, type); /// Adds a constant node with an explicit constant type. - public int Constant(object value, Type type) + public ushort Constant(object value, Type type) { if (value == null || value is string || value is Type || value is decimal) - return AddRawExpressionNode(type, value, ExpressionType.Constant); + return AddNode(ExpressionType.Constant, type, value); 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); + 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 (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); + if (!In32BitRange(tc)) + return AddNode(ExpressionType.Constant, type, value); + EnsureIndexZeroSentinel(); + return checked((ushort)Nodes.Add(new ExprNode(type, ToInlineValue(value, tc)))); } - // 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 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)] + 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) => AddRawExpressionNode(type ?? typeof(object), null, ExpressionType.Constant); + public ushort ConstantNull(Type type = null) => AddNode(ExpressionType.Constant, type ?? typeof(object)); /// Adds an constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantInt(int value) => AddRawExpressionNode(typeof(int), value, ExpressionType.Constant); + 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 int ConstantOf(T value) => Constant(value, typeof(T)); + 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); /// 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 AddRawExpressionNode(type, null, ExpressionType.New); + return AddNode(ExpressionType.New, type); foreach (var ctor in type.GetConstructors()) if (ctor.GetParameters().Length == 0) @@ -294,1150 +337,831 @@ public int New(Type type) throw new ArgumentException($"The type {type} is missing the default constructor"); } + /// 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 ushort MayBeCloneChildForOwner(ushort childIdx, ushort ownerIdx) + { + 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 void AppendPreparedChild(ushort childIdx, ref ushort firstChildIdx, ref ushort prevChildIdx, ref ushort childCount) + { + if (childCount == 0) + firstChildIdx = childIdx; + else + Nodes.GetSurePresentRef(prevChildIdx).NextIdx = childIdx; + prevChildIdx = childIdx; + ++childCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort WithOneChild(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0) + { + var ownerIdx = AddNode(nodeType, type, obj, flags, kind); + ushort first = 0; + ushort count = 0; + if (ch0 != 0) + { + first = MayBeCloneChildForOwner(ch0, ownerIdx); + count = 1; + } + Nodes.GetSurePresentRef(ownerIdx).SetChildrenInfo(count, first); + return ownerIdx; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ushort WithTwoChildren(ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind, ushort ch0, ushort ch1) + { + 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 (ch1 != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch1, ownerIdx), ref first, ref prev, ref count); + + 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) + { + 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 (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); + + 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 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 (ch1 != 0) + AppendPreparedChild(MayBeCloneChildForOwner(ch1, 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); + } + } + + Nodes.GetSurePresentRef(ownerIdx).SetChildrenInfo(count, first); + 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) + { + more ??= Array.Empty(); +#endif + var ownerIdx = AddNode(nodeType, type, obj, flags, kind); + ushort first = 0, prev = 0, count = 0; + + if (ch0 != 0) + { + 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. - public int New(System.Reflection.ConstructorInfo constructor, params int[] arguments) => - AddFactoryExpressionNode(constructor.DeclaringType, constructor, ExpressionType.New, arguments); + [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. - public int NewArrayInit(Type elementType, params int[] expressions) => - AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayInit, expressions); + [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. - public int NewArrayBounds(Type elementType, params int[] bounds) => - AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayBounds, bounds); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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)); + [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. - public int Call(System.Reflection.MethodInfo method, params int[] arguments) => - AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, arguments); + [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. - public int Call(int instance, System.Reflection.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)); + [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. - 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); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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, System.Reflection.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, System.Reflection.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(System.Reflection.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, System.Reflection.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. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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, System.Reflection.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, System.Reflection.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, System.Reflection.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, System.Reflection.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, System.Reflection.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, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Equal, left, right, method: method); - - /// Adds a binary node of the specified kind. - public int MakeBinary(ExpressionType nodeType, int left, int right, bool isLiftedToNull = false, - System.Reflection.MethodInfo method = null, int? conversion = null, Type type = null) - => 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); + + /// 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 (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[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 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) + /// 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 = parameters == null || parameters.Length == 0 - ? AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, 0, body) - : AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, PrependToChildList(body, parameters)); + 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)] +#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. - public int Bind(System.Reflection.MemberInfo member, int expression) => - AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberAssignment, expression); + [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. - public int MemberBind(System.Reflection.MemberInfo member, params int[] bindings) => - AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberMemberBinding, bindings); + [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. - public int ElementInit(System.Reflection.MethodInfo addMethod, params int[] arguments) => - AddFactoryAuxNode(addMethod.DeclaringType, addMethod, ExprNodeKind.ElementInit, arguments); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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(System.Reflection.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)); + [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. - 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)); + [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. - public int Label(Type type = null, string name = null) - { - var id = Nodes.Count + 1; - return AddRawLeafAuxNode(type ?? typeof(void), name, ExprNodeKind.LabelTarget, childIdx: id); - } + /// 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))); + + [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. - /// The node idx is recorded in . - public int Label(int target, int? defaultValue = null) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort Label(ushort target, ushort defaultValue = 0) { - var idx = defaultValue.HasValue - ? AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target, defaultValue.Value) - : AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target); + 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. - /// The node idx is recorded in . - public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type type = null) + /// 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.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); + var resultType = type ?? (value != 0 ? Nodes[value].Type : typeof(void)); + var idx = WithTwoChildren(ExpressionType.Goto, resultType, null, (byte)gotoKind, default, target, value); GotoNodes.Add(idx); return idx; } /// 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) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort Loop(ushort body, ushort @break = 0, ushort @continue = 0) { - 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); + 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. - public int SwitchCase(int body, params int[] testValues) + // @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)); + + /// 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) { - 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); + 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, switchValue, defaultBody, casesIdx); } /// Adds a switch node without an explicit default case or comparer. - public int Switch(int switchValue, params int[] cases) => - Switch(Nodes[switchValue].Type, switchValue, null, null, cases); - - /// Adds a switch node. - public int Switch(Type type, int switchValue, int? defaultBody, System.Reflection.MethodInfo comparison, params int[] cases) + public ushort Switch(ushort switchValue, 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 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. - public int Catch(int variable, int body) => - AddFactoryAuxNode(Nodes[variable].Type, null, ExprNodeKind.CatchBlock, CatchHasVariableFlag, variable, body); + /// 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, CatchHasVariableFlag, ExprNodeKind.CatchBlock, variable, body); /// 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) + /// 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) { - 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); + 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. - /// The node idx is recorded in . - public int TryCatch(int body, params int[] handlers) + /// Adds a try/catch node. Layout: body, handlers… + public ushort TryCatch(ushort body, params ushort[] 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); - } + var idx = WithOneOrMoreChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, body, handlers); TryCatchNodes.Add(idx); return idx; } /// Adds a try/finally node. - /// The node idx is recorded in . - public int TryFinally(int body, int @finally) + public ushort TryFinally(ushort body, ushort @finally) { - var idx = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body, @finally); + var idx = WithTwoChildren(ExpressionType.Try, Nodes[body].Type, null, default, default, 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) + public ushort TryFault(ushort body, ushort fault) { - var idx = AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, TryFaultFlag, body, 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. - /// The node idx is recorded in . - public int TryCatchFinally(int body, int? @finally, params int[] 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) { - 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); + 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 int TypeIs(int expression, Type type) => - AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeIs, expression); + public ushort TypeIs(ushort expr, Type type) => + WithOneChild(ExpressionType.TypeIs, typeof(bool), type, default, default, expr); /// Adds a type-equality test node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int TypeEqual(int expression, Type type) => - AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeEqual, expression); + public ushort TypeEqual(ushort expr, Type type) => + WithOneChild(ExpressionType.TypeEqual, typeof(bool), type, default, default, expr); - /// Adds a dynamic-expression node. - public int Dynamic(Type delegateType, CallSiteBinder binder, params int[] arguments) + /// Adds a dynamic-expression node. Delegate type stored as ObjectReference first child. + public ushort Dynamic(Type delegateType, CallSiteBinder binder, params ushort[] args) { - 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); + 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 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()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child)); - - [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)); - - [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)); - - [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)); - - [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)); - - [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)); - - [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)); - - [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)); + public ushort RuntimeVariables(params ushort[] vars) => + WithChildren(ExpressionType.RuntimeVariables, typeof(IRuntimeVariables), null, default, default, vars); - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) + /// 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) { - if (children != null) - switch (children.Length) - { - case 1: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0]); - case 2: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1]); - case 3: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2]); - case 4: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3]); - case 5: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4]); - case 6: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4], children[5]); - case 7: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4], children[5], children[6]); - } - - var cloned = CloneChildren(children); - return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); - } - - [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) => - 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); - - [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 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)); - - [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, CloneChild(child0), CloneChild(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); + 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); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, in ChildList children) + private static ushort[] AppendUShort(ushort[] prefix, ushort last) { - var cloned = CloneChildren(children); - return AddNode(type, obj, ExpressionType.Extension, kind, flags, in cloned); + 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; } - [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) + /// Flattens a System.Linq expression into this tree and sets . + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public ushort FromSysExpr(SysExpr expr) { - ChildList children = default; - children.Add(AddUInt16PairNode(startLine, startColumn)); - children.Add(AddUInt16PairNode(endLine, endColumn)); - return children; + _parameterIds = default; + _labelIds = default; + RootIdx = AddSysExpression(expr); + return (ushort)RootIdx; } - private static ChildList PrependToChildList(int first, int[] rest) + // @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) { - ChildList children = default; - children.Add(first); - if (rest != null) - for (var i = 0; i < rest.Length; ++i) - children.Add(rest[i]); - return children; + // Prefer Sys round-trip for a single From core; Light→Sys preserves identity via ToExpression. + return FromSysExpr(expr.ToExpression()); } - /// Builds the flat representation while preserving parameter and label identity. - private struct Builder + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private ushort AddSysExpression(SysExpr expr) { - private SmallMap16> _parameterIds; - private SmallMap16> _labelIds; - private ExprTree _tree; - - public ExprTree Build(SysExpr expression) + switch (expr.NodeType) { - _tree.RootIdx = AddExpression(expression); - return _tree; - } + case ExpressionType.Constant: + return Constant(((ConstantExpression)expr).Value, expr.Type); - private int AddExpression(SysExpr expression) - { - switch (expression.NodeType) - { - case ExpressionType.Constant: - return AddConstant((System.Linq.Expressions.ConstantExpression)expression); - case ExpressionType.Default: - return _tree.AddRawExpressionNode(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)); - } - 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 = (System.Linq.Expressions.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, 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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.MemberExpression)expression; - ChildList children = default; - if (member.Expression != null) - children.Add(AddExpression(member.Expression)); - return _tree.AddRawExpressionNode(expression.Type, member.Member, expression.NodeType, - children); - } - case ExpressionType.Call: - { - var call = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.ConditionalExpression)expression; - ChildList children = default; - children.Add(AddExpression(conditional.Test)); - children.Add(AddExpression(conditional.IfTrue)); - children.Add(AddExpression(conditional.IfFalse)); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, - children[0], children[1], children[2]); - } - case ExpressionType.Loop: - { - var loop = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.TryExpression)expression; - ChildList children = default; - children.Add(AddExpression(@try.Body)); - var flags = (byte)0; - if (@try.Fault != null) - { - flags = TryFaultFlag; - children.Add(AddExpression(@try.Fault)); - } - else if (@try.Finally != null) - children.Add(AddExpression(@try.Finally)); - 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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.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 = (System.Linq.Expressions.DynamicExpression)expression; - ChildList children = default; - children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); - for (var i = 0; i < dynamic.Arguments.Count; ++i) - children.Add(AddExpression(dynamic.Arguments[i])); - return _tree.AddRawExpressionNode(expression.Type, dynamic.Binder, expression.NodeType, children); - } - case ExpressionType.RuntimeVariables: - { - var runtime = (System.Linq.Expressions.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 = (System.Linq.Expressions.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) - { - ChildList children = default; - children.Add(AddExpression(unary.Operand)); - return _tree.AddRawExpressionNode(expression.Type, unary.Method, expression.NodeType, - children); - } + case ExpressionType.Default: + return Default(expr.Type); - if (expression is System.Linq.Expressions.BinaryExpression binary) + case ExpressionType.Parameter: + { + var parameter = (SysParameterExpression)expr; + var id = checked((ushort)GetId(ref _parameterIds, parameter)); + return ParameterWithId(parameter.IsByRef ? parameter.Type : expr.Type, parameter.Name, id); + } + case ExpressionType.Lambda: + { + var lambda = (LambdaExpression)expr; + 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: + { + var block = (BlockExpression)expr; + ushort[] vars = null; + if (block.Variables.Count != 0) { - 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); + vars = new ushort[block.Variables.Count]; + for (var i = 0; i < vars.Length; ++i) + vars[i] = AddSysExpression(block.Variables[i]); } - - throw new NotSupportedException($"Flattening of `ExpressionType.{expression.NodeType}` is not supported yet."); - } - } - - private int AddConstant(System.Linq.Expressions.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(((System.Linq.Expressions.MemberAssignment)binding).Expression)); - return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, - assignmentChildren); - case MemberBindingType.MemberBinding: + 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; + return member.Expression != null + ? MakeMemberAccess(AddSysExpression(member.Expression), member.Member) + : MakeMemberAccess(member.Member); + } + case ExpressionType.Call: + { + var call = (MethodCallExpression)expr; + 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; + 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; + 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; + 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; + 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; + return Condition( + AddSysExpression(conditional.Test), + AddSysExpression(conditional.IfTrue), + AddSysExpression(conditional.IfFalse), + expr.Type); + } + case ExpressionType.Loop: + { + var loop = (LoopExpression)expr; + 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; + return MakeGoto(@goto.Kind, AddSysLabelTarget(@goto.Target), + @goto.Value != null ? AddSysExpression(@goto.Value) : (ushort)0, expr.Type); + } + case ExpressionType.Label: + { + var label = (LabelExpression)expr; + return Label(AddSysLabelTarget(label.Target), + label.DefaultValue != null ? AddSysExpression(label.DefaultValue) : (ushort)0); + } + case ExpressionType.Switch: + { + var @switch = (SwitchExpression)expr; + var cases = new ushort[@switch.Cases.Count]; + for (var i = 0; i < cases.Length; ++i) { - var memberBinding = (System.Linq.Expressions.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); + 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); } - case MemberBindingType.ListBinding: + 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; + 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) { - var listBinding = (System.Linq.Expressions.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); + 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); } - 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); - } + if (@try.Finally != null) + return handlers.Length != 0 + ? TryCatchFinally(AddSysExpression(@try.Body), AddSysExpression(@try.Finally), handlers) + : TryFinally(AddSysExpression(@try.Body), AddSysExpression(@try.Finally)); - 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; + return TryCatch(AddSysExpression(@try.Body), handlers); + } + case ExpressionType.MemberInit: + { + var memberInit = (MemberInitExpression)expr; + 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; + 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; + return TypeEqual(AddSysExpression(typeBinary.Expression), typeBinary.TypeOperand); + } + case ExpressionType.Dynamic: + { + var dynamic = (DynamicExpression)expr; + 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; + 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 DebugInfo(debug.Document.FileName, debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn); + } + default: + if (expr is UnaryExpression unary) + return MakeUnary(expr.NodeType, AddSysExpression(unary.Operand), expr.Type, unary.Method); + + if (expr is BinaryExpression binary) + 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."); } - - private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch - { - System.Reflection.FieldInfo field => field.FieldType, - System.Reflection.PropertyInfo property => property.PropertyType, - _ => typeof(object) - }; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddLeafNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int childIdx, int childCount) - { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, childIdx, childCount); - return nodeIdx; - } - - [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 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; - } - - 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, int c0, int 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); - return nodeIdx; - } - - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int 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); - return nodeIdx; - } - - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int 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); - 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) - { - 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); - 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) - { - 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); - 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) - { - 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); - return nodeIdx; - } - - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int[] children) - { - if (children == null || children.Length == 0) - return AddNode(type, obj, nodeType, kind, flags); - - var 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]).SetNextIdx(children[i]); - return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, in ChildList children) - { - 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]).SetNextIdx(children[i]); - return nodeIdx; - } - - [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)] - private static uint ToInlineValue(object value, TypeCode tc) => tc switch + private ushort AddSysLabelTarget(SysLabelTarget target) { - TypeCode.Boolean => (bool)value ? 1u : 0u, - TypeCode.Byte => (byte)value, - TypeCode.SByte => (uint)(byte)(sbyte)value, - TypeCode.Char => (char)value, - TypeCode.Int16 => (uint)(ushort)(short)value, - TypeCode.UInt16 => (ushort)value, - TypeCode.Int32 => (uint)(int)value, - TypeCode.UInt32 => (uint)value, - TypeCode.Single => FloatBits.ToUInt((float)value), - _ => FlatExpressionThrow.UnsupportedInlineConstantType(value, tc) - }; - - private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch - { - System.Reflection.FieldInfo field => field.FieldType, - System.Reflection.PropertyInfo property => property.PropertyType, - _ => typeof(object) - }; - - private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType, System.Reflection.MethodInfo method) => - nodeType switch + 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) { - ExpressionType.IsFalse or ExpressionType.IsTrue or ExpressionType.TypeIs or ExpressionType.TypeEqual => typeof(bool), - _ => method?.ReturnType ?? operandType - }; - - private static Type GetBinaryResultType(ExpressionType nodeType, Type leftType, Type rightType, System.Reflection.MethodInfo method) - { - if (method != null) - return method.ReturnType; - - return nodeType switch - { - ExpressionType.Equal or ExpressionType.NotEqual or ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual - or ExpressionType.LessThan or ExpressionType.LessThanOrEqual or ExpressionType.AndAlso or ExpressionType.OrElse => typeof(bool), - ExpressionType.ArrayIndex => leftType.GetElementType(), - ExpressionType.Assign => leftType, - _ => leftType - }; - } - - private static Type GetArrayElementType(Type arrayType, int depth) - { - var elementType = arrayType; - for (var i = 0; i < depth; ++i) - elementType = elementType.GetElementType(); - return elementType ?? typeof(object); + 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); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CloneChild(int idx) + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private ushort AddSysMemberBinding(SysMemberBinding binding) { - 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); + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + return Bind(binding.Member, AddSysExpression(((MemberAssignment)binding).Expression)); + case MemberBindingType.MemberBinding: + { + 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 MemberBindingType.ListBinding: + { + 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); + } + default: + throw new NotSupportedException($"Flattening of member binding `{binding.BindingType}` is not supported yet."); + } } - private ChildList CloneChildren(int[] children) + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private ushort AddSysElementInit(SysElementInit init) { - ChildList cloned = default; - if (children == null) - return cloned; - - for (var i = 0; i < children.Length; ++i) - cloned.Add(CloneChild(children[i])); - return cloned; + 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 ChildList CloneChildren(in ChildList children) + private static int GetId(ref SmallMap16> ids, object item) { - ChildList cloned = default; - for (var i = 0; i < children.Count; ++i) - cloned.Add(CloneChild(children[i])); - return cloned; + ref var id = ref ids.Map.AddOrGetValueRef(item, out var found); + if (!found) + id = ids.Map.Count; + return id; } - private void CollectLambdaClosureParameterUsages(int lambdaIdx) + private void CollectLambdaClosureParameterUsages(ushort lambdaIdx) { var children = GetChildren(lambdaIdx); if (children.Count == 0) @@ -1449,20 +1173,60 @@ private void CollectLambdaClosureParameterUsages(int lambdaIdx) SmallList, NoArrayPool> localParameterIds = default; SmallList, NoArrayPool> captures = default; - CollectClosureParameterUsages(children[0], ToStoredUShortIdx(lambdaIdx), ref lambdaParameterIds, ref localParameterIds, ref captures); + 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( - int idx, + 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: @@ -1470,61 +1234,72 @@ private void CollectClosureParameterUsages( var parameterId = ToStoredUShortIdx(node.ChildIdx); if (!Contains(ref lambdaParameterIds, parameterId) && !Contains(ref localParameterIds, parameterId)) - AddClosureParameterUsage(lambdaIdx, ToStoredUShortIdx(idx), parameterId, ref captures); + AddClosureParameterUsage(lambdaIdx, idx, parameterId, ref captures); return; } case ExpressionType.Lambda: - PropagateNestedLambdaClosureParameterUsages(ToStoredUShortIdx(idx), lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + PropagateNestedLambdaClosureParameterUsages(idx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); return; case ExpressionType.Block: { - var children = GetChildren(idx); + // Layout: first child = BlockExprs (expression list); remaining siblings = variables. var localCount = localParameterIds.Count; - var hasVariables = children.Count == 2; - if (hasVariables) + 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) { - var variableIdxs = GetChildren(children[0]); - for (var i = 0; i < variableIdxs.Count; ++i) - localParameterIds.Add(ToStoredUShortIdx(Nodes[variableIdxs[i]].ChildIdx)); + ref var v = ref Nodes.GetSurePresentRef(varIdx); + localParameterIds.Add(ToStoredUShortIdx(v.ChildIdx)); + varIdx = v.NextIdx; } - 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); + 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 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); + WalkClosureChildren(idx, ref node, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); return; } } - if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) || node.ChildCount == 0) + WalkClosureChildren(idx, ref node, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + } + + 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; - var childIdxs = GetChildren(idx); - for (var i = 0; i < childIdxs.Count; ++i) - CollectClosureParameterUsages(childIdxs[i], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + // 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( - int idx, + ushort idx, ushort lambdaIdx, ref SmallList, NoArrayPool> lambdaParameterIds, ref SmallList, NoArrayPool> localParameterIds, @@ -1533,15 +1308,18 @@ private void CollectCatchBlockClosureParameterUsages( ref var node = ref Nodes.GetSurePresentRef(idx); Debug.Assert(node.Is(ExprNodeKind.CatchBlock)); - var children = GetChildren(idx); var localCount = localParameterIds.Count; - var childIdx = 0; + var childIdx = node.ChildIdx; if (node.HasFlag(CatchHasVariableFlag)) - localParameterIds.Add(ToStoredUShortIdx(Nodes[children[childIdx++]].ChildIdx)); + { + localParameterIds.Add(ToStoredUShortIdx(Nodes.GetSurePresentRef(childIdx).ChildIdx)); + childIdx = Nodes.GetSurePresentRef(childIdx).NextIdx; + } - var bodyIdx = children[childIdx++]; + var bodyIdx = childIdx; + childIdx = Nodes.GetSurePresentRef(childIdx).NextIdx; if (node.HasFlag(CatchHasFilterFlag)) - CollectClosureParameterUsages(children[childIdx], lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); + CollectClosureParameterUsages(childIdx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); CollectClosureParameterUsages(bodyIdx, lambdaIdx, ref lambdaParameterIds, ref localParameterIds, ref captures); localParameterIds.Count = localCount; } @@ -1577,21 +1355,116 @@ private static void AddClosureParameterUsage( captures.Add(new LambdaClosureParameterUsage(lambdaIdx, parameterIdx, parameterId)); } - private ChildList GetChildren(int idx) + private ChildIdxs GetChildren(int idx) { ref var node = ref Nodes.GetSurePresentRef(idx); - if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) || node.ChildCount == 0) + if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker) || node.ChildCount == 0 || node.ChildIdx == 0) return default; var count = node.ChildCount; - ChildList children = default; + 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 = Nodes.GetSurePresentRef(childIdx).NextIdx; } return children; } + /// 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 FastExpressionCompiler.LightExpression.Expression 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 || + tc == TypeCode.Char || tc == TypeCode.Int16 || tc == TypeCode.UInt16 || + tc == TypeCode.Int32 || tc == TypeCode.UInt32 || tc == TypeCode.Single; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ToInlineValue(object value, TypeCode tc) => tc switch + { + TypeCode.Boolean => (bool)value ? 1u : 0u, + TypeCode.Byte => (byte)value, + TypeCode.SByte => (uint)(byte)(sbyte)value, + TypeCode.Char => (char)value, + TypeCode.Int16 => (uint)(ushort)(short)value, + TypeCode.UInt16 => (ushort)value, + TypeCode.Int32 => (uint)(int)value, + TypeCode.UInt32 => (uint)value, + TypeCode.Single => FloatBits.ToUInt((float)value), + _ => FlatExpressionThrow.UnsupportedInlineConstantType(value, tc) + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Type GetMemberType(MemberInfo member) => member switch + { + FieldInfo field => field.FieldType, + PropertyInfo property => property.PropertyType, + _ => typeof(object) + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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 + }; + + [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), + ExpressionType.ArrayIndex => leftType.GetElementType(), + ExpressionType.Assign => leftType, + _ => leftType + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Type GetArrayElementType(Type arrayType, int depth) + { + var elementType = arrayType; + for (var i = 0; i < depth; ++i) + elementType = elementType.GetElementType(); + return elementType ?? typeof(object); + } private static bool Contains(ref SmallList ids, ushort value) where TStack : struct, IStack @@ -1606,14 +1479,430 @@ private static bool Contains(ref SmallList [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort ToStoredUShortIdx(int idx) => checked((ushort)idx); + private struct StructuralComparer + { + private ChildIdxs _xParameterIds, _yParameterIds; + private SmallList, NoArrayPool> _xLabelIds, _yLabelIds; + private SmallList, NoArrayPool> _eqFrames; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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; + + var xIdx = xTree.RootIdx; + var yIdx = yTree.RootIdx; + var remainingSiblings = 0; + while (true) + { + ref var x = ref xTree.Nodes.GetSurePresentRef(xIdx); + ref var y = ref yTree.Nodes.GetSurePresentRef(yIdx); + if (x.Kind == ExprNodeKind.UInt16Pair) + { + if (!x.HasSameShape(ref y)) + return false; + } + else if (x.NodeType == ExpressionType.Constant) + { + + if (x.Type != y.Type || x.NodeType != y.NodeType || x.FlagsAndKind != y.FlagsAndKind) + return false; + } + else if (!x.HasSameShapeExceptChildIdx(ref y)) + return false; + + var descendX = 0; + var descendY = 0; + var descendChildCount = 0; + var restoreXParameterCount = -1; + var restoreYParameterCount = -1; + + if (x.Kind != ExprNodeKind.UInt16Pair) + { + if (x.Kind == ExprNodeKind.LabelTarget) + { + if (!EqLabelTarget(ref x, ref y)) + return false; + } + else if (x.Kind == ExprNodeKind.CatchBlock) + { + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; + descendX = x.ChildIdx; + descendY = y.ChildIdx; + 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); + 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 (!AreConstantsEqual(ref xTree, ref x, ref yTree, ref y)) + return false; + break; + + case ExpressionType.Lambda: + if (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: + // Layout: first child = BlockExprs; remaining siblings = variables. + if (x.ChildCount == 0) + return false; + + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = 1; + + 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 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; + } + break; + + default: + if (!EqObj(ref x, ref y)) + return false; + if (x.ChildCount != 0) + { + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = x.ChildCount; + } + break; + } + } + } + + if (descendChildCount != 0) + { + _eqFrames.Add(new TraversalFrame(x.NextIdx, y.NextIdx, remainingSiblings, restoreXParameterCount, restoreYParameterCount)); + xIdx = descendX; + yIdx = descendY; + remainingSiblings = descendChildCount - 1; + continue; + } + + var advanced = false; + while (true) + { + if (remainingSiblings != 0) + { + xIdx = x.NextIdx; + yIdx = y.NextIdx; + remainingSiblings--; + advanced = true; + break; + } + + if (_eqFrames.Count == 0) + return true; + + var frame = _eqFrames[_eqFrames.Count - 1]; + _eqFrames.Count -= 1; + if (frame.XParameterCount >= 0) + _xParameterIds.Count = frame.XParameterCount; + if (frame.YParameterCount >= 0) + _yParameterIds.Count = frame.YParameterCount; + if (frame.RemainingSiblingsAfterNode != 0) + { + xIdx = frame.XNextIdx; + yIdx = frame.YNextIdx; + remainingSiblings = frame.RemainingSiblingsAfterNode - 1; + advanced = true; + break; + } + } + if (advanced) + continue; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Hash(ref ExprTree tree) => + tree.Nodes.Count == 0 ? 0 : HashNode(ref 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 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); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool AreEquivalentParameterDeclarations(ref ExprNode x, ref ExprNode y) => + x.NodeType == ExpressionType.Parameter && + y.NodeType == ExpressionType.Parameter && + x.HasSameShapeExceptChildIdx(ref y); + + 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) + { + 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(ref 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, GetConstantHashCode(ref tree, ref node)); + + case ExpressionType.Lambda: + return HashLambda(ref tree, idx, h); + + case ExpressionType.Block: + return HashBlock(ref tree, idx, h); + } + + 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(ref ExprTree tree, int idx, int h) + { + var scopeCount = _xParameterIds.Count; + 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(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(ref tree, bodyIdx)); + _xParameterIds.Count = scopeCount; + return 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; + ref var exprList = ref tree.Nodes.GetSurePresentRef(bodyListIdx); + var variableIdx = exprList.NextIdx; + for (var i = 1; i < node.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; + } + + h = Combine(h, HashNode(ref tree, bodyListIdx)); + _xParameterIds.Count = scopeCount; + return h; + } + + 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 scopeCount = _xParameterIds.Count; + var childIdx = 0; + var catchChildIdx = node.ChildIdx; + if (node.HasFlag(CatchHasVariableFlag)) + { + 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(ref tree, catchChildIdx)); + catchChildIdx = tree.Nodes.GetSurePresentRef(catchChildIdx).NextIdx; + childIdx++; + if (node.HasFlag(CatchHasFilterFlag)) + h = Combine(h, HashNode(ref tree, catchChildIdx)); + + _xParameterIds.Count = scopeCount; + return h; + } + + 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 xInline = ReferenceEquals(x.Obj, ExprNode.InlineValueMarker); + var yInline = ReferenceEquals(y.Obj, ExprNode.InlineValueMarker); + 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 xObj?.Equals(yObj) ?? yObj == null; + } + + if (x.Type.IsEnum) + return x.InlineValue == y.InlineValue; + + var typeCode = Type.GetTypeCode(x.Type); + Debug.Assert(In32BitRange(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) => + ReferenceEquals(node.Obj, ClosureConstantMarker) ? tree.ClosureConstants[node.ChildIdx] : node.Obj; + + private static int GetInlineConstantHashCode(Type type, uint data) + { + if (!type.IsEnum) + { + var typeCode = Type.GetTypeCode(type); + Debug.Assert(In32BitRange(typeCode)); + if (typeCode == TypeCode.Single) + return FloatBits.ToFloat(data).GetHashCode(); + } + + return data.GetHashCode(); + } + + [StructLayout(LayoutKind.Sequential)] + private struct TraversalFrame + { + 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) + { + RemainingSiblingsAfterNode = remainingSiblingsAfterNode; + XParameterCount = xParameterCount; + YParameterCount = yParameterCount; + XNextIdx = checked((ushort)xNextIdx); + YNextIdx = checked((ushort)yNextIdx); + } + } + } + /// 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; @@ -1624,7 +1913,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) @@ -1663,16 +1952,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]); @@ -1681,11 +1967,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; @@ -1698,7 +1984,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); } @@ -1717,7 +2003,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)]; @@ -1744,7 +2030,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: { @@ -1754,13 +2040,14 @@ public SysExpr ReadExpression(int idx) } case ExpressionType.Switch: { + // Layout: switchValue, optional defaultBody, optional SwitchCases group. 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.SwitchCases)) { caseIdxs = GetChildren(children[children.Count - 1]); if (children.Count == 3) @@ -1772,29 +2059,26 @@ 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: { + // 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.ChildList); - 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: @@ -1803,7 +2087,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: { @@ -1811,7 +2095,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); @@ -1844,16 +2128,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."); @@ -1915,7 +2199,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: @@ -1946,16 +2230,20 @@ 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) + 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; - ChildList children = default; + 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; @@ -1994,7 +2282,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) @@ -2005,9 +2293,8 @@ 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); } - } /// Union struct for reinterpreting float bits as uint without unsafe code. @@ -2043,9 +2330,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. - public static ExprTree ToFlatExpression(this SysExpr expression) => ExprTree.FromExpression(expression); + /// 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. - public static ExprTree ToFlatExpression(this LightExpression expression) => ExprTree.FromLightExpression(expression); + /// 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 99e76db9..41168b5b 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 { @@ -799,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 @@ -838,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.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 3e3ff694..c1715d04 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() @@ -34,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(); @@ -55,9 +55,13 @@ 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; + } public void Can_compile_lambda_without_converting_to_expression() { @@ -280,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(); @@ -392,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); @@ -415,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() @@ -584,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. @@ -782,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); @@ -1023,5 +1023,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); + } } } 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