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..edaed427 100644
--- a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
+++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
@@ -22,7 +22,7 @@ namespace FastExpressionCompiler.FlatExpression;
public enum ExprNodeKind : byte
{
/// Represents a regular expression node.
- Expression,
+ Expression = 0,
/// Represents a switch case payload.
SwitchCase,
/// Represents a catch block payload.
@@ -61,6 +61,7 @@ public struct ExprNode
private const uint MetaKeepWithoutNext = 0xFFFF0000u;
// _data layout: bits [31:16]=ChildCount | [15:0]=ChildIdx (or full uint for inline constants)
private const int DataCountShift = 16;
+ private const uint DataKeepWithoutChildIdx = 0xFFFF0000u;
private const uint DataIdxMask = 0xFFFFu;
private const int FlagsShift = 4;
private const uint KindMask = 0x0Fu;
@@ -106,6 +107,7 @@ public struct ExprNode
internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0, int nextIdx = 0)
{
+ Debug.Assert(!RequiresInlineConstantStorage(type, obj, nodeType));
Type = type;
Obj = obj;
var tag = (byte)((flags << FlagsShift) | (byte)kind);
@@ -137,7 +139,41 @@ internal void SetChildInfo(int childIdx, int childCount) =>
internal bool IsExpression() => Kind == ExprNodeKind.Expression;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal bool HasFlag(byte flag) => (Flags & flag) != 0;
+ 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)]
+ 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;
+
+ [Flags]
+ private enum NodeFlags : byte { None = 0 }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool HasFlag(byte flag) =>
+#if NET6_0_OR_GREATER
+ ((NodeFlags)Flags).HasFlag((NodeFlags)flag);
+#else
+ (Flags & flag) != 0;
+#endif
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool HasSameHeaderExceptNext(ref ExprNode other) =>
+ Type == other.Type && (_meta & MetaKeepWithoutNext) == (other._meta & MetaKeepWithoutNext);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool HasSameShapeExceptLinks(ref ExprNode other) =>
+ HasSameHeaderExceptNext(ref other) &&
+ (_data & DataKeepWithoutChildIdx) == (other._data & DataKeepWithoutChildIdx);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal bool HasSameShapeExceptNext(ref ExprNode other) =>
+ HasSameHeaderExceptNext(ref other) && _data == other._data;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool ShouldCloneWhenLinked() =>
@@ -173,7 +209,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 +252,8 @@ 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);
- }
+ public int Parameter(Type type, string name = null) =>
+ AddLeafNode(type, name, ExpressionType.Parameter, flags: type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: Nodes.Count + 1);
/// Adds a typed parameter node and returns its idx.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -232,7 +265,7 @@ public int Parameter(Type type, string name = null)
/// Adds a default-value node and returns its idx.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int Default(Type type) => AddRawExpressionNode(type, null, ExpressionType.Default);
+ public int Default(Type type) => AddLeafNode(type, null, ExpressionType.Default);
/// Adds a constant node using the runtime type of the supplied value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -243,38 +276,32 @@ public int Constant(object value) =>
public int Constant(object value, Type type)
{
if (value == null || value is string || value is Type || value is decimal)
- return AddRawExpressionNode(type, value, ExpressionType.Constant);
+ return AddLeafNode(type, value, ExpressionType.Constant);
if (type.IsEnum)
- {
- var underlyingTc = Type.GetTypeCode(Enum.GetUnderlyingType(type));
- if (IsSmallPrimitive(underlyingTc))
- return AddInlineConstantNode(type, (uint)System.Convert.ToInt64(value));
- // long/ulong-backed enum (extremely rare): store boxed in Obj
- return AddRawExpressionNode(type, value, ExpressionType.Constant);
- }
+ return IsSmallPrimitive(Type.GetTypeCode(Enum.GetUnderlyingType(type)))
+ ? AddInlineConstantNode(type, (uint)System.Convert.ToInt64(value))
+ : AddLeafNode(type, value, ExpressionType.Constant); // long/ulong-backed enum (extremely rare): store boxed in Obj
if (type.IsPrimitive)
{
var tc = Type.GetTypeCode(type);
- if (IsSmallPrimitive(tc))
- return AddInlineConstantNode(type, ToInlineValue(value, tc));
- // long, ulong, double: primitive but too wide for _data, store boxed in Obj
- return AddRawExpressionNode(type, value, ExpressionType.Constant);
+ return IsSmallPrimitive(tc)
+ ? AddInlineConstantNode(type, ToInlineValue(value, tc))
+ : AddLeafNode(type, value, ExpressionType.Constant); // long, ulong, double: primitive but too wide for _data, store boxed in Obj
}
// Delegate, array types, and user-defined reference/value types go to ClosureConstants
- var constantIdx = ClosureConstants.Add(value);
- return AddRawLeafExpressionNode(type, ClosureConstantMarker, ExpressionType.Constant, childIdx: constantIdx);
+ return AddLeafNode(type, ClosureConstantMarker, ExpressionType.Constant, childIdx: ClosureConstants.Add(value));
}
/// Adds a null constant node.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int ConstantNull(Type type = null) => AddRawExpressionNode(type ?? typeof(object), null, ExpressionType.Constant);
+ public int ConstantNull(Type type = null) => AddLeafNode(type ?? typeof(object), null, ExpressionType.Constant);
/// Adds an constant node.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int ConstantInt(int value) => AddRawExpressionNode(typeof(int), value, ExpressionType.Constant);
+ public int ConstantInt(int value) => AddInlineConstantNode(typeof(int), unchecked((uint)value));
/// Adds a typed constant node.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -285,7 +312,7 @@ public int Constant(object value, Type type)
public int New(Type type)
{
if (type.IsValueType)
- return AddRawExpressionNode(type, null, ExpressionType.New);
+ return AddLeafNode(type, null, ExpressionType.New);
foreach (var ctor in type.GetConstructors())
if (ctor.GetParameters().Length == 0)
@@ -326,7 +353,7 @@ public int Call(int instance, System.Reflection.MethodInfo method, params int[]
public int MakeMemberAccess(int? instance, System.Reflection.MemberInfo member) =>
instance.HasValue
? AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, instance.Value)
- : AddRawExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess);
+ : AddLeafNode(GetMemberType(member), member, ExpressionType.MemberAccess);
/// Adds a field-access node.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -709,6 +736,29 @@ public SysExpr ToExpression() =>
[RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)]
public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression());
+ /// Structurally compares two flat expression trees.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(ExprTree other) =>
+ new StructuralComparer().Eq(ref this, ref other);
+
+ /// Structurally compares this tree with another object.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override bool Equals(object obj) =>
+ obj is ExprTree other && Equals(other);
+
+ /// Computes a content-addressable hash for the flat expression tree.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() =>
+ new StructuralComparer().Hash(ref this);
+
+ /// Determines whether two flat expression trees are structurally equal.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator ==(ExprTree left, ExprTree right) => left.Equals(right);
+
+ /// Determines whether two flat expression trees are not structurally equal.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator !=(ExprTree left, ExprTree right) => !left.Equals(right);
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) =>
AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child));
@@ -773,10 +823,6 @@ private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeT
return AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, in cloned);
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType) =>
- AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, 0, 0, 0);
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) =>
AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in children);
@@ -789,10 +835,6 @@ private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType,
private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int child0, int child1, int child2) =>
AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, child0, child1, child2);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private int AddRawLeafExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags = 0, int childIdx = 0, int childCount = 0) =>
- AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, flags, childIdx, childCount);
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child) =>
AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child));
@@ -880,12 +922,12 @@ private int AddExpression(SysExpr expression)
case ExpressionType.Constant:
return AddConstant((System.Linq.Expressions.ConstantExpression)expression);
case ExpressionType.Default:
- return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType);
+ return _tree.AddLeafNode(expression.Type, null, expression.NodeType);
case ExpressionType.Parameter:
{
var parameter = (SysParameterExpression)expression;
- return _tree.AddRawLeafExpressionNode(expression.Type, parameter.Name, expression.NodeType,
- parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter));
+ return _tree.AddLeafNode(expression.Type, parameter.Name, expression.NodeType,
+ flags: parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter));
}
case ExpressionType.Lambda:
{
@@ -898,7 +940,7 @@ private int AddExpression(SysExpr expression)
children.Add(AddExpression(lambda.Body));
for (var i = 0; i < lambda.Parameters.Count; ++i)
children.Add(AddExpression(lambda.Parameters[i]));
- var lambdaIdx = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children);
+ var lambdaIdx = _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children);
_tree.LambdaNodes.Add(lambdaIdx);
_tree.CollectLambdaClosureParameterUsages(lambdaIdx);
return lambdaIdx;
@@ -989,8 +1031,7 @@ private int AddExpression(SysExpr expression)
children.Add(AddExpression(conditional.Test));
children.Add(AddExpression(conditional.IfTrue));
children.Add(AddExpression(conditional.IfFalse));
- return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType,
- children[0], children[1], children[2]);
+ return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children[0], children[1], children[2]);
}
case ExpressionType.Loop:
{
@@ -1222,7 +1263,8 @@ private static int GetId(ref SmallMap16