diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 791ade83..7c2348c1 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -32,19 +32,12 @@ jobs:
- name: Build
run: dotnet build -c:Release
- - name: Tests - net10.0 (Latest)
+ - name: Tests - net10.0
run: dotnet run --no-build -c:Release -f:net10.0 --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj
- - name: Tests - net9.0 (Latest)
- run: dotnet run --no-build -c:Release -f:net9.0 --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj
-
- - name: Tests - net8.0 (Latest LTS)
+ - name: Tests - net8.0
run: dotnet run --no-build -c:Release -f:net8.0 --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj
- # - if: matrix.os == 'windows-latest'
- # name: Tests - net6.0 (Previous LTS)
- # run: dotnet run --no-build -c:Release -f:net6.0 --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj
-
- if: matrix.os == 'windows-latest'
name: Tests - net472 (Windows only)
run: dotnet run --no-build -c:Release --project test/FastExpressionCompiler.TestsRunner.Net472
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 8552cb5e..d269d61c 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -10,7 +10,7 @@
"request": "launch",
"preLaunchTask": "build_testsrunner",
// If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/test/FastExpressionCompiler.TestsRunner/bin/Debug/net9.0/FastExpressionCompiler.TestsRunner.dll",
+ "program": "${workspaceFolder}/test/FastExpressionCompiler.TestsRunner/bin/Debug/net10.0/FastExpressionCompiler.TestsRunner.dll",
"args": [],
"cwd": "${workspaceFolder}/test/FastExpressionCompiler.TestsRunner",
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
@@ -26,7 +26,7 @@
"request": "launch",
"preLaunchTask": "build_benchmarks",
// If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/test/FastExpressionCompiler.Benchmarks/bin/Debug/net9.0/FastExpressionCompiler.Benchmarks.exe",
+ "program": "${workspaceFolder}/test/FastExpressionCompiler.Benchmarks/bin/Debug/net10.0/FastExpressionCompiler.Benchmarks.exe",
"args": [],
"cwd": "${workspaceFolder}/test/FastExpressionCompiler.Benchmarks",
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
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/Directory.Build.props b/Directory.Build.props
index 591b8919..6e8cc903 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -32,7 +32,7 @@
-
+
diff --git a/b.bat b/b.bat
index 1517d9dc..379b7958 100644
--- a/b.bat
+++ b/b.bat
@@ -10,10 +10,7 @@ echo:
echo:## Starting TESTS...
echo:
-dotnet run --no-build -p:DevMode=true -f:net9.0 -c:Release --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj
-if %ERRORLEVEL% neq 0 goto :error
-
-dotnet run --no-build -p:DevMode=true -c:Release --project test/FastExpressionCompiler.TestsRunner.Net472
+dotnet run --no-build -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/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/btdebug.bat b/btd.bat
similarity index 65%
rename from btdebug.bat
rename to btd.bat
index f2de6b70..bef60162 100644
--- a/btdebug.bat
+++ b/btd.bat
@@ -3,7 +3,7 @@
echo:
echo:## Running TESTS on the Latest Supported .NET...
echo:
-dotnet run -p:DevMode=true -f:net9.0 -c:Debug --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj
+dotnet run -p:DevMode=true -f:net10.0 -c:Debug --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj
if %ERRORLEVEL% neq 0 goto :error
echo:
diff --git a/build.bat b/build.bat
index a46b739f..94ce8330 100644
--- a/build.bat
+++ b/build.bat
@@ -16,25 +16,15 @@ echo:## Finished: RESTORE and BUILD
echo:
echo:## Starting: TESTS...
echo:
-echo:running on .NET 10.0 (Latest)
+echo:running on .NET 10.0
dotnet run --no-build -f:net10.0 -c:Release --project test/FastExpressionCompiler.TestsRunner
if %ERRORLEVEL% neq 0 goto :error
echo:
-echo:running on .NET 9.0 (Latest)
-dotnet run --no-build -f:net9.0 -c:Release --project test/FastExpressionCompiler.TestsRunner
-if %ERRORLEVEL% neq 0 goto :error
-
-echo:
-echo:running on .NET 8.0 (LTS)
+echo:running on .NET 8.0
dotnet run --no-build -f:net8.0 -c:Release --project test/FastExpressionCompiler.TestsRunner
if %ERRORLEVEL% neq 0 goto :error
-echo:
-echo:running on .NET 6.0 (Previous LTS)
-dotnet run --no-build -f:net6.0 -c:Release --project test/FastExpressionCompiler.TestsRunner
-if %ERRORLEVEL% neq 0 goto :error
-
echo:
echo:running on .NET 4.7.2
dotnet run --no-build -c:Release --project test/FastExpressionCompiler.TestsRunner.Net472
diff --git a/build_debug.bat b/build_debug.bat
index b8b8db99..3ca66ac1 100644
--- a/build_debug.bat
+++ b/build_debug.bat
@@ -16,8 +16,8 @@ echo:## Finished: RESTORE and BUILD
echo:
echo:## Starting: TESTS...
echo:
-echo:running on .NET 9.0 (Latest)
-dotnet run --no-build -f:net9.0 -c:Debug --project test/FastExpressionCompiler.TestsRunner
+echo:running on .NET 10.0
+dotnet run --no-build -f:net10.0 -c:Debug --project test/FastExpressionCompiler.TestsRunner
if %ERRORLEVEL% neq 0 goto :error
echo:
@@ -25,11 +25,6 @@ echo:running on .NET 8.0 (LTS)
dotnet run --no-build -f:net8.0 -c:Debug --project test/FastExpressionCompiler.TestsRunner
if %ERRORLEVEL% neq 0 goto :error
-echo:
-echo:running on .NET 6.0 (Previous LTS)
-dotnet run --no-build -f:net6.0 -c:Debug --project test/FastExpressionCompiler.TestsRunner
-if %ERRORLEVEL% neq 0 goto :error
-
echo:
echo:running on .NET 4.7.2
dotnet run --no-build -c:Debug --project test/FastExpressionCompiler.TestsRunner.Net472
diff --git a/src/FastExpressionCompiler.LightExpression/FastExpressionCompiler.LightExpression.csproj b/src/FastExpressionCompiler.LightExpression/FastExpressionCompiler.LightExpression.csproj
index c4b55d69..e4e954d1 100644
--- a/src/FastExpressionCompiler.LightExpression/FastExpressionCompiler.LightExpression.csproj
+++ b/src/FastExpressionCompiler.LightExpression/FastExpressionCompiler.LightExpression.csproj
@@ -1,7 +1,7 @@
- net472;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0;net10.0
- net472;net9.0;net10.0
+ net472;netstandard2.0;netstandard2.1;net8.0;net10.0
+ net472;net10.0
5.4.1
diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs
index ad07b3fe..214ea38a 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,105 @@ 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);
/// 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)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void Set(ref ExprNode n, ExpressionType nodeType, Type type, object obj = null)
{
- 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);
+ n.Type = type;
+ n.Obj = obj;
+ n._nodeType = (byte)nodeType;
}
- /// Constructs an inline primitive constant node; is set to .
- internal ExprNode(Type type, uint inlineValue)
+ /// Sets the child-link metadata for the node.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void SetChildrenInfo(ushort childCount, ushort childIdx) => _child = ((uint)childCount << ChildCountShift) | childIdx;
+
+ /// Sets the child-link metadata for the node.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void SetChildrenInfo(ref ExprNode n, ushort childCount, ushort childIdx) =>
+ n._child = ((uint)childCount << ChildCountShift) | childIdx;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static void Set(ref ExprNode n, ExpressionType nodeType, Type type, object obj, byte flags, ExprNodeKind kind,
+ ushort childIdx = 0, ushort childCount = 0, ushort nextIdx = 0)
{
- Type = type;
- Obj = InlineValueMarker;
- _meta = (uint)(byte)ExpressionType.Constant << MetaNodeTypeShift;
- _data = inlineValue;
+ n.Type = type;
+ n._nodeType = (byte)nodeType;
+ n.Obj = obj;
+ n._child = ((uint)childCount << ChildCountShift) | childIdx;
+ n.FlagsAndKind = (byte)((flags << 4) | ((byte)kind & 0b1111));
+ n.NextIdx = nextIdx;
}
+ /// Constructs an inline primitive constant node, is set to .
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void SetNextIdx(int nextIdx) =>
- _meta = (_meta & MetaKeepWithoutNext) | checked((ushort)nextIdx);
+ internal static void Set(ref ExprNode n, Type type, uint inlineValue)
+ {
+ n.Type = type;
+ n.Obj = InlineValueMarker;
+ n._nodeType = (byte)ExpressionType.Constant;
+ n._child = inlineValue;
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void SetChildInfo(int childIdx, int childCount) =>
- _data = ((uint)checked((ushort)childCount) << DataCountShift) | checked((ushort)childIdx);
+ internal bool Is(ExprNodeKind kind) => Kind == kind;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal bool Is(ExprNodeKind kind) => Kind == kind;
+ 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 IsExpression() => Kind == ExprNodeKind.Expression;
+ private static bool IsSmallPrimitive(TypeCode tc) =>
+ tc == TypeCode.Boolean || tc == TypeCode.Byte || tc == TypeCode.SByte ||
+ tc == TypeCode.Char || tc == TypeCode.Int16 || tc == TypeCode.UInt16 ||
+ tc == TypeCode.Int32 || tc == TypeCode.UInt32 || tc == TypeCode.Single;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool HasFlag(byte flag) => (Flags & flag) != 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal bool ShouldCloneWhenLinked() =>
- ReferenceEquals(Obj, InlineValueMarker) ||
- Kind == ExprNodeKind.LabelTarget || NodeType == ExpressionType.Parameter ||
- Kind == ExprNodeKind.ObjectReference || ChildCount == 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 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 +200,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 +215,156 @@ 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