diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e1fde86c..f7b1b9fd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -2,12 +2,11 @@ name: Publish on: push: - tags: - - 'v*.*.*' + tags: + - "v*.*.*" jobs: build: - runs-on: ubuntu-latest env: DOTNET_NOLOGO: true @@ -15,34 +14,34 @@ jobs: DOTNET_CLI_TELEMETRY_OPTOUT: true steps: - - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - - - name: Build - run: dotnet build -c:Release - - - name: Make Internal - shell: pwsh - run: ./BuildScripts/MakeInternal.ps1 - - - name: Pack with dotnet - run: | - arrTag=(${GITHUB_REF//\// }) - VERSION="${arrTag[2]}" - VERSION="${VERSION//v}" - echo "$VERSION" - - dotnet pack --no-build --output artifacts -p:Version=$VERSION -p:ContinuousIntegrationBuild=True ./nuspecs/FastExpressionCompiler.src.nuspec - dotnet pack --no-build --output artifacts -p:Version=$VERSION -p:ContinuousIntegrationBuild=True ./nuspecs/FastExpressionCompiler.LightExpression.src.nuspec - dotnet pack --no-build --output artifacts -p:Version=$VERSION -p:ContinuousIntegrationBuild=True ./nuspecs/FastExpressionCompiler.Internal.src.nuspec - dotnet pack --no-build --output artifacts -p:Version=$VERSION -p:ContinuousIntegrationBuild=True ./nuspecs/FastExpressionCompiler.LightExpression.Internal.src.nuspec - - - uses: actions/upload-artifact@v4 - with: - name: Packages - path: ./artifacts - + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Build + run: dotnet build -c:Release + + - name: Make Internal + shell: pwsh + run: ./BuildScripts/MakeInternal.ps1 + + - name: Pack with dotnet + run: | + arrTag=(${GITHUB_REF//\// }) + VERSION="${arrTag[2]}" + VERSION="${VERSION//v}" + echo "$VERSION" + + dotnet pack --no-build --output artifacts -p:Version=$VERSION -p:ContinuousIntegrationBuild=True ./nuspecs/FastExpressionCompiler.src.nuspec + dotnet pack --no-build --output artifacts -p:Version=$VERSION -p:ContinuousIntegrationBuild=True ./nuspecs/FastExpressionCompiler.LightExpression.src.nuspec + dotnet pack --no-build --output artifacts -p:Version=$VERSION -p:ContinuousIntegrationBuild=True ./nuspecs/FastExpressionCompiler.Internal.src.nuspec + dotnet pack --no-build --output artifacts -p:Version=$VERSION -p:ContinuousIntegrationBuild=True ./nuspecs/FastExpressionCompiler.LightExpression.Internal.src.nuspec + + - uses: actions/upload-artifact@v4 + with: + name: Packages + path: ./artifacts + # - name: Push with dotnet # run: dotnet nuget push artifacts/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json diff --git a/Directory.Build.props b/Directory.Build.props index d8875008..808a8b20 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,12 +5,15 @@ en-US latest - net9.0 true true IDE0251;IDE0079;IDE0047;NETSDK1212 + + + false + diff --git a/b.bat b/b.bat index 7d5ece18..1517d9dc 100644 --- a/b.bat +++ b/b.bat @@ -1,13 +1,19 @@ @echo off +echo: +echo:## Starting BUILD... +echo: +dotnet build -p:DevMode=true -v:m -c:Release +if %ERRORLEVEL% neq 0 goto :error + echo: -echo:## Starting: TESTS... +echo:## Starting TESTS... echo: -dotnet run -c:Release -f:net9.0 --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj +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 -c:Release --project test/FastExpressionCompiler.TestsRunner.Net472 +dotnet run --no-build -p:DevMode=true -c:Release --project test/FastExpressionCompiler.TestsRunner.Net472 if %ERRORLEVEL% neq 0 goto :error echo: diff --git a/bt.bat b/bt.bat index 7cb4a889..f5fe81d3 100644 --- a/bt.bat +++ b/bt.bat @@ -3,7 +3,7 @@ echo: echo:## Running TESTS on the Latest Supported .NET... echo: -dotnet run -c:Release -f:net9.0 --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj +dotnet run -p:DevMode=true -f:net9.0 -c:Release --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj if %ERRORLEVEL% neq 0 goto :error echo: diff --git a/build.bat b/build.bat index 56921252..8118e34e 100644 --- a/build.bat +++ b/build.bat @@ -3,22 +3,11 @@ setlocal EnableDelayedExpansion echo: # BUILDING AND RUNNING THE TESTS IN RELEASE MODE echo: -echo: - -set "FrameworkParam=-f:net9.0" -set "LatestSupportedNetProp=-p:LatestSupportedNet=net9.0" -if [%1] NEQ [] ( - set "FrameworkParam=-f:%1" - set "LatestSupportedNetProp=-p:LatestSupportedNet=%1" -) -echo:FrameworkParam == '%FrameworkParam%', LatestSupportedNetProp == '%LatestSupportedNetProp%' - echo: echo:## Starting: RESTORE and BUILD... echo: - -dotnet clean -v:m %LatestSupportedNetProp% -dotnet build -v:m %LatestSupportedNetProp% -c:Release +dotnet clean -v:m +dotnet build -v:m -c:Release if %ERRORLEVEL% neq 0 goto :error echo: @@ -27,19 +16,22 @@ echo:## Finished: RESTORE and BUILD echo: echo:## Starting: TESTS... echo: -echo: running on .NET 9.0 (Latest) -echo: -dotnet run --no-build net9.0 %FrameworkParam% -c:Release --project test/FastExpressionCompiler.TestsRunner +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: running on .NET 8.0 (LTS) echo: -dotnet run --no-build net8.0 %FrameworkParam% -c:Release --project test/FastExpressionCompiler.TestsRunner +echo:running on .NET 8.0 (LTS) +dotnet run --no-build -f:net8.0 -c:Release --project test/FastExpressionCompiler.TestsRunner +if %ERRORLEVEL% neq 0 goto :error -echo: running on .NET 6.0 (LTS) echo: -dotnet run --no-build net6.0 %FrameworkParam% -c:Release --project test/FastExpressionCompiler.TestsRunner +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 if %ERRORLEVEL% neq 0 goto :error echo: diff --git a/build_debug.bat b/build_debug.bat index ab41dfc2..b8b8db99 100644 --- a/build_debug.bat +++ b/build_debug.bat @@ -1,23 +1,13 @@ @echo off setlocal EnableDelayedExpansion -echo: # BUILDING AND RUNNNG THE TESTS IN DEBUG MODE +echo: # BUILDING AND RUNNING THE TESTS IN DEBUG MODE echo: -echo: - -set "FrameworkParam=-f:net9.0" -set "LatestSupportedNetProp=-p:LatestSupportedNet=net9.0" -if [%1] NEQ [] ( - set "FrameworkParam=-f:%1" - set "LatestSupportedNetProp=-p:LatestSupportedNet=%1" -) -echo:FrameworkParam == '%FrameworkParam%', LatestSupportedNetProp == '%LatestSupportedNetProp%' - echo: echo:## Starting: RESTORE and BUILD... echo: -dotnet build %LatestSupportedNetProp% -c:Debug +dotnet build -c:Debug if %ERRORLEVEL% neq 0 goto :error echo: @@ -26,10 +16,22 @@ 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 +if %ERRORLEVEL% neq 0 goto :error -dotnet run --no-build %LatestSupportedNetProp% %FrameworkParam% -c:Debug --project test/FastExpressionCompiler.TestsRunner +echo: +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 if %ERRORLEVEL% neq 0 goto :error echo: diff --git a/running b/running new file mode 100644 index 00000000..9b276f2e --- /dev/null +++ b/running @@ -0,0 +1,6 @@ + on .NET 9.0 (Latest) + on .NET 8.0 (LTS) + on .NET 6.0 (Previous LTS) + on .NET 4.7.2 + on .NET 9.0 (Latest) + on .NET 8.0 (LTS) diff --git a/src/FastExpressionCompiler.LightExpression/Expression.cs b/src/FastExpressionCompiler.LightExpression/Expression.cs index 8a026693..4d834f09 100644 --- a/src/FastExpressionCompiler.LightExpression/Expression.cs +++ b/src/FastExpressionCompiler.LightExpression/Expression.cs @@ -184,9 +184,25 @@ private static ParameterExpression TryToMakeKnownTypeParameter(Type type, string public static readonly ConstantExpression OneConstant = new IntConstantExpression(1); public static readonly ConstantExpression MinusOneConstant = new IntConstantExpression(-1); - public static ConstantRefExpression ConstantRef(T value) => new ConstantRefExpression(value); + /// Holds the value that you can change lately + public sealed class ValueRef + { + /// Reflection access to the value FieldInfo + public static readonly FieldInfo ValueField = typeof(ValueRef).GetField(nameof(Value), BindingFlags.Public | BindingFlags.Instance); + /// The adjustable value + public T Value; + /// Construct with the initial value + public ValueRef(T value) => Value = value; + } - public static ConstantRefExpression ConstantRef(object value, Type type) => new ConstantRefExpression(value, type); + /// Simplifies the constant which is always hold in Closure and which Value can be modified after the compilation + public static Expression ConstantRef(T value, out ValueRef valueRef) + { + valueRef = new ValueRef(value); + + // todo: @perf try the intrinsic? + return new InstanceFieldExpression(new ValueConstantExpression>(valueRef), ValueRef.ValueField); + } /// Avoids the boxing for all (two) bool values public static ConstantExpression Constant(bool value) => value ? TrueConstant : FalseConstant; @@ -3827,10 +3843,6 @@ public abstract class ConstantExpression : Expression public sealed override ExpressionType NodeType => ExpressionType.Constant; public abstract object Value { get; } - ///Constant with this field defined will be always put in Closure as a Constant instance and not its value, - /// and will always be accessed by its mutable Field directly, without putting its value into the local variable - public virtual FieldInfo RefField => null; - #if SUPPORTS_VISITOR [RequiresUnreferencedCode(Trimming.Message)] protected internal override Expression Accept(ExpressionVisitor visitor) => visitor.VisitConstant(this); @@ -3869,36 +3881,7 @@ public sealed class ValueConstantExpression : ConstantExpression // Note: the Value is specifically an object despite possibility of strongly typed T, because this way it will be a single boxing. // Otherwise even using the typed `T _value`, it will be boxed multiple times through its `object Value` accessor. public override object Value { get; } - internal ValueConstantExpression(object value) => Value = value; -} - -/// A special case of Constant entirely stored in Closure, -/// so that its ValueRef can be updated and the all its Value usage sites will be updated as well, #466. -public sealed class ConstantRefExpression : ConstantExpression -{ - public override Type Type => typeof(T); - public override object Value => ValueRef; - private static readonly FieldInfo ValueRefField = typeof(ConstantRefExpression).GetField(nameof(ValueRef)); - public override FieldInfo RefField => ValueRefField; - public T ValueRef; - internal ConstantRefExpression(T value) => ValueRef = value; -} - -/// -/// Note: There is no RefConstantExpression which relies on `Value.GetType()`, because the Value may change (by design), so the Type, which may produce unexpected results. -/// -public sealed class ConstantRefExpression : ConstantExpression -{ - public override Type Type { get; } - public override object Value => ValueRef; - public object ValueRef; - private static readonly FieldInfo ValueRefField = typeof(ConstantRefExpression).GetField(nameof(ValueRef)); - public override FieldInfo RefField => ValueRefField; - internal ConstantRefExpression(object value, Type type) - { - Type = type ?? typeof(object); // The type always should be set - ValueRef = value; - } + internal ValueConstantExpression(T value) => Value = value; } public sealed class TypedValueConstantExpression : ConstantExpression diff --git a/src/FastExpressionCompiler.LightExpression/FastExpressionCompiler.LightExpression.csproj b/src/FastExpressionCompiler.LightExpression/FastExpressionCompiler.LightExpression.csproj index 8f2def0f..b3c4c038 100644 --- a/src/FastExpressionCompiler.LightExpression/FastExpressionCompiler.LightExpression.csproj +++ b/src/FastExpressionCompiler.LightExpression/FastExpressionCompiler.LightExpression.csproj @@ -1,11 +1,10 @@  - net472;netstandard2.0;netstandard2.1;net6.0;net7.0 - net472;netstandard2.0;netstandard2.1;net6.0;net8.0 - net472;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0 + net472;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0 + net472;net9.0 5.2.0 - preview-02 + preview-03 FastExpressionCompiler.LightExpression @@ -16,7 +15,8 @@ (this LambdaExpression lambdaExpr, (TDelegate)(TryCompileBoundToFirstClosureParam( typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate), lambdaExpr.Body, #if LIGHT_EXPRESSION - lambdaExpr, RentOrNewClosureTypeToParamTypes(lambdaExpr), + lambdaExpr, #else - lambdaExpr.Parameters, RentOrNewClosureTypeToParamTypes(lambdaExpr.Parameters), + lambdaExpr.Parameters, #endif lambdaExpr.ReturnType, flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys())); @@ -166,9 +168,9 @@ public static bool CompileFastToIL(this LambdaExpression lambdaExpr, ILGenerator public static Delegate CompileFast(this LambdaExpression lambdaExpr, bool ifFastFailedReturnNull = false, CompilerFlags flags = CompilerFlags.Default) => (Delegate)TryCompileBoundToFirstClosureParam(lambdaExpr.Type, lambdaExpr.Body, #if LIGHT_EXPRESSION - lambdaExpr, RentOrNewClosureTypeToParamTypes(lambdaExpr), + lambdaExpr, #else - lambdaExpr.Parameters, RentOrNewClosureTypeToParamTypes(lambdaExpr.Parameters), + lambdaExpr.Parameters, #endif lambdaExpr.ReturnType, flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); @@ -218,7 +220,7 @@ public static Func CompileFast(this Expression> lambdaExpr, bool i #else lambdaExpr.Parameters, #endif - _closureAsASingleParamType, typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. public static Func CompileFast(this Expression> lambdaExpr, @@ -229,7 +231,7 @@ public static Func CompileFast(this Expression> lambda #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1) }, typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. public static Func CompileFast(this Expression> lambdaExpr, @@ -240,7 +242,6 @@ public static Func CompileFast(this ExpressionCompiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. @@ -252,7 +253,7 @@ public static Func CompileFast( #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3) }, typeof(R), flags) + typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to TDelegate type. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. @@ -264,7 +265,7 @@ public static Func CompileFast( #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, typeof(R), flags) + typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. @@ -276,7 +277,7 @@ public static Func CompileFast( #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, typeof(R), flags) + typeof(R), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. @@ -288,7 +289,7 @@ public static Func CompileFastCompiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. @@ -299,7 +300,7 @@ public static Action CompileFast(this Expression lambdaExpr, bool ifFast #else lambdaExpr.Parameters, #endif - _closureAsASingleParamType, typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. public static Action CompileFast(this Expression> lambdaExpr, @@ -310,7 +311,7 @@ public static Action CompileFast(this Expression> lambdaExpr, #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1) }, typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. public static Action CompileFast(this Expression> lambdaExpr, @@ -321,7 +322,7 @@ public static Action CompileFast(this Expression> #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1), typeof(T2) }, typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. public static Action CompileFast(this Expression> lambdaExpr, @@ -332,7 +333,7 @@ public static Action CompileFast(this ExpressionCompiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. @@ -344,7 +345,7 @@ public static Action CompileFast( #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, typeof(void), flags) + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. @@ -356,7 +357,7 @@ public static Action CompileFast( #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, typeof(void), flags) + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); /// Compiles lambda expression to delegate. Use ifFastFailedReturnNull parameter to Not fallback to Expression.Compile, useful for testing. @@ -368,7 +369,7 @@ public static Action CompileFast #else lambdaExpr.Parameters, #endif - new[] { typeof(ArrayClosure), typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) }, typeof(void), flags) + typeof(void), flags) ?? (ifFastFailedReturnNull ? null : lambdaExpr.CompileSys()); #endregion @@ -378,9 +379,9 @@ public static TDelegate TryCompile(this LambdaExpression lambdaExpr, where TDelegate : class => (TDelegate)TryCompileBoundToFirstClosureParam(typeof(TDelegate) == typeof(Delegate) ? lambdaExpr.Type : typeof(TDelegate), lambdaExpr.Body, #if LIGHT_EXPRESSION - lambdaExpr, RentOrNewClosureTypeToParamTypes(lambdaExpr), + lambdaExpr, #else - lambdaExpr.Parameters, RentOrNewClosureTypeToParamTypes(lambdaExpr.Parameters), + lambdaExpr.Parameters, #endif lambdaExpr.ReturnType, flags); @@ -484,20 +485,24 @@ private static Delegate CompileNoArgsNew(ConstructorInfo ctor, Type delegateType #if LIGHT_EXPRESSION internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IParameterProvider paramExprs, - Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags) + Type returnType, CompilerFlags flags) { + var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(paramExprs); if (bodyExpr is NoArgsNewClassIntrinsicExpression newNoArgs) + { + // there is no Return of the pooled parameter types here, because in the rarest case with the unused lambda arguments we may just exaust the pooled instance return CompileNoArgsNew(newNoArgs.Constructor, delegateType, closurePlusParamTypes, returnType); + } #else internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IReadOnlyList paramExprs, - Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags) + Type returnType, CompilerFlags flags) { + var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(paramExprs); #endif // Try to avoid compilation altogether for Func delegates via Interpreter, see #468 - if ((flags & CompilerFlags.DisableInterpreter) == 0 & - returnType == typeof(bool) & closurePlusParamTypes.Length == 1 + if (returnType == typeof(bool) & closurePlusParamTypes.Length == 1 && Interpreter.IsCandidateForInterpretation(bodyExpr) - && Interpreter.TryInterpretBoolean(out var result, bodyExpr)) + && Interpreter.TryInterpretBool(out var result, bodyExpr, flags)) return result ? Interpreter.TrueFunc : Interpreter.FalseFunc; // The method collects the info from the all nested lambdas deep down up-front and de-duplicates the lambdas as well. @@ -540,13 +545,14 @@ internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Exp return null; il.Demit(OpCodes.Ret); + ReturnClosureTypeToParamTypesToPool(closurePlusParamTypes); + return method.CreateDelegate(delegateType, closure); } private static readonly Type[] _closureAsASingleParamType = { typeof(ArrayClosure) }; private static readonly Type[][] _closureTypePlusParamTypesPool = new Type[8][]; // todo: @perf @mem could we use this for other Type arrays? - // todo: @perf optimize #if LIGHT_EXPRESSION private static Type[] RentOrNewClosureTypeToParamTypes(IParameterProvider paramExprs) { @@ -559,37 +565,23 @@ private static Type[] RentOrNewClosureTypeToParamTypes(IReadOnlyList paramEx if (count == 0) return _closureAsASingleParamType; - if (count < 8) - { - var pooledClosureAndParamTypes = Interlocked.Exchange(ref _closureTypePlusParamTypesPool[count], null); - if (pooledClosureAndParamTypes != null) - { - for (var i = 0; i < count; i++) - { - var parameterExpr = paramExprs.GetParameter(i); // todo: @perf can we avoid calling virtual GetParameter() and maybe use intrinsic with NoByRef? - pooledClosureAndParamTypes[i + 1] = parameterExpr.IsByRef ? parameterExpr.Type.MakeByRefType() : parameterExpr.Type; - } - return pooledClosureAndParamTypes; - } - } - - // todo: @perf the code maybe simplified and then will be the candidate for the inlining - var closureAndParamTypes = new Type[count + 1]; - closureAndParamTypes[0] = typeof(ArrayClosure); + var pooled = count < 8 ? Interlocked.Exchange(ref _closureTypePlusParamTypesPool[count], null) ?? new Type[count + 1] : new Type[count + 1]; + pooled[0] = typeof(ArrayClosure); for (var i = 0; i < count; i++) { - var parameterExpr = paramExprs.GetParameter(i); - closureAndParamTypes[i + 1] = parameterExpr.IsByRef ? parameterExpr.Type.MakeByRefType() : parameterExpr.Type; + var paramExpr = paramExprs.GetParameter(i); // todo: @perf can we avoid calling virtual GetParameter() and maybe use intrinsic with NoByRef? + pooled[i + 1] = !paramExpr.IsByRef ? paramExpr.Type : paramExpr.Type.MakeByRefType(); } - return closureAndParamTypes; + + return pooled; } [MethodImpl((MethodImplOptions)256)] private static void ReturnClosureTypeToParamTypesToPool(Type[] closurePlusParamTypes) { - var paramCount = closurePlusParamTypes.Length - 1; - if (paramCount != 0 && paramCount < 8) - Interlocked.Exchange(ref _closureTypePlusParamTypesPool[paramCount], closurePlusParamTypes); // todo: @perf we don't need the Interlocked here + var paramCountOnly = closurePlusParamTypes.Length - 1; + if (paramCountOnly != 0 & paramCountOnly < 8) + Interlocked.Exchange(ref _closureTypePlusParamTypesPool[paramCountOnly], closurePlusParamTypes); // todo: @perf we don't need the Interlocked here } #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member @@ -1199,13 +1191,6 @@ public static Result TryCollectInfo(ref ClosureInfo closure, Expression expr, { case ExpressionType.Constant: #if LIGHT_EXPRESSION - if (((ConstantExpression)expr).RefField != null) - { - // Register the constant expression itself in the closure - closure.AddConstantOrIncrementUsageCount(expr); - return Result.OK; - } - if (expr == NullConstant | expr == FalseConstant | expr == TrueConstant || expr is IntConstantExpression) return r; #endif @@ -2086,8 +2071,7 @@ public static bool TryEmit(Expression expr, case ExpressionType.Equal: case ExpressionType.NotEqual: { - if ((setup & CompilerFlags.DisableInterpreter) == 0 && exprType.IsPrimitive && - Interpreter.TryInterpretBoolean(out var boolResult, expr)) + if (exprType.IsPrimitive && Interpreter.TryInterpretBool(out var boolResult, expr, setup)) { if ((parent & ParentFlags.IgnoreResult) == 0) il.Demit(boolResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); @@ -2097,53 +2081,46 @@ public static bool TryEmit(Expression expr, ref closure, setup, parent); } case ExpressionType.Add: - case ExpressionType.AddChecked: case ExpressionType.Subtract: - case ExpressionType.SubtractChecked: case ExpressionType.Multiply: - case ExpressionType.MultiplyChecked: case ExpressionType.Divide: case ExpressionType.Modulo: - { - if ((setup & CompilerFlags.DisableInterpreter) == 0 && exprType.IsPrimitive && - Interpreter.TryInterpret(out var resultObj, expr)) - { - if ((parent & ParentFlags.IgnoreResult) == 0) - TryEmitPrimitiveOrEnumOrDecimalConstant(il, resultObj, exprType); - return true; - } - return TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il, - ref closure, setup, parent); - } - case ExpressionType.Power: case ExpressionType.And: case ExpressionType.Or: case ExpressionType.ExclusiveOr: case ExpressionType.LeftShift: case ExpressionType.RightShift: - // todo: @wip @feature #472 add interpretation when those node types are supported + { + return exprType.IsPrimitive + && TryInterpretAndEmitResult(expr, il, parent, setup) + || TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il, + ref closure, setup, parent); + } + // todo: @feature #472 add interpretation when those node types are supported + case ExpressionType.AddChecked: + case ExpressionType.SubtractChecked: + case ExpressionType.MultiplyChecked: + case ExpressionType.Power: return TryEmitArithmetic(((BinaryExpression)expr).Left, ((BinaryExpression)expr).Right, nodeType, exprType, paramExprs, il, ref closure, setup, parent); case ExpressionType.AndAlso: case ExpressionType.OrElse: { - if ((setup & CompilerFlags.DisableInterpreter) == 0 && exprType.IsPrimitive && - Interpreter.TryInterpretBoolean(out var boolResult, expr)) + if (Interpreter.TryInterpretBool(out var resultBool, expr, setup)) { if ((parent & ParentFlags.IgnoreResult) == 0) - il.Demit(boolResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); return true; } return TryEmitLogicalOperator((BinaryExpression)expr, nodeType, paramExprs, il, ref closure, setup, parent); } case ExpressionType.Not: { - if ((setup & CompilerFlags.DisableInterpreter) == 0 && exprType.IsPrimitive && - Interpreter.TryInterpretBoolean(out var boolResult, expr)) + if (Interpreter.TryInterpretBool(out var resultBool, expr, setup)) { if ((parent & ParentFlags.IgnoreResult) == 0) - il.Demit(boolResult ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); return true; } return TryEmitNot((UnaryExpression)expr, paramExprs, il, ref closure, setup, parent); @@ -2153,7 +2130,13 @@ public static bool TryEmit(Expression expr, case ExpressionType.Conditional: var condExpr = (ConditionalExpression)expr; - return TryEmitConditional(condExpr.Test, condExpr.IfTrue, condExpr.IfFalse, paramExprs, il, ref closure, setup, parent); + var testExpr = condExpr.Test; + if (Interpreter.TryInterpretBool(out var testIsTrue, testExpr, setup)) + { + expr = testIsTrue ? condExpr.IfTrue : condExpr.IfFalse; + continue; // no recursion, just continue with the left or right side of condition + } + return TryEmitConditional(testExpr, condExpr.IfTrue, condExpr.IfFalse, paramExprs, il, ref closure, setup, parent); case ExpressionType.PostIncrementAssign: case ExpressionType.PreIncrementAssign: @@ -2682,35 +2665,38 @@ private static bool TryEmitCoalesceOperator(BinaryExpression expr, IReadOnlyList private static void EmitDefault(Type type, ILGenerator il) { - if (!type.GetTypeInfo().IsValueType) + if (type.IsClass) { il.Demit(OpCodes.Ldnull); + return; } - else if ( - type == typeof(bool) || - type == typeof(byte) || - type == typeof(char) || - type == typeof(sbyte) || - type == typeof(int) || - type == typeof(uint) || - type == typeof(short) || - type == typeof(ushort)) - { - il.Demit(OpCodes.Ldc_I4_0); - } - else if ( - type == typeof(long) || - type == typeof(ulong)) + switch (Type.GetTypeCode(type)) { - il.Demit(OpCodes.Ldc_I4_0); - il.Demit(OpCodes.Conv_I8); + case TypeCode.Boolean: + case TypeCode.Char: + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + il.Demit(OpCodes.Ldc_I4_0); + break; + case TypeCode.Int64: + case TypeCode.UInt64: + il.Demit(OpCodes.Ldc_I4_0); + il.Demit(OpCodes.Conv_I8); + break; + case TypeCode.Single: + il.Demit(OpCodes.Ldc_R4, default(float)); + break; + case TypeCode.Double: + il.Demit(OpCodes.Ldc_R8, default(double)); + break; + default: + EmitLoadLocalVariable(il, InitValueTypeVariable(il, type)); + break; } - else if (type == typeof(float)) - il.Demit(OpCodes.Ldc_R4, default(float)); - else if (type == typeof(double)) - il.Demit(OpCodes.Ldc_R8, default(double)); - else - EmitLoadLocalVariable(il, InitValueTypeVariable(il, type)); } #if LIGHT_EXPRESSION @@ -3463,14 +3449,7 @@ public static bool TryEmitConstant(ConstantExpression expr, Type exprType, ILGen { var ok = false; #if LIGHT_EXPRESSION - var refField = expr.RefField; - if (refField != null) - { - Debug.Assert(closure.ContainsConstantsOrNestedLambdas()); - ok = TryEmitConstant(true, null, null, expr, il, ref closure, byRefIndex, refField); - if (!ok) return false; - } - else if (expr == NullConstant) + if (expr == NullConstant) { il.Demit(OpCodes.Ldnull); ok = true; @@ -3617,33 +3596,33 @@ public static bool TryEmitPrimitiveOrEnumOrDecimalConstant(ILGenerator il, objec case TypeCode.Int16: EmitLoadConstantInt(il, (short)constValue); break; - case TypeCode.Int32: - EmitLoadConstantInt(il, (int)constValue); - break; - case TypeCode.Int64: - il.Demit(OpCodes.Ldc_I8, (long)constValue); - break; - case TypeCode.Double: - il.Demit(OpCodes.Ldc_R8, (double)constValue); - break; - case TypeCode.Single: - il.Demit(OpCodes.Ldc_R4, (float)constValue); - break; case TypeCode.UInt16: EmitLoadConstantInt(il, (ushort)constValue); break; + case TypeCode.Int32: + EmitLoadConstantInt(il, (int)constValue); + break; case TypeCode.UInt32: unchecked { EmitLoadConstantInt(il, (int)(uint)constValue); } break; + case TypeCode.Int64: + il.Demit(OpCodes.Ldc_I8, (long)constValue); + break; case TypeCode.UInt64: unchecked { il.Demit(OpCodes.Ldc_I8, (long)(ulong)constValue); } break; + case TypeCode.Double: + il.Demit(OpCodes.Ldc_R8, (double)constValue); + break; + case TypeCode.Single: + il.Demit(OpCodes.Ldc_R4, (float)constValue); + break; case TypeCode.Decimal: EmitDecimalConstant((decimal)constValue, il); break; @@ -3718,22 +3697,7 @@ internal static void EmitLoadConstantsAndNestedLambdasIntoVars(ILGenerator il, r var constType = constValue.GetType(); if (constType.IsValueType) il.Demit(OpCodes.Unbox_Any, constType); -#if LIGHT_EXPRESSION - else if (constValue is ConstantExpression ce) - { - var refField = ce.RefField; - if (refField != null) - { - il.Demit(OpCodes.Ldfld, refField); - if (refField.FieldType == typeof(object)) - { - var refValueType = ce.Value.GetType(); - if (refValueType.IsValueType) - il.Demit(OpCodes.Unbox_Any, refValueType); - } - } - } -#endif + varIndex = (short)il.GetNextLocalVarIndex(constType); constUsage = (short)(varIndex + 1); // to distinguish from the default 1 EmitStoreLocalVariable(il, varIndex); @@ -4133,7 +4097,7 @@ private static bool TryEmitArithmeticAndOrAssign( var paramIndex = -1; var localVarIndex = closure.GetDefinedLocalVarOrDefault(p); if (localVarIndex != -1) - EmitLoadLocalVariable(il, localVarIndex); // todo: @wip #346 + EmitLoadLocalVariable(il, localVarIndex); else { paramIndex = paramExprCount - 1; @@ -5298,9 +5262,17 @@ private static bool TryEmitSwitch(SwitchExpression expr, IReadOnlyList param var cs0 = cases[0]; if (cs0.TestValues.Count == 1) { - Expression testExpr = customEqualMethod == null - ? Equal(switchValueExpr, cs0.TestValues[0]) - : Call(customEqualMethod, switchValueExpr, cs0.TestValues[0]); + Expression testExpr; + if (customEqualMethod == null) + { + // todo: @perf avoid creation of the additional expression + testExpr = Equal(switchValueExpr, cs0.TestValues[0]); + if (Interpreter.TryInterpretBool(out var testResult, testExpr, setup)) + return TryEmit(testResult ? cs0.Body : expr.DefaultBody, paramExprs, il, ref closure, setup, parent); + } + else + testExpr = Call(customEqualMethod, switchValueExpr, cs0.TestValues[0]); + return TryEmitConditional(testExpr, cs0.Body, expr.DefaultBody, paramExprs, il, ref closure, setup, parent); } } @@ -6037,7 +6009,8 @@ private static bool TryEmitConditional( { oppositeTestExpr = sideDefaultExpr == testLeftExpr ? testRightExpr : testLeftExpr; var testSideType = sideDefaultExpr.Type; - if (testSideType.IsPrimitiveOrDecimalWithZeroDefault()) + // except decimal, because its 0 is Decimal.Zero a struct and is not working with Brtrue/Brfalse + if (testSideType.IsPrimitiveWithZeroDefaultExceptDecimal()) useBrFalseOrTrue = 0; else if (testSideType.IsClass || testSideType.IsNullable()) { @@ -6419,6 +6392,100 @@ private static void EmitLoadArgAddress(ILGenerator il, int paramIndex) else il.Demit(OpCodes.Ldarga, (short)paramIndex); } + + /// Tries to interpret and emit the result IL + /// In case of exception return false, to allow FEC emit normally and throw in the invocation phase + public static bool TryInterpretAndEmitResult(Expression expr, ILGenerator il, ParentFlags parent, CompilerFlags flags) + { + var type = expr.Type; + Debug.Assert(type.IsPrimitive); + if ((flags & CompilerFlags.DisableInterpreter) != 0) + return false; + + var typeCode = Type.GetTypeCode(type); + try + { + switch (typeCode) + { + case TypeCode.Boolean: + var resultBool = false; + if (!Interpreter.TryInterpretBool(ref resultBool, expr, expr.NodeType)) + return false; + il.Demit(resultBool ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + break; + + case TypeCode.Int32: + int resultInt = 0; + if (!Interpreter.TryInterpretInt(ref resultInt, expr, expr.NodeType)) + return false; + if ((parent & ParentFlags.IgnoreResult) == 0) + EmitLoadConstantInt(il, resultInt); + break; + case TypeCode.Decimal: + decimal resultDec = default; + if (!Interpreter.TryInterpretDecimal(ref resultDec, expr, expr.NodeType)) + return false; + if ((parent & ParentFlags.IgnoreResult) == 0) + EmitDecimalConstant(resultDec, il); + break; + default: + Interpreter.PValue resultVal = default; + if (!Interpreter.TryInterpretPrimitiveValue(ref resultVal, expr, typeCode, expr.NodeType)) + return false; + if ((parent & ParentFlags.IgnoreResult) == 0) + switch (typeCode) + { + case TypeCode.Char: + EmitLoadConstantInt(il, resultVal.CharValue); + break; + case TypeCode.SByte: + EmitLoadConstantInt(il, resultVal.SByteValue); + break; + case TypeCode.Byte: + EmitLoadConstantInt(il, resultVal.ByteValue); + break; + case TypeCode.Int16: + EmitLoadConstantInt(il, resultVal.Int16Value); + break; + case TypeCode.UInt16: + EmitLoadConstantInt(il, resultVal.UInt16Value); + break; + case TypeCode.Int32: + EmitLoadConstantInt(il, resultVal.Int32Value); + break; + case TypeCode.UInt32: + unchecked + { + EmitLoadConstantInt(il, (int)resultVal.UInt32Value); + } + break; + case TypeCode.Int64: + il.Demit(OpCodes.Ldc_I8, resultVal.Int64Value); + break; + case TypeCode.UInt64: + unchecked + { + il.Demit(OpCodes.Ldc_I8, (long)resultVal.UInt64Value); + } + break; + case TypeCode.Single: + il.Demit(OpCodes.Ldc_R4, resultVal.SingleValue); + break; + case TypeCode.Double: + il.Demit(OpCodes.Ldc_R8, resultVal.DoubleValue); + break; + default: Interpreter.UnreachableCase(typeCode); break; + } + break; + } + return true; + } + catch + { + // ignore exception and return the false and rethrow the exception in the invocation time + return false; + } + } } /// Interpreter @@ -6429,23 +6496,29 @@ public static class Interpreter /// Always returns false public static readonly Func FalseFunc = static () => false; - /// Single instance of true object - public static readonly object TrueObject = true; - /// Single instance of false object - public static readonly object FalseObject = false; - + /// Return value should be ignored [MethodImpl(MethodImplOptions.NoInlining)] - private static T UnreachableCase() + internal static void UnreachableCase(T @case, + [CallerMemberName] string caller = "", [CallerLineNumber] int line = -1) { - throw new InvalidCastException("Unreachable switch case reached"); +#if INTERPRETATION_DIAGNOSTICS + Console.WriteLine($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); + Debugger.Break(); +#endif + throw new InvalidCastException($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); } - /// Operation accepting bool inputs and producing bool output - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsLogical(ExpressionType nodeType) => - nodeType == ExpressionType.AndAlso | - nodeType == ExpressionType.OrElse | - nodeType == ExpressionType.Not; + /// Return value should be ignored + [MethodImpl(MethodImplOptions.NoInlining)] + private static R UnreachableCase(T @case, R result, + [CallerMemberName] string caller = "", [CallerLineNumber] int line = -1) + { +#if INTERPRETATION_DIAGNOSTICS + Console.WriteLine($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); + Debugger.Break(); +#endif + throw new InvalidCastException($"Unreachable switch case detected `{@case}` at `{caller}`:{line}"); + } /// Operation accepting IComparable inputs and producing bool output [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -6459,148 +6532,555 @@ public static bool IsComparison(ExpressionType nodeType) => /// Operation accepting the same primitive type inputs (or of the coalescing types) and producing the "same" primitive type output [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsArithmetic(ExpressionType nodeType) => + public static bool IsArithmeticBinary(ExpressionType nodeType) => nodeType == ExpressionType.Add | nodeType == ExpressionType.Subtract | nodeType == ExpressionType.Multiply | nodeType == ExpressionType.Divide | nodeType == ExpressionType.Modulo | - nodeType == ExpressionType.Negate; - - /// Eval negate - public static object DoNegateOrNull(object operand) - { - return Type.GetTypeCode(operand.GetType()) switch - { - TypeCode.SByte => -(sbyte)operand, - TypeCode.Byte => -(byte)operand, - TypeCode.Int16 => -(short)operand, - TypeCode.UInt16 => -(ushort)operand, - TypeCode.Int32 => -(int)operand, - TypeCode.UInt32 => -(uint)operand, - TypeCode.Int64 => -(long)operand, - TypeCode.Single => -(float)operand, - TypeCode.Double => -(double)operand, - TypeCode.Decimal => -(decimal)operand, - TypeCode.UInt64 => null, - _ => null, - }; - } + nodeType == ExpressionType.Power | + nodeType == ExpressionType.LeftShift | + nodeType == ExpressionType.RightShift | + nodeType == ExpressionType.And | + nodeType == ExpressionType.Or | + nodeType == ExpressionType.ExclusiveOr; - /// Interpret arithmetic. The types of the left and the right operands assumed to be the same. - /// The Expression.Add, Divide, etc, expects the operands to be of the same type - public static object DoArithmeticOrNull(object left, object right, ExpressionType nodeType) - { - Debug.Assert(left != null && right != null, "left and right should not be null"); - Debug.Assert(left.GetType() == right.GetType(), "left and right should be of the same type"); - - var leftCode = Type.GetTypeCode(left.GetType()); - return nodeType switch - { - ExpressionType.Add => leftCode switch - { - // Systems expression does not define the Add for sbyte and byte, but let's keep it here because it is allowed in C# and LightExpression - TypeCode.SByte => (sbyte)left + (sbyte)right, - TypeCode.Byte => (byte)left + (byte)right, - // the rest - TypeCode.Int16 => (short)left + (short)right, - TypeCode.UInt16 => (ushort)left + (ushort)right, - TypeCode.Int32 => (int)left + (int)right, - TypeCode.UInt32 => (uint)left + (uint)right, - TypeCode.Int64 => (long)left + (long)right, - TypeCode.UInt64 => (ulong)left + (ulong)right, - TypeCode.Single => (float)left + (float)right, - TypeCode.Double => (double)left + (double)right, - TypeCode.Decimal => (decimal)left + (decimal)right, - _ => null, - }, - ExpressionType.Subtract => leftCode switch - { - TypeCode.SByte => (sbyte)left - (sbyte)right, - TypeCode.Byte => (byte)left - (byte)right, - TypeCode.Int16 => (short)left - (short)right, - TypeCode.UInt16 => (ushort)left - (ushort)right, - TypeCode.Int32 => (int)left - (int)right, - TypeCode.UInt32 => (uint)left - (uint)right, - TypeCode.Int64 => (long)left - (long)right, - TypeCode.UInt64 => (ulong)left - (ulong)right, - TypeCode.Single => (float)left - (float)right, - TypeCode.Double => (double)left - (double)right, - TypeCode.Decimal => (decimal)left - (decimal)right, - _ => null, - }, - ExpressionType.Multiply => leftCode switch - { - TypeCode.SByte => (sbyte)left * (sbyte)right, - TypeCode.Byte => (byte)left * (byte)right, - TypeCode.Int16 => (short)left * (short)right, - TypeCode.UInt16 => (ushort)left * (ushort)right, - TypeCode.Int32 => (int)left * (int)right, - TypeCode.UInt32 => (uint)left * (uint)right, - TypeCode.Int64 => (long)left * (long)right, - TypeCode.UInt64 => (ulong)left * (ulong)right, - TypeCode.Single => (float)left * (float)right, - TypeCode.Double => (double)left * (double)right, - TypeCode.Decimal => (decimal)left * (decimal)right, - _ => null, - }, - ExpressionType.Divide => leftCode switch - { - TypeCode.SByte => (sbyte)left / (sbyte)right, - TypeCode.Byte => (byte)left / (byte)right, - TypeCode.Int16 => (short)left / (short)right, - TypeCode.UInt16 => (ushort)left / (ushort)right, - TypeCode.Int32 => (int)left / (int)right, - TypeCode.UInt32 => (uint)left / (uint)right, - TypeCode.Int64 => (long)left / (long)right, - TypeCode.UInt64 => (ulong)left / (ulong)right, - TypeCode.Single => (float)left / (float)right, - TypeCode.Double => (double)left / (double)right, - TypeCode.Decimal => (decimal)left / (decimal)right, - _ => null, - }, - ExpressionType.Modulo => leftCode switch - { - TypeCode.SByte => (sbyte)left % (sbyte)right, - TypeCode.Byte => (byte)left % (byte)right, - TypeCode.Int16 => (short)left % (short)right, - TypeCode.UInt16 => (ushort)left % (ushort)right, - TypeCode.Int32 => (int)left % (int)right, - TypeCode.UInt32 => (uint)left % (uint)right, - TypeCode.Int64 => (long)left % (long)right, - TypeCode.UInt64 => (ulong)left % (ulong)right, - TypeCode.Single => (float)left % (float)right, - TypeCode.Double => (double)left % (double)right, - TypeCode.Decimal => (decimal)left % (decimal)right, - _ => null, - }, - _ => null, - }; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void NegatePrimitiveValue(ref PValue value, TypeCode code) + { + switch (code) + { + case TypeCode.Char: value.CharValue = (char)-value.CharValue; break; + case TypeCode.SByte: value.SByteValue = (sbyte)-value.SByteValue; break; + case TypeCode.Byte: value.Int16Value = (short)-value.ByteValue; break; + case TypeCode.Int16: value.Int16Value = (short)-value.Int16Value; break; + case TypeCode.UInt16: value.Int32Value = (int)-value.UInt16Value; break; + case TypeCode.Int32: value.Int32Value = -value.Int32Value; break; + // Negate can not be applied to the UInt32 + case TypeCode.UInt32: UnreachableCase(code); break; + case TypeCode.Single: value.SingleValue = -value.SingleValue; break; + case TypeCode.Double: value.DoubleValue = -value.DoubleValue; break; + default: UnreachableCase(code); break; + } } - public static class ZeroDefault + internal static void ConvertPrimitiveValueFromTo(ref PValue value, TypeCode fromCode, TypeCode toCode) { - public static readonly object Instance = default(T); + switch (toCode) + { + case TypeCode.SByte: + switch (fromCode) + { + case TypeCode.Char: break; + case TypeCode.SByte: break; + case TypeCode.Byte: value.SByteValue = (sbyte)value.ByteValue; break; + case TypeCode.Int16: value.SByteValue = (sbyte)value.Int16Value; break; + case TypeCode.UInt16: value.SByteValue = (sbyte)value.UInt16Value; break; + case TypeCode.Int32: value.SByteValue = (sbyte)value.Int32Value; break; + case TypeCode.UInt32: value.SByteValue = (sbyte)value.UInt32Value; break; + case TypeCode.Int64: value.SByteValue = (sbyte)value.Int64Value; break; + case TypeCode.UInt64: value.SByteValue = (sbyte)value.UInt64Value; break; + case TypeCode.Single: value.SByteValue = (sbyte)value.SingleValue; break; + case TypeCode.Double: value.SByteValue = (sbyte)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Byte: + switch (fromCode) + { + case TypeCode.Char: value.ByteValue = (byte)value.CharValue; break; + case TypeCode.SByte: value.ByteValue = (byte)value.SByteValue; break; + case TypeCode.Byte: break; + case TypeCode.Int16: value.ByteValue = (byte)value.Int16Value; break; + case TypeCode.UInt16: value.ByteValue = (byte)value.UInt16Value; break; + case TypeCode.Int32: value.ByteValue = (byte)value.Int32Value; break; + case TypeCode.UInt32: value.ByteValue = (byte)value.UInt32Value; break; + case TypeCode.Int64: value.ByteValue = (byte)value.Int64Value; break; + case TypeCode.UInt64: value.ByteValue = (byte)value.UInt64Value; break; + case TypeCode.Single: value.ByteValue = (byte)value.SingleValue; break; + case TypeCode.Double: value.ByteValue = (byte)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Int16: + switch (fromCode) + { + case TypeCode.Char: value.Int16Value = (short)value.CharValue; break; + case TypeCode.SByte: value.Int16Value = (short)value.SByteValue; break; + case TypeCode.Byte: value.Int16Value = (short)value.ByteValue; break; + case TypeCode.Int16: break; + case TypeCode.UInt16: value.Int16Value = (short)value.UInt16Value; break; + case TypeCode.Int32: value.Int16Value = (short)value.Int32Value; break; + case TypeCode.UInt32: value.Int16Value = (short)value.UInt32Value; break; + case TypeCode.Int64: value.Int16Value = (short)value.Int64Value; break; + case TypeCode.UInt64: value.Int16Value = (short)value.UInt64Value; break; + case TypeCode.Single: value.Int16Value = (short)value.SingleValue; break; + case TypeCode.Double: value.Int16Value = (short)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.UInt16: + switch (fromCode) + { + case TypeCode.Char: value.UInt16Value = (ushort)value.CharValue; break; + case TypeCode.SByte: value.UInt16Value = (ushort)value.SByteValue; break; + case TypeCode.Byte: value.UInt16Value = (ushort)value.ByteValue; break; + case TypeCode.Int16: value.UInt16Value = (ushort)value.Int16Value; break; + case TypeCode.UInt16: break; + case TypeCode.Int32: value.UInt16Value = (ushort)value.Int32Value; break; + case TypeCode.UInt32: value.UInt16Value = (ushort)value.UInt32Value; break; + case TypeCode.Int64: value.UInt16Value = (ushort)value.Int64Value; break; + case TypeCode.UInt64: value.UInt16Value = (ushort)value.UInt64Value; break; + case TypeCode.Single: value.UInt16Value = (ushort)value.SingleValue; break; + case TypeCode.Double: value.UInt16Value = (ushort)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Int32: + switch (fromCode) + { + case TypeCode.Char: value.Int32Value = (int)value.CharValue; break; + case TypeCode.SByte: value.Int32Value = (int)value.SByteValue; break; + case TypeCode.Byte: value.Int32Value = (int)value.ByteValue; break; + case TypeCode.Int16: value.Int32Value = (int)value.Int16Value; break; + case TypeCode.UInt16: value.Int32Value = (int)value.UInt16Value; break; + case TypeCode.Int32: break; + case TypeCode.UInt32: value.Int32Value = (int)value.UInt32Value; break; + case TypeCode.Int64: value.Int32Value = (int)value.Int64Value; break; + case TypeCode.UInt64: value.Int32Value = (int)value.UInt64Value; break; + case TypeCode.Single: value.Int32Value = (int)value.SingleValue; break; + case TypeCode.Double: value.Int32Value = (int)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.UInt32: + switch (fromCode) + { + case TypeCode.Char: value.UInt32Value = (uint)value.CharValue; break; + case TypeCode.SByte: value.UInt32Value = (uint)value.SByteValue; break; + case TypeCode.Byte: value.UInt32Value = (uint)value.ByteValue; break; + case TypeCode.Int16: value.UInt32Value = (uint)value.Int16Value; break; + case TypeCode.UInt16: value.UInt32Value = (uint)value.UInt16Value; break; + case TypeCode.Int32: value.UInt32Value = (uint)value.Int32Value; break; + case TypeCode.UInt32: break; + case TypeCode.Int64: value.UInt32Value = (uint)value.Int64Value; break; + case TypeCode.UInt64: value.UInt32Value = (uint)value.UInt64Value; break; + case TypeCode.Single: value.UInt32Value = (uint)value.SingleValue; break; + case TypeCode.Double: value.UInt32Value = (uint)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Int64: + switch (fromCode) + { + case TypeCode.Char: value.Int64Value = (long)value.CharValue; break; + case TypeCode.SByte: value.Int64Value = (long)value.SByteValue; break; + case TypeCode.Byte: value.Int64Value = (long)value.ByteValue; break; + case TypeCode.Int16: value.Int64Value = (long)value.Int16Value; break; + case TypeCode.UInt16: value.Int64Value = (long)value.UInt16Value; break; + case TypeCode.Int32: value.Int64Value = (long)value.Int32Value; break; + case TypeCode.UInt32: value.Int64Value = (long)value.UInt32Value; break; + case TypeCode.Int64: break; + case TypeCode.UInt64: value.Int64Value = (long)value.UInt64Value; break; + case TypeCode.Single: value.Int64Value = (long)value.SingleValue; break; + case TypeCode.Double: value.Int64Value = (long)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.UInt64: + switch (fromCode) + { + case TypeCode.Char: value.UInt64Value = (ulong)value.CharValue; break; + case TypeCode.SByte: value.UInt64Value = (ulong)value.SByteValue; break; + case TypeCode.Byte: value.UInt64Value = (ulong)value.ByteValue; break; + case TypeCode.Int16: value.UInt64Value = (ulong)value.Int16Value; break; + case TypeCode.UInt16: value.UInt64Value = (ulong)value.UInt16Value; break; + case TypeCode.Int32: value.UInt64Value = (ulong)value.Int32Value; break; + case TypeCode.UInt32: value.UInt64Value = (ulong)value.UInt32Value; break; + case TypeCode.Int64: value.UInt64Value = (ulong)value.Int64Value; break; + case TypeCode.UInt64: break; + case TypeCode.Single: value.UInt64Value = (ulong)value.SingleValue; break; + case TypeCode.Double: value.UInt64Value = (ulong)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Single: + switch (fromCode) + { + case TypeCode.Char: value.SingleValue = (float)value.CharValue; break; + case TypeCode.SByte: value.SingleValue = (float)value.SByteValue; break; + case TypeCode.Byte: value.SingleValue = (float)value.ByteValue; break; + case TypeCode.Int16: value.SingleValue = (float)value.Int16Value; break; + case TypeCode.UInt16: value.SingleValue = (float)value.UInt16Value; break; + case TypeCode.Int32: value.SingleValue = (float)value.Int32Value; break; + case TypeCode.UInt32: value.SingleValue = (float)value.UInt32Value; break; + case TypeCode.Int64: value.SingleValue = (float)value.Int64Value; break; + case TypeCode.UInt64: value.SingleValue = (float)value.UInt64Value; break; + case TypeCode.Single: break; + case TypeCode.Double: value.SingleValue = (float)value.DoubleValue; break; + default: UnreachableCase(fromCode); break; + } + break; + case TypeCode.Double: + switch (fromCode) + { + case TypeCode.Char: value.DoubleValue = (double)value.CharValue; break; + case TypeCode.SByte: value.DoubleValue = (double)value.SByteValue; break; + case TypeCode.Byte: value.DoubleValue = (double)value.ByteValue; break; + case TypeCode.Int16: value.DoubleValue = (double)value.Int16Value; break; + case TypeCode.UInt16: value.DoubleValue = (double)value.UInt16Value; break; + case TypeCode.Int32: value.DoubleValue = (double)value.Int32Value; break; + case TypeCode.UInt32: value.DoubleValue = (double)value.UInt32Value; break; + case TypeCode.Int64: value.DoubleValue = (double)value.Int64Value; break; + case TypeCode.UInt64: value.DoubleValue = (double)value.UInt64Value; break; + case TypeCode.Single: value.DoubleValue = (double)value.SingleValue; break; + case TypeCode.Double: break; + default: UnreachableCase(fromCode); break; + } + break; + } + } + + [DebuggerDisplay("{Code}")] + [StructLayout(LayoutKind.Explicit)] + internal struct PValue + { + [FieldOffset(0)] + public char CharValue; + [FieldOffset(0)] + public sbyte SByteValue; + [FieldOffset(0)] + public byte ByteValue; + [FieldOffset(0)] + public short Int16Value; + [FieldOffset(0)] + public ushort UInt16Value; + [FieldOffset(0)] + public int Int32Value; + [FieldOffset(0)] + public uint UInt32Value; + [FieldOffset(0)] + public long Int64Value; + [FieldOffset(0)] + public ulong UInt64Value; + [FieldOffset(0)] + public float SingleValue; + [FieldOffset(0)] + public double DoubleValue; + public PValue(char value) => CharValue = value; + public PValue(sbyte value) => SByteValue = value; + public PValue(byte value) => ByteValue = value; + public PValue(short value) => Int16Value = value; + public PValue(ushort value) => UInt16Value = value; + public PValue(int value) => Int32Value = value; + public PValue(uint value) => UInt32Value = value; + public PValue(long value) => Int64Value = value; + public PValue(ulong value) => UInt64Value = value; + public PValue(float value) => SingleValue = value; + public PValue(double value) => DoubleValue = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TryUnboxToPrimitiveValue(ref PValue value, object boxedValue, TypeCode code) + { + switch (code) + { + case TypeCode.Char: value.CharValue = (char)boxedValue; break; + case TypeCode.SByte: value.SByteValue = (sbyte)boxedValue; break; + case TypeCode.Byte: value.ByteValue = (byte)boxedValue; break; + case TypeCode.Int16: value.Int16Value = (short)boxedValue; break; + case TypeCode.UInt16: value.UInt16Value = (ushort)boxedValue; break; + case TypeCode.Int32: value.Int32Value = (int)boxedValue; break; + case TypeCode.UInt32: value.UInt32Value = (uint)boxedValue; break; + case TypeCode.Int64: value.Int64Value = (long)boxedValue; break; + case TypeCode.UInt64: value.UInt64Value = (ulong)boxedValue; break; + case TypeCode.Single: value.SingleValue = (float)boxedValue; break; + case TypeCode.Double: value.DoubleValue = (double)boxedValue; break; + default: return false; + } + return true; } - public static object GetZeroDefaultObject(TypeCode typeCode) + // todo: @perf think how to avoid this boxing thing altogether, maybe do not expose it at all to force client to handle the union values + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static object BoxPrimitiveValue(ref PValue value, TypeCode code) => code switch + { + TypeCode.Char => value.CharValue, + TypeCode.SByte => value.SByteValue, + TypeCode.Byte => value.ByteValue, + TypeCode.Int16 => value.Int16Value, + TypeCode.UInt16 => value.UInt16Value, + TypeCode.Int32 => value.Int32Value, + TypeCode.UInt32 => value.UInt32Value, + TypeCode.Int64 => value.Int64Value, + TypeCode.UInt64 => value.UInt64Value, + TypeCode.Single => value.SingleValue, + TypeCode.Double => value.DoubleValue, + _ => UnreachableCase(code, (object)null) + }; + + internal static bool ComparePrimitiveValues(ref PValue left, ref PValue right, TypeCode code, ExpressionType nodeType) { - return typeCode switch + switch (nodeType) { - TypeCode.Boolean => FalseObject, - TypeCode.SByte => ZeroDefault.Instance, - TypeCode.Byte => ZeroDefault.Instance, - TypeCode.Int16 => ZeroDefault.Instance, - TypeCode.UInt16 => ZeroDefault.Instance, - TypeCode.Int32 => ZeroDefault.Instance, - TypeCode.UInt32 => ZeroDefault.Instance, - TypeCode.Int64 => ZeroDefault.Instance, - TypeCode.UInt64 => ZeroDefault.Instance, - TypeCode.Single => ZeroDefault.Instance, - TypeCode.Double => ZeroDefault.Instance, - TypeCode.Decimal => ZeroDefault.Instance, - _ => null, - }; + case ExpressionType.GreaterThan: + return code switch + { + TypeCode.Char => left.CharValue > right.CharValue, + TypeCode.SByte => left.SByteValue > right.SByteValue, + TypeCode.Byte => left.ByteValue > right.ByteValue, + TypeCode.Int16 => left.Int16Value > right.Int16Value, + TypeCode.UInt16 => left.UInt16Value > right.UInt16Value, + TypeCode.Int32 => left.Int32Value > right.Int32Value, + TypeCode.UInt32 => left.UInt32Value > right.UInt32Value, + TypeCode.Int64 => left.Int64Value > right.Int64Value, + TypeCode.UInt64 => left.UInt64Value > right.UInt64Value, + TypeCode.Single => left.SingleValue > right.SingleValue, + TypeCode.Double => left.DoubleValue > right.DoubleValue, + _ => UnreachableCase(code, false) + }; + case ExpressionType.GreaterThanOrEqual: + return code switch + { + TypeCode.Char => left.CharValue >= right.CharValue, + TypeCode.SByte => left.SByteValue >= right.SByteValue, + TypeCode.Byte => left.ByteValue >= right.ByteValue, + TypeCode.Int16 => left.Int16Value >= right.Int16Value, + TypeCode.UInt16 => left.UInt16Value >= right.UInt16Value, + TypeCode.Int32 => left.Int32Value >= right.Int32Value, + TypeCode.UInt32 => left.UInt32Value >= right.UInt32Value, + TypeCode.Int64 => left.Int64Value >= right.Int64Value, + TypeCode.UInt64 => left.UInt64Value >= right.UInt64Value, + TypeCode.Single => left.SingleValue >= right.SingleValue, + TypeCode.Double => left.DoubleValue >= right.DoubleValue, + _ => UnreachableCase(code, false) + }; + case ExpressionType.LessThan: + return code switch + { + TypeCode.Char => left.CharValue < right.CharValue, + TypeCode.SByte => left.SByteValue < right.SByteValue, + TypeCode.Byte => left.ByteValue < right.ByteValue, + TypeCode.Int16 => left.Int16Value < right.Int16Value, + TypeCode.UInt16 => left.UInt16Value < right.UInt16Value, + TypeCode.Int32 => left.Int32Value < right.Int32Value, + TypeCode.UInt32 => left.UInt32Value < right.UInt32Value, + TypeCode.Int64 => left.Int64Value < right.Int64Value, + TypeCode.UInt64 => left.UInt64Value < right.UInt64Value, + TypeCode.Single => left.SingleValue < right.SingleValue, + TypeCode.Double => left.DoubleValue < right.DoubleValue, + _ => UnreachableCase(code, false) + }; + case ExpressionType.LessThanOrEqual: + return code switch + { + TypeCode.Char => left.CharValue <= right.CharValue, + TypeCode.SByte => left.SByteValue <= right.SByteValue, + TypeCode.Byte => left.ByteValue <= right.ByteValue, + TypeCode.Int16 => left.Int16Value <= right.Int16Value, + TypeCode.UInt16 => left.UInt16Value <= right.UInt16Value, + TypeCode.Int32 => left.Int32Value <= right.Int32Value, + TypeCode.UInt32 => left.UInt32Value <= right.UInt32Value, + TypeCode.Int64 => left.Int64Value <= right.Int64Value, + TypeCode.UInt64 => left.UInt64Value <= right.UInt64Value, + TypeCode.Single => left.SingleValue <= right.SingleValue, + TypeCode.Double => left.DoubleValue <= right.DoubleValue, + _ => UnreachableCase(code, false) + }; + default: return UnreachableCase(nodeType, false); + } + } + + /// Puts the result to the `left` para meter + internal static void DoArithmeticForPrimitiveValues(ref PValue left, ref PValue right, TypeCode code, ExpressionType nodeType) + { + switch (nodeType) + { + case ExpressionType.Add: + switch (code) + { + case TypeCode.Char: left.CharValue += right.CharValue; break; + // System Expression does not define the Add for sbyte and byte, but let's keep it here because it is allowed in C# and LightExpression + case TypeCode.SByte: left.SByteValue += right.SByteValue; break; + case TypeCode.Byte: left.ByteValue += right.ByteValue; break; + // the rest + case TypeCode.Int16: left.Int16Value += right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value += right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value += right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value += right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value += right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value += right.UInt64Value; break; + case TypeCode.Single: left.SingleValue += right.SingleValue; break; + case TypeCode.Double: left.DoubleValue += right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Subtract: + switch (code) + { + case TypeCode.Char: left.CharValue -= right.CharValue; break; + case TypeCode.SByte: left.SByteValue -= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue -= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value -= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value -= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value -= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value -= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value -= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value -= right.UInt64Value; break; + case TypeCode.Single: left.SingleValue -= right.SingleValue; break; + case TypeCode.Double: left.DoubleValue -= right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Multiply: + switch (code) + { + case TypeCode.Char: left.CharValue *= right.CharValue; break; + case TypeCode.SByte: left.SByteValue *= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue *= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value *= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value *= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value *= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value *= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value *= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value *= right.UInt64Value; break; + case TypeCode.Single: left.SingleValue *= right.SingleValue; break; + case TypeCode.Double: left.DoubleValue *= right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Divide: + switch (code) + { + case TypeCode.Char: left.CharValue /= right.CharValue; break; + case TypeCode.SByte: left.SByteValue /= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue /= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value /= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value /= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value /= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value /= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value /= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value /= right.UInt64Value; break; + case TypeCode.Single: left.SingleValue /= right.SingleValue; break; + case TypeCode.Double: left.DoubleValue /= right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Modulo: + switch (code) + { + case TypeCode.Char: left.CharValue %= right.CharValue; break; + case TypeCode.SByte: left.SByteValue %= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue %= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value %= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value %= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value %= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value %= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value %= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value %= right.UInt64Value; break; + case TypeCode.Single: left.SingleValue %= right.SingleValue; break; + case TypeCode.Double: left.DoubleValue %= right.DoubleValue; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.And: + switch (code) + { + case TypeCode.Char: left.CharValue &= right.CharValue; break; + case TypeCode.SByte: left.SByteValue &= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue &= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value &= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value &= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value &= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value &= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value &= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value &= right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.Or: + switch (code) + { + case TypeCode.Char: left.CharValue |= right.CharValue; break; + case TypeCode.SByte: left.SByteValue |= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue |= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value |= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value |= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value |= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value |= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value |= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value |= right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.ExclusiveOr: + switch (code) + { + case TypeCode.Char: left.CharValue ^= right.CharValue; break; + case TypeCode.SByte: left.SByteValue ^= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue ^= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value ^= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value ^= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value ^= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value ^= right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value ^= right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value ^= right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.LeftShift: + switch (code) + { + case TypeCode.Char: left.CharValue <<= right.CharValue; break; + case TypeCode.SByte: left.SByteValue <<= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue <<= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value <<= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value <<= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value <<= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value <<= (int)right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value <<= (int)right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value <<= (int)right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + case ExpressionType.RightShift: + switch (code) + { + case TypeCode.Char: left.CharValue >>= right.CharValue; break; + case TypeCode.SByte: left.SByteValue >>= right.SByteValue; break; + case TypeCode.Byte: left.ByteValue >>= right.ByteValue; break; + case TypeCode.Int16: left.Int16Value >>= right.Int16Value; break; + case TypeCode.UInt16: left.UInt16Value >>= right.UInt16Value; break; + case TypeCode.Int32: left.Int32Value >>= right.Int32Value; break; + case TypeCode.UInt32: left.UInt32Value >>= (int)right.UInt32Value; break; + case TypeCode.Int64: left.Int64Value >>= (int)right.Int64Value; break; + case TypeCode.UInt64: left.UInt64Value >>= (int)right.UInt64Value; break; + default: UnreachableCase(code); break; + } + break; + default: UnreachableCase(nodeType); break; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool TrySetPrimitiveValueToDefault(ref PValue value, TypeCode code) + { + switch (code) + { + case TypeCode.Char: value.CharValue = default; break; + case TypeCode.SByte: value.SByteValue = default; break; + case TypeCode.Byte: value.ByteValue = default; break; + case TypeCode.Int16: value.Int16Value = default; break; + case TypeCode.UInt16: value.UInt16Value = default; break; + case TypeCode.Int32: value.Int32Value = default; break; + case TypeCode.UInt32: value.UInt32Value = default; break; + case TypeCode.Int64: value.Int64Value = default; break; + case TypeCode.UInt64: value.UInt64Value = default; break; + case TypeCode.Single: value.SingleValue = default; break; + case TypeCode.Double: value.DoubleValue = default; break; + default: return false; + } + return true; } /// Fast, mostly negative check to skip or proceed with interpretation. @@ -6618,225 +7098,596 @@ public static bool IsCandidateForInterpretation(Expression expr) expr is BinaryExpression; } - /// Wraps `TryInterpretPrimitive` in the try catch block. - /// In case of exception FEC will emit the whole computation to throw exception in the invocation phase - public static bool TryInterpret(out object result, Expression expr) +#if INTERPRETATION_DIAGNOSTICS + + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in diagnostics only")] + [UnconditionalSuppressMessage("Trimming", "IL2075:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Used in diagnostics only")] + private static void CollectCallingTestName() { - Debug.Assert(expr.Type.IsPrimitive); - try + var stackTrace = new StackTrace(); + var frames = stackTrace.GetFrames(); + + // Skip this method and its immediate caller, and start from the outer callers + var found = false; + for (int i = 3; i < frames.Length; ++i) { - if (TryInterpretPrimitive(out result, expr)) - return true; + var frame = frames[i]; + var method = frame.GetMethod(); + var type = method?.DeclaringType; + if (type == null) + continue; + + var ifaces = type.GetInterfaces(); + if (ifaces.Length == 0) + continue; + + foreach (var iface in ifaces) + if (iface.Name.Contains("Test")) + { + found = true; + break; + } + + if (found) + { + found = true; + Console.WriteLine($"Interpretation in: {type.Name}.{method.Name}"); + break; // collect the first found thing in stack trace + } } - catch + + if (!found) { - // eat up the expression this time + var methodTrace = string.Join("; ", frames.Skip(3).Select(f => f.GetMethod().Name).ToArray()); + Console.WriteLine($"Interpretation in: not found in stack trace: {methodTrace}"); } - result = false; - return false; } +#endif - /// In case of exception FEC will emit the whole computation to throw exception in the invocation phase - public static bool TryInterpretBoolean(out bool result, Expression expr) + /// Wraps `TryInterpretPrimitive` in the try catch block. + /// In case of exception FEC will emit the whole computation to throw exception in the invocation phase + public static bool TryInterpretBool(out bool result, Expression expr, CompilerFlags flags) { Debug.Assert(expr.Type.IsPrimitive); + result = false; + if ((flags & CompilerFlags.DisableInterpreter) != 0) + return false; try { - if (TryInterpretPrimitive(out var resultObj, expr)) - { - result = resultObj == TrueObject; - return true; - } + var ok = TryInterpretBool(ref result, expr, expr.NodeType); +#if INTERPRETATION_DIAGNOSTICS + if (ok) CollectCallingTestName(); +#endif + return ok; } catch { - // eat up the expression this time + // ignore exception and return the false and rethrow the exception in the invocation time + return false; } - result = false; - return false; } + // todo: @perf try split to `TryInterpretBinary` overload to streamline the calls for TryEmitConditional and similar /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. - public static bool TryInterpretPrimitive(out object result, Expression expr) + internal static bool TryInterpretBool(ref bool resultBool, Expression expr, ExpressionType nodeType) { - result = null; - - // The order of the checks for the type of the expression is deliberate, - // because we are starting with complex expressions first. - // And for the simplest ones like a Constant? the method may not be even called. - // Instead, the Constant check and interpretation may be done inline. - - var nodeType = expr.NodeType; + // what operations are supported, to get the boolean result? + // yes: not, logical, comparison, default + // not: negate, arithmetic, convert to bool if (nodeType == ExpressionType.Not) { - var unaryExpr = (UnaryExpression)expr; - var operandExpr = unaryExpr.Operand; + var operandExpr = ((UnaryExpression)expr).Operand; if (operandExpr is ConstantExpression co) - { -#if LIGHT_EXPRESSION - if (co.RefField != null) return false; -#endif - result = (bool)co.Value ? FalseObject : TrueObject; - return true; - } - if (!TryInterpretPrimitive(out var boolVal, operandExpr)) + resultBool = (bool)co.Value; + else if (!TryInterpretBool(ref resultBool, operandExpr, operandExpr.NodeType)) return false; - result = boolVal == TrueObject ? FalseObject : TrueObject; + + resultBool = !resultBool; return true; } - if (IsLogical(nodeType)) + if (nodeType == ExpressionType.AndAlso | + nodeType == ExpressionType.OrElse) { var binaryExpr = (BinaryExpression)expr; // Interpreting the left part as the first candidate for the result var left = binaryExpr.Left; if (left is ConstantExpression lc) - { -#if LIGHT_EXPRESSION - if (lc.RefField != null) return false; -#endif - result = (bool)lc.Value ? TrueObject : FalseObject; - } - else if (!TryInterpretPrimitive(out result, left)) + resultBool = (bool)lc.Value; + else if (!TryInterpretBool(ref resultBool, left, left.NodeType)) return false; // Short circuit the interpretation, because this is an actual logic of these logical operations - if (result == TrueObject & nodeType == ExpressionType.OrElse || - result == FalseObject & nodeType == ExpressionType.AndAlso) + if (resultBool & nodeType == ExpressionType.OrElse || + !resultBool & nodeType == ExpressionType.AndAlso) return true; // If the first part is not enough to decide of the expression result, go right var right = binaryExpr.Right; if (right is ConstantExpression rc) { -#if LIGHT_EXPRESSION - if (rc.RefField != null) return false; -#endif - result = (bool)rc.Value ? TrueObject : FalseObject; + resultBool = (bool)rc.Value; return true; } - return TryInterpretPrimitive(out result, right); + return TryInterpretBool(ref resultBool, right, right.NodeType); } - var isComparison = IsComparison(nodeType); - if (isComparison || IsArithmetic(nodeType)) + if (nodeType == ExpressionType.Equal | + nodeType == ExpressionType.NotEqual) { var binaryExpr = (BinaryExpression)expr; - - // Interpreting left part + var right = binaryExpr.Right; var left = binaryExpr.Left; - object leftVal = null; - if (left is ConstantExpression lc) + var leftCode = Type.GetTypeCode(left.Type); + if (leftCode == TypeCode.Boolean) { -#if LIGHT_EXPRESSION - if (lc.RefField != null) return false; -#endif - leftVal = lc.Value; + var leftBool = false; + if (left is ConstantExpression lc) + leftBool = (bool)lc.Value; + else if (!TryInterpretBool(ref leftBool, left, left.NodeType)) + return false; + + var rightBool = false; + if (right is ConstantExpression rc) + rightBool = (bool)rc.Value; + else if (!TryInterpretBool(ref rightBool, right, right.NodeType)) + return false; + + resultBool = nodeType == ExpressionType.Equal ? leftBool == rightBool : leftBool != rightBool; + return true; } - else if (!TryInterpretPrimitive(out leftVal, left)) - return false; + if (leftCode == TypeCode.Int32) + { + var leftInt = 0; + if (left is ConstantExpression lc) + leftInt = (int)lc.Value; + else if (!TryInterpretInt(ref leftInt, left, left.NodeType)) + return false; - // Interpreting right part - var right = binaryExpr.Right; - object rightVal = null; - if (right is ConstantExpression rc) + var rightInt = 0; + if (right is ConstantExpression rc) + rightInt = (int)rc.Value; + else if (!TryInterpretInt(ref rightInt, right, right.NodeType)) + return false; + + resultBool = nodeType == ExpressionType.Equal ? leftInt == rightInt : leftInt != rightInt; + return true; + } + if (leftCode == TypeCode.Decimal) { -#if LIGHT_EXPRESSION - if (rc.RefField != null) return false; -#endif - rightVal = rc.Value; + decimal decimalLeft = default; + if (left is ConstantExpression lc) + decimalLeft = (decimal)lc.Value; + else if (!TryInterpretDecimal(ref decimalLeft, left, left.NodeType)) + return false; + + decimal rightDec = default; + if (right is ConstantExpression rc) + rightDec = (decimal)rc.Value; + else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) + return false; + + resultBool = nodeType == ExpressionType.Equal ? decimalLeft == rightDec : decimalLeft != rightDec; + return true; } - else if (!TryInterpretPrimitive(out rightVal, right)) - return false; + // not a bool, int, or decimal + { + PValue leftVal = default; + if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref leftVal, lc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref leftVal, left, leftCode, left.NodeType)) + return false; + + PValue rightVal = default; + if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) + return false; - // Now do the operation on the left and right - if (isComparison) + resultBool = leftCode switch + { + TypeCode.Char => leftVal.CharValue == rightVal.CharValue, + TypeCode.SByte => leftVal.SByteValue == rightVal.SByteValue, + TypeCode.Byte => leftVal.ByteValue == rightVal.ByteValue, + TypeCode.Int16 => leftVal.Int16Value == rightVal.Int16Value, + TypeCode.UInt16 => leftVal.UInt16Value == rightVal.UInt16Value, + TypeCode.Int32 => leftVal.Int32Value == rightVal.Int32Value, + TypeCode.UInt32 => leftVal.UInt32Value == rightVal.UInt32Value, + TypeCode.Int64 => leftVal.Int64Value == rightVal.Int64Value, + TypeCode.UInt64 => leftVal.UInt64Value == rightVal.UInt64Value, + TypeCode.Single => leftVal.SingleValue == rightVal.SingleValue, + TypeCode.Double => leftVal.DoubleValue == rightVal.DoubleValue, + _ => UnreachableCase(leftCode, false), + }; + resultBool = nodeType == ExpressionType.Equal ? resultBool : !resultBool; + return true; + } + } + + if (nodeType == ExpressionType.GreaterThan | + nodeType == ExpressionType.GreaterThanOrEqual | + nodeType == ExpressionType.LessThan | + nodeType == ExpressionType.LessThanOrEqual) + { + var binaryExpr = (BinaryExpression)expr; + var left = binaryExpr.Left; + var right = binaryExpr.Right; + var leftCode = Type.GetTypeCode(left.Type); + Debug.Assert(leftCode != TypeCode.Boolean, "Boolean values are not comparable by less or greater"); + + if (leftCode == TypeCode.Int32) { - if (nodeType == ExpressionType.Equal | nodeType == ExpressionType.NotEqual) + int intLeft = 0; + if (left is ConstantExpression lc) + intLeft = (int)lc.Value; + else if (!TryInterpretInt(ref intLeft, left, left.NodeType)) + return false; + + int rightInt = 0; + if (right is ConstantExpression rc) + rightInt = (int)rc.Value; + else if (!TryInterpretInt(ref rightInt, right, right.NodeType)) + return false; + + resultBool = nodeType switch { - var boolVal = leftVal.Equals(rightVal); - result = nodeType == ExpressionType.Equal - ? (boolVal ? TrueObject : FalseObject) - : (boolVal ? FalseObject : TrueObject); - } - else + ExpressionType.GreaterThan => intLeft > rightInt, + ExpressionType.GreaterThanOrEqual => intLeft >= rightInt, + ExpressionType.LessThan => intLeft < rightInt, + ExpressionType.LessThanOrEqual => intLeft <= rightInt, + _ => UnreachableCase(nodeType, false), + }; + return true; + } + if (leftCode == TypeCode.Decimal) + { + decimal leftDec = default; + if (left is ConstantExpression lc) + leftDec = (decimal)lc.Value; + else if (!TryInterpretDecimal(ref leftDec, left, left.NodeType)) + return false; + + decimal rightDec = default; + if (right is ConstantExpression rc) + rightDec = (decimal)rc.Value; + else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) + return false; + + resultBool = nodeType switch { - // Assuming that the both sides are of the same type, we can use only the left one for comparison - var cmp = leftVal as IComparable; - if (cmp == null) - return false; - var res = cmp.CompareTo(rightVal); - var boolVal = nodeType switch - { - ExpressionType.GreaterThan => res > 0, - ExpressionType.GreaterThanOrEqual => res >= 0, - ExpressionType.LessThan => res < 0, - ExpressionType.LessThanOrEqual => res <= 0, - _ => UnreachableCase(), - }; - result = boolVal ? TrueObject : FalseObject; - } + ExpressionType.GreaterThan => leftDec > rightDec, + ExpressionType.GreaterThanOrEqual => leftDec >= rightDec, + ExpressionType.LessThan => leftDec < rightDec, + ExpressionType.LessThanOrEqual => leftDec <= rightDec, + _ => UnreachableCase(nodeType, false), + }; return true; } + // not a bool, int, or decimal + { + PValue leftVal = default; + if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref leftVal, lc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref leftVal, left, leftCode, left.NodeType)) + return false; - // For Arithmetic - result = DoArithmeticOrNull(leftVal, rightVal, nodeType); - return result != null; + PValue rightVal = default; + if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) + return false; + + resultBool = ComparePrimitiveValues(ref leftVal, ref rightVal, leftCode, nodeType); + return true; + } } - if (nodeType == ExpressionType.Negate) + if (expr is ConstantExpression constExpr) { - var unaryExpr = (UnaryExpression)expr; - var operandExpr = unaryExpr.Operand; - object val = null; - if (operandExpr is ConstantExpression co) + resultBool = (bool)constExpr.Value; + return true; + } + + if (nodeType == ExpressionType.Default) + { + resultBool = false; + return true; + } + + return false; + } + + /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. + internal static bool TryInterpretDecimal(ref decimal resultDec, Expression expr, ExpressionType nodeType) + { + // What operations are supported, to get the decimal result: + // yes: arithmetic, negate, default, convert + // no: not, logical, comparison + + if (IsArithmeticBinary(nodeType)) + { + var binaryExpr = (BinaryExpression)expr; + var left = binaryExpr.Left; + if (left is ConstantExpression lc) + resultDec = (decimal)lc.Value; + else if (!TryInterpretDecimal(ref resultDec, left, left.NodeType)) + return false; + + decimal rightDec = default; + var right = binaryExpr.Right; + if (right is ConstantExpression rc) + rightDec = (decimal)rc.Value; + else if (!TryInterpretDecimal(ref rightDec, right, right.NodeType)) + return false; + + switch (nodeType) { -#if LIGHT_EXPRESSION - if (co.RefField != null) return false; -#endif - val = co.Value; + case ExpressionType.Add: resultDec += rightDec; break; + case ExpressionType.Subtract: resultDec -= rightDec; break; + case ExpressionType.Multiply: resultDec *= rightDec; break; + case ExpressionType.Divide: resultDec /= rightDec; break; + case ExpressionType.Modulo: resultDec %= rightDec; break; + default: UnreachableCase(nodeType); break; } - if (!TryInterpretPrimitive(out val, operandExpr)) + return true; + } + + if (nodeType == ExpressionType.Negate) + { + var operandExpr = ((UnaryExpression)expr).Operand; + if (operandExpr is ConstantExpression co) + resultDec = (decimal)co.Value; + else if (!TryInterpretDecimal(ref resultDec, operandExpr, operandExpr.NodeType)) return false; - result = DoNegateOrNull(val); - return result != null; + + resultDec = -resultDec; + return true; } if (expr is ConstantExpression constExpr) { -#if LIGHT_EXPRESSION - if (constExpr.RefField != null) return false; -#endif - result = constExpr.Value; - if (expr.Type == typeof(bool)) - result = (bool)result ? TrueObject : FalseObject; + resultDec = (decimal)constExpr.Value; return true; } if (nodeType == ExpressionType.Default) { - result = GetZeroDefaultObject(Type.GetTypeCode(expr.Type)); + resultDec = default; return true; } if (nodeType == ExpressionType.Convert) { - var unaryExpr = (UnaryExpression)expr; - var operandExpr = unaryExpr.Operand; + var operandExpr = ((UnaryExpression)expr).Operand; + var operandCode = Type.GetTypeCode(operandExpr.Type); + Debug.Assert(operandCode != TypeCode.Boolean, + "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in UValue"); + + PValue operandVal = default; + if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref operandVal, co.Value, operandCode) || + !TryInterpretPrimitiveValue(ref operandVal, operandExpr, operandCode, operandExpr.NodeType)) + return false; + + resultDec = operandCode switch + { + TypeCode.Char => operandVal.CharValue, + TypeCode.SByte => operandVal.SByteValue, + TypeCode.Byte => operandVal.ByteValue, + TypeCode.Int16 => operandVal.Int16Value, + TypeCode.UInt16 => operandVal.UInt16Value, + TypeCode.Int32 => operandVal.Int32Value, + TypeCode.UInt32 => operandVal.UInt32Value, + TypeCode.Int64 => operandVal.Int64Value, + TypeCode.UInt64 => operandVal.UInt64Value, + TypeCode.Single => (decimal)operandVal.SingleValue, + TypeCode.Double => (decimal)operandVal.DoubleValue, + _ => UnreachableCase(operandCode, default(decimal)), + }; + return true; + } + + return false; + } - if (!TryInterpretPrimitive(out result, operandExpr)) + /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. + /// Returns `false` if it failed to do so. + internal static bool TryInterpretInt(ref int resultInt, Expression expr, ExpressionType nodeType) + { + // What is supported for the int result + // yes: arithmetic, convert, default + // no: not, logical, comparison + if (IsArithmeticBinary(nodeType)) + { + var binaryExpr = (BinaryExpression)expr; + var left = binaryExpr.Left; + if (left is ConstantExpression lc) + resultInt = (int)lc.Value; + else if (!TryInterpretInt(ref resultInt, left, left.NodeType)) return false; - var exprType = expr.Type; - if (operandExpr.Type != exprType && !exprType.IsAssignableFrom(operandExpr.Type)) + + int rightVal = 0; + var right = binaryExpr.Right; + if (right is ConstantExpression rc) + rightVal = (int)rc.Value; + else if (!TryInterpretInt(ref rightVal, right, right.NodeType)) + return false; + + resultInt = nodeType switch + { + ExpressionType.Add => resultInt + rightVal, + ExpressionType.Subtract => resultInt - rightVal, + ExpressionType.Multiply => resultInt * rightVal, + ExpressionType.Divide => resultInt / rightVal, + ExpressionType.Modulo => resultInt % rightVal, + ExpressionType.LeftShift => resultInt << rightVal, + ExpressionType.RightShift => resultInt >> rightVal, + ExpressionType.And => resultInt & rightVal, + ExpressionType.Or => resultInt | rightVal, + ExpressionType.ExclusiveOr => resultInt ^ rightVal, + ExpressionType.Power => (int)Math.Pow(resultInt, rightVal), + _ => UnreachableCase(nodeType, 0), + }; + return true; + } + + if (nodeType == ExpressionType.Negate) + { + var operandExpr = ((UnaryExpression)expr).Operand; + if (operandExpr is ConstantExpression co) + resultInt = (int)co.Value; + else if (!TryInterpretInt(ref resultInt, operandExpr, operandExpr.NodeType)) + return false; + + resultInt = -resultInt; + return true; + } + + if (expr is ConstantExpression constExpr) + { + resultInt = (int)constExpr.Value; + return true; + } + + if (nodeType == ExpressionType.Default) + { + resultInt = 0; + return true; + } + + if (nodeType == ExpressionType.Convert) + { + var operandExpr = ((UnaryExpression)expr).Operand; + var operandCode = Type.GetTypeCode(operandExpr.Type); + Debug.Assert(operandCode != TypeCode.Boolean, + "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in PValue"); + + if (operandCode != TypeCode.Decimal) { - var converted = System.Convert.ChangeType(result, exprType); - result = exprType != typeof(bool) ? converted : (bool)converted ? TrueObject : FalseObject; + PValue operandVal = default; + if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref operandVal, co.Value, operandCode) || + !TryInterpretPrimitiveValue(ref operandVal, operandExpr, operandCode, operandExpr.NodeType)) + return false; + + resultInt = operandCode switch + { + TypeCode.Char => operandVal.CharValue, + TypeCode.SByte => operandVal.SByteValue, + TypeCode.Byte => operandVal.ByteValue, + TypeCode.Int16 => operandVal.Int16Value, + TypeCode.UInt16 => operandVal.UInt16Value, + TypeCode.Int32 => operandVal.Int32Value, + TypeCode.UInt32 => (int)operandVal.UInt32Value, + TypeCode.Int64 => (int)operandVal.Int64Value, + TypeCode.UInt64 => (int)operandVal.UInt64Value, + TypeCode.Single => (int)operandVal.SingleValue, + TypeCode.Double => (int)operandVal.DoubleValue, + _ => UnreachableCase(operandCode, 0), + }; + return true; } + // then for the decimal + { + decimal resultDec = default; + if (operandExpr is ConstantExpression co) + resultDec = (decimal)co.Value; + else if (!TryInterpretDecimal(ref resultDec, operandExpr, operandExpr.NodeType)) + return false; + + resultInt = (int)resultDec; + return true; + } + } + return false; + } + + /// Tries to interpret the expression of the Primitive type of Constant, Convert, Logical, Comparison, Arithmetic. + /// Returns `false` if it is failed to do so. + internal static bool TryInterpretPrimitiveValue(ref PValue result, Expression expr, TypeCode exprCode, ExpressionType nodeType) + { + // What is supported for the non-boolean, non-decimal result + // yes: arithmetic, convert, default + // no: not, logical, comparison + if (IsArithmeticBinary(nodeType)) + { + var binaryExpr = (BinaryExpression)expr; + var left = binaryExpr.Left; + var leftCode = Type.GetTypeCode(left.Type); + if (left is ConstantExpression lc && !TryUnboxToPrimitiveValue(ref result, lc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref result, left, leftCode, left.NodeType)) + return false; + + PValue rightVal = default; + var right = binaryExpr.Right; + // Using the leftCode to interpret the right part of the binary expression, + // because for supported operations left and right types are the same + if (right is ConstantExpression rc && !TryUnboxToPrimitiveValue(ref rightVal, rc.Value, leftCode) || + !TryInterpretPrimitiveValue(ref rightVal, right, leftCode, right.NodeType)) + return false; + + DoArithmeticForPrimitiveValues(ref result, ref rightVal, leftCode, nodeType); + return true; + } + + if (nodeType == ExpressionType.Negate) + { + var operandExpr = ((UnaryExpression)expr).Operand; + var operandCode = Type.GetTypeCode(operandExpr.Type); + if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref result, co.Value, operandCode) || + !TryInterpretPrimitiveValue(ref result, operandExpr, operandCode, operandExpr.NodeType)) + return false; + + NegatePrimitiveValue(ref result, operandCode); return true; } - result = null; + if (expr is ConstantExpression constExpr) + return TryUnboxToPrimitiveValue(ref result, constExpr.Value, exprCode); + + if (nodeType == ExpressionType.Default) + return TrySetPrimitiveValueToDefault(ref result, exprCode); + + if (nodeType == ExpressionType.Convert) + { + var operandExpr = ((UnaryExpression)expr).Operand; + var operandCode = Type.GetTypeCode(operandExpr.Type); + Debug.Assert(operandCode != TypeCode.Boolean, + "Operand may be a decimal but cannot be bool, because there is no conversation from bool to the types in PValue"); + + if (operandCode != TypeCode.Decimal) + { + if (operandExpr is ConstantExpression co && !TryUnboxToPrimitiveValue(ref result, co.Value, operandCode) || + !TryInterpretPrimitiveValue(ref result, operandExpr, operandCode, operandExpr.NodeType)) + return false; + + if (exprCode != operandCode) + ConvertPrimitiveValueFromTo(ref result, operandCode, exprCode); + return true; + } + // then for the decimal + { + decimal operandDec = default; + if (operandExpr is ConstantExpression co) + operandDec = (decimal)co.Value; + else if (!TryInterpretDecimal(ref operandDec, operandExpr, operandExpr.NodeType)) + return false; + + switch (exprCode) + { + case TypeCode.Char: result.CharValue = (char)operandDec; break; + case TypeCode.SByte: result.SByteValue = (sbyte)operandDec; break; + case TypeCode.Byte: result.ByteValue = (byte)operandDec; break; + case TypeCode.Int16: result.Int16Value = (short)operandDec; break; + case TypeCode.UInt16: result.UInt16Value = (ushort)operandDec; break; + case TypeCode.Int32: result.Int32Value = (int)operandDec; break; + case TypeCode.UInt32: result.UInt32Value = (uint)operandDec; break; + case TypeCode.Int64: result.Int64Value = (long)operandDec; break; + case TypeCode.UInt64: result.UInt64Value = (ulong)operandDec; break; + case TypeCode.Single: result.SingleValue = (float)operandDec; break; + case TypeCode.Double: result.DoubleValue = (double)operandDec; break; + default: + // todo: @feature #472 support conversion to nullable, put nullable marker into PValue or something + return false; + } + return true; + } + } return false; } } @@ -6875,7 +7726,7 @@ internal static bool IsFloatingPoint(this Type type) => type == typeof(float) || type == typeof(double); - internal static bool IsPrimitiveOrDecimalWithZeroDefault(this Type type) + internal static bool IsPrimitiveWithZeroDefaultExceptDecimal(this Type type) { switch (Type.GetTypeCode(type)) { @@ -6891,10 +7742,7 @@ internal static bool IsPrimitiveOrDecimalWithZeroDefault(this Type type) case TypeCode.UInt64: case TypeCode.Single: case TypeCode.Double: - case TypeCode.Decimal: return true; - // case TypeCode.DateTime: - // case TypeCode.String: default: return false; } diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.csproj b/src/FastExpressionCompiler/FastExpressionCompiler.csproj index a91651d9..01d4ae48 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.csproj +++ b/src/FastExpressionCompiler/FastExpressionCompiler.csproj @@ -1,11 +1,11 @@  - net472;netstandard2.0;netstandard2.1;net6.0;net7.0 - net472;netstandard2.0;netstandard2.1;net6.0;net8.0 - net472;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0 + net472;netstandard2.0;netstandard2.1;net6.0;net8.0;net9.0 + net472;net9.0 5.2.0 - preview-02 + preview-03 + FastExpressionCompiler $(Product) $(Product) @@ -14,7 +14,8 @@ ).GetConstructor(new[] { typeof(void*), typeof(int) }); Debug.Assert(spanConstructor != null); - // Set capcity to estimated size = 1 + 1 + 1 + 5 + 1 = 9 bytes + a small buffer + // Set capacity to estimated size = 1 + 1 + 1 + 5 + 1 = 9 bytes + a small buffer var il = dynamicMethod.GetILGenerator(16); // IL to replicate: return new Span(Unsafe.AsPointer(ref this), StackCapacity); diff --git a/src/FastExpressionCompiler/TestTools.cs b/src/FastExpressionCompiler/TestTools.cs index cc3919b2..990be070 100644 --- a/src/FastExpressionCompiler/TestTools.cs +++ b/src/FastExpressionCompiler/TestTools.cs @@ -8,6 +8,7 @@ using System.IO; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using static System.Environment; #if LIGHT_EXPRESSION namespace FastExpressionCompiler.LightExpression; @@ -530,6 +531,10 @@ public enum AssertKind IsNotNullNullable, AreEqual, AreNotEqual, + AreSame, + AreNotSame, + AreEqualCollections, + GreaterOrEqual, Throws, } @@ -585,8 +590,13 @@ public static implicit operator TestContext(TestRun t) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Fail(string testName, int sourceLineNumber, AssertKind assertKind, string message) { +#if DEBUG + // When debugging raise the fail immediately as excpetion to avoid false sense of security + throw new AssertionException($"`{testName}` failed at line {sourceLineNumber}:{NewLine}{message}{NewLine}"); +#else TestRun.Failures.Add(new TestFailure(testName, sourceLineNumber, assertKind, message)); return false; +#endif } /// Always fails with the provided message @@ -598,11 +608,10 @@ public void Fail(string message, /// Checks if `actual is true`. Method returns `bool` so the latter test logic may depend on it, e.g. to return early [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsTrue(bool actual, - [CallerArgumentExpression(nameof(actual))] string actualName = "", [CallerMemberName] string testName = "", - [CallerLineNumber] int sourceLineNumber = -1) => - actual || - Fail(testName, sourceLineNumber, AssertKind.IsTrue, - $"Expected `IsTrue({actualName})`, but found false"); + [CallerArgumentExpression(nameof(actual))] string actualName = "", + [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) => + actual || Fail(testName, sourceLineNumber, AssertKind.IsTrue, + $"Expected `IsTrue({actualName})`, but found false"); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool AreEqual(T expected, T actual, @@ -610,48 +619,39 @@ public bool AreEqual(T expected, T actual, [CallerArgumentExpression(nameof(actual))] string actualName = "", [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) => Equals(expected, actual) || Fail(testName, sourceLineNumber, AssertKind.AreEqual, - $"Expected `AreEqual({expectedName}, {actualName})`, but found `{expected.ToCode()}` is Not equal to `{actual.ToCode()}`"); + $"Expected `AreEqual(expected: {expectedName}, actual: {actualName})`,{NewLine} but found expected: `{expected.ToCode()}` and actual: `{actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool AreSame(T expected, T actual, - [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", - [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") where T : class => - ReferenceEquals(expected, actual) ? true : throw new AssertionException( + public bool AreSame(T expected, T actual, + [CallerArgumentExpression(nameof(expected))] string expectedName = "", + [CallerArgumentExpression(nameof(actual))] string actualName = "", + [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) where T : class => + ReferenceEquals(expected, actual) || Fail(testName, sourceLineNumber, AssertKind.AreSame, $"Expected `AreSame({expectedName}, {actualName})`, but found `{expected.ToCode()}` is Not the same `{actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool AreNotSame(T expected, T actual, - [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", - [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") where T : class => - !ReferenceEquals(expected, actual) ? true : throw new AssertionException( + public bool AreNotSame(T expected, T actual, + [CallerArgumentExpression(nameof(expected))] string expectedName = "", + [CallerArgumentExpression(nameof(actual))] string actualName = "", + [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) where T : class => + !ReferenceEquals(expected, actual) || Fail(testName, sourceLineNumber, AssertKind.AreNotSame, $"Expected `AreNotSame({expectedName}, {actualName})`, but found `{expected.ToCode()}` is same as `{actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool AreEqual(T expected, T actual, - [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", - [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") => - Equals(expected, actual) ? true : throw new AssertionException( - $"Expected `AreEqual({expectedName}, {actualName})`, but found `{expected.ToCode()}` is Not equal to `{actual.ToCode()}`"); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool AreNotEqual(T expected, T actual, - [CallerArgumentExpression(nameof(expected))] string expectedName = "expected", [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") => - !Equals(expected, actual) ? true : throw new AssertionException( + public bool AreNotEqual(T expected, T actual, + [CallerArgumentExpression(nameof(expected))] string expectedName = "", + [CallerArgumentExpression(nameof(actual))] string actualName = "", + [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) => + !Equals(expected, actual) || Fail(testName, sourceLineNumber, AssertKind.AreNotEqual, $"Expected `AreNotEqual({expectedName}, {actualName})`, but found `{expected.ToCode()}` is equal to `{actual.ToCode()}`"); public record struct ItemsCompared(int Index, bool IsEqual, T Expected, T Actual); /// Should cover the case with the `expected` to be an array as well. - public static bool AreEqual(IEnumerable expected, IEnumerable actual, - [CallerArgumentExpression(nameof(expected))] string expectedName = "expected", - [CallerArgumentExpression(nameof(actual))] string actualName = "actual") + public bool AreEqual(IEnumerable expected, IEnumerable actual, + [CallerArgumentExpression(nameof(expected))] string expectedName = "", + [CallerArgumentExpression(nameof(actual))] string actualName = "", + [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) { var expectedEnumerator = expected.GetEnumerator(); var actualEnumerator = actual.GetEnumerator(); @@ -791,110 +791,101 @@ public static bool AreEqual(IEnumerable expected, IEnumerable actual, sb.AppendLine($"{index,4}{(isEqual ? " " : " -> ")}{expectedItem.ToCode(),16},{actualItem.ToCode(),16}"); } - throw new AssertionException(sb.ToString()); + return Fail(testName, sourceLineNumber, AssertKind.AreEqualCollections, sb.ToString()); } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool AreEqual(T[] expected, T[] actual, - [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", - [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") => - AreEqual((IEnumerable)expected, actual, expectedName, actualName); + public bool AreEqual(T[] expected, T[] actual, + [CallerArgumentExpression(nameof(expected))] string expectedName = "", + [CallerArgumentExpression(nameof(actual))] string actualName = "", + [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) => + AreEqual((IEnumerable)expected, actual, expectedName, actualName, testName, sourceLineNumber); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool GreaterOrEqual(T expected, T actual, - [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", - [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") + public bool GreaterOrEqual(T expected, T actual, + [CallerArgumentExpression(nameof(expected))] string expectedName = "", + [CallerArgumentExpression(nameof(actual))] string actualName = "", + [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) where T : IComparable => - expected.CompareTo(actual) >= 0 ? true : throw new AssertionException( + expected.CompareTo(actual) >= 0 || Fail(testName, sourceLineNumber, AssertKind.GreaterOrEqual, $"Expected `GreaterOrEqual({expectedName}, {actualName})`, but found `{expected.ToCode()} < {actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Less(T expected, T actual, + public bool Less(T expected, T actual, [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", + string expectedName = "", [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") + string actualName = "") where T : IComparable => expected.CompareTo(actual) < 0 ? true : throw new AssertionException( $"Expected `Less({expectedName}, {actualName})`, but found `{expected.ToCode()} >= {actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Greater(T expected, T actual, + public bool Greater(T expected, T actual, [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", + string expectedName = "", [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") + string actualName = "") where T : IComparable => expected.CompareTo(actual) > 0 ? true : throw new AssertionException( $"Expected `Greater({expectedName}, {actualName})`, but found `{expected.ToCode()} <= {actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool LessOrEqual(T expected, T actual, + public bool LessOrEqual(T expected, T actual, [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", + string expectedName = "", [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") + string actualName = "") where T : IComparable => expected.CompareTo(actual) <= 0 ? true : throw new AssertionException( $"Expected `LessOrEqual({expectedName}, {actualName})`, but found `{expected.ToCode()} > {actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNull(T actual, - [CallerArgumentExpression(nameof(actual))] string actualName = "actual", + [CallerArgumentExpression(nameof(actual))] string actualName = "", [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) where T : class => actual is null || Fail(testName, sourceLineNumber, AssertKind.IsNull, $"Expected `IsNull({actualName})`, but found not null `{actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNull(T? actual, - [CallerArgumentExpression(nameof(actual))] string actualName = "actual", + [CallerArgumentExpression(nameof(actual))] string actualName = "", [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) where T : struct => !actual.HasValue || Fail(testName, sourceLineNumber, AssertKind.IsNullNullable, $"Expected the nullable `IsNull({actualName})`, but found it has a value `{actual.Value}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNotNull(T actual, - [CallerArgumentExpression(nameof(actual))] string actualName = "actual", + [CallerArgumentExpression(nameof(actual))] string actualName = "", [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) where T : class => actual is not null || Fail(testName, sourceLineNumber, AssertKind.IsNotNull, $"Expected `IsNotNull({actualName})`, but found null"); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNotNull(T? actual, - [CallerArgumentExpression(nameof(actual))] string actualName = "actual", + [CallerArgumentExpression(nameof(actual))] string actualName = "", [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) where T : struct => actual.HasValue || Fail(testName, sourceLineNumber, AssertKind.IsNotNullNullable, $"Expected the nullable `IsNotNull({actualName})`, but found null"); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsTrue(bool actual, - [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") => - actual ? true : throw new AssertionException( - $"Expected `IsTrue({actualName})`, but found false"); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsFalse(bool actual, - [CallerArgumentExpression(nameof(actual))] string actualName = "actual", + [CallerArgumentExpression(nameof(actual))] string actualName = "", [CallerMemberName] string testName = "", [CallerLineNumber] int sourceLineNumber = -1) => !actual || Fail(testName, sourceLineNumber, AssertKind.IsFalse, $"Expected `IsFalse({actualName})`, but found true"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsInstanceOf(object actual, + public bool IsInstanceOf(object actual, [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") => + string actualName = "") => actual is T ? true : throw new AssertionException( $"Expected `IsInstanceOf<{typeof(T).ToCode()}>({actualName})`, but found `IsInstanceOf<{actual?.GetType().ToCode() ?? "_"}>({actual.ToCode()})`"); - public static E Throws(Action action, + public E Throws(Action action, [CallerArgumentExpression(nameof(action))] string actionName = "") where E : Exception @@ -916,29 +907,29 @@ public static E Throws(Action action, } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(string expected, string actual, + public bool Contains(string expected, string actual, [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", + string expectedName = "", [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") => + string actualName = "") => actual.Contains(expected) ? true : throw new AssertionException( $"Expected string `Contains({expectedName}, {actualName})`, but found expected `{expected.ToCode()}` is not in `{actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool DoesNotContain(string expected, string actual, + public bool DoesNotContain(string expected, string actual, [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", + string expectedName = "", [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") => + string actualName = "") => !actual.Contains(expected) ? true : throw new AssertionException( $"Expected string `DoesNotContain({expectedName}, {actualName})`, but found expected `{expected.ToCode()}` is in `{actual.ToCode()}`"); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool StartsWith(string expected, string actual, + public bool StartsWith(string expected, string actual, [CallerArgumentExpression(nameof(expected))] - string expectedName = "expected", + string expectedName = "", [CallerArgumentExpression(nameof(actual))] - string actualName = "actual") => + string actualName = "") => actual.StartsWith(expected) ? true : throw new AssertionException( $"Expected string `StartsWith({expectedName}, {actualName})`, but found expected `{expected.ToCode()}` is not at start of `{actual.ToCode()}`"); } @@ -989,7 +980,7 @@ public void Run(T test, TestTracking tracking = TestTracking.TrackFailedTests ++FailedTestCount; if (testStopException != null) - Console.WriteLine($"Unexpected exception in test '{testsName}':{Environment.NewLine}'{testStopException}'"); + Console.WriteLine($"Unexpected exception in test '{testsName}':{NewLine}'{testStopException}'"); if (testFailureCount > 0) { @@ -997,7 +988,7 @@ public void Run(T test, TestTracking tracking = TestTracking.TrackFailedTests for (var i = 0; i < testFailureCount; ++i) { ref var f = ref Failures.GetSurePresentItemRef(failureCount + i); - Console.WriteLine($"#{i} at line {f.SourceLineNumber}:{Environment.NewLine}'{f.Message}'"); + Console.WriteLine($"{i}. `{f.TestMethodName}` failed at line {f.SourceLineNumber}:{NewLine}{f.Message}{NewLine}"); } } } @@ -1005,4 +996,4 @@ public void Run(T test, TestTracking tracking = TestTracking.TrackFailedTests } } -#pragma warning restore CS1591 +#pragma warning restore CS1591 \ No newline at end of file diff --git a/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj b/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj index 29a64710..608e0b1d 100644 --- a/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj +++ b/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj @@ -1,7 +1,8 @@  - $(LatestSupportedNet);net8.0 + net9.0;net8.0 + net9.0 Exe false diff --git a/test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs b/test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs index 341398d0..77026e88 100644 --- a/test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs +++ b/test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs @@ -95,13 +95,56 @@ Using this latter (System) overload drastically slows down the compilation but r | InvokeCompiledFast | 0.1105 ns | 0.0360 ns | 0.0799 ns | 0.0689 ns | 0.22 | 0.16 | 1 | 1 | - | NA | | InvokeCompiledFast_DisableInterpreter | 1.0607 ns | 0.0540 ns | 0.0887 ns | 1.0301 ns | 2.13 | 0.34 | 3 | 2 | - | NA | +## Comparing to the direct interpretation + +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +|-------------------------------------- |-----------:|----------:|----------:|-----------:|-------:|--------:|-----:|-------:|----------:|------------:| +| InvokeCompiled | 0.3347 ns | 0.0313 ns | 0.0373 ns | 0.3241 ns | 1.01 | 0.15 | 3 | - | - | NA | +| InvokeCompiledFast | 0.0269 ns | 0.0214 ns | 0.0229 ns | 0.0198 ns | 0.08 | 0.07 | 1 | - | - | NA | +| InvokeCompiledFast_DisableInterpreter | 0.9317 ns | 0.0485 ns | 0.0558 ns | 0.9097 ns | 2.81 | 0.31 | 4 | - | - | NA | +| Interpret | 81.7969 ns | 0.6588 ns | 0.5501 ns | 81.7534 ns | 246.89 | 23.21 | 5 | 0.0076 | 48 B | NA | +| JustFunc | 0.0335 ns | 0.0219 ns | 0.0499 ns | 0.0000 ns | 0.10 | 0.15 | 2 | - | - | NA | + +## Comparing basic interpretation without boxing all-in-one and new one split by types: bool, decimal, the rest + +DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Allocated | Alloc Ratio | +|-------------- |---------:|---------:|---------:|---------:|------:|--------:|-----:|----------:|------------:| +| Interpret | 93.63 ns | 1.819 ns | 3.838 ns | 92.05 ns | 1.00 | 0.06 | 2 | - | NA | +| Interpret_new | 68.28 ns | 1.350 ns | 1.554 ns | 68.08 ns | 0.73 | 0.03 | 1 | - | NA | + +## Removing the type code from the PValue struct, no changes + +DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Allocated | Alloc Ratio | +|-------------- |---------:|---------:|---------:|------:|--------:|-----:|----------:|------------:| +| Interpret | 86.94 ns | 1.015 ns | 0.847 ns | 1.00 | 0.01 | 2 | - | NA | +| Interpret_new | 64.04 ns | 0.838 ns | 1.446 ns | 0.74 | 0.02 | 1 | - | NA | + +## Specializing for int does it job + +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Allocated | Alloc Ratio | +|-------------- |---------:|---------:|---------:|---------:|------:|--------:|-----:|----------:|------------:| +| Interpret | 94.70 ns | 1.923 ns | 2.936 ns | 95.38 ns | 1.00 | 0.04 | 2 | - | NA | +| Interpret_new | 57.18 ns | 1.175 ns | 3.075 ns | 55.78 ns | 0.60 | 0.04 | 1 | - | NA | + +## More int specialization + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Allocated | Alloc Ratio | +|-------------- |---------:|---------:|---------:|------:|--------:|-----:|----------:|------------:| +| Interpret | 88.66 ns | 1.701 ns | 1.747 ns | 1.00 | 0.03 | 2 | - | NA | +| Interpret_new | 48.80 ns | 0.671 ns | 0.595 ns | 0.55 | 0.01 | 1 | - | NA | + */ [MemoryDiagnoser, RankColumn] -[HardwareCounters(HardwareCounter.BranchInstructions)] +// [HardwareCounters(HardwareCounter.BranchInstructions)] // [SimpleJob(RuntimeMoniker.Net90)] // [SimpleJob(RuntimeMoniker.Net80)] public class Issue468_InvokeCompiled_vs_InvokeCompiledFast { + Expression> _expr; Func _compiled, _compiledFast, _compiledFast_DisableInterpreter, _justFunc = static () => true; [GlobalSetup] @@ -111,26 +154,39 @@ public void Setup() _compiled = expr.CompileSys(); _compiledFast = expr.CompileFast(); _compiledFast_DisableInterpreter = expr.CompileFast(flags: CompilerFlags.DisableInterpreter); + _expr = expr; } - [Benchmark(Baseline = true)] + // [Benchmark(Baseline = true)] public bool InvokeCompiled() { return _compiled(); } - [Benchmark] + // [Benchmark] public bool InvokeCompiledFast() { return _compiledFast(); } - [Benchmark] + // [Benchmark] public bool InvokeCompiledFast_DisableInterpreter() { return _compiledFast_DisableInterpreter(); } + // [Benchmark(Baseline = true)] + // public bool Interpret() + // { + // return ExpressionCompiler.Interpreter.TryInterpretBool(out var result, _expr.Body) && result; + // } + + [Benchmark] + public bool Interpret_new() + { + return ExpressionCompiler.Interpreter.TryInterpretBool(out var result, _expr.Body, CompilerFlags.Default) && result; + } + // [Benchmark] public bool JustFunc() { @@ -174,7 +230,7 @@ public bool JustFunc() | CompiledFast_WithEvalFlag | 171.8 ns | 3.49 ns | 9.44 ns | 167.6 ns | 1.00 | 0.08 | 1 | 0.0610 | - | 384 B | 1.00 | -## Now we're talking (after small interpretator optimizations) +## Now we're talking (after small interpreter optimizations) DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 @@ -183,6 +239,16 @@ public bool JustFunc() | Compiled | 22,937.50 ns | 447.883 ns | 784.432 ns | 22,947.67 ns | 230.86 | 14.14 | 3 | 0.6714 | 0.6409 | 4232 B | 88.17 | | CompiledFast | 99.62 ns | 2.044 ns | 5.275 ns | 97.03 ns | 1.00 | 0.07 | 1 | 0.0076 | - | 48 B | 1.00 | | CompiledFast_DisableInterpreter | 3,010.37 ns | 60.174 ns | 91.893 ns | 3,010.03 ns | 30.30 | 1.80 | 2 | 0.1755 | 0.1678 | 1143 B | 23.81 | + + +## New results after cleanup + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +|-------------------------------- |-------------:|-----------:|-----------:|-------:|--------:|-----:|-------:|-------:|----------:|------------:| +| Compiled | 22,844.78 ns | 327.497 ns | 290.317 ns | 351.80 | 6.40 | 3 | 0.6714 | 0.6409 | 4232 B | NA | +| CompiledFast_DisableInterpreter | 3,089.13 ns | 54.757 ns | 48.541 ns | 47.57 | 0.96 | 2 | 0.1793 | 0.1755 | 1144 B | NA | +| CompiledFast | 64.95 ns | 1.082 ns | 0.903 ns | 1.00 | 0.02 | 1 | - | - | - | NA | + */ [MemoryDiagnoser, RankColumn] // [SimpleJob(RuntimeMoniker.Net90)] @@ -203,41 +269,15 @@ public object Compiled() return _expr.Compile(); } - [Benchmark(Baseline = true)] - public object CompiledFast() - { - return _expr.CompileFast(); - } - [Benchmark] public object CompiledFast_DisableInterpreter() { return _expr.CompileFast(flags: CompilerFlags.DisableInterpreter); } -} - -[MemoryDiagnoser, RankColumn] -// [SimpleJob(RuntimeMoniker.Net90)] -// [SimpleJob(RuntimeMoniker.Net80)] -public class Issue468_Eval_Optimization -{ - Expression> _expr; - - [GlobalSetup] - public void Setup() - { - _expr = IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.CreateExpression(); - } - // [Benchmark(Baseline = true)] - // public object Baseline() - // { - // return ExpressionCompiler.Interpreter.TryEvalPrimitive_OLD(out var result, _expr) ? result : null; - // } - - [Benchmark] - public object Optimized() + [Benchmark(Baseline = true)] + public object CompiledFast() { - return ExpressionCompiler.Interpreter.TryInterpretPrimitive(out var result, _expr) ? result : null; + return _expr.CompileFast(); } } diff --git a/test/FastExpressionCompiler.Benchmarks/Program.cs b/test/FastExpressionCompiler.Benchmarks/Program.cs index 2a68e389..68788ea4 100644 --- a/test/FastExpressionCompiler.Benchmarks/Program.cs +++ b/test/FastExpressionCompiler.Benchmarks/Program.cs @@ -25,8 +25,8 @@ public static void Main() //-------------------------------------------- - // BenchmarkRunner.Run(); - BenchmarkRunner.Run(); + BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); diff --git a/test/FastExpressionCompiler.IssueTests/FastExpressionCompiler.IssueTests.csproj b/test/FastExpressionCompiler.IssueTests/FastExpressionCompiler.IssueTests.csproj index 8c18f4d5..bf0aca82 100644 --- a/test/FastExpressionCompiler.IssueTests/FastExpressionCompiler.IssueTests.csproj +++ b/test/FastExpressionCompiler.IssueTests/FastExpressionCompiler.IssueTests.csproj @@ -1,6 +1,7 @@  - net472;net9.0;net8.0;net6.0 + net472;net6.0;net8.0;net9.0 + net472;net9.0 diff --git a/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs b/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs index 24d7b99e..7873940d 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs @@ -8,8 +8,6 @@ using System.Text; using System.Runtime.InteropServices; - - using SysExpr = System.Linq.Expressions.Expression; #pragma warning disable CS0164, CS0649 diff --git a/test/FastExpressionCompiler.IssueTests/Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.cs b/test/FastExpressionCompiler.IssueTests/Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.cs index 1d8e7881..2215104f 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET.cs @@ -40,7 +40,7 @@ public static Expression> CreateExpression( e[8] = MakeBinary(ExpressionType.Equal, e[9] = Constant(42), #if LIGHT_EXPRESSION - e[10] = !addClosure ? Constant(42) : ConstantRef(42) + e[10] = !addClosure ? Constant(42) : ConstantRef(42, out _) #else e[10] = Constant(42) #endif diff --git a/test/FastExpressionCompiler.IssueTests/Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation.cs b/test/FastExpressionCompiler.IssueTests/Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation.cs index 0453e790..4eaf22a8 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation.cs @@ -14,6 +14,7 @@ public struct Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_ { public void Run(TestRun t) { + Logical_expression_started_with_not_Without_Interpreter_due_param_use(t); Logical_expression_started_with_not(t); } @@ -21,7 +22,8 @@ public void Logical_expression_started_with_not(TestContext t) { var p = Parameter(typeof(bool), "p"); var expr = Lambda>( - Not(AndAlso(Constant(true), p)), + OrElse( + Not(AndAlso(Constant(true), Constant(false))), p), p); expr.PrintCSharp(); @@ -29,11 +31,31 @@ public void Logical_expression_started_with_not(TestContext t) var fs = expr.CompileSys(); fs.PrintIL(); t.IsTrue(fs(true)); - t.IsFalse(fs(false)); + t.IsTrue(fs(false)); var ff = expr.CompileFast(false); ff.PrintIL(); t.IsTrue(ff(true)); t.IsTrue(ff(false)); } + + public void Logical_expression_started_with_not_Without_Interpreter_due_param_use(TestContext t) + { + var p = Parameter(typeof(bool), "p"); + var expr = Lambda>( + Not(AndAlso(Constant(true), p)), + p); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + t.IsFalse(fs(true)); + t.IsTrue(fs(false)); + + var ff = expr.CompileFast(false); + ff.PrintIL(); + t.IsFalse(ff(true)); + t.IsTrue(ff(false)); + } } \ No newline at end of file diff --git a/test/FastExpressionCompiler.IssueTests/Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression.cs b/test/FastExpressionCompiler.IssueTests/Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression.cs new file mode 100644 index 00000000..c4f5d676 --- /dev/null +++ b/test/FastExpressionCompiler.IssueTests/Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression.cs @@ -0,0 +1,45 @@ +using System; + +#if LIGHT_EXPRESSION +using ExpressionType = System.Linq.Expressions.ExpressionType; +using static FastExpressionCompiler.LightExpression.Expression; +namespace FastExpressionCompiler.LightExpression.IssueTests; +#else +using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; +namespace FastExpressionCompiler.IssueTests; +#endif + +public struct Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression : ITestX +{ + public void Run(TestRun t) + { + Original_case(t); + } + + public void Original_case(TestContext t) + { + var left = Convert(Constant(0d), typeof(decimal)); + var right = Constant(3m); + + var cond = Condition(Equal(left, Default(left.Type)), + right, + Multiply(left, right)); + + var expr = Lambda>(cond); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + t.AreEqual(3, fs()); + + var ff = expr.CompileFast(false, CompilerFlags.DisableInterpreter); + ff.PrintIL(); + t.AreEqual(3, ff()); + + var ffi = expr.CompileFast(false); + ff.PrintIL(); + t.AreEqual(3, ffi()); + } +} \ No newline at end of file diff --git a/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj b/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj index 8046f4b1..d10a3535 100644 --- a/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.IssueTests/FastExpressionCompiler.LightExpression.IssueTests.csproj @@ -1,6 +1,7 @@  - net472;net9.0;net8.0;net6.0 + net472;net6.0;net8.0;net9.0 + net472;net9.0 LIGHT_EXPRESSION @@ -19,7 +20,7 @@ - + diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj index 50c8f1e7..057d6c12 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj @@ -1,6 +1,7 @@  - net472;net9.0;net8.0;net6.0 + net472;net6.0;net8.0;net9.0 + net472;net9.0 LIGHT_EXPRESSION diff --git a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs index 135ce1f5..614d728b 100644 --- a/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner.Net472/Program.cs @@ -35,6 +35,9 @@ static void RunLightExpressionTests(object state) var t = (LightExpression.TestRun)state; t.Run(new LightExpression.IssueTests.Issue183_NullableDecimal()); + t.Run(new LightExpression.IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET()); + t.Run(new LightExpression.IssueTests.Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation()); + t.Run(new LightExpression.IssueTests.Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression()); Console.WriteLine($"Just LightExpression tests are passing in {justLightTestsStopwatch.ElapsedMilliseconds} ms."); } @@ -46,6 +49,10 @@ static void RunLightExpressionTests(object state) var fecTests = new TestRun(); fecTests.Run(new Issue183_NullableDecimal()); + fecTests.Run(new Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET()); + fecTests.Run(new Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation()); + fecTests.Run(new Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression()); + Console.WriteLine($"FEC tests are passing in {fecTestsStopwatch.ElapsedMilliseconds} ms."); @@ -65,7 +72,7 @@ static void RunLightExpressionTests(object state) } var totalTestCount = fecTests.TotalTestCount + lightTests.TotalTestCount; - Console.WriteLine($"All {totalTestCount,-4} tests are passing in {totalStopwatch.ElapsedMilliseconds} ms."); + Console.WriteLine($"TestX: {totalTestCount,-4} tests are passing in {totalStopwatch.ElapsedMilliseconds} ms."); } public static void RunAllTests() diff --git a/test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj b/test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj index 9cd5f41b..293d2df7 100644 --- a/test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj +++ b/test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj @@ -1,6 +1,7 @@ - net9.0;net8.0;net6.0 + net9.0;net8.0;net6.0 + net9.0 Exe false diff --git a/test/FastExpressionCompiler.TestsRunner/Program.cs b/test/FastExpressionCompiler.TestsRunner/Program.cs index 941af08b..696d5c54 100644 --- a/test/FastExpressionCompiler.TestsRunner/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner/Program.cs @@ -1,4 +1,6 @@ -using System; +//#define INTERPRETATION_DIAGNOSTICS + +using System; using System.Diagnostics; using System.Threading.Tasks; using FastExpressionCompiler.IssueTests; @@ -10,12 +12,13 @@ public class Program public static void Main() { var t = new LightExpression.TestRun(); - t.Run(new LightExpression.IssueTests.Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation()); + t.Run(new LightExpression.IssueTests.Issue468_Optimize_the_delegate_access_to_the_Closure_object_for_the_modern_NET()); + t.Run(new LightExpression.IssueTests.Issue472_TryInterpret_and_Reduce_primitive_arithmetic_and_logical_expressions_during_the_compilation()); + t.Run(new LightExpression.IssueTests.Issue473_InvalidProgramException_when_using_Expression_Condition_with_converted_decimal_expression()); // new Issue55_CompileFast_crash_with_ref_parameter().Run(); - // todo: @wip add to FEC, check the possibility of the increment compilation and the artifacts reusability // new LightExpression.UnitTests.ConstantAndConversionTests().Run(); // new LightExpression.IssueTests.Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run(); diff --git a/test/FastExpressionCompiler.UnitTests/ConstantAndConversionTests.cs b/test/FastExpressionCompiler.UnitTests/ConstantAndConversionTests.cs index 7a05e0a2..2c80aa2d 100644 --- a/test/FastExpressionCompiler.UnitTests/ConstantAndConversionTests.cs +++ b/test/FastExpressionCompiler.UnitTests/ConstantAndConversionTests.cs @@ -19,11 +19,8 @@ public int Run() Issue464_Bound_closure_constants_can_be_modified_afterwards(); Issue465_The_primitive_constant_can_be_configured_to_put_in_closure(); Issue466_The_constant_may_be_referenced_multiple_times(); - Issue466_The_constant_may_be_loosely_defined_with_runtime_type(); - Issue466_The_constant_may_be_loosely_defined_with_runtime_type_and_used_multiple_times(); #endif The_constant_changing_in_a_loop(); - Expressions_with_small_long_casts_should_not_crash(); Expressions_with_larger_long_casts_should_not_crash(); Expressions_with_long_constants_and_casts(); @@ -39,7 +36,7 @@ public int Run() Expressions_with_char_and_short(); Can_use_constant_of_byte_Enum_type(); Can_return_constant(); - return 16; + return 18; } public void Expressions_with_small_long_casts_should_not_crash() @@ -148,8 +145,8 @@ public class Foo public void Issue464_Bound_closure_constants_can_be_modified_afterwards() { - var foo = ConstantRef(new Foo { Value = 43 }); - var expr = Lambda>(Field(foo, nameof(Foo.Value))); + var foo = new Foo { Value = 43 }; + var expr = Lambda>(Field(Constant(foo), nameof(Foo.Value))); expr.PrintCSharp(); var fs = expr.CompileFast(true); @@ -157,14 +154,13 @@ public void Issue464_Bound_closure_constants_can_be_modified_afterwards() Asserts.AreEqual(43, fs()); - foo.ValueRef = new Foo { Value = 45 }; + foo.Value = 45; Asserts.AreEqual(45, fs()); } public void Issue465_The_primitive_constant_can_be_configured_to_put_in_closure() { - var n = ConstantRef(16); - var expr = Lambda>(n); + var expr = Lambda>(ConstantRef(16, out var n)); expr.PrintCSharp(); var fs = expr.CompileFast(true); @@ -172,44 +168,14 @@ public void Issue465_The_primitive_constant_can_be_configured_to_put_in_closure( Asserts.AreEqual(16, fs()); - n.ValueRef = 45; // <-- WIN! + n.Value = 45; // <-- WIN! Asserts.AreEqual(45, fs()); } public void Issue466_The_constant_may_be_referenced_multiple_times() { - var n = ConstantRef(16); - var expr = Lambda>(Add(n, n)); - expr.PrintCSharp(); - - var fs = expr.CompileFast(true); - fs.PrintIL(); - - Asserts.AreEqual(32, fs()); - - n.ValueRef = 45; - Asserts.AreEqual(90, fs()); - } - - public void Issue466_The_constant_may_be_loosely_defined_with_runtime_type() - { - var n = ConstantRef(16, typeof(int)); - var expr = Lambda>(Add(n, Constant(1))); - expr.PrintCSharp(); - - var fs = expr.CompileFast(true); - fs.PrintIL(); - - Asserts.AreEqual(17, fs()); - - n.ValueRef = 45; - Asserts.AreEqual(46, fs()); - } - - public void Issue466_The_constant_may_be_loosely_defined_with_runtime_type_and_used_multiple_times() - { - var n = ConstantRef(16, typeof(int)); - var expr = Lambda>(Add(n, n)); + var nExpr = ConstantRef(16, out var n); + var expr = Lambda>(Add(nExpr, nExpr)); expr.PrintCSharp(); var fs = expr.CompileFast(true); @@ -217,21 +183,21 @@ public void Issue466_The_constant_may_be_loosely_defined_with_runtime_type_and_u Asserts.AreEqual(32, fs()); - n.ValueRef = 45; // <-- WIN! + n.Value = 45; Asserts.AreEqual(90, fs()); } public void Issue457_The_constant_changing_in_a_loop_without_recompilation() { var sw = Stopwatch.StartNew(); - var refConst = ConstantRef(0); + var refConst = ConstantRef(0, out var val); var blockExpr = Block(refConst); var lambda = Lambda>(blockExpr); var fastCompiled = lambda.CompileFast(true); for (int n = -200; n < 200; n++) { - refConst.ValueRef = n; + val.Value = n; Asserts.AreEqual(n, fastCompiled()); } diff --git a/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj b/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj index a93b3f56..5826f08b 100644 --- a/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj +++ b/test/FastExpressionCompiler.UnitTests/FastExpressionCompiler.UnitTests.csproj @@ -1,6 +1,7 @@  - net472;net9.0;net8.0;net6.0 + net472;net6.0;net8.0;net9.0 + net472;net9.0