diff --git a/README.md b/README.md index 0f277396..4e13933b 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,11 @@ In addition, the memory consumption taken by the compilation will be much smalle **Updated to .NET 9.0** ```ini -BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4391/23H2/2023Update/SunValley3) +BenchmarkDotNet v0.15.0, Windows 11 (10.0.26100.4061/24H2/2024Update/HudsonValley) Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores -.NET SDK 9.0.100 - [Host] : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2 - DefaultJob : .NET 9.0.0 (9.0.24.52809), X64 RyuJIT AVX2 +.NET SDK 9.0.203 +[Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 +DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 ``` ### Hoisted expression with the constructor and two arguments in closure @@ -75,18 +75,18 @@ Expression> e = () => new X(a, b); Compiling expression: -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | -| ----------- | ---------: | --------: | --------: | ----: | ------: | -----: | -----: | --------: | ----------: | -| Compile | 151.570 us | 3.0196 us | 6.7538 us | 44.27 | 2.13 | 0.7324 | - | 4.49 KB | 2.92 | -| CompileFast | 3.425 us | 0.0676 us | 0.0664 us | 1.00 | 0.03 | 0.2441 | 0.2365 | 1.54 KB | 1.00 | +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +| ----------- | ---------: | --------: | --------: | ----: | ------: | ---: | -----: | -----: | --------: | ----------: | +| CompileFast | 3.183 us | 0.0459 us | 0.0407 us | 1.00 | 0.02 | 1 | 0.1984 | 0.1945 | 1.23 KB | 1.00 | +| Compile | 147.312 us | 1.9291 us | 1.8946 us | 46.28 | 0.81 | 2 | 0.4883 | 0.2441 | 4.48 KB | 3.65 | Invoking the compiled delegate (comparing to the direct constructor call): -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | -| --------------------- | -------: | --------: | --------: | -------: | ----: | ------: | -----: | --------: | ----------: | -| DirectConstructorCall | 6.920 ns | 0.2007 ns | 0.3462 ns | 7.051 ns | 0.86 | 0.06 | 0.0051 | 32 B | 1.00 | -| CompiledLambda | 8.095 ns | 0.2195 ns | 0.5216 ns | 7.845 ns | 1.01 | 0.08 | 0.0051 | 32 B | 1.00 | -| FastCompiledLambda | 8.066 ns | 0.2206 ns | 0.3234 ns | 8.156 ns | 1.00 | 0.06 | 0.0051 | 32 B | 1.00 | +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +| --------------------- | -------: | --------: | --------: | ----: | ------: | ---: | -----: | --------: | ----------: | +| DirectConstructorCall | 6.055 ns | 0.0632 ns | 0.0560 ns | 1.00 | 0.01 | 1 | 0.0051 | 32 B | 1.00 | +| CompiledLambda | 7.853 ns | 0.2013 ns | 0.1681 ns | 1.30 | 0.03 | 2 | 0.0051 | 32 B | 1.00 | +| FastCompiledLambda | 7.962 ns | 0.2186 ns | 0.4052 ns | 1.31 | 0.07 | 2 | 0.0051 | 32 B | 1.00 | ### Hoisted expression with the static method and two nested lambdas and two arguments in closure @@ -99,19 +99,18 @@ Expression> getXExpr = () => CreateX((aa, bb) => new X(aa, bb), new Lazy Compiling expression: -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | -| ----------- | --------: | -------: | --------: | --------: | ----: | ------: | -----: | -----: | --------: | ----------: | -| Compile | 421.09 us | 8.382 us | 18.221 us | 413.02 us | 36.29 | 2.09 | 1.9531 | 0.9766 | 12.04 KB | 2.61 | -| CompileFast | 11.62 us | 0.230 us | 0.464 us | 11.42 us | 1.00 | 0.06 | 0.7324 | 0.7019 | 4.62 KB | 1.00 | - +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +| ----------- | --------: | -------: | -------: | ----: | ------: | ---: | -----: | -----: | --------: | ----------: | +| CompileFast | 11.12 us | 0.189 us | 0.158 us | 1.00 | 0.02 | 1 | 0.6104 | 0.5798 | 3.77 KB | 1.00 | +| Compile | 415.09 us | 4.277 us | 3.571 us | 37.34 | 0.60 | 2 | 1.9531 | 1.4648 | 12.04 KB | 3.19 | Invoking compiled delegate comparing to direct method call: -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | -| ------------------- | ----------: | --------: | --------: | ----------: | ----: | ------: | -----: | --------: | ----------: | -| DirectMethodCall | 43.45 ns | 0.922 ns | 1.905 ns | 44.13 ns | 1.09 | 0.08 | 0.0268 | 168 B | 1.62 | -| Invoke_Compiled | 1,181.25 ns | 23.664 ns | 56.240 ns | 1,161.87 ns | 29.66 | 2.24 | 0.0420 | 264 B | 2.54 | -| Invoke_CompiledFast | 39.96 ns | 0.856 ns | 2.442 ns | 38.96 ns | 1.00 | 0.08 | 0.0166 | 104 B | 1.00 | +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +| ------------------- | ----------: | --------: | --------: | ----: | ------: | ---: | -----: | --------: | ----------: | +| DirectMethodCall | 40.29 ns | 0.549 ns | 0.487 ns | 1.00 | 0.02 | 1 | 0.0268 | 168 B | 1.00 | +| Invoke_CompiledFast | 40.59 ns | 0.157 ns | 0.123 ns | 1.01 | 0.01 | 1 | 0.0166 | 104 B | 0.62 | +| Invoke_Compiled | 1,142.12 ns | 11.877 ns | 14.586 ns | 28.35 | 0.48 | 2 | 0.0420 | 264 B | 1.57 | ### Manually composed expression with parameters and closure @@ -127,21 +126,20 @@ var expr = Expression.Lambda( Compiling expression: -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | -| ---------------------------- | --------: | --------: | --------: | --------: | ----: | ------: | -----: | -----: | --------: | ----------: | -| Compile_SystemExpression | 89.076 us | 2.6699 us | 7.6605 us | 85.180 us | 28.12 | 3.05 | 0.7324 | 0.4883 | 4.74 KB | 3.41 | -| CompileFast_SystemExpression | 3.138 us | 0.0550 us | 0.0565 us | 3.118 us | 0.99 | 0.03 | 0.2213 | 0.2136 | 1.39 KB | 1.00 | -| CompileFast_LightExpression | 3.180 us | 0.0602 us | 0.0591 us | 3.163 us | 1.00 | 0.00 | 0.2213 | 0.2136 | 1.39 KB | 1.00 | - +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +| ---------------------------- | ---------: | --------: | --------: | ----: | ------: | ---: | -----: | -----: | --------: | ----------: | +| CompileFast_LightExpression | 3.107 us | 0.0562 us | 0.0498 us | 0.99 | 0.02 | 1 | 0.1755 | 0.1678 | 1.08 KB | 1.00 | +| CompileFast_SystemExpression | 3.126 us | 0.0288 us | 0.0256 us | 1.00 | 0.01 | 1 | 0.1755 | 0.1678 | 1.08 KB | 1.00 | +| Compile_SystemExpression | 103.948 us | 1.9593 us | 2.5477 us | 33.26 | 0.84 | 2 | 0.7324 | 0.4883 | 4.74 KB | 4.40 | Invoking the compiled delegate compared to the normal delegate and the direct call: -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | -| ----------------------------- | -------: | --------: | --------: | -------: | ----: | ------: | -----: | --------: | ----------: | -| DirectCall | 8.388 ns | 0.2655 ns | 0.7575 ns | 8.092 ns | 1.00 | 0.07 | 0.0051 | 32 B | 1.00 | -| Compiled_SystemExpression | 9.474 ns | 0.1870 ns | 0.4105 ns | 9.381 ns | 1.10 | 0.05 | 0.0051 | 32 B | 1.00 | -| CompiledFast_SystemExpression | 8.575 ns | 0.1624 ns | 0.1440 ns | 8.517 ns | 1.00 | 0.02 | 0.0051 | 32 B | 1.00 | -| CompiledFast_LightExpression | 8.584 ns | 0.0776 ns | 0.0862 ns | 8.594 ns | 1.00 | 0.00 | 0.0051 | 32 B | 1.00 | +| Method | Mean | Error | StdDev | Ratio | Rank | Gen0 | Allocated | Alloc Ratio | +| ----------------------------- | -------: | -------: | -------: | ----: | ---: | -----: | --------: | ----------: | +| DirectCall | 10.19 ns | 0.108 ns | 0.085 ns | 1.00 | 1 | 0.0051 | 32 B | 1.00 | +| CompiledFast_LightExpression | 10.70 ns | 0.089 ns | 0.070 ns | 1.05 | 2 | 0.0051 | 32 B | 1.00 | +| CompiledFast_SystemExpression | 10.91 ns | 0.071 ns | 0.066 ns | 1.07 | 2 | 0.0051 | 32 B | 1.00 | +| Compiled_SystemExpression | 11.59 ns | 0.098 ns | 0.081 ns | 1.14 | 3 | 0.0051 | 32 B | 1.00 | ### FastExpressionCompiler.LightExpression.Expression vs System.Linq.Expressions.Expression @@ -169,20 +167,18 @@ Hopefully you are checking the expression arguments yourself and not waiting for Creating the expression: -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | -| -------------------------------------- | ---------: | -------: | -------: | ---------: | ----: | ------: | -----: | --------: | ----------: | -| Create_SystemExpression | 1,110.9 ns | 22.19 ns | 62.23 ns | 1,086.1 ns | 7.25 | 0.56 | 0.2060 | 1304 B | 2.63 | -| Create_LightExpression | 153.7 ns | 3.14 ns | 8.61 ns | 150.5 ns | 1.00 | 0.08 | 0.0789 | 496 B | 1.00 | -| Create_LightExpression_with_intrinsics | 161.0 ns | 2.80 ns | 2.19 ns | 161.0 ns | 1.05 | 0.06 | 0.0777 | 488 B | 0.98 | +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +| ----------------------- | ---------: | -------: | -------: | ---------: | ----: | ------: | ---: | -----: | --------: | ----------: | +| Create_LightExpression | 156.6 ns | 3.19 ns | 8.18 ns | 151.9 ns | 1.00 | 0.07 | 1 | 0.0827 | 520 B | 1.00 | +| Create_SystemExpression | 1,065.0 ns | 14.24 ns | 11.89 ns | 1,069.3 ns | 6.82 | 0.34 | 2 | 0.2060 | 1304 B | 2.51 | Creating and compiling: -| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | -| ---------------------------------------------------- | ---------: | --------: | ---------: | ----: | ------: | -----: | -----: | --------: | ----------: | -| Create_SystemExpression_and_Compile | 212.157 us | 4.2180 us | 11.4036 us | 44.77 | 3.31 | 0.9766 | 0.4883 | 7.15 KB | 2.95 | -| Create_SystemExpression_and_CompileFast | 6.656 us | 0.1322 us | 0.3065 us | 1.40 | 0.10 | 0.5188 | 0.4883 | 3.27 KB | 1.35 | -| Create_LightExpression_and_CompileFast | 4.751 us | 0.0947 us | 0.2411 us | 1.00 | 0.07 | 0.3815 | 0.3662 | 2.42 KB | 1.00 | -| CreateLightExpression_and_CompileFast_with_intrinsic | 4.604 us | 0.0918 us | 0.1915 us | 0.97 | 0.06 | 0.3815 | 0.3662 | 2.35 KB | 0.97 | +| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +| --------------------------------------- | ---------: | --------: | --------: | ---------: | ----: | ------: | ---: | -----: | -----: | --------: | ----------: | +| Create_LightExpression_and_CompileFast | 4.957 us | 0.0986 us | 0.2362 us | 4.913 us | 1.00 | 0.07 | 1 | 0.3510 | 0.3052 | 2.15 KB | 1.00 | +| Create_SystemExpression_and_CompileFast | 6.518 us | 0.1889 us | 0.5541 us | 6.300 us | 1.32 | 0.13 | 2 | 0.4578 | 0.4272 | 2.97 KB | 1.38 | +| Create_SystemExpression_and_Compile | 205.000 us | 4.0938 us | 7.3819 us | 206.353 us | 41.44 | 2.45 | 3 | 0.9766 | 0.4883 | 7.15 KB | 3.33 | ## Difference between FastExpressionCompiler and FastExpressionCompiler.LightExpression @@ -300,17 +296,91 @@ FEC V3 has added powerful diagnostics and code generation tools. You may pass the optional `CompilerFlags.EnableDelegateDebugInfo` into the `CompileFast` methods. -`EnableDelegateDebugInfo` adds the diagnostic info into the compiled delegate including its source Expression and C# code. -Can be used as following: +`EnableDelegateDebugInfo` adds the diagnostic info into the compiled delegate including its source Expression and compiled IL code. + +It can be used as following: + +```cs +System.Linq.Expressions.Expression>> e = + n => () => n + 1; +var f = e.CompileFast(flags: CompilerFlags.EnableDelegateDebugInfo); +var d = f.TryGetDebugInfo(); +d.PrintExpression(); +d.PrintCSharp(); +d.PrintIL(); // available in NET8+ +``` + +
Expand to see the output of the above code... + + +Output of `d.PrintExpression()` is the valid C#: ```cs -var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); -var di = f.Target as IDelegateDebugInfo; -Asserts.IsNotNull(di.Expression); -Asserts.IsNotNull(di.ExpressionString); -Asserts.IsNotNull(di.CSharpString); +var p = new ParameterExpression[1]; // the parameter expressions +var e = new Expression[3]; // the unique expressions +var expr = Lambda>>( + e[0]=Lambda>( + e[1]=MakeBinary(ExpressionType.Add, + p[0]=Parameter(typeof(int), "n"), + e[2]=Constant(1)), new ParameterExpression[0]), + p[0 // (int n) + ]); ``` +Output of `d.PrintCSharp()` is the valid C#: + +```cs +var @cs = (Func>)((int n) => //Func + (Func)(() => //int + n + 1)); +``` + +Output of `d.PrintIL()` (includes the IL of the nested lambda): + +``` + +0 ldarg.0 +1 ldfld object[] ExpressionCompiler.ArrayClosure.ConstantsAndNestedLambdas +6 stloc.0 +7 ldloc.0 +8 ldc.i4.0 +9 ldelem.ref +10 stloc.1 +11 ldloc.1 +12 ldc.i4.1 +13 newarr object +18 stloc.2 +19 ldloc.2 +20 stfld object[] ExpressionCompiler.NestedLambdaForNonPassedParams.NonPassedParams +25 ldloc.2 +26 ldc.i4.0 +27 ldarg.1 +28 box int +33 stelem.ref +34 ldloc.1 +35 ldfld object ExpressionCompiler.NestedLambdaForNonPassedParams.NestedLambda +40 ldloc.2 +41 ldloc.1 +42 ldfld object[] ExpressionCompiler.NestedLambdaForNonPassedParamsWithConstants.ConstantsAndNestedLambdas +47 newobj ExpressionCompiler.ArrayClosureWithNonPassedParams(System.Object[], System.Object[]) +52 call Func ExpressionCompiler.CurryClosureFuncs.Curry(System.Func`2[FastExpressionCompiler.LightExpression.ExpressionCompiler+ArrayClosure,System.Int32], ArrayClosure) +57 ret + +<0_nested_in_Caller> +0 ldarg.0 +1 ldfld object[] ExpressionCompiler.ArrayClosureWithNonPassedParams.NonPassedParams +6 ldc.i4.0 +7 ldelem.ref +8 unbox.any int +13 ldc.i4.1 +14 add +15 ret + +``` + +
+ + ### ThrowOnNotSupportedExpression and NotSupported_ flags FEC V3.1 has added the compiler flag `CompilerFlags.ThrowOnNotSupportedExpression`. diff --git a/btdebug.bat b/btdebug.bat new file mode 100644 index 00000000..f2de6b70 --- /dev/null +++ b/btdebug.bat @@ -0,0 +1,20 @@ +@echo off + +echo: +echo:## Running TESTS on the Latest Supported .NET... +echo: +dotnet run -p:DevMode=true -f:net9.0 -c:Debug --project test/FastExpressionCompiler.TestsRunner/FastExpressionCompiler.TestsRunner.csproj +if %ERRORLEVEL% neq 0 goto :error + +echo: +echo:## Finished: TESTS + +echo:# Finished: ALL +echo: +exit /b 0 + +:error +echo: +echo:## :-( Failed with ERROR: %ERRORLEVEL% +echo: +exit /b %ERRORLEVEL% diff --git a/src/Directory.Build.props b/src/Directory.Build.props index dd269b26..4be2f293 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -33,6 +33,7 @@ + strict true diff --git a/src/FastExpressionCompiler/FastExpressionCompiler.cs b/src/FastExpressionCompiler/FastExpressionCompiler.cs index ff483123..9883b537 100644 --- a/src/FastExpressionCompiler/FastExpressionCompiler.cs +++ b/src/FastExpressionCompiler/FastExpressionCompiler.cs @@ -42,6 +42,7 @@ namespace FastExpressionCompiler.LightExpression using static FastExpressionCompiler.LightExpression.Expression; using PE = FastExpressionCompiler.LightExpression.ParameterExpression; using FastExpressionCompiler.LightExpression.ImTools; + using FastExpressionCompiler.LightExpression.ILDecoder; using static FastExpressionCompiler.LightExpression.ImTools.SmallMap4; #else namespace FastExpressionCompiler @@ -49,6 +50,7 @@ namespace FastExpressionCompiler using static System.Linq.Expressions.Expression; using PE = System.Linq.Expressions.ParameterExpression; using FastExpressionCompiler.ImTools; + using FastExpressionCompiler.ILDecoder; using static FastExpressionCompiler.ImTools.SmallMap4; #endif using System; @@ -100,17 +102,13 @@ public interface IDelegateDebugInfo { /// The lambda expression object that was compiled to the delegate LambdaExpression Expression { get; } - /// The lambda expression construction syntax C# code - string ExpressionString { get; } - /// The equivalent C# code of the lambda expression - string CSharpString { get; } - // todo: @feature add the debug info to the nested lambdas - // /// Total nested lambda counting - // ushort NestedLambdaCount { get; } // todo: @wip count nested lambdas and expressions + /// Delegate IL op-codes + ILInstruction[] ILInstructions { get; } - // /// Nested lambda compiled counting, should be less or equal to `NestedLambdaCount` so that the same lambda compiled only once. - // ushort NestedLambdaCompiledTimesCount { get; } + /// Enumerate any nested lambdas in the delegate + [RequiresUnreferencedCode(Trimming.Message)] + IEnumerable EnumerateNestedLambdas(); } /// Compiles expression to delegate ~20 times faster than Expression.Compile. @@ -413,9 +411,9 @@ internal static TDelegate TryCompileWithPreCreatedClosure( this LambdaExpression lambdaExpr, ref ClosureInfo closureInfo, CompilerFlags flags) where TDelegate : class { #if LIGHT_EXPRESSION - var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(lambdaExpr); + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr); #else - var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(lambdaExpr.Parameters); + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.Parameters); #endif var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes, typeof(ExpressionCompiler), skipVisibility: true); @@ -436,9 +434,9 @@ internal static TDelegate TryCompileWithPreCreatedClosure( il.Demit(OpCodes.Ret); var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type; - var @delegate = (TDelegate)(object)method.CreateDelegate(delegateType, new ArrayClosure(closureInfo.Constants.Items)); - FreeClosureTypeAndParamTypes(closurePlusParamTypes); - return @delegate; + var dlg = (TDelegate)(object)method.CreateDelegate(delegateType, new ArrayClosure(closureInfo.Constants.Items)); + FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); + return dlg; } /// Tries to compile expression to "static" delegate, skipping the step of collecting the closure object. @@ -447,9 +445,9 @@ public static TDelegate TryCompileWithoutClosure(this LambdaExpressio { var closureInfo = new ClosureInfo(ClosureStatus.UserProvided); #if LIGHT_EXPRESSION - var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(lambdaExpr); + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr); #else - var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(lambdaExpr.Parameters); + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(lambdaExpr.Parameters); #endif var method = new DynamicMethod(string.Empty, lambdaExpr.ReturnType, closurePlusParamTypes, typeof(ArrayClosure), skipVisibility: true); @@ -467,258 +465,113 @@ public static TDelegate TryCompileWithoutClosure(this LambdaExpressio il.Demit(OpCodes.Ret); var delegateType = typeof(TDelegate) != typeof(Delegate) ? typeof(TDelegate) : lambdaExpr.Type; - var @delegate = (TDelegate)(object)method.CreateDelegate(delegateType, EmptyArrayClosure); - FreeClosureTypeAndParamTypes(closurePlusParamTypes); - return @delegate; + var dlg = (TDelegate)(object)method.CreateDelegate(delegateType, EmptyArrayClosure); + FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); + return dlg; } - private static Delegate CompileNoArgsNew(ConstructorInfo ctor, Type delegateType, Type[] closurePlusParamTypes, Type returnType) + private static Delegate CompileNoArgsNew(NewExpression newExpr, Type delegateType, Type[] closurePlusParamTypes, Type returnType, CompilerFlags flags) { var method = new DynamicMethod(string.Empty, returnType, closurePlusParamTypes, typeof(ArrayClosure), true); - var il = method.GetILGenerator(16); // 16 is enough for maximum of 3 possible ops - il.Demit(OpCodes.Newobj, ctor); + var il = DynamicMethodHacks.RentPooledOrNewILGenerator(method, returnType, closurePlusParamTypes, newStreamSize: 16); + il.Demit(OpCodes.Newobj, newExpr.Constructor); if (returnType == typeof(void)) il.Demit(OpCodes.Pop); il.Demit(OpCodes.Ret); - return method.CreateDelegate(delegateType, EmptyArrayClosure); + + var closure = (flags & CompilerFlags.EnableDelegateDebugInfo) == 0 + ? EmptyArrayClosure + : new DebugArrayClosure(null, null, Lambda(newExpr, Tools.Empty())); + + var dlg = method.CreateDelegate(delegateType, closure); + DynamicMethodHacks.FreePooledILGenerator(method, il); + + if (closure is DebugArrayClosure diagClosure) + diagClosure.ILInstructions = dlg.Method.ReadAllInstructions(); + + return dlg; } #if LIGHT_EXPRESSION internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IParameterProvider paramExprs, 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 exhaust the pooled instance - return CompileNoArgsNew(newNoArgs.Constructor, delegateType, closurePlusParamTypes, returnType); - } + // There is no Return of the pooled parameter types here, + // because in the rarest case with the unused lambda arguments we may just exhaust the pooled instance + var closureAndParamTypes = RentPooledOrNewClosureTypeToParamTypes(paramExprs); + if (bodyExpr is NoArgsNewClassIntrinsicExpression newExpr) + return CompileNoArgsNew(newExpr, delegateType, closureAndParamTypes, returnType, flags); #else internal static object TryCompileBoundToFirstClosureParam(Type delegateType, Expression bodyExpr, IReadOnlyList paramExprs, Type returnType, CompilerFlags flags) { - var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(paramExprs); + var closureAndParamTypes = RentPooledOrNewClosureTypeToParamTypes(paramExprs); #endif // Try to avoid compilation altogether for Func delegates via Interpreter, see #468 - if (returnType == typeof(bool) & closurePlusParamTypes.Length == 1 + if (returnType == typeof(bool) & closureAndParamTypes.Length == 1 && Interpreter.IsCandidateForInterpretation(bodyExpr) && Interpreter.TryInterpretBool(out var result, bodyExpr, flags)) return result ? Interpreter.TrueFunc : Interpreter.FalseFunc; + Delegate compiledDelegate = null; + // The method collects the info from the all nested lambdas deep down up-front and de-duplicates the lambdas as well. var closureInfo = new ClosureInfo(ClosureStatus.ToBeCollected); - if (!TryCollectBoundConstants(ref closureInfo, bodyExpr, paramExprs, null, ref closureInfo.NestedLambdas, flags)) - return null; - - ArrayClosure closure; - if ((flags & CompilerFlags.EnableDelegateDebugInfo) == 0) + var collectResult = TryCollectInfo(ref closureInfo, bodyExpr, paramExprs, null, ref closureInfo.NestedLambdas, flags); + if (collectResult == Result.OK) { - closure = (closureInfo.Status & ClosureStatus.HasClosure) == 0 - ? EmptyArrayClosure - : new ArrayClosure(closureInfo.GetArrayOfConstantsAndNestedLambdas()); - } - else - { // todo: @feature add the debug info to the nested lambdas! - var debugExpr = Lambda(delegateType, bodyExpr, paramExprs?.ToReadOnlyList() ?? Tools.Empty()); - var constantsAndNestedLambdas = (closureInfo.Status & ClosureStatus.HasClosure) == 0 - ? null - : closureInfo.GetArrayOfConstantsAndNestedLambdas(); - closure = new DebugArrayClosure(constantsAndNestedLambdas, debugExpr); - } - - // note: @slow this is what System.Compiles does and which makes the compilation 10x slower, but the invocation become faster by a single branch instruction - // var method = new DynamicMethod(string.Empty, returnType, closurePlusParamTypes, true); - // this is FEC way, significantly faster compilation, but +1 branch instruction in the invocation - var method = new DynamicMethod(string.Empty, returnType, closurePlusParamTypes, typeof(ArrayClosure), true); - - // todo: @perf can we just count the Expressions in the TryCollect phase and use it as N * 4 or something? - var il = method.GetILGenerator(); + var constantsAndNestedLambdas = (closureInfo.Status & ClosureStatus.HasClosure) != 0 + ? closureInfo.GetArrayOfConstantsAndNestedLambdas() + : null; - if (closure.ConstantsAndNestedLambdas != null) - EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo); - - var parent = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; - if (returnType.IsByRef) - parent |= ParentFlags.ReturnByRef; - - if (!EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, ref closureInfo, flags, parent)) - return null; - il.Demit(OpCodes.Ret); - - FreeClosureTypeAndParamTypes(closurePlusParamTypes); - - return method.CreateDelegate(delegateType, closure); - } - -#if NET8_0_OR_GREATER - internal static readonly FieldInfo IlGeneratorField = - typeof(DynamicMethod).GetField("_ilGenerator", BindingFlags.Instance | BindingFlags.NonPublic); - internal static readonly Type DynamicILGeneratorType = IlGeneratorField.FieldType; - internal static readonly ConstructorInfo ScopeTreeCtor = - DynamicILGeneratorType.BaseType - .GetField("m_ScopeTree", BindingFlags.Instance | BindingFlags.NonPublic) - .FieldType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); - - internal static readonly MethodInfo ArrayClearMethod = - typeof(Array).GetMethod(nameof(Array.Clear), [typeof(Array), typeof(int), typeof(int)]); - internal static readonly FieldInfo ListOfObjectsSize = - typeof(List).GetField("_size", BindingFlags.Instance | BindingFlags.NonPublic); - - internal static readonly FieldInfo DynamicILGeneratorScopeField = - DynamicILGeneratorType.GetField("m_scope", BindingFlags.Instance | BindingFlags.NonPublic); - internal static readonly FieldInfo DynamicScopeTokensField = - DynamicILGeneratorScopeField.FieldType.GetField("m_tokens", BindingFlags.Instance | BindingFlags.NonPublic); - internal static readonly PropertyInfo DynamicScopeTokensItem = - DynamicScopeTokensField.FieldType.GetProperty("Item"); - internal static MethodInfo GetMethodSigHelperMethod = typeof(SignatureHelper) - .GetMethod("GetMethodSigHelper", BindingFlags.Static | BindingFlags.Public, null, [typeof(Module), typeof(Type), typeof(Type[])], null); - internal static MethodInfo GetSignatureMethod = typeof(SignatureHelper) - .GetMethod("GetSignature", BindingFlags.Instance | BindingFlags.NonPublic, null, [typeof(bool)], null); - internal static MethodInfo GetTokenForMethod = DynamicILGeneratorScopeField.FieldType - .GetMethod("GetTokenFor", BindingFlags.Instance | BindingFlags.Public, null, [typeof(byte[])], null); - internal static FieldInfo MethodSigTokenField = - DynamicILGeneratorType.GetField("m_methodSigToken", BindingFlags.Instance | BindingFlags.NonPublic); - internal static Action ReuseDynamicILGeneratorOfAnyMethodSignature() - { - const BindingFlags allDeclared = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly; - - var dynMethod = new DynamicMethod(string.Empty, - typeof(void), - [typeof(ExpressionCompiler.ArrayClosure), typeof(DynamicMethod), typeof(ILGenerator), typeof(Type), typeof(Type[])], - typeof(ExpressionCompiler.ArrayClosure), - true); - - var il = dynMethod.GetILGenerator(256); // precalculating the size to avoid waste - - var baseFields = DynamicILGeneratorType.BaseType.GetFields(allDeclared); - foreach (var field in baseFields) - { - var fieldName = field.Name; - if (fieldName == "m_localSignature") // todo: skip, let's see how it works - continue; - - // m_ScopeTree = new ScopeTree(); - if (fieldName == "m_ScopeTree") + ArrayClosure closure; + if ((flags & CompilerFlags.EnableDelegateDebugInfo) == 0) + closure = constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas); + else { - il.Demit(OpCodes.Ldarg_2); - il.Demit(OpCodes.Newobj, ScopeTreeCtor); - il.Demit(OpCodes.Stfld, field); - continue; + var debugLambdaExpr = Lambda(delegateType, bodyExpr, paramExprs?.ToReadOnlyList() ?? Tools.Empty()); + closure = new DebugArrayClosure(null, constantsAndNestedLambdas, debugLambdaExpr); } - // m_methodBuilder = method; // dynamicMethod - if (fieldName == "m_methodBuilder") - { - il.Demit(OpCodes.Ldarg_2); - il.Demit(OpCodes.Ldarg_1); - il.Demit(OpCodes.Stfld, field); - continue; - } + // note: @slow this is what System.Compiles does and which makes the compilation 10x slower, but the invocation become faster by a single branch instruction + // var method = new DynamicMethod(string.Empty, returnType, closurePlusParamTypes, true); + // this is FEC way, significantly faster compilation, but +1 branch instruction in the invocation + var dynMethod = new DynamicMethod(string.Empty, returnType, closureAndParamTypes, typeof(ArrayClosure), true); - // instead of m_ILStream = new byte[Math.Max(size, DefaultSize)]; - // let's clear it and reuse the buffer - if (fieldName == "m_ILStream") - { - il.Demit(OpCodes.Ldarg_2); - il.Demit(OpCodes.Ldfld, field); - var ilStreamVar = ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, typeof(byte[])); - il.Demit(OpCodes.Ldc_I4_0); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, ilStreamVar); - il.Demit(OpCodes.Ldlen); - il.Demit(OpCodes.Call, ArrayClearMethod); - continue; - } + var il = DynamicMethodHacks.RentPooledOrNewILGenerator(dynMethod, returnType, closureAndParamTypes); - il.Demit(OpCodes.Ldarg_2); - ExpressionCompiler.EmittingVisitor.EmitDefault(il, field.FieldType); - il.Demit(OpCodes.Stfld, field); - } - - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Ldfld, DynamicILGeneratorScopeField); - var scopeVar = ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, DynamicILGeneratorScopeField.FieldType); - il.Emit(OpCodes.Ldfld, DynamicScopeTokensField); - il.Emit(OpCodes.Dup); - - // reset its List._size to 1, keep the 0th item - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Stfld, ListOfObjectsSize); - - // set the 0th item to null - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Call, DynamicScopeTokensItem.SetMethod); - - // byte[] methodSignature = - // SignatureHelper.GetMethodSigHelper(Module? mod, Type? returnType, Type[]? parameterTypes).GetSignature(true); - il.Emit(OpCodes.Ldnull); // for the module - il.Emit(OpCodes.Ldarg_3); // load return type - il.Emit(OpCodes.Ldarg_S, 4); // load parameter types arrays - il.Emit(OpCodes.Call, GetMethodSigHelperMethod); - il.Emit(OpCodes.Ldc_I4_1); // load true - il.Emit(OpCodes.Call, GetSignatureMethod); - var signatureBytesVar = ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, typeof(byte[])); // todo: perf could reuse byte[]? - - // m_methodSigToken = m_scope.GetTokenFor(methodSignature); - il.Emit(OpCodes.Ldarg_2); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, signatureBytesVar); - il.Emit(OpCodes.Call, GetTokenForMethod); - il.Emit(OpCodes.Stfld, MethodSigTokenField); - - // store the reused ILGenerator to - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Stfld, IlGeneratorField); - - il.Emit(OpCodes.Ret); - - var act = dynMethod.CreateDelegate(typeof(Action), ExpressionCompiler.EmptyArrayClosure); - return (Action)act; - } + if (closure.ConstantsAndNestedLambdas != null) + EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref closureInfo); - internal static Action - ReuseDynamicILGeneratorOfAnySignature = ReuseDynamicILGeneratorOfAnyMethodSignature(); + var parent = returnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; + if (returnType.IsByRef) + parent |= ParentFlags.ReturnByRef; - [ThreadStatic] - internal static ILGenerator pooledILGenerator; -#endif + if (EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, ref closureInfo, flags, parent)) + { + il.Demit(OpCodes.Ret); + compiledDelegate = dynMethod.CreateDelegate(delegateType, closure); + if (closure is DebugArrayClosure diagClosure) + diagClosure.ILInstructions = compiledDelegate.Method.ReadAllInstructions(); + } - /// Get new or pool and configure existing DynamicILGenerator - [MethodImpl((MethodImplOptions)256)] - public static ILGenerator PoolOrNewILGenerator(DynamicMethod dynMethod, Type returnType, Type[] paramTypes) - { -#if NET8_0_OR_GREATER - var il = Interlocked.Exchange(ref pooledILGenerator, null); - if (il != null) - ReuseDynamicILGeneratorOfAnySignature(dynMethod, il, typeof(void), paramTypes); - else - il = dynMethod.GetILGenerator(); - return il; -#else - return dynMethod.GetILGenerator(); -#endif - } + DynamicMethodHacks.FreePooledILGenerator(dynMethod, il); + } + FreePooledClosureTypeAndParamTypes(closureAndParamTypes); - /// Should be called only after call to DynamicMethod.CreateDelegate - [MethodImpl((MethodImplOptions)256)] - public static void FreeILGenerator(DynamicMethod dynMethod, ILGenerator il) - { -#if NET8_0_OR_GREATER - IlGeneratorField.SetValue(dynMethod, null); // required to break the link with the current method and avoid memory leak - Interlocked.Exchange(ref pooledILGenerator, il); -#endif + return compiledDelegate + ?? ((flags & CompilerFlags.ThrowOnNotSupportedExpression) == 0 ? null : NotSupportedCase(collectResult)); } private static readonly Type[] _closureAsASingleParamType = { typeof(ArrayClosure) }; private static readonly Type[][] _paramTypesPoolWithElem0OfLength1 = new Type[8][]; // todo: @perf @mem could we use this for other Type arrays? #if LIGHT_EXPRESSION - internal static Type[] RentOrNewClosureTypeToParamTypes(IParameterProvider paramExprs) + internal static Type[] RentPooledOrNewClosureTypeToParamTypes(IParameterProvider paramExprs) { var count = paramExprs.ParameterCount; #else - internal static Type[] RentOrNewClosureTypeToParamTypes(IReadOnlyList paramExprs) + internal static Type[] RentPooledOrNewClosureTypeToParamTypes(IReadOnlyList paramExprs) { var count = paramExprs.Count; #endif @@ -736,12 +589,31 @@ internal static Type[] RentOrNewClosureTypeToParamTypes(IReadOnlyList paramE return pooledOrNew; } - /// Renting the array of types of closure + plus the passed parameter types + /// Renting the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static Type[] RentPooledOrNewParamTypes(Type p0) + { + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[0], null) ?? new Type[1]; + pooledOrNew[0] = p0; + return pooledOrNew; + } + + /// Renting the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1) + { + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[1], null) ?? new Type[2]; + pooledOrNew[0] = p0; + pooledOrNew[1] = p1; + return pooledOrNew; + } + + /// Renting the array of the passed parameter types [MethodImpl((MethodImplOptions)256)] - public static Type[] RentOrNewClosureTypeToParamTypes(Type p1, Type p2) + public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2) { var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[2], null) ?? new Type[3]; - pooledOrNew[0] = typeof(ArrayClosure); + pooledOrNew[0] = p0; pooledOrNew[1] = p1; pooledOrNew[2] = p2; return pooledOrNew; @@ -749,30 +621,45 @@ public static Type[] RentOrNewClosureTypeToParamTypes(Type p1, Type p2) /// Renting the array of the passed parameter types [MethodImpl((MethodImplOptions)256)] - public static Type[] RentParamTypes(Type p0, Type p1) + public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2, Type p3) { - var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[1], null) ?? new Type[2]; + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[3], null) ?? new Type[4]; + pooledOrNew[0] = p0; + pooledOrNew[1] = p1; + pooledOrNew[2] = p2; + pooledOrNew[3] = p3; + return pooledOrNew; + } + + /// Renting the array of the passed parameter types + [MethodImpl((MethodImplOptions)256)] + public static Type[] RentPooledOrNewParamTypes(Type p0, Type p1, Type p2, Type p3, Type p4) + { + var pooledOrNew = Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[4], null) ?? new Type[5]; pooledOrNew[0] = p0; pooledOrNew[1] = p1; + pooledOrNew[2] = p2; + pooledOrNew[3] = p3; + pooledOrNew[4] = p4; return pooledOrNew; } /// Freeing to the pool the array of types of closure + plus the passed parameter types [MethodImpl((MethodImplOptions)256)] - public static void FreeClosureTypeAndParamTypes(Type[] closurePlusParamTypes) + public static void FreePooledClosureTypeAndParamTypes(Type[] closurePlusParamTypes) { var paramCountOnly = closurePlusParamTypes.Length - 1; if (paramCountOnly != 0 & paramCountOnly < 8) - Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCountOnly], closurePlusParamTypes); // todo: @perf we don't need the Interlocked here + Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCountOnly], closurePlusParamTypes); } /// Freeing to the pool the array of the passed parameter types [MethodImpl((MethodImplOptions)256)] - public static void FreeParamTypes(Type[] paramTypes) + public static void FreePooledParamTypes(Type[] paramTypes) { var paramCount = paramTypes.Length; if (paramCount != 0 & paramCount < 8) - Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCount - 1], paramTypes); // todo: @perf we don't need the Interlocked here + Interlocked.Exchange(ref _paramTypesPoolWithElem0OfLength1[paramCount - 1], paramTypes); } #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member @@ -1106,6 +993,8 @@ private static Label GetOrDefineLabel(ref this LabelInfo label, ILGenerator il) public static ConstructorInfo ArrayClosureWithNonPassedParamsCtor = _nonPassedParamsArrayClosureCtors[1]; + private static ConstructorInfo DebugArrayClosureCtor = typeof(DebugArrayClosure).GetConstructors()[0]; + public static Result NotSupported_RuntimeVariables { get; private set; } public class ArrayClosure @@ -1115,78 +1004,52 @@ public class ArrayClosure public ArrayClosure(object[] constantsAndNestedLambdas) => ConstantsAndNestedLambdas = constantsAndNestedLambdas; } - [RequiresUnreferencedCode(Trimming.Message)] - public sealed class DebugArrayClosure : ArrayClosure, IDelegateDebugInfo - { - public LambdaExpression Expression { get; internal set; } - - private readonly Lazy _expressionString; - public string ExpressionString => _expressionString.Value; - - private readonly Lazy _csharpString; - public string CSharpString => _csharpString.Value; - public DebugArrayClosure(object[] constantsAndNestedLambdas, LambdaExpression expr) - : base(constantsAndNestedLambdas) - { - Expression = expr; - _expressionString = new Lazy(() => Expression?.ToExpressionString() ?? ""); - _csharpString = new Lazy(() => Expression?.ToCSharpString() ?? ""); - } - } - - public static bool TryGetDebugClosureNestedLambdaOrConstant(this Delegate parentLambda, out object item, int itemIndex = 0) + internal static IEnumerable EnumerateNestedLambdas(object[] constantsAndNestedLambdas) { - var target = parentLambda.Target; - if (target is ExpressionCompiler.DebugArrayClosure t) - { - var closureItems = t.ConstantsAndNestedLambdas; - if (itemIndex < closureItems.Length) + if (constantsAndNestedLambdas != null && constantsAndNestedLambdas.Length != 0) + // todo: @perf how to skip until the nested lambdas fast + foreach (var item in constantsAndNestedLambdas) { - item = closureItems[itemIndex]; - return true; - } - } - item = null; - return false; - } + if (item is IDelegateDebugInfo diagInfo) + yield return diagInfo; - public static bool TryGetDebugClosureNestedLambda(this Delegate parentLambda, int itemIndex, out Delegate d) - { - var target = parentLambda.Target; - if (target is ExpressionCompiler.DebugArrayClosure t) - { - var closureItems = t.ConstantsAndNestedLambdas; - if (itemIndex < closureItems.Length) - { - var nestedLambda = closureItems[itemIndex]; - d = (Delegate)(nestedLambda is NestedLambdaForNonPassedParams n ? n.NestedLambda : nestedLambda); - return true; + var dlg = (item is NestedLambdaForNonPassedParams nestedLambda ? nestedLambda.NestedLambda : item) as Delegate; + if (dlg != null && dlg.Target is IDelegateDebugInfo dlgDebugInfo) + yield return dlgDebugInfo; } - } - d = null; - return false; } - public static IEnumerable EnumerateDebugConstantsAndNestedLambdas(this Delegate parentLambda) + /// Enumerate any nested lambdas in the delegate compiled with CompilerFlags.EnableDelegateDebugInfo flag + public static IEnumerable EnumerateNestedLambdas( + this Delegate fastCompiledDelegateWithDebugInfoFlag) => + fastCompiledDelegateWithDebugInfoFlag.Target is ArrayClosure closure ? EnumerateNestedLambdas(closure.ConstantsAndNestedLambdas) : []; + + [RequiresUnreferencedCode(Trimming.Message)] + public sealed class DebugArrayClosure : ArrayClosureWithNonPassedParams, IDelegateDebugInfo { - var target = parentLambda.Target; - if (target is ExpressionCompiler.DebugArrayClosure t) + public LambdaExpression Expression { get; internal set; } + + public ILInstruction[] ILInstructions { get; internal set; } + + public DebugArrayClosure(object[] nonPassedParams, object[] constantsAndNestedLambdas, + LambdaExpression expr, ILInstruction[] il = null) + : base(nonPassedParams, constantsAndNestedLambdas) { - foreach (var item in t.ConstantsAndNestedLambdas) - if (item is NestedLambdaForNonPassedParams nestedLambda) - yield return nestedLambda.NestedLambda; - else - yield return item; + Expression = expr; + ILInstructions = il; } + + [RequiresUnreferencedCode(Trimming.Message)] + public IEnumerable EnumerateNestedLambdas() => + ExpressionCompiler.EnumerateNestedLambdas(ConstantsAndNestedLambdas); } // todo: @perf better to move the case with no constants to another class OR we can reuse ArrayClosure but now ConstantsAndNestedLambdas will hold NonPassedParams - public sealed class ArrayClosureWithNonPassedParams : ArrayClosure + public class ArrayClosureWithNonPassedParams : ArrayClosure { public readonly object[] NonPassedParams; public ArrayClosureWithNonPassedParams(object[] nonPassedParams, object[] constantsAndNestedLambdas) : base(constantsAndNestedLambdas) => NonPassedParams = nonPassedParams; - // todo: @perf optimize for this case public ArrayClosureWithNonPassedParams(object[] nonPassedParams) : base(null) => NonPassedParams = nonPassedParams; } @@ -1209,16 +1072,28 @@ public class NestedLambdaForNonPassedParams public NestedLambdaForNonPassedParams(object nestedLambda) => NestedLambda = nestedLambda; } - public sealed class NestedLambdaForNonPassedParamsWithConstants : NestedLambdaForNonPassedParams + public class NestedLambdaForNonPassedParamsWithConstants : NestedLambdaForNonPassedParams { public static FieldInfo ConstantsAndNestedLambdasField = typeof(NestedLambdaForNonPassedParamsWithConstants).GetField(nameof(ConstantsAndNestedLambdas)); - public readonly object ConstantsAndNestedLambdas; - public NestedLambdaForNonPassedParamsWithConstants(object nestedLambda, object constantsAndNestedLambdas) + public readonly object[] ConstantsAndNestedLambdas; + public NestedLambdaForNonPassedParamsWithConstants(object nestedLambda, object[] constantsAndNestedLambdas) : base(nestedLambda) => ConstantsAndNestedLambdas = constantsAndNestedLambdas; } + public sealed class NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo : NestedLambdaForNonPassedParamsWithConstants, IDelegateDebugInfo + { + public LambdaExpression Expression { get; } + public ILInstruction[] ILInstructions { get; internal set; } + public NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo(object nestedLambda, object[] constantsAndNestedLambdas, LambdaExpression expr) + : base(nestedLambda, constantsAndNestedLambdas) => Expression = expr; + + [RequiresUnreferencedCode(Trimming.Message)] + public IEnumerable EnumerateNestedLambdas() => + ExpressionCompiler.EnumerateNestedLambdas(ConstantsAndNestedLambdas); + } + internal static class CurryClosureFuncs { public static readonly MethodInfo[] Methods = typeof(CurryClosureFuncs).GetMethods(); @@ -1342,6 +1217,19 @@ public enum Result NotSupported_ExceptionCatchFilter = 1010 } + /// Return value is ignored + [MethodImpl(MethodImplOptions.NoInlining)] + internal static T NotSupportedCase(Result reason) + { + if (reason == Result.OK) + { + Debug.WriteLine($"Not support case found in TryEmit phase because the TryCollect phase is {reason}"); + Debugger.Break(); + } + Debug.WriteLine($"Not supported case is found with the reason: {reason}"); + throw new NotSupportedExpressionException(reason); + } + /// Wraps the call to `TryCollectInfo` for the compatibility and provide the root place to check the returned error code. /// Important: The method collects the info from the nested lambdas up-front and de-duplicates the lambdas as well. [MethodImpl((MethodImplOptions)256)] @@ -1354,9 +1242,7 @@ public static bool TryCollectBoundConstants(ref ClosureInfo closure, Expression NestedLambdaInfo nestedLambda, ref SmallList rootNestedLambdas, CompilerFlags flags) { var r = TryCollectInfo(ref closure, expr, paramExprs, nestedLambda, ref rootNestedLambdas, flags); - if (r != Result.OK & (flags & CompilerFlags.ThrowOnNotSupportedExpression) != 0) - throw new NotSupportedExpressionException(r); - return true; // exposed here for debugging to set a breakpoint + return r == Result.OK || (flags & CompilerFlags.ThrowOnNotSupportedExpression) != 0 && NotSupportedCase(r); } /// Collects the information about closure constants, nested lambdas, non-passed parameters, goto labels and variables in blocks. @@ -1911,7 +1797,7 @@ private static bool FindAlreadyCompiledNestedLambdaInfoInLambdas( return false; } - private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, NestedLambdaInfo nestedLambdaInfo, CompilerFlags setup) + private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, NestedLambdaInfo nestedLambdaInfo, CompilerFlags flags) { // 1. Try to compile nested lambda in place // 2. Check that parameters used in compiled lambda are passed or closed by outer lambda @@ -1922,11 +1808,11 @@ private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, Ne #if LIGHT_EXPRESSION var nestedLambdaParamExprs = (IParameterProvider)nestedLambdaExpr; - if (nestedLambdaBody is NoArgsNewClassIntrinsicExpression newNoArgs) + if (nestedLambdaBody is NoArgsNewClassIntrinsicExpression newExpr) { - var paramTypes = RentOrNewClosureTypeToParamTypes(nestedLambdaParamExprs); - nestedLambdaInfo.Lambda = CompileNoArgsNew(newNoArgs.Constructor, nestedLambdaExpr.Type, paramTypes, nestedReturnType); - FreeClosureTypeAndParamTypes(paramTypes); + var paramTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs); + nestedLambdaInfo.Lambda = CompileNoArgsNew(newExpr, nestedLambdaExpr.Type, paramTypes, nestedReturnType, flags); + FreePooledClosureTypeAndParamTypes(paramTypes); return true; } #else @@ -1936,47 +1822,66 @@ private static bool TryCompileNestedLambda(ref ClosureInfo nestedClosureInfo, Ne nestedClosureInfo.NestedLambdas = nestedLambdaInfo.NestedLambdas; nestedClosureInfo.NonPassedParameters = nestedLambdaInfo.NonPassedParameters; - var nestedConstsAndLambdas = nestedClosureInfo.GetArrayOfConstantsAndNestedLambdas(); + var constantsAndNestedLambdas = (nestedClosureInfo.Status & ClosureStatus.HasClosure) != 0 + ? nestedClosureInfo.GetArrayOfConstantsAndNestedLambdas() + : null; ArrayClosure nestedLambdaClosure = null; + var hasDebugInfo = (flags & CompilerFlags.EnableDelegateDebugInfo) != 0; var hasNonPassedParameters = nestedLambdaInfo.NonPassedParameters.Count != 0; if (!hasNonPassedParameters) - nestedLambdaClosure = (nestedClosureInfo.Status & ClosureStatus.HasClosure) == 0 - ? EmptyArrayClosure - : new ArrayClosure(nestedConstsAndLambdas); + nestedLambdaClosure = !hasDebugInfo + ? (constantsAndNestedLambdas == null ? EmptyArrayClosure : new ArrayClosure(constantsAndNestedLambdas)) + : new DebugArrayClosure(null, constantsAndNestedLambdas, nestedLambdaExpr); - var closurePlusParamTypes = RentOrNewClosureTypeToParamTypes(nestedLambdaParamExprs); + var closurePlusParamTypes = RentPooledOrNewClosureTypeToParamTypes(nestedLambdaParamExprs); var method = new DynamicMethod(string.Empty, nestedReturnType, closurePlusParamTypes, typeof(ArrayClosure), true); - var il = method.GetILGenerator(); + var il = DynamicMethodHacks.RentPooledOrNewILGenerator(method, nestedReturnType, closurePlusParamTypes); - if (nestedConstsAndLambdas != null) + if (constantsAndNestedLambdas != null) EmittingVisitor.EmitLoadConstantsAndNestedLambdasIntoVars(il, ref nestedClosureInfo); var parent = nestedReturnType == typeof(void) ? ParentFlags.IgnoreResult : ParentFlags.LambdaCall; if (nestedReturnType.IsByRef) parent |= ParentFlags.ReturnByRef; - if (!EmittingVisitor.TryEmit(nestedLambdaBody, nestedLambdaParamExprs, il, ref nestedClosureInfo, setup, parent)) - return false; - il.Demit(OpCodes.Ret); + var emitOk = EmittingVisitor.TryEmit(nestedLambdaBody, nestedLambdaParamExprs, il, ref nestedClosureInfo, flags, parent); + if (emitOk) + { + il.Demit(OpCodes.Ret); - // If we don't have closure then create a static or an open delegate to pass closure later in `TryEmitNestedLambda`, - // constructing the new closure with NonPassedParams and the rest of items stored in NestedLambdaWithConstantsAndNestedLambdas - var nestedLambda = nestedLambdaClosure != null - ? method.CreateDelegate(nestedLambdaExpr.Type, nestedLambdaClosure) - : method.CreateDelegate(Tools.GetFuncOrActionType(closurePlusParamTypes, nestedReturnType), null); + // If we don't have closure then create a static or an open delegate to pass closure later in `TryEmitNestedLambda`, + // constructing the new closure with NonPassedParams and the rest of items stored in NestedLambdaWithConstantsAndNestedLambdas + var nestedLambda = nestedLambdaClosure != null + ? method.CreateDelegate(nestedLambdaExpr.Type, nestedLambdaClosure) + : method.CreateDelegate(Tools.GetFuncOrActionType(closurePlusParamTypes, nestedReturnType), null); - nestedLambdaInfo.Lambda = !hasNonPassedParameters ? nestedLambda - : nestedConstsAndLambdas == null ? new NestedLambdaForNonPassedParams(nestedLambda) - : new NestedLambdaForNonPassedParamsWithConstants(nestedLambda, nestedConstsAndLambdas); + nestedLambdaInfo.Lambda = !hasNonPassedParameters + ? nestedLambda + : !hasDebugInfo + ? constantsAndNestedLambdas == null + ? new NestedLambdaForNonPassedParams(nestedLambda) + : new NestedLambdaForNonPassedParamsWithConstants(nestedLambda, constantsAndNestedLambdas) + : new NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo(nestedLambda, constantsAndNestedLambdas, nestedLambdaExpr); - FreeClosureTypeAndParamTypes(closurePlusParamTypes); - return true; + if (hasDebugInfo) + { + var ilInstructions = nestedLambda.Method.ReadAllInstructions(); + if (nestedLambdaClosure is DebugArrayClosure debugInfoClosure) + debugInfoClosure.ILInstructions = ilInstructions; + else + ((NestedLambdaForNonPassedParamsWithConstantsWithDebugInfo)nestedLambdaInfo.Lambda).ILInstructions = ilInstructions; + } + } + DynamicMethodHacks.FreePooledILGenerator(method, il); + FreePooledClosureTypeAndParamTypes(closurePlusParamTypes); + return emitOk; } /// Return IDelegateDebugInfo if the delegate is fast compiled with `CompilerFlags.EnableDelegateDebugInfo` flag - public static IDelegateDebugInfo TryGetDebugInfo(this D d) where D : Delegate => d.Target as IDelegateDebugInfo; + public static IDelegateDebugInfo TryGetDebugInfo(this TDelegate d) + where TDelegate : Delegate => d?.Target as IDelegateDebugInfo; #if LIGHT_EXPRESSION private static Result TryCollectMemberInitExprConstants(ref ClosureInfo closure, MemberInitExpression expr, @@ -2482,18 +2387,12 @@ public static bool TryEmit(Expression expr, case ExpressionType.Throw: { - var throwExpr = (UnaryExpression)expr; - if (throwExpr.Operand is null) - { - il.Demit(OpCodes.Rethrow); - } - else - { - if (!TryEmit(throwExpr.Operand, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult)) - return false; - il.Demit(OpCodes.Throw); - } - return true; + var ok = true; + var throwOperand = ((UnaryExpression)expr).Operand; + if (throwOperand != null) + ok = TryEmit(throwOperand, paramExprs, il, ref closure, setup, parent & ~ParentFlags.IgnoreResult); + il.Demit(throwOperand != null ? OpCodes.Throw : OpCodes.Rethrow); + return ok; } case ExpressionType.Default: @@ -3668,11 +3567,9 @@ public static bool TryEmitConstant(ConstantExpression expr, Type exprType, ILGen var constValue = expr.Value; if (constValue != null) ok = TryEmitConstant(closure.ContainsConstantsOrNestedLambdas(), exprType, constValue.GetType(), constValue, il, ref closure, byRefIndex); - else if (exprType.IsValueType) // null for a value type - { - EmitLoadLocalVariable(il, InitValueTypeVariable(il, exprType)); // yep, this is a proper way to emit the Nullable null - ok = true; - } + else if (exprType.IsValueType) + // null for a value type and yep, this is a proper way to emit the Nullable null + ok = EmitLoadLocalVariable(il, InitValueTypeVariable(il, exprType)); else { il.Demit(OpCodes.Ldnull); @@ -3680,15 +3577,21 @@ public static bool TryEmitConstant(ConstantExpression expr, Type exprType, ILGen } } - if (ok && byRefIndex != -1) - EmitStoreAndLoadLocalVariableAddress(il, exprType); // todo: @wip are we doing it twice inside the TryEmitConstant and here? + if (ok & byRefIndex != -1) + EmitStoreAndLoadLocalVariableAddress(il, exprType); return ok; } [MethodImpl((MethodImplOptions)256)] - public static bool TryEmitNotNullConstant(bool considerClosure, object consValue, ILGenerator il, ref ClosureInfo closure, - int byRefIndex = -1) => - TryEmitConstant(considerClosure, null, consValue.GetType(), consValue, il, ref closure, byRefIndex); + public static bool TryEmitNotNullConstant(bool considerClosure, object constValue, ILGenerator il, ref ClosureInfo closure, int byRefIndex = -1) + { + Debug.Assert(constValue != null, "Expecting that the constant value is not null here"); + var constType = constValue.GetType(); + var ok = TryEmitConstant(considerClosure, null, constType, constValue, il, ref closure, byRefIndex); + if (ok & byRefIndex != -1) + EmitStoreAndLoadLocalVariableAddress(il, constType); + return ok; + } public static bool TryEmitConstant(bool considerClosure, Type exprType, Type constType, object constValue, ILGenerator il, ref ClosureInfo closure, int byRefIndex = -1, FieldInfo refField = null) @@ -3724,19 +3627,13 @@ public static bool TryEmitConstant(bool considerClosure, Type exprType, Type con } #endif if (constType.IsValueType) - { il.Demit(OpCodes.Unbox_Any, constType); - if (byRefIndex != -1) - EmitStoreAndLoadLocalVariableAddress(il, constType); - } #if NETFRAMEWORK else - { - // The cast probably required only for Full CLR starting, + // The cast is probably required only for the Full CLR, // e.g. `Test_283_Case6_MappingSchemaTests_CultureInfo_VerificationException`. // .NET Core does not seem to care about verifiability and it's faster without the explicit cast. il.Demit(OpCodes.Castclass, constType); - } #endif } } @@ -4475,7 +4372,7 @@ private static bool TryEmitArithmeticAndOrAssign( // required for calling the method on the value type parameter var objType = objExpr.Type; - objVarByAddress = objType.IsValueType && !closure.LastEmitIsAddress && // todo: @wip avoid ad-hocking with parameter here + objVarByAddress = !closure.LastEmitIsAddress && objType.IsValueType && // todo: @wip avoid ad-hocking with parameter here (objExpr.NodeType != ExpressionType.Parameter || !((ParameterExpression)objExpr).IsByRef); if (objVarByAddress) objVar = EmitStoreAndLoadLocalVariableAddress(il, objType); @@ -5332,7 +5229,8 @@ private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr, IReadOnlyLi EmitLoadLocalVariable(il, nonPassedParamsVarIndex); // Load the constants as a second argument and call the closure constructor - if (nestedLambdaInfo.Lambda is NestedLambdaForNonPassedParamsWithConstants) + var lambda = nestedLambdaInfo.Lambda; + if (lambda is NestedLambdaForNonPassedParamsWithConstants) { EmitLoadLocalVariable(il, nestedLambdaInfo.LambdaVarIndex); il.Demit(OpCodes.Ldfld, NestedLambdaForNonPassedParamsWithConstants.ConstantsAndNestedLambdasField); @@ -5342,7 +5240,6 @@ private static bool TryEmitNestedLambda(LambdaExpression lambdaExpr, IReadOnlyLi il.Demit(OpCodes.Newobj, ArrayClosureWithNonPassedParamsCtor); // Call the `Curry` method with the nested lambda and closure to produce a closed lambda with the expected signature - var lambda = nestedLambdaInfo.Lambda; var lambdaType = (lambda is NestedLambdaForNonPassedParams lp ? lp.NestedLambda : lambda).GetType(); var lambdaTypeArgs = lambdaType.GetGenericArguments(); var nestedLambdaExpr = nestedLambdaInfo.LambdaExpression; @@ -5676,8 +5573,8 @@ private static bool TryEmitComparison( if (!TryEmit(left, paramExprs, il, ref closure, setup, operandParent)) return false; - // This is only required for the special case, check the #341 `Nullable_decimal_parameter_with_decimal_constant_comparison_cases` - if (leftType.GetUnderlyingNullableTypeUnsafe() == typeof(decimal)) + // See #341 `Nullable_decimal_parameter_with_decimal_constant_comparison_cases` + if (!closure.LastEmitIsAddress && !(left is ParameterExpression p && p.IsByRef)) // Nullable type does not track IsByRef for some reason, so we check the param explicitly, see #461 `Case_equal_nullable_and_object_null` EmitStoreAndLoadLocalVariableAddress(il, leftType); EmitMethodCall(il, leftType.GetNullableHasValueGetterMethod()); @@ -5691,7 +5588,7 @@ private static bool TryEmitComparison( if (!TryEmit(right, paramExprs, il, ref closure, setup, operandParent)) return false; - if (rightType.GetUnderlyingNullableTypeUnsafe() == typeof(decimal)) + if (!closure.LastEmitIsAddress && !(right is ParameterExpression p && p.IsByRef)) EmitStoreAndLoadLocalVariableAddress(il, rightType); EmitMethodCall(il, rightType.GetNullableHasValueGetterMethod()); @@ -8323,11 +8220,17 @@ public static T GetFirst(this IEnumerable source) [RequiresUnreferencedCode(Trimming.Message)] public static class ILGeneratorTools { + /// Configuration option to disable the pooling + public static bool DisableILGeneratorPooling; + /// Configuration option to disable the ILGenerator Emit debug output + public static bool DisableDemit; + #if DEMIT [MethodImpl((MethodImplOptions)256)] public static void Demit(this ILGenerator il, OpCode opcode, [CallerMemberName] string emitterName = "", [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode); + if (DisableDemit) return; Debug.WriteLine($"{opcode} -- {emitterName}:{emitterLine}"); } @@ -8335,6 +8238,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, [CallerMemberName] public static void Demit(this ILGenerator il, OpCode opcode, Type type, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, type); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {type.ToCode(stripNamespace: true)} -- {emitterName}:{emitterLine}"); } @@ -8342,6 +8246,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, Type type, [CallerM public static void Demit(this ILGenerator il, OpCode opcode, FieldInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; var fieldType = value.FieldType.ToCode(stripNamespace: true); @@ -8352,6 +8257,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, FieldInfo value, [C public static void Demit(this ILGenerator il, OpCode opcode, MethodInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; var retType = value.ReturnType.ToCode(stripNamespace: true); @@ -8365,6 +8271,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, MethodInfo value, [ public static void Demit(this ILGenerator il, OpCode opcode, ConstructorInfo value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; var declType = value.DeclaringType?.ToCode(stripNamespace: true) ?? ""; var signature = value.ToString(); @@ -8378,6 +8285,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, Label value, [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {valueName ?? value.ToString()} -- {emitterName}:{emitterLine}"); } @@ -8386,6 +8294,7 @@ public static void DmarkLabel(this ILGenerator il, Label value, [CallerArgumentExpression("value")] string valueName = null, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.MarkLabel(value); + if (DisableDemit) return; Debug.WriteLine($"{valueName ?? value.ToString()} -- {emitterName}:{emitterLine}: "); } @@ -8393,6 +8302,7 @@ public static void DmarkLabel(this ILGenerator il, Label value, public static void Demit(this ILGenerator il, OpCode opcode, byte value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); } @@ -8400,6 +8310,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, byte value, [Caller public static void Demit(this ILGenerator il, OpCode opcode, sbyte value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); } @@ -8407,6 +8318,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, sbyte value, [Calle public static void Demit(this ILGenerator il, OpCode opcode, short value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); } @@ -8414,6 +8326,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, short value, [Calle public static void Demit(this ILGenerator il, OpCode opcode, int value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); } @@ -8421,6 +8334,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, int value, [CallerM public static void Demit(this ILGenerator il, OpCode opcode, long value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); } @@ -8428,6 +8342,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, long value, [Caller public static void Demit(this ILGenerator il, OpCode opcode, float value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); } @@ -8435,6 +8350,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, float value, [Calle public static void Demit(this ILGenerator il, OpCode opcode, double value, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); } @@ -8442,6 +8358,7 @@ public static void Demit(this ILGenerator il, OpCode opcode, double value, [Call public static void Demit(this ILGenerator il, string value, OpCode opcode, [CallerMemberName] string emitterName = null, [CallerLineNumber] int emitterLine = 0) { il.Emit(opcode, value); + if (DisableDemit) return; Debug.WriteLine($"{opcode} {value} -- {emitterName}:{emitterLine}"); } @@ -8496,86 +8413,535 @@ public static void Demit(this ILGenerator il, string value, OpCode opcode, [Call /// Reflecting the internal methods to access the more performant for defining the local variable [RequiresUnreferencedCode(Trimming.Message)] - public static class ILGeneratorHacks + public static class DynamicMethodHacks { - // The original ILGenerator methods we are trying to hack without allocating the `LocalBuilder` - /* - public virtual LocalBuilder DeclareLocal(Type localType) + [ThreadStatic] + internal static ILGenerator _pooledILGenerator; + +#if NET6_0_OR_GREATER + /// Get new or pool and configure existing DynamicILGenerator + [MethodImpl((MethodImplOptions)256)] + public static ILGenerator RentPooledOrNewILGenerator(DynamicMethod dynMethod, Type returnType, Type[] paramTypes, + // the default ILGenerator size is 64 in .NET 8.0+ + int newStreamSize = 64) { - return this.DeclareLocal(localType, false); + var reuseILGenerator = DynamicMethodHacks.ReuseDynamicILGenerator; + if (reuseILGenerator != null) + { + var pooledIL = _pooledILGenerator; + _pooledILGenerator = null; + if (pooledIL != null) + { + reuseILGenerator(dynMethod, pooledIL, returnType, paramTypes); + return pooledIL; + } + else + { + Debug.WriteLine("Unexpected: using a New ILGenerator instead of the pooled one"); + } + } + return dynMethod.GetILGenerator(newStreamSize); } - public virtual LocalBuilder DeclareLocal(Type localType, bool pinned) - { - MethodBuilder methodBuilder = this.m_methodBuilder as MethodBuilder; - if ((MethodInfo)methodBuilder == (MethodInfo)null) - throw new NotSupportedException(); - if (methodBuilder.IsTypeCreated()) - throw new InvalidOperationException(SR.InvalidOperation_TypeHasBeenCreated); - if (localType == (Type)null) - throw new ArgumentNullException(nameof(localType)); - if (methodBuilder.m_bIsBaked) - throw new InvalidOperationException(SR.InvalidOperation_MethodBaked); - this.m_localSignature.AddArgument(localType, pinned); - LocalBuilder localBuilder = new LocalBuilder(this.m_localCount, localType, (MethodInfo)methodBuilder, pinned); - ++this.m_localCount; - return localBuilder; + /// Should be called only after call to DynamicMethod.CreateDelegate + [MethodImpl((MethodImplOptions)256)] + public static void FreePooledILGenerator(DynamicMethod dynMethod, ILGenerator il) + { + if (DynamicMethodHacks.ReuseDynamicILGenerator != null) + _pooledILGenerator = il; } - */ +#else + /// Get new or pool and configure existing DynamicILGenerator + [MethodImpl((MethodImplOptions)256)] + public static ILGenerator RentPooledOrNewILGenerator(DynamicMethod dynMethod, Type returnType, Type[] paramTypes, + int newStreamSize = 64) => + dynMethod.GetILGenerator(newStreamSize); + + /// Should be called only after call to DynamicMethod.CreateDelegate + [MethodImpl((MethodImplOptions)256)] + public static void FreePooledILGenerator(DynamicMethod dynMethod, ILGenerator il) { /* do nothing */ } +#endif - internal static readonly Func _getNextLocalVarIndex; + internal static readonly Func GetNextLocalVarLocation; internal static int PostInc(ref int i) => i++; - static ILGeneratorHacks() + internal static Action ReuseDynamicILGenerator; + +#pragma warning disable CS0649 // Warning is about the unused field, but it is used by the generated ReuseDynamicILGenerator + [ThreadStatic] + internal static SignatureHelper _pooledSignatureHelper; +#pragma warning restore CS0649 + + internal static FieldInfo ILGeneratorField; + internal static Type DynamicILGeneratorType; + + static DynamicMethodHacks() { - // the default allocatee method - _getNextLocalVarIndex = (i, t) => i.DeclareLocal(t).LocalIndex; + const BindingFlags instanceNonPublic = BindingFlags.Instance | BindingFlags.NonPublic; + const BindingFlags instancePublic = BindingFlags.Instance | BindingFlags.Public; + const BindingFlags staticNonPublic = BindingFlags.Static | BindingFlags.NonPublic; + const BindingFlags staticPublic = BindingFlags.Static | BindingFlags.Public; + + ILGeneratorField = typeof(DynamicMethod).GetField("_ilGenerator", instanceNonPublic); + if (ILGeneratorField == null) + return; // nothing to do here + + DynamicILGeneratorType = ILGeneratorField.FieldType; + + // Avoid demit polluting the output of the the initialization phase + var prevDemitValue = ILGeneratorTools.DisableDemit; + ILGeneratorTools.DisableDemit = true; + + if (!ILGeneratorTools.DisableILGeneratorPooling) + { + // ## 1. Reuse the DynamicILGenerator + // + // Reference code - NET. v8, v9: + /* + public ILGenerator GetILGenerator(int streamSize) + { + if (_ilGenerator == null) + { + byte[] methodSignature = SignatureHelper.GetMethodSigHelper( + null, CallingConvention, ReturnType, null, null, _parameterTypes, null, null).GetSignature(true); + _ilGenerator = new DynamicILGenerator(this, methodSignature, streamSize); + } + return _ilGenerator; + } + + internal sealed class DynamicScope + { + internal readonly List m_tokens = new List { null }; + public int GetTokenFor(byte[] signature) + { + m_tokens.Add(signature); + return m_tokens.Count - 1 | (int)MetadataTokenType.Signature; + } + } + + internal DynamicScope m_scope; + private readonly int m_methodSigToken; + + public DynamicILGenerator( + DynamicMethod method, + byte[] methodSignature, + int streamSize) + { + m_scope = new DynamicScope(); + m_methodSigToken = m_scope.GetTokenFor(methodSignature); + + m_ScopeTree = new ScopeTree(); + m_ILStream = new byte[Math.Max(size, DefaultSize)]; + + m_localSignature = SignatureHelper.GetLocalVarSigHelper((method as RuntimeMethodBuilder)?.GetTypeBuilder().Module); + m_methodBuilder = method; // set to the new DynamicMethod + } + */ + var m_ScopeTreeField = DynamicILGeneratorType.GetField("m_ScopeTree", instanceNonPublic); + if (m_ScopeTreeField == null) + goto endOfReuse; + + var ScopeTreeCtor = m_ScopeTreeField.FieldType.GetConstructor(instanceNonPublic, null, Type.EmptyTypes, null); + if (ScopeTreeCtor == null) + goto endOfReuse; + + var DynamicILGeneratorScopeField = DynamicILGeneratorType.GetField("m_scope", instanceNonPublic); + if (DynamicILGeneratorScopeField == null) + goto endOfReuse; + + var DynamicILGeneratorScopeType = DynamicILGeneratorScopeField.FieldType; + var DynamicScopeTokensField = DynamicILGeneratorScopeType.GetField("m_tokens", instanceNonPublic); + if (DynamicScopeTokensField == null) + goto endOfReuse; + + var DynamicScopeTokensItem = DynamicScopeTokensField.FieldType.GetProperty("Item"); + Debug.Assert(DynamicScopeTokensItem != null, "DynamicScopeTokens.Item should not be null"); + + var getMethodSigHelperParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Module), typeof(Type), typeof(Type[])); + var GetMethodSigHelperMethod = typeof(SignatureHelper).GetMethod("GetMethodSigHelper", staticPublic, null, getMethodSigHelperParams, null); + ExpressionCompiler.FreePooledParamTypes(getMethodSigHelperParams); + if (GetMethodSigHelperMethod == null) + goto endOfReuse; + + var GetLocalVarSigHelperMethod = typeof(SignatureHelper).GetMethod("GetLocalVarSigHelper", staticPublic, null, Type.EmptyTypes, null); + if (GetLocalVarSigHelperMethod == null) + goto endOfReuse; + + var getSignatureParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(bool)); + var SignatureHelper_GetSignatureMethod = typeof(SignatureHelper).GetMethod("GetSignature", instanceNonPublic, null, getSignatureParams, null); + ExpressionCompiler.FreePooledParamTypes(getSignatureParams); + if (SignatureHelper_GetSignatureMethod == null) + goto endOfReuse; + + var getTokenForParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(byte[])); + var GetTokenForMethod = DynamicILGeneratorScopeType.GetMethod("GetTokenFor", instancePublic, null, getTokenForParams, null); + ExpressionCompiler.FreePooledParamTypes(getTokenForParams); + if (GetTokenForMethod == null) + goto endOfReuse; + + var MethodSigTokenField = DynamicILGeneratorType.GetField("m_methodSigToken", instanceNonPublic); + if (MethodSigTokenField == null) + goto endOfReuse; + + var dynMethodParamTypes = ExpressionCompiler.RentPooledOrNewParamTypes( + typeof(ExpressionCompiler.ArrayClosure), typeof(DynamicMethod), typeof(ILGenerator), typeof(Type), typeof(Type[])); + + var dynMethod = new DynamicMethod(string.Empty, typeof(void), dynMethodParamTypes, typeof(ExpressionCompiler.ArrayClosure), true); + + var il = dynMethod.GetILGenerator(512); // precalculated size to avoid waste + + var baseFields = DynamicILGeneratorType.BaseType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); + foreach (var field in baseFields) + { + var fieldName = field.Name; + if (fieldName == "m_localSignature") + { + il.Demit(OpCodes.Ldarg_2); + il.Demit(OpCodes.Call, GetLocalVarSigHelperMethod); + il.Demit(OpCodes.Stfld, field); + continue; + } + + // m_ScopeTree = new ScopeTree(); + if (fieldName == "m_ScopeTree") + { + il.Demit(OpCodes.Ldarg_2); + il.Demit(OpCodes.Newobj, ScopeTreeCtor); + il.Demit(OpCodes.Stfld, field); + continue; + } + + // m_methodBuilder = method; // dynamicMethod + if (fieldName == "m_methodBuilder") + { + il.Demit(OpCodes.Ldarg_2); + il.Demit(OpCodes.Ldarg_1); + il.Demit(OpCodes.Stfld, field); + continue; + } - // now let's try to acquire the more efficient less allocating method - var ilGenTypeInfo = typeof(ILGenerator).GetTypeInfo(); - var localSignatureField = ilGenTypeInfo.GetDeclaredField("m_localSignature"); - if (localSignatureField == null) - return; + if (fieldName == "m_ILStream") + continue; // reuse the previous il stream - var localCountField = ilGenTypeInfo.GetDeclaredField("m_localCount"); - if (localCountField == null) - return; + il.Demit(OpCodes.Ldarg_2); + ExpressionCompiler.EmittingVisitor.EmitDefault(il, field.FieldType); + il.Demit(OpCodes.Stfld, field); + } + + // var scope = new DynamicScope(); + var dynamicScopeCtor = DynamicILGeneratorScopeType.GetConstructor(Type.EmptyTypes); + if (dynamicScopeCtor == null) + goto endOfReuse; + il.Emit(OpCodes.Newobj, dynamicScopeCtor); + var scopeVar = il.DeclareLocal(DynamicILGeneratorScopeType).LocalIndex; + ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, scopeVar); + + /* + private byte[] m_signature; // todo: @perf keep it, because it would be copied anyway + private int m_currSig; // index into m_signature buffer for next available byte + private int m_sizeLoc; // index into m_signature buffer to put m_argCount (will be NO_SIZE_IN_SIG if no arg count is needed) + private ModuleBuilder? m_module; + private bool m_sigDone; + private int m_argCount; // tracking number of arguments in the signature + + internal static SignatureHelper GetMethodSigHelper( + Module? scope, CallingConventions callingConvention, int cGenericParam, + Type? returnType, Type[]? requiredReturnTypeCustomModifiers, Type[]? optionalReturnTypeCustomModifiers, + Type[]? parameterTypes, Type[][]? requiredParameterTypeCustomModifiers, Type[][]? optionalParameterTypeCustomModifiers) + { + SignatureHelper sigHelp; + MdSigCallingConvention intCall; + + // not needed, always provided + returnType ??= typeof(void); + + // not needed, always CallingConventions.Standard + intCall = MdSigCallingConvention.Default; + if ((callingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs) + intCall = MdSigCallingConvention.Vararg; + + // not needed, always 0 + if (cGenericParam > 0) + { + intCall |= MdSigCallingConvention.Generic; + } + + // not needed, can be externalized as a const, precalculate the `unchecked((byte)CallingConventions.Standard) & Mask)` + const byte Mask = (byte)(CallingConventions.HasThis | CallingConventions.ExplicitThis); + intCall = (MdSigCallingConvention)((byte)intCall | (unchecked((byte)callingConvention) & Mask)); + + sigHelp = new SignatureHelper(scope, intCall, cGenericParam, returnType, + requiredReturnTypeCustomModifiers, optionalReturnTypeCustomModifiers); + + // m_signature = new byte[32]; + // m_currSig = 0; + // m_module = mod as ModuleBuilder; + // m_argCount = 0; + // m_sigDone = false; + // m_sizeLoc = NO_SIZE_IN_SIG; + + sigHelp.AddArguments(parameterTypes, requiredParameterTypeCustomModifiers, optionalParameterTypeCustomModifiers); + + return sigHelp; + } + */ + // pseudo code to reuse the pooled SignatureHelper + /* + var signatureHelper = _pooledSignatureHelper; + _pooledSignatureHelper = null; + if (signatureHelper == null) + signatureBytes = SignatureHelper.GetMethodSigHelper(null, returnType, paramTypes).GetSignature(true); + else + { + // no need to set, can reuse the previous value + // m_signature = new byte[32]; + // signatureHelper.m_module = null; + signatureHelper.m_currSig = 0; + signatureHelper.m_argCount = 0; + signatureHelper.m_sigDone = false; + signatureHelper.m_sizeLoc = -1; + signatureHelper.AddOneArgTypeHelper(returnType, null, null); + signatureHelper.AddArguments(paramTypes, null, null); + + var signatureBytes = signatureHelper.GetSignature(true); + _pooledSignatureHelper = signatureHelper; + } + */ + var sigBytesVar = il.DeclareLocal(typeof(byte[])).LocalIndex; + + var pooledSignatureHelperField = typeof(DynamicMethodHacks).GetField(nameof(_pooledSignatureHelper), staticNonPublic); + Debug.Assert(pooledSignatureHelperField != null, "_pooledSignatureHelper field not found!"); + + il.Emit(OpCodes.Ldsfld, pooledSignatureHelperField); + var sigHelperVar = il.DeclareLocal(typeof(SignatureHelper)).LocalIndex; + ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, sigHelperVar); // loading it here to do a Brfalse below, after setting the field to null + il.Emit(OpCodes.Ldnull); // set the pooled instance to null + il.Emit(OpCodes.Stsfld, pooledSignatureHelperField); + + var labelSigHelperNull = il.DefineLabel(); + il.Emit(OpCodes.Brfalse, labelSigHelperNull); + + // signatureHelper.m_currSig = 0 + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_0); + var m_currSig = typeof(SignatureHelper).GetField("m_currSig", instanceNonPublic); + if (m_currSig == null) + goto endOfReuse; + il.Emit(OpCodes.Stfld, m_currSig); + + //signatureHelper.m_argCount = 0; + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_0); + var m_argCount = typeof(SignatureHelper).GetField("m_argCount", instanceNonPublic); + if (m_argCount == null) + goto endOfReuse; + il.Emit(OpCodes.Stfld, m_argCount); + + // signatureHelper.m_sigDone = false; + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_0); + var m_sigDone = typeof(SignatureHelper).GetField("m_sigDone", instanceNonPublic); + if (m_sigDone == null) + goto endOfReuse; + il.Emit(OpCodes.Stfld, m_sigDone); + + // signatureHelper.m_sizeLoc = -1; + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_M1); + var m_sizeLoc = typeof(SignatureHelper).GetField("m_sizeLoc", instanceNonPublic); + if (m_sizeLoc == null) + goto endOfReuse; + il.Emit(OpCodes.Stfld, m_sizeLoc); + + // signatureHelper.AddOneArgTypeHelper(returnType, null, null); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldarg_3); // load return type + il.Emit(OpCodes.Ldnull); // load required return type custom modifiers + il.Emit(OpCodes.Ldnull); // load optional return type custom modifiers + var addOneArgTypeHelperParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type), typeof(Type[]), typeof(Type[])); + var AddOneArgTypeHelperMethod = typeof(SignatureHelper).GetMethod("AddOneArgTypeHelper", instanceNonPublic, null, addOneArgTypeHelperParams, null); + ExpressionCompiler.FreePooledParamTypes(addOneArgTypeHelperParams); + if (AddOneArgTypeHelperMethod == null) + goto endOfReuse; + il.Emit(OpCodes.Call, AddOneArgTypeHelperMethod); + + // signatureHelper.AddArguments(paramTypes, null, null); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldarg_S, 4); // load parameter types arrays + il.Emit(OpCodes.Ldnull); // load required parameter type custom modifiers + il.Emit(OpCodes.Ldnull); // load optional parameter type custom modifiers + + var addArgumentsParams = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type[]), typeof(Type[][]), typeof(Type[][])); + var AddArgumentsMethod = typeof(SignatureHelper).GetMethod("AddArguments", instancePublic, null, addArgumentsParams, null); + ExpressionCompiler.FreePooledParamTypes(addArgumentsParams); + if (AddArgumentsMethod == null) + goto endOfReuse; + il.Emit(OpCodes.Call, AddArgumentsMethod); + + // signatureBytes = signatureHelper.GetSignature(true); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Ldc_I4_1); // load true + il.Emit(OpCodes.Call, SignatureHelper_GetSignatureMethod); + ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, sigBytesVar); + + // free the signature helper to the pool + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigHelperVar); + il.Emit(OpCodes.Stsfld, pooledSignatureHelperField); + + // done + var labelSigHelperDone = il.DefineLabel(); + il.Emit(OpCodes.Br, labelSigHelperDone); + + // Standard handling: + // signatureBytes = SignatureHelper.GetMethodSigHelper(null, returnType, paramTypes).GetSignature(true); + il.MarkLabel(labelSigHelperNull); + + il.Emit(OpCodes.Ldnull); // for the module + il.Emit(OpCodes.Ldarg_3); // load return type + il.Emit(OpCodes.Ldarg_S, 4); // load parameter types arrays + il.Emit(OpCodes.Call, GetMethodSigHelperMethod); + il.Emit(OpCodes.Ldc_I4_1); // load true + il.Emit(OpCodes.Call, SignatureHelper_GetSignatureMethod); + ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, sigBytesVar); + + // todo: @perf GetSignature(true) will copy internal byte buffer almost always, see + /* + internal byte[] GetSignature(bool appendEndOfSig) + { + // Chops the internal signature to the appropriate length. Adds the + // end token to the signature and marks the signature as finished so that + // no further tokens can be added. Return the full signature in a trimmed array. + if (!m_sigDone) + { + if (appendEndOfSig) + AddElementType(CorElementType.ELEMENT_TYPE_END); + SetNumberOfSignatureElements(true); + m_sigDone = true; + } + + // This case will only happen if the user got the signature through + // InternalGetSignature first and then called GetSignature. + if (m_signature.Length > m_currSig) + { + byte[] temp = new byte[m_currSig]; + Array.Copy(m_signature, temp, m_currSig); + m_signature = temp; + } + + return m_signature; + } + */ + + il.MarkLabel(labelSigHelperDone); + + // m_methodSigToken = scope.GetTokenFor(methodSignature); + il.Emit(OpCodes.Ldarg_2); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, sigBytesVar); + il.Emit(OpCodes.Call, GetTokenForMethod); + il.Emit(OpCodes.Stfld, MethodSigTokenField); + + // m_scope = scope; + il.Emit(OpCodes.Ldarg_2); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); + il.Emit(OpCodes.Stfld, DynamicILGeneratorScopeField); - // looking for the `SignatureHelper.AddArgument(Type argument, bool pinned)` - var typeAndBoolParamTypes = ExpressionCompiler.RentParamTypes(typeof(Type), typeof(bool)); - var addArgumentMethod = typeof(SignatureHelper).GetMethod("AddArgument", typeAndBoolParamTypes); - if (addArgumentMethod == null) - return; - ExpressionCompiler.FreeParamTypes(typeAndBoolParamTypes); + // store the reused ILGenerator to + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldarg_2); + il.Emit(OpCodes.Stfld, ILGeneratorField); - // our own helper - always available - var postIncMethod = typeof(ILGeneratorHacks).GetMethod(nameof(PostInc), BindingFlags.Static | BindingFlags.NonPublic); - Debug.Assert(postIncMethod != null, "PostInc method not found!"); + il.Emit(OpCodes.Ret); - var paramTypes = ExpressionCompiler.RentOrNewClosureTypeToParamTypes(typeof(ILGenerator), typeof(Type)); - var efficientMethod = new DynamicMethod(string.Empty, typeof(int), paramTypes, typeof(ExpressionCompiler.ArrayClosure), true); - var il = efficientMethod.GetILGenerator(); + ReuseDynamicILGenerator = (Action) + dynMethod.CreateDelegate(typeof(Action), ExpressionCompiler.EmptyArrayClosure); - // emitting `il.m_localSignature.AddArgument(type);` - il.Emit(OpCodes.Ldarg_1); // load `il` argument (arg_0 is the empty closure object) - il.Emit(OpCodes.Ldfld, localSignatureField); - il.Emit(OpCodes.Ldarg_2); // load `type` argument - il.Emit(OpCodes.Ldc_I4_0); // load `pinned: false` argument - il.Emit(OpCodes.Call, addArgumentMethod); + // Put the first used ILGenerator into the pool, let's not waste it from te get go + _pooledILGenerator = il; - // emitting `return PostInc(ref il.LocalCount);` - il.Emit(OpCodes.Ldarg_1); // load `il` argument - il.Emit(OpCodes.Ldflda, localCountField); - il.Emit(OpCodes.Call, postIncMethod); + ExpressionCompiler.FreePooledParamTypes(dynMethodParamTypes); + endOfReuse:; + } + { + // ## 2. Get Next Local Variable Index/Location + + // The original ILGenerator methods we are trying to hack without allocating the `LocalBuilder` + /* + public virtual LocalBuilder DeclareLocal(Type localType) + { + return this.DeclareLocal(localType, false); + } + + public virtual LocalBuilder DeclareLocal(Type localType, bool pinned) + { + MethodBuilder methodBuilder = this.m_methodBuilder as MethodBuilder; + if ((MethodInfo)methodBuilder == (MethodInfo)null) + throw new NotSupportedException(); + if (methodBuilder.IsTypeCreated()) + throw new InvalidOperationException(SR.InvalidOperation_TypeHasBeenCreated); + if (localType == (Type)null) + throw new ArgumentNullException(nameof(localType)); + if (methodBuilder.m_bIsBaked) + throw new InvalidOperationException(SR.InvalidOperation_MethodBaked); + this.m_localSignature.AddArgument(localType, pinned); + LocalBuilder localBuilder = new LocalBuilder(this.m_localCount, localType, (MethodInfo)methodBuilder, pinned); + ++this.m_localCount; + return localBuilder; + } + */ + // Let's try to acquire the more efficient less allocating method + var m_localSignatureField = DynamicILGeneratorType.GetField("m_localSignature", instanceNonPublic); + if (m_localSignatureField == null) + goto endOfGetNextVar; + + var m_localCountField = DynamicILGeneratorType.GetField("m_localCount", instanceNonPublic); + if (m_localCountField == null) + goto endOfGetNextVar; - il.Emit(OpCodes.Ret); + // looking for the `SignatureHelper.AddArgument(Type argument, bool pinned)` + var typeAndBoolParamTypes = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(Type), typeof(bool)); + var SignatureHelper_AddArgumentMethod = typeof(SignatureHelper).GetMethod("AddArgument", typeAndBoolParamTypes); + ExpressionCompiler.FreePooledParamTypes(typeAndBoolParamTypes); + if (SignatureHelper_AddArgumentMethod == null) + goto endOfGetNextVar; - _getNextLocalVarIndex = (Func)efficientMethod.CreateDelegate( - typeof(Func), ExpressionCompiler.EmptyArrayClosure); + // our own helper - always available + var postIncMethod = typeof(DynamicMethodHacks).GetMethod(nameof(PostInc), staticNonPublic); + Debug.Assert(postIncMethod != null, "PostInc method not found!"); + + var paramTypes = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(ExpressionCompiler.ArrayClosure), typeof(ILGenerator), typeof(Type)); + var dynMethod = new DynamicMethod(string.Empty, typeof(int), paramTypes, typeof(ExpressionCompiler.ArrayClosure), true); + + // it does not use the pooled il generator here, to isolate this variable hack from the il generator pooling hack and for the better problem diagnostics + var il = dynMethod.GetILGenerator(32); + + // emitting `il.m_localSignature.AddArgument(type);` + il.Emit(OpCodes.Ldarg_1); // load `il` argument (arg_0 is the empty closure object) + il.Emit(OpCodes.Ldfld, m_localSignatureField); + il.Emit(OpCodes.Ldarg_2); // load `type` argument + il.Emit(OpCodes.Ldc_I4_0); // load `pinned: false` argument + il.Emit(OpCodes.Call, SignatureHelper_AddArgumentMethod); + + // emitting `return PostInc(ref il.LocalCount);` + il.Emit(OpCodes.Ldarg_1); // load `il` argument + il.Emit(OpCodes.Ldflda, m_localCountField); + il.Emit(OpCodes.Call, postIncMethod); + + il.Emit(OpCodes.Ret); + + GetNextLocalVarLocation = (Func) + dynMethod.CreateDelegate(typeof(Func), ExpressionCompiler.EmptyArrayClosure); + + ExpressionCompiler.FreePooledParamTypes(paramTypes); + endOfGetNextVar:; + } - ExpressionCompiler.FreeClosureTypeAndParamTypes(paramTypes); + // Restore the demit + ILGeneratorTools.DisableDemit = prevDemitValue; + // ## 3 TBD + // // todo: @perf do batch Emit by manually calling `EnsureCapacity` once then `InternalEmit` multiple times // todo: @perf Replace the `Emit(opcode, int)` with the more specialized `Emit(opcode)`, `Emit(opcode, byte)` or `Emit(opcode, short)` // avoiding internal check for Ldc_I4, Ldarg, Ldarga, Starg then call `PutInteger4` only if needed see https://source.dot.net/#System.Private.CoreLib/src/System/Reflection/Emit/ILGenerator.cs,690f350859394132 @@ -8610,7 +8976,7 @@ public static int GetNextLocalVarIndex(this ILGenerator il, Type t) Debug.WriteLine("Error tracking the local variable usage: " + ex); } #endif - return _getNextLocalVarIndex(il, t); + return GetNextLocalVarLocation?.Invoke(il, t) ?? il.DeclareLocal(t).LocalIndex; } // todo: @perf add MultiOpCodes emit to save on the EnsureCapacity calls diff --git a/src/FastExpressionCompiler/ILReader.cs b/src/FastExpressionCompiler/ILReader.cs index 269cb3b8..c3be6db5 100644 --- a/src/FastExpressionCompiler/ILReader.cs +++ b/src/FastExpressionCompiler/ILReader.cs @@ -9,6 +9,8 @@ using System.Reflection.Emit; using System.Text; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Linq; #if LIGHT_EXPRESSION namespace FastExpressionCompiler.LightExpression.ILDecoder; @@ -24,14 +26,14 @@ public static class ILReaderFactory private static readonly Type _runtimeMethodInfoType = Type.GetType("System.Reflection.RuntimeMethodInfo"); private static readonly Type _runtimeConstructorInfoType = Type.GetType("System.Reflection.RuntimeConstructorInfo"); -#if !NET8_0_OR_GREATER +#if !NET6_0_OR_GREATER private static readonly Type _rtDynamicMethodType = Type.GetType("System.Reflection.Emit.DynamicMethod+RTDynamicMethod"); private static readonly FieldInfo _fiOwner = _rtDynamicMethodType.GetField("m_owner", BindingFlags.Instance | BindingFlags.NonPublic); #endif - public static ILReader Create(object source) + public static ILReader GetILReaderOrNull(MethodBase source) { var sourceType = source.GetType(); var dynamicMethod = source as DynamicMethod; @@ -42,21 +44,20 @@ public static ILReader Create(object source) Console.WriteLine($"m_runtimeMethodInfoType: {_runtimeMethodInfoType},\n_runtimeConstructorInfoType: {_runtimeConstructorInfoType} "); #endif -#if !NET8_0_OR_GREATER - if (dynamicMethod == null && sourceType == _rtDynamicMethodType) +#if !NET6_0_OR_GREATER + if (dynamicMethod == null & sourceType == _rtDynamicMethodType) dynamicMethod = (DynamicMethod)_fiOwner.GetValue(source); #if DEBUG_INTERNALS Console.WriteLine($"m_rtDynamicMethodType: {_rtDynamicMethodType}, _fiOwner: {_fiOwner}"); Console.WriteLine($"dynamicMethod < NET8: {(dynamicMethod?.ToString() ?? "null")}"); #endif #endif - - if (dynamicMethod != null) + if (dynamicMethod != null && DynamicScopeTokenResolver.IsSupported) return new ILReader(new DynamicMethodILProvider(dynamicMethod), new DynamicScopeTokenResolver(dynamicMethod)); if (sourceType == _runtimeMethodInfoType || sourceType == _runtimeConstructorInfoType) - return new ILReader((MethodBase)source); + return new ILReader(source); Debug.WriteLine($"Reading IL from type {sourceType} is currently not supported"); return null; @@ -64,77 +65,110 @@ public static ILReader Create(object source) public static StringBuilder ToILString(this MethodInfo method, StringBuilder s = null) { - if (method is null) throw new ArgumentNullException(nameof(method)); - - s ??= new StringBuilder(); + var il = GetILReaderOrNull(method); + return il != null + ? il.ToILString(s) + : (s ?? new StringBuilder()).AppendLine($"ILReader for {method} is not supported"); + } - var ilReader = Create(method); + public static ILInstruction[] ReadAllInstructions(this MethodBase source) => + GetILReaderOrNull(source)?.ToArray() ?? []; + public static StringBuilder ToILString(this IEnumerable ilInstructions, StringBuilder s = null) + { + s ??= new StringBuilder(); var line = 0; - foreach (var il in ilReader) + foreach (var il in ilInstructions) { try { s = line++ > 0 ? s.AppendLine() : s; - s.Append($"{il.Offset,-4}{il.OpCode}"); - - if (il is InlineFieldInstruction f) + switch (il.OperandType) { - s.Append(' ') - .AppendTypeName(f.Field.FieldType).Append(' ') - .AppendTypeName(f.Field.DeclaringType).Append('.') - .Append(f.Field.Name); - } - else if (il is InlineMethodInstruction m) - { - var sig = m.Method.ToString(); - var paramStart = sig.IndexOf('('); - var paramList = paramStart == -1 ? "()" : sig.Substring(paramStart); - - if (m.Method is MethodInfo met) - { + case OperandType.InlineField: + var f = (InlineFieldInstruction)il; s.Append(' ') - .AppendTypeName(met.ReturnType).Append(' ') - .AppendTypeName(met.DeclaringType).Append('.') - .Append(met.Name) - .Append(paramList); - } - else if (m.Method is ConstructorInfo con) - s.Append(' ').AppendTypeName(con.DeclaringType).Append(paramList); - else - s.Append(' ').AppendTypeName(m.Method.DeclaringType).Append('.').Append(sig); - } - else if (il is InlineTypeInstruction t) - s.Append(' ').AppendTypeName(t.Type); - else if (il is InlineTokInstruction tok) - s.Append(' ').Append(tok.Member.Name); - else if (il is InlineBrTargetInstruction br) - s.Append(' ').Append(br.TargetOffset); - else if (il is InlineSwitchInstruction sw) - { - s.Append(' '); - foreach (var offset in sw.TargetOffsets) - s.Append(offset).Append(','); + .AppendTypeName(f.Field.FieldType).Append(' ') + .AppendTypeName(f.Field.DeclaringType).Append('.') + .Append(f.Field.Name); + break; + case OperandType.InlineMethod: + var m = (InlineMethodInstruction)il; + var sig = m.Method.ToString(); + var paramStart = sig.IndexOf('('); + var paramList = paramStart == -1 ? "()" : sig.Substring(paramStart); + + if (m.Method is MethodInfo met) + { + s.Append(' ') + .AppendTypeName(met.ReturnType).Append(' ') + .AppendTypeName(met.DeclaringType).Append('.') + .Append(met.Name) + .Append(paramList); + } + else if (m.Method is ConstructorInfo con) + s.Append(' ').AppendTypeName(con.DeclaringType).Append(paramList); + else + s.Append(' ').AppendTypeName(m.Method.DeclaringType).Append('.').Append(sig); + break; + case OperandType.InlineType: + var t = (InlineTypeInstruction)il; + s.Append(' ').AppendTypeName(t.Type); + break; + case OperandType.InlineTok: + var tok = (InlineTokInstruction)il; + s.Append(' ').Append(tok.Member.Name); + break; + case OperandType.InlineBrTarget: + var br = (InlineBrTargetInstruction)il; + s.Append(' ').Append(br.TargetOffset); + break; + case OperandType.InlineSwitch: + var sw = (InlineSwitchInstruction)il; + s.Append(' '); + foreach (var offset in sw.TargetOffsets) + s.Append(offset).Append(','); + break; + case OperandType.ShortInlineBrTarget: + var sbr = (ShortInlineBrTargetInstruction)il; + s.Append(' ').Append(sbr.TargetOffset); + break; + case OperandType.InlineString: + var si = (InlineStringInstruction)il; + s.Append(" \"").Append(si.String).Append('"'); + break; + case OperandType.ShortInlineI: + var sii = (ShortInlineIInstruction)il; + s.Append(' ').Append(sii.Byte); + break; + case OperandType.InlineI: + var ii = (InlineIInstruction)il; + s.Append(' ').Append(ii.Int32); + break; + case OperandType.InlineI8: + var i8 = (InlineI8Instruction)il; + s.Append(' ').Append(i8.Int64); + break; + case OperandType.ShortInlineR: + var sir = (ShortInlineRInstruction)il; + s.Append(' ').Append(sir.Single); + break; + case OperandType.InlineR: + var ir = (InlineRInstruction)il; + s.Append(' ').Append(ir.Double); + break; + case OperandType.InlineVar: + var iv = (InlineVarInstruction)il; + s.Append(' ').Append(iv.Ordinal); + break; + case OperandType.ShortInlineVar: + var siv = (ShortInlineVarInstruction)il; + s.Append(' ').Append(siv.Ordinal); + break; + default: + break; } - else if (il is ShortInlineBrTargetInstruction sbr) - s.Append(' ').Append(sbr.TargetOffset); - else if (il is InlineStringInstruction si) - s.Append(" \"").Append(si.String).Append('"'); - else if (il is ShortInlineIInstruction sii) - s.Append(' ').Append(sii.Byte); - else if (il is InlineIInstruction ii) - s.Append(' ').Append(ii.Int32); - else if (il is InlineI8Instruction i8) - s.Append(' ').Append(i8.Int64); - else if (il is ShortInlineRInstruction sir) - s.Append(' ').Append(sir.Single); - else if (il is InlineRInstruction ir) - s.Append(' ').Append(ir.Double); - else if (il is InlineVarInstruction iv) - s.Append(' ').Append(iv.Ordinal); - else if (il is ShortInlineVarInstruction siv) - s.Append(' ').Append(siv.Ordinal); } catch (Exception ex) { @@ -171,18 +205,16 @@ static ILReader() private readonly ITokenResolver _resolver; private readonly byte[] _byteArray; - public ILReader(MethodBase method) - : this(new MethodBaseILProvider(method), new ModuleScopeTokenResolver(method)) - { - } - public ILReader(IILProvider ilProvider, ITokenResolver tokenResolver) { _resolver = tokenResolver; _byteArray = ilProvider?.GetByteArray() ?? throw new ArgumentNullException(nameof(ilProvider)); } - // todo: @perf implement optimized IEnumerator which does not need to allocate ILInstruction objects + public ILReader(MethodBase method) + : this(new MethodBaseILProvider(method), new ModuleScopeTokenResolver(method)) { } + + // todo: @perf implement optimized IEnumerator which does not need to allocate ILInstruction objects, let's try a data-oriented modeling! public IEnumerator GetEnumerator() { var position = 0; @@ -196,67 +228,49 @@ private ILInstruction Next(ref int position) { var offset = position; - // read first 1 or 2 bytes as opCode + // Read first 1 or 2 bytes as opCode var code = ReadByte(ref position); var opCode = code != 0xFE ? _oneByteOpCodes[code] : _twoByteOpCodes[ReadByte(ref position)]; - switch (opCode.OperandType) + return opCode.OperandType switch { - case OperandType.InlineNone: - return new InlineNoneInstruction(offset, opCode); + OperandType.InlineNone => new InlineNoneInstruction(offset, opCode), // 8-bit integer branch target - case OperandType.ShortInlineBrTarget: - return new ShortInlineBrTargetInstruction(offset, opCode, ReadSByte(ref position)); + OperandType.ShortInlineBrTarget => new ShortInlineBrTargetInstruction(offset, opCode, ReadSByte(ref position)), // 32-bit integer branch target - case OperandType.InlineBrTarget: - return new InlineBrTargetInstruction(offset, opCode, ReadInt32(ref position)); + OperandType.InlineBrTarget => new InlineBrTargetInstruction(offset, opCode, ReadInt32(ref position)), // 8-bit integer: 001F ldc.i4.s, FE12 unaligned. - case OperandType.ShortInlineI: - return new ShortInlineIInstruction(offset, opCode, ReadByte(ref position)); + OperandType.ShortInlineI => new ShortInlineIInstruction(offset, opCode, ReadByte(ref position)), // 32-bit integer - case OperandType.InlineI: - return new InlineIInstruction(offset, opCode, ReadInt32(ref position)); + OperandType.InlineI => new InlineIInstruction(offset, opCode, ReadInt32(ref position)), // 64-bit integer - case OperandType.InlineI8: - return new InlineI8Instruction(offset, opCode, ReadInt64(ref position)); + OperandType.InlineI8 => new InlineI8Instruction(offset, opCode, ReadInt64(ref position)), // 32-bit IEEE floating point number - case OperandType.ShortInlineR: - return new ShortInlineRInstruction(offset, opCode, ReadSingle(ref position)); + OperandType.ShortInlineR => new ShortInlineRInstruction(offset, opCode, ReadSingle(ref position)), // 64-bit IEEE floating point number - case OperandType.InlineR: - return new InlineRInstruction(offset, opCode, ReadDouble(ref position)); + OperandType.InlineR => new InlineRInstruction(offset, opCode, ReadDouble(ref position)), // 8-bit integer containing the ordinal of a local variable or an argument - case OperandType.ShortInlineVar: - return new ShortInlineVarInstruction(offset, opCode, ReadByte(ref position)); + OperandType.ShortInlineVar => new ShortInlineVarInstruction(offset, opCode, ReadByte(ref position)), // 16-bit integer containing the ordinal of a local variable or an argument - case OperandType.InlineVar: - return new InlineVarInstruction(offset, opCode, ReadUInt16(ref position)); + OperandType.InlineVar => new InlineVarInstruction(offset, opCode, ReadUInt16(ref position)), // 32-bit metadata string token - case OperandType.InlineString: - return new InlineStringInstruction(offset, opCode, ReadInt32(ref position), _resolver); + OperandType.InlineString => new InlineStringInstruction(offset, opCode, ReadInt32(ref position), _resolver), // 32-bit metadata signature token - case OperandType.InlineSig: - return new InlineSigInstruction(offset, opCode, ReadInt32(ref position), _resolver); + OperandType.InlineSig => new InlineSigInstruction(offset, opCode, ReadInt32(ref position), _resolver), // 32-bit metadata token - case OperandType.InlineMethod: - return new InlineMethodInstruction(offset, opCode, ReadInt32(ref position), _resolver); + OperandType.InlineMethod => new InlineMethodInstruction(offset, opCode, ReadInt32(ref position), _resolver), // 32-bit metadata token - case OperandType.InlineField: - return new InlineFieldInstruction(_resolver, offset, opCode, ReadInt32(ref position)); + OperandType.InlineField => new InlineFieldInstruction(_resolver, offset, opCode, ReadInt32(ref position)), // 32-bit metadata token - case OperandType.InlineType: - return new InlineTypeInstruction(offset, opCode, ReadInt32(ref position), _resolver); + OperandType.InlineType => new InlineTypeInstruction(offset, opCode, ReadInt32(ref position), _resolver), // FieldRef, MethodRef, or TypeRef token - case OperandType.InlineTok: - return new InlineTokInstruction(offset, opCode, ReadInt32(ref position), _resolver); + OperandType.InlineTok => new InlineTokInstruction(offset, opCode, ReadInt32(ref position), _resolver), // 32-bit integer argument to a switch instruction - case OperandType.InlineSwitch: - return new InlineSwitchInstruction(offset, opCode, ReadDeltas(ref position)); - default: - throw new NotSupportedException($"Unsupported operand type: {opCode.OperandType}"); - } + OperandType.InlineSwitch => new InlineSwitchInstruction(offset, opCode, ReadDeltas(ref position)), + _ => throw new NotSupportedException($"Unsupported operand type: {opCode.OperandType}"), + }; } private int[] ReadDeltas(ref int position) @@ -268,14 +282,6 @@ private int[] ReadDeltas(ref int position) return deltas; } - public void Accept(ILInstructionVisitor visitor) - { - if (visitor == null) - throw new ArgumentNullException(nameof(visitor)); - foreach (var instruction in this) - instruction.Accept(visitor); - } - private byte ReadByte(ref int position) => _byteArray[position++]; private sbyte ReadSByte(ref int position) => (sbyte)ReadByte(ref position); @@ -316,9 +322,9 @@ private double ReadDouble(ref int position) } } - public abstract class ILInstruction { + public abstract OperandType OperandType { get; } public readonly int Offset; public readonly OpCode OpCode; internal ILInstruction(int offset, OpCode opCode) @@ -326,44 +332,38 @@ internal ILInstruction(int offset, OpCode opCode) Offset = offset; OpCode = opCode; } - - public abstract void Accept(ILInstructionVisitor visitor); } -public class InlineNoneInstruction : ILInstruction +public sealed class InlineNoneInstruction : ILInstruction { - internal InlineNoneInstruction(int offset, OpCode opCode) - : base(offset, opCode) - { - } + public override OperandType OperandType => OperandType.InlineNone; - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineNoneInstruction(this); + internal InlineNoneInstruction(int offset, OpCode opCode) + : base(offset, opCode) { } } -public class InlineBrTargetInstruction : ILInstruction +public sealed class InlineBrTargetInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineBrTarget; public int Delta { get; } public int TargetOffset => Offset + Delta + 1 + 4; internal InlineBrTargetInstruction(int offset, OpCode opCode, int delta) : base(offset, opCode) => Delta = delta; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineBrTargetInstruction(this); } -public class ShortInlineBrTargetInstruction : ILInstruction +public sealed class ShortInlineBrTargetInstruction : ILInstruction { + public override OperandType OperandType => OperandType.ShortInlineBrTarget; public sbyte Delta { get; } public int TargetOffset => Offset + Delta + 1 + 1; - internal ShortInlineBrTargetInstruction(int offset, OpCode opCode, sbyte delta) : base(offset, opCode) => Delta = delta; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitShortInlineBrTargetInstruction(this); } -public class InlineSwitchInstruction : ILInstruction +public sealed class InlineSwitchInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineSwitch; private readonly int[] _deltas; private int[] _targetOffsets; @@ -387,80 +387,70 @@ public int[] TargetOffsets return _targetOffsets; } } - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineSwitchInstruction(this); } -public class InlineIInstruction : ILInstruction +public sealed class InlineIInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineI; public int Int32 { get; } - internal InlineIInstruction(int offset, OpCode opCode, int value) : base(offset, opCode) => Int32 = value; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineIInstruction(this); } -public class InlineI8Instruction : ILInstruction +public sealed class InlineI8Instruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineI8; public long Int64 { get; } internal InlineI8Instruction(int offset, OpCode opCode, long value) : base(offset, opCode) => Int64 = value; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineI8Instruction(this); } -public class ShortInlineIInstruction : ILInstruction +public sealed class ShortInlineIInstruction : ILInstruction { + public override OperandType OperandType => OperandType.ShortInlineI; public byte Byte { get; } internal ShortInlineIInstruction(int offset, OpCode opCode, byte value) : base(offset, opCode) => Byte = value; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitShortInlineIInstruction(this); } public class InlineRInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineR; public double Double { get; } internal InlineRInstruction(int offset, OpCode opCode, double value) : base(offset, opCode) => Double = value; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineRInstruction(this); } -public class ShortInlineRInstruction : ILInstruction +public sealed class ShortInlineRInstruction : ILInstruction { + public override OperandType OperandType => OperandType.ShortInlineR; public float Single { get; } internal ShortInlineRInstruction(int offset, OpCode opCode, float value) : base(offset, opCode) => Single = value; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitShortInlineRInstruction(this); } -public class InlineFieldInstruction : ILInstruction +public sealed class InlineFieldInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineField; private readonly ITokenResolver _resolver; public int Token { get; } - private FieldInfo _field; public FieldInfo Field => _field ??= _resolver.AsField(Token); - internal InlineFieldInstruction(ITokenResolver resolver, int offset, OpCode opCode, int token) : base(offset, opCode) { _resolver = resolver; Token = token; } - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineFieldInstruction(this); } -public class InlineMethodInstruction : ILInstruction +public sealed class InlineMethodInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineMethod; private readonly ITokenResolver _resolver; public int Token { get; } private MethodBase _method; @@ -472,12 +462,11 @@ internal InlineMethodInstruction(int offset, OpCode opCode, int token, ITokenRes _resolver = resolver; Token = token; } - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineMethodInstruction(this); } -public class InlineTypeInstruction : ILInstruction +public sealed class InlineTypeInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineType; private readonly ITokenResolver _resolver; public int Token { get; } private Type _type; @@ -489,12 +478,11 @@ internal InlineTypeInstruction(int offset, OpCode opCode, int token, ITokenResol _resolver = resolver; Token = token; } - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineTypeInstruction(this); } -public class InlineSigInstruction : ILInstruction +public sealed class InlineSigInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineSig; private readonly ITokenResolver _resolver; public int Token { get; } private byte[] _signature; @@ -506,12 +494,11 @@ internal InlineSigInstruction(int offset, OpCode opCode, int token, ITokenResolv _resolver = resolver; Token = token; } - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineSigInstruction(this); } -public class InlineTokInstruction : ILInstruction +public sealed class InlineTokInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineTok; private readonly ITokenResolver _resolver; public int Token { get; } private MemberInfo _member; @@ -523,12 +510,11 @@ internal InlineTokInstruction(int offset, OpCode opCode, int token, ITokenResolv _resolver = resolver; Token = token; } - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineTokInstruction(this); } -public class InlineStringInstruction : ILInstruction +public sealed class InlineStringInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineString; private readonly ITokenResolver _resolver; public int Token { get; } private string _string; @@ -540,28 +526,23 @@ internal InlineStringInstruction(int offset, OpCode opCode, int token, ITokenRes _resolver = resolver; Token = token; } - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineStringInstruction(this); } -public class InlineVarInstruction : ILInstruction +public sealed class InlineVarInstruction : ILInstruction { + public override OperandType OperandType => OperandType.InlineVar; public ushort Ordinal { get; } - internal InlineVarInstruction(int offset, OpCode opCode, ushort ordinal) : base(offset, opCode) => Ordinal = ordinal; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitInlineVarInstruction(this); } -public class ShortInlineVarInstruction : ILInstruction +public sealed class ShortInlineVarInstruction : ILInstruction { + public override OperandType OperandType => OperandType.ShortInlineVar; public byte Ordinal { get; } internal ShortInlineVarInstruction(int offset, OpCode opCode, byte ordinal) : base(offset, opCode) => Ordinal = ordinal; - - public override void Accept(ILInstructionVisitor visitor) => visitor.VisitShortInlineVarInstruction(this); } public interface IILProvider @@ -584,8 +565,7 @@ public MethodBaseILProvider(MethodBase method) throw new ArgumentNullException(nameof(method)); var methodType = method.GetType(); - - if (methodType != _runtimeMethodInfoType && methodType != _runtimeConstructorInfoType) + if (methodType != _runtimeMethodInfoType & methodType != _runtimeConstructorInfoType) throw new ArgumentException("Must have type RuntimeMethodInfo or RuntimeConstructorInfo.", nameof(method)); _method = method; @@ -604,7 +584,7 @@ public byte[] GetByteArray() [UnconditionalSuppressMessage("Trimming", "IL2069:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' requirements.", Justification = "Uses reflection on internal types and is not trim-compatible.")] public class DynamicMethodILProvider : IILProvider { -#if !NET8_0_OR_GREATER +#if !NET6_0_OR_GREATER private static readonly Type _runtimeILGeneratorType = typeof(ILGenerator); #else private static readonly Type _runtimeILGeneratorType = Type.GetType("System.Reflection.Emit.RuntimeILGenerator"); @@ -644,7 +624,7 @@ public byte[] GetByteArray() } } -public interface IFormatProvider +public interface IFormatter { string Int32ToHex(int int32); string Int16ToHex(int int16); @@ -656,19 +636,15 @@ public interface IFormatProvider string SigByteArrayToString(byte[] sig); } -public class DefaultFormatProvider : IFormatProvider +public struct DefaultFormatter : IFormatter { - private DefaultFormatProvider() { } + public string Int32ToHex(int int32) => int32.ToString("X8"); + public string Int16ToHex(int int16) => int16.ToString("X4"); + public string Int8ToHex(int int8) => int8.ToString("X2"); + public string Argument(int ordinal) => $"V_{ordinal}"; + public string Label(int offset) => $"IL_{offset:x4}"; - public static readonly DefaultFormatProvider Instance = new(); - - public virtual string Int32ToHex(int int32) => int32.ToString("X8"); - public virtual string Int16ToHex(int int16) => int16.ToString("X4"); - public virtual string Int8ToHex(int int8) => int8.ToString("X2"); - public virtual string Argument(int ordinal) => $"V_{ordinal}"; - public virtual string Label(int offset) => $"IL_{offset:x4}"; - - public virtual string MultipleLabels(int[] offsets) + public string MultipleLabels(int[] offsets) { var sb = new StringBuilder(); var length = offsets.Length; @@ -681,7 +657,7 @@ public virtual string MultipleLabels(int[] offsets) return sb.ToString(); } - public virtual string EscapedString(string str) + public string EscapedString(string str) { var length = str.Length; var sb = new StringBuilder(length * 2); @@ -706,11 +682,10 @@ public virtual string EscapedString(string str) sb.Append(ch); } sb.Append('"'); - return sb.ToString(); } - public virtual string SigByteArrayToString(byte[] sig) + public string SigByteArrayToString(byte[] sig) { var sb = new StringBuilder(); var length = sig.Length; @@ -724,273 +699,195 @@ public virtual string SigByteArrayToString(byte[] sig) } } -public interface IILStringCollector +// todo: @feat waiting for C# support of the default/optional generic parameters, e.g. for `ReadableILStringProcessor` +public sealed class ReadableILStringProcessor where TFormatter : struct, IFormatter { - void Process(ILInstruction ilInstruction, string operandString); -} + private static readonly TFormatter _formatProvider = default; + readonly TextWriter _writer; -public class ReadableILStringToTextWriter : IILStringCollector -{ - protected TextWriter _writer; - - public ReadableILStringToTextWriter(TextWriter writer) => _writer = writer; - - public virtual void Process(ILInstruction ilInstruction, string operandString) - { - _writer.WriteLine("IL_{0:x4}: {1,-10} {2}", - ilInstruction.Offset, - ilInstruction.OpCode.Name, - operandString); - } -} + public ReadableILStringProcessor(TextWriter writer) => _writer = writer; -public class RawILStringToTextWriter : ReadableILStringToTextWriter -{ - public RawILStringToTextWriter(TextWriter writer) : base(writer) { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Write(ILInstruction i, string operandString) => + _writer.WriteLine("IL_{0:x4}: {1,-10} {2}", i.Offset, i.OpCode.Name, operandString); - public override void Process(ILInstruction ilInstruction, string operandString) + public void ProcessInstruction(ILInstruction i) { - _writer.WriteLine("IL_{0:x4}: {1,-4:x2}| {2, -8}", - ilInstruction.Offset, - ilInstruction.OpCode.Value, - operandString); - } -} - -public abstract class ILInstructionVisitor -{ - public virtual void VisitInlineBrTargetInstruction(InlineBrTargetInstruction inlineBrTargetInstruction) { } - - public virtual void VisitInlineFieldInstruction(InlineFieldInstruction inlineFieldInstruction) { } - - public virtual void VisitInlineIInstruction(InlineIInstruction inlineIInstruction) { } - - public virtual void VisitInlineI8Instruction(InlineI8Instruction inlineI8Instruction) { } - - public virtual void VisitInlineMethodInstruction(InlineMethodInstruction inlineMethodInstruction) { } - - public virtual void VisitInlineNoneInstruction(InlineNoneInstruction inlineNoneInstruction) { } - - public virtual void VisitInlineRInstruction(InlineRInstruction inlineRInstruction) { } - - public virtual void VisitInlineSigInstruction(InlineSigInstruction inlineSigInstruction) { } - - public virtual void VisitInlineStringInstruction(InlineStringInstruction inlineStringInstruction) { } - - public virtual void VisitInlineSwitchInstruction(InlineSwitchInstruction inlineSwitchInstruction) { } - - public virtual void VisitInlineTokInstruction(InlineTokInstruction inlineTokInstruction) { } - - public virtual void VisitInlineTypeInstruction(InlineTypeInstruction inlineTypeInstruction) { } - - public virtual void VisitInlineVarInstruction(InlineVarInstruction inlineVarInstruction) { } - - public virtual void VisitShortInlineBrTargetInstruction(ShortInlineBrTargetInstruction shortInlineBrTargetInstruction) { } - - public virtual void VisitShortInlineIInstruction(ShortInlineIInstruction shortInlineIInstruction) { } - - public virtual void VisitShortInlineRInstruction(ShortInlineRInstruction shortInlineRInstruction) { } - - public virtual void VisitShortInlineVarInstruction(ShortInlineVarInstruction shortInlineVarInstruction) { } -} - -public class ReadableILStringVisitor : ILInstructionVisitor -{ - protected IFormatProvider _formatProvider; - protected IILStringCollector _collector; - - public ReadableILStringVisitor(IILStringCollector collector) - : this(collector, DefaultFormatProvider.Instance) { } - - public ReadableILStringVisitor(IILStringCollector collector, IFormatProvider formatProvider) - { - _formatProvider = formatProvider; - _collector = collector; - } - - public override void VisitInlineBrTargetInstruction(InlineBrTargetInstruction inlineBrTargetInstruction) - { - _collector.Process(inlineBrTargetInstruction, _formatProvider.Label(inlineBrTargetInstruction.TargetOffset)); - } - - public override void VisitInlineFieldInstruction(InlineFieldInstruction inlineFieldInstruction) - { - string field; - try - { - field = inlineFieldInstruction.Field + "/" + inlineFieldInstruction.Field.DeclaringType; - } - catch (Exception ex) - { - field = "!" + ex.Message + "!"; - } - _collector.Process(inlineFieldInstruction, field); - } - - public override void VisitInlineIInstruction(InlineIInstruction inlineIInstruction) - { - _collector.Process(inlineIInstruction, inlineIInstruction.Int32.ToString()); - } - - public override void VisitInlineI8Instruction(InlineI8Instruction inlineI8Instruction) - { - _collector.Process(inlineI8Instruction, inlineI8Instruction.Int64.ToString()); - } - - public override void VisitInlineMethodInstruction(InlineMethodInstruction inlineMethodInstruction) - { - string method; - try + switch (i.OperandType) { - method = inlineMethodInstruction.Method + "/" + inlineMethodInstruction.Method.DeclaringType; - } - catch (Exception ex) - { - method = "!" + ex.Message + "!"; - } - _collector.Process(inlineMethodInstruction, method); - } - - public override void VisitInlineNoneInstruction(InlineNoneInstruction inlineNoneInstruction) - { - _collector.Process(inlineNoneInstruction, string.Empty); - } - - public override void VisitInlineRInstruction(InlineRInstruction inlineRInstruction) - { - _collector.Process(inlineRInstruction, inlineRInstruction.Double.ToString()); - } - - public override void VisitInlineSigInstruction(InlineSigInstruction inlineSigInstruction) - { - _collector.Process(inlineSigInstruction, _formatProvider.SigByteArrayToString(inlineSigInstruction.Signature)); - } - - public override void VisitInlineStringInstruction(InlineStringInstruction inlineStringInstruction) - { - _collector.Process(inlineStringInstruction, _formatProvider.EscapedString(inlineStringInstruction.String)); - } - - public override void VisitInlineSwitchInstruction(InlineSwitchInstruction inlineSwitchInstruction) - { - _collector.Process(inlineSwitchInstruction, _formatProvider.MultipleLabels(inlineSwitchInstruction.TargetOffsets)); - } - - public override void VisitInlineTokInstruction(InlineTokInstruction inlineTokInstruction) - { - string member; - try - { - member = inlineTokInstruction.Member + "/" + inlineTokInstruction.Member.DeclaringType; - } - catch (Exception ex) - { - member = "!" + ex.Message + "!"; - } - _collector.Process(inlineTokInstruction, member); - } - - public override void VisitInlineTypeInstruction(InlineTypeInstruction inlineTypeInstruction) - { - string type; - try - { - type = inlineTypeInstruction.Type.ToString(); - } - catch (Exception ex) - { - type = "!" + ex.Message + "!"; + case OperandType.InlineBrTarget: + Write(i, _formatProvider.Label(((InlineBrTargetInstruction)i).TargetOffset)); + break; + case OperandType.InlineField: + var inlineField = (InlineFieldInstruction)i; + string field; + try + { + field = inlineField.Field + "/" + inlineField.Field.DeclaringType; + } + catch (Exception ex) + { + field = "!" + ex.Message + "!"; + } + Write(i, field); + break; + case OperandType.InlineI: + Write(i, ((InlineIInstruction)i).Int32.ToString()); + break; + case OperandType.InlineI8: + Write(i, ((InlineI8Instruction)i).Int64.ToString()); + break; + case OperandType.InlineMethod: + var inlineMethod = (InlineMethodInstruction)i; + string method; + try + { + method = inlineMethod.Method + "/" + inlineMethod.Method.DeclaringType; + } + catch (Exception ex) + { + method = "!" + ex.Message + "!"; + } + Write(i, method); + break; + case OperandType.InlineNone: + Write(i, string.Empty); + break; + case OperandType.InlineR: + Write(i, ((InlineRInstruction)i).Double.ToString()); + break; + case OperandType.InlineSig: + Write(i, _formatProvider.SigByteArrayToString(((InlineSigInstruction)i).Signature)); + break; + case OperandType.InlineString: + Write(i, _formatProvider.EscapedString(((InlineStringInstruction)i).String)); + break; + case OperandType.InlineSwitch: + var inlineSwitch = (InlineSwitchInstruction)i; + Write(i, _formatProvider.MultipleLabels(inlineSwitch.TargetOffsets)); + break; + case OperandType.InlineTok: + var inlineTok = (InlineTokInstruction)i; + string member; + try + { + member = inlineTok.Member + "/" + inlineTok.Member.DeclaringType; + } + catch (Exception ex) + { + member = "!" + ex.Message + "!"; + } + Write(i, member); + break; + case OperandType.InlineType: + var inlineType = (InlineTypeInstruction)i; + string type; + try + { + type = inlineType.Type.ToString(); + } + catch (Exception ex) + { + type = "!" + ex.Message + "!"; + } + Write(i, type); + break; + case OperandType.InlineVar: + var inlineVar = (InlineVarInstruction)i; + Write(i, _formatProvider.Argument(inlineVar.Ordinal)); + break; + case OperandType.ShortInlineBrTarget: + var shortInlineBrTarget = (ShortInlineBrTargetInstruction)i; + Write(i, _formatProvider.Label(shortInlineBrTarget.TargetOffset)); + break; + case OperandType.ShortInlineI: + Write(i, ((ShortInlineIInstruction)i).Byte.ToString()); + break; + case OperandType.ShortInlineR: + Write(i, ((ShortInlineRInstruction)i).Single.ToString()); + break; + case OperandType.ShortInlineVar: + var shortInlineVar = (ShortInlineVarInstruction)i; + Write(i, _formatProvider.Argument(shortInlineVar.Ordinal)); + break; + default: + Debug.Fail("all cases are covered above, so it is not expected to reach here"); + break; } - _collector.Process(inlineTypeInstruction, type); - } - - public override void VisitInlineVarInstruction(InlineVarInstruction inlineVarInstruction) - { - _collector.Process(inlineVarInstruction, _formatProvider.Argument(inlineVarInstruction.Ordinal)); - } - - public override void VisitShortInlineBrTargetInstruction(ShortInlineBrTargetInstruction shortInlineBrTargetInstruction) - { - _collector.Process(shortInlineBrTargetInstruction, _formatProvider.Label(shortInlineBrTargetInstruction.TargetOffset)); - } - - public override void VisitShortInlineIInstruction(ShortInlineIInstruction shortInlineIInstruction) - { - _collector.Process(shortInlineIInstruction, shortInlineIInstruction.Byte.ToString()); - } - - public override void VisitShortInlineRInstruction(ShortInlineRInstruction shortInlineRInstruction) - { - _collector.Process(shortInlineRInstruction, shortInlineRInstruction.Single.ToString()); - } - - public override void VisitShortInlineVarInstruction(ShortInlineVarInstruction shortInlineVarInstruction) - { - _collector.Process(shortInlineVarInstruction, _formatProvider.Argument(shortInlineVarInstruction.Ordinal)); } } -public class RawILStringVisitor : ReadableILStringVisitor +public sealed class RawILStringProcessor where TFormatter : struct, IFormatter { - public RawILStringVisitor(IILStringCollector collector) - : this(collector, DefaultFormatProvider.Instance) { } - - public RawILStringVisitor(IILStringCollector collector, IFormatProvider formatProvider) - : base(collector, formatProvider) { } - - public override void VisitInlineBrTargetInstruction(InlineBrTargetInstruction inlineBrTargetInstruction) - { - _collector.Process(inlineBrTargetInstruction, _formatProvider.Int32ToHex(inlineBrTargetInstruction.Delta)); - } + static readonly TFormatter _formatter = default; + readonly ReadableILStringProcessor _fallbackProcessor; + readonly TextWriter _writer; - public override void VisitInlineFieldInstruction(InlineFieldInstruction inlineFieldInstruction) + public RawILStringProcessor(TextWriter writer) { - _collector.Process(inlineFieldInstruction, _formatProvider.Int32ToHex(inlineFieldInstruction.Token)); + _fallbackProcessor = new ReadableILStringProcessor(writer); + _writer = writer; } - public override void VisitInlineMethodInstruction(InlineMethodInstruction inlineMethodInstruction) - { - _collector.Process(inlineMethodInstruction, _formatProvider.Int32ToHex(inlineMethodInstruction.Token)); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Write(ILInstruction i, string operandString) => + _writer.WriteLine("IL_{0:x4}: {1,-4:x2}| {2, -8}", i.Offset, i.OpCode.Value, operandString); - public override void VisitInlineSigInstruction(InlineSigInstruction inlineSigInstruction) + public void ProcessInstruction(ILInstruction i) { - _collector.Process(inlineSigInstruction, _formatProvider.Int32ToHex(inlineSigInstruction.Token)); - } - - public override void VisitInlineStringInstruction(InlineStringInstruction inlineStringInstruction) - { - _collector.Process(inlineStringInstruction, _formatProvider.Int32ToHex(inlineStringInstruction.Token)); - } - - public override void VisitInlineSwitchInstruction(InlineSwitchInstruction inlineSwitchInstruction) - { - _collector.Process(inlineSwitchInstruction, "..."); - } - - public override void VisitInlineTokInstruction(InlineTokInstruction inlineTokInstruction) - { - _collector.Process(inlineTokInstruction, _formatProvider.Int32ToHex(inlineTokInstruction.Token)); - } - - public override void VisitInlineTypeInstruction(InlineTypeInstruction inlineTypeInstruction) - { - _collector.Process(inlineTypeInstruction, _formatProvider.Int32ToHex(inlineTypeInstruction.Token)); - } - - public override void VisitInlineVarInstruction(InlineVarInstruction inlineVarInstruction) - { - _collector.Process(inlineVarInstruction, _formatProvider.Int16ToHex(inlineVarInstruction.Ordinal)); - } - - public override void VisitShortInlineBrTargetInstruction(ShortInlineBrTargetInstruction shortInlineBrTargetInstruction) - { - _collector.Process(shortInlineBrTargetInstruction, _formatProvider.Int8ToHex(shortInlineBrTargetInstruction.Delta)); - } - - public override void VisitShortInlineVarInstruction(ShortInlineVarInstruction shortInlineVarInstruction) - { - _collector.Process(shortInlineVarInstruction, _formatProvider.Int8ToHex(shortInlineVarInstruction.Ordinal)); + switch (i.OperandType) + { + case OperandType.InlineBrTarget: + Write(i, _formatter.Int32ToHex(((InlineBrTargetInstruction)i).TargetOffset)); + break; + case OperandType.InlineField: + Write(i, _formatter.Int32ToHex(((InlineFieldInstruction)i).Token)); + break; + case OperandType.InlineI: + case OperandType.InlineI8: + _fallbackProcessor.ProcessInstruction(i); + break; + case OperandType.InlineMethod: + Write(i, _formatter.Int32ToHex(((InlineMethodInstruction)i).Token)); + break; + case OperandType.InlineNone: + case OperandType.InlineR: + _fallbackProcessor.ProcessInstruction(i); + break; + case OperandType.InlineSig: + Write(i, _formatter.Int32ToHex(((InlineSigInstruction)i).Token)); + break; + case OperandType.InlineString: + Write(i, _formatter.Int32ToHex(((InlineStringInstruction)i).Token)); + break; + case OperandType.InlineSwitch: + Write(i, "..."); + break; + case OperandType.InlineTok: + Write(i, _formatter.Int32ToHex(((InlineTokInstruction)i).Token)); + break; + case OperandType.InlineType: + Write(i, _formatter.Int32ToHex(((InlineTypeInstruction)i).Token)); + break; + case OperandType.InlineVar: + Write(i, _formatter.Int16ToHex(((InlineVarInstruction)i).Ordinal)); + break; + case OperandType.ShortInlineBrTarget: + Write(i, _formatter.Int8ToHex(((ShortInlineBrTargetInstruction)i).Delta)); + break; + case OperandType.ShortInlineI: + Write(i, _formatter.Int8ToHex(((ShortInlineIInstruction)i).Byte)); + break; + case OperandType.ShortInlineR: + _fallbackProcessor.ProcessInstruction(i); + break; + case OperandType.ShortInlineVar: + Write(i, _formatter.Int8ToHex(((ShortInlineVarInstruction)i).Ordinal)); + break; + default: + Debug.Fail("all cases are covered above, so it is not expected to reach here"); + break; + } } } @@ -1005,7 +902,7 @@ public interface ITokenResolver } [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Uses reflection on internal types and is not trim-compatible.")] -public class ModuleScopeTokenResolver : ITokenResolver +public sealed class ModuleScopeTokenResolver : ITokenResolver { private readonly Module _module; private readonly Type[] _methodContext; @@ -1028,10 +925,12 @@ public ModuleScopeTokenResolver(MethodBase method) [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "Uses reflection on internal types and is not trim-compatible.")] [UnconditionalSuppressMessage("Trimming", "IL2080:'this' argument does not satisfy 'DynamicallyAccessedMemberTypes' in call to 'target method'. The field/type does not have matching annotations.", Justification = "Uses reflection on internal types and is not trim-compatible.")] -internal class DynamicScopeTokenResolver : ITokenResolver +[UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "Reflection on internal types, not trim-compatible.")] +internal sealed class DynamicScopeTokenResolver : ITokenResolver { - private static readonly PropertyInfo _indexer; - private static readonly FieldInfo _scopeFi; + static readonly FieldInfo ILGeneratorField; + private static readonly PropertyInfo _ilGeneratorScopeIndexer; + private static readonly FieldInfo _ilGeneratorScope; private static readonly Type _genMethodInfoType; private static readonly FieldInfo _genmethFi1; @@ -1044,37 +943,45 @@ internal class DynamicScopeTokenResolver : ITokenResolver private static readonly FieldInfo _genfieldFi1; private static readonly FieldInfo _genfieldFi2; + public static bool IsSupported => ILGeneratorField != null; + static DynamicScopeTokenResolver() { - const BindingFlags memberFlags = BindingFlags.NonPublic | BindingFlags.Instance; + const BindingFlags instanceNonPublic = BindingFlags.NonPublic | BindingFlags.Instance; + + ILGeneratorField = typeof(DynamicMethod).GetField("_ilGenerator", instanceNonPublic); + + // Stop at this moment if the DynamicILGenerator type is not found. + if (ILGeneratorField == null) + return; - var dynamicScopeType = Type.GetType("System.Reflection.Emit.DynamicScope") ?? throw new InvalidOperationException("DynamicScope type is not found"); - _indexer = dynamicScopeType.GetProperty("Item", memberFlags) ?? throw new InvalidOperationException("DynamicScope.Item property is not found"); + var dynamicIlGeneratorType = ILGeneratorField.FieldType; + _ilGeneratorScope = dynamicIlGeneratorType.GetField("m_scope", instanceNonPublic) + ?? throw new InvalidOperationException("DynamicILGenerator._scope field is not found"); - var dynamicIlGeneratorType = Type.GetType("System.Reflection.Emit.DynamicILGenerator") ?? throw new InvalidOperationException("DynamicILGenerator type is not found"); - _scopeFi = dynamicIlGeneratorType.GetField("m_scope", memberFlags) ?? throw new InvalidOperationException("DynamicILGenerator._scope field is not found"); + var dynamicScopeType = _ilGeneratorScope.FieldType; + _ilGeneratorScopeIndexer = dynamicScopeType.GetProperty("Item", instanceNonPublic) + ?? throw new InvalidOperationException("DynamicScope.Item property is not found"); _varArgMethodType = Type.GetType("System.Reflection.Emit.VarArgMethod"); - _varargFi1 = _varArgMethodType.GetField("m_method", memberFlags); + _varargFi1 = _varArgMethodType.GetField("m_method", instanceNonPublic); _genMethodInfoType = Type.GetType("System.Reflection.Emit.GenericMethodInfo"); - _genmethFi1 = _genMethodInfoType.GetField("m_methodHandle", memberFlags); - _genmethFi2 = _genMethodInfoType.GetField("m_context", memberFlags); + _genmethFi1 = _genMethodInfoType.GetField("m_methodHandle", instanceNonPublic); + _genmethFi2 = _genMethodInfoType.GetField("m_context", instanceNonPublic); _genFieldInfoType = Type.GetType("System.Reflection.Emit.GenericFieldInfo", throwOnError: false); - _genfieldFi1 = _genFieldInfoType?.GetField("m_fieldHandle", memberFlags); - _genfieldFi2 = _genFieldInfoType?.GetField("m_context", memberFlags); + _genfieldFi1 = _genFieldInfoType?.GetField("m_fieldHandle", instanceNonPublic); + _genfieldFi2 = _genFieldInfoType?.GetField("m_context", instanceNonPublic); } private readonly object _scope; - private object this[int token] => _indexer.GetValue(_scope, [token]); + private object this[int token] => _ilGeneratorScopeIndexer.GetValue(_scope, [token]); - public DynamicScopeTokenResolver(DynamicMethod dm) - { - _scope = _scopeFi.GetValue(dm.GetILGenerator()); - } + public DynamicScopeTokenResolver(DynamicMethod dm) => + _scope = _ilGeneratorScope.GetValue(dm.GetILGenerator()); public string AsString(int token) => this[token] as string; @@ -1084,11 +991,9 @@ public FieldInfo AsField(int token) return FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)this[token]); if (this[token].GetType() == _genFieldInfoType) - { return FieldInfo.GetFieldFromHandle( (RuntimeFieldHandle)_genfieldFi1.GetValue(this[token]), (RuntimeTypeHandle)_genfieldFi2.GetValue(this[token])); - } Debug.Assert(false, $"unexpected type: {this[token].GetType()}"); return null; diff --git a/src/FastExpressionCompiler/ImTools.cs b/src/FastExpressionCompiler/ImTools.cs index 04304990..8096c185 100644 --- a/src/FastExpressionCompiler/ImTools.cs +++ b/src/FastExpressionCompiler/ImTools.cs @@ -175,12 +175,15 @@ public static int TryGetIndex(this ref SmallList source, TIte public static int GetIndexOrAdd(this ref SmallList source, in TItem item, TEq eq = default) where TEq : struct, IEq { - var i = TryGetIndex(source.Items, in item, 0, source.Count, eq); - if (i != -1) - return i; - i = source.Count; + var count = source.Count; + if (count != 0) + { + var index = TryGetIndex(source.Items, in item, 0, count, eq); + if (index != -1) + return index; + } source.Add() = item; - return i; + return count; } /// Returns surely present item ref by its index diff --git a/src/FastExpressionCompiler/TestTools.cs b/src/FastExpressionCompiler/TestTools.cs index 9fdb9cbb..b59dd047 100644 --- a/src/FastExpressionCompiler/TestTools.cs +++ b/src/FastExpressionCompiler/TestTools.cs @@ -12,10 +12,12 @@ #if LIGHT_EXPRESSION namespace FastExpressionCompiler.LightExpression; + using FastExpressionCompiler.LightExpression.ILDecoder; using FastExpressionCompiler.LightExpression.ImTools; #else namespace FastExpressionCompiler; + using FastExpressionCompiler.ILDecoder; using FastExpressionCompiler.ImTools; using System.Linq.Expressions; @@ -40,40 +42,35 @@ static TestTools() #endif } - public static void AssertOpCodes(this Delegate @delegate, params OpCode[] expectedCodes) => - AssertOpCodes(@delegate.Method, expectedCodes); - - public static void AssertOpCodes(this MethodInfo method, params OpCode[] expectedCodes) + public static void AssertOpCodes(this Delegate dlg, params OpCode[] expectedCodes) { - if (DisableAssertOpCodes) return; + var diagInfo = dlg.TryGetDebugInfo(); + if (diagInfo != null) + AssertOpCodes(diagInfo.ILInstructions, expectedCodes); + else + AssertOpCodes(dlg.Method, expectedCodes); + } - var ilReader = ILReaderFactory.Create(method); - if (ilReader is null) - { - Debug.WriteLine($"Reading IL is currently not supported"); - return; - } - var actualCodes = ilReader.Select(x => x.OpCode).ToArray(); + public static void AssertOpCodes(this MethodInfo method, params OpCode[] expectedCodes) => + AssertOpCodes(ILReaderFactory.GetILReaderOrNull(method)?.ToArray() ?? [], expectedCodes); - var sb = new StringBuilder(); - var index = 0; - foreach (var code in actualCodes) - { - if (index < 1000) - sb.AppendLine($"{index,-4}{code}"); - else if (index < 10000000) - sb.AppendLine($"{index,-8}{code}"); - else - sb.AppendLine($"{index,-12}{code}"); - ++index; - } + public static void AssertOpCodes(this IDelegateDebugInfo debugInfo, params OpCode[] expectedCodes) => + AssertOpCodes(debugInfo.ILInstructions, expectedCodes); - Asserts.AreEqual(expectedCodes, actualCodes); + public static void AssertOpCodes(this IEnumerable il, params OpCode[] expectedCodes) + { +#if NET8_0_OR_GREATER + if (DisableAssertOpCodes) return; + Asserts.AreEqual(expectedCodes, il?.Select(x => x.OpCode) ?? []); +#endif } - public static void PrintExpression(this Expression expr, bool completeTypeNames = false) + public static void PrintExpression(this Expression expr, bool completeTypeNames = false, + [CallerMemberName] string caller = "", [CallerFilePath] string filePath = "") { if (!AllowPrintExpression) return; + Console.WriteLine(); + Console.WriteLine($"//{Path.GetFileNameWithoutExtension(filePath)}.{caller}"); Console.WriteLine( expr.ToExpressionString(out var _, out var _, out var _, stripNamespace: true, @@ -82,6 +79,10 @@ public static void PrintExpression(this Expression expr, bool completeTypeNames ); } + public static void PrintExpression(this IDelegateDebugInfo debugInfo, bool completeTypeNames = false, + [CallerMemberName] string caller = "", [CallerFilePath] string filePath = "") => + PrintExpression(debugInfo.Expression, completeTypeNames, caller, filePath); + public static void PrintCSharp(this Expression expr, bool completeTypeNames = false, [CallerMemberName] string caller = "", [CallerFilePath] string filePath = "") { @@ -89,6 +90,12 @@ public static void PrintCSharp(this Expression expr, bool completeTypeNames = fa Console.WriteLine(); Console.WriteLine($"//{Path.GetFileNameWithoutExtension(filePath)}.{caller}"); + if (expr == null) + { + Console.WriteLine(""); + return; + } + var sb = new StringBuilder(1024); sb.Append("var @cs = "); sb = expr.ToCSharpString(sb, @@ -125,18 +132,77 @@ public static void PrintCSharp(this Expression expr, ref string result) Console.WriteLine(result = expr.ToCSharpString()); } - public static void PrintIL(this Delegate @delegate, [CallerMemberName] string tag = null) + /// The method outputs the whole code of the expression including the code of the nested lambdas. + /// In case of nested lambda represented in the expression of the Constant Delegate, + /// and the Delegate.Target being IDelegateDebugInfo, you may call `IDelegateDebugInfo.EnumerateNestedLambdas()` + /// and output C# for each nested lambda + public static void PrintCSharp(this IDelegateDebugInfo debugInfo, bool completeTypeNames = false, + [CallerMemberName] string caller = "", [CallerFilePath] string filePath = "") => + debugInfo.Expression.PrintCSharp(completeTypeNames, caller, filePath); + + public static void PrintIL(this Delegate dlg, [CallerMemberName] string tag = null) { if (!AllowPrintIL) return; - @delegate.Method.PrintIL(tag); + if (dlg.Target is IDelegateDebugInfo debugInfo) + debugInfo.PrintIL(tag); + else + dlg.Method.PrintIL(tag); } - public static void PrintIL(this MethodInfo method, string tag = null) + public static void PrintIL(this MethodInfo method, [CallerMemberName] string tag = null) => + PrintIL(tag, method, static (m, s) => m.ToILString(s)); + + /// Prints the IL instructions of the delegate debug info, including nested lambdas. + public static void PrintIL(this IDelegateDebugInfo debugInfo, [CallerMemberName] string tag = null) + { + if (!AllowPrintIL) return; + + SmallMap4, + SmallMap4.SingleArrayEntries> + > uniquePrinted = default; + var totalNestedCount = 0; + + PrintIL(debugInfo, ref totalNestedCount, ref uniquePrinted, tag ?? "top"); + + if (totalNestedCount > 0) + { + Console.WriteLine("--------------------------------------"); + Console.WriteLine($"Nested lambdas total: {totalNestedCount}, unique: {uniquePrinted.Count}"); + } + } + + private static void PrintIL(IDelegateDebugInfo debugInfo, + ref int totalNestedCount, + ref SmallMap4, + SmallMap4.SingleArrayEntries>> uniquePrinted, + string tag) + { + Debug.Assert(tag != null, "tag should not be null"); + + PrintIL(tag, debugInfo.ILInstructions, static (il, s) => il.ToILString(s)); + + var n = 0; + foreach (var nested in debugInfo.EnumerateNestedLambdas()) + { + ref var printedTag = ref uniquePrinted.AddOrGetValueRef(nested, out var printed); + if (printed) + PrintIL($"{printedTag}", "printed already", static (ap, s) => s.Append(ap)); + else + { + printedTag = $"{n}_nested_in_{tag}"; + PrintIL(nested, ref totalNestedCount, ref uniquePrinted, printedTag); + } + ++n; + ++totalNestedCount; + } + } + + private static void PrintIL(string tag, A state, Action printIL) { if (!AllowPrintIL) return; var s = new StringBuilder(); s.Append(tag == null ? "" : "<" + tag + ">").AppendLine(); - method.ToILString(s); + printIL(state, s); s.AppendLine().Append(tag == null ? "" : ""); Console.WriteLine(s); } @@ -456,8 +522,7 @@ public static bool IsInstanceOf(object actual, [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "The method is used for the testing purposes only.")] public static E Throws(Action action, - [CallerArgumentExpression(nameof(action))] - string actionName = "") + [CallerArgumentExpression(nameof(action))] string actionName = "") where E : Exception { try @@ -551,10 +616,12 @@ public record struct TestStats( int FirstFailureIndex, int FailureCount); -public enum TestTracking +[Flags] +public enum TestFlags : byte { - TrackFailedTestsOnly = 0, - TrackAllTests, + Default = 0, + TrackAllInsteadOfFailedOnlyTests = 1, + RethrowException = 1 << 1, } #if !NETCOREAPP3_0_OR_GREATER @@ -946,10 +1013,14 @@ public sealed class TestRun public SmallList Stats; public SmallList Failures; - // todo: @wip put the output under the feature flag + public TestFlags Flags; + public TestRun(TestFlags flags = TestFlags.Default) => Flags = flags; + /// Will output the failures while running - public void Run(T test, TestTracking tracking = TestTracking.TrackFailedTestsOnly) where T : ITestX + public void Run(T test, TestFlags flags = TestFlags.Default) where T : ITestX { + // use global flags if the local flags are default + flags = flags != TestFlags.Default ? flags : Flags; var totalTestCount = TotalTestCount; var failureCount = Failures.Count; Exception testStopException = null; @@ -957,15 +1028,13 @@ public void Run(T test, TestTracking tracking = TestTracking.TrackFailedTests { test.Run(this); } - catch (Exception ex) + catch (Exception ex) when ((flags & TestFlags.RethrowException) == 0) { testStopException = ex; } var testFailureCount = Failures.Count - failureCount; - if (testStopException != null || - tracking == TestTracking.TrackAllTests || - tracking == TestTracking.TrackFailedTestsOnly & testFailureCount > 0) + if (testStopException != null | testFailureCount > 0 | (flags & TestFlags.TrackAllInsteadOfFailedOnlyTests) != 0) { // todo: @perf Or may be we can put it under the debug only? var testsType = test.GetType(); diff --git a/test/FastExpressionCompiler.Benchmarks/ExprLinqAnyOfNotNullDecimal.cs b/test/FastExpressionCompiler.Benchmarks/ExprLinqAnyOfNotNullDecimal.cs index b891e0df..21197e4a 100644 --- a/test/FastExpressionCompiler.Benchmarks/ExprLinqAnyOfNotNullDecimal.cs +++ b/test/FastExpressionCompiler.Benchmarks/ExprLinqAnyOfNotNullDecimal.cs @@ -71,12 +71,29 @@ public class Test2 static Expression> _expression = t => t.A.Any(e => e.Value != null); /* + +## Baseline + | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | |-------------- |-----------:|----------:|----------:|------:|--------:|-------:|-------:|----------:|------------:| -| CompileSystem | 111.985 us | 2.0430 us | 1.8111 us | 15.30 | 0.46 | 1.4648 | 1.2207 | 10.04 KB | 3.15 | | CompileFast | 7.286 us | 0.1417 us | 0.1891 us | 1.00 | 0.00 | 0.5188 | 0.4883 | 3.19 KB | 1.00 | +| CompileSystem | 111.985 us | 2.0430 us | 1.8111 us | 15.30 | 0.46 | 1.4648 | 1.2207 | 10.04 KB | 3.15 | + + +## v5.3.0 + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) +Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores +.NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +|-------------- |-----------:|----------:|----------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| +| CompileFast | 7.070 us | 0.1356 us | 0.1268 us | 1.00 | 0.02 | 1 | 0.3815 | 0.3662 | 2.38 KB | 1.00 | +| CompileSystem | 137.265 us | 1.6377 us | 1.4518 us | 19.42 | 0.39 | 2 | 1.4648 | 1.2207 | 9.82 KB | 4.12 | */ - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class Compile { [Benchmark] diff --git a/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj b/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj index 608e0b1d..10a5283e 100644 --- a/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj +++ b/test/FastExpressionCompiler.Benchmarks/FastExpressionCompiler.Benchmarks.csproj @@ -17,8 +17,8 @@ - - + + diff --git a/test/FastExpressionCompiler.Benchmarks/HoistedLambdaBenchmark.cs b/test/FastExpressionCompiler.Benchmarks/HoistedLambdaBenchmark.cs index 99fe6ac9..446c9fda 100644 --- a/test/FastExpressionCompiler.Benchmarks/HoistedLambdaBenchmark.cs +++ b/test/FastExpressionCompiler.Benchmarks/HoistedLambdaBenchmark.cs @@ -17,7 +17,7 @@ private static System.Linq.Expressions.Expression> GetHoistedExpr() private static readonly System.Linq.Expressions.Expression> _hoistedExpr = GetHoistedExpr(); - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class Compilation { /* @@ -122,6 +122,42 @@ .NET SDK 9.0.100 | Compile | 145.015 us | 2.0703 us | 1.7288 us | 42.06 | 1.91 | 0.7324 | - | 4.49 KB | 2.92 | | CompileFast | 3.454 us | 0.0688 us | 0.1495 us | 1.00 | 0.06 | 0.2441 | 0.2365 | 1.54 KB | 1.00 | | ConvertToLight_CompileFast | 3.947 us | 0.0789 us | 0.1520 us | 1.14 | 0.07 | 0.3052 | 0.2899 | 1.96 KB | 1.27 | + + ## v5.2.0 Baseline before ILGenerator pooling + + BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) + Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores + .NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | + |------------ |-----------:|----------:|----------:|------:|--------:|-------:|-------:|----------:|------------:| + | Compile | 152.305 us | 2.9218 us | 4.0960 us | 45.18 | 1.35 | 0.7324 | - | 4.49 KB | 2.92 | + | CompileFast | 3.371 us | 0.0543 us | 0.0482 us | 1.00 | 0.02 | 0.2441 | 0.2365 | 1.54 KB | 1.00 | + + + ## v5.3.0 Dynamic ILGenerator+SignatureHelper pooling + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | + |------------ |-----------:|----------:|----------:|------:|--------:|-------:|-------:|----------:|------------:| + | Compile | 149.614 us | 2.8135 us | 2.7633 us | 47.01 | 1.58 | 0.7324 | - | 4.49 KB | 3.66 | + | CompileFast | 3.185 us | 0.0629 us | 0.0941 us | 1.00 | 0.04 | 0.1984 | 0.1907 | 1.23 KB | 1.00 | + + ## v5.3.0 + BDN v0.15.0 + + BenchmarkDotNet v0.15.0, Windows 11 (10.0.26100.4061/24H2/2024Update/HudsonValley) + Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores + .NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | + |------------ |-----------:|----------:|----------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| + | CompileFast | 3.183 us | 0.0459 us | 0.0407 us | 1.00 | 0.02 | 1 | 0.1984 | 0.1945 | 1.23 KB | 1.00 | + | Compile | 147.312 us | 1.9291 us | 1.8946 us | 46.28 | 0.81 | 2 | 0.4883 | 0.2441 | 4.48 KB | 3.65 | */ [Benchmark] @@ -130,11 +166,11 @@ .NET SDK 9.0.100 [Benchmark(Baseline = true)] public object CompileFast() => _hoistedExpr.CompileFast(); - [Benchmark] + // [Benchmark] public object ConvertToLight_CompileFast() => _hoistedExpr.ToLightExpression().CompileFast(); } - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class Invocation { /* @@ -213,6 +249,14 @@ .NET SDK 8.0.100-rc.2.23502.2 | DirectConstructorCall | 5.734 ns | 0.1501 ns | 0.2745 ns | 5.679 ns | 0.86 | 0.05 | 0.0051 | 32 B | 1.00 | | CompiledLambda | 6.857 ns | 0.1915 ns | 0.5434 ns | 6.704 ns | 1.01 | 0.09 | 0.0051 | 32 B | 1.00 | | FastCompiledLambda | 6.746 ns | 0.1627 ns | 0.1442 ns | 6.751 ns | 1.00 | 0.00 | 0.0051 | 32 B | 1.00 | + + ## v5.3.0 + BDN v0.15.0 + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | + |---------------------- |---------:|----------:|----------:|------:|--------:|-----:|-------:|----------:|------------:| + | DirectConstructorCall | 6.055 ns | 0.0632 ns | 0.0560 ns | 1.00 | 0.01 | 1 | 0.0051 | 32 B | 1.00 | + | CompiledLambda | 7.853 ns | 0.2013 ns | 0.1681 ns | 1.30 | 0.03 | 2 | 0.0051 | 32 B | 1.00 | + | FastCompiledLambda | 7.962 ns | 0.2186 ns | 0.4052 ns | 1.31 | 0.07 | 2 | 0.0051 | 32 B | 1.00 | */ private static readonly Func _lambdaCompiled = _hoistedExpr.Compile(); @@ -221,7 +265,7 @@ .NET SDK 8.0.100-rc.2.23502.2 private readonly A _aa = new A(); private readonly B _bb = new B(); - [Benchmark] + [Benchmark(Baseline = true)] public object DirectConstructorCall() => new X(_aa, _bb); @@ -229,7 +273,7 @@ public object DirectConstructorCall() => public object CompiledLambda() => _lambdaCompiled(); - [Benchmark(Baseline = true)] + [Benchmark] public object FastCompiledLambda() => _lambdaCompiledFast(); } diff --git a/test/FastExpressionCompiler.Benchmarks/HoistedLambdaWithNestedLambdaBenchmark.cs b/test/FastExpressionCompiler.Benchmarks/HoistedLambdaWithNestedLambdaBenchmark.cs index e2406ec6..f9131dd4 100644 --- a/test/FastExpressionCompiler.Benchmarks/HoistedLambdaWithNestedLambdaBenchmark.cs +++ b/test/FastExpressionCompiler.Benchmarks/HoistedLambdaWithNestedLambdaBenchmark.cs @@ -18,7 +18,7 @@ private static Expression> GetHoistedExpr() private static readonly Expression> _hoistedExpr = GetHoistedExpr(); - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class Compilation { /* @@ -123,6 +123,34 @@ .NET SDK 9.0.100 | Compile | 421.09 us | 8.382 us | 18.221 us | 413.02 us | 36.29 | 2.09 | 1.9531 | 0.9766 | 12.04 KB | 2.61 | | CompileFast | 11.62 us | 0.230 us | 0.464 us | 11.42 us | 1.00 | 0.06 | 0.7324 | 0.7019 | 4.62 KB | 1.00 | + ## v5.3.0 ILGenerator pooling + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | + |------------ |----------:|---------:|---------:|------:|--------:|-------:|-------:|----------:|------------:| + | Compile | 410.18 us | 6.928 us | 5.785 us | 36.97 | 0.92 | 1.9531 | 1.4648 | 12.04 KB | 2.74 | + | CompileFast | 11.10 us | 0.214 us | 0.237 us | 1.00 | 0.03 | 0.7019 | 0.6714 | 4.4 KB | 1.00 | + + ## v5.3.0 ILGenerator pooling for the nested lambdas too + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | + |------------ |----------:|---------:|---------:|------:|--------:|-------:|-------:|----------:|------------:| + | Compile | 413.38 us | 5.859 us | 5.480 us | 39.90 | 0.88 | 1.9531 | 1.4648 | 12.04 KB | 3.06 | + | CompileFast | 10.36 us | 0.195 us | 0.191 us | 1.00 | 0.03 | 0.6409 | 0.6104 | 3.93 KB | 1.00 | + + ## v5.3.0 ILGenerator+SignaturHelper pooling + + | Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | + |------------ |----------:|---------:|----------:|----------:|------:|--------:|-------:|-------:|----------:|------------:| + | Compile | 437.16 us | 8.631 us | 20.004 us | 438.94 us | 39.26 | 3.33 | 1.9531 | 0.9766 | 12.04 KB | 3.19 | + | CompileFast | 11.20 us | 0.329 us | 0.896 us | 10.93 us | 1.01 | 0.11 | 0.6104 | 0.5951 | 3.77 KB | 1.00 | + + ## v5.3.0 + BDN v0.15.0 + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | + |------------ |----------:|---------:|---------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| + | CompileFast | 11.12 us | 0.189 us | 0.158 us | 1.00 | 0.02 | 1 | 0.6104 | 0.5798 | 3.77 KB | 1.00 | + | Compile | 415.09 us | 4.277 us | 3.571 us | 37.34 | 0.60 | 2 | 1.9531 | 1.4648 | 12.04 KB | 3.19 | + */ [Benchmark] public Func Compile() => _hoistedExpr.Compile(); @@ -131,7 +159,7 @@ .NET SDK 9.0.100 public Func CompileFast() => _hoistedExpr.CompileFast(); } - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class Invocation { /* @@ -232,7 +260,7 @@ .NET SDK 9.0.100 private readonly A _aa = new A(); private readonly B _bb = new B(); - [Benchmark] + [Benchmark(Baseline = true)] public X DirectMethodCall() => CreateX((a, b) => new X(a, b), new Lazy(() => _aa), _bb); @@ -240,7 +268,7 @@ public X DirectMethodCall() => public X Invoke_Compiled() => _lambdaCompiled(); - [Benchmark(Baseline = true)] + [Benchmark] public X Invoke_CompiledFast() => _lambdaCompiledFast(); } diff --git a/test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs b/test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs index 57790d59..04bc27e5 100644 --- a/test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs +++ b/test/FastExpressionCompiler.Benchmarks/Issue468_Compile_vs_FastCompile.cs @@ -315,6 +315,14 @@ .NET SDK 9.0.203 | PoolILGenerator | 2.173 us | 0.0407 us | 0.0734 us | 2.140 us | 0.78 | 0.04 | 1 | 0.1068 | 0.1030 | 687 B | 0.60 | | CreateILGenerator | 2.799 us | 0.0559 us | 0.0949 us | 2.767 us | 1.00 | 0.05 | 2 | 0.1793 | 0.1755 | 1144 B | 1.00 | + + ## Results after fixing the pooling for all tests + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | + |------------------- |---------:|----------:|----------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| + | ReuseILGenerator | 2.791 us | 0.0542 us | 0.0556 us | 0.88 | 0.05 | 1 | 0.1411 | 0.1373 | 904 B | 0.79 | + | NoReuseILGenerator | 3.195 us | 0.0640 us | 0.1846 us | 1.00 | 0.08 | 2 | 0.1793 | 0.1755 | 1144 B | 1.00 | + */ [Benchmark(Baseline = true)] public object NoReuseILGenerator() diff --git a/test/FastExpressionCompiler.Benchmarks/LightExprVsExpr_CreateAndCompile_ComplexExpr.cs b/test/FastExpressionCompiler.Benchmarks/LightExprVsExpr_CreateAndCompile_ComplexExpr.cs index fe129976..ac27a25c 100644 --- a/test/FastExpressionCompiler.Benchmarks/LightExprVsExpr_CreateAndCompile_ComplexExpr.cs +++ b/test/FastExpressionCompiler.Benchmarks/LightExprVsExpr_CreateAndCompile_ComplexExpr.cs @@ -4,7 +4,7 @@ namespace FastExpressionCompiler.Benchmarks { - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class LightExprVsExpr_CreateAndCompile_ComplexExpr { /** @@ -139,6 +139,21 @@ .NET SDK 9.0.100 | Create_SystemExpression_and_CompileFast | 6.656 us | 0.1322 us | 0.3065 us | 1.40 | 0.10 | 0.5188 | 0.4883 | 3.27 KB | 1.35 | | Create_LightExpression_and_CompileFast | 4.751 us | 0.0947 us | 0.2411 us | 1.00 | 0.07 | 0.3815 | 0.3662 | 2.42 KB | 1.00 | | CreateLightExpression_and_CompileFast_with_intrinsic | 4.604 us | 0.0918 us | 0.1915 us | 0.97 | 0.06 | 0.3815 | 0.3662 | 2.35 KB | 0.97 | + + + ## v5.3.0 ILGenerator pooling + + BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) + Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores + .NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + + | Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | + |---------------------------------------- |-----------:|----------:|----------:|-----------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| + | Create_LightExpression_and_CompileFast | 4.494 us | 0.0809 us | 0.0932 us | 4.481 us | 1.00 | 0.03 | 1 | 0.3510 | 0.3281 | 2.15 KB | 1.00 | + | Create_SystemExpression_and_CompileFast | 6.465 us | 0.1249 us | 0.1439 us | 6.472 us | 1.44 | 0.04 | 2 | 0.4578 | 0.4272 | 2.97 KB | 1.38 | + | Create_SystemExpression_and_Compile | 207.973 us | 4.1326 us | 6.5548 us | 211.782 us | 46.30 | 1.71 | 3 | 0.9766 | 0.7324 | 7.15 KB | 3.33 | */ [Benchmark] @@ -153,12 +168,12 @@ public object Create_SystemExpression_and_CompileFast() => public object Create_LightExpression_and_CompileFast() => LE.CompileFast(LightExpressionTests.CreateComplexLightExpression()); - [Benchmark] + // [Benchmark] public object CreateLightExpression_and_CompileFast_with_intrinsic() => LE.CompileFast(LightExpressionTests.CreateComplexLightExpression_with_intrinsics()); } - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class LightExprVsExpr_Create_ComplexExpr { /* @@ -246,6 +261,15 @@ .NET SDK 9.0.100 | Create_LightExpression | 153.7 ns | 3.14 ns | 8.61 ns | 150.5 ns | 1.00 | 0.08 | 0.0789 | 496 B | 1.00 | | Create_LightExpression_with_intrinsics | 161.0 ns | 2.80 ns | 2.19 ns | 161.0 ns | 1.05 | 0.06 | 0.0777 | 488 B | 0.98 | + + ## v5.3.0 + BDN v0.15.0 + + | Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | + |---------------------------------------- |-----------:|----------:|----------:|-----------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| + | Create_LightExpression_and_CompileFast | 4.957 us | 0.0986 us | 0.2362 us | 4.913 us | 1.00 | 0.07 | 1 | 0.3510 | 0.3052 | 2.15 KB | 1.00 | + | Create_SystemExpression_and_CompileFast | 6.518 us | 0.1889 us | 0.5541 us | 6.300 us | 1.32 | 0.13 | 2 | 0.4578 | 0.4272 | 2.97 KB | 1.38 | + | Create_SystemExpression_and_Compile | 205.000 us | 4.0938 us | 7.3819 us | 206.353 us | 41.44 | 2.45 | 3 | 0.9766 | 0.4883 | 7.15 KB | 3.33 | + */ [Benchmark] @@ -256,7 +280,7 @@ public object Create_SystemExpression() => public object Create_LightExpression() => LightExpressionTests.CreateComplexLightExpression(); - [Benchmark] + // [Benchmark] public object Create_LightExpression_with_intrinsics() => LightExpressionTests.CreateComplexLightExpression_with_intrinsics(); } diff --git a/test/FastExpressionCompiler.Benchmarks/ManuallyComposedLambdaBenchmark.cs b/test/FastExpressionCompiler.Benchmarks/ManuallyComposedLambdaBenchmark.cs index c4b6883a..c7ce6fe1 100644 --- a/test/FastExpressionCompiler.Benchmarks/ManuallyComposedLambdaBenchmark.cs +++ b/test/FastExpressionCompiler.Benchmarks/ManuallyComposedLambdaBenchmark.cs @@ -153,7 +153,7 @@ public Func Create_LightExpression_Then_CompileFast() => LightExpression.ExpressionCompiler.CompileFast(CreateManualLightExprWithParams(), true); } - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class Compilation { /* @@ -227,22 +227,44 @@ .NET SDK 8.0.100-rc.2.23502.2 | CompileFast_SystemExpression | 3.047 us | 0.0607 us | 0.1183 us | 3.010 us | 0.97 | 0.06 | 0.2213 | 0.2136 | 1.39 KB | 1.00 | | CompileFast_LightExpression | 3.151 us | 0.0628 us | 0.1117 us | 3.130 us | 1.00 | 0.00 | 0.2213 | 0.2136 | 1.39 KB | 1.00 | + # v5.3.0 - Pooling the ILGenerator + + BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) + Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores + .NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | + |----------------------------- |-----------:|----------:|----------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| + | CompileFast_SystemExpression | 3.219 us | 0.0380 us | 0.0337 us | 0.98 | 0.01 | 1 | 0.1793 | 0.1755 | 1.12 KB | 1.00 | + | CompileFast_LightExpression | 3.292 us | 0.0407 us | 0.0381 us | 1.00 | 0.02 | 1 | 0.1793 | 0.1755 | 1.12 KB | 1.00 | + | Compile_SystemExpression | 102.515 us | 1.4959 us | 2.2390 us | 31.14 | 0.75 | 2 | 0.7324 | 0.4883 | 4.74 KB | 4.24 | + + # v5.3.0 - Pooling the ILGenerator+SignatureHelper + + | Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | + |----------------------------- |-----------:|----------:|----------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| + | CompileFast_SystemExpression | 3.190 us | 0.0519 us | 0.0485 us | 0.97 | 0.02 | 1 | 0.1755 | 0.1678 | 1.08 KB | 1.00 | + | CompileFast_LightExpression | 3.300 us | 0.0633 us | 0.0650 us | 1.00 | 0.03 | 1 | 0.1755 | 0.1678 | 1.08 KB | 1.00 | + | Compile_SystemExpression | 106.339 us | 2.1083 us | 4.1120 us | 32.24 | 1.38 | 2 | 0.7324 | 0.6104 | 4.74 KB | 4.40 | + */ [Benchmark] public Func Compile_SystemExpression() => _expr.Compile(); - [Benchmark] + [Benchmark(Baseline = true)] public Func CompileFast_SystemExpression() => _expr.CompileFast(true); - [Benchmark(Baseline = true)] + [Benchmark] public Func CompileFast_LightExpression() => LightExpression.ExpressionCompiler.CompileFast(_leExpr, true); } - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] public class Invocation { /* @@ -317,7 +339,7 @@ public class Invocation private static readonly B _bb = new B(); private static readonly Func _lambda = b => new X(_aa, b); - [Benchmark] + [Benchmark(Baseline = true)] public X DirectCall() => _lambda(_bb); [Benchmark] @@ -326,7 +348,7 @@ public class Invocation [Benchmark] public X CompiledFast_SystemExpression() => _lambdaCompiledFast(_bb); - [Benchmark(Baseline = true)] + [Benchmark] public X CompiledFast_LightExpression() => _lambdaCompiledFast_LightExpession(_bb); } } diff --git a/test/FastExpressionCompiler.Benchmarks/NestedLambdasVsVars.cs b/test/FastExpressionCompiler.Benchmarks/NestedLambdasVsVars.cs index 90c4c912..c9185e43 100644 --- a/test/FastExpressionCompiler.Benchmarks/NestedLambdasVsVars.cs +++ b/test/FastExpressionCompiler.Benchmarks/NestedLambdasVsVars.cs @@ -9,7 +9,7 @@ namespace FastExpressionCompiler.Benchmarks { - [MemoryDiagnoser] + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] // [HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.BranchMispredictions, HardwareCounter.BranchInstructions)] public class NestedLambdasVsVars { @@ -117,6 +117,19 @@ .NET SDK 7.0.307 | LightExpression_with_sub_expressions_CompiledFast | 19.31 us | 0.321 us | 0.343 us | 1.00 | 0.00 | 1.1902 | 1.1292 | 7.32 KB | 1.00 | | Expression_with_sub_expressions_Compiled | 602.67 us | 22.702 us | 65.502 us | 30.55 | 3.29 | 3.9063 | 2.9297 | 28.7 KB | 3.92 | +## v5.3.0 Pooled ILGenerator + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) +Intel Core i9-8950HK CPU 2.90GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores +.NET SDK 9.0.203 + [Host] : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + DefaultJob : .NET 9.0.4 (9.0.425.16305), X64 RyuJIT AVX2 + +| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio | +|-------------------------------------------------- |----------:|----------:|----------:|------:|--------:|-----:|-------:|-------:|----------:|------------:| +| LightExpression_with_sub_expressions_CompiledFast | 16.81 us | 0.298 us | 0.264 us | 1.00 | 0.02 | 1 | 1.1292 | 1.0986 | 6.98 KB | 1.00 | +| Expression_with_sub_expressions_Compiled | 527.85 us | 10.287 us | 16.016 us | 31.41 | 1.06 | 2 | 3.9063 | 2.9297 | 28.25 KB | 4.05 | + */ private Expression> _expr;//, _exprWithVars; private LightExpression.Expression> _lightExpr; diff --git a/test/FastExpressionCompiler.Benchmarks/Program.cs b/test/FastExpressionCompiler.Benchmarks/Program.cs index ee4f3877..5f268002 100644 --- a/test/FastExpressionCompiler.Benchmarks/Program.cs +++ b/test/FastExpressionCompiler.Benchmarks/Program.cs @@ -21,11 +21,11 @@ public static void Main() // BenchmarkRunner.Run(); // not included in README.md, may be it needs to // BenchmarkRunner.Run(); - // BenchmarkRunner.Run(); + BenchmarkRunner.Run(); //-------------------------------------------- - BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); diff --git a/test/FastExpressionCompiler.IssueTests/Issue14_String_constant_comparisons_fail.cs b/test/FastExpressionCompiler.IssueTests/Issue14_String_constant_comparisons_fail.cs index fede44b9..23bc6ddc 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue14_String_constant_comparisons_fail.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue14_String_constant_comparisons_fail.cs @@ -28,7 +28,6 @@ public int Run() return 6; } - public void String_equality_should_work() { System.Linq.Expressions.Expression> se = str => str == "Hello"; @@ -43,7 +42,6 @@ public void String_equality_should_work() Asserts.IsTrue(isHello(new StringBuilder("Hello").ToString())); } - public void String_not_equality_should_work() { System.Linq.Expressions.Expression> se = str => str != "Hello"; @@ -58,7 +56,6 @@ public void String_not_equality_should_work() Asserts.IsFalse(isHello(new StringBuilder("Hello").ToString())); } - public void Guid_equality_should_work() { var expectedId = Guid.NewGuid(); @@ -72,7 +69,6 @@ public void Guid_equality_should_work() Asserts.IsTrue(isExpectedId(expectedId)); } - public void Guid_not_equality_should_work() { var expectedId = Guid.NewGuid(); @@ -86,7 +82,6 @@ public void Guid_not_equality_should_work() Asserts.IsFalse(isExpectedId(expectedId)); } - public void Enum_equality_should_work() { var expectedExpr = Constant(Blah.Bar, typeof(Blah)); @@ -102,7 +97,6 @@ public void Enum_equality_should_work() enum Blah { Foo, Bar } - public void Class_Equals_equality_should_work() { var expected = new Pooh(42); diff --git a/test/FastExpressionCompiler.IssueTests/Issue159_NumericConversions.cs b/test/FastExpressionCompiler.IssueTests/Issue159_NumericConversions.cs index 6c00632f..0b2c68fa 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue159_NumericConversions.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue159_NumericConversions.cs @@ -118,7 +118,7 @@ public void UnsignedNullableLongComparison() var expectedResult = expected(new ValueHolder { Value = ulong.MaxValue }); Asserts.AreEqual(false, expectedResult); - var actual = lambdaExpr.CompileFast(true); + var actual = lambdaExpr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); actual.Method.AssertOpCodes( OpCodes.Ldarg_1, @@ -315,7 +315,7 @@ public void ComparisonsWithConversionsShouldWork3() var expr = Lambda, bool>>(condition, floatParamExpr); var source = new ValueHolder { Value = float.MaxValue }; - var compiledFast = expr.CompileFast(true); + var compiledFast = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); compiledFast.AssertOpCodes( OpCodes.Ldarg_1, @@ -346,7 +346,7 @@ public void ComparisonsWithConversionsShouldWork4() var compiled = expr.CompileSys(); compiled.PrintIL(); - var compiledFast = expr.CompileFast(true); + var compiledFast = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); compiledFast.PrintIL(); compiledFast.AssertOpCodes( @@ -632,7 +632,7 @@ public void NullableIntToDoubleCastsShouldWork() block, nullableIntHolderParam); - var adapt = adaptExpr.CompileFast(true); + var adapt = adaptExpr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); adapt.Method.AssertOpCodes( OpCodes.Newobj, OpCodes.Stloc_0, // todo: can be replaced with dup # @@ -663,7 +663,7 @@ public void NullableIntToDoubleCastsShouldWork_with_MemberInit() var adaptExpr = Lambda, ValueHolder>>( memberInit, nullableIntHolderParam); - var adapt = adaptExpr.CompileFast(true); + var adapt = adaptExpr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); adapt.Method.AssertOpCodes( OpCodes.Newobj, @@ -702,7 +702,7 @@ public void NullableDecimalToDoubleCastsShouldWork() var source = new ValueHolder { Value = 938378.637m }; - var adapt = expr.CompileFast(true); + var adapt = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); adapt.Method.AssertOpCodes( OpCodes.Newobj, OpCodes.Stloc_0, // todo: can be simplified with dup #173 @@ -742,7 +742,7 @@ public void DecimalToNullableDoubleCastsShouldWork() var source = new ValueHolder { Value = 5332.00m }; - var adapt = expr.CompileFast(true); + var adapt = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); adapt.Method.AssertOpCodes( OpCodes.Newobj, OpCodes.Stloc_0, // todo: can be simplified with dup #173 diff --git a/test/FastExpressionCompiler.IssueTests/Issue170_Serializer_Person_Ref.cs b/test/FastExpressionCompiler.IssueTests/Issue170_Serializer_Person_Ref.cs index 7b723710..a7d403c6 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue170_Serializer_Person_Ref.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue170_Serializer_Person_Ref.cs @@ -74,7 +74,7 @@ void LocalAssert(DeserializeDelegate invoke) func.PrintIL(); LocalAssert(func); - var funcFast = lambda.CompileFast(true); + var funcFast = lambda.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); funcFast.PrintIL(); funcFast.AssertOpCodes( OpCodes.Ldarg_3, @@ -180,7 +180,7 @@ void LocalAssert(DeserializeDelegateSimple invoke) s.PrintIL(); // LocalAssert(s); // system thing does not work for the structs, but works for the class - var f = lambda.CompileFast(true); + var f = lambda.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.PrintIL(); f.AssertOpCodes( OpCodes.Ldarg_1, @@ -224,7 +224,7 @@ void LocalAssert(DeserializeDelegateSimple invoke) s.PrintIL(); LocalAssert(s); // works for class - var f = lambda.CompileFast(true); + var f = lambda.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.PrintIL(); f.AssertOpCodes( OpCodes.Ldarg_1, diff --git a/test/FastExpressionCompiler.IssueTests/Issue243_Pass_Parameter_By_Ref_is_supported.cs b/test/FastExpressionCompiler.IssueTests/Issue243_Pass_Parameter_By_Ref_is_supported.cs index c653a068..57617ce2 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue243_Pass_Parameter_By_Ref_is_supported.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue243_Pass_Parameter_By_Ref_is_supported.cs @@ -92,7 +92,7 @@ public void Lambda_Ref_Parameter_Passed_Into_Static_Value_Method() var a = "test"; Asserts.AreEqual(a, systCompiled(ref a)); - var fastCompiled = lambda.CompileFast(ifFastFailedReturnNull: true); + var fastCompiled = lambda.CompileFast(ifFastFailedReturnNull: true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(fastCompiled); fastCompiled.AssertOpCodes( @@ -131,7 +131,7 @@ public void Lambda_Ref_Parameter_Passed_Into_Instance_Value_Method() var a = "test"; Asserts.AreEqual(a, systCompiled(ref cls, ref a)); - var fastCompiled = lambda.CompileFast(ifFastFailedReturnNull: true); + var fastCompiled = lambda.CompileFast(ifFastFailedReturnNull: true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(fastCompiled); fastCompiled.AssertOpCodes( @@ -172,7 +172,7 @@ public void Lambda_Ref_Parameter_Passed_Into_Struct_Instance_Value_Method() var a = 1; Asserts.AreEqual("1", systCompiled(ref cls, ref a)); - var fastCompiled = lambda.CompileFast(ifFastFailedReturnNull: true); + var fastCompiled = lambda.CompileFast(ifFastFailedReturnNull: true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(fastCompiled); fastCompiled.PrintIL(); @@ -195,7 +195,7 @@ public void Lambda_Ref_ValueType_Parameter_Passed_Into_Value_Method() var lambda = Lambda(call, parameter); - var fastCompiled = lambda.CompileFast(ifFastFailedReturnNull: true); + var fastCompiled = lambda.CompileFast(ifFastFailedReturnNull: true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(fastCompiled); fastCompiled.Method.AssertOpCodes( diff --git a/test/FastExpressionCompiler.IssueTests/Issue248_Calling_method_with_in_out_parameters_in_expression_lead_to_NullReferenceException_on_calling_site.cs b/test/FastExpressionCompiler.IssueTests/Issue248_Calling_method_with_in_out_parameters_in_expression_lead_to_NullReferenceException_on_calling_site.cs index dc022fce..04a580d0 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue248_Calling_method_with_in_out_parameters_in_expression_lead_to_NullReferenceException_on_calling_site.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue248_Calling_method_with_in_out_parameters_in_expression_lead_to_NullReferenceException_on_calling_site.cs @@ -33,7 +33,7 @@ public void Test_1() expr.PrintCSharp(); - var serialize = expr.CompileFast(true); + var serialize = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(serialize); serialize.PrintIL(); @@ -74,7 +74,7 @@ public void Test_2() OpCodes.Callvirt, OpCodes.Ret); - var serialize = expr.CompileFast(true); + var serialize = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(serialize); serialize.PrintIL(); @@ -113,7 +113,7 @@ public void Test_3() OpCodes.Callvirt, OpCodes.Ret); - var serialize = expr.CompileFast(true); + var serialize = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(serialize); serialize.PrintIL(); @@ -157,7 +157,7 @@ public void Test_4() OpCodes.Callvirt, OpCodes.Ret); - var serialize = expr.CompileFast(true); + var serialize = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(serialize); serialize.PrintIL(); diff --git a/test/FastExpressionCompiler.IssueTests/Issue251_Bad_code_gen_for_byRef_parameters.cs b/test/FastExpressionCompiler.IssueTests/Issue251_Bad_code_gen_for_byRef_parameters.cs index 1b730a90..71377cf6 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue251_Bad_code_gen_for_byRef_parameters.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue251_Bad_code_gen_for_byRef_parameters.cs @@ -29,7 +29,7 @@ public void Test_1() expr.PrintCSharp(); - var f = expr.CompileFast(true); + var f = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(f); f.PrintIL(); diff --git a/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs b/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs index 7873940d..c6c9cb59 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue261_Loop_wih_conditions_fails.cs @@ -608,7 +608,7 @@ public void Test_class_items_array_index_via_variable_access_then_the_member_acc var fs = e.CompileSys(); fs.PrintIL(); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.AssertOpCodes( OpCodes.Ldarg_2, OpCodes.Stloc_0, @@ -641,7 +641,7 @@ public void Test_struct_items_array_index_via_variable_access_then_the_member_ac elArr, index ); - var f = e.CompileFast(true); + var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.AssertOpCodes( OpCodes.Ldarg_2, diff --git a/test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs b/test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs index 7430da05..bfc126b0 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue274_Failing_Expressions_in_Linq2DB.cs @@ -19,257 +19,254 @@ namespace FastExpressionCompiler.IssueTests; public class Issue274_Failing_Expressions_in_Linq2DB : ITest { - public int Run() - { - Test_case_2_Full_ExecutionEngineException(); - - Test_287_Case1_ConvertTests_NullableParameterInOperatorConvert_VerificationException(); - Test_287_Case2_SimpleDelegate_as_nested_lambda_in_TesCollection_test(); - - Test_283_Case6_MappingSchemaTests_CultureInfo_VerificationException(); - Test_283_Case5_ConvertTests_NullableIntToNullableEnum_NullReferenceException(); - Test_283_Case4_SecurityVerificationException(); - Test_283_Case3_SecurityVerificationException(); - Test_283_Case2_NullRefException(); - Test_283_Case2_Minimal_NullRefException(); + public int Run() + { + The_expression_with_anonymous_class_should_output_without_special_symbols(); - Test_case_4_simplified_InvalidCastException(); + Test_case_2_Full_ExecutionEngineException(); - Test_case_4_Full_InvalidCastException(); - Test_case_3_Full_NullReferenceException(); - Test_case_1_Minimal_compare_nullable_with_null_conditional(); - Test_case_1_Minimal_compare_nullable_returned_by_the_method_with_null_conditional(); - Test_case_1_Minimal_compare_nullable_with_null_conditional_and_nested_conditional(); - Test_case_1_Full_AccessViolationException(); - The_expression_with_anonymous_class_should_output_without_special_symbols(); + Test_287_Case1_ConvertTests_NullableParameterInOperatorConvert_VerificationException(); + Test_287_Case2_SimpleDelegate_as_nested_lambda_in_TesCollection_test(); - return 17; - } + Test_283_Case6_MappingSchemaTests_CultureInfo_VerificationException(); + Test_283_Case5_ConvertTests_NullableIntToNullableEnum_NullReferenceException(); + Test_283_Case4_SecurityVerificationException(); + Test_283_Case3_SecurityVerificationException(); + Test_283_Case2_NullRefException(); + Test_283_Case2_Minimal_NullRefException(); + Test_case_4_simplified_InvalidCastException(); - public void The_expression_with_anonymous_class_should_output_without_special_symbols() - { - int? fortyTwo = 42; - var e = Lambda>( - PropertyOrField(Constant(new { X = fortyTwo }), "X")); + Test_case_4_Full_InvalidCastException(); + Test_case_3_Full_NullReferenceException(); + Test_case_1_Minimal_compare_nullable_with_null_conditional(); + Test_case_1_Minimal_compare_nullable_returned_by_the_method_with_null_conditional(); + Test_case_1_Minimal_compare_nullable_with_null_conditional_and_nested_conditional(); + Test_case_1_Full_AccessViolationException(); - var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); + return 17; + } - var de = f.Target as IDelegateDebugInfo; - Asserts.IsNotNull(de.Expression); - Asserts.IsNotNull(de.ExpressionString); - Asserts.DoesNotContain("<>", de.ExpressionString); + public void The_expression_with_anonymous_class_should_output_without_special_symbols() + { + int? fortyTwo = 42; + var e = Lambda>( + PropertyOrField(Constant(new { X = fortyTwo }), "X")); - Asserts.IsNotNull(de.CSharpString); - Asserts.DoesNotContain("<>", de.CSharpString); - } + var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); + var de = f.Target as IDelegateDebugInfo; + Asserts.IsNotNull(de.Expression); +#if NET8_0_OR_GREATER + Asserts.IsTrue(de.ILInstructions.Length > 0); +#endif + } - public void Test_case_1_Minimal_compare_nullable_with_null_conditional() - { - var p = Parameter(typeof(int?), "i"); - var e = Lambda>( - Condition(Equal(p, Constant(null, typeof(int?))), Constant(null, typeof(int?)), Convert(Constant(100), typeof(int?))), p); + public void Test_case_1_Minimal_compare_nullable_with_null_conditional() + { + var p = Parameter(typeof(int?), "i"); + var e = Lambda>( + Condition(Equal(p, Constant(null, typeof(int?))), Constant(null, typeof(int?)), Convert(Constant(100), typeof(int?))), p); - var fs = e.CompileSys(); - fs.PrintIL(); + var fs = e.CompileSys(); + fs.PrintIL(); - var f = e.CompileFast(true); - f.PrintIL(); + var f = e.CompileFast(true); + f.PrintIL(); - Asserts.AreEqual(100, f(42)); - } + Asserts.AreEqual(100, f(42)); + } - public void Test_case_1_Minimal_compare_nullable_returned_by_the_method_with_null_conditional() - { - var p = Parameter(typeof(int?), "i"); - var e = Lambda>( - Condition( - Equal(Call(GetType().GetMethod(nameof(CheckNullable)), p), - Constant(null, typeof(int?))), - Constant(null, typeof(int?)), - Convert(Constant(100), typeof(int?))), p); + public void Test_case_1_Minimal_compare_nullable_returned_by_the_method_with_null_conditional() + { + var p = Parameter(typeof(int?), "i"); + var e = Lambda>( + Condition( + Equal(Call(GetType().GetMethod(nameof(CheckNullable)), p), + Constant(null, typeof(int?))), + Constant(null, typeof(int?)), + Convert(Constant(100), typeof(int?))), p); - var fs = e.CompileSys(); - fs.PrintIL(); + var fs = e.CompileSys(); + fs.PrintIL(); - var f = e.CompileFast(true); - f.PrintIL(); + var f = e.CompileFast(true); + f.PrintIL(); - Asserts.AreEqual(100, f(42)); - } + Asserts.AreEqual(100, f(42)); + } - public void Test_case_1_Minimal_compare_nullable_with_null_conditional_and_nested_conditional() - { - var i = Parameter(typeof(int?), "i"); - var e = Lambda>( - Condition( - Equal(i, Constant(null, typeof(int?))), - Constant(null, typeof(int?)), + public void Test_case_1_Minimal_compare_nullable_with_null_conditional_and_nested_conditional() + { + var i = Parameter(typeof(int?), "i"); + var e = Lambda>( Condition( - Equal(Call(GetType().GetMethod(nameof(CheckNullable)), i), Constant(null, typeof(int?))), + Equal(i, Constant(null, typeof(int?))), Constant(null, typeof(int?)), - Convert(Constant(100), typeof(int?))) - ), i); - - var fs = e.CompileSys(); - fs.PrintIL(); - Asserts.AreEqual(100, fs(42)); - - var f = e.CompileFast(true); - f.PrintIL(); - Asserts.AreEqual(100, f(42)); - } - - public static int? CheckNullable(int? i) => 5; - - - public void Test_case_1_Full_AccessViolationException() - { - var p = new ParameterExpression[10]; // the parameter expressions i - var e = new Expression[30]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda( // $ - typeof(Func>>), - e[0] = Invoke( - e[1] = Lambda( // $ - typeof(Func>>), - e[2] = Block( - typeof(f__AnonymousType142>), - new[] { + Condition( + Equal(Call(GetType().GetMethod(nameof(CheckNullable)), i), Constant(null, typeof(int?))), + Constant(null, typeof(int?)), + Convert(Constant(100), typeof(int?))) + ), i); + + var fs = e.CompileSys(); + fs.PrintIL(); + Asserts.AreEqual(100, fs(42)); + + var f = e.CompileFast(true); + f.PrintIL(); + Asserts.AreEqual(100, f(42)); + } + + public static int? CheckNullable(int? i) => 5; + + + public void Test_case_1_Full_AccessViolationException() + { + var p = new ParameterExpression[10]; // the parameter expressions i + var e = new Expression[30]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda( // $ + typeof(Func>>), + e[0] = Invoke( + e[1] = Lambda( // $ + typeof(Func>>), + e[2] = Block( + typeof(f__AnonymousType142>), + new[] { p[0]=Parameter(typeof(SQLiteDataReader), "ldr"), p[1]=Parameter(typeof(f__AnonymousType142>)) - }, - e[3] = MakeBinary(ExpressionType.Assign, - p[0 // (SQLiteDataReader ldr) - ], - e[4] = Convert( - p[2] = Parameter(typeof(IDataReader), "dr"), - typeof(SQLiteDataReader))), - e[5] = MakeBinary(ExpressionType.Assign, - p[1 // (f__AnonymousType142> __f__AnonymousType142_Nullable_int____12662012) - ], - e[6] = New(/*1 args*/ - typeof(f__AnonymousType142>).GetTypeInfo().DeclaredConstructors.ToArray()[0], - e[7] = Condition( - e[8] = MakeBinary(ExpressionType.NotEqual, - e[9] = Condition( - e[10] = Call( - p[0 // (SQLiteDataReader ldr) - ], - typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "IsDBNull" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - e[11] = Constant((int)0)), - e[12] = Constant(null, typeof(Nullable)), - e[13] = New(/*1 args*/ - typeof(Nullable).GetTypeInfo().DeclaredConstructors.ToArray()[0], - e[14] = Call( + }, + e[3] = MakeBinary(ExpressionType.Assign, + p[0 // (SQLiteDataReader ldr) + ], + e[4] = Convert( + p[2] = Parameter(typeof(IDataReader), "dr"), + typeof(SQLiteDataReader))), + e[5] = MakeBinary(ExpressionType.Assign, + p[1 // (f__AnonymousType142> __f__AnonymousType142_Nullable_int____12662012) + ], + e[6] = New(/*1 args*/ + typeof(f__AnonymousType142>).GetTypeInfo().DeclaredConstructors.ToArray()[0], + e[7] = Condition( + e[8] = MakeBinary(ExpressionType.NotEqual, + e[9] = Condition( + e[10] = Call( + p[0 // (SQLiteDataReader ldr) + ], + typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "IsDBNull" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), + e[11] = Constant((int)0)), + e[12] = Constant(null, typeof(Nullable)), + e[13] = New(/*1 args*/ + typeof(Nullable).GetTypeInfo().DeclaredConstructors.ToArray()[0], + e[14] = Call( + p[0 // (SQLiteDataReader ldr) + ], + typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "GetInt32" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), + e[15] = Constant((int)0))), + typeof(Nullable)), + e[16] = Constant(null, typeof(Nullable))), + e[17] = Condition( + e[18] = Call( p[0 // (SQLiteDataReader ldr) ], - typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "GetInt32" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - e[15] = Constant((int)0))), - typeof(Nullable)), - e[16] = Constant(null, typeof(Nullable))), - e[17] = Condition( - e[18] = Call( - p[0 // (SQLiteDataReader ldr) - ], - typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "IsDBNull" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - e[19] = Constant((int)0)), - e[20] = Constant(null, typeof(Nullable)), - e[21] = New(/*1 args*/ - typeof(Nullable).GetTypeInfo().DeclaredConstructors.ToArray()[0], - e[22] = Call( - p[0 // (SQLiteDataReader ldr) - ], - typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "GetInt32" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - e[23] = Constant((int)0))), - typeof(Nullable)), - e[24] = Convert( - e[25] = Constant((int)100), - typeof(Nullable)), - typeof(Nullable)))), - p[1 // (f__AnonymousType142> __f__AnonymousType142_Nullable_int____12662012) - ]), - p[3] = Parameter(typeof(IQueryRunner), "qr"), - p[4] = Parameter(typeof(IDataContext), "dctx"), - p[5] = Parameter(typeof(IDataReader), "rd"), - p[6] = Parameter(typeof(System.Linq.Expressions.Expression), "expr"), - p[7] = Parameter(typeof(object[]), "ps"), - p[8] = Parameter(typeof(object[]), "preamble")), - p[9] = Parameter(typeof(IQueryRunner), "qr"), - e[26] = Property( - p[9 // (IQueryRunner qr) - ], - typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("DataContext")), - p[2 // (IDataReader dr) - ], - e[27] = Property( - p[9 // (IQueryRunner qr) - ], - typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Expression")), - e[28] = Property( - p[9 // (IQueryRunner qr) - ], - typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Parameters")), - e[29] = Property( + typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "IsDBNull" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), + e[19] = Constant((int)0)), + e[20] = Constant(null, typeof(Nullable)), + e[21] = New(/*1 args*/ + typeof(Nullable).GetTypeInfo().DeclaredConstructors.ToArray()[0], + e[22] = Call( + p[0 // (SQLiteDataReader ldr) + ], + typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "GetInt32" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), + e[23] = Constant((int)0))), + typeof(Nullable)), + e[24] = Convert( + e[25] = Constant((int)100), + typeof(Nullable)), + typeof(Nullable)))), + p[1 // (f__AnonymousType142> __f__AnonymousType142_Nullable_int____12662012) + ]), + p[3] = Parameter(typeof(IQueryRunner), "qr"), + p[4] = Parameter(typeof(IDataContext), "dctx"), + p[5] = Parameter(typeof(IDataReader), "rd"), + p[6] = Parameter(typeof(System.Linq.Expressions.Expression), "expr"), + p[7] = Parameter(typeof(object[]), "ps"), + p[8] = Parameter(typeof(object[]), "preamble")), + p[9] = Parameter(typeof(IQueryRunner), "qr"), + e[26] = Property( + p[9 // (IQueryRunner qr) + ], + typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("DataContext")), + p[2 // (IDataReader dr) + ], + e[27] = Property( + p[9 // (IQueryRunner qr) + ], + typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Expression")), + e[28] = Property( + p[9 // (IQueryRunner qr) + ], + typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Parameters")), + e[29] = Property( + p[9 // (IQueryRunner qr) + ], + typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Preambles"))), p[9 // (IQueryRunner qr) ], - typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Preambles"))), - p[9 // (IQueryRunner qr) - ], - p[2 // (IDataReader dr) - ]); - - expr.PrintCSharp(); - - var fs = expr.CompileSys(); - fs.PrintIL(); - - var f = expr.CompileFast(true, CompilerFlags.Default); - f.PrintIL(); - } - - - public void Test_case_2_Full_ExecutionEngineException() - { - var p = new ParameterExpression[1]; // the parameter expressions - var e = new Expression[13]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda, Nullable>>( // $ - e[0] = Condition( - e[1] = Property( - p[0] = Parameter(typeof(Nullable), "p"), - typeof(Nullable).GetTypeInfo().GetDeclaredProperty("HasValue")), - e[2] = Switch( - e[3] = Convert( - p[0 // (Nullable p) - ], - typeof(Enum15)), - e[4] = Convert( - e[5] = Call( - null, - typeof(ConvertBuilder).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Single(x => !x.IsGenericMethod && x.Name == "ConvertDefault" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(object), typeof(Type) })), - e[6] = Convert( - e[3 // Convert of Enum15 + p[2 // (IDataReader dr) + ]); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + + var f = expr.CompileFast(true, CompilerFlags.Default); + f.PrintIL(); + } + + + public void Test_case_2_Full_ExecutionEngineException() + { + var p = new ParameterExpression[1]; // the parameter expressions + var e = new Expression[13]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda, Nullable>>( // $ + e[0] = Condition( + e[1] = Property( + p[0] = Parameter(typeof(Nullable), "p"), + typeof(Nullable).GetTypeInfo().GetDeclaredProperty("HasValue")), + e[2] = Switch( + e[3] = Convert( + p[0 // (Nullable p) ], - typeof(object)), - e[7] = Constant(typeof(Nullable))), + typeof(Enum15)), + e[4] = Convert( + e[5] = Call( + null, + typeof(ConvertBuilder).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Single(x => !x.IsGenericMethod && x.Name == "ConvertDefault" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(object), typeof(Type) })), + e[6] = Convert( + e[3 // Convert of Enum15 + ], + typeof(object)), + e[7] = Constant(typeof(Nullable))), + typeof(Nullable)), + SwitchCase( + e[8] = Constant((int)10, typeof(Nullable)), + e[9] = Constant(Enum15.AA)), + SwitchCase( + e[10] = Constant((int)20, typeof(Nullable)), + e[11] = Constant(Enum15.BB))), + e[12] = Constant(null, typeof(Nullable)), typeof(Nullable)), - SwitchCase( - e[8] = Constant((int)10, typeof(Nullable)), - e[9] = Constant(Enum15.AA)), - SwitchCase( - e[10] = Constant((int)20, typeof(Nullable)), - e[11] = Constant(Enum15.BB))), - e[12] = Constant(null, typeof(Nullable)), - typeof(Nullable)), - p[0 // (Nullable p) - ]); - - expr.PrintCSharp(); + p[0 // (Nullable p) + ]); + + expr.PrintCSharp(); #if LIGHT_EXPRESSION var sysExpr = expr.ToLambdaExpression(); @@ -278,48 +275,48 @@ public void Test_case_2_Full_ExecutionEngineException() Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString()); #endif - var fs = expr.CompileSys(); - fs.PrintIL(); - Asserts.AreEqual(10, fs(Enum15.AA)); - Asserts.AreEqual(20, fs(Enum15.BB)); - Asserts.Throws(() => - fs((Enum15)3)); - - var f = expr.CompileFast(true); - f.PrintIL(); - Asserts.AreEqual(10, f(Enum15.AA)); - Asserts.AreEqual(20, f(Enum15.BB)); - Asserts.Throws(() => - f((Enum15)3)); - } - - - public void Test_case_3_Full_NullReferenceException() - { - var p = new ParameterExpression[9]; // the parameter expressions - var e = new Expression[23]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda>>( - e[0] = Invoke( - e[1] = Lambda( // $ - typeof(Func>), - e[2] = Block( - typeof(IGrouping), - new[] { + var fs = expr.CompileSys(); + fs.PrintIL(); + Asserts.AreEqual(10, fs(Enum15.AA)); + Asserts.AreEqual(20, fs(Enum15.BB)); + Asserts.Throws(() => + fs((Enum15)3)); + + var f = expr.CompileFast(true); + f.PrintIL(); + Asserts.AreEqual(10, f(Enum15.AA)); + Asserts.AreEqual(20, f(Enum15.BB)); + Asserts.Throws(() => + f((Enum15)3)); + } + + + public void Test_case_3_Full_NullReferenceException() + { + var p = new ParameterExpression[9]; // the parameter expressions + var e = new Expression[23]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda>>( + e[0] = Invoke( + e[1] = Lambda( // $ + typeof(Func>), + e[2] = Block( + typeof(IGrouping), + new[] { p[0]=Parameter(typeof(SQLiteDataReader), "ldr") - }, - e[3] = MakeBinary(ExpressionType.Assign, - p[0 // (SQLiteDataReader ldr) - ], - e[4] = Convert( - p[1] = Parameter(typeof(IDataReader), "dr"), - typeof(SQLiteDataReader))), - e[5] = Call( - null, - typeof(GroupByBuilder.GroupByContext.GroupByHelper>).GetMethods(BindingFlags.NonPublic | BindingFlags.Static) - .Single(x => !x.IsGenericMethod && x.Name == "GetGrouping" && - x.GetParameters().Select(y => y.ParameterType) - .SequenceEqual(new[] { + }, + e[3] = MakeBinary(ExpressionType.Assign, + p[0 // (SQLiteDataReader ldr) + ], + e[4] = Convert( + p[1] = Parameter(typeof(IDataReader), "dr"), + typeof(SQLiteDataReader))), + e[5] = Call( + null, + typeof(GroupByBuilder.GroupByContext.GroupByHelper>).GetMethods(BindingFlags.NonPublic | BindingFlags.Static) + .Single(x => !x.IsGenericMethod && x.Name == "GetGrouping" && + x.GetParameters().Select(y => y.ParameterType) + .SequenceEqual(new[] { typeof(IQueryRunner), typeof(IDataContext), typeof(IDataReader), @@ -328,176 +325,176 @@ public void Test_case_3_Full_NullReferenceException() typeof(object[]), typeof(Func), typeof(Func>) })), - p[2] = Parameter(typeof(IQueryRunner), "qr"), - p[3] = Parameter(typeof(IDataContext), "dctx"), - p[4] = Parameter(typeof(IDataReader), "rd"), - e[6] = Constant(new List()), - p[5] = Parameter(typeof(System.Linq.Expressions.Expression), "expr"), - p[6] = Parameter(typeof(object[]), "ps"), - e[7] = Lambda( // $ - typeof(Func), - e[8] = Condition( - e[9] = Call( - p[0 // (SQLiteDataReader ldr) - ], - typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "IsDBNull" && - x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - e[10] = Constant((int)0)), - e[11] = Constant(false), - e[12] = Convert( - e[13] = Call( - null, - typeof(ConvertBuilder).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Single(x => !x.IsGenericMethod && x.Name == "ConvertDefault" && - x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(object), typeof(Type) })), - e[14] = Convert( - e[15] = Call( + p[2] = Parameter(typeof(IQueryRunner), "qr"), + p[3] = Parameter(typeof(IDataContext), "dctx"), + p[4] = Parameter(typeof(IDataReader), "rd"), + e[6] = Constant(new List()), + p[5] = Parameter(typeof(System.Linq.Expressions.Expression), "expr"), + p[6] = Parameter(typeof(object[]), "ps"), + e[7] = Lambda( // $ + typeof(Func), + e[8] = Condition( + e[9] = Call( p[0 // (SQLiteDataReader ldr) - ], - typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "GetInt64" && + ], + typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "IsDBNull" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - e[16] = Constant((int)0)), - typeof(object)), - e[17] = Constant(typeof(bool))), - typeof(bool)), - typeof(bool)), - p[2 // (IQueryRunner qr) - ], - p[3 // (IDataContext dctx) - ], - p[4 // (IDataReader rd) - ], - p[5 // (System.Linq.Expressions.Expression expr) - ], - p[6 // (object[] ps) - ]), - e[18] = Constant(null, typeof(Func>)))), - p[2 // (IQueryRunner qr) - ], - p[3 // (IDataContext dctx) - ], - p[4 // (IDataReader rd) - ], - p[5 // (System.Linq.Expressions.Expression expr) - ], - p[6 // (object[] ps) - ], - p[7] = Parameter(typeof(object[]), "preamble")), - p[8] = Parameter(typeof(IQueryRunner), "qr"), - e[19] = Property( - p[8 // (IQueryRunner qr) - ], - typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("DataContext")), - p[1 // (IDataReader dr) - ], - e[20] = Property( - p[8 // (IQueryRunner qr) - ], - typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Expression")), - e[21] = Property( - p[8 // (IQueryRunner qr) - ], - typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Parameters")), - e[22] = Property( - p[8 // (IQueryRunner qr) - ], - typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Preambles"))), - p[8 // (IQueryRunner qr) - ], - p[1 // (IDataReader dr) - ]); - - expr.PrintCSharp(); - var reExpr = expr.ToExpressionString(); - Asserts.Contains(")/*NOTE: Provide the non-default value", reExpr); // contains the constant - - var fs = expr.CompileSys(); - fs.PrintIL(); - - var f = expr.CompileFast(true); - f.PrintIL(); - - _ = f(new NullQueryRunner(), new SQLiteDataReader()); - } - - public class Customer - { - } - - class ParameterAccessor - { - } - - class ExpressionBuilder - { - public class GroupSubQuery { } - } - - class GroupByBuilder - { - internal class GroupByContext + e[10] = Constant((int)0)), + e[11] = Constant(false), + e[12] = Convert( + e[13] = Call( + null, + typeof(ConvertBuilder).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).Single(x => !x.IsGenericMethod && x.Name == "ConvertDefault" && + x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(object), typeof(Type) })), + e[14] = Convert( + e[15] = Call( + p[0 // (SQLiteDataReader ldr) + ], + typeof(IDataRecord).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "GetInt64" && + x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), + e[16] = Constant((int)0)), + typeof(object)), + e[17] = Constant(typeof(bool))), + typeof(bool)), + typeof(bool)), + p[2 // (IQueryRunner qr) + ], + p[3 // (IDataContext dctx) + ], + p[4 // (IDataReader rd) + ], + p[5 // (System.Linq.Expressions.Expression expr) + ], + p[6 // (object[] ps) + ]), + e[18] = Constant(null, typeof(Func>)))), + p[2 // (IQueryRunner qr) + ], + p[3 // (IDataContext dctx) + ], + p[4 // (IDataReader rd) + ], + p[5 // (System.Linq.Expressions.Expression expr) + ], + p[6 // (object[] ps) + ], + p[7] = Parameter(typeof(object[]), "preamble")), + p[8] = Parameter(typeof(IQueryRunner), "qr"), + e[19] = Property( + p[8 // (IQueryRunner qr) + ], + typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("DataContext")), + p[1 // (IDataReader dr) + ], + e[20] = Property( + p[8 // (IQueryRunner qr) + ], + typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Expression")), + e[21] = Property( + p[8 // (IQueryRunner qr) + ], + typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Parameters")), + e[22] = Property( + p[8 // (IQueryRunner qr) + ], + typeof(IQueryRunner).GetTypeInfo().GetDeclaredProperty("Preambles"))), + p[8 // (IQueryRunner qr) + ], + p[1 // (IDataReader dr) + ]); + + expr.PrintCSharp(); + var reExpr = expr.ToExpressionString(); + Asserts.Contains(")/*NOTE: Provide the non-default value", reExpr); // contains the constant + + var fs = expr.CompileSys(); + fs.PrintIL(); + + var f = expr.CompileFast(true); + f.PrintIL(); + + _ = f(new NullQueryRunner(), new SQLiteDataReader()); + } + + public class Customer { - internal class GroupByHelper - { - internal static IGrouping GetGrouping( - IQueryRunner runner, - IDataContext dataContext, - IDataReader dataReader, - List parameterAccessor, - System.Linq.Expressions.Expression expr, - object[] ps, - Func keyReader, - Func> itemReader) - { - var key = keyReader(runner, dataContext, dataReader, expr, ps); - return null; - } - } } - } + class ParameterAccessor + { + } - public void Test_case_4_simplified_InvalidCastException() - { - var hs = Constant(new Delegate[] { (Action)null }); - var expr = Lambda>( - Convert( - ArrayIndex(hs, Constant(0)), - typeof(SimpleDelegate)) - ); + class ExpressionBuilder + { + public class GroupSubQuery { } + } - expr.PrintCSharp(); + class GroupByBuilder + { + internal class GroupByContext + { + internal class GroupByHelper + { + internal static IGrouping GetGrouping( + IQueryRunner runner, + IDataContext dataContext, + IDataReader dataReader, + List parameterAccessor, + System.Linq.Expressions.Expression expr, + object[] ps, + Func keyReader, + Func> itemReader) + { + var key = keyReader(runner, dataContext, dataReader, expr, ps); + return null; + } + } + } + } - var fs = expr.CompileSys(); - fs.PrintIL(); - Asserts.IsNull(fs()); - var f = expr.CompileFast(true); - f.PrintIL(); - Asserts.IsNull(f()); - } + public void Test_case_4_simplified_InvalidCastException() + { + var hs = Constant(new Delegate[] { (Action)null }); + var expr = Lambda>( + Convert( + ArrayIndex(hs, Constant(0)), + typeof(SimpleDelegate)) + ); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + Asserts.IsNull(fs()); + + var f = expr.CompileFast(true); + f.PrintIL(); + Asserts.IsNull(f()); + } - public static void SimpleStringHandler(string s) { } + public static void SimpleStringHandler(string s) { } - public void Test_case_4_Full_InvalidCastException() - { - var p = new ParameterExpression[2]; // the parameter expressions - var e = new Expression[6]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda>( // $ - e[0] = Block( - typeof(SampleClass), - new[] { + public void Test_case_4_Full_InvalidCastException() + { + var p = new ParameterExpression[2]; // the parameter expressions + var e = new Expression[6]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda>( // $ + e[0] = Block( + typeof(SampleClass), + new[] { p[0]=Parameter(typeof(SampleClass)) - }, - e[1] = MakeBinary(ExpressionType.Assign, - p[0 // (SampleClass sampleclass__14492072) - ], - e[2] = New(/*2 args*/ - typeof(SampleClass).GetTypeInfo().DeclaredConstructors.ToArray()[0], - p[1] = Parameter(typeof(object)), - e[3] = Constant(new Delegate[] - { + }, + e[1] = MakeBinary(ExpressionType.Assign, + p[0 // (SampleClass sampleclass__14492072) + ], + e[2] = New(/*2 args*/ + typeof(SampleClass).GetTypeInfo().DeclaredConstructors.ToArray()[0], + p[1] = Parameter(typeof(object)), + e[3] = Constant(new Delegate[] + { (Func)(x => 42), (Func)((x, i) => new OtherClass()), (Action)(x => {}), @@ -517,625 +514,625 @@ public void Test_case_4_Full_InvalidCastException() // default(Func), (SimpleDelegate)HandleString, (Func)((x, s) => 43) - }) - ) - ), - e[4] = Invoke( - e[5] = Constant((Action)((SampleClass s) => - { - ((SimpleDelegate)s.Delegates[3]).Invoke("Hey!"); - })), - p[0 // (SampleClass SampleClass__14492072) - ]), - p[0 // (SampleClass SampleClass__14492072) - ]), - p[1 // (object object__42147750) - ]); - - expr.PrintCSharp(); - - var fs = expr.CompileSys(); - fs.PrintIL(); - var obj1 = new object(); - var s1 = fs(obj1); - Asserts.IsInstanceOf(s1); - - var f = expr.CompileFast(true); - f.PrintIL(); - var obj2 = new object(); - var s2 = f(obj2); - Asserts.IsInstanceOf(s2); - } - - - public void Test_287_Case2_SimpleDelegate_as_nested_lambda_in_TesCollection_test() - { - string gotS = null; - SimpleDelegate sd = s => gotS = s; - - var p = new ParameterExpression[9]; // the parameter expressions - var e = new Expression[63]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda>( // $ - e[0] = Block( - typeof(void), - new ParameterExpression[0], - e[1] = Call( - p[0] = Parameter(typeof(Dynamic.SampleClass)), - typeof(Dynamic.SampleClass).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "add_SimpleDelegateEvent" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(Dynamic.SimpleDelegate) })), - e[4] = Lambda(Invoke( - Constant(sd, typeof(SimpleDelegate)), - p[1] = Parameter(typeof(string), "_")), - p[1])) - ), - p[0 // (SampleClass sampleclass__26320983) - ]); - - expr.PrintCSharp(); - - var fs = expr.CompileSys(); - fs.PrintIL(); - var s = new Dynamic.SampleClass(); - fs(s); - s.InvokeSimpleDelegateEvent("1"); - Asserts.AreEqual("1", gotS); - - var fx = expr.CompileFast(true); - fx.PrintIL(); - s = new Dynamic.SampleClass(); - fx(s); - s.InvokeSimpleDelegateEvent("2"); - Asserts.AreEqual("2", gotS); - } - - - public void Test_287_Case1_ConvertTests_NullableParameterInOperatorConvert_VerificationException() - { - var p = new ParameterExpression[1]; // the parameter expressions - var e = new Expression[2]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda>( - e[0] = Convert( - e[1] = New(/*1 args*/ - typeof(System.Nullable).GetTypeInfo().DeclaredConstructors.ToArray()[0], - p[0] = Parameter(typeof(System.Decimal), "p")), - typeof(CustomMoneyType), - typeof(CustomMoneyType).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "op_Explicit" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(System.Nullable) }))), - p[0 // ([struct] System.Decimal p) - ]); - - expr.PrintCSharp(); - - var fs = expr.CompileSys(); - fs.PrintIL(); - Asserts.AreEqual(new CustomMoneyType { Amount = 1.11m }, fs(1.11m)); - - var fx = expr.CompileFast(true); - fx.PrintIL(); - Asserts.AreEqual(new CustomMoneyType { Amount = 1.11m }, fx(1.11m)); - } - - private struct CustomMoneyType - { - public decimal? Amount; - public static explicit operator CustomMoneyType(decimal? amount) => - new CustomMoneyType() { Amount = amount }; - } - - - public void Test_283_Case6_MappingSchemaTests_CultureInfo_VerificationException() - { - var ci = (CultureInfo)new CultureInfo("ru-RU", false).Clone(); - - ci.DateTimeFormat.FullDateTimePattern = "dd.MM.yyyy HH:mm:ss"; - ci.DateTimeFormat.LongDatePattern = "dd.MM.yyyy"; - ci.DateTimeFormat.ShortDatePattern = "dd.MM.yyyy"; - ci.DateTimeFormat.LongTimePattern = "HH:mm:ss"; - ci.DateTimeFormat.ShortTimePattern = "HH:mm:ss"; - - var x = new c__DisplayClass41_0(); - x.info = ci; - - var p = new ParameterExpression[1]; // the parameter expressions - var e = new Expression[4]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda>( // $ - e[0] = Call( - p[0] = Parameter(typeof(System.DateTime), "v"), - typeof(System.DateTime).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "ToString" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(System.IFormatProvider) })), - e[1] = Property( - e[2] = Field( - e[3] = Constant(x, typeof(c__DisplayClass41_0)), - typeof(c__DisplayClass41_0).GetTypeInfo().GetDeclaredField("info")), - typeof(CultureInfo).GetTypeInfo().GetDeclaredProperty("DateTimeFormat"))), - p[0 // ([struct] System.DateTime v) - ]); - - expr.PrintCSharp(); - - var fs = expr.CompileSys(); - fs.PrintIL(); - Asserts.AreEqual("20.01.2012 16:30:40", fs(new DateTime(2012, 1, 20, 16, 30, 40))); - - var fx = expr.CompileFast(true); - fx.PrintIL(); - Asserts.AreEqual("20.01.2012 16:30:40", fx(new DateTime(2012, 1, 20, 16, 30, 40))); - } - - class c__DisplayClass41_0 - { - public System.Globalization.CultureInfo info; - } - - - public void Test_283_Case5_ConvertTests_NullableIntToNullableEnum_NullReferenceException() - { - var p = new ParameterExpression[1]; // the parameter expressions - var e = new Expression[5]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda, System.Nullable>>( // $ - e[0] = Condition( - e[1] = Property( - p[0] = Parameter(typeof(System.Nullable), "p"), - typeof(System.Nullable).GetTypeInfo().GetDeclaredProperty("HasValue")), - e[2] = Convert( - e[3] = Property( - p[0 // ([struct] System.Nullable p) - ], - typeof(System.Nullable).GetTypeInfo().GetDeclaredProperty("Value")), - typeof(System.Nullable)), - e[4] = Constant(null, typeof(System.Nullable)), - typeof(System.Nullable)), - p[0 // ([struct] System.Nullable p) - ]); - - expr.PrintCSharp(); - - var fs = expr.CompileSys(); - fs.PrintIL(); - Asserts.IsNull(fs(null)); - - var fx = expr.CompileFast(true); - fx.PrintIL(); - Asserts.IsNull(fx(null)); - } - - public enum Enum1 { X } - - - public void Test_283_Case4_SecurityVerificationException() - { - var p = new ParameterExpression[1]; // the parameter expressions - var e = new Expression[1]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda>( // $ - e[0] = Call( - p[0] = Parameter(typeof(int), "p"), - typeof(int).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "ToString" && x.GetParameters().Length == 0)), - p[0 // (int p) - ]); - - var fs = expr.CompileSys(); - fs.PrintIL(); - Asserts.AreEqual("1", fs(1)); - - var fx = expr.CompileFast(true); - fx.PrintIL(); - Asserts.AreEqual("1", fx(1)); - } - - - public void Test_283_Case3_SecurityVerificationException() - { - var p = new ParameterExpression[2]; // the parameter expressions - var e = new Expression[2]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda>( - e[0] = Property( - e[1] = Call( - p[0] = Parameter(typeof(SampleClass), "s"), - typeof(SampleClass).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "GetOther" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - p[1] = Parameter(typeof(int), "i")), - typeof(OtherClass).GetTypeInfo().GetDeclaredProperty("OtherStrProp")), - p[0 // (SampleClass s) - ], - p[1 // (int i) - ]); - - expr.PrintCSharp(); - - var concrete = new SampleClass { Id = 1, Value = 33 }; - - var fs = expr.CompileSys(); - fs.PrintIL(); - Asserts.AreEqual("OtherStrValue22", fs(concrete, 22)); - - var fx = expr.CompileFast(true); - fx.PrintIL(); - Asserts.AreEqual("OtherStrValue22", fx(concrete, 22)); - } - - - public void Test_283_Case2_NullRefException() - { - var p = new ParameterExpression[3]; // the parameter expressions - var e = new Expression[23]; // the unique expressions - var l = new LabelTarget[0]; // the labels - var expr = Lambda>( // $ - e[0] = Convert( - e[1] = Call( - null, - typeof(ConvertTo).GetMethods().Where(x => x.IsGenericMethod && x.Name == "From" && x.GetGenericArguments().Length == 1) - .Select(x => x.IsGenericMethodDefinition ? x.MakeGenericMethod(typeof(System.Enum)) : x) - .Single(x => x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(System.Enum) })), - e[2] = Convert( - e[3] = Field( - e[4] = Convert( - e[5] = Property( - e[6] = Convert( - e[7] = Property( - e[8] = Convert( - e[9] = Property( - e[10] = Convert( - e[11] = Call( - e[12] = Property( - e[13] = Convert( - e[14] = Property( - e[15] = Convert( - e[16] = Property( - e[17] = Convert( - e[18] = Call( - e[19] = Property( - e[20] = Convert( - p[0] = Parameter(typeof(System.Linq.Expressions.Expression), "expr"), - typeof(System.Linq.Expressions.MethodCallExpression)), - typeof(System.Linq.Expressions.MethodCallExpression).GetTypeInfo().GetDeclaredProperty("Arguments")), - typeof(System.Collections.ObjectModel.ReadOnlyCollection).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "get_Item" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - e[21] = Constant((int)1)), - typeof(System.Linq.Expressions.UnaryExpression)), - typeof(System.Linq.Expressions.UnaryExpression).GetTypeInfo().GetDeclaredProperty("Operand")), - typeof(System.Linq.Expressions.LambdaExpression)), - typeof(System.Linq.Expressions.LambdaExpression).GetTypeInfo().GetDeclaredProperty("Body")), - typeof(System.Linq.Expressions.MethodCallExpression)), - typeof(System.Linq.Expressions.MethodCallExpression).GetTypeInfo().GetDeclaredProperty("Arguments")), - typeof(System.Collections.ObjectModel.ReadOnlyCollection).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "get_Item" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), - e[22] = Constant((int)0)), - typeof(System.Linq.Expressions.UnaryExpression)), - typeof(System.Linq.Expressions.UnaryExpression).GetTypeInfo().GetDeclaredProperty("Operand")), - typeof(System.Linq.Expressions.MemberExpression)), - typeof(System.Linq.Expressions.MemberExpression).GetTypeInfo().GetDeclaredProperty("Expression")), - typeof(System.Linq.Expressions.ConstantExpression)), - typeof(System.Linq.Expressions.ConstantExpression).GetTypeInfo().GetDeclaredProperty("Value")), - typeof(c__DisplayClass6_0)), - typeof(c__DisplayClass6_0).GetTypeInfo().GetDeclaredField("flag")), - typeof(System.Enum))), - typeof(object)), - p[0 // (System.Linq.Expressions.Expression expr) - ], - p[1] = Parameter(typeof(IDataContext), "dctx"), - p[2] = Parameter(typeof(object[]), "ps")); - - expr.PrintCSharp(); - /* - (Func)( - Expression expr, - Issue274_Failing_Expressions_in_Linq2DB.IDataContext dctx, - object[] ps) => - ((object)Issue274_Failing_Expressions_in_Linq2DB.ConvertTo.From(((Enum)((Issue274_Failing_Expressions_in_Linq2DB.c__DisplayClass6_0)((ConstantExpression)((MemberExpression)((UnaryExpression)((MethodCallExpression)((LambdaExpression)((UnaryExpression)((MethodCallExpression)expr).Arguments.Item).Operand).Body).Arguments.Item).Operand).Expression).Value).flag))); - */ - - var fs = expr.CompileSys(); - fs.PrintIL(); - - var f = expr.CompileFast(true); - f.PrintIL(); - } - - - public void Test_283_Case2_Minimal_NullRefException() - { - var p = Parameter(typeof(object), "o"); - var expr = Lambda>( - Convert( - Convert( - Field( - Convert(p, typeof(c__DisplayClass6_0)), - typeof(c__DisplayClass6_0).GetField(nameof(c__DisplayClass6_0.flag))), - typeof(System.Enum)), - typeof(object)), - p); + }) + ) + ), + e[4] = Invoke( + e[5] = Constant((Action)((SampleClass s) => + { + ((SimpleDelegate)s.Delegates[3]).Invoke("Hey!"); + })), + p[0 // (SampleClass SampleClass__14492072) + ]), + p[0 // (SampleClass SampleClass__14492072) + ]), + p[1 // (object object__42147750) + ]); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + var obj1 = new object(); + var s1 = fs(obj1); + Asserts.IsInstanceOf(s1); + + var f = expr.CompileFast(true); + f.PrintIL(); + var obj2 = new object(); + var s2 = f(obj2); + Asserts.IsInstanceOf(s2); + } - expr.PrintCSharp(); - var fs = expr.CompileSys(); - fs.PrintIL(); + public void Test_287_Case2_SimpleDelegate_as_nested_lambda_in_TesCollection_test() + { + string gotS = null; + SimpleDelegate sd = s => gotS = s; + + var p = new ParameterExpression[9]; // the parameter expressions + var e = new Expression[63]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda>( // $ + e[0] = Block( + typeof(void), + new ParameterExpression[0], + e[1] = Call( + p[0] = Parameter(typeof(Dynamic.SampleClass)), + typeof(Dynamic.SampleClass).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "add_SimpleDelegateEvent" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(Dynamic.SimpleDelegate) })), + e[4] = Lambda(Invoke( + Constant(sd, typeof(SimpleDelegate)), + p[1] = Parameter(typeof(string), "_")), + p[1])) + ), + p[0 // (SampleClass sampleclass__26320983) + ]); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + var s = new Dynamic.SampleClass(); + fs(s); + s.InvokeSimpleDelegateEvent("1"); + Asserts.AreEqual("1", gotS); + + var fx = expr.CompileFast(true); + fx.PrintIL(); + s = new Dynamic.SampleClass(); + fx(s); + s.InvokeSimpleDelegateEvent("2"); + Asserts.AreEqual("2", gotS); + } - var f = expr.CompileFast(true); - f.PrintIL(); - var flags = f(new c__DisplayClass6_0()); - Asserts.AreEqual(FlagsEnum.All, (FlagsEnum)flags); - } - [Flags] - public enum FlagsEnum - { - None = 0, + public void Test_287_Case1_ConvertTests_NullableParameterInOperatorConvert_VerificationException() + { + var p = new ParameterExpression[1]; // the parameter expressions + var e = new Expression[2]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda>( + e[0] = Convert( + e[1] = New(/*1 args*/ + typeof(System.Nullable).GetTypeInfo().DeclaredConstructors.ToArray()[0], + p[0] = Parameter(typeof(System.Decimal), "p")), + typeof(CustomMoneyType), + typeof(CustomMoneyType).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "op_Explicit" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(System.Nullable) }))), + p[0 // ([struct] System.Decimal p) + ]); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + Asserts.AreEqual(new CustomMoneyType { Amount = 1.11m }, fs(1.11m)); + + var fx = expr.CompileFast(true); + fx.PrintIL(); + Asserts.AreEqual(new CustomMoneyType { Amount = 1.11m }, fx(1.11m)); + } - Flag1 = 0x1, - Flag2 = 0x2, - Flag3 = 0x4, + private struct CustomMoneyType + { + public decimal? Amount; + public static explicit operator CustomMoneyType(decimal? amount) => + new CustomMoneyType() { Amount = amount }; + } - All = Flag1 | Flag2 | Flag3 - } - class c__DisplayClass6_0 - { - public FlagsEnum flag = FlagsEnum.All; - } + public void Test_283_Case6_MappingSchemaTests_CultureInfo_VerificationException() + { + var ci = (CultureInfo)new CultureInfo("ru-RU", false).Clone(); + + ci.DateTimeFormat.FullDateTimePattern = "dd.MM.yyyy HH:mm:ss"; + ci.DateTimeFormat.LongDatePattern = "dd.MM.yyyy"; + ci.DateTimeFormat.ShortDatePattern = "dd.MM.yyyy"; + ci.DateTimeFormat.LongTimePattern = "HH:mm:ss"; + ci.DateTimeFormat.ShortTimePattern = "HH:mm:ss"; + + var x = new c__DisplayClass41_0(); + x.info = ci; + + var p = new ParameterExpression[1]; // the parameter expressions + var e = new Expression[4]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda>( // $ + e[0] = Call( + p[0] = Parameter(typeof(System.DateTime), "v"), + typeof(System.DateTime).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "ToString" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(System.IFormatProvider) })), + e[1] = Property( + e[2] = Field( + e[3] = Constant(x, typeof(c__DisplayClass41_0)), + typeof(c__DisplayClass41_0).GetTypeInfo().GetDeclaredField("info")), + typeof(CultureInfo).GetTypeInfo().GetDeclaredProperty("DateTimeFormat"))), + p[0 // ([struct] System.DateTime v) + ]); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + Asserts.AreEqual("20.01.2012 16:30:40", fs(new DateTime(2012, 1, 20, 16, 30, 40))); + + var fx = expr.CompileFast(true); + fx.PrintIL(); + Asserts.AreEqual("20.01.2012 16:30:40", fx(new DateTime(2012, 1, 20, 16, 30, 40))); + } - public delegate void SimpleDelegate(string input); + class c__DisplayClass41_0 + { + public System.Globalization.CultureInfo info; + } - public class Dynamic - { - public delegate void SimpleDelegate(string input); - public class SampleClass + public void Test_283_Case5_ConvertTests_NullableIntToNullableEnum_NullReferenceException() { - public event SimpleDelegate SimpleDelegateEvent; + var p = new ParameterExpression[1]; // the parameter expressions + var e = new Expression[5]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda, System.Nullable>>( // $ + e[0] = Condition( + e[1] = Property( + p[0] = Parameter(typeof(System.Nullable), "p"), + typeof(System.Nullable).GetTypeInfo().GetDeclaredProperty("HasValue")), + e[2] = Convert( + e[3] = Property( + p[0 // ([struct] System.Nullable p) + ], + typeof(System.Nullable).GetTypeInfo().GetDeclaredProperty("Value")), + typeof(System.Nullable)), + e[4] = Constant(null, typeof(System.Nullable)), + typeof(System.Nullable)), + p[0 // ([struct] System.Nullable p) + ]); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + Asserts.IsNull(fs(null)); + + var fx = expr.CompileFast(true); + fx.PrintIL(); + Asserts.IsNull(fx(null)); + } + + public enum Enum1 { X } - public void InvokeSimpleDelegateEvent(string s) => SimpleDelegateEvent?.Invoke(s); + + public void Test_283_Case4_SecurityVerificationException() + { + var p = new ParameterExpression[1]; // the parameter expressions + var e = new Expression[1]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda>( // $ + e[0] = Call( + p[0] = Parameter(typeof(int), "p"), + typeof(int).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "ToString" && x.GetParameters().Length == 0)), + p[0 // (int p) + ]); + + var fs = expr.CompileSys(); + fs.PrintIL(); + Asserts.AreEqual("1", fs(1)); + + var fx = expr.CompileFast(true); + fx.PrintIL(); + Asserts.AreEqual("1", fx(1)); } - } - public static void HandleString(string s) - { - } + public void Test_283_Case3_SecurityVerificationException() + { + var p = new ParameterExpression[2]; // the parameter expressions + var e = new Expression[2]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda>( + e[0] = Property( + e[1] = Call( + p[0] = Parameter(typeof(SampleClass), "s"), + typeof(SampleClass).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "GetOther" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), + p[1] = Parameter(typeof(int), "i")), + typeof(OtherClass).GetTypeInfo().GetDeclaredProperty("OtherStrProp")), + p[0 // (SampleClass s) + ], + p[1 // (int i) + ]); - class TypeWrapper - { - public object instance_ { get; } - } + expr.PrintCSharp(); + var concrete = new SampleClass { Id = 1, Value = 33 }; - class SampleClass : TypeWrapper - { - public int Id { get; set; } - public int Value { get; set; } - public object Instance; - public Delegate[] Delegates; + var fs = expr.CompileSys(); + fs.PrintIL(); + Asserts.AreEqual("OtherStrValue22", fs(concrete, 22)); - public OtherClass GetOther(int idx) => new OtherClass { OtherStrProp = "OtherStrValue" + idx }; + var fx = expr.CompileFast(true); + fx.PrintIL(); + Asserts.AreEqual("OtherStrValue22", fx(concrete, 22)); + } - public OtherClass GetOtherAnother(int idx) => new OtherClass { OtherStrProp = "OtherAnotherStrValue" + idx }; - private SimpleDelegate _SimpleDelegateEvent; - public event SimpleDelegate SimpleDelegateEvent + public void Test_283_Case2_NullRefException() { - add => _SimpleDelegateEvent = (SimpleDelegate)Delegate.Combine(_SimpleDelegateEvent, value); - remove => _SimpleDelegateEvent = (SimpleDelegate)Delegate.Remove(_SimpleDelegateEvent, value); + var p = new ParameterExpression[3]; // the parameter expressions + var e = new Expression[23]; // the unique expressions + var l = new LabelTarget[0]; // the labels + var expr = Lambda>( // $ + e[0] = Convert( + e[1] = Call( + null, + typeof(ConvertTo).GetMethods().Where(x => x.IsGenericMethod && x.Name == "From" && x.GetGenericArguments().Length == 1) + .Select(x => x.IsGenericMethodDefinition ? x.MakeGenericMethod(typeof(System.Enum)) : x) + .Single(x => x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(System.Enum) })), + e[2] = Convert( + e[3] = Field( + e[4] = Convert( + e[5] = Property( + e[6] = Convert( + e[7] = Property( + e[8] = Convert( + e[9] = Property( + e[10] = Convert( + e[11] = Call( + e[12] = Property( + e[13] = Convert( + e[14] = Property( + e[15] = Convert( + e[16] = Property( + e[17] = Convert( + e[18] = Call( + e[19] = Property( + e[20] = Convert( + p[0] = Parameter(typeof(System.Linq.Expressions.Expression), "expr"), + typeof(System.Linq.Expressions.MethodCallExpression)), + typeof(System.Linq.Expressions.MethodCallExpression).GetTypeInfo().GetDeclaredProperty("Arguments")), + typeof(System.Collections.ObjectModel.ReadOnlyCollection).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "get_Item" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), + e[21] = Constant((int)1)), + typeof(System.Linq.Expressions.UnaryExpression)), + typeof(System.Linq.Expressions.UnaryExpression).GetTypeInfo().GetDeclaredProperty("Operand")), + typeof(System.Linq.Expressions.LambdaExpression)), + typeof(System.Linq.Expressions.LambdaExpression).GetTypeInfo().GetDeclaredProperty("Body")), + typeof(System.Linq.Expressions.MethodCallExpression)), + typeof(System.Linq.Expressions.MethodCallExpression).GetTypeInfo().GetDeclaredProperty("Arguments")), + typeof(System.Collections.ObjectModel.ReadOnlyCollection).GetMethods().Single(x => !x.IsGenericMethod && x.Name == "get_Item" && x.GetParameters().Select(y => y.ParameterType).SequenceEqual(new[] { typeof(int) })), + e[22] = Constant((int)0)), + typeof(System.Linq.Expressions.UnaryExpression)), + typeof(System.Linq.Expressions.UnaryExpression).GetTypeInfo().GetDeclaredProperty("Operand")), + typeof(System.Linq.Expressions.MemberExpression)), + typeof(System.Linq.Expressions.MemberExpression).GetTypeInfo().GetDeclaredProperty("Expression")), + typeof(System.Linq.Expressions.ConstantExpression)), + typeof(System.Linq.Expressions.ConstantExpression).GetTypeInfo().GetDeclaredProperty("Value")), + typeof(c__DisplayClass6_0)), + typeof(c__DisplayClass6_0).GetTypeInfo().GetDeclaredField("flag")), + typeof(System.Enum))), + typeof(object)), + p[0 // (System.Linq.Expressions.Expression expr) + ], + p[1] = Parameter(typeof(IDataContext), "dctx"), + p[2] = Parameter(typeof(object[]), "ps")); + + expr.PrintCSharp(); + /* + (Func)( + Expression expr, + Issue274_Failing_Expressions_in_Linq2DB.IDataContext dctx, + object[] ps) => + ((object)Issue274_Failing_Expressions_in_Linq2DB.ConvertTo.From(((Enum)((Issue274_Failing_Expressions_in_Linq2DB.c__DisplayClass6_0)((ConstantExpression)((MemberExpression)((UnaryExpression)((MethodCallExpression)((LambdaExpression)((UnaryExpression)((MethodCallExpression)expr).Arguments.Item).Operand).Body).Arguments.Item).Operand).Expression).Value).flag))); + */ + + var fs = expr.CompileSys(); + fs.PrintIL(); + + var f = expr.CompileFast(true); + f.PrintIL(); } - public SampleClass(object instance = null, Delegate[] delegates = null) + public void Test_283_Case2_Minimal_NullRefException() { - Instance = instance; - Delegates = delegates; + var p = Parameter(typeof(object), "o"); + var expr = Lambda>( + Convert( + Convert( + Field( + Convert(p, typeof(c__DisplayClass6_0)), + typeof(c__DisplayClass6_0).GetField(nameof(c__DisplayClass6_0.flag))), + typeof(System.Enum)), + typeof(object)), + p); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + + var f = expr.CompileFast(true); + f.PrintIL(); + var flags = f(new c__DisplayClass6_0()); + Asserts.AreEqual(FlagsEnum.All, (FlagsEnum)flags); } - } - class OtherClass - { - public string OtherStrProp { get; set; } - } - - enum RegularEnum1 { } - enum RegularEnum2 { } - - enum Enum15 - { - AA, - BB, - } - - static class ConvertBuilder - { - internal static object ConvertDefault(object value, Type conversionType) + + [Flags] + public enum FlagsEnum { - try - { - return System.Convert.ChangeType(value, conversionType, System.Threading.Thread.CurrentThread.CurrentCulture); - } - catch (Exception ex) - { - throw new InvalidOperationException($"Cannot convert value '{value}' to type '{conversionType.FullName}'", ex); - } + None = 0, + + Flag1 = 0x1, + Flag2 = 0x2, + Flag3 = 0x4, + + All = Flag1 | Flag2 | Flag3 } - } - public static class ConvertTo - { - public static TTo From(TFrom obj) + class c__DisplayClass6_0 { - return (TTo)System.Convert.ChangeType(obj, typeof(TTo), System.Threading.Thread.CurrentThread.CurrentCulture); + public FlagsEnum flag = FlagsEnum.All; } - } - public static class Convert - { + public delegate void SimpleDelegate(string input); - } + public class Dynamic + { + public delegate void SimpleDelegate(string input); - interface IDataContext { } + public class SampleClass + { + public event SimpleDelegate SimpleDelegateEvent; - interface IQueryRunner - { - IDataContext DataContext { get; set; } - System.Linq.Expressions.Expression Expression { get; set; } - object[] Parameters { get; set; } - object[] Preambles { get; set; } - } + public void InvokeSimpleDelegateEvent(string s) => SimpleDelegateEvent?.Invoke(s); + } + } - class NullQueryRunner : IQueryRunner - { - public IDataContext DataContext { get; set; } - public System.Linq.Expressions.Expression Expression { get; set; } - public object[] Parameters { get; set; } - public object[] Preambles { get; set; } - } + public static void HandleString(string s) + { - class f__AnonymousType142 - { - public T Value; - public f__AnonymousType142(T value) => Value = value; - } + } - class SQLiteDataReader : IDataReader - { - public object this[int i] => throw new NotImplementedException(); + class TypeWrapper + { + public object instance_ { get; } + } - public object this[string name] => throw new NotImplementedException(); - public int Depth => throw new NotImplementedException(); + class SampleClass : TypeWrapper + { + public int Id { get; set; } + public int Value { get; set; } + public object Instance; + public Delegate[] Delegates; - public bool IsClosed => throw new NotImplementedException(); + public OtherClass GetOther(int idx) => new OtherClass { OtherStrProp = "OtherStrValue" + idx }; - public int RecordsAffected => throw new NotImplementedException(); + public OtherClass GetOtherAnother(int idx) => new OtherClass { OtherStrProp = "OtherAnotherStrValue" + idx }; - public int FieldCount => throw new NotImplementedException(); + private SimpleDelegate _SimpleDelegateEvent; + public event SimpleDelegate SimpleDelegateEvent + { + add => _SimpleDelegateEvent = (SimpleDelegate)Delegate.Combine(_SimpleDelegateEvent, value); + remove => _SimpleDelegateEvent = (SimpleDelegate)Delegate.Remove(_SimpleDelegateEvent, value); + } - public void Close() - { - throw new NotImplementedException(); - } - public void Dispose() - { - throw new NotImplementedException(); + public SampleClass(object instance = null, Delegate[] delegates = null) + { + Instance = instance; + Delegates = delegates; + } } - - public bool GetBoolean(int i) + class OtherClass { - throw new NotImplementedException(); + public string OtherStrProp { get; set; } } - public byte GetByte(int i) - { - throw new NotImplementedException(); - } + enum RegularEnum1 { } + enum RegularEnum2 { } - public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + enum Enum15 { - throw new NotImplementedException(); + AA, + BB, } - public char GetChar(int i) + static class ConvertBuilder { - throw new NotImplementedException(); + internal static object ConvertDefault(object value, Type conversionType) + { + try + { + return System.Convert.ChangeType(value, conversionType, System.Threading.Thread.CurrentThread.CurrentCulture); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Cannot convert value '{value}' to type '{conversionType.FullName}'", ex); + } + } } - public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + public static class ConvertTo { - throw new NotImplementedException(); + public static TTo From(TFrom obj) + { + return (TTo)System.Convert.ChangeType(obj, typeof(TTo), System.Threading.Thread.CurrentThread.CurrentCulture); + } } - public IDataReader GetData(int i) + public static class Convert { - throw new NotImplementedException(); - } - public string GetDataTypeName(int i) - { - throw new NotImplementedException(); } - public DateTime GetDateTime(int i) - { - throw new NotImplementedException(); - } + interface IDataContext { } - public decimal GetDecimal(int i) + interface IQueryRunner { - throw new NotImplementedException(); + IDataContext DataContext { get; set; } + System.Linq.Expressions.Expression Expression { get; set; } + object[] Parameters { get; set; } + object[] Preambles { get; set; } } - public double GetDouble(int i) + class NullQueryRunner : IQueryRunner { - throw new NotImplementedException(); + public IDataContext DataContext { get; set; } + public System.Linq.Expressions.Expression Expression { get; set; } + public object[] Parameters { get; set; } + public object[] Preambles { get; set; } } - public Type GetFieldType(int i) + class f__AnonymousType142 { - throw new NotImplementedException(); + public T Value; + public f__AnonymousType142(T value) => Value = value; } - public float GetFloat(int i) + class SQLiteDataReader : IDataReader { - throw new NotImplementedException(); - } + public object this[int i] => throw new NotImplementedException(); - public Guid GetGuid(int i) - { - throw new NotImplementedException(); - } + public object this[string name] => throw new NotImplementedException(); - public short GetInt16(int i) - { - throw new NotImplementedException(); - } + public int Depth => throw new NotImplementedException(); - public int GetInt32(int i) - { - throw new NotImplementedException(); - } + public bool IsClosed => throw new NotImplementedException(); - public long GetInt64(int i) - { - return (long)i; - } + public int RecordsAffected => throw new NotImplementedException(); - public string GetName(int i) - { - throw new NotImplementedException(); - } + public int FieldCount => throw new NotImplementedException(); - public int GetOrdinal(string name) - { - throw new NotImplementedException(); - } + public void Close() + { + throw new NotImplementedException(); + } - public DataTable GetSchemaTable() - { - throw new NotImplementedException(); - } + public void Dispose() + { + throw new NotImplementedException(); + } - public string GetString(int i) - { - throw new NotImplementedException(); - } + public bool GetBoolean(int i) + { + throw new NotImplementedException(); + } - public object GetValue(int i) - { - throw new NotImplementedException(); - } + public byte GetByte(int i) + { + throw new NotImplementedException(); + } - public int GetValues(object[] values) - { - throw new NotImplementedException(); - } + public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + throw new NotImplementedException(); + } - public bool IsDBNull(int i) - { - return false; - } + public char GetChar(int i) + { + throw new NotImplementedException(); + } - public bool NextResult() - { - throw new NotImplementedException(); - } + public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) + { + throw new NotImplementedException(); + } - public bool Read() - { - throw new NotImplementedException(); + public IDataReader GetData(int i) + { + throw new NotImplementedException(); + } + + public string GetDataTypeName(int i) + { + throw new NotImplementedException(); + } + + public DateTime GetDateTime(int i) + { + throw new NotImplementedException(); + } + + public decimal GetDecimal(int i) + { + throw new NotImplementedException(); + } + + public double GetDouble(int i) + { + throw new NotImplementedException(); + } + + public Type GetFieldType(int i) + { + throw new NotImplementedException(); + } + + public float GetFloat(int i) + { + throw new NotImplementedException(); + } + + public Guid GetGuid(int i) + { + throw new NotImplementedException(); + } + + public short GetInt16(int i) + { + throw new NotImplementedException(); + } + + public int GetInt32(int i) + { + throw new NotImplementedException(); + } + + public long GetInt64(int i) + { + return (long)i; + } + + public string GetName(int i) + { + throw new NotImplementedException(); + } + + public int GetOrdinal(string name) + { + throw new NotImplementedException(); + } + + public DataTable GetSchemaTable() + { + throw new NotImplementedException(); + } + + public string GetString(int i) + { + throw new NotImplementedException(); + } + + public object GetValue(int i) + { + throw new NotImplementedException(); + } + + public int GetValues(object[] values) + { + throw new NotImplementedException(); + } + + public bool IsDBNull(int i) + { + return false; + } + + public bool NextResult() + { + throw new NotImplementedException(); + } + + public bool Read() + { + throw new NotImplementedException(); + } } - } } \ No newline at end of file diff --git a/test/FastExpressionCompiler.IssueTests/Issue281_Index_Out_of_Range.cs b/test/FastExpressionCompiler.IssueTests/Issue281_Index_Out_of_Range.cs index ab21a730..2efc45b3 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue281_Index_Out_of_Range.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue281_Index_Out_of_Range.cs @@ -47,7 +47,7 @@ public void Index_Out_of_Range() var m = new List { "a" }; Asserts.AreEqual("a", fs(m, 0)); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, diff --git a/test/FastExpressionCompiler.IssueTests/Issue316_in_parameter.cs b/test/FastExpressionCompiler.IssueTests/Issue316_in_parameter.cs index 50606699..5c68143d 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue316_in_parameter.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue316_in_parameter.cs @@ -60,16 +60,16 @@ public void Test_constructor_in_struct_parameter_constant() var position = new TextPosition { Position = 42 }; var program = Throw(New(typeof(ParseException).GetConstructors()[0], Constant("314"), Constant(position))); - var expr = Expression.Lambda(program); + var expr = Lambda(program); expr.PrintCSharp(s => s.Replace(GetType().Name + ".", "")); - var fSys = expr.CompileSys(); - fSys.PrintIL("sys"); - Asserts.Throws(() => fSys()); + var fs = expr.CompileSys(); + fs.PrintIL("sys"); + Asserts.Throws(() => fs()); - var fFast = expr.CompileFast(); - fFast.PrintIL("fast"); - Asserts.Throws(() => fFast()); + var ff = expr.CompileFast(); + ff.PrintIL("fast"); + Asserts.Throws(() => ff()); } #if LIGHT_EXPRESSION diff --git a/test/FastExpressionCompiler.IssueTests/Issue341_Equality_comparison_between_nullable_and_null_inside_Any_produces_incorrect_compiled_expression.cs b/test/FastExpressionCompiler.IssueTests/Issue341_Equality_comparison_between_nullable_and_null_inside_Any_produces_incorrect_compiled_expression.cs index 139cbb1c..1745e4a1 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue341_Equality_comparison_between_nullable_and_null_inside_Any_produces_incorrect_compiled_expression.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue341_Equality_comparison_between_nullable_and_null_inside_Any_produces_incorrect_compiled_expression.cs @@ -15,6 +15,8 @@ public class Issue341_Equality_comparison_between_nullable_and_null_inside_Any_p { public int Run() { + Nullable_decimal_member_not_equal_to_null_inside_predicate(); + foreach (var (a, op, b, expected) in Data) Nullable_decimal_parameters_comparison_cases(a, op, b, expected); @@ -29,7 +31,6 @@ public int Run() Null_not_equal_to_nullable_decimal(); Nullable_decimal_equal_to_null(); Nullable_decimal_member_not_equal_to_null(); - Nullable_decimal_member_not_equal_to_null_inside_predicate(); return Data.Length * 2 + 9; } @@ -307,13 +308,9 @@ public void Nullable_decimal_member_not_equal_to_null_inside_predicate() var compiledSys = expression.CompileSys(); compiledSys.PrintIL("sys"); - var compiledFast = expression.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); - Asserts.IsNotNull(compiledFast); - - compiledFast.PrintIL("fast"); - - if (compiledFast.TryGetDebugClosureNestedLambdaOrConstant(out var item) && item is Delegate d) - d.PrintIL("predicate"); + var f = expression.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); + Asserts.IsNotNull(f); + f.PrintIL("fast"); var instance = new Test() { @@ -326,7 +323,7 @@ public void Nullable_decimal_member_not_equal_to_null_inside_predicate() var result = compiledSys(instance); Asserts.IsTrue(result); - result = compiledFast(instance); + result = f(instance); Asserts.IsTrue(result); } diff --git a/test/FastExpressionCompiler.IssueTests/Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList.cs b/test/FastExpressionCompiler.IssueTests/Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList.cs index 372321f9..a2f7cd44 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList.cs @@ -17,19 +17,32 @@ namespace FastExpressionCompiler.LightExpression.IssueTests namespace FastExpressionCompiler.IssueTests #endif { - public class Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList : ITest { public int Run() { + Issue478_Test_diagnostics(); Test_struct_parameter_in_closure_of_the_nested_lambda(); Test_passing_struct_item_in_object_array_parameter(); Test_nullable_param_in_closure_of_the_nested_lambda(); Test_nullable_of_struct_and_struct_field_in_the_nested_lambda(); Test_original(); - return 5; + return 6; } + public void Issue478_Test_diagnostics() + { + System.Linq.Expressions.Expression>> expr = n => () => n + 1; + var e = expr.FromSysExpression(); + e.PrintExpression(); + e.PrintCSharp(); + + var f = e.CompileFast(flags: CompilerFlags.EnableDelegateDebugInfo); + var d = f.TryGetDebugInfo(); + d.PrintExpression(); + d.PrintCSharp(); + d.PrintIL(); + } public void Test_passing_struct_item_in_object_array_parameter() { @@ -62,7 +75,6 @@ public void Test_passing_struct_item_in_object_array_parameter() Asserts.AreEqual(42, y); } - public void Test_struct_parameter_in_closure_of_the_nested_lambda() { var incMethod = GetType().GetMethod(nameof(Inc), BindingFlags.Public | BindingFlags.Static); @@ -88,27 +100,23 @@ public void Test_struct_parameter_in_closure_of_the_nested_lambda() Asserts.IsNotNull(f); f.PrintIL(); - if (f.TryGetDebugClosureNestedLambda(0, out var d)) - { - d.PrintIL("nested"); - d.AssertOpCodes( - OpCodes.Ldarg_0, - OpCodes.Ldfld, // ArrayClosureWithNonPassedParams.NonPassedParams - OpCodes.Ldc_I4_0, - OpCodes.Ldelem_Ref, - OpCodes.Unbox_Any, // NotifyModel - OpCodes.Stloc_0, - OpCodes.Ldloca_S, // 0 - OpCodes.Call, // NotifyModel.get_Number1 - OpCodes.Ret - ); - } + var nested = f.EnumerateNestedLambdas().First(); + nested.AssertOpCodes( + OpCodes.Ldarg_0, + OpCodes.Ldfld, // ArrayClosureWithNonPassedParams.NonPassedParams + OpCodes.Ldc_I4_0, + OpCodes.Ldelem_Ref, + OpCodes.Unbox_Any, // NotifyModel + OpCodes.Stloc_0, + OpCodes.Ldloca_S, // 0 + OpCodes.Call, // NotifyModel.get_Number1 + OpCodes.Ret + ); var y = f(m); Asserts.AreEqual(43, y); } - public void Test_nullable_param_in_closure_of_the_nested_lambda() { var incMethod = GetType().GetMethod(nameof(Inc), BindingFlags.Public | BindingFlags.Static); @@ -138,7 +146,6 @@ public void Test_nullable_param_in_closure_of_the_nested_lambda() public static int Inc(Func f) => f() + 1; - public void Test_nullable_of_struct_and_struct_field_in_the_nested_lambda() { var incMethod = GetType().GetMethod(nameof(Inc), BindingFlags.Public | BindingFlags.Static); @@ -180,7 +187,6 @@ public void Test_nullable_of_struct_and_struct_field_in_the_nested_lambda() Asserts.AreEqual(43, y); } - public void Test_original() { // Expression>> e = @@ -246,9 +252,6 @@ public void Test_original() Asserts.IsNotNull(f); f.PrintIL(); - if (f.TryGetDebugClosureNestedLambdaOrConstant(out var item) && item is Delegate d) - d.PrintIL("nested"); - var y = f(container); Asserts.AreEqual(1, y.Count); } diff --git a/test/FastExpressionCompiler.IssueTests/Issue352_xxxAssign_does_not_work_with_MemberAccess.cs b/test/FastExpressionCompiler.IssueTests/Issue352_xxxAssign_does_not_work_with_MemberAccess.cs index e4fe4e59..ba340754 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue352_xxxAssign_does_not_work_with_MemberAccess.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue352_xxxAssign_does_not_work_with_MemberAccess.cs @@ -94,7 +94,7 @@ public void Check_ArrayAccess_Assign_InAction() fs(a1); Asserts.AreEqual(33, a1[2]); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -137,7 +137,7 @@ public void Check_ArrayAccess_Assign_ParameterByRef_InAction() fs(a1, ref b1); Asserts.AreEqual(33, a1[2]); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -175,7 +175,7 @@ public void Check_MultiArrayAccess_Assign_InAction() fs(a1); Asserts.AreEqual(33, a1[1, 2]); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -259,7 +259,7 @@ public void Check_IndexerAccess_Assign_InAction() fs(a1); Asserts.AreEqual(-33, a1.Elem); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -297,7 +297,7 @@ public void Check_Val_IndexerAccess_Assign_InAction() fs(a1); Asserts.AreEqual(9, a1.Elem); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarga_S, @@ -337,7 +337,7 @@ public void Check_Val_Ref_IndexerAccess_Assign_InAction() fs(ref a1); Asserts.AreEqual(-33, a1.Elem); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -574,7 +574,7 @@ public void Check_Ref_ArrayAccess_AddAssign_PlusOne() fs(ref a1); Asserts.AreEqual(10, a1[2]); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -621,7 +621,7 @@ public void Check_Val_IndexerAccess_AddAssign_PlusOne_InAction() fs(a1); Asserts.AreEqual(9, a1.Elem); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -670,7 +670,7 @@ public void Check_Val_Ref_IndexerAccess_AddAssign_PlusOne_InAction() fs(ref a1); Asserts.AreEqual(9, a1.Elem); // todo: @sys does not work, or is there bug in converting to the Expression? - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -730,7 +730,7 @@ public void Check_Val_Ref_NoIndexer_AddAssign_PlusOne() fs(ref a1); Asserts.AreEqual(-3, a1.Elem); // todo: @sys does not work, or is there bug in converting to the Expression? - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -876,7 +876,7 @@ public void Check_MemberAccess_AddAssign_StaticMember() fs(); Asserts.AreEqual(33, Box.StaticField); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldsfld, @@ -914,7 +914,7 @@ public void Check_MemberAccess_AddAssign_StaticProp() fs(); Asserts.AreEqual(33, Box.StaticProp); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Call, // Box.get_StaticProp @@ -954,7 +954,7 @@ public void Check_MemberAccess_AddAssign() fs(b1); Asserts.AreEqual(42, b1.Field); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1003,7 +1003,7 @@ public void Check_MemberAccess_AddAssign_NullablePlusNullable() Asserts.AreEqual(null, b1.NullableField); Asserts.AreEqual(42, b2.NullableField); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1070,7 +1070,7 @@ public void Check_ArrayAccess_AddAssign_NullablePlusNullable() Asserts.AreEqual(null, a1[2]); Asserts.AreEqual(42, a2[2]); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); // ff.AssertOpCodes( // OpCodes.Ldarg_1, @@ -1138,7 +1138,7 @@ public void Check_MemberAccess_AddAssign_NullablePlusNullable_Prop() Asserts.AreEqual(null, b1.NullableProp); Asserts.AreEqual(42, b2.NullableProp); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1247,7 +1247,7 @@ public void Check_MemberAccess_PreIncrementAssign() fs(b1); Asserts.AreEqual(10, b1.Field); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1289,7 +1289,7 @@ public void Check_MemberAccess_PlusOneAssign() fs(b1); Asserts.AreEqual(10, b1.Field); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1410,7 +1410,7 @@ public void Check_Ref_ValueType_MemberAccess_PreIncrementAssign() // Asserts.AreEqual(10, v1.Value); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations Asserts.AreEqual(9, v1.Field); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( @@ -1461,7 +1461,7 @@ public void Check_Ref_ValueType_MemberAccess_PreIncrementAssign_Nullable() Asserts.AreEqual(null, v1.NullableField); Asserts.AreEqual(9, v2.NullableField); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( @@ -1524,7 +1524,7 @@ public void Check_Ref_ValueType_MemberAccess_PreIncrementAssign_Nullable_Prop() Asserts.AreEqual(null, v1.NullableProp); Asserts.AreEqual(9, v2.NullableProp); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( @@ -1592,7 +1592,7 @@ public void Check_Ref_ValueType_MemberAccess_PreIncrementAssign_Nullable_Returni Asserts.AreEqual(9, v2.NullableField); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations Asserts.AreEqual(10, x2); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1663,7 +1663,7 @@ public void Check_Ref_ValueType_MemberAccess_PreIncrementAssign_Nullable_Returni Asserts.AreEqual(9, v2.NullableProp); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations Asserts.AreEqual(10, x2); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1733,7 +1733,7 @@ public void Check_Ref_ValueType_MemberAccess_PostIncrementAssign_Nullable_Return Asserts.AreEqual(9, v2.NullableField); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations Asserts.AreEqual(9, x2); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1796,7 +1796,7 @@ public void Check_Ref_ValueType_MemberAccess_PreIncrementAssign_Returning() Asserts.AreEqual(9, v1.Field); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations Asserts.AreEqual(10, x1); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1846,7 +1846,7 @@ public void Check_Ref_ValueType_MemberAccess_PostIncrementAssign_Returning() Asserts.AreEqual(9, v1.Field); // todo: @sys that System.Compile does not work with ref ValueType.Member Increment/Decrement operations Asserts.AreEqual(9, x1); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1901,7 +1901,7 @@ public void Check_MemberAccess_PreIncrementAssign_Nullable() Asserts.AreEqual(null, b1.NullableField); Asserts.AreEqual(42, b2.NullableField); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -1967,7 +1967,7 @@ public void Check_MemberAccess_PreIncrementAssign_Nullable_ReturningNullable() Asserts.AreEqual(42, b2.NullableField); Asserts.AreEqual(42, x2); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, diff --git a/test/FastExpressionCompiler.IssueTests/Issue353_NullReferenceException_when_calling_CompileFast_results.cs b/test/FastExpressionCompiler.IssueTests/Issue353_NullReferenceException_when_calling_CompileFast_results.cs index 0cea3858..a02d50d5 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue353_NullReferenceException_when_calling_CompileFast_results.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue353_NullReferenceException_when_calling_CompileFast_results.cs @@ -210,9 +210,6 @@ public void Test1_simplified() Asserts.IsNotNull(f); f.PrintIL(); - if (f.TryGetDebugClosureNestedLambdaOrConstant(out var item) && item is Delegate d) - d.PrintIL("sumFunc"); - var y = f(10); Asserts.AreEqual(1009, y); } diff --git a/test/FastExpressionCompiler.IssueTests/Issue365_Working_with_ref_return_values.cs b/test/FastExpressionCompiler.IssueTests/Issue365_Working_with_ref_return_values.cs index 13ba0e79..a391ac2a 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue365_Working_with_ref_return_values.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue365_Working_with_ref_return_values.cs @@ -46,7 +46,7 @@ public void Test_access_ref_returning_method_assigned_var_then_property() // var fs = e.CompileSys(); // todo: does not convert ref returning method calls, cause unable to find the property on the T& type - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.AssertOpCodes( OpCodes.Ldarg_1, OpCodes.Call, // ParamValue& GetParamValueByRef() @@ -89,7 +89,7 @@ public void Test_access_ref_returning_method_then_property() // var fs = e.CompileSys(); // todo: does not conver ref returning method calls, cause unable cannot find the property on the T& type - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.AssertOpCodes( OpCodes.Ldarg_1, OpCodes.Call, // ParamValue& GetParamValueByRef() diff --git a/test/FastExpressionCompiler.IssueTests/Issue374_CompileFast_does_not_work_with_HasFlag.cs b/test/FastExpressionCompiler.IssueTests/Issue374_CompileFast_does_not_work_with_HasFlag.cs index 8cf775db..2cf6951c 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue374_CompileFast_does_not_work_with_HasFlag.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue374_CompileFast_does_not_work_with_HasFlag.cs @@ -37,7 +37,7 @@ public void Test1() Asserts.IsTrue(fs(b1)); Asserts.IsFalse(fs(b2)); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, diff --git a/test/FastExpressionCompiler.IssueTests/Issue414_Incorrect_il_when_passing_by_ref_value.cs b/test/FastExpressionCompiler.IssueTests/Issue414_Incorrect_il_when_passing_by_ref_value.cs index 195b1b8b..e7ec22e1 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue414_Incorrect_il_when_passing_by_ref_value.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue414_Incorrect_il_when_passing_by_ref_value.cs @@ -51,7 +51,7 @@ public void Issue414_ReturnRefParameter() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -86,7 +86,7 @@ public void Issue414_PassByRefParameter() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -127,7 +127,7 @@ public void Issue413_ParameterStructIndexer() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -167,7 +167,7 @@ public void Issue413_VariableStructIndexer() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -204,7 +204,7 @@ public void Issue414_PassByRefVariable() expr.PrintCSharp(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -234,7 +234,7 @@ public void Issue415_ReturnRefParameterByRef() // var @cs = (MyDelegateByRef)((ref int int__32854180) => //Int32 // ref int__32854180); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -261,7 +261,7 @@ public void Issue415_ReturnRefParameterByRef_ReturnRefCall() // var @cs = (MyDelegateByRef)((ref int int__32854180) => //Int32 // ref Issue414_Incorrect_il_when_passing_by_ref_value.ReturnRef(ref int__32854180)); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -287,7 +287,7 @@ public void Issue415_ReturnRefParameter_ReturnRefCall() // var @cs = (MyDelegate)((ref int int__32854180) => //Int32 // ref Issue414_Incorrect_il_when_passing_by_ref_value.ReturnRef(ref int__32854180)); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); // ff.AssertOpCodes( diff --git a/test/FastExpressionCompiler.IssueTests/Issue418_Wrong_output_when_comparing_NaN_value.cs b/test/FastExpressionCompiler.IssueTests/Issue418_Wrong_output_when_comparing_NaN_value.cs index 4d9fe757..898671c9 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue418_Wrong_output_when_comparing_NaN_value.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue418_Wrong_output_when_comparing_NaN_value.cs @@ -41,7 +41,7 @@ public void Original_case() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -68,7 +68,7 @@ public void Case_with_int() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -95,7 +95,7 @@ public void Case_with_Uint() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -127,7 +127,7 @@ public void Case_with_less_then() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -157,7 +157,7 @@ public void Case_with_one() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -189,7 +189,7 @@ public void Case_with_Minus_one() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -221,7 +221,7 @@ public void Case_with_lte_instead_of_gte() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( diff --git a/test/FastExpressionCompiler.IssueTests/Issue422_InvalidProgramException_when_having_TryCatch_Default_in_Catch.cs b/test/FastExpressionCompiler.IssueTests/Issue422_InvalidProgramException_when_having_TryCatch_Default_in_Catch.cs index 38cd1230..07287088 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue422_InvalidProgramException_when_having_TryCatch_Default_in_Catch.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue422_InvalidProgramException_when_having_TryCatch_Default_in_Catch.cs @@ -52,7 +52,7 @@ public void Original_case() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( diff --git a/test/FastExpressionCompiler.IssueTests/Issue423_Converting_a_uint_to_a_float_gives_the_wrong_result.cs b/test/FastExpressionCompiler.IssueTests/Issue423_Converting_a_uint_to_a_float_gives_the_wrong_result.cs index 68053db5..7bdee164 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue423_Converting_a_uint_to_a_float_gives_the_wrong_result.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue423_Converting_a_uint_to_a_float_gives_the_wrong_result.cs @@ -30,7 +30,7 @@ public void Original_case() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( diff --git a/test/FastExpressionCompiler.IssueTests/Issue426_Directly_passing_a_method_result_to_another_method_by_ref_fails_with_InvalidProgramException.cs b/test/FastExpressionCompiler.IssueTests/Issue426_Directly_passing_a_method_result_to_another_method_by_ref_fails_with_InvalidProgramException.cs index b98406d6..a7305958 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue426_Directly_passing_a_method_result_to_another_method_by_ref_fails_with_InvalidProgramException.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue426_Directly_passing_a_method_result_to_another_method_by_ref_fails_with_InvalidProgramException.cs @@ -52,7 +52,7 @@ public void Original_case() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -80,7 +80,7 @@ public void Class_ref_param_case() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( @@ -108,7 +108,7 @@ public void Two_ref_value_params_case() var fs = expr.CompileSys(); fs.PrintIL(); - var ff = expr.CompileFast(true, CompilerFlags.ThrowOnNotSupportedExpression); + var ff = expr.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo | CompilerFlags.ThrowOnNotSupportedExpression); ff.PrintIL(); ff.AssertOpCodes( diff --git a/test/FastExpressionCompiler.IssueTests/Issue461_InvalidProgramException_when_null_checking_type_by_ref.cs b/test/FastExpressionCompiler.IssueTests/Issue461_InvalidProgramException_when_null_checking_type_by_ref.cs index 790462e4..27e3413b 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue461_InvalidProgramException_when_null_checking_type_by_ref.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue461_InvalidProgramException_when_null_checking_type_by_ref.cs @@ -44,7 +44,7 @@ public void Original_case() Asserts.IsTrue(fs(null)); Asserts.IsFalse(fs(new Target())); - var ff = expr.CompileFast(false); + var ff = expr.CompileFast(false, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); Asserts.IsTrue(ff(null)); Asserts.IsFalse(ff(new Target())); @@ -73,7 +73,7 @@ public void Original_case_null_on_the_right() Asserts.IsFalse(fs(null)); Asserts.IsTrue(fs(new Target())); - var ff = expr.CompileFast(false); + var ff = expr.CompileFast(false, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); Asserts.IsFalse(ff(null)); Asserts.IsTrue(ff(new Target())); @@ -109,7 +109,7 @@ public void Case_equal_nullable_and_object_null() Asserts.IsTrue(fs(null)); Asserts.IsFalse(fs(new XX())); - var ff = expr.CompileFast(false); + var ff = expr.CompileFast(false, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); Asserts.IsTrue(ff(null)); Asserts.IsFalse(ff(new XX())); @@ -138,7 +138,7 @@ public void Case_equal_nullable_and_nullable_null_on_the_left() Asserts.IsFalse(fs(null)); Asserts.IsTrue(fs(new XX())); - var ff = expr.CompileFast(false); + var ff = expr.CompileFast(false, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); Asserts.IsFalse(ff(null)); Asserts.IsTrue(ff(new XX())); @@ -164,7 +164,7 @@ public void Case_not_equal_nullable_decimal() fs.PrintIL(); Asserts.IsTrue(fs(1.142m)); - var ff = expr.CompileFast(false); + var ff = expr.CompileFast(false, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); Asserts.IsTrue(ff(1.142m)); 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 index c4f5d676..c9f8aff7 100644 --- 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 @@ -1,7 +1,6 @@ using System; #if LIGHT_EXPRESSION -using ExpressionType = System.Linq.Expressions.ExpressionType; using static FastExpressionCompiler.LightExpression.Expression; namespace FastExpressionCompiler.LightExpression.IssueTests; #else diff --git a/test/FastExpressionCompiler.IssueTests/Issue475_Reuse_DynamicMethod_if_possible.cs b/test/FastExpressionCompiler.IssueTests/Issue475_Reuse_DynamicMethod_if_possible.cs index 6de2265f..ba635bef 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue475_Reuse_DynamicMethod_if_possible.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue475_Reuse_DynamicMethod_if_possible.cs @@ -104,22 +104,18 @@ public int GetTokenFor(byte[] signature) private readonly int m_methodSigToken; public DynamicILGenerator( - DynamicMethod method, - byte[] methodSignature, + DynamicMethod method, + byte[] methodSignature, int streamSize) { m_scope = new DynamicScope(); m_methodSigToken = m_scope.GetTokenFor(methodSignature); m_ScopeTree = new ScopeTree(); - // clear ilstream bytes to reuse the buffer m_ILStream = new byte[Math.Max(size, DefaultSize)]; m_localSignature = SignatureHelper.GetLocalVarSigHelper((method as RuntimeMethodBuilder)?.GetTypeBuilder().Module); - - // set to the new DynamicMethod - m_methodBuilder = method; - + m_methodBuilder = method; // set to the new DynamicMethod } */ public void TryToReuseIlGenerator(TestContext t) @@ -224,7 +220,7 @@ public void TryToReuseIlGenerator_for_any_signature(TestContext t) public static object CreateDynamicILGenerator() { - var paramTypes = ExpressionCompiler.RentOrNewClosureTypeToParamTypes(typeof(int), typeof(int).MakeByRefType()); + var paramTypes = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(ExpressionCompiler.ArrayClosure), typeof(int), typeof(int).MakeByRefType()); var dynMethod = new DynamicMethod(string.Empty, typeof(void), paramTypes, @@ -242,20 +238,20 @@ public static object CreateDynamicILGenerator() il.Emit(OpCodes.Ret); var func = (Action2ndByRef)dynMethod.CreateDelegate(typeof(Action2ndByRef), ExpressionCompiler.EmptyArrayClosure); - ExpressionCompiler.FreeClosureTypeAndParamTypes(paramTypes); + ExpressionCompiler.FreePooledParamTypes(paramTypes); return func; } public static object PoolDynamicILGenerator() { - var paramTypes = ExpressionCompiler.RentOrNewClosureTypeToParamTypes(typeof(int), typeof(int).MakeByRefType()); + var paramTypes = ExpressionCompiler.RentPooledOrNewParamTypes(typeof(ExpressionCompiler.ArrayClosure), typeof(int), typeof(int).MakeByRefType()); var dynMethod = new DynamicMethod(string.Empty, typeof(void), paramTypes, typeof(ExpressionCompiler.ArrayClosure), true); - var il = ExpressionCompiler.PoolOrNewILGenerator(dynMethod, typeof(void), paramTypes); + var il = DynamicMethodHacks.RentPooledOrNewILGenerator(dynMethod, typeof(void), paramTypes); il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Ldarg_2); @@ -267,15 +263,18 @@ public static object PoolDynamicILGenerator() var func = (Action2ndByRef)dynMethod.CreateDelegate(typeof(Action2ndByRef), ExpressionCompiler.EmptyArrayClosure); - ExpressionCompiler.FreeILGenerator(dynMethod, il); + DynamicMethodHacks.FreePooledILGenerator(dynMethod, il); - ExpressionCompiler.FreeClosureTypeAndParamTypes(paramTypes); + ExpressionCompiler.FreePooledParamTypes(paramTypes); return func; } internal static MethodInfo GetMethodSigHelperMethod = typeof(SignatureHelper) - .GetMethod("GetMethodSigHelper", BindingFlags.Static | BindingFlags.Public, null, [typeof(Module), typeof(Type), typeof(Type[])], null); + .GetMethod(nameof(SignatureHelper.GetMethodSigHelper), BindingFlags.Static | BindingFlags.Public, null, [typeof(Module), typeof(Type), typeof(Type[])], null); + + internal static MethodInfo GetLocalVarSigHelperMethod = typeof(SignatureHelper) + .GetMethod(nameof(SignatureHelper.GetLocalVarSigHelper), BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null); internal static MethodInfo GetSignatureMethod = typeof(SignatureHelper) .GetMethod("GetSignature", BindingFlags.Instance | BindingFlags.NonPublic, null, [typeof(bool)], null); @@ -302,7 +301,13 @@ internal static Action ReuseDynamicILG { var fieldName = field.Name; if (fieldName == "m_localSignature") // todo: skip, let's see how it works + { + // m_localSignature = SignatureHelper.GetLocalVarSigHelper((method as RuntimeMethodBuilder)?.GetTypeBuilder().Module); + il.Demit(OpCodes.Ldarg_2); + il.Demit(OpCodes.Call, GetLocalVarSigHelperMethod); + il.Demit(OpCodes.Stfld, field); continue; + } // m_ScopeTree = new ScopeTree(); if (fieldName == "m_ScopeTree") @@ -326,13 +331,20 @@ internal static Action ReuseDynamicILG // let's clear it and reuse the buffer if (fieldName == "m_ILStream") { + // New Array. todo: @perf pool the array il.Demit(OpCodes.Ldarg_2); - il.Demit(OpCodes.Ldfld, field); - var ilStreamVar = ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, typeof(byte[])); - il.Demit(OpCodes.Ldc_I4_0); - ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, ilStreamVar); - il.Demit(OpCodes.Ldlen); - il.Demit(OpCodes.Call, ArrayClearMethod); + il.Demit(OpCodes.Ldc_I4_S, 64); + il.Emit(OpCodes.Newarr, typeof(byte)); + il.Demit(OpCodes.Stfld, field); + + // Clear Array + // il.Demit(OpCodes.Ldarg_2); + // il.Demit(OpCodes.Ldfld, field); + // var ilStreamVar = ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, typeof(byte[])); + // il.Demit(OpCodes.Ldc_I4_0); + // ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, ilStreamVar); + // il.Demit(OpCodes.Ldlen); + // il.Demit(OpCodes.Call, ArrayClearMethod); continue; } @@ -341,20 +353,24 @@ internal static Action ReuseDynamicILG il.Demit(OpCodes.Stfld, field); } - il.Emit(OpCodes.Ldarg_2); - il.Emit(OpCodes.Ldfld, DynamicILGeneratorScopeField); - var scopeVar = ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, DynamicILGeneratorScopeField.FieldType); - il.Emit(OpCodes.Ldfld, DynamicScopeTokensField); - il.Emit(OpCodes.Dup); + // il.Emit(OpCodes.Ldarg_2); + // var scope = new DynamicScope(); + il.Emit(OpCodes.Newobj, DynamicILGeneratorScopeField.FieldType.GetConstructor(Type.EmptyTypes)); + var scopeVar = ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, DynamicILGeneratorScopeField.FieldType); - // reset its List._size to 1, keep the 0th item - il.Emit(OpCodes.Ldc_I4_1); - il.Emit(OpCodes.Stfld, ListOfObjectsSize); + // il.Emit(OpCodes.Ldfld, DynamicILGeneratorScopeField); + // var scopeVar = ExpressionCompiler.EmittingVisitor.EmitStoreAndLoadLocalVariable(il, DynamicILGeneratorScopeField.FieldType); + // il.Emit(OpCodes.Ldfld, DynamicScopeTokensField); + // il.Emit(OpCodes.Dup); - // set the 0th item to null - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Call, DynamicScopeTokensItem.SetMethod); + // // reset its List._size to 1, keep the 0th item + // il.Emit(OpCodes.Ldc_I4_1); + // il.Emit(OpCodes.Stfld, ListOfObjectsSize); + + // // set the 0th item to null + // il.Emit(OpCodes.Ldc_I4_0); + // il.Emit(OpCodes.Ldnull); + // il.Emit(OpCodes.Call, DynamicScopeTokensItem.SetMethod); // byte[] methodSignature = // SignatureHelper.GetMethodSigHelper(Module? mod, Type? returnType, Type[]? parameterTypes).GetSignature(true); @@ -366,14 +382,19 @@ internal static Action ReuseDynamicILG il.Emit(OpCodes.Call, GetSignatureMethod); var signatureBytesVar = ExpressionCompiler.EmittingVisitor.EmitStoreLocalVariable(il, typeof(byte[])); // todo: perf could reuse byte[]? - // m_methodSigToken = m_scope.GetTokenFor(methodSignature); + // m_methodSigToken = scope.GetTokenFor(methodSignature); il.Emit(OpCodes.Ldarg_2); ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, signatureBytesVar); il.Emit(OpCodes.Call, GetTokenForMethod); il.Emit(OpCodes.Stfld, MethodSigTokenField); - // store the reused ILGenerator to + // m_scope = scope; + il.Emit(OpCodes.Ldarg_2); + ExpressionCompiler.EmittingVisitor.EmitLoadLocalVariable(il, scopeVar); + il.Emit(OpCodes.Stfld, DynamicILGeneratorScopeField); + + // store the reused ILGenerator to il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_2); il.Emit(OpCodes.Stfld, IlGeneratorField); @@ -607,7 +628,7 @@ void TryToReuseTheDynamicMethod_FailedWithInternalClrError_BecauseTheResultDeleg // d.StoreDynamicMethod(this); // return d; - // todo: @wip Calling this second time in `-c:Release` will produce `Fatal error. Internal CLR error. (0x80131506) ## :-( Failed with ERROR: -1073741819` (the -c:Debug is working fine) + // note: Calling this second time in `-c:Release` will produce `Fatal error. Internal CLR error. (0x80131506) ## :-( Failed with ERROR: -1073741819` (the -c:Debug is working fine) // runtimeMethodHandle = GetMethodDescriptorMethod.Invoke(dynMethod, null); // var func2 = (Func)CreateDelegateMethod.Invoke(null, [typeof(Func), ExpressionCompiler.EmptyArrayClosure, runtimeMethodHandle]); diff --git a/test/FastExpressionCompiler.IssueTests/Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary.cs b/test/FastExpressionCompiler.IssueTests/Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary.cs new file mode 100644 index 00000000..dc2d5646 --- /dev/null +++ b/test/FastExpressionCompiler.IssueTests/Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Concurrent; + +#if LIGHT_EXPRESSION +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 Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary : ITestX +{ + public void Run(TestRun t) + { + Original_case(t); + } + + public class Record + { + public DateTimeOffset? Timestamp { get; set; } + } + + public void Original_case(TestContext t) + { + System.Linq.Expressions.Expression> sExpr = record => record.Timestamp != null; + var expr = sExpr.FromSysExpression(); + + expr.PrintCSharp(); + + var fs = expr.CompileSys(); + fs.PrintIL(); + + var currTime = DateTimeOffset.UtcNow; + var notNull = new Record() { Timestamp = currTime }; + var aNull = new Record() { Timestamp = null }; + + t.IsTrue(fs(notNull)); + t.IsFalse(fs(aNull)); + + var ff = expr.CompileFast(false); + ff.PrintIL(); + + t.IsTrue(ff(notNull)); + t.IsFalse(ff(aNull)); + } +} \ No newline at end of file diff --git a/test/FastExpressionCompiler.IssueTests/Issue55_CompileFast_crash_with_ref_parameter.cs b/test/FastExpressionCompiler.IssueTests/Issue55_CompileFast_crash_with_ref_parameter.cs index 7dd9a143..8968228f 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue55_CompileFast_crash_with_ref_parameter.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue55_CompileFast_crash_with_ref_parameter.cs @@ -20,6 +20,7 @@ public class Issue55_CompileFast_crash_with_ref_parameter : ITest { public int Run() { + RefMethodCallingRefMethodWithLocal_OfStruct(); RefMethodCallingRefMethodWithLocal_OfString(); BlockWithNonRefStatementLast(); @@ -29,7 +30,6 @@ public int Run() RefMethodCallingRefMethod(); RefMethodCallingRefMethodCustomStruct(); RefMethodCallingRefMethodWithLocal_OfInt(); - RefMethodCallingRefMethodWithLocal_OfStruct(); OutRefMethodCallingRefMethodWithLocal(); RefMethodCallingRefMethodWithLocalReturnLocalCalled(); VariableVariableRefVariableRefParameterReturn(); @@ -246,7 +246,7 @@ static void SetIntoLocalVariableAndCallOtherRef(ref string localByRef) fs(ref a); Asserts.AreEqual("0", a); - var ff = lambda.CompileFast(true); + var ff = lambda.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.AssertOpCodes(expectedIL); a = "0"; ff(ref a); @@ -287,14 +287,14 @@ void SetIntoLocalVariableAndCallOtherRef(ref RecVal localByRef) OpCodes.Ret }; - var compiledS = lambda.CompileSys(); - compiledS.AssertOpCodes(expectedIL); + var fs = lambda.CompileSys(); + fs.AssertOpCodes(expectedIL); - var compiledB = lambda.CompileFast(true); - compiledB.AssertOpCodes(expectedIL); + var ff = lambda.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); + ff.AssertOpCodes(expectedIL); var exampleB = new RecVal("0"); - compiledB(ref exampleB); + ff(ref exampleB); Asserts.AreEqual("0", exampleB.S); ActionRef direct = SetIntoLocalVariableAndCallOtherRef; @@ -467,15 +467,15 @@ int SetIntoLocalVariableAndCallOtherRef(ref int localByRef) ), objRef); - var compiledB = lambda.CompileFast>(true); - compiledB.Method.AssertOpCodes( + var ff = lambda.CompileFast>(true, CompilerFlags.EnableDelegateDebugInfo); + ff.AssertOpCodes( OpCodes.Ldarg_1, OpCodes.Ldarg_1, OpCodes.Call, OpCodes.Ldc_I4_M1, OpCodes.Ret); - LocalAssert(compiledB); + LocalAssert(ff); FuncRef direct = SetIntoLocalVariableAndCallOtherRef; LocalAssert(direct); @@ -772,7 +772,7 @@ public void NonGenericSetterFieldShould_not_crash() var s = lambda.CompileSys(); s.PrintIL(); - var f = lambda.CompileFast(true); + var f = lambda.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.PrintIL(); f.AssertOpCodes( OpCodes.Ldarg_1, @@ -799,7 +799,7 @@ public void GenericRefStructFieldShould_not_crash() var e = Lambda>(body, objRef, objVal); - var fs = e.CompileFast>(true); + var fs = e.CompileFast>(true, CompilerFlags.EnableDelegateDebugInfo); fs.AssertOpCodes( OpCodes.Ldarg_1, @@ -812,7 +812,6 @@ public void GenericRefStructFieldShould_not_crash() Asserts.AreEqual(7, x1.IntField); } - public void RefAssign() { void AddSet(ref double byRef) @@ -830,12 +829,12 @@ void LocalAssert(ActionRef invoke) Asserts.AreEqual(8.0, exampleA); } - var compiledA = lambda.CompileSys(); - compiledA.PrintIL(); - LocalAssert(compiledA); + var fs = lambda.CompileSys(); + fs.PrintIL(); + LocalAssert(fs); - var compiledB = lambda.CompileFast(true); - compiledB.AssertOpCodes( + var ff = lambda.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); + ff.AssertOpCodes( OpCodes.Ldarg_1, OpCodes.Ldarg_1, OpCodes.Ldind_R8, @@ -844,13 +843,12 @@ void LocalAssert(ActionRef invoke) OpCodes.Stind_R8, OpCodes.Ret); - LocalAssert(compiledB); + LocalAssert(ff); ActionRef direct = AddSet; LocalAssert(direct); } - public void RefAssignCustomStruct() { void AddSet(ref BigInteger byRef) diff --git a/test/FastExpressionCompiler.IssueTests/Issue78_blocks_with_constant_return.cs b/test/FastExpressionCompiler.IssueTests/Issue78_blocks_with_constant_return.cs index 3d80d19f..f908add6 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue78_blocks_with_constant_return.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue78_blocks_with_constant_return.cs @@ -41,7 +41,7 @@ public void MultipleConstantReturnsAreRemoved() { var ret = Block(Constant(7), Constant(7), Constant(7)); var lambda = Lambda>(ret); - var fastCompiled = lambda.CompileFast>(true); + var fastCompiled = lambda.CompileFast>(true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(fastCompiled); Asserts.AreEqual(7, fastCompiled()); diff --git a/test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs b/test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs index 6b2408e2..1e1331d1 100644 --- a/test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs +++ b/test/FastExpressionCompiler.IssueTests/Issue91_Issue95_Tests.cs @@ -47,7 +47,7 @@ public void NullComparisonTest() Constant(0)); var lambda = Lambda>(condition, pParam); - var convert1 = lambda.CompileFast(true); + var convert1 = lambda.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); Asserts.IsNotNull(convert1); Asserts.AreEqual(1, convert1("aaa")); diff --git a/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs b/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs index 2e673423..325c7245 100644 --- a/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs +++ b/test/FastExpressionCompiler.LightExpression.IssueTests/Issue346_Is_it_possible_to_implement_ref_local_variables.cs @@ -50,7 +50,7 @@ public void Check_assignment_to_by_ref_int_parameter_PlusOne() s(ref x); Asserts.AreEqual(2, x); - var f = e.CompileFast(true); + var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.PrintIL(); f.AssertOpCodes( OpCodes.Ldarg_1, @@ -88,7 +88,7 @@ public void Check_assignment_to_by_ref_int_parameter_PostIncrement_Void() s(ref x); Asserts.AreEqual(2, x); - var f = e.CompileFast(true); + var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.PrintIL(); f.AssertOpCodes( OpCodes.Ldarg_1, @@ -146,9 +146,9 @@ public void Check_assignment_to_by_ref_float_parameter_PostIncrement_Returning() Asserts.AreEqual(2.0f, x); Asserts.AreEqual(1.0f, y); - var f = e.CompileFast(true); + var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.PrintIL(); - // todo: @wip the IL codes is the same for the System Compile but the expected values are different + // todo: @wip the IL codes is the same for the System.Compile but the expected values are different // f.AssertOpCodes( // IL_0000: ldarg.1 // IL_0001: ldarg.1 @@ -190,7 +190,7 @@ public void Check_assignment_to_by_ref_int_parameter_PostIncrement_Returning() Asserts.AreEqual(2, x); Asserts.AreEqual(1, y); - var f = e.CompileFast(true); + var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.PrintIL(); f.AssertOpCodes( OpCodes.Ldarg_1, @@ -238,7 +238,7 @@ public void Get_array_element_ref_and_increment_it() @cs(array); Asserts.AreEqual(43, array[0]); - var fs = e.CompileFast(true); + var fs = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); fs.PrintIL(); fs.AssertOpCodes( OpCodes.Ldarg_1, @@ -292,7 +292,7 @@ public void Get_array_element_ref_and_member_change_and_increment_it() var vs = @cs(); Asserts.AreEqual(12, vs[0].x); - var fs = e.CompileFast(true); + var fs = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); fs.PrintIL(); fs.AssertOpCodes( OpCodes.Ldc_I4_S,// 10 @@ -353,7 +353,7 @@ public void Get_array_element_ref_and_member_change_and_Post_increment_it() Asserts.AreEqual(0, x); Asserts.AreEqual(1, a[9].x); - var fs = e.CompileFast(true); + var fs = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); fs.PrintIL(); fs.AssertOpCodes( OpCodes.Ldarg_1, @@ -416,7 +416,7 @@ public void Get_array_element_ref_and_member_change_and_Pre_increment_it() Asserts.AreEqual(1, x); Asserts.AreEqual(1, a[9].x); - var fs = e.CompileFast(true); + var fs = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); fs.PrintIL(); fs.AssertOpCodes( OpCodes.Ldarg_1, @@ -481,7 +481,7 @@ public void Get_array_element_ref_and_member_change_and_increment_it_then_method var vs = @cs(); Asserts.AreEqual(53, vs[0].x); - var fs = e.CompileFast(true); + var fs = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); fs.PrintIL(); fs.AssertOpCodes( OpCodes.Ldc_I4_S,// 10 @@ -584,7 +584,7 @@ public void Real_world_test_ref_array_element() var a = @cs(); Asserts.AreEqual(100, a.Length); - var f = e.CompileFast(true); + var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); f.PrintIL(); f.AssertOpCodes( OpCodes.Ldc_I4_S,// 100 diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/NestedLambdasSharedToExpressionCodeStringTest.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/NestedLambdasSharedToExpressionCodeStringTest.cs index d2bbda04..47fb93cc 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/NestedLambdasSharedToExpressionCodeStringTest.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/NestedLambdasSharedToExpressionCodeStringTest.cs @@ -10,11 +10,25 @@ public class NestedLambdasSharedToExpressionCodeStringTest : ITest { public int Run() { + Issue478_Debug_info_should_be_included_into_nested_lambdas(); Should_output_a_valid_expression_code(); Test_the_output_expression_code(); - return 2; + return 3; } + public void Issue478_Debug_info_should_be_included_into_nested_lambdas() + { + var e = CreateExpression(); + + var f = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); + + var di = f.TryGetDebugInfo(); + di.PrintCSharp(); + + di.PrintIL(); + + Asserts.IsNotNull(f); + } public void Should_output_a_valid_expression_code() { diff --git a/test/FastExpressionCompiler.TestsRunner/Program.cs b/test/FastExpressionCompiler.TestsRunner/Program.cs index d7188b01..ec6020aa 100644 --- a/test/FastExpressionCompiler.TestsRunner/Program.cs +++ b/test/FastExpressionCompiler.TestsRunner/Program.cs @@ -11,39 +11,31 @@ public class Program { public static void Main() { -#if NET8_0_OR_GREATER - var ts = new TestRun(); - ts.Run(new Issue475_Reuse_DynamicMethod_if_possible()); -#endif - - var t = new LightExpression.TestRun(); - - 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()); + // ILGeneratorTools.DisableILGeneratorPooling = true; + // LightExpression.ILGeneratorTools.DisableILGeneratorPooling = true; - // new Issue55_CompileFast_crash_with_ref_parameter().Run(); + new LightExpression.IssueTests.Issue347_InvalidProgramException_on_compiling_an_expression_that_returns_a_record_which_implements_IList().Run(); - // new LightExpression.UnitTests.ConstantAndConversionTests().Run(); - - // new LightExpression.IssueTests.Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run(); - // new Issue341_Equality_comparison_between_nullable_and_null_inside_Any_produces_incorrect_compiled_expression().Run(); - // new LightExpression.IssueTests.Issue460_ArgumentException_when_converting_from_object_to_type_with_explicit_operator().Run(); - // new LightExpression.IssueTests.Issue458_Support_TryFault().Run(); - // new LightExpression.IssueTests.Issue451_Operator_implicit_explicit_produces_InvalidProgram().Run(); + // new LightExpression.UnitTests.NestedLambdasSharedToExpressionCodeStringTest().Run(); + // new LightExpression.IssueTests.Issue274_Failing_Expressions_in_Linq2DB().Run(); + // new Issue316_in_parameter().Run(); // new LightExpression.IssueTests.Issue55_CompileFast_crash_with_ref_parameter().Run(); + // new LightExpression.IssueTests.Issue341_Equality_comparison_between_nullable_and_null_inside_Any_produces_incorrect_compiled_expression().Run(); + // new Issue441_Fails_to_pass_Constant_as_call_parameter_by_reference().Run(); + // new Issue461_InvalidProgramException_when_null_checking_type_by_ref().Run(); + + var t = new LightExpression.TestRun(LightExpression.TestFlags.RethrowException); - // new Issue357_Invalid_program_exception().Run(); - // new ValueTypeTests().Run(); - // new LightExpression.IssueTestsArithmeticOperationsTests().Run(); + t.Run(new LightExpression.IssueTests.Issue476_System_ExecutionEngineException_with_nullables_on_repeated_calls_to_ConcurrentDictionary()); - // new LightExpression.IssueTests.Issue183_NullableDecimal().Run(); - // new LightExpression.IssueTests.Issue159_NumericConversions().Run(); - // new AssignTests().Run(); + 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 LightExpression.IssueTests.Issue449_MemberInit_produces_InvalidProgram().Run(); - // new LightExpression.IssueTests.Issue437_Shared_variables_with_nested_lambdas_returning_incorrect_values().Run(); - // new LightExpression.IssueTests.Issue353_NullReferenceException_when_calling_CompileFast_results().Run(); +#if NET8_0_OR_GREATER + var ts = new TestRun(); + ts.Run(new Issue475_Reuse_DynamicMethod_if_possible()); +#endif RunAllTests(); } diff --git a/test/FastExpressionCompiler.UnitTests/AssignTests.cs b/test/FastExpressionCompiler.UnitTests/AssignTests.cs index 0c6b2cff..0056ffd6 100644 --- a/test/FastExpressionCompiler.UnitTests/AssignTests.cs +++ b/test/FastExpressionCompiler.UnitTests/AssignTests.cs @@ -380,7 +380,6 @@ public void Array_multi_dimensional_index_assign_value_type_block() // todo: @wip #431 generates different names for the unnamed variables which is not comparable Asserts.AreEqual(expr.ToCSharpString(), restoredExpr.ToCSharpString()); #endif - Asserts.IsNotNull(fs); Asserts.AreEqual(5, fs()); } diff --git a/test/FastExpressionCompiler.UnitTests/ConvertOperatorsTests.cs b/test/FastExpressionCompiler.UnitTests/ConvertOperatorsTests.cs index a798deda..2548aee0 100644 --- a/test/FastExpressionCompiler.UnitTests/ConvertOperatorsTests.cs +++ b/test/FastExpressionCompiler.UnitTests/ConvertOperatorsTests.cs @@ -79,7 +79,7 @@ public void Convert_Func_to_Custom_delegate_should_work() var getString = @cs(() => "hey"); Asserts.AreEqual("hey", getString()); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1, @@ -103,7 +103,7 @@ public void Convert_Func_to_Custom_delegate_should_work() var getString = @cs(() => "hey"); Asserts.AreEqual("hey", getString()); - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); #if NET6_0_OR_GREATER ff.AssertOpCodes( diff --git a/test/FastExpressionCompiler.UnitTests/UnaryExpressionTests.cs b/test/FastExpressionCompiler.UnitTests/UnaryExpressionTests.cs index a470ca81..484f3d02 100644 --- a/test/FastExpressionCompiler.UnitTests/UnaryExpressionTests.cs +++ b/test/FastExpressionCompiler.UnitTests/UnaryExpressionTests.cs @@ -377,7 +377,7 @@ public void ArrayOfStructParameter_MemberPostDecrementAssign_works() Asserts.AreEqual(33, fs(arr, 1)); Asserts.AreEqual(33 /*should be 32*/, arr[1].N); // todo: @sys Compile is wrong and evaluates to 33 - var ff = e.CompileFast(true); + var ff = e.CompileFast(true, CompilerFlags.EnableDelegateDebugInfo); ff.PrintIL(); ff.AssertOpCodes( OpCodes.Ldarg_1,