diff --git a/.vscode/settings.json b/.vscode/settings.json index 98afea1c..bd22d1da 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "Funcs", "gotos", "Hasher", + "idxs", "iface", "ifaces", "ifthen", diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs index ad07b3fe..d4265395 100644 --- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -5,11 +5,12 @@ namespace FastExpressionCompiler.FlatExpression; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using FastExpressionCompiler.LightExpression.ImTools; -using ChildList = FastExpressionCompiler.LightExpression.ImTools.SmallList, FastExpressionCompiler.LightExpression.ImTools.NoArrayPool>; -using LightExpression = FastExpressionCompiler.LightExpression.Expression; +using ChildList = LightExpression.ImTools.SmallList, LightExpression.ImTools.NoArrayPool>; +using LightExpression = LightExpression.Expression; using SysCatchBlock = System.Linq.Expressions.CatchBlock; using SysElementInit = System.Linq.Expressions.ElementInit; using SysExpr = System.Linq.Expressions.Expression; @@ -22,7 +23,7 @@ namespace FastExpressionCompiler.FlatExpression; public enum ExprNodeKind : byte { /// Represents a regular expression node. - Expression, + Expression = 0, /// Represents a switch case payload. SwitchCase, /// Represents a catch block payload. @@ -46,24 +47,16 @@ public enum ExprNodeKind : byte } /// Stores one flat expression node and its child-link metadata in 24 bytes on 64-bit runtimes. -/// -/// Layout (64-bit): Type(8) | Obj(8) | _meta(4) | _data(4) = 24 bytes. -/// _meta bits: NodeType(8)|Tag(8)|NextIdx(16). -/// _data bits: ChildCount(16)|ChildIdx(16) for regular nodes, -/// or the raw 32-bit value for inline primitive constants (when == ). -/// [StructLayout(LayoutKind.Explicit, Size = 24)] public struct ExprNode { // _meta layout: bits [31:24]=NodeType | [23:20]=Flags | [19:16]=Kind | [15:0]=NextIdx - private const int MetaNodeTypeShift = 24; private const int MetaTagShift = 16; - private const uint MetaKeepWithoutNext = 0xFFFF0000u; // _data layout: bits [31:16]=ChildCount | [15:0]=ChildIdx (or full uint for inline constants) - private const int DataCountShift = 16; - private const uint DataIdxMask = 0xFFFFu; + private const int ChildCountShift = 16; + private const uint ChildCountMask = 0xFFFF0000u; + private const uint ChildIdxMask = 0xFFFFu; private const int FlagsShift = 4; - private const uint KindMask = 0x0Fu; /// Sentinel placed in to indicate the node holds a small primitive constant in . internal static readonly object InlineValueMarker = new(); @@ -76,74 +69,96 @@ 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); + public ExprNodeKind Kind => (ExprNodeKind)(FlagsAndKind & 0b1111); - internal byte Flags => (byte)((_meta >> (MetaTagShift + FlagsShift)) & 0xFu); - - /// Gets the next sibling node idx. - public int NextIdx => (int)(_meta & 0xFFFFu); + internal byte Flags => (byte)(FlagsAndKind >> 4); /// Gets the number of direct children linked from this node. - public int ChildCount => (int)(_data >> DataCountShift); + public ushort ChildCount => (ushort)(_child >> ChildCountShift); /// Gets the first child idx or an auxiliary payload idx. - public int ChildIdx => (int)(_data & DataIdxMask); + public ushort ChildIdx => (ushort)(_child & ChildIdxMask); + + public void SetChild(ushort childCount, ushort childIdx) => _child = ((uint)childCount << ChildCountShift) | childIdx; /// Gets the raw 32-bit value for inline primitive constants. Only valid when == . - internal uint InlineValue => _data; + internal uint InlineValue => _child; + + internal ExprNode(ExpressionType nodeType, Type type, object obj = null) + { + Type = type; + Obj = obj; + _nodeType = (byte)nodeType; + } - internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0, int nextIdx = 0) + internal ExprNode(ExpressionType nodeType, Type type, object obj, + ExprNodeKind kind = default, byte flags = default, + ushort childIdx = 0, ushort childCount = 0, ushort nextIdx = 0) { 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 +188,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; @@ -216,11 +231,9 @@ public struct ExprTree public SmallList, NoArrayPool> LambdaClosureParameterUsages; /// Adds a parameter node and returns its idx. - public int Parameter(Type type, string name = null) - { - var id = Nodes.Count + 1; - return AddRawLeafExpressionNode(type, name, ExpressionType.Parameter, type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: id); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Parameter(Type type, string name = null) => + Nodes.Add(new(ExpressionType.Parameter, type, name, flags: type.IsByRef ? ParameterByRefFlag : (byte)0)); /// Adds a typed parameter node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -232,60 +245,33 @@ public int Parameter(Type type, string name = null) /// Adds a default-value node and returns its idx. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Default(Type type) => 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 int Default(Type type) => Nodes.Add(new(ExpressionType.Default, type, null)); /// Adds a constant node with an explicit constant type. - public int Constant(object value, Type type) - { - if (value == null || value is string || value is Type || value is decimal) - return AddRawExpressionNode(type, value, ExpressionType.Constant); - - if (type.IsEnum) - { - var underlyingTc = Type.GetTypeCode(Enum.GetUnderlyingType(type)); - if (IsSmallPrimitive(underlyingTc)) - return AddInlineConstantNode(type, (uint)System.Convert.ToInt64(value)); - // long/ulong-backed enum (extremely rare): store boxed in Obj - return AddRawExpressionNode(type, value, ExpressionType.Constant); - } - - 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); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Constant(object value, Type type) => Nodes.Add(new(ExpressionType.Constant, type, value)); - // Delegate, array types, and user-defined reference/value types go to ClosureConstants - var constantIdx = ClosureConstants.Add(value); - return AddRawLeafExpressionNode(type, ClosureConstantMarker, ExpressionType.Constant, childIdx: constantIdx); - } + /// Adds a constant node using the runtime type of the supplied value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Constant(object value) => Constant(value, value?.GetType() ?? typeof(object)); /// Adds a null constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantNull(Type type = null) => AddRawExpressionNode(type ?? typeof(object), null, ExpressionType.Constant); + public int ConstantNull(Type type = null) => Nodes.Add(new(ExpressionType.Constant, type ?? typeof(object), null)); /// Adds an constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantInt(int value) => AddRawExpressionNode(typeof(int), value, ExpressionType.Constant); + public int ConstantInt(int value) => Nodes.Add(new(typeof(int), unchecked((uint)value))); - /// Adds a typed constant node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ConstantOf(T value) => Constant(value, typeof(T)); + public int New(ConstructorInfo ctor) => Nodes.Add(new(ExpressionType.New, ctor.DeclaringType, ctor)); /// Adds a parameterless new node for the specified type. [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public int New(Type type) { if (type.IsValueType) - return AddRawExpressionNode(type, null, ExpressionType.New); + return Nodes.Add(new(ExpressionType.New, type, null)); foreach (var ctor in type.GetConstructors()) if (ctor.GetParameters().Length == 0) @@ -295,8 +281,29 @@ public int New(Type type) } /// Adds a constructor call node. - public int New(System.Reflection.ConstructorInfo constructor, params int[] arguments) => - AddFactoryExpressionNode(constructor.DeclaringType, constructor, ExpressionType.New, arguments); + public int New(ConstructorInfo ctor, params int[] args) + { + var newNode = new ExprNode(ExpressionType.New, ctor.DeclaringType, ctor); + if (args == null || args.Length == 0) + return Nodes.Add(in newNode); + + ushort argIdx = (ushort)args.GetSurePresent(0); + ref var arg = ref Nodes.GetSurePresentRef(argIdx); + argIdx = arg.NextIdx == 0 ? argIdx : (ushort)Nodes.AddCopy(arg); + newNode.SetChild((ushort)args.Length, argIdx); + + if (args.Length > 1) + for (var i = 1; i < args.Length; ++i) + { + argIdx = (ushort)args.GetSurePresent(i); + ref var nextArg = ref Nodes.GetSurePresentRef(argIdx); + arg.NextIdx = nextArg.NextIdx == 0 ? argIdx : (ushort)Nodes.AddCopy(nextArg); + arg = ref nextArg; + } + + // do not forget to set last arg.NextIdx to its parent index to navigate upwards + return arg.NextIdx = (ushort)Nodes.Add(in newNode); + } /// Adds an array initialization node. public int NewArrayInit(Type elementType, params int[] expressions) => @@ -313,35 +320,35 @@ public int Invoke(int expression, params int[] arguments) => : AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, PrependToChildList(expression, arguments)); /// Adds a static-call node. - public int Call(System.Reflection.MethodInfo method, params int[] arguments) => + public int Call(MethodInfo method, params int[] arguments) => AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, arguments); /// Adds an instance-call node. - public int Call(int instance, System.Reflection.MethodInfo method, params int[] arguments) => + public int Call(int instance, MethodInfo method, params int[] arguments) => arguments == null || arguments.Length == 0 ? AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, instance) : AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, PrependToChildList(instance, arguments)); /// Adds a field or property access node. - public int MakeMemberAccess(int? instance, System.Reflection.MemberInfo member) => + public int MakeMemberAccess(int? instance, MemberInfo member) => instance.HasValue ? AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, instance.Value) - : AddRawExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess); + : AddLeafNode(GetMemberType(member), member, ExpressionType.MemberAccess); /// Adds a field-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Field(int instance, System.Reflection.FieldInfo field) => MakeMemberAccess(instance, field); + public int Field(int instance, FieldInfo field) => MakeMemberAccess(instance, field); /// Adds a property-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Property(int instance, System.Reflection.PropertyInfo property) => MakeMemberAccess(instance, property); + public int Property(int instance, PropertyInfo property) => MakeMemberAccess(instance, property); /// Adds a static property-access node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Property(System.Reflection.PropertyInfo property) => MakeMemberAccess(null, property); + public int Property(PropertyInfo property) => MakeMemberAccess(null, property); /// Adds an indexed property-access node. - public int Property(int instance, System.Reflection.PropertyInfo property, params int[] arguments) => + public int Property(int instance, PropertyInfo property, params int[] arguments) => arguments == null || arguments.Length == 0 ? Property(instance, property) : AddFactoryExpressionNode(property.PropertyType, property, ExpressionType.Index, PrependToChildList(instance, arguments)); @@ -358,7 +365,7 @@ public int ArrayAccess(int array, params int[] idxs) => /// Adds a conversion node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Convert(int operand, Type type, System.Reflection.MethodInfo method = null) => + public int Convert(int operand, Type type, MethodInfo method = null) => AddFactoryExpressionNode(type, method, ExpressionType.Convert, operand); /// Adds a type-as node. @@ -368,16 +375,16 @@ public int TypeAs(int operand, Type type) => /// Adds a numeric negation node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Negate(int operand, System.Reflection.MethodInfo method = null) => + public int Negate(int operand, MethodInfo method = null) => MakeUnary(ExpressionType.Negate, operand, method: method); /// Adds a logical or bitwise not node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Not(int operand, System.Reflection.MethodInfo method = null) => + public int Not(int operand, MethodInfo method = null) => MakeUnary(ExpressionType.Not, operand, method: method); /// Adds a unary node of the specified kind. - public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, System.Reflection.MethodInfo method = null) => + public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, MethodInfo method = null) => AddFactoryExpressionNode(type ?? GetUnaryResultType(nodeType, Nodes[operand].Type, method), method, nodeType, operand); /// Adds an assignment node. @@ -386,15 +393,15 @@ public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, Sys /// Adds an addition node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Add(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Add, left, right, method: method); + public int Add(int left, int right, MethodInfo method = null) => MakeBinary(ExpressionType.Add, left, right, method: method); /// Adds an equality node. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Equal(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Equal, left, right, method: method); + public int Equal(int left, int right, MethodInfo method = null) => MakeBinary(ExpressionType.Equal, left, right, method: method); /// Adds a binary node of the specified kind. public int MakeBinary(ExpressionType nodeType, int left, int right, bool isLiftedToNull = false, - System.Reflection.MethodInfo method = null, int? conversion = null, Type type = null) + MethodInfo method = null, int? conversion = null, Type type = null) => conversion.HasValue ? AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), method, nodeType, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, left, right, conversion.Value) @@ -467,19 +474,19 @@ public int Lambda(Type delegateType, int body, params int[] parameters) } /// Adds a member-assignment binding node. - public int Bind(System.Reflection.MemberInfo member, int expression) => + public int Bind(MemberInfo member, int expression) => AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberAssignment, expression); /// Adds a nested member-binding node. - public int MemberBind(System.Reflection.MemberInfo member, params int[] bindings) => + public int MemberBind(MemberInfo member, params int[] bindings) => AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberMemberBinding, bindings); /// Adds an element-initializer node. - public int ElementInit(System.Reflection.MethodInfo addMethod, params int[] arguments) => + public int ElementInit(MethodInfo addMethod, params int[] arguments) => AddFactoryAuxNode(addMethod.DeclaringType, addMethod, ExprNodeKind.ElementInit, arguments); /// Adds a list-binding node. - public int ListBind(System.Reflection.MemberInfo member, params int[] initializers) => + public int ListBind(MemberInfo member, params int[] initializers) => AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberListBinding, initializers); /// Adds a member-init node. @@ -561,7 +568,7 @@ public int Switch(int switchValue, params int[] cases) => Switch(Nodes[switchValue].Type, switchValue, null, null, cases); /// Adds a switch node. - public int Switch(Type type, int switchValue, int? defaultBody, System.Reflection.MethodInfo comparison, params int[] cases) + public int Switch(Type type, int switchValue, int? defaultBody, MethodInfo comparison, params int[] cases) { ChildList children = default; children.Add(switchValue); @@ -709,37 +716,60 @@ public SysExpr ToExpression() => [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); + /// Structurally compares two flat expression trees. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child)); + public bool Equals(ExprTree other) => + new StructuralComparer().Eq(ref this, ref other); + /// Structurally compares this tree with another object. [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)); + public override bool Equals(object obj) => + obj is ExprTree other && Equals(other); + + /// Computes a content-addressable hash for the flat expression tree. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => + new StructuralComparer().Hash(ref this); + + /// Determines whether two flat expression trees are structurally equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(ExprTree left, ExprTree right) => left.Equals(right); + + /// Determines whether two flat expression trees are not structurally equal. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(ExprTree left, ExprTree right) => !left.Equals(right); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, ushort c0) => + Nodes.Add(new(nodeType, type, obj, childIdx: MayBeCloneChild(c0), childCount: 1)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2), MayBeCloneChild(c3)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2), MayBeCloneChild(c3), MayBeCloneChild(c4)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4, int c5) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4), CloneChild(c5)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2), MayBeCloneChild(c3), MayBeCloneChild(c4), MayBeCloneChild(c5)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4, int c5, int c6) => - AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4), CloneChild(c5), CloneChild(c6)); + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, MayBeCloneChild(c0), MayBeCloneChild(c1), MayBeCloneChild(c2), MayBeCloneChild(c3), MayBeCloneChild(c4), MayBeCloneChild(c5), MayBeCloneChild(c6)); private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) { @@ -756,7 +786,7 @@ private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeT } var cloned = CloneChildren(children); - return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); + return Nodes.Add(new(nodeType, type, obj, ExprNodeKind.Expression, 0, in cloned)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -773,10 +803,6 @@ private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeT return AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, in cloned); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType) => - AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, 0, 0, 0); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in children); @@ -789,13 +815,9 @@ private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int child0, int child1, int child2) => AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, child0, child1, child2); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddRawLeafExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags = 0, int childIdx = 0, int childCount = 0) => - AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, flags, childIdx, childCount); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child) => - AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child)); + AddNode(type, obj, ExpressionType.Extension, kind, flags, MayBeCloneChild(child)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int child) => @@ -803,7 +825,7 @@ private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int chil [MethodImpl(MethodImplOptions.AggressiveInlining)] private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child0, int child1) => - AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child0), CloneChild(child1)); + AddNode(type, obj, ExpressionType.Extension, kind, flags, MayBeCloneChild(child0), MayBeCloneChild(child1)); private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int[] children) { @@ -878,14 +900,14 @@ private int AddExpression(SysExpr expression) switch (expression.NodeType) { case ExpressionType.Constant: - return AddConstant((System.Linq.Expressions.ConstantExpression)expression); + return AddConstant((ConstantExpression)expression); case ExpressionType.Default: - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType); + return _tree.AddLeafNode(expression.Type, null, expression.NodeType); case ExpressionType.Parameter: { var parameter = (SysParameterExpression)expression; - return _tree.AddRawLeafExpressionNode(expression.Type, parameter.Name, expression.NodeType, - parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); + return _tree.AddLeafNode(expression.Type, parameter.Name, expression.NodeType, + flags: parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); } case ExpressionType.Lambda: { @@ -893,12 +915,12 @@ private int AddExpression(SysExpr expression) // Body is stored before parameters so that the Reader encounters parameter // refs in the body before their decl nodes (out-of-order decl); identity // is preserved via the shared _parametersById id-map. - var lambda = (System.Linq.Expressions.LambdaExpression)expression; + var lambda = (LambdaExpression)expression; ChildList children = default; children.Add(AddExpression(lambda.Body)); for (var i = 0; i < lambda.Parameters.Count; ++i) children.Add(AddExpression(lambda.Parameters[i])); - var lambdaIdx = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + var lambdaIdx = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children); _tree.LambdaNodes.Add(lambdaIdx); _tree.CollectLambdaClosureParameterUsages(lambdaIdx); return lambdaIdx; @@ -908,7 +930,7 @@ private int AddExpression(SysExpr expression) // With variables: children[0] is the variable list and children[1] is the expression list. // Without variables: children[0] is the expression list. // children.Count == 2 means the block has explicit variables. - var block = (System.Linq.Expressions.BlockExpression)expression; + var block = (BlockExpression)expression; ChildList children = default; var hasVariables = block.Variables.Count != 0; if (hasVariables) @@ -929,7 +951,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.MemberAccess: { - var member = (System.Linq.Expressions.MemberExpression)expression; + var member = (MemberExpression)expression; ChildList children = default; if (member.Expression != null) children.Add(AddExpression(member.Expression)); @@ -938,7 +960,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Call: { - var call = (System.Linq.Expressions.MethodCallExpression)expression; + var call = (MethodCallExpression)expression; ChildList children = default; if (call.Object != null) children.Add(AddExpression(call.Object)); @@ -948,7 +970,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.New: { - var @new = (System.Linq.Expressions.NewExpression)expression; + var @new = (NewExpression)expression; ChildList children = default; for (var i = 0; i < @new.Arguments.Count; ++i) children.Add(AddExpression(@new.Arguments[i])); @@ -957,7 +979,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.NewArrayInit: case ExpressionType.NewArrayBounds: { - var array = (System.Linq.Expressions.NewArrayExpression)expression; + var array = (NewArrayExpression)expression; ChildList children = default; for (var i = 0; i < array.Expressions.Count; ++i) children.Add(AddExpression(array.Expressions[i])); @@ -965,7 +987,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Invoke: { - var invoke = (System.Linq.Expressions.InvocationExpression)expression; + var invoke = (InvocationExpression)expression; ChildList children = default; children.Add(AddExpression(invoke.Expression)); for (var i = 0; i < invoke.Arguments.Count; ++i) @@ -974,7 +996,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Index: { - var indexExpr = (System.Linq.Expressions.IndexExpression)expression; + var indexExpr = (IndexExpression)expression; ChildList children = default; if (indexExpr.Object != null) children.Add(AddExpression(indexExpr.Object)); @@ -984,17 +1006,16 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Conditional: { - var conditional = (System.Linq.Expressions.ConditionalExpression)expression; + var conditional = (ConditionalExpression)expression; ChildList children = default; children.Add(AddExpression(conditional.Test)); children.Add(AddExpression(conditional.IfTrue)); children.Add(AddExpression(conditional.IfFalse)); - return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, - children[0], children[1], children[2]); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children[0], children[1], children[2]); } case ExpressionType.Loop: { - var loop = (System.Linq.Expressions.LoopExpression)expression; + var loop = (LoopExpression)expression; ChildList children = default; children.Add(AddExpression(loop.Body)); if (loop.BreakLabel != null) @@ -1006,7 +1027,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Goto: { - var @goto = (System.Linq.Expressions.GotoExpression)expression; + var @goto = (GotoExpression)expression; ChildList children = default; children.Add(AddLabelTarget(@goto.Target)); if (@goto.Value != null) @@ -1017,7 +1038,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Label: { - var label = (System.Linq.Expressions.LabelExpression)expression; + var label = (LabelExpression)expression; ChildList children = default; children.Add(AddLabelTarget(label.Target)); if (label.DefaultValue != null) @@ -1028,7 +1049,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Switch: { - var @switch = (System.Linq.Expressions.SwitchExpression)expression; + var @switch = (SwitchExpression)expression; ChildList children = default; children.Add(AddExpression(@switch.SwitchValue)); if (@switch.DefaultBody != null) @@ -1044,7 +1065,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Try: { - var @try = (System.Linq.Expressions.TryExpression)expression; + var @try = (TryExpression)expression; ChildList children = default; children.Add(AddExpression(@try.Body)); var flags = (byte)0; @@ -1068,7 +1089,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.MemberInit: { - var memberInit = (System.Linq.Expressions.MemberInitExpression)expression; + var memberInit = (MemberInitExpression)expression; ChildList children = default; children.Add(AddExpression(memberInit.NewExpression)); for (var i = 0; i < memberInit.Bindings.Count; ++i) @@ -1077,7 +1098,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.ListInit: { - var listInit = (System.Linq.Expressions.ListInitExpression)expression; + var listInit = (ListInitExpression)expression; ChildList children = default; children.Add(AddExpression(listInit.NewExpression)); for (var i = 0; i < listInit.Initializers.Count; ++i) @@ -1087,7 +1108,7 @@ private int AddExpression(SysExpr expression) case ExpressionType.TypeIs: case ExpressionType.TypeEqual: { - var typeBinary = (System.Linq.Expressions.TypeBinaryExpression)expression; + var typeBinary = (TypeBinaryExpression)expression; ChildList children = default; children.Add(AddExpression(typeBinary.Expression)); return _tree.AddRawExpressionNode(expression.Type, typeBinary.TypeOperand, expression.NodeType, @@ -1095,7 +1116,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.Dynamic: { - var dynamic = (System.Linq.Expressions.DynamicExpression)expression; + var dynamic = (DynamicExpression)expression; ChildList children = default; children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); for (var i = 0; i < dynamic.Arguments.Count; ++i) @@ -1104,7 +1125,7 @@ private int AddExpression(SysExpr expression) } case ExpressionType.RuntimeVariables: { - var runtime = (System.Linq.Expressions.RuntimeVariablesExpression)expression; + var runtime = (RuntimeVariablesExpression)expression; ChildList children = default; for (var i = 0; i < runtime.Variables.Count; ++i) children.Add(AddExpression(runtime.Variables[i])); @@ -1112,12 +1133,12 @@ private int AddExpression(SysExpr expression) } case ExpressionType.DebugInfo: { - var debug = (System.Linq.Expressions.DebugInfoExpression)expression; + var debug = (DebugInfoExpression)expression; return _tree.AddFactoryExpressionNode(expression.Type, debug.Document.FileName, expression.NodeType, _tree.CreateDebugInfoChildren(debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn)); } default: - if (expression is System.Linq.Expressions.UnaryExpression unary) + if (expression is UnaryExpression unary) { ChildList children = default; children.Add(AddExpression(unary.Operand)); @@ -1125,7 +1146,7 @@ private int AddExpression(SysExpr expression) children); } - if (expression is System.Linq.Expressions.BinaryExpression binary) + if (expression is BinaryExpression binary) { ChildList children = default; children.Add(AddExpression(binary.Left)); @@ -1140,7 +1161,7 @@ private int AddExpression(SysExpr expression) } } - private int AddConstant(System.Linq.Expressions.ConstantExpression constant) => + private int AddConstant(ConstantExpression constant) => _tree.Constant(constant.Value, constant.Type); private int AddSwitchCase(SysSwitchCase switchCase) @@ -1173,12 +1194,12 @@ private int AddMemberBinding(SysMemberBinding binding) { case MemberBindingType.Assignment: ChildList assignmentChildren = default; - assignmentChildren.Add(AddExpression(((System.Linq.Expressions.MemberAssignment)binding).Expression)); + assignmentChildren.Add(AddExpression(((MemberAssignment)binding).Expression)); return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, assignmentChildren); case MemberBindingType.MemberBinding: { - var memberBinding = (System.Linq.Expressions.MemberMemberBinding)binding; + var memberBinding = (MemberMemberBinding)binding; ChildList children = default; for (var i = 0; i < memberBinding.Bindings.Count; ++i) children.Add(AddMemberBinding(memberBinding.Bindings[i])); @@ -1186,7 +1207,7 @@ private int AddMemberBinding(SysMemberBinding binding) } case MemberBindingType.ListBinding: { - var listBinding = (System.Linq.Expressions.MemberListBinding)binding; + var listBinding = (MemberListBinding)binding; ChildList children = default; for (var i = 0; i < listBinding.Initializers.Count; ++i) children.Add(AddElementInit(listBinding.Initializers[i])); @@ -1213,31 +1234,21 @@ private static int GetId(ref SmallMap16> ids, object return id; } - private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch + private static Type GetMemberType(MemberInfo member) => member switch { - System.Reflection.FieldInfo field => field.FieldType, - System.Reflection.PropertyInfo property => property.PropertyType, + FieldInfo field => field.FieldType, + PropertyInfo property => property.PropertyType, _ => typeof(object) }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddLeafNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, 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; - } + private int AddLeafNode(Type type, object obj, ExpressionType nodeType, + ExprNodeKind kind = ExprNodeKind.Expression, byte flags = default, ushort childIdx = default, ushort childCount = default) => + Nodes.Add(new(nodeType, type, obj, kind, flags, childIdx, childCount)); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int AddInlineConstantNode(Type type, uint inlineValue) - { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, inlineValue); - return nodeIdx; - } + private int AddInlineConstantNode(Type type, uint inlineValue) => Nodes.Add(new(type, inlineValue)); private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags) { @@ -1247,84 +1258,79 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int child0) - { - var nodeIdx = Nodes.Count; - ref var newNode = ref Nodes.AddDefaultAndGetRef(); - newNode = new ExprNode(type, obj, nodeType, kind, flags, child0, 1); - return nodeIdx; - } + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0) => + Nodes.Add(new(nodeType, type, obj, kind, flags, c0, 1)); - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 2); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); + Nodes.GetSurePresentRef(c0).NextIdx = c1; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 3); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 4); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); - Nodes.GetSurePresentRef(c2).SetNextIdx(c3); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; + Nodes.GetSurePresentRef(c2).NextIdx = c3; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3, ushort c4) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 5); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); - Nodes.GetSurePresentRef(c2).SetNextIdx(c3); - Nodes.GetSurePresentRef(c3).SetNextIdx(c4); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; + Nodes.GetSurePresentRef(c2).NextIdx = c3; + Nodes.GetSurePresentRef(c3).NextIdx = c4; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4, int c5) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3, ushort c4, ushort c5) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 6); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); - Nodes.GetSurePresentRef(c2).SetNextIdx(c3); - Nodes.GetSurePresentRef(c3).SetNextIdx(c4); - Nodes.GetSurePresentRef(c4).SetNextIdx(c5); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; + Nodes.GetSurePresentRef(c2).NextIdx = c3; + Nodes.GetSurePresentRef(c3).NextIdx = c4; + Nodes.GetSurePresentRef(c4).NextIdx = c5; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4, int c5, int c6) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort c0, ushort c1, ushort c2, ushort c3, ushort c4, ushort c5, ushort c6) { var nodeIdx = Nodes.Count; ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 7); - Nodes.GetSurePresentRef(c0).SetNextIdx(c1); - Nodes.GetSurePresentRef(c1).SetNextIdx(c2); - Nodes.GetSurePresentRef(c2).SetNextIdx(c3); - Nodes.GetSurePresentRef(c3).SetNextIdx(c4); - Nodes.GetSurePresentRef(c4).SetNextIdx(c5); - Nodes.GetSurePresentRef(c5).SetNextIdx(c6); + Nodes.GetSurePresentRef(c0).NextIdx = c1; + Nodes.GetSurePresentRef(c1).NextIdx = c2; + Nodes.GetSurePresentRef(c2).NextIdx = c3; + Nodes.GetSurePresentRef(c3).NextIdx = c4; + Nodes.GetSurePresentRef(c4).NextIdx = c5; + Nodes.GetSurePresentRef(c5).NextIdx = c6; return nodeIdx; } - private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int[] children) + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, ushort[] children) { if (children == null || children.Length == 0) return AddNode(type, obj, nodeType, kind, flags); @@ -1333,7 +1339,7 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, children[0], children.Length); for (var i = 1; i < children.Length; ++i) - Nodes.GetSurePresentRef(children[i - 1]).SetNextIdx(children[i]); + Nodes.GetSurePresentRef(children[i - 1]).NextIdx = children[i]; return nodeIdx; } @@ -1346,12 +1352,12 @@ private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind ref var newNode = ref Nodes.AddDefaultAndGetRef(); newNode = new ExprNode(type, obj, nodeType, kind, flags, children[0], children.Count); for (var i = 1; i < children.Count; ++i) - Nodes.GetSurePresentRef(children[i - 1]).SetNextIdx(children[i]); + Nodes.GetSurePresentRef(children[i - 1]).NextIdx = children[i]; return nodeIdx; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsSmallPrimitive(TypeCode tc) => + private static bool In32BitRange(TypeCode tc) => tc == TypeCode.Boolean || tc == TypeCode.Byte || tc == TypeCode.SByte || tc == TypeCode.Char || tc == TypeCode.Int16 || tc == TypeCode.UInt16 || tc == TypeCode.Int32 || tc == TypeCode.UInt32 || tc == TypeCode.Single; @@ -1371,21 +1377,21 @@ private static bool IsSmallPrimitive(TypeCode tc) => _ => FlatExpressionThrow.UnsupportedInlineConstantType(value, tc) }; - private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch + private static Type GetMemberType(MemberInfo member) => member switch { - System.Reflection.FieldInfo field => field.FieldType, - System.Reflection.PropertyInfo property => property.PropertyType, + FieldInfo field => field.FieldType, + PropertyInfo property => property.PropertyType, _ => typeof(object) }; - private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType, System.Reflection.MethodInfo method) => + private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType, MethodInfo method) => nodeType switch { ExpressionType.IsFalse or ExpressionType.IsTrue or ExpressionType.TypeIs or ExpressionType.TypeEqual => typeof(bool), _ => method?.ReturnType ?? operandType }; - private static Type GetBinaryResultType(ExpressionType nodeType, Type leftType, Type rightType, System.Reflection.MethodInfo method) + private static Type GetBinaryResultType(ExpressionType nodeType, Type leftType, Type rightType, MethodInfo method) { if (method != null) return method.ReturnType; @@ -1408,35 +1414,6 @@ private static Type GetArrayElementType(Type arrayType, int depth) return elementType ?? typeof(object); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int CloneChild(int idx) - { - ref var node = ref Nodes[idx]; - if (!node.ShouldCloneWhenLinked()) return idx; - if (ReferenceEquals(node.Obj, ExprNode.InlineValueMarker)) - return AddInlineConstantNode(node.Type, node.InlineValue); - return AddLeafNode(node.Type, node.Obj, node.NodeType, node.Kind, node.Flags, node.ChildIdx, node.ChildCount); - } - - private ChildList CloneChildren(int[] children) - { - ChildList cloned = default; - if (children == null) - return cloned; - - for (var i = 0; i < children.Length; ++i) - cloned.Add(CloneChild(children[i])); - return cloned; - } - - private ChildList CloneChildren(in ChildList children) - { - ChildList cloned = default; - for (var i = 0; i < children.Count; ++i) - cloned.Add(CloneChild(children[i])); - return cloned; - } - private void CollectLambdaClosureParameterUsages(int lambdaIdx) { var children = GetChildren(lambdaIdx); @@ -1447,7 +1424,7 @@ private void CollectLambdaClosureParameterUsages(int lambdaIdx) for (var i = 1; i < children.Count; ++i) lambdaParameterIds.Add(ToStoredUShortIdx(Nodes[children[i]].ChildIdx)); - SmallList, NoArrayPool> localParameterIds = default; + ChildList localParameterIds = default; SmallList, NoArrayPool> captures = default; CollectClosureParameterUsages(children[0], ToStoredUShortIdx(lambdaIdx), ref lambdaParameterIds, ref localParameterIds, ref captures); @@ -1459,7 +1436,7 @@ private void CollectClosureParameterUsages( int idx, ushort lambdaIdx, ref SmallList, NoArrayPool> lambdaParameterIds, - ref SmallList, NoArrayPool> localParameterIds, + ref ChildList localParameterIds, ref SmallList, NoArrayPool> captures) { ref var node = ref Nodes.GetSurePresentRef(idx); @@ -1527,7 +1504,7 @@ private void CollectCatchBlockClosureParameterUsages( int idx, ushort lambdaIdx, ref SmallList, NoArrayPool> lambdaParameterIds, - ref SmallList, NoArrayPool> localParameterIds, + ref ChildList localParameterIds, ref SmallList, NoArrayPool> captures) { ref var node = ref Nodes.GetSurePresentRef(idx); @@ -1550,7 +1527,7 @@ private void PropagateNestedLambdaClosureParameterUsages( ushort nestedLambdaIdx, ushort lambdaIdx, ref SmallList, NoArrayPool> lambdaParameterIds, - ref SmallList, NoArrayPool> localParameterIds, + ref ChildList localParameterIds, ref SmallList, NoArrayPool> captures) { for (var i = 0; i < LambdaClosureParameterUsages.Count; ++i) @@ -1606,6 +1583,429 @@ private static bool Contains(ref SmallList [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ushort ToStoredUShortIdx(int idx) => checked((ushort)idx); + private struct StructuralComparer + { + private ChildList _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: + if (x.ChildCount == 0) + return false; + + restoreXParameterCount = _xParameterIds.Count; + restoreYParameterCount = _yParameterIds.Count; + descendX = x.ChildIdx; + descendY = y.ChildIdx; + descendChildCount = 1; + if (x.ChildCount == 2) + { + ref var xVariables = ref xTree.Nodes.GetSurePresentRef(descendX); + ref var yVariables = ref yTree.Nodes.GetSurePresentRef(descendY); + if (xVariables.Kind != ExprNodeKind.ChildList || yVariables.Kind != ExprNodeKind.ChildList || + xVariables.ChildCount != yVariables.ChildCount) + return false; + + var xVariableIdx = xVariables.ChildIdx; + var yVariableIdx = yVariables.ChildIdx; + for (var i = 0; i < xVariables.ChildCount; ++i) + { + ref var xv = ref xTree.Nodes.GetSurePresentRef(xVariableIdx); + ref var yv = ref yTree.Nodes.GetSurePresentRef(yVariableIdx); + if (!AreEquivalentParameterDeclarations(ref xv, ref yv)) + return false; + _xParameterIds.Add(ToStoredUShortIdx(xv.ChildIdx)); + _yParameterIds.Add(ToStoredUShortIdx(yv.ChildIdx)); + xVariableIdx = xv.NextIdx; + yVariableIdx = yv.NextIdx; + } + + descendX = xVariables.NextIdx; + descendY = yVariables.NextIdx; + } + break; + + 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) + { + var scopeCount = _xParameterIds.Count; + ref var node = ref tree.Nodes.GetSurePresentRef(idx); + var bodyListIdx = node.ChildIdx; + if (node.ChildCount == 2) + { + ref var variables = ref tree.Nodes.GetSurePresentRef(bodyListIdx); + var variableIdx = variables.ChildIdx; + for (var i = 0; i < variables.ChildCount; ++i) + { + ref var variable = ref tree.Nodes.GetSurePresentRef(variableIdx); + _xParameterIds.Add(ToStoredUShortIdx(variable.ChildIdx)); + h = Combine(h, Combine(variable.Type?.GetHashCode() ?? 0, variable.HasFlag(ParameterByRefFlag) ? 1 : 0)); + variableIdx = variable.NextIdx; + } + bodyListIdx = variables.NextIdx; + } + + 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 { @@ -1624,7 +2024,7 @@ public Reader(ExprTree tree) public SysExpr ReadExpression(int idx) { ref var node = ref _tree.Nodes[idx]; - if (!node.IsExpression()) + if (node.Kind != ExprNodeKind.Expression) throw new InvalidOperationException($"Node at idx {idx} is not an expression node."); switch (node.NodeType) @@ -1681,11 +2081,11 @@ public SysExpr ReadExpression(int idx) case ExpressionType.MemberAccess: { var children = GetChildren(idx); - return SysExpr.MakeMemberAccess(children.Count != 0 ? ReadExpression(children[0]) : null, (System.Reflection.MemberInfo)node.Obj); + return SysExpr.MakeMemberAccess(children.Count != 0 ? ReadExpression(children[0]) : null, (MemberInfo)node.Obj); } case ExpressionType.Call: { - var method = (System.Reflection.MethodInfo)node.Obj; + var method = (MethodInfo)node.Obj; var children = GetChildren(idx); var hasInstance = !method.IsStatic; var instance = hasInstance ? ReadExpression(children[0]) : null; @@ -1698,7 +2098,7 @@ public SysExpr ReadExpression(int idx) { var children = GetChildren(idx); var arguments = ReadExpressions(children); - return node.Obj is System.Reflection.ConstructorInfo ctor + return node.Obj is ConstructorInfo ctor ? SysExpr.New(ctor, arguments) : CreateValueTypeNewExpression(node.Type); } @@ -1717,7 +2117,7 @@ public SysExpr ReadExpression(int idx) case ExpressionType.Index: { var children = GetChildren(idx); - var property = (System.Reflection.PropertyInfo)node.Obj; + var property = (PropertyInfo)node.Obj; var hasInstance = property != null || children.Count > 1; var instance = hasInstance ? ReadExpression(children[0]) : null; var arguments = new SysExpr[children.Count - (hasInstance ? 1 : 0)]; @@ -1772,7 +2172,7 @@ public SysExpr ReadExpression(int idx) var cases = new SysSwitchCase[caseIdxs.Count]; for (var i = 0; i < cases.Length; ++i) cases[i] = ReadSwitchCase(caseIdxs[i]); - return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, (System.Reflection.MethodInfo)node.Obj, cases); + return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, (MethodInfo)node.Obj, cases); } case ExpressionType.Try: { @@ -1803,7 +2203,7 @@ public SysExpr ReadExpression(int idx) var bindings = new SysMemberBinding[children.Count - 1]; for (var i = 1; i < children.Count; ++i) bindings[i - 1] = ReadMemberBinding(children[i]); - return SysExpr.MemberInit((System.Linq.Expressions.NewExpression)ReadExpression(children[0]), bindings); + return SysExpr.MemberInit((NewExpression)ReadExpression(children[0]), bindings); } case ExpressionType.ListInit: { @@ -1811,7 +2211,7 @@ public SysExpr ReadExpression(int idx) var initializers = new SysElementInit[children.Count - 1]; for (var i = 1; i < children.Count; ++i) initializers[i - 1] = ReadElementInit(children[i]); - return SysExpr.ListInit((System.Linq.Expressions.NewExpression)ReadExpression(children[0]), initializers); + return SysExpr.ListInit((NewExpression)ReadExpression(children[0]), initializers); } case ExpressionType.TypeIs: return SysExpr.TypeIs(ReadExpression(GetChildren(idx)[0]), (Type)node.Obj); @@ -1844,16 +2244,16 @@ public SysExpr ReadExpression(int idx) default: if (node.ChildCount == 1) { - var method = node.Obj as System.Reflection.MethodInfo; + var method = node.Obj as MethodInfo; return SysExpr.MakeUnary(node.NodeType, ReadExpression(GetChildren(idx)[0]), node.Type, method); } if (node.ChildCount >= 2) { var children = GetChildren(idx); - var conversion = children.Count > 2 ? (System.Linq.Expressions.LambdaExpression)ReadExpression(children[2]) : null; + var conversion = children.Count > 2 ? (LambdaExpression)ReadExpression(children[2]) : null; return SysExpr.MakeBinary(node.NodeType, ReadExpression(children[0]), ReadExpression(children[1]), - node.HasFlag(BinaryLiftedToNullFlag), (System.Reflection.MethodInfo)node.Obj, conversion); + node.HasFlag(BinaryLiftedToNullFlag), (MethodInfo)node.Obj, conversion); } throw new NotSupportedException($"Reconstruction of `ExpressionType.{node.NodeType}` is not supported yet."); @@ -1915,7 +2315,7 @@ private void ReadUInt16Pair(int idx, out int first, out int second) private SysMemberBinding ReadMemberBinding(int idx) { ref var node = ref _tree.Nodes[idx]; - var member = (System.Reflection.MemberInfo)node.Obj; + var member = (MemberInfo)node.Obj; switch (node.Kind) { case ExprNodeKind.MemberAssignment: @@ -1946,7 +2346,7 @@ private SysElementInit ReadElementInit(int idx) { ref var node = ref _tree.Nodes[idx]; Debug.Assert(node.Is(ExprNodeKind.ElementInit)); - return SysExpr.ElementInit((System.Reflection.MethodInfo)node.Obj, ReadExpressions(GetChildren(idx))); + return SysExpr.ElementInit((MethodInfo)node.Obj, ReadExpressions(GetChildren(idx))); } private ChildList GetChildren(int idx) @@ -2005,7 +2405,7 @@ private SysExpr[] ReadExpressions(in ChildList childIdxs) [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] - private static System.Linq.Expressions.NewExpression CreateValueTypeNewExpression(Type type) => SysExpr.New(type); + private static NewExpression CreateValueTypeNewExpression(Type type) => SysExpr.New(type); } } diff --git a/src/FastExpressionCompiler/ImTools.cs b/src/FastExpressionCompiler/ImTools.cs index 99e76db9..a84fa62e 100644 --- a/src/FastExpressionCompiler/ImTools.cs +++ b/src/FastExpressionCompiler/ImTools.cs @@ -851,6 +851,10 @@ public int Add(in T item) return index; } + /// Adds the item copy to the end of the list aka the Stack.Push. Returns the index of the added item. + [MethodImpl((MethodImplOptions)256)] + public int AddCopy(T item) => Add(in item); + /// Looks for the item in the list and return its index if found or -1 for the absent item [MethodImpl((MethodImplOptions)256)] public int TryGetIndex(in T item, TEq eq = default) where TEq : struct, IEq diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 3e3ff694..df384290 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -11,7 +11,6 @@ namespace FastExpressionCompiler.LightExpression.UnitTests { - public partial class LightExpressionTests : ITest { public int Run() @@ -55,10 +54,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() { var funcExpr = Lambda( @@ -1023,5 +1025,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); + } } }